Merge branch 'stable-5.3' into stable-5.4

* stable-5.3:
  Fix API problem filters
  Fix unclosed resource warning in SmartOutputStream
  JschConfigSessionFactory: fix boxing warning
  SshSupport#runSshCommand: don't throw exception in finally block
  Don't override already managed maven-compiler-plugin version
  Remove unused import from CreateFileSnapshotBenchmark
  Remove duplicate ignore_optional_problems entry in .classpath
  Update maven-site-plugin used by benchmark module to 3.8.2
  Add dependency to enable site generation for benchmark module
  Ignore warnings for generated source code in
org.eclipse.jgit.benchmark
  Fix MBean registration
  Enhance WindowCache statistics

Change-Id: I1b560b36d169cfa02cc5450ad0fa0bd85f9f42d8
Signed-off-by: Matthias Sohn <matthias.sohn@sap.com>
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 dc89a6c..1d02d2b 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 4a03b57..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.7.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.7,5.4.0)",
- org.eclipse.jgit.junit;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.lib;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.util;version="[5.3.7,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 99c58ca..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.7-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 d7386e6..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.7.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.7,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.7";
- 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 7f5da47..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.7-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 a3499c5..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.7.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.7,5.4.0)",
- org.eclipse.jgit.lib;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.nls;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.revwalk;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.util;version="[5.3.7,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.7";
+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 74c9065..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.7.qualifier
-Eclipse-SourceBundle: org.eclipse.jgit.archive;version="5.3.7.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 3c47f15..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.7-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 b91a141..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.7-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 bd78a25..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.7-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.7-SNAPSHOT</version>
+      <version>5.4.4-SNAPSHOT</version>
     </dependency>
     <dependency>
       <groupId>org.eclipse.jgit</groupId>
       <artifactId>org.eclipse.jgit.ant</artifactId>
-      <version>5.3.7-SNAPSHOT</version>
+      <version>5.4.4-SNAPSHOT</version>
     </dependency>
     <dependency>
       <groupId>org.eclipse.jgit</groupId>
       <artifactId>org.eclipse.jgit.archive</artifactId>
-      <version>5.3.7-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.7-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.7-SNAPSHOT</version>
+      <version>5.4.4-SNAPSHOT</version>
     </dependency>
     <dependency>
       <groupId>org.eclipse.jgit</groupId>
       <artifactId>org.eclipse.jgit.lfs</artifactId>
-      <version>5.3.7-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.7-SNAPSHOT</version>
+      <version>5.4.4-SNAPSHOT</version>
     </dependency>
     <dependency>
       <groupId>org.eclipse.jgit</groupId>
       <artifactId>org.eclipse.jgit.pgm</artifactId>
-      <version>5.3.7-SNAPSHOT</version>
+      <version>5.4.4-SNAPSHOT</version>
     </dependency>
     <dependency>
       <groupId>org.eclipse.jgit</groupId>
       <artifactId>org.eclipse.jgit.ui</artifactId>
-      <version>5.3.7-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.7-SNAPSHOT</version>
+      <version>5.4.4-SNAPSHOT</version>
     </dependency>
 
     <dependency>
       <groupId>org.eclipse.jgit</groupId>
       <artifactId>org.eclipse.jgit.test</artifactId>
-      <version>5.3.7-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.7-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.7-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.7-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.7-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.7-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.7-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 3ea2f90..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.7.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.7,5.4.0)",
- org.eclipse.jgit.nls;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.transport.http;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.util;version="[5.3.7,5.4.0)"
-Export-Package: org.eclipse.jgit.transport.http.apache;version="5.3.7";
+ 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 ab56aea..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.7-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 b42ac1d..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.7.qualifier
+Bundle-Version: 5.4.4.qualifier
 Bundle-Localization: plugin
 Bundle-Vendor: %provider_name
