Merge branch 'stable-4.7' into stable-4.8

* stable-4.7:
  Update Oxygen Orbit p2 repository to R20170516192513
  Fix exception handling for opening bitmap index files

Change-Id: I1e4fcf84506ff4316567bbb1713e84d8d196c2a1
Signed-off-by: Matthias Sohn <matthias.sohn@sap.com>
diff --git a/WORKSPACE b/WORKSPACE
index efa161b..8251578 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -105,46 +105,46 @@
     sha1 = "a60a5e993c98c864010053cb901b7eab25306568",
 )
 
-JETTY_VER = "9.3.17.v20170317"
+JETTY_VER = "9.4.5.v20170502"
 
 maven_jar(
     name = "jetty_servlet",
     artifact = "org.eclipse.jetty:jetty-servlet:" + JETTY_VER,
-    sha1 = "ed6986b0d0ca7b9b0f9015c9efb80442e3043a8e",
-    src_sha1 = "ee6b4784a00a92e5c1b6111033b7ae41ac6052a3",
+    sha1 = "394a535b76ca7399b25be3266f06f614e020517e",
+    src_sha1 = "4e85803c8d539aa0a8389e113095ef86032ac425",
 )
 
 maven_jar(
     name = "jetty_security",
     artifact = "org.eclipse.jetty:jetty-security:" + JETTY_VER,
-    sha1 = "ca52535569445682d42aaa97c7039442719a0507",
-    src_sha1 = "2ff9f4fb18b320fd5a0272a427bacc4d5fe7bc86",
+    sha1 = "4f4fc4cbe3504b6c91143ee37b38a1f3de2dcc72",
+    src_sha1 = "2124a757c87eacea7ad6507be6a415b5b51139b5",
 )
 
 maven_jar(
     name = "jetty_server",
     artifact = "org.eclipse.jetty:jetty-server:" + JETTY_VER,
-    sha1 = "194e9a02e6ba249ef4a3f4bd56b4993087992299",
-    src_sha1 = "0c9bd572f530c411592aefb71781ecca0b3719a9",
+    sha1 = "b4d30340213c3d2a5f908860ba170c5a697829be",
+    src_sha1 = "295d873f609a0e2863f33b5dbc8906ca348f1107",
 )
 
 maven_jar(
     name = "jetty_http",
     artifact = "org.eclipse.jetty:jetty-http:" + JETTY_VER,
-    sha1 = "6c02d728e15d4868486254039c867a1ac3e4a52e",
-    src_sha1 = "3c0a2a82792f268631b4fefd77be9f126ec974b1",
+    sha1 = "c51b8a6a67d64672889249dd958edd77bff8fc0c",
+    src_sha1 = "c1bee39aeb565a4f26852b1851192d98ab611dbc",
 )
 
 maven_jar(
     name = "jetty_io",
     artifact = "org.eclipse.jetty:jetty-io:" + JETTY_VER,
-    sha1 = "756a8cd2a1cbfb84a94973b6332dd3eccd47c0cd",
-    src_sha1 = "a9afa99cccb19b441364fa805d027f457cbbb136",
+    sha1 = "76086f955d4e943396b8f340fd5bae3ce4da19d9",
+    src_sha1 = "8d41e410b2f0dd284a6e199ed08f45ef7ab2acf1",
 )
 
 maven_jar(
     name = "jetty_util",
     artifact = "org.eclipse.jetty:jetty-util:" + JETTY_VER,
-    sha1 = "b8512ab02819de01f0f5a5c6026163041f579beb",
-    src_sha1 = "96f8e3dcdc3660a5c91f19c46695daa70ac95625",
+    sha1 = "5fd36dfcf39110b809bd9b20cec62706ab694711",
+    src_sha1 = "629fcda1e4eecfd795e24cc07715ab9797970980",
 )
diff --git a/org.eclipse.jgit.ant.test/META-INF/MANIFEST.MF b/org.eclipse.jgit.ant.test/META-INF/MANIFEST.MF
index 0380568..5889109 100644
--- a/org.eclipse.jgit.ant.test/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.ant.test/META-INF/MANIFEST.MF
@@ -3,13 +3,13 @@
 Bundle-ManifestVersion: 2
 Bundle-Name: %plugin_name
 Bundle-SymbolicName: org.eclipse.jgit.ant.test
-Bundle-Version: 4.7.2.qualifier
+Bundle-Version: 4.8.1.qualifier
 Bundle-ActivationPolicy: lazy
 Bundle-RequiredExecutionEnvironment: JavaSE-1.8
 Import-Package: org.apache.tools.ant,
- org.eclipse.jgit.ant.tasks;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.junit;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.lib;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.util;version="[4.7.2,4.8.0)",
+ org.eclipse.jgit.ant.tasks;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.junit;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.lib;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.util;version="[4.8.1,4.9.0)",
  org.hamcrest.core;version="[1.1.0,2.0.0)",
  org.junit;version="[4.0.0,5.0.0)"
diff --git a/org.eclipse.jgit.ant.test/pom.xml b/org.eclipse.jgit.ant.test/pom.xml
index 79c81af..386ecd2 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>4.7.2-SNAPSHOT</version>
+    <version>4.8.1-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.ant.test</artifactId>
diff --git a/org.eclipse.jgit.ant/META-INF/MANIFEST.MF b/org.eclipse.jgit.ant/META-INF/MANIFEST.MF
index 6ca7f1c..be009d1 100644
--- a/org.eclipse.jgit.ant/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.ant/META-INF/MANIFEST.MF
@@ -2,11 +2,11 @@
 Bundle-ManifestVersion: 2
 Bundle-Name: %Bundle-Name
 Bundle-SymbolicName: org.eclipse.jgit.ant
-Bundle-Version: 4.7.2.qualifier
+Bundle-Version: 4.8.1.qualifier
 Bundle-RequiredExecutionEnvironment: JavaSE-1.8
 Import-Package: org.apache.tools.ant,
-  org.eclipse.jgit.storage.file;version="[4.7.2,4.8.0)"
+  org.eclipse.jgit.storage.file;version="[4.8.1,4.9.0)"
 Bundle-Localization: plugin
 Bundle-Vendor: %Provider-Name
-Export-Package: org.eclipse.jgit.ant.tasks;version="4.7.2";
+Export-Package: org.eclipse.jgit.ant.tasks;version="4.8.1";
  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 5b888e9..5712d29 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>4.7.2-SNAPSHOT</version>
+		<version>4.8.1-SNAPSHOT</version>
 	</parent>
 
 	<artifactId>org.eclipse.jgit.ant</artifactId>
