Merge branch 'stable-5.0' into stable-5.1

* stable-5.0:
  Prepare 4.11.10-SNAPSHOT builds
  JGit v4.11.9.201909030838-r
  Bazel: Update bazlets to the latest master revision
  Bazel: Remove FileTreeIteratorWithTimeControl from BUILD file
  BatchRefUpdate: repro racy atomic update, and fix it
  Delete unused FileTreeIteratorWithTimeControl
  Fix RacyGitTests#testRacyGitDetection
  Change RacyGitTests to create a racy git situation in a stable way
  Silence API warnings

Change-Id: I172136a031ff0730e575327cafb3527c9650a71d
Signed-off-by: Matthias Sohn <matthias.sohn@sap.com>
diff --git a/.mailmap b/.mailmap
index 96a4f257..7c9dc3d 100644
--- a/.mailmap
+++ b/.mailmap
@@ -7,3 +7,4 @@
 Shawn Pearce <spearce@spearce.org>           Shawn O. Pearce <sop@google.com>
 Shawn Pearce <spearce@spearce.org>           Shawn Pearce <sop@google.com>
 Shawn Pearce <spearce@spearce.org>           Shawn O. Pearce <spearce@spearce.org>
+Terry Parker <tparker@google.com>            tparker <tparker@google.com>
diff --git a/WORKSPACE b/WORKSPACE
index ee900b0..02de720 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -11,8 +11,8 @@
 
 maven_jar(
     name = "jsch",
-    artifact = "com.jcraft:jsch:0.1.53",
-    sha1 = "658b682d5c817b27ae795637dfec047c63d29935",
+    artifact = "com.jcraft:jsch:0.1.55",
+    sha1 = "bbd40e5aa7aa3cfad5db34965456cee738a42a50",
 )
 
 maven_jar(
@@ -29,26 +29,26 @@
 
 maven_jar(
     name = "httpclient",
-    artifact = "org.apache.httpcomponents:httpclient:4.5.2",
-    sha1 = "733db77aa8d9b2d68015189df76ab06304406e50",
+    artifact = "org.apache.httpcomponents:httpclient:4.5.6",
+    sha1 = "1afe5621985efe90a92d0fbc9be86271efbe796f",
 )
 
 maven_jar(
     name = "httpcore",
-    artifact = "org.apache.httpcomponents:httpcore:4.4.6",
-    sha1 = "e3fd8ced1f52c7574af952e2e6da0df8df08eb82",
+    artifact = "org.apache.httpcomponents:httpcore:4.4.10",
+    sha1 = "acc54d9b28bdffe4bbde89ed2e4a1e86b5285e2b",
 )
 
 maven_jar(
     name = "commons-codec",
-    artifact = "commons-codec:commons-codec:1.4",
-    sha1 = "4216af16d38465bbab0f3dff8efa14204f7a399a",
+    artifact = "commons-codec:commons-codec:1.10",
+    sha1 = "4b95f4897fa13f2cd904aee711aeafc0c5295cd8",
 )
 
 maven_jar(
     name = "commons-logging",
-    artifact = "commons-logging:commons-logging:1.1.3",
-    sha1 = "f6f66e966c70a83ffbdb6f17a0919eaf7c8aca7f",
+    artifact = "commons-logging:commons-logging:1.2",
+    sha1 = "4bfc12adfe4842bf07b657f0369c4cb522955686",
 )
 
 maven_jar(
@@ -89,8 +89,8 @@
 
 maven_jar(
     name = "junit",
-    artifact = "junit:junit:4.11",
-    sha1 = "4e031bb61df09069aeb2bffb4019e7a5034a4ee0",
+    artifact = "junit:junit:4.12",
+    sha1 = "2973d150c0dc1fefe998f834810d68f278ea58ec",
 )
 
 maven_jar(
@@ -106,51 +106,75 @@
 )
 
 maven_jar(
+    name = "mockito-core",
+    artifact = "org.mockito:mockito-core:2.23.0",
+    sha1 = "497ddb32fd5d01f9dbe99a2ec790aeb931dff1b1",
+)
+
+maven_jar(
+    name = "bytebuddy",
+    artifact = "net.bytebuddy:byte-buddy:1.9.0",
+    sha1 = "8cb0d5baae526c9df46ae17693bbba302640538b",
+)
+
+maven_jar(
+    name = "bytebuddy-agent",
+    artifact = "net.bytebuddy:byte-buddy-agent:1.9.0",
+    sha1 = "37b5703b4a6290be3fffc63ae9c6bcaaee0ff856",
+)
+
+maven_jar(
+    name = "objenesis",
+    artifact = "org.objenesis:objenesis:2.6",
+    sha1 = "639033469776fd37c08358c6b92a4761feb2af4b",
+)
+
+maven_jar(
     name = "gson",
     artifact = "com.google.code.gson:gson:2.8.2",
     sha1 = "3edcfe49d2c6053a70a2a47e4e1c2f94998a49cf",
 )
 
-JETTY_VER = "9.4.8.v20171121"
+JETTY_VER = "9.4.11.v20180605"
 
 maven_jar(
     name = "jetty-servlet",
     artifact = "org.eclipse.jetty:jetty-servlet:" + JETTY_VER,
-    sha1 = "bbbb9b5de08f468c7b9b3de6aea0b098d2c679b6",
-    src_sha1 = "6ef1e65a5af7ab2d79ba6043923affdaeaafb1e5",
+    sha1 = "66d31900fcfc70e3666f0b3335b6660635154f98",
+    src_sha1 = "930c50de49b9c258d5f0329426cbcac4d3143497",
 )
 
 maven_jar(
     name = "jetty-security",
     artifact = "org.eclipse.jetty:jetty-security:" + JETTY_VER,
-    sha1 = "e8350eec683b55494287f06740543e4be6f75425",
-    src_sha1 = "e3a879d8675fa10bc305e7a59006f1d09db04a68",
+    sha1 = "926def86d31ee07ca4b4658833dc6ee6918b8e86",
+    src_sha1 = "019bc7c2a366cbb201950f24dd64d9d9a49b6840",
 )
 
 maven_jar(
     name = "jetty-server",
     artifact = "org.eclipse.jetty:jetty-server:" + JETTY_VER,
-    sha1 = "34614bd9a29de57ef28ca31f1f2b49a412af196d",
-    src_sha1 = "fef49ac6b2bbc6d142dc0be34f68f0fb0792d52b",
+    sha1 = "58353c2f27515b007fc83ae22002feb34fc24714",
+    src_sha1 = "e7d832d74df616137755996b41bc28bb82b3bc42",
 )
 
 maven_jar(
     name = "jetty-http",
     artifact = "org.eclipse.jetty:jetty-http:" + JETTY_VER,
-    sha1 = "9879d6c4e37400bf43f0cd4b3c6e34a3ba409864",
-    src_sha1 = "5e746cd0ccb732eef0427c8c4b9dcb034e26c61b",
+    sha1 = "20c35f5336befe35b0bd5c4a63e07170fe7872d7",
+    src_sha1 = "5bc30d1f7e8c4456c22cc85999b8cafd3741bdff",
 )
 
 maven_jar(
     name = "jetty-io",
     artifact = "org.eclipse.jetty:jetty-io:" + JETTY_VER,
-    sha1 = "d3fe2dfa62f52ee91ff07cb359f63387e0e30b40",
-    src_sha1 = "41f25e1e1bba14ab0d3415488fa189f09c27a1cf",
+    sha1 = "d164de1dac18c4ca80a1b783d879c97449909c3b",
+    src_sha1 = "02c0caba292b1cb74cec1d36c6f91dc863c89b5a",
 )
 
 maven_jar(
     name = "jetty-util",
     artifact = "org.eclipse.jetty:jetty-util:" + JETTY_VER,
-    sha1 = "d6ec1a1613c7fa72aa6bf5d8c204750afbc3df3b",
-    src_sha1 = "a74ecb43f96b2e21852f6908604316d7348a16ad",
+    sha1 = "f0f25aa2f27d618a04bc7356fa247ae4a05245b3",
+    src_sha1 = "4e5c4c483cfd9804c2fc5d5751866243bbb9d740",
 )
diff --git a/lib/BUILD b/lib/BUILD
index 0e659e5..1b6f1d7 100644
--- a/lib/BUILD
+++ b/lib/BUILD
@@ -12,6 +12,7 @@
     visibility = [
         "//org.eclipse.jgit.archive:__pkg__",
         "//org.eclipse.jgit.pgm.test:__pkg__",
+        "//org.eclipse.jgit.test:__pkg__",
     ],
     exports = ["@commons-compress//jar"],
 )
@@ -51,6 +52,7 @@
     name = "httpcore",
     visibility = [
         "//org.eclipse.jgit.http.apache:__pkg__",
+        "//org.eclipse.jgit.http.test:__pkg__",
         "//org.eclipse.jgit.lfs.server:__pkg__",
         "//org.eclipse.jgit.lfs.server.test:__pkg__",
         "//org.eclipse.jgit.pgm:__pkg__",
@@ -130,9 +132,13 @@
     testonly = 1,
     visibility = ["//visibility:public"],
     exports = [
+        "@bytebuddy//jar",
+        "@bytebuddy-agent//jar",
         "@hamcrest-core//jar",
         "@hamcrest-library//jar",
         "@junit//jar",
+        "@mockito-core//jar",
+        "@objenesis//jar"
     ],
 )
 
diff --git a/org.eclipse.jgit.ant.test/META-INF/MANIFEST.MF b/org.eclipse.jgit.ant.test/META-INF/MANIFEST.MF
index 268baee..13fb43a 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.0.4.qualifier
+Bundle-Version: 5.1.11.qualifier
 Bundle-ActivationPolicy: lazy
 Bundle-RequiredExecutionEnvironment: JavaSE-1.8
 Import-Package: org.apache.tools.ant,
- org.eclipse.jgit.ant.tasks;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.junit;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.lib;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.util;version="[5.0.4,5.1.0)",
+ org.eclipse.jgit.ant.tasks;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.junit;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.lib;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.util;version="[5.1.11,5.2.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 4bfbc9e..d1374e5 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.0.4-SNAPSHOT</version>
+    <version>5.1.11-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.ant.test</artifactId>
@@ -105,7 +105,7 @@
       <plugin>
         <artifactId>maven-surefire-plugin</artifactId>
         <configuration>
-          <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.test/src/org/eclipse/jgit/ant/tasks/GitCloneTaskTest.java b/org.eclipse.jgit.ant.test/src/org/eclipse/jgit/ant/tasks/GitCloneTaskTest.java
index 3ce0663..8043d2b 100644
--- a/org.eclipse.jgit.ant.test/src/org/eclipse/jgit/ant/tasks/GitCloneTaskTest.java
+++ b/org.eclipse.jgit.ant.test/src/org/eclipse/jgit/ant/tasks/GitCloneTaskTest.java
@@ -65,12 +65,13 @@ public class GitCloneTaskTest extends LocalDiskRepositoryTestCase {
 
 	@Before
 	public void before() throws IOException {
+		dest = createTempFile();
+		FS.getFileStoreAttributes(dest.toPath().getParent());
 		project = new Project();
 		project.init();
 		enableLogging();
 		project.addTaskDefinition("git-clone", GitCloneTask.class);
 		task = (GitCloneTask) project.createTask("git-clone");
-		dest = createTempFile();
 		task.setDest(dest);
 	}
 
diff --git a/org.eclipse.jgit.ant/META-INF/MANIFEST.MF b/org.eclipse.jgit.ant/META-INF/MANIFEST.MF
index 6df3091..1acbebd 100644
--- a/org.eclipse.jgit.ant/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.ant/META-INF/MANIFEST.MF
@@ -3,11 +3,11 @@
 Bundle-Name: %Bundle-Name
 Automatic-Module-Name: org.eclipse.jgit.ant
 Bundle-SymbolicName: org.eclipse.jgit.ant
-Bundle-Version: 5.0.4.qualifier
+Bundle-Version: 5.1.11.qualifier
 Bundle-RequiredExecutionEnvironment: JavaSE-1.8
 Import-Package: org.apache.tools.ant,
-  org.eclipse.jgit.storage.file;version="[5.0.4,5.1.0)"
+  org.eclipse.jgit.storage.file;version="[5.1.11,5.2.0)"
 Bundle-Localization: plugin
 Bundle-Vendor: %Provider-Name
-Export-Package: org.eclipse.jgit.ant.tasks;version="5.0.4";
+Export-Package: org.eclipse.jgit.ant.tasks;version="5.1.11";
  uses:="org.apache.tools.ant.types,org.apache.tools.ant"
diff --git a/org.eclipse.jgit.ant/pom.xml b/org.eclipse.jgit.ant/pom.xml
index a288360..24fc316 100644
--- a/org.eclipse.jgit.ant/pom.xml
+++ b/org.eclipse.jgit.ant/pom.xml
@@ -48,7 +48,7 @@
 	<parent>
 		<groupId>org.eclipse.jgit</groupId>
 		<artifactId>org.eclipse.jgit-parent</artifactId>
-		<version>5.0.4-SNAPSHOT</version>
+		<version>5.1.11-SNAPSHOT</version>
 	</parent>
 
 	<artifactId>org.eclipse.jgit.ant</artifactId>
diff --git a/org.eclipse.jgit.archive/META-INF/MANIFEST.MF b/org.eclipse.jgit.archive/META-INF/MANIFEST.MF
index 3285ee4..5bc5291 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.0.4.qualifier
+Bundle-Version: 5.1.11.qualifier
 Bundle-Vendor: %provider_name
 Bundle-Localization: plugin
 Bundle-RequiredExecutionEnvironment: JavaSE-1.8
@@ -13,15 +13,15 @@
  org.apache.commons.compress.compressors.bzip2;version="[1.4,2.0)",
  org.apache.commons.compress.compressors.gzip;version="[1.4,2.0)",
  org.apache.commons.compress.compressors.xz;version="[1.4,2.0)",
- org.eclipse.jgit.api;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.lib;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.nls;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.revwalk;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.util;version="[5.0.4,5.1.0)",
+ org.eclipse.jgit.api;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.lib;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.nls;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.revwalk;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.util;version="[5.1.11,5.2.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.0.4";
+Export-Package: org.eclipse.jgit.archive;version="5.1.11";
   uses:="org.eclipse.jgit.lib,
    org.eclipse.jgit.api,
    org.apache.commons.compress.archivers,
diff --git a/org.eclipse.jgit.archive/META-INF/SOURCE-MANIFEST.MF b/org.eclipse.jgit.archive/META-INF/SOURCE-MANIFEST.MF
index f85551e..2eeebd8 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.0.4.qualifier
-Eclipse-SourceBundle: org.eclipse.jgit.archive;version="5.0.4.qualifier";roots="."
+Bundle-Version: 5.1.11.qualifier
+Eclipse-SourceBundle: org.eclipse.jgit.archive;version="5.1.11.qualifier";roots="."
diff --git a/org.eclipse.jgit.archive/pom.xml b/org.eclipse.jgit.archive/pom.xml
index 4bfa176..93ca0dd 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.0.4-SNAPSHOT</version>
+    <version>5.1.11-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.archive</artifactId>
diff --git a/org.eclipse.jgit.archive/src/org/eclipse/jgit/archive/TarFormat.java b/org.eclipse.jgit.archive/src/org/eclipse/jgit/archive/TarFormat.java
index 9ed60d9..aee80d8 100644
--- a/org.eclipse.jgit.archive/src/org/eclipse/jgit/archive/TarFormat.java
+++ b/org.eclipse.jgit.archive/src/org/eclipse/jgit/archive/TarFormat.java
@@ -42,7 +42,7 @@
  */
 package org.eclipse.jgit.archive;
 
-import static org.eclipse.jgit.lib.Constants.CHARACTER_ENCODING;
+import static java.nio.charset.StandardCharsets.UTF_8;
 
 import java.io.IOException;
 import java.io.OutputStream;
@@ -85,7 +85,7 @@ public ArchiveOutputStream createArchiveOutputStream(OutputStream s)
 	public ArchiveOutputStream createArchiveOutputStream(OutputStream s,
 			Map<String, Object> o) throws IOException {
 		TarArchiveOutputStream out = new TarArchiveOutputStream(s,
-				CHARACTER_ENCODING);
+				UTF_8.name());
 		out.setLongFileMode(TarArchiveOutputStream.LONGFILE_POSIX);
 		out.setBigNumberMode(TarArchiveOutputStream.BIGNUMBER_POSIX);
 		return applyFormatOptions(out, o);
@@ -99,8 +99,7 @@ public void putEntry(ArchiveOutputStream out,
 		if (mode == FileMode.SYMLINK) {
 			final TarArchiveEntry entry = new TarArchiveEntry(
 					path, TarConstants.LF_SYMLINK);
-			entry.setLinkName(new String(
-					loader.getCachedBytes(100), CHARACTER_ENCODING));
+			entry.setLinkName(new String(loader.getCachedBytes(100), UTF_8));
 			out.putArchiveEntry(entry);
 			out.closeArchiveEntry();
 			return;
diff --git a/org.eclipse.jgit.http.apache/.settings/.api_filters b/org.eclipse.jgit.http.apache/.settings/.api_filters
deleted file mode 100644
index bc002ad..0000000
--- a/org.eclipse.jgit.http.apache/.settings/.api_filters
+++ /dev/null
@@ -1,11 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<component id="org.eclipse.jgit.http.apache" version="2">
-    <resource path="META-INF/MANIFEST.MF">
-        <filter id="925892614">
-            <message_arguments>
-                <message_argument value="5.0.4"/>
-                <message_argument value="4.11.0"/>
-            </message_arguments>
-        </filter>
-    </resource>
-</component>
diff --git a/org.eclipse.jgit.http.apache/META-INF/MANIFEST.MF b/org.eclipse.jgit.http.apache/META-INF/MANIFEST.MF
index 69fb47c..8297618 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.0.4.qualifier
+Bundle-Version: 5.1.11.qualifier
 Bundle-RequiredExecutionEnvironment: JavaSE-1.8
 Bundle-Localization: plugin
 Bundle-Vendor: %Provider-Name
@@ -23,10 +23,10 @@
  org.apache.http.impl.client;version="[4.3.0,5.0.0)",
  org.apache.http.impl.conn;version="[4.3.0,5.0.0)",
  org.apache.http.params;version="[4.3.0,5.0.0)",
- org.eclipse.jgit.nls;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.transport.http;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.util;version="[5.0.4,5.1.0)"
-Export-Package: org.eclipse.jgit.transport.http.apache;version="5.0.4";
+ org.eclipse.jgit.nls;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.transport.http;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.util;version="[5.1.11,5.2.0)"
+Export-Package: org.eclipse.jgit.transport.http.apache;version="5.1.11";
   uses:="org.apache.http.client,
    org.eclipse.jgit.transport.http,
    org.apache.http.entity,
diff --git a/org.eclipse.jgit.http.apache/pom.xml b/org.eclipse.jgit.http.apache/pom.xml
index bc71faa..02daded 100644
--- a/org.eclipse.jgit.http.apache/pom.xml
+++ b/org.eclipse.jgit.http.apache/pom.xml
@@ -48,7 +48,7 @@
 	<parent>
 		<groupId>org.eclipse.jgit</groupId>
 		<artifactId>org.eclipse.jgit-parent</artifactId>
-		<version>5.0.4-SNAPSHOT</version>
+		<version>5.1.11-SNAPSHOT</version>
 	</parent>
 
 	<artifactId>org.eclipse.jgit.http.apache</artifactId>
diff --git a/org.eclipse.jgit.http.apache/src/org/eclipse/jgit/transport/http/apache/HttpClientConnection.java b/org.eclipse.jgit.http.apache/src/org/eclipse/jgit/transport/http/apache/HttpClientConnection.java
index 7f1fecb..77c5dc0 100644
--- a/org.eclipse.jgit.http.apache/src/org/eclipse/jgit/transport/http/apache/HttpClientConnection.java
+++ b/org.eclipse.jgit.http.apache/src/org/eclipse/jgit/transport/http/apache/HttpClientConnection.java
@@ -272,10 +272,14 @@ private void execute() throws IOException, ClientProtocolException {
 	public Map<String, List<String>> getHeaderFields() {
 		Map<String, List<String>> ret = new HashMap<>();
 		for (Header hdr : resp.getAllHeaders()) {
-			List<String> list = new LinkedList<>();
-			for (HeaderElement hdrElem : hdr.getElements())
+			List<String> list = ret.get(hdr.getName());
+			if (list == null) {
+				list = new LinkedList<>();
+				ret.put(hdr.getName(), list);
+			}
+			for (HeaderElement hdrElem : hdr.getElements()) {
 				list.add(hdrElem.toString());
-			ret.put(hdr.getName(), list);
+			}
 		}
 		return ret;
 	}
diff --git a/org.eclipse.jgit.http.server/META-INF/MANIFEST.MF b/org.eclipse.jgit.http.server/META-INF/MANIFEST.MF
index c97b927..5a66245 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.0.4.qualifier
+Bundle-Version: 5.1.11.qualifier
 Bundle-Localization: plugin
 Bundle-Vendor: %provider_name
-Export-Package: org.eclipse.jgit.http.server;version="5.0.4",
- org.eclipse.jgit.http.server.glue;version="5.0.4";
+Export-Package: org.eclipse.jgit.http.server;version="5.1.11",
+ org.eclipse.jgit.http.server.glue;version="5.1.11";
   uses:="javax.servlet,javax.servlet.http",
- org.eclipse.jgit.http.server.resolver;version="5.0.4";
+ org.eclipse.jgit.http.server.resolver;version="5.1.11";
   uses:="org.eclipse.jgit.transport.resolver,
    org.eclipse.jgit.lib,
    org.eclipse.jgit.transport,
@@ -18,12 +18,12 @@
 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.0.4,5.1.0)",
- org.eclipse.jgit.internal.storage.dfs;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.internal.storage.file;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.lib;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.nls;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.revwalk;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.transport;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.transport.resolver;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.util;version="[5.0.4,5.1.0)"
+ org.eclipse.jgit.errors;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.internal.storage.dfs;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.internal.storage.file;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.lib;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.nls;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.revwalk;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.transport;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.transport.resolver;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.util;version="[5.1.11,5.2.0)"
diff --git a/org.eclipse.jgit.http.server/pom.xml b/org.eclipse.jgit.http.server/pom.xml
index 6f70081..4bf1e66 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.0.4-SNAPSHOT</version>
+    <version>5.1.11-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.http.server</artifactId>
diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/FileSender.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/FileSender.java
index 05510a0..946fb15 100644
--- a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/FileSender.java
+++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/FileSender.java
@@ -58,12 +58,14 @@
 import java.io.OutputStream;
 import java.io.RandomAccessFile;
 import java.text.MessageFormat;
+import java.time.Instant;
 import java.util.Enumeration;
 
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
 import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.util.FS;
 
 /**
  * Dumps a file over HTTP GET (or its information via HEAD).
@@ -76,7 +78,7 @@ final class FileSender {
 
 	private final RandomAccessFile source;
 
-	private final long lastModified;
+	private final Instant lastModified;
 
 	private final long fileLen;
 
@@ -89,7 +91,7 @@ final class FileSender {
 		this.source = new RandomAccessFile(path, "r");
 
 		try {
-			this.lastModified = path.lastModified();
+			this.lastModified = FS.DETECTED.lastModifiedInstant(path);
 			this.fileLen = source.getChannel().size();
 			this.end = fileLen;
 		} catch (IOException e) {
@@ -114,7 +116,7 @@ void close() {
 		}
 	}
 
-	long getLastModified() {
+	Instant getLastModified() {
 		return lastModified;
 	}
 
diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/InfoRefsServlet.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/InfoRefsServlet.java
index 0f4633b..b084b0d 100644
--- a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/InfoRefsServlet.java
+++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/InfoRefsServlet.java
@@ -43,6 +43,7 @@
 
 package org.eclipse.jgit.http.server;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.eclipse.jgit.http.server.ServletUtils.getRepository;
 
 import java.io.IOException;
@@ -68,12 +69,12 @@ public void doGet(final HttpServletRequest req,
 		// Assume a dumb client and send back the dumb client
 		// version of the info/refs file.
 		rsp.setContentType(HttpSupport.TEXT_PLAIN);
-		rsp.setCharacterEncoding(Constants.CHARACTER_ENCODING);
+		rsp.setCharacterEncoding(UTF_8.name());
 
 		final Repository db = getRepository(req);
 		try (OutputStreamWriter out = new OutputStreamWriter(
 				new SmartOutputStream(req, rsp, true),
-				Constants.CHARSET)) {
+				UTF_8)) {
 			final RefAdvertiser adv = new RefAdvertiser() {
 				@Override
 				protected void writeOne(CharSequence line)
diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/ObjectFileServlet.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/ObjectFileServlet.java
index 62f075c..5a27be6 100644
--- a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/ObjectFileServlet.java
+++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/ObjectFileServlet.java
@@ -54,6 +54,7 @@
 import java.io.File;
 import java.io.FileNotFoundException;
 import java.io.IOException;
+import java.time.Instant;
 
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServlet;
@@ -76,7 +77,9 @@ static class Loose extends ObjectFileServlet {
 
 		@Override
 		String etag(FileSender sender) throws IOException {
-			return Long.toHexString(sender.getLastModified());
+			Instant lastModified = sender.getLastModified();
+			return Long.toHexString(lastModified.getEpochSecond())
+					+ Long.toHexString(lastModified.getNano());
 		}
 	}
 
@@ -145,7 +148,9 @@ private void serve(final HttpServletRequest req,
 
 		try {
 			final String etag = etag(sender);
-			final long lastModified = (sender.getLastModified() / 1000) * 1000;
+			// HTTP header Last-Modified header has a resolution of 1 sec, see
+			// https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.29
+			final long lastModified = sender.getLastModified().getEpochSecond();
 
 			String ifNoneMatch = req.getHeader(HDR_IF_NONE_MATCH);
 			if (etag != null && etag.equals(ifNoneMatch)) {
diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/ServletUtils.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/ServletUtils.java
index 9601c8c..b6d73b5 100644
--- a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/ServletUtils.java
+++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/ServletUtils.java
@@ -43,6 +43,7 @@
 
 package org.eclipse.jgit.http.server;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.eclipse.jgit.util.HttpSupport.ENCODING_GZIP;
 import static org.eclipse.jgit.util.HttpSupport.ENCODING_X_GZIP;
 import static org.eclipse.jgit.util.HttpSupport.HDR_ACCEPT_ENCODING;
@@ -191,9 +192,9 @@ public static void consumeRequestBody(InputStream in) {
 	public static void sendPlainText(final String content,
 			final HttpServletRequest req, final HttpServletResponse rsp)
 			throws IOException {
-		final byte[] raw = content.getBytes(Constants.CHARACTER_ENCODING);
+		final byte[] raw = content.getBytes(UTF_8);
 		rsp.setContentType(TEXT_PLAIN);
-		rsp.setCharacterEncoding(Constants.CHARACTER_ENCODING);
+		rsp.setCharacterEncoding(UTF_8.name());
 		send(raw, req, rsp);
 	}
 
diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/SmartServiceInfoRefs.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/SmartServiceInfoRefs.java
index 195dff9..75611ee 100644
--- a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/SmartServiceInfoRefs.java
+++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/SmartServiceInfoRefs.java
@@ -77,7 +77,7 @@ abstract class SmartServiceInfoRefs implements Filter {
 
 	SmartServiceInfoRefs(String service, List<Filter> filters) {
 		this.svc = service;
-		this.filters = filters.toArray(new Filter[filters.size()]);
+		this.filters = filters.toArray(new Filter[0]);
 	}
 
 	/** {@inheritDoc} */
diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/ServletBinderImpl.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/ServletBinderImpl.java
index 18650eb..f167497 100644
--- a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/ServletBinderImpl.java
+++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/ServletBinderImpl.java
@@ -98,7 +98,7 @@ protected HttpServlet getServlet() {
 	 * @return the configured filters; zero-length array if none.
 	 */
 	protected Filter[] getFilters() {
-		return filters.toArray(new Filter[filters.size()]);
+		return filters.toArray(new Filter[0]);
 	}
 
 	/** @return the pipeline that matches and executes this chain. */
diff --git a/org.eclipse.jgit.http.test/BUILD b/org.eclipse.jgit.http.test/BUILD
index 85a2242..dcffa06 100644
--- a/org.eclipse.jgit.http.test/BUILD
+++ b/org.eclipse.jgit.http.test/BUILD
@@ -10,6 +10,7 @@
     deps = [
         ":helpers",
         "//lib:commons-logging",
+        "//lib:httpcore",
         "//lib:jetty-http",
         "//lib:jetty-io",
         "//lib:jetty-security",
diff --git a/org.eclipse.jgit.http.test/META-INF/MANIFEST.MF b/org.eclipse.jgit.http.test/META-INF/MANIFEST.MF
index fed632d..c2e7c7e 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.0.4.qualifier
+Bundle-Version: 5.1.11.qualifier
 Bundle-Vendor: %provider_name
 Bundle-Localization: plugin
 Bundle-RequiredExecutionEnvironment: JavaSE-1.8
@@ -11,6 +11,9 @@
  javax.servlet.http;version="[2.5.0,3.2.0)",
  org.apache.commons.codec;version="[1.6.0,2.0.0)",
  org.apache.commons.codec.binary;version="[1.6.0,2.0.0)",
+ org.apache.http;version="[4.3.0,5.0.0)",
+ org.apache.http.client;version="[4.3.0,5.0.0)",
+ org.apache.http.message;version="[4.3.0,5.0.0)",
  org.eclipse.jetty.continuation;version="[9.4.5,10.0.0)",
  org.eclipse.jetty.http;version="[9.4.5,10.0.0)",
  org.eclipse.jetty.io;version="[9.4.5,10.0.0)",
@@ -25,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.0.4,5.1.0)",
- org.eclipse.jgit.http.server;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.http.server.glue;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.http.server.resolver;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.internal;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.internal.storage.dfs;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.internal.storage.file;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.internal.storage.reftable;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.junit;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.junit.http;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.lib;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.nls;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.revwalk;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.storage.file;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.transport;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.transport.http;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.transport.http.apache;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.transport.resolver;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.util;version="[5.0.4,5.1.0)",
+ org.eclipse.jgit.errors;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.http.server;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.http.server.glue;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.http.server.resolver;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.internal;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.internal.storage.dfs;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.internal.storage.file;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.internal.storage.reftable;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.junit;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.junit.http;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.lib;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.nls;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.revwalk;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.storage.file;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.transport;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.transport.http;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.transport.http.apache;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.transport.resolver;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.util;version="[5.1.11,5.2.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 87e2f4c..230f7e2 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.0.4-SNAPSHOT</version>
+    <version>5.1.11-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.http.test</artifactId>
@@ -139,7 +139,7 @@
       <plugin>
         <artifactId>maven-surefire-plugin</artifactId>
         <configuration>
-          <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/HttpClientTests.java b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/HttpClientTests.java
index ef059bf..53626b1 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
@@ -175,7 +175,9 @@ public void testRepositoryNotFound_Dumb() throws Exception {
 			} catch (NoRemoteRepositoryException err) {
 				String exp = uri + ": " + uri
 						+ "/info/refs?service=git-upload-pack not found";
-				assertEquals(exp, err.getMessage());
+				assertNotNull(err.getMessage());
+				assertTrue("Unexpected error message",
+						err.getMessage().startsWith(exp));
 			}
 		}
 	}
@@ -191,7 +193,9 @@ public void testRepositoryNotFound_Smart() throws Exception {
 			} catch (NoRemoteRepositoryException err) {
 				String exp = uri + ": " + uri
 						+ "/info/refs?service=git-upload-pack not found";
-				assertEquals(exp, err.getMessage());
+				assertNotNull(err.getMessage());
+				assertTrue("Unexpected error message",
+						err.getMessage().startsWith(exp));
 			}
 		}
 	}
@@ -363,7 +367,7 @@ public void testHttpClientWantsV2ButServerNotConfigured() throws Exception {
 		c.setRequestMethod("GET");
 		c.setRequestProperty("Git-Protocol", "version=2");
 		c.connect();
-		assertThat(c.getResponseCode(), is(200));
+		assertEquals(200, c.getResponseCode());
 
 		PacketLineIn pckIn = new PacketLineIn(c.getInputStream());
 
@@ -384,7 +388,7 @@ public void testV2HttpFirstResponse() throws Exception {
 		c.setRequestMethod("GET");
 		c.setRequestProperty("Git-Protocol", "version=2");
 		c.connect();
-		assertThat(c.getResponseCode(), is(200));
+		assertEquals(200, c.getResponseCode());
 
 		PacketLineIn pckIn = new PacketLineIn(c.getInputStream());
 		assertThat(pckIn.readString(), is("version 2"));
@@ -415,12 +419,12 @@ public void testV2HttpSubsequentResponse() throws Exception {
 		// properly. Tests for other commands go in
 		// UploadPackTest.java.
 
-		OutputStream os = c.getOutputStream();
-		PacketLineOut pckOut = new PacketLineOut(os);
-		pckOut.writeString("command=ls-refs");
-		pckOut.writeDelim();
-		pckOut.end();
-		os.close();
+		try (OutputStream os = c.getOutputStream()) {
+			PacketLineOut pckOut = new PacketLineOut(os);
+			pckOut.writeString("command=ls-refs");
+			pckOut.writeDelim();
+			pckOut.end();
+		}
 
 		PacketLineIn pckIn = new PacketLineIn(c.getInputStream());
 
@@ -430,6 +434,6 @@ public void testV2HttpSubsequentResponse() throws Exception {
 			assertTrue(s.matches("[0-9a-f]{40} [A-Za-z/]*"));
 		}
 
-		assertThat(c.getResponseCode(), is(200));
+		assertEquals(200, c.getResponseCode());
 	}
 }
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 65210d1..78bf778 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
@@ -43,7 +43,7 @@
 
 package org.eclipse.jgit.http.test;
 
-import static org.eclipse.jgit.lib.Constants.CHARSET;
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.eclipse.jgit.util.HttpSupport.HDR_CONTENT_ENCODING;
 import static org.eclipse.jgit.util.HttpSupport.HDR_CONTENT_LENGTH;
 import static org.eclipse.jgit.util.HttpSupport.HDR_CONTENT_TYPE;
@@ -107,7 +107,6 @@
 import org.eclipse.jgit.revwalk.RevBlob;
 import org.eclipse.jgit.revwalk.RevCommit;
 import org.eclipse.jgit.revwalk.RevWalk;
-import org.eclipse.jgit.storage.file.FileBasedConfig;
 import org.eclipse.jgit.transport.AbstractAdvertiseRefsHook;
 import org.eclipse.jgit.transport.AdvertiseRefsHook;
 import org.eclipse.jgit.transport.CredentialItem;
@@ -127,7 +126,6 @@
 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.FS;
 import org.eclipse.jgit.util.HttpSupport;
 import org.eclipse.jgit.util.SystemReader;
 import org.hamcrest.Matchers;
@@ -298,7 +296,7 @@ public void doFilter(ServletRequest request,
 					throws IOException, ServletException {
 				final HttpServletResponse r = (HttpServletResponse) response;
 				r.setContentType("text/plain");
-				r.setCharacterEncoding(Constants.CHARACTER_ENCODING);
+				r.setCharacterEncoding(UTF_8.name());
 				try (PrintWriter w = r.getWriter()) {
 					w.print("OK");
 				}
@@ -652,8 +650,8 @@ public void testInitialClone_RedirectMultiple() throws Exception {
 
 	@Test
 	public void testInitialClone_RedirectMax() throws Exception {
-		FileBasedConfig userConfig = SystemReader.getInstance()
-				.openUserConfig(null, FS.DETECTED);
+		StoredConfig userConfig = SystemReader.getInstance()
+				.getUserConfig();
 		userConfig.setInt("http", null, "maxRedirects", 4);
 		userConfig.save();
 		initialClone_Redirect(4, 302);
@@ -661,8 +659,8 @@ public void testInitialClone_RedirectMax() throws Exception {
 
 	@Test
 	public void testInitialClone_RedirectTooOften() throws Exception {
-		FileBasedConfig userConfig = SystemReader.getInstance()
-				.openUserConfig(null, FS.DETECTED);
+		StoredConfig userConfig = SystemReader.getInstance()
+				.getUserConfig();
 		userConfig.setInt("http", null, "maxRedirects", 3);
 		userConfig.save();
 		Repository dst = createBareRepository();
@@ -701,8 +699,8 @@ public void testInitialClone_RedirectLoop() throws Exception {
 
 	@Test
 	public void testInitialClone_RedirectOnPostAllowed() throws Exception {
-		FileBasedConfig userConfig = SystemReader.getInstance()
-				.openUserConfig(null, FS.DETECTED);
+		StoredConfig userConfig = SystemReader.getInstance()
+				.getUserConfig();
 		userConfig.setString("http", null, "followRedirects", "true");
 		userConfig.save();
 		Repository dst = createBareRepository();
@@ -764,8 +762,8 @@ public void testInitialClone_RedirectOnPostForbidden() throws Exception {
 
 	@Test
 	public void testInitialClone_RedirectForbidden() throws Exception {
-		FileBasedConfig userConfig = SystemReader.getInstance()
-				.openUserConfig(null, FS.DETECTED);
+		StoredConfig userConfig = SystemReader.getInstance()
+				.getUserConfig();
 		userConfig.setString("http", null, "followRedirects", "false");
 		userConfig.save();
 
@@ -1163,7 +1161,7 @@ public void testInitialClone_BrokenServer() throws Exception {
 	public void testInvalidWant() throws Exception {
 		@SuppressWarnings("resource")
 		ObjectId id = new ObjectInserter.Formatter().idFor(Constants.OBJ_BLOB,
-				"testInvalidWant".getBytes(CHARSET));
+				"testInvalidWant".getBytes(UTF_8));
 
 		Repository dst = createBareRepository();
 		try (Transport t = Transport.open(dst, remoteURI);
diff --git a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/transport/http/apache/HttpClientConnectionTest.java b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/transport/http/apache/HttpClientConnectionTest.java
new file mode 100644
index 0000000..8a0d59c
--- /dev/null
+++ b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/transport/http/apache/HttpClientConnectionTest.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2018 Gabriel Couto <gmcouto@gmail.com>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ *   copyright notice, this list of conditions and the following
+ *   disclaimer in the documentation and/or other materials provided
+ *   with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ *   names of its contributors may be used to endorse or promote
+ *   products derived from this software without specific prior
+ *   written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.eclipse.jgit.transport.http.apache;
+
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpResponse;
+import org.apache.http.ProtocolVersion;
+import org.apache.http.StatusLine;
+import org.apache.http.message.AbstractHttpMessage;
+import org.junit.Test;
+
+import java.net.MalformedURLException;
+import java.util.List;
+import java.util.Locale;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+public class HttpClientConnectionTest {
+	@Test
+	public void testGetHeaderFieldsAllowMultipleValues()
+			throws MalformedURLException {
+		HttpResponse responseMock = new HttpResponseMock();
+		String headerField = "WWW-Authenticate";
+		responseMock.addHeader(headerField, "Basic");
+		responseMock.addHeader(headerField, "Digest");
+		responseMock.addHeader(headerField, "NTLM");
+		HttpClientConnection connection = new HttpClientConnection(
+				"http://0.0.0.0/");
+		connection.resp = responseMock;
+		List<String> headerValues = connection.getHeaderFields()
+				.get(headerField);
+		assertEquals(3, headerValues.size());
+		assertTrue(headerValues.contains("Basic"));
+		assertTrue(headerValues.contains("Digest"));
+		assertTrue(headerValues.contains("NTLM"));
+	}
+
+	private static class HttpResponseMock extends AbstractHttpMessage
+			implements HttpResponse {
+		@Override
+		public StatusLine getStatusLine() {
+			throw new UnsupportedOperationException();
+		}
+
+		@Override
+		public void setStatusLine(StatusLine statusLine) {
+			throw new UnsupportedOperationException();
+		}
+
+		@Override
+		public void setStatusLine(ProtocolVersion protocolVersion, int i) {
+			throw new UnsupportedOperationException();
+		}
+
+		@Override
+		public void setStatusLine(ProtocolVersion protocolVersion, int i,
+				String s) {
+			throw new UnsupportedOperationException();
+		}
+
+		@Override
+		public void setStatusCode(int i) throws IllegalStateException {
+			throw new UnsupportedOperationException();
+		}
+
+		@Override
+		public void setReasonPhrase(String s) throws IllegalStateException {
+			throw new UnsupportedOperationException();
+		}
+
+		@Override
+		public HttpEntity getEntity() {
+			throw new UnsupportedOperationException();
+		}
+
+		@Override
+		public void setEntity(HttpEntity httpEntity) {
+			throw new UnsupportedOperationException();
+		}
+
+		@Override
+		public Locale getLocale() {
+			throw new UnsupportedOperationException();
+		}
+
+		@Override
+		public void setLocale(Locale locale) {
+			throw new UnsupportedOperationException();
+		}
+
+		@Override
+		public ProtocolVersion getProtocolVersion() {
+			throw new UnsupportedOperationException();
+		}
+	}
+}
diff --git a/org.eclipse.jgit.junit.http/META-INF/MANIFEST.MF b/org.eclipse.jgit.junit.http/META-INF/MANIFEST.MF
index a68d152..8da6c7a 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.0.4.qualifier
+Bundle-Version: 5.1.11.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.0.4,5.1.0)",
- org.eclipse.jgit.http.server;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.internal.storage.file;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.junit;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.lib;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.revwalk;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.transport;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.transport.resolver;version="[5.0.4,5.1.0)",
+ org.eclipse.jgit.errors;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.http.server;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.internal.storage.file;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.junit;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.lib;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.revwalk;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.transport;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.transport.resolver;version="[5.1.11,5.2.0)",
  org.junit;version="[4.12,5.0.0)"
-Export-Package: org.eclipse.jgit.junit.http;version="5.0.4";
+Export-Package: org.eclipse.jgit.junit.http;version="5.1.11";
   uses:="org.eclipse.jgit.transport,
    org.eclipse.jgit.junit,
    javax.servlet.http,
diff --git a/org.eclipse.jgit.junit.http/pom.xml b/org.eclipse.jgit.junit.http/pom.xml
index eb7d02f..c57cdfa 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.0.4-SNAPSHOT</version>
+    <version>5.1.11-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.junit.http</artifactId>
diff --git a/org.eclipse.jgit.junit.http/src/org/eclipse/jgit/junit/http/AppServer.java b/org.eclipse.jgit.junit.http/src/org/eclipse/jgit/junit/http/AppServer.java
index 9309fe7..9aef086 100644
--- a/org.eclipse.jgit.junit.http/src/org/eclipse/jgit/junit/http/AppServer.java
+++ b/org.eclipse.jgit.junit.http/src/org/eclipse/jgit/junit/http/AppServer.java
@@ -349,7 +349,7 @@ private void auth(ServletContextHandler ctx, Authenticator authType,
 		sec.setAuthenticator(authType);
 		sec.setLoginService(users);
 		sec.setConstraintMappings(
-				mappings.toArray(new ConstraintMapping[mappings.size()]));
+				mappings.toArray(new ConstraintMapping[0]));
 		sec.setHandler(ctx);
 
 		contexts.removeHandler(ctx);
diff --git a/org.eclipse.jgit.junit/.settings/org.eclipse.jdt.core.prefs b/org.eclipse.jgit.junit/.settings/org.eclipse.jdt.core.prefs
index 794592d..6251b76 100644
--- a/org.eclipse.jgit.junit/.settings/org.eclipse.jdt.core.prefs
+++ b/org.eclipse.jgit.junit/.settings/org.eclipse.jdt.core.prefs
@@ -1,10 +1,13 @@
 eclipse.preferences.version=1
-org.eclipse.jdt.core.compiler.annotation.inheritNullAnnotations=disabled
+org.eclipse.jdt.core.compiler.annotation.inheritNullAnnotations=enabled
 org.eclipse.jdt.core.compiler.annotation.missingNonNullByDefaultAnnotation=ignore
-org.eclipse.jdt.core.compiler.annotation.nonnull=org.eclipse.jdt.annotation.NonNull
-org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=org.eclipse.jdt.annotation.NonNullByDefault
-org.eclipse.jdt.core.compiler.annotation.nullable=org.eclipse.jdt.annotation.Nullable
-org.eclipse.jdt.core.compiler.annotation.nullanalysis=disabled
+org.eclipse.jdt.core.compiler.annotation.nonnull=org.eclipse.jgit.annotations.NonNull
+org.eclipse.jdt.core.compiler.annotation.nonnull.secondary=
+org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=org.eclipse.jgit.annotations.NonNullByDefault
+org.eclipse.jdt.core.compiler.annotation.nonnullbydefault.secondary=
+org.eclipse.jdt.core.compiler.annotation.nullable=org.eclipse.jgit.annotations.Nullable
+org.eclipse.jdt.core.compiler.annotation.nullable.secondary=
+org.eclipse.jdt.core.compiler.annotation.nullanalysis=enabled
 org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
 org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate
 org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
@@ -14,6 +17,7 @@
 org.eclipse.jdt.core.compiler.debug.localVariable=generate
 org.eclipse.jdt.core.compiler.debug.sourceFile=generate
 org.eclipse.jdt.core.compiler.doc.comment.support=enabled
+org.eclipse.jdt.core.compiler.problem.APILeak=warning
 org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning
 org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
 org.eclipse.jdt.core.compiler.problem.autoboxing=warning
@@ -48,7 +52,7 @@
 org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=ignore
 org.eclipse.jdt.core.compiler.problem.missingEnumCaseDespiteDefault=disabled
 org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=error
-org.eclipse.jdt.core.compiler.problem.missingJavadocComments=ignore
+org.eclipse.jdt.core.compiler.problem.missingJavadocComments=error
 org.eclipse.jdt.core.compiler.problem.missingJavadocCommentsOverriding=disabled
 org.eclipse.jdt.core.compiler.problem.missingJavadocCommentsVisibility=protected
 org.eclipse.jdt.core.compiler.problem.missingJavadocTagDescription=return_tag
@@ -64,14 +68,16 @@
 org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=error
 org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=ignore
 org.eclipse.jdt.core.compiler.problem.nonnullParameterAnnotationDropped=warning
+org.eclipse.jdt.core.compiler.problem.nonnullTypeVariableFromLegacyInvocation=warning
 org.eclipse.jdt.core.compiler.problem.nullAnnotationInferenceConflict=error
 org.eclipse.jdt.core.compiler.problem.nullReference=error
 org.eclipse.jdt.core.compiler.problem.nullSpecViolation=error
-org.eclipse.jdt.core.compiler.problem.nullUncheckedConversion=warning
+org.eclipse.jdt.core.compiler.problem.nullUncheckedConversion=ignore
 org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning
 org.eclipse.jdt.core.compiler.problem.parameterAssignment=ignore
+org.eclipse.jdt.core.compiler.problem.pessimisticNullAnalysisForFreeTypeVariables=warning
 org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=error
-org.eclipse.jdt.core.compiler.problem.potentialNullReference=warning
+org.eclipse.jdt.core.compiler.problem.potentialNullReference=error
 org.eclipse.jdt.core.compiler.problem.potentiallyUnclosedCloseable=ignore
 org.eclipse.jdt.core.compiler.problem.rawTypeReference=ignore
 org.eclipse.jdt.core.compiler.problem.redundantNullAnnotation=warning
@@ -86,16 +92,21 @@
 org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled
 org.eclipse.jdt.core.compiler.problem.syntacticNullAnalysisForFields=disabled
 org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore
+org.eclipse.jdt.core.compiler.problem.terminalDeprecation=warning
 org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning
 org.eclipse.jdt.core.compiler.problem.unavoidableGenericTypeProblems=enabled
 org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning
 org.eclipse.jdt.core.compiler.problem.unclosedCloseable=warning
 org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=warning
 org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning
+org.eclipse.jdt.core.compiler.problem.unlikelyCollectionMethodArgumentType=warning
+org.eclipse.jdt.core.compiler.problem.unlikelyCollectionMethodArgumentTypeStrict=disabled
+org.eclipse.jdt.core.compiler.problem.unlikelyEqualsArgumentType=info
 org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore
 org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=error
 org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore
-org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=error
+org.eclipse.jdt.core.compiler.problem.unstableAutoModuleName=warning
+org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=warning
 org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled
 org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled
 org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled
diff --git a/org.eclipse.jgit.junit/META-INF/MANIFEST.MF b/org.eclipse.jgit.junit/META-INF/MANIFEST.MF
index 749f8fe..ae9d8e6 100644
--- a/org.eclipse.jgit.junit/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.junit/META-INF/MANIFEST.MF
@@ -3,31 +3,31 @@
 Bundle-Name: %plugin_name
 Automatic-Module-Name: org.eclipse.jgit.junit
 Bundle-SymbolicName: org.eclipse.jgit.junit
-Bundle-Version: 5.0.4.qualifier
+Bundle-Version: 5.1.11.qualifier
 Bundle-Localization: plugin
 Bundle-Vendor: %provider_name
 Bundle-ActivationPolicy: lazy
 Bundle-RequiredExecutionEnvironment: JavaSE-1.8
-Import-Package: org.eclipse.jgit.api;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.api.errors;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.dircache;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.errors;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.internal.storage.file;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.internal.storage.pack;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.lib;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.merge;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.revwalk;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.storage.file;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.treewalk;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.treewalk.filter;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.util;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.util.io;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.util.time;version="[5.0.4,5.1.0)",
+Import-Package: org.eclipse.jgit.api;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.api.errors;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.dircache;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.errors;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.internal.storage.file;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.internal.storage.pack;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.lib;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.merge;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.revwalk;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.storage.file;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.treewalk;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.treewalk.filter;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.util;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.util.io;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.util.time;version="[5.1.11,5.2.0)",
  org.junit;version="[4.12,5.0.0)",
  org.junit.rules;version="[4.12,5.0.0)",
  org.junit.runner;version="[4.12,5.0.0)",
  org.junit.runners.model;version="[4.12,5.0.0)"
-Export-Package: org.eclipse.jgit.junit;version="5.0.4";
+Export-Package: org.eclipse.jgit.junit;version="5.1.11";
   uses:="org.eclipse.jgit.dircache,
    org.eclipse.jgit.lib,
    org.eclipse.jgit.revwalk,
@@ -36,4 +36,4 @@
    org.eclipse.jgit.util,
    org.eclipse.jgit.storage.file,
    org.eclipse.jgit.api",
- org.eclipse.jgit.junit.time;version="5.0.4"
+ org.eclipse.jgit.junit.time;version="5.1.11"
diff --git a/org.eclipse.jgit.junit/pom.xml b/org.eclipse.jgit.junit/pom.xml
index 30ce0b3..aec7b15 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.0.4-SNAPSHOT</version>
+    <version>5.1.11-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.junit</artifactId>
diff --git a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/JGitTestUtil.java b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/JGitTestUtil.java
index a102da1..b59b714 100644
--- a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/JGitTestUtil.java
+++ b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/JGitTestUtil.java
@@ -45,7 +45,7 @@
 
 package org.eclipse.jgit.junit;
 
-import static org.eclipse.jgit.lib.Constants.CHARSET;
+import static java.nio.charset.StandardCharsets.UTF_8;
 
 import java.io.File;
 import java.io.FileNotFoundException;
@@ -246,7 +246,7 @@ public static void write(File f, String body)
 			throws IOException {
 		FileUtils.mkdirs(f.getParentFile(), true);
 		try (Writer w = new OutputStreamWriter(new FileOutputStream(f),
-				CHARSET)) {
+				UTF_8)) {
 			w.write(body);
 		}
 	}
@@ -263,7 +263,7 @@ public static void write(File f, String body)
 	 */
 	public static String read(File file) throws IOException {
 		final byte[] body = IO.readFully(file);
-		return new String(body, 0, body.length, CHARSET);
+		return new String(body, 0, body.length, UTF_8);
 	}
 
 	/**
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 6cdd0eb..29579d0 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
@@ -45,12 +45,14 @@
 
 package org.eclipse.jgit.junit;
 
-import static org.eclipse.jgit.lib.Constants.CHARSET;
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.fail;
 
 import java.io.File;
 import java.io.IOException;
+import java.io.PrintStream;
+import java.time.Instant;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
@@ -127,16 +129,24 @@ public void setUp() throws Exception {
 			throw new IOException("Cannot create " + tmp);
 
 		mockSystemReader = new MockSystemReader();
-		mockSystemReader.userGitConfig = new FileBasedConfig(new File(tmp,
-				"usergitconfig"), FS.DETECTED);
+		SystemReader.setInstance(mockSystemReader);
+
+		// Measure timer resolution before the test to avoid time critical tests
+		// are affected by time needed for measurement.
+		// The MockSystemReader must be configured first since we need to use
+		// the same one here
+		FS.getFileStoreAttributes(tmp.toPath().getParent());
+
+		FileBasedConfig userConfig = new FileBasedConfig(
+				new File(tmp, "usergitconfig"), FS.DETECTED);
 		// We have to set autoDetach to false for tests, because tests expect to be able
 		// to clean up by recursively removing the repository, and background GC might be
 		// in the middle of writing or deleting files, which would disrupt this.
-		mockSystemReader.userGitConfig.setBoolean(ConfigConstants.CONFIG_GC_SECTION,
+		userConfig.setBoolean(ConfigConstants.CONFIG_GC_SECTION,
 				null, ConfigConstants.CONFIG_KEY_AUTODETACH, false);
-		mockSystemReader.userGitConfig.save();
+		userConfig.save();
+		mockSystemReader.setUserGitConfig(userConfig);
 		ceilTestDirectories(getCeilings());
-		SystemReader.setInstance(mockSystemReader);
 
 		author = new PersonIdent("J. Author", "jauthor@example.com");
 		committer = new PersonIdent("J. Committer", "jcommitter@example.com");
@@ -232,35 +242,30 @@ protected void recursiveDelete(File dir) {
 	private static boolean recursiveDelete(final File dir,
 			boolean silent, boolean failOnError) {
 		assert !(silent && failOnError);
-		if (!dir.exists())
-			return silent;
-		final File[] ls = dir.listFiles();
-		if (ls != null)
-			for (int k = 0; k < ls.length; k++) {
-				final File e = ls[k];
-				if (e.isDirectory())
-					silent = recursiveDelete(e, silent, failOnError);
-				else if (!e.delete()) {
-					if (!silent)
-						reportDeleteFailure(failOnError, e);
-					silent = !failOnError;
-				}
-			}
-		if (!dir.delete()) {
-			if (!silent)
-				reportDeleteFailure(failOnError, dir);
-			silent = !failOnError;
+		int options = FileUtils.RECURSIVE | FileUtils.RETRY
+				| FileUtils.SKIP_MISSING;
+		if (silent) {
+			options |= FileUtils.IGNORE_ERRORS;
 		}
-		return silent;
+		try {
+			FileUtils.delete(dir, options);
+		} catch (IOException e) {
+			reportDeleteFailure(failOnError, dir, e);
+			return !failOnError;
+		}
+		return true;
 	}
 
-	private static void reportDeleteFailure(boolean failOnError, File e) {
+	private static void reportDeleteFailure(boolean failOnError, File f,
+			Exception cause) {
 		String severity = failOnError ? "ERROR" : "WARNING";
-		String msg = severity + ": Failed to delete " + e;
-		if (failOnError)
+		String msg = severity + ": Failed to delete " + f;
+		if (failOnError) {
 			fail(msg);
-		else
+		} else {
 			System.err.println(msg);
+		}
+		cause.printStackTrace(new PrintStream(System.err));
 	}
 
 	/** Constant <code>MOD_TIME=1</code> */
@@ -322,12 +327,13 @@ public static String indexState(Repository repo, int includedOptions)
 			throws IllegalStateException, IOException {
 		DirCache dc = repo.readDirCache();
 		StringBuilder sb = new StringBuilder();
-		TreeSet<Long> timeStamps = new TreeSet<>();
+		TreeSet<Instant> timeStamps = new TreeSet<>();
 
 		// iterate once over the dircache just to collect all time stamps
 		if (0 != (includedOptions & MOD_TIME)) {
-			for (int i=0; i<dc.getEntryCount(); ++i)
-				timeStamps.add(Long.valueOf(dc.getEntry(i).getLastModified()));
+			for (int i = 0; i < dc.getEntryCount(); ++i) {
+				timeStamps.add(dc.getEntry(i).getLastModifiedInstant());
+			}
 		}
 
 		// iterate again, now produce the result string
@@ -339,7 +345,8 @@ public static String indexState(Repository repo, int includedOptions)
 				sb.append(", stage:" + stage);
 			if (0 != (includedOptions & MOD_TIME)) {
 				sb.append(", time:t"+
-						timeStamps.headSet(Long.valueOf(entry.getLastModified())).size());
+						timeStamps.headSet(entry.getLastModifiedInstant())
+								.size());
 			}
 			if (0 != (includedOptions & SMUDGE))
 				if (entry.isSmudged())
@@ -352,7 +359,7 @@ public static String indexState(Repository repo, int includedOptions)
 			if (0 != (includedOptions & CONTENT)) {
 				sb.append(", content:"
 						+ new String(repo.open(entry.getObjectId(),
-						Constants.OBJ_BLOB).getCachedBytes(), CHARSET));
+								Constants.OBJ_BLOB).getCachedBytes(), UTF_8));
 			}
 			if (0 != (includedOptions & ASSUME_UNCHANGED))
 				sb.append(", assume-unchanged:"
diff --git a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/MockSystemReader.java b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/MockSystemReader.java
index d3d7d68..123fdb3 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
@@ -60,6 +60,7 @@
 import org.eclipse.jgit.errors.ConfigInvalidException;
 import org.eclipse.jgit.lib.Config;
 import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.StoredConfig;
 import org.eclipse.jgit.storage.file.FileBasedConfig;
 import org.eclipse.jgit.util.FS;
 import org.eclipse.jgit.util.SystemReader;
@@ -70,7 +71,7 @@
  * Mock {@link org.eclipse.jgit.util.SystemReader} for tests.
  */
 public class MockSystemReader extends SystemReader {
-	private final class MockConfig extends FileBasedConfig {
+	private static final class MockConfig extends FileBasedConfig {
 		private MockConfig(File cfgLocation, FS fs) {
 			super(cfgLocation, fs);
 		}
@@ -81,20 +82,56 @@ public void load() throws IOException, ConfigInvalidException {
 		}
 
 		@Override
+		public void save() throws IOException {
+			// Do nothing
+		}
+
+		@Override
 		public boolean isOutdated() {
 			return false;
 		}
+
+		@Override
+		public String toString() {
+			return "MockConfig";
+		}
 	}
 
 	long now = 1250379778668L; // Sat Aug 15 20:12:58 GMT-03:30 2009
 
 	final Map<String, String> values = new HashMap<>();
 
-	FileBasedConfig userGitConfig;
+	private FileBasedConfig userGitConfig;
 
 	FileBasedConfig systemGitConfig;
 
 	/**
+	 * Set the user-level git config
+	 *
+	 * @param userGitConfig
+	 *            set another user-level git config
+	 * @return the old user-level git config
+	 */
+	public FileBasedConfig setUserGitConfig(FileBasedConfig userGitConfig) {
+		FileBasedConfig old = this.userGitConfig;
+		this.userGitConfig = userGitConfig;
+		return old;
+	}
+
+	/**
+	 * Set the system-level git config
+	 *
+	 * @param systemGitConfig
+	 *            the new system-level git config
+	 * @return the old system-level config
+	 */
+	public FileBasedConfig setSystemGitConfig(FileBasedConfig systemGitConfig) {
+		FileBasedConfig old = this.systemGitConfig;
+		this.systemGitConfig = systemGitConfig;
+		return old;
+	}
+
+	/**
 	 * Constructor for <code>MockSystemReader</code>
 	 */
 	public MockSystemReader() {
@@ -156,6 +193,18 @@ public FileBasedConfig openSystemConfig(Config parent, FS fs) {
 		return systemGitConfig;
 	}
 
+	@Override
+	public StoredConfig getUserConfig()
+			throws IOException, ConfigInvalidException {
+		return userGitConfig;
+	}
+
+	@Override
+	public StoredConfig getSystemConfig()
+			throws IOException, ConfigInvalidException {
+		return systemGitConfig;
+	}
+
 	/** {@inheritDoc} */
 	@Override
 	public String getHostname() {
@@ -280,4 +329,10 @@ private void resetOsNames() {
 			e.printStackTrace();
 		}
 	}
+
+	@Override
+	public String toString() {
+		return "MockSystemReader";
+	}
+
 }
diff --git a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/Repeat.java b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/Repeat.java
index 08220ce..94df554 100644
--- a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/Repeat.java
+++ b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/Repeat.java
@@ -56,4 +56,13 @@
 	 * Number of repetitions
 	 */
 	public abstract int n();
+
+	/**
+	 * Whether to abort execution on first test failure
+	 *
+	 * @return {@code true} if execution should be aborted on the first failure,
+	 *         otherwise count failures and continue execution
+	 * @since 5.1.9
+	 */
+	public boolean abortOnFailure() default true;
 }
diff --git a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/RepeatRule.java b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/RepeatRule.java
index 8165738..8636f2a 100644
--- a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/RepeatRule.java
+++ b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/RepeatRule.java
@@ -81,9 +81,31 @@ public class RepeatRule implements TestRule {
 	private static Logger LOG = Logger
 			.getLogger(RepeatRule.class.getName());
 
+	/**
+	 * Exception thrown if repeated execution of a test annotated with
+	 * {@code @Repeat} failed.
+	 */
 	public static class RepeatedTestException extends RuntimeException {
 		private static final long serialVersionUID = 1L;
 
+		/**
+		 * Constructor
+		 *
+		 * @param message
+		 *            the error message
+		 */
+		public RepeatedTestException(String message) {
+			super(message);
+		}
+
+		/**
+		 * Constructor
+		 *
+		 * @param message
+		 *            the error message
+		 * @param cause
+		 *            exception causing this exception
+		 */
 		public RepeatedTestException(String message, Throwable cause) {
 			super(message, cause);
 		}
@@ -93,28 +115,45 @@ private static class RepeatStatement extends Statement {
 
 		private final int repetitions;
 
+		private boolean abortOnFailure;
+
 		private final Statement statement;
 
-		private RepeatStatement(int repetitions, Statement statement) {
+		private RepeatStatement(int repetitions, boolean abortOnFailure,
+				Statement statement) {
 			this.repetitions = repetitions;
+			this.abortOnFailure = abortOnFailure;
 			this.statement = statement;
 		}
 
 		@Override
 		public void evaluate() throws Throwable {
+			int failures = 0;
 			for (int i = 0; i < repetitions; i++) {
 				try {
 					statement.evaluate();
 				} catch (Throwable e) {
+					failures += 1;
 					RepeatedTestException ex = new RepeatedTestException(
 							MessageFormat.format(
 									"Repeated test failed when run for the {0}. time",
 									Integer.valueOf(i + 1)),
 							e);
 					LOG.log(Level.SEVERE, ex.getMessage(), ex);
-					throw ex;
+					if (abortOnFailure) {
+						throw ex;
+					}
 				}
 			}
+			if (failures > 0) {
+				RepeatedTestException e = new RepeatedTestException(
+						MessageFormat.format(
+								"Test failed {0} times out of {1} repeated executions",
+								Integer.valueOf(failures),
+								Integer.valueOf(repetitions)));
+				LOG.log(Level.SEVERE, e.getMessage(), e);
+				throw e;
+			}
 		}
 	}
 
@@ -125,7 +164,8 @@ public Statement apply(Statement statement, Description description) {
 		Repeat repeat = description.getAnnotation(Repeat.class);
 		if (repeat != null) {
 			int n = repeat.n();
-			result = new RepeatStatement(n, statement);
+			boolean abortOnFailure = repeat.abortOnFailure();
+			result = new RepeatStatement(n, abortOnFailure, statement);
 		}
 		return result;
 	}
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 e983e5d..5aacbba 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
@@ -46,7 +46,7 @@
 
 package org.eclipse.jgit.junit;
 
-import static org.eclipse.jgit.lib.Constants.CHARSET;
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.junit.Assert.assertEquals;
 
 import java.io.File;
@@ -57,7 +57,9 @@
 import java.io.InputStreamReader;
 import java.io.Reader;
 import java.nio.file.Path;
+import java.time.Instant;
 import java.util.Map;
+import java.util.concurrent.TimeUnit;
 
 import org.eclipse.jgit.api.Git;
 import org.eclipse.jgit.api.errors.GitAPIException;
@@ -192,7 +194,7 @@ protected void deleteTrashFile(String name) throws IOException {
 	protected static void checkFile(File f, String checkData)
 			throws IOException {
 		try (Reader r = new InputStreamReader(new FileInputStream(f),
-				CHARSET)) {
+				UTF_8)) {
 			if (checkData.length() > 0) {
 				char[] data = new char[checkData.length()];
 				assertEquals(data.length, r.read(data));
@@ -284,7 +286,7 @@ protected void resetIndex(FileTreeIterator treeItr)
 
 				dce = new DirCacheEntry(treeItr.getEntryPathString());
 				dce.setFileMode(treeItr.getEntryFileMode());
-				dce.setLastModified(treeItr.getEntryLastModified());
+				dce.setLastModified(treeItr.getEntryLastModifiedInstant());
 				dce.setLength((int) len);
 				try (FileInputStream in = new FileInputStream(
 						treeItr.getEntryFile())) {
@@ -349,7 +351,8 @@ public static String slashify(String str) {
 	 * younger modification timestamp than the modification timestamp of the
 	 * given file. This is done by touching a temporary file, reading the
 	 * lastmodified attribute and, if needed, sleeping. After sleeping this loop
-	 * starts again until the filesystem timer has advanced enough.
+	 * starts again until the filesystem timer has advanced enough. The
+	 * temporary file will be created as a sibling of lastFile.
 	 *
 	 * @param lastFile
 	 *            the file on which we want to wait until the filesystem timer
@@ -360,24 +363,31 @@ public static String slashify(String str) {
 	 * @throws InterruptedException
 	 * @throws IOException
 	 */
-	public static long fsTick(File lastFile) throws InterruptedException,
+	public static Instant fsTick(File lastFile)
+			throws InterruptedException,
 			IOException {
-		long sleepTime = 64;
+		File tmp;
 		FS fs = FS.DETECTED;
-		if (lastFile != null && !fs.exists(lastFile))
-			throw new FileNotFoundException(lastFile.getPath());
-		File tmp = File.createTempFile("FileTreeIteratorWithTimeControl", null);
+		if (lastFile == null) {
+			lastFile = tmp = File
+					.createTempFile("fsTickTmpFile", null);
+		} else {
+			if (!fs.exists(lastFile)) {
+				throw new FileNotFoundException(lastFile.getPath());
+			}
+			tmp = File.createTempFile("fsTickTmpFile", null,
+					lastFile.getParentFile());
+		}
+		long res = FS.getFileStoreAttributes(tmp.toPath())
+				.getFsTimestampResolution().toNanos();
+		long sleepTime = res / 10;
 		try {
-			long startTime = (lastFile == null) ? fs.lastModified(tmp) : fs
-					.lastModified(lastFile);
-			long actTime = fs.lastModified(tmp);
-			while (actTime <= startTime) {
-				Thread.sleep(sleepTime);
-				sleepTime *= 2;
-				try (FileOutputStream fos = new FileOutputStream(tmp)) {
-					// Do nothing
-				}
-				actTime = fs.lastModified(tmp);
+			Instant startTime = fs.lastModifiedInstant(lastFile);
+			Instant actTime = fs.lastModifiedInstant(tmp);
+			while (actTime.compareTo(startTime) <= 0) {
+				TimeUnit.NANOSECONDS.sleep(sleepTime);
+				FileUtils.touch(tmp.toPath());
+				actTime = fs.lastModifiedInstant(tmp);
 			}
 			return actTime;
 		} finally {
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 d23c0d3..49a2b20 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
@@ -43,7 +43,7 @@
 
 package org.eclipse.jgit.junit;
 
-import static org.eclipse.jgit.lib.Constants.CHARSET;
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.fail;
 
@@ -268,7 +268,7 @@ public void setAuthorAndCommitter(org.eclipse.jgit.lib.CommitBuilder c) {
 	 * @throws Exception
 	 */
 	public RevBlob blob(String content) throws Exception {
-		return blob(content.getBytes(CHARSET));
+		return blob(content.getBytes(UTF_8));
 	}
 
 	/**
@@ -276,7 +276,7 @@ public RevBlob blob(String content) throws Exception {
 	 *
 	 * @param content
 	 *            binary file content.
-	 * @return reference to the blob.
+	 * @return the new, fully parsed blob.
 	 * @throws Exception
 	 */
 	public RevBlob blob(byte[] content) throws Exception {
@@ -285,7 +285,7 @@ public RevBlob blob(byte[] content) throws Exception {
 			id = ins.insert(Constants.OBJ_BLOB, content);
 			ins.flush();
 		}
-		return pool.lookupBlob(id);
+		return (RevBlob) pool.parseAny(id);
 	}
 
 	/**
@@ -312,21 +312,22 @@ public DirCacheEntry file(String path, RevBlob blob)
 	 * @param entries
 	 *            the files to include in the tree. The collection does not need
 	 *            to be sorted properly and may be empty.
-	 * @return reference to the tree specified by the entry list.
+	 * @return the new, fully parsed tree specified by the entry list.
 	 * @throws Exception
 	 */
 	public RevTree tree(DirCacheEntry... entries) throws Exception {
 		final DirCache dc = DirCache.newInCore();
 		final DirCacheBuilder b = dc.builder();
-		for (DirCacheEntry e : entries)
+		for (DirCacheEntry e : entries) {
 			b.add(e);
+		}
 		b.finish();
 		ObjectId root;
 		try (ObjectInserter ins = inserter) {
 			root = dc.writeTree(ins);
 			ins.flush();
 		}
-		return pool.lookupTree(root);
+		return pool.parseTree(root);
 	}
 
 	/**
@@ -422,7 +423,7 @@ public RevCommit commit(int secDelta, RevCommit... parents)
 	 *            the root tree for the commit.
 	 * @param parents
 	 *            zero or more parents of the commit.
-	 * @return the new commit.
+	 * @return the new, fully parsed commit.
 	 * @throws Exception
 	 */
 	public RevCommit commit(final int secDelta, final RevTree tree,
@@ -442,7 +443,7 @@ public RevCommit commit(final int secDelta, final RevTree tree,
 			id = ins.insert(c);
 			ins.flush();
 		}
-		return pool.lookupCommit(id);
+		return pool.parseCommit(id);
 	}
 
 	/**
@@ -467,7 +468,7 @@ public CommitBuilder commit() {
 	 *            with {@code refs/tags/}.
 	 * @param dst
 	 *            object the tag should be pointed at.
-	 * @return the annotated tag object.
+	 * @return the new, fully parsed annotated tag object.
 	 * @throws Exception
 	 */
 	public RevTag tag(String name, RevObject dst) throws Exception {
@@ -481,7 +482,7 @@ public RevTag tag(String name, RevObject dst) throws Exception {
 			id = ins.insert(t);
 			ins.flush();
 		}
-		return (RevTag) pool.lookupAny(id, Constants.OBJ_TAG);
+		return pool.parseTag(id);
 	}
 
 	/**
@@ -705,8 +706,8 @@ public void reset(String name) throws Exception {
 	 *
 	 * @param id
 	 *            commit-ish to cherry-pick.
-	 * @return newly created commit, or null if no work was done due to the
-	 *         resulting tree being identical.
+	 * @return the new, fully parsed commit, or null if no work was done due to
+	 *         the resulting tree being identical.
 	 * @throws Exception
 	 */
 	public RevCommit cherryPick(AnyObjectId id) throws Exception {
@@ -757,7 +758,7 @@ public RevCommit cherryPick(AnyObjectId id) throws Exception {
 	public void updateServerInfo() throws Exception {
 		if (db instanceof FileRepository) {
 			final FileRepository fr = (FileRepository) db;
-			RefWriter rw = new RefWriter(fr.getAllRefs().values()) {
+			RefWriter rw = new RefWriter(fr.getRefDatabase().getRefs()) {
 				@Override
 				protected void writeFile(String name, byte[] bin)
 						throws IOException {
@@ -852,7 +853,7 @@ public void fsck(RevObject... tips) throws MissingObjectException,
 				for (RevObject o : tips)
 					ow.markStart(ow.parseAny(o));
 			} else {
-				for (Ref r : db.getAllRefs().values())
+				for (Ref r : db.getRefDatabase().getRefs())
 					ow.markStart(ow.parseAny(r.getObjectId()));
 			}
 
@@ -905,7 +906,7 @@ public void packAndPrune() throws Exception {
 			final File pack, idx;
 			try (PackWriter pw = new PackWriter(db)) {
 				Set<ObjectId> all = new HashSet<>();
-				for (Ref r : db.getAllRefs().values())
+				for (Ref r : db.getRefDatabase().getRefs())
 					all.add(r.getObjectId());
 				pw.preparePack(m, all, PackWriter.NONE);
 
@@ -1058,6 +1059,14 @@ public class CommitBuilder {
 			parents.add(prior.create());
 		}
 
+		/**
+		 * set parent commit
+		 *
+		 * @param p
+		 *            parent commit
+		 * @return this commit builder
+		 * @throws Exception
+		 */
 		public CommitBuilder parent(RevCommit p) throws Exception {
 			if (parents.isEmpty()) {
 				DirCacheBuilder b = tree.builder();
@@ -1070,29 +1079,71 @@ public CommitBuilder parent(RevCommit p) throws Exception {
 			return this;
 		}
 
+		/**
+		 * Get parent commits
+		 *
+		 * @return parent commits
+		 */
 		public List<RevCommit> parents() {
 			return Collections.unmodifiableList(parents);
 		}
 
+		/**
+		 * Remove parent commits
+		 *
+		 * @return this commit builder
+		 */
 		public CommitBuilder noParents() {
 			parents.clear();
 			return this;
 		}
 
+		/**
+		 * Remove files
+		 *
+		 * @return this commit builder
+		 */
 		public CommitBuilder noFiles() {
 			tree.clear();
 			return this;
 		}
 
+		/**
+		 * Set top level tree
+		 *
+		 * @param treeId
+		 *            the top level tree
+		 * @return this commit builder
+		 */
 		public CommitBuilder setTopLevelTree(ObjectId treeId) {
 			topLevelTree = treeId;
 			return this;
 		}
 
+		/**
+		 * Add file with given content
+		 *
+		 * @param path
+		 *            path of the file
+		 * @param content
+		 *            the file content
+		 * @return this commit builder
+		 * @throws Exception
+		 */
 		public CommitBuilder add(String path, String content) throws Exception {
 			return add(path, blob(content));
 		}
 
+		/**
+		 * Add file with given path and blob
+		 *
+		 * @param path
+		 *            path of the file
+		 * @param id
+		 *            blob for this file
+		 * @return this commit builder
+		 * @throws Exception
+		 */
 		public CommitBuilder add(String path, RevBlob id)
 				throws Exception {
 			return edit(new PathEdit(path) {
@@ -1104,6 +1155,13 @@ public void apply(DirCacheEntry ent) {
 			});
 		}
 
+		/**
+		 * Edit the index
+		 *
+		 * @param edit
+		 *            the index record update
+		 * @return this commit builder
+		 */
 		public CommitBuilder edit(PathEdit edit) {
 			DirCacheEditor e = tree.editor();
 			e.add(edit);
@@ -1111,6 +1169,13 @@ public CommitBuilder edit(PathEdit edit) {
 			return this;
 		}
 
+		/**
+		 * Remove a file
+		 *
+		 * @param path
+		 *            path of the file
+		 * @return this commit builder
+		 */
 		public CommitBuilder rm(String path) {
 			DirCacheEditor e = tree.editor();
 			e.add(new DeletePath(path));
@@ -1119,49 +1184,111 @@ public CommitBuilder rm(String path) {
 			return this;
 		}
 
+		/**
+		 * Set commit message
+		 *
+		 * @param m
+		 *            the message
+		 * @return this commit builder
+		 */
 		public CommitBuilder message(String m) {
 			message = m;
 			return this;
 		}
 
+		/**
+		 * Get the commit message
+		 *
+		 * @return the commit message
+		 */
 		public String message() {
 			return message;
 		}
 
+		/**
+		 * Tick the clock
+		 *
+		 * @param secs
+		 *            number of seconds
+		 * @return this commit builder
+		 */
 		public CommitBuilder tick(int secs) {
 			tick = secs;
 			return this;
 		}
 
+		/**
+		 * Set author and committer identity
+		 *
+		 * @param ident
+		 *            identity to set
+		 * @return this commit builder
+		 */
 		public CommitBuilder ident(PersonIdent ident) {
 			author = ident;
 			committer = ident;
 			return this;
 		}
 
+		/**
+		 * Set the author identity
+		 *
+		 * @param a
+		 *            the author's identity
+		 * @return this commit builder
+		 */
 		public CommitBuilder author(PersonIdent a) {
 			author = a;
 			return this;
 		}
 
+		/**
+		 * Get the author identity
+		 *
+		 * @return the author identity
+		 */
 		public PersonIdent author() {
 			return author;
 		}
 
+		/**
+		 * Set the committer identity
+		 *
+		 * @param c
+		 *            the committer identity
+		 * @return this commit builder
+		 */
 		public CommitBuilder committer(PersonIdent c) {
 			committer = c;
 			return this;
 		}
 
+		/**
+		 * Get the committer identity
+		 *
+		 * @return the committer identity
+		 */
 		public PersonIdent committer() {
 			return committer;
 		}
 
+		/**
+		 * Insert changeId
+		 *
+		 * @return this commit builder
+		 */
 		public CommitBuilder insertChangeId() {
 			changeId = "";
 			return this;
 		}
 
+		/**
+		 * Insert given changeId
+		 *
+		 * @param c
+		 *            changeId
+		 * @return this commit builder
+		 */
 		public CommitBuilder insertChangeId(String c) {
 			// Validate, but store as a string so we can use "" as a sentinel.
 			ObjectId.fromString(c);
@@ -1169,6 +1296,13 @@ public CommitBuilder insertChangeId(String c) {
 			return this;
 		}
 
+		/**
+		 * Create the commit
+		 *
+		 * @return the new commit
+		 * @throws Exception
+		 *             if creation failed
+		 */
 		public RevCommit create() throws Exception {
 			if (self == null) {
 				TestRepository.this.tick(tick);
@@ -1197,7 +1331,7 @@ public RevCommit create() throws Exception {
 					commitId = ins.insert(c);
 					ins.flush();
 				}
-				self = pool.lookupCommit(commitId);
+				self = pool.parseCommit(commitId);
 
 				if (branch != null)
 					branch.update(self);
@@ -1229,6 +1363,12 @@ private void insertChangeId(org.eclipse.jgit.lib.CommitBuilder c) {
 						+ cid.getName() + "\n"); //$NON-NLS-1$
 		}
 
+		/**
+		 * Create child commit builder
+		 *
+		 * @return child commit builder
+		 * @throws Exception
+		 */
 		public CommitBuilder child() throws Exception {
 			return new CommitBuilder(this);
 		}
diff --git a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/time/TimeUtil.java b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/time/TimeUtil.java
new file mode 100644
index 0000000..1f8070d
--- /dev/null
+++ b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/time/TimeUtil.java
@@ -0,0 +1,99 @@
+/*
+ * 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.junit.time;
+
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.attribute.FileTime;
+import java.time.Instant;
+
+import org.eclipse.jgit.util.FS;
+
+/**
+ * Utility methods for handling timestamps
+ */
+public class TimeUtil {
+	/**
+	 * Set the lastModified time of a given file by adding a given offset to the
+	 * current lastModified time
+	 *
+	 * @param path
+	 *            path of a file to set last modified
+	 * @param offsetMillis
+	 *            offset in milliseconds, if negative the new lastModified time
+	 *            is offset before the original lastModified time, otherwise
+	 *            after the original time
+	 * @return the new lastModified time
+	 */
+	public static Instant setLastModifiedWithOffset(Path path,
+			long offsetMillis) {
+		Instant mTime = FS.DETECTED.lastModifiedInstant(path)
+				.plusMillis(offsetMillis);
+		try {
+			Files.setLastModifiedTime(path, FileTime.from(mTime));
+			return mTime;
+		} catch (IOException e) {
+			throw new UncheckedIOException(e);
+		}
+	}
+
+	/**
+	 * Set the lastModified time of file a to the one from file b
+	 *
+	 * @param a
+	 *            file to set lastModified time
+	 * @param b
+	 *            file to read lastModified time from
+	 */
+	public static void setLastModifiedOf(Path a, Path b) {
+		Instant mTime = FS.DETECTED.lastModifiedInstant(b);
+		try {
+			Files.setLastModifiedTime(a, FileTime.from(mTime));
+		} catch (IOException e) {
+			throw new UncheckedIOException(e);
+		}
+	}
+
+}
diff --git a/org.eclipse.jgit.lfs.server.test/BUILD b/org.eclipse.jgit.lfs.server.test/BUILD
index 1341dd6..fb0d691 100644
--- a/org.eclipse.jgit.lfs.server.test/BUILD
+++ b/org.eclipse.jgit.lfs.server.test/BUILD
@@ -32,7 +32,7 @@
         exclude = TEST_BASE,
     ),
     jvm_flags = [
-        "-Xmx256m",
+        "-Xmx512m",
         "-Dfile.encoding=UTF-8",
     ],
     tags = ["lfs-server"],
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 c805f75..1a60ddc 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.0.4.qualifier
+Bundle-Version: 5.1.11.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.0.4,5.1.0)",
- org.eclipse.jgit.api.errors;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.internal.storage.file;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.junit;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.junit.http;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.lfs;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.lfs.errors;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.lfs.lib;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.lfs.server;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.lfs.server.fs;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.lfs.test;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.lib;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.revwalk;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.storage.file;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.transport;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.treewalk;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.treewalk.filter;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.util;version="[5.0.4,5.1.0)",
+ org.eclipse.jgit.api;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.api.errors;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.internal.storage.file;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.junit;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.junit.http;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.lfs;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.lfs.errors;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.lfs.lib;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.lfs.server;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.lfs.server.fs;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.lfs.test;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.lib;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.revwalk;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.storage.file;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.transport;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.treewalk;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.treewalk.filter;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.util;version="[5.1.11,5.2.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 dee3199..d43f4c5 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.0.4-SNAPSHOT</version>
+    <version>5.1.11-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.lfs.server.test</artifactId>
@@ -111,7 +111,6 @@
     <dependency>
       <groupId>org.apache.httpcomponents</groupId>
       <artifactId>httpclient</artifactId>
-      <version>4.3.6</version>
     </dependency>
   </dependencies>
 
@@ -138,7 +137,7 @@
       <plugin>
         <artifactId>maven-surefire-plugin</artifactId>
         <configuration>
-          <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/LfsServerTest.java b/org.eclipse.jgit.lfs.server.test/tst/org/eclipse/jgit/lfs/server/fs/LfsServerTest.java
index 50a06f9..e55c4c6 100644
--- a/org.eclipse.jgit.lfs.server.test/tst/org/eclipse/jgit/lfs/server/fs/LfsServerTest.java
+++ b/org.eclipse.jgit.lfs.server.test/tst/org/eclipse/jgit/lfs/server/fs/LfsServerTest.java
@@ -42,7 +42,7 @@
  */
 package org.eclipse.jgit.lfs.server.fs;
 
-import static org.eclipse.jgit.lib.Constants.CHARSET;
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.junit.Assert.assertEquals;
 
 import java.io.BufferedInputStream;
@@ -74,6 +74,7 @@
 import org.apache.http.impl.client.HttpClientBuilder;
 import org.eclipse.jetty.servlet.ServletContextHandler;
 import org.eclipse.jetty.servlet.ServletHolder;
+import org.eclipse.jgit.junit.MockSystemReader;
 import org.eclipse.jgit.junit.http.AppServer;
 import org.eclipse.jgit.lfs.errors.LfsException;
 import org.eclipse.jgit.lfs.lib.AnyLongObjectId;
@@ -82,8 +83,10 @@
 import org.eclipse.jgit.lfs.server.LargeFileRepository;
 import org.eclipse.jgit.lfs.server.LfsProtocolServlet;
 import org.eclipse.jgit.lfs.test.LongObjectIdTestUtils;
+import org.eclipse.jgit.util.FS;
 import org.eclipse.jgit.util.FileUtils;
 import org.eclipse.jgit.util.IO;
+import org.eclipse.jgit.util.SystemReader;
 import org.junit.After;
 import org.junit.Before;
 
@@ -118,7 +121,13 @@ public Path getDir() {
 
 	@Before
 	public void setup() throws Exception {
+		SystemReader.setInstance(new MockSystemReader());
 		tmp = Files.createTempDirectory("jgit_test_");
+
+		// measure timer resolution before the test to avoid time critical tests
+		// are affected by time needed for measurement
+		FS.getFileStoreAttributes(tmp.getParent());
+
 		server = new AppServer();
 		ServletContextHandler app = server.addContext("/lfs");
 		dir = Paths.get(tmp.toString(), "lfs");
@@ -211,11 +220,11 @@ private void checkResponseStatus(HttpResponse response) {
 				if (buf.hasArray()) {
 					error = new String(buf.array(),
 							buf.arrayOffset() + buf.position(), buf.remaining(),
-							CHARSET);
+							UTF_8);
 				} else {
 					final byte[] b = new byte[buf.remaining()];
 					buf.duplicate().get(b);
-					error = new String(b, CHARSET);
+					error = new String(b, UTF_8);
 				}
 			} catch (IOException e) {
 				error = statusLine.getReasonPhrase();
diff --git a/org.eclipse.jgit.lfs.server/.settings/.api_filters b/org.eclipse.jgit.lfs.server/.settings/.api_filters
new file mode 100644
index 0000000..6609c3d
--- /dev/null
+++ b/org.eclipse.jgit.lfs.server/.settings/.api_filters
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<component id="org.eclipse.jgit.lfs.server" version="2">
+    <resource path="src/org/eclipse/jgit/lfs/server/fs/ObjectUploadListener.java" type="org.eclipse.jgit.lfs.server.fs.ObjectUploadListener">
+        <filter id="1142947843">
+            <message_arguments>
+                <message_argument value="5.1.7"/>
+                <message_argument value="setCallback(ObjectUploadListener.Callback)"/>
+            </message_arguments>
+        </filter>
+    </resource>
+    <resource path="src/org/eclipse/jgit/lfs/server/fs/ObjectUploadListener.java" type="org.eclipse.jgit.lfs.server.fs.ObjectUploadListener$Callback">
+        <filter id="1142947843">
+            <message_arguments>
+                <message_argument value="5.1.7"/>
+                <message_argument value="Callback"/>
+            </message_arguments>
+        </filter>
+    </resource>
+</component>
diff --git a/org.eclipse.jgit.lfs.server/.settings/org.eclipse.pde.api.tools.prefs b/org.eclipse.jgit.lfs.server/.settings/org.eclipse.pde.api.tools.prefs
index 112e3c3..c0030de 100644
--- a/org.eclipse.jgit.lfs.server/.settings/org.eclipse.pde.api.tools.prefs
+++ b/org.eclipse.jgit.lfs.server/.settings/org.eclipse.pde.api.tools.prefs
@@ -81,7 +81,7 @@
 METHOD_ELEMENT_TYPE_CHANGED_VARARGS_TO_ARRAY=Error
 METHOD_ELEMENT_TYPE_REMOVED_ANNOTATION_DEFAULT_VALUE=Error
 METHOD_ELEMENT_TYPE_REMOVED_TYPE_PARAMETER=Error
-MISSING_EE_DESCRIPTIONS=Error
+MISSING_EE_DESCRIPTIONS=Warning
 TYPE_PARAMETER_ELEMENT_TYPE_ADDED_CLASS_BOUND=Error
 TYPE_PARAMETER_ELEMENT_TYPE_ADDED_INTERFACE_BOUND=Error
 TYPE_PARAMETER_ELEMENT_TYPE_CHANGED_CLASS_BOUND=Error
diff --git a/org.eclipse.jgit.lfs.server/META-INF/MANIFEST.MF b/org.eclipse.jgit.lfs.server/META-INF/MANIFEST.MF
index 14d3ab8..6bfc6b8 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.0.4.qualifier
+Bundle-Version: 5.1.11.qualifier
 Bundle-Localization: plugin
 Bundle-Vendor: %provider_name
-Export-Package: org.eclipse.jgit.lfs.server;version="5.0.4";
+Export-Package: org.eclipse.jgit.lfs.server;version="5.1.11";
   uses:="javax.servlet.http,
    org.eclipse.jgit.lfs.lib",
- org.eclipse.jgit.lfs.server.fs;version="5.0.4";
+ org.eclipse.jgit.lfs.server.fs;version="5.1.11";
   uses:="javax.servlet,
    javax.servlet.http,
    org.eclipse.jgit.lfs.server,
    org.eclipse.jgit.lfs.lib",
- org.eclipse.jgit.lfs.server.internal;version="5.0.4";x-internal:=true,
- org.eclipse.jgit.lfs.server.s3;version="5.0.4";
+ org.eclipse.jgit.lfs.server.internal;version="5.1.11";x-internal:=true,
+ org.eclipse.jgit.lfs.server.s3;version="5.1.11";
   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.0.4,5.1.0)",
- org.eclipse.jgit.internal;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.internal.storage.file;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.lfs.errors;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.lfs.internal;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.lfs.lib;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.lib;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.nls;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.transport.http;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.transport.http.apache;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.util;version="[5.0.4,5.1.0)",
+ org.eclipse.jgit.annotations;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.internal;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.internal.storage.file;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.lfs.errors;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.lfs.internal;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.lfs.lib;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.lib;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.nls;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.transport.http;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.transport.http.apache;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.util;version="[5.1.11,5.2.0)",
  org.slf4j;version="[1.7.0,2.0.0)"
diff --git a/org.eclipse.jgit.lfs.server/pom.xml b/org.eclipse.jgit.lfs.server/pom.xml
index 7c81c1a..34c9c0a 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.0.4-SNAPSHOT</version>
+    <version>5.1.11-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.lfs.server</artifactId>
diff --git a/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/LfsProtocolServlet.java b/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/LfsProtocolServlet.java
index d22d459..c7f55dd 100644
--- a/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/LfsProtocolServlet.java
+++ b/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/LfsProtocolServlet.java
@@ -42,7 +42,7 @@
  */
 package org.eclipse.jgit.lfs.server;
 
-import static org.eclipse.jgit.lib.Constants.CHARSET;
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.apache.http.HttpStatus.SC_FORBIDDEN;
 import static org.apache.http.HttpStatus.SC_INSUFFICIENT_STORAGE;
 import static org.apache.http.HttpStatus.SC_INTERNAL_SERVER_ERROR;
@@ -202,10 +202,10 @@ public boolean isVerify() {
 	protected void doPost(HttpServletRequest req, HttpServletResponse res)
 			throws ServletException, IOException {
 		Writer w = new BufferedWriter(
-				new OutputStreamWriter(res.getOutputStream(), CHARSET));
+				new OutputStreamWriter(res.getOutputStream(), UTF_8));
 
 		Reader r = new BufferedReader(
-				new InputStreamReader(req.getInputStream(), CHARSET));
+				new InputStreamReader(req.getInputStream(), UTF_8));
 		LfsRequest request = LfsGson.fromJson(r, LfsRequest.class);
 		String path = req.getPathInfo();
 
diff --git a/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/fs/ObjectUploadListener.java b/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/fs/ObjectUploadListener.java
index c5b6a67..3bb2899 100644
--- a/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/fs/ObjectUploadListener.java
+++ b/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/fs/ObjectUploadListener.java
@@ -48,6 +48,7 @@
 import java.nio.channels.Channels;
 import java.nio.channels.ReadableByteChannel;
 import java.nio.channels.WritableByteChannel;
+import java.nio.file.Path;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
@@ -87,6 +88,29 @@ public class ObjectUploadListener implements ReadListener {
 
 	private final ByteBuffer buffer = ByteBuffer.allocateDirect(8192);
 
+	private final Path path;
+
+	private long uploaded;
+
+	private Callback callback;
+
+	/**
+	 * Callback invoked after object upload completed.
+	 *
+	 * @since 5.1.7
+	 */
+	public interface Callback {
+		/**
+		 * Notified after object upload completed.
+		 *
+		 * @param path
+		 *            path to the object on the backend
+		 * @param size
+		 *            uploaded size in bytes
+		 */
+		void uploadCompleted(String path, long size);
+	}
+
 	/**
 	 * Constructor for ObjectUploadListener.
 	 *
@@ -113,10 +137,25 @@ public ObjectUploadListener(FileLfsRepository repository,
 		this.inChannel = Channels.newChannel(in);
 		this.out = repository.getOutputStream(id);
 		this.channel = Channels.newChannel(out);
+		this.path = repository.getPath(id);
+		this.uploaded = 0L;
 		response.setContentType(Constants.CONTENT_TYPE_GIT_LFS_JSON);
 	}
 
 	/**
+	 * Set the callback to invoke after upload completed.
+	 *
+	 * @param callback
+	 *            the callback
+	 * @return {@code this}.
+	 * @since 5.1.7
+	 */
+	public ObjectUploadListener setCallback(Callback callback) {
+		this.callback = callback;
+		return this;
+	}
+
+	/**
 	 * {@inheritDoc}
 	 *
 	 * Writes all the received data to the output channel
@@ -126,12 +165,13 @@ public void onDataAvailable() throws IOException {
 		while (in.isReady()) {
 			if (inChannel.read(buffer) > 0) {
 				buffer.flip();
-				channel.write(buffer);
+				uploaded += Integer.valueOf(channel.write(buffer)).longValue();
 				buffer.compact();
 			} else {
 				buffer.flip();
 				while (buffer.hasRemaining()) {
-					channel.write(buffer);
+					uploaded += Integer.valueOf(channel.write(buffer))
+							.longValue();
 				}
 				close();
 				return;
@@ -159,6 +199,9 @@ protected void close() throws IOException {
 			if (!response.isCommitted()) {
 				response.setStatus(HttpServletResponse.SC_OK);
 			}
+			if (callback != null) {
+				callback.uploadCompleted(path.toString(), uploaded);
+			}
 		} finally {
 			context.complete();
 		}
diff --git a/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/s3/SignerV4.java b/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/s3/SignerV4.java
index 374a560..b21c94e 100644
--- a/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/s3/SignerV4.java
+++ b/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/s3/SignerV4.java
@@ -43,7 +43,7 @@
  */
 package org.eclipse.jgit.lfs.server.s3;
 
-import static org.eclipse.jgit.lib.Constants.CHARSET;
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.eclipse.jgit.util.HttpSupport.HDR_AUTHORIZATION;
 
 import java.io.UnsupportedEncodingException;
@@ -359,13 +359,13 @@ private static String canonicalizeResourcePath(URL endpoint) {
 
 	private static byte[] hash(String s) {
 		MessageDigest md = Constants.newMessageDigest();
-		md.update(s.getBytes(CHARSET));
+		md.update(s.getBytes(UTF_8));
 		return md.digest();
 	}
 
 	private static byte[] sign(String stringData, byte[] key) {
 		try {
-			byte[] data = stringData.getBytes(CHARSET);
+			byte[] data = stringData.getBytes(UTF_8);
 			Mac mac = Mac.getInstance(HMACSHA256);
 			mac.init(new SecretKeySpec(key, HMACSHA256));
 			return mac.doFinal(data);
@@ -395,7 +395,7 @@ private static String toHex(byte[] bytes) {
 	private static String urlEncode(String url, boolean keepPathSlash) {
 		String encoded;
 		try {
-			encoded = URLEncoder.encode(url, CHARSET.name());
+			encoded = URLEncoder.encode(url, UTF_8.name());
 		} catch (UnsupportedEncodingException e) {
 			throw new RuntimeException(LfsServerText.get().unsupportedUtf8, e);
 		}
diff --git a/org.eclipse.jgit.lfs.test/META-INF/MANIFEST.MF b/org.eclipse.jgit.lfs.test/META-INF/MANIFEST.MF
index d8376ce..f698e06 100644
--- a/org.eclipse.jgit.lfs.test/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.lfs.test/META-INF/MANIFEST.MF
@@ -3,23 +3,23 @@
 Bundle-Name: %plugin_name
 Automatic-Module-Name: org.eclipse.jgit.lfs.test
 Bundle-SymbolicName: org.eclipse.jgit.lfs.test
-Bundle-Version: 5.0.4.qualifier
+Bundle-Version: 5.1.11.qualifier
 Bundle-Vendor: %provider_name
 Bundle-Localization: plugin
 Bundle-RequiredExecutionEnvironment: JavaSE-1.8
-Import-Package: org.eclipse.jgit.internal.storage.dfs;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.junit;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.lfs;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.lfs.errors;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.lfs.lib;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.lib;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.revwalk;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.treewalk;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.treewalk.filter;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.util;version="[5.0.4,5.1.0)",
+Import-Package: org.eclipse.jgit.internal.storage.dfs;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.junit;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.lfs;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.lfs.errors;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.lfs.lib;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.lib;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.revwalk;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.treewalk;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.treewalk.filter;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.util;version="[5.1.11,5.2.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.0.4";x-friends:="org.eclipse.jgit.lfs.server.test"
+Export-Package: org.eclipse.jgit.lfs.test;version="5.1.11";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 a55cecd..566c2df 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.0.4-SNAPSHOT</version>
+    <version>5.1.11-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.lfs.test</artifactId>
@@ -111,7 +111,7 @@
       <plugin>
         <artifactId>maven-surefire-plugin</artifactId>
         <configuration>
-          <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.test/src/org/eclipse/jgit/lfs/test/LongObjectIdTestUtils.java b/org.eclipse.jgit.lfs.test/src/org/eclipse/jgit/lfs/test/LongObjectIdTestUtils.java
index e3c6ef8..c3c3859 100644
--- a/org.eclipse.jgit.lfs.test/src/org/eclipse/jgit/lfs/test/LongObjectIdTestUtils.java
+++ b/org.eclipse.jgit.lfs.test/src/org/eclipse/jgit/lfs/test/LongObjectIdTestUtils.java
@@ -42,7 +42,7 @@
  */
 package org.eclipse.jgit.lfs.test;
 
-import static org.eclipse.jgit.lib.Constants.CHARSET;
+import static java.nio.charset.StandardCharsets.UTF_8;
 
 import java.io.BufferedInputStream;
 import java.io.FileNotFoundException;
@@ -66,7 +66,7 @@ public class LongObjectIdTestUtils {
 	 */
 	public static LongObjectId hash(String s) {
 		MessageDigest md = Constants.newMessageDigest();
-		md.update(s.getBytes(CHARSET));
+		md.update(s.getBytes(UTF_8));
 		return LongObjectId.fromRaw(md.digest());
 	}
 
diff --git a/org.eclipse.jgit.lfs.test/tst/org/eclipse/jgit/lfs/lib/LFSPointerTest.java b/org.eclipse.jgit.lfs.test/tst/org/eclipse/jgit/lfs/lib/LFSPointerTest.java
index a1283dd..146a25e 100644
--- a/org.eclipse.jgit.lfs.test/tst/org/eclipse/jgit/lfs/lib/LFSPointerTest.java
+++ b/org.eclipse.jgit.lfs.test/tst/org/eclipse/jgit/lfs/lib/LFSPointerTest.java
@@ -43,7 +43,7 @@
 
 package org.eclipse.jgit.lfs.lib;
 
-import static org.eclipse.jgit.lib.Constants.CHARACTER_ENCODING;
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.junit.Assert.assertEquals;
 
 import java.io.ByteArrayOutputStream;
@@ -66,7 +66,7 @@ public void testEncoding() throws IOException {
 			assertEquals(
 					"version https://git-lfs.github.com/spec/v1\noid sha256:"
 							+ s + "\nsize 4\n",
-					baos.toString(CHARACTER_ENCODING));
+					baos.toString(UTF_8.name()));
 		}
 	}
 }
diff --git a/org.eclipse.jgit.lfs.test/tst/org/eclipse/jgit/lfs/lib/LongObjectIdTest.java b/org.eclipse.jgit.lfs.test/tst/org/eclipse/jgit/lfs/lib/LongObjectIdTest.java
index 8642e7e..92a8176 100644
--- a/org.eclipse.jgit.lfs.test/tst/org/eclipse/jgit/lfs/lib/LongObjectIdTest.java
+++ b/org.eclipse.jgit.lfs.test/tst/org/eclipse/jgit/lfs/lib/LongObjectIdTest.java
@@ -43,6 +43,7 @@
 
 package org.eclipse.jgit.lfs.lib;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static java.nio.charset.StandardCharsets.US_ASCII;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -62,7 +63,6 @@
 import org.eclipse.jgit.junit.JGitTestUtil;
 import org.eclipse.jgit.lfs.errors.InvalidLongObjectIdException;
 import org.eclipse.jgit.lfs.test.LongObjectIdTestUtils;
-import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.util.FileUtils;
 import org.junit.AfterClass;
 import org.junit.BeforeClass;
@@ -393,7 +393,7 @@ public void testCopyToWriter() throws IOException {
 		AnyLongObjectId id1 = LongObjectIdTestUtils.hash("test");
 		ByteArrayOutputStream os = new ByteArrayOutputStream(64);
 		try (OutputStreamWriter w = new OutputStreamWriter(os,
-				Constants.CHARSET)) {
+				UTF_8)) {
 			id1.copyTo(w);
 		}
 		assertEquals(id1, LongObjectId.fromString(os.toByteArray(), 0));
@@ -404,7 +404,7 @@ public void testCopyToWriterWithBuf() throws IOException {
 		AnyLongObjectId id1 = LongObjectIdTestUtils.hash("test");
 		ByteArrayOutputStream os = new ByteArrayOutputStream(64);
 		try (OutputStreamWriter w = new OutputStreamWriter(os,
-				Constants.CHARSET)) {
+				UTF_8)) {
 			char[] buf = new char[64];
 			id1.copyTo(buf, w);
 		}
diff --git a/org.eclipse.jgit.lfs/.settings/org.eclipse.pde.api.tools.prefs b/org.eclipse.jgit.lfs/.settings/org.eclipse.pde.api.tools.prefs
index 112e3c3..c0030de 100644
--- a/org.eclipse.jgit.lfs/.settings/org.eclipse.pde.api.tools.prefs
+++ b/org.eclipse.jgit.lfs/.settings/org.eclipse.pde.api.tools.prefs
@@ -81,7 +81,7 @@
 METHOD_ELEMENT_TYPE_CHANGED_VARARGS_TO_ARRAY=Error
 METHOD_ELEMENT_TYPE_REMOVED_ANNOTATION_DEFAULT_VALUE=Error
 METHOD_ELEMENT_TYPE_REMOVED_TYPE_PARAMETER=Error
-MISSING_EE_DESCRIPTIONS=Error
+MISSING_EE_DESCRIPTIONS=Warning
 TYPE_PARAMETER_ELEMENT_TYPE_ADDED_CLASS_BOUND=Error
 TYPE_PARAMETER_ELEMENT_TYPE_ADDED_INTERFACE_BOUND=Error
 TYPE_PARAMETER_ELEMENT_TYPE_CHANGED_CLASS_BOUND=Error
diff --git a/org.eclipse.jgit.lfs/META-INF/MANIFEST.MF b/org.eclipse.jgit.lfs/META-INF/MANIFEST.MF
index 3e1e221..dc33103 100644
--- a/org.eclipse.jgit.lfs/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.lfs/META-INF/MANIFEST.MF
@@ -3,34 +3,33 @@
 Bundle-Name: %plugin_name
 Automatic-Module-Name: org.eclipse.jgit.lfs
 Bundle-SymbolicName: org.eclipse.jgit.lfs
-Bundle-Version: 5.0.4.qualifier
+Bundle-Version: 5.1.11.qualifier
 Bundle-Localization: plugin
 Bundle-Vendor: %provider_name
-Export-Package: org.eclipse.jgit.lfs;version="5.0.4",
- org.eclipse.jgit.lfs.errors;version="5.0.4",
- org.eclipse.jgit.lfs.internal;version="5.0.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.0.4"
+Export-Package: org.eclipse.jgit.lfs;version="5.1.11",
+ org.eclipse.jgit.lfs.errors;version="5.1.11",
+ org.eclipse.jgit.lfs.internal;version="5.1.11";x-friends:="org.eclipse.jgit.lfs.test,org.eclipse.jgit.lfs.server.fs,org.eclipse.jgit.lfs.server",
+ org.eclipse.jgit.lfs.lib;version="5.1.11"
 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.0.4,5.1.0)";resolution:=optional,
- org.eclipse.jgit.api.errors;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.attributes;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.diff;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.errors;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.hooks;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.internal.storage.file;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.lib;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.nls;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.revwalk;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.storage.file;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.storage.pack;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.transport;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.transport.http;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.treewalk;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.treewalk.filter;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.util;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.util.io;version="[5.0.4,5.1.0)",
- org.slf4j;version="[1.7.0,2.0.0)"
+ org.eclipse.jgit.annotations;version="[5.1.11,5.2.0)";resolution:=optional,
+ org.eclipse.jgit.api.errors;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.attributes;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.diff;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.errors;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.hooks;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.internal.storage.file;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.lib;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.nls;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.revwalk;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.storage.file;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.storage.pack;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.transport;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.transport.http;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.treewalk;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.treewalk.filter;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.util;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.util.io;version="[5.1.11,5.2.0)"
diff --git a/org.eclipse.jgit.lfs/pom.xml b/org.eclipse.jgit.lfs/pom.xml
index c231692..bdf91ec 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.0.4-SNAPSHOT</version>
+    <version>5.1.11-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.lfs</artifactId>
diff --git a/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/InstallBuiltinLfsCommand.java b/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/InstallBuiltinLfsCommand.java
index 028b19b..b7b0535 100644
--- a/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/InstallBuiltinLfsCommand.java
+++ b/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/InstallBuiltinLfsCommand.java
@@ -43,14 +43,12 @@
 package org.eclipse.jgit.lfs;
 
 import java.io.IOException;
-import java.text.MessageFormat;
 
+import org.eclipse.jgit.api.errors.InvalidConfigurationException;
 import org.eclipse.jgit.errors.ConfigInvalidException;
-import org.eclipse.jgit.lfs.internal.LfsText;
 import org.eclipse.jgit.lib.ConfigConstants;
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.lib.StoredConfig;
-import org.eclipse.jgit.storage.file.FileBasedConfig;
 import org.eclipse.jgit.util.FS;
 import org.eclipse.jgit.util.LfsFactory.LfsInstallCommand;
 import org.eclipse.jgit.util.SystemReader;
@@ -70,12 +68,28 @@ public class InstallBuiltinLfsCommand implements LfsInstallCommand {
 
 	private Repository repository;
 
-	/** {@inheritDoc} */
+	/**
+	 * {@inheritDoc}
+	 *
+	 * @throws IOException
+	 *             if an I/O error occurs while accessing a git config or
+	 *             executing {@code git lfs install} in an external process
+	 * @throws InvalidConfigurationException
+	 *             if a git configuration is invalid
+	 * @throws InterruptedException
+	 *             if the current thread is interrupted while waiting for the
+	 *             {@code git lfs install} executed in an external process
+	 */
 	@Override
-	public Void call() throws Exception {
+	public Void call() throws IOException, InvalidConfigurationException,
+			InterruptedException {
 		StoredConfig cfg = null;
 		if (repository == null) {
-			cfg = loadUserConfig();
+			try {
+				cfg = SystemReader.getInstance().getUserConfig();
+			} catch (ConfigInvalidException e) {
+				throw new InvalidConfigurationException(e.getMessage(), e);
+			}
 		} else {
 			cfg = repository.getConfig();
 		}
@@ -116,19 +130,4 @@ public LfsInstallCommand setRepository(Repository repo) {
 		return this;
 	}
 
-	private StoredConfig loadUserConfig() throws IOException {
-		FileBasedConfig c = SystemReader.getInstance().openUserConfig(null,
-				FS.DETECTED);
-		try {
-			c.load();
-		} catch (ConfigInvalidException e1) {
-			throw new IOException(MessageFormat
-					.format(LfsText.get().userConfigInvalid, c.getFile()
-							.getAbsolutePath(), e1),
-					e1);
-		}
-
-		return c;
-	}
-
 }
diff --git a/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/LfsPointer.java b/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/LfsPointer.java
index 4f95940..0e3830c 100644
--- a/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/LfsPointer.java
+++ b/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/LfsPointer.java
@@ -42,7 +42,7 @@
  */
 package org.eclipse.jgit.lfs;
 
-import static org.eclipse.jgit.lib.Constants.CHARSET;
+import static java.nio.charset.StandardCharsets.UTF_8;
 
 import java.io.BufferedReader;
 import java.io.IOException;
@@ -134,7 +134,7 @@ public long getSize() {
 	 */
 	public void encode(OutputStream out) {
 		try (PrintStream ps = new PrintStream(out, false,
-				CHARSET.name())) {
+				UTF_8.name())) {
 			ps.print("version "); //$NON-NLS-1$
 			ps.print(VERSION + "\n"); //$NON-NLS-1$
 			ps.print("oid " + HASH_FUNCTION_NAME + ":"); //$NON-NLS-1$ //$NON-NLS-2$
@@ -143,7 +143,7 @@ public void encode(OutputStream out) {
 			ps.print(size + "\n"); //$NON-NLS-1$
 		} catch (UnsupportedEncodingException e) {
 			// should not happen, we are using a standard charset
-			throw new UnsupportedCharsetException(CHARSET.name());
+			throw new UnsupportedCharsetException(UTF_8.name());
 		}
 	}
 
@@ -165,7 +165,7 @@ public static LfsPointer parseLfsPointer(InputStream in)
 		long sz = -1;
 
 		try (BufferedReader br = new BufferedReader(
-				new InputStreamReader(in, CHARSET))) {
+				new InputStreamReader(in, UTF_8))) {
 			for (String s = br.readLine(); s != null; s = br.readLine()) {
 				if (s.startsWith("#") || s.length() == 0) { //$NON-NLS-1$
 					continue;
diff --git a/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/LfsPrePushHook.java b/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/LfsPrePushHook.java
index de4449f..3e6a261 100644
--- a/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/LfsPrePushHook.java
+++ b/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/LfsPrePushHook.java
@@ -42,7 +42,7 @@
  */
 package org.eclipse.jgit.lfs;
 
-import static org.eclipse.jgit.lib.Constants.CHARSET;
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.eclipse.jgit.lfs.Protocol.OPERATION_UPLOAD;
 import static org.eclipse.jgit.lfs.internal.LfsConnectionFactory.toRequest;
 import static org.eclipse.jgit.transport.http.HttpConnection.HTTP_OK;
@@ -201,14 +201,14 @@ private String remote() {
 
 	private Map<String, LfsPointer> requestBatchUpload(HttpConnection api,
 			Set<LfsPointer> toPush) throws IOException {
-		LfsPointer[] res = toPush.toArray(new LfsPointer[toPush.size()]);
+		LfsPointer[] res = toPush.toArray(new LfsPointer[0]);
 		Map<String, LfsPointer> oidStr2ptr = new HashMap<>();
 		for (LfsPointer p : res) {
 			oidStr2ptr.put(p.getOid().name(), p);
 		}
 		Gson gson = Protocol.gson();
 		api.getOutputStream().write(
-				gson.toJson(toRequest(OPERATION_UPLOAD, res)).getBytes(CHARSET));
+				gson.toJson(toRequest(OPERATION_UPLOAD, res)).getBytes(UTF_8));
 		int responseCode = api.getResponseCode();
 		if (responseCode != HTTP_OK) {
 			throw new IOException(
@@ -221,7 +221,7 @@ private Map<String, LfsPointer> requestBatchUpload(HttpConnection api,
 	private void uploadContents(HttpConnection api,
 			Map<String, LfsPointer> oid2ptr) throws IOException {
 		try (JsonReader reader = new JsonReader(
-				new InputStreamReader(api.getInputStream()))) {
+				new InputStreamReader(api.getInputStream(), UTF_8))) {
 			for (Protocol.ObjectInfo o : parseObjects(reader)) {
 				if (o.actions == null) {
 					continue;
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 6bff12f..fac87c1 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
@@ -42,7 +42,7 @@
  */
 package org.eclipse.jgit.lfs;
 
-import static org.eclipse.jgit.lib.Constants.CHARSET;
+import static java.nio.charset.StandardCharsets.UTF_8;
 
 import java.io.IOException;
 import java.io.InputStream;
@@ -170,7 +170,7 @@ public static Collection<Path> downloadLfsResource(Lfs lfs, Repository db,
 				.write(gson
 						.toJson(LfsConnectionFactory
 								.toRequest(Protocol.OPERATION_DOWNLOAD, res))
-						.getBytes(CHARSET));
+						.getBytes(UTF_8));
 		int responseCode = lfsServerConn.getResponseCode();
 		if (responseCode != HttpConnection.HTTP_OK) {
 			throw new IOException(
@@ -179,7 +179,8 @@ public static Collection<Path> downloadLfsResource(Lfs lfs, Repository db,
 							Integer.valueOf(responseCode)));
 		}
 		try (JsonReader reader = new JsonReader(
-				new InputStreamReader(lfsServerConn.getInputStream()))) {
+				new InputStreamReader(lfsServerConn.getInputStream(),
+						UTF_8))) {
 			Protocol.Response resp = gson.fromJson(reader,
 					Protocol.Response.class);
 			for (Protocol.ObjectInfo o : resp.objects) {
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 d09852b..e8b9fce 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.0.4.qualifier"
+      version="5.1.11.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 a9a58af..814925a 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.0.4-SNAPSHOT</version>
+    <version>5.1.11-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 485fd6b..7715f6e 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.0.4.qualifier"
+      version="5.1.11.qualifier"
       provider-name="%providerName">
 
    <description url="http://www.eclipse.org/jgit/">
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.http.apache.feature/pom.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.http.apache.feature/pom.xml
index d148ac3..ad5891e 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.0.4-SNAPSHOT</version>
+    <version>5.1.11-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 6551330..62e1691 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.0.4.qualifier"
+      version="5.1.11.qualifier"
       provider-name="%providerName">
 
    <description url="http://www.eclipse.org/jgit/">
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 f65326e..b32c7e2 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.0.4-SNAPSHOT</version>
+    <version>5.1.11-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 72e15da..30405a3 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.0.4.qualifier"
+      version="5.1.11.qualifier"
       provider-name="%providerName">
 
    <description url="http://www.eclipse.org/jgit/">
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.lfs.feature/pom.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.lfs.feature/pom.xml
index 35a4fda..c420eca 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.0.4-SNAPSHOT</version>
+    <version>5.1.11-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 18a7d1a..d275d0a 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.0.4.qualifier"
+      version="5.1.11.qualifier"
       provider-name="%providerName">
 
    <description url="http://www.eclipse.org/jgit/">
@@ -31,8 +31,8 @@
          version="0.0.0"/>
 
    <requires>
-      <import feature="org.eclipse.jgit" version="5.0.4" match="equivalent"/>
-      <import feature="org.eclipse.jgit.lfs" version="5.0.4" match="equivalent"/>
+      <import feature="org.eclipse.jgit" version="5.1.11" match="equivalent"/>
+      <import feature="org.eclipse.jgit.lfs" version="5.1.11" 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 73a7d3f..9fbb6e9 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.0.4-SNAPSHOT</version>
+    <version>5.1.11-SNAPSHOT</version>
   </parent>
 
   <groupId>org.eclipse.jgit.feature</groupId>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.source.feature/feature.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.source.feature/feature.xml
index 810d018..970ac17 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.source.feature/feature.xml
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.source.feature/feature.xml
@@ -2,7 +2,7 @@
 <feature
       id="org.eclipse.jgit.pgm.source"
       label="%featureName"
-      version="5.0.4.qualifier"
+      version="5.1.11.qualifier"
       provider-name="%providerName">
 
    <description url="http://www.eclipse.org/jgit/">
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.source.feature/pom.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.source.feature/pom.xml
index 88b8ce9..5d89506 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.source.feature/pom.xml
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.source.feature/pom.xml
@@ -50,7 +50,7 @@
   <parent>
     <groupId>org.eclipse.jgit</groupId>
     <artifactId>jgit.tycho.parent</artifactId>
-    <version>5.0.4-SNAPSHOT</version>
+    <version>5.1.11-SNAPSHOT</version>
   </parent>
 
   <groupId>org.eclipse.jgit.feature</groupId>
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 c8c3b51..4903b3b 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.0.4-SNAPSHOT</version>
+    <version>5.1.11-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.repository</artifactId>
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 12d6fe3..e3e28a5 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.source.feature/feature.xml
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.source.feature/feature.xml
@@ -2,7 +2,7 @@
 <feature
       id="org.eclipse.jgit.source"
       label="%featureName"
-      version="5.0.4.qualifier"
+      version="5.1.11.qualifier"
       provider-name="%providerName">
 
    <description url="http://www.eclipse.org/jgit/">
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.source.feature/pom.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.source.feature/pom.xml
index 7c83241..6cea04e 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.0.4-SNAPSHOT</version>
+    <version>5.1.11-SNAPSHOT</version>
   </parent>
 
   <groupId>org.eclipse.jgit.feature</groupId>
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 fc7dfa6..d54f247 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.0.4.qualifier
+Bundle-Version: 5.1.11.qualifier
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.5.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.5.target
index dfb5a8f..dba5c72 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.5.target
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.5.target
@@ -1,40 +1,40 @@
 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
 <?pde?>
-<!-- generated with https://github.com/mbarbero/fr.obeo.releng.targetplatform -->
-<target name="jgit-4.5" sequenceNumber="1535179254">
+<!-- generated with https://github.com/eclipse-cbi/targetplatform-dsl -->
+<target name="jgit-4.5" sequenceNumber="1565603712">
   <locations>
     <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
-      <unit id="org.eclipse.jetty.client" version="9.4.8.v20171121"/>
-      <unit id="org.eclipse.jetty.client.source" version="9.4.8.v20171121"/>
-      <unit id="org.eclipse.jetty.continuation" version="9.4.8.v20171121"/>
-      <unit id="org.eclipse.jetty.continuation.source" version="9.4.8.v20171121"/>
-      <unit id="org.eclipse.jetty.http" version="9.4.8.v20171121"/>
-      <unit id="org.eclipse.jetty.http.source" version="9.4.8.v20171121"/>
-      <unit id="org.eclipse.jetty.io" version="9.4.8.v20171121"/>
-      <unit id="org.eclipse.jetty.io.source" version="9.4.8.v20171121"/>
-      <unit id="org.eclipse.jetty.security" version="9.4.8.v20171121"/>
-      <unit id="org.eclipse.jetty.security.source" version="9.4.8.v20171121"/>
-      <unit id="org.eclipse.jetty.server" version="9.4.8.v20171121"/>
-      <unit id="org.eclipse.jetty.server.source" version="9.4.8.v20171121"/>
-      <unit id="org.eclipse.jetty.servlet" version="9.4.8.v20171121"/>
-      <unit id="org.eclipse.jetty.servlet.source" version="9.4.8.v20171121"/>
-      <unit id="org.eclipse.jetty.util" version="9.4.8.v20171121"/>
-      <unit id="org.eclipse.jetty.util.source" version="9.4.8.v20171121"/>
-      <repository id="jetty-9.4.8" location="http://download.eclipse.org/jetty/updates/jetty-bundles-9.x/9.4.8.v20171121"/>
+      <unit id="org.eclipse.jetty.client" version="9.4.11.v20180605"/>
+      <unit id="org.eclipse.jetty.client.source" version="9.4.11.v20180605"/>
+      <unit id="org.eclipse.jetty.continuation" version="9.4.11.v20180605"/>
+      <unit id="org.eclipse.jetty.continuation.source" version="9.4.11.v20180605"/>
+      <unit id="org.eclipse.jetty.http" version="9.4.11.v20180605"/>
+      <unit id="org.eclipse.jetty.http.source" version="9.4.11.v20180605"/>
+      <unit id="org.eclipse.jetty.io" version="9.4.11.v20180605"/>
+      <unit id="org.eclipse.jetty.io.source" version="9.4.11.v20180605"/>
+      <unit id="org.eclipse.jetty.security" version="9.4.11.v20180605"/>
+      <unit id="org.eclipse.jetty.security.source" version="9.4.11.v20180605"/>
+      <unit id="org.eclipse.jetty.server" version="9.4.11.v20180605"/>
+      <unit id="org.eclipse.jetty.server.source" version="9.4.11.v20180605"/>
+      <unit id="org.eclipse.jetty.servlet" version="9.4.11.v20180605"/>
+      <unit id="org.eclipse.jetty.servlet.source" version="9.4.11.v20180605"/>
+      <unit id="org.eclipse.jetty.util" version="9.4.11.v20180605"/>
+      <unit id="org.eclipse.jetty.util.source" version="9.4.11.v20180605"/>
+      <repository id="jetty-9.4.11" location="http://download.eclipse.org/jetty/updates/jetty-bundles-9.x/9.4.11.v20180605"/>
     </location>
     <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
       <unit id="org.apache.ant" version="1.9.6.v201510161327"/>
       <unit id="org.apache.ant.source" version="1.9.6.v201510161327"/>
-      <unit id="org.apache.commons.codec" version="1.9.0.v20170208-1614"/>
-      <unit id="org.apache.commons.codec.source" version="1.9.0.v20170208-1614"/>
+      <unit id="org.apache.commons.codec" version="1.10.0.v20180409-1845"/>
+      <unit id="org.apache.commons.codec.source" version="1.10.0.v20180409-1845"/>
       <unit id="org.apache.commons.compress" version="1.15.0.v20180119-1613"/>
       <unit id="org.apache.commons.compress.source" version="1.15.0.v20180119-1613"/>
-      <unit id="org.apache.commons.logging" version="1.1.1.v201101211721"/>
-      <unit id="org.apache.commons.logging.source" version="1.1.1.v201101211721"/>
-      <unit id="org.apache.httpcomponents.httpcore" version="4.4.6.v20170210-0925"/>
-      <unit id="org.apache.httpcomponents.httpcore.source" version="4.4.6.v20170210-0925"/>
-      <unit id="org.apache.httpcomponents.httpclient" version="4.5.2.v20180410-1551"/>
-      <unit id="org.apache.httpcomponents.httpclient.source" version="4.5.2.v20180410-1551"/>
+      <unit id="org.apache.commons.logging" version="1.2.0.v20180409-1502"/>
+      <unit id="org.apache.commons.logging.source" version="1.2.0.v20180409-1502"/>
+      <unit id="org.apache.httpcomponents.httpclient" version="4.5.6.v20190503-0009"/>
+      <unit id="org.apache.httpcomponents.httpclient.source" version="4.5.6.v20190503-0009"/>
+      <unit id="org.apache.httpcomponents.httpcore" version="4.4.10.v20190123-2214"/>
+      <unit id="org.apache.httpcomponents.httpcore.source" version="4.4.10.v20190123-2214"/>
       <unit id="org.apache.log4j" version="1.2.15.v201012070815"/>
       <unit id="org.apache.log4j.source" version="1.2.15.v201012070815"/>
       <unit id="org.kohsuke.args4j" version="2.33.0.v20160323-2218"/>
@@ -46,14 +46,18 @@
       <unit id="org.hamcrest.library.source" version="1.3.0.v20180524-2246"/>
       <unit id="javaewah" version="1.1.6.v20160919-1400"/>
       <unit id="javaewah.source" version="1.1.6.v20160919-1400"/>
-      <unit id="org.objenesis" version="1.0.0.v201505121915"/>
-      <unit id="org.objenesis.source" version="1.0.0.v201505121915"/>
-      <unit id="org.mockito" version="1.8.4.v201303031500"/>
-      <unit id="org.mockito.source" version="1.8.4.v201303031500"/>
+      <unit id="org.objenesis" version="2.6.0.v20180420-1519"/>
+      <unit id="org.objenesis.source" version="2.6.0.v20180420-1519"/>
+      <unit id="org.mockito" version="2.23.0.v20190527-1420"/>
+      <unit id="org.mockito.source" version="2.23.0.v20190527-1420"/>
+      <unit id="net.bytebuddy.byte-buddy" version="1.9.0.v20181107-1410"/>
+      <unit id="net.bytebuddy.byte-buddy.source" version="1.9.0.v20181107-1410"/>
+      <unit id="net.bytebuddy.byte-buddy-agent" version="1.9.0.v20181106-1534"/>
+      <unit id="net.bytebuddy.byte-buddy-agent.source" version="1.9.0.v20181106-1534"/>
       <unit id="com.google.gson" version="2.8.2.v20180104-1110"/>
       <unit id="com.google.gson.source" version="2.8.2.v20180104-1110"/>
-      <unit id="com.jcraft.jsch" version="0.1.54.v20170116-1932"/>
-      <unit id="com.jcraft.jsch.source" version="0.1.54.v20170116-1932"/>
+      <unit id="com.jcraft.jsch" version="0.1.55.v20190404-1902"/>
+      <unit id="com.jcraft.jsch.source" version="0.1.55.v20190404-1902"/>
       <unit id="org.junit" version="4.12.0.v201504281640"/>
       <unit id="org.junit.source" version="4.12.0.v201504281640"/>
       <unit id="javax.servlet" version="3.1.0.v201410161800"/>
@@ -66,7 +70,7 @@
       <unit id="org.slf4j.impl.log4j12.source" version="1.7.2.v20131105-2200"/>
       <unit id="com.jcraft.jzlib" version="1.1.1.v201205102305"/>
       <unit id="com.jcraft.jzlib.source" version="1.1.1.v201205102305"/>
-      <repository location="http://download.eclipse.org/tools/orbit/downloads/drops/R20180606145124/repository"/>
+      <repository location="http://download.eclipse.org/tools/orbit/downloads/drops/R20190602212107/repository"/>
     </location>
     <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
       <unit id="org.eclipse.osgi" version="0.0.0"/>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.5.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.5.tpd
index c85343c..7a5feb0 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.5.tpd
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.5.tpd
@@ -1,7 +1,7 @@
 target "jgit-4.5" with source configurePhase
 
-include "projects/jetty-9.4.8.tpd"
-include "orbit/R20180606145124-Photon.tpd"
+include "projects/jetty-9.4.11.tpd"
+include "orbit/R20190602212107-2019-06.tpd"
 
 location "http://download.eclipse.org/releases/mars/" {
 	org.eclipse.osgi lazy
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.6.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.6.target
index dbfd694..6c7de21 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,40 +1,40 @@
 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
 <?pde?>
-<!-- generated with https://github.com/mbarbero/fr.obeo.releng.targetplatform -->
-<target name="jgit-4.6" sequenceNumber="1535179241">
+<!-- generated with https://github.com/eclipse-cbi/targetplatform-dsl -->
+<target name="jgit-4.6" sequenceNumber="1565603721">
   <locations>
     <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
-      <unit id="org.eclipse.jetty.client" version="9.4.8.v20171121"/>
-      <unit id="org.eclipse.jetty.client.source" version="9.4.8.v20171121"/>
-      <unit id="org.eclipse.jetty.continuation" version="9.4.8.v20171121"/>
-      <unit id="org.eclipse.jetty.continuation.source" version="9.4.8.v20171121"/>
-      <unit id="org.eclipse.jetty.http" version="9.4.8.v20171121"/>
-      <unit id="org.eclipse.jetty.http.source" version="9.4.8.v20171121"/>
-      <unit id="org.eclipse.jetty.io" version="9.4.8.v20171121"/>
-      <unit id="org.eclipse.jetty.io.source" version="9.4.8.v20171121"/>
-      <unit id="org.eclipse.jetty.security" version="9.4.8.v20171121"/>
-      <unit id="org.eclipse.jetty.security.source" version="9.4.8.v20171121"/>
-      <unit id="org.eclipse.jetty.server" version="9.4.8.v20171121"/>
-      <unit id="org.eclipse.jetty.server.source" version="9.4.8.v20171121"/>
-      <unit id="org.eclipse.jetty.servlet" version="9.4.8.v20171121"/>
-      <unit id="org.eclipse.jetty.servlet.source" version="9.4.8.v20171121"/>
-      <unit id="org.eclipse.jetty.util" version="9.4.8.v20171121"/>
-      <unit id="org.eclipse.jetty.util.source" version="9.4.8.v20171121"/>
-      <repository id="jetty-9.4.8" location="http://download.eclipse.org/jetty/updates/jetty-bundles-9.x/9.4.8.v20171121"/>
+      <unit id="org.eclipse.jetty.client" version="9.4.11.v20180605"/>
+      <unit id="org.eclipse.jetty.client.source" version="9.4.11.v20180605"/>
+      <unit id="org.eclipse.jetty.continuation" version="9.4.11.v20180605"/>
+      <unit id="org.eclipse.jetty.continuation.source" version="9.4.11.v20180605"/>
+      <unit id="org.eclipse.jetty.http" version="9.4.11.v20180605"/>
+      <unit id="org.eclipse.jetty.http.source" version="9.4.11.v20180605"/>
+      <unit id="org.eclipse.jetty.io" version="9.4.11.v20180605"/>
+      <unit id="org.eclipse.jetty.io.source" version="9.4.11.v20180605"/>
+      <unit id="org.eclipse.jetty.security" version="9.4.11.v20180605"/>
+      <unit id="org.eclipse.jetty.security.source" version="9.4.11.v20180605"/>
+      <unit id="org.eclipse.jetty.server" version="9.4.11.v20180605"/>
+      <unit id="org.eclipse.jetty.server.source" version="9.4.11.v20180605"/>
+      <unit id="org.eclipse.jetty.servlet" version="9.4.11.v20180605"/>
+      <unit id="org.eclipse.jetty.servlet.source" version="9.4.11.v20180605"/>
+      <unit id="org.eclipse.jetty.util" version="9.4.11.v20180605"/>
+      <unit id="org.eclipse.jetty.util.source" version="9.4.11.v20180605"/>
+      <repository id="jetty-9.4.11" location="http://download.eclipse.org/jetty/updates/jetty-bundles-9.x/9.4.11.v20180605"/>
     </location>
     <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
       <unit id="org.apache.ant" version="1.9.6.v201510161327"/>
       <unit id="org.apache.ant.source" version="1.9.6.v201510161327"/>
-      <unit id="org.apache.commons.codec" version="1.9.0.v20170208-1614"/>
-      <unit id="org.apache.commons.codec.source" version="1.9.0.v20170208-1614"/>
+      <unit id="org.apache.commons.codec" version="1.10.0.v20180409-1845"/>
+      <unit id="org.apache.commons.codec.source" version="1.10.0.v20180409-1845"/>
       <unit id="org.apache.commons.compress" version="1.15.0.v20180119-1613"/>
       <unit id="org.apache.commons.compress.source" version="1.15.0.v20180119-1613"/>
-      <unit id="org.apache.commons.logging" version="1.1.1.v201101211721"/>
-      <unit id="org.apache.commons.logging.source" version="1.1.1.v201101211721"/>
-      <unit id="org.apache.httpcomponents.httpcore" version="4.4.6.v20170210-0925"/>
-      <unit id="org.apache.httpcomponents.httpcore.source" version="4.4.6.v20170210-0925"/>
-      <unit id="org.apache.httpcomponents.httpclient" version="4.5.2.v20180410-1551"/>
-      <unit id="org.apache.httpcomponents.httpclient.source" version="4.5.2.v20180410-1551"/>
+      <unit id="org.apache.commons.logging" version="1.2.0.v20180409-1502"/>
+      <unit id="org.apache.commons.logging.source" version="1.2.0.v20180409-1502"/>
+      <unit id="org.apache.httpcomponents.httpclient" version="4.5.6.v20190503-0009"/>
+      <unit id="org.apache.httpcomponents.httpclient.source" version="4.5.6.v20190503-0009"/>
+      <unit id="org.apache.httpcomponents.httpcore" version="4.4.10.v20190123-2214"/>
+      <unit id="org.apache.httpcomponents.httpcore.source" version="4.4.10.v20190123-2214"/>
       <unit id="org.apache.log4j" version="1.2.15.v201012070815"/>
       <unit id="org.apache.log4j.source" version="1.2.15.v201012070815"/>
       <unit id="org.kohsuke.args4j" version="2.33.0.v20160323-2218"/>
@@ -46,14 +46,18 @@
       <unit id="org.hamcrest.library.source" version="1.3.0.v20180524-2246"/>
       <unit id="javaewah" version="1.1.6.v20160919-1400"/>
       <unit id="javaewah.source" version="1.1.6.v20160919-1400"/>
-      <unit id="org.objenesis" version="1.0.0.v201505121915"/>
-      <unit id="org.objenesis.source" version="1.0.0.v201505121915"/>
-      <unit id="org.mockito" version="1.8.4.v201303031500"/>
-      <unit id="org.mockito.source" version="1.8.4.v201303031500"/>
+      <unit id="org.objenesis" version="2.6.0.v20180420-1519"/>
+      <unit id="org.objenesis.source" version="2.6.0.v20180420-1519"/>
+      <unit id="org.mockito" version="2.23.0.v20190527-1420"/>
+      <unit id="org.mockito.source" version="2.23.0.v20190527-1420"/>
+      <unit id="net.bytebuddy.byte-buddy" version="1.9.0.v20181107-1410"/>
+      <unit id="net.bytebuddy.byte-buddy.source" version="1.9.0.v20181107-1410"/>
+      <unit id="net.bytebuddy.byte-buddy-agent" version="1.9.0.v20181106-1534"/>
+      <unit id="net.bytebuddy.byte-buddy-agent.source" version="1.9.0.v20181106-1534"/>
       <unit id="com.google.gson" version="2.8.2.v20180104-1110"/>
       <unit id="com.google.gson.source" version="2.8.2.v20180104-1110"/>
-      <unit id="com.jcraft.jsch" version="0.1.54.v20170116-1932"/>
-      <unit id="com.jcraft.jsch.source" version="0.1.54.v20170116-1932"/>
+      <unit id="com.jcraft.jsch" version="0.1.55.v20190404-1902"/>
+      <unit id="com.jcraft.jsch.source" version="0.1.55.v20190404-1902"/>
       <unit id="org.junit" version="4.12.0.v201504281640"/>
       <unit id="org.junit.source" version="4.12.0.v201504281640"/>
       <unit id="javax.servlet" version="3.1.0.v201410161800"/>
@@ -66,7 +70,7 @@
       <unit id="org.slf4j.impl.log4j12.source" version="1.7.2.v20131105-2200"/>
       <unit id="com.jcraft.jzlib" version="1.1.1.v201205102305"/>
       <unit id="com.jcraft.jzlib.source" version="1.1.1.v201205102305"/>
-      <repository location="http://download.eclipse.org/tools/orbit/downloads/drops/R20180606145124/repository"/>
+      <repository location="http://download.eclipse.org/tools/orbit/downloads/drops/R20190602212107/repository"/>
     </location>
     <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
       <unit id="org.eclipse.osgi" version="0.0.0"/>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.6.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.6.tpd
index 3c2a910..8b3dd0b 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.6.tpd
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.6.tpd
@@ -1,7 +1,7 @@
 target "jgit-4.6" with source configurePhase
 
-include "projects/jetty-9.4.8.tpd"
-include "orbit/R20180606145124-Photon.tpd"
+include "projects/jetty-9.4.11.tpd"
+include "orbit/R20190602212107-2019-06.tpd"
 
 location "http://download.eclipse.org/releases/neon/" {
 	org.eclipse.osgi lazy
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.7.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.7.target
index be7719d..0fb8d4c 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,40 +1,40 @@
 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
 <?pde?>
-<!-- generated with https://github.com/mbarbero/fr.obeo.releng.targetplatform -->
-<target name="jgit-4.7" sequenceNumber="1535179221">
+<!-- generated with https://github.com/eclipse-cbi/targetplatform-dsl -->
+<target name="jgit-4.7" sequenceNumber="1565603704">
   <locations>
     <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
-      <unit id="org.eclipse.jetty.client" version="9.4.8.v20171121"/>
-      <unit id="org.eclipse.jetty.client.source" version="9.4.8.v20171121"/>
-      <unit id="org.eclipse.jetty.continuation" version="9.4.8.v20171121"/>
-      <unit id="org.eclipse.jetty.continuation.source" version="9.4.8.v20171121"/>
-      <unit id="org.eclipse.jetty.http" version="9.4.8.v20171121"/>
-      <unit id="org.eclipse.jetty.http.source" version="9.4.8.v20171121"/>
-      <unit id="org.eclipse.jetty.io" version="9.4.8.v20171121"/>
-      <unit id="org.eclipse.jetty.io.source" version="9.4.8.v20171121"/>
-      <unit id="org.eclipse.jetty.security" version="9.4.8.v20171121"/>
-      <unit id="org.eclipse.jetty.security.source" version="9.4.8.v20171121"/>
-      <unit id="org.eclipse.jetty.server" version="9.4.8.v20171121"/>
-      <unit id="org.eclipse.jetty.server.source" version="9.4.8.v20171121"/>
-      <unit id="org.eclipse.jetty.servlet" version="9.4.8.v20171121"/>
-      <unit id="org.eclipse.jetty.servlet.source" version="9.4.8.v20171121"/>
-      <unit id="org.eclipse.jetty.util" version="9.4.8.v20171121"/>
-      <unit id="org.eclipse.jetty.util.source" version="9.4.8.v20171121"/>
-      <repository id="jetty-9.4.8" location="http://download.eclipse.org/jetty/updates/jetty-bundles-9.x/9.4.8.v20171121"/>
+      <unit id="org.eclipse.jetty.client" version="9.4.11.v20180605"/>
+      <unit id="org.eclipse.jetty.client.source" version="9.4.11.v20180605"/>
+      <unit id="org.eclipse.jetty.continuation" version="9.4.11.v20180605"/>
+      <unit id="org.eclipse.jetty.continuation.source" version="9.4.11.v20180605"/>
+      <unit id="org.eclipse.jetty.http" version="9.4.11.v20180605"/>
+      <unit id="org.eclipse.jetty.http.source" version="9.4.11.v20180605"/>
+      <unit id="org.eclipse.jetty.io" version="9.4.11.v20180605"/>
+      <unit id="org.eclipse.jetty.io.source" version="9.4.11.v20180605"/>
+      <unit id="org.eclipse.jetty.security" version="9.4.11.v20180605"/>
+      <unit id="org.eclipse.jetty.security.source" version="9.4.11.v20180605"/>
+      <unit id="org.eclipse.jetty.server" version="9.4.11.v20180605"/>
+      <unit id="org.eclipse.jetty.server.source" version="9.4.11.v20180605"/>
+      <unit id="org.eclipse.jetty.servlet" version="9.4.11.v20180605"/>
+      <unit id="org.eclipse.jetty.servlet.source" version="9.4.11.v20180605"/>
+      <unit id="org.eclipse.jetty.util" version="9.4.11.v20180605"/>
+      <unit id="org.eclipse.jetty.util.source" version="9.4.11.v20180605"/>
+      <repository id="jetty-9.4.11" location="http://download.eclipse.org/jetty/updates/jetty-bundles-9.x/9.4.11.v20180605"/>
     </location>
     <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
       <unit id="org.apache.ant" version="1.9.6.v201510161327"/>
       <unit id="org.apache.ant.source" version="1.9.6.v201510161327"/>
-      <unit id="org.apache.commons.codec" version="1.9.0.v20170208-1614"/>
-      <unit id="org.apache.commons.codec.source" version="1.9.0.v20170208-1614"/>
+      <unit id="org.apache.commons.codec" version="1.10.0.v20180409-1845"/>
+      <unit id="org.apache.commons.codec.source" version="1.10.0.v20180409-1845"/>
       <unit id="org.apache.commons.compress" version="1.15.0.v20180119-1613"/>
       <unit id="org.apache.commons.compress.source" version="1.15.0.v20180119-1613"/>
-      <unit id="org.apache.commons.logging" version="1.1.1.v201101211721"/>
-      <unit id="org.apache.commons.logging.source" version="1.1.1.v201101211721"/>
-      <unit id="org.apache.httpcomponents.httpcore" version="4.4.6.v20170210-0925"/>
-      <unit id="org.apache.httpcomponents.httpcore.source" version="4.4.6.v20170210-0925"/>
-      <unit id="org.apache.httpcomponents.httpclient" version="4.5.2.v20180410-1551"/>
-      <unit id="org.apache.httpcomponents.httpclient.source" version="4.5.2.v20180410-1551"/>
+      <unit id="org.apache.commons.logging" version="1.2.0.v20180409-1502"/>
+      <unit id="org.apache.commons.logging.source" version="1.2.0.v20180409-1502"/>
+      <unit id="org.apache.httpcomponents.httpclient" version="4.5.6.v20190503-0009"/>
+      <unit id="org.apache.httpcomponents.httpclient.source" version="4.5.6.v20190503-0009"/>
+      <unit id="org.apache.httpcomponents.httpcore" version="4.4.10.v20190123-2214"/>
+      <unit id="org.apache.httpcomponents.httpcore.source" version="4.4.10.v20190123-2214"/>
       <unit id="org.apache.log4j" version="1.2.15.v201012070815"/>
       <unit id="org.apache.log4j.source" version="1.2.15.v201012070815"/>
       <unit id="org.kohsuke.args4j" version="2.33.0.v20160323-2218"/>
@@ -46,14 +46,18 @@
       <unit id="org.hamcrest.library.source" version="1.3.0.v20180524-2246"/>
       <unit id="javaewah" version="1.1.6.v20160919-1400"/>
       <unit id="javaewah.source" version="1.1.6.v20160919-1400"/>
-      <unit id="org.objenesis" version="1.0.0.v201505121915"/>
-      <unit id="org.objenesis.source" version="1.0.0.v201505121915"/>
-      <unit id="org.mockito" version="1.8.4.v201303031500"/>
-      <unit id="org.mockito.source" version="1.8.4.v201303031500"/>
+      <unit id="org.objenesis" version="2.6.0.v20180420-1519"/>
+      <unit id="org.objenesis.source" version="2.6.0.v20180420-1519"/>
+      <unit id="org.mockito" version="2.23.0.v20190527-1420"/>
+      <unit id="org.mockito.source" version="2.23.0.v20190527-1420"/>
+      <unit id="net.bytebuddy.byte-buddy" version="1.9.0.v20181107-1410"/>
+      <unit id="net.bytebuddy.byte-buddy.source" version="1.9.0.v20181107-1410"/>
+      <unit id="net.bytebuddy.byte-buddy-agent" version="1.9.0.v20181106-1534"/>
+      <unit id="net.bytebuddy.byte-buddy-agent.source" version="1.9.0.v20181106-1534"/>
       <unit id="com.google.gson" version="2.8.2.v20180104-1110"/>
       <unit id="com.google.gson.source" version="2.8.2.v20180104-1110"/>
-      <unit id="com.jcraft.jsch" version="0.1.54.v20170116-1932"/>
-      <unit id="com.jcraft.jsch.source" version="0.1.54.v20170116-1932"/>
+      <unit id="com.jcraft.jsch" version="0.1.55.v20190404-1902"/>
+      <unit id="com.jcraft.jsch.source" version="0.1.55.v20190404-1902"/>
       <unit id="org.junit" version="4.12.0.v201504281640"/>
       <unit id="org.junit.source" version="4.12.0.v201504281640"/>
       <unit id="javax.servlet" version="3.1.0.v201410161800"/>
@@ -66,7 +70,7 @@
       <unit id="org.slf4j.impl.log4j12.source" version="1.7.2.v20131105-2200"/>
       <unit id="com.jcraft.jzlib" version="1.1.1.v201205102305"/>
       <unit id="com.jcraft.jzlib.source" version="1.1.1.v201205102305"/>
-      <repository location="http://download.eclipse.org/tools/orbit/downloads/drops/R20180606145124/repository"/>
+      <repository location="http://download.eclipse.org/tools/orbit/downloads/drops/R20190602212107/repository"/>
     </location>
     <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
       <unit id="org.eclipse.osgi" version="0.0.0"/>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.7.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.7.tpd
index 4e543ff..e93fc46 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.7.tpd
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.7.tpd
@@ -1,7 +1,7 @@
 target "jgit-4.7" with source configurePhase
 
-include "projects/jetty-9.4.8.tpd"
-include "orbit/R20180606145124-Photon.tpd"
+include "projects/jetty-9.4.11.tpd"
+include "orbit/R20190602212107-2019-06.tpd"
 
 location "http://download.eclipse.org/releases/oxygen/" {
 	org.eclipse.osgi lazy
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.8.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.8.target
index 3de172b..34f3863 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,40 +1,40 @@
 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
 <?pde?>
-<!-- generated with https://github.com/mbarbero/fr.obeo.releng.targetplatform -->
-<target name="jgit-4.8" sequenceNumber="1535179205">
+<!-- generated with https://github.com/eclipse-cbi/targetplatform-dsl -->
+<target name="jgit-4.8" sequenceNumber="1565603695">
   <locations>
     <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
-      <unit id="org.eclipse.jetty.client" version="9.4.8.v20171121"/>
-      <unit id="org.eclipse.jetty.client.source" version="9.4.8.v20171121"/>
-      <unit id="org.eclipse.jetty.continuation" version="9.4.8.v20171121"/>
-      <unit id="org.eclipse.jetty.continuation.source" version="9.4.8.v20171121"/>
-      <unit id="org.eclipse.jetty.http" version="9.4.8.v20171121"/>
-      <unit id="org.eclipse.jetty.http.source" version="9.4.8.v20171121"/>
-      <unit id="org.eclipse.jetty.io" version="9.4.8.v20171121"/>
-      <unit id="org.eclipse.jetty.io.source" version="9.4.8.v20171121"/>
-      <unit id="org.eclipse.jetty.security" version="9.4.8.v20171121"/>
-      <unit id="org.eclipse.jetty.security.source" version="9.4.8.v20171121"/>
-      <unit id="org.eclipse.jetty.server" version="9.4.8.v20171121"/>
-      <unit id="org.eclipse.jetty.server.source" version="9.4.8.v20171121"/>
-      <unit id="org.eclipse.jetty.servlet" version="9.4.8.v20171121"/>
-      <unit id="org.eclipse.jetty.servlet.source" version="9.4.8.v20171121"/>
-      <unit id="org.eclipse.jetty.util" version="9.4.8.v20171121"/>
-      <unit id="org.eclipse.jetty.util.source" version="9.4.8.v20171121"/>
-      <repository id="jetty-9.4.8" location="http://download.eclipse.org/jetty/updates/jetty-bundles-9.x/9.4.8.v20171121"/>
+      <unit id="org.eclipse.jetty.client" version="9.4.11.v20180605"/>
+      <unit id="org.eclipse.jetty.client.source" version="9.4.11.v20180605"/>
+      <unit id="org.eclipse.jetty.continuation" version="9.4.11.v20180605"/>
+      <unit id="org.eclipse.jetty.continuation.source" version="9.4.11.v20180605"/>
+      <unit id="org.eclipse.jetty.http" version="9.4.11.v20180605"/>
+      <unit id="org.eclipse.jetty.http.source" version="9.4.11.v20180605"/>
+      <unit id="org.eclipse.jetty.io" version="9.4.11.v20180605"/>
+      <unit id="org.eclipse.jetty.io.source" version="9.4.11.v20180605"/>
+      <unit id="org.eclipse.jetty.security" version="9.4.11.v20180605"/>
+      <unit id="org.eclipse.jetty.security.source" version="9.4.11.v20180605"/>
+      <unit id="org.eclipse.jetty.server" version="9.4.11.v20180605"/>
+      <unit id="org.eclipse.jetty.server.source" version="9.4.11.v20180605"/>
+      <unit id="org.eclipse.jetty.servlet" version="9.4.11.v20180605"/>
+      <unit id="org.eclipse.jetty.servlet.source" version="9.4.11.v20180605"/>
+      <unit id="org.eclipse.jetty.util" version="9.4.11.v20180605"/>
+      <unit id="org.eclipse.jetty.util.source" version="9.4.11.v20180605"/>
+      <repository id="jetty-9.4.11" location="http://download.eclipse.org/jetty/updates/jetty-bundles-9.x/9.4.11.v20180605"/>
     </location>
     <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
       <unit id="org.apache.ant" version="1.9.6.v201510161327"/>
       <unit id="org.apache.ant.source" version="1.9.6.v201510161327"/>
-      <unit id="org.apache.commons.codec" version="1.9.0.v20170208-1614"/>
-      <unit id="org.apache.commons.codec.source" version="1.9.0.v20170208-1614"/>
+      <unit id="org.apache.commons.codec" version="1.10.0.v20180409-1845"/>
+      <unit id="org.apache.commons.codec.source" version="1.10.0.v20180409-1845"/>
       <unit id="org.apache.commons.compress" version="1.15.0.v20180119-1613"/>
       <unit id="org.apache.commons.compress.source" version="1.15.0.v20180119-1613"/>
-      <unit id="org.apache.commons.logging" version="1.1.1.v201101211721"/>
-      <unit id="org.apache.commons.logging.source" version="1.1.1.v201101211721"/>
-      <unit id="org.apache.httpcomponents.httpcore" version="4.4.6.v20170210-0925"/>
-      <unit id="org.apache.httpcomponents.httpcore.source" version="4.4.6.v20170210-0925"/>
-      <unit id="org.apache.httpcomponents.httpclient" version="4.5.2.v20180410-1551"/>
-      <unit id="org.apache.httpcomponents.httpclient.source" version="4.5.2.v20180410-1551"/>
+      <unit id="org.apache.commons.logging" version="1.2.0.v20180409-1502"/>
+      <unit id="org.apache.commons.logging.source" version="1.2.0.v20180409-1502"/>
+      <unit id="org.apache.httpcomponents.httpclient" version="4.5.6.v20190503-0009"/>
+      <unit id="org.apache.httpcomponents.httpclient.source" version="4.5.6.v20190503-0009"/>
+      <unit id="org.apache.httpcomponents.httpcore" version="4.4.10.v20190123-2214"/>
+      <unit id="org.apache.httpcomponents.httpcore.source" version="4.4.10.v20190123-2214"/>
       <unit id="org.apache.log4j" version="1.2.15.v201012070815"/>
       <unit id="org.apache.log4j.source" version="1.2.15.v201012070815"/>
       <unit id="org.kohsuke.args4j" version="2.33.0.v20160323-2218"/>
@@ -46,14 +46,18 @@
       <unit id="org.hamcrest.library.source" version="1.3.0.v20180524-2246"/>
       <unit id="javaewah" version="1.1.6.v20160919-1400"/>
       <unit id="javaewah.source" version="1.1.6.v20160919-1400"/>
-      <unit id="org.objenesis" version="1.0.0.v201505121915"/>
-      <unit id="org.objenesis.source" version="1.0.0.v201505121915"/>
-      <unit id="org.mockito" version="1.8.4.v201303031500"/>
-      <unit id="org.mockito.source" version="1.8.4.v201303031500"/>
+      <unit id="org.objenesis" version="2.6.0.v20180420-1519"/>
+      <unit id="org.objenesis.source" version="2.6.0.v20180420-1519"/>
+      <unit id="org.mockito" version="2.23.0.v20190527-1420"/>
+      <unit id="org.mockito.source" version="2.23.0.v20190527-1420"/>
+      <unit id="net.bytebuddy.byte-buddy" version="1.9.0.v20181107-1410"/>
+      <unit id="net.bytebuddy.byte-buddy.source" version="1.9.0.v20181107-1410"/>
+      <unit id="net.bytebuddy.byte-buddy-agent" version="1.9.0.v20181106-1534"/>
+      <unit id="net.bytebuddy.byte-buddy-agent.source" version="1.9.0.v20181106-1534"/>
       <unit id="com.google.gson" version="2.8.2.v20180104-1110"/>
       <unit id="com.google.gson.source" version="2.8.2.v20180104-1110"/>
-      <unit id="com.jcraft.jsch" version="0.1.54.v20170116-1932"/>
-      <unit id="com.jcraft.jsch.source" version="0.1.54.v20170116-1932"/>
+      <unit id="com.jcraft.jsch" version="0.1.55.v20190404-1902"/>
+      <unit id="com.jcraft.jsch.source" version="0.1.55.v20190404-1902"/>
       <unit id="org.junit" version="4.12.0.v201504281640"/>
       <unit id="org.junit.source" version="4.12.0.v201504281640"/>
       <unit id="javax.servlet" version="3.1.0.v201410161800"/>
@@ -66,7 +70,7 @@
       <unit id="org.slf4j.impl.log4j12.source" version="1.7.2.v20131105-2200"/>
       <unit id="com.jcraft.jzlib" version="1.1.1.v201205102305"/>
       <unit id="com.jcraft.jzlib.source" version="1.1.1.v201205102305"/>
-      <repository location="http://download.eclipse.org/tools/orbit/downloads/drops/R20180606145124/repository"/>
+      <repository location="http://download.eclipse.org/tools/orbit/downloads/drops/R20190602212107/repository"/>
     </location>
     <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
       <unit id="org.eclipse.osgi" version="0.0.0"/>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.8.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.8.tpd
index 0ea2b99..ba6337d 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.8.tpd
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.8.tpd
@@ -1,7 +1,7 @@
 target "jgit-4.8" with source configurePhase
 
-include "projects/jetty-9.4.8.tpd"
-include "orbit/R20180606145124-Photon.tpd"
+include "projects/jetty-9.4.11.tpd"
+include "orbit/R20190602212107-2019-06.tpd"
 
 location "http://download.eclipse.org/releases/photon/" {
 	org.eclipse.osgi lazy
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.9-staging.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.9-staging.target
new file mode 100644
index 0000000..66b8b3a
--- /dev/null
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.9-staging.target
@@ -0,0 +1,80 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<?pde?>
+<!-- generated with https://github.com/eclipse-cbi/targetplatform-dsl -->
+<target name="jgit-4.9-staging" sequenceNumber="1565603678">
+  <locations>
+    <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
+      <unit id="org.eclipse.jetty.client" version="9.4.11.v20180605"/>
+      <unit id="org.eclipse.jetty.client.source" version="9.4.11.v20180605"/>
+      <unit id="org.eclipse.jetty.continuation" version="9.4.11.v20180605"/>
+      <unit id="org.eclipse.jetty.continuation.source" version="9.4.11.v20180605"/>
+      <unit id="org.eclipse.jetty.http" version="9.4.11.v20180605"/>
+      <unit id="org.eclipse.jetty.http.source" version="9.4.11.v20180605"/>
+      <unit id="org.eclipse.jetty.io" version="9.4.11.v20180605"/>
+      <unit id="org.eclipse.jetty.io.source" version="9.4.11.v20180605"/>
+      <unit id="org.eclipse.jetty.security" version="9.4.11.v20180605"/>
+      <unit id="org.eclipse.jetty.security.source" version="9.4.11.v20180605"/>
+      <unit id="org.eclipse.jetty.server" version="9.4.11.v20180605"/>
+      <unit id="org.eclipse.jetty.server.source" version="9.4.11.v20180605"/>
+      <unit id="org.eclipse.jetty.servlet" version="9.4.11.v20180605"/>
+      <unit id="org.eclipse.jetty.servlet.source" version="9.4.11.v20180605"/>
+      <unit id="org.eclipse.jetty.util" version="9.4.11.v20180605"/>
+      <unit id="org.eclipse.jetty.util.source" version="9.4.11.v20180605"/>
+      <repository id="jetty-9.4.11" location="http://download.eclipse.org/jetty/updates/jetty-bundles-9.x/9.4.11.v20180605"/>
+    </location>
+    <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
+      <unit id="org.apache.ant" version="1.9.6.v201510161327"/>
+      <unit id="org.apache.ant.source" version="1.9.6.v201510161327"/>
+      <unit id="org.apache.commons.codec" version="1.10.0.v20180409-1845"/>
+      <unit id="org.apache.commons.codec.source" version="1.10.0.v20180409-1845"/>
+      <unit id="org.apache.commons.compress" version="1.15.0.v20180119-1613"/>
+      <unit id="org.apache.commons.compress.source" version="1.15.0.v20180119-1613"/>
+      <unit id="org.apache.commons.logging" version="1.2.0.v20180409-1502"/>
+      <unit id="org.apache.commons.logging.source" version="1.2.0.v20180409-1502"/>
+      <unit id="org.apache.httpcomponents.httpclient" version="4.5.6.v20190503-0009"/>
+      <unit id="org.apache.httpcomponents.httpclient.source" version="4.5.6.v20190503-0009"/>
+      <unit id="org.apache.httpcomponents.httpcore" version="4.4.10.v20190123-2214"/>
+      <unit id="org.apache.httpcomponents.httpcore.source" version="4.4.10.v20190123-2214"/>
+      <unit id="org.apache.log4j" version="1.2.15.v201012070815"/>
+      <unit id="org.apache.log4j.source" version="1.2.15.v201012070815"/>
+      <unit id="org.kohsuke.args4j" version="2.33.0.v20160323-2218"/>
+      <unit id="org.kohsuke.args4j.source" version="2.33.0.v20160323-2218"/>
+      <unit id="org.hamcrest" version="1.1.0.v20090501071000"/>
+      <unit id="org.hamcrest.core" version="1.3.0.v20180420-1519"/>
+      <unit id="org.hamcrest.core.source" version="1.3.0.v20180420-1519"/>
+      <unit id="org.hamcrest.library" version="1.3.0.v20180524-2246"/>
+      <unit id="org.hamcrest.library.source" version="1.3.0.v20180524-2246"/>
+      <unit id="javaewah" version="1.1.6.v20160919-1400"/>
+      <unit id="javaewah.source" version="1.1.6.v20160919-1400"/>
+      <unit id="org.objenesis" version="2.6.0.v20180420-1519"/>
+      <unit id="org.objenesis.source" version="2.6.0.v20180420-1519"/>
+      <unit id="org.mockito" version="2.23.0.v20190527-1420"/>
+      <unit id="org.mockito.source" version="2.23.0.v20190527-1420"/>
+      <unit id="net.bytebuddy.byte-buddy" version="1.9.0.v20181107-1410"/>
+      <unit id="net.bytebuddy.byte-buddy.source" version="1.9.0.v20181107-1410"/>
+      <unit id="net.bytebuddy.byte-buddy-agent" version="1.9.0.v20181106-1534"/>
+      <unit id="net.bytebuddy.byte-buddy-agent.source" version="1.9.0.v20181106-1534"/>
+      <unit id="com.google.gson" version="2.8.2.v20180104-1110"/>
+      <unit id="com.google.gson.source" version="2.8.2.v20180104-1110"/>
+      <unit id="com.jcraft.jsch" version="0.1.55.v20190404-1902"/>
+      <unit id="com.jcraft.jsch.source" version="0.1.55.v20190404-1902"/>
+      <unit id="org.junit" version="4.12.0.v201504281640"/>
+      <unit id="org.junit.source" version="4.12.0.v201504281640"/>
+      <unit id="javax.servlet" version="3.1.0.v201410161800"/>
+      <unit id="javax.servlet.source" version="3.1.0.v201410161800"/>
+      <unit id="org.tukaani.xz" version="1.6.0.v20170629-1752"/>
+      <unit id="org.tukaani.xz.source" version="1.6.0.v20170629-1752"/>
+      <unit id="org.slf4j.api" version="1.7.2.v20121108-1250"/>
+      <unit id="org.slf4j.api.source" version="1.7.2.v20121108-1250"/>
+      <unit id="org.slf4j.impl.log4j12" version="1.7.2.v20131105-2200"/>
+      <unit id="org.slf4j.impl.log4j12.source" version="1.7.2.v20131105-2200"/>
+      <unit id="com.jcraft.jzlib" version="1.1.1.v201205102305"/>
+      <unit id="com.jcraft.jzlib.source" version="1.1.1.v201205102305"/>
+      <repository location="http://download.eclipse.org/tools/orbit/downloads/drops/R20190602212107/repository"/>
+    </location>
+    <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
+      <unit id="org.eclipse.osgi" version="0.0.0"/>
+      <repository location="http://download.eclipse.org/staging/2018-09/"/>
+    </location>
+  </locations>
+</target>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.9-staging.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.9-staging.tpd
new file mode 100644
index 0000000..fc01799
--- /dev/null
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.9-staging.tpd
@@ -0,0 +1,8 @@
+target "jgit-4.9-staging" with source configurePhase
+
+include "projects/jetty-9.4.11.tpd"
+include "orbit/R20190602212107-2019-06.tpd"
+
+location "http://download.eclipse.org/staging/2018-09/" {
+	org.eclipse.osgi lazy
+}
\ No newline at end of file
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
new file mode 100644
index 0000000..f49571e
--- /dev/null
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/R20190602212107-2019-06.tpd
@@ -0,0 +1,52 @@
+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" {
+	org.apache.ant [1.9.6.v201510161327,1.9.6.v201510161327]
+	org.apache.ant.source [1.9.6.v201510161327,1.9.6.v201510161327]
+	org.apache.commons.codec [1.10.0.v20180409-1845,1.10.0.v20180409-1845]
+	org.apache.commons.codec.source [1.10.0.v20180409-1845,1.10.0.v20180409-1845]
+	org.apache.commons.compress [1.15.0.v20180119-1613,1.15.0.v20180119-1613]
+	org.apache.commons.compress.source [1.15.0.v20180119-1613,1.15.0.v20180119-1613s]
+	org.apache.commons.logging [1.2.0.v20180409-1502,1.2.0.v20180409-1502]
+	org.apache.commons.logging.source [1.2.0.v20180409-1502,1.2.0.v20180409-1502]
+	org.apache.httpcomponents.httpclient [4.5.6.v20190503-0009,4.5.6.v20190503-0009]
+	org.apache.httpcomponents.httpclient.source [4.5.6.v20190503-0009,4.5.6.v20190503-0009]
+	org.apache.httpcomponents.httpcore [4.4.10.v20190123-2214,4.4.10.v20190123-2214]
+	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.kohsuke.args4j [2.33.0.v20160323-2218,2.33.0.v20160323-2218]
+	org.kohsuke.args4j.source [2.33.0.v20160323-2218,2.33.0.v20160323-2218]
+	org.hamcrest [1.1.0.v20090501071000,1.1.0.v20090501071000]
+	org.hamcrest.core [1.3.0.v20180420-1519,1.3.0.v20180420-1519]
+	org.hamcrest.core.source [1.3.0.v20180420-1519,1.3.0.v20180420-1519]
+	org.hamcrest.library [1.3.0.v20180524-2246,1.3.0.v20180524-2246]
+	org.hamcrest.library.source [1.3.0.v20180524-2246,1.3.0.v20180524-2246]
+	javaewah [1.1.6.v20160919-1400,1.1.6.v20160919-1400]
+	javaewah.source [1.1.6.v20160919-1400,1.1.6.v20160919-1400]
+	org.objenesis [2.6.0.v20180420-1519,2.6.0.v20180420-1519]
+	org.objenesis.source [2.6.0.v20180420-1519,2.6.0.v20180420-1519]
+	org.mockito [2.23.0.v20190527-1420,2.23.0.v20190527-1420]
+	org.mockito.source [2.23.0.v20190527-1420,2.23.0.v20190527-1420]
+	net.bytebuddy.byte-buddy [1.9.0.v20181107-1410,1.9.0.v20181107-1410]
+	net.bytebuddy.byte-buddy.source [1.9.0.v20181107-1410,1.9.0.v20181107-1410]
+	net.bytebuddy.byte-buddy-agent [1.9.0.v20181106-1534,1.9.0.v20181106-1534]
+	net.bytebuddy.byte-buddy-agent.source [1.9.0.v20181106-1534,1.9.0.v20181106-1534]
+	com.google.gson [2.8.2.v20180104-1110,2.8.2.v20180104-1110]
+	com.google.gson.source [2.8.2.v20180104-1110,2.8.2.v20180104-1110]
+	com.jcraft.jsch [0.1.55.v20190404-1902,0.1.55.v20190404-1902]
+	com.jcraft.jsch.source [0.1.55.v20190404-1902,0.1.55.v20190404-1902]
+	org.junit [4.12.0.v201504281640,4.12.0.v201504281640]
+	org.junit.source [4.12.0.v201504281640,4.12.0.v201504281640]
+	javax.servlet [3.1.0.v201410161800,3.1.0.v201410161800]
+	javax.servlet.source [3.1.0.v201410161800,3.1.0.v201410161800]
+	org.tukaani.xz [1.6.0.v20170629-1752,1.6.0.v20170629-1752]
+	org.tukaani.xz.source [1.6.0.v20170629-1752,1.6.0.v20170629-1752]
+	org.slf4j.api [1.7.2.v20121108-1250,1.7.2.v20121108-1250]
+	org.slf4j.api.source [1.7.2.v20121108-1250,1.7.2.v20121108-1250]
+	org.slf4j.impl.log4j12 [1.7.2.v20131105-2200,1.7.2.v20131105-2200]
+	org.slf4j.impl.log4j12.source [1.7.2.v20131105-2200,1.7.2.v20131105-2200]
+	com.jcraft.jzlib [1.1.1.v201205102305,1.1.1.v201205102305]
+	com.jcraft.jzlib.source [1.1.1.v201205102305,1.1.1.v201205102305]
+}
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/pom.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/pom.xml
index 3c60a83..c13677f 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.0.4-SNAPSHOT</version>
+    <version>5.1.11-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.target</artifactId>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/projects/jetty-9.4.11.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/projects/jetty-9.4.11.tpd
new file mode 100644
index 0000000..d2fd901
--- /dev/null
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/projects/jetty-9.4.11.tpd
@@ -0,0 +1,20 @@
+target "jetty-9.4.11" with source configurePhase
+
+location jetty-9.4.11 "http://download.eclipse.org/jetty/updates/jetty-bundles-9.x/9.4.11.v20180605" {
+	org.eclipse.jetty.client [9.4.11.v20180605,9.4.11.v20180605]
+	org.eclipse.jetty.client.source [9.4.11.v20180605,9.4.11.v20180605]
+	org.eclipse.jetty.continuation [9.4.11.v20180605,9.4.11.v20180605]
+	org.eclipse.jetty.continuation.source [9.4.11.v20180605,9.4.11.v20180605]
+	org.eclipse.jetty.http [9.4.11.v20180605,9.4.11.v20180605]
+	org.eclipse.jetty.http.source [9.4.11.v20180605,9.4.11.v20180605]
+	org.eclipse.jetty.io [9.4.11.v20180605,9.4.11.v20180605]
+	org.eclipse.jetty.io.source [9.4.11.v20180605,9.4.11.v20180605]
+	org.eclipse.jetty.security [9.4.11.v20180605,9.4.11.v20180605]
+	org.eclipse.jetty.security.source [9.4.11.v20180605,9.4.11.v20180605]
+	org.eclipse.jetty.server [9.4.11.v20180605,9.4.11.v20180605]
+	org.eclipse.jetty.server.source [9.4.11.v20180605,9.4.11.v20180605]
+	org.eclipse.jetty.servlet [9.4.11.v20180605,9.4.11.v20180605]
+	org.eclipse.jetty.servlet.source [9.4.11.v20180605,9.4.11.v20180605]
+	org.eclipse.jetty.util [9.4.11.v20180605,9.4.11.v20180605]
+	org.eclipse.jetty.util.source [9.4.11.v20180605,9.4.11.v20180605]
+}
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/projects/jetty-9.4.8.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/projects/jetty-9.4.8.tpd
deleted file mode 100644
index e37a062..0000000
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/projects/jetty-9.4.8.tpd
+++ /dev/null
@@ -1,20 +0,0 @@
-target "jetty-9.4.8" with source configurePhase
-
-location jetty-9.4.8 "http://download.eclipse.org/jetty/updates/jetty-bundles-9.x/9.4.8.v20171121" {
-	org.eclipse.jetty.client [9.4.8.v20171121,9.4.8.v20171121]
-	org.eclipse.jetty.client.source [9.4.8.v20171121,9.4.8.v20171121]
-	org.eclipse.jetty.continuation [9.4.8.v20171121,9.4.8.v20171121]
-	org.eclipse.jetty.continuation.source [9.4.8.v20171121,9.4.8.v20171121]
-	org.eclipse.jetty.http [9.4.8.v20171121,9.4.8.v20171121]
-	org.eclipse.jetty.http.source [9.4.8.v20171121,9.4.8.v20171121]
-	org.eclipse.jetty.io [9.4.8.v20171121,9.4.8.v20171121]
-	org.eclipse.jetty.io.source [9.4.8.v20171121,9.4.8.v20171121]
-	org.eclipse.jetty.security [9.4.8.v20171121,9.4.8.v20171121]
-	org.eclipse.jetty.security.source [9.4.8.v20171121,9.4.8.v20171121]
-	org.eclipse.jetty.server [9.4.8.v20171121,9.4.8.v20171121]
-	org.eclipse.jetty.server.source [9.4.8.v20171121,9.4.8.v20171121]
-	org.eclipse.jetty.servlet [9.4.8.v20171121,9.4.8.v20171121]
-	org.eclipse.jetty.servlet.source [9.4.8.v20171121,9.4.8.v20171121]
-	org.eclipse.jetty.util [9.4.8.v20171121,9.4.8.v20171121]
-	org.eclipse.jetty.util.source [9.4.8.v20171121,9.4.8.v20171121]
-}
diff --git a/org.eclipse.jgit.packaging/pom.xml b/org.eclipse.jgit.packaging/pom.xml
index 3cb21c1..81cdde0 100644
--- a/org.eclipse.jgit.packaging/pom.xml
+++ b/org.eclipse.jgit.packaging/pom.xml
@@ -47,19 +47,15 @@
     xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
   <modelVersion>4.0.0</modelVersion>
 
-  <prerequisites>
-    <maven>3.0</maven>
-  </prerequisites>
-
   <groupId>org.eclipse.jgit</groupId>
   <artifactId>jgit.tycho.parent</artifactId>
-  <version>5.0.4-SNAPSHOT</version>
+  <version>5.1.11-SNAPSHOT</version>
   <packaging>pom</packaging>
 
   <name>JGit Tycho Parent</name>
 
   <properties>
-    <tycho-version>1.1.0</tycho-version>
+    <tycho-version>1.3.0</tycho-version>
     <tycho-extras-version>${tycho-version}</tycho-extras-version>
     <target-platform>jgit-4.6</target-platform>
   </properties>
@@ -169,7 +165,7 @@
         <plugin>
           <groupId>org.apache.maven.plugins</groupId>
           <artifactId>maven-resources-plugin</artifactId>
-          <version>3.0.2</version>
+          <version>3.1.0</version>
           <configuration>
             <encoding>ISO-8859-1</encoding>
           </configuration>
@@ -224,6 +220,21 @@
           <version>${tycho-version}</version>
         </plugin>
         <plugin>
+          <groupId>org.eclipse.tycho</groupId>
+          <artifactId>tycho-p2-publisher-plugin</artifactId>
+          <version>${tycho-version}</version>
+        </plugin>
+        <plugin>
+          <groupId>org.eclipse.tycho</groupId>
+          <artifactId>tycho-p2-repository-plugin</artifactId>
+          <version>${tycho-version}</version>
+        </plugin>
+        <plugin>
+          <groupId>org.eclipse.tycho</groupId>
+          <artifactId>tycho-packaging-plugin</artifactId>
+          <version>${tycho-version}</version>
+        </plugin>
+        <plugin>
           <groupId>org.eclipse.tycho.extras</groupId>
           <artifactId>tycho-pack200a-plugin</artifactId>
           <version>${tycho-extras-version}</version>
@@ -236,12 +247,31 @@
         <plugin>
           <groupId>org.eclipse.cbi.maven.plugins</groupId>
           <artifactId>eclipse-jarsigner-plugin</artifactId>
-          <version>1.1.4</version>
+          <version>1.1.5</version>
         </plugin>
         <plugin>
           <groupId>org.codehaus.mojo</groupId>
           <artifactId>build-helper-maven-plugin</artifactId>
-          <version>1.12</version>
+          <version>3.0.0</version>
+        </plugin>
+        <plugin>
+          <artifactId>maven-clean-plugin</artifactId>
+          <version>3.1.0</version>
+        </plugin>
+        <plugin>
+          <groupId>org.apache.maven.plugins</groupId>
+          <artifactId>maven-deploy-plugin</artifactId>
+          <version>3.0.0-M1</version>
+        </plugin>
+        <plugin>
+          <groupId>org.apache.maven.plugins</groupId>
+          <artifactId>maven-install-plugin</artifactId>
+          <version>3.0.0-M1</version>
+        </plugin>
+        <plugin>
+          <groupId>org.apache.maven.plugins</groupId>
+          <artifactId>maven-site-plugin</artifactId>
+          <version>3.7.1</version>
         </plugin>
       </plugins>
     </pluginManagement>
diff --git a/org.eclipse.jgit.pgm.test/BUILD b/org.eclipse.jgit.pgm.test/BUILD
index 5bedf9a..f32fa12 100644
--- a/org.eclipse.jgit.pgm.test/BUILD
+++ b/org.eclipse.jgit.pgm.test/BUILD
@@ -7,7 +7,7 @@
     name = "pgm",
     srcs = glob(["tst/**/*.java"]),
     jvm_flags = [
-        "-Xmx256m",
+        "-Xmx512m",
         "-Dfile.encoding=UTF-8",
     ],
     tags = ["pgm"],
diff --git a/org.eclipse.jgit.pgm.test/META-INF/MANIFEST.MF b/org.eclipse.jgit.pgm.test/META-INF/MANIFEST.MF
index 39910ce..f1c1766 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.0.4.qualifier
+Bundle-Version: 5.1.11.qualifier
 Bundle-Vendor: %provider_name
 Bundle-Localization: plugin
 Bundle-ActivationPolicy: lazy
 Bundle-RequiredExecutionEnvironment: JavaSE-1.8
-Import-Package: org.eclipse.jgit.api;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.api.errors;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.diff;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.dircache;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.internal.storage.file;version="5.0.4",
- org.eclipse.jgit.junit;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.lib;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.merge;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.pgm;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.pgm.internal;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.pgm.opt;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.revwalk;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.storage.file;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.transport;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.treewalk;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.util;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.util.io;version="[5.0.4,5.1.0)",
+Import-Package: org.eclipse.jgit.api;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.api.errors;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.diff;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.dircache;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.internal.storage.file;version="5.1.11",
+ org.eclipse.jgit.junit;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.lib;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.merge;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.pgm;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.pgm.internal;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.pgm.opt;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.revwalk;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.storage.file;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.transport;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.treewalk;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.util;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.util.io;version="[5.1.11,5.2.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 30c32ed..40c0c4a 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.0.4-SNAPSHOT</version>
+    <version>5.1.11-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.pgm.test</artifactId>
@@ -109,7 +109,7 @@
       <plugin>
         <artifactId>maven-surefire-plugin</artifactId>
         <configuration>
-          <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/pgm/CLIGitCommand.java b/org.eclipse.jgit.pgm.test/src/org/eclipse/jgit/pgm/CLIGitCommand.java
index 44ad79d..81875f1 100644
--- a/org.eclipse.jgit.pgm.test/src/org/eclipse/jgit/pgm/CLIGitCommand.java
+++ b/org.eclipse.jgit.pgm.test/src/org/eclipse/jgit/pgm/CLIGitCommand.java
@@ -230,7 +230,7 @@ else if (r.length() > 0) {
 		}
 		if (r.length() > 0)
 			list.add(r.toString());
-		return list.toArray(new String[list.size()]);
+		return list.toArray(new String[0]);
 	}
 
 	public static class Result {
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 afeb5ef..47eb156 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
@@ -42,7 +42,7 @@
  */
 package org.eclipse.jgit.pgm;
 
-import static org.eclipse.jgit.lib.Constants.CHARSET;
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.fail;
@@ -544,7 +544,7 @@ public void testArchiveWithLongFilename() throws Exception {
 
 		byte[] result = CLIGitCommand.executeRaw(
 				"git archive --format=zip HEAD", db).outBytes();
-		assertArrayEquals(l.toArray(new String[l.size()]),
+		assertArrayEquals(l.toArray(new String[0]),
 				listZipEntries(result));
 	}
 
@@ -564,7 +564,7 @@ public void testTarWithLongFilename() throws Exception {
 
 		byte[] result = CLIGitCommand.executeRaw(
 				"git archive --format=tar HEAD", db).outBytes();
-		assertArrayEquals(l.toArray(new String[l.size()]),
+		assertArrayEquals(l.toArray(new String[0]),
 				listTarEntries(result));
 	}
 
@@ -612,7 +612,7 @@ private Process spawnAssumingCommandPresent(String... cmdline) {
 
 	private BufferedReader readFromProcess(Process proc) throws Exception {
 		return new BufferedReader(
-				new InputStreamReader(proc.getInputStream(), CHARSET));
+				new InputStreamReader(proc.getInputStream(), UTF_8));
 	}
 
 	private void grepForEntry(String name, String mode, String... cmdline)
@@ -697,7 +697,7 @@ private void writeRaw(String filename, byte[] data)
 			while ((e = in.getNextEntry()) != null)
 				l.add(e.getName());
 		}
-		return l.toArray(new String[l.size()]);
+		return l.toArray(new String[0]);
 	}
 
 	private static Future<Object> writeAsync(OutputStream stream, byte[] data) {
@@ -730,7 +730,7 @@ public Object call() throws IOException {
 				while ((line = reader.readLine()) != null)
 					l.add(line);
 
-				return l.toArray(new String[l.size()]);
+				return l.toArray(new String[0]);
 			} finally {
 				writing.get();
 				proc.destroy();
@@ -750,11 +750,11 @@ public Object call() throws IOException {
 			// found!
 			List<String> l = new ArrayList<>();
 			BufferedReader reader = new BufferedReader(
-					new InputStreamReader(in, CHARSET));
+					new InputStreamReader(in, UTF_8));
 			String line;
 			while ((line = reader.readLine()) != null)
 				l.add(line);
-			return l.toArray(new String[l.size()]);
+			return l.toArray(new String[0]);
 		}
 
 		// not found
@@ -774,7 +774,7 @@ public Object call() throws IOException {
 				while ((line = reader.readLine()) != null)
 					l.add(line);
 
-				return l.toArray(new String[l.size()]);
+				return l.toArray(new String[0]);
 			} finally {
 				writing.get();
 				proc.destroy();
diff --git a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/ConfigTest.java b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/ConfigTest.java
index 0ce6451..1ce86d1 100644
--- a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/ConfigTest.java
+++ b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/ConfigTest.java
@@ -43,14 +43,14 @@
 package org.eclipse.jgit.pgm;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
 
-import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.List;
+import java.util.HashMap;
+import java.util.Map;
 
 import org.eclipse.jgit.api.Git;
 import org.eclipse.jgit.lib.CLIRepositoryTestCase;
-import org.eclipse.jgit.util.FS;
 import org.eclipse.jgit.util.SystemReader;
 import org.junit.Before;
 import org.junit.Test;
@@ -65,6 +65,7 @@ public void setUp() throws Exception {
 		}
 	}
 
+	@SuppressWarnings("boxing")
 	@Test
 	public void testListConfig() throws Exception {
 		boolean isWindows = SystemReader.getInstance().getProperty("os.name")
@@ -73,19 +74,31 @@ public void testListConfig() throws Exception {
 				.equals("Mac OS X");
 
 		String[] output = execute("git config --list");
-		List<String> expect = new ArrayList<>();
-		expect.add("gc.autoDetach=false");
-		expect.add("core.filemode=" + !isWindows);
-		expect.add("core.logallrefupdates=true");
-		if (isMac)
-			expect.add("core.precomposeunicode=true");
-		expect.add("core.repositoryformatversion=0");
-		if (!FS.DETECTED.supportsSymlinks())
-			expect.add("core.symlinks=false");
-		expect.add(""); // ends with LF (last line empty)
-		assertEquals("expected default configuration",
-				Arrays.asList(expect.toArray()).toString(),
-				Arrays.asList(output).toString());
+
+		Map<String, String> options = parseOptions(output);
+
+		assertEquals(!isWindows, Boolean.valueOf(options.get("core.filemode")));
+		assertTrue((Boolean.valueOf(options.get("core.logallrefupdates"))));
+		if (isMac) {
+			assertTrue(
+					(Boolean.valueOf(options.get("core.precomposeunicode"))));
+		}
+		assertEquals(Integer.valueOf(0),
+				Integer.valueOf(options.get("core.repositoryformatversion")));
+	}
+
+	private Map<String, String> parseOptions(String[] output) {
+		Map<String, String> options = new HashMap<>();
+		Arrays.stream(output).forEachOrdered(s -> {
+			int p = s.indexOf('=');
+			if (p == -1) {
+				return;
+			}
+			String key = s.substring(0, p);
+			String value = s.substring(p + 1);
+			options.put(key, value);
+		});
+		return options;
 	}
 
 }
diff --git a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/LsFilesTest.java b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/LsFilesTest.java
new file mode 100644
index 0000000..d290206
--- /dev/null
+++ b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/LsFilesTest.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2017, 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.pgm;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.io.File;
+
+import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.junit.JGitTestUtil;
+import org.eclipse.jgit.lib.CLIRepositoryTestCase;
+import org.eclipse.jgit.util.FileUtils;
+import org.junit.Before;
+import org.junit.Test;
+
+public class LsFilesTest extends CLIRepositoryTestCase {
+
+	@Before
+	@Override
+	public void setUp() throws Exception {
+		super.setUp();
+		try (Git git = new Git(db)) {
+			JGitTestUtil.writeTrashFile(db, "hello", "hello");
+			JGitTestUtil.writeTrashFile(db, "dir", "world", "world");
+			git.add().addFilepattern("dir").call();
+			git.commit().setMessage("Initial commit").call();
+
+			JGitTestUtil.writeTrashFile(db, "hello2", "hello");
+			git.add().addFilepattern("hello2").call();
+			FileUtils.createSymLink(new File(db.getWorkTree(), "link"),
+					"target");
+			FileUtils.mkdir(new File(db.getWorkTree(), "target"));
+			writeTrashFile("target/file", "someData");
+			git.add().addFilepattern("target").addFilepattern("link").call();
+			git.commit().setMessage("hello2").call();
+
+			JGitTestUtil.writeTrashFile(db, "staged", "x");
+			JGitTestUtil.deleteTrashFile(db, "hello2");
+			git.add().addFilepattern("staged").call();
+			JGitTestUtil.writeTrashFile(db, "untracked", "untracked");
+		}
+	}
+
+	@Test
+	public void testHelp() throws Exception {
+		String[] result = execute("git ls-files -h");
+		assertTrue(result[1].startsWith("jgit ls-files"));
+	}
+
+	@Test
+	public void testLsFiles() throws Exception {
+		assertArrayEquals(new String[] { "dir/world", "hello2", "link",
+				"staged", "target/file", "" }, execute("git ls-files"));
+	}
+}
diff --git a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/ProxyConfigTest.java b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/ProxyConfigTest.java
index 40a223d..42530f3 100644
--- a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/ProxyConfigTest.java
+++ b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/ProxyConfigTest.java
@@ -37,6 +37,7 @@
  */
 package org.eclipse.jgit.pgm;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.junit.Assert.assertEquals;
 
 import java.io.ByteArrayOutputStream;
@@ -47,7 +48,6 @@
 import java.util.List;
 import java.util.Map;
 
-import org.eclipse.jgit.lib.Constants;
 import org.junit.Before;
 import org.junit.Test;
 
@@ -204,7 +204,7 @@ private static String getOutput(Process p)
 			while ((length = inputStream.read(buffer)) != -1) {
 				result.write(buffer, 0, length);
 			}
-			return result.toString(Constants.CHARACTER_ENCODING);
+			return result.toString(UTF_8.name());
 		}
 	}
 }
diff --git a/org.eclipse.jgit.pgm/.settings/.api_filters b/org.eclipse.jgit.pgm/.settings/.api_filters
deleted file mode 100644
index 736a9ab..0000000
--- a/org.eclipse.jgit.pgm/.settings/.api_filters
+++ /dev/null
@@ -1,11 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<component id="org.eclipse.jgit.pgm" version="2">
-    <resource path="META-INF/MANIFEST.MF">
-        <filter id="925892614">
-            <message_arguments>
-                <message_argument value="5.0.4"/>
-                <message_argument value="4.11.0"/>
-            </message_arguments>
-        </filter>
-    </resource>
-</component>
diff --git a/org.eclipse.jgit.pgm/META-INF/MANIFEST.MF b/org.eclipse.jgit.pgm/META-INF/MANIFEST.MF
index f8aa095..4738a37 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.0.4.qualifier
+Bundle-Version: 5.1.11.qualifier
 Bundle-Vendor: %provider_name
 Bundle-ActivationPolicy: lazy
 Bundle-Localization: plugin
@@ -28,49 +28,49 @@
  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.0.4,5.1.0)",
- org.eclipse.jgit.api.errors;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.archive;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.awtui;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.blame;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.diff;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.dircache;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.errors;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.gitrepo;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.internal.ketch;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.internal.storage.dfs;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.internal.storage.file;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.internal.storage.io;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.internal.storage.pack;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.internal.storage.reftable;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.internal.storage.reftree;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.lfs;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.lfs.lib;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.lfs.server;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.lfs.server.fs;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.lfs.server.s3;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.lib;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.merge;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.nls;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.notes;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.revplot;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.revwalk;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.revwalk.filter;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.storage.file;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.storage.pack;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.transport;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.transport.http.apache;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.transport.resolver;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.treewalk;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.treewalk.filter;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.util;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.util.io;version="[5.0.4,5.1.0)",
+ org.eclipse.jgit.api;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.api.errors;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.archive;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.awtui;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.blame;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.diff;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.dircache;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.errors;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.gitrepo;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.internal.ketch;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.internal.storage.dfs;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.internal.storage.file;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.internal.storage.io;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.internal.storage.pack;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.internal.storage.reftable;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.internal.storage.reftree;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.lfs;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.lfs.lib;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.lfs.server;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.lfs.server.fs;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.lfs.server.s3;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.lib;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.merge;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.nls;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.notes;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.revplot;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.revwalk;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.revwalk.filter;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.storage.file;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.storage.pack;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.transport;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.transport.http.apache;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.transport.resolver;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.treewalk;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.treewalk.filter;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.util;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.util.io;version="[5.1.11,5.2.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.0.4";
+Export-Package: org.eclipse.jgit.console;version="5.1.11";
   uses:="org.eclipse.jgit.transport,
    org.eclipse.jgit.util",
- org.eclipse.jgit.pgm;version="5.0.4";
+ org.eclipse.jgit.pgm;version="5.1.11";
   uses:="org.eclipse.jgit.revwalk,
    org.eclipse.jgit.treewalk.filter,
    org.eclipse.jgit.pgm.opt,
@@ -81,11 +81,11 @@
    org.eclipse.jgit.treewalk,
    javax.swing,
    org.eclipse.jgit.transport",
- org.eclipse.jgit.pgm.debug;version="5.0.4";
+ org.eclipse.jgit.pgm.debug;version="5.1.11";
   uses:="org.eclipse.jgit.util.io,
    org.eclipse.jgit.pgm",
- org.eclipse.jgit.pgm.internal;version="5.0.4";x-friends:="org.eclipse.jgit.pgm.test,org.eclipse.jgit.test",
- org.eclipse.jgit.pgm.opt;version="5.0.4";
+ org.eclipse.jgit.pgm.internal;version="5.1.11";x-friends:="org.eclipse.jgit.pgm.test,org.eclipse.jgit.test",
+ org.eclipse.jgit.pgm.opt;version="5.1.11";
   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 1d4575e..8fb03e1 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.0.4.qualifier
-Eclipse-SourceBundle: org.eclipse.jgit.pgm;version="5.0.4.qualifier";roots="."
+Bundle-Version: 5.1.11.qualifier
+Eclipse-SourceBundle: org.eclipse.jgit.pgm;version="5.1.11.qualifier";roots="."
diff --git a/org.eclipse.jgit.pgm/META-INF/services/org.eclipse.jgit.pgm.TextBuiltin b/org.eclipse.jgit.pgm/META-INF/services/org.eclipse.jgit.pgm.TextBuiltin
index 9025473..39fbe2e 100644
--- a/org.eclipse.jgit.pgm/META-INF/services/org.eclipse.jgit.pgm.TextBuiltin
+++ b/org.eclipse.jgit.pgm/META-INF/services/org.eclipse.jgit.pgm.TextBuiltin
@@ -18,6 +18,7 @@
 org.eclipse.jgit.pgm.IndexPack
 org.eclipse.jgit.pgm.Init
 org.eclipse.jgit.pgm.Log
+org.eclipse.jgit.pgm.LsFiles
 org.eclipse.jgit.pgm.LsRemote
 org.eclipse.jgit.pgm.LsTree
 org.eclipse.jgit.pgm.Merge
diff --git a/org.eclipse.jgit.pgm/pom.xml b/org.eclipse.jgit.pgm/pom.xml
index db6745e..8c68a6d 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.0.4-SNAPSHOT</version>
+    <version>5.1.11-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.pgm</artifactId>
@@ -108,19 +108,16 @@
     <dependency>
       <groupId>org.slf4j</groupId>
       <artifactId>slf4j-api</artifactId>
-      <version>${slf4j-version}</version>
     </dependency>
 
     <dependency>
       <groupId>org.slf4j</groupId>
       <artifactId>slf4j-log4j12</artifactId>
-      <version>${slf4j-version}</version>
     </dependency>
 
     <dependency>
       <groupId>log4j</groupId>
       <artifactId>log4j</artifactId>
-      <version>${log4j-version}</version>
     </dependency>
 
     <dependency>
diff --git a/org.eclipse.jgit.pgm/resources/org/eclipse/jgit/pgm/internal/CLIText.properties b/org.eclipse.jgit.pgm/resources/org/eclipse/jgit/pgm/internal/CLIText.properties
index e937093..8ff9c6b 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
@@ -245,6 +245,7 @@
 usage_LFSRunStore=Store (fs | s3), store lfs objects in file system or Amazon S3
 usage_LFSStoreUrl=URL of the LFS store
 usage_LongFormat=Always output the long format
+usage_LsFiles=Show information about files in the index and the working tree
 usage_LsRemote=List references in a remote repository
 usage_lsRemoteHeads=Show only refs starting with refs/heads
 usage_lsRemoteTags=Show only refs starting with refs/tags
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 5754d7c..dbdccc1 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
@@ -43,7 +43,7 @@
 
 package org.eclipse.jgit.pgm;
 
-import static org.eclipse.jgit.lib.Constants.CHARSET;
+import static java.nio.charset.StandardCharsets.UTF_8;
 
 import java.io.BufferedReader;
 import java.io.IOException;
@@ -114,7 +114,7 @@ public static CommandRef get(String name) {
 	}
 
 	private static CommandRef[] toSortedArray(Collection<CommandRef> c) {
-		final CommandRef[] r = c.toArray(new CommandRef[c.size()]);
+		final CommandRef[] r = c.toArray(new CommandRef[0]);
 		Arrays.sort(r, new Comparator<CommandRef>() {
 			@Override
 			public int compare(CommandRef o1, CommandRef o2) {
@@ -148,7 +148,7 @@ private Enumeration<URL> catalogs() {
 
 	private void scan(URL cUrl) {
 		try (BufferedReader cIn = new BufferedReader(
-				new InputStreamReader(cUrl.openStream(), CHARSET))) {
+				new InputStreamReader(cUrl.openStream(), UTF_8))) {
 			String line;
 			while ((line = cIn.readLine()) != null) {
 				if (line.length() > 0 && !line.startsWith("#")) //$NON-NLS-1$
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 f9c1cf5..f914748 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
@@ -73,7 +73,7 @@ protected void run() throws Exception {
 			if (tree != null)
 				cmd.setTarget(tree);
 			cmd.setLong(longDesc);
-			cmd.setMatch(patterns.toArray(new String[patterns.size()]));
+			cmd.setMatch(patterns.toArray(new String[0]));
 			String result = null;
 			try {
 				result = cmd.call();
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
new file mode 100644
index 0000000..dc13000
--- /dev/null
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/LsFiles.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2017, 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.pgm;
+
+import static java.util.function.Predicate.isEqual;
+import static org.eclipse.jgit.lib.FileMode.EXECUTABLE_FILE;
+import static org.eclipse.jgit.lib.FileMode.GITLINK;
+import static org.eclipse.jgit.lib.FileMode.REGULAR_FILE;
+import static org.eclipse.jgit.lib.FileMode.SYMLINK;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import org.eclipse.jgit.dircache.DirCacheIterator;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.FileMode;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.treewalk.CanonicalTreeParser;
+import org.eclipse.jgit.treewalk.TreeWalk;
+import org.eclipse.jgit.treewalk.filter.PathFilterGroup;
+import org.eclipse.jgit.util.QuotedString;
+import org.kohsuke.args4j.Option;
+import org.kohsuke.args4j.spi.StopOptionHandler;
+
+@Command(common = true, usage = "usage_LsFiles")
+class LsFiles extends TextBuiltin {
+
+	@Option(name = "--", metaVar = "metaVar_paths", handler = StopOptionHandler.class)
+	private List<String> paths = new ArrayList<>();
+
+	@Override
+	protected void run() throws Exception {
+		try (RevWalk rw = new RevWalk(db);
+				TreeWalk tw = new TreeWalk(db)) {
+			final ObjectId head = db.resolve(Constants.HEAD);
+			if (head == null) {
+				return;
+			}
+			RevCommit c = rw.parseCommit(head);
+			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) {
+				tw.setFilter(PathFilterGroup.createFromStrings(paths));
+			}
+			tw.addTree(p);
+			tw.addTree(new DirCacheIterator(db.readDirCache()));
+			tw.setRecursive(true);
+			while (tw.next()) {
+				if (filterFileMode(tw, EXECUTABLE_FILE, GITLINK, REGULAR_FILE,
+						SYMLINK)) {
+					outw.println(
+							QuotedString.GIT_PATH.quote(tw.getPathString()));
+				}
+			}
+		}
+	}
+
+	private boolean filterFileMode(TreeWalk tw, FileMode... modes) {
+		return Arrays.stream(modes).anyMatch(isEqual(tw.getFileMode(0))
+				.or(isEqual(tw.getFileMode(1))));
+	}
+}
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 ac53de9..ad10ec9 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
@@ -44,7 +44,7 @@
 
 package org.eclipse.jgit.pgm;
 
-import static org.eclipse.jgit.lib.Constants.CHARSET;
+import static java.nio.charset.StandardCharsets.UTF_8;
 
 import java.io.File;
 import java.io.IOException;
@@ -71,7 +71,6 @@
 import org.eclipse.jgit.pgm.internal.CLIText;
 import org.eclipse.jgit.pgm.opt.CmdLineParser;
 import org.eclipse.jgit.pgm.opt.SubcommandHandler;
-import org.eclipse.jgit.storage.file.WindowCacheConfig;
 import org.eclipse.jgit.transport.HttpTransport;
 import org.eclipse.jgit.transport.http.apache.HttpClientConnectionFactory;
 import org.eclipse.jgit.util.CachedAuthenticator;
@@ -106,19 +105,10 @@ public class Main {
 
 	private ExecutorService gcExecutor;
 
-	private static final int MB = 1024 * 1024;
-
 	/**
 	 * <p>Constructor for Main.</p>
 	 */
 	public Main() {
-		final WindowCacheConfig c = new WindowCacheConfig();
-		c.setPackedGitMMAP(true);
-		c.setPackedGitWindowSize(8 * 1024);
-		c.setPackedGitLimit(10 * MB);
-		c.setDeltaBaseCacheLimit(10 * MB);
-		c.setStreamFileThreshold(50 * MB);
-		c.install();
 		HttpTransport.setConnectionFactory(new HttpClientConnectionFactory());
 		BuiltinLFS.register();
 		gcExecutor = Executors.newSingleThreadExecutor(new ThreadFactory() {
@@ -227,7 +217,7 @@ protected void run(String[] argv) throws Exception {
 	}
 
 	PrintWriter createErrorWriter() {
-		return new PrintWriter(new OutputStreamWriter(System.err, CHARSET));
+		return new PrintWriter(new OutputStreamWriter(System.err, UTF_8));
 	}
 
 	private void execute(String[] argv) throws Exception {
@@ -285,7 +275,7 @@ private void execute(String[] argv) throws Exception {
 		final TextBuiltin cmd = subcommand;
 		init(cmd);
 		try {
-			cmd.execute(arguments.toArray(new String[arguments.size()]));
+			cmd.execute(arguments.toArray(new String[0]));
 		} finally {
 			if (cmd.outw != null) {
 				cmd.outw.flush();
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Remote.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Remote.java
index 3308e18..63eba15 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Remote.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Remote.java
@@ -130,7 +130,7 @@ protected void run() throws Exception {
 					fetchArgs.add(name);
 				}
 
-				fetch.execute(fetchArgs.toArray(new String[fetchArgs.size()]));
+				fetch.execute(fetchArgs.toArray(new String[0]));
 
 				// flush the streams
 				fetch.outw.flush();
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/BenchmarkReftable.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/BenchmarkReftable.java
index 575a122..248eaac 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/BenchmarkReftable.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/BenchmarkReftable.java
@@ -43,7 +43,7 @@
 
 package org.eclipse.jgit.pgm.debug;
 
-import static org.eclipse.jgit.lib.Constants.CHARSET;
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.eclipse.jgit.lib.Constants.HEAD;
 import static org.eclipse.jgit.lib.Constants.MASTER;
 import static org.eclipse.jgit.lib.Constants.R_HEADS;
@@ -154,7 +154,7 @@ private RefList<Ref> readLsRemote()
 			throws IOException, FileNotFoundException {
 		RefList.Builder<Ref> list = new RefList.Builder<>();
 		try (BufferedReader br = new BufferedReader(new InputStreamReader(
-				new FileInputStream(lsRemotePath), CHARSET))) {
+				new FileInputStream(lsRemotePath), UTF_8))) {
 			Ref last = null;
 			String line;
 			while ((line = br.readLine()) != null) {
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/ReadReftable.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/ReadReftable.java
index fda1901..0b36e9c 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/ReadReftable.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/ReadReftable.java
@@ -70,7 +70,7 @@ protected void run() throws Exception {
 				BlockSource src = BlockSource.from(in);
 				ReftableReader reader = new ReftableReader(src)) {
 			try (RefCursor rc = ref != null
-					? reader.seekRef(ref)
+					? reader.seekRefsWithPrefix(ref)
 					: reader.allRefs()) {
 				while (rc.next()) {
 					write(rc.getRef());
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/RebuildCommitGraph.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/RebuildCommitGraph.java
index 2d16fef..8948c27 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/RebuildCommitGraph.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/RebuildCommitGraph.java
@@ -43,6 +43,8 @@
 
 package org.eclipse.jgit.pgm.debug;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
+
 import java.io.BufferedReader;
 import java.io.File;
 import java.io.FileInputStream;
@@ -141,7 +143,7 @@ private void recreateCommitGraph() throws IOException {
 		try (RevWalk rw = new RevWalk(db);
 				final BufferedReader br = new BufferedReader(
 						new InputStreamReader(new FileInputStream(graph),
-								Constants.CHARSET))) {
+								UTF_8))) {
 			String line;
 			while ((line = br.readLine()) != null) {
 				final String[] parts = line.split("[ \t]{1,}"); //$NON-NLS-1$
@@ -280,7 +282,7 @@ private Map<String, Ref> computeNewRefs() throws IOException {
 		try (RevWalk rw = new RevWalk(db);
 				BufferedReader br = new BufferedReader(
 						new InputStreamReader(new FileInputStream(refList),
-								Constants.CHARSET))) {
+								UTF_8))) {
 			String line;
 			while ((line = br.readLine()) != null) {
 				final String[] parts = line.split("[ \t]{1,}"); //$NON-NLS-1$
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/ShowDirCache.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/ShowDirCache.java
index 7f99d76..14a60a3 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/ShowDirCache.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/ShowDirCache.java
@@ -48,8 +48,10 @@
 
 import static java.lang.Integer.valueOf;
 
-import java.text.SimpleDateFormat;
-import java.util.Date;
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+import java.util.Locale;
 
 import org.eclipse.jgit.dircache.DirCache;
 import org.eclipse.jgit.dircache.DirCacheEntry;
@@ -67,25 +69,27 @@ class ShowDirCache extends TextBuiltin {
 	/** {@inheritDoc} */
 	@Override
 	protected void run() throws Exception {
-		final SimpleDateFormat fmt;
-		fmt = new SimpleDateFormat("yyyy-MM-dd,HH:mm:ss.SSS"); //$NON-NLS-1$
+		final DateTimeFormatter fmt = DateTimeFormatter
+				.ofPattern("yyyy-MM-dd,HH:mm:ss.nnnnnnnnn") //$NON-NLS-1$
+				.withLocale(Locale.getDefault())
+				.withZone(ZoneId.systemDefault());
 
 		final DirCache cache = db.readDirCache();
 		for (int i = 0; i < cache.getEntryCount(); i++) {
 			final DirCacheEntry ent = cache.getEntry(i);
 			final FileMode mode = FileMode.fromBits(ent.getRawMode());
 			final int len = ent.getLength();
-			long lastModified = ent.getLastModified();
-			final Date mtime = new Date(lastModified);
+			Instant mtime = ent.getLastModifiedInstant();
 			final int stage = ent.getStage();
 
 			outw.print(mode);
 			outw.format(" %6d", valueOf(len)); //$NON-NLS-1$
 			outw.print(' ');
-			if (millis)
-				outw.print(lastModified);
-			else
+			if (millis) {
+				outw.print(mtime.toEpochMilli());
+			} else {
 				outw.print(fmt.format(mtime));
+			}
 			outw.print(' ');
 			outw.print(ent.getObjectId().name());
 			outw.print(' ');
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/TextHashFunctions.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/TextHashFunctions.java
index bb51b50..300a01d 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/TextHashFunctions.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/TextHashFunctions.java
@@ -474,7 +474,7 @@ private static abstract class Fold {
 	}
 
 	/** Utility to help us identify unique lines in a file. */
-	private class Line {
+	private static class Line {
 		private final RawText txt;
 
 		private final int pos;
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/WriteReftable.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/WriteReftable.java
index c5ea028..6cbc1b0 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/WriteReftable.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/WriteReftable.java
@@ -43,7 +43,7 @@
 
 package org.eclipse.jgit.pgm.debug;
 
-import static org.eclipse.jgit.lib.Constants.CHARSET;
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.eclipse.jgit.lib.Constants.HEAD;
 import static org.eclipse.jgit.lib.Constants.MASTER;
 import static org.eclipse.jgit.lib.Constants.R_HEADS;
@@ -192,7 +192,7 @@ private void printf(String fmt, Object... args) throws IOException {
 	static List<Ref> readRefs(String inputFile) throws IOException {
 		List<Ref> refs = new ArrayList<>();
 		try (BufferedReader br = new BufferedReader(
-				new InputStreamReader(new FileInputStream(inputFile), CHARSET))) {
+				new InputStreamReader(new FileInputStream(inputFile), UTF_8))) {
 			String line;
 			while ((line = br.readLine()) != null) {
 				ObjectId id = ObjectId.fromString(line.substring(0, 40));
@@ -227,7 +227,7 @@ private static List<LogEntry> readLog(String logPath)
 
 		List<LogEntry> log = new ArrayList<>();
 		try (BufferedReader br = new BufferedReader(
-				new InputStreamReader(new FileInputStream(logPath), CHARSET))) {
+				new InputStreamReader(new FileInputStream(logPath), UTF_8))) {
 			@SuppressWarnings("nls")
 			Pattern pattern = Pattern.compile("([^,]+)" // 1: ref
 					+ ",([0-9]+(?:[.][0-9]+)?)" // 2: time
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/CmdLineParser.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/CmdLineParser.java
index 5cc98ca..b97aa5b 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/CmdLineParser.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/CmdLineParser.java
@@ -177,7 +177,7 @@ public void parseArgument(String... args) throws CmdLineException {
 		}
 
 		try {
-			super.parseArgument(tmp.toArray(new String[tmp.size()]));
+			super.parseArgument(tmp.toArray(new String[0]));
 		} catch (Die e) {
 			if (!seenHelp) {
 				throw e;
diff --git a/org.eclipse.jgit.test/BUILD b/org.eclipse.jgit.test/BUILD
index ac8c191..89bc90f 100644
--- a/org.eclipse.jgit.test/BUILD
+++ b/org.eclipse.jgit.test/BUILD
@@ -30,7 +30,7 @@
     PKG + "lib/sorttest.gitindex.dat",
 ]
 
-tests(glob(
+tests(tests = glob(
     ["tst/**/*.java"],
     exclude = HELPERS + DATA,
 ))
@@ -42,6 +42,7 @@
     resources = DATA,
     deps = [
         "//lib:junit",
+        "//lib:slf4j-simple",
         "//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 4b83fb0..b74d8da 100644
--- a/org.eclipse.jgit.test/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.test/META-INF/MANIFEST.MF
@@ -3,63 +3,77 @@
 Bundle-Name: %plugin_name
 Automatic-Module-Name: org.eclipse.jgit.test
 Bundle-SymbolicName: org.eclipse.jgit.test
-Bundle-Version: 5.0.4.qualifier
+Bundle-Version: 5.1.11.qualifier
 Bundle-Localization: plugin
 Bundle-Vendor: %provider_name
 Bundle-ActivationPolicy: lazy
 Bundle-RequiredExecutionEnvironment: JavaSE-1.8
 Import-Package: com.googlecode.javaewah;version="[1.1.6,2.0.0)",
  com.jcraft.jsch;version="[0.1.54,0.2.0)",
- org.eclipse.jgit.api;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.api.errors;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.attributes;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.awtui;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.blame;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.diff;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.dircache;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.errors;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.events;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.fnmatch;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.gitrepo;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.hooks;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.ignore;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.ignore.internal;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.internal;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.internal.fsck;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.internal.storage.dfs;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.internal.storage.file;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.internal.storage.io;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.internal.storage.pack;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.internal.storage.reftable;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.internal.storage.reftree;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.junit;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.lfs;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.lib;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.merge;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.nls;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.notes;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.patch;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.pgm;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.pgm.internal;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.revplot;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.revwalk;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.revwalk.filter;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.storage.file;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.storage.pack;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.submodule;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.transport;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.transport.http;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.transport.resolver;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.treewalk;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.treewalk.filter;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.util;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.util.io;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.util.sha1;version="[5.0.4,5.1.0)",
+ net.bytebuddy.dynamic.loading;version="[1.9.0,2.0.0)",
+ org.apache.commons.compress.archivers;version="[1.15.0,2.0)",
+ org.apache.commons.compress.archivers.tar;version="[1.15.0,2.0)",
+ org.apache.commons.compress.archivers.zip;version="[1.15.0,2.0)",
+ org.apache.commons.compress.compressors.bzip2;version="[1.15.0,2.0)",
+ org.apache.commons.compress.compressors.gzip;version="[1.15.0,2.0)",
+ org.apache.commons.compress.compressors.xz;version="[1.15.0,2.0)",
+ org.eclipse.jgit.api;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.api.errors;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.archive;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.attributes;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.awtui;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.blame;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.diff;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.dircache;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.errors;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.events;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.fnmatch;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.gitrepo;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.hooks;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.ignore;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.ignore.internal;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.internal;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.internal.fsck;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.internal.storage.dfs;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.internal.storage.file;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.internal.storage.io;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.internal.storage.pack;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.internal.storage.reftable;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.internal.storage.reftree;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.junit;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.junit.time;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.lfs;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.lib;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.merge;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.nls;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.notes;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.patch;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.pgm;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.pgm.internal;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.revplot;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.revwalk;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.revwalk.filter;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.storage.file;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.storage.pack;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.submodule;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.transport;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.transport.http;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.transport.resolver;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.treewalk;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.treewalk.filter;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.util;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.util.io;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.util.sha1;version="[5.1.11,5.2.0)",
  org.junit;version="[4.12,5.0.0)",
  org.junit.experimental.theories;version="[4.12,5.0.0)",
  org.junit.rules;version="[4.12,5.0.0)",
  org.junit.runner;version="[4.12,5.0.0)",
  org.junit.runners;version="[4.12,5.0.0)",
- org.slf4j;version="[1.7.0,2.0.0)"
+ org.mockito;version="[2.23.0,3.0.0)",
+ org.mockito.junit;version="[2.23.0,3.0.0)",
+ org.mockito.stubbing;version="2.23.0",
+ org.objenesis;version="2.6.0",
+ org.slf4j;version="[1.7.0,2.0.0)",
+ org.tukaani.xz;version="[1.6.0,2.0)"
 Require-Bundle: org.hamcrest.core;bundle-version="[1.1.0,2.0.0)",
  org.hamcrest.library;bundle-version="[1.1.0,2.0.0)"
diff --git a/org.eclipse.jgit.test/exttst/org/eclipse/jgit/ignore/CGitVsJGitRandomIgnorePatternTest.java b/org.eclipse.jgit.test/exttst/org/eclipse/jgit/ignore/CGitVsJGitRandomIgnorePatternTest.java
index 4d9e864..438d2d6 100644
--- a/org.eclipse.jgit.test/exttst/org/eclipse/jgit/ignore/CGitVsJGitRandomIgnorePatternTest.java
+++ b/org.eclipse.jgit.test/exttst/org/eclipse/jgit/ignore/CGitVsJGitRandomIgnorePatternTest.java
@@ -42,7 +42,7 @@
  */
 package org.eclipse.jgit.ignore;
 
-import static org.eclipse.jgit.lib.Constants.CHARSET;
+import static java.nio.charset.StandardCharsets.UTF_8;
 
 import java.io.BufferedReader;
 import java.io.File;
@@ -158,7 +158,7 @@ public CGitIgnoreRule(File gitDir, String pattern) throws IOException {
 			this.gitDir = gitDir;
 			this.pattern = pattern;
 			Files.write(FileUtils.toPath(new File(gitDir, ".gitignore")),
-					(pattern + "\n").getBytes(CHARSET), StandardOpenOption.CREATE,
+					(pattern + "\n").getBytes(UTF_8), StandardOpenOption.CREATE,
 					StandardOpenOption.TRUNCATE_EXISTING,
 					StandardOpenOption.WRITE);
 		}
@@ -188,7 +188,7 @@ private Process startCgitCheckIgnore(String path) throws IOException {
 			Process proc = Runtime.getRuntime().exec(command, new String[0],
 					gitDir);
 			try (OutputStream out = proc.getOutputStream()) {
-				out.write((path + "\n").getBytes(CHARSET));
+				out.write((path + "\n").getBytes(UTF_8));
 				out.flush();
 			}
 			return proc;
diff --git a/org.eclipse.jgit.test/exttst/org/eclipse/jgit/patch/EGitPatchHistoryTest.java b/org.eclipse.jgit.test/exttst/org/eclipse/jgit/patch/EGitPatchHistoryTest.java
index 79d8d0e..f597f2f 100644
--- a/org.eclipse.jgit.test/exttst/org/eclipse/jgit/patch/EGitPatchHistoryTest.java
+++ b/org.eclipse.jgit.test/exttst/org/eclipse/jgit/patch/EGitPatchHistoryTest.java
@@ -44,6 +44,7 @@
 package org.eclipse.jgit.patch;
 
 import static java.nio.charset.StandardCharsets.ISO_8859_1;
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
@@ -56,7 +57,6 @@
 import java.util.HashMap;
 import java.util.HashSet;
 
-import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.util.MutableInteger;
 import org.eclipse.jgit.util.RawParseUtils;
 import org.eclipse.jgit.util.TemporaryBuffer;
@@ -176,7 +176,7 @@ void onCommit(String commitId, byte[] buf) {
 				i.added = RawParseUtils.parseBase10(buf, ptr.value, ptr);
 				i.deleted = RawParseUtils.parseBase10(buf, ptr.value + 1, ptr);
 				final int eol = RawParseUtils.nextLF(buf, ptr.value);
-				final String name = RawParseUtils.decode(Constants.CHARSET,
+				final String name = RawParseUtils.decode(UTF_8,
 						buf, ptr.value + 1, eol - 1);
 				files.put(name, i);
 				ptr.value = eol;
diff --git a/org.eclipse.jgit.test/pom.xml b/org.eclipse.jgit.test/pom.xml
index e6c7128..c3bb16e 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.0.4-SNAPSHOT</version>
+    <version>5.1.11-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.test</artifactId>
@@ -90,6 +90,12 @@
     </dependency>
 
     <dependency>
+      <groupId>org.mockito</groupId>
+      <artifactId>mockito-core</artifactId>
+      <version>2.23.0</version>
+    </dependency>
+
+    <dependency>
       <groupId>org.eclipse.jgit</groupId>
       <artifactId>org.eclipse.jgit</artifactId>
       <version>${project.version}</version>
@@ -112,6 +118,12 @@
       <artifactId>org.eclipse.jgit.pgm</artifactId>
       <version>${project.version}</version>
     </dependency>
+
+    <dependency>
+      <groupId>org.tukaani</groupId>
+      <artifactId>xz</artifactId>
+      <optional>true</optional>
+    </dependency>
   </dependencies>
 
   <profiles>
@@ -123,6 +135,7 @@
           <plugin>
             <groupId>org.apache.maven.plugins</groupId>
             <artifactId>maven-surefire-plugin</artifactId>
+            <version>${maven-surefire-plugin-version}</version>
             <configuration>
               <argLine>-Djgit.test.long=true</argLine>
             </configuration>
@@ -157,7 +170,7 @@
       <plugin>
         <artifactId>maven-surefire-plugin</artifactId>
         <configuration>
-          <argLine>-Xmx256m -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/src/org/eclipse/jgit/events/ChangeRecorder.java b/org.eclipse.jgit.test/src/org/eclipse/jgit/events/ChangeRecorder.java
index c5582a8..228df35 100644
--- a/org.eclipse.jgit.test/src/org/eclipse/jgit/events/ChangeRecorder.java
+++ b/org.eclipse.jgit.test/src/org/eclipse/jgit/events/ChangeRecorder.java
@@ -74,11 +74,11 @@ public void onWorkingTreeModified(WorkingTreeModifiedEvent event) {
 	}
 
 	private String[] getModified() {
-		return modified.toArray(new String[modified.size()]);
+		return modified.toArray(new String[0]);
 	}
 
 	private String[] getDeleted() {
-		return deleted.toArray(new String[deleted.size()]);
+		return deleted.toArray(new String[0]);
 	}
 
 	private void reset() {
diff --git a/org.eclipse.jgit.test/tests.bzl b/org.eclipse.jgit.test/tests.bzl
index f368000..345da81 100644
--- a/org.eclipse.jgit.test/tests.bzl
+++ b/org.eclipse.jgit.test/tests.bzl
@@ -7,15 +7,16 @@
     for src in tests:
         name = src[len("tst/"):len(src) - len(".java")].replace("/", "_")
         labels = []
+        timeout = "moderate"
         if name.startswith("org_eclipse_jgit_"):
-            l = name[len("org.eclipse.jgit_"):]
-            if l.startswith("internal_storage_"):
-                l = l[len("internal.storage_"):]
-            i = l.find("_")
-            if i > 0:
-                labels.append(l[:i])
+            package = name[len("org.eclipse.jgit_"):]
+            if package.startswith("internal_storage_"):
+                package = package[len("internal.storage_"):]
+            index = package.find("_")
+            if index > 0:
+                labels.append(package[:index])
             else:
-                labels.append(i)
+                labels.append(index)
         if "lib" not in labels:
             labels.append("lib")
 
@@ -41,6 +42,18 @@
             additional_deps = [
                 "//lib:jsch",
             ]
+        if src.endswith("ArchiveCommandTest.java"):
+            additional_deps = [
+                "//lib:commons-compress",
+                "//lib:xz",
+                "//org.eclipse.jgit.archive:jgit-archive",
+            ]
+
+        heap_size = "-Xmx256m"
+        if src.endswith("HugeCommitMessageTest.java"):
+            heap_size = "-Xmx512m"
+        if src.endswith("EolRepositoryTest.java") or src.endswith("GcCommitSelectionTest.java"):
+            timeout = "long"
 
         junit_tests(
             name = name,
@@ -57,5 +70,6 @@
                 "//org.eclipse.jgit.lfs:jgit-lfs",
             ],
             flaky = flaky,
-            jvm_flags = ["-Xmx256m", "-Dfile.encoding=UTF-8"],
+            jvm_flags = [heap_size, "-Dfile.encoding=UTF-8"],
+            timeout = timeout,
         )
diff --git a/org.eclipse.jgit.test/tst-rsrc/log4j.properties b/org.eclipse.jgit.test/tst-rsrc/log4j.properties
index 14620ff..856a731 100644
--- a/org.eclipse.jgit.test/tst-rsrc/log4j.properties
+++ b/org.eclipse.jgit.test/tst-rsrc/log4j.properties
@@ -7,3 +7,8 @@
 log4j.appender.stdout.Target=System.out
 log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
 log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n
+log4j.appender.fileLogger.bufferedIO = true
+log4j.appender.fileLogger.bufferSize = 4096
+
+#log4j.logger.org.eclipse.jgit.util.FS = DEBUG
+#log4j.logger.org.eclipse.jgit.internal.storage.file.FileSnapshot = DEBUG
diff --git a/org.eclipse.jgit.test/tst-rsrc/simplelogger.properties b/org.eclipse.jgit.test/tst-rsrc/simplelogger.properties
new file mode 100644
index 0000000..011b2f8
--- /dev/null
+++ b/org.eclipse.jgit.test/tst-rsrc/simplelogger.properties
@@ -0,0 +1,9 @@
+org.slf4j.simpleLogger.logFile = System.err
+org.slf4j.simpleLogger.cacheOutputStream = true
+org.slf4j.simpleLogger.defaultLogLevel = info
+org.slf4j.simpleLogger.showDateTime = true
+org.slf4j.simpleLogger.dateTimeFormat = HH:mm:ss.SSSXXX
+org.slf4j.simpleLogger.showThreadName = true
+
+#org.slf4j.simpleLogger.log.org.eclipse.jgit.util.FS = debug
+#org.slf4j.simpleLogger.log.org.eclipse.jgit.internal.storage.file.FileSnapshot = debug
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/AddCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/AddCommandTest.java
index 911f659..c67c86a 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/AddCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/AddCommandTest.java
@@ -1266,7 +1266,7 @@ private static DirCacheEntry addEntryToBuilder(String path, File file,
 		DirCacheEntry entry = new DirCacheEntry(path, stage);
 		entry.setObjectId(id);
 		entry.setFileMode(FileMode.REGULAR_FILE);
-		entry.setLastModified(file.lastModified());
+		entry.setLastModified(FS.DETECTED.lastModifiedInstant(file));
 		entry.setLength((int) file.length());
 
 		builder.add(entry);
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/ArchiveCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/ArchiveCommandTest.java
index 1300f98..fbec024 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/ArchiveCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/ArchiveCommandTest.java
@@ -46,20 +46,44 @@
 import static org.junit.Assert.assertNull;
 
 import java.beans.Statement;
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
 import java.io.IOException;
+import java.io.InputStream;
 import java.io.OutputStream;
+import java.nio.file.Files;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
+import org.apache.commons.compress.archivers.ArchiveEntry;
+import org.apache.commons.compress.archivers.ArchiveInputStream;
+import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
+import org.apache.commons.compress.archivers.zip.ZipArchiveInputStream;
+import org.apache.commons.compress.compressors.bzip2.BZip2CompressorInputStream;
+import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream;
+import org.apache.commons.compress.compressors.xz.XZCompressorInputStream;
+import org.eclipse.jgit.api.errors.AbortedByHookException;
+import org.eclipse.jgit.api.errors.ConcurrentRefUpdateException;
 import org.eclipse.jgit.api.errors.GitAPIException;
+import org.eclipse.jgit.api.errors.NoFilepatternException;
+import org.eclipse.jgit.api.errors.NoHeadException;
+import org.eclipse.jgit.api.errors.NoMessageException;
+import org.eclipse.jgit.api.errors.UnmergedPathsException;
+import org.eclipse.jgit.api.errors.WrongRepositoryStateException;
+import org.eclipse.jgit.archive.ArchiveFormats;
+import org.eclipse.jgit.errors.AmbiguousObjectException;
+import org.eclipse.jgit.errors.IncorrectObjectTypeException;
 import org.eclipse.jgit.junit.RepositoryTestCase;
 import org.eclipse.jgit.lib.FileMode;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.ObjectLoader;
 import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.util.IO;
 import org.eclipse.jgit.util.StringUtils;
 import org.junit.After;
 import org.junit.Before;
@@ -67,9 +91,14 @@
 
 public class ArchiveCommandTest extends RepositoryTestCase {
 
+	// archives store timestamp with 1 second resolution
+	private static final int WAIT = 2000;
 	private static final String UNEXPECTED_ARCHIVE_SIZE  = "Unexpected archive size";
 	private static final String UNEXPECTED_FILE_CONTENTS = "Unexpected file contents";
 	private static final String UNEXPECTED_TREE_CONTENTS = "Unexpected tree contents";
+	private static final String UNEXPECTED_LAST_MODIFIED =
+			"Unexpected lastModified mocked by MockSystemReader, truncated to 1 second";
+	private static final String UNEXPECTED_DIFFERENT_HASH = "Unexpected different hash";
 
 	private MockFormat format = null;
 
@@ -77,25 +106,20 @@ public class ArchiveCommandTest extends RepositoryTestCase {
 	public void setup() {
 		format = new MockFormat();
 		ArchiveCommand.registerFormat(format.SUFFIXES.get(0), format);
+		ArchiveFormats.registerAll();
 	}
 
 	@Override
 	@After
 	public void tearDown() {
 		ArchiveCommand.unregisterFormat(format.SUFFIXES.get(0));
+		ArchiveFormats.unregisterAll();
 	}
 
 	@Test
 	public void archiveHeadAllFiles() throws IOException, GitAPIException {
 		try (Git git = new Git(db)) {
-			writeTrashFile("file_1.txt", "content_1_1");
-			git.add().addFilepattern("file_1.txt").call();
-			git.commit().setMessage("create file").call();
-
-			writeTrashFile("file_1.txt", "content_1_2");
-			writeTrashFile("file_2.txt", "content_2_2");
-			git.add().addFilepattern(".").call();
-			git.commit().setMessage("updated file").call();
+			createTestContent(git);
 
 			git.archive().setOutputStream(new MockOutputStream())
 					.setFormat(format.SUFFIXES.get(0))
@@ -190,7 +214,159 @@ public void archiveByDirectoryPath() throws GitAPIException, IOException {
 		}
 	}
 
-	private class MockFormat implements ArchiveCommand.Format<MockOutputStream> {
+	@Test
+	public void archiveHeadAllFilesTarTimestamps() throws Exception {
+		try (Git git = new Git(db)) {
+			createTestContent(git);
+			String fmt = "tar";
+			File archive = new File(getTemporaryDirectory(),
+					"archive." + format);
+			archive(git, archive, fmt);
+			ObjectId hash1 = ObjectId.fromRaw(IO.readFully(archive));
+
+			try (InputStream fi = Files.newInputStream(archive.toPath());
+					InputStream bi = new BufferedInputStream(fi);
+					ArchiveInputStream o = new TarArchiveInputStream(bi)) {
+				assertEntries(o);
+			}
+
+			Thread.sleep(WAIT);
+			archive(git, archive, fmt);
+			assertEquals(UNEXPECTED_DIFFERENT_HASH, hash1,
+					ObjectId.fromRaw(IO.readFully(archive)));
+		}
+	}
+
+	@Test
+	public void archiveHeadAllFilesTgzTimestamps() throws Exception {
+		try (Git git = new Git(db)) {
+			createTestContent(git);
+			String fmt = "tgz";
+			File archive = new File(getTemporaryDirectory(),
+					"archive." + fmt);
+			archive(git, archive, fmt);
+			ObjectId hash1 = ObjectId.fromRaw(IO.readFully(archive));
+
+			try (InputStream fi = Files.newInputStream(archive.toPath());
+					InputStream bi = new BufferedInputStream(fi);
+					InputStream gzi = new GzipCompressorInputStream(bi);
+					ArchiveInputStream o = new TarArchiveInputStream(gzi)) {
+				assertEntries(o);
+			}
+
+			Thread.sleep(WAIT);
+			archive(git, archive, fmt);
+			assertEquals(UNEXPECTED_DIFFERENT_HASH, hash1,
+					ObjectId.fromRaw(IO.readFully(archive)));
+		}
+	}
+
+	@Test
+	public void archiveHeadAllFilesTbz2Timestamps() throws Exception {
+		try (Git git = new Git(db)) {
+			createTestContent(git);
+			String fmt = "tbz2";
+			File archive = new File(getTemporaryDirectory(),
+					"archive." + fmt);
+			archive(git, archive, fmt);
+			ObjectId hash1 = ObjectId.fromRaw(IO.readFully(archive));
+
+			try (InputStream fi = Files.newInputStream(archive.toPath());
+					InputStream bi = new BufferedInputStream(fi);
+					InputStream gzi = new BZip2CompressorInputStream(bi);
+					ArchiveInputStream o = new TarArchiveInputStream(gzi)) {
+				assertEntries(o);
+			}
+
+			Thread.sleep(WAIT);
+			archive(git, archive, fmt);
+			assertEquals(UNEXPECTED_DIFFERENT_HASH, hash1,
+					ObjectId.fromRaw(IO.readFully(archive)));
+		}
+	}
+
+	@Test
+	public void archiveHeadAllFilesTxzTimestamps() throws Exception {
+		try (Git git = new Git(db)) {
+			createTestContent(git);
+			String fmt = "txz";
+			File archive = new File(getTemporaryDirectory(), "archive." + fmt);
+			archive(git, archive, fmt);
+			ObjectId hash1 = ObjectId.fromRaw(IO.readFully(archive));
+
+			try (InputStream fi = Files.newInputStream(archive.toPath());
+					InputStream bi = new BufferedInputStream(fi);
+					InputStream gzi = new XZCompressorInputStream(bi);
+					ArchiveInputStream o = new TarArchiveInputStream(gzi)) {
+				assertEntries(o);
+			}
+
+			Thread.sleep(WAIT);
+			archive(git, archive, fmt);
+			assertEquals(UNEXPECTED_DIFFERENT_HASH, hash1,
+					ObjectId.fromRaw(IO.readFully(archive)));
+		}
+	}
+
+	@Test
+	public void archiveHeadAllFilesZipTimestamps() throws Exception {
+		try (Git git = new Git(db)) {
+			createTestContent(git);
+			String fmt = "zip";
+			File archive = new File(getTemporaryDirectory(), "archive." + fmt);
+			archive(git, archive, fmt);
+			ObjectId hash1 = ObjectId.fromRaw(IO.readFully(archive));
+
+			try (InputStream fi = Files.newInputStream(archive.toPath());
+					InputStream bi = new BufferedInputStream(fi);
+					ArchiveInputStream o = new ZipArchiveInputStream(bi)) {
+				assertEntries(o);
+			}
+
+			Thread.sleep(WAIT);
+			archive(git, archive, fmt);
+			assertEquals(UNEXPECTED_DIFFERENT_HASH, hash1,
+					ObjectId.fromRaw(IO.readFully(archive)));
+		}
+	}
+
+	private void createTestContent(Git git) throws IOException, GitAPIException,
+			NoFilepatternException, NoHeadException, NoMessageException,
+			UnmergedPathsException, ConcurrentRefUpdateException,
+			WrongRepositoryStateException, AbortedByHookException {
+		writeTrashFile("file_1.txt", "content_1_1");
+		git.add().addFilepattern("file_1.txt").call();
+		git.commit().setMessage("create file").call();
+
+		writeTrashFile("file_1.txt", "content_1_2");
+		writeTrashFile("file_2.txt", "content_2_2");
+		git.add().addFilepattern(".").call();
+		git.commit().setMessage("updated file").call();
+	}
+
+	private static void archive(Git git, File archive, String fmt)
+			throws GitAPIException,
+			FileNotFoundException, AmbiguousObjectException,
+			IncorrectObjectTypeException, IOException {
+		git.archive().setOutputStream(new FileOutputStream(archive))
+				.setFormat(fmt)
+				.setTree(git.getRepository().resolve("HEAD")).call();
+	}
+
+	private static void assertEntries(ArchiveInputStream o) throws IOException {
+		ArchiveEntry e;
+		int n = 0;
+		while ((e = o.getNextEntry()) != null) {
+			n++;
+			assertEquals(UNEXPECTED_LAST_MODIFIED,
+					(1250379778668L / 1000L) * 1000L,
+					e.getLastModifiedDate().getTime());
+		}
+		assertEquals(UNEXPECTED_ARCHIVE_SIZE, 2, n);
+	}
+
+	private static class MockFormat
+			implements ArchiveCommand.Format<MockOutputStream> {
 
 		private Map<String, String> entries = new HashMap<>();
 
@@ -240,7 +416,7 @@ public Iterable<String> suffixes() {
 		}
 	}
 
-	public class MockOutputStream extends OutputStream {
+	public static class MockOutputStream extends OutputStream {
 
 		private int foo;
 
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CheckoutCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CheckoutCommandTest.java
index 71df59e..08ad7b8 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CheckoutCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CheckoutCommandTest.java
@@ -43,6 +43,7 @@
  */
 package org.eclipse.jgit.api;
 
+import static java.time.Instant.EPOCH;
 import static org.eclipse.jgit.lib.Constants.MASTER;
 import static org.eclipse.jgit.lib.Constants.R_HEADS;
 import static org.hamcrest.CoreMatchers.is;
@@ -60,6 +61,9 @@
 import java.io.IOException;
 import java.net.MalformedURLException;
 import java.net.URISyntaxException;
+import java.nio.file.Files;
+import java.nio.file.attribute.FileTime;
+import java.time.Instant;
 
 import org.eclipse.jgit.api.CheckoutResult.Status;
 import org.eclipse.jgit.api.CreateBranchCommand.SetupUpstreamMode;
@@ -74,6 +78,7 @@
 import org.eclipse.jgit.dircache.DirCacheEntry;
 import org.eclipse.jgit.junit.JGitTestUtil;
 import org.eclipse.jgit.junit.RepositoryTestCase;
+import org.eclipse.jgit.junit.time.TimeUtil;
 import org.eclipse.jgit.lfs.BuiltinLFS;
 import org.eclipse.jgit.lib.ConfigConstants;
 import org.eclipse.jgit.lib.Constants;
@@ -86,6 +91,7 @@
 import org.eclipse.jgit.storage.file.FileBasedConfig;
 import org.eclipse.jgit.transport.RemoteConfig;
 import org.eclipse.jgit.transport.URIish;
+import org.eclipse.jgit.util.FS;
 import org.eclipse.jgit.util.FileUtils;
 import org.eclipse.jgit.util.SystemReader;
 import org.junit.Before;
@@ -360,14 +366,14 @@ public void testUpdateSmudgedEntries() throws Exception {
 
 		File file = new File(db.getWorkTree(), "Test.txt");
 		long size = file.length();
-		long mTime = file.lastModified() - 5000L;
-		assertTrue(file.setLastModified(mTime));
+		Instant mTime = TimeUtil.setLastModifiedWithOffset(file.toPath(),
+				-5000L);
 
 		DirCache cache = DirCache.lock(db.getIndexFile(), db.getFS());
 		DirCacheEntry entry = cache.getEntry("Test.txt");
 		assertNotNull(entry);
 		entry.setLength(0);
-		entry.setLastModified(0);
+		entry.setLastModified(EPOCH);
 		cache.write();
 		assertTrue(cache.commit());
 
@@ -375,10 +381,12 @@ public void testUpdateSmudgedEntries() throws Exception {
 		entry = cache.getEntry("Test.txt");
 		assertNotNull(entry);
 		assertEquals(0, entry.getLength());
-		assertEquals(0, entry.getLastModified());
+		assertEquals(EPOCH, entry.getLastModifiedInstant());
 
-		db.getIndexFile().setLastModified(
-				db.getIndexFile().lastModified() - 5000);
+		Files.setLastModifiedTime(db.getIndexFile().toPath(),
+				FileTime.from(FS.DETECTED
+						.lastModifiedInstant(db.getIndexFile())
+						.minusMillis(5000L)));
 
 		assertNotNull(git.checkout().setName("test").call());
 
@@ -386,7 +394,7 @@ public void testUpdateSmudgedEntries() throws Exception {
 		entry = cache.getEntry("Test.txt");
 		assertNotNull(entry);
 		assertEquals(size, entry.getLength());
-		assertEquals(mTime, entry.getLastModified());
+		assertEquals(mTime, entry.getLastModifiedInstant());
 	}
 
 	@Test
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CloneCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CloneCommandTest.java
index 0d7009d..613ca5c 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
@@ -68,9 +68,9 @@
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.Ref;
 import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.lib.StoredConfig;
 import org.eclipse.jgit.revwalk.RevBlob;
 import org.eclipse.jgit.revwalk.RevCommit;
-import org.eclipse.jgit.storage.file.FileBasedConfig;
 import org.eclipse.jgit.submodule.SubmoduleStatus;
 import org.eclipse.jgit.submodule.SubmoduleStatusType;
 import org.eclipse.jgit.submodule.SubmoduleWalk;
@@ -633,8 +633,8 @@ public void testCloneWithAutoSetupRebase() throws Exception {
 				ConfigConstants.CONFIG_BRANCH_SECTION, "test",
 				ConfigConstants.CONFIG_KEY_REBASE, null));
 
-		FileBasedConfig userConfig = SystemReader.getInstance().openUserConfig(
-				null, git.getRepository().getFS());
+		StoredConfig userConfig = SystemReader.getInstance()
+				.getUserConfig();
 		userConfig.setString(ConfigConstants.CONFIG_BRANCH_SECTION, null,
 				ConfigConstants.CONFIG_KEY_AUTOSETUPREBASE,
 				ConfigConstants.CONFIG_KEY_ALWAYS);
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 3a13aa5..a6072a0 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
@@ -61,6 +61,7 @@
 import org.eclipse.jgit.dircache.DirCacheBuilder;
 import org.eclipse.jgit.dircache.DirCacheEntry;
 import org.eclipse.jgit.junit.RepositoryTestCase;
+import org.eclipse.jgit.junit.time.TimeUtil;
 import org.eclipse.jgit.lib.ConfigConstants;
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.FileMode;
@@ -311,11 +312,11 @@ public void commitSubmoduleUpdate() throws Exception {
 	public void commitUpdatesSmudgedEntries() throws Exception {
 		try (Git git = new Git(db)) {
 			File file1 = writeTrashFile("file1.txt", "content1");
-			assertTrue(file1.setLastModified(file1.lastModified() - 5000));
+			TimeUtil.setLastModifiedWithOffset(file1.toPath(), -5000L);
 			File file2 = writeTrashFile("file2.txt", "content2");
-			assertTrue(file2.setLastModified(file2.lastModified() - 5000));
+			TimeUtil.setLastModifiedWithOffset(file2.toPath(), -5000L);
 			File file3 = writeTrashFile("file3.txt", "content3");
-			assertTrue(file3.setLastModified(file3.lastModified() - 5000));
+			TimeUtil.setLastModifiedWithOffset(file3.toPath(), -5000L);
 
 			assertNotNull(git.add().addFilepattern("file1.txt")
 					.addFilepattern("file2.txt").addFilepattern("file3.txt").call());
@@ -346,11 +347,12 @@ public void commitUpdatesSmudgedEntries() throws Exception {
 			assertEquals(0, cache.getEntry("file2.txt").getLength());
 			assertEquals(0, cache.getEntry("file3.txt").getLength());
 
-			long indexTime = db.getIndexFile().lastModified();
-			db.getIndexFile().setLastModified(indexTime - 5000);
+			TimeUtil.setLastModifiedWithOffset(db.getIndexFile().toPath(),
+					-5000L);
 
 			write(file1, "content4");
-			assertTrue(file1.setLastModified(file1.lastModified() + 2500));
+
+			TimeUtil.setLastModifiedWithOffset(file1.toPath(), 2500L);
 			assertNotNull(git.commit().setMessage("edit file").setOnly("file1.txt")
 					.call());
 
@@ -368,9 +370,9 @@ public void commitUpdatesSmudgedEntries() throws Exception {
 	public void commitIgnoresSmudgedEntryWithDifferentId() throws Exception {
 		try (Git git = new Git(db)) {
 			File file1 = writeTrashFile("file1.txt", "content1");
-			assertTrue(file1.setLastModified(file1.lastModified() - 5000));
+			TimeUtil.setLastModifiedWithOffset(file1.toPath(), -5000L);
 			File file2 = writeTrashFile("file2.txt", "content2");
-			assertTrue(file2.setLastModified(file2.lastModified() - 5000));
+			TimeUtil.setLastModifiedWithOffset(file2.toPath(), -5000L);
 
 			assertNotNull(git.add().addFilepattern("file1.txt")
 					.addFilepattern("file2.txt").call());
@@ -399,11 +401,11 @@ public void commitIgnoresSmudgedEntryWithDifferentId() throws Exception {
 			assertEquals(0, cache.getEntry("file1.txt").getLength());
 			assertEquals(0, cache.getEntry("file2.txt").getLength());
 
-			long indexTime = db.getIndexFile().lastModified();
-			db.getIndexFile().setLastModified(indexTime - 5000);
+			TimeUtil.setLastModifiedWithOffset(db.getIndexFile().toPath(),
+					-5000L);
 
 			write(file1, "content5");
-			assertTrue(file1.setLastModified(file1.lastModified() + 1000));
+			TimeUtil.setLastModifiedWithOffset(file1.toPath(), 1000L);
 
 			assertNotNull(git.commit().setMessage("edit file").setOnly("file1.txt")
 					.call());
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitOnlyTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitOnlyTest.java
index bbd6ec0..43c0051 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitOnlyTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitOnlyTest.java
@@ -1294,9 +1294,15 @@ static private String getHead(Git git, String path)
 		try {
 			final Repository repo = git.getRepository();
 			final ObjectId headId = repo.resolve(Constants.HEAD + "^{commit}");
+			if (headId == null) {
+				return "";
+			}
 			try (RevWalk rw = new RevWalk(repo)) {
 				final TreeWalk tw = TreeWalk.forPath(repo, path,
 						rw.parseTree(headId));
+				if (tw == null) {
+					return "";
+				}
 				return new String(tw.getObjectReader().open(tw.getObjectId(0))
 						.getBytes());
 			}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/DiffCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/DiffCommandTest.java
index 43c3a8c..3a93839 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/DiffCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/DiffCommandTest.java
@@ -44,7 +44,6 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
 
 import java.io.ByteArrayOutputStream;
 import java.io.File;
@@ -55,6 +54,7 @@
 import org.eclipse.jgit.diff.DiffEntry;
 import org.eclipse.jgit.diff.DiffEntry.ChangeType;
 import org.eclipse.jgit.junit.RepositoryTestCase;
+import org.eclipse.jgit.junit.time.TimeUtil;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.ObjectReader;
 import org.eclipse.jgit.revwalk.RevWalk;
@@ -230,7 +230,7 @@ public void testDiffWithNegativeLineCount() throws Exception {
 	@Test
 	public void testNoOutputStreamSet() throws Exception {
 		File file = writeTrashFile("test.txt", "a");
-		assertTrue(file.setLastModified(file.lastModified() - 5000));
+		TimeUtil.setLastModifiedWithOffset(file.toPath(), -5000L);
 		try (Git git = new Git(db)) {
 			git.add().addFilepattern(".").call();
 			write(file, "b");
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/EolStreamTypeUtilTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/EolStreamTypeUtilTest.java
index bb303cc..1e3a39a 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/EolStreamTypeUtilTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/EolStreamTypeUtilTest.java
@@ -42,7 +42,7 @@
 
 package org.eclipse.jgit.api;
 
-import static org.eclipse.jgit.lib.Constants.CHARSET;
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.eclipse.jgit.lib.CoreConfig.EolStreamType.AUTO_CRLF;
 import static org.eclipse.jgit.lib.CoreConfig.EolStreamType.AUTO_LF;
 import static org.eclipse.jgit.lib.CoreConfig.EolStreamType.DIRECT;
@@ -150,8 +150,8 @@ private void testCheckout(EolStreamType streamTypeText,
 			EolStreamType streamTypeWithBinaryCheck, String output,
 			String expectedConversion) throws Exception {
 		ByteArrayOutputStream b;
-		byte[] outputBytes = output.getBytes(CHARSET);
-		byte[] expectedConversionBytes = expectedConversion.getBytes(CHARSET);
+		byte[] outputBytes = output.getBytes(UTF_8);
+		byte[] expectedConversionBytes = expectedConversion.getBytes(UTF_8);
 
 		// test using output text and assuming it was declared TEXT
 		b = new ByteArrayOutputStream();
@@ -277,8 +277,8 @@ public void testCheckinCRLF() throws Exception {
 	private void testCheckin(EolStreamType streamTypeText,
 			EolStreamType streamTypeWithBinaryCheck, String input,
 			String expectedConversion) throws Exception {
-		byte[] inputBytes = input.getBytes(CHARSET);
-		byte[] expectedConversionBytes = expectedConversion.getBytes(CHARSET);
+		byte[] inputBytes = input.getBytes(UTF_8);
+		byte[] expectedConversionBytes = expectedConversion.getBytes(UTF_8);
 
 		// test using input text and assuming it was declared TEXT
 		try (InputStream in = EolStreamTypeUtil.wrapInputStream(
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/NotesCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/NotesCommandTest.java
index e234aa3..6e06e95 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/NotesCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/NotesCommandTest.java
@@ -42,7 +42,7 @@
  */
 package org.eclipse.jgit.api;
 
-import static org.eclipse.jgit.lib.Constants.CHARSET;
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.junit.Assert.assertEquals;
 
 import java.util.List;
@@ -88,7 +88,7 @@ public void testAddAndRemoveNote() throws Exception {
 		git.notesAdd().setObjectId(commit2).setMessage("data").call();
 		Note note = git.notesShow().setObjectId(commit2).call();
 		String content = new String(db.open(note.getData()).getCachedBytes(),
-				CHARSET);
+				UTF_8);
 		assertEquals(content, "data");
 
 		git.notesRemove().setObjectId(commit2).call();
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 0b0e3bf..9461c42 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
@@ -42,7 +42,7 @@
  */
 package org.eclipse.jgit.api;
 
-import static org.eclipse.jgit.lib.Constants.CHARSET;
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
@@ -592,7 +592,7 @@ public void setUp() throws Exception {
 	private static void writeToFile(File actFile, String string)
 			throws IOException {
 		try (FileOutputStream fos = new FileOutputStream(actFile)) {
-			fos.write(string.getBytes(CHARSET));
+			fos.write(string.getBytes(UTF_8));
 		}
 	}
 
@@ -606,7 +606,7 @@ private static void assertFileContentsEqual(File actFile, String string)
 				bos.write(buffer, 0, read);
 				read = fis.read(buffer);
 			}
-			String content = new String(bos.toByteArray(), CHARSET);
+			String content = new String(bos.toByteArray(), UTF_8);
 			assertEquals(string, content);
 		}
 	}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PullCommandWithRebaseTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PullCommandWithRebaseTest.java
index b349c66..913b4ac 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PullCommandWithRebaseTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PullCommandWithRebaseTest.java
@@ -42,7 +42,7 @@
  */
 package org.eclipse.jgit.api;
 
-import static org.eclipse.jgit.lib.Constants.CHARSET;
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
@@ -396,7 +396,7 @@ public void setUp() throws Exception {
 	private static void writeToFile(File actFile, String string)
 			throws IOException {
 		try (FileOutputStream fos = new FileOutputStream(actFile)) {
-			fos.write(string.getBytes(CHARSET));
+			fos.write(string.getBytes(UTF_8));
 		}
 	}
 
@@ -410,7 +410,7 @@ private static void assertFileContentsEqual(File actFile, String string)
 				bos.write(buffer, 0, read);
 				read = fis.read(buffer);
 			}
-			String content = new String(bos.toByteArray(), CHARSET);
+			String content = new String(bos.toByteArray(), UTF_8);
 			assertEquals(string, content);
 		}
 	}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PushCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PushCommandTest.java
index 1af37e2..ca86d81 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PushCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PushCommandTest.java
@@ -245,7 +245,7 @@ public void testPushRefUpdate() throws Exception {
 				git.add().addFilepattern("f" + i).call();
 				commit = git.commit().setMessage("adding f" + i).call();
 				git.push().setRemote("test").call();
-				git2.getRepository().getAllRefs();
+				git2.getRepository().getRefDatabase().getRefs();
 				assertEquals("failed to update on attempt " + i, commit.getId(),
 						git2.getRepository().resolve("refs/heads/test"));
 			}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RebaseCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RebaseCommandTest.java
index 96e7091..4401bce 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RebaseCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RebaseCommandTest.java
@@ -42,7 +42,7 @@
  */
 package org.eclipse.jgit.api;
 
-import static org.eclipse.jgit.lib.Constants.CHARSET;
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.hamcrest.CoreMatchers.equalTo;
 import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
@@ -57,9 +57,12 @@
 import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.InputStreamReader;
+import java.util.ArrayList;
 import java.util.Collections;
+import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Set;
 
 import org.eclipse.jgit.api.MergeResult.MergeStatus;
 import org.eclipse.jgit.api.RebaseCommand.InteractiveHandler;
@@ -75,6 +78,7 @@
 import org.eclipse.jgit.errors.IllegalTodoFileModification;
 import org.eclipse.jgit.errors.IncorrectObjectTypeException;
 import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.events.ListenerHandle;
 import org.eclipse.jgit.junit.RepositoryTestCase;
 import org.eclipse.jgit.lib.AbbreviatedObjectId;
 import org.eclipse.jgit.lib.ConfigConstants;
@@ -1464,7 +1468,7 @@ public void testAuthorScriptConverter() throws Exception {
 		assertEquals("GIT_AUTHOR_DATE='@123456789 -0100'", lines[2]);
 
 		PersonIdent parsedIdent = git.rebase().parseAuthor(
-				convertedAuthor.getBytes(CHARSET));
+				convertedAuthor.getBytes(UTF_8));
 		assertEquals(ident.getName(), parsedIdent.getName());
 		assertEquals(ident.getEmailAddress(), parsedIdent.getEmailAddress());
 		// this is rounded to the last second
@@ -1481,7 +1485,7 @@ public void testAuthorScriptConverter() throws Exception {
 		assertEquals("GIT_AUTHOR_DATE='@123456789 +0930'", lines[2]);
 
 		parsedIdent = git.rebase().parseAuthor(
-				convertedAuthor.getBytes(CHARSET));
+				convertedAuthor.getBytes(UTF_8));
 		assertEquals(ident.getName(), parsedIdent.getName());
 		assertEquals(ident.getEmailAddress(), parsedIdent.getEmailAddress());
 		assertEquals(123456789000L, parsedIdent.getWhen().getTime());
@@ -1981,6 +1985,62 @@ public void testRebaseWithAutoStash()
 	}
 
 	@Test
+	public void testRebaseWithAutoStashAndSubdirs() throws Exception {
+		// create file0, add and commit
+		db.getConfig().setBoolean(ConfigConstants.CONFIG_REBASE_SECTION, null,
+				ConfigConstants.CONFIG_KEY_AUTOSTASH, true);
+		writeTrashFile("sub/file0", "file0");
+		git.add().addFilepattern("sub/file0").call();
+		git.commit().setMessage("commit0").call();
+		// create file1, add and commit
+		writeTrashFile(FILE1, "file1");
+		git.add().addFilepattern(FILE1).call();
+		RevCommit commit = git.commit().setMessage("commit1").call();
+
+		// create topic branch and checkout / create file2, add and commit
+		createBranch(commit, "refs/heads/topic");
+		checkoutBranch("refs/heads/topic");
+		writeTrashFile("file2", "file2");
+		git.add().addFilepattern("file2").call();
+		git.commit().setMessage("commit2").call();
+
+		// checkout master branch / modify file1, add and commit
+		checkoutBranch("refs/heads/master");
+		writeTrashFile(FILE1, "modified file1");
+		git.add().addFilepattern(FILE1).call();
+		git.commit().setMessage("commit3").call();
+
+		// checkout topic branch / modify file0
+		checkoutBranch("refs/heads/topic");
+		writeTrashFile("sub/file0", "unstaged modified file0");
+
+		Set<String> modifiedFiles = new HashSet<>();
+		ListenerHandle handle = db.getListenerList()
+				.addWorkingTreeModifiedListener(
+						event -> modifiedFiles.addAll(event.getModified()));
+		try {
+			// rebase
+			assertEquals(Status.OK, git.rebase()
+					.setUpstream("refs/heads/master").call().getStatus());
+		} finally {
+			handle.remove();
+		}
+		checkFile(new File(new File(db.getWorkTree(), "sub"), "file0"),
+				"unstaged modified file0");
+		checkFile(new File(db.getWorkTree(), FILE1), "modified file1");
+		checkFile(new File(db.getWorkTree(), "file2"), "file2");
+		assertEquals(
+				"[file1, mode:100644, content:modified file1]"
+						+ "[file2, mode:100644, content:file2]"
+						+ "[sub/file0, mode:100644, content:file0]",
+				indexState(CONTENT));
+		assertEquals(RepositoryState.SAFE, db.getRepositoryState());
+		List<String> modified = new ArrayList<>(modifiedFiles);
+		Collections.sort(modified);
+		assertEquals("[file1, sub/file0]", modified.toString());
+	}
+
+	@Test
 	public void testRebaseWithAutoStashConflictOnApply() throws Exception {
 		// create file0, add and commit
 		db.getConfig().setBoolean(ConfigConstants.CONFIG_REBASE_SECTION, null,
@@ -2104,7 +2164,7 @@ private int countPicks() throws IOException {
 		int count = 0;
 		File todoFile = getTodoFile();
 		try (BufferedReader br = new BufferedReader(new InputStreamReader(
-				new FileInputStream(todoFile), CHARSET))) {
+				new FileInputStream(todoFile), UTF_8))) {
 			String line = br.readLine();
 			while (line != null) {
 				int firstBlank = line.indexOf(' ');
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/ReflogCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/ReflogCommandTest.java
index b07c703..8922837 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/ReflogCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/ReflogCommandTest.java
@@ -89,7 +89,7 @@ public void testHeadReflog() throws Exception {
 		Collection<ReflogEntry> reflog = git.reflog().call();
 		assertNotNull(reflog);
 		assertEquals(3, reflog.size());
-		ReflogEntry[] reflogs = reflog.toArray(new ReflogEntry[reflog.size()]);
+		ReflogEntry[] reflogs = reflog.toArray(new ReflogEntry[0]);
 		assertEquals(reflogs[2].getComment(),
 				"commit (initial): Initial commit");
 		assertEquals(reflogs[2].getNewId(), commit1.getId());
@@ -114,7 +114,7 @@ public void testBranchReflog() throws Exception {
 				.setRef(Constants.R_HEADS + "b1").call();
 		assertNotNull(reflog);
 		assertEquals(2, reflog.size());
-		ReflogEntry[] reflogs = reflog.toArray(new ReflogEntry[reflog.size()]);
+		ReflogEntry[] reflogs = reflog.toArray(new ReflogEntry[0]);
 		assertEquals(reflogs[0].getComment(), "commit: Removed file");
 		assertEquals(reflogs[0].getNewId(), commit2.getId());
 		assertEquals(reflogs[0].getOldId(), commit1.getId());
@@ -136,7 +136,7 @@ public void testAmendReflog() throws Exception {
 		Collection<ReflogEntry> reflog = git.reflog().call();
 		assertNotNull(reflog);
 		assertEquals(4, reflog.size());
-		ReflogEntry[] reflogs = reflog.toArray(new ReflogEntry[reflog.size()]);
+		ReflogEntry[] reflogs = reflog.toArray(new ReflogEntry[0]);
 		assertEquals(reflogs[3].getComment(),
 				"commit (initial): Initial commit");
 		assertEquals(reflogs[3].getNewId(), commit1.getId());
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/ResetCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/ResetCommandTest.java
index 8f56a92..588387d 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/ResetCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/ResetCommandTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2011-2013, Chris Aniszczyk <caniszczyk@gmail.com>
+ * Copyright (C) 2011-2018, Chris Aniszczyk <caniszczyk@gmail.com>
  * and other copyright owners as documented in the project's IP log.
  *
  * This program and the accompanying materials are made available
@@ -42,6 +42,7 @@
  */
 package org.eclipse.jgit.api;
 
+import static java.time.Instant.EPOCH;
 import static org.eclipse.jgit.api.ResetCommand.ResetType.HARD;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -52,7 +53,9 @@
 
 import java.io.File;
 import java.io.IOException;
-import java.io.PrintWriter;
+import java.nio.file.Files;
+import java.nio.file.attribute.FileTime;
+import java.time.Instant;
 
 import org.eclipse.jgit.api.ResetCommand.ResetType;
 import org.eclipse.jgit.api.errors.GitAPIException;
@@ -94,45 +97,25 @@ public void setupRepository() throws IOException, JGitInternalException,
 		git = new Git(db);
 		initialCommit = git.commit().setMessage("initial commit").call();
 
+		// create file
+		indexFile = writeTrashFile("a.txt", "content");
+
 		// create nested file
-		File dir = new File(db.getWorkTree(), "dir");
-		FileUtils.mkdir(dir);
-		File nestedFile = new File(dir, "b.txt");
-		FileUtils.createNewFile(nestedFile);
+		writeTrashFile("dir/b.txt", "content");
 
-		try (PrintWriter nestedFileWriter = new PrintWriter(nestedFile)) {
-			nestedFileWriter.print("content");
-			nestedFileWriter.flush();
+		// add files and commit them
+		git.add().addFilepattern("a.txt").addFilepattern("dir/b.txt").call();
+		secondCommit = git.commit().setMessage("adding a.txt and dir/b.txt").call();
 
-			// create file
-			indexFile = new File(db.getWorkTree(), "a.txt");
-			FileUtils.createNewFile(indexFile);
-			try (PrintWriter writer = new PrintWriter(indexFile)) {
-				writer.print("content");
-				writer.flush();
+		prestage = DirCache.read(db.getIndexFile(), db.getFS()).getEntry(indexFile.getName());
 
-				// add file and commit it
-				git.add().addFilepattern("dir").addFilepattern("a.txt").call();
-				secondCommit = git.commit()
-						.setMessage("adding a.txt and dir/b.txt").call();
-
-				prestage = DirCache.read(db.getIndexFile(), db.getFS())
-						.getEntry(indexFile.getName());
-
-				// modify file and add to index
-				writer.print("new content");
-			}
-			nestedFileWriter.print("new content");
-		}
-		git.add().addFilepattern("a.txt").addFilepattern("dir").call();
+		// modify files and add to index
+		writeTrashFile("a.txt", "new content");
+		writeTrashFile("dir/b.txt", "new content");
+		git.add().addFilepattern("a.txt").addFilepattern("dir/b.txt").call();
 
 		// create a file not added to the index
-		untrackedFile = new File(db.getWorkTree(),
-				"notAddedToIndex.txt");
-		FileUtils.createNewFile(untrackedFile);
-		try (PrintWriter writer2 = new PrintWriter(untrackedFile)) {
-			writer2.print("content");
-		}
+		untrackedFile = writeTrashFile("notAddedToIndex.txt", "content");
 	}
 
 	@Test
@@ -179,31 +162,29 @@ public void testHardResetReflogDisabled() throws Exception {
 	}
 
 	@Test
-	public void testHardResetWithConflicts_DoOverWriteUntrackedFile()
-			throws JGitInternalException,
-			AmbiguousObjectException, IOException, GitAPIException {
+	public void testHardResetWithConflicts_OverwriteUntrackedFile() throws Exception {
 		setupRepository();
+
 		git.rm().setCached(true).addFilepattern("a.txt").call();
 		assertTrue(new File(db.getWorkTree(), "a.txt").exists());
-		git.reset().setMode(ResetType.HARD).setRef(Constants.HEAD)
-				.call();
+
+		git.reset().setMode(ResetType.HARD).setRef(Constants.HEAD).call();
 		assertTrue(new File(db.getWorkTree(), "a.txt").exists());
 		assertEquals("content", read(new File(db.getWorkTree(), "a.txt")));
 	}
 
 	@Test
-	public void testHardResetWithConflicts_DoDeleteFileFolderConflicts()
-			throws JGitInternalException,
-			AmbiguousObjectException, IOException, GitAPIException {
+	public void testHardResetWithConflicts_DeleteFileFolderConflict() throws Exception {
 		setupRepository();
-		writeTrashFile("d/c.txt", "x");
-		git.add().addFilepattern("d/c.txt").call();
-		FileUtils.delete(new File(db.getWorkTree(), "d"), FileUtils.RECURSIVE);
-		writeTrashFile("d", "y");
 
-		git.reset().setMode(ResetType.HARD).setRef(Constants.HEAD)
-				.call();
-		assertFalse(new File(db.getWorkTree(), "d").exists());
+		writeTrashFile("dir-or-file/c.txt", "content");
+		git.add().addFilepattern("dir-or-file/c.txt").call();
+
+		FileUtils.delete(new File(db.getWorkTree(), "dir-or-file"), FileUtils.RECURSIVE);
+		writeTrashFile("dir-or-file", "content");
+
+		git.reset().setMode(ResetType.HARD).setRef(Constants.HEAD).call();
+		assertFalse(new File(db.getWorkTree(), "dir-or-file").exists());
 	}
 
 	@Test
@@ -269,13 +250,13 @@ public void testMixedReset() throws JGitInternalException,
 	public void testMixedResetRetainsSizeAndModifiedTime() throws Exception {
 		git = new Git(db);
 
-		writeTrashFile("a.txt", "a").setLastModified(
-				System.currentTimeMillis() - 60 * 1000);
+		Files.setLastModifiedTime(writeTrashFile("a.txt", "a").toPath(),
+				FileTime.from(Instant.now().minusSeconds(60)));
 		assertNotNull(git.add().addFilepattern("a.txt").call());
 		assertNotNull(git.commit().setMessage("a commit").call());
 
-		writeTrashFile("b.txt", "b").setLastModified(
-				System.currentTimeMillis() - 60 * 1000);
+		Files.setLastModifiedTime(writeTrashFile("b.txt", "b").toPath(),
+				FileTime.from(Instant.now().minusSeconds(60)));
 		assertNotNull(git.add().addFilepattern("b.txt").call());
 		RevCommit commit2 = git.commit().setMessage("b commit").call();
 		assertNotNull(commit2);
@@ -285,12 +266,12 @@ public void testMixedResetRetainsSizeAndModifiedTime() throws Exception {
 		DirCacheEntry aEntry = cache.getEntry("a.txt");
 		assertNotNull(aEntry);
 		assertTrue(aEntry.getLength() > 0);
-		assertTrue(aEntry.getLastModified() > 0);
+		assertTrue(aEntry.getLastModifiedInstant().compareTo(EPOCH) > 0);
 
 		DirCacheEntry bEntry = cache.getEntry("b.txt");
 		assertNotNull(bEntry);
 		assertTrue(bEntry.getLength() > 0);
-		assertTrue(bEntry.getLastModified() > 0);
+		assertTrue(bEntry.getLastModifiedInstant().compareTo(EPOCH) > 0);
 
 		assertSameAsHead(git.reset().setMode(ResetType.MIXED)
 				.setRef(commit2.getName()).call());
@@ -299,13 +280,17 @@ public void testMixedResetRetainsSizeAndModifiedTime() throws Exception {
 
 		DirCacheEntry mixedAEntry = cache.getEntry("a.txt");
 		assertNotNull(mixedAEntry);
-		assertEquals(aEntry.getLastModified(), mixedAEntry.getLastModified());
-		assertEquals(aEntry.getLastModified(), mixedAEntry.getLastModified());
+		assertEquals(aEntry.getLastModifiedInstant(),
+				mixedAEntry.getLastModifiedInstant());
+		assertEquals(aEntry.getLastModifiedInstant(),
+				mixedAEntry.getLastModifiedInstant());
 
 		DirCacheEntry mixedBEntry = cache.getEntry("b.txt");
 		assertNotNull(mixedBEntry);
-		assertEquals(bEntry.getLastModified(), mixedBEntry.getLastModified());
-		assertEquals(bEntry.getLastModified(), mixedBEntry.getLastModified());
+		assertEquals(bEntry.getLastModifiedInstant(),
+				mixedBEntry.getLastModifiedInstant());
+		assertEquals(bEntry.getLastModifiedInstant(),
+				mixedBEntry.getLastModifiedInstant());
 	}
 
 	@Test
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/StashApplyCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/StashApplyCommandTest.java
index ad3ab7f..fa6c960 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/StashApplyCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/StashApplyCommandTest.java
@@ -234,7 +234,7 @@ public void workingDirectoryModifyInSubfolder() throws Exception {
 		ObjectId unstashed = git.stashApply().call();
 		assertEquals(stashed, unstashed);
 		assertEquals("content2", read(subfolderFile));
-		recorder.assertEvent(new String[] { "d1/d2/f.txt", "d1/d2", "d1" },
+		recorder.assertEvent(new String[] { "d1/d2/f.txt" },
 				ChangeRecorder.EMPTY);
 
 		Status status = git.status().call();
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/CGitAttributesTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/CGitAttributesTest.java
index 344d1af..b661507 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/CGitAttributesTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/CGitAttributesTest.java
@@ -42,6 +42,7 @@
  */
 package org.eclipse.jgit.attributes;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 
@@ -57,7 +58,6 @@
 import java.util.Set;
 
 import org.eclipse.jgit.junit.RepositoryTestCase;
-import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.StoredConfig;
 import org.eclipse.jgit.treewalk.FileTreeIterator;
 import org.eclipse.jgit.treewalk.TreeWalk;
@@ -127,14 +127,14 @@ private LinkedHashMap<String, Attributes> cgitAttributes(
 		builder.directory(db.getWorkTree());
 		builder.environment().put("HOME", fs.userHome().getAbsolutePath());
 		ExecutionResult result = fs.execute(builder, new ByteArrayInputStream(
-				input.toString().getBytes(Constants.CHARSET)));
+				input.toString().getBytes(UTF_8)));
 		String errorOut = toString(result.getStderr());
 		assertEquals("External git failed", "exit 0\n",
 				"exit " + result.getRc() + '\n' + errorOut);
 		LinkedHashMap<String, Attributes> map = new LinkedHashMap<>();
 		try (BufferedReader r = new BufferedReader(new InputStreamReader(
 				new BufferedInputStream(result.getStdout().openInputStream()),
-				Constants.CHARSET))) {
+				UTF_8))) {
 			r.lines().forEach(line -> {
 				// Parse the line and add to result map
 				int start = 0;
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/AbstractDiffTestCase.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/AbstractDiffTestCase.java
index 32f3421..0f13a68 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/AbstractDiffTestCase.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/AbstractDiffTestCase.java
@@ -43,7 +43,7 @@
 
 package org.eclipse.jgit.diff;
 
-import static org.eclipse.jgit.lib.Constants.CHARSET;
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
@@ -240,6 +240,6 @@ public static RawText t(String text) {
 			r.append(text.charAt(i));
 			r.append('\n');
 		}
-		return new RawText(r.toString().getBytes(CHARSET));
+		return new RawText(r.toString().getBytes(UTF_8));
 	}
 }
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/RawTextTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/RawTextTest.java
index 58a8b2d..5885d9b 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/RawTextTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/RawTextTest.java
@@ -44,7 +44,7 @@
 
 package org.eclipse.jgit.diff;
 
-import static org.eclipse.jgit.lib.Constants.CHARSET;
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -147,8 +147,8 @@ public void testComparatorReduceCommonStartEnd() {
 		e = c.reduceCommonStartEnd(t("abQxy"), t("abRxy"), e);
 		assertEquals(new Edit(2, 3, 2, 3), e);
 
-		RawText a = new RawText("p\na b\nQ\nc d\n".getBytes(CHARSET));
-		RawText b = new RawText("p\na  b \nR\n c  d \n".getBytes(CHARSET));
+		RawText a = new RawText("p\na b\nQ\nc d\n".getBytes(UTF_8));
+		RawText b = new RawText("p\na  b \nR\n c  d \n".getBytes(UTF_8));
 		e = new Edit(0, 4, 0, 4);
 		e = RawTextComparator.WS_IGNORE_ALL.reduceCommonStartEnd(a, b, e);
 		assertEquals(new Edit(2, 3, 2, 3), e);
@@ -160,14 +160,14 @@ public void testComparatorReduceCommonStartEnd_EmptyLine() {
 		RawText b;
 		Edit e;
 
-		a = new RawText("R\n y\n".getBytes(CHARSET));
-		b = new RawText("S\n\n y\n".getBytes(CHARSET));
+		a = new RawText("R\n y\n".getBytes(UTF_8));
+		b = new RawText("S\n\n y\n".getBytes(UTF_8));
 		e = new Edit(0, 2, 0, 3);
 		e = RawTextComparator.DEFAULT.reduceCommonStartEnd(a, b, e);
 		assertEquals(new Edit(0, 1, 0, 2), e);
 
-		a = new RawText("S\n\n y\n".getBytes(CHARSET));
-		b = new RawText("R\n y\n".getBytes(CHARSET));
+		a = new RawText("S\n\n y\n".getBytes(UTF_8));
+		b = new RawText("R\n y\n".getBytes(UTF_8));
 		e = new Edit(0, 3, 0, 2);
 		e = RawTextComparator.DEFAULT.reduceCommonStartEnd(a, b, e);
 		assertEquals(new Edit(0, 2, 0, 1), e);
@@ -178,8 +178,8 @@ public void testComparatorReduceCommonStartButLastLineNoEol() {
 		RawText a;
 		RawText b;
 		Edit e;
-		a = new RawText("start".getBytes(CHARSET));
-		b = new RawText("start of line".getBytes(CHARSET));
+		a = new RawText("start".getBytes(UTF_8));
+		b = new RawText("start of line".getBytes(UTF_8));
 		e = new Edit(0, 1, 0, 1);
 		e = RawTextComparator.DEFAULT.reduceCommonStartEnd(a, b, e);
 		assertEquals(new Edit(0, 1, 0, 1), e);
@@ -190,8 +190,8 @@ public void testComparatorReduceCommonStartButLastLineNoEol_2() {
 		RawText a;
 		RawText b;
 		Edit e;
-		a = new RawText("start".getBytes(CHARSET));
-		b = new RawText("start of\nlastline".getBytes(CHARSET));
+		a = new RawText("start".getBytes(UTF_8));
+		b = new RawText("start of\nlastline".getBytes(UTF_8));
 		e = new Edit(0, 1, 0, 2);
 		e = RawTextComparator.DEFAULT.reduceCommonStartEnd(a, b, e);
 		assertEquals(new Edit(0, 1, 0, 2), e);
@@ -250,6 +250,6 @@ private static RawText t(String text) {
 			r.append(text.charAt(i));
 			r.append('\n');
 		}
-		return new RawText(r.toString().getBytes(CHARSET));
+		return new RawText(r.toString().getBytes(UTF_8));
 	}
 }
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/SimilarityIndexTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/SimilarityIndexTest.java
index 51a6f81..f168e83 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/SimilarityIndexTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/SimilarityIndexTest.java
@@ -43,7 +43,7 @@
 
 package org.eclipse.jgit.diff;
 
-import static org.eclipse.jgit.lib.Constants.CHARSET;
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
@@ -82,7 +82,7 @@ public void testIndexingLargeObject() throws IOException,
 				+ "A\n" //
 				+ "B\n" //
 				+ "B\n" //
-				+ "B\n").getBytes(CHARSET);
+				+ "B\n").getBytes(UTF_8);
 		SimilarityIndex si = new SimilarityIndex();
 		si.hash(new ByteArrayInputStream(in), in.length, false);
 		assertEquals(2, si.size());
@@ -130,12 +130,12 @@ public void testCommonScoreLargeObject_SameFiles_CR_canonicalization()
 				+ "D\r\n" //
 				+ "B\r\n";
 		SimilarityIndex src = new SimilarityIndex();
-		byte[] bytes1 = text.getBytes(CHARSET);
+		byte[] bytes1 = text.getBytes(UTF_8);
 		src.hash(new ByteArrayInputStream(bytes1), bytes1.length, true);
 		src.sort();
 
 		SimilarityIndex dst = new SimilarityIndex();
-		byte[] bytes2 = text.replace("\r", "").getBytes(CHARSET);
+		byte[] bytes2 = text.replace("\r", "").getBytes(UTF_8);
 		dst.hash(new ByteArrayInputStream(bytes2), bytes2.length, true);
 		dst.sort();
 
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheBuilderTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheBuilderTest.java
index d12f302..d9a4203 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
@@ -53,6 +53,7 @@
 import static org.junit.Assert.fail;
 
 import java.io.File;
+import java.time.Instant;
 
 import org.eclipse.jgit.events.IndexChangedEvent;
 import org.eclipse.jgit.events.IndexChangedListener;
@@ -98,7 +99,7 @@ public void testBuildRejectsUnsetFileMode() throws Exception {
 	public void testBuildOneFile_FinishWriteCommit() throws Exception {
 		final String path = "a-file-path";
 		final FileMode mode = FileMode.REGULAR_FILE;
-		final long lastModified = 1218123387057L;
+		final Instant lastModified = Instant.ofEpochMilli(1218123387057L);
 		final int length = 1342;
 		final DirCacheEntry entOrig;
 		{
@@ -116,7 +117,7 @@ public void testBuildOneFile_FinishWriteCommit() throws Exception {
 			assertEquals(ObjectId.zeroId(), entOrig.getObjectId());
 			assertEquals(mode.getBits(), entOrig.getRawMode());
 			assertEquals(0, entOrig.getStage());
-			assertEquals(lastModified, entOrig.getLastModified());
+			assertEquals(lastModified, entOrig.getLastModifiedInstant());
 			assertEquals(length, entOrig.getLength());
 			assertFalse(entOrig.isAssumeValid());
 			b.add(entOrig);
@@ -138,7 +139,7 @@ public void testBuildOneFile_FinishWriteCommit() throws Exception {
 			assertEquals(ObjectId.zeroId(), entOrig.getObjectId());
 			assertEquals(mode.getBits(), entOrig.getRawMode());
 			assertEquals(0, entOrig.getStage());
-			assertEquals(lastModified, entOrig.getLastModified());
+			assertEquals(lastModified, entOrig.getLastModifiedInstant());
 			assertEquals(length, entOrig.getLength());
 			assertFalse(entOrig.isAssumeValid());
 		}
@@ -148,7 +149,7 @@ public void testBuildOneFile_FinishWriteCommit() throws Exception {
 	public void testBuildOneFile_Commit() throws Exception {
 		final String path = "a-file-path";
 		final FileMode mode = FileMode.REGULAR_FILE;
-		final long lastModified = 1218123387057L;
+		final Instant lastModified = Instant.ofEpochMilli(1218123387057L);
 		final int length = 1342;
 		final DirCacheEntry entOrig;
 		{
@@ -166,7 +167,7 @@ public void testBuildOneFile_Commit() throws Exception {
 			assertEquals(ObjectId.zeroId(), entOrig.getObjectId());
 			assertEquals(mode.getBits(), entOrig.getRawMode());
 			assertEquals(0, entOrig.getStage());
-			assertEquals(lastModified, entOrig.getLastModified());
+			assertEquals(lastModified, entOrig.getLastModifiedInstant());
 			assertEquals(length, entOrig.getLength());
 			assertFalse(entOrig.isAssumeValid());
 			b.add(entOrig);
@@ -186,7 +187,7 @@ public void testBuildOneFile_Commit() throws Exception {
 			assertEquals(ObjectId.zeroId(), entOrig.getObjectId());
 			assertEquals(mode.getBits(), entOrig.getRawMode());
 			assertEquals(0, entOrig.getStage());
-			assertEquals(lastModified, entOrig.getLastModified());
+			assertEquals(lastModified, entOrig.getLastModifiedInstant());
 			assertEquals(length, entOrig.getLength());
 			assertFalse(entOrig.isAssumeValid());
 		}
@@ -203,7 +204,7 @@ final class ReceivedEventMarkerException extends RuntimeException {
 		final String path = "a-file-path";
 		final FileMode mode = FileMode.REGULAR_FILE;
 		// "old" date in 2008
-		final long lastModified = 1218123387057L;
+		final Instant lastModified = Instant.ofEpochMilli(1218123387057L);
 		final int length = 1342;
 		DirCacheEntry entOrig;
 		boolean receivedEvent = false;
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheCGitCompatabilityTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheCGitCompatabilityTest.java
index c362e74..847d0ab 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheCGitCompatabilityTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheCGitCompatabilityTest.java
@@ -43,7 +43,7 @@
 
 package org.eclipse.jgit.dircache;
 
-import static org.eclipse.jgit.lib.Constants.CHARSET;
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.eclipse.jgit.junit.Assert.assertEquals;
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
@@ -236,7 +236,7 @@ private static File pathOf(String name) {
 	private static Map<String, CGitIndexRecord> readLsFiles() throws Exception {
 		final LinkedHashMap<String, CGitIndexRecord> r = new LinkedHashMap<>();
 		try (BufferedReader br = new BufferedReader(new InputStreamReader(
-				new FileInputStream(pathOf("gitgit.lsfiles")), CHARSET))) {
+				new FileInputStream(pathOf("gitgit.lsfiles")), UTF_8))) {
 			String line;
 			while ((line = br.readLine()) != null) {
 				final CGitIndexRecord cr = new CGitIndexRecord(line);
@@ -249,7 +249,7 @@ private static Map<String, CGitIndexRecord> readLsFiles() throws Exception {
 	private static Map<String, CGitLsTreeRecord> readLsTree() throws Exception {
 		final LinkedHashMap<String, CGitLsTreeRecord> r = new LinkedHashMap<>();
 		try (BufferedReader br = new BufferedReader(new InputStreamReader(
-				new FileInputStream(pathOf("gitgit.lstree")), CHARSET))) {
+				new FileInputStream(pathOf("gitgit.lstree")), UTF_8))) {
 			String line;
 			while ((line = br.readLine()) != null) {
 				final CGitLsTreeRecord cr = new CGitLsTreeRecord(line);
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheEntryTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheEntryTest.java
index 86e2852..475819d 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheEntryTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheEntryTest.java
@@ -43,6 +43,7 @@
 
 package org.eclipse.jgit.dircache;
 
+import static java.time.Instant.EPOCH;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertSame;
@@ -188,7 +189,7 @@ private static void copyMetaDataHelper(boolean keepStage) {
 		e.setAssumeValid(false);
 		e.setCreationTime(2L);
 		e.setFileMode(FileMode.EXECUTABLE_FILE);
-		e.setLastModified(3L);
+		e.setLastModified(EPOCH.plusMillis(3L));
 		e.setLength(100L);
 		e.setObjectId(ObjectId
 				.fromString("0123456789012345678901234567890123456789"));
@@ -199,7 +200,7 @@ private static void copyMetaDataHelper(boolean keepStage) {
 		f.setAssumeValid(true);
 		f.setCreationTime(10L);
 		f.setFileMode(FileMode.SYMLINK);
-		f.setLastModified(20L);
+		f.setLastModified(EPOCH.plusMillis(20L));
 		f.setLength(100000000L);
 		f.setObjectId(ObjectId
 				.fromString("1234567890123456789012345678901234567890"));
@@ -212,7 +213,7 @@ private static void copyMetaDataHelper(boolean keepStage) {
 				ObjectId.fromString("1234567890123456789012345678901234567890"),
 				e.getObjectId());
 		assertEquals(FileMode.SYMLINK, e.getFileMode());
-		assertEquals(20L, e.getLastModified());
+		assertEquals(EPOCH.plusMillis(20L), e.getLastModifiedInstant());
 		assertEquals(100000000L, e.getLength());
 		if (keepStage)
 			assertEquals(DirCacheEntry.STAGE_2, e.getStage());
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/gitrepo/ManifestParserTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/gitrepo/ManifestParserTest.java
index 69a48cc..88a0776 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/gitrepo/ManifestParserTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/gitrepo/ManifestParserTest.java
@@ -42,7 +42,8 @@
  */
 package org.eclipse.jgit.gitrepo;
 
-import static org.eclipse.jgit.lib.Constants.CHARSET;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
@@ -51,6 +52,8 @@
 import java.net.URI;
 import java.util.HashSet;
 import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 import org.junit.Test;
 import org.xml.sax.SAXException;
@@ -82,7 +85,7 @@ public void testManifestParser() throws Exception {
 
 		ManifestParser parser = new ManifestParser(
 				null, null, "master", baseUrl, null, null);
-		parser.read(new ByteArrayInputStream(xmlContent.toString().getBytes(CHARSET)));
+		parser.read(new ByteArrayInputStream(xmlContent.toString().getBytes(UTF_8)));
 		// Unfiltered projects should have them all.
 		results.clear();
 		results.add("foo");
@@ -136,7 +139,7 @@ public void testManifestParserWithMissingFetchOnRemote() throws Exception {
 				baseUrl, null, null);
 		try {
 			parser.read(new ByteArrayInputStream(
-					xmlContent.toString().getBytes(CHARSET)));
+					xmlContent.toString().getBytes(UTF_8)));
 			fail("ManifestParser did not throw exception for missing fetch");
 		} catch (IOException e) {
 			assertTrue(e.getCause() instanceof SAXException);
@@ -145,6 +148,29 @@ public void testManifestParserWithMissingFetchOnRemote() throws Exception {
 		}
 	}
 
+	@Test
+	public void testRemoveProject() throws Exception {
+		StringBuilder xmlContent = new StringBuilder();
+		xmlContent.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n")
+				.append("<manifest>")
+				.append("<remote name=\"remote1\" fetch=\".\" />")
+				.append("<default revision=\"master\" remote=\"remote1\" />")
+				.append("<project path=\"foo\" name=\"foo\" />")
+				.append("<project path=\"bar\" name=\"bar\" />")
+				.append("<remove-project name=\"foo\" />")
+				.append("<project path=\"foo\" name=\"baz\" />")
+				.append("</manifest>");
+
+		ManifestParser parser = new ManifestParser(null, null, "master",
+				"https://git.google.com/", null, null);
+		parser.read(new ByteArrayInputStream(
+				xmlContent.toString().getBytes(UTF_8)));
+
+		assertEquals(Stream.of("bar", "baz").collect(Collectors.toSet()),
+				parser.getProjects().stream().map(RepoProject::getName)
+						.collect(Collectors.toSet()));
+	}
+
 	void testNormalize(String in, String want) {
 		URI got = ManifestParser.normalizeEmptyPath(URI.create(in));
 		if (!got.toString().equals(want)) {
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/gitrepo/RepoCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/gitrepo/RepoCommandTest.java
index df31ab0..f23e4be 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/gitrepo/RepoCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/gitrepo/RepoCommandTest.java
@@ -42,7 +42,7 @@
  */
 package org.eclipse.jgit.gitrepo;
 
-import static org.eclipse.jgit.lib.Constants.CHARSET;
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNull;
@@ -139,7 +139,7 @@ public void setUp() throws Exception {
 		resolveRelativeUris();
 	}
 
-	class IndexedRepos implements RepoCommand.RemoteReader {
+	static class IndexedRepos implements RepoCommand.RemoteReader {
 		Map<String, Repository> uriRepoMap;
 		IndexedRepos() {
 			uriRepoMap = new HashMap<>();
@@ -217,7 +217,7 @@ public void runTwiceIsNOP() throws Exception {
 
 			RevCommit commit = cmd
 					.setInputStream(new ByteArrayInputStream(
-							xmlContent.toString().getBytes(CHARSET)))
+							xmlContent.toString().getBytes(UTF_8)))
 					.setRemoteReader(repos).setURI("platform/")
 					.setTargetURI("platform/superproject")
 					.setRecordRemoteBranch(true).setRecordSubmoduleLabels(true)
@@ -226,7 +226,7 @@ public void runTwiceIsNOP() throws Exception {
 			String firstIdStr = commit.getId().name() + ":" + ".gitmodules";
 			commit = new RepoCommand(dest)
 					.setInputStream(new ByteArrayInputStream(
-							xmlContent.toString().getBytes(CHARSET)))
+							xmlContent.toString().getBytes(UTF_8)))
 					.setRemoteReader(repos).setURI("platform/")
 					.setTargetURI("platform/superproject")
 					.setRecordRemoteBranch(true).setRecordSubmoduleLabels(true)
@@ -254,7 +254,7 @@ public void androidSetup() throws Exception {
 
 			RevCommit commit = cmd
 					.setInputStream(new ByteArrayInputStream(
-							xmlContent.toString().getBytes(CHARSET)))
+							xmlContent.toString().getBytes(UTF_8)))
 					.setRemoteReader(repos).setURI("platform/")
 					.setTargetURI("platform/superproject")
 					.setRecordRemoteBranch(true).setRecordSubmoduleLabels(true)
@@ -268,7 +268,8 @@ public void androidSetup() throws Exception {
 						.getCachedBytes(Integer.MAX_VALUE);
 				Config base = new Config();
 				BlobBasedConfig cfg = new BlobBasedConfig(base, bytes);
-				String subUrl = cfg.getString("submodule", "base", "url");
+				String subUrl = cfg.getString("submodule", "platform/base",
+						"url");
 				assertEquals(subUrl, "../base");
 			}
 		}
@@ -287,7 +288,7 @@ public void recordUnreachableRemotes() throws Exception {
 		try (Repository dest = cloneRepository(db, true)) {
 			RevCommit commit = new RepoCommand(dest)
 					.setInputStream(new ByteArrayInputStream(
-							xmlContent.toString().getBytes(CHARSET)))
+							xmlContent.toString().getBytes(UTF_8)))
 					.setRemoteReader(new IndexedRepos()).setURI("platform/")
 					.setTargetURI("platform/superproject")
 					.setRecordRemoteBranch(true).setIgnoreRemoteFailures(true)
@@ -301,7 +302,8 @@ public void recordUnreachableRemotes() throws Exception {
 						.getCachedBytes(Integer.MAX_VALUE);
 				Config base = new Config();
 				BlobBasedConfig cfg = new BlobBasedConfig(base, bytes);
-				String subUrl = cfg.getString("submodule", "base", "url");
+				String subUrl = cfg.getString("submodule", "platform/base",
+						"url");
 				assertEquals(subUrl, "https://host.com/platform/base");
 			}
 		}
@@ -325,7 +327,7 @@ public void gerritSetup() throws Exception {
 
 			RevCommit commit = cmd
 					.setInputStream(new ByteArrayInputStream(
-							xmlContent.toString().getBytes(CHARSET)))
+							xmlContent.toString().getBytes(UTF_8)))
 					.setRemoteReader(repos).setURI("").setTargetURI("gerrit")
 					.setRecordRemoteBranch(true).setRecordSubmoduleLabels(true)
 					.call();
@@ -374,7 +376,7 @@ public void absoluteRemoteURL() throws Exception {
 
 					RevCommit commit = cmd
 							.setInputStream(new ByteArrayInputStream(
-									xmlContent.toString().getBytes(CHARSET)))
+									xmlContent.toString().getBytes(UTF_8)))
 							.setRemoteReader(repos).setURI(baseUrl)
 							.setTargetURI("gerrit").setRecordRemoteBranch(true)
 							.setRecordSubmoduleLabels(true).call();
@@ -387,8 +389,8 @@ public void absoluteRemoteURL() throws Exception {
 								.getCachedBytes(Integer.MAX_VALUE);
 						Config base = new Config();
 						BlobBasedConfig cfg = new BlobBasedConfig(base, bytes);
-						String subUrl = cfg.getString("submodule", "src",
-								"url");
+						String subUrl = cfg.getString("submodule",
+								"chromium/src", "url");
 						assertEquals(
 								"https://chromium.googlesource.com/chromium/src",
 								subUrl);
@@ -429,7 +431,7 @@ public void absoluteRemoteURLAbsoluteTargetURL() throws Exception {
 
 					RevCommit commit = cmd
 							.setInputStream(new ByteArrayInputStream(
-									xmlContent.toString().getBytes(CHARSET)))
+									xmlContent.toString().getBytes(UTF_8)))
 							.setRemoteReader(repos).setURI(baseUrl)
 							.setTargetURI(abs + "/superproject")
 							.setRecordRemoteBranch(true)
@@ -443,8 +445,8 @@ public void absoluteRemoteURLAbsoluteTargetURL() throws Exception {
 								.getCachedBytes(Integer.MAX_VALUE);
 						Config base = new Config();
 						BlobBasedConfig cfg = new BlobBasedConfig(base, bytes);
-						String subUrl = cfg.getString("submodule", "src",
-								"url");
+						String subUrl = cfg.getString("submodule",
+								"chromium/src", "url");
 						assertEquals("../chromium/src", subUrl);
 					}
 					fetchSlash = !fetchSlash;
@@ -613,7 +615,7 @@ public void testBareRepo() throws Exception {
 				String content = reader.readLine();
 				assertEquals(
 						"The first line of .gitmodules file should be as expected",
-						"[submodule \"foo\"]", content);
+						"[submodule \"" + defaultUri + "\"]", content);
 			}
 			// The gitlink should be the same as remote head sha1
 			String gitlink = localDb.resolve(Constants.HEAD + ":foo").name();
@@ -801,9 +803,9 @@ public void testReplaceManifestBare() throws Exception {
 				.append("<manifest>")
 				.append("<remote name=\"remote1\" fetch=\".\" />")
 				.append("<default revision=\"master\" remote=\"remote1\" />")
-				.append("<project path=\"bar\" name=\"").append(defaultUri)
-				.append("\" revision=\"").append(BRANCH).append("\" >")
-				.append("<copyfile src=\"hello.txt\" dest=\"Hello.txt\" />")
+				.append("<project path=\"bar\" name=\"").append(notDefaultUri)
+				.append("\" >")
+				.append("<copyfile src=\"world.txt\" dest=\"World.txt\" />")
 				.append("</project>").append("</manifest>");
 		JGitTestUtil.writeTrashFile(tempDb, "new.xml", xmlContent.toString());
 		command = new RepoCommand(remoteDb);
@@ -819,8 +821,8 @@ public void testReplaceManifestBare() throws Exception {
 			File hello = new File(localDb.getWorkTree(), "Hello");
 			assertFalse("The Hello file shouldn't exist", hello.exists());
 			// The Hello.txt file should exist
-			File hellotxt = new File(localDb.getWorkTree(), "Hello.txt");
-			assertTrue("The Hello.txt file should exist", hellotxt.exists());
+			File hellotxt = new File(localDb.getWorkTree(), "World.txt");
+			assertTrue("The World.txt file should exist", hellotxt.exists());
 			dotmodules = new File(localDb.getWorkTree(),
 					Constants.DOT_GIT_MODULES);
 		}
@@ -835,9 +837,9 @@ public void testReplaceManifestBare() throws Exception {
 				String line = reader.readLine();
 				if (line == null)
 					break;
-				if (line.contains("submodule \"foo\""))
+				if (line.contains("submodule \"" + defaultUri + "\""))
 					foo = true;
-				if (line.contains("submodule \"bar\""))
+				if (line.contains("submodule \"" + notDefaultUri + "\""))
 					bar = true;
 			}
 			assertTrue("The bar submodule should exist", bar);
@@ -876,9 +878,7 @@ public void testRemoveOverlappingBare() throws Exception {
 				Constants.DOT_GIT_MODULES);
 		}
 
-		// The .gitmodules file should have 'submodule "foo"' and shouldn't
-		// have
-		// 'submodule "foo/bar"' lines.
+		// Check .gitmodules file
 		try (BufferedReader reader = new BufferedReader(
 				new FileReader(dotmodules))) {
 			boolean foo = false;
@@ -888,16 +888,17 @@ public void testRemoveOverlappingBare() throws Exception {
 				String line = reader.readLine();
 				if (line == null)
 					break;
-				if (line.contains("submodule \"foo\""))
+				if (line.contains("submodule \"" + defaultUri + "\""))
 					foo = true;
-				if (line.contains("submodule \"foo/bar\""))
+				if (line.contains("submodule \"" + groupBUri + "\""))
 					foobar = true;
-				if (line.contains("submodule \"a\""))
+				if (line.contains("submodule \"" + groupAUri + "\""))
 					a = true;
 			}
-			assertTrue("The foo submodule should exist", foo);
-			assertFalse("The foo/bar submodule shouldn't exist", foobar);
-			assertTrue("The a submodule should exist", a);
+			assertTrue("The " + defaultUri + " submodule should exist", foo);
+			assertFalse("The " + groupBUri + " submodule shouldn't exist",
+					foobar);
+			assertTrue("The " + groupAUri + " submodule should exist", a);
 		}
 	}
 
@@ -1033,11 +1034,11 @@ public void testRecordRemoteBranch() throws Exception {
 			assertEquals(
 					"Recording remote branches should work for short branch descriptions",
 					"master",
-					c.getString("submodule", "with-branch", "branch"));
+					c.getString("submodule", notDefaultUri, "branch"));
 			assertEquals(
 					"Recording remote branches should work for full ref specs",
 					"refs/heads/master",
-					c.getString("submodule", "with-long-branch", "branch"));
+					c.getString("submodule", defaultUri, "branch"));
 		}
 	}
 
@@ -1095,7 +1096,7 @@ public void testRecordShallowRecommendation() throws Exception {
 				.append("<project path=\"shallow-please\" ").append("name=\"")
 				.append(defaultUri).append("\" ").append("clone-depth=\"1\" />")
 				.append("<project path=\"non-shallow\" ").append("name=\"")
-				.append(defaultUri).append("\" />").append("</manifest>");
+				.append(notDefaultUri).append("\" />").append("</manifest>");
 		JGitTestUtil.writeTrashFile(tempDb, "manifest.xml",
 				xmlContent.toString());
 
@@ -1115,9 +1116,9 @@ public void testRecordShallowRecommendation() throws Exception {
 			FileBasedConfig c = new FileBasedConfig(gitmodules, FS.DETECTED);
 			c.load();
 			assertEquals("Recording shallow configuration should work", "true",
-					c.getString("submodule", "shallow-please", "shallow"));
+					c.getString("submodule", defaultUri, "shallow"));
 			assertNull("Recording non shallow configuration should work",
-					c.getString("submodule", "non-shallow", "shallow"));
+					c.getString("submodule", notDefaultUri, "shallow"));
 		}
 	}
 
@@ -1176,6 +1177,43 @@ public void testDefaultRemoteRevision() throws Exception {
 		}
 	}
 
+	@Test
+	public void testTwoPathUseTheSameName() throws Exception {
+		Repository remoteDb = createBareRepository();
+		Repository tempDb = createWorkRepository();
+
+		StringBuilder xmlContent = new StringBuilder();
+		xmlContent.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n")
+				.append("<manifest>")
+				.append("<remote name=\"remote1\" fetch=\".\" />")
+				.append("<default revision=\"master\" remote=\"remote1\" />")
+				.append("<project path=\"path1\" ").append("name=\"")
+				.append(defaultUri).append("\" />")
+				.append("<project path=\"path2\" ").append("name=\"")
+				.append(defaultUri).append("\" />").append("</manifest>");
+		JGitTestUtil.writeTrashFile(tempDb, "manifest.xml",
+				xmlContent.toString());
+
+		RepoCommand command = new RepoCommand(remoteDb);
+		command.setPath(
+				tempDb.getWorkTree().getAbsolutePath() + "/manifest.xml")
+				.setURI(rootUri).setRecommendShallow(true).call();
+		File directory = createTempDirectory("testBareRepo");
+		try (Repository localDb = Git.cloneRepository().setDirectory(directory)
+				.setURI(remoteDb.getDirectory().toURI().toString()).call()
+				.getRepository();) {
+			File gitmodules = new File(localDb.getWorkTree(), ".gitmodules");
+			assertTrue("The .gitmodules file should exist",
+					gitmodules.exists());
+			FileBasedConfig c = new FileBasedConfig(gitmodules, FS.DETECTED);
+			c.load();
+			assertEquals("A module should exist for path1", "path1",
+					c.getString("submodule", defaultUri + "/path1", "path"));
+			assertEquals("A module should exist for path2", "path2",
+					c.getString("submodule", defaultUri + "/path2", "path"));
+		}
+	}
+
 	private void resolveRelativeUris() {
 		// Find the longest common prefix ends with "/" as rootUri.
 		defaultUri = defaultDb.getDirectory().toURI().toString();
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/CGitIgnoreTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/CGitIgnoreTest.java
index 0c6ed0c..b4c20a7 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/CGitIgnoreTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/CGitIgnoreTest.java
@@ -42,6 +42,7 @@
  */
 package org.eclipse.jgit.ignore;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -56,7 +57,6 @@
 import java.util.Set;
 
 import org.eclipse.jgit.junit.RepositoryTestCase;
-import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.StoredConfig;
 import org.eclipse.jgit.treewalk.FileTreeIterator;
 import org.eclipse.jgit.treewalk.TreeWalk;
@@ -113,7 +113,7 @@ private String toString(TemporaryBuffer b) throws IOException {
 				"exit " + result.getRc() + '\n' + errorOut);
 		try (BufferedReader r = new BufferedReader(new InputStreamReader(
 				new BufferedInputStream(result.getStdout().openInputStream()),
-				Constants.CHARSET))) {
+				UTF_8))) {
 			return r.lines().toArray(String[]::new);
 		}
 	}
@@ -131,7 +131,7 @@ private String toString(TemporaryBuffer b) throws IOException {
 				"exit " + result.getRc() + '\n' + errorOut);
 		try (BufferedReader r = new BufferedReader(new InputStreamReader(
 				new BufferedInputStream(result.getStdout().openInputStream()),
-				Constants.CHARSET))) {
+				UTF_8))) {
 			return r.lines().toArray(String[]::new);
 		}
 	}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/IgnoreNodeTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/IgnoreNodeTest.java
index 78d9a82..cbc0761 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/IgnoreNodeTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/IgnoreNodeTest.java
@@ -42,7 +42,7 @@
  */
 package org.eclipse.jgit.ignore;
 
-import static org.eclipse.jgit.lib.Constants.CHARSET;
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.eclipse.jgit.junit.Assert.assertEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -767,6 +767,6 @@ private InputStream writeToString(String... rules) {
 		for (String line : rules) {
 			data.append(line + "\n");
 		}
-		return new ByteArrayInputStream(data.toString().getBytes(CHARSET));
+		return new ByteArrayInputStream(data.toString().getBytes(UTF_8));
 	}
 }
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/indexdiff/IndexDiffWithSymlinkTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/indexdiff/IndexDiffWithSymlinkTest.java
index 26c11c7..7e513d2 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/indexdiff/IndexDiffWithSymlinkTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/indexdiff/IndexDiffWithSymlinkTest.java
@@ -41,7 +41,7 @@
  */
 package org.eclipse.jgit.indexdiff;
 
-import static org.eclipse.jgit.lib.Constants.CHARSET;
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
@@ -128,7 +128,7 @@ private File restoreGitRepo(InputStream in, File testDir, String name)
 		File restoreScript = new File(testDir, name + ".sh");
 		try (OutputStream out = new BufferedOutputStream(
 				new FileOutputStream(restoreScript));
-				Writer writer = new OutputStreamWriter(out, CHARSET)) {
+				Writer writer = new OutputStreamWriter(out, UTF_8)) {
 			writer.write("echo `which git` 1>&2\n");
 			writer.write("echo `git --version` 1>&2\n");
 			writer.write("git init " + name + " && \\\n");
@@ -170,7 +170,7 @@ private void copy(InputStream from, File to) throws IOException {
 
 	private String readStream(InputStream stream) throws IOException {
 		try (BufferedReader in = new BufferedReader(
-				new InputStreamReader(stream))) {
+				new InputStreamReader(stream, UTF_8))) {
 			StringBuilder out = new StringBuilder();
 			String line;
 			while ((line = in.readLine()) != null) {
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 5b567d0..bfa30d5 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
@@ -972,7 +972,7 @@ private void run(DfsGarbageCollector gc) throws IOException {
 	private static boolean isReachable(Repository repo, AnyObjectId id)
 			throws IOException {
 		try (RevWalk rw = new RevWalk(repo)) {
-			for (Ref ref : repo.getAllRefs().values()) {
+			for (Ref ref : repo.getRefDatabase().getRefs()) {
 				rw.markStart(rw.parseCommit(ref.getObjectId()));
 			}
 			for (RevCommit next; (next = rw.next()) != null;) {
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsPackDescriptionTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsPackDescriptionTest.java
new file mode 100644
index 0000000..55e1a9c
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsPackDescriptionTest.java
@@ -0,0 +1,293 @@
+/*
+ * Copyright (C) 2018, Google LLC.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ *   copyright notice, this list of conditions and the following
+ *   disclaimer in the documentation and/or other materials provided
+ *   with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ *   names of its contributors may be used to endorse or promote
+ *   products derived from this software without specific prior
+ *   written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.internal.storage.dfs;
+
+import static org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource.COMPACT;
+import static org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource.GC;
+import static org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource.GC_REST;
+import static org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource.GC_TXN;
+import static org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource.INSERT;
+import static org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource.RECEIVE;
+import static org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource.UNREACHABLE_GARBAGE;
+import static org.eclipse.jgit.internal.storage.pack.PackExt.INDEX;
+import static org.eclipse.jgit.internal.storage.pack.PackExt.PACK;
+import static org.junit.Assert.assertEquals;
+
+import java.util.Comparator;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource;
+import org.junit.Before;
+import org.junit.Test;
+
+public final class DfsPackDescriptionTest {
+	private AtomicInteger counter;
+
+	@Before
+	public void setUp() {
+		counter = new AtomicInteger();
+	}
+
+	@Test
+	public void objectLookupComparatorEqual() throws Exception {
+		DfsPackDescription a = create(RECEIVE);
+		a.setFileSize(PACK, 1);
+		a.setFileSize(INDEX, 1);
+		a.setLastModified(1);
+		a.setObjectCount(1);
+		a.setMaxUpdateIndex(1);
+
+		DfsPackDescription b = create(INSERT);
+		b.setFileSize(PACK, 1);
+		b.setFileSize(INDEX, 2);
+		b.setLastModified(1);
+		b.setObjectCount(1);
+		b.setMaxUpdateIndex(2);
+
+		assertComparesEqual(DfsPackDescription.objectLookupComparator(), a, b);
+	}
+
+	@Test
+	public void objectLookupComparatorPackSource() throws Exception {
+		DfsPackDescription a = create(COMPACT);
+		a.setFileSize(PACK, 2);
+		a.setLastModified(1);
+		a.setObjectCount(2);
+
+		DfsPackDescription b = create(GC);
+		b.setFileSize(PACK, 1);
+		b.setLastModified(2);
+		b.setObjectCount(1);
+
+		assertComparesLessThan(DfsPackDescription.objectLookupComparator(), a, b);
+	}
+
+	@Test
+	public void objectLookupComparatorCustomPackSourceComparator()
+			throws Exception {
+		DfsPackDescription a = create(GC);
+
+		DfsPackDescription b = create(COMPACT);
+
+		assertComparesLessThan(DfsPackDescription.objectLookupComparator(), b, a);
+		assertComparesLessThan(
+				DfsPackDescription.objectLookupComparator(
+					new PackSource.ComparatorBuilder()
+						.add(GC)
+						.add(INSERT, RECEIVE, GC_REST, GC_TXN, UNREACHABLE_GARBAGE)
+						.add(COMPACT)
+						.build()),
+				a, b);
+	}
+
+	@Test
+	public void objectLookupComparatorGcFileSize() throws Exception {
+		// a is older and smaller.
+		DfsPackDescription a = create(GC_REST);
+		a.setFileSize(PACK, 100);
+		a.setLastModified(1);
+		a.setObjectCount(2);
+
+		// b is newer and larger.
+		DfsPackDescription b = create(GC_REST);
+		b.setFileSize(PACK, 200);
+		b.setLastModified(2);
+		b.setObjectCount(1);
+
+		// Since they have the same GC type, tiebreaker is size, and a comes first.
+		assertComparesLessThan(DfsPackDescription.objectLookupComparator(), a, b);
+	}
+
+	@Test
+	public void objectLookupComparatorNonGcLastModified()
+			throws Exception {
+		// a is older and smaller.
+		DfsPackDescription a = create(INSERT);
+		a.setFileSize(PACK, 100);
+		a.setLastModified(1);
+		a.setObjectCount(2);
+
+		// b is newer and larger.
+		DfsPackDescription b = create(INSERT);
+		b.setFileSize(PACK, 200);
+		b.setLastModified(2);
+		b.setObjectCount(1);
+
+		// Since they have the same type but not GC, tiebreaker is last modified,
+		// and b comes first.
+		assertComparesLessThan(DfsPackDescription.objectLookupComparator(), b, a);
+	}
+
+	@Test
+	public void objectLookupComparatorObjectCount() throws Exception {
+		DfsPackDescription a = create(INSERT);
+		a.setObjectCount(1);
+
+		DfsPackDescription b = create(INSERT);
+		b.setObjectCount(2);
+
+		assertComparesLessThan(DfsPackDescription.objectLookupComparator(), a, b);
+	}
+
+	@Test
+	public void reftableComparatorEqual() throws Exception {
+		DfsPackDescription a = create(INSERT);
+		a.setFileSize(PACK, 100);
+		a.setObjectCount(1);
+
+		DfsPackDescription b = create(INSERT);
+		b.setFileSize(PACK, 200);
+		a.setObjectCount(2);
+
+		assertComparesEqual(DfsPackDescription.reftableComparator(), a, b);
+	}
+
+	@Test
+	public void reftableComparatorPackSource() throws Exception {
+		DfsPackDescription a = create(INSERT);
+		a.setMaxUpdateIndex(1);
+		a.setLastModified(1);
+
+		DfsPackDescription b = create(GC);
+		b.setMaxUpdateIndex(2);
+		b.setLastModified(2);
+
+		assertComparesLessThan(DfsPackDescription.reftableComparator(), b, a);
+	}
+
+	@Test
+	public void reftableComparatorMaxUpdateIndex() throws Exception {
+		DfsPackDescription a = create(INSERT);
+		a.setMaxUpdateIndex(1);
+		a.setLastModified(2);
+
+		DfsPackDescription b = create(INSERT);
+		b.setMaxUpdateIndex(2);
+		b.setLastModified(1);
+
+		assertComparesLessThan(DfsPackDescription.reftableComparator(), a, b);
+	}
+
+	@Test
+	public void reftableComparatorLastModified() throws Exception {
+		DfsPackDescription a = create(INSERT);
+		a.setLastModified(1);
+
+		DfsPackDescription b = create(INSERT);
+		b.setLastModified(2);
+
+		assertComparesLessThan(DfsPackDescription.reftableComparator(), a, b);
+	}
+
+	@Test
+	public void reuseComparatorEqual() throws Exception {
+		DfsPackDescription a = create(RECEIVE);
+		a.setFileSize(PACK, 1);
+		a.setFileSize(INDEX, 1);
+		a.setLastModified(1);
+		a.setObjectCount(1);
+		a.setMaxUpdateIndex(1);
+
+		DfsPackDescription b = create(INSERT);
+		b.setFileSize(PACK, 2);
+		b.setFileSize(INDEX, 2);
+		b.setLastModified(2);
+		b.setObjectCount(2);
+		b.setMaxUpdateIndex(2);
+
+		assertComparesEqual(DfsPackDescription.reuseComparator(), a, b);
+	}
+
+	@Test
+	public void reuseComparatorGcPackSize() throws Exception {
+		DfsPackDescription a = create(GC_REST);
+		a.setFileSize(PACK, 1);
+		a.setFileSize(INDEX, 1);
+		a.setLastModified(2);
+		a.setObjectCount(1);
+		a.setMaxUpdateIndex(1);
+
+		DfsPackDescription b = create(GC_REST);
+		b.setFileSize(PACK, 2);
+		b.setFileSize(INDEX, 2);
+		b.setLastModified(1);
+		b.setObjectCount(2);
+		b.setMaxUpdateIndex(2);
+
+		assertComparesLessThan(DfsPackDescription.reuseComparator(), b, a);
+	}
+
+	private DfsPackDescription create(PackSource source) {
+		return new DfsPackDescription(
+				new DfsRepositoryDescription("repo"),
+				"pack_" + counter.incrementAndGet(),
+				source);
+	}
+
+	private static <T> void assertComparesEqual(
+			Comparator<T> comparator, T o1, T o2) {
+		assertEquals(
+				"first object must compare equal to itself",
+				0, comparator.compare(o1, o1));
+		assertEquals(
+				"second object must compare equal to itself",
+				0, comparator.compare(o2, o2));
+		assertEquals(
+				"first object must compare equal to second object",
+				0, comparator.compare(o1, o2));
+	}
+
+	private static <T> void assertComparesLessThan(
+			Comparator<T> comparator, T o1, T o2) {
+		assertEquals(
+				"first object must compare equal to itself",
+				0, comparator.compare(o1, o1));
+		assertEquals(
+				"second object must compare equal to itself",
+				0, comparator.compare(o2, o2));
+		assertEquals(
+				"first object must compare less than second object",
+				-1, comparator.compare(o1, o2));
+	}
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/PackSourceTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/PackSourceTest.java
new file mode 100644
index 0000000..7bf1f58
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/PackSourceTest.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2018, Google LLC.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ *   copyright notice, this list of conditions and the following
+ *   disclaimer in the documentation and/or other materials provided
+ *   with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ *   names of its contributors may be used to endorse or promote
+ *   products derived from this software without specific prior
+ *   written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.internal.storage.dfs;
+
+import static org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource.COMPACT;
+import static org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource.DEFAULT_COMPARATOR;
+import static org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource.GC;
+import static org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource.GC_REST;
+import static org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource.GC_TXN;
+import static org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource.INSERT;
+import static org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource.RECEIVE;
+import static org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource.UNREACHABLE_GARBAGE;
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+
+public class PackSourceTest {
+	@Test
+	public void defaultComaprator() throws Exception {
+		assertEquals(0, DEFAULT_COMPARATOR.compare(INSERT, INSERT));
+		assertEquals(0, DEFAULT_COMPARATOR.compare(RECEIVE, RECEIVE));
+		assertEquals(0, DEFAULT_COMPARATOR.compare(COMPACT, COMPACT));
+		assertEquals(0, DEFAULT_COMPARATOR.compare(GC, GC));
+		assertEquals(0, DEFAULT_COMPARATOR.compare(GC_REST, GC_REST));
+		assertEquals(0, DEFAULT_COMPARATOR.compare(GC_TXN, GC_TXN));
+		assertEquals(0, DEFAULT_COMPARATOR.compare(UNREACHABLE_GARBAGE, UNREACHABLE_GARBAGE));
+
+		assertEquals(0, DEFAULT_COMPARATOR.compare(INSERT, RECEIVE));
+		assertEquals(0, DEFAULT_COMPARATOR.compare(RECEIVE, INSERT));
+
+		assertEquals(-1, DEFAULT_COMPARATOR.compare(INSERT, COMPACT));
+		assertEquals(1, DEFAULT_COMPARATOR.compare(COMPACT, INSERT));
+
+		assertEquals(-1, DEFAULT_COMPARATOR.compare(RECEIVE, COMPACT));
+		assertEquals(1, DEFAULT_COMPARATOR.compare(COMPACT, RECEIVE));
+
+		assertEquals(-1, DEFAULT_COMPARATOR.compare(COMPACT, GC));
+		assertEquals(1, DEFAULT_COMPARATOR.compare(GC, COMPACT));
+
+		assertEquals(-1, DEFAULT_COMPARATOR.compare(GC, GC_REST));
+		assertEquals(1, DEFAULT_COMPARATOR.compare(GC_REST, GC));
+
+		assertEquals(-1, DEFAULT_COMPARATOR.compare(GC_REST, GC_TXN));
+		assertEquals(1, DEFAULT_COMPARATOR.compare(GC_TXN, GC_REST));
+
+		assertEquals(-1, DEFAULT_COMPARATOR.compare(GC_TXN, UNREACHABLE_GARBAGE));
+		assertEquals(1, DEFAULT_COMPARATOR.compare(UNREACHABLE_GARBAGE, GC_TXN));
+	}
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/ConcurrentRepackTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/ConcurrentRepackTest.java
index 643daa5..6bec056 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/ConcurrentRepackTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/ConcurrentRepackTest.java
@@ -56,6 +56,7 @@
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.OutputStream;
+import java.time.Instant;
 
 import org.eclipse.jgit.errors.IncorrectObjectTypeException;
 import org.eclipse.jgit.errors.MissingObjectException;
@@ -71,6 +72,7 @@
 import org.eclipse.jgit.revwalk.RevObject;
 import org.eclipse.jgit.revwalk.RevWalk;
 import org.eclipse.jgit.storage.file.WindowCacheConfig;
+import org.eclipse.jgit.util.FS;
 import org.eclipse.jgit.util.FileUtils;
 import org.junit.After;
 import org.junit.Before;
@@ -235,7 +237,8 @@ private RevObject parse(AnyObjectId id)
 
 	private static void write(File[] files, PackWriter pw)
 			throws IOException {
-		final long begin = files[0].getParentFile().lastModified();
+		final Instant begin = FS.DETECTED
+				.lastModifiedInstant(files[0].getParentFile());
 		NullProgressMonitor m = NullProgressMonitor.INSTANCE;
 
 		try (OutputStream out = new BufferedOutputStream(
@@ -252,7 +255,8 @@ private static void write(File[] files, PackWriter pw)
 	}
 
 	private static void delete(File[] list) throws IOException {
-		final long begin = list[0].getParentFile().lastModified();
+		final Instant begin = FS.DETECTED
+				.lastModifiedInstant(list[0].getParentFile());
 		for (File f : list) {
 			FileUtils.delete(f);
 			assertFalse(f + " was removed", f.exists());
@@ -260,14 +264,14 @@ private static void delete(File[] list) throws IOException {
 		touch(begin, list[0].getParentFile());
 	}
 
-	private static void touch(long begin, File dir) {
-		while (begin >= dir.lastModified()) {
+	private static void touch(Instant begin, File dir) throws IOException {
+		while (begin.compareTo(FS.DETECTED.lastModifiedInstant(dir)) >= 0) {
 			try {
 				Thread.sleep(25);
 			} catch (InterruptedException ie) {
 				//
 			}
-			dir.setLastModified(System.currentTimeMillis());
+			FS.DETECTED.setLastModified(dir.toPath(), Instant.now());
 		}
 	}
 
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/FileSnapshotTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/FileSnapshotTest.java
index 9ceaa34..35f4fcb 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/FileSnapshotTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/FileSnapshotTest.java
@@ -42,43 +42,70 @@
  */
 package org.eclipse.jgit.internal.storage.file;
 
+import static org.eclipse.jgit.junit.JGitTestUtil.read;
+import static org.eclipse.jgit.junit.JGitTestUtil.write;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
 import java.io.File;
-import java.io.FileOutputStream;
 import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
+import java.nio.file.StandardOpenOption;
+import java.nio.file.attribute.FileTime;
+import java.time.Duration;
+import java.time.Instant;
 import java.util.ArrayList;
-import java.util.List;
+import java.util.concurrent.TimeUnit;
 
+import org.eclipse.jgit.junit.MockSystemReader;
+import org.eclipse.jgit.util.FS;
+import org.eclipse.jgit.util.FS.FileStoreAttributes;
 import org.eclipse.jgit.util.FileUtils;
+import org.eclipse.jgit.util.Stats;
+import org.eclipse.jgit.util.SystemReader;
 import org.junit.After;
+import org.junit.Assume;
 import org.junit.Before;
 import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 public class FileSnapshotTest {
+	private static final Logger LOG = LoggerFactory
+			.getLogger(FileSnapshotTest.class);
 
-	private List<File> files = new ArrayList<>();
+	private Path trash;
 
-	private File trash;
+	private FileStoreAttributes fsAttrCache;
 
 	@Before
 	public void setUp() throws Exception {
-		trash = File.createTempFile("tmp_", "");
-		trash.delete();
-		assertTrue("mkdir " + trash, trash.mkdir());
+		SystemReader.setInstance(new MockSystemReader());
+		trash = Files.createTempDirectory("tmp_");
+		// measure timer resolution before the test to avoid time critical tests
+		// are affected by time needed for measurement
+		fsAttrCache = FS
+				.getFileStoreAttributes(trash.getParent());
 	}
 
 	@Before
 	@After
 	public void tearDown() throws Exception {
-		FileUtils.delete(trash, FileUtils.RECURSIVE | FileUtils.SKIP_MISSING);
+		FileUtils.delete(trash.toFile(),
+				FileUtils.RECURSIVE | FileUtils.SKIP_MISSING);
 	}
 
-	private static void waitNextSec(File f) {
-		long initialLastModified = f.lastModified();
+	private static void waitNextTick(Path f) throws IOException {
+		Instant initialLastModified = FS.DETECTED.lastModifiedInstant(f);
 		do {
-			f.setLastModified(System.currentTimeMillis());
-		} while (f.lastModified() == initialLastModified);
+			FS.DETECTED.setLastModified(f, Instant.now());
+		} while (FS.DETECTED.lastModifiedInstant(f)
+				.equals(initialLastModified));
 	}
 
 	/**
@@ -88,12 +115,12 @@ private static void waitNextSec(File f) {
 	 */
 	@Test
 	public void testActuallyIsModifiedTrivial() throws Exception {
-		File f1 = createFile("simple");
-		waitNextSec(f1);
-		FileSnapshot save = FileSnapshot.save(f1);
+		Path f1 = createFile("simple");
+		waitNextTick(f1);
+		FileSnapshot save = FileSnapshot.save(f1.toFile());
 		append(f1, (byte) 'x');
-		waitNextSec(f1);
-		assertTrue(save.isModified(f1));
+		waitNextTick(f1);
+		assertTrue(save.isModified(f1.toFile()));
 	}
 
 	/**
@@ -106,11 +133,17 @@ public void testActuallyIsModifiedTrivial() throws Exception {
 	 */
 	@Test
 	public void testNewFileWithWait() throws Exception {
-		File f1 = createFile("newfile");
-		waitNextSec(f1);
-		FileSnapshot save = FileSnapshot.save(f1);
-		Thread.sleep(1500);
-		assertTrue(save.isModified(f1));
+		// if filesystem timestamp resolution is high the snapshot won't be
+		// racily clean
+		Assume.assumeTrue(
+				fsAttrCache.getFsTimestampResolution()
+						.compareTo(Duration.ofMillis(10)) > 0);
+		Path f1 = createFile("newfile");
+		waitNextTick(f1);
+		FileSnapshot save = FileSnapshot.save(f1.toFile());
+		TimeUnit.NANOSECONDS.sleep(
+				fsAttrCache.getFsTimestampResolution().dividedBy(2).toNanos());
+		assertTrue(save.isModified(f1.toFile()));
 	}
 
 	/**
@@ -120,22 +153,152 @@ public void testNewFileWithWait() throws Exception {
 	 */
 	@Test
 	public void testNewFileNoWait() throws Exception {
-		File f1 = createFile("newfile");
-		waitNextSec(f1);
-		FileSnapshot save = FileSnapshot.save(f1);
-		Thread.sleep(1500);
-		assertTrue(save.isModified(f1));
+		// if filesystem timestamp resolution is smaller than time needed to
+		// create a file and FileSnapshot the snapshot won't be racily clean
+		Assume.assumeTrue(fsAttrCache.getFsTimestampResolution()
+				.compareTo(Duration.ofMillis(10)) > 0);
+		for (int i = 0; i < 50; i++) {
+			Instant start = Instant.now();
+			Path f1 = createFile("newfile");
+			FileSnapshot save = FileSnapshot.save(f1.toFile());
+			Duration res = FS.getFileStoreAttributes(f1)
+					.getFsTimestampResolution();
+			Instant end = Instant.now();
+			if (Duration.between(start, end)
+					.compareTo(res.multipliedBy(2)) > 0) {
+				// This test is racy: under load, there may be a delay between createFile() and
+				// FileSnapshot.save(). This can stretch the time between the read TS and FS
+				// creation TS to the point that it exceeds the FS granularity, and we
+				// conclude it cannot be racily clean, and therefore must be really clean.
+				//
+				// This should be relatively uncommon.
+				continue;
+			}
+			// The file wasn't really modified, but it looks just like a "maybe racily clean"
+			// file.
+			assertTrue(save.isModified(f1.toFile()));
+			return;
+		}
+		fail("too much load for this test");
 	}
 
-	private File createFile(String string) throws IOException {
-		trash.mkdirs();
-		File f = File.createTempFile(string, "tdat", trash);
-		files.add(f);
-		return f;
+	/**
+	 * Simulate packfile replacement in same file which may occur if set of
+	 * objects in the pack is the same but pack config was different. On Posix
+	 * filesystems this should change the inode (filekey in java.nio
+	 * terminology).
+	 *
+	 * @throws Exception
+	 */
+	@Test
+	public void testSimulatePackfileReplacement() throws Exception {
+		Assume.assumeFalse(SystemReader.getInstance().isWindows());
+		Path f1 = createFile("file"); // inode y
+		Path f2 = createFile("fool"); // Guarantees new inode x
+		// wait on f2 since this method resets lastModified of the file
+		// and leaves lastModified of f1 untouched
+		waitNextTick(f2);
+		waitNextTick(f2);
+		FileTime timestamp = Files.getLastModifiedTime(f1);
+		FileSnapshot save = FileSnapshot.save(f1.toFile());
+		Files.move(f2, f1, // Now "file" is inode x
+				StandardCopyOption.REPLACE_EXISTING,
+				StandardCopyOption.ATOMIC_MOVE);
+		Files.setLastModifiedTime(f1, timestamp);
+		assertTrue(save.isModified(f1.toFile()));
+		assertTrue("unexpected change of fileKey", save.wasFileKeyChanged());
+		assertFalse("unexpected size change", save.wasSizeChanged());
+		assertFalse("unexpected lastModified change",
+				save.wasLastModifiedChanged());
+		assertFalse("lastModified was unexpectedly racily clean",
+				save.wasLastModifiedRacilyClean());
 	}
 
-	private static void append(File f, byte b) throws IOException {
-		try (FileOutputStream os = new FileOutputStream(f, true)) {
+	/**
+	 * Append a character to a file to change its size and set original
+	 * lastModified
+	 *
+	 * @throws Exception
+	 */
+	@Test
+	public void testFileSizeChanged() throws Exception {
+		Path f = createFile("file");
+		FileTime timestamp = Files.getLastModifiedTime(f);
+		FileSnapshot save = FileSnapshot.save(f.toFile());
+		append(f, (byte) 'x');
+		Files.setLastModifiedTime(f, timestamp);
+		assertTrue(save.isModified(f.toFile()));
+		assertTrue(save.wasSizeChanged());
+	}
+
+	@Test
+	public void fileSnapshotEquals() throws Exception {
+		// 0 sized FileSnapshot.
+		FileSnapshot fs1 = FileSnapshot.MISSING_FILE;
+		// UNKNOWN_SIZE FileSnapshot.
+		FileSnapshot fs2 = FileSnapshot.save(fs1.lastModifiedInstant());
+
+		assertTrue(fs1.equals(fs2));
+		assertTrue(fs2.equals(fs1));
+	}
+
+	@SuppressWarnings("boxing")
+	@Test
+	public void detectFileModified() throws IOException {
+		int failures = 0;
+		long racyNanos = 0;
+		final int COUNT = 10000;
+		ArrayList<Long> deltas = new ArrayList<>();
+		File f = createFile("test").toFile();
+		for (int i = 0; i < COUNT; i++) {
+			write(f, "a");
+			FileSnapshot snapshot = FileSnapshot.save(f);
+			assertEquals("file should contain 'a'", "a", read(f));
+			write(f, "b");
+			if (!snapshot.isModified(f)) {
+				deltas.add(snapshot.lastDelta());
+				racyNanos = snapshot.lastRacyThreshold();
+				failures++;
+			}
+			assertEquals("file should contain 'b'", "b", read(f));
+		}
+		if (failures > 0) {
+			Stats stats = new Stats();
+			LOG.debug(
+					"delta [ns] since modification FileSnapshot failed to detect");
+			for (Long d : deltas) {
+				stats.add(d);
+				LOG.debug(String.format("%,d", d));
+			}
+			LOG.error(
+					"count, failures, eff. racy threshold [ns], delta min [ns],"
+							+ " delta max [ns], delta avg [ns],"
+							+ " delta stddev [ns]");
+			LOG.error(String.format(
+					"%,d, %,d, %,d, %,.0f, %,.0f, %,.0f, %,.0f", COUNT,
+					failures, racyNanos, stats.min(), stats.max(),
+					stats.avg(), stats.stddev()));
+		}
+		assertTrue(
+				String.format(
+						"FileSnapshot: failures to detect file modifications"
+								+ " %d out of %d\n"
+								+ "timestamp resolution %d µs"
+								+ " min racy threshold %d µs"
+						, failures, COUNT,
+						fsAttrCache.getFsTimestampResolution().toNanos() / 1000,
+						fsAttrCache.getMinimalRacyInterval().toNanos() / 1000),
+				failures == 0);
+	}
+
+	private Path createFile(String string) throws IOException {
+		Files.createDirectories(trash);
+		return Files.createTempFile(trash, string, "tdat");
+	}
+
+	private static void append(Path f, byte b) throws IOException {
+		try (OutputStream os = Files.newOutputStream(f,
+				StandardOpenOption.APPEND)) {
 			os.write(b);
 		}
 	}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcTestCase.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcTestCase.java
index d16998d..eaa245b 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcTestCase.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcTestCase.java
@@ -147,9 +147,10 @@ protected RevCommit commitChain(int depth, int width) throws Exception {
 		return tip;
 	}
 
-	protected long lastModified(AnyObjectId objectId) throws IOException {
-		return repo.getFS().lastModified(
-				repo.getObjectDatabase().fileFor(objectId));
+	protected long lastModified(AnyObjectId objectId) {
+		return repo.getFS()
+				.lastModifiedInstant(repo.getObjectDatabase().fileFor(objectId))
+				.toEpochMilli();
 	}
 
 	protected static void fsTick() throws InterruptedException, IOException {
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/ObjectDirectoryTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/ObjectDirectoryTest.java
index cbb73bb..8cc06d9 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
@@ -65,6 +65,7 @@
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.revwalk.RevCommit;
 import org.eclipse.jgit.storage.file.FileBasedConfig;
+import org.eclipse.jgit.util.FS;
 import org.junit.Assume;
 import org.junit.Rule;
 import org.junit.Test;
@@ -157,13 +158,14 @@ public void testScanningForPackfiles() throws Exception {
 
 			// To deal with racy-git situations JGit's Filesnapshot class will
 			// report a file/folder potentially dirty if
-			// cachedLastReadTime-cachedLastModificationTime < 2500ms. This
-			// causes JGit to always rescan a file after modification. But:
-			// this was true only if the difference between current system time
-			// and cachedLastModification time was less than 2500ms. If the
-			// modification is more than 2500ms ago we may have reported a
-			// file/folder to be clean although it has not been rescanned. A
-			// Bug. To show the bug we sleep for more than 2500ms
+			// cachedLastReadTime-cachedLastModificationTime < filesystem
+			// timestamp resolution. This causes JGit to always rescan a file
+			// after modification. But: this was true only if the difference
+			// between current system time and cachedLastModification time was
+			// less than 2500ms. If the modification is more than 2500ms ago we
+			// may have reported a file/folder to be clean although it has not
+			// been rescanned. A bug. To show the bug we sleep for more than
+			// 2500ms
 			Thread.sleep(2600);
 
 			File[] ret = packsFolder.listFiles(new FilenameFilter() {
@@ -173,7 +175,9 @@ public boolean accept(File dir, String name) {
 				}
 			});
 			assertTrue(ret != null && ret.length == 1);
-			Assume.assumeTrue(tmpFile.lastModified() == ret[0].lastModified());
+			FS fs = db.getFS();
+			Assume.assumeTrue(fs.lastModifiedInstant(tmpFile)
+					.equals(fs.lastModifiedInstant(ret[0])));
 
 			// all objects are in a new packfile but we will not detect it
 			assertFalse(receivingDB.hasObject(unknownID));
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackFileSnapshotTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackFileSnapshotTest.java
new file mode 100644
index 0000000..d5bc61a
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackFileSnapshotTest.java
@@ -0,0 +1,358 @@
+/*
+ * 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.internal.storage.file;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.Writer;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.StandardCopyOption;
+import java.nio.file.StandardOpenOption;
+//import java.nio.file.attribute.BasicFileAttributes;
+import java.text.ParseException;
+import java.time.Instant;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.Random;
+import java.util.zip.Deflater;
+
+import org.eclipse.jgit.api.GarbageCollectCommand;
+import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.api.errors.AbortedByHookException;
+import org.eclipse.jgit.api.errors.ConcurrentRefUpdateException;
+import org.eclipse.jgit.api.errors.GitAPIException;
+import org.eclipse.jgit.api.errors.NoFilepatternException;
+import org.eclipse.jgit.api.errors.NoHeadException;
+import org.eclipse.jgit.api.errors.NoMessageException;
+import org.eclipse.jgit.api.errors.UnmergedPathsException;
+import org.eclipse.jgit.api.errors.WrongRepositoryStateException;
+import org.eclipse.jgit.junit.RepositoryTestCase;
+import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.lib.ConfigConstants;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.storage.file.FileBasedConfig;
+import org.eclipse.jgit.storage.pack.PackConfig;
+import org.eclipse.jgit.util.FS;
+import org.junit.Test;
+
+public class PackFileSnapshotTest extends RepositoryTestCase {
+
+	private static ObjectId unknownID = ObjectId
+			.fromString("1234567890123456789012345678901234567890");
+
+	@Test
+	public void testSamePackDifferentCompressionDetectChecksumChanged()
+			throws Exception {
+		Git git = Git.wrap(db);
+		File f = writeTrashFile("file", "foobar ");
+		for (int i = 0; i < 10; i++) {
+			appendRandomLine(f);
+			git.add().addFilepattern("file").call();
+			git.commit().setMessage("message" + i).call();
+		}
+
+		FileBasedConfig c = db.getConfig();
+		c.setInt(ConfigConstants.CONFIG_GC_SECTION, null,
+				ConfigConstants.CONFIG_KEY_AUTOPACKLIMIT, 1);
+		c.save();
+		Collection<PackFile> packs = gc(Deflater.NO_COMPRESSION);
+		assertEquals("expected 1 packfile after gc", 1, packs.size());
+		PackFile p1 = packs.iterator().next();
+		PackFileSnapshot snapshot = p1.getFileSnapshot();
+
+		packs = gc(Deflater.BEST_COMPRESSION);
+		assertEquals("expected 1 packfile after gc", 1, packs.size());
+		PackFile p2 = packs.iterator().next();
+		File pf = p2.getPackFile();
+
+		// changing compression level with aggressive gc may change size,
+		// fileKey (on *nix) and checksum. Hence FileSnapshot.isModified can
+		// return true already based on size or fileKey.
+		// So the only thing we can test here is that we ensure that checksum
+		// also changed when we read it here in this test
+		assertTrue("expected snapshot to detect modified pack",
+				snapshot.isModified(pf));
+		assertTrue("expected checksum changed", snapshot.isChecksumChanged(pf));
+	}
+
+	private void appendRandomLine(File f, int length, Random r)
+			throws IOException {
+		try (Writer w = Files.newBufferedWriter(f.toPath(),
+				StandardOpenOption.APPEND)) {
+			appendRandomLine(w, length, r);
+		}
+	}
+
+	private void appendRandomLine(File f) throws IOException {
+		appendRandomLine(f, 5, new Random());
+	}
+
+	private void appendRandomLine(Writer w, int len, Random r)
+			throws IOException {
+		final int c1 = 32; // ' '
+		int c2 = 126; // '~'
+		for (int i = 0; i < len; i++) {
+			w.append((char) (c1 + r.nextInt(1 + c2 - c1)));
+		}
+	}
+
+	private ObjectId createTestRepo(int testDataSeed, int testDataLength)
+			throws IOException, GitAPIException, NoFilepatternException,
+			NoHeadException, NoMessageException, UnmergedPathsException,
+			ConcurrentRefUpdateException, WrongRepositoryStateException,
+			AbortedByHookException {
+		// Create a repo with two commits and one file. Each commit adds
+		// testDataLength number of bytes. Data are random bytes. Since the
+		// seed for the random number generator is specified we will get
+		// the same set of bytes for every run and for every platform
+		Random r = new Random(testDataSeed);
+		Git git = Git.wrap(db);
+		File f = writeTrashFile("file", "foobar ");
+		appendRandomLine(f, testDataLength, r);
+		git.add().addFilepattern("file").call();
+		git.commit().setMessage("message1").call();
+		appendRandomLine(f, testDataLength, r);
+		git.add().addFilepattern("file").call();
+		return git.commit().setMessage("message2").call().getId();
+	}
+
+	// Try repacking so fast that you get two new packs which differ only in
+	// content/chksum but have same name, size and lastmodified.
+	// Since this is done with standard gc (which creates new tmp files and
+	// renames them) the filekeys of the new packfiles differ helping jgit
+	// to detect the fast modification
+	@Test
+	public void testDetectModificationAlthoughSameSizeAndModificationtime()
+			throws Exception {
+		int testDataSeed = 1;
+		int testDataLength = 100;
+		FileBasedConfig config = db.getConfig();
+		// don't use mtime of the parent folder to detect pack file
+		// modification.
+		config.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
+				ConfigConstants.CONFIG_KEY_TRUSTFOLDERSTAT, false);
+		config.save();
+
+		createTestRepo(testDataSeed, testDataLength);
+
+		// repack to create initial packfile
+		PackFile pf = repackAndCheck(5, null, null, null);
+		Path packFilePath = pf.getPackFile().toPath();
+		AnyObjectId chk1 = pf.getPackChecksum();
+		String name = pf.getPackName();
+		Long length = Long.valueOf(pf.getPackFile().length());
+		FS fs = db.getFS();
+		Instant m1 = fs.lastModifiedInstant(packFilePath);
+
+		// Wait for a filesystem timer tick to enhance probability the rest of
+		// this test is done before the filesystem timer ticks again.
+		fsTick(packFilePath.toFile());
+
+		// Repack to create packfile with same name, length. Lastmodified and
+		// content and checksum are different since compression level differs
+		AnyObjectId chk2 = repackAndCheck(6, name, length, chk1)
+				.getPackChecksum();
+		Instant m2 = fs.lastModifiedInstant(packFilePath);
+		assumeFalse(m2.equals(m1));
+
+		// Repack to create packfile with same name, length. Lastmodified is
+		// equal to the previous one because we are in the same filesystem timer
+		// slot. Content and its checksum are different
+		AnyObjectId chk3 = repackAndCheck(7, name, length, chk2)
+				.getPackChecksum();
+		Instant m3 = fs.lastModifiedInstant(packFilePath);
+
+		// ask for an unknown git object to force jgit to rescan the list of
+		// available packs. If we would ask for a known objectid then JGit would
+		// skip searching for new/modified packfiles
+		db.getObjectDatabase().has(unknownID);
+		assertEquals(chk3, getSinglePack(db.getObjectDatabase().getPacks())
+				.getPackChecksum());
+		assumeTrue(m3.equals(m2));
+	}
+
+	// Try repacking so fast that we get two new packs which differ only in
+	// content and checksum but have same name, size and lastmodified.
+	// To avoid that JGit detects modification by checking the filekey create
+	// two new packfiles upfront and create copies of them. Then modify the
+	// packfiles in-place by opening them for write and then copying the
+	// content.
+	@Test
+	public void testDetectModificationAlthoughSameSizeAndModificationtimeAndFileKey()
+			throws Exception {
+		int testDataSeed = 1;
+		int testDataLength = 100;
+		FileBasedConfig config = db.getConfig();
+		config.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
+				ConfigConstants.CONFIG_KEY_TRUSTFOLDERSTAT, false);
+		config.save();
+
+		createTestRepo(testDataSeed, testDataLength);
+
+		// Repack to create initial packfile. Make a copy of it
+		PackFile pf = repackAndCheck(5, null, null, null);
+		Path packFilePath = pf.getPackFile().toPath();
+		Path packFileBasePath = packFilePath.resolveSibling(
+				packFilePath.getFileName().toString().replaceAll(".pack", ""));
+		AnyObjectId chk1 = pf.getPackChecksum();
+		String name = pf.getPackName();
+		Long length = Long.valueOf(pf.getPackFile().length());
+		copyPack(packFileBasePath, "", ".copy1");
+
+		// Repack to create second packfile. Make a copy of it
+		AnyObjectId chk2 = repackAndCheck(6, name, length, chk1)
+				.getPackChecksum();
+		copyPack(packFileBasePath, "", ".copy2");
+
+		// Repack to create third packfile
+		AnyObjectId chk3 = repackAndCheck(7, name, length, chk2)
+				.getPackChecksum();
+		FS fs = db.getFS();
+		Instant m3 = fs.lastModifiedInstant(packFilePath);
+		db.getObjectDatabase().has(unknownID);
+		assertEquals(chk3, getSinglePack(db.getObjectDatabase().getPacks())
+				.getPackChecksum());
+
+		// Wait for a filesystem timer tick to enhance probability the rest of
+		// this test is done before the filesystem timer ticks.
+		fsTick(packFilePath.toFile());
+
+		// Copy copy2 to packfile data to force modification of packfile without
+		// changing the packfile's filekey.
+		copyPack(packFileBasePath, ".copy2", "");
+		Instant m2 = fs.lastModifiedInstant(packFilePath);
+		assumeFalse(m3.equals(m2));
+
+		db.getObjectDatabase().has(unknownID);
+		assertEquals(chk2, getSinglePack(db.getObjectDatabase().getPacks())
+				.getPackChecksum());
+
+		// Copy copy2 to packfile data to force modification of packfile without
+		// changing the packfile's filekey.
+		copyPack(packFileBasePath, ".copy1", "");
+		Instant m1 = fs.lastModifiedInstant(packFilePath);
+		assumeTrue(m2.equals(m1));
+		db.getObjectDatabase().has(unknownID);
+		assertEquals(chk1, getSinglePack(db.getObjectDatabase().getPacks())
+				.getPackChecksum());
+	}
+
+	// Copy file from src to dst but avoid creating a new File (with new
+	// FileKey) if dst already exists
+	private Path copyFile(Path src, Path dst) throws IOException {
+		if (Files.exists(dst)) {
+			dst.toFile().setWritable(true);
+			try (OutputStream dstOut = Files.newOutputStream(dst)) {
+				Files.copy(src, dstOut);
+				return dst;
+			}
+		} else {
+			return Files.copy(src, dst, StandardCopyOption.REPLACE_EXISTING);
+		}
+	}
+
+	private Path copyPack(Path base, String srcSuffix, String dstSuffix)
+			throws IOException {
+		copyFile(Paths.get(base + ".idx" + srcSuffix),
+				Paths.get(base + ".idx" + dstSuffix));
+		copyFile(Paths.get(base + ".bitmap" + srcSuffix),
+				Paths.get(base + ".bitmap" + dstSuffix));
+		return copyFile(Paths.get(base + ".pack" + srcSuffix),
+				Paths.get(base + ".pack" + dstSuffix));
+	}
+
+	private PackFile repackAndCheck(int compressionLevel, String oldName,
+			Long oldLength, AnyObjectId oldChkSum)
+			throws IOException, ParseException {
+		PackFile p = getSinglePack(gc(compressionLevel));
+		File pf = p.getPackFile();
+		// The following two assumptions should not cause the test to fail. If
+		// on a certain platform we get packfiles (containing the same git
+		// objects) where the lengths differ or the checksums don't differ we
+		// just skip this test. A reason for that could be that compression
+		// works differently or random number generator works differently. Then
+		// we have to search for more consistent test data or checkin these
+		// packfiles as test resources
+		assumeTrue(oldLength == null || pf.length() == oldLength.longValue());
+		assumeTrue(oldChkSum == null || !p.getPackChecksum().equals(oldChkSum));
+		assertTrue(oldName == null || p.getPackName().equals(oldName));
+		return p;
+	}
+
+	private PackFile getSinglePack(Collection<PackFile> packs) {
+		Iterator<PackFile> pIt = packs.iterator();
+		PackFile p = pIt.next();
+		assertFalse(pIt.hasNext());
+		return p;
+	}
+
+	private Collection<PackFile> gc(int compressionLevel)
+			throws IOException, ParseException {
+		GC gc = new GC(db);
+		PackConfig pc = new PackConfig(db.getConfig());
+		pc.setCompressionLevel(compressionLevel);
+
+		pc.setSinglePack(true);
+
+		// --aggressive
+		pc.setDeltaSearchWindowSize(
+				GarbageCollectCommand.DEFAULT_GC_AGGRESSIVE_WINDOW);
+		pc.setMaxDeltaDepth(GarbageCollectCommand.DEFAULT_GC_AGGRESSIVE_DEPTH);
+		pc.setReuseObjects(false);
+
+		gc.setPackConfig(pc);
+		gc.setExpireAgeMillis(0);
+		gc.setPackExpireAgeMillis(0);
+		return gc.gc();
+	}
+
+}
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 5a2bd9c..24e3bc0 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
@@ -59,6 +59,7 @@
 
 import java.io.File;
 import java.io.IOException;
+import java.time.Instant;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Map;
@@ -70,6 +71,7 @@
 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;
 import org.eclipse.jgit.lib.AnyObjectId;
 import org.eclipse.jgit.lib.Ref;
@@ -78,6 +80,7 @@
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.revwalk.RevCommit;
 import org.eclipse.jgit.revwalk.RevTag;
+import org.eclipse.jgit.util.FS;
 import org.junit.Before;
 import org.junit.Test;
 
@@ -642,6 +645,7 @@ public void testGetRefs_DiscoversModifiedLoose() throws IOException {
 		assertEquals(B, all.get(HEAD).getObjectId());
 	}
 
+	@Repeat(n = 100, abortOnFailure = false)
 	@Test
 	public void testGetRef_DiscoversModifiedLoose() throws IOException {
 		Map<String, Ref> all;
@@ -1319,10 +1323,8 @@ private void writePackedRef(String name, AnyObjectId id) throws IOException {
 	private void writePackedRefs(String content) throws IOException {
 		File pr = new File(diskRepo.getDirectory(), "packed-refs");
 		write(pr, content);
-
-		final long now = System.currentTimeMillis();
-		final int oneHourAgo = 3600 * 1000;
-		pr.setLastModified(now - oneHourAgo);
+		FS fs = diskRepo.getFS();
+		fs.setLastModified(pr.toPath(), Instant.now().minusSeconds(3600));
 	}
 
 	private void deleteLooseRef(String name) {
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefUpdateTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefUpdateTest.java
index e1adeed..3a43564 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefUpdateTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefUpdateTest.java
@@ -46,7 +46,7 @@
 package org.eclipse.jgit.internal.storage.file;
 
 import static org.eclipse.jgit.junit.Assert.assertEquals;
-import static org.eclipse.jgit.lib.Constants.CHARSET;
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.eclipse.jgit.lib.Constants.LOCK_SUFFIX;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -263,7 +263,7 @@ public void testDeleteHeadInBareRepo() throws IOException {
 
 		ObjectId blobId;
 		try (ObjectInserter ins = bareRepo.newObjectInserter()) {
-			blobId = ins.insert(Constants.OBJ_BLOB, "contents".getBytes(CHARSET));
+			blobId = ins.insert(Constants.OBJ_BLOB, "contents".getBytes(UTF_8));
 			ins.flush();
 		}
 
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/T0003_BasicTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/T0003_BasicTest.java
index e113db1..a450969 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/T0003_BasicTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/T0003_BasicTest.java
@@ -47,7 +47,7 @@
 package org.eclipse.jgit.internal.storage.file;
 
 import static java.nio.charset.StandardCharsets.ISO_8859_1;
-import static org.eclipse.jgit.lib.Constants.CHARSET;
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
@@ -59,6 +59,7 @@
 import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.UnsupportedEncodingException;
+import java.time.Instant;
 
 import org.eclipse.jgit.errors.ConfigInvalidException;
 import org.eclipse.jgit.errors.IncorrectObjectTypeException;
@@ -82,6 +83,7 @@
 import org.eclipse.jgit.storage.file.FileBasedConfig;
 import org.eclipse.jgit.storage.file.FileRepositoryBuilder;
 import org.eclipse.jgit.test.resources.SampleDataRepositoryTestCase;
+import org.eclipse.jgit.util.FS;
 import org.eclipse.jgit.util.FileUtils;
 import org.eclipse.jgit.util.IO;
 import org.junit.Rule;
@@ -368,7 +370,7 @@ public void test006_ReadUglyConfig() throws IOException,
 				+ "  email = A U Thor <thor@example.com> # Just an example...\n"
 				+ " name = \"A  Thor \\\\ \\\"\\t \"\n"
 				+ "    defaultCheckInComment = a many line\\ncomment\\n to test\n";
-		assertEquals(expectedStr, new String(IO.readFully(cfg), Constants.CHARSET));
+		assertEquals(expectedStr, new String(IO.readFully(cfg), UTF_8));
 	}
 
 	@Test
@@ -517,7 +519,7 @@ public void test023_createCommitNonAnullii() throws IOException {
 				4294967295000L, 60));
 		commit.setCommitter(new PersonIdent("Joe Hacker", "joe2@example.com",
 				4294967295000L, 60));
-		commit.setEncoding(CHARSET);
+		commit.setEncoding(UTF_8);
 		commit.setMessage("\u00dcbergeeks");
 		ObjectId cid = insertCommit(commit);
 		assertEquals("4680908112778718f37e686cbebcc912730b3154", cid.name());
@@ -559,7 +561,7 @@ public void test026_CreateCommitMultipleparents() throws IOException {
 		final ObjectId treeId;
 		try (ObjectInserter oi = db.newObjectInserter()) {
 			final ObjectId blobId = oi.insert(Constants.OBJ_BLOB,
-					"and this is the data in me\n".getBytes(Constants.CHARSET
+					"and this is the data in me\n".getBytes(UTF_8
 							.name()));
 			TreeFormatter fmt = new TreeFormatter();
 			fmt.append("i-am-a-file", FileMode.REGULAR_FILE, blobId);
@@ -666,9 +668,9 @@ public void test028_LockPackedRef() throws IOException {
 		ObjectId id2;
 		try (ObjectInserter ins = db.newObjectInserter()) {
 			id1 = ins.insert(
-					Constants.OBJ_BLOB, "contents1".getBytes(Constants.CHARSET));
+					Constants.OBJ_BLOB, "contents1".getBytes(UTF_8));
 			id2 = ins.insert(
-					Constants.OBJ_BLOB, "contents2".getBytes(Constants.CHARSET));
+					Constants.OBJ_BLOB, "contents2".getBytes(UTF_8));
 			ins.flush();
 		}
 
@@ -790,12 +792,14 @@ private RevTag parseTag(AnyObjectId id) throws MissingObjectException,
 	 *
 	 * @param name
 	 *            the file in the repository to force a time change on.
+	 * @throws IOException
 	 */
-	private void BUG_WorkAroundRacyGitIssues(String name) {
+	private void BUG_WorkAroundRacyGitIssues(String name) throws IOException {
 		File path = new File(db.getDirectory(), name);
-		long old = path.lastModified();
+		FS fs = db.getFS();
+		Instant old = fs.lastModifiedInstant(path);
 		long set = 1250379778668L; // Sat Aug 15 20:12:58 GMT-03:30 2009
-		path.setLastModified(set);
-		assertTrue("time changed", old != path.lastModified());
+		fs.setLastModified(path.toPath(), Instant.ofEpochMilli(set));
+		assertFalse("time changed", old.equals(fs.lastModifiedInstant(path)));
 	}
 }
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/WindowCacheGetTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/WindowCacheGetTest.java
index 82ad28e..9063b65 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/WindowCacheGetTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/WindowCacheGetTest.java
@@ -43,6 +43,7 @@
 
 package org.eclipse.jgit.internal.storage.file;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
@@ -77,7 +78,7 @@ public void setUp() throws Exception {
 		try (BufferedReader br = new BufferedReader(new InputStreamReader(
 				new FileInputStream(JGitTestUtil
 						.getTestResourceFile("all_packed_objects.txt")),
-				Constants.CHARSET))) {
+				UTF_8))) {
 			String line;
 			while ((line = br.readLine()) != null) {
 				final String[] parts = line.split(" {1,}");
@@ -140,7 +141,7 @@ private void doCacheTests() throws IOException {
 		}
 	}
 
-	private class TestObject {
+	private static class TestObject {
 		ObjectId id;
 
 		int type;
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/pack/GcCommitSelectionTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/pack/GcCommitSelectionTest.java
index d9b58e2..ffb6f4e 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/pack/GcCommitSelectionTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/pack/GcCommitSelectionTest.java
@@ -50,6 +50,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
 
@@ -243,7 +244,8 @@ public void testSelectionOrderingWithChains() throws Exception {
 
 		List<RevCommit> commits = Arrays.asList(m0, m1, m2, b3, m4, b5, m6, b7,
 				m8, m9);
-		PackWriterBitmapPreparer preparer = newPeparer(m9, commits);
+		PackWriterBitmapPreparer preparer = newPreparer(
+				Collections.singleton(m9), commits, new PackConfig());
 		List<BitmapCommit> selection = new ArrayList<>(
 				preparer.selectCommits(commits.size(), PackWriter.NONE));
 
@@ -267,15 +269,107 @@ private RevCommit addCommit(BranchBuilder bb, String msg,
 		return commit.create();
 	}
 
-	private PackWriterBitmapPreparer newPeparer(RevCommit want,
-			List<RevCommit> commits)
-			throws IOException {
+	@Test
+	public void testDistributionOnMultipleBranches() throws Exception {
+		BranchBuilder[] branches = { tr.branch("refs/heads/main"),
+				tr.branch("refs/heads/a"), tr.branch("refs/heads/b"),
+				tr.branch("refs/heads/c") };
+		RevCommit[] tips = new RevCommit[branches.length];
+		List<RevCommit> commits = createHistory(branches, tips);
+		PackConfig config = new PackConfig();
+		config.setBitmapContiguousCommitCount(1);
+		config.setBitmapRecentCommitSpan(5);
+		config.setBitmapDistantCommitSpan(20);
+		config.setBitmapRecentCommitCount(100);
+		Set<RevCommit> wants = new HashSet<>(Arrays.asList(tips));
+		PackWriterBitmapPreparer preparer = newPreparer(wants, commits, config);
+		List<BitmapCommit> selection = new ArrayList<>(
+				preparer.selectCommits(commits.size(), PackWriter.NONE));
+		Set<ObjectId> selected = new HashSet<>();
+		for (BitmapCommit c : selection) {
+			selected.add(c.toObjectId());
+		}
+
+		// Verify that each branch has uniform bitmap selection coverage
+		for (RevCommit c : wants) {
+			assertTrue(selected.contains(c.toObjectId()));
+			int count = 1;
+			int selectedCount = 1;
+			RevCommit parent = c;
+			while (parent.getParentCount() != 0) {
+				parent = parent.getParent(0);
+				count++;
+				if (selected.contains(parent.toObjectId())) {
+					selectedCount++;
+				}
+			}
+			// The selection algorithm prefers merges and will look in the
+			// current range plus the recent commit span before selecting a
+			// commit. Since this history has no merges, we expect the recent
+			// span should have 100/10=10 and distant commit spans should have
+			// 100/25=4 per 100 commit range.
+			int expectedCount = 10 + (count - 100 - 24) / 25;
+			assertTrue(expectedCount <= selectedCount);
+		}
+	}
+
+	private List<RevCommit> createHistory(BranchBuilder[] branches,
+			RevCommit[] tips) throws Exception {
+		/*-
+		 * Create a history like this, where branches a, b and c branch off of the main branch
+		 * at commits 100, 200 and 300, and where commit times move forward alternating between
+		 * branches.
+		 *
+		 * o...o...o...o...o      commits root,m0,m1,...,m399
+		 *      \   \   \
+		 *       \   \   o...     commits branch_c,c300,c301,...,c399
+		 *        \   \
+		 *         \   o...o...   commits branch_b,b200,b201,...,b399
+		 *          \
+		 *           o...o...o... commits branch_a,b100,b101,...,a399
+		 */
+		List<RevCommit> commits = new ArrayList<>();
+		String[] prefixes = { "m", "a", "b", "c" };
+		int branchCount = branches.length;
+		tips[0] = addCommit(commits, branches[0], "root");
+		int counter = 0;
+
+		for (int b = 0; b < branchCount; b++) {
+			for (int i = 0; i < 100; i++, counter++) {
+				for (int j = 0; j <= b; j++) {
+					tips[j] = addCommit(commits, branches[j],
+							prefixes[j] + counter);
+				}
+			}
+			// Create a new branch from current value of the master branch
+			if (b < branchCount - 1) {
+				tips[b + 1] = addCommit(branches[b + 1],
+						"branch_" + prefixes[b + 1], tips[0]);
+			}
+		}
+		return commits;
+	}
+
+	private RevCommit addCommit(List<RevCommit> commits, BranchBuilder bb,
+			String msg, RevCommit... parents) throws Exception {
+		CommitBuilder commit = bb.commit().message(msg).add(msg, msg).tick(1);
+		if (parents.length > 0) {
+			commit.noParents();
+			for (RevCommit parent : parents) {
+				commit.parent(parent);
+			}
+		}
+		RevCommit c = commit.create();
+		commits.add(c);
+		return c;
+	}
+
+	private PackWriterBitmapPreparer newPreparer(Set<RevCommit> wants,
+			List<RevCommit> commits, PackConfig config) throws IOException {
 		List<ObjectToPack> objects = new ArrayList<>(commits.size());
 		for (RevCommit commit : commits) {
 			objects.add(new ObjectToPack(commit, Constants.OBJ_COMMIT));
 		}
-		Set<ObjectId> wants = Collections.singleton((ObjectId) want);
-		PackConfig config = new PackConfig();
 		PackBitmapIndexBuilder builder = new PackBitmapIndexBuilder(objects);
 		return new PackWriterBitmapPreparer(
 				tr.getRepository().newObjectReader(), builder,
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftable/MergedReftableTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftable/MergedReftableTest.java
index ec60bd9..1d11573 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftable/MergedReftableTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftable/MergedReftableTest.java
@@ -80,7 +80,7 @@ public void noTables() throws IOException {
 		try (RefCursor rc = mr.seekRef(HEAD)) {
 			assertFalse(rc.next());
 		}
-		try (RefCursor rc = mr.seekRef(R_HEADS)) {
+		try (RefCursor rc = mr.seekRefsWithPrefix(R_HEADS)) {
 			assertFalse(rc.next());
 		}
 	}
@@ -94,7 +94,7 @@ public void oneEmptyTable() throws IOException {
 		try (RefCursor rc = mr.seekRef(HEAD)) {
 			assertFalse(rc.next());
 		}
-		try (RefCursor rc = mr.seekRef(R_HEADS)) {
+		try (RefCursor rc = mr.seekRefsWithPrefix(R_HEADS)) {
 			assertFalse(rc.next());
 		}
 	}
@@ -108,7 +108,7 @@ public void twoEmptyTables() throws IOException {
 		try (RefCursor rc = mr.seekRef(HEAD)) {
 			assertFalse(rc.next());
 		}
-		try (RefCursor rc = mr.seekRef(R_HEADS)) {
+		try (RefCursor rc = mr.seekRefsWithPrefix(R_HEADS)) {
 			assertFalse(rc.next());
 		}
 	}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftable/ReftableTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftable/ReftableTest.java
index 3ea3061..0ee785c 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftable/ReftableTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftable/ReftableTest.java
@@ -101,7 +101,7 @@ public void emptyTable() throws IOException {
 		try (RefCursor rc = t.seekRef(HEAD)) {
 			assertFalse(rc.next());
 		}
-		try (RefCursor rc = t.seekRef(R_HEADS)) {
+		try (RefCursor rc = t.seekRefsWithPrefix(R_HEADS)) {
 			assertFalse(rc.next());
 		}
 		try (LogCursor rc = t.allLogs()) {
@@ -317,10 +317,10 @@ public void seekNotFound() throws IOException {
 	public void namespaceNotFound() throws IOException {
 		Ref exp = ref(MASTER, 1);
 		ReftableReader t = read(write(exp));
-		try (RefCursor rc = t.seekRef("refs/changes/")) {
+		try (RefCursor rc = t.seekRefsWithPrefix("refs/changes/")) {
 			assertFalse(rc.next());
 		}
-		try (RefCursor rc = t.seekRef("refs/tags/")) {
+		try (RefCursor rc = t.seekRefsWithPrefix("refs/tags/")) {
 			assertFalse(rc.next());
 		}
 	}
@@ -332,12 +332,12 @@ public void namespaceHeads() throws IOException {
 		Ref v1 = tag(V1_0, 3, 4);
 
 		ReftableReader t = read(write(master, next, v1));
-		try (RefCursor rc = t.seekRef("refs/tags/")) {
+		try (RefCursor rc = t.seekRefsWithPrefix("refs/tags/")) {
 			assertTrue(rc.next());
 			assertEquals(V1_0, rc.getRef().getName());
 			assertFalse(rc.next());
 		}
-		try (RefCursor rc = t.seekRef("refs/heads/")) {
+		try (RefCursor rc = t.seekRefsWithPrefix("refs/heads/")) {
 			assertTrue(rc.next());
 			assertEquals(MASTER, rc.getRef().getName());
 
@@ -484,7 +484,7 @@ public void onlyReflog() throws IOException {
 		try (RefCursor rc = t.allRefs()) {
 			assertFalse(rc.next());
 		}
-		try (RefCursor rc = t.seekRef("refs/heads/")) {
+		try (RefCursor rc = t.seekRefsWithPrefix("refs/heads/")) {
 			assertFalse(rc.next());
 		}
 		try (LogCursor lc = t.allLogs()) {
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftree/LocalDiskRefTreeDatabaseTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftree/LocalDiskRefTreeDatabaseTest.java
index d5a07e0..cafec04 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftree/LocalDiskRefTreeDatabaseTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftree/LocalDiskRefTreeDatabaseTest.java
@@ -80,6 +80,7 @@ public class LocalDiskRefTreeDatabaseTest extends LocalDiskRepositoryTestCase {
 	@Override
 	@Before
 	public void setUp() throws Exception {
+		super.setUp();
 		FileRepository init = createWorkRepository();
 		FileBasedConfig cfg = init.getConfig();
 		cfg.setInt("core", null, "repositoryformatversion", 1);
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/junit/TestRepositoryTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/junit/TestRepositoryTest.java
index 965899e..b7027f3 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/junit/TestRepositoryTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/junit/TestRepositoryTest.java
@@ -43,7 +43,7 @@
 
 package org.eclipse.jgit.junit;
 
-import static org.eclipse.jgit.lib.Constants.CHARSET;
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
@@ -418,6 +418,6 @@ private String blobAsString(AnyObjectId treeish, String path)
 		RevObject obj = tr.get(rw.parseTree(treeish), path);
 		assertSame(RevBlob.class, obj.getClass());
 		ObjectLoader loader = rw.getObjectReader().open(obj);
-		return new String(loader.getCachedBytes(), CHARSET);
+		return new String(loader.getCachedBytes(), UTF_8);
 	}
 }
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ConfigTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ConfigTest.java
index c4c4da8..2d0fe86 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
@@ -52,6 +52,8 @@
 import static java.util.concurrent.TimeUnit.HOURS;
 import static java.util.concurrent.TimeUnit.MILLISECONDS;
 import static java.util.concurrent.TimeUnit.MINUTES;
+import static java.util.concurrent.TimeUnit.NANOSECONDS;
+import static java.util.concurrent.TimeUnit.MICROSECONDS;
 import static java.util.concurrent.TimeUnit.SECONDS;
 import static org.eclipse.jgit.util.FileUtils.pathToString;
 import static org.junit.Assert.assertArrayEquals;
@@ -928,8 +930,18 @@ private static Config parse(String content, Config baseConfig)
 
 	@Test
 	public void testTimeUnit() throws ConfigInvalidException {
+		assertEquals(0, parseTime("0", NANOSECONDS));
+		assertEquals(2, parseTime("2ns", NANOSECONDS));
+		assertEquals(200, parseTime("200 nanoseconds", NANOSECONDS));
+
+		assertEquals(0, parseTime("0", MICROSECONDS));
+		assertEquals(2, parseTime("2us", MICROSECONDS));
+		assertEquals(2, parseTime("2000 nanoseconds", MICROSECONDS));
+		assertEquals(200, parseTime("200 microseconds", MICROSECONDS));
+
 		assertEquals(0, parseTime("0", MILLISECONDS));
 		assertEquals(2, parseTime("2ms", MILLISECONDS));
+		assertEquals(2, parseTime("2000microseconds", MILLISECONDS));
 		assertEquals(200, parseTime("200 milliseconds", MILLISECONDS));
 
 		assertEquals(0, parseTime("0s", SECONDS));
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/HugeCommitMessageTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/HugeCommitMessageTest.java
new file mode 100644
index 0000000..4193c4b
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/HugeCommitMessageTest.java
@@ -0,0 +1,95 @@
+/*
+ * 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.lib;
+
+import static org.junit.Assert.assertTrue;
+
+import java.util.List;
+
+import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.junit.RepositoryTestCase;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.storage.file.WindowCacheConfig;
+import org.eclipse.jgit.storage.pack.PackConfig;
+import org.junit.Test;
+
+public class HugeCommitMessageTest extends RepositoryTestCase {
+
+	private static final int HUGE_SIZE = Math.max(15 * WindowCacheConfig.MB,
+			PackConfig.DEFAULT_BIG_FILE_THRESHOLD + WindowCacheConfig.MB);
+	// Larger than the 5MB fallback limit in RevWalk.getCachedBytes(RevObject
+	// obj, ObjectLoader ldr), and also larger than the default
+	// streamFileThreshold.
+
+	@Test
+	public void testHugeCommitMessage() throws Exception {
+		try (Git git = new Git(db)) {
+			writeTrashFile("foo", "foo");
+			git.add().addFilepattern("foo").call();
+			WindowCacheConfig wc = new WindowCacheConfig();
+			wc.setStreamFileThreshold(HUGE_SIZE + WindowCacheConfig.MB);
+			wc.install();
+			RevCommit commit = git.commit()
+					.setMessage(insanelyHugeCommitMessage()).call();
+			Ref master = db.findRef("master");
+			List<Ref> actual = git.branchList().setContains(commit.getName())
+					.call();
+			assertTrue("Should be contained in branch master",
+					actual.contains(master));
+		}
+	}
+
+	private String insanelyHugeCommitMessage() {
+		final String oneLine = "012345678901234567890123456789012345678901234567890123456789\n";
+		StringBuilder b = new StringBuilder(HUGE_SIZE + oneLine.length());
+		// Give the message a real header; otherwise even writing the reflog
+		// message may run into troubles because RevCommit.getShortMessage()
+		// will return the whole message.
+		b.append("An insanely huge commit message\n\n");
+		while (b.length() < HUGE_SIZE) {
+			b.append(oneLine);
+		}
+		return b.toString();
+	}
+
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/IndexModificationTimesTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/IndexModificationTimesTest.java
index b9bbbeb..a363414 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/IndexModificationTimesTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/IndexModificationTimesTest.java
@@ -37,8 +37,12 @@
  */
 package org.eclipse.jgit.lib;
 
+import static java.time.Instant.EPOCH;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
+import java.time.Instant;
+
 import org.eclipse.jgit.api.Git;
 import org.eclipse.jgit.dircache.DirCache;
 import org.eclipse.jgit.dircache.DirCacheEntry;
@@ -63,11 +67,11 @@ public void testLastModifiedTimes() throws Exception {
 			DirCacheEntry entry = dc.getEntry(path);
 			DirCacheEntry entry2 = dc.getEntry(path);
 
-			assertTrue("last modified shall not be zero!",
-					entry.getLastModified() != 0);
+			assertFalse("last modified shall not be the epoch!",
+					entry.getLastModifiedInstant().equals(EPOCH));
 
-			assertTrue("last modified shall not be zero!",
-					entry2.getLastModified() != 0);
+			assertFalse("last modified shall not be the epoch!",
+					entry2.getLastModifiedInstant().equals(EPOCH));
 
 			writeTrashFile(path, "new content");
 			git.add().addFilepattern(path).call();
@@ -77,11 +81,11 @@ public void testLastModifiedTimes() throws Exception {
 			entry = dc.getEntry(path);
 			entry2 = dc.getEntry(path);
 
-			assertTrue("last modified shall not be zero!",
-					entry.getLastModified() != 0);
+			assertFalse("last modified shall not be the epoch!",
+					entry.getLastModifiedInstant().equals(EPOCH));
 
-			assertTrue("last modified shall not be zero!",
-					entry2.getLastModified() != 0);
+			assertFalse("last modified shall not be the epoch!",
+					entry2.getLastModifiedInstant().equals(EPOCH));
 		}
 	}
 
@@ -97,7 +101,7 @@ public void testModify() throws Exception {
 			DirCache dc = db.readDirCache();
 			DirCacheEntry entry = dc.getEntry(path);
 
-			long masterLastMod = entry.getLastModified();
+			Instant masterLastMod = entry.getLastModifiedInstant();
 
 			git.checkout().setCreateBranch(true).setName("side").call();
 
@@ -110,7 +114,7 @@ public void testModify() throws Exception {
 			dc = db.readDirCache();
 			entry = dc.getEntry(path);
 
-			long sideLastMode = entry.getLastModified();
+			Instant sideLastMod = entry.getLastModifiedInstant();
 
 			Thread.sleep(2000);
 
@@ -120,9 +124,10 @@ public void testModify() throws Exception {
 			dc = db.readDirCache();
 			entry = dc.getEntry(path);
 
-			assertTrue("shall have equal mod time!", masterLastMod == sideLastMode);
-			assertTrue("shall not equal master timestamp!",
-					entry.getLastModified() == masterLastMod);
+			assertTrue("shall have equal mod time!",
+					masterLastMod.equals(sideLastMod));
+			assertTrue("shall have equal master timestamp!",
+					entry.getLastModifiedInstant().equals(masterLastMod));
 		}
 	}
 
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/MergeHeadMsgTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/MergeHeadMsgTest.java
index 347883f..abf7d56 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/MergeHeadMsgTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/MergeHeadMsgTest.java
@@ -42,6 +42,7 @@
  */
 package org.eclipse.jgit.lib;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 
@@ -72,7 +73,9 @@ public void testReadWriteMergeHeads() throws IOException {
 		// same test again, this time with lower-level io
 		try (FileOutputStream fos = new FileOutputStream(
 				new File(db.getDirectory(), "MERGE_HEAD"));) {
-			fos.write("0000000000000000000000000000000000000000\n1c6db447abdbb291b25f07be38ea0b1bf94947c5\n".getBytes(Constants.CHARACTER_ENCODING));
+			fos.write(
+					"0000000000000000000000000000000000000000\n1c6db447abdbb291b25f07be38ea0b1bf94947c5\n"
+							.getBytes(UTF_8));
 		}
 		assertEquals(db.readMergeHeads().size(), 2);
 		assertEquals(db.readMergeHeads().get(0), ObjectId.zeroId());
@@ -82,7 +85,7 @@ public void testReadWriteMergeHeads() throws IOException {
 		assertEquals(db.readMergeHeads(), null);
 		try (FileOutputStream fos = new FileOutputStream(
 				new File(db.getDirectory(), "MERGE_HEAD"))) {
-			fos.write(sampleId.getBytes(Constants.CHARACTER_ENCODING));
+			fos.write(sampleId.getBytes(UTF_8));
 		}
 		assertEquals(db.readMergeHeads().size(), 1);
 		assertEquals(db.readMergeHeads().get(0), ObjectId.fromString(sampleId));
@@ -100,7 +103,7 @@ public void testReadWriteMergeMsg() throws IOException {
 		assertFalse(new File(db.getDirectory(), "MERGE_MSG").exists());
 		try (FileOutputStream fos = new FileOutputStream(
 				new File(db.getDirectory(), Constants.MERGE_MSG))) {
-			fos.write(mergeMsg.getBytes(Constants.CHARACTER_ENCODING));
+			fos.write(mergeMsg.getBytes(UTF_8));
 		}
 		assertEquals(db.readMergeCommitMsg(), mergeMsg);
 	}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ObjectCheckerTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ObjectCheckerTest.java
index 1ab36f0..531c918 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
@@ -45,7 +45,7 @@
 package org.eclipse.jgit.lib;
 
 import static java.lang.Integer.valueOf;
-import static org.eclipse.jgit.lib.Constants.CHARSET;
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.eclipse.jgit.junit.JGitTestUtil.concat;
 import static org.eclipse.jgit.lib.Constants.OBJECT_ID_LENGTH;
 import static org.eclipse.jgit.lib.Constants.OBJ_BAD;
@@ -1560,7 +1560,7 @@ public void testInvalidTreeDuplicateNames5()
 		StringBuilder b = new StringBuilder();
 		entry(b, "100644 A");
 		entry(b, "100644 a");
-		byte[] data = b.toString().getBytes(CHARSET);
+		byte[] data = b.toString().getBytes(UTF_8);
 		checker.setSafeForWindows(true);
 		assertCorrupt("duplicate entry names", OBJ_TREE, data);
 		assertSkipListAccepts(OBJ_TREE, data);
@@ -1574,7 +1574,7 @@ public void testInvalidTreeDuplicateNames6()
 		StringBuilder b = new StringBuilder();
 		entry(b, "100644 A");
 		entry(b, "100644 a");
-		byte[] data = b.toString().getBytes(CHARSET);
+		byte[] data = b.toString().getBytes(UTF_8);
 		checker.setSafeForMacOS(true);
 		assertCorrupt("duplicate entry names", OBJ_TREE, data);
 		assertSkipListAccepts(OBJ_TREE, data);
@@ -1588,7 +1588,7 @@ public void testInvalidTreeDuplicateNames7()
 		StringBuilder b = new StringBuilder();
 		entry(b, "100644 \u0065\u0301");
 		entry(b, "100644 \u00e9");
-		byte[] data = b.toString().getBytes(CHARSET);
+		byte[] data = b.toString().getBytes(UTF_8);
 		checker.setSafeForMacOS(true);
 		assertCorrupt("duplicate entry names", OBJ_TREE, data);
 		assertSkipListAccepts(OBJ_TREE, data);
@@ -1602,7 +1602,7 @@ public void testInvalidTreeDuplicateNames8()
 		StringBuilder b = new StringBuilder();
 		entry(b, "100644 A");
 		checker.setSafeForMacOS(true);
-		checker.checkTree(b.toString().getBytes(CHARSET));
+		checker.checkTree(b.toString().getBytes(UTF_8));
 	}
 
 	@Test
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RacyGitTests.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RacyGitTests.java
index 6bc8ff7..d3e4efe 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RacyGitTests.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RacyGitTests.java
@@ -42,6 +42,7 @@
  */
 package org.eclipse.jgit.lib;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
@@ -49,12 +50,15 @@
 import java.io.File;
 import java.io.FileOutputStream;
 import java.io.IOException;
+import java.time.Instant;
 
 import org.eclipse.jgit.api.Git;
 import org.eclipse.jgit.dircache.DirCache;
 import org.eclipse.jgit.junit.RepositoryTestCase;
+import org.eclipse.jgit.junit.time.TimeUtil;
 import org.eclipse.jgit.treewalk.FileTreeIterator;
 import org.eclipse.jgit.treewalk.WorkingTreeOptions;
+import org.eclipse.jgit.util.FS;
 import org.junit.Test;
 
 public class RacyGitTests extends RepositoryTestCase {
@@ -73,8 +77,8 @@ public void testRacyGitDetection() throws Exception {
 		// create two files
 		File a = writeToWorkDir("a", "a");
 		File b = writeToWorkDir("b", "b");
-		assertTrue(a.setLastModified(b.lastModified()));
-		assertTrue(b.setLastModified(b.lastModified()));
+		TimeUtil.setLastModifiedOf(a.toPath(), b.toPath());
+		TimeUtil.setLastModifiedOf(b.toPath(), b.toPath());
 
 		// wait to ensure that file-modTimes and therefore index entry modTime
 		// doesn't match the modtime of index-file after next persistance
@@ -96,10 +100,11 @@ public void testRacyGitDetection() throws Exception {
 		// filesystem timestamp resolution. By changing the index file
 		// artificially, we create a fake racy situation.
 		File updatedA = writeToWorkDir("a", "a2");
-		long newLastModified = updatedA.lastModified() + 100;
-		assertTrue(updatedA.setLastModified(newLastModified));
+		Instant newLastModified = TimeUtil
+				.setLastModifiedWithOffset(updatedA.toPath(), 100L);
 		resetIndex(new FileTreeIterator(db));
-		assertTrue(db.getIndexFile().setLastModified(newLastModified));
+		FS.DETECTED.setLastModified(db.getIndexFile().toPath(),
+				newLastModified);
 
 		DirCache dc = db.readDirCache();
 		// check index state: although racily clean a should not be reported as
@@ -123,7 +128,7 @@ public void testRacyGitDetection() throws Exception {
 	private File writeToWorkDir(String path, String content) throws IOException {
 		File f = new File(db.getWorkTree(), path);
 		try (FileOutputStream fos = new FileOutputStream(f)) {
-			fos.write(content.getBytes(Constants.CHARACTER_ENCODING));
+			fos.write(content.getBytes(UTF_8));
 			return f;
 		}
 	}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RefTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RefTest.java
index 2481e64..a42027b 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,7 +58,7 @@
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.util.List;
-import java.util.Map;
+import java.util.Optional;
 import java.util.TreeSet;
 
 import org.eclipse.jgit.lib.Ref.Storage;
@@ -148,17 +148,22 @@ public void testReadAllIncludingSymrefs() throws Exception {
 		ObjectId r = db.resolve("refs/remotes/origin/HEAD");
 		assertEquals(masterId, r);
 
-		Map<String, Ref> allRefs = db.getAllRefs();
-		Ref refHEAD = allRefs.get("refs/remotes/origin/HEAD");
-		assertNotNull(refHEAD);
-		assertEquals(masterId, refHEAD.getObjectId());
-		assertFalse(refHEAD.isPeeled());
-		assertNull(refHEAD.getPeeledObjectId());
+		List<Ref> allRefs = db.getRefDatabase().getRefs();
+		Optional<Ref> refHEAD = allRefs.stream()
+				.filter(ref -> ref.getName().equals("refs/remotes/origin/HEAD"))
+				.findAny();
+		assertTrue(refHEAD.isPresent());
+		assertEquals(masterId, refHEAD.get().getObjectId());
+		assertFalse(refHEAD.get().isPeeled());
+		assertNull(refHEAD.get().getPeeledObjectId());
 
-		Ref refmaster = allRefs.get("refs/remotes/origin/master");
-		assertEquals(masterId, refmaster.getObjectId());
-		assertFalse(refmaster.isPeeled());
-		assertNull(refmaster.getPeeledObjectId());
+		Optional<Ref> refmaster = allRefs.stream().filter(
+				ref -> ref.getName().equals("refs/remotes/origin/master"))
+				.findAny();
+		assertTrue(refmaster.isPresent());
+		assertEquals(masterId, refmaster.get().getObjectId());
+		assertFalse(refmaster.get().isPeeled());
+		assertNull(refmaster.get().getPeeledObjectId());
 	}
 
 	@Test
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/SquashCommitMsgTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/SquashCommitMsgTest.java
index 203c00e..f58ab06 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/SquashCommitMsgTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/SquashCommitMsgTest.java
@@ -42,6 +42,7 @@
  */
 package org.eclipse.jgit.lib;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 
@@ -68,7 +69,7 @@ public void testReadWriteMergeMsg() throws IOException {
 		assertFalse(new File(db.getDirectory(), Constants.SQUASH_MSG).exists());
 		try (FileOutputStream fos = new FileOutputStream(
 				new File(db.getDirectory(), Constants.SQUASH_MSG))) {
-			fos.write(squashMsg.getBytes(Constants.CHARACTER_ENCODING));
+			fos.write(squashMsg.getBytes(UTF_8));
 		}
 		assertEquals(db.readSquashCommitMsg(), squashMsg);
 	}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/CrissCrossMergeTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/CrissCrossMergeTest.java
index aaa08a9..a67c750 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/CrissCrossMergeTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/CrissCrossMergeTest.java
@@ -42,6 +42,8 @@
  */
 package org.eclipse.jgit.merge;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
@@ -881,7 +883,7 @@ private String contentAsString(Repository r, ObjectId treeId, String path)
 		StringBuilder result = new StringBuilder();
 		ObjectReader or = r.newObjectReader();
 		try (BufferedReader br = new BufferedReader(
-				new InputStreamReader(or.open(blobId).openStream()))) {
+				new InputStreamReader(or.open(blobId).openStream(), UTF_8))) {
 			String line;
 			boolean first = true;
 			while ((line = br.readLine()) != null) {
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/MergeAlgorithmTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/MergeAlgorithmTest.java
index 5af62b6..61ab042 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/MergeAlgorithmTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/MergeAlgorithmTest.java
@@ -43,6 +43,7 @@
 
 package org.eclipse.jgit.merge;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.junit.Assert.assertEquals;
 
 import java.io.ByteArrayOutputStream;
@@ -301,8 +302,8 @@ private String merge(String commonBase, String ours, String theirs) throws IOExc
 		MergeResult r = new MergeAlgorithm().merge(RawTextComparator.DEFAULT,
 				T(commonBase), T(ours), T(theirs));
 		ByteArrayOutputStream bo=new ByteArrayOutputStream(50);
-		fmt.formatMerge(bo, r, "B", "O", "T", Constants.CHARACTER_ENCODING);
-		return new String(bo.toByteArray(), Constants.CHARACTER_ENCODING);
+		fmt.formatMerge(bo, r, "B", "O", "T", UTF_8.name());
+		return new String(bo.toByteArray(), UTF_8);
 	}
 
 	public String t(String text) {
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/MergerTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/MergerTest.java
index 58093a3..f22b7d6 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
@@ -42,7 +42,8 @@
  */
 package org.eclipse.jgit.merge;
 
-import static org.eclipse.jgit.lib.Constants.CHARSET;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static java.time.Instant.EPOCH;
 import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -54,12 +55,14 @@
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.IOException;
+import java.time.Instant;
 import java.util.Arrays;
 import java.util.Map;
 
 import org.eclipse.jgit.api.Git;
 import org.eclipse.jgit.api.MergeResult;
 import org.eclipse.jgit.api.MergeResult.MergeStatus;
+import org.eclipse.jgit.api.RebaseResult;
 import org.eclipse.jgit.api.errors.CheckoutConflictException;
 import org.eclipse.jgit.api.errors.GitAPIException;
 import org.eclipse.jgit.api.errors.JGitInternalException;
@@ -428,6 +431,44 @@ public void mergeWithCrlfAutoCrlfTrue(MergeStrategy strategy)
 				indexState(CONTENT));
 	}
 
+	@Theory
+	public void rebaseWithCrlfAutoCrlfTrue(MergeStrategy strategy)
+			throws IOException, GitAPIException {
+		Git git = Git.wrap(db);
+		db.getConfig().setString("core", null, "autocrlf", "true");
+		db.getConfig().save();
+		writeTrashFile("crlf.txt", "line 1\r\nline 2\r\nline 3\r\n");
+		git.add().addFilepattern("crlf.txt").call();
+		RevCommit first = git.commit().setMessage("base").call();
+
+		git.checkout().setCreateBranch(true).setStartPoint(first)
+				.setName("brancha").call();
+
+		File testFile = writeTrashFile("crlf.txt",
+				"line 1\r\nmodified line\r\nline 3\r\n");
+		git.add().addFilepattern("crlf.txt").call();
+		git.commit().setMessage("on brancha").call();
+
+		git.checkout().setName("master").call();
+		File otherFile = writeTrashFile("otherfile.txt", "a line\r\n");
+		git.add().addFilepattern("otherfile.txt").call();
+		git.commit().setMessage("on master").call();
+
+		git.checkout().setName("brancha").call();
+		checkFile(testFile, "line 1\r\nmodified line\r\nline 3\r\n");
+		assertFalse(otherFile.exists());
+
+		RebaseResult rebaseResult = git.rebase().setStrategy(strategy)
+				.setUpstream(db.resolve("master")).call();
+		assertEquals(RebaseResult.Status.OK, rebaseResult.getStatus());
+		checkFile(testFile, "line 1\r\nmodified line\r\nline 3\r\n");
+		checkFile(otherFile, "a line\r\n");
+		assertEquals(
+				"[crlf.txt, mode:100644, content:line 1\nmodified line\nline 3\n]"
+						+ "[otherfile.txt, mode:100644, content:a line\n]",
+				indexState(CONTENT));
+	}
+
 	/**
 	 * Merging two equal subtrees when the index does not contain any file in
 	 * that subtree should lead to a merged state.
@@ -754,7 +795,7 @@ public void checkContentMergeLargeBinaries(MergeStrategy strategy) throws Except
 		}
 		binary[50] = '\0';
 
-		writeTrashFile("file", new String(binary, CHARSET));
+		writeTrashFile("file", new String(binary, UTF_8));
 		git.add().addFilepattern("file").call();
 		RevCommit first = git.commit().setMessage("added file").call();
 
@@ -762,7 +803,7 @@ public void checkContentMergeLargeBinaries(MergeStrategy strategy) throws Except
 		int idx = LINELEN * 1200 + 1;
 		byte save = binary[idx];
 		binary[idx] = '@';
-		writeTrashFile("file", new String(binary, CHARSET));
+		writeTrashFile("file", new String(binary, UTF_8));
 
 		binary[idx] = save;
 		git.add().addFilepattern("file").call();
@@ -771,7 +812,7 @@ public void checkContentMergeLargeBinaries(MergeStrategy strategy) throws Except
 
 		git.checkout().setCreateBranch(true).setStartPoint(first).setName("side").call();
 		binary[LINELEN * 1500 + 1] = '!';
-		writeTrashFile("file", new String(binary, CHARSET));
+		writeTrashFile("file", new String(binary, UTF_8));
 		git.add().addFilepattern("file").call();
 		RevCommit sideCommit = git.commit().setAll(true)
 			.setMessage("modified file l 1500").call();
@@ -800,7 +841,7 @@ public ObjectReader newReader() {
 	/**
 	 * Throws an exception if reading beyond limit.
 	 */
-	class BigReadForbiddenStream extends ObjectStream.Filter {
+	static class BigReadForbiddenStream extends ObjectStream.Filter {
 		int limit;
 
 		BigReadForbiddenStream(ObjectStream orig, int limit) {
@@ -839,7 +880,7 @@ public int read(byte[] b, int off, int len) throws IOException {
 		}
 	}
 
-	class BigReadForbiddenReader extends ObjectReader.Filter {
+	static class BigReadForbiddenReader extends ObjectReader.Filter {
 		ObjectReader delegate;
 		int limit;
 
@@ -933,7 +974,7 @@ public void checkContentMergeConflict_noTree(MergeStrategy strategy)
 			merger.getMergeResults().get("file");
 			try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
 				fmt.formatMerge(out, merger.getMergeResults().get("file"),
-						"BASE", "OURS", "THEIRS", CHARSET.name());
+						"BASE", "OURS", "THEIRS", UTF_8.name());
 				String expected = "<<<<<<< OURS\n"
 						+ "1master\n"
 						+ "=======\n"
@@ -941,7 +982,7 @@ public void checkContentMergeConflict_noTree(MergeStrategy strategy)
 						+ ">>>>>>> THEIRS\n"
 						+ "2\n"
 						+ "3";
-				assertEquals(expected, new String(out.toByteArray(), CHARSET));
+				assertEquals(expected, new String(out.toByteArray(), UTF_8));
 			}
 		}
 	}
@@ -1051,13 +1092,13 @@ public void checkLockedFilesToBeDeleted(MergeStrategy strategy)
 	@Theory
 	public void checkForCorrectIndex(MergeStrategy strategy) throws Exception {
 		File f;
-		long lastTs4, lastTsIndex;
+		Instant lastTs4, lastTsIndex;
 		Git git = Git.wrap(db);
 		File indexFile = db.getIndexFile();
 
 		// Create initial content and remember when the last file was written.
 		f = writeTrashFiles(false, "orig", "orig", "1\n2\n3", "orig", "orig");
-		lastTs4 = FS.DETECTED.lastModified(f);
+		lastTs4 = FS.DETECTED.lastModifiedInstant(f);
 
 		// add all files, commit and check this doesn't update any working tree
 		// files and that the index is in a new file system timer tick. Make
@@ -1070,8 +1111,9 @@ public void checkForCorrectIndex(MergeStrategy strategy) throws Exception {
 		checkConsistentLastModified("0", "1", "2", "3", "4");
 		checkModificationTimeStampOrder("1", "2", "3", "4", "<.git/index");
 		assertEquals("Commit should not touch working tree file 4", lastTs4,
-				FS.DETECTED.lastModified(new File(db.getWorkTree(), "4")));
-		lastTsIndex = FS.DETECTED.lastModified(indexFile);
+				FS.DETECTED
+						.lastModifiedInstant(new File(db.getWorkTree(), "4")));
+		lastTsIndex = FS.DETECTED.lastModifiedInstant(indexFile);
 
 		// Do modifications on the master branch. Then add and commit. This
 		// should touch only "0", "2 and "3"
@@ -1085,7 +1127,7 @@ public void checkForCorrectIndex(MergeStrategy strategy) throws Exception {
 		checkConsistentLastModified("0", "1", "2", "3", "4");
 		checkModificationTimeStampOrder("1", "4", "*" + lastTs4, "<*"
 				+ lastTsIndex, "<0", "2", "3", "<.git/index");
-		lastTsIndex = FS.DETECTED.lastModified(indexFile);
+		lastTsIndex = FS.DETECTED.lastModifiedInstant(indexFile);
 
 		// Checkout a side branch. This should touch only "0", "2 and "3"
 		fsTick(indexFile);
@@ -1094,7 +1136,7 @@ public void checkForCorrectIndex(MergeStrategy strategy) throws Exception {
 		checkConsistentLastModified("0", "1", "2", "3", "4");
 		checkModificationTimeStampOrder("1", "4", "*" + lastTs4, "<*"
 				+ lastTsIndex, "<0", "2", "3", ".git/index");
-		lastTsIndex = FS.DETECTED.lastModified(indexFile);
+		lastTsIndex = FS.DETECTED.lastModifiedInstant(indexFile);
 
 		// This checkout may have populated worktree and index so fast that we
 		// may have smudged entries now. Check that we have the right content
@@ -1107,13 +1149,13 @@ public void checkForCorrectIndex(MergeStrategy strategy) throws Exception {
 				indexState(CONTENT));
 		fsTick(indexFile);
 		f = writeTrashFiles(false, "orig", "orig", "1\n2\n3", "orig", "orig");
-		lastTs4 = FS.DETECTED.lastModified(f);
+		lastTs4 = FS.DETECTED.lastModifiedInstant(f);
 		fsTick(f);
 		git.add().addFilepattern(".").call();
 		checkConsistentLastModified("0", "1", "2", "3", "4");
 		checkModificationTimeStampOrder("*" + lastTsIndex, "<0", "1", "2", "3",
 				"4", "<.git/index");
-		lastTsIndex = FS.DETECTED.lastModified(indexFile);
+		lastTsIndex = FS.DETECTED.lastModifiedInstant(indexFile);
 
 		// Do modifications on the side branch. Touch only "1", "2 and "3"
 		fsTick(indexFile);
@@ -1124,7 +1166,7 @@ public void checkForCorrectIndex(MergeStrategy strategy) throws Exception {
 		checkConsistentLastModified("0", "1", "2", "3", "4");
 		checkModificationTimeStampOrder("0", "4", "*" + lastTs4, "<*"
 				+ lastTsIndex, "<1", "2", "3", "<.git/index");
-		lastTsIndex = FS.DETECTED.lastModified(indexFile);
+		lastTsIndex = FS.DETECTED.lastModifiedInstant(indexFile);
 
 		// merge master and side. Should only touch "0," "2" and "3"
 		fsTick(indexFile);
@@ -1291,9 +1333,10 @@ private void checkConsistentLastModified(String... pathes)
 			assertEquals(
 					"IndexEntry with path "
 							+ path
-							+ " has lastmodified with is different from the worktree file",
-					FS.DETECTED.lastModified(new File(workTree, path)), dc.getEntry(path)
-							.getLastModified());
+							+ " has lastmodified which is different from the worktree file",
+					FS.DETECTED.lastModifiedInstant(new File(workTree, path)),
+					dc.getEntry(path)
+							.getLastModifiedInstant());
 	}
 
 	// Assert that modification timestamps of working tree files are as
@@ -1302,21 +1345,22 @@ private void checkConsistentLastModified(String... pathes)
 	// then this file must be younger then file i. A path "*<modtime>"
 	// represents a file with a modification time of <modtime>
 	// E.g. ("a", "b", "<c", "f/a.txt") means: a<=b<c<=f/a.txt
-	private void checkModificationTimeStampOrder(String... pathes)
-			throws IOException {
-		long lastMod = Long.MIN_VALUE;
+	private void checkModificationTimeStampOrder(String... pathes) {
+		Instant lastMod = EPOCH;
 		for (String p : pathes) {
 			boolean strong = p.startsWith("<");
 			boolean fixed = p.charAt(strong ? 1 : 0) == '*';
 			p = p.substring((strong ? 1 : 0) + (fixed ? 1 : 0));
-			long curMod = fixed ? Long.valueOf(p).longValue()
-					: FS.DETECTED.lastModified(new File(db.getWorkTree(), p));
-			if (strong)
+			Instant curMod = fixed ? Instant.parse(p)
+					: FS.DETECTED
+							.lastModifiedInstant(new File(db.getWorkTree(), p));
+			if (strong) {
 				assertTrue("path " + p + " is not younger than predecesssor",
-						curMod > lastMod);
-			else
+						curMod.compareTo(lastMod) > 0);
+			} else {
 				assertTrue("path " + p + " is older than predecesssor",
-						curMod >= lastMod);
+						curMod.compareTo(lastMod) >= 0);
+			}
 		}
 	}
 
@@ -1328,6 +1372,7 @@ private String readBlob(ObjectId treeish, String path) throws Exception {
 		if (obj == null) {
 			return null;
 		}
-		return new String(rw.getObjectReader().open(obj, OBJ_BLOB).getBytes(), CHARSET);
+		return new String(rw.getObjectReader().open(obj, OBJ_BLOB).getBytes(),
+				UTF_8);
 	}
 }
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/patch/GetTextTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/patch/GetTextTest.java
index 7b5868a..e383f36 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/patch/GetTextTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/patch/GetTextTest.java
@@ -44,7 +44,7 @@
 package org.eclipse.jgit.patch;
 
 import static java.nio.charset.StandardCharsets.ISO_8859_1;
-import static org.eclipse.jgit.lib.Constants.CHARSET;
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
@@ -83,7 +83,7 @@ public void testGetText_NoBinary() throws IOException {
 	@Test
 	public void testGetText_Convert() throws IOException {
 		final Charset csOld = ISO_8859_1;
-		final Charset csNew = CHARSET;
+		final Charset csNew = UTF_8;
 		final Patch p = parseTestPatchFile();
 		assertTrue(p.getErrors().isEmpty());
 		assertEquals(1, p.getFiles().size());
@@ -103,7 +103,7 @@ public void testGetText_Convert() throws IOException {
 	@Test
 	public void testGetText_DiffCc() throws IOException {
 		final Charset csOld = ISO_8859_1;
-		final Charset csNew = CHARSET;
+		final Charset csNew = UTF_8;
 		final Patch p = parseTestPatchFile();
 		assertTrue(p.getErrors().isEmpty());
 		assertEquals(1, p.getFiles().size());
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revplot/PlotCommitListTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revplot/PlotCommitListTest.java
index 9a6043f..7297de3 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revplot/PlotCommitListTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revplot/PlotCommitListTest.java
@@ -56,7 +56,7 @@
 
 public class PlotCommitListTest extends RevWalkTestCase {
 
-	class CommitListAssert {
+	static class CommitListAssert {
 		private PlotCommitList<PlotLane> pcl;
 		private PlotCommit<PlotLane> current;
 		private int nextIndex = 0;
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevCommitParseTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevCommitParseTest.java
index cfefac3..b814984 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevCommitParseTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevCommitParseTest.java
@@ -44,7 +44,7 @@
 package org.eclipse.jgit.revwalk;
 
 import static java.nio.charset.StandardCharsets.ISO_8859_1;
-import static org.eclipse.jgit.lib.Constants.CHARSET;
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
@@ -114,7 +114,7 @@ public void testParse_NoParents() throws Exception {
 		assertNull(c.getTree());
 		assertNull(c.parents);
 
-		c.parseCanonical(rw, body.toString().getBytes(CHARSET));
+		c.parseCanonical(rw, body.toString().getBytes(UTF_8));
 		assertNotNull(c.getTree());
 		assertEquals(treeId, c.getTree().getId());
 		assertSame(rw.lookupTree(treeId), c.getTree());
@@ -148,7 +148,7 @@ private RevCommit create(String msg) throws Exception {
 
 		final RevCommit c;
 		c = new RevCommit(id("9473095c4cb2f12aefe1db8a355fe3fafba42f67"));
-		c.parseCanonical(new RevWalk(db), b.toString().getBytes(CHARSET));
+		c.parseCanonical(new RevWalk(db), b.toString().getBytes(UTF_8));
 		return c;
 	}
 
@@ -161,7 +161,7 @@ public void testParse_WeirdHeaderOnlyCommit() throws Exception {
 
 		final RevCommit c;
 		c = new RevCommit(id("9473095c4cb2f12aefe1db8a355fe3fafba42f67"));
-		c.parseCanonical(new RevWalk(db), b.toString().getBytes(CHARSET));
+		c.parseCanonical(new RevWalk(db), b.toString().getBytes(UTF_8));
 
 		assertEquals("", c.getFullMessage());
 		assertEquals("", c.getShortMessage());
@@ -176,7 +176,7 @@ public void testParse_incompleteAuthorAndCommitter() throws Exception {
 
 		final RevCommit c;
 		c = new RevCommit(id("9473095c4cb2f12aefe1db8a355fe3fafba42f67"));
-		c.parseCanonical(new RevWalk(db), b.toString().getBytes(CHARSET));
+		c.parseCanonical(new RevWalk(db), b.toString().getBytes(UTF_8));
 
 		assertEquals(new PersonIdent("", "a_u_thor@example.com", 1218123387000l, 7), c.getAuthorIdent());
 		assertEquals(new PersonIdent("", "", 1218123390000l, -5), c.getCommitterIdent());
@@ -185,18 +185,18 @@ public void testParse_incompleteAuthorAndCommitter() throws Exception {
 	@Test
 	public void testParse_implicit_UTF8_encoded() throws Exception {
 		final ByteArrayOutputStream b = new ByteArrayOutputStream();
-		b.write("tree 9788669ad918b6fcce64af8882fc9a81cb6aba67\n".getBytes(CHARSET));
-		b.write("author F\u00f6r fattare <a_u_thor@example.com> 1218123387 +0700\n".getBytes(CHARSET));
-		b.write("committer C O. Miter <c@example.com> 1218123390 -0500\n".getBytes(CHARSET));
-		b.write("\n".getBytes(CHARSET));
-		b.write("Sm\u00f6rg\u00e5sbord\n".getBytes(CHARSET));
-		b.write("\n".getBytes(CHARSET));
-		b.write("\u304d\u308c\u3044\n".getBytes(CHARSET));
+		b.write("tree 9788669ad918b6fcce64af8882fc9a81cb6aba67\n".getBytes(UTF_8));
+		b.write("author F\u00f6r fattare <a_u_thor@example.com> 1218123387 +0700\n".getBytes(UTF_8));
+		b.write("committer C O. Miter <c@example.com> 1218123390 -0500\n".getBytes(UTF_8));
+		b.write("\n".getBytes(UTF_8));
+		b.write("Sm\u00f6rg\u00e5sbord\n".getBytes(UTF_8));
+		b.write("\n".getBytes(UTF_8));
+		b.write("\u304d\u308c\u3044\n".getBytes(UTF_8));
 		final RevCommit c;
 		c = new RevCommit(id("9473095c4cb2f12aefe1db8a355fe3fafba42f67")); // bogus id
 		c.parseCanonical(new RevWalk(db), b.toByteArray());
 
-		assertSame(Constants.CHARSET, c.getEncoding());
+		assertSame(UTF_8, c.getEncoding());
 		assertEquals("F\u00f6r fattare", c.getAuthorIdent().getName());
 		assertEquals("Sm\u00f6rg\u00e5sbord", c.getShortMessage());
 		assertEquals("Sm\u00f6rg\u00e5sbord\n\n\u304d\u308c\u3044\n", c.getFullMessage());
@@ -205,18 +205,18 @@ public void testParse_implicit_UTF8_encoded() throws Exception {
 	@Test
 	public void testParse_implicit_mixed_encoded() throws Exception {
 		final ByteArrayOutputStream b = new ByteArrayOutputStream();
-		b.write("tree 9788669ad918b6fcce64af8882fc9a81cb6aba67\n".getBytes(CHARSET));
+		b.write("tree 9788669ad918b6fcce64af8882fc9a81cb6aba67\n".getBytes(UTF_8));
 		b.write("author F\u00f6r fattare <a_u_thor@example.com> 1218123387 +0700\n".getBytes(ISO_8859_1));
-		b.write("committer C O. Miter <c@example.com> 1218123390 -0500\n".getBytes(CHARSET));
-		b.write("\n".getBytes(CHARSET));
-		b.write("Sm\u00f6rg\u00e5sbord\n".getBytes(CHARSET));
-		b.write("\n".getBytes(CHARSET));
-		b.write("\u304d\u308c\u3044\n".getBytes(CHARSET));
+		b.write("committer C O. Miter <c@example.com> 1218123390 -0500\n".getBytes(UTF_8));
+		b.write("\n".getBytes(UTF_8));
+		b.write("Sm\u00f6rg\u00e5sbord\n".getBytes(UTF_8));
+		b.write("\n".getBytes(UTF_8));
+		b.write("\u304d\u308c\u3044\n".getBytes(UTF_8));
 		final RevCommit c;
 		c = new RevCommit(id("9473095c4cb2f12aefe1db8a355fe3fafba42f67")); // bogus id
 		c.parseCanonical(new RevWalk(db), b.toByteArray());
 
-		assertSame(Constants.CHARSET, c.getEncoding());
+		assertSame(UTF_8, c.getEncoding());
 		assertEquals("F\u00f6r fattare", c.getAuthorIdent().getName());
 		assertEquals("Sm\u00f6rg\u00e5sbord", c.getShortMessage());
 		assertEquals("Sm\u00f6rg\u00e5sbord\n\n\u304d\u308c\u3044\n", c.getFullMessage());
@@ -260,14 +260,14 @@ public void testParse_explicit_encoded() throws Exception {
 	@Test
 	public void testParse_explicit_bad_encoded() throws Exception {
 		final ByteArrayOutputStream b = new ByteArrayOutputStream();
-		b.write("tree 9788669ad918b6fcce64af8882fc9a81cb6aba67\n".getBytes(CHARSET));
+		b.write("tree 9788669ad918b6fcce64af8882fc9a81cb6aba67\n".getBytes(UTF_8));
 		b.write("author F\u00f6r fattare <a_u_thor@example.com> 1218123387 +0700\n".getBytes(ISO_8859_1));
-		b.write("committer C O. Miter <c@example.com> 1218123390 -0500\n".getBytes(CHARSET));
-		b.write("encoding EUC-JP\n".getBytes(CHARSET));
-		b.write("\n".getBytes(CHARSET));
-		b.write("\u304d\u308c\u3044\n".getBytes(CHARSET));
-		b.write("\n".getBytes(CHARSET));
-		b.write("Hi\n".getBytes(CHARSET));
+		b.write("committer C O. Miter <c@example.com> 1218123390 -0500\n".getBytes(UTF_8));
+		b.write("encoding EUC-JP\n".getBytes(UTF_8));
+		b.write("\n".getBytes(UTF_8));
+		b.write("\u304d\u308c\u3044\n".getBytes(UTF_8));
+		b.write("\n".getBytes(UTF_8));
+		b.write("Hi\n".getBytes(UTF_8));
 		final RevCommit c;
 		c = new RevCommit(id("9473095c4cb2f12aefe1db8a355fe3fafba42f67")); // bogus id
 		c.parseCanonical(new RevWalk(db), b.toByteArray());
@@ -291,14 +291,14 @@ public void testParse_explicit_bad_encoded() throws Exception {
 	@Test
 	public void testParse_explicit_bad_encoded2() throws Exception {
 		final ByteArrayOutputStream b = new ByteArrayOutputStream();
-		b.write("tree 9788669ad918b6fcce64af8882fc9a81cb6aba67\n".getBytes(CHARSET));
-		b.write("author F\u00f6r fattare <a_u_thor@example.com> 1218123387 +0700\n".getBytes(CHARSET));
-		b.write("committer C O. Miter <c@example.com> 1218123390 -0500\n".getBytes(CHARSET));
-		b.write("encoding ISO-8859-1\n".getBytes(CHARSET));
-		b.write("\n".getBytes(CHARSET));
-		b.write("\u304d\u308c\u3044\n".getBytes(CHARSET));
-		b.write("\n".getBytes(CHARSET));
-		b.write("Hi\n".getBytes(CHARSET));
+		b.write("tree 9788669ad918b6fcce64af8882fc9a81cb6aba67\n".getBytes(UTF_8));
+		b.write("author F\u00f6r fattare <a_u_thor@example.com> 1218123387 +0700\n".getBytes(UTF_8));
+		b.write("committer C O. Miter <c@example.com> 1218123390 -0500\n".getBytes(UTF_8));
+		b.write("encoding ISO-8859-1\n".getBytes(UTF_8));
+		b.write("\n".getBytes(UTF_8));
+		b.write("\u304d\u308c\u3044\n".getBytes(UTF_8));
+		b.write("\n".getBytes(UTF_8));
+		b.write("Hi\n".getBytes(UTF_8));
 		final RevCommit c;
 		c = new RevCommit(id("9473095c4cb2f12aefe1db8a355fe3fafba42f67")); // bogus id
 		c.parseCanonical(new RevWalk(db), b.toByteArray());
@@ -313,13 +313,13 @@ public void testParse_explicit_bad_encoded2() throws Exception {
 	public void testParse_incorrectUtf8Name() throws Exception {
 		ByteArrayOutputStream b = new ByteArrayOutputStream();
 		b.write("tree 9788669ad918b6fcce64af8882fc9a81cb6aba67\n"
-				.getBytes(CHARSET));
-		b.write("author au <a@example.com> 1218123387 +0700\n".getBytes(CHARSET));
+				.getBytes(UTF_8));
+		b.write("author au <a@example.com> 1218123387 +0700\n".getBytes(UTF_8));
 		b.write("committer co <c@example.com> 1218123390 -0500\n"
-				.getBytes(CHARSET));
-		b.write("encoding 'utf8'\n".getBytes(CHARSET));
-		b.write("\n".getBytes(CHARSET));
-		b.write("Sm\u00f6rg\u00e5sbord\n".getBytes(CHARSET));
+				.getBytes(UTF_8));
+		b.write("encoding 'utf8'\n".getBytes(UTF_8));
+		b.write("\n".getBytes(UTF_8));
+		b.write("Sm\u00f6rg\u00e5sbord\n".getBytes(UTF_8));
 
 		RevCommit c = new RevCommit(
 				id("9473095c4cb2f12aefe1db8a355fe3fafba42f67"));
@@ -338,12 +338,12 @@ public void testParse_incorrectUtf8Name() throws Exception {
 	@Test
 	public void testParse_illegalEncoding() throws Exception {
 		ByteArrayOutputStream b = new ByteArrayOutputStream();
-		b.write("tree 9788669ad918b6fcce64af8882fc9a81cb6aba67\n".getBytes(CHARSET));
-		b.write("author au <a@example.com> 1218123387 +0700\n".getBytes(CHARSET));
-		b.write("committer co <c@example.com> 1218123390 -0500\n".getBytes(CHARSET));
-		b.write("encoding utf-8logoutputencoding=gbk\n".getBytes(CHARSET));
-		b.write("\n".getBytes(CHARSET));
-		b.write("message\n".getBytes(CHARSET));
+		b.write("tree 9788669ad918b6fcce64af8882fc9a81cb6aba67\n".getBytes(UTF_8));
+		b.write("author au <a@example.com> 1218123387 +0700\n".getBytes(UTF_8));
+		b.write("committer co <c@example.com> 1218123390 -0500\n".getBytes(UTF_8));
+		b.write("encoding utf-8logoutputencoding=gbk\n".getBytes(UTF_8));
+		b.write("\n".getBytes(UTF_8));
+		b.write("message\n".getBytes(UTF_8));
 
 		RevCommit c = new RevCommit(
 				id("9473095c4cb2f12aefe1db8a355fe3fafba42f67"));
@@ -365,12 +365,12 @@ public void testParse_illegalEncoding() throws Exception {
 	@Test
 	public void testParse_unsupportedEncoding() throws Exception {
 		ByteArrayOutputStream b = new ByteArrayOutputStream();
-		b.write("tree 9788669ad918b6fcce64af8882fc9a81cb6aba67\n".getBytes(CHARSET));
-		b.write("author au <a@example.com> 1218123387 +0700\n".getBytes(CHARSET));
-		b.write("committer co <c@example.com> 1218123390 -0500\n".getBytes(CHARSET));
-		b.write("encoding it_IT.UTF8\n".getBytes(CHARSET));
-		b.write("\n".getBytes(CHARSET));
-		b.write("message\n".getBytes(CHARSET));
+		b.write("tree 9788669ad918b6fcce64af8882fc9a81cb6aba67\n".getBytes(UTF_8));
+		b.write("author au <a@example.com> 1218123387 +0700\n".getBytes(UTF_8));
+		b.write("committer co <c@example.com> 1218123390 -0500\n".getBytes(UTF_8));
+		b.write("encoding it_IT.UTF8\n".getBytes(UTF_8));
+		b.write("\n".getBytes(UTF_8));
+		b.write("message\n".getBytes(UTF_8));
 
 		RevCommit c = new RevCommit(
 				id("9473095c4cb2f12aefe1db8a355fe3fafba42f67"));
@@ -486,4 +486,36 @@ public void testParse_GitStyleMessageWithCRLF() throws Exception {
 	private static ObjectId id(String str) {
 		return ObjectId.fromString(str);
 	}
+
+	@Test
+	public void testParse_gpgSig() throws Exception {
+		String commit = "tree e3a1035abd2b319bb01e57d69b0ba6cab289297e\n" +
+		"parent 54e895b87c0768d2317a2b17062e3ad9f76a8105\n" +
+		"committer A U Thor <author@xample.com 1528968566 +0200\n" +
+		"gpgsig -----BEGIN PGP SIGNATURE-----\n" +
+		" \n" +
+		" wsBcBAABCAAQBQJbGB4pCRBK7hj4Ov3rIwAAdHIIAENrvz23867ZgqrmyPemBEZP\n" +
+		" U24B1Tlq/DWvce2buaxmbNQngKZ0pv2s8VMc11916WfTIC9EKvioatmpjduWvhqj\n" +
+		" znQTFyiMor30pyYsfrqFuQZvqBW01o8GEWqLg8zjf9Rf0R3LlOEw86aT8CdHRlm6\n" +
+		" wlb22xb8qoX4RB+LYfz7MhK5F+yLOPXZdJnAVbuyoMGRnDpwdzjL5Hj671+XJxN5\n" +
+		" SasRdhxkkfw/ZnHxaKEc4juMz8Nziz27elRwhOQqlTYoXNJnsV//wy5Losd7aKi1\n" +
+		" xXXyUpndEOmT0CIcKHrN/kbYoVL28OJaxoBuva3WYQaRrzEe3X02NMxZe9gkSqA=\n" +
+		" =TClh\n" +
+		" -----END PGP SIGNATURE-----\n" +
+		"some other header\n\n" +
+		"commit message";
+
+		final RevCommit c;
+		c = new RevCommit(id("9473095c4cb2f12aefe1db8a355fe3fafba42f67"));
+		c.parseCanonical(new RevWalk(db), commit.getBytes(UTF_8));
+		String gpgSig = new String(c.getRawGpgSignature(), UTF_8);
+		assertTrue(gpgSig.startsWith("-----BEGIN"));
+		assertTrue(gpgSig.endsWith("END PGP SIGNATURE-----"));
+	}
+
+	@Test
+	public void testParse_NoGpgSig() throws Exception {
+		final RevCommit c = create("a message");
+		assertNull(c.getRawGpgSignature());
+	}
 }
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 8e389ae..4969305 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
@@ -144,13 +144,13 @@ public void testAddRevFlag() throws Exception {
 		final RevCommit a = commit();
 		final RevFlag flag1 = rw.newFlag("flag1");
 		final RevFlag flag2 = rw.newFlag("flag2");
-		assertEquals(0, a.flags);
+		assertEquals(RevWalk.PARSED, a.flags);
 
 		a.add(flag1);
-		assertEquals(flag1.mask, a.flags);
+		assertEquals(RevWalk.PARSED | flag1.mask, a.flags);
 
 		a.add(flag2);
-		assertEquals(flag1.mask | flag2.mask, a.flags);
+		assertEquals(RevWalk.PARSED | flag1.mask | flag2.mask, a.flags);
 	}
 
 	@Test
@@ -162,10 +162,10 @@ public void testAddRevFlagSet() throws Exception {
 		s.add(flag1);
 		s.add(flag2);
 
-		assertEquals(0, a.flags);
+		assertEquals(RevWalk.PARSED, a.flags);
 
 		a.add(s);
-		assertEquals(flag1.mask | flag2.mask, a.flags);
+		assertEquals(RevWalk.PARSED | flag1.mask | flag2.mask, a.flags);
 	}
 
 	@Test
@@ -175,9 +175,9 @@ public void testRemoveRevFlag() throws Exception {
 		final RevFlag flag2 = rw.newFlag("flag2");
 		a.add(flag1);
 		a.add(flag2);
-		assertEquals(flag1.mask | flag2.mask, a.flags);
+		assertEquals(RevWalk.PARSED | flag1.mask | flag2.mask, a.flags);
 		a.remove(flag2);
-		assertEquals(flag1.mask, a.flags);
+		assertEquals(RevWalk.PARSED | flag1.mask, a.flags);
 	}
 
 	@Test
@@ -191,8 +191,8 @@ public void testRemoveRevFlagSet() throws Exception {
 		s.add(flag2);
 		a.add(flag3);
 		a.add(s);
-		assertEquals(flag1.mask | flag2.mask | flag3.mask, a.flags);
+		assertEquals(RevWalk.PARSED | flag1.mask | flag2.mask | flag3.mask, a.flags);
 		a.remove(s);
-		assertEquals(flag3.mask, a.flags);
+		assertEquals(RevWalk.PARSED | flag3.mask, a.flags);
 	}
 }
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevTagParseTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevTagParseTest.java
index e11cef7..1b45473 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevTagParseTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevTagParseTest.java
@@ -44,7 +44,7 @@
 package org.eclipse.jgit.revwalk;
 
 import static java.nio.charset.StandardCharsets.ISO_8859_1;
-import static org.eclipse.jgit.lib.Constants.CHARSET;
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
@@ -98,7 +98,7 @@ private void testOneType(int typeCode) throws Exception {
 		assertNull(c.getObject());
 		assertNull(c.getTagName());
 
-		c.parseCanonical(rw, b.toString().getBytes(CHARSET));
+		c.parseCanonical(rw, b.toString().getBytes(UTF_8));
 		assertNotNull(c.getObject());
 		assertEquals(id, c.getObject().getId());
 		assertSame(rw.lookupAny(id, typeCode), c.getObject());
@@ -141,7 +141,7 @@ public void testParseAllFields() throws Exception {
 		assertNull(c.getObject());
 		assertNull(c.getTagName());
 
-		c.parseCanonical(rw, body.toString().getBytes(CHARSET));
+		c.parseCanonical(rw, body.toString().getBytes(UTF_8));
 		assertNotNull(c.getObject());
 		assertEquals(treeId, c.getObject().getId());
 		assertSame(rw.lookupTree(treeId), c.getObject());
@@ -189,7 +189,7 @@ public void testParseOldStyleNoTagger() throws Exception {
 		assertNull(c.getObject());
 		assertNull(c.getTagName());
 
-		c.parseCanonical(rw, body.toString().getBytes(CHARSET));
+		c.parseCanonical(rw, body.toString().getBytes(UTF_8));
 		assertNotNull(c.getObject());
 		assertEquals(treeId, c.getObject().getId());
 		assertSame(rw.lookupTree(treeId), c.getObject());
@@ -213,7 +213,7 @@ private RevTag create(String msg) throws Exception {
 
 		final RevTag c;
 		c = new RevTag(id("9473095c4cb2f12aefe1db8a355fe3fafba42f67"));
-		c.parseCanonical(new RevWalk(db), b.toString().getBytes(CHARSET));
+		c.parseCanonical(new RevWalk(db), b.toString().getBytes(UTF_8));
 		return c;
 	}
 
@@ -221,17 +221,17 @@ private RevTag create(String msg) throws Exception {
 	public void testParse_implicit_UTF8_encoded() throws Exception {
 		final ByteArrayOutputStream b = new ByteArrayOutputStream();
 		b.write("object 9788669ad918b6fcce64af8882fc9a81cb6aba67\n"
-				.getBytes(CHARSET));
-		b.write("type tree\n".getBytes(CHARSET));
-		b.write("tag v1.2.3.4.5\n".getBytes(CHARSET));
+				.getBytes(UTF_8));
+		b.write("type tree\n".getBytes(UTF_8));
+		b.write("tag v1.2.3.4.5\n".getBytes(UTF_8));
 
 		b
 				.write("tagger F\u00f6r fattare <a_u_thor@example.com> 1218123387 +0700\n"
-						.getBytes(CHARSET));
-		b.write("\n".getBytes(CHARSET));
-		b.write("Sm\u00f6rg\u00e5sbord\n".getBytes(CHARSET));
-		b.write("\n".getBytes(CHARSET));
-		b.write("\u304d\u308c\u3044\n".getBytes(CHARSET));
+						.getBytes(UTF_8));
+		b.write("\n".getBytes(UTF_8));
+		b.write("Sm\u00f6rg\u00e5sbord\n".getBytes(UTF_8));
+		b.write("\n".getBytes(UTF_8));
+		b.write("\u304d\u308c\u3044\n".getBytes(UTF_8));
 		final RevTag c;
 		c = new RevTag(id("9473095c4cb2f12aefe1db8a355fe3fafba42f67"));
 		c.parseCanonical(new RevWalk(db), b.toByteArray());
@@ -246,15 +246,15 @@ public void testParse_implicit_UTF8_encoded() throws Exception {
 	public void testParse_implicit_mixed_encoded() throws Exception {
 		final ByteArrayOutputStream b = new ByteArrayOutputStream();
 		b.write("object 9788669ad918b6fcce64af8882fc9a81cb6aba67\n"
-				.getBytes(CHARSET));
-		b.write("type tree\n".getBytes(CHARSET));
-		b.write("tag v1.2.3.4.5\n".getBytes(CHARSET));
+				.getBytes(UTF_8));
+		b.write("type tree\n".getBytes(UTF_8));
+		b.write("tag v1.2.3.4.5\n".getBytes(UTF_8));
 		b.write("tagger F\u00f6r fattare <a_u_thor@example.com> 1218123387 +0700\n"
 				.getBytes(ISO_8859_1));
-		b.write("\n".getBytes(CHARSET));
-		b.write("Sm\u00f6rg\u00e5sbord\n".getBytes(CHARSET));
-		b.write("\n".getBytes(CHARSET));
-		b.write("\u304d\u308c\u3044\n".getBytes(CHARSET));
+		b.write("\n".getBytes(UTF_8));
+		b.write("Sm\u00f6rg\u00e5sbord\n".getBytes(UTF_8));
+		b.write("\n".getBytes(UTF_8));
+		b.write("\u304d\u308c\u3044\n".getBytes(UTF_8));
 		final RevTag c;
 		c = new RevTag(id("9473095c4cb2f12aefe1db8a355fe3fafba42f67"));
 		c.parseCanonical(new RevWalk(db), b.toByteArray());
@@ -307,17 +307,17 @@ public void testParse_explicit_encoded() throws Exception {
 	public void testParse_explicit_bad_encoded() throws Exception {
 		final ByteArrayOutputStream b = new ByteArrayOutputStream();
 		b.write("object 9788669ad918b6fcce64af8882fc9a81cb6aba67\n"
-				.getBytes(CHARSET));
-		b.write("type tree\n".getBytes(CHARSET));
-		b.write("tag v1.2.3.4.5\n".getBytes(CHARSET));
+				.getBytes(UTF_8));
+		b.write("type tree\n".getBytes(UTF_8));
+		b.write("tag v1.2.3.4.5\n".getBytes(UTF_8));
 		b
 				.write("tagger F\u00f6r fattare <a_u_thor@example.com> 1218123387 +0700\n"
 						.getBytes(ISO_8859_1));
-		b.write("encoding EUC-JP\n".getBytes(CHARSET));
-		b.write("\n".getBytes(CHARSET));
-		b.write("\u304d\u308c\u3044\n".getBytes(CHARSET));
-		b.write("\n".getBytes(CHARSET));
-		b.write("Hi\n".getBytes(CHARSET));
+		b.write("encoding EUC-JP\n".getBytes(UTF_8));
+		b.write("\n".getBytes(UTF_8));
+		b.write("\u304d\u308c\u3044\n".getBytes(UTF_8));
+		b.write("\n".getBytes(UTF_8));
+		b.write("Hi\n".getBytes(UTF_8));
 		final RevTag c;
 		c = new RevTag(id("9473095c4cb2f12aefe1db8a355fe3fafba42f67"));
 		c.parseCanonical(new RevWalk(db), b.toByteArray());
@@ -342,17 +342,17 @@ public void testParse_explicit_bad_encoded() throws Exception {
 	public void testParse_explicit_bad_encoded2() throws Exception {
 		final ByteArrayOutputStream b = new ByteArrayOutputStream();
 		b.write("object 9788669ad918b6fcce64af8882fc9a81cb6aba67\n"
-				.getBytes(CHARSET));
-		b.write("type tree\n".getBytes(CHARSET));
-		b.write("tag v1.2.3.4.5\n".getBytes(CHARSET));
+				.getBytes(UTF_8));
+		b.write("type tree\n".getBytes(UTF_8));
+		b.write("tag v1.2.3.4.5\n".getBytes(UTF_8));
 		b
 				.write("tagger F\u00f6r fattare <a_u_thor@example.com> 1218123387 +0700\n"
-						.getBytes(CHARSET));
-		b.write("encoding ISO-8859-1\n".getBytes(CHARSET));
-		b.write("\n".getBytes(CHARSET));
-		b.write("\u304d\u308c\u3044\n".getBytes(CHARSET));
-		b.write("\n".getBytes(CHARSET));
-		b.write("Hi\n".getBytes(CHARSET));
+						.getBytes(UTF_8));
+		b.write("encoding ISO-8859-1\n".getBytes(UTF_8));
+		b.write("\n".getBytes(UTF_8));
+		b.write("\u304d\u308c\u3044\n".getBytes(UTF_8));
+		b.write("\n".getBytes(UTF_8));
+		b.write("Hi\n".getBytes(UTF_8));
 		final RevTag c;
 		c = new RevTag(id("9473095c4cb2f12aefe1db8a355fe3fafba42f67"));
 		c.parseCanonical(new RevWalk(db), b.toByteArray());
@@ -365,13 +365,13 @@ public void testParse_explicit_bad_encoded2() throws Exception {
 	@Test
 	public void testParse_illegalEncoding() throws Exception {
 		ByteArrayOutputStream b = new ByteArrayOutputStream();
-		b.write("object 9788669ad918b6fcce64af8882fc9a81cb6aba67\n".getBytes(CHARSET));
-		b.write("type tree\n".getBytes(CHARSET));
-		b.write("tag v1.0\n".getBytes(CHARSET));
-		b.write("tagger t <t@example.com> 1218123387 +0700\n".getBytes(CHARSET));
-		b.write("encoding utf-8logoutputencoding=gbk\n".getBytes(CHARSET));
-		b.write("\n".getBytes(CHARSET));
-		b.write("message\n".getBytes(CHARSET));
+		b.write("object 9788669ad918b6fcce64af8882fc9a81cb6aba67\n".getBytes(UTF_8));
+		b.write("type tree\n".getBytes(UTF_8));
+		b.write("tag v1.0\n".getBytes(UTF_8));
+		b.write("tagger t <t@example.com> 1218123387 +0700\n".getBytes(UTF_8));
+		b.write("encoding utf-8logoutputencoding=gbk\n".getBytes(UTF_8));
+		b.write("\n".getBytes(UTF_8));
+		b.write("message\n".getBytes(UTF_8));
 
 		RevTag t = new RevTag(id("9473095c4cb2f12aefe1db8a355fe3fafba42f67"));
 		t.parseCanonical(new RevWalk(db), b.toByteArray());
@@ -384,13 +384,13 @@ public void testParse_illegalEncoding() throws Exception {
 	@Test
 	public void testParse_unsupportedEncoding() throws Exception {
 		ByteArrayOutputStream b = new ByteArrayOutputStream();
-		b.write("object 9788669ad918b6fcce64af8882fc9a81cb6aba67\n".getBytes(CHARSET));
-		b.write("type tree\n".getBytes(CHARSET));
-		b.write("tag v1.0\n".getBytes(CHARSET));
-		b.write("tagger t <t@example.com> 1218123387 +0700\n".getBytes(CHARSET));
-		b.write("encoding it_IT.UTF8\n".getBytes(CHARSET));
-		b.write("\n".getBytes(CHARSET));
-		b.write("message\n".getBytes(CHARSET));
+		b.write("object 9788669ad918b6fcce64af8882fc9a81cb6aba67\n".getBytes(UTF_8));
+		b.write("type tree\n".getBytes(UTF_8));
+		b.write("tag v1.0\n".getBytes(UTF_8));
+		b.write("tagger t <t@example.com> 1218123387 +0700\n".getBytes(UTF_8));
+		b.write("encoding it_IT.UTF8\n".getBytes(UTF_8));
+		b.write("\n".getBytes(UTF_8));
+		b.write("message\n".getBytes(UTF_8));
 
 		RevTag t = new RevTag(id("9473095c4cb2f12aefe1db8a355fe3fafba42f67"));
 		t.parseCanonical(new RevWalk(db), b.toByteArray());
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkCullTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkCullTest.java
index fb52828..7984a37 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkCullTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkCullTest.java
@@ -85,7 +85,7 @@ public void testProperlyCullAllAncestors2() throws Exception {
 
 	@Test
 	public void testProperlyCullAllAncestors_LongHistory() throws Exception {
-		final RevCommit a = commit();
+		RevCommit a = commit();
 		RevCommit b = commit(a);
 		for (int i = 0; i < 24; i++) {
 			b = commit(b);
@@ -94,6 +94,12 @@ public void testProperlyCullAllAncestors_LongHistory() throws Exception {
 		}
 		final RevCommit c = commit(b);
 
+		// TestRepository eagerly parses newly created objects. The current rw
+		// is caching that parsed state. To verify that RevWalk itself is lazy,
+		// set up a new one.
+		rw.close();
+		rw = createRevWalk();
+		RevCommit a2 = rw.lookupCommit(a);
 		markStart(c);
 		markUninteresting(b);
 		assertCommit(c, rw.next());
@@ -102,6 +108,6 @@ public void testProperlyCullAllAncestors_LongHistory() throws Exception {
 		// We should have aborted before we got back so far that "a"
 		// would be parsed. Thus, its parents shouldn't be allocated.
 		//
-		assertNull(a.parents);
+		assertNull(a2.parents);
 	}
 }
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkShallowTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkShallowTest.java
index 6df36e7..7554d7a 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkShallowTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkShallowTest.java
@@ -58,138 +58,155 @@ public class RevWalkShallowTest extends RevWalkTestCase {
 
 	@Test
 	public void testDepth1() throws Exception {
-		final RevCommit a = commit();
-		final RevCommit b = commit(a);
-		final RevCommit c = commit(b);
-		final RevCommit d = commit(c);
+		RevCommit[] commits = setupLinearChain();
 
-		createShallowFile(d);
+		createShallowFile(commits[3]);
+		updateCommits(commits);
 
-		rw.reset();
-		markStart(d);
-		assertCommit(d, rw.next());
+		rw.markStart(commits[3]);
+		assertCommit(commits[3], rw.next());
 		assertNull(rw.next());
 	}
 
 	@Test
 	public void testDepth2() throws Exception {
-		final RevCommit a = commit();
-		final RevCommit b = commit(a);
-		final RevCommit c = commit(b);
-		final RevCommit d = commit(c);
+		RevCommit[] commits = setupLinearChain();
 
-		createShallowFile(c);
+		createShallowFile(commits[2]);
+		updateCommits(commits);
 
-		rw.reset();
-		markStart(d);
-		assertCommit(d, rw.next());
-		assertCommit(c, rw.next());
+		rw.markStart(commits[3]);
+		assertCommit(commits[3], rw.next());
+		assertCommit(commits[2], rw.next());
 		assertNull(rw.next());
 	}
 
 	@Test
 	public void testDepth3() throws Exception {
-		final RevCommit a = commit();
-		final RevCommit b = commit(a);
-		final RevCommit c = commit(b);
-		final RevCommit d = commit(c);
+		RevCommit[] commits = setupLinearChain();
 
-		createShallowFile(b);
+		createShallowFile(commits[1]);
+		updateCommits(commits);
 
-		rw.reset();
-		markStart(d);
-		assertCommit(d, rw.next());
-		assertCommit(c, rw.next());
-		assertCommit(b, rw.next());
-		assertNull(rw.next());
-	}
-
-	@Test
-	public void testMergeCommitOneParentShallow() throws Exception {
-		final RevCommit a = commit();
-		final RevCommit b = commit(a);
-		final RevCommit c = commit(b);
-		final RevCommit d = commit(b);
-		final RevCommit e = commit(d);
-		final RevCommit merge = commit(c, e);
-
-		createShallowFile(e);
-
-		rw.reset();
-		markStart(merge);
-		assertCommit(merge, rw.next());
-		assertCommit(e, rw.next());
-		assertCommit(c, rw.next());
-		assertCommit(b, rw.next());
-		assertCommit(a, rw.next());
-		assertNull(rw.next());
-	}
-
-	@Test
-	public void testMergeCommitEntirelyShallow() throws Exception {
-		final RevCommit a = commit();
-		final RevCommit b = commit(a);
-		final RevCommit c = commit(b);
-		final RevCommit d = commit(b);
-		final RevCommit e = commit(d);
-		final RevCommit merge = commit(c, e);
-
-		createShallowFile(c, e);
-
-		rw.reset();
-		markStart(merge);
-		assertCommit(merge, rw.next());
-		assertCommit(e, rw.next());
-		assertCommit(c, rw.next());
+		rw.markStart(commits[3]);
+		assertCommit(commits[3], rw.next());
+		assertCommit(commits[2], rw.next());
+		assertCommit(commits[1], rw.next());
 		assertNull(rw.next());
 	}
 
 	@Test
 	public void testObjectDirectorySnapshot() throws Exception {
-		RevCommit a = commit();
-		RevCommit b = commit(a);
-		RevCommit c = commit(b);
-		RevCommit d = commit(c);
+		RevCommit[] commits = setupLinearChain();
 
-		createShallowFile(d);
+		createShallowFile(commits[3]);
+		updateCommits(commits);
 
-		rw.reset();
-		markStart(d);
-		assertCommit(d, rw.next());
+		markStart(commits[3]);
+		assertCommit(commits[3], rw.next());
 		assertNull(rw.next());
 
-		rw = createRevWalk();
-		a = rw.lookupCommit(a);
-		b = rw.lookupCommit(b);
-		c = rw.lookupCommit(c);
-		d = rw.lookupCommit(d);
+		createShallowFile(commits[2]);
+		updateCommits(commits);
 
-		rw.reset();
-		markStart(d);
-		assertCommit(d, rw.next());
+		markStart(commits[3]);
+		assertCommit(commits[3], rw.next());
+		assertCommit(commits[2], rw.next());
 		assertNull(rw.next());
+	}
 
-		createShallowFile(c);
+	private RevCommit[] setupLinearChain() throws Exception {
+		RevCommit[] commits = new RevCommit[4];
+		RevCommit parent = null;
+		for (int i = 0; i < commits.length; i++) {
+			commits[i] = parent != null ? commit(parent) : commit();
+			parent = commits[i];
+		}
+		return commits;
+	}
 
-		rw = createRevWalk();
-		a = rw.lookupCommit(a);
-		b = rw.lookupCommit(b);
-		c = rw.lookupCommit(c);
-		d = rw.lookupCommit(d);
+	@Test
+	public void testMergeCommitOneParentShallow() throws Exception {
+		RevCommit[] commits = setupMergeChain();
 
-		rw.reset();
-		markStart(d);
-		assertCommit(d, rw.next());
-		assertCommit(c, rw.next());
+		createShallowFile(commits[4]);
+		updateCommits(commits);
+
+		markStart(commits[5]);
+		assertCommit(commits[5], rw.next());
+		assertCommit(commits[4], rw.next());
+		assertCommit(commits[2], rw.next());
+		assertCommit(commits[1], rw.next());
+		assertCommit(commits[0], rw.next());
 		assertNull(rw.next());
 	}
 
+	@Test
+	public void testMergeCommitEntirelyShallow() throws Exception {
+		RevCommit[] commits = setupMergeChain();
+
+		createShallowFile(commits[2], commits[4]);
+		updateCommits(commits);
+
+		markStart(commits[5]);
+		assertCommit(commits[5], rw.next());
+		assertCommit(commits[4], rw.next());
+		assertCommit(commits[2], rw.next());
+		assertNull(rw.next());
+	}
+
+	private RevCommit[] setupMergeChain() throws Exception {
+		/*-
+		 * Create a history like this, diverging at 1 and merging at 5:
+		 *
+		 *      ---o--o       commits 3,4
+		 *     /       \
+		 * o--o--o------o   commits 0,1,2,5
+		 */
+		RevCommit[] commits = new RevCommit[6];
+		commits[0] = commit();
+		commits[1] = commit(commits[0]);
+		commits[2] = commit(commits[1]);
+		commits[3] = commit(commits[1]);
+		commits[4] = commit(commits[3]);
+		commits[5] = commit(commits[2], commits[4]);
+		return commits;
+	}
+
+	private void updateCommits(RevCommit[] commits) {
+		// Relookup commits using the new RevWalk
+		for (int i = 0; i < commits.length; i++) {
+			commits[i] = rw.lookupCommit(commits[i].getId());
+		}
+	}
+
 	private void createShallowFile(ObjectId... shallowCommits)
 			throws IOException {
-		final StringBuilder builder = new StringBuilder();
-		for (ObjectId commit : shallowCommits)
+		// Reset the RevWalk since the new shallow file invalidates the existing
+		// RevWalk's shallow state.
+		rw.close();
+		rw = createRevWalk();
+		StringBuilder builder = new StringBuilder();
+		for (ObjectId commit : shallowCommits) {
 			builder.append(commit.getName() + "\n");
+		}
 		JGitTestUtil.write(new File(db.getDirectory(), "shallow"),
 				builder.toString());
 	}
+
+	@Test
+	public void testShallowCommitParse() throws Exception {
+		RevCommit a = commit();
+		RevCommit b = commit(a);
+
+		createShallowFile(b);
+
+		rw.close();
+		rw = createRevWalk();
+		b = rw.parseCommit(b);
+
+		markStart(b);
+		assertCommit(b, rw.next());
+		assertNull(rw.next());
+	}
 }
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkUtilsReachableTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkUtilsReachableTest.java
index a26ae10..cb92a95 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkUtilsReachableTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkUtilsReachableTest.java
@@ -115,7 +115,7 @@ private Ref branch(String name, RevCommit dst) throws Exception {
 	}
 
 	private void assertContains(RevCommit commit, Collection<Ref> refsThatShouldContainCommit) throws Exception {
-		Collection<Ref> allRefs = db.getAllRefs().values();
+		Collection<Ref> allRefs = db.getRefDatabase().getRefs();
 		Collection<Ref> sortedRefs = RefComparator.sort(allRefs);
 		List<Ref> actual = RevWalkUtils.findBranchesReachableFrom(commit,
 				rw, sortedRefs);
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/FileBasedConfigTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/FileBasedConfigTest.java
index 0dea5ce..f3cd61d 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/FileBasedConfigTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/FileBasedConfigTest.java
@@ -42,21 +42,24 @@
  */
 package org.eclipse.jgit.storage.file;
 
-import static org.eclipse.jgit.lib.Constants.CHARSET;
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.eclipse.jgit.util.FileUtils.pathToString;
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
 
 import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.FileOutputStream;
 import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
 
 import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.junit.MockSystemReader;
 import org.eclipse.jgit.util.FS;
 import org.eclipse.jgit.util.FileUtils;
 import org.eclipse.jgit.util.IO;
+import org.eclipse.jgit.util.SystemReader;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -77,42 +80,45 @@ public class FileBasedConfigTest {
 	private static final String CONTENT2 = "[" + USER + "]\n\t" + NAME + " = "
 			+ BOB + "\n";
 
-	private File trash;
+	private Path trash;
 
 	@Before
 	public void setUp() throws Exception {
-		trash = File.createTempFile("tmp_", "");
-		trash.delete();
-		assertTrue("mkdir " + trash, trash.mkdir());
+		SystemReader.setInstance(new MockSystemReader());
+		trash = Files.createTempDirectory("tmp_");
+		FS.getFileStoreAttributes(trash.getParent());
 	}
 
 	@After
 	public void tearDown() throws Exception {
-		FileUtils.delete(trash, FileUtils.RECURSIVE | FileUtils.SKIP_MISSING);
+		FileUtils.delete(trash.toFile(),
+				FileUtils.RECURSIVE | FileUtils.SKIP_MISSING | FileUtils.RETRY);
 	}
 
 	@Test
 	public void testSystemEncoding() throws IOException, ConfigInvalidException {
-		final File file = createFile(CONTENT1.getBytes());
-		final FileBasedConfig config = new FileBasedConfig(file, FS.DETECTED);
+		final Path file = createFile(CONTENT1.getBytes());
+		final FileBasedConfig config = new FileBasedConfig(file.toFile(),
+				FS.DETECTED);
 		config.load();
 		assertEquals(ALICE, config.getString(USER, null, NAME));
 
 		config.setString(USER, null, NAME, BOB);
 		config.save();
-		assertArrayEquals(CONTENT2.getBytes(), IO.readFully(file));
+		assertArrayEquals(CONTENT2.getBytes(), IO.readFully(file.toFile()));
 	}
 
 	@Test
 	public void testUTF8withoutBOM() throws IOException, ConfigInvalidException {
-		final File file = createFile(CONTENT1.getBytes(CHARSET));
-		final FileBasedConfig config = new FileBasedConfig(file, FS.DETECTED);
+		final Path file = createFile(CONTENT1.getBytes(UTF_8));
+		final FileBasedConfig config = new FileBasedConfig(file.toFile(),
+				FS.DETECTED);
 		config.load();
 		assertEquals(ALICE, config.getString(USER, null, NAME));
 
 		config.setString(USER, null, NAME, BOB);
 		config.save();
-		assertArrayEquals(CONTENT2.getBytes(), IO.readFully(file));
+		assertArrayEquals(CONTENT2.getBytes(), IO.readFully(file.toFile()));
 	}
 
 	@Test
@@ -121,10 +127,11 @@ public void testUTF8withBOM() throws IOException, ConfigInvalidException {
 		bos1.write(0xEF);
 		bos1.write(0xBB);
 		bos1.write(0xBF);
-		bos1.write(CONTENT1.getBytes(CHARSET));
+		bos1.write(CONTENT1.getBytes(UTF_8));
 
-		final File file = createFile(bos1.toByteArray());
-		final FileBasedConfig config = new FileBasedConfig(file, FS.DETECTED);
+		final Path file = createFile(bos1.toByteArray());
+		final FileBasedConfig config = new FileBasedConfig(file.toFile(),
+				FS.DETECTED);
 		config.load();
 		assertEquals(ALICE, config.getString(USER, null, NAME));
 
@@ -135,8 +142,8 @@ public void testUTF8withBOM() throws IOException, ConfigInvalidException {
 		bos2.write(0xEF);
 		bos2.write(0xBB);
 		bos2.write(0xBF);
-		bos2.write(CONTENT2.getBytes(CHARSET));
-		assertArrayEquals(bos2.toByteArray(), IO.readFully(file));
+		bos2.write(CONTENT2.getBytes(UTF_8));
+		assertArrayEquals(bos2.toByteArray(), IO.readFully(file.toFile()));
 	}
 
 	@Test
@@ -145,8 +152,9 @@ public void testLeadingWhitespaces() throws IOException, ConfigInvalidException
 		bos1.write(" \n\t".getBytes());
 		bos1.write(CONTENT1.getBytes());
 
-		final File file = createFile(bos1.toByteArray());
-		final FileBasedConfig config = new FileBasedConfig(file, FS.DETECTED);
+		final Path file = createFile(bos1.toByteArray());
+		final FileBasedConfig config = new FileBasedConfig(file.toFile(),
+				FS.DETECTED);
 		config.load();
 		assertEquals(ALICE, config.getString(USER, null, NAME));
 
@@ -156,19 +164,20 @@ public void testLeadingWhitespaces() throws IOException, ConfigInvalidException
 		final ByteArrayOutputStream bos2 = new ByteArrayOutputStream();
 		bos2.write(" \n\t".getBytes());
 		bos2.write(CONTENT2.getBytes());
-		assertArrayEquals(bos2.toByteArray(), IO.readFully(file));
+		assertArrayEquals(bos2.toByteArray(), IO.readFully(file.toFile()));
 	}
 
 	@Test
 	public void testIncludeAbsolute()
 			throws IOException, ConfigInvalidException {
-		final File includedFile = createFile(CONTENT1.getBytes());
+		final Path includedFile = createFile(CONTENT1.getBytes());
 		final ByteArrayOutputStream bos = new ByteArrayOutputStream();
 		bos.write("[include]\npath=".getBytes());
-		bos.write(pathToString(includedFile).getBytes());
+		bos.write(pathToString(includedFile.toFile()).getBytes());
 
-		final File file = createFile(bos.toByteArray());
-		final FileBasedConfig config = new FileBasedConfig(file, FS.DETECTED);
+		final Path file = createFile(bos.toByteArray());
+		final FileBasedConfig config = new FileBasedConfig(file.toFile(),
+				FS.DETECTED);
 		config.load();
 		assertEquals(ALICE, config.getString(USER, null, NAME));
 	}
@@ -176,13 +185,14 @@ public void testIncludeAbsolute()
 	@Test
 	public void testIncludeRelativeDot()
 			throws IOException, ConfigInvalidException {
-		final File includedFile = createFile(CONTENT1.getBytes(), "dir1");
+		final Path includedFile = createFile(CONTENT1.getBytes(), "dir1");
 		final ByteArrayOutputStream bos = new ByteArrayOutputStream();
 		bos.write("[include]\npath=".getBytes());
-		bos.write(("./" + includedFile.getName()).getBytes());
+		bos.write(("./" + includedFile.getFileName()).getBytes());
 
-		final File file = createFile(bos.toByteArray(), "dir1");
-		final FileBasedConfig config = new FileBasedConfig(file, FS.DETECTED);
+		final Path file = createFile(bos.toByteArray(), "dir1");
+		final FileBasedConfig config = new FileBasedConfig(file.toFile(),
+				FS.DETECTED);
 		config.load();
 		assertEquals(ALICE, config.getString(USER, null, NAME));
 	}
@@ -190,14 +200,15 @@ public void testIncludeRelativeDot()
 	@Test
 	public void testIncludeRelativeDotDot()
 			throws IOException, ConfigInvalidException {
-		final File includedFile = createFile(CONTENT1.getBytes(), "dir1");
+		final Path includedFile = createFile(CONTENT1.getBytes(), "dir1");
 		final ByteArrayOutputStream bos = new ByteArrayOutputStream();
 		bos.write("[include]\npath=".getBytes());
-		bos.write(("../" + includedFile.getParentFile().getName() + "/"
-				+ includedFile.getName()).getBytes());
+		bos.write(("../" + includedFile.getParent().getFileName() + "/"
+				+ includedFile.getFileName()).getBytes());
 
-		final File file = createFile(bos.toByteArray(), "dir2");
-		final FileBasedConfig config = new FileBasedConfig(file, FS.DETECTED);
+		final Path file = createFile(bos.toByteArray(), "dir2");
+		final FileBasedConfig config = new FileBasedConfig(file.toFile(),
+				FS.DETECTED);
 		config.load();
 		assertEquals(ALICE, config.getString(USER, null, NAME));
 	}
@@ -205,13 +216,14 @@ public void testIncludeRelativeDotDot()
 	@Test
 	public void testIncludeRelativeDotDotNotFound()
 			throws IOException, ConfigInvalidException {
-		final File includedFile = createFile(CONTENT1.getBytes());
+		final Path includedFile = createFile(CONTENT1.getBytes());
 		final ByteArrayOutputStream bos = new ByteArrayOutputStream();
 		bos.write("[include]\npath=".getBytes());
-		bos.write(("../" + includedFile.getName()).getBytes());
+		bos.write(("../" + includedFile.getFileName()).getBytes());
 
-		final File file = createFile(bos.toByteArray());
-		final FileBasedConfig config = new FileBasedConfig(file, FS.DETECTED);
+		final Path file = createFile(bos.toByteArray());
+		final FileBasedConfig config = new FileBasedConfig(file.toFile(),
+				FS.DETECTED);
 		config.load();
 		assertEquals(null, config.getString(USER, null, NAME));
 	}
@@ -219,30 +231,31 @@ public void testIncludeRelativeDotDotNotFound()
 	@Test
 	public void testIncludeWithTilde()
 			throws IOException, ConfigInvalidException {
-		final File includedFile = createFile(CONTENT1.getBytes(), "home");
+		final Path includedFile = createFile(CONTENT1.getBytes(), "home");
 		final ByteArrayOutputStream bos = new ByteArrayOutputStream();
 		bos.write("[include]\npath=".getBytes());
-		bos.write(("~/" + includedFile.getName()).getBytes());
+		bos.write(("~/" + includedFile.getFileName()).getBytes());
 
-		final File file = createFile(bos.toByteArray(), "repo");
+		final Path file = createFile(bos.toByteArray(), "repo");
 		final FS fs = FS.DETECTED.newInstance();
-		fs.setUserHome(includedFile.getParentFile());
+		fs.setUserHome(includedFile.getParent().toFile());
 
-		final FileBasedConfig config = new FileBasedConfig(file, fs);
+		final FileBasedConfig config = new FileBasedConfig(file.toFile(), fs);
 		config.load();
 		assertEquals(ALICE, config.getString(USER, null, NAME));
 	}
 
-	private File createFile(byte[] content) throws IOException {
+	private Path createFile(byte[] content) throws IOException {
 		return createFile(content, null);
 	}
 
-	private File createFile(byte[] content, String subdir) throws IOException {
-		File dir = subdir != null ? new File(trash, subdir) : trash;
-		dir.mkdirs();
+	private Path createFile(byte[] content, String subdir) throws IOException {
+		Path dir = subdir != null ? trash.resolve(subdir) : trash;
+		Files.createDirectories(dir);
 
-		File f = File.createTempFile(getClass().getName(), null, dir);
-		try (FileOutputStream os = new FileOutputStream(f, true)) {
+		Path f = Files.createTempFile(dir, getClass().getName(), null);
+		try (OutputStream os = Files.newOutputStream(f,
+				StandardOpenOption.APPEND)) {
 			os.write(content);
 		}
 		return f;
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleAddTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleAddTest.java
index 5780973..1ff64a2 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleAddTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleAddTest.java
@@ -136,7 +136,49 @@ public void addSubmodule() throws Exception {
 			}
 
 			SubmoduleWalk generator = SubmoduleWalk.forIndex(db);
+			generator.loadModulesConfig();
 			assertTrue(generator.next());
+			assertEquals(path, generator.getModuleName());
+			assertEquals(path, generator.getPath());
+			assertEquals(commit, generator.getObjectId());
+			assertEquals(uri, generator.getModulesUrl());
+			assertEquals(path, generator.getModulesPath());
+			assertEquals(uri, generator.getConfigUrl());
+			try (Repository subModRepo = generator.getRepository()) {
+				assertNotNull(subModRepo);
+				assertEquals(subCommit, commit);
+			}
+
+			Status status = Git.wrap(db).status().call();
+			assertTrue(status.getAdded().contains(Constants.DOT_GIT_MODULES));
+			assertTrue(status.getAdded().contains(path));
+		}
+	}
+
+	@Test
+	public void addSubmoduleWithName() throws Exception {
+		try (Git git = new Git(db)) {
+			writeTrashFile("file.txt", "content");
+			git.add().addFilepattern("file.txt").call();
+			RevCommit commit = git.commit().setMessage("create file").call();
+
+			SubmoduleAddCommand command = new SubmoduleAddCommand(db);
+			String name = "testsub";
+			command.setName(name);
+			String path = "sub";
+			command.setPath(path);
+			String uri = db.getDirectory().toURI().toString();
+			command.setURI(uri);
+			ObjectId subCommit;
+			try (Repository repo = command.call()) {
+				assertNotNull(repo);
+				subCommit = repo.resolve(Constants.HEAD);
+			}
+
+			SubmoduleWalk generator = SubmoduleWalk.forIndex(db);
+			generator.loadModulesConfig();
+			assertTrue(generator.next());
+			assertEquals(name, generator.getModuleName());
 			assertEquals(path, generator.getPath());
 			assertEquals(commit, generator.getObjectId());
 			assertEquals(uri, generator.getModulesUrl());
@@ -187,16 +229,13 @@ public void apply(DirCacheEntry ent) {
 	public void addSubmoduleWithInvalidPath() throws Exception {
 		SubmoduleAddCommand command = new SubmoduleAddCommand(db);
 		command.setPath("-invalid-path");
-		// TODO(ms) set name to a valid value in 5.1.0 and adapt expected
-		// message below
+		command.setName("sub");
 		command.setURI("http://example.com/repo/x.git");
 		try {
 			command.call().close();
 			fail("Exception not thrown");
 		} catch (IllegalArgumentException e) {
-			// TODO(ms) should check for submodule path, but can't set name
-			// before 5.1.0
-			assertEquals("Invalid submodule name '-invalid-path'",
+			assertEquals("Invalid submodule path '-invalid-path'",
 					e.getMessage());
 		}
 	}
@@ -299,4 +338,18 @@ public void addSubmoduleWithExistingSubmoduleDefined() throws Exception {
 					ConfigConstants.CONFIG_KEY_URL));
 		}
 	}
+
+	@Test
+	public void denySubmoduleWithDotDot() throws Exception {
+		SubmoduleAddCommand command = new SubmoduleAddCommand(db);
+		command.setName("dir/../");
+		command.setPath("sub");
+		command.setURI(db.getDirectory().toURI().toString());
+		try {
+			command.call();
+			fail();
+		} catch (IllegalArgumentException e) {
+			// Expected
+		}
+	}
 }
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleDeinitTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleDeinitTest.java
index df4b963..815ce9b 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleDeinitTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleDeinitTest.java
@@ -136,10 +136,12 @@ public void dirtySubmoduleBecauseNewCommit() throws Exception {
 		generator.next();
 
 		//want to create a commit inside the repo...
-		Repository submoduleLocalRepo = generator.getRepository();
-		JGitTestUtil.writeTrashFile(submoduleLocalRepo, "file.txt", "new data");
-		Git.wrap(submoduleLocalRepo).commit().setAll(true).setMessage("local commit").call();
-
+		try (Repository submoduleLocalRepo = generator.getRepository()) {
+			JGitTestUtil.writeTrashFile(submoduleLocalRepo, "file.txt",
+					"new data");
+			Git.wrap(submoduleLocalRepo).commit().setAll(true)
+					.setMessage("local commit").call();
+		}
 		SubmoduleDeinitResult result = runDeinit(new SubmoduleDeinitCommand(db).addPath("sub"));
 		assertEquals(path, result.getPath());
 		assertEquals(SubmoduleDeinitCommand.SubmoduleDeinitStatus.DIRTY, result.getStatus());
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/BundleWriterTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/BundleWriterTest.java
index d30ac84..7b31bfa 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/BundleWriterTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/BundleWriterTest.java
@@ -45,7 +45,7 @@
 
 package org.eclipse.jgit.transport;
 
-import static org.eclipse.jgit.lib.Constants.CHARSET;
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
@@ -199,7 +199,8 @@ public void testCustomObjectReader() throws Exception {
 			Ref ref = repo.exactRef(refName);
 			assertNotNull(ref);
 			assertEquals(id, ref.getObjectId());
-			assertEquals(data, new String(repo.open(id, OBJ_BLOB).getBytes(), CHARSET));
+			assertEquals(data,
+					new String(repo.open(id, OBJ_BLOB).getBytes(), UTF_8));
 		}
 	}
 
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/NetRCTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/NetRCTest.java
index 4e5d56a..2a4e492 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/NetRCTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/NetRCTest.java
@@ -42,7 +42,7 @@
 
 package org.eclipse.jgit.transport;
 
-import static org.eclipse.jgit.lib.Constants.CHARSET;
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
 
@@ -76,7 +76,7 @@ public void setUp() throws Exception {
 
 	private void config(String data) throws IOException {
 		try (OutputStreamWriter fw = new OutputStreamWriter(
-				new FileOutputStream(configFile), CHARSET)) {
+				new FileOutputStream(configFile), UTF_8)) {
 			fw.write(data);
 		}
 	}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/OpenSshConfigTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/OpenSshConfigTest.java
index abf80ec..0358718 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/OpenSshConfigTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/OpenSshConfigTest.java
@@ -43,7 +43,7 @@
 
 package org.eclipse.jgit.transport;
 
-import static org.eclipse.jgit.lib.Constants.CHARSET;
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -57,10 +57,13 @@
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.OutputStreamWriter;
+import java.time.Instant;
+import java.util.concurrent.TimeUnit;
 
 import org.eclipse.jgit.junit.RepositoryTestCase;
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.transport.OpenSshConfig.Host;
+import org.eclipse.jgit.util.FS;
 import org.eclipse.jgit.util.FileUtils;
 import org.eclipse.jgit.util.SystemReader;
 import org.junit.Before;
@@ -91,13 +94,19 @@ public void setUp() throws Exception {
 	}
 
 	private void config(String data) throws IOException {
-		long lastMtime = configFile.lastModified();
+		FS fs = FS.DETECTED;
+		long resolution = FS.getFileStoreAttributes(configFile.toPath())
+				.getFsTimestampResolution().toNanos();
+		Instant lastMtime = fs.lastModifiedInstant(configFile);
 		do {
 			try (final OutputStreamWriter fw = new OutputStreamWriter(
-					new FileOutputStream(configFile), CHARSET)) {
+					new FileOutputStream(configFile), UTF_8)) {
 				fw.write(data);
+				TimeUnit.NANOSECONDS.sleep(resolution);
+			} catch (InterruptedException e) {
+				Thread.interrupted();
 			}
-		} while (lastMtime == configFile.lastModified());
+		} while (lastMtime.equals(fs.lastModifiedInstant(configFile)));
 	}
 
 	@Test
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PacketLineOutTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PacketLineOutTest.java
index 391a701..ad8ae42 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PacketLineOutTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PacketLineOutTest.java
@@ -43,6 +43,7 @@
 
 package org.eclipse.jgit.transport;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.fail;
 
@@ -50,7 +51,6 @@
 import java.io.IOException;
 import java.io.OutputStream;
 
-import org.eclipse.jgit.lib.Constants;
 import org.junit.Before;
 import org.junit.Test;
 
@@ -173,8 +173,8 @@ public void flush() throws IOException {
 		assertEquals(1, flushCnt[0]);
 	}
 
-	private void assertBuffer(String exp) throws IOException {
+	private void assertBuffer(String exp) {
 		assertEquals(exp, new String(rawOut.toByteArray(),
-				Constants.CHARACTER_ENCODING));
+				UTF_8));
 	}
 }
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/ProtocolV2ParserTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/ProtocolV2ParserTest.java
new file mode 100644
index 0000000..bf67d46
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/ProtocolV2ParserTest.java
@@ -0,0 +1,321 @@
+/*
+ * Copyright (C) 2018, Google LLC.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ *   copyright notice, this list of conditions and the following
+ *   disclaimer in the documentation and/or other materials provided
+ *   with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ *   names of its contributors may be used to endorse or promote
+ *   products derived from this software without specific prior
+ *   written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.eclipse.jgit.transport;
+
+import static org.hamcrest.Matchers.hasItems;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import org.eclipse.jgit.errors.PackProtocolException;
+import org.eclipse.jgit.internal.storage.dfs.DfsRepositoryDescription;
+import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
+import org.eclipse.jgit.junit.TestRepository;
+import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+public class ProtocolV2ParserTest {
+
+	@Rule
+	public ExpectedException thrown = ExpectedException.none();
+
+	private TestRepository<InMemoryRepository> testRepo;
+
+	@Before
+	public void setUp() throws Exception {
+		testRepo = new TestRepository<>(newRepo("protocol-v2-parser-test"));
+	}
+
+	private static InMemoryRepository newRepo(String name) {
+		return new InMemoryRepository(new DfsRepositoryDescription(name));
+	}
+
+	private static class ConfigBuilder {
+
+		private boolean allowRefInWant;
+
+		private boolean allowFilter;
+
+		private ConfigBuilder() {
+		}
+
+		static ConfigBuilder start() {
+			return new ConfigBuilder();
+		}
+
+		static TransferConfig getDefault() {
+			return start().done();
+		}
+
+		ConfigBuilder allowRefInWant() {
+			allowRefInWant = true;
+			return this;
+		}
+
+		ConfigBuilder allowFilter() {
+			allowFilter = true;
+			return this;
+		}
+
+		TransferConfig done() {
+			Config rc = new Config();
+			rc.setBoolean("uploadpack", null, "allowrefinwant", allowRefInWant);
+			rc.setBoolean("uploadpack", null, "allowfilter", allowFilter);
+			return new TransferConfig(rc);
+		}
+	}
+
+	/*
+	 * Convert the input lines to the PacketLine that the parser reads.
+	 */
+	private static PacketLineIn formatAsPacketLine(String... inputLines)
+			throws IOException {
+		ByteArrayOutputStream send = new ByteArrayOutputStream();
+		PacketLineOut pckOut = new PacketLineOut(send);
+		for (String line : inputLines) {
+			if (line == PacketLineIn.END) {
+				pckOut.end();
+			} else if (line == PacketLineIn.DELIM) {
+				pckOut.writeDelim();
+			} else {
+				pckOut.writeString(line);
+			}
+		}
+
+		return new PacketLineIn(new ByteArrayInputStream(send.toByteArray()));
+	}
+
+	private static List<String> objIdsAsStrings(Collection<ObjectId> objIds) {
+		// TODO(ifrade) Translate this to a matcher, so it would read as
+		// assertThat(req.wantsIds(), hasObjectIds("...", "..."))
+		return objIds.stream().map(ObjectId::name).collect(Collectors.toList());
+	}
+
+	/*
+	 * Succesful fetch with the basic core commands of the protocol.
+	 */
+	@Test
+	public void testFetchBasicArguments()
+			throws PackProtocolException, IOException {
+		PacketLineIn pckIn = formatAsPacketLine(
+				PacketLineIn.DELIM,
+				"thin-pack", "no-progress", "include-tag", "ofs-delta",
+				"want 4624442d68ee402a94364191085b77137618633e",
+				"want f900c8326a43303685c46b279b9f70411bff1a4b",
+				"have 554f6e41067b9e3e565b6988a8294fac1cb78f4b",
+				"have abc760ab9ad72f08209943251b36cb886a578f87", "done",
+				PacketLineIn.END);
+		ProtocolV2Parser parser = new ProtocolV2Parser(
+				ConfigBuilder.getDefault());
+		FetchV2Request request = parser.parseFetchRequest(pckIn);
+		assertTrue(request.getOptions()
+				.contains(GitProtocolConstants.OPTION_THIN_PACK));
+		assertTrue(request.getOptions()
+				.contains(GitProtocolConstants.OPTION_NO_PROGRESS));
+		assertTrue(request.getOptions()
+				.contains(GitProtocolConstants.OPTION_INCLUDE_TAG));
+		assertTrue(request.getOptions()
+				.contains(GitProtocolConstants.CAPABILITY_OFS_DELTA));
+		assertThat(objIdsAsStrings(request.getWantsIds()),
+				hasItems("4624442d68ee402a94364191085b77137618633e",
+						"f900c8326a43303685c46b279b9f70411bff1a4b"));
+		assertThat(objIdsAsStrings(request.getPeerHas()),
+				hasItems("554f6e41067b9e3e565b6988a8294fac1cb78f4b",
+						"abc760ab9ad72f08209943251b36cb886a578f87"));
+		assertTrue(request.getWantedRefs().isEmpty());
+		assertTrue(request.wasDoneReceived());
+	}
+
+	@Test
+	public void testFetchWithShallow_deepen() throws IOException {
+		PacketLineIn pckIn = formatAsPacketLine(
+				PacketLineIn.DELIM,
+				"deepen 15",
+				"deepen-relative",
+				"shallow 28274d02c489f4c7e68153056e9061a46f62d7a0",
+				"shallow 145e683b229dcab9d0e2ccb01b386f9ecc17d29d",
+				PacketLineIn.END);
+		ProtocolV2Parser parser = new ProtocolV2Parser(
+				ConfigBuilder.getDefault());
+		FetchV2Request request = parser.parseFetchRequest(pckIn);
+		assertThat(objIdsAsStrings(request.getClientShallowCommits()),
+				hasItems("28274d02c489f4c7e68153056e9061a46f62d7a0",
+						"145e683b229dcab9d0e2ccb01b386f9ecc17d29d"));
+		assertTrue(request.getDeepenNotRefs().isEmpty());
+		assertEquals(15, request.getDepth());
+		assertTrue(request.getOptions()
+				.contains(GitProtocolConstants.OPTION_DEEPEN_RELATIVE));
+	}
+
+	@Test
+	public void testFetchWithShallow_deepenNot() throws IOException {
+		PacketLineIn pckIn = formatAsPacketLine(PacketLineIn.DELIM,
+				"shallow 28274d02c489f4c7e68153056e9061a46f62d7a0",
+				"shallow 145e683b229dcab9d0e2ccb01b386f9ecc17d29d",
+				"deepen-not a08595f76159b09d57553e37a5123f1091bb13e7",
+				PacketLineIn.END);
+		ProtocolV2Parser parser = new ProtocolV2Parser(
+				ConfigBuilder.getDefault());
+		FetchV2Request request = parser.parseFetchRequest(pckIn);
+		assertThat(objIdsAsStrings(request.getClientShallowCommits()),
+				hasItems("28274d02c489f4c7e68153056e9061a46f62d7a0",
+						"145e683b229dcab9d0e2ccb01b386f9ecc17d29d"));
+		assertThat(request.getDeepenNotRefs(),
+				hasItems("a08595f76159b09d57553e37a5123f1091bb13e7"));
+	}
+
+	@Test
+	public void testFetchWithShallow_deepenSince() throws IOException {
+		PacketLineIn pckIn = formatAsPacketLine(PacketLineIn.DELIM,
+				"shallow 28274d02c489f4c7e68153056e9061a46f62d7a0",
+				"shallow 145e683b229dcab9d0e2ccb01b386f9ecc17d29d",
+				"deepen-since 123123123",
+				PacketLineIn.END);
+		ProtocolV2Parser parser = new ProtocolV2Parser(
+				ConfigBuilder.getDefault());
+		FetchV2Request request = parser.parseFetchRequest(pckIn);
+		assertThat(objIdsAsStrings(request.getClientShallowCommits()),
+				hasItems("28274d02c489f4c7e68153056e9061a46f62d7a0",
+						"145e683b229dcab9d0e2ccb01b386f9ecc17d29d"));
+		assertEquals(123123123, request.getDeepenSince());
+	}
+
+	@Test
+	public void testFetchWithNoneFilter() throws IOException {
+		PacketLineIn pckIn = formatAsPacketLine(PacketLineIn.DELIM,
+				"filter blob:none",
+				PacketLineIn.END);
+		ProtocolV2Parser parser = new ProtocolV2Parser(
+				ConfigBuilder.start().allowFilter().done());
+		FetchV2Request request = parser.parseFetchRequest(pckIn);
+		assertEquals(0, request.getFilterBlobLimit());
+	}
+
+	@Test
+	public void testFetchWithBlobSizeFilter() throws IOException {
+		PacketLineIn pckIn = formatAsPacketLine(PacketLineIn.DELIM,
+				"filter blob:limit=15",
+				PacketLineIn.END);
+		ProtocolV2Parser parser = new ProtocolV2Parser(
+				ConfigBuilder.start().allowFilter().done());
+		FetchV2Request request = parser.parseFetchRequest(pckIn);
+		assertEquals(15, request.getFilterBlobLimit());
+	}
+
+	@Test
+	public void testFetchMustNotHaveMultipleFilters() throws IOException {
+		thrown.expect(PackProtocolException.class);
+		PacketLineIn pckIn = formatAsPacketLine(PacketLineIn.DELIM,
+				"filter blob:none",
+				"filter blob:limit=12",
+				PacketLineIn.END);
+		ProtocolV2Parser parser = new ProtocolV2Parser(
+				ConfigBuilder.start().allowFilter().done());
+		FetchV2Request request = parser.parseFetchRequest(pckIn);
+		assertEquals(0, request.getFilterBlobLimit());
+	}
+
+	@Test
+	public void testFetchFilterWithoutAllowFilter() throws IOException {
+		thrown.expect(PackProtocolException.class);
+		PacketLineIn pckIn = formatAsPacketLine(PacketLineIn.DELIM,
+				"filter blob:limit=12", PacketLineIn.END);
+		ProtocolV2Parser parser = new ProtocolV2Parser(
+				ConfigBuilder.getDefault());
+		parser.parseFetchRequest(pckIn);
+	}
+
+	@Test
+	public void testFetchWithRefInWant() throws Exception {
+		RevCommit one = testRepo.commit().message("1").create();
+		RevCommit two = testRepo.commit().message("2").create();
+		testRepo.update("branchA", one);
+		testRepo.update("branchB", two);
+
+		PacketLineIn pckIn = formatAsPacketLine(PacketLineIn.DELIM,
+				"want e4980cdc48cfa1301493ca94eb70523f6788b819",
+				"want-ref refs/heads/branchA",
+				PacketLineIn.END);
+		ProtocolV2Parser parser = new ProtocolV2Parser(
+				ConfigBuilder.start().allowRefInWant().done());
+
+		FetchV2Request request = parser.parseFetchRequest(pckIn);
+		assertEquals(1, request.getWantedRefs().size());
+		assertThat(request.getWantedRefs(), hasItems("refs/heads/branchA"));
+		assertEquals(1, request.getWantsIds().size());
+		assertThat(objIdsAsStrings(request.getWantsIds()),
+				hasItems("e4980cdc48cfa1301493ca94eb70523f6788b819"));
+	}
+
+	@Test
+	public void testFetchWithRefInWantUnknownRef() throws Exception {
+		PacketLineIn pckIn = formatAsPacketLine(PacketLineIn.DELIM,
+				"want e4980cdc48cfa1301493ca94eb70523f6788b819",
+				"want-ref refs/heads/branchC",
+				PacketLineIn.END);
+		ProtocolV2Parser parser = new ProtocolV2Parser(
+				ConfigBuilder.start().allowRefInWant().done());
+
+		RevCommit one = testRepo.commit().message("1").create();
+		RevCommit two = testRepo.commit().message("2").create();
+		testRepo.update("branchA", one);
+		testRepo.update("branchB", two);
+
+		FetchV2Request request = parser.parseFetchRequest(pckIn);
+		assertEquals(1, request.getWantedRefs().size());
+		assertThat(request.getWantedRefs(), hasItems("refs/heads/branchC"));
+	}
+
+}
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 0ffbe65..c959f6c 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
@@ -259,6 +259,7 @@ public void testSuccess() throws Exception {
 		try (TransportLocal t = newTransportLocalWithStrictValidation()) {
 			t.setPushThin(true);
 			r = t.push(PM, Collections.singleton(u));
+			dst.close();
 		}
 
 		assertNotNull("have result", r);
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/SideBandOutputStreamTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/SideBandOutputStreamTest.java
index 4d3e162..b6cf356 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/SideBandOutputStreamTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/SideBandOutputStreamTest.java
@@ -44,6 +44,7 @@
 package org.eclipse.jgit.transport;
 
 import static java.lang.Integer.valueOf;
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.eclipse.jgit.transport.SideBandOutputStream.CH_DATA;
 import static org.eclipse.jgit.transport.SideBandOutputStream.CH_ERROR;
 import static org.eclipse.jgit.transport.SideBandOutputStream.CH_PROGRESS;
@@ -59,7 +60,6 @@
 import java.text.MessageFormat;
 
 import org.eclipse.jgit.internal.JGitText;
-import org.eclipse.jgit.lib.Constants;
 import org.junit.Before;
 import org.junit.Test;
 
@@ -259,8 +259,7 @@ public void testConstructor_RejectsBadBufferSize() throws Exception {
 		}
 	}
 
-	private void assertBuffer(String exp) throws IOException {
-		assertEquals(exp, new String(rawOut.toByteArray(),
-				Constants.CHARACTER_ENCODING));
+	private void assertBuffer(String exp) {
+		assertEquals(exp, new String(rawOut.toByteArray(), UTF_8));
 	}
 }
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/TestProtocolTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/TestProtocolTest.java
index 86c92bb..953c9fc 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
@@ -77,6 +77,7 @@ public class TestProtocolTest {
 			"+refs/heads/master:refs/heads/master");
 
 	private static final int HAVES_PER_ROUND = 32;
+	private static final int MAX_HAVES = 256;
 
 	private static class User {
 		private final String name;
@@ -187,7 +188,7 @@ public void testFullNegotiation() throws Exception {
 	}
 
 	@Test
-	public void testMinimalNegotiation() throws Exception {
+	public void testMaxHaves() throws Exception {
 		TestProtocol<User> proto = registerDefault();
 		URIish uri = proto.register(new User("user"), remote.getRepository());
 
@@ -200,28 +201,13 @@ public void testMinimalNegotiation() throws Exception {
 		RevCommit master = remote.branch("master").commit()
 				.add("readme.txt", "unique commit").create();
 
-		TestProtocol.setFetchConfig(new FetchConfig(true, true));
+		TestProtocol.setFetchConfig(new FetchConfig(true, MAX_HAVES));
 		try (Git git = new Git(local.getRepository())) {
 			assertNull(local.getRepository().exactRef("refs/heads/master"));
 			git.fetch().setRemote(uri.toString()).setRefSpecs(MASTER).call();
 			assertEquals(master, local.getRepository()
 					.exactRef("refs/heads/master").getObjectId());
-			assertTrue(havesCount <= 2 * HAVES_PER_ROUND);
-
-			// Update the remote master and add local branches for 5 more rounds
-			// of negotiation, where the local branch commits have newer
-			// timestamps. Negotiation should send 5 rounds for those newer
-			// branches before getting to the round that sends its stale version
-			// of master.
-			master = remote.branch("master").commit().parent(master).create();
-			local.tick(2 * HAVES_PER_ROUND);
-			for (int i = 0; i < 5 * HAVES_PER_ROUND; i++) {
-				local.branch("local-" + i).commit().create();
-			}
-			git.fetch().setRemote(uri.toString()).setRefSpecs(MASTER).call();
-			assertEquals(master, local.getRepository()
-					.exactRef("refs/heads/master").getObjectId());
-			assertEquals(6 * HAVES_PER_ROUND, havesCount);
+			assertTrue(havesCount <= MAX_HAVES);
 		}
 	}
 
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 ef083da..317ac32 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
@@ -1,20 +1,25 @@
 package org.eclipse.jgit.transport;
 
+import static org.hamcrest.Matchers.containsString;
 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.assertThat;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Map;
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.StringWriter;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
 import org.eclipse.jgit.errors.PackProtocolException;
 import org.eclipse.jgit.errors.TransportException;
 import org.eclipse.jgit.internal.storage.dfs.DfsGarbageCollector;
@@ -29,8 +34,8 @@
 import org.eclipse.jgit.lib.TextProgressMonitor;
 import org.eclipse.jgit.revwalk.RevBlob;
 import org.eclipse.jgit.revwalk.RevCommit;
-import org.eclipse.jgit.revwalk.RevTree;
 import org.eclipse.jgit.revwalk.RevTag;
+import org.eclipse.jgit.revwalk.RevTree;
 import org.eclipse.jgit.transport.UploadPack.RequestPolicy;
 import org.eclipse.jgit.transport.resolver.ServiceNotAuthorizedException;
 import org.eclipse.jgit.transport.resolver.ServiceNotEnabledException;
@@ -227,6 +232,44 @@ public UploadPack create(Object req, Repository db)
 	}
 
 	@Test
+	public void testFetchExplicitBlobWithFilter() throws Exception {
+		InMemoryRepository server2 = newRepo("server2");
+		TestRepository<InMemoryRepository> remote2 =
+				new TestRepository<>(server2);
+		RevBlob blob1 = remote2.blob("foobar");
+		RevBlob blob2 = remote2.blob("fooba");
+		RevTree tree = remote2.tree(remote2.file("1", blob1),
+				remote2.file("2", blob2));
+		RevCommit commit = remote2.commit(tree);
+		remote2.update("master", commit);
+		remote2.update("a_blob", blob1);
+
+		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;
+					}
+				}, null);
+		uri = testProtocol.register(ctx, server2);
+
+		try (Transport tn = testProtocol.open(uri, client, "server2")) {
+			tn.setFilterBlobLimit(0);
+			tn.fetch(NullProgressMonitor.INSTANCE, Arrays.asList(
+						new RefSpec(commit.name()),
+						new RefSpec(blob1.name())));
+			assertTrue(client.hasObject(tree.toObjectId()));
+			assertTrue(client.hasObject(blob1.toObjectId()));
+			assertFalse(client.hasObject(blob2.toObjectId()));
+		}
+	}
+
+	@Test
 	public void testFetchWithBlobLimitFilter() throws Exception {
 		InMemoryRepository server2 = newRepo("server2");
 		TestRepository<InMemoryRepository> remote2 =
@@ -262,6 +305,47 @@ public UploadPack create(Object req, Repository db)
 	}
 
 	@Test
+	public void testFetchExplicitBlobWithFilterAndBitmaps() throws Exception {
+		InMemoryRepository server2 = newRepo("server2");
+		TestRepository<InMemoryRepository> remote2 =
+				new TestRepository<>(server2);
+		RevBlob blob1 = remote2.blob("foobar");
+		RevBlob blob2 = remote2.blob("fooba");
+		RevTree tree = remote2.tree(remote2.file("1", blob1),
+				remote2.file("2", blob2));
+		RevCommit commit = remote2.commit(tree);
+		remote2.update("master", commit);
+		remote2.update("a_blob", blob1);
+
+		server2.getConfig().setBoolean("uploadpack", null, "allowfilter", true);
+
+		// generate bitmaps
+		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;
+					}
+				}, null);
+		uri = testProtocol.register(ctx, server2);
+
+		try (Transport tn = testProtocol.open(uri, client, "server2")) {
+			tn.setFilterBlobLimit(0);
+			tn.fetch(NullProgressMonitor.INSTANCE, Arrays.asList(
+						new RefSpec(commit.name()),
+						new RefSpec(blob1.name())));
+			assertTrue(client.hasObject(blob1.toObjectId()));
+			assertFalse(client.hasObject(blob2.toObjectId()));
+		}
+	}
+
+	@Test
 	public void testFetchWithBlobLimitFilterAndBitmaps() throws Exception {
 		InMemoryRepository server2 = newRepo("server2");
 		TestRepository<InMemoryRepository> remote2 =
@@ -340,7 +424,8 @@ public UploadPack create(Object req, Repository db)
 	 * and returns UploadPack's output stream.
 	 */
 	private ByteArrayInputStream uploadPackV2Setup(RequestPolicy requestPolicy,
-			RefFilter refFilter, String... inputLines) throws Exception {
+			RefFilter refFilter, ProtocolV2Hook hook, String... inputLines)
+			throws Exception {
 
 		ByteArrayOutputStream send = new ByteArrayOutputStream();
 		PacketLineOut pckOut = new PacketLineOut(send);
@@ -361,6 +446,9 @@ private ByteArrayInputStream uploadPackV2Setup(RequestPolicy requestPolicy,
 		if (refFilter != null)
 			up.setRefFilter(refFilter);
 		up.setExtraParameters(Sets.of("version=2"));
+		if (hook != null) {
+			up.setProtocolV2Hook(hook);
+		}
 
 		ByteArrayOutputStream recv = new ByteArrayOutputStream();
 		up.upload(new ByteArrayInputStream(send.toByteArray()), recv, null);
@@ -374,9 +462,10 @@ private ByteArrayInputStream uploadPackV2Setup(RequestPolicy requestPolicy,
 	 * advertisement by the server.
 	 */
 	private ByteArrayInputStream uploadPackV2(RequestPolicy requestPolicy,
-			RefFilter refFilter, String... inputLines) throws Exception {
+			RefFilter refFilter, ProtocolV2Hook hook, String... inputLines)
+			throws Exception {
 		ByteArrayInputStream recvStream =
-			uploadPackV2Setup(requestPolicy, refFilter, inputLines);
+				uploadPackV2Setup(requestPolicy, refFilter, hook, inputLines);
 		PacketLineIn pckIn = new PacketLineIn(recvStream);
 
 		// drain capabilities
@@ -387,15 +476,33 @@ private ByteArrayInputStream uploadPackV2(RequestPolicy requestPolicy,
 	}
 
 	private ByteArrayInputStream uploadPackV2(String... inputLines) throws Exception {
-		return uploadPackV2(null, null, inputLines);
+		return uploadPackV2(null, null, null, inputLines);
+	}
+
+	private static class TestV2Hook implements ProtocolV2Hook {
+		private CapabilitiesV2Request capabilitiesRequest;
+
+		private LsRefsV2Request lsRefsRequest;
+
+		@Override
+		public void onCapabilities(CapabilitiesV2Request req) {
+			capabilitiesRequest = req;
+		}
+
+		@Override
+		public void onLsRefs(LsRefsV2Request req) {
+			lsRefsRequest = req;
+		}
 	}
 
 	@Test
 	public void testV2Capabilities() throws Exception {
+		TestV2Hook hook = new TestV2Hook();
 		ByteArrayInputStream recvStream =
-			uploadPackV2Setup(null, null, PacketLineIn.END);
+				uploadPackV2Setup(null, null, hook, PacketLineIn.END);
 		PacketLineIn pckIn = new PacketLineIn(recvStream);
 
+		assertThat(hook.capabilitiesRequest, notNullValue());
 		assertThat(pckIn.readString(), is("version 2"));
 		assertThat(
 			Arrays.asList(pckIn.readString(), pckIn.readString()),
@@ -413,7 +520,7 @@ public void testV2Capabilities() throws Exception {
 	public void testV2CapabilitiesAllowFilter() throws Exception {
 		server.getConfig().setBoolean("uploadpack", null, "allowfilter", true);
 		ByteArrayInputStream recvStream =
-			uploadPackV2Setup(null, null, PacketLineIn.END);
+				uploadPackV2Setup(null, null, null, PacketLineIn.END);
 		PacketLineIn pckIn = new PacketLineIn(recvStream);
 
 		assertThat(pckIn.readString(), is("version 2"));
@@ -426,12 +533,56 @@ public void testV2CapabilitiesAllowFilter() throws Exception {
 	}
 
 	@Test
-	@SuppressWarnings("boxing")
+	public void testV2CapabilitiesRefInWant() throws Exception {
+		server.getConfig().setBoolean("uploadpack", null, "allowrefinwant", true);
+		ByteArrayInputStream recvStream =
+				uploadPackV2Setup(null, null, null, PacketLineIn.END);
+		PacketLineIn pckIn = new PacketLineIn(recvStream);
+
+		assertThat(pckIn.readString(), is("version 2"));
+		assertThat(
+			Arrays.asList(pckIn.readString(), pckIn.readString()),
+			// TODO(jonathantanmy) This check overspecifies the
+			// order of the capabilities of "fetch".
+			hasItems("ls-refs", "fetch=ref-in-want shallow"));
+		assertTrue(pckIn.readString() == PacketLineIn.END);
+	}
+
+	@Test
+	public void testV2CapabilitiesRefInWantNotAdvertisedIfUnallowed() throws Exception {
+		server.getConfig().setBoolean("uploadpack", null, "allowrefinwant", false);
+		ByteArrayInputStream recvStream =
+				uploadPackV2Setup(null, null, null, PacketLineIn.END);
+		PacketLineIn pckIn = new PacketLineIn(recvStream);
+
+		assertThat(pckIn.readString(), is("version 2"));
+		assertThat(
+			Arrays.asList(pckIn.readString(), pckIn.readString()),
+			hasItems("ls-refs", "fetch=shallow"));
+		assertTrue(pckIn.readString() == PacketLineIn.END);
+	}
+
+	@Test
+	public void testV2CapabilitiesRefInWantNotAdvertisedIfAdvertisingForbidden() throws Exception {
+		server.getConfig().setBoolean("uploadpack", null, "allowrefinwant", true);
+		server.getConfig().setBoolean("uploadpack", null, "advertiserefinwant", false);
+		ByteArrayInputStream recvStream =
+				uploadPackV2Setup(null, null, null, PacketLineIn.END);
+		PacketLineIn pckIn = new PacketLineIn(recvStream);
+
+		assertThat(pckIn.readString(), is("version 2"));
+		assertThat(
+			Arrays.asList(pckIn.readString(), pckIn.readString()),
+			hasItems("ls-refs", "fetch=shallow"));
+		assertTrue(pckIn.readString() == PacketLineIn.END);
+	}
+
+	@Test
 	public void testV2EmptyRequest() throws Exception {
 		ByteArrayInputStream recvStream = uploadPackV2(PacketLineIn.END);
 		// Verify that there is nothing more after the capability
 		// advertisement.
-		assertThat(recvStream.available(), is(0));
+		assertEquals(0, recvStream.available());
 	}
 
 	@Test
@@ -442,9 +593,12 @@ public void testV2LsRefs() throws Exception {
 		RevTag tag = remote.tag("tag", tip);
 		remote.update("refs/tags/tag", tag);
 
-		ByteArrayInputStream recvStream = uploadPackV2("command=ls-refs\n", PacketLineIn.END);
+		TestV2Hook hook = new TestV2Hook();
+		ByteArrayInputStream recvStream = uploadPackV2(null, null, hook,
+				"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"));
@@ -579,6 +733,10 @@ private ReceivedPackStatistics parsePack(ByteArrayInputStream recvStream, Progre
 				new StringWriter(), NullOutputStream.INSTANCE);
 		PackParser pp = client.newObjectInserter().newPackParser(sb);
 		pp.parse(NullProgressMonitor.INSTANCE);
+
+		// Ensure that there is nothing left in the stream.
+		assertEquals(-1, recvStream.read());
+
 		return pp.getReceivedPackStatistics();
 	}
 
@@ -592,6 +750,7 @@ public void testV2FetchRequestPolicyAdvertised() throws Exception {
 		uploadPackV2(
 			RequestPolicy.ADVERTISED,
 			null,
+			null,
 			"command=fetch\n",
 			PacketLineIn.DELIM,
 			"want " + advertized.name() + "\n",
@@ -604,6 +763,7 @@ public void testV2FetchRequestPolicyAdvertised() throws Exception {
 		uploadPackV2(
 			RequestPolicy.ADVERTISED,
 			null,
+			null,
 			"command=fetch\n",
 			PacketLineIn.DELIM,
 			"want " + unadvertized.name() + "\n",
@@ -621,6 +781,7 @@ public void testV2FetchRequestPolicyReachableCommit() throws Exception {
 		uploadPackV2(
 			RequestPolicy.REACHABLE_COMMIT,
 			null,
+			null,
 			"command=fetch\n",
 			PacketLineIn.DELIM,
 			"want " + reachable.name() + "\n",
@@ -633,6 +794,7 @@ public void testV2FetchRequestPolicyReachableCommit() throws Exception {
 		uploadPackV2(
 			RequestPolicy.REACHABLE_COMMIT,
 			null,
+			null,
 			"command=fetch\n",
 			PacketLineIn.DELIM,
 			"want " + unreachable.name() + "\n",
@@ -649,6 +811,7 @@ public void testV2FetchRequestPolicyTip() throws Exception {
 		uploadPackV2(
 			RequestPolicy.TIP,
 			new RejectAllRefFilter(),
+			null,
 			"command=fetch\n",
 			PacketLineIn.DELIM,
 			"want " + tip.name() + "\n",
@@ -661,6 +824,7 @@ public void testV2FetchRequestPolicyTip() throws Exception {
 		uploadPackV2(
 			RequestPolicy.TIP,
 			new RejectAllRefFilter(),
+			null,
 			"command=fetch\n",
 			PacketLineIn.DELIM,
 			"want " + parentOfTip.name() + "\n",
@@ -678,6 +842,7 @@ public void testV2FetchRequestPolicyReachableCommitTip() throws Exception {
 		uploadPackV2(
 			RequestPolicy.REACHABLE_COMMIT_TIP,
 			new RejectAllRefFilter(),
+			null,
 			"command=fetch\n",
 			PacketLineIn.DELIM,
 			"want " + parentOfTip.name() + "\n",
@@ -690,6 +855,7 @@ public void testV2FetchRequestPolicyReachableCommitTip() throws Exception {
 		uploadPackV2(
 			RequestPolicy.REACHABLE_COMMIT_TIP,
 			new RejectAllRefFilter(),
+			null,
 			"command=fetch\n",
 			PacketLineIn.DELIM,
 			"want " + unreachable.name() + "\n",
@@ -704,6 +870,7 @@ public void testV2FetchRequestPolicyAny() throws Exception {
 		uploadPackV2(
 			RequestPolicy.ANY,
 			null,
+			null,
 			"command=fetch\n",
 			PacketLineIn.DELIM,
 			"want " + unreachable.name() + "\n",
@@ -1078,6 +1245,216 @@ public void testV2FetchFilterWhenNotAllowed() throws Exception {
 			PacketLineIn.END);
 	}
 
+	@Test
+	public void testV2FetchWantRefIfNotAllowed() throws Exception {
+		RevCommit one = remote.commit().message("1").create();
+		remote.update("one", one);
+
+		try {
+			uploadPackV2(
+				"command=fetch\n",
+				PacketLineIn.DELIM,
+				"want-ref refs/heads/one\n",
+				"done\n",
+				PacketLineIn.END);
+		} catch (PackProtocolException e) {
+			assertThat(
+				e.getMessage(),
+				containsString("unexpected want-ref refs/heads/one"));
+			return;
+		}
+		fail("expected PackProtocolException");
+	}
+
+	@Test
+	public void testV2FetchWantRef() throws Exception {
+		RevCommit one = remote.commit().message("1").create();
+		RevCommit two = remote.commit().message("2").create();
+		RevCommit three = remote.commit().message("3").create();
+		remote.update("one", one);
+		remote.update("two", two);
+		remote.update("three", three);
+
+		server.getConfig().setBoolean("uploadpack", null, "allowrefinwant", true);
+
+		ByteArrayInputStream recvStream = uploadPackV2(
+			"command=fetch\n",
+			PacketLineIn.DELIM,
+			"want-ref refs/heads/one\n",
+			"want-ref refs/heads/two\n",
+			"done\n",
+			PacketLineIn.END);
+		PacketLineIn pckIn = new PacketLineIn(recvStream);
+		assertThat(pckIn.readString(), is("wanted-refs"));
+		assertThat(
+				Arrays.asList(pckIn.readString(), pckIn.readString()),
+				hasItems(
+					one.toObjectId().getName() + " refs/heads/one",
+					two.toObjectId().getName() + " refs/heads/two"));
+		assertThat(pckIn.readString(), theInstance(PacketLineIn.DELIM));
+		assertThat(pckIn.readString(), is("packfile"));
+		parsePack(recvStream);
+
+		assertTrue(client.hasObject(one.toObjectId()));
+		assertTrue(client.hasObject(two.toObjectId()));
+		assertFalse(client.hasObject(three.toObjectId()));
+	}
+
+	@Test
+	public void testV2FetchBadWantRef() throws Exception {
+		RevCommit one = remote.commit().message("1").create();
+		remote.update("one", one);
+
+		server.getConfig().setBoolean("uploadpack", null, "allowrefinwant", true);
+
+		try {
+			uploadPackV2(
+				"command=fetch\n",
+				PacketLineIn.DELIM,
+				"want-ref refs/heads/one\n",
+				"want-ref refs/heads/nonExistentRef\n",
+				"done\n",
+				PacketLineIn.END);
+		} catch (PackProtocolException e) {
+			assertThat(
+				e.getMessage(),
+				containsString("Invalid ref name: refs/heads/nonExistentRef"));
+			return;
+		}
+		fail("expected PackProtocolException");
+	}
+
+	@Test
+	public void testV2FetchMixedWantRef() throws Exception {
+		RevCommit one = remote.commit().message("1").create();
+		RevCommit two = remote.commit().message("2").create();
+		RevCommit three = remote.commit().message("3").create();
+		remote.update("one", one);
+		remote.update("two", two);
+		remote.update("three", three);
+
+		server.getConfig().setBoolean("uploadpack", null, "allowrefinwant", true);
+
+		ByteArrayInputStream recvStream = uploadPackV2(
+			"command=fetch\n",
+			PacketLineIn.DELIM,
+			"want-ref refs/heads/one\n",
+			"want " + two.toObjectId().getName() + "\n",
+			"done\n",
+			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));
+		assertThat(pckIn.readString(), is("packfile"));
+		parsePack(recvStream);
+
+		assertTrue(client.hasObject(one.toObjectId()));
+		assertTrue(client.hasObject(two.toObjectId()));
+		assertFalse(client.hasObject(three.toObjectId()));
+	}
+
+	@Test
+	public void testV2FetchWantRefWeAlreadyHave() throws Exception {
+		RevCommit one = remote.commit().message("1").create();
+		remote.update("one", one);
+
+		server.getConfig().setBoolean("uploadpack", null, "allowrefinwant", true);
+
+		ByteArrayInputStream recvStream = uploadPackV2(
+			"command=fetch\n",
+			PacketLineIn.DELIM,
+			"want-ref refs/heads/one\n",
+			"have " + one.toObjectId().getName(),
+			"done\n",
+			PacketLineIn.END);
+		PacketLineIn pckIn = new PacketLineIn(recvStream);
+
+		// The client still needs to know the hash of the object that
+		// refs/heads/one points to, even though it already has the
+		// object ...
+		assertThat(pckIn.readString(), is("wanted-refs"));
+		assertThat(
+				pckIn.readString(),
+				is(one.toObjectId().getName() + " refs/heads/one"));
+		assertThat(pckIn.readString(), theInstance(PacketLineIn.DELIM));
+
+		// ... but the client does not need the object itself.
+		assertThat(pckIn.readString(), is("packfile"));
+		parsePack(recvStream);
+		assertFalse(client.hasObject(one.toObjectId()));
+	}
+
+	@Test
+	public void testV2FetchWantRefAndDeepen() throws Exception {
+		RevCommit parent = remote.commit().message("parent").create();
+		RevCommit child = remote.commit().message("x").parent(parent).create();
+		remote.update("branch1", child);
+
+		server.getConfig().setBoolean("uploadpack", null, "allowrefinwant", true);
+
+		ByteArrayInputStream recvStream = uploadPackV2(
+			"command=fetch\n",
+			PacketLineIn.DELIM,
+			"want-ref refs/heads/branch1\n",
+			"deepen 1\n",
+			"done\n",
+			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));
+		assertThat(pckIn.readString(), is("wanted-refs"));
+		assertThat(pckIn.readString(), is(child.toObjectId().getName() + " refs/heads/branch1"));
+		assertThat(pckIn.readString(), theInstance(PacketLineIn.DELIM));
+		assertThat(pckIn.readString(), is("packfile"));
+		parsePack(recvStream);
+		assertTrue(client.hasObject(child.toObjectId()));
+		assertFalse(client.hasObject(parent.toObjectId()));
+	}
+
+	@Test
+	public void testV2FetchMissingShallow() throws Exception {
+		RevCommit one = remote.commit().message("1").create();
+		RevCommit two = remote.commit().message("2").parent(one).create();
+		RevCommit three = remote.commit().message("3").parent(two).create();
+		remote.update("three", three);
+
+		server.getConfig().setBoolean("uploadpack", null, "allowrefinwant",
+				true);
+
+		ByteArrayInputStream recvStream = uploadPackV2("command=fetch\n",
+				PacketLineIn.DELIM,
+				"want-ref refs/heads/three\n",
+				"deepen 3",
+				"shallow 0123012301230123012301230123012301230123",
+				"shallow " + two.getName() + '\n',
+				"done\n",
+				PacketLineIn.END);
+		PacketLineIn pckIn = new PacketLineIn(recvStream);
+
+		assertThat(pckIn.readString(), is("shallow-info"));
+		assertThat(pckIn.readString(),
+				is("shallow " + one.toObjectId().getName()));
+		assertThat(pckIn.readString(),
+				is("unshallow " + two.toObjectId().getName()));
+		assertThat(pckIn.readString(), theInstance(PacketLineIn.DELIM));
+		assertThat(pckIn.readString(), is("wanted-refs"));
+		assertThat(pckIn.readString(),
+				is(three.toObjectId().getName() + " refs/heads/three"));
+		assertThat(pckIn.readString(), theInstance(PacketLineIn.DELIM));
+		assertThat(pckIn.readString(), is("packfile"));
+		parsePack(recvStream);
+
+		assertTrue(client.hasObject(one.toObjectId()));
+		assertTrue(client.hasObject(two.toObjectId()));
+		assertTrue(client.hasObject(three.toObjectId()));
+	}
+
 	private static class RejectAllRefFilter implements RefFilter {
 		@Override
 		public Map<String, Ref> filter(Map<String, Ref> refs) {
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/WalkEncryptionTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/WalkEncryptionTest.java
index 33e2529..f2fb022 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/WalkEncryptionTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/WalkEncryptionTest.java
@@ -43,7 +43,7 @@
 
 package org.eclipse.jgit.transport;
 
-import static org.eclipse.jgit.lib.Constants.CHARSET;
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.eclipse.jgit.transport.WalkEncryptionTest.Util.cryptoCipherListPBE;
 import static org.eclipse.jgit.transport.WalkEncryptionTest.Util.cryptoCipherListTrans;
 import static org.eclipse.jgit.transport.WalkEncryptionTest.Util.folderDelete;
@@ -360,7 +360,7 @@ static class Util {
 		 * @throws Exception
 		 */
 		static String textRead(File file) throws Exception {
-			return new String(Files.readAllBytes(file.toPath()), CHARSET);
+			return new String(Files.readAllBytes(file.toPath()), UTF_8);
 		}
 
 		/**
@@ -371,7 +371,7 @@ static String textRead(File file) throws Exception {
 		 * @throws Exception
 		 */
 		static void textWrite(File file, String text) throws Exception {
-			Files.write(file.toPath(), text.getBytes(CHARSET));
+			Files.write(file.toPath(), text.getBytes(UTF_8));
 		}
 
 		static void verifyFileContent(File fileOne, File fileTwo)
@@ -420,7 +420,7 @@ static String publicAddress() throws Exception {
 				c.setConnectTimeout(500);
 				c.setReadTimeout(500);
 				try (BufferedReader reader = new BufferedReader(
-						new InputStreamReader(c.getInputStream()))) {
+						new InputStreamReader(c.getInputStream(), UTF_8))) {
 					return reader.readLine();
 				}
 			} catch (UnknownHostException | SocketTimeoutException e) {
@@ -738,7 +738,7 @@ static void remoteVerify() throws Exception {
 			AmazonS3 s3 = new AmazonS3(props);
 			String file = JGIT_USER + "-" + UUID.randomUUID().toString();
 			String path = JGIT_REMOTE_DIR + "/" + file;
-			s3.put(bucket, path, file.getBytes(CHARSET));
+			s3.put(bucket, path, file.getBytes(UTF_8));
 			s3.delete(bucket, path);
 		}
 
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/AbstractTreeIteratorTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/AbstractTreeIteratorTest.java
index 934984f..8afd49a 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/AbstractTreeIteratorTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/AbstractTreeIteratorTest.java
@@ -66,7 +66,7 @@ private static String prefix(String path) {
 		return s > 0 ? path.substring(0, s) : "";
 	}
 
-	public class FakeTreeIterator extends WorkingTreeIterator {
+	public static class FakeTreeIterator extends WorkingTreeIterator {
 		public FakeTreeIterator(String pathName, FileMode fileMode) {
 			super(prefix(pathName), new Config().get(WorkingTreeOptions.KEY));
 			mode = fileMode.getBits();
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/CanonicalTreeParserTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/CanonicalTreeParserTest.java
index 6195e64..41bd355 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/CanonicalTreeParserTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/CanonicalTreeParserTest.java
@@ -43,6 +43,7 @@
 
 package org.eclipse.jgit.treewalk;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.eclipse.jgit.lib.FileMode.REGULAR_FILE;
 import static org.eclipse.jgit.lib.FileMode.SYMLINK;
 import static org.junit.Assert.assertEquals;
@@ -110,7 +111,7 @@ public void setUp() throws Exception {
 	}
 
 	private String path() {
-		return RawParseUtils.decode(Constants.CHARSET, ctp.path,
+		return RawParseUtils.decode(UTF_8, ctp.path,
 				ctp.pathOffset, ctp.pathLen);
 	}
 
@@ -370,7 +371,7 @@ public void testFreakingHugePathName() throws Exception {
 		final String name = b.toString();
 		ctp.reset(entry(m644, name, hash_a));
 		assertFalse(ctp.eof());
-		assertEquals(name, RawParseUtils.decode(Constants.CHARSET, ctp.path,
+		assertEquals(name, RawParseUtils.decode(UTF_8, ctp.path,
 				ctp.pathOffset, ctp.pathLen));
 	}
 
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 0e009b9..9a0e7d2 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
@@ -43,6 +43,7 @@
 
 package org.eclipse.jgit.treewalk;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
@@ -51,6 +52,7 @@
 import java.io.File;
 import java.io.IOException;
 import java.security.MessageDigest;
+import java.time.Instant;
 
 import org.eclipse.jgit.api.Git;
 import org.eclipse.jgit.api.ResetCommand.ResetType;
@@ -85,7 +87,7 @@
 public class FileTreeIteratorTest extends RepositoryTestCase {
 	private final String[] paths = { "a,", "a,b", "a/b", "a0b" };
 
-	private long[] mtime;
+	private Instant[] mtime;
 
 	@Override
 	@Before
@@ -98,11 +100,11 @@ public void setUp() throws Exception {
 		// This should stress the sorting code better than doing it in
 		// the correct order.
 		//
-		mtime = new long[paths.length];
+		mtime = new Instant[paths.length];
 		for (int i = paths.length - 1; i >= 0; i--) {
 			final String s = paths[i];
 			writeTrashFile(s, s);
-			mtime[i] = FS.DETECTED.lastModified(new File(trash, s));
+			mtime[i] = db.getFS().lastModifiedInstant(new File(trash, s));
 		}
 	}
 
@@ -198,7 +200,7 @@ public void testSimpleIterate() throws Exception {
 		assertEquals(FileMode.REGULAR_FILE.getBits(), top.mode);
 		assertEquals(paths[0], nameOf(top));
 		assertEquals(paths[0].length(), top.getEntryLength());
-		assertEquals(mtime[0], top.getEntryLastModified());
+		assertEquals(mtime[0], top.getEntryLastModifiedInstant());
 
 		top.next(1);
 		assertFalse(top.first());
@@ -206,7 +208,7 @@ public void testSimpleIterate() throws Exception {
 		assertEquals(FileMode.REGULAR_FILE.getBits(), top.mode);
 		assertEquals(paths[1], nameOf(top));
 		assertEquals(paths[1].length(), top.getEntryLength());
-		assertEquals(mtime[1], top.getEntryLastModified());
+		assertEquals(mtime[1], top.getEntryLastModifiedInstant());
 
 		top.next(1);
 		assertFalse(top.first());
@@ -221,7 +223,7 @@ public void testSimpleIterate() throws Exception {
 		assertFalse(sub.eof());
 		assertEquals(paths[2], nameOf(sub));
 		assertEquals(paths[2].length(), subfti.getEntryLength());
-		assertEquals(mtime[2], subfti.getEntryLastModified());
+		assertEquals(mtime[2], subfti.getEntryLastModifiedInstant());
 
 		sub.next(1);
 		assertTrue(sub.eof());
@@ -232,7 +234,7 @@ public void testSimpleIterate() throws Exception {
 		assertEquals(FileMode.REGULAR_FILE.getBits(), top.mode);
 		assertEquals(paths[3], nameOf(top));
 		assertEquals(paths[3].length(), top.getEntryLength());
-		assertEquals(mtime[3], top.getEntryLastModified());
+		assertEquals(mtime[3], top.getEntryLastModifiedInstant());
 
 		top.next(1);
 		assertTrue(top.eof());
@@ -331,7 +333,7 @@ public void testIsModifiedSymlinkAsFile() throws Exception {
 		DirCacheEntry dce = db.readDirCache().getEntry("symlink");
 		dce.setFileMode(FileMode.SYMLINK);
 		try (ObjectReader objectReader = db.newObjectReader()) {
-			DirCacheCheckout.checkoutEntry(db, dce, objectReader);
+			DirCacheCheckout.checkoutEntry(db, dce, objectReader, false, null);
 
 			FileTreeIterator fti = new FileTreeIterator(trash, db.getFS(),
 					db.getConfig().get(WorkingTreeOptions.KEY));
@@ -344,20 +346,21 @@ public void testIsModifiedSymlinkAsFile() throws Exception {
 	@Test
 	public void testIsModifiedFileSmudged() throws Exception {
 		File f = writeTrashFile("file", "content");
+		FS fs = db.getFS();
 		try (Git git = new Git(db)) {
 			// The idea of this test is to check the smudged handling
 			// Hopefully fsTick will make sure our entry gets smudged
 			fsTick(f);
 			writeTrashFile("file", "content");
-			long lastModified = f.lastModified();
+			Instant lastModified = fs.lastModifiedInstant(f);
 			git.add().addFilepattern("file").call();
 			writeTrashFile("file", "conten2");
-			f.setLastModified(lastModified);
+			fs.setLastModified(f.toPath(), lastModified);
 			// We cannot trust this to go fast enough on
 			// a system with less than one-second lastModified
 			// resolution, so we force the index to have the
 			// same timestamp as the file we look at.
-			db.getIndexFile().setLastModified(lastModified);
+			fs.setLastModified(db.getIndexFile().toPath(), lastModified);
 		}
 		DirCacheEntry dce = db.readDirCache().getEntry("file");
 		FileTreeIterator fti = new FileTreeIterator(trash, db.getFS(), db
@@ -822,6 +825,6 @@ private static void assertEntry(String sha1string, String path, TreeWalk tw)
 	}
 
 	private static String nameOf(AbstractTreeIterator i) {
-		return RawParseUtils.decode(Constants.CHARSET, i.path, 0, i.pathLen);
+		return RawParseUtils.decode(UTF_8, i.path, 0, i.pathLen);
 	}
 }
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/filter/PathFilterGroupTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/filter/PathFilterGroupTest.java
index 71a2f32..15d03d3 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/filter/PathFilterGroupTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/filter/PathFilterGroupTest.java
@@ -231,8 +231,8 @@ private void assertMatches(Set<String> expect, TreeWalk tw)
 			}
 		}
 
-		String[] e = expect.toArray(new String[expect.size()]);
-		String[] a = actual.toArray(new String[actual.size()]);
+		String[] e = expect.toArray(new String[0]);
+		String[] a = actual.toArray(new String[0]);
 		Arrays.sort(e);
 		Arrays.sort(a);
 		assertArrayEquals(e, a);
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FSTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FSTest.java
index 2c8273d..3e1202c 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FSTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FSTest.java
@@ -43,6 +43,7 @@
 
 package org.eclipse.jgit.util;
 
+import static java.time.Instant.EPOCH;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
@@ -52,12 +53,21 @@
 import java.io.IOException;
 import java.nio.charset.Charset;
 import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.attribute.FileTime;
 import java.nio.file.attribute.PosixFileAttributeView;
 import java.nio.file.attribute.PosixFilePermission;
+import java.time.Duration;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+import java.util.Locale;
 import java.util.Set;
+import java.util.concurrent.TimeUnit;
 
 import org.eclipse.jgit.errors.CommandFailedException;
+import org.eclipse.jgit.junit.MockSystemReader;
 import org.eclipse.jgit.junit.RepositoryTestCase;
+import org.eclipse.jgit.lib.RepositoryCache;
 import org.junit.After;
 import org.junit.Assume;
 import org.junit.Before;
@@ -68,6 +78,7 @@ public class FSTest {
 
 	@Before
 	public void setUp() throws Exception {
+		SystemReader.setInstance(new MockSystemReader());
 		trash = File.createTempFile("tmp_", "");
 		trash.delete();
 		assertTrue("mkdir " + trash, trash.mkdir());
@@ -98,7 +109,7 @@ public void testSymlinkAttributes() throws IOException, InterruptedException {
 		assertTrue(fs.exists(link));
 		String targetName = fs.readSymLink(link);
 		assertEquals("å", targetName);
-		assertTrue(fs.lastModified(link) > 0);
+		assertTrue(fs.lastModifiedInstant(link).compareTo(EPOCH) > 0);
 		assertTrue(fs.exists(link));
 		assertFalse(fs.canExecute(link));
 		assertEquals(2, fs.length(link));
@@ -111,8 +122,9 @@ public void testSymlinkAttributes() throws IOException, InterruptedException {
 		// Now create the link target
 		FileUtils.createNewFile(target);
 		assertTrue(fs.exists(link));
-		assertTrue(fs.lastModified(link) > 0);
-		assertTrue(fs.lastModified(target) > fs.lastModified(link));
+		assertTrue(fs.lastModifiedInstant(link).compareTo(EPOCH) > 0);
+		assertTrue(fs.lastModifiedInstant(target)
+				.compareTo(fs.lastModifiedInstant(link)) > 0);
 		assertFalse(fs.canExecute(link));
 		fs.setExecute(target, true);
 		assertFalse(fs.canExecute(link));
@@ -186,4 +198,42 @@ public void testReadPipeCommandStartFailure()
 				  new String[] { "this-command-does-not-exist" },
 				  Charset.defaultCharset().name());
 	}
+
+	@Test
+	public void testFsTimestampResolution() throws Exception {
+		DateTimeFormatter formatter = DateTimeFormatter
+				.ofPattern("uuuu-MMM-dd HH:mm:ss.nnnnnnnnn", Locale.ENGLISH)
+				.withZone(ZoneId.systemDefault());
+		Path dir = Files.createTempDirectory("probe-filesystem");
+		Duration resolution = FS.getFileStoreAttributes(dir)
+				.getFsTimestampResolution();
+		long resolutionNs = resolution.toNanos();
+		assertTrue(resolutionNs > 0);
+		for (int i = 0; i < 10; i++) {
+			Path f = null;
+			try {
+				f = dir.resolve("testTimestampResolution" + i);
+				Files.createFile(f);
+				FileUtils.touch(f);
+				FileTime t1 = Files.getLastModifiedTime(f);
+				TimeUnit.NANOSECONDS.sleep(resolutionNs);
+				FileUtils.touch(f);
+				FileTime t2 = Files.getLastModifiedTime(f);
+				assertTrue(String.format(
+						"expected t2=%s to be larger than t1=%s\nsince file timestamp resolution was measured to be %,d ns",
+						formatter.format(t2.toInstant()),
+						formatter.format(t1.toInstant()),
+						Long.valueOf(resolutionNs)), t2.compareTo(t1) > 0);
+			} finally {
+				Files.delete(f);
+			}
+		}
+	}
+
+	// bug 548682
+	@Test
+	public void testRepoCacheRelativePathUnbornRepo() {
+		assertFalse(RepositoryCache.FileKey
+				.isGitRepository(new File("repo.git"), FS.DETECTED));
+	}
 }
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FS_POSIXTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FS_POSIXTest.java
new file mode 100644
index 0000000..87349a2
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FS_POSIXTest.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2019, Vishal Devgire <vishaldevgire@gmail.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;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+import org.eclipse.jgit.junit.MockSystemReader;
+import org.eclipse.jgit.lib.ConfigConstants;
+import org.eclipse.jgit.storage.file.FileBasedConfig;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+public class FS_POSIXTest {
+	private SystemReader originalSystemReaderInstance;
+
+	private FileBasedConfig systemConfig;
+
+	private FileBasedConfig userConfig;
+
+	private Path tmp;
+
+	@Before
+	public void setUp() throws Exception {
+		tmp = Files.createTempDirectory("jgit_test_");
+		MockSystemReader mockSystemReader = new MockSystemReader();
+		SystemReader.setInstance(mockSystemReader);
+
+		// Measure timer resolution before the test to avoid time critical tests
+		// are affected by time needed for measurement.
+		// The MockSystemReader must be configured first since we need to use
+		// the same one here
+		FS.getFileStoreAttributes(tmp.getParent());
+		systemConfig = new FileBasedConfig(
+				new File(tmp.toFile(), "systemgitconfig"), FS.DETECTED);
+		userConfig = new FileBasedConfig(systemConfig,
+				new File(tmp.toFile(), "usergitconfig"), FS.DETECTED);
+		// We have to set autoDetach to false for tests, because tests expect to
+		// be able to clean up by recursively removing the repository, and
+		// background GC might be in the middle of writing or deleting files,
+		// which would disrupt this.
+		userConfig.setBoolean(ConfigConstants.CONFIG_GC_SECTION, null,
+				ConfigConstants.CONFIG_KEY_AUTODETACH, false);
+		userConfig.save();
+		mockSystemReader.setSystemGitConfig(systemConfig);
+		mockSystemReader.setUserGitConfig(userConfig);
+
+		originalSystemReaderInstance = SystemReader.getInstance();
+		SystemReader.setInstance(mockSystemReader);
+	}
+
+	@After
+	public void tearDown() throws IOException {
+		SystemReader.setInstance(originalSystemReaderInstance);
+		FileUtils.delete(tmp.toFile(), FileUtils.RECURSIVE | FileUtils.RETRY);
+	}
+
+	@Test
+	public void supportsAtomicCreateNewFile_shouldReturnSupportedAsDefault() {
+		assertTrue(new FS_POSIX().supportsAtomicCreateNewFile());
+	}
+
+	@Test
+	public void supportsAtomicCreateNewFile_shouldReturnTrueIfFlagIsSetInUserConfig() {
+		setAtomicCreateCreationFlag(userConfig, "true");
+		assertTrue(new FS_POSIX().supportsAtomicCreateNewFile());
+	}
+
+	@Test
+	public void supportsAtomicCreateNewFile_shouldReturnTrueIfFlagIsSetInSystemConfig() {
+		setAtomicCreateCreationFlag(systemConfig, "true");
+		assertTrue(new FS_POSIX().supportsAtomicCreateNewFile());
+	}
+
+	@Test
+	public void supportsAtomicCreateNewFile_shouldReturnFalseIfFlagUnsetInUserConfig() {
+		setAtomicCreateCreationFlag(userConfig, "false");
+		assertFalse(new FS_POSIX().supportsAtomicCreateNewFile());
+	}
+
+	@Test
+	public void supportsAtomicCreateNewFile_shouldReturnFalseIfFlagUnsetInSystemConfig() {
+		setAtomicCreateCreationFlag(systemConfig, "false");
+		assertFalse(new FS_POSIX().supportsAtomicCreateNewFile());
+	}
+
+	private void setAtomicCreateCreationFlag(FileBasedConfig config,
+			String value) {
+		config.setString(ConfigConstants.CONFIG_CORE_SECTION, null,
+				ConfigConstants.CONFIG_KEY_SUPPORTSATOMICFILECREATION, value);
+	}
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FilterCommandsTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FilterCommandsTest.java
index 0d31811..8dfdd0f 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FilterCommandsTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FilterCommandsTest.java
@@ -85,6 +85,8 @@ public FilterCommand create(Repository repo, InputStream in,
 				public int run() throws IOException {
 					int b = in.read();
 					if (b == -1) {
+						in.close();
+						out.close();
 						return b;
 					}
 					out.write(prefix);
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RawParseUtilsTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RawParseUtilsTest.java
index 7bd9adb..3aae59e 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RawParseUtilsTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RawParseUtilsTest.java
@@ -52,7 +52,24 @@
 import org.eclipse.jgit.lib.Constants;
 import org.junit.Test;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
+
 public class RawParseUtilsTest {
+	String commit = "tree e3a1035abd2b319bb01e57d69b0ba6cab289297e\n" +
+		"parent 54e895b87c0768d2317a2b17062e3ad9f76a8105\n" +
+		"committer A U Thor <author@xample.com 1528968566 +0200\n" +
+		"gpgsig -----BEGIN PGP SIGNATURE-----\n" +
+		" \n" +
+		" wsBcBAABCAAQBQJbGB4pCRBK7hj4Ov3rIwAAdHIIAENrvz23867ZgqrmyPemBEZP\n" +
+		" U24B1Tlq/DWvce2buaxmbNQngKZ0pv2s8VMc11916WfTIC9EKvioatmpjduWvhqj\n" +
+		" znQTFyiMor30pyYsfrqFuQZvqBW01o8GEWqLg8zjf9Rf0R3LlOEw86aT8CdHRlm6\n" +
+		" wlb22xb8qoX4RB+LYfz7MhK5F+yLOPXZdJnAVbuyoMGRnDpwdzjL5Hj671+XJxN5\n" +
+		" SasRdhxkkfw/ZnHxaKEc4juMz8Nziz27elRwhOQqlTYoXNJnsV//wy5Losd7aKi1\n" +
+		" xXXyUpndEOmT0CIcKHrN/kbYoVL28OJaxoBuva3WYQaRrzEe3X02NMxZe9gkSqA=\n" +
+		" =TClh\n" +
+		" -----END PGP SIGNATURE-----\n" +
+		"some other header\n\n" +
+		"commit message";
 
 	@Test
 	public void testParseEncoding_ISO8859_1_encoding() {
@@ -79,4 +96,30 @@ public void testParseEncoding_badEncoding() {
 		}
 	}
 
+	@Test
+	public void testHeaderStart() {
+		byte[] headerName = "some".getBytes(UTF_8);
+		byte[] commitBytes = commit.getBytes(UTF_8);
+		assertEquals(625, RawParseUtils.headerStart(headerName, commitBytes, 0));
+		assertEquals(625, RawParseUtils.headerStart(headerName, commitBytes, 4));
+
+		byte[] missingHeaderName = "missing".getBytes(UTF_8);
+		assertEquals(-1, RawParseUtils.headerStart(missingHeaderName,
+							   commitBytes, 0));
+
+		byte[] fauxHeaderName = "other".getBytes(UTF_8);
+		assertEquals(-1, RawParseUtils.headerStart(fauxHeaderName, commitBytes, 625 + 4));
+	}
+
+	@Test
+	public void testHeaderEnd() {
+		byte[] commitBytes = commit.getBytes(UTF_8);
+		int[] expected = new int[] {45, 93, 148, 619, 637};
+		int start = 0;
+		for (int i = 0; i < expected.length; i++) {
+			start = RawParseUtils.headerEnd(commitBytes, start);
+			assertEquals(expected[i], start);
+			start += 1;
+		}
+	}
 }
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RawSubStringPatternTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RawSubStringPatternTest.java
index a748b52..e8566d2 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RawSubStringPatternTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RawSubStringPatternTest.java
@@ -42,7 +42,7 @@
  */
 package org.eclipse.jgit.util;
 
-import static org.eclipse.jgit.lib.Constants.CHARSET;
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 
@@ -93,7 +93,7 @@ private static void assertMatchResult(String pattern, String input, int position
 	}
 
 	private static RawCharSequence raw(String text) {
-		byte[] bytes = text.getBytes(CHARSET);
+		byte[] bytes = text.getBytes(UTF_8);
 		return new RawCharSequence(bytes, 0, bytes.length);
 	}
 }
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/SimpleLruCacheTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/SimpleLruCacheTest.java
new file mode 100644
index 0000000..5894f7d
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/SimpleLruCacheTest.java
@@ -0,0 +1,135 @@
+/*
+ * 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;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+public class SimpleLruCacheTest {
+
+	private Path trash;
+
+	private SimpleLruCache<String, String> cache;
+
+
+	@Before
+	public void setup() throws IOException {
+		trash = Files.createTempDirectory("tmp_");
+		cache = new SimpleLruCache<>(100, 0.2f);
+	}
+
+	@Before
+	@After
+	public void tearDown() throws Exception {
+		FileUtils.delete(trash.toFile(),
+				FileUtils.RECURSIVE | FileUtils.SKIP_MISSING);
+	}
+
+	@Test
+	public void testPutGet() {
+		cache.put("a", "A");
+		cache.put("z", "Z");
+		assertEquals("A", cache.get("a"));
+		assertEquals("Z", cache.get("z"));
+	}
+
+	@Test(expected = IllegalArgumentException.class)
+	public void testPurgeFactorTooLarge() {
+		cache.configure(5, 1.01f);
+	}
+
+	@Test(expected = IllegalArgumentException.class)
+	public void testPurgeFactorTooLarge2() {
+		cache.configure(5, 100);
+	}
+
+	@Test(expected = IllegalArgumentException.class)
+	public void testPurgeFactorTooSmall() {
+		cache.configure(5, 0);
+	}
+
+	@Test(expected = IllegalArgumentException.class)
+	public void testPurgeFactorTooSmall2() {
+		cache.configure(5, -100);
+	}
+
+	@Test
+	public void testGetMissing() {
+		assertEquals(null, cache.get("a"));
+	}
+
+	@Test
+	public void testPurge() {
+		for (int i = 0; i < 101; i++) {
+			cache.put("a" + i, "a" + i);
+		}
+		assertEquals(80, cache.size());
+		assertNull(cache.get("a0"));
+		assertNull(cache.get("a20"));
+		assertNotNull(cache.get("a21"));
+		assertNotNull(cache.get("a99"));
+	}
+
+	@Test
+	public void testConfigure() {
+		for (int i = 0; i < 100; i++) {
+			cache.put("a" + i, "a" + i);
+		}
+		assertEquals(100, cache.size());
+		cache.configure(10, 0.3f);
+		assertEquals(7, cache.size());
+		assertNull(cache.get("a0"));
+		assertNull(cache.get("a92"));
+		assertNotNull(cache.get("a93"));
+		assertNotNull(cache.get("a99"));
+	}
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/StatsTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/StatsTest.java
new file mode 100644
index 0000000..8b25382
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/StatsTest.java
@@ -0,0 +1,137 @@
+/*
+ * 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;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import org.eclipse.jgit.util.Stats;
+import org.junit.Test;
+
+public class StatsTest {
+	@Test
+	public void testStatsTrivial() {
+		Stats s = new Stats();
+		s.add(1);
+		s.add(1);
+		s.add(1);
+		assertEquals(3, s.count());
+		assertEquals(1.0, s.min(), 1E-6);
+		assertEquals(1.0, s.max(), 1E-6);
+		assertEquals(1.0, s.avg(), 1E-6);
+		assertEquals(0.0, s.var(), 1E-6);
+		assertEquals(0.0, s.stddev(), 1E-6);
+	}
+
+	@Test
+	public void testStats() {
+		Stats s = new Stats();
+		s.add(1);
+		s.add(2);
+		s.add(3);
+		s.add(4);
+		assertEquals(4, s.count());
+		assertEquals(1.0, s.min(), 1E-6);
+		assertEquals(4.0, s.max(), 1E-6);
+		assertEquals(2.5, s.avg(), 1E-6);
+		assertEquals(1.666667, s.var(), 1E-6);
+		assertEquals(1.290994, s.stddev(), 1E-6);
+	}
+
+	@Test
+	/**
+	 * see
+	 * https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance#Example
+	 */
+	public void testStatsCancellationExample1() {
+		Stats s = new Stats();
+		s.add(1E8 + 4);
+		s.add(1E8 + 7);
+		s.add(1E8 + 13);
+		s.add(1E8 + 16);
+		assertEquals(4, s.count());
+		assertEquals(1E8 + 4, s.min(), 1E-6);
+		assertEquals(1E8 + 16, s.max(), 1E-6);
+		assertEquals(1E8 + 10, s.avg(), 1E-6);
+		assertEquals(30, s.var(), 1E-6);
+		assertEquals(5.477226, s.stddev(), 1E-6);
+	}
+
+	@Test
+	/**
+	 * see
+	 * https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance#Example
+	 */
+	public void testStatsCancellationExample2() {
+		Stats s = new Stats();
+		s.add(1E9 + 4);
+		s.add(1E9 + 7);
+		s.add(1E9 + 13);
+		s.add(1E9 + 16);
+		assertEquals(4, s.count());
+		assertEquals(1E9 + 4, s.min(), 1E-6);
+		assertEquals(1E9 + 16, s.max(), 1E-6);
+		assertEquals(1E9 + 10, s.avg(), 1E-6);
+		assertEquals(30, s.var(), 1E-6);
+		assertEquals(5.477226, s.stddev(), 1E-6);
+	}
+
+	@Test
+	public void testNoValues() {
+		Stats s = new Stats();
+		assertTrue(Double.isNaN(s.var()));
+		assertTrue(Double.isNaN(s.stddev()));
+		assertTrue(Double.isNaN(s.avg()));
+		assertTrue(Double.isNaN(s.min()));
+		assertTrue(Double.isNaN(s.max()));
+		s.add(42.3);
+		assertTrue(Double.isNaN(s.var()));
+		assertTrue(Double.isNaN(s.stddev()));
+		assertEquals(42.3, s.avg(), 1E-6);
+		assertEquals(42.3, s.max(), 1E-6);
+		assertEquals(42.3, s.min(), 1E-6);
+		s.add(42.3);
+		assertEquals(0, s.var(), 1E-6);
+		assertEquals(0, s.stddev(), 1E-6);
+	}
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/SystemReaderTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/SystemReaderTest.java
new file mode 100644
index 0000000..1fe1ba9
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/SystemReaderTest.java
@@ -0,0 +1,127 @@
+/*
+ * 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;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.when;
+
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+import org.eclipse.jgit.storage.file.FileBasedConfig;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+@RunWith(MockitoJUnitRunner.class)
+public class SystemReaderTest {
+	private Path trash;
+
+	private Path mockSystemConfig;
+
+	private Path mockUserConfig;
+
+	@Mock
+	private FS fs;
+
+	@Before
+	public void setup() throws Exception {
+		trash = Files.createTempDirectory("jgit_test");
+		mockSystemConfig = trash.resolve("systemgitconfig");
+		Files.write(mockSystemConfig, "[core]\n  trustFolderStat = false\n"
+				.getBytes(StandardCharsets.UTF_8));
+		mockUserConfig = trash.resolve(".gitconfig");
+		Files.write(mockUserConfig,
+				"[core]\n  bare = false\n".getBytes(StandardCharsets.UTF_8));
+		when(fs.getGitSystemConfig()).thenReturn(mockSystemConfig.toFile());
+		when(fs.userHome()).thenReturn(trash.toFile());
+		SystemReader.setInstance(null);
+	}
+
+	@After
+	public void teardown() throws Exception {
+		FileUtils.delete(trash.toFile(), FileUtils.RECURSIVE | FileUtils.RETRY);
+	}
+
+	@Test
+	public void openSystemConfigReturnsDifferentInstances() throws Exception {
+		FileBasedConfig system1 = SystemReader.getInstance()
+				.openSystemConfig(null, fs);
+		system1.load();
+		assertEquals("false",
+				system1.getString("core", null, "trustFolderStat"));
+
+		FileBasedConfig system2 = SystemReader.getInstance()
+				.openSystemConfig(null, fs);
+		system2.load();
+		assertEquals("false",
+				system2.getString("core", null, "trustFolderStat"));
+
+		system1.setString("core", null, "trustFolderStat", "true");
+		assertEquals("true",
+				system1.getString("core", null, "trustFolderStat"));
+		assertEquals("false",
+				system2.getString("core", null, "trustFolderStat"));
+	}
+
+	@Test
+	public void openUserConfigReturnsDifferentInstances() throws Exception {
+		FileBasedConfig user1 = SystemReader.getInstance().openUserConfig(null,
+				fs);
+		user1.load();
+		assertEquals("false", user1.getString("core", null, "bare"));
+
+		FileBasedConfig user2 = SystemReader.getInstance().openUserConfig(null,
+				fs);
+		user2.load();
+		assertEquals("false", user2.getString("core", null, "bare"));
+
+		user1.setString("core", null, "bare", "true");
+		assertEquals("true", user1.getString("core", null, "bare"));
+		assertEquals("false", user2.getString("core", null, "bare"));
+	}
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/io/AutoLFInputStreamTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/io/AutoLFInputStreamTest.java
index 5058165..37ca951 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/io/AutoLFInputStreamTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/io/AutoLFInputStreamTest.java
@@ -44,7 +44,7 @@
 
 package org.eclipse.jgit.util.io;
 
-import static org.eclipse.jgit.lib.Constants.CHARSET;
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.junit.Assert.assertEquals;
 
 import java.io.ByteArrayInputStream;
@@ -132,6 +132,6 @@ private static void test(byte[] input, byte[] expected,
 	}
 
 	private static byte[] asBytes(String in) {
-		return in.getBytes(CHARSET);
+		return in.getBytes(UTF_8);
 	}
 }
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/io/TimeoutOutputStreamTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/io/TimeoutOutputStreamTest.java
index a63b1cb..c35f90c 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/io/TimeoutOutputStreamTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/io/TimeoutOutputStreamTest.java
@@ -282,7 +282,7 @@ private static long now() {
 		return System.currentTimeMillis();
 	}
 
-	private final class FullPipeInputStream extends PipedInputStream {
+	private static final class FullPipeInputStream extends PipedInputStream {
 		FullPipeInputStream(PipedOutputStream src) throws IOException {
 			super(src);
 			src.write(new byte[PIPE_SIZE]);
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/sha1/SHA1Test.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/sha1/SHA1Test.java
index 3c612e1..e6045a9 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/sha1/SHA1Test.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/sha1/SHA1Test.java
@@ -43,7 +43,7 @@
 
 package org.eclipse.jgit.util.sha1;
 
-import static org.eclipse.jgit.lib.Constants.CHARSET;
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
@@ -95,15 +95,15 @@ public void test1() throws NoSuchAlgorithmException {
 				.fromString("a9993e364706816aba3e25717850c26c9cd0d89d");
 
 		MessageDigest m = MessageDigest.getInstance("SHA-1");
-		m.update(TEST1.getBytes(CHARSET));
+		m.update(TEST1.getBytes(UTF_8));
 		ObjectId m1 = ObjectId.fromRaw(m.digest());
 
 		SHA1 s = SHA1.newInstance();
-		s.update(TEST1.getBytes(CHARSET));
+		s.update(TEST1.getBytes(UTF_8));
 		ObjectId s1 = ObjectId.fromRaw(s.digest());
 
 		s.reset();
-		s.update(TEST1.getBytes(CHARSET));
+		s.update(TEST1.getBytes(UTF_8));
 		ObjectId s2 = s.toObjectId();
 
 		assertEquals(m1, s1);
@@ -117,15 +117,15 @@ public void test2() throws NoSuchAlgorithmException {
 				.fromString("84983e441c3bd26ebaae4aa1f95129e5e54670f1");
 
 		MessageDigest m = MessageDigest.getInstance("SHA-1");
-		m.update(TEST2.getBytes(CHARSET));
+		m.update(TEST2.getBytes(UTF_8));
 		ObjectId m1 = ObjectId.fromRaw(m.digest());
 
 		SHA1 s = SHA1.newInstance();
-		s.update(TEST2.getBytes(CHARSET));
+		s.update(TEST2.getBytes(UTF_8));
 		ObjectId s1 = ObjectId.fromRaw(s.digest());
 
 		s.reset();
-		s.update(TEST2.getBytes(CHARSET));
+		s.update(TEST2.getBytes(UTF_8));
 		ObjectId s2 = s.toObjectId();
 
 		assertEquals(m1, s1);
diff --git a/org.eclipse.jgit.ui/.settings/.api_filters b/org.eclipse.jgit.ui/.settings/.api_filters
deleted file mode 100644
index b4788ba..0000000
--- a/org.eclipse.jgit.ui/.settings/.api_filters
+++ /dev/null
@@ -1,11 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<component id="org.eclipse.jgit.ui" version="2">
-    <resource path="META-INF/MANIFEST.MF">
-        <filter id="925892614">
-            <message_arguments>
-                <message_argument value="5.0.4"/>
-                <message_argument value="4.11.0"/>
-            </message_arguments>
-        </filter>
-    </resource>
-</component>
diff --git a/org.eclipse.jgit.ui/META-INF/MANIFEST.MF b/org.eclipse.jgit.ui/META-INF/MANIFEST.MF
index 7667af4..19fbd3d 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.0.4.qualifier
+Bundle-Version: 5.1.11.qualifier
 Bundle-Vendor: %provider_name
 Bundle-RequiredExecutionEnvironment: JavaSE-1.8
-Export-Package: org.eclipse.jgit.awtui;version="5.0.4"
-Import-Package: org.eclipse.jgit.errors;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.lib;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.nls;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.revplot;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.revwalk;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.transport;version="[5.0.4,5.1.0)",
- org.eclipse.jgit.util;version="[5.0.4,5.1.0)"
+Export-Package: org.eclipse.jgit.awtui;version="5.1.11"
+Import-Package: org.eclipse.jgit.errors;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.lib;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.nls;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.revplot;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.revwalk;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.transport;version="[5.1.11,5.2.0)",
+ org.eclipse.jgit.util;version="[5.1.11,5.2.0)"
diff --git a/org.eclipse.jgit.ui/pom.xml b/org.eclipse.jgit.ui/pom.xml
index 7d10c09..bdbfe54 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.0.4-SNAPSHOT</version>
+    <version>5.1.11-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.ui</artifactId>
diff --git a/org.eclipse.jgit/.settings/.api_filters b/org.eclipse.jgit/.settings/.api_filters
index 404b45d..c62afb3 100644
--- a/org.eclipse.jgit/.settings/.api_filters
+++ b/org.eclipse.jgit/.settings/.api_filters
@@ -1,5 +1,25 @@
 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
 <component id="org.eclipse.jgit" version="2">
+    <resource path="src/org/eclipse/jgit/dircache/DirCacheEntry.java" type="org.eclipse.jgit.dircache.DirCacheEntry">
+        <filter id="1142947843">
+            <message_arguments>
+                <message_argument value="5.1.9"/>
+                <message_argument value="getLastModifiedInstant()"/>
+            </message_arguments>
+        </filter>
+        <filter id="1142947843">
+            <message_arguments>
+                <message_argument value="5.1.9"/>
+                <message_argument value="mightBeRacilyClean(Instant)"/>
+            </message_arguments>
+        </filter>
+        <filter id="1142947843">
+            <message_arguments>
+                <message_argument value="5.1.9"/>
+                <message_argument value="setLastModified(Instant)"/>
+            </message_arguments>
+        </filter>
+    </resource>
     <resource path="src/org/eclipse/jgit/errors/PackInvalidException.java" type="org.eclipse.jgit.errors.PackInvalidException">
         <filter id="1142947843">
             <message_arguments>
@@ -14,12 +34,28 @@
             </message_arguments>
         </filter>
     </resource>
-    <resource path="src/org/eclipse/jgit/lib/Constants.java" type="org.eclipse.jgit.lib.Constants">
-        <filter id="1141899266">
+    <resource path="src/org/eclipse/jgit/lib/ConfigConstants.java" type="org.eclipse.jgit.lib.ConfigConstants">
+        <filter id="337768515">
             <message_arguments>
-                <message_argument value="4.7"/>
-                <message_argument value="5.0"/>
-                <message_argument value="LOCK_SUFFIX"/>
+                <message_argument value="org.eclipse.jgit.lib.ConfigConstants"/>
+            </message_arguments>
+        </filter>
+        <filter id="1142947843">
+            <message_arguments>
+                <message_argument value="5.1.9"/>
+                <message_argument value="CONFIG_FILESYSTEM_SECTION"/>
+            </message_arguments>
+        </filter>
+        <filter id="1142947843">
+            <message_arguments>
+                <message_argument value="5.1.9"/>
+                <message_argument value="CONFIG_KEY_MIN_RACY_THRESHOLD"/>
+            </message_arguments>
+        </filter>
+        <filter id="1142947843">
+            <message_arguments>
+                <message_argument value="5.1.9"/>
+                <message_argument value="CONFIG_KEY_TIMESTAMP_RESOLUTION"/>
             </message_arguments>
         </filter>
     </resource>
@@ -39,19 +75,102 @@
             </message_arguments>
         </filter>
     </resource>
-    <resource path="src/org/eclipse/jgit/lib/ObjectIdSerializer.java" type="org.eclipse.jgit.lib.ObjectIdSerializer">
-        <filter id="1141899266">
+    <resource path="src/org/eclipse/jgit/storage/pack/PackConfig.java" type="org.eclipse.jgit.storage.pack.PackConfig">
+        <filter id="336658481">
             <message_arguments>
-                <message_argument value="4.11"/>
-                <message_argument value="5.0"/>
-                <message_argument value="readWithoutMarker(InputStream)"/>
+                <message_argument value="org.eclipse.jgit.storage.pack.PackConfig"/>
+                <message_argument value="DEFAULT_MINSIZE_PREVENT_RACY_PACK"/>
             </message_arguments>
         </filter>
-        <filter id="1141899266">
+        <filter id="336658481">
             <message_arguments>
-                <message_argument value="4.11"/>
-                <message_argument value="5.0"/>
-                <message_argument value="writeWithoutMarker(OutputStream, AnyObjectId)"/>
+                <message_argument value="org.eclipse.jgit.storage.pack.PackConfig"/>
+                <message_argument value="DEFAULT_WAIT_PREVENT_RACY_PACK"/>
+            </message_arguments>
+        </filter>
+        <filter id="1142947843">
+            <message_arguments>
+                <message_argument value="5.1.8"/>
+                <message_argument value="DEFAULT_MINSIZE_PREVENT_RACY_PACK"/>
+            </message_arguments>
+        </filter>
+        <filter id="1142947843">
+            <message_arguments>
+                <message_argument value="5.1.8"/>
+                <message_argument value="DEFAULT_WAIT_PREVENT_RACY_PACK"/>
+            </message_arguments>
+        </filter>
+        <filter id="1142947843">
+            <message_arguments>
+                <message_argument value="5.1.8"/>
+                <message_argument value="doWaitPreventRacyPack(long)"/>
+            </message_arguments>
+        </filter>
+        <filter id="1142947843">
+            <message_arguments>
+                <message_argument value="5.1.8"/>
+                <message_argument value="getMinSizePreventRacyPack()"/>
+            </message_arguments>
+        </filter>
+        <filter id="1142947843">
+            <message_arguments>
+                <message_argument value="5.1.8"/>
+                <message_argument value="isWaitPreventRacyPack()"/>
+            </message_arguments>
+        </filter>
+        <filter id="1142947843">
+            <message_arguments>
+                <message_argument value="5.1.8"/>
+                <message_argument value="setMinSizePreventRacyPack(long)"/>
+            </message_arguments>
+        </filter>
+        <filter id="1142947843">
+            <message_arguments>
+                <message_argument value="5.1.8"/>
+                <message_argument value="setWaitPreventRacyPack(boolean)"/>
+            </message_arguments>
+        </filter>
+    </resource>
+    <resource path="src/org/eclipse/jgit/transport/GitProtocolConstants.java" type="org.eclipse.jgit.transport.GitProtocolConstants">
+        <filter id="337768515">
+            <message_arguments>
+                <message_argument value="org.eclipse.jgit.transport.GitProtocolConstants"/>
+            </message_arguments>
+        </filter>
+    </resource>
+    <resource path="src/org/eclipse/jgit/transport/TransferConfig.java" type="org.eclipse.jgit.transport.TransferConfig">
+        <filter id="1159725059">
+            <message_arguments>
+                <message_argument value="5.1.4"/>
+                <message_argument value="TransferConfig(Config)"/>
+            </message_arguments>
+        </filter>
+        <filter id="1159725059">
+            <message_arguments>
+                <message_argument value="5.1.4"/>
+                <message_argument value="TransferConfig(Repository)"/>
+            </message_arguments>
+        </filter>
+    </resource>
+    <resource path="src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java" type="org.eclipse.jgit.treewalk.WorkingTreeIterator">
+        <filter id="1142947843">
+            <message_arguments>
+                <message_argument value="5.1.9"/>
+                <message_argument value="getEntryLastModifiedInstant()"/>
+            </message_arguments>
+        </filter>
+    </resource>
+    <resource path="src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java" type="org.eclipse.jgit.treewalk.WorkingTreeIterator$Entry">
+        <filter id="336695337">
+            <message_arguments>
+                <message_argument value="org.eclipse.jgit.treewalk.WorkingTreeIterator.Entry"/>
+                <message_argument value="getLastModifiedInstant()"/>
+            </message_arguments>
+        </filter>
+        <filter id="1142947843">
+            <message_arguments>
+                <message_argument value="5.1.9"/>
+                <message_argument value="getLastModifiedInstant()"/>
             </message_arguments>
         </filter>
     </resource>
@@ -59,7 +178,7 @@
         <filter id="1141899266">
             <message_arguments>
                 <message_argument value="4.7"/>
-                <message_argument value="5.0"/>
+                <message_argument value="5.1"/>
                 <message_argument value="createNewFileAtomic(File)"/>
             </message_arguments>
         </filter>
@@ -69,14 +188,98 @@
                 <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>
+        </filter>
+        <filter id="1142947843">
+            <message_arguments>
+                <message_argument value="5.1.9"/>
+                <message_argument value="lastModifiedInstant(File)"/>
+            </message_arguments>
+        </filter>
+        <filter id="1142947843">
+            <message_arguments>
+                <message_argument value="5.1.9"/>
+                <message_argument value="lastModifiedInstant(Path)"/>
+            </message_arguments>
+        </filter>
+        <filter id="1142947843">
+            <message_arguments>
+                <message_argument value="5.1.9"/>
+                <message_argument value="setAsyncFileStoreAttributes(boolean)"/>
+            </message_arguments>
+        </filter>
+        <filter id="1142947843">
+            <message_arguments>
+                <message_argument value="5.1.9"/>
+                <message_argument value="setLastModified(Path, Instant)"/>
+            </message_arguments>
+        </filter>
+    </resource>
+    <resource path="src/org/eclipse/jgit/util/FS.java" type="org.eclipse.jgit.util.FS$Attributes">
+        <filter id="1142947843">
+            <message_arguments>
+                <message_argument value="5.1.9"/>
+                <message_argument value="getLastModifiedInstant()"/>
+            </message_arguments>
+        </filter>
+    </resource>
+    <resource path="src/org/eclipse/jgit/util/FS.java" type="org.eclipse.jgit.util.FS$FileStoreAttributes">
+        <filter id="1142947843">
+            <message_arguments>
+                <message_argument value="5.1.9"/>
+                <message_argument value="FileStoreAttributes"/>
+            </message_arguments>
+        </filter>
     </resource>
     <resource path="src/org/eclipse/jgit/util/FS.java" type="org.eclipse.jgit.util.FS$LockToken">
         <filter id="1141899266">
             <message_arguments>
                 <message_argument value="4.7"/>
-                <message_argument value="5.0"/>
+                <message_argument value="5.1"/>
                 <message_argument value="LockToken"/>
             </message_arguments>
         </filter>
     </resource>
+    <resource path="src/org/eclipse/jgit/util/FileUtils.java" type="org.eclipse.jgit.util.FileUtils">
+        <filter id="1142947843">
+            <message_arguments>
+                <message_argument value="5.1.8"/>
+                <message_argument value="touch(Path)"/>
+            </message_arguments>
+        </filter>
+    </resource>
+    <resource path="src/org/eclipse/jgit/util/SimpleLruCache.java" type="org.eclipse.jgit.util.SimpleLruCache">
+        <filter id="1109393411">
+            <message_arguments>
+                <message_argument value="5.1.9"/>
+                <message_argument value="org.eclipse.jgit.util.SimpleLruCache"/>
+            </message_arguments>
+        </filter>
+    </resource>
+    <resource path="src/org/eclipse/jgit/util/Stats.java" type="org.eclipse.jgit.util.Stats">
+        <filter id="1109393411">
+            <message_arguments>
+                <message_argument value="5.1.9"/>
+                <message_argument value="org.eclipse.jgit.util.Stats"/>
+            </message_arguments>
+        </filter>
+    </resource>
+    <resource path="src/org/eclipse/jgit/util/SystemReader.java" type="org.eclipse.jgit.util.SystemReader">
+        <filter id="1142947843">
+            <message_arguments>
+                <message_argument value="5.1.9"/>
+                <message_argument value="getSystemConfig()"/>
+            </message_arguments>
+        </filter>
+        <filter id="1142947843">
+            <message_arguments>
+                <message_argument value="5.1.9"/>
+                <message_argument value="getUserConfig()"/>
+            </message_arguments>
+        </filter>
+    </resource>
 </component>
diff --git a/org.eclipse.jgit/META-INF/MANIFEST.MF b/org.eclipse.jgit/META-INF/MANIFEST.MF
index a8114d9..507a9f8 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.0.4.qualifier
+Bundle-Version: 5.1.11.qualifier
 Bundle-Localization: plugin
 Bundle-Vendor: %provider_name
 Bundle-ActivationPolicy: lazy
-Export-Package: org.eclipse.jgit.annotations;version="5.0.4",
- org.eclipse.jgit.api;version="5.0.4";
+Export-Package: org.eclipse.jgit.annotations;version="5.1.11",
+ org.eclipse.jgit.api;version="5.1.11";
   uses:="org.eclipse.jgit.revwalk,
    org.eclipse.jgit.treewalk.filter,
    org.eclipse.jgit.diff,
@@ -22,52 +22,52 @@
    org.eclipse.jgit.submodule,
    org.eclipse.jgit.transport,
    org.eclipse.jgit.merge",
- org.eclipse.jgit.api.errors;version="5.0.4";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.errors",
- org.eclipse.jgit.attributes;version="5.0.4",
- org.eclipse.jgit.blame;version="5.0.4";
+ org.eclipse.jgit.api.errors;version="5.1.11";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.errors",
+ org.eclipse.jgit.attributes;version="5.1.11",
+ org.eclipse.jgit.blame;version="5.1.11";
   uses:="org.eclipse.jgit.lib,
    org.eclipse.jgit.revwalk,
    org.eclipse.jgit.treewalk.filter,
    org.eclipse.jgit.diff",
- org.eclipse.jgit.diff;version="5.0.4";
+ org.eclipse.jgit.diff;version="5.1.11";
   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.0.4";
+ org.eclipse.jgit.dircache;version="5.1.11";
   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.0.4";
+ org.eclipse.jgit.errors;version="5.1.11";
   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.0.4";uses:="org.eclipse.jgit.lib",
- org.eclipse.jgit.fnmatch;version="5.0.4",
- org.eclipse.jgit.gitrepo;version="5.0.4";
+ org.eclipse.jgit.events;version="5.1.11";uses:="org.eclipse.jgit.lib",
+ org.eclipse.jgit.fnmatch;version="5.1.11",
+ org.eclipse.jgit.gitrepo;version="5.1.11";
   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.0.4";x-internal:=true,
- org.eclipse.jgit.hooks;version="5.0.4";uses:="org.eclipse.jgit.lib",
- org.eclipse.jgit.ignore;version="5.0.4",
- org.eclipse.jgit.ignore.internal;version="5.0.4";x-friends:="org.eclipse.jgit.test",
- org.eclipse.jgit.internal;version="5.0.4";x-friends:="org.eclipse.jgit.test,org.eclipse.jgit.http.test",
- org.eclipse.jgit.internal.fsck;version="5.0.4";x-friends:="org.eclipse.jgit.test",
- org.eclipse.jgit.internal.ketch;version="5.0.4";x-friends:="org.eclipse.jgit.junit,org.eclipse.jgit.test,org.eclipse.jgit.pgm",
- org.eclipse.jgit.internal.storage.dfs;version="5.0.4";
+ org.eclipse.jgit.gitrepo.internal;version="5.1.11";x-internal:=true,
+ org.eclipse.jgit.hooks;version="5.1.11";uses:="org.eclipse.jgit.lib",
+ org.eclipse.jgit.ignore;version="5.1.11",
+ org.eclipse.jgit.ignore.internal;version="5.1.11";x-friends:="org.eclipse.jgit.test",
+ org.eclipse.jgit.internal;version="5.1.11";x-friends:="org.eclipse.jgit.test,org.eclipse.jgit.http.test",
+ org.eclipse.jgit.internal.fsck;version="5.1.11";x-friends:="org.eclipse.jgit.test",
+ org.eclipse.jgit.internal.ketch;version="5.1.11";x-friends:="org.eclipse.jgit.junit,org.eclipse.jgit.test,org.eclipse.jgit.pgm",
+ org.eclipse.jgit.internal.storage.dfs;version="5.1.11";
   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.0.4";
+ org.eclipse.jgit.internal.storage.file;version="5.1.11";
   x-friends:="org.eclipse.jgit.test,
    org.eclipse.jgit.junit,
    org.eclipse.jgit.junit.http,
@@ -75,12 +75,12 @@
    org.eclipse.jgit.lfs,
    org.eclipse.jgit.pgm,
    org.eclipse.jgit.pgm.test",
- org.eclipse.jgit.internal.storage.io;version="5.0.4";x-friends:="org.eclipse.jgit.junit,org.eclipse.jgit.test,org.eclipse.jgit.pgm",
- org.eclipse.jgit.internal.storage.pack;version="5.0.4";x-friends:="org.eclipse.jgit.junit,org.eclipse.jgit.test,org.eclipse.jgit.pgm",
- org.eclipse.jgit.internal.storage.reftable;version="5.0.4";
+ org.eclipse.jgit.internal.storage.io;version="5.1.11";x-friends:="org.eclipse.jgit.junit,org.eclipse.jgit.test,org.eclipse.jgit.pgm",
+ org.eclipse.jgit.internal.storage.pack;version="5.1.11";x-friends:="org.eclipse.jgit.junit,org.eclipse.jgit.test,org.eclipse.jgit.pgm",
+ org.eclipse.jgit.internal.storage.reftable;version="5.1.11";
   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.0.4";x-friends:="org.eclipse.jgit.junit,org.eclipse.jgit.test,org.eclipse.jgit.pgm",
- org.eclipse.jgit.lib;version="5.0.4";
+ org.eclipse.jgit.internal.storage.reftree;version="5.1.11";x-friends:="org.eclipse.jgit.junit,org.eclipse.jgit.test,org.eclipse.jgit.pgm",
+ org.eclipse.jgit.lib;version="5.1.11";
   uses:="org.eclipse.jgit.revwalk,
    org.eclipse.jgit.treewalk.filter,
    org.eclipse.jgit.util,
@@ -90,33 +90,33 @@
    org.eclipse.jgit.treewalk,
    org.eclipse.jgit.transport,
    org.eclipse.jgit.submodule",
- org.eclipse.jgit.lib.internal;version="5.0.4";x-internal:=true,
- org.eclipse.jgit.merge;version="5.0.4";
+ org.eclipse.jgit.lib.internal;version="5.1.11";x-internal:=true,
+ org.eclipse.jgit.merge;version="5.1.11";
   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.0.4",
- org.eclipse.jgit.notes;version="5.0.4";
+ org.eclipse.jgit.nls;version="5.1.11",
+ org.eclipse.jgit.notes;version="5.1.11";
   uses:="org.eclipse.jgit.lib,
    org.eclipse.jgit.treewalk,
    org.eclipse.jgit.revwalk,
    org.eclipse.jgit.merge",
- org.eclipse.jgit.patch;version="5.0.4";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.diff",
- org.eclipse.jgit.revplot;version="5.0.4";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.revwalk",
- org.eclipse.jgit.revwalk;version="5.0.4";
+ org.eclipse.jgit.patch;version="5.1.11";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.diff",
+ org.eclipse.jgit.revplot;version="5.1.11";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.revwalk",
+ org.eclipse.jgit.revwalk;version="5.1.11";
   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.0.4";uses:="org.eclipse.jgit.revwalk,org.eclipse.jgit.lib,org.eclipse.jgit.util",
- org.eclipse.jgit.storage.file;version="5.0.4";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.util",
- org.eclipse.jgit.storage.pack;version="5.0.4";uses:="org.eclipse.jgit.lib",
- org.eclipse.jgit.submodule;version="5.0.4";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.treewalk.filter,org.eclipse.jgit.treewalk",
- org.eclipse.jgit.transport;version="5.0.4";
+ org.eclipse.jgit.revwalk.filter;version="5.1.11";uses:="org.eclipse.jgit.revwalk,org.eclipse.jgit.lib,org.eclipse.jgit.util",
+ org.eclipse.jgit.storage.file;version="5.1.11";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.util",
+ org.eclipse.jgit.storage.pack;version="5.1.11";uses:="org.eclipse.jgit.lib",
+ org.eclipse.jgit.submodule;version="5.1.11";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.treewalk.filter,org.eclipse.jgit.treewalk",
+ org.eclipse.jgit.transport;version="5.1.11";
   uses:="org.eclipse.jgit.transport.resolver,
    org.eclipse.jgit.revwalk,
    org.eclipse.jgit.internal.storage.pack,
@@ -128,24 +128,24 @@
    org.eclipse.jgit.transport.http,
    org.eclipse.jgit.errors,
    org.eclipse.jgit.storage.pack",
- org.eclipse.jgit.transport.http;version="5.0.4";uses:="javax.net.ssl",
- org.eclipse.jgit.transport.resolver;version="5.0.4";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.transport",
- org.eclipse.jgit.treewalk;version="5.0.4";
+ org.eclipse.jgit.transport.http;version="5.1.11";uses:="javax.net.ssl",
+ org.eclipse.jgit.transport.resolver;version="5.1.11";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.transport",
+ org.eclipse.jgit.treewalk;version="5.1.11";
   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.0.4";uses:="org.eclipse.jgit.treewalk",
- org.eclipse.jgit.util;version="5.0.4";
+ org.eclipse.jgit.treewalk.filter;version="5.1.11";uses:="org.eclipse.jgit.treewalk",
+ org.eclipse.jgit.util;version="5.1.11";
   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.0.4",
- org.eclipse.jgit.util.sha1;version="5.0.4",
- org.eclipse.jgit.util.time;version="5.0.4"
+ org.eclipse.jgit.util.io;version="5.1.11",
+ org.eclipse.jgit.util.sha1;version="5.1.11",
+ org.eclipse.jgit.util.time;version="5.1.11"
 Bundle-RequiredExecutionEnvironment: JavaSE-1.8
 Import-Package: com.googlecode.javaewah;version="[1.1.6,2.0.0)",
  com.jcraft.jsch;version="[0.1.37,0.2.0)",
diff --git a/org.eclipse.jgit/META-INF/SOURCE-MANIFEST.MF b/org.eclipse.jgit/META-INF/SOURCE-MANIFEST.MF
index b2d694a..d431002 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.0.4.qualifier
-Eclipse-SourceBundle: org.eclipse.jgit;version="5.0.4.qualifier";roots="."
+Bundle-Version: 5.1.11.qualifier
+Eclipse-SourceBundle: org.eclipse.jgit;version="5.1.11.qualifier";roots="."
diff --git a/org.eclipse.jgit/pom.xml b/org.eclipse.jgit/pom.xml
index 2b56d0a..b97e873 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.0.4-SNAPSHOT</version>
+    <version>5.1.11-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit</artifactId>
@@ -84,10 +84,6 @@
       <artifactId>JavaEWAH</artifactId>
     </dependency>
 
-    <dependency>
-      <groupId>org.apache.httpcomponents</groupId>
-      <artifactId>httpclient</artifactId>
-    </dependency>
 
     <dependency>
       <groupId>org.slf4j</groupId>
@@ -213,6 +209,7 @@
         <plugin>
           <groupId>com.github.spotbugs</groupId>
           <artifactId>spotbugs-maven-plugin</artifactId>
+          <version>${spotbugs-maven-plugin-version}</version>
           <configuration>
             <excludeFilterFile>findBugs/FindBugsExcludeFilter.xml</excludeFilterFile>
           </configuration>
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 017662d..dc1db81 100644
--- a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties
+++ b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties
@@ -17,6 +17,7 @@
 archiveFormatAlreadyAbsent=Archive format already absent: {0}
 archiveFormatAlreadyRegistered=Archive format already registered with different implementation: {0}
 argumentIsNotAValidCommentString=Invalid comment: {0}
+assumeAtomicCreateNewFile=Reading option "core.supportsAtomicFileCreation" failed, fallback to default assuming atomic file creation is supported
 atLeastOnePathIsRequired=At least one path is required.
 atLeastOnePatternIsRequired=At least one pattern is required.
 atLeastTwoFiltersNeeded=At least two filters needed.
@@ -104,6 +105,7 @@
 cannotReadTree=Cannot read tree {0}
 cannotRebaseWithoutCurrentHead=Can not rebase without a current HEAD
 cannotResolveLocalTrackingRefForUpdating=Cannot resolve local tracking ref {0} for updating.
+cannotSaveConfig=Cannot save config file ''{0}''
 cannotSquashFixupWithoutPreviousCommit=Cannot {0} without previous commit.
 cannotStoreObjects=cannot store objects
 cannotResolveUniquelyAbbrevObjectId=Could not resolve uniquely the abbreviated object ID
@@ -301,8 +303,9 @@
 expectedPktLineWithService=expected pkt-line with ''# service=-'', got ''{0}''
 expectedReceivedContentType=expected Content-Type {0}; received Content-Type {1}
 expectedReportForRefNotReceived={0}: expected report for ref {1} not received
-failedAtomicFileCreation=Atomic file creation failed, number of hard links to file {0} was not 2 but {1}"
-failedToDetermineFilterDefinition=An exception occured while determining filter definitions
+failedAtomicFileCreation=Atomic file creation failed, number of hard links to file {0} was not 2 but {1}
+failedCreateLockFile=Creating lock file {} failed
+failedToDetermineFilterDefinition=An exception occurred while determining filter definitions
 failedUpdatingRefs=failed updating refs
 failureDueToOneOfTheFollowing=Failure due to one of the following:
 failureUpdatingFETCH_HEAD=Failure updating FETCH_HEAD: {0}
@@ -389,7 +392,9 @@
 invalidPathPeriodAtEndWindows=Invalid path (period at end is ignored by Windows): {0}
 invalidPathSpaceAtEndWindows=Invalid path (space at end is ignored by Windows): {0}
 invalidPathReservedOnWindows=Invalid path (''{0}'' is reserved on Windows): {1}
+invalidPurgeFactor=Invalid purgeFactor {0}, values have to be in range between 0 and 1
 invalidRedirectLocation=Invalid redirect location {0} -> {1}
+invalidRefAdvertisementLine=Invalid ref advertisement line: ''{1}''
 invalidReflogRevision=Invalid reflog revision: {0}
 invalidRefName=Invalid ref name: {0}
 invalidReftableBlock=Invalid reftable block
@@ -426,6 +431,7 @@
 localRepository=local repository
 lockCountMustBeGreaterOrEqual1=lockCount must be >= 1
 lockError=lock error: {0}
+lockFailedRetry=locking {0} failed after {1} retries
 lockOnNotClosed=Lock on {0} not closed.
 lockOnNotHeld=Lock on {0} not held.
 malformedpersonIdentString=Malformed PersonIdent string (no < was found): {0}
@@ -556,8 +562,11 @@
 pushNotPermitted=push not permitted
 pushOptionsNotSupported=Push options not supported; received {0}
 rawLogMessageDoesNotParseAsLogEntry=Raw log message does not parse as log entry
+readConfigFailed=Reading config file ''{0}'' failed
+readFileStoreAttributesFailed=Reading FileStore attributes from user config failed
 readerIsRequired=Reader is required
 readingObjectsFromLocalRepositoryFailed=reading objects from local repository failed: {0}
+readLastModifiedFailed=Reading lastModified of {0} failed
 readTimedOut=Read timed out after {0} ms
 receivePackObjectTooLarge1=Object too large, rejecting the pack. Max object size limit is {0} bytes.
 receivePackObjectTooLarge2=Object too large ({0} bytes), rejecting the pack. Max object size limit is {1} bytes.
@@ -582,6 +591,7 @@
 renameBranchFailedBecauseTag=Can not rename as Ref {0} is a tag
 renameBranchFailedUnknownReason=Rename failed with unknown reason
 renameBranchUnexpectedResult=Unexpected rename result {0}
+renameCancelled=Rename detection was cancelled
 renameFileFailed=Could not rename file {0} to {1}
 renamesAlreadyFound=Renames have already been found.
 renamesBreakingModifies=Breaking apart modified file pairs
@@ -610,6 +620,7 @@
 s3ActionDeletion=Deletion
 s3ActionReading=Reading
 s3ActionWriting=Writing
+saveFileStoreAttributesFailed=Saving measured FileStore attributes to user config failed
 searchForReuse=Finding sources
 searchForSizes=Getting sizes
 secondsAgo={0} seconds ago
@@ -678,6 +689,7 @@
 threadInterruptedWhileRunning="Current thread interrupted while running {0}"
 timeIsUncertain=Time is uncertain
 timerAlreadyTerminated=Timer already terminated
+timeoutMeasureFsTimestampResolution=measuring filesystem timestamp resolution for ''{0}'' timed out, fall back to resolution of 2 seconds
 tooManyCommands=Too many commands
 tooManyFilters=Too many "filter" lines in request
 tooManyIncludeRecursions=Too many recursions; circular includes in config file(s)?
@@ -766,9 +778,9 @@
 upstreamBranchName=branch ''{0}'' of {1}
 uriNotConfigured=Submodule URI not configured
 uriNotFound={0} not found
+uriNotFoundWithMessage={0} not found: {1}
 URINotSupported=URI not supported: {0}
-URLNotFound={0} not found
-userConfigFileInvalid=User config file {0} invalid {1}
+userConfigInvalid=Git config in the user's home directory {0} is invalid {1}
 walkFailure=Walk failure.
 wantNotValid=want {0} not valid
 weeksAgo={0} weeks ago
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/AddCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/AddCommand.java
index f0408ab..a2cd4ae 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/AddCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/AddCommand.java
@@ -50,6 +50,7 @@
 
 import java.io.IOException;
 import java.io.InputStream;
+import java.time.Instant;
 import java.util.Collection;
 import java.util.LinkedList;
 
@@ -228,7 +229,7 @@ public DirCache call() throws GitAPIException, NoFilepatternException {
 
 				if (GITLINK != mode) {
 					entry.setLength(f.getEntryLength());
-					entry.setLastModified(f.getEntryLastModified());
+					entry.setLastModified(f.getEntryLastModifiedInstant());
 					long len = f.getEntryContentLength();
 					// We read and filter the content multiple times.
 					// f.getEntryContentLength() reads and filters the input and
@@ -241,7 +242,7 @@ public DirCache call() throws GitAPIException, NoFilepatternException {
 					}
 				} else {
 					entry.setLength(0);
-					entry.setLastModified(0);
+					entry.setLastModified(Instant.ofEpochSecond(0));
 					entry.setObjectId(f.getEntryObjectId());
 				}
 				builder.add(entry);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/ArchiveCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/ArchiveCommand.java
index 27bb5a9..3f7306b 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/ArchiveCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/ArchiveCommand.java
@@ -56,13 +56,18 @@
 
 import org.eclipse.jgit.api.errors.GitAPIException;
 import org.eclipse.jgit.api.errors.JGitInternalException;
+import org.eclipse.jgit.errors.IncorrectObjectTypeException;
 import org.eclipse.jgit.internal.JGitText;
+import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.FileMode;
 import org.eclipse.jgit.lib.MutableObjectId;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.ObjectLoader;
 import org.eclipse.jgit.lib.ObjectReader;
 import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevObject;
+import org.eclipse.jgit.revwalk.RevTree;
 import org.eclipse.jgit.revwalk.RevWalk;
 import org.eclipse.jgit.treewalk.TreeWalk;
 import org.eclipse.jgit.treewalk.filter.PathFilterGroup;
@@ -375,13 +380,15 @@ private <T extends Closeable> OutputStream writeArchive(Format<T> fmt) {
 				MutableObjectId idBuf = new MutableObjectId();
 				ObjectReader reader = walk.getObjectReader();
 
-				walk.reset(rw.parseTree(tree));
-				if (!paths.isEmpty())
+				RevObject o = rw.peel(rw.parseAny(tree));
+				walk.reset(getTree(o));
+				if (!paths.isEmpty()) {
 					walk.setFilter(PathFilterGroup.createFromStrings(paths));
+				}
 
 				// Put base directory into archive
 				if (pfx.endsWith("/")) { //$NON-NLS-1$
-					fmt.putEntry(outa, tree, pfx.replaceAll("[/]+$", "/"), //$NON-NLS-1$ //$NON-NLS-2$
+					fmt.putEntry(outa, o, pfx.replaceAll("[/]+$", "/"), //$NON-NLS-1$ //$NON-NLS-2$
 							FileMode.TREE, null);
 				}
 
@@ -392,17 +399,18 @@ private <T extends Closeable> OutputStream writeArchive(Format<T> fmt) {
 					if (walk.isSubtree())
 						walk.enterSubtree();
 
-					if (mode == FileMode.GITLINK)
+					if (mode == FileMode.GITLINK) {
 						// TODO(jrn): Take a callback to recurse
 						// into submodules.
 						mode = FileMode.TREE;
+					}
 
 					if (mode == FileMode.TREE) {
-						fmt.putEntry(outa, tree, name + "/", mode, null); //$NON-NLS-1$
+						fmt.putEntry(outa, o, name + "/", mode, null); //$NON-NLS-1$
 						continue;
 					}
 					walk.getObjectId(idBuf, 0);
-					fmt.putEntry(outa, tree, name, mode, reader.open(idBuf));
+					fmt.putEntry(outa, o, name, mode, reader.open(idBuf));
 				}
 				return out;
 			} finally {
@@ -534,4 +542,19 @@ public ArchiveCommand setPaths(String... paths) {
 		this.paths = Arrays.asList(paths);
 		return this;
 	}
+
+	private RevTree getTree(RevObject o)
+			throws IncorrectObjectTypeException {
+		final RevTree t;
+		if (o instanceof RevCommit) {
+			t = ((RevCommit) o).getTree();
+		} else if (!(o instanceof RevTree)) {
+			throw new IncorrectObjectTypeException(tree.toObjectId(),
+					Constants.TYPE_TREE);
+		} else {
+			t = (RevTree) o;
+		}
+		return t;
+	}
+
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/CleanCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/CleanCommand.java
index 0d9fe41..43085dc 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CleanCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CleanCommand.java
@@ -136,7 +136,7 @@ public Set<String> call() throws NoWorkTreeException, GitAPIException {
 		} catch (IOException e) {
 			throw new JGitInternalException(e.getMessage(), e);
 		} finally {
-			if (!files.isEmpty()) {
+			if (!dryRun && !files.isEmpty()) {
 				repo.fireEvent(new WorkingTreeModifiedEvent(null, files));
 			}
 		}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/CloneCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/CloneCommand.java
index 5c06bac..eee3da6 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CloneCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CloneCommand.java
@@ -700,7 +700,7 @@ private void cleanup() {
 					FileUtils.delete(gitDir, FileUtils.RECURSIVE
 							| FileUtils.SKIP_MISSING | FileUtils.IGNORE_ERRORS);
 				} else {
-					deleteChildren(directory);
+					deleteChildren(gitDir);
 				}
 			}
 		} catch (IOException e) {
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 d07532c..cea28fa 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java
@@ -392,7 +392,7 @@ private DirCache createTemporaryIndex(ObjectId headId, DirCache index,
 						final DirCacheEntry dcEntry = new DirCacheEntry(path);
 						long entryLength = fTree.getEntryLength();
 						dcEntry.setLength(entryLength);
-						dcEntry.setLastModified(fTree.getEntryLastModified());
+						dcEntry.setLastModified(fTree.getEntryLastModifiedInstant());
 						dcEntry.setFileMode(fTree.getIndexFileMode(dcTree));
 
 						boolean objectExists = (dcTree != null
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 98c16b8..9653c36 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java
@@ -43,6 +43,8 @@
  */
 package org.eclipse.jgit.api;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
+
 import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.FileNotFoundException;
@@ -275,9 +277,7 @@ public RebaseResult call() throws GitAPIException, NoHeadException,
 					throw new JGitInternalException(ioe.getMessage(), ioe);
 				}
 			case PROCESS_STEPS:
-				// fall through
 			case SKIP:
-				// fall through
 			case CONTINUE:
 				String upstreamCommitId = rebaseState.readFile(ONTO);
 				try {
@@ -1017,8 +1017,7 @@ private RebaseResult stop(RevCommit commitToPick, RebaseResult.Status status)
 			df.setRepository(repo);
 			df.format(commitToPick.getParent(0), commitToPick);
 		}
-		rebaseState.createFile(PATCH, new String(bos.toByteArray(),
-				Constants.CHARACTER_ENCODING));
+		rebaseState.createFile(PATCH, new String(bos.toByteArray(), UTF_8));
 		rebaseState.createFile(STOPPED_SHA,
 				repo.newObjectReader()
 				.abbreviate(
@@ -1735,7 +1734,7 @@ private static void createFile(File parentDir, String name,
 				throws IOException {
 			File file = new File(parentDir, name);
 			try (FileOutputStream fos = new FileOutputStream(file)) {
-				fos.write(content.getBytes(Constants.CHARACTER_ENCODING));
+				fos.write(content.getBytes(UTF_8));
 				fos.write('\n');
 			}
 		}
@@ -1743,7 +1742,7 @@ private static void createFile(File parentDir, String name,
 		private static void appendToFile(File file, String content)
 				throws IOException {
 			try (FileOutputStream fos = new FileOutputStream(file, true)) {
-				fos.write(content.getBytes(Constants.CHARACTER_ENCODING));
+				fos.write(content.getBytes(UTF_8));
 				fos.write('\n');
 			}
 		}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/ResetCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/ResetCommand.java
index 13ce4e7..d7c9ad5 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/ResetCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/ResetCommand.java
@@ -422,7 +422,7 @@ private void resetIndex(ObjectId commitTree) throws IOException {
 						DirCacheIterator.class);
 				if (dcIter != null && dcIter.idEqual(cIter)) {
 					DirCacheEntry indexEntry = dcIter.getDirCacheEntry();
-					entry.setLastModified(indexEntry.getLastModified());
+					entry.setLastModified(indexEntry.getLastModifiedInstant());
 					entry.setLength(indexEntry.getLength());
 				}
 
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/StashApplyCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/StashApplyCommand.java
index 01d070c..ff7c4c6 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/StashApplyCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/StashApplyCommand.java
@@ -332,7 +332,7 @@ private void resetIndex(RevTree tree) throws IOException {
 						DirCacheIterator.class);
 				if (dcIter != null && dcIter.idEqual(cIter)) {
 					DirCacheEntry indexEntry = dcIter.getDirCacheEntry();
-					entry.setLastModified(indexEntry.getLastModified());
+					entry.setLastModified(indexEntry.getLastModifiedInstant());
 					entry.setLength(indexEntry.getLength());
 				}
 
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/StashCreateCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/StashCreateCommand.java
index c32890d..0d010ad 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/StashCreateCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/StashCreateCommand.java
@@ -300,7 +300,8 @@ public RevCommit call() throws GitAPIException {
 						final DirCacheEntry entry = new DirCacheEntry(
 								treeWalk.getRawPath());
 						entry.setLength(wtIter.getEntryLength());
-						entry.setLastModified(wtIter.getEntryLastModified());
+						entry.setLastModified(
+								wtIter.getEntryLastModifiedInstant());
 						entry.setFileMode(wtIter.getEntryFileMode());
 						long contentLength = wtIter.getEntryContentLength();
 						try (InputStream in = wtIter.openEntryStream()) {
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 9d9626f..244a156 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleAddCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleAddCommand.java
@@ -77,6 +77,8 @@
 public class SubmoduleAddCommand extends
 		TransportCommand<SubmoduleAddCommand, Repository> {
 
+	private String name;
+
 	private String path;
 
 	private String uri;
@@ -94,6 +96,18 @@ public SubmoduleAddCommand(Repository repo) {
 	}
 
 	/**
+	 * Set the submodule name
+	 *
+	 * @param name
+	 * @return this command
+	 * @since 5.1
+	 */
+	public SubmoduleAddCommand setName(String name) {
+		this.name = name;
+		return this;
+	}
+
+	/**
 	 * Set repository-relative path of submodule
 	 *
 	 * @param path
@@ -161,9 +175,28 @@ public Repository call() throws GitAPIException {
 			throw new IllegalArgumentException(JGitText.get().pathNotConfigured);
 		if (uri == null || uri.length() == 0)
 			throw new IllegalArgumentException(JGitText.get().uriNotConfigured);
+		if (name == null || name.length() == 0) {
+			// Use the path as the default.
+			name = path;
+		}
+		if (name.contains("/../") || name.contains("\\..\\") //$NON-NLS-1$ //$NON-NLS-2$
+				|| name.startsWith("../") || name.startsWith("..\\") //$NON-NLS-1$ //$NON-NLS-2$
+				|| name.endsWith("/..") || name.endsWith("\\..")) { //$NON-NLS-1$ //$NON-NLS-2$
+			// Submodule names are used to store the submodule repositories
+			// under $GIT_DIR/modules. Having ".." in submodule names makes a
+			// vulnerability (CVE-2018-11235
+			// https://bugs.eclipse.org/bugs/show_bug.cgi?id=535027#c0)
+			// Reject the names with them. The callers need to make sure the
+			// names free from these. We don't automatically replace these
+			// characters or canonicalize by regarding the name as a file path.
+			// Since Path class is platform dependent, we manually check '/' and
+			// '\\' patterns here.
+			throw new IllegalArgumentException(MessageFormat
+					.format(JGitText.get().invalidNameContainsDotDot, name));
+		}
 
 		try {
-			SubmoduleValidator.assertValidSubmoduleName(path);
+			SubmoduleValidator.assertValidSubmoduleName(name);
 			SubmoduleValidator.assertValidSubmodulePath(path);
 			SubmoduleValidator.assertValidSubmoduleUri(uri);
 		} catch (SubmoduleValidator.SubmoduleValidationException e) {
@@ -202,7 +235,7 @@ public Repository call() throws GitAPIException {
 
 		// Save submodule URL to parent repository's config
 		StoredConfig config = repo.getConfig();
-		config.setString(ConfigConstants.CONFIG_SUBMODULE_SECTION, path,
+		config.setString(ConfigConstants.CONFIG_SUBMODULE_SECTION, name,
 				ConfigConstants.CONFIG_KEY_URL, resolvedUri);
 		try {
 			config.save();
@@ -216,9 +249,9 @@ public Repository call() throws GitAPIException {
 		try {
 			modulesConfig.load();
 			modulesConfig.setString(ConfigConstants.CONFIG_SUBMODULE_SECTION,
-					path, ConfigConstants.CONFIG_KEY_PATH, path);
+					name, ConfigConstants.CONFIG_KEY_PATH, path);
 			modulesConfig.setString(ConfigConstants.CONFIG_SUBMODULE_SECTION,
-					path, ConfigConstants.CONFIG_KEY_URL, uri);
+					name, ConfigConstants.CONFIG_KEY_URL, uri);
 			modulesConfig.save();
 		} catch (IOException 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 569a8e3..5a0528b 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleDeinitCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleDeinitCommand.java
@@ -227,11 +227,11 @@ private SubmoduleDeinitStatus checkDirty(RevWalk revWalk, String path)
 				return SubmoduleDeinitStatus.DIRTY;
 			}
 
-			Repository submoduleRepo = w.getRepository();
-
-			Status status = Git.wrap(submoduleRepo).status().call();
-			return status.isClean() ? SubmoduleDeinitStatus.SUCCESS
-					: SubmoduleDeinitStatus.DIRTY;
+			try (Repository submoduleRepo = w.getRepository()) {
+				Status status = Git.wrap(submoduleRepo).status().call();
+				return status.isClean() ? SubmoduleDeinitStatus.SUCCESS
+						: SubmoduleDeinitStatus.DIRTY;
+			}
 		}
 	}
 
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesNode.java b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesNode.java
index 62bf9f2..bcd7231 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesNode.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesNode.java
@@ -42,6 +42,8 @@
  */
 package org.eclipse.jgit.attributes;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
+
 import java.io.BufferedReader;
 import java.io.IOException;
 import java.io.InputStream;
@@ -50,8 +52,6 @@
 import java.util.Collections;
 import java.util.List;
 
-import org.eclipse.jgit.lib.Constants;
-
 /**
  * Represents a bundle of attributes inherited from a base directory.
  *
@@ -115,7 +115,7 @@ else if (patternEndTab == -1)
 	}
 
 	private static BufferedReader asReader(InputStream in) {
-		return new BufferedReader(new InputStreamReader(in, Constants.CHARSET));
+		return new BufferedReader(new InputStreamReader(in, UTF_8));
 	}
 
 	/**
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/blame/BlameGenerator.java b/org.eclipse.jgit/src/org/eclipse/jgit/blame/BlameGenerator.java
index 1ad7a30..9cec645 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/blame/BlameGenerator.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/blame/BlameGenerator.java
@@ -539,7 +539,7 @@ private boolean result(Candidate n) throws IOException {
 		n.beginResult(revPool);
 		outCandidate = n;
 		outRegion = n.regionList;
-		return true;
+		return outRegion != null;
 	}
 
 	private boolean reverseResult(Candidate parent, Candidate source)
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffConfig.java
index 21dca6b..ca37a10 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffConfig.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffConfig.java
@@ -82,7 +82,7 @@ private DiffConfig(Config rc) {
 		renameDetectionType = parseRenameDetectionType(rc.getString(
 				ConfigConstants.CONFIG_DIFF_SECTION, null, ConfigConstants.CONFIG_KEY_RENAMES));
 		renameLimit = rc.getInt(ConfigConstants.CONFIG_DIFF_SECTION,
-				ConfigConstants.CONFIG_KEY_RENAMELIMIT, 200);
+				ConfigConstants.CONFIG_KEY_RENAMELIMIT, 400);
 	}
 
 	/**
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 7aaa500..e7ad0bc 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffFormatter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffFormatter.java
@@ -67,6 +67,7 @@
 import org.eclipse.jgit.dircache.DirCacheIterator;
 import org.eclipse.jgit.errors.AmbiguousObjectException;
 import org.eclipse.jgit.errors.BinaryBlobException;
+import org.eclipse.jgit.errors.CancelledException;
 import org.eclipse.jgit.errors.CorruptObjectException;
 import org.eclipse.jgit.errors.IncorrectObjectTypeException;
 import org.eclipse.jgit.errors.MissingObjectException;
@@ -577,7 +578,14 @@ private List<DiffEntry> detectRenames(List<DiffEntry> files)
 			throws IOException {
 		renameDetector.reset();
 		renameDetector.addAll(files);
-		return renameDetector.compute(reader, progressMonitor);
+		try {
+			return renameDetector.compute(reader, progressMonitor);
+		} catch (CancelledException e) {
+			// TODO: consider propagating once bug 536323 is tackled
+			// (making DiffEntry.scan() and DiffFormatter.scan() and
+			// format() cancellable).
+			return Collections.emptyList();
+		}
 	}
 
 	private boolean isAdd(List<DiffEntry> files) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/diff/RenameDetector.java b/org.eclipse.jgit/src/org/eclipse/jgit/diff/RenameDetector.java
index 7bb217d..772fbb5 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/diff/RenameDetector.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/diff/RenameDetector.java
@@ -57,6 +57,7 @@
 
 import org.eclipse.jgit.diff.DiffEntry.ChangeType;
 import org.eclipse.jgit.diff.SimilarityIndex.TableFullException;
+import org.eclipse.jgit.errors.CancelledException;
 import org.eclipse.jgit.internal.JGitText;
 import org.eclipse.jgit.lib.AbbreviatedObjectId;
 import org.eclipse.jgit.lib.FileMode;
@@ -332,8 +333,13 @@ public List<DiffEntry> compute() throws IOException {
 	 *         representing all files that have been changed.
 	 * @throws java.io.IOException
 	 *             file contents cannot be read from the repository.
+	 * @throws CancelledException
+	 *             if rename detection was cancelled
 	 */
-	public List<DiffEntry> compute(ProgressMonitor pm) throws IOException {
+	// TODO(ms): use org.eclipse.jgit.api.errors.CanceledException in next major
+	// version
+	public List<DiffEntry> compute(ProgressMonitor pm)
+			throws IOException, CancelledException {
 		if (!done) {
 			try {
 				return compute(objectReader, pm);
@@ -355,9 +361,13 @@ public List<DiffEntry> compute(ProgressMonitor pm) throws IOException {
 	 *         representing all files that have been changed.
 	 * @throws java.io.IOException
 	 *             file contents cannot be read from the repository.
+	 * @throws CancelledException
+	 *             if rename detection was cancelled
 	 */
+	// TODO(ms): use org.eclipse.jgit.api.errors.CanceledException in next major
+	// version
 	public List<DiffEntry> compute(ObjectReader reader, ProgressMonitor pm)
-			throws IOException {
+			throws IOException, CancelledException {
 		final ContentSource cs = ContentSource.create(reader);
 		return compute(new ContentSource.Pair(cs, cs), pm);
 	}
@@ -373,9 +383,13 @@ public List<DiffEntry> compute(ObjectReader reader, ProgressMonitor pm)
 	 *         representing all files that have been changed.
 	 * @throws java.io.IOException
 	 *             file contents cannot be read from the repository.
+	 * @throws CancelledException
+	 *             if rename detection was cancelled
 	 */
+	// TODO(ms): use org.eclipse.jgit.api.errors.CanceledException in next major
+	// version
 	public List<DiffEntry> compute(ContentSource.Pair reader, ProgressMonitor pm)
-			throws IOException {
+			throws IOException, CancelledException {
 		if (!done) {
 			done = true;
 
@@ -415,8 +429,15 @@ public void reset() {
 		done = false;
 	}
 
+	private void advanceOrCancel(ProgressMonitor pm) throws CancelledException {
+		if (pm.isCancelled()) {
+			throw new CancelledException(JGitText.get().renameCancelled);
+		}
+		pm.update(1);
+	}
+
 	private void breakModifies(ContentSource.Pair reader, ProgressMonitor pm)
-			throws IOException {
+			throws IOException, CancelledException {
 		ArrayList<DiffEntry> newEntries = new ArrayList<>(entries.size());
 
 		pm.beginTask(JGitText.get().renamesBreakingModifies, entries.size());
@@ -437,13 +458,13 @@ private void breakModifies(ContentSource.Pair reader, ProgressMonitor pm)
 			} else {
 				newEntries.add(e);
 			}
-			pm.update(1);
+			advanceOrCancel(pm);
 		}
 
 		entries = newEntries;
 	}
 
-	private void rejoinModifies(ProgressMonitor pm) {
+	private void rejoinModifies(ProgressMonitor pm) throws CancelledException {
 		HashMap<String, DiffEntry> nameMap = new HashMap<>();
 		ArrayList<DiffEntry> newAdded = new ArrayList<>(added.size());
 
@@ -452,7 +473,7 @@ private void rejoinModifies(ProgressMonitor pm) {
 
 		for (DiffEntry src : deleted) {
 			nameMap.put(src.oldPath, src);
-			pm.update(1);
+			advanceOrCancel(pm);
 		}
 
 		for (DiffEntry dst : added) {
@@ -468,7 +489,7 @@ private void rejoinModifies(ProgressMonitor pm) {
 			} else {
 				newAdded.add(dst);
 			}
-			pm.update(1);
+			advanceOrCancel(pm);
 		}
 
 		added = newAdded;
@@ -498,7 +519,7 @@ private int calculateModifyScore(ContentSource.Pair reader, DiffEntry d)
 
 	private void findContentRenames(ContentSource.Pair reader,
 			ProgressMonitor pm)
-			throws IOException {
+			throws IOException, CancelledException {
 		int cnt = Math.max(added.size(), deleted.size());
 		if (getRenameLimit() == 0 || cnt <= getRenameLimit()) {
 			SimilarityRenameDetector d;
@@ -516,7 +537,8 @@ private void findContentRenames(ContentSource.Pair reader,
 	}
 
 	@SuppressWarnings("unchecked")
-	private void findExactRenames(ProgressMonitor pm) {
+	private void findExactRenames(ProgressMonitor pm)
+			throws CancelledException {
 		pm.beginTask(JGitText.get().renamesFindingExact, //
 				added.size() + added.size() + deleted.size()
 						+ added.size() * deleted.size());
@@ -562,7 +584,7 @@ private void findExactRenames(ProgressMonitor pm) {
 			} else {
 				left.add(a);
 			}
-			pm.update(1);
+			advanceOrCancel(pm);
 		}
 
 		for (List<DiffEntry> adds : nonUniqueAdds) {
@@ -604,6 +626,10 @@ private void findExactRenames(ProgressMonitor pm) {
 						int score = SimilarityRenameDetector.nameScore(addedName, deletedName);
 						matrix[mNext] = SimilarityRenameDetector.encode(score, delIdx, addIdx);
 						mNext++;
+						if (pm.isCancelled()) {
+							throw new CancelledException(
+									JGitText.get().renameCancelled);
+						}
 					}
 				}
 
@@ -617,7 +643,7 @@ private void findExactRenames(ProgressMonitor pm) {
 					DiffEntry a = adds.get(addIdx);
 
 					if (a == null) {
-						pm.update(1);
+						advanceOrCancel(pm);
 						continue; // was already matched earlier
 					}
 
@@ -635,11 +661,12 @@ private void findExactRenames(ProgressMonitor pm) {
 
 					entries.add(DiffEntry.pair(type, d, a, 100));
 					adds.set(addIdx, null); // Claim the destination was matched.
-					pm.update(1);
+					advanceOrCancel(pm);
 				}
 			} else {
 				left.addAll(adds);
 			}
+			advanceOrCancel(pm);
 		}
 		added = left;
 
@@ -692,7 +719,8 @@ private static DiffEntry bestPathMatch(DiffEntry src, List<DiffEntry> list) {
 
 	@SuppressWarnings("unchecked")
 	private HashMap<AbbreviatedObjectId, Object> populateMap(
-			List<DiffEntry> diffEntries, ProgressMonitor pm) {
+			List<DiffEntry> diffEntries, ProgressMonitor pm)
+			throws CancelledException {
 		HashMap<AbbreviatedObjectId, Object> map = new HashMap<>();
 		for (DiffEntry de : diffEntries) {
 			Object old = map.put(id(de), de);
@@ -706,7 +734,7 @@ private HashMap<AbbreviatedObjectId, Object> populateMap(
 				((List<DiffEntry>) old).add(de);
 				map.put(id(de), old);
 			}
-			pm.update(1);
+			advanceOrCancel(pm);
 		}
 		return map;
 	}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/diff/SimilarityRenameDetector.java b/org.eclipse.jgit/src/org/eclipse/jgit/diff/SimilarityRenameDetector.java
index 653658b..d8a05c3 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/diff/SimilarityRenameDetector.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/diff/SimilarityRenameDetector.java
@@ -54,6 +54,7 @@
 
 import org.eclipse.jgit.diff.DiffEntry.ChangeType;
 import org.eclipse.jgit.diff.SimilarityIndex.TableFullException;
+import org.eclipse.jgit.errors.CancelledException;
 import org.eclipse.jgit.internal.JGitText;
 import org.eclipse.jgit.lib.FileMode;
 import org.eclipse.jgit.lib.NullProgressMonitor;
@@ -128,7 +129,7 @@ void setRenameScore(int score) {
 		renameScore = score;
 	}
 
-	void compute(ProgressMonitor pm) throws IOException {
+	void compute(ProgressMonitor pm) throws IOException, CancelledException {
 		if (pm == null)
 			pm = NullProgressMonitor.INSTANCE;
 
@@ -142,6 +143,11 @@ void compute(ProgressMonitor pm) throws IOException {
 		// we have looked at everything that is above our minimum score.
 		//
 		for (--mNext; mNext >= 0; mNext--) {
+			if (pm.isCancelled()) {
+				// TODO(ms): use org.eclipse.jgit.api.errors.CanceledException
+				// in next major version
+				throw new CancelledException(JGitText.get().renameCancelled);
+			}
 			long ent = matrix[mNext];
 			int sIdx = srcFile(ent);
 			int dIdx = dstFile(ent);
@@ -209,7 +215,8 @@ private static List<DiffEntry> compactDstList(List<DiffEntry> in) {
 		return r;
 	}
 
-	private int buildMatrix(ProgressMonitor pm) throws IOException {
+	private int buildMatrix(ProgressMonitor pm)
+			throws IOException, CancelledException {
 		// Allocate for the worst-case scenario where every pair has a
 		// score that we need to consider. We might not need that many.
 		//
@@ -234,6 +241,14 @@ private int buildMatrix(ProgressMonitor pm) throws IOException {
 			SimilarityIndex s = null;
 
 			for (int dstIdx = 0; dstIdx < dsts.size(); dstIdx++) {
+				if (pm.isCancelled()) {
+					// TODO(ms): use
+					// org.eclipse.jgit.api.errors.CanceledException in next
+					// major version
+					throw new CancelledException(
+							JGitText.get().renameCancelled);
+				}
+
 				DiffEntry dstEnt = dsts.get(dstIdx);
 
 				if (!isFile(dstEnt.newMode)) {
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 14653fe..a778de9 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCache.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCache.java
@@ -58,6 +58,7 @@
 import java.security.DigestOutputStream;
 import java.security.MessageDigest;
 import java.text.MessageFormat;
+import java.time.Instant;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Comparator;
@@ -497,8 +498,7 @@ else if (ver != 2)
 			throw new CorruptObjectException(JGitText.get().DIRCHasTooManyEntries);
 
 		snapshot = FileSnapshot.save(liveFile);
-		int smudge_s = (int) (snapshot.lastModified() / 1000);
-		int smudge_ns = ((int) (snapshot.lastModified() % 1000)) * 1000000;
+		Instant smudge = snapshot.lastModifiedInstant();
 
 		// Load the individual file entries.
 		//
@@ -507,8 +507,9 @@ else if (ver != 2)
 		sortedEntries = new DirCacheEntry[entryCnt];
 
 		final MutableInteger infoAt = new MutableInteger();
-		for (int i = 0; i < entryCnt; i++)
-			sortedEntries[i] = new DirCacheEntry(infos, infoAt, in, md, smudge_s, smudge_ns);
+		for (int i = 0; i < entryCnt; i++) {
+			sortedEntries[i] = new DirCacheEntry(infos, infoAt, in, md, smudge);
+		}
 
 		// After the file entries are index extensions, and then a footer.
 		//
@@ -670,8 +671,7 @@ void writeTo(File dir, OutputStream os) throws IOException {
 
 		// Write the individual file entries.
 
-		final int smudge_s;
-		final int smudge_ns;
+		Instant smudge;
 		if (myLock != null) {
 			// For new files we need to smudge the index entry
 			// if they have been modified "now". Ideally we'd
@@ -679,12 +679,10 @@ void writeTo(File dir, OutputStream os) throws IOException {
 			// so we use the current timestamp as a approximation.
 			myLock.createCommitSnapshot();
 			snapshot = myLock.getCommitSnapshot();
-			smudge_s = (int) (snapshot.lastModified() / 1000);
-			smudge_ns = ((int) (snapshot.lastModified() % 1000)) * 1000000;
+			smudge = snapshot.lastModifiedInstant();
 		} else {
 			// Used in unit tests only
-			smudge_ns = 0;
-			smudge_s = 0;
+			smudge = Instant.EPOCH;
 		}
 
 		// Check if tree is non-null here since calling updateSmudgedEntries
@@ -696,8 +694,9 @@ void writeTo(File dir, OutputStream os) throws IOException {
 
 		for (int i = 0; i < entryCnt; i++) {
 			final DirCacheEntry e = sortedEntries[i];
-			if (e.mightBeRacilyClean(smudge_s, smudge_ns))
+			if (e.mightBeRacilyClean(smudge)) {
 				e.smudgeRacilyClean();
+			}
 			e.write(dos);
 		}
 
@@ -1025,7 +1024,7 @@ private void updateSmudgedEntries() throws IOException {
 				DirCacheEntry entry = iIter.getDirCacheEntry();
 				if (entry.isSmudged() && iIter.idEqual(fIter)) {
 					entry.setLength(fIter.getEntryLength());
-					entry.setLastModified(fIter.getEntryLastModified());
+					entry.setLastModified(fIter.getEntryLastModifiedInstant());
 				}
 			}
 		}
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 0b03eb1..5110d77 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java
@@ -50,6 +50,7 @@
 import java.io.OutputStream;
 import java.nio.file.StandardCopyOption;
 import java.text.MessageFormat;
+import java.time.Instant;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.Iterator;
@@ -425,8 +426,10 @@ void processEntry(CanonicalTreeParser m, DirCacheBuildIterator i,
 					// update the timestamp of the index with the one from the
 					// file if not set, as we are sure to be in sync here.
 					DirCacheEntry entry = i.getDirCacheEntry();
-					if (entry.getLastModified() == 0)
-						entry.setLastModified(f.getEntryLastModified());
+					Instant mtime = entry.getLastModifiedInstant();
+					if (mtime == null || mtime.equals(Instant.EPOCH)) {
+						entry.setLastModified(f.getEntryLastModifiedInstant());
+					}
 					keep(entry);
 				}
 			} else
@@ -513,7 +516,7 @@ private boolean doCheckout() throws CorruptObjectException, IOException,
 
 			if (!conflicts.isEmpty()) {
 				if (failOnConflict)
-					throw new CheckoutConflictException(conflicts.toArray(new String[conflicts.size()]));
+					throw new CheckoutConflictException(conflicts.toArray(new String[0]));
 				else
 					cleanUpConflicts();
 			}
@@ -611,7 +614,7 @@ private void checkoutGitlink(String path, DirCacheEntry entry)
 		File gitlinkDir = new File(repo.getWorkTree(), path);
 		FileUtils.mkdirs(gitlinkDir, true);
 		FS fs = repo.getFS();
-		entry.setLastModified(fs.lastModified(gitlinkDir));
+		entry.setLastModified(fs.lastModifiedInstant(gitlinkDir));
 	}
 
 	private static ArrayList<String> filterOut(ArrayList<String> strings,
@@ -1366,7 +1369,11 @@ private boolean isModifiedSubtree_IndexTree(String path, ObjectId tree)
 	 *            object reader to use for checkout
 	 * @throws java.io.IOException
 	 * @since 3.6
+	 * @deprecated since 5.1, use
+	 *             {@link #checkoutEntry(Repository, DirCacheEntry, ObjectReader, boolean, CheckoutMetadata)}
+	 *             instead
 	 */
+	@Deprecated
 	public static void checkoutEntry(Repository repo, DirCacheEntry entry,
 			ObjectReader or) throws IOException {
 		checkoutEntry(repo, entry, or, false, null);
@@ -1429,7 +1436,7 @@ public static void checkoutEntry(Repository repo, DirCacheEntry entry,
 			}
 			fs.createSymLink(f, target);
 			entry.setLength(bytes.length);
-			entry.setLastModified(fs.lastModified(f));
+			entry.setLastModified(fs.lastModifiedInstant(f));
 			return;
 		}
 
@@ -1498,7 +1505,7 @@ public static void checkoutEntry(Repository repo, DirCacheEntry entry,
 				FileUtils.delete(tmpFile);
 			}
 		}
-		entry.setLastModified(fs.lastModified(f));
+		entry.setLastModified(fs.lastModifiedInstant(f));
 	}
 
 	// Run an external filter command
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 fee9f51..b118fd6 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEntry.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEntry.java
@@ -46,6 +46,8 @@
 
 package org.eclipse.jgit.dircache;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
+
 import java.io.ByteArrayOutputStream;
 import java.io.EOFException;
 import java.io.IOException;
@@ -54,6 +56,7 @@
 import java.nio.ByteBuffer;
 import java.security.MessageDigest;
 import java.text.MessageFormat;
+import java.time.Instant;
 import java.util.Arrays;
 
 import org.eclipse.jgit.errors.CorruptObjectException;
@@ -143,8 +146,8 @@ public class DirCacheEntry {
 	private byte inCoreFlags;
 
 	DirCacheEntry(final byte[] sharedInfo, final MutableInteger infoAt,
-			final InputStream in, final MessageDigest md, final int smudge_s,
-			final int smudge_ns) throws IOException {
+			final InputStream in, final MessageDigest md, final Instant smudge)
+			throws IOException {
 		info = sharedInfo;
 		infoOffset = infoAt.value;
 
@@ -213,8 +216,9 @@ public class DirCacheEntry {
 			md.update(nullpad, 0, padLen);
 		}
 
-		if (mightBeRacilyClean(smudge_s, smudge_ns))
+		if (mightBeRacilyClean(smudge)) {
 			smudgeRacilyClean();
+		}
 	}
 
 	/**
@@ -342,8 +346,29 @@ void write(OutputStream os) throws IOException {
 	 * @param smudge_ns
 	 *            nanoseconds component of the index's last modified time.
 	 * @return true if extra careful checks should be used.
+	 * @deprecated use {@link #mightBeRacilyClean(Instant)} instead
 	 */
+	@Deprecated
 	public final boolean mightBeRacilyClean(int smudge_s, int smudge_ns) {
+		return mightBeRacilyClean(Instant.ofEpochSecond(smudge_s, smudge_ns));
+	}
+
+	/**
+	 * Is it possible for this entry to be accidentally assumed clean?
+	 * <p>
+	 * The "racy git" problem happens when a work file can be updated faster
+	 * than the filesystem records file modification timestamps. It is possible
+	 * for an application to edit a work file, update the index, then edit it
+	 * again before the filesystem will give the work file a new modification
+	 * timestamp. This method tests to see if file was written out at the same
+	 * time as the index.
+	 *
+	 * @param smudge
+	 *            index's last modified time.
+	 * @return true if extra careful checks should be used.
+	 * @since 5.1.9
+	 */
+	public final boolean mightBeRacilyClean(Instant smudge) {
 		// If the index has a modification time then it came from disk
 		// and was not generated from scratch in memory. In such cases
 		// the entry is 'racily clean' if the entry's cached modification
@@ -353,8 +378,9 @@ public final boolean mightBeRacilyClean(int smudge_s, int smudge_ns) {
 		//
 		final int base = infoOffset + P_MTIME;
 		final int mtime = NB.decodeInt32(info, base);
-		if (smudge_s == mtime)
-			return smudge_ns <= NB.decodeInt32(info, base + 4);
+		if ((int) smudge.getEpochSecond() == mtime) {
+			return smudge.getNano() <= NB.decodeInt32(info, base + 4);
+		}
 		return false;
 	}
 
@@ -561,22 +587,51 @@ public void setCreationTime(long when) {
 	 *
 	 * @return last modification time of this file, in milliseconds since the
 	 *         Java epoch (midnight Jan 1, 1970 UTC).
+	 * @deprecated use {@link #getLastModifiedInstant()} instead
 	 */
+	@Deprecated
 	public long getLastModified() {
 		return decodeTS(P_MTIME);
 	}
 
 	/**
+	 * Get the cached last modification date of this file.
+	 * <p>
+	 * One of the indicators that the file has been modified by an application
+	 * changing the working tree is if the last modification time for the file
+	 * differs from the time stored in this entry.
+	 *
+	 * @return last modification time of this file.
+	 * @since 5.1.9
+	 */
+	public Instant getLastModifiedInstant() {
+		return decodeTSInstant(P_MTIME);
+	}
+
+	/**
 	 * Set the cached last modification date of this file, using milliseconds.
 	 *
 	 * @param when
 	 *            new cached modification date of the file, in milliseconds.
+	 * @deprecated use {@link #setLastModified(Instant)} instead
 	 */
+	@Deprecated
 	public void setLastModified(long when) {
 		encodeTS(P_MTIME, when);
 	}
 
 	/**
+	 * Set the cached last modification date of this file.
+	 *
+	 * @param when
+	 *            new cached modification date of the file.
+	 * @since 5.1.9
+	 */
+	public void setLastModified(Instant when) {
+		encodeTS(P_MTIME, when);
+	}
+
+	/**
 	 * Get the cached size (mod 4 GB) (in bytes) of this file.
 	 * <p>
 	 * One of the indicators that the file has been modified by an application
@@ -690,7 +745,8 @@ public String getPathString() {
 	@SuppressWarnings("nls")
 	@Override
 	public String toString() {
-		return getFileMode() + " " + getLength() + " " + getLastModified()
+		return getFileMode() + " " + getLength() + " "
+				+ getLastModifiedInstant()
 				+ " " + getObjectId() + " " + getStage() + " "
 				+ getPathString() + "\n";
 	}
@@ -748,12 +804,25 @@ private long decodeTS(int pIdx) {
 		return 1000L * sec + ms;
 	}
 
+	private Instant decodeTSInstant(int pIdx) {
+		final int base = infoOffset + pIdx;
+		final int sec = NB.decodeInt32(info, base);
+		final int nano = NB.decodeInt32(info, base + 4);
+		return Instant.ofEpochSecond(sec, nano);
+	}
+
 	private void encodeTS(int pIdx, long when) {
 		final int base = infoOffset + pIdx;
 		NB.encodeInt32(info, base, (int) (when / 1000));
 		NB.encodeInt32(info, base + 4, ((int) (when % 1000)) * 1000000);
 	}
 
+	private void encodeTS(int pIdx, Instant when) {
+		final int base = infoOffset + pIdx;
+		NB.encodeInt32(info, base, (int) when.getEpochSecond());
+		NB.encodeInt32(info, base + 4, when.getNano());
+	}
+
 	private int getExtendedFlags() {
 		if (isExtended())
 			return NB.decodeUInt16(info, infoOffset + P_FLAGS2) << 16;
@@ -772,7 +841,7 @@ private static void checkPath(byte[] path) {
 	}
 
 	static String toString(byte[] path) {
-		return Constants.CHARSET.decode(ByteBuffer.wrap(path)).toString();
+		return UTF_8.decode(ByteBuffer.wrap(path)).toString();
 	}
 
 	static int getMaximumInfoLength(boolean extended) {
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 b605f3c..11a3474 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheTree.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheTree.java
@@ -44,6 +44,7 @@
 
 package org.eclipse.jgit.dircache;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.eclipse.jgit.lib.FileMode.TREE;
 import static org.eclipse.jgit.lib.TreeFormatter.entrySize;
 
@@ -276,7 +277,7 @@ public ObjectId getObjectId() {
 	 */
 	public String getNameString() {
 		final ByteBuffer bb = ByteBuffer.wrap(encodedName);
-		return Constants.CHARSET.decode(bb).toString();
+		return UTF_8.decode(bb).toString();
 	}
 
 	/**
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/ManifestParser.java b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/ManifestParser.java
index 929ffac..26e783d 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/ManifestParser.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/ManifestParser.java
@@ -252,6 +252,9 @@ public void startElement(
 							RepoText.get().errorIncludeFile, path), e);
 				}
 			}
+		} else if ("remove-project".equals(qName)) { //$NON-NLS-1$
+			String name = attributes.getValue("name"); //$NON-NLS-1$
+			projects.removeIf((p) -> p.getName().equals(name));
 		}
 	}
 
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoCommand.java
index 80fd3cf..5a73cdc 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoCommand.java
@@ -42,6 +42,7 @@
  */
 package org.eclipse.jgit.gitrepo;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.eclipse.jgit.lib.Constants.DEFAULT_REMOTE_NAME;
 import static org.eclipse.jgit.lib.Constants.R_REMOTES;
 
@@ -55,8 +56,8 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
-import java.util.Set;
 import java.util.StringJoiner;
+import java.util.TreeMap;
 
 import org.eclipse.jgit.annotations.Nullable;
 import org.eclipse.jgit.api.Git;
@@ -115,16 +116,15 @@ public class RepoCommand extends GitCommand<RevCommit> {
 	private String groupsParam;
 	private String branch;
 	private String targetBranch = Constants.HEAD;
-	private boolean recordRemoteBranch = false;
-	private boolean recordSubmoduleLabels = false;
-	private boolean recordShallowSubmodules = false;
+	private boolean recordRemoteBranch = true;
+	private boolean recordSubmoduleLabels = true;
+	private boolean recordShallowSubmodules = true;
 	private PersonIdent author;
 	private RemoteReader callback;
 	private InputStream inputStream;
 	private IncludedFileReader includedReader;
 	private boolean ignoreRemoteFailures = false;
 
-	private List<RepoProject> bareProjects;
 	private ProgressMonitor monitor;
 
 	/**
@@ -519,37 +519,33 @@ public RevCommit call() throws GitAPIException {
 		}
 
 		if (repo.isBare()) {
-			bareProjects = new ArrayList<>();
 			if (author == null)
 				author = new PersonIdent(repo);
 			if (callback == null)
 				callback = new DefaultRemoteReader();
-			for (RepoProject proj : filteredProjects) {
-				addSubmoduleBare(proj.getUrl(), proj.getPath(),
-						proj.getRevision(), proj.getCopyFiles(),
-						proj.getLinkFiles(), proj.getGroups(),
-						proj.getRecommendShallow());
-			}
+			List<RepoProject> renamedProjects = renameProjects(filteredProjects);
+
 			DirCache index = DirCache.newInCore();
 			DirCacheBuilder builder = index.builder();
 			ObjectInserter inserter = repo.newObjectInserter();
 			try (RevWalk rw = new RevWalk(repo)) {
 				Config cfg = new Config();
 				StringBuilder attributes = new StringBuilder();
-				for (RepoProject proj : bareProjects) {
+				for (RepoProject proj : renamedProjects) {
+					String name = proj.getName();
 					String path = proj.getPath();
-					String nameUri = proj.getName();
+					String url = proj.getUrl();
 					ObjectId objectId;
 					if (ObjectId.isId(proj.getRevision())) {
 						objectId = ObjectId.fromString(proj.getRevision());
 					} else {
-						objectId = callback.sha1(nameUri, proj.getRevision());
+						objectId = callback.sha1(url, proj.getRevision());
 						if (objectId == null && !ignoreRemoteFailures) {
-							throw new RemoteUnavailableException(nameUri);
+							throw new RemoteUnavailableException(url);
 						}
 						if (recordRemoteBranch) {
 							// can be branch or tag
-							cfg.setString("submodule", path, "branch", //$NON-NLS-1$ //$NON-NLS-2$
+							cfg.setString("submodule", name, "branch", //$NON-NLS-1$ //$NON-NLS-2$
 									proj.getRevision());
 						}
 
@@ -559,7 +555,7 @@ public RevCommit call() throws GitAPIException {
 							// depth in the 'clone-depth' field, while
 							// git core only uses a binary 'shallow = true/false'
 							// hint, we'll map any depth to 'shallow = true'
-							cfg.setBoolean("submodule", path, "shallow", //$NON-NLS-1$ //$NON-NLS-2$
+							cfg.setBoolean("submodule", name, "shallow", //$NON-NLS-1$ //$NON-NLS-2$
 									true);
 						}
 					}
@@ -575,12 +571,13 @@ public RevCommit call() throws GitAPIException {
 						attributes.append(rec.toString());
 					}
 
-					URI submodUrl = URI.create(nameUri);
+					URI submodUrl = URI.create(url);
 					if (targetUri != null) {
 						submodUrl = relativize(targetUri, submodUrl);
 					}
-					cfg.setString("submodule", path, "path", path); //$NON-NLS-1$ //$NON-NLS-2$
-					cfg.setString("submodule", path, "url", submodUrl.toString()); //$NON-NLS-1$ //$NON-NLS-2$
+					cfg.setString("submodule", name, "path", path); //$NON-NLS-1$ //$NON-NLS-2$
+					cfg.setString("submodule", name, "url", //$NON-NLS-1$ //$NON-NLS-2$
+							submodUrl.toString());
 
 					// create gitlink
 					if (objectId != null) {
@@ -591,7 +588,7 @@ public RevCommit call() throws GitAPIException {
 
 						for (CopyFile copyfile : proj.getCopyFiles()) {
 							byte[] src = callback.readFile(
-								nameUri, proj.getRevision(), copyfile.src);
+								url, proj.getRevision(), copyfile.src);
 							objectId = inserter.insert(Constants.OBJ_BLOB, src);
 							dcEntry = new DirCacheEntry(copyfile.dest);
 							dcEntry.setObjectId(objectId);
@@ -610,8 +607,7 @@ public RevCommit call() throws GitAPIException {
 							}
 
 							objectId = inserter.insert(Constants.OBJ_BLOB,
-								link.getBytes(
-									Constants.CHARACTER_ENCODING));
+									link.getBytes(UTF_8));
 							dcEntry = new DirCacheEntry(linkfile.dest);
 							dcEntry.setObjectId(objectId);
 							dcEntry.setFileMode(FileMode.SYMLINK);
@@ -624,7 +620,7 @@ public RevCommit call() throws GitAPIException {
 				// create a new DirCacheEntry for .gitmodules file.
 				final DirCacheEntry dcEntry = new DirCacheEntry(Constants.DOT_GIT_MODULES);
 				ObjectId objectId = inserter.insert(Constants.OBJ_BLOB,
-						content.getBytes(Constants.CHARACTER_ENCODING));
+						content.getBytes(UTF_8));
 				dcEntry.setObjectId(objectId);
 				dcEntry.setFileMode(FileMode.REGULAR_FILE);
 				builder.add(dcEntry);
@@ -633,7 +629,7 @@ public RevCommit call() throws GitAPIException {
 					// create a new DirCacheEntry for .gitattributes file.
 					final DirCacheEntry dcEntryAttr = new DirCacheEntry(Constants.DOT_GIT_ATTRIBUTES);
 					ObjectId attrId = inserter.insert(Constants.OBJ_BLOB,
-							attributes.toString().getBytes(Constants.CHARACTER_ENCODING));
+							attributes.toString().getBytes(UTF_8));
 					dcEntryAttr.setObjectId(attrId);
 					dcEntryAttr.setFileMode(FileMode.REGULAR_FILE);
 					builder.add(dcEntryAttr);
@@ -691,7 +687,7 @@ public RevCommit call() throws GitAPIException {
 		} else {
 			try (Git git = new Git(repo)) {
 				for (RepoProject proj : filteredProjects) {
-					addSubmodule(proj.getUrl(), proj.getPath(),
+					addSubmodule(proj.getName(), proj.getUrl(), proj.getPath(),
 							proj.getRevision(), proj.getCopyFiles(),
 							proj.getLinkFiles(), git);
 				}
@@ -703,9 +699,9 @@ public RevCommit call() throws GitAPIException {
 		}
 	}
 
-	private void addSubmodule(String url, String path, String revision,
-			List<CopyFile> copyfiles, List<LinkFile> linkfiles, Git git)
-			throws GitAPIException, IOException {
+	private void addSubmodule(String name, String url, String path,
+			String revision, List<CopyFile> copyfiles, List<LinkFile> linkfiles,
+			Git git) throws GitAPIException, IOException {
 		assert (!repo.isBare());
 		assert (git != null);
 		if (!linkfiles.isEmpty()) {
@@ -713,7 +709,8 @@ private void addSubmodule(String url, String path, String revision,
 					JGitText.get().nonBareLinkFilesNotSupported);
 		}
 
-		SubmoduleAddCommand add = git.submoduleAdd().setPath(path).setURI(url);
+		SubmoduleAddCommand add = git.submoduleAdd().setName(name).setPath(path)
+				.setURI(url);
 		if (monitor != null)
 			add.setProgressMonitor(monitor);
 
@@ -731,16 +728,42 @@ private void addSubmodule(String url, String path, String revision,
 		}
 	}
 
-	private void addSubmoduleBare(String url, String path, String revision,
-			List<CopyFile> copyfiles, List<LinkFile> linkfiles,
-			Set<String> groups, String recommendShallow) {
-		assert (repo.isBare());
-		assert (bareProjects != null);
-		RepoProject proj = new RepoProject(url, path, revision, null, groups,
-				recommendShallow);
-		proj.addCopyFiles(copyfiles);
-		proj.addLinkFiles(linkfiles);
-		bareProjects.add(proj);
+	/**
+	 * Rename the projects if there's a conflict when converted to submodules.
+	 *
+	 * @param projects
+	 *            parsed projects
+	 * @return projects that are renamed if necessary
+	 */
+	private List<RepoProject> renameProjects(List<RepoProject> projects) {
+		Map<String, List<RepoProject>> m = new TreeMap<>();
+		for (RepoProject proj : projects) {
+			List<RepoProject> l = m.get(proj.getName());
+			if (l == null) {
+				l = new ArrayList<>();
+				m.put(proj.getName(), l);
+			}
+			l.add(proj);
+		}
+
+		List<RepoProject> ret = new ArrayList<>();
+		for (List<RepoProject> ps : m.values()) {
+			boolean nameConflict = ps.size() != 1;
+			for (RepoProject proj : ps) {
+				String name = proj.getName();
+				if (nameConflict) {
+					name += SLASH + proj.getPath();
+				}
+				RepoProject p = new RepoProject(name,
+						proj.getPath(), proj.getRevision(), null,
+						proj.getGroups(), proj.getRecommendShallow());
+				p.setUrl(proj.getUrl());
+				p.addCopyFiles(proj.getCopyFiles());
+				p.addLinkFiles(proj.getLinkFiles());
+				ret.add(p);
+			}
+		}
+		return ret;
 	}
 
 	/*
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/hooks/GitHook.java b/org.eclipse.jgit/src/org/eclipse/jgit/hooks/GitHook.java
index 8a61d1b..ad43e2c 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/hooks/GitHook.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/hooks/GitHook.java
@@ -42,7 +42,7 @@
  */
 package org.eclipse.jgit.hooks;
 
-import static org.eclipse.jgit.lib.Constants.CHARSET;
+import static java.nio.charset.StandardCharsets.UTF_8;
 
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
@@ -158,7 +158,7 @@ protected void doRun() throws AbortedByHookException {
 		PrintStream hookErrRedirect = null;
 		try {
 			hookErrRedirect = new PrintStream(errorByteArray, false,
-					CHARSET.name());
+					UTF_8.name());
 		} catch (UnsupportedEncodingException e) {
 			// UTF-8 is guaranteed to be available
 		}
@@ -167,7 +167,7 @@ protected void doRun() throws AbortedByHookException {
 				hookErrRedirect, getStdinArgs());
 		if (result.isExecutedWithError()) {
 			throw new AbortedByHookException(
-					new String(errorByteArray.toByteArray(), CHARSET),
+					new String(errorByteArray.toByteArray(), UTF_8),
 					getHookName(), result.getExitCode());
 		}
 	}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/IgnoreNode.java b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/IgnoreNode.java
index d570fde..864f8bf 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/IgnoreNode.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/IgnoreNode.java
@@ -42,6 +42,8 @@
  */
 package org.eclipse.jgit.ignore;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
+
 import java.io.BufferedReader;
 import java.io.IOException;
 import java.io.InputStream;
@@ -50,8 +52,6 @@
 import java.util.Collections;
 import java.util.List;
 
-import org.eclipse.jgit.lib.Constants;
-
 /**
  * Represents a bundle of ignore rules inherited from a base directory.
  *
@@ -121,7 +121,7 @@ public void parse(InputStream in) throws IOException {
 	}
 
 	private static BufferedReader asReader(InputStream in) {
-		return new BufferedReader(new InputStreamReader(in, Constants.CHARSET));
+		return new BufferedReader(new InputStreamReader(in, UTF_8));
 	}
 
 	/**
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 d2f6232..4c60266 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java
@@ -78,6 +78,7 @@ public static JGitText get() {
 	/***/ public String archiveFormatAlreadyAbsent;
 	/***/ public String archiveFormatAlreadyRegistered;
 	/***/ public String argumentIsNotAValidCommentString;
+	/***/ public String assumeAtomicCreateNewFile;
 	/***/ public String atLeastOnePathIsRequired;
 	/***/ public String atLeastOnePatternIsRequired;
 	/***/ public String atLeastTwoFiltersNeeded;
@@ -165,6 +166,7 @@ public static JGitText get() {
 	/***/ public String cannotReadTree;
 	/***/ public String cannotRebaseWithoutCurrentHead;
 	/***/ public String cannotResolveLocalTrackingRefForUpdating;
+	/***/ public String cannotSaveConfig;
 	/***/ public String cannotSquashFixupWithoutPreviousCommit;
 	/***/ public String cannotStoreObjects;
 	/***/ public String cannotResolveUniquelyAbbrevObjectId;
@@ -363,6 +365,7 @@ public static JGitText get() {
 	/***/ public String expectedReceivedContentType;
 	/***/ public String expectedReportForRefNotReceived;
 	/***/ public String failedAtomicFileCreation;
+	/***/ public String failedCreateLockFile;
 	/***/ public String failedToDetermineFilterDefinition;
 	/***/ public String failedUpdatingRefs;
 	/***/ public String failureDueToOneOfTheFollowing;
@@ -450,7 +453,9 @@ public static JGitText get() {
 	/***/ public String invalidPathPeriodAtEndWindows;
 	/***/ public String invalidPathSpaceAtEndWindows;
 	/***/ public String invalidPathReservedOnWindows;
+	/***/ public String invalidPurgeFactor;
 	/***/ public String invalidRedirectLocation;
+	/***/ public String invalidRefAdvertisementLine;
 	/***/ public String invalidReflogRevision;
 	/***/ public String invalidRefName;
 	/***/ public String invalidReftableBlock;
@@ -487,6 +492,7 @@ public static JGitText get() {
 	/***/ public String localRepository;
 	/***/ public String lockCountMustBeGreaterOrEqual1;
 	/***/ public String lockError;
+	/***/ public String lockFailedRetry;
 	/***/ public String lockOnNotClosed;
 	/***/ public String lockOnNotHeld;
 	/***/ public String malformedpersonIdentString;
@@ -617,8 +623,11 @@ public static JGitText get() {
 	/***/ public String pushNotPermitted;
 	/***/ public String pushOptionsNotSupported;
 	/***/ public String rawLogMessageDoesNotParseAsLogEntry;
+	/***/ public String readConfigFailed;
+	/***/ public String readFileStoreAttributesFailed;
 	/***/ public String readerIsRequired;
 	/***/ public String readingObjectsFromLocalRepositoryFailed;
+	/***/ public String readLastModifiedFailed;
 	/***/ public String readTimedOut;
 	/***/ public String receivePackObjectTooLarge1;
 	/***/ public String receivePackObjectTooLarge2;
@@ -643,6 +652,7 @@ public static JGitText get() {
 	/***/ public String renameBranchFailedBecauseTag;
 	/***/ public String renameBranchFailedUnknownReason;
 	/***/ public String renameBranchUnexpectedResult;
+	/***/ public String renameCancelled;
 	/***/ public String renameFileFailed;
 	/***/ public String renamesAlreadyFound;
 	/***/ public String renamesBreakingModifies;
@@ -671,6 +681,7 @@ public static JGitText get() {
 	/***/ public String s3ActionDeletion;
 	/***/ public String s3ActionReading;
 	/***/ public String s3ActionWriting;
+	/***/ public String saveFileStoreAttributesFailed;
 	/***/ public String searchForReuse;
 	/***/ public String searchForSizes;
 	/***/ public String secondsAgo;
@@ -735,6 +746,7 @@ public static JGitText get() {
 	/***/ public String tagAlreadyExists;
 	/***/ public String tagNameInvalid;
 	/***/ public String tagOnRepoWithoutHEADCurrentlyNotSupported;
+	/***/ public String timeoutMeasureFsTimestampResolution;
 	/***/ public String transactionAborted;
 	/***/ public String theFactoryMustNotBeNull;
 	/***/ public String threadInterruptedWhileRunning;
@@ -827,9 +839,9 @@ public static JGitText get() {
 	/***/ public String upstreamBranchName;
 	/***/ public String uriNotConfigured;
 	/***/ public String uriNotFound;
+	/***/ public String uriNotFoundWithMessage;
 	/***/ public String URINotSupported;
-	/***/ public String URLNotFound;
-	/***/ public String userConfigFileInvalid;
+	/***/ public String userConfigInvalid;
 	/***/ public String walkFailure;
 	/***/ public String wantNotValid;
 	/***/ public String weeksAgo;
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 bc5ba39..fa32b26 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
@@ -274,8 +274,8 @@ public void setReplicas(Collection<KetchReplica> replicas) {
 
 		lock.lock();
 		try {
-			voters = v.toArray(new KetchReplica[v.size()]);
-			followers = f.toArray(new KetchReplica[f.size()]);
+			voters = v.toArray(new KetchReplica[0]);
+			followers = f.toArray(new KetchReplica[0]);
 			self = me;
 		} finally {
 			lock.unlock();
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 7ddde63..6f1f5c5 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
@@ -209,7 +209,7 @@ private void update(Repository git, ReplicaPushRequest req,
 		checkFailed(failed, accepted);
 		checkFailed(failed, committed);
 		if (!failed.isEmpty()) {
-			String[] arr = failed.toArray(new String[failed.size()]);
+			String[] arr = failed.toArray(new String[0]);
 			req.setRefs(refdb.exactRef(arr));
 		}
 	}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsConfig.java
index 3e2963a..2f796a9 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsConfig.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsConfig.java
@@ -48,7 +48,13 @@
 import org.eclipse.jgit.errors.ConfigInvalidException;
 import org.eclipse.jgit.lib.StoredConfig;
 
-final class DfsConfig extends StoredConfig {
+/**
+ * Config implementation used by DFS repositories.
+ * <p>
+ * The current implementation acts as if there is no persistent storage: loading
+ * simply clears the config, and saving does nothing.
+ */
+public final class DfsConfig extends StoredConfig {
 	/** {@inheritDoc} */
 	@Override
 	public void load() throws IOException, ConfigInvalidException {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsFsck.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsFsck.java
index 985393c..3f96d09 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsFsck.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsFsck.java
@@ -146,7 +146,7 @@ private void checkConnectivity(ProgressMonitor pm, FsckError errors)
 			throws IOException {
 		pm.beginTask(JGitText.get().countingObjects, ProgressMonitor.UNKNOWN);
 		try (ObjectWalk ow = new ObjectWalk(repo)) {
-			for (Ref r : repo.getAllRefs().values()) {
+			for (Ref r : repo.getRefDatabase().getRefs()) {
 				ObjectId objectId = r.getObjectId();
 				if (objectId == null) {
 					// skip unborn branch
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsObjDatabase.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsObjDatabase.java
index ca54ee2..09d5937 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsObjDatabase.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsObjDatabase.java
@@ -43,13 +43,17 @@
 
 package org.eclipse.jgit.internal.storage.dfs;
 
+import static java.util.stream.Collectors.joining;
+
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.util.ArrayList;
+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.List;
 import java.util.Map;
 import java.util.Set;
@@ -86,10 +90,16 @@ public void markDirty() {
 		}
 	};
 
-	/** Sources for a pack file. */
+	/**
+	 * Sources for a pack file.
+	 * <p>
+	 * <strong>Note:</strong> When sorting packs by source, do not use the default
+	 * comparator based on {@link Enum#compareTo}. Prefer {@link
+	 * #DEFAULT_COMPARATOR} or your own {@link ComparatorBuilder}.
+	 */
 	public static enum PackSource {
 		/** The pack is created by ObjectInserter due to local activity. */
-		INSERT(0),
+		INSERT,
 
 		/**
 		 * The pack is created by PackParser due to a network event.
@@ -100,7 +110,7 @@ public static enum PackSource {
 		 * storage layout preferred by this version. Received packs are likely
 		 * to be either compacted or garbage collected in the future.
 		 */
-		RECEIVE(0),
+		RECEIVE,
 
 		/**
 		 * The pack was created by compacting multiple packs together.
@@ -111,7 +121,7 @@ public static enum PackSource {
 		 *
 		 * @see DfsPackCompactor
 		 */
-		COMPACT(1),
+		COMPACT,
 
 		/**
 		 * Pack was created by Git garbage collection by this implementation.
@@ -122,17 +132,17 @@ public static enum PackSource {
 		 *
 		 * @see DfsGarbageCollector
 		 */
-		GC(2),
+		GC,
 
 		/** Created from non-heads by {@link DfsGarbageCollector}. */
-		GC_REST(3),
+		GC_REST,
 
 		/**
 		 * RefTreeGraph pack was created by Git garbage collection.
 		 *
 		 * @see DfsGarbageCollector
 		 */
-		GC_TXN(4),
+		GC_TXN,
 
 		/**
 		 * Pack was created by Git garbage collection.
@@ -141,12 +151,86 @@ public static enum PackSource {
 		 * last GC pass. It is retained in a new pack until it is safe to prune
 		 * these objects from the repository.
 		 */
-		UNREACHABLE_GARBAGE(5);
+		UNREACHABLE_GARBAGE;
 
-		final int category;
+		/**
+		 * Default comparator for sources.
+		 * <p>
+		 * Sorts generally newer, smaller types such as {@code INSERT} and {@code
+		 * RECEIVE} earlier; older, larger types such as {@code GC} later; and
+		 * {@code UNREACHABLE_GARBAGE} at the end.
+		 */
+		public static final Comparator<PackSource> DEFAULT_COMPARATOR =
+				new ComparatorBuilder()
+						.add(INSERT, RECEIVE)
+						.add(COMPACT)
+						.add(GC)
+						.add(GC_REST)
+						.add(GC_TXN)
+						.add(UNREACHABLE_GARBAGE)
+						.build();
 
-		PackSource(int category) {
-			this.category = category;
+		/**
+		 * Builder for describing {@link PackSource} ordering where some values are
+		 * explicitly considered equal to others.
+		 */
+		public static class ComparatorBuilder {
+			private final Map<PackSource, Integer> ranks = new HashMap<>();
+			private int counter;
+
+			/**
+			 * Add a collection of sources that should sort as equal.
+			 * <p>
+			 * Sources in the input will sort after sources listed in previous calls
+			 * to this method.
+			 *
+			 * @param sources
+			 *            sources in this equivalence class.
+			 * @return this.
+			 */
+			public ComparatorBuilder add(PackSource... sources) {
+				for (PackSource s : sources) {
+					ranks.put(s, Integer.valueOf(counter));
+				}
+				counter++;
+				return this;
+			}
+
+			/**
+			 * Build the comparator.
+			 *
+			 * @return new comparator instance.
+			 * @throws IllegalArgumentException
+			 *             not all {@link PackSource} instances were explicitly assigned
+			 *             an equivalence class.
+			 */
+			public Comparator<PackSource> build() {
+				return new PackSourceComparator(ranks);
+			}
+		}
+
+		private static class PackSourceComparator implements Comparator<PackSource> {
+			private final Map<PackSource, Integer> ranks;
+
+			private PackSourceComparator(Map<PackSource, Integer> ranks) {
+				if (!ranks.keySet().equals(
+							new HashSet<>(Arrays.asList(PackSource.values())))) {
+					throw new IllegalArgumentException();
+				}
+				this.ranks = new HashMap<>(ranks);
+			}
+
+			@Override
+			public int compare(PackSource a, PackSource b) {
+				return ranks.get(a).compareTo(ranks.get(b));
+			}
+
+			@Override
+			public String toString() {
+				return Arrays.stream(PackSource.values())
+						.map(s -> s + "=" + ranks.get(s)) //$NON-NLS-1$
+						.collect(joining(", ", getClass().getSimpleName() + "{", "}")); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+			}
 		}
 	}
 
@@ -156,6 +240,8 @@ public static enum PackSource {
 
 	private DfsReaderOptions readerOptions;
 
+	private Comparator<DfsPackDescription> packComparator;
+
 	/**
 	 * Initialize an object database for our repository.
 	 *
@@ -169,6 +255,7 @@ protected DfsObjDatabase(DfsRepository repository,
 		this.repository = repository;
 		this.packList = new AtomicReference<>(NO_PACKS);
 		this.readerOptions = options;
+		this.packComparator = DfsPackDescription.objectLookupComparator();
 	}
 
 	/**
@@ -180,6 +267,21 @@ public DfsReaderOptions getReaderOptions() {
 		return readerOptions;
 	}
 
+	/**
+	 * Set the comparator used when searching for objects across packs.
+	 * <p>
+	 * An optimal comparator will find more objects without having to load large
+	 * idx files from storage only to find that they don't contain the object.
+	 * See {@link DfsPackDescription#objectLookupComparator()} for the default
+	 * heuristics.
+	 *
+	 * @param packComparator
+	 *            comparator.
+	 */
+	public void setPackComparator(Comparator<DfsPackDescription> packComparator) {
+		this.packComparator = packComparator;
+	}
+
 	/** {@inheritDoc} */
 	@Override
 	public DfsReader newReader() {
@@ -523,7 +625,7 @@ private PackList scanPacksImpl(PackList old) throws IOException {
 		Map<DfsPackDescription, DfsReftable> reftables = reftableMap(old);
 
 		List<DfsPackDescription> scanned = listPacks();
-		Collections.sort(scanned);
+		Collections.sort(scanned, packComparator);
 
 		List<DfsPackFile> newPacks = new ArrayList<>(scanned.size());
 		List<DfsReftable> newReftables = new ArrayList<>(scanned.size());
@@ -584,30 +686,9 @@ private static Map<DfsPackDescription, DfsReftable> reftableMap(PackList old) {
 	 * @return comparator to sort {@link DfsReftable} by priority.
 	 */
 	protected Comparator<DfsReftable> reftableComparator() {
-		return (fa, fb) -> {
-			DfsPackDescription a = fa.getPackDescription();
-			DfsPackDescription b = fb.getPackDescription();
-
-			// GC, COMPACT reftables first by higher category.
-			int c = category(b) - category(a);
-			if (c != 0) {
-				return c;
-			}
-
-			// Lower maxUpdateIndex first.
-			c = Long.signum(a.getMaxUpdateIndex() - b.getMaxUpdateIndex());
-			if (c != 0) {
-				return c;
-			}
-
-			// Older reftable first.
-			return Long.signum(a.getLastModified() - b.getLastModified());
-		};
-	}
-
-	static int category(DfsPackDescription d) {
-		PackSource s = d.getPackSource();
-		return s != null ? s.category : 0;
+		return Comparator.comparing(
+				DfsReftable::getPackDescription,
+				DfsPackDescription.reftableComparator());
 	}
 
 	/**
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 b43b9b1..127ee6b 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
@@ -404,12 +404,11 @@ private void addObjectsToPack(PackWriter pw, DfsReader ctx,
 		// Sort packs by description ordering, this places newer packs before
 		// older packs, allowing the PackWriter to be handed newer objects
 		// first and older objects last.
-		Collections.sort(srcPacks, new Comparator<DfsPackFile>() {
-			@Override
-			public int compare(DfsPackFile a, DfsPackFile b) {
-				return a.getPackDescription().compareTo(b.getPackDescription());
-			}
-		});
+		Collections.sort(
+				srcPacks,
+				Comparator.comparing(
+						DfsPackFile::getPackDescription,
+						DfsPackDescription.objectLookupComparator()));
 
 		rw = new RevWalk(ctx);
 		added = rw.newFlag("ADDED"); //$NON-NLS-1$
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackDescription.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackDescription.java
index 45eb7b0..5a1ac02 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackDescription.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackDescription.java
@@ -47,7 +47,9 @@
 import static org.eclipse.jgit.internal.storage.pack.PackExt.REFTABLE;
 
 import java.util.Arrays;
+import java.util.Comparator;
 
+import org.eclipse.jgit.annotations.NonNull;
 import org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource;
 import org.eclipse.jgit.internal.storage.pack.PackExt;
 import org.eclipse.jgit.internal.storage.reftable.ReftableWriter;
@@ -61,7 +63,107 @@
  * Instances of this class are cached with the DfsPackFile, and should not be
  * modified once initialized and presented to the JGit DFS library.
  */
-public class DfsPackDescription implements Comparable<DfsPackDescription> {
+public class DfsPackDescription {
+	/**
+	 * Comparator for packs when looking up objects in indexes.
+	 * <p>
+	 * This comparator tries to position packs in the order readers should examine
+	 * them when looking for objects by SHA-1. The default tries to sort packs
+	 * with more recent modification dates before older packs, and packs with
+	 * fewer objects before packs with more objects.
+	 * <p>
+	 * Uses {@link PackSource#DEFAULT_COMPARATOR} for the portion of comparison
+	 * where packs are sorted by source.
+	 *
+	 * @return comparator.
+	 */
+	public static Comparator<DfsPackDescription> objectLookupComparator() {
+		return objectLookupComparator(PackSource.DEFAULT_COMPARATOR);
+	}
+
+	/**
+	 * Comparator for packs when looking up objects in indexes.
+	 * <p>
+	 * This comparator tries to position packs in the order readers should examine
+	 * them when looking for objects by SHA-1. The default tries to sort packs
+	 * with more recent modification dates before older packs, and packs with
+	 * fewer objects before packs with more objects.
+	 *
+	 * @param packSourceComparator
+	 *            comparator for the {@link PackSource}, used as the first step in
+	 *            comparison.
+	 * @return comparator.
+	 */
+	public static Comparator<DfsPackDescription> objectLookupComparator(
+			Comparator<PackSource> packSourceComparator) {
+		return Comparator.comparing(
+					DfsPackDescription::getPackSource, packSourceComparator)
+			.thenComparing((a, b) -> {
+				PackSource as = a.getPackSource();
+				PackSource bs = b.getPackSource();
+
+				// Tie break GC type packs by smallest first. There should be at most
+				// one of each source, but when multiple exist concurrent GCs may have
+				// run. Preferring the smaller file selects higher quality delta
+				// compression, placing less demand on the DfsBlockCache.
+				if (as == bs && isGC(as)) {
+					int cmp = Long.signum(a.getFileSize(PACK) - b.getFileSize(PACK));
+					if (cmp != 0) {
+						return cmp;
+					}
+				}
+
+				// Newer packs should sort first.
+				int cmp = Long.signum(b.getLastModified() - a.getLastModified());
+				if (cmp != 0) {
+					return cmp;
+				}
+
+				// Break ties on smaller index. Readers may get lucky and find
+				// the object they care about in the smaller index. This also pushes
+				// big historical packs to the end of the list, due to more objects.
+				return Long.signum(a.getObjectCount() - b.getObjectCount());
+			});
+	}
+
+	static Comparator<DfsPackDescription> reftableComparator() {
+		return (a, b) -> {
+				// GC, COMPACT reftables first by reversing default order.
+				int c = PackSource.DEFAULT_COMPARATOR.reversed()
+						.compare(a.getPackSource(), b.getPackSource());
+				if (c != 0) {
+					return c;
+				}
+
+				// Lower maxUpdateIndex first.
+				c = Long.signum(a.getMaxUpdateIndex() - b.getMaxUpdateIndex());
+				if (c != 0) {
+					return c;
+				}
+
+				// Older reftable first.
+				return Long.signum(a.getLastModified() - b.getLastModified());
+			};
+	}
+
+	static Comparator<DfsPackDescription> reuseComparator() {
+		return (a, b) -> {
+			PackSource as = a.getPackSource();
+			PackSource bs = b.getPackSource();
+
+			if (as == bs && DfsPackDescription.isGC(as)) {
+				// Push smaller GC files last; these likely have higher quality
+				// delta compression and the contained representation should be
+				// favored over other files.
+				return Long.signum(b.getFileSize(PACK) - a.getFileSize(PACK));
+			}
+
+			// DfsPackDescription.compareTo already did a reasonable sort.
+			// Rely on Arrays.sort being stable, leaving equal elements.
+			return 0;
+		};
+	}
+
 	private final DfsRepositoryDescription repoDesc;
 	private final String packName;
 	private PackSource packSource;
@@ -93,11 +195,15 @@ public class DfsPackDescription implements Comparable<DfsPackDescription> {
 	 *            name of the pack file. Must end with ".pack".
 	 * @param repoDesc
 	 *            description of the repo containing the pack file.
+	 * @param packSource
+	 *            the source of the pack.
 	 */
-	public DfsPackDescription(DfsRepositoryDescription repoDesc, String name) {
+	public DfsPackDescription(DfsRepositoryDescription repoDesc, String name,
+			@NonNull PackSource packSource) {
 		this.repoDesc = repoDesc;
 		int dot = name.lastIndexOf('.');
 		this.packName = (dot < 0) ? name : name.substring(0, dot);
+		this.packSource = packSource;
 
 		int extCnt = PackExt.values().length;
 		sizeMap = new long[extCnt];
@@ -162,6 +268,7 @@ public DfsStreamKey getStreamKey(PackExt ext) {
 	 *
 	 * @return the source of the pack.
 	 */
+	@NonNull
 	public PackSource getPackSource() {
 		return packSource;
 	}
@@ -173,7 +280,7 @@ public PackSource getPackSource() {
 	 *            the source of the pack.
 	 * @return {@code this}
 	 */
-	public DfsPackDescription setPackSource(PackSource source) {
+	public DfsPackDescription setPackSource(@NonNull PackSource source) {
 		packSource = source;
 		return this;
 	}
@@ -455,49 +562,6 @@ public boolean equals(Object b) {
 		return false;
 	}
 
-	/**
-	 * {@inheritDoc}
-	 * <p>
-	 * Sort packs according to the optimal lookup ordering.
-	 * <p>
-	 * This method tries to position packs in the order readers should examine
-	 * them when looking for objects by SHA-1. The default tries to sort packs
-	 * with more recent modification dates before older packs, and packs with
-	 * fewer objects before packs with more objects.
-	 */
-	@Override
-	public int compareTo(DfsPackDescription b) {
-		// Cluster by PackSource, pushing UNREACHABLE_GARBAGE to the end.
-		PackSource as = getPackSource();
-		PackSource bs = b.getPackSource();
-		if (as != null && bs != null) {
-			int cmp = as.category - bs.category;
-			if (cmp != 0)
-				return cmp;
-		}
-
-		// Tie break GC type packs by smallest first. There should be at most
-		// one of each source, but when multiple exist concurrent GCs may have
-		// run. Preferring the smaller file selects higher quality delta
-		// compression, placing less demand on the DfsBlockCache.
-		if (as != null && as == bs && isGC(as)) {
-			int cmp = Long.signum(getFileSize(PACK) - b.getFileSize(PACK));
-			if (cmp != 0) {
-				return cmp;
-			}
-		}
-
-		// Newer packs should sort first.
-		int cmp = Long.signum(b.getLastModified() - getLastModified());
-		if (cmp != 0)
-			return cmp;
-
-		// Break ties on smaller index. Readers may get lucky and find
-		// the object they care about in the smaller index. This also pushes
-		// big historical packs to the end of the list, due to more objects.
-		return Long.signum(getObjectCount() - b.getObjectCount());
-	}
-
 	static boolean isGC(PackSource s) {
 		switch (s) {
 		case GC:
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 197114b..d04709f 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
@@ -45,7 +45,6 @@
 package org.eclipse.jgit.internal.storage.dfs;
 
 import static org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource.UNREACHABLE_GARBAGE;
-import static org.eclipse.jgit.internal.storage.pack.PackExt.PACK;
 import static org.eclipse.jgit.lib.Constants.OBJECT_ID_LENGTH;
 
 import java.io.IOException;
@@ -66,7 +65,6 @@
 import org.eclipse.jgit.errors.StoredObjectRepresentationNotAvailableException;
 import org.eclipse.jgit.internal.JGitText;
 import org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackList;
-import org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource;
 import org.eclipse.jgit.internal.storage.file.BitmapIndexImpl;
 import org.eclipse.jgit.internal.storage.file.PackBitmapIndex;
 import org.eclipse.jgit.internal.storage.file.PackIndex;
@@ -611,26 +609,9 @@ private void trySelectRepresentation(PackWriter packer,
 		}
 	}
 
-	private static final Comparator<DfsPackFile> PACK_SORT_FOR_REUSE = new Comparator<DfsPackFile>() {
-		@Override
-		public int compare(DfsPackFile af, DfsPackFile bf) {
-			DfsPackDescription ad = af.getPackDescription();
-			DfsPackDescription bd = bf.getPackDescription();
-			PackSource as = ad.getPackSource();
-			PackSource bs = bd.getPackSource();
-
-			if (as != null && as == bs && DfsPackDescription.isGC(as)) {
-				// Push smaller GC files last; these likely have higher quality
-				// delta compression and the contained representation should be
-				// favored over other files.
-				return Long.signum(bd.getFileSize(PACK) - ad.getFileSize(PACK));
-			}
-
-			// DfsPackDescription.compareTo already did a reasonable sort.
-			// Rely on Arrays.sort being stable, leaving equal elements.
-			return 0;
-		}
-	};
+	private static final Comparator<DfsPackFile> PACK_SORT_FOR_REUSE =
+		Comparator.comparing(
+				DfsPackFile::getPackDescription, DfsPackDescription.reuseComparator());
 
 	private List<DfsPackFile> sortPacksForSelectRepresentation()
 			throws IOException {
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 40cfb71..7081630 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
@@ -45,6 +45,9 @@
 
 import java.io.IOException;
 import java.util.Arrays;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
 import java.util.Map;
 import java.util.concurrent.locks.ReentrantLock;
 
@@ -196,7 +199,7 @@ public boolean isNameConflicting(String refName) throws IOException {
 			}
 
 			// Cannot be the container of an existing reference.
-			return table.hasRef(refName + '/');
+			return table.hasRefsWithPrefix(refName + '/');
 		} finally {
 			lock.unlock();
 		}
@@ -238,7 +241,8 @@ public Map<String, Ref> getRefs(String prefix) throws IOException {
 		try {
 			Reftable table = reader();
 			try (RefCursor rc = ALL.equals(prefix) ? table.allRefs()
-					: table.seekRef(prefix)) {
+					: (prefix.endsWith("/") ? table.seekRefsWithPrefix(prefix) //$NON-NLS-1$
+							: table.seekRef(prefix))) {
 				while (rc.next()) {
 					Ref ref = table.resolve(rc.getRef());
 					if (ref != null && ref.getObjectId() != null) {
@@ -256,6 +260,29 @@ public Map<String, Ref> getRefs(String prefix) throws IOException {
 
 	/** {@inheritDoc} */
 	@Override
+	public List<Ref> getRefsByPrefix(String prefix) throws IOException {
+		List<Ref> all = new ArrayList<>();
+		lock.lock();
+		try {
+			Reftable table = reader();
+			try (RefCursor rc = ALL.equals(prefix) ? table.allRefs()
+					: table.seekRefsWithPrefix(prefix)) {
+				while (rc.next()) {
+					Ref ref = table.resolve(rc.getRef());
+					if (ref != null && ref.getObjectId() != null) {
+						all.add(ref);
+					}
+				}
+			}
+		} finally {
+			lock.unlock();
+		}
+
+		return Collections.unmodifiableList(all);
+	}
+
+	/** {@inheritDoc} */
+	@Override
 	public Ref peel(Ref ref) throws IOException {
 		Ref oldLeaf = ref.getLeaf();
 		if (oldLeaf.isPeeled() || oldLeaf.getObjectId() == null) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsStreamKey.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsStreamKey.java
index c11f696..8793d83 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsStreamKey.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsStreamKey.java
@@ -43,7 +43,7 @@
 
 package org.eclipse.jgit.internal.storage.dfs;
 
-import static org.eclipse.jgit.lib.Constants.CHARSET;
+import static java.nio.charset.StandardCharsets.UTF_8;
 
 import java.util.Arrays;
 
@@ -67,7 +67,7 @@ public abstract class DfsStreamKey {
 	 */
 	public static DfsStreamKey of(DfsRepositoryDescription repo, String name,
 			@Nullable PackExt ext) {
-		return new ByteArrayDfsStreamKey(repo, name.getBytes(CHARSET), ext);
+		return new ByteArrayDfsStreamKey(repo, name.getBytes(UTF_8), ext);
 	}
 
 	final int hash;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/InMemoryRepository.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/InMemoryRepository.java
index 662c3fe..5b6894d 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/InMemoryRepository.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/InMemoryRepository.java
@@ -10,6 +10,7 @@
 import java.util.concurrent.atomic.AtomicInteger;
 
 import org.eclipse.jgit.annotations.Nullable;
+import org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource;
 import org.eclipse.jgit.internal.storage.pack.PackExt;
 import org.eclipse.jgit.internal.storage.reftable.ReftableConfig;
 import org.eclipse.jgit.lib.RefDatabase;
@@ -118,10 +119,10 @@ protected synchronized List<DfsPackDescription> listPacks() {
 		@Override
 		protected DfsPackDescription newPack(PackSource source) {
 			int id = packId.incrementAndGet();
-			DfsPackDescription desc = new MemPack(
+			return new MemPack(
 					"pack-" + id + "-" + source.name(), //$NON-NLS-1$ //$NON-NLS-2$
-					getRepository().getDescription());
-			return desc.setPackSource(source);
+					getRepository().getDescription(),
+					source);
 		}
 
 		@Override
@@ -169,8 +170,8 @@ public void flush() {
 	private static class MemPack extends DfsPackDescription {
 		final byte[][] fileMap = new byte[PackExt.values().length][];
 
-		MemPack(String name, DfsRepositoryDescription repoDesc) {
-			super(repoDesc, name);
+		MemPack(String name, DfsRepositoryDescription repoDesc, PackSource source) {
+			super(repoDesc, name, source);
 		}
 
 		void put(PackExt ext, byte[] data) {
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 e0c056a..47ac4ec 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
@@ -70,7 +70,6 @@
 import org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource;
 import org.eclipse.jgit.internal.storage.io.BlockSource;
 import org.eclipse.jgit.internal.storage.pack.PackExt;
-import org.eclipse.jgit.internal.storage.reftable.RefCursor;
 import org.eclipse.jgit.internal.storage.reftable.Reftable;
 import org.eclipse.jgit.internal.storage.reftable.ReftableCompactor;
 import org.eclipse.jgit.internal.storage.reftable.ReftableConfig;
@@ -240,11 +239,7 @@ private boolean checkConflicting(List<ReceiveCommand> pending)
 	private boolean checkExpected(Reftable table, List<ReceiveCommand> pending)
 			throws IOException {
 		for (ReceiveCommand cmd : pending) {
-			Ref ref;
-			try (RefCursor rc = table.seekRef(cmd.getRefName())) {
-				ref = rc.next() ? rc.getRef() : null;
-			}
-			if (!matchOld(cmd, ref)) {
+			if (!matchOld(cmd, table.exactRef(cmd.getRefName()))) {
 				cmd.setResult(LOCK_FAILURE);
 				if (isAtomic()) {
 					ReceiveCommand.abort(pending);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/BasePackBitmapIndex.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/BasePackBitmapIndex.java
index 407061f..3884180 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/BasePackBitmapIndex.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/BasePackBitmapIndex.java
@@ -104,6 +104,7 @@ EWAHCompressedBitmap getBitmap() {
 				r = xb.xorBitmap.bitmapContainer;
 				if (r instanceof EWAHCompressedBitmap) {
 					out = out.xor((EWAHCompressedBitmap) r);
+					out.trim();
 					bitmapContainer = out;
 					return out;
 				}
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 d02888a..356d64b 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
@@ -81,15 +81,17 @@
 import org.eclipse.jgit.lib.RefUpdate;
 import org.eclipse.jgit.lib.ReflogReader;
 import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.lib.StoredConfig;
 import org.eclipse.jgit.storage.file.FileBasedConfig;
 import org.eclipse.jgit.storage.file.FileRepositoryBuilder;
 import org.eclipse.jgit.storage.pack.PackConfig;
-import org.eclipse.jgit.util.FS;
 import org.eclipse.jgit.util.FileUtils;
 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.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 /**
  * Represents a Git repository. A repository holds all objects and refs used for
@@ -116,10 +118,10 @@
  * This implementation only handles a subtly undocumented subset of git features.
  */
 public class FileRepository extends Repository {
+	private static final Logger LOG = LoggerFactory
+			.getLogger(FileRepository.class);
 	private static final String UNNAMED = "Unnamed repository; edit this file to name it for gitweb."; //$NON-NLS-1$
 
-	private final FileBasedConfig systemConfig;
-	private final FileBasedConfig userConfig;
 	private final FileBasedConfig repoConfig;
 	private final RefDatabase refs;
 	private final ObjectDirectory objectDatabase;
@@ -178,32 +180,16 @@ public FileRepository(String gitDir) throws IOException {
 	 */
 	public FileRepository(BaseRepositoryBuilder options) throws IOException {
 		super(options);
-
-		if (StringUtils.isEmptyOrNull(SystemReader.getInstance().getenv(
-				Constants.GIT_CONFIG_NOSYSTEM_KEY)))
-			systemConfig = SystemReader.getInstance().openSystemConfig(null,
-					getFS());
-		else
-			systemConfig = new FileBasedConfig(null, FS.DETECTED) {
-				@Override
-				public void load() {
-					// empty, do not load
-				}
-
-				@Override
-				public boolean isOutdated() {
-					// regular class would bomb here
-					return false;
-				}
-			};
-		userConfig = SystemReader.getInstance().openUserConfig(systemConfig,
-				getFS());
+		StoredConfig userConfig = null;
+		try {
+			userConfig = SystemReader.getInstance().getUserConfig();
+		} catch (ConfigInvalidException e) {
+			LOG.error(e.getMessage(), e);
+			throw new IOException(e.getMessage(), e);
+		}
 		repoConfig = new FileBasedConfig(userConfig, getFS().resolve(
 				getDirectory(), Constants.CONFIG),
 				getFS());
-
-		loadSystemConfig();
-		loadUserConfig();
 		loadRepoConfig();
 
 		repoConfig.addChangeListener(new ConfigChangedListener() {
@@ -247,28 +233,6 @@ public void onConfigChanged(ConfigChangedEvent event) {
 		}
 	}
 
-	private void loadSystemConfig() throws IOException {
-		try {
-			systemConfig.load();
-		} catch (ConfigInvalidException e) {
-			throw new IOException(MessageFormat.format(JGitText
-					.get().systemConfigFileInvalid, systemConfig.getFile()
-							.getAbsolutePath(),
-					e), e);
-		}
-	}
-
-	private void loadUserConfig() throws IOException {
-		try {
-			userConfig.load();
-		} catch (ConfigInvalidException e) {
-			throw new IOException(MessageFormat.format(JGitText
-					.get().userConfigFileInvalid, userConfig.getFile()
-							.getAbsolutePath(),
-					e), e);
-		}
-	}
-
 	private void loadRepoConfig() throws IOException {
 		try {
 			repoConfig.load();
@@ -398,26 +362,13 @@ public RefDatabase getRefDatabase() {
 	/** {@inheritDoc} */
 	@Override
 	public FileBasedConfig getConfig() {
-		if (systemConfig.isOutdated()) {
-			try {
-				loadSystemConfig();
-			} catch (IOException e) {
-				throw new RuntimeException(e);
+		try {
+			SystemReader.getInstance().getUserConfig();
+			if (repoConfig.isOutdated()) {
+				loadRepoConfig();
 			}
-		}
-		if (userConfig.isOutdated()) {
-			try {
-				loadUserConfig();
-			} catch (IOException e) {
-				throw new RuntimeException(e);
-			}
-		}
-		if (repoConfig.isOutdated()) {
-				try {
-					loadRepoConfig();
-				} catch (IOException e) {
-					throw new RuntimeException(e);
-				}
+		} catch (IOException | ConfigInvalidException e) {
+			throw new RuntimeException(e);
 		}
 		return repoConfig;
 	}
@@ -480,7 +431,7 @@ private File descriptionFile() {
 	/**
 	 * {@inheritDoc}
 	 * <p>
-	 * Objects known to exist but not expressed by {@link #getAllRefs()}.
+	 * Objects known to exist but not expressed by {@code #getAllRefs()}.
 	 * <p>
 	 * When a repository borrows objects from another repository, it can
 	 * advertise that it safely has that other repository's references, without
@@ -493,12 +444,12 @@ public Set<ObjectId> getAdditionalHaves() {
 	}
 
 	/**
-	 * Objects known to exist but not expressed by {@link #getAllRefs()}.
+	 * Objects known to exist but not expressed by {@code #getAllRefs()}.
 	 * <p>
 	 * When a repository borrows objects from another repository, it can
 	 * advertise that it safely has that other repository's references, without
-	 * exposing any other details about the other repository.  This may help
-	 * a client trying to push changes avoid pushing more than it needs to.
+	 * exposing any other details about the other repository. This may help a
+	 * client trying to push changes avoid pushing more than it needs to.
 	 *
 	 * @param skips
 	 *            Set of AlternateHandle Ids already seen
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 f26eba3..976f946 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
@@ -43,15 +43,24 @@
 
 package org.eclipse.jgit.internal.storage.file;
 
+import static org.eclipse.jgit.util.FS.FileStoreAttributes.FALLBACK_FILESTORE_ATTRIBUTES;
+import static org.eclipse.jgit.util.FS.FileStoreAttributes.FALLBACK_TIMESTAMP_RESOLUTION;
 import java.io.File;
 import java.io.IOException;
 import java.nio.file.attribute.BasicFileAttributes;
-import java.text.DateFormat;
-import java.text.SimpleDateFormat;
-import java.util.Date;
+import java.time.Duration;
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
 import java.util.Locale;
+import java.util.Objects;
+import java.util.concurrent.TimeUnit;
 
+import org.eclipse.jgit.annotations.NonNull;
 import org.eclipse.jgit.util.FS;
+import org.eclipse.jgit.util.FS.FileStoreAttributes;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 /**
  * Caches when a file was last read, making it possible to detect future edits.
@@ -70,6 +79,8 @@
  * file is less than 3 seconds ago.
  */
 public class FileSnapshot {
+	private static final Logger LOG = LoggerFactory
+			.getLogger(FileSnapshot.class);
 	/**
 	 * An unknown file size.
 	 *
@@ -77,6 +88,14 @@ public class FileSnapshot {
 	 */
 	public static final long UNKNOWN_SIZE = -1;
 
+	private static final Instant UNKNOWN_TIME = Instant.ofEpochMilli(-1);
+
+	private static final Object MISSING_FILEKEY = new Object();
+
+	private static final DateTimeFormatter dateFmt = DateTimeFormatter
+			.ofPattern("yyyy-MM-dd HH:mm:ss.nnnnnnnnn") //$NON-NLS-1$
+			.withLocale(Locale.getDefault()).withZone(ZoneId.systemDefault());
+
 	/**
 	 * A FileSnapshot that is considered to always be modified.
 	 * <p>
@@ -84,7 +103,8 @@ public class FileSnapshot {
 	 * file, but only after {@link #isModified(File)} gets invoked. The returned
 	 * snapshot contains only invalid status information.
 	 */
-	public static final FileSnapshot DIRTY = new FileSnapshot(-1, -1, UNKNOWN_SIZE);
+	public static final FileSnapshot DIRTY = new FileSnapshot(UNKNOWN_TIME,
+			UNKNOWN_TIME, UNKNOWN_SIZE, Duration.ZERO, MISSING_FILEKEY);
 
 	/**
 	 * A FileSnapshot that is clean if the file does not exist.
@@ -93,7 +113,8 @@ public class FileSnapshot {
 	 * file to be clean. {@link #isModified(File)} will return false if the file
 	 * path does not exist.
 	 */
-	public static final FileSnapshot MISSING_FILE = new FileSnapshot(0, 0, 0) {
+	public static final FileSnapshot MISSING_FILE = new FileSnapshot(
+			Instant.EPOCH, Instant.EPOCH, 0, Duration.ZERO, MISSING_FILEKEY) {
 		@Override
 		public boolean isModified(File path) {
 			return FS.DETECTED.exists(path);
@@ -111,18 +132,28 @@ public boolean isModified(File path) {
 	 * @return the snapshot.
 	 */
 	public static FileSnapshot save(File path) {
-		long read = System.currentTimeMillis();
-		long modified;
-		long size;
-		try {
-			BasicFileAttributes fileAttributes = FS.DETECTED.fileAttributes(path);
-			modified = fileAttributes.lastModifiedTime().toMillis();
-			size = fileAttributes.size();
-		} catch (IOException e) {
-			modified = path.lastModified();
-			size = path.length();
-		}
-		return new FileSnapshot(read, modified, size);
+		return new FileSnapshot(path);
+	}
+
+	/**
+	 * Record a snapshot for a specific file path without using config file to
+	 * get filesystem timestamp resolution.
+	 * <p>
+	 * This method should be invoked before the file is accessed. It is used by
+	 * FileBasedConfig to avoid endless recursion.
+	 *
+	 * @param path
+	 *            the path to later remember. The path's current status
+	 *            information is saved.
+	 * @return the snapshot.
+	 */
+	public static FileSnapshot saveNoConfig(File path) {
+		return new FileSnapshot(path, false);
+	}
+
+	private static Object getFileKey(BasicFileAttributes fileAttributes) {
+		Object fileKey = fileAttributes.fileKey();
+		return fileKey == null ? MISSING_FILEKEY : fileKey;
 	}
 
 	/**
@@ -130,21 +161,50 @@ public static FileSnapshot save(File path) {
 	 * already known.
 	 * <p>
 	 * This method should be invoked before the file is accessed.
+	 * <p>
+	 * Note that this method cannot rely on measuring file timestamp resolution
+	 * to avoid racy git issues caused by finite file timestamp resolution since
+	 * it's unknown in which filesystem the file is located. Hence the worst
+	 * case fallback for timestamp resolution is used.
+	 *
+	 * @param modified
+	 *            the last modification time of the file
+	 * @return the snapshot.
+	 * @deprecated use {@link #save(Instant)} instead.
+	 */
+	@Deprecated
+	public static FileSnapshot save(long modified) {
+		final Instant read = Instant.now();
+		return new FileSnapshot(read, Instant.ofEpochMilli(modified),
+				UNKNOWN_SIZE, FALLBACK_TIMESTAMP_RESOLUTION, MISSING_FILEKEY);
+	}
+
+	/**
+	 * Record a snapshot for a file for which the last modification time is
+	 * already known.
+	 * <p>
+	 * This method should be invoked before the file is accessed.
+	 * <p>
+	 * Note that this method cannot rely on measuring file timestamp resolution
+	 * to avoid racy git issues caused by finite file timestamp resolution since
+	 * it's unknown in which filesystem the file is located. Hence the worst
+	 * case fallback for timestamp resolution is used.
 	 *
 	 * @param modified
 	 *            the last modification time of the file
 	 * @return the snapshot.
 	 */
-	public static FileSnapshot save(long modified) {
-		final long read = System.currentTimeMillis();
-		return new FileSnapshot(read, modified, -1);
+	public static FileSnapshot save(Instant modified) {
+		final Instant read = Instant.now();
+		return new FileSnapshot(read, modified, UNKNOWN_SIZE,
+				FALLBACK_TIMESTAMP_RESOLUTION, MISSING_FILEKEY);
 	}
 
 	/** Last observed modification time of the path. */
-	private final long lastModified;
+	private final Instant lastModified;
 
 	/** Last wall-clock time the path was read. */
-	private volatile long lastRead;
+	private volatile Instant lastRead;
 
 	/** True once {@link #lastRead} is far later than {@link #lastModified}. */
 	private boolean cannotBeRacilyClean;
@@ -154,11 +214,100 @@ public static FileSnapshot save(long modified) {
 	 * When set to {@link #UNKNOWN_SIZE} the size is not considered for modification checks. */
 	private final long size;
 
-	private FileSnapshot(long read, long modified, long size) {
+	/** measured FileStore attributes */
+	private FileStoreAttributes fileStoreAttributeCache;
+
+	/**
+	 * Object that uniquely identifies the given file, or {@code
+	 * null} if a file key is not available
+	 */
+	private final Object fileKey;
+
+	private final File file;
+
+	/**
+	 * Record a snapshot for a specific file path.
+	 * <p>
+	 * This method should be invoked before the file is accessed.
+	 *
+	 * @param file
+	 *            the path to remember meta data for. The path's current status
+	 *            information is saved.
+	 */
+	protected FileSnapshot(File file) {
+		this(file, true);
+	}
+
+	/**
+	 * Record a snapshot for a specific file path.
+	 * <p>
+	 * This method should be invoked before the file is accessed.
+	 *
+	 * @param file
+	 *            the path to remember meta data for. The path's current status
+	 *            information is saved.
+	 * @param useConfig
+	 *            if {@code true} read filesystem time resolution from
+	 *            configuration file otherwise use fallback resolution
+	 */
+	protected FileSnapshot(File file, boolean useConfig) {
+		this.file = file;
+		this.lastRead = Instant.now();
+		this.fileStoreAttributeCache = useConfig
+				? FS.getFileStoreAttributes(file.toPath().getParent())
+				: FALLBACK_FILESTORE_ATTRIBUTES;
+		BasicFileAttributes fileAttributes = null;
+		try {
+			fileAttributes = FS.DETECTED.fileAttributes(file);
+		} catch (IOException e) {
+			this.lastModified = Instant.ofEpochMilli(file.lastModified());
+			this.size = file.length();
+			this.fileKey = MISSING_FILEKEY;
+			return;
+		}
+		this.lastModified = fileAttributes.lastModifiedTime().toInstant();
+		this.size = fileAttributes.size();
+		this.fileKey = getFileKey(fileAttributes);
+		if (LOG.isDebugEnabled()) {
+			LOG.debug("file={}, create new FileSnapshot: lastRead={}, lastModified={}, size={}, fileKey={}", //$NON-NLS-1$
+					file, dateFmt.format(lastRead),
+					dateFmt.format(lastModified), Long.valueOf(size),
+					fileKey.toString());
+		}
+	}
+
+	private boolean sizeChanged;
+
+	private boolean fileKeyChanged;
+
+	private boolean lastModifiedChanged;
+
+	private boolean wasRacyClean;
+
+	private long delta;
+
+	private long racyThreshold;
+
+	private FileSnapshot(Instant read, Instant modified, long size,
+			@NonNull Duration fsTimestampResolution, @NonNull Object fileKey) {
+		this.file = null;
 		this.lastRead = read;
 		this.lastModified = modified;
-		this.cannotBeRacilyClean = notRacyClean(read);
+		this.fileStoreAttributeCache = new FileStoreAttributes(
+				fsTimestampResolution);
 		this.size = size;
+		this.fileKey = fileKey;
+	}
+
+	/**
+	 * Get time of last snapshot update
+	 *
+	 * @return time of last snapshot update
+	 * @deprecated use {@link #lastModifiedInstant()} instead
+	 */
+	@Deprecated
+	public long lastModified() {
+		return lastModified.toEpochMilli();
 	}
 
 	/**
@@ -166,7 +315,7 @@ private FileSnapshot(long read, long modified, long size) {
 	 *
 	 * @return time of last snapshot update
 	 */
-	public long lastModified() {
+	public Instant lastModifiedInstant() {
 		return lastModified;
 	}
 
@@ -185,17 +334,32 @@ public long size() {
 	 * @return true if the path needs to be read again.
 	 */
 	public boolean isModified(File path) {
-		long currLastModified;
+		Instant currLastModified;
 		long currSize;
+		Object currFileKey;
 		try {
 			BasicFileAttributes fileAttributes = FS.DETECTED.fileAttributes(path);
-			currLastModified = fileAttributes.lastModifiedTime().toMillis();
+			currLastModified = fileAttributes.lastModifiedTime().toInstant();
 			currSize = fileAttributes.size();
+			currFileKey = getFileKey(fileAttributes);
 		} catch (IOException e) {
-			currLastModified = path.lastModified();
+			currLastModified = Instant.ofEpochMilli(path.lastModified());
 			currSize = path.length();
+			currFileKey = MISSING_FILEKEY;
 		}
-		return (currSize != UNKNOWN_SIZE && currSize != size) || isModified(currLastModified);
+		sizeChanged = isSizeChanged(currSize);
+		if (sizeChanged) {
+			return true;
+		}
+		fileKeyChanged = isFileKeyChanged(currFileKey);
+		if (fileKeyChanged) {
+			return true;
+		}
+		lastModifiedChanged = isModified(currLastModified);
+		if (lastModifiedChanged) {
+			return true;
+		}
+		return false;
 	}
 
 	/**
@@ -221,13 +385,28 @@ public boolean isModified(File path) {
 	 *            the other snapshot.
 	 */
 	public void setClean(FileSnapshot other) {
-		final long now = other.lastRead;
-		if (notRacyClean(now))
+		final Instant now = other.lastRead;
+		if (!isRacyClean(now)) {
 			cannotBeRacilyClean = true;
+		}
 		lastRead = now;
 	}
 
 	/**
+	 * Wait until this snapshot's file can't be racy anymore
+	 *
+	 * @throws InterruptedException
+	 *             if sleep was interrupted
+	 */
+	public void waitUntilNotRacy() throws InterruptedException {
+		long timestampResolution = fileStoreAttributeCache
+				.getFsTimestampResolution().toNanos();
+		while (isRacyClean(Instant.now())) {
+			TimeUnit.NANOSECONDS.sleep(timestampResolution);
+		}
+	}
+
+	/**
 	 * Compare two snapshots to see if they cache the same information.
 	 *
 	 * @param other
@@ -235,72 +414,172 @@ public void setClean(FileSnapshot other) {
 	 * @return true if the two snapshots share the same information.
 	 */
 	public boolean equals(FileSnapshot other) {
-		return lastModified == other.lastModified;
+		boolean sizeEq = size == UNKNOWN_SIZE || other.size == UNKNOWN_SIZE || size == other.size;
+		return lastModified.equals(other.lastModified) && sizeEq
+				&& Objects.equals(fileKey, other.fileKey);
 	}
 
 	/** {@inheritDoc} */
 	@Override
-	public boolean equals(Object other) {
-		if (other instanceof FileSnapshot)
-			return equals((FileSnapshot) other);
-		return false;
+	public boolean equals(Object obj) {
+		if (this == obj) {
+			return true;
+		}
+		if (obj == null) {
+			return false;
+		}
+		if (!(obj instanceof FileSnapshot)) {
+			return false;
+		}
+		FileSnapshot other = (FileSnapshot) obj;
+		return equals(other);
 	}
 
 	/** {@inheritDoc} */
 	@Override
 	public int hashCode() {
-		// This is pretty pointless, but override hashCode to ensure that
-		// x.hashCode() == y.hashCode() when x.equals(y) is true.
-		//
-		return (int) lastModified;
+		return Objects.hash(lastModified, Long.valueOf(size), fileKey);
+	}
+
+	/**
+	 * @return {@code true} if FileSnapshot.isModified(File) found the file size
+	 *         changed
+	 */
+	boolean wasSizeChanged() {
+		return sizeChanged;
+	}
+
+	/**
+	 * @return {@code true} if FileSnapshot.isModified(File) found the file key
+	 *         changed
+	 */
+	boolean wasFileKeyChanged() {
+		return fileKeyChanged;
+	}
+
+	/**
+	 * @return {@code true} if FileSnapshot.isModified(File) found the file's
+	 *         lastModified changed
+	 */
+	boolean wasLastModifiedChanged() {
+		return lastModifiedChanged;
+	}
+
+	/**
+	 * @return {@code true} if FileSnapshot.isModified(File) detected that
+	 *         lastModified is racily clean
+	 */
+	boolean wasLastModifiedRacilyClean() {
+		return wasRacyClean;
+	}
+
+	/**
+	 * @return the delta in nanoseconds between lastModified and lastRead during
+	 *         last racy check
+	 */
+	public long lastDelta() {
+		return delta;
+	}
+
+	/**
+	 * @return the racyLimitNanos threshold in nanoseconds during last racy
+	 *         check
+	 */
+	public long lastRacyThreshold() {
+		return racyThreshold;
 	}
 
 	/** {@inheritDoc} */
+	@SuppressWarnings("nls")
 	@Override
 	public String toString() {
-		if (this == DIRTY)
-			return "DIRTY"; //$NON-NLS-1$
-		if (this == MISSING_FILE)
-			return "MISSING_FILE"; //$NON-NLS-1$
-		DateFormat f = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", //$NON-NLS-1$
-				Locale.US);
-		return "FileSnapshot[modified: " + f.format(new Date(lastModified)) //$NON-NLS-1$
-				+ ", read: " + f.format(new Date(lastRead)) + "]"; //$NON-NLS-1$ //$NON-NLS-2$
+		if (this == DIRTY) {
+			return "DIRTY";
+		}
+		if (this == MISSING_FILE) {
+			return "MISSING_FILE";
+		}
+		return "FileSnapshot[modified: " + dateFmt.format(lastModified)
+				+ ", read: " + dateFmt.format(lastRead) + ", size:" + size
+				+ ", fileKey: " + fileKey + "]";
 	}
 
-	private boolean notRacyClean(long read) {
-		// The last modified time granularity of FAT filesystems is 2 seconds.
-		// Using 2.5 seconds here provides a reasonably high assurance that
-		// a modification was not missed.
-		//
-		return read - lastModified > 2500;
+	private boolean isRacyClean(Instant read) {
+		racyThreshold = getEffectiveRacyThreshold();
+		delta = Duration.between(lastModified, read).toNanos();
+		wasRacyClean = delta <= racyThreshold;
+		if (LOG.isDebugEnabled()) {
+			LOG.debug(
+					"file={}, isRacyClean={}, read={}, lastModified={}, delta={} ns, racy<={} ns", //$NON-NLS-1$
+					file, Boolean.valueOf(wasRacyClean), dateFmt.format(read),
+					dateFmt.format(lastModified), Long.valueOf(delta),
+					Long.valueOf(racyThreshold));
+		}
+		return wasRacyClean;
 	}
 
-	private boolean isModified(long currLastModified) {
+	private long getEffectiveRacyThreshold() {
+		long timestampResolution = fileStoreAttributeCache
+				.getFsTimestampResolution().toNanos();
+		long minRacyInterval = fileStoreAttributeCache.getMinimalRacyInterval()
+				.toNanos();
+		long max = Math.max(timestampResolution, minRacyInterval);
+		// safety margin: factor 2.5 below 100ms otherwise 1.25
+		return max < 100_000_000L ? max * 5 / 2 : max * 5 / 4;
+	}
+
+	private boolean isModified(Instant currLastModified) {
 		// Any difference indicates the path was modified.
-		//
-		if (lastModified != currLastModified)
+
+		lastModifiedChanged = !lastModified.equals(currLastModified);
+		if (lastModifiedChanged) {
+			if (LOG.isDebugEnabled()) {
+				LOG.debug(
+						"file={}, lastModified changed from {} to {}", //$NON-NLS-1$
+						file, dateFmt.format(lastModified),
+						dateFmt.format(currLastModified));
+			}
 			return true;
+		}
 
 		// We have already determined the last read was far enough
 		// after the last modification that any new modifications
 		// are certain to change the last modified time.
-		//
-		if (cannotBeRacilyClean)
+		if (cannotBeRacilyClean) {
+			LOG.debug("file={}, cannot be racily clean", file); //$NON-NLS-1$
 			return false;
-
-		if (notRacyClean(lastRead)) {
+		}
+		if (!isRacyClean(lastRead)) {
 			// Our last read should have marked cannotBeRacilyClean,
 			// but this thread may not have seen the change. The read
 			// of the volatile field lastRead should have fixed that.
-			//
+			LOG.debug("file={}, is unmodified", file); //$NON-NLS-1$
 			return false;
 		}
 
 		// We last read this path too close to its last observed
 		// modification time. We may have missed a modification.
 		// Scan again, to ensure we still see the same state.
-		//
+		LOG.debug("file={}, is racily clean", file); //$NON-NLS-1$
 		return true;
 	}
+
+	private boolean isFileKeyChanged(Object currFileKey) {
+		boolean changed = currFileKey != MISSING_FILEKEY
+				&& !currFileKey.equals(fileKey);
+		if (changed) {
+			LOG.debug("file={}, FileKey changed from {} to {}", //$NON-NLS-1$
+					file, fileKey, currFileKey);
+		}
+		return changed;
+	}
+
+	private boolean isSizeChanged(long currSize) {
+		boolean changed = (currSize != UNKNOWN_SIZE) && (currSize != size);
+		if (changed) {
+			LOG.debug("file={}, size changed from {} to {} bytes", //$NON-NLS-1$
+					file, Long.valueOf(size), Long.valueOf(currSize));
+		}
+		return changed;
+	}
 }
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 cfe0294..791a108 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
@@ -178,7 +178,7 @@ public static void setExecutor(ExecutorService e) {
 
 	private Date packExpire;
 
-	private PackConfig pconfig = null;
+	private PackConfig pconfig;
 
 	/**
 	 * the refs which existed during the last call to {@link #repack()}. This is
@@ -214,6 +214,7 @@ public static void setExecutor(ExecutorService e) {
 	 */
 	public GC(FileRepository repo) {
 		this.repo = repo;
+		this.pconfig = new PackConfig(repo);
 		this.pm = NullProgressMonitor.INSTANCE;
 	}
 
@@ -245,6 +246,7 @@ public GC(FileRepository repo) {
 	 *             parsed
 	 */
 	// TODO(ms): in 5.0 change signature and return Future<Collection<PackFile>>
+	@SuppressWarnings("FutureReturnValueIgnored")
 	public Collection<PackFile> gc() throws IOException, ParseException {
 		if (!background) {
 			return doGc();
@@ -370,8 +372,9 @@ private void deleteOldPacks(Collection<PackFile> oldPacks,
 					continue oldPackLoop;
 
 			if (!oldPack.shouldBeKept()
-					&& repo.getFS().lastModified(
-							oldPack.getPackFile()) < packExpireDate) {
+					&& repo.getFS()
+							.lastModifiedInstant(oldPack.getPackFile())
+							.toEpochMilli() < packExpireDate) {
 				oldPack.close();
 				if (shouldLoosen) {
 					loosen(inserter, reader, oldPack, ids);
@@ -397,7 +400,7 @@ private void deleteOldPacks(Collection<PackFile> oldPacks,
 	 */
 	private void removeOldPack(File packFile, String packName, PackExt ext,
 			int deleteOptions) throws IOException {
-		if (pconfig != null && pconfig.isPreserveOldPacks()) {
+		if (pconfig.isPreserveOldPacks()) {
 			File oldPackDir = repo.getObjectDatabase().getPreservedDirectory();
 			FileUtils.mkdir(oldPackDir, true);
 
@@ -413,7 +416,7 @@ private void removeOldPack(File packFile, String packName, PackExt ext,
 	 * Delete the preserved directory including all pack files within
 	 */
 	private void prunePreserved() {
-		if (pconfig != null && pconfig.isPrunePreserved()) {
+		if (pconfig.isPrunePreserved()) {
 			try {
 				FileUtils.delete(repo.getObjectDatabase().getPreservedDirectory(),
 						FileUtils.RECURSIVE | FileUtils.RETRY | FileUtils.SKIP_MISSING);
@@ -559,8 +562,10 @@ public void prune(Set<ObjectId> objectsToKeep) throws IOException,
 					String fName = f.getName();
 					if (fName.length() != Constants.OBJECT_ID_STRING_LENGTH - 2)
 						continue;
-					if (repo.getFS().lastModified(f) >= expireDate)
+					if (repo.getFS().lastModifiedInstant(f)
+							.toEpochMilli() >= expireDate) {
 						continue;
+					}
 					try {
 						ObjectId id = ObjectId.fromString(d + fName);
 						if (objectsToKeep.contains(id))
@@ -855,7 +860,7 @@ public Collection<PackFile> repack() throws IOException {
 		nonHeads.addAll(indexObjects);
 
 		// Combine the GC_REST objects into the GC pack if requested
-		if (pconfig != null && pconfig.getSinglePack()) {
+		if (pconfig.getSinglePack()) {
 			allHeadsAndTags.addAll(nonHeads);
 			nonHeads.clear();
 		}
@@ -1158,7 +1163,7 @@ private PackFile writePack(@NonNull Set<? extends ObjectId> want,
 			return Integer.signum(o1.hashCode() - o2.hashCode());
 		});
 		try (PackWriter pw = new PackWriter(
-				(pconfig == null) ? new PackConfig(repo) : pconfig,
+				pconfig,
 				repo.newObjectReader())) {
 			// prepare the PackWriter
 			pw.setDeltaBaseAsOffset(true);
@@ -1254,8 +1259,23 @@ private PackFile writePack(@NonNull Set<? extends ObjectId> want,
 							realExt), e);
 				}
 			}
-
-			return repo.getObjectDatabase().openPack(realPack);
+			boolean interrupted = false;
+			try {
+				FileSnapshot snapshot = FileSnapshot.save(realPack);
+				if (pconfig.doWaitPreventRacyPack(snapshot.size())) {
+					snapshot.waitUntilNotRacy();
+				}
+			} catch (InterruptedException e) {
+				interrupted = true;
+			}
+			try {
+				return repo.getObjectDatabase().openPack(realPack);
+			} finally {
+				if (interrupted) {
+					// Re-set interrupted flag
+					Thread.currentThread().interrupt();
+				}
+			}
 		} finally {
 			if (tmpPack != null && tmpPack.exists())
 				tmpPack.delete();
@@ -1433,7 +1453,7 @@ public void setPackExpireAgeMillis(long packExpireAgeMillis) {
 	 *            the {@link org.eclipse.jgit.storage.pack.PackConfig} used when
 	 *            writing packs
 	 */
-	public void setPackConfig(PackConfig pconfig) {
+	public void setPackConfig(@NonNull PackConfig pconfig) {
 		this.pconfig = pconfig;
 	}
 
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GcLog.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GcLog.java
index 0e587ce..82458c1 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GcLog.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GcLog.java
@@ -43,7 +43,7 @@
 
 package org.eclipse.jgit.internal.storage.file;
 
-import static org.eclipse.jgit.lib.Constants.CHARSET;
+import static java.nio.charset.StandardCharsets.UTF_8;
 
 import java.io.File;
 import java.io.IOException;
@@ -171,6 +171,6 @@ void write(String content) throws IOException {
 		if (content.length() > 0) {
 			nonEmpty = true;
 		}
-		lock.write(content.getBytes(CHARSET));
+		lock.write(content.getBytes(UTF_8));
 	}
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LazyObjectIdSetFile.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LazyObjectIdSetFile.java
index c82d52e..3d0e9c7 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LazyObjectIdSetFile.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LazyObjectIdSetFile.java
@@ -43,7 +43,7 @@
 
 package org.eclipse.jgit.internal.storage.file;
 
-import static org.eclipse.jgit.lib.Constants.CHARSET;
+import static java.nio.charset.StandardCharsets.UTF_8;
 
 import java.io.BufferedReader;
 import java.io.File;
@@ -86,7 +86,7 @@ public boolean contains(AnyObjectId objectId) {
 	private ObjectIdOwnerMap<Entry> load() {
 		ObjectIdOwnerMap<Entry> r = new ObjectIdOwnerMap<>();
 		try (FileInputStream fin = new FileInputStream(src);
-				Reader rin = new InputStreamReader(fin, CHARSET);
+				Reader rin = new InputStreamReader(fin, UTF_8);
 				BufferedReader br = new BufferedReader(rin)) {
 			MutableObjectId id = new MutableObjectId();
 			for (String line; (line = br.readLine()) != null;) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LocalCachedPack.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LocalCachedPack.java
index 289d89d..e931c1f 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LocalCachedPack.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LocalCachedPack.java
@@ -62,13 +62,13 @@ class LocalCachedPack extends CachedPack {
 
 	LocalCachedPack(ObjectDirectory odb, List<String> packNames) {
 		this.odb = odb;
-		this.packNames = packNames.toArray(new String[packNames.size()]);
+		this.packNames = packNames.toArray(new String[0]);
 	}
 
 	LocalCachedPack(List<PackFile> packs) {
 		odb = null;
 		packNames = null;
-		this.packs = packs.toArray(new PackFile[packs.size()]);
+		this.packs = packs.toArray(new PackFile[0]);
 	}
 
 	/** {@inheritDoc} */
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 b80c58c..f7e78b9 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
@@ -56,8 +56,12 @@
 import java.nio.ByteBuffer;
 import java.nio.channels.Channels;
 import java.nio.channels.FileChannel;
+import java.nio.file.Files;
 import java.nio.file.StandardCopyOption;
+import java.nio.file.attribute.FileTime;
 import java.text.MessageFormat;
+import java.time.Instant;
+import java.util.concurrent.TimeUnit;
 
 import org.eclipse.jgit.internal.JGitText;
 import org.eclipse.jgit.lib.Constants;
@@ -161,7 +165,12 @@ public LockFile(File f) {
 	 */
 	public boolean lock() throws IOException {
 		FileUtils.mkdirs(lck.getParentFile(), true);
-		token = FS.DETECTED.createNewFileAtomic(lck);
+		try {
+			token = FS.DETECTED.createNewFileAtomic(lck);
+		} catch (IOException e) {
+			LOG.error(JGitText.get().failedCreateLockFile, lck, e);
+			throw e;
+		}
 		if (token.isCreated()) {
 			haveLck = true;
 			try {
@@ -422,9 +431,16 @@ public void setFSync(boolean on) {
 	public void waitForStatChange() throws InterruptedException {
 		FileSnapshot o = FileSnapshot.save(ref);
 		FileSnapshot n = FileSnapshot.save(lck);
+		long fsTimeResolution = FS.getFileStoreAttributes(lck.toPath())
+				.getFsTimestampResolution().toNanos();
 		while (o.equals(n)) {
-			Thread.sleep(25 /* milliseconds */);
-			lck.setLastModified(System.currentTimeMillis());
+			TimeUnit.NANOSECONDS.sleep(fsTimeResolution);
+			try {
+				Files.setLastModifiedTime(lck.toPath(),
+						FileTime.from(Instant.now()));
+			} catch (IOException e) {
+				n.waitUntilNotRacy();
+			}
 			n = FileSnapshot.save(lck);
 		}
 	}
@@ -474,12 +490,23 @@ private void saveStatInformation() {
 	 * Get the modification time of the output file when it was committed.
 	 *
 	 * @return modification time of the lock file right before we committed it.
+	 * @deprecated use {@link #getCommitLastModifiedInstant()} instead
 	 */
+	@Deprecated
 	public long getCommitLastModified() {
 		return commitSnapshot.lastModified();
 	}
 
 	/**
+	 * Get the modification time of the output file when it was committed.
+	 *
+	 * @return modification time of the lock file right before we committed it.
+	 */
+	public Instant getCommitLastModifiedInstant() {
+		return commitSnapshot.lastModifiedInstant();
+	}
+
+	/**
 	 * Get the {@link FileSnapshot} just before commit.
 	 *
 	 * @return get the {@link FileSnapshot} just before commit.
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectory.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectory.java
index 103ab10..e35b9c9 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectory.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectory.java
@@ -911,9 +911,10 @@ private PackList scanPacksImpl(PackList old) {
 
 			final String packName = base + PACK.getExtension();
 			final File packFile = new File(packDirectory, packName);
-			final PackFile oldPack = forReuse.remove(packName);
+			final PackFile oldPack = forReuse.get(packName);
 			if (oldPack != null
 					&& !oldPack.getFileSnapshot().isModified(packFile)) {
+				forReuse.remove(packName);
 				list.add(oldPack);
 				continue;
 			}
@@ -939,7 +940,7 @@ private PackList scanPacksImpl(PackList old) {
 		if (list.isEmpty())
 			return new PackList(snapshot, NO_PACKS.packs);
 
-		final PackFile[] r = list.toArray(new PackFile[list.size()]);
+		final PackFile[] r = list.toArray(new PackFile[0]);
 		Arrays.sort(r, PackFile.SORT);
 		return new PackList(snapshot, r);
 	}
@@ -1031,7 +1032,7 @@ Set<AlternateHandle.Id> addMe(Set<AlternateHandle.Id> skips) {
 				l.add(openAlternate(line));
 			}
 		}
-		return l.toArray(new AlternateHandle[l.size()]);
+		return l.toArray(new AlternateHandle[0]);
 	}
 
 	private static BufferedReader open(File f)
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 0cec2d5..6e8a15e 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
@@ -65,6 +65,7 @@
 import org.eclipse.jgit.lib.CoreConfig;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.ProgressMonitor;
+import org.eclipse.jgit.storage.pack.PackConfig;
 import org.eclipse.jgit.transport.PackParser;
 import org.eclipse.jgit.transport.PackedObjectInfo;
 import org.eclipse.jgit.util.FileUtils;
@@ -122,9 +123,12 @@ public class ObjectDirectoryPackParser extends PackParser {
 	/** The pack that was created, if parsing was successful. */
 	private PackFile newPack;
 
+	private PackConfig pconfig;
+
 	ObjectDirectoryPackParser(FileObjectDatabase odb, InputStream src) {
 		super(odb, src);
 		this.db = odb;
+		this.pconfig = new PackConfig(odb.getConfig());
 		this.crc = new CRC32();
 		this.tailDigest = Constants.newMessageDigest();
 
@@ -514,6 +518,15 @@ private PackLock renameAndOpenPack(String lockMessage)
 					JGitText.get().cannotMoveIndexTo, finalIdx), e);
 		}
 
+		boolean interrupted = false;
+		try {
+			FileSnapshot snapshot = FileSnapshot.save(finalPack);
+			if (pconfig.doWaitPreventRacyPack(snapshot.size())) {
+				snapshot.waitUntilNotRacy();
+			}
+		} catch (InterruptedException e) {
+			interrupted = true;
+		}
 		try {
 			newPack = db.openPack(finalPack);
 		} catch (IOException err) {
@@ -523,6 +536,11 @@ private PackLock renameAndOpenPack(String lockMessage)
 			if (finalIdx.exists())
 				FileUtils.delete(finalIdx);
 			throw err;
+		} finally {
+			if (interrupted) {
+				// Re-set interrupted flag
+				Thread.currentThread().interrupt();
+			}
 		}
 
 		return lockMessage != null ? keep : null;
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 70eb10e..eff7958 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
@@ -351,6 +351,7 @@ public StoredEntry next() {
 						PositionEntry entry = positionEntries.get(item);
 						if (entry == null)
 							throw new IllegalStateException();
+						bestBitmap.trim();
 						return new StoredEntry(entry.namePosition, bestBitmap,
 								bestXorOffset, item.getFlags());
 					}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackBitmapIndexRemapper.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackBitmapIndexRemapper.java
index c04c90f..964cc4d 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackBitmapIndexRemapper.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackBitmapIndexRemapper.java
@@ -200,13 +200,14 @@ public EWAHCompressedBitmap getBitmap(AnyObjectId objectId) {
 		for (IntIterator i = oldBitmap.getBitmap().intIterator(); i.hasNext();)
 			inflated.set(prevToNewMapping[i.next()]);
 		bitmap = inflated.toEWAHCompressedBitmap();
+		bitmap.trim();
 		convertedBitmaps.add(
 				new StoredBitmap(objectId, bitmap, null, oldBitmap.getFlags()));
 		return bitmap;
 	}
 
 	/** An entry in the old PackBitmapIndex. */
-	public final class Entry extends ObjectId {
+	public static final class Entry extends ObjectId {
 		private final int flags;
 
 		Entry(AnyObjectId src, int flags) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackFile.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackFile.java
index d834b1a..88e05af 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackFile.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackFile.java
@@ -60,6 +60,7 @@
 import java.nio.file.AccessDeniedException;
 import java.nio.file.NoSuchFileException;
 import java.text.MessageFormat;
+import java.time.Instant;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.Comparator;
@@ -93,6 +94,8 @@
 import org.eclipse.jgit.util.LongList;
 import org.eclipse.jgit.util.NB;
 import org.eclipse.jgit.util.RawParseUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 /**
  * A Git version 2 pack file representation. A pack file contains Git objects in
@@ -100,11 +103,12 @@
  * objects are similar.
  */
 public class PackFile implements Iterable<PackIndex.MutableEntry> {
+	private final static Logger LOG = LoggerFactory.getLogger(PackFile.class);
 	/** Sorts PackFiles to be most recently created to least recently created. */
 	public static final Comparator<PackFile> SORT = new Comparator<PackFile>() {
 		@Override
 		public int compare(PackFile a, PackFile b) {
-			return b.packLastModified - a.packLastModified;
+			return b.packLastModified.compareTo(a.packLastModified);
 		}
 	};
 
@@ -129,9 +133,9 @@ public int compare(PackFile a, PackFile b) {
 
 	private int activeCopyRawData;
 
-	int packLastModified;
+	Instant packLastModified;
 
-	private FileSnapshot fileSnapshot;
+	private PackFileSnapshot fileSnapshot;
 
 	private volatile boolean invalid;
 
@@ -168,8 +172,8 @@ public int compare(PackFile a, PackFile b) {
 	 */
 	public PackFile(File packFile, int extensions) {
 		this.packFile = packFile;
-		this.fileSnapshot = FileSnapshot.save(packFile);
-		this.packLastModified = (int) (fileSnapshot.lastModified() >> 10);
+		this.fileSnapshot = PackFileSnapshot.save(packFile);
+		this.packLastModified = fileSnapshot.lastModifiedInstant();
 		this.extensions = extensions;
 
 		// Multiply by 31 here so we can more directly combine with another
@@ -189,10 +193,22 @@ private PackIndex idx() throws IOException {
 						throw new PackInvalidException(packFile, invalidatingCause);
 					}
 					try {
+						long start = System.currentTimeMillis();
 						idx = PackIndex.open(extFile(INDEX));
+						if (LOG.isDebugEnabled()) {
+							LOG.debug(String.format(
+									"Opening pack index %s, size %.3f MB took %d ms", //$NON-NLS-1$
+									extFile(INDEX).getAbsolutePath(),
+									Float.valueOf(extFile(INDEX).length()
+											/ (1024f * 1024)),
+									Long.valueOf(System.currentTimeMillis()
+											- start)));
+						}
 
 						if (packChecksum == null) {
 							packChecksum = idx.packChecksum;
+							fileSnapshot.setChecksum(
+									ObjectId.fromRaw(packChecksum));
 						} else if (!Arrays.equals(packChecksum,
 								idx.packChecksum)) {
 							throw new PackMismatchException(MessageFormat
@@ -371,10 +387,14 @@ ObjectId findObjectForOffset(long offset) throws IOException {
 	 *
 	 * @return the packfile @{@link FileSnapshot} that the object is loaded from.
 	 */
-	FileSnapshot getFileSnapshot() {
+	PackFileSnapshot getFileSnapshot() {
 		return fileSnapshot;
 	}
 
+	AnyObjectId getPackChecksum() {
+		return ObjectId.fromRaw(packChecksum);
+	}
+
 	private final byte[] decompress(final long position, final int sz,
 			final WindowCursor curs) throws IOException, DataFormatException {
 		byte[] dstbuf;
@@ -1209,4 +1229,12 @@ private File extFile(PackExt ext) {
 	private boolean hasExt(PackExt ext) {
 		return (extensions & ext.getBit()) != 0;
 	}
+
+	@SuppressWarnings("nls")
+	@Override
+	public String toString() {
+		return "PackFile [packFileName=" + packFile.getName() + ", length="
+				+ packFile.length() + ", packChecksum="
+				+ ObjectId.fromRaw(packChecksum).name() + "]";
+	}
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackFileSnapshot.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackFileSnapshot.java
new file mode 100644
index 0000000..19ec3af
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackFileSnapshot.java
@@ -0,0 +1,123 @@
+/*
+ * 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.internal.storage.file;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+
+import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.lib.ObjectId;
+
+class PackFileSnapshot extends FileSnapshot {
+
+	private static final ObjectId MISSING_CHECKSUM = ObjectId.zeroId();
+
+	/**
+	 * Record a snapshot for a specific packfile path.
+	 * <p>
+	 * This method should be invoked before the packfile is accessed.
+	 *
+	 * @param path
+	 *            the path to later remember. The path's current status
+	 *            information is saved.
+	 * @return the snapshot.
+	 */
+	public static PackFileSnapshot save(File path) {
+		return new PackFileSnapshot(path);
+	}
+
+	private AnyObjectId checksum = MISSING_CHECKSUM;
+
+	private boolean wasChecksumChanged;
+
+
+	PackFileSnapshot(File packFile) {
+		super(packFile);
+	}
+
+	void setChecksum(AnyObjectId checksum) {
+		this.checksum = checksum;
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public boolean isModified(File packFile) {
+		if (!super.isModified(packFile)) {
+			return false;
+		}
+		if (wasSizeChanged() || wasFileKeyChanged()
+				|| !wasLastModifiedRacilyClean()) {
+			return true;
+		}
+		return isChecksumChanged(packFile);
+	}
+
+	boolean isChecksumChanged(File packFile) {
+		return wasChecksumChanged = checksum != MISSING_CHECKSUM
+				&& !checksum.equals(readChecksum(packFile));
+	}
+
+	private AnyObjectId readChecksum(File packFile) {
+		try (RandomAccessFile fd = new RandomAccessFile(packFile, "r")) { //$NON-NLS-1$
+			fd.seek(fd.length() - 20);
+			final byte[] buf = new byte[20];
+			fd.readFully(buf, 0, 20);
+			return ObjectId.fromRaw(buf);
+		} catch (IOException e) {
+			return MISSING_CHECKSUM;
+		}
+	}
+
+	boolean wasChecksumChanged() {
+		return wasChecksumChanged;
+	}
+
+	@SuppressWarnings("nls")
+	@Override
+	public String toString() {
+		return "PackFileSnapshot [checksum=" + checksum + ", "
+				+ super.toString() + "]";
+	}
+
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackInserter.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackInserter.java
index 0ce3cc9..a27a2b0 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackInserter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackInserter.java
@@ -86,6 +86,7 @@
 import org.eclipse.jgit.lib.ObjectLoader;
 import org.eclipse.jgit.lib.ObjectReader;
 import org.eclipse.jgit.lib.ObjectStream;
+import org.eclipse.jgit.storage.pack.PackConfig;
 import org.eclipse.jgit.transport.PackParser;
 import org.eclipse.jgit.transport.PackedObjectInfo;
 import org.eclipse.jgit.util.BlockList;
@@ -115,8 +116,11 @@ public class PackInserter extends ObjectInserter {
 	private PackStream packOut;
 	private Inflater cachedInflater;
 
+	private PackConfig pconfig;
+
 	PackInserter(ObjectDirectory db) {
 		this.db = db;
+		this.pconfig = new PackConfig(db.getConfig());
 	}
 
 	/**
@@ -296,9 +300,25 @@ public void flush() throws IOException {
 					realIdx), e);
 		}
 
-		db.openPack(realPack);
-		rollback = false;
-		clear();
+		boolean interrupted = false;
+		try {
+			FileSnapshot snapshot = FileSnapshot.save(realPack);
+			if (pconfig.doWaitPreventRacyPack(snapshot.size())) {
+				snapshot.waitUntilNotRacy();
+			}
+		} catch (InterruptedException e) {
+			interrupted = true;
+		}
+		try {
+			db.openPack(realPack);
+			rollback = false;
+		} finally {
+			clear();
+			if (interrupted) {
+				// Re-set interrupted flag
+				Thread.currentThread().interrupt();
+			}
+		}
 	}
 
 	private static void writePackIndex(File idx, byte[] packHash,
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 6a18df8..de7e4b3 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
@@ -46,7 +46,7 @@
 
 package org.eclipse.jgit.internal.storage.file;
 
-import static org.eclipse.jgit.lib.Constants.CHARSET;
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.eclipse.jgit.lib.Constants.HEAD;
 import static org.eclipse.jgit.lib.Constants.LOGS;
 import static org.eclipse.jgit.lib.Constants.OBJECT_ID_STRING_LENGTH;
@@ -945,7 +945,7 @@ private PackedRefList readPackedRefs() throws IOException {
 			try (BufferedReader br = new BufferedReader(new InputStreamReader(
 					new DigestInputStream(new FileInputStream(packedRefsFile),
 							digest),
-					CHARSET))) {
+					UTF_8))) {
 				try {
 					return new PackedRefList(parsePackedRefs(br), snapshot,
 							ObjectId.fromRaw(digest.digest()));
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/BaseSearch.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/BaseSearch.java
index d231ccb..f42b3a5 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/BaseSearch.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/BaseSearch.java
@@ -91,7 +91,7 @@ class BaseSearch {
 			List<ObjectToPack> edges, ObjectReader or) {
 		progress = countingMonitor;
 		reader = or;
-		baseTrees = bases.toArray(new ObjectId[bases.size()]);
+		baseTrees = bases.toArray(new ObjectId[0]);
 		objectsMap = objects;
 		edgeObjects = edges;
 
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 36d6f0a..24af8a7 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
@@ -1970,7 +1970,7 @@ private void findObjectsToPack(@NonNull ProgressMonitor countingMonitor,
 				byte[] pathBuf = walker.getPathBuffer();
 				int pathLen = walker.getPathLength();
 				bases.addBase(o.getType(), pathBuf, pathLen, pathHash);
-				filterAndAddObject(o, o.getType(), pathHash);
+				filterAndAddObject(o, o.getType(), pathHash, want);
 				countingMonitor.update(1);
 			}
 		} else {
@@ -1980,7 +1980,7 @@ private void findObjectsToPack(@NonNull ProgressMonitor countingMonitor,
 					continue;
 				if (exclude(o))
 					continue;
-				filterAndAddObject(o, o.getType(), walker.getPathHashCode());
+				filterAndAddObject(o, o.getType(), walker.getPathHashCode(), want);
 				countingMonitor.update(1);
 			}
 		}
@@ -2013,7 +2013,7 @@ private void findObjectsToPackUsingBitmaps(
 				needBitmap.remove(objectId);
 				continue;
 			}
-			filterAndAddObject(objectId, obj.getType(), 0);
+			filterAndAddObject(objectId, obj.getType(), 0, want);
 		}
 
 		if (thin)
@@ -2075,12 +2075,14 @@ private void addObject(
 	// 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,
-			int pathHashCode) throws IOException {
+			int pathHashCode, @NonNull Set<? extends AnyObjectId> want)
+			throws IOException {
 
 		// Check if this object needs to be rejected, doing the cheaper
 		// checks first.
 		boolean reject = filterBlobLimit >= 0 &&
 			type == OBJ_BLOB &&
+			!want.contains(src) &&
 			reader.getObjectSize(src, OBJ_BLOB) > filterBlobLimit;
 		if (!reject) {
 			addObject(src, type, pathHashCode);
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 38d3458..99db749 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,11 +91,10 @@ class PackWriterBitmapPreparer {
 
 	private static final int DAY_IN_SECONDS = 24 * 60 * 60;
 
-	private static final Comparator<BitmapBuilderEntry> ORDER_BY_CARDINALITY = new Comparator<BitmapBuilderEntry>() {
+	private static final Comparator<RevCommit> ORDER_BY_REVERSE_TIMESTAMP = new Comparator<RevCommit>() {
 		@Override
-		public int compare(BitmapBuilderEntry a, BitmapBuilderEntry b) {
-			return Integer.signum(a.getBuilder().cardinality()
-					- b.getBuilder().cardinality());
+		public int compare(RevCommit a, RevCommit b) {
+			return Integer.signum(b.getCommitTime() - a.getCommitTime());
 		}
 	};
 
@@ -164,154 +163,177 @@ Collection<BitmapCommit> selectCommits(int expectedCommitCount,
 		 * the cache hits for clients that are close to HEAD, which is the
 		 * majority of calculations performed.
 		 */
-		pm.beginTask(JGitText.get().selectingCommits, ProgressMonitor.UNKNOWN);
-		RevWalk rw = new RevWalk(reader);
-		rw.setRetainBody(false);
-		CommitSelectionHelper selectionHelper = setupTipCommitBitmaps(rw,
-				expectedCommitCount, excludeFromBitmapSelection);
-		pm.endTask();
+		try (RevWalk rw = new RevWalk(reader);
+				RevWalk rw2 = new RevWalk(reader)) {
+			pm.beginTask(JGitText.get().selectingCommits,
+					ProgressMonitor.UNKNOWN);
+			rw.setRetainBody(false);
+			CommitSelectionHelper selectionHelper = captureOldAndNewCommits(rw,
+					expectedCommitCount, excludeFromBitmapSelection);
+			pm.endTask();
 
-		int totCommits = selectionHelper.getCommitCount();
-		BlockList<BitmapCommit> selections = new BlockList<>(
-				totCommits / recentCommitSpan + 1);
-		for (BitmapCommit reuse : selectionHelper.reusedCommits) {
-			selections.add(reuse);
-		}
-
-		if (totCommits == 0) {
-			for (AnyObjectId id : selectionHelper.peeledWants) {
-				selections.add(new BitmapCommit(id, false, 0));
-			}
-			return selections;
-		}
-
-		pm.beginTask(JGitText.get().selectingCommits, totCommits);
-		int totalWants = selectionHelper.peeledWants.size();
-
-		for (BitmapBuilderEntry entry : selectionHelper.tipCommitBitmaps) {
-			BitmapBuilder bitmap = entry.getBuilder();
-			int cardinality = bitmap.cardinality();
-
-			// Within this branch, keep ordered lists of commits representing
-			// chains in its history, where each chain is a "sub-branch".
-			// Ordering commits by these chains makes for fewer differences
-			// between consecutive selected commits, which in turn provides
-			// better compression/on the run-length encoding of the XORs between
-			// them.
-			List<List<BitmapCommit>> chains =
-					new ArrayList<>();
-
-			// Mark the current branch as inactive if its tip commit isn't
-			// recent and there are an excessive number of branches, to
-			// prevent memory bloat of computing too many bitmaps for stale
-			// branches.
-			boolean isActiveBranch = true;
-			if (totalWants > excessiveBranchCount
-					&& !isRecentCommit(entry.getCommit())) {
-				isActiveBranch = false;
+			// Add reused bitmaps from the previous GC pack's bitmap indices.
+			// Currently they are always fully reused, even if their spans don't
+			// match this run's PackConfig values.
+			int newCommits = selectionHelper.getCommitCount();
+			BlockList<BitmapCommit> selections = new BlockList<>(
+					selectionHelper.reusedCommits.size()
+							+ newCommits / recentCommitSpan + 1);
+			for (BitmapCommit reuse : selectionHelper.reusedCommits) {
+				selections.add(reuse);
 			}
 
-			// Insert bitmaps at the offsets suggested by the
-			// nextSelectionDistance() heuristic. Only reuse bitmaps created
-			// for more distant commits.
-			int index = -1;
-			int nextIn = nextSpan(cardinality);
-			int nextFlg = nextIn == distantCommitSpan
-					? PackBitmapIndex.FLAG_REUSE : 0;
+			if (newCommits == 0) {
+				for (AnyObjectId id : selectionHelper.newWants) {
+					selections.add(new BitmapCommit(id, false, 0));
+				}
+				return selections;
+			}
 
-			// For the current branch, iterate through all commits from oldest
-			// to newest.
-			for (RevCommit c : selectionHelper) {
-				// Optimization: if we have found all the commits for this
-				// branch, stop searching
-				int distanceFromTip = cardinality - index - 1;
-				if (distanceFromTip == 0) {
-					break;
+			pm.beginTask(JGitText.get().selectingCommits, newCommits);
+			int totalWants = want.size();
+			BitmapBuilder seen = commitBitmapIndex.newBitmapBuilder();
+			seen.or(selectionHelper.reusedCommitsBitmap);
+			rw2.setRetainBody(false);
+			rw2.setRevFilter(new NotInBitmapFilter(seen));
+
+			// For each branch, do a revwalk to enumerate its commits. Exclude
+			// both reused commits and any commits seen in a previous branch.
+			// Then iterate through all new commits from oldest to newest,
+			// selecting well-spaced commits in this branch.
+			for (RevCommit rc : selectionHelper.newWantsByNewest) {
+				BitmapBuilder tipBitmap = commitBitmapIndex.newBitmapBuilder();
+				rw2.markStart((RevCommit) rw2.peel(rw2.parseAny(rc)));
+				RevCommit rc2;
+				while ((rc2 = rw2.next()) != null) {
+					tipBitmap.addObject(rc2, Constants.OBJ_COMMIT);
+				}
+				int cardinality = tipBitmap.cardinality();
+				seen.or(tipBitmap);
+
+				// Within this branch, keep ordered lists of commits
+				// representing chains in its history, where each chain is a
+				// "sub-branch". Ordering commits by these chains makes for
+				// fewer differences between consecutive selected commits, which
+				// in turn provides better compression/on the run-length
+				// encoding of the XORs between them.
+				List<List<BitmapCommit>> chains = new ArrayList<>();
+
+				// Mark the current branch as inactive if its tip commit isn't
+				// recent and there are an excessive number of branches, to
+				// prevent memory bloat of computing too many bitmaps for stale
+				// branches.
+				boolean isActiveBranch = true;
+				if (totalWants > excessiveBranchCount && !isRecentCommit(rc)) {
+					isActiveBranch = false;
 				}
 
-				// Ignore commits that are not in this branch
-				if (!bitmap.contains(c)) {
-					continue;
-				}
+				// Insert bitmaps at the offsets suggested by the
+				// nextSelectionDistance() heuristic. Only reuse bitmaps created
+				// for more distant commits.
+				int index = -1;
+				int nextIn = nextSpan(cardinality);
+				int nextFlg = nextIn == distantCommitSpan
+						? PackBitmapIndex.FLAG_REUSE
+						: 0;
 
-				index++;
-				nextIn--;
-				pm.update(1);
-
-				// Always pick the items in wants, prefer merge commits.
-				if (selectionHelper.peeledWants.remove(c)) {
-					if (nextIn > 0) {
-						nextFlg = 0;
+				// For the current branch, iterate through all commits from
+				// oldest to newest.
+				for (RevCommit c : selectionHelper) {
+					// Optimization: if we have found all the commits for this
+					// branch, stop searching
+					int distanceFromTip = cardinality - index - 1;
+					if (distanceFromTip == 0) {
+						break;
 					}
-				} else {
-					boolean stillInSpan = nextIn >= 0;
-					boolean isMergeCommit = c.getParentCount() > 1;
-					// Force selection if:
-					// a) we have exhausted the window looking for merges
-					// b) we are in the top commits of an active branch
-					// c) we are at a branch tip
-					boolean mustPick = (nextIn <= -recentCommitSpan)
-							|| (isActiveBranch
-									&& (distanceFromTip <= contiguousCommitCount))
-							|| (distanceFromTip == 1); // most recent commit
-					if (!mustPick && (stillInSpan || !isMergeCommit)) {
+
+					// Ignore commits that are not in this branch
+					if (!tipBitmap.contains(c)) {
 						continue;
 					}
-				}
 
-				// This commit is selected.
-				// Calculate where to look for the next one.
-				int flags = nextFlg;
-				nextIn = nextSpan(distanceFromTip);
-				nextFlg = nextIn == distantCommitSpan
-						? PackBitmapIndex.FLAG_REUSE : 0;
+					index++;
+					nextIn--;
+					pm.update(1);
 
-				BitmapBuilder fullBitmap = commitBitmapIndex.newBitmapBuilder();
-				rw.reset();
-				rw.markStart(c);
-				rw.setRevFilter(new AddUnseenToBitmapFilter(
-						selectionHelper.reusedCommitsBitmap, fullBitmap));
-
-				while (rw.next() != null) {
-					// The RevFilter adds the reachable commits from this
-					// selected commit to fullBitmap.
-				}
-
-				// Sort the commits by independent chains in this branch's
-				// history, yielding better compression when building bitmaps.
-				List<BitmapCommit> longestAncestorChain = null;
-				for (List<BitmapCommit> chain : chains) {
-					BitmapCommit mostRecentCommit = chain.get(chain.size() - 1);
-					if (fullBitmap.contains(mostRecentCommit)) {
-						if (longestAncestorChain == null
-								|| longestAncestorChain.size() < chain.size()) {
-							longestAncestorChain = chain;
+					// Always pick the items in wants, prefer merge commits.
+					if (selectionHelper.newWants.remove(c)) {
+						if (nextIn > 0) {
+							nextFlg = 0;
+						}
+					} else {
+						boolean stillInSpan = nextIn >= 0;
+						boolean isMergeCommit = c.getParentCount() > 1;
+						// Force selection if:
+						// a) we have exhausted the window looking for merges
+						// b) we are in the top commits of an active branch
+						// c) we are at a branch tip
+						boolean mustPick = (nextIn <= -recentCommitSpan)
+								|| (isActiveBranch
+										&& (distanceFromTip <= contiguousCommitCount))
+								|| (distanceFromTip == 1); // most recent commit
+						if (!mustPick && (stillInSpan || !isMergeCommit)) {
+							continue;
 						}
 					}
+
+					// This commit is selected.
+					// Calculate where to look for the next one.
+					int flags = nextFlg;
+					nextIn = nextSpan(distanceFromTip);
+					nextFlg = nextIn == distantCommitSpan
+							? PackBitmapIndex.FLAG_REUSE
+							: 0;
+
+					// Create the commit bitmap for the current commit
+					BitmapBuilder bitmap = commitBitmapIndex.newBitmapBuilder();
+					rw.reset();
+					rw.markStart(c);
+					rw.setRevFilter(new AddUnseenToBitmapFilter(
+							selectionHelper.reusedCommitsBitmap, bitmap));
+					while (rw.next() != null) {
+						// The filter adds the reachable commits to bitmap.
+					}
+
+					// Sort the commits by independent chains in this branch's
+					// history, yielding better compression when building
+					// bitmaps.
+					List<BitmapCommit> longestAncestorChain = null;
+					for (List<BitmapCommit> chain : chains) {
+						BitmapCommit mostRecentCommit = chain
+								.get(chain.size() - 1);
+						if (bitmap.contains(mostRecentCommit)) {
+							if (longestAncestorChain == null
+									|| longestAncestorChain.size() < chain
+											.size()) {
+								longestAncestorChain = chain;
+							}
+						}
+					}
+
+					if (longestAncestorChain == null) {
+						longestAncestorChain = new ArrayList<>();
+						chains.add(longestAncestorChain);
+					}
+					longestAncestorChain.add(new BitmapCommit(c,
+							!longestAncestorChain.isEmpty(), flags));
+					writeBitmaps.addBitmap(c, bitmap, 0);
 				}
 
-				if (longestAncestorChain == null) {
-					longestAncestorChain = new ArrayList<>();
-					chains.add(longestAncestorChain);
+				for (List<BitmapCommit> chain : chains) {
+					selections.addAll(chain);
 				}
-				longestAncestorChain.add(new BitmapCommit(
-						c, !longestAncestorChain.isEmpty(), flags));
-				writeBitmaps.addBitmap(c, fullBitmap, 0);
+			}
+			writeBitmaps.clearBitmaps(); // Remove the temporary commit bitmaps.
+
+			// Add the remaining peeledWant
+			for (AnyObjectId remainingWant : selectionHelper.newWants) {
+				selections.add(new BitmapCommit(remainingWant, false, 0));
 			}
 
-			for (List<BitmapCommit> chain : chains) {
-				selections.addAll(chain);
-			}
+			pm.endTask();
+			return selections;
 		}
-		writeBitmaps.clearBitmaps(); // Remove the temporary commit bitmaps.
-
-		// Add the remaining peeledWant
-		for (AnyObjectId remainingWant : selectionHelper.peeledWants) {
-			selections.add(new BitmapCommit(remainingWant, false, 0));
-		}
-
-		pm.endTask();
-		return selections;
 	}
 
 	private boolean isRecentCommit(RevCommit revCommit) {
@@ -358,9 +380,8 @@ public final boolean requiresCommitBody() {
 	}
 
 	/**
-	 * For each of the {@code want}s, which represent the tip commit of each
-	 * branch, set up an initial {@link BitmapBuilder}. Reuse previously built
-	 * bitmaps if possible.
+	 * Records which of the {@code wants} can be found in the previous GC pack's
+	 * bitmap indices and which are new.
 	 *
 	 * @param rw
 	 *            a {@link RevWalk} to find reachable objects in this repository
@@ -369,8 +390,9 @@ public final boolean requiresCommitBody() {
 	 *            unreachable garbage.
 	 * @param excludeFromBitmapSelection
 	 *            commits that should be excluded from bitmap selection
-	 * @return a {@link CommitSelectionHelper} containing bitmaps for the tip
-	 *         commits
+	 * @return a {@link CommitSelectionHelper} capturing which commits are
+	 *         covered by a previous pack's bitmaps and which new commits need
+	 *         bitmap coverage
 	 * @throws IncorrectObjectTypeException
 	 *             if any of the processed objects is not a commit
 	 * @throws IOException
@@ -378,11 +400,12 @@ public final boolean requiresCommitBody() {
 	 * @throws MissingObjectException
 	 *             if an expected object is missing
 	 */
-	private CommitSelectionHelper setupTipCommitBitmaps(RevWalk rw,
+	private CommitSelectionHelper captureOldAndNewCommits(RevWalk rw,
 			int expectedCommitCount,
 			Set<? extends ObjectId> excludeFromBitmapSelection)
 			throws IncorrectObjectTypeException, IOException,
 			MissingObjectException {
+		// Track bitmaps and commits from the previous GC pack bitmap indices.
 		BitmapBuilder reuse = commitBitmapIndex.newBitmapBuilder();
 		List<BitmapCommit> reuseCommits = new ArrayList<>();
 		for (PackBitmapIndexRemapper.Entry entry : bitmapRemapper) {
@@ -404,11 +427,10 @@ private CommitSelectionHelper setupTipCommitBitmaps(RevWalk rw,
 			}
 		}
 
-		// Add branch tips that are not represented in old bitmap indices. Set
-		// up the RevWalk to walk the new commits not in the old packs.
-		List<BitmapBuilderEntry> tipCommitBitmaps = new ArrayList<>(
-				want.size());
-		Set<RevCommit> peeledWant = new HashSet<>(want.size());
+		// Add branch tips that are not represented in a previous pack's bitmap
+		// indices. Set up a RevWalk to find new commits not in the old packs.
+		List<RevCommit> newWantsByNewest = new ArrayList<>(want.size());
+		Set<RevCommit> newWants = new HashSet<>(want.size());
 		for (AnyObjectId objectId : want) {
 			RevObject ro = rw.peel(rw.parseAny(objectId));
 			if (!(ro instanceof RevCommit) || reuse.contains(ro)
@@ -417,59 +439,26 @@ private CommitSelectionHelper setupTipCommitBitmaps(RevWalk rw,
 			}
 
 			RevCommit rc = (RevCommit) ro;
-			peeledWant.add(rc);
 			rw.markStart(rc);
-
-			BitmapBuilder bitmap = commitBitmapIndex.newBitmapBuilder();
-			bitmap.addObject(rc, Constants.OBJ_COMMIT);
-			tipCommitBitmaps.add(new BitmapBuilderEntry(rc, bitmap));
+			newWants.add(rc);
+			newWantsByNewest.add(rc);
 		}
 
-		// Create a list of commits in reverse order (older to newer).
-		// For each branch that contains the commit, mark its parents as being
-		// in the bitmap.
+		// Create a list of commits in reverse order (older to newer) that are
+		// not in the previous bitmap indices and are reachable.
 		rw.setRevFilter(new NotInBitmapFilter(reuse));
 		RevCommit[] commits = new RevCommit[expectedCommitCount];
 		int pos = commits.length;
 		RevCommit rc;
 		while ((rc = rw.next()) != null && pos > 0) {
 			commits[--pos] = rc;
-			for (BitmapBuilderEntry entry : tipCommitBitmaps) {
-				BitmapBuilder bitmap = entry.getBuilder();
-				if (!bitmap.contains(rc)) {
-					continue;
-				}
-				for (RevCommit c : rc.getParents()) {
-					if (reuse.contains(c)) {
-						continue;
-					}
-					bitmap.addObject(c, Constants.OBJ_COMMIT);
-				}
-			}
 			pm.update(1);
 		}
 
-		// Sort the tip commit bitmaps. Find the one containing the most
-		// commits, remove those commits from the remaining bitmaps, resort and
-		// repeat.
-		List<BitmapBuilderEntry> orderedTipCommitBitmaps = new ArrayList<>(
-				tipCommitBitmaps.size());
-		while (!tipCommitBitmaps.isEmpty()) {
-			BitmapBuilderEntry largest =
-					Collections.max(tipCommitBitmaps, ORDER_BY_CARDINALITY);
-			tipCommitBitmaps.remove(largest);
-			orderedTipCommitBitmaps.add(largest);
-
-			// Update the remaining paths, by removing the objects from
-			// the path that was just added.
-			for (int i = tipCommitBitmaps.size() - 1; i >= 0; i--) {
-				tipCommitBitmaps.get(i).getBuilder()
-						.andNot(largest.getBuilder());
-			}
-		}
-
-		return new CommitSelectionHelper(peeledWant, commits, pos,
-				orderedTipCommitBitmaps, reuse, reuseCommits);
+		// Sort the new wants by reverse commit time.
+		Collections.sort(newWantsByNewest, ORDER_BY_REVERSE_TIMESTAMP);
+		return new CommitSelectionHelper(newWants, commits, pos,
+				newWantsByNewest, reuse, reuseCommits);
 	}
 
 	/*-
@@ -537,54 +526,36 @@ int getFlags() {
 	}
 
 	/**
-	 * A POJO representing a Pair<RevCommit, BitmapBuidler>.
-	 */
-	private static final class BitmapBuilderEntry {
-		private final RevCommit commit;
-		private final BitmapBuilder builder;
-
-		BitmapBuilderEntry(RevCommit commit, BitmapBuilder builder) {
-			this.commit = commit;
-			this.builder = builder;
-		}
-
-		RevCommit getCommit() {
-			return commit;
-		}
-
-		BitmapBuilder getBuilder() {
-			return builder;
-		}
-	}
-
-	/**
 	 * Container for state used in the first phase of selecting commits, which
-	 * walks all of the reachable commits via the branch tips (
-	 * {@code peeledWants}), stores them in {@code commitsByOldest}, and sets up
-	 * bitmaps for each branch tip ({@code tipCommitBitmaps}).
-	 * {@code commitsByOldest} is initialized with an expected size of all
-	 * commits, but may be smaller if some commits are unreachable, in which
-	 * case {@code commitStartPos} will contain a positive offset to the root
-	 * commit.
+	 * walks all of the reachable commits via the branch tips that are not
+	 * covered by a previous pack's bitmaps ({@code newWants}) and stores them
+	 * in {@code newCommitsByOldest}. {@code newCommitsByOldest} is initialized
+	 * with an expected size of all commits, but may be smaller if some commits
+	 * are unreachable and/or some commits are covered by a previous pack's
+	 * bitmaps. {@code commitStartPos} will contain a positive offset to either
+	 * the root commit or the oldest commit not covered by previous bitmaps.
 	 */
 	private static final class CommitSelectionHelper implements Iterable<RevCommit> {
-		final Set<? extends ObjectId> peeledWants;
-		final List<BitmapBuilderEntry> tipCommitBitmaps;
+		final Set<? extends ObjectId> newWants;
 
+		final List<RevCommit> newWantsByNewest;
 		final BitmapBuilder reusedCommitsBitmap;
-		final Iterable<BitmapCommit> reusedCommits;
-		final RevCommit[] commitsByOldest;
-		final int commitStartPos;
 
-		CommitSelectionHelper(Set<? extends ObjectId> peeledWant,
+		final List<BitmapCommit> reusedCommits;
+
+		final RevCommit[] newCommitsByOldest;
+
+		final int newCommitStartPos;
+
+		CommitSelectionHelper(Set<? extends ObjectId> newWants,
 				RevCommit[] commitsByOldest, int commitStartPos,
-				List<BitmapBuilderEntry> bitmapEntries,
+				List<RevCommit> newWantsByNewest,
 				BitmapBuilder reusedCommitsBitmap,
-				Iterable<BitmapCommit> reuse) {
-			this.peeledWants = peeledWant;
-			this.commitsByOldest = commitsByOldest;
-			this.commitStartPos = commitStartPos;
-			this.tipCommitBitmaps = bitmapEntries;
+				List<BitmapCommit> reuse) {
+			this.newWants = newWants;
+			this.newCommitsByOldest = commitsByOldest;
+			this.newCommitStartPos = commitStartPos;
+			this.newWantsByNewest = newWantsByNewest;
 			this.reusedCommitsBitmap = reusedCommitsBitmap;
 			this.reusedCommits = reuse;
 		}
@@ -594,16 +565,16 @@ public Iterator<RevCommit> iterator() {
 			// Member variables referenced by this iterator will have synthetic
 			// accessors generated for them if they are made private.
 			return new Iterator<RevCommit>() {
-				int pos = commitStartPos;
+				int pos = newCommitStartPos;
 
 				@Override
 				public boolean hasNext() {
-					return pos < commitsByOldest.length;
+					return pos < newCommitsByOldest.length;
 				}
 
 				@Override
 				public RevCommit next() {
-					return commitsByOldest[pos++];
+					return newCommitsByOldest[pos++];
 				}
 
 				@Override
@@ -614,7 +585,7 @@ public void remove() {
 		}
 
 		int getCommitCount() {
-			return commitsByOldest.length - commitStartPos;
+			return newCommitsByOldest.length - newCommitStartPos;
 		}
 	}
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/BlockReader.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/BlockReader.java
index 942d72f..ce2ba4a 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/BlockReader.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/BlockReader.java
@@ -43,7 +43,7 @@
 
 package org.eclipse.jgit.internal.storage.reftable;
 
-import static org.eclipse.jgit.lib.Constants.CHARSET;
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.eclipse.jgit.internal.storage.reftable.BlockWriter.compare;
 import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.FILE_BLOCK_TYPE;
 import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.FILE_HEADER_LEN;
@@ -138,7 +138,7 @@ String name() {
 		if (blockType == LOG_BLOCK_TYPE) {
 			len -= 9;
 		}
-		return RawParseUtils.decode(CHARSET, nameBuf, 0, len);
+		return RawParseUtils.decode(UTF_8, nameBuf, 0, len);
 	}
 
 	boolean match(byte[] match, boolean matchIsPrefix) {
@@ -171,7 +171,7 @@ long readUpdateIndexDelta() {
 	}
 
 	Ref readRef() throws IOException {
-		String name = RawParseUtils.decode(CHARSET, nameBuf, 0, nameLen);
+		String name = RawParseUtils.decode(UTF_8, nameBuf, 0, nameLen);
 		switch (valueType & VALUE_TYPE_MASK) {
 		case VALUE_NONE: // delete
 			return newRef(name);
@@ -266,7 +266,7 @@ private ObjectId readValueId() {
 	private String readValueString() {
 		int len = readVarint32();
 		int end = ptr + len;
-		String s = RawParseUtils.decode(CHARSET, buf, ptr, end);
+		String s = RawParseUtils.decode(UTF_8, buf, ptr, end);
 		ptr = end;
 		return s;
 	}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/BlockWriter.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/BlockWriter.java
index 3d8fbf4..b3173e8 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/BlockWriter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/BlockWriter.java
@@ -43,7 +43,7 @@
 
 package org.eclipse.jgit.internal.storage.reftable;
 
-import static org.eclipse.jgit.lib.Constants.CHARSET;
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.FILE_HEADER_LEN;
 import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.INDEX_BLOCK_TYPE;
 import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.LOG_BLOCK_TYPE;
@@ -440,7 +440,7 @@ void writeValue(ReftableOutputStream os) throws IOException {
 		}
 
 		private static byte[] nameUtf8(Ref ref) {
-			return ref.getName().getBytes(CHARSET);
+			return ref.getName().getBytes(UTF_8);
 		}
 	}
 
@@ -559,13 +559,13 @@ static class LogEntry extends Entry {
 			this.newId = newId;
 			this.timeSecs = who.getWhen().getTime() / 1000L;
 			this.tz = (short) who.getTimeZoneOffset();
-			this.name = who.getName().getBytes(CHARSET);
-			this.email = who.getEmailAddress().getBytes(CHARSET);
-			this.msg = message.getBytes(CHARSET);
+			this.name = who.getName().getBytes(UTF_8);
+			this.email = who.getEmailAddress().getBytes(UTF_8);
+			this.msg = message.getBytes(UTF_8);
 		}
 
 		static byte[] key(String ref, long index) {
-			byte[] name = ref.getBytes(CHARSET);
+			byte[] name = ref.getBytes(UTF_8);
 			byte[] key = Arrays.copyOf(name, name.length + 1 + 8);
 			NB.encodeInt64(key, key.length - 8, reverseUpdateIndex(index));
 			return key;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/MergedReftable.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/MergedReftable.java
index ef686a3..17894b1 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/MergedReftable.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/MergedReftable.java
@@ -113,6 +113,16 @@ public RefCursor seekRef(String name) throws IOException {
 
 	/** {@inheritDoc} */
 	@Override
+	public RefCursor seekRefsWithPrefix(String prefix) throws IOException {
+		MergedRefCursor m = new MergedRefCursor();
+		for (int i = 0; i < tables.length; i++) {
+			m.add(new RefQueueEntry(tables[i].seekRefsWithPrefix(prefix), i));
+		}
+		return m;
+	}
+
+	/** {@inheritDoc} */
+	@Override
 	public RefCursor byObjectId(AnyObjectId name) throws IOException {
 		MergedRefCursor m = new MergedRefCursor();
 		for (int i = 0; i < tables.length; i++) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/Reftable.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/Reftable.java
index 510c1a1..a1087e2 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/Reftable.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/Reftable.java
@@ -108,18 +108,14 @@ public void setIncludeDeletes(boolean deletes) {
 	public abstract RefCursor allRefs() throws IOException;
 
 	/**
-	 * Seek either to a reference, or a reference subtree.
+	 * Seek to a reference.
 	 * <p>
-	 * If {@code refName} ends with {@code "/"} the method will seek to the
-	 * subtree of all references starting with {@code refName} as a prefix. If
-	 * no references start with this prefix, an empty cursor is returned.
-	 * <p>
-	 * Otherwise exactly {@code refName} will be looked for. If present, the
+	 * This method will seek to the reference {@code refName}. If present, the
 	 * returned cursor will iterate exactly one entry. If not found, an empty
 	 * cursor is returned.
 	 *
 	 * @param refName
-	 *            reference name or subtree to find.
+	 *            reference name.
 	 * @return cursor to iterate; empty cursor if no references match.
 	 * @throws java.io.IOException
 	 *             if references cannot be read.
@@ -127,6 +123,21 @@ public void setIncludeDeletes(boolean deletes) {
 	public abstract RefCursor seekRef(String refName) throws IOException;
 
 	/**
+	 * Seek references with prefix.
+	 * <p>
+	 * The method will seek all the references starting with {@code prefix} as a
+	 * prefix. If no references start with this prefix, an empty cursor is
+	 * returned.
+	 *
+	 * @param prefix
+	 *            prefix to find.
+	 * @return cursor to iterate; empty cursor if no references match.
+	 * @throws java.io.IOException
+	 *             if references cannot be read.
+	 */
+	public abstract RefCursor seekRefsWithPrefix(String prefix) throws IOException;
+
+	/**
 	 * Match references pointing to a specific object.
 	 *
 	 * @param id
@@ -191,17 +202,11 @@ public Ref exactRef(String refName) throws IOException {
 	}
 
 	/**
-	 * Test if a reference or reference subtree exists.
-	 * <p>
-	 * If {@code refName} ends with {@code "/"}, the method tests if any
-	 * reference starts with {@code refName} as a prefix.
-	 * <p>
-	 * Otherwise, the method checks if {@code refName} exists.
+	 * Test if a reference exists.
 	 *
 	 * @param refName
 	 *            reference name or subtree to find.
-	 * @return {@code true} if the reference exists, or at least one reference
-	 *         exists in the subtree.
+	 * @return {@code true} if the reference exists.
 	 * @throws java.io.IOException
 	 *             if references cannot be read.
 	 */
@@ -212,6 +217,21 @@ public boolean hasRef(String refName) throws IOException {
 	}
 
 	/**
+	 * Test if any reference starts with {@code prefix} as a prefix.
+	 *
+	 * @param prefix
+	 *            prefix to find.
+	 * @return {@code true} if at least one reference exists with prefix.
+	 * @throws java.io.IOException
+	 *             if references cannot be read.
+	 */
+	public boolean hasRefsWithPrefix(String prefix) throws IOException {
+		try (RefCursor rc = seekRefsWithPrefix(prefix)) {
+			return rc.next();
+		}
+	}
+
+	/**
 	 * Test if any reference directly refers to the object.
 	 *
 	 * @param id
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableOutputStream.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableOutputStream.java
index 44bbb16..1fc43c9 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableOutputStream.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableOutputStream.java
@@ -43,7 +43,7 @@
 
 package org.eclipse.jgit.internal.storage.reftable;
 
-import static org.eclipse.jgit.lib.Constants.CHARSET;
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.FILE_HEADER_LEN;
 import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.INDEX_BLOCK_TYPE;
 import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.LOG_BLOCK_TYPE;
@@ -160,7 +160,7 @@ void writeId(ObjectId id) {
 	}
 
 	void writeVarintString(String s) {
-		writeVarintString(s.getBytes(CHARSET));
+		writeVarintString(s.getBytes(UTF_8));
 	}
 
 	void writeVarintString(byte[] msg) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableReader.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableReader.java
index 5356952..81b30e4 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableReader.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableReader.java
@@ -43,7 +43,7 @@
 
 package org.eclipse.jgit.internal.storage.reftable;
 
-import static org.eclipse.jgit.lib.Constants.CHARSET;
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.eclipse.jgit.internal.storage.reftable.BlockReader.decodeBlockLen;
 import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.FILE_BLOCK_TYPE;
 import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.FILE_FOOTER_LEN;
@@ -182,10 +182,19 @@ public RefCursor allRefs() throws IOException {
 	public RefCursor seekRef(String refName) throws IOException {
 		initRefIndex();
 
-		byte[] key = refName.getBytes(CHARSET);
-		boolean prefix = key[key.length - 1] == '/';
+		byte[] key = refName.getBytes(UTF_8);
+		RefCursorImpl i = new RefCursorImpl(refEnd, key, false);
+		i.block = seek(REF_BLOCK_TYPE, key, refIndex, 0, refEnd);
+		return i;
+	}
 
-		RefCursorImpl i = new RefCursorImpl(refEnd, key, prefix);
+	/** {@inheritDoc} */
+	@Override
+	public RefCursor seekRefsWithPrefix(String prefix) throws IOException {
+		initRefIndex();
+
+		byte[] key = prefix.getBytes(UTF_8);
+		RefCursorImpl i = new RefCursorImpl(refEnd, key, true);
 		i.block = seek(REF_BLOCK_TYPE, key, refIndex, 0, refEnd);
 		return i;
 	}
@@ -223,7 +232,7 @@ public LogCursor seekLog(String refName, long updateIndex)
 		initLogIndex();
 		if (logPosition > 0) {
 			byte[] key = LogEntry.key(refName, updateIndex);
-			byte[] match = refName.getBytes(CHARSET);
+			byte[] match = refName.getBytes(UTF_8);
 			LogCursorImpl i = new LogCursorImpl(logEnd, match);
 			i.block = seek(LOG_BLOCK_TYPE, key, logIndex, logPosition, logEnd);
 			return i;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BaseRepositoryBuilder.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BaseRepositoryBuilder.java
index 2c7c6cb..d1cf1cd 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BaseRepositoryBuilder.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BaseRepositoryBuilder.java
@@ -283,7 +283,7 @@ public B addAlternateObjectDirectories(File[] inList) {
 		final List<File> alts = alternateObjectDirectories;
 		if (alts == null)
 			return null;
-		return alts.toArray(new File[alts.size()]);
+		return alts.toArray(new File[0]);
 	}
 
 	/**
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BlobBasedConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BlobBasedConfig.java
index e008be3..59154b7 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BlobBasedConfig.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BlobBasedConfig.java
@@ -45,7 +45,7 @@
 
 package org.eclipse.jgit.lib;
 
-import static org.eclipse.jgit.lib.Constants.CHARSET;
+import static java.nio.charset.StandardCharsets.UTF_8;
 
 import java.io.FileNotFoundException;
 import java.io.IOException;
@@ -83,7 +83,7 @@ public BlobBasedConfig(Config base, byte[] blob)
 		super(base);
 		final String decoded;
 		if (isUtf8(blob)) {
-			decoded = RawParseUtils.decode(CHARSET, blob, 3, blob.length);
+			decoded = RawParseUtils.decode(UTF_8, blob, 3, blob.length);
 		} else {
 			decoded = RawParseUtils.decode(blob);
 		}
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 59a13f6..c30833d 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/CommitBuilder.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/CommitBuilder.java
@@ -45,6 +45,8 @@
 
 package org.eclipse.jgit.lib;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
+
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.OutputStreamWriter;
@@ -93,7 +95,7 @@ public class CommitBuilder {
 	 */
 	public CommitBuilder() {
 		parentIds = EMPTY_OBJECTID_LIST;
-		encoding = Constants.CHARSET;
+		encoding = UTF_8;
 	}
 
 	/**
@@ -314,7 +316,7 @@ public Charset getEncoding() {
 			w.flush();
 			os.write('\n');
 
-			if (getEncoding() != Constants.CHARSET) {
+			if (getEncoding() != UTF_8) {
 				os.write(hencoding);
 				os.write(' ');
 				os.write(Constants.encodeASCII(getEncoding().name()));
@@ -375,7 +377,7 @@ public String toString() {
 		r.append(committer != null ? committer.toString() : "NOT_SET");
 		r.append("\n");
 
-		if (encoding != null && encoding != Constants.CHARSET) {
+		if (encoding != null && 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 0e01cca..b666f21 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Config.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Config.java
@@ -51,7 +51,7 @@
 
 package org.eclipse.jgit.lib;
 
-import static org.eclipse.jgit.lib.Constants.CHARSET;
+import static java.nio.charset.StandardCharsets.UTF_8;
 
 import java.text.MessageFormat;
 import java.util.ArrayList;
@@ -1168,7 +1168,7 @@ private void addIncludedConfig(final List<ConfigLine> newEntries,
 
 		String decoded;
 		if (isUtf8(bytes)) {
-			decoded = RawParseUtils.decode(CHARSET, bytes, 3, bytes.length);
+			decoded = RawParseUtils.decode(UTF_8, bytes, 3, bytes.length);
 		} else {
 			decoded = RawParseUtils.decode(bytes);
 		}
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 5a79035..82ccd7b 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java
@@ -49,7 +49,7 @@
  * configuration keys
  */
 @SuppressWarnings("nls")
-public class ConfigConstants {
+public final class ConfigConstants {
 	/** The "core" section */
 	public static final String CONFIG_CORE_SECTION = "core";
 
@@ -432,4 +432,23 @@ public class ConfigConstants {
 	 * @since 4.11
 	 */
 	public static final String CONFIG_SECTION_LFS = "lfs";
+
+	/**
+	 * The "filesystem" section
+	 * @since 5.1.9
+	 */
+	public static final String CONFIG_FILESYSTEM_SECTION = "filesystem";
+
+	/**
+	 * The "timestampResolution" key
+	 * @since 5.1.9
+	 */
+	public static final String CONFIG_KEY_TIMESTAMP_RESOLUTION = "timestampResolution";
+
+	/**
+	 * The "minRacyThreshold" key
+	 *
+	 * @since 5.1.9
+	 */
+	public static final String CONFIG_KEY_MIN_RACY_THRESHOLD = "minRacyThreshold";
 }
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 9023bd8..4c55196 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java
@@ -227,10 +227,22 @@ public final class Constants {
 	 */
 	public static final byte[] PACK_SIGNATURE = { 'P', 'A', 'C', 'K' };
 
-	/** Native character encoding for commit messages, file names... */
+	/**
+	 * Native character encoding for commit messages, file names...
+	 *
+	 * @deprecated Use {@link java.nio.charset.StandardCharsets#UTF_8} directly
+	 *             instead.
+	 */
+	@Deprecated
 	public static final Charset CHARSET;
 
-	/** Native character encoding for commit messages, file names... */
+	/**
+	 * Native character encoding for commit messages, file names...
+	 *
+	 * @deprecated Use {@link java.nio.charset.StandardCharsets#UTF_8} directly
+	 *             instead.
+	 */
+	@Deprecated
 	public static final String CHARACTER_ENCODING;
 
 	/** Default main branch name */
@@ -638,7 +650,7 @@ public static int decodeTypeString(final AnyObjectId id,
 	 * @see #CHARACTER_ENCODING
 	 */
 	public static byte[] encode(String str) {
-		final ByteBuffer bb = Constants.CHARSET.encode(str);
+		final ByteBuffer bb = UTF_8.encode(str);
 		final int len = bb.limit();
 		if (bb.hasArray() && bb.arrayOffset() == 0) {
 			final byte[] arr = bb.array();
@@ -655,7 +667,7 @@ public static int decodeTypeString(final AnyObjectId id,
 		if (OBJECT_ID_LENGTH != newMessageDigest().getDigestLength())
 			throw new LinkageError(JGitText.get().incorrectOBJECT_ID_LENGTH);
 		CHARSET = UTF_8;
-		CHARACTER_ENCODING = CHARSET.name();
+		CHARACTER_ENCODING = UTF_8.name();
 	}
 
 	/** name of the file containing the commit msg for a merge commit */
@@ -687,11 +699,23 @@ public static int decodeTypeString(final AnyObjectId id,
 	 */
 	public static final String COMMIT_EDITMSG = "COMMIT_EDITMSG";
 
-	/** objectid for the empty blob */
+	/**
+	 * Well-known object ID for the empty blob.
+	 *
+	 * @since 0.9.1
+	 */
 	public static final ObjectId EMPTY_BLOB_ID = ObjectId
 			.fromString("e69de29bb2d1d6434b8b29ae775ad8c2e48c5391");
 
 	/**
+	 * Well-known object ID for the empty tree.
+	 *
+	 * @since 5.1
+	 */
+	public static final ObjectId EMPTY_TREE_ID = ObjectId
+			.fromString("4b825dc642cb6eb9a060e54bf8d69288fbee4904");
+
+	/**
 	 * Suffix of lock file name
 	 *
 	 * @since 4.7
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/DefaultTypedConfigGetter.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/DefaultTypedConfigGetter.java
index 891c7f2..001ae93 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/DefaultTypedConfigGetter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/DefaultTypedConfigGetter.java
@@ -226,6 +226,14 @@ public long getTimeUnit(Config config, String section, String subsection,
 			inputUnit = wantUnit;
 			inputMul = 1;
 
+		} else if (match(unitName, "ns", "nanoseconds")) { //$NON-NLS-1$ //$NON-NLS-2$
+			inputUnit = TimeUnit.NANOSECONDS;
+			inputMul = 1;
+
+		} else if (match(unitName, "us", "microseconds")) { //$NON-NLS-1$ //$NON-NLS-2$
+			inputUnit = TimeUnit.MICROSECONDS;
+			inputMul = 1;
+
 		} else if (match(unitName, "ms", "milliseconds")) { //$NON-NLS-1$ //$NON-NLS-2$
 			inputUnit = TimeUnit.MILLISECONDS;
 			inputMul = 1;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdOwnerMap.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdOwnerMap.java
index 9df5933..74c712c 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdOwnerMap.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdOwnerMap.java
@@ -135,6 +135,9 @@ public void clear() {
 	 */
 	@SuppressWarnings("unchecked")
 	public V get(AnyObjectId toFind) {
+		if (toFind == null) {
+			return null;
+		}
 		int h = toFind.w1;
 		V obj = directory[h & mask][h >>> SEGMENT_SHIFT];
 		for (; obj != null; obj = (V) obj.next)
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 3871605..0d31851 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RebaseTodoFile.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RebaseTodoFile.java
@@ -43,7 +43,7 @@
 
 package org.eclipse.jgit.lib;
 
-import static org.eclipse.jgit.lib.Constants.CHARSET;
+import static java.nio.charset.StandardCharsets.UTF_8;
 
 import java.io.BufferedOutputStream;
 import java.io.File;
@@ -137,11 +137,13 @@ private static void parseComments(byte[] buf, int tokenBegin,
 			if (skip != -1) {
 				// try to parse the line as non-comment
 				line = parseLine(buf, skip, lineEnd);
-				// successfully parsed as non-comment line
-				// mark this line as a comment explicitly
-				line.setAction(Action.COMMENT);
-				// use the read line as comment string
-				line.setComment(commentString);
+				if (line != null) {
+					// successfully parsed as non-comment line
+					// mark this line as a comment explicitly
+					line.setAction(Action.COMMENT);
+					// use the read line as comment string
+					line.setComment(commentString);
+				}
 			}
 		} catch (Exception e) {
 			// parsing as non-comment line failed
@@ -183,7 +185,7 @@ private static RebaseTodoLine parseLine(byte[] buf, int tokenBegin,
 			switch (tokenCount) {
 			case 0:
 				String actionToken = new String(buf, tokenBegin,
-						nextSpace - tokenBegin - 1, CHARSET);
+						nextSpace - tokenBegin - 1, UTF_8);
 				tokenBegin = nextSpace;
 				action = RebaseTodoLine.Action.parse(actionToken);
 				if (action == null)
@@ -192,7 +194,7 @@ private static RebaseTodoLine parseLine(byte[] buf, int tokenBegin,
 			case 1:
 				nextSpace = RawParseUtils.next(buf, tokenBegin, ' ');
 				String commitToken = new String(buf, tokenBegin,
-						nextSpace - tokenBegin - 1, CHARSET);
+						nextSpace - tokenBegin - 1, UTF_8);
 				tokenBegin = nextSpace;
 				commit = AbbreviatedObjectId.fromString(commitToken);
 				break;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefUpdate.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefUpdate.java
index 5cd593e..fc3ea84 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefUpdate.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefUpdate.java
@@ -823,7 +823,7 @@ private static RevObject safeParseOld(RevWalk rw, AnyObjectId oldId)
 	 * Handle the abstraction of storing a ref update. This is because both
 	 * updating and deleting of a ref have merge testing in common.
 	 */
-	private abstract class Store {
+	private static abstract class Store {
 		abstract Result execute(Result status) throws IOException;
 	}
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java
index 29cc19c..2a2699f 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java
@@ -49,6 +49,7 @@
 package org.eclipse.jgit.lib;
 
 import static org.eclipse.jgit.lib.Constants.LOCK_SUFFIX;
+import static java.nio.charset.StandardCharsets.UTF_8;
 
 import java.io.BufferedOutputStream;
 import java.io.File;
@@ -1094,7 +1095,9 @@ public final Ref findRef(String name) throws IOException {
 	 * not point to any object yet.
 	 *
 	 * @return mutable map of all known refs (heads, tags, remotes).
+	 * @deprecated use {@code getRefDatabase().getRefs()} instead.
 	 */
+	@Deprecated
 	@NonNull
 	public Map<String, Ref> getAllRefs() {
 		try {
@@ -1963,7 +1966,7 @@ private String readCommitMsgFile(String msgFilename) throws IOException {
 	private void writeCommitMsg(File msgFile, String msg) throws IOException {
 		if (msg != null) {
 			try (FileOutputStream fos = new FileOutputStream(msgFile)) {
-				fos.write(msg.getBytes(Constants.CHARACTER_ENCODING));
+				fos.write(msg.getBytes(UTF_8));
 			}
 		} else {
 			FileUtils.delete(msgFile, FileUtils.SKIP_MISSING);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/TagBuilder.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/TagBuilder.java
index bd03165..7669e95 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/TagBuilder.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/TagBuilder.java
@@ -45,6 +45,8 @@
 
 package org.eclipse.jgit.lib;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
+
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.OutputStreamWriter;
@@ -181,7 +183,7 @@ public void setMessage(String newMessage) {
 	public byte[] build() {
 		ByteArrayOutputStream os = new ByteArrayOutputStream();
 		try (OutputStreamWriter w = new OutputStreamWriter(os,
-				Constants.CHARSET)) {
+				UTF_8)) {
 			w.write("object "); //$NON-NLS-1$
 			getObjectId().copyTo(w);
 			w.write('\n');
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/TextProgressMonitor.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/TextProgressMonitor.java
index 2f759e5..936ce3d 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/TextProgressMonitor.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/TextProgressMonitor.java
@@ -44,7 +44,7 @@
 
 package org.eclipse.jgit.lib;
 
-import static org.eclipse.jgit.lib.Constants.CHARSET;
+import static java.nio.charset.StandardCharsets.UTF_8;
 
 import java.io.IOException;
 import java.io.OutputStreamWriter;
@@ -63,7 +63,7 @@ public class TextProgressMonitor extends BatchingProgressMonitor {
 	 * Initialize a new progress monitor.
 	 */
 	public TextProgressMonitor() {
-		this(new PrintWriter(new OutputStreamWriter(System.err, CHARSET)));
+		this(new PrintWriter(new OutputStreamWriter(System.err, UTF_8)));
 	}
 
 	/**
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 da6a3da..e37f207 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java
@@ -46,10 +46,11 @@
  */
 package org.eclipse.jgit.merge;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static java.time.Instant.EPOCH;
 import static org.eclipse.jgit.diff.DiffAlgorithm.SupportedAlgorithm.HISTOGRAM;
 import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_DIFF_SECTION;
 import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_ALGORITHM;
-import static org.eclipse.jgit.lib.Constants.CHARACTER_ENCODING;
 import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
 
 import java.io.BufferedOutputStream;
@@ -59,6 +60,7 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.time.Instant;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
@@ -78,6 +80,7 @@
 import org.eclipse.jgit.dircache.DirCacheBuildIterator;
 import org.eclipse.jgit.dircache.DirCacheBuilder;
 import org.eclipse.jgit.dircache.DirCacheCheckout;
+import org.eclipse.jgit.dircache.DirCacheCheckout.CheckoutMetadata;
 import org.eclipse.jgit.dircache.DirCacheEntry;
 import org.eclipse.jgit.errors.BinaryBlobException;
 import org.eclipse.jgit.errors.CorruptObjectException;
@@ -297,6 +300,12 @@ public enum MergeFailureReason {
 	 */
 	private int inCoreLimit;
 
+	/**
+	 * Keeps {@link CheckoutMetadata} for {@link #checkout()} and
+	 * {@link #cleanUp()}.
+	 */
+	private Map<String, CheckoutMetadata> checkoutMetadata;
+
 	private static MergeAlgorithm getMergeAlgorithm(Config config) {
 		SupportedAlgorithm diffAlg = config.getEnum(
 				CONFIG_DIFF_SECTION, null, CONFIG_KEY_ALGORITHM,
@@ -313,6 +322,8 @@ private static int getInCoreLimit(Config config) {
 		return new String[] { "BASE", "OURS", "THEIRS" }; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
 	}
 
+	private static final Attributes NO_ATTRIBUTES = new Attributes();
+
 	/**
 	 * Constructor for ResolveMerger.
 	 *
@@ -369,15 +380,20 @@ protected ResolveMerger(ObjectInserter inserter, Config config) {
 	/** {@inheritDoc} */
 	@Override
 	protected boolean mergeImpl() throws IOException {
-		if (implicitDirCache)
+		if (implicitDirCache) {
 			dircache = nonNullRepo().lockDirCache();
-
+		}
+		if (!inCore) {
+			checkoutMetadata = new HashMap<>();
+		}
 		try {
 			return mergeTrees(mergeBase(), sourceTrees[0], sourceTrees[1],
 					false);
 		} finally {
-			if (implicitDirCache)
+			checkoutMetadata = null;
+			if (implicitDirCache) {
 				dircache.unlock();
+			}
 		}
 	}
 
@@ -400,7 +416,8 @@ private void checkout() throws NoWorkTreeException, IOException {
 			if (cacheEntry.getFileMode() == FileMode.GITLINK) {
 				new File(nonNullRepo().getWorkTree(), entry.getKey()).mkdirs();
 			} else {
-				DirCacheCheckout.checkoutEntry(db, cacheEntry, reader);
+				DirCacheCheckout.checkoutEntry(db, cacheEntry, reader, false,
+						checkoutMetadata.get(entry.getKey()));
 				modifiedFiles.add(entry.getKey());
 			}
 		}
@@ -428,10 +445,12 @@ protected void cleanUp() throws NoWorkTreeException,
 		DirCache dc = nonNullRepo().readDirCache();
 		Iterator<String> mpathsIt=modifiedFiles.iterator();
 		while(mpathsIt.hasNext()) {
-			String mpath=mpathsIt.next();
+			String mpath = mpathsIt.next();
 			DirCacheEntry entry = dc.getEntry(mpath);
-			if (entry != null)
-				DirCacheCheckout.checkoutEntry(db, entry, reader);
+			if (entry != null) {
+				DirCacheCheckout.checkoutEntry(db, entry, reader, false,
+						checkoutMetadata.get(mpath));
+			}
 			mpathsIt.remove();
 		}
 	}
@@ -447,7 +466,7 @@ protected void cleanUp() throws NoWorkTreeException,
 	 * @return the entry which was added to the index
 	 */
 	private DirCacheEntry add(byte[] path, CanonicalTreeParser p, int stage,
-			long lastMod, long len) {
+			Instant lastMod, long len) {
 		if (p != null && !p.getEntryFileMode().equals(FileMode.TREE)) {
 			DirCacheEntry e = new DirCacheEntry(path, stage);
 			e.setFileMode(p.getEntryFileMode());
@@ -474,13 +493,78 @@ private DirCacheEntry keep(DirCacheEntry e) {
 				e.getStage());
 		newEntry.setFileMode(e.getFileMode());
 		newEntry.setObjectId(e.getObjectId());
-		newEntry.setLastModified(e.getLastModified());
+		newEntry.setLastModified(e.getLastModifiedInstant());
 		newEntry.setLength(e.getLength());
 		builder.add(newEntry);
 		return newEntry;
 	}
 
 	/**
+	 * Remembers the {@link CheckoutMetadata} for the given path; it may be
+	 * needed in {@link #checkout()} or in {@link #cleanUp()}.
+	 *
+	 * @param path
+	 *            of the current node
+	 * @param attributes
+	 *            for the current node
+	 * @throws IOException
+	 *             if the smudge filter cannot be determined
+	 * @since 5.1
+	 */
+	protected void addCheckoutMetadata(String path, Attributes attributes)
+			throws IOException {
+		if (checkoutMetadata != null) {
+			EolStreamType eol = EolStreamTypeUtil.detectStreamType(
+					OperationType.CHECKOUT_OP, workingTreeOptions, attributes);
+			CheckoutMetadata data = new CheckoutMetadata(eol,
+					tw.getFilterCommand(Constants.ATTR_FILTER_TYPE_SMUDGE));
+			checkoutMetadata.put(path, data);
+		}
+	}
+
+	/**
+	 * Adds a {@link DirCacheEntry} for direct checkout and remembers its
+	 * {@link CheckoutMetadata}.
+	 *
+	 * @param path
+	 *            of the entry
+	 * @param entry
+	 *            to add
+	 * @param attributes
+	 *            for the current entry
+	 * @throws IOException
+	 *             if the {@link CheckoutMetadata} cannot be determined
+	 * @since 5.1
+	 */
+	protected void addToCheckout(String path, DirCacheEntry entry,
+			Attributes attributes) throws IOException {
+		toBeCheckedOut.put(path, entry);
+		addCheckoutMetadata(path, attributes);
+	}
+
+	/**
+	 * Remember a path for deletion, and remember its {@link CheckoutMetadata}
+	 * in case it has to be restored in {@link #cleanUp()}.
+	 *
+	 * @param path
+	 *            of the entry
+	 * @param isFile
+	 *            whether it is a file
+	 * @param attributes
+	 *            for the entry
+	 * @throws IOException
+	 *             if the {@link CheckoutMetadata} cannot be determined
+	 * @since 5.1
+	 */
+	protected void addDeletion(String path, boolean isFile,
+			Attributes attributes) throws IOException {
+		toBeDeleted.add(path);
+		if (isFile) {
+			addCheckoutMetadata(path, attributes);
+		}
+	}
+
+	/**
 	 * Processes one path and tries to merge taking git attributes in account.
 	 * This method will do all trivial (not content) merges and will also detect
 	 * if a merge will fail. The merge will fail when one of the following is
@@ -585,16 +669,17 @@ protected boolean processEntry(CanonicalTreeParser base,
 						// we know about length and lastMod only after we have written the new content.
 						// This will happen later. Set these values to 0 for know.
 						DirCacheEntry e = add(tw.getRawPath(), theirs,
-								DirCacheEntry.STAGE_0, 0, 0);
-						toBeCheckedOut.put(tw.getPathString(), e);
+								DirCacheEntry.STAGE_0, EPOCH, 0);
+						addToCheckout(tw.getPathString(), e, attributes);
 					}
 					return true;
 				} else {
 					// FileModes are not mergeable. We found a conflict on modes.
 					// For conflicting entries we don't know lastModified and length.
-					add(tw.getRawPath(), base, DirCacheEntry.STAGE_1, 0, 0);
-					add(tw.getRawPath(), ours, DirCacheEntry.STAGE_2, 0, 0);
-					add(tw.getRawPath(), theirs, DirCacheEntry.STAGE_3, 0, 0);
+					add(tw.getRawPath(), base, DirCacheEntry.STAGE_1, EPOCH, 0);
+					add(tw.getRawPath(), ours, DirCacheEntry.STAGE_2, EPOCH, 0);
+					add(tw.getRawPath(), theirs, DirCacheEntry.STAGE_3, EPOCH,
+							0);
 					unmergedPaths.add(tw.getPathString());
 					mergeResults.put(
 							tw.getPathString(),
@@ -626,19 +711,24 @@ protected boolean processEntry(CanonicalTreeParser base,
 				// the new content.
 				// This will happen later. Set these values to 0 for know.
 				DirCacheEntry e = add(tw.getRawPath(), theirs,
-						DirCacheEntry.STAGE_0, 0, 0);
-				if (e != null)
-					toBeCheckedOut.put(tw.getPathString(), e);
+						DirCacheEntry.STAGE_0, EPOCH, 0);
+				if (e != null) {
+					addToCheckout(tw.getPathString(), e, attributes);
+				}
 				return true;
 			} else {
 				// we want THEIRS ... but THEIRS contains a folder or the
-				// deletion of the path. Delete what's in the workingtree (the
-				// workingtree is clean) but do not complain if the file is
-				// already deleted locally. This complements the test in
-				// isWorktreeDirty() for the same case.
-				if (tw.getTreeCount() > T_FILE && tw.getRawMode(T_FILE) == 0)
+				// deletion of the path. Delete what's in the working tree,
+				// which we know to be clean.
+				if (tw.getTreeCount() > T_FILE && tw.getRawMode(T_FILE) == 0) {
+					// Not present in working tree, so nothing to delete
 					return true;
-				toBeDeleted.add(tw.getPathString());
+				}
+				if (modeT != 0 && modeT == modeB) {
+					// Base, ours, and theirs all contain a folder: don't delete
+					return true;
+				}
+				addDeletion(tw.getPathString(), nonTree(modeO), attributes);
 				return true;
 			}
 		}
@@ -650,16 +740,16 @@ protected boolean processEntry(CanonicalTreeParser base,
 			// detected later
 			if (nonTree(modeO) && !nonTree(modeT)) {
 				if (nonTree(modeB))
-					add(tw.getRawPath(), base, DirCacheEntry.STAGE_1, 0, 0);
-				add(tw.getRawPath(), ours, DirCacheEntry.STAGE_2, 0, 0);
+					add(tw.getRawPath(), base, DirCacheEntry.STAGE_1, EPOCH, 0);
+				add(tw.getRawPath(), ours, DirCacheEntry.STAGE_2, EPOCH, 0);
 				unmergedPaths.add(tw.getPathString());
 				enterSubtree = false;
 				return true;
 			}
 			if (nonTree(modeT) && !nonTree(modeO)) {
 				if (nonTree(modeB))
-					add(tw.getRawPath(), base, DirCacheEntry.STAGE_1, 0, 0);
-				add(tw.getRawPath(), theirs, DirCacheEntry.STAGE_3, 0, 0);
+					add(tw.getRawPath(), base, DirCacheEntry.STAGE_1, EPOCH, 0);
+				add(tw.getRawPath(), theirs, DirCacheEntry.STAGE_3, EPOCH, 0);
 				unmergedPaths.add(tw.getPathString());
 				enterSubtree = false;
 				return true;
@@ -686,9 +776,9 @@ protected boolean processEntry(CanonicalTreeParser base,
 			boolean gitlinkConflict = isGitLink(modeO) || isGitLink(modeT);
 			// Don't attempt to resolve submodule link conflicts
 			if (gitlinkConflict || !attributes.canBeContentMerged()) {
-				add(tw.getRawPath(), base, DirCacheEntry.STAGE_1, 0, 0);
-				add(tw.getRawPath(), ours, DirCacheEntry.STAGE_2, 0, 0);
-				add(tw.getRawPath(), theirs, DirCacheEntry.STAGE_3, 0, 0);
+				add(tw.getRawPath(), base, DirCacheEntry.STAGE_1, EPOCH, 0);
+				add(tw.getRawPath(), ours, DirCacheEntry.STAGE_2, EPOCH, 0);
+				add(tw.getRawPath(), theirs, DirCacheEntry.STAGE_3, EPOCH, 0);
 
 				if (gitlinkConflict) {
 					MergeResult<SubmoduleConflict> result = new MergeResult<>(
@@ -722,9 +812,12 @@ protected boolean processEntry(CanonicalTreeParser base,
 				result.setContainsConflicts(false);
 			}
 			updateIndex(base, ours, theirs, result, attributes);
-			if (result.containsConflicts() && !ignoreConflicts)
-				unmergedPaths.add(tw.getPathString());
-			modifiedFiles.add(tw.getPathString());
+			String currentPath = tw.getPathString();
+			if (result.containsConflicts() && !ignoreConflicts) {
+				unmergedPaths.add(currentPath);
+			}
+			modifiedFiles.add(currentPath);
+			addCheckoutMetadata(currentPath, attributes);
 		} else if (modeO != modeT) {
 			// OURS or THEIRS has been deleted
 			if (((modeO != 0 && !tw.idEqual(T_BASE, T_OURS)) || (modeT != 0 && !tw
@@ -732,10 +825,10 @@ protected boolean processEntry(CanonicalTreeParser base,
 				MergeResult<RawText> result = contentMerge(base, ours, theirs,
 						attributes);
 
-				add(tw.getRawPath(), base, DirCacheEntry.STAGE_1, 0, 0);
-				add(tw.getRawPath(), ours, DirCacheEntry.STAGE_2, 0, 0);
+				add(tw.getRawPath(), base, DirCacheEntry.STAGE_1, EPOCH, 0);
+				add(tw.getRawPath(), ours, DirCacheEntry.STAGE_2, EPOCH, 0);
 				DirCacheEntry e = add(tw.getRawPath(), theirs,
-						DirCacheEntry.STAGE_3, 0, 0);
+						DirCacheEntry.STAGE_3, EPOCH, 0);
 
 				// OURS was deleted checkout THEIRS
 				if (modeO == 0) {
@@ -743,8 +836,9 @@ protected boolean processEntry(CanonicalTreeParser base,
 					if (isWorktreeDirty(work, ourDce))
 						return false;
 					if (nonTree(modeT)) {
-						if (e != null)
-							toBeCheckedOut.put(tw.getPathString(), e);
+						if (e != null) {
+							addToCheckout(tw.getPathString(), e, attributes);
+						}
 					}
 				}
 
@@ -866,9 +960,9 @@ private void updateIndex(CanonicalTreeParser base,
 				// A conflict occurred, the file will contain conflict markers
 				// the index will be populated with the three stages and the
 				// workdir (if used) contains the halfway merged content.
-				add(tw.getRawPath(), base, DirCacheEntry.STAGE_1, 0, 0);
-				add(tw.getRawPath(), ours, DirCacheEntry.STAGE_2, 0, 0);
-				add(tw.getRawPath(), theirs, DirCacheEntry.STAGE_3, 0, 0);
+				add(tw.getRawPath(), base, DirCacheEntry.STAGE_1, EPOCH, 0);
+				add(tw.getRawPath(), ours, DirCacheEntry.STAGE_2, EPOCH, 0);
+				add(tw.getRawPath(), theirs, DirCacheEntry.STAGE_3, EPOCH, 0);
 				mergeResults.put(tw.getPathString(), result);
 				return;
 			}
@@ -885,7 +979,7 @@ private void updateIndex(CanonicalTreeParser base,
 					? FileMode.REGULAR_FILE : FileMode.fromBits(newMode));
 			if (mergedFile != null) {
 				dce.setLastModified(
-						nonNullRepo().getFS().lastModified(mergedFile));
+						nonNullRepo().getFS().lastModifiedInstant(mergedFile));
 				dce.setLength((int) mergedFile.length());
 			}
 			dce.setObjectId(insertMergeResult(rawMerged, attributes));
@@ -935,7 +1029,7 @@ private TemporaryBuffer doMerge(MergeResult<RawText> result)
 				db != null ? nonNullRepo().getDirectory() : null, inCoreLimit);
 		try {
 			new MergeFormatter().formatMerge(buf, result,
-					Arrays.asList(commitNames), CHARACTER_ENCODING);
+					Arrays.asList(commitNames), UTF_8.name());
 			buf.close();
 		} catch (IOException e) {
 			buf.destroy();
@@ -1245,7 +1339,8 @@ protected boolean mergeTreeWalk(TreeWalk treeWalk, boolean ignoreConflicts)
 					hasWorkingTreeIterator ? treeWalk.getTree(T_FILE,
 							WorkingTreeIterator.class) : null,
 					ignoreConflicts, hasAttributeNodeProvider
-							? treeWalk.getAttributes() : new Attributes())) {
+							? treeWalk.getAttributes()
+							: NO_ATTRIBUTES)) {
 				cleanUp();
 				return false;
 			}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/patch/FileHeader.java b/org.eclipse.jgit/src/org/eclipse/jgit/patch/FileHeader.java
index d0a5216..1f4beb0 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/patch/FileHeader.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/patch/FileHeader.java
@@ -43,6 +43,7 @@
 
 package org.eclipse.jgit.patch;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.eclipse.jgit.lib.Constants.encodeASCII;
 import static org.eclipse.jgit.util.RawParseUtils.decode;
 import static org.eclipse.jgit.util.RawParseUtils.decodeNoFallback;
@@ -63,7 +64,6 @@
 import org.eclipse.jgit.diff.EditList;
 import org.eclipse.jgit.internal.JGitText;
 import org.eclipse.jgit.lib.AbbreviatedObjectId;
-import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.FileMode;
 import org.eclipse.jgit.util.QuotedString;
 import org.eclipse.jgit.util.RawParseUtils;
@@ -198,7 +198,7 @@ public int getEndOffset() {
 	 * Convert the patch script for this file into a string.
 	 * <p>
 	 * The default character encoding
-	 * ({@link org.eclipse.jgit.lib.Constants#CHARSET}) is assumed for both the
+	 * ({@link java.nio.charset.StandardCharsets#UTF_8}) is assumed for both the
 	 * old and new files.
 	 *
 	 * @return the patch script, as a Unicode string.
@@ -240,8 +240,9 @@ String getScriptText(Charset[] charsetGuess) {
 
 		if (trySimpleConversion(charsetGuess)) {
 			Charset cs = charsetGuess != null ? charsetGuess[0] : null;
-			if (cs == null)
-				cs = Constants.CHARSET;
+			if (cs == null) {
+				cs = UTF_8;
+			}
 			try {
 				return decodeNoFallback(cs, buf, startOffset, endOffset);
 			} catch (CharacterCodingException cee) {
@@ -290,8 +291,9 @@ private static boolean trySimpleConversion(Charset[] charsetGuess) {
 			final String[] r = new String[tmp.length];
 			for (int i = 0; i < tmp.length; i++) {
 				Charset cs = csGuess != null ? csGuess[i] : null;
-				if (cs == null)
-					cs = Constants.CHARSET;
+				if (cs == null) {
+					cs = UTF_8;
+				}
 				r[i] = RawParseUtils.decode(cs, tmp[i].toByteArray());
 			}
 			return r;
@@ -429,7 +431,7 @@ int parseGitFileName(int ptr, int end) {
 					oldPath = QuotedString.GIT_PATH.dequote(buf, bol, sp - 1);
 					oldPath = p1(oldPath);
 				} else {
-					oldPath = decode(Constants.CHARSET, buf, aStart, sp - 1);
+					oldPath = decode(UTF_8, buf, aStart, sp - 1);
 				}
 				newPath = oldPath;
 				return eol;
@@ -572,7 +574,7 @@ private String parseName(String expect, int ptr, int end) {
 				tab--;
 			if (ptr == tab)
 				tab = end;
-			r = decode(Constants.CHARSET, buf, ptr, tab - 1);
+			r = decode(UTF_8, buf, ptr, tab - 1);
 		}
 
 		if (r.equals(DEV_NULL))
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/patch/FormatError.java b/org.eclipse.jgit/src/org/eclipse/jgit/patch/FormatError.java
index 1dd24d7..10ea778 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/patch/FormatError.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/patch/FormatError.java
@@ -43,9 +43,10 @@
 
 package org.eclipse.jgit.patch;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
+
 import java.util.Locale;
 
-import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.util.RawParseUtils;
 
 /**
@@ -120,7 +121,7 @@ public int getOffset() {
 	 */
 	public String getLineText() {
 		final int eol = RawParseUtils.nextLF(buf, offset);
-		return RawParseUtils.decode(Constants.CHARSET, buf, offset, eol);
+		return RawParseUtils.decode(UTF_8, buf, offset, eol);
 	}
 
 	/** {@inheritDoc} */
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revplot/PlotWalk.java b/org.eclipse.jgit/src/org/eclipse/jgit/revplot/PlotWalk.java
index f27f356..ee18fe7 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revplot/PlotWalk.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revplot/PlotWalk.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2008-2009, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2008-2018, Robin Rosenberg <robin.rosenberg@dewire.com>
  * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
  * and other copyright owners as documented in the project's IP log.
  *
@@ -53,6 +53,7 @@
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Comparator;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Map;
 import java.util.Set;
@@ -75,13 +76,25 @@
  */
 public class PlotWalk extends RevWalk {
 
+	private Map<AnyObjectId, Set<Ref>> additionalRefMap;
+
 	private Map<AnyObjectId, Set<Ref>> reverseRefMap;
 
+	private Repository repository;
+
 	/** {@inheritDoc} */
 	@Override
 	public void dispose() {
 		super.dispose();
-		reverseRefMap.clear();
+		if (reverseRefMap != null) {
+			reverseRefMap.clear();
+			reverseRefMap = null;
+		}
+		if (additionalRefMap != null) {
+			additionalRefMap.clear();
+			additionalRefMap = null;
+		}
+		repository = null;
 	}
 
 	/**
@@ -93,7 +106,8 @@ public void dispose() {
 	public PlotWalk(Repository repo) {
 		super(repo);
 		super.sort(RevSort.TOPO, true);
-		reverseRefMap = repo.getAllRefsByPeeledObjectId();
+		additionalRefMap = new HashMap<>();
+		repository = repo;
 	}
 
 	/**
@@ -105,14 +119,14 @@ public PlotWalk(Repository repo) {
 	 */
 	public void addAdditionalRefs(Iterable<Ref> refs) throws IOException {
 		for (Ref ref : refs) {
-			Set<Ref> set = reverseRefMap.get(ref.getObjectId());
+			Set<Ref> set = additionalRefMap.get(ref.getObjectId());
 			if (set == null)
 				set = Collections.singleton(ref);
 			else {
 				set = new HashSet<>(set);
 				set.add(ref);
 			}
-			reverseRefMap.put(ref.getObjectId(), set);
+			additionalRefMap.put(ref.getObjectId(), set);
 		}
 	}
 
@@ -141,11 +155,29 @@ public RevCommit next() throws MissingObjectException,
 	}
 
 	private Ref[] getRefs(AnyObjectId commitId) {
+		if (reverseRefMap == null) {
+			reverseRefMap = repository.getAllRefsByPeeledObjectId();
+			for (Map.Entry<AnyObjectId, Set<Ref>> entry : additionalRefMap
+					.entrySet()) {
+				Set<Ref> set = reverseRefMap.get(entry.getKey());
+				Set<Ref> additional = entry.getValue();
+				if (set != null) {
+					if (additional.size() == 1) {
+						// It's an unmodifiable singleton set...
+						additional = new HashSet<>(additional);
+					}
+					additional.addAll(set);
+				}
+				reverseRefMap.put(entry.getKey(), additional);
+			}
+			additionalRefMap.clear();
+			additionalRefMap = null;
+		}
 		Collection<Ref> list = reverseRefMap.get(commitId);
-		if (list == null)
+		if (list == null) {
 			return PlotCommit.NO_REFS;
-		else {
-			Ref[] tags = list.toArray(new Ref[list.size()]);
+		} else {
+			Ref[] tags = list.toArray(new Ref[0]);
 			Arrays.sort(tags, new PlotRefComparator());
 			return tags;
 		}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevCommit.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevCommit.java
index b67f934..86ecd8e 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevCommit.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevCommit.java
@@ -44,13 +44,14 @@
 
 package org.eclipse.jgit.revwalk;
 
-import static org.eclipse.jgit.lib.Constants.CHARSET;
+import static java.nio.charset.StandardCharsets.UTF_8;
 
 import java.io.IOException;
 import java.nio.charset.Charset;
 import java.nio.charset.IllegalCharsetNameException;
 import java.nio.charset.UnsupportedCharsetException;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 
@@ -168,10 +169,10 @@ void parseBody(RevWalk walk) throws MissingObjectException,
 		}
 	}
 
-	void parseCanonical(RevWalk walk, byte[] raw)
-			throws IOException {
-		if (!walk.shallowCommitsInitialized)
-			walk.initializeShallowCommits();
+	void parseCanonical(RevWalk walk, byte[] raw) throws IOException {
+		if (!walk.shallowCommitsInitialized) {
+			walk.initializeShallowCommits(this);
+		}
 
 		final MutableObjectId idBuffer = walk.idBuffer;
 		idBuffer.fromString(raw, 5);
@@ -182,13 +183,14 @@ void parseCanonical(RevWalk walk, byte[] raw)
 			RevCommit[] pList = new RevCommit[1];
 			int nParents = 0;
 			for (;;) {
-				if (raw[ptr] != 'p')
+				if (raw[ptr] != 'p') {
 					break;
+				}
 				idBuffer.fromString(raw, ptr + 7);
 				final RevCommit p = walk.lookupCommit(idBuffer);
-				if (nParents == 0)
+				if (nParents == 0) {
 					pList[nParents++] = p;
-				else if (nParents == 1) {
+				} else if (nParents == 1) {
 					pList = new RevCommit[] { pList[0], p };
 					nParents = 2;
 				} else {
@@ -218,8 +220,9 @@ else if (nParents == 1) {
 			commitTime = RawParseUtils.parseBase10(raw, ptr, null);
 		}
 
-		if (walk.isRetainBody())
+		if (walk.isRetainBody()) {
 			buffer = raw;
+		}
 		flags |= PARSED;
 	}
 
@@ -388,6 +391,34 @@ public final RevCommit getParent(int nth) {
 	}
 
 	/**
+	 * Parse the gpg signature from the raw buffer.
+	 * <p>
+	 * This method parses and returns the raw content of the gpgsig lines. This
+	 * method is fairly expensive and produces a new byte[] instance on each
+	 * invocation. Callers should invoke this method only if they are certain
+	 * they will need, and should cache the return value for as long as
+	 * necessary to use all information from it.
+	 * <p>
+	 * RevFilter implementations should try to use
+	 * {@link org.eclipse.jgit.util.RawParseUtils} to scan the
+	 * {@link #getRawBuffer()} instead, as this will allow faster evaluation of
+	 * commits.
+	 *
+	 * @return contents of the gpg signature; null if the commit was not signed.
+	 * @since 5.1
+	 */
+	public final @Nullable byte[] getRawGpgSignature() {
+		final byte[] raw = buffer;
+		final byte[] header = {'g', 'p', 'g', 's', 'i', 'g'};
+		final int start = RawParseUtils.headerStart(header, raw, 0);
+		if (start < 0) {
+			return null;
+		}
+		final int end = RawParseUtils.headerEnd(raw, start);
+		return Arrays.copyOfRange(raw, start, end);
+	}
+
+	/**
 	 * Parse the author identity from the raw buffer.
 	 * <p>
 	 * This method parses and returns the content of the author line, after
@@ -539,7 +570,7 @@ private Charset guessEncoding() {
 		try {
 			return getEncoding();
 		} catch (IllegalCharsetNameException | UnsupportedCharsetException e) {
-			return CHARSET;
+			return UTF_8;
 		}
 	}
 
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevTag.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevTag.java
index 0050bac..32e7adf 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevTag.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevTag.java
@@ -45,7 +45,7 @@
 
 package org.eclipse.jgit.revwalk;
 
-import static org.eclipse.jgit.lib.Constants.CHARSET;
+import static java.nio.charset.StandardCharsets.UTF_8;
 
 import java.io.IOException;
 import java.nio.charset.Charset;
@@ -169,7 +169,7 @@ void parseCanonical(RevWalk walk, byte[] rawTag)
 
 		int p = pos.value += 4; // "tag "
 		final int nameEnd = RawParseUtils.nextLF(rawTag, p) - 1;
-		tagName = RawParseUtils.decode(CHARSET, rawTag, p, nameEnd);
+		tagName = RawParseUtils.decode(UTF_8, rawTag, p, nameEnd);
 
 		if (walk.isRetainBody())
 			buffer = rawTag;
@@ -257,7 +257,7 @@ private Charset guessEncoding() {
 		try {
 			return RawParseUtils.parseEncoding(buffer);
 		} catch (IllegalCharsetNameException | UnsupportedCharsetException e) {
-			return CHARSET;
+			return UTF_8;
 		}
 	}
 
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 a986cfd..4d555d2 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevWalk.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevWalk.java
@@ -1460,17 +1460,44 @@ public void assumeShallow(Collection<? extends ObjectId> ids) {
 			lookupCommit(id).parents = RevCommit.NO_PARENTS;
 	}
 
-	void initializeShallowCommits() throws IOException {
-		if (shallowCommitsInitialized)
+	/**
+	 * Reads the "shallow" file and applies it by setting the parents of shallow
+	 * commits to an empty array.
+	 * <p>
+	 * There is a sequencing problem if the first commit being parsed is a
+	 * shallow commit, since {@link RevCommit#parseCanonical(RevWalk, byte[])}
+	 * calls this method before its callers add the new commit to the
+	 * {@link RevWalk#objects} map. That means a call from this method to
+	 * {@link #lookupCommit(AnyObjectId)} fails to find that commit and creates
+	 * a new one, which is promptly discarded.
+	 * <p>
+	 * To avoid that, {@link RevCommit#parseCanonical(RevWalk, byte[])} passes
+	 * its commit to this method, so that this method can apply the shallow
+	 * state to it directly and avoid creating the duplicate commit object.
+	 *
+	 * @param rc
+	 *            the initial commit being parsed
+	 * @throws IOException
+	 *             if the shallow commits file can't be read
+	 */
+	void initializeShallowCommits(RevCommit rc) throws IOException {
+		if (shallowCommitsInitialized) {
 			throw new IllegalStateException(
 					JGitText.get().shallowCommitsAlreadyInitialized);
+		}
 
 		shallowCommitsInitialized = true;
 
-		if (reader == null)
+		if (reader == null) {
 			return;
+		}
 
-		for (ObjectId id : reader.getShallowCommits())
-			lookupCommit(id).parents = RevCommit.NO_PARENTS;
+		for (ObjectId id : reader.getShallowCommits()) {
+			if (id.equals(rc.getId())) {
+				rc.parents = RevCommit.NO_PARENTS;
+			} else {
+				lookupCommit(id).parents = RevCommit.NO_PARENTS;
+			}
+		}
 	}
 }
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 4b272ba..e7b0941 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
@@ -49,7 +49,7 @@
 
 package org.eclipse.jgit.storage.file;
 
-import static org.eclipse.jgit.lib.Constants.CHARSET;
+import static java.nio.charset.StandardCharsets.UTF_8;
 
 import java.io.ByteArrayOutputStream;
 import java.io.File;
@@ -153,7 +153,9 @@ public void load() throws IOException, ConfigInvalidException {
 		int retries = 0;
 		while (true) {
 			final FileSnapshot oldSnapshot = snapshot;
-			final FileSnapshot newSnapshot = FileSnapshot.save(getFile());
+			final FileSnapshot newSnapshot;
+			// don't use config in this snapshot to avoid endless recursion
+			newSnapshot = FileSnapshot.saveNoConfig(getFile());
 			try {
 				final byte[] in = IO.readFully(getFile());
 				final ObjectId newHash = hash(in);
@@ -166,7 +168,7 @@ public void load() throws IOException, ConfigInvalidException {
 				} else {
 					final String decoded;
 					if (isUtf8(in)) {
-						decoded = RawParseUtils.decode(CHARSET,
+						decoded = RawParseUtils.decode(UTF_8,
 								in, 3, in.length);
 						utf8Bom = true;
 					} else {
@@ -224,7 +226,7 @@ public void save() throws IOException {
 			bos.write(0xEF);
 			bos.write(0xBB);
 			bos.write(0xBF);
-			bos.write(text.getBytes(CHARSET));
+			bos.write(text.getBytes(UTF_8));
 			out = bos.toByteArray();
 		} else {
 			out = Constants.encode(text);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackConfig.java
index 256e41d..6bd32dd 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackConfig.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackConfig.java
@@ -116,12 +116,30 @@ public class PackConfig {
 	 */
 	public static final int DEFAULT_DELTA_SEARCH_WINDOW_SIZE = 10;
 
+	private static final int MB = 1 << 20;
+
 	/**
 	 * Default big file threshold: {@value}
 	 *
 	 * @see #setBigFileThreshold(int)
 	 */
-	public static final int DEFAULT_BIG_FILE_THRESHOLD = 50 * 1024 * 1024;
+	public static final int DEFAULT_BIG_FILE_THRESHOLD = 50 * MB;
+
+	/**
+	 * Default if we wait before opening a newly written pack to prevent its
+	 * lastModified timestamp could be racy
+	 *
+	 * @since 5.1.8
+	 */
+	public static final boolean DEFAULT_WAIT_PREVENT_RACY_PACK = false;
+
+	/**
+	 * Default if we wait before opening a newly written pack to prevent its
+	 * lastModified timestamp could be racy
+	 *
+	 * @since 5.1.8
+	 */
+	public static final long DEFAULT_MINSIZE_PREVENT_RACY_PACK = 100 * MB;
 
 	/**
 	 * Default delta cache size: {@value}
@@ -238,6 +256,10 @@ public class PackConfig {
 
 	private int bigFileThreshold = DEFAULT_BIG_FILE_THRESHOLD;
 
+	private boolean waitPreventRacyPack = DEFAULT_WAIT_PREVENT_RACY_PACK;
+
+	private long minSizePreventRacyPack = DEFAULT_MINSIZE_PREVENT_RACY_PACK;
+
 	private int threads;
 
 	private Executor executor;
@@ -314,6 +336,8 @@ public PackConfig(PackConfig cfg) {
 		this.deltaCacheSize = cfg.deltaCacheSize;
 		this.deltaCacheLimit = cfg.deltaCacheLimit;
 		this.bigFileThreshold = cfg.bigFileThreshold;
+		this.waitPreventRacyPack = cfg.waitPreventRacyPack;
+		this.minSizePreventRacyPack = cfg.minSizePreventRacyPack;
 		this.threads = cfg.threads;
 		this.executor = cfg.executor;
 		this.indexVersion = cfg.indexVersion;
@@ -737,6 +761,76 @@ public void setBigFileThreshold(int bigFileThreshold) {
 	}
 
 	/**
+	 * Get whether we wait before opening a newly written pack to prevent its
+	 * lastModified timestamp could be racy
+	 *
+	 * @return whether we wait before opening a newly written pack to prevent
+	 *         its lastModified timestamp could be racy
+	 * @since 5.1.8
+	 */
+	public boolean isWaitPreventRacyPack() {
+		return waitPreventRacyPack;
+	}
+
+	/**
+	 * Get whether we wait before opening a newly written pack to prevent its
+	 * lastModified timestamp could be racy. Returns {@code true} if
+	 * {@code waitToPreventRacyPack = true} and
+	 * {@code packSize > minSizePreventRacyPack}, {@code false} otherwise.
+	 *
+	 * @param packSize
+	 *            size of the pack file
+	 *
+	 * @return whether we wait before opening a newly written pack to prevent
+	 *         its lastModified timestamp could be racy
+	 * @since 5.1.8
+	 */
+	public boolean doWaitPreventRacyPack(long packSize) {
+		return isWaitPreventRacyPack()
+				&& packSize > getMinSizePreventRacyPack();
+	}
+
+	/**
+	 * Set whether we wait before opening a newly written pack to prevent its
+	 * lastModified timestamp could be racy
+	 *
+	 * @param waitPreventRacyPack
+	 *            whether we wait before opening a newly written pack to prevent
+	 *            its lastModified timestamp could be racy
+	 * @since 5.1.8
+	 */
+	public void setWaitPreventRacyPack(boolean waitPreventRacyPack) {
+		this.waitPreventRacyPack = waitPreventRacyPack;
+	}
+
+	/**
+	 * Get minimum packfile size for which we wait before opening a newly
+	 * written pack to prevent its lastModified timestamp could be racy if
+	 * {@code isWaitToPreventRacyPack} is {@code true}.
+	 *
+	 * @return minimum packfile size, default is 100 MiB
+	 *
+	 * @since 5.1.8
+	 */
+	public long getMinSizePreventRacyPack() {
+		return minSizePreventRacyPack;
+	}
+
+	/**
+	 * Set minimum packfile size for which we wait before opening a newly
+	 * written pack to prevent its lastModified timestamp could be racy if
+	 * {@code isWaitToPreventRacyPack} is {@code true}.
+	 *
+	 * @param minSizePreventRacyPack
+	 *            minimum packfile size, default is 100 MiB
+	 *
+	 * @since 5.1.8
+	 */
+	public void setMinSizePreventRacyPack(long minSizePreventRacyPack) {
+		this.minSizePreventRacyPack = minSizePreventRacyPack;
+	}
+
+	/**
 	 * Get the compression level applied to objects in the pack.
 	 *
 	 * Default setting: {@value java.util.zip.Deflater#DEFAULT_COMPRESSION}
@@ -1083,6 +1177,10 @@ public void fromConfig(Config rc) {
 		setBitmapInactiveBranchAgeInDays(
 				rc.getInt("pack", "bitmapinactivebranchageindays", //$NON-NLS-1$ //$NON-NLS-2$
 						getBitmapInactiveBranchAgeInDays()));
+		setWaitPreventRacyPack(rc.getBoolean("pack", "waitpreventracypack", //$NON-NLS-1$ //$NON-NLS-2$
+				isWaitPreventRacyPack()));
+		setMinSizePreventRacyPack(rc.getLong("pack", "minsizepreventracypack", //$NON-NLS-1$//$NON-NLS-2$
+				getMinSizePreventRacyPack()));
 	}
 
 	/** {@inheritDoc} */
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/AmazonS3.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/AmazonS3.java
index a0fc57c..f6ec4b9 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/AmazonS3.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/AmazonS3.java
@@ -43,7 +43,7 @@
 
 package org.eclipse.jgit.transport;
 
-import static org.eclipse.jgit.lib.Constants.CHARSET;
+import static java.nio.charset.StandardCharsets.UTF_8;
 
 import java.io.ByteArrayOutputStream;
 import java.io.File;
@@ -635,7 +635,7 @@ void authorize(HttpURLConnection c) throws IOException {
 		try {
 			final Mac m = Mac.getInstance(HMAC);
 			m.init(privateKey);
-			sec = Base64.encodeBytes(m.doFinal(s.toString().getBytes(CHARSET)));
+			sec = Base64.encodeBytes(m.doFinal(s.toString().getBytes(UTF_8)));
 		} catch (NoSuchAlgorithmException e) {
 			throw new IOException(MessageFormat.format(JGitText.get().noHMACsupport, HMAC, e.getMessage()));
 		} catch (InvalidKeyException e) {
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 38eae1c..fcf78ac 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackConnection.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackConnection.java
@@ -57,6 +57,7 @@
 import java.util.LinkedHashMap;
 import java.util.Set;
 
+import org.eclipse.jgit.errors.InvalidObjectIdException;
 import org.eclipse.jgit.errors.NoRemoteRepositoryException;
 import org.eclipse.jgit.errors.PackProtocolException;
 import org.eclipse.jgit.errors.RemoteRepositoryException;
@@ -222,6 +223,10 @@ private void readAdvertisedRefsImpl() throws IOException {
 				}
 			}
 
+			// Expecting to get a line in the form "sha1 refname"
+			if (line.length() < 41 || line.charAt(40) != ' ') {
+				throw invalidRefAdvertisementLine(line);
+			}
 			String name = line.substring(41, line.length());
 			if (avail.isEmpty() && name.equals("capabilities^{}")) { //$NON-NLS-1$
 				// special line from git-receive-pack to show
@@ -229,7 +234,12 @@ private void readAdvertisedRefsImpl() throws IOException {
 				continue;
 			}
 
-			final ObjectId id = ObjectId.fromString(line.substring(0, 40));
+			final ObjectId id;
+			try {
+				id  = ObjectId.fromString(line.substring(0, 40));
+			} catch (InvalidObjectIdException e) {
+				throw invalidRefAdvertisementLine(line);
+			}
 			if (name.equals(".have")) { //$NON-NLS-1$
 				additionalHaves.add(id);
 			} else if (name.endsWith("^{}")) { //$NON-NLS-1$
@@ -318,6 +328,10 @@ private PackProtocolException duplicateAdvertisement(String name) {
 		return new PackProtocolException(uri, MessageFormat.format(JGitText.get().duplicateAdvertisementsOf, name));
 	}
 
+	private PackProtocolException invalidRefAdvertisementLine(String line) {
+		return new PackProtocolException(uri, MessageFormat.format(JGitText.get().invalidRefAdvertisementLine, line));
+	}
+
 	/** {@inheritDoc} */
 	@Override
 	public void close() {
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 0dfcd87..ed7465c 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackFetchConnection.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackFetchConnection.java
@@ -52,7 +52,6 @@
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Date;
-import java.util.HashSet;
 import java.util.Set;
 
 import org.eclipse.jgit.errors.PackProtocolException;
@@ -235,12 +234,12 @@ public abstract class BasePackFetchConnection extends BasePackConnection
 
 	private boolean noProgress;
 
-	private Set<AnyObjectId> minimalNegotiationSet;
-
 	private String lockMessage;
 
 	private PackLock packLock;
 
+	private int maxHaves;
+
 	/** RPC state, if {@link BasePackConnection#statelessRPC} is true. */
 	private TemporaryBuffer.Heap state;
 
@@ -261,12 +260,12 @@ public BasePackFetchConnection(PackTransport packTransport) {
 		if (local != null) {
 			final FetchConfig cfg = getFetchConfig();
 			allowOfsDelta = cfg.allowOfsDelta;
-			if (cfg.minimalNegotiation) {
-				minimalNegotiationSet = new HashSet<>();
-			}
+			maxHaves = cfg.maxHaves;
 		} else {
 			allowOfsDelta = true;
+			maxHaves = Integer.MAX_VALUE;
 		}
+
 		includeTags = transport.getTagOpt() != TagOpt.NO_TAGS;
 		thinPack = transport.isFetchThin();
 		filterBlobLimit = transport.getFilterBlobLimit();
@@ -294,17 +293,16 @@ public BasePackFetchConnection(PackTransport packTransport) {
 	static class FetchConfig {
 		final boolean allowOfsDelta;
 
-		final boolean minimalNegotiation;
+		final int maxHaves;
 
 		FetchConfig(Config c) {
 			allowOfsDelta = c.getBoolean("repack", "usedeltabaseoffset", true); //$NON-NLS-1$ //$NON-NLS-2$
-			minimalNegotiation = c.getBoolean("fetch", "useminimalnegotiation", //$NON-NLS-1$ //$NON-NLS-2$
-					false);
+			maxHaves = c.getInt("fetch", "maxhaves", Integer.MAX_VALUE); //$NON-NLS-1$ //$NON-NLS-2$
 		}
 
-		FetchConfig(boolean allowOfsDelta, boolean minimalNegotiation) {
+		FetchConfig(boolean allowOfsDelta, int maxHaves) {
 			this.allowOfsDelta = allowOfsDelta;
-			this.minimalNegotiation = minimalNegotiation;
+			this.maxHaves = maxHaves;
 		}
 	}
 
@@ -518,15 +516,6 @@ private boolean sendWants(Collection<Ref> want) throws IOException {
 			}
 			line.append('\n');
 			p.writeString(line.toString());
-			if (minimalNegotiationSet != null) {
-				Ref current = local.exactRef(r.getName());
-				if (current != null) {
-					ObjectId o = current.getObjectId();
-					if (o != null && !o.equals(ObjectId.zeroId())) {
-						minimalNegotiationSet.add(o);
-					}
-				}
-			}
 		}
 		if (first) {
 			return false;
@@ -610,9 +599,6 @@ private void negotiate(ProgressMonitor monitor) throws IOException,
 			pckOut.writeString("have " + o.name() + "\n"); //$NON-NLS-1$ //$NON-NLS-2$
 			havesSent++;
 			havesSinceLastContinue++;
-			if (minimalNegotiationSet != null) {
-				minimalNegotiationSet.remove(o);
-			}
 
 			if ((31 & havesSent) != 0) {
 				// We group the have lines into blocks of 32, each marked
@@ -646,16 +632,6 @@ private void negotiate(ProgressMonitor monitor) throws IOException,
 					// pack on the remote side. Keep doing that.
 					//
 					resultsPending--;
-					if (minimalNegotiationSet != null
-							&& minimalNegotiationSet.isEmpty()) {
-						// Minimal negotiation was requested and we sent out our
-						// current reference values for our wants, so terminate
-						// negotiation early.
-						if (statelessRPC) {
-							state.writeTo(out, null);
-						}
-						break SEND_HAVES;
-					}
 					break READ_RESULT;
 
 				case ACK:
@@ -686,14 +662,6 @@ private void negotiate(ProgressMonitor monitor) throws IOException,
 					if (anr == AckNackResult.ACK_READY) {
 						receivedReady = true;
 					}
-					if (minimalNegotiationSet != null && minimalNegotiationSet.isEmpty()) {
-						// Minimal negotiation was requested and we sent out our current reference
-						// values for our wants, so terminate negotiation early.
-						if (statelessRPC) {
-							state.writeTo(out, null);
-						}
-						break SEND_HAVES;
-					}
 					break;
 				}
 
@@ -709,7 +677,8 @@ private void negotiate(ProgressMonitor monitor) throws IOException,
 				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
 				// recognize any of them. Avoid sending our entire history
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 449f529..4b20f6c 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleFetchConnection.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleFetchConnection.java
@@ -47,6 +47,8 @@
 
 package org.eclipse.jgit.transport;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
+
 import java.io.BufferedInputStream;
 import java.io.IOException;
 import java.io.InputStream;
@@ -66,7 +68,6 @@
 import org.eclipse.jgit.errors.TransportException;
 import org.eclipse.jgit.internal.JGitText;
 import org.eclipse.jgit.internal.storage.file.PackLock;
-import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.NullProgressMonitor;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.ObjectIdRef;
@@ -173,7 +174,7 @@ private String readLine(byte[] hdrbuf) throws IOException {
 				IO.skipFully(bin, 1);
 				done = true;
 			}
-			line.append(RawParseUtils.decode(Constants.CHARSET, hdrbuf, 0, lf));
+			line.append(RawParseUtils.decode(UTF_8, hdrbuf, 0, lf));
 		}
 		return line.toString();
 	}
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 f2a261b..56aaede 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleWriter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleWriter.java
@@ -43,6 +43,8 @@
 
 package org.eclipse.jgit.transport;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
+
 import java.io.IOException;
 import java.io.OutputStream;
 import java.io.OutputStreamWriter;
@@ -231,7 +233,7 @@ public void writeBundle(ProgressMonitor monitor, OutputStream os)
 				packWriter.setTagTargets(tagTargets);
 			packWriter.preparePack(monitor, inc, exc);
 
-			final Writer w = new OutputStreamWriter(os, Constants.CHARSET);
+			final Writer w = new OutputStreamWriter(os, UTF_8);
 			w.write(TransportBundle.V2_BUNDLE_SIGNATURE);
 			w.write('\n');
 
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/CapabilitiesV2Request.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/CapabilitiesV2Request.java
new file mode 100644
index 0000000..b133ab5
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/CapabilitiesV2Request.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2018, Google LLC.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ *   copyright notice, this list of conditions and the following
+ *   disclaimer in the documentation and/or other materials provided
+ *   with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ *   names of its contributors may be used to endorse or promote
+ *   products derived from this software without specific prior
+ *   written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.eclipse.jgit.transport;
+
+/**
+ * Capabilities protocol v2 request.
+ *
+ * <p>
+ * This is used as an input to {@link ProtocolV2Hook}.
+ *
+ * @since 5.1
+ */
+public final class CapabilitiesV2Request {
+	private CapabilitiesV2Request() {
+	}
+
+	/** @return A builder of {@link CapabilitiesV2Request}. */
+	public static Builder builder() {
+		return new Builder();
+	}
+
+	/** A builder for {@link CapabilitiesV2Request}. */
+	public static final class Builder {
+		private Builder() {
+		}
+
+		/** @return CapabilitiesV2Request */
+		public CapabilitiesV2Request build() {
+			return new CapabilitiesV2Request();
+		}
+	}
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/CredentialsProvider.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/CredentialsProvider.java
index 8115946..f7a295d 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/CredentialsProvider.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/CredentialsProvider.java
@@ -155,7 +155,7 @@ public abstract boolean get(URIish uri, CredentialItem... items)
 	 */
 	public boolean get(URIish uri, List<CredentialItem> items)
 			throws UnsupportedCredentialItem {
-		return get(uri, items.toArray(new CredentialItem[items.size()]));
+		return get(uri, items.toArray(new CredentialItem[0]));
 	}
 
 	/**
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchV2Request.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchV2Request.java
new file mode 100644
index 0000000..34f3484
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchV2Request.java
@@ -0,0 +1,352 @@
+/*
+ * Copyright (C) 2018, Google LLC.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ *   copyright notice, this list of conditions and the following
+ *   disclaimer in the documentation and/or other materials provided
+ *   with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ *   names of its contributors may be used to endorse or promote
+ *   products derived from this software without specific prior
+ *   written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.eclipse.jgit.transport;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.eclipse.jgit.annotations.NonNull;
+import org.eclipse.jgit.lib.ObjectId;
+
+/**
+ * fetch protocol v2 request.
+ *
+ * <p>
+ * This is used as an input to {@link ProtocolV2Hook}.
+ *
+ * @since 5.1
+ */
+public final class FetchV2Request {
+	private final List<ObjectId> peerHas;
+
+	private final List<String> wantedRefs;
+
+	private final Set<ObjectId> wantsIds;
+
+	private final Set<ObjectId> clientShallowCommits;
+
+	private final int deepenSince;
+
+	private final List<String> deepenNotRefs;
+
+	private final int depth;
+
+	private final long filterBlobLimit;
+
+	private final Set<String> options;
+
+	private final boolean doneReceived;
+
+	private FetchV2Request(List<ObjectId> peerHas,
+			List<String> wantedRefs, Set<ObjectId> wantsIds,
+			Set<ObjectId> clientShallowCommits, int deepenSince,
+			List<String> deepenNotRefs, int depth, long filterBlobLimit,
+			boolean doneReceived, Set<String> options) {
+		this.peerHas = peerHas;
+		this.wantedRefs = wantedRefs;
+		this.wantsIds = wantsIds;
+		this.clientShallowCommits = clientShallowCommits;
+		this.deepenSince = deepenSince;
+		this.deepenNotRefs = deepenNotRefs;
+		this.depth = depth;
+		this.filterBlobLimit = filterBlobLimit;
+		this.doneReceived = doneReceived;
+		this.options = options;
+	}
+
+	/**
+	 * @return object ids in the "have" lines of the request
+	 */
+	@NonNull
+	List<ObjectId> getPeerHas() {
+		return this.peerHas;
+	}
+
+	/**
+	 * @return list of references in the "want-ref" lines of the request
+	 */
+	@NonNull
+	List<String> getWantedRefs() {
+		return wantedRefs;
+	}
+
+	/**
+	 * @return object ids in the "want" (but not "want-ref") lines of the request
+	 */
+	@NonNull
+	Set<ObjectId> getWantsIds() {
+		return wantsIds;
+	}
+
+	/**
+	 * Shallow commits the client already has.
+	 *
+	 * These are sent by the client in "shallow" request lines.
+	 *
+	 * @return set of commits the client has declared as shallow.
+	 */
+	@NonNull
+	Set<ObjectId> getClientShallowCommits() {
+		return clientShallowCommits;
+	}
+
+	/**
+	 * The value in a "deepen-since" line in the request, indicating the
+	 * timestamp where to stop fetching/cloning.
+	 *
+	 * @return timestamp in seconds since the epoch, where to stop the shallow
+	 *         fetch/clone. Defaults to 0 if not set in the request.
+	 */
+	int getDeepenSince() {
+		return deepenSince;
+	}
+
+	/**
+	 * @return the refs in "deepen-not" lines in the request.
+	 */
+	@NonNull
+	List<String> getDeepenNotRefs() {
+		return deepenNotRefs;
+	}
+
+	/**
+	 * @return the depth set in a "deepen" line. 0 by default.
+	 */
+	int getDepth() {
+		return depth;
+	}
+
+	/**
+	 * @return the blob limit set in a "filter" line (-1 if not set)
+	 */
+	long getFilterBlobLimit() {
+		return filterBlobLimit;
+	}
+
+	/**
+	 * @return true if the request had a "done" line
+	 */
+	boolean wasDoneReceived() {
+		return doneReceived;
+	}
+
+	/**
+	 * Options that tune the expected response from the server, like
+	 * "thin-pack", "no-progress" or "ofs-delta"
+	 *
+	 * These are options listed and well-defined in the git protocol
+	 * specification
+	 *
+	 * @return options found in the request lines
+	 */
+	@NonNull
+	Set<String> getOptions() {
+		return options;
+	}
+
+	/** @return A builder of {@link FetchV2Request}. */
+	static Builder builder() {
+		return new Builder();
+	}
+
+
+	/** A builder for {@link FetchV2Request}. */
+	static final class Builder {
+		List<ObjectId> peerHas = new ArrayList<>();
+
+		List<String> wantedRefs = new ArrayList<>();
+
+		Set<ObjectId> wantsIds = new HashSet<>();
+
+		Set<ObjectId> clientShallowCommits = new HashSet<>();
+
+		List<String> deepenNotRefs = new ArrayList<>();
+
+		Set<String> options = new HashSet<>();
+
+		int depth;
+
+		int deepenSince;
+
+		long filterBlobLimit = -1;
+
+		boolean doneReceived;
+
+		private Builder() {
+		}
+
+		/**
+		 * @param objectId
+		 *            from a "have" line in a fetch request
+		 * @return the builder
+		 */
+		Builder addPeerHas(ObjectId objectId) {
+			peerHas.add(objectId);
+			return this;
+		}
+
+		/**
+		 * From a "want-ref" line in a fetch request
+		 *
+		 * @param refName
+		 *            reference name
+		 * @return the builder
+		 */
+		Builder addWantedRef(String refName) {
+			wantedRefs.add(refName);
+			return this;
+		}
+
+		/**
+		 * @param option
+		 *            fetch request lines acting as options
+		 * @return the builder
+		 */
+		Builder addOption(String option) {
+			options.add(option);
+			return this;
+		}
+
+		/**
+		 * @param objectId
+		 *            from a "want" line in a fetch request
+		 * @return the builder
+		 */
+		Builder addWantsId(ObjectId objectId) {
+			wantsIds.add(objectId);
+			return this;
+		}
+
+		/**
+		 * @param shallowOid
+		 *            from a "shallow" line in the fetch request
+		 * @return the builder
+		 */
+		Builder addClientShallowCommit(ObjectId shallowOid) {
+			this.clientShallowCommits.add(shallowOid);
+			return this;
+		}
+
+		/**
+		 * @param d
+		 *            from a "deepen" line in the fetch request
+		 * @return the builder
+		 */
+		Builder setDepth(int d) {
+			this.depth = d;
+			return this;
+		}
+
+		/**
+		 * @return depth set in the request (via a "deepen" line). Defaulting to
+		 *         0 if not set.
+		 */
+		int getDepth() {
+			return this.depth;
+		}
+
+		/**
+		 * @return if there has been any "deepen not" line in the request
+		 */
+		boolean hasDeepenNotRefs() {
+			return !deepenNotRefs.isEmpty();
+		}
+
+		/**
+		 * @param deepenNotRef reference in a "deepen not" line
+		 * @return the builder
+		 */
+		Builder addDeepenNotRef(String deepenNotRef) {
+			this.deepenNotRefs.add(deepenNotRef);
+			return this;
+		}
+
+		/**
+		 * @param value
+		 *            Unix timestamp received in a "deepen since" line
+		 * @return the builder
+		 */
+		Builder setDeepenSince(int value) {
+			this.deepenSince = value;
+			return this;
+		}
+
+		/**
+		 * @return shallow since value, sent before in a "deepen since" line. 0
+		 *         by default.
+		 */
+		int getDeepenSince() {
+			return this.deepenSince;
+		}
+
+		/**
+		 * @param filterBlobLimit
+		 *            set in a "filter" line
+		 * @return the builder
+		 */
+		Builder setFilterBlobLimit(long filterBlobLimit) {
+			this.filterBlobLimit = filterBlobLimit;
+			return this;
+		}
+
+		/**
+		 * Mark that the "done" line has been received.
+		 *
+		 * @return the builder
+		 */
+		Builder setDoneReceived() {
+			this.doneReceived = true;
+			return this;
+		}
+		/**
+		 * @return Initialized fetch request
+		 */
+		FetchV2Request build() {
+			return new FetchV2Request(peerHas, wantedRefs, wantsIds,
+					clientShallowCommits, deepenSince, deepenNotRefs,
+					depth, filterBlobLimit, doneReceived, options);
+		}
+	}
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/GitProtocolConstants.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/GitProtocolConstants.java
index 10cd775..760ac6c 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/GitProtocolConstants.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/GitProtocolConstants.java
@@ -50,7 +50,7 @@
  *
  * @since 3.2
  */
-public class GitProtocolConstants {
+public final class GitProtocolConstants {
 	/**
 	 * Include tags if we are also including the referenced objects.
 	 *
@@ -167,6 +167,13 @@ public class GitProtocolConstants {
 	public static final String OPTION_FILTER = "filter"; //$NON-NLS-1$
 
 	/**
+	 * The client specified a want-ref expression.
+	 *
+	 * @since 5.1
+	 */
+	public static final String OPTION_WANT_REF = "want-ref"; //$NON-NLS-1$
+
+	/**
 	 * The client supports atomic pushes. If this option is used, the server
 	 * will update all refs within one atomic transaction.
 	 *
@@ -231,6 +238,13 @@ public class GitProtocolConstants {
 	public static final String CAPABILITY_PUSH_OPTIONS = "push-options"; //$NON-NLS-1$
 
 	/**
+	 * The server supports the client specifying ref names.
+	 *
+	 * @since 5.1
+	 */
+	public static final String CAPABILITY_REF_IN_WANT = "ref-in-want"; //$NON-NLS-1$
+
+	/**
 	 * The server supports listing refs using protocol v2.
 	 *
 	 * @since 5.0
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/HMACSHA1NonceGenerator.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/HMACSHA1NonceGenerator.java
index 1415334..6c26b70 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/HMACSHA1NonceGenerator.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/HMACSHA1NonceGenerator.java
@@ -43,7 +43,7 @@
 package org.eclipse.jgit.transport;
 
 import static java.nio.charset.StandardCharsets.ISO_8859_1;
-import static org.eclipse.jgit.lib.Constants.CHARSET;
+import static java.nio.charset.StandardCharsets.UTF_8;
 
 import java.io.File;
 import java.security.InvalidKeyException;
@@ -102,7 +102,7 @@ public synchronized String createNonce(Repository repo, long timestamp)
 		}
 
 		String input = path + ":" + String.valueOf(timestamp); //$NON-NLS-1$
-		byte[] rawHmac = mac.doFinal(input.getBytes(CHARSET));
+		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/HttpAuthMethod.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/HttpAuthMethod.java
index fb03190..69745eb 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/HttpAuthMethod.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/HttpAuthMethod.java
@@ -43,7 +43,7 @@
 
 package org.eclipse.jgit.transport;
 
-import static org.eclipse.jgit.lib.Constants.CHARSET;
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.eclipse.jgit.util.HttpSupport.HDR_AUTHORIZATION;
 import static org.eclipse.jgit.util.HttpSupport.HDR_WWW_AUTHENTICATE;
 
@@ -315,7 +315,7 @@ void authorize(String username, String password) {
 		@Override
 		void configureRequest(HttpConnection conn) throws IOException {
 			String ident = user + ":" + pass; //$NON-NLS-1$
-			String enc = Base64.encodeBytes(ident.getBytes(CHARSET));
+			String enc = Base64.encodeBytes(ident.getBytes(UTF_8));
 			conn.setRequestProperty(HDR_AUTHORIZATION, type.getSchemeName()
 					+ " " + enc); //$NON-NLS-1$
 		}
@@ -430,15 +430,15 @@ private static String uri(URL u) {
 
 		private static String H(String data) {
 			MessageDigest md = newMD5();
-			md.update(data.getBytes(CHARSET));
+			md.update(data.getBytes(UTF_8));
 			return LHEX(md.digest());
 		}
 
 		private static String KD(String secret, String data) {
 			MessageDigest md = newMD5();
-			md.update(secret.getBytes(CHARSET));
+			md.update(secret.getBytes(UTF_8));
 			md.update((byte) ':');
-			md.update(data.getBytes(CHARSET));
+			md.update(data.getBytes(UTF_8));
 			return LHEX(md.digest());
 		}
 
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 101ce35..ce9e1b3 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/HttpConfig.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/HttpConfig.java
@@ -53,8 +53,7 @@
 import org.eclipse.jgit.errors.ConfigInvalidException;
 import org.eclipse.jgit.internal.JGitText;
 import org.eclipse.jgit.lib.Config;
-import org.eclipse.jgit.storage.file.FileBasedConfig;
-import org.eclipse.jgit.util.FS;
+import org.eclipse.jgit.lib.StoredConfig;
 import org.eclipse.jgit.util.StringUtils;
 import org.eclipse.jgit.util.SystemReader;
 import org.slf4j.Logger;
@@ -210,14 +209,12 @@ public HttpConfig(Config config, URIish uri) {
 	 *            to get the configuration values for
 	 */
 	public HttpConfig(URIish uri) {
-		FileBasedConfig userConfig = SystemReader.getInstance()
-				.openUserConfig(null, FS.DETECTED);
+		StoredConfig userConfig = null;
 		try {
-			userConfig.load();
+			userConfig = SystemReader.getInstance().getUserConfig();
 		} catch (IOException | ConfigInvalidException e) {
 			// Log it and then work with default values.
-			LOG.error(MessageFormat.format(JGitText.get().userConfigFileInvalid,
-					userConfig.getFile().getAbsolutePath(), e));
+			LOG.error(e.getMessage(), e);
 			init(new Config(), uri);
 			return;
 		}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/JschConfigSessionFactory.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/JschConfigSessionFactory.java
index eab3b3c..4e712a5 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/JschConfigSessionFactory.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/JschConfigSessionFactory.java
@@ -1,4 +1,5 @@
 /*
+ * Copyright (C) 2018, Sasa Zivkov <sasa.zivkov@sap.com>
  * Copyright (C) 2016, Mark Ingram <markdingram@gmail.com>
  * Copyright (C) 2009, Constantine Plotnikov <constantine.plotnikov@gmail.com>
  * Copyright (C) 2008-2009, Google Inc.
@@ -49,6 +50,10 @@
 
 package org.eclipse.jgit.transport;
 
+import static java.util.stream.Collectors.joining;
+import static java.util.stream.Collectors.toList;
+import static org.eclipse.jgit.transport.OpenSshConfig.SSH_PORT;
+
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
@@ -59,9 +64,11 @@
 import java.net.UnknownHostException;
 import java.text.MessageFormat;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Locale;
 import java.util.Map;
 import java.util.concurrent.TimeUnit;
+import java.util.stream.Stream;
 
 import org.eclipse.jgit.errors.TransportException;
 import org.eclipse.jgit.internal.JGitText;
@@ -71,6 +78,8 @@
 
 import com.jcraft.jsch.ConfigRepository;
 import com.jcraft.jsch.ConfigRepository.Config;
+import com.jcraft.jsch.HostKey;
+import com.jcraft.jsch.HostKeyRepository;
 import com.jcraft.jsch.JSch;
 import com.jcraft.jsch.JSchException;
 import com.jcraft.jsch.Session;
@@ -224,6 +233,9 @@ Session createSession(CredentialsProvider credentialsProvider,
 					credentialsProvider));
 		}
 		safeConfig(session, hc.getConfig());
+		if (hc.getConfig().getValue("HostKeyAlgorithms") == null) { //$NON-NLS-1$
+			setPreferredKeyTypesOrder(session);
+		}
 		configure(hc, session);
 		return session;
 	}
@@ -239,6 +251,36 @@ private void safeConfig(Session session, Config cfg) {
 				"CheckSignatures"); //$NON-NLS-1$
 	}
 
+	private static void setPreferredKeyTypesOrder(Session session) {
+		HostKeyRepository hkr = session.getHostKeyRepository();
+		List<String> known = Stream.of(hkr.getHostKey(hostName(session), null))
+				.map(HostKey::getType)
+				.collect(toList());
+
+		if (!known.isEmpty()) {
+			String serverHostKey = "server_host_key"; //$NON-NLS-1$
+			String current = session.getConfig(serverHostKey);
+			if (current == null) {
+				session.setConfig(serverHostKey, String.join(",", known)); //$NON-NLS-1$
+				return;
+			}
+
+			String knownFirst = Stream.concat(
+							known.stream(),
+							Stream.of(current.split(",")) //$NON-NLS-1$
+									.filter(s -> !known.contains(s)))
+					.collect(joining(",")); //$NON-NLS-1$
+			session.setConfig(serverHostKey, knownFirst);
+		}
+	}
+
+	private static String hostName(Session s) {
+		if (s.getPort() == SSH_PORT) {
+			return s.getHost();
+		}
+		return String.format("[%s]:%d", s.getHost(), s.getPort()); //$NON-NLS-1$
+	}
+
 	private void copyConfigValueToSession(Session session, Config cfg,
 			String from, String to) {
 		String value = cfg.getValue(from);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/LsRefsV2Request.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/LsRefsV2Request.java
new file mode 100644
index 0000000..3aff584
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/LsRefsV2Request.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2018, Google LLC.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ *   copyright notice, this list of conditions and the following
+ *   disclaimer in the documentation and/or other materials provided
+ *   with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ *   names of its contributors may be used to endorse or promote
+ *   products derived from this software without specific prior
+ *   written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.eclipse.jgit.transport;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * ls-refs protocol v2 request.
+ *
+ * <p>
+ * This is used as an input to {@link ProtocolV2Hook}.
+ *
+ * @since 5.1
+ */
+public final class LsRefsV2Request {
+	private final List<String> refPrefixes;
+
+	private final boolean symrefs;
+
+	private final boolean peel;
+
+	private LsRefsV2Request(List<String> refPrefixes, boolean symrefs,
+			boolean peel) {
+		this.refPrefixes = refPrefixes;
+		this.symrefs = symrefs;
+		this.peel = peel;
+	}
+
+	/** @return ref prefixes that the client requested. */
+	public List<String> getRefPrefixes() {
+		return refPrefixes;
+	}
+
+	/** @return true if the client requests symbolic references. */
+	public boolean getSymrefs() {
+		return symrefs;
+	}
+
+	/** @return true if the client requests tags to be peeled. */
+	public boolean getPeel() {
+		return peel;
+	}
+
+	/** @return A builder of {@link LsRefsV2Request}. */
+	public static Builder builder() {
+		return new Builder();
+	}
+
+	/** A builder for {@link LsRefsV2Request}. */
+	public static final class Builder {
+		private List<String> refPrefixes = Collections.emptyList();
+
+		private boolean symrefs;
+
+		private boolean peel;
+
+		private Builder() {
+		}
+
+		/**
+		 * @param value
+		 * @return the Builder
+		 */
+		public Builder setRefPrefixes(List<String> value) {
+			refPrefixes = value;
+			return this;
+		}
+
+		/**
+		 * @param value
+		 * @return the Builder
+		 */
+		public Builder setSymrefs(boolean value) {
+			symrefs = value;
+			return this;
+		}
+
+		/**
+		 * @param value
+		 * @return the Builder
+		 */
+		public Builder setPeel(boolean value) {
+			peel = value;
+			return this;
+		}
+
+		/** @return LsRefsV2Request */
+		public LsRefsV2Request build() {
+			return new LsRefsV2Request(
+					Collections.unmodifiableList(refPrefixes), symrefs, peel);
+		}
+	}
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/NetRC.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/NetRC.java
index e688f63..4f1eba6 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/NetRC.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/NetRC.java
@@ -46,6 +46,7 @@
 import java.io.File;
 import java.io.FileReader;
 import java.io.IOException;
+import java.time.Instant;
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.Locale;
@@ -123,7 +124,7 @@ boolean complete() {
 
 	private File netrc;
 
-	private long lastModified;
+	private Instant lastModified;
 
 	private Map<String, NetRCEntry> hosts = new HashMap<>();
 
@@ -187,8 +188,10 @@ public NetRCEntry getEntry(String host) {
 		if (netrc == null)
 			return null;
 
-		if (this.lastModified != this.netrc.lastModified())
+		if (!this.lastModified
+				.equals(FS.DETECTED.lastModifiedInstant(this.netrc))) {
 			parse();
+		}
 
 		NetRCEntry entry = this.hosts.get(host);
 
@@ -209,7 +212,7 @@ public Collection<NetRCEntry> getEntries() {
 
 	private void parse() {
 		this.hosts.clear();
-		this.lastModified = this.netrc.lastModified();
+		this.lastModified = FS.DETECTED.lastModifiedInstant(this.netrc);
 
 		try (BufferedReader r = new BufferedReader(new FileReader(netrc))) {
 			String line = null;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/OpenSshConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/OpenSshConfig.java
index f5ccdc8..4dd5df9 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/OpenSshConfig.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/OpenSshConfig.java
@@ -51,6 +51,7 @@
 import java.io.InputStreamReader;
 import java.security.AccessController;
 import java.security.PrivilegedAction;
+import java.time.Instant;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -166,7 +167,7 @@ public static OpenSshConfig get(FS fs) {
 	private final File configFile;
 
 	/** Modification time of {@link #configFile} when it was last loaded. */
-	private long lastModified;
+	private Instant lastModified;
 
 	/**
 	 * Encapsulates entries read out of the configuration file, and
@@ -224,8 +225,8 @@ public Host lookup(String hostName) {
 	}
 
 	private synchronized State refresh() {
-		final long mtime = configFile.lastModified();
-		if (mtime != lastModified) {
+		final Instant mtime = FS.DETECTED.lastModifiedInstant(configFile);
+		if (!mtime.equals(lastModified)) {
 			State newState = new State();
 			try (FileInputStream in = new FileInputStream(configFile)) {
 				newState.entries = parse(in);
@@ -524,7 +525,7 @@ public String getValue(String key) {
 			if (values == null || values.isEmpty()) {
 				return new String[0];
 			}
-			return values.toArray(new String[values.size()]);
+			return values.toArray(new String[0]);
 		}
 
 		public void setValue(String key, String value) {
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 cc556f8..c6e19d5 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PacketLineIn.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PacketLineIn.java
@@ -45,13 +45,14 @@
 
 package org.eclipse.jgit.transport;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
+
 import java.io.IOException;
 import java.io.InputStream;
 import java.text.MessageFormat;
 
 import org.eclipse.jgit.errors.PackProtocolException;
 import org.eclipse.jgit.internal.JGitText;
-import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.MutableObjectId;
 import org.eclipse.jgit.util.IO;
 import org.eclipse.jgit.util.RawParseUtils;
@@ -186,7 +187,7 @@ public String readString() throws IOException {
 		if (raw[len - 1] == '\n')
 			len--;
 
-		String s = RawParseUtils.decode(Constants.CHARSET, raw, 0, len);
+		String s = RawParseUtils.decode(UTF_8, raw, 0, len);
 		log.debug("git< " + s); //$NON-NLS-1$
 		return s;
 	}
@@ -218,7 +219,7 @@ public String readStringRaw() throws IOException {
 
 		IO.readFully(in, raw, 0, len);
 
-		String s = RawParseUtils.decode(Constants.CHARSET, raw, 0, len);
+		String s = RawParseUtils.decode(UTF_8, raw, 0, len);
 		log.debug("git< " + s); //$NON-NLS-1$
 		return s;
 	}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PacketLineOut.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PacketLineOut.java
index a26d1d7..e940091 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PacketLineOut.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PacketLineOut.java
@@ -45,6 +45,8 @@
 
 package org.eclipse.jgit.transport;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
+
 import java.io.IOException;
 import java.io.OutputStream;
 
@@ -141,7 +143,7 @@ public void writePacket(byte[] buf, int pos, int len) throws IOException {
 		out.write(lenbuffer, 0, 4);
 		out.write(buf, pos, len);
 		if (log.isDebugEnabled()) {
-			String s = RawParseUtils.decode(Constants.CHARSET, buf, pos, len);
+			String s = RawParseUtils.decode(UTF_8, buf, pos, len);
 			log.debug("git> " + s); //$NON-NLS-1$
 		}
 	}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ProgressSpinner.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ProgressSpinner.java
index 2364434..41af807 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ProgressSpinner.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ProgressSpinner.java
@@ -43,7 +43,7 @@
 
 package org.eclipse.jgit.transport;
 
-import static org.eclipse.jgit.lib.Constants.CHARSET;
+import static java.nio.charset.StandardCharsets.UTF_8;
 
 import java.io.IOException;
 import java.io.OutputStream;
@@ -141,7 +141,7 @@ public void endTask(String result) {
 	private void write(String s) {
 		if (write) {
 			try {
-				out.write(s.getBytes(CHARSET));
+				out.write(s.getBytes(UTF_8));
 				out.flush();
 			} catch (IOException e) {
 				write = false;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ProtocolV2Hook.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ProtocolV2Hook.java
new file mode 100644
index 0000000..d67b90b
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ProtocolV2Hook.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2018, Google LLC.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ *   copyright notice, this list of conditions and the following
+ *   disclaimer in the documentation and/or other materials provided
+ *   with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ *   names of its contributors may be used to endorse or promote
+ *   products derived from this software without specific prior
+ *   written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.eclipse.jgit.transport;
+
+/**
+ * Hook to allow callers to be notified on Git protocol v2 requests.
+ *
+ * @see UploadPack#setProtocolV2Hook(ProtocolV2Hook)
+ * @since 5.1
+ */
+public interface ProtocolV2Hook {
+	/**
+	 * The default hook implementation that does nothing.
+	 */
+	static ProtocolV2Hook DEFAULT = new ProtocolV2Hook() {
+		// No override.
+	};
+
+	/**
+	 * @param req
+	 *            the capabilities request
+	 * @throws ServiceMayNotContinueException
+	 *             abort; the message will be sent to the user
+	 * @since 5.1
+	 */
+	default void onCapabilities(CapabilitiesV2Request req)
+			throws ServiceMayNotContinueException {
+		// Do nothing by default.
+	}
+
+	/**
+	 * @param req
+	 *            the ls-refs request
+	 * @throws ServiceMayNotContinueException
+	 *             abort; the message will be sent to the user
+	 * @since 5.1
+	 */
+	default void onLsRefs(LsRefsV2Request req)
+			throws ServiceMayNotContinueException {
+		// Do nothing by default.
+	}
+
+	/**
+	 * @param req the fetch request
+	 * @throws ServiceMayNotContinueException abort; the message will be sent to the user
+	 */
+	default void onFetch(FetchV2Request req)
+			throws ServiceMayNotContinueException {
+		// Do nothing by default
+	}
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ProtocolV2Parser.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ProtocolV2Parser.java
new file mode 100644
index 0000000..2cc50a7
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ProtocolV2Parser.java
@@ -0,0 +1,220 @@
+/*
+ * Copyright (C) 2018, Google LLC.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ *   copyright notice, this list of conditions and the following
+ *   disclaimer in the documentation and/or other materials provided
+ *   with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ *   names of its contributors may be used to endorse or promote
+ *   products derived from this software without specific prior
+ *   written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.eclipse.jgit.transport;
+
+import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_DEEPEN_RELATIVE;
+import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_FILTER;
+import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_INCLUDE_TAG;
+import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_NO_PROGRESS;
+import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_OFS_DELTA;
+import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_SIDE_BAND_64K;
+import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_THIN_PACK;
+import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_WANT_REF;
+
+import java.io.IOException;
+import java.text.MessageFormat;
+
+import org.eclipse.jgit.errors.PackProtocolException;
+import org.eclipse.jgit.internal.JGitText;
+import org.eclipse.jgit.lib.ObjectId;
+
+/**
+ * Parse the incoming git protocol lines from the wire and translate them into a
+ * Request object.
+ *
+ * It requires a transferConfig object to know what the server supports (e.g.
+ * ref-in-want and/or filters).
+ */
+final class ProtocolV2Parser {
+
+	private final TransferConfig transferConfig;
+
+	ProtocolV2Parser(TransferConfig transferConfig) {
+		this.transferConfig = transferConfig;
+	}
+
+	/**
+	 * Parse the incoming fetch request arguments from the wire. The caller must
+	 * be sure that what is comings is a fetch request before coming here.
+	 *
+	 * @param pckIn
+	 *            incoming lines
+	 * @return A FetchV2Request populated with information received from the
+	 *         wire.
+	 * @throws PackProtocolException
+	 *             incompatible options, wrong type of arguments or other issues
+	 *             where the request breaks the protocol.
+	 * @throws IOException
+	 *             an IO error prevented reading the incoming message.
+	 */
+	FetchV2Request parseFetchRequest(PacketLineIn pckIn)
+			throws PackProtocolException, IOException {
+		FetchV2Request.Builder reqBuilder = FetchV2Request.builder();
+
+		// Packs are always sent multiplexed and using full 64K
+		// lengths.
+		reqBuilder.addOption(OPTION_SIDE_BAND_64K);
+
+		String line;
+
+		// Currently, we do not support any capabilities, so the next
+		// line is DELIM.
+		if ((line = pckIn.readString()) != PacketLineIn.DELIM) {
+			throw new PackProtocolException(MessageFormat
+					.format(JGitText.get().unexpectedPacketLine, line));
+		}
+
+		boolean filterReceived = false;
+		while ((line = pckIn.readString()) != PacketLineIn.END) {
+			if (line.startsWith("want ")) { //$NON-NLS-1$
+				reqBuilder.addWantsId(ObjectId.fromString(line.substring(5)));
+			} else if (transferConfig.isAllowRefInWant()
+					&& line.startsWith(OPTION_WANT_REF + " ")) { //$NON-NLS-1$
+				reqBuilder.addWantedRef(line.substring(OPTION_WANT_REF.length() + 1));
+			} else if (line.startsWith("have ")) { //$NON-NLS-1$
+				reqBuilder.addPeerHas(ObjectId.fromString(line.substring(5)));
+			} else if (line.equals("done")) { //$NON-NLS-1$
+				reqBuilder.setDoneReceived();
+			} else if (line.equals(OPTION_THIN_PACK)) {
+				reqBuilder.addOption(OPTION_THIN_PACK);
+			} else if (line.equals(OPTION_NO_PROGRESS)) {
+				reqBuilder.addOption(OPTION_NO_PROGRESS);
+			} else if (line.equals(OPTION_INCLUDE_TAG)) {
+				reqBuilder.addOption(OPTION_INCLUDE_TAG);
+			} else if (line.equals(OPTION_OFS_DELTA)) {
+				reqBuilder.addOption(OPTION_OFS_DELTA);
+			} else if (line.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));
+				if (parsedDepth <= 0) {
+					throw new PackProtocolException(
+							MessageFormat.format(JGitText.get().invalidDepth,
+									Integer.valueOf(parsedDepth)));
+				}
+				if (reqBuilder.getDeepenSince() != 0) {
+					throw new PackProtocolException(
+							JGitText.get().deepenSinceWithDeepen);
+				}
+				if (reqBuilder.hasDeepenNotRefs()) {
+					throw new PackProtocolException(
+							JGitText.get().deepenNotWithDeepen);
+				}
+				reqBuilder.setDepth(parsedDepth);
+			} else if (line.startsWith("deepen-not ")) { //$NON-NLS-1$
+				reqBuilder.addDeepenNotRef(line.substring(11));
+				if (reqBuilder.getDepth() != 0) {
+					throw new PackProtocolException(
+							JGitText.get().deepenNotWithDeepen);
+				}
+			} else if (line.equals(OPTION_DEEPEN_RELATIVE)) {
+				reqBuilder.addOption(OPTION_DEEPEN_RELATIVE);
+			} else if (line.startsWith("deepen-since ")) { //$NON-NLS-1$
+				int ts = Integer.parseInt(line.substring(13));
+				if (ts <= 0) {
+					throw new PackProtocolException(MessageFormat
+							.format(JGitText.get().invalidTimestamp, line));
+				}
+				if (reqBuilder.getDepth() != 0) {
+					throw new PackProtocolException(
+							JGitText.get().deepenSinceWithDeepen);
+				}
+				reqBuilder.setDeepenSince(ts);
+			} else if (transferConfig.isAllowFilter()
+					&& line.startsWith(OPTION_FILTER + ' ')) {
+				if (filterReceived) {
+					throw new PackProtocolException(
+							JGitText.get().tooManyFilters);
+				}
+				filterReceived = true;
+				reqBuilder.setFilterBlobLimit(filterLine(
+						line.substring(OPTION_FILTER.length() + 1)));
+			} else {
+				throw new PackProtocolException(MessageFormat
+						.format(JGitText.get().unexpectedPacketLine, line));
+			}
+		}
+
+		return reqBuilder.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/PushCertificateIdent.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushCertificateIdent.java
index 178c80d..f9fddbe 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushCertificateIdent.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushCertificateIdent.java
@@ -43,7 +43,7 @@
 
 package org.eclipse.jgit.transport;
 
-import static org.eclipse.jgit.lib.Constants.CHARSET;
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.eclipse.jgit.util.RawParseUtils.lastIndexOfTrim;
 
 import java.text.SimpleDateFormat;
@@ -95,7 +95,7 @@ public class PushCertificateIdent {
 	 */
 	public static PushCertificateIdent parse(String str) {
 		MutableInteger p = new MutableInteger();
-		byte[] raw = str.getBytes(CHARSET);
+		byte[] raw = str.getBytes(UTF_8);
 		int tzBegin = raw.length - 1;
 		tzBegin = lastIndexOfTrim(raw, ' ', tzBegin);
 		if (tzBegin < 0 || raw[tzBegin] != ' ') {
@@ -129,7 +129,7 @@ public static PushCertificateIdent parse(String str) {
 				idEnd = raw.length;
 			}
 		}
-		String id = new String(raw, 0, idEnd, CHARSET);
+		String id = new String(raw, 0, idEnd, UTF_8);
 
 		return new PushCertificateIdent(str, id, when * 1000L, tz);
 	}
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 7f5a340..aeca635 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushCertificateStore.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushCertificateStore.java
@@ -43,7 +43,7 @@
 
 package org.eclipse.jgit.transport;
 
-import static org.eclipse.jgit.lib.Constants.CHARSET;
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
 import static org.eclipse.jgit.lib.Constants.OBJ_COMMIT;
 import static org.eclipse.jgit.lib.FileMode.TYPE_FILE;
@@ -292,7 +292,8 @@ static PushCertificate read(TreeWalk tw) throws IOException {
 		ObjectLoader loader =
 				tw.getObjectReader().open(tw.getObjectId(0), OBJ_BLOB);
 		try (InputStream in = loader.openStream();
-				Reader r = new BufferedReader(new InputStreamReader(in, CHARSET))) {
+				Reader r = new BufferedReader(
+						new InputStreamReader(in, UTF_8))) {
 			return PushCertificateParser.fromReader(r);
 		}
 	}
@@ -473,7 +474,7 @@ private ObjectId saveCert(ObjectInserter inserter, DirCache dc,
 
 		DirCacheEditor editor = dc.editor();
 		String certText = pc.cert.toText() + pc.cert.getSignature();
-		final ObjectId certId = inserter.insert(OBJ_BLOB, certText.getBytes(CHARSET));
+		final ObjectId certId = inserter.insert(OBJ_BLOB, certText.getBytes(UTF_8));
 		boolean any = false;
 		for (ReceiveCommand cmd : pc.cert.getCommands()) {
 			if (byRef != null && !commandsEqual(cmd, byRef.get(cmd.getRefName()))) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefAdvertiser.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefAdvertiser.java
index dc1871b..4662435 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefAdvertiser.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefAdvertiser.java
@@ -43,7 +43,7 @@
 
 package org.eclipse.jgit.transport;
 
-import static org.eclipse.jgit.lib.Constants.CHARSET;
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.eclipse.jgit.lib.Constants.OBJECT_ID_STRING_LENGTH;
 import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_SYMREF;
 
@@ -74,7 +74,7 @@
 public abstract class RefAdvertiser {
 	/** Advertiser which frames lines in a {@link PacketLineOut} format. */
 	public static class PacketLineOutRefAdvertiser extends RefAdvertiser {
-		private final CharsetEncoder utf8 = CHARSET.newEncoder();
+		private final CharsetEncoder utf8 = UTF_8.newEncoder();
 		private final PacketLineOut pckOut;
 
 		private byte[] binArr = new byte[256];
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/SideBandInputStream.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/SideBandInputStream.java
index 3100cb4..90600cb 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/SideBandInputStream.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/SideBandInputStream.java
@@ -44,6 +44,7 @@
 
 package org.eclipse.jgit.transport;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.eclipse.jgit.transport.SideBandOutputStream.HDR_SIZE;
 
 import java.io.IOException;
@@ -57,7 +58,6 @@
 import org.eclipse.jgit.errors.PackProtocolException;
 import org.eclipse.jgit.errors.TransportException;
 import org.eclipse.jgit.internal.JGitText;
-import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.ProgressMonitor;
 import org.eclipse.jgit.util.IO;
 import org.eclipse.jgit.util.RawParseUtils;
@@ -257,6 +257,6 @@ private static String remote(String msg) {
 	private String readString(int len) throws IOException {
 		final byte[] raw = new byte[len];
 		IO.readFully(rawIn, raw, 0, len);
-		return RawParseUtils.decode(Constants.CHARSET, raw, 0, len);
+		return RawParseUtils.decode(UTF_8, raw, 0, len);
 	}
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransferConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransferConfig.java
index 4ae1ccb..db95396 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransferConfig.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransferConfig.java
@@ -126,18 +126,38 @@ enum ProtocolVersion {
 	private final boolean allowInvalidPersonIdent;
 	private final boolean safeForWindows;
 	private final boolean safeForMacOS;
+	private final boolean allowRefInWant;
 	private final boolean allowTipSha1InWant;
 	private final boolean allowReachableSha1InWant;
 	private final boolean allowFilter;
 	final @Nullable ProtocolVersion protocolVersion;
 	final String[] hideRefs;
 
-	TransferConfig(Repository db) {
+	/**
+	 * Create a configuration honoring the repository's settings.
+	 *
+	 * @param db
+	 *            the repository to read settings from. The repository is not
+	 *            retained by the new configuration, instead its settings are
+	 *            copied during the constructor.
+	 * @since 5.1.4
+	 */
+	public TransferConfig(Repository db) {
 		this(db.getConfig());
 	}
 
+	/**
+	 * Create a configuration honoring settings in a
+	 * {@link org.eclipse.jgit.lib.Config}.
+	 *
+	 * @param rc
+	 *            the source to read settings from. The source is not retained
+	 *            by the new configuration, instead its settings are copied
+	 *            during the constructor.
+	 * @since 5.1.4
+	 */
 	@SuppressWarnings("nls")
-	TransferConfig(Config rc) {
+	public TransferConfig(Config rc) {
 		boolean fsck = rc.getBoolean("transfer", "fsckobjects", false);
 		fetchFsck = rc.getBoolean("fetch", "fsckobjects", fsck);
 		receiveFsck = rc.getBoolean("receive", "fsckobjects", fsck);
@@ -180,6 +200,7 @@ enum ProtocolVersion {
 			ignore.add(ObjectChecker.ErrorType.ZERO_PADDED_FILEMODE);
 		}
 
+		allowRefInWant = rc.getBoolean("uploadpack", "allowrefinwant", false);
 		allowTipSha1InWant = rc.getBoolean(
 				"uploadpack", "allowtipsha1inwant", false);
 		allowReachableSha1InWant = rc.getBoolean(
@@ -262,6 +283,14 @@ public boolean isAllowFilter() {
 	}
 
 	/**
+	 * @return true if clients are allowed to specify a "want-ref" line
+	 * @since 5.1
+	 */
+	public boolean isAllowRefInWant() {
+		return allowRefInWant;
+	}
+
+	/**
 	 * Get {@link org.eclipse.jgit.transport.RefFilter} respecting configured
 	 * hidden refs.
 	 *
@@ -297,6 +326,16 @@ private boolean prefixMatch(String p, String s) {
 		};
 	}
 
+	/**
+	 * Like {@code getRefFilter() == RefFilter.DEFAULT}, but faster.
+	 *
+	 * @return {@code true} if no ref filtering is needed because there
+	 *         are no configured hidden refs.
+	 */
+	boolean hasDefaultRefFilter() {
+		return hideRefs.length == 0;
+	}
+
 	static class FsckKeyNameHolder {
 		private static final Map<String, ObjectChecker.ErrorType> errors;
 
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 d342ef4..621c2ea 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/Transport.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/Transport.java
@@ -46,7 +46,7 @@
 
 package org.eclipse.jgit.transport;
 
-import static org.eclipse.jgit.lib.Constants.CHARSET;
+import static java.nio.charset.StandardCharsets.UTF_8;
 
 import java.io.BufferedReader;
 import java.io.IOException;
@@ -144,7 +144,7 @@ private static Enumeration<URL> catalogs(ClassLoader ldr) {
 
 	private static void scan(ClassLoader ldr, URL url) {
 		try (BufferedReader br = new BufferedReader(
-				new InputStreamReader(url.openStream(), CHARSET))) {
+				new InputStreamReader(url.openStream(), UTF_8))) {
 			String line;
 			while ((line = br.readLine()) != null) {
 				line = line.trim();
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 c08f400..0df1b70 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportHttp.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportHttp.java
@@ -46,6 +46,7 @@
 
 package org.eclipse.jgit.transport;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.eclipse.jgit.lib.Constants.HEAD;
 import static org.eclipse.jgit.util.HttpSupport.ENCODING_GZIP;
 import static org.eclipse.jgit.util.HttpSupport.ENCODING_X_GZIP;
@@ -107,11 +108,9 @@
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.lib.StoredConfig;
 import org.eclipse.jgit.lib.SymbolicRef;
-import org.eclipse.jgit.storage.file.FileBasedConfig;
 import org.eclipse.jgit.transport.HttpAuthMethod.Type;
 import org.eclipse.jgit.transport.HttpConfig.HttpRedirectMode;
 import org.eclipse.jgit.transport.http.HttpConnection;
-import org.eclipse.jgit.util.FS;
 import org.eclipse.jgit.util.HttpSupport;
 import org.eclipse.jgit.util.IO;
 import org.eclipse.jgit.util.RawParseUtils;
@@ -426,7 +425,7 @@ private WalkFetchConnection newDumbConnection(InputStream in)
 	}
 
 	private BufferedReader toBufferedReader(InputStream in) {
-		return new BufferedReader(new InputStreamReader(in, Constants.CHARSET));
+		return new BufferedReader(new InputStreamReader(in, UTF_8));
 	}
 
 	/** {@inheritDoc} */
@@ -483,6 +482,18 @@ public void setAdditionalHeaders(Map<String, String> headers) {
 		this.headers = headers;
 	}
 
+	private NoRemoteRepositoryException createNotFoundException(URIish u,
+			URL url, String msg) {
+		String text;
+		if (msg != null && !msg.isEmpty()) {
+			text = MessageFormat.format(JGitText.get().uriNotFoundWithMessage,
+					url, msg);
+		} else {
+			text = MessageFormat.format(JGitText.get().uriNotFound, url);
+		}
+		return new NoRemoteRepositoryException(u, text);
+	}
+
 	private HttpConnection connect(String service)
 			throws TransportException, NotSupportedException {
 		URL u = getServiceURL(service);
@@ -511,8 +522,8 @@ private HttpConnection connect(String service)
 					return conn;
 
 				case HttpConnection.HTTP_NOT_FOUND:
-					throw new NoRemoteRepositoryException(uri,
-							MessageFormat.format(JGitText.get().uriNotFound, u));
+					throw createNotFoundException(uri, u,
+							conn.getResponseMessage());
 
 				case HttpConnection.HTTP_UNAUTHORIZED:
 					authMethod = HttpAuthMethod.scanResponse(conn, ignoreTypes);
@@ -702,15 +713,13 @@ private void updateSslVerify(StoredConfig config, boolean value) {
 	}
 
 	private void updateSslVerifyUser(boolean value) {
-		FileBasedConfig userConfig = SystemReader.getInstance()
-				.openUserConfig(null, FS.DETECTED);
+		StoredConfig userConfig = null;
 		try {
-			userConfig.load();
+			userConfig = SystemReader.getInstance().getUserConfig();
 			updateSslVerify(userConfig, value);
 		} catch (IOException | ConfigInvalidException e) {
 			// Log it, but otherwise ignore here.
-			LOG.error(MessageFormat.format(JGitText.get().userConfigFileInvalid,
-					userConfig.getFile().getAbsolutePath(), e));
+			LOG.error(e.getMessage(), e);
 		}
 	}
 
@@ -940,7 +949,7 @@ BufferedReader openReader(String path) throws IOException {
 			// Line oriented readable content is likely to compress well.
 			// Request gzip encoding.
 			InputStream is = open(path, AcceptEncoding.GZIP).in;
-			return new BufferedReader(new InputStreamReader(is, Constants.CHARSET));
+			return new BufferedReader(new InputStreamReader(is, UTF_8));
 		}
 
 		@Override
@@ -1180,9 +1189,8 @@ void sendRequest() throws IOException {
 						return;
 
 					case HttpConnection.HTTP_NOT_FOUND:
-						throw new NoRemoteRepositoryException(uri,
-								MessageFormat.format(JGitText.get().uriNotFound,
-										conn.getURL()));
+						throw createNotFoundException(uri, conn.getURL(),
+								conn.getResponseMessage());
 
 					case HttpConnection.HTTP_FORBIDDEN:
 						throw new TransportException(uri,
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/URIish.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/URIish.java
index 026fd81..70fb1f0 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/URIish.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/URIish.java
@@ -48,10 +48,11 @@
 
 package org.eclipse.jgit.transport;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
+
 import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.Serializable;
-import java.io.UnsupportedEncodingException;
 import java.net.URISyntaxException;
 import java.net.URL;
 import java.util.BitSet;
@@ -282,12 +283,7 @@ private static String unescape(String s) throws URISyntaxException {
 		if (s.indexOf('%') < 0)
 			return s;
 
-		byte[] bytes;
-		try {
-			bytes = s.getBytes(Constants.CHARACTER_ENCODING);
-		} catch (UnsupportedEncodingException e) {
-			throw new RuntimeException(e); // can't happen
-		}
+		byte[] bytes = s.getBytes(UTF_8);
 
 		byte[] os = new byte[bytes.length];
 		int j = 0;
@@ -335,12 +331,7 @@ private static String escape(String s, boolean escapeReservedChars,
 		if (s == null)
 			return null;
 		ByteArrayOutputStream os = new ByteArrayOutputStream(s.length());
-		byte[] bytes;
-		try {
-			bytes = s.getBytes(Constants.CHARACTER_ENCODING);
-		} catch (UnsupportedEncodingException e) {
-			throw new RuntimeException(e); // cannot happen
-		}
+		byte[] bytes = s.getBytes(UTF_8);
 		for (int i = 0; i < bytes.length; ++i) {
 			int b = bytes[i] & 0xFF;
 			if (b <= 32 || (encodeNonAscii && b > 127) || b == '%'
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java
index 6a3d9a1..48a3e0b 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java
@@ -46,6 +46,7 @@
 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.COMMAND_FETCH;
 import static org.eclipse.jgit.transport.GitProtocolConstants.COMMAND_LS_REFS;
 import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_AGENT;
@@ -78,6 +79,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.TreeMap;
 
 import org.eclipse.jgit.annotations.Nullable;
 import org.eclipse.jgit.errors.CorruptObjectException;
@@ -268,6 +270,9 @@ public Set<String> getOptions() {
 	 */
 	private Map<String, Ref> refs;
 
+	/** Hook used while processing Git protocol v2 requests. */
+	private ProtocolV2Hook protocolV2Hook = ProtocolV2Hook.DEFAULT;
+
 	/** Hook used while advertising the refs to the client. */
 	private AdvertiseRefsHook advertiseRefsHook = AdvertiseRefsHook.DEFAULT;
 
@@ -297,7 +302,7 @@ public Set<String> getOptions() {
 	private final Set<RevObject> commonBase = new HashSet<>();
 
 	/** Shallow commits the client already has. */
-	private final Set<ObjectId> clientShallowCommits = new HashSet<>();
+	private Set<ObjectId> clientShallowCommits = new HashSet<>();
 
 	/** Desired depth from the client on a shallow request. */
 	private int depth;
@@ -310,10 +315,10 @@ public Set<String> getOptions() {
 
 	/**
 	 * (Possibly short) ref names, ancestors of which the client has asked us
-	 * not to send using --shallow-exclude. Cannot be non-null if depth is
+	 * not to send using --shallow-exclude. Cannot be non-empty if depth is
 	 * nonzero.
 	 */
-	private @Nullable List<String> shallowExcludeRefs;
+	private List<String> deepenNotRefs = new ArrayList<>();
 
 	/** Commit time of the oldest common commit, in seconds. */
 	private int oldestTime;
@@ -583,6 +588,16 @@ public void setAdvertiseRefsHook(AdvertiseRefsHook advertiseRefsHook) {
 	}
 
 	/**
+	 * Set the protocol V2 hook.
+	 *
+	 * @param hook
+	 * @since 5.1
+	 */
+	public void setProtocolV2Hook(ProtocolV2Hook hook) {
+		this.protocolV2Hook = hook;
+	}
+
+	/**
 	 * Set the filter used while advertising the refs to the client.
 	 * <p>
 	 * Only refs allowed by this filter will be sent to the client. The filter
@@ -834,6 +849,37 @@ private Map<String, Ref> getFilteredRefs(Collection<String> refPrefixes)
 				.collect(toMap(Ref::getName, identity()));
 	}
 
+	/**
+	 * Read a ref on behalf of the client.
+	 * <p>
+	 * This checks that the ref is present in the ref advertisement since
+	 * otherwise the client might not be supposed to be able to read it.
+	 *
+	 * @param name
+	 *            the unabbreviated name of the reference.
+	 * @return the requested Ref, or {@code null} if it is not visible or
+	 *         does not exist.
+	 * @throws java.io.IOException
+	 *            on failure to read the ref or check it for visibility.
+	 */
+	@Nullable
+	private Ref getRef(String name) throws IOException {
+		if (refs != null) {
+			return refs.get(name);
+		}
+		if (!advertiseRefsHookCalled) {
+			advertiseRefsHook.advertiseRefs(this);
+			advertiseRefsHookCalled = true;
+		}
+		if (refs == null &&
+				refFilter == RefFilter.DEFAULT &&
+				transferConfig.hasDefaultRefFilter()) {
+			// Fast path: no ref filtering is needed.
+			return db.getRefDatabase().exactRef(name);
+		}
+		return getAdvertisedOrDefaultRefs().get(name);
+	}
+
 	private void service() throws IOException {
 		boolean sendPack = false;
 		// If it's a non-bidi request, we need to read the entire request before
@@ -867,7 +913,7 @@ else if (requestValidator instanceof AnyRequestValidator)
 				multiAck = MultiAck.OFF;
 
 			if (!clientShallowCommits.isEmpty())
-				verifyClientShallow();
+				verifyClientShallow(clientShallowCommits);
 			if (depth != 0)
 				processShallow(null, unshallowCommits, true);
 			if (!clientShallowCommits.isEmpty())
@@ -927,25 +973,19 @@ else if (requestValidator instanceof AnyRequestValidator)
 	}
 
 	private void lsRefsV2() throws IOException {
-		PacketLineOutRefAdvertiser adv = new PacketLineOutRefAdvertiser(pckOut);
-		String line;
-		ArrayList<String> refPrefixes = new ArrayList<>();
-		boolean needToFindSymrefs = false;
-
-		adv.setUseProtocolV2(true);
-
-		line = pckIn.readString();
-
+		LsRefsV2Request.Builder builder = LsRefsV2Request.builder();
+		List<String> prefixes = new ArrayList<>();
+		String line = pckIn.readString();
 		// Currently, we do not support any capabilities, so the next
 		// line is DELIM if there are arguments or END if not.
 		if (line == PacketLineIn.DELIM) {
 			while ((line = pckIn.readString()) != PacketLineIn.END) {
 				if (line.equals("peel")) { //$NON-NLS-1$
-					adv.setDerefTags(true);
+					builder.setPeel(true);
 				} else if (line.equals("symrefs")) { //$NON-NLS-1$
-					needToFindSymrefs = true;
+					builder.setSymrefs(true);
 				} else if (line.startsWith("ref-prefix ")) { //$NON-NLS-1$
-					refPrefixes.add(line.substring("ref-prefix ".length())); //$NON-NLS-1$
+					prefixes.add(line.substring("ref-prefix ".length())); //$NON-NLS-1$
 				} else {
 					throw new PackProtocolException(MessageFormat
 							.format(JGitText.get().unexpectedPacketLine, line));
@@ -955,11 +995,18 @@ private void lsRefsV2() throws IOException {
 			throw new PackProtocolException(MessageFormat
 					.format(JGitText.get().unexpectedPacketLine, line));
 		}
+		LsRefsV2Request req = builder.setRefPrefixes(prefixes).build();
+
+		protocolV2Hook.onLsRefs(req);
+
 		rawOut.stopBuffering();
-
-		Map<String, Ref> refsToSend = getFilteredRefs(refPrefixes);
-
-		if (needToFindSymrefs) {
+		PacketLineOutRefAdvertiser adv = new PacketLineOutRefAdvertiser(pckOut);
+		adv.setUseProtocolV2(true);
+		if (req.getPeel()) {
+			adv.setDerefTags(true);
+		}
+		Map<String, Ref> refsToSend = getFilteredRefs(req.getRefPrefixes());
+		if (req.getSymrefs()) {
 			findSymrefs(adv, refsToSend);
 		}
 
@@ -968,12 +1015,6 @@ private void lsRefsV2() throws IOException {
 	}
 
 	private void fetchV2() throws IOException {
-		options = new HashSet<>();
-
-		// Packs are always sent multiplexed and using full 64K
-		// lengths.
-		options.add(OPTION_SIDE_BAND_64K);
-
 		// Depending on the requestValidator, #processHaveLines may
 		// require that advertised be set. Set it only in the required
 		// circumstances (to avoid a full ref lookup in the case that
@@ -986,113 +1027,65 @@ private void fetchV2() throws IOException {
 			advertised = refIdSet(getAdvertisedOrDefaultRefs().values());
 		}
 
-		String line;
-		List<ObjectId> peerHas = new ArrayList<>();
-		boolean doneReceived = false;
-
-		// Currently, we do not support any capabilities, so the next
-		// line is DELIM.
-		if ((line = pckIn.readString()) != PacketLineIn.DELIM) {
-			throw new PackProtocolException(MessageFormat
-					.format(JGitText.get().unexpectedPacketLine, line));
-		}
-
-		boolean includeTag = false;
-		boolean filterReceived = false;
-		while ((line = pckIn.readString()) != PacketLineIn.END) {
-			if (line.startsWith("want ")) { //$NON-NLS-1$
-				wantIds.add(ObjectId.fromString(line.substring(5)));
-			} else if (line.startsWith("have ")) { //$NON-NLS-1$
-				peerHas.add(ObjectId.fromString(line.substring(5)));
-			} else if (line.equals("done")) { //$NON-NLS-1$
-				doneReceived = true;
-			} else if (line.equals(OPTION_THIN_PACK)) {
-				options.add(OPTION_THIN_PACK);
-			} else if (line.equals(OPTION_NO_PROGRESS)) {
-				options.add(OPTION_NO_PROGRESS);
-			} else if (line.equals(OPTION_INCLUDE_TAG)) {
-				options.add(OPTION_INCLUDE_TAG);
-				includeTag = true;
-			} else if (line.equals(OPTION_OFS_DELTA)) {
-				options.add(OPTION_OFS_DELTA);
-			} else if (line.startsWith("shallow ")) { //$NON-NLS-1$
-				clientShallowCommits.add(ObjectId.fromString(line.substring(8)));
-			} else if (line.startsWith("deepen ")) { //$NON-NLS-1$
-				depth = Integer.parseInt(line.substring(7));
-				if (depth <= 0) {
-					throw new PackProtocolException(
-							MessageFormat.format(JGitText.get().invalidDepth,
-									Integer.valueOf(depth)));
-				}
-				if (shallowSince != 0) {
-					throw new PackProtocolException(
-							JGitText.get().deepenSinceWithDeepen);
-				}
-				if (shallowExcludeRefs != null) {
-					throw new PackProtocolException(
-							JGitText.get().deepenNotWithDeepen);
-				}
-			} else if (line.startsWith("deepen-not ")) { //$NON-NLS-1$
-				List<String> exclude = shallowExcludeRefs;
-				if (exclude == null) {
-					exclude = shallowExcludeRefs = new ArrayList<>();
-				}
-				exclude.add(line.substring(11));
-				if (depth != 0) {
-					throw new PackProtocolException(
-							JGitText.get().deepenNotWithDeepen);
-				}
-			} else if (line.equals(OPTION_DEEPEN_RELATIVE)) {
-				options.add(OPTION_DEEPEN_RELATIVE);
-			} else if (line.startsWith("deepen-since ")) { //$NON-NLS-1$
-				shallowSince = Integer.parseInt(line.substring(13));
-				if (shallowSince <= 0) {
-					throw new PackProtocolException(
-							MessageFormat.format(
-									JGitText.get().invalidTimestamp, line));
-				}
-				if (depth !=  0) {
-					throw new PackProtocolException(
-							JGitText.get().deepenSinceWithDeepen);
-				}
-			} else if (transferConfig.isAllowFilter()
-					&& line.startsWith(OPTION_FILTER + ' ')) {
-				if (filterReceived) {
-					throw new PackProtocolException(JGitText.get().tooManyFilters);
-				}
-				filterReceived = true;
-				parseFilter(line.substring(OPTION_FILTER.length() + 1));
-			} else {
-				throw new PackProtocolException(MessageFormat
-						.format(JGitText.get().unexpectedPacketLine, line));
-			}
-		}
+		ProtocolV2Parser parser = new ProtocolV2Parser(transferConfig);
+		FetchV2Request req = parser.parseFetchRequest(pckIn);
 		rawOut.stopBuffering();
 
+		protocolV2Hook.onFetch(req);
+
+		// TODO(ifrade): Refactor to pass around the Request object, instead of
+		// copying data back to class fields
+		options = req.getOptions();
+		clientShallowCommits = req.getClientShallowCommits();
+		depth = req.getDepth();
+		shallowSince = req.getDeepenSince();
+		filterBlobLimit = req.getFilterBlobLimit();
+		deepenNotRefs = req.getDeepenNotRefs();
+
+		wantIds.addAll(req.getWantsIds());
+		Map<String, ObjectId> wantedRefs = new TreeMap<>();
+		for (String refName : req.getWantedRefs()) {
+			Ref ref = getRef(refName);
+			if (ref == null) {
+				throw new PackProtocolException(MessageFormat
+						.format(JGitText.get().invalidRefName, refName));
+			}
+			ObjectId oid = ref.getObjectId();
+			if (oid == null) {
+				throw new PackProtocolException(MessageFormat
+						.format(JGitText.get().invalidRefName, refName));
+			}
+			wantIds.add(oid);
+			wantedRefs.put(refName, oid);
+		}
+
 		boolean sectionSent = false;
 		@Nullable List<ObjectId> shallowCommits = null;
 		List<ObjectId> unshallowCommits = new ArrayList<>();
 
-		if (!clientShallowCommits.isEmpty()) {
-			verifyClientShallow();
+		if (!req.getClientShallowCommits().isEmpty()) {
+			verifyClientShallow(req.getClientShallowCommits());
 		}
-		if (depth != 0 || shallowSince != 0 || shallowExcludeRefs != null) {
-			shallowCommits = new ArrayList<ObjectId>();
+		if (req.getDepth() != 0 || req.getDeepenSince() != 0
+				|| !req.getDeepenNotRefs().isEmpty()) {
+			shallowCommits = new ArrayList<>();
 			processShallow(shallowCommits, unshallowCommits, false);
 		}
-		if (!clientShallowCommits.isEmpty())
-			walk.assumeShallow(clientShallowCommits);
+		if (!req.getClientShallowCommits().isEmpty())
+			walk.assumeShallow(req.getClientShallowCommits());
 
-		if (doneReceived) {
-			processHaveLines(peerHas, ObjectId.zeroId(), new PacketLineOut(NullOutputStream.INSTANCE));
+		if (req.wasDoneReceived()) {
+			processHaveLines(req.getPeerHas(), ObjectId.zeroId(),
+					new PacketLineOut(NullOutputStream.INSTANCE));
 		} else {
 			pckOut.writeString("acknowledgments\n"); //$NON-NLS-1$
-			for (ObjectId id : peerHas) {
+			for (ObjectId id : req.getPeerHas()) {
 				if (walk.getObjectReader().has(id)) {
 					pckOut.writeString("ACK " + id.getName() + "\n"); //$NON-NLS-1$ //$NON-NLS-2$
 				}
 			}
-			processHaveLines(peerHas, ObjectId.zeroId(), new PacketLineOut(NullOutputStream.INSTANCE));
+			processHaveLines(req.getPeerHas(), ObjectId.zeroId(),
+					new PacketLineOut(NullOutputStream.INSTANCE));
 			if (okToGiveUp()) {
 				pckOut.writeString("ready\n"); //$NON-NLS-1$
 			} else if (commonBase.isEmpty()) {
@@ -1101,7 +1094,7 @@ private void fetchV2() throws IOException {
 			sectionSent = true;
 		}
 
-		if (doneReceived || okToGiveUp()) {
+		if (req.wasDoneReceived() || okToGiveUp()) {
 			if (shallowCommits != null) {
 				if (sectionSent)
 					pckOut.writeDelim();
@@ -1115,16 +1108,33 @@ private void fetchV2() throws IOException {
 				sectionSent = true;
 			}
 
+			if (!wantedRefs.isEmpty()) {
+				if (sectionSent) {
+					pckOut.writeDelim();
+				}
+				pckOut.writeString("wanted-refs\n"); //$NON-NLS-1$
+				for (Map.Entry<String, ObjectId> entry :
+						wantedRefs.entrySet()) {
+					pckOut.writeString(entry.getValue().getName() + ' ' +
+							entry.getKey() + '\n');
+				}
+				sectionSent = true;
+			}
+
 			if (sectionSent)
 				pckOut.writeDelim();
 			pckOut.writeString("packfile\n"); //$NON-NLS-1$
 			sendPack(new PackStatistics.Accumulator(),
-					includeTag
+					req.getOptions().contains(OPTION_INCLUDE_TAG)
 						? db.getRefDatabase().getRefsByPrefix(R_TAGS)
 						: null,
 					unshallowCommits);
+			// sendPack invokes pckOut.end() for us, so we do not
+			// need to invoke it here.
+		} else {
+			// Invoke pckOut.end() by ourselves.
+			pckOut.end();
 		}
-		pckOut.end();
 	}
 
 	/*
@@ -1161,9 +1171,13 @@ private List<String> getV2CapabilityAdvertisement() {
 		ArrayList<String> caps = new ArrayList<>();
 		caps.add("version 2"); //$NON-NLS-1$
 		caps.add(COMMAND_LS_REFS);
+		boolean advertiseRefInWant = transferConfig.isAllowRefInWant() &&
+				db.getConfig().getBoolean("uploadpack", null, //$NON-NLS-1$
+						"advertiserefinwant", true); //$NON-NLS-1$
 		caps.add(
 				COMMAND_FETCH + '=' +
 				(transferConfig.isAllowFilter() ? OPTION_FILTER + ' ' : "") + //$NON-NLS-1$
+				(advertiseRefInWant ? CAPABILITY_REF_IN_WANT + ' ' : "") + //$NON-NLS-1$
 				OPTION_SHALLOW);
 		return caps;
 	}
@@ -1174,6 +1188,8 @@ private void serviceV2() throws IOException {
 			// is sent only if this is a bidirectional pipe. (If
 			// not, the client is expected to call
 			// sendAdvertisedRefs() on its own.)
+			protocolV2Hook
+					.onCapabilities(CapabilitiesV2Request.builder().build());
 			for (String s : getV2CapabilityAdvertisement()) {
 				pckOut.writeString(s + "\n"); //$NON-NLS-1$
 			}
@@ -1221,7 +1237,7 @@ private void processShallow(@Nullable List<ObjectId> shallowCommits,
 			boolean writeToPckOut) throws IOException {
 		if (options.contains(OPTION_DEEPEN_RELATIVE) ||
 				shallowSince != 0 ||
-				shallowExcludeRefs != null) {
+				!deepenNotRefs.isEmpty()) {
 			// TODO(jonathantanmy): Implement deepen-relative, deepen-since,
 			// and deepen-not.
 			throw new UnsupportedOperationException();
@@ -1272,9 +1288,14 @@ private void processShallow(@Nullable List<ObjectId> shallowCommits,
 		}
 	}
 
-	private void verifyClientShallow()
+	/*
+	 * Verify all shallow lines refer to commits
+	 *
+	 * It can mutate the input set (removing missing object ids from it)
+	 */
+	private void verifyClientShallow(Set<ObjectId> shallowCommits)
 			throws IOException, PackProtocolException {
-		AsyncRevObjectQueue q = walk.parseAny(clientShallowCommits, true);
+		AsyncRevObjectQueue q = walk.parseAny(shallowCommits, true);
 		try {
 			for (;;) {
 				try {
@@ -1292,7 +1313,7 @@ private void verifyClientShallow()
 				} catch (MissingObjectException notCommit) {
 					// shallow objects not known at the server are ignored
 					// by git-core upload-pack, match that behavior.
-					clientShallowCommits.remove(notCommit.getObjectId());
+					shallowCommits.remove(notCommit.getObjectId());
 					continue;
 				}
 			}
@@ -1338,6 +1359,8 @@ public void sendAdvertisedRefs(RefAdvertiser adv,
 		if (useProtocolV2()) {
 			// The equivalent in v2 is only the capabilities
 			// advertisement.
+			protocolV2Hook
+					.onCapabilities(CapabilitiesV2Request.builder().build());
 			for (String s : getV2CapabilityAdvertisement()) {
 				adv.writeOne(s);
 			}
@@ -1413,33 +1436,6 @@ public OutputStream getMessageOutputStream() {
 		return msgOut;
 	}
 
-	private void parseFilter(String arg) throws PackProtocolException {
-		if (arg.equals("blob:none")) { //$NON-NLS-1$
-			filterBlobLimit = 0;
-		} else if (arg.startsWith("blob:limit=")) { //$NON-NLS-1$
-			try {
-				filterBlobLimit = Long.parseLong(
-						arg.substring("blob:limit=".length())); //$NON-NLS-1$
-			} catch (NumberFormatException e) {
-				throw new PackProtocolException(
-						MessageFormat.format(JGitText.get().invalidFilter,
-								arg));
-			}
-		}
-		/*
-		 * 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 (filterBlobLimit < 0) {
-			throw new PackProtocolException(
-					MessageFormat.format(JGitText.get().invalidFilter,
-							arg));
-		}
-	}
-
 	private void recvWants() throws IOException {
 		boolean isFirst = true;
 		boolean filterReceived = false;
@@ -1480,7 +1476,7 @@ private void recvWants() throws IOException {
 				}
 				filterReceived = true;
 
-				parseFilter(arg);
+				filterBlobLimit = ProtocolV2Parser.filterLine(arg);
 				continue;
 			}
 
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkRemoteObjectDatabase.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkRemoteObjectDatabase.java
index aa71c94..6d4df4f 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkRemoteObjectDatabase.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkRemoteObjectDatabase.java
@@ -43,6 +43,8 @@
 
 package org.eclipse.jgit.transport;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
+
 import java.io.BufferedReader;
 import java.io.ByteArrayOutputStream;
 import java.io.FileNotFoundException;
@@ -363,7 +365,7 @@ void writeInfoPacks(Collection<String> packNames) throws IOException {
 	 */
 	BufferedReader openReader(String path) throws IOException {
 		final InputStream is = open(path).in;
-		return new BufferedReader(new InputStreamReader(is, Constants.CHARSET));
+		return new BufferedReader(new InputStreamReader(is, UTF_8));
 	}
 
 	/**
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/AbstractTreeIterator.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/AbstractTreeIterator.java
index 470ed02..335abe1 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/AbstractTreeIterator.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/AbstractTreeIterator.java
@@ -45,6 +45,8 @@
 
 package org.eclipse.jgit.treewalk;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
+
 import java.io.IOException;
 import java.nio.ByteBuffer;
 import java.nio.CharBuffer;
@@ -182,7 +184,7 @@ protected AbstractTreeIterator(String prefix) {
 		if (prefix != null && prefix.length() > 0) {
 			final ByteBuffer b;
 
-			b = Constants.CHARSET.encode(CharBuffer.wrap(prefix));
+			b = UTF_8.encode(CharBuffer.wrap(prefix));
 			pathLen = b.limit();
 			path = new byte[Math.max(DEFAULT_PATH_SIZE, pathLen + 1)];
 			b.get(path, 0, pathLen);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/FileTreeIterator.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/FileTreeIterator.java
index 24b9ac0..4f3eb05 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/FileTreeIterator.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/FileTreeIterator.java
@@ -46,11 +46,14 @@
 
 package org.eclipse.jgit.treewalk;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
+
 import java.io.ByteArrayInputStream;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.time.Instant;
 
 import org.eclipse.jgit.dircache.DirCacheIterator;
 import org.eclipse.jgit.errors.IncorrectObjectTypeException;
@@ -404,15 +407,24 @@ public long getLength() {
 		}
 
 		@Override
+		@Deprecated
 		public long getLastModified() {
-			return attributes.getLastModifiedTime();
+			return attributes.getLastModifiedInstant().toEpochMilli();
+		}
+
+		/**
+		 * @since 5.1.9
+		 */
+		@Override
+		public Instant getLastModifiedInstant() {
+			return attributes.getLastModifiedInstant();
 		}
 
 		@Override
 		public InputStream openInputStream() throws IOException {
 			if (attributes.isSymbolicLink()) {
 				return new ByteArrayInputStream(fs.readSymLink(getFile())
-						.getBytes(Constants.CHARACTER_ENCODING));
+						.getBytes(UTF_8));
 			} else {
 				return new FileInputStream(getFile());
 			}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/TreeWalk.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/TreeWalk.java
index d500aae..69303d6 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/TreeWalk.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/TreeWalk.java
@@ -44,10 +44,13 @@
 
 package org.eclipse.jgit.treewalk;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
+
 import java.io.IOException;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Set;
+import java.util.regex.Matcher;
 
 import org.eclipse.jgit.annotations.Nullable;
 import org.eclipse.jgit.api.errors.JGitInternalException;
@@ -1045,7 +1048,7 @@ public String getNameString() {
 		final AbstractTreeIterator t = currentHead;
 		final int off = t.pathOffset;
 		final int end = t.pathLen;
-		return RawParseUtils.decode(Constants.CHARSET, t.path, off, end);
+		return RawParseUtils.decode(UTF_8, t.path, off, end);
 	}
 
 	/**
@@ -1378,11 +1381,11 @@ private CanonicalTreeParser parserFor(AnyObjectId id)
 	}
 
 	static String pathOf(AbstractTreeIterator t) {
-		return RawParseUtils.decode(Constants.CHARSET, t.path, 0, t.pathLen);
+		return RawParseUtils.decode(UTF_8, t.path, 0, t.pathLen);
 	}
 
 	static String pathOf(byte[] buf, int pos, int end) {
-		return RawParseUtils.decode(Constants.CHARSET, buf, pos, end);
+		return RawParseUtils.decode(UTF_8, buf, pos, end);
 	}
 
 	/**
@@ -1436,7 +1439,8 @@ public String getFilterCommand(String filterCommandType)
 			return null;
 		}
 		return filterCommand.replaceAll("%f", //$NON-NLS-1$
-				QuotedString.BOURNE.quote((getPathString())));
+				Matcher.quoteReplacement(
+						QuotedString.BOURNE.quote((getPathString()))));
 	}
 
 	/**
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 179fd46..299f07f 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java
@@ -46,6 +46,8 @@
 
 package org.eclipse.jgit.treewalk;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
+
 import java.io.ByteArrayInputStream;
 import java.io.File;
 import java.io.FileInputStream;
@@ -57,6 +59,7 @@
 import java.nio.charset.CharacterCodingException;
 import java.nio.charset.CharsetEncoder;
 import java.text.MessageFormat;
+import java.time.Instant;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.Comparator;
@@ -643,12 +646,24 @@ public long getEntryContentLength() throws IOException {
 	 *
 	 * @return last modified time of this file, in milliseconds since the epoch
 	 *         (Jan 1, 1970 UTC).
+	 * @deprecated use {@link #getEntryLastModifiedInstant()} instead
 	 */
+	@Deprecated
 	public long getEntryLastModified() {
 		return current().getLastModified();
 	}
 
 	/**
+	 * Get the last modified time of this entry.
+	 *
+	 * @return last modified time of this file
+	 * @since 5.1.9
+	 */
+	public Instant getEntryLastModifiedInstant() {
+		return current().getLastModifiedInstant();
+	}
+
+	/**
 	 * Obtain an input stream to read the file content.
 	 * <p>
 	 * Efficient implementations are not required. The caller will usually
@@ -922,30 +937,28 @@ public MetadataDiff compareMetadata(DirCacheEntry entry) {
 
 		// Git under windows only stores seconds so we round the timestamp
 		// Java gives us if it looks like the timestamp in index is seconds
-		// only. Otherwise we compare the timestamp at millisecond precision,
+		// only. Otherwise we compare the timestamp at nanosecond precision,
 		// unless core.checkstat is set to "minimal", in which case we only
 		// compare the whole second part.
-		long cacheLastModified = entry.getLastModified();
-		long fileLastModified = getEntryLastModified();
-		long lastModifiedMillis = fileLastModified % 1000;
-		long cacheMillis = cacheLastModified % 1000;
-		if (getOptions().getCheckStat() == CheckStat.MINIMAL) {
-			fileLastModified = fileLastModified - lastModifiedMillis;
-			cacheLastModified = cacheLastModified - cacheMillis;
-		} else if (cacheMillis == 0)
-			fileLastModified = fileLastModified - lastModifiedMillis;
-		// Some Java version on Linux return whole seconds only even when
-		// the file systems supports more precision.
-		else if (lastModifiedMillis == 0)
-			cacheLastModified = cacheLastModified - cacheMillis;
-
-		if (fileLastModified != cacheLastModified)
+		Instant cacheLastModified = entry.getLastModifiedInstant();
+		Instant fileLastModified = getEntryLastModifiedInstant();
+		if ((getOptions().getCheckStat() == CheckStat.MINIMAL)
+				|| (cacheLastModified.getNano() == 0)
+				// Some Java version on Linux return whole seconds only even
+				// when the file systems supports more precision.
+				|| (fileLastModified.getNano() == 0)) {
+			if (fileLastModified.getEpochSecond() != cacheLastModified
+					.getEpochSecond()) {
+				return MetadataDiff.DIFFER_BY_TIMESTAMP;
+			}
+		}
+		if (!fileLastModified.equals(cacheLastModified)) {
 			return MetadataDiff.DIFFER_BY_TIMESTAMP;
-		else if (!entry.isSmudged())
-			// The file is clean when you look at timestamps.
-			return MetadataDiff.EQUAL;
-		else
+		} else if (entry.isSmudged()) {
 			return MetadataDiff.SMUDGED;
+		}
+		// The file is clean when when comparing timestamps
+		return MetadataDiff.EQUAL;
 	}
 
 	/**
@@ -1272,10 +1285,26 @@ public String toString() {
 		 * instance member instead.
 		 *
 		 * @return time since the epoch (in ms) of the last change.
+		 * @deprecated use {@link #getLastModifiedInstant()} instead
 		 */
+		@Deprecated
 		public abstract long getLastModified();
 
 		/**
+		 * Get the last modified time of this entry.
+		 * <p>
+		 * <b>Note: Efficient implementation required.</b>
+		 * <p>
+		 * The implementation of this method must be efficient. If a subclass
+		 * needs to compute the value they should cache the reference within an
+		 * instance member instead.
+		 *
+		 * @return time of the last change.
+		 * @since 5.1.9
+		 */
+		public abstract Instant getLastModifiedInstant();
+
+		/**
 		 * Get the name of this entry within its directory.
 		 * <p>
 		 * Efficient implementations are not required. The caller will obtain
@@ -1413,7 +1442,7 @@ private static final class IteratorState {
 
 		IteratorState(WorkingTreeOptions options) {
 			this.options = options;
-			this.nameEncoder = Constants.CHARSET.newEncoder();
+			this.nameEncoder = UTF_8.newEncoder();
 		}
 
 		void initializeReadBuffer() {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/Base64.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/Base64.java
index 442f079..69f8547 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/Base64.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/Base64.java
@@ -6,7 +6,7 @@
 
 package org.eclipse.jgit.util;
 
-import static org.eclipse.jgit.lib.Constants.CHARSET;
+import static java.nio.charset.StandardCharsets.UTF_8;
 
 import java.text.MessageFormat;
 import java.util.Arrays;
@@ -54,7 +54,7 @@ public class Base64 {
 				+ "abcdefghijklmnopqrstuvwxyz" // //$NON-NLS-1$
 				+ "0123456789" // //$NON-NLS-1$
 				+ "+/" // //$NON-NLS-1$
-		).getBytes(CHARSET);
+		).getBytes(UTF_8);
 
 		DEC = new byte[128];
 		Arrays.fill(DEC, INVALID_DEC);
@@ -177,7 +177,7 @@ public static String encodeBytes(byte[] source, int off, int len) {
 			e += 4;
 		}
 
-		return new String(outBuff, 0, e, CHARSET);
+		return new String(outBuff, 0, e, UTF_8);
 	}
 
 	/**
@@ -293,7 +293,7 @@ else if (source[srcOffset + 3] == EQUALS_SIGN) {
 	 * @return the decoded data
 	 */
 	public static byte[] decode(String s) {
-		byte[] bytes = s.getBytes(CHARSET);
+		byte[] bytes = s.getBytes(UTF_8);
 		return decode(bytes, 0, bytes.length);
 	}
 }
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 d093818..7d37cfa 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java
@@ -43,6 +43,9 @@
 
 package org.eclipse.jgit.util;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static java.time.Instant.EPOCH;
+
 import java.io.BufferedReader;
 import java.io.ByteArrayInputStream;
 import java.io.Closeable;
@@ -51,31 +54,53 @@
 import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.io.OutputStream;
+import java.io.OutputStreamWriter;
 import java.io.PrintStream;
+import java.io.Writer;
 import java.nio.charset.Charset;
+import java.nio.file.AccessDeniedException;
+import java.nio.file.FileStore;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.attribute.BasicFileAttributes;
+import java.nio.file.attribute.FileTime;
 import java.security.AccessController;
 import java.security.PrivilegedAction;
 import java.text.MessageFormat;
+import java.time.Duration;
+import java.time.Instant;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Optional;
+import java.util.UUID;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicReference;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
 
+import org.eclipse.jgit.annotations.NonNull;
 import org.eclipse.jgit.annotations.Nullable;
 import org.eclipse.jgit.api.errors.JGitInternalException;
 import org.eclipse.jgit.errors.CommandFailedException;
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.errors.LockFailedException;
 import org.eclipse.jgit.internal.JGitText;
+import org.eclipse.jgit.internal.storage.file.FileSnapshot;
+import org.eclipse.jgit.lib.ConfigConstants;
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.lib.StoredConfig;
 import org.eclipse.jgit.treewalk.FileTreeIterator.FileEntry;
 import org.eclipse.jgit.treewalk.FileTreeIterator.FileModeStrategy;
 import org.eclipse.jgit.treewalk.WorkingTreeIterator.Entry;
@@ -178,6 +203,487 @@ public int getRc() {
 		}
 	}
 
+	/**
+	 * Attributes of FileStores on this system
+	 *
+	 * @since 5.1.9
+	 */
+	public final static class FileStoreAttributes {
+
+		private static final Duration UNDEFINED_DURATION = Duration
+				.ofNanos(Long.MAX_VALUE);
+
+		/**
+		 * Fallback filesystem timestamp resolution. The worst case timestamp
+		 * resolution on FAT filesystems is 2 seconds.
+		 */
+		public static final Duration FALLBACK_TIMESTAMP_RESOLUTION = Duration
+				.ofMillis(2000);
+
+		/**
+		 * Fallback FileStore attributes used when we can't measure the
+		 * filesystem timestamp resolution. The last modified time granularity
+		 * of FAT filesystems is 2 seconds.
+		 */
+		public static final FileStoreAttributes FALLBACK_FILESTORE_ATTRIBUTES = new FileStoreAttributes(
+				FALLBACK_TIMESTAMP_RESOLUTION);
+
+		private static final Map<FileStore, FileStoreAttributes> attributeCache = new ConcurrentHashMap<>();
+
+		private static final SimpleLruCache<Path, FileStoreAttributes> attrCacheByPath = new SimpleLruCache<>(
+				100, 0.2f);
+
+		private static AtomicBoolean background = new AtomicBoolean();
+
+		private static Map<FileStore, Lock> locks = new ConcurrentHashMap<>();
+
+		private static void setBackground(boolean async) {
+			background.set(async);
+		}
+
+		private static final String javaVersionPrefix = System
+				.getProperty("java.vendor") + '|' //$NON-NLS-1$
+				+ System.getProperty("java.version") + '|'; //$NON-NLS-1$
+
+		private static final Duration FALLBACK_MIN_RACY_INTERVAL = Duration
+				.ofMillis(10);
+
+		/**
+		 * Configures size and purge factor of the path-based cache for file
+		 * system attributes. Caching of file system attributes avoids recurring
+		 * lookup of @{code FileStore} of files which may be expensive on some
+		 * platforms.
+		 *
+		 * @param maxSize
+		 *            maximum size of the cache, default is 100
+		 * @param purgeFactor
+		 *            when the size of the map reaches maxSize the oldest
+		 *            entries will be purged to free up some space for new
+		 *            entries, {@code purgeFactor} is the fraction of
+		 *            {@code maxSize} to purge when this happens
+		 * @since 5.1.9
+		 */
+		public static void configureAttributesPathCache(int maxSize,
+				float purgeFactor) {
+			FileStoreAttributes.attrCacheByPath.configure(maxSize, purgeFactor);
+		}
+
+		/**
+		 * Get the FileStoreAttributes for the given FileStore
+		 *
+		 * @param path
+		 *            file residing in the FileStore to get attributes for
+		 * @return FileStoreAttributes for the given path.
+		 */
+		public static FileStoreAttributes get(Path path) {
+			path = path.toAbsolutePath();
+			Path dir = Files.isDirectory(path) ? path : path.getParent();
+			FileStoreAttributes cached = attrCacheByPath.get(dir);
+			if (cached != null) {
+				return cached;
+			}
+			FileStoreAttributes attrs = getFileStoreAttributes(dir);
+			attrCacheByPath.put(dir, attrs);
+			return attrs;
+		}
+
+		private static FileStoreAttributes getFileStoreAttributes(Path dir) {
+			FileStore s;
+			try {
+				if (Files.exists(dir)) {
+					s = Files.getFileStore(dir);
+					FileStoreAttributes c = attributeCache.get(s);
+					if (c != null) {
+						return c;
+					}
+					if (!Files.isWritable(dir)) {
+						// cannot measure resolution in a read-only directory
+						LOG.debug(
+								"{}: cannot measure timestamp resolution in read-only directory {}", //$NON-NLS-1$
+								Thread.currentThread(), dir);
+						return FALLBACK_FILESTORE_ATTRIBUTES;
+					}
+				} else {
+					// cannot determine FileStore of an unborn directory
+					LOG.debug(
+							"{}: cannot measure timestamp resolution of unborn directory {}", //$NON-NLS-1$
+							Thread.currentThread(), dir);
+					return FALLBACK_FILESTORE_ATTRIBUTES;
+				}
+
+				CompletableFuture<Optional<FileStoreAttributes>> f = CompletableFuture
+						.supplyAsync(() -> {
+							Lock lock = locks.computeIfAbsent(s,
+									l -> new ReentrantLock());
+							if (!lock.tryLock()) {
+								LOG.debug(
+										"{}: couldn't get lock to measure timestamp resolution in {}", //$NON-NLS-1$
+										Thread.currentThread(), dir);
+								return Optional.empty();
+							}
+							Optional<FileStoreAttributes> attributes = Optional
+									.empty();
+							try {
+								// Some earlier future might have set the value
+								// and removed itself since we checked for the
+								// value above. Hence check cache again.
+								FileStoreAttributes c = attributeCache
+										.get(s);
+								if (c != null) {
+									return Optional.of(c);
+								}
+								attributes = readFromConfig(s);
+								if (attributes.isPresent()) {
+									attributeCache.put(s, attributes.get());
+									return attributes;
+								}
+
+								Optional<Duration> resolution = measureFsTimestampResolution(
+										s, dir);
+								if (resolution.isPresent()) {
+									c = new FileStoreAttributes(
+											resolution.get());
+									attributeCache.put(s, c);
+									// for high timestamp resolution measure
+									// minimal racy interval
+									if (c.fsTimestampResolution
+											.toNanos() < 100_000_000L) {
+										c.minimalRacyInterval = measureMinimalRacyInterval(
+												dir);
+									}
+									if (LOG.isDebugEnabled()) {
+										LOG.debug(c.toString());
+									}
+									saveToConfig(s, c);
+								}
+								attributes = Optional.of(c);
+							} finally {
+								lock.unlock();
+								locks.remove(s);
+							}
+							return attributes;
+						});
+				f.exceptionally(e -> {
+					LOG.error(e.getLocalizedMessage(), e);
+					return Optional.empty();
+				});
+				// even if measuring in background wait a little - if the result
+				// arrives, it's better than returning the large fallback
+				Optional<FileStoreAttributes> d = background.get() ? f.get(
+						100, TimeUnit.MILLISECONDS) : f.get();
+				if (d.isPresent()) {
+					return d.get();
+				}
+				// return fallback until measurement is finished
+			} catch (IOException | InterruptedException
+					| ExecutionException | CancellationException e) {
+				LOG.error(e.getMessage(), e);
+			} catch (TimeoutException | SecurityException e) {
+				// use fallback
+			}
+			LOG.debug("{}: use fallback timestamp resolution for directory {}", //$NON-NLS-1$
+					Thread.currentThread(), dir);
+			return FALLBACK_FILESTORE_ATTRIBUTES;
+		}
+
+		@SuppressWarnings("boxing")
+		private static Duration measureMinimalRacyInterval(Path dir) {
+			LOG.debug("{}: start measure minimal racy interval in {}", //$NON-NLS-1$
+					Thread.currentThread(), dir);
+			int n = 0;
+			int failures = 0;
+			long racyNanos = 0;
+			ArrayList<Long> deltas = new ArrayList<>();
+			Path probe = dir.resolve(".probe-" + UUID.randomUUID()); //$NON-NLS-1$
+			Instant end = Instant.now().plusSeconds(3);
+			try {
+				Files.createFile(probe);
+				do {
+					n++;
+					write(probe, "a"); //$NON-NLS-1$
+					FileSnapshot snapshot = FileSnapshot.save(probe.toFile());
+					read(probe);
+					write(probe, "b"); //$NON-NLS-1$
+					if (!snapshot.isModified(probe.toFile())) {
+						deltas.add(Long.valueOf(snapshot.lastDelta()));
+						racyNanos = snapshot.lastRacyThreshold();
+						failures++;
+					}
+				} while (Instant.now().compareTo(end) < 0);
+			} catch (IOException e) {
+				LOG.error(e.getMessage(), e);
+				return FALLBACK_MIN_RACY_INTERVAL;
+			} finally {
+				deleteProbe(probe);
+			}
+			if (failures > 0) {
+				Stats stats = new Stats();
+				for (Long d : deltas) {
+					stats.add(d);
+				}
+				LOG.debug(
+						"delta [ns] since modification FileSnapshot failed to detect\n" //$NON-NLS-1$
+								+ "count, failures, racy limit [ns], delta min [ns]," //$NON-NLS-1$
+								+ " delta max [ns], delta avg [ns]," //$NON-NLS-1$
+								+ " delta stddev [ns]\n" //$NON-NLS-1$
+								+ "{}, {}, {}, {}, {}, {}, {}", //$NON-NLS-1$
+						n, failures, racyNanos, stats.min(), stats.max(),
+						stats.avg(), stats.stddev());
+				return Duration
+						.ofNanos(Double.valueOf(stats.max()).longValue());
+			}
+			// since no failures occurred using the measured filesystem
+			// timestamp resolution there is no need for minimal racy interval
+			LOG.debug("{}: no failures when measuring minimal racy interval", //$NON-NLS-1$
+					Thread.currentThread());
+			return Duration.ZERO;
+		}
+
+		private static void write(Path p, String body) throws IOException {
+			FileUtils.mkdirs(p.getParent().toFile(), true);
+			try (Writer w = new OutputStreamWriter(Files.newOutputStream(p),
+					UTF_8)) {
+				w.write(body);
+			}
+		}
+
+		private static String read(Path p) throws IOException {
+			final byte[] body = IO.readFully(p.toFile());
+			return new String(body, 0, body.length, UTF_8);
+		}
+
+		private static Optional<Duration> measureFsTimestampResolution(
+			FileStore s, Path dir) {
+			LOG.debug("{}: start measure timestamp resolution {} in {}", //$NON-NLS-1$
+					Thread.currentThread(), s, dir);
+			Path probe = dir.resolve(".probe-" + UUID.randomUUID()); //$NON-NLS-1$
+			try {
+				Files.createFile(probe);
+				FileTime t1 = Files.getLastModifiedTime(probe);
+				FileTime t2 = t1;
+				Instant t1i = t1.toInstant();
+				for (long i = 1; t2.compareTo(t1) <= 0; i += 1 + i / 20) {
+					Files.setLastModifiedTime(probe,
+							FileTime.from(t1i.plusNanos(i * 1000)));
+					t2 = Files.getLastModifiedTime(probe);
+				}
+				Duration fsResolution = Duration.between(t1.toInstant(), t2.toInstant());
+				Duration clockResolution = measureClockResolution();
+				fsResolution = fsResolution.plus(clockResolution);
+				LOG.debug("{}: end measure timestamp resolution {} in {}", //$NON-NLS-1$
+						Thread.currentThread(), s, dir);
+				return Optional.of(fsResolution);
+			} catch (AccessDeniedException e) {
+				LOG.warn(e.getLocalizedMessage(), e); // see bug 548648
+			} catch (IOException e) {
+				LOG.error(e.getLocalizedMessage(), e);
+			} finally {
+				deleteProbe(probe);
+			}
+			return Optional.empty();
+		}
+
+		private static Duration measureClockResolution() {
+			Duration clockResolution = Duration.ZERO;
+			for (int i = 0; i < 10; i++) {
+				Instant t1 = Instant.now();
+				Instant t2 = t1;
+				while (t2.compareTo(t1) <= 0) {
+					t2 = Instant.now();
+				}
+				Duration r = Duration.between(t1, t2);
+				if (r.compareTo(clockResolution) > 0) {
+					clockResolution = r;
+				}
+			}
+			return clockResolution;
+		}
+
+		private static void deleteProbe(Path probe) {
+			try {
+				FileUtils.delete(probe.toFile(),
+						FileUtils.SKIP_MISSING | FileUtils.RETRY);
+			} catch (IOException e) {
+				LOG.error(e.getMessage(), e);
+			}
+		}
+
+		private static Optional<FileStoreAttributes> readFromConfig(
+				FileStore s) {
+			StoredConfig userConfig;
+			try {
+				userConfig = SystemReader.getInstance().getUserConfig();
+			} catch (IOException | ConfigInvalidException e) {
+				LOG.error(JGitText.get().readFileStoreAttributesFailed, e);
+				return Optional.empty();
+			}
+			String key = getConfigKey(s);
+			Duration resolution = Duration.ofNanos(userConfig.getTimeUnit(
+					ConfigConstants.CONFIG_FILESYSTEM_SECTION, key,
+					ConfigConstants.CONFIG_KEY_TIMESTAMP_RESOLUTION,
+					UNDEFINED_DURATION.toNanos(), TimeUnit.NANOSECONDS));
+			if (UNDEFINED_DURATION.equals(resolution)) {
+				return Optional.empty();
+			}
+			Duration minRacyThreshold = Duration.ofNanos(userConfig.getTimeUnit(
+					ConfigConstants.CONFIG_FILESYSTEM_SECTION, key,
+					ConfigConstants.CONFIG_KEY_MIN_RACY_THRESHOLD,
+					UNDEFINED_DURATION.toNanos(), TimeUnit.NANOSECONDS));
+			FileStoreAttributes c = new FileStoreAttributes(resolution);
+			if (!UNDEFINED_DURATION.equals(minRacyThreshold)) {
+				c.minimalRacyInterval = minRacyThreshold;
+			}
+			return Optional.of(c);
+		}
+
+		private static void saveToConfig(FileStore s,
+				FileStoreAttributes c) {
+			StoredConfig userConfig;
+			try {
+				userConfig = SystemReader.getInstance().getUserConfig();
+			} catch (IOException | ConfigInvalidException e) {
+				LOG.error(JGitText.get().saveFileStoreAttributesFailed, e);
+				return;
+			}
+			long resolution = c.getFsTimestampResolution().toNanos();
+			TimeUnit resolutionUnit = getUnit(resolution);
+			long resolutionValue = resolutionUnit.convert(resolution,
+					TimeUnit.NANOSECONDS);
+
+			long minRacyThreshold = c.getMinimalRacyInterval().toNanos();
+			TimeUnit minRacyThresholdUnit = getUnit(minRacyThreshold);
+			long minRacyThresholdValue = minRacyThresholdUnit
+					.convert(minRacyThreshold, TimeUnit.NANOSECONDS);
+
+			final int max_retries = 5;
+			int retries = 0;
+			boolean succeeded = false;
+			String key = getConfigKey(s);
+			while (!succeeded && retries < max_retries) {
+				try {
+					userConfig.load();
+					userConfig.setString(
+							ConfigConstants.CONFIG_FILESYSTEM_SECTION, key,
+							ConfigConstants.CONFIG_KEY_TIMESTAMP_RESOLUTION,
+							String.format("%d %s", //$NON-NLS-1$
+									Long.valueOf(resolutionValue),
+									resolutionUnit.name().toLowerCase()));
+					userConfig.setString(
+							ConfigConstants.CONFIG_FILESYSTEM_SECTION, key,
+							ConfigConstants.CONFIG_KEY_MIN_RACY_THRESHOLD,
+							String.format("%d %s", //$NON-NLS-1$
+									Long.valueOf(minRacyThresholdValue),
+									minRacyThresholdUnit.name().toLowerCase()));
+					userConfig.save();
+					succeeded = true;
+				} catch (LockFailedException e) {
+					// race with another thread, wait a bit and try again
+					try {
+						retries++;
+						if (retries < max_retries) {
+							Thread.sleep(100);
+							LOG.debug("locking {} failed, retries {}/{}", //$NON-NLS-1$
+									userConfig, Integer.valueOf(retries),
+									Integer.valueOf(max_retries));
+						} else {
+							LOG.warn(MessageFormat.format(
+									JGitText.get().lockFailedRetry, userConfig,
+									Integer.valueOf(retries)));
+						}
+					} catch (InterruptedException e1) {
+						Thread.currentThread().interrupt();
+						break;
+					}
+				} catch (IOException e) {
+					LOG.error(MessageFormat.format(
+							JGitText.get().cannotSaveConfig, userConfig), e);
+					break;
+				} catch (ConfigInvalidException e) {
+					LOG.error(MessageFormat.format(
+							JGitText.get().repositoryConfigFileInvalid,
+							userConfig, e.getMessage()));
+					break;
+				}
+			}
+		}
+
+		private static String getConfigKey(FileStore s) {
+			final String storeKey;
+			if (SystemReader.getInstance().isWindows()) {
+				Object attribute = null;
+				try {
+					attribute = s.getAttribute("volume:vsn"); //$NON-NLS-1$
+				} catch (IOException ignored) {
+					// ignore
+				}
+				if (attribute instanceof Integer) {
+					storeKey = attribute.toString();
+				} else {
+					storeKey = s.name();
+				}
+			} else {
+				storeKey = s.name();
+			}
+			return javaVersionPrefix + storeKey;
+		}
+
+		private static TimeUnit getUnit(long nanos) {
+			TimeUnit unit;
+			if (nanos < 200_000L) {
+				unit = TimeUnit.NANOSECONDS;
+			} else if (nanos < 200_000_000L) {
+				unit = TimeUnit.MICROSECONDS;
+			} else {
+				unit = TimeUnit.MILLISECONDS;
+			}
+			return unit;
+		}
+
+		private final @NonNull Duration fsTimestampResolution;
+
+		private Duration minimalRacyInterval;
+
+		/**
+		 * @return the measured minimal interval after a file has been modified
+		 *         in which we cannot rely on lastModified to detect
+		 *         modifications
+		 */
+		public Duration getMinimalRacyInterval() {
+			return minimalRacyInterval;
+		}
+
+		/**
+		 * @return the measured filesystem timestamp resolution
+		 */
+		@NonNull
+		public Duration getFsTimestampResolution() {
+			return fsTimestampResolution;
+		}
+
+		/**
+		 * Construct a FileStoreAttributeCache entry for the given filesystem
+		 * timestamp resolution
+		 *
+		 * @param fsTimestampResolution
+		 */
+		public FileStoreAttributes(
+				@NonNull Duration fsTimestampResolution) {
+			this.fsTimestampResolution = fsTimestampResolution;
+			this.minimalRacyInterval = Duration.ZERO;
+		}
+
+		@SuppressWarnings({ "nls", "boxing" })
+		@Override
+		public String toString() {
+			return String.format(
+					"FileStoreAttributes[fsTimestampResolution=%,d µs, "
+							+ "minimalRacyInterval=%,d µs]",
+					fsTimestampResolution.toNanos() / 1000,
+					minimalRacyInterval.toNanos() / 1000);
+		}
+
+	}
+
 	/** The auto-detected implementation selected for this operating system and JRE. */
 	public static final FS DETECTED = detect();
 
@@ -193,6 +699,19 @@ public static FS detect() {
 	}
 
 	/**
+	 * Whether FileStore attributes should be determined asynchronously
+	 *
+	 * @param asynch
+	 *            whether FileStore attributes should be determined
+	 *            asynchronously. If false access to cached attributes may block
+	 *            for some seconds for the first call per FileStore
+	 * @since 5.1.9
+	 */
+	public static void setAsyncFileStoreAttributes(boolean asynch) {
+		FileStoreAttributes.setBackground(asynch);
+	}
+
+	/**
 	 * Auto-detect the appropriate file system abstraction, taking into account
 	 * the presence of a Cygwin installation on the system. Using jgit in
 	 * combination with Cygwin requires a more elaborate (and possibly slower)
@@ -219,6 +738,21 @@ public static FS detect(Boolean cygwinUsed) {
 		return factory.detect(cygwinUsed);
 	}
 
+	/**
+	 * Get cached FileStore attributes, if not yet available measure them using
+	 * a probe file under the given directory.
+	 *
+	 * @param dir
+	 *            the directory under which the probe file will be created to
+	 *            measure the timer resolution.
+	 * @return measured filesystem timestamp resolution
+	 * @since 5.1.9
+	 */
+	public static FileStoreAttributes getFileStoreAttributes(
+			@NonNull Path dir) {
+		return FileStoreAttributes.get(dir);
+	}
+
 	private volatile Holder<File> userHome;
 
 	private volatile Holder<File> gitSystemConfig;
@@ -330,12 +864,42 @@ public boolean supportsSymlinks() {
 	 * @return last modified time of f
 	 * @throws java.io.IOException
 	 * @since 3.0
+	 * @deprecated use {@link #lastModifiedInstant(Path)} instead
 	 */
+	@Deprecated
 	public long lastModified(File f) throws IOException {
 		return FileUtils.lastModified(f);
 	}
 
 	/**
+	 * Get the last modified time of a file system object. If the OS/JRE support
+	 * symbolic links, the modification time of the link is returned, rather
+	 * than that of the link target.
+	 *
+	 * @param p
+	 *            a {@link Path} object.
+	 * @return last modified time of p
+	 * @since 5.1.9
+	 */
+	public Instant lastModifiedInstant(Path p) {
+		return FileUtils.lastModifiedInstant(p);
+	}
+
+	/**
+	 * Get the last modified time of a file system object. If the OS/JRE support
+	 * symbolic links, the modification time of the link is returned, rather
+	 * than that of the link target.
+	 *
+	 * @param f
+	 *            a {@link File} object.
+	 * @return last modified time of p
+	 * @since 5.1.9
+	 */
+	public Instant lastModifiedInstant(File f) {
+		return FileUtils.lastModifiedInstant(f.toPath());
+	}
+
+	/**
 	 * Set the last modified time of a file system object. If the OS/JRE support
 	 * symbolic links, the link is modified, not the target,
 	 *
@@ -345,12 +909,29 @@ public long lastModified(File f) throws IOException {
 	 *            last modified time
 	 * @throws java.io.IOException
 	 * @since 3.0
+	 * @deprecated use {@link #setLastModified(Path, Instant)} instead
 	 */
+	@Deprecated
 	public void setLastModified(File f, long time) throws IOException {
 		FileUtils.setLastModified(f, time);
 	}
 
 	/**
+	 * Set the last modified time of a file system object. If the OS/JRE support
+	 * symbolic links, the link is modified, not the target,
+	 *
+	 * @param p
+	 *            a {@link Path} object.
+	 * @param time
+	 *            last modified time
+	 * @throws java.io.IOException
+	 * @since 5.1.9
+	 */
+	public void setLastModified(Path p, Instant time) throws IOException {
+		FileUtils.setLastModified(p, time);
+	}
+
+	/**
 	 * Get the length of a file or link, If the OS/JRE supports symbolic links
 	 * it's the length of the link, else the length of the target.
 	 *
@@ -654,12 +1235,15 @@ private boolean waitForProcessCompletion(IOException originalError) {
 							JGitText.get().commandClosedStderrButDidntExit,
 							desc, PROCESS_EXIT_TIMEOUT), -1);
 					fail.set(true);
+					return false;
 				}
 			} catch (InterruptedException e) {
-				LOG.error(MessageFormat.format(
-						JGitText.get().threadInterruptedWhileRunning, desc), e);
+				setError(originalError, MessageFormat.format(
+						JGitText.get().threadInterruptedWhileRunning, desc), -1);
+				fail.set(true);
+				return false;
 			}
-			return false;
+			return true;
 		}
 
 		private void setError(IOException e, String message, int exitCode) {
@@ -1183,7 +1767,7 @@ public int runProcess(ProcessBuilder processBuilder,
 			OutputStream outRedirect, OutputStream errRedirect, String stdinArgs)
 			throws IOException, InterruptedException {
 		InputStream in = (stdinArgs == null) ? null : new ByteArrayInputStream(
-				stdinArgs.getBytes(Constants.CHARACTER_ENCODING));
+						stdinArgs.getBytes(UTF_8));
 		return runProcess(processBuilder, outRedirect, errRedirect, in);
 	}
 
@@ -1414,9 +1998,19 @@ public long getCreationTime() {
 		/**
 		 * @return the time (milliseconds since 1970-01-01) when this object was
 		 *         last modified
+		 * @deprecated use getLastModifiedInstant instead
 		 */
+		@Deprecated
 		public long getLastModifiedTime() {
-			return lastModifiedTime;
+			return lastModifiedInstant.toEpochMilli();
+		}
+
+		/**
+		 * @return the time when this object was last modified
+		 * @since 5.1.9
+		 */
+		public Instant getLastModifiedInstant() {
+			return lastModifiedInstant;
 		}
 
 		private final boolean isDirectory;
@@ -1427,7 +2021,7 @@ public long getLastModifiedTime() {
 
 		private final long creationTime;
 
-		private final long lastModifiedTime;
+		private final Instant lastModifiedInstant;
 
 		private final boolean isExecutable;
 
@@ -1445,7 +2039,7 @@ public long getLastModifiedTime() {
 		Attributes(FS fs, File file, boolean exists, boolean isDirectory,
 				boolean isExecutable, boolean isSymbolicLink,
 				boolean isRegularFile, long creationTime,
-				long lastModifiedTime, long length) {
+				Instant lastModifiedInstant, long length) {
 			this.fs = fs;
 			this.file = file;
 			this.exists = exists;
@@ -1454,7 +2048,7 @@ public long getLastModifiedTime() {
 			this.isSymbolicLink = isSymbolicLink;
 			this.isRegularFile = isRegularFile;
 			this.creationTime = creationTime;
-			this.lastModifiedTime = lastModifiedTime;
+			this.lastModifiedInstant = lastModifiedInstant;
 			this.length = length;
 		}
 
@@ -1466,7 +2060,7 @@ public long getLastModifiedTime() {
 		 * @param path
 		 */
 		public Attributes(File path, FS fs) {
-			this(fs, path, false, false, false, false, false, 0L, 0L, 0L);
+			this(fs, path, false, false, false, false, false, 0L, EPOCH, 0L);
 		}
 
 		/**
@@ -1512,7 +2106,7 @@ public Attributes getAttributes(File path) {
 		boolean exists = isDirectory || isFile;
 		boolean canExecute = exists && !isDirectory && canExecute(path);
 		boolean isSymlink = false;
-		long lastModified = exists ? path.lastModified() : 0L;
+		Instant lastModified = exists ? lastModifiedInstant(path) : EPOCH;
 		long createTime = 0L;
 		return new Attributes(this, path, exists, isDirectory, canExecute,
 				isSymlink, isFile, createTime, lastModified, -1);
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 8795329..eda8afb 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
@@ -42,13 +42,18 @@
  */
 package org.eclipse.jgit.util;
 
+import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_CORE_SECTION;
+import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_SUPPORTSATOMICFILECREATION;
+
 import java.io.BufferedReader;
 import java.io.File;
 import java.io.IOException;
 import java.io.InputStreamReader;
 import java.io.PrintStream;
 import java.nio.charset.Charset;
+import java.nio.file.FileAlreadyExistsException;
 import java.nio.file.Files;
+import java.nio.file.InvalidPathException;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.nio.file.attribute.PosixFilePermission;
@@ -65,10 +70,9 @@
 import org.eclipse.jgit.errors.CommandFailedException;
 import org.eclipse.jgit.errors.ConfigInvalidException;
 import org.eclipse.jgit.internal.JGitText;
-import org.eclipse.jgit.lib.ConfigConstants;
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.storage.file.FileBasedConfig;
+import org.eclipse.jgit.lib.StoredConfig;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -85,7 +89,7 @@ public class FS_POSIX extends FS {
 
 	private volatile boolean supportsUnixNLink = true;
 
-	private volatile AtomicFileCreation supportsAtomicCreateNewFile = AtomicFileCreation.UNDEFINED;
+	private volatile AtomicFileCreation supportsAtomicFileCreation = AtomicFileCreation.UNDEFINED;
 
 	private enum AtomicFileCreation {
 		SUPPORTED, NOT_SUPPORTED, UNDEFINED
@@ -110,37 +114,6 @@ protected FS_POSIX(FS src) {
 		}
 	}
 
-	private void determineAtomicFileCreationSupport() {
-		// @TODO: enhance SystemReader to support this without copying code
-		AtomicFileCreation ret = getAtomicFileCreationSupportOption(
-				SystemReader.getInstance().openUserConfig(null, this));
-		if (ret == AtomicFileCreation.UNDEFINED
-				&& StringUtils.isEmptyOrNull(SystemReader.getInstance()
-						.getenv(Constants.GIT_CONFIG_NOSYSTEM_KEY))) {
-			ret = getAtomicFileCreationSupportOption(
-					SystemReader.getInstance().openSystemConfig(null, this));
-		}
-		supportsAtomicCreateNewFile = ret;
-	}
-
-	private AtomicFileCreation getAtomicFileCreationSupportOption(
-			FileBasedConfig config) {
-		try {
-			config.load();
-			String value = config.getString(ConfigConstants.CONFIG_CORE_SECTION,
-					null,
-					ConfigConstants.CONFIG_KEY_SUPPORTSATOMICFILECREATION);
-			if (value == null) {
-				return AtomicFileCreation.UNDEFINED;
-			}
-			return StringUtils.toBoolean(value)
-					? AtomicFileCreation.SUPPORTED
-					: AtomicFileCreation.NOT_SUPPORTED;
-		} catch (IOException | ConfigInvalidException e) {
-			return AtomicFileCreation.SUPPORTED;
-		}
-	}
-
 	/** {@inheritDoc} */
 	@Override
 	public FS newInstance() {
@@ -355,10 +328,24 @@ public File findHook(Repository repository, String hookName) {
 	/** {@inheritDoc} */
 	@Override
 	public boolean supportsAtomicCreateNewFile() {
-		if (supportsAtomicCreateNewFile == AtomicFileCreation.UNDEFINED) {
-			determineAtomicFileCreationSupport();
+		if (supportsAtomicFileCreation == AtomicFileCreation.UNDEFINED) {
+			try {
+				StoredConfig config = SystemReader.getInstance().getUserConfig();
+				String value = config.getString(CONFIG_CORE_SECTION, null,
+						CONFIG_KEY_SUPPORTSATOMICFILECREATION);
+				if (value != null) {
+					supportsAtomicFileCreation = StringUtils.toBoolean(value)
+							? AtomicFileCreation.SUPPORTED
+							: AtomicFileCreation.NOT_SUPPORTED;
+				} else {
+					supportsAtomicFileCreation = AtomicFileCreation.SUPPORTED;
+				}
+			} catch (IOException | ConfigInvalidException e) {
+				LOG.warn(JGitText.get().assumeAtomicCreateNewFile, e);
+				supportsAtomicFileCreation = AtomicFileCreation.SUPPORTED;
+			}
 		}
-		return supportsAtomicCreateNewFile == AtomicFileCreation.SUPPORTED;
+		return supportsAtomicFileCreation == AtomicFileCreation.SUPPORTED;
 	}
 
 	@Override
@@ -422,7 +409,7 @@ public boolean createNewFile(File lock) throws IOException {
 	 * An implementation of the File#createNewFile() semantics which can create
 	 * a unique file atomically also on NFS. If the config option
 	 * {@code core.supportsAtomicCreateNewFile = true} (which is the default)
-	 * then simply File#createNewFile() is called.
+	 * then simply Files#createFile() is called.
 	 *
 	 * But if {@code core.supportsAtomicCreateNewFile = false} then after
 	 * successful creation of the lock file a hard link to that lock file is
@@ -443,14 +430,17 @@ public boolean createNewFile(File lock) throws IOException {
 	 */
 	@Override
 	public LockToken createNewFileAtomic(File file) throws IOException {
-		if (!file.createNewFile()) {
+		Path path;
+		try {
+			path = file.toPath();
+			Files.createFile(path);
+		} catch (FileAlreadyExistsException | InvalidPathException e) {
 			return token(false, null);
 		}
 		if (supportsAtomicCreateNewFile() || !supportsUnixNLink) {
 			return token(true, null);
 		}
 		Path link = null;
-		Path path = file.toPath();
 		try {
 			link = Files.createLink(Paths.get(uniqueLinkPath(file)), path);
 			Integer nlink = (Integer) (Files.getAttribute(path,
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 9f99e28..7c07270 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
@@ -149,7 +149,7 @@ public FileVisitResult visitFile(Path file,
 									attrs.isSymbolicLink(),
 									attrs.isRegularFile(),
 									attrs.creationTime().toMillis(),
-									attrs.lastModifiedTime().toMillis(),
+									attrs.lastModifiedTime().toInstant(),
 									attrs.size());
 							result.add(new FileEntry(f, fs, attributes,
 									fileModeStrategy));
@@ -169,7 +169,7 @@ public FileVisitResult visitFileFailed(Path file,
 		if (result.isEmpty()) {
 			return NO_ENTRIES;
 		}
-		return result.toArray(new Entry[result.size()]);
+		return result.toArray(new Entry[0]);
 	}
 
 	/** {@inheritDoc} */
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 3393fbf..e5c8d9d 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
@@ -43,7 +43,7 @@
 
 package org.eclipse.jgit.util;
 
-import static org.eclipse.jgit.lib.Constants.CHARSET;
+import static java.nio.charset.StandardCharsets.UTF_8;
 
 import java.io.File;
 import java.io.PrintStream;
@@ -126,7 +126,7 @@ public File resolve(File dir, String pn) {
 			try {
 				w = readPipe(dir, //
 					new String[] { cygpath, "--windows", "--absolute", pn }, // //$NON-NLS-1$ //$NON-NLS-2$
-					CHARSET.name());
+					UTF_8.name());
 			} catch (CommandFailedException e) {
 				LOG.warn(e.getMessage());
 				return null;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/FileUtils.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/FileUtils.java
index f1e05e5..9f7d9a2 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/FileUtils.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/FileUtils.java
@@ -45,15 +45,20 @@
 
 package org.eclipse.jgit.util;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
+
 import java.io.File;
 import java.io.IOException;
+import java.nio.channels.FileChannel;
 import java.nio.file.AtomicMoveNotSupportedException;
 import java.nio.file.CopyOption;
 import java.nio.file.Files;
 import java.nio.file.InvalidPathException;
 import java.nio.file.LinkOption;
+import java.nio.file.NoSuchFileException;
 import java.nio.file.Path;
 import java.nio.file.StandardCopyOption;
+import java.nio.file.StandardOpenOption;
 import java.nio.file.attribute.BasicFileAttributeView;
 import java.nio.file.attribute.BasicFileAttributes;
 import java.nio.file.attribute.FileTime;
@@ -63,6 +68,7 @@
 import java.text.MessageFormat;
 import java.text.Normalizer;
 import java.text.Normalizer.Form;
+import java.time.Instant;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Locale;
@@ -71,11 +77,14 @@
 import org.eclipse.jgit.internal.JGitText;
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.util.FS.Attributes;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 /**
  * File Utilities
  */
 public class FileUtils {
+	private static final Logger LOG = LoggerFactory.getLogger(FileUtils.class);
 
 	/**
 	 * Option to delete given {@code File}
@@ -651,13 +660,37 @@ static boolean isSymlink(File file) {
 	 * @return lastModified attribute for given file, not following symbolic
 	 *         links
 	 * @throws IOException
+	 * @deprecated use {@link #lastModifiedInstant(Path)} instead which returns
+	 *             FileTime
 	 */
+	@Deprecated
 	static long lastModified(File file) throws IOException {
 		return Files.getLastModifiedTime(toPath(file), LinkOption.NOFOLLOW_LINKS)
 				.toMillis();
 	}
 
 	/**
+	 * @param path
+	 * @return lastModified attribute for given file, not following symbolic
+	 *         links
+	 */
+	static Instant lastModifiedInstant(Path path) {
+		try {
+			return Files.getLastModifiedTime(path, LinkOption.NOFOLLOW_LINKS)
+					.toInstant();
+		} catch (NoSuchFileException e) {
+			LOG.debug(
+					"Cannot read lastModifiedInstant since path {} does not exist", //$NON-NLS-1$
+					path);
+			return Instant.EPOCH;
+		} catch (IOException e) {
+			LOG.error(MessageFormat
+					.format(JGitText.get().readLastModifiedFailed, path), e);
+			return Instant.ofEpochMilli(path.toFile().lastModified());
+		}
+	}
+
+	/**
 	 * Return all the attributes of a file, without following symbolic links.
 	 *
 	 * @param file
@@ -675,11 +708,22 @@ static BasicFileAttributes fileAttributes(File file) throws IOException {
 	 * @param time
 	 * @throws IOException
 	 */
+	@Deprecated
 	static void setLastModified(File file, long time) throws IOException {
 		Files.setLastModifiedTime(toPath(file), FileTime.fromMillis(time));
 	}
 
 	/**
+	 * @param path
+	 * @param time
+	 * @throws IOException
+	 */
+	static void setLastModified(Path path, Instant time)
+			throws IOException {
+		Files.setLastModifiedTime(path, FileTime.from(time));
+	}
+
+	/**
 	 * @param file
 	 * @return {@code true} if the given file exists, not following symbolic
 	 *         links
@@ -725,7 +769,7 @@ public static long getLength(File file) throws IOException {
 		Path nioPath = toPath(file);
 		if (Files.isSymbolicLink(nioPath))
 			return Files.readSymbolicLink(nioPath).toString()
-					.getBytes(Constants.CHARSET).length;
+					.getBytes(UTF_8).length;
 		return Files.size(nioPath);
 	}
 
@@ -783,7 +827,7 @@ static Attributes getFileAttributesBasic(FS fs, File file) {
 					readAttributes.isSymbolicLink(),
 					readAttributes.isRegularFile(), //
 					readAttributes.creationTime().toMillis(), //
-					readAttributes.lastModifiedTime().toMillis(),
+					readAttributes.lastModifiedTime().toInstant(),
 					readAttributes.isSymbolicLink() ? Constants
 							.encode(readSymLink(file)).length
 							: readAttributes.size());
@@ -822,7 +866,7 @@ public static Attributes getFileAttributesPosix(FS fs, File file) {
 					readAttributes.isSymbolicLink(),
 					readAttributes.isRegularFile(), //
 					readAttributes.creationTime().toMillis(), //
-					readAttributes.lastModifiedTime().toMillis(),
+					readAttributes.lastModifiedTime().toInstant(),
 					readAttributes.size());
 			return attributes;
 		} catch (IOException e) {
@@ -906,4 +950,20 @@ public static String pathToString(File file) {
 		}
 		return path;
 	}
+
+	/**
+	 * Touch the given file
+	 *
+	 * @param f
+	 *            the file to touch
+	 * @throws IOException
+	 * @since 5.1.8
+	 */
+	public static void touch(Path f) throws IOException {
+		try (FileChannel fc = FileChannel.open(f, StandardOpenOption.CREATE,
+				StandardOpenOption.APPEND, StandardOpenOption.SYNC)) {
+			// touch
+		}
+		Files.setLastModifiedTime(f, FileTime.from(Instant.now()));
+	}
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/HttpSupport.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/HttpSupport.java
index 6f92b37..9190a59 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/HttpSupport.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/HttpSupport.java
@@ -44,7 +44,7 @@
 
 package org.eclipse.jgit.util;
 
-import static org.eclipse.jgit.lib.Constants.CHARACTER_ENCODING;
+import static java.nio.charset.StandardCharsets.UTF_8;
 
 import java.io.IOException;
 import java.io.UnsupportedEncodingException;
@@ -181,7 +181,7 @@ public static void encode(StringBuilder urlstr, String key) {
 		if (key == null || key.length() == 0)
 			return;
 		try {
-			urlstr.append(URLEncoder.encode(key, CHARACTER_ENCODING));
+			urlstr.append(URLEncoder.encode(key, UTF_8.name()));
 		} catch (UnsupportedEncodingException e) {
 			throw new RuntimeException(JGitText.get().couldNotURLEncodeToUTF8, e);
 		}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/QuotedString.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/QuotedString.java
index 4d58d06..a55cad3 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/QuotedString.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/QuotedString.java
@@ -43,6 +43,8 @@
 
 package org.eclipse.jgit.util;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
+
 import java.util.Arrays;
 
 import org.eclipse.jgit.lib.Constants;
@@ -181,7 +183,7 @@ public String dequote(byte[] in, int ip, int ie) {
 					continue;
 				}
 			}
-			return RawParseUtils.decode(Constants.CHARSET, r, 0, rPtr);
+			return RawParseUtils.decode(UTF_8, r, 0, rPtr);
 		}
 	}
 
@@ -294,7 +296,7 @@ public String quote(String instr) {
 		public String dequote(byte[] in, int inPtr, int inEnd) {
 			if (2 <= inEnd - inPtr && in[inPtr] == '"' && in[inEnd - 1] == '"')
 				return dq(in, inPtr + 1, inEnd - 1);
-			return RawParseUtils.decode(Constants.CHARSET, in, inPtr, inEnd);
+			return RawParseUtils.decode(UTF_8, in, inPtr, inEnd);
 		}
 
 		private static String dq(byte[] in, int inPtr, int inEnd) {
@@ -370,7 +372,7 @@ private static String dq(byte[] in, int inPtr, int inEnd) {
 				}
 			}
 
-			return RawParseUtils.decode(Constants.CHARSET, r, 0, rPtr);
+			return RawParseUtils.decode(UTF_8, r, 0, rPtr);
 		}
 
 		private GitPathStyle() {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/RawParseUtils.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/RawParseUtils.java
index c22c8de..28f406a 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/RawParseUtils.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/RawParseUtils.java
@@ -549,6 +549,62 @@ public static final int nextLF(byte[] b, int ptr, char chrA) {
 	}
 
 	/**
+	 * Locate the end of the header.  Note that headers may be
+	 * more than one line long.
+	 * @param b
+	 *            buffer to scan.
+	 * @param ptr
+	 *            position within buffer to start looking for the end-of-header.
+	 * @return new position just after the header.  This is either
+	 * b.length, or the index of the header's terminating newline.
+	 * @since 5.1
+	 */
+	public static final int headerEnd(final byte[] b, int ptr) {
+		final int sz = b.length;
+		while (ptr < sz) {
+			final byte c = b[ptr++];
+			if (c == '\n' && (ptr == sz || b[ptr] != ' ')) {
+				return ptr - 1;
+			}
+		}
+		return ptr - 1;
+	}
+
+	/**
+	 * Find the start of the contents of a given header.
+	 *
+	 * @param b
+	 *            buffer to scan.
+	 * @param headerName
+	 *            header to search for
+	 * @param ptr
+	 *            position within buffer to start looking for header at.
+	 * @return new position at the start of the header's contents, -1 for
+	 *         not found
+	 * @since 5.1
+	 */
+	public static final int headerStart(byte[] headerName, byte[] b, int ptr) {
+		// Start by advancing to just past a LF or buffer start
+		if (ptr != 0) {
+			ptr = nextLF(b, ptr - 1);
+		}
+		while (ptr < b.length - (headerName.length + 1)) {
+			boolean found = true;
+			for (int i = 0; i < headerName.length; i++) {
+				if (headerName[i] != b[ptr++]) {
+					found = false;
+					break;
+				}
+			}
+			if (found && b[ptr++] == ' ') {
+				return ptr;
+			}
+			ptr = nextLF(b, ptr);
+		}
+		return -1;
+	}
+
+	/**
 	 * Locate the first position before a given character.
 	 *
 	 * @param b
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/SimpleLruCache.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/SimpleLruCache.java
new file mode 100644
index 0000000..709d9ee
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/SimpleLruCache.java
@@ -0,0 +1,253 @@
+/*
+ * Copyright (C) 2019, Marc Strapetz <marc.strapetz@syntevo.com>
+ * 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;
+
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+import org.eclipse.jgit.annotations.NonNull;
+import org.eclipse.jgit.internal.JGitText;
+
+/**
+ * Simple limited size cache based on ConcurrentHashMap purging entries in LRU
+ * order when reaching size limit
+ *
+ * @param <K>
+ *            the type of keys maintained by this cache
+ * @param <V>
+ *            the type of mapped values
+ *
+ * @since 5.1.9
+ */
+public class SimpleLruCache<K, V> {
+
+	private static class Entry<K, V> {
+
+		private final K key;
+
+		private final V value;
+
+		// pseudo clock timestamp of the last access to this entry
+		private volatile long lastAccessed;
+
+		private long lastAccessedSorting;
+
+		Entry(K key, V value, long lastAccessed) {
+			this.key = key;
+			this.value = value;
+			this.lastAccessed = lastAccessed;
+		}
+
+		void copyAccessTime() {
+			lastAccessedSorting = lastAccessed;
+		}
+
+		@SuppressWarnings("nls")
+		@Override
+		public String toString() {
+			return "Entry [lastAccessed=" + lastAccessed + ", key=" + key
+					+ ", value=" + value + "]";
+		}
+	}
+
+	private Lock lock = new ReentrantLock();
+
+	private Map<K, Entry<K,V>> map = new ConcurrentHashMap<>();
+
+	private volatile int maximumSize;
+
+	private int purgeSize;
+
+	// pseudo clock to implement LRU order of access to entries
+	private volatile long time = 0L;
+
+	private static void checkPurgeFactor(float purgeFactor) {
+		if (purgeFactor <= 0 || purgeFactor >= 1) {
+			throw new IllegalArgumentException(
+					MessageFormat.format(JGitText.get().invalidPurgeFactor,
+							Float.valueOf(purgeFactor)));
+		}
+	}
+
+	private static int purgeSize(int maxSize, float purgeFactor) {
+		return (int) ((1 - purgeFactor) * maxSize);
+	}
+
+	/**
+	 * Create a new cache
+	 *
+	 * @param maxSize
+	 *            maximum size of the cache, to reduce need for synchronization
+	 *            this is not a hard limit. The real size of the cache could be
+	 *            slightly above this maximum if multiple threads put new values
+	 *            concurrently
+	 * @param purgeFactor
+	 *            when the size of the map reaches maxSize the oldest entries
+	 *            will be purged to free up some space for new entries,
+	 *            {@code purgeFactor} is the fraction of {@code maxSize} to
+	 *            purge when this happens
+	 */
+	public SimpleLruCache(int maxSize, float purgeFactor) {
+		checkPurgeFactor(purgeFactor);
+		this.maximumSize = maxSize;
+		this.purgeSize = purgeSize(maxSize, purgeFactor);
+	}
+
+	/**
+	 * Returns the value to which the specified key is mapped, or {@code null}
+	 * if this map contains no mapping for the key.
+	 *
+	 * <p>
+	 * More formally, if this cache contains a mapping from a key {@code k} to a
+	 * value {@code v} such that {@code key.equals(k)}, then this method returns
+	 * {@code v}; otherwise it returns {@code null}. (There can be at most one
+	 * such mapping.)
+	 *
+	 * @param key
+	 *            the key
+	 *
+	 * @throws NullPointerException
+	 *             if the specified key is null
+	 *
+	 * @return value mapped for this key, or {@code null} if no value is mapped
+	 */
+	public V get(Object key) {
+		Entry<K, V> entry = map.get(key);
+		if (entry != null) {
+			entry.lastAccessed = ++time;
+			return entry.value;
+		}
+		return null;
+	}
+
+	/**
+	 * Maps the specified key to the specified value in this cache. Neither the
+	 * key nor the value can be null.
+	 *
+	 * <p>
+	 * The value can be retrieved by calling the {@code get} method with a key
+	 * that is equal to the original key.
+	 *
+	 * @param key
+	 *            key with which the specified value is to be associated
+	 * @param value
+	 *            value to be associated with the specified key
+	 * @return the previous value associated with {@code key}, or {@code null}
+	 *         if there was no mapping for {@code key}
+	 * @throws NullPointerException
+	 *             if the specified key or value is null
+	 */
+	public V put(@NonNull K key, @NonNull V value) {
+		map.put(key, new Entry<>(key, value, ++time));
+		if (map.size() > maximumSize) {
+			purge();
+		}
+		return value;
+	}
+
+	/**
+	 * Returns the current size of this cache
+	 *
+	 * @return the number of key-value mappings in this cache
+	 */
+	public int size() {
+		return map.size();
+	}
+
+	/**
+	 * Reconfigures the cache. If {@code maxSize} is reduced some entries will
+	 * be purged.
+	 *
+	 * @param maxSize
+	 *            maximum size of the cache
+	 *
+	 * @param purgeFactor
+	 *            when the size of the map reaches maxSize the oldest entries
+	 *            will be purged to free up some space for new entries,
+	 *            {@code purgeFactor} is the fraction of {@code maxSize} to
+	 *            purge when this happens
+	 */
+	public void configure(int maxSize, float purgeFactor) {
+		lock.lock();
+		try {
+			checkPurgeFactor(purgeFactor);
+			this.maximumSize = maxSize;
+			this.purgeSize = purgeSize(maxSize, purgeFactor);
+			if (map.size() >= maximumSize) {
+				purge();
+			}
+		} finally {
+			lock.unlock();
+		}
+	}
+
+	private void purge() {
+		// don't try to compete if another thread already has the lock
+		if (lock.tryLock()) {
+			try {
+				List<Entry> entriesToPurge = new ArrayList<>(map.values());
+				// copy access times to avoid other threads interfere with
+				// sorting
+				for (Entry e : entriesToPurge) {
+					e.copyAccessTime();
+				}
+				Collections.sort(entriesToPurge,
+						Comparator.comparingLong(o -> -o.lastAccessedSorting));
+				for (int index = purgeSize; index < entriesToPurge
+						.size(); index++) {
+					map.remove(entriesToPurge.get(index).key);
+				}
+			} finally {
+				lock.unlock();
+			}
+		}
+	}
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/Stats.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/Stats.java
new file mode 100644
index 0000000..e9307d3
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/Stats.java
@@ -0,0 +1,132 @@
+/*
+ * 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;
+
+/**
+ * Simple double statistics, computed incrementally, variance and standard
+ * deviation using Welford's online algorithm, see
+ * https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance#Welford's_online_algorithm
+ *
+ * @since 5.1.9
+ */
+public class Stats {
+	private int n = 0;
+
+	private double avg = 0.0;
+
+	private double min = 0.0;
+
+	private double max = 0.0;
+
+	private double sum = 0.0;
+
+	/**
+	 * Add a value
+	 *
+	 * @param x
+	 *            value
+	 */
+	public void add(double x) {
+		n++;
+		min = n == 1 ? x : Math.min(min, x);
+		max = n == 1 ? x : Math.max(max, x);
+		double d = x - avg;
+		avg += d / n;
+		sum += d * d * (n - 1) / n;
+	}
+
+	/**
+	 * @return number of the added values
+	 */
+	public int count() {
+		return n;
+	}
+
+	/**
+	 * @return minimum of the added values
+	 */
+	public double min() {
+		if (n < 1) {
+			return Double.NaN;
+		}
+		return min;
+	}
+
+	/**
+	 * @return maximum of the added values
+	 */
+	public double max() {
+		if (n < 1) {
+			return Double.NaN;
+		}
+		return max;
+	}
+
+	/**
+	 * @return average of the added values
+	 */
+
+	public double avg() {
+		if (n < 1) {
+			return Double.NaN;
+		}
+		return avg;
+	}
+
+	/**
+	 * @return variance of the added values
+	 */
+	public double var() {
+		if (n < 2) {
+			return Double.NaN;
+		}
+		return sum / (n - 1);
+	}
+
+	/**
+	 * @return standard deviation of the added values
+	 */
+	public double stddev() {
+		return Math.sqrt(this.var());
+	}
+}
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 953c976..cccac66 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/SystemReader.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/SystemReader.java
@@ -47,6 +47,7 @@
 package org.eclipse.jgit.util;
 
 import java.io.File;
+import java.io.IOException;
 import java.net.InetAddress;
 import java.net.UnknownHostException;
 import java.security.AccessController;
@@ -55,13 +56,19 @@
 import java.text.SimpleDateFormat;
 import java.util.Locale;
 import java.util.TimeZone;
+import java.util.concurrent.atomic.AtomicReference;
 
+import org.eclipse.jgit.errors.ConfigInvalidException;
 import org.eclipse.jgit.errors.CorruptObjectException;
 import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.ObjectChecker;
+import org.eclipse.jgit.lib.StoredConfig;
 import org.eclipse.jgit.storage.file.FileBasedConfig;
 import org.eclipse.jgit.util.time.MonotonicClock;
 import org.eclipse.jgit.util.time.MonotonicSystemClock;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 /**
  * Interface to read values from the system.
@@ -72,6 +79,10 @@
  * </p>
  */
 public abstract class SystemReader {
+
+	private final static Logger LOG = LoggerFactory
+			.getLogger(SystemReader.class);
+
 	private static final SystemReader DEFAULT;
 
 	private static Boolean isMacOS;
@@ -99,28 +110,31 @@ public String getProperty(String key) {
 
 		@Override
 		public FileBasedConfig openSystemConfig(Config parent, FS fs) {
-			File configFile = fs.getGitSystemConfig();
-			if (configFile == null) {
-				return new FileBasedConfig(null, fs) {
-					@Override
-					public void load() {
-						// empty, do not load
-					}
-
-					@Override
-					public boolean isOutdated() {
-						// regular class would bomb here
-						return false;
-					}
-				};
+			if (StringUtils
+					.isEmptyOrNull(getenv(Constants.GIT_CONFIG_NOSYSTEM_KEY))) {
+				File configFile = fs.getGitSystemConfig();
+				if (configFile != null) {
+					return new FileBasedConfig(parent, configFile, fs);
+				}
 			}
-			return new FileBasedConfig(parent, configFile, fs);
+			return new FileBasedConfig(parent, null, fs) {
+				@Override
+				public void load() {
+					// empty, do not load
+				}
+
+				@Override
+				public boolean isOutdated() {
+					// regular class would bomb here
+					return false;
+				}
+			};
 		}
 
 		@Override
 		public FileBasedConfig openUserConfig(Config parent, FS fs) {
-			final File home = fs.userHome();
-			return new FileBasedConfig(parent, new File(home, ".gitconfig"), fs); //$NON-NLS-1$
+			return new FileBasedConfig(parent, new File(fs.userHome(), ".gitconfig"), //$NON-NLS-1$
+					fs);
 		}
 
 		@Override
@@ -149,19 +163,19 @@ public int getTimezone(long when) {
 		}
 	}
 
-	private static SystemReader INSTANCE = DEFAULT;
+	private static volatile SystemReader INSTANCE = DEFAULT;
 
 	/**
-	 * Get time since epoch, with up to millisecond resolution.
+	 * Get the current SystemReader instance
 	 *
-	 * @return time since epoch, with up to millisecond resolution.
+	 * @return the current SystemReader instance.
 	 */
 	public static SystemReader getInstance() {
 		return INSTANCE;
 	}
 
 	/**
-	 * Set the new instance to use when accessing properties.
+	 * Set a new SystemReader instance to use when accessing properties.
 	 *
 	 * @param newReader
 	 *            the new instance to use when accessing properties, or null for
@@ -180,6 +194,10 @@ public static void setInstance(SystemReader newReader) {
 
 	private ObjectChecker platformChecker;
 
+	private AtomicReference<FileBasedConfig> systemConfig = new AtomicReference<>();
+
+	private AtomicReference<FileBasedConfig> userConfig = new AtomicReference<>();
+
 	private void init() {
 		// Creating ObjectChecker must be deferred. Unit tests change
 		// behavior of is{Windows,MacOS} in constructor of subclass.
@@ -225,7 +243,10 @@ protected final void setPlatformChecker() {
 	public abstract String getProperty(String key);
 
 	/**
-	 * Open the git configuration found in the user home
+	 * Open the git configuration found in the user home. Use
+	 * {@link #getUserConfig()} to get the current git configuration in the user
+	 * home since it manages automatic reloading when the gitconfig file was
+	 * modified and avoids unnecessary reloads.
 	 *
 	 * @param parent
 	 *            a config with values not found directly in the returned config
@@ -237,7 +258,10 @@ protected final void setPlatformChecker() {
 	public abstract FileBasedConfig openUserConfig(Config parent, FS fs);
 
 	/**
-	 * Open the gitconfig configuration found in the system-wide "etc" directory
+	 * Open the gitconfig configuration found in the system-wide "etc"
+	 * directory. Use {@link #getSystemConfig()} to get the current system-wide
+	 * git configuration since it manages automatic reloading when the gitconfig
+	 * file was modified and avoids unnecessary reloads.
 	 *
 	 * @param parent
 	 *            a config with values not found directly in the returned
@@ -251,6 +275,65 @@ protected final void setPlatformChecker() {
 	public abstract FileBasedConfig openSystemConfig(Config parent, FS fs);
 
 	/**
+	 * Get the git configuration found in the user home. The configuration will
+	 * be reloaded automatically if the configuration file was modified. Also
+	 * reloads the system config if the system config file was modified. If the
+	 * configuration file wasn't modified returns the cached configuration.
+	 *
+	 * @return the git configuration found in the user home
+	 * @throws ConfigInvalidException
+	 *             if configuration is invalid
+	 * @throws IOException
+	 *             if something went wrong when reading files
+	 * @since 5.1.9
+	 */
+	public StoredConfig getUserConfig()
+			throws IOException, ConfigInvalidException {
+		FileBasedConfig c = userConfig.get();
+		if (c == null) {
+			userConfig.compareAndSet(null,
+					openUserConfig(getSystemConfig(), FS.DETECTED));
+			c = userConfig.get();
+		} else {
+			// Ensure the parent is up to date
+			getSystemConfig();
+		}
+		if (c.isOutdated()) {
+			LOG.debug("loading user config {}", userConfig); //$NON-NLS-1$
+			c.load();
+		}
+		return c;
+	}
+
+	/**
+	 * Get the gitconfig configuration found in the system-wide "etc" directory.
+	 * The configuration will be reloaded automatically if the configuration
+	 * file was modified otherwise returns the cached system level config.
+	 *
+	 * @return the gitconfig configuration found in the system-wide "etc"
+	 *         directory
+	 * @throws ConfigInvalidException
+	 *             if configuration is invalid
+	 * @throws IOException
+	 *             if something went wrong when reading files
+	 * @since 5.1.9
+	 */
+	public StoredConfig getSystemConfig()
+			throws IOException, ConfigInvalidException {
+		FileBasedConfig c = systemConfig.get();
+		if (c == null) {
+			systemConfig.compareAndSet(null,
+					openSystemConfig(null, FS.DETECTED));
+			c = systemConfig.get();
+		}
+		if (c.isOutdated()) {
+			LOG.debug("loading system config {}", systemConfig); //$NON-NLS-1$
+			c.load();
+		}
+		return c;
+	}
+
+	/**
 	 * Get the current system time
 	 *
 	 * @return the current system time
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/MessageWriter.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/MessageWriter.java
index 94e5ef3..12d809b 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/MessageWriter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/MessageWriter.java
@@ -43,13 +43,14 @@
 
 package org.eclipse.jgit.util.io;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
+
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.OutputStream;
 import java.io.OutputStreamWriter;
 import java.io.Writer;
 
-import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.util.RawParseUtils;
 
 /**
@@ -80,7 +81,7 @@ public class MessageWriter extends Writer {
 	 */
 	public MessageWriter() {
 		buf = new ByteArrayOutputStream();
-		enc = new OutputStreamWriter(getRawStream(), Constants.CHARSET);
+		enc = new OutputStreamWriter(getRawStream(), UTF_8);
 	}
 
 	/** {@inheritDoc} */
diff --git a/pom.xml b/pom.xml
index 3347847..22ad2ce 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.0.4-SNAPSHOT</version>
+  <version>5.1.11-SNAPSHOT</version>
 
   <name>JGit - Parent</name>
   <url>${jgit-url}</url>
@@ -198,7 +198,7 @@
     <bundle-manifest>${project.build.directory}/META-INF/MANIFEST.MF</bundle-manifest>
 
     <jgit-last-release-version>4.11.0.201803080745-r</jgit-last-release-version>
-    <jsch-version>0.1.54</jsch-version>
+    <jsch-version>0.1.55</jsch-version>
     <jzlib-version>1.1.1</jzlib-version>
     <javaewah-version>1.1.6</javaewah-version>
     <junit-version>4.12</junit-version>
@@ -207,17 +207,19 @@
     <commons-compress-version>1.15</commons-compress-version>
     <osgi-core-version>4.3.1</osgi-core-version>
     <servlet-api-version>3.1.0</servlet-api-version>
-    <jetty-version>9.4.8.v20171121</jetty-version>
-    <japicmp-version>0.11.0</japicmp-version>
-    <httpclient-version>4.5.2</httpclient-version>
-    <httpcore-version>4.4.6</httpcore-version>
+    <jetty-version>9.4.11.v20180605</jetty-version>
+    <japicmp-version>0.14.1</japicmp-version>
+    <httpclient-version>4.5.6</httpclient-version>
+    <httpcore-version>4.4.10</httpcore-version>
     <slf4j-version>1.7.2</slf4j-version>
     <log4j-version>1.2.15</log4j-version>
-    <maven-javadoc-plugin-version>3.0.1</maven-javadoc-plugin-version>
-    <tycho-extras-version>1.1.0</tycho-extras-version>
+    <maven-javadoc-plugin-version>3.1.0</maven-javadoc-plugin-version>
+    <tycho-extras-version>1.3.0</tycho-extras-version>
     <gson-version>2.8.2</gson-version>
-    <spotbugs-maven-plugin-version>3.1.2</spotbugs-maven-plugin-version>
-    <maven-surefire-report-plugin-version>2.20.1</maven-surefire-report-plugin-version>
+    <spotbugs-maven-plugin-version>3.1.12</spotbugs-maven-plugin-version>
+    <maven-surefire-plugin-version>3.0.0-M3</maven-surefire-plugin-version>
+    <maven-surefire-report-plugin-version>${maven-surefire-plugin-version}</maven-surefire-report-plugin-version>
+    <maven-compiler-plugin-version>3.8.1</maven-compiler-plugin-version>
 
     <!-- Properties to enable jacoco code coverage analysis -->
     <sonar.core.codeCoveragePlugin>jacoco</sonar.core.codeCoveragePlugin>
@@ -249,7 +251,7 @@
         <plugin>
           <groupId>org.apache.maven.plugins</groupId>
           <artifactId>maven-jar-plugin</artifactId>
-          <version>3.0.2</version>
+          <version>3.1.2</version>
           <configuration>
             <archive>
               <manifestEntries>
@@ -268,13 +270,13 @@
 
         <plugin>
           <artifactId>maven-clean-plugin</artifactId>
-          <version>3.0.0</version>
+          <version>3.1.0</version>
         </plugin>
 
         <plugin>
           <groupId>org.apache.maven.plugins</groupId>
           <artifactId>maven-shade-plugin</artifactId>
-          <version>3.1.0</version>
+          <version>3.2.1</version>
         </plugin>
 
         <plugin>
@@ -292,7 +294,7 @@
         <plugin>
           <groupId>org.apache.maven.plugins</groupId>
           <artifactId>maven-source-plugin</artifactId>
-          <version>3.0.1</version>
+          <version>3.1.0</version>
         </plugin>
 
         <plugin>
@@ -304,7 +306,7 @@
         <plugin>
           <groupId>org.apache.maven.plugins</groupId>
           <artifactId>maven-surefire-plugin</artifactId>
-          <version>2.20.1</version>
+          <version>${maven-surefire-plugin-version}</version>
           <configuration>
             <forkCount>${test-fork-count}</forkCount>
             <reuseForks>true</reuseForks>
@@ -337,7 +339,7 @@
         <plugin>
           <groupId>org.apache.maven.plugins</groupId>
           <artifactId>maven-pmd-plugin</artifactId>
-          <version>3.8</version>
+          <version>3.12.0</version>
           <configuration>
             <sourceEncoding>utf-8</sourceEncoding>
             <minimumTokens>100</minimumTokens>
@@ -360,7 +362,7 @@
         <plugin>
           <groupId>org.eclipse.cbi.maven.plugins</groupId>
           <artifactId>eclipse-jarsigner-plugin</artifactId>
-          <version>1.1.4</version>
+          <version>1.1.5</version>
         </plugin>
         <plugin>
           <groupId>org.eclipse.tycho.extras</groupId>
@@ -375,17 +377,17 @@
         <plugin>
           <groupId>org.jacoco</groupId>
           <artifactId>jacoco-maven-plugin</artifactId>
-          <version>0.7.9</version>
+          <version>0.8.4</version>
         </plugin>
         <plugin>
           <groupId>org.apache.maven.plugins</groupId>
           <artifactId>maven-site-plugin</artifactId>
-          <version>3.6</version>
+          <version>3.7.1</version>
           <dependencies>
             <dependency><!-- add support for ssh/scp -->
               <groupId>org.apache.maven.wagon</groupId>
               <artifactId>wagon-ssh</artifactId>
-              <version>2.12</version>
+              <version>3.1.0</version>
             </dependency>
           </dependencies>
         </plugin>
@@ -397,12 +399,32 @@
         <plugin>
           <groupId>org.apache.maven.plugins</groupId>
           <artifactId>maven-jxr-plugin</artifactId>
-          <version>2.5</version>
+          <version>3.0.0</version>
         </plugin>
         <plugin>
           <groupId>org.apache.maven.plugins</groupId>
           <artifactId>maven-project-info-reports-plugin</artifactId>
-          <version>2.9</version>
+          <version>3.0.0</version>
+        </plugin>
+        <plugin>
+          <groupId>org.apache.maven.plugins</groupId>
+          <artifactId>maven-deploy-plugin</artifactId>
+          <version>3.0.0-M1</version>
+        </plugin>
+        <plugin>
+          <groupId>org.apache.maven.plugins</groupId>
+          <artifactId>maven-install-plugin</artifactId>
+          <version>3.0.0-M1</version>
+        </plugin>
+        <plugin>
+          <groupId>org.apache.maven.plugins</groupId>
+          <artifactId>maven-compiler-plugin</artifactId>
+          <version>${maven-compiler-plugin-version}</version>
+        </plugin>
+        <plugin>
+          <groupId>org.apache.maven.plugins</groupId>
+          <artifactId>maven-resources-plugin</artifactId>
+          <version>3.1.0</version>
         </plugin>
       </plugins>
     </pluginManagement>
@@ -411,7 +433,7 @@
       <plugin>
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-enforcer-plugin</artifactId>
-        <version>3.0.0-M1</version>
+        <version>3.0.0-M2</version>
         <executions>
           <execution>
             <id>enforce-maven</id>
@@ -744,7 +766,7 @@
         <plugins>
           <plugin>
             <artifactId>maven-compiler-plugin</artifactId>
-            <version>3.7.0</version>
+            <version>${maven-compiler-plugin-version}</version>
             <configuration>
               <encoding>UTF-8</encoding>
               <source>1.8</source>
@@ -772,6 +794,9 @@
                 <configuration>
                   <compilerId>javac-with-errorprone</compilerId>
                   <forceJavacCompilerUse>true</forceJavacCompilerUse>
+                  <compilerArgs>
+                    <arg>-Xep:ExpectedExceptionChecker:ERROR</arg>
+                  </compilerArgs>
                   <excludes>
                     <exclude>org/eclipse/jgit/transport/InsecureCipherFactory.java</exclude>
                   </excludes>
@@ -782,19 +807,19 @@
               <dependency>
                 <groupId>org.codehaus.plexus</groupId>
                 <artifactId>plexus-compiler-javac</artifactId>
-                <version>2.8.2</version>
+                <version>2.8.5</version>
               </dependency>
               <dependency>
                 <groupId>org.codehaus.plexus</groupId>
                 <artifactId>plexus-compiler-javac-errorprone</artifactId>
-                <version>2.8.4</version>
+                <version>2.8.5</version>
               </dependency>
               <!-- override plexus-compiler-javac-errorprone's dependency on
                   Error Prone with the latest version -->
               <dependency>
                 <groupId>com.google.errorprone</groupId>
                 <artifactId>error_prone_core</artifactId>
-                <version>2.3.1</version>
+                <version>2.3.3</version>
               </dependency>
             </dependencies>
           </plugin>
@@ -810,7 +835,7 @@
         <plugins>
           <plugin>
             <artifactId>maven-compiler-plugin</artifactId>
-            <version>3.7.0</version>
+            <version>${maven-compiler-plugin-version}</version>
             <configuration>
               <compilerId>eclipse</compilerId>
               <encoding>UTF-8</encoding>
@@ -827,12 +852,12 @@
               <dependency>
                 <groupId>org.codehaus.plexus</groupId>
                 <artifactId>plexus-compiler-eclipse</artifactId>
-                <version>2.8.4</version>
+                <version>2.8.5</version>
               </dependency>
               <dependency>
                 <groupId>org.eclipse.jdt</groupId>
                 <artifactId>ecj</artifactId>
-                <version>3.13.102</version>
+                <version>3.17.0</version>
               </dependency>
             </dependencies>
           </plugin>