-Export-Package: org.eclipse.jgit.http.server;version="5.3.7",
- org.eclipse.jgit.http.server.glue;version="5.3.7";
+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.7";
+ 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.7,5.4.0)",
- org.eclipse.jgit.internal.storage.dfs;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.internal.storage.file;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.internal.transport.parser;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.lib;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.nls;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.revwalk;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.transport;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.transport.resolver;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.util;version="[5.3.7,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 843139d..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.7-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 9bbeb1a..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.7.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.7,5.4.0)",
- org.eclipse.jgit.http.server;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.http.server.glue;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.http.server.resolver;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.internal;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.internal.storage.dfs;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.internal.storage.file;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.internal.storage.reftable;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.junit;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.junit.http;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.lib;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.nls;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.revwalk;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.storage.file;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.transport;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.transport.http;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.transport.http.apache;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.transport.resolver;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.util;version="[5.3.7,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 e87dcbb..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.7-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 fa4fc2c..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.7.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.7,5.4.0)",
- org.eclipse.jgit.http.server;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.internal.storage.file;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.junit;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.lib;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.revwalk;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.transport;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.transport.resolver;version="[5.3.7,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.7";
+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 0296e9d..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.7-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 4e1d38a..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.7.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.7,5.4.0)",
- org.eclipse.jgit.lib;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.transport;version="[5.3.7,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.7"
+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 97b70dc..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.7-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/META-INF/MANIFEST.MF b/org.eclipse.jgit.junit/META-INF/MANIFEST.MF
index 1e24fe3..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.7.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.7,5.4.0)",
- org.eclipse.jgit.api;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.api.errors;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.dircache;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.errors;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.internal.storage.file;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.internal.storage.pack;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.lib;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.merge;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.revwalk;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.storage.file;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.transport;version="5.3.7",
- org.eclipse.jgit.treewalk;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.treewalk.filter;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.util;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.util.io;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.util.time;version="[5.3.7,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.7";
+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.7";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 69f1275..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.7-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 55ed767..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.7.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.7,5.4.0)",
- org.eclipse.jgit.api.errors;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.internal.storage.file;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.junit;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.junit.http;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.lfs;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.lfs.errors;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.lfs.lib;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.lfs.server;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.lfs.server.fs;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.lfs.test;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.lib;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.revwalk;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.storage.file;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.transport;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.treewalk;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.treewalk.filter;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.util;version="[5.3.7,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 77c1c24..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.7-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 15ef303..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.7.qualifier
+Bundle-Version: 5.4.4.qualifier
 Bundle-Localization: plugin
 Bundle-Vendor: %provider_name
-Export-Package: org.eclipse.jgit.lfs.server;version="5.3.7";
+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.7";
+ 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.7";x-internal:=true,
- org.eclipse.jgit.lfs.server.s3;version="5.3.7";
+ 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.7,5.4.0)",
- org.eclipse.jgit.internal;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.internal.storage.file;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.lfs.errors;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.lfs.internal;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.lfs.lib;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.lib;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.nls;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.transport.http;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.transport.http.apache;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.util;version="[5.3.7,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 af0b397..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.7-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 4ec19ec..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.7.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.7,5.4.0)",
- org.eclipse.jgit.junit;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.lfs;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.lfs.errors;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.lfs.lib;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.lib;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.revwalk;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.treewalk;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.treewalk.filter;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.util;version="[5.3.7,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.7";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 e9f392e..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.7-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 9837dd9..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.7.qualifier
+Bundle-Version: 5.4.4.qualifier
 Bundle-Localization: plugin
 Bundle-Vendor: %provider_name
-Export-Package: org.eclipse.jgit.lfs;version="5.3.7",
- org.eclipse.jgit.lfs.errors;version="5.3.7",
- org.eclipse.jgit.lfs.internal;version="5.3.7";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.7"
+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.7,5.4.0)";resolution:=optional,
- org.eclipse.jgit.api.errors;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.attributes;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.diff;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.errors;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.hooks;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.internal.storage.file;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.lib;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.nls;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.revwalk;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.storage.file;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.storage.pack;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.transport;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.transport.http;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.treewalk;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.treewalk.filter;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.util;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.util.io;version="[5.3.7,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 92b86be..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.7-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 7172b23..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.7.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 1df0290..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.7-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 9fc034b..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.7.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 94f61f2..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.7-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 a850c33..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.7.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 04583ab..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.7-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 e7799a9..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.7.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 ac5cfc4..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.7-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 343d1e7..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.7.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.7" match="equivalent"/>
-      <import feature="org.eclipse.jgit.lfs" version="5.3.7" match="equivalent"/>
-      <import feature="org.eclipse.jgit.ssh.apache" version="5.3.7" 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 1eefe17..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.7-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 b52ed9c..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.7.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 9bb8cf8..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.7-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 86071e1..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.7-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 02fe61b..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.7.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 cbb12d6..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.7-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 ebcd542..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.7.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 c5798f7..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.7-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 0153921..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.7.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 3a28a5a..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.7-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 c70489f..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.7.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 86653be..36660ff 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="1566244557">
+<target name="jgit-4.10" sequenceNumber="1566251637">
   <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-staging.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.11-staging.tpd