diff --git a/org.eclipse.jgit.archive/BUILD b/org.eclipse.jgit.archive/BUILD
index dfdbfdc..8c65fc0 100644
--- a/org.eclipse.jgit.archive/BUILD
+++ b/org.eclipse.jgit.archive/BUILD
@@ -3,7 +3,7 @@
 java_library(
     name = "jgit-archive",
     srcs = glob(
-        ["src/**"],
+        ["src/**/*.java"],
         exclude = ["src/org/eclipse/jgit/archive/FormatActivator.java"],
     ),
     resource_strip_prefix = "org.eclipse.jgit.archive/resources",
diff --git a/org.eclipse.jgit.archive/META-INF/MANIFEST.MF b/org.eclipse.jgit.archive/META-INF/MANIFEST.MF
index bf54571..4080946 100644
--- a/org.eclipse.jgit.archive/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.archive/META-INF/MANIFEST.MF
@@ -2,7 +2,7 @@
 Bundle-ManifestVersion: 2
 Bundle-Name: %plugin_name
 Bundle-SymbolicName: org.eclipse.jgit.archive
-Bundle-Version: 4.7.2.qualifier
+Bundle-Version: 4.8.1.qualifier
 Bundle-Vendor: %provider_name
 Bundle-Localization: plugin
 Bundle-RequiredExecutionEnvironment: JavaSE-1.8
@@ -12,15 +12,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="[4.7.2,4.8.0)",
- org.eclipse.jgit.lib;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.nls;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.revwalk;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.util;version="[4.7.2,4.8.0)",
+ org.eclipse.jgit.api;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.lib;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.nls;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.revwalk;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.util;version="[4.8.1,4.9.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="4.7.2";
+Export-Package: org.eclipse.jgit.archive;version="4.8.1";
   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 8d955b6..c03191a 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: 4.7.2.qualifier
-Eclipse-SourceBundle: org.eclipse.jgit.archive;version="4.7.2.qualifier";roots="."
+Bundle-Version: 4.8.1.qualifier
+Eclipse-SourceBundle: org.eclipse.jgit.archive;version="4.8.1.qualifier";roots="."
diff --git a/org.eclipse.jgit.archive/pom.xml b/org.eclipse.jgit.archive/pom.xml
index 0646c6d..eea1d80 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>4.7.2-SNAPSHOT</version>
+    <version>4.8.1-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.archive</artifactId>
diff --git a/org.eclipse.jgit.http.apache/BUILD b/org.eclipse.jgit.http.apache/BUILD
index c1538ab..5ea118c 100644
--- a/org.eclipse.jgit.http.apache/BUILD
+++ b/org.eclipse.jgit.http.apache/BUILD
@@ -2,7 +2,7 @@
 
 java_library(
     name = "http-apache",
-    srcs = glob(["src/**"]),
+    srcs = glob(["src/**/*.java"]),
     resource_strip_prefix = "org.eclipse.jgit.http.apache/resources",
     resources = glob(["resources/**"]),
     deps = [
diff --git a/org.eclipse.jgit.http.apache/META-INF/MANIFEST.MF b/org.eclipse.jgit.http.apache/META-INF/MANIFEST.MF
index 67ed714..2fc4543 100644
--- a/org.eclipse.jgit.http.apache/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.http.apache/META-INF/MANIFEST.MF
@@ -2,7 +2,7 @@
 Bundle-ManifestVersion: 2
 Bundle-Name: %Bundle-Name
 Bundle-SymbolicName: org.eclipse.jgit.http.apache
-Bundle-Version: 4.7.2.qualifier
+Bundle-Version: 4.8.1.qualifier
 Bundle-RequiredExecutionEnvironment: JavaSE-1.8
 Bundle-Localization: plugin
 Bundle-Vendor: %Provider-Name
@@ -22,10 +22,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="[4.7.2,4.8.0)",
- org.eclipse.jgit.transport.http;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.util;version="[4.7.2,4.8.0)"
-Export-Package: org.eclipse.jgit.transport.http.apache;version="4.7.2";
+ org.eclipse.jgit.nls;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.transport.http;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.util;version="[4.8.1,4.9.0)"
+Export-Package: org.eclipse.jgit.transport.http.apache;version="4.8.1";
   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 66086f4..068e367 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>4.7.2-SNAPSHOT</version>
+		<version>4.8.1-SNAPSHOT</version>
 	</parent>
 
 	<artifactId>org.eclipse.jgit.http.apache</artifactId>
diff --git a/org.eclipse.jgit.http.server/BUILD b/org.eclipse.jgit.http.server/BUILD
index 876c5fa..9d5673b 100644
--- a/org.eclipse.jgit.http.server/BUILD
+++ b/org.eclipse.jgit.http.server/BUILD
@@ -2,7 +2,7 @@
 
 java_library(
     name = "jgit-servlet",
-    srcs = glob(["src/**"]),
+    srcs = glob(["src/**/*.java"]),
     resource_strip_prefix = "org.eclipse.jgit.http.server/resources",
     resources = glob(["resources/**"]),
     deps = [
diff --git a/org.eclipse.jgit.http.server/META-INF/MANIFEST.MF b/org.eclipse.jgit.http.server/META-INF/MANIFEST.MF
index 5f0d888..2b51b4b 100644
--- a/org.eclipse.jgit.http.server/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.http.server/META-INF/MANIFEST.MF
@@ -2,13 +2,13 @@
 Bundle-ManifestVersion: 2
 Bundle-Name: %plugin_name
 Bundle-SymbolicName: org.eclipse.jgit.http.server
-Bundle-Version: 4.7.2.qualifier
+Bundle-Version: 4.8.1.qualifier
 Bundle-Localization: plugin
 Bundle-Vendor: %provider_name
-Export-Package: org.eclipse.jgit.http.server;version="4.7.2",
- org.eclipse.jgit.http.server.glue;version="4.7.2";
+Export-Package: org.eclipse.jgit.http.server;version="4.8.1",
+ org.eclipse.jgit.http.server.glue;version="4.8.1";
   uses:="javax.servlet,javax.servlet.http",
- org.eclipse.jgit.http.server.resolver;version="4.7.2";
+ org.eclipse.jgit.http.server.resolver;version="4.8.1";
   uses:="org.eclipse.jgit.transport.resolver,
    org.eclipse.jgit.lib,
    org.eclipse.jgit.transport,
@@ -17,12 +17,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="[4.7.2,4.8.0)",
- org.eclipse.jgit.internal.storage.dfs;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.internal.storage.file;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.lib;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.nls;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.revwalk;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.transport;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.transport.resolver;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.util;version="[4.7.2,4.8.0)"
+ org.eclipse.jgit.errors;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.internal.storage.dfs;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.internal.storage.file;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.lib;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.nls;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.revwalk;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.transport;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.transport.resolver;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.util;version="[4.8.1,4.9.0)"
diff --git a/org.eclipse.jgit.http.server/pom.xml b/org.eclipse.jgit.http.server/pom.xml
index 669e457..a011594 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>4.7.2-SNAPSHOT</version>
+    <version>4.8.1-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.http.server</artifactId>
diff --git a/org.eclipse.jgit.http.test/META-INF/MANIFEST.MF b/org.eclipse.jgit.http.test/META-INF/MANIFEST.MF
index 5817ce3..811585e 100644
--- a/org.eclipse.jgit.http.test/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.http.test/META-INF/MANIFEST.MF
@@ -2,44 +2,44 @@
 Bundle-ManifestVersion: 2
 Bundle-Name: %plugin_name
 Bundle-SymbolicName: org.eclipse.jgit.http.test
-Bundle-Version: 4.7.2.qualifier
+Bundle-Version: 4.8.1.qualifier
 Bundle-Vendor: %provider_name
 Bundle-Localization: plugin
 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.jetty.continuation;version="[9.0.0,9.4.0)",
- org.eclipse.jetty.http;version="[9.0.0,9.4.0)",
- org.eclipse.jetty.io;version="[9.0.0,9.4.0)",
- org.eclipse.jetty.security;version="[9.0.0,9.4.0)",
- org.eclipse.jetty.security.authentication;version="[9.0.0,9.4.0)",
- org.eclipse.jetty.server;version="[9.0.0,9.4.0)",
- org.eclipse.jetty.server.handler;version="[9.0.0,9.4.0)",
- org.eclipse.jetty.server.nio;version="[9.0.0,9.4.0)",
- org.eclipse.jetty.servlet;version="[9.0.0,9.4.0)",
- org.eclipse.jetty.util;version="[9.0.0,9.4.0)",
- org.eclipse.jetty.util.component;version="[9.0.0,9.4.0)",
- org.eclipse.jetty.util.log;version="[9.0.0,9.4.0)",
- org.eclipse.jetty.util.security;version="[9.0.0,9.4.0)",
- org.eclipse.jetty.util.thread;version="[9.0.0,9.4.0)",
- org.eclipse.jgit.errors;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.http.server;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.http.server.glue;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.http.server.resolver;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.internal;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.internal.storage.dfs;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.internal.storage.file;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.junit;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.junit.http;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.lib;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.nls;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.revwalk;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.storage.file;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.transport;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.transport.http;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.transport.http.apache;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.transport.resolver;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.util;version="[4.7.2,4.8.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)",
+ org.eclipse.jetty.security;version="[9.4.5,10.0.0)",
+ org.eclipse.jetty.security.authentication;version="[9.4.5,10.0.0)",
+ org.eclipse.jetty.server;version="[9.4.5,10.0.0)",
+ org.eclipse.jetty.server.handler;version="[9.4.5,10.0.0)",
+ org.eclipse.jetty.server.nio;version="[9.4.5,10.0.0)",
+ org.eclipse.jetty.servlet;version="[9.4.5,10.0.0)",
+ org.eclipse.jetty.util;version="[9.4.5,10.0.0)",
+ org.eclipse.jetty.util.component;version="[9.4.5,10.0.0)",
+ 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="[4.8.1,4.9.0)",
+ org.eclipse.jgit.http.server;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.http.server.glue;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.http.server.resolver;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.internal;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.internal.storage.dfs;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.internal.storage.file;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.junit;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.junit.http;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.lib;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.nls;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.revwalk;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.storage.file;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.transport;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.transport.http;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.transport.http.apache;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.transport.resolver;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.util;version="[4.8.1,4.9.0)",
  org.hamcrest.core;version="[1.1.0,2.0.0)",
  org.junit;version="[4.0.0,5.0.0)",
  org.junit.runner;version="[4.0.0,5.0.0)",
diff --git a/org.eclipse.jgit.http.test/pom.xml b/org.eclipse.jgit.http.test/pom.xml
index eee0092..1538601 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>4.7.2-SNAPSHOT</version>
+    <version>4.8.1-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.http.test</artifactId>
diff --git a/org.eclipse.jgit.junit.http/BUILD b/org.eclipse.jgit.junit.http/BUILD
index be6e1ae..2a29acc 100644
--- a/org.eclipse.jgit.junit.http/BUILD
+++ b/org.eclipse.jgit.junit.http/BUILD
@@ -3,7 +3,7 @@
 java_library(
     name = "junit-http",
     testonly = 1,
-    srcs = glob(["src/**"]),
+    srcs = glob(["src/**/*.java"]),
     resources = glob(["resources/**"]),
     # TODO(davido): we want here provided deps
     deps = [
diff --git a/org.eclipse.jgit.junit.http/META-INF/MANIFEST.MF b/org.eclipse.jgit.junit.http/META-INF/MANIFEST.MF
index b313dfa..00a7a65 100644
--- a/org.eclipse.jgit.junit.http/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.junit.http/META-INF/MANIFEST.MF
@@ -2,7 +2,7 @@
 Bundle-ManifestVersion: 2
 Bundle-Name: %plugin_name
 Bundle-SymbolicName: org.eclipse.jgit.junit.http
-Bundle-Version: 4.7.2.qualifier
+Bundle-Version: 4.8.1.qualifier
 Bundle-Localization: plugin
 Bundle-Vendor: %provider_name
 Bundle-ActivationPolicy: lazy
@@ -10,26 +10,26 @@
 Import-Package: javax.servlet;version="[2.5.0,3.2.0)",
  javax.servlet.http;version="[2.5.0,3.2.0)",
  org.apache.commons.logging;version="[1.1.1,2.0.0)",
- org.eclipse.jetty.http;version="[9.0.0,9.4.0)",
- org.eclipse.jetty.security;version="[9.0.0,9.4.0)",
- org.eclipse.jetty.security.authentication;version="[9.0.0,9.4.0)",
- org.eclipse.jetty.server;version="[9.0.0,9.4.0)",
- org.eclipse.jetty.server.handler;version="[9.0.0,9.4.0)",
- org.eclipse.jetty.server.nio;version="[9.0.0,9.4.0)",
- org.eclipse.jetty.servlet;version="[9.0.0,9.4.0)",
- org.eclipse.jetty.util.component;version="[9.0.0,9.4.0)",
- org.eclipse.jetty.util.log;version="[9.0.0,9.4.0)",
- org.eclipse.jetty.util.security;version="[9.0.0,9.4.0)",
- org.eclipse.jgit.errors;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.http.server;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.internal.storage.file;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.junit;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.lib;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.revwalk;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.transport;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.transport.resolver;version="[4.7.2,4.8.0)",
+ org.eclipse.jetty.http;version="[9.4.5,10.0.0)",
+ org.eclipse.jetty.security;version="[9.4.5,10.0.0)",
+ org.eclipse.jetty.security.authentication;version="[9.4.5,10.0.0)",
+ org.eclipse.jetty.server;version="[9.4.5,10.0.0)",
+ org.eclipse.jetty.server.handler;version="[9.4.5,10.0.0)",
+ org.eclipse.jetty.server.nio;version="[9.4.5,10.0.0)",
+ org.eclipse.jetty.servlet;version="[9.4.5,10.0.0)",
+ org.eclipse.jetty.util.component;version="[9.4.5,10.0.0)",
+ 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.jgit.errors;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.http.server;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.internal.storage.file;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.junit;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.lib;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.revwalk;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.transport;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.transport.resolver;version="[4.8.1,4.9.0)",
  org.junit;version="[4.0.0,5.0.0)"
-Export-Package: org.eclipse.jgit.junit.http;version="4.7.2";
+Export-Package: org.eclipse.jgit.junit.http;version="4.8.1";
   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 ecd6122..0c1acf9 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>4.7.2-SNAPSHOT</version>
+    <version>4.8.1-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.junit.http</artifactId>
diff --git a/org.eclipse.jgit.junit.http/src/org/eclipse/jgit/junit/http/AccessEvent.java b/org.eclipse.jgit.junit.http/src/org/eclipse/jgit/junit/http/AccessEvent.java
index 6b7853d..0154a7f 100644
--- a/org.eclipse.jgit.junit.http/src/org/eclipse/jgit/junit/http/AccessEvent.java
+++ b/org.eclipse.jgit.junit.http/src/org/eclipse/jgit/junit/http/AccessEvent.java
@@ -102,7 +102,7 @@
 
 	@SuppressWarnings("unchecked")
 	private static Map<String, String[]> clone(Map parameterMap) {
-		return new TreeMap<String, String[]>(parameterMap);
+		return new TreeMap<>(parameterMap);
 	}
 
 	/** @return {@code "GET"} or {@code "POST"} */
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 a663484..28c0f21 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
@@ -46,25 +46,25 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
-import java.io.IOException;
 import java.net.InetAddress;
 import java.net.URI;
 import java.net.URISyntaxException;
 import java.net.UnknownHostException;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
 
+import org.eclipse.jetty.security.AbstractLoginService;
 import org.eclipse.jetty.security.Authenticator;
 import org.eclipse.jetty.security.ConstraintMapping;
 import org.eclipse.jetty.security.ConstraintSecurityHandler;
-import org.eclipse.jetty.security.MappedLoginService;
 import org.eclipse.jetty.security.authentication.BasicAuthenticator;
 import org.eclipse.jetty.server.Connector;
 import org.eclipse.jetty.server.HttpConfiguration;
 import org.eclipse.jetty.server.HttpConnectionFactory;
 import org.eclipse.jetty.server.Server;
 import org.eclipse.jetty.server.ServerConnector;
-import org.eclipse.jetty.server.UserIdentity;
 import org.eclipse.jetty.server.handler.ContextHandlerCollection;
 import org.eclipse.jetty.servlet.ServletContextHandler;
 import org.eclipse.jetty.util.security.Constraint;
@@ -168,36 +168,41 @@
 		return ctx;
 	}
 
-	static class TestMappedLoginService extends MappedLoginService {
+	static class TestMappedLoginService extends AbstractLoginService {
 		private String role;
 
+		protected final ConcurrentMap<String, UserPrincipal> users = new ConcurrentHashMap<>();
+
 		TestMappedLoginService(String role) {
 			this.role = role;
 		}
 
 		@Override
-		protected UserIdentity loadUser(String who) {
-			return null;
+		protected void doStart() throws Exception {
+			UserPrincipal p = new UserPrincipal(username,
+					new Password(password));
+			users.put(username, p);
+			super.doStart();
 		}
 
 		@Override
-		protected void loadUsers() throws IOException {
-			putUser(username, new Password(password), new String[] { role });
+		protected String[] loadRoleInfo(UserPrincipal user) {
+			if (users.get(user.getName()) == null)
+				return null;
+			else
+				return new String[] { role };
 		}
 
-		protected String[] loadRoleInfo(KnownUser user) {
-			return null;
-		}
-
-		protected KnownUser loadUserInfo(String usrname) {
-			return null;
+		@Override
+		protected UserPrincipal loadUserInfo(String user) {
+			return users.get(user);
 		}
 	}
 
 	private void auth(ServletContextHandler ctx, Authenticator authType) {
 		final String role = "can-access";
 
-		MappedLoginService users = new TestMappedLoginService(role);
+		AbstractLoginService users = new TestMappedLoginService(role);
 		ConstraintMapping cm = new ConstraintMapping();
 		cm.setConstraint(new Constraint());
 		cm.getConstraint().setAuthenticate(true);
diff --git a/org.eclipse.jgit.junit.http/src/org/eclipse/jgit/junit/http/RecordingLogger.java b/org.eclipse.jgit.junit.http/src/org/eclipse/jgit/junit/http/RecordingLogger.java
index 415398d..4e35ff6 100644
--- a/org.eclipse.jgit.junit.http/src/org/eclipse/jgit/junit/http/RecordingLogger.java
+++ b/org.eclipse.jgit.junit.http/src/org/eclipse/jgit/junit/http/RecordingLogger.java
@@ -161,6 +161,12 @@
 	@Override
 	public void warn(String msg, Object... args) {
 		synchronized (warnings) {
+			int i = 0;
+			int index = msg.indexOf("{}");
+			while (index >= 0) {
+				msg = msg.replaceFirst("\\{\\}", "{" + i++ + "}");
+				index = msg.indexOf("{}");
+			}
 			warnings.add(new Warning(MessageFormat.format(msg, args)));
 		}
 	}
diff --git a/org.eclipse.jgit.junit/BUILD b/org.eclipse.jgit.junit/BUILD
index 350b25f..74498fd 100644
--- a/org.eclipse.jgit.junit/BUILD
+++ b/org.eclipse.jgit.junit/BUILD
@@ -3,7 +3,7 @@
 java_library(
     name = "junit",
     testonly = 1,
-    srcs = glob(["src/**"]),
+    srcs = glob(["src/**/*.java"]),
     resource_strip_prefix = "org.eclipse.jgit.junit/resources",
     resources = glob(["resources/**"]),
     deps = [
diff --git a/org.eclipse.jgit.junit/META-INF/MANIFEST.MF b/org.eclipse.jgit.junit/META-INF/MANIFEST.MF
index 2ddb638..c864c5c 100644
--- a/org.eclipse.jgit.junit/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.junit/META-INF/MANIFEST.MF
@@ -2,31 +2,31 @@
 Bundle-ManifestVersion: 2
 Bundle-Name: %plugin_name
 Bundle-SymbolicName: org.eclipse.jgit.junit
-Bundle-Version: 4.7.2.qualifier
+Bundle-Version: 4.8.1.qualifier
 Bundle-Localization: plugin
 Bundle-Vendor: %provider_name
 Bundle-ActivationPolicy: lazy
 Bundle-RequiredExecutionEnvironment: JavaSE-1.8
-Import-Package: org.eclipse.jgit.api;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.api.errors;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.dircache;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.errors;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.internal.storage.file;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.internal.storage.pack;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.lib;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.merge;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.revwalk;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.storage.file;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.treewalk;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.treewalk.filter;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.util;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.util.io;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.util.time;version="[4.7.2,4.8.0)",
+Import-Package: org.eclipse.jgit.api;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.api.errors;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.dircache;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.errors;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.internal.storage.file;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.internal.storage.pack;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.lib;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.merge;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.revwalk;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.storage.file;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.treewalk;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.treewalk.filter;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.util;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.util.io;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.util.time;version="[4.8.1,4.9.0)",
  org.junit;version="[4.0.0,5.0.0)",
  org.junit.rules;version="[4.9.0,5.0.0)",
  org.junit.runner;version="[4.0.0,5.0.0)",
  org.junit.runners.model;version="[4.5.0,5.0.0)"
-Export-Package: org.eclipse.jgit.junit;version="4.7.2";
+Export-Package: org.eclipse.jgit.junit;version="4.8.1";
   uses:="org.eclipse.jgit.dircache,
    org.eclipse.jgit.lib,
    org.eclipse.jgit.revwalk,
@@ -35,4 +35,4 @@
    org.eclipse.jgit.util,
    org.eclipse.jgit.storage.file,
    org.eclipse.jgit.api",
- org.eclipse.jgit.junit.time;version="4.7.2"
+ org.eclipse.jgit.junit.time;version="4.8.1"
diff --git a/org.eclipse.jgit.junit/pom.xml b/org.eclipse.jgit.junit/pom.xml
index 1496470..3f843e6 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>4.7.2-SNAPSHOT</version>
+    <version>4.8.1-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.junit</artifactId>
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 aa69609..f7960c7 100644
--- a/org.eclipse.jgit.lfs.server.test/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.lfs.server.test/META-INF/MANIFEST.MF
@@ -2,7 +2,7 @@
 Bundle-ManifestVersion: 2
 Bundle-Name: %plugin_name
 Bundle-SymbolicName: org.eclipse.jgit.lfs.server.test
-Bundle-Version: 4.7.2.qualifier
+Bundle-Version: 4.8.1.qualifier
 Bundle-Vendor: %provider_name
 Bundle-Localization: plugin
 Bundle-RequiredExecutionEnvironment: JavaSE-1.8
@@ -13,25 +13,25 @@
  org.apache.http.client.methods;version="[4.3.0,5.0.0)",
  org.apache.http.entity;version="[4.3.0,5.0.0)",
  org.apache.http.impl.client;version="[4.3.0,5.0.0)",
- org.eclipse.jetty.continuation;version="[9.0.0,9.4.0)",
- org.eclipse.jetty.http;version="[9.0.0,9.4.0)",
- org.eclipse.jetty.io;version="[9.0.0,9.4.0)",
- org.eclipse.jetty.security;version="[9.0.0,9.4.0)",
- org.eclipse.jetty.security.authentication;version="[9.0.0,9.4.0)",
- org.eclipse.jetty.server;version="[9.0.0,9.4.0)",
- org.eclipse.jetty.server.handler;version="[9.0.0,9.4.0)",
- org.eclipse.jetty.server.nio;version="[9.0.0,9.4.0)",
- org.eclipse.jetty.servlet;version="[9.0.0,9.4.0)",
- org.eclipse.jetty.util;version="[9.0.0,9.4.0)",
- org.eclipse.jetty.util.component;version="[9.0.0,9.4.0)",
- org.eclipse.jetty.util.log;version="[9.0.0,9.4.0)",
- org.eclipse.jetty.util.security;version="[9.0.0,9.4.0)",
- org.eclipse.jetty.util.thread;version="[9.0.0,9.4.0)",
- org.eclipse.jgit.junit.http;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.lfs.lib;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.lfs.server.fs;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.lfs.test;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.util;version="[4.7.2,4.8.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)",
+ org.eclipse.jetty.security;version="[9.4.5,10.0.0)",
+ org.eclipse.jetty.security.authentication;version="[9.4.5,10.0.0)",
+ org.eclipse.jetty.server;version="[9.4.5,10.0.0)",
+ org.eclipse.jetty.server.handler;version="[9.4.5,10.0.0)",
+ org.eclipse.jetty.server.nio;version="[9.4.5,10.0.0)",
+ org.eclipse.jetty.servlet;version="[9.4.5,10.0.0)",
+ org.eclipse.jetty.util;version="[9.4.5,10.0.0)",
+ org.eclipse.jetty.util.component;version="[9.4.5,10.0.0)",
+ 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.junit.http;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.lfs.lib;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.lfs.server.fs;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.lfs.test;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.util;version="[4.8.1,4.9.0)",
  org.hamcrest.core;version="[1.1.0,2.0.0)",
  org.junit;version="[4.0.0,5.0.0)",
  org.junit.runner;version="[4.0.0,5.0.0)",
diff --git a/org.eclipse.jgit.lfs.server.test/pom.xml b/org.eclipse.jgit.lfs.server.test/pom.xml
index f687723..9fd7080 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>4.7.2-SNAPSHOT</version>
+    <version>4.8.1-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.lfs.server.test</artifactId>
diff --git a/org.eclipse.jgit.lfs.server/.settings/.api_filters b/org.eclipse.jgit.lfs.server/.settings/.api_filters
deleted file mode 100644
index b5f65e1..0000000
--- a/org.eclipse.jgit.lfs.server/.settings/.api_filters
+++ /dev/null
@@ -1,11 +0,0 @@
-<?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/LfsProtocolServlet.java" type="org.eclipse.jgit.lfs.server.LfsProtocolServlet">
-        <filter comment="breaking API for implementors is ok in minor releases" id="336695337">
-            <message_arguments>
-                <message_argument value="org.eclipse.jgit.lfs.server.LfsProtocolServlet"/>
-                <message_argument value="getLargeFileRepository(LfsProtocolServlet.LfsRequest, String, String)"/>
-            </message_arguments>
-        </filter>
-    </resource>
-</component>
diff --git a/org.eclipse.jgit.lfs.server/BUILD b/org.eclipse.jgit.lfs.server/BUILD
index fa14e8a..f3b9005 100644
--- a/org.eclipse.jgit.lfs.server/BUILD
+++ b/org.eclipse.jgit.lfs.server/BUILD
@@ -2,7 +2,7 @@
 
 java_library(
     name = "jgit-lfs-server",
-    srcs = glob(["src/**"]),
+    srcs = glob(["src/**/*.java"]),
     resource_strip_prefix = "org.eclipse.jgit.lfs.server/resources",
     resources = glob(["resources/**"]),
     deps = [
diff --git a/org.eclipse.jgit.lfs.server/META-INF/MANIFEST.MF b/org.eclipse.jgit.lfs.server/META-INF/MANIFEST.MF
index 773f0dc..3366e1f 100644
--- a/org.eclipse.jgit.lfs.server/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.lfs.server/META-INF/MANIFEST.MF
@@ -2,19 +2,19 @@
 Bundle-ManifestVersion: 2
 Bundle-Name: %plugin_name
 Bundle-SymbolicName: org.eclipse.jgit.lfs.server
-Bundle-Version: 4.7.2.qualifier
+Bundle-Version: 4.8.1.qualifier
 Bundle-Localization: plugin
 Bundle-Vendor: %provider_name
-Export-Package: org.eclipse.jgit.lfs.server;version="4.7.2";
+Export-Package: org.eclipse.jgit.lfs.server;version="4.8.1";
   uses:="javax.servlet.http,
    org.eclipse.jgit.lfs.lib",
- org.eclipse.jgit.lfs.server.fs;version="4.7.2";
+ org.eclipse.jgit.lfs.server.fs;version="4.8.1";
   uses:="javax.servlet,
    javax.servlet.http,
    org.eclipse.jgit.lfs.server,
    org.eclipse.jgit.lfs.lib",
- org.eclipse.jgit.lfs.server.internal;version="4.7.2";x-internal:=true,
- org.eclipse.jgit.lfs.server.s3;version="4.7.2";
+ org.eclipse.jgit.lfs.server.internal;version="4.8.1";x-internal:=true,
+ org.eclipse.jgit.lfs.server.s3;version="4.8.1";
   uses:="org.eclipse.jgit.lfs.server,
    org.eclipse.jgit.lfs.lib"
 Bundle-RequiredExecutionEnvironment: JavaSE-1.8
@@ -24,14 +24,14 @@
  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="[4.7.2,4.8.0)",
- org.eclipse.jgit.internal;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.internal.storage.file;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.lfs.errors;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.lfs.internal;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.lfs.lib;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.nls;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.transport.http;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.transport.http.apache;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.util;version="[4.7.2,4.8.0)",
+ org.eclipse.jgit.annotations;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.internal;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.internal.storage.file;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.lfs.errors;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.lfs.internal;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.lfs.lib;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.nls;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.transport.http;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.transport.http.apache;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.util;version="[4.8.1,4.9.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 0a719ae..f247cae 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>4.7.2-SNAPSHOT</version>
+    <version>4.8.1-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.lfs.server</artifactId>
@@ -140,25 +140,6 @@
           </archive>
         </configuration>
       </plugin>
-
-      <plugin>
-        <groupId>org.codehaus.mojo</groupId>
-        <artifactId>clirr-maven-plugin</artifactId>
-      </plugin>
     </plugins>
   </build>
-
-  <reporting>
-    <plugins>
-      <plugin>
-        <groupId>org.codehaus.mojo</groupId>
-        <artifactId>clirr-maven-plugin</artifactId>
-        <version>${clirr-version}</version>
-        <configuration>
-          <comparisonVersion>${jgit-last-release-version}</comparisonVersion>
-          <minSeverity>info</minSeverity>
-        </configuration>
-      </plugin>
-    </plugins>
-  </reporting>
 </project>
diff --git a/org.eclipse.jgit.lfs.test/META-INF/MANIFEST.MF b/org.eclipse.jgit.lfs.test/META-INF/MANIFEST.MF
index 3cc1048..2bdbb59 100644
--- a/org.eclipse.jgit.lfs.test/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.lfs.test/META-INF/MANIFEST.MF
@@ -2,23 +2,23 @@
 Bundle-ManifestVersion: 2
 Bundle-Name: %plugin_name
 Bundle-SymbolicName: org.eclipse.jgit.lfs.test
-Bundle-Version: 4.7.2.qualifier
+Bundle-Version: 4.8.1.qualifier
 Bundle-Vendor: %provider_name
 Bundle-Localization: plugin
 Bundle-RequiredExecutionEnvironment: JavaSE-1.8
-Import-Package: org.eclipse.jgit.internal.storage.dfs;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.junit;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.lfs;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.lfs.errors;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.lfs.lib;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.lib;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.revwalk;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.treewalk;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.treewalk.filter;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.util;version="[4.7.2,4.8.0)",
+Import-Package: org.eclipse.jgit.internal.storage.dfs;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.junit;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.lfs;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.lfs.errors;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.lfs.lib;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.lib;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.revwalk;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.treewalk;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.treewalk.filter;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.util;version="[4.8.1,4.9.0)",
  org.hamcrest.core;version="[1.1.0,2.0.0)",
  org.junit;version="[4.0.0,5.0.0)",
  org.junit.runner;version="[4.0.0,5.0.0)",
  org.junit.runners;version="[4.0.0,5.0.0)"
-Export-Package: org.eclipse.jgit.lfs.test;version="4.7.2";x-friends:="org.eclipse.jgit.lfs.server.test"
+Export-Package: org.eclipse.jgit.lfs.test;version="4.8.1";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 cb77a17..f599ca0 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>4.7.2-SNAPSHOT</version>
+    <version>4.8.1-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.lfs.test</artifactId>
diff --git a/org.eclipse.jgit.lfs/BUILD b/org.eclipse.jgit.lfs/BUILD
index c4c9f8a..0c7b1b2 100644
--- a/org.eclipse.jgit.lfs/BUILD
+++ b/org.eclipse.jgit.lfs/BUILD
@@ -2,7 +2,7 @@
 
 java_library(
     name = "jgit-lfs",
-    srcs = glob(["src/**"]),
+    srcs = glob(["src/**/*.java"]),
     resource_strip_prefix = "org.eclipse.jgit.lfs/resources",
     resources = glob(["resources/**"]),
     deps = [
diff --git a/org.eclipse.jgit.lfs/META-INF/MANIFEST.MF b/org.eclipse.jgit.lfs/META-INF/MANIFEST.MF
index de2bec6..489fd12 100644
--- a/org.eclipse.jgit.lfs/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.lfs/META-INF/MANIFEST.MF
@@ -2,20 +2,20 @@
 Bundle-ManifestVersion: 2
 Bundle-Name: %plugin_name
 Bundle-SymbolicName: org.eclipse.jgit.lfs
-Bundle-Version: 4.7.2.qualifier
+Bundle-Version: 4.8.1.qualifier
 Bundle-Localization: plugin
 Bundle-Vendor: %provider_name
-Export-Package: org.eclipse.jgit.lfs;version="4.7.2",
- org.eclipse.jgit.lfs.errors;version="4.7.2",
- org.eclipse.jgit.lfs.internal;version="4.7.2";x-friends:="org.eclipse.jgit.lfs.test,org.eclipse.jgit.lfs.server.fs,org.eclipse.jgit.lfs.server",
- org.eclipse.jgit.lfs.lib;version="4.7.2"
+Export-Package: org.eclipse.jgit.lfs;version="4.8.1",
+ org.eclipse.jgit.lfs.errors;version="4.8.1",
+ org.eclipse.jgit.lfs.internal;version="4.8.1";x-friends:="org.eclipse.jgit.lfs.test,org.eclipse.jgit.lfs.server.fs,org.eclipse.jgit.lfs.server",
+ org.eclipse.jgit.lfs.lib;version="4.8.1"
 Bundle-RequiredExecutionEnvironment: JavaSE-1.8
-Import-Package: org.eclipse.jgit.annotations;version="[4.7.2,4.8.0)";resolution:=optional,
- org.eclipse.jgit.attributes;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.errors;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.internal.storage.file;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.lib;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.nls;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.treewalk;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.treewalk.filter;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.util;version="[4.7.2,4.8.0)"
+Import-Package: org.eclipse.jgit.annotations;version="[4.8.1,4.9.0)";resolution:=optional,
+ org.eclipse.jgit.attributes;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.errors;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.internal.storage.file;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.lib;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.nls;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.treewalk;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.treewalk.filter;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.util;version="[4.8.1,4.9.0)"
diff --git a/org.eclipse.jgit.lfs/pom.xml b/org.eclipse.jgit.lfs/pom.xml
index 54e4445..98216fb 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>4.7.2-SNAPSHOT</version>
+    <version>4.8.1-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.lfs</artifactId>
@@ -116,25 +116,6 @@
           </archive>
         </configuration>
       </plugin>
-
-      <plugin>
-        <groupId>org.codehaus.mojo</groupId>
-        <artifactId>clirr-maven-plugin</artifactId>
-      </plugin>
     </plugins>
   </build>
-
-  <reporting>
-    <plugins>
-      <plugin>
-        <groupId>org.codehaus.mojo</groupId>
-        <artifactId>clirr-maven-plugin</artifactId>
-        <version>${clirr-version}</version>
-        <configuration>
-          <comparisonVersion>${jgit-last-release-version}</comparisonVersion>
-          <minSeverity>info</minSeverity>
-        </configuration>
-      </plugin>
-    </plugins>
-  </reporting>
 </project>
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 c244338..0d9c4b2 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="4.7.2.qualifier"
+      version="4.8.1.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 b90c14d..c6ae404 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>4.7.2-SNAPSHOT</version>
+    <version>4.8.1-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 0abf4bd..c2662fc 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="4.7.2.qualifier"
+      version="4.8.1.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 af4e71f..4fa4fca 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>4.7.2-SNAPSHOT</version>
+    <version>4.8.1-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 74b9925..408b1d8 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="4.7.2.qualifier"
+      version="4.8.1.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 4f66ab6..bdaa809 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>4.7.2-SNAPSHOT</version>
+    <version>4.8.1-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 7daa0b2..7113e7b 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="4.7.2.qualifier"
+      version="4.8.1.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 2ea040d..b39af8c 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>4.7.2-SNAPSHOT</version>
+    <version>4.8.1-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 546539b..1129615 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="4.7.2.qualifier"
+      version="4.8.1.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="4.7.1" match="equivalent"/>
-      <import feature="org.eclipse.jgit.lfs" version="4.7.1" match="equivalent"/>
+      <import feature="org.eclipse.jgit" version="4.8.1" match="equivalent"/>
+      <import feature="org.eclipse.jgit.lfs" version="4.8.1" 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 55d6657..b8cb2c8 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>4.7.2-SNAPSHOT</version>
+    <version>4.8.1-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 8aa9618..0556fb5 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="4.7.2.qualifier"
+      version="4.8.1.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 5057957..70813bc 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>4.7.2-SNAPSHOT</version>
+    <version>4.8.1-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 c64d62c..432c5d4 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>4.7.2-SNAPSHOT</version>
+    <version>4.8.1-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 b3feeb7..5834a0c 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="4.7.2.qualifier"
+      version="4.8.1.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 e23b722..ec57876 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>4.7.2-SNAPSHOT</version>
+    <version>4.8.1-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 65dabc3..a43734b 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: 4.7.2.qualifier
+Bundle-Version: 4.8.1.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 be02826..8051080 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,26 +1,26 @@
 <?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="1502747250">
+<target name="jgit-4.5" sequenceNumber="1502749391">
   <locations>
     <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
-      <unit id="org.eclipse.jetty.client" version="9.3.17.v20170317"/>
-      <unit id="org.eclipse.jetty.client.source" version="9.3.17.v20170317"/>
-      <unit id="org.eclipse.jetty.continuation" version="9.3.17.v20170317"/>
-      <unit id="org.eclipse.jetty.continuation.source" version="9.3.17.v20170317"/>
-      <unit id="org.eclipse.jetty.http" version="9.3.17.v20170317"/>
-      <unit id="org.eclipse.jetty.http.source" version="9.3.17.v20170317"/>
-      <unit id="org.eclipse.jetty.io" version="9.3.17.v20170317"/>
-      <unit id="org.eclipse.jetty.io.source" version="9.3.17.v20170317"/>
-      <unit id="org.eclipse.jetty.security" version="9.3.17.v20170317"/>
-      <unit id="org.eclipse.jetty.security.source" version="9.3.17.v20170317"/>
-      <unit id="org.eclipse.jetty.server" version="9.3.17.v20170317"/>
-      <unit id="org.eclipse.jetty.server.source" version="9.3.17.v20170317"/>
-      <unit id="org.eclipse.jetty.servlet" version="9.3.17.v20170317"/>
-      <unit id="org.eclipse.jetty.servlet.source" version="9.3.17.v20170317"/>
-      <unit id="org.eclipse.jetty.util" version="9.3.17.v20170317"/>
-      <unit id="org.eclipse.jetty.util.source" version="9.3.17.v20170317"/>
-      <repository id="jetty-9.3.17" location="http://download.eclipse.org/jetty/updates/jetty-bundles-9.x/9.3.17.v20170317/"/>
+      <unit id="org.eclipse.jetty.client" version="9.4.5.v20170502"/>
+      <unit id="org.eclipse.jetty.client.source" version="9.4.5.v20170502"/>
+      <unit id="org.eclipse.jetty.continuation" version="9.4.5.v20170502"/>
+      <unit id="org.eclipse.jetty.continuation.source" version="9.4.5.v20170502"/>
+      <unit id="org.eclipse.jetty.http" version="9.4.5.v20170502"/>
+      <unit id="org.eclipse.jetty.http.source" version="9.4.5.v20170502"/>
+      <unit id="org.eclipse.jetty.io" version="9.4.5.v20170502"/>
+      <unit id="org.eclipse.jetty.io.source" version="9.4.5.v20170502"/>
+      <unit id="org.eclipse.jetty.security" version="9.4.5.v20170502"/>
+      <unit id="org.eclipse.jetty.security.source" version="9.4.5.v20170502"/>
+      <unit id="org.eclipse.jetty.server" version="9.4.5.v20170502"/>
+      <unit id="org.eclipse.jetty.server.source" version="9.4.5.v20170502"/>
+      <unit id="org.eclipse.jetty.servlet" version="9.4.5.v20170502"/>
+      <unit id="org.eclipse.jetty.servlet.source" version="9.4.5.v20170502"/>
+      <unit id="org.eclipse.jetty.util" version="9.4.5.v20170502"/>
+      <unit id="org.eclipse.jetty.util.source" version="9.4.5.v20170502"/>
+      <repository id="jetty-9.4.5" location="http://download.eclipse.org/jetty/updates/jetty-bundles-9.x/9.4.5.v20170502/"/>
     </location>
     <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
       <unit id="org.apache.ant" version="1.9.4.v201504302020"/>
@@ -98,7 +98,7 @@
       <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"/>
-      <repository location="http://download.eclipse.org/tools/orbit/R-builds/R20170516192513/repository"/>
+      <repository location="http://download.eclipse.org/tools/orbit/downloads/drops/R20170516192513/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 efc1f44..f9653b2 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,6 +1,6 @@
 target "jgit-4.5" with source configurePhase
 
-include "projects/jetty-9.3.17.tpd"
+include "projects/jetty-9.4.5.tpd"
 include "orbit/R20160221192158-Mars.tpd"
 include "orbit/R20170516192513-Oxygen.tpd"
 
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 bcef50b..b6bbcda 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,26 +1,26 @@
 <?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="1502747233">
+<target name="jgit-4.6" sequenceNumber="1502749371">
   <locations>
     <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
-      <unit id="org.eclipse.jetty.client" version="9.3.17.v20170317"/>
-      <unit id="org.eclipse.jetty.client.source" version="9.3.17.v20170317"/>
-      <unit id="org.eclipse.jetty.continuation" version="9.3.17.v20170317"/>
-      <unit id="org.eclipse.jetty.continuation.source" version="9.3.17.v20170317"/>
-      <unit id="org.eclipse.jetty.http" version="9.3.17.v20170317"/>
-      <unit id="org.eclipse.jetty.http.source" version="9.3.17.v20170317"/>
-      <unit id="org.eclipse.jetty.io" version="9.3.17.v20170317"/>
-      <unit id="org.eclipse.jetty.io.source" version="9.3.17.v20170317"/>
-      <unit id="org.eclipse.jetty.security" version="9.3.17.v20170317"/>
-      <unit id="org.eclipse.jetty.security.source" version="9.3.17.v20170317"/>
-      <unit id="org.eclipse.jetty.server" version="9.3.17.v20170317"/>
-      <unit id="org.eclipse.jetty.server.source" version="9.3.17.v20170317"/>
-      <unit id="org.eclipse.jetty.servlet" version="9.3.17.v20170317"/>
-      <unit id="org.eclipse.jetty.servlet.source" version="9.3.17.v20170317"/>
-      <unit id="org.eclipse.jetty.util" version="9.3.17.v20170317"/>
-      <unit id="org.eclipse.jetty.util.source" version="9.3.17.v20170317"/>
-      <repository id="jetty-9.3.17" location="http://download.eclipse.org/jetty/updates/jetty-bundles-9.x/9.3.17.v20170317/"/>
+      <unit id="org.eclipse.jetty.client" version="9.4.5.v20170502"/>
+      <unit id="org.eclipse.jetty.client.source" version="9.4.5.v20170502"/>
+      <unit id="org.eclipse.jetty.continuation" version="9.4.5.v20170502"/>
+      <unit id="org.eclipse.jetty.continuation.source" version="9.4.5.v20170502"/>
+      <unit id="org.eclipse.jetty.http" version="9.4.5.v20170502"/>
+      <unit id="org.eclipse.jetty.http.source" version="9.4.5.v20170502"/>
+      <unit id="org.eclipse.jetty.io" version="9.4.5.v20170502"/>
+      <unit id="org.eclipse.jetty.io.source" version="9.4.5.v20170502"/>
+      <unit id="org.eclipse.jetty.security" version="9.4.5.v20170502"/>
+      <unit id="org.eclipse.jetty.security.source" version="9.4.5.v20170502"/>
+      <unit id="org.eclipse.jetty.server" version="9.4.5.v20170502"/>
+      <unit id="org.eclipse.jetty.server.source" version="9.4.5.v20170502"/>
+      <unit id="org.eclipse.jetty.servlet" version="9.4.5.v20170502"/>
+      <unit id="org.eclipse.jetty.servlet.source" version="9.4.5.v20170502"/>
+      <unit id="org.eclipse.jetty.util" version="9.4.5.v20170502"/>
+      <unit id="org.eclipse.jetty.util.source" version="9.4.5.v20170502"/>
+      <repository id="jetty-9.4.5" location="http://download.eclipse.org/jetty/updates/jetty-bundles-9.x/9.4.5.v20170502/"/>
     </location>
     <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
       <unit id="org.apache.ant" version="1.9.6.v201510161327"/>
@@ -60,7 +60,7 @@
       <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"/>
-      <repository location="http://download.eclipse.org/tools/orbit/R-builds/R20170516192513/repository"/>
+      <repository location="http://download.eclipse.org/tools/orbit/downloads/drops/R20170516192513/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 90f62ae..9ddba2d 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,6 +1,6 @@
 target "jgit-4.6" with source configurePhase
 
-include "projects/jetty-9.3.17.tpd"
+include "projects/jetty-9.4.5.tpd"
 include "orbit/R20170516192513-Oxygen.tpd"
 
 location "http://download.eclipse.org/releases/neon/" {
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 52ea6f8..6071c8f 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,26 +1,26 @@
 <?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="1502747215">
+<target name="jgit-4.7" sequenceNumber="1502749365">
   <locations>
     <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
-      <unit id="org.eclipse.jetty.client" version="9.3.17.v20170317"/>
-      <unit id="org.eclipse.jetty.client.source" version="9.3.17.v20170317"/>
-      <unit id="org.eclipse.jetty.continuation" version="9.3.17.v20170317"/>
-      <unit id="org.eclipse.jetty.continuation.source" version="9.3.17.v20170317"/>
-      <unit id="org.eclipse.jetty.http" version="9.3.17.v20170317"/>
-      <unit id="org.eclipse.jetty.http.source" version="9.3.17.v20170317"/>
-      <unit id="org.eclipse.jetty.io" version="9.3.17.v20170317"/>
-      <unit id="org.eclipse.jetty.io.source" version="9.3.17.v20170317"/>
-      <unit id="org.eclipse.jetty.security" version="9.3.17.v20170317"/>
-      <unit id="org.eclipse.jetty.security.source" version="9.3.17.v20170317"/>
-      <unit id="org.eclipse.jetty.server" version="9.3.17.v20170317"/>
-      <unit id="org.eclipse.jetty.server.source" version="9.3.17.v20170317"/>
-      <unit id="org.eclipse.jetty.servlet" version="9.3.17.v20170317"/>
-      <unit id="org.eclipse.jetty.servlet.source" version="9.3.17.v20170317"/>
-      <unit id="org.eclipse.jetty.util" version="9.3.17.v20170317"/>
-      <unit id="org.eclipse.jetty.util.source" version="9.3.17.v20170317"/>
-      <repository id="jetty-9.3.17" location="http://download.eclipse.org/jetty/updates/jetty-bundles-9.x/9.3.17.v20170317/"/>
+      <unit id="org.eclipse.jetty.client" version="9.4.5.v20170502"/>
+      <unit id="org.eclipse.jetty.client.source" version="9.4.5.v20170502"/>
+      <unit id="org.eclipse.jetty.continuation" version="9.4.5.v20170502"/>
+      <unit id="org.eclipse.jetty.continuation.source" version="9.4.5.v20170502"/>
+      <unit id="org.eclipse.jetty.http" version="9.4.5.v20170502"/>
+      <unit id="org.eclipse.jetty.http.source" version="9.4.5.v20170502"/>
+      <unit id="org.eclipse.jetty.io" version="9.4.5.v20170502"/>
+      <unit id="org.eclipse.jetty.io.source" version="9.4.5.v20170502"/>
+      <unit id="org.eclipse.jetty.security" version="9.4.5.v20170502"/>
+      <unit id="org.eclipse.jetty.security.source" version="9.4.5.v20170502"/>
+      <unit id="org.eclipse.jetty.server" version="9.4.5.v20170502"/>
+      <unit id="org.eclipse.jetty.server.source" version="9.4.5.v20170502"/>
+      <unit id="org.eclipse.jetty.servlet" version="9.4.5.v20170502"/>
+      <unit id="org.eclipse.jetty.servlet.source" version="9.4.5.v20170502"/>
+      <unit id="org.eclipse.jetty.util" version="9.4.5.v20170502"/>
+      <unit id="org.eclipse.jetty.util.source" version="9.4.5.v20170502"/>
+      <repository id="jetty-9.4.5" location="http://download.eclipse.org/jetty/updates/jetty-bundles-9.x/9.4.5.v20170502/"/>
     </location>
     <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
       <unit id="org.apache.ant" version="1.9.6.v201510161327"/>
@@ -60,7 +60,7 @@
       <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"/>
-      <repository location="http://download.eclipse.org/tools/orbit/R-builds/R20170516192513/repository"/>
+      <repository location="http://download.eclipse.org/tools/orbit/downloads/drops/R20170516192513/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 1d0e693..4185079 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,8 +1,8 @@
 target "jgit-4.7" with source configurePhase
 
-include "projects/jetty-9.3.17.tpd"
+include "projects/jetty-9.4.5.tpd"
 include "orbit/R20170516192513-Oxygen.tpd"
 
 location "http://download.eclipse.org/releases/oxygen/" {
 	org.eclipse.osgi lazy
-}
\ No newline at end of file
+}
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/R20170516192513-Oxygen.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/R20170516192513-Oxygen.tpd
index 3600628..ef19fa6 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/R20170516192513-Oxygen.tpd
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/R20170516192513-Oxygen.tpd
@@ -1,7 +1,7 @@
 target "R20170516192513-Oxygen" with source configurePhase
 // see http://download.eclipse.org/tools/orbit/downloads/
 
-location "http://download.eclipse.org/tools/orbit/R-builds/R20170516192513/repository" {
+location "http://download.eclipse.org/tools/orbit/downloads/drops/R20170516192513/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.compress [1.6.0.v201310281400,1.6.0.v201310281400]
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 ad92198..d1934e7 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>4.7.2-SNAPSHOT</version>
+    <version>4.8.1-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.target</artifactId>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/projects/jetty-9.3.17.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/projects/jetty-9.3.17.tpd
deleted file mode 100644
index 662df09..0000000
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/projects/jetty-9.3.17.tpd
+++ /dev/null
@@ -1,20 +0,0 @@
-target "jetty-9.3.17" with source configurePhase
-
-location jetty-9.3.17 "http://download.eclipse.org/jetty/updates/jetty-bundles-9.x/9.3.17.v20170317/" {
-	org.eclipse.jetty.client [9.3.17.v20170317,9.3.17.v20170317]
-	org.eclipse.jetty.client.source [9.3.17.v20170317,9.3.17.v20170317]
-	org.eclipse.jetty.continuation [9.3.17.v20170317,9.3.17.v20170317]
-	org.eclipse.jetty.continuation.source [9.3.17.v20170317,9.3.17.v20170317]
-	org.eclipse.jetty.http [9.3.17.v20170317,9.3.17.v20170317]
-	org.eclipse.jetty.http.source [9.3.17.v20170317,9.3.17.v20170317]
-	org.eclipse.jetty.io [9.3.17.v20170317,9.3.17.v20170317]
-	org.eclipse.jetty.io.source [9.3.17.v20170317,9.3.17.v20170317]
-	org.eclipse.jetty.security [9.3.17.v20170317,9.3.17.v20170317]
-	org.eclipse.jetty.security.source [9.3.17.v20170317,9.3.17.v20170317]
-	org.eclipse.jetty.server [9.3.17.v20170317,9.3.17.v20170317]
-	org.eclipse.jetty.server.source [9.3.17.v20170317,9.3.17.v20170317]
-	org.eclipse.jetty.servlet [9.3.17.v20170317,9.3.17.v20170317]
-	org.eclipse.jetty.servlet.source [9.3.17.v20170317,9.3.17.v20170317]
-	org.eclipse.jetty.util [9.3.17.v20170317,9.3.17.v20170317]
-	org.eclipse.jetty.util.source [9.3.17.v20170317,9.3.17.v20170317]
-}
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/projects/jetty-9.3.9.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/projects/jetty-9.3.9.tpd
deleted file mode 100644
index d5621a0..0000000
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/projects/jetty-9.3.9.tpd
+++ /dev/null
@@ -1,20 +0,0 @@
-target "jetty-9.4.3" with source configurePhase
-
-location jetty-9.4.3 "http://download.eclipse.org/jetty/updates/jetty-bundles-9.x/9.3.9.v20160517/" {
-	org.eclipse.jetty.client [9.3.9.v20160517,9.3.9.v20160517]
-	org.eclipse.jetty.client.source [9.3.9.v20160517,9.3.9.v20160517]
-	org.eclipse.jetty.continuation [9.3.9.v20160517,9.3.9.v20160517]
-	org.eclipse.jetty.continuation.source [9.3.9.v20160517,9.3.9.v20160517]
-	org.eclipse.jetty.http [9.3.9.v20160517,9.3.9.v20160517]
-	org.eclipse.jetty.http.source [9.3.9.v20160517,9.3.9.v20160517]
-	org.eclipse.jetty.io [9.3.9.v20160517,9.3.9.v20160517]
-	org.eclipse.jetty.io.source [9.3.9.v20160517,9.3.9.v20160517]
-	org.eclipse.jetty.security [9.3.9.v20160517,9.3.9.v20160517]
-	org.eclipse.jetty.security.source [9.3.9.v20160517,9.3.9.v20160517]
-	org.eclipse.jetty.server [9.3.9.v20160517,9.3.9.v20160517]
-	org.eclipse.jetty.server.source [9.3.9.v20160517,9.3.9.v20160517]
-	org.eclipse.jetty.servlet [9.3.9.v20160517,9.3.9.v20160517]
-	org.eclipse.jetty.servlet.source [9.3.9.v20160517,9.3.9.v20160517]
-	org.eclipse.jetty.util [9.3.9.v20160517,9.3.9.v20160517]
-	org.eclipse.jetty.util.source [9.3.9.v20160517,9.3.9.v20160517]
-}
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/projects/jetty-9.4.5.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/projects/jetty-9.4.5.tpd
new file mode 100644
index 0000000..363c600
--- /dev/null
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/projects/jetty-9.4.5.tpd
@@ -0,0 +1,20 @@
+target "jetty-9.4.5" with source configurePhase
+
+location jetty-9.4.5 "http://download.eclipse.org/jetty/updates/jetty-bundles-9.x/9.4.5.v20170502/" {
+	org.eclipse.jetty.client [9.4.5.v20170502,9.4.5.v20170502]
+	org.eclipse.jetty.client.source [9.4.5.v20170502,9.4.5.v20170502]
+	org.eclipse.jetty.continuation [9.4.5.v20170502,9.4.5.v20170502]
+	org.eclipse.jetty.continuation.source [9.4.5.v20170502,9.4.5.v20170502]
+	org.eclipse.jetty.http [9.4.5.v20170502,9.4.5.v20170502]
+	org.eclipse.jetty.http.source [9.4.5.v20170502,9.4.5.v20170502]
+	org.eclipse.jetty.io [9.4.5.v20170502,9.4.5.v20170502]
+	org.eclipse.jetty.io.source [9.4.5.v20170502,9.4.5.v20170502]
+	org.eclipse.jetty.security [9.4.5.v20170502,9.4.5.v20170502]
+	org.eclipse.jetty.security.source [9.4.5.v20170502,9.4.5.v20170502]
+	org.eclipse.jetty.server [9.4.5.v20170502,9.4.5.v20170502]
+	org.eclipse.jetty.server.source [9.4.5.v20170502,9.4.5.v20170502]
+	org.eclipse.jetty.servlet [9.4.5.v20170502,9.4.5.v20170502]
+	org.eclipse.jetty.servlet.source [9.4.5.v20170502,9.4.5.v20170502]
+	org.eclipse.jetty.util [9.4.5.v20170502,9.4.5.v20170502]
+	org.eclipse.jetty.util.source [9.4.5.v20170502,9.4.5.v20170502]
+}
diff --git a/org.eclipse.jgit.packaging/pom.xml b/org.eclipse.jgit.packaging/pom.xml
index b284442..2613686 100644
--- a/org.eclipse.jgit.packaging/pom.xml
+++ b/org.eclipse.jgit.packaging/pom.xml
@@ -53,7 +53,7 @@
 
   <groupId>org.eclipse.jgit</groupId>
   <artifactId>jgit.tycho.parent</artifactId>
-  <version>4.7.2-SNAPSHOT</version>
+  <version>4.8.1-SNAPSHOT</version>
   <packaging>pom</packaging>
 
   <name>JGit Tycho Parent</name>
@@ -216,7 +216,7 @@
         <plugin>
           <groupId>org.eclipse.cbi.maven.plugins</groupId>
           <artifactId>eclipse-jarsigner-plugin</artifactId>
-          <version>1.1.3</version>
+          <version>1.1.4</version>
         </plugin>
         <plugin>
           <groupId>org.codehaus.mojo</groupId>
diff --git a/org.eclipse.jgit.pgm.test/META-INF/MANIFEST.MF b/org.eclipse.jgit.pgm.test/META-INF/MANIFEST.MF
index 3cba803..37bef75 100644
--- a/org.eclipse.jgit.pgm.test/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.pgm.test/META-INF/MANIFEST.MF
@@ -2,28 +2,28 @@
 Bundle-ManifestVersion: 2
 Bundle-Name: %plugin_name
 Bundle-SymbolicName: org.eclipse.jgit.pgm.test
-Bundle-Version: 4.7.2.qualifier
+Bundle-Version: 4.8.1.qualifier
 Bundle-Vendor: %provider_name
 Bundle-Localization: plugin
 Bundle-ActivationPolicy: lazy
 Bundle-RequiredExecutionEnvironment: JavaSE-1.8
-Import-Package: org.eclipse.jgit.api;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.api.errors;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.diff;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.dircache;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.internal.storage.file;version="4.7.2",
- org.eclipse.jgit.junit;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.lib;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.merge;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.pgm;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.pgm.internal;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.pgm.opt;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.revwalk;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.storage.file;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.transport;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.treewalk;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.util;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.util.io;version="[4.7.2,4.8.0)",
+Import-Package: org.eclipse.jgit.api;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.api.errors;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.diff;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.dircache;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.internal.storage.file;version="4.8.1",
+ org.eclipse.jgit.junit;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.lib;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.merge;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.pgm;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.pgm.internal;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.pgm.opt;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.revwalk;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.storage.file;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.transport;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.treewalk;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.util;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.util.io;version="[4.8.1,4.9.0)",
  org.hamcrest.core;bundle-version="[1.1.0,2.0.0)",
  org.junit;version="[4.11.0,5.0.0)",
  org.junit.rules;version="[4.11.0,5.0.0)",
diff --git a/org.eclipse.jgit.pgm.test/pom.xml b/org.eclipse.jgit.pgm.test/pom.xml
index 4cc2eeb..ee234d8 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>4.7.2-SNAPSHOT</version>
+    <version>4.8.1-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.pgm.test</artifactId>
diff --git a/org.eclipse.jgit.pgm/BUILD b/org.eclipse.jgit.pgm/BUILD
index 6d32790..0792268 100644
--- a/org.eclipse.jgit.pgm/BUILD
+++ b/org.eclipse.jgit.pgm/BUILD
@@ -1,6 +1,6 @@
 java_library(
     name = "pgm",
-    srcs = glob(["src/**"]),
+    srcs = glob(["src/**/*.java"]),
     resource_strip_prefix = "org.eclipse.jgit.pgm/resources",
     resources = glob(["resources/**"]),
     visibility = ["//visibility:public"],
diff --git a/org.eclipse.jgit.pgm/META-INF/MANIFEST.MF b/org.eclipse.jgit.pgm/META-INF/MANIFEST.MF
index 7a8b1e7..5bde6a3 100644
--- a/org.eclipse.jgit.pgm/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.pgm/META-INF/MANIFEST.MF
@@ -2,7 +2,7 @@
 Bundle-ManifestVersion: 2
 Bundle-Name: %plugin_name
 Bundle-SymbolicName: org.eclipse.jgit.pgm
-Bundle-Version: 4.7.2.qualifier
+Bundle-Version: 4.8.1.qualifier
 Bundle-Vendor: %provider_name
 Bundle-ActivationPolicy: lazy
 Bundle-Localization: plugin
@@ -13,60 +13,60 @@
  org.apache.commons.compress.archivers.tar;version="[1.3,2.0)",
  org.apache.commons.compress.archivers.zip;version="[1.3,2.0)",
  org.apache.commons.logging;version="1.1.1",
- org.eclipse.jetty.continuation;version="[9.0.0,9.4.0)",
- org.eclipse.jetty.http;version="[9.0.0,9.4.0)",
- org.eclipse.jetty.io;version="[9.0.0,9.4.0)",
- org.eclipse.jetty.security;version="[9.0.0,9.4.0)",
- org.eclipse.jetty.security.authentication;version="[9.0.0,9.4.0)",
- org.eclipse.jetty.server;version="[9.0.0,9.4.0)",
- org.eclipse.jetty.server.handler;version="[9.0.0,9.4.0)",
- org.eclipse.jetty.server.nio;version="[9.0.0,9.4.0)",
- org.eclipse.jetty.servlet;version="[9.0.0,9.4.0)",
- org.eclipse.jetty.util;version="[9.0.0,9.4.0)",
- org.eclipse.jetty.util.component;version="[9.0.0,9.4.0)",
- org.eclipse.jetty.util.log;version="[9.0.0,9.4.0)",
- org.eclipse.jetty.util.security;version="[9.0.0,9.4.0)",
- org.eclipse.jetty.util.thread;version="[9.0.0,9.4.0)",
- org.eclipse.jgit.api;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.api.errors;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.archive;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.awtui;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.blame;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.diff;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.dircache;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.errors;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.gitrepo;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.internal.ketch;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.internal.storage.file;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.internal.storage.pack;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.internal.storage.reftree;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.lfs;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.lfs.lib;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.lfs.server;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.lfs.server.fs;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.lfs.server.s3;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.lib;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.merge;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.nls;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.notes;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.revplot;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.revwalk;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.revwalk.filter;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.storage.file;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.storage.pack;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.transport;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.transport.http.apache;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.transport.resolver;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.treewalk;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.treewalk.filter;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.util;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.util.io;version="[4.7.2,4.8.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)",
+ org.eclipse.jetty.security;version="[9.4.5,10.0.0)",
+ org.eclipse.jetty.security.authentication;version="[9.4.5,10.0.0)",
+ org.eclipse.jetty.server;version="[9.4.5,10.0.0)",
+ org.eclipse.jetty.server.handler;version="[9.4.5,10.0.0)",
+ org.eclipse.jetty.server.nio;version="[9.4.5,10.0.0)",
+ org.eclipse.jetty.servlet;version="[9.4.5,10.0.0)",
+ org.eclipse.jetty.util;version="[9.4.5,10.0.0)",
+ org.eclipse.jetty.util.component;version="[9.4.5,10.0.0)",
+ 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="[4.8.1,4.9.0)",
+ org.eclipse.jgit.api.errors;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.archive;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.awtui;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.blame;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.diff;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.dircache;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.errors;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.gitrepo;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.internal.ketch;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.internal.storage.file;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.internal.storage.pack;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.internal.storage.reftree;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.lfs;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.lfs.lib;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.lfs.server;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.lfs.server.fs;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.lfs.server.s3;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.lib;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.merge;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.nls;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.notes;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.revplot;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.revwalk;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.revwalk.filter;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.storage.file;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.storage.pack;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.transport;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.transport.http.apache;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.transport.resolver;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.treewalk;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.treewalk.filter;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.util;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.util.io;version="[4.8.1,4.9.0)",
  org.kohsuke.args4j;version="[2.0.12,2.1.0)",
  org.kohsuke.args4j.spi;version="[2.0.15,2.1.0)"
-Export-Package: org.eclipse.jgit.console;version="4.7.2";
+Export-Package: org.eclipse.jgit.console;version="4.8.1";
   uses:="org.eclipse.jgit.transport,
    org.eclipse.jgit.util",
- org.eclipse.jgit.pgm;version="4.7.2";
+ org.eclipse.jgit.pgm;version="4.8.1";
   uses:="org.eclipse.jgit.revwalk,
    org.eclipse.jgit.treewalk.filter,
    org.eclipse.jgit.pgm.opt,
@@ -77,11 +77,11 @@
    org.eclipse.jgit.treewalk,
    javax.swing,
    org.eclipse.jgit.transport",
- org.eclipse.jgit.pgm.debug;version="4.7.2";
+ org.eclipse.jgit.pgm.debug;version="4.8.1";
   uses:="org.eclipse.jgit.util.io,
    org.eclipse.jgit.pgm",
- org.eclipse.jgit.pgm.internal;version="4.7.2";x-friends:="org.eclipse.jgit.pgm.test,org.eclipse.jgit.test",
- org.eclipse.jgit.pgm.opt;version="4.7.2";
+ org.eclipse.jgit.pgm.internal;version="4.8.1";x-friends:="org.eclipse.jgit.pgm.test,org.eclipse.jgit.test",
+ org.eclipse.jgit.pgm.opt;version="4.8.1";
   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 913c8f4..d5d71ee 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: 4.7.2.qualifier
-Eclipse-SourceBundle: org.eclipse.jgit.pgm;version="4.7.2.qualifier";roots="."
+Bundle-Version: 4.8.1.qualifier
+Eclipse-SourceBundle: org.eclipse.jgit.pgm;version="4.8.1.qualifier";roots="."
diff --git a/org.eclipse.jgit.pgm/pom.xml b/org.eclipse.jgit.pgm/pom.xml
index 8bad5ce..7d6ec1d 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>4.7.2-SNAPSHOT</version>
+    <version>4.8.1-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.pgm</artifactId>
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 06e4d94..c3d7c68 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
@@ -44,6 +44,7 @@
 cantWrite=Can''t write {0}
 changesNotStagedForCommit=Changes not staged for commit:
 changesToBeCommitted=Changes to be committed:
+checkingOut=Submodule path ''{0}'': checked out ''{1}''
 checkoutConflict=error: Your local changes to the following files would be overwritten by checkout:
 checkoutConflictPathLine=\t{0}
 cleanRequireForce=clean.requireForce defaults to true and neither -n nor -f given; refusing to clean
@@ -66,12 +67,14 @@
 failedToLockTag=Failed to lock tag {0}: {1}
 fatalError=fatal: {0}
 fatalThisProgramWillDestroyTheRepository=fatal: This program will destroy the repository\nfatal:\nfatal:\nfatal:    {0}\nfatal:\nfatal: To continue, add {1} to the command line\nfatal:
+fetchingSubmodule=Fetching submodule {0}
 fileIsRequired=argument file is required
 ffNotPossibleAborting=Not possible to fast-forward, aborting.
 forcedUpdate=forced update
 fromURI=From {0}
 initializedEmptyGitRepositoryIn=Initialized empty Git repository in {0}
 invalidHttpProxyOnlyHttpSupported=Invalid http_proxy: {0}: Only http supported.
+invalidRecurseSubmodulesMode=Invalid recurse submodules mode: {0}
 jgitVersion=jgit version {0}
 lineFormat={0}
 listeningOn=Listening on {0}
@@ -197,6 +200,7 @@
 statusDeletedByUs=deleted by us:
 statusBothAdded=both added:
 statusBothModified=both modified:
+submoduleRegistered=Submodule {0} registered
 switchedToNewBranch=Switched to a new branch ''{0}''
 switchedToBranch=Switched to branch ''{0}''
 tagAlreadyExists=tag ''{0}'' already exists
@@ -283,7 +287,7 @@
 usage_addFileContentsToTheIndex=Add file contents to the index
 usage_alterTheDetailShown=alter the detail shown
 usage_approveDestructionOfRepository=approve destruction of repository
-usage_archive=zip up files from the named tree
+usage_archive=Zip up files from the named tree
 usage_archiveFormat=archive format. Currently supported formats: 'tar', 'zip', 'tgz', 'tbz2', 'txz'
 usage_archiveOutput=output file to write the archive to
 usage_archivePrefix=string to prepend to each pathname in the archive
@@ -357,6 +361,7 @@
 usage_noCommit=Don't commit after a successful merge
 usage_noPrefix=do not show any source or destination prefix
 usage_noRenames=disable rename detection
+usage_noRecurseSubmodules=Disable recursive fetching of submodules (this has the same effect as using the --recurse-submodules=no option)
 usage_noShowStandardNotes=Disable showing notes from the standard /refs/notes/commits branch
 usage_onlyMatchAgainstAlreadyTrackedFiles=Only match <filepattern> against already tracked files in the index rather than the working tree
 usage_outputFile=Output file
@@ -371,6 +376,7 @@
 usage_quiet=don't show progress messages
 usage_recordChangesToRepository=Record changes to the repository
 usage_recurseIntoSubtrees=recurse into subtrees
+usage_recurseSubmodules=recurse into submodules
 usage_removeUntrackedDirectories=remove untracked directories
 usage_renameLimit=limit size of rename matrix
 usage_reset=Reset current HEAD to the specified state
@@ -381,7 +387,7 @@
 usage_runLfsStore=Run LFS Store in a given directory
 usage_S3NoSslVerify=Skip verification of Amazon server certificate and hostname
 usage_setTheGitRepositoryToOperateOn=set the git repository to operate on
-usage_show=display one commit
+usage_show=Display one commit
 usage_showRefNamesMatchingCommits=Show ref names matching commits
 usage_showPatch=display patch
 usage_showNotes=Add this ref to the list of note branches from which notes are displayed
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/AbstractFetchCommand.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/AbstractFetchCommand.java
index 2cee2cb..08ec096 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/AbstractFetchCommand.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/AbstractFetchCommand.java
@@ -90,6 +90,9 @@
 			}
 		}
 		showRemoteMessages(errw, r.getMessages());
+		for (FetchResult submoduleResult : r.submoduleResults().values()) {
+			showFetchResult(submoduleResult);
+		}
 	}
 
 	static void showRemoteMessages(ThrowingPrintWriter writer, String pkt) throws IOException {
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Clone.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Clone.java
index 0407828..ca5205a 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Clone.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Clone.java
@@ -44,11 +44,14 @@
 package org.eclipse.jgit.pgm;
 
 import java.io.File;
+import java.io.IOException;
 import java.text.MessageFormat;
+import java.util.Collection;
 
 import org.eclipse.jgit.api.CloneCommand;
 import org.eclipse.jgit.api.Git;
 import org.eclipse.jgit.api.errors.InvalidRemoteException;
+import org.eclipse.jgit.lib.AnyObjectId;
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.TextProgressMonitor;
 import org.eclipse.jgit.pgm.internal.CLIText;
@@ -58,7 +61,7 @@
 import org.kohsuke.args4j.Option;
 
 @Command(common = true, usage = "usage_cloneRepositoryIntoNewDir")
-class Clone extends AbstractFetchCommand {
+class Clone extends AbstractFetchCommand implements CloneCommand.Callback {
 	@Option(name = "--origin", aliases = { "-o" }, metaVar = "metaVar_remoteName", usage = "usage_useNameInsteadOfOriginToTrackUpstream")
 	private String remoteName = Constants.DEFAULT_REMOTE_NAME;
 
@@ -74,6 +77,9 @@
 	@Option(name = "--quiet", usage = "usage_quiet")
 	private Boolean quiet;
 
+	@Option(name = "--recurse-submodules", usage = "usage_recurseSubmodules")
+	private boolean cloneSubmodules;
+
 	@Argument(index = 0, required = true, metaVar = "metaVar_uriish")
 	private String sourceUri;
 
@@ -109,13 +115,15 @@
 
 		CloneCommand command = Git.cloneRepository();
 		command.setURI(sourceUri).setRemote(remoteName).setBare(isBare)
-				.setNoCheckout(noCheckout).setBranch(branch);
+				.setNoCheckout(noCheckout).setBranch(branch)
+				.setCloneSubmodules(cloneSubmodules);
 
 		command.setGitDir(gitdir == null ? null : new File(gitdir));
 		command.setDirectory(localNameF);
 		boolean msgs = quiet == null || !quiet.booleanValue();
 		if (msgs) {
-			command.setProgressMonitor(new TextProgressMonitor(errw));
+			command.setProgressMonitor(new TextProgressMonitor(errw))
+					.setCallback(this);
 			outw.println(MessageFormat.format(
 					CLIText.get().cloningInto, localName));
 			outw.flush();
@@ -136,4 +144,39 @@
 			outw.flush();
 		}
 	}
+
+	@Override
+	public void initializedSubmodules(Collection<String> submodules) {
+		try {
+			for (String submodule : submodules) {
+				outw.println(MessageFormat
+						.format(CLIText.get().submoduleRegistered, submodule));
+			}
+			outw.flush();
+		} catch (IOException e) {
+			// ignore
+		}
+	}
+
+	@Override
+	public void cloningSubmodule(String path) {
+		try {
+			outw.println(MessageFormat.format(
+					CLIText.get().cloningInto, path));
+			outw.flush();
+		} catch (IOException e) {
+			// ignore
+		}
+	}
+
+	@Override
+	public void checkingOut(AnyObjectId commit, String path) {
+		try {
+			outw.println(MessageFormat.format(CLIText.get().checkingOut,
+					path, commit.getName()));
+			outw.flush();
+		} catch (IOException e) {
+			// ignore
+		}
+	}
 }
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Fetch.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Fetch.java
index ed06733..5ed23b9 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Fetch.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Fetch.java
@@ -45,11 +45,15 @@
 
 package org.eclipse.jgit.pgm;
 
+import java.io.IOException;
+import java.text.MessageFormat;
 import java.util.List;
 
 import org.eclipse.jgit.api.FetchCommand;
 import org.eclipse.jgit.api.Git;
 import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.SubmoduleConfig.FetchRecurseSubmodulesMode;
+import org.eclipse.jgit.pgm.internal.CLIText;
 import org.eclipse.jgit.lib.TextProgressMonitor;
 import org.eclipse.jgit.transport.FetchResult;
 import org.eclipse.jgit.transport.RefSpec;
@@ -58,7 +62,7 @@
 import org.kohsuke.args4j.Option;
 
 @Command(common = true, usage = "usage_updateRemoteRefsFromAnotherRepository")
-class Fetch extends AbstractFetchCommand {
+class Fetch extends AbstractFetchCommand implements FetchCommand.Callback {
 	@Option(name = "--timeout", metaVar = "metaVar_seconds", usage = "usage_abortConnectionIfNoActivity")
 	int timeout = -1;
 
@@ -96,6 +100,31 @@
 		tags = Boolean.FALSE;
 	}
 
+	private FetchRecurseSubmodulesMode recurseSubmodules;
+
+	@Option(name = "--recurse-submodules", usage = "usage_recurseSubmodules")
+	void recurseSubmodules(String mode) {
+		if (mode == null || mode.isEmpty()) {
+			recurseSubmodules = FetchRecurseSubmodulesMode.YES;
+		} else {
+			for (FetchRecurseSubmodulesMode m : FetchRecurseSubmodulesMode
+					.values()) {
+				if (m.matchConfigValue(mode)) {
+					recurseSubmodules = m;
+					return;
+				}
+			}
+			throw die(MessageFormat
+					.format(CLIText.get().invalidRecurseSubmodulesMode, mode));
+		}
+	}
+
+	@Option(name = "--no-recurse-submodules", usage = "usage_noRecurseSubmodules")
+	void noRecurseSubmodules(@SuppressWarnings("unused")
+	final boolean ignored) {
+		recurseSubmodules = FetchRecurseSubmodulesMode.NO;
+	}
+
 	@Argument(index = 0, metaVar = "metaVar_uriish")
 	private String remote = Constants.DEFAULT_REMOTE_NAME;
 
@@ -124,12 +153,25 @@
 				fetch.setThin(thin.booleanValue());
 			if (quiet == null || !quiet.booleanValue())
 				fetch.setProgressMonitor(new TextProgressMonitor(errw));
+			fetch.setRecurseSubmodules(recurseSubmodules).setCallback(this);
 
 			FetchResult result = fetch.call();
-			if (result.getTrackingRefUpdates().isEmpty())
+			if (result.getTrackingRefUpdates().isEmpty()
+					&& result.submoduleResults().isEmpty())
 				return;
 
 			showFetchResult(result);
 		}
 	}
+
+	@Override
+	public void fetchingSubmodule(String name) {
+		try {
+			outw.println(MessageFormat.format(CLIText.get().fetchingSubmodule,
+					name));
+			outw.flush();
+		} catch (IOException e) {
+			// ignore
+		}
+	}
 }
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 3addecb..c94ba0b 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,8 +44,11 @@
 
 package org.eclipse.jgit.pgm;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
+
 import java.io.File;
 import java.io.IOException;
+import java.io.OutputStreamWriter;
 import java.io.PrintWriter;
 import java.lang.reflect.InvocationTargetException;
 import java.net.MalformedURLException;
@@ -54,6 +57,10 @@
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Locale;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.TimeUnit;
 
 import org.eclipse.jgit.awtui.AwtAuthenticator;
 import org.eclipse.jgit.awtui.AwtCredentialsProvider;
@@ -95,6 +102,8 @@
 
 	PrintWriter writer;
 
+	private ExecutorService gcExecutor;
+
 	/**
 	 *
 	 */
@@ -102,6 +111,17 @@
 		HttpTransport.setConnectionFactory(new HttpClientConnectionFactory());
 		CleanFilter.register();
 		SmudgeFilter.register();
+		gcExecutor = Executors.newSingleThreadExecutor(new ThreadFactory() {
+			private final ThreadFactory baseFactory = Executors
+					.defaultThreadFactory();
+
+			@Override
+			public Thread newThread(Runnable taskBody) {
+				Thread thr = baseFactory.newThread(taskBody);
+				thr.setName("JGit-autoGc"); //$NON-NLS-1$
+				return thr;
+			}
+		});
 	}
 
 	/**
@@ -189,10 +209,12 @@
 			// broken pipe
 			exit(1, null);
 		}
+		gcExecutor.shutdown();
+		gcExecutor.awaitTermination(10, TimeUnit.MINUTES);
 	}
 
 	PrintWriter createErrorWriter() {
-		return new PrintWriter(System.err);
+		return new PrintWriter(new OutputStreamWriter(System.err, UTF_8));
 	}
 
 	private void execute(final String[] argv) throws Exception {
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/internal/CLIText.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/internal/CLIText.java
index 4842b98..e012372 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/internal/CLIText.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/internal/CLIText.java
@@ -121,6 +121,7 @@
 	/***/ public String cantWrite;
 	/***/ public String changesNotStagedForCommit;
 	/***/ public String changesToBeCommitted;
+	/***/ public String checkingOut;
 	/***/ public String checkoutConflict;
 	/***/ public String checkoutConflictPathLine;
 	/***/ public String cleanRequireForce;
@@ -142,12 +143,14 @@
 	/***/ public String failedToLockTag;
 	/***/ public String fatalError;
 	/***/ public String fatalThisProgramWillDestroyTheRepository;
+	/***/ public String fetchingSubmodule;
 	/***/ public String fileIsRequired;
 	/***/ public String ffNotPossibleAborting;
 	/***/ public String forcedUpdate;
 	/***/ public String fromURI;
 	/***/ public String initializedEmptyGitRepositoryIn;
 	/***/ public String invalidHttpProxyOnlyHttpSupported;
+	/***/ public String invalidRecurseSubmodulesMode;
 	/***/ public String jgitVersion;
 	/***/ public String lfsNoAccessKey;
 	/***/ public String lfsNoSecretKey;
@@ -263,6 +266,7 @@
 	/***/ public String statusDeletedByUs;
 	/***/ public String statusBothAdded;
 	/***/ public String statusBothModified;
+	/***/ public String submoduleRegistered;
 	/***/ public String switchedToNewBranch;
 	/***/ public String switchedToBranch;
 	/***/ public String tagAlreadyExists;
diff --git a/org.eclipse.jgit.test/META-INF/MANIFEST.MF b/org.eclipse.jgit.test/META-INF/MANIFEST.MF
index 73c1a03..7bacb05 100644
--- a/org.eclipse.jgit.test/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.test/META-INF/MANIFEST.MF
@@ -2,54 +2,54 @@
 Bundle-ManifestVersion: 2
 Bundle-Name: %plugin_name
 Bundle-SymbolicName: org.eclipse.jgit.test
-Bundle-Version: 4.7.2.qualifier
+Bundle-Version: 4.8.1.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)",
- org.eclipse.jgit.api;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.api.errors;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.attributes;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.awtui;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.blame;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.diff;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.dircache;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.errors;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.events;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.fnmatch;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.gitrepo;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.hooks;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.ignore;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.ignore.internal;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.internal;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.internal.storage.dfs;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.internal.storage.file;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.internal.storage.pack;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.internal.storage.reftree;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.junit;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.lfs;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.lib;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.merge;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.nls;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.notes;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.patch;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.pgm;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.pgm.internal;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.revplot;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.revwalk;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.revwalk.filter;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.storage.file;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.storage.pack;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.submodule;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.transport;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.transport.http;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.transport.resolver;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.treewalk;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.treewalk.filter;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.util;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.util.io;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.util.sha1;version="[4.7.2,4.8.0)",
+ org.eclipse.jgit.api;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.api.errors;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.attributes;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.awtui;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.blame;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.diff;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.dircache;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.errors;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.events;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.fnmatch;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.gitrepo;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.hooks;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.ignore;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.ignore.internal;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.internal;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.internal.storage.dfs;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.internal.storage.file;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.internal.storage.pack;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.internal.storage.reftree;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.junit;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.lfs;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.lib;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.merge;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.nls;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.notes;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.patch;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.pgm;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.pgm.internal;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.revplot;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.revwalk;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.revwalk.filter;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.storage.file;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.storage.pack;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.submodule;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.transport;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.transport.http;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.transport.resolver;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.treewalk;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.treewalk.filter;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.util;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.util.io;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.util.sha1;version="[4.8.1,4.9.0)",
  org.junit;version="[4.4.0,5.0.0)",
  org.junit.experimental.theories;version="[4.4.0,5.0.0)",
  org.junit.rules;version="[4.11.0,5.0.0)",
diff --git a/org.eclipse.jgit.test/pom.xml b/org.eclipse.jgit.test/pom.xml
index 6ea0642..c373112 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>4.7.2-SNAPSHOT</version>
+    <version>4.8.1-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.test</artifactId>
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 b405f6a..6f6b115 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
@@ -61,6 +61,7 @@
 import org.eclipse.jgit.lib.ConfigConstants;
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Ref;
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.lib.RepositoryState;
 import org.eclipse.jgit.lib.StoredConfig;
@@ -148,6 +149,57 @@
 	}
 
 	@Test
+	public void testPullFastForwardDetachedHead() throws Exception {
+		Repository repository = source.getRepository();
+		writeToFile(sourceFile, "2nd commit");
+		source.add().addFilepattern("SomeFile.txt").call();
+		source.commit().setMessage("2nd commit").call();
+
+		try (RevWalk revWalk = new RevWalk(repository)) {
+			// git checkout HEAD^
+			String initialBranch = repository.getBranch();
+			Ref initialRef = repository.findRef(Constants.HEAD);
+			RevCommit initialCommit = revWalk
+					.parseCommit(initialRef.getObjectId());
+			assertEquals("this test need linear history", 1,
+					initialCommit.getParentCount());
+			source.checkout().setName(initialCommit.getParent(0).getName())
+					.call();
+			assertFalse("expected detached HEAD",
+					repository.getFullBranch().startsWith(Constants.R_HEADS));
+
+			// change and commit another file
+			File otherFile = new File(sourceFile.getParentFile(),
+					System.currentTimeMillis() + ".tst");
+			writeToFile(otherFile, "other 2nd commit");
+			source.add().addFilepattern(otherFile.getName()).call();
+			RevCommit newCommit = source.commit().setMessage("other 2nd commit")
+					.call();
+
+			// git pull --rebase initialBranch
+			source.pull().setRebase(true).setRemote(".")
+					.setRemoteBranchName(initialBranch)
+					.call();
+
+			assertEquals(RepositoryState.SAFE,
+					source.getRepository().getRepositoryState());
+			Ref head = source.getRepository().findRef(Constants.HEAD);
+			RevCommit headCommit = revWalk.parseCommit(head.getObjectId());
+
+			// HEAD^ == initialCommit, no merge commit
+			assertEquals(1, headCommit.getParentCount());
+			assertEquals(initialCommit, headCommit.getParent(0));
+
+			// both contributions for both commits are available
+			assertFileContentsEqual(sourceFile, "2nd commit");
+			assertFileContentsEqual(otherFile, "other 2nd commit");
+			// HEAD has same message as rebased commit
+			assertEquals(newCommit.getShortMessage(),
+					headCommit.getShortMessage());
+		}
+	}
+
+	@Test
 	public void testPullConflict() throws Exception {
 		PullResult res = target.pull().call();
 		// nothing to update since we don't have different data yet
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 b8f8dcb..4130b7e 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
@@ -196,6 +196,32 @@
 	}
 
 	@Test
+	public void testEdit_DeleteNearCommonTail() {
+		EditList r = diff(t("aCq}nD}nb"), t("aq}nb"));
+		assertEquals(new Edit(1, 2, 1, 1), r.get(0));
+		assertEquals(new Edit(5, 8, 4, 4), r.get(1));
+		assertEquals(2, r.size());
+	}
+
+	@Test
+	public void testEdit_DeleteNearCommonCenter() {
+		EditList r = diff(t("abcd123123uvwxpq"), t("aBcd123uvwxPq"));
+		assertEquals(new Edit(1, 2, 1, 2), r.get(0));
+		assertEquals(new Edit(7, 10, 7, 7), r.get(1));
+		assertEquals(new Edit(14, 15, 11, 12), r.get(2));
+		assertEquals(3, r.size());
+	}
+
+	@Test
+	public void testEdit_InsertNearCommonCenter() {
+		EditList r = diff(t("aBcd123uvwxPq"), t("abcd123123uvwxpq"));
+		assertEquals(new Edit(1, 2, 1, 2), r.get(0));
+		assertEquals(new Edit(7, 7, 7, 10), r.get(1));
+		assertEquals(new Edit(11, 12, 14, 15), r.get(2));
+		assertEquals(3, r.size());
+	}
+
+	@Test
 	public void testEdit_LinuxBug() {
 		EditList r = diff(t("a{bcdE}z"), t("a{0bcdEE}z"));
 		assertEquals(new Edit(2, 2, 2, 3), r.get(0));
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/gitrepo/RepoCommandSymlinkTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/gitrepo/RepoCommandSymlinkTest.java
new file mode 100644
index 0000000..12f4dcc
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/gitrepo/RepoCommandSymlinkTest.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2016, Google Inc.
+ * 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.gitrepo;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+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.junit.RepositoryTestCase;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.util.FS;
+import org.junit.Before;
+import org.junit.Test;
+
+public class RepoCommandSymlinkTest extends RepositoryTestCase {
+	@Before
+	public void beforeMethod() {
+		// If this assumption fails the tests are skipped. When running on a
+		// filesystem not supporting symlinks I don't want this tests
+		org.junit.Assume.assumeTrue(FS.DETECTED.supportsSymlinks());
+	}
+
+	private Repository defaultDb;
+
+	private String rootUri;
+	private String defaultUri;
+
+	@Override
+	public void setUp() throws Exception {
+		super.setUp();
+
+		defaultDb = createWorkRepository();
+		try (Git git = new Git(defaultDb)) {
+			JGitTestUtil.writeTrashFile(defaultDb, "hello.txt", "hello world");
+			git.add().addFilepattern("hello.txt").call();
+			git.commit().setMessage("Initial commit").call();
+			addRepoToClose(defaultDb);
+		}
+
+		defaultUri = defaultDb.getDirectory().toURI().toString();
+		int root = defaultUri.lastIndexOf("/",
+				defaultUri.lastIndexOf("/.git") - 1)
+				+ 1;
+		rootUri = defaultUri.substring(0, root)
+				+ "manifest";
+		defaultUri = defaultUri.substring(root);
+	}
+
+	@Test
+	public void testLinkFileBare() throws Exception {
+		try (
+				Repository remoteDb = createBareRepository();
+				Repository tempDb = createWorkRepository()) {
+			StringBuilder xmlContent = new StringBuilder();
+			xmlContent
+					.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n")
+					.append("<manifest>")
+					.append("<remote name=\"remote1\" fetch=\".\" />")
+					.append("<default revision=\"master\" remote=\"remote1\" />")
+					.append("<project path=\"foo\" name=\"").append(defaultUri)
+					.append("\" revision=\"master\" >")
+					.append("<linkfile src=\"hello.txt\" dest=\"LinkedHello\" />")
+					.append("<linkfile src=\"hello.txt\" dest=\"foo/LinkedHello\" />")
+					.append("<linkfile src=\"hello.txt\" dest=\"subdir/LinkedHello\" />")
+					.append("</project>")
+					.append("<project path=\"bar/baz\" name=\"")
+					.append(defaultUri).append("\" revision=\"master\" >")
+					.append("<linkfile src=\"hello.txt\" dest=\"bar/foo/LinkedHello\" />")
+					.append("</project>").append("</manifest>");
+			JGitTestUtil.writeTrashFile(tempDb, "manifest.xml",
+					xmlContent.toString());
+			RepoCommand command = new RepoCommand(remoteDb);
+			command.setPath(
+					tempDb.getWorkTree().getAbsolutePath() + "/manifest.xml")
+					.setURI(rootUri).call();
+			// Clone it
+			File directory = createTempDirectory("testCopyFileBare");
+			Repository localDb = Git.cloneRepository().setDirectory(directory)
+					.setURI(remoteDb.getDirectory().toURI().toString()).call()
+					.getRepository();
+
+			// The LinkedHello symlink should exist.
+			File linkedhello = new File(localDb.getWorkTree(), "LinkedHello");
+			assertTrue("The LinkedHello file should exist",
+					localDb.getFS().exists(linkedhello));
+			assertTrue("The LinkedHello file should be a symlink",
+					localDb.getFS().isSymLink(linkedhello));
+			assertEquals("foo/hello.txt",
+					localDb.getFS().readSymLink(linkedhello));
+
+			// The foo/LinkedHello file should be skipped.
+			File linkedfoohello = new File(localDb.getWorkTree(), "foo/LinkedHello");
+			assertFalse("The foo/LinkedHello file should be skipped",
+					localDb.getFS().exists(linkedfoohello));
+
+			// The subdir/LinkedHello file should use a relative ../
+			File linkedsubdirhello = new File(localDb.getWorkTree(),
+					"subdir/LinkedHello");
+			assertTrue("The subdir/LinkedHello file should exist",
+					localDb.getFS().exists(linkedsubdirhello));
+			assertTrue("The subdir/LinkedHello file should be a symlink",
+					localDb.getFS().isSymLink(linkedsubdirhello));
+			assertEquals("../foo/hello.txt",
+					localDb.getFS().readSymLink(linkedsubdirhello));
+
+			// The bar/foo/LinkedHello file should use a single relative ../
+			File linkedbarfoohello = new File(localDb.getWorkTree(),
+					"bar/foo/LinkedHello");
+			assertTrue("The bar/foo/LinkedHello file should exist",
+					localDb.getFS().exists(linkedbarfoohello));
+			assertTrue("The bar/foo/LinkedHello file should be a symlink",
+					localDb.getFS().isSymLink(linkedbarfoohello));
+			assertEquals("../baz/hello.txt",
+					localDb.getFS().readSymLink(linkedbarfoohello));
+
+			localDb.close();
+		}
+	}
+}
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 9cf4569..6ed2c21 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
@@ -46,12 +46,14 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
 import java.io.BufferedReader;
 import java.io.ByteArrayInputStream;
 import java.io.File;
 import java.io.FileReader;
 import java.io.IOException;
+import java.net.URI;
 import java.nio.charset.StandardCharsets;
 import java.util.HashMap;
 import java.util.Map;
@@ -184,6 +186,107 @@
 	}
 
 	@Test
+	public void androidSetup() throws Exception {
+		Repository child = Git.cloneRepository()
+				.setURI(groupADb.getDirectory().toURI().toString())
+				.setDirectory(createUniqueTestGitDir(true)).setBare(true).call()
+				.getRepository();
+
+		Repository dest = Git.cloneRepository()
+				.setURI(db.getDirectory().toURI().toString())
+				.setDirectory(createUniqueTestGitDir(true)).setBare(true).call()
+				.getRepository();
+
+		assertTrue(dest.isBare());
+		assertTrue(child.isBare());
+
+		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=\"base\" name=\"platform/base\" />")
+			.append("</manifest>");
+		RepoCommand cmd = new RepoCommand(dest);
+
+		IndexedRepos repos = new IndexedRepos();
+		repos.put("platform/base", child);
+
+		RevCommit commit = cmd
+			.setInputStream(new ByteArrayInputStream(xmlContent.toString().getBytes(StandardCharsets.UTF_8)))
+			.setRemoteReader(repos)
+			.setURI("platform/")
+			.setTargetURI("platform/superproject")
+			.setRecordRemoteBranch(true)
+			.setRecordSubmoduleLabels(true)
+			.call();
+
+		String idStr = commit.getId().name() + ":" + ".gitmodules";
+		ObjectId modId = dest.resolve(idStr);
+
+		try (ObjectReader reader = dest.newObjectReader()) {
+			byte[] bytes = reader.open(modId).getCachedBytes(Integer.MAX_VALUE);
+			Config base = new Config();
+			BlobBasedConfig cfg = new BlobBasedConfig(base, bytes);
+			String subUrl = cfg.getString("submodule", "base", "url");
+			assertEquals(subUrl, "../base");
+		}
+
+		child.close();
+		dest.close();
+	}
+
+	@Test
+	public void gerritSetup() throws Exception {
+		Repository child =
+			Git.cloneRepository().setURI(groupADb.getDirectory().toURI().toString())
+				.setDirectory(createUniqueTestGitDir(true))
+				.setBare(true).call().getRepository();
+
+		Repository dest = Git.cloneRepository()
+			.setURI(db.getDirectory().toURI().toString()).setDirectory(createUniqueTestGitDir(true))
+			.setBare(true).call().getRepository();
+
+		assertTrue(dest.isBare());
+		assertTrue(child.isBare());
+
+		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=\"plugins/cookbook\" name=\"plugins/cookbook\" />")
+			.append("</manifest>");
+		RepoCommand cmd = new RepoCommand(dest);
+
+		IndexedRepos repos = new IndexedRepos();
+		repos.put("plugins/cookbook", child);
+
+		RevCommit commit = cmd
+			.setInputStream(new ByteArrayInputStream(xmlContent.toString().getBytes(StandardCharsets.UTF_8)))
+			.setRemoteReader(repos)
+			.setURI("")
+			.setTargetURI("gerrit")
+			.setRecordRemoteBranch(true)
+			.setRecordSubmoduleLabels(true)
+			.call();
+
+		String idStr = commit.getId().name() + ":" + ".gitmodules";
+		ObjectId modId = dest.resolve(idStr);
+
+		try (ObjectReader reader = dest.newObjectReader()) {
+			byte[] bytes = reader.open(modId).getCachedBytes(Integer.MAX_VALUE);
+			Config base = new Config();
+			BlobBasedConfig cfg = new BlobBasedConfig(base, bytes);
+			String subUrl = cfg.getString("submodule", "plugins/cookbook", "url");
+			assertEquals(subUrl, "../plugins/cookbook");
+		}
+
+		child.close();
+		dest.close();
+	}
+
+	@Test
 	public void absoluteRemoteURL() throws Exception {
 		Repository child =
 			Git.cloneRepository().setURI(groupADb.getDirectory().toURI().toString())
@@ -217,6 +320,7 @@
 					.setInputStream(new ByteArrayInputStream(xmlContent.toString().getBytes(StandardCharsets.UTF_8)))
 					.setRemoteReader(repos)
 					.setURI(baseUrl)
+					.setTargetURI("gerrit")
 					.setRecordRemoteBranch(true)
 					.setRecordSubmoduleLabels(true)
 					.call();
@@ -997,4 +1101,28 @@
 			start = newStart;
 		}
 	}
+
+	void testRelative(String a, String b, String want) {
+		String got = RepoCommand.relativize(URI.create(a), URI.create(b)).toString();
+
+		if (!got.equals(want)) {
+			fail(String.format("relative('%s', '%s') = '%s', want '%s'", a, b, got, want));
+		}
+	}
+
+	@Test
+	public void relative() {
+		testRelative("a/b/", "a/", "../");
+		// Normalization:
+		testRelative("a/p/..//b/", "a/", "../");
+		testRelative("a/b", "a/", "");
+		testRelative("a/", "a/b/", "b/");
+		testRelative("a/", "a/b", "b");
+		testRelative("/a/b/c", "/b/c", "../../b/c");
+		testRelative("/abc", "bcd", "bcd");
+		testRelative("abc", "def", "def");
+		testRelative("abc", "/bcd", "/bcd");
+		testRelative("http://a", "a/b", "a/b");
+		testRelative("http://base.com/a/", "http://child.com/a/b", "http://child.com/a/b");
+	}
 }
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 c70b6f2..17c1835 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
@@ -7,7 +7,6 @@
 import static org.eclipse.jgit.internal.storage.pack.PackExt.PACK;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertTrue;
@@ -180,41 +179,133 @@
 		RevCommit commit1 = commit().message("1").parent(commit0).create();
 		git.update("master", commit0);
 
-		gcNoTtl();
 		gcWithTtl();
-
-		// The repository has an UNREACHABLE_GARBAGE pack that could have
-		// expired, but since we never purge the most recent UNREACHABLE_GARBAGE
-		// pack, it must have survived the GC.
-		boolean commit1Found = false;
+		// The repository should have a GC pack with commit0 and an
+		// UNREACHABLE_GARBAGE pack with commit1.
+		assertEquals(2, odb.getPacks().length);
+		boolean gcPackFound = false;
+		boolean garbagePackFound = false;
 		for (DfsPackFile pack : odb.getPacks()) {
 			DfsPackDescription d = pack.getPackDescription();
 			if (d.getPackSource() == GC) {
+				gcPackFound = true;
 				assertTrue("has commit0", isObjectInPack(commit0, pack));
 				assertFalse("no commit1", isObjectInPack(commit1, pack));
 			} else if (d.getPackSource() == UNREACHABLE_GARBAGE) {
-				commit1Found |= isObjectInPack(commit1, pack);
+				garbagePackFound = true;
+				assertFalse("no commit0", isObjectInPack(commit0, pack));
+				assertTrue("has commit1", isObjectInPack(commit1, pack));
 			} else {
 				fail("unexpected " + d.getPackSource());
 			}
 		}
-		assertTrue("garbage commit1 still readable", commit1Found);
-
-		// Find oldest UNREACHABLE_GARBAGE; it will be pruned by next GC.
-		DfsPackDescription oldestGarbagePack = null;
-		for (DfsPackFile pack : odb.getPacks()) {
-			DfsPackDescription d = pack.getPackDescription();
-			if (d.getPackSource() == UNREACHABLE_GARBAGE) {
-				oldestGarbagePack = oldestPack(oldestGarbagePack, d);
-			}
-		}
-		assertNotNull("has UNREACHABLE_GARBAGE", oldestGarbagePack);
+		assertTrue("gc pack found", gcPackFound);
+		assertTrue("garbage pack found", garbagePackFound);
 
 		gcWithTtl();
-		assertTrue("has packs", odb.getPacks().length > 0);
+		// The gc operation should have removed UNREACHABLE_GARBAGE pack along with commit1.
+		DfsPackFile[] packs = odb.getPacks();
+		assertEquals(1, packs.length);
+
+		assertEquals(GC, packs[0].getPackDescription().getPackSource());
+		assertTrue("has commit0", isObjectInPack(commit0, packs[0]));
+		assertFalse("no commit1", isObjectInPack(commit1, packs[0]));
+	}
+
+	@Test
+	public void testCollectionWithGarbageAndRereferencingGarbage()
+			throws Exception {
+		RevCommit commit0 = commit().message("0").create();
+		RevCommit commit1 = commit().message("1").parent(commit0).create();
+		git.update("master", commit0);
+
+		gcWithTtl();
+		// The repository should have a GC pack with commit0 and an
+		// UNREACHABLE_GARBAGE pack with commit1.
+		assertEquals(2, odb.getPacks().length);
+		boolean gcPackFound = false;
+		boolean garbagePackFound = false;
 		for (DfsPackFile pack : odb.getPacks()) {
-			assertNotEquals(oldestGarbagePack, pack.getPackDescription());
+			DfsPackDescription d = pack.getPackDescription();
+			if (d.getPackSource() == GC) {
+				gcPackFound = true;
+				assertTrue("has commit0", isObjectInPack(commit0, pack));
+				assertFalse("no commit1", isObjectInPack(commit1, pack));
+			} else if (d.getPackSource() == UNREACHABLE_GARBAGE) {
+				garbagePackFound = true;
+				assertFalse("no commit0", isObjectInPack(commit0, pack));
+				assertTrue("has commit1", isObjectInPack(commit1, pack));
+			} else {
+				fail("unexpected " + d.getPackSource());
+			}
 		}
+		assertTrue("gc pack found", gcPackFound);
+		assertTrue("garbage pack found", garbagePackFound);
+
+		git.update("master", commit1);
+
+		gcWithTtl();
+		// The gc operation should have removed the UNREACHABLE_GARBAGE pack and
+		// moved commit1 into GC pack.
+		DfsPackFile[] packs = odb.getPacks();
+		assertEquals(1, packs.length);
+
+		assertEquals(GC, packs[0].getPackDescription().getPackSource());
+		assertTrue("has commit0", isObjectInPack(commit0, packs[0]));
+		assertTrue("has commit1", isObjectInPack(commit1, packs[0]));
+	}
+
+	@Test
+	public void testCollectionWithPureGarbageAndGarbagePacksPurged()
+			throws Exception {
+		RevCommit commit0 = commit().message("0").create();
+		RevCommit commit1 = commit().message("1").parent(commit0).create();
+
+		gcWithTtl();
+		// The repository should have a single UNREACHABLE_GARBAGE pack with commit0
+		// and commit1.
+		DfsPackFile[] packs = odb.getPacks();
+		assertEquals(1, packs.length);
+
+		assertEquals(UNREACHABLE_GARBAGE, packs[0].getPackDescription().getPackSource());
+		assertTrue("has commit0", isObjectInPack(commit0, packs[0]));
+		assertTrue("has commit1", isObjectInPack(commit1, packs[0]));
+
+		gcWithTtl();
+		// The gc operation should have removed UNREACHABLE_GARBAGE pack along
+		// with commit0 and commit1.
+		assertEquals(0, odb.getPacks().length);
+	}
+
+	@Test
+	public void testCollectionWithPureGarbageAndRereferencingGarbage()
+			throws Exception {
+		RevCommit commit0 = commit().message("0").create();
+		RevCommit commit1 = commit().message("1").parent(commit0).create();
+
+		gcWithTtl();
+		// The repository should have a single UNREACHABLE_GARBAGE pack with commit0
+		// and commit1.
+		DfsPackFile[] packs = odb.getPacks();
+		assertEquals(1, packs.length);
+
+		DfsPackDescription pack = packs[0].getPackDescription();
+		assertEquals(UNREACHABLE_GARBAGE, pack.getPackSource());
+		assertTrue("has commit0", isObjectInPack(commit0, packs[0]));
+		assertTrue("has commit1", isObjectInPack(commit1, packs[0]));
+
+		git.update("master", commit0);
+
+		gcWithTtl();
+		// The gc operation should have moved commit0 into the GC pack and
+		// removed UNREACHABLE_GARBAGE along with commit1.
+		packs = odb.getPacks();
+		assertEquals(1, packs.length);
+
+		pack = packs[0].getPackDescription();
+		assertEquals(GC, pack.getPackSource());
+		assertTrue("has commit0", isObjectInPack(commit0, packs[0]));
+		assertFalse("no commit1", isObjectInPack(commit1, packs[0]));
 	}
 
 	@Test
@@ -583,19 +674,11 @@
 
 	private boolean isObjectInPack(AnyObjectId id, DfsPackFile pack)
 			throws IOException {
-		try (DfsReader reader = new DfsReader(odb)) {
+		try (DfsReader reader = odb.newReader()) {
 			return pack.hasObject(reader, id);
 		}
 	}
 
-	private static DfsPackDescription oldestPack(DfsPackDescription a,
-			DfsPackDescription b) {
-		if (a != null && a.getLastModified() < b.getLastModified()) {
-			return a;
-		}
-		return b;
-	}
-
 	private int countPacks(PackSource source) throws IOException {
 		int cnt = 0;
 		for (DfsPackFile pack : odb.getPacks()) {
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/AlternatesTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/AlternatesTest.java
new file mode 100644
index 0000000..b09db03
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/AlternatesTest.java
@@ -0,0 +1,149 @@
+/*
+ * 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.internal.storage.file;
+
+import static org.junit.Assert.assertTrue;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Path;
+
+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.JGitTestUtil;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.test.resources.SampleDataRepositoryTestCase;
+import org.junit.Test;
+
+public class AlternatesTest extends SampleDataRepositoryTestCase {
+
+	private FileRepository db2;
+
+	@Override
+	public void setUp() throws Exception {
+		super.setUp();
+		db2 = createWorkRepository();
+	}
+
+	private void setAlternate(FileRepository from, FileRepository to)
+			throws IOException {
+		File alt = new File(from.getObjectDatabase().getDirectory(),
+				"info/alternates");
+		alt.getParentFile().mkdirs();
+		File fromDir = from.getObjectDatabase().getDirectory();
+		File toDir = to.getObjectDatabase().getDirectory();
+		Path relative = fromDir.toPath().relativize(toDir.toPath());
+		write(alt, relative.toString() + "\n");
+	}
+
+	@Test
+	public void testAlternate() throws Exception {
+		setAlternate(db2, db);
+		RevCommit c = createCommit();
+		assertCommit(c);
+		assertAlternateObjects(db2);
+	}
+
+	@Test
+	public void testAlternateCyclic2() throws Exception {
+		setAlternate(db2, db);
+		setAlternate(db, db2);
+		RevCommit c = createCommit();
+		assertCommit(c);
+		assertAlternateObjects(db2);
+	}
+
+	@Test
+	public void testAlternateCyclic3() throws Exception {
+		FileRepository db3 = createBareRepository();
+		setAlternate(db2, db3);
+		setAlternate(db3, db);
+		setAlternate(db, db2);
+		RevCommit c = createCommit();
+		assertCommit(c);
+		assertAlternateObjects(db2);
+	}
+
+	private RevCommit createCommit() throws IOException, GitAPIException,
+			NoFilepatternException, NoHeadException, NoMessageException,
+			UnmergedPathsException, ConcurrentRefUpdateException,
+			WrongRepositoryStateException, AbortedByHookException {
+		JGitTestUtil.writeTrashFile(db, "test", "test");
+		Git git = Git.wrap(db2);
+		git.add().addFilepattern("test").call();
+		RevCommit c = git.commit().setMessage("adding test").call();
+		return c;
+	}
+
+	private void assertCommit(RevCommit c) {
+		ObjectDirectory od = db2.getObjectDatabase();
+		assertTrue("can't find expected commit" + c.name(),
+				od.has(c.toObjectId()));
+	}
+
+	private void assertAlternateObjects(FileRepository repo) {
+		// check some objects in alternate
+		final ObjectId alternateObjects[] = new ObjectId[] {
+				ObjectId.fromString("49322bb17d3acc9146f98c97d078513228bbf3c0"),
+				ObjectId.fromString("d0114ab8ac326bab30e3a657a0397578c5a1af88"),
+				ObjectId.fromString("f73b95671f326616d66b2afb3bdfcdbbce110b44"),
+				ObjectId.fromString("6020a3b8d5d636e549ccbd0c53e2764684bb3125"),
+				ObjectId.fromString("0a3d7772488b6b106fb62813c4d6d627918d9181"),
+				ObjectId.fromString("da0f8ed91a8f2f0f067b3bdf26265d5ca48cf82c"),
+				ObjectId.fromString(
+						"cd4bcfc27da62c6b840de700be1c60a7e69952a5") };
+		ObjectDirectory od = repo.getObjectDatabase();
+		for (ObjectId o : alternateObjects) {
+			assertTrue(String.format("can't find object %s in alternate",
+					o.getName()), od.has(o));
+		}
+	}
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackWriterTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackWriterTest.java
index c817dc3..9b97eb4 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackWriterTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackWriterTest.java
@@ -711,7 +711,7 @@
 			}
 			ObjectWalk ow = walk.toObjectWalkWithSameObjects();
 
-			pw.preparePack(NullProgressMonitor.INSTANCE, ow, want, have);
+			pw.preparePack(NullProgressMonitor.INSTANCE, ow, want, have, NONE);
 			String id = pw.computeName().getName();
 			File packdir = new File(repo.getObjectsDirectory(), "pack");
 			File packFile = new File(packdir, "pack-" + id + ".pack");
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 20b8c51..d9b58e2 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
@@ -69,6 +69,15 @@
 
 	@Test
 	public void testBitmapSpansNoMerges() throws Exception {
+		testBitmapSpansNoMerges(false);
+	}
+
+	@Test
+	public void testBitmapSpansNoMergesWithTags() throws Exception {
+		testBitmapSpansNoMerges(true);
+	}
+
+	private void testBitmapSpansNoMerges(boolean withTags) throws Exception {
 		/*
 		 * Commit counts -> expected bitmap counts for history without merges.
 		 * The top 100 contiguous commits should always have bitmaps, and the
@@ -89,7 +98,10 @@
 			assertTrue(nextCommitCount > currentCommits); // programming error
 			for (int i = currentCommits; i < nextCommitCount; i++) {
 				String str = "A" + i;
-				bb.commit().message(str).add(str, str).create();
+				RevCommit rc = bb.commit().message(str).add(str, str).create();
+				if (withTags) {
+					tr.lightweightTag(str, rc);
+				}
 			}
 			currentCommits = nextCommitCount;
 
@@ -233,7 +245,7 @@
 				m8, m9);
 		PackWriterBitmapPreparer preparer = newPeparer(m9, commits);
 		List<BitmapCommit> selection = new ArrayList<>(
-				preparer.selectCommits(commits.size()));
+				preparer.selectCommits(commits.size(), PackWriter.NONE));
 
 		// Verify that the output is ordered by the separate "chains"
 		String[] expected = { m0.name(), m1.name(), m2.name(), m4.name(),
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/DirCacheCheckoutTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/DirCacheCheckoutTest.java
index 75b574e..f8c2d45 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/DirCacheCheckoutTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/DirCacheCheckoutTest.java
@@ -1672,6 +1672,20 @@
 		}
 	}
 
+	@Test
+	public void testLongFilename() throws Exception {
+		char[] bytes = new char[253];
+		Arrays.fill(bytes, 'f');
+		String longFileName = new String(bytes);
+		// 1
+		doit(mkmap(longFileName, "a"), mkmap(longFileName, "b"),
+				mkmap(longFileName, "a"));
+		writeTrashFile(longFileName, "a");
+		checkout();
+		assertNoConflicts();
+		assertUpdated(longFileName);
+	}
+
 	public void assertWorkDir(Map<String, String> i)
 			throws CorruptObjectException,
 			IOException {
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 d4a3d62..5af62b6 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
@@ -132,6 +132,45 @@
 	}
 
 	/**
+	 * Merge two modifications with a shared delete at the end. The underlying
+	 * diff algorithm has to provide consistent edit results to get the expected
+	 * merge result.
+	 *
+	 * @throws IOException
+	 */
+	@Test
+	public void testTwoModificationsWithSharedDelete() throws IOException {
+		assertEquals(t("Cb}n}"),
+				merge("ab}n}n}", "ab}n}", "Cb}n}"));
+	}
+
+	/**
+	 * Merge modifications with a shared insert in the middle. The
+	 * underlying diff algorithm has to provide consistent edit
+	 * results to get the expected merge result.
+	 *
+	 * @throws IOException
+	 */
+	@Test
+	public void testModificationsWithMiddleInsert() throws IOException {
+		assertEquals(t("aBcd123123uvwxPq"),
+				merge("abcd123uvwxpq", "aBcd123123uvwxPq", "abcd123123uvwxpq"));
+	}
+
+	/**
+	 * Merge modifications with a shared delete in the middle. The
+	 * underlying diff algorithm has to provide consistent edit
+	 * results to get the expected merge result.
+	 *
+	 * @throws IOException
+	 */
+	@Test
+	public void testModificationsWithMiddleDelete() throws IOException {
+		assertEquals(t("Abz}z123Q"),
+				merge("abz}z}z123q", "Abz}z123Q", "abz}z123q"));
+	}
+
+	/**
 	 * Test a conflicting region at the very start of the text.
 	 *
 	 * @throws IOException
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/ResolveMergerTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/ResolveMergerTest.java
index a08dbbc..d8b8750 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/ResolveMergerTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/ResolveMergerTest.java
@@ -42,12 +42,17 @@
  */
 package org.eclipse.jgit.merge;
 
+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.assertFalse;
 import static org.junit.Assert.assertTrue;
 
+import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.IOException;
+import java.util.Arrays;
 
 import org.eclipse.jgit.api.Git;
 import org.eclipse.jgit.api.MergeResult;
@@ -59,8 +64,14 @@
 import org.eclipse.jgit.errors.NoMergeBaseException;
 import org.eclipse.jgit.errors.NoMergeBaseException.MergeBaseFailureReason;
 import org.eclipse.jgit.junit.RepositoryTestCase;
+import org.eclipse.jgit.junit.TestRepository;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectInserter;
 import org.eclipse.jgit.merge.ResolveMerger.MergeFailureReason;
 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.FileTreeIterator;
 import org.eclipse.jgit.util.FS;
 import org.eclipse.jgit.util.FileUtils;
@@ -408,7 +419,7 @@
 
 	/**
 	 * Merging two equal subtrees with an incore merger should lead to a merged
-	 * state (The 'Gerrit' use case).
+	 * state.
 	 *
 	 * @param strategy
 	 * @throws Exception
@@ -442,6 +453,43 @@
 	}
 
 	/**
+	 * Merging two equal subtrees with an incore merger should lead to a merged
+	 * state, without using a Repository (the 'Gerrit' use case).
+	 *
+	 * @param strategy
+	 * @throws Exception
+	 */
+	@Theory
+	public void checkMergeEqualTreesInCore_noRepo(MergeStrategy strategy)
+			throws Exception {
+		Git git = Git.wrap(db);
+
+		writeTrashFile("d/1", "orig");
+		git.add().addFilepattern("d/1").call();
+		RevCommit first = git.commit().setMessage("added d/1").call();
+
+		writeTrashFile("d/1", "modified");
+		RevCommit masterCommit = git.commit().setAll(true)
+				.setMessage("modified d/1 on master").call();
+
+		git.checkout().setCreateBranch(true).setStartPoint(first)
+				.setName("side").call();
+		writeTrashFile("d/1", "modified");
+		RevCommit sideCommit = git.commit().setAll(true)
+				.setMessage("modified d/1 on side").call();
+
+		git.rm().addFilepattern("d/1").call();
+		git.rm().addFilepattern("d").call();
+
+		try (ObjectInserter ins = db.newObjectInserter()) {
+			ThreeWayMerger resolveMerger =
+					(ThreeWayMerger) strategy.newMerger(ins, db.getConfig());
+			boolean noProblems = resolveMerger.merge(masterCommit, sideCommit);
+			assertTrue(noProblems);
+		}
+	}
+
+	/**
 	 * Merging two equal subtrees when the index and HEAD does not contain any
 	 * file in that subtree should lead to a merged state.
 	 *
@@ -586,6 +634,136 @@
 		}
 	}
 
+	@Theory
+	public void checkContentMergeNoConflict(MergeStrategy strategy)
+			throws Exception {
+		Git git = Git.wrap(db);
+
+		writeTrashFile("file", "1\n2\n3");
+		git.add().addFilepattern("file").call();
+		RevCommit first = git.commit().setMessage("added file").call();
+
+		writeTrashFile("file", "1master\n2\n3");
+		git.commit().setAll(true).setMessage("modified file on master").call();
+
+		git.checkout().setCreateBranch(true).setStartPoint(first)
+				.setName("side").call();
+		writeTrashFile("file", "1\n2\n3side");
+		RevCommit sideCommit = git.commit().setAll(true)
+				.setMessage("modified file on side").call();
+
+		git.checkout().setName("master").call();
+		MergeResult result =
+				git.merge().setStrategy(strategy).include(sideCommit).call();
+		assertEquals(MergeStatus.MERGED, result.getMergeStatus());
+		String expected = "1master\n2\n3side";
+		assertEquals(expected, read("file"));
+	}
+
+	@Theory
+	public void checkContentMergeNoConflict_noRepo(MergeStrategy strategy)
+			throws Exception {
+		Git git = Git.wrap(db);
+
+		writeTrashFile("file", "1\n2\n3");
+		git.add().addFilepattern("file").call();
+		RevCommit first = git.commit().setMessage("added file").call();
+
+		writeTrashFile("file", "1master\n2\n3");
+		RevCommit masterCommit = git.commit().setAll(true)
+				.setMessage("modified file on master").call();
+
+		git.checkout().setCreateBranch(true).setStartPoint(first)
+				.setName("side").call();
+		writeTrashFile("file", "1\n2\n3side");
+		RevCommit sideCommit = git.commit().setAll(true)
+				.setMessage("modified file on side").call();
+
+		try (ObjectInserter ins = db.newObjectInserter()) {
+			ResolveMerger merger =
+					(ResolveMerger) strategy.newMerger(ins, db.getConfig());
+			boolean noProblems = merger.merge(masterCommit, sideCommit);
+			assertTrue(noProblems);
+			assertEquals("1master\n2\n3side",
+					readBlob(merger.getResultTreeId(), "file"));
+		}
+	}
+
+	@Theory
+	public void checkContentMergeConflict(MergeStrategy strategy)
+			throws Exception {
+		Git git = Git.wrap(db);
+
+		writeTrashFile("file", "1\n2\n3");
+		git.add().addFilepattern("file").call();
+		RevCommit first = git.commit().setMessage("added file").call();
+
+		writeTrashFile("file", "1master\n2\n3");
+		git.commit().setAll(true).setMessage("modified file on master").call();
+
+		git.checkout().setCreateBranch(true).setStartPoint(first)
+				.setName("side").call();
+		writeTrashFile("file", "1side\n2\n3");
+		RevCommit sideCommit = git.commit().setAll(true)
+				.setMessage("modified file on side").call();
+
+		git.checkout().setName("master").call();
+		MergeResult result =
+				git.merge().setStrategy(strategy).include(sideCommit).call();
+		assertEquals(MergeStatus.CONFLICTING, result.getMergeStatus());
+		String expected = "<<<<<<< HEAD\n"
+				+ "1master\n"
+				+ "=======\n"
+				+ "1side\n"
+				+ ">>>>>>> " + sideCommit.name() + "\n"
+				+ "2\n"
+				+ "3";
+		assertEquals(expected, read("file"));
+	}
+
+	@Theory
+	public void checkContentMergeConflict_noTree(MergeStrategy strategy)
+			throws Exception {
+		Git git = Git.wrap(db);
+
+		writeTrashFile("file", "1\n2\n3");
+		git.add().addFilepattern("file").call();
+		RevCommit first = git.commit().setMessage("added file").call();
+
+		writeTrashFile("file", "1master\n2\n3");
+		RevCommit masterCommit = git.commit().setAll(true)
+				.setMessage("modified file on master").call();
+
+		git.checkout().setCreateBranch(true).setStartPoint(first)
+				.setName("side").call();
+		writeTrashFile("file", "1side\n2\n3");
+		RevCommit sideCommit = git.commit().setAll(true)
+				.setMessage("modified file on side").call();
+
+		try (ObjectInserter ins = db.newObjectInserter()) {
+			ResolveMerger merger =
+					(ResolveMerger) strategy.newMerger(ins, db.getConfig());
+			boolean noProblems = merger.merge(masterCommit, sideCommit);
+			assertFalse(noProblems);
+			assertEquals(Arrays.asList("file"), merger.getUnmergedPaths());
+
+			MergeFormatter fmt = new MergeFormatter();
+			merger.getMergeResults().get("file");
+			try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
+				fmt.formatMerge(out, merger.getMergeResults().get("file"),
+						"BASE", "OURS", "THEIRS", UTF_8.name());
+				String expected = "<<<<<<< OURS\n"
+						+ "1master\n"
+						+ "=======\n"
+						+ "1side\n"
+						+ ">>>>>>> THEIRS\n"
+						+ "2\n"
+						+ "3";
+				assertEquals(expected, new String(out.toByteArray(), UTF_8));
+			}
+		}
+	}
+
 	/**
 	 * Merging after criss-cross merges. In this case we merge together two
 	 * commits which have two equally good common ancestors
@@ -817,4 +995,15 @@
 						curMod >= lastMod);
 		}
 	}
+
+	private String readBlob(ObjectId treeish, String path) throws Exception {
+		TestRepository<?> tr = new TestRepository<>(db);
+		RevWalk rw = tr.getRevWalk();
+		RevTree tree = rw.parseTree(treeish);
+		RevObject obj = tr.get(tree, path);
+		if (obj == null) {
+			return null;
+		}
+		return new String(rw.getObjectReader().open(obj, OBJ_BLOB).getBytes(), UTF_8);
+	}
 }
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/SimpleMergeTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/SimpleMergeTest.java
index db9d87d..951568e 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/SimpleMergeTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/SimpleMergeTest.java
@@ -72,6 +72,16 @@
 	}
 
 	@Test
+	public void testOurs_noRepo() throws IOException {
+		try (ObjectInserter ins = db.newObjectInserter()) {
+			Merger ourMerger = MergeStrategy.OURS.newMerger(ins, db.getConfig());
+			boolean merge = ourMerger.merge(new ObjectId[] { db.resolve("a"), db.resolve("c") });
+			assertTrue(merge);
+			assertEquals(db.resolve("a^{tree}"), ourMerger.getResultTreeId());
+		}
+	}
+
+	@Test
 	public void testTheirs() throws IOException {
 		Merger ourMerger = MergeStrategy.THEIRS.newMerger(db);
 		boolean merge = ourMerger.merge(new ObjectId[] { db.resolve("a"), db.resolve("c") });
@@ -80,6 +90,16 @@
 	}
 
 	@Test
+	public void testTheirs_noRepo() throws IOException {
+		try (ObjectInserter ins = db.newObjectInserter()) {
+			Merger ourMerger = MergeStrategy.THEIRS.newMerger(db);
+			boolean merge = ourMerger.merge(new ObjectId[] { db.resolve("a"), db.resolve("c") });
+			assertTrue(merge);
+			assertEquals(db.resolve("c^{tree}"), ourMerger.getResultTreeId());
+		}
+	}
+
+	@Test
 	public void testTrivialTwoWay() throws IOException {
 		Merger ourMerger = MergeStrategy.SIMPLE_TWO_WAY_IN_CORE.newMerger(db);
 		boolean merge = ourMerger.merge(new ObjectId[] { db.resolve("a"), db.resolve("c") });
@@ -104,6 +124,16 @@
 	}
 
 	@Test
+	public void testTrivialTwoWay_noRepo() throws IOException {
+		try (ObjectInserter ins = db.newObjectInserter()) {
+			Merger ourMerger = MergeStrategy.SIMPLE_TWO_WAY_IN_CORE.newMerger(ins, db.getConfig());
+			boolean merge = ourMerger.merge(new ObjectId[] { db.resolve("a^0^0^0"), db.resolve("a^0^0^1") });
+			assertTrue(merge);
+			assertEquals(db.resolve("a^0^0^{tree}"), ourMerger.getResultTreeId());
+		}
+	}
+
+	@Test
 	public void testTrivialTwoWay_conflict() throws IOException {
 		Merger ourMerger = MergeStrategy.SIMPLE_TWO_WAY_IN_CORE.newMerger(db);
 		boolean merge = ourMerger.merge(new ObjectId[] { db.resolve("f"), db.resolve("g") });
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 a83a993..658b971 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,10 @@
 
 package org.eclipse.jgit.transport;
 
+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;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
@@ -59,10 +62,16 @@
 import java.util.Set;
 
 import org.eclipse.jgit.errors.MissingBundlePrerequisiteException;
+import org.eclipse.jgit.errors.MissingObjectException;
 import org.eclipse.jgit.errors.NotSupportedException;
 import org.eclipse.jgit.errors.TransportException;
+import org.eclipse.jgit.internal.storage.dfs.DfsRepositoryDescription;
+import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
+import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.NullProgressMonitor;
 import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectInserter;
+import org.eclipse.jgit.lib.ObjectReader;
 import org.eclipse.jgit.lib.Ref;
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.revwalk.RevCommit;
@@ -161,6 +170,39 @@
 		assertTrue(caught);
 	}
 
+	@Test
+	public void testCustomObjectReader() throws Exception {
+		String refName = "refs/heads/blob";
+		String data = "unflushed data";
+		ObjectId id;
+		ByteArrayOutputStream out = new ByteArrayOutputStream();
+		try (Repository repo = new InMemoryRepository(
+					new DfsRepositoryDescription("repo"));
+				ObjectInserter ins = repo.newObjectInserter();
+				ObjectReader or = ins.newReader()) {
+			id = ins.insert(OBJ_BLOB, Constants.encode(data));
+			BundleWriter bw = new BundleWriter(or);
+			bw.include(refName, id);
+			bw.writeBundle(NullProgressMonitor.INSTANCE, out);
+			assertNull(repo.exactRef(refName));
+			try {
+				repo.open(id, OBJ_BLOB);
+				fail("We should not be able to open the unflushed blob");
+			} catch (MissingObjectException e) {
+				// Expected.
+			}
+		}
+
+		try (Repository repo = new InMemoryRepository(
+					new DfsRepositoryDescription("copy"))) {
+			fetchFromBundle(repo, out.toByteArray());
+			Ref ref = repo.exactRef(refName);
+			assertNotNull(ref);
+			assertEquals(id, ref.getObjectId());
+			assertEquals(data, new String(repo.open(id, OBJ_BLOB).getBytes(), UTF_8));
+		}
+	}
+
 	private static FetchResult fetchFromBundle(final Repository newRepo,
 			final byte[] bundle) throws URISyntaxException,
 			NotSupportedException, TransportException {
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 4061b56..2c8273d 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
@@ -172,9 +172,18 @@
 		FS fs = FS.DETECTED.newInstance();
 		assumeTrue(fs instanceof FS_POSIX);
 
-		String r = FS.readPipe(fs.userHome(),
-				new String[] { "bash", "--login", "-c", "foobar" },
+		FS.readPipe(fs.userHome(),
+				new String[] { "/bin/sh", "-c", "exit 1" },
 				Charset.defaultCharset().name());
-		System.out.println(r);
+	}
+
+	@Test(expected = CommandFailedException.class)
+	public void testReadPipeCommandStartFailure()
+			throws CommandFailedException {
+		FS fs = FS.DETECTED.newInstance();
+
+		FS.readPipe(fs.userHome(),
+				  new String[] { "this-command-does-not-exist" },
+				  Charset.defaultCharset().name());
 	}
 }
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FileUtilsTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FileUtilsTest.java
index 109d0e6..c0f4965 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FileUtilsTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FileUtilsTest.java
@@ -465,12 +465,12 @@
 
 	@Test
 	public void testRelativize_doc() {
-		// This is the javadoc example
+		// This is the example from the javadoc
 		String base = toOSPathString("c:\\Users\\jdoe\\eclipse\\git\\project");
 		String other = toOSPathString("c:\\Users\\jdoe\\eclipse\\git\\another_project\\pom.xml");
 		String expected = toOSPathString("..\\another_project\\pom.xml");
 
-		String actual = FileUtils.relativize(base, other);
+		String actual = FileUtils.relativizeNativePath(base, other);
 		assertEquals(expected, actual);
 	}
 
@@ -483,13 +483,13 @@
 		String expectedCaseSensitive = toOSPathString("..\\..\\Git\\test\\d\\f.txt");
 
 		if (systemReader.isWindows()) {
-			String actual = FileUtils.relativize(base, other);
+			String actual = FileUtils.relativizeNativePath(base, other);
 			assertEquals(expectedCaseInsensitive, actual);
 		} else if (systemReader.isMacOS()) {
-			String actual = FileUtils.relativize(base, other);
+			String actual = FileUtils.relativizeNativePath(base, other);
 			assertEquals(expectedCaseInsensitive, actual);
 		} else {
-			String actual = FileUtils.relativize(base, other);
+			String actual = FileUtils.relativizeNativePath(base, other);
 			assertEquals(expectedCaseSensitive, actual);
 		}
 	}
@@ -501,7 +501,7 @@
 		// 'file.java' is treated as a folder
 		String expected = toOSPathString("../../project");
 
-		String actual = FileUtils.relativize(base, other);
+		String actual = FileUtils.relativizeNativePath(base, other);
 		assertEquals(expected, actual);
 	}
 
@@ -511,7 +511,7 @@
 		String other = toOSPathString("file:/home/eclipse/runtime-New_configuration/project_1");
 		String expected = "";
 
-		String actual = FileUtils.relativize(base, other);
+		String actual = FileUtils.relativizeNativePath(base, other);
 		assertEquals(expected, actual);
 	}
 
@@ -521,7 +521,7 @@
 		String other = toOSPathString("/home/eclipse 3.4/runtime New_configuration/project_1/file");
 		String expected = "file";
 
-		String actual = FileUtils.relativize(base, other);
+		String actual = FileUtils.relativizeNativePath(base, other);
 		assertEquals(expected, actual);
 	}
 
diff --git a/org.eclipse.jgit.ui/BUILD b/org.eclipse.jgit.ui/BUILD
index 85ae5c0..eec4a38 100644
--- a/org.eclipse.jgit.ui/BUILD
+++ b/org.eclipse.jgit.ui/BUILD
@@ -2,7 +2,7 @@
 
 java_library(
     name = "ui",
-    srcs = glob(["src/**"]),
+    srcs = glob(["src/**/*.java"]),
     resource_strip_prefix = "org.eclipse.jgit.ui/resources",
     resources = glob(["resources/**"]),
     deps = ["//org.eclipse.jgit:jgit"],
diff --git a/org.eclipse.jgit.ui/META-INF/MANIFEST.MF b/org.eclipse.jgit.ui/META-INF/MANIFEST.MF
index e4834a5..4312cc3 100644
--- a/org.eclipse.jgit.ui/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.ui/META-INF/MANIFEST.MF
@@ -3,14 +3,14 @@
 Bundle-ManifestVersion: 2
 Bundle-Name: %plugin_name
 Bundle-SymbolicName: org.eclipse.jgit.ui
-Bundle-Version: 4.7.2.qualifier
+Bundle-Version: 4.8.1.qualifier
 Bundle-Vendor: %provider_name
 Bundle-RequiredExecutionEnvironment: JavaSE-1.8
-Export-Package: org.eclipse.jgit.awtui;version="4.7.2"
-Import-Package: org.eclipse.jgit.errors;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.lib;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.nls;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.revplot;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.revwalk;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.transport;version="[4.7.2,4.8.0)",
- org.eclipse.jgit.util;version="[4.7.2,4.8.0)"
+Export-Package: org.eclipse.jgit.awtui;version="4.8.1"
+Import-Package: org.eclipse.jgit.errors;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.lib;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.nls;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.revplot;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.revwalk;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.transport;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.util;version="[4.8.1,4.9.0)"
diff --git a/org.eclipse.jgit.ui/pom.xml b/org.eclipse.jgit.ui/pom.xml
index 655e13b..06e45ef 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>4.7.2-SNAPSHOT</version>
+    <version>4.8.1-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 822f121..671988c 100644
--- a/org.eclipse.jgit/.settings/.api_filters
+++ b/org.eclipse.jgit/.settings/.api_filters
@@ -1,61 +1,26 @@
 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
 <component id="org.eclipse.jgit" version="2">
-    <resource path="META-INF/MANIFEST.MF">
-        <filter id="924844039">
+    <resource path="src/org/eclipse/jgit/lib/ConfigConstants.java" type="org.eclipse.jgit.lib.ConfigConstants">
+        <filter id="1141899266">
             <message_arguments>
-                <message_argument value="4.7.1"/>
-                <message_argument value="4.7.0"/>
-            </message_arguments>
-        </filter>
-    </resource>
-    <resource path="src/org/eclipse/jgit/api/ArchiveCommand.java" type="org.eclipse.jgit.api.ArchiveCommand$Format">
-        <filter comment="OSGi semver allows to break implementors in minor releases" id="403804204">
-            <message_arguments>
-                <message_argument value="org.eclipse.jgit.api.ArchiveCommand.Format"/>
-                <message_argument value="putEntry(T, ObjectId, String, FileMode, ObjectLoader)"/>
-            </message_arguments>
-        </filter>
-    </resource>
-    <resource path="src/org/eclipse/jgit/errors/NoPackSignatureException.java" type="org.eclipse.jgit.errors.NoPackSignatureException">
-        <filter comment="non-breaking addition of exception classes needed to cleanly fix error handling in PackFile" id="1108344834">
-            <message_arguments>
-                <message_argument value="4.5"/>
                 <message_argument value="4.7"/>
-                <message_argument value="org.eclipse.jgit.errors.NoPackSignatureException"/>
+                <message_argument value="4.8"/>
+                <message_argument value="CONFIG_KEY_AUTODETACH"/>
             </message_arguments>
         </filter>
-    </resource>
-    <resource path="src/org/eclipse/jgit/errors/UnsupportedPackIndexVersionException.java" type="org.eclipse.jgit.errors.UnsupportedPackIndexVersionException">
-        <filter comment="non-breaking addition of exception classes needed to cleanly fix error handling in PackFile" id="1108344834">
+        <filter id="1141899266">
             <message_arguments>
-                <message_argument value="4.5"/>
                 <message_argument value="4.7"/>
-                <message_argument value="org.eclipse.jgit.errors.UnsupportedPackIndexVersionException"/>
+                <message_argument value="4.8"/>
+                <message_argument value="CONFIG_KEY_LOGEXPIRY"/>
             </message_arguments>
         </filter>
     </resource>
-    <resource path="src/org/eclipse/jgit/errors/UnsupportedPackVersionException.java" type="org.eclipse.jgit.errors.UnsupportedPackVersionException">
-        <filter comment="non-breaking addition of exception classes needed to cleanly fix error handling in PackFile" id="1108344834">
+    <resource path="src/org/eclipse/jgit/merge/MergeStrategy.java" type="org.eclipse.jgit.merge.MergeStrategy">
+        <filter comment="OSGi semantic versioning allows breaking implementors of an API in a minor version" id="336695337">
             <message_arguments>
-                <message_argument value="4.5"/>
-                <message_argument value="4.7"/>
-                <message_argument value="org.eclipse.jgit.errors.UnsupportedPackVersionException"/>
-            </message_arguments>
-        </filter>
-    </resource>
-    <resource path="src/org/eclipse/jgit/lib/ObjectInserter.java" type="org.eclipse.jgit.lib.ObjectInserter">
-        <filter comment="changed return type of digest() breaking implementors which is ok in minor releases according to OSGi semantic versioning" id="338792546">
-            <message_arguments>
-                <message_argument value="org.eclipse.jgit.lib.ObjectInserter"/>
-                <message_argument value="digest()"/>
-            </message_arguments>
-        </filter>
-    </resource>
-    <resource path="src/org/eclipse/jgit/transport/http/HttpConnection.java" type="org.eclipse.jgit.transport.http.HttpConnection">
-        <filter comment="OSGi semantic versioning rules allow to break implementors in minor releases" id="403767336">
-            <message_arguments>
-                <message_argument value="org.eclipse.jgit.transport.http.HttpConnection"/>
-                <message_argument value="HTTP_MOVED_PERM"/>
+                <message_argument value="org.eclipse.jgit.merge.MergeStrategy"/>
+                <message_argument value="newMerger(ObjectInserter, Config)"/>
             </message_arguments>
         </filter>
     </resource>
diff --git a/org.eclipse.jgit/BUILD b/org.eclipse.jgit/BUILD
index 75f4fe6..a8a53f2 100644
--- a/org.eclipse.jgit/BUILD
+++ b/org.eclipse.jgit/BUILD
@@ -5,7 +5,7 @@
 ]
 
 SRCS = glob(
-    ["src/**"],
+    ["src/**/*.java"],
     exclude = INSECURE_CIPHER_FACTORY,
 )
 
diff --git a/org.eclipse.jgit/META-INF/MANIFEST.MF b/org.eclipse.jgit/META-INF/MANIFEST.MF
index aa88e21..9b6a9f1 100644
--- a/org.eclipse.jgit/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit/META-INF/MANIFEST.MF
@@ -2,12 +2,12 @@
 Bundle-ManifestVersion: 2
 Bundle-Name: %plugin_name
 Bundle-SymbolicName: org.eclipse.jgit
-Bundle-Version: 4.7.2.qualifier
+Bundle-Version: 4.8.1.qualifier
 Bundle-Localization: plugin
 Bundle-Vendor: %provider_name
 Bundle-ActivationPolicy: lazy
-Export-Package: org.eclipse.jgit.annotations;version="4.7.2",
- org.eclipse.jgit.api;version="4.7.2";
+Export-Package: org.eclipse.jgit.annotations;version="4.8.1",
+ org.eclipse.jgit.api;version="4.8.1";
   uses:="org.eclipse.jgit.revwalk,
    org.eclipse.jgit.treewalk.filter,
    org.eclipse.jgit.diff,
@@ -21,51 +21,51 @@
    org.eclipse.jgit.submodule,
    org.eclipse.jgit.transport,
    org.eclipse.jgit.merge",
- org.eclipse.jgit.api.errors;version="4.7.2";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.errors",
- org.eclipse.jgit.attributes;version="4.7.2",
- org.eclipse.jgit.blame;version="4.7.2";
+ org.eclipse.jgit.api.errors;version="4.8.1";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.errors",
+ org.eclipse.jgit.attributes;version="4.8.1",
+ org.eclipse.jgit.blame;version="4.8.1";
   uses:="org.eclipse.jgit.lib,
    org.eclipse.jgit.revwalk,
    org.eclipse.jgit.treewalk.filter,
    org.eclipse.jgit.diff",
- org.eclipse.jgit.diff;version="4.7.2";
+ org.eclipse.jgit.diff;version="4.8.1";
   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="4.7.2";
+ org.eclipse.jgit.dircache;version="4.8.1";
   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="4.7.2";
+ org.eclipse.jgit.errors;version="4.8.1";
   uses:="org.eclipse.jgit.lib,
    org.eclipse.jgit.internal.storage.pack,
    org.eclipse.jgit.transport,
    org.eclipse.jgit.dircache",
- org.eclipse.jgit.events;version="4.7.2";uses:="org.eclipse.jgit.lib",
- org.eclipse.jgit.fnmatch;version="4.7.2",
- org.eclipse.jgit.gitrepo;version="4.7.2";
+ org.eclipse.jgit.events;version="4.8.1";uses:="org.eclipse.jgit.lib",
+ org.eclipse.jgit.fnmatch;version="4.8.1",
+ org.eclipse.jgit.gitrepo;version="4.8.1";
   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="4.7.2";x-internal:=true,
- org.eclipse.jgit.hooks;version="4.7.2";uses:="org.eclipse.jgit.lib",
- org.eclipse.jgit.ignore;version="4.7.2",
- org.eclipse.jgit.ignore.internal;version="4.7.2";x-friends:="org.eclipse.jgit.test",
- org.eclipse.jgit.internal;version="4.7.2";x-friends:="org.eclipse.jgit.test,org.eclipse.jgit.http.test",
- org.eclipse.jgit.internal.ketch;version="4.7.2";x-friends:="org.eclipse.jgit.junit,org.eclipse.jgit.test,org.eclipse.jgit.pgm",
- org.eclipse.jgit.internal.storage.dfs;version="4.7.2";
+ org.eclipse.jgit.gitrepo.internal;version="4.8.1";x-internal:=true,
+ org.eclipse.jgit.hooks;version="4.8.1";uses:="org.eclipse.jgit.lib",
+ org.eclipse.jgit.ignore;version="4.8.1",
+ org.eclipse.jgit.ignore.internal;version="4.8.1";x-friends:="org.eclipse.jgit.test",
+ org.eclipse.jgit.internal;version="4.8.1";x-friends:="org.eclipse.jgit.test,org.eclipse.jgit.http.test",
+ org.eclipse.jgit.internal.ketch;version="4.8.1";x-friends:="org.eclipse.jgit.junit,org.eclipse.jgit.test,org.eclipse.jgit.pgm",
+ org.eclipse.jgit.internal.storage.dfs;version="4.8.1";
   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="4.7.2";
+ org.eclipse.jgit.internal.storage.file;version="4.8.1";
   x-friends:="org.eclipse.jgit.test,
    org.eclipse.jgit.junit,
    org.eclipse.jgit.junit.http,
@@ -73,9 +73,9 @@
    org.eclipse.jgit.lfs,
    org.eclipse.jgit.pgm,
    org.eclipse.jgit.pgm.test",
- org.eclipse.jgit.internal.storage.pack;version="4.7.2";x-friends:="org.eclipse.jgit.junit,org.eclipse.jgit.test,org.eclipse.jgit.pgm",
- org.eclipse.jgit.internal.storage.reftree;version="4.7.2";x-friends:="org.eclipse.jgit.junit,org.eclipse.jgit.test,org.eclipse.jgit.pgm",
- org.eclipse.jgit.lib;version="4.7.2";
+ org.eclipse.jgit.internal.storage.pack;version="4.8.1";x-friends:="org.eclipse.jgit.junit,org.eclipse.jgit.test,org.eclipse.jgit.pgm",
+ org.eclipse.jgit.internal.storage.reftree;version="4.8.1";x-friends:="org.eclipse.jgit.junit,org.eclipse.jgit.test,org.eclipse.jgit.pgm",
+ org.eclipse.jgit.lib;version="4.8.1";
   uses:="org.eclipse.jgit.revwalk,
    org.eclipse.jgit.treewalk.filter,
    org.eclipse.jgit.util,
@@ -85,32 +85,33 @@
    org.eclipse.jgit.treewalk,
    org.eclipse.jgit.transport,
    org.eclipse.jgit.submodule",
- org.eclipse.jgit.merge;version="4.7.2";
+ org.eclipse.jgit.lib.internal;version="4.8.1";x-internal:=true,
+ org.eclipse.jgit.merge;version="4.8.1";
   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="4.7.2",
- org.eclipse.jgit.notes;version="4.7.2";
+ org.eclipse.jgit.nls;version="4.8.1",
+ org.eclipse.jgit.notes;version="4.8.1";
   uses:="org.eclipse.jgit.lib,
    org.eclipse.jgit.treewalk,
    org.eclipse.jgit.revwalk,
    org.eclipse.jgit.merge",
- org.eclipse.jgit.patch;version="4.7.2";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.diff",
- org.eclipse.jgit.revplot;version="4.7.2";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.revwalk",
- org.eclipse.jgit.revwalk;version="4.7.2";
+ org.eclipse.jgit.patch;version="4.8.1";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.diff",
+ org.eclipse.jgit.revplot;version="4.8.1";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.revwalk",
+ org.eclipse.jgit.revwalk;version="4.8.1";
   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="4.7.2";uses:="org.eclipse.jgit.revwalk,org.eclipse.jgit.lib,org.eclipse.jgit.util",
- org.eclipse.jgit.storage.file;version="4.7.2";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.util",
- org.eclipse.jgit.storage.pack;version="4.7.2";uses:="org.eclipse.jgit.lib",
- org.eclipse.jgit.submodule;version="4.7.2";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.treewalk.filter,org.eclipse.jgit.treewalk",
- org.eclipse.jgit.transport;version="4.7.2";
+ org.eclipse.jgit.revwalk.filter;version="4.8.1";uses:="org.eclipse.jgit.revwalk,org.eclipse.jgit.lib,org.eclipse.jgit.util",
+ org.eclipse.jgit.storage.file;version="4.8.1";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.util",
+ org.eclipse.jgit.storage.pack;version="4.8.1";uses:="org.eclipse.jgit.lib",
+ org.eclipse.jgit.submodule;version="4.8.1";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.treewalk.filter,org.eclipse.jgit.treewalk",
+ org.eclipse.jgit.transport;version="4.8.1";
   uses:="org.eclipse.jgit.transport.resolver,
    org.eclipse.jgit.revwalk,
    org.eclipse.jgit.internal.storage.pack,
@@ -122,24 +123,24 @@
    org.eclipse.jgit.transport.http,
    org.eclipse.jgit.errors,
    org.eclipse.jgit.storage.pack",
- org.eclipse.jgit.transport.http;version="4.7.2";uses:="javax.net.ssl",
- org.eclipse.jgit.transport.resolver;version="4.7.2";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.transport",
- org.eclipse.jgit.treewalk;version="4.7.2";
+ org.eclipse.jgit.transport.http;version="4.8.1";uses:="javax.net.ssl",
+ org.eclipse.jgit.transport.resolver;version="4.8.1";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.transport",
+ org.eclipse.jgit.treewalk;version="4.8.1";
   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="4.7.2";uses:="org.eclipse.jgit.treewalk",
- org.eclipse.jgit.util;version="4.7.2";
+ org.eclipse.jgit.treewalk.filter;version="4.8.1";uses:="org.eclipse.jgit.treewalk",
+ org.eclipse.jgit.util;version="4.8.1";
   uses:="org.eclipse.jgit.lib,
    org.eclipse.jgit.transport.http,
    org.eclipse.jgit.storage.file,
    org.ietf.jgss",
- org.eclipse.jgit.util.io;version="4.7.2",
- org.eclipse.jgit.util.sha1;version="4.7.2",
- org.eclipse.jgit.util.time;version="4.7.2"
+ org.eclipse.jgit.util.io;version="4.8.1",
+ org.eclipse.jgit.util.sha1;version="4.8.1",
+ org.eclipse.jgit.util.time;version="4.8.1"
 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 d849a0e..63e6bbc 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: 4.7.2.qualifier
-Eclipse-SourceBundle: org.eclipse.jgit;version="4.7.2.qualifier";roots="."
+Bundle-Version: 4.8.1.qualifier
+Eclipse-SourceBundle: org.eclipse.jgit;version="4.8.1.qualifier";roots="."
diff --git a/org.eclipse.jgit/pom.xml b/org.eclipse.jgit/pom.xml
index 3feb523..673f3ff 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>4.7.2-SNAPSHOT</version>
+    <version>4.8.1-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit</artifactId>
diff --git a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties
index 03065da..627007d 100644
--- a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties
+++ b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties
@@ -433,6 +433,7 @@
 noHMACsupport=No {0} support: {1}
 noMergeBase=No merge base could be determined. Reason={0}. {1}
 noMergeHeadSpecified=No merge head specified
+nonBareLinkFilesNotSupported=Link files are not supported with nonbare repos
 noSuchRef=no such ref
 notABoolean=Not a boolean: {0}
 notABundle=not a bundle
@@ -540,6 +541,7 @@
 renamesRejoiningModifies=Rejoining modified file pairs
 repositoryAlreadyExists=Repository already exists: {0}
 repositoryConfigFileInvalid=Repository config file {0} invalid {1}
+repositoryIsRequired=repository is required
 repositoryNotFound=repository not found: {0}
 repositoryState_applyMailbox=Apply mailbox
 repositoryState_bare=Bare
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 4b815b4..d450c64 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CloneCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CloneCommand.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2011, 2013 Chris Aniszczyk <caniszczyk@gmail.com>
+ * Copyright (C) 2011, 2017 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
@@ -58,6 +58,7 @@
 import org.eclipse.jgit.errors.IncorrectObjectTypeException;
 import org.eclipse.jgit.errors.MissingObjectException;
 import org.eclipse.jgit.internal.JGitText;
+import org.eclipse.jgit.lib.AnyObjectId;
 import org.eclipse.jgit.lib.BranchConfig.BranchRebaseMode;
 import org.eclipse.jgit.lib.ConfigConstants;
 import org.eclipse.jgit.lib.Constants;
@@ -75,6 +76,8 @@
 import org.eclipse.jgit.transport.RemoteConfig;
 import org.eclipse.jgit.transport.TagOpt;
 import org.eclipse.jgit.transport.URIish;
+import org.eclipse.jgit.util.FS;
+import org.eclipse.jgit.util.FileUtils;
 
 /**
  * Clone a repository into a new working directory
@@ -106,6 +109,46 @@
 
 	private Collection<String> branchesToClone;
 
+	private Callback callback;
+
+	private boolean directoryExistsInitially;
+
+	private boolean gitDirExistsInitially;
+
+	/**
+	 * Callback for status of clone operation.
+	 *
+	 * @since 4.8
+	 */
+	public interface Callback {
+		/**
+		 * Notify initialized submodules.
+		 *
+		 * @param submodules
+		 *            the submodules
+		 *
+		 */
+		void initializedSubmodules(Collection<String> submodules);
+
+		/**
+		 * Notify starting to clone a submodule.
+		 *
+		 * @param path
+		 *            the submodule path
+		 */
+		void cloningSubmodule(String path);
+
+		/**
+		 * Notify checkout of commit
+		 *
+		 * @param commit
+		 *            the id of the commit being checked out
+		 * @param path
+		 *            the submodule path
+		 */
+		void checkingOut(AnyObjectId commit, String path);
+	}
+
 	/**
 	 * Create clone command with no repository set
 	 */
@@ -130,26 +173,55 @@
 	@Override
 	public Git call() throws GitAPIException, InvalidRemoteException,
 			org.eclipse.jgit.api.errors.TransportException {
-		Repository repository = null;
+		URIish u = null;
 		try {
-			URIish u = new URIish(uri);
-			repository = init(u);
-			FetchResult result = fetch(repository, u);
-			if (!noCheckout)
-				checkout(repository, result);
-			return new Git(repository, true);
+			u = new URIish(uri);
+			verifyDirectories(u);
+		} catch (URISyntaxException e) {
+			throw new InvalidRemoteException(
+					MessageFormat.format(JGitText.get().invalidURL, uri));
+		}
+		Repository repository = null;
+		FetchResult fetchResult = null;
+		Thread cleanupHook = new Thread(() -> cleanup());
+		Runtime.getRuntime().addShutdownHook(cleanupHook);
+		try {
+			repository = init();
+			fetchResult = fetch(repository, u);
 		} catch (IOException ioe) {
 			if (repository != null) {
 				repository.close();
 			}
+			cleanup();
 			throw new JGitInternalException(ioe.getMessage(), ioe);
 		} catch (URISyntaxException e) {
 			if (repository != null) {
 				repository.close();
 			}
+			cleanup();
 			throw new InvalidRemoteException(MessageFormat.format(
 					JGitText.get().invalidRemote, remote));
+		} catch (GitAPIException | RuntimeException e) {
+			if (repository != null) {
+				repository.close();
+			}
+			cleanup();
+			throw e;
+		} finally {
+			Runtime.getRuntime().removeShutdownHook(cleanupHook);
 		}
+		if (!noCheckout) {
+			try {
+				checkout(repository, fetchResult);
+			} catch (IOException ioe) {
+				repository.close();
+				throw new JGitInternalException(ioe.getMessage(), ioe);
+			} catch (GitAPIException | RuntimeException e) {
+				repository.close();
+				throw e;
+			}
+		}
+		return new Git(repository, true);
 	}
 
 	private static boolean isNonEmptyDirectory(File dir) {
@@ -160,12 +232,12 @@
 		return false;
 	}
 
-	private Repository init(URIish u) throws GitAPIException {
-		InitCommand command = Git.init();
-		command.setBare(bare);
+	private void verifyDirectories(URIish u) {
 		if (directory == null && gitDir == null) {
 			directory = new File(u.getHumanishName(), Constants.DOT_GIT);
 		}
+		directoryExistsInitially = directory != null && directory.exists();
+		gitDirExistsInitially = gitDir != null && gitDir.exists();
 		validateDirs(directory, gitDir, bare);
 		if (isNonEmptyDirectory(directory)) {
 			throw new JGitInternalException(MessageFormat.format(
@@ -175,6 +247,11 @@
 			throw new JGitInternalException(MessageFormat.format(
 					JGitText.get().cloneNonEmptyDirectory, gitDir.getName()));
 		}
+	}
+
+	private Repository init() throws GitAPIException {
+		InitCommand command = Git.init();
+		command.setBare(bare);
 		if (directory != null) {
 			command.setDirectory(directory);
 		}
@@ -280,12 +357,18 @@
 	private void cloneSubmodules(Repository clonedRepo) throws IOException,
 			GitAPIException {
 		SubmoduleInitCommand init = new SubmoduleInitCommand(clonedRepo);
-		if (init.call().isEmpty())
+		Collection<String> submodules = init.call();
+		if (submodules.isEmpty()) {
 			return;
+		}
+		if (callback != null) {
+			callback.initializedSubmodules(submodules);
+		}
 
 		SubmoduleUpdateCommand update = new SubmoduleUpdateCommand(clonedRepo);
 		configure(update);
 		update.setProgressMonitor(monitor);
+		update.setCallback(callback);
 		if (!update.call().isEmpty()) {
 			SubmoduleWalk walk = SubmoduleWalk.forIndex(clonedRepo);
 			while (walk.next()) {
@@ -523,6 +606,19 @@
 		return this;
 	}
 
+	/**
+	 * Register a progress callback.
+	 *
+	 * @param callback
+	 *            the callback
+	 * @return {@code this}
+	 * @since 4.8
+	 */
+	public CloneCommand setCallback(Callback callback) {
+		this.callback = callback;
+		return this;
+	}
+
 	private static void validateDirs(File directory, File gitDir, boolean bare)
 			throws IllegalStateException {
 		if (directory != null) {
@@ -548,4 +644,38 @@
 			}
 		}
 	}
+
+	private void cleanup() {
+		try {
+			if (directory != null) {
+				if (!directoryExistsInitially) {
+					FileUtils.delete(directory, FileUtils.RECURSIVE
+							| FileUtils.SKIP_MISSING | FileUtils.IGNORE_ERRORS);
+				} else {
+					deleteChildren(directory);
+				}
+			}
+			if (gitDir != null) {
+				if (!gitDirExistsInitially) {
+					FileUtils.delete(gitDir, FileUtils.RECURSIVE
+							| FileUtils.SKIP_MISSING | FileUtils.IGNORE_ERRORS);
+				} else {
+					deleteChildren(directory);
+				}
+			}
+		} catch (IOException e) {
+			// Ignore; this is a best-effort cleanup in error cases, and
+			// IOException should not be raised anyway
+		}
+	}
+
+	private void deleteChildren(File file) throws IOException {
+		if (!FS.DETECTED.isDirectory(file)) {
+			return;
+		}
+		for (File child : file.listFiles()) {
+			FileUtils.delete(child, FileUtils.RECURSIVE | FileUtils.SKIP_MISSING
+					| FileUtils.IGNORE_ERRORS);
+		}
+	}
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/FetchCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/FetchCommand.java
index cc3302b..785c20c 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/FetchCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/FetchCommand.java
@@ -99,6 +99,24 @@
 
 	private FetchRecurseSubmodulesMode submoduleRecurseMode = null;
 
+	private Callback callback;
+
+	/**
+	 * Callback for status of fetch operation.
+	 *
+	 * @since 4.8
+	 *
+	 */
+	public interface Callback {
+		/**
+		 * Notify fetching a submodule.
+		 *
+		 * @param name
+		 *            the submodule name.
+		 */
+		void fetchingSubmodule(String name);
+	}
+
 	/**
 	 * @param repo
 	 */
@@ -173,6 +191,9 @@
 							.setThin(thin).setRefSpecs(refSpecs)
 							.setDryRun(dryRun)
 							.setRecurseSubmodules(recurseMode);
+					if (callback != null) {
+						callback.fetchingSubmodule(walk.getPath());
+					}
 					results.addSubmodule(walk.getPath(), f.call());
 				}
 			}
@@ -434,4 +455,17 @@
 		this.tagOption = tagOpt;
 		return this;
 	}
+
+	/**
+	 * Register a progress callback.
+	 *
+	 * @param callback
+	 *            the callback
+	 * @return {@code this}
+	 * @since 4.8
+	 */
+	public FetchCommand setCallback(Callback callback) {
+		this.callback = callback;
+		return this;
+	}
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/PullCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/PullCommand.java
index ae822da..9c5ae43 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/PullCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/PullCommand.java
@@ -203,62 +203,63 @@
 	@Override
 	public PullResult call() throws GitAPIException,
 			WrongRepositoryStateException, InvalidConfigurationException,
-			DetachedHeadException, InvalidRemoteException, CanceledException,
+			InvalidRemoteException, CanceledException,
 			RefNotFoundException, RefNotAdvertisedException, NoHeadException,
 			org.eclipse.jgit.api.errors.TransportException {
 		checkCallable();
 
 		monitor.beginTask(JGitText.get().pullTaskName, 2);
+		Config repoConfig = repo.getConfig();
 
-		String branchName;
+		String branchName = null;
 		try {
 			String fullBranch = repo.getFullBranch();
-			if (fullBranch == null)
-				throw new NoHeadException(
-						JGitText.get().pullOnRepoWithoutHEADCurrentlyNotSupported);
-			if (!fullBranch.startsWith(Constants.R_HEADS)) {
-				// we can not pull if HEAD is detached and branch is not
-				// specified explicitly
-				throw new DetachedHeadException();
+			if (fullBranch != null
+					&& fullBranch.startsWith(Constants.R_HEADS)) {
+				branchName = fullBranch.substring(Constants.R_HEADS.length());
 			}
-			branchName = fullBranch.substring(Constants.R_HEADS.length());
 		} catch (IOException e) {
 			throw new JGitInternalException(
 					JGitText.get().exceptionCaughtDuringExecutionOfPullCommand,
 					e);
 		}
+		if (remoteBranchName == null && branchName != null) {
+			// get the name of the branch in the remote repository
+			// stored in configuration key branch.<branch name>.merge
+			remoteBranchName = repoConfig.getString(
+					ConfigConstants.CONFIG_BRANCH_SECTION, branchName,
+					ConfigConstants.CONFIG_KEY_MERGE);
+		}
+		if (remoteBranchName == null) {
+			remoteBranchName = branchName;
+		}
+		if (remoteBranchName == null) {
+			throw new NoHeadException(
+					JGitText.get().cannotCheckoutFromUnbornBranch);
+		}
 
 		if (!repo.getRepositoryState().equals(RepositoryState.SAFE))
 			throw new WrongRepositoryStateException(MessageFormat.format(
 					JGitText.get().cannotPullOnARepoWithState, repo
 							.getRepositoryState().name()));
 
-		Config repoConfig = repo.getConfig();
-		if (remote == null) {
+		if (remote == null && branchName != null) {
 			// get the configured remote for the currently checked out branch
 			// stored in configuration key branch.<branch name>.remote
 			remote = repoConfig.getString(
 					ConfigConstants.CONFIG_BRANCH_SECTION, branchName,
 					ConfigConstants.CONFIG_KEY_REMOTE);
 		}
-		if (remote == null)
+		if (remote == null) {
 			// fall back to default remote
 			remote = Constants.DEFAULT_REMOTE_NAME;
-
-		if (remoteBranchName == null)
-			// get the name of the branch in the remote repository
-			// stored in configuration key branch.<branch name>.merge
-			remoteBranchName = repoConfig.getString(
-					ConfigConstants.CONFIG_BRANCH_SECTION, branchName,
-					ConfigConstants.CONFIG_KEY_MERGE);
+		}
 
 		// determines whether rebase should be used after fetching
-		if (pullRebaseMode == null) {
+		if (pullRebaseMode == null && branchName != null) {
 			pullRebaseMode = getRebaseMode(branchName, repoConfig);
 		}
 
-		if (remoteBranchName == null)
-			remoteBranchName = branchName;
 
 		final boolean isRemote = !remote.equals("."); //$NON-NLS-1$
 		String remoteUri;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleUpdateCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleUpdateCommand.java
index 29d5d49..4d3dff0 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleUpdateCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleUpdateCommand.java
@@ -89,6 +89,8 @@
 
 	private MergeStrategy strategy = MergeStrategy.RECURSIVE;
 
+	private CloneCommand.Callback callback;
+
 	/**
 	 * @param repo
 	 */
@@ -161,6 +163,9 @@
 				Repository submoduleRepo = generator.getRepository();
 				// Clone repository is not present
 				if (submoduleRepo == null) {
+					if (callback != null) {
+						callback.cloningSubmodule(generator.getPath());
+					}
 					CloneCommand clone = Git.cloneRepository();
 					configure(clone);
 					clone.setURI(url);
@@ -201,6 +206,10 @@
 								Constants.HEAD, true);
 						refUpdate.setNewObjectId(commit);
 						refUpdate.forceUpdate();
+						if (callback != null) {
+							callback.checkingOut(commit,
+									generator.getPath());
+						}
 					}
 				} finally {
 					submoduleRepo.close();
@@ -225,4 +234,17 @@
 		this.strategy = strategy;
 		return this;
 	}
+
+	/**
+	 * Set status callback for submodule clone operation.
+	 *
+	 * @param callback
+	 *            the callback
+	 * @return {@code this}
+	 * @since 4.8
+	 */
+	public SubmoduleUpdateCommand setCallback(CloneCommand.Callback callback) {
+		this.callback = callback;
+		return this;
+	}
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/TooLargeObjectInPackException.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/TooLargeObjectInPackException.java
index 08c25c2..b841f57 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/TooLargeObjectInPackException.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/TooLargeObjectInPackException.java
@@ -38,7 +38,8 @@
 package org.eclipse.jgit.api.errors;
 
 /**
- * Exception thrown when the server rejected a too large pack
+ * Exception thrown when PackParser finds an object larger than a predefined
+ * limit
  *
  * @since 4.4
  */
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffAlgorithm.java b/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffAlgorithm.java
index bd6e5c8..5f01366 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffAlgorithm.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffAlgorithm.java
@@ -121,23 +121,7 @@
 			Subsequence<S> as = Subsequence.a(a, region);
 			Subsequence<S> bs = Subsequence.b(b, region);
 			EditList e = Subsequence.toBase(diffNonCommon(cs, as, bs), as, bs);
-
-			// The last insertion may need to be shifted later if it
-			// inserts elements that were previously reduced out as
-			// common at the end.
-			//
-			Edit last = e.get(e.size() - 1);
-			if (last.getType() == Edit.Type.INSERT) {
-				while (last.endB < b.size()
-						&& cmp.equals(b, last.beginB, b, last.endB)) {
-					last.beginA++;
-					last.endA++;
-					last.beginB++;
-					last.endB++;
-				}
-			}
-
-			return e;
+			return normalize(cmp, e, a, b);
 		}
 
 		case EMPTY:
@@ -153,6 +137,107 @@
 	}
 
 	/**
+	 * Reorganize an {@link EditList} for better diff consistency.
+	 * <p>
+	 * {@code DiffAlgorithms} may return {@link Edit.Type#INSERT} or
+	 * {@link Edit.Type#DELETE} edits that can be "shifted". For
+	 * example, the deleted section
+	 * <pre>
+	 * -a
+	 * -b
+	 * -c
+	 *  a
+	 *  b
+	 *  c
+	 * </pre>
+	 * can be shifted down by 1, 2 or 3 locations.
+	 * <p>
+	 * To avoid later merge issues, we shift such edits to a
+	 * consistent location. {@code normalize} uses a simple strategy of
+	 * shifting such edits to their latest possible location.
+	 * <p>
+	 * This strategy may not always produce an aesthetically pleasing
+	 * diff. For instance, it works well with
+	 * <pre>
+	 *  function1 {
+	 *   ...
+	 *  }
+	 *
+	 * +function2 {
+	 * + ...
+	 * +}
+	 * +
+	 * function3 {
+	 * ...
+	 * }
+	 * </pre>
+	 * but less so for
+	 * <pre>
+	 *  #
+	 *  # comment1
+	 *  #
+	 *  function1() {
+	 *  }
+	 *
+	 *  #
+	 * +# comment3
+	 * +#
+	 * +function3() {
+	 * +}
+	 * +
+	 * +#
+	 *  # comment2
+	 *  #
+	 *  function2() {
+	 *  }
+	 * </pre>
+	 * <a href="https://github.com/mhagger/diff-slider-tools">More
+	 * sophisticated strategies</a> are possible, say by calculating a
+	 * suitable "aesthetic cost" for each possible position and using
+	 * the lowest cost, but {@code normalize} just shifts edits
+	 * to the end as much as possible.
+	 *
+	 * @param <S>
+	 *            type of sequence being compared.
+	 * @param cmp
+	 *            the comparator supplying the element equivalence function.
+	 * @param e
+	 *            a modifiable edit list comparing the provided sequences.
+	 * @param a
+	 *            the first (also known as old or pre-image) sequence.
+	 * @param b
+	 *            the second (also known as new or post-image) sequence.
+	 * @return a modifiable edit list with edit regions shifted to their
+	 *         latest possible location. The result list is never null.
+	 * @since 4.7
+	 */
+	private static <S extends Sequence> EditList normalize(
+		SequenceComparator<? super S> cmp, EditList e, S a, S b) {
+		Edit prev = null;
+		for (int i = e.size() - 1; i >= 0; i--) {
+			Edit cur = e.get(i);
+			Edit.Type curType = cur.getType();
+
+			int maxA = (prev == null) ? a.size() : prev.beginA;
+			int maxB = (prev == null) ? b.size() : prev.beginB;
+
+			if (curType == Edit.Type.INSERT) {
+				while (cur.endA < maxA && cur.endB < maxB
+					&& cmp.equals(b, cur.beginB, b, cur.endB)) {
+					cur.shift(1);
+				}
+			} else if (curType == Edit.Type.DELETE) {
+				while (cur.endA < maxA && cur.endB < maxB
+					&& cmp.equals(a, cur.beginA, a, cur.endA)) {
+					cur.shift(1);
+				}
+			}
+			prev = cur;
+		}
+		return e;
+	}
+
+	/**
 	 * Compare two sequences and identify a list of edits between them.
 	 *
 	 * This method should be invoked only after the two sequences have been
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/diff/Edit.java b/org.eclipse.jgit/src/org/eclipse/jgit/diff/Edit.java
index 7eecd13..a2e167f 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/diff/Edit.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/diff/Edit.java
@@ -170,6 +170,21 @@
 	}
 
 	/**
+	 * Move the edit region by the specified amount.
+	 *
+	 * @param amount
+	 *            the region is shifted by this amount, and can be positive or
+	 *            negative.
+	 * @since 4.8
+	 */
+	public final void shift(int amount) {
+		beginA += amount;
+		endA += amount;
+		beginB += amount;
+		endB += amount;
+	}
+
+	/**
 	 * Construct a new edit representing the region before cut.
 	 *
 	 * @param cut
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 bc52473..d899429 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/diff/RenameDetector.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/diff/RenameDetector.java
@@ -220,7 +220,9 @@
 	 * must be allocated, and 1,000,000 file compares may need to be performed.
 	 *
 	 * @param limit
-	 *            new file limit.
+	 *            new file limit. 0 means no limit; a negative number means no
+	 *            inexact rename detection will be performed, only exact rename
+	 *            detection.
 	 */
 	public void setRenameLimit(int limit) {
 		renameLimit = limit;
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 84f0da9..aed76ac 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java
@@ -1299,8 +1299,13 @@
 			return;
 		}
 
+		String name = f.getName();
+		if (name.length() > 200) {
+			name = name.substring(0, 200);
+		}
 		File tmpFile = File.createTempFile(
-				"._" + f.getName(), null, parentDir); //$NON-NLS-1$
+				"._" + name, null, parentDir); //$NON-NLS-1$
+
 		EolStreamType nonNullEolStreamType;
 		if (checkoutMetadata.eolStreamType != null) {
 			nonNullEolStreamType = checkoutMetadata.eolStreamType;
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 94c8e43..ddc6add 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/ManifestParser.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/ManifestParser.java
@@ -60,6 +60,8 @@
 import org.eclipse.jgit.annotations.NonNull;
 import org.eclipse.jgit.api.errors.GitAPIException;
 import org.eclipse.jgit.gitrepo.RepoProject.CopyFile;
+import org.eclipse.jgit.gitrepo.RepoProject.LinkFile;
+import org.eclipse.jgit.gitrepo.RepoProject.ReferenceFile;
 import org.eclipse.jgit.gitrepo.internal.RepoText;
 import org.eclipse.jgit.internal.JGitText;
 import org.eclipse.jgit.lib.Repository;
@@ -209,6 +211,15 @@
 						currentProject.getPath(),
 						attributes.getValue("src"), //$NON-NLS-1$
 						attributes.getValue("dest"))); //$NON-NLS-1$
+		} else if ("linkfile".equals(qName)) { //$NON-NLS-1$
+			if (currentProject == null) {
+				throw new SAXException(RepoText.get().invalidManifest);
+			}
+			currentProject.addLinkFile(new LinkFile(
+						rootRepo,
+						currentProject.getPath(),
+						attributes.getValue("src"), //$NON-NLS-1$
+						attributes.getValue("dest"))); //$NON-NLS-1$
 		} else if ("include".equals(qName)) { //$NON-NLS-1$
 			String name = attributes.getValue("name"); //$NON-NLS-1$
 			if (includedReader != null) {
@@ -359,19 +370,25 @@
 			else
 				last = p;
 		}
-		removeNestedCopyfiles();
+		removeNestedCopyAndLinkfiles();
 	}
 
-	/** Remove copyfiles that sit in a subdirectory of any other project. */
-	void removeNestedCopyfiles() {
+	private void removeNestedCopyAndLinkfiles() {
 		for (RepoProject proj : filteredProjects) {
 			List<CopyFile> copyfiles = new ArrayList<>(proj.getCopyFiles());
 			proj.clearCopyFiles();
 			for (CopyFile copyfile : copyfiles) {
-				if (!isNestedCopyfile(copyfile)) {
+				if (!isNestedReferencefile(copyfile)) {
 					proj.addCopyFile(copyfile);
 				}
 			}
+			List<LinkFile> linkfiles = new ArrayList<>(proj.getLinkFiles());
+			proj.clearLinkFiles();
+			for (LinkFile linkfile : linkfiles) {
+				if (!isNestedReferencefile(linkfile)) {
+					proj.addLinkFile(linkfile);
+				}
+			}
 		}
 	}
 
@@ -393,18 +410,18 @@
 		return false;
 	}
 
-	private boolean isNestedCopyfile(CopyFile copyfile) {
-		if (copyfile.dest.indexOf('/') == -1) {
-			// If the copyfile is at root level then it won't be nested.
+	private boolean isNestedReferencefile(ReferenceFile referencefile) {
+		if (referencefile.dest.indexOf('/') == -1) {
+			// If the referencefile is at root level then it won't be nested.
 			return false;
 		}
 		for (RepoProject proj : filteredProjects) {
-			if (proj.getPath().compareTo(copyfile.dest) > 0) {
+			if (proj.getPath().compareTo(referencefile.dest) > 0) {
 				// Early return as remaining projects can't be ancestor of this
-				// copyfile config (filteredProjects is sorted).
+				// referencefile config (filteredProjects is sorted).
 				return false;
 			}
-			if (proj.isAncestorOf(copyfile.dest)) {
+			if (proj.isAncestorOf(referencefile.dest)) {
 				return true;
 			}
 		}
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 e105dc0..1de8a0b 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoCommand.java
@@ -49,11 +49,14 @@
 import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.lang.UnsupportedOperationException;
+import java.net.URI;
 import java.text.MessageFormat;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.StringJoiner;
 
 import org.eclipse.jgit.annotations.Nullable;
 import org.eclipse.jgit.api.Git;
@@ -67,6 +70,7 @@
 import org.eclipse.jgit.dircache.DirCacheEntry;
 import org.eclipse.jgit.gitrepo.ManifestParser.IncludedFileReader;
 import org.eclipse.jgit.gitrepo.RepoProject.CopyFile;
+import org.eclipse.jgit.gitrepo.RepoProject.LinkFile;
 import org.eclipse.jgit.gitrepo.internal.RepoText;
 import org.eclipse.jgit.internal.JGitText;
 import org.eclipse.jgit.lib.CommitBuilder;
@@ -106,7 +110,8 @@
  */
 public class RepoCommand extends GitCommand<RevCommit> {
 	private String manifestPath;
-	private String uri;
+	private String baseUri;
+	private URI targetUri;
 	private String groupsParam;
 	private String branch;
 	private String targetBranch = Constants.HEAD;
@@ -274,7 +279,25 @@
 	 * @return this command
 	 */
 	public RepoCommand setURI(String uri) {
-		this.uri = uri;
+		this.baseUri = uri;
+		return this;
+	}
+
+	/**
+	 * Set the URI of the superproject (this repository), so the .gitmodules
+	 * file can specify the submodule URLs relative to the superproject.
+	 *
+	 * @param uri
+	 *            the URI of the repository holding the superproject.
+	 * @return this command
+	 * @since 4.8
+	 */
+	public RepoCommand setTargetURI(String uri) {
+		// The repo name is interpreted as a directory, for example
+		// Gerrit (http://gerrit.googlesource.com/gerrit) has a
+		// .gitmodules referencing ../plugins/hooks, which is
+		// on http://gerrit.googlesource.com/plugins/hooks,
+		this.targetUri = URI.create(uri + "/"); //$NON-NLS-1$
 		return this;
 	}
 
@@ -452,9 +475,8 @@
 	public RevCommit call() throws GitAPIException {
 		try {
 			checkCallable();
-			if (uri == null || uri.length() == 0) {
-				throw new IllegalArgumentException(
-					JGitText.get().uriNotConfigured);
+			if (baseUri == null) {
+				baseUri = ""; //$NON-NLS-1$
 			}
 			if (inputStream == null) {
 				if (manifestPath == null || manifestPath.length() == 0)
@@ -478,7 +500,7 @@
 				git = new Git(repo);
 
 			ManifestParser parser = new ManifestParser(
-					includedReader, manifestPath, branch, uri, groupsParam, repo);
+					includedReader, manifestPath, branch, baseUri, groupsParam, repo);
 			try {
 				parser.read(inputStream);
 				for (RepoProject proj : parser.getFilteredProjects()) {
@@ -486,6 +508,7 @@
 							proj.getPath(),
 							proj.getRevision(),
 							proj.getCopyFiles(),
+							proj.getLinkFiles(),
 							proj.getGroups(),
 							proj.getRecommendShallow());
 				}
@@ -550,8 +573,13 @@
 						rec.append("\n"); //$NON-NLS-1$
 						attributes.append(rec.toString());
 					}
+
+					URI submodUrl = URI.create(nameUri);
+					if (targetUri != null) {
+						submodUrl = relativize(targetUri, submodUrl);
+					}
 					cfg.setString("submodule", path, "path", path); //$NON-NLS-1$ //$NON-NLS-2$
-					cfg.setString("submodule", path, "url", nameUri); //$NON-NLS-1$ //$NON-NLS-2$
+					cfg.setString("submodule", path, "url", submodUrl.toString()); //$NON-NLS-1$ //$NON-NLS-2$
 
 					// create gitlink
 					DirCacheEntry dcEntry = new DirCacheEntry(path);
@@ -568,6 +596,25 @@
 						dcEntry.setFileMode(FileMode.REGULAR_FILE);
 						builder.add(dcEntry);
 					}
+					for (LinkFile linkfile : proj.getLinkFiles()) {
+						String link;
+						if (linkfile.dest.contains("/")) { //$NON-NLS-1$
+							link = FileUtils.relativizeGitPath(
+									linkfile.dest.substring(0,
+											linkfile.dest.lastIndexOf('/')),
+									proj.getPath() + "/" + linkfile.src); //$NON-NLS-1$
+						} else {
+							link = proj.getPath() + "/" + linkfile.src; //$NON-NLS-1$
+						}
+
+						objectId = inserter.insert(Constants.OBJ_BLOB,
+								link.getBytes(
+										Constants.CHARACTER_ENCODING));
+						dcEntry = new DirCacheEntry(linkfile.dest);
+						dcEntry.setObjectId(objectId);
+						dcEntry.setFileMode(FileMode.SYMLINK);
+						builder.add(dcEntry);
+					}
 				}
 				String content = cfg.toText();
 
@@ -642,13 +689,20 @@
 	}
 
 	private void addSubmodule(String url, String path, String revision,
-			List<CopyFile> copyfiles, Set<String> groups, String recommendShallow)
+			List<CopyFile> copyfiles, List<LinkFile> linkfiles,
+			Set<String> groups, String recommendShallow)
 			throws GitAPIException, IOException {
 		if (repo.isBare()) {
 			RepoProject proj = new RepoProject(url, path, revision, null, groups, recommendShallow);
 			proj.addCopyFiles(copyfiles);
+			proj.addLinkFiles(linkfiles);
 			bareProjects.add(proj);
 		} else {
+			if (!linkfiles.isEmpty()) {
+				throw new UnsupportedOperationException(
+						JGitText.get().nonBareLinkFilesNotSupported);
+			}
+
 			SubmoduleAddCommand add = git
 				.submoduleAdd()
 				.setPath(path)
@@ -672,6 +726,77 @@
 		}
 	}
 
+	/*
+	 * Assume we are document "a/b/index.html", what should we put in a href to get to "a/" ?
+	 * Returns the child if either base or child is not a bare path. This provides a missing feature in
+	 * java.net.URI (see http://bugs.java.com/view_bug.do?bug_id=6226081).
+	 */
+	private static final String SLASH = "/"; //$NON-NLS-1$
+	static URI relativize(URI current, URI target) {
+
+		// We only handle bare paths for now.
+		if (!target.toString().equals(target.getPath())) {
+			return target;
+		}
+		if (!current.toString().equals(current.getPath())) {
+			return target;
+		}
+
+		String cur = current.normalize().getPath();
+		String dest = target.normalize().getPath();
+
+		// TODO(hanwen): maybe (absolute, relative) should throw an exception.
+		if (cur.startsWith(SLASH) != dest.startsWith(SLASH)) {
+			return target;
+		}
+
+		while (cur.startsWith(SLASH)) {
+			cur = cur.substring(1);
+		}
+		while (dest.startsWith(SLASH)) {
+			dest = dest.substring(1);
+		}
+
+		if (cur.indexOf('/') == -1 || dest.indexOf('/') == -1) {
+			// Avoid having to special-casing in the next two ifs.
+			String prefix = "prefix/"; //$NON-NLS-1$
+			cur = prefix + cur;
+			dest = prefix + dest;
+		}
+
+		if (!cur.endsWith(SLASH)) {
+			// The current file doesn't matter.
+			int lastSlash = cur.lastIndexOf('/');
+			cur = cur.substring(0, lastSlash);
+		}
+		String destFile = ""; //$NON-NLS-1$
+		if (!dest.endsWith(SLASH)) {
+			// We always have to provide the destination file.
+			int lastSlash = dest.lastIndexOf('/');
+			destFile = dest.substring(lastSlash + 1, dest.length());
+			dest = dest.substring(0, dest.lastIndexOf('/'));
+		}
+
+		String[] cs = cur.split(SLASH);
+		String[] ds = dest.split(SLASH);
+
+		int common = 0;
+		while (common < cs.length && common < ds.length && cs[common].equals(ds[common])) {
+			common++;
+		}
+
+		StringJoiner j = new StringJoiner(SLASH);
+		for (int i = common; i < cs.length; i++) {
+			j.add(".."); //$NON-NLS-1$
+		}
+		for (int i = common; i < ds.length; i++) {
+			j.add(ds[i]);
+		}
+
+		j.add(destFile);
+		return URI.create(j.toString());
+	}
+
 	private static String findRef(String ref, Repository repo)
 			throws IOException {
 		if (!ObjectId.isId(ref)) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoProject.java b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoProject.java
index 700cf11..00cd38d 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoProject.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoProject.java
@@ -70,14 +70,17 @@
 	private final String remote;
 	private final Set<String> groups;
 	private final List<CopyFile> copyfiles;
+	private final List<LinkFile> linkfiles;
 	private String recommendShallow;
 	private String url;
 	private String defaultRevision;
 
 	/**
-	 * The representation of a copy file configuration.
+	 * The representation of a reference file configuration.
+	 *
+	 * @since 4.8
 	 */
-	public static class CopyFile {
+	public static class ReferenceFile {
 		final Repository repo;
 		final String path;
 		final String src;
@@ -93,12 +96,31 @@
 		 * @param dest
 		 *            the destination path relative to the super project.
 		 */
-		public CopyFile(Repository repo, String path, String src, String dest) {
+		public ReferenceFile(Repository repo, String path, String src, String dest) {
 			this.repo = repo;
 			this.path = path;
 			this.src = src;
 			this.dest = dest;
 		}
+	}
+
+	/**
+	 * The representation of a copy file configuration.
+	 */
+	public static class CopyFile extends ReferenceFile {
+		/**
+		 * @param repo
+		 *            the super project.
+		 * @param path
+		 *            the path of the project containing this copyfile config.
+		 * @param src
+		 *            the source path relative to the sub repo.
+		 * @param dest
+		 *            the destination path relative to the super project.
+		 */
+		public CopyFile(Repository repo, String path, String src, String dest) {
+			super(repo, path, src, dest);
+		}
 
 		/**
 		 * Do the copy file action.
@@ -126,6 +148,27 @@
 	}
 
 	/**
+	 * The representation of a link file configuration.
+	 *
+	 * @since 4.8
+	 */
+	public static class LinkFile extends ReferenceFile {
+		/**
+		 * @param repo
+		 *            the super project.
+		 * @param path
+		 *            the path of the project containing this linkfile config.
+		 * @param src
+		 *            the source path relative to the sub repo.
+		 * @param dest
+		 *            the destination path relative to the super project.
+		 */
+		public LinkFile(Repository repo, String path, String src, String dest) {
+			super(repo, path, src, dest);
+		}
+	}
+
+	/**
 	 * @param name
 	 *            the relative path to the {@code remote}
 	 * @param path
@@ -156,6 +199,7 @@
 		this.groups = groups;
 		this.recommendShallow = recommendShallow;
 		copyfiles = new ArrayList<>();
+		linkfiles = new ArrayList<>();
 	}
 
 	/**
@@ -250,6 +294,16 @@
 	}
 
 	/**
+	 * Getter for the linkfile configurations.
+	 *
+	 * @return Immutable copy of {@code linkfiles}
+	 * @since 4.8
+	 */
+	public List<LinkFile> getLinkFiles() {
+		return Collections.unmodifiableList(linkfiles);
+	}
+
+	/**
 	 * Get the url of the sub repo.
 	 *
 	 * @return {@code url}
@@ -335,6 +389,35 @@
 		this.copyfiles.clear();
 	}
 
+	/**
+	 * Add a link file configuration.
+	 *
+	 * @param linkfile
+	 * @since 4.8
+	 */
+	public void addLinkFile(LinkFile linkfile) {
+		linkfiles.add(linkfile);
+	}
+
+	/**
+	 * Add a bunch of linkfile configurations.
+	 *
+	 * @param linkFiles
+	 * @since 4.8
+	 */
+	public void addLinkFiles(Collection<LinkFile> linkFiles) {
+		this.linkfiles.addAll(linkFiles);
+	}
+
+	/**
+	 * Clear all the linkfiles.
+	 *
+	 * @since 4.8
+	 */
+	public void clearLinkFiles() {
+		this.linkfiles.clear();
+	}
+
 	private String getPathWithSlash() {
 		if (path.endsWith("/")) //$NON-NLS-1$
 			return path;
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 c1aca6a..62a6749 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/hooks/GitHook.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/hooks/GitHook.java
@@ -42,9 +42,12 @@
  */
 package org.eclipse.jgit.hooks;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
+
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.PrintStream;
+import java.io.UnsupportedEncodingException;
 import java.util.concurrent.Callable;
 
 import org.eclipse.jgit.api.errors.AbortedByHookException;
@@ -147,12 +150,19 @@
 	 */
 	protected void doRun() throws AbortedByHookException {
 		final ByteArrayOutputStream errorByteArray = new ByteArrayOutputStream();
-		final PrintStream hookErrRedirect = new PrintStream(errorByteArray);
+		PrintStream hookErrRedirect = null;
+		try {
+			hookErrRedirect = new PrintStream(errorByteArray, false,
+					UTF_8.name());
+		} catch (UnsupportedEncodingException e) {
+			// UTF-8 is guaranteed to be available
+		}
 		ProcessResult result = FS.DETECTED.runHookIfPresent(getRepository(),
 				getHookName(), getParameters(), getOutputStream(),
 				hookErrRedirect, getStdinArgs());
 		if (result.isExecutedWithError()) {
-			throw new AbortedByHookException(errorByteArray.toString(),
+			throw new AbortedByHookException(
+					new String(errorByteArray.toByteArray(), UTF_8),
 					getHookName(), result.getExitCode());
 		}
 	}
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 32bfb7e..2ba3b8f 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java
@@ -492,6 +492,7 @@
 	/***/ public String noHMACsupport;
 	/***/ public String noMergeBase;
 	/***/ public String noMergeHeadSpecified;
+	/***/ public String nonBareLinkFilesNotSupported;
 	/***/ public String noSuchRef;
 	/***/ public String notABoolean;
 	/***/ public String notABundle;
@@ -599,6 +600,7 @@
 	/***/ public String renamesRejoiningModifies;
 	/***/ public String repositoryAlreadyExists;
 	/***/ public String repositoryConfigFileInvalid;
+	/***/ public String repositoryIsRequired;
 	/***/ public String repositoryNotFound;
 	/***/ public String repositoryState_applyMailbox;
 	/***/ public String repositoryState_bare;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlockCache.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlockCache.java
index ef0b80c..6fff656 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlockCache.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlockCache.java
@@ -322,6 +322,7 @@
 		HashEntry e1 = table.get(slot);
 		DfsBlock v = scan(e1, key, position);
 		if (v != null) {
+			ctx.stats.blockCacheHit++;
 			statHit.incrementAndGet();
 			return v;
 		}
@@ -334,6 +335,7 @@
 			if (e2 != e1) {
 				v = scan(e2, key, position);
 				if (v != null) {
+					ctx.stats.blockCacheHit++;
 					statHit.incrementAndGet();
 					creditSpace(blockSize);
 					return v;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsGarbageCollector.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsGarbageCollector.java
index d380302..55f9cc2 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsGarbageCollector.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsGarbageCollector.java
@@ -53,12 +53,12 @@
 import static org.eclipse.jgit.internal.storage.pack.PackExt.BITMAP_INDEX;
 import static org.eclipse.jgit.internal.storage.pack.PackExt.INDEX;
 import static org.eclipse.jgit.internal.storage.pack.PackExt.PACK;
+import static org.eclipse.jgit.internal.storage.pack.PackWriter.NONE;
 
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Calendar;
 import java.util.Collection;
-import java.util.Collections;
 import java.util.EnumSet;
 import java.util.GregorianCalendar;
 import java.util.HashSet;
@@ -112,7 +112,8 @@
 	private List<DfsPackFile> packsBefore;
 	private List<DfsPackFile> expiredGarbagePacks;
 
-	private Set<ObjectId> allHeads;
+	private Set<ObjectId> allHeadsAndTags;
+	private Set<ObjectId> allTags;
 	private Set<ObjectId> nonHeads;
 	private Set<ObjectId> txnHeads;
 	private Set<ObjectId> tagTargets;
@@ -234,7 +235,7 @@
 					JGitText.get().supportOnlyPackIndexVersion2);
 
 		startTimeMillis = SystemReader.getInstance().getCurrentTime();
-		ctx = (DfsReader) objdb.newReader();
+		ctx = objdb.newReader();
 		try {
 			refdb.refresh();
 			objdb.clearCache();
@@ -242,30 +243,36 @@
 			Collection<Ref> refsBefore = getAllRefs();
 			readPacksBefore();
 
-			if (packsBefore.isEmpty()) {
-				if (!expiredGarbagePacks.isEmpty()) {
-					objdb.commitPack(noPacks(), toPrune());
-				}
-				return true;
-			}
-
-			allHeads = new HashSet<>();
+			Set<ObjectId> allHeads = new HashSet<>();
+			allHeadsAndTags = new HashSet<>();
+			allTags = new HashSet<>();
 			nonHeads = new HashSet<>();
 			txnHeads = new HashSet<>();
 			tagTargets = new HashSet<>();
 			for (Ref ref : refsBefore) {
-				if (ref.isSymbolic() || ref.getObjectId() == null)
+				if (ref.isSymbolic() || ref.getObjectId() == null) {
 					continue;
-				if (isHead(ref) || isTag(ref))
+				}
+				if (isHead(ref)) {
 					allHeads.add(ref.getObjectId());
-				else if (RefTreeNames.isRefTree(refdb, ref.getName()))
+				} else if (isTag(ref)) {
+					allTags.add(ref.getObjectId());
+				} else if (RefTreeNames.isRefTree(refdb, ref.getName())) {
 					txnHeads.add(ref.getObjectId());
-				else
+				} else {
 					nonHeads.add(ref.getObjectId());
-				if (ref.getPeeledObjectId() != null)
+				}
+				if (ref.getPeeledObjectId() != null) {
 					tagTargets.add(ref.getPeeledObjectId());
+				}
 			}
-			tagTargets.addAll(allHeads);
+			// Don't exclude tags that are also branch tips.
+			allTags.removeAll(allHeads);
+			allHeadsAndTags.addAll(allHeads);
+			allHeadsAndTags.addAll(allTags);
+
+			// Hoist all branch tips and tags earlier in the pack file
+			tagTargets.addAll(allHeadsAndTags);
 
 			boolean rollback = true;
 			try {
@@ -307,13 +314,12 @@
 		packsBefore = new ArrayList<>(packs.length);
 		expiredGarbagePacks = new ArrayList<>(packs.length);
 
-		long mostRecentGC = mostRecentGC(packs);
 		long now = SystemReader.getInstance().getCurrentTime();
 		for (DfsPackFile p : packs) {
 			DfsPackDescription d = p.getPackDescription();
 			if (d.getPackSource() != UNREACHABLE_GARBAGE) {
 				packsBefore.add(p);
-			} else if (packIsExpiredGarbage(d, mostRecentGC, now)) {
+			} else if (packIsExpiredGarbage(d, now)) {
 				expiredGarbagePacks.add(p);
 			} else if (packIsCoalesceableGarbage(d, now)) {
 				packsBefore.add(p);
@@ -321,39 +327,13 @@
 		}
 	}
 
-	private static long mostRecentGC(DfsPackFile[] packs) {
-		long r = 0;
-		for (DfsPackFile p : packs) {
-			DfsPackDescription d = p.getPackDescription();
-			if (d.getPackSource() == GC || d.getPackSource() == GC_REST) {
-				r = Math.max(r, d.getLastModified());
-			}
-		}
-		return r;
-	}
-
-	private boolean packIsExpiredGarbage(DfsPackDescription d,
-			long mostRecentGC, long now) {
-		// It should be safe to remove an UNREACHABLE_GARBAGE pack if it:
-		//
-		// (a) Predates the most recent prior run of this class. This check
-		// ensures the graph traversal algorithm had a chance to consider
-		// all objects in this pack and copied them into a GC or GC_REST
-		// pack if the graph contained live edges to the objects.
-		//
-		// This check is safe because of the ordering of packing; the GC
-		// packs are written first and then the UNREACHABLE_GARBAGE is
-		// constructed. Any UNREACHABLE_GARBAGE dated earlier than the GC
-		// was input to the prior GC's graph traversal.
-		//
-		// (b) Is older than garbagePackTtl. This check gives concurrent
-		// inserter threads sufficient time to identify an object is not
-		// in the graph and should have a new copy written, rather than
-		// relying on something from an UNREACHABLE_GARBAGE pack.
-		//
-		// Both (a) and (b) must be met to safely remove UNREACHABLE_GARBAGE.
+	private boolean packIsExpiredGarbage(DfsPackDescription d, long now) {
+		// Consider the garbage pack as expired when it's older than
+		// garbagePackTtl. This check gives concurrent inserter threads
+		// sufficient time to identify an object is not in the graph and should
+		// have a new copy written, rather than relying on something from an
+		// UNREACHABLE_GARBAGE pack.
 		return d.getPackSource() == UNREACHABLE_GARBAGE
-				&& d.getLastModified() < mostRecentGC
 				&& garbageTtlMillis > 0
 				&& now - d.getLastModified() >= garbageTtlMillis;
 	}
@@ -448,12 +428,12 @@
 	}
 
 	private void packHeads(ProgressMonitor pm) throws IOException {
-		if (allHeads.isEmpty())
+		if (allHeadsAndTags.isEmpty())
 			return;
 
 		try (PackWriter pw = newPackWriter()) {
 			pw.setTagTargets(tagTargets);
-			pw.preparePack(pm, allHeads, PackWriter.NONE);
+			pw.preparePack(pm, allHeadsAndTags, NONE, NONE, allTags);
 			if (0 < pw.getObjectCount())
 				writePack(GC, pw, pm,
 						estimateGcPackSize(INSERT, RECEIVE, COMPACT, GC));
@@ -467,7 +447,7 @@
 		try (PackWriter pw = newPackWriter()) {
 			for (ObjectIdSet packedObjs : newPackObj)
 				pw.excludeObjects(packedObjs);
-			pw.preparePack(pm, nonHeads, allHeads);
+			pw.preparePack(pm, nonHeads, allHeadsAndTags);
 			if (0 < pw.getObjectCount())
 				writePack(GC_REST, pw, pm,
 						estimateGcPackSize(INSERT, RECEIVE, COMPACT, GC_REST));
@@ -481,7 +461,7 @@
 		try (PackWriter pw = newPackWriter()) {
 			for (ObjectIdSet packedObjs : newPackObj)
 				pw.excludeObjects(packedObjs);
-			pw.preparePack(pm, txnHeads, PackWriter.NONE);
+			pw.preparePack(pm, txnHeads, NONE);
 			if (0 < pw.getObjectCount())
 				writePack(GC_TXN, pw, pm, 0 /* unknown pack size */);
 		}
@@ -605,8 +585,4 @@
 		DfsBlockCache.getInstance().getOrCreate(pack, null);
 		return pack;
 	}
-
-	private static List<DfsPackDescription> noPacks() {
-		return Collections.emptyList();
-	}
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsInserter.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsInserter.java
index fd72756..e65c9fd 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsInserter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsInserter.java
@@ -530,7 +530,7 @@
 	}
 
 	private class Reader extends ObjectReader {
-		private final DfsReader ctx = new DfsReader(db);
+		private final DfsReader ctx = db.newReader();
 
 		@Override
 		public ObjectReader newReader() {
@@ -647,7 +647,7 @@
 
 		@Override
 		public ObjectStream openStream() throws IOException {
-			final DfsReader ctx = new DfsReader(db);
+			final DfsReader ctx = db.newReader();
 			if (srcPack != packKey) {
 				try {
 					// Post DfsInserter.flush() use the normal code path.
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 b1cb72d..32ee6c2 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
@@ -170,7 +170,7 @@
 	}
 
 	@Override
-	public ObjectReader newReader() {
+	public DfsReader newReader() {
 		return new DfsReader(this);
 	}
 
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 0a29ac5..f7c87a4 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
@@ -201,7 +201,7 @@
 			pm = NullProgressMonitor.INSTANCE;
 
 		DfsObjDatabase objdb = repo.getObjectDatabase();
-		try (DfsReader ctx = (DfsReader) objdb.newReader()) {
+		try (DfsReader ctx = objdb.newReader()) {
 			PackConfig pc = new PackConfig(repo);
 			pc.setIndexVersion(2);
 			pc.setDeltaCompress(false);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackFile.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackFile.java
index f15d427..ae2e7e4 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackFile.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackFile.java
@@ -251,6 +251,8 @@
 
 			PackIndex idx;
 			try {
+				ctx.stats.readIdx++;
+				long start = System.nanoTime();
 				ReadableChannel rc = ctx.db.openFile(packDesc, INDEX);
 				try {
 					InputStream in = Channels.newInputStream(rc);
@@ -260,10 +262,11 @@
 						bs = (wantSize / bs) * bs;
 					else if (bs <= 0)
 						bs = wantSize;
-					in = new BufferedInputStream(in, bs);
-					idx = PackIndex.read(in);
+					idx = PackIndex.read(new BufferedInputStream(in, bs));
+					ctx.stats.readIdxBytes += rc.position();
 				} finally {
 					rc.close();
+					ctx.stats.readIdxMicros += elapsedMicros(start);
 				}
 			} catch (EOFException e) {
 				invalid = true;
@@ -286,6 +289,10 @@
 		}
 	}
 
+	private static long elapsedMicros(long start) {
+		return (System.nanoTime() - start) / 1000L;
+	}
+
 	final boolean isGarbage() {
 		return packDesc.getPackSource() == UNREACHABLE_GARBAGE;
 	}
@@ -314,6 +321,8 @@
 			long size;
 			PackBitmapIndex idx;
 			try {
+				ctx.stats.readBitmap++;
+				long start = System.nanoTime();
 				ReadableChannel rc = ctx.db.openFile(packDesc, BITMAP_INDEX);
 				try {
 					InputStream in = Channels.newInputStream(rc);
@@ -329,6 +338,8 @@
 				} finally {
 					size = rc.position();
 					rc.close();
+					ctx.stats.readIdxBytes += size;
+					ctx.stats.readIdxMicros += elapsedMicros(start);
 				}
 			} catch (EOFException e) {
 				IOException e2 = new IOException(MessageFormat.format(
@@ -777,6 +788,8 @@
 		if (invalid)
 			throw new PackInvalidException(getPackName());
 
+		ctx.stats.readBlock++;
+		long start = System.nanoTime();
 		ReadableChannel rc = ctx.db.openFile(packDesc, PACK);
 		try {
 			int size = blockSize(rc);
@@ -803,6 +816,7 @@
 			byte[] buf = new byte[size];
 			rc.position(pos);
 			int cnt = read(rc, ByteBuffer.wrap(buf, 0, size));
+			ctx.stats.readBlockBytes += cnt;
 			if (cnt != size) {
 				if (0 <= len) {
 					throw new EOFException(MessageFormat.format(
@@ -824,10 +838,10 @@
 				length = len = rc.size();
 			}
 
-			DfsBlock v = new DfsBlock(key, pos, buf);
-			return v;
+			return new DfsBlock(key, pos, buf);
 		} finally {
 			rc.close();
+			ctx.stats.readBlockMicros += elapsedMicros(start);
 		}
 	}
 
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 755b163..d611469 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
@@ -95,7 +95,7 @@
  * See the base {@link ObjectReader} documentation for details. Notably, a
  * reader is not thread safe.
  */
-public final class DfsReader extends ObjectReader implements ObjectReuseAsIs {
+public class DfsReader extends ObjectReader implements ObjectReuseAsIs {
 	private static final int MAX_RESOLVE_MATCHES = 256;
 
 	/** Temporary buffer large enough for at least one raw object id. */
@@ -104,17 +104,21 @@
 	/** Database this reader loads objects from. */
 	final DfsObjDatabase db;
 
+	final DfsReaderIoStats.Accumulator stats = new DfsReaderIoStats.Accumulator();
+
 	private Inflater inf;
-
 	private DfsBlock block;
-
 	private DeltaBaseCache baseCache;
-
 	private DfsPackFile last;
-
 	private boolean avoidUnreachable;
 
-	DfsReader(DfsObjDatabase db) {
+	/**
+	 * Initialize a new DfsReader
+	 *
+	 * @param db
+	 *            parent DfsObjDatabase.
+	 */
+	protected DfsReader(DfsObjDatabase db) {
 		this.db = db;
 		this.streamFileThreshold = db.getReaderOptions().getStreamFileThreshold();
 	}
@@ -131,7 +135,7 @@
 
 	@Override
 	public ObjectReader newReader() {
-		return new DfsReader(db);
+		return db.newReader();
 	}
 
 	@Override
@@ -170,6 +174,7 @@
 		PackList packList = db.getPackList();
 		resolveImpl(packList, id, matches);
 		if (matches.size() < MAX_RESOLVE_MATCHES && packList.dirty()) {
+			stats.scanPacks++;
 			resolveImpl(db.scanPacks(packList), id, matches);
 		}
 		return matches;
@@ -198,6 +203,7 @@
 		if (hasImpl(packList, objectId)) {
 			return true;
 		} else if (packList.dirty()) {
+			stats.scanPacks++;
 			return hasImpl(db.scanPacks(packList), objectId);
 		}
 		return false;
@@ -234,6 +240,7 @@
 			return checkType(ldr, objectId, typeHint);
 		}
 		if (packList.dirty()) {
+			stats.scanPacks++;
 			ldr = openImpl(db.scanPacks(packList), objectId);
 			if (ldr != null) {
 				return checkType(ldr, objectId, typeHint);
@@ -316,6 +323,7 @@
 		List<FoundObject<T>> r = new ArrayList<>();
 		findAllImpl(packList, pending, r);
 		if (!pending.isEmpty() && packList.dirty()) {
+			stats.scanPacks++;
 			findAllImpl(db.scanPacks(packList), pending, r);
 		}
 		for (T t : pending) {
@@ -452,7 +460,6 @@
 		final IOException findAllError = error;
 		return new AsyncObjectSizeQueue<T>() {
 			private FoundObject<T> cur;
-
 			private long sz;
 
 			@Override
@@ -718,9 +725,10 @@
 		for (int dstoff = 0;;) {
 			int n = inf.inflate(dstbuf, dstoff, dstbuf.length - dstoff);
 			dstoff += n;
-			if (inf.finished() || (headerOnly && dstoff == dstbuf.length))
+			if (inf.finished() || (headerOnly && dstoff == dstbuf.length)) {
+				stats.inflatedBytes += dstoff;
 				return dstoff;
-			if (inf.needsInput()) {
+			} else if (inf.needsInput()) {
 				pin(pack, position);
 				position += block.setInput(position, inf);
 			} else if (n == 0)
@@ -764,6 +772,11 @@
 		block = null;
 	}
 
+	/** @return IO statistics accumulated by this reader. */
+	public DfsReaderIoStats getIoStats() {
+		return new DfsReaderIoStats(stats);
+	}
+
 	/** Release the current window cursor. */
 	@Override
 	public void close() {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReaderIoStats.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReaderIoStats.java
new file mode 100644
index 0000000..9a174c8
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReaderIoStats.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2017, Google Inc.
+ * 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;
+
+/** IO statistics for a {@link DfsReader}. */
+public class DfsReaderIoStats {
+	/** POJO to accumulate IO statistics. */
+	public static class Accumulator {
+		/** Number of times the reader explicitly called scanPacks. */
+		long scanPacks;
+
+		/** Total number of complete pack indexes read into memory. */
+		long readIdx;
+
+		/** Total number of complete bitmap indexes read into memory. */
+		long readBitmap;
+
+		/** Total number of bytes read from indexes. */
+		long readIdxBytes;
+
+		/** Total microseconds spent reading pack or bitmap indexes. */
+		long readIdxMicros;
+
+		/** Total number of block cache hits. */
+		long blockCacheHit;
+
+		/** Total number of discrete blocks read from pack file(s). */
+		long readBlock;
+
+		/** Total number of compressed bytes read as block sized units. */
+		long readBlockBytes;
+
+		/** Total microseconds spent reading {@link #readBlock} blocks. */
+		long readBlockMicros;
+
+		/** Total number of bytes decompressed. */
+		long inflatedBytes;
+
+		Accumulator() {
+		}
+	}
+
+	private final Accumulator stats;
+
+	DfsReaderIoStats(Accumulator stats) {
+		this.stats = stats;
+	}
+
+	/** @return number of times the reader explicitly called scanPacks. */
+	public long getScanPacks() {
+		return stats.scanPacks;
+	}
+
+	/** @return total number of complete pack indexes read into memory. */
+	public long getReadPackIndexCount() {
+		return stats.readIdx;
+	}
+
+	/** @return total number of complete bitmap indexes read into memory. */
+	public long getReadBitmapIndexCount() {
+		return stats.readBitmap;
+	}
+
+	/** @return total number of bytes read from indexes. */
+	public long getReadIndexBytes() {
+		return stats.readIdxBytes;
+	}
+
+	/** @return total microseconds spent reading pack or bitmap indexes. */
+	public long getReadIndexMicros() {
+		return stats.readIdxMicros;
+	}
+
+	/** @return total number of block cache hits. */
+	public long getBlockCacheHits() {
+		return stats.blockCacheHit;
+	}
+
+	/** @return total number of discrete blocks read from pack file(s). */
+	public long getReadBlocksCount() {
+		return stats.readBlock;
+	}
+
+	/** @return total number of compressed bytes read as block sized units. */
+	public long getReadBlocksBytes() {
+		return stats.readBlockBytes;
+	}
+
+	/** @return total microseconds spent reading blocks. */
+	public long getReadBlocksMicros() {
+		return stats.readBlockMicros;
+	}
+
+	/** @return total number of bytes decompressed. */
+	public long getInflatedBytes() {
+		return stats.inflatedBytes;
+	}
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/LargePackedWholeObject.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/LargePackedWholeObject.java
index 6d40a75..73a93e6 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/LargePackedWholeObject.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/LargePackedWholeObject.java
@@ -99,7 +99,7 @@
 
 	@Override
 	public ObjectStream openStream() throws MissingObjectException, IOException {
-		DfsReader ctx = new DfsReader(db);
+		DfsReader ctx = db.newReader();
 		InputStream in;
 		try {
 			in = new PackInputStream(pack, objectOffset + headerLength, ctx);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/CachedObjectDirectory.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/CachedObjectDirectory.java
index d47b304..ae8260a 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/CachedObjectDirectory.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/CachedObjectDirectory.java
@@ -47,8 +47,10 @@
 import java.io.File;
 import java.io.IOException;
 import java.util.Collection;
+import java.util.HashSet;
 import java.util.Set;
 
+import org.eclipse.jgit.internal.storage.file.ObjectDirectory.AlternateHandle;
 import org.eclipse.jgit.internal.storage.pack.ObjectToPack;
 import org.eclipse.jgit.internal.storage.pack.PackWriter;
 import org.eclipse.jgit.lib.AbbreviatedObjectId;
@@ -160,43 +162,70 @@
 		return alts;
 	}
 
+	private Set<AlternateHandle.Id> skipMe(Set<AlternateHandle.Id> skips) {
+		Set<AlternateHandle.Id> withMe = new HashSet<>();
+		if (skips != null) {
+			withMe.addAll(skips);
+		}
+		withMe.add(getAlternateId());
+		return withMe;
+	}
+
 	@Override
 	void resolve(Set<ObjectId> matches, AbbreviatedObjectId id)
 			throws IOException {
-		// In theory we could accelerate the loose object scan using our
-		// unpackedObjects map, but its not worth the huge code complexity.
-		// Scanning a single loose directory is fast enough, and this is
-		// unlikely to be called anyway.
-		//
 		wrapped.resolve(matches, id);
 	}
 
 	@Override
 	public boolean has(final AnyObjectId objectId) throws IOException {
-		if (unpackedObjects.contains(objectId))
+		return has(objectId, null);
+	}
+
+	private boolean has(final AnyObjectId objectId, Set<AlternateHandle.Id> skips)
+			throws IOException {
+		if (unpackedObjects.contains(objectId)) {
 			return true;
-		if (wrapped.hasPackedObject(objectId))
+		}
+		if (wrapped.hasPackedObject(objectId)) {
 			return true;
+		}
+		skips = skipMe(skips);
 		for (CachedObjectDirectory alt : myAlternates()) {
-			if (alt.has(objectId))
-				return true;
+			if (!skips.contains(alt.getAlternateId())) {
+				if (alt.has(objectId, skips)) {
+					return true;
+				}
+			}
 		}
 		return false;
 	}
 
 	@Override
-	ObjectLoader openObject(final WindowCursor curs,
-			final AnyObjectId objectId) throws IOException {
+	ObjectLoader openObject(final WindowCursor curs, final AnyObjectId objectId)
+			throws IOException {
+		return openObject(curs, objectId, null);
+	}
+
+	private ObjectLoader openObject(final WindowCursor curs,
+			final AnyObjectId objectId, Set<AlternateHandle.Id> skips)
+			throws IOException {
 		ObjectLoader ldr = openLooseObject(curs, objectId);
-		if (ldr != null)
+		if (ldr != null) {
 			return ldr;
+		}
 		ldr = wrapped.openPackedObject(curs, objectId);
-		if (ldr != null)
+		if (ldr != null) {
 			return ldr;
+		}
+		skips = skipMe(skips);
 		for (CachedObjectDirectory alt : myAlternates()) {
-			ldr = alt.openObject(curs, objectId);
-			if (ldr != null)
-				return ldr;
+			if (!skips.contains(alt.getAlternateId())) {
+				ldr = alt.openObject(curs, objectId, skips);
+				if (ldr != null) {
+					return ldr;
+				}
+			}
 		}
 		return null;
 	}
@@ -260,4 +289,8 @@
 			super(id);
 		}
 	}
+
+	private AlternateHandle.Id getAlternateId() {
+		return wrapped.getAlternateId();
+	}
 }
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 ef9aa37..6a674aa 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
@@ -490,10 +490,28 @@
 	 */
 	@Override
 	public Set<ObjectId> getAdditionalHaves() {
+		return getAdditionalHaves(null);
+	}
+
+	/**
+	 * Objects known to exist but not expressed by {@link #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.
+	 *
+	 * @param skips
+	 *            Set of AlternateHandle Ids already seen
+	 *
+	 * @return unmodifiable collection of other known objects.
+	 */
+	private Set<ObjectId> getAdditionalHaves(Set<AlternateHandle.Id> skips) {
 		HashSet<ObjectId> r = new HashSet<>();
+		skips = objectDatabase.addMe(skips);
 		for (AlternateHandle d : objectDatabase.myAlternates()) {
-			if (d instanceof AlternateRepository) {
-				Repository repo;
+			if (d instanceof AlternateRepository && !skips.contains(d.getId())) {
+				FileRepository repo;
 
 				repo = ((AlternateRepository) d).repository;
 				for (Ref ref : repo.getAllRefs().values()) {
@@ -502,7 +520,7 @@
 					if (ref.getPeeledObjectId() != null)
 						r.add(ref.getPeeledObjectId());
 				}
-				r.addAll(repo.getAdditionalHaves());
+				r.addAll(repo.getAdditionalHaves(skips));
 			}
 		}
 		return r;
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 e1b309d..ad611d3 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
@@ -78,7 +78,6 @@
 import java.util.concurrent.Callable;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
 import java.util.concurrent.Future;
 import java.util.regex.Pattern;
 import java.util.stream.Collectors;
@@ -107,6 +106,7 @@
 import org.eclipse.jgit.lib.ProgressMonitor;
 import org.eclipse.jgit.lib.Ref;
 import org.eclipse.jgit.lib.Ref.Storage;
+import org.eclipse.jgit.lib.internal.WorkQueue;
 import org.eclipse.jgit.lib.RefDatabase;
 import org.eclipse.jgit.lib.ReflogEntry;
 import org.eclipse.jgit.lib.ReflogReader;
@@ -151,7 +151,19 @@
 
 	private static final int DEFAULT_AUTOLIMIT = 6700;
 
-	private static ExecutorService executor = Executors.newFixedThreadPool(1);
+	private static volatile ExecutorService executor;
+
+	/**
+	 * Set the executor for running auto-gc in the background. If no executor is
+	 * set JGit's own WorkQueue will be used.
+	 *
+	 * @param e
+	 *            the executor to be used for running auto-gc
+	 * @since 4.8
+	 */
+	public static void setExecutor(ExecutorService e) {
+		executor = e;
+	}
 
 	private final FileRepository repo;
 
@@ -271,7 +283,7 @@
 			}
 			return Collections.emptyList();
 		};
-		Future<Collection<PackFile>> result = executor.submit(gcTask);
+		Future<Collection<PackFile>> result = executor().submit(gcTask);
 		if (background) {
 			// TODO(ms): in 5.0 change signature and return the Future
 			return Collections.emptyList();
@@ -283,6 +295,10 @@
 		}
 	}
 
+	private ExecutorService executor() {
+		return (executor != null) ? executor : WorkQueue.getExecutor();
+	}
+
 	private Collection<PackFile> doGc() throws IOException, ParseException {
 		if (automatic && !needGc()) {
 			return Collections.emptyList();
@@ -804,7 +820,9 @@
 		long time = System.currentTimeMillis();
 		Collection<Ref> refsBefore = getAllRefs();
 
+		Set<ObjectId> allHeadsAndTags = new HashSet<>();
 		Set<ObjectId> allHeads = new HashSet<>();
+		Set<ObjectId> allTags = new HashSet<>();
 		Set<ObjectId> nonHeads = new HashSet<>();
 		Set<ObjectId> txnHeads = new HashSet<>();
 		Set<ObjectId> tagTargets = new HashSet<>();
@@ -814,16 +832,21 @@
 		for (Ref ref : refsBefore) {
 			checkCancelled();
 			nonHeads.addAll(listRefLogObjects(ref, 0));
-			if (ref.isSymbolic() || ref.getObjectId() == null)
+			if (ref.isSymbolic() || ref.getObjectId() == null) {
 				continue;
-			if (isHead(ref) || isTag(ref))
+			}
+			if (isHead(ref)) {
 				allHeads.add(ref.getObjectId());
-			else if (RefTreeNames.isRefTree(refdb, ref.getName()))
+			} else if (isTag(ref)) {
+				allTags.add(ref.getObjectId());
+			} else if (RefTreeNames.isRefTree(refdb, ref.getName())) {
 				txnHeads.add(ref.getObjectId());
-			else
+			} else {
 				nonHeads.add(ref.getObjectId());
-			if (ref.getPeeledObjectId() != null)
+			}
+			if (ref.getPeeledObjectId() != null) {
 				tagTargets.add(ref.getPeeledObjectId());
+			}
 		}
 
 		List<ObjectIdSet> excluded = new LinkedList<>();
@@ -833,13 +856,19 @@
 				excluded.add(f.getIndex());
 		}
 
-		tagTargets.addAll(allHeads);
+		// Don't exclude tags that are also branch tips
+		allTags.removeAll(allHeads);
+		allHeadsAndTags.addAll(allHeads);
+		allHeadsAndTags.addAll(allTags);
+
+		// Hoist all branch tips and tags earlier in the pack file
+		tagTargets.addAll(allHeadsAndTags);
 		nonHeads.addAll(indexObjects);
 
 		List<PackFile> ret = new ArrayList<>(2);
 		PackFile heads = null;
-		if (!allHeads.isEmpty()) {
-			heads = writePack(allHeads, Collections.<ObjectId> emptySet(),
+		if (!allHeadsAndTags.isEmpty()) {
+			heads = writePack(allHeadsAndTags, PackWriter.NONE, allTags,
 					tagTargets, excluded);
 			if (heads != null) {
 				ret.add(heads);
@@ -847,12 +876,14 @@
 			}
 		}
 		if (!nonHeads.isEmpty()) {
-			PackFile rest = writePack(nonHeads, allHeads, tagTargets, excluded);
+			PackFile rest = writePack(nonHeads, allHeadsAndTags, PackWriter.NONE,
+					tagTargets, excluded);
 			if (rest != null)
 				ret.add(rest);
 		}
 		if (!txnHeads.isEmpty()) {
-			PackFile txn = writePack(txnHeads, PackWriter.NONE, null, excluded);
+			PackFile txn = writePack(txnHeads, PackWriter.NONE, PackWriter.NONE,
+					null, excluded);
 			if (txn != null)
 				ret.add(txn);
 		}
@@ -1036,8 +1067,9 @@
 	}
 
 	private PackFile writePack(@NonNull Set<? extends ObjectId> want,
-			@NonNull Set<? extends ObjectId> have, Set<ObjectId> tagTargets,
-			List<ObjectIdSet> excludeObjects) throws IOException {
+			@NonNull Set<? extends ObjectId> have, @NonNull Set<ObjectId> tags,
+			Set<ObjectId> tagTargets, List<ObjectIdSet> excludeObjects)
+			throws IOException {
 		checkCancelled();
 		File tmpPack = null;
 		Map<PackExt, File> tmpExts = new TreeMap<>(
@@ -1063,12 +1095,13 @@
 			// prepare the PackWriter
 			pw.setDeltaBaseAsOffset(true);
 			pw.setReuseDeltaCommits(false);
-			if (tagTargets != null)
+			if (tagTargets != null) {
 				pw.setTagTargets(tagTargets);
+			}
 			if (excludeObjects != null)
 				for (ObjectIdSet idx : excludeObjects)
 					pw.excludeObjects(idx);
-			pw.preparePack(pm, want, have);
+			pw.preparePack(pm, want, have, PackWriter.NONE, tags);
 			if (pw.getObjectCount() == 0)
 				return null;
 			checkCancelled();
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 00e3953..d953b87 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
@@ -64,6 +64,7 @@
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.atomic.AtomicReference;
 
@@ -117,6 +118,8 @@
 	/** Maximum number of candidates offered as resolutions of abbreviation. */
 	private static final int RESOLVE_ABBREV_LIMIT = 256;
 
+	private final AlternateHandle handle = new AlternateHandle(this);
+
 	private final Config config;
 
 	private final File objects;
@@ -294,26 +297,38 @@
 	@Override
 	public boolean has(AnyObjectId objectId) {
 		return unpackedObjectCache.isUnpacked(objectId)
-				|| hasPackedInSelfOrAlternate(objectId)
-				|| hasLooseInSelfOrAlternate(objectId);
+				|| hasPackedInSelfOrAlternate(objectId, null)
+				|| hasLooseInSelfOrAlternate(objectId, null);
 	}
 
-	private boolean hasPackedInSelfOrAlternate(AnyObjectId objectId) {
-		if (hasPackedObject(objectId))
+	private boolean hasPackedInSelfOrAlternate(AnyObjectId objectId,
+			Set<AlternateHandle.Id> skips) {
+		if (hasPackedObject(objectId)) {
 			return true;
+		}
+		skips = addMe(skips);
 		for (AlternateHandle alt : myAlternates()) {
-			if (alt.db.hasPackedInSelfOrAlternate(objectId))
-				return true;
+			if (!skips.contains(alt.getId())) {
+				if (alt.db.hasPackedInSelfOrAlternate(objectId, skips)) {
+					return true;
+				}
+			}
 		}
 		return false;
 	}
 
-	private boolean hasLooseInSelfOrAlternate(AnyObjectId objectId) {
-		if (fileFor(objectId).exists())
+	private boolean hasLooseInSelfOrAlternate(AnyObjectId objectId,
+			Set<AlternateHandle.Id> skips) {
+		if (fileFor(objectId).exists()) {
 			return true;
+		}
+		skips = addMe(skips);
 		for (AlternateHandle alt : myAlternates()) {
-			if (alt.db.hasLooseInSelfOrAlternate(objectId))
-				return true;
+			if (!skips.contains(alt.getId())) {
+				if (alt.db.hasLooseInSelfOrAlternate(objectId, skips)) {
+					return true;
+				}
+			}
 		}
 		return false;
 	}
@@ -340,6 +355,12 @@
 	@Override
 	void resolve(Set<ObjectId> matches, AbbreviatedObjectId id)
 			throws IOException {
+		resolve(matches, id, null);
+	}
+
+	private void resolve(Set<ObjectId> matches, AbbreviatedObjectId id,
+			Set<AlternateHandle.Id> skips)
+			throws IOException {
 		// Go through the packs once. If we didn't find any resolutions
 		// scan for new packs and check once more.
 		int oldSize = matches.size();
@@ -376,10 +397,14 @@
 			}
 		}
 
+		skips = addMe(skips);
 		for (AlternateHandle alt : myAlternates()) {
-			alt.db.resolve(matches, id);
-			if (matches.size() > RESOLVE_ABBREV_LIMIT)
-				return;
+			if (!skips.contains(alt.getId())) {
+				alt.db.resolve(matches, id, skips);
+				if (matches.size() > RESOLVE_ABBREV_LIMIT) {
+					return;
+				}
+			}
 		}
 	}
 
@@ -388,37 +413,50 @@
 			throws IOException {
 		if (unpackedObjectCache.isUnpacked(objectId)) {
 			ObjectLoader ldr = openLooseObject(curs, objectId);
-			if (ldr != null)
+			if (ldr != null) {
 				return ldr;
+			}
 		}
-		ObjectLoader ldr = openPackedFromSelfOrAlternate(curs, objectId);
-		if (ldr != null)
+		ObjectLoader ldr = openPackedFromSelfOrAlternate(curs, objectId, null);
+		if (ldr != null) {
 			return ldr;
-		return openLooseFromSelfOrAlternate(curs, objectId);
+		}
+		return openLooseFromSelfOrAlternate(curs, objectId, null);
 	}
 
 	private ObjectLoader openPackedFromSelfOrAlternate(WindowCursor curs,
-			AnyObjectId objectId) {
+			AnyObjectId objectId, Set<AlternateHandle.Id> skips) {
 		ObjectLoader ldr = openPackedObject(curs, objectId);
-		if (ldr != null)
+		if (ldr != null) {
 			return ldr;
+		}
+		skips = addMe(skips);
 		for (AlternateHandle alt : myAlternates()) {
-			ldr = alt.db.openPackedFromSelfOrAlternate(curs, objectId);
-			if (ldr != null)
-				return ldr;
+			if (!skips.contains(alt.getId())) {
+				ldr = alt.db.openPackedFromSelfOrAlternate(curs, objectId, skips);
+				if (ldr != null) {
+					return ldr;
+				}
+			}
 		}
 		return null;
 	}
 
 	private ObjectLoader openLooseFromSelfOrAlternate(WindowCursor curs,
-			AnyObjectId objectId) throws IOException {
+			AnyObjectId objectId, Set<AlternateHandle.Id> skips)
+					throws IOException {
 		ObjectLoader ldr = openLooseObject(curs, objectId);
-		if (ldr != null)
+		if (ldr != null) {
 			return ldr;
+		}
+		skips = addMe(skips);
 		for (AlternateHandle alt : myAlternates()) {
-			ldr = alt.db.openLooseFromSelfOrAlternate(curs, objectId);
-			if (ldr != null)
-				return ldr;
+			if (!skips.contains(alt.getId())) {
+				ldr = alt.db.openLooseFromSelfOrAlternate(curs, objectId, skips);
+				if (ldr != null) {
+					return ldr;
+				}
+			}
 		}
 		return null;
 	}
@@ -469,37 +507,49 @@
 			throws IOException {
 		if (unpackedObjectCache.isUnpacked(id)) {
 			long len = getLooseObjectSize(curs, id);
-			if (0 <= len)
+			if (0 <= len) {
 				return len;
+			}
 		}
-		long len = getPackedSizeFromSelfOrAlternate(curs, id);
-		if (0 <= len)
+		long len = getPackedSizeFromSelfOrAlternate(curs, id, null);
+		if (0 <= len) {
 			return len;
-		return getLooseSizeFromSelfOrAlternate(curs, id);
+		}
+		return getLooseSizeFromSelfOrAlternate(curs, id, null);
 	}
 
 	private long getPackedSizeFromSelfOrAlternate(WindowCursor curs,
-			AnyObjectId id) {
+			AnyObjectId id, Set<AlternateHandle.Id> skips) {
 		long len = getPackedObjectSize(curs, id);
-		if (0 <= len)
+		if (0 <= len) {
 			return len;
+		}
+		skips = addMe(skips);
 		for (AlternateHandle alt : myAlternates()) {
-			len = alt.db.getPackedSizeFromSelfOrAlternate(curs, id);
-			if (0 <= len)
-				return len;
+			if (!skips.contains(alt.getId())) {
+				len = alt.db.getPackedSizeFromSelfOrAlternate(curs, id, skips);
+				if (0 <= len) {
+					return len;
+				}
+			}
 		}
 		return -1;
 	}
 
 	private long getLooseSizeFromSelfOrAlternate(WindowCursor curs,
-			AnyObjectId id) throws IOException {
+			AnyObjectId id, Set<AlternateHandle.Id> skips) throws IOException {
 		long len = getLooseObjectSize(curs, id);
-		if (0 <= len)
+		if (0 <= len) {
 			return len;
+		}
+		skips = addMe(skips);
 		for (AlternateHandle alt : myAlternates()) {
-			len = alt.db.getLooseSizeFromSelfOrAlternate(curs, id);
-			if (0 <= len)
-				return len;
+			if (!skips.contains(alt.getId())) {
+				len = alt.db.getLooseSizeFromSelfOrAlternate(curs, id, skips);
+				if (0 <= len) {
+					return len;
+				}
+			}
 		}
 		return -1;
 	}
@@ -546,7 +596,12 @@
 
 	@Override
 	void selectObjectRepresentation(PackWriter packer, ObjectToPack otp,
-			WindowCursor curs) throws IOException {
+																	WindowCursor curs) throws IOException {
+		selectObjectRepresentation(packer, otp, curs, null);
+	}
+
+	private void selectObjectRepresentation(PackWriter packer, ObjectToPack otp,
+			WindowCursor curs, Set<AlternateHandle.Id> skips) throws IOException {
 		PackList pList = packList.get();
 		SEARCH: for (;;) {
 			for (final PackFile p : pList.packs) {
@@ -567,8 +622,12 @@
 			break SEARCH;
 		}
 
-		for (AlternateHandle h : myAlternates())
-			h.db.selectObjectRepresentation(packer, otp, curs);
+		skips = addMe(skips);
+		for (AlternateHandle h : myAlternates()) {
+			if (!skips.contains(h.getId())) {
+				h.db.selectObjectRepresentation(packer, otp, curs, skips);
+			}
+		}
 	}
 
 	private void handlePackError(IOException e, PackFile p) {
@@ -930,6 +989,14 @@
 		return alt;
 	}
 
+	Set<AlternateHandle.Id> addMe(Set<AlternateHandle.Id> skips) {
+		if (skips == null) {
+			skips = new HashSet<>();
+		}
+		skips.add(handle.getId());
+		return skips;
+	}
+
 	private AlternateHandle[] loadAlternates() throws IOException {
 		final List<AlternateHandle> l = new ArrayList<>(4);
 		final BufferedReader br = open(alternatesFile);
@@ -996,6 +1063,38 @@
 	}
 
 	static class AlternateHandle {
+		static class Id {
+			String alternateId;
+
+			public Id(File object) {
+				try {
+					this.alternateId = object.getCanonicalPath();
+				} catch (Exception e) {
+					alternateId = null;
+				}
+			}
+
+			@Override
+			public boolean equals(Object o) {
+				if (o == this) {
+					return true;
+				}
+				if (o == null || !(o instanceof Id)) {
+					return false;
+				}
+				Id aId = (Id) o;
+				return Objects.equals(alternateId, aId.alternateId);
+			}
+
+			@Override
+			public int hashCode() {
+				if (alternateId == null) {
+					return 1;
+				}
+				return alternateId.hashCode();
+			}
+		}
+
 		final ObjectDirectory db;
 
 		AlternateHandle(ObjectDirectory db) {
@@ -1005,6 +1104,10 @@
 		void close() {
 			db.close();
 		}
+
+		public Id getId(){
+			return db.getAlternateId();
+		}
 	}
 
 	static class AlternateRepository extends AlternateHandle {
@@ -1029,4 +1132,8 @@
 	CachedObjectDirectory newCachedFileObjectDatabase() {
 		return new CachedObjectDirectory(this);
 	}
+
+	AlternateHandle.Id getAlternateId() {
+		return new AlternateHandle.Id(objects);
+	}
 }
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 93dbee3..7271560 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
@@ -233,7 +233,9 @@
 
 	private List<CachedPack> cachedPacks = new ArrayList<>(2);
 
-	private Set<ObjectId> tagTargets = Collections.emptySet();
+	private Set<ObjectId> tagTargets = NONE;
+
+	private Set<? extends ObjectId> excludeFromBitmapSelection = NONE;
 
 	private ObjectIdSet[] excludeInPacks;
 
@@ -712,8 +714,7 @@
 	public void preparePack(ProgressMonitor countingMonitor,
 			@NonNull Set<? extends ObjectId> want,
 			@NonNull Set<? extends ObjectId> have) throws IOException {
-		preparePack(countingMonitor,
-				want, have, Collections.<ObjectId> emptySet());
+		preparePack(countingMonitor, want, have, NONE, NONE);
 	}
 
 	/**
@@ -721,9 +722,9 @@
 	 * <p>
 	 * Like {@link #preparePack(ProgressMonitor, Set, Set)} but also allows
 	 * specifying commits that should not be walked past ("shallow" commits).
-	 * The caller is responsible for filtering out commits that should not
-	 * be shallow any more ("unshallow" commits as in {@link #setShallowPack})
-	 * from the shallow set.
+	 * The caller is responsible for filtering out commits that should not be
+	 * shallow any more ("unshallow" commits as in {@link #setShallowPack}) from
+	 * the shallow set.
 	 *
 	 * @param countingMonitor
 	 *            progress during object enumeration.
@@ -731,27 +732,67 @@
 	 *            objects of interest, ancestors of which will be included in
 	 *            the pack. Must not be {@code null}.
 	 * @param have
-	 *            objects whose ancestors (up to and including
-	 *            {@code shallow} commits) do not need to be included in the
-	 *            pack because they are already available from elsewhere.
-	 *            Must not be {@code null}.
+	 *            objects whose ancestors (up to and including {@code shallow}
+	 *            commits) do not need to be included in the pack because they
+	 *            are already available from elsewhere. Must not be
+	 *            {@code null}.
 	 * @param shallow
 	 *            commits indicating the boundary of the history marked with
-	 *            {@code have}. Shallow commits have parents but those
-	 *            parents are considered not to be already available.
-	 *            Parents of {@code shallow} commits and earlier generations
-	 *            will be included in the pack if requested by {@code want}.
-	 *            Must not be {@code null}.
+	 *            {@code have}. Shallow commits have parents but those parents
+	 *            are considered not to be already available. Parents of
+	 *            {@code shallow} commits and earlier generations will be
+	 *            included in the pack if requested by {@code want}. Must not be
+	 *            {@code null}.
 	 * @throws IOException
-	 *            an I/O problem occured while reading objects.
+	 *             an I/O problem occurred while reading objects.
 	 */
 	public void preparePack(ProgressMonitor countingMonitor,
 			@NonNull Set<? extends ObjectId> want,
 			@NonNull Set<? extends ObjectId> have,
 			@NonNull Set<? extends ObjectId> shallow) throws IOException {
+		preparePack(countingMonitor, want, have, shallow, NONE);
+	}
+
+	/**
+	 * Prepare the list of objects to be written to the pack stream.
+	 * <p>
+	 * Like {@link #preparePack(ProgressMonitor, Set, Set)} but also allows
+	 * specifying commits that should not be walked past ("shallow" commits).
+	 * The caller is responsible for filtering out commits that should not be
+	 * shallow any more ("unshallow" commits as in {@link #setShallowPack}) from
+	 * the shallow set.
+	 *
+	 * @param countingMonitor
+	 *            progress during object enumeration.
+	 * @param want
+	 *            objects of interest, ancestors of which will be included in
+	 *            the pack. Must not be {@code null}.
+	 * @param have
+	 *            objects whose ancestors (up to and including {@code shallow}
+	 *            commits) do not need to be included in the pack because they
+	 *            are already available from elsewhere. Must not be
+	 *            {@code null}.
+	 * @param shallow
+	 *            commits indicating the boundary of the history marked with
+	 *            {@code have}. Shallow commits have parents but those parents
+	 *            are considered not to be already available. Parents of
+	 *            {@code shallow} commits and earlier generations will be
+	 *            included in the pack if requested by {@code want}. Must not be
+	 *            {@code null}.
+	 * @param noBitmaps
+	 *            collection of objects to be excluded from bitmap commit
+	 *            selection.
+	 * @throws IOException
+	 *             an I/O problem occurred while reading objects.
+	 */
+	public void preparePack(ProgressMonitor countingMonitor,
+			@NonNull Set<? extends ObjectId> want,
+			@NonNull Set<? extends ObjectId> have,
+			@NonNull Set<? extends ObjectId> shallow,
+			@NonNull Set<? extends ObjectId> noBitmaps) throws IOException {
 		try (ObjectWalk ow = getObjectWalk()) {
 			ow.assumeShallow(shallow);
-			preparePack(countingMonitor, ow, want, have);
+			preparePack(countingMonitor, ow, want, have, noBitmaps);
 		}
 	}
 
@@ -784,13 +825,17 @@
 	 *            points of graph traversal). Pass {@link #NONE} if all objects
 	 *            reachable from {@code want} are desired, such as when serving
 	 *            a clone.
+	 * @param noBitmaps
+	 *            collection of objects to be excluded from bitmap commit
+	 *            selection.
 	 * @throws IOException
 	 *             when some I/O problem occur during reading objects.
 	 */
 	public void preparePack(ProgressMonitor countingMonitor,
 			@NonNull ObjectWalk walk,
 			@NonNull Set<? extends ObjectId> interestingObjects,
-			@NonNull Set<? extends ObjectId> uninterestingObjects)
+			@NonNull Set<? extends ObjectId> uninterestingObjects,
+			@NonNull Set<? extends ObjectId> noBitmaps)
 			throws IOException {
 		if (countingMonitor == null)
 			countingMonitor = NullProgressMonitor.INSTANCE;
@@ -798,7 +843,7 @@
 			throw new IllegalArgumentException(
 					JGitText.get().shallowPacksRequireDepthWalk);
 		findObjectsToPack(countingMonitor, walk, interestingObjects,
-				uninterestingObjects);
+				uninterestingObjects, noBitmaps);
 	}
 
 	/**
@@ -965,8 +1010,9 @@
 	/**
 	 * Write the prepared pack to the supplied stream.
 	 * <p>
-	 * Called after {@link #preparePack(ProgressMonitor, ObjectWalk, Set, Set)}
-	 * or {@link #preparePack(ProgressMonitor, Set, Set)}.
+	 * Called after
+	 * {@link #preparePack(ProgressMonitor, ObjectWalk, Set, Set, Set)} or
+	 * {@link #preparePack(ProgressMonitor, Set, Set)}.
 	 * <p>
 	 * Performs delta search if enabled and writes the pack stream.
 	 * <p>
@@ -1652,12 +1698,14 @@
 
 	private void findObjectsToPack(@NonNull ProgressMonitor countingMonitor,
 			@NonNull ObjectWalk walker, @NonNull Set<? extends ObjectId> want,
-			@NonNull Set<? extends ObjectId> have) throws IOException {
+			@NonNull Set<? extends ObjectId> have,
+			@NonNull Set<? extends ObjectId> noBitmaps) throws IOException {
 		final long countingStart = System.currentTimeMillis();
 		beginPhase(PackingPhase.COUNTING, countingMonitor, ProgressMonitor.UNKNOWN);
 
 		stats.interestingObjects = Collections.unmodifiableSet(new HashSet<ObjectId>(want));
 		stats.uninterestingObjects = Collections.unmodifiableSet(new HashSet<ObjectId>(have));
+		excludeFromBitmapSelection = noBitmaps;
 
 		canBuildBitmaps = config.isBuildBitmaps()
 				&& !shallowPack
@@ -1874,7 +1922,6 @@
 			throws MissingObjectException, IncorrectObjectTypeException,
 			IOException {
 		BitmapBuilder haveBitmap = bitmapWalker.findObjects(have, null, true);
-		bitmapWalker.reset();
 		BitmapBuilder wantBitmap = bitmapWalker.findObjects(want, haveBitmap,
 				false);
 		BitmapBuilder needBitmap = wantBitmap.andNot(haveBitmap);
@@ -2071,19 +2118,17 @@
 		PackWriterBitmapPreparer bitmapPreparer = new PackWriterBitmapPreparer(
 				reader, writeBitmaps, pm, stats.interestingObjects, config);
 
-		Collection<PackWriterBitmapPreparer.BitmapCommit> selectedCommits =
-				bitmapPreparer.selectCommits(numCommits);
+		Collection<PackWriterBitmapPreparer.BitmapCommit> selectedCommits = bitmapPreparer
+				.selectCommits(numCommits, excludeFromBitmapSelection);
 
 		beginPhase(PackingPhase.BUILDING_BITMAPS, pm, selectedCommits.size());
 
 		PackWriterBitmapWalker walker = bitmapPreparer.newBitmapWalker();
 		AnyObjectId last = null;
 		for (PackWriterBitmapPreparer.BitmapCommit cmit : selectedCommits) {
-			if (cmit.isReuseWalker())
-				walker.reset();
-			else
+			if (!cmit.isReuseWalker()) {
 				walker = bitmapPreparer.newBitmapWalker();
-
+			}
 			BitmapBuilder bitmap = walker.findObjects(
 					Collections.singleton(cmit), null, false);
 
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 07a03b4..8bedddb 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
@@ -141,6 +141,8 @@
 	 *
 	 * @param expectedCommitCount
 	 *            count of commits in the pack
+	 * @param excludeFromBitmapSelection
+	 *            commits that should be excluded from bitmap selection
 	 * @return commit objects for which bitmap indices should be built
 	 * @throws IncorrectObjectTypeException
 	 *             if any of the processed objects is not a commit
@@ -149,7 +151,8 @@
 	 * @throws MissingObjectException
 	 *             if an expected object is missing
 	 */
-	Collection<BitmapCommit> selectCommits(int expectedCommitCount)
+	Collection<BitmapCommit> selectCommits(int expectedCommitCount,
+			Set<? extends ObjectId> excludeFromBitmapSelection)
 			throws IncorrectObjectTypeException, IOException,
 			MissingObjectException {
 		/*
@@ -164,7 +167,7 @@
 		RevWalk rw = new RevWalk(reader);
 		rw.setRetainBody(false);
 		CommitSelectionHelper selectionHelper = setupTipCommitBitmaps(rw,
-				expectedCommitCount);
+				expectedCommitCount, excludeFromBitmapSelection);
 		pm.endTask();
 
 		int totCommits = selectionHelper.getCommitCount();
@@ -363,6 +366,8 @@
 	 * @param expectedCommitCount
 	 *            expected count of commits. The actual count may be less due to
 	 *            unreachable garbage.
+	 * @param excludeFromBitmapSelection
+	 *            commits that should be excluded from bitmap selection
 	 * @return a {@link CommitSelectionHelper} containing bitmaps for the tip
 	 *         commits
 	 * @throws IncorrectObjectTypeException
@@ -373,8 +378,10 @@
 	 *             if an expected object is missing
 	 */
 	private CommitSelectionHelper setupTipCommitBitmaps(RevWalk rw,
-			int expectedCommitCount) throws IncorrectObjectTypeException,
-					IOException, MissingObjectException {
+			int expectedCommitCount,
+			Set<? extends ObjectId> excludeFromBitmapSelection)
+			throws IncorrectObjectTypeException, IOException,
+			MissingObjectException {
 		BitmapBuilder reuse = commitBitmapIndex.newBitmapBuilder();
 		List<BitmapCommit> reuseCommits = new ArrayList<>();
 		for (PackBitmapIndexRemapper.Entry entry : bitmapRemapper) {
@@ -403,7 +410,8 @@
 		Set<RevCommit> peeledWant = new HashSet<>(want.size());
 		for (AnyObjectId objectId : want) {
 			RevObject ro = rw.peel(rw.parseAny(objectId));
-			if (!(ro instanceof RevCommit) || reuse.contains(ro)) {
+			if (!(ro instanceof RevCommit) || reuse.contains(ro)
+					|| excludeFromBitmapSelection.contains(ro)) {
 				continue;
 			}
 
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriterBitmapWalker.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriterBitmapWalker.java
index 2ec4d56..a5c3b71 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriterBitmapWalker.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriterBitmapWalker.java
@@ -44,7 +44,7 @@
 package org.eclipse.jgit.internal.storage.pack;
 
 import java.io.IOException;
-import java.util.Set;
+import java.util.Arrays;
 
 import org.eclipse.jgit.errors.IncorrectObjectTypeException;
 import org.eclipse.jgit.errors.MissingObjectException;
@@ -84,9 +84,60 @@
 		return countOfBitmapIndexMisses;
 	}
 
-	BitmapBuilder findObjects(Set<? extends ObjectId> start, BitmapBuilder seen, boolean ignoreMissingStart)
+	BitmapBuilder findObjects(Iterable<? extends ObjectId> start, BitmapBuilder seen,
+			boolean ignoreMissing)
+			throws MissingObjectException, IncorrectObjectTypeException,
+				   IOException {
+		if (!ignoreMissing) {
+			return findObjectsWalk(start, seen, false);
+		}
+
+		try {
+			return findObjectsWalk(start, seen, true);
+		} catch (MissingObjectException ignore) {
+			// An object reachable from one of the "start"s is missing.
+			// Walk from the "start"s one at a time so it can be excluded.
+		}
+
+		final BitmapBuilder result = bitmapIndex.newBitmapBuilder();
+		for (ObjectId obj : start) {
+			Bitmap bitmap = bitmapIndex.getBitmap(obj);
+			if (bitmap != null) {
+				result.or(bitmap);
+			}
+		}
+
+		for (ObjectId obj : start) {
+			if (result.contains(obj)) {
+				continue;
+			}
+			try {
+				result.or(findObjectsWalk(Arrays.asList(obj), result, false));
+			} catch (MissingObjectException ignore) {
+				// An object reachable from this "start" is missing.
+				//
+				// This can happen when the client specified a "have" line
+				// pointing to an object that is present but unreachable:
+				// "git prune" and "git fsck" only guarantee that the object
+				// database will continue to contain all objects reachable
+				// from a ref and does not guarantee connectivity for other
+				// objects in the object database.
+				//
+				// In this situation, skip the relevant "start" and move on
+				// to the next one.
+				//
+				// TODO(czhen): Make findObjectsWalk resume the walk instead
+				// once RevWalk and ObjectWalk support that.
+			}
+		}
+		return result;
+	}
+
+	private BitmapBuilder findObjectsWalk(Iterable<? extends ObjectId> start, BitmapBuilder seen,
+			boolean ignoreMissingStart)
 			throws MissingObjectException, IncorrectObjectTypeException,
 			IOException {
+		walker.reset();
 		final BitmapBuilder bitmapResult = bitmapIndex.newBitmapBuilder();
 
 		for (ObjectId obj : start) {
@@ -141,10 +192,6 @@
 		return bitmapResult;
 	}
 
-	void reset() {
-		walker.reset();
-	}
-
 	/**
 	 * A RevFilter that adds the visited commits to {@code bitmap} as a side
 	 * effect.
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BatchingProgressMonitor.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BatchingProgressMonitor.java
index 54c8052..a75293d 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BatchingProgressMonitor.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BatchingProgressMonitor.java
@@ -46,6 +46,8 @@
 import java.util.concurrent.Future;
 import java.util.concurrent.TimeUnit;
 
+import org.eclipse.jgit.lib.internal.WorkQueue;
+
 /** ProgressMonitor that batches update events. */
 public abstract class BatchingProgressMonitor implements ProgressMonitor {
 	private long delayStartTime;
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 1047a6d..c4923a3 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RebaseTodoFile.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RebaseTodoFile.java
@@ -43,6 +43,8 @@
 
 package org.eclipse.jgit.lib;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
+
 import java.io.BufferedOutputStream;
 import java.io.File;
 import java.io.FileOutputStream;
@@ -177,8 +179,8 @@
 		while (tokenCount < 3 && nextSpace < lineEnd) {
 			switch (tokenCount) {
 			case 0:
-				String actionToken = new String(buf, tokenBegin, nextSpace
-						- tokenBegin - 1);
+				String actionToken = new String(buf, tokenBegin,
+						nextSpace - tokenBegin - 1, UTF_8);
 				tokenBegin = nextSpace;
 				action = RebaseTodoLine.Action.parse(actionToken);
 				if (action == null)
@@ -186,14 +188,14 @@
 				break;
 			case 1:
 				nextSpace = RawParseUtils.next(buf, tokenBegin, ' ');
-				String commitToken = new String(buf, tokenBegin, nextSpace
-						- tokenBegin - 1);
+				String commitToken = new String(buf, tokenBegin,
+						nextSpace - tokenBegin - 1, UTF_8);
 				tokenBegin = nextSpace;
 				commit = AbbreviatedObjectId.fromString(commitToken);
 				break;
 			case 2:
-				return new RebaseTodoLine(action, commit, RawParseUtils.decode(
-						buf, tokenBegin, 1 + lineEnd));
+				return new RebaseTodoLine(action, commit,
+						RawParseUtils.decode(buf, tokenBegin, 1 + lineEnd));
 			}
 			tokenCount++;
 		}
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 ea37e79..1f2ab9d 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java
@@ -1149,6 +1149,33 @@
 	}
 
 	/**
+	 * Locate a reference to a commit and immediately parse its content.
+	 * <p>
+	 * This method only returns successfully if the commit object exists,
+	 * is verified to be a commit, and was parsed without error.
+	 *
+	 * @param id
+	 *            name of the commit object.
+	 * @return reference to the commit object. Never null.
+	 * @throws MissingObjectException
+	 *             the supplied commit does not exist.
+	 * @throws IncorrectObjectTypeException
+	 *             the supplied id is not a commit or an annotated tag.
+	 * @throws IOException
+	 *             a pack file or loose object could not be read.
+	 * @since 4.8
+	 */
+	public RevCommit parseCommit(AnyObjectId id) throws IncorrectObjectTypeException,
+			IOException, MissingObjectException {
+		if (id instanceof RevCommit && ((RevCommit) id).getRawBuffer() != null) {
+			return (RevCommit) id;
+		}
+		try (RevWalk walk = new RevWalk(this)) {
+			return walk.parseCommit(id);
+		}
+	}
+
+	/**
 	 * Create a new in-core index representation and read an index from disk.
 	 * <p>
 	 * The new index will be read before it is returned to the caller. Read
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryCache.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryCache.java
index baa5286..53e9fe3 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryCache.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryCache.java
@@ -55,6 +55,7 @@
 import org.eclipse.jgit.annotations.NonNull;
 import org.eclipse.jgit.errors.RepositoryNotFoundException;
 import org.eclipse.jgit.internal.storage.file.FileRepository;
+import org.eclipse.jgit.lib.internal.WorkQueue;
 import org.eclipse.jgit.util.FS;
 import org.eclipse.jgit.util.IO;
 import org.eclipse.jgit.util.RawParseUtils;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/SubmoduleConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/SubmoduleConfig.java
index 3126160..12f7b82 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/SubmoduleConfig.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/SubmoduleConfig.java
@@ -43,6 +43,10 @@
 
 package org.eclipse.jgit.lib;
 
+import java.util.Locale;
+
+import org.eclipse.jgit.util.StringUtils;
+
 /**
  * Submodule section of a Git configuration file.
  *
@@ -75,12 +79,17 @@
 
 		@Override
 		public String toConfigValue() {
-			return configValue;
+			return name().toLowerCase(Locale.ROOT).replace('_', '-');
 		}
 
 		@Override
 		public boolean matchConfigValue(String s) {
-			return configValue.equals(s);
+			if (StringUtils.isEmptyOrNull(s)) {
+				return false;
+			}
+			s = s.replace('-', '_');
+			return name().equalsIgnoreCase(s)
+					|| configValue.equalsIgnoreCase(s);
 		}
 	}
 }
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 7675fcc..c31c3c6 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,10 @@
 
 package org.eclipse.jgit.lib;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
+
 import java.io.IOException;
+import java.io.OutputStreamWriter;
 import java.io.PrintWriter;
 import java.io.Writer;
 
@@ -56,7 +59,7 @@
 
 	/** Initialize a new progress monitor. */
 	public TextProgressMonitor() {
-		this(new PrintWriter(System.err));
+		this(new PrintWriter(new OutputStreamWriter(System.err, UTF_8)));
 	}
 
 	/**
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/WorkQueue.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/internal/WorkQueue.java
similarity index 95%
rename from org.eclipse.jgit/src/org/eclipse/jgit/lib/WorkQueue.java
rename to org.eclipse.jgit/src/org/eclipse/jgit/lib/internal/WorkQueue.java
index 07b87f5..3303f47 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/WorkQueue.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/internal/WorkQueue.java
@@ -41,7 +41,7 @@
  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-package org.eclipse.jgit.lib;
+package org.eclipse.jgit.lib.internal;
 
 import java.util.concurrent.Executors;
 import java.util.concurrent.ScheduledThreadPoolExecutor;
@@ -50,7 +50,7 @@
 /**
  * Simple work queue to run tasks in the background
  */
-class WorkQueue {
+public class WorkQueue {
 	private static final ScheduledThreadPoolExecutor executor;
 
 	static final Object executorKiller;
@@ -94,7 +94,10 @@
 		};
 	}
 
-	static ScheduledThreadPoolExecutor getExecutor() {
+	/**
+	 * @return the WorkQueue's executor
+	 */
+	public static ScheduledThreadPoolExecutor getExecutor() {
 		return executor;
 	}
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeStrategy.java b/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeStrategy.java
index 656480e..af3d5ca 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeStrategy.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeStrategy.java
@@ -49,6 +49,8 @@
 import java.util.HashMap;
 
 import org.eclipse.jgit.internal.JGitText;
+import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.ObjectInserter;
 import org.eclipse.jgit.lib.Repository;
 
 /**
@@ -170,4 +172,20 @@
 	 * @return the new merge instance which implements this strategy.
 	 */
 	public abstract Merger newMerger(Repository db, boolean inCore);
+
+	/**
+	 * Create a new merge instance.
+	 * <p>
+	 * The merge will happen in memory, working folder will not be modified, in
+	 * case of a non-trivial merge that requires manual resolution, the merger
+	 * will fail.
+	 *
+	 * @param inserter
+	 *            inserter to write results back to.
+	 * @param config
+	 *            repo config for reading diff algorithm settings.
+	 * @return the new merge instance which implements this strategy.
+	 * @since 4.8
+	 */
+	public abstract Merger newMerger(ObjectInserter inserter, Config config);
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/merge/Merger.java b/org.eclipse.jgit/src/org/eclipse/jgit/merge/Merger.java
index bee2d03..0c4488c 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/Merger.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/Merger.java
@@ -47,6 +47,7 @@
 import java.io.IOException;
 import java.text.MessageFormat;
 
+import org.eclipse.jgit.annotations.Nullable;
 import org.eclipse.jgit.errors.IncorrectObjectTypeException;
 import org.eclipse.jgit.errors.NoMergeBaseException;
 import org.eclipse.jgit.errors.NoMergeBaseException.MergeBaseFailureReason;
@@ -70,7 +71,15 @@
  * Instance of a specific {@link MergeStrategy} for a single {@link Repository}.
  */
 public abstract class Merger {
-	/** The repository this merger operates on. */
+	/**
+	 * The repository this merger operates on.
+	 * <p>
+	 * Null if and only if the merger was constructed with {@link
+	 * #Merger(ObjectInserter)}. Callers that want to assume the repo is not null
+	 * (e.g. because of a previous check that the merger is not in-core) may use
+	 * {@link #nonNullRepo()}.
+	 */
+	@Nullable
 	protected final Repository db;
 
 	/** Reader to support {@link #walk} and other object loading. */
@@ -104,20 +113,55 @@
 	 *            the repository this merger will read and write data on.
 	 */
 	protected Merger(final Repository local) {
+		if (local == null) {
+			throw new NullPointerException(JGitText.get().repositoryIsRequired);
+		}
 		db = local;
-		inserter = db.newObjectInserter();
+		inserter = local.newObjectInserter();
 		reader = inserter.newReader();
 		walk = new RevWalk(reader);
 	}
 
 	/**
+	 * Create a new in-core merge instance from an inserter.
+	 *
+	 * @param oi
+	 *            the inserter to write objects to. Will be closed at the
+	 *            conclusion of {@code merge}, unless {@code flush} is false.
+	 * @since 4.8
+	 */
+	protected Merger(ObjectInserter oi) {
+		db = null;
+		inserter = oi;
+		reader = oi.newReader();
+		walk = new RevWalk(reader);
+	}
+
+	/**
 	 * @return the repository this merger operates on.
 	 */
+	@Nullable
 	public Repository getRepository() {
 		return db;
 	}
 
-	/** @return an object writer to create objects in {@link #getRepository()}. */
+	/**
+	 * @return non-null repository instance
+	 * @throws NullPointerException
+	 *             if the merger was constructed without a repository.
+	 * @since 4.8
+	 */
+	protected Repository nonNullRepo() {
+		if (db == null) {
+			throw new NullPointerException(JGitText.get().repositoryIsRequired);
+		}
+		return db;
+	}
+
+	/**
+	 * @return an object writer to create objects, writing objects to {@link
+	 * #getRepository()} (if a repository was provided).
+	 */
 	public ObjectInserter getObjectInserter() {
 		return inserter;
 	}
@@ -131,7 +175,9 @@
 	 *
 	 * @param oi
 	 *            the inserter instance to use. Must be associated with the
-	 *            repository instance returned by {@link #getRepository()}.
+	 *            repository instance returned by {@link #getRepository()} (if a
+	 *            repository was provided). Will be closed at the conclusion of
+	 *            {@code merge}, unless {@code flush} is false.
 	 */
 	public void setObjectInserter(ObjectInserter oi) {
 		walk.close();
@@ -173,9 +219,9 @@
 	 *
 	 * @since 3.5
 	 * @param flush
-	 *            whether to flush the underlying object inserter when finished to
-	 *            store any content-merged blobs and virtual merged bases; if
-	 *            false, callers are responsible for flushing.
+	 *            whether to flush and close the underlying object inserter when
+	 *            finished to store any content-merged blobs and virtual merged
+	 *            bases; if false, callers are responsible for flushing.
 	 * @param tips
 	 *            source trees to be combined together. The merge base is not
 	 *            included in this set.
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/merge/RecursiveMerger.java b/org.eclipse.jgit/src/org/eclipse/jgit/merge/RecursiveMerger.java
index f8e1998..1375cd3 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/RecursiveMerger.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/RecursiveMerger.java
@@ -61,7 +61,9 @@
 import org.eclipse.jgit.errors.NoMergeBaseException;
 import org.eclipse.jgit.internal.JGitText;
 import org.eclipse.jgit.lib.CommitBuilder;
+import org.eclipse.jgit.lib.Config;
 import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectInserter;
 import org.eclipse.jgit.lib.PersonIdent;
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.revwalk.RevCommit;
@@ -111,6 +113,17 @@
 	}
 
 	/**
+	 * Normal recursive merge, implies inCore.
+	 *
+	 * @param inserter
+	 * @param config
+	 * @since 4.8
+	 */
+	protected RecursiveMerger(ObjectInserter inserter, Config config) {
+		super(inserter, config);
+	}
+
+	/**
 	 * Get a single base commit for two given commits. If the two source commits
 	 * have more than one base commit recursively merge the base commits
 	 * together until you end up with a single base commit.
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 f667af2..1aac352 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java
@@ -44,6 +44,9 @@
  */
 package org.eclipse.jgit.merge;
 
+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;
 
@@ -79,9 +82,10 @@
 import org.eclipse.jgit.errors.IndexWriteException;
 import org.eclipse.jgit.errors.MissingObjectException;
 import org.eclipse.jgit.errors.NoWorkTreeException;
-import org.eclipse.jgit.lib.ConfigConstants;
+import org.eclipse.jgit.lib.Config;
 import org.eclipse.jgit.lib.FileMode;
 import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectInserter;
 import org.eclipse.jgit.lib.ObjectReader;
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.revwalk.RevTree;
@@ -266,18 +270,25 @@
 	 */
 	protected MergeAlgorithm mergeAlgorithm;
 
+	private static MergeAlgorithm getMergeAlgorithm(Config config) {
+		SupportedAlgorithm diffAlg = config.getEnum(
+				CONFIG_DIFF_SECTION, null, CONFIG_KEY_ALGORITHM,
+				HISTOGRAM);
+		return new MergeAlgorithm(DiffAlgorithm.getAlgorithm(diffAlg));
+	}
+
+	private static String[] defaultCommitNames() {
+		return new String[] { "BASE", "OURS", "THEIRS" }; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+	}
+
 	/**
 	 * @param local
 	 * @param inCore
 	 */
 	protected ResolveMerger(Repository local, boolean inCore) {
 		super(local);
-		SupportedAlgorithm diffAlg = local.getConfig().getEnum(
-				ConfigConstants.CONFIG_DIFF_SECTION, null,
-				ConfigConstants.CONFIG_KEY_ALGORITHM,
-				SupportedAlgorithm.HISTOGRAM);
-		mergeAlgorithm = new MergeAlgorithm(DiffAlgorithm.getAlgorithm(diffAlg));
-		commitNames = new String[] { "BASE", "OURS", "THEIRS" }; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+		mergeAlgorithm = getMergeAlgorithm(local.getConfig());
+		commitNames = defaultCommitNames();
 		this.inCore = inCore;
 
 		if (inCore) {
@@ -295,10 +306,24 @@
 		this(local, false);
 	}
 
+	/**
+	 * @param inserter
+	 * @param config
+	 * @since 4.8
+	 */
+	protected ResolveMerger(ObjectInserter inserter, Config config) {
+		super(inserter);
+		mergeAlgorithm = getMergeAlgorithm(config);
+		commitNames = defaultCommitNames();
+		inCore = true;
+		implicitDirCache = false;
+		dircache = DirCache.newInCore();
+	}
+
 	@Override
 	protected boolean mergeImpl() throws IOException {
 		if (implicitDirCache)
-			dircache = getRepository().lockDirCache();
+			dircache = nonNullRepo().lockDirCache();
 
 		try {
 			return mergeTrees(mergeBase(), sourceTrees[0], sourceTrees[1],
@@ -315,7 +340,7 @@
 		// of a non-empty directory, for which delete() would fail.
 		for (int i = toBeDeleted.size() - 1; i >= 0; i--) {
 			String fileName = toBeDeleted.get(i);
-			File f = new File(db.getWorkTree(), fileName);
+			File f = new File(nonNullRepo().getWorkTree(), fileName);
 			if (!f.delete())
 				if (!f.isDirectory())
 					failingPaths.put(fileName,
@@ -348,7 +373,7 @@
 			return;
 		}
 
-		DirCache dc = db.readDirCache();
+		DirCache dc = nonNullRepo().readDirCache();
 		Iterator<String> mpathsIt=modifiedFiles.iterator();
 		while(mpathsIt.hasNext()) {
 			String mpath=mpathsIt.next();
@@ -785,8 +810,8 @@
 	 */
 	private File writeMergedFile(MergeResult<RawText> result)
 			throws FileNotFoundException, IOException {
-		File workTree = db.getWorkTree();
-		FS fs = db.getFS();
+		File workTree = nonNullRepo().getWorkTree();
+		FS fs = nonNullRepo().getFS();
 		File of = new File(workTree, tw.getPathString());
 		File parentFolder = of.getParentFile();
 		if (!fs.exists(parentFolder))
@@ -802,7 +827,7 @@
 	private ObjectId insertMergeResult(MergeResult<RawText> result)
 			throws IOException {
 		TemporaryBuffer.LocalFile buf = new TemporaryBuffer.LocalFile(
-				db.getDirectory(), 10 << 20);
+				db != null ? nonNullRepo().getDirectory() : null, 10 << 20);
 		try {
 			new MergeFormatter().formatMerge(buf, result,
 					Arrays.asList(commitNames), CHARACTER_ENCODING);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/merge/StrategyOneSided.java b/org.eclipse.jgit/src/org/eclipse/jgit/merge/StrategyOneSided.java
index 12d6c6b..2224dbc 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/StrategyOneSided.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/StrategyOneSided.java
@@ -46,7 +46,9 @@
 
 import java.io.IOException;
 
+import org.eclipse.jgit.lib.Config;
 import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectInserter;
 import org.eclipse.jgit.lib.Repository;
 
 /**
@@ -89,6 +91,11 @@
 		return new OneSide(db, treeIndex);
 	}
 
+	@Override
+	public Merger newMerger(final ObjectInserter inserter, final Config config) {
+		return new OneSide(inserter, treeIndex);
+	}
+
 	static class OneSide extends Merger {
 		private final int treeIndex;
 
@@ -97,6 +104,11 @@
 			treeIndex = index;
 		}
 
+		protected OneSide(final ObjectInserter inserter, final int index) {
+			super(inserter);
+			treeIndex = index;
+		}
+
 		@Override
 		protected boolean mergeImpl() throws IOException {
 			return treeIndex < sourceTrees.length;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/merge/StrategyRecursive.java b/org.eclipse.jgit/src/org/eclipse/jgit/merge/StrategyRecursive.java
index 22e608e..56128dd 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/StrategyRecursive.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/StrategyRecursive.java
@@ -43,6 +43,8 @@
 
 package org.eclipse.jgit.merge;
 
+import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.ObjectInserter;
 import org.eclipse.jgit.lib.Repository;
 
 /**
@@ -63,6 +65,11 @@
 	}
 
 	@Override
+	public ThreeWayMerger newMerger(ObjectInserter inserter, Config config) {
+		return new RecursiveMerger(inserter, config);
+	}
+
+	@Override
 	public String getName() {
 		return "recursive"; //$NON-NLS-1$
 	}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/merge/StrategyResolve.java b/org.eclipse.jgit/src/org/eclipse/jgit/merge/StrategyResolve.java
index 07368e5..17044b5 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/StrategyResolve.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/StrategyResolve.java
@@ -43,6 +43,8 @@
  */
 package org.eclipse.jgit.merge;
 
+import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.ObjectInserter;
 import org.eclipse.jgit.lib.Repository;
 
 /**
@@ -60,8 +62,16 @@
 		return new ResolveMerger(db, inCore);
 	}
 
+	/**
+	 * @since 4.8
+	 */
+	@Override
+	public ThreeWayMerger newMerger(ObjectInserter inserter, Config config) {
+		return new ResolveMerger(inserter, config);
+	}
+
 	@Override
 	public String getName() {
 		return "resolve"; //$NON-NLS-1$
 	}
-}
\ No newline at end of file
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/merge/StrategySimpleTwoWayInCore.java b/org.eclipse.jgit/src/org/eclipse/jgit/merge/StrategySimpleTwoWayInCore.java
index ec903c1..cd427bd 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/StrategySimpleTwoWayInCore.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/StrategySimpleTwoWayInCore.java
@@ -49,6 +49,7 @@
 import org.eclipse.jgit.dircache.DirCacheBuilder;
 import org.eclipse.jgit.dircache.DirCacheEntry;
 import org.eclipse.jgit.errors.UnmergedPathException;
+import org.eclipse.jgit.lib.Config;
 import org.eclipse.jgit.lib.FileMode;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.ObjectInserter;
@@ -89,6 +90,14 @@
 		return newMerger(db);
 	}
 
+	/**
+	 * @since 4.8
+	 */
+	@Override
+	public ThreeWayMerger newMerger(ObjectInserter inserter, Config config) {
+		return new InCoreMerger(inserter);
+	}
+
 	private static class InCoreMerger extends ThreeWayMerger {
 		private static final int T_BASE = 0;
 
@@ -110,6 +119,12 @@
 			cache = DirCache.newInCore();
 		}
 
+		InCoreMerger(final ObjectInserter inserter) {
+			super(inserter);
+			tw = new NameConflictTreeWalk(null, reader);
+			cache = DirCache.newInCore();
+		}
+
 		@Override
 		protected boolean mergeImpl() throws IOException {
 			tw.addTree(mergeBase());
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/merge/ThreeWayMerger.java b/org.eclipse.jgit/src/org/eclipse/jgit/merge/ThreeWayMerger.java
index fbedaef..b3ef0fb 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/ThreeWayMerger.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/ThreeWayMerger.java
@@ -50,6 +50,7 @@
 import org.eclipse.jgit.errors.MissingObjectException;
 import org.eclipse.jgit.lib.AnyObjectId;
 import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectInserter;
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.revwalk.RevCommit;
 import org.eclipse.jgit.revwalk.RevTree;
@@ -85,6 +86,17 @@
 	}
 
 	/**
+	 * Create a new in-core merge instance from an inserter.
+	 *
+	 * @param inserter
+	 *            the inserter to write objects to.
+	 * @since 4.8
+	 */
+	protected ThreeWayMerger(ObjectInserter inserter) {
+		super(inserter);
+	}
+
+	/**
 	 * Set the common ancestor tree.
 	 *
 	 * @param id
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/MergeBaseGenerator.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/MergeBaseGenerator.java
index 5d7e72d..73ce985 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/MergeBaseGenerator.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/MergeBaseGenerator.java
@@ -69,26 +69,21 @@
  */
 class MergeBaseGenerator extends Generator {
 	private static final int PARSED = RevWalk.PARSED;
-
 	private static final int IN_PENDING = RevWalk.SEEN;
-
 	private static final int POPPED = RevWalk.TEMP_MARK;
-
 	private static final int MERGE_BASE = RevWalk.REWRITE;
 
 	private final RevWalk walker;
-
 	private final DateRevQueue pending;
 
 	private int branchMask;
-
 	private int recarryTest;
-
 	private int recarryMask;
-
 	private int mergeBaseAncestor = -1;
 	private LinkedList<RevCommit> ret = new LinkedList<>();
 
+	private CarryStack stack;
+
 	MergeBaseGenerator(final RevWalk w) {
 		walker = w;
 		pending = new DateRevQueue();
@@ -202,29 +197,56 @@
 		return null;
 	}
 
-	private void carryOntoHistory(RevCommit c, final int carry) {
+	private void carryOntoHistory(RevCommit c, int carry) {
+		stack = null;
 		for (;;) {
-			final RevCommit[] pList = c.parents;
-			if (pList == null)
-				return;
-			final int n = pList.length;
-			if (n == 0)
-				return;
-
-			for (int i = 1; i < n; i++) {
-				final RevCommit p = pList[i];
-				if (!carryOntoOne(p, carry))
-					carryOntoHistory(p, carry);
-			}
-
-			c = pList[0];
-			if (carryOntoOne(c, carry))
+			carryOntoHistoryInnerLoop(c, carry);
+			if (stack == null) {
 				break;
+			}
+			c = stack.c;
+			carry = stack.carry;
+			stack = stack.prev;
 		}
 	}
 
-	private boolean carryOntoOne(final RevCommit p, final int carry) {
-		final boolean haveAll = (p.flags & carry) == carry;
+	private void carryOntoHistoryInnerLoop(RevCommit c, int carry) {
+		for (;;) {
+			RevCommit[] parents = c.parents;
+			if (parents == null || parents.length == 0) {
+				break;
+			}
+
+			int e = parents.length - 1;
+			for (int i = 0; i < e; i++) {
+				RevCommit p = parents[i];
+				if (carryOntoOne(p, carry) == CONTINUE) {
+					// Walking p will be required, buffer p on stack.
+					stack = new CarryStack(stack, p, carry);
+				}
+				// For other results from carryOntoOne:
+				// HAVE_ALL: p has all bits, do nothing to skip that path.
+				// CONTINUE_ON_STACK: callee pushed StackElement for p.
+			}
+
+			c = parents[e];
+			if (carryOntoOne(c, carry) != CONTINUE) {
+				break;
+			}
+		}
+	}
+
+	private static final int CONTINUE = 0;
+	private static final int HAVE_ALL = 1;
+	private static final int CONTINUE_ON_STACK = 2;
+
+	private int carryOntoOne(RevCommit p, int carry) {
+		// If we already had all carried flags, our parents do too.
+		// Return HAVE_ALL to stop caller from running down this leg
+		// of the revision graph any further.
+		//
+		// Otherwise return CONTINUE to ask the caller to walk history.
+		int rc = (p.flags & carry) == carry ? HAVE_ALL : CONTINUE;
 		p.flags |= carry;
 
 		if ((p.flags & recarryMask) == recarryTest) {
@@ -232,17 +254,23 @@
 			// voted to be one. Inject ourselves back at the front of the
 			// pending queue and tell all of our ancestors they are within
 			// the merge base now.
-			//
 			p.flags &= ~POPPED;
 			pending.add(p);
-			carryOntoHistory(p, branchMask | MERGE_BASE);
-			return true;
+			stack = new CarryStack(stack, p, branchMask | MERGE_BASE);
+			return CONTINUE_ON_STACK;
 		}
+		return rc;
+	}
 
-		// If we already had all carried flags, our parents do too.
-		// Return true to stop the caller from running down this leg
-		// of the revision graph any further.
-		//
-		return haveAll;
+	private static class CarryStack {
+		final CarryStack prev;
+		final RevCommit c;
+		final int carry;
+
+		CarryStack(CarryStack prev, RevCommit c, int carry) {
+			this.prev = prev;
+			this.c = c;
+			this.carry = carry;
+		}
 	}
 }
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 37d70e3..0920f21 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleWriter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleWriter.java
@@ -58,6 +58,7 @@
 import org.eclipse.jgit.lib.AnyObjectId;
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectReader;
 import org.eclipse.jgit.lib.ProgressMonitor;
 import org.eclipse.jgit.lib.Ref;
 import org.eclipse.jgit.lib.Repository;
@@ -84,6 +85,8 @@
 public class BundleWriter {
 	private final Repository db;
 
+	private final ObjectReader reader;
+
 	private final Map<String, ObjectId> include;
 
 	private final Set<RevCommit> assume;
@@ -100,8 +103,26 @@
 	 * @param repo
 	 *            repository where objects are stored.
 	 */
-	public BundleWriter(final Repository repo) {
+	public BundleWriter(Repository repo) {
 		db = repo;
+		reader = null;
+		include = new TreeMap<>();
+		assume = new HashSet<>();
+		tagTargets = new HashSet<>();
+	}
+
+	/**
+	 * Create a writer for a bundle.
+	 *
+	 * @param or
+	 *            reader for reading objects. Will be closed at the end of {@link
+	 *            #writeBundle(ProgressMonitor, OutputStream)}, but readers may be
+	 *            reused after closing.
+	 * @since 4.8
+	 */
+	public BundleWriter(ObjectReader or) {
+		db = null;
+		reader = or;
 		include = new TreeMap<>();
 		assume = new HashSet<>();
 		tagTargets = new HashSet<>();
@@ -112,7 +133,8 @@
 	 *
 	 * @param pc
 	 *            configuration controlling packing parameters. If null the
-	 *            source repository's settings will be used.
+	 *            source repository's settings will be used, or the default
+	 *            settings if constructed without a repo.
 	 */
 	public void setPackConfig(PackConfig pc) {
 		this.packConfig = pc;
@@ -196,10 +218,7 @@
 	 */
 	public void writeBundle(ProgressMonitor monitor, OutputStream os)
 			throws IOException {
-		PackConfig pc = packConfig;
-		if (pc == null)
-			pc = new PackConfig(db);
-		try (PackWriter packWriter = new PackWriter(pc, db.newObjectReader())) {
+		try (PackWriter packWriter = newPackWriter()) {
 			packWriter.setObjectCountCallback(callback);
 
 			final HashSet<ObjectId> inc = new HashSet<>();
@@ -242,6 +261,14 @@
 		}
 	}
 
+	private PackWriter newPackWriter() {
+		PackConfig pc = packConfig;
+		if (pc == null) {
+			pc = db != null ? new PackConfig(db) : new PackConfig();
+		}
+		return new PackWriter(pc, reader != null ? reader : db.newObjectReader());
+	}
+
 	/**
 	 * Set the {@link ObjectCountCallback}.
 	 * <p>
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 58fdd25..17af0b9 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java
@@ -58,6 +58,7 @@
 import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_SIDE_BAND_64K;
 import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_THIN_PACK;
 
+import java.io.ByteArrayOutputStream;
 import java.io.EOFException;
 import java.io.IOException;
 import java.io.InputStream;
@@ -235,7 +236,7 @@
 
 	private InputStream rawIn;
 
-	private OutputStream rawOut;
+	private ResponseBufferedOutputStream rawOut;
 
 	private PacketLineIn pckIn;
 
@@ -644,11 +645,10 @@
 	 *            other network connections this should be null.
 	 * @throws IOException
 	 */
-	public void upload(final InputStream input, final OutputStream output,
+	public void upload(final InputStream input, OutputStream output,
 			final OutputStream messages) throws IOException {
 		try {
 			rawIn = input;
-			rawOut = output;
 			if (messages != null)
 				msgOut = messages;
 
@@ -656,11 +656,17 @@
 				final Thread caller = Thread.currentThread();
 				timer = new InterruptTimer(caller.getName() + "-Timer"); //$NON-NLS-1$
 				TimeoutInputStream i = new TimeoutInputStream(rawIn, timer);
-				TimeoutOutputStream o = new TimeoutOutputStream(rawOut, timer);
+				@SuppressWarnings("resource")
+				TimeoutOutputStream o = new TimeoutOutputStream(output, timer);
 				i.setTimeout(timeout * 1000);
 				o.setTimeout(timeout * 1000);
 				rawIn = i;
-				rawOut = o;
+				output = o;
+			}
+
+			rawOut = new ResponseBufferedOutputStream(output);
+			if (biDirectionalPipe) {
+				rawOut.stopBuffering();
 			}
 
 			pckIn = new PacketLineIn(rawIn);
@@ -714,6 +720,8 @@
 
 	private void service() throws IOException {
 		boolean sendPack;
+		// If it's a non-bidi request, we need to read the entire request before
+		// writing a response. Buffer the response until then.
 		try {
 			if (biDirectionalPipe)
 				sendAdvertisedRefs(new PacketLineOutRefAdvertiser(pckOut));
@@ -769,6 +777,8 @@
 				throw new UploadPackInternalServerErrorException(err);
 			}
 			throw err;
+		} finally {
+			rawOut.stopBuffering();
 		}
 
 		if (sendPack)
@@ -1513,7 +1523,7 @@
 				walk.reset();
 
 				ObjectWalk ow = rw.toObjectWalkWithSameObjects();
-				pw.preparePack(pm, ow, wantAll, commonBase);
+				pw.preparePack(pm, ow, wantAll, commonBase, PackWriter.NONE);
 				rw = ow;
 			}
 
@@ -1572,4 +1582,47 @@
 			adv.addSymref(Constants.HEAD, head.getLeaf().getName());
 		}
 	}
+
+	private static class ResponseBufferedOutputStream extends OutputStream {
+		private final OutputStream rawOut;
+
+		private OutputStream out;
+
+		ResponseBufferedOutputStream(OutputStream rawOut) {
+			this.rawOut = rawOut;
+			this.out = new ByteArrayOutputStream();
+		}
+
+		@Override
+		public void write(int b) throws IOException {
+			out.write(b);
+		}
+
+		@Override
+		public void write(byte b[]) throws IOException {
+			out.write(b);
+		}
+
+		@Override
+		public void write(byte b[], int off, int len) throws IOException {
+			out.write(b, off, len);
+		}
+
+		@Override
+		public void flush() throws IOException {
+			out.flush();
+		}
+
+		@Override
+		public void close() throws IOException {
+			out.close();
+		}
+
+		void stopBuffering() throws IOException {
+			if (out != rawOut) {
+				((ByteArrayOutputStream) out).writeTo(rawOut);
+				out = rawOut;
+			}
+		}
+	}
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/NameConflictTreeWalk.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/NameConflictTreeWalk.java
index c0b29ef..59cf798 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/NameConflictTreeWalk.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/NameConflictTreeWalk.java
@@ -45,6 +45,7 @@
 
 import java.io.IOException;
 
+import org.eclipse.jgit.annotations.Nullable;
 import org.eclipse.jgit.dircache.DirCacheBuilder;
 import org.eclipse.jgit.errors.CorruptObjectException;
 import org.eclipse.jgit.lib.FileMode;
@@ -110,7 +111,7 @@
 	 *            the reader the walker will obtain tree data from.
 	 * @since 4.3
 	 */
-	public NameConflictTreeWalk(Repository repo, final ObjectReader or) {
+	public NameConflictTreeWalk(@Nullable Repository repo, final ObjectReader or) {
 		super(repo, or);
 	}
 
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 68b7130..1cc39bd 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java
@@ -497,7 +497,13 @@
 			if (env != null) {
 				pb.environment().putAll(env);
 			}
-			Process p = pb.start();
+			Process p;
+			try {
+				p = pb.start();
+			} catch (IOException e) {
+				// Process failed to start
+				throw new CommandFailedException(-1, e.getMessage(), e);
+			}
 			p.getOutputStream().close();
 			GobblerThread gobbler = new GobblerThread(p, command, dir);
 			gobbler.start();
@@ -777,7 +783,7 @@
 	}
 
 	/**
-	 * See {@link FileUtils#relativize(String, String)}.
+	 * See {@link FileUtils#relativizePath(String, String, String, boolean)}.
 	 *
 	 * @param base
 	 *            The path against which <code>other</code> should be
@@ -786,11 +792,11 @@
 	 *            The path that will be made relative to <code>base</code>.
 	 * @return A relative path that, when resolved against <code>base</code>,
 	 *         will yield the original <code>other</code>.
-	 * @see FileUtils#relativize(String, String)
+	 * @see FileUtils#relativizePath(String, String, String, boolean)
 	 * @since 3.7
 	 */
 	public String relativize(String base, String other) {
-		return FileUtils.relativize(base, other);
+		return FileUtils.relativizePath(base, other, File.separator, this.isCaseSensitive());
 	}
 
 	/**
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 1f20e97..76dbb87 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/FileUtils.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/FileUtils.java
@@ -468,10 +468,71 @@
 		throw new IOException(JGitText.get().cannotCreateTempDir);
 	}
 
+
 	/**
-	 * This will try and make a given path relative to another.
+	 * @deprecated Use the more-clearly-named
+	 *             {@link FileUtils#relativizeNativePath(String, String)}
+	 *             instead, or directly call
+	 *             {@link FileUtils#relativizePath(String, String, String, boolean)}
+	 *
+	 *             Expresses <code>other</code> as a relative file path from
+	 *             <code>base</code>. File-separator and case sensitivity are
+	 *             based on the current file system.
+	 *
+	 *             See also
+	 *             {@link FileUtils#relativizePath(String, String, String, boolean)}.
+	 *
+	 * @param base
+	 *            Base path
+	 * @param other
+	 *            Destination path
+	 * @return Relative path from <code>base</code> to <code>other</code>
+	 * @since 3.7
+	 */
+	@Deprecated
+	public static String relativize(String base, String other) {
+		return relativizeNativePath(base, other);
+	}
+
+	/**
+	 * Expresses <code>other</code> as a relative file path from <code>base</code>.
+	 * File-separator and case sensitivity are based on the current file system.
+	 *
+	 * See also {@link FileUtils#relativizePath(String, String, String, boolean)}.
+	 *
+	 * @param base
+	 *            Base path
+	 * @param other
+	 *             Destination path
+	 * @return Relative path from <code>base</code> to <code>other</code>
+	 * @since 4.8
+	 */
+	public static String relativizeNativePath(String base, String other) {
+		return FS.DETECTED.relativize(base, other);
+	}
+
+	/**
+	 * Expresses <code>other</code> as a relative file path from <code>base</code>.
+	 * File-separator and case sensitivity are based on Git's internal representation of files (which matches Unix).
+	 *
+	 * See also {@link FileUtils#relativizePath(String, String, String, boolean)}.
+	 *
+	 * @param base
+	 *            Base path
+	 * @param other
+	 *             Destination path
+	 * @return Relative path from <code>base</code> to <code>other</code>
+	 * @since 4.8
+	 */
+	public static String relativizeGitPath(String base, String other) {
+		return relativizePath(base, other, "/", false); //$NON-NLS-1$
+	}
+
+
+	/**
+	 * Expresses <code>other</code> as a relative file path from <code>base</code>
 	 * <p>
-	 * For example, if this is called with the two following paths :
+	 * For example, if called with the two following paths :
 	 *
 	 * <pre>
 	 * <code>base = "c:\\Users\\jdoe\\eclipse\\git\\project"</code>
@@ -480,9 +541,7 @@
 	 *
 	 * This will return "..\\another_project\\pom.xml".
 	 * </p>
-	 * <p>
-	 * This method uses {@link File#separator} to split the paths into segments.
-	 * </p>
+	 *
 	 * <p>
 	 * <b>Note</b> that this will return the empty String if <code>base</code>
 	 * and <code>other</code> are equal.
@@ -494,41 +553,44 @@
 	 *            folder and not a file.
 	 * @param other
 	 *            The path that will be made relative to <code>base</code>.
+	 * @param dirSeparator
+	 *            A string that separates components of the path. In practice, this is "/" or "\\".
+	 * @param caseSensitive
+	 *            Whether to consider differently-cased directory names as distinct
 	 * @return A relative path that, when resolved against <code>base</code>,
 	 *         will yield the original <code>other</code>.
-	 * @since 3.7
+	 * @since 4.8
 	 */
-	public static String relativize(String base, String other) {
+	public static String relativizePath(String base, String other, String dirSeparator, boolean caseSensitive) {
 		if (base.equals(other))
 			return ""; //$NON-NLS-1$
 
-		final boolean ignoreCase = !FS.DETECTED.isCaseSensitive();
-		final String[] baseSegments = base.split(Pattern.quote(File.separator));
+		final String[] baseSegments = base.split(Pattern.quote(dirSeparator));
 		final String[] otherSegments = other.split(Pattern
-				.quote(File.separator));
+				.quote(dirSeparator));
 
 		int commonPrefix = 0;
 		while (commonPrefix < baseSegments.length
 				&& commonPrefix < otherSegments.length) {
-			if (ignoreCase
+			if (caseSensitive
+					&& baseSegments[commonPrefix]
+					.equals(otherSegments[commonPrefix]))
+				commonPrefix++;
+			else if (!caseSensitive
 					&& baseSegments[commonPrefix]
 							.equalsIgnoreCase(otherSegments[commonPrefix]))
 				commonPrefix++;
-			else if (!ignoreCase
-					&& baseSegments[commonPrefix]
-							.equals(otherSegments[commonPrefix]))
-				commonPrefix++;
 			else
 				break;
 		}
 
 		final StringBuilder builder = new StringBuilder();
 		for (int i = commonPrefix; i < baseSegments.length; i++)
-			builder.append("..").append(File.separator); //$NON-NLS-1$
+			builder.append("..").append(dirSeparator); //$NON-NLS-1$
 		for (int i = commonPrefix; i < otherSegments.length; i++) {
 			builder.append(otherSegments[i]);
 			if (i < otherSegments.length - 1)
-				builder.append(File.separator);
+				builder.append(dirSeparator);
 		}
 		return builder.toString();
 	}
diff --git a/pom.xml b/pom.xml
index 76ea009..b74de4e 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>4.7.2-SNAPSHOT</version>
+  <version>4.8.1-SNAPSHOT</version>
 
   <name>JGit - Parent</name>
   <url>${jgit-url}</url>
@@ -70,6 +70,10 @@
     <connection>scm:git:https://git.eclipse.org/r/jgit/jgit</connection>
   </scm>
 
+  <prerequisites>
+    <maven>3.3.1</maven>
+  </prerequisites>
+
   <ciManagement>
     <system>hudson</system>
     <url>https://hudson.eclipse.org/jgit/</url>
@@ -191,7 +195,7 @@
     <maven.build.timestamp.format>yyyyMMddHHmm</maven.build.timestamp.format>
     <bundle-manifest>${project.build.directory}/META-INF/MANIFEST.MF</bundle-manifest>
 
-    <jgit-last-release-version>4.6.0.201612231935-r</jgit-last-release-version>
+    <jgit-last-release-version>4.7.0.201704051617-r</jgit-last-release-version>
     <jsch-version>0.1.54</jsch-version>
     <javaewah-version>1.1.6</javaewah-version>
     <junit-version>4.12</junit-version>
@@ -200,15 +204,16 @@
     <commons-compress-version>1.6</commons-compress-version>
     <osgi-core-version>4.3.1</osgi-core-version>
     <servlet-api-version>3.1.0</servlet-api-version>
-    <jetty-version>9.3.17.v20170317</jetty-version>
-    <japicmp-version>0.5.3</japicmp-version>
+    <jetty-version>9.4.5.v20170502</jetty-version>
+    <japicmp-version>0.10.0</japicmp-version>
     <httpclient-version>4.3.6</httpclient-version>
     <slf4j-version>1.7.2</slf4j-version>
     <log4j-version>1.2.15</log4j-version>
     <maven-javadoc-plugin-version>2.10.4</maven-javadoc-plugin-version>
-    <tycho-extras-version>0.26.0</tycho-extras-version>
+    <tycho-extras-version>1.0.0</tycho-extras-version>
     <gson-version>2.2.4</gson-version>
     <findbugs-maven-plugin-version>3.0.4</findbugs-maven-plugin-version>
+    <maven-surefire-report-plugin-version>2.20</maven-surefire-report-plugin-version>
 
     <!-- Properties to enable jacoco code coverage analysis -->
     <sonar.core.codeCoveragePlugin>jacoco</sonar.core.codeCoveragePlugin>
@@ -352,7 +357,7 @@
         <plugin>
           <groupId>org.apache.maven.plugins</groupId>
           <artifactId>maven-surefire-plugin</artifactId>
-          <version>2.19.1</version>
+          <version>2.20</version>
           <configuration>
             <forkCount>${test-fork-count}</forkCount>
             <reuseForks>true</reuseForks>
@@ -385,13 +390,16 @@
         <plugin>
           <groupId>org.apache.maven.plugins</groupId>
           <artifactId>maven-pmd-plugin</artifactId>
-          <version>3.7</version>
+          <version>3.8</version>
           <configuration>
             <sourceEncoding>utf-8</sourceEncoding>
             <minimumTokens>100</minimumTokens>
             <targetJdk>1.8</targetJdk>
             <format>xml</format>
             <failOnViolation>false</failOnViolation>
+            <excludes>
+              <exclude>**/UbcCheck.java</exclude>
+            </excludes>
           </configuration>
           <executions>
             <execution>
@@ -405,7 +413,7 @@
         <plugin>
           <groupId>org.eclipse.cbi.maven.plugins</groupId>
           <artifactId>eclipse-jarsigner-plugin</artifactId>
-          <version>1.1.3</version>
+          <version>1.1.4</version>
         </plugin>
         <plugin>
           <groupId>org.eclipse.tycho.extras</groupId>
@@ -430,14 +438,14 @@
             <dependency><!-- add support for ssh/scp -->
               <groupId>org.apache.maven.wagon</groupId>
               <artifactId>wagon-ssh</artifactId>
-              <version>2.10</version>
+              <version>2.12</version>
             </dependency>
           </dependencies>
         </plugin>
         <plugin>
           <groupId>org.apache.maven.plugins</groupId>
           <artifactId>maven-surefire-report-plugin</artifactId>
-          <version>2.19.1</version>
+          <version>${maven-surefire-report-plugin-version}</version>
         </plugin>
         <plugin>
           <groupId>org.apache.maven.plugins</groupId>
@@ -578,7 +586,7 @@
       <plugin>
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-surefire-report-plugin</artifactId>
-        <version>2.19.1</version>
+        <version>${maven-surefire-report-plugin-version}</version>
         <configuration>
           <aggregate>true</aggregate>
           <alwaysGenerateSurefireReport>false</alwaysGenerateSurefireReport>
diff --git a/tools/version.sh b/tools/version.sh
index 81ffe06..e5c98ec 100755
--- a/tools/version.sh
+++ b/tools/version.sh
@@ -131,6 +131,7 @@
 		$seen_version = 1 if (!/<\?xml/ &&
 		s/(version=")[^"]*(")/${1}'"$OSGI_V"'${2}/);
 	}
+	s/(import feature="org\.eclipse\.jgit.*" version=")[^"]*(")/${1}'"$API_V"'${2}/;
 	' org.eclipse.jgit.packaging/org.*.feature/feature.xml
 
 perl -pi~ -e '