Merge branch 'stable-5.3' into stable-5.4

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

Change-Id: I3d8eb2fa721e1a791db47a2342acc690ced01715
diff --git a/.gitignore b/.gitignore
index 3679a33..553ecac 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,9 @@
 /.project
 /target
-.DS_Store
 infer-out
 bazel-*
-*~
+
+# Do not add editor- and OS-specific files like *~ (Emacs) and .DS_Store
+# (macOS). Instead, add them to $XDG_CONFIG_HOME/git/ignore
+# (~/.config/git/ignore if $XDG_CONFIG_HOME is not set) or
+# $GIT_DIR/info/exclude. See "git help gitignore" for details.
diff --git a/WORKSPACE b/WORKSPACE
index 8e6b864..4fa9968 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -89,15 +89,15 @@
 )
 
 maven_jar(
-    name = "sshd-core",
-    artifact = "org.apache.sshd:sshd-core:2.0.0",
-    sha1 = "f4275079a2463cfd2bf1548a80e1683288a8e86b",
+    name = "sshd-osgi",
+    artifact = "org.apache.sshd:sshd-osgi:2.2.0",
+    sha1 = "a45d48cb53678e699816e8e054e55fa33f5a4558",
 )
 
 maven_jar(
     name = "sshd-sftp",
-    artifact = "org.apache.sshd:sshd-sftp:2.0.0",
-    sha1 = "a12d64dc2d5d23271a4dc58075e55f9c64a68494",
+    artifact = "org.apache.sshd:sshd-sftp:2.2.0",
+    sha1 = "3d011e00adf38e49bb8711a9dd762fe908a2170c",
 )
 
 maven_jar(
@@ -242,25 +242,25 @@
     src_sha1 = "94e89a8c9f82e38555e95b9f7f58344a247e862c",
 )
 
-BOUNCYCASTLE_VER = "1.60"
+BOUNCYCASTLE_VER = "1.61"
 
 maven_jar(
-    name = "bcpg-jdk15on",
+    name = "bcpg",
     artifact = "org.bouncycastle:bcpg-jdk15on:" + BOUNCYCASTLE_VER,
-    sha1 = "13c7a199c484127daad298996e95818478431a2c",
-    src_sha1 = "edcd9e86d95e39b4da39bb295efd93bc4f56266e",
+    sha1 = "422656435514ab8a28752b117d5d2646660a0ace",
+    src_sha1 = "836da34e11114cbce8fa99f54175f8f3278d1cce",
 )
 
 maven_jar(
-    name = "bcprov-jdk15on",
+    name = "bcprov",
     artifact = "org.bouncycastle:bcprov-jdk15on:" + BOUNCYCASTLE_VER,
-    sha1 = "bd47ad3bd14b8e82595c7adaa143501e60842a84",
-    src_sha1 = "7c57a4d13fe53d9abb967bba600dd0b293dafd6a",
+    sha1 = "00df4b474e71be02c1349c3292d98886f888d1f7",
+    src_sha1 = "3bf88046a16098ea6cc41576dd50d512854d39e1",
 )
 
 maven_jar(
-    name = "bcpkix-jdk15on",
+    name = "bcpkix",
     artifact = "org.bouncycastle:bcpkix-jdk15on:" + BOUNCYCASTLE_VER,
-    sha1 = "d0c46320fbc07be3a24eb13a56cee4e3d38e0c75",
-    src_sha1 = "a25f041293f401af08efba63ff4bbdce98134a03",
+    sha1 = "89bb3aa5b98b48e584eee2a7401b7682a46779b4",
+    src_sha1 = "a0498d09200a18737eccc05aa81bbd05c1be0f8c",
 )
diff --git a/lib/BUILD b/lib/BUILD
index 47125ed..93bb731 100644
--- a/lib/BUILD
+++ b/lib/BUILD
@@ -71,14 +71,14 @@
 )
 
 java_library(
-    name = "sshd-core",
+    name = "sshd-osgi",
     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"],
+    exports = ["@sshd-osgi//jar"],
 )
 
 java_library(
@@ -156,7 +156,7 @@
         "//org.eclipse.jgit:__pkg__",
         "//org.eclipse.jgit.test:__pkg__",
     ],
-    exports = ["@bcpg-jdk15on//jar"],
+    exports = ["@bcpg//jar"],
 )
 
 java_library(
@@ -165,7 +165,7 @@
         "//org.eclipse.jgit:__pkg__",
         "//org.eclipse.jgit.test:__pkg__",
     ],
-    exports = ["@bcprov-jdk15on//jar"],
+    exports = ["@bcprov//jar"],
 )
 
 java_library(
@@ -174,7 +174,7 @@
         "//org.eclipse.jgit:__pkg__",
         "//org.eclipse.jgit.test:__pkg__",
     ],
-    exports = ["@bcpkix-jdk15on//jar"],
+    exports = ["@bcpkix//jar"],
 )
 
 java_library(
diff --git a/org.eclipse.jgit.ant.test/.settings/org.eclipse.pde.prefs b/org.eclipse.jgit.ant.test/.settings/org.eclipse.pde.prefs
new file mode 100644
index 0000000..2174e4f
--- /dev/null
+++ b/org.eclipse.jgit.ant.test/.settings/org.eclipse.pde.prefs
@@ -0,0 +1,34 @@
+compilers.f.unresolved-features=1
+compilers.f.unresolved-plugins=1
+compilers.incompatible-environment=1
+compilers.p.build=1
+compilers.p.build.bin.includes=1
+compilers.p.build.encodings=2
+compilers.p.build.java.compiler=2
+compilers.p.build.java.compliance=1
+compilers.p.build.missing.output=2
+compilers.p.build.output.library=1
+compilers.p.build.source.library=1
+compilers.p.build.src.includes=1
+compilers.p.deprecated=1
+compilers.p.discouraged-class=1
+compilers.p.internal=1
+compilers.p.missing-packages=2
+compilers.p.missing-version-export-package=2
+compilers.p.missing-version-import-package=2
+compilers.p.missing-version-require-bundle=2
+compilers.p.no-required-att=0
+compilers.p.no.automatic.module=1
+compilers.p.not-externalized-att=2
+compilers.p.service.component.without.lazyactivation=1
+compilers.p.unknown-attribute=1
+compilers.p.unknown-class=1
+compilers.p.unknown-element=1
+compilers.p.unknown-identifier=1
+compilers.p.unknown-resource=1
+compilers.p.unresolved-ex-points=0
+compilers.p.unresolved-import=0
+compilers.s.create-docs=false
+compilers.s.doc-folder=doc
+compilers.s.open-tags=1
+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 e9a41d0..3fd704f 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.3.10.qualifier
+Bundle-Version: 5.4.4.qualifier
 Bundle-ActivationPolicy: lazy
 Bundle-RequiredExecutionEnvironment: JavaSE-1.8
 Import-Package: org.apache.tools.ant,
- org.eclipse.jgit.ant.tasks;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.junit;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.lib;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.util;version="[5.3.10,5.4.0)",
+ org.eclipse.jgit.ant.tasks;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.junit;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.lib;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.util;version="[5.4.4,5.5.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 675db1b..b0473e5 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.3.10-SNAPSHOT</version>
+    <version>5.4.4-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.ant.test</artifactId>
@@ -105,7 +105,7 @@
       <plugin>
         <artifactId>maven-surefire-plugin</artifactId>
         <configuration>
-          <argLine>@{argLine} -Xmx256m -Dfile.encoding=UTF-8 -Djava.io.tmpdir=${project.build.directory}</argLine>
+          <argLine>@{argLine} -Xmx512m -Dfile.encoding=UTF-8 -Djava.io.tmpdir=${project.build.directory}</argLine>
         </configuration>
       </plugin>
     </plugins>
diff --git a/org.eclipse.jgit.ant/META-INF/MANIFEST.MF b/org.eclipse.jgit.ant/META-INF/MANIFEST.MF
index d1a9601..0ab7ae5 100644
--- a/org.eclipse.jgit.ant/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.ant/META-INF/MANIFEST.MF
@@ -3,11 +3,13 @@
 Bundle-Name: %Bundle-Name
 Automatic-Module-Name: org.eclipse.jgit.ant
 Bundle-SymbolicName: org.eclipse.jgit.ant
-Bundle-Version: 5.3.10.qualifier
+Bundle-Version: 5.4.4.qualifier
 Bundle-RequiredExecutionEnvironment: JavaSE-1.8
 Import-Package: org.apache.tools.ant,
-  org.eclipse.jgit.storage.file;version="[5.3.10,5.4.0)"
+  org.eclipse.jgit.storage.file;version="[5.4.4,5.5.0)"
 Bundle-Localization: plugin
 Bundle-Vendor: %Provider-Name
-Export-Package: org.eclipse.jgit.ant.tasks;version="5.3.10";
- uses:="org.apache.tools.ant.types,org.apache.tools.ant"
+Export-Package: org.eclipse.jgit.ant;version="5.4.4",
+ org.eclipse.jgit.ant.tasks;version="5.4.4";
+  uses:="org.apache.tools.ant,
+   org.apache.tools.ant.types"
diff --git a/org.eclipse.jgit.ant/META-INF/SOURCE-MANIFEST.MF b/org.eclipse.jgit.ant/META-INF/SOURCE-MANIFEST.MF
new file mode 100644
index 0000000..a720849
--- /dev/null
+++ b/org.eclipse.jgit.ant/META-INF/SOURCE-MANIFEST.MF
@@ -0,0 +1,7 @@
+Manifest-Version: 1.0
+Bundle-ManifestVersion: 2
+Bundle-Name: org.eclipse.jgit.ant - Sources
+Bundle-SymbolicName: org.eclipse.jgit.ant.source
+Bundle-Vendor: Eclipse.org - JGit
+Bundle-Version: 5.4.4.qualifier
+Eclipse-SourceBundle: org.eclipse.jgit.ant;version="5.4.4.qualifier";roots="."
diff --git a/org.eclipse.jgit.ant/pom.xml b/org.eclipse.jgit.ant/pom.xml
index acc44f4..71be666 100644
--- a/org.eclipse.jgit.ant/pom.xml
+++ b/org.eclipse.jgit.ant/pom.xml
@@ -45,62 +45,103 @@
 	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.3.10-SNAPSHOT</version>
-	</parent>
+  <parent>
+    <groupId>org.eclipse.jgit</groupId>
+    <artifactId>org.eclipse.jgit-parent</artifactId>
+    <version>5.4.4-SNAPSHOT</version>
+  </parent>
 
-	<artifactId>org.eclipse.jgit.ant</artifactId>
-	<name>JGit - Ant Tasks</name>
+  <artifactId>org.eclipse.jgit.ant</artifactId>
+  <name>JGit - Ant Tasks</name>
 
-	<description>
-    Ant based user interface for Git
-    </description>
+  <description>Ant based user interface for Git</description>
 
-	<properties>
-		<translate-qualifier />
-	</properties>
+  <properties>
+    <translate-qualifier />
+    <source-bundle-manifest>${project.build.directory}/META-INF/SOURCE-MANIFEST.MF</source-bundle-manifest>
+  </properties>
 
-	<dependencies>
-		<dependency>
-			<groupId>org.eclipse.jgit</groupId>
-			<artifactId>org.eclipse.jgit</artifactId>
-			<version>${project.version}</version>
-		</dependency>
+  <dependencies>
+    <dependency>
+      <groupId>org.eclipse.jgit</groupId>
+      <artifactId>org.eclipse.jgit</artifactId>
+      <version>${project.version}</version>
+    </dependency>
 
-		<dependency>
-			<groupId>org.apache.ant</groupId>
-			<artifactId>ant</artifactId>
-			<version>1.10.5</version>
-		</dependency>
-	</dependencies>
+    <dependency>
+      <groupId>org.apache.ant</groupId>
+      <artifactId>ant</artifactId>
+      <version>1.10.5</version>
+    </dependency>
+  </dependencies>
 
-	<build>
-		<sourceDirectory>src/</sourceDirectory>
+  <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>
+	<resources>
+      <resource>
+        <directory>.</directory>
+          <includes>
+            <include>plugin.properties</include>
+            <include>about.html</include>
+          </includes>
+      </resource>
+      <resource>
+        <directory>resources/</directory>
+      </resource>
+    </resources>
 
-		<plugins>
-			<plugin>
-				<artifactId>maven-jar-plugin</artifactId>
-				<configuration>
-					<archive>
-						<manifestFile>${bundle-manifest}</manifestFile>
-					</archive>
-				</configuration>
-			</plugin>
+    <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>
@@ -143,10 +184,10 @@
           </execution>
         </executions>
       </plugin>
-		</plugins>
-	</build>
+    </plugins>
+  </build>
 
-	<reporting>
+  <reporting>
     <plugins>
       <plugin>
           <groupId>com.github.siom79.japicmp</groupId>
diff --git a/org.eclipse.jgit.archive/META-INF/MANIFEST.MF b/org.eclipse.jgit.archive/META-INF/MANIFEST.MF
index 305f864..cac5489 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.3.10.qualifier
+Bundle-Version: 5.4.4.qualifier
 Bundle-Vendor: %provider_name
 Bundle-Localization: plugin
 Bundle-RequiredExecutionEnvironment: JavaSE-1.8
@@ -13,16 +13,17 @@
  org.apache.commons.compress.compressors.bzip2;version="[1.4,2.0)",
  org.apache.commons.compress.compressors.gzip;version="[1.4,2.0)",
  org.apache.commons.compress.compressors.xz;version="[1.4,2.0)",
- org.eclipse.jgit.api;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.lib;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.nls;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.revwalk;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.util;version="[5.3.10,5.4.0)",
+ org.eclipse.jgit.api;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.lib;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.nls;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.revwalk;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.util;version="[5.4.4,5.5.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.3.10";
+Export-Package: org.eclipse.jgit.archive;version="5.4.4";
   uses:="org.eclipse.jgit.lib,
    org.eclipse.jgit.api,
    org.apache.commons.compress.archivers,
-   org.osgi.framework"
+   org.osgi.framework",
+ org.eclipse.jgit.archive.internal;version="5.4.4";x-internal:=true
diff --git a/org.eclipse.jgit.archive/META-INF/SOURCE-MANIFEST.MF b/org.eclipse.jgit.archive/META-INF/SOURCE-MANIFEST.MF
index a7d5611..3fa9790 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.3.10.qualifier
-Eclipse-SourceBundle: org.eclipse.jgit.archive;version="5.3.10.qualifier";roots="."
+Bundle-Version: 5.4.4.qualifier
+Eclipse-SourceBundle: org.eclipse.jgit.archive;version="5.4.4.qualifier";roots="."
diff --git a/org.eclipse.jgit.archive/pom.xml b/org.eclipse.jgit.archive/pom.xml
index f6e63de..1cfbc3b 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.3.10-SNAPSHOT</version>
+    <version>5.4.4-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.archive</artifactId>
@@ -64,6 +64,7 @@
 
   <properties>
     <translate-qualifier/>
+    <source-bundle-manifest>${project.build.directory}/META-INF/SOURCE-MANIFEST.MF</source-bundle-manifest>
   </properties>
 
   <dependencies>
@@ -99,6 +100,48 @@
 
     <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>
diff --git a/org.eclipse.jgit.benchmarks/pom.xml b/org.eclipse.jgit.benchmarks/pom.xml
index 7109eda..2dd6a85 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.3.10-SNAPSHOT</version>
+  <version>5.4.4-SNAPSHOT</version>
   <artifactId>org.eclipse.jgit.benchmarks</artifactId>
   <packaging>jar</packaging>
 
diff --git a/org.eclipse.jgit.coverage/pom.xml b/org.eclipse.jgit.coverage/pom.xml
index c79a3f1..d4c140c 100644
--- a/org.eclipse.jgit.coverage/pom.xml
+++ b/org.eclipse.jgit.coverage/pom.xml
@@ -5,7 +5,7 @@
   <parent>
     <groupId>org.eclipse.jgit</groupId>
     <artifactId>org.eclipse.jgit-parent</artifactId>
-    <version>5.3.10-SNAPSHOT</version>
+    <version>5.4.4-SNAPSHOT</version>
   </parent>
   <modelVersion>4.0.0</modelVersion>
 
@@ -18,88 +18,88 @@
     <dependency>
       <groupId>org.eclipse.jgit</groupId>
       <artifactId>org.eclipse.jgit</artifactId>
-      <version>5.3.10-SNAPSHOT</version>
+      <version>5.4.4-SNAPSHOT</version>
     </dependency>
     <dependency>
       <groupId>org.eclipse.jgit</groupId>
       <artifactId>org.eclipse.jgit.ant</artifactId>
-      <version>5.3.10-SNAPSHOT</version>
+      <version>5.4.4-SNAPSHOT</version>
     </dependency>
     <dependency>
       <groupId>org.eclipse.jgit</groupId>
       <artifactId>org.eclipse.jgit.archive</artifactId>
-      <version>5.3.10-SNAPSHOT</version>
+      <version>5.4.4-SNAPSHOT</version>
     </dependency>
     <dependency>
       <groupId>org.eclipse.jgit</groupId>
       <artifactId>org.eclipse.jgit.http.apache</artifactId>
-      <version>5.3.10-SNAPSHOT</version>
+      <version>5.4.4-SNAPSHOT</version>
     </dependency>
     <dependency>
       <groupId>org.eclipse.jgit</groupId>
       <artifactId>org.eclipse.jgit.http.server</artifactId>
-      <version>5.3.10-SNAPSHOT</version>
+      <version>5.4.4-SNAPSHOT</version>
     </dependency>
     <dependency>
       <groupId>org.eclipse.jgit</groupId>
       <artifactId>org.eclipse.jgit.lfs</artifactId>
-      <version>5.3.10-SNAPSHOT</version>
+      <version>5.4.4-SNAPSHOT</version>
     </dependency>
     <dependency>
       <groupId>org.eclipse.jgit</groupId>
       <artifactId>org.eclipse.jgit.lfs.server</artifactId>
-      <version>5.3.10-SNAPSHOT</version>
+      <version>5.4.4-SNAPSHOT</version>
     </dependency>
     <dependency>
       <groupId>org.eclipse.jgit</groupId>
       <artifactId>org.eclipse.jgit.pgm</artifactId>
-      <version>5.3.10-SNAPSHOT</version>
+      <version>5.4.4-SNAPSHOT</version>
     </dependency>
     <dependency>
       <groupId>org.eclipse.jgit</groupId>
       <artifactId>org.eclipse.jgit.ui</artifactId>
-      <version>5.3.10-SNAPSHOT</version>
+      <version>5.4.4-SNAPSHOT</version>
     </dependency>
     <dependency>
       <groupId>org.eclipse.jgit</groupId>
       <artifactId>org.eclipse.jgit.ssh.apache</artifactId>
-      <version>5.3.10-SNAPSHOT</version>
+      <version>5.4.4-SNAPSHOT</version>
     </dependency>
 
     <dependency>
       <groupId>org.eclipse.jgit</groupId>
       <artifactId>org.eclipse.jgit.test</artifactId>
-      <version>5.3.10-SNAPSHOT</version>
+      <version>5.4.4-SNAPSHOT</version>
     </dependency>
     <dependency>
       <groupId>org.eclipse.jgit</groupId>
       <artifactId>org.eclipse.jgit.ant.test</artifactId>
-      <version>5.3.10-SNAPSHOT</version>
+      <version>5.4.4-SNAPSHOT</version>
     </dependency>
     <dependency>
       <groupId>org.eclipse.jgit</groupId>
       <artifactId>org.eclipse.jgit.http.test</artifactId>
-      <version>5.3.10-SNAPSHOT</version>
+      <version>5.4.4-SNAPSHOT</version>
     </dependency>
     <dependency>
       <groupId>org.eclipse.jgit</groupId>
       <artifactId>org.eclipse.jgit.pgm.test</artifactId>
-      <version>5.3.10-SNAPSHOT</version>
+      <version>5.4.4-SNAPSHOT</version>
     </dependency>
     <dependency>
       <groupId>org.eclipse.jgit</groupId>
       <artifactId>org.eclipse.jgit.lfs.test</artifactId>
-      <version>5.3.10-SNAPSHOT</version>
+      <version>5.4.4-SNAPSHOT</version>
     </dependency>
     <dependency>
       <groupId>org.eclipse.jgit</groupId>
       <artifactId>org.eclipse.jgit.lfs.server.test</artifactId>
-      <version>5.3.10-SNAPSHOT</version>
+      <version>5.4.4-SNAPSHOT</version>
     </dependency>
     <dependency>
       <groupId>org.eclipse.jgit</groupId>
       <artifactId>org.eclipse.jgit.ssh.apache.test</artifactId>
-      <version>5.3.10-SNAPSHOT</version>
+      <version>5.4.4-SNAPSHOT</version>
     </dependency>
   </dependencies>
 
diff --git a/org.eclipse.jgit.http.apache/META-INF/MANIFEST.MF b/org.eclipse.jgit.http.apache/META-INF/MANIFEST.MF
index 4ee9b61..05c07d1 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.3.10.qualifier
+Bundle-Version: 5.4.4.qualifier
 Bundle-RequiredExecutionEnvironment: JavaSE-1.8
 Bundle-Localization: plugin
 Bundle-Vendor: %Provider-Name
@@ -23,11 +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.annotations;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.nls;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.transport.http;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.util;version="[5.3.10,5.4.0)"
-Export-Package: org.eclipse.jgit.transport.http.apache;version="5.3.10";
+ org.eclipse.jgit.annotations;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.nls;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.transport.http;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.util;version="[5.4.4,5.5.0)"
+Export-Package: org.eclipse.jgit.transport.http.apache;version="5.4.4";
   uses:="org.apache.http.client,
    org.eclipse.jgit.transport.http,
    org.apache.http.entity,
diff --git a/org.eclipse.jgit.http.apache/META-INF/SOURCE-MANIFEST.MF b/org.eclipse.jgit.http.apache/META-INF/SOURCE-MANIFEST.MF
new file mode 100644
index 0000000..9c4b862
--- /dev/null
+++ b/org.eclipse.jgit.http.apache/META-INF/SOURCE-MANIFEST.MF
@@ -0,0 +1,7 @@
+Manifest-Version: 1.0
+Bundle-ManifestVersion: 2
+Bundle-Name: org.eclipse.jgit.http.apache - Sources
+Bundle-SymbolicName: org.eclipse.jgit.http.apache.source
+Bundle-Vendor: Eclipse.org - JGit
+Bundle-Version: 5.4.4.qualifier
+Eclipse-SourceBundle: org.eclipse.jgit.http.apache;version="5.4.4.qualifier";roots="."
diff --git a/org.eclipse.jgit.http.apache/pom.xml b/org.eclipse.jgit.http.apache/pom.xml
index abf7dfa..98b58e9 100644
--- a/org.eclipse.jgit.http.apache/pom.xml
+++ b/org.eclipse.jgit.http.apache/pom.xml
@@ -45,64 +45,107 @@
 	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.3.10-SNAPSHOT</version>
-	</parent>
+  <parent>
+    <groupId>org.eclipse.jgit</groupId>
+    <artifactId>org.eclipse.jgit-parent</artifactId>
+    <version>5.4.4-SNAPSHOT</version>
+  </parent>
 
-	<artifactId>org.eclipse.jgit.http.apache</artifactId>
-	<name>JGit - Apache httpclient based HTTP support</name>
+  <artifactId>org.eclipse.jgit.http.apache</artifactId>
+  <name>JGit - Apache httpclient based HTTP support</name>
 
-	<description>
+  <description>
     Apache httpclient based HTTP support
-    </description>
+  </description>
 
-	<properties>
-		<translate-qualifier />
-	</properties>
+  <properties>
+    <translate-qualifier />
+    <source-bundle-manifest>${project.build.directory}/META-INF/SOURCE-MANIFEST.MF</source-bundle-manifest>
+  </properties>
 
-	<dependencies>
-		<dependency>
-			<groupId>org.eclipse.jgit</groupId>
-			<artifactId>org.eclipse.jgit</artifactId>
-			<version>${project.version}</version>
-		</dependency>
-        <dependency>
-          <groupId>org.apache.httpcomponents</groupId>
-          <artifactId>httpclient</artifactId>
-        </dependency>
-        <dependency>
-          <groupId>org.apache.httpcomponents</groupId>
-          <artifactId>httpcore</artifactId>
-        </dependency>
-	</dependencies>
+  <dependencies>
+    <dependency>
+      <groupId>org.eclipse.jgit</groupId>
+      <artifactId>org.eclipse.jgit</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.httpcomponents</groupId>
+      <artifactId>httpclient</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.httpcomponents</groupId>
+      <artifactId>httpcore</artifactId>
+    </dependency>
+  </dependencies>
 
-	<build>
-		<sourceDirectory>src/</sourceDirectory>
+  <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>
+    <resources>
+      <resource>
+        <directory>.</directory>
+        <includes>
+          <include>plugin.properties</include>
+          <include>about.html</include>
+        </includes>
+      </resource>
+      <resource>
+        <directory>resources/</directory>
+      </resource>
+    </resources>
 
-		<plugins>
-			<plugin>
-				<artifactId>maven-jar-plugin</artifactId>
-				<configuration>
-					<archive>
-						<manifestFile>${bundle-manifest}</manifestFile>
-					</archive>
-				</configuration>
-			</plugin>
+    <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>
@@ -145,10 +188,10 @@
           </execution>
         </executions>
       </plugin>
-		</plugins>
-	</build>
+    </plugins>
+  </build>
 
-	<reporting>
+  <reporting>
     <plugins>
       <plugin>
           <groupId>com.github.siom79.japicmp</groupId>
diff --git a/org.eclipse.jgit.http.server/META-INF/MANIFEST.MF b/org.eclipse.jgit.http.server/META-INF/MANIFEST.MF
index 4823333..aaed0f7 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.3.10.qualifier
+Bundle-Version: 5.4.4.qualifier
 Bundle-Localization: plugin
 Bundle-Vendor: %provider_name
-Export-Package: org.eclipse.jgit.http.server;version="5.3.10",
- org.eclipse.jgit.http.server.glue;version="5.3.10";
+Export-Package: org.eclipse.jgit.http.server;version="5.4.4",
+ org.eclipse.jgit.http.server.glue;version="5.4.4";
   uses:="javax.servlet,javax.servlet.http",
- org.eclipse.jgit.http.server.resolver;version="5.3.10";
+ org.eclipse.jgit.http.server.resolver;version="5.4.4";
   uses:="org.eclipse.jgit.transport.resolver,
    org.eclipse.jgit.lib,
    org.eclipse.jgit.transport,
@@ -18,13 +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.3.10,5.4.0)",
- org.eclipse.jgit.internal.storage.dfs;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.internal.storage.file;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.internal.transport.parser;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.lib;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.nls;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.revwalk;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.transport;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.transport.resolver;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.util;version="[5.3.10,5.4.0)"
+ org.eclipse.jgit.errors;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.internal.storage.dfs;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.internal.storage.file;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.internal.transport.parser;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.lib;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.nls;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.revwalk;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.transport;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.transport.resolver;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.util;version="[5.4.4,5.5.0)"
diff --git a/org.eclipse.jgit.http.server/META-INF/SOURCE-MANIFEST.MF b/org.eclipse.jgit.http.server/META-INF/SOURCE-MANIFEST.MF
new file mode 100644
index 0000000..fea01da
--- /dev/null
+++ b/org.eclipse.jgit.http.server/META-INF/SOURCE-MANIFEST.MF
@@ -0,0 +1,7 @@
+Manifest-Version: 1.0
+Bundle-ManifestVersion: 2
+Bundle-Name: org.eclipse.jgit.http.server - Sources
+Bundle-SymbolicName: org.eclipse.jgit.http.server.source
+Bundle-Vendor: Eclipse.org - JGit
+Bundle-Version: 5.4.4.qualifier
+Eclipse-SourceBundle: org.eclipse.jgit.http.server;version="5.4.4.qualifier";roots="."
diff --git a/org.eclipse.jgit.http.server/pom.xml b/org.eclipse.jgit.http.server/pom.xml
index 8363bc7..0e1ea2a 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.3.10-SNAPSHOT</version>
+    <version>5.4.4-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.http.server</artifactId>
@@ -64,6 +64,7 @@
 
   <properties>
     <translate-qualifier/>
+    <source-bundle-manifest>${project.build.directory}/META-INF/SOURCE-MANIFEST.MF</source-bundle-manifest>
   </properties>
 
   <dependencies>
@@ -99,6 +100,28 @@
     <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>
diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/GitFilter.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/GitFilter.java
index 2ebe1b7..51de8ab 100644
--- a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/GitFilter.java
+++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/GitFilter.java
@@ -202,7 +202,7 @@
 		if (resolver == null) {
 			File root = getFile(filterConfig, "base-path");
 			boolean exportAll = getBoolean(filterConfig, "export-all");
-			setRepositoryResolver(new FileResolver<HttpServletRequest>(root, exportAll));
+			setRepositoryResolver(new FileResolver<>(root, exportAll));
 		}
 
 		initialized = true;
diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/ServletUtils.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/ServletUtils.java
index b6d73b5..256279b 100644
--- a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/ServletUtils.java
+++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/ServletUtils.java
@@ -64,7 +64,6 @@
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
-import org.eclipse.jgit.internal.storage.dfs.DfsRepository;
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.Repository;
@@ -276,12 +275,11 @@
 	}
 
 	static String identify(Repository git) {
-		if (git instanceof DfsRepository) {
-			return ((DfsRepository) git).getDescription().getRepositoryName();
-		} else if (git.getDirectory() != null) {
-			return git.getDirectory().getPath();
+		String identifier = git.getIdentifier();
+		if (identifier == null) {
+			return "unknown";
 		}
-		return "unknown";
+		return identifier;
 	}
 
 	private ServletUtils() {
diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/MetaServlet.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/MetaServlet.java
index d8fa712..14b6506 100644
--- a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/MetaServlet.java
+++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/MetaServlet.java
@@ -47,7 +47,6 @@
 
 import java.io.IOException;
 
-import javax.servlet.FilterChain;
 import javax.servlet.ServletConfig;
 import javax.servlet.ServletContext;
 import javax.servlet.ServletException;
@@ -140,14 +139,10 @@
 	@Override
 	protected void service(HttpServletRequest req, HttpServletResponse res)
 			throws ServletException, IOException {
-		filter.doFilter(req, res, new FilterChain() {
-			@Override
-			public void doFilter(ServletRequest request,
-					ServletResponse response) throws IOException,
-					ServletException {
-				((HttpServletResponse) response).sendError(SC_NOT_FOUND);
-			}
-		});
+		filter.doFilter(req, res,
+				(ServletRequest request, ServletResponse response) -> {
+					((HttpServletResponse) response).sendError(SC_NOT_FOUND);
+				});
 	}
 
 	/**
diff --git a/org.eclipse.jgit.http.test/.settings/org.eclipse.pde.prefs b/org.eclipse.jgit.http.test/.settings/org.eclipse.pde.prefs
new file mode 100644
index 0000000..2174e4f
--- /dev/null
+++ b/org.eclipse.jgit.http.test/.settings/org.eclipse.pde.prefs
@@ -0,0 +1,34 @@
+compilers.f.unresolved-features=1
+compilers.f.unresolved-plugins=1
+compilers.incompatible-environment=1
+compilers.p.build=1
+compilers.p.build.bin.includes=1
+compilers.p.build.encodings=2
+compilers.p.build.java.compiler=2
+compilers.p.build.java.compliance=1
+compilers.p.build.missing.output=2
+compilers.p.build.output.library=1
+compilers.p.build.source.library=1
+compilers.p.build.src.includes=1
+compilers.p.deprecated=1
+compilers.p.discouraged-class=1
+compilers.p.internal=1
+compilers.p.missing-packages=2
+compilers.p.missing-version-export-package=2
+compilers.p.missing-version-import-package=2
+compilers.p.missing-version-require-bundle=2
+compilers.p.no-required-att=0
+compilers.p.no.automatic.module=1
+compilers.p.not-externalized-att=2
+compilers.p.service.component.without.lazyactivation=1
+compilers.p.unknown-attribute=1
+compilers.p.unknown-class=1
+compilers.p.unknown-element=1
+compilers.p.unknown-identifier=1
+compilers.p.unknown-resource=1
+compilers.p.unresolved-ex-points=0
+compilers.p.unresolved-import=0
+compilers.s.create-docs=false
+compilers.s.doc-folder=doc
+compilers.s.open-tags=1
+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 1aa377b..ef21e8b 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.3.10.qualifier
+Bundle-Version: 5.4.4.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.3.10,5.4.0)",
- org.eclipse.jgit.http.server;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.http.server.glue;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.http.server.resolver;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.internal;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.internal.storage.dfs;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.internal.storage.file;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.internal.storage.reftable;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.junit;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.junit.http;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.lib;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.nls;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.revwalk;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.storage.file;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.transport;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.transport.http;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.transport.http.apache;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.transport.resolver;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.util;version="[5.3.10,5.4.0)",
+ org.eclipse.jgit.errors;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.http.server;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.http.server.glue;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.http.server.resolver;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.internal;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.internal.storage.dfs;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.internal.storage.file;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.internal.storage.reftable;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.junit;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.junit.http;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.lib;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.nls;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.revwalk;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.storage.file;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.transport;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.transport.http;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.transport.http.apache;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.transport.resolver;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.util;version="[5.4.4,5.5.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 23c59bd..29e54cd 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.3.10-SNAPSHOT</version>
+    <version>5.4.4-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}  -Xmx300m</argLine>
+          <argLine>@{argLine} -Djava.io.tmpdir=${project.build.directory}  -Xmx512m</argLine>
           <includes>
             <include>**/*Test.java</include>
             <include>**/*Tests.java</include>
diff --git a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/AdvertiseErrorTest.java b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/AdvertiseErrorTest.java
index 5a46967..ec9ced0 100644
--- a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/AdvertiseErrorTest.java
+++ b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/AdvertiseErrorTest.java
@@ -69,7 +69,6 @@
 import org.eclipse.jgit.transport.RemoteRefUpdate;
 import org.eclipse.jgit.transport.Transport;
 import org.eclipse.jgit.transport.URIish;
-import org.eclipse.jgit.transport.resolver.RepositoryResolver;
 import org.eclipse.jgit.transport.resolver.ServiceNotAuthorizedException;
 import org.eclipse.jgit.transport.resolver.ServiceNotEnabledException;
 import org.junit.Before;
@@ -90,18 +89,13 @@
 
 		ServletContextHandler app = server.addContext("/git");
 		GitServlet gs = new GitServlet();
-		gs.setRepositoryResolver(new RepositoryResolver<HttpServletRequest>() {
-			@Override
-			public Repository open(HttpServletRequest req, String name)
-					throws RepositoryNotFoundException,
-					ServiceNotEnabledException {
-				if (!name.equals(srcName))
-					throw new RepositoryNotFoundException(name);
-
-				final Repository db = src.getRepository();
-				db.incrementOpen();
-				return db;
+		gs.setRepositoryResolver((HttpServletRequest req, String name) -> {
+			if (!name.equals(srcName)) {
+				throw new RepositoryNotFoundException(name);
 			}
+			final Repository db = src.getRepository();
+			db.incrementOpen();
+			return db;
 		});
 		gs.setReceivePackFactory(new DefaultReceivePackFactory() {
 			@Override
diff --git a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/GitServletResponseTests.java b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/GitServletResponseTests.java
index 8dce98b..eb19365 100644
--- a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/GitServletResponseTests.java
+++ b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/GitServletResponseTests.java
@@ -75,7 +75,6 @@
 import org.eclipse.jgit.transport.RemoteRefUpdate;
 import org.eclipse.jgit.transport.Transport;
 import org.eclipse.jgit.transport.URIish;
-import org.eclipse.jgit.transport.resolver.RepositoryResolver;
 import org.eclipse.jgit.transport.resolver.ServiceNotAuthorizedException;
 import org.eclipse.jgit.transport.resolver.ServiceNotEnabledException;
 import org.junit.Before;
@@ -117,18 +116,13 @@
 
 		ServletContextHandler app = server.addContext("/git");
 		gs = new GitServlet();
-		gs.setRepositoryResolver(new RepositoryResolver<HttpServletRequest>() {
-			@Override
-			public Repository open(HttpServletRequest req, String name)
-					throws RepositoryNotFoundException,
-					ServiceNotEnabledException {
-				if (!name.equals(repoName))
-					throw new RepositoryNotFoundException(name);
-
-				final Repository db = srv.getRepository();
-				db.incrementOpen();
-				return db;
+		gs.setRepositoryResolver((HttpServletRequest req, String name) -> {
+			if (!name.equals(repoName)) {
+				throw new RepositoryNotFoundException(name);
 			}
+			final Repository db = srv.getRepository();
+			db.incrementOpen();
+			return db;
 		});
 		gs.setReceivePackFactory(new DefaultReceivePackFactory() {
 			@Override
@@ -179,12 +173,8 @@
 
 		maxPackSize = 0;
 		postHook = null;
-		preHook = new PreReceiveHook() {
-			@Override
-			public void onPreReceive(ReceivePack rp,
-					Collection<ReceiveCommand> commands) {
-				throw new IllegalStateException();
-			}
+		preHook = (ReceivePack rp, Collection<ReceiveCommand> commands) -> {
+			throw new IllegalStateException();
 		};
 
 		try (Transport t = Transport.open(clientRepo, srvURI)) {
@@ -263,15 +253,11 @@
 		maxPackSize = 100;
 		// this PostReceiveHook when called after an unsuccesfull unpack will
 		// lead to an IllegalStateException
-		postHook = new PostReceiveHook() {
-			@Override
-			public void onPostReceive(ReceivePack rp,
-					Collection<ReceiveCommand> commands) {
-				// the maxPackSize setting caused that the packfile couldn't be
-				// saved to disk. Calling getPackSize() now will lead to a
-				// IllegalStateException.
-				rp.getPackSize();
-			}
+		postHook = (ReceivePack rp, Collection<ReceiveCommand> commands) -> {
+			// the maxPackSize setting caused that the packfile couldn't be
+			// saved to disk. Calling getPackSize() now will lead to a
+			// IllegalStateException.
+			rp.getPackSize();
 		};
 
 		try (Transport t = Transport.open(clientRepo, srvURI)) {
diff --git a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/HookMessageTest.java b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/HookMessageTest.java
index 5a5ff1a..49ff51a 100644
--- a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/HookMessageTest.java
+++ b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/HookMessageTest.java
@@ -70,14 +70,12 @@
 import org.eclipse.jgit.lib.StoredConfig;
 import org.eclipse.jgit.revwalk.RevBlob;
 import org.eclipse.jgit.revwalk.RevCommit;
-import org.eclipse.jgit.transport.PreReceiveHook;
 import org.eclipse.jgit.transport.PushResult;
 import org.eclipse.jgit.transport.ReceiveCommand;
 import org.eclipse.jgit.transport.ReceivePack;
 import org.eclipse.jgit.transport.RemoteRefUpdate;
 import org.eclipse.jgit.transport.Transport;
 import org.eclipse.jgit.transport.URIish;
-import org.eclipse.jgit.transport.resolver.RepositoryResolver;
 import org.eclipse.jgit.transport.resolver.ServiceNotAuthorizedException;
 import org.eclipse.jgit.transport.resolver.ServiceNotEnabledException;
 import org.junit.Before;
@@ -98,18 +96,13 @@
 
 		ServletContextHandler app = server.addContext("/git");
 		GitServlet gs = new GitServlet();
-		gs.setRepositoryResolver(new RepositoryResolver<HttpServletRequest>() {
-			@Override
-			public Repository open(HttpServletRequest req, String name)
-					throws RepositoryNotFoundException,
-					ServiceNotEnabledException {
-				if (!name.equals(srcName))
-					throw new RepositoryNotFoundException(name);
-
-				final Repository db = src.getRepository();
-				db.incrementOpen();
-				return db;
+		gs.setRepositoryResolver((HttpServletRequest req, String name) -> {
+			if (!name.equals(srcName)) {
+				throw new RepositoryNotFoundException(name);
 			}
+			final Repository db = src.getRepository();
+			db.incrementOpen();
+			return db;
 		});
 		gs.setReceivePackFactory(new DefaultReceivePackFactory() {
 			@Override
@@ -117,14 +110,11 @@
 					throws ServiceNotEnabledException,
 					ServiceNotAuthorizedException {
 				ReceivePack recv = super.create(req, db);
-				recv.setPreReceiveHook(new PreReceiveHook() {
-					@Override
-					public void onPreReceive(ReceivePack rp,
-							Collection<ReceiveCommand> commands) {
-						rp.sendMessage("message line 1");
-						rp.sendError("no soup for you!");
-						rp.sendMessage("come back next year!");
-					}
+				recv.setPreReceiveHook((ReceivePack rp,
+						Collection<ReceiveCommand> commands) -> {
+					rp.sendMessage("message line 1");
+					rp.sendError("no soup for you!");
+					rp.sendMessage("come back next year!");
 				});
 				return recv;
 			}
diff --git a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/HttpClientTests.java b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/HttpClientTests.java
index 53626b1..8ec2f51 100644
--- a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/HttpClientTests.java
+++ b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/HttpClientTests.java
@@ -44,7 +44,6 @@
 package org.eclipse.jgit.http.test;
 
 import static org.hamcrest.Matchers.is;
-import static org.hamcrest.Matchers.theInstance;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
@@ -87,8 +86,6 @@
 import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider;
 import org.eclipse.jgit.transport.http.HttpConnection;
 import org.eclipse.jgit.transport.http.JDKHttpConnectionFactory;
-import org.eclipse.jgit.transport.resolver.RepositoryResolver;
-import org.eclipse.jgit.transport.resolver.ServiceNotEnabledException;
 import org.junit.Before;
 import org.junit.Test;
 
@@ -141,18 +138,13 @@
 
 	private ServletContextHandler smart(String path) {
 		GitServlet gs = new GitServlet();
-		gs.setRepositoryResolver(new RepositoryResolver<HttpServletRequest>() {
-			@Override
-			public Repository open(HttpServletRequest req, String name)
-					throws RepositoryNotFoundException,
-					ServiceNotEnabledException {
-				final Repository db = remoteRepository.getRepository();
-				if (!name.equals(nameOf(db)))
-					throw new RepositoryNotFoundException(name);
-
-				db.incrementOpen();
-				return db;
+		gs.setRepositoryResolver((HttpServletRequest req, String name) -> {
+			final Repository db = remoteRepository.getRepository();
+			if (!name.equals(nameOf(db))) {
+				throw new RepositoryNotFoundException(name);
 			}
+			db.incrementOpen();
+			return db;
 		});
 
 		ServletContextHandler ctx = server.addContext(path);
@@ -373,7 +365,7 @@
 
 		// Check that we get a v0 response.
 		assertThat(pckIn.readString(), is("# service=git-upload-pack"));
-		assertThat(pckIn.readString(), theInstance(PacketLineIn.END));
+		assertTrue(PacketLineIn.isEnd(pckIn.readString()));
 		assertTrue(pckIn.readString().matches("[0-9a-f]{40} HEAD.*"));
 	}
 
@@ -395,8 +387,7 @@
 
 		// What remains are capabilities - ensure that all of them are
 		// non-empty strings, and that we see END at the end.
-		String s;
-		while ((s = pckIn.readString()) != PacketLineIn.END) {
+		for (String s : pckIn.readStrings()) {
 			assertTrue(!s.isEmpty());
 		}
 	}
@@ -429,8 +420,7 @@
 		PacketLineIn pckIn = new PacketLineIn(c.getInputStream());
 
 		// Just check that we get what looks like a ref advertisement.
-		String s;
-		while ((s = pckIn.readString()) != PacketLineIn.END) {
+		for (String s : pckIn.readStrings()) {
 			assertTrue(s.matches("[0-9a-f]{40} [A-Za-z/]*"));
 		}
 
diff --git a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/MeasurePackSizeTest.java b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/MeasurePackSizeTest.java
index 0415bcb..79df5a2 100644
--- a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/MeasurePackSizeTest.java
+++ b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/MeasurePackSizeTest.java
@@ -63,14 +63,12 @@
 import org.eclipse.jgit.lib.StoredConfig;
 import org.eclipse.jgit.revwalk.RevBlob;
 import org.eclipse.jgit.revwalk.RevCommit;
-import org.eclipse.jgit.transport.PostReceiveHook;
 import org.eclipse.jgit.transport.PushResult;
 import org.eclipse.jgit.transport.ReceiveCommand;
 import org.eclipse.jgit.transport.ReceivePack;
 import org.eclipse.jgit.transport.RemoteRefUpdate;
 import org.eclipse.jgit.transport.Transport;
 import org.eclipse.jgit.transport.URIish;
-import org.eclipse.jgit.transport.resolver.RepositoryResolver;
 import org.eclipse.jgit.transport.resolver.ServiceNotAuthorizedException;
 import org.eclipse.jgit.transport.resolver.ServiceNotEnabledException;
 import org.junit.Before;
@@ -93,18 +91,13 @@
 
 		ServletContextHandler app = server.addContext("/git");
 		GitServlet gs = new GitServlet();
-		gs.setRepositoryResolver(new RepositoryResolver<HttpServletRequest>() {
-			@Override
-			public Repository open(HttpServletRequest req, String name)
-					throws RepositoryNotFoundException,
-					ServiceNotEnabledException {
-				if (!name.equals(srcName))
-					throw new RepositoryNotFoundException(name);
-
-				final Repository db = src.getRepository();
-				db.incrementOpen();
-				return db;
+		gs.setRepositoryResolver((HttpServletRequest req, String name) -> {
+			if (!name.equals(srcName)) {
+				throw new RepositoryNotFoundException(name);
 			}
+			final Repository db = src.getRepository();
+			db.incrementOpen();
+			return db;
 		});
 		gs.setReceivePackFactory(new DefaultReceivePackFactory() {
 			@Override
@@ -112,13 +105,9 @@
 					throws ServiceNotEnabledException,
 					ServiceNotAuthorizedException {
 				ReceivePack recv = super.create(req, db);
-				recv.setPostReceiveHook(new PostReceiveHook() {
-
-					@Override
-					public void onPostReceive(ReceivePack rp,
-							Collection<ReceiveCommand> commands) {
-						packSize = rp.getPackSize();
-					}
+				recv.setPostReceiveHook((ReceivePack rp,
+						Collection<ReceiveCommand> commands) -> {
+					packSize = rp.getPackSize();
 				});
 				return recv;
 			}
diff --git a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/ProtocolErrorTest.java b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/ProtocolErrorTest.java
index a1baae3..6a3d882 100644
--- a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/ProtocolErrorTest.java
+++ b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/ProtocolErrorTest.java
@@ -44,7 +44,7 @@
 package org.eclipse.jgit.http.test;
 
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
 
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
@@ -71,8 +71,6 @@
 import org.eclipse.jgit.transport.PacketLineIn;
 import org.eclipse.jgit.transport.PacketLineOut;
 import org.eclipse.jgit.transport.URIish;
-import org.eclipse.jgit.transport.resolver.RepositoryResolver;
-import org.eclipse.jgit.transport.resolver.ServiceNotEnabledException;
 import org.eclipse.jgit.util.NB;
 import org.junit.Before;
 import org.junit.Test;
@@ -94,18 +92,13 @@
 
 		ServletContextHandler app = server.addContext("/git");
 		GitServlet gs = new GitServlet();
-		gs.setRepositoryResolver(new RepositoryResolver<HttpServletRequest>() {
-			@Override
-			public Repository open(HttpServletRequest req, String name)
-					throws RepositoryNotFoundException,
-					ServiceNotEnabledException {
-				if (!name.equals(srcName))
-					throw new RepositoryNotFoundException(name);
-
-				final Repository db = src.getRepository();
-				db.incrementOpen();
-				return db;
+		gs.setRepositoryResolver((HttpServletRequest req, String name) -> {
+			if (!name.equals(srcName)) {
+				throw new RepositoryNotFoundException(name);
 			}
+			final Repository db = src.getRepository();
+			db.incrementOpen();
+			return db;
 		});
 		app.addServlet(new ServletHolder(gs), "/*");
 
@@ -164,7 +157,7 @@
 						pckin.readString());
 				assertEquals("ng refs/objects/A n/a (unpacker error)",
 						pckin.readString());
-				assertSame(PacketLineIn.END, pckin.readString());
+				assertTrue(PacketLineIn.isEnd(pckin.readString()));
 			}
 		} finally {
 			c.disconnect();
diff --git a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/SmartClientSmartServerTest.java b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/SmartClientSmartServerTest.java
index 017e2c7..8b85bef 100644
--- a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/SmartClientSmartServerTest.java
+++ b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/SmartClientSmartServerTest.java
@@ -123,9 +123,6 @@
 import org.eclipse.jgit.transport.http.HttpConnectionFactory;
 import org.eclipse.jgit.transport.http.JDKHttpConnectionFactory;
 import org.eclipse.jgit.transport.http.apache.HttpClientConnectionFactory;
-import org.eclipse.jgit.transport.resolver.ServiceNotAuthorizedException;
-import org.eclipse.jgit.transport.resolver.ServiceNotEnabledException;
-import org.eclipse.jgit.transport.resolver.UploadPackFactory;
 import org.eclipse.jgit.util.HttpSupport;
 import org.eclipse.jgit.util.SystemReader;
 import org.hamcrest.Matchers;
@@ -190,18 +187,13 @@
 						ConfigConstants.CONFIG_KEY_LOGALLREFUPDATES, true);
 
 		GitServlet gs = new GitServlet();
-		gs.setUploadPackFactory(new UploadPackFactory<HttpServletRequest>() {
-			@Override
-			public UploadPack create(HttpServletRequest req, Repository db)
-					throws ServiceNotEnabledException,
-					ServiceNotAuthorizedException {
-				DefaultUploadPackFactory f = new DefaultUploadPackFactory();
-				UploadPack up = f.create(req, db);
-				if (advertiseRefsHook != null) {
-					up.setAdvertiseRefsHook(advertiseRefsHook);
-				}
-				return up;
+		gs.setUploadPackFactory((HttpServletRequest req, Repository db) -> {
+			DefaultUploadPackFactory f = new DefaultUploadPackFactory();
+			UploadPack up = f.create(req, db);
+			if (advertiseRefsHook != null) {
+				up.setAdvertiseRefsHook(advertiseRefsHook);
 			}
+			return up;
 		});
 
 		ServletContextHandler app = addNormalContext(gs, src, srcName);
diff --git a/org.eclipse.jgit.junit.http/META-INF/MANIFEST.MF b/org.eclipse.jgit.junit.http/META-INF/MANIFEST.MF
index d34d573..d2b692b 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.3.10.qualifier
+Bundle-Version: 5.4.4.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.3.10,5.4.0)",
- org.eclipse.jgit.http.server;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.internal.storage.file;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.junit;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.lib;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.revwalk;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.transport;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.transport.resolver;version="[5.3.10,5.4.0)",
+ org.eclipse.jgit.errors;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.http.server;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.internal.storage.file;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.junit;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.lib;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.revwalk;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.transport;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.transport.resolver;version="[5.4.4,5.5.0)",
  org.junit;version="[4.12,5.0.0)"
-Export-Package: org.eclipse.jgit.junit.http;version="5.3.10";
+Export-Package: org.eclipse.jgit.junit.http;version="5.4.4";
   uses:="org.eclipse.jgit.transport,
    org.eclipse.jgit.junit,
    javax.servlet.http,
diff --git a/org.eclipse.jgit.junit.http/META-INF/SOURCE-MANIFEST.MF b/org.eclipse.jgit.junit.http/META-INF/SOURCE-MANIFEST.MF
new file mode 100644
index 0000000..decfed1
--- /dev/null
+++ b/org.eclipse.jgit.junit.http/META-INF/SOURCE-MANIFEST.MF
@@ -0,0 +1,7 @@
+Manifest-Version: 1.0
+Bundle-ManifestVersion: 2
+Bundle-Name: org.eclipse.jgit.junit.http - Sources
+Bundle-SymbolicName: org.eclipse.jgit.junit.http.source
+Bundle-Vendor: Eclipse.org - JGit
+Bundle-Version: 5.4.4.qualifier
+Eclipse-SourceBundle: org.eclipse.jgit.junit.http;version="5.4.4.qualifier";roots="."
diff --git a/org.eclipse.jgit.junit.http/pom.xml b/org.eclipse.jgit.junit.http/pom.xml
index 06976bb..e82c243 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.3.10-SNAPSHOT</version>
+    <version>5.4.4-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.junit.http</artifactId>
@@ -62,6 +62,7 @@
 
   <properties>
     <translate-qualifier/>
+    <source-bundle-manifest>${project.build.directory}/META-INF/SOURCE-MANIFEST.MF</source-bundle-manifest>
   </properties>
 
   <dependencies>
@@ -109,6 +110,48 @@
 
     <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>
diff --git a/org.eclipse.jgit.junit.http/src/org/eclipse/jgit/junit/http/SimpleHttpServer.java b/org.eclipse.jgit.junit.http/src/org/eclipse/jgit/junit/http/SimpleHttpServer.java
index 245b510..7c78330 100644
--- a/org.eclipse.jgit.junit.http/src/org/eclipse/jgit/junit/http/SimpleHttpServer.java
+++ b/org.eclipse.jgit.junit.http/src/org/eclipse/jgit/junit/http/SimpleHttpServer.java
@@ -54,8 +54,6 @@
 import org.eclipse.jgit.http.server.GitServlet;
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.transport.URIish;
-import org.eclipse.jgit.transport.resolver.RepositoryResolver;
-import org.eclipse.jgit.transport.resolver.ServiceNotEnabledException;
 
 /**
  * Simple http server for testing http access to Git repositories.
@@ -136,17 +134,12 @@
 
 	private ServletContextHandler smart(String path) {
 		GitServlet gs = new GitServlet();
-		gs.setRepositoryResolver(new RepositoryResolver<HttpServletRequest>() {
-			@Override
-			public Repository open(HttpServletRequest req, String name)
-					throws RepositoryNotFoundException,
-					ServiceNotEnabledException {
-				if (!name.equals(nameOf(db)))
-					throw new RepositoryNotFoundException(name);
-
-				db.incrementOpen();
-				return db;
+		gs.setRepositoryResolver((HttpServletRequest req, String name) -> {
+			if (!name.equals(nameOf(db))) {
+				throw new RepositoryNotFoundException(name);
 			}
+			db.incrementOpen();
+			return db;
 		});
 
 		ServletContextHandler ctx = server.addContext(path);
diff --git a/org.eclipse.jgit.junit.ssh/BUILD b/org.eclipse.jgit.junit.ssh/BUILD
index a8b96b7..906053e 100644
--- a/org.eclipse.jgit.junit.ssh/BUILD
+++ b/org.eclipse.jgit.junit.ssh/BUILD
@@ -9,7 +9,7 @@
     resource_strip_prefix = "org.eclipse.jgit.junit.ssh/resources",
     resources = glob(["resources/**"]),
     deps = [
-        "//lib:sshd-core",
+        "//lib:sshd-osgi",
         "//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
index c207ded..0167b95 100644
--- a/org.eclipse.jgit.junit.ssh/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.junit.ssh/META-INF/MANIFEST.MF
@@ -3,34 +3,35 @@
 Bundle-Name: %plugin_name
 Automatic-Module-Name: org.eclipse.jgit.junit.ssh
 Bundle-SymbolicName: org.eclipse.jgit.junit.ssh
-Bundle-Version: 5.3.10.qualifier
+Bundle-Version: 5.4.4.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.3.10,5.4.0)",
- org.eclipse.jgit.lib;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.transport;version="[5.3.10,5.4.0)",
+Import-Package: org.apache.sshd.common;version="[2.2.0,2.3.0)",
+ org.apache.sshd.common.config.keys;version="[2.2.0,2.3.0)",
+ org.apache.sshd.common.file.virtualfs;version="[2.2.0,2.3.0)",
+ org.apache.sshd.common.helpers;version="[2.2.0,2.3.0)",
+ org.apache.sshd.common.io;version="[2.2.0,2.3.0)",
+ org.apache.sshd.common.kex;version="[2.2.0,2.3.0)",
+ org.apache.sshd.common.keyprovider;version="[2.2.0,2.3.0)",
+ org.apache.sshd.common.session;version="[2.2.0,2.3.0)",
+ org.apache.sshd.common.util.buffer;version="[2.2.0,2.3.0)",
+ org.apache.sshd.common.util.logging;version="[2.2.0,2.3.0)",
+ org.apache.sshd.common.util.security;version="[2.2.0,2.3.0)",
+ org.apache.sshd.common.util.threads;version="[2.2.0,2.3.0)",
+ org.apache.sshd.server;version="[2.2.0,2.3.0)",
+ org.apache.sshd.server.auth;version="[2.2.0,2.3.0)",
+ org.apache.sshd.server.auth.gss;version="[2.2.0,2.3.0)",
+ org.apache.sshd.server.auth.keyboard;version="[2.2.0,2.3.0)",
+ org.apache.sshd.server.auth.password;version="[2.2.0,2.3.0)",
+ org.apache.sshd.server.command;version="[2.2.0,2.3.0)",
+ org.apache.sshd.server.session;version="[2.2.0,2.3.0)",
+ org.apache.sshd.server.shell;version="[2.2.0,2.3.0)",
+ org.apache.sshd.server.subsystem;version="[2.2.0,2.3.0)",
+ org.apache.sshd.server.subsystem.sftp;version="[2.2.0,2.3.0)",
+ org.eclipse.jgit.annotations;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.lib;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.transport;version="[5.4.4,5.5.0)",
  org.slf4j;version="[1.7.0,2.0.0)"
-Export-Package: org.eclipse.jgit.junit.ssh;version="5.3.10"
+Export-Package: org.eclipse.jgit.junit.ssh;version="5.4.4"
diff --git a/org.eclipse.jgit.junit.ssh/META-INF/SOURCE-MANIFEST.MF b/org.eclipse.jgit.junit.ssh/META-INF/SOURCE-MANIFEST.MF
new file mode 100644
index 0000000..a0b8429
--- /dev/null
+++ b/org.eclipse.jgit.junit.ssh/META-INF/SOURCE-MANIFEST.MF
@@ -0,0 +1,7 @@
+Manifest-Version: 1.0
+Bundle-ManifestVersion: 2
+Bundle-Name: org.eclipse.jgit.junit.ssh - Sources
+Bundle-SymbolicName: org.eclipse.jgit.junit.ssh.source
+Bundle-Vendor: Eclipse.org - JGit
+Bundle-Version: 5.4.4.qualifier
+Eclipse-SourceBundle: org.eclipse.jgit.junit.ssh;version="5.4.4.qualifier";roots="."
diff --git a/org.eclipse.jgit.junit.ssh/pom.xml b/org.eclipse.jgit.junit.ssh/pom.xml
index 1d7d8a7..9e91018 100644
--- a/org.eclipse.jgit.junit.ssh/pom.xml
+++ b/org.eclipse.jgit.junit.ssh/pom.xml
@@ -50,7 +50,7 @@
   <parent>
     <groupId>org.eclipse.jgit</groupId>
     <artifactId>org.eclipse.jgit-parent</artifactId>
-    <version>5.3.10-SNAPSHOT</version>
+    <version>5.4.4-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.junit.ssh</artifactId>
@@ -62,6 +62,7 @@
 
   <properties>
     <translate-qualifier/>
+    <source-bundle-manifest>${project.build.directory}/META-INF/SOURCE-MANIFEST.MF</source-bundle-manifest>
   </properties>
 
   <dependencies>
@@ -73,7 +74,7 @@
 
     <dependency>
       <groupId>org.apache.sshd</groupId>
-      <artifactId>sshd-core</artifactId>
+      <artifactId>sshd-osgi</artifactId>
       <version>${apache-sshd-version}</version>
     </dependency>
 
@@ -104,6 +105,48 @@
 
     <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>
diff --git a/org.eclipse.jgit.junit.ssh/src/org/eclipse/jgit/junit/ssh/SshTestGitServer.java b/org.eclipse.jgit.junit.ssh/src/org/eclipse/jgit/junit/ssh/SshTestGitServer.java
index f5af2e5..25d952f 100644
--- a/org.eclipse.jgit.junit.ssh/src/org/eclipse/jgit/junit/ssh/SshTestGitServer.java
+++ b/org.eclipse.jgit.junit.ssh/src/org/eclipse/jgit/junit/ssh/SshTestGitServer.java
@@ -55,10 +55,9 @@
 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.NamedResource;
 import org.apache.sshd.common.SshConstants;
 import org.apache.sshd.common.config.keys.AuthorizedKeyEntry;
 import org.apache.sshd.common.config.keys.KeyUtils;
@@ -67,6 +66,8 @@
 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.common.util.threads.CloseableExecutorService;
+import org.apache.sshd.common.util.threads.ThreadUtils;
 import org.apache.sshd.server.ServerAuthenticationManager;
 import org.apache.sshd.server.SshServer;
 import org.apache.sshd.server.auth.UserAuth;
@@ -110,8 +111,8 @@
 	@NonNull
 	protected PublicKey testKey;
 
-	private final ExecutorService executorService = Executors
-			.newFixedThreadPool(2);
+	private final CloseableExecutorService executorService = ThreadUtils
+			.newFixedThreadPool("SshTestGitServerPool", 2);
 
 	/**
 	 * Creates a ssh git <em>test</em> server. It serves one single repository,
@@ -138,11 +139,12 @@
 		server = SshServer.setUpDefaultServer();
 		// Set host key
 		try (ByteArrayInputStream in = new ByteArrayInputStream(hostKey)) {
-			hostKeys.add(SecurityUtils.loadKeyPairIdentity("", in, null));
+			SecurityUtils.loadKeyPairIdentities(null, null, in, null)
+					.forEach((k) -> hostKeys.add(k));
 		} catch (IOException | GeneralSecurityException e) {
 			// Ignore.
 		}
-		server.setKeyPairProvider(() -> hostKeys);
+		server.setKeyPairProvider((session) -> hostKeys);
 
 		configureAuthentication();
 
@@ -276,8 +278,10 @@
 	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);
+			KeyPair pair = SecurityUtils
+					.loadKeyPairIdentities(null,
+							NamedResource.ofName(key.toString()), in, null)
+					.iterator().next();
 			if (inFront) {
 				hostKeys.add(0, pair);
 			} else {
@@ -335,14 +339,14 @@
 	public void setTestUserPublicKey(Path key)
 			throws IOException, GeneralSecurityException {
 		this.testKey = AuthorizedKeyEntry.readAuthorizedKeys(key).get(0)
-				.resolvePublicKey(PublicKeyEntryResolver.IGNORING);
+				.resolvePublicKey(null, PublicKeyEntryResolver.IGNORING);
 	}
 
 	private class GitUploadPackCommand extends AbstractCommandSupport {
 
 		protected GitUploadPackCommand(String command,
-				ExecutorService executorService) {
-			super(command, executorService, false);
+				CloseableExecutorService executorService) {
+			super(command, ThreadUtils.noClose(executorService));
 		}
 
 		@Override
@@ -370,8 +374,8 @@
 	private class GitReceivePackCommand extends AbstractCommandSupport {
 
 		protected GitReceivePackCommand(String command,
-				ExecutorService executorService) {
-			super(command, executorService, false);
+				CloseableExecutorService executorService) {
+			super(command, ThreadUtils.noClose(executorService));
 		}
 
 		@Override
diff --git a/org.eclipse.jgit.junit/.settings/.api_filters b/org.eclipse.jgit.junit/.settings/.api_filters
new file mode 100644
index 0000000..05665a3
--- /dev/null
+++ b/org.eclipse.jgit.junit/.settings/.api_filters
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<component id="org.eclipse.jgit.junit" version="2">
+    <resource path="src/org/eclipse/jgit/junit/Repeat.java" type="org.eclipse.jgit.junit.Repeat">
+        <filter id="1092616195">
+            <message_arguments>
+                <message_argument value="5.1.9"/>
+                <message_argument value="abortOnFailure()"/>
+            </message_arguments>
+        </filter>
+    </resource>
+</component>
diff --git a/org.eclipse.jgit.junit/META-INF/MANIFEST.MF b/org.eclipse.jgit.junit/META-INF/MANIFEST.MF
index 45c873a..4b4f882 100644
--- a/org.eclipse.jgit.junit/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.junit/META-INF/MANIFEST.MF
@@ -3,34 +3,34 @@
 Bundle-Name: %plugin_name
 Automatic-Module-Name: org.eclipse.jgit.junit
 Bundle-SymbolicName: org.eclipse.jgit.junit
-Bundle-Version: 5.3.10.qualifier
+Bundle-Version: 5.4.4.qualifier
 Bundle-Localization: plugin
 Bundle-Vendor: %provider_name
 Bundle-ActivationPolicy: lazy
 Bundle-RequiredExecutionEnvironment: JavaSE-1.8
-Import-Package: org.eclipse.jgit.annotations;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.api;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.api.errors;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.dircache;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.errors;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.internal.storage.file;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.internal.storage.pack;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.lib;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.merge;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.revwalk;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.storage.file;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.transport;version="5.3.10",
- org.eclipse.jgit.treewalk;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.treewalk.filter;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.util;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.util.io;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.util.time;version="[5.3.10,5.4.0)",
+Import-Package: org.eclipse.jgit.annotations;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.api;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.api.errors;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.dircache;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.errors;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.internal.storage.file;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.internal.storage.pack;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.lib;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.merge;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.revwalk;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.storage.file;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.transport;version="5.4.4",
+ org.eclipse.jgit.treewalk;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.treewalk.filter;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.util;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.util.io;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.util.time;version="[5.4.4,5.5.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)",
  org.slf4j;version="[1.7.0,2.0.0)"
-Export-Package: org.eclipse.jgit.junit;version="5.3.10";
+Export-Package: org.eclipse.jgit.junit;version="5.4.4";
   uses:="org.eclipse.jgit.dircache,
    org.eclipse.jgit.lib,
    org.eclipse.jgit.revwalk,
@@ -43,4 +43,4 @@
    org.junit.runners.model,
    org.junit.runner,
    org.eclipse.jgit.util.time",
- org.eclipse.jgit.junit.time;version="5.3.10";uses:="org.eclipse.jgit.util.time"
+ org.eclipse.jgit.junit.time;version="5.4.4";uses:="org.eclipse.jgit.util.time"
diff --git a/org.eclipse.jgit.junit/META-INF/SOURCE-MANIFEST.MF b/org.eclipse.jgit.junit/META-INF/SOURCE-MANIFEST.MF
new file mode 100644
index 0000000..02bc82d
--- /dev/null
+++ b/org.eclipse.jgit.junit/META-INF/SOURCE-MANIFEST.MF
@@ -0,0 +1,7 @@
+Manifest-Version: 1.0
+Bundle-ManifestVersion: 2
+Bundle-Name: org.eclipse.jgit.junit - Sources
+Bundle-SymbolicName: org.eclipse.jgit.junit.source
+Bundle-Vendor: Eclipse.org - JGit
+Bundle-Version: 5.4.4.qualifier
+Eclipse-SourceBundle: org.eclipse.jgit.junit;version="5.4.4.qualifier";roots="."
diff --git a/org.eclipse.jgit.junit/pom.xml b/org.eclipse.jgit.junit/pom.xml
index 6c18355..ff4c0e7 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.3.10-SNAPSHOT</version>
+    <version>5.4.4-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.junit</artifactId>
@@ -64,6 +64,7 @@
 
   <properties>
     <translate-qualifier/>
+    <source-bundle-manifest>${project.build.directory}/META-INF/SOURCE-MANIFEST.MF</source-bundle-manifest>
   </properties>
 
   <dependencies>
@@ -94,6 +95,48 @@
 
     <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>
diff --git a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/LocalDiskRepositoryTestCase.java b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/LocalDiskRepositoryTestCase.java
index 3b7cdde..5c2cd6a 100644
--- a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/LocalDiskRepositoryTestCase.java
+++ b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/LocalDiskRepositoryTestCase.java
@@ -373,7 +373,9 @@
 	/**
 	 * Creates a new empty bare repository.
 	 *
-	 * @return the newly created repository, opened for access
+	 * @return the newly created bare repository, opened for access. The
+	 *         repository will not be closed in {@link #tearDown()}; the caller
+	 *         is responsible for closing it.
 	 * @throws IOException
 	 *             the repository could not be created in the temporary area
 	 */
@@ -384,7 +386,9 @@
 	/**
 	 * Creates a new empty repository within a new empty working directory.
 	 *
-	 * @return the newly created repository, opened for access
+	 * @return the newly created repository, opened for access. The repository
+	 *         will not be closed in {@link #tearDown()}; the caller is
+	 *         responsible for closing it.
 	 * @throws IOException
 	 *             the repository could not be created in the temporary area
 	 */
@@ -398,7 +402,9 @@
 	 * @param bare
 	 *            true to create a bare repository; false to make a repository
 	 *            within its working directory
-	 * @return the newly created repository, opened for access
+	 * @return the newly created repository, opened for access. The repository
+	 *         will not be closed in {@link #tearDown()}; the caller is
+	 *         responsible for closing it.
 	 * @throws IOException
 	 *             the repository could not be created in the temporary area
 	 * @since 5.3
@@ -555,13 +561,7 @@
 		try {
 			write(f, body);
 			return f;
-		} catch (Error e) {
-			f.delete();
-			throw e;
-		} catch (RuntimeException e) {
-			f.delete();
-			throw e;
-		} catch (IOException e) {
+		} catch (Error | RuntimeException | IOException e) {
 			f.delete();
 			throw e;
 		}
diff --git a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/MockSystemReader.java b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/MockSystemReader.java
index 123fdb3..13c2932 100644
--- a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/MockSystemReader.java
+++ b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/MockSystemReader.java
@@ -220,22 +220,20 @@
 	/** {@inheritDoc} */
 	@Override
 	public MonotonicClock getClock() {
-		return new MonotonicClock() {
-			@Override
-			public ProposedTimestamp propose() {
-				long t = getCurrentTime();
-				return new ProposedTimestamp() {
-					@Override
-					public long read(TimeUnit unit) {
-						return unit.convert(t, TimeUnit.MILLISECONDS);
-					}
+		return () -> {
+			long t = getCurrentTime();
+			return new ProposedTimestamp() {
 
-					@Override
-					public void blockUntil(Duration maxWait) {
-						// Do not wait.
-					}
-				};
-			}
+				@Override
+				public long read(TimeUnit unit) {
+					return unit.convert(t, TimeUnit.MILLISECONDS);
+				}
+
+				@Override
+				public void blockUntil(Duration maxWait) {
+					// Do not wait.
+				}
+			};
 		};
 	}
 
diff --git a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/RepositoryTestCase.java b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/RepositoryTestCase.java
index 5aacbba..23f49a4 100644
--- a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/RepositoryTestCase.java
+++ b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/RepositoryTestCase.java
@@ -496,9 +496,7 @@
 				git.branchCreate().setName(branch).setStartPoint(commit).call();
 
 			return commit;
-		} catch (IOException e) {
-			throw new RuntimeException(e);
-		} catch (GitAPIException e) {
+		} catch (IOException | GitAPIException e) {
 			throw new RuntimeException(e);
 		}
 	}
diff --git a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/TestRepository.java b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/TestRepository.java
index 02ffe4f..e89cf0f 100644
--- a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/TestRepository.java
+++ b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/TestRepository.java
@@ -728,7 +728,7 @@
 		ThreeWayMerger merger = MergeStrategy.RECURSIVE.newMerger(db, true);
 		merger.setBase(parent.getTree());
 		if (merger.merge(head, commit)) {
-			if (AnyObjectId.equals(head.getTree(), merger.getResultTreeId()))
+			if (AnyObjectId.isEqual(head.getTree(), merger.getResultTreeId()))
 				return null;
 			tick(1);
 			org.eclipse.jgit.lib.CommitBuilder b =
@@ -1368,7 +1368,7 @@
 				firstParentId = parents.get(0);
 
 			ObjectId cid;
-			if (changeId.equals(""))
+			if (changeId.isEmpty())
 				cid = ChangeIdUtil.computeChangeId(c.getTreeId(), firstParentId,
 						c.getAuthor(), c.getCommitter(), message);
 			else
diff --git a/org.eclipse.jgit.lfs.server.test/.settings/org.eclipse.pde.prefs b/org.eclipse.jgit.lfs.server.test/.settings/org.eclipse.pde.prefs
new file mode 100644
index 0000000..2174e4f
--- /dev/null
+++ b/org.eclipse.jgit.lfs.server.test/.settings/org.eclipse.pde.prefs
@@ -0,0 +1,34 @@
+compilers.f.unresolved-features=1
+compilers.f.unresolved-plugins=1
+compilers.incompatible-environment=1
+compilers.p.build=1
+compilers.p.build.bin.includes=1
+compilers.p.build.encodings=2
+compilers.p.build.java.compiler=2
+compilers.p.build.java.compliance=1
+compilers.p.build.missing.output=2
+compilers.p.build.output.library=1
+compilers.p.build.source.library=1
+compilers.p.build.src.includes=1
+compilers.p.deprecated=1
+compilers.p.discouraged-class=1
+compilers.p.internal=1
+compilers.p.missing-packages=2
+compilers.p.missing-version-export-package=2
+compilers.p.missing-version-import-package=2
+compilers.p.missing-version-require-bundle=2
+compilers.p.no-required-att=0
+compilers.p.no.automatic.module=1
+compilers.p.not-externalized-att=2
+compilers.p.service.component.without.lazyactivation=1
+compilers.p.unknown-attribute=1
+compilers.p.unknown-class=1
+compilers.p.unknown-element=1
+compilers.p.unknown-identifier=1
+compilers.p.unknown-resource=1
+compilers.p.unresolved-ex-points=0
+compilers.p.unresolved-import=0
+compilers.s.create-docs=false
+compilers.s.doc-folder=doc
+compilers.s.open-tags=1
+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 10d87e8..be2ff2e 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.3.10.qualifier
+Bundle-Version: 5.4.4.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.3.10,5.4.0)",
- org.eclipse.jgit.api.errors;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.internal.storage.file;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.junit;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.junit.http;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.lfs;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.lfs.errors;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.lfs.lib;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.lfs.server;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.lfs.server.fs;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.lfs.test;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.lib;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.revwalk;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.storage.file;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.transport;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.treewalk;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.treewalk.filter;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.util;version="[5.3.10,5.4.0)",
+ org.eclipse.jgit.api;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.api.errors;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.internal.storage.file;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.junit;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.junit.http;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.lfs;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.lfs.errors;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.lfs.lib;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.lfs.server;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.lfs.server.fs;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.lfs.test;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.lib;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.revwalk;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.storage.file;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.transport;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.treewalk;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.treewalk.filter;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.util;version="[5.4.4,5.5.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 cff60d9..fdcf556 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.3.10-SNAPSHOT</version>
+    <version>5.4.4-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}  -Xmx300m</argLine>
+          <argLine>@{argLine} -Djava.io.tmpdir=${project.build.directory}  -Xmx512m</argLine>
         </configuration>
       </plugin>
     </plugins>
diff --git a/org.eclipse.jgit.lfs.server.test/tst/org/eclipse/jgit/lfs/server/fs/UploadTest.java b/org.eclipse.jgit.lfs.server.test/tst/org/eclipse/jgit/lfs/server/fs/UploadTest.java
index 09f8d0a..2334ec3 100644
--- a/org.eclipse.jgit.lfs.server.test/tst/org/eclipse/jgit/lfs/server/fs/UploadTest.java
+++ b/org.eclipse.jgit.lfs.server.test/tst/org/eclipse/jgit/lfs/server/fs/UploadTest.java
@@ -44,6 +44,7 @@
 
 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.Assert.fail;
 
@@ -53,10 +54,10 @@
 import java.text.MessageFormat;
 import java.util.ArrayList;
 import java.util.List;
-import java.util.concurrent.Callable;
 import java.util.concurrent.CyclicBarrier;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
 import java.util.concurrent.TimeUnit;
 
 import org.eclipse.jgit.lfs.lib.AnyLongObjectId;
@@ -123,14 +124,12 @@
 		ExecutorService e = Executors.newFixedThreadPool(count);
 		try {
 			for (Path p : paths) {
-				e.submit(new Callable<Void>() {
-					@Override
-					public Void call() throws Exception {
-						barrier.await();
-						putContent(p);
-						return null;
-					}
+				Future<Object> result = e.submit(() -> {
+					barrier.await();
+					putContent(p);
+					return null;
 				});
+				assertNotNull(result);
 			}
 		} finally {
 			e.shutdown();
diff --git a/org.eclipse.jgit.lfs.server/META-INF/MANIFEST.MF b/org.eclipse.jgit.lfs.server/META-INF/MANIFEST.MF
index b1f9dec..a97e096 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.3.10.qualifier
+Bundle-Version: 5.4.4.qualifier
 Bundle-Localization: plugin
 Bundle-Vendor: %provider_name
-Export-Package: org.eclipse.jgit.lfs.server;version="5.3.10";
+Export-Package: org.eclipse.jgit.lfs.server;version="5.4.4";
   uses:="javax.servlet.http,
    org.eclipse.jgit.lfs.lib",
- org.eclipse.jgit.lfs.server.fs;version="5.3.10";
+ org.eclipse.jgit.lfs.server.fs;version="5.4.4";
   uses:="javax.servlet,
    javax.servlet.http,
    org.eclipse.jgit.lfs.server,
    org.eclipse.jgit.lfs.lib",
- org.eclipse.jgit.lfs.server.internal;version="5.3.10";x-internal:=true,
- org.eclipse.jgit.lfs.server.s3;version="5.3.10";
+ org.eclipse.jgit.lfs.server.internal;version="5.4.4";x-internal:=true,
+ org.eclipse.jgit.lfs.server.s3;version="5.4.4";
   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.3.10,5.4.0)",
- org.eclipse.jgit.internal;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.internal.storage.file;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.lfs.errors;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.lfs.internal;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.lfs.lib;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.lib;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.nls;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.transport.http;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.transport.http.apache;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.util;version="[5.3.10,5.4.0)",
+ org.eclipse.jgit.annotations;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.internal;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.internal.storage.file;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.lfs.errors;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.lfs.internal;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.lfs.lib;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.lib;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.nls;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.transport.http;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.transport.http.apache;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.util;version="[5.4.4,5.5.0)",
  org.slf4j;version="[1.7.0,2.0.0)"
diff --git a/org.eclipse.jgit.lfs.server/META-INF/SOURCE-MANIFEST.MF b/org.eclipse.jgit.lfs.server/META-INF/SOURCE-MANIFEST.MF
new file mode 100644
index 0000000..41435df
--- /dev/null
+++ b/org.eclipse.jgit.lfs.server/META-INF/SOURCE-MANIFEST.MF
@@ -0,0 +1,7 @@
+Manifest-Version: 1.0
+Bundle-ManifestVersion: 2
+Bundle-Name: org.eclipse.jgit.lfs.server - Sources
+Bundle-SymbolicName: org.eclipse.jgit.lfs.server.source
+Bundle-Vendor: Eclipse.org - JGit
+Bundle-Version: 5.4.4.qualifier
+Eclipse-SourceBundle: org.eclipse.jgit.lfs.server;version="5.4.4.qualifier";roots="."
diff --git a/org.eclipse.jgit.lfs.server/pom.xml b/org.eclipse.jgit.lfs.server/pom.xml
index c893f83..201a5f0 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.3.10-SNAPSHOT</version>
+    <version>5.4.4-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.lfs.server</artifactId>
@@ -62,6 +62,7 @@
 
   <properties>
     <translate-qualifier/>
+    <source-bundle-manifest>${project.build.directory}/META-INF/SOURCE-MANIFEST.MF</source-bundle-manifest>
   </properties>
 
   <dependencies>
@@ -114,6 +115,28 @@
     <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>
diff --git a/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/TransferHandler.java b/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/TransferHandler.java
index 4fea92e..6c36661 100644
--- a/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/TransferHandler.java
+++ b/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/TransferHandler.java
@@ -84,7 +84,7 @@
 		@Override
 		Body process() throws IOException {
 			Response.Body body = new Response.Body();
-			if (objects.size() > 0) {
+			if (!objects.isEmpty()) {
 				body.objects = new ArrayList<>();
 				for (LfsObject o : objects) {
 					addObjectInfo(body, o);
@@ -122,7 +122,7 @@
 		@Override
 		Body process() throws IOException {
 			Response.Body body = new Response.Body();
-			if (objects.size() > 0) {
+			if (!objects.isEmpty()) {
 				body.objects = new ArrayList<>();
 				for (LfsObject o : objects) {
 					addObjectInfo(body, o);
diff --git a/org.eclipse.jgit.lfs.test/.settings/org.eclipse.pde.prefs b/org.eclipse.jgit.lfs.test/.settings/org.eclipse.pde.prefs
new file mode 100644
index 0000000..2174e4f
--- /dev/null
+++ b/org.eclipse.jgit.lfs.test/.settings/org.eclipse.pde.prefs
@@ -0,0 +1,34 @@
+compilers.f.unresolved-features=1
+compilers.f.unresolved-plugins=1
+compilers.incompatible-environment=1
+compilers.p.build=1
+compilers.p.build.bin.includes=1
+compilers.p.build.encodings=2
+compilers.p.build.java.compiler=2
+compilers.p.build.java.compliance=1
+compilers.p.build.missing.output=2
+compilers.p.build.output.library=1
+compilers.p.build.source.library=1
+compilers.p.build.src.includes=1
+compilers.p.deprecated=1
+compilers.p.discouraged-class=1
+compilers.p.internal=1
+compilers.p.missing-packages=2
+compilers.p.missing-version-export-package=2
+compilers.p.missing-version-import-package=2
+compilers.p.missing-version-require-bundle=2
+compilers.p.no-required-att=0
+compilers.p.no.automatic.module=1
+compilers.p.not-externalized-att=2
+compilers.p.service.component.without.lazyactivation=1
+compilers.p.unknown-attribute=1
+compilers.p.unknown-class=1
+compilers.p.unknown-element=1
+compilers.p.unknown-identifier=1
+compilers.p.unknown-resource=1
+compilers.p.unresolved-ex-points=0
+compilers.p.unresolved-import=0
+compilers.s.create-docs=false
+compilers.s.doc-folder=doc
+compilers.s.open-tags=1
+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 ea1a297..c44feb6 100644
--- a/org.eclipse.jgit.lfs.test/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.lfs.test/META-INF/MANIFEST.MF
@@ -3,23 +3,22 @@
 Bundle-Name: %plugin_name
 Automatic-Module-Name: org.eclipse.jgit.lfs.test
 Bundle-SymbolicName: org.eclipse.jgit.lfs.test
-Bundle-Version: 5.3.10.qualifier
+Bundle-Version: 5.4.4.qualifier
 Bundle-Vendor: %provider_name
 Bundle-Localization: plugin
 Bundle-RequiredExecutionEnvironment: JavaSE-1.8
-Import-Package: org.eclipse.jgit.internal.storage.dfs;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.junit;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.lfs;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.lfs.errors;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.lfs.lib;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.lib;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.revwalk;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.treewalk;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.treewalk.filter;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.util;version="[5.3.10,5.4.0)",
+Import-Package: org.eclipse.jgit.internal.storage.dfs;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.junit;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.lfs;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.lfs.errors;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.lfs.lib;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.lib;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.revwalk;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.treewalk;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.treewalk.filter;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.util;version="[5.4.4,5.5.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.3.10";x-friends:="org.eclipse.jgit.lfs.server.test"
-
+Export-Package: org.eclipse.jgit.lfs.test;version="5.4.4";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 ac85b19..0494322 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.3.10-SNAPSHOT</version>
+    <version>5.4.4-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}  -Xmx300m</argLine>
+          <argLine>@{argLine} -Djava.io.tmpdir=${project.build.directory}  -Xmx512m</argLine>
         </configuration>
       </plugin>
     </plugins>
diff --git a/org.eclipse.jgit.lfs/META-INF/MANIFEST.MF b/org.eclipse.jgit.lfs/META-INF/MANIFEST.MF
index b7934a3..097ec66 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.3.10.qualifier
+Bundle-Version: 5.4.4.qualifier
 Bundle-Localization: plugin
 Bundle-Vendor: %provider_name
-Export-Package: org.eclipse.jgit.lfs;version="5.3.10",
- org.eclipse.jgit.lfs.errors;version="5.3.10",
- org.eclipse.jgit.lfs.internal;version="5.3.10";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.3.10"
+Export-Package: org.eclipse.jgit.lfs;version="5.4.4",
+ org.eclipse.jgit.lfs.errors;version="5.4.4",
+ org.eclipse.jgit.lfs.internal;version="5.4.4";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.4.4"
 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.3.10,5.4.0)";resolution:=optional,
- org.eclipse.jgit.api.errors;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.attributes;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.diff;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.errors;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.hooks;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.internal.storage.file;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.lib;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.nls;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.revwalk;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.storage.file;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.storage.pack;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.transport;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.transport.http;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.treewalk;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.treewalk.filter;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.util;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.util.io;version="[5.3.10,5.4.0)"
+ org.eclipse.jgit.annotations;version="[5.4.4,5.5.0)";resolution:=optional,
+ org.eclipse.jgit.api.errors;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.attributes;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.diff;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.errors;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.hooks;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.internal.storage.file;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.lib;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.nls;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.revwalk;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.storage.file;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.storage.pack;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.transport;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.transport.http;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.treewalk;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.treewalk.filter;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.util;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.util.io;version="[5.4.4,5.5.0)"
diff --git a/org.eclipse.jgit.lfs/META-INF/SOURCE-MANIFEST.MF b/org.eclipse.jgit.lfs/META-INF/SOURCE-MANIFEST.MF
new file mode 100644
index 0000000..dda63a9
--- /dev/null
+++ b/org.eclipse.jgit.lfs/META-INF/SOURCE-MANIFEST.MF
@@ -0,0 +1,7 @@
+Manifest-Version: 1.0
+Bundle-ManifestVersion: 2
+Bundle-Name: org.eclipse.jgit.lfs - Sources
+Bundle-SymbolicName: org.eclipse.jgit.lfs.source
+Bundle-Vendor: Eclipse.org - JGit
+Bundle-Version: 5.4.4.qualifier
+Eclipse-SourceBundle: org.eclipse.jgit.lfs;version="5.4.4.qualifier";roots="."
diff --git a/org.eclipse.jgit.lfs/pom.xml b/org.eclipse.jgit.lfs/pom.xml
index f1779e9..b66eb24 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.3.10-SNAPSHOT</version>
+    <version>5.4.4-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.lfs</artifactId>
@@ -62,6 +62,7 @@
 
   <properties>
     <translate-qualifier/>
+    <source-bundle-manifest>${project.build.directory}/META-INF/SOURCE-MANIFEST.MF</source-bundle-manifest>
   </properties>
 
   <dependencies>
@@ -94,6 +95,28 @@
     <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>
diff --git a/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/CleanFilter.java b/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/CleanFilter.java
index 217e46f..80da802 100644
--- a/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/CleanFilter.java
+++ b/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/CleanFilter.java
@@ -78,14 +78,7 @@
 	 * The factory is responsible for creating instances of
 	 * {@link org.eclipse.jgit.lfs.CleanFilter}
 	 */
-	public final static FilterCommandFactory FACTORY = new FilterCommandFactory() {
-
-		@Override
-		public FilterCommand create(Repository db, InputStream in,
-				OutputStream out) throws IOException {
-			return new CleanFilter(db, in, out);
-		}
-	};
+	public final static FilterCommandFactory FACTORY = CleanFilter::new;
 
 	/**
 	 * Registers this filter by calling
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 7bacf49..6b30da3 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
@@ -92,13 +92,7 @@
 	 * The factory is responsible for creating instances of
 	 * {@link org.eclipse.jgit.lfs.SmudgeFilter}
 	 */
-	public final static FilterCommandFactory FACTORY = new FilterCommandFactory() {
-		@Override
-		public FilterCommand create(Repository db, InputStream in,
-				OutputStream out) throws IOException {
-			return new SmudgeFilter(db, in, out);
-		}
-	};
+	public final static FilterCommandFactory FACTORY = SmudgeFilter::new;
 
 	/**
 	 * Register this filter in JGit
diff --git a/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/lib/AbbreviatedLongObjectId.java b/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/lib/AbbreviatedLongObjectId.java
index bdd1b39..5109d9b 100644
--- a/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/lib/AbbreviatedLongObjectId.java
+++ b/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/lib/AbbreviatedLongObjectId.java
@@ -170,7 +170,7 @@
 			r |= RawParseUtils.parseHexInt4(bs[p++]);
 			n++;
 		}
-		return r << (16 - n) * 4;
+		return r << ((16 - n) * 4);
 	}
 
 	static long mask(int nibbles, long word, long v) {
diff --git a/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/lib/AnyLongObjectId.java b/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/lib/AnyLongObjectId.java
index 0788922..b095d20 100644
--- a/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/lib/AnyLongObjectId.java
+++ b/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/lib/AnyLongObjectId.java
@@ -50,6 +50,7 @@
 
 import org.eclipse.jgit.lib.AnyObjectId;
 import org.eclipse.jgit.util.NB;
+import org.eclipse.jgit.util.References;
 
 /**
  * A (possibly mutable) SHA-256 abstraction.
@@ -73,11 +74,31 @@
 	 * @param secondObjectId
 	 *            the second identifier to compare. Must not be null.
 	 * @return true if the two identifiers are the same.
+	 * @deprecated use {@link #isEqual(AnyLongObjectId, AnyLongObjectId)}
+	 *             instead.
 	 */
+	@Deprecated
+	@SuppressWarnings("AmbiguousMethodReference")
 	public static boolean equals(final AnyLongObjectId firstObjectId,
 			final AnyLongObjectId secondObjectId) {
-		if (firstObjectId == secondObjectId)
+		return isEqual(firstObjectId, secondObjectId);
+	}
+
+	/**
+	 * Compare two object identifier byte sequences for equality.
+	 *
+	 * @param firstObjectId
+	 *            the first identifier to compare. Must not be null.
+	 * @param secondObjectId
+	 *            the second identifier to compare. Must not be null.
+	 * @return true if the two identifiers are the same.
+	 * @since 5.4
+	 */
+	public static boolean isEqual(final AnyLongObjectId firstObjectId,
+			final AnyLongObjectId secondObjectId) {
+		if (References.isSameObject(firstObjectId, secondObjectId)) {
 			return true;
+		}
 
 		// We test word 2 first as odds are someone already used our
 		// word 1 as a hash code, and applying that came up with these
@@ -274,6 +295,7 @@
 	 *            the other id to compare to. May be null.
 	 * @return true only if both LongObjectIds have identical bits.
 	 */
+	@SuppressWarnings({ "NonOverridingEquals", "AmbiguousMethodReference" })
 	public final boolean equals(AnyLongObjectId other) {
 		return other != null ? equals(this, other) : false;
 	}
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 bb3b77e..791f8c0 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.3.10.qualifier"
+      version="5.4.4.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 9e1634e..3c759d4 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.3.10-SNAPSHOT</version>
+    <version>5.4.4-SNAPSHOT</version>
   </parent>
 
   <groupId>org.eclipse.jgit.feature</groupId>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.http.apache.feature/feature.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.http.apache.feature/feature.xml
index bc1f1d1..7343dd6 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.3.10.qualifier"
+      version="5.4.4.qualifier"
       provider-name="%providerName">
 
    <description url="http://www.eclipse.org/jgit/">
@@ -23,7 +23,7 @@
    </url>
 
    <requires>
-      <import plugin="org.eclipse.jgit"/>
+      <import plugin="org.eclipse.jgit" version="5.4.4" match="equivalent"/>
    </requires>
 
    <plugin
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.http.apache.feature/pom.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.http.apache.feature/pom.xml
index b43e66c..5006993 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.3.10-SNAPSHOT</version>
+    <version>5.4.4-SNAPSHOT</version>
   </parent>
 
   <groupId>org.eclipse.jgit.feature</groupId>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.junit.feature/feature.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.junit.feature/feature.xml
index 1d69d7e..f8ed86a 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.3.10.qualifier"
+      version="5.4.4.qualifier"
       provider-name="%providerName">
 
    <description url="http://www.eclipse.org/jgit/">
@@ -24,6 +24,7 @@
 
    <requires>
       <import plugin="com.jcraft.jsch"/>
+      <import plugin="org.eclipse.jgit" version="5.4.4" match="equivalent"/>
    </requires>
 
    <plugin
@@ -62,7 +63,7 @@
          unpack="false"/>
 
    <plugin
-         id="org.apache.sshd.core"
+         id="org.apache.sshd.osgi"
          download-size="0"
          install-size="0"
          version="0.0.0"
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 4e6292e..0df1c8a 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.3.10-SNAPSHOT</version>
+    <version>5.4.4-SNAPSHOT</version>
   </parent>
 
   <groupId>org.eclipse.jgit.feature</groupId>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.lfs.feature/feature.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.lfs.feature/feature.xml
index 8c3a710..c99f82d 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.3.10.qualifier"
+      version="5.4.4.qualifier"
       provider-name="%providerName">
 
    <description url="http://www.eclipse.org/jgit/">
@@ -23,7 +23,7 @@
    </url>
 
    <requires>
-      <import plugin="org.eclipse.jgit"/>
+      <import feature="org.eclipse.jgit" version="5.4.4" match="equivalent"/>
    </requires>
 
    <plugin
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.lfs.feature/pom.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.lfs.feature/pom.xml
index 8533466..2e2298c 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.3.10-SNAPSHOT</version>
+    <version>5.4.4-SNAPSHOT</version>
   </parent>
 
   <groupId>org.eclipse.jgit.feature</groupId>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.feature/feature.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.feature/feature.xml
index d3909b0..c00d360 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.3.10.qualifier"
+      version="5.4.4.qualifier"
       provider-name="%providerName">
 
    <description url="http://www.eclipse.org/jgit/">
@@ -30,10 +30,14 @@
          id="org.eclipse.jgit.lfs"
          version="0.0.0"/>
 
+   <includes
+         id="org.eclipse.jgit.ssh.apache"
+         version="0.0.0"/>
+
    <requires>
-      <import feature="org.eclipse.jgit" version="5.3.10" match="equivalent"/>
-      <import feature="org.eclipse.jgit.lfs" version="5.3.10" match="equivalent"/>
-      <import feature="org.eclipse.jgit.ssh.apache" version="5.3.10" match="equivalent"/>
+      <import feature="org.eclipse.jgit" version="5.4.4" match="equivalent"/>
+      <import feature="org.eclipse.jgit.lfs" version="5.4.4" match="equivalent"/>
+      <import feature="org.eclipse.jgit.ssh.apache" version="5.4.4" 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 9e071dc..748ee58 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.3.10-SNAPSHOT</version>
+    <version>5.4.4-SNAPSHOT</version>
   </parent>
 
   <groupId>org.eclipse.jgit.feature</groupId>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.source.feature/.gitignore b/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.source.feature/.gitignore
deleted file mode 100644
index eb5a316..0000000
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.source.feature/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-target
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.source.feature/.project b/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.source.feature/.project
deleted file mode 100644
index 5f57483..0000000
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.source.feature/.project
+++ /dev/null
@@ -1,17 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<projectDescription>
-	<name>org.eclipse.jgit.pgm.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.pgm.source.feature/.settings/org.eclipse.core.resources.prefs b/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.source.feature/.settings/org.eclipse.core.resources.prefs
deleted file mode 100644
index 6f96ce8..0000000
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.source.feature/.settings/org.eclipse.core.resources.prefs
+++ /dev/null
@@ -1,3 +0,0 @@
-#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.pgm.source.feature/.settings/org.eclipse.core.runtime.prefs b/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.source.feature/.settings/org.eclipse.core.runtime.prefs
deleted file mode 100644
index 1a32dba..0000000
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.source.feature/.settings/org.eclipse.core.runtime.prefs
+++ /dev/null
@@ -1,3 +0,0 @@
-#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.pgm.source.feature/.settings/org.eclipse.mylyn.tasks.ui.prefs b/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.source.feature/.settings/org.eclipse.mylyn.tasks.ui.prefs
deleted file mode 100644
index 823c0f5..0000000
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.source.feature/.settings/org.eclipse.mylyn.tasks.ui.prefs
+++ /dev/null
@@ -1,4 +0,0 @@
-#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.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
deleted file mode 100644
index 2fca432..0000000
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.source.feature/.settings/org.eclipse.mylyn.team.ui.prefs
+++ /dev/null
@@ -1,3 +0,0 @@
-#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.pgm.source.feature/build.properties b/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.source.feature/build.properties
deleted file mode 100644
index b4a8dde..0000000
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.source.feature/build.properties
+++ /dev/null
@@ -1,4 +0,0 @@
-bin.includes = feature.xml,\
-               edl-v10.html,\
-               feature.properties,\
-               license.html
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.source.feature/edl-v10.html b/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.source.feature/edl-v10.html
deleted file mode 100644
index 8caddac..0000000
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.source.feature/edl-v10.html
+++ /dev/null
@@ -1,59 +0,0 @@
-<?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.pgm.source.feature/feature.properties b/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.source.feature/feature.properties
deleted file mode 100644
index 73701b2..0000000
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.source.feature/feature.properties
+++ /dev/null
@@ -1,178 +0,0 @@
-###############################################################################
-# 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 - Command Line Interface - 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 support for PDE's JUnit runner for a Target Platform\n
-################ end of description property ##################################
-
-# "copyright" property - text of the "Feature Update Copyright"
-copyright=\
-Copyright (c) 2005, 2012 Shawn Pearce, Robin Rosenberg, et.al.\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.pgm.source.feature/feature.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.source.feature/feature.xml
deleted file mode 100644
index 6b09c61..0000000
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.source.feature/feature.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<feature
-      id="org.eclipse.jgit.pgm.source"
-      label="%featureName"
-      version="5.3.10.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.pgm.source"
-         download-size="0"
-         install-size="0"
-         version="0.0.0"
-         unpack="false"/>
-</feature>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.source.feature/license.html b/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.source.feature/license.html
deleted file mode 100644
index 008b801..0000000
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.source.feature/license.html
+++ /dev/null
@@ -1,189 +0,0 @@
-<?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.pgm.source.feature/pom.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.source.feature/pom.xml
deleted file mode 100644
index b26bca3..0000000
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.source.feature/pom.xml
+++ /dev/null
@@ -1,62 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-   Copyright (C) 2012, 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.
--->
-
-<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.3.10-SNAPSHOT</version>
-  </parent>
-
-  <groupId>org.eclipse.jgit.feature</groupId>
-  <artifactId>org.eclipse.jgit.pgm.source</artifactId>
-  <packaging>eclipse-feature</packaging>
-
-  <name>JGit Command Line Interface Source Feature</name>
-
-</project>
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 995bd21..e2aeee3 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.repository/category.xml
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.repository/category.xml
@@ -15,24 +15,32 @@
    <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>
    <feature url="features/org.eclipse.jgit.http.apache_0.0.0.qualifier.jar" id="org.eclipse.jgit.http.apache" version="0.0.0" patch="true">
       <category name="JGit"/>
    </feature>
-   <feature url="features/org.eclipse.jgit.lfs_0.0.0.qualifier.jar" id="org.eclipse.jgit.lfs" version="0.0.0">
+   <feature url="features/org.eclipse.jgit.lfs_0.0.0.qualifier.jar" id="org.eclipse.jgit.lfs" version="0.0.0" patch="true">
       <category name="JGit"/>
    </feature>
+   <bundle id="org.eclipse.jgit.ant" version="0.0.0">
+      <category name="JGit-additional-bundles"/>
+   </bundle>
+   <bundle id="org.eclipse.jgit.archive" version="0.0.0">
+      <category name="JGit-additional-bundles"/>
+   </bundle>
+   <bundle id="org.eclipse.jgit.ui" version="0.0.0">
+      <category name="JGit-additional-bundles"/>
+   </bundle>
    <category-def name="JGit" label="Java implementation of Git">
       <description>
          Java implementation of Git
       </description>
    </category-def>
+   <category-def name="JGit-additional-bundles" label="Java implementation of Git - additional bundles">
+      <description>
+         Java implementation of Git - additional bundles
+      </description>
+   </category-def>
 </site>
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 7f17ff7..81859e4 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.3.10-SNAPSHOT</version>
+    <version>5.4.4-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.repository</artifactId>
@@ -64,24 +64,24 @@
       <artifactId>org.eclipse.jgit</artifactId>
       <version>${project.version}</version>
     </dependency>
-	<dependency>
-	  <groupId>org.eclipse.jgit</groupId>
-	  <artifactId>org.eclipse.jgit.http.apache</artifactId>
-	  <version>${project.version}</version>
-	</dependency>
-    <dependency>
-	  <groupId>org.eclipse.jgit</groupId>
-	  <artifactId>org.eclipse.jgit.lfs</artifactId>
-	  <version>${project.version}</version>
-	</dependency>
     <dependency>
       <groupId>org.eclipse.jgit</groupId>
-      <artifactId>org.eclipse.jgit.lfs.server</artifactId>
+      <artifactId>org.eclipse.jgit.ant</artifactId>
       <version>${project.version}</version>
     </dependency>
     <dependency>
       <groupId>org.eclipse.jgit</groupId>
-      <artifactId>org.eclipse.jgit.pgm</artifactId>
+      <artifactId>org.eclipse.jgit.archive</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jgit</groupId>
+      <artifactId>org.eclipse.jgit.http.apache</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>
@@ -101,7 +101,17 @@
     </dependency>
     <dependency>
       <groupId>org.eclipse.jgit</groupId>
-      <artifactId>org.eclipse.jgit.http.server</artifactId>
+      <artifactId>org.eclipse.jgit.lfs</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jgit</groupId>
+      <artifactId>org.eclipse.jgit.lfs.server</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jgit</groupId>
+      <artifactId>org.eclipse.jgit.pgm</artifactId>
       <version>${project.version}</version>
     </dependency>
     <dependency>
@@ -109,5 +119,10 @@
       <artifactId>org.eclipse.jgit.ssh.apache</artifactId>
       <version>${project.version}</version>
     </dependency>
+    <dependency>
+      <groupId>org.eclipse.jgit</groupId>
+      <artifactId>org.eclipse.jgit.ui</artifactId>
+      <version>${project.version}</version>
+    </dependency>
   </dependencies>
 </project>
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 6c2446c..efd90a1 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,19 +2,19 @@
 <feature
       id="org.eclipse.jgit.source"
       label="%featureName"
-      version="5.3.10.qualifier"
+      version="5.4.4.qualifier"
       provider-name="%providerName">
 
    <description url="http://www.eclipse.org/jgit/">
-     %description
+      %description
    </description>
 
    <copyright>
-     %copyright
+      %copyright
    </copyright>
 
    <license url="%licenseURL">
-     %license
+      %license
    </license>
 
    <url>
@@ -22,10 +22,99 @@
       <discovery label="%updateSiteName" url="http://download.eclipse.org/egit/updates"/>
    </url>
 
+   <requires>
+      <import feature="org.eclipse.jgit" version="5.4.4" match="equivalent"/>
+   </requires>
+
    <plugin
          id="org.eclipse.jgit.source"
          download-size="0"
          install-size="0"
          version="0.0.0"
          unpack="false"/>
+
+   <plugin
+         id="org.eclipse.jgit.ant.source"
+         download-size="0"
+         install-size="0"
+         version="0.0.0"
+         unpack="false"/>
+
+   <plugin
+         id="org.eclipse.jgit.archive.source"
+         download-size="0"
+         install-size="0"
+         version="0.0.0"
+         unpack="false"/>
+
+   <plugin
+         id="org.eclipse.jgit.http.apache.source"
+         download-size="0"
+         install-size="0"
+         version="0.0.0"
+         unpack="false"/>
+
+   <plugin
+         id="org.eclipse.jgit.http.server.source"
+         download-size="0"
+         install-size="0"
+         version="0.0.0"
+         unpack="false"/>
+
+   <plugin
+         id="org.eclipse.jgit.junit.source"
+         download-size="0"
+         install-size="0"
+         version="0.0.0"
+         unpack="false"/>
+
+   <plugin
+         id="org.eclipse.jgit.junit.http.source"
+         download-size="0"
+         install-size="0"
+         version="0.0.0"
+         unpack="false"/>
+
+   <plugin
+         id="org.eclipse.jgit.junit.ssh.source"
+         download-size="0"
+         install-size="0"
+         version="0.0.0"
+         unpack="false"/>
+
+   <plugin
+         id="org.eclipse.jgit.lfs.source"
+         download-size="0"
+         install-size="0"
+         version="0.0.0"
+         unpack="false"/>
+
+   <plugin
+         id="org.eclipse.jgit.lfs.server.source"
+         download-size="0"
+         install-size="0"
+         version="0.0.0"
+         unpack="false"/>
+
+   <plugin
+         id="org.eclipse.jgit.pgm.source"
+         download-size="0"
+         install-size="0"
+         version="0.0.0"
+         unpack="false"/>
+
+   <plugin
+         id="org.eclipse.jgit.ssh.apache.source"
+         download-size="0"
+         install-size="0"
+         version="0.0.0"
+         unpack="false"/>
+
+   <plugin
+         id="org.eclipse.jgit.ui.source"
+         download-size="0"
+         install-size="0"
+         version="0.0.0"
+         unpack="false"/>
+
 </feature>
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 b4fad34..bb8fedd 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.3.10-SNAPSHOT</version>
+    <version>5.4.4-SNAPSHOT</version>
   </parent>
 
   <groupId>org.eclipse.jgit.feature</groupId>
@@ -59,4 +59,12 @@
 
   <name>JGit Source Feature</name>
 
+  <dependencies>
+    <dependency>
+      <groupId>org.eclipse.jgit.feature</groupId>
+      <artifactId>org.eclipse.jgit</artifactId>
+      <version>5.4.4-SNAPSHOT</version>
+    </dependency>
+  </dependencies>
+
 </project>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.apache.feature/feature.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.apache.feature/feature.xml
index 818c1d4..75cbdaf 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.apache.feature/feature.xml
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.apache.feature/feature.xml
@@ -2,7 +2,7 @@
 <feature
       id="org.eclipse.jgit.ssh.apache"
       label="%featureName"
-      version="5.3.10.qualifier"
+      version="5.4.4.qualifier"
       provider-name="%providerName">
 
    <description url="http://www.eclipse.org/jgit/">
@@ -23,7 +23,7 @@
    </url>
 
    <requires>
-      <import plugin="org.eclipse.jgit"/>
+      <import feature="org.eclipse.jgit" version="5.4.4" match="equivalent"/>
    </requires>
 
    <plugin
@@ -34,7 +34,7 @@
          unpack="false"/>
 
    <plugin
-         id="org.apache.sshd.core"
+         id="org.apache.sshd.osgi"
          download-size="0"
          install-size="0"
          version="0.0.0"
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.apache.feature/pom.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.apache.feature/pom.xml
index 3dc9e4d..888673a 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.apache.feature/pom.xml
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.apache.feature/pom.xml
@@ -50,7 +50,7 @@
   <parent>
     <groupId>org.eclipse.jgit</groupId>
     <artifactId>jgit.tycho.parent</artifactId>
-    <version>5.3.10-SNAPSHOT</version>
+    <version>5.4.4-SNAPSHOT</version>
   </parent>
 
   <groupId>org.eclipse.jgit.feature</groupId>
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
deleted file mode 100644
index eb5a316..0000000
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.apache.source.feature/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-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
deleted file mode 100644
index 5562084..0000000
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.apache.source.feature/.project
+++ /dev/null
@@ -1,17 +0,0 @@
-<?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
deleted file mode 100644
index 6f96ce8..0000000
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.apache.source.feature/.settings/org.eclipse.core.resources.prefs
+++ /dev/null
@@ -1,3 +0,0 @@
-#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
deleted file mode 100644
index 1a32dba..0000000
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.apache.source.feature/.settings/org.eclipse.core.runtime.prefs
+++ /dev/null
@@ -1,3 +0,0 @@
-#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
deleted file mode 100644
index 823c0f5..0000000
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.apache.source.feature/.settings/org.eclipse.mylyn.tasks.ui.prefs
+++ /dev/null
@@ -1,4 +0,0 @@
-#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
deleted file mode 100644
index 2fca432..0000000
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.apache.source.feature/.settings/org.eclipse.mylyn.team.ui.prefs
+++ /dev/null
@@ -1,3 +0,0 @@
-#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
deleted file mode 100644
index b4a8dde..0000000
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.apache.source.feature/build.properties
+++ /dev/null
@@ -1,4 +0,0 @@
-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
deleted file mode 100644
index 8caddac..0000000
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.apache.source.feature/edl-v10.html
+++ /dev/null
@@ -1,59 +0,0 @@
-<?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
deleted file mode 100644
index 9935431..0000000
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.apache.source.feature/feature.properties
+++ /dev/null
@@ -1,178 +0,0 @@
-###############################################################################
-# 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
deleted file mode 100644
index 89548d0..0000000
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.apache.source.feature/feature.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<feature
-      id="org.eclipse.jgit.ssh.apache.source"
-      label="%featureName"
-      version="5.3.10.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
deleted file mode 100644
index 008b801..0000000
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.apache.source.feature/license.html
+++ /dev/null
@@ -1,189 +0,0 @@
-<?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
deleted file mode 100644
index 958c19f..0000000
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.apache.source.feature/pom.xml
+++ /dev/null
@@ -1,62 +0,0 @@
-<?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.3.10-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/.classpath b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/.classpath
deleted file mode 100644
index eca7bdb..0000000
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/.classpath
+++ /dev/null
@@ -1,7 +0,0 @@
-<?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.packaging/org.eclipse.jgit.target/.project b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/.project
index 48c2bd1..ed148da 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/.project
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/.project
@@ -10,14 +10,8 @@
 			<arguments>
 			</arguments>
 		</buildCommand>
-		<buildCommand>
-			<name>org.eclipse.pde.ManifestBuilder</name>
-			<arguments>
-			</arguments>
-		</buildCommand>
 	</buildSpec>
 	<natures>
-		<nature>org.eclipse.pde.PluginNature</nature>
 		<nature>org.eclipse.xtext.ui.shared.xtextNature</nature>
 	</natures>
 </projectDescription>
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 731640a..ae9a02a 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.3.10.qualifier
+Bundle-Version: 5.4.4.qualifier
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/build.properties b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/build.properties
deleted file mode 100644
index 34d2e4d..0000000
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/build.properties
+++ /dev/null
@@ -1,4 +0,0 @@
-source.. = src/
-output.. = bin/
-bin.includes = META-INF/,\
-               .
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.10.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.10.target
index 8176f0c..941d496 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.10.target
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.10.target
@@ -1,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.10" sequenceNumber="1638578807">
+<target name="jgit-4.10" sequenceNumber="1638580694">
   <locations>
     <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
       <unit id="org.eclipse.jetty.client" version="9.4.14.v20181114"/>
@@ -37,12 +37,12 @@
       <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.bouncycastle.bcpg" version="1.60.0.v20181210-2057"/>
-      <unit id="org.bouncycastle.bcpg.source" version="1.60.0.v20181210-2057"/>
-      <unit id="org.bouncycastle.bcpkix" version="1.60.0.v20181210-2057"/>
-      <unit id="org.bouncycastle.bcpkix.source" version="1.60.0.v20181210-2057"/>
-      <unit id="org.bouncycastle.bcprov" version="1.60.0.v20181210-2057"/>
-      <unit id="org.bouncycastle.bcprov.source" version="1.60.0.v20181210-2057"/>
+      <unit id="org.bouncycastle.bcpg" version="1.61.0.v20190602-1335"/>
+      <unit id="org.bouncycastle.bcpg.source" version="1.61.0.v20190602-1335"/>
+      <unit id="org.bouncycastle.bcpkix" version="1.61.0.v20190602-1335"/>
+      <unit id="org.bouncycastle.bcpkix.source" version="1.61.0.v20190602-1335"/>
+      <unit id="org.bouncycastle.bcprov" version="1.61.0.v20190602-1335"/>
+      <unit id="org.bouncycastle.bcprov.source" version="1.61.0.v20190602-1335"/>
       <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"/>
@@ -78,11 +78,11 @@
       <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"/>
+      <unit id="org.apache.sshd.osgi" version="2.2.0.v20190425-2127"/>
+      <unit id="org.apache.sshd.osgi.source" version="2.2.0.v20190425-2127"/>
+      <unit id="org.apache.sshd.sftp" version="2.2.0.v20190425-2127"/>
+      <unit id="org.apache.sshd.sftp.source" version="2.2.0.v20190425-2127"/>
+      <repository location="https://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"/>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.11.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.11.target
index 414e02c..4be2f46 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.11.target
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.11.target
@@ -1,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.11" sequenceNumber="1638579452">
+<target name="jgit-4.11" sequenceNumber="1638580694">
   <locations>
     <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
       <unit id="org.eclipse.jetty.client" version="9.4.14.v20181114"/>
@@ -37,12 +37,12 @@
       <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.bouncycastle.bcpg" version="1.60.0.v20181210-2057"/>
-      <unit id="org.bouncycastle.bcpg.source" version="1.60.0.v20181210-2057"/>
-      <unit id="org.bouncycastle.bcpkix" version="1.60.0.v20181210-2057"/>
-      <unit id="org.bouncycastle.bcpkix.source" version="1.60.0.v20181210-2057"/>
-      <unit id="org.bouncycastle.bcprov" version="1.60.0.v20181210-2057"/>
-      <unit id="org.bouncycastle.bcprov.source" version="1.60.0.v20181210-2057"/>
+      <unit id="org.bouncycastle.bcpg" version="1.61.0.v20190602-1335"/>
+      <unit id="org.bouncycastle.bcpg.source" version="1.61.0.v20190602-1335"/>
+      <unit id="org.bouncycastle.bcpkix" version="1.61.0.v20190602-1335"/>
+      <unit id="org.bouncycastle.bcpkix.source" version="1.61.0.v20190602-1335"/>
+      <unit id="org.bouncycastle.bcprov" version="1.61.0.v20190602-1335"/>
+      <unit id="org.bouncycastle.bcprov.source" version="1.61.0.v20190602-1335"/>
       <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"/>
@@ -78,11 +78,11 @@
       <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"/>
+      <unit id="org.apache.sshd.osgi" version="2.2.0.v20190425-2127"/>
+      <unit id="org.apache.sshd.osgi.source" version="2.2.0.v20190425-2127"/>
+      <unit id="org.apache.sshd.sftp" version="2.2.0.v20190425-2127"/>
+      <unit id="org.apache.sshd.sftp.source" version="2.2.0.v20190425-2127"/>
+      <repository location="https://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"/>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.12.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.12.target
new file mode 100644
index 0000000..34ffd45
--- /dev/null
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.12.target
@@ -0,0 +1,92 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<?pde?>
+<!-- generated with https://github.com/eclipse-cbi/targetplatform-dsl -->
+<target name="jgit-4.12" sequenceNumber="1638580694">
+  <locations>
+    <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
+      <unit id="org.eclipse.jetty.client" version="9.4.14.v20181114"/>
+      <unit id="org.eclipse.jetty.client.source" version="9.4.14.v20181114"/>
+      <unit id="org.eclipse.jetty.continuation" version="9.4.14.v20181114"/>
+      <unit id="org.eclipse.jetty.continuation.source" version="9.4.14.v20181114"/>
+      <unit id="org.eclipse.jetty.http" version="9.4.14.v20181114"/>
+      <unit id="org.eclipse.jetty.http.source" version="9.4.14.v20181114"/>
+      <unit id="org.eclipse.jetty.io" version="9.4.14.v20181114"/>
+      <unit id="org.eclipse.jetty.io.source" version="9.4.14.v20181114"/>
+      <unit id="org.eclipse.jetty.security" version="9.4.14.v20181114"/>
+      <unit id="org.eclipse.jetty.security.source" version="9.4.14.v20181114"/>
+      <unit id="org.eclipse.jetty.server" version="9.4.14.v20181114"/>
+      <unit id="org.eclipse.jetty.server.source" version="9.4.14.v20181114"/>
+      <unit id="org.eclipse.jetty.servlet" version="9.4.14.v20181114"/>
+      <unit id="org.eclipse.jetty.servlet.source" version="9.4.14.v20181114"/>
+      <unit id="org.eclipse.jetty.util" version="9.4.14.v20181114"/>
+      <unit id="org.eclipse.jetty.util.source" version="9.4.14.v20181114"/>
+      <repository id="jetty-9.4.14" location="https://archive.eclipse.org/jetty/updates/jetty-bundles-9.x/jetty-bundles-9.x/9.4.14.v20181114"/>
+    </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.bouncycastle.bcpg" version="1.61.0.v20190602-1335"/>
+      <unit id="org.bouncycastle.bcpg.source" version="1.61.0.v20190602-1335"/>
+      <unit id="org.bouncycastle.bcpkix" version="1.61.0.v20190602-1335"/>
+      <unit id="org.bouncycastle.bcpkix.source" version="1.61.0.v20190602-1335"/>
+      <unit id="org.bouncycastle.bcprov" version="1.61.0.v20190602-1335"/>
+      <unit id="org.bouncycastle.bcprov.source" version="1.61.0.v20190602-1335"/>
+      <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.8.0.v20180207-1613"/>
+      <unit id="org.tukaani.xz.source" version="1.8.0.v20180207-1613"/>
+      <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.osgi" version="2.2.0.v20190425-2127"/>
+      <unit id="org.apache.sshd.osgi.source" version="2.2.0.v20190425-2127"/>
+      <unit id="org.apache.sshd.sftp" version="2.2.0.v20190425-2127"/>
+      <unit id="org.apache.sshd.sftp.source" version="2.2.0.v20190425-2127"/>
+      <repository location="https://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="https://download.eclipse.org/releases/2019-06/"/>
+    </location>
+  </locations>
+</target>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.12.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.12.tpd
new file mode 100644
index 0000000..cff6026
--- /dev/null
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.12.tpd
@@ -0,0 +1,8 @@
+target "jgit-4.12" with source configurePhase
+
+include "projects/jetty-9.4.14.tpd"
+include "orbit/R20190602212107-2019-06.tpd"
+
+location "https://download.eclipse.org/releases/2019-06/" {
+	org.eclipse.osgi lazy
+}
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.6.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.6.target
index 1a9608e..489b01c 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="1638579461">
+<target name="jgit-4.6" sequenceNumber="1638580694">
   <locations>
     <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
       <unit id="org.eclipse.jetty.client" version="9.4.14.v20181114"/>
@@ -37,12 +37,12 @@
       <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.bouncycastle.bcpg" version="1.60.0.v20181210-2057"/>
-      <unit id="org.bouncycastle.bcpg.source" version="1.60.0.v20181210-2057"/>
-      <unit id="org.bouncycastle.bcpkix" version="1.60.0.v20181210-2057"/>
-      <unit id="org.bouncycastle.bcpkix.source" version="1.60.0.v20181210-2057"/>
-      <unit id="org.bouncycastle.bcprov" version="1.60.0.v20181210-2057"/>
-      <unit id="org.bouncycastle.bcprov.source" version="1.60.0.v20181210-2057"/>
+      <unit id="org.bouncycastle.bcpg" version="1.61.0.v20190602-1335"/>
+      <unit id="org.bouncycastle.bcpg.source" version="1.61.0.v20190602-1335"/>
+      <unit id="org.bouncycastle.bcpkix" version="1.61.0.v20190602-1335"/>
+      <unit id="org.bouncycastle.bcpkix.source" version="1.61.0.v20190602-1335"/>
+      <unit id="org.bouncycastle.bcprov" version="1.61.0.v20190602-1335"/>
+      <unit id="org.bouncycastle.bcprov.source" version="1.61.0.v20190602-1335"/>
       <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"/>
@@ -78,11 +78,11 @@
       <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"/>
+      <unit id="org.apache.sshd.osgi" version="2.2.0.v20190425-2127"/>
+      <unit id="org.apache.sshd.osgi.source" version="2.2.0.v20190425-2127"/>
+      <unit id="org.apache.sshd.sftp" version="2.2.0.v20190425-2127"/>
+      <unit id="org.apache.sshd.sftp.source" version="2.2.0.v20190425-2127"/>
+      <repository location="https://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"/>
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 4d8e451..627a83a 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="1638579458">
+<target name="jgit-4.7" sequenceNumber="1638580694">
   <locations>
     <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
       <unit id="org.eclipse.jetty.client" version="9.4.14.v20181114"/>
@@ -37,12 +37,12 @@
       <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.bouncycastle.bcpg" version="1.60.0.v20181210-2057"/>
-      <unit id="org.bouncycastle.bcpg.source" version="1.60.0.v20181210-2057"/>
-      <unit id="org.bouncycastle.bcpkix" version="1.60.0.v20181210-2057"/>
-      <unit id="org.bouncycastle.bcpkix.source" version="1.60.0.v20181210-2057"/>
-      <unit id="org.bouncycastle.bcprov" version="1.60.0.v20181210-2057"/>
-      <unit id="org.bouncycastle.bcprov.source" version="1.60.0.v20181210-2057"/>
+      <unit id="org.bouncycastle.bcpg" version="1.61.0.v20190602-1335"/>
+      <unit id="org.bouncycastle.bcpg.source" version="1.61.0.v20190602-1335"/>
+      <unit id="org.bouncycastle.bcpkix" version="1.61.0.v20190602-1335"/>
+      <unit id="org.bouncycastle.bcpkix.source" version="1.61.0.v20190602-1335"/>
+      <unit id="org.bouncycastle.bcprov" version="1.61.0.v20190602-1335"/>
+      <unit id="org.bouncycastle.bcprov.source" version="1.61.0.v20190602-1335"/>
       <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"/>
@@ -78,11 +78,11 @@
       <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"/>
+      <unit id="org.apache.sshd.osgi" version="2.2.0.v20190425-2127"/>
+      <unit id="org.apache.sshd.osgi.source" version="2.2.0.v20190425-2127"/>
+      <unit id="org.apache.sshd.sftp" version="2.2.0.v20190425-2127"/>
+      <unit id="org.apache.sshd.sftp.source" version="2.2.0.v20190425-2127"/>
+      <repository location="https://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"/>
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 491ff48..dc45c7d 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="1638579452">
+<target name="jgit-4.8" sequenceNumber="1638580694">
   <locations>
     <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
       <unit id="org.eclipse.jetty.client" version="9.4.14.v20181114"/>
@@ -37,12 +37,12 @@
       <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.bouncycastle.bcpg" version="1.60.0.v20181210-2057"/>
-      <unit id="org.bouncycastle.bcpg.source" version="1.60.0.v20181210-2057"/>
-      <unit id="org.bouncycastle.bcpkix" version="1.60.0.v20181210-2057"/>
-      <unit id="org.bouncycastle.bcpkix.source" version="1.60.0.v20181210-2057"/>
-      <unit id="org.bouncycastle.bcprov" version="1.60.0.v20181210-2057"/>
-      <unit id="org.bouncycastle.bcprov.source" version="1.60.0.v20181210-2057"/>
+      <unit id="org.bouncycastle.bcpg" version="1.61.0.v20190602-1335"/>
+      <unit id="org.bouncycastle.bcpg.source" version="1.61.0.v20190602-1335"/>
+      <unit id="org.bouncycastle.bcpkix" version="1.61.0.v20190602-1335"/>
+      <unit id="org.bouncycastle.bcpkix.source" version="1.61.0.v20190602-1335"/>
+      <unit id="org.bouncycastle.bcprov" version="1.61.0.v20190602-1335"/>
+      <unit id="org.bouncycastle.bcprov.source" version="1.61.0.v20190602-1335"/>
       <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"/>
@@ -78,11 +78,11 @@
       <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"/>
+      <unit id="org.apache.sshd.osgi" version="2.2.0.v20190425-2127"/>
+      <unit id="org.apache.sshd.osgi.source" version="2.2.0.v20190425-2127"/>
+      <unit id="org.apache.sshd.sftp" version="2.2.0.v20190425-2127"/>
+      <unit id="org.apache.sshd.sftp.source" version="2.2.0.v20190425-2127"/>
+      <repository location="https://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"/>
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 d326705..040c1d1 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="1638578807">
+<target name="jgit-4.9" sequenceNumber="1638580694">
   <locations>
     <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
       <unit id="org.eclipse.jetty.client" version="9.4.14.v20181114"/>
@@ -37,12 +37,12 @@
       <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.bouncycastle.bcpg" version="1.60.0.v20181210-2057"/>
-      <unit id="org.bouncycastle.bcpg.source" version="1.60.0.v20181210-2057"/>
-      <unit id="org.bouncycastle.bcpkix" version="1.60.0.v20181210-2057"/>
-      <unit id="org.bouncycastle.bcpkix.source" version="1.60.0.v20181210-2057"/>
-      <unit id="org.bouncycastle.bcprov" version="1.60.0.v20181210-2057"/>
-      <unit id="org.bouncycastle.bcprov.source" version="1.60.0.v20181210-2057"/>
+      <unit id="org.bouncycastle.bcpg" version="1.61.0.v20190602-1335"/>
+      <unit id="org.bouncycastle.bcpg.source" version="1.61.0.v20190602-1335"/>
+      <unit id="org.bouncycastle.bcpkix" version="1.61.0.v20190602-1335"/>
+      <unit id="org.bouncycastle.bcpkix.source" version="1.61.0.v20190602-1335"/>
+      <unit id="org.bouncycastle.bcprov" version="1.61.0.v20190602-1335"/>
+      <unit id="org.bouncycastle.bcprov.source" version="1.61.0.v20190602-1335"/>
       <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"/>
@@ -78,11 +78,11 @@
       <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"/>
+      <unit id="org.apache.sshd.osgi" version="2.2.0.v20190425-2127"/>
+      <unit id="org.apache.sshd.osgi.source" version="2.2.0.v20190425-2127"/>
+      <unit id="org.apache.sshd.sftp" version="2.2.0.v20190425-2127"/>
+      <unit id="org.apache.sshd.sftp.source" version="2.2.0.v20190425-2127"/>
+      <repository location="https://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"/>
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 65d5c9f..db67449 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
@@ -1,7 +1,7 @@
 target "R20190602212107-2019-06" with source configurePhase
 // see http://download.eclipse.org/tools/orbit/downloads/
 
-location "http://download.eclipse.org/tools/orbit/downloads/drops/R20190602212107/repository" {
+location "https://download.eclipse.org/tools/orbit/downloads/drops/R20190602212107/repository" {
 	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]
@@ -16,12 +16,12 @@
 	org.apache.httpcomponents.httpcore.source [4.4.10.v20190123-2214,4.4.10.v20190123-2214]
 	org.apache.log4j [1.2.15.v201012070815,1.2.15.v201012070815]
 	org.apache.log4j.source [1.2.15.v201012070815,1.2.15.v201012070815]
-	org.bouncycastle.bcpg [1.60.0.v20181210-2057,1.60.0.v20181210-2057]
-	org.bouncycastle.bcpg.source [1.60.0.v20181210-2057,1.60.0.v20181210-2057]
-	org.bouncycastle.bcpkix [1.60.0.v20181210-2057,1.60.0.v20181210-2057]
-	org.bouncycastle.bcpkix.source [1.60.0.v20181210-2057,1.60.0.v20181210-2057]
-	org.bouncycastle.bcprov [1.60.0.v20181210-2057,1.60.0.v20181210-2057]
-	org.bouncycastle.bcprov.source [1.60.0.v20181210-2057,1.60.0.v20181210-2057]
+	org.bouncycastle.bcpg [1.61.0.v20190602-1335,1.61.0.v20190602-1335]
+	org.bouncycastle.bcpg.source [1.61.0.v20190602-1335,1.61.0.v20190602-1335]
+	org.bouncycastle.bcpkix [1.61.0.v20190602-1335,1.61.0.v20190602-1335]
+	org.bouncycastle.bcpkix.source [1.61.0.v20190602-1335,1.61.0.v20190602-1335]
+	org.bouncycastle.bcprov [1.61.0.v20190602-1335,1.61.0.v20190602-1335]
+	org.bouncycastle.bcprov.source [1.61.0.v20190602-1335,1.61.0.v20190602-1335]
 	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]
@@ -57,8 +57,9 @@
 	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]
+	org.apache.sshd.osgi [2.2.0.v20190425-2127,2.2.0.v20190425-2127]
+	org.apache.sshd.osgi.source [2.2.0.v20190425-2127,2.2.0.v20190425-2127]
+	org.apache.sshd.sftp [2.2.0.v20190425-2127,2.2.0.v20190425-2127]
+	org.apache.sshd.sftp.source [2.2.0.v20190425-2127,2.2.0.v20190425-2127]
 }
+
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 b978c3f..b278457 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.3.10-SNAPSHOT</version>
+    <version>5.4.4-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.target</artifactId>
diff --git a/org.eclipse.jgit.packaging/pom.xml b/org.eclipse.jgit.packaging/pom.xml
index 7bdbf38..5a90df0 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.3.10-SNAPSHOT</version>
+  <version>5.4.4-SNAPSHOT</version>
   <packaging>pom</packaging>
 
   <name>JGit Tycho Parent</name>
@@ -79,8 +79,6 @@
     <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>
@@ -109,6 +107,60 @@
     </dependency>
     <dependency>
       <groupId>org.eclipse.jgit</groupId>
+      <artifactId>org.eclipse.jgit.ant</artifactId>
+      <version>${project.version}</version>
+      <classifier>sources</classifier>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jgit</groupId>
+      <artifactId>org.eclipse.jgit.archive</artifactId>
+      <version>${project.version}</version>
+      <classifier>sources</classifier>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jgit</groupId>
+      <artifactId>org.eclipse.jgit.http.apache</artifactId>
+      <version>${project.version}</version>
+      <classifier>sources</classifier>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jgit</groupId>
+      <artifactId>org.eclipse.jgit.http.server</artifactId>
+      <version>${project.version}</version>
+      <classifier>sources</classifier>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jgit</groupId>
+      <artifactId>org.eclipse.jgit.junit</artifactId>
+      <version>${project.version}</version>
+      <classifier>sources</classifier>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jgit</groupId>
+      <artifactId>org.eclipse.jgit.junit.http</artifactId>
+      <version>${project.version}</version>
+      <classifier>sources</classifier>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jgit</groupId>
+      <artifactId>org.eclipse.jgit.junit.ssh</artifactId>
+      <version>${project.version}</version>
+      <classifier>sources</classifier>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jgit</groupId>
+      <artifactId>org.eclipse.jgit.lfs</artifactId>
+      <version>${project.version}</version>
+      <classifier>sources</classifier>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jgit</groupId>
+      <artifactId>org.eclipse.jgit.lfs.server</artifactId>
+      <version>${project.version}</version>
+      <classifier>sources</classifier>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jgit</groupId>
       <artifactId>org.eclipse.jgit.pgm</artifactId>
       <version>${project.version}</version>
       <classifier>sources</classifier>
@@ -119,6 +171,12 @@
       <version>${project.version}</version>
       <classifier>sources</classifier>
     </dependency>
+    <dependency>
+      <groupId>org.eclipse.jgit</groupId>
+      <artifactId>org.eclipse.jgit.ui</artifactId>
+      <version>${project.version}</version>
+      <classifier>sources</classifier>
+    </dependency>
   </dependencies>
 
   <build>
diff --git a/org.eclipse.jgit.pgm.test/.settings/org.eclipse.pde.prefs b/org.eclipse.jgit.pgm.test/.settings/org.eclipse.pde.prefs
new file mode 100644
index 0000000..2174e4f
--- /dev/null
+++ b/org.eclipse.jgit.pgm.test/.settings/org.eclipse.pde.prefs
@@ -0,0 +1,34 @@
+compilers.f.unresolved-features=1
+compilers.f.unresolved-plugins=1
+compilers.incompatible-environment=1
+compilers.p.build=1
+compilers.p.build.bin.includes=1
+compilers.p.build.encodings=2
+compilers.p.build.java.compiler=2
+compilers.p.build.java.compliance=1
+compilers.p.build.missing.output=2
+compilers.p.build.output.library=1
+compilers.p.build.source.library=1
+compilers.p.build.src.includes=1
+compilers.p.deprecated=1
+compilers.p.discouraged-class=1
+compilers.p.internal=1
+compilers.p.missing-packages=2
+compilers.p.missing-version-export-package=2
+compilers.p.missing-version-import-package=2
+compilers.p.missing-version-require-bundle=2
+compilers.p.no-required-att=0
+compilers.p.no.automatic.module=1
+compilers.p.not-externalized-att=2
+compilers.p.service.component.without.lazyactivation=1
+compilers.p.unknown-attribute=1
+compilers.p.unknown-class=1
+compilers.p.unknown-element=1
+compilers.p.unknown-identifier=1
+compilers.p.unknown-resource=1
+compilers.p.unresolved-ex-points=0
+compilers.p.unresolved-import=0
+compilers.s.create-docs=false
+compilers.s.doc-folder=doc
+compilers.s.open-tags=1
+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 d648217..b3d615c 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.3.10.qualifier
+Bundle-Version: 5.4.4.qualifier
 Bundle-Vendor: %provider_name
 Bundle-Localization: plugin
 Bundle-ActivationPolicy: lazy
 Bundle-RequiredExecutionEnvironment: JavaSE-1.8
-Import-Package: org.eclipse.jgit.api;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.api.errors;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.diff;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.dircache;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.internal.storage.file;version="5.3.10",
- org.eclipse.jgit.junit;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.lib;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.merge;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.pgm;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.pgm.internal;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.pgm.opt;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.revwalk;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.storage.file;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.transport;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.treewalk;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.util;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.util.io;version="[5.3.10,5.4.0)",
+Import-Package: org.eclipse.jgit.api;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.api.errors;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.diff;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.dircache;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.internal.storage.file;version="5.4.4",
+ org.eclipse.jgit.junit;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.lib;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.merge;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.pgm;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.pgm.internal;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.pgm.opt;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.revwalk;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.storage.file;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.transport;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.treewalk;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.util;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.util.io;version="[5.4.4,5.5.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 b11ddbe..ecf3d70 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.3.10-SNAPSHOT</version>
+    <version>5.4.4-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.pgm.test</artifactId>
@@ -109,7 +109,7 @@
       <plugin>
         <artifactId>maven-surefire-plugin</artifactId>
         <configuration>
-          <argLine>@{argLine} -Djava.io.tmpdir=${project.build.directory}</argLine>
+          <argLine>@{argLine} -Xmx512m -Djava.io.tmpdir=${project.build.directory}</argLine>
         </configuration>
       </plugin>
     </plugins>
diff --git a/org.eclipse.jgit.pgm.test/src/org/eclipse/jgit/lib/CLIRepositoryTestCase.java b/org.eclipse.jgit.pgm.test/src/org/eclipse/jgit/lib/CLIRepositoryTestCase.java
index 0d1894b..a830ff2 100644
--- a/org.eclipse.jgit.pgm.test/src/org/eclipse/jgit/lib/CLIRepositoryTestCase.java
+++ b/org.eclipse.jgit.pgm.test/src/org/eclipse/jgit/lib/CLIRepositoryTestCase.java
@@ -214,7 +214,7 @@
 	protected void assertStringArrayEquals(String expected, String[] actual) {
 		// if there is more than one line, ignore last one if empty
 		assertEquals(1,
-				actual.length > 1 && actual[actual.length - 1].equals("")
+				actual.length > 1 && actual[actual.length - 1].isEmpty()
 						? actual.length - 1 : actual.length);
 		assertEquals(expected, actual[0]);
 	}
diff --git a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/ArchiveTest.java b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/ArchiveTest.java
index 47eb156..e07fdd5 100644
--- a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/ArchiveTest.java
+++ b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/ArchiveTest.java
@@ -60,7 +60,6 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
-import java.util.concurrent.Callable;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 import java.util.concurrent.Future;
@@ -703,15 +702,12 @@
 	private static Future<Object> writeAsync(OutputStream stream, byte[] data) {
 		ExecutorService executor = Executors.newSingleThreadExecutor();
 
-		return executor.submit(new Callable<Object>() {
-			@Override
-			public Object call() throws IOException {
-				try {
-					stream.write(data);
-					return null;
-				} finally {
-					stream.close();
-				}
+		return executor.submit(() -> {
+			try {
+				stream.write(data);
+				return null;
+			} finally {
+				stream.close();
 			}
 		});
 	}
diff --git a/org.eclipse.jgit.pgm/META-INF/MANIFEST.MF b/org.eclipse.jgit.pgm/META-INF/MANIFEST.MF
index 93f3fc5..d928303 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.3.10.qualifier
+Bundle-Version: 5.4.4.qualifier
 Bundle-Vendor: %provider_name
 Bundle-ActivationPolicy: lazy
 Bundle-Localization: plugin
@@ -28,50 +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.3.10,5.4.0)",
- org.eclipse.jgit.api.errors;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.archive;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.awtui;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.blame;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.diff;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.dircache;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.errors;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.gitrepo;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.internal.ketch;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.internal.storage.dfs;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.internal.storage.file;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.internal.storage.io;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.internal.storage.pack;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.internal.storage.reftable;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.internal.storage.reftree;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.lfs;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.lfs.lib;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.lfs.server;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.lfs.server.fs;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.lfs.server.s3;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.lib;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.merge;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.nls;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.notes;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.revplot;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.revwalk;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.revwalk.filter;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.storage.file;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.storage.pack;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.transport;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.transport.http.apache;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.transport.resolver;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.transport.sshd;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.treewalk;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.treewalk.filter;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.util;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.util.io;version="[5.3.10,5.4.0)",
+ org.eclipse.jgit.api;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.api.errors;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.archive;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.awtui;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.blame;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.diff;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.dircache;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.errors;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.gitrepo;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.internal.ketch;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.internal.storage.dfs;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.internal.storage.file;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.internal.storage.io;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.internal.storage.pack;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.internal.storage.reftable;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.internal.storage.reftree;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.lfs;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.lfs.lib;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.lfs.server;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.lfs.server.fs;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.lfs.server.s3;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.lib;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.merge;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.nls;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.notes;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.revplot;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.revwalk;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.revwalk.filter;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.storage.file;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.storage.pack;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.transport;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.transport.http.apache;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.transport.resolver;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.transport.sshd;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.treewalk;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.treewalk.filter;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.util;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.util.io;version="[5.4.4,5.5.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.3.10";
+Export-Package: org.eclipse.jgit.console;version="5.4.4";
   uses:="org.eclipse.jgit.transport,
    org.eclipse.jgit.util",
- org.eclipse.jgit.pgm;version="5.3.10";
+ org.eclipse.jgit.pgm;version="5.4.4";
   uses:="org.eclipse.jgit.revwalk,
    org.eclipse.jgit.treewalk.filter,
    org.eclipse.jgit.pgm.opt,
@@ -82,11 +82,11 @@
    org.eclipse.jgit.treewalk,
    javax.swing,
    org.eclipse.jgit.transport",
- org.eclipse.jgit.pgm.debug;version="5.3.10";
+ org.eclipse.jgit.pgm.debug;version="5.4.4";
   uses:="org.eclipse.jgit.util.io,
    org.eclipse.jgit.pgm",
- org.eclipse.jgit.pgm.internal;version="5.3.10";x-friends:="org.eclipse.jgit.pgm.test,org.eclipse.jgit.test",
- org.eclipse.jgit.pgm.opt;version="5.3.10";
+ org.eclipse.jgit.pgm.internal;version="5.4.4";x-friends:="org.eclipse.jgit.pgm.test,org.eclipse.jgit.test",
+ org.eclipse.jgit.pgm.opt;version="5.4.4";
   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 8a5ba07..750676b 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.3.10.qualifier
-Eclipse-SourceBundle: org.eclipse.jgit.pgm;version="5.3.10.qualifier";roots="."
+Bundle-Version: 5.4.4.qualifier
+Eclipse-SourceBundle: org.eclipse.jgit.pgm;version="5.4.4.qualifier";roots="."
diff --git a/org.eclipse.jgit.pgm/pom.xml b/org.eclipse.jgit.pgm/pom.xml
index bd93e81..f7e8407 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.3.10-SNAPSHOT</version>
+    <version>5.4.4-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.pgm</artifactId>
@@ -142,6 +142,12 @@
       <artifactId>org.eclipse.jgit.lfs.server</artifactId>
       <version>${project.version}</version>
     </dependency>
+
+    <dependency>
+      <groupId>org.tukaani</groupId>
+      <artifactId>xz</artifactId>
+      <optional>true</optional>
+    </dependency>
   </dependencies>
 
   <build>
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 aa2608d..124bb44 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
@@ -219,6 +219,7 @@
 untrackedFiles=Untracked files:
 updating=Updating {0}..{1}
 usage_Aggressive=This option will cause gc to more aggressively optimize the repository at the expense of taking much more time
+usage_AlwaysFallback=Show uniquely abbreviated commit object as fallback
 usage_bareClone=Make a bare Git repository. That is, instead of creating [DIRECTORY] and placing the administrative files in [DIRECTORY]/.git, make the [DIRECTORY] itself the $GIT_DIR.
 usage_Blame=Show what revision and author last modified each line
 usage_Clean=Remove untracked files from the working tree
@@ -286,6 +287,7 @@
 usage_StopTrackingAFile=Stop tracking a file
 usage_TextHashFunctions=Scan repository to compute maximum number of collisions for hash functions
 usage_UpdateRemoteRepositoryFromLocalRefs=Update remote repository from local refs
+usage_UseTags=Use any tag including lightweight tags
 usage_WriteDirCache=Write the DirCache
 usage_abbrevCommits=abbreviate commits to N + 1 digits
 usage_abortConnectionIfNoActivity=abort connection if no activity
@@ -311,7 +313,7 @@
 usage_beMoreVerbose=be more verbose
 usage_beVerbose=be verbose
 usage_cached=compare against index
-usage_checkout=Checkout a branch to the working tree
+usage_checkout=Check out a branch to the working tree
 usage_cloneRepositoryIntoNewDir=Clone a repository into a new directory
 usage_configFile=configuration file
 usage_configGlobal=use global configuration in ~/.gitconfig
@@ -319,7 +321,7 @@
 usage_configLocal=use local configuration in .git/config
 usage_configSystem=use system-wide configuration in $(prefix)/etc/gitconfig
 usage_configureTheServiceInDaemonServicename=configure the service in daemon.servicename
-usage_createBranchAndCheckout=create branch and checkout
+usage_createBranchAndCheckout=create branch and check out
 usage_deleteBranchEvenIfNotMerged=delete branch (even if not merged)
 usage_deleteFullyMergedBranch=delete fully merged branch
 usage_date=date format, one of default, rfc, local, iso, short, raw (as defined by git-log(1) ), locale or localelocal (jgit extensions)
@@ -412,7 +414,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 remote's HEAD
+usage_checkoutBranchAfterClone=check out 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 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/Blame.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Blame.java
index 3858b3d..e38cb46 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Blame.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Blame.java
@@ -218,7 +218,8 @@
 					dateWidth = Math.max(dateWidth, date(line).length());
 					pathWidth = Math.max(pathWidth, path(line).length());
 				}
-				while (line + 1 < end && blame.getSourceCommit(line + 1) == c) {
+				while (line + 1 < end
+						&& sameCommit(blame.getSourceCommit(line + 1), c)) {
 					line++;
 				}
 				maxSourceLine = Math.max(maxSourceLine, blame.getSourceLine(line));
@@ -257,13 +258,22 @@
 					blame.getResultContents().writeLine(outs, line);
 					outs.flush();
 					outw.print('\n');
-				} while (++line < end && blame.getSourceCommit(line) == c);
+				} while (++line < end
+						&& sameCommit(blame.getSourceCommit(line), c));
 			}
 		} catch (NoWorkTreeException | IOException e) {
 			throw die(e.getMessage(), e);
 		}
 	}
 
+	@SuppressWarnings("ReferenceEquality")
+	private static boolean sameCommit(RevCommit a, RevCommit b) {
+		// Reference comparison is intentional; BlameGenerator uses a single
+		// RevWalk which caches the RevCommit objects, and if a given commit
+		// is cached the RevWalk returns the same instance.
+		return a == b;
+	}
+
 	private int uniqueAbbrevLen(ObjectReader reader, RevCommit commit)
 			throws IOException {
 		return reader.abbreviate(commit, abbrev).length();
@@ -295,14 +305,14 @@
 			}
 		}
 
-		if (beginStr.equals("")) //$NON-NLS-1$
+		if (beginStr.isEmpty())
 			begin = 0;
 		else if (beginStr.startsWith("/")) //$NON-NLS-1$
 			begin = findLine(0, beginStr);
 		else
 			begin = Math.max(0, Integer.parseInt(beginStr) - 1);
 
-		if (endStr.equals("")) //$NON-NLS-1$
+		if (endStr.isEmpty())
 			end = blame.getResultContents().size();
 		else if (endStr.startsWith("/")) //$NON-NLS-1$
 			end = findLine(begin, endStr);
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Checkout.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Checkout.java
index 7e1737f..3df37e5 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Checkout.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Checkout.java
@@ -96,7 +96,7 @@
 		try (Git git = new Git(db)) {
 			CheckoutCommand command = git.checkout()
 					.setProgressMonitor(new TextProgressMonitor(errw));
-			if (paths.size() > 0) {
+			if (!paths.isEmpty()) {
 				command.setStartPoint(name);
 				if (paths.size() == 1 && paths.get(0).equals(".")) { //$NON-NLS-1$
 					command.setAllPaths(true);
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/CommandCatalog.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/CommandCatalog.java
index dbdccc1..dab52f6 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/CommandCatalog.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/CommandCatalog.java
@@ -52,7 +52,6 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
-import java.util.Comparator;
 import java.util.Enumeration;
 import java.util.HashMap;
 import java.util.Map;
@@ -115,12 +114,8 @@
 
 	private static CommandRef[] toSortedArray(Collection<CommandRef> c) {
 		final CommandRef[] r = c.toArray(new CommandRef[0]);
-		Arrays.sort(r, new Comparator<CommandRef>() {
-			@Override
-			public int compare(CommandRef o1, CommandRef o2) {
-				return o1.getName().compareTo(o2.getName());
-			}
-		});
+		Arrays.sort(r, (CommandRef o1, CommandRef o2) -> o1.getName()
+				.compareTo(o2.getName()));
 		return r;
 	}
 
@@ -163,13 +158,9 @@
 		final Class<? extends TextBuiltin> clazz;
 		try {
 			clazz = Class.forName(cn, false, ldr).asSubclass(TextBuiltin.class);
-		} catch (ClassNotFoundException notBuiltin) {
+		} catch (ClassNotFoundException | ClassCastException notBuiltin) {
 			// Doesn't exist, even though the service entry is present.
-			//
-			return;
-		} catch (ClassCastException notBuiltin) {
 			// Isn't really a builtin, even though its listed as such.
-			//
 			return;
 		}
 
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/CommandRef.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/CommandRef.java
index 1773de5..85ace3a 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/CommandRef.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/CommandRef.java
@@ -156,24 +156,19 @@
 		final Constructor<? extends TextBuiltin> c;
 		try {
 			c = impl.getDeclaredConstructor();
-		} catch (SecurityException e) {
-			throw new RuntimeException(MessageFormat.format(CLIText.get().cannotCreateCommand, getName(), e));
-		} catch (NoSuchMethodException e) {
-			throw new RuntimeException(MessageFormat.format(CLIText.get().cannotCreateCommand, getName(), e));
+		} catch (SecurityException | NoSuchMethodException e) {
+			throw new RuntimeException(MessageFormat
+					.format(CLIText.get().cannotCreateCommand, getName(), e));
 		}
 		c.setAccessible(true);
 
 		final TextBuiltin r;
 		try {
 			r = c.newInstance();
-		} catch (InstantiationException e) {
-			throw new RuntimeException(MessageFormat.format(CLIText.get().cannotCreateCommand, getName(), e));
-		} catch (IllegalAccessException e) {
-			throw new RuntimeException(MessageFormat.format(CLIText.get().cannotCreateCommand, getName(), e));
-		} catch (IllegalArgumentException e) {
-			throw new RuntimeException(MessageFormat.format(CLIText.get().cannotCreateCommand, getName(), e));
-		} catch (InvocationTargetException e) {
-			throw new RuntimeException(MessageFormat.format(CLIText.get().cannotCreateCommand, getName(), e));
+		} catch (InstantiationException | IllegalAccessException
+				| IllegalArgumentException | InvocationTargetException e) {
+			throw new RuntimeException(MessageFormat
+					.format(CLIText.get().cannotCreateCommand, getName(), e));
 		}
 		r.setCommandName(getName());
 		return r;
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Daemon.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Daemon.java
index f989d2e..8b86c16 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Daemon.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Daemon.java
@@ -69,7 +69,6 @@
 import org.eclipse.jgit.transport.ReceivePack;
 import org.eclipse.jgit.transport.resolver.FileResolver;
 import org.eclipse.jgit.transport.resolver.ReceivePackFactory;
-import org.eclipse.jgit.transport.resolver.ServiceNotAuthorizedException;
 import org.eclipse.jgit.transport.resolver.ServiceNotEnabledException;
 import org.eclipse.jgit.util.FS;
 import org.eclipse.jgit.util.SystemReader;
@@ -203,22 +202,17 @@
 		final ReceivePackFactory<DaemonClient> factory;
 
 		factory = daemon.getReceivePackFactory();
-		daemon.setReceivePackFactory(new ReceivePackFactory<DaemonClient>() {
-			@Override
-			public ReceivePack create(DaemonClient req, Repository repo)
-					throws ServiceNotEnabledException,
-					ServiceNotAuthorizedException {
-				ReceivePack rp = factory.create(req, repo);
-				KetchLeader leader;
-				try {
-					leader = leaders.get(repo);
-				} catch (URISyntaxException err) {
-					throw new ServiceNotEnabledException(
-							KetchText.get().invalidFollowerUri, err);
-				}
-				rp.setPreReceiveHook(new KetchPreReceive(leader));
-				return rp;
+		daemon.setReceivePackFactory((DaemonClient req, Repository repo) -> {
+			ReceivePack rp = factory.create(req, repo);
+			KetchLeader leader;
+			try {
+				leader = leaders.get(repo);
+			} catch (URISyntaxException err) {
+				throw new ServiceNotEnabledException(
+						KetchText.get().invalidFollowerUri, err);
 			}
+			rp.setPreReceiveHook(new KetchPreReceive(leader));
+			return rp;
 		});
 	}
 }
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Describe.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Describe.java
index d89fee6..8a572f4 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Describe.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Describe.java
@@ -65,6 +65,12 @@
 	@Option(name = "--long", usage = "usage_LongFormat")
 	private boolean longDesc;
 
+	@Option(name = "--tags", usage = "usage_UseTags")
+	private boolean useTags;
+
+	@Option(name = "--always", usage = "usage_AlwaysFallback")
+	private boolean always;
+
 	@Option(name = "--match", usage = "usage_Match", metaVar = "metaVar_pattern")
 	private List<String> patterns = new ArrayList<>();
 
@@ -77,6 +83,8 @@
 				cmd.setTarget(tree);
 			}
 			cmd.setLong(longDesc);
+			cmd.setTags(useTags);
+			cmd.setAlways(always);
 			cmd.setMatch(patterns.toArray(new String[0]));
 			String result = null;
 			try {
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Diff.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Diff.java
index 7747dc7..3d6ebfd 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Diff.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Diff.java
@@ -87,7 +87,7 @@
 	@Argument(index = 1, metaVar = "metaVar_treeish")
 	private AbstractTreeIterator newTree;
 
-	@Option(name = "--cached", usage = "usage_cached")
+	@Option(name = "--cached", aliases = { "--staged" }, usage = "usage_cached")
 	private boolean cached;
 
 	@Option(name = "--", metaVar = "metaVar_paths", handler = PathTreeFilterHandler.class)
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Glog.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Glog.java
index 2b5af5d..00bbfb7 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Glog.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Glog.java
@@ -47,7 +47,6 @@
 import java.awt.BorderLayout;
 import java.awt.FlowLayout;
 import java.awt.event.ActionEvent;
-import java.awt.event.ActionListener;
 import java.awt.event.WindowAdapter;
 import java.awt.event.WindowEvent;
 import java.io.File;
@@ -87,11 +86,8 @@
 		final JPanel buttons = new JPanel(new FlowLayout());
 		final JButton repaint = new JButton();
 		repaint.setText(CLIText.get().repaint);
-		repaint.addActionListener(new ActionListener() {
-			@Override
-			public void actionPerformed(ActionEvent e) {
-				graphPane.repaint();
-			}
+		repaint.addActionListener((ActionEvent e) -> {
+			graphPane.repaint();
 		});
 		buttons.add(repaint);
 
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/LsFiles.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/LsFiles.java
index ef25844..e3ed30d 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/LsFiles.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/LsFiles.java
@@ -85,7 +85,7 @@
 			CanonicalTreeParser p = new CanonicalTreeParser();
 			p.reset(rw.getObjectReader(), c.getTree());
 			tw.reset(); // drop the first empty tree, which we do not need here
-			if (paths.size() > 0) {
+			if (!paths.isEmpty()) {
 				tw.setFilter(PathFilterGroup.createFromStrings(paths));
 			}
 			tw.addTree(p);
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/LsRemote.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/LsRemote.java
index 1c2564d..708fcde 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/LsRemote.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/LsRemote.java
@@ -46,7 +46,6 @@
 package org.eclipse.jgit.pgm;
 
 import java.io.IOException;
-import java.util.Comparator;
 import java.util.TreeSet;
 
 import org.eclipse.jgit.api.Git;
@@ -76,13 +75,8 @@
 	protected void run() {
 		LsRemoteCommand command = Git.lsRemoteRepository().setRemote(remote)
 				.setTimeout(timeout).setHeads(heads).setTags(tags);
-		TreeSet<Ref> refs = new TreeSet<>(new Comparator<Ref>() {
-
-			@Override
-			public int compare(Ref r1, Ref r2) {
-				return r1.getName().compareTo(r2.getName());
-			}
-		});
+		TreeSet<Ref> refs = new TreeSet<>(
+				(Ref r1, Ref r2) -> r1.getName().compareTo(r2.getName()));
 		try {
 			refs.addAll(command.call());
 			for (Ref r : refs) {
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/LsTree.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/LsTree.java
index 2a2bb7c..19f2d7a 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/LsTree.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/LsTree.java
@@ -76,7 +76,7 @@
 	protected void run() {
 		try (TreeWalk walk = new TreeWalk(db)) {
 			walk.reset(); // drop the first empty tree, which we do not need here
-			if (paths.size() > 0) {
+			if (!paths.isEmpty()) {
 				walk.setFilter(PathFilterGroup.createFromStrings(paths));
 			}
 			walk.setRecursive(recursive);
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Main.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Main.java
index ad10ec9..9952f5c 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Main.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Main.java
@@ -330,22 +330,12 @@
 			install("org.eclipse.jgit.console.ConsoleAuthenticator"); //$NON-NLS-1$
 			install("org.eclipse.jgit.console.ConsoleCredentialsProvider"); //$NON-NLS-1$
 			return true;
-		} catch (ClassNotFoundException e) {
+		} catch (ClassNotFoundException | NoClassDefFoundError
+				| UnsupportedClassVersionError e) {
 			return false;
-		} catch (NoClassDefFoundError e) {
-			return false;
-		} catch (UnsupportedClassVersionError e) {
-			return false;
-
-		} catch (IllegalArgumentException e) {
-			throw new RuntimeException(CLIText.get().cannotSetupConsole, e);
-		} catch (SecurityException e) {
-			throw new RuntimeException(CLIText.get().cannotSetupConsole, e);
-		} catch (IllegalAccessException e) {
-			throw new RuntimeException(CLIText.get().cannotSetupConsole, e);
-		} catch (InvocationTargetException e) {
-			throw new RuntimeException(CLIText.get().cannotSetupConsole, e);
-		} catch (NoSuchMethodException e) {
+		} catch (IllegalArgumentException | SecurityException
+				| IllegalAccessException | InvocationTargetException
+				| NoSuchMethodException e) {
 			throw new RuntimeException(CLIText.get().cannotSetupConsole, e);
 		}
 	}
@@ -390,12 +380,12 @@
 			if (s == null && protocol.equals("https")) { //$NON-NLS-1$
 				s = System.getenv("HTTPS_PROXY"); //$NON-NLS-1$
 			}
-			if (s == null || s.equals("")) { //$NON-NLS-1$
+			if (s == null || s.isEmpty()) {
 				continue;
 			}
 
 			final URL u = new URL(
-					(s.indexOf("://") == -1) ? protocol + "://" + s : s); //$NON-NLS-1$ //$NON-NLS-2$
+					(!s.contains("://")) ? protocol + "://" + s : s); //$NON-NLS-1$ //$NON-NLS-2$
 			if (!u.getProtocol().startsWith("http")) //$NON-NLS-1$
 				throw new MalformedURLException(MessageFormat.format(
 						CLIText.get().invalidHttpProxyOnlyHttpSupported, s));
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Reset.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Reset.java
index b3e81c6..e1a2907 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Reset.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Reset.java
@@ -80,7 +80,7 @@
 		try (Git git = new Git(db)) {
 			ResetCommand command = git.reset();
 			command.setRef(commit);
-			if (paths.size() > 0) {
+			if (!paths.isEmpty()) {
 				for (String path : paths) {
 					command.addPath(path);
 				}
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Status.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Status.java
index dfc8a94..c3887fe 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Status.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Status.java
@@ -93,7 +93,7 @@
 	protected void run() {
 		try (Git git = new Git(db)) {
 			StatusCommand statusCommand = git.status();
-			if (filterPaths != null && filterPaths.size() > 0) {
+			if (filterPaths != null) {
 				for (String path : filterPaths) {
 					statusCommand.addPath(path);
 				}
@@ -282,7 +282,7 @@
 		if (!porcelain) {
 			outw.println(CLIText.formatLine(MessageFormat.format(pattern,
 					arguments)));
-			if (!pattern.equals("")) //$NON-NLS-1$
+			if (!pattern.isEmpty())
 				outw.println(CLIText.formatLine("")); //$NON-NLS-1$
 			outw.flush();
 		}
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 05f2378..d81a3ae 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
@@ -45,8 +45,8 @@
 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.ConfigConstants.CONFIG_SECTION_I18N;
 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;
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/DiffAlgorithms.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/DiffAlgorithms.java
index 0e1b398..85a7444 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/DiffAlgorithms.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/DiffAlgorithms.java
@@ -52,7 +52,6 @@
 import java.lang.reflect.Field;
 import java.util.ArrayList;
 import java.util.Collections;
-import java.util.Comparator;
 import java.util.List;
 
 import org.eclipse.jgit.diff.DiffAlgorithm;
@@ -235,15 +234,12 @@
 			}
 		}
 
-		Collections.sort(all, new Comparator<Test>() {
-			@Override
-			public int compare(Test a, Test b) {
-				int result = Long.signum(a.runningTimeNanos - b.runningTimeNanos);
-				if (result == 0) {
-					result = a.algorithm.name.compareTo(b.algorithm.name);
-				}
-				return result;
+		Collections.sort(all, (Test a, Test b) -> {
+			int result = Long.signum(a.runningTimeNanos - b.runningTimeNanos);
+			if (result == 0) {
+				result = a.algorithm.name.compareTo(b.algorithm.name);
 			}
+			return result;
 		});
 
 		File directory = repo.getDirectory();
@@ -338,12 +334,9 @@
 					}
 				}
 			}
-		} catch (IllegalArgumentException e) {
-			throw die("Cannot determine names", e); //$NON-NLS-1$
-		} catch (IllegalAccessException e) {
+		} catch (IllegalArgumentException | IllegalAccessException e) {
 			throw die("Cannot determine names", e); //$NON-NLS-1$
 		}
-
 		return all;
 	}
 
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/TextHashFunctions.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/TextHashFunctions.java
index 300a01d..a2ea8c2 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/TextHashFunctions.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/TextHashFunctions.java
@@ -403,9 +403,7 @@
 					folds.add(fold);
 				}
 			}
-		} catch (IllegalArgumentException e) {
-			throw new RuntimeException("Cannot determine names", e); //$NON-NLS-1$
-		} catch (IllegalAccessException e) {
+		} catch (IllegalArgumentException | IllegalAccessException e) {
 			throw new RuntimeException("Cannot determine names", e); //$NON-NLS-1$
 		}
 
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/VerifyReftable.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/VerifyReftable.java
index b38de14..15b6ff9 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/VerifyReftable.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/VerifyReftable.java
@@ -189,7 +189,7 @@
 			return;
 		}
 
-		if (!AnyObjectId.equals(exp.getObjectId(), act.getObjectId())) {
+		if (!AnyObjectId.isEqual(exp.getObjectId(), act.getObjectId())) {
 			throw die(String.format("expected %s to be %s, found %s",
 					exp.getName(),
 					id(exp.getObjectId()),
@@ -197,7 +197,8 @@
 		}
 
 		if (exp.getPeeledObjectId() != null
-				&& !AnyObjectId.equals(exp.getPeeledObjectId(), act.getPeeledObjectId())) {
+				&& !AnyObjectId.isEqual(exp.getPeeledObjectId(),
+						act.getPeeledObjectId())) {
 			throw die(String.format("expected %s to be %s, found %s",
 					exp.getName(),
 					id(exp.getPeeledObjectId()),
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/AbstractTreeIteratorHandler.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/AbstractTreeIteratorHandler.java
index a14f651..213d987 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/AbstractTreeIteratorHandler.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/AbstractTreeIteratorHandler.java
@@ -133,10 +133,7 @@
 		final CanonicalTreeParser p = new CanonicalTreeParser();
 		try (ObjectReader curs = clp.getRepository().newObjectReader()) {
 			p.reset(curs, clp.getRevWalk().parseTree(id));
-		} catch (MissingObjectException e) {
-			throw new CmdLineException(clp,
-					CLIText.format(CLIText.get().notATree), name);
-		} catch (IncorrectObjectTypeException e) {
+		} catch (MissingObjectException | IncorrectObjectTypeException e) {
 			throw new CmdLineException(clp,
 					CLIText.format(CLIText.get().notATree), name);
 		} catch (IOException e) {
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/PathTreeFilterHandler.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/PathTreeFilterHandler.java
index d99f88e..4a50708 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/PathTreeFilterHandler.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/PathTreeFilterHandler.java
@@ -96,7 +96,7 @@
 			filters.add(PathFilter.create(path));
 		}
 
-		if (filters.size() == 0)
+		if (filters.isEmpty())
 			return 0;
 		if (filters.size() == 1) {
 			setter.addValue(filters.get(0));
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/RevCommitHandler.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/RevCommitHandler.java
index b925e31..d4effa3 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/RevCommitHandler.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/RevCommitHandler.java
@@ -130,10 +130,7 @@
 		final RevCommit c;
 		try {
 			c = clp.getRevWalk().parseCommit(id);
-		} catch (MissingObjectException e) {
-			throw new CmdLineException(clp,
-					CLIText.format(CLIText.get().notACommit), name);
-		} catch (IncorrectObjectTypeException e) {
+		} catch (MissingObjectException | IncorrectObjectTypeException e) {
 			throw new CmdLineException(clp,
 					CLIText.format(CLIText.get().notACommit), name);
 		} catch (IOException e) {
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/RevTreeHandler.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/RevTreeHandler.java
index 85922a2..19841f6 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/RevTreeHandler.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/RevTreeHandler.java
@@ -102,10 +102,7 @@
 		final RevTree c;
 		try {
 			c = clp.getRevWalk().parseTree(id);
-		} catch (MissingObjectException e) {
-			throw new CmdLineException(clp,
-					CLIText.format(CLIText.get().notATree), name);
-		} catch (IncorrectObjectTypeException e) {
+		} catch (MissingObjectException | IncorrectObjectTypeException e) {
 			throw new CmdLineException(clp,
 					CLIText.format(CLIText.get().notATree), name);
 		} catch (IOException e) {
diff --git a/org.eclipse.jgit.ssh.apache.test/.settings/org.eclipse.pde.prefs b/org.eclipse.jgit.ssh.apache.test/.settings/org.eclipse.pde.prefs
new file mode 100644
index 0000000..2174e4f
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/.settings/org.eclipse.pde.prefs
@@ -0,0 +1,34 @@
+compilers.f.unresolved-features=1
+compilers.f.unresolved-plugins=1
+compilers.incompatible-environment=1
+compilers.p.build=1
+compilers.p.build.bin.includes=1
+compilers.p.build.encodings=2
+compilers.p.build.java.compiler=2
+compilers.p.build.java.compliance=1
+compilers.p.build.missing.output=2
+compilers.p.build.output.library=1
+compilers.p.build.source.library=1
+compilers.p.build.src.includes=1
+compilers.p.deprecated=1
+compilers.p.discouraged-class=1
+compilers.p.internal=1
+compilers.p.missing-packages=2
+compilers.p.missing-version-export-package=2
+compilers.p.missing-version-import-package=2
+compilers.p.missing-version-require-bundle=2
+compilers.p.no-required-att=0
+compilers.p.no.automatic.module=1
+compilers.p.not-externalized-att=2
+compilers.p.service.component.without.lazyactivation=1
+compilers.p.unknown-attribute=1
+compilers.p.unknown-class=1
+compilers.p.unknown-element=1
+compilers.p.unknown-identifier=1
+compilers.p.unknown-resource=1
+compilers.p.unresolved-ex-points=0
+compilers.p.unresolved-import=0
+compilers.s.create-docs=false
+compilers.s.doc-folder=doc
+compilers.s.open-tags=1
+eclipse.preferences.version=1
diff --git a/org.eclipse.jgit.ssh.apache.test/BUILD b/org.eclipse.jgit.ssh.apache.test/BUILD
index a13cf0b..18a48dc 100644
--- a/org.eclipse.jgit.ssh.apache.test/BUILD
+++ b/org.eclipse.jgit.ssh.apache.test/BUILD
@@ -10,7 +10,7 @@
     deps = [
         "//lib:eddsa",
         "//lib:junit",
-        "//lib:sshd-core",
+        "//lib:sshd-osgi",
         "//lib:sshd-sftp",
         "//org.eclipse.jgit:jgit",
         "//org.eclipse.jgit.ssh.apache:ssh-apache",
diff --git a/org.eclipse.jgit.ssh.apache.test/META-INF/MANIFEST.MF b/org.eclipse.jgit.ssh.apache.test/META-INF/MANIFEST.MF
index a297b89..e6debfe 100644
--- a/org.eclipse.jgit.ssh.apache.test/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.ssh.apache.test/META-INF/MANIFEST.MF
@@ -3,17 +3,17 @@
 Bundle-Name: %Bundle-Name
 Automatic-Module-Name: org.eclipse.jgit.ssh.apache.test
 Bundle-SymbolicName: org.eclipse.jgit.ssh.apache.test
-Bundle-Version: 5.3.10.qualifier
+Bundle-Version: 5.4.4.qualifier
 Bundle-Vendor: %Provider-Name
 Bundle-RequiredExecutionEnvironment: JavaSE-1.8
-Import-Package: org.eclipse.jgit.internal.transport.sshd.proxy;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.junit;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.junit.ssh;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.lib;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.transport;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.transport.ssh;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.transport.sshd;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.util;version="[5.3.10,5.4.0)",
+Import-Package: org.eclipse.jgit.internal.transport.sshd.proxy;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.junit;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.junit.ssh;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.lib;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.transport;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.transport.ssh;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.transport.sshd;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.util;version="[5.4.4,5.5.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/pom.xml b/org.eclipse.jgit.ssh.apache.test/pom.xml
index b28b5d6..367a3d4 100644
--- a/org.eclipse.jgit.ssh.apache.test/pom.xml
+++ b/org.eclipse.jgit.ssh.apache.test/pom.xml
@@ -50,7 +50,7 @@
   <parent>
     <groupId>org.eclipse.jgit</groupId>
     <artifactId>org.eclipse.jgit-parent</artifactId>
-    <version>5.3.10-SNAPSHOT</version>
+    <version>5.4.4-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.ssh.apache.test</artifactId>
diff --git a/org.eclipse.jgit.ssh.apache/BUILD b/org.eclipse.jgit.ssh.apache/BUILD
index 42388ad..fd88a8a 100644
--- a/org.eclipse.jgit.ssh.apache/BUILD
+++ b/org.eclipse.jgit.ssh.apache/BUILD
@@ -14,7 +14,7 @@
     deps = [
         "//lib:eddsa",
         "//lib:slf4j-api",
-        "//lib:sshd-core",
+        "//lib:sshd-osgi",
         "//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
index 7b0de19..1054cec 100644
--- a/org.eclipse.jgit.ssh.apache/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.ssh.apache/META-INF/MANIFEST.MF
@@ -5,9 +5,9 @@
 Bundle-SymbolicName: org.eclipse.jgit.ssh.apache
 Bundle-Vendor: %Provider-Name
 Bundle-ActivationPolicy: lazy
-Bundle-Version: 5.3.10.qualifier
+Bundle-Version: 5.4.4.qualifier
 Bundle-RequiredExecutionEnvironment: JavaSE-1.8
-Export-Package: org.eclipse.jgit.internal.transport.sshd;version="5.3.10";x-internal:=true;
+Export-Package: org.eclipse.jgit.internal.transport.sshd;version="5.4.4";x-internal:=true;
   uses:="org.apache.sshd.client,
    org.apache.sshd.client.auth,
    org.apache.sshd.client.auth.keyboard,
@@ -22,9 +22,9 @@
    org.apache.sshd.common.signature,
    org.apache.sshd.common.util.buffer,
    org.eclipse.jgit.transport",
- org.eclipse.jgit.internal.transport.sshd.auth;version="5.3.10";x-internal:=true,
- org.eclipse.jgit.internal.transport.sshd.proxy;version="5.3.10";x-friends:="org.eclipse.jgit.ssh.apache.test",
- org.eclipse.jgit.transport.sshd;version="5.3.10";
+ org.eclipse.jgit.internal.transport.sshd.auth;version="5.4.4";x-internal:=true,
+ org.eclipse.jgit.internal.transport.sshd.proxy;version="5.4.4";x-friends:="org.eclipse.jgit.ssh.apache.test",
+ org.eclipse.jgit.transport.sshd;version="5.4.4";
   uses:="org.eclipse.jgit.transport,
    org.apache.sshd.client.config.hosts,
    org.apache.sshd.common.keyprovider,
@@ -32,52 +32,54 @@
    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.3.10,5.4.0)",
- org.eclipse.jgit.errors;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.fnmatch;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.internal.storage.file;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.internal.transport.ssh;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.nls;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.transport;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.util;version="[5.3.10,5.4.0)",
+ org.apache.sshd.agent;version="[2.2.0,2.3.0)",
+ org.apache.sshd.client;version="[2.2.0,2.3.0)",
+ org.apache.sshd.client.auth;version="[2.2.0,2.3.0)",
+ org.apache.sshd.client.auth.keyboard;version="[2.2.0,2.3.0)",
+ org.apache.sshd.client.auth.password;version="[2.2.0,2.3.0)",
+ org.apache.sshd.client.auth.pubkey;version="[2.2.0,2.3.0)",
+ org.apache.sshd.client.channel;version="[2.2.0,2.3.0)",
+ org.apache.sshd.client.config.hosts;version="[2.2.0,2.3.0)",
+ org.apache.sshd.client.config.keys;version="[2.2.0,2.3.0)",
+ org.apache.sshd.client.future;version="[2.2.0,2.3.0)",
+ org.apache.sshd.client.keyverifier;version="[2.2.0,2.3.0)",
+ org.apache.sshd.client.session;version="[2.2.0,2.3.0)",
+ org.apache.sshd.client.subsystem.sftp;version="[2.2.0,2.3.0)",
+ org.apache.sshd.common;version="[2.2.0,2.3.0)",
+ org.apache.sshd.common.auth;version="[2.2.0,2.3.0)",
+ org.apache.sshd.common.channel;version="[2.2.0,2.3.0)",
+ org.apache.sshd.common.compression;version="[2.2.0,2.3.0)",
+ org.apache.sshd.common.config.keys;version="[2.2.0,2.3.0)",
+ org.apache.sshd.common.config.keys.loader;version="[2.2.0,2.3.0)",
+ org.apache.sshd.common.config.keys.loader.openssh.kdf;version="[2.2.0,2.3.0)",
+ org.apache.sshd.common.digest;version="[2.2.0,2.3.0)",
+ org.apache.sshd.common.forward;version="[2.2.0,2.3.0)",
+ org.apache.sshd.common.future;version="[2.2.0,2.3.0)",
+ org.apache.sshd.common.helpers;version="[2.2.0,2.3.0)",
+ org.apache.sshd.common.io;version="[2.2.0,2.3.0)",
+ org.apache.sshd.common.kex;version="[2.2.0,2.3.0)",
+ org.apache.sshd.common.keyprovider;version="[2.2.0,2.3.0)",
+ org.apache.sshd.common.mac;version="[2.2.0,2.3.0)",
+ org.apache.sshd.common.random;version="[2.2.0,2.3.0)",
+ org.apache.sshd.common.session;version="[2.2.0,2.3.0)",
+ org.apache.sshd.common.session.helpers;version="[2.2.0,2.3.0)",
+ org.apache.sshd.common.signature;version="[2.2.0,2.3.0)",
+ org.apache.sshd.common.subsystem.sftp;version="[2.2.0,2.3.0)",
+ org.apache.sshd.common.util;version="[2.2.0,2.3.0)",
+ org.apache.sshd.common.util.buffer;version="[2.2.0,2.3.0)",
+ org.apache.sshd.common.util.closeable;version="[2.2.0,2.3.0)",
+ org.apache.sshd.common.util.io;version="[2.2.0,2.3.0)",
+ org.apache.sshd.common.util.io.resource;version="[2.2.0,2.3.0)",
+ org.apache.sshd.common.util.logging;version="[2.2.0,2.3.0)",
+ org.apache.sshd.common.util.net;version="[2.2.0,2.3.0)",
+ org.apache.sshd.common.util.security;version="[2.2.0,2.3.0)",
+ org.apache.sshd.server.auth;version="[2.2.0,2.3.0)",
+ org.eclipse.jgit.annotations;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.errors;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.fnmatch;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.internal.storage.file;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.internal.transport.ssh;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.nls;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.transport;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.util;version="[5.4.4,5.5.0)",
  org.slf4j;version="[1.7.0,2.0.0)"
diff --git a/org.eclipse.jgit.ssh.apache/META-INF/SOURCE-MANIFEST.MF b/org.eclipse.jgit.ssh.apache/META-INF/SOURCE-MANIFEST.MF
index bedc9b0..3df5ff0 100644
--- a/org.eclipse.jgit.ssh.apache/META-INF/SOURCE-MANIFEST.MF
+++ b/org.eclipse.jgit.ssh.apache/META-INF/SOURCE-MANIFEST.MF
@@ -3,5 +3,5 @@
 Bundle-Name: org.eclipse.jgit.ssh.apache - Sources
 Bundle-SymbolicName: org.eclipse.jgit.ssh.apache.source
 Bundle-Vendor: Eclipse.org - JGit
-Bundle-Version: 5.3.10.qualifier
-Eclipse-SourceBundle: org.eclipse.jgit.ssh.apache;version="5.3.10.qualifier";roots="."
+Bundle-Version: 5.4.4.qualifier
+Eclipse-SourceBundle: org.eclipse.jgit.ssh.apache;version="5.4.4.qualifier";roots="."
diff --git a/org.eclipse.jgit.ssh.apache/pom.xml b/org.eclipse.jgit.ssh.apache/pom.xml
index d85c3a9..bb21214 100644
--- a/org.eclipse.jgit.ssh.apache/pom.xml
+++ b/org.eclipse.jgit.ssh.apache/pom.xml
@@ -50,7 +50,7 @@
   <parent>
     <groupId>org.eclipse.jgit</groupId>
     <artifactId>org.eclipse.jgit-parent</artifactId>
-    <version>5.3.10-SNAPSHOT</version>
+    <version>5.4.4-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.ssh.apache</artifactId>
@@ -75,7 +75,7 @@
 
     <dependency>
       <groupId>org.apache.sshd</groupId>
-      <artifactId>sshd-core</artifactId>
+      <artifactId>sshd-osgi</artifactId>
       <version>${apache-sshd-version}</version>
     </dependency>
 
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
index 1072f32..a1ec318 100644
--- 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
@@ -45,10 +45,13 @@
 import static java.text.MessageFormat.format;
 
 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.InvalidKeyException;
 import java.security.KeyPair;
+import java.security.PrivateKey;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
@@ -57,13 +60,20 @@
 import java.util.NoSuchElementException;
 import java.util.concurrent.CancellationException;
 
+import javax.security.auth.DestroyFailedException;
+
+import org.apache.sshd.common.NamedResource;
+import org.apache.sshd.common.config.keys.FilePasswordProvider;
+import org.apache.sshd.common.keyprovider.FileKeyPairProvider;
+import org.apache.sshd.common.session.SessionContext;
+import org.apache.sshd.common.util.io.resource.IoResource;
+import org.apache.sshd.common.util.security.SecurityUtils;
 import org.eclipse.jgit.transport.sshd.KeyCache;
 
 /**
- * A {@link EncryptedFileKeyPairProvider} that uses an external
- * {@link KeyCache}.
+ * A {@link FileKeyPairProvider} that uses an external {@link KeyCache}.
  */
-public class CachingKeyPairProvider extends EncryptedFileKeyPairProvider
+public class CachingKeyPairProvider extends FileKeyPairProvider
 		implements Iterable<KeyPair> {
 
 	private final KeyCache cache;
@@ -71,7 +81,7 @@
 	/**
 	 * Creates a new {@link CachingKeyPairProvider} using the given
 	 * {@link KeyCache}. If the cache is {@code null}, this is a simple
-	 * {@link EncryptedFileKeyPairProvider}.
+	 * {@link FileKeyPairProvider}.
 	 *
 	 * @param paths
 	 *            to load keys from
@@ -85,36 +95,36 @@
 
 	@Override
 	public Iterator<KeyPair> iterator() {
+		return iterator(null);
+	}
+
+	private Iterator<KeyPair> iterator(SessionContext session) {
 		Collection<? extends Path> resources = getPaths();
 		if (resources.isEmpty()) {
 			return Collections.emptyListIterator();
 		}
-		return new CancellingKeyPairIterator(resources);
+		return new CancellingKeyPairIterator(session, resources);
 	}
 
 	@Override
-	public Iterable<KeyPair> loadKeys() {
-		return this;
+	public Iterable<KeyPair> loadKeys(SessionContext session) {
+		return () -> iterator(session);
 	}
 
-	@Override
-	protected KeyPair doLoadKey(Path resource)
+	private KeyPair loadKey(SessionContext session, Path path)
 			throws IOException, GeneralSecurityException {
-		if (!Files.exists(resource)) {
-			log.warn(format(SshdText.get().identityFileNotFound, resource));
+		if (!Files.exists(path)) {
+			log.warn(format(SshdText.get().identityFileNotFound, path));
 			return null;
 		}
-		// 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();
+		IoResource<Path> resource = getIoResource(session, path);
 		if (cache == null) {
-			return doLoadKey(resourceId, resource, getPasswordFinder());
+			return loadKey(session, resource, path, getPasswordFinder());
 		}
 		Throwable t[] = { null };
-		KeyPair key = cache.get(resource, p -> {
+		KeyPair key = cache.get(path, p -> {
 			try {
-				return doLoadKey(resourceId, p, getPasswordFinder());
+				return loadKey(session, resource, p, getPasswordFinder());
 			} catch (IOException | GeneralSecurityException e) {
 				t[0] = e;
 				return null;
@@ -130,18 +140,55 @@
 		return key;
 	}
 
+	private KeyPair loadKey(SessionContext session, NamedResource resource,
+			Path path, FilePasswordProvider passwordProvider)
+			throws IOException, GeneralSecurityException {
+		try (InputStream stream = Files.newInputStream(path)) {
+			Iterable<KeyPair> ids = SecurityUtils.loadKeyPairIdentities(session,
+					resource, stream, passwordProvider);
+			if (ids == null) {
+				throw new InvalidKeyException(
+						format(SshdText.get().identityFileNoKey, path));
+			}
+			Iterator<KeyPair> keys = ids.iterator();
+			if (!keys.hasNext()) {
+				throw new InvalidKeyException(format(
+						SshdText.get().identityFileUnsupportedFormat, path));
+			}
+			KeyPair result = keys.next();
+			if (keys.hasNext()) {
+				log.warn(format(SshdText.get().identityFileMultipleKeys, path));
+				keys.forEachRemaining(k -> {
+					PrivateKey pk = k.getPrivate();
+					if (pk != null) {
+						try {
+							pk.destroy();
+						} catch (DestroyFailedException e) {
+							// Ignore
+						}
+					}
+				});
+			}
+			return result;
+		}
+	}
+
 	private class CancellingKeyPairIterator implements Iterator<KeyPair> {
 
+		private final SessionContext context;
+
 		private final Iterator<Path> paths;
 
 		private KeyPair nextItem;
 
 		private boolean nextSet;
 
-		public CancellingKeyPairIterator(Collection<? extends Path> resources) {
+		public CancellingKeyPairIterator(SessionContext session,
+				Collection<? extends Path> resources) {
 			List<Path> copy = new ArrayList<>(resources.size());
 			copy.addAll(resources);
 			paths = copy.iterator();
+			context = session;
 		}
 
 		@Override
@@ -152,7 +199,7 @@
 			nextSet = true;
 			while (nextItem == null && paths.hasNext()) {
 				try {
-					nextItem = doLoadKey(paths.next());
+					nextItem = loadKey(context, paths.next());
 				} catch (CancellationException cancelled) {
 					throw cancelled;
 				} catch (Exception other) {
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
deleted file mode 100644
index ef8e611..0000000
--- a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/EncryptedFileKeyPairProvider.java
+++ /dev/null
@@ -1,158 +0,0 @@
-/*
- * 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 abstract 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/JGitClientSession.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitClientSession.java
index dcf330a..56f8ade 100644
--- a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitClientSession.java
+++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitClientSession.java
@@ -46,6 +46,7 @@
 
 import java.io.IOException;
 import java.net.SocketAddress;
+import java.security.GeneralSecurityException;
 import java.security.PublicKey;
 import java.util.ArrayList;
 import java.util.Iterator;
@@ -173,7 +174,8 @@
 	}
 
 	@Override
-	protected byte[] sendKexInit() throws IOException {
+	protected byte[] sendKexInit()
+			throws IOException, GeneralSecurityException {
 		StatefulProxyConnector proxy = proxyHandler;
 		if (proxy != null) {
 			try {
@@ -187,7 +189,7 @@
 				// This is called only from the ClientSessionImpl
 				// constructor, where the return value is ignored.
 				return null;
-			} catch (IOException e) {
+			} catch (IOException | GeneralSecurityException e) {
 				throw e;
 			} catch (Exception other) {
 				throw new IOException(other.getLocalizedMessage(), other);
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
index 7b22b88..21e8bea 100644
--- 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
@@ -53,28 +53,11 @@
  * 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.
- *
  */
 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.
 	 *
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
deleted file mode 100644
index 0b3de4a..0000000
--- a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitPublicKeyAuthFactory.java
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * 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
deleted file mode 100644
index 63b3990..0000000
--- a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitPublicKeyAuthentication.java
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- * 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
deleted file mode 100644
index cda1262..0000000
--- a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitPublicKeyIterator.java
+++ /dev/null
@@ -1,275 +0,0 @@
-/*
- * 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
index b9ff5e5..98e71df 100644
--- a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitSshClient.java
+++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitSshClient.java
@@ -48,10 +48,12 @@
 import java.io.IOException;
 import java.net.InetSocketAddress;
 import java.net.Proxy;
+import java.net.SocketAddress;
 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.KeyPair;
 import java.util.Arrays;
 import java.util.Iterator;
@@ -66,12 +68,14 @@
 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.AttributeRepository;
 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.AbstractResourceKeyPairProvider;
-import org.apache.sshd.common.keyprovider.KeyPairProvider;
+import org.apache.sshd.common.keyprovider.KeyIdentityProvider;
+import org.apache.sshd.common.session.SessionContext;
 import org.apache.sshd.common.session.helpers.AbstractSession;
 import org.apache.sshd.common.util.ValidateUtils;
 import org.eclipse.jgit.internal.transport.sshd.proxy.HttpClientConnector;
@@ -117,7 +121,8 @@
 	}
 
 	@Override
-	public ConnectFuture connect(HostConfigEntry hostConfig)
+	public ConnectFuture connect(HostConfigEntry hostConfig,
+			AttributeRepository context, SocketAddress localAddress)
 			throws IOException {
 		if (connector == null) {
 			throw new IllegalStateException("SshClient not started."); //$NON-NLS-1$
@@ -149,7 +154,7 @@
 			address = configureProxy(proxy, address);
 			proxy.clearPassword();
 		}
-		connector.connect(address).addListener(listener);
+		connector.connect(address, this, localAddress).addListener(listener);
 		return connectFuture;
 	}
 
@@ -263,16 +268,16 @@
 				identities, keyCache);
 		ourConfiguredKeysProvider.setPasswordFinder(passwordProvider);
 		if (hostConfig.isIdentitiesOnly()) {
-			session.setKeyPairProvider(ourConfiguredKeysProvider);
+			session.setKeyIdentityProvider(ourConfiguredKeysProvider);
 		} else {
-			KeyPairProvider defaultKeysProvider = getKeyPairProvider();
+			KeyIdentityProvider defaultKeysProvider = getKeyIdentityProvider();
 			if (defaultKeysProvider instanceof AbstractResourceKeyPairProvider<?>) {
 				((AbstractResourceKeyPairProvider<?>) defaultKeysProvider)
 						.setPasswordFinder(passwordProvider);
 			}
-			KeyPairProvider combinedProvider = new CombinedKeyPairProvider(
+			KeyIdentityProvider combinedProvider = new CombinedKeyIdentityProvider(
 					ourConfiguredKeysProvider, defaultKeysProvider);
-			session.setKeyPairProvider(combinedProvider);
+			session.setKeyIdentityProvider(combinedProvider);
 		}
 		return session;
 	}
@@ -363,39 +368,30 @@
 	}
 
 	/**
-	 * A {@link KeyPairProvider} that iterates over the {@link Iterable}s
-	 * returned by other {@link KeyPairProvider}s.
+	 * A {@link KeyIdentityProvider} that iterates over the {@link Iterable}s
+	 * returned by other {@link KeyIdentityProvider}s.
 	 */
-	private static class CombinedKeyPairProvider implements KeyPairProvider {
+	private static class CombinedKeyIdentityProvider
+			implements KeyIdentityProvider {
 
-		private final List<KeyPairProvider> providers;
+		private final List<KeyIdentityProvider> providers;
 
-		public CombinedKeyPairProvider(KeyPairProvider... providers) {
+		public CombinedKeyIdentityProvider(KeyIdentityProvider... providers) {
 			this(Arrays.stream(providers).filter(Objects::nonNull)
 					.collect(Collectors.toList()));
 		}
 
-		public CombinedKeyPairProvider(List<KeyPairProvider> providers) {
+		public CombinedKeyIdentityProvider(
+				List<KeyIdentityProvider> 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() {
+		public Iterable<KeyPair> loadKeys(SessionContext context) {
 			return () -> new Iterator<KeyPair>() {
 
-				private Iterator<KeyPairProvider> factories = providers.iterator();
+				private Iterator<KeyIdentityProvider> factories = providers
+						.iterator();
 				private Iterator<KeyPair> current;
 
 				private Boolean hasElement;
@@ -407,7 +403,12 @@
 					}
 					while (current == null || !current.hasNext()) {
 						if (factories.hasNext()) {
-							current = factories.next().loadKeys().iterator();
+							try {
+								current = factories.next().loadKeys(context)
+										.iterator();
+							} catch (IOException | GeneralSecurityException e) {
+								throw new RuntimeException(e);
+							}
 						} else {
 							current = null;
 							hasElement = Boolean.FALSE;
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
index 9846439..6468b3e 100644
--- 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
@@ -47,11 +47,13 @@
 
 import java.io.File;
 import java.io.IOException;
+import java.net.SocketAddress;
 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.AttributeRepository;
 import org.apache.sshd.common.util.net.SshdSocketAddress;
 import org.eclipse.jgit.annotations.NonNull;
 import org.eclipse.jgit.internal.transport.ssh.OpenSshConfigFile;
@@ -101,7 +103,8 @@
 
 	@Override
 	public HostConfigEntry resolveEffectiveHost(String host, int port,
-			String username) throws IOException {
+			SocketAddress localAddress, String username,
+			AttributeRepository attributes) 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
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
index 7d8f3fd..381f7cf 100644
--- 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
@@ -60,6 +60,7 @@
 import java.security.GeneralSecurityException;
 import java.security.PublicKey;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.LinkedList;
@@ -586,9 +587,7 @@
 					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);
-			}
+			messages.addAll(Arrays.asList(warning.split("\n"))); //$NON-NLS-1$
 
 			CredentialsProvider provider = getCredentialsProvider(
 					clientSession);
@@ -673,7 +672,7 @@
 						continue;
 					}
 					try {
-						PublicKey serverKey = keyPart.resolvePublicKey(
+						PublicKey serverKey = keyPart.resolvePublicKey(null,
 								PublicKeyEntryResolver.IGNORING);
 						if (serverKey == null) {
 							LOG.warn(format(
diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/PasswordProviderWrapper.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/PasswordProviderWrapper.java
index 93bd102..bec65f1 100644
--- a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/PasswordProviderWrapper.java
+++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/PasswordProviderWrapper.java
@@ -50,6 +50,8 @@
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.atomic.AtomicInteger;
 
+import org.apache.sshd.common.NamedResource;
+import org.apache.sshd.common.session.SessionContext;
 import org.eclipse.jgit.annotations.NonNull;
 import org.eclipse.jgit.transport.CredentialsProvider;
 import org.eclipse.jgit.transport.URIish;
@@ -83,10 +85,12 @@
 	}
 
 	@Override
-	public String getPassword(String resourceKey) throws IOException {
+	public String getPassword(SessionContext session, NamedResource resource,
+			int attemptIndex) throws IOException {
+		String key = resource.getName();
 		int attempt = counts
-				.computeIfAbsent(resourceKey, k -> new AtomicInteger()).get();
-		char[] passphrase = delegate.getPassphrase(toUri(resourceKey), attempt);
+				.computeIfAbsent(key, k -> new AtomicInteger()).get();
+		char[] passphrase = delegate.getPassphrase(toUri(key), attempt);
 		if (passphrase == null) {
 			return null;
 		}
@@ -98,21 +102,23 @@
 	}
 
 	@Override
-	public ResourceDecodeResult handleDecodeAttemptResult(String resourceKey,
+	public ResourceDecodeResult handleDecodeAttemptResult(
+			SessionContext session, NamedResource resource, int retryIndex,
 			String password, Exception err)
 			throws IOException, GeneralSecurityException {
-		AtomicInteger count = counts.get(resourceKey);
+		String key = resource.getName();
+		AtomicInteger count = counts.get(key);
 		int numberOfAttempts = count == null ? 0 : count.incrementAndGet();
 		ResourceDecodeResult result = null;
 		try {
-			if (delegate.keyLoaded(toUri(resourceKey), numberOfAttempts, err)) {
+			if (delegate.keyLoaded(toUri(key), numberOfAttempts, err)) {
 				result = ResourceDecodeResult.RETRY;
 			} else {
 				result = ResourceDecodeResult.TERMINATE;
 			}
 		} finally {
 			if (result != ResourceDecodeResult.RETRY) {
-				counts.remove(resourceKey);
+				counts.remove(key);
 			}
 		}
 		return result;
diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/RepeatingFilePasswordProvider.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/RepeatingFilePasswordProvider.java
index e491cae..977f1a2 100644
--- 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
@@ -42,9 +42,6 @@
  */
 package org.eclipse.jgit.internal.transport.sshd;
 
-import java.io.IOException;
-import java.security.GeneralSecurityException;
-
 import org.apache.sshd.common.config.keys.FilePasswordProvider;
 
 /**
@@ -74,42 +71,4 @@
 		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/auth/BasicAuthentication.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/auth/BasicAuthentication.java
index a257a5e..6fa528d 100644
--- 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
@@ -145,18 +145,13 @@
 	 */
 	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,
+		PasswordAuthentication auth = AccessController.doPrivileged(
+				(PrivilegedAction<PasswordAuthentication>) () -> Authenticator
+						.requestPasswordAuthentication(proxy.getHostString(),
+								proxy.getAddress(), proxy.getPort(),
+								SshConstants.SSH_SCHEME,
 								SshdText.get().proxyPasswordPrompt, "Basic", //$NON-NLS-1$
-								null, RequestorType.PROXY);
-					}
-				});
+								null, RequestorType.PROXY));
 		if (auth == null) {
 			user = ""; //$NON-NLS-1$
 			throw new CancellationException(
diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/SshdSession.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/SshdSession.java
index 7d0e686..1e8d7d1 100644
--- a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/SshdSession.java
+++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/SshdSession.java
@@ -177,10 +177,7 @@
 				timeoutMillis -= TimeUnit.NANOSECONDS
 						.toMillis(System.nanoTime() - start);
 			}
-		} catch (IOException e) {
-			exec.close(true);
-			throw e;
-		} catch (RuntimeException e) {
+		} catch (IOException | RuntimeException e) {
 			exec.close(true);
 			throw e;
 		}
diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/SshdSessionFactory.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/SshdSessionFactory.java
index cdd47bf..90dc8ca 100644
--- a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/SshdSessionFactory.java
+++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/SshdSessionFactory.java
@@ -64,18 +64,19 @@
 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.auth.pubkey.UserAuthPublicKeyFactory;
 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.KeyPairProvider;
+import org.apache.sshd.common.config.keys.loader.openssh.kdf.BCryptKdfOptions;
+import org.apache.sshd.common.keyprovider.KeyIdentityProvider;
 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;
@@ -157,6 +158,11 @@
 		super();
 		this.keyCache = keyCache;
 		this.proxies = proxies;
+		// sshd limits the number of BCrypt KDF rounds to 255 by default.
+		// Decrypting such a key takes about two seconds on my machine.
+		// I consider this limit too low. The time increases linearly with the
+		// number of rounds.
+		BCryptKdfOptions.setMaxAllowedRounds(16384);
 	}
 
 	/** A simple general map key. */
@@ -211,7 +217,7 @@
 				}
 				HostConfigEntryResolver configFile = getHostConfigEntryResolver(
 						home, sshDir);
-				KeyPairProvider defaultKeysProvider = toKeyPairProvider(
+				KeyIdentityProvider defaultKeysProvider = toKeyIdentityProvider(
 						getDefaultKeys(sshDir));
 				KeyPasswordProvider passphrases = createKeyPasswordProvider(
 						credentialsProvider);
@@ -227,7 +233,7 @@
 				client.setUserInteraction(
 						new JGitUserInteraction(credentialsProvider));
 				client.setUserAuthFactories(getUserAuthFactories());
-				client.setKeyPairProvider(defaultKeysProvider);
+				client.setKeyIdentityProvider(defaultKeysProvider);
 				// JGit-specific things:
 				JGitSshClient jgitClient = (JGitSshClient) client;
 				jgitClient.setKeyCache(getKeyCache());
@@ -438,17 +444,18 @@
 
 	/**
 	 * Converts an {@link Iterable} of {link KeyPair}s into a
-	 * {@link KeyPairProvider}.
+	 * {@link KeyIdentityProvider}.
 	 *
 	 * @param keys
-	 *            to provide via the returned {@link KeyPairProvider}
-	 * @return a {@link KeyPairProvider} that provides the given {@code keys}
+	 *            to provide via the returned {@link KeyIdentityProvider}
+	 * @return a {@link KeyIdentityProvider} that provides the given
+	 *         {@code keys}
 	 */
-	private KeyPairProvider toKeyPairProvider(Iterable<KeyPair> keys) {
-		if (keys instanceof KeyPairProvider) {
-			return (KeyPairProvider) keys;
+	private KeyIdentityProvider toKeyIdentityProvider(Iterable<KeyPair> keys) {
+		if (keys instanceof KeyIdentityProvider) {
+			return (KeyIdentityProvider) keys;
 		}
-		return () -> keys;
+		return (session) -> keys;
 	}
 
 	/**
@@ -522,7 +529,7 @@
 		// Password auth doesn't have this problem.
 		return Collections.unmodifiableList(
 				Arrays.asList(GssApiWithMicAuthFactory.INSTANCE,
-						JGitPublicKeyAuthFactory.INSTANCE,
+						UserAuthPublicKeyFactory.INSTANCE,
 						JGitPasswordAuthFactory.INSTANCE,
 						UserAuthKeyboardInteractiveFactory.INSTANCE));
 	}
diff --git a/org.eclipse.jgit.test/.settings/org.eclipse.pde.prefs b/org.eclipse.jgit.test/.settings/org.eclipse.pde.prefs
new file mode 100644
index 0000000..2174e4f
--- /dev/null
+++ b/org.eclipse.jgit.test/.settings/org.eclipse.pde.prefs
@@ -0,0 +1,34 @@
+compilers.f.unresolved-features=1
+compilers.f.unresolved-plugins=1
+compilers.incompatible-environment=1
+compilers.p.build=1
+compilers.p.build.bin.includes=1
+compilers.p.build.encodings=2
+compilers.p.build.java.compiler=2
+compilers.p.build.java.compliance=1
+compilers.p.build.missing.output=2
+compilers.p.build.output.library=1
+compilers.p.build.source.library=1
+compilers.p.build.src.includes=1
+compilers.p.deprecated=1
+compilers.p.discouraged-class=1
+compilers.p.internal=1
+compilers.p.missing-packages=2
+compilers.p.missing-version-export-package=2
+compilers.p.missing-version-import-package=2
+compilers.p.missing-version-require-bundle=2
+compilers.p.no-required-att=0
+compilers.p.no.automatic.module=1
+compilers.p.not-externalized-att=2
+compilers.p.service.component.without.lazyactivation=1
+compilers.p.unknown-attribute=1
+compilers.p.unknown-class=1
+compilers.p.unknown-element=1
+compilers.p.unknown-identifier=1
+compilers.p.unknown-resource=1
+compilers.p.unresolved-ex-points=0
+compilers.p.unresolved-import=0
+compilers.s.create-docs=false
+compilers.s.doc-folder=doc
+compilers.s.open-tags=1
+eclipse.preferences.version=1
diff --git a/org.eclipse.jgit.test/BUILD b/org.eclipse.jgit.test/BUILD
index 2858427..9c8cc89 100644
--- a/org.eclipse.jgit.test/BUILD
+++ b/org.eclipse.jgit.test/BUILD
@@ -20,6 +20,7 @@
     "nls/MissingPropertyBundle.java",
     "nls/NoPropertiesBundle.java",
     "nls/NonTranslatedBundle.java",
+    "revwalk/ReachabilityCheckerTestCase.java",
     "revwalk/RevQueueTestCase.java",
     "revwalk/RevWalkTestCase.java",
     "transport/ObjectIdMatcher.java",
@@ -27,6 +28,7 @@
     "treewalk/filter/AlwaysCloneTreeFilter.java",
     "test/resources/SampleDataRepositoryTestCase.java",
     "util/CPUTimeStopWatch.java",
+    "util/http/HttpCookiesMatcher.java",
     "util/io/Strings.java",
 ]]
 
@@ -69,7 +71,7 @@
     deps = [
         "//lib:jsch",
         "//lib:junit",
-        "//lib:sshd-core",
+        "//lib:sshd-osgi",
         "//lib:sshd-sftp",
         "//org.eclipse.jgit:jgit",
         "//org.eclipse.jgit.junit:junit",
diff --git a/org.eclipse.jgit.test/META-INF/MANIFEST.MF b/org.eclipse.jgit.test/META-INF/MANIFEST.MF
index 254b99c..94a3a20 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.3.10.qualifier
+Bundle-Version: 5.4.4.qualifier
 Bundle-Localization: plugin
 Bundle-Vendor: %provider_name
 Bundle-ActivationPolicy: lazy
@@ -17,57 +17,58 @@
  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.bouncycastle.util.encoders;version="[1.60.0,2.0.0)",
- org.eclipse.jgit.annotations;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.api;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.api.errors;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.archive;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.attributes;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.awtui;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.blame;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.diff;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.dircache;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.errors;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.events;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.fnmatch;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.gitrepo;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.hooks;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.ignore;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.ignore.internal;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.internal;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.internal.fsck;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.internal.storage.dfs;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.internal.storage.file;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.internal.storage.io;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.internal.storage.pack;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.internal.storage.reftable;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.internal.storage.reftree;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.internal.transport.parser;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.junit;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.junit.ssh;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.junit.time;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.lfs;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.lib;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.merge;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.nls;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.notes;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.patch;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.pgm;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.pgm.internal;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.revplot;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.revwalk;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.revwalk.filter;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.storage.file;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.storage.pack;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.submodule;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.transport;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.transport.http;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.transport.resolver;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.treewalk;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.treewalk.filter;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.util;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.util.io;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.util.sha1;version="[5.3.10,5.4.0)",
+ org.bouncycastle.util.encoders;version="[1.61.0,2.0.0)",
+ org.eclipse.jgit.annotations;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.api;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.api.errors;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.archive;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.attributes;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.awtui;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.blame;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.diff;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.dircache;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.errors;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.events;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.fnmatch;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.gitrepo;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.hooks;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.ignore;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.ignore.internal;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.internal;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.internal.fsck;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.internal.storage.dfs;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.internal.storage.file;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.internal.storage.io;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.internal.storage.pack;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.internal.storage.reftable;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.internal.storage.reftree;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.internal.transport.http;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.internal.transport.parser;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.junit;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.junit.ssh;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.junit.time;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.lfs;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.lib;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.merge;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.nls;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.notes;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.patch;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.pgm;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.pgm.internal;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.revplot;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.revwalk;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.revwalk.filter;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.storage.file;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.storage.pack;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.submodule;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.transport;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.transport.http;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.transport.resolver;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.treewalk;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.treewalk.filter;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.util;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.util.io;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.util.sha1;version="[5.4.4,5.5.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)",
@@ -82,4 +83,4 @@
  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.3.10";x-friends:="org.eclipse.jgit.ssh.apache.test"
+Export-Package: org.eclipse.jgit.transport.ssh;version="5.4.4";x-friends:="org.eclipse.jgit.ssh.apache.test"
diff --git a/org.eclipse.jgit.test/pom.xml b/org.eclipse.jgit.test/pom.xml
index cfec525..720fa8c 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.3.10-SNAPSHOT</version>
+    <version>5.4.4-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.test</artifactId>
@@ -197,7 +197,7 @@
       <plugin>
         <artifactId>maven-surefire-plugin</artifactId>
         <configuration>
-          <argLine>@{argLine} -Xmx1024m -Dfile.encoding=UTF-8 -Djava.io.tmpdir=${project.build.directory}</argLine>
+          <argLine>@{argLine} -Xmx768m -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_ed25519_expensive_testpass b/org.eclipse.jgit.test/resources/org/eclipse/jgit/transport/ssh/id_ed25519_expensive_testpass
new file mode 100644
index 0000000..904cf30
--- /dev/null
+++ b/org.eclipse.jgit.test/resources/org/eclipse/jgit/transport/ssh/id_ed25519_expensive_testpass
@@ -0,0 +1,8 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jdHIAAAAGYmNyeXB0AAAAGAAAABAsFN8vig
+Nw4/Ow6xbb7MAZAAABAAAAAAEAAAAzAAAAC3NzaC1lZDI1NTE5AAAAIEZXZRjuttLufaP8
+wFD/i4lYPnKk01z46Jwv/9U4mPioAAAAkHLErPaXeC179rzXMaSwClstzsKvJ/Gqh2cY8d
+cWzymXtKZcivWMKesRHbC+1qRx53ofx15IzT5Fmg6NuNk4sm2s+lH8x8HN3CPWBfjGIelP
+iQUR6M6Y91mPigpRC2HUJmJIaFNdrRqFF84a5+qyK//tdy1fv4gNMLi5yPdXiL/Ttw05FS
+LkFikjfvSGZSO/MA==
+-----END OPENSSH PRIVATE KEY-----
diff --git a/org.eclipse.jgit.test/resources/org/eclipse/jgit/transport/ssh/id_ed25519_expensive_testpass.pub b/org.eclipse.jgit.test/resources/org/eclipse/jgit/transport/ssh/id_ed25519_expensive_testpass.pub
new file mode 100644
index 0000000..65038b5
--- /dev/null
+++ b/org.eclipse.jgit.test/resources/org/eclipse/jgit/transport/ssh/id_ed25519_expensive_testpass.pub
@@ -0,0 +1 @@
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEZXZRjuttLufaP8wFD/i4lYPnKk01z46Jwv/9U4mPio test
diff --git a/org.eclipse.jgit.test/src/org/eclipse/jgit/lib/Sets.java b/org.eclipse.jgit.test/src/org/eclipse/jgit/lib/Sets.java
index 5a01eae..e6100e2 100644
--- a/org.eclipse.jgit.test/src/org/eclipse/jgit/lib/Sets.java
+++ b/org.eclipse.jgit.test/src/org/eclipse/jgit/lib/Sets.java
@@ -43,6 +43,7 @@
 
 package org.eclipse.jgit.lib;
 
+import java.util.Arrays;
 import java.util.HashSet;
 import java.util.Set;
 
@@ -50,8 +51,7 @@
 	@SafeVarargs
 	public static <T> Set<T> of(T... elements) {
 		Set<T> ret = new HashSet<>();
-		for (T element : elements)
-			ret.add(element);
+		ret.addAll(Arrays.asList(elements));
 		return ret;
 	}
 }
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
index 2f367ba..b8c90b2 100644
--- 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
@@ -89,7 +89,9 @@
 			"id_rsa_4096_testpass", //
 			"id_ecdsa_256_testpass", //
 			"id_ecdsa_384_testpass", //
-			"id_ecdsa_521_testpass" };
+			"id_ecdsa_521_testpass", //
+			"id_ed25519_testpass", //
+			"id_ed25519_expensive_testpass" };
 
 	protected File defaultCloneDir;
 
diff --git a/org.eclipse.jgit.test/tests.bzl b/org.eclipse.jgit.test/tests.bzl
index d2f6d70..f27efcc 100644
--- a/org.eclipse.jgit.test/tests.bzl
+++ b/org.eclipse.jgit.test/tests.bzl
@@ -46,7 +46,7 @@
             additional_deps = [
                 "//lib:jsch",
                 "//lib:jzlib",
-                "//lib:sshd-core",
+                "//lib:sshd-osgi",
                 "//lib:sshd-sftp",
                 ":sshd-helpers",
             ]
@@ -54,6 +54,10 @@
             additional_deps = [
                 "//lib:mockito",
             ]
+        if src.endswith("TransportHttpTest.java"):
+            additional_deps = [
+                "//lib:mockito",
+            ]
         if src.endswith("ArchiveCommandTest.java"):
             additional_deps = [
                 "//lib:commons-compress",
diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/internal/transport/http/cookies-invalid.txt b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/internal/transport/http/cookies-invalid.txt
new file mode 100644
index 0000000..bbc6a73
--- /dev/null
+++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/internal/transport/http/cookies-invalid.txt
@@ -0,0 +1 @@
+some-domain	/some/path1	FALSE	0	key1	value1
\ No newline at end of file
diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/internal/transport/http/cookies-simple1.txt b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/internal/transport/http/cookies-simple1.txt
new file mode 100644
index 0000000..e06b38c
--- /dev/null
+++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/internal/transport/http/cookies-simple1.txt
@@ -0,0 +1,2 @@
+some-domain1	TRUE	/some/path1	FALSE	1893499200000	key1	valueFromSimple1
+some-domain1	TRUE	/some/path1	FALSE	1893499200000	key2	valueFromSimple1
\ No newline at end of file
diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/internal/transport/http/cookies-simple2.txt b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/internal/transport/http/cookies-simple2.txt
new file mode 100644
index 0000000..4bf6723f
--- /dev/null
+++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/internal/transport/http/cookies-simple2.txt
@@ -0,0 +1,2 @@
+some-domain1	TRUE	/some/path1	FALSE	1893499200000	key1	valueFromSimple2
+some-domain1	TRUE	/some/path1	FALSE	1893499200000	key3	valueFromSimple2
\ No newline at end of file
diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/internal/transport/http/cookies-with-empty-and-comment-lines.txt b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/internal/transport/http/cookies-with-empty-and-comment-lines.txt
new file mode 100644
index 0000000..a9b8a28
--- /dev/null
+++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/internal/transport/http/cookies-with-empty-and-comment-lines.txt
@@ -0,0 +1,8 @@
+# first line is a comment
+# the next cookie is supposed to be removed, because it has expired already
+some-domain1	TRUE	/some/path1	FALSE	0	key1	value1
+
+# expires date is 01/01/2030 @ 12:00am (UTC)
+#HttpOnly_.some-domain2	TRUE	/some/path2	TRUE	1893499200000	key2	value2
+
+some-domain3	TRUE	/some/path3	FALSE	1893499200000	key3	value3
\ No newline at end of file
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 139f199..c9852e8 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
@@ -98,7 +98,7 @@
 		StatusCommand command = git.status();
 		Status status = command.call();
 		Set<String> files = status.getUntracked();
-		assertTrue(files.size() > 0);
+		assertFalse(files.isEmpty());
 
 		// run clean
 		Set<String> cleanedFiles = git.clean().call();
@@ -120,7 +120,7 @@
 		StatusCommand command = git.status();
 		Status status = command.call();
 		Set<String> files = status.getUntracked();
-		assertTrue(files.size() > 0);
+		assertFalse(files.isEmpty());
 
 		// run clean
 		Set<String> cleanedFiles = git.clean().setCleanDirectories(true).call();
@@ -128,7 +128,7 @@
 		status = git.status().call();
 		files = status.getUntracked();
 
-		assertTrue(files.size() == 0);
+		assertTrue(files.isEmpty());
 		assertTrue(cleanedFiles.contains("File2.txt"));
 		assertTrue(cleanedFiles.contains("File3.txt"));
 		assertTrue(!cleanedFiles.contains("sub-noclean/File1.txt"));
@@ -143,7 +143,7 @@
 		StatusCommand command = git.status();
 		Status status = command.call();
 		Set<String> files = status.getUntracked();
-		assertTrue(files.size() > 0);
+		assertFalse(files.isEmpty());
 
 		// run clean with setPaths
 		Set<String> paths = new TreeSet<>();
@@ -164,7 +164,7 @@
 		StatusCommand command = git.status();
 		Status status = command.call();
 		Set<String> files = status.getUntracked();
-		assertTrue(files.size() > 0);
+		assertFalse(files.isEmpty());
 
 		// run clean
 		Set<String> cleanedFiles = git.clean().setDryRun(true).call();
@@ -186,7 +186,7 @@
 		StatusCommand command = git.status();
 		Status status = command.call();
 		Set<String> files = status.getUntracked();
-		assertTrue(files.size() > 0);
+		assertFalse(files.isEmpty());
 
 		// run clean
 		Set<String> cleanedFiles = git.clean().setDryRun(true)
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 383436c..2270a6a 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
@@ -718,6 +718,34 @@
 
 	}
 
+	@Test
+	public void testCloneWithPullMerge() throws Exception {
+		File directory = createTempDirectory("testCloneRepository1");
+		try (Git g = Git.init().setDirectory(directory).setBare(false).call()) {
+			g.remoteAdd().setName(Constants.DEFAULT_REMOTE_NAME)
+					.setUri(new URIish(fileUri())).call();
+			PullResult result = g.pull().setRebase(false).call();
+			assertTrue(result.isSuccessful());
+			assertEquals("refs/heads/master",
+					g.getRepository().getFullBranch());
+			checkFile(new File(directory, "Test.txt"), "Hello world");
+		}
+	}
+
+	@Test
+	public void testCloneWithPullRebase() throws Exception {
+		File directory = createTempDirectory("testCloneRepository1");
+		try (Git g = Git.init().setDirectory(directory).setBare(false).call()) {
+			g.remoteAdd().setName(Constants.DEFAULT_REMOTE_NAME)
+					.setUri(new URIish(fileUri())).call();
+			PullResult result = g.pull().setRebase(true).call();
+			assertTrue(result.isSuccessful());
+			assertEquals("refs/heads/master",
+					g.getRepository().getFullBranch());
+			checkFile(new File(directory, "Test.txt"), "Hello world");
+		}
+	}
+
 	private String fileUri() {
 		return "file://" + git.getRepository().getWorkTree().getAbsolutePath();
 	}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitCommandTest.java
index e4b7ed7..3bde0eb 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitCommandTest.java
@@ -487,7 +487,7 @@
 	}
 
 	private static String reflogComments(List<ReflogEntry> entries) {
-		StringBuffer b = new StringBuffer();
+		StringBuilder b = new StringBuilder();
 		for (ReflogEntry e : entries) {
 			b.append(e.getComment()).append(";");
 		}
@@ -663,6 +663,54 @@
 		}
 	}
 
+	@Test
+	public void testDeletionConflictWithAutoCrlf() throws Exception {
+		try (Git git = new Git(db)) {
+			// Commit a file with CR/LF into the index
+			FileBasedConfig config = db.getConfig();
+			config.setString("core", null, "autocrlf", "false");
+			config.save();
+			File file = writeTrashFile("file.txt", "foo\r\n");
+			git.add().addFilepattern("file.txt").call();
+			git.commit().setMessage("Initial").call();
+			// Switch to side branch
+			git.checkout().setCreateBranch(true).setName("side").call();
+			assertTrue(file.delete());
+			git.rm().addFilepattern("file.txt").call();
+			git.commit().setMessage("Side").call();
+			// Switch on autocrlf=true
+			config.setString("core", null, "autocrlf", "true");
+			config.save();
+			// Switch back to master and commit a conflict
+			git.checkout().setName("master").call();
+			writeTrashFile("file.txt", "foob\r\n");
+			git.add().addFilepattern("file.txt").call();
+			assertEquals("[file.txt, mode:100644, content:foob\r\n]",
+					indexState(CONTENT));
+			writeTrashFile("g", "file2.txt", "anything");
+			git.add().addFilepattern("g/file2.txt");
+			RevCommit master = git.commit().setMessage("Second").call();
+			// Switch to side branch again so that the deletion is "ours"
+			git.checkout().setName("side").call();
+			// Cherry pick master: produces a delete-modify conflict.
+			CherryPickResult pick = git.cherryPick().include(master).call();
+			assertEquals("Expected a cherry-pick conflict",
+					CherryPickStatus.CONFLICTING, pick.getStatus());
+			// XXX: g/file2.txt should actually be staged already, but isn't.
+			git.add().addFilepattern("g/file2.txt").call();
+			// Resolve the conflict by taking the master version
+			writeTrashFile("file.txt", "foob\r\n");
+			git.add().addFilepattern("file.txt").call();
+			git.commit().setMessage("Cherry").call();
+			// We expect this to be committed with a single LF since there is no
+			// "ours" stage.
+			assertEquals(
+					"[file.txt, mode:100644, content:foob\n]"
+							+ "[g/file2.txt, mode:100644, content:anything]",
+					indexState(CONTENT));
+		}
+	}
+
 	private void testConflictWithAutoCrlf(String baseLf, String lf)
 			throws Exception {
 		try (Git git = new Git(db)) {
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/DescribeCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/DescribeCommandTest.java
index 807079e..df9ae6a 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
@@ -109,7 +109,7 @@
 		assertNameStartsWith(c4, "3e563c5");
 
 		assertNull(describe(c1));
-		assertNull(describe(c1, true));
+		assertNull(describe(c1, true, false));
 		assertNull(describe(c1, "a*", "b*", "c*"));
 		assertNull(describe(c2, "bob*"));
 		assertNull(describe(c2, "?ob*"));
@@ -120,7 +120,7 @@
 			assertEquals("alice-t1", describe(c2, "a*", "b*", "c*"));
 
 			assertEquals("bob-t2", describe(c3));
-			assertEquals("bob-t2-0-g44579eb", describe(c3, true));
+			assertEquals("bob-t2-0-g44579eb", describe(c3, true, false));
 			assertEquals("alice-t1-1-g44579eb", describe(c3, "alice*"));
 			assertEquals("alice-t1-1-g44579eb", describe(c3, "a??c?-t*"));
 			assertEquals("bob-t2", describe(c3, "bob*"));
@@ -129,7 +129,7 @@
 
 			// the value verified with git-describe(1)
 			assertEquals("bob-t2-1-g3e563c5", describe(c4));
-			assertEquals("bob-t2-1-g3e563c5", describe(c4, true));
+			assertEquals("bob-t2-1-g3e563c5", describe(c4, true, false));
 			assertEquals("alice-t1-2-g3e563c5", describe(c4, "alice*"));
 			assertEquals("bob-t2-1-g3e563c5", describe(c4, "bob*"));
 			assertEquals("bob-t2-1-g3e563c5", describe(c4, "a*", "b*", "c*"));
@@ -137,6 +137,10 @@
 			assertEquals(null, describe(c2));
 			assertEquals(null, describe(c3));
 			assertEquals(null, describe(c4));
+
+			assertEquals("3747db3", describe(c2, false, true));
+			assertEquals("44579eb", describe(c3, false, true));
+			assertEquals("3e563c5", describe(c4, false, true));
 		}
 
 		// test default target
@@ -169,6 +173,10 @@
 		if (!useAnnotatedTags && !describeUseAllTags) {
 			assertEquals(null, describe(c1));
 			assertEquals(null, describe(c2));
+
+			assertEquals("fd70040", describe(c1, false, true));
+			assertEquals("b89dead", describe(c2, false, true));
+
 			return;
 		}
 
@@ -203,7 +211,6 @@
 			assertNotNull(describe(c1, "v1.1*", "v1.0*"));
 			assertNotNull(describe(c2, "v1.0*", "v1.1*"));
 			assertNotNull(describe(c2, "v1.1*", "v1.0*"));
-
 		}
 	}
 
@@ -236,9 +243,11 @@
 			assertEquals("2 commits: c4 and c3", "t-2-g119892b", describe(c4));
 		} else {
 			assertEquals(null, describe(c4));
+
+			assertEquals("119892b", describe(c4, false, true));
 		}
 		assertNull(describe(c3));
-		assertNull(describe(c3, true));
+		assertNull(describe(c3, true, false));
 	}
 
 	private void branch(String name, ObjectId base) throws GitAPIException {
@@ -280,6 +289,9 @@
 		} else {
 			assertEquals(null, describe(c4));
 			assertEquals(null, describe(c3));
+
+			assertEquals("119892b", describe(c4, false, true));
+			assertEquals("0244e7f", describe(c3, false, true));
 		}
 	}
 
@@ -369,6 +381,8 @@
 			assertEquals("t1-3-gbb389a4", describe(c4));
 		} else {
 			assertEquals(null, describe(c4));
+
+			assertEquals("bb389a4", describe(c4, false, true));
 		}
 	}
 
@@ -402,6 +416,25 @@
 			assertEquals("t2-4-gbb389a4", describe(c4));
 		} else {
 			assertEquals(null, describe(c4));
+
+			assertEquals("bb389a4", describe(c4, false, true));
+		}
+	}
+
+	@Test
+	public void globMatchWithSlashes() throws Exception {
+		ObjectId c1 = modify("aaa");
+		tag("a/b/version");
+		ObjectId c2 = modify("bbb");
+		tag("a/b/version2");
+		if (useAnnotatedTags || describeUseAllTags) {
+			assertEquals("a/b/version", describe(c1, "*/version*"));
+			assertEquals("a/b/version2", describe(c2, "*/version*"));
+		} else {
+			assertNull(describe(c1));
+			assertNull(describe(c1, "*/version*"));
+			assertNull(describe(c2));
+			assertNull(describe(c2, "*/version*"));
 		}
 	}
 
@@ -434,14 +467,14 @@
 		}
 	}
 
-	private String describe(ObjectId c1, boolean longDesc)
+	private String describe(ObjectId c1, boolean longDesc, boolean always)
 			throws GitAPIException, IOException {
 		return git.describe().setTarget(c1).setTags(describeUseAllTags)
-				.setLong(longDesc).call();
+				.setLong(longDesc).setAlways(always).call();
 	}
 
 	private String describe(ObjectId c1) throws GitAPIException, IOException {
-		return describe(c1, false);
+		return describe(c1, false, false);
 	}
 
 	private String describe(ObjectId c1, String... patterns) throws Exception {
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PullCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PullCommandTest.java
index 9461c42..2fd3788 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PullCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PullCommandTest.java
@@ -346,14 +346,11 @@
 	@Test
 	/** global rebase config should be respected */
 	public void testPullWithRebasePreserve1Config() throws Exception {
-		Callable<PullResult> setup = new Callable<PullResult>() {
-			@Override
-			public PullResult call() throws Exception {
-				StoredConfig config = dbTarget.getConfig();
-				config.setString("pull", null, "rebase", "preserve");
-				config.save();
-				return target.pull().call();
-			}
+		Callable<PullResult> setup = () -> {
+			StoredConfig config = dbTarget.getConfig();
+			config.setString("pull", null, "rebase", "preserve");
+			config.save();
+			return target.pull().call();
 		};
 		doTestPullWithRebase(setup, TestPullMode.REBASE_PREASERVE);
 	}
@@ -361,15 +358,12 @@
 	@Test
 	/** the branch-local config should win over the global config */
 	public void testPullWithRebasePreserveConfig2() throws Exception {
-		Callable<PullResult> setup = new Callable<PullResult>() {
-			@Override
-			public PullResult call() throws Exception {
-				StoredConfig config = dbTarget.getConfig();
-				config.setString("pull", null, "rebase", "false");
-				config.setString("branch", "master", "rebase", "preserve");
-				config.save();
-				return target.pull().call();
-			}
+		Callable<PullResult> setup = () -> {
+			StoredConfig config = dbTarget.getConfig();
+			config.setString("pull", null, "rebase", "false");
+			config.setString("branch", "master", "rebase", "preserve");
+			config.save();
+			return target.pull().call();
 		};
 		doTestPullWithRebase(setup, TestPullMode.REBASE_PREASERVE);
 	}
@@ -377,14 +371,11 @@
 	@Test
 	/** the branch-local config should be respected */
 	public void testPullWithRebasePreserveConfig3() throws Exception {
-		Callable<PullResult> setup = new Callable<PullResult>() {
-			@Override
-			public PullResult call() throws Exception {
-				StoredConfig config = dbTarget.getConfig();
-				config.setString("branch", "master", "rebase", "preserve");
-				config.save();
-				return target.pull().call();
-			}
+		Callable<PullResult> setup = () -> {
+			StoredConfig config = dbTarget.getConfig();
+			config.setString("branch", "master", "rebase", "preserve");
+			config.save();
+			return target.pull().call();
 		};
 		doTestPullWithRebase(setup, TestPullMode.REBASE_PREASERVE);
 	}
@@ -392,14 +383,11 @@
 	@Test
 	/** global rebase config should be respected */
 	public void testPullWithRebaseConfig1() throws Exception {
-		Callable<PullResult> setup = new Callable<PullResult>() {
-			@Override
-			public PullResult call() throws Exception {
-				StoredConfig config = dbTarget.getConfig();
-				config.setString("pull", null, "rebase", "true");
-				config.save();
-				return target.pull().call();
-			}
+		Callable<PullResult> setup = () -> {
+			StoredConfig config = dbTarget.getConfig();
+			config.setString("pull", null, "rebase", "true");
+			config.save();
+			return target.pull().call();
 		};
 		doTestPullWithRebase(setup, TestPullMode.REBASE);
 	}
@@ -407,15 +395,12 @@
 	@Test
 	/** the branch-local config should win over the global config */
 	public void testPullWithRebaseConfig2() throws Exception {
-		Callable<PullResult> setup = new Callable<PullResult>() {
-			@Override
-			public PullResult call() throws Exception {
-				StoredConfig config = dbTarget.getConfig();
-				config.setString("pull", null, "rebase", "preserve");
-				config.setString("branch", "master", "rebase", "true");
-				config.save();
-				return target.pull().call();
-			}
+		Callable<PullResult> setup = () -> {
+			StoredConfig config = dbTarget.getConfig();
+			config.setString("pull", null, "rebase", "preserve");
+			config.setString("branch", "master", "rebase", "true");
+			config.save();
+			return target.pull().call();
 		};
 		doTestPullWithRebase(setup, TestPullMode.REBASE);
 	}
@@ -423,14 +408,11 @@
 	@Test
 	/** the branch-local config should be respected */
 	public void testPullWithRebaseConfig3() throws Exception {
-		Callable<PullResult> setup = new Callable<PullResult>() {
-			@Override
-			public PullResult call() throws Exception {
-				StoredConfig config = dbTarget.getConfig();
-				config.setString("branch", "master", "rebase", "true");
-				config.save();
-				return target.pull().call();
-			}
+		Callable<PullResult> setup = () -> {
+			StoredConfig config = dbTarget.getConfig();
+			config.setString("branch", "master", "rebase", "true");
+			config.save();
+			return target.pull().call();
 		};
 		doTestPullWithRebase(setup, TestPullMode.REBASE);
 	}
@@ -438,27 +420,19 @@
 	@Test
 	/** without config it should merge */
 	public void testPullWithoutConfig() throws Exception {
-		Callable<PullResult> setup = new Callable<PullResult>() {
-			@Override
-			public PullResult call() throws Exception {
-				return target.pull().call();
-			}
-		};
+		Callable<PullResult> setup = target.pull()::call;
 		doTestPullWithRebase(setup, TestPullMode.MERGE);
 	}
 
 	@Test
 	/** the branch local config should win over the global config */
 	public void testPullWithMergeConfig() throws Exception {
-		Callable<PullResult> setup = new Callable<PullResult>() {
-			@Override
-			public PullResult call() throws Exception {
-				StoredConfig config = dbTarget.getConfig();
-				config.setString("pull", null, "rebase", "true");
-				config.setString("branch", "master", "rebase", "false");
-				config.save();
-				return target.pull().call();
-			}
+		Callable<PullResult> setup = () -> {
+			StoredConfig config = dbTarget.getConfig();
+			config.setString("pull", null, "rebase", "true");
+			config.setString("branch", "master", "rebase", "false");
+			config.save();
+			return target.pull().call();
 		};
 		doTestPullWithRebase(setup, TestPullMode.MERGE);
 	}
@@ -466,14 +440,11 @@
 	@Test
 	/** the branch local config should win over the global config */
 	public void testPullWithMergeConfig2() throws Exception {
-		Callable<PullResult> setup = new Callable<PullResult>() {
-			@Override
-			public PullResult call() throws Exception {
-				StoredConfig config = dbTarget.getConfig();
-				config.setString("pull", null, "rebase", "false");
-				config.save();
-				return target.pull().call();
-			}
+		Callable<PullResult> setup = () -> {
+			StoredConfig config = dbTarget.getConfig();
+			config.setString("pull", null, "rebase", "false");
+			config.save();
+			return target.pull().call();
 		};
 		doTestPullWithRebase(setup, TestPullMode.MERGE);
 	}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/TreeWalkAttributeTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/TreeWalkAttributeTest.java
index 1d9cd78..d6aead4 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/TreeWalkAttributeTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/TreeWalkAttributeTest.java
@@ -49,6 +49,7 @@
 import java.io.File;
 import java.io.IOException;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashSet;
@@ -854,8 +855,7 @@
 
 	static Set<Attribute> asSet(Attribute... attrs) {
 		HashSet<Attribute> result = new HashSet<>();
-		for (Attribute attr : attrs)
-			result.add(attr);
+		result.addAll(Arrays.asList(attrs));
 		return result;
 	}
 
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheBasicTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheBasicTest.java
index 014406e..972dc60 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheBasicTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheBasicTest.java
@@ -205,8 +205,9 @@
 		}
 
 		final DirCacheBuilder b = dc.builder();
-		for (int i = 0; i < ents.length; i++)
-			b.add(ents[i]);
+		for (DirCacheEntry ent : ents) {
+			b.add(ent);
+		}
 		b.finish();
 		assertFalse(dc.hasUnmergedPaths());
 
@@ -229,8 +230,9 @@
 		ents[2].setFileMode(FileMode.REGULAR_FILE);
 
 		final DirCacheBuilder b = dc.builder();
-		for (int i = 0; i < ents.length; i++)
-			b.add(ents[i]);
+		for (DirCacheEntry ent : ents) {
+			b.add(ent);
+		}
 		b.finish();
 		assertTrue(dc.hasUnmergedPaths());
 	}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheBuilderIteratorTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheBuilderIteratorTest.java
index 3598f3a..6020a74 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheBuilderIteratorTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheBuilderIteratorTest.java
@@ -71,8 +71,9 @@
 		}
 		{
 			final DirCacheBuilder b = dc.builder();
-			for (int i = 0; i < ents.length; i++)
-				b.add(ents[i]);
+			for (DirCacheEntry ent : ents) {
+				b.add(ent);
+			}
 			b.finish();
 		}
 
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 50753ae..cdf86f0 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
@@ -211,12 +211,8 @@
 		boolean receivedEvent = false;
 
 		DirCache dc = db.lockDirCache();
-		IndexChangedListener listener = new IndexChangedListener() {
-
-			@Override
-			public void onIndexChanged(IndexChangedEvent event) {
-				throw new ReceivedEventMarkerException();
-			}
+		IndexChangedListener listener = (IndexChangedEvent event) -> {
+			throw new ReceivedEventMarkerException();
 		};
 
 		ListenerList l = db.getListenerList();
@@ -239,12 +235,8 @@
 		// do the same again, as this doesn't change index compared to first
 		// round we should get no event this time
 		dc = db.lockDirCache();
-		listener = new IndexChangedListener() {
-
-			@Override
-			public void onIndexChanged(IndexChangedEvent event) {
-				throw new ReceivedEventMarkerException();
-			}
+		listener = (IndexChangedEvent event) -> {
+			throw new ReceivedEventMarkerException();
 		};
 
 		l = db.getListenerList();
@@ -302,8 +294,9 @@
 		}
 
 		final DirCacheBuilder b = dc.builder();
-		for (int i = 0; i < ents.length; i++)
-			b.add(ents[i]);
+		for (DirCacheEntry ent : ents) {
+			b.add(ent);
+		}
 		b.finish();
 
 		assertEquals(paths.length, dc.getEntryCount());
@@ -352,8 +345,9 @@
 		}
 		{
 			final DirCacheBuilder b = dc.builder();
-			for (int i = 0; i < ents.length; i++)
-				b.add(ents[i]);
+			for (DirCacheEntry ent : ents) {
+				b.add(ent);
+			}
 			b.finish();
 		}
 		assertEquals(paths.length, dc.getEntryCount());
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheFindTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheFindTest.java
index 3b8c6ee..7fc2801 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheFindTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheFindTest.java
@@ -66,8 +66,9 @@
 		final int aLast = 3;
 
 		final DirCacheBuilder b = dc.builder();
-		for (int i = 0; i < ents.length; i++)
-			b.add(ents[i]);
+		for (DirCacheEntry ent : ents) {
+			b.add(ent);
+		}
 		b.finish();
 
 		assertEquals(paths.length, dc.getEntryCount());
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheIteratorTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheIteratorTest.java
index 82565fc..bc99aee 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheIteratorTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheIteratorTest.java
@@ -94,8 +94,9 @@
 		}
 
 		final DirCacheBuilder b = dc.builder();
-		for (int i = 0; i < ents.length; i++)
-			b.add(ents[i]);
+		for (DirCacheEntry ent : ents) {
+			b.add(ent);
+		}
 		b.finish();
 
 		final DirCacheIterator i = new DirCacheIterator(dc);
@@ -121,8 +122,9 @@
 		}
 
 		final DirCacheBuilder b = dc.builder();
-		for (int i = 0; i < ents.length; i++)
-			b.add(ents[i]);
+		for (DirCacheEntry ent : ents) {
+			b.add(ent);
+		}
 		b.finish();
 
 		final DirCacheIterator i = new DirCacheIterator(dc);
@@ -154,8 +156,9 @@
 		}
 
 		final DirCacheBuilder b = dc.builder();
-		for (int i = 0; i < ents.length; i++)
-			b.add(ents[i]);
+		for (DirCacheEntry ent : ents) {
+			b.add(ent);
+		}
 		b.finish();
 
 		final String[] expPaths = { "a-", "a", "a0b" };
@@ -200,8 +203,9 @@
 		}
 
 		final DirCacheBuilder b = dc.builder();
-		for (int i = 0; i < ents.length; i++)
-			b.add(ents[i]);
+		for (DirCacheEntry ent : ents) {
+			b.add(ent);
+		}
 		b.finish();
 
 		final DirCacheIterator i = new DirCacheIterator(dc);
@@ -236,8 +240,9 @@
 		}
 
 		final DirCacheBuilder b = dc.builder();
-		for (int i = 0; i < ents.length; i++)
-			b.add(ents[i]);
+		for (DirCacheEntry ent : ents) {
+			b.add(ent);
+		}
 		b.finish();
 
 		try (TreeWalk tw = new TreeWalk(db)) {
@@ -271,8 +276,9 @@
 		}
 
 		final DirCacheBuilder b = dc.builder();
-		for (int i = 0; i < ents.length; i++)
-			b.add(ents[i]);
+		for (DirCacheEntry ent : ents) {
+			b.add(ent);
+		}
 		b.finish();
 
 		DirCacheIterator dci = new DirCacheIterator(dc);
@@ -365,8 +371,9 @@
 		}
 
 		final DirCacheBuilder b = dc.builder();
-		for (int i = 0; i < ents.length; i++)
-			b.add(ents[i]);
+		for (DirCacheEntry ent : ents) {
+			b.add(ent);
+		}
 		b.finish();
 
 		DirCacheIterator dci = new DirCacheIterator(dc);
@@ -398,8 +405,9 @@
 		}
 
 		final DirCacheBuilder b = dc.builder();
-		for (int i = 0; i < ents.length; i++)
-			b.add(ents[i]);
+		for (DirCacheEntry ent : ents) {
+			b.add(ent);
+		}
 		b.finish();
 
 		try (TreeWalk tw = new TreeWalk(db)) {
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheTreeTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheTreeTest.java
index f662e26..513db75 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheTreeTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheTreeTest.java
@@ -102,8 +102,9 @@
 		final int aLast = 3;
 
 		final DirCacheBuilder b = dc.builder();
-		for (int i = 0; i < ents.length; i++)
-			b.add(ents[i]);
+		for (DirCacheEntry ent : ents) {
+			b.add(ent);
+		}
 		b.finish();
 
 		assertNull(dc.getCacheTree(false));
@@ -142,8 +143,9 @@
 		final int acLast = 3;
 
 		final DirCacheBuilder b = dc.builder();
-		for (int i = 0; i < ents.length; i++)
-			b.add(ents[i]);
+		for (DirCacheEntry ent : ents) {
+			b.add(ent);
+		}
 		b.finish();
 
 		assertNull(dc.getCacheTree(false));
@@ -198,8 +200,9 @@
 		}
 
 		final DirCacheBuilder b = dc.builder();
-		for (int i = 0; i < ents.length; i++)
-			b.add(ents[i]);
+		for (DirCacheEntry ent : ents) {
+			b.add(ent);
+		}
 
 		b.commit();
 		DirCache read = db.readDirCache();
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/events/ConfigChangeEventTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/events/ConfigChangeEventTest.java
index 3624100..b6f312d 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/events/ConfigChangeEventTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/events/ConfigChangeEventTest.java
@@ -54,12 +54,9 @@
 	@Test
 	public void testFileRepository_ChangeEventsOnlyOnSave() throws Exception {
 		final ConfigChangedEvent[] events = new ConfigChangedEvent[1];
-		db.getListenerList().addConfigChangedListener(
-				new ConfigChangedListener() {
-					@Override
-					public void onConfigChanged(ConfigChangedEvent event) {
-						events[0] = event;
-					}
+		db.getListenerList()
+				.addConfigChangedListener((ConfigChangedEvent event) -> {
+					events[0] = event;
 				});
 		FileBasedConfig config = db.getConfig();
 		assertNull(events[0]);
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsGarbageCollectorTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsGarbageCollectorTest.java
index b5c73c2..858f5b9 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsGarbageCollectorTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsGarbageCollectorTest.java
@@ -975,7 +975,7 @@
 				rw.markStart(rw.parseCommit(ref.getObjectId()));
 			}
 			for (RevCommit next; (next = rw.next()) != null;) {
-				if (AnyObjectId.equals(next, id)) {
+				if (AnyObjectId.isEqual(next, id)) {
 					return true;
 				}
 			}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/InMemoryRepositoryTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/InMemoryRepositoryTest.java
new file mode 100644
index 0000000..bab6110
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/InMemoryRepositoryTest.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2019, 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.storage.dfs;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Set;
+
+import org.eclipse.jgit.junit.TestRepository;
+import org.eclipse.jgit.lib.ObjectIdRef;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.Ref.Storage;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevTag;
+import org.junit.Test;
+
+public class InMemoryRepositoryTest {
+
+	@Test
+	public void keepUpdateIndexPeelingTag() throws Exception {
+		InMemoryRepository repo = new InMemoryRepository(
+				new DfsRepositoryDescription());
+		try (TestRepository<InMemoryRepository> git = new TestRepository<>(
+				repo)) {
+			RevCommit commit = git.branch("master").commit()
+					.message("first commit").create();
+			RevTag tag = git.tag("v0.1", commit);
+			git.update("refs/tags/v0.1", tag);
+
+			Ref unpeeledTag = new ObjectIdRef.Unpeeled(Storage.LOOSE,
+					"refs/tags/v0.1", tag.getId(), 1000);
+
+			Ref peeledTag = repo.getRefDatabase().peel(unpeeledTag);
+			assertTrue(peeledTag instanceof ObjectIdRef.PeeledTag);
+			assertEquals(1000, peeledTag.getUpdateIndex());
+		}
+	}
+
+	@Test
+	public void keepUpdateIndexPeelingNonTag() throws Exception {
+		InMemoryRepository repo = new InMemoryRepository(
+				new DfsRepositoryDescription());
+		try (TestRepository<InMemoryRepository> git = new TestRepository<>(
+				repo)) {
+			RevCommit commit = git.branch("master").commit()
+					.message("first commit").create();
+
+			Ref unpeeledRef = new ObjectIdRef.Unpeeled(Storage.LOOSE,
+					"refs/heads/master", commit.getId(), 1000);
+			Ref peeledRef = repo.getRefDatabase().peel(unpeeledRef);
+			assertTrue(peeledRef instanceof ObjectIdRef.PeeledNonTag);
+			assertEquals(1000, peeledRef.getUpdateIndex());
+		}
+	}
+
+	@Test
+	public void sha1ToTip_ref() throws Exception {
+		InMemoryRepository repo = new InMemoryRepository(
+				new DfsRepositoryDescription());
+		try (TestRepository<InMemoryRepository> git = new TestRepository<>(
+				repo)) {
+			RevCommit commit = git.branch("master").commit()
+					.message("first commit").create();
+
+			Set<Ref> tipsWithSha1 = repo.getRefDatabase()
+					.getTipsWithSha1(commit.getId());
+			assertEquals(1, tipsWithSha1.size());
+			Ref ref = tipsWithSha1.iterator().next();
+			assertEquals(ref.getName(), "refs/heads/master");
+			assertEquals(commit.getId(), ref.getObjectId());
+		}
+	}
+
+	@Test
+	public void sha1ToTip_annotatedTag() throws Exception {
+		InMemoryRepository repo = new InMemoryRepository(
+				new DfsRepositoryDescription());
+		try (TestRepository<InMemoryRepository> git = new TestRepository<>(
+				repo)) {
+			RevCommit commit = git.commit()
+					.message("first commit").create();
+			RevTag tagObj = git.tag("v0.1", commit);
+			git.update("refs/tags/v0.1", tagObj);
+			Set<Ref> tipsWithSha1 = repo.getRefDatabase()
+					.getTipsWithSha1(commit.getId());
+			assertEquals(1, tipsWithSha1.size());
+			Ref ref = tipsWithSha1.iterator().next();
+			assertEquals(ref.getName(), "refs/tags/v0.1");
+			assertEquals(commit.getId(), ref.getPeeledObjectId());
+		}
+	}
+
+	@Test
+	public void sha1ToTip_tag() throws Exception {
+		InMemoryRepository repo = new InMemoryRepository(
+				new DfsRepositoryDescription());
+		try (TestRepository<InMemoryRepository> git = new TestRepository<>(
+				repo)) {
+			RevCommit commit = git.commit().message("first commit").create();
+			git.update("refs/tags/v0.2", commit);
+			Set<Ref> tipsWithSha1 = repo.getRefDatabase()
+					.getTipsWithSha1(commit.getId());
+			assertEquals(1, tipsWithSha1.size());
+			Ref ref = tipsWithSha1.iterator().next();
+			assertEquals(ref.getName(), "refs/tags/v0.2");
+			assertEquals(commit.getId(), ref.getObjectId());
+		}
+	}
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/BatchRefUpdateTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/BatchRefUpdateTest.java
index d0e3943..c211eab 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/BatchRefUpdateTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/BatchRefUpdateTest.java
@@ -1233,6 +1233,7 @@
 		REJECTED_MISSING_OBJECT(ReceiveCommand.Result.REJECTED_MISSING_OBJECT),
 		TRANSACTION_ABORTED(ReceiveCommand::isTransactionAborted);
 
+		@SuppressWarnings("ImmutableEnumChecker")
 		final Predicate<? super ReceiveCommand> p;
 
 		private Result(Predicate<? super ReceiveCommand> p) {
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcConcurrentTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcConcurrentTest.java
index 05f7c65..9e7d41a 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcConcurrentTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcConcurrentTest.java
@@ -239,20 +239,15 @@
 		SampleDataRepositoryTestCase.copyCGitTestPacks(repo);
 		ExecutorService executor = Executors.newSingleThreadExecutor();
 		final CountDownLatch latch = new CountDownLatch(1);
-		Future<Collection<PackFile>> result = executor
-				.submit(new Callable<Collection<PackFile>>() {
-
-					@Override
-					public Collection<PackFile> call() throws Exception {
-						long start = System.currentTimeMillis();
-						System.out.println("starting gc");
-						latch.countDown();
-						Collection<PackFile> r = gc.gc();
-						System.out.println("gc took "
-								+ (System.currentTimeMillis() - start) + " ms");
-						return r;
-					}
-				});
+		Future<Collection<PackFile>> result = executor.submit(() -> {
+			long start = System.currentTimeMillis();
+			System.out.println("starting gc");
+			latch.countDown();
+			Collection<PackFile> r = gc.gc();
+			System.out.println(
+					"gc took " + (System.currentTimeMillis() - start) + " ms");
+			return r;
+		});
 		try {
 			latch.await();
 			Thread.sleep(5);
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcPackRefsTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcPackRefsTest.java
index c43bdbd..54708da 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcPackRefsTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcPackRefsTest.java
@@ -155,41 +155,34 @@
 		final CyclicBarrier packRefsDone = new CyclicBarrier(2);
 		ExecutorService pool = Executors.newFixedThreadPool(2);
 		try {
-			Future<Result> result = pool.submit(new Callable<Result>() {
-
-				@Override
-				public Result call() throws Exception {
-					RefUpdate update = new RefDirectoryUpdate(
-							(RefDirectory) repo.getRefDatabase(),
-							repo.exactRef("refs/tags/t")) {
-						@Override
-						public boolean isForceUpdate() {
-							try {
-								refUpdateLockedRef.await();
-								packRefsDone.await();
-							} catch (InterruptedException e) {
-								Thread.currentThread().interrupt();
-							} catch (BrokenBarrierException e) {
-								Thread.currentThread().interrupt();
-							}
-							return super.isForceUpdate();
+			Future<Result> result = pool.submit(() -> {
+				RefUpdate update = new RefDirectoryUpdate(
+						(RefDirectory) repo.getRefDatabase(),
+						repo.exactRef("refs/tags/t")) {
+					@Override
+					public boolean isForceUpdate() {
+						try {
+							refUpdateLockedRef.await();
+							packRefsDone.await();
+						} catch (InterruptedException
+								| BrokenBarrierException e) {
+							Thread.currentThread().interrupt();
 						}
-					};
-					update.setForceUpdate(true);
-					update.setNewObjectId(b);
-					return update.update();
-				}
+						return super.isForceUpdate();
+					}
+				};
+				update.setForceUpdate(true);
+				update.setNewObjectId(b);
+				return update.update();
 			});
 
-			pool.submit(new Callable<Void>() {
-				@Override
-				public Void call() throws Exception {
-					refUpdateLockedRef.await();
-					gc.packRefs();
-					packRefsDone.await();
-					return null;
-				}
+			Future<Result> result2 = pool.submit(() -> {
+				refUpdateLockedRef.await();
+				gc.packRefs();
+				packRefsDone.await();
+				return null;
 			});
+			assertNull(result2.get());
 
 			assertSame(result.get(), Result.FORCED);
 
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 9d47c7e..97c5638 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
@@ -47,7 +47,6 @@
 import static org.junit.Assert.assertTrue;
 
 import java.io.File;
-import java.io.FilenameFilter;
 import java.io.IOException;
 import java.io.PrintWriter;
 import java.text.MessageFormat;
@@ -169,12 +168,8 @@
 			// 2500ms
 			Thread.sleep(2600);
 
-			File[] ret = packsFolder.listFiles(new FilenameFilter() {
-				@Override
-				public boolean accept(File dir, String name) {
-					return name.endsWith(".pack");
-				}
-			});
+			File[] ret = packsFolder.listFiles(
+					(File dir, String name) -> name.endsWith(".pack"));
 			assertTrue(ret != null && ret.length == 1);
 			FS fs = db.getFS();
 			Assume.assumeTrue(fs.lastModifiedInstant(tmpFile)
@@ -224,12 +219,8 @@
 
 	private Collection<Callable<ObjectId>> blobInsertersForTheSameFanOutDir(
 			final ObjectDirectory dir) {
-		Callable<ObjectId> callable = new Callable<ObjectId>() {
-			@Override
-			public ObjectId call() throws Exception {
-				return dir.newInserter().insert(Constants.OBJ_BLOB, new byte[0]);
-			}
-		};
+		Callable<ObjectId> callable = () -> dir.newInserter()
+				.insert(Constants.OBJ_BLOB, new byte[0]);
 		return Collections.nCopies(4, callable);
 	}
 
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackWriterTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackWriterTest.java
index ca44862..5d0a7e2 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackWriterTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackWriterTest.java
@@ -60,7 +60,6 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
-import java.util.Comparator;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
@@ -881,12 +880,8 @@
 		for (MutableEntry me : pack) {
 			entries.add(me.cloneEntry());
 		}
-		Collections.sort(entries, new Comparator<PackIndex.MutableEntry>() {
-			@Override
-			public int compare(MutableEntry o1, MutableEntry o2) {
-				return Long.signum(o1.getOffset() - o2.getOffset());
-			}
-		});
+		Collections.sort(entries, (MutableEntry o1, MutableEntry o2) -> Long
+				.signum(o1.getOffset() - o2.getOffset()));
 
 		int i = 0;
 		for (MutableEntry me : entries) {
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefDirectoryTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefDirectoryTest.java
index 2357edf..d8e1685 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefDirectoryTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefDirectoryTest.java
@@ -71,7 +71,6 @@
 import org.eclipse.jgit.errors.LockFailedException;
 import org.eclipse.jgit.events.ListenerHandle;
 import org.eclipse.jgit.events.RefsChangedEvent;
-import org.eclipse.jgit.events.RefsChangedListener;
 import org.eclipse.jgit.junit.LocalDiskRepositoryTestCase;
 import org.eclipse.jgit.junit.Repeat;
 import org.eclipse.jgit.junit.TestRepository;
@@ -573,12 +572,8 @@
 		final int[] count = new int[1];
 
 		ListenerHandle listener = Repository.getGlobalListenerList()
-				.addRefsChangedListener(new RefsChangedListener() {
-
-					@Override
-					public void onRefsChanged(RefsChangedEvent event) {
-						count[0]++;
-					}
+				.addRefsChangedListener((RefsChangedEvent event) -> {
+					count[0]++;
 				});
 
 		refs = refdir.getRefs(RefDatabase.ALL);
@@ -1320,19 +1315,15 @@
 		final AtomicReference<StackOverflowError> error = new AtomicReference<>();
 		final AtomicReference<IOException> exception = new AtomicReference<>();
 		final AtomicInteger changeCount = new AtomicInteger();
-		newRepo.getListenerList().addRefsChangedListener(
-				new RefsChangedListener() {
-
-					@Override
-					public void onRefsChanged(RefsChangedEvent event) {
-						try {
-							refDb.getRefsByPrefix("ref");
-							changeCount.incrementAndGet();
-						} catch (StackOverflowError soe) {
-							error.set(soe);
-						} catch (IOException ioe) {
-							exception.set(ioe);
-						}
+		newRepo.getListenerList()
+				.addRefsChangedListener((RefsChangedEvent event) -> {
+					try {
+						refDb.getRefsByPrefix("ref");
+						changeCount.incrementAndGet();
+					} catch (StackOverflowError soe) {
+						error.set(soe);
+					} catch (IOException ioe) {
+						exception.set(ioe);
 					}
 				});
 		refDb.getRefsByPrefix("ref");
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftree/RefTreeDatabaseTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftree/RefTreeDatabaseTest.java
index 8ef21e6..c3f5baa 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftree/RefTreeDatabaseTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftree/RefTreeDatabaseTest.java
@@ -633,35 +633,23 @@
 
 	private void symref(String name, String dst)
 			throws IOException {
-		commit(new Function() {
-			@Override
-			public boolean apply(ObjectReader reader, RefTree tree)
-					throws IOException {
-				Ref old = tree.exactRef(reader, name);
-				Command n = new Command(
-					old,
-					new SymbolicRef(
-						name,
-						new ObjectIdRef.Unpeeled(Ref.Storage.NEW, dst, null)));
-				return tree.apply(Collections.singleton(n));
-			}
+		commit((ObjectReader reader, RefTree tree) -> {
+			Ref old = tree.exactRef(reader, name);
+			Command n = new Command(old, new SymbolicRef(name,
+					new ObjectIdRef.Unpeeled(Ref.Storage.NEW, dst, null)));
+			return tree.apply(Collections.singleton(n));
 		});
 	}
 
 	private void update(String name, ObjectId id)
 			throws IOException {
-		commit(new Function() {
-			@Override
-			public boolean apply(ObjectReader reader, RefTree tree)
-					throws IOException {
-				Ref old = tree.exactRef(reader, name);
-				Command n;
-				try (RevWalk rw = new RevWalk(repo)) {
-					n = new Command(old,
-							Command.toRef(rw, id, null, name, true));
-				}
-				return tree.apply(Collections.singleton(n));
+		commit((ObjectReader reader, RefTree tree) -> {
+			Ref old = tree.exactRef(reader, name);
+			Command n;
+			try (RevWalk rw = new RevWalk(repo)) {
+				n = new Command(old, Command.toRef(rw, id, null, name, true));
 			}
+			return tree.apply(Collections.singleton(n));
 		});
 	}
 
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/transport/http/NetscapeCookieFileTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/transport/http/NetscapeCookieFileTest.java
new file mode 100644
index 0000000..e9c7f9a
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/transport/http/NetscapeCookieFileTest.java
@@ -0,0 +1,338 @@
+/*
+ * Copyright (C) 2018, Konrad Windszus <konrad_w@gmx.de>
+ * 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.http;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Writer;
+import java.net.HttpCookie;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
+import java.time.Instant;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.regex.Pattern;
+
+import org.eclipse.jgit.internal.storage.file.LockFile;
+import org.eclipse.jgit.util.http.HttpCookiesMatcher;
+import org.hamcrest.CoreMatchers;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+public class NetscapeCookieFileTest {
+
+	@Rule
+	public TemporaryFolder folder = new TemporaryFolder();
+
+	private Path tmpFile;
+
+	private URL baseUrl;
+
+	/**
+	 * This is the expiration date that is used in the test cookie files
+	 */
+	private static long JAN_01_2030_NOON = Instant
+			.parse("2030-01-01T12:00:00.000Z").toEpochMilli();
+
+	@Before
+	public void setUp() throws IOException {
+		// this will not only return a new file name but also create new empty
+		// file!
+		tmpFile = folder.newFile().toPath();
+		baseUrl = new URL("http://domain.com/my/path");
+	}
+
+	@Test
+	public void testMergeCookies() {
+		Set<HttpCookie> cookieSet1 = new LinkedHashSet<>();
+		HttpCookie cookie = new HttpCookie("key1", "valueFromSet1");
+		cookieSet1.add(cookie);
+		cookie = new HttpCookie("key2", "valueFromSet1");
+		cookieSet1.add(cookie);
+
+		Set<HttpCookie> cookieSet2 = new LinkedHashSet<>();
+		cookie = new HttpCookie("key1", "valueFromSet2");
+		cookieSet2.add(cookie);
+		cookie = new HttpCookie("key3", "valueFromSet2");
+		cookieSet2.add(cookie);
+
+		Set<HttpCookie> cookiesExpectedMergedSet = new LinkedHashSet<>();
+		cookie = new HttpCookie("key1", "valueFromSet1");
+		cookiesExpectedMergedSet.add(cookie);
+		cookie = new HttpCookie("key2", "valueFromSet1");
+		cookiesExpectedMergedSet.add(cookie);
+		cookie = new HttpCookie("key3", "valueFromSet2");
+		cookiesExpectedMergedSet.add(cookie);
+
+		Assert.assertThat(
+				NetscapeCookieFile.mergeCookies(cookieSet1, cookieSet2),
+				HttpCookiesMatcher.containsInOrder(cookiesExpectedMergedSet));
+
+		Assert.assertThat(NetscapeCookieFile.mergeCookies(cookieSet1, null),
+				HttpCookiesMatcher.containsInOrder(cookieSet1));
+	}
+
+	@Test
+	public void testWriteToNewFile() throws IOException {
+		Set<HttpCookie> cookies = new LinkedHashSet<>();
+		cookies.add(new HttpCookie("key1", "value"));
+		// first cookie is a session cookie (and should be ignored)
+
+		HttpCookie cookie = new HttpCookie("key2", "value");
+		cookie.setSecure(true);
+		cookie.setDomain("mydomain.com");
+		cookie.setPath("/");
+		cookie.setMaxAge(1000);
+		cookies.add(cookie);
+		Date creationDate = new Date();
+		try (Writer writer = Files.newBufferedWriter(tmpFile,
+				StandardCharsets.US_ASCII)) {
+			NetscapeCookieFile.write(writer, cookies, baseUrl, creationDate);
+		}
+
+		String expectedExpiration = String
+				.valueOf(creationDate.getTime() + (cookie.getMaxAge() * 1000));
+
+		Assert.assertThat(
+				Files.readAllLines(tmpFile, StandardCharsets.US_ASCII),
+				CoreMatchers
+						.equalTo(Arrays.asList("mydomain.com\tTRUE\t/\tTRUE\t"
+								+ expectedExpiration + "\tkey2\tvalue")));
+	}
+
+	@Test
+	public void testWriteToExistingFile() throws IOException {
+		try (InputStream input = this.getClass()
+				.getResourceAsStream("cookies-simple1.txt")) {
+			Files.copy(input, tmpFile, StandardCopyOption.REPLACE_EXISTING);
+		}
+
+		Set<HttpCookie> cookies = new LinkedHashSet<>();
+		HttpCookie cookie = new HttpCookie("key2", "value2");
+		cookie.setMaxAge(1000);
+		cookies.add(cookie);
+		Date creationDate = new Date();
+		try (Writer writer = Files.newBufferedWriter(tmpFile,
+				StandardCharsets.US_ASCII)) {
+			NetscapeCookieFile.write(writer, cookies, baseUrl, creationDate);
+		}
+		String expectedExpiration = String
+				.valueOf(creationDate.getTime() + (cookie.getMaxAge() * 1000));
+
+		Assert.assertThat(
+				Files.readAllLines(tmpFile, StandardCharsets.US_ASCII),
+				CoreMatchers.equalTo(
+						Arrays.asList("domain.com\tTRUE\t/my/path\tFALSE\t"
+								+ expectedExpiration + "\tkey2\tvalue2")));
+	}
+
+	@Test(expected = IOException.class)
+	public void testWriteWhileSomeoneIsHoldingTheLock()
+			throws IllegalArgumentException, IOException, InterruptedException {
+		try (InputStream input = this.getClass()
+				.getResourceAsStream("cookies-simple1.txt")) {
+			Files.copy(input, tmpFile, StandardCopyOption.REPLACE_EXISTING);
+		}
+		NetscapeCookieFile cookieFile = new NetscapeCookieFile(tmpFile);
+		// now imitate another process/thread holding the lock file
+		LockFile lockFile = new LockFile(tmpFile.toFile());
+		try {
+			Assert.assertTrue("Could not acquire lock", lockFile.lock());
+			cookieFile.write(baseUrl);
+		} finally {
+			lockFile.unlock();
+		}
+	}
+
+	@Test
+	public void testWriteAfterAnotherJgitProcessModifiedTheFile()
+			throws IOException, InterruptedException {
+		try (InputStream input = this.getClass()
+				.getResourceAsStream("cookies-simple1.txt")) {
+			Files.copy(input, tmpFile, StandardCopyOption.REPLACE_EXISTING);
+		}
+		NetscapeCookieFile cookieFile = new NetscapeCookieFile(tmpFile);
+		cookieFile.getCookies(true);
+		// now modify file externally
+		try (InputStream input = this.getClass()
+				.getResourceAsStream("cookies-simple2.txt")) {
+			Files.copy(input, tmpFile, StandardCopyOption.REPLACE_EXISTING);
+		}
+		// now try to write
+		cookieFile.write(baseUrl);
+
+		// validate that the external changes are there as well
+		// due to rounding errors (conversion from ms to sec to ms)
+		// the expiration date might not be exact
+		List<String> lines = Files.readAllLines(tmpFile,
+				StandardCharsets.US_ASCII);
+
+		Assert.assertEquals("Expected 3 lines", 3, lines.size());
+		assertStringMatchesPatternWithInexactNumber(lines.get(0),
+				"some-domain1\tTRUE\t/some/path1\tFALSE\t(\\d*)\tkey1\tvalueFromSimple2",
+				JAN_01_2030_NOON, 1000);
+		assertStringMatchesPatternWithInexactNumber(lines.get(1),
+				"some-domain1\tTRUE\t/some/path1\tFALSE\t(\\d*)\tkey3\tvalueFromSimple2",
+				JAN_01_2030_NOON, 1000);
+		assertStringMatchesPatternWithInexactNumber(lines.get(2),
+				"some-domain1\tTRUE\t/some/path1\tFALSE\t(\\d*)\tkey2\tvalueFromSimple1",
+				JAN_01_2030_NOON, 1000);
+	}
+
+	@SuppressWarnings("boxing")
+	private static final void assertStringMatchesPatternWithInexactNumber(
+			String string, String pattern, long expectedNumericValue,
+			long delta) {
+		java.util.regex.Matcher matcher = Pattern.compile(pattern)
+				.matcher(string);
+		Assert.assertTrue("Given string '" + string + "' does not match '"
+				+ pattern + "'", matcher.matches());
+		// extract numeric value
+		Long actualNumericValue = Long.decode(matcher.group(1));
+
+		Assert.assertTrue(
+				"Value is supposed to be close to " + expectedNumericValue
+						+ " but is " + actualNumericValue + ".",
+				Math.abs(expectedNumericValue - actualNumericValue) <= delta);
+	}
+
+	@Test
+	public void testWriteAndReadCycle() throws IOException {
+		Set<HttpCookie> cookies = new LinkedHashSet<>();
+
+		HttpCookie cookie = new HttpCookie("key1", "value1");
+		cookie.setPath("/some/path1");
+		cookie.setDomain("some-domain1");
+		cookie.setMaxAge(1000);
+		cookies.add(cookie);
+		cookie = new HttpCookie("key2", "value2");
+		cookie.setSecure(true);
+		cookie.setPath("/some/path2");
+		cookie.setDomain("some-domain2");
+		cookie.setMaxAge(1000);
+		cookie.setHttpOnly(true);
+		cookies.add(cookie);
+
+		Date creationDate = new Date();
+
+		try (Writer writer = Files.newBufferedWriter(tmpFile,
+				StandardCharsets.US_ASCII)) {
+			NetscapeCookieFile.write(writer, cookies, baseUrl, creationDate);
+		}
+		Set<HttpCookie> actualCookies = new NetscapeCookieFile(tmpFile,
+				creationDate).getCookies(true);
+		Assert.assertThat(actualCookies,
+				HttpCookiesMatcher.containsInOrder(cookies));
+	}
+
+	@Test
+	public void testReadAndWriteCycle() throws IOException {
+		try (InputStream input = this.getClass()
+				.getResourceAsStream("cookies-simple1.txt")) {
+			Files.copy(input, tmpFile, StandardCopyOption.REPLACE_EXISTING);
+		}
+		// round up to the next second (to prevent rounding errors)
+		Date creationDate = new Date(
+				(System.currentTimeMillis() / 1000) * 1000);
+		Set<HttpCookie> cookies = new NetscapeCookieFile(tmpFile, creationDate)
+				.getCookies(true);
+		Path tmpFile2 = folder.newFile().toPath();
+		try (Writer writer = Files.newBufferedWriter(tmpFile2,
+				StandardCharsets.US_ASCII)) {
+			NetscapeCookieFile.write(writer, cookies, baseUrl, creationDate);
+		}
+		// compare original file with newly written one, they should not differ
+		Assert.assertEquals(Files.readAllLines(tmpFile),
+				Files.readAllLines(tmpFile2));
+	}
+
+	@Test
+	public void testReadWithEmptyAndCommentLines() throws IOException {
+		try (InputStream input = this.getClass().getResourceAsStream(
+				"cookies-with-empty-and-comment-lines.txt")) {
+			Files.copy(input, tmpFile, StandardCopyOption.REPLACE_EXISTING);
+		}
+
+		Date creationDate = new Date();
+		Set<HttpCookie> cookies = new LinkedHashSet<>();
+
+		HttpCookie cookie = new HttpCookie("key2", "value2");
+		cookie.setDomain("some-domain2");
+		cookie.setPath("/some/path2");
+		cookie.setMaxAge((JAN_01_2030_NOON - creationDate.getTime()) / 1000);
+		cookie.setSecure(true);
+		cookie.setHttpOnly(true);
+		cookies.add(cookie);
+
+		cookie = new HttpCookie("key3", "value3");
+		cookie.setDomain("some-domain3");
+		cookie.setPath("/some/path3");
+		cookie.setMaxAge((JAN_01_2030_NOON - creationDate.getTime()) / 1000);
+		cookies.add(cookie);
+
+		Set<HttpCookie> actualCookies = new NetscapeCookieFile(tmpFile, creationDate)
+				.getCookies(true);
+		Assert.assertThat(actualCookies,
+				HttpCookiesMatcher.containsInOrder(cookies));
+	}
+
+	@Test
+	public void testReadInvalidFile() throws IOException {
+		try (InputStream input = this.getClass()
+				.getResourceAsStream("cookies-invalid.txt")) {
+			Files.copy(input, tmpFile, StandardCopyOption.REPLACE_EXISTING);
+		}
+
+		new NetscapeCookieFile(tmpFile)
+				.getCookies(true);
+	}
+}
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 22dc471..e839545 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
@@ -1462,6 +1462,48 @@
 		assertEquals("xt", parseEscapedSubsection("\"x\\t\""));
 	}
 
+	@Test
+	public void testInvalidGroupHeader() throws ConfigInvalidException {
+		expectedEx.expect(ConfigInvalidException.class);
+		expectedEx.expectMessage(JGitText.get().badGroupHeader);
+		parse("[foo \"bar\" ]\nfoo=bar\n");
+	}
+
+	@Test
+	public void testCrLf() throws ConfigInvalidException {
+		assertEquals("true", parseEscapedValue("true\r\n"));
+	}
+
+	@Test
+	public void testLfContinuation() throws ConfigInvalidException {
+		assertEquals("true", parseEscapedValue("tr\\\nue"));
+	}
+
+	@Test
+	public void testCrCharContinuation() throws ConfigInvalidException {
+		expectedEx.expect(ConfigInvalidException.class);
+		expectedEx.expectMessage("Bad escape: \\u000d");
+		parseEscapedValue("tr\\\rue");
+	}
+
+	@Test
+	public void testCrEOFContinuation() throws ConfigInvalidException {
+		expectedEx.expect(ConfigInvalidException.class);
+		expectedEx.expectMessage("Bad escape: \\u000d");
+		parseEscapedValue("tr\\\r");
+	}
+
+	@Test
+	public void testCrLfContinuation() throws ConfigInvalidException {
+		assertEquals("true", parseEscapedValue("tr\\\r\nue"));
+	}
+
+	@Test
+	public void testWhitespaceContinuation() throws ConfigInvalidException {
+		assertEquals("tr   ue", parseEscapedValue("tr \\\n  ue"));
+		assertEquals("tr   ue", parseEscapedValue("tr \\\r\n  ue"));
+	}
+
 	private static void assertValueRoundTrip(String value)
 			throws ConfigInvalidException {
 		assertValueRoundTrip(value, value);
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ObjectCheckerTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ObjectCheckerTest.java
index 531c918..e7a5b28 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ObjectCheckerTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ObjectCheckerTest.java
@@ -1678,7 +1678,6 @@
 		rejectName('>');
 		rejectName(':');
 		rejectName('"');
-		rejectName('/');
 		rejectName('\\');
 		rejectName('|');
 		rejectName('?');
@@ -1693,7 +1692,8 @@
 			checkOneName("te" + c + "st");
 			fail("incorrectly accepted with " + c);
 		} catch (CorruptObjectException e) {
-			assertEquals("name contains '" + c + "'", e.getMessage());
+
+			assertEquals("char '" + c + "' not allowed in Windows filename", e.getMessage());
 		}
 	}
 
@@ -1703,7 +1703,19 @@
 			checkOneName("te" + ((char) c) + "st");
 			fail("incorrectly accepted with 0x" + h);
 		} catch (CorruptObjectException e) {
-			assertEquals("name contains byte 0x" + h, e.getMessage());
+			assertEquals("byte 0x" + h + " not allowed in Windows filename", e.getMessage());
+		}
+	}
+
+
+	@Test
+	public void testRejectInvalidCharacter() {
+		try {
+			checkOneName("te/st");
+			fail("incorrectly accepted with /");
+		} catch (CorruptObjectException e) {
+
+			assertEquals("name contains '/'", e.getMessage());
 		}
 	}
 
@@ -1763,16 +1775,13 @@
 	}
 
 	private static ObjectIdSet set(ObjectId... ids) {
-		return new ObjectIdSet() {
-			@Override
-			public boolean contains(AnyObjectId objectId) {
-				for (ObjectId id : ids) {
-					if (id.equals(objectId)) {
-						return true;
-					}
+		return (AnyObjectId objectId) -> {
+			for (ObjectId id : ids) {
+				if (id.equals(objectId)) {
+					return true;
 				}
-				return false;
 			}
+			return false;
 		};
 	}
 
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RebaseTodoFileTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RebaseTodoFileTest.java
new file mode 100644
index 0000000..5cfc75c
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RebaseTodoFileTest.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2019, 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.lib;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Arrays;
+import java.util.List;
+
+import org.eclipse.jgit.junit.RepositoryTestCase;
+import org.junit.Test;
+
+public class RebaseTodoFileTest extends RepositoryTestCase {
+
+	private static final String TEST_TODO = "test.todo";
+
+	private void createTodoList(String... lines) throws IOException {
+		Path p = Paths.get(db.getDirectory().getAbsolutePath(), TEST_TODO);
+		Files.write(p, Arrays.asList(lines));
+	}
+
+	@Test
+	public void testReadTodoFile() throws Exception {
+		String[] expected = { "reword " + ObjectId.zeroId().name() + " Foo",
+				"# A comment in the the todo list",
+				"pick " + ObjectId.zeroId().name() + " Foo fie",
+				"squash " + ObjectId.zeroId().name() + " F",
+				"fixup " + ObjectId.zeroId().name(),
+				"edit " + ObjectId.zeroId().name() + " f",
+				"edit " + ObjectId.zeroId().name() + ' ' };
+		createTodoList(expected);
+		RebaseTodoFile todo = new RebaseTodoFile(db);
+		List<RebaseTodoLine> lines = todo.readRebaseTodo(TEST_TODO, true);
+		assertEquals("Expected 7 lines", 7, lines.size());
+		int i = 0;
+		for (RebaseTodoLine line : lines) {
+			switch (i) {
+			case 0:
+				assertEquals("Expected REWORD", RebaseTodoLine.Action.REWORD,
+						line.getAction());
+				assertEquals("Unexpected ID", ObjectId.zeroId().abbreviate(40),
+						line.getCommit());
+				assertEquals("Unexpected Message", "Foo",
+						line.getShortMessage());
+				break;
+			case 1:
+				assertEquals("Expected COMMENT", RebaseTodoLine.Action.COMMENT,
+						line.getAction());
+				assertEquals("Unexpected Message",
+						"# A comment in the the todo list",
+						line.getComment());
+				break;
+			case 2:
+				assertEquals("Expected PICK", RebaseTodoLine.Action.PICK,
+						line.getAction());
+				assertEquals("Unexpected ID", ObjectId.zeroId().abbreviate(40),
+						line.getCommit());
+				assertEquals("Unexpected Message", "Foo fie",
+						line.getShortMessage());
+				break;
+			case 3:
+				assertEquals("Expected SQUASH", RebaseTodoLine.Action.SQUASH,
+						line.getAction());
+				assertEquals("Unexpected ID", ObjectId.zeroId().abbreviate(40),
+						line.getCommit());
+				assertEquals("Unexpected Message", "F", line.getShortMessage());
+				break;
+			case 4:
+				assertEquals("Expected FIXUP", RebaseTodoLine.Action.FIXUP,
+						line.getAction());
+				assertEquals("Unexpected ID", ObjectId.zeroId().abbreviate(40),
+						line.getCommit());
+				assertEquals("Unexpected Message", "", line.getShortMessage());
+				break;
+			case 5:
+				assertEquals("Expected EDIT", RebaseTodoLine.Action.EDIT,
+						line.getAction());
+				assertEquals("Unexpected ID", ObjectId.zeroId().abbreviate(40),
+						line.getCommit());
+				assertEquals("Unexpected Message", "f", line.getShortMessage());
+				break;
+			case 6:
+				assertEquals("Expected EDIT", RebaseTodoLine.Action.EDIT,
+						line.getAction());
+				assertEquals("Unexpected ID", ObjectId.zeroId().abbreviate(40),
+						line.getCommit());
+				assertEquals("Unexpected Message", "", line.getShortMessage());
+				break;
+			default:
+				fail("Too many lines");
+				return;
+			}
+			i++;
+		}
+	}
+}
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 7d2c4a2..b53d5b9 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
@@ -58,8 +58,10 @@
 import java.io.File;
 import java.io.FileOutputStream;
 import java.io.IOException;
+import java.util.Collection;
 import java.util.List;
 import java.util.Optional;
+import java.util.Set;
 import java.util.TreeSet;
 
 import org.eclipse.jgit.lib.Ref.Storage;
@@ -313,7 +315,7 @@
 		assertEquals(dst.isPeeled(), ref.isPeeled());
 	}
 
-	private static void checkContainsRef(List<Ref> haystack, Ref needle) {
+	private static void checkContainsRef(Collection<Ref> haystack, Ref needle) {
 		for (Ref ref : haystack) {
 			if (ref.getName().equals(needle.getName()) &&
 					ref.getObjectId().equals(needle.getObjectId())) {
@@ -347,4 +349,17 @@
 		checkContainsRef(refs, db.exactRef("refs/heads/prefix/a"));
 		checkContainsRef(refs, db.exactRef("refs/tags/A"));
 	}
+
+	@Test
+	public void testResolveTipSha1() throws IOException {
+		ObjectId masterId = db.resolve("refs/heads/master");
+		Set<Ref> resolved = db.getRefDatabase().getTipsWithSha1(masterId);
+
+		assertEquals(2, resolved.size());
+		checkContainsRef(resolved, db.exactRef("refs/heads/master"));
+		checkContainsRef(resolved, db.exactRef("HEAD"));
+
+		assertEquals(db.getRefDatabase()
+				.getTipsWithSha1(ObjectId.zeroId()).size(), 0);
+	}
 }
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ReflogConfigTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ReflogConfigTest.java
index df1a52d..f2f277c6e 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ReflogConfigTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ReflogConfigTest.java
@@ -45,7 +45,6 @@
 
 package org.eclipse.jgit.lib;
 
-import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
@@ -63,7 +62,8 @@
 
 		// check that there are no entries in the reflog and turn off writing
 		// reflogs
-		assertEquals(0, db.getReflogReader(Constants.HEAD).getReverseEntries().size());
+		assertTrue(db.getReflogReader(Constants.HEAD).getReverseEntries()
+				.isEmpty());
 		final FileBasedConfig cfg = db.getConfig();
 		cfg.setBoolean("core", null, "logallrefupdates", false);
 		cfg.save();
@@ -72,9 +72,8 @@
 		// written
 		commit("A Commit\n", commitTime, tz);
 		commitTime += 60 * 1000;
-		assertTrue(
-				"Reflog for HEAD still contain no entry",
-				db.getReflogReader(Constants.HEAD).getReverseEntries().size() == 0);
+		assertTrue("Reflog for HEAD still contain no entry", db
+				.getReflogReader(Constants.HEAD).getReverseEntries().isEmpty());
 
 		// set the logAllRefUpdates parameter to true and check it
 		cfg.setBoolean("core", null, "logallrefupdates", true);
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ThreadSafeProgressMonitorTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ThreadSafeProgressMonitorTest.java
index 545a188..6829822 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ThreadSafeProgressMonitorTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ThreadSafeProgressMonitorTest.java
@@ -61,29 +61,26 @@
 		final MockProgressMonitor mock = new MockProgressMonitor();
 		final ThreadSafeProgressMonitor pm = new ThreadSafeProgressMonitor(mock);
 
-		runOnThread(new Runnable() {
-			@Override
-			public void run() {
-				try {
-					pm.start(1);
-					fail("start did not fail on background thread");
-				} catch (IllegalStateException notMainThread) {
-					// Expected result
-				}
+		runOnThread(() -> {
+			try {
+				pm.start(1);
+				fail("start did not fail on background thread");
+			} catch (IllegalStateException notMainThread) {
+				// Expected result
+			}
 
-				try {
-					pm.beginTask("title", 1);
-					fail("beginTask did not fail on background thread");
-				} catch (IllegalStateException notMainThread) {
-					// Expected result
-				}
+			try {
+				pm.beginTask("title", 1);
+				fail("beginTask did not fail on background thread");
+			} catch (IllegalStateException notMainThread) {
+				// Expected result
+			}
 
-				try {
-					pm.endTask();
-					fail("endTask did not fail on background thread");
-				} catch (IllegalStateException notMainThread) {
-					// Expected result
-				}
+			try {
+				pm.endTask();
+				fail("endTask did not fail on background thread");
+			} catch (IllegalStateException notMainThread) {
+				// Expected result
 			}
 		});
 
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 2ae9c11..62495fb 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
@@ -842,9 +842,9 @@
 	 * Throws an exception if reading beyond limit.
 	 */
 	static class BigReadForbiddenStream extends ObjectStream.Filter {
-		int limit;
+		long limit;
 
-		BigReadForbiddenStream(ObjectStream orig, int limit) {
+		BigReadForbiddenStream(ObjectStream orig, long limit) {
 			super(orig.getType(), orig.getSize(), orig);
 			this.limit = limit;
 		}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/BitmapCalculatorTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/BitmapCalculatorTest.java
new file mode 100644
index 0000000..c5d4d42
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/BitmapCalculatorTest.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2019, 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.revwalk;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import org.eclipse.jgit.internal.storage.file.FileRepository;
+import org.eclipse.jgit.internal.storage.file.GC;
+import org.eclipse.jgit.junit.LocalDiskRepositoryTestCase;
+import org.eclipse.jgit.junit.TestRepository;
+import org.eclipse.jgit.lib.BitmapIndex.BitmapBuilder;
+import org.eclipse.jgit.lib.NullProgressMonitor;
+import org.eclipse.jgit.lib.ProgressMonitor;
+import org.junit.Before;
+import org.junit.Test;
+
+public class BitmapCalculatorTest extends LocalDiskRepositoryTestCase {
+	TestRepository<FileRepository> repo;
+
+	/** {@inheritDoc} */
+	@Override
+	@Before
+	public void setUp() throws Exception {
+		super.setUp();
+		FileRepository db = createWorkRepository();
+		repo = new TestRepository<>(db);
+	}
+
+	@Test
+	public void addOnlyCommits() throws Exception {
+		RevBlob abBlob = repo.blob("a_b_content");
+		RevCommit root = repo.commit().add("a/b", abBlob).create();
+		repo.update("refs/heads/master", root);
+
+		// GC creates bitmap index with ALL objects
+		GC gc = new GC(repo.getRepository());
+		gc.setAuto(false);
+		gc.gc();
+
+		// These objects are not in the bitmap index.
+		RevBlob acBlob = repo.blob("a_c_content");
+		RevCommit head = repo.commit().parent(root).add("a/c", acBlob).create();
+		repo.update("refs/heads/master", head);
+
+		BitmapCalculator bitmapWalker = new BitmapCalculator(repo.getRevWalk());
+		BitmapBuilder bitmap = bitmapWalker
+				.getBitmap(head, NullProgressMonitor.INSTANCE);
+
+		assertTrue(bitmap.contains(root.getId()));
+		assertTrue(bitmap.contains(root.getTree().getId()));
+		assertTrue(bitmap.contains(abBlob.getId()));
+
+		// BitmapCalculator added only the commit, no other objects.
+		assertTrue(bitmap.contains(head.getId()));
+		assertFalse(bitmap.contains(head.getTree().getId()));
+		assertFalse(bitmap.contains(acBlob.getId()));
+	}
+
+	@Test
+	public void walkUntilBitmap() throws Exception {
+		RevCommit root = repo.commit().create();
+		repo.update("refs/heads/master", root);
+
+		// GC creates bitmap index with ALL objects
+		GC gc = new GC(repo.getRepository());
+		gc.setAuto(false);
+		gc.gc();
+
+		// These objects are not in the bitmap index.
+		RevCommit commit1 = repo.commit(root);
+		RevCommit commit2 = repo.commit(commit1);
+		repo.update("refs/heads/master", commit2);
+
+		CounterProgressMonitor monitor = new CounterProgressMonitor();
+		BitmapCalculator bitmapWalker = new BitmapCalculator(repo.getRevWalk());
+		BitmapBuilder bitmap = bitmapWalker.getBitmap(commit2, monitor);
+
+		assertTrue(bitmap.contains(root));
+		assertTrue(bitmap.contains(commit1));
+		assertTrue(bitmap.contains(commit2));
+		assertEquals(2, monitor.getUpdates());
+	}
+
+	@Test
+	public void noNeedToWalk() throws Exception {
+		RevCommit root = repo.commit().create();
+		RevCommit commit1 = repo.commit(root);
+		RevCommit commit2 = repo.commit(commit1);
+		repo.update("refs/heads/master", commit2);
+
+		// GC creates bitmap index with ALL objects
+		GC gc = new GC(repo.getRepository());
+		gc.setAuto(false);
+		gc.gc();
+
+		CounterProgressMonitor monitor = new CounterProgressMonitor();
+		BitmapCalculator bitmapWalker = new BitmapCalculator(repo.getRevWalk());
+		BitmapBuilder bitmap = bitmapWalker.getBitmap(commit2, monitor);
+
+		assertTrue(bitmap.contains(root));
+		assertTrue(bitmap.contains(commit1));
+		assertTrue(bitmap.contains(commit2));
+		assertEquals(0, monitor.getUpdates());
+	}
+
+	private static class CounterProgressMonitor implements ProgressMonitor {
+
+		private int counter;
+
+		@Override
+		public void start(int totalTasks) {
+			// Nothing to do in tests
+		}
+
+		@Override
+		public void beginTask(String title, int totalWork) {
+			// Nothing to to in tests
+		}
+
+		@Override
+		public void update(int completed) {
+			counter += 1;
+		}
+
+		@Override
+		public void endTask() {
+			// Nothing to do in tests
+		}
+
+		@Override
+		public boolean isCancelled() {
+			return false;
+		}
+
+		int getUpdates() {
+			return counter;
+		}
+	}
+}
\ No newline at end of file
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/BitmappedReachabilityCheckerTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/BitmappedReachabilityCheckerTest.java
new file mode 100644
index 0000000..5d3adf5
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/BitmappedReachabilityCheckerTest.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2019, 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.revwalk;
+
+import static org.junit.Assert.assertNotNull;
+
+import org.eclipse.jgit.internal.storage.file.FileRepository;
+import org.eclipse.jgit.internal.storage.file.GC;
+import org.eclipse.jgit.junit.TestRepository;
+
+public class BitmappedReachabilityCheckerTest
+		extends ReachabilityCheckerTestCase {
+
+	@Override
+	protected ReachabilityChecker getChecker(
+			TestRepository<FileRepository> repository) throws Exception {
+		// GC generates the bitmaps
+		GC gc = new GC(repo.getRepository());
+		gc.setAuto(false);
+		gc.gc();
+
+		// This is null when the test didn't create any branch
+		assertNotNull("Probably the test didn't define any ref",
+				repo.getRevWalk().getObjectReader().getBitmapIndex());
+
+		return new BitmappedReachabilityChecker(repository.getRevWalk());
+	}
+
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/PedestrianReachabilityCheckerTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/PedestrianReachabilityCheckerTest.java
new file mode 100644
index 0000000..8d3e78c
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/PedestrianReachabilityCheckerTest.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2019, 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.revwalk;
+
+import org.eclipse.jgit.internal.storage.file.FileRepository;
+import org.eclipse.jgit.junit.TestRepository;
+
+public class PedestrianReachabilityCheckerTest
+		extends ReachabilityCheckerTestCase {
+
+	@Override
+	protected ReachabilityChecker getChecker(
+			TestRepository<FileRepository> repository) {
+		return new PedestrianReachabilityChecker(true, repository.getRevWalk());
+	}
+
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/ReachabilityCheckerTestCase.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/ReachabilityCheckerTestCase.java
new file mode 100644
index 0000000..dd73e35
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/ReachabilityCheckerTestCase.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2019, 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.revwalk;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Arrays;
+import java.util.Optional;
+
+import org.eclipse.jgit.internal.storage.file.FileRepository;
+import org.eclipse.jgit.junit.LocalDiskRepositoryTestCase;
+import org.eclipse.jgit.junit.TestRepository;
+import org.junit.Before;
+import org.junit.Test;
+
+public abstract class ReachabilityCheckerTestCase
+		extends LocalDiskRepositoryTestCase {
+
+	protected abstract ReachabilityChecker getChecker(
+			TestRepository<FileRepository> repository) throws Exception;
+
+	TestRepository<FileRepository> repo;
+
+	/** {@inheritDoc} */
+	@Override
+	@Before
+	public void setUp() throws Exception {
+		super.setUp();
+		FileRepository db = createWorkRepository();
+		repo = new TestRepository<>(db);
+	}
+
+	@Test
+	public void reachable() throws Exception {
+		RevCommit a = repo.commit().create();
+		RevCommit b1 = repo.commit(a);
+		RevCommit b2 = repo.commit(b1);
+		RevCommit c1 = repo.commit(a);
+		RevCommit c2 = repo.commit(c1);
+		repo.update("refs/heads/checker", b2);
+
+		ReachabilityChecker checker = getChecker(repo);
+
+		assertReachable("reachable from one tip",
+				checker.areAllReachable(Arrays.asList(a), Arrays.asList(c2)));
+		assertReachable("reachable from another tip",
+				checker.areAllReachable(Arrays.asList(a), Arrays.asList(b2)));
+		assertReachable("reachable from itself",
+				checker.areAllReachable(Arrays.asList(a), Arrays.asList(b2)));
+	}
+
+	@Test
+	public void reachable_merge() throws Exception {
+		RevCommit a = repo.commit().create();
+		RevCommit b1 = repo.commit(a);
+		RevCommit b2 = repo.commit(b1);
+		RevCommit c1 = repo.commit(a);
+		RevCommit c2 = repo.commit(c1);
+		RevCommit merge = repo.commit(c2, b2);
+		repo.update("refs/heads/checker", merge);
+
+		ReachabilityChecker checker = getChecker(repo);
+
+		assertReachable("reachable through one branch",
+				checker.areAllReachable(Arrays.asList(b1),
+						Arrays.asList(merge)));
+		assertReachable("reachable through another branch",
+				checker.areAllReachable(Arrays.asList(c1),
+						Arrays.asList(merge)));
+		assertReachable("reachable, before the branching",
+				checker.areAllReachable(Arrays.asList(a),
+						Arrays.asList(merge)));
+	}
+
+	@Test
+	public void unreachable_isLaterCommit() throws Exception {
+		RevCommit a = repo.commit().create();
+		RevCommit b1 = repo.commit(a);
+		RevCommit b2 = repo.commit(b1);
+		repo.update("refs/heads/checker", b2);
+
+		ReachabilityChecker checker = getChecker(repo);
+
+		assertUnreachable("unreachable from the future",
+				checker.areAllReachable(Arrays.asList(b2), Arrays.asList(b1)));
+	}
+
+	@Test
+	public void unreachable_differentBranch() throws Exception {
+		RevCommit a = repo.commit().create();
+		RevCommit b1 = repo.commit(a);
+		RevCommit b2 = repo.commit(b1);
+		RevCommit c1 = repo.commit(a);
+		repo.update("refs/heads/checker", b2);
+
+		ReachabilityChecker checker = getChecker(repo);
+
+		assertUnreachable("unreachable from different branch",
+				checker.areAllReachable(Arrays.asList(c1), Arrays.asList(b2)));
+	}
+
+	@Test
+	public void reachable_longChain() throws Exception {
+		RevCommit root = repo.commit().create();
+		RevCommit head = root;
+		for (int i = 0; i < 10000; i++) {
+			head = repo.commit(head);
+		}
+		repo.update("refs/heads/master", head);
+
+		ReachabilityChecker checker = getChecker(repo);
+
+		assertReachable("reachable with long chain in the middle", checker
+				.areAllReachable(Arrays.asList(root), Arrays.asList(head)));
+	}
+
+	private static void assertReachable(String msg,
+			Optional<RevCommit> result) {
+		assertFalse(msg, result.isPresent());
+	}
+
+	private static void assertUnreachable(String msg,
+			Optional<RevCommit> result) {
+		assertTrue(msg, result.isPresent());
+	}
+
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevObjectTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevObjectTest.java
index 4969305..e397220 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevObjectTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevObjectTest.java
@@ -89,8 +89,8 @@
 		assertEquals(a1.hashCode(), a2.hashCode());
 		assertEquals(b1.hashCode(), b2.hashCode());
 
-		assertTrue(AnyObjectId.equals(a1, a2));
-		assertTrue(AnyObjectId.equals(b1, b2));
+		assertTrue(AnyObjectId.isEqual(a1, a2));
+		assertTrue(AnyObjectId.isEqual(b1, b2));
 	}
 
 	@Test
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PacketLineInTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PacketLineInTest.java
index 8b1d860..f512d28 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PacketLineInTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PacketLineInTest.java
@@ -44,7 +44,8 @@
 package org.eclipse.jgit.transport;
 
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.assertSame;
 import static org.junit.Assert.fail;
 
@@ -142,21 +143,21 @@
 		init("0004");
 		final String act = in.readString();
 		assertEquals("", act);
-		assertNotSame(PacketLineIn.END, act);
+		assertFalse(PacketLineIn.isEnd(act));
 		assertEOF();
 	}
 
 	@Test
 	public void testReadString_End() throws IOException {
 		init("0000");
-		assertSame(PacketLineIn.END, in.readString());
+		assertTrue(PacketLineIn.isEnd(in.readString()));
 		assertEOF();
 	}
 
 	@Test
 	public void testReadString_Delim() throws IOException {
 		init("0001");
-		assertSame(PacketLineIn.DELIM, in.readString());
+		assertTrue(PacketLineIn.isDelimiter(in.readString()));
 		assertEOF();
 	}
 
@@ -183,14 +184,14 @@
 		init("0004");
 		final String act = in.readStringRaw();
 		assertEquals("", act);
-		assertNotSame(PacketLineIn.END, act);
+		assertFalse(PacketLineIn.isEnd(act));
 		assertEOF();
 	}
 
 	@Test
 	public void testReadStringRaw_End() throws IOException {
 		init("0000");
-		assertSame(PacketLineIn.END, in.readStringRaw());
+		assertTrue(PacketLineIn.isEnd(in.readString()));
 		assertEOF();
 	}
 
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/ProtocolV0ParserTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/ProtocolV0ParserTest.java
index 2c98c84..6e8327c 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/ProtocolV0ParserTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/ProtocolV0ParserTest.java
@@ -64,9 +64,9 @@
 		ByteArrayOutputStream send = new ByteArrayOutputStream();
 		PacketLineOut pckOut = new PacketLineOut(send);
 		for (String line : inputLines) {
-			if (line == PacketLineIn.END) {
+			if (PacketLineIn.isEnd(line)) {
 				pckOut.end();
-			} else if (line == PacketLineIn.DELIM) {
+			} else if (PacketLineIn.isDelimiter(line)) {
 				pckOut.writeDelim();
 			} else {
 				pckOut.writeString(line);
@@ -90,7 +90,7 @@
 						"4624442d68ee402a94364191085b77137618633e", "thin-pack",
 						"no-progress", "include-tag", "ofs-delta", "\n"),
 				"want f900c8326a43303685c46b279b9f70411bff1a4b\n",
-				PacketLineIn.END);
+				PacketLineIn.end());
 		ProtocolV0Parser parser = new ProtocolV0Parser(defaultConfig());
 		FetchV0Request request = parser.recvWants(pckIn);
 		assertTrue(request.getClientCapabilities()
@@ -114,7 +114,7 @@
 						"4624442d68ee402a94364191085b77137618633e", "thin-pack",
 						"agent=JGit.test/0.0.1", "\n"),
 				"want f900c8326a43303685c46b279b9f70411bff1a4b\n",
-				PacketLineIn.END);
+				PacketLineIn.end());
 		ProtocolV0Parser parser = new ProtocolV0Parser(defaultConfig());
 		FetchV0Request request = parser.recvWants(pckIn);
 		assertTrue(request.getClientCapabilities()
@@ -136,7 +136,7 @@
 		PacketLineIn pckIn = formatAsPacketLine(
 				"want 4624442d68ee402a94364191085b77137618633e\n",
 				"want f900c8326a43303685c46b279b9f70411bff1a4b\n",
-				PacketLineIn.END);
+				PacketLineIn.end());
 		ProtocolV0Parser parser = new ProtocolV0Parser(defaultConfig());
 		FetchV0Request request = parser.recvWants(pckIn);
 		assertTrue(request.getClientCapabilities().isEmpty());
@@ -151,7 +151,7 @@
 		PacketLineIn pckIn = formatAsPacketLine(
 				"want 4624442d68ee402a94364191085b77137618633e\n",
 				"want f900c8326a43303685c46b279b9f70411bff1a4b\n", "deepen 3\n",
-				PacketLineIn.END);
+				PacketLineIn.end());
 		ProtocolV0Parser parser = new ProtocolV0Parser(defaultConfig());
 		FetchV0Request request = parser.recvWants(pckIn);
 		assertTrue(request.getClientCapabilities().isEmpty());
@@ -168,7 +168,7 @@
 				"want 4624442d68ee402a94364191085b77137618633e\n",
 				"want f900c8326a43303685c46b279b9f70411bff1a4b\n",
 				"shallow 4b643d0ef739a1b494e7d6926d8d8ed80d35edf4\n",
-				PacketLineIn.END);
+				PacketLineIn.end());
 		ProtocolV0Parser parser = new ProtocolV0Parser(defaultConfig());
 		FetchV0Request request = parser.recvWants(pckIn);
 		assertTrue(request.getClientCapabilities().isEmpty());
@@ -186,14 +186,15 @@
 				"want 4624442d68ee402a94364191085b77137618633e\n",
 				"want f900c8326a43303685c46b279b9f70411bff1a4b\n",
 				"filter blob:limit=13000\n",
-				PacketLineIn.END);
+				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());
+		assertEquals(13000, request.getFilterSpec().getBlobLimit());
+		assertEquals(-1, request.getFilterSpec().getTreeDepthLimit());
 	}
 
 }
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/ProtocolV2ParserTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/ProtocolV2ParserTest.java
index dafa81e..0d70cd6 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
@@ -123,9 +123,9 @@
 		ByteArrayOutputStream send = new ByteArrayOutputStream();
 		PacketLineOut pckOut = new PacketLineOut(send);
 		for (String line : inputLines) {
-			if (line == PacketLineIn.END) {
+			if (PacketLineIn.isEnd(line)) {
 				pckOut.end();
-			} else if (line == PacketLineIn.DELIM) {
+			} else if (PacketLineIn.isDelimiter(line)) {
 				pckOut.writeDelim();
 			} else {
 				pckOut.writeString(line);
@@ -136,19 +136,19 @@
 	}
 
 	/*
-	 * Succesful fetch with the basic core commands of the protocol.
+	 * Successful fetch with the basic core commands of the protocol.
 	 */
 	@Test
 	public void testFetchBasicArguments()
 			throws PackProtocolException, IOException {
 		PacketLineIn pckIn = formatAsPacketLine(
-				PacketLineIn.DELIM,
+				PacketLineIn.delimiter(),
 				"thin-pack", "no-progress", "include-tag", "ofs-delta",
 				"want 4624442d68ee402a94364191085b77137618633e",
 				"want f900c8326a43303685c46b279b9f70411bff1a4b",
 				"have 554f6e41067b9e3e565b6988a8294fac1cb78f4b",
 				"have abc760ab9ad72f08209943251b36cb886a578f87", "done",
-				PacketLineIn.END);
+				PacketLineIn.end());
 		ProtocolV2Parser parser = new ProtocolV2Parser(
 				ConfigBuilder.getDefault());
 		FetchV2Request request = parser.parseFetchRequest(pckIn);
@@ -173,12 +173,12 @@
 	@Test
 	public void testFetchWithShallow_deepen() throws IOException {
 		PacketLineIn pckIn = formatAsPacketLine(
-				PacketLineIn.DELIM,
+				PacketLineIn.delimiter(),
 				"deepen 15",
 				"deepen-relative",
 				"shallow 28274d02c489f4c7e68153056e9061a46f62d7a0",
 				"shallow 145e683b229dcab9d0e2ccb01b386f9ecc17d29d",
-				PacketLineIn.END);
+				PacketLineIn.end());
 		ProtocolV2Parser parser = new ProtocolV2Parser(
 				ConfigBuilder.getDefault());
 		FetchV2Request request = parser.parseFetchRequest(pckIn);
@@ -193,11 +193,11 @@
 
 	@Test
 	public void testFetchWithShallow_deepenNot() throws IOException {
-		PacketLineIn pckIn = formatAsPacketLine(PacketLineIn.DELIM,
+		PacketLineIn pckIn = formatAsPacketLine(PacketLineIn.delimiter(),
 				"shallow 28274d02c489f4c7e68153056e9061a46f62d7a0",
 				"shallow 145e683b229dcab9d0e2ccb01b386f9ecc17d29d",
 				"deepen-not a08595f76159b09d57553e37a5123f1091bb13e7",
-				PacketLineIn.END);
+				PacketLineIn.end());
 		ProtocolV2Parser parser = new ProtocolV2Parser(
 				ConfigBuilder.getDefault());
 		FetchV2Request request = parser.parseFetchRequest(pckIn);
@@ -210,11 +210,11 @@
 
 	@Test
 	public void testFetchWithShallow_deepenSince() throws IOException {
-		PacketLineIn pckIn = formatAsPacketLine(PacketLineIn.DELIM,
+		PacketLineIn pckIn = formatAsPacketLine(PacketLineIn.delimiter(),
 				"shallow 28274d02c489f4c7e68153056e9061a46f62d7a0",
 				"shallow 145e683b229dcab9d0e2ccb01b386f9ecc17d29d",
 				"deepen-since 123123123",
-				PacketLineIn.END);
+				PacketLineIn.end());
 		ProtocolV2Parser parser = new ProtocolV2Parser(
 				ConfigBuilder.getDefault());
 		FetchV2Request request = parser.parseFetchRequest(pckIn);
@@ -226,32 +226,46 @@
 
 	@Test
 	public void testFetchWithNoneFilter() throws IOException {
-		PacketLineIn pckIn = formatAsPacketLine(PacketLineIn.DELIM,
+		PacketLineIn pckIn = formatAsPacketLine(PacketLineIn.delimiter(),
 				"filter blob:none",
-				PacketLineIn.END);
+				PacketLineIn.end());
 		ProtocolV2Parser parser = new ProtocolV2Parser(
 				ConfigBuilder.start().allowFilter().done());
 		FetchV2Request request = parser.parseFetchRequest(pckIn);
-		assertEquals(0, request.getFilterBlobLimit());
+		assertEquals(0, request.getFilterSpec().getBlobLimit());
+		assertEquals(-1, request.getFilterSpec().getTreeDepthLimit());
 	}
 
 	@Test
 	public void testFetchWithBlobSizeFilter() throws IOException {
-		PacketLineIn pckIn = formatAsPacketLine(PacketLineIn.DELIM,
+		PacketLineIn pckIn = formatAsPacketLine(PacketLineIn.delimiter(),
 				"filter blob:limit=15",
-				PacketLineIn.END);
+				PacketLineIn.end());
 		ProtocolV2Parser parser = new ProtocolV2Parser(
 				ConfigBuilder.start().allowFilter().done());
 		FetchV2Request request = parser.parseFetchRequest(pckIn);
-		assertEquals(15, request.getFilterBlobLimit());
+		assertEquals(15, request.getFilterSpec().getBlobLimit());
+		assertEquals(-1, request.getFilterSpec().getTreeDepthLimit());
+	}
+
+	@Test
+	public void testFetchWithTreeDepthFilter() throws IOException {
+		PacketLineIn pckIn = formatAsPacketLine(PacketLineIn.delimiter(),
+				"filter tree:3",
+				PacketLineIn.end());
+		ProtocolV2Parser parser = new ProtocolV2Parser(
+				ConfigBuilder.start().allowFilter().done());
+		FetchV2Request request = parser.parseFetchRequest(pckIn);
+		assertEquals(-1, request.getFilterSpec().getBlobLimit());
+		assertEquals(3, request.getFilterSpec().getTreeDepthLimit());
 	}
 
 	@Test
 	public void testFetchMustNotHaveMultipleFilters() throws IOException {
-		PacketLineIn pckIn = formatAsPacketLine(PacketLineIn.DELIM,
+		PacketLineIn pckIn = formatAsPacketLine(PacketLineIn.delimiter(),
 				"filter blob:none",
 				"filter blob:limit=12",
-				PacketLineIn.END);
+				PacketLineIn.end());
 		ProtocolV2Parser parser = new ProtocolV2Parser(
 				ConfigBuilder.start().allowFilter().done());
 
@@ -261,8 +275,8 @@
 
 	@Test
 	public void testFetchFilterWithoutAllowFilter() throws IOException {
-		PacketLineIn pckIn = formatAsPacketLine(PacketLineIn.DELIM,
-				"filter blob:limit=12", PacketLineIn.END);
+		PacketLineIn pckIn = formatAsPacketLine(PacketLineIn.delimiter(),
+				"filter blob:limit=12", PacketLineIn.end());
 		ProtocolV2Parser parser = new ProtocolV2Parser(
 				ConfigBuilder.getDefault());
 
@@ -277,10 +291,10 @@
 		testRepo.update("branchA", one);
 		testRepo.update("branchB", two);
 
-		PacketLineIn pckIn = formatAsPacketLine(PacketLineIn.DELIM,
+		PacketLineIn pckIn = formatAsPacketLine(PacketLineIn.delimiter(),
 				"want e4980cdc48cfa1301493ca94eb70523f6788b819",
 				"want-ref refs/heads/branchA",
-				PacketLineIn.END);
+				PacketLineIn.end());
 		ProtocolV2Parser parser = new ProtocolV2Parser(
 				ConfigBuilder.start().allowRefInWant().done());
 
@@ -295,10 +309,10 @@
 
 	@Test
 	public void testFetchWithRefInWantUnknownRef() throws Exception {
-		PacketLineIn pckIn = formatAsPacketLine(PacketLineIn.DELIM,
+		PacketLineIn pckIn = formatAsPacketLine(PacketLineIn.delimiter(),
 				"want e4980cdc48cfa1301493ca94eb70523f6788b819",
 				"want-ref refs/heads/branchC",
-				PacketLineIn.END);
+				PacketLineIn.end());
 		ProtocolV2Parser parser = new ProtocolV2Parser(
 				ConfigBuilder.start().allowRefInWant().done());
 
@@ -314,8 +328,8 @@
 
 	@Test
 	public void testLsRefsMinimalReq() throws IOException {
-		PacketLineIn pckIn = formatAsPacketLine(PacketLineIn.DELIM,
-				PacketLineIn.END);
+		PacketLineIn pckIn = formatAsPacketLine(PacketLineIn.delimiter(),
+				PacketLineIn.end());
 
 		ProtocolV2Parser parser = new ProtocolV2Parser(
 				ConfigBuilder.getDefault());
@@ -327,8 +341,8 @@
 
 	@Test
 	public void testLsRefsSymrefs() throws IOException {
-		PacketLineIn pckIn = formatAsPacketLine(PacketLineIn.DELIM, "symrefs",
-				PacketLineIn.END);
+		PacketLineIn pckIn = formatAsPacketLine(PacketLineIn.delimiter(), "symrefs",
+				PacketLineIn.end());
 
 		ProtocolV2Parser parser = new ProtocolV2Parser(
 				ConfigBuilder.getDefault());
@@ -342,9 +356,9 @@
 	@Test
 	public void testLsRefsPeel() throws IOException {
 		PacketLineIn pckIn = formatAsPacketLine(
-				PacketLineIn.DELIM,
+				PacketLineIn.delimiter(),
 				"peel",
-				PacketLineIn.END);
+				PacketLineIn.end());
 
 		ProtocolV2Parser parser = new ProtocolV2Parser(
 				ConfigBuilder.getDefault());
@@ -356,9 +370,9 @@
 
 	@Test
 	public void testLsRefsRefPrefixes() throws IOException {
-		PacketLineIn pckIn = formatAsPacketLine(PacketLineIn.DELIM,
+		PacketLineIn pckIn = formatAsPacketLine(PacketLineIn.delimiter(),
 				"ref-prefix refs/for", "ref-prefix refs/heads",
-				PacketLineIn.END);
+				PacketLineIn.end());
 
 		ProtocolV2Parser parser = new ProtocolV2Parser(
 				ConfigBuilder.getDefault());
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushConnectionTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushConnectionTest.java
index cea432e..89d02d7 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushConnectionTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushConnectionTest.java
@@ -67,9 +67,6 @@
 import org.eclipse.jgit.lib.ObjectInserter;
 import org.eclipse.jgit.lib.RefUpdate;
 import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.transport.resolver.ReceivePackFactory;
-import org.eclipse.jgit.transport.resolver.ServiceNotAuthorizedException;
-import org.eclipse.jgit.transport.resolver.ServiceNotEnabledException;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -91,27 +88,16 @@
 		server = newRepo("server");
 		client = newRepo("client");
 		processedRefs = new ArrayList<>();
-		testProtocol = new TestProtocol<>(
-				null,
-				new ReceivePackFactory<Object>() {
-					@Override
-					public ReceivePack create(Object req, Repository db)
-							throws ServiceNotEnabledException,
-							ServiceNotAuthorizedException {
-						ReceivePack rp = new ReceivePack(db);
-						rp.setPreReceiveHook(
-								new PreReceiveHook() {
-									@Override
-									public void onPreReceive(ReceivePack receivePack,
-											Collection<ReceiveCommand> cmds) {
-										for (ReceiveCommand cmd : cmds) {
-											processedRefs.add(cmd.getRefName());
-										}
-									}
-								});
-						return rp;
-					}
-				});
+		testProtocol = new TestProtocol<>(null, (Object req, Repository db) -> {
+			ReceivePack rp = new ReceivePack(db);
+			rp.setPreReceiveHook((ReceivePack receivePack,
+					Collection<ReceiveCommand> cmds) -> {
+				for (ReceiveCommand cmd : cmds) {
+					processedRefs.add(cmd.getRefName());
+				}
+			});
+			return rp;
+		});
 		uri = testProtocol.register(ctx, server);
 
 		try (ObjectInserter ins = server.newObjectInserter()) {
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushOptionsTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushOptionsTest.java
index f26201d..fd1c3bf 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushOptionsTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushOptionsTest.java
@@ -69,9 +69,6 @@
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.lib.StoredConfig;
 import org.eclipse.jgit.revwalk.RevCommit;
-import org.eclipse.jgit.transport.resolver.ReceivePackFactory;
-import org.eclipse.jgit.transport.resolver.ServiceNotAuthorizedException;
-import org.eclipse.jgit.transport.resolver.ServiceNotEnabledException;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -95,16 +92,11 @@
 		client = newRepo("client");
 
 		testProtocol = new TestProtocol<>(null,
-				new ReceivePackFactory<Object>() {
-					@Override
-					public ReceivePack create(Object req, Repository git)
-							throws ServiceNotEnabledException,
-							ServiceNotAuthorizedException {
-						receivePack = new ReceivePack(git);
-						receivePack.setAllowPushOptions(true);
-						receivePack.setAtomic(true);
-						return receivePack;
-					}
+				(Object req, Repository git) -> {
+					receivePack = new ReceivePack(git);
+					receivePack.setAllowPushOptions(true);
+					receivePack.setAtomic(true);
+					return receivePack;
 				});
 
 		uri = testProtocol.register(ctx, server);
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 50c8a29..5d208ee 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
@@ -306,11 +306,11 @@
 		int nul = master.indexOf('\0');
 		assertTrue("has capability list", nul > 0);
 		assertEquals(B.name() + ' ' + R_MASTER, master.substring(0, nul));
-		assertSame(PacketLineIn.END, r.readString());
+		assertTrue(PacketLineIn.isEnd(r.readString()));
 
 		assertEquals("unpack error Missing commit " + P.name(), r.readString());
 		assertEquals("ng refs/heads/s n/a (unpacker error)", r.readString());
-		assertSame(PacketLineIn.END, r.readString());
+		assertTrue(PacketLineIn.isEnd(r.readString()));
 	}
 
 	private static void receive(final ReceivePack rp,
@@ -366,13 +366,13 @@
 			int nul = master.indexOf('\0');
 			assertTrue("has capability list", nul > 0);
 			assertEquals(B.name() + ' ' + R_MASTER, master.substring(0, nul));
-			assertSame(PacketLineIn.END, r.readString());
+			assertTrue(PacketLineIn.isEnd(r.readString()));
 
 			assertEquals("unpack error Missing blob " + b.name(),
 					r.readString());
 			assertEquals("ng refs/heads/s n/a (unpacker error)",
 					r.readString());
-			assertSame(PacketLineIn.END, r.readString());
+			assertTrue(PacketLineIn.isEnd(r.readString()));
 		}
 	}
 
@@ -419,13 +419,13 @@
 			int nul = master.indexOf('\0');
 			assertTrue("has capability list", nul > 0);
 			assertEquals(B.name() + ' ' + R_MASTER, master.substring(0, nul));
-			assertSame(PacketLineIn.END, r.readString());
+			assertTrue(PacketLineIn.isEnd(r.readString()));
 
 			assertEquals("unpack error Missing blob " + b.name(),
 					r.readString());
 			assertEquals("ng refs/heads/s n/a (unpacker error)",
 					r.readString());
-			assertSame(PacketLineIn.END, r.readString());
+			assertTrue(PacketLineIn.isEnd(r.readString()));
 		}
 	}
 
@@ -473,13 +473,13 @@
 			int nul = master.indexOf('\0');
 			assertTrue("has capability list", nul > 0);
 			assertEquals(B.name() + ' ' + R_MASTER, master.substring(0, nul));
-			assertSame(PacketLineIn.END, r.readString());
+			assertTrue(PacketLineIn.isEnd(r.readString()));
 
 			assertEquals("unpack error Missing blob " + n.name(),
 					r.readString());
 			assertEquals("ng refs/heads/s n/a (unpacker error)",
 					r.readString());
-			assertSame(PacketLineIn.END, r.readString());
+			assertTrue(PacketLineIn.isEnd(r.readString()));
 		}
 	}
 
@@ -504,13 +504,13 @@
 		int nul = master.indexOf('\0');
 		assertTrue("has capability list", nul > 0);
 		assertEquals(B.name() + ' ' + R_MASTER, master.substring(0, nul));
-		assertSame(PacketLineIn.END, r.readString());
+		assertTrue(PacketLineIn.isEnd(r.readString()));
 
 		String errorLine = r.readString();
 		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());
+		assertTrue(PacketLineIn.isEnd(r.readString()));
 	}
 
 	private TemporaryBuffer.Heap setupSourceRepoInvalidGitmodules()
@@ -589,13 +589,13 @@
 			int nul = master.indexOf('\0');
 			assertTrue("has capability list", nul > 0);
 			assertEquals(B.name() + ' ' + R_MASTER, master.substring(0, nul));
-			assertSame(PacketLineIn.END, r.readString());
+			assertTrue(PacketLineIn.isEnd(r.readString()));
 
 			assertEquals("unpack error Missing tree " + t.name(),
 					r.readString());
 			assertEquals("ng refs/heads/s n/a (unpacker error)",
 					r.readString());
-			assertSame(PacketLineIn.END, r.readString());
+			assertTrue(PacketLineIn.isEnd(r.readString()));
 		}
 	}
 
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/RefAdvertiserTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/RefAdvertiserTest.java
index ce69adf..ccb548d 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/RefAdvertiserTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/RefAdvertiserTest.java
@@ -45,7 +45,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
 
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
@@ -95,7 +95,7 @@
 		assertEquals(id(3).name() + " refs/Iñtërnâtiônàlizætiøn☃💩\n", s);
 
 		s = pckIn.readStringRaw();
-		assertSame(PacketLineIn.END, s);
+		assertTrue(PacketLineIn.isEnd(s));
 	}
 
 	private static ObjectId id(int i) {
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 1c4d0cf..f4c55aa 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
@@ -92,11 +92,8 @@
 		@Override
 		public UploadPack create(User req, Repository db) {
 			UploadPack up = new UploadPack(db);
-			up.setPostUploadHook(new PostUploadHook() {
-				@Override
-				public void onPostUpload(PackStatistics stats) {
-					havesCount = stats.getHaves();
-				}
+			up.setPostUploadHook((PackStatistics stats) -> {
+				havesCount = stats.getHaves();
 			});
 			return up;
 		}
@@ -217,16 +214,12 @@
 		ObjectId master = remote.branch("master").commit().create();
 
 		final AtomicInteger rejected = new AtomicInteger();
-		TestProtocol<User> proto = registerProto(new UploadPackFactory<User>() {
-			@Override
-			public UploadPack create(User req, Repository db)
-					throws ServiceNotAuthorizedException {
-				if (!"user2".equals(req.name)) {
-					rejected.incrementAndGet();
-					throw new ServiceNotAuthorizedException();
-				}
-				return new UploadPack(db);
+		TestProtocol<User> proto = registerProto((User req, Repository db) -> {
+			if (!"user2".equals(req.name)) {
+				rejected.incrementAndGet();
+				throw new ServiceNotAuthorizedException();
 			}
+			return new UploadPack(db);
 		}, new DefaultReceive());
 
 		// Same repository, different users.
@@ -262,16 +255,12 @@
 
 		final AtomicInteger rejected = new AtomicInteger();
 		TestProtocol<User> proto = registerProto(new DefaultUpload(),
-				new ReceivePackFactory<User>() {
-					@Override
-					public ReceivePack create(User req, Repository db)
-							throws ServiceNotAuthorizedException {
-						if (!"user2".equals(req.name)) {
-							rejected.incrementAndGet();
-							throw new ServiceNotAuthorizedException();
-						}
-						return new ReceivePack(db);
+				(User req, Repository db) -> {
+					if (!"user2".equals(req.name)) {
+						rejected.incrementAndGet();
+						throw new ServiceNotAuthorizedException();
 					}
+					return new ReceivePack(db);
 				});
 
 		// Same repository, different users.
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/TransportHttpTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/TransportHttpTest.java
new file mode 100644
index 0000000..ee0e0af
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/TransportHttpTest.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2018, Konrad Windszus <konrad_w@gmx.de>
+ * 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.File;
+import java.io.IOException;
+import java.net.HttpCookie;
+import java.time.Instant;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Date;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+import org.eclipse.jgit.internal.transport.http.NetscapeCookieFile;
+import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.test.resources.SampleDataRepositoryTestCase;
+import org.eclipse.jgit.transport.http.HttpConnection;
+import org.eclipse.jgit.util.http.HttpCookiesMatcher;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentMatchers;
+import org.mockito.Mockito;
+
+public class TransportHttpTest extends SampleDataRepositoryTestCase {
+	private URIish uri;
+	private File cookieFile;
+
+	@Override
+	@Before
+	public void setUp() throws Exception {
+		super.setUp();
+		uri = new URIish("https://everyones.loves.git/u/2");
+
+		final Config config = db.getConfig();
+		config.setBoolean("http", null, "saveCookies", true);
+		cookieFile = createTempFile();
+		config.setString("http", null, "cookieFile",
+				cookieFile.getAbsolutePath());
+	}
+
+	@Test
+	public void testMatchesCookieDomain() {
+		Assert.assertTrue(TransportHttp.matchesCookieDomain("example.com",
+				"example.com"));
+		Assert.assertTrue(TransportHttp.matchesCookieDomain("Example.Com",
+				"example.cOM"));
+		Assert.assertTrue(TransportHttp.matchesCookieDomain(
+				"some.subdomain.example.com", "example.com"));
+		Assert.assertFalse(TransportHttp
+				.matchesCookieDomain("someotherexample.com", "example.com"));
+		Assert.assertFalse(TransportHttp.matchesCookieDomain("example.com",
+				"example1.com"));
+		Assert.assertFalse(TransportHttp
+				.matchesCookieDomain("sub.sub.example.com", ".example.com"));
+		Assert.assertTrue(TransportHttp.matchesCookieDomain("host.example.com",
+				"example.com"));
+		Assert.assertTrue(TransportHttp.matchesCookieDomain(
+				"something.example.com", "something.example.com"));
+		Assert.assertTrue(TransportHttp.matchesCookieDomain(
+				"host.something.example.com", "something.example.com"));
+	}
+
+	@Test
+	public void testMatchesCookiePath() {
+		Assert.assertTrue(
+				TransportHttp.matchesCookiePath("/some/path", "/some/path"));
+		Assert.assertTrue(TransportHttp.matchesCookiePath("/some/path/child",
+				"/some/path"));
+		Assert.assertTrue(TransportHttp.matchesCookiePath("/some/path/child",
+				"/some/path/"));
+		Assert.assertFalse(TransportHttp.matchesCookiePath("/some/pathother",
+				"/some/path"));
+		Assert.assertFalse(
+				TransportHttp.matchesCookiePath("otherpath", "/some/path"));
+	}
+
+	@Test
+	public void testProcessResponseCookies() throws IOException {
+		HttpConnection connection = Mockito.mock(HttpConnection.class);
+		Mockito.when(
+				connection.getHeaderFields(ArgumentMatchers.eq("Set-Cookie")))
+				.thenReturn(Arrays.asList(
+						"id=a3fWa; Expires=Fri, 01 Jan 2100 11:00:00 GMT; Secure; HttpOnly",
+						"sessionid=38afes7a8; HttpOnly; Path=/"));
+		Mockito.when(
+				connection.getHeaderFields(ArgumentMatchers.eq("Set-Cookie2")))
+				.thenReturn(Collections
+						.singletonList("cookie2=some value; Max-Age=1234; Path=/"));
+
+		try (TransportHttp transportHttp = new TransportHttp(db, uri)) {
+			Date creationDate = new Date();
+			transportHttp.processResponseCookies(connection);
+
+			// evaluate written cookie file
+			Set<HttpCookie> expectedCookies = new LinkedHashSet<>();
+
+			HttpCookie cookie = new HttpCookie("id", "a3fWa");
+			cookie.setDomain("everyones.loves.git");
+			cookie.setPath("/u/2/");
+
+			cookie.setMaxAge(
+					(Instant.parse("2100-01-01T11:00:00.000Z").toEpochMilli()
+							- creationDate.getTime()) / 1000);
+			cookie.setSecure(true);
+			cookie.setHttpOnly(true);
+			expectedCookies.add(cookie);
+
+			cookie = new HttpCookie("cookie2", "some value");
+			cookie.setDomain("everyones.loves.git");
+			cookie.setPath("/");
+			cookie.setMaxAge(1234);
+			expectedCookies.add(cookie);
+
+			Assert.assertThat(
+					new NetscapeCookieFile(cookieFile.toPath())
+							.getCookies(true),
+					HttpCookiesMatcher.containsInOrder(expectedCookies, 5));
+		}
+	}
+
+	@Test
+	public void testProcessResponseCookiesNotPersistingWithSaveCookiesFalse()
+			throws IOException {
+		HttpConnection connection = Mockito.mock(HttpConnection.class);
+		Mockito.when(
+				connection.getHeaderFields(ArgumentMatchers.eq("Set-Cookie")))
+				.thenReturn(Arrays.asList(
+						"id=a3fWa; Expires=Thu, 21 Oct 2100 11:00:00 GMT; Secure; HttpOnly",
+						"sessionid=38afes7a8; HttpOnly; Path=/"));
+		Mockito.when(
+				connection.getHeaderFields(ArgumentMatchers.eq("Set-Cookie2")))
+				.thenReturn(Collections.singletonList(
+						"cookie2=some value; Max-Age=1234; Path=/"));
+
+		// tweak config
+		final Config config = db.getConfig();
+		config.setBoolean("http", null, "saveCookies", false);
+
+		try (TransportHttp transportHttp = new TransportHttp(db, uri)) {
+			transportHttp.processResponseCookies(connection);
+
+			// evaluate written cookie file
+			Assert.assertFalse("Cookie file was not supposed to be written!",
+					cookieFile.exists());
+		}
+	}
+}
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 1cbe8a4..260130b 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
@@ -4,7 +4,6 @@
 import static org.hamcrest.Matchers.hasItems;
 import static org.hamcrest.Matchers.is;
 import static org.hamcrest.Matchers.notNullValue;
-import static org.hamcrest.Matchers.theInstance;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
@@ -16,11 +15,16 @@
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.StringWriter;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 
+import org.eclipse.jgit.dircache.DirCache;
+import org.eclipse.jgit.dircache.DirCacheBuilder;
+import org.eclipse.jgit.dircache.DirCacheEntry;
 import org.eclipse.jgit.errors.PackProtocolException;
 import org.eclipse.jgit.errors.TransportException;
 import org.eclipse.jgit.internal.storage.dfs.DfsGarbageCollector;
@@ -28,6 +32,8 @@
 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.ObjectId;
+import org.eclipse.jgit.lib.ObjectInserter;
 import org.eclipse.jgit.lib.PersonIdent;
 import org.eclipse.jgit.lib.ProgressMonitor;
 import org.eclipse.jgit.lib.Ref;
@@ -38,10 +44,8 @@
 import org.eclipse.jgit.revwalk.RevCommit;
 import org.eclipse.jgit.revwalk.RevTag;
 import org.eclipse.jgit.revwalk.RevTree;
+import org.eclipse.jgit.storage.pack.PackStatistics;
 import org.eclipse.jgit.transport.UploadPack.RequestPolicy;
-import org.eclipse.jgit.transport.resolver.ServiceNotAuthorizedException;
-import org.eclipse.jgit.transport.resolver.ServiceNotEnabledException;
-import org.eclipse.jgit.transport.resolver.UploadPackFactory;
 import org.eclipse.jgit.util.io.NullOutputStream;
 import org.hamcrest.Matchers;
 import org.junit.After;
@@ -69,6 +73,8 @@
 
 	private TestRepository<InMemoryRepository> remote;
 
+	private PackStatistics stats;
+
 	@Before
 	public void setUp() throws Exception {
 		server = newRepo("server");
@@ -92,17 +98,11 @@
 	}
 
 	private static TestProtocol<Object> generateReachableCommitUploadPackProtocol() {
-		return new TestProtocol<>(
-				new UploadPackFactory<Object>() {
-					@Override
-					public UploadPack create(Object req, Repository db)
-							throws ServiceNotEnabledException,
-							ServiceNotAuthorizedException {
-						UploadPack up = new UploadPack(db);
-						up.setRequestPolicy(RequestPolicy.REACHABLE_COMMIT);
-						return up;
-					}
-				}, null);
+		return new TestProtocol<>((Object req, Repository db) -> {
+			UploadPack up = new UploadPack(db);
+			up.setRequestPolicy(RequestPolicy.REACHABLE_COMMIT);
+			return up;
+		}, null);
 	}
 
 	@Test
@@ -112,20 +112,14 @@
 		RevCommit tip = remote.commit().message("2").parent(commit1).create();
 		remote.update("master", tip);
 
-		testProtocol = new TestProtocol<>(
-				new UploadPackFactory<Object>() {
-					@Override
-					public UploadPack create(Object req, Repository db)
-							throws ServiceNotEnabledException,
-							ServiceNotAuthorizedException {
-						UploadPack up = new UploadPack(db);
-						up.setRequestPolicy(RequestPolicy.REACHABLE_COMMIT);
-						// assume client has a shallow commit
-						up.getRevWalk().assumeShallow(
-								Collections.singleton(commit1.getId()));
-						return up;
-					}
-				}, null);
+		testProtocol = new TestProtocol<>((Object req, Repository db) -> {
+			UploadPack up = new UploadPack(db);
+			up.setRequestPolicy(RequestPolicy.REACHABLE_COMMIT);
+			// assume client has a shallow commit
+			up.getRevWalk()
+					.assumeShallow(Collections.singleton(commit1.getId()));
+			return up;
+		}, null);
 		uri = testProtocol.register(ctx, server);
 
 		assertFalse(client.getObjectDatabase().has(commit0.toObjectId()));
@@ -212,19 +206,14 @@
 			server2.getConfig().setBoolean("uploadpack", null, "allowfilter",
 					true);
 
-			testProtocol = new TestProtocol<>(new UploadPackFactory<Object>() {
-				@Override
-				public UploadPack create(Object req, Repository db)
-						throws ServiceNotEnabledException,
-						ServiceNotAuthorizedException {
-					UploadPack up = new UploadPack(db);
-					return up;
-				}
+			testProtocol = new TestProtocol<>((Object req, Repository db) -> {
+				UploadPack up = new UploadPack(db);
+				return up;
 			}, null);
 			uri = testProtocol.register(ctx, server2);
 
 			try (Transport tn = testProtocol.open(uri, client, "server2")) {
-				tn.setFilterBlobLimit(0);
+				tn.setFilterSpec(FilterSpec.withBlobLimit(0));
 				tn.fetch(NullProgressMonitor.INSTANCE,
 						Collections.singletonList(new RefSpec(commit.name())));
 				assertTrue(client.getObjectDatabase().has(tree.toObjectId()));
@@ -250,19 +239,14 @@
 			server2.getConfig().setBoolean("uploadpack", null, "allowfilter",
 					true);
 
-			testProtocol = new TestProtocol<>(new UploadPackFactory<Object>() {
-				@Override
-				public UploadPack create(Object req, Repository db)
-						throws ServiceNotEnabledException,
-						ServiceNotAuthorizedException {
-					UploadPack up = new UploadPack(db);
-					return up;
-				}
+			testProtocol = new TestProtocol<>((Object req, Repository db) -> {
+				UploadPack up = new UploadPack(db);
+				return up;
 			}, null);
 			uri = testProtocol.register(ctx, server2);
 
 			try (Transport tn = testProtocol.open(uri, client, "server2")) {
-				tn.setFilterBlobLimit(0);
+				tn.setFilterSpec(FilterSpec.withBlobLimit(0));
 				tn.fetch(NullProgressMonitor.INSTANCE, Arrays.asList(
 						new RefSpec(commit.name()), new RefSpec(blob1.name())));
 				assertTrue(client.getObjectDatabase().has(tree.toObjectId()));
@@ -287,19 +271,14 @@
 			server2.getConfig().setBoolean("uploadpack", null, "allowfilter",
 					true);
 
-			testProtocol = new TestProtocol<>(new UploadPackFactory<Object>() {
-				@Override
-				public UploadPack create(Object req, Repository db)
-						throws ServiceNotEnabledException,
-						ServiceNotAuthorizedException {
-					UploadPack up = new UploadPack(db);
-					return up;
-				}
+			testProtocol = new TestProtocol<>((Object req, Repository db) -> {
+				UploadPack up = new UploadPack(db);
+				return up;
 			}, null);
 			uri = testProtocol.register(ctx, server2);
 
 			try (Transport tn = testProtocol.open(uri, client, "server2")) {
-				tn.setFilterBlobLimit(5);
+				tn.setFilterSpec(FilterSpec.withBlobLimit(5));
 				tn.fetch(NullProgressMonitor.INSTANCE,
 						Collections.singletonList(new RefSpec(commit.name())));
 				assertFalse(
@@ -330,19 +309,14 @@
 			new DfsGarbageCollector(server2).pack(null);
 			server2.scanForRepoChanges();
 
-			testProtocol = new TestProtocol<>(new UploadPackFactory<Object>() {
-				@Override
-				public UploadPack create(Object req, Repository db)
-						throws ServiceNotEnabledException,
-						ServiceNotAuthorizedException {
-					UploadPack up = new UploadPack(db);
-					return up;
-				}
+			testProtocol = new TestProtocol<>((Object req, Repository db) -> {
+				UploadPack up = new UploadPack(db);
+				return up;
 			}, null);
 			uri = testProtocol.register(ctx, server2);
 
 			try (Transport tn = testProtocol.open(uri, client, "server2")) {
-				tn.setFilterBlobLimit(0);
+				tn.setFilterSpec(FilterSpec.withBlobLimit(0));
 				tn.fetch(NullProgressMonitor.INSTANCE, Arrays.asList(
 						new RefSpec(commit.name()), new RefSpec(blob1.name())));
 				assertTrue(client.getObjectDatabase().has(blob1.toObjectId()));
@@ -370,19 +344,14 @@
 			new DfsGarbageCollector(server2).pack(null);
 			server2.scanForRepoChanges();
 
-			testProtocol = new TestProtocol<>(new UploadPackFactory<Object>() {
-				@Override
-				public UploadPack create(Object req, Repository db)
-						throws ServiceNotEnabledException,
-						ServiceNotAuthorizedException {
-					UploadPack up = new UploadPack(db);
-					return up;
-				}
+			testProtocol = new TestProtocol<>((Object req, Repository db) -> {
+				UploadPack up = new UploadPack(db);
+				return up;
 			}, null);
 			uri = testProtocol.register(ctx, server2);
 
 			try (Transport tn = testProtocol.open(uri, client, "server2")) {
-				tn.setFilterBlobLimit(5);
+				tn.setFilterSpec(FilterSpec.withBlobLimit(5));
 				tn.fetch(NullProgressMonitor.INSTANCE,
 						Collections.singletonList(new RefSpec(commit.name())));
 				assertFalse(
@@ -406,19 +375,14 @@
 			server2.getConfig().setBoolean("uploadpack", null, "allowfilter",
 					false);
 
-			testProtocol = new TestProtocol<>(new UploadPackFactory<Object>() {
-				@Override
-				public UploadPack create(Object req, Repository db)
-						throws ServiceNotEnabledException,
-						ServiceNotAuthorizedException {
-					UploadPack up = new UploadPack(db);
-					return up;
-				}
+			testProtocol = new TestProtocol<>((Object req, Repository db) -> {
+				UploadPack up = new UploadPack(db);
+				return up;
 			}, null);
 			uri = testProtocol.register(ctx, server2);
 
 			try (Transport tn = testProtocol.open(uri, client, "server2")) {
-				tn.setFilterBlobLimit(0);
+				tn.setFilterSpec(FilterSpec.withBlobLimit(0));
 
 				thrown.expect(TransportException.class);
 				thrown.expectMessage(
@@ -453,6 +417,7 @@
 
 		ByteArrayOutputStream recv = new ByteArrayOutputStream();
 		up.upload(send, recv, null);
+		stats = up.getStatistics();
 
 		return new ByteArrayInputStream(recv.toByteArray());
 	}
@@ -462,9 +427,9 @@
 		try (ByteArrayOutputStream send = new ByteArrayOutputStream()) {
 			PacketLineOut pckOut = new PacketLineOut(send);
 			for (String line : inputLines) {
-				if (line == PacketLineIn.END) {
+				if (PacketLineIn.isEnd(line)) {
 					pckOut.end();
-				} else if (line == PacketLineIn.DELIM) {
+				} else if (PacketLineIn.isDelimiter(line)) {
 					pckOut.writeDelim();
 				} else {
 					pckOut.writeString(line);
@@ -487,7 +452,7 @@
 		PacketLineIn pckIn = new PacketLineIn(recvStream);
 
 		// drain capabilities
-		while (pckIn.readString() != PacketLineIn.END) {
+		while (!PacketLineIn.isEnd(pckIn.readString())) {
 			// do nothing
 		}
 		return recvStream;
@@ -524,7 +489,7 @@
 	public void testV2Capabilities() throws Exception {
 		TestV2Hook hook = new TestV2Hook();
 		ByteArrayInputStream recvStream =
-				uploadPackV2Setup(null, null, hook, PacketLineIn.END);
+				uploadPackV2Setup(null, null, hook, PacketLineIn.end());
 		PacketLineIn pckIn = new PacketLineIn(recvStream);
 		assertThat(hook.capabilitiesRequest, notNullValue());
 		assertThat(pckIn.readString(), is("version 2"));
@@ -538,14 +503,14 @@
 				// 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);
+		assertTrue(PacketLineIn.isEnd(pckIn.readString()));
 	}
 
 	@Test
 	public void testV2CapabilitiesAllowFilter() throws Exception {
 		server.getConfig().setBoolean("uploadpack", null, "allowfilter", true);
 		ByteArrayInputStream recvStream =
-				uploadPackV2Setup(null, null, null, PacketLineIn.END);
+				uploadPackV2Setup(null, null, null, PacketLineIn.end());
 		PacketLineIn pckIn = new PacketLineIn(recvStream);
 
 		assertThat(pckIn.readString(), is("version 2"));
@@ -555,14 +520,14 @@
 				// 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);
+		assertTrue(PacketLineIn.isEnd(pckIn.readString()));
 	}
 
 	@Test
 	public void testV2CapabilitiesRefInWant() throws Exception {
 		server.getConfig().setBoolean("uploadpack", null, "allowrefinwant", true);
 		ByteArrayInputStream recvStream =
-				uploadPackV2Setup(null, null, null, PacketLineIn.END);
+				uploadPackV2Setup(null, null, null, PacketLineIn.end());
 		PacketLineIn pckIn = new PacketLineIn(recvStream);
 
 		assertThat(pckIn.readString(), is("version 2"));
@@ -573,14 +538,14 @@
 				// order of the capabilities of "fetch".
 				hasItems("ls-refs", "fetch=ref-in-want shallow",
 						"server-option"));
-		assertTrue(pckIn.readString() == PacketLineIn.END);
+		assertTrue(PacketLineIn.isEnd(pckIn.readString()));
 	}
 
 	@Test
 	public void testV2CapabilitiesRefInWantNotAdvertisedIfUnallowed() throws Exception {
 		server.getConfig().setBoolean("uploadpack", null, "allowrefinwant", false);
 		ByteArrayInputStream recvStream =
-				uploadPackV2Setup(null, null, null, PacketLineIn.END);
+				uploadPackV2Setup(null, null, null, PacketLineIn.end());
 		PacketLineIn pckIn = new PacketLineIn(recvStream);
 
 		assertThat(pckIn.readString(), is("version 2"));
@@ -588,7 +553,7 @@
 				Arrays.asList(pckIn.readString(), pckIn.readString(),
 						pckIn.readString()),
 				hasItems("ls-refs", "fetch=shallow", "server-option"));
-		assertTrue(pckIn.readString() == PacketLineIn.END);
+		assertTrue(PacketLineIn.isEnd(pckIn.readString()));
 	}
 
 	@Test
@@ -596,7 +561,7 @@
 		server.getConfig().setBoolean("uploadpack", null, "allowrefinwant", true);
 		server.getConfig().setBoolean("uploadpack", null, "advertiserefinwant", false);
 		ByteArrayInputStream recvStream =
-				uploadPackV2Setup(null, null, null, PacketLineIn.END);
+				uploadPackV2Setup(null, null, null, PacketLineIn.end());
 		PacketLineIn pckIn = new PacketLineIn(recvStream);
 
 		assertThat(pckIn.readString(), is("version 2"));
@@ -604,12 +569,12 @@
 				Arrays.asList(pckIn.readString(), pckIn.readString(),
 						pckIn.readString()),
 				hasItems("ls-refs", "fetch=shallow", "server-option"));
-		assertTrue(pckIn.readString() == PacketLineIn.END);
+		assertTrue(PacketLineIn.isEnd(pckIn.readString()));
 	}
 
 	@Test
 	public void testV2EmptyRequest() throws Exception {
-		ByteArrayInputStream recvStream = uploadPackV2(PacketLineIn.END);
+		ByteArrayInputStream recvStream = uploadPackV2(PacketLineIn.end());
 		// Verify that there is nothing more after the capability
 		// advertisement.
 		assertEquals(0, recvStream.available());
@@ -625,14 +590,14 @@
 
 		TestV2Hook hook = new TestV2Hook();
 		ByteArrayInputStream recvStream = uploadPackV2(null, null, hook,
-				"command=ls-refs\n", PacketLineIn.END);
+				"command=ls-refs\n", PacketLineIn.end());
 		PacketLineIn pckIn = new PacketLineIn(recvStream);
 
 		assertThat(hook.lsRefsRequest, notNullValue());
 		assertThat(pckIn.readString(), is(tip.toObjectId().getName() + " HEAD"));
 		assertThat(pckIn.readString(), is(tip.toObjectId().getName() + " refs/heads/master"));
 		assertThat(pckIn.readString(), is(tag.toObjectId().getName() + " refs/tags/tag"));
-		assertTrue(pckIn.readString() == PacketLineIn.END);
+		assertTrue(PacketLineIn.isEnd(pckIn.readString()));
 	}
 
 	@Test
@@ -643,13 +608,14 @@
 		RevTag tag = remote.tag("tag", tip);
 		remote.update("refs/tags/tag", tag);
 
-		ByteArrayInputStream recvStream = uploadPackV2("command=ls-refs\n", PacketLineIn.DELIM, "symrefs", PacketLineIn.END);
+		ByteArrayInputStream recvStream = uploadPackV2("command=ls-refs\n",
+				PacketLineIn.delimiter(), "symrefs", PacketLineIn.end());
 		PacketLineIn pckIn = new PacketLineIn(recvStream);
 
 		assertThat(pckIn.readString(), is(tip.toObjectId().getName() + " HEAD symref-target:refs/heads/master"));
 		assertThat(pckIn.readString(), is(tip.toObjectId().getName() + " refs/heads/master"));
 		assertThat(pckIn.readString(), is(tag.toObjectId().getName() + " refs/tags/tag"));
-		assertTrue(pckIn.readString() == PacketLineIn.END);
+		assertTrue(PacketLineIn.isEnd(pckIn.readString()));
 	}
 
 	@Test
@@ -660,7 +626,8 @@
 		RevTag tag = remote.tag("tag", tip);
 		remote.update("refs/tags/tag", tag);
 
-		ByteArrayInputStream recvStream = uploadPackV2("command=ls-refs\n", PacketLineIn.DELIM, "peel", PacketLineIn.END);
+		ByteArrayInputStream recvStream = uploadPackV2("command=ls-refs\n",
+				PacketLineIn.delimiter(), "peel", PacketLineIn.end());
 		PacketLineIn pckIn = new PacketLineIn(recvStream);
 
 		assertThat(pckIn.readString(), is(tip.toObjectId().getName() + " HEAD"));
@@ -669,7 +636,7 @@
 			pckIn.readString(),
 			is(tag.toObjectId().getName() + " refs/tags/tag peeled:"
 				+ tip.toObjectId().getName()));
-		assertTrue(pckIn.readString() == PacketLineIn.END);
+		assertTrue(PacketLineIn.isEnd(pckIn.readString()));
 	}
 
 	@Test
@@ -681,8 +648,9 @@
 		remote.update("refs/tags/tag", tag);
 
 		ByteArrayInputStream recvStream = uploadPackV2(
-			"command=ls-refs\n", PacketLineIn.DELIM, "symrefs", "peel", PacketLineIn.END,
-			"command=ls-refs\n", PacketLineIn.DELIM, PacketLineIn.END);
+				"command=ls-refs\n", PacketLineIn.delimiter(), "symrefs",
+				"peel", PacketLineIn.end(), "command=ls-refs\n",
+				PacketLineIn.delimiter(), PacketLineIn.end());
 		PacketLineIn pckIn = new PacketLineIn(recvStream);
 
 		assertThat(pckIn.readString(), is(tip.toObjectId().getName() + " HEAD symref-target:refs/heads/master"));
@@ -691,11 +659,11 @@
 			pckIn.readString(),
 			is(tag.toObjectId().getName() + " refs/tags/tag peeled:"
 				+ tip.toObjectId().getName()));
-		assertTrue(pckIn.readString() == PacketLineIn.END);
+		assertTrue(PacketLineIn.isEnd(pckIn.readString()));
 		assertThat(pckIn.readString(), is(tip.toObjectId().getName() + " HEAD"));
 		assertThat(pckIn.readString(), is(tip.toObjectId().getName() + " refs/heads/master"));
 		assertThat(pckIn.readString(), is(tag.toObjectId().getName() + " refs/tags/tag"));
-		assertTrue(pckIn.readString() == PacketLineIn.END);
+		assertTrue(PacketLineIn.isEnd(pckIn.readString()));
 	}
 
 	@Test
@@ -707,15 +675,15 @@
 
 		ByteArrayInputStream recvStream = uploadPackV2(
 			"command=ls-refs\n",
-			PacketLineIn.DELIM,
+			PacketLineIn.delimiter(),
 			"ref-prefix refs/heads/maste",
 			"ref-prefix refs/heads/other",
-			PacketLineIn.END);
+				PacketLineIn.end());
 		PacketLineIn pckIn = new PacketLineIn(recvStream);
 
 		assertThat(pckIn.readString(), is(tip.toObjectId().getName() + " refs/heads/master"));
 		assertThat(pckIn.readString(), is(tip.toObjectId().getName() + " refs/heads/other"));
-		assertTrue(pckIn.readString() == PacketLineIn.END);
+		assertTrue(PacketLineIn.isEnd(pckIn.readString()));
 	}
 
 	@Test
@@ -726,15 +694,15 @@
 
 		ByteArrayInputStream recvStream = uploadPackV2(
 			"command=ls-refs\n",
-			PacketLineIn.DELIM,
+			PacketLineIn.delimiter(),
 			"ref-prefix refs/heads/maste",
 			"ref-prefix r",
-			PacketLineIn.END);
+				PacketLineIn.end());
 		PacketLineIn pckIn = new PacketLineIn(recvStream);
 
 		assertThat(pckIn.readString(), is(tip.toObjectId().getName() + " refs/heads/master"));
 		assertThat(pckIn.readString(), is(tip.toObjectId().getName() + " refs/heads/other"));
-		assertTrue(pckIn.readString() == PacketLineIn.END);
+		assertTrue(PacketLineIn.isEnd(pckIn.readString()));
 	}
 
 	@Test
@@ -743,17 +711,17 @@
 		thrown.expectMessage("unexpected invalid-argument");
 		uploadPackV2(
 			"command=ls-refs\n",
-			PacketLineIn.DELIM,
+			PacketLineIn.delimiter(),
 			"invalid-argument\n",
-			PacketLineIn.END);
+				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 };
+				PacketLineIn.delimiter(),
+				PacketLineIn.end() };
 
 		TestV2Hook testHook = new TestV2Hook();
 		uploadPackV2Setup(null, null, testHook, lines);
@@ -797,9 +765,9 @@
 			null,
 			null,
 			"command=fetch\n",
-			PacketLineIn.DELIM,
+			PacketLineIn.delimiter(),
 			"want " + advertized.name() + "\n",
-			PacketLineIn.END);
+				PacketLineIn.end());
 
 		// This doesn't
 		thrown.expect(TransportException.class);
@@ -810,9 +778,9 @@
 			null,
 			null,
 			"command=fetch\n",
-			PacketLineIn.DELIM,
+			PacketLineIn.delimiter(),
 			"want " + unadvertized.name() + "\n",
-			PacketLineIn.END);
+				PacketLineIn.end());
 	}
 
 	@Test
@@ -828,9 +796,9 @@
 			null,
 			null,
 			"command=fetch\n",
-			PacketLineIn.DELIM,
+			PacketLineIn.delimiter(),
 			"want " + reachable.name() + "\n",
-			PacketLineIn.END);
+				PacketLineIn.end());
 
 		// This doesn't
 		thrown.expect(TransportException.class);
@@ -841,9 +809,9 @@
 			null,
 			null,
 			"command=fetch\n",
-			PacketLineIn.DELIM,
+			PacketLineIn.delimiter(),
 			"want " + unreachable.name() + "\n",
-			PacketLineIn.END);
+				PacketLineIn.end());
 	}
 
 	@Test
@@ -858,9 +826,9 @@
 			new RejectAllRefFilter(),
 			null,
 			"command=fetch\n",
-			PacketLineIn.DELIM,
+			PacketLineIn.delimiter(),
 			"want " + tip.name() + "\n",
-			PacketLineIn.END);
+				PacketLineIn.end());
 
 		// This doesn't
 		thrown.expect(TransportException.class);
@@ -871,9 +839,9 @@
 			new RejectAllRefFilter(),
 			null,
 			"command=fetch\n",
-			PacketLineIn.DELIM,
+			PacketLineIn.delimiter(),
 			"want " + parentOfTip.name() + "\n",
-			PacketLineIn.END);
+				PacketLineIn.end());
 	}
 
 	@Test
@@ -889,9 +857,9 @@
 			new RejectAllRefFilter(),
 			null,
 			"command=fetch\n",
-			PacketLineIn.DELIM,
+			PacketLineIn.delimiter(),
 			"want " + parentOfTip.name() + "\n",
-			PacketLineIn.END);
+				PacketLineIn.end());
 
 		// This doesn't
 		thrown.expect(TransportException.class);
@@ -902,9 +870,9 @@
 			new RejectAllRefFilter(),
 			null,
 			"command=fetch\n",
-			PacketLineIn.DELIM,
+			PacketLineIn.delimiter(),
 			"want " + unreachable.name() + "\n",
-			PacketLineIn.END);
+				PacketLineIn.end());
 	}
 
 	@Test
@@ -917,9 +885,9 @@
 			null,
 			null,
 			"command=fetch\n",
-			PacketLineIn.DELIM,
+			PacketLineIn.delimiter(),
 			"want " + unreachable.name() + "\n",
-			PacketLineIn.END);
+				PacketLineIn.end());
 	}
 
 	@Test
@@ -933,16 +901,16 @@
 
 		ByteArrayInputStream recvStream = uploadPackV2(
 			"command=fetch\n",
-			PacketLineIn.DELIM,
+			PacketLineIn.delimiter(),
 			"want " + fooChild.toObjectId().getName() + "\n",
 			"want " + barChild.toObjectId().getName() + "\n",
 			"have " + fooParent.toObjectId().getName() + "\n",
-			PacketLineIn.END);
+				PacketLineIn.end());
 		PacketLineIn pckIn = new PacketLineIn(recvStream);
 
 		assertThat(pckIn.readString(), is("acknowledgments"));
 		assertThat(pckIn.readString(), is("ACK " + fooParent.toObjectId().getName()));
-		assertThat(pckIn.readString(), theInstance(PacketLineIn.END));
+		assertTrue(PacketLineIn.isEnd(pckIn.readString()));
 	}
 
 	@Test
@@ -956,12 +924,12 @@
 
 		ByteArrayInputStream recvStream = uploadPackV2(
 			"command=fetch\n",
-			PacketLineIn.DELIM,
+			PacketLineIn.delimiter(),
 			"want " + fooChild.toObjectId().getName() + "\n",
 			"want " + barChild.toObjectId().getName() + "\n",
 			"have " + fooParent.toObjectId().getName() + "\n",
 			"have " + barParent.toObjectId().getName() + "\n",
-			PacketLineIn.END);
+				PacketLineIn.end());
 		PacketLineIn pckIn = new PacketLineIn(recvStream);
 
 		assertThat(pckIn.readString(), is("acknowledgments"));
@@ -971,7 +939,7 @@
 				"ACK " + fooParent.toObjectId().getName(),
 				"ACK " + barParent.toObjectId().getName()));
 		assertThat(pckIn.readString(), is("ready"));
-		assertThat(pckIn.readString(), theInstance(PacketLineIn.DELIM));
+		assertTrue(PacketLineIn.isDelimiter(pckIn.readString()));
 		assertThat(pckIn.readString(), is("packfile"));
 		parsePack(recvStream);
 		assertFalse(client.getObjectDatabase().has(fooParent.toObjectId()));
@@ -991,12 +959,12 @@
 
 		ByteArrayInputStream recvStream = uploadPackV2(
 			"command=fetch\n",
-			PacketLineIn.DELIM,
+			PacketLineIn.delimiter(),
 			"want " + fooChild.toObjectId().getName() + "\n",
 			"want " + barChild.toObjectId().getName() + "\n",
 			"have " + fooParent.toObjectId().getName() + "\n",
 			"done\n",
-			PacketLineIn.END);
+				PacketLineIn.end());
 		PacketLineIn pckIn = new PacketLineIn(recvStream);
 
 		assertThat(pckIn.readString(), is("packfile"));
@@ -1020,12 +988,12 @@
 		// Pretend that we have parent to get a thin pack based on it.
 		ByteArrayInputStream recvStream = uploadPackV2(
 			"command=fetch\n",
-			PacketLineIn.DELIM,
+			PacketLineIn.delimiter(),
 			"want " + child.toObjectId().getName() + "\n",
 			"have " + parent.toObjectId().getName() + "\n",
 			"thin-pack\n",
 			"done\n",
-			PacketLineIn.END);
+				PacketLineIn.end());
 		PacketLineIn pckIn = new PacketLineIn(recvStream);
 
 		assertThat(pckIn.readString(), is("packfile"));
@@ -1046,10 +1014,10 @@
 		StringWriter sw = new StringWriter();
 		ByteArrayInputStream recvStream = uploadPackV2(
 			"command=fetch\n",
-			PacketLineIn.DELIM,
+			PacketLineIn.delimiter(),
 			"want " + commit.toObjectId().getName() + "\n",
 			"done\n",
-			PacketLineIn.END);
+				PacketLineIn.end());
 		PacketLineIn pckIn = new PacketLineIn(recvStream);
 		assertThat(pckIn.readString(), is("packfile"));
 		parsePack(recvStream, new TextProgressMonitor(sw));
@@ -1059,11 +1027,11 @@
 		sw = new StringWriter();
 		recvStream = uploadPackV2(
 			"command=fetch\n",
-			PacketLineIn.DELIM,
+			PacketLineIn.delimiter(),
 			"want " + commit.toObjectId().getName() + "\n",
 			"no-progress\n",
 			"done\n",
-			PacketLineIn.END);
+				PacketLineIn.end());
 		pckIn = new PacketLineIn(recvStream);
 		assertThat(pckIn.readString(), is("packfile"));
 		parsePack(recvStream, new TextProgressMonitor(sw));
@@ -1080,10 +1048,10 @@
 		// Without include-tag.
 		ByteArrayInputStream recvStream = uploadPackV2(
 			"command=fetch\n",
-			PacketLineIn.DELIM,
+			PacketLineIn.delimiter(),
 			"want " + commit.toObjectId().getName() + "\n",
 			"done\n",
-			PacketLineIn.END);
+				PacketLineIn.end());
 		PacketLineIn pckIn = new PacketLineIn(recvStream);
 		assertThat(pckIn.readString(), is("packfile"));
 		parsePack(recvStream);
@@ -1092,11 +1060,11 @@
 		// With tag.
 		recvStream = uploadPackV2(
 			"command=fetch\n",
-			PacketLineIn.DELIM,
+			PacketLineIn.delimiter(),
 			"want " + commit.toObjectId().getName() + "\n",
 			"include-tag\n",
 			"done\n",
-			PacketLineIn.END);
+				PacketLineIn.end());
 		pckIn = new PacketLineIn(recvStream);
 		assertThat(pckIn.readString(), is("packfile"));
 		parsePack(recvStream);
@@ -1116,27 +1084,27 @@
 		// Without ofs-delta.
 		ByteArrayInputStream recvStream = uploadPackV2(
 			"command=fetch\n",
-			PacketLineIn.DELIM,
+			PacketLineIn.delimiter(),
 			"want " + child.toObjectId().getName() + "\n",
 			"done\n",
-			PacketLineIn.END);
+				PacketLineIn.end());
 		PacketLineIn pckIn = new PacketLineIn(recvStream);
 		assertThat(pckIn.readString(), is("packfile"));
-		ReceivedPackStatistics stats = parsePack(recvStream);
-		assertTrue(stats.getNumOfsDelta() == 0);
+		ReceivedPackStatistics receivedStats = parsePack(recvStream);
+		assertTrue(receivedStats.getNumOfsDelta() == 0);
 
 		// With ofs-delta.
 		recvStream = uploadPackV2(
 			"command=fetch\n",
-			PacketLineIn.DELIM,
+			PacketLineIn.delimiter(),
 			"want " + child.toObjectId().getName() + "\n",
 			"ofs-delta\n",
 			"done\n",
-			PacketLineIn.END);
+				PacketLineIn.end());
 		pckIn = new PacketLineIn(recvStream);
 		assertThat(pckIn.readString(), is("packfile"));
-		stats = parsePack(recvStream);
-		assertTrue(stats.getNumOfsDelta() != 0);
+		receivedStats = parsePack(recvStream);
+		assertTrue(receivedStats.getNumOfsDelta() != 0);
 	}
 
 	@Test
@@ -1150,11 +1118,11 @@
 		// commonParent, so it doesn't send it.
 		ByteArrayInputStream recvStream = uploadPackV2(
 			"command=fetch\n",
-			PacketLineIn.DELIM,
+			PacketLineIn.delimiter(),
 			"want " + barChild.toObjectId().getName() + "\n",
 			"have " + fooChild.toObjectId().getName() + "\n",
 			"done\n",
-			PacketLineIn.END);
+				PacketLineIn.end());
 		PacketLineIn pckIn = new PacketLineIn(recvStream);
 		assertThat(pckIn.readString(), is("packfile"));
 		parsePack(recvStream);
@@ -1165,12 +1133,12 @@
 		// commonParent, so it sends it.
 		recvStream = uploadPackV2(
 			"command=fetch\n",
-			PacketLineIn.DELIM,
+			PacketLineIn.delimiter(),
 			"want " + barChild.toObjectId().getName() + "\n",
 			"have " + fooChild.toObjectId().getName() + "\n",
 			"shallow " + fooChild.toObjectId().getName() + "\n",
 			"done\n",
-			PacketLineIn.END);
+				PacketLineIn.end());
 		pckIn = new PacketLineIn(recvStream);
 		assertThat(pckIn.readString(), is("packfile"));
 		parsePack(recvStream);
@@ -1186,15 +1154,15 @@
 		// "deepen 1" sends only the child.
 		ByteArrayInputStream recvStream = uploadPackV2(
 			"command=fetch\n",
-			PacketLineIn.DELIM,
+			PacketLineIn.delimiter(),
 			"want " + child.toObjectId().getName() + "\n",
 			"deepen 1\n",
 			"done\n",
-			PacketLineIn.END);
+				PacketLineIn.end());
 		PacketLineIn pckIn = new PacketLineIn(recvStream);
 		assertThat(pckIn.readString(), is("shallow-info"));
 		assertThat(pckIn.readString(), is("shallow " + child.toObjectId().getName()));
-		assertThat(pckIn.readString(), theInstance(PacketLineIn.DELIM));
+		assertTrue(PacketLineIn.isDelimiter(pckIn.readString()));
 		assertThat(pckIn.readString(), is("packfile"));
 		parsePack(recvStream);
 		assertTrue(client.getObjectDatabase().has(child.toObjectId()));
@@ -1203,10 +1171,10 @@
 		// Without that, the parent is sent too.
 		recvStream = uploadPackV2(
 			"command=fetch\n",
-			PacketLineIn.DELIM,
+			PacketLineIn.delimiter(),
 			"want " + child.toObjectId().getName() + "\n",
 			"done\n",
-			PacketLineIn.END);
+				PacketLineIn.end());
 		pckIn = new PacketLineIn(recvStream);
 		assertThat(pckIn.readString(), is("packfile"));
 		parsePack(recvStream);
@@ -1221,10 +1189,10 @@
 
 		ByteArrayInputStream recvStream = uploadPackV2(
 			"command=fetch\n",
-			PacketLineIn.DELIM,
+			PacketLineIn.delimiter(),
 			"want " + child.toObjectId().getName() + "\n",
 			"deepen 1\n",
-			PacketLineIn.END);
+				PacketLineIn.end());
 		PacketLineIn pckIn = new PacketLineIn(recvStream);
 
 		// Verify that only the correct section is sent. "shallow-info"
@@ -1232,7 +1200,7 @@
 		// sent only if a packfile is sent.
 		assertThat(pckIn.readString(), is("acknowledgments"));
 		assertThat(pckIn.readString(), is("NAK"));
-		assertThat(pckIn.readString(), theInstance(PacketLineIn.END));
+		assertTrue(PacketLineIn.isEnd(pckIn.readString()));
 	}
 
 	@Test
@@ -1253,13 +1221,13 @@
 		// Report that we only have "boundary" as a shallow boundary.
 		ByteArrayInputStream recvStream = uploadPackV2(
 			"command=fetch\n",
-			PacketLineIn.DELIM,
+			PacketLineIn.delimiter(),
 			"shallow " + boundary.toObjectId().getName() + "\n",
 			"deepen-since 1510000\n",
 			"want " + merge.toObjectId().getName() + "\n",
 			"have " + boundary.toObjectId().getName() + "\n",
 			"done\n",
-			PacketLineIn.END);
+				PacketLineIn.end());
 		PacketLineIn pckIn = new PacketLineIn(recvStream);
 		assertThat(pckIn.readString(), is("shallow-info"));
 
@@ -1271,7 +1239,7 @@
 		// later than the given deepen-since time.
 		assertThat(pckIn.readString(), is("unshallow " + boundary.toObjectId().getName()));
 
-		assertThat(pckIn.readString(), theInstance(PacketLineIn.DELIM));
+		assertTrue(PacketLineIn.isDelimiter(pckIn.readString()));
 		assertThat(pckIn.readString(), is("packfile"));
 		parsePack(recvStream);
 
@@ -1304,12 +1272,12 @@
 
 		ByteArrayInputStream recvStream = uploadPackV2(
 			"command=fetch\n",
-			PacketLineIn.DELIM,
+			PacketLineIn.delimiter(),
 			"deepen-since 1510000\n",
 			"want " + child1.toObjectId().getName() + "\n",
 			"want " + child2.toObjectId().getName() + "\n",
 			"done\n",
-			PacketLineIn.END);
+				PacketLineIn.end());
 		PacketLineIn pckIn = new PacketLineIn(recvStream);
 		assertThat(pckIn.readString(), is("shallow-info"));
 
@@ -1320,7 +1288,7 @@
 				"shallow " + child1.toObjectId().getName(),
 				"shallow " + child2.toObjectId().getName()));
 
-		assertThat(pckIn.readString(), theInstance(PacketLineIn.DELIM));
+		assertTrue(PacketLineIn.isDelimiter(pckIn.readString()));
 		assertThat(pckIn.readString(), is("packfile"));
 		parsePack(recvStream);
 
@@ -1343,11 +1311,11 @@
 		thrown.expectMessage("No commits selected for shallow request");
 		uploadPackV2(
 			"command=fetch\n",
-			PacketLineIn.DELIM,
+			PacketLineIn.delimiter(),
 			"deepen-since 1510000\n",
 			"want " + tooOld.toObjectId().getName() + "\n",
 			"done\n",
-			PacketLineIn.END);
+				PacketLineIn.end());
 	}
 
 	@Test
@@ -1366,13 +1334,13 @@
 		// wants "merge" while excluding "side".
 		ByteArrayInputStream recvStream = uploadPackV2(
 			"command=fetch\n",
-			PacketLineIn.DELIM,
+			PacketLineIn.delimiter(),
 			"shallow " + three.toObjectId().getName() + "\n",
 			"deepen-not side\n",
 			"want " + merge.toObjectId().getName() + "\n",
 			"have " + three.toObjectId().getName() + "\n",
 			"done\n",
-			PacketLineIn.END);
+				PacketLineIn.end());
 		PacketLineIn pckIn = new PacketLineIn(recvStream);
 		assertThat(pckIn.readString(), is("shallow-info"));
 
@@ -1387,7 +1355,7 @@
 		// "three" is unshallow because its parent "two" is now available.
 		assertThat(pckIn.readString(), is("unshallow " + three.toObjectId().getName()));
 
-		assertThat(pckIn.readString(), theInstance(PacketLineIn.DELIM));
+		assertTrue(PacketLineIn.isDelimiter(pckIn.readString()));
 		assertThat(pckIn.readString(), is("packfile"));
 		parsePack(recvStream);
 
@@ -1419,11 +1387,11 @@
 		thrown.expectMessage("No commits selected for shallow request");
 		uploadPackV2(
 			"command=fetch\n",
-			PacketLineIn.DELIM,
+			PacketLineIn.delimiter(),
 			"deepen-not four\n",
 			"want " + two.toObjectId().getName() + "\n",
 			"done\n",
-			PacketLineIn.END);
+				PacketLineIn.end());
 	}
 
 	@Test
@@ -1439,15 +1407,15 @@
 
 		ByteArrayInputStream recvStream = uploadPackV2(
 			"command=fetch\n",
-			PacketLineIn.DELIM,
+			PacketLineIn.delimiter(),
 			"deepen-not twotag\n",
 			"want " + four.toObjectId().getName() + "\n",
 			"done\n",
-			PacketLineIn.END);
+				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));
+		assertTrue(PacketLineIn.isDelimiter(pckIn.readString()));
 		assertThat(pckIn.readString(), is("packfile"));
 		parsePack(recvStream);
 		assertFalse(client.getObjectDatabase().has(one.toObjectId()));
@@ -1473,12 +1441,12 @@
 
 		ByteArrayInputStream recvStream = uploadPackV2(
 			"command=fetch\n",
-			PacketLineIn.DELIM,
+			PacketLineIn.delimiter(),
 			"deepen-not base\n",
 			"want " + child1.toObjectId().getName() + "\n",
 			"want " + child2.toObjectId().getName() + "\n",
 			"done\n",
-			PacketLineIn.END);
+				PacketLineIn.end());
 		PacketLineIn pckIn = new PacketLineIn(recvStream);
 		assertThat(pckIn.readString(), is("shallow-info"));
 
@@ -1489,7 +1457,7 @@
 				"shallow " + child1.toObjectId().getName(),
 				"shallow " + child2.toObjectId().getName()));
 
-		assertThat(pckIn.readString(), theInstance(PacketLineIn.DELIM));
+		assertTrue(PacketLineIn.isDelimiter(pckIn.readString()));
 		assertThat(pckIn.readString(), is("packfile"));
 		parsePack(recvStream);
 
@@ -1505,16 +1473,16 @@
 		thrown.expectMessage("unexpected invalid-argument");
 		uploadPackV2(
 			"command=fetch\n",
-			PacketLineIn.DELIM,
+			PacketLineIn.delimiter(),
 			"invalid-argument\n",
-			PacketLineIn.END);
+				PacketLineIn.end());
 	}
 
 	@Test
 	public void testV2FetchServerOptions() throws Exception {
 		String[] lines = { "command=fetch\n", "server-option=one\n",
-				"server-option=two\n", PacketLineIn.DELIM,
-				PacketLineIn.END };
+				"server-option=two\n", PacketLineIn.delimiter(),
+				PacketLineIn.end() };
 
 		TestV2Hook testHook = new TestV2Hook();
 		uploadPackV2Setup(null, null, testHook, lines);
@@ -1538,11 +1506,11 @@
 
 		ByteArrayInputStream recvStream = uploadPackV2(
 			"command=fetch\n",
-			PacketLineIn.DELIM,
+			PacketLineIn.delimiter(),
 			"want " + commit.toObjectId().getName() + "\n",
 			"filter blob:limit=5\n",
 			"done\n",
-			PacketLineIn.END);
+				PacketLineIn.end());
 		PacketLineIn pckIn = new PacketLineIn(recvStream);
 		assertThat(pckIn.readString(), is("packfile"));
 		parsePack(recvStream);
@@ -1551,6 +1519,324 @@
 		assertTrue(client.getObjectDatabase().has(small.toObjectId()));
 	}
 
+	abstract class TreeBuilder {
+		abstract void addElements(DirCacheBuilder dcBuilder) throws Exception;
+
+		RevTree build() throws Exception {
+			DirCache dc = DirCache.newInCore();
+			DirCacheBuilder dcBuilder = dc.builder();
+			addElements(dcBuilder);
+			dcBuilder.finish();
+			ObjectId id;
+			try (ObjectInserter ins =
+					remote.getRepository().newObjectInserter()) {
+				id = dc.writeTree(ins);
+				ins.flush();
+			}
+			return remote.getRevWalk().parseTree(id);
+		}
+	}
+
+	class DeepTreePreparator {
+		RevBlob blobLowDepth = remote.blob("lo");
+		RevBlob blobHighDepth = remote.blob("hi");
+
+		RevTree subtree = remote.tree(remote.file("1", blobHighDepth));
+		RevTree rootTree = (new TreeBuilder() {
+				@Override
+				void addElements(DirCacheBuilder dcBuilder) throws Exception {
+					dcBuilder.add(remote.file("1", blobLowDepth));
+					dcBuilder.addTree(new byte[] {'2'}, DirCacheEntry.STAGE_0,
+							remote.getRevWalk().getObjectReader(), subtree);
+				}
+			}).build();
+		RevCommit commit = remote.commit(rootTree);
+
+		DeepTreePreparator() throws Exception {}
+	}
+
+	private void uploadV2WithTreeDepthFilter(
+			long depth, ObjectId... wants) throws Exception {
+		server.getConfig().setBoolean("uploadpack", null, "allowfilter", true);
+
+		List<String> input = new ArrayList<>();
+		input.add("command=fetch\n");
+		input.add(PacketLineIn.delimiter());
+		for (ObjectId want : wants) {
+			input.add("want " + want.getName() + "\n");
+		}
+		input.add("filter tree:" + depth + "\n");
+		input.add("done\n");
+		input.add(PacketLineIn.end());
+		ByteArrayInputStream recvStream =
+				uploadPackV2(RequestPolicy.ANY, /*refFilter=*/null,
+							 /*hook=*/null, input.toArray(new String[0]));
+		PacketLineIn pckIn = new PacketLineIn(recvStream);
+		assertThat(pckIn.readString(), is("packfile"));
+		parsePack(recvStream);
+	}
+
+	@Test
+	public void testV2FetchFilterTreeDepth0() throws Exception {
+		DeepTreePreparator preparator = new DeepTreePreparator();
+		remote.update("master", preparator.commit);
+
+		uploadV2WithTreeDepthFilter(0, preparator.commit.toObjectId());
+
+		assertFalse(client.getObjectDatabase()
+				.has(preparator.rootTree.toObjectId()));
+		assertFalse(client.getObjectDatabase()
+				.has(preparator.subtree.toObjectId()));
+		assertFalse(client.getObjectDatabase()
+				.has(preparator.blobLowDepth.toObjectId()));
+		assertFalse(client.getObjectDatabase()
+				.has(preparator.blobHighDepth.toObjectId()));
+		assertEquals(1, stats.getTreesTraversed());
+	}
+
+	@Test
+	public void testV2FetchFilterTreeDepth1_serverHasBitmap() throws Exception {
+		DeepTreePreparator preparator = new DeepTreePreparator();
+		remote.update("master", preparator.commit);
+
+		// The bitmap should be ignored since we need to track the depth while
+		// traversing the trees.
+		generateBitmaps(server);
+
+		uploadV2WithTreeDepthFilter(1, preparator.commit.toObjectId());
+
+		assertTrue(client.getObjectDatabase()
+				.has(preparator.rootTree.toObjectId()));
+		assertFalse(client.getObjectDatabase()
+				.has(preparator.subtree.toObjectId()));
+		assertFalse(client.getObjectDatabase()
+				.has(preparator.blobLowDepth.toObjectId()));
+		assertFalse(client.getObjectDatabase()
+				.has(preparator.blobHighDepth.toObjectId()));
+		assertEquals(1, stats.getTreesTraversed());
+	}
+
+	@Test
+	public void testV2FetchFilterTreeDepth2() throws Exception {
+		DeepTreePreparator preparator = new DeepTreePreparator();
+		remote.update("master", preparator.commit);
+
+		uploadV2WithTreeDepthFilter(2, preparator.commit.toObjectId());
+
+		assertTrue(client.getObjectDatabase()
+				.has(preparator.rootTree.toObjectId()));
+		assertTrue(client.getObjectDatabase()
+				.has(preparator.subtree.toObjectId()));
+		assertTrue(client.getObjectDatabase()
+				.has(preparator.blobLowDepth.toObjectId()));
+		assertFalse(client.getObjectDatabase()
+				.has(preparator.blobHighDepth.toObjectId()));
+		assertEquals(2, stats.getTreesTraversed());
+	}
+
+	/**
+	 * Creates a commit with the following files:
+	 * <pre>
+	 * a/x/b/foo
+	 * x/b/foo
+	 * </pre>
+	 * which has an identical tree in two locations: once at / and once at /a
+	 */
+	class RepeatedSubtreePreparator {
+		RevBlob foo = remote.blob("foo");
+		RevTree subtree3 = remote.tree(remote.file("foo", foo));
+		RevTree subtree2 = (new TreeBuilder() {
+			@Override
+			void addElements(DirCacheBuilder dcBuilder) throws Exception {
+				dcBuilder.addTree(new byte[] {'b'}, DirCacheEntry.STAGE_0,
+						remote.getRevWalk().getObjectReader(), subtree3);
+			}
+		}).build();
+		RevTree subtree1 = (new TreeBuilder() {
+			@Override
+			void addElements(DirCacheBuilder dcBuilder) throws Exception {
+				dcBuilder.addTree(new byte[] {'x'}, DirCacheEntry.STAGE_0,
+						remote.getRevWalk().getObjectReader(), subtree2);
+			}
+		}).build();
+		RevTree rootTree = (new TreeBuilder() {
+			@Override
+			void addElements(DirCacheBuilder dcBuilder) throws Exception {
+				dcBuilder.addTree(new byte[] {'a'}, DirCacheEntry.STAGE_0,
+						remote.getRevWalk().getObjectReader(), subtree1);
+				dcBuilder.addTree(new byte[] {'x'}, DirCacheEntry.STAGE_0,
+						remote.getRevWalk().getObjectReader(), subtree2);
+			}
+		}).build();
+		RevCommit commit = remote.commit(rootTree);
+
+		RepeatedSubtreePreparator() throws Exception {}
+	}
+
+	@Test
+	public void testV2FetchFilterTreeDepth_iterateOverTreeAtTwoLevels()
+			throws Exception {
+		// Test tree:<depth> where a tree is iterated to twice - once where a
+		// subentry is too deep to be included, and again where the blob inside
+		// it is shallow enough to be included.
+		RepeatedSubtreePreparator preparator = new RepeatedSubtreePreparator();
+		remote.update("master", preparator.commit);
+
+		uploadV2WithTreeDepthFilter(4, preparator.commit.toObjectId());
+
+		assertTrue(client.getObjectDatabase()
+				.has(preparator.foo.toObjectId()));
+	}
+
+	/**
+	 * Creates a commit with the following files:
+	 * <pre>
+	 * a/x/b/foo
+	 * b/u/c/baz
+	 * y/x/b/foo
+	 * z/v/c/baz
+	 * </pre>
+	 * which has two pairs of identical trees:
+	 * <ul>
+	 * <li>one at /a and /y
+	 * <li>one at /b/u and /z/v
+	 * </ul>
+	 * Note that this class defines unique 8 trees (rootTree and subtree1-7)
+	 * which means PackStatistics should report having visited 8 trees.
+	 */
+	class RepeatedSubtreeAtSameLevelPreparator {
+		RevBlob foo = remote.blob("foo");
+
+		/** foo */
+		RevTree subtree1 = remote.tree(remote.file("foo", foo));
+
+		/** b/foo */
+		RevTree subtree2 = (new TreeBuilder() {
+			@Override
+			void addElements(DirCacheBuilder dcBuilder) throws Exception {
+				dcBuilder.addTree(new byte[] {'b'}, DirCacheEntry.STAGE_0,
+						remote.getRevWalk().getObjectReader(), subtree1);
+			}
+		}).build();
+
+		/** x/b/foo */
+		RevTree subtree3 = (new TreeBuilder() {
+			@Override
+			void addElements(DirCacheBuilder dcBuilder) throws Exception {
+				dcBuilder.addTree(new byte[] {'x'}, DirCacheEntry.STAGE_0,
+						remote.getRevWalk().getObjectReader(), subtree2);
+			}
+		}).build();
+
+		RevBlob baz = remote.blob("baz");
+
+		/** baz */
+		RevTree subtree4 = remote.tree(remote.file("baz", baz));
+
+		/** c/baz */
+		RevTree subtree5 = (new TreeBuilder() {
+			@Override
+			void addElements(DirCacheBuilder dcBuilder) throws Exception {
+				dcBuilder.addTree(new byte[] {'c'}, DirCacheEntry.STAGE_0,
+						remote.getRevWalk().getObjectReader(), subtree4);
+			}
+		}).build();
+
+		/** u/c/baz */
+		RevTree subtree6 = (new TreeBuilder() {
+			@Override
+			void addElements(DirCacheBuilder dcBuilder) throws Exception {
+				dcBuilder.addTree(new byte[] {'u'}, DirCacheEntry.STAGE_0,
+						remote.getRevWalk().getObjectReader(), subtree5);
+			}
+		}).build();
+
+		/** v/c/baz */
+		RevTree subtree7 = (new TreeBuilder() {
+			@Override
+			void addElements(DirCacheBuilder dcBuilder) throws Exception {
+				dcBuilder.addTree(new byte[] {'v'}, DirCacheEntry.STAGE_0,
+						remote.getRevWalk().getObjectReader(), subtree5);
+			}
+		}).build();
+
+		RevTree rootTree = (new TreeBuilder() {
+			@Override
+			void addElements(DirCacheBuilder dcBuilder) throws Exception {
+				dcBuilder.addTree(new byte[] {'a'}, DirCacheEntry.STAGE_0,
+						remote.getRevWalk().getObjectReader(), subtree3);
+				dcBuilder.addTree(new byte[] {'b'}, DirCacheEntry.STAGE_0,
+						remote.getRevWalk().getObjectReader(), subtree6);
+				dcBuilder.addTree(new byte[] {'y'}, DirCacheEntry.STAGE_0,
+						remote.getRevWalk().getObjectReader(), subtree3);
+				dcBuilder.addTree(new byte[] {'z'}, DirCacheEntry.STAGE_0,
+						remote.getRevWalk().getObjectReader(), subtree7);
+			}
+		}).build();
+		RevCommit commit = remote.commit(rootTree);
+
+		RepeatedSubtreeAtSameLevelPreparator() throws Exception {}
+	}
+
+	@Test
+	public void testV2FetchFilterTreeDepth_repeatTreeAtSameLevelIncludeFile()
+			throws Exception {
+		RepeatedSubtreeAtSameLevelPreparator preparator =
+				new RepeatedSubtreeAtSameLevelPreparator();
+		remote.update("master", preparator.commit);
+
+		uploadV2WithTreeDepthFilter(5, preparator.commit.toObjectId());
+
+		assertTrue(client.getObjectDatabase()
+				.has(preparator.foo.toObjectId()));
+		assertTrue(client.getObjectDatabase()
+				.has(preparator.baz.toObjectId()));
+		assertEquals(8, stats.getTreesTraversed());
+	}
+
+	@Test
+	public void testV2FetchFilterTreeDepth_repeatTreeAtSameLevelExcludeFile()
+			throws Exception {
+		RepeatedSubtreeAtSameLevelPreparator preparator =
+				new RepeatedSubtreeAtSameLevelPreparator();
+		remote.update("master", preparator.commit);
+
+		uploadV2WithTreeDepthFilter(4, preparator.commit.toObjectId());
+
+		assertFalse(client.getObjectDatabase()
+				.has(preparator.foo.toObjectId()));
+		assertFalse(client.getObjectDatabase()
+				.has(preparator.baz.toObjectId()));
+		assertEquals(8, stats.getTreesTraversed());
+	}
+
+	@Test
+	public void testWantFilteredObject() throws Exception {
+		RepeatedSubtreePreparator preparator = new RepeatedSubtreePreparator();
+		remote.update("master", preparator.commit);
+
+		// Specify wanted blob objects that are deep enough to be filtered. We
+		// should still upload them.
+		uploadV2WithTreeDepthFilter(
+				3,
+				preparator.commit.toObjectId(),
+				preparator.foo.toObjectId());
+		assertTrue(client.getObjectDatabase()
+				.has(preparator.foo.toObjectId()));
+
+		client = newRepo("client");
+		// Specify a wanted tree object that is deep enough to be filtered. We
+		// should still upload it.
+		uploadV2WithTreeDepthFilter(
+				2,
+				preparator.commit.toObjectId(),
+				preparator.subtree3.toObjectId());
+		assertTrue(client.getObjectDatabase()
+				.has(preparator.foo.toObjectId()));
+		assertTrue(client.getObjectDatabase()
+				.has(preparator.subtree3.toObjectId()));
+	}
+
 	@Test
 	public void testV2FetchFilterWhenNotAllowed() throws Exception {
 		RevCommit commit = remote.commit().message("0").create();
@@ -1562,11 +1848,11 @@
 		thrown.expectMessage("unexpected filter blob:limit=5");
 		uploadPackV2(
 			"command=fetch\n",
-			PacketLineIn.DELIM,
+			PacketLineIn.delimiter(),
 			"want " + commit.toObjectId().getName() + "\n",
 			"filter blob:limit=5\n",
 			"done\n",
-			PacketLineIn.END);
+				PacketLineIn.end());
 	}
 
 	@Test
@@ -1577,10 +1863,10 @@
 		try {
 			uploadPackV2(
 				"command=fetch\n",
-				PacketLineIn.DELIM,
+				PacketLineIn.delimiter(),
 				"want-ref refs/heads/one\n",
 				"done\n",
-				PacketLineIn.END);
+					PacketLineIn.end());
 		} catch (PackProtocolException e) {
 			assertThat(
 				e.getMessage(),
@@ -1603,11 +1889,11 @@
 
 		ByteArrayInputStream recvStream = uploadPackV2(
 			"command=fetch\n",
-			PacketLineIn.DELIM,
+			PacketLineIn.delimiter(),
 			"want-ref refs/heads/one\n",
 			"want-ref refs/heads/two\n",
 			"done\n",
-			PacketLineIn.END);
+				PacketLineIn.end());
 		PacketLineIn pckIn = new PacketLineIn(recvStream);
 		assertThat(pckIn.readString(), is("wanted-refs"));
 		assertThat(
@@ -1615,7 +1901,7 @@
 				hasItems(
 					one.toObjectId().getName() + " refs/heads/one",
 					two.toObjectId().getName() + " refs/heads/two"));
-		assertThat(pckIn.readString(), theInstance(PacketLineIn.DELIM));
+		assertTrue(PacketLineIn.isDelimiter(pckIn.readString()));
 		assertThat(pckIn.readString(), is("packfile"));
 		parsePack(recvStream);
 
@@ -1634,11 +1920,11 @@
 		try {
 			uploadPackV2(
 				"command=fetch\n",
-				PacketLineIn.DELIM,
+				PacketLineIn.delimiter(),
 				"want-ref refs/heads/one\n",
 				"want-ref refs/heads/nonExistentRef\n",
 				"done\n",
-				PacketLineIn.END);
+					PacketLineIn.end());
 		} catch (PackProtocolException e) {
 			assertThat(
 				e.getMessage(),
@@ -1661,17 +1947,17 @@
 
 		ByteArrayInputStream recvStream = uploadPackV2(
 			"command=fetch\n",
-			PacketLineIn.DELIM,
+			PacketLineIn.delimiter(),
 			"want-ref refs/heads/one\n",
 			"want " + two.toObjectId().getName() + "\n",
 			"done\n",
-			PacketLineIn.END);
+				PacketLineIn.end());
 		PacketLineIn pckIn = new PacketLineIn(recvStream);
 		assertThat(pckIn.readString(), is("wanted-refs"));
 		assertThat(
 				pckIn.readString(),
 				is(one.toObjectId().getName() + " refs/heads/one"));
-		assertThat(pckIn.readString(), theInstance(PacketLineIn.DELIM));
+		assertTrue(PacketLineIn.isDelimiter(pckIn.readString()));
 		assertThat(pckIn.readString(), is("packfile"));
 		parsePack(recvStream);
 
@@ -1689,11 +1975,11 @@
 
 		ByteArrayInputStream recvStream = uploadPackV2(
 			"command=fetch\n",
-			PacketLineIn.DELIM,
+			PacketLineIn.delimiter(),
 			"want-ref refs/heads/one\n",
 			"have " + one.toObjectId().getName(),
 			"done\n",
-			PacketLineIn.END);
+				PacketLineIn.end());
 		PacketLineIn pckIn = new PacketLineIn(recvStream);
 
 		// The client still needs to know the hash of the object that
@@ -1703,7 +1989,7 @@
 		assertThat(
 				pckIn.readString(),
 				is(one.toObjectId().getName() + " refs/heads/one"));
-		assertThat(pckIn.readString(), theInstance(PacketLineIn.DELIM));
+		assertTrue(PacketLineIn.isDelimiter(pckIn.readString()));
 
 		// ... but the client does not need the object itself.
 		assertThat(pckIn.readString(), is("packfile"));
@@ -1721,20 +2007,20 @@
 
 		ByteArrayInputStream recvStream = uploadPackV2(
 			"command=fetch\n",
-			PacketLineIn.DELIM,
+			PacketLineIn.delimiter(),
 			"want-ref refs/heads/branch1\n",
 			"deepen 1\n",
 			"done\n",
-			PacketLineIn.END);
+				PacketLineIn.end());
 		PacketLineIn pckIn = new PacketLineIn(recvStream);
 
 		// shallow-info appears first, then wanted-refs.
 		assertThat(pckIn.readString(), is("shallow-info"));
 		assertThat(pckIn.readString(), is("shallow " + child.toObjectId().getName()));
-		assertThat(pckIn.readString(), theInstance(PacketLineIn.DELIM));
+		assertTrue(PacketLineIn.isDelimiter(pckIn.readString()));
 		assertThat(pckIn.readString(), is("wanted-refs"));
 		assertThat(pckIn.readString(), is(child.toObjectId().getName() + " refs/heads/branch1"));
-		assertThat(pckIn.readString(), theInstance(PacketLineIn.DELIM));
+		assertTrue(PacketLineIn.isDelimiter(pckIn.readString()));
 		assertThat(pckIn.readString(), is("packfile"));
 		parsePack(recvStream);
 		assertTrue(client.getObjectDatabase().has(child.toObjectId()));
@@ -1752,13 +2038,13 @@
 				true);
 
 		ByteArrayInputStream recvStream = uploadPackV2("command=fetch\n",
-				PacketLineIn.DELIM,
+				PacketLineIn.delimiter(),
 				"want-ref refs/heads/three\n",
 				"deepen 3",
 				"shallow 0123012301230123012301230123012301230123",
 				"shallow " + two.getName() + '\n',
 				"done\n",
-				PacketLineIn.END);
+				PacketLineIn.end());
 		PacketLineIn pckIn = new PacketLineIn(recvStream);
 
 		assertThat(pckIn.readString(), is("shallow-info"));
@@ -1766,11 +2052,11 @@
 				is("shallow " + one.toObjectId().getName()));
 		assertThat(pckIn.readString(),
 				is("unshallow " + two.toObjectId().getName()));
-		assertThat(pckIn.readString(), theInstance(PacketLineIn.DELIM));
+		assertTrue(PacketLineIn.isDelimiter(pckIn.readString()));
 		assertThat(pckIn.readString(), is("wanted-refs"));
 		assertThat(pckIn.readString(),
 				is(three.toObjectId().getName() + " refs/heads/three"));
-		assertThat(pckIn.readString(), theInstance(PacketLineIn.DELIM));
+		assertTrue(PacketLineIn.isDelimiter(pckIn.readString()));
 		assertThat(pckIn.readString(), is("packfile"));
 		parsePack(recvStream);
 
@@ -1787,7 +2073,7 @@
 		UploadPack up = new UploadPack(server);
 		ByteArrayInputStream send = linesAsInputStream(
 				"want " + one.getName() + " agent=JGit-test/1.2.3\n",
-				PacketLineIn.END,
+				PacketLineIn.end(),
 				"have 11cedf1b796d44207da702f7d420684022fc0f09\n", "done\n");
 
 		ByteArrayOutputStream recv = new ByteArrayOutputStream();
@@ -1808,9 +2094,9 @@
 
 		ByteArrayInputStream send = linesAsInputStream(
 				"command=fetch\n", "agent=JGit-test/1.2.4\n",
-				PacketLineIn.DELIM, "want " + one.getName() + "\n",
+				PacketLineIn.delimiter(), "want " + one.getName() + "\n",
 				"have 11cedf1b796d44207da702f7d420684022fc0f09\n", "done\n",
-				PacketLineIn.END);
+				PacketLineIn.end());
 
 		ByteArrayOutputStream recv = new ByteArrayOutputStream();
 		up.upload(send, recv, null);
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/FileTreeIteratorTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/FileTreeIteratorTest.java
index 754341d..a3ce4ae 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/FileTreeIteratorTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/FileTreeIteratorTest.java
@@ -584,26 +584,23 @@
 		}
 	}
 
-	private final FileTreeIterator.FileModeStrategy NO_GITLINKS_STRATEGY =
-			new FileTreeIterator.FileModeStrategy() {
-				@Override
-				public FileMode getMode(File f, FS.Attributes attributes) {
-					if (attributes.isSymbolicLink()) {
-						return FileMode.SYMLINK;
-					} else if (attributes.isDirectory()) {
-						// NOTE: in the production DefaultFileModeStrategy, there is
-						// a check here for a subdirectory called '.git', and if it
-						// exists, we create a GITLINK instead of recursing into the
-						// tree.  In this custom strategy, we ignore nested git dirs
-						// and treat all directories the same.
-						return FileMode.TREE;
-					} else if (attributes.isExecutable()) {
-						return FileMode.EXECUTABLE_FILE;
-					} else {
-						return FileMode.REGULAR_FILE;
-					}
-				}
-			};
+	private final FileTreeIterator.FileModeStrategy NO_GITLINKS_STRATEGY = (
+			File f, FS.Attributes attributes) -> {
+		if (attributes.isSymbolicLink()) {
+			return FileMode.SYMLINK;
+		} else if (attributes.isDirectory()) {
+			// NOTE: in the production DefaultFileModeStrategy, there is
+			// a check here for a subdirectory called '.git', and if it
+			// exists, we create a GITLINK instead of recursing into the
+			// tree. In this custom strategy, we ignore nested git dirs
+			// and treat all directories the same.
+			return FileMode.TREE;
+		} else if (attributes.isExecutable()) {
+			return FileMode.EXECUTABLE_FILE;
+		} else {
+			return FileMode.REGULAR_FILE;
+		}
+	};
 
 	private Repository createNestedRepo() throws IOException {
 		File gitdir = createUniqueTestGitDir(false);
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FileUtilsTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FileUtilsTest.java
index c0f4965..9ee9613 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FileUtilsTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FileUtilsTest.java
@@ -137,7 +137,6 @@
 	}
 
 	@Test
-
 	public void testDeleteRecursiveEmpty() throws IOException {
 		File f1 = new File(trash, "test/test/a");
 		File f2 = new File(trash, "test/a");
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/LRUMapTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/LRUMapTest.java
new file mode 100644
index 0000000..da59533
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/LRUMapTest.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2018, Konrad Windszus <konrad_w@gmx.de>
+ * 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.util;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import org.hamcrest.collection.IsIterableContainingInOrder;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class LRUMapTest {
+
+	@SuppressWarnings("boxing")
+	@Test
+	public void testLRUEntriesAreEvicted() {
+		Map<Integer, Integer> map = new LRUMap<>(3, 3);
+		for (int i = 0; i < 3; i++) {
+			map.put(i, i);
+		}
+		// access the last ones
+		map.get(2);
+		map.get(0);
+
+		// put another one which exceeds the limit (entry with key "1" is
+		// evicted)
+		map.put(3, 3);
+
+		Map<Integer, Integer> expectedMap = new LinkedHashMap<>();
+		expectedMap.put(2, 2);
+		expectedMap.put(0, 0);
+		expectedMap.put(3, 3);
+
+		Assert.assertThat(map.entrySet(),
+				IsIterableContainingInOrder
+						.contains(expectedMap.entrySet().toArray()));
+	}
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RefListTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RefListTest.java
index d124d73..9981bd6 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RefListTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RefListTest.java
@@ -128,6 +128,41 @@
 	}
 
 	@Test
+	public void testBuilder_AddThenDedupe() {
+		RefList.Builder<Ref> builder = new RefList.Builder<>(1);
+		builder.add(REF_B);
+		builder.add(REF_A);
+		builder.add(REF_A);
+		builder.add(REF_B);
+		builder.add(REF_c);
+
+		builder.sort();
+		builder.dedupe((a, b) -> b);
+		RefList<Ref> list = builder.toRefList();
+
+		assertEquals(3, list.size());
+		assertSame(REF_A, list.get(0));
+		assertSame(REF_B, list.get(1));
+		assertSame(REF_c, list.get(2));
+	}
+
+	@Test
+	public void testBuilder_AddThenDedupe_Border() {
+		RefList.Builder<Ref> builder = new RefList.Builder<>(1);
+		builder.sort();
+		builder.dedupe((a, b) -> b);
+		RefList<Ref> list = builder.toRefList();
+		assertTrue(list.isEmpty());
+
+		builder = new RefList.Builder<>(1);
+		builder.add(REF_A);
+		builder.sort();
+		builder.dedupe((a, b) -> b);
+		list = builder.toRefList();
+		assertEquals(1, list.size());
+	}
+
+	@Test
 	public void testBuilder_AddAll() {
 		RefList.Builder<Ref> builder = new RefList.Builder<>(1);
 		Ref[] src = { REF_A, REF_B, REF_c, REF_A };
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/http/HttpCookiesMatcher.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/http/HttpCookiesMatcher.java
new file mode 100644
index 0000000..8c5b127
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/http/HttpCookiesMatcher.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2018, Konrad Windszus <konrad_w@gmx.de>
+ * 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.util.http;
+
+import java.net.HttpCookie;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.hamcrest.Description;
+import org.hamcrest.Matcher;
+import org.hamcrest.TypeSafeMatcher;
+import org.hamcrest.collection.IsIterableContainingInOrder;
+
+public final class HttpCookiesMatcher {
+	public static Matcher<Iterable<? extends HttpCookie>> containsInOrder(
+			Iterable<HttpCookie> expectedCookies) {
+		return containsInOrder(expectedCookies, 0);
+	}
+
+	public static Matcher<Iterable<? extends HttpCookie>> containsInOrder(
+			Iterable<HttpCookie> expectedCookies, int allowedMaxAgeDelta) {
+		final List<Matcher<? super HttpCookie>> cookieMatchers = new LinkedList<>();
+		for (HttpCookie cookie : expectedCookies) {
+			cookieMatchers
+					.add(new HttpCookieMatcher(cookie, allowedMaxAgeDelta));
+		}
+		return new IsIterableContainingInOrder<>(cookieMatchers);
+	}
+
+	/**
+	 * The default {@link HttpCookie#equals(Object)} is not good enough for
+	 * testing purposes. Also the {@link HttpCookie#toString()} only emits some
+	 * of the cookie attributes. For testing a dedicated matcher is needed which
+	 * takes into account all attributes.
+	 */
+	private static final class HttpCookieMatcher
+			extends TypeSafeMatcher<HttpCookie> {
+
+		private final HttpCookie cookie;
+
+		private final int allowedMaxAgeDelta;
+
+		public HttpCookieMatcher(HttpCookie cookie, int allowedMaxAgeDelta) {
+			this.cookie = cookie;
+			this.allowedMaxAgeDelta = allowedMaxAgeDelta;
+		}
+
+		@Override
+		public void describeTo(Description description) {
+			describeCookie(description, cookie);
+		}
+
+		@Override
+		protected void describeMismatchSafely(HttpCookie item,
+				Description mismatchDescription) {
+			mismatchDescription.appendText("was ");
+			describeCookie(mismatchDescription, item);
+		}
+
+		@Override
+		protected boolean matchesSafely(HttpCookie otherCookie) {
+			// the equals method in HttpCookie is not specific enough, we want
+			// to consider all attributes!
+			return (equals(cookie.getName(), otherCookie.getName())
+					&& equals(cookie.getValue(), otherCookie.getValue())
+					&& equals(cookie.getDomain(), otherCookie.getDomain())
+					&& equals(cookie.getPath(), otherCookie.getPath())
+					&& (cookie.getMaxAge() >= otherCookie.getMaxAge()
+							- allowedMaxAgeDelta)
+					&& (cookie.getMaxAge() <= otherCookie.getMaxAge()
+							+ allowedMaxAgeDelta)
+					&& cookie.isHttpOnly() == otherCookie.isHttpOnly()
+					&& cookie.getSecure() == otherCookie.getSecure()
+					&& cookie.getVersion() == otherCookie.getVersion());
+		}
+
+		private static boolean equals(String value1, String value2) {
+			if (value1 == null && value2 == null) {
+				return true;
+			}
+			if (value1 == null || value2 == null) {
+				return false;
+			}
+			return value1.equals(value2);
+		}
+
+		@SuppressWarnings("boxing")
+		protected static void describeCookie(Description description,
+				HttpCookie cookie) {
+			description.appendText("HttpCookie[");
+			description.appendText("name: ").appendValue(cookie.getName())
+					.appendText(", ");
+			description.appendText("value: ").appendValue(cookie.getValue())
+					.appendText(", ");
+			description.appendText("domain: ").appendValue(cookie.getDomain())
+					.appendText(", ");
+			description.appendText("path: ").appendValue(cookie.getPath())
+					.appendText(", ");
+			description.appendText("maxAge: ").appendValue(cookie.getMaxAge())
+					.appendText(", ");
+			description.appendText("httpOnly: ")
+					.appendValue(cookie.isHttpOnly()).appendText(", ");
+			description.appendText("secure: ").appendValue(cookie.getSecure())
+					.appendText(", ");
+			description.appendText("version: ").appendValue(cookie.getVersion())
+					.appendText(", ");
+			description.appendText("]");
+		}
+	}
+}
\ No newline at end of file
diff --git a/org.eclipse.jgit.ui/META-INF/MANIFEST.MF b/org.eclipse.jgit.ui/META-INF/MANIFEST.MF
index 72a0aaf..8103933 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.3.10.qualifier
+Bundle-Version: 5.4.4.qualifier
 Bundle-Vendor: %provider_name
 Bundle-RequiredExecutionEnvironment: JavaSE-1.8
-Export-Package: org.eclipse.jgit.awtui;version="5.3.10"
-Import-Package: org.eclipse.jgit.errors;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.lib;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.nls;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.revplot;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.revwalk;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.transport;version="[5.3.10,5.4.0)",
- org.eclipse.jgit.util;version="[5.3.10,5.4.0)"
+Export-Package: org.eclipse.jgit.awtui;version="5.4.4"
+Import-Package: org.eclipse.jgit.errors;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.lib;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.nls;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.revplot;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.revwalk;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.transport;version="[5.4.4,5.5.0)",
+ org.eclipse.jgit.util;version="[5.4.4,5.5.0)"
diff --git a/org.eclipse.jgit.ui/META-INF/SOURCE-MANIFEST.MF b/org.eclipse.jgit.ui/META-INF/SOURCE-MANIFEST.MF
new file mode 100644
index 0000000..e36062d
--- /dev/null
+++ b/org.eclipse.jgit.ui/META-INF/SOURCE-MANIFEST.MF
@@ -0,0 +1,7 @@
+Manifest-Version: 1.0
+Bundle-ManifestVersion: 2
+Bundle-Name: org.eclipse.jgit.ui - Sources
+Bundle-SymbolicName: org.eclipse.jgit.ui.source
+Bundle-Vendor: Eclipse.org - JGit
+Bundle-Version: 5.4.4.qualifier
+Eclipse-SourceBundle: org.eclipse.jgit.ui;version="5.4.4.qualifier";roots="."
diff --git a/org.eclipse.jgit.ui/pom.xml b/org.eclipse.jgit.ui/pom.xml
index c6ccafc..a4200fd 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.3.10-SNAPSHOT</version>
+    <version>5.4.4-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.ui</artifactId>
@@ -64,6 +64,7 @@
 
   <properties>
     <translate-qualifier/>
+    <source-bundle-manifest>${project.build.directory}/META-INF/SOURCE-MANIFEST.MF</source-bundle-manifest>
   </properties>
 
   <dependencies>
@@ -92,6 +93,48 @@
 
     <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>
diff --git a/org.eclipse.jgit.ui/src/org/eclipse/jgit/awtui/CommitGraphPane.java b/org.eclipse.jgit.ui/src/org/eclipse/jgit/awtui/CommitGraphPane.java
index 943a325..b898caf 100644
--- a/org.eclipse.jgit.ui/src/org/eclipse/jgit/awtui/CommitGraphPane.java
+++ b/org.eclipse.jgit.ui/src/org/eclipse/jgit/awtui/CommitGraphPane.java
@@ -64,6 +64,7 @@
 import org.eclipse.jgit.lib.PersonIdent;
 import org.eclipse.jgit.revplot.PlotCommit;
 import org.eclipse.jgit.revplot.PlotCommitList;
+import org.eclipse.jgit.util.References;
 
 /**
  * Draws a commit graph in a JTable.
@@ -176,7 +177,7 @@
 		}
 
 		PersonIdent authorFor(PlotCommit<SwingLane> c) {
-			if (c != lastCommit) {
+			if (!References.isSameObject(c, lastCommit)) {
 				lastCommit = c;
 				lastAuthor = c.getAuthorIdent();
 			}
diff --git a/org.eclipse.jgit/.settings/.api_filters b/org.eclipse.jgit/.settings/.api_filters
index a0ee918..574ed4d 100644
--- a/org.eclipse.jgit/.settings/.api_filters
+++ b/org.eclipse.jgit/.settings/.api_filters
@@ -90,33 +90,27 @@
             </message_arguments>
         </filter>
     </resource>
-    <resource path="src/org/eclipse/jgit/lib/ObjectIdRef.java" type="org.eclipse.jgit.lib.ObjectIdRef">
-        <filter id="338722907">
-            <message_arguments>
-                <message_argument value="org.eclipse.jgit.lib.ObjectIdRef"/>
-                <message_argument value="ObjectIdRef(Ref.Storage, String, ObjectId)"/>
-            </message_arguments>
-        </filter>
-    </resource>
     <resource path="src/org/eclipse/jgit/lib/Ref.java" type="org.eclipse.jgit.lib.Ref">
-        <filter id="404000815">
+        <filter id="403767336">
             <message_arguments>
                 <message_argument value="org.eclipse.jgit.lib.Ref"/>
-                <message_argument value="getUpdateIndex()"/>
+                <message_argument value="UNDEFINED_UPDATE_INDEX"/>
             </message_arguments>
         </filter>
     </resource>
-    <resource path="src/org/eclipse/jgit/lib/RefDatabase.java" type="org.eclipse.jgit.lib.RefDatabase">
-        <filter id="421650549">
+    <resource path="src/org/eclipse/jgit/lib/Repository.java" type="org.eclipse.jgit.lib.Repository">
+        <filter id="336695337">
             <message_arguments>
-                <message_argument value="org.eclipse.jgit.lib.RefDatabase"/>
-                <message_argument value="exactRef(String)"/>
+                <message_argument value="org.eclipse.jgit.lib.Repository"/>
+                <message_argument value="getIdentifier()"/>
             </message_arguments>
         </filter>
-        <filter id="421654647">
+    </resource>
+    <resource path="src/org/eclipse/jgit/revwalk/ObjectWalk.java" type="org.eclipse.jgit.revwalk.ObjectWalk">
+        <filter id="336658481">
             <message_arguments>
-                <message_argument value="org.eclipse.jgit.lib.RefDatabase"/>
-                <message_argument value="getRef(String)"/>
+                <message_argument value="org.eclipse.jgit.revwalk.ObjectWalk"/>
+                <message_argument value="SIMPLE_VISITATION_POLICY"/>
             </message_arguments>
         </filter>
     </resource>
@@ -199,55 +193,61 @@
             </message_arguments>
         </filter>
     </resource>
-    <resource path="src/org/eclipse/jgit/transport/BaseReceivePack.java" type="org.eclipse.jgit.transport.BaseReceivePack">
-        <filter id="421650549">
+    <resource path="src/org/eclipse/jgit/storage/pack/PackStatistics.java" type="org.eclipse.jgit.storage.pack.PackStatistics$Accumulator">
+        <filter id="336658481">
             <message_arguments>
-                <message_argument value="org.eclipse.jgit.transport.BaseReceivePack"/>
-                <message_argument value="getAdvertisedRefs()"/>
-            </message_arguments>
-        </filter>
-        <filter id="421650549">
-            <message_arguments>
-                <message_argument value="org.eclipse.jgit.transport.BaseReceivePack"/>
-                <message_argument value="getPushCertificate()"/>
-            </message_arguments>
-        </filter>
-        <filter id="421650549">
-            <message_arguments>
-                <message_argument value="org.eclipse.jgit.transport.BaseReceivePack"/>
-                <message_argument value="getRepository()"/>
-            </message_arguments>
-        </filter>
-        <filter id="421650549">
-            <message_arguments>
-                <message_argument value="org.eclipse.jgit.transport.BaseReceivePack"/>
-                <message_argument value="getRevWalk()"/>
-            </message_arguments>
-        </filter>
-        <filter id="421650549">
-            <message_arguments>
-                <message_argument value="org.eclipse.jgit.transport.BaseReceivePack"/>
-                <message_argument value="setAdvertisedRefs(Map&lt;String,Ref&gt;, Set&lt;ObjectId&gt;)"/>
-            </message_arguments>
-        </filter>
-        <filter id="421650549">
-            <message_arguments>
-                <message_argument value="org.eclipse.jgit.transport.BaseReceivePack"/>
-                <message_argument value="setPushCertificate(PushCertificate)"/>
+                <message_argument value="org.eclipse.jgit.storage.pack.PackStatistics.Accumulator"/>
+                <message_argument value="treesTraversed"/>
             </message_arguments>
         </filter>
     </resource>
-    <resource path="src/org/eclipse/jgit/transport/TransferConfig.java" type="org.eclipse.jgit.transport.TransferConfig">
-        <filter id="1159725059">
+    <resource path="src/org/eclipse/jgit/transport/HttpConfig.java" type="org.eclipse.jgit.transport.HttpConfig">
+        <filter id="336658481">
             <message_arguments>
-                <message_argument value="5.1.4"/>
-                <message_argument value="TransferConfig(Config)"/>
+                <message_argument value="org.eclipse.jgit.transport.HttpConfig"/>
+                <message_argument value="COOKIE_FILE_CACHE_LIMIT_KEY"/>
             </message_arguments>
         </filter>
-        <filter id="1159725059">
+        <filter id="336658481">
             <message_arguments>
-                <message_argument value="5.1.4"/>
-                <message_argument value="TransferConfig(Repository)"/>
+                <message_argument value="org.eclipse.jgit.transport.HttpConfig"/>
+                <message_argument value="COOKIE_FILE_KEY"/>
+            </message_arguments>
+        </filter>
+        <filter id="336658481">
+            <message_arguments>
+                <message_argument value="org.eclipse.jgit.transport.HttpConfig"/>
+                <message_argument value="SAVE_COOKIES_KEY"/>
+            </message_arguments>
+        </filter>
+    </resource>
+    <resource path="src/org/eclipse/jgit/transport/Transport.java" type="org.eclipse.jgit.transport.Transport">
+        <filter id="421654647">
+            <message_arguments>
+                <message_argument value="org.eclipse.jgit.transport.Transport"/>
+                <message_argument value="getFilterBlobLimit()"/>
+            </message_arguments>
+        </filter>
+        <filter id="421654647">
+            <message_arguments>
+                <message_argument value="org.eclipse.jgit.transport.Transport"/>
+                <message_argument value="setFilterBlobLimit(long)"/>
+            </message_arguments>
+        </filter>
+    </resource>
+    <resource path="src/org/eclipse/jgit/transport/UploadPack.java" type="org.eclipse.jgit.transport.UploadPack">
+        <filter id="421654647">
+            <message_arguments>
+                <message_argument value="org.eclipse.jgit.transport.UploadPack"/>
+                <message_argument value="getFilterBlobLimit()"/>
+            </message_arguments>
+        </filter>
+    </resource>
+    <resource path="src/org/eclipse/jgit/treewalk/FileTreeIterator.java" type="org.eclipse.jgit.treewalk.FileTreeIterator$FileEntry">
+        <filter id="1142947843">
+            <message_arguments>
+                <message_argument value="5.1.9"/>
+                <message_argument value="getLastModifiedInstant()"/>
             </message_arguments>
         </filter>
     </resource>
@@ -276,12 +276,6 @@
     <resource path="src/org/eclipse/jgit/util/FS.java" type="org.eclipse.jgit.util.FS">
         <filter id="1142947843">
             <message_arguments>
-                <message_argument value="4.5.6"/>
-                <message_argument value="fileAttributes(File)"/>
-            </message_arguments>
-        </filter>
-        <filter id="1142947843">
-            <message_arguments>
                 <message_argument value="5.1.9"/>
                 <message_argument value="getFileStoreAttributes(Path)"/>
             </message_arguments>
@@ -335,6 +329,26 @@
             </message_arguments>
         </filter>
     </resource>
+    <resource path="src/org/eclipse/jgit/util/HttpSupport.java" type="org.eclipse.jgit.util.HttpSupport">
+        <filter id="336658481">
+            <message_arguments>
+                <message_argument value="org.eclipse.jgit.util.HttpSupport"/>
+                <message_argument value="HDR_COOKIE"/>
+            </message_arguments>
+        </filter>
+        <filter id="336658481">
+            <message_arguments>
+                <message_argument value="org.eclipse.jgit.util.HttpSupport"/>
+                <message_argument value="HDR_SET_COOKIE"/>
+            </message_arguments>
+        </filter>
+        <filter id="336658481">
+            <message_arguments>
+                <message_argument value="org.eclipse.jgit.util.HttpSupport"/>
+                <message_argument value="HDR_SET_COOKIE2"/>
+            </message_arguments>
+        </filter>
+    </resource>
     <resource path="src/org/eclipse/jgit/util/Monitoring.java" type="org.eclipse.jgit.util.Monitoring">
         <filter id="1109393411">
             <message_arguments>
diff --git a/org.eclipse.jgit/META-INF/MANIFEST.MF b/org.eclipse.jgit/META-INF/MANIFEST.MF
index be462f1..5bfa344 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.3.10.qualifier
+Bundle-Version: 5.4.4.qualifier
 Bundle-Localization: plugin
 Bundle-Vendor: %provider_name
 Bundle-ActivationPolicy: lazy
-Export-Package: org.eclipse.jgit.annotations;version="5.3.10",
- org.eclipse.jgit.api;version="5.3.10";
+Export-Package: org.eclipse.jgit.annotations;version="5.4.4",
+ org.eclipse.jgit.api;version="5.4.4";
   uses:="org.eclipse.jgit.revwalk,
    org.eclipse.jgit.treewalk.filter,
    org.eclipse.jgit.diff,
@@ -22,53 +22,53 @@
    org.eclipse.jgit.submodule,
    org.eclipse.jgit.transport,
    org.eclipse.jgit.merge",
- org.eclipse.jgit.api.errors;version="5.3.10";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.errors",
- org.eclipse.jgit.attributes;version="5.3.10",
- org.eclipse.jgit.blame;version="5.3.10";
+ org.eclipse.jgit.api.errors;version="5.4.4";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.errors",
+ org.eclipse.jgit.attributes;version="5.4.4",
+ org.eclipse.jgit.blame;version="5.4.4";
   uses:="org.eclipse.jgit.lib,
    org.eclipse.jgit.revwalk,
    org.eclipse.jgit.treewalk.filter,
    org.eclipse.jgit.diff",
- org.eclipse.jgit.diff;version="5.3.10";
+ org.eclipse.jgit.diff;version="5.4.4";
   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.3.10";
+ org.eclipse.jgit.dircache;version="5.4.4";
   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.3.10";
+ org.eclipse.jgit.errors;version="5.4.4";
   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.3.10";uses:="org.eclipse.jgit.lib",
- org.eclipse.jgit.fnmatch;version="5.3.10",
- org.eclipse.jgit.gitrepo;version="5.3.10";
+ org.eclipse.jgit.events;version="5.4.4";uses:="org.eclipse.jgit.lib",
+ org.eclipse.jgit.fnmatch;version="5.4.4",
+ org.eclipse.jgit.gitrepo;version="5.4.4";
   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.3.10";x-internal:=true,
- org.eclipse.jgit.hooks;version="5.3.10";uses:="org.eclipse.jgit.lib",
- org.eclipse.jgit.ignore;version="5.3.10",
- org.eclipse.jgit.ignore.internal;version="5.3.10";x-friends:="org.eclipse.jgit.test",
- org.eclipse.jgit.internal;version="5.3.10";x-friends:="org.eclipse.jgit.test,org.eclipse.jgit.http.test",
- org.eclipse.jgit.internal.fsck;version="5.3.10";x-friends:="org.eclipse.jgit.test",
- org.eclipse.jgit.internal.ketch;version="5.3.10";x-friends:="org.eclipse.jgit.junit,org.eclipse.jgit.test,org.eclipse.jgit.pgm",
- org.eclipse.jgit.internal.revwalk;version="5.3.10";x-internal:=true,
- org.eclipse.jgit.internal.storage.dfs;version="5.3.10";
+ org.eclipse.jgit.gitrepo.internal;version="5.4.4";x-internal:=true,
+ org.eclipse.jgit.hooks;version="5.4.4";uses:="org.eclipse.jgit.lib",
+ org.eclipse.jgit.ignore;version="5.4.4",
+ org.eclipse.jgit.ignore.internal;version="5.4.4";x-friends:="org.eclipse.jgit.test",
+ org.eclipse.jgit.internal;version="5.4.4";x-friends:="org.eclipse.jgit.test,org.eclipse.jgit.http.test",
+ org.eclipse.jgit.internal.fsck;version="5.4.4";x-friends:="org.eclipse.jgit.test",
+ org.eclipse.jgit.internal.ketch;version="5.4.4";x-friends:="org.eclipse.jgit.junit,org.eclipse.jgit.test,org.eclipse.jgit.pgm",
+ org.eclipse.jgit.internal.revwalk;version="5.4.4";x-internal:=true,
+ org.eclipse.jgit.internal.storage.dfs;version="5.4.4";
   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.3.10";
+ org.eclipse.jgit.internal.storage.file;version="5.4.4";
   x-friends:="org.eclipse.jgit.test,
    org.eclipse.jgit.junit,
    org.eclipse.jgit.junit.http,
@@ -77,18 +77,19 @@
    org.eclipse.jgit.pgm,
    org.eclipse.jgit.pgm.test,
    org.eclipse.jgit.ssh.apache",
- org.eclipse.jgit.internal.storage.io;version="5.3.10";x-friends:="org.eclipse.jgit.junit,org.eclipse.jgit.test,org.eclipse.jgit.pgm",
- org.eclipse.jgit.internal.storage.pack;version="5.3.10";x-friends:="org.eclipse.jgit.junit,org.eclipse.jgit.test,org.eclipse.jgit.pgm",
- org.eclipse.jgit.internal.storage.reftable;version="5.3.10";
+ org.eclipse.jgit.internal.storage.io;version="5.4.4";x-friends:="org.eclipse.jgit.junit,org.eclipse.jgit.test,org.eclipse.jgit.pgm",
+ org.eclipse.jgit.internal.storage.pack;version="5.4.4";x-friends:="org.eclipse.jgit.junit,org.eclipse.jgit.test,org.eclipse.jgit.pgm",
+ org.eclipse.jgit.internal.storage.reftable;version="5.4.4";
   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.3.10";x-friends:="org.eclipse.jgit.junit,org.eclipse.jgit.test,org.eclipse.jgit.pgm",
- org.eclipse.jgit.internal.submodule;version="5.3.10";x-internal:=true,
- org.eclipse.jgit.internal.transport.parser;version="5.3.10";x-friends:="org.eclipse.jgit.http.server,org.eclipse.jgit.test",
- org.eclipse.jgit.internal.transport.ssh;version="5.3.10";x-friends:="org.eclipse.jgit.ssh.apache",
- org.eclipse.jgit.lib;version="5.3.10";
+ org.eclipse.jgit.internal.storage.reftree;version="5.4.4";x-friends:="org.eclipse.jgit.junit,org.eclipse.jgit.test,org.eclipse.jgit.pgm",
+ org.eclipse.jgit.internal.submodule;version="5.4.4";x-internal:=true,
+ org.eclipse.jgit.internal.transport.http;version="5.4.4";x-friends:="org.eclipse.jgit.test",
+ org.eclipse.jgit.internal.transport.parser;version="5.4.4";x-friends:="org.eclipse.jgit.http.server,org.eclipse.jgit.test",
+ org.eclipse.jgit.internal.transport.ssh;version="5.4.4";x-friends:="org.eclipse.jgit.ssh.apache",
+ org.eclipse.jgit.lib;version="5.4.4";
   uses:="org.eclipse.jgit.revwalk,
    org.eclipse.jgit.treewalk.filter,
    org.eclipse.jgit.util,
@@ -98,33 +99,33 @@
    org.eclipse.jgit.treewalk,
    org.eclipse.jgit.transport,
    org.eclipse.jgit.submodule",
- org.eclipse.jgit.lib.internal;version="5.3.10";x-internal:=true,
- org.eclipse.jgit.merge;version="5.3.10";
+ org.eclipse.jgit.lib.internal;version="5.4.4";x-internal:=true,
+ org.eclipse.jgit.merge;version="5.4.4";
   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.3.10",
- org.eclipse.jgit.notes;version="5.3.10";
+ org.eclipse.jgit.nls;version="5.4.4",
+ org.eclipse.jgit.notes;version="5.4.4";
   uses:="org.eclipse.jgit.lib,
    org.eclipse.jgit.treewalk,
    org.eclipse.jgit.revwalk,
    org.eclipse.jgit.merge",
- org.eclipse.jgit.patch;version="5.3.10";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.diff",
- org.eclipse.jgit.revplot;version="5.3.10";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.revwalk",
- org.eclipse.jgit.revwalk;version="5.3.10";
+ org.eclipse.jgit.patch;version="5.4.4";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.diff",
+ org.eclipse.jgit.revplot;version="5.4.4";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.revwalk",
+ org.eclipse.jgit.revwalk;version="5.4.4";
   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.3.10";uses:="org.eclipse.jgit.revwalk,org.eclipse.jgit.lib,org.eclipse.jgit.util",
- org.eclipse.jgit.storage.file;version="5.3.10";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.util",
- org.eclipse.jgit.storage.pack;version="5.3.10";uses:="org.eclipse.jgit.lib",
- org.eclipse.jgit.submodule;version="5.3.10";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.treewalk.filter,org.eclipse.jgit.treewalk",
- org.eclipse.jgit.transport;version="5.3.10";
+ org.eclipse.jgit.revwalk.filter;version="5.4.4";uses:="org.eclipse.jgit.revwalk,org.eclipse.jgit.lib,org.eclipse.jgit.util",
+ org.eclipse.jgit.storage.file;version="5.4.4";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.util",
+ org.eclipse.jgit.storage.pack;version="5.4.4";uses:="org.eclipse.jgit.lib",
+ org.eclipse.jgit.submodule;version="5.4.4";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.treewalk.filter,org.eclipse.jgit.treewalk",
+ org.eclipse.jgit.transport;version="5.4.4";
   uses:="org.eclipse.jgit.transport.resolver,
    org.eclipse.jgit.revwalk,
    org.eclipse.jgit.internal.storage.pack,
@@ -137,39 +138,40 @@
    org.eclipse.jgit.transport.http,
    org.eclipse.jgit.errors,
    org.eclipse.jgit.storage.pack",
- org.eclipse.jgit.transport.http;version="5.3.10";uses:="javax.net.ssl",
- org.eclipse.jgit.transport.resolver;version="5.3.10";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.transport",
- org.eclipse.jgit.treewalk;version="5.3.10";
+ org.eclipse.jgit.transport.http;version="5.4.4";uses:="javax.net.ssl",
+ org.eclipse.jgit.transport.resolver;version="5.4.4";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.transport",
+ org.eclipse.jgit.treewalk;version="5.4.4";
   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.3.10";uses:="org.eclipse.jgit.treewalk",
- org.eclipse.jgit.util;version="5.3.10";
+ org.eclipse.jgit.treewalk.filter;version="5.4.4";uses:="org.eclipse.jgit.treewalk",
+ org.eclipse.jgit.util;version="5.4.4";
   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.3.10",
- org.eclipse.jgit.util.sha1;version="5.3.10",
- org.eclipse.jgit.util.time;version="5.3.10"
+ org.eclipse.jgit.util.io;version="5.4.4",
+ org.eclipse.jgit.util.sha1;version="5.4.4",
+ org.eclipse.jgit.util.time;version="5.4.4"
 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)",
  javax.crypto,
  javax.net.ssl,
- org.bouncycastle;version="[1.60.0,2.0.0)",
- org.bouncycastle.bcpg;version="[1.60.0,2.0.0)",
- org.bouncycastle.gpg;version="[1.60.0,2.0.0)",
- org.bouncycastle.gpg.keybox;version="[1.60.0,2.0.0)",
- org.bouncycastle.jce.provider;version="[1.60.0,2.0.0)",
- org.bouncycastle.openpgp;version="[1.60.0,2.0.0)",
- org.bouncycastle.openpgp.jcajce;version="[1.60.0,2.0.0)",
- org.bouncycastle.openpgp.operator;version="[1.60.0,2.0.0)",
- org.bouncycastle.openpgp.operator.jcajce;version="[1.60.0,2.0.0)",
- org.bouncycastle.util.encoders;version="[1.60.0,2.0.0)",
+ org.bouncycastle;version="[1.61.0,2.0.0)",
+ org.bouncycastle.bcpg;version="[1.61.0,2.0.0)",
+ org.bouncycastle.gpg;version="[1.61.0,2.0.0)",
+ org.bouncycastle.gpg.keybox;version="[1.61.0,2.0.0)",
+ org.bouncycastle.gpg.keybox.jcajce;version="[1.61.0,2.0.0)",
+ org.bouncycastle.jce.provider;version="[1.61.0,2.0.0)",
+ org.bouncycastle.openpgp;version="[1.61.0,2.0.0)",
+ org.bouncycastle.openpgp.jcajce;version="[1.61.0,2.0.0)",
+ org.bouncycastle.openpgp.operator;version="[1.61.0,2.0.0)",
+ org.bouncycastle.openpgp.operator.jcajce;version="[1.61.0,2.0.0)",
+ org.bouncycastle.util.encoders;version="[1.61.0,2.0.0)",
  org.slf4j;version="[1.7.0,2.0.0)",
  org.xml.sax,
  org.xml.sax.helpers
diff --git a/org.eclipse.jgit/META-INF/SOURCE-MANIFEST.MF b/org.eclipse.jgit/META-INF/SOURCE-MANIFEST.MF
index c2ec4a0..c3d5224 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.3.10.qualifier
-Eclipse-SourceBundle: org.eclipse.jgit;version="5.3.10.qualifier";roots="."
+Bundle-Version: 5.4.4.qualifier
+Eclipse-SourceBundle: org.eclipse.jgit;version="5.4.4.qualifier";roots="."
diff --git a/org.eclipse.jgit/pom.xml b/org.eclipse.jgit/pom.xml
index 632b637..7576f37 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.3.10-SNAPSHOT</version>
+    <version>5.4.4-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit</artifactId>
@@ -115,7 +115,7 @@
         <includes>
           <include>plugin.properties</include>
           <include>about.html</include>
-		  <include>META-INF/eclipse.inf</include>
+          <include>META-INF/eclipse.inf</include>
         </includes>
       </resource>
       <resource>
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 324302c..8890c9f 100644
--- a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties
+++ b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties
@@ -51,7 +51,7 @@
 cannotBeCombined=Cannot be combined.
 cannotBeRecursiveWhenTreesAreIncluded=TreeWalk shouldn't be recursive when tree objects are included.
 cannotChangeActionOnComment=Cannot change action on comment line in git-rebase-todo file, old action: {0}, new action: {1}.
-cannotCheckoutFromUnbornBranch=Cannot checkout from unborn branch
+cannotCheckoutFromUnbornBranch=Cannot check out from unborn branch
 cannotCheckoutOursSwitchBranch=Checking out ours/theirs is only possible when checking out index, not when switching branches.
 cannotCombineSquashWithNoff=Cannot combine --squash with --no-ff.
 cannotCombineTreeFilterWithRevFilter=Cannot combine TreeFilter {0} with RevFilter {1}.
@@ -130,6 +130,7 @@
 configSubsectionContainsNullByte=config subsection name contains byte 0x00
 configValueContainsNullByte=config value contains byte 0x00
 configHandleIsStale=config file handle is stale, {0}. retry
+configHandleMayBeLocked=config file handle may be locked by other process, {0}. retry
 connectionFailed=connection failed
 connectionTimeOut=Connection time out: {0}
 contextMustBeNonNegative=context must be >= 0
@@ -162,8 +163,8 @@
 corruptObjectInvalidType=invalid type
 corruptObjectInvalidType2=invalid type {0}
 corruptObjectMissingEmail=missing email
-corruptObjectNameContainsByte=name contains byte 0x%x
-corruptObjectNameContainsChar=name contains '%c'
+corruptObjectNameContainsByte=byte 0x%x not allowed in Windows filename
+corruptObjectNameContainsChar=char '%c' not allowed in Windows filename
 corruptObjectNameContainsNullByte=name contains byte 0x00
 corruptObjectNameContainsSlash=name contains '/'
 corruptObjectNameDot=invalid name '.'
@@ -185,7 +186,12 @@
 corruptUseCnt=close() called when useCnt is already zero for {0}
 couldNotGetAdvertisedRef=Remote {0} did not advertise Ref for branch {1}. This Ref may not exist in the remote or may be hidden by permission settings.
 couldNotGetRepoStatistics=Could not get repository statistics
+couldNotFindTabInLine=Could not find tab in line {0}. Tab is the mandatory separator for the Netscape Cookie File Format.
+couldNotFindSixTabsInLine=Could not find 6 tabs but only {0} in line '{1}'. 7 tab separated columns per line are mandatory for the Netscape Cookie File Format.
 couldNotLockHEAD=Could not lock HEAD
+couldNotPersistCookies=Could not persist received cookies in file ''{0}''
+couldNotReadCookieFile=Could not read cookie file ''{0}''
+couldNotReadIndexInOneGo=Could not read index in one go, only {0} out of {1} read
 couldNotReadObjectWhileParsingCommit=Could not read an object while parsing commit {0}
 couldNotRewindToUpstreamCommit=Could not rewind to upstream commit
 couldNotURLEncodeToUTF8=Could not URL encode to UTF-8
@@ -297,6 +303,7 @@
 gpgNoKeyInLegacySecring=no matching secret key found in legacy secring.gpg for key or user id: {0}
 gpgNoPublicKeyFound=Unable to find a public-key with key or user id: {0}
 gpgNoSecretKeyForPublicKey=unable to find associated secret key for public key: {0}
+gpgNotASigningKey=Secret key ({0}) is not suitable for signing
 gpgKeyInfo=GPG Key (fingerprint {0})
 gpgSigningCancelled=Signing was cancelled
 headRequiredToStash=HEAD required to stash local changes
@@ -412,6 +419,7 @@
 mismatchCRC=mismatch CRC for object {0}
 missingAccesskey=Missing accesskey.
 missingConfigurationForKey=No value for key {0} found in configuration
+missingCookieFile=Configured http.cookieFile ''{0}'' is missing
 missingCRC=missing CRC for object {0}
 missingDeltaBase=delta base
 missingForwardImageInGITBinaryPatch=Missing forward-image in GIT binary patch
@@ -576,6 +584,7 @@
 s3ActionDeletion=Deletion
 s3ActionReading=Reading
 s3ActionWriting=Writing
+searchForReachableBranches=Finding reachable branches
 saveFileStoreAttributesFailed=Saving measured FileStore attributes to user config failed
 searchForReuse=Finding sources
 searchForSizes=Getting sizes
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/CherryPickCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/CherryPickCommand.java
index 65b72f7..c9dd547 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CherryPickCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CherryPickCommand.java
@@ -157,8 +157,8 @@
 				merger.setCommitNames(new String[] { "BASE", ourName, //$NON-NLS-1$
 						cherryPickName });
 				if (merger.merge(newHead, srcCommit)) {
-					if (AnyObjectId.equals(newHead.getTree().getId(), merger
-							.getResultTreeId()))
+					if (AnyObjectId.isEqual(newHead.getTree().getId(),
+							merger.getResultTreeId()))
 						continue;
 					DirCacheCheckout dco = new DirCacheCheckout(repo,
 							newHead.getTree(), repo.lockDirCache(),
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java
index 61d51cc..b55987e 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java
@@ -282,7 +282,7 @@
 					ru.setRefLogMessage(reflogComment, false);
 				} else {
 					String prefix = amend ? "commit (amend): " //$NON-NLS-1$
-							: parents.size() == 0 ? "commit (initial): " //$NON-NLS-1$
+							: parents.isEmpty() ? "commit (initial): " //$NON-NLS-1$
 									: "commit: "; //$NON-NLS-1$
 					ru.setRefLogMessage(prefix + revCommit.getShortMessage(),
 							false);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/DeleteBranchCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/DeleteBranchCommand.java
index 31e7281..c3feaef 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/DeleteBranchCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/DeleteBranchCommand.java
@@ -46,6 +46,7 @@
 import java.io.IOException;
 import java.text.MessageFormat;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
@@ -188,8 +189,7 @@
 	public DeleteBranchCommand setBranchNames(String... branchnames) {
 		checkCallable();
 		this.branchNames.clear();
-		for (String branch : branchnames)
-			this.branchNames.add(branch);
+		this.branchNames.addAll(Arrays.asList(branchnames));
 		return this;
 	}
 
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/DeleteTagCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/DeleteTagCommand.java
index 63a090c..1970240 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/DeleteTagCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/DeleteTagCommand.java
@@ -45,6 +45,7 @@
 import java.io.IOException;
 import java.text.MessageFormat;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
@@ -135,8 +136,7 @@
 	public DeleteTagCommand setTags(String... tags) {
 		checkCallable();
 		this.tags.clear();
-		for (String tagName : tags)
-			this.tags.add(tagName);
+		this.tags.addAll(Arrays.asList(tags));
 		return this;
 	}
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/DescribeCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/DescribeCommand.java
index a484742..9ad77e6 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/DescribeCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/DescribeCommand.java
@@ -63,8 +63,7 @@
 import org.eclipse.jgit.errors.IncorrectObjectTypeException;
 import org.eclipse.jgit.errors.InvalidPatternException;
 import org.eclipse.jgit.errors.MissingObjectException;
-import org.eclipse.jgit.ignore.internal.IMatcher;
-import org.eclipse.jgit.ignore.internal.PathMatcher;
+import org.eclipse.jgit.fnmatch.FileNameMatcher;
 import org.eclipse.jgit.internal.JGitText;
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.ObjectId;
@@ -102,14 +101,19 @@
 	private boolean longDesc;
 
 	/**
-	 * Pattern matchers to be applied to tags under consideration
+	 * Pattern matchers to be applied to tags under consideration.
 	 */
-	private List<IMatcher> matchers = new ArrayList<>();
+	private List<FileNameMatcher> matchers = new ArrayList<>();
 
 	/**
-	 * Whether to use all tags (incl. lightweight) or not
+	 * Whether to use all tags (incl. lightweight) or not.
 	 */
-	private boolean useTags = false;
+	private boolean useTags;
+
+	/**
+	 * Whether to show a uniquely abbreviated commit hash as a fallback or not.
+	 */
+	private boolean always;
 
 	/**
 	 * Constructor for DescribeCommand.
@@ -197,6 +201,21 @@
 		return this;
 	}
 
+	/**
+	 * Always describe the commit by eventually falling back to a uniquely
+	 * abbreviated commit hash if no other name matches.
+	 *
+	 * @param always
+	 *            <code>true</code> enables falling back to a uniquely
+	 *            abbreviated commit hash
+	 * @return {@code this}
+	 * @since 5.4
+	 */
+	public DescribeCommand setAlways(boolean always) {
+		this.always = always;
+		return this;
+	}
+
 	private String longDescription(Ref tag, int depth, ObjectId tip)
 			throws IOException {
 		return String.format(
@@ -222,7 +241,7 @@
 	 */
 	public DescribeCommand setMatch(String... patterns) throws InvalidPatternException {
 		for (String p : patterns) {
-			matchers.add(PathMatcher.createPathMatcher(p, null, false));
+			matchers.add(new FileNameMatcher(p, null));
 		}
 		return this;
 	}
@@ -246,18 +265,24 @@
 	};
 
 	private Optional<Ref> getBestMatch(List<Ref> tags) {
-		if (tags == null || tags.size() == 0) {
+		if (tags == null || tags.isEmpty()) {
 			return Optional.empty();
-		} else if (matchers.size() == 0) {
+		} else if (matchers.isEmpty()) {
 			Collections.sort(tags, TAG_TIE_BREAKER);
 			return Optional.of(tags.get(0));
 		} else {
 			// Find the first tag that matches in the stream of all tags
 			// filtered by matchers ordered by tie break order
 			Stream<Ref> matchingTags = Stream.empty();
-			for (IMatcher matcher : matchers) {
+			for (FileNameMatcher matcher : matchers) {
 				Stream<Ref> m = tags.stream().filter(
-						tag -> matcher.matches(tag.getName(), false, false));
+						tag -> {
+							matcher.append(
+									tag.getName().substring(R_TAGS.length()));
+							boolean result = matcher.isMatch();
+							matcher.reset();
+							return result;
+						});
 				matchingTags = Stream.of(matchingTags, m).flatMap(i -> i);
 			}
 			return matchingTags.sorted(TAG_TIE_BREAKER).findFirst();
@@ -399,15 +424,12 @@
 			}
 
 			// if all the nodes are dominated by all the tags, the walk stops
-			if (candidates.isEmpty())
-				return null;
+			if (candidates.isEmpty()) {
+				return always ? w.getObjectReader().abbreviate(target).name() : null;
+			}
 
-			Candidate best = Collections.min(candidates, new Comparator<Candidate>() {
-				@Override
-				public int compare(Candidate o1, Candidate o2) {
-					return o1.depth - o2.depth;
-				}
-			});
+			Candidate best = Collections.min(candidates,
+					(Candidate o1, Candidate o2) -> o1.depth - o2.depth);
 
 			return best.describe(target);
 		} catch (IOException e) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/Git.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/Git.java
index 400a7df..835e7b5 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/Git.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/Git.java
@@ -43,6 +43,8 @@
  */
 package org.eclipse.jgit.api;
 
+import static java.util.Objects.requireNonNull;
+
 import java.io.File;
 import java.io.IOException;
 
@@ -220,9 +222,7 @@
 	}
 
 	Git(Repository repo, boolean closeRepo) {
-		if (repo == null)
-			throw new NullPointerException();
-		this.repo = repo;
+		this.repo = requireNonNull(repo);
 		this.closeRepo = closeRepo;
 	}
 
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 29a51a0..e0eafc7 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/ListBranchCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/ListBranchCommand.java
@@ -53,7 +53,6 @@
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
-import java.util.Comparator;
 import java.util.List;
 
 import org.eclipse.jgit.api.errors.GitAPIException;
@@ -134,12 +133,8 @@
 			throw new JGitInternalException(e.getMessage(), e);
 		}
 
-		Collections.sort(resultRefs, new Comparator<Ref>() {
-			@Override
-			public int compare(Ref o1, Ref o2) {
-				return o1.getName().compareTo(o2.getName());
-			}
-		});
+		Collections.sort(resultRefs,
+				(Ref o1, Ref o2) -> o1.getName().compareTo(o2.getName()));
 		setCallable(false);
 		return resultRefs;
 	}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/ListTagCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/ListTagCommand.java
index 01c1991..c894d05 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/ListTagCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/ListTagCommand.java
@@ -45,7 +45,6 @@
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collections;
-import java.util.Comparator;
 import java.util.List;
 
 import org.eclipse.jgit.api.errors.GitAPIException;
@@ -87,12 +86,8 @@
 		} catch (IOException e) {
 			throw new JGitInternalException(e.getMessage(), e);
 		}
-		Collections.sort(tags, new Comparator<Ref>() {
-			@Override
-			public int compare(Ref o1, Ref o2) {
-				return o1.getName().compareTo(o2.getName());
-			}
-		});
+		Collections.sort(tags,
+				(Ref o1, Ref o2) -> o1.getName().compareTo(o2.getName()));
 		setCallable(false);
 		return tags;
 	}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/LogCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/LogCommand.java
index cf3d35f..66de8ae 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/LogCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/LogCommand.java
@@ -133,7 +133,7 @@
 	@Override
 	public Iterable<RevCommit> call() throws GitAPIException, NoHeadException {
 		checkCallable();
-		if (pathFilters.size() > 0)
+		if (!pathFilters.isEmpty())
 			walk.setTreeFilter(AndTreeFilter.create(
 					PathFilterGroup.create(pathFilters), TreeFilter.ANY_DIFF));
 		if (skip > -1 && maxCount > -1)
@@ -282,13 +282,11 @@
 			RevCommit commit = null;
 			try {
 				commit = walk.parseCommit(objectId);
-			} catch (MissingObjectException e) {
-				// ignore: the ref points to an object that does not exist;
-				// it should be ignored as traversal starting point.
-			} catch (IncorrectObjectTypeException e) {
-				// ignore: the ref points to an object that is not a commit
-				// (e.g. a tree or a blob);
-				// it should be ignored as traversal starting point.
+			} catch (MissingObjectException | IncorrectObjectTypeException e) {
+				// ignore as traversal starting point:
+				// - the ref points to an object that does not exist
+				// - the ref points to an object that is not a commit (e.g. a
+				// tree or a blob)
 			}
 			if (commit != null)
 				add(commit);
@@ -348,9 +346,7 @@
 			} else
 				walk.markUninteresting(walk.lookupCommit(start));
 			return this;
-		} catch (MissingObjectException e) {
-			throw e;
-		} catch (IncorrectObjectTypeException e) {
+		} catch (MissingObjectException | IncorrectObjectTypeException e) {
 			throw e;
 		} catch (IOException e) {
 			throw new JGitInternalException(MessageFormat.format(
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/PullCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/PullCommand.java
index f0ad29d..bdb2d1bb 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/PullCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/PullCommand.java
@@ -60,6 +60,7 @@
 import org.eclipse.jgit.api.errors.RefNotAdvertisedException;
 import org.eclipse.jgit.api.errors.RefNotFoundException;
 import org.eclipse.jgit.api.errors.WrongRepositoryStateException;
+import org.eclipse.jgit.dircache.DirCacheCheckout;
 import org.eclipse.jgit.internal.JGitText;
 import org.eclipse.jgit.lib.AnyObjectId;
 import org.eclipse.jgit.lib.BranchConfig.BranchRebaseMode;
@@ -67,12 +68,17 @@
 import org.eclipse.jgit.lib.ConfigConstants;
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.NullProgressMonitor;
+import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.ProgressMonitor;
 import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.RefUpdate;
+import org.eclipse.jgit.lib.RefUpdate.Result;
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.lib.RepositoryState;
 import org.eclipse.jgit.lib.SubmoduleConfig.FetchRecurseSubmodulesMode;
 import org.eclipse.jgit.merge.MergeStrategy;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevWalk;
 import org.eclipse.jgit.transport.FetchResult;
 import org.eclipse.jgit.transport.TagOpt;
 
@@ -339,6 +345,45 @@
 
 		PullResult result;
 		if (pullRebaseMode != BranchRebaseMode.NONE) {
+			try {
+				Ref head = repo.exactRef(Constants.HEAD);
+				if (head == null) {
+					throw new NoHeadException(JGitText
+							.get().commitOnRepoWithoutHEADCurrentlyNotSupported);
+				}
+				ObjectId headId = head.getObjectId();
+				if (headId == null) {
+					// Pull on an unborn branch: checkout
+					try (RevWalk revWalk = new RevWalk(repo)) {
+						RevCommit srcCommit = revWalk
+								.parseCommit(commitToMerge);
+						DirCacheCheckout dco = new DirCacheCheckout(repo,
+								repo.lockDirCache(), srcCommit.getTree());
+						dco.setFailOnConflict(true);
+						dco.setProgressMonitor(monitor);
+						dco.checkout();
+						RefUpdate refUpdate = repo
+								.updateRef(head.getTarget().getName());
+						refUpdate.setNewObjectId(commitToMerge);
+						refUpdate.setExpectedOldObjectId(null);
+						refUpdate.setRefLogMessage("initial pull", false); //$NON-NLS-1$
+						if (refUpdate.update() != Result.NEW) {
+							throw new NoHeadException(JGitText
+									.get().commitOnRepoWithoutHEADCurrentlyNotSupported);
+						}
+						monitor.endTask();
+						return new PullResult(fetchRes, remote,
+								RebaseResult.result(
+										RebaseResult.Status.FAST_FORWARD,
+										srcCommit));
+					}
+				}
+			} catch (NoHeadException e) {
+				throw e;
+			} catch (IOException e) {
+				throw new JGitInternalException(JGitText
+						.get().exceptionCaughtDuringExecutionOfPullCommand, e);
+			}
 			RebaseCommand rebase = new RebaseCommand(repo);
 			RebaseResult rebaseRes = rebase.setUpstream(commitToMerge)
 					.setUpstreamName(upstreamName).setProgressMonitor(monitor)
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java
index 0e3d000..0dacd4d 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java
@@ -362,7 +362,7 @@
 
 			List<RebaseTodoLine> steps = repo.readRebaseTodo(
 					rebaseState.getPath(GIT_REBASE_TODO), false);
-			if (steps.size() == 0) {
+			if (steps.isEmpty()) {
 				return finishRebase(walk.parseCommit(repo.resolve(Constants.HEAD)), false);
 			}
 			if (isInteractive()) {
@@ -490,7 +490,7 @@
 			resetSoftToParent();
 			List<RebaseTodoLine> steps = repo.readRebaseTodo(
 					rebaseState.getPath(GIT_REBASE_TODO), false);
-			RebaseTodoLine nextStep = steps.size() > 0 ? steps.get(0) : null;
+			RebaseTodoLine nextStep = steps.isEmpty() ? null : steps.get(0);
 			File messageFixupFile = rebaseState.getFile(MESSAGE_FIXUP);
 			File messageSquashFile = rebaseState.getFile(MESSAGE_SQUASH);
 			if (isSquash && messageFixupFile.exists())
@@ -575,7 +575,7 @@
 			ObjectId headId = getHead().getObjectId();
 			// getHead() checks for null
 			assert headId != null;
-			if (!AnyObjectId.equals(headId, newParents.get(0)))
+			if (!AnyObjectId.isEqual(headId, newParents.get(0)))
 				checkoutCommit(headId.getName(), newParents.get(0));
 
 			// Use the cherry-pick strategy if all non-first parents did not
@@ -1083,7 +1083,7 @@
 
 		repo.writeRebaseTodoFile(rebaseState.getPath(GIT_REBASE_TODO),
 				todoLines, false);
-		if (poppedLines.size() > 0) {
+		if (!poppedLines.isEmpty()) {
 			repo.writeRebaseTodoFile(rebaseState.getPath(DONE), poppedLines,
 					true);
 		}
@@ -1295,13 +1295,8 @@
 				}
 			}
 			return newCommit;
-		} catch (RefAlreadyExistsException e) {
-			throw new JGitInternalException(e.getMessage(), e);
-		} catch (RefNotFoundException e) {
-			throw new JGitInternalException(e.getMessage(), e);
-		} catch (InvalidRefNameException e) {
-			throw new JGitInternalException(e.getMessage(), e);
-		} catch (CheckoutConflictException e) {
+		} catch (RefAlreadyExistsException | RefNotFoundException
+				| InvalidRefNameException | CheckoutConflictException e) {
 			throw new JGitInternalException(e.getMessage(), e);
 		}
 	}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/RevertCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/RevertCommand.java
index 46e0df7..ddd60b6 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/RevertCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/RevertCommand.java
@@ -175,8 +175,8 @@
 						+ "This reverts commit " + srcCommit.getId().getName() //$NON-NLS-1$
 						+ ".\n"; //$NON-NLS-1$
 				if (merger.merge(headCommit, srcParent)) {
-					if (AnyObjectId.equals(headCommit.getTree().getId(), merger
-							.getResultTreeId()))
+					if (AnyObjectId.isEqual(headCommit.getTree().getId(),
+							merger.getResultTreeId()))
 						continue;
 					DirCacheCheckout dco = new DirCacheCheckout(repo,
 							headCommit.getTree(), repo.lockDirCache(),
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 f92455a..8908277 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleAddCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleAddCommand.java
@@ -238,9 +238,7 @@
 			modulesConfig.setString(ConfigConstants.CONFIG_SUBMODULE_SECTION,
 					name, ConfigConstants.CONFIG_KEY_URL, uri);
 			modulesConfig.save();
-		} catch (IOException e) {
-			throw new JGitInternalException(e.getMessage(), e);
-		} catch (ConfigInvalidException e) {
+		} catch (IOException | ConfigInvalidException e) {
 			throw new JGitInternalException(e.getMessage(), e);
 		}
 
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleDeinitCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleDeinitCommand.java
index 5a0528b..ebcea4b 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleDeinitCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleDeinitCommand.java
@@ -173,8 +173,8 @@
 		}
 		final File[] ls = dir.listFiles();
 		if (ls != null) {
-			for (int i = 0; i < ls.length; i++) {
-				FileUtils.delete(ls[i], RECURSIVE);
+			for (File f : ls) {
+				FileUtils.delete(f, RECURSIVE);
 			}
 		}
 	}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleInitCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleInitCommand.java
index 2db12b8..6fd9405 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleInitCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleInitCommand.java
@@ -128,9 +128,7 @@
 			if (!initialized.isEmpty())
 				config.save();
 			return initialized;
-		} catch (IOException e) {
-			throw new JGitInternalException(e.getMessage(), e);
-		} catch (ConfigInvalidException e) {
+		} catch (IOException | ConfigInvalidException e) {
 			throw new JGitInternalException(e.getMessage(), e);
 		}
 	}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleStatusCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleStatusCommand.java
index 0606c5b..58e5959 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleStatusCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleStatusCommand.java
@@ -108,9 +108,7 @@
 				statuses.put(status.getPath(), status);
 			}
 			return statuses;
-		} catch (IOException e) {
-			throw new JGitInternalException(e.getMessage(), e);
-		} catch (ConfigInvalidException e) {
+		} catch (IOException | ConfigInvalidException e) {
 			throw new JGitInternalException(e.getMessage(), e);
 		}
 	}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleSyncCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleSyncCommand.java
index 7cf4b73..5239369 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleSyncCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleSyncCommand.java
@@ -162,9 +162,7 @@
 			if (!synced.isEmpty())
 				config.save();
 			return synced;
-		} catch (IOException e) {
-			throw new JGitInternalException(e.getMessage(), e);
-		} catch (ConfigInvalidException e) {
+		} catch (IOException | ConfigInvalidException e) {
 			throw new JGitInternalException(e.getMessage(), e);
 		}
 	}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffFormatter.java b/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffFormatter.java
index e7ad0bc..1cecff6 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffFormatter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffFormatter.java
@@ -1063,7 +1063,7 @@
 					entry.newId = id;
 					break;
 				}
-			} else if (ids.size() == 0)
+			} else if (ids.isEmpty())
 				throw new MissingObjectException(id, Constants.OBJ_BLOB);
 			else
 				throw new AmbiguousObjectException(id, ids);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCache.java b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCache.java
index a778de9..f07c24e 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCache.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCache.java
@@ -112,14 +112,12 @@
 
 	private static final byte[] NO_CHECKSUM = {};
 
-	static final Comparator<DirCacheEntry> ENT_CMP = new Comparator<DirCacheEntry>() {
-		@Override
-		public int compare(DirCacheEntry o1, DirCacheEntry o2) {
-			final int cr = cmp(o1, o2);
-			if (cr != 0)
-				return cr;
-			return o1.getStage() - o2.getStage();
-		}
+	static final Comparator<DirCacheEntry> ENT_CMP = (DirCacheEntry o1,
+			DirCacheEntry o2) -> {
+		final int cr = cmp(o1, o2);
+		if (cr != 0)
+			return cr;
+		return o1.getStage() - o2.getStage();
 	};
 
 	static int cmp(DirCacheEntry a, DirCacheEntry b) {
@@ -254,13 +252,7 @@
 
 		try {
 			c.read();
-		} catch (IOException e) {
-			c.unlock();
-			throw e;
-		} catch (RuntimeException e) {
-			c.unlock();
-			throw e;
-		} catch (Error e) {
+		} catch (IOException | RuntimeException | Error e) {
 			c.unlock();
 			throw e;
 		}
@@ -637,13 +629,7 @@
 		try (OutputStream o = tmp.getOutputStream();
 				OutputStream bo = new BufferedOutputStream(o)) {
 			writeTo(liveFile.getParentFile(), bo);
-		} catch (IOException err) {
-			tmp.unlock();
-			throw err;
-		} catch (RuntimeException err) {
-			tmp.unlock();
-			throw err;
-		} catch (Error err) {
+		} catch (IOException | RuntimeException | Error err) {
 			tmp.unlock();
 			throw err;
 		}
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 2d62286..22090f5 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java
@@ -635,7 +635,7 @@
 			if (!builder.commit())
 				throw new IndexWriteException();
 		}
-		return toBeDeleted.size() == 0;
+		return toBeDeleted.isEmpty();
 	}
 
 	private void checkoutGitlink(String path, DirCacheEntry entry)
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEditor.java b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEditor.java
index 74ba97f..5b8e11f 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEditor.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEditor.java
@@ -75,13 +75,11 @@
  * @see DirCacheBuilder
  */
 public class DirCacheEditor extends BaseDirCacheEditor {
-	private static final Comparator<PathEdit> EDIT_CMP = new Comparator<PathEdit>() {
-		@Override
-		public int compare(PathEdit o1, PathEdit o2) {
-			final byte[] a = o1.path;
-			final byte[] b = o2.path;
-			return cmp(a, a.length, b, b.length);
-		}
+	private static final Comparator<PathEdit> EDIT_CMP = (PathEdit o1,
+			PathEdit o2) -> {
+		final byte[] a = o1.path;
+		final byte[] b = o2.path;
+		return cmp(a, a.length, b, b.length);
 	};
 
 	private final List<PathEdit> edits;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEntry.java b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEntry.java
index b118fd6..0e91f0d 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEntry.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEntry.java
@@ -448,9 +448,9 @@
 	 */
 	public void setAssumeValid(boolean assume) {
 		if (assume)
-			info[infoOffset + P_FLAGS] |= ASSUME_VALID;
+			info[infoOffset + P_FLAGS] |= (byte) ASSUME_VALID;
 		else
-			info[infoOffset + P_FLAGS] &= ~ASSUME_VALID;
+			info[infoOffset + P_FLAGS] &= (byte) ~ASSUME_VALID;
 	}
 
 	/**
@@ -470,9 +470,9 @@
 	 */
 	public void setUpdateNeeded(boolean updateNeeded) {
 		if (updateNeeded)
-			inCoreFlags |= UPDATE_NEEDED;
+			inCoreFlags |= (byte) UPDATE_NEEDED;
 		else
-			inCoreFlags &= ~UPDATE_NEEDED;
+			inCoreFlags &= (byte) ~UPDATE_NEEDED;
 	}
 
 	/**
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheTree.java b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheTree.java
index 11a3474..80e1084 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheTree.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheTree.java
@@ -81,25 +81,26 @@
 
 	private static final DirCacheTree[] NO_CHILDREN = {};
 
-	private static final Comparator<DirCacheTree> TREE_CMP = new Comparator<DirCacheTree>() {
-		@Override
-		public int compare(DirCacheTree o1, DirCacheTree o2) {
-			final byte[] a = o1.encodedName;
-			final byte[] b = o2.encodedName;
-			final int aLen = a.length;
-			final int bLen = b.length;
-			int cPos;
-			for (cPos = 0; cPos < aLen && cPos < bLen; cPos++) {
-				final int cmp = (a[cPos] & 0xff) - (b[cPos] & 0xff);
-				if (cmp != 0)
-					return cmp;
+	private static final Comparator<DirCacheTree> TREE_CMP = (DirCacheTree o1,
+			DirCacheTree o2) -> {
+		final byte[] a = o1.encodedName;
+		final byte[] b = o2.encodedName;
+		final int aLen = a.length;
+		final int bLen = b.length;
+		int cPos;
+		for (cPos = 0; cPos < aLen && cPos < bLen; cPos++) {
+			final int cmp = (a[cPos] & 0xff) - (b[cPos] & 0xff);
+			if (cmp != 0) {
+				return cmp;
 			}
-			if (aLen == bLen)
-				return 0;
-			if (aLen < bLen)
-				return '/' - (b[cPos] & 0xff);
-			return (a[cPos] & 0xff) - '/';
 		}
+		if (aLen == bLen) {
+			return 0;
+		}
+		if (aLen < bLen) {
+			return '/' - (b[cPos] & 0xff);
+		}
+		return (a[cPos] & 0xff) - '/';
 	};
 
 	/** Tree this tree resides in; null if we are the root. */
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/errors/IncorrectObjectTypeException.java b/org.eclipse.jgit/src/org/eclipse/jgit/errors/IncorrectObjectTypeException.java
index 5abd0c3..15fd803 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/errors/IncorrectObjectTypeException.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/errors/IncorrectObjectTypeException.java
@@ -63,7 +63,7 @@
 	private static final long serialVersionUID = 1L;
 
 	/**
-	 * Construct and IncorrectObjectTypeException for the specified object id.
+	 * Construct an IncorrectObjectTypeException for the specified object id.
 	 *
 	 * Provide the type to make it easier to track down the problem.
 	 *
@@ -75,7 +75,7 @@
 	}
 
 	/**
-	 * Construct and IncorrectObjectTypeException for the specified object id.
+	 * Construct an IncorrectObjectTypeException for the specified object id.
 	 *
 	 * Provide the type to make it easier to track down the problem.
 	 *
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/WildMatcher.java b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/WildMatcher.java
index 2ad87da..239e2c8 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/WildMatcher.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/WildMatcher.java
@@ -64,7 +64,7 @@
 	public final boolean matches(String path, boolean assumeDirectory,
 			boolean pathMatch) {
 		return !dirOnly || assumeDirectory
-				|| !pathMatch && isSubdirectory(path);
+				|| (!pathMatch && isSubdirectory(path));
 	}
 
 	/** {@inheritDoc} */
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 59e6d24..01c8b37 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java
@@ -191,6 +191,7 @@
 	/***/ public String configSubsectionContainsNullByte;
 	/***/ public String configValueContainsNullByte;
 	/***/ public String configHandleIsStale;
+	/***/ public String configHandleMayBeLocked;
 	/***/ public String connectionFailed;
 	/***/ public String connectionTimeOut;
 	/***/ public String contextMustBeNonNegative;
@@ -245,9 +246,14 @@
 	/***/ public String corruptObjectZeroId;
 	/***/ public String corruptPack;
 	/***/ public String corruptUseCnt;
+	/***/ public String couldNotFindTabInLine;
+	/***/ public String couldNotFindSixTabsInLine;
 	/***/ public String couldNotGetAdvertisedRef;
 	/***/ public String couldNotGetRepoStatistics;
 	/***/ public String couldNotLockHEAD;
+	/***/ public String couldNotPersistCookies;
+	/***/ public String couldNotReadCookieFile;
+	/***/ public String couldNotReadIndexInOneGo;
 	/***/ public String couldNotReadObjectWhileParsingCommit;
 	/***/ public String couldNotRewindToUpstreamCommit;
 	/***/ public String couldNotURLEncodeToUTF8;
@@ -358,6 +364,7 @@
 	/***/ public String gpgNoKeyInLegacySecring;
 	/***/ public String gpgNoPublicKeyFound;
 	/***/ public String gpgNoSecretKeyForPublicKey;
+	/***/ public String gpgNotASigningKey;
 	/***/ public String gpgKeyInfo;
 	/***/ public String gpgSigningCancelled;
 	/***/ public String headRequiredToStash;
@@ -473,6 +480,7 @@
 	/***/ public String mismatchCRC;
 	/***/ public String missingAccesskey;
 	/***/ public String missingConfigurationForKey;
+	/***/ public String missingCookieFile;
 	/***/ public String missingCRC;
 	/***/ public String missingDeltaBase;
 	/***/ public String missingForwardImageInGITBinaryPatch;
@@ -638,6 +646,7 @@
 	/***/ public String s3ActionReading;
 	/***/ public String s3ActionWriting;
 	/***/ public String saveFileStoreAttributesFailed;
+	/***/ public String searchForReachableBranches;
 	/***/ public String searchForReuse;
 	/***/ public String searchForSizes;
 	/***/ public String secondsAgo;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/KetchLeader.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/KetchLeader.java
index fa32b26..c0364ac 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/KetchLeader.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/KetchLeader.java
@@ -385,12 +385,7 @@
 
 	private void scheduleLeader() {
 		idle = false;
-		system.getExecutor().execute(new Runnable() {
-			@Override
-			public void run() {
-				runLeader();
-			}
-		});
+		system.getExecutor().execute(this::runLeader);
 	}
 
 	private void runLeader() {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/KetchReplica.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/KetchReplica.java
index a0176d7..0e8377d 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/KetchReplica.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/KetchReplica.java
@@ -345,7 +345,7 @@
 	}
 
 	private static boolean equals(@Nullable ObjectId a, LogIndex b) {
-		return a != null && b != null && AnyObjectId.equals(a, b);
+		return a != null && b != null && AnyObjectId.isEqual(a, b);
 	}
 
 	/**
@@ -749,7 +749,7 @@
 				Ref oldRef = remote.remove(name);
 				ObjectId oldId = getId(oldRef);
 				ObjectId newId = tw.getObjectId(0);
-				if (!AnyObjectId.equals(oldId, newId)) {
+				if (!AnyObjectId.isEqual(oldId, newId)) {
 					delta.add(new ReceiveCommand(oldId, newId, name));
 				}
 			}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/LagCheck.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/LagCheck.java
index c09d872..53fd198 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/LagCheck.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/LagCheck.java
@@ -106,7 +106,7 @@
 			return UNKNOWN;
 		}
 
-		if (AnyObjectId.equals(remoteId, ObjectId.zeroId())) {
+		if (AnyObjectId.isEqual(remoteId, ObjectId.zeroId())) {
 			// Replica does not have the txnAccepted reference.
 			return LAGGING;
 		}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/LocalReplica.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/LocalReplica.java
index 6f1f5c5..cd0ded5 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/LocalReplica.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/LocalReplica.java
@@ -123,21 +123,18 @@
 	/** {@inheritDoc} */
 	@Override
 	protected void startPush(ReplicaPushRequest req) {
-		getSystem().getExecutor().execute(new Runnable() {
-			@Override
-			public void run() {
-				MonotonicClock clk = getSystem().getClock();
-				try (Repository git = getLeader().openRepository();
-						ProposedTimestamp ts = clk.propose()) {
-					try {
-						update(git, req, ts);
-						req.done(git);
-					} catch (Throwable err) {
-						req.setException(git, err);
-					}
-				} catch (IOException err) {
-					req.setException(null, err);
+		getSystem().getExecutor().execute(() -> {
+			MonotonicClock clk = getSystem().getClock();
+			try (Repository git = getLeader().openRepository();
+					ProposedTimestamp ts = clk.propose()) {
+				try {
+					update(git, req, ts);
+					req.done(git);
+				} catch (Throwable err) {
+					req.setException(git, err);
 				}
+			} catch (IOException err) {
+				req.setException(null, err);
 			}
 		});
 	}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/RemoteGitReplica.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/RemoteGitReplica.java
index b61274e..4bed575 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/RemoteGitReplica.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/RemoteGitReplica.java
@@ -139,19 +139,16 @@
 	/** {@inheritDoc} */
 	@Override
 	protected void startPush(ReplicaPushRequest req) {
-		getSystem().getExecutor().execute(new Runnable() {
-			@Override
-			public void run() {
-				try (Repository git = getLeader().openRepository()) {
-					try {
-						push(git, req);
-						req.done(git);
-					} catch (Throwable err) {
-						req.setException(git, err);
-					}
-				} catch (IOException err) {
-					req.setException(null, err);
+		getSystem().getExecutor().execute(() -> {
+			try (Repository git = getLeader().openRepository()) {
+				try {
+					push(git, req);
+					req.done(git);
+				} catch (Throwable err) {
+					req.setException(git, err);
 				}
+			} catch (IOException err) {
+				req.setException(null, err);
 			}
 		});
 	}
@@ -203,7 +200,7 @@
 	private static boolean isExpectedValue(Map<String, Ref> adv,
 			RemoteRefUpdate u) {
 		Ref r = adv.get(u.getRemoteName());
-		if (!AnyObjectId.equals(getId(r), u.getExpectedOldObjectId())) {
+		if (!AnyObjectId.isEqual(getId(r), u.getExpectedOldObjectId())) {
 			((RemoteCommand) u).cmd.setResult(LOCK_FAILURE);
 			return false;
 		}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/StageBuilder.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/StageBuilder.java
index ae82dce..815984d 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/StageBuilder.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/StageBuilder.java
@@ -138,7 +138,7 @@
 		try (RevWalk rw = new RevWalk(git);
 				TreeWalk tw = new TreeWalk(rw.getObjectReader());
 				ObjectInserter ins = git.newObjectInserter()) {
-			if (AnyObjectId.equals(oldTree, ObjectId.zeroId())) {
+			if (AnyObjectId.isEqual(oldTree, ObjectId.zeroId())) {
 				tw.addTree(new EmptyTreeIterator());
 			} else {
 				tw.addTree(rw.parseTree(oldTree));
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DeltaBaseCache.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DeltaBaseCache.java
index bd4b4d2..3b92dee 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DeltaBaseCache.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DeltaBaseCache.java
@@ -180,8 +180,8 @@
 
 	int getMemoryUsedByTableForTest() {
 		int r = 0;
-		for (int i = 0; i < table.length; i++) {
-			for (Entry e = table[i]; e != null; e = e.tableNext) {
+		for (Entry t : table) {
+			for (Entry e = t; e != null; e = e.tableNext) {
 				r += e.data.length;
 			}
 		}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsGarbageCollector.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsGarbageCollector.java
index ca11fb9..f10a1d8 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsGarbageCollector.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsGarbageCollector.java
@@ -661,7 +661,7 @@
 	private int objectsBefore() {
 		int cnt = 0;
 		for (DfsPackFile p : packsBefore)
-			cnt += p.getPackDescription().getObjectCount();
+			cnt += (int) p.getPackDescription().getObjectCount();
 		return cnt;
 	}
 
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackCompactor.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackCompactor.java
index 127ee6b..6f3f2bd 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackCompactor.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackCompactor.java
@@ -469,12 +469,8 @@
 					continue SCAN;
 			want.add(new ObjectIdWithOffset(id, ent.getOffset()));
 		}
-		Collections.sort(want, new Comparator<ObjectIdWithOffset>() {
-			@Override
-			public int compare(ObjectIdWithOffset a, ObjectIdWithOffset b) {
-				return Long.signum(a.offset - b.offset);
-			}
-		});
+		Collections.sort(want, (ObjectIdWithOffset a,
+				ObjectIdWithOffset b) -> Long.signum(a.offset - b.offset));
 		return want;
 	}
 
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackParser.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackParser.java
index 3a30d7d..e4c37cb 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackParser.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackParser.java
@@ -429,10 +429,10 @@
 		final byte[] buf = buffer();
 		int sz = data.length;
 		int len = 0;
-		buf[len++] = (byte) ((typeCode << 4) | sz & 15);
+		buf[len++] = (byte) ((typeCode << 4) | (sz & 15));
 		sz >>>= 4;
 		while (sz > 0) {
-			buf[len - 1] |= 0x80;
+			buf[len - 1] |= (byte) 0x80;
 			buf[len++] = (byte) (sz & 0x7f);
 			sz >>>= 7;
 		}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReader.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReader.java
index d04709f..c75b88f 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReader.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReader.java
@@ -287,14 +287,12 @@
 		return Collections.emptySet();
 	}
 
-	private static final Comparator<FoundObject<?>> FOUND_OBJECT_SORT = new Comparator<FoundObject<?>>() {
-		@Override
-		public int compare(FoundObject<?> a, FoundObject<?> b) {
-			int cmp = a.packIndex - b.packIndex;
-			if (cmp == 0)
-				cmp = Long.signum(a.offset - b.offset);
-			return cmp;
-		}
+	private static final Comparator<FoundObject<?>> FOUND_OBJECT_SORT = (
+			FoundObject<?> a, FoundObject<?> b) -> {
+		int cmp = a.packIndex - b.packIndex;
+		if (cmp == 0)
+			cmp = Long.signum(a.offset - b.offset);
+		return cmp;
 	};
 
 	private static class FoundObject<T extends ObjectId> {
@@ -565,12 +563,9 @@
 		return new DfsObjectToPack(objectId, type);
 	}
 
-	private static final Comparator<DfsObjectToPack> OFFSET_SORT = new Comparator<DfsObjectToPack>() {
-		@Override
-		public int compare(DfsObjectToPack a, DfsObjectToPack b) {
-			return Long.signum(a.getOffset() - b.getOffset());
-		}
-	};
+	private static final Comparator<DfsObjectToPack> OFFSET_SORT = (
+			DfsObjectToPack a,
+			DfsObjectToPack b) -> Long.signum(a.getOffset() - b.getOffset());
 
 	@Override
 	public void selectObjectRepresentation(PackWriter packer,
@@ -757,6 +752,7 @@
 	 */
 	int inflate(DfsPackFile pack, long position, byte[] dstbuf,
 			boolean headerOnly) throws IOException, DataFormatException {
+		long start = System.nanoTime();
 		prepareInflater();
 		pin(pack, position);
 		position += block.setInput(position, inf);
@@ -765,6 +761,7 @@
 			dstoff += n;
 			if (inf.finished() || (headerOnly && dstoff == dstbuf.length)) {
 				stats.inflatedBytes += dstoff;
+				stats.inflationMicros += BlockBasedFile.elapsedMicros(start);
 				return dstoff;
 			} else if (inf.needsInput()) {
 				pin(pack, position);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReaderIoStats.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReaderIoStats.java
index c35801f..d6401a1 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReaderIoStats.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReaderIoStats.java
@@ -85,6 +85,9 @@
 		/** Total number of bytes decompressed. */
 		long inflatedBytes;
 
+		/** Total microseconds spent inflating compressed bytes. */
+		long inflationMicros;
+
 		Accumulator() {
 		}
 	}
@@ -186,4 +189,13 @@
 	public long getInflatedBytes() {
 		return stats.inflatedBytes;
 	}
+
+	/**
+	 * Get total microseconds spent inflating compressed bytes.
+	 *
+	 * @return total microseconds inflating compressed bytes.
+	 */
+	public long getInflationMicros() {
+		return stats.inflationMicros;
+	}
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsRefDatabase.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsRefDatabase.java
index 8b2a03d..732cd4d 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsRefDatabase.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsRefDatabase.java
@@ -43,6 +43,7 @@
 
 package org.eclipse.jgit.internal.storage.dfs;
 
+import static org.eclipse.jgit.lib.Ref.UNDEFINED_UPDATE_INDEX;
 import static org.eclipse.jgit.lib.Ref.Storage.NEW;
 
 import java.io.IOException;
@@ -175,7 +176,7 @@
 			cachePeeledState(oldLeaf, newLeaf);
 		}
 
-		return recreate(ref, newLeaf);
+		return recreate(ref, newLeaf, hasVersioning());
 	}
 
 	Ref doPeel(Ref leaf) throws MissingObjectException,
@@ -187,20 +188,26 @@
 						leaf.getStorage(),
 						leaf.getName(),
 						leaf.getObjectId(),
-						rw.peel(obj).copy());
+						rw.peel(obj).copy(),
+						hasVersioning() ? leaf.getUpdateIndex()
+								: UNDEFINED_UPDATE_INDEX);
 			} else {
 				return new ObjectIdRef.PeeledNonTag(
 						leaf.getStorage(),
 						leaf.getName(),
-						leaf.getObjectId());
+						leaf.getObjectId(),
+						hasVersioning() ? leaf.getUpdateIndex()
+								: UNDEFINED_UPDATE_INDEX);
 			}
 		}
 	}
 
-	static Ref recreate(Ref old, Ref leaf) {
+	static Ref recreate(Ref old, Ref leaf, boolean hasVersioning) {
 		if (old.isSymbolic()) {
-			Ref dst = recreate(old.getTarget(), leaf);
-			return new SymbolicRef(old.getName(), dst);
+			Ref dst = recreate(old.getTarget(), leaf, hasVersioning);
+			return new SymbolicRef(old.getName(), dst,
+					hasVersioning ? old.getUpdateIndex()
+							: UNDEFINED_UPDATE_INDEX);
 		}
 		return leaf;
 	}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReftableDatabase.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReftableDatabase.java
index 83394bb..6050c15 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReftableDatabase.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReftableDatabase.java
@@ -47,8 +47,10 @@
 import java.util.Arrays;
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.concurrent.locks.ReentrantLock;
 
 import org.eclipse.jgit.annotations.Nullable;
@@ -277,12 +279,31 @@
 
 	/** {@inheritDoc} */
 	@Override
+	public Set<Ref> getTipsWithSha1(ObjectId id) throws IOException {
+		if (!getReftableConfig().isIndexObjects()) {
+			return super.getTipsWithSha1(id);
+		}
+		lock.lock();
+		try {
+			RefCursor cursor = reader().byObjectId(id);
+			Set<Ref> refs = new HashSet<>();
+			while (cursor.next()) {
+				refs.add(cursor.getRef());
+			}
+			return refs;
+		} finally {
+			lock.unlock();
+		}
+	}
+
+	/** {@inheritDoc} */
+	@Override
 	public Ref peel(Ref ref) throws IOException {
 		Ref oldLeaf = ref.getLeaf();
 		if (oldLeaf.isPeeled() || oldLeaf.getObjectId() == null) {
 			return ref;
 		}
-		return recreate(ref, doPeel(oldLeaf));
+		return recreate(ref, doPeel(oldLeaf), hasVersioning());
 	}
 
 	@Override
@@ -315,6 +336,7 @@
 			throws IOException {
 		ReceiveCommand cmd = toCommand(oldRef, newRef);
 		try (RevWalk rw = new RevWalk(getRepository())) {
+			rw.setRetainBody(false);
 			newBatchUpdate().setAllowNonFastForwards(true).addCommand(cmd)
 					.execute(rw, NullProgressMonitor.INSTANCE);
 		}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsRepository.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsRepository.java
index 5169e92..8e5c5a7 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsRepository.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsRepository.java
@@ -126,6 +126,12 @@
 
 	/** {@inheritDoc} */
 	@Override
+	public String getIdentifier() {
+		return getDescription().getRepositoryName();
+	}
+
+	/** {@inheritDoc} */
+	@Override
 	public void scanForRepoChanges() throws IOException {
 		getRefDatabase().refresh();
 		getObjectDatabase().clearCache();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/ReftableBatchRefUpdate.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/ReftableBatchRefUpdate.java
index 47ac4ec..07fd00f 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/ReftableBatchRefUpdate.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/ReftableBatchRefUpdate.java
@@ -252,7 +252,7 @@
 
 	private static boolean matchOld(ReceiveCommand cmd, @Nullable Ref ref) {
 		if (ref == null) {
-			return AnyObjectId.equals(ObjectId.zeroId(), cmd.getOldId())
+			return AnyObjectId.isEqual(ObjectId.zeroId(), cmd.getOldId())
 					&& cmd.getOldSymref() == null;
 		} else if (ref.isSymbolic()) {
 			return ref.getTarget().getName().equals(cmd.getOldSymref());
@@ -368,7 +368,7 @@
 			String name = cmd.getRefName();
 			ObjectId newId = cmd.getNewId();
 			String newSymref = cmd.getNewSymref();
-			if (AnyObjectId.equals(ObjectId.zeroId(), newId)
+			if (AnyObjectId.isEqual(ObjectId.zeroId(), newId)
 					&& newSymref == null) {
 				refs.add(new ObjectIdRef.Unpeeled(NEW, name, null));
 				continue;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileRepository.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileRepository.java
index 356d64b..4f5f8a6 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileRepository.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileRepository.java
@@ -62,8 +62,6 @@
 import org.eclipse.jgit.attributes.AttributesNode;
 import org.eclipse.jgit.attributes.AttributesNodeProvider;
 import org.eclipse.jgit.errors.ConfigInvalidException;
-import org.eclipse.jgit.events.ConfigChangedEvent;
-import org.eclipse.jgit.events.ConfigChangedListener;
 import org.eclipse.jgit.events.IndexChangedEvent;
 import org.eclipse.jgit.internal.JGitText;
 import org.eclipse.jgit.internal.storage.file.ObjectDirectory.AlternateHandle;
@@ -192,12 +190,7 @@
 				getFS());
 		loadRepoConfig();
 
-		repoConfig.addChangeListener(new ConfigChangedListener() {
-			@Override
-			public void onConfigChanged(ConfigChangedEvent event) {
-				fireEvent(event);
-			}
-		});
+		repoConfig.addChangeListener(this::fireEvent);
 
 		final long repositoryFormatVersion = getConfig().getLong(
 				ConfigConstants.CONFIG_CORE_SECTION, null,
@@ -361,6 +354,17 @@
 
 	/** {@inheritDoc} */
 	@Override
+	public String getIdentifier() {
+		File directory = getDirectory();
+		if (directory != null) {
+			return directory.getPath();
+		} else {
+			throw new IllegalStateException();
+		}
+	}
+
+	/** {@inheritDoc} */
+	@Override
 	public FileBasedConfig getConfig() {
 		try {
 			SystemReader.getInstance().getUserConfig();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileSnapshot.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileSnapshot.java
index 487ae06..93158f6 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileSnapshot.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileSnapshot.java
@@ -417,6 +417,7 @@
 	 *            the other snapshot.
 	 * @return true if the two snapshots share the same information.
 	 */
+	@SuppressWarnings("NonOverridingEquals")
 	public boolean equals(FileSnapshot other) {
 		boolean sizeEq = size == UNKNOWN_SIZE || other.size == UNKNOWN_SIZE || size == other.size;
 		return lastModified.equals(other.lastModified) && sizeEq
@@ -494,7 +495,7 @@
 	}
 
 	/** {@inheritDoc} */
-	@SuppressWarnings("nls")
+	@SuppressWarnings({ "nls", "ReferenceEquality" })
 	@Override
 	public String toString() {
 		if (this == DIRTY) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GC.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GC.java
index 104d8d4..0ff3f61 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GC.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GC.java
@@ -246,7 +246,7 @@
 	 *             If the configuration parameter "gc.pruneexpire" couldn't be
 	 *             parsed
 	 */
-	// TODO(ms): in 5.0 change signature and return Future<Collection<PackFile>>
+	// TODO(ms): change signature and return Future<Collection<PackFile>>
 	@SuppressWarnings("FutureReturnValueIgnored")
 	public Collection<PackFile> gc() throws IOException, ParseException {
 		if (!background) {
@@ -283,7 +283,7 @@
 			}
 			return Collections.emptyList();
 		};
-		// TODO(ms): in 5.0 change signature and return the Future
+		// TODO(ms): change signature and return the Future
 		executor().submit(gcTask);
 		return Collections.emptyList();
 	}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LockFile.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LockFile.java
index 548ea5a..eda4da8 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LockFile.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LockFile.java
@@ -120,12 +120,8 @@
 	}
 
 	/** Filter to skip over active lock files when listing a directory. */
-	static final FilenameFilter FILTER = new FilenameFilter() {
-		@Override
-		public boolean accept(File dir, String name) {
-			return !name.endsWith(LOCK_SUFFIX);
-		}
-	};
+	static final FilenameFilter FILTER = (File dir,
+			String name) -> !name.endsWith(LOCK_SUFFIX);
 
 	private final File ref;
 
@@ -250,13 +246,7 @@
 			// Don't worry about a file that doesn't exist yet, it
 			// conceptually has no current content to copy.
 			//
-		} catch (IOException ioe) {
-			unlock();
-			throw ioe;
-		} catch (RuntimeException ioe) {
-			unlock();
-			throw ioe;
-		} catch (Error ioe) {
+		} catch (IOException | RuntimeException | Error ioe) {
 			unlock();
 			throw ioe;
 		}
@@ -310,13 +300,7 @@
 			}
 			os.close();
 			os = null;
-		} catch (IOException ioe) {
-			unlock();
-			throw ioe;
-		} catch (RuntimeException ioe) {
-			unlock();
-			throw ioe;
-		} catch (Error ioe) {
+		} catch (IOException | RuntimeException | Error ioe) {
 			unlock();
 			throw ioe;
 		}
@@ -364,13 +348,7 @@
 						os.getChannel().force(true);
 					out.close();
 					os = null;
-				} catch (IOException ioe) {
-					unlock();
-					throw ioe;
-				} catch (RuntimeException ioe) {
-					unlock();
-					throw ioe;
-				} catch (Error ioe) {
+				} catch (IOException | RuntimeException | Error ioe) {
 					unlock();
 					throw ioe;
 				}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectoryPackParser.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectoryPackParser.java
index 889a510..8fa683f 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectoryPackParser.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectoryPackParser.java
@@ -353,10 +353,10 @@
 		final byte[] buf = buffer();
 		int sz = data.length;
 		int len = 0;
-		buf[len++] = (byte) ((typeCode << 4) | sz & 15);
+		buf[len++] = (byte) ((typeCode << 4) | (sz & 15));
 		sz >>>= 4;
 		while (sz > 0) {
-			buf[len - 1] |= 0x80;
+			buf[len - 1] |= (byte) 0x80;
 			buf[len++] = (byte) (sz & 0x7f);
 			sz >>>= 7;
 		}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackBitmapIndexBuilder.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackBitmapIndexBuilder.java
index eff7958..9941ff3 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackBitmapIndexBuilder.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackBitmapIndexBuilder.java
@@ -45,7 +45,6 @@
 
 import java.text.MessageFormat;
 import java.util.Collections;
-import java.util.Comparator;
 import java.util.Iterator;
 import java.util.List;
 import java.util.NoSuchElementException;
@@ -133,12 +132,8 @@
 		for (int i = 0; i < entries.size(); i++) {
 			positionEntries.add(new PositionEntry(entries.get(i), i));
 		}
-		Collections.sort(entries, new Comparator<ObjectToPack>() {
-			@Override
-			public int compare(ObjectToPack a, ObjectToPack b) {
-				return Long.signum(a.getOffset() - b.getOffset());
-			}
-		});
+		Collections.sort(entries, (ObjectToPack a, ObjectToPack b) -> Long
+				.signum(a.getOffset() - b.getOffset()));
 		for (int i = 0; i < entries.size(); i++) {
 			PositionEntry e = positionEntries.get(entries.get(i));
 			e.offsetPosition = i;
@@ -310,57 +305,55 @@
 	public Iterable<StoredEntry> getCompressedBitmaps() {
 		// Add order is from oldest to newest. The reverse add order is the
 		// output order.
-		return new Iterable<StoredEntry>() {
+		return () -> new Iterator<StoredEntry>() {
+
+			private int index = byAddOrder.size() - 1;
+
 			@Override
-			public Iterator<StoredEntry> iterator() {
-				return new Iterator<StoredEntry>() {
-					private int index = byAddOrder.size() - 1;
+			public boolean hasNext() {
+				return index >= 0;
+			}
 
-					@Override
-					public boolean hasNext() {
-						return index >= 0;
+			@Override
+			public StoredEntry next() {
+				if (!hasNext()) {
+					throw new NoSuchElementException();
+				}
+				StoredBitmap item = byAddOrder.get(index);
+				int bestXorOffset = 0;
+				EWAHCompressedBitmap bestBitmap = item.getBitmap();
+
+				// Attempt to compress the bitmap with an XOR of the
+				// previously written entries.
+				for (int i = 1; i <= MAX_XOR_OFFSET_SEARCH; i++) {
+					int curr = i + index;
+					if (curr >= byAddOrder.size()) {
+						break;
 					}
 
-					@Override
-					public StoredEntry next() {
-						if (!hasNext())
-							throw new NoSuchElementException();
-						StoredBitmap item = byAddOrder.get(index);
-						int bestXorOffset = 0;
-						EWAHCompressedBitmap bestBitmap = item.getBitmap();
+					StoredBitmap other = byAddOrder.get(curr);
+					EWAHCompressedBitmap bitmap = other.getBitmap()
+							.xor(item.getBitmap());
 
-						// Attempt to compress the bitmap with an XOR of the
-						// previously written entries.
-						for (int i = 1; i <= MAX_XOR_OFFSET_SEARCH; i++) {
-							int curr = i + index;
-							if (curr >= byAddOrder.size())
-								break;
-
-							StoredBitmap other = byAddOrder.get(curr);
-							EWAHCompressedBitmap bitmap = other.getBitmap()
-									.xor(item.getBitmap());
-
-							if (bitmap.sizeInBytes()
-									< bestBitmap.sizeInBytes()) {
-								bestBitmap = bitmap;
-								bestXorOffset = i;
-							}
-						}
-						index--;
-
-						PositionEntry entry = positionEntries.get(item);
-						if (entry == null)
-							throw new IllegalStateException();
-						bestBitmap.trim();
-						return new StoredEntry(entry.namePosition, bestBitmap,
-								bestXorOffset, item.getFlags());
+					if (bitmap.sizeInBytes() < bestBitmap.sizeInBytes()) {
+						bestBitmap = bitmap;
+						bestXorOffset = i;
 					}
+				}
+				index--;
 
-					@Override
-					public void remove() {
-						throw new UnsupportedOperationException();
-					}
-				};
+				PositionEntry entry = positionEntries.get(item);
+				if (entry == null) {
+					throw new IllegalStateException();
+				}
+				bestBitmap.trim();
+				return new StoredEntry(entry.namePosition, bestBitmap,
+						bestXorOffset, item.getFlags());
+			}
+
+			@Override
+			public void remove() {
+				throw new UnsupportedOperationException();
 			}
 		};
 	}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectory.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectory.java
index 9542025..8cc9383 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectory.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectory.java
@@ -716,7 +716,7 @@
 		for (LockFile ol : heldLocks.values()) {
 			ol.requireLock();
 		}
-		if (refs.size() == 0) {
+		if (refs.isEmpty()) {
 			return null;
 		}
 		FS fs = parent.getFS();
@@ -1099,9 +1099,11 @@
 
 		// check whether the found new ref is the an additional ref. These refs
 		// should not go into looseRefs
-		for (int i = 0; i < additionalRefsNames.length; i++)
-			if (name.equals(additionalRefsNames[i]))
+		for (String additionalRefsName : additionalRefsNames) {
+			if (name.equals(additionalRefsName)) {
 				return n;
+			}
+		}
 
 		if (looseRefs.compareAndSet(curList, curList.add(idx, n)))
 			modCnt.incrementAndGet();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/UnpackedObject.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/UnpackedObject.java
index cf474af..79f1307 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/UnpackedObject.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/UnpackedObject.java
@@ -295,7 +295,7 @@
 		 * can always correctly determine the buffer format.
 		 */
 		final int fb = hdr[0] & 0xff;
-		return (fb & 0x8f) == 0x08 && (((fb << 8) | hdr[1] & 0xff) % 31) == 0;
+		return (fb & 0x8f) == 0x08 && (((fb << 8) | (hdr[1] & 0xff)) % 31) == 0;
 	}
 
 	static InputStream inflate(final InputStream in, final long size,
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/UnpackedObjectCache.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/UnpackedObjectCache.java
index 967754a..ea0d269 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/UnpackedObjectCache.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/UnpackedObjectCache.java
@@ -112,7 +112,7 @@
 				if (obj == null)
 					break;
 
-				if (AnyObjectId.equals(obj, toFind))
+				if (AnyObjectId.isEqual(obj, toFind))
 					return true;
 
 				if (++i == ids.length())
@@ -132,7 +132,7 @@
 						continue;
 				}
 
-				if (AnyObjectId.equals(obj, toAdd))
+				if (AnyObjectId.isEqual(obj, toAdd))
 					return true;
 
 				if (++i == ids.length())
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/BinaryDelta.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/BinaryDelta.java
index c7e5ad6..5f69d0a 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/BinaryDelta.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/BinaryDelta.java
@@ -142,7 +142,7 @@
 		int c, shift = 0;
 		do {
 			c = delta[deltaPtr++] & 0xff;
-			baseLen |= ((long) (c & 0x7f)) << shift;
+			baseLen |= (c & 0x7f) << shift;
 			shift += 7;
 		} while ((c & 0x80) != 0);
 		if (base.length != baseLen)
@@ -155,7 +155,7 @@
 		shift = 0;
 		do {
 			c = delta[deltaPtr++] & 0xff;
-			resLen |= ((long) (c & 0x7f)) << shift;
+			resLen |= (c & 0x7f) << shift;
 			shift += 7;
 		} while ((c & 0x80) != 0);
 
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/DeltaTask.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/DeltaTask.java
index 0347644..a211d16 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/DeltaTask.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/DeltaTask.java
@@ -46,7 +46,6 @@
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collections;
-import java.util.Comparator;
 import java.util.Iterator;
 import java.util.LinkedList;
 import java.util.List;
@@ -212,12 +211,8 @@
 			}
 
 			// Sort by starting index to identify gaps later.
-			Collections.sort(topPaths, new Comparator<WeightedPath>() {
-				@Override
-				public int compare(WeightedPath a, WeightedPath b) {
-					return a.slice.beginIndex - b.slice.beginIndex;
-				}
-			});
+			Collections.sort(topPaths, (WeightedPath a,
+					WeightedPath b) -> a.slice.beginIndex - b.slice.beginIndex);
 
 			bytesPerUnit = 1;
 			while (MAX_METER <= (totalWeight / bytesPerUnit)) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/DeltaWindow.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/DeltaWindow.java
index a047534..d152a39 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/DeltaWindow.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/DeltaWindow.java
@@ -365,9 +365,7 @@
 
 				resObj.setCachedDelta(deltaCache.cache(zbuf, len, deltaLen));
 				resObj.setCachedSize(deltaLen);
-			} catch (IOException err) {
-				deltaCache.credit(deltaLen);
-			} catch (OutOfMemoryError err) {
+			} catch (IOException | OutOfMemoryError err) {
 				deltaCache.credit(deltaLen);
 			}
 		}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriter.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriter.java
index 1e3d74a..6506789 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriter.java
@@ -44,6 +44,7 @@
 
 package org.eclipse.jgit.internal.storage.pack;
 
+import static java.util.Objects.requireNonNull;
 import static org.eclipse.jgit.internal.storage.pack.StoredObjectRepresentation.PACK_DELTA;
 import static org.eclipse.jgit.internal.storage.pack.StoredObjectRepresentation.PACK_WHOLE;
 import static org.eclipse.jgit.lib.Constants.OBJECT_ID_LENGTH;
@@ -61,7 +62,7 @@
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
-import java.util.Comparator;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
@@ -119,6 +120,7 @@
 import org.eclipse.jgit.revwalk.RevTree;
 import org.eclipse.jgit.storage.pack.PackConfig;
 import org.eclipse.jgit.storage.pack.PackStatistics;
+import org.eclipse.jgit.transport.FilterSpec;
 import org.eclipse.jgit.transport.ObjectCountCallback;
 import org.eclipse.jgit.transport.WriteAbortedException;
 import org.eclipse.jgit.util.BlockList;
@@ -303,7 +305,7 @@
 
 	private ObjectCountCallback callback;
 
-	private long filterBlobLimit = -1;
+	private FilterSpec filterSpec = FilterSpec.NO_FILTER;
 
 	/**
 	 * Create writer for specified repository.
@@ -641,10 +643,11 @@
 	}
 
 	/**
-	 * @param bytes exclude blobs of size greater than this
+	 * @param filter the filter which indicates what and what not this writer
+	 *            should include
 	 */
-	public void setFilterBlobLimit(long bytes) {
-		filterBlobLimit = bytes;
+	public void setFilterSpec(@NonNull FilterSpec filter) {
+		filterSpec = requireNonNull(filter);
 	}
 
 	/**
@@ -870,6 +873,37 @@
 	}
 
 	/**
+	 * A visitation policy which uses the depth at which the object is seen to
+	 * decide if re-traversal is necessary. In particular, if the object has
+	 * already been visited at this depth or shallower, it is not necessary to
+	 * re-visit at this depth.
+	 */
+	private static class DepthAwareVisitationPolicy
+			implements ObjectWalk.VisitationPolicy {
+		private final Map<ObjectId, Integer> lowestDepthVisited = new HashMap<>();
+
+		private final ObjectWalk walk;
+
+		DepthAwareVisitationPolicy(ObjectWalk walk) {
+			this.walk = requireNonNull(walk);
+		}
+
+		@Override
+		public boolean shouldVisit(RevObject o) {
+			Integer lastDepth = lowestDepthVisited.get(o);
+			if (lastDepth == null) {
+				return true;
+			}
+			return walk.getTreeDepth() < lastDepth.intValue();
+		}
+
+		@Override
+		public void visited(RevObject o) {
+			lowestDepthVisited.put(o, Integer.valueOf(walk.getTreeDepth()));
+		}
+	}
+
+	/**
 	 * Prepare the list of objects to be written to the pack stream.
 	 * <p>
 	 * Basing on these 2 sets, another set of objects to put in a pack file is
@@ -910,6 +944,9 @@
 		if (shallowPack && !(walk instanceof DepthWalk.ObjectWalk))
 			throw new IllegalArgumentException(
 					JGitText.get().shallowPacksRequireDepthWalk);
+		if (filterSpec.getTreeDepthLimit() >= 0) {
+			walk.setVisitationPolicy(new DepthAwareVisitationPolicy(walk));
+		}
 		findObjectsToPack(countingMonitor, walk, interestingObjects,
 				uninterestingObjects, noBitmaps);
 	}
@@ -1380,32 +1417,33 @@
 		// applies "Linus' Law" which states that newer files tend to be the
 		// bigger ones, because source files grow and hardly ever shrink.
 		//
-		Arrays.sort(list, 0, cnt, new Comparator<ObjectToPack>() {
-			@Override
-			public int compare(ObjectToPack a, ObjectToPack b) {
-				int cmp = (a.isDoNotDelta() ? 1 : 0)
-						- (b.isDoNotDelta() ? 1 : 0);
-				if (cmp != 0)
-					return cmp;
-
-				cmp = a.getType() - b.getType();
-				if (cmp != 0)
-					return cmp;
-
-				cmp = (a.getPathHash() >>> 1) - (b.getPathHash() >>> 1);
-				if (cmp != 0)
-					return cmp;
-
-				cmp = (a.getPathHash() & 1) - (b.getPathHash() & 1);
-				if (cmp != 0)
-					return cmp;
-
-				cmp = (a.isEdge() ? 0 : 1) - (b.isEdge() ? 0 : 1);
-				if (cmp != 0)
-					return cmp;
-
-				return b.getWeight() - a.getWeight();
+		Arrays.sort(list, 0, cnt, (ObjectToPack a, ObjectToPack b) -> {
+			int cmp = (a.isDoNotDelta() ? 1 : 0) - (b.isDoNotDelta() ? 1 : 0);
+			if (cmp != 0) {
+				return cmp;
 			}
+
+			cmp = a.getType() - b.getType();
+			if (cmp != 0) {
+				return cmp;
+			}
+
+			cmp = (a.getPathHash() >>> 1) - (b.getPathHash() >>> 1);
+			if (cmp != 0) {
+				return cmp;
+			}
+
+			cmp = (a.getPathHash() & 1) - (b.getPathHash() & 1);
+			if (cmp != 0) {
+				return cmp;
+			}
+
+			cmp = (a.isEdge() ? 0 : 1) - (b.isEdge() ? 0 : 1);
+			if (cmp != 0) {
+				return cmp;
+			}
+
+			return b.getWeight() - a.getWeight();
 		});
 
 		// Above we stored the objects we cannot delta onto the end.
@@ -1499,7 +1537,7 @@
 
 		Executor executor = config.getExecutor();
 		final List<Throwable> errors =
-				Collections.synchronizedList(new ArrayList<Throwable>(threads));
+				Collections.synchronizedList(new ArrayList<>(threads));
 		if (executor instanceof ExecutorService) {
 			// Caller supplied us a service, use it directly.
 			runTasks((ExecutorService) executor, pm, taskBlock, errors);
@@ -1526,14 +1564,11 @@
 			// asynchronous execution.  Wrap everything and hope it
 			// can schedule these for us.
 			for (DeltaTask task : taskBlock.tasks) {
-				executor.execute(new Runnable() {
-					@Override
-					public void run() {
-						try {
-							task.call();
-						} catch (Throwable failure) {
-							errors.add(failure);
-						}
+				executor.execute(() -> {
+					try {
+						task.call();
+					} catch (Throwable failure) {
+						errors.add(failure);
 					}
 				});
 			}
@@ -1969,7 +2004,9 @@
 				byte[] pathBuf = walker.getPathBuffer();
 				int pathLen = walker.getPathLength();
 				bases.addBase(o.getType(), pathBuf, pathLen, pathHash);
-				filterAndAddObject(o, o.getType(), pathHash, want);
+				if (!depthSkip(o, walker)) {
+					filterAndAddObject(o, o.getType(), pathHash, want);
+				}
 				countingMonitor.update(1);
 			}
 		} else {
@@ -1979,7 +2016,10 @@
 					continue;
 				if (exclude(o))
 					continue;
-				filterAndAddObject(o, o.getType(), walker.getPathHashCode(), want);
+				if (!depthSkip(o, walker)) {
+					filterAndAddObject(o, o.getType(), walker.getPathHashCode(),
+									   want);
+				}
 				countingMonitor.update(1);
 			}
 		}
@@ -2071,6 +2111,44 @@
 		objectsMap.add(otp);
 	}
 
+	/**
+	 * Determines if the object should be omitted from the pack as a result of
+	 * its depth (probably because of the tree:<depth> filter).
+	 * <p>
+	 * Causes {@code walker} to skip traversing the current tree, which ought to
+	 * have just started traversal, assuming this method is called as soon as a
+	 * new depth is reached.
+	 * <p>
+	 * This method increments the {@code treesTraversed} statistic.
+	 *
+	 * @param obj
+	 *            the object to check whether it should be omitted.
+	 * @param walker
+	 *            the walker being used for traveresal.
+	 * @return whether the given object should be skipped.
+	 */
+	private boolean depthSkip(@NonNull RevObject obj, ObjectWalk walker) {
+		long treeDepth = walker.getTreeDepth();
+
+		// Check if this object needs to be rejected because it is a tree or
+		// blob that is too deep from the root tree.
+
+		// A blob is considered one level deeper than the tree that contains it.
+		if (obj.getType() == OBJ_BLOB) {
+			treeDepth++;
+		} else {
+			stats.treesTraversed++;
+		}
+
+		if (filterSpec.getTreeDepthLimit() < 0 ||
+			treeDepth <= filterSpec.getTreeDepthLimit()) {
+			return false;
+		}
+
+		walker.skipTree();
+		return true;
+	}
+
 	// Adds the given object as an object to be packed, first performing
 	// filtering on blobs at or exceeding a given size.
 	private void filterAndAddObject(@NonNull AnyObjectId src, int type,
@@ -2079,10 +2157,10 @@
 
 		// Check if this object needs to be rejected, doing the cheaper
 		// checks first.
-		boolean reject = filterBlobLimit >= 0 &&
+		boolean reject = filterSpec.getBlobLimit() >= 0 &&
 			type == OBJ_BLOB &&
 			!want.contains(src) &&
-			reader.getObjectSize(src, OBJ_BLOB) > filterBlobLimit;
+			reader.getObjectSize(src, OBJ_BLOB) > filterSpec.getBlobLimit();
 		if (!reject) {
 			addObject(src, type, pathHashCode);
 		}
@@ -2120,7 +2198,7 @@
 		if (!cachedPacks.isEmpty()) {
 			if (otp.isEdge())
 				return;
-			if ((nFmt == PACK_WHOLE) | (nFmt == PACK_DELTA)) {
+			if (nFmt == PACK_WHOLE || nFmt == PACK_DELTA) {
 				for (CachedPack pack : cachedPacks) {
 					if (pack.hasObject(otp, next)) {
 						otp.setEdge();
@@ -2163,7 +2241,7 @@
 			otp.clearReuseAsIs();
 		}
 
-		otp.setDeltaAttempted(reuseDeltas & next.wasDeltaAttempted());
+		otp.setDeltaAttempted(reuseDeltas && next.wasDeltaAttempted());
 		otp.select(next);
 	}
 
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriterBitmapPreparer.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriterBitmapPreparer.java
index 99db749..d3b5e12 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriterBitmapPreparer.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriterBitmapPreparer.java
@@ -91,12 +91,9 @@
 
 	private static final int DAY_IN_SECONDS = 24 * 60 * 60;
 
-	private static final Comparator<RevCommit> ORDER_BY_REVERSE_TIMESTAMP = new Comparator<RevCommit>() {
-		@Override
-		public int compare(RevCommit a, RevCommit b) {
-			return Integer.signum(b.getCommitTime() - a.getCommitTime());
-		}
-	};
+	private static final Comparator<RevCommit> ORDER_BY_REVERSE_TIMESTAMP = (
+			RevCommit a, RevCommit b) -> Integer
+					.signum(b.getCommitTime() - a.getCommitTime());
 
 	private final ObjectReader reader;
 	private final ProgressMonitor pm;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/transport/http/NetscapeCookieFile.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/transport/http/NetscapeCookieFile.java
new file mode 100644
index 0000000..e16adb9
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/transport/http/NetscapeCookieFile.java
@@ -0,0 +1,488 @@
+/*
+ * Copyright (C) 2018, Konrad Windszus <konrad_w@gmx.de>
+ * 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.http;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.io.StringReader;
+import java.io.Writer;
+import java.net.HttpCookie;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Path;
+import java.text.MessageFormat;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Date;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+import org.eclipse.jgit.annotations.NonNull;
+import org.eclipse.jgit.annotations.Nullable;
+import org.eclipse.jgit.internal.JGitText;
+import org.eclipse.jgit.internal.storage.file.FileSnapshot;
+import org.eclipse.jgit.internal.storage.file.LockFile;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.storage.file.FileBasedConfig;
+import org.eclipse.jgit.util.FileUtils;
+import org.eclipse.jgit.util.IO;
+import org.eclipse.jgit.util.RawParseUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Wraps all cookies persisted in a <strong>Netscape Cookie File Format</strong>
+ * being referenced via the git config <a href=
+ * "https://git-scm.com/docs/git-config#git-config-httpcookieFile">http.cookieFile</a>.
+ * <p>
+ * It will only load the cookies lazily, i.e. before calling
+ * {@link #getCookies(boolean)} the file is not evaluated. This class also
+ * allows persisting cookies in that file format.
+ * <p>
+ * In general this class is not thread-safe. So any consumer needs to take care
+ * of synchronization!
+ *
+ * @see <a href="http://www.cookiecentral.com/faq/#3.5">Netscape Cookie File
+ *      Format</a>
+ * @see <a href=
+ *      "https://unix.stackexchange.com/questions/36531/format-of-cookies-when-using-wget">Cookie
+ *      format for wget</a>
+ * @see <a href=
+ *      "https://github.com/curl/curl/blob/07ebaf837843124ee670e5b8c218b80b92e06e47/lib/cookie.c#L745">libcurl
+ *      Cookie file parsing</a>
+ * @see <a href=
+ *      "https://github.com/curl/curl/blob/07ebaf837843124ee670e5b8c218b80b92e06e47/lib/cookie.c#L1417">libcurl
+ *      Cookie file writing</a>
+ * @see NetscapeCookieFileCache
+ */
+public final class NetscapeCookieFile {
+
+	private static final String HTTP_ONLY_PREAMBLE = "#HttpOnly_"; //$NON-NLS-1$
+
+	private static final String COLUMN_SEPARATOR = "\t"; //$NON-NLS-1$
+
+	private static final String LINE_SEPARATOR = "\n"; //$NON-NLS-1$
+
+	/**
+	 * Maximum number of retries to acquire the lock for writing to the
+	 * underlying file.
+	 */
+	private static final int LOCK_ACQUIRE_MAX_RETRY_COUNT = 4;
+
+	/**
+	 * Sleep time in milliseconds between retries to acquire the lock for
+	 * writing to the underlying file.
+	 */
+	private static final int LOCK_ACQUIRE_RETRY_SLEEP = 500;
+
+	private final Path path;
+
+	private FileSnapshot snapshot;
+
+	private byte[] hash;
+
+	final Date creationDate;
+
+	private Set<HttpCookie> cookies = null;
+
+	private static final Logger LOG = LoggerFactory
+			.getLogger(NetscapeCookieFile.class);
+
+	/**
+	 * @param path
+	 *            where to find the cookie file
+	 */
+	public NetscapeCookieFile(Path path) {
+		this(path, new Date());
+	}
+
+	NetscapeCookieFile(Path path, Date creationDate) {
+		this.path = path;
+		this.snapshot = FileSnapshot.DIRTY;
+		this.creationDate = creationDate;
+	}
+
+	/**
+	 * Path to the underlying cookie file.
+	 *
+	 * @return the path
+	 */
+	public Path getPath() {
+		return path;
+	}
+
+	/**
+	 * Return all cookies from the underlying cookie file.
+	 *
+	 * @param refresh
+	 *            if {@code true} updates the list from the underlying cookie
+	 *            file if it has been modified since the last read otherwise
+	 *            returns the current transient state. In case the cookie file
+	 *            has never been read before will always read from the
+	 *            underlying file disregarding the value of this parameter.
+	 * @return all cookies (may contain session cookies as well). This does not
+	 *         return a copy of the list but rather the original one. Every
+	 *         addition to the returned list can afterwards be persisted via
+	 *         {@link #write(URL)}. Errors in the underlying file will not lead
+	 *         to exceptions but rather to an empty set being returned and the
+	 *         underlying error being logged.
+	 */
+	public Set<HttpCookie> getCookies(boolean refresh) {
+		if (cookies == null || refresh) {
+			try {
+				byte[] in = getFileContentIfModified();
+				Set<HttpCookie> newCookies = parseCookieFile(in, creationDate);
+				if (cookies != null) {
+					cookies = mergeCookies(newCookies, cookies);
+				} else {
+					cookies = newCookies;
+				}
+				return cookies;
+			} catch (IOException | IllegalArgumentException e) {
+				LOG.warn(
+						MessageFormat.format(
+								JGitText.get().couldNotReadCookieFile, path),
+						e);
+				if (cookies == null) {
+					cookies = new LinkedHashSet<>();
+				}
+			}
+		}
+		return cookies;
+
+	}
+
+	/**
+	 * Parses the given file and extracts all cookie information from it.
+	 *
+	 * @param input
+	 *            the file content to parse
+	 * @param creationDate
+	 *            the date for the creation of the cookies (used to calculate
+	 *            the maxAge based on the expiration date given within the file)
+	 * @return the set of parsed cookies from the given file (even expired
+	 *         ones). If there is more than one cookie with the same name in
+	 *         this file the last one overwrites the first one!
+	 * @throws IOException
+	 *             if the given file could not be read for some reason
+	 * @throws IllegalArgumentException
+	 *             if the given file does not have a proper format
+	 */
+	private static Set<HttpCookie> parseCookieFile(@NonNull byte[] input,
+			@NonNull Date creationDate)
+			throws IOException, IllegalArgumentException {
+
+		String decoded = RawParseUtils.decode(StandardCharsets.US_ASCII, input);
+
+		Set<HttpCookie> cookies = new LinkedHashSet<>();
+		try (BufferedReader reader = new BufferedReader(
+				new StringReader(decoded))) {
+			String line;
+			while ((line = reader.readLine()) != null) {
+				HttpCookie cookie = parseLine(line, creationDate);
+				if (cookie != null) {
+					cookies.add(cookie);
+				}
+			}
+		}
+		return cookies;
+	}
+
+	private static HttpCookie parseLine(@NonNull String line,
+			@NonNull Date creationDate) {
+		if (line.isEmpty() || (line.startsWith("#") //$NON-NLS-1$
+				&& !line.startsWith(HTTP_ONLY_PREAMBLE))) {
+			return null;
+		}
+		String[] cookieLineParts = line.split(COLUMN_SEPARATOR, 7);
+		if (cookieLineParts == null) {
+			throw new IllegalArgumentException(MessageFormat
+					.format(JGitText.get().couldNotFindTabInLine, line));
+		}
+		if (cookieLineParts.length < 7) {
+			throw new IllegalArgumentException(MessageFormat.format(
+					JGitText.get().couldNotFindSixTabsInLine,
+					Integer.valueOf(cookieLineParts.length), line));
+		}
+		String name = cookieLineParts[5];
+		String value = cookieLineParts[6];
+		HttpCookie cookie = new HttpCookie(name, value);
+
+		String domain = cookieLineParts[0];
+		if (domain.startsWith(HTTP_ONLY_PREAMBLE)) {
+			cookie.setHttpOnly(true);
+			domain = domain.substring(HTTP_ONLY_PREAMBLE.length());
+		}
+		// strip off leading "."
+		// (https://tools.ietf.org/html/rfc6265#section-5.2.3)
+		if (domain.startsWith(".")) { //$NON-NLS-1$
+			domain = domain.substring(1);
+		}
+		cookie.setDomain(domain);
+		// domain evaluation as boolean flag not considered (i.e. always assumed
+		// to be true)
+		cookie.setPath(cookieLineParts[2]);
+		cookie.setSecure(Boolean.parseBoolean(cookieLineParts[3]));
+
+		long expires = Long.parseLong(cookieLineParts[4]);
+		long maxAge = (expires - creationDate.getTime()) / 1000;
+		if (maxAge <= 0) {
+			return null; // skip expired cookies
+		}
+		cookie.setMaxAge(maxAge);
+		return cookie;
+	}
+
+	/**
+	 * Read the underying file and return its content but only in case it has
+	 * been modified since the last access.
+	 * <p>
+	 * Internally calculates the hash and maintains {@link FileSnapshot}s to
+	 * prevent issues described as <a href=
+	 * "https://github.com/git/git/blob/master/Documentation/technical/racy-git.txt">"Racy
+	 * Git problem"</a>. Inspired by {@link FileBasedConfig#load()}.
+	 *
+	 * @return the file contents in case the file has been modified since the
+	 *         last access, otherwise {@code null}
+	 * @throws IOException
+	 *             if the file is not found or cannot be read
+	 */
+	private byte[] getFileContentIfModified() throws IOException {
+		final int maxStaleRetries = 5;
+		int retries = 0;
+		File file = getPath().toFile();
+		if (!file.exists()) {
+			LOG.warn(MessageFormat.format(JGitText.get().missingCookieFile,
+					file.getAbsolutePath()));
+			return new byte[0];
+		}
+		while (true) {
+			final FileSnapshot oldSnapshot = snapshot;
+			final FileSnapshot newSnapshot = FileSnapshot.save(file);
+			try {
+				final byte[] in = IO.readFully(file);
+				byte[] newHash = hash(in);
+				if (Arrays.equals(hash, newHash)) {
+					if (oldSnapshot.equals(newSnapshot)) {
+						oldSnapshot.setClean(newSnapshot);
+					} else {
+						snapshot = newSnapshot;
+					}
+				} else {
+					snapshot = newSnapshot;
+					hash = newHash;
+				}
+				return in;
+			} catch (FileNotFoundException e) {
+				throw e;
+			} catch (IOException e) {
+				if (FileUtils.isStaleFileHandle(e)
+						&& retries < maxStaleRetries) {
+					if (LOG.isDebugEnabled()) {
+						LOG.debug(MessageFormat.format(
+								JGitText.get().configHandleIsStale,
+								Integer.valueOf(retries)), e);
+					}
+					retries++;
+					continue;
+				}
+				throw new IOException(MessageFormat
+						.format(JGitText.get().cannotReadFile, getPath()), e);
+			}
+		}
+
+	}
+
+	private static byte[] hash(final byte[] in) {
+		return Constants.newMessageDigest().digest(in);
+	}
+
+	/**
+	 * Writes all the cookies being maintained in the set being returned by
+	 * {@link #getCookies(boolean)} to the underlying file.
+	 * <p>
+	 * Session-cookies will not be persisted.
+	 *
+	 * @param url
+	 *            url for which to write the cookies (important to derive
+	 *            default values for non-explicitly set attributes)
+	 * @throws IOException
+	 *             if the underlying cookie file could not be read or written or
+	 *             a problem with the lock file
+	 * @throws InterruptedException
+	 *             if the thread is interrupted while waiting for the lock
+	 */
+	public void write(URL url) throws IOException, InterruptedException {
+		try {
+			byte[] cookieFileContent = getFileContentIfModified();
+			if (cookieFileContent != null) {
+				LOG.debug("Reading the underlying cookie file '{}' " //$NON-NLS-1$
+						+ "as it has been modified since " //$NON-NLS-1$
+						+ "the last access", //$NON-NLS-1$
+						path);
+				// reread new changes if necessary
+				Set<HttpCookie> cookiesFromFile = NetscapeCookieFile
+						.parseCookieFile(cookieFileContent, creationDate);
+				this.cookies = mergeCookies(cookiesFromFile, cookies);
+			}
+		} catch (FileNotFoundException e) {
+			// ignore if file previously did not exist yet!
+		}
+
+		ByteArrayOutputStream output = new ByteArrayOutputStream();
+		try (Writer writer = new OutputStreamWriter(output,
+				StandardCharsets.US_ASCII)) {
+			write(writer, cookies, url, creationDate);
+		}
+		LockFile lockFile = new LockFile(path.toFile());
+		for (int retryCount = 0; retryCount < LOCK_ACQUIRE_MAX_RETRY_COUNT; retryCount++) {
+			if (lockFile.lock()) {
+				try {
+					lockFile.setNeedSnapshot(true);
+					lockFile.write(output.toByteArray());
+					if (!lockFile.commit()) {
+						throw new IOException(MessageFormat.format(
+								JGitText.get().cannotCommitWriteTo, path));
+					}
+				} finally {
+					lockFile.unlock();
+				}
+				return;
+			}
+			Thread.sleep(LOCK_ACQUIRE_RETRY_SLEEP);
+		}
+		throw new IOException(
+				MessageFormat.format(JGitText.get().cannotLock, lockFile));
+	}
+
+	/**
+	 * Writes the given cookies to the file in the Netscape Cookie File Format
+	 * (also used by curl).
+	 *
+	 * @param writer
+	 *            the writer to use to persist the cookies
+	 * @param cookies
+	 *            the cookies to write into the file
+	 * @param url
+	 *            the url for which to write the cookie (to derive the default
+	 *            values for certain cookie attributes)
+	 * @param creationDate
+	 *            the date when the cookie has been created. Important for
+	 *            calculation the cookie expiration time (calculated from
+	 *            cookie's maxAge and this creation time)
+	 * @throws IOException
+	 *             if an I/O error occurs
+	 */
+	static void write(@NonNull Writer writer,
+			@NonNull Collection<HttpCookie> cookies, @NonNull URL url,
+			@NonNull Date creationDate) throws IOException {
+		for (HttpCookie cookie : cookies) {
+			writeCookie(writer, cookie, url, creationDate);
+		}
+	}
+
+	private static void writeCookie(@NonNull Writer writer,
+			@NonNull HttpCookie cookie, @NonNull URL url,
+			@NonNull Date creationDate) throws IOException {
+		if (cookie.getMaxAge() <= 0) {
+			return; // skip expired cookies
+		}
+		String domain = ""; //$NON-NLS-1$
+		if (cookie.isHttpOnly()) {
+			domain = HTTP_ONLY_PREAMBLE;
+		}
+		if (cookie.getDomain() != null) {
+			domain += cookie.getDomain();
+		} else {
+			domain += url.getHost();
+		}
+		writer.write(domain);
+		writer.write(COLUMN_SEPARATOR);
+		writer.write("TRUE"); //$NON-NLS-1$
+		writer.write(COLUMN_SEPARATOR);
+		String path = cookie.getPath();
+		if (path == null) {
+			path = url.getPath();
+		}
+		writer.write(path);
+		writer.write(COLUMN_SEPARATOR);
+		writer.write(Boolean.toString(cookie.getSecure()).toUpperCase());
+		writer.write(COLUMN_SEPARATOR);
+		final String expirationDate;
+		// whenCreated field is not accessible in HttpCookie
+		expirationDate = String
+				.valueOf(creationDate.getTime() + (cookie.getMaxAge() * 1000));
+		writer.write(expirationDate);
+		writer.write(COLUMN_SEPARATOR);
+		writer.write(cookie.getName());
+		writer.write(COLUMN_SEPARATOR);
+		writer.write(cookie.getValue());
+		writer.write(LINE_SEPARATOR);
+	}
+
+	/**
+	 * Merge the given sets in the following way. All cookies from
+	 * {@code cookies1} and {@code cookies2} are contained in the resulting set
+	 * which have unique names. If there is a duplicate entry for one name only
+	 * the entry from set {@code cookies1} ends up in the resulting set.
+	 *
+	 * @param cookies1
+	 *            first set of cookies
+	 * @param cookies2
+	 *            second set of cookies
+	 *
+	 * @return the merged cookies
+	 */
+	static Set<HttpCookie> mergeCookies(Set<HttpCookie> cookies1,
+			@Nullable Set<HttpCookie> cookies2) {
+		Set<HttpCookie> mergedCookies = new LinkedHashSet<>(cookies1);
+		if (cookies2 != null) {
+			mergedCookies.addAll(cookies2);
+		}
+		return mergedCookies;
+	}
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/transport/http/NetscapeCookieFileCache.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/transport/http/NetscapeCookieFileCache.java
new file mode 100644
index 0000000..882b2d0
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/transport/http/NetscapeCookieFileCache.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2018, Konrad Windszus <konrad_w@gmx.de>
+ * 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.http;
+
+import java.nio.file.Path;
+
+import org.eclipse.jgit.transport.HttpConfig;
+import org.eclipse.jgit.util.LRUMap;
+
+/**
+ * A cache of all known cookie files ({@link NetscapeCookieFile}). May contain
+ * at most {@code n} entries, where the least-recently used one is evicted as
+ * soon as more entries are added. The maximum number of entries (={@code n})
+ * can be set via the git config key {@code http.cookieFileCacheLimit}. By
+ * default it is set to 10.
+ * <p>
+ * The cache is global, i.e. it is shared among all consumers within the same
+ * Java process.
+ *
+ * @see NetscapeCookieFile
+ *
+ */
+public class NetscapeCookieFileCache {
+
+	private final LRUMap<Path, NetscapeCookieFile> cookieFileMap;
+
+	private static NetscapeCookieFileCache instance;
+
+	private NetscapeCookieFileCache(HttpConfig config) {
+		cookieFileMap = new LRUMap<>(config.getCookieFileCacheLimit(),
+				config.getCookieFileCacheLimit());
+	}
+
+	/**
+	 * @param config
+	 *            the config which defines the limit for this cache
+	 * @return the singleton instance of the cookie file cache. If the cache has
+	 *         already been created the given config is ignored (even if it
+	 *         differs from the config, with which the cache has originally been
+	 *         created)
+	 */
+	public static NetscapeCookieFileCache getInstance(HttpConfig config) {
+		if (instance == null) {
+			return new NetscapeCookieFileCache(config);
+		} else {
+			return instance;
+		}
+	}
+
+	/**
+	 * @param path
+	 *            the path of the cookie file to retrieve
+	 * @return the cache entry belonging to the requested file
+	 */
+	public NetscapeCookieFile getEntry(Path path) {
+		if (!cookieFileMap.containsKey(path)) {
+			synchronized (NetscapeCookieFileCache.class) {
+				if (!cookieFileMap.containsKey(path)) {
+					cookieFileMap.put(path, new NetscapeCookieFile(path));
+				}
+			}
+		}
+		return cookieFileMap.get(path);
+	}
+
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/AbbreviatedObjectId.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/AbbreviatedObjectId.java
index d105d0d..b0339c6 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/AbbreviatedObjectId.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/AbbreviatedObjectId.java
@@ -162,7 +162,7 @@
 			r |= RawParseUtils.parseHexInt4(bs[p++]);
 			n++;
 		}
-		return r << (8 - n) * 4;
+		return r << ((8 - n) * 4);
 	}
 
 	static int mask(int nibbles, int word, int v) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/AnyObjectId.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/AnyObjectId.java
index 978dd3a..4f90e69 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/AnyObjectId.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/AnyObjectId.java
@@ -49,6 +49,7 @@
 import java.nio.ByteBuffer;
 
 import org.eclipse.jgit.util.NB;
+import org.eclipse.jgit.util.References;
 
 /**
  * A (possibly mutable) SHA-1 abstraction.
@@ -60,19 +61,37 @@
 public abstract class AnyObjectId implements Comparable<AnyObjectId> {
 
 	/**
-	 * Compare to object identifier byte sequences for equality.
+	 * Compare two object identifier byte sequences for equality.
 	 *
 	 * @param firstObjectId
 	 *            the first identifier to compare. Must not be null.
 	 * @param secondObjectId
 	 *            the second identifier to compare. Must not be null.
 	 * @return true if the two identifiers are the same.
+	 * @deprecated use {@link #isEqual(AnyObjectId, AnyObjectId)} instead
 	 */
+	@Deprecated
+	@SuppressWarnings("AmbiguousMethodReference")
 	public static boolean equals(final AnyObjectId firstObjectId,
 			final AnyObjectId secondObjectId) {
-		if (firstObjectId == secondObjectId)
-			return true;
+		return isEqual(firstObjectId, secondObjectId);
+	}
 
+	/**
+	 * Compare two object identifier byte sequences for equality.
+	 *
+	 * @param firstObjectId
+	 *            the first identifier to compare. Must not be null.
+	 * @param secondObjectId
+	 *            the second identifier to compare. Must not be null.
+	 * @return true if the two identifiers are the same.
+	 * @since 5.4
+	 */
+	public static boolean isEqual(final AnyObjectId firstObjectId,
+			final AnyObjectId secondObjectId) {
+		if (References.isSameObject(firstObjectId, secondObjectId)) {
+			return true;
+		}
 		// We test word 3 first since the git file-based ODB
 		// uses the first byte of w1, and we use w2 as the
 		// hash code, one of those probably came up with these
@@ -80,7 +99,6 @@
 		// Therefore the first two words are very likely to be
 		// identical. We want to break away from collisions as
 		// quickly as possible.
-		//
 		return firstObjectId.w3 == secondObjectId.w3
 				&& firstObjectId.w4 == secondObjectId.w4
 				&& firstObjectId.w5 == secondObjectId.w5
@@ -276,8 +294,9 @@
 	 *            the other id to compare to. May be null.
 	 * @return true only if both ObjectIds have identical bits.
 	 */
+	@SuppressWarnings({ "NonOverridingEquals", "AmbiguousMethodReference" })
 	public final boolean equals(AnyObjectId other) {
-		return other != null ? equals(this, other) : false;
+		return other != null ? isEqual(this, other) : false;
 	}
 
 	/** {@inheritDoc} */
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/CommitBuilder.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/CommitBuilder.java
index 6cbddec..13f71a7 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/CommitBuilder.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/CommitBuilder.java
@@ -57,6 +57,7 @@
 import java.util.List;
 
 import org.eclipse.jgit.internal.JGitText;
+import org.eclipse.jgit.util.References;
 
 /**
  * Mutable builder to construct a commit recording the state of a project.
@@ -365,7 +366,7 @@
 				os.write('\n');
 			}
 
-			if (getEncoding() != UTF_8) {
+			if (!References.isSameObject(getEncoding(), UTF_8)) {
 				os.write(hencoding);
 				os.write(' ');
 				os.write(Constants.encodeASCII(getEncoding().name()));
@@ -474,7 +475,7 @@
 		r.append(gpgSignature != null ? gpgSignature.toString() : "NOT_SET");
 		r.append("\n");
 
-		if (encoding != null && encoding != UTF_8) {
+		if (encoding != null && !References.isSameObject(encoding, UTF_8)) {
 			r.append("encoding ");
 			r.append(encoding.name());
 			r.append("\n");
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 6ab5c14..16db717 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Config.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Config.java
@@ -107,7 +107,7 @@
 	 * must ensure it is a special copy of the empty string.  It also must
 	 * be treated like the empty string.
 	 */
-	static final String MAGIC_EMPTY_VALUE = new String();
+	private static final String MISSING_ENTRY = new String();
 
 	/**
 	 * Create a configuration with no default fallback.
@@ -129,6 +129,18 @@
 	}
 
 	/**
+	 * Check if a given string is the "missing" value.
+	 *
+	 * @param value string to be checked.
+	 * @return true if the given string is the "missing" value.
+	 * @since 5.4
+	 */
+	@SuppressWarnings({ "ReferenceEquality", "StringEquality" })
+	public static boolean isMissing(String value) {
+		return value == MISSING_ENTRY;
+	}
+
+	/**
 	 * Globally sets a {@link org.eclipse.jgit.lib.TypedConfigGetter} that is
 	 * subsequently used to read typed values from all git configs.
 	 *
@@ -1041,7 +1053,7 @@
 				if (e.prefix == null || "".equals(e.prefix)) //$NON-NLS-1$
 					out.append('\t');
 				out.append(e.name);
-				if (MAGIC_EMPTY_VALUE != e.value) {
+				if (!isMissing(e.value)) {
 					out.append(" ="); //$NON-NLS-1$
 					if (e.value != null) {
 						out.append(' ');
@@ -1132,7 +1144,7 @@
 				e.name = readKeyName(in);
 				if (e.name.endsWith("\n")) { //$NON-NLS-1$
 					e.name = e.name.substring(0, e.name.length() - 1);
-					e.value = MAGIC_EMPTY_VALUE;
+					e.value = MISSING_ENTRY;
 				} else
 					e.value = readValue(in);
 
@@ -1165,7 +1177,7 @@
 	private void addIncludedConfig(final List<ConfigLine> newEntries,
 			ConfigLine line, int depth) throws ConfigInvalidException {
 		if (!line.name.equalsIgnoreCase("path") || //$NON-NLS-1$
-				line.value == null || line.value.equals(MAGIC_EMPTY_VALUE)) {
+				line.value == null || line.value.equals(MISSING_ENTRY)) {
 			throw new ConfigInvalidException(MessageFormat.format(
 					JGitText.get().invalidLineInConfigFileWithParam, line));
 		}
@@ -1413,11 +1425,23 @@
 				case '"':
 					value.append('"');
 					continue;
-				default:
-					throw new ConfigInvalidException(MessageFormat.format(
-							JGitText.get().badEscape,
-							Character.valueOf(((char) c))));
+				case '\r': {
+					int next = in.read();
+					if (next == '\n') {
+						continue; // CR-LF
+					} else if (next >= 0) {
+						in.reset();
+					}
+					break;
 				}
+				default:
+					break;
+				}
+				throw new ConfigInvalidException(
+						MessageFormat.format(JGitText.get().badEscape,
+								Character.isAlphabetic(c)
+										? Character.valueOf(((char) c))
+										: toUnicodeLiteral(c)));
 			}
 
 			if ('"' == c) {
@@ -1430,6 +1454,11 @@
 		return value.length() > 0 ? value.toString() : null;
 	}
 
+	private static String toUnicodeLiteral(int c) {
+		return String.format("\\u%04x", //$NON-NLS-1$
+				Integer.valueOf(c));
+	}
+
 	/**
 	 * Parses a section of the configuration into an application model object.
 	 * <p>
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 c72395f..078bf78 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java
@@ -395,50 +395,50 @@
 	public static final String CONFIG_KEY_CHECKSTAT = "checkstat";
 
 	/**
-	 * The "renamelimit" key in the "diff section"
+	 * The "renamelimit" key in the "diff" section
 	 * @since 3.0
 	 */
 	public static final String CONFIG_KEY_RENAMELIMIT = "renamelimit";
 
 	/**
-	 * The "trustfolderstat" key in the "core section"
+	 * The "trustfolderstat" key in the "core" section
 	 * @since 3.6
 	 */
 	public static final String CONFIG_KEY_TRUSTFOLDERSTAT = "trustfolderstat";
 
 	/**
-	 * The "supportsAtomicFileCreation" key in the "core section"
+	 * The "supportsAtomicFileCreation" key in the "core" section
 	 *
 	 * @since 4.5
 	 */
 	public static final String CONFIG_KEY_SUPPORTSATOMICFILECREATION = "supportsatomicfilecreation";
 
 	/**
-	 * The "noprefix" key in the "diff section"
+	 * The "noprefix" key in the "diff" section
 	 * @since 3.0
 	 */
 	public static final String CONFIG_KEY_NOPREFIX = "noprefix";
 
 	/**
-	 * A "renamelimit" value in the "diff section"
+	 * A "renamelimit" value in the "diff" section
 	 * @since 3.0
 	 */
 	public static final String CONFIG_RENAMELIMIT_COPY = "copy";
 
 	/**
-	 * A "renamelimit" value in the "diff section"
+	 * A "renamelimit" value in the "diff" section
 	 * @since 3.0
 	 */
 	public static final String CONFIG_RENAMELIMIT_COPIES = "copies";
 
 	/**
-	 * The "renames" key in the "diff section"
+	 * The "renames" key in the "diff" section
 	 * @since 3.0
 	 */
 	public static final String CONFIG_KEY_RENAMES = "renames";
 
 	/**
-	 * The "inCoreLimit" key in the "merge section". It's a size limit (bytes) used to
+	 * The "inCoreLimit" key in the "merge" section. It's a size limit (bytes) used to
 	 * control a file to be stored in {@code Heap} or {@code LocalFile} during the merge.
 	 * @since 4.9
 	 */
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java
index 4c55196..8f4468e 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java
@@ -58,7 +58,7 @@
 import org.eclipse.jgit.util.MutableInteger;
 
 /**
- * Misc. constants used throughout JGit.
+ * Misc. constants and helpers used throughout JGit.
  */
 @SuppressWarnings("nls")
 public final class Constants {
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 fb23939..e865da8 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/DefaultTypedConfigGetter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/DefaultTypedConfigGetter.java
@@ -72,7 +72,7 @@
 		if (n == null) {
 			return defaultValue;
 		}
-		if (Config.MAGIC_EMPTY_VALUE == n) {
+		if (Config.isMissing(n)) {
 			return true;
 		}
 		try {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileMode.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileMode.java
index d4c4d5b..8fa8d5f 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileMode.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileMode.java
@@ -88,6 +88,7 @@
 	public static final FileMode TREE = new FileMode(TYPE_TREE,
 			Constants.OBJ_TREE) {
 		@Override
+		@SuppressWarnings("NonOverridingEquals")
 		public boolean equals(int modeBits) {
 			return (modeBits & TYPE_MASK) == TYPE_TREE;
 		}
@@ -97,6 +98,7 @@
 	public static final FileMode SYMLINK = new FileMode(TYPE_SYMLINK,
 			Constants.OBJ_BLOB) {
 		@Override
+		@SuppressWarnings("NonOverridingEquals")
 		public boolean equals(int modeBits) {
 			return (modeBits & TYPE_MASK) == TYPE_SYMLINK;
 		}
@@ -106,6 +108,7 @@
 	public static final FileMode REGULAR_FILE = new FileMode(0100644,
 			Constants.OBJ_BLOB) {
 		@Override
+		@SuppressWarnings("NonOverridingEquals")
 		public boolean equals(int modeBits) {
 			return (modeBits & TYPE_MASK) == TYPE_FILE && (modeBits & 0111) == 0;
 		}
@@ -115,6 +118,7 @@
 	public static final FileMode EXECUTABLE_FILE = new FileMode(0100755,
 			Constants.OBJ_BLOB) {
 		@Override
+		@SuppressWarnings("NonOverridingEquals")
 		public boolean equals(int modeBits) {
 			return (modeBits & TYPE_MASK) == TYPE_FILE && (modeBits & 0111) != 0;
 		}
@@ -124,6 +128,7 @@
 	public static final FileMode GITLINK = new FileMode(TYPE_GITLINK,
 			Constants.OBJ_COMMIT) {
 		@Override
+		@SuppressWarnings("NonOverridingEquals")
 		public boolean equals(int modeBits) {
 			return (modeBits & TYPE_MASK) == TYPE_GITLINK;
 		}
@@ -133,6 +138,7 @@
 	public static final FileMode MISSING = new FileMode(TYPE_MISSING,
 			Constants.OBJ_BAD) {
 		@Override
+		@SuppressWarnings("NonOverridingEquals")
 		public boolean equals(int modeBits) {
 			return modeBits == 0;
 		}
@@ -165,6 +171,7 @@
 
 		return new FileMode(bits, Constants.OBJ_BAD) {
 			@Override
+			@SuppressWarnings("NonOverridingEquals")
 			public boolean equals(int a) {
 				return bits == a;
 			}
@@ -206,6 +213,7 @@
 	 *            a int.
 	 * @return true if the mode bits represent the same mode as this object
 	 */
+	@SuppressWarnings("NonOverridingEquals")
 	public abstract boolean equals(int modebits);
 
 	/**
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 f37c310..ce1eb59 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/IndexDiff.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/IndexDiff.java
@@ -352,12 +352,7 @@
 		public WorkingTreeIterator getWorkingTreeIterator(Repository repo);
 	}
 
-	private WorkingTreeIteratorFactory wTreeIt = new WorkingTreeIteratorFactory() {
-		@Override
-		public WorkingTreeIterator getWorkingTreeIterator(Repository repo) {
-			return new FileTreeIterator(repo);
-		}
-	};
+	private WorkingTreeIteratorFactory wTreeIt = FileTreeIterator::new;
 
 	/**
 	 * Allows higher layers to set the factory for WorkingTreeIterators.
@@ -647,11 +642,12 @@
 	private void addConflict(String path, int stage) {
 		StageState existingStageStates = conflicts.get(path);
 		byte stageMask = 0;
-		if (existingStageStates != null)
-			stageMask |= existingStageStates.getStageMask();
+		if (existingStageStates != null) {
+			stageMask |= (byte) existingStageStates.getStageMask();
+		}
 		// stage 1 (base) should be shifted 0 times
 		int shifts = stage - 1;
-		stageMask |= (1 << shifts);
+		stageMask |= (byte) (1 << shifts);
 		StageState stageState = StageState.fromMask(stageMask);
 		conflicts.put(path, stageState);
 	}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdRef.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdRef.java
index b791c64..d65c1bd 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdRef.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdRef.java
@@ -67,7 +67,7 @@
 		 */
 		public Unpeeled(@NonNull Storage st, @NonNull String name,
 				@Nullable ObjectId id) {
-			super(st, name, id, -1);
+			super(st, name, id, UNDEFINED_UPDATE_INDEX);
 		}
 
 		/**
@@ -119,7 +119,7 @@
 		 */
 		public PeeledTag(@NonNull Storage st, @NonNull String name,
 				@Nullable ObjectId id, @NonNull ObjectId p) {
-			super(st, name, id, -1);
+			super(st, name, id, UNDEFINED_UPDATE_INDEX);
 			peeledObjectId = p;
 		}
 
@@ -172,7 +172,7 @@
 		 */
 		public PeeledNonTag(@NonNull Storage st, @NonNull String name,
 				@Nullable ObjectId id) {
-			super(st, name, id, -1);
+			super(st, name, id, UNDEFINED_UPDATE_INDEX);
 		}
 
 		/**
@@ -284,7 +284,7 @@
 	 */
 	@Override
 	public long getUpdateIndex() {
-		if (updateIndex == -1) {
+		if (updateIndex == UNDEFINED_UPDATE_INDEX) {
 			throw new UnsupportedOperationException();
 		}
 		return updateIndex;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdSubclassMap.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdSubclassMap.java
index cd57bda..470275b 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdSubclassMap.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdSubclassMap.java
@@ -103,8 +103,9 @@
 		V obj;
 
 		while ((obj = tbl[i]) != null) {
-			if (AnyObjectId.equals(obj, toFind))
+			if (AnyObjectId.isEqual(obj, toFind)) {
 				return obj;
+			}
 			i = (i + 1) & msk;
 		}
 		return null;
@@ -162,7 +163,7 @@
 		V obj;
 
 		while ((obj = tbl[i]) != null) {
-			if (AnyObjectId.equals(obj, newValue))
+			if (AnyObjectId.isEqual(obj, newValue))
 				return obj;
 			i = (i + 1) & msk;
 		}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RebaseTodoFile.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RebaseTodoFile.java
index 0d31851..ebbb3a4 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RebaseTodoFile.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RebaseTodoFile.java
@@ -181,7 +181,7 @@
 
 		int nextSpace = RawParseUtils.next(buf, tokenBegin, ' ');
 		int tokenCount = 0;
-		while (tokenCount < 3 && nextSpace < lineEnd) {
+		while (tokenCount < 3 && nextSpace <= lineEnd) {
 			switch (tokenCount) {
 			case 0:
 				String actionToken = new String(buf, tokenBegin,
@@ -193,8 +193,14 @@
 				break;
 			case 1:
 				nextSpace = RawParseUtils.next(buf, tokenBegin, ' ');
-				String commitToken = new String(buf, tokenBegin,
-						nextSpace - tokenBegin - 1, UTF_8);
+				String commitToken;
+				if (nextSpace > lineEnd + 1) {
+					commitToken = new String(buf, tokenBegin,
+							lineEnd - tokenBegin + 1, UTF_8);
+				} else {
+					commitToken = new String(buf, tokenBegin,
+							nextSpace - tokenBegin - 1, UTF_8);
+				}
 				tokenBegin = nextSpace;
 				commit = AbbreviatedObjectId.fromString(commitToken);
 				break;
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 32c8b06..4082d21 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Ref.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Ref.java
@@ -126,6 +126,13 @@
 	}
 
 	/**
+	 * Update index value when a reference doesn't have one
+	 *
+	 * @since 5.4
+	 */
+	long UNDEFINED_UPDATE_INDEX = -1L;
+
+	/**
 	 * What this ref is called within the repository.
 	 *
 	 * @return name of this ref.
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 8777920..4d9450e 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDatabase.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDatabase.java
@@ -44,6 +44,7 @@
 package org.eclipse.jgit.lib;
 
 import static java.util.stream.Collectors.toList;
+import static java.util.stream.Collectors.toSet;
 
 import java.io.IOException;
 import java.util.ArrayList;
@@ -52,7 +53,7 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
-
+import java.util.Set;
 import org.eclipse.jgit.annotations.NonNull;
 import org.eclipse.jgit.annotations.Nullable;
 
@@ -470,6 +471,31 @@
 		return Collections.unmodifiableList(result);
 	}
 
+
+	/**
+	 * Returns all refs that resolve directly to the given {@link ObjectId}.
+	 * Includes peeled {@linkObjectId}s. This is the inverse lookup of
+	 * {@link #exactRef(String...)}.
+	 *
+	 * <p>
+	 * The default implementation uses a linear scan. Implementors of
+	 * {@link RefDatabase} should override this method directly if a better
+	 * implementation is possible.
+	 *
+	 * @param id
+	 *            {@link ObjectId} to resolve
+	 * @return a {@link Set} of {@link Ref}s whose tips point to the provided
+	 *         id.
+	 * @throws java.io.IOException
+	 *             the reference space cannot be accessed.
+	 * @since 5.4
+	 */
+	@NonNull
+	public Set<Ref> getTipsWithSha1(ObjectId id) throws IOException {
+		return getRefs().stream().filter(r -> id.equals(r.getObjectId())
+				|| id.equals(r.getPeeledObjectId())).collect(toSet());
+	}
+
 	/**
 	 * Check if any refs exist in the ref database.
 	 * <p>
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefUpdate.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefUpdate.java
index 1ce1528..eca15c0 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefUpdate.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefUpdate.java
@@ -53,6 +53,7 @@
 import org.eclipse.jgit.revwalk.RevObject;
 import org.eclipse.jgit.revwalk.RevWalk;
 import org.eclipse.jgit.transport.PushCertificate;
+import org.eclipse.jgit.util.References;
 
 /**
  * Creates, updates or deletes any reference.
@@ -599,6 +600,7 @@
 	 */
 	public Result update() throws IOException {
 		try (RevWalk rw = new RevWalk(getRepository())) {
+			rw.setRetainBody(false);
 			return update(rw);
 		}
 	}
@@ -646,6 +648,7 @@
 	 */
 	public Result delete() throws IOException {
 		try (RevWalk rw = new RevWalk(getRepository())) {
+			rw.setRetainBody(false);
 			return delete(rw);
 		}
 	}
@@ -751,7 +754,7 @@
 			if (expValue != null) {
 				final ObjectId o;
 				o = oldValue != null ? oldValue : ObjectId.zeroId();
-				if (!AnyObjectId.equals(expValue, o)) {
+				if (!AnyObjectId.isEqual(expValue, o)) {
 					return Result.LOCK_FAILURE;
 				}
 			}
@@ -766,7 +769,8 @@
 			}
 
 			oldObj = safeParseOld(walk, oldValue);
-			if (newObj == oldObj && !detachingSymbolicRef) {
+			if (References.isSameObject(newObj, oldObj)
+					&& !detachingSymbolicRef) {
 				return store.execute(Result.NO_CHANGE);
 			}
 
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 a61897a..68866ea 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java
@@ -240,6 +240,15 @@
 	}
 
 	/**
+	 * Get repository identifier.
+	 *
+	 * @return repository identifier. The returned identifier has to be unique
+	 *         within a given Git server.
+	 * @since 5.4
+	 */
+	public abstract String getIdentifier();
+
+	/**
 	 * Get the object database which stores this repository's data.
 	 *
 	 * @return the object database which stores this repository's data.
@@ -489,6 +498,7 @@
 			throws AmbiguousObjectException, IncorrectObjectTypeException,
 			RevisionSyntaxException, IOException {
 		try (RevWalk rw = new RevWalk(this)) {
+			rw.setRetainBody(false);
 			Object resolved = resolve(rw, revstr);
 			if (resolved instanceof String) {
 				final Ref ref = findRef((String) resolved);
@@ -515,6 +525,7 @@
 	public String simplify(String revstr)
 			throws AmbiguousObjectException, IOException {
 		try (RevWalk rw = new RevWalk(this)) {
+			rw.setRetainBody(true);
 			Object resolved = resolve(rw, revstr);
 			if (resolved != null)
 				if (resolved instanceof String)
@@ -606,7 +617,7 @@
 								if (!(rev instanceof RevBlob))
 									throw new IncorrectObjectTypeException(rev,
 											Constants.TYPE_BLOB);
-							} else if (item.equals("")) { //$NON-NLS-1$
+							} else if (item.isEmpty()) {
 								rev = rw.peel(rev);
 							} else
 								throw new RevisionSyntaxException(revstr);
@@ -707,7 +718,7 @@
 					if (time.equals("upstream")) { //$NON-NLS-1$
 						if (name == null)
 							name = new String(revChars, done, i);
-						if (name.equals("")) //$NON-NLS-1$
+						if (name.isEmpty())
 							// Currently checked out branch, HEAD if
 							// detached
 							name = Constants.HEAD;
@@ -762,7 +773,7 @@
 					} else {
 						if (name == null)
 							name = new String(revChars, done, i);
-						if (name.equals("")) //$NON-NLS-1$
+						if (name.isEmpty())
 							name = Constants.HEAD;
 						if (!Repository.isValidRefName("x/" + name)) //$NON-NLS-1$
 							throw new RevisionSyntaxException(MessageFormat
@@ -788,7 +799,7 @@
 				if (rev == null) {
 					if (name == null)
 						name = new String(revChars, done, i);
-					if (name.equals("")) //$NON-NLS-1$
+					if (name.isEmpty())
 						name = Constants.HEAD;
 					rev = parseSimple(rw, name);
 					name = null;
@@ -921,7 +932,7 @@
 		AbbreviatedObjectId id = AbbreviatedObjectId.fromString(revstr);
 		try (ObjectReader reader = newObjectReader()) {
 			Collection<ObjectId> matches = reader.resolve(id);
-			if (matches.size() == 0)
+			if (matches.isEmpty())
 				return null;
 			else if (matches.size() == 1)
 				return matches.iterator().next();
@@ -1275,11 +1286,8 @@
 			CorruptObjectException, IOException {
 		// we want DirCache to inform us so that we can inform registered
 		// listeners about index changes
-		IndexChangedListener l = new IndexChangedListener() {
-			@Override
-			public void onIndexChanged(IndexChangedEvent event) {
-				notifyIndexChanged(true);
-			}
+		IndexChangedListener l = (IndexChangedEvent event) -> {
+			notifyIndexChanged(true);
 		};
 		return DirCache.lock(this, l);
 	}
@@ -1528,19 +1536,22 @@
 		final String filePath = file.getPath();
 		final String workDirPath = workDir.getPath();
 
-		if (filePath.length() <= workDirPath.length() ||
-		    filePath.charAt(workDirPath.length()) != File.separatorChar ||
-		    !filePath.startsWith(workDirPath)) {
-			File absWd = workDir.isAbsolute() ? workDir : workDir.getAbsoluteFile();
+		if (filePath.length() <= workDirPath.length()
+				|| filePath.charAt(workDirPath.length()) != File.separatorChar
+				|| !filePath.startsWith(workDirPath)) {
+			File absWd = workDir.isAbsolute() ? workDir
+					: workDir.getAbsoluteFile();
 			File absFile = file.isAbsolute() ? file : file.getAbsoluteFile();
-			if (absWd == workDir && absFile == file)
+			if (absWd.equals(workDir) && absFile.equals(file)) {
 				return ""; //$NON-NLS-1$
+			}
 			return stripWorkDir(absWd, absFile);
 		}
 
 		String relName = filePath.substring(workDirPath.length() + 1);
-		if (File.separatorChar != '/')
+		if (File.separatorChar != '/') {
 			relName = relName.replace(File.separatorChar, '/');
+		}
 		return relName;
 	}
 
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryCache.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryCache.java
index 400342b1..27befba 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryCache.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryCache.java
@@ -255,14 +255,11 @@
 			if (delay == RepositoryCacheConfig.NO_CLEANUP) {
 				return;
 			}
-			cleanupTask = scheduler.scheduleWithFixedDelay(new Runnable() {
-				@Override
-				public void run() {
-					try {
-						cache.clearAllExpired();
-					} catch (Throwable e) {
-						LOG.error(e.getMessage(), e);
-					}
+			cleanupTask = scheduler.scheduleWithFixedDelay(() -> {
+				try {
+					cache.clearAllExpired();
+				} catch (Throwable e) {
+					LOG.error(e.getMessage(), e);
 				}
 			}, delay, delay, TimeUnit.MILLISECONDS);
 		}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/SymbolicRef.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/SymbolicRef.java
index 00fcf52..9f0568f 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/SymbolicRef.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/SymbolicRef.java
@@ -71,7 +71,7 @@
 	public SymbolicRef(@NonNull String refName, @NonNull Ref target) {
 		this.name = refName;
 		this.target = target;
-		this.updateIndex = -1;
+		this.updateIndex = UNDEFINED_UPDATE_INDEX;
 	}
 
 	/**
@@ -155,7 +155,7 @@
 	 */
 	@Override
 	public long getUpdateIndex() {
-		if (updateIndex == -1) {
+		if (updateIndex == UNDEFINED_UPDATE_INDEX) {
 			throw new UnsupportedOperationException();
 		}
 		return updateIndex;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/internal/BouncyCastleGpgKeyLocator.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/internal/BouncyCastleGpgKeyLocator.java
index 091667d..0d44317 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/internal/BouncyCastleGpgKeyLocator.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/internal/BouncyCastleGpgKeyLocator.java
@@ -54,6 +54,8 @@
 import java.nio.file.InvalidPathException;
 import java.nio.file.Path;
 import java.nio.file.Paths;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
 import java.text.MessageFormat;
 import java.util.Iterator;
 import java.util.Locale;
@@ -67,6 +69,7 @@
 import org.bouncycastle.gpg.keybox.KeyInformation;
 import org.bouncycastle.gpg.keybox.PublicKeyRingBlob;
 import org.bouncycastle.gpg.keybox.UserID;
+import org.bouncycastle.gpg.keybox.jcajce.JcaKeyBoxBuilder;
 import org.bouncycastle.openpgp.PGPException;
 import org.bouncycastle.openpgp.PGPPublicKey;
 import org.bouncycastle.openpgp.PGPSecretKey;
@@ -85,6 +88,8 @@
 import org.eclipse.jgit.internal.JGitText;
 import org.eclipse.jgit.util.FS;
 import org.eclipse.jgit.util.SystemReader;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 /**
  * Locates GPG keys from either <code>~/.gnupg/private-keys-v1.d</code> or
@@ -92,6 +97,9 @@
  */
 class BouncyCastleGpgKeyLocator {
 
+	private static final Logger log = LoggerFactory
+			.getLogger(BouncyCastleGpgKeyLocator.class);
+
 	private static final Path GPG_DIRECTORY = findGpgDirectory();
 
 	private static final Path USER_KEYBOX_PATH = GPG_DIRECTORY
@@ -157,11 +165,14 @@
 	private PGPSecretKey attemptParseSecretKey(Path keyFile,
 			PGPDigestCalculatorProvider calculatorProvider,
 			PBEProtectionRemoverFactory passphraseProvider,
-			PGPPublicKey publicKey) throws IOException {
+			PGPPublicKey publicKey) {
 		try (InputStream in = newInputStream(keyFile)) {
 			return new SExprParser(calculatorProvider).parseSecretKey(
 					new BufferedInputStream(in), passphraseProvider, publicKey);
-		} catch (PGPException | ClassCastException e) {
+		} catch (IOException | PGPException | ClassCastException e) {
+			if (log.isDebugEnabled())
+				log.debug("Ignoring unreadable file '{}': {}", keyFile, //$NON-NLS-1$
+						e.getMessage(), e);
 			return null;
 		}
 	}
@@ -173,10 +184,11 @@
 
 	private PGPPublicKey findPublicKeyByKeyId(KeyBlob keyBlob)
 			throws IOException {
+		String keyId = signingKey.toLowerCase(Locale.ROOT);
 		for (KeyInformation keyInfo : keyBlob.getKeyInformation()) {
-			if (signingKey.toLowerCase(Locale.ROOT)
-					.equals(Hex.toHexString(keyInfo.getKeyID())
-							.toLowerCase(Locale.ROOT))) {
+			String fingerprint = Hex.toHexString(keyInfo.getFingerprint())
+					.toLowerCase(Locale.ROOT);
+			if (fingerprint.endsWith(keyId)) {
 				return getFirstPublicKey(keyBlob);
 			}
 		}
@@ -201,9 +213,12 @@
 	 * @return publicKey the public key (maybe <code>null</code>)
 	 * @throws IOException
 	 *             in case of problems reading the file
+	 * @throws NoSuchAlgorithmException
+	 * @throws NoSuchProviderException
 	 */
 	private PGPPublicKey findPublicKeyInKeyBox(Path keyboxFile)
-			throws IOException {
+			throws IOException, NoSuchAlgorithmException,
+			NoSuchProviderException {
 		KeyBox keyBox = readKeyBoxFile(keyboxFile);
 		for (KeyBlob keyBlob : keyBox.getKeyBlobs()) {
 			if (keyBlob.getType() == BlobType.OPEN_PGP_BLOB) {
@@ -227,15 +242,17 @@
 	 * @return the secret key
 	 * @throws IOException
 	 *             in case of issues reading key files
+	 * @throws NoSuchAlgorithmException
+	 * @throws NoSuchProviderException
 	 * @throws PGPException
 	 *             in case of issues finding a key
 	 * @throws CanceledException
 	 * @throws URISyntaxException
 	 * @throws UnsupportedCredentialItem
 	 */
-	public BouncyCastleGpgKey findSecretKey()
-			throws IOException, PGPException, CanceledException,
-			UnsupportedCredentialItem, URISyntaxException {
+	public BouncyCastleGpgKey findSecretKey() throws IOException,
+			NoSuchAlgorithmException, NoSuchProviderException, PGPException,
+			CanceledException, UnsupportedCredentialItem, URISyntaxException {
 		if (exists(USER_KEYBOX_PATH)) {
 			PGPPublicKey publicKey = //
 					findPublicKeyInKeyBox(USER_KEYBOX_PATH);
@@ -252,6 +269,10 @@
 					USER_PGP_LEGACY_SECRING_FILE);
 
 			if (secretKey != null) {
+				if (!secretKey.isSigningKey()) {
+					throw new PGPException(MessageFormat.format(
+							JGitText.get().gpgNotASigningKey, signingKey));
+				}
 				return new BouncyCastleGpgKey(secretKey, USER_PGP_LEGACY_SECRING_FILE);
 			}
 
@@ -285,6 +306,10 @@
 				PGPSecretKey secretKey = attemptParseSecretKey(keyFile,
 						calculatorProvider, passphraseProvider, publicKey);
 				if (secretKey != null) {
+					if (!secretKey.isSigningKey()) {
+						throw new PGPException(MessageFormat.format(
+								JGitText.get().gpgNotASigningKey, signingKey));
+					}
 					return new BouncyCastleGpgKey(secretKey, userKeyboxPath);
 				}
 			}
@@ -326,6 +351,7 @@
 					PGPUtil.getDecoderStream(new BufferedInputStream(in)),
 					new JcaKeyFingerprintCalculator());
 
+			String keyId = signingkey.toLowerCase(Locale.ROOT);
 			Iterator<PGPSecretKeyRing> keyrings = pgpSec.getKeyRings();
 			while (keyrings.hasNext()) {
 				PGPSecretKeyRing keyRing = keyrings.next();
@@ -336,8 +362,7 @@
 					String fingerprint = Hex
 							.toHexString(key.getPublicKey().getFingerprint())
 							.toLowerCase(Locale.ROOT);
-					if (fingerprint
-							.endsWith(signingkey.toLowerCase(Locale.ROOT))) {
+					if (fingerprint.endsWith(keyId)) {
 						return key;
 					}
 					// try user id
@@ -359,14 +384,12 @@
 				.getPublicKey();
 	}
 
-	private KeyBox readKeyBoxFile(Path keyboxFile) throws IOException {
+	private KeyBox readKeyBoxFile(Path keyboxFile) throws IOException,
+			NoSuchAlgorithmException, NoSuchProviderException {
 		KeyBox keyBox;
 		try (InputStream in = new BufferedInputStream(
 				newInputStream(keyboxFile))) {
-			// note: KeyBox constructor reads in the whole InputStream at once
-			// this code will change in 1.61 to
-			// either 'new BcKeyBox(in)' or 'new JcaKeyBoxBuilder().build(in)'
-			keyBox = new KeyBox(in, new JcaKeyFingerprintCalculator());
+			keyBox = new JcaKeyBoxBuilder().build(in);
 		}
 		return keyBox;
 	}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/internal/BouncyCastleGpgSigner.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/internal/BouncyCastleGpgSigner.java
index 4d696dd..cfe0931 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/internal/BouncyCastleGpgSigner.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/internal/BouncyCastleGpgSigner.java
@@ -45,6 +45,8 @@
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.net.URISyntaxException;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
 import java.security.Security;
 
 import org.bouncycastle.bcpg.ArmoredOutputStream;
@@ -100,7 +102,8 @@
 			BouncyCastleGpgKey gpgKey = locateSigningKey(gpgSigningKey,
 					committer, passphrasePrompt);
 			return gpgKey != null;
-		} catch (PGPException | IOException | URISyntaxException e) {
+		} catch (PGPException | IOException | NoSuchAlgorithmException
+				| NoSuchProviderException | URISyntaxException e) {
 			return false;
 		}
 	}
@@ -109,7 +112,8 @@
 			PersonIdent committer,
 			BouncyCastleGpgKeyPassphrasePrompt passphrasePrompt)
 			throws CanceledException, UnsupportedCredentialItem, IOException,
-			PGPException, URISyntaxException {
+			NoSuchAlgorithmException, NoSuchProviderException, PGPException,
+			URISyntaxException {
 		if (gpgSigningKey == null || gpgSigningKey.isEmpty()) {
 			gpgSigningKey = committer.getEmailAddress();
 		}
@@ -153,7 +157,8 @@
 				signatureGenerator.generate().encode(out);
 			}
 			commit.setGpgSignature(new GpgSignature(buffer.toByteArray()));
-		} catch (PGPException | IOException | URISyntaxException e) {
+		} catch (PGPException | IOException | NoSuchAlgorithmException
+				| NoSuchProviderException | URISyntaxException e) {
 			throw new JGitInternalException(e.getMessage(), e);
 		}
 	}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeAlgorithm.java b/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeAlgorithm.java
index dd42e43..a77cb4f 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeAlgorithm.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeAlgorithm.java
@@ -86,6 +86,11 @@
 	private final static Edit END_EDIT = new Edit(Integer.MAX_VALUE,
 			Integer.MAX_VALUE);
 
+	@SuppressWarnings("ReferenceEquality")
+	private static boolean isEndEdit(Edit edit) {
+		return edit == END_EDIT;
+	}
+
 	/**
 	 * Does the three way merge between a common base and two sequences.
 	 *
@@ -145,7 +150,7 @@
 		// iterate over all edits from base to ours and from base to theirs
 		// leave the loop when there are no edits more for ours or for theirs
 		// (or both)
-		while (theirsEdit != END_EDIT || oursEdit != END_EDIT) {
+		while (!isEndEdit(theirsEdit) || !isEndEdit(oursEdit)) {
 			if (oursEdit.getEndA() < theirsEdit.getBeginA()) {
 				// something was changed in ours not overlapping with any change
 				// from theirs. First add the common part in front of the edit
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 75334dd..0b423fb 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java
@@ -1182,7 +1182,7 @@
 	 *         fail.
 	 */
 	public Map<String, MergeFailureReason> getFailingPaths() {
-		return (failingPaths.size() == 0) ? null : failingPaths;
+		return failingPaths.isEmpty() ? null : failingPaths;
 	}
 
 	/**
@@ -1193,7 +1193,7 @@
 	 *         otherwise
 	 */
 	public boolean failed() {
-		return failingPaths.size() > 0;
+		return !failingPaths.isEmpty();
 	}
 
 	/**
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/nls/GlobalBundleCache.java b/org.eclipse.jgit/src/org/eclipse/jgit/nls/GlobalBundleCache.java
index fd425ab..b437f63 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/nls/GlobalBundleCache.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/nls/GlobalBundleCache.java
@@ -43,6 +43,7 @@
 
 package org.eclipse.jgit.nls;
 
+import java.lang.reflect.InvocationTargetException;
 import java.util.HashMap;
 import java.util.Locale;
 import java.util.Map;
@@ -92,14 +93,13 @@
 			}
 			TranslationBundle bundle = bundles.get(type);
 			if (bundle == null) {
-				bundle = type.newInstance();
+				bundle = type.getDeclaredConstructor().newInstance();
 				bundle.load(locale);
 				bundles.put(type, bundle);
 			}
 			return (T) bundle;
-		} catch (InstantiationException e) {
-			throw new Error(e);
-		} catch (IllegalAccessException e) {
+		} catch (InstantiationException | IllegalAccessException
+				| InvocationTargetException | NoSuchMethodException e) {
 			throw new Error(e);
 		}
 	}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/nls/TranslationBundle.java b/org.eclipse.jgit/src/org/eclipse/jgit/nls/TranslationBundle.java
index cdd7be2..c41fb41 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/nls/TranslationBundle.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/nls/TranslationBundle.java
@@ -182,9 +182,7 @@
 					field.set(this, translatedText);
 				} catch (MissingResourceException e) {
 					throw new TranslationStringMissingException(bundleClass, locale, field.getName(), e);
-				} catch (IllegalArgumentException e) {
-					throw new Error(e);
-				} catch (IllegalAccessException e) {
+				} catch (IllegalArgumentException | IllegalAccessException e) {
 					throw new Error(e);
 				}
 			}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/notes/NoteMapMerger.java b/org.eclipse.jgit/src/org/eclipse/jgit/notes/NoteMapMerger.java
index 325ff4f..ba7223b 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/notes/NoteMapMerger.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/notes/NoteMapMerger.java
@@ -295,14 +295,14 @@
 	private static boolean sameNote(Note a, Note b) {
 		if (a == null && b == null)
 			return true;
-		return a != null && b != null && AnyObjectId.equals(a, b);
+		return a != null && b != null && AnyObjectId.isEqual(a, b);
 	}
 
 	private static boolean sameContent(Note a, Note b) {
 		if (a == null && b == null)
 			return true;
 		return a != null && b != null
-				&& AnyObjectId.equals(a.getData(), b.getData());
+				&& AnyObjectId.isEqual(a.getData(), b.getData());
 	}
 
 	private static InMemoryNoteBucket addIfNotNull(InMemoryNoteBucket result,
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/patch/CombinedHunkHeader.java b/org.eclipse.jgit/src/org/eclipse/jgit/patch/CombinedHunkHeader.java
index d278132..74eec81 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/patch/CombinedHunkHeader.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/patch/CombinedHunkHeader.java
@@ -109,12 +109,13 @@
 		final MutableInteger ptr = new MutableInteger();
 		ptr.value = nextLF(buf, startOffset, ' ');
 
-		for (int n = 0; n < old.length; n++) {
-			old[n].startLine = -parseBase10(buf, ptr.value, ptr);
-			if (buf[ptr.value] == ',')
-				old[n].lineCount = parseBase10(buf, ptr.value + 1, ptr);
-			else
-				old[n].lineCount = 1;
+		for (CombinedOldImage o : old) {
+			o.startLine = -parseBase10(buf, ptr.value, ptr);
+			if (buf[ptr.value] == ',') {
+				o.lineCount = parseBase10(buf, ptr.value + 1, ptr);
+			} else {
+				o.lineCount = 1;
+			}
 		}
 
 		newStartLine = parseBase10(buf, ptr.value + 1, ptr);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revplot/AbstractPlotRenderer.java b/org.eclipse.jgit/src/org/eclipse/jgit/revplot/AbstractPlotRenderer.java
index 58e2106..b2f8d11 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revplot/AbstractPlotRenderer.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revplot/AbstractPlotRenderer.java
@@ -114,39 +114,39 @@
 			drawLine(myColor, myLaneX, h, myLaneX, (h + dotSize) / 2,
 					LINE_WIDTH);
 
-			for (int i = 0; i < commit.mergingLanes.length; i++) {
-				final TLane pLane = (TLane) commit.mergingLanes[i];
+			for (PlotLane mergingLane : commit.mergingLanes) {
+				final TLane pLane = (TLane) mergingLane;
 				final TColor pColor = laneColor(pLane);
 				final int cx = laneC(pLane);
-
 				if (Math.abs(myLaneX - cx) > LANE_WIDTH) {
 					final int ix;
-					if (myLaneX < cx)
+					if (myLaneX < cx) {
 						ix = cx - LANE_WIDTH / 2;
-					else
+					} else {
 						ix = cx + LANE_WIDTH / 2;
+					}
 
 					drawLine(pColor, myLaneX, h / 2, ix, h / 2, LINE_WIDTH);
 					drawLine(pColor, ix, h / 2, cx, h, LINE_WIDTH);
 				} else
 					drawLine(pColor, myLaneX, h / 2, cx, h, LINE_WIDTH);
-
 				maxCenter = Math.max(maxCenter, cx);
 			}
 		}
 
 
 		if (commit.getChildCount() > 0) {
-			for (int i = 0; i < commit.forkingOffLanes.length; i++) {
-				final TLane childLane = (TLane) commit.forkingOffLanes[i];
+			for (PlotLane forkingOffLane : commit.forkingOffLanes) {
+				final TLane childLane = (TLane) forkingOffLane;
 				final TColor cColor = laneColor(childLane);
 				final int cx = laneC(childLane);
 				if (Math.abs(myLaneX - cx) > LANE_WIDTH) {
 					final int ix;
-					if (myLaneX < cx)
+					if (myLaneX < cx) {
 						ix = cx - LANE_WIDTH / 2;
-					else
+					} else {
 						ix = cx + LANE_WIDTH / 2;
+					}
 
 					drawLine(cColor, myLaneX, h / 2, ix, h / 2, LINE_WIDTH);
 					drawLine(cColor, ix, h / 2, cx, 0, LINE_WIDTH);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/BitmapCalculator.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/BitmapCalculator.java
new file mode 100644
index 0000000..14e9567
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/BitmapCalculator.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2019, 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.revwalk;
+
+import static java.util.Objects.requireNonNull;
+
+import java.io.IOException;
+
+import org.eclipse.jgit.errors.IncorrectObjectTypeException;
+import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.internal.revwalk.AddToBitmapFilter;
+import org.eclipse.jgit.lib.BitmapIndex;
+import org.eclipse.jgit.lib.BitmapIndex.Bitmap;
+import org.eclipse.jgit.lib.BitmapIndex.BitmapBuilder;
+import org.eclipse.jgit.lib.ProgressMonitor;
+
+/**
+ * Calculate the bitmap indicating what other commits are reachable from certain
+ * commit.
+ * <p>
+ * This bitmap refers only to commits. For a bitmap with ALL objects reachable
+ * from certain object, see {@code BitmapWalker}.
+ */
+class BitmapCalculator {
+
+	private final RevWalk walk;
+	private final BitmapIndex bitmapIndex;
+
+	BitmapCalculator(RevWalk walk) throws IOException {
+		this.walk = walk;
+		this.bitmapIndex = requireNonNull(
+				walk.getObjectReader().getBitmapIndex());
+	}
+
+	/**
+	 * Get the reachability bitmap from certain commit to other commits.
+	 * <p>
+	 * This will return a precalculated bitmap if available or walk building one
+	 * until finding a precalculated bitmap (and returning the union).
+	 * <p>
+	 * Beware that the returned bitmap it is guaranteed to include ONLY the
+	 * commits reachable from the initial commit. It COULD include other objects
+	 * (because precalculated bitmaps have them) but caller shouldn't count on
+	 * that. See {@link BitmapWalker} for a full reachability bitmap.
+	 *
+	 * @param start
+	 *            the commit. Use {@code walk.parseCommit(objectId)} to get this
+	 *            object from the id.
+	 * @param pm
+	 *            progress monitor. Updated by one per commit browsed in the
+	 *            graph
+	 * @return the bitmap of reachable commits (and maybe some extra objects)
+	 *         for the commit
+	 * @throws MissingObjectException
+	 *             the supplied id doesn't exist
+	 * @throws IncorrectObjectTypeException
+	 *             the supplied id doesn't refer to a commit or a tag
+	 * @throws IOException
+	 *             if the walk cannot open a packfile or loose object
+	 */
+	BitmapBuilder getBitmap(RevCommit start, ProgressMonitor pm)
+			throws MissingObjectException,
+			IncorrectObjectTypeException, IOException {
+		Bitmap precalculatedBitmap = bitmapIndex.getBitmap(start);
+		if (precalculatedBitmap != null) {
+			return asBitmapBuilder(precalculatedBitmap);
+		}
+
+		walk.reset();
+		walk.sort(RevSort.TOPO);
+		walk.markStart(start);
+		// Unbounded walk. If the repo has bitmaps, it should bump into one at
+		// some point.
+
+		BitmapBuilder bitmapResult = bitmapIndex.newBitmapBuilder();
+		walk.setRevFilter(new AddToBitmapFilter(bitmapResult));
+		while (walk.next() != null) {
+			// Iterate through all of the commits. The BitmapRevFilter does
+			// the work.
+			//
+			// filter.include returns true for commits that do not have
+			// a bitmap in bitmapIndex and are not reachable from a
+			// bitmap in bitmapIndex encountered earlier in the walk.
+			// Thus the number of commits returned by next() measures how
+			// much history was traversed without being able to make use
+			// of bitmaps.
+			pm.update(1);
+		}
+
+		return bitmapResult;
+	}
+
+	private BitmapBuilder asBitmapBuilder(Bitmap bitmap) {
+		return bitmapIndex.newBitmapBuilder().or(bitmap);
+	}
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/BitmappedReachabilityChecker.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/BitmappedReachabilityChecker.java
new file mode 100644
index 0000000..6e510f6
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/BitmappedReachabilityChecker.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2019, 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.revwalk;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Optional;
+
+import org.eclipse.jgit.errors.IncorrectObjectTypeException;
+import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.lib.BitmapIndex.BitmapBuilder;
+import org.eclipse.jgit.lib.NullProgressMonitor;
+
+/**
+ * Checks the reachability using bitmaps.
+ */
+class BitmappedReachabilityChecker implements ReachabilityChecker {
+
+	private final RevWalk walk;
+
+	/**
+	 * @param walk
+	 *            walk on the repository to get or create the bitmaps for the
+	 *            commits. It must have bitmaps.
+	 * @throws AssertionError
+	 *             runtime exception if walk is over a repository without
+	 *             bitmaps
+	 * @throws IOException
+	 *             if the index or the object reader cannot be opened.
+	 */
+	public BitmappedReachabilityChecker(RevWalk walk)
+			throws IOException {
+		this.walk = walk;
+		if (walk.getObjectReader().getBitmapIndex() == null) {
+			throw new AssertionError(
+					"Trying to use bitmapped reachability check " //$NON-NLS-1$
+							+ "on a repository without bitmaps"); //$NON-NLS-1$
+		}
+	}
+
+	/**
+	 * Check all targets are reachable from the starters.
+	 * <p>
+	 * In this implementation, it is recommended to put the most popular
+	 * starters (e.g. refs/heads tips) at the beginning of the collection
+	 */
+	@Override
+	public Optional<RevCommit> areAllReachable(Collection<RevCommit> targets,
+			Collection<RevCommit> starters) throws MissingObjectException,
+			IncorrectObjectTypeException, IOException {
+		BitmapCalculator calculator = new BitmapCalculator(walk);
+
+		/**
+		 * Iterate over starters bitmaps and remove targets as they become
+		 * reachable.
+		 *
+		 * Building the total starters bitmap has the same cost (iterating over
+		 * all starters adding the bitmaps) and this gives us the chance to
+		 * shorcut the loop.
+		 *
+		 * This is based on the assuption that most of the starters will have
+		 * the reachability bitmap precalculated. If many require a walk, the
+		 * walk.reset() could start to take too much time.
+		 */
+		List<RevCommit> remainingTargets = new ArrayList<>(targets);
+		for (RevCommit starter : starters) {
+			BitmapBuilder starterBitmap = calculator.getBitmap(starter,
+					NullProgressMonitor.INSTANCE);
+			remainingTargets.removeIf(starterBitmap::contains);
+			if (remainingTargets.isEmpty()) {
+				return Optional.empty();
+			}
+		}
+
+		return Optional.of(remainingTargets.get(0));
+	}
+
+}
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 5154920..5199a29 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/DepthGenerator.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/DepthGenerator.java
@@ -108,12 +108,25 @@
 
 		// Begin by sucking out all of the source's commits, and
 		// adding them to the pending queue
+		FIFORevQueue unshallowCommits = new FIFORevQueue();
 		for (;;) {
 			RevCommit c = s.next();
 			if (c == null)
 				break;
-			if (((DepthWalk.Commit) c).getDepth() == 0)
+			if (c.has(UNSHALLOW)) {
+				unshallowCommits.add(c);
+			} else if (((DepthWalk.Commit) c).getDepth() == 0) {
 				pending.add(c);
+			}
+		}
+		// Move unshallow commits to the front so that the REINTERESTING flag
+		// carry over code is executed first.
+		for (;;) {
+			RevCommit c = unshallowCommits.next();
+			if (c == null) {
+				break;
+			}
+			pending.unpop(c);
 		}
 
 		// Mark DEEPEN_NOT on all deepen-not commits and their ancestors.
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/FooterLine.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/FooterLine.java
index d6fed66..84b6d2e 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/FooterLine.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/FooterLine.java
@@ -95,7 +95,7 @@
 		for (int kPtr = 0; kPtr < len;) {
 			byte b = buffer[bPtr++];
 			if ('A' <= b && b <= 'Z')
-				b += 'a' - 'A';
+				b += (byte) ('a' - 'A');
 			if (b != kRaw[kPtr++])
 				return false;
 		}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/ObjectWalk.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/ObjectWalk.java
index fd578da..b6c5810 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/ObjectWalk.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/ObjectWalk.java
@@ -43,6 +43,7 @@
 
 package org.eclipse.jgit.revwalk;
 
+import static java.util.Objects.requireNonNull;
 import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
 import static org.eclipse.jgit.lib.Constants.OBJ_COMMIT;
 import static org.eclipse.jgit.lib.Constants.OBJ_TREE;
@@ -97,6 +98,55 @@
 	 */
 	private static final int IN_PENDING = RevWalk.REWRITE;
 
+	/**
+	 * When walking over a tree and blob graph, objects are usually marked as
+	 * seen as they are visited and this "seen" status is checked upon the next
+	 * visit. If they are already "seen" then they are not processed (returned
+	 * by {@link ObjectWalk#nextObject()}) again. However, this behavior can be
+	 * overridden by supplying a different implementation of this class.
+	 *
+	 * @since 5.4
+	 */
+	public interface VisitationPolicy {
+		/**
+		 * Whenever the rev or object walk reaches a Git object, if that object
+		 * already exists as a RevObject, this method is called to determine if
+		 * that object should be visited.
+		 *
+		 * @param o
+		 *            the object to check if it should be visited
+		 * @return true if the object should be visited
+		 */
+		boolean shouldVisit(RevObject o);
+
+		/**
+		 * Called when an object is visited.
+		 *
+		 * @param o
+		 *            the object that was visited
+		 */
+		void visited(RevObject o);
+	}
+
+	/**
+	 * The default visitation policy: causes all objects to be visited exactly
+	 * once.
+	 *
+	 * @since 5.4
+	 */
+	public static final VisitationPolicy SIMPLE_VISITATION_POLICY =
+			new VisitationPolicy() {
+		@Override
+		public boolean shouldVisit(RevObject o) {
+			return (o.flags & SEEN) == 0;
+		}
+
+		@Override
+		public void visited(RevObject o) {
+			o.flags |= SEEN;
+		}
+	};
+
 	private List<RevObject> rootObjects;
 
 	private BlockObjQueue pendingObjects;
@@ -113,6 +163,8 @@
 
 	private boolean boundary;
 
+	private VisitationPolicy visitationPolicy = SIMPLE_VISITATION_POLICY;
+
 	/**
 	 * Create a new revision and object walker for a given repository.
 	 *
@@ -299,6 +351,18 @@
 		objectFilter = newFilter != null ? newFilter : ObjectFilter.ALL;
 	}
 
+	/**
+	 * Sets the visitation policy to use during this walk.
+	 *
+	 * @param policy
+	 *            the {@code VisitationPolicy} to use
+	 * @since 5.4
+	 */
+	public void setVisitationPolicy(VisitationPolicy policy) {
+		assertNotStarted();
+		visitationPolicy = requireNonNull(policy);
+	}
+
 	/** {@inheritDoc} */
 	@Override
 	public RevCommit next() throws MissingObjectException,
@@ -326,6 +390,17 @@
 	}
 
 	/**
+	 * Skips the current tree such that {@link #nextObject()} does not return
+	 * any objects inside it. This should be called right after
+	 * {@link #nextObject()} returns the tree.
+	 *
+	 * @since 5.4
+	 */
+	public void skipTree() {
+		currVisit.ptr = currVisit.buf.length;
+	}
+
+	/**
 	 * Pop the next most recent object.
 	 *
 	 * @return next most recent object; null if traversal is over.
@@ -357,24 +432,23 @@
 				}
 
 				RevObject obj = objects.get(idBuffer);
-				if (obj != null && (obj.flags & SEEN) != 0)
+				if (obj != null && !visitationPolicy.shouldVisit(obj))
 					continue;
 
 				int mode = parseMode(buf, startPtr, ptr, tv);
-				int flags;
 				switch (mode >>> TYPE_SHIFT) {
 				case TYPE_FILE:
 				case TYPE_SYMLINK:
 					if (obj == null) {
 						obj = new RevBlob(idBuffer);
-						obj.flags = SEEN;
+						visitationPolicy.visited(obj);
 						objects.add(obj);
 						return obj;
 					}
 					if (!(obj instanceof RevBlob))
 						throw new IncorrectObjectTypeException(obj, OBJ_BLOB);
-					obj.flags = flags = obj.flags | SEEN;
-					if ((flags & UNINTERESTING) == 0)
+					visitationPolicy.visited(obj);
+					if ((obj.flags & UNINTERESTING) == 0)
 						return obj;
 					if (boundary)
 						return obj;
@@ -383,17 +457,17 @@
 				case TYPE_TREE:
 					if (obj == null) {
 						obj = new RevTree(idBuffer);
-						obj.flags = SEEN;
+						visitationPolicy.visited(obj);
 						objects.add(obj);
-						return enterTree(obj);
+						return pushTree(obj);
 					}
 					if (!(obj instanceof RevTree))
 						throw new IncorrectObjectTypeException(obj, OBJ_TREE);
-					obj.flags = flags = obj.flags | SEEN;
-					if ((flags & UNINTERESTING) == 0)
-						return enterTree(obj);
+					visitationPolicy.visited(obj);
+					if ((obj.flags & UNINTERESTING) == 0)
+						return pushTree(obj);
 					if (boundary)
-						return enterTree(obj);
+						return pushTree(obj);
 					continue;
 
 				case TYPE_GITLINK:
@@ -419,30 +493,23 @@
 			if (o == null) {
 				return null;
 			}
-			int flags = o.flags;
-			if ((flags & SEEN) != 0)
+			if (!visitationPolicy.shouldVisit(o)) {
 				continue;
-			flags |= SEEN;
-			o.flags = flags;
-			if ((flags & UNINTERESTING) == 0 | boundary) {
+			}
+			visitationPolicy.visited(o);
+			if ((o.flags & UNINTERESTING) == 0 || boundary) {
 				if (o instanceof RevTree) {
-					tv = newTreeVisit(o);
-					tv.parent = null;
-					currVisit = tv;
+					// The previous while loop should have exhausted the stack
+					// of trees.
+					assert currVisit == null;
+
+					pushTree(o);
 				}
 				return o;
 			}
 		}
 	}
 
-	private RevObject enterTree(RevObject obj) throws MissingObjectException,
-			IncorrectObjectTypeException, IOException {
-		TreeVisit tv = newTreeVisit(obj);
-		tv.parent = currVisit;
-		currVisit = tv;
-		return obj;
-	}
-
 	private static int findObjectId(byte[] buf, int ptr) {
 		// Skip over the mode and name until the NUL before the ObjectId
 		// can be located. Skip the NUL as the function returns.
@@ -582,6 +649,17 @@
 	}
 
 	/**
+	 * @return the current traversal depth from the root tree object
+	 * @since 5.4
+	 */
+	public int getTreeDepth() {
+		if (currVisit == null) {
+			return 0;
+		}
+		return currVisit.depth;
+	}
+
+	/**
 	 * Get the current object's path hash code.
 	 * <p>
 	 * This method computes a hash code on the fly for this path, the hash is
@@ -768,7 +846,7 @@
 		}
 	}
 
-	private TreeVisit newTreeVisit(RevObject obj) throws LargeObjectException,
+	private RevObject pushTree(RevObject obj) throws LargeObjectException,
 			MissingObjectException, IncorrectObjectTypeException, IOException {
 		TreeVisit tv = freeVisit;
 		if (tv != null) {
@@ -782,7 +860,15 @@
 		}
 		tv.obj = obj;
 		tv.buf = reader.open(obj, OBJ_TREE).getCachedBytes();
-		return tv;
+		tv.parent = currVisit;
+		currVisit = tv;
+		if (tv.parent == null) {
+			tv.depth = 1;
+		} else {
+			tv.depth = tv.parent.depth + 1;
+		}
+
+		return obj;
 	}
 
 	private void releaseTreeVisit(TreeVisit tv) {
@@ -812,5 +898,8 @@
 
 		/** Number of bytes in the path leading up to this tree. */
 		int pathLen;
+
+		/** Number of levels deep from the root tree. 0 for root tree. */
+		int depth;
 	}
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/PedestrianReachabilityChecker.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/PedestrianReachabilityChecker.java
new file mode 100644
index 0000000..bba3c5c
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/PedestrianReachabilityChecker.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2019, 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.revwalk;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Optional;
+
+import org.eclipse.jgit.errors.IncorrectObjectTypeException;
+import org.eclipse.jgit.errors.MissingObjectException;
+
+/**
+ * Checks the reachability walking the graph from the starters towards the
+ * target.
+ */
+class PedestrianReachabilityChecker implements ReachabilityChecker {
+
+	private final boolean topoSort;
+
+	private final RevWalk walk;
+
+	/**
+	 * New instance of the reachability checker using a existing walk.
+	 *
+	 * @param topoSort
+	 *            walk commits in topological order
+	 * @param walk
+	 *            RevWalk instance to reuse. Caller retains ownership.
+	 */
+	public PedestrianReachabilityChecker(boolean topoSort,
+			RevWalk walk) {
+		this.topoSort = topoSort;
+		this.walk = walk;
+	}
+
+	@Override
+	public Optional<RevCommit> areAllReachable(Collection<RevCommit> targets,
+			Collection<RevCommit> starters)
+					throws MissingObjectException, IncorrectObjectTypeException,
+					IOException {
+		walk.reset();
+		if (topoSort) {
+			walk.sort(RevSort.TOPO);
+		}
+
+		for (RevCommit target: targets) {
+			walk.markStart(target);
+		}
+
+		for (RevCommit starter : starters) {
+			walk.markUninteresting(starter);
+		}
+
+		return Optional.ofNullable(walk.next());
+	}
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/ReachabilityChecker.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/ReachabilityChecker.java
new file mode 100644
index 0000000..2ed06d1
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/ReachabilityChecker.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2019, 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.revwalk;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Optional;
+
+import org.eclipse.jgit.errors.IncorrectObjectTypeException;
+import org.eclipse.jgit.errors.MissingObjectException;
+
+/**
+ * Check if a commit is reachable from a collection of starting commits.
+ * <p>
+ * Note that this checks the reachability of commits (and tags). Trees, blobs or
+ * any other object will cause IncorrectObjectTypeException exceptions.
+ *
+ * @since 5.4
+ */
+public interface ReachabilityChecker {
+
+	/**
+	 * Check if all targets are reachable from the {@code starter} commits.
+	 * <p>
+	 * Caller should parse the objectIds (preferably with
+	 * {@code walk.parseCommit()} and handle missing/incorrect type objects
+	 * before calling this method.
+	 *
+	 * @param targets
+	 *            commits to reach.
+	 * @param starters
+	 *            known starting points.
+	 * @return An unreachable target if at least one of the targets is
+	 *         unreachable. An empty optional if all targets are reachable from
+	 *         the starters.
+	 *
+	 * @throws MissingObjectException
+	 *             if any of the incoming objects doesn't exist in the
+	 *             repository.
+	 * @throws IncorrectObjectTypeException
+	 *             if any of the incoming objects is not a commit or a tag.
+	 * @throws IOException
+	 *             if any of the underlying indexes or readers can not be
+	 *             opened.
+	 */
+	Optional<RevCommit> areAllReachable(Collection<RevCommit> targets,
+			Collection<RevCommit> starters)
+			throws MissingObjectException, IncorrectObjectTypeException,
+			IOException;
+}
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 0a43e8f..f50d189 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevWalk.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevWalk.java
@@ -72,6 +72,7 @@
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.revwalk.filter.RevFilter;
 import org.eclipse.jgit.treewalk.filter.TreeFilter;
+import org.eclipse.jgit.util.References;
 
 /**
  * Walks a commit graph and produces the matching commits in order.
@@ -250,6 +251,23 @@
 	}
 
 	/**
+	 * Get a reachability checker for commits over this revwalk.
+	 *
+	 * @return the most efficient reachability checker for this repository.
+	 * @throws IOException
+	 *             if it cannot open any of the underlying indices.
+	 *
+	 * @since 5.4
+	 */
+	public ReachabilityChecker createReachabilityChecker() throws IOException {
+		if (reader.getBitmapIndex() != null) {
+			return new BitmappedReachabilityChecker(this);
+		}
+
+		return new PedestrianReachabilityChecker(true, this);
+	}
+
+	/**
 	 * {@inheritDoc}
 	 * <p>
 	 * Release any resources used by this walker's reader.
@@ -416,9 +434,11 @@
 			markStart(tip);
 			markStart(base);
 			RevCommit mergeBase;
-			while ((mergeBase = next()) != null)
-				if (mergeBase == base)
+			while ((mergeBase = next()) != null) {
+				if (References.isSameObject(mergeBase, base)) {
 					return true;
+				}
+			}
 			return false;
 		} finally {
 			filter = oldRF;
@@ -506,7 +526,7 @@
 
 		if (sorting.size() > 1)
 			sorting.remove(RevSort.NONE);
-		else if (sorting.size() == 0)
+		else if (sorting.isEmpty())
 			sorting.add(RevSort.NONE);
 	}
 
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevWalkUtils.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevWalkUtils.java
index f1252a4..2b721b8 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevWalkUtils.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevWalkUtils.java
@@ -50,6 +50,9 @@
 
 import org.eclipse.jgit.errors.IncorrectObjectTypeException;
 import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.internal.JGitText;
+import org.eclipse.jgit.lib.NullProgressMonitor;
+import org.eclipse.jgit.lib.ProgressMonitor;
 import org.eclipse.jgit.lib.Ref;
 
 /**
@@ -130,7 +133,7 @@
 
 	/**
 	 * Find the list of branches a given commit is reachable from when following
-	 * parent.s
+	 * parents.
 	 * <p>
 	 * Note that this method calls
 	 * {@link org.eclipse.jgit.revwalk.RevWalk#reset()} at the beginning.
@@ -153,15 +156,51 @@
 			RevWalk revWalk, Collection<Ref> refs)
 			throws MissingObjectException, IncorrectObjectTypeException,
 			IOException {
+		return findBranchesReachableFrom(commit, revWalk, refs,
+				NullProgressMonitor.INSTANCE);
+	}
+
+	/**
+	 * Find the list of branches a given commit is reachable from when following
+	 * parents.
+	 * <p>
+	 * Note that this method calls
+	 * {@link org.eclipse.jgit.revwalk.RevWalk#reset()} at the beginning.
+	 * <p>
+	 * In order to improve performance this method assumes clock skew among
+	 * committers is never larger than 24 hours.
+	 *
+	 * @param commit
+	 *            the commit we are looking at
+	 * @param revWalk
+	 *            The RevWalk to be used.
+	 * @param refs
+	 *            the set of branches we want to see reachability from
+	 * @param monitor
+	 *            the callback for progress and cancellation
+	 * @return the list of branches a given commit is reachable from
+	 * @throws org.eclipse.jgit.errors.MissingObjectException
+	 * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException
+	 * @throws java.io.IOException
+	 * @since 5.4
+	 */
+	public static List<Ref> findBranchesReachableFrom(RevCommit commit,
+			RevWalk revWalk, Collection<Ref> refs, ProgressMonitor monitor)
+			throws MissingObjectException, IncorrectObjectTypeException,
+			IOException {
 
 		// Make sure commit is from the same RevWalk
 		commit = revWalk.parseCommit(commit.getId());
 		revWalk.reset();
 		List<Ref> result = new ArrayList<>();
-
+		monitor.beginTask(JGitText.get().searchForReachableBranches,
+				refs.size());
 		final int SKEW = 24*3600; // one day clock skew
 
 		for (Ref ref : refs) {
+			if (monitor.isCancelled())
+				return result;
+			monitor.update(1);
 			RevObject maybehead = revWalk.parseAny(ref.getObjectId());
 			if (!(maybehead instanceof RevCommit))
 				continue;
@@ -176,6 +215,7 @@
 			if (revWalk.isMergedInto(commit, headCommit))
 				result.add(ref);
 		}
+		monitor.endTask();
 		return result;
 	}
 
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/RevFlagFilter.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/RevFlagFilter.java
index c67c44b..c3dc3de 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/RevFlagFilter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/RevFlagFilter.java
@@ -44,6 +44,7 @@
 package org.eclipse.jgit.revwalk.filter;
 
 import java.io.IOException;
+import java.util.Arrays;
 
 import org.eclipse.jgit.errors.IncorrectObjectTypeException;
 import org.eclipse.jgit.errors.MissingObjectException;
@@ -78,8 +79,7 @@
 	 */
 	public static RevFilter hasAll(RevFlag... a) {
 		final RevFlagSet set = new RevFlagSet();
-		for (RevFlag flag : a)
-			set.add(flag);
+		set.addAll(Arrays.asList(a));
 		return new HasAll(set);
 	}
 
@@ -103,8 +103,7 @@
 	 */
 	public static RevFilter hasAny(RevFlag... a) {
 		final RevFlagSet set = new RevFlagSet();
-		for (RevFlag flag : a)
-			set.add(flag);
+		set.addAll(Arrays.asList(a));
 		return new HasAny(set);
 	}
 
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 072938b..9bec2e1 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
@@ -148,7 +148,8 @@
 	 */
 	@Override
 	public void load() throws IOException, ConfigInvalidException {
-		final int maxStaleRetries = 5;
+		final int maxRetries = 5;
+		int retryDelayMillis = 20;
 		int retries = 0;
 		while (true) {
 			final FileSnapshot oldSnapshot = snapshot;
@@ -179,6 +180,22 @@
 				}
 				return;
 			} catch (FileNotFoundException noFile) {
+				// might be locked by another process (see exception Javadoc)
+				if (retries < maxRetries && configFile.exists()) {
+					if (LOG.isDebugEnabled()) {
+						LOG.debug(MessageFormat.format(
+								JGitText.get().configHandleMayBeLocked,
+								Integer.valueOf(retries)), noFile);
+					}
+					try {
+						Thread.sleep(retryDelayMillis);
+					} catch (InterruptedException e) {
+						Thread.currentThread().interrupt();
+					}
+					retries++;
+					retryDelayMillis *= 2; // max wait 1260 ms
+					continue;
+				}
 				if (configFile.exists()) {
 					throw noFile;
 				}
@@ -187,7 +204,7 @@
 				return;
 			} catch (IOException e) {
 				if (FileUtils.isStaleFileHandle(e)
-						&& retries < maxStaleRetries) {
+						&& retries < maxRetries) {
 					if (LOG.isDebugEnabled()) {
 						LOG.debug(MessageFormat.format(
 								JGitText.get().configHandleIsStale,
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackStatistics.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackStatistics.java
index 68878e5..e6e3d4f 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackStatistics.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackStatistics.java
@@ -266,6 +266,10 @@
 		/** Time in ms spent writing the pack. */
 		public long timeWriting;
 
+		/** Number of trees traversed in the walk when writing the pack.
+		 * @since 5.4*/
+		public long treesTraversed;
+
 		/**
 		 * Statistics about each object type in the pack (commits, tags, trees
 		 * and blobs.)
@@ -586,6 +590,14 @@
 	}
 
 	/**
+	 * @return number of trees traversed in the walk when writing the pack.
+	 * @since 5.4
+	 */
+	public long getTreesTraversed() {
+		return statistics.treesTraversed;
+	}
+
+	/**
 	 * Get total time spent processing this pack.
 	 *
 	 * @return total time spent processing this pack.
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/submodule/SubmoduleWalk.java b/org.eclipse.jgit/src/org/eclipse/jgit/submodule/SubmoduleWalk.java
index ce8995a..e5559de 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/submodule/SubmoduleWalk.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/submodule/SubmoduleWalk.java
@@ -144,7 +144,8 @@
 	 *            path and .gitmodules at the root.
 	 * @param path
 	 *            a {@link java.lang.String} object.
-	 * @return generator at given path, null if no submodule at given path
+	 * @return generator at given path. The caller is responsible for calling
+	 *         {@link #close()}. Null if no submodule at given path.
 	 * @throws java.io.IOException
 	 */
 	public static SubmoduleWalk forPath(Repository repository,
@@ -177,7 +178,8 @@
 	 *            path and .gitmodules at the root.
 	 * @param path
 	 *            a {@link java.lang.String} object.
-	 * @return generator at given path, null if no submodule at given path
+	 * @return generator at given path. The caller is responsible for calling
+	 *         {@link #close()}. Null if no submodule at given path.
 	 * @throws java.io.IOException
 	 */
 	public static SubmoduleWalk forPath(Repository repository,
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackConnection.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackConnection.java
index fcf78ac..e8724b7 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackConnection.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackConnection.java
@@ -53,6 +53,7 @@
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.text.MessageFormat;
+import java.util.Arrays;
 import java.util.HashSet;
 import java.util.LinkedHashMap;
 import java.util.Set;
@@ -182,10 +183,7 @@
 		} catch (TransportException err) {
 			close();
 			throw err;
-		} catch (IOException err) {
-			close();
-			throw new TransportException(err.getMessage(), err);
-		} catch (RuntimeException err) {
+		} catch (IOException | RuntimeException err) {
 			close();
 			throw new TransportException(err.getMessage(), err);
 		}
@@ -203,7 +201,7 @@
 					throw noRepository();
 				throw eof;
 			}
-			if (line == PacketLineIn.END)
+			if (PacketLineIn.isEnd(line))
 				break;
 
 			if (line.startsWith("ERR ")) { //$NON-NLS-1$
@@ -217,8 +215,8 @@
 				if (nul >= 0) {
 					// The first line (if any) may contain "hidden"
 					// capability values after a NUL byte.
-					for (String c : line.substring(nul + 1).split(" ")) //$NON-NLS-1$
-						remoteCapablities.add(c);
+					remoteCapablities.addAll(
+							Arrays.asList(line.substring(nul + 1).split(" "))); //$NON-NLS-1$
 					line = line.substring(0, nul);
 				}
 			}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackFetchConnection.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackFetchConnection.java
index ed7465c..57d6bc2 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackFetchConnection.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackFetchConnection.java
@@ -245,8 +245,12 @@
 
 	private PacketLineOut pckState;
 
-	/** If not -1, the maximum blob size to be sent to the server. */
-	private final long filterBlobLimit;
+	/**
+	 * Either FilterSpec.NO_FILTER for a filter that doesn't filter
+	 * anything, or a filter that indicates what and what not to send to the
+	 * server.
+	 */
+	private final FilterSpec filterSpec;
 
 	/**
 	 * Create a new connection to fetch using the native git transport.
@@ -268,10 +272,11 @@
 
 		includeTags = transport.getTagOpt() != TagOpt.NO_TAGS;
 		thinPack = transport.isFetchThin();
-		filterBlobLimit = transport.getFilterBlobLimit();
+		filterSpec = transport.getFilterSpec();
 
 		if (local != null) {
 			walk = new RevWalk(local);
+			walk.setRetainBody(false);
 			reachableCommits = new RevCommitList<>();
 			REACHABLE = walk.newFlag("REACHABLE"); //$NON-NLS-1$
 			COMMON = walk.newFlag("COMMON"); //$NON-NLS-1$
@@ -395,10 +400,7 @@
 		} catch (CancelledException ce) {
 			close();
 			return; // Caller should test (or just know) this themselves.
-		} catch (IOException err) {
-			close();
-			throw new TransportException(err.getMessage(), err);
-		} catch (RuntimeException err) {
+		} catch (IOException | RuntimeException err) {
 			close();
 			throw new TransportException(err.getMessage(), err);
 		}
@@ -520,10 +522,8 @@
 		if (first) {
 			return false;
 		}
-		if (filterBlobLimit == 0) {
-			p.writeString(OPTION_FILTER + " blob:none"); //$NON-NLS-1$
-		} else if (filterBlobLimit > 0) {
-			p.writeString(OPTION_FILTER + " blob:limit=" + filterBlobLimit); //$NON-NLS-1$
+		if (!filterSpec.isNoOp()) {
+			p.writeString(filterSpec.filterLine());
 		}
 		p.end();
 		outNeedsEnd = false;
@@ -565,7 +565,7 @@
 					OPTION_MULTI_ACK_DETAILED));
 		}
 
-		if (filterBlobLimit >= 0 && !wantCapability(line, OPTION_FILTER)) {
+		if (!filterSpec.isNoOp() && !wantCapability(line, OPTION_FILTER)) {
 			throw new PackProtocolException(uri,
 					JGitText.get().filterRequiresCapability);
 		}
@@ -670,14 +670,14 @@
 				}
 			}
 
-			if (noDone & receivedReady) {
+			if (noDone && receivedReady) {
 				break SEND_HAVES;
 			}
 			if (statelessRPC) {
 				state.writeTo(out, null);
 			}
 
-			if (receivedContinue && havesSinceLastContinue > MAX_HAVES
+			if ((receivedContinue && havesSinceLastContinue > MAX_HAVES)
 					|| havesSent >= maxHaves) {
 				// Our history must be really different from the remote's.
 				// We just sent a whole slew of have lines, and it did not
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackPushConnection.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackPushConnection.java
index 847e901..35ea35e 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackPushConnection.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackPushConnection.java
@@ -383,8 +383,7 @@
 					JGitText.get().errorOccurredDuringUnpackingOnTheRemoteEnd, unpackStatus));
 		}
 
-		String refLine;
-		while ((refLine = pckIn.readString()) != PacketLineIn.END) {
+		for (String refLine : pckIn.readStrings()) {
 			boolean ok = false;
 			int refNameEnd = -1;
 			if (refLine.startsWith("ok ")) { //$NON-NLS-1$
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 6f17620..e402de0 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseReceivePack.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseReceivePack.java
@@ -314,6 +314,7 @@
 	protected BaseReceivePack(Repository into) {
 		db = into;
 		walk = new RevWalk(db);
+		walk.setRetainBody(false);
 
 		TransferConfig tc = db.getConfig().get(TransferConfig.KEY);
 		objectChecker = tc.newReceiveObjectChecker();
@@ -1240,7 +1241,7 @@
 			adv.advertiseCapability(CAPABILITY_PUSH_OPTIONS);
 		}
 		adv.advertiseCapability(OPTION_AGENT, UserAgent.get());
-		adv.send(getAdvertisedOrDefaultRefs());
+		adv.send(getAdvertisedOrDefaultRefs().values());
 		for (ObjectId obj : advertisedHaves)
 			adv.advertiseHave(obj);
 		if (adv.isEmpty())
@@ -1281,7 +1282,7 @@
 						return;
 					throw eof;
 				}
-				if (line == PacketLineIn.END) {
+				if (PacketLineIn.isEnd(line)) {
 					break;
 				}
 
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleFetchConnection.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleFetchConnection.java
index 84a0972..6cf7503 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleFetchConnection.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleFetchConnection.java
@@ -112,10 +112,7 @@
 		} catch (TransportException err) {
 			close();
 			throw err;
-		} catch (IOException err) {
-			close();
-			throw new TransportException(transport.uri, err.getMessage(), err);
-		} catch (RuntimeException err) {
+		} catch (IOException | RuntimeException err) {
 			close();
 			throw new TransportException(transport.uri, err.getMessage(), err);
 		}
@@ -205,10 +202,7 @@
 				packLock = parser.parse(NullProgressMonitor.INSTANCE);
 				ins.flush();
 			}
-		} catch (IOException err) {
-			close();
-			throw new TransportException(transport.uri, err.getMessage(), err);
-		} catch (RuntimeException err) {
+		} catch (IOException | RuntimeException err) {
 			close();
 			throw new TransportException(transport.uri, err.getMessage(), err);
 		}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleWriter.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleWriter.java
index 56aaede..d1db51e 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleWriter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleWriter.java
@@ -227,10 +227,11 @@
 				exc.add(r.getId());
 			packWriter.setIndexDisabled(true);
 			packWriter.setDeltaBaseAsOffset(true);
-			packWriter.setThin(exc.size() > 0);
+			packWriter.setThin(!exc.isEmpty());
 			packWriter.setReuseValidatingObjects(false);
-			if (exc.size() == 0)
+			if (exc.isEmpty()) {
 				packWriter.setTagTargets(tagTargets);
+			}
 			packWriter.preparePack(monitor, inc, exc);
 
 			final Writer w = new OutputStreamWriter(os, UTF_8);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/Daemon.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/Daemon.java
index 7289ce7..2b27df2 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/Daemon.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/Daemon.java
@@ -115,36 +115,26 @@
 
 		repositoryResolver = (RepositoryResolver<DaemonClient>) RepositoryResolver.NONE;
 
-		uploadPackFactory = new UploadPackFactory<DaemonClient>() {
-			@Override
-			public UploadPack create(DaemonClient req, Repository db)
-					throws ServiceNotEnabledException,
-					ServiceNotAuthorizedException {
-				UploadPack up = new UploadPack(db);
-				up.setTimeout(getTimeout());
-				up.setPackConfig(getPackConfig());
-				return up;
-			}
+		uploadPackFactory = (DaemonClient req, Repository db) -> {
+			UploadPack up = new UploadPack(db);
+			up.setTimeout(getTimeout());
+			up.setPackConfig(getPackConfig());
+			return up;
 		};
 
-		receivePackFactory = new ReceivePackFactory<DaemonClient>() {
-			@Override
-			public ReceivePack create(DaemonClient req, Repository db)
-					throws ServiceNotEnabledException,
-					ServiceNotAuthorizedException {
-				ReceivePack rp = new ReceivePack(db);
+		receivePackFactory = (DaemonClient req, Repository db) -> {
+			ReceivePack rp = new ReceivePack(db);
 
-				InetAddress peer = req.getRemoteAddress();
-				String host = peer.getCanonicalHostName();
-				if (host == null)
-					host = peer.getHostAddress();
-				String name = "anonymous"; //$NON-NLS-1$
-				String email = name + "@" + host; //$NON-NLS-1$
-				rp.setRefLogIdent(new PersonIdent(name, email));
-				rp.setTimeout(getTimeout());
+			InetAddress peer = req.getRemoteAddress();
+			String host = peer.getCanonicalHostName();
+			if (host == null)
+				host = peer.getHostAddress();
+			String name = "anonymous"; //$NON-NLS-1$
+			String email = name + "@" + host; //$NON-NLS-1$
+			rp.setRefLogIdent(new PersonIdent(name, email));
+			rp.setTimeout(getTimeout());
 
-				return rp;
-			}
+			return rp;
 		};
 
 		services = new DaemonService[] {
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 681ae12..e584440 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchProcess.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchProcess.java
@@ -200,6 +200,7 @@
 				.setAllowNonFastForwards(true)
 				.setRefLogMessage("fetch", true); //$NON-NLS-1$
 		try (RevWalk walk = new RevWalk(transport.local)) {
+			walk.setRetainBody(false);
 			if (monitor instanceof BatchingProgressMonitor) {
 				((BatchingProgressMonitor) monitor).setDelayStart(
 						250, TimeUnit.MILLISECONDS);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchRequest.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchRequest.java
index 40ba3a3..4dd7d6e 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchRequest.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchRequest.java
@@ -62,7 +62,7 @@
 
 	final Set<ObjectId> clientShallowCommits;
 
-	final long filterBlobLimit;
+	final FilterSpec filterSpec;
 
 	final Set<String> clientCapabilities;
 
@@ -82,8 +82,8 @@
 	 *            how deep to go in the tree
 	 * @param clientShallowCommits
 	 *            commits the client has without history
-	 * @param filterBlobLimit
-	 *            to exclude blobs on certain conditions
+	 * @param filterSpec
+	 *            the filter spec
 	 * @param clientCapabilities
 	 *            capabilities sent in the request
 	 * @param deepenNotRefs
@@ -96,13 +96,14 @@
 	 *            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<ObjectId> clientShallowCommits,
+			@NonNull FilterSpec filterSpec,
 			@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.filterSpec = requireNonNull(filterSpec);
 		this.clientCapabilities = requireNonNull(clientCapabilities);
 		this.deepenSince = deepenSince;
 		this.deepenNotRefs = requireNonNull(deepenNotRefs);
@@ -137,10 +138,11 @@
 	}
 
 	/**
-	 * @return the blob limit set in a "filter" line (-1 if not set)
+	 * @return the filter spec given in a "filter" line
 	 */
-	long getFilterBlobLimit() {
-		return filterBlobLimit;
+	@NonNull
+	FilterSpec getFilterSpec() {
+		return filterSpec;
 	}
 
 	/**
@@ -191,4 +193,4 @@
 	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
index 05f4a81..231ab9f 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchV0Request.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchV0Request.java
@@ -42,6 +42,8 @@
  */
 package org.eclipse.jgit.transport;
 
+import static java.util.Objects.requireNonNull;
+
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashSet;
@@ -57,10 +59,11 @@
 final class FetchV0Request extends FetchRequest {
 
 	FetchV0Request(@NonNull Set<ObjectId> wantIds, int depth,
-			@NonNull Set<ObjectId> clientShallowCommits, long filterBlobLimit,
+			@NonNull Set<ObjectId> clientShallowCommits,
+			@NonNull FilterSpec filterSpec,
 			@NonNull Set<String> clientCapabilities, @Nullable String agent) {
-		super(wantIds, depth, clientShallowCommits, filterBlobLimit,
-				clientCapabilities, 0, Collections.emptyList(), agent);
+		super(wantIds, depth, clientShallowCommits, filterSpec,
+				clientCapabilities, 0, Collections.emptyList(),	agent);
 	}
 
 	static final class Builder {
@@ -71,7 +74,7 @@
 
 		final Set<ObjectId> clientShallowCommits = new HashSet<>();
 
-		long filterBlobLimit = -1;
+		FilterSpec filterSpec = FilterSpec.NO_FILTER;
 
 		final Set<String> clientCaps = new HashSet<>();
 
@@ -129,18 +132,18 @@
 		}
 
 		/**
-		 * @param filterBlobLim
-		 *            blob limit set in a "filter" line
+		 * @param filter
+		 *            the filter set in a filter line
 		 * @return this builder
 		 */
-		Builder setFilterBlobLimit(long filterBlobLim) {
-			filterBlobLimit = filterBlobLim;
+		Builder setFilterSpec(@NonNull FilterSpec filter) {
+			filterSpec = requireNonNull(filter);
 			return this;
 		}
 
 		FetchV0Request build() {
 			return new FetchV0Request(wantIds, depth, clientShallowCommits,
-					filterBlobLimit, clientCaps, agent);
+					filterSpec, 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 ac6361c..6c24269 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchV2Request.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchV2Request.java
@@ -77,11 +77,12 @@
 			@NonNull Set<ObjectId> wantIds,
 			@NonNull Set<ObjectId> clientShallowCommits, int deepenSince,
 			@NonNull List<String> deepenNotRefs, int depth,
-			long filterBlobLimit,
+			@NonNull FilterSpec filterSpec,
 			boolean doneReceived, @NonNull Set<String> clientCapabilities,
 			@Nullable String agent, @NonNull List<String> serverOptions) {
-		super(wantIds, depth, clientShallowCommits, filterBlobLimit,
-				clientCapabilities, deepenSince, deepenNotRefs, agent);
+		super(wantIds, depth, clientShallowCommits, filterSpec,
+				clientCapabilities, deepenSince,
+				deepenNotRefs, agent);
 		this.peerHas = requireNonNull(peerHas);
 		this.wantedRefs = requireNonNull(wantedRefs);
 		this.doneReceived = doneReceived;
@@ -98,9 +99,11 @@
 
 	/**
 	 * @return list of references received in "want-ref" lines
+	 *
+	 * @since 5.4
 	 */
 	@NonNull
-	List<String> getWantedRefs() {
+	public List<String> getWantedRefs() {
 		return wantedRefs;
 	}
 
@@ -147,7 +150,7 @@
 
 		int deepenSince;
 
-		long filterBlobLimit = -1;
+		FilterSpec filterSpec = FilterSpec.NO_FILTER;
 
 		boolean doneReceived;
 
@@ -266,12 +269,12 @@
 		}
 
 		/**
-		 * @param filterBlobLim
-		 *            set in a "filter" line
+		 * @param filter
+		 *            spec set in a "filter" line
 		 * @return this builder
 		 */
-		Builder setFilterBlobLimit(long filterBlobLim) {
-			filterBlobLimit = filterBlobLim;
+		Builder setFilterSpec(@NonNull FilterSpec filter) {
+			filterSpec = requireNonNull(filter);
 			return this;
 		}
 
@@ -320,7 +323,7 @@
 		FetchV2Request build() {
 			return new FetchV2Request(peerHas, wantedRefs, wantIds,
 					clientShallowCommits, deepenSince, deepenNotRefs,
-					depth, filterBlobLimit, doneReceived, clientCapabilities,
+					depth, filterSpec, doneReceived, clientCapabilities,
 					agent, Collections.unmodifiableList(serverOptions));
 		}
 	}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/FilterSpec.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/FilterSpec.java
new file mode 100644
index 0000000..a663c9b
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/FilterSpec.java
@@ -0,0 +1,192 @@
+/*
+ * Copyright (C) 2019, 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.text.MessageFormat;
+
+import org.eclipse.jgit.annotations.Nullable;
+import org.eclipse.jgit.errors.PackProtocolException;
+import org.eclipse.jgit.internal.JGitText;
+
+/**
+ * Represents either a filter specified in a protocol "filter" line, or a
+ * placeholder to indicate no filtering.
+ *
+ * @since 5.4
+ */
+public final class FilterSpec {
+
+	private final long blobLimit;
+
+	private final long treeDepthLimit;
+
+	private FilterSpec(long blobLimit, long treeDepthLimit) {
+		this.blobLimit = blobLimit;
+		this.treeDepthLimit = treeDepthLimit;
+	}
+
+	/**
+	 * Process the content of "filter" line from the protocol. It has a shape
+	 * like:
+	 *
+	 * <ul>
+	 *   <li>"blob:none"
+	 *   <li>"blob:limit=N", with N &gt;= 0
+	 *   <li>"tree:DEPTH", with DEPTH &gt;= 0
+	 * </ul>
+	 *
+	 * @param filterLine
+	 *            the content of the "filter" line in the protocol
+	 * @return a FilterSpec representing the given filter
+	 * @throws PackProtocolException
+	 *             invalid filter because due to unrecognized format or
+	 *             negative/non-numeric filter.
+	 */
+	public static FilterSpec fromFilterLine(String filterLine)
+			throws PackProtocolException {
+		if (filterLine.equals("blob:none")) { //$NON-NLS-1$
+			return FilterSpec.withBlobLimit(0);
+		} else if (filterLine.startsWith("blob:limit=")) { //$NON-NLS-1$
+			long blobLimit = -1;
+			try {
+				blobLimit = Long
+						.parseLong(filterLine.substring("blob:limit=".length())); //$NON-NLS-1$
+			} catch (NumberFormatException e) {
+				// Do not change blobLimit so that we throw a
+				// PackProtocolException later.
+			}
+			if (blobLimit >= 0) {
+				return FilterSpec.withBlobLimit(blobLimit);
+			}
+		} else if (filterLine.startsWith("tree:")) { //$NON-NLS-1$
+			long treeDepthLimit = -1;
+			try {
+				treeDepthLimit = Long
+						.parseLong(filterLine.substring("tree:".length())); //$NON-NLS-1$
+			} catch (NumberFormatException e) {
+				// Do not change blobLimit so that we throw a
+				// PackProtocolException later.
+			}
+			if (treeDepthLimit >= 0) {
+				return FilterSpec.withTreeDepthLimit(treeDepthLimit);
+			}
+		}
+
+		// Did not match any known filter format.
+		throw new PackProtocolException(
+				MessageFormat.format(JGitText.get().invalidFilter, filterLine));
+	}
+
+	/**
+	 * @param blobLimit
+	 *            the blob limit in a "blob:[limit]" or "blob:none" filter line
+	 * @return a filter spec which filters blobs above a certain size
+	 */
+	static FilterSpec withBlobLimit(long blobLimit) {
+		if (blobLimit < 0) {
+			throw new IllegalArgumentException(
+					"blobLimit cannot be negative: " + blobLimit); //$NON-NLS-1$
+		}
+		return new FilterSpec(blobLimit, -1);
+	}
+
+	/**
+	 * @param treeDepthLimit
+	 *            the tree depth limit in a "tree:[depth]" filter line
+	 * @return a filter spec which filters blobs and trees beyond a certain tree
+	 *         depth
+	 */
+	static FilterSpec withTreeDepthLimit(long treeDepthLimit) {
+		if (treeDepthLimit < 0) {
+			throw new IllegalArgumentException(
+					"treeDepthLimit cannot be negative: " + treeDepthLimit); //$NON-NLS-1$
+		}
+		return new FilterSpec(-1, treeDepthLimit);
+	}
+
+	/**
+	 * A placeholder that indicates no filtering.
+	 */
+	public static final FilterSpec NO_FILTER = new FilterSpec(-1, -1);
+
+	/**
+	 * @return -1 if this filter does not filter blobs based on size, or a
+	 *         non-negative integer representing the max size of blobs to allow
+	 */
+	public long getBlobLimit() {
+		return blobLimit;
+	}
+
+	/**
+	 * @return -1 if this filter does not filter blobs and trees based on depth,
+	 *         or a non-negative integer representing the max tree depth of
+	 *         blobs and trees to fetch
+	 */
+	public long getTreeDepthLimit() {
+		return treeDepthLimit;
+	}
+
+	/**
+	 * @return true if this filter doesn't filter out anything
+	 */
+	public boolean isNoOp() {
+		return blobLimit == -1 && treeDepthLimit == -1;
+	}
+
+	/**
+	 * @return the filter line which describes this spec, e.g. "filter blob:limit=42"
+	 */
+	@Nullable
+	public String filterLine() {
+		if (blobLimit == 0) {
+			return GitProtocolConstants.OPTION_FILTER + " blob:none"; //$NON-NLS-1$
+		}
+
+		if (blobLimit > 0) {
+			return GitProtocolConstants.OPTION_FILTER + " blob:limit=" + blobLimit; //$NON-NLS-1$
+		}
+
+		return null;
+	}
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/HMACSHA1NonceGenerator.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/HMACSHA1NonceGenerator.java
index 6c26b70..01f6fec 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/HMACSHA1NonceGenerator.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/HMACSHA1NonceGenerator.java
@@ -45,14 +45,12 @@
 import static java.nio.charset.StandardCharsets.ISO_8859_1;
 import static java.nio.charset.StandardCharsets.UTF_8;
 
-import java.io.File;
 import java.security.InvalidKeyException;
 import java.security.NoSuchAlgorithmException;
 
 import javax.crypto.Mac;
 import javax.crypto.spec.SecretKeySpec;
 
-import org.eclipse.jgit.internal.storage.dfs.DfsRepository;
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.transport.PushCertificate.NonceStatus;
 
@@ -78,9 +76,7 @@
 			SecretKeySpec signingKey = new SecretKeySpec(keyBytes, "HmacSHA1"); //$NON-NLS-1$
 			mac = Mac.getInstance("HmacSHA1"); //$NON-NLS-1$
 			mac.init(signingKey);
-		} catch (InvalidKeyException e) {
-			throw new IllegalStateException(e);
-		} catch (NoSuchAlgorithmException e) {
+		} catch (InvalidKeyException | NoSuchAlgorithmException e) {
 			throw new IllegalStateException(e);
 		}
 	}
@@ -89,19 +85,7 @@
 	@Override
 	public synchronized String createNonce(Repository repo, long timestamp)
 			throws IllegalStateException {
-		String path;
-		if (repo instanceof DfsRepository) {
-			path = ((DfsRepository) repo).getDescription().getRepositoryName();
-		} else {
-			File directory = repo.getDirectory();
-			if (directory != null) {
-				path = directory.getPath();
-			} else {
-				throw new IllegalStateException();
-			}
-		}
-
-		String input = path + ":" + String.valueOf(timestamp); //$NON-NLS-1$
+		String input = repo.getIdentifier() + ":" + String.valueOf(timestamp); //$NON-NLS-1$
 		byte[] rawHmac = mac.doFinal(input.getBytes(UTF_8));
 		return Long.toString(timestamp) + "-" + toHex(rawHmac); //$NON-NLS-1$
 	}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/HttpConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/HttpConfig.java
index ce9e1b3..be7111d 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/HttpConfig.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/HttpConfig.java
@@ -88,6 +88,30 @@
 	/** git config key for the "sslVerify" setting. */
 	public static final String SSL_VERIFY_KEY = "sslVerify"; //$NON-NLS-1$
 
+	/**
+	 * git config key for the "cookieFile" setting.
+	 *
+	 * @since 5.4
+	 */
+	public static final String COOKIE_FILE_KEY = "cookieFile"; //$NON-NLS-1$
+
+	/**
+	 * git config key for the "saveCookies" setting.
+	 *
+	 * @since 5.4
+	 */
+	public static final String SAVE_COOKIES_KEY = "saveCookies"; //$NON-NLS-1$
+
+	/**
+	 * Custom JGit config key which holds the maximum number of cookie files to
+	 * keep in the cache.
+	 *
+	 * @since 5.4
+	 */
+	public static final String COOKIE_FILE_CACHE_LIMIT_KEY = "cookieFileCacheLimit"; //$NON-NLS-1$
+
+	private static final int DEFAULT_COOKIE_FILE_CACHE_LIMIT = 10;
+
 	private static final String MAX_REDIRECT_SYSTEM_PROPERTY = "http.maxRedirects"; //$NON-NLS-1$
 
 	private static final int DEFAULT_MAX_REDIRECTS = 5;
@@ -152,6 +176,12 @@
 
 	private int maxRedirects;
 
+	private String cookieFile;
+
+	private boolean saveCookies;
+
+	private int cookieFileCacheLimit;
+
 	/**
 	 * Get the "http.postBuffer" setting
 	 *
@@ -189,6 +219,40 @@
 	}
 
 	/**
+	 * Get the "http.cookieFile" setting
+	 *
+	 * @return the value of the "http.cookieFile" setting
+	 *
+	 * @since 5.4
+	 */
+	public String getCookieFile() {
+		return cookieFile;
+	}
+
+	/**
+	 * Get the "http.saveCookies" setting
+	 *
+	 * @return the value of the "http.saveCookies" setting
+	 *
+	 * @since 5.4
+	 */
+	public boolean getSaveCookies() {
+		return saveCookies;
+	}
+
+	/**
+	 * Get the "http.cookieFileCacheLimit" setting (gives the maximum number of
+	 * cookie files to keep in the LRU cache)
+	 *
+	 * @return the value of the "http.cookieFileCacheLimit" setting
+	 *
+	 * @since 5.4
+	 */
+	public int getCookieFileCacheLimit() {
+		return cookieFileCacheLimit;
+	}
+
+	/**
 	 * Creates a new {@link org.eclipse.jgit.transport.HttpConfig} tailored to
 	 * the given {@link org.eclipse.jgit.transport.URIish}.
 	 *
@@ -234,6 +298,10 @@
 		if (redirectLimit < 0) {
 			redirectLimit = MAX_REDIRECTS;
 		}
+		cookieFile = config.getString(HTTP, null, COOKIE_FILE_KEY);
+		saveCookies = config.getBoolean(HTTP, SAVE_COOKIES_KEY, false);
+		cookieFileCacheLimit = config.getInt(HTTP, COOKIE_FILE_CACHE_LIMIT_KEY,
+				DEFAULT_COOKIE_FILE_CACHE_LIMIT);
 		String match = findMatch(config.getSubsections(HTTP), uri);
 		if (match != null) {
 			// Override with more specific items
@@ -248,6 +316,13 @@
 			if (newMaxRedirects >= 0) {
 				redirectLimit = newMaxRedirects;
 			}
+			String urlSpecificCookieFile = config.getString(HTTP, match,
+					COOKIE_FILE_KEY);
+			if (urlSpecificCookieFile != null) {
+				cookieFile = urlSpecificCookieFile;
+			}
+			saveCookies = config.getBoolean(HTTP, match, SAVE_COOKIES_KEY,
+					saveCookies);
 		}
 		postBuffer = postBufferSize;
 		sslVerify = sslVerifyFlag;
@@ -319,8 +394,9 @@
 				// A longer path match is always preferred even over a user
 				// match. If the path matches are equal, a match with user wins
 				// over a match without user.
-				if (matchLength > bestMatchLength || !withUser && hasUser
-						&& matchLength >= 0 && matchLength == bestMatchLength) {
+				if (matchLength > bestMatchLength
+						|| (!withUser && hasUser && matchLength >= 0
+								&& matchLength == bestMatchLength)) {
 					bestMatch = s;
 					bestMatchLength = matchLength;
 					withUser = hasUser;
@@ -366,7 +442,7 @@
 		int uLength = uriPath.length();
 		int mLength = matchPath.length();
 		if (mLength == uLength || matchPath.charAt(mLength - 1) == '/'
-				|| mLength < uLength && uriPath.charAt(mLength) == '/') {
+				|| (mLength < uLength && uriPath.charAt(mLength) == '/')) {
 			return mLength;
 		}
 		return -1;
@@ -386,7 +462,7 @@
 			if (slash < 0) {
 				slash = length;
 			}
-			if (slash == i || slash == i + 1 && path.charAt(i) == '.') {
+			if (slash == i || (slash == i + 1 && path.charAt(i) == '.')) {
 				// Skip /. or also double slashes
 			} else if (slash == i + 2 && path.charAt(i) == '.'
 					&& path.charAt(i + 1) == '.') {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/InternalFetchConnection.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/InternalFetchConnection.java
index 9fb9062..dc3dcbc 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/InternalFetchConnection.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/InternalFetchConnection.java
@@ -107,16 +107,12 @@
 				try {
 					final UploadPack rp = uploadPackFactory.create(req, remote);
 					rp.upload(out_r, in_w, null);
-				} catch (ServiceNotEnabledException e) {
+				} catch (ServiceNotEnabledException
+						| ServiceNotAuthorizedException e) {
 					// Ignored. Client cannot use this repository.
-				} catch (ServiceNotAuthorizedException e) {
-					// Ignored. Client cannot use this repository.
-				} catch (IOException err) {
+				} catch (IOException | RuntimeException err) {
 					// Client side of the pipes should report the problem.
 					err.printStackTrace();
-				} catch (RuntimeException err) {
-					// Client side will notice we went away, and report.
-					err.printStackTrace();
 				} finally {
 					try {
 						out_r.close();
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 f05e0b8..9663de0 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/InternalPushConnection.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/InternalPushConnection.java
@@ -100,16 +100,14 @@
 				try {
 					final ReceivePack rp = receivePackFactory.create(req, remote);
 					rp.receive(out_r, in_w, System.err);
-				} catch (ServiceNotEnabledException e) {
-					// Ignored. Client cannot use this repository.
-				} catch (ServiceNotAuthorizedException e) {
+				} catch (ServiceNotEnabledException
+						| ServiceNotAuthorizedException e) {
 					// Ignored. Client cannot use this repository.
 				} 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.
+					// 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 {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PacketLineIn.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PacketLineIn.java
index c6e19d5..d73e193 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PacketLineIn.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PacketLineIn.java
@@ -49,7 +49,9 @@
 
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.UncheckedIOException;
 import java.text.MessageFormat;
+import java.util.Iterator;
 
 import org.eclipse.jgit.errors.PackProtocolException;
 import org.eclipse.jgit.internal.JGitText;
@@ -72,14 +74,25 @@
 public class PacketLineIn {
 	private static final Logger log = LoggerFactory.getLogger(PacketLineIn.class);
 
-	/** Magic return from {@link #readString()} when a flush packet is found. */
+	/**
+	 * Magic return from {@link #readString()} when a flush packet is found.
+	 *
+	 * @deprecated Callers should use {@link #isEnd(String)} to check if a
+	 *             string is the end marker, or
+	 *             {@link PacketLineIn#readStrings()} to iterate over all
+	 *             strings in the input stream until the marker is reached.
+	 */
+	@Deprecated
 	public static final String END = new StringBuilder(0).toString(); 	/* must not string pool */
 
 	/**
 	 * Magic return from {@link #readString()} when a delim packet is found.
 	 *
 	 * @since 5.0
+	 * @deprecated Callers should use {@link #isDelimiter(String)} to check if a
+	 *             string is the delimiter.
 	 */
+	@Deprecated
 	public static final String DELIM = new StringBuilder(0).toString(); 	/* must not string pool */
 
 	static enum AckNackResult {
@@ -193,6 +206,20 @@
 	}
 
 	/**
+	 * Get an iterator to read strings from the input stream.
+	 *
+	 * @return an iterator that calls {@link #readString()} until {@link #END}
+	 *         is encountered.
+	 *
+	 * @throws IOException
+	 *             on failure to read the initial packet line.
+	 * @since 5.4
+	 */
+	public PacketLineInIterator readStrings() throws IOException {
+		return new PacketLineInIterator(this);
+	}
+
+	/**
 	 * Read a single UTF-8 encoded string packet from the input stream.
 	 * <p>
 	 * Unlike {@link #readString()} a trailing LF will be retained.
@@ -224,6 +251,54 @@
 		return s;
 	}
 
+	/**
+	 * Check if a string is the delimiter marker.
+	 *
+	 * @param s
+	 *            the string to check
+	 * @return true if the given string is {@link #DELIM}, otherwise false.
+	 * @since 5.4
+	 */
+	@SuppressWarnings({ "ReferenceEquality", "StringEquality" })
+	public static boolean isDelimiter(String s) {
+		return s == DELIM;
+	}
+
+	/**
+	 * Get the delimiter marker.
+	 * <p>
+	 * Intended for use only in tests.
+	 *
+	 * @return The delimiter marker.
+	 */
+	static String delimiter() {
+		return DELIM;
+	}
+
+	/**
+	 * Get the end marker.
+	 * <p>
+	 * Intended for use only in tests.
+	 *
+	 * @return The end marker.
+	 */
+	static String end() {
+		return END;
+	}
+
+	/**
+	 * Check if a string is the packet end marker.
+	 *
+	 * @param s
+	 *            the string to check
+	 * @return true if the given string is {@link #END}, otherwise false.
+	 * @since 5.4
+	 */
+	@SuppressWarnings({ "ReferenceEquality", "StringEquality" })
+	public static boolean isEnd(String s) {
+		return s == END;
+	}
+
 	void discardUntilEnd() throws IOException {
 		for (;;) {
 			int n = readLength();
@@ -282,4 +357,46 @@
 	public static class InputOverLimitIOException extends IOException {
 		private static final long serialVersionUID = 1L;
 	}
+
+	/**
+	 * Iterator over packet lines.
+	 * <p>
+	 * Calls {@link #readString()} on the {@link PacketLineIn} until
+	 * {@link #END} is encountered.
+	 *
+	 * @since 5.4
+	 *
+	 */
+	public static class PacketLineInIterator implements Iterable<String> {
+		private PacketLineIn in;
+
+		private String current;
+
+		PacketLineInIterator(PacketLineIn in) throws IOException {
+			this.in = in;
+			current = in.readString();
+		}
+
+		@Override
+		public Iterator<String> iterator() {
+			return new Iterator<String>() {
+				@Override
+				public boolean hasNext() {
+					return !PacketLineIn.isEnd(current);
+				}
+
+				@Override
+				public String next() {
+					String next = current;
+					try {
+						current = in.readString();
+					} catch (IOException e) {
+						throw new UncheckedIOException(e);
+					}
+					return next;
+				}
+			};
+		}
+
+	}
 }
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 ba5d2f3..28a146d 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PostReceiveHook.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PostReceiveHook.java
@@ -63,12 +63,9 @@
  */
 public interface PostReceiveHook {
 	/** A simple no-op hook. */
-	PostReceiveHook NULL = new PostReceiveHook() {
-		@Override
-		public void onPostReceive(final ReceivePack rp,
-				final Collection<ReceiveCommand> commands) {
-			// Do nothing.
-		}
+	PostReceiveHook NULL = (final ReceivePack rp,
+			final Collection<ReceiveCommand> commands) -> {
+		// Do nothing.
 	};
 
 	/**
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 3aa3b12..251bfe2 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PostUploadHook.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PostUploadHook.java
@@ -57,11 +57,8 @@
  */
 public interface PostUploadHook {
 	/** A simple no-op hook. */
-	PostUploadHook NULL = new PostUploadHook() {
-		@Override
-		public void onPostUpload(PackStatistics stats) {
-			// Do nothing.
-		}
+	PostUploadHook NULL = (PackStatistics stats) -> {
+		// Do nothing.
 	};
 
 	/**
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 30845d3..b91756b 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PreReceiveHook.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PreReceiveHook.java
@@ -79,12 +79,9 @@
  */
 public interface PreReceiveHook {
 	/** A simple no-op hook. */
-	PreReceiveHook NULL = new PreReceiveHook() {
-		@Override
-		public void onPreReceive(final ReceivePack rp,
-				final Collection<ReceiveCommand> commands) {
-			// Do nothing.
-		}
+	PreReceiveHook NULL = (final ReceivePack rp,
+			final Collection<ReceiveCommand> commands) -> {
+		// Do nothing.
 	};
 
 	/**
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ProtocolV0Parser.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ProtocolV0Parser.java
index 21498d6..428a45c 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ProtocolV0Parser.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ProtocolV0Parser.java
@@ -99,7 +99,7 @@
 				throw eof;
 			}
 
-			if (line == PacketLineIn.END) {
+			if (PacketLineIn.isEnd(line)) {
 				break;
 			}
 
@@ -130,7 +130,7 @@
 				}
 				filterReceived = true;
 
-				reqBuilder.setFilterBlobLimit(ProtocolV2Parser.filterLine(arg));
+				reqBuilder.setFilterSpec(FilterSpec.fromFilterLine(arg));
 				continue;
 			}
 
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 8f4b86e..caba15f 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ProtocolV2Parser.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ProtocolV2Parser.java
@@ -92,7 +92,7 @@
 		String agentPrefix = OPTION_AGENT + '=';
 
 		String line = pckIn.readString();
-		while (line != PacketLineIn.DELIM && line != PacketLineIn.END) {
+		while (!PacketLineIn.isDelimiter(line) && !PacketLineIn.isEnd(line)) {
 			if (line.startsWith(serverOptionPrefix)) {
 				serverOptionConsumer
 						.accept(line.substring(serverOptionPrefix.length()));
@@ -133,40 +133,41 @@
 				serverOption -> reqBuilder.addServerOption(serverOption),
 				agent -> reqBuilder.setAgent(agent));
 
-		if (line == PacketLineIn.END) {
+		if (PacketLineIn.isEnd(line)) {
 			return reqBuilder.build();
 		}
 
-		if (line != PacketLineIn.DELIM) {
+		if (!PacketLineIn.isDelimiter(line)) {
 			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.addWantId(ObjectId.fromString(line.substring(5)));
+		for (String line2 : pckIn.readStrings()) {
+			if (line2.startsWith("want ")) { //$NON-NLS-1$
+				reqBuilder.addWantId(ObjectId.fromString(line2.substring(5)));
 			} else if (transferConfig.isAllowRefInWant()
-					&& line.startsWith(OPTION_WANT_REF + " ")) { //$NON-NLS-1$
-				reqBuilder.addWantedRef(line.substring(OPTION_WANT_REF.length() + 1));
-			} else if (line.startsWith("have ")) { //$NON-NLS-1$
-				reqBuilder.addPeerHas(ObjectId.fromString(line.substring(5)));
-			} else if (line.equals("done")) { //$NON-NLS-1$
+					&& line2.startsWith(OPTION_WANT_REF + " ")) { //$NON-NLS-1$
+				reqBuilder.addWantedRef(
+						line2.substring(OPTION_WANT_REF.length() + 1));
+			} else if (line2.startsWith("have ")) { //$NON-NLS-1$
+				reqBuilder.addPeerHas(ObjectId.fromString(line2.substring(5)));
+			} else if (line2.equals("done")) { //$NON-NLS-1$
 				reqBuilder.setDoneReceived();
-			} else if (line.equals(OPTION_THIN_PACK)) {
+			} else if (line2.equals(OPTION_THIN_PACK)) {
 				reqBuilder.addClientCapability(OPTION_THIN_PACK);
-			} else if (line.equals(OPTION_NO_PROGRESS)) {
+			} else if (line2.equals(OPTION_NO_PROGRESS)) {
 				reqBuilder.addClientCapability(OPTION_NO_PROGRESS);
-			} else if (line.equals(OPTION_INCLUDE_TAG)) {
+			} else if (line2.equals(OPTION_INCLUDE_TAG)) {
 				reqBuilder.addClientCapability(OPTION_INCLUDE_TAG);
-			} else if (line.equals(OPTION_OFS_DELTA)) {
+			} else if (line2.equals(OPTION_OFS_DELTA)) {
 				reqBuilder.addClientCapability(OPTION_OFS_DELTA);
-			} else if (line.startsWith("shallow ")) { //$NON-NLS-1$
+			} else if (line2.startsWith("shallow ")) { //$NON-NLS-1$
 				reqBuilder.addClientShallowCommit(
-						ObjectId.fromString(line.substring(8)));
-			} else if (line.startsWith("deepen ")) { //$NON-NLS-1$
-				int parsedDepth = Integer.parseInt(line.substring(7));
+						ObjectId.fromString(line2.substring(8)));
+			} else if (line2.startsWith("deepen ")) { //$NON-NLS-1$
+				int parsedDepth = Integer.parseInt(line2.substring(7));
 				if (parsedDepth <= 0) {
 					throw new PackProtocolException(
 							MessageFormat.format(JGitText.get().invalidDepth,
@@ -181,19 +182,19 @@
 							JGitText.get().deepenNotWithDeepen);
 				}
 				reqBuilder.setDepth(parsedDepth);
-			} else if (line.startsWith("deepen-not ")) { //$NON-NLS-1$
-				reqBuilder.addDeepenNotRef(line.substring(11));
+			} else if (line2.startsWith("deepen-not ")) { //$NON-NLS-1$
+				reqBuilder.addDeepenNotRef(line2.substring(11));
 				if (reqBuilder.getDepth() != 0) {
 					throw new PackProtocolException(
 							JGitText.get().deepenNotWithDeepen);
 				}
-			} else if (line.equals(OPTION_DEEPEN_RELATIVE)) {
+			} else if (line2.equals(OPTION_DEEPEN_RELATIVE)) {
 				reqBuilder.addClientCapability(OPTION_DEEPEN_RELATIVE);
-			} else if (line.startsWith("deepen-since ")) { //$NON-NLS-1$
-				int ts = Integer.parseInt(line.substring(13));
+			} else if (line2.startsWith("deepen-since ")) { //$NON-NLS-1$
+				int ts = Integer.parseInt(line2.substring(13));
 				if (ts <= 0) {
 					throw new PackProtocolException(MessageFormat
-							.format(JGitText.get().invalidTimestamp, line));
+							.format(JGitText.get().invalidTimestamp, line2));
 				}
 				if (reqBuilder.getDepth() != 0) {
 					throw new PackProtocolException(
@@ -201,17 +202,17 @@
 				}
 				reqBuilder.setDeepenSince(ts);
 			} else if (transferConfig.isAllowFilter()
-					&& line.startsWith(OPTION_FILTER + ' ')) {
+					&& line2.startsWith(OPTION_FILTER + ' ')) {
 				if (filterReceived) {
 					throw new PackProtocolException(
 							JGitText.get().tooManyFilters);
 				}
 				filterReceived = true;
-				reqBuilder.setFilterBlobLimit(filterLine(
-						line.substring(OPTION_FILTER.length() + 1)));
+				reqBuilder.setFilterSpec(FilterSpec.fromFilterLine(
+						line2.substring(OPTION_FILTER.length() + 1)));
 			} else {
 				throw new PackProtocolException(MessageFormat
-						.format(JGitText.get().unexpectedPacketLine, line));
+						.format(JGitText.get().unexpectedPacketLine, line2));
 			}
 		}
 
@@ -244,67 +245,29 @@
 				serverOption -> builder.addServerOption(serverOption),
 				agent -> builder.setAgent(agent));
 
-		if (line == PacketLineIn.END) {
+		if (PacketLineIn.isEnd(line)) {
 			return builder.build();
 		}
 
-		if (line != PacketLineIn.DELIM) {
+		if (!PacketLineIn.isDelimiter(line)) {
 			throw new PackProtocolException(MessageFormat
 					.format(JGitText.get().unexpectedPacketLine, line));
 		}
 
-		while ((line = pckIn.readString()) != PacketLineIn.END) {
-			if (line.equals("peel")) { //$NON-NLS-1$
+		for (String line2 : pckIn.readStrings()) {
+			if (line2.equals("peel")) { //$NON-NLS-1$
 				builder.setPeel(true);
-			} else if (line.equals("symrefs")) { //$NON-NLS-1$
+			} else if (line2.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 if (line2.startsWith("ref-prefix ")) { //$NON-NLS-1$
+				prefixes.add(line2.substring("ref-prefix ".length())); //$NON-NLS-1$
 			} else {
 				throw new PackProtocolException(MessageFormat
-						.format(JGitText.get().unexpectedPacketLine, line));
+						.format(JGitText.get().unexpectedPacketLine, line2));
 			}
 		}
 
 		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.
-	 *
-	 * @param blobLine
-	 *            the content of the "filter" line in the protocol
-	 * @return N, the limit, defaulting to 0 if "none"
-	 * @throws PackProtocolException
-	 *             invalid filter because due to unrecognized format or
-	 *             negative/non-numeric filter.
-	 */
-	static long filterLine(String blobLine) throws PackProtocolException {
-		long blobLimit = -1;
-
-		if (blobLine.equals("blob:none")) { //$NON-NLS-1$
-			blobLimit = 0;
-		} else if (blobLine.startsWith("blob:limit=")) { //$NON-NLS-1$
-			try {
-				blobLimit = Long
-						.parseLong(blobLine.substring("blob:limit=".length())); //$NON-NLS-1$
-			} catch (NumberFormatException e) {
-				throw new PackProtocolException(MessageFormat
-						.format(JGitText.get().invalidFilter, blobLine));
-			}
-		}
-		/*
-		 * We must have (1) either "blob:none" or "blob:limit=" set (because we
-		 * only support blob size limits for now), and (2) if the latter, then
-		 * it must be nonnegative. Throw if (1) or (2) is not met.
-		 */
-		if (blobLimit < 0) {
-			throw new PackProtocolException(
-					MessageFormat.format(JGitText.get().invalidFilter, blobLine));
-		}
-
-		return blobLimit;
-	}
-
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushCertificateStore.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushCertificateStore.java
index aeca635..14afc44 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushCertificateStore.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushCertificateStore.java
@@ -57,7 +57,6 @@
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
-import java.util.Comparator;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
@@ -193,81 +192,78 @@
 	 *         close resources.
 	 */
 	public Iterable<PushCertificate> getAll(String refName) {
-		return new Iterable<PushCertificate>() {
-			@Override
-			public Iterator<PushCertificate> iterator() {
-				return new Iterator<PushCertificate>() {
-					private final String path = pathName(refName);
-					private PushCertificate next;
+		return () -> new Iterator<PushCertificate>() {
+			private final String path = pathName(refName);
 
-					private RevWalk rw;
-					{
+			private PushCertificate next;
+
+			private RevWalk rw;
+			{
+				try {
+					if (reader == null) {
+						load();
+					}
+					if (commit != null) {
+						rw = new RevWalk(reader);
+						rw.setTreeFilter(AndTreeFilter.create(
+								PathFilterGroup.create(Collections
+										.singleton(PathFilter.create(path))),
+								TreeFilter.ANY_DIFF));
+						rw.setRewriteParents(false);
+						rw.markStart(rw.parseCommit(commit));
+					} else {
+						rw = null;
+					}
+				} catch (IOException e) {
+					throw new RuntimeException(e);
+				}
+			}
+
+			@Override
+			public boolean hasNext() {
+				try {
+					if (next == null) {
+						if (rw == null) {
+							return false;
+						}
 						try {
-							if (reader == null) {
-								load();
-							}
-							if (commit != null) {
-								rw = new RevWalk(reader);
-								rw.setTreeFilter(AndTreeFilter.create(
-										PathFilterGroup.create(
-											Collections.singleton(PathFilter.create(path))),
-										TreeFilter.ANY_DIFF));
-								rw.setRewriteParents(false);
-								rw.markStart(rw.parseCommit(commit));
+							RevCommit c = rw.next();
+							if (c != null) {
+								try (TreeWalk tw = TreeWalk.forPath(
+										rw.getObjectReader(), path,
+										c.getTree())) {
+									next = read(tw);
+								}
 							} else {
-								rw = null;
+								next = null;
 							}
 						} catch (IOException e) {
 							throw new RuntimeException(e);
 						}
 					}
-
-					@Override
-					public boolean hasNext() {
-						try {
-							if (next == null) {
-								if (rw == null) {
-									return false;
-								}
-								try {
-									RevCommit c = rw.next();
-									if (c != null) {
-										try (TreeWalk tw = TreeWalk.forPath(
-												rw.getObjectReader(), path, c.getTree())) {
-											next = read(tw);
-										}
-									} else {
-										next = null;
-									}
-								} catch (IOException e) {
-									throw new RuntimeException(e);
-								}
-							}
-							return next != null;
-						} finally {
-							if (next == null && rw != null) {
-								rw.close();
-								rw = null;
-							}
-						}
+					return next != null;
+				} finally {
+					if (next == null && rw != null) {
+						rw.close();
+						rw = null;
 					}
+				}
+			}
 
-					@Override
-					public PushCertificate next() {
-						hasNext();
-						PushCertificate n = next;
-						if (n == null) {
-							throw new NoSuchElementException();
-						}
-						next = null;
-						return n;
-					}
+			@Override
+			public PushCertificate next() {
+				hasNext();
+				PushCertificate n = next;
+				if (n == null) {
+					throw new NoSuchElementException();
+				}
+				next = null;
+				return n;
+			}
 
-					@Override
-					public void remove() {
-						throw new UnsupportedOperationException();
-					}
-				};
+			@Override
+			public void remove() {
+				throw new UnsupportedOperationException();
 			}
 		};
 	}
@@ -442,13 +438,8 @@
 	}
 
 	private static void sortPending(List<PendingCert> pending) {
-		Collections.sort(pending, new Comparator<PendingCert>() {
-			@Override
-			public int compare(PendingCert a, PendingCert b) {
-				return Long.signum(
-						a.ident.getWhen().getTime() - b.ident.getWhen().getTime());
-			}
-		});
+		Collections.sort(pending, (PendingCert a, PendingCert b) -> Long.signum(
+				a.ident.getWhen().getTime() - b.ident.getWhen().getTime()));
 	}
 
 	private DirCache newDirCache() throws IOException {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceiveCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceiveCommand.java
index d61aeb0..a9a995c 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceiveCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceiveCommand.java
@@ -429,7 +429,7 @@
 		this.newId = ObjectId.zeroId();
 		this.newSymref = newSymref;
 		this.name = name;
-		if (AnyObjectId.equals(ObjectId.zeroId(), oldId)) {
+		if (AnyObjectId.isEqual(ObjectId.zeroId(), oldId)) {
 			type = Type.CREATE;
 		} else if (newSymref != null) {
 			type = Type.UPDATE;
@@ -468,7 +468,7 @@
 		this.name = name;
 		if (oldSymref == null) {
 			type = Type.CREATE;
-		} else if (!AnyObjectId.equals(ObjectId.zeroId(), newId)) {
+		} else if (!AnyObjectId.isEqual(ObjectId.zeroId(), newId)) {
 			type = Type.UPDATE;
 		} else {
 			type = Type.DELETE;
@@ -750,7 +750,7 @@
 	public void updateType(RevWalk walk) throws IOException {
 		if (typeIsCorrect)
 			return;
-		if (type == Type.UPDATE && !AnyObjectId.equals(oldId, newId)) {
+		if (type == Type.UPDATE && !AnyObjectId.isEqual(oldId, newId)) {
 			RevObject o = walk.parseAny(oldId);
 			RevObject n = walk.parseAny(newId);
 			if (!(o instanceof RevCommit)
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 4652c3f..d6adf1e 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivePack.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivePack.java
@@ -348,7 +348,7 @@
 			pushOptions = new ArrayList<>(4);
 			for (;;) {
 				String option = in.readString();
-				if (option == PacketLineIn.END) {
+				if (PacketLineIn.isEnd(option)) {
 					break;
 				}
 				pushOptions.add(option);
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 d6d6198..d19c652 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefFilter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefFilter.java
@@ -61,12 +61,7 @@
 	/**
 	 * The default filter, allows all refs to be shown.
 	 */
-	RefFilter DEFAULT = new RefFilter() {
-		@Override
-		public Map<String, Ref> filter (Map<String, Ref> refs) {
-			return refs;
-		}
-	};
+	RefFilter DEFAULT = (Map<String, Ref> refs) -> refs;
 
 	/**
 	 * Filters a {@code Map} of refs before it is advertised to the client.
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefSpec.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefSpec.java
index afd3ada..16c6faf 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefSpec.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefSpec.java
@@ -49,6 +49,7 @@
 import org.eclipse.jgit.internal.JGitText;
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.util.References;
 
 /**
  * Describes how refs in one repository copy into another repository.
@@ -585,8 +586,9 @@
 	}
 
 	private static boolean eq(String a, String b) {
-		if (a == b)
+		if (References.isSameObject(a, b)) {
 			return true;
+		}
 		if (a == null || b == null)
 			return false;
 		return a.equals(b);
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 005a0c2..fdb19df 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/SshSessionFactory.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/SshSessionFactory.java
@@ -100,13 +100,9 @@
 	 * @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);
-			}
-		});
+		return AccessController
+				.doPrivileged((PrivilegedAction<String>) () -> SystemReader
+						.getInstance().getProperty(Constants.OS_USER_NAME_KEY));
 	}
 
 	/**
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TrackingRefUpdate.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TrackingRefUpdate.java
index ba2a673..550a9ef 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TrackingRefUpdate.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TrackingRefUpdate.java
@@ -175,7 +175,7 @@
 		private RefUpdate.Result decode(ReceiveCommand.Result status) {
 			switch (status) {
 			case OK:
-				if (AnyObjectId.equals(oldObjectId, newObjectId))
+				if (AnyObjectId.isEqual(oldObjectId, newObjectId))
 					return RefUpdate.Result.NO_CHANGE;
 				switch (getType()) {
 				case CREATE:
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/Transport.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/Transport.java
index 621c2ea..0b79070 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/Transport.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/Transport.java
@@ -47,6 +47,7 @@
 package org.eclipse.jgit.transport;
 
 import static java.nio.charset.StandardCharsets.UTF_8;
+import static java.util.Objects.requireNonNull;
 
 import java.io.BufferedReader;
 import java.io.IOException;
@@ -70,6 +71,7 @@
 import java.util.Vector;
 import java.util.concurrent.CopyOnWriteArrayList;
 
+import org.eclipse.jgit.annotations.NonNull;
 import org.eclipse.jgit.api.errors.AbortedByHookException;
 import org.eclipse.jgit.errors.NotSupportedException;
 import org.eclipse.jgit.errors.TransportException;
@@ -178,10 +180,7 @@
 				TransportProtocol proto;
 				try {
 					proto = (TransportProtocol) f.get(null);
-				} catch (IllegalArgumentException e) {
-					// If we cannot access the field, don't.
-					continue;
-				} catch (IllegalAccessException e) {
+				} catch (IllegalArgumentException | IllegalAccessException e) {
 					// If we cannot access the field, don't.
 					continue;
 				}
@@ -790,7 +789,7 @@
 	/** Should refs no longer on the source be pruned from the destination? */
 	private boolean removeDeletedRefs;
 
-	private long filterBlobLimit = -1;
+	private FilterSpec filterSpec = FilterSpec.NO_FILTER;
 
 	/** Timeout in seconds to wait before aborting an IO read or write. */
 	private int timeout;
@@ -1067,20 +1066,42 @@
 	}
 
 	/**
-	 * @return the last value passed to {@link #setFilterBlobLimit}, or -1 if
-	 *         it was never invoked.
+	 * @return the blob limit value set with {@link #setFilterBlobLimit} or
+	 *         {@link #setFilterSpec(FilterSpec)}, or -1 if no blob limit value
+	 *         was set
 	 * @since 5.0
+	 * @deprecated Use {@link #getFilterSpec()} instead
 	 */
-	public long getFilterBlobLimit() {
-		return filterBlobLimit;
+	@Deprecated
+	public final long getFilterBlobLimit() {
+		return filterSpec.getBlobLimit();
 	}
 
 	/**
 	 * @param bytes exclude blobs of size greater than this
 	 * @since 5.0
+	 * @deprecated Use {@link #setFilterSpec(FilterSpec)} instead
 	 */
-	public void setFilterBlobLimit(long bytes) {
-		filterBlobLimit = bytes;
+	@Deprecated
+	public final void setFilterBlobLimit(long bytes) {
+		setFilterSpec(FilterSpec.withBlobLimit(bytes));
+	}
+
+	/**
+	 * @return the last filter spec set with {@link #setFilterSpec(FilterSpec)},
+	 *         or {@link FilterSpec#NO_FILTER} if it was never invoked.
+	 * @since 5.4
+	 */
+	public final FilterSpec getFilterSpec() {
+		return filterSpec;
+	}
+
+	/**
+	 * @param filter a new filter to use for this transport
+	 * @since 5.4
+	 */
+	public final void setFilterSpec(@NonNull FilterSpec filter) {
+		filterSpec = requireNonNull(filter);
 	}
 
 	/**
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportHttp.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportHttp.java
index 0df1b70..a591dba 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportHttp.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportHttp.java
@@ -54,8 +54,11 @@
 import static org.eclipse.jgit.util.HttpSupport.HDR_ACCEPT_ENCODING;
 import static org.eclipse.jgit.util.HttpSupport.HDR_CONTENT_ENCODING;
 import static org.eclipse.jgit.util.HttpSupport.HDR_CONTENT_TYPE;
+import static org.eclipse.jgit.util.HttpSupport.HDR_COOKIE;
 import static org.eclipse.jgit.util.HttpSupport.HDR_LOCATION;
 import static org.eclipse.jgit.util.HttpSupport.HDR_PRAGMA;
+import static org.eclipse.jgit.util.HttpSupport.HDR_SET_COOKIE;
+import static org.eclipse.jgit.util.HttpSupport.HDR_SET_COOKIE2;
 import static org.eclipse.jgit.util.HttpSupport.HDR_USER_AGENT;
 import static org.eclipse.jgit.util.HttpSupport.HDR_WWW_AUTHENTICATE;
 import static org.eclipse.jgit.util.HttpSupport.METHOD_GET;
@@ -68,11 +71,15 @@
 import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.io.OutputStream;
+import java.net.HttpCookie;
 import java.net.MalformedURLException;
 import java.net.Proxy;
 import java.net.ProxySelector;
 import java.net.URISyntaxException;
 import java.net.URL;
+import java.nio.file.InvalidPathException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
 import java.security.cert.CertPathBuilderException;
 import java.security.cert.CertPathValidatorException;
 import java.security.cert.CertificateException;
@@ -84,6 +91,8 @@
 import java.util.EnumSet;
 import java.util.HashSet;
 import java.util.LinkedHashSet;
+import java.util.LinkedList;
+import java.util.List;
 import java.util.Locale;
 import java.util.Map;
 import java.util.Set;
@@ -100,6 +109,8 @@
 import org.eclipse.jgit.errors.TransportException;
 import org.eclipse.jgit.internal.JGitText;
 import org.eclipse.jgit.internal.storage.file.RefDirectory;
+import org.eclipse.jgit.internal.transport.http.NetscapeCookieFile;
+import org.eclipse.jgit.internal.transport.http.NetscapeCookieFileCache;
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.ObjectIdRef;
@@ -114,6 +125,7 @@
 import org.eclipse.jgit.util.HttpSupport;
 import org.eclipse.jgit.util.IO;
 import org.eclipse.jgit.util.RawParseUtils;
+import org.eclipse.jgit.util.StringUtils;
 import org.eclipse.jgit.util.SystemReader;
 import org.eclipse.jgit.util.TemporaryBuffer;
 import org.eclipse.jgit.util.io.DisabledOutputStream;
@@ -272,6 +284,19 @@
 
 	private boolean sslFailure = false;
 
+	/**
+	 * All stored cookies bound to this repo (independent of the baseUrl)
+	 */
+	private final NetscapeCookieFile cookieFile;
+
+	/**
+	 * The cookies to be sent with each request to the given {@link #baseUrl}.
+	 * Filtered view on top of {@link #cookieFile} where only cookies which
+	 * apply to the current url are left. This set needs to be filtered for
+	 * expired entries each time prior to sending them.
+	 */
+	private final Set<HttpCookie> relevantCookies;
+
 	TransportHttp(Repository local, URIish uri)
 			throws NotSupportedException {
 		super(local, uri);
@@ -279,6 +304,8 @@
 		http = new HttpConfig(local.getConfig(), uri);
 		proxySelector = ProxySelector.getDefault();
 		sslVerify = http.isSslVerify();
+		cookieFile = getCookieFileFromConfig(http);
+		relevantCookies = filterCookies(cookieFile, baseUrl);
 	}
 
 	private URL toURL(URIish urish) throws MalformedURLException {
@@ -319,6 +346,8 @@
 		http = new HttpConfig(uri);
 		proxySelector = ProxySelector.getDefault();
 		sslVerify = http.isSslVerify();
+		cookieFile = getCookieFileFromConfig(http);
+		relevantCookies = filterCookies(cookieFile, baseUrl);
 	}
 
 	/**
@@ -361,9 +390,7 @@
 			try (InputStream in = openInputStream(c)) {
 				return getConnection(c, in, service);
 			}
-		} catch (NotSupportedException err) {
-			throw err;
-		} catch (TransportException err) {
+		} catch (NotSupportedException | TransportException err) {
 			throw err;
 		} catch (IOException err) {
 			throw new TransportException(uri, JGitText.get().errorReadingInfoRefs, err);
@@ -447,9 +474,7 @@
 					throw new NotSupportedException(msg);
 				}
 			}
-		} catch (NotSupportedException err) {
-			throw err;
-		} catch (TransportException err) {
+		} catch (NotSupportedException | TransportException err) {
 			throw err;
 		} catch (IOException err) {
 			throw new TransportException(uri, JGitText.get().errorReadingInfoRefs, err);
@@ -510,6 +535,7 @@
 					conn.setRequestProperty(HDR_ACCEPT, "*/*"); //$NON-NLS-1$
 				}
 				final int status = HttpSupport.response(conn);
+				processResponseCookies(conn);
 				switch (status) {
 				case HttpConnection.HTTP_OK:
 					// Check if HttpConnection did some authentication in the
@@ -573,9 +599,7 @@
 					String err = status + " " + conn.getResponseMessage(); //$NON-NLS-1$
 					throw new TransportException(uri, err);
 				}
-			} catch (NotSupportedException e) {
-				throw e;
-			} catch (TransportException e) {
+			} catch (NotSupportedException | TransportException e) {
 				throw e;
 			} catch (SSLHandshakeException e) {
 				handleSslFailure(e);
@@ -600,6 +624,57 @@
 		}
 	}
 
+	void processResponseCookies(HttpConnection conn) {
+		if (cookieFile != null && http.getSaveCookies()) {
+			List<HttpCookie> foundCookies = new LinkedList<>();
+
+			List<String> cookieHeaderValues = conn
+					.getHeaderFields(HDR_SET_COOKIE);
+			if (!cookieHeaderValues.isEmpty()) {
+				foundCookies.addAll(
+						extractCookies(HDR_SET_COOKIE, cookieHeaderValues));
+			}
+			cookieHeaderValues = conn.getHeaderFields(HDR_SET_COOKIE2);
+			if (!cookieHeaderValues.isEmpty()) {
+				foundCookies.addAll(
+						extractCookies(HDR_SET_COOKIE2, cookieHeaderValues));
+			}
+			if (!foundCookies.isEmpty()) {
+				try {
+					// update cookie lists with the newly received cookies!
+					Set<HttpCookie> cookies = cookieFile.getCookies(false);
+					cookies.addAll(foundCookies);
+					cookieFile.write(baseUrl);
+					relevantCookies.addAll(foundCookies);
+				} catch (IOException | IllegalArgumentException
+						| InterruptedException e) {
+					LOG.warn(MessageFormat.format(
+							JGitText.get().couldNotPersistCookies,
+							cookieFile.getPath()), e);
+				}
+			}
+		}
+	}
+
+	private List<HttpCookie> extractCookies(String headerKey,
+			List<String> headerValues) {
+		List<HttpCookie> foundCookies = new LinkedList<>();
+		for (String headerValue : headerValues) {
+			foundCookies
+					.addAll(HttpCookie.parse(headerKey + ':' + headerValue));
+		}
+		// HttpCookies.parse(...) is only compliant with RFC 2965. Make it RFC
+		// 6265 compliant by applying the logic from
+		// https://tools.ietf.org/html/rfc6265#section-5.2.3
+		for (HttpCookie foundCookie : foundCookies) {
+			String domain = foundCookie.getDomain();
+			if (domain != null && domain.startsWith(".")) { //$NON-NLS-1$
+				foundCookie.setDomain(domain.substring(1));
+			}
+		}
+		return foundCookies;
+	}
+
 	private static class CredentialItems {
 		CredentialItem.InformationalMessage message;
 
@@ -775,7 +850,7 @@
 		}
 		// git allows only rewriting the root, i.e., everything before INFO_REFS
 		// or the service name
-		if (next.indexOf(checkFor) < 0) {
+		if (!next.contains(checkFor)) {
 			return false;
 		}
 		// Basically we should test here that whatever follows INFO_REFS is
@@ -849,14 +924,35 @@
 			conn.setConnectTimeout(effTimeOut);
 			conn.setReadTimeout(effTimeOut);
 		}
+		// set cookie header if necessary
+		if (!relevantCookies.isEmpty()) {
+			setCookieHeader(conn);
+		}
+
 		if (this.headers != null && !this.headers.isEmpty()) {
-			for (Map.Entry<String, String> entry : this.headers.entrySet())
+			for (Map.Entry<String, String> entry : this.headers.entrySet()) {
 				conn.setRequestProperty(entry.getKey(), entry.getValue());
+			}
 		}
 		authMethod.configureRequest(conn);
 		return conn;
 	}
 
+	private void setCookieHeader(HttpConnection conn) {
+		StringBuilder cookieHeaderValue = new StringBuilder();
+		for (HttpCookie cookie : relevantCookies) {
+			if (!cookie.hasExpired()) {
+				if (cookieHeaderValue.length() > 0) {
+					cookieHeaderValue.append(';');
+				}
+				cookieHeaderValue.append(cookie.toString());
+			}
+		}
+		if (cookieHeaderValue.length() > 0) {
+			conn.setRequestProperty(HDR_COOKIE, cookieHeaderValue.toString());
+		}
+	}
+
 	final InputStream openInputStream(HttpConnection conn)
 			throws IOException {
 		InputStream input = conn.getInputStream();
@@ -870,6 +966,150 @@
 		return new TransportException(uri, why);
 	}
 
+	private static NetscapeCookieFile getCookieFileFromConfig(
+			HttpConfig config) {
+		if (!StringUtils.isEmptyOrNull(config.getCookieFile())) {
+			try {
+				Path cookieFilePath = Paths.get(config.getCookieFile());
+				return NetscapeCookieFileCache.getInstance(config)
+						.getEntry(cookieFilePath);
+			} catch (InvalidPathException e) {
+				LOG.warn(MessageFormat.format(
+						JGitText.get().couldNotReadCookieFile,
+						config.getCookieFile()), e);
+			}
+		}
+		return null;
+	}
+
+	private static Set<HttpCookie> filterCookies(NetscapeCookieFile cookieFile,
+			URL url) {
+		if (cookieFile != null) {
+			return filterCookies(cookieFile.getCookies(true), url);
+		}
+		return Collections.emptySet();
+	}
+
+	/**
+	 *
+	 * @param allCookies
+	 *            a list of cookies.
+	 * @param url
+	 *            the url for which to filter the list of cookies.
+	 * @return only the cookies from {@code allCookies} which are relevant (i.e.
+	 *         are not expired, have a matching domain, have a matching path and
+	 *         have a matching secure attribute)
+	 */
+	private static Set<HttpCookie> filterCookies(Set<HttpCookie> allCookies,
+			URL url) {
+		Set<HttpCookie> filteredCookies = new HashSet<>();
+		for (HttpCookie cookie : allCookies) {
+			if (cookie.hasExpired()) {
+				continue;
+			}
+			if (!matchesCookieDomain(url.getHost(), cookie.getDomain())) {
+				continue;
+			}
+			if (!matchesCookiePath(url.getPath(), cookie.getPath())) {
+				continue;
+			}
+			if (cookie.getSecure() && !"https".equals(url.getProtocol())) { //$NON-NLS-1$
+				continue;
+			}
+			filteredCookies.add(cookie);
+		}
+		return filteredCookies;
+	}
+
+	/**
+	 *
+	 * The utility method to check whether a host name is in a cookie's domain
+	 * or not. Similar to {@link HttpCookie#domainMatches(String, String)} but
+	 * implements domain matching rules according to
+	 * <a href="https://tools.ietf.org/html/rfc6265#section-5.1.3">RFC 6265,
+	 * section 5.1.3</a> instead of the rules from
+	 * <a href="https://tools.ietf.org/html/rfc2965#section-3.3">RFC 2965,
+	 * section 3.3.1</a>.
+	 * <p>
+	 * The former rules are also used by libcurl internally.
+	 * <p>
+	 * The rules are as follows
+	 *
+	 * A string matches another domain string if at least one of the following
+	 * conditions holds:
+	 * <ul>
+	 * <li>The domain string and the string are identical. (Note that both the
+	 * domain string and the string will have been canonicalized to lower case
+	 * at this point.)</li>
+	 * <li>All of the following conditions hold
+	 * <ul>
+	 * <li>The domain string is a suffix of the string.</li>
+	 * <li>The last character of the string that is not included in the domain
+	 * string is a %x2E (".") character.</li>
+	 * <li>The string is a host name (i.e., not an IP address).</li>
+	 * </ul>
+	 * </li>
+	 * </ul>
+	 *
+	 * @param host
+	 *            the host to compare against the cookieDomain
+	 * @param cookieDomain
+	 *            the domain to compare against
+	 * @return {@code true} if they domain-match; {@code false} if not
+	 *
+	 * @see <a href= "https://tools.ietf.org/html/rfc6265#section-5.1.3">RFC
+	 *      6265, section 5.1.3 (Domain Matching)</a>
+	 * @see <a href=
+	 *      "https://bugs.java.com/bugdatabase/view_bug.do?bug_id=8206092">JDK-8206092
+	 *      : HttpCookie.domainMatches() does not match to sub-sub-domain</a>
+	 */
+	static boolean matchesCookieDomain(String host, String cookieDomain) {
+		cookieDomain = cookieDomain.toLowerCase(Locale.ROOT);
+		host = host.toLowerCase(Locale.ROOT);
+		if (host.equals(cookieDomain)) {
+			return true;
+		} else {
+			if (!host.endsWith(cookieDomain)) {
+				return false;
+			}
+			return host
+					.charAt(host.length() - cookieDomain.length() - 1) == '.';
+		}
+	}
+
+	/**
+	 * The utility method to check whether a path is matching a cookie path
+	 * domain or not. The rules are defined by
+	 * <a href="https://tools.ietf.org/html/rfc6265#section-5.1.4">RFC 6265,
+	 * section 5.1.4</a>:
+	 *
+	 * A request-path path-matches a given cookie-path if at least one of the
+	 * following conditions holds:
+	 * <ul>
+	 * <li>The cookie-path and the request-path are identical.</li>
+	 * <li>The cookie-path is a prefix of the request-path, and the last
+	 * character of the cookie-path is %x2F ("/").</li>
+	 * <li>The cookie-path is a prefix of the request-path, and the first
+	 * character of the request-path that is not included in the cookie- path is
+	 * a %x2F ("/") character.</li>
+	 * </ul>
+	 * @param path
+	 *            the path to check
+	 * @param cookiePath
+	 *            the cookie's path
+	 *
+	 * @return {@code true} if they path-match; {@code false} if not
+	 */
+	static boolean matchesCookiePath(String path, String cookiePath) {
+		if (cookiePath.equals(path)) {
+			return true;
+		}
+		if (!cookiePath.endsWith("/")) { //$NON-NLS-1$
+			cookiePath += "/"; //$NON-NLS-1$
+		}
+		return path.startsWith(cookiePath);
+	}
+
 	private boolean isSmartHttp(HttpConnection c, String service) {
 		final String expType = "application/x-" + service + "-advertisement"; //$NON-NLS-1$ //$NON-NLS-2$
 		final String actType = c.getContentType();
@@ -904,7 +1144,7 @@
 					JGitText.get().expectedGot, exp, act));
 		}
 
-		while (pckIn.readString() != PacketLineIn.END) {
+		while (!PacketLineIn.isEnd(pckIn.readString())) {
 			// for now, ignore the remaining header lines
 		}
 	}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportLocal.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportLocal.java
index fbb2c44..f2a2b0a 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportLocal.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportLocal.java
@@ -188,12 +188,8 @@
 				&& !"git upload-pack".equals(up)) //$NON-NLS-1$
 			return new ForkLocalFetchConnection();
 
-		UploadPackFactory<Void> upf = new UploadPackFactory<Void>() {
-			@Override
-			public UploadPack create(Void req, Repository db) {
-				return createUploadPack(db);
-			}
-		};
+		UploadPackFactory<Void> upf = (Void req,
+				Repository db) -> createUploadPack(db);
 		return new InternalFetchConnection<>(this, upf, null, openRepo());
 	}
 
@@ -205,12 +201,8 @@
 				&& !"git receive-pack".equals(rp)) //$NON-NLS-1$
 			return new ForkLocalPushConnection();
 
-		ReceivePackFactory<Void> rpf = new ReceivePackFactory<Void>() {
-			@Override
-			public ReceivePack create(Void req, Repository db) {
-				return createReceivePack(db);
-			}
-		};
+		ReceivePackFactory<Void> rpf = (Void req,
+				Repository db) -> createReceivePack(db);
 		return new InternalPushConnection<>(this, rpf, null, openRepo());
 	}
 
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/URIish.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/URIish.java
index 70fb1f0..7ca9cc1 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/URIish.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/URIish.java
@@ -62,6 +62,7 @@
 import org.eclipse.jgit.internal.JGitText;
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.util.RawParseUtils;
+import org.eclipse.jgit.util.References;
 import org.eclipse.jgit.util.StringUtils;
 
 /**
@@ -362,8 +363,8 @@
 		if (p.length() >= 3
 				&& p.charAt(0) == '/'
 				&& p.charAt(2) == ':'
-				&& (p.charAt(1) >= 'A' && p.charAt(1) <= 'Z' || p.charAt(1) >= 'a'
-						&& p.charAt(1) <= 'z'))
+				&& ((p.charAt(1) >= 'A' && p.charAt(1) <= 'Z')
+						|| (p.charAt(1) >= 'a' && p.charAt(1) <= 'z')))
 			return p.substring(1);
 		else if (s != null && p.length() >= 2 && p.charAt(0) == '/'
 				&& p.charAt(1) == '~')
@@ -624,8 +625,9 @@
 	}
 
 	private static boolean eq(String a, String b) {
-		if (a == b)
+		if (References.isSameObject(a, b)) {
 			return true;
+		}
 		if (StringUtils.isEmptyOrNull(a) && StringUtils.isEmptyOrNull(b))
 			return true;
 		if (a == null || b == 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 1d0f836..9278f42 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java
@@ -44,13 +44,11 @@
 package org.eclipse.jgit.transport;
 
 import static java.util.Collections.unmodifiableMap;
-import static java.util.function.Function.identity;
-import static java.util.stream.Collectors.toMap;
 import static org.eclipse.jgit.lib.Constants.R_TAGS;
 import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_REF_IN_WANT;
+import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_SERVER_OPTION;
 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;
@@ -66,6 +64,7 @@
 import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_SIDE_BAND;
 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.util.RefMap.toRefMap;
 
 import java.io.ByteArrayOutputStream;
 import java.io.EOFException;
@@ -81,8 +80,10 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.Optional;
 import java.util.Set;
 import java.util.TreeMap;
+import java.util.stream.Collectors;
 
 import org.eclipse.jgit.annotations.NonNull;
 import org.eclipse.jgit.annotations.Nullable;
@@ -107,6 +108,7 @@
 import org.eclipse.jgit.revwalk.BitmapWalker;
 import org.eclipse.jgit.revwalk.DepthWalk;
 import org.eclipse.jgit.revwalk.ObjectWalk;
+import org.eclipse.jgit.revwalk.ReachabilityChecker;
 import org.eclipse.jgit.revwalk.RevCommit;
 import org.eclipse.jgit.revwalk.RevFlag;
 import org.eclipse.jgit.revwalk.RevFlagSet;
@@ -817,7 +819,7 @@
 			// Fall back to all refs.
 			setAdvertisedRefs(
 					db.getRefDatabase().getRefs().stream()
-						.collect(toMap(Ref::getName, identity())));
+							.collect(toRefMap((a, b) -> b)));
 		}
 		return refs;
 	}
@@ -836,7 +838,7 @@
 			String[] prefixes = refPrefixes.toArray(new String[0]);
 			Map<String, Ref> rs =
 					db.getRefDatabase().getRefsByPrefix(prefixes).stream()
-						.collect(toMap(Ref::getName, identity(), (a, b) -> b));
+							.collect(toRefMap((a, b) -> b));
 			if (refFilter != RefFilter.DEFAULT) {
 				return refFilter.filter(rs);
 			}
@@ -848,7 +850,7 @@
 		return refs.values().stream()
 				.filter(ref -> refPrefixes.stream()
 						.anyMatch(ref.getName()::startsWith))
-				.collect(toMap(Ref::getName, identity()));
+				.collect(toRefMap((a, b) -> b));
 	}
 
 	/**
@@ -871,7 +873,7 @@
 				names.stream()
 					.map(refs::get)
 					.filter(Objects::nonNull)
-					.collect(toMap(Ref::getName, identity(), (a, b) -> b)));
+						.collect(toRefMap((a, b) -> b)));
 	}
 
 	/**
@@ -1066,7 +1068,7 @@
 			findSymrefs(adv, refsToSend);
 		}
 
-		adv.send(refsToSend);
+		adv.send(refsToSend.values());
 		adv.end();
 	}
 
@@ -1227,7 +1229,7 @@
 			/* EOF when awaiting command is fine */
 			return true;
 		}
-		if (command == PacketLineIn.END) {
+		if (PacketLineIn.isEnd(command)) {
 			// A blank request is valid according
 			// to the protocol; do nothing in this
 			// case.
@@ -1477,7 +1479,8 @@
 		}
 		adv.setDerefTags(true);
 		findSymrefs(adv, advertisedOrDefaultRefs);
-		advertised = adv.send(advertisedOrDefaultRefs);
+		advertised = adv.send(advertisedOrDefaultRefs.values());
+
 		if (adv.isEmpty())
 			adv.advertiseId(ObjectId.zeroId(), "capabilities^{}"); //$NON-NLS-1$
 		adv.end();
@@ -1526,17 +1529,30 @@
 	}
 
 	/**
-	 * Returns the filter blob limit for the current request. Valid only after
-	 * calling recvWants(). A limit -1 means no limit.
+	 * Deprecated synonym for {@code getFilterSpec().getBlobLimit()}.
 	 *
 	 * @return filter blob limit requested by the client, or -1 if no limit
 	 * @since 5.3
+	 * @deprecated Use {@link #getFilterSpec()} instead
 	 */
-	public long getFilterBlobLimit() {
+	@Deprecated
+	public final long getFilterBlobLimit() {
+		return getFilterSpec().getBlobLimit();
+	}
+
+	/**
+	 * Returns the filter spec for the current request. Valid only after
+	 * calling recvWants(). This may be a no-op filter spec, but it won't be
+	 * null.
+	 *
+	 * @return filter requested by the client
+	 * @since 5.4
+	 */
+	public final FilterSpec getFilterSpec() {
 		if (currentRequest == null) {
 			throw new RequestNotYetReadException();
 		}
-		return currentRequest.getFilterBlobLimit();
+		return currentRequest.getFilterSpec();
 	}
 
 	/**
@@ -1584,7 +1600,7 @@
 				throw eof;
 			}
 
-			if (line == PacketLineIn.END) {
+			if (PacketLineIn.isEnd(line)) {
 				last = processHaveLines(peerHas, last, pckOut);
 				if (commonBase.isEmpty() || multiAck != MultiAck.OFF)
 					pckOut.writeString("NAK\n"); //$NON-NLS-1$
@@ -1854,58 +1870,88 @@
 
 	private static void checkNotAdvertisedWants(UploadPack up,
 			List<ObjectId> notAdvertisedWants, Set<ObjectId> reachableFrom)
-			throws MissingObjectException, IncorrectObjectTypeException, IOException {
-		// Walk the requested commits back to the provided set of commits. If any
-		// commit exists, a branch was deleted or rewound and the repository owner
-		// no longer exports that requested item. If the requested commit is merged
-		// into an advertised branch it will be marked UNINTERESTING and no commits
-		// return.
+			throws IOException {
 
 		ObjectReader reader = up.getRevWalk().getObjectReader();
 		try (RevWalk walk = new RevWalk(reader)) {
-			AsyncRevObjectQueue q = walk.parseAny(notAdvertisedWants, true);
-			try {
-				RevObject obj;
-				while ((obj = q.next()) != null) {
-					if (!(obj instanceof RevCommit)) {
-						// If unadvertized non-commits are requested, use
-						// bitmaps. If there are no bitmaps, instead of
-						// incurring the expense of a manual walk, reject
-						// the request.
-						BitmapIndex bitmapIndex = reader.getBitmapIndex();
-						if (bitmapIndex != null) {
-							checkNotAdvertisedWantsUsingBitmap(
-									reader,
-									bitmapIndex,
-									notAdvertisedWants,
-									reachableFrom);
-							return;
-						}
-						throw new WantNotValidException(obj);
-					}
-					walk.markStart((RevCommit) obj);
+			walk.setRetainBody(false);
+			// Missing "wants" throw exception here
+			List<RevObject> wantsAsObjs = objectIdsToRevObjects(walk,
+					notAdvertisedWants);
+			List<RevCommit> wantsAsCommits = wantsAsObjs.stream()
+					.filter(obj -> obj instanceof RevCommit)
+					.map(obj -> (RevCommit) obj)
+					.collect(Collectors.toList());
+			boolean allWantsAreCommits = wantsAsObjs.size() == wantsAsCommits
+					.size();
+			boolean repoHasBitmaps = reader.getBitmapIndex() != null;
+
+			if (!allWantsAreCommits) {
+				if (!repoHasBitmaps) {
+					// If unadvertized non-commits are requested, use
+					// bitmaps. If there are no bitmaps, instead of
+					// incurring the expense of a manual walk, reject
+					// the request.
+					RevObject nonCommit = wantsAsObjs
+							.stream()
+							.filter(obj -> !(obj instanceof RevCommit))
+							.limit(1)
+							.collect(Collectors.toList()).get(0);
+					throw new WantNotValidException(nonCommit);
+
 				}
-			} catch (MissingObjectException notFound) {
-				throw new WantNotValidException(notFound.getObjectId(),
-						notFound);
-			} finally {
-				q.release();
-			}
-			for (ObjectId id : reachableFrom) {
-				try {
-					walk.markUninteresting(walk.parseCommit(id));
-				} catch (IncorrectObjectTypeException notCommit) {
-					continue;
-				}
+				checkNotAdvertisedWantsUsingBitmap(reader,
+						reader.getBitmapIndex(), notAdvertisedWants,
+						reachableFrom);
+				return;
 			}
 
-			RevCommit bad = walk.next();
-			if (bad != null) {
-				throw new WantNotValidException(bad);
+			// All wants are commits, we can use ReachabilityChecker
+			ReachabilityChecker reachabilityChecker = walk
+					.createReachabilityChecker();
+
+			List<RevCommit> starters = objectIdsToRevCommits(walk,
+					reachableFrom);
+			Optional<RevCommit> unreachable = reachabilityChecker
+					.areAllReachable(wantsAsCommits, starters);
+			if (unreachable.isPresent()) {
+				throw new WantNotValidException(unreachable.get());
 			}
+
+		} catch (MissingObjectException notFound) {
+			throw new WantNotValidException(notFound.getObjectId(), notFound);
 		}
 	}
 
+	// Resolve the ObjectIds into RevObjects. Any missing object raises an
+	// exception
+	private static List<RevObject> objectIdsToRevObjects(RevWalk walk,
+			Iterable<ObjectId> objectIds)
+			throws MissingObjectException, IOException {
+		List<RevObject> result = new ArrayList<>();
+		for (ObjectId objectId : objectIds) {
+			result.add(walk.parseAny(objectId));
+		}
+		return result;
+	}
+
+	// Get commits from object ids. If the id is not a commit, ignore it. If the
+	// id doesn't exist, report the missing object in a exception.
+	private static List<RevCommit> objectIdsToRevCommits(RevWalk walk,
+			Iterable<ObjectId> objectIds)
+			throws MissingObjectException, IOException {
+		List<RevCommit> result = new ArrayList<>();
+		for (ObjectId objectId : objectIds) {
+			try {
+				result.add(walk.parseCommit(objectId));
+			} catch (IncorrectObjectTypeException e) {
+				continue;
+			}
+		}
+		return result;
+	}
+
+
 	private void addCommonBase(RevObject o) {
 		if (!o.has(COMMON)) {
 			o.add(COMMON);
@@ -1988,21 +2034,12 @@
 			} catch (ServiceMayNotContinueException noPack) {
 				// This was already reported on (below).
 				throw noPack;
-			} catch (IOException err) {
-				if (reportInternalServerErrorOverSideband())
+			} catch (IOException | RuntimeException | Error err) {
+				if (reportInternalServerErrorOverSideband()) {
 					throw new UploadPackInternalServerErrorException(err);
-				else
+				} else {
 					throw err;
-			} catch (RuntimeException err) {
-				if (reportInternalServerErrorOverSideband())
-					throw new UploadPackInternalServerErrorException(err);
-				else
-					throw err;
-			} catch (Error err) {
-				if (reportInternalServerErrorOverSideband())
-					throw new UploadPackInternalServerErrorException(err);
-				else
-					throw err;
+				}
 			}
 		} else {
 			sendPack(false, req, accumulator, allTags, unshallowCommits, deepenNots);
@@ -2097,15 +2134,16 @@
 				accumulator);
 		try {
 			pw.setIndexDisabled(true);
-			if (req.getFilterBlobLimit() >= 0) {
-				pw.setFilterBlobLimit(req.getFilterBlobLimit());
-				pw.setUseCachedPacks(false);
-			} else {
+			if (req.getFilterSpec().isNoOp()) {
 				pw.setUseCachedPacks(true);
+			} else {
+				pw.setFilterSpec(req.getFilterSpec());
+				pw.setUseCachedPacks(false);
 			}
 			pw.setUseBitmaps(
 					req.getDepth() == 0
-							&& req.getClientShallowCommits().isEmpty());
+							&& req.getClientShallowCommits().isEmpty()
+							&& req.getFilterSpec().getTreeDepthLimit() == -1);
 			pw.setClientShallowCommits(req.getClientShallowCommits());
 			pw.setReuseDeltaCommits(true);
 			pw.setDeltaBaseAsOffset(
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkFetchConnection.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkFetchConnection.java
index 2bb5814..b289e42 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkFetchConnection.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkFetchConnection.java
@@ -657,7 +657,7 @@
 		}
 
 		ObjectId act = inserter.insert(type, raw);
-		if (!AnyObjectId.equals(id, act)) {
+		if (!AnyObjectId.isEqual(id, act)) {
 			throw new TransportException(MessageFormat.format(
 					JGitText.get().incorrectHashFor, id.name(), act.name(),
 					Constants.typeString(type),
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkPushConnection.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkPushConnection.java
index 4c75425..d9103f8 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkPushConnection.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkPushConnection.java
@@ -165,7 +165,7 @@
 				continue;
 			}
 
-			if (AnyObjectId.equals(ObjectId.zeroId(), u.getNewObjectId()))
+			if (AnyObjectId.isEqual(ObjectId.zeroId(), u.getNewObjectId()))
 				deleteCommand(u);
 			else
 				updates.add(u);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/resolver/FileResolver.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/resolver/FileResolver.java
index 60acd2f..b4a7af0 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/resolver/FileResolver.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/resolver/FileResolver.java
@@ -127,11 +127,7 @@
 				} else
 					throw new ServiceNotEnabledException();
 
-			} catch (RuntimeException e) {
-				db.close();
-				throw new RepositoryNotFoundException(name, e);
-
-			} catch (IOException e) {
+			} catch (RuntimeException | IOException e) {
 				db.close();
 				throw new RepositoryNotFoundException(name, e);
 
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 b850d1e..5d36e58 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,12 +57,8 @@
 	/**
 	 * A factory disabling the ReceivePack service for all repositories
 	 */
-	ReceivePackFactory<?> DISABLED = new ReceivePackFactory<Object>() {
-		@Override
-		public ReceivePack create(Object req, Repository db)
-				throws ServiceNotEnabledException {
-			throw new ServiceNotEnabledException();
-		}
+	ReceivePackFactory<?> DISABLED = (Object req, Repository db) -> {
+		throw new 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 4816f21..dd24b7a 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,12 +57,8 @@
 	/**
 	 * Resolver configured to open nothing.
 	 */
-	RepositoryResolver<?> NONE = new RepositoryResolver<Object>() {
-		@Override
-		public Repository open(Object req, String name)
-				throws RepositoryNotFoundException {
-			throw new RepositoryNotFoundException(name);
-		}
+	RepositoryResolver<?> NONE = (Object req, String name) -> {
+		throw new RepositoryNotFoundException(name);
 	};
 
 	/**
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 bb43b13..b8673f5 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,12 +57,8 @@
 	/**
 	 * A factory disabling the UploadPack service for all repositories.
 	 */
-	UploadPackFactory<?> DISABLED = new UploadPackFactory<Object>() {
-		@Override
-		public UploadPack create(Object req, Repository db)
-				throws ServiceNotEnabledException {
-			throw new ServiceNotEnabledException();
-		}
+	UploadPackFactory<?> DISABLED = (Object req, Repository db) -> {
+		throw new ServiceNotEnabledException();
 	};
 
 	/**
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/NameConflictTreeWalk.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/NameConflictTreeWalk.java
index 61b130f..2f8af78 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/NameConflictTreeWalk.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/NameConflictTreeWalk.java
@@ -323,8 +323,7 @@
 	@Override
 	void popEntriesEqual() throws CorruptObjectException {
 		final AbstractTreeIterator ch = currentHead;
-		for (int i = 0; i < trees.length; i++) {
-			final AbstractTreeIterator t = trees[i];
+		for (AbstractTreeIterator t : trees) {
 			if (t.matches == ch) {
 				if (t.matchShift == 0)
 					t.next(1);
@@ -343,8 +342,7 @@
 	@Override
 	void skipEntriesEqual() throws CorruptObjectException {
 		final AbstractTreeIterator ch = currentHead;
-		for (int i = 0; i < trees.length; i++) {
-			final AbstractTreeIterator t = trees[i];
+		for (AbstractTreeIterator t : trees) {
 			if (t.matches == ch) {
 				if (t.matchShift == 0)
 					t.skip();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/TreeWalk.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/TreeWalk.java
index 69303d6..65d8512 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/TreeWalk.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/TreeWalk.java
@@ -1338,8 +1338,7 @@
 
 	void popEntriesEqual() throws CorruptObjectException {
 		final AbstractTreeIterator ch = currentHead;
-		for (int i = 0; i < trees.length; i++) {
-			final AbstractTreeIterator t = trees[i];
+		for (AbstractTreeIterator t : trees) {
 			if (t.matches == ch) {
 				t.next(1);
 				t.matches = null;
@@ -1349,8 +1348,7 @@
 
 	void skipEntriesEqual() throws CorruptObjectException {
 		final AbstractTreeIterator ch = currentHead;
-		for (int i = 0; i < trees.length; i++) {
-			final AbstractTreeIterator t = trees[i];
+		for (AbstractTreeIterator t : trees) {
 			if (t.matches == ch) {
 				t.skip();
 				t.matches = null;
@@ -1398,10 +1396,8 @@
 	 * @param <T>
 	 *            a tree type.
 	 */
-	public <T extends AbstractTreeIterator> T getTree(
-			Class<T> type) {
-		for (int i = 0; i < trees.length; i++) {
-			AbstractTreeIterator tree = trees[i];
+	public <T extends AbstractTreeIterator> T getTree(Class<T> type) {
+		for (AbstractTreeIterator tree : trees) {
 			if (type.isInstance(tree)) {
 				return type.cast(tree);
 			}
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 e8ff1a9..8bb68dc 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java
@@ -790,14 +790,10 @@
 		return attributesNode;
 	}
 
-	private static final Comparator<Entry> ENTRY_CMP = new Comparator<Entry>() {
-		@Override
-		public int compare(Entry a, Entry b) {
-			return Paths.compare(
-					a.encodedName, 0, a.encodedNameLen, a.getMode().getBits(),
-					b.encodedName, 0, b.encodedNameLen, b.getMode().getBits());
-		}
-	};
+	private static final Comparator<Entry> ENTRY_CMP = (Entry a,
+			Entry b) -> Paths.compare(a.encodedName, 0, a.encodedNameLen,
+					a.getMode().getBits(), b.encodedName, 0, b.encodedNameLen,
+					b.getMode().getBits());
 
 	/**
 	 * Constructor helper.
@@ -1543,6 +1539,7 @@
 			ObjectId blobId = entry.getObjectId();
 			if (entry.getStage() > 0
 					&& entry.getStage() != DirCacheEntry.STAGE_2) {
+				blobId = null;
 				// Merge conflict: check ours (stage 2)
 				byte[] name = entry.getRawPath();
 				int i = 0;
@@ -1550,7 +1547,8 @@
 					dirCache.next(1);
 					i++;
 					entry = dirCache.getDirCacheEntry();
-					if (!Arrays.equals(name, entry.getRawPath())) {
+					if (entry == null
+							|| !Arrays.equals(name, entry.getRawPath())) {
 						break;
 					}
 					if (entry.getStage() == DirCacheEntry.STAGE_2) {
@@ -1560,17 +1558,20 @@
 				}
 				dirCache.back(i);
 			}
-			try (ObjectReader reader = repository.newObjectReader()) {
-				ObjectLoader loader = reader.open(blobId, Constants.OBJ_BLOB);
-				try {
-					return RawText.isCrLfText(loader.getCachedBytes());
-				} catch (LargeObjectException e) {
-					try (InputStream in = loader.openStream()) {
-						return RawText.isCrLfText(in);
+			if (blobId != null) {
+				try (ObjectReader reader = repository.newObjectReader()) {
+					ObjectLoader loader = reader.open(blobId,
+							Constants.OBJ_BLOB);
+					try {
+						return RawText.isCrLfText(loader.getCachedBytes());
+					} catch (LargeObjectException e) {
+						try (InputStream in = loader.openStream()) {
+							return RawText.isCrLfText(in);
+						}
 					}
+				} catch (IOException e) {
+					// Ignore and return false below
 				}
-			} catch (IOException e) {
-				// Ignore and return false below
 			}
 		}
 		return false;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/TreeFilterMarker.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/TreeFilterMarker.java
index 738ccbd..c28f035 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/TreeFilterMarker.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/TreeFilterMarker.java
@@ -113,7 +113,7 @@
 				try {
 					boolean marked = filter.include(walk);
 					if (marked)
-						marks |= (1L << index);
+						marks |= (1 << index);
 				} catch (StopWalkException e) {
 					// Don't check tree filter anymore, it will no longer
 					// match
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 a9cef59..9030501 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java
@@ -363,7 +363,7 @@
 							}
 							return attributes;
 						});
-				f.exceptionally(e -> {
+				f = f.exceptionally(e -> {
 					LOG.error(e.getLocalizedMessage(), e);
 					return Optional.empty();
 				});
@@ -1042,13 +1042,9 @@
 	 * @return the user's home directory; null if the user does not have one.
 	 */
 	protected File userHomeImpl() {
-		final String home = AccessController
-				.doPrivileged(new PrivilegedAction<String>() {
-					@Override
-					public String run() {
-						return System.getProperty("user.home"); //$NON-NLS-1$
-					}
-				});
+		final String home = AccessController.doPrivileged(
+				(PrivilegedAction<String>) () -> System.getProperty("user.home") //$NON-NLS-1$
+		);
 		if (home == null || home.length() == 0)
 			return null;
 		return new File(home).getAbsoluteFile();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_POSIX.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_POSIX.java
index eda0fae..a485389 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_POSIX.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_POSIX.java
@@ -51,8 +51,9 @@
 import java.io.InputStreamReader;
 import java.io.PrintStream;
 import java.nio.charset.Charset;
-import java.nio.file.AccessDeniedException;
 import java.nio.file.FileAlreadyExistsException;
+import java.nio.file.FileStore;
+import java.nio.file.FileSystemException;
 import java.nio.file.Files;
 import java.nio.file.InvalidPathException;
 import java.nio.file.Path;
@@ -62,9 +63,11 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
+import java.util.Map;
 import java.util.Optional;
 import java.util.Set;
 import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
 
 import org.eclipse.jgit.annotations.Nullable;
 import org.eclipse.jgit.api.errors.JGitInternalException;
@@ -88,7 +91,7 @@
 	private static final int DEFAULT_UMASK = 0022;
 	private volatile int umask = -1;
 
-	private volatile boolean supportsUnixNLink = true;
+	private static final Map<FileStore, Boolean> CAN_HARD_LINK = new ConcurrentHashMap<>();
 
 	private volatile AtomicFileCreation supportsAtomicFileCreation = AtomicFileCreation.UNDEFINED;
 
@@ -375,12 +378,23 @@
 		if (!lock.createNewFile()) {
 			return false;
 		}
-		if (supportsAtomicCreateNewFile() || !supportsUnixNLink) {
+		if (supportsAtomicCreateNewFile()) {
 			return true;
 		}
 		Path lockPath = lock.toPath();
 		Path link = null;
+		FileStore store = null;
 		try {
+			store = Files.getFileStore(lockPath);
+		} catch (SecurityException e) {
+			return true;
+		}
+		try {
+			Boolean canLink = CAN_HARD_LINK.computeIfAbsent(store,
+					s -> Boolean.TRUE);
+			if (Boolean.FALSE.equals(canLink)) {
+				return true;
+			}
 			link = Files.createLink(
 					Paths.get(lock.getAbsolutePath() + ".lnk"), //$NON-NLS-1$
 					lockPath);
@@ -392,11 +406,11 @@
 						nlink));
 				return false;
 			} else if (nlink < 2) {
-				supportsUnixNLink = false;
+				CAN_HARD_LINK.put(store, Boolean.FALSE);
 			}
 			return true;
 		} catch (UnsupportedOperationException | IllegalArgumentException e) {
-			supportsUnixNLink = false;
+			CAN_HARD_LINK.put(store, Boolean.FALSE);
 			return true;
 		} finally {
 			if (link != null) {
@@ -439,11 +453,22 @@
 		} catch (FileAlreadyExistsException | InvalidPathException e) {
 			return token(false, null);
 		}
-		if (supportsAtomicCreateNewFile() || !supportsUnixNLink) {
+		if (supportsAtomicCreateNewFile()) {
 			return token(true, null);
 		}
 		Path link = null;
+		FileStore store = null;
 		try {
+			store = Files.getFileStore(path);
+		} catch (SecurityException e) {
+			return token(true, null);
+		}
+		try {
+			Boolean canLink = CAN_HARD_LINK.computeIfAbsent(store,
+					s -> Boolean.TRUE);
+			if (Boolean.FALSE.equals(canLink)) {
+				return token(true, null);
+			}
 			link = Files.createLink(Paths.get(uniqueLinkPath(file)), path);
 			Integer nlink = (Integer) (Files.getAttribute(path,
 					"unix:nlink")); //$NON-NLS-1$
@@ -452,12 +477,12 @@
 						JGitText.get().failedAtomicFileCreation, path, nlink));
 				return token(false, link);
 			} else if (nlink.intValue() < 2) {
-				supportsUnixNLink = false;
+				CAN_HARD_LINK.put(store, Boolean.FALSE);
 			}
 			return token(true, link);
 		} catch (UnsupportedOperationException | IllegalArgumentException
-				| AccessDeniedException | SecurityException e) {
-			supportsUnixNLink = false;
+				| FileSystemException | SecurityException e) {
+			CAN_HARD_LINK.put(store, Boolean.FALSE);
 			return token(true, link);
 		}
 	}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_Win32.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_Win32.java
index 7c07270..7fe80bb 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_Win32.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_Win32.java
@@ -205,18 +205,21 @@
 	@Override
 	protected File userHomeImpl() {
 		String home = SystemReader.getInstance().getenv("HOME"); //$NON-NLS-1$
-		if (home != null)
+		if (home != null) {
 			return resolve(null, home);
+		}
 		String homeDrive = SystemReader.getInstance().getenv("HOMEDRIVE"); //$NON-NLS-1$
 		if (homeDrive != null) {
 			String homePath = SystemReader.getInstance().getenv("HOMEPATH"); //$NON-NLS-1$
-			if (homePath != null)
+			if (homePath != null) {
 				return new File(homeDrive, homePath);
+			}
 		}
 
 		String homeShare = SystemReader.getInstance().getenv("HOMESHARE"); //$NON-NLS-1$
-		if (homeShare != null)
+		if (homeShare != null) {
 			return new File(homeShare);
+		}
 
 		return super.userHomeImpl();
 	}
@@ -237,8 +240,9 @@
 	/** {@inheritDoc} */
 	@Override
 	public boolean supportsSymlinks() {
-		if (supportSymlinks == null)
+		if (supportSymlinks == null) {
 			detectSymlinkSupport();
+		}
 		return Boolean.TRUE.equals(supportSymlinks);
 	}
 
@@ -254,12 +258,13 @@
 				| InternalError e) {
 			supportSymlinks = Boolean.FALSE;
 		} finally {
-			if (tempFile != null)
+			if (tempFile != null) {
 				try {
 					FileUtils.delete(tempFile);
 				} catch (IOException e) {
 					throw new RuntimeException(e); // panic
 				}
+			}
 		}
 	}
 
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_Win32_Cygwin.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_Win32_Cygwin.java
index e5c8d9d..9a163e8 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_Win32_Cygwin.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_Win32_Cygwin.java
@@ -80,12 +80,9 @@
 	 */
 	public static boolean isCygwin() {
 		final String path = AccessController
-				.doPrivileged(new PrivilegedAction<String>() {
-					@Override
-					public String run() {
-						return System.getProperty("java.library.path"); //$NON-NLS-1$
-					}
-				});
+				.doPrivileged((PrivilegedAction<String>) () -> System
+						.getProperty("java.library.path") //$NON-NLS-1$
+				);
 		if (path == null)
 			return false;
 		File found = FS.searchPath(path, "cygpath.exe"); //$NON-NLS-1$
@@ -141,13 +138,9 @@
 	/** {@inheritDoc} */
 	@Override
 	protected File userHomeImpl() {
-		final String home = AccessController
-				.doPrivileged(new PrivilegedAction<String>() {
-					@Override
-					public String run() {
-						return System.getenv("HOME"); //$NON-NLS-1$
-					}
-				});
+		final String home = AccessController.doPrivileged(
+				(PrivilegedAction<String>) () -> System.getenv("HOME") //$NON-NLS-1$
+		);
 		if (home == null || home.length() == 0)
 			return super.userHomeImpl();
 		return resolve(new File("."), home); //$NON-NLS-1$
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/FileUtils.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/FileUtils.java
index 9f7d9a2..4d791e4 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/FileUtils.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/FileUtils.java
@@ -48,6 +48,7 @@
 import static java.nio.charset.StandardCharsets.UTF_8;
 
 import java.io.File;
+import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.nio.channels.FileChannel;
 import java.nio.file.AtomicMoveNotSupportedException;
@@ -200,31 +201,50 @@
 		if ((options & EMPTY_DIRECTORIES_ONLY) != 0) {
 			if (f.isDirectory()) {
 				delete = true;
-			} else {
-				if ((options & IGNORE_ERRORS) == 0)
-					throw new IOException(MessageFormat.format(
-							JGitText.get().deleteFileFailed,
-							f.getAbsolutePath()));
+			} else if ((options & IGNORE_ERRORS) == 0) {
+				throw new IOException(MessageFormat.format(
+						JGitText.get().deleteFileFailed, f.getAbsolutePath()));
 			}
 		} else {
 			delete = true;
 		}
 
-		if (delete && !f.delete()) {
-			if ((options & RETRY) != 0 && fs.exists(f)) {
+		if (delete) {
+			Throwable t = null;
+			Path p = f.toPath();
+			try {
+				Files.delete(p);
+				return;
+			} catch (FileNotFoundException e) {
+				if ((options & (SKIP_MISSING | IGNORE_ERRORS)) == 0) {
+					throw new IOException(MessageFormat.format(
+							JGitText.get().deleteFileFailed,
+							f.getAbsolutePath()), e);
+				}
+				return;
+			} catch (IOException e) {
+				t = e;
+			}
+			if ((options & RETRY) != 0) {
 				for (int i = 1; i < 10; i++) {
 					try {
 						Thread.sleep(100);
-					} catch (InterruptedException e) {
+					} catch (InterruptedException ex) {
 						// ignore
 					}
-					if (f.delete())
+					try {
+						Files.deleteIfExists(p);
 						return;
+					} catch (IOException e) {
+						t = e;
+					}
 				}
 			}
-			if ((options & IGNORE_ERRORS) == 0)
+			if ((options & IGNORE_ERRORS) == 0) {
 				throw new IOException(MessageFormat.format(
-						JGitText.get().deleteFileFailed, f.getAbsolutePath()));
+						JGitText.get().deleteFileFailed, f.getAbsolutePath()),
+						t);
+			}
 		}
 	}
 
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/GSSManagerFactory.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/GSSManagerFactory.java
index da57999..5927b33 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/GSSManagerFactory.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/GSSManagerFactory.java
@@ -145,13 +145,8 @@
 
 				return (GSSManager) GSS_MANAGER_IMPL_CONSTRUCTOR
 						.newInstance(httpCaller);
-			} catch (InstantiationException e) {
-				throw new Error(e);
-			} catch (IllegalAccessException e) {
-				throw new Error(e);
-			} catch (IllegalArgumentException e) {
-				throw new Error(e);
-			} catch (InvocationTargetException e) {
+			} catch (InstantiationException | IllegalAccessException
+					| IllegalArgumentException | InvocationTargetException e) {
 				throw new Error(e);
 			}
 		}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/GitDateParser.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/GitDateParser.java
index a339b9a..56a1731 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/GitDateParser.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/GitDateParser.java
@@ -126,7 +126,7 @@
 		DEFAULT("EEE MMM dd HH:mm:ss yyyy Z"), // //$NON-NLS-1$
 		LOCAL("EEE MMM dd HH:mm:ss yyyy"); //$NON-NLS-1$
 
-		String formatStr;
+		private final String formatStr;
 
 		private ParseableSimpleDateFormat(String formatStr) {
 			this.formatStr = formatStr;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/HttpSupport.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/HttpSupport.java
index 9190a59..640670d 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/HttpSupport.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/HttpSupport.java
@@ -170,6 +170,27 @@
 	public static final String HDR_WWW_AUTHENTICATE = "WWW-Authenticate"; //$NON-NLS-1$
 
 	/**
+	 * The {@code Cookie} header.
+	 *
+	 * @since 5.4
+	 */
+	public static final String HDR_COOKIE = "Cookie"; //$NON-NLS-1$
+
+	/**
+	 * The {@code Set-Cookie} header.
+	 *
+	 * @since 5.4
+	 */
+	public static final String HDR_SET_COOKIE = "Set-Cookie"; //$NON-NLS-1$
+
+	/**
+	 * The {@code Set-Cookie2} header.
+	 *
+	 * @since 5.4
+	 */
+	public static final String HDR_SET_COOKIE2 = "Set-Cookie2"; //$NON-NLS-1$
+
+	/**
 	 * URL encode a value string into an output buffer.
 	 *
 	 * @param urlstr
@@ -303,9 +324,7 @@
 		try {
 			conn.configure(null, trustAllCerts, null);
 			conn.setHostnameVerifier(new DummyHostnameVerifier());
-		} catch (KeyManagementException e) {
-			throw new IOException(e.getMessage());
-		} catch (NoSuchAlgorithmException e) {
+		} catch (KeyManagementException | NoSuchAlgorithmException e) {
 			throw new IOException(e.getMessage());
 		}
 	}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/LRUMap.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/LRUMap.java
new file mode 100644
index 0000000..41c1536
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/LRUMap.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2018, Konrad Windszus <konrad_w@gmx.de>
+ * 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.util;
+
+import java.util.LinkedHashMap;
+
+/**
+ * Map with only up to n entries. If a new entry is added so that the map
+ * contains more than those n entries the least-recently used entry is removed
+ * from the map.
+ *
+ * @param <K>
+ *            the type of keys maintained by this map
+ * @param <V>
+ *            the type of mapped values
+ *
+ * @since 5.4
+ */
+public class LRUMap<K, V> extends LinkedHashMap<K, V> {
+
+	private static final long serialVersionUID = 4329609127403759486L;
+
+	private final int limit;
+
+	/**
+	 * Constructs an empty map which may contain at most the given amount of
+	 * entries.
+	 *
+	 * @param initialCapacity
+	 *            the initial capacity
+	 * @param limit
+	 *            the number of entries the map should have at most
+	 */
+	public LRUMap(int initialCapacity, int limit) {
+		super(initialCapacity, 0.75f, true);
+		this.limit = limit;
+	}
+
+	@Override
+	protected boolean removeEldestEntry(java.util.Map.Entry<K, V> eldest) {
+		return size() > limit;
+	}
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/RefList.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/RefList.java
index 639c353..60dead5 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/RefList.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/RefList.java
@@ -48,7 +48,10 @@
 import java.util.Iterator;
 import java.util.List;
 import java.util.NoSuchElementException;
+import java.util.function.BinaryOperator;
+import java.util.stream.Collector;
 
+import org.eclipse.jgit.annotations.Nullable;
 import org.eclipse.jgit.lib.Ref;
 import org.eclipse.jgit.lib.RefComparator;
 
@@ -333,6 +336,32 @@
 	}
 
 	/**
+	 * Create a {@link Collector} for {@link Ref}.
+	 *
+	 * @param mergeFunction
+	 *            if specified the result will be sorted and deduped.
+	 * @return {@link Collector} for {@link Ref}
+	 * @since 5.4
+	 */
+	public static <T extends Ref> Collector<T, ?, RefList<T>> toRefList(
+			@Nullable BinaryOperator<T> mergeFunction) {
+		return Collector.of(
+				() -> new Builder<>(),
+				Builder<T>::add, (b1, b2) -> {
+					Builder<T> b = new Builder<>();
+					b.addAll(b1);
+					b.addAll(b2);
+					return b;
+				}, (b) -> {
+					if (mergeFunction != null) {
+						b.sort();
+						b.dedupe(mergeFunction);
+					}
+					return b.toRefList();
+				});
+	}
+
+	/**
 	 * Builder to facilitate fast construction of an immutable RefList.
 	 *
 	 * @param <T>
@@ -405,6 +434,16 @@
 		}
 
 		/**
+		 * Add all items from another builder.
+		 *
+		 * @param other
+		 * @since 5.4
+		 */
+		public void addAll(Builder other) {
+			addAll(other.list, 0, other.size);
+		}
+
+		/**
 		 * Add all items from a source array.
 		 * <p>
 		 * References must be added in sort order, or the array must be sorted
@@ -444,6 +483,31 @@
 			Arrays.sort(list, 0, size, RefComparator.INSTANCE);
 		}
 
+		/**
+		 * Dedupe the refs in place. Must be called after {@link #sort}.
+		 *
+		 * @param mergeFunction
+		 */
+		@SuppressWarnings("unchecked")
+		void dedupe(BinaryOperator<T> mergeFunction) {
+			if (size == 0) {
+				return;
+			}
+			int lastElement = 0;
+			for (int i = 1; i < size; i++) {
+				if (RefComparator.INSTANCE.compare(list[lastElement],
+						list[i]) == 0) {
+					list[lastElement] = mergeFunction
+							.apply((T) list[lastElement], (T) list[i]);
+				} else {
+					list[lastElement + 1] = list[i];
+					lastElement++;
+				}
+			}
+			size = lastElement + 1;
+			Arrays.fill(list, size, list.length, null);
+		}
+
 		/** @return an unmodifiable list using this collection's backing array. */
 		public RefList<T> toRefList() {
 			return new RefList<>(list, size);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/RefMap.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/RefMap.java
index a3f9730..9663e3c 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/RefMap.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/RefMap.java
@@ -49,6 +49,9 @@
 import java.util.Map;
 import java.util.NoSuchElementException;
 import java.util.Set;
+import java.util.function.BinaryOperator;
+import java.util.stream.Collector;
+import java.util.stream.Collectors;
 
 import org.eclipse.jgit.lib.AnyObjectId;
 import org.eclipse.jgit.lib.ObjectId;
@@ -285,6 +288,21 @@
 		return r.toString();
 	}
 
+	/**
+	 * Create a {@link Collector} for {@link Ref}.
+	 *
+	 * @param mergeFunction
+	 * @return {@link Collector} for {@link Ref}
+	 * @since 5.4
+	 */
+	public static Collector<Ref, ?, RefMap> toRefMap(
+			BinaryOperator<Ref> mergeFunction) {
+		return Collectors.collectingAndThen(RefList.toRefList(mergeFunction),
+				(refs) -> new RefMap("", //$NON-NLS-1$
+							refs, RefList.emptyList(),
+						RefList.emptyList()));
+	}
+
 	private String toRefName(String name) {
 		if (0 < prefix.length())
 			name = prefix + name;
@@ -425,8 +443,10 @@
 					if (r.getName().equals(ref.getName())) {
 						final ObjectId a = r.getObjectId();
 						final ObjectId b = ref.getObjectId();
-						if (a != null && b != null && AnyObjectId.equals(a, b))
+						if (a != null && b != null
+								&& AnyObjectId.isEqual(a, b)) {
 							return true;
+						}
 					}
 				}
 			}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/References.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/References.java
new file mode 100644
index 0000000..341fbfa
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/References.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2019, 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.util;
+
+/**
+ * Utility methods for object references
+ *
+ * @since 5.4
+ */
+public interface References {
+
+	/**
+	 * Compare two references
+	 *
+	 * @param <T>
+	 *            type of the references
+	 * @param ref1
+	 *            first reference
+	 * @param ref2
+	 *            second reference
+	 * @return {@code true} if both references refer to the same object
+	 */
+	@SuppressWarnings("ReferenceEquality")
+	public static <T> boolean isSameObject(T ref1, T ref2) {
+		return ref1 == ref2;
+	}
+}
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 3fcfd21..c213bf9 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/SimpleLruCache.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/SimpleLruCache.java
@@ -159,6 +159,7 @@
 	 *
 	 * @return value mapped for this key, or {@code null} if no value is mapped
 	 */
+	@SuppressWarnings("NonAtomicVolatileUpdate")
 	public V get(Object key) {
 		Entry<K, V> entry = map.get(key);
 		if (entry != null) {
@@ -185,6 +186,7 @@
 	 * @throws NullPointerException
 	 *             if the specified key or value is null
 	 */
+	@SuppressWarnings("NonAtomicVolatileUpdate")
 	public V put(@NonNull K key, @NonNull V value) {
 		map.put(key, new Entry<>(key, value, tick()));
 		if (map.size() > maximumSize) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/StringUtils.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/StringUtils.java
index 3868e56..f4b6f9d 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/StringUtils.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/StringUtils.java
@@ -139,8 +139,9 @@
 	 * @return true if a equals b
 	 */
 	public static boolean equalsIgnoreCase(String a, String b) {
-		if (a == b)
+		if (References.isSameObject(a, b)) {
 			return true;
+		}
 		if (a.length() != b.length())
 			return false;
 		for (int i = 0; i < a.length(); i++) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/SystemReader.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/SystemReader.java
index cccac66..b80ffb5 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/SystemReader.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/SystemReader.java
@@ -451,12 +451,9 @@
 	}
 
 	private String getOsName() {
-		return AccessController.doPrivileged(new PrivilegedAction<String>() {
-			@Override
-			public String run() {
-				return getProperty("os.name"); //$NON-NLS-1$
-			}
-		});
+		return AccessController.doPrivileged(
+				(PrivilegedAction<String>) () -> getProperty("os.name") //$NON-NLS-1$
+		);
 	}
 
 	/**
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/IsolatedOutputStream.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/IsolatedOutputStream.java
index 65470d4..18de705 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/IsolatedOutputStream.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/IsolatedOutputStream.java
@@ -88,7 +88,7 @@
 	public IsolatedOutputStream(OutputStream out) {
 		dst = out;
 		copier = new ThreadPoolExecutor(1, 1, 0, TimeUnit.MILLISECONDS,
-				new ArrayBlockingQueue<Runnable>(1), new NamedThreadFactory());
+				new ArrayBlockingQueue<>(1), new NamedThreadFactory());
 	}
 
 	/** {@inheritDoc} */
@@ -102,12 +102,9 @@
 	public void write(byte[] buf, int pos, int cnt)
 			throws IOException {
 		checkClosed();
-		execute(new Callable<Void>() {
-			@Override
-			public Void call() throws IOException {
-				dst.write(buf, pos, cnt);
-				return null;
-			}
+		execute(() -> {
+			dst.write(buf, pos, cnt);
+			return null;
 		});
 	}
 
@@ -115,12 +112,9 @@
 	@Override
 	public void flush() throws IOException {
 		checkClosed();
-		execute(new Callable<Void>() {
-			@Override
-			public Void call() throws IOException {
-				dst.flush();
-				return null;
-			}
+		execute(() -> {
+			dst.flush();
+			return null;
 		});
 	}
 
@@ -159,12 +153,9 @@
 	}
 
 	private void cleanClose() throws IOException {
-		execute(new Callable<Void>() {
-			@Override
-			public Void call() throws IOException {
-				dst.close();
-				return null;
-			}
+		execute(() -> {
+			dst.close();
+			return null;
 		});
 	}
 
@@ -178,12 +169,9 @@
 
 		Future<Void> close;
 		try {
-			close = copier.submit(new Callable<Void>() {
-				@Override
-				public Void call() throws IOException {
-					dst.close();
-					return null;
-				}
+			close = copier.submit(() -> {
+				dst.close();
+				return null;
 			});
 		} catch (RejectedExecutionException e) {
 			throw new IOException(e);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/ThrowingPrintWriter.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/ThrowingPrintWriter.java
index caabcef..9431aaf 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/ThrowingPrintWriter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/ThrowingPrintWriter.java
@@ -68,12 +68,10 @@
 	 */
 	public ThrowingPrintWriter(Writer out) {
 		this.out = out;
-		LF = AccessController.doPrivileged(new PrivilegedAction<String>() {
-			@Override
-			public String run() {
-				return SystemReader.getInstance().getProperty("line.separator"); //$NON-NLS-1$
-			}
-		});
+		LF = AccessController
+				.doPrivileged((PrivilegedAction<String>) () -> SystemReader
+						.getInstance().getProperty("line.separator") //$NON-NLS-1$
+				);
 	}
 
 	/** {@inheritDoc} */
diff --git a/pom.xml b/pom.xml
index d5c3a78..26b1c1a 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.3.10-SNAPSHOT</version>
+  <version>5.4.4-SNAPSHOT</version>
 
   <name>JGit - Parent</name>
   <url>${jgit-url}</url>
@@ -182,8 +182,8 @@
     <maven.build.timestamp.format>yyyyMMddHHmm</maven.build.timestamp.format>
     <bundle-manifest>${project.build.directory}/META-INF/MANIFEST.MF</bundle-manifest>
 
-    <jgit-last-release-version>5.2.0.201812061821-r</jgit-last-release-version>
-    <apache-sshd-version>2.0.0</apache-sshd-version>
+    <jgit-last-release-version>5.3.0.201903130848-r</jgit-last-release-version>
+    <apache-sshd-version>2.2.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>
@@ -202,9 +202,9 @@
     <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>
-    <bouncycastle-version>1.60</bouncycastle-version>
+    <bouncycastle-version>1.61</bouncycastle-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>
+    <maven-surefire-plugin-version>3.0.0-M5</maven-surefire-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>
@@ -619,7 +619,7 @@
       <plugin>
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-surefire-report-plugin</artifactId>
-        <version>${maven-surefire-version}</version>
+        <version>${maven-surefire-report-plugin-version}</version>
         <configuration>
           <aggregate>true</aggregate>
           <alwaysGenerateSurefireReport>false</alwaysGenerateSurefireReport>
diff --git a/tools/BUILD b/tools/BUILD
index 6741d23..e5931dc 100644
--- a/tools/BUILD
+++ b/tools/BUILD
@@ -27,13 +27,13 @@
         "-Xep:ReferenceEquality:WARN",
         "-Xep:StringEquality:WARN",
         "-Xep:WildcardImport:ERROR",
-        "-Xep:AmbiguousMethodReference:WARN",
+        "-Xep:AmbiguousMethodReference:ERROR",
         "-Xep:BadAnnotationImplementation:ERROR",
         "-Xep:BadComparable:WARN",
         "-Xep:BoxedPrimitiveConstructor:ERROR",
         "-Xep:CannotMockFinalClass:ERROR",
         "-Xep:ClassCanBeStatic:ERROR",
-        "-Xep:ClassNewInstance:WARN",
+        "-Xep:ClassNewInstance:ERROR",
         "-Xep:DefaultCharset:ERROR",
         "-Xep:DoubleCheckedLocking:ERROR",
         "-Xep:ElementsCountedInLoop:ERROR",
@@ -45,10 +45,10 @@
         "-Xep:FragmentInjection:ERROR",
         "-Xep:FragmentNotInstantiable:ERROR",
         "-Xep:FunctionalInterfaceClash:ERROR",
-        "-Xep:FutureReturnValueIgnored:WARN",
+        "-Xep:FutureReturnValueIgnored:ERROR",
         "-Xep:GetClassOnEnum:ERROR",
         "-Xep:ImmutableAnnotationChecker:ERROR",
-        "-Xep:ImmutableEnumChecker:WARN",
+        "-Xep:ImmutableEnumChecker:ERROR",
         "-Xep:IncompatibleModifiers:ERROR",
         "-Xep:InjectOnConstructorOfAbstractClass:ERROR",
         "-Xep:InputStreamSlowMultibyteRead:ERROR",
@@ -59,19 +59,19 @@
         "-Xep:MissingFail:ERROR",
         "-Xep:MissingOverride:ERROR",
         "-Xep:MutableConstantField:ERROR",
-        "-Xep:NarrowingCompoundAssignment:WARN",
+        "-Xep:NarrowingCompoundAssignment:ERROR",
         "-Xep:NonAtomicVolatileUpdate:ERROR",
-        "-Xep:NonOverridingEquals:WARN",
+        "-Xep:NonOverridingEquals:ERROR",
         "-Xep:NullableConstructor:ERROR",
         "-Xep:NullablePrimitive:ERROR",
         "-Xep:NullableVoid:ERROR",
-        "-Xep:OperatorPrecedence:WARN",
+        "-Xep:OperatorPrecedence:ERROR",
         "-Xep:OverridesGuiceInjectableMethod:ERROR",
         "-Xep:PreconditionsInvalidPlaceholder:ERROR",
         "-Xep:ProtoFieldPreconditionsCheckNotNull:ERROR",
         "-Xep:ProtocolBufferOrdinal:ERROR",
         "-Xep:RequiredModifiers:ERROR",
-        "-Xep:ShortCircuitBoolean:WARN",
+        "-Xep:ShortCircuitBoolean:ERROR",
         "-Xep:SimpleDateFormatConstant:ERROR",
         "-Xep:StaticGuardedByInstance:ERROR",
         "-Xep:SynchronizeOnNonFinalField:ERROR",
diff --git a/tools/version.sh b/tools/version.sh
index 8b8095d..80693cd 100755
--- a/tools/version.sh
+++ b/tools/version.sh
@@ -131,7 +131,8 @@
 		$seen_version = 1 if (!/<\?xml/ &&
 		s/(version=")[^"]*(")/${1}'"$OSGI_V"'${2}/);
 	}
-	s/(import feature="org\.eclipse\.jgit.*" version=")[^"]*(")/${1}'"$API_V"'${2}/;
+	s/(import feature="org\.eclipse\.jgit[^"]*" version=")[^"]*(")/${1}'"$API_V"'${2}/;
+	s/(import plugin="org\.eclipse\.jgit[^"]*" version=")[^"]*(")/${1}'"$API_V"'${2}/;
 	' org.eclipse.jgit.packaging/org.*.feature/feature.xml
 
 perl -pi~ -e '
@@ -139,22 +140,11 @@
 		$seen_version = 0;
 		$old_argv = $ARGV;
 	}
-	if (!$seen_version) {
-		$seen_version = 1 if
+	if ($seen_version < 2) {
+		$seen_version++ if
 		s{<(version)>.*</\1>}{<${1}>'"$POM_V"'</${1}>};
 	}
-	' org.eclipse.jgit.packaging/org.*.feature/pom.xml
-
-perl -pi~ -e '
-	if ($ARGV ne $old_argv) {
-		$seen_version = 0;
-		$old_argv = $ARGV;
-	}
-	if (!$seen_version) {
-		$seen_version = 1 if
-		s{<(version)>.*</\1>}{<${1}>'"$POM_V"'</${1}>};
-	}
-	' org.eclipse.jgit.packaging/pom.xml
+	' org.eclipse.jgit.packaging/org.*.source.feature/pom.xml
 
 perl -pi~ -e '
 	if ($ARGV ne $old_argv) {
@@ -162,7 +152,7 @@
 		$old_argv = $ARGV;
 	}
 	if ($seen_version < 18) {
-			$seen_version++ if
+		$seen_version++ if
 		s{<(version)>.*</\1>}{<${1}>'"$POM_V"'</${1}>};
 	}
 	' org.eclipse.jgit.coverage/pom.xml