deleted file mode 100644
index 4fc23ed..0000000
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.11-staging.tpd
+++ /dev/null
@@ -1,8 +0,0 @@
-target "jgit-4.11-staging" with source configurePhase
-
-include "projects/jetty-9.4.14.tpd"
-include "orbit/R20190602212107-2019-06.tpd"
-
-location "http://download.eclipse.org/staging/2019-03/" {
-	org.eclipse.osgi lazy
-}
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.11-staging.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.11.target
similarity index 84%
copy from org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.11-staging.target
copy to org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.11.target
index 0857af3..afa4b3d 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.11-staging.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-staging" sequenceNumber="1566244434">
+<target name="jgit-4.11" sequenceNumber="1566251633">
   <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,15 +78,15 @@
       <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"/>
-      <repository location="http://download.eclipse.org/staging/2019-03/"/>
+      <repository location="http://download.eclipse.org/releases/2019-03/"/>
     </location>
   </locations>
 </target>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.11.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.11.tpd
new file mode 100644
index 0000000..e09e8c3
--- /dev/null
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.11.tpd
@@ -0,0 +1,8 @@
+target "jgit-4.11" with source configurePhase
+
+include "projects/jetty-9.4.14.tpd"
+include "orbit/R20190602212107-2019-06.tpd"
+
+location "http://download.eclipse.org/releases/2019-03/" {
+	org.eclipse.osgi lazy
+}
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.11-staging.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.12-staging.target
similarity index 85%
rename from org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.11-staging.target
rename to org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.12-staging.target
index 0857af3..92c9a88 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.11-staging.target
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.12-staging.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-staging" sequenceNumber="1566244434">
+<target name="jgit-4.12-staging" sequenceNumber="1566251632">
   <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,15 +78,15 @@
       <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"/>
-      <repository location="http://download.eclipse.org/staging/2019-03/"/>
+      <repository location="http://download.eclipse.org/staging/2019-06/"/>
     </location>
   </locations>
 </target>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.12-staging.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.12-staging.tpd
new file mode 100644
index 0000000..43b64a0
--- /dev/null
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.12-staging.tpd
@@ -0,0 +1,8 @@
+target "jgit-4.12-staging" with source configurePhase
+
+include "projects/jetty-9.4.14.tpd"
+include "orbit/R20190602212107-2019-06.tpd"
+
+location "http://download.eclipse.org/staging/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 05488b9..9724939 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="1566244561">
+<target name="jgit-4.6" sequenceNumber="1566251641">
   <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 e82222a..6bd70e0 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="1566244564">
+<target name="jgit-4.7" sequenceNumber="1566251627">
   <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 0b353db..e33ece7 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="1566244569">
+<target name="jgit-4.8" sequenceNumber="1566251616">
   <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 94a752c..9b3fe1b 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="1566244572">
+<target name="jgit-4.9" sequenceNumber="1566251604">
   <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 03c338e..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.7-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 fa57f7d..1d6e6a0 100644
--- a/org.eclipse.jgit.packaging/pom.xml
+++ b/org.eclipse.jgit.packaging/pom.xml
@@ -49,13 +49,13 @@
 
   <groupId>org.eclipse.jgit</groupId>
   <artifactId>jgit.tycho.parent</artifactId>
-  <version>5.3.7-SNAPSHOT</version>
+  <version>5.4.4-SNAPSHOT</version>
   <packaging>pom</packaging>
 
   <name>JGit Tycho Parent</name>
 
   <properties>
-    <tycho-version>1.3.0</tycho-version>
+    <tycho-version>1.4.0</tycho-version>
     <tycho-extras-version>${tycho-version}</tycho-extras-version>
     <target-platform>jgit-4.6</target-platform>
   </properties>
@@ -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 0d786fc..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.7.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.7,5.4.0)",
- org.eclipse.jgit.api.errors;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.diff;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.dircache;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.internal.storage.file;version="5.3.7",
- org.eclipse.jgit.junit;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.lib;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.merge;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.pgm;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.pgm.internal;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.pgm.opt;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.revwalk;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.storage.file;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.transport;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.treewalk;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.util;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.util.io;version="[5.3.7,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 2525f3a..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.7-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 da78a4d..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.7.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.7,5.4.0)",
- org.eclipse.jgit.api.errors;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.archive;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.awtui;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.blame;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.diff;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.dircache;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.errors;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.gitrepo;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.internal.ketch;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.internal.storage.dfs;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.internal.storage.file;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.internal.storage.io;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.internal.storage.pack;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.internal.storage.reftable;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.internal.storage.reftree;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.lfs;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.lfs.lib;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.lfs.server;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.lfs.server.fs;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.lfs.server.s3;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.lib;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.merge;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.nls;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.notes;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.revplot;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.revwalk;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.revwalk.filter;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.storage.file;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.storage.pack;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.transport;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.transport.http.apache;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.transport.resolver;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.transport.sshd;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.treewalk;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.treewalk.filter;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.util;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.util.io;version="[5.3.7,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.7";
+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.7";
+ 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.7";
+ 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.7";x-friends:="org.eclipse.jgit.pgm.test,org.eclipse.jgit.test",
- org.eclipse.jgit.pgm.opt;version="5.3.7";
+ 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 d8196e1..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.7.qualifier
-Eclipse-SourceBundle: org.eclipse.jgit.pgm;version="5.3.7.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 f77d72d..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.7-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 319b5e3..4382663 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
@@ -66,7 +66,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.kohsuke.args4j.Argument;
@@ -188,22 +187,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 602da1d..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.7.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.7,5.4.0)",
- org.eclipse.jgit.junit;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.junit.ssh;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.lib;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.transport;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.transport.ssh;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.transport.sshd;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.util;version="[5.3.7,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 cc17f91..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.7-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 1e66e67..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.7.qualifier
+Bundle-Version: 5.4.4.qualifier
 Bundle-RequiredExecutionEnvironment: JavaSE-1.8
-Export-Package: org.eclipse.jgit.internal.transport.sshd;version="5.3.7";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.7";x-internal:=true,
- org.eclipse.jgit.internal.transport.sshd.proxy;version="5.3.7";x-friends:="org.eclipse.jgit.ssh.apache.test",
- org.eclipse.jgit.transport.sshd;version="5.3.7";
+ 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.7,5.4.0)",
- org.eclipse.jgit.errors;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.fnmatch;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.internal.storage.file;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.internal.transport.ssh;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.nls;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.transport;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.util;version="[5.3.7,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 2afdd6f..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.7.qualifier
-Eclipse-SourceBundle: org.eclipse.jgit.ssh.apache;version="5.3.7.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 59066b7..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.7-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 8faf131..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.7.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.7,5.4.0)",
- org.eclipse.jgit.api;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.api.errors;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.archive;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.attributes;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.awtui;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.blame;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.diff;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.dircache;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.errors;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.events;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.fnmatch;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.gitrepo;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.hooks;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.ignore;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.ignore.internal;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.internal;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.internal.fsck;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.internal.storage.dfs;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.internal.storage.file;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.internal.storage.io;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.internal.storage.pack;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.internal.storage.reftable;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.internal.storage.reftree;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.internal.transport.parser;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.junit;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.junit.ssh;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.junit.time;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.lfs;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.lib;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.merge;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.nls;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.notes;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.patch;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.pgm;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.pgm.internal;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.revplot;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.revwalk;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.revwalk.filter;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.storage.file;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.storage.pack;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.submodule;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.transport;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.transport.http;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.transport.resolver;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.treewalk;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.treewalk.filter;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.util;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.util.io;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.util.sha1;version="[5.3.7,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.7";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 d88d5e3..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.7-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 bfa30d5..5a5ae1d 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
@@ -976,7 +976,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 2ac4a84..79cf4d5 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
@@ -967,6 +967,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..0a3f89e
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/transport/http/NetscapeCookieFileTest.java
@@ -0,0 +1,339 @@
+/*
+ * 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.internal.transport.http.NetscapeCookieFile;
+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 0c0fd09..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.7.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.7"
-Import-Package: org.eclipse.jgit.errors;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.lib;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.nls;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.revplot;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.revwalk;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.transport;version="[5.3.7,5.4.0)",
- org.eclipse.jgit.util;version="[5.3.7,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 ec59722..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.7-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 abdf350..7930b13 100644
--- a/org.eclipse.jgit/.settings/.api_filters
+++ b/org.eclipse.jgit/.settings/.api_filters
@@ -60,33 +60,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>
@@ -155,55 +149,53 @@
             </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>
@@ -232,12 +224,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>
@@ -291,6 +277,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 38b9452..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.7.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.7",
- org.eclipse.jgit.api;version="5.3.7";
+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.7";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.errors",
- org.eclipse.jgit.attributes;version="5.3.7",
- org.eclipse.jgit.blame;version="5.3.7";
+ 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.7";
+ 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.7";
+ 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.7";
+ 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.7";uses:="org.eclipse.jgit.lib",
- org.eclipse.jgit.fnmatch;version="5.3.7",
- org.eclipse.jgit.gitrepo;version="5.3.7";
+ 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.7";x-internal:=true,
- org.eclipse.jgit.hooks;version="5.3.7";uses:="org.eclipse.jgit.lib",
- org.eclipse.jgit.ignore;version="5.3.7",
- org.eclipse.jgit.ignore.internal;version="5.3.7";x-friends:="org.eclipse.jgit.test",
- org.eclipse.jgit.internal;version="5.3.7";x-friends:="org.eclipse.jgit.test,org.eclipse.jgit.http.test",
- org.eclipse.jgit.internal.fsck;version="5.3.7";x-friends:="org.eclipse.jgit.test",
- org.eclipse.jgit.internal.ketch;version="5.3.7";x-friends:="org.eclipse.jgit.junit,org.eclipse.jgit.test,org.eclipse.jgit.pgm",
- org.eclipse.jgit.internal.revwalk;version="5.3.7";x-internal:=true,
- org.eclipse.jgit.internal.storage.dfs;version="5.3.7";
+ 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.7";
+ 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.7";x-friends:="org.eclipse.jgit.junit,org.eclipse.jgit.test,org.eclipse.jgit.pgm",
- org.eclipse.jgit.internal.storage.pack;version="5.3.7";x-friends:="org.eclipse.jgit.junit,org.eclipse.jgit.test,org.eclipse.jgit.pgm",
- org.eclipse.jgit.internal.storage.reftable;version="5.3.7";
+ 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.7";x-friends:="org.eclipse.jgit.junit,org.eclipse.jgit.test,org.eclipse.jgit.pgm",
- org.eclipse.jgit.internal.submodule;version="5.3.7";x-internal:=true,
- org.eclipse.jgit.internal.transport.parser;version="5.3.7";x-friends:="org.eclipse.jgit.http.server,org.eclipse.jgit.test",
- org.eclipse.jgit.internal.transport.ssh;version="5.3.7";x-friends:="org.eclipse.jgit.ssh.apache",
- org.eclipse.jgit.lib;version="5.3.7";
+ 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.7";x-internal:=true,
- org.eclipse.jgit.merge;version="5.3.7";
+ 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.7",
- org.eclipse.jgit.notes;version="5.3.7";
+ 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.7";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.diff",
- org.eclipse.jgit.revplot;version="5.3.7";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.revwalk",
- org.eclipse.jgit.revwalk;version="5.3.7";
+ 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.7";uses:="org.eclipse.jgit.revwalk,org.eclipse.jgit.lib,org.eclipse.jgit.util",
- org.eclipse.jgit.storage.file;version="5.3.7";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.util",
- org.eclipse.jgit.storage.pack;version="5.3.7";uses:="org.eclipse.jgit.lib",
- org.eclipse.jgit.submodule;version="5.3.7";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.treewalk.filter,org.eclipse.jgit.treewalk",
- org.eclipse.jgit.transport;version="5.3.7";
+ 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.7";uses:="javax.net.ssl",
- org.eclipse.jgit.transport.resolver;version="5.3.7";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.transport",
- org.eclipse.jgit.treewalk;version="5.3.7";
+ 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.7";uses:="org.eclipse.jgit.treewalk",
- org.eclipse.jgit.util;version="5.3.7";
+ 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.7",
- org.eclipse.jgit.util.sha1;version="5.3.7",
- org.eclipse.jgit.util.time;version="5.3.7"
+ 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 691174a..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.7.qualifier
-Eclipse-SourceBundle: org.eclipse.jgit;version="5.3.7.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 cec9e48..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.7-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 c41a565..0498882 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
@@ -296,6 +302,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
@@ -411,6 +418,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
@@ -575,6 +583,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 d51e052..60d1dab 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;
@@ -357,6 +363,7 @@
 	/***/ public String gpgNoKeyInLegacySecring;
 	/***/ public String gpgNoPublicKeyFound;
 	/***/ public String gpgNoSecretKeyForPublicKey;
+	/***/ public String gpgNotASigningKey;
 	/***/ public String gpgKeyInfo;
 	/***/ public String gpgSigningCancelled;
 	/***/ public String headRequiredToStash;
@@ -472,6 +479,7 @@
 	/***/ public String mismatchCRC;
 	/***/ public String missingAccesskey;
 	/***/ public String missingConfigurationForKey;
+	/***/ public String missingCookieFile;
 	/***/ public String missingCRC;
 	/***/ public String missingDeltaBase;
 	/***/ public String missingForwardImageInGITBinaryPatch;
@@ -637,6 +645,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 976f946..8650ebf 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
@@ -413,6 +413,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
@@ -490,7 +491,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 7400308..08bb6cb 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
@@ -244,7 +244,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) {
@@ -281,7 +281,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 f7e78b9..6af4125 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;
 
@@ -248,13 +244,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;
 		}
@@ -308,13 +298,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;
 		}
@@ -362,13 +346,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 6e8a15e..7d31673 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 a4729bb..e5cea6c 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
@@ -745,7 +745,7 @@
 		for (LockFile ol : heldLocks.values()) {
 			ol.requireLock();
 		}
-		if (refs.size() == 0) {
+		if (refs.isEmpty()) {
 			return null;
 		}
 		FS fs = parent.getFS();
@@ -1128,9 +1128,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 7877391..068a8d8 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java
@@ -365,50 +365,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 bdbd7c9..5bb8153 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 bd81c5a..7c47ca4 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.7-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>
@@ -200,10 +200,10 @@
     <slf4j-version>1.7.2</slf4j-version>
     <log4j-version>1.2.15</log4j-version>
     <maven-javadoc-plugin-version>3.1.0</maven-javadoc-plugin-version>
-    <tycho-extras-version>1.3.0</tycho-extras-version>
+    <tycho-extras-version>1.4.0</tycho-extras-version>
     <gson-version>2.8.2</gson-version>
-    <bouncycastle-version>1.60</bouncycastle-version>
-    <spotbugs-maven-plugin-version>3.1.11</spotbugs-maven-plugin-version>
+    <bouncycastle-version>1.61</bouncycastle-version>
+    <spotbugs-maven-plugin-version>3.1.12</spotbugs-maven-plugin-version>
     <maven-project-info-reports-plugin-version>3.0.0</maven-project-info-reports-plugin-version>
     <maven-jxr-plugin-version>3.0.0</maven-jxr-plugin-version>
     <spotbugs-maven-plugin-version>3.1.12</spotbugs-maven-plugin-version>
@@ -620,7 +620,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