Merge branch 'stable-4.4' into stable-4.5

* stable-4.4:
  Prepare 4.4.2-SNAPSHOT builds
  JGit v4.0.3.201509231615-r

Change-Id: Icd66a796b0cce93c75a52cc77fec8f9df3eeccb4
Signed-off-by: David Pursehouse <david.pursehouse@gmail.com>
diff --git a/.mailmap b/.mailmap
index 8f11a1c..ab4c963 100644
--- a/.mailmap
+++ b/.mailmap
@@ -1,3 +1,4 @@
+Mark Ingram <markdingram@gmail.com>          markdingram <markdingram@gmail.com>
 Roberto Tyley <roberto.tyley@guardian.co.uk> roberto <roberto.tyley@guardian.co.uk>
 Saša Živkov <sasa.zivkov@sap.com>            Sasa Zivkov <sasa.zivkov@sap.com>
 Saša Živkov <sasa.zivkov@sap.com>            Saša Živkov <zivkov@gmail.com>
diff --git a/README.md b/README.md
index 333fa28..9aca10f 100644
--- a/README.md
+++ b/README.md
@@ -18,10 +18,6 @@
     All portions of JGit are covered by the EDL. Absolutely no GPL,
     LGPL or EPL contributions are accepted within this package.
 
-- org.eclipse.jgit.java7
-
-    Extensions for users of Java 7.
-
 - org.eclipse.jgit.ant
 
     Ant tasks based on JGit.
@@ -59,10 +55,6 @@
 
     Unit tests for org.eclipse.jgit
 
-- org.eclipse.jgit.java7.test
-
-    Unit tests for Java 7 specific features
-
 - org.eclipse.jgit.ant.test
 - org.eclipse.jgit.pgm.test
 - org.eclipse.jgit.http.test
@@ -73,11 +65,9 @@
 Warnings/Caveats
 ----------------
 
-- Native smbolic links are supported, but only if you are using Java 7
-  or newer and include the org.eclipse.jgit.java7 jar/bundle in the
-  classpath, provided the file system supports them. For Windows you
-  must have Windows Vista/Windows 2008 or newer, use a
-  non-administrator account and have the SeCreateSymbolicLinkPrivilege.
+- Native smbolic links are supported, provided the file system supports
+  them. For Windows you must have Windows Vista/Windows 2008 or newer,
+  use a non-administrator account and have the SeCreateSymbolicLinkPrivilege.
 
 - Only the timestamp of the index is used by jgit if the index is
   dirty.
@@ -153,12 +143,6 @@
     * Assorted set of command line utilities. Mostly for ad-hoc testing of jgit
       log, glog, fetch etc.
 
-- org.eclipse.jgit.java7/
-
-    * Support for symbolic links.
-
-    * Optimizations for reading file system attributes
-
 - org.eclipse.jgit.ant/
 
     * Ant tasks
diff --git a/org.eclipse.jgit.ant.test/.settings/org.eclipse.jdt.core.prefs b/org.eclipse.jgit.ant.test/.settings/org.eclipse.jdt.core.prefs
index dcc0d3a..87210fb 100644
--- a/org.eclipse.jgit.ant.test/.settings/org.eclipse.jdt.core.prefs
+++ b/org.eclipse.jgit.ant.test/.settings/org.eclipse.jdt.core.prefs
@@ -99,6 +99,7 @@
 org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled
 org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled
 org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled
+org.eclipse.jdt.core.compiler.problem.unusedExceptionParameter=ignore
 org.eclipse.jdt.core.compiler.problem.unusedImport=error
 org.eclipse.jdt.core.compiler.problem.unusedLabel=error
 org.eclipse.jdt.core.compiler.problem.unusedLocal=error
diff --git a/org.eclipse.jgit.ant.test/META-INF/MANIFEST.MF b/org.eclipse.jgit.ant.test/META-INF/MANIFEST.MF
index 371c66f..4eeaf47 100644
--- a/org.eclipse.jgit.ant.test/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.ant.test/META-INF/MANIFEST.MF
@@ -3,14 +3,14 @@
 Bundle-ManifestVersion: 2
 Bundle-Name: %plugin_name
 Bundle-SymbolicName: org.eclipse.jgit.ant.test
-Bundle-Version: 4.4.2.qualifier
+Bundle-Version: 4.5.5.qualifier
 Bundle-ActivationPolicy: lazy
 Bundle-RequiredExecutionEnvironment: JavaSE-1.7
 Import-Package: org.apache.tools.ant,
- org.eclipse.jgit.ant.tasks;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.internal.storage.file;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.junit;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.lib;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.util;version="[4.4.2,4.5.0)",
+ org.eclipse.jgit.ant.tasks;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.internal.storage.file;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.junit;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.lib;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.util;version="[4.5.5,4.6.0)",
  org.hamcrest;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 a4f4355..37e71d7 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.4.2-SNAPSHOT</version>
+    <version>4.5.5-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.ant.test</artifactId>
diff --git a/org.eclipse.jgit.ant/.settings/org.eclipse.jdt.core.prefs b/org.eclipse.jgit.ant/.settings/org.eclipse.jdt.core.prefs
index 13a031a..cd620c3 100644
--- a/org.eclipse.jgit.ant/.settings/org.eclipse.jdt.core.prefs
+++ b/org.eclipse.jgit.ant/.settings/org.eclipse.jdt.core.prefs
@@ -99,6 +99,7 @@
 org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled
 org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled
 org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled
+org.eclipse.jdt.core.compiler.problem.unusedExceptionParameter=ignore
 org.eclipse.jdt.core.compiler.problem.unusedImport=error
 org.eclipse.jdt.core.compiler.problem.unusedLabel=error
 org.eclipse.jdt.core.compiler.problem.unusedLocal=error
diff --git a/org.eclipse.jgit.ant/META-INF/MANIFEST.MF b/org.eclipse.jgit.ant/META-INF/MANIFEST.MF
index 4ae7d7b..8a4c823 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.4.2.qualifier
+Bundle-Version: 4.5.5.qualifier
 Bundle-RequiredExecutionEnvironment: JavaSE-1.7
 Import-Package: org.apache.tools.ant,
-  org.eclipse.jgit.storage.file;version="[4.4.2,4.5.0)"
+  org.eclipse.jgit.storage.file;version="[4.5.5,4.6.0)"
 Bundle-Localization: plugin
 Bundle-Vendor: %Provider-Name
-Export-Package: org.eclipse.jgit.ant.tasks;version="4.4.2";
+Export-Package: org.eclipse.jgit.ant.tasks;version="4.5.5";
  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 6f21e4d..38c70cc 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.4.2-SNAPSHOT</version>
+		<version>4.5.5-SNAPSHOT</version>
 	</parent>
 
 	<artifactId>org.eclipse.jgit.ant</artifactId>
diff --git a/org.eclipse.jgit.archive/.settings/org.eclipse.jdt.core.prefs b/org.eclipse.jgit.archive/.settings/org.eclipse.jdt.core.prefs
index 45d6d2c..bfaf736 100644
--- a/org.eclipse.jgit.archive/.settings/org.eclipse.jdt.core.prefs
+++ b/org.eclipse.jgit.archive/.settings/org.eclipse.jdt.core.prefs
@@ -99,6 +99,7 @@
 org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled
 org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled
 org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled
+org.eclipse.jdt.core.compiler.problem.unusedExceptionParameter=ignore
 org.eclipse.jdt.core.compiler.problem.unusedImport=error
 org.eclipse.jdt.core.compiler.problem.unusedLabel=error
 org.eclipse.jdt.core.compiler.problem.unusedLocal=error
diff --git a/org.eclipse.jgit.archive/META-INF/MANIFEST.MF b/org.eclipse.jgit.archive/META-INF/MANIFEST.MF
index 2b960ae..c9b4863 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.4.2.qualifier
+Bundle-Version: 4.5.5.qualifier
 Bundle-Vendor: %provider_name
 Bundle-Localization: plugin
 Bundle-RequiredExecutionEnvironment: JavaSE-1.7
@@ -12,14 +12,14 @@
  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.4.2,4.5.0)",
- org.eclipse.jgit.lib;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.nls;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.util;version="[4.4.2,4.5.0)",
+ org.eclipse.jgit.api;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.lib;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.nls;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.util;version="[4.5.5,4.6.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.4.2";
+Export-Package: org.eclipse.jgit.archive;version="4.5.5";
   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 b4a519c..a82400f 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.4.2.qualifier
-Eclipse-SourceBundle: org.eclipse.jgit.archive;version="4.4.2.qualifier";roots="."
+Bundle-Version: 4.5.5.qualifier
+Eclipse-SourceBundle: org.eclipse.jgit.archive;version="4.5.5.qualifier";roots="."
diff --git a/org.eclipse.jgit.archive/pom.xml b/org.eclipse.jgit.archive/pom.xml
index 0df67f8..5a444c8 100644
--- a/org.eclipse.jgit.archive/pom.xml
+++ b/org.eclipse.jgit.archive/pom.xml
@@ -50,11 +50,11 @@
   <parent>
     <groupId>org.eclipse.jgit</groupId>
     <artifactId>org.eclipse.jgit-parent</artifactId>
-    <version>4.4.2-SNAPSHOT</version>
+    <version>4.5.5-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.archive</artifactId>
-  <name>JGit Archive Formats</name>
+  <name>JGit - Archive Formats</name>
 
   <description>
     Support for archiving a Git tree in formats such as zip and tar.
diff --git a/org.eclipse.jgit.http.apache/.settings/org.eclipse.jdt.core.prefs b/org.eclipse.jgit.http.apache/.settings/org.eclipse.jdt.core.prefs
index 13a031a..cd620c3 100644
--- a/org.eclipse.jgit.http.apache/.settings/org.eclipse.jdt.core.prefs
+++ b/org.eclipse.jgit.http.apache/.settings/org.eclipse.jdt.core.prefs
@@ -99,6 +99,7 @@
 org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled
 org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled
 org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled
+org.eclipse.jdt.core.compiler.problem.unusedExceptionParameter=ignore
 org.eclipse.jdt.core.compiler.problem.unusedImport=error
 org.eclipse.jdt.core.compiler.problem.unusedLabel=error
 org.eclipse.jdt.core.compiler.problem.unusedLocal=error
diff --git a/org.eclipse.jgit.http.apache/META-INF/MANIFEST.MF b/org.eclipse.jgit.http.apache/META-INF/MANIFEST.MF
index 200e0ce..906b76e 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.4.2.qualifier
+Bundle-Version: 4.5.5.qualifier
 Bundle-RequiredExecutionEnvironment: JavaSE-1.7
 Bundle-Localization: plugin
 Bundle-Vendor: %Provider-Name
@@ -19,10 +19,10 @@
  org.apache.http.impl.client;version="[4.1.0,5.0.0)",
  org.apache.http.impl.client.cache;version="[4.1.0,5.0.0)",
  org.apache.http.params;version="[4.1.0,5.0.0)",
- org.eclipse.jgit.nls;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.transport.http;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.util;version="[4.4.2,4.5.0)"
-Export-Package: org.eclipse.jgit.transport.http.apache;version="4.4.2";
+ org.eclipse.jgit.nls;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.transport.http;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.util;version="[4.5.5,4.6.0)"
+Export-Package: org.eclipse.jgit.transport.http.apache;version="4.5.5";
   uses:="org.eclipse.jgit.transport.http,
    javax.net.ssl,
    org.apache.http.client,
diff --git a/org.eclipse.jgit.http.apache/pom.xml b/org.eclipse.jgit.http.apache/pom.xml
index 45bb22e..f250eb2 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.4.2-SNAPSHOT</version>
+		<version>4.5.5-SNAPSHOT</version>
 	</parent>
 
 	<artifactId>org.eclipse.jgit.http.apache</artifactId>
diff --git a/org.eclipse.jgit.http.apache/src/org/eclipse/jgit/transport/http/apache/HttpClientConnection.java b/org.eclipse.jgit.http.apache/src/org/eclipse/jgit/transport/http/apache/HttpClientConnection.java
index a0eeef8..2d9d17a 100644
--- a/org.eclipse.jgit.http.apache/src/org/eclipse/jgit/transport/http/apache/HttpClientConnection.java
+++ b/org.eclipse.jgit.http.apache/src/org/eclipse/jgit/transport/http/apache/HttpClientConnection.java
@@ -226,17 +226,25 @@
 	}
 
 	private void execute() throws IOException, ClientProtocolException {
-		if (resp == null)
-			if (entity != null) {
-				if (req instanceof HttpEntityEnclosingRequest) {
-					HttpEntityEnclosingRequest eReq = (HttpEntityEnclosingRequest) req;
-					eReq.setEntity(entity);
-				}
-				resp = getClient().execute(req);
-				entity.getBuffer().close();
-				entity = null;
-			} else
-				resp = getClient().execute(req);
+		if (resp != null) {
+			return;
+		}
+
+		if (entity == null) {
+			resp = getClient().execute(req);
+			return;
+		}
+
+		try {
+			if (req instanceof HttpEntityEnclosingRequest) {
+				HttpEntityEnclosingRequest eReq = (HttpEntityEnclosingRequest) req;
+				eReq.setEntity(entity);
+			}
+			resp = getClient().execute(req);
+		} finally {
+			entity.close();
+			entity = null;
+		}
 	}
 
 	public Map<String, List<String>> getHeaderFields() {
diff --git a/org.eclipse.jgit.http.apache/src/org/eclipse/jgit/transport/http/apache/TemporaryBufferEntity.java b/org.eclipse.jgit.http.apache/src/org/eclipse/jgit/transport/http/apache/TemporaryBufferEntity.java
index 1ff168e..377e5ca 100644
--- a/org.eclipse.jgit.http.apache/src/org/eclipse/jgit/transport/http/apache/TemporaryBufferEntity.java
+++ b/org.eclipse.jgit.http.apache/src/org/eclipse/jgit/transport/http/apache/TemporaryBufferEntity.java
@@ -55,7 +55,8 @@
  *
  * @since 3.3
  */
-public class TemporaryBufferEntity extends AbstractHttpEntity {
+public class TemporaryBufferEntity extends AbstractHttpEntity
+		implements AutoCloseable {
 	private TemporaryBuffer buffer;
 
 	private Integer contentLength;
@@ -106,4 +107,16 @@
 	public void setContentLength(int contentLength) {
 		this.contentLength = new Integer(contentLength);
 	}
+
+	/**
+	 * Close destroys the associated buffer used to buffer the entity
+	 *
+	 * @since 4.5
+	 */
+	@Override
+	public void close() {
+		if (buffer != null) {
+			buffer.destroy();
+		}
+	}
 }
diff --git a/org.eclipse.jgit.http.server/.settings/org.eclipse.jdt.core.prefs b/org.eclipse.jgit.http.server/.settings/org.eclipse.jdt.core.prefs
index 13a031a..cd620c3 100644
--- a/org.eclipse.jgit.http.server/.settings/org.eclipse.jdt.core.prefs
+++ b/org.eclipse.jgit.http.server/.settings/org.eclipse.jdt.core.prefs
@@ -99,6 +99,7 @@
 org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled
 org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled
 org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled
+org.eclipse.jdt.core.compiler.problem.unusedExceptionParameter=ignore
 org.eclipse.jdt.core.compiler.problem.unusedImport=error
 org.eclipse.jdt.core.compiler.problem.unusedLabel=error
 org.eclipse.jdt.core.compiler.problem.unusedLocal=error
diff --git a/org.eclipse.jgit.http.server/META-INF/MANIFEST.MF b/org.eclipse.jgit.http.server/META-INF/MANIFEST.MF
index d28ac28..18f31d3 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.4.2.qualifier
+Bundle-Version: 4.5.5.qualifier
 Bundle-Localization: plugin
 Bundle-Vendor: %provider_name
-Export-Package: org.eclipse.jgit.http.server;version="4.4.2",
- org.eclipse.jgit.http.server.glue;version="4.4.2";
+Export-Package: org.eclipse.jgit.http.server;version="4.5.5",
+ org.eclipse.jgit.http.server.glue;version="4.5.5";
   uses:="javax.servlet,javax.servlet.http",
- org.eclipse.jgit.http.server.resolver;version="4.4.2";
+ org.eclipse.jgit.http.server.resolver;version="4.5.5";
   uses:="org.eclipse.jgit.transport.resolver,
    org.eclipse.jgit.lib,
    org.eclipse.jgit.transport,
@@ -17,12 +17,12 @@
 Bundle-RequiredExecutionEnvironment: JavaSE-1.7
 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.4.2,4.5.0)",
- org.eclipse.jgit.internal.storage.dfs;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.internal.storage.file;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.lib;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.nls;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.revwalk;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.transport;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.transport.resolver;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.util;version="[4.4.2,4.5.0)"
+ org.eclipse.jgit.errors;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.internal.storage.dfs;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.internal.storage.file;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.lib;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.nls;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.revwalk;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.transport;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.transport.resolver;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.util;version="[4.5.5,4.6.0)"
diff --git a/org.eclipse.jgit.http.server/pom.xml b/org.eclipse.jgit.http.server/pom.xml
index 6c5db67..5f8376c 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.4.2-SNAPSHOT</version>
+    <version>4.5.5-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.http.server</artifactId>
diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/RepositoryFilter.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/RepositoryFilter.java
index a021c1f..b3fad3d 100644
--- a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/RepositoryFilter.java
+++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/RepositoryFilter.java
@@ -143,7 +143,7 @@
 			res.sendError(SC_UNAUTHORIZED, e.getMessage());
 			return;
 		} catch (ServiceMayNotContinueException e) {
-			sendError(req, res, SC_FORBIDDEN, e.getMessage());
+			sendError(req, res, e.getStatusCode(), e.getMessage());
 			return;
 		}
 		try {
diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/SmartServiceInfoRefs.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/SmartServiceInfoRefs.java
index 7d4f21b..a06bb1e 100644
--- a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/SmartServiceInfoRefs.java
+++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/SmartServiceInfoRefs.java
@@ -139,7 +139,7 @@
 			if (e.isOutput())
 				buf.close();
 			else
-				sendError(req, res, SC_FORBIDDEN, e.getMessage());
+				sendError(req, res, e.getStatusCode(), e.getMessage());
 		}
 	}
 
diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/UploadPackServlet.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/UploadPackServlet.java
index 8c27b71..a9a0c5b 100644
--- a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/UploadPackServlet.java
+++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/UploadPackServlet.java
@@ -197,7 +197,7 @@
 				out.close();
 			} else if (!rsp.isCommitted()) {
 				rsp.reset();
-				sendError(req, rsp, SC_FORBIDDEN, e.getMessage());
+				sendError(req, rsp, e.getStatusCode(), e.getMessage());
 			}
 			return;
 
diff --git a/org.eclipse.jgit.http.test/.settings/org.eclipse.jdt.core.prefs b/org.eclipse.jgit.http.test/.settings/org.eclipse.jdt.core.prefs
index dcc0d3a..87210fb 100644
--- a/org.eclipse.jgit.http.test/.settings/org.eclipse.jdt.core.prefs
+++ b/org.eclipse.jgit.http.test/.settings/org.eclipse.jdt.core.prefs
@@ -99,6 +99,7 @@
 org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled
 org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled
 org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled
+org.eclipse.jdt.core.compiler.problem.unusedExceptionParameter=ignore
 org.eclipse.jdt.core.compiler.problem.unusedImport=error
 org.eclipse.jdt.core.compiler.problem.unusedLabel=error
 org.eclipse.jdt.core.compiler.problem.unusedLocal=error
diff --git a/org.eclipse.jgit.http.test/META-INF/MANIFEST.MF b/org.eclipse.jgit.http.test/META-INF/MANIFEST.MF
index 90ee569..af492c1 100644
--- a/org.eclipse.jgit.http.test/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.http.test/META-INF/MANIFEST.MF
@@ -2,7 +2,7 @@
 Bundle-ManifestVersion: 2
 Bundle-Name: %plugin_name
 Bundle-SymbolicName: org.eclipse.jgit.http.test
-Bundle-Version: 4.4.2.qualifier
+Bundle-Version: 4.5.5.qualifier
 Bundle-Vendor: %provider_name
 Bundle-Localization: plugin
 Bundle-RequiredExecutionEnvironment: JavaSE-1.7
@@ -22,24 +22,24 @@
  org.eclipse.jetty.util.log;version="[9.0.0,10.0.0)",
  org.eclipse.jetty.util.security;version="[9.0.0,10.0.0)",
  org.eclipse.jetty.util.thread;version="[9.0.0,10.0.0)",
- org.eclipse.jgit.errors;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.http.server;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.http.server.glue;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.http.server.resolver;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.internal;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.internal.storage.dfs;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.internal.storage.file;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.junit;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.junit.http;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.lib;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.nls;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.revwalk;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.storage.file;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.transport;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.transport.http;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.transport.http.apache;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.transport.resolver;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.util;version="[4.4.2,4.5.0)",
+ org.eclipse.jgit.errors;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.http.server;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.http.server.glue;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.http.server.resolver;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.internal;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.internal.storage.dfs;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.internal.storage.file;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.junit;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.junit.http;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.lib;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.nls;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.revwalk;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.storage.file;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.transport;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.transport.http;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.transport.http.apache;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.transport.resolver;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.util;version="[4.5.5,4.6.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/build.properties b/org.eclipse.jgit.http.test/build.properties
index 9ffa0ca..e8bacac 100644
--- a/org.eclipse.jgit.http.test/build.properties
+++ b/org.eclipse.jgit.http.test/build.properties
@@ -1,4 +1,5 @@
-source.. = tst/
+source.. = tst/,\
+           src/
 output.. = bin/
 bin.includes = META-INF/,\
                .,\
diff --git a/org.eclipse.jgit.http.test/pom.xml b/org.eclipse.jgit.http.test/pom.xml
index c39817f..16c4d66 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.4.2-SNAPSHOT</version>
+    <version>4.5.5-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.http.test</artifactId>
diff --git a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/SmartClientSmartServerTest.java b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/SmartClientSmartServerTest.java
index 073c751..2b9105c 100644
--- a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/SmartClientSmartServerTest.java
+++ b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/SmartClientSmartServerTest.java
@@ -610,7 +610,7 @@
 		fsck(remoteRepository, Q);
 
 		final ReflogReader log = remoteRepository.getReflogReader(dstName);
-		assertNotNull("has log for " + dstName);
+		assertNotNull("has log for " + dstName, log);
 
 		final ReflogEntry last = log.getLastEntry();
 		assertNotNull("has last entry", last);
diff --git a/org.eclipse.jgit.junit.http/.settings/org.eclipse.jdt.core.prefs b/org.eclipse.jgit.junit.http/.settings/org.eclipse.jdt.core.prefs
index dcc0d3a..87210fb 100644
--- a/org.eclipse.jgit.junit.http/.settings/org.eclipse.jdt.core.prefs
+++ b/org.eclipse.jgit.junit.http/.settings/org.eclipse.jdt.core.prefs
@@ -99,6 +99,7 @@
 org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled
 org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled
 org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled
+org.eclipse.jdt.core.compiler.problem.unusedExceptionParameter=ignore
 org.eclipse.jdt.core.compiler.problem.unusedImport=error
 org.eclipse.jdt.core.compiler.problem.unusedLabel=error
 org.eclipse.jdt.core.compiler.problem.unusedLocal=error
diff --git a/org.eclipse.jgit.junit.http/META-INF/MANIFEST.MF b/org.eclipse.jgit.junit.http/META-INF/MANIFEST.MF
index c406a14..29dd705 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.4.2.qualifier
+Bundle-Version: 4.5.5.qualifier
 Bundle-Localization: plugin
 Bundle-Vendor: %provider_name
 Bundle-ActivationPolicy: lazy
@@ -20,16 +20,16 @@
  org.eclipse.jetty.util.component;version="[9.0.0,10.0.0)",
  org.eclipse.jetty.util.log;version="[9.0.0,10.0.0)",
  org.eclipse.jetty.util.security;version="[9.0.0,10.0.0)",
- org.eclipse.jgit.errors;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.http.server;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.internal.storage.file;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.junit;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.lib;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.revwalk;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.transport;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.transport.resolver;version="[4.4.2,4.5.0)",
+ org.eclipse.jgit.errors;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.http.server;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.internal.storage.file;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.junit;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.lib;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.revwalk;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.transport;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.transport.resolver;version="[4.5.5,4.6.0)",
  org.junit;version="[4.0.0,5.0.0)"
-Export-Package: org.eclipse.jgit.junit.http;version="4.4.2";
+Export-Package: org.eclipse.jgit.junit.http;version="4.5.5";
   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 201998f..1f2ee88 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.4.2-SNAPSHOT</version>
+    <version>4.5.5-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.junit.http</artifactId>
diff --git a/org.eclipse.jgit.junit.http/src/org/eclipse/jgit/junit/http/AppServer.java b/org.eclipse.jgit.junit.http/src/org/eclipse/jgit/junit/http/AppServer.java
index c36c297..cca4f43 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
@@ -168,21 +168,38 @@
 		return ctx;
 	}
 
+	static class TestMappedLoginService extends MappedLoginService {
+		private String role;
+
+		TestMappedLoginService(String role) {
+			this.role = role;
+		}
+
+		@Override
+		protected UserIdentity loadUser(String who) {
+			return null;
+		}
+
+		@Override
+		protected void loadUsers() throws IOException {
+			putUser(username, new Password(password), new String[] { role });
+		}
+
+		protected String[] loadRoleInfo(
+				@SuppressWarnings("unused") KnownUser user) {
+			return null;
+		}
+
+		protected KnownUser loadUserInfo(
+				@SuppressWarnings("unused") String usrname) {
+			return null;
+		}
+	}
+
 	private void auth(ServletContextHandler ctx, Authenticator authType) {
 		final String role = "can-access";
 
-		MappedLoginService users = new MappedLoginService() {
-			@Override
-			protected UserIdentity loadUser(String who) {
-				return null;
-			}
-
-			@Override
-			protected void loadUsers() throws IOException {
-				putUser(username, new Password(password), new String[] { role });
-			}
-		};
-
+		MappedLoginService 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/HttpTestCase.java b/org.eclipse.jgit.junit.http/src/org/eclipse/jgit/junit/http/HttpTestCase.java
index 84bb888..ab5d3e1 100644
--- a/org.eclipse.jgit.junit.http/src/org/eclipse/jgit/junit/http/HttpTestCase.java
+++ b/org.eclipse.jgit.junit.http/src/org/eclipse/jgit/junit/http/HttpTestCase.java
@@ -117,7 +117,9 @@
 
 	protected static void fsck(Repository db, RevObject... tips)
 			throws Exception {
-		new TestRepository(db).fsck(tips);
+		TestRepository<? extends Repository> tr =
+				new TestRepository<Repository>(db);
+		tr.fsck(tips);
 	}
 
 	protected static Set<RefSpec> mirror(String... refs) {
diff --git a/org.eclipse.jgit.junit/.settings/org.eclipse.jdt.core.prefs b/org.eclipse.jgit.junit/.settings/org.eclipse.jdt.core.prefs
index dcc0d3a..87210fb 100644
--- a/org.eclipse.jgit.junit/.settings/org.eclipse.jdt.core.prefs
+++ b/org.eclipse.jgit.junit/.settings/org.eclipse.jdt.core.prefs
@@ -99,6 +99,7 @@
 org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled
 org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled
 org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled
+org.eclipse.jdt.core.compiler.problem.unusedExceptionParameter=ignore
 org.eclipse.jdt.core.compiler.problem.unusedImport=error
 org.eclipse.jdt.core.compiler.problem.unusedLabel=error
 org.eclipse.jdt.core.compiler.problem.unusedLocal=error
diff --git a/org.eclipse.jgit.junit/META-INF/MANIFEST.MF b/org.eclipse.jgit.junit/META-INF/MANIFEST.MF
index 0202328..7bddadb 100644
--- a/org.eclipse.jgit.junit/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.junit/META-INF/MANIFEST.MF
@@ -2,27 +2,30 @@
 Bundle-ManifestVersion: 2
 Bundle-Name: %plugin_name
 Bundle-SymbolicName: org.eclipse.jgit.junit
-Bundle-Version: 4.4.2.qualifier
+Bundle-Version: 4.5.5.qualifier
 Bundle-Localization: plugin
 Bundle-Vendor: %provider_name
 Bundle-ActivationPolicy: lazy
 Bundle-RequiredExecutionEnvironment: JavaSE-1.7
-Import-Package: org.eclipse.jgit.api;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.api.errors;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.dircache;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.errors;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.internal.storage.file;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.internal.storage.pack;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.lib;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.merge;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.revwalk;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.storage.file;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.treewalk;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.treewalk.filter;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.util;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.util.io;version="[4.4.2,4.5.0)",
- org.junit;version="[4.0.0,5.0.0)"
-Export-Package: org.eclipse.jgit.junit;version="4.4.2";
+Import-Package: org.eclipse.jgit.api;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.api.errors;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.dircache;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.errors;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.internal.storage.file;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.internal.storage.pack;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.lib;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.merge;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.revwalk;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.storage.file;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.treewalk;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.treewalk.filter;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.util;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.util.io;version="[4.5.5,4.6.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.5.5";
   uses:="org.eclipse.jgit.dircache,
    org.eclipse.jgit.lib,
    org.eclipse.jgit.revwalk,
diff --git a/org.eclipse.jgit.junit/pom.xml b/org.eclipse.jgit.junit/pom.xml
index 90dd865..184d72c 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.4.2-SNAPSHOT</version>
+    <version>4.5.5-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.junit</artifactId>
diff --git a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/Repeat.java b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/Repeat.java
new file mode 100644
index 0000000..22b5007
--- /dev/null
+++ b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/Repeat.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2016, Matthias Sohn <matthias.sohn@sap.com>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ *   copyright notice, this list of conditions and the following
+ *   disclaimer in the documentation and/or other materials provided
+ *   with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ *   names of its contributors may be used to endorse or promote
+ *   products derived from this software without specific prior
+ *   written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.eclipse.jgit.junit;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ java.lang.annotation.ElementType.METHOD })
+public @interface Repeat {
+	public abstract int n();
+}
\ No newline at end of file
diff --git a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/RepeatRule.java b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/RepeatRule.java
new file mode 100644
index 0000000..75e1a67
--- /dev/null
+++ b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/RepeatRule.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2016, Matthias Sohn <matthias.sohn@sap.com>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ *   copyright notice, this list of conditions and the following
+ *   disclaimer in the documentation and/or other materials provided
+ *   with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ *   names of its contributors may be used to endorse or promote
+ *   products derived from this software without specific prior
+ *   written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.eclipse.jgit.junit;
+
+import java.text.MessageFormat;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+/**
+ * {@link TestRule} which enables to run the same JUnit test repeatedly. Add
+ * this rule to the test class
+ *
+ * <pre>
+ * public class MyTest {
+ * 	&#64;Rule
+ * 	public RepeatRule repeatRule = new RepeatRule();
+ * 	...
+ * }
+ * </pre>
+ *
+ * and annotate the test to be repeated with the
+ * {@code @Repeat(n=<repetitions>)} annotation
+ *
+ * <pre>
+ * &#64;Test
+ * &#64;Repeat(n = 100)
+ * public void test() {
+ * 	...
+ * }
+ * </pre>
+ *
+ * then this test will be repeated 100 times. If any test execution fails test
+ * repetition will be stopped.
+ */
+public class RepeatRule implements TestRule {
+
+	private static Logger LOG = Logger
+			.getLogger(RepeatRule.class.getName());
+
+	public static class RepeatedTestException extends RuntimeException {
+		private static final long serialVersionUID = 1L;
+
+		public RepeatedTestException(String message, Throwable cause) {
+			super(message, cause);
+		}
+	}
+
+	private static class RepeatStatement extends Statement {
+
+		private final int repetitions;
+
+		private final Statement statement;
+
+		private RepeatStatement(int repetitions, Statement statement) {
+			this.repetitions = repetitions;
+			this.statement = statement;
+		}
+
+		@Override
+		public void evaluate() throws Throwable {
+			for (int i = 0; i < repetitions; i++) {
+				try {
+					statement.evaluate();
+				} catch (Throwable e) {
+					RepeatedTestException ex = new RepeatedTestException(
+							MessageFormat.format(
+									"Repeated test failed when run for the {0}. time",
+									Integer.valueOf(i + 1)),
+							e);
+					LOG.log(Level.SEVERE, ex.getMessage(), ex);
+					throw ex;
+				}
+			}
+		}
+	}
+
+	@Override
+	public Statement apply(Statement statement, Description description) {
+		Statement result = statement;
+		Repeat repeat = description.getAnnotation(Repeat.class);
+		if (repeat != null) {
+			int n = repeat.n();
+			result = new RepeatStatement(n, statement);
+		}
+		return result;
+	}
+}
\ No newline at end of file
diff --git a/org.eclipse.jgit.lfs.server.test/.settings/org.eclipse.jdt.core.prefs b/org.eclipse.jgit.lfs.server.test/.settings/org.eclipse.jdt.core.prefs
index dcc0d3a..87210fb 100644
--- a/org.eclipse.jgit.lfs.server.test/.settings/org.eclipse.jdt.core.prefs
+++ b/org.eclipse.jgit.lfs.server.test/.settings/org.eclipse.jdt.core.prefs
@@ -99,6 +99,7 @@
 org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled
 org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled
 org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled
+org.eclipse.jdt.core.compiler.problem.unusedExceptionParameter=ignore
 org.eclipse.jdt.core.compiler.problem.unusedImport=error
 org.eclipse.jdt.core.compiler.problem.unusedLabel=error
 org.eclipse.jdt.core.compiler.problem.unusedLocal=error
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 7ef63d8..71de1cd 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.4.2.qualifier
+Bundle-Version: 4.5.5.qualifier
 Bundle-Vendor: %provider_name
 Bundle-Localization: plugin
 Bundle-RequiredExecutionEnvironment: JavaSE-1.7
@@ -27,11 +27,11 @@
  org.eclipse.jetty.util.log;version="[9.0.0,10.0.0)",
  org.eclipse.jetty.util.security;version="[9.0.0,10.0.0)",
  org.eclipse.jetty.util.thread;version="[9.0.0,10.0.0)",
- org.eclipse.jgit.junit.http;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.lfs.lib;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.lfs.server.fs;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.lfs.test;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.util;version="[4.4.2,4.5.0)",
+ org.eclipse.jgit.junit.http;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.lfs.lib;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.lfs.server.fs;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.lfs.test;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.util;version="[4.5.5,4.6.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 bf975c0..f844abc 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.4.2-SNAPSHOT</version>
+    <version>4.5.5-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.lfs.server.test</artifactId>
diff --git a/org.eclipse.jgit.lfs.server.test/tst/org/eclipse/jgit/lfs/server/fs/DownloadTest.java b/org.eclipse.jgit.lfs.server.test/tst/org/eclipse/jgit/lfs/server/fs/DownloadTest.java
index 6c4f3cb..f92e638 100644
--- a/org.eclipse.jgit.lfs.server.test/tst/org/eclipse/jgit/lfs/server/fs/DownloadTest.java
+++ b/org.eclipse.jgit.lfs.server.test/tst/org/eclipse/jgit/lfs/server/fs/DownloadTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2015, Matthias Sohn <matthias.sohnk@sap.com>
+ * Copyright (C) 2015, 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
@@ -42,6 +42,8 @@
  */
 package org.eclipse.jgit.lfs.server.fs;
 
+import static org.apache.http.HttpStatus.SC_NOT_FOUND;
+import static org.apache.http.HttpStatus.SC_UNPROCESSABLE_ENTITY;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.fail;
 
@@ -72,13 +74,15 @@
 	public void testDownloadInvalidPathInfo()
 			throws ClientProtocolException, IOException {
 		String TEXT = "test";
-		AnyLongObjectId id = putContent(TEXT);
+		String id = putContent(TEXT).name().substring(0, 60);
 		Path f = Paths.get(getTempDirectory().toString(), "download");
 		try {
-			getContent(id.name().substring(0, 60), f);
+			getContent(id, f);
 			fail("expected RuntimeException");
 		} catch (RuntimeException e) {
-			assertEquals("Status: 400 Bad Request",
+			String error = String.format(
+					"Invalid pathInfo '/%s' does not match '/{SHA-256}'", id);
+			assertEquals(formatErrorMessage(SC_UNPROCESSABLE_ENTITY, error),
 					e.getMessage());
 		}
 	}
@@ -87,13 +91,14 @@
 	public void testDownloadInvalidId()
 			throws ClientProtocolException, IOException {
 		String TEXT = "test";
-		AnyLongObjectId id = putContent(TEXT);
+		String id = putContent(TEXT).name().replace('f', 'z');
 		Path f = Paths.get(getTempDirectory().toString(), "download");
 		try {
-			getContent(id.name().replace('f', 'z'), f);
+			getContent(id, f);
 			fail("expected RuntimeException");
 		} catch (RuntimeException e) {
-			assertEquals("Status: 400 Bad Request",
+			String error = String.format("Invalid id: : %s", id);
+			assertEquals(formatErrorMessage(SC_UNPROCESSABLE_ENTITY, error),
 					e.getMessage());
 		}
 	}
@@ -108,7 +113,8 @@
 			getContent(id, f);
 			fail("expected RuntimeException");
 		} catch (RuntimeException e) {
-			assertEquals("Status: 404 Not Found",
+			String error = String.format("Object '%s' not found", id.getName());
+			assertEquals(formatErrorMessage(SC_NOT_FOUND, error),
 					e.getMessage());
 		}
 	}
@@ -129,4 +135,10 @@
 		FileUtils.delete(f.toFile(), FileUtils.RETRY);
 
 	}
+
+	@SuppressWarnings("boxing")
+	private String formatErrorMessage(int status, String message) {
+		return String.format("Status: %d {\n  \"message\": \"%s\"\n}", status,
+				message);
+	}
 }
diff --git a/org.eclipse.jgit.lfs.server.test/tst/org/eclipse/jgit/lfs/server/fs/LfsServerTest.java b/org.eclipse.jgit.lfs.server.test/tst/org/eclipse/jgit/lfs/server/fs/LfsServerTest.java
index 8c266d4..4d948b9 100644
--- a/org.eclipse.jgit.lfs.server.test/tst/org/eclipse/jgit/lfs/server/fs/LfsServerTest.java
+++ b/org.eclipse.jgit.lfs.server.test/tst/org/eclipse/jgit/lfs/server/fs/LfsServerTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2015, Matthias Sohn <matthias.sohnk@sap.com>
+ * Copyright (C) 2015, 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
@@ -42,6 +42,7 @@
  */
 package org.eclipse.jgit.lfs.server.fs;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.junit.Assert.assertEquals;
 
 import java.io.BufferedInputStream;
@@ -79,6 +80,7 @@
 import org.eclipse.jgit.lfs.lib.LongObjectId;
 import org.eclipse.jgit.lfs.test.LongObjectIdTestUtils;
 import org.eclipse.jgit.util.FileUtils;
+import org.eclipse.jgit.util.IO;
 import org.junit.After;
 import org.junit.Before;
 
@@ -186,8 +188,23 @@
 		StatusLine statusLine = response.getStatusLine();
 		int status = statusLine.getStatusCode();
 		if (statusLine.getStatusCode() >= 400) {
-			throw new RuntimeException("Status: " + status + " "
-					+ statusLine.getReasonPhrase());
+			String error;
+			try {
+				ByteBuffer buf = IO.readWholeStream(new BufferedInputStream(
+						response.getEntity().getContent()), 1024);
+				if (buf.hasArray()) {
+					error = new String(buf.array(),
+							buf.arrayOffset() + buf.position(), buf.remaining(),
+							UTF_8);
+				} else {
+					final byte[] b = new byte[buf.remaining()];
+					buf.duplicate().get(b);
+					error = new String(b, UTF_8);
+				}
+			} catch (IOException e) {
+				error = statusLine.getReasonPhrase();
+			}
+			throw new RuntimeException("Status: " + status + " " + error);
 		}
 		assertEquals(200, status);
 	}
diff --git a/org.eclipse.jgit.lfs.server.test/tst/org/eclipse/jgit/lfs/server/fs/UploadTest.java b/org.eclipse.jgit.lfs.server.test/tst/org/eclipse/jgit/lfs/server/fs/UploadTest.java
index 1fb91bd..8a8f49c 100644
--- a/org.eclipse.jgit.lfs.server.test/tst/org/eclipse/jgit/lfs/server/fs/UploadTest.java
+++ b/org.eclipse.jgit.lfs.server.test/tst/org/eclipse/jgit/lfs/server/fs/UploadTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2015, Matthias Sohn <matthias.sohnk@sap.com>
+ * Copyright (C) 2015, 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
diff --git a/org.eclipse.jgit.lfs.server/.settings/.api_filters b/org.eclipse.jgit.lfs.server/.settings/.api_filters
new file mode 100644
index 0000000..e662937
--- /dev/null
+++ b/org.eclipse.jgit.lfs.server/.settings/.api_filters
@@ -0,0 +1,17 @@
+<?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 implementors only which is ok under OSGi semver rules" id="336695337">
+            <message_arguments>
+                <message_argument value="org.eclipse.jgit.lfs.server.LfsProtocolServlet"/>
+                <message_argument value="getLargeFileRepository(LfsProtocolServlet.LfsRequest, String)"/>
+            </message_arguments>
+        </filter>
+        <filter comment="breaking implementors only which is ok under OSGi semver rules" id="338792546">
+            <message_arguments>
+                <message_argument value="org.eclipse.jgit.lfs.server.LfsProtocolServlet"/>
+                <message_argument value="getLargeFileRepository()"/>
+            </message_arguments>
+        </filter>
+    </resource>
+</component>
diff --git a/org.eclipse.jgit.lfs.server/.settings/org.eclipse.jdt.core.prefs b/org.eclipse.jgit.lfs.server/.settings/org.eclipse.jdt.core.prefs
index ff39d16..1ce7cd0 100644
--- a/org.eclipse.jgit.lfs.server/.settings/org.eclipse.jdt.core.prefs
+++ b/org.eclipse.jgit.lfs.server/.settings/org.eclipse.jdt.core.prefs
@@ -99,6 +99,7 @@
 org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled
 org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled
 org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled
+org.eclipse.jdt.core.compiler.problem.unusedExceptionParameter=ignore
 org.eclipse.jdt.core.compiler.problem.unusedImport=error
 org.eclipse.jdt.core.compiler.problem.unusedLabel=error
 org.eclipse.jdt.core.compiler.problem.unusedLocal=error
diff --git a/org.eclipse.jgit.lfs.server/META-INF/MANIFEST.MF b/org.eclipse.jgit.lfs.server/META-INF/MANIFEST.MF
index 5688ea1..8e6ab94 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.4.2.qualifier
+Bundle-Version: 4.5.5.qualifier
 Bundle-Localization: plugin
 Bundle-Vendor: %provider_name
-Export-Package: org.eclipse.jgit.lfs.server;version="4.4.2";
+Export-Package: org.eclipse.jgit.lfs.server;version="4.5.5";
   uses:="javax.servlet.http,
    org.eclipse.jgit.lfs.lib",
- org.eclipse.jgit.lfs.server.fs;version="4.4.2";
+ org.eclipse.jgit.lfs.server.fs;version="4.5.5";
   uses:="javax.servlet,
    javax.servlet.http,
    org.eclipse.jgit.lfs.server,
    org.eclipse.jgit.lfs.lib",
- org.eclipse.jgit.lfs.server.internal;version="4.4.2";x-internal:=true,
- org.eclipse.jgit.lfs.server.s3;version="4.4.2";
+ org.eclipse.jgit.lfs.server.internal;version="4.5.5";x-internal:=true,
+ org.eclipse.jgit.lfs.server.s3;version="4.5.5";
   uses:="org.eclipse.jgit.lfs.server,
    org.eclipse.jgit.lfs.lib"
 Bundle-RequiredExecutionEnvironment: JavaSE-1.7
@@ -24,12 +24,12 @@
  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.4.2,4.5.0)",
- org.eclipse.jgit.internal;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.internal.storage.file;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.lfs.errors;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.lfs.lib;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.nls;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.transport.http;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.transport.http.apache;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.util;version="[4.4.2,4.5.0)"
+ org.eclipse.jgit.annotations;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.internal;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.internal.storage.file;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.lfs.errors;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.lfs.lib;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.nls;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.transport.http;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.transport.http.apache;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.util;version="[4.5.5,4.6.0)"
diff --git a/org.eclipse.jgit.lfs.server/pom.xml b/org.eclipse.jgit.lfs.server/pom.xml
index 71b05ea..5c81ae6 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.4.2-SNAPSHOT</version>
+    <version>4.5.5-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.lfs.server</artifactId>
diff --git a/org.eclipse.jgit.lfs.server/resources/org/eclipse/jgit/lfs/server/internal/LfsServerText.properties b/org.eclipse.jgit.lfs.server/resources/org/eclipse/jgit/lfs/server/internal/LfsServerText.properties
index 451064d..f97acac 100644
--- a/org.eclipse.jgit.lfs.server/resources/org/eclipse/jgit/lfs/server/internal/LfsServerText.properties
+++ b/org.eclipse.jgit.lfs.server/resources/org/eclipse/jgit/lfs/server/internal/LfsServerText.properties
@@ -2,11 +2,11 @@
 failedToCalcSignature=Failed to calculate a request signature: {0}
 invalidPathInfo=Invalid pathInfo ''{0}'' does not match ''/'{'SHA-256'}'''
 objectNotFound=Object ''{0}'' not found
-undefinedS3AccessKey=S3 configuration: ''accessKey'' is undefined
-undefinedS3Bucket=S3 configuration: ''bucket'' is undefined
-undefinedS3Region=S3 configuration: ''region'' is undefined
-undefinedS3SecretKey=S3 configuration: ''secretKey'' is undefined
-undefinedS3StorageClass=S3 configuration: ''storageClass'' is undefined
+undefinedS3AccessKey=S3 configuration: 'accessKey' is undefined
+undefinedS3Bucket=S3 configuration: 'bucket' is undefined
+undefinedS3Region=S3 configuration: 'region' is undefined
+undefinedS3SecretKey=S3 configuration: 'secretKey' is undefined
+undefinedS3StorageClass=S3 configuration: 'storageClass' is undefined
 unparsableEndpoint=Unable to parse service endpoint: {0}
 unsupportedOperation=Operation ''{0}'' is not supported
 unsupportedUtf8=UTF-8 encoding is not supported.
diff --git a/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/LfsObject.java b/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/LfsObject.java
index 30ba22e..4d97502 100644
--- a/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/LfsObject.java
+++ b/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/LfsObject.java
@@ -42,7 +42,26 @@
  */
 package org.eclipse.jgit.lfs.server;
 
-class LfsObject {
+/**
+ * LFS object.
+ *
+ * @since 4.5
+ */
+public class LfsObject {
 	String oid;
 	long size;
+
+	/**
+	 * @return the object ID.
+	 */
+	public String getOid() {
+		return oid;
+	}
+
+	/**
+	 * @return the object size.
+	 */
+	public long getSize() {
+		return size;
+	}
 }
diff --git a/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/LfsProtocolServlet.java b/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/LfsProtocolServlet.java
index 394137c..eb49ff0 100644
--- a/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/LfsProtocolServlet.java
+++ b/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/LfsProtocolServlet.java
@@ -43,8 +43,13 @@
 package org.eclipse.jgit.lfs.server;
 
 import static java.nio.charset.StandardCharsets.UTF_8;
-import static javax.servlet.http.HttpServletResponse.SC_OK;
-import static javax.servlet.http.HttpServletResponse.SC_SERVICE_UNAVAILABLE;
+import static org.apache.http.HttpStatus.SC_FORBIDDEN;
+import static org.apache.http.HttpStatus.SC_INTERNAL_SERVER_ERROR;
+import static org.apache.http.HttpStatus.SC_INSUFFICIENT_STORAGE;
+import static org.apache.http.HttpStatus.SC_NOT_FOUND;
+import static org.apache.http.HttpStatus.SC_OK;
+import static org.apache.http.HttpStatus.SC_SERVICE_UNAVAILABLE;
+import static org.apache.http.HttpStatus.SC_UNPROCESSABLE_ENTITY;
 
 import java.io.BufferedReader;
 import java.io.BufferedWriter;
@@ -60,6 +65,15 @@
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
+import org.eclipse.jgit.lfs.errors.LfsBandwidthLimitExceeded;
+import org.eclipse.jgit.lfs.errors.LfsException;
+import org.eclipse.jgit.lfs.errors.LfsInsufficientStorage;
+import org.eclipse.jgit.lfs.errors.LfsRateLimitExceeded;
+import org.eclipse.jgit.lfs.errors.LfsRepositoryNotFound;
+import org.eclipse.jgit.lfs.errors.LfsRepositoryReadOnly;
+import org.eclipse.jgit.lfs.errors.LfsUnavailable;
+import org.eclipse.jgit.lfs.errors.LfsValidationError;
+
 import com.google.gson.FieldNamingPolicy;
 import com.google.gson.Gson;
 import com.google.gson.GsonBuilder;
@@ -67,7 +81,7 @@
 /**
  * LFS protocol handler implementing the LFS batch API [1]
  *
- * [1] https://github.com/github/git-lfs/blob/master/docs/api/http-v1-batch.md
+ * [1] https://github.com/github/git-lfs/blob/master/docs/api/v1/http-v1-batch.md
  *
  * @since 4.3
  */
@@ -75,52 +89,143 @@
 
 	private static final long serialVersionUID = 1L;
 
-	private static final String CONTENTTYPE_VND_GIT_LFS_JSON = "application/vnd.git-lfs+json"; //$NON-NLS-1$
+	private static final String CONTENTTYPE_VND_GIT_LFS_JSON =
+			"application/vnd.git-lfs+json; charset=utf-8"; //$NON-NLS-1$
+
+	private static final int SC_RATE_LIMIT_EXCEEDED = 429;
+
+	private static final int SC_BANDWIDTH_LIMIT_EXCEEDED = 509;
 
 	private Gson gson = createGson();
 
 	/**
-	 * Get the large file repository
+	 * Get the large file repository for the given request and path.
 	 *
-	 * @return the large file repository storing large files
+	 * @param request
+	 *            the request
+	 * @param path
+	 *            the path
+	 *
+	 * @return the large file repository storing large files.
+	 * @throws LfsException
+	 *             implementations should throw more specific exceptions to
+	 *             signal which type of error occurred:
+	 *             <dl>
+	 *             <dt>{@link LfsValidationError}</dt>
+	 *             <dd>when there is a validation error with one or more of the
+	 *             objects in the request</dd>
+	 *             <dt>{@link LfsRepositoryNotFound}</dt>
+	 *             <dd>when the repository does not exist for the user</dd>
+	 *             <dt>{@link LfsRepositoryReadOnly}</dt>
+	 *             <dd>when the user has read, but not write access. Only
+	 *             applicable when the operation in the request is "upload"</dd>
+	 *             <dt>{@link LfsRateLimitExceeded}</dt>
+	 *             <dd>when the user has hit a rate limit with the server</dd>
+	 *             <dt>{@link LfsBandwidthLimitExceeded}</dt>
+	 *             <dd>when the bandwidth limit for the user or repository has
+	 *             been exceeded</dd>
+	 *             <dt>{@link LfsInsufficientStorage}</dt>
+	 *             <dd>when there is insufficient storage on the server</dd>
+	 *             <dt>{@link LfsUnavailable}</dt>
+	 *             <dd>when LFS is not available</dd>
+	 *             <dt>{@link LfsException}</dt>
+	 *             <dd>when an unexpected internal server error occurred</dd>
+	 *             </dl>
+	 * @since 4.5
 	 */
-	protected abstract LargeFileRepository getLargeFileRepository();
+	protected abstract LargeFileRepository getLargeFileRepository(
+			LfsRequest request, String path) throws LfsException;
+
+	/**
+	 * LFS request.
+	 *
+	 * @since 4.5
+	 */
+	protected static class LfsRequest {
+		private String operation;
+
+		private List<LfsObject> objects;
+
+		/**
+		 * Get the LFS operation.
+		 *
+		 * @return the operation
+		 */
+		public String getOperation() {
+			return operation;
+		}
+
+		/**
+		 * Get the LFS objects.
+		 *
+		 * @return the objects
+		 */
+		public List<LfsObject> getObjects() {
+			return objects;
+		}
+	}
 
 	@Override
 	protected void doPost(HttpServletRequest req, HttpServletResponse res)
 			throws ServletException, IOException {
-		res.setStatus(SC_OK);
-		res.setContentType(CONTENTTYPE_VND_GIT_LFS_JSON);
-
 		Writer w = new BufferedWriter(
 				new OutputStreamWriter(res.getOutputStream(), UTF_8));
 
-		Reader r = new BufferedReader(new InputStreamReader(req.getInputStream(), UTF_8));
+		Reader r = new BufferedReader(
+				new InputStreamReader(req.getInputStream(), UTF_8));
 		LfsRequest request = gson.fromJson(r, LfsRequest.class);
+		String path = req.getPathInfo();
 
-		LargeFileRepository repo = getLargeFileRepository();
-		if (repo == null) {
-			res.setStatus(SC_SERVICE_UNAVAILABLE);
-			return;
+		res.setContentType(CONTENTTYPE_VND_GIT_LFS_JSON);
+		LargeFileRepository repo = null;
+		try {
+			repo = getLargeFileRepository(request, path);
+			if (repo == null) {
+				throw new LfsException("unexpected error"); //$NON-NLS-1$
+			}
+			res.setStatus(SC_OK);
+			TransferHandler handler = TransferHandler
+					.forOperation(request.operation, repo, request.objects);
+			gson.toJson(handler.process(), w);
+		} catch (LfsValidationError e) {
+			sendError(res, w, SC_UNPROCESSABLE_ENTITY, e.getMessage());
+		} catch (LfsRepositoryNotFound e) {
+			sendError(res, w, SC_NOT_FOUND, e.getMessage());
+		} catch (LfsRepositoryReadOnly e) {
+			sendError(res, w, SC_FORBIDDEN, e.getMessage());
+		} catch (LfsRateLimitExceeded e) {
+			sendError(res, w, SC_RATE_LIMIT_EXCEEDED, e.getMessage());
+		} catch (LfsBandwidthLimitExceeded e) {
+			sendError(res, w, SC_BANDWIDTH_LIMIT_EXCEEDED, e.getMessage());
+		} catch (LfsInsufficientStorage e) {
+			sendError(res, w, SC_INSUFFICIENT_STORAGE, e.getMessage());
+		} catch (LfsUnavailable e) {
+			sendError(res, w, SC_SERVICE_UNAVAILABLE, e.getMessage());
+		} catch (LfsException e) {
+			sendError(res, w, SC_INTERNAL_SERVER_ERROR, e.getMessage());
+		} finally {
+			w.flush();
 		}
-
-		TransferHandler handler = TransferHandler
-				.forOperation(request.operation, repo, request.objects);
-		gson.toJson(handler.process(), w);
-		w.flush();
 	}
 
-	private static class LfsRequest {
-		String operation;
+	static class Error {
+		String message;
 
-		List<LfsObject> objects;
+		Error(String m) {
+			this.message = m;
+		}
 	}
 
-	private static Gson createGson() {
-		GsonBuilder gb = new GsonBuilder()
-				.setFieldNamingPolicy(
-						FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
-				.setPrettyPrinting().disableHtmlEscaping();
-		return gb.create();
+	private void sendError(HttpServletResponse rsp, Writer writer, int status,
+			String message) {
+		rsp.setStatus(status);
+		gson.toJson(new Error(message), writer);
+	}
+
+	private Gson createGson() {
+		return new GsonBuilder()
+				.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
+				.disableHtmlEscaping()
+				.create();
 	}
 }
diff --git a/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/fs/FileLfsServlet.java b/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/fs/FileLfsServlet.java
index 8864af8..2ecba6d 100644
--- a/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/fs/FileLfsServlet.java
+++ b/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/fs/FileLfsServlet.java
@@ -43,6 +43,7 @@
 package org.eclipse.jgit.lfs.server.fs;
 
 import java.io.IOException;
+import java.io.PrintWriter;
 import java.text.MessageFormat;
 
 import javax.servlet.AsyncContext;
@@ -59,6 +60,10 @@
 import org.eclipse.jgit.lfs.lib.LongObjectId;
 import org.eclipse.jgit.lfs.server.internal.LfsServerText;
 
+import com.google.gson.FieldNamingPolicy;
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+
 /**
  * Servlet supporting upload and download of large objects as defined by the
  * GitHub Large File Storage extension API extending git to allow separate
@@ -76,6 +81,8 @@
 
 	private final long timeout;
 
+	private static Gson gson = createGson();
+
 	/**
 	 * @param repository
 	 *            the repository storing the large objects
@@ -106,7 +113,8 @@
 		if (obj != null) {
 			if (repository.getSize(obj) == -1) {
 				sendError(rsp, HttpStatus.SC_NOT_FOUND, MessageFormat
-						.format(LfsServerText.get().objectNotFound, obj));
+						.format(LfsServerText.get().objectNotFound,
+								obj.getName()));
 				return;
 			}
 			AsyncContext context = req.startAsync();
@@ -120,15 +128,16 @@
 	private AnyLongObjectId getObjectToTransfer(HttpServletRequest req,
 			HttpServletResponse rsp) throws IOException {
 		String info = req.getPathInfo();
-		if (info.length() != 1 + Constants.LONG_OBJECT_ID_STRING_LENGTH) {
-			sendError(rsp, HttpStatus.SC_BAD_REQUEST, MessageFormat
+		int length = 1 + Constants.LONG_OBJECT_ID_STRING_LENGTH;
+		if (info.length() != length) {
+			sendError(rsp, HttpStatus.SC_UNPROCESSABLE_ENTITY, MessageFormat
 					.format(LfsServerText.get().invalidPathInfo, info));
 			return null;
 		}
 		try {
-			return LongObjectId.fromString(info.substring(1, 65));
+			return LongObjectId.fromString(info.substring(1, length));
 		} catch (InvalidLongObjectIdException e) {
-			sendError(rsp, HttpStatus.SC_BAD_REQUEST, e.getMessage());
+			sendError(rsp, HttpStatus.SC_UNPROCESSABLE_ENTITY, e.getMessage());
 			return null;
 		}
 	}
@@ -157,11 +166,29 @@
 		}
 	}
 
+	static class Error {
+		String message;
+
+		Error(String m) {
+			this.message = m;
+		}
+	}
+
 	static void sendError(HttpServletResponse rsp, int status, String message)
 			throws IOException {
 		rsp.setStatus(status);
-		// TODO return message in response body in json format as specified in
-		// https://github.com/github/git-lfs/blob/master/docs/api/http-v1-batch.md
+		PrintWriter writer = rsp.getWriter();
+		gson.toJson(new Error(message), writer);
+		writer.flush();
+		writer.close();
 		rsp.flushBuffer();
 	}
+
+	private static Gson createGson() {
+		GsonBuilder gb = new GsonBuilder()
+				.setFieldNamingPolicy(
+						FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
+				.setPrettyPrinting().disableHtmlEscaping();
+		return gb.create();
+	}
 }
diff --git a/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/fs/ObjectDownloadListener.java b/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/fs/ObjectDownloadListener.java
index bfdea4f..f179b6c 100644
--- a/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/fs/ObjectDownloadListener.java
+++ b/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/fs/ObjectDownloadListener.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2015, Matthias Sohn <matthias.sohnk@sap.com>
+ * Copyright (C) 2015, 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
diff --git a/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/fs/ObjectUploadListener.java b/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/fs/ObjectUploadListener.java
index e524ac6..d44b3db 100644
--- a/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/fs/ObjectUploadListener.java
+++ b/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/fs/ObjectUploadListener.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2015, Matthias Sohn <matthias.sohnk@sap.com>
+ * Copyright (C) 2015, 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
diff --git a/org.eclipse.jgit.lfs.test/.settings/org.eclipse.jdt.core.prefs b/org.eclipse.jgit.lfs.test/.settings/org.eclipse.jdt.core.prefs
index dcc0d3a..87210fb 100644
--- a/org.eclipse.jgit.lfs.test/.settings/org.eclipse.jdt.core.prefs
+++ b/org.eclipse.jgit.lfs.test/.settings/org.eclipse.jdt.core.prefs
@@ -99,6 +99,7 @@
 org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled
 org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled
 org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled
+org.eclipse.jdt.core.compiler.problem.unusedExceptionParameter=ignore
 org.eclipse.jdt.core.compiler.problem.unusedImport=error
 org.eclipse.jdt.core.compiler.problem.unusedLabel=error
 org.eclipse.jdt.core.compiler.problem.unusedLocal=error
diff --git a/org.eclipse.jgit.lfs.test/META-INF/MANIFEST.MF b/org.eclipse.jgit.lfs.test/META-INF/MANIFEST.MF
index f0ecdef..c47a675 100644
--- a/org.eclipse.jgit.lfs.test/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.lfs.test/META-INF/MANIFEST.MF
@@ -2,18 +2,18 @@
 Bundle-ManifestVersion: 2
 Bundle-Name: %plugin_name
 Bundle-SymbolicName: org.eclipse.jgit.lfs.test
-Bundle-Version: 4.4.2.qualifier
+Bundle-Version: 4.5.5.qualifier
 Bundle-Vendor: %provider_name
 Bundle-Localization: plugin
 Bundle-RequiredExecutionEnvironment: JavaSE-1.7
-Import-Package: org.eclipse.jgit.junit;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.lfs.errors;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.lfs.lib;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.lib;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.util;version="[4.4.2,4.5.0)",
+Import-Package: org.eclipse.jgit.junit;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.lfs.errors;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.lfs.lib;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.lib;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.util;version="[4.5.5,4.6.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.4.2";x-friends:="org.eclipse.jgit.lfs.server.test"
+Export-Package: org.eclipse.jgit.lfs.test;version="4.5.5";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 782c63e..9eeb65a 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.4.2-SNAPSHOT</version>
+    <version>4.5.5-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.lfs.test</artifactId>
diff --git a/org.eclipse.jgit.lfs/.settings/org.eclipse.jdt.core.prefs b/org.eclipse.jgit.lfs/.settings/org.eclipse.jdt.core.prefs
index ff39d16..1ce7cd0 100644
--- a/org.eclipse.jgit.lfs/.settings/org.eclipse.jdt.core.prefs
+++ b/org.eclipse.jgit.lfs/.settings/org.eclipse.jdt.core.prefs
@@ -99,6 +99,7 @@
 org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled
 org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled
 org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled
+org.eclipse.jdt.core.compiler.problem.unusedExceptionParameter=ignore
 org.eclipse.jdt.core.compiler.problem.unusedImport=error
 org.eclipse.jdt.core.compiler.problem.unusedLabel=error
 org.eclipse.jdt.core.compiler.problem.unusedLocal=error
diff --git a/org.eclipse.jgit.lfs/META-INF/MANIFEST.MF b/org.eclipse.jgit.lfs/META-INF/MANIFEST.MF
index 77b6fce..f9564d3 100644
--- a/org.eclipse.jgit.lfs/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.lfs/META-INF/MANIFEST.MF
@@ -2,14 +2,14 @@
 Bundle-ManifestVersion: 2
 Bundle-Name: %plugin_name
 Bundle-SymbolicName: org.eclipse.jgit.lfs
-Bundle-Version: 4.4.2.qualifier
+Bundle-Version: 4.5.5.qualifier
 Bundle-Localization: plugin
 Bundle-Vendor: %provider_name
-Export-Package: org.eclipse.jgit.lfs.errors;version="4.4.2",
- org.eclipse.jgit.lfs.internal;version="4.4.2";x-friends:="org.eclipse.jgit.lfs.test",
- org.eclipse.jgit.lfs.lib;version="4.4.2"
+Export-Package: org.eclipse.jgit.lfs.errors;version="4.5.5",
+ org.eclipse.jgit.lfs.internal;version="4.5.5";x-friends:="org.eclipse.jgit.lfs.test",
+ org.eclipse.jgit.lfs.lib;version="4.5.5"
 Bundle-RequiredExecutionEnvironment: JavaSE-1.7
-Import-Package: org.eclipse.jgit.internal.storage.file;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.lib;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.nls;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.util;version="[4.4.2,4.5.0)"
+Import-Package: org.eclipse.jgit.internal.storage.file;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.lib;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.nls;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.util;version="[4.5.5,4.6.0)"
diff --git a/org.eclipse.jgit.lfs/pom.xml b/org.eclipse.jgit.lfs/pom.xml
index 1e6fd7d..fcc74ab 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.4.2-SNAPSHOT</version>
+    <version>4.5.5-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.lfs</artifactId>
diff --git a/org.eclipse.jgit.lfs/resources/org/eclipse/jgit/lfs/internal/LfsText.properties b/org.eclipse.jgit.lfs/resources/org/eclipse/jgit/lfs/internal/LfsText.properties
index 700e2d5..7c3aea2 100644
--- a/org.eclipse.jgit.lfs/resources/org/eclipse/jgit/lfs/internal/LfsText.properties
+++ b/org.eclipse.jgit.lfs/resources/org/eclipse/jgit/lfs/internal/LfsText.properties
@@ -1,4 +1,7 @@
 incorrectLONG_OBJECT_ID_LENGTH=Incorrect LONG_OBJECT_ID_LENGTH.
 invalidLongId=Invalid id: {0}
 invalidLongIdLength=Invalid id length {0}; should be {1}
-requiredHashFunctionNotAvailable=Required hash function {0} not available.
\ No newline at end of file
+requiredHashFunctionNotAvailable=Required hash function {0} not available.
+repositoryNotFound=Repository {0} not found
+repositoryReadOnly=Repository {0} is read-only
+lfsUnavailable=LFS is not available for repository {0}
\ No newline at end of file
diff --git a/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/errors/LfsBandwidthLimitExceeded.java b/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/errors/LfsBandwidthLimitExceeded.java
new file mode 100644
index 0000000..1b1baec
--- /dev/null
+++ b/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/errors/LfsBandwidthLimitExceeded.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2016, David Pursehouse <david.pursehouse@gmail.com>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ *   copyright notice, this list of conditions and the following
+ *   disclaimer in the documentation and/or other materials provided
+ *   with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ *   names of its contributors may be used to endorse or promote
+ *   products derived from this software without specific prior
+ *   written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.lfs.errors;
+
+/**
+ * Thrown when the bandwidth limit for the user or repository has been exceeded.
+ *
+ * @since 4.5
+ *
+ */
+public class LfsBandwidthLimitExceeded extends LfsException {
+	private static final long serialVersionUID = 1L;
+
+	/**
+	 * @param message
+	 *            error message, which may be shown to an end-user.
+	 */
+	public LfsBandwidthLimitExceeded(String message) {
+		super(message);
+	}
+}
diff --git a/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/errors/LfsException.java b/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/errors/LfsException.java
new file mode 100644
index 0000000..3b83639
--- /dev/null
+++ b/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/errors/LfsException.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2016, David Pursehouse <david.pursehouse@gmail.com>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ *   copyright notice, this list of conditions and the following
+ *   disclaimer in the documentation and/or other materials provided
+ *   with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ *   names of its contributors may be used to endorse or promote
+ *   products derived from this software without specific prior
+ *   written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.lfs.errors;
+
+/**
+ * Thrown when an error occurs during LFS operation.
+ *
+ * @since 4.5
+ */
+public class LfsException extends Exception {
+	private static final long serialVersionUID = 1L;
+
+	/**
+	 * @param message
+	 *            error message, which may be shown to an end-user.
+	 */
+	public LfsException(String message) {
+		super(message);
+	}
+}
diff --git a/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/errors/LfsInsufficientStorage.java b/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/errors/LfsInsufficientStorage.java
new file mode 100644
index 0000000..4faace9
--- /dev/null
+++ b/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/errors/LfsInsufficientStorage.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2016, David Pursehouse <david.pursehouse@gmail.com>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ *   copyright notice, this list of conditions and the following
+ *   disclaimer in the documentation and/or other materials provided
+ *   with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ *   names of its contributors may be used to endorse or promote
+ *   products derived from this software without specific prior
+ *   written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.lfs.errors;
+
+/**
+ * Thrown when there is insufficient storage on the server.
+ *
+ * @since 4.5
+ *
+ */
+public class LfsInsufficientStorage extends LfsException {
+	private static final long serialVersionUID = 1L;
+
+	/**
+	 * @param message
+	 *            error message, which may be shown to an end-user.
+	 */
+	public LfsInsufficientStorage(String message) {
+		super(message);
+	}
+}
diff --git a/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/errors/LfsRateLimitExceeded.java b/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/errors/LfsRateLimitExceeded.java
new file mode 100644
index 0000000..606783041
--- /dev/null
+++ b/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/errors/LfsRateLimitExceeded.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2016, David Pursehouse <david.pursehouse@gmail.com>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ *   copyright notice, this list of conditions and the following
+ *   disclaimer in the documentation and/or other materials provided
+ *   with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ *   names of its contributors may be used to endorse or promote
+ *   products derived from this software without specific prior
+ *   written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.lfs.errors;
+
+/**
+ * Thrown when the user has hit a rate limit with the server.
+ *
+ * @since 4.5
+ *
+ */
+public class LfsRateLimitExceeded extends LfsException {
+	private static final long serialVersionUID = 1L;
+
+	/**
+	 * @param message
+	 *            error message, which may be shown to an end-user.
+	 */
+	public LfsRateLimitExceeded(String message) {
+		super(message);
+	}
+}
diff --git a/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/errors/LfsRepositoryNotFound.java b/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/errors/LfsRepositoryNotFound.java
new file mode 100644
index 0000000..52c932a
--- /dev/null
+++ b/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/errors/LfsRepositoryNotFound.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2016, David Pursehouse <david.pursehouse@gmail.com>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ *   copyright notice, this list of conditions and the following
+ *   disclaimer in the documentation and/or other materials provided
+ *   with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ *   names of its contributors may be used to endorse or promote
+ *   products derived from this software without specific prior
+ *   written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.lfs.errors;
+
+import java.text.MessageFormat;
+
+import org.eclipse.jgit.lfs.internal.LfsText;
+
+/**
+ * Thrown when the repository does not exist for the user.
+ *
+ * @since 4.5
+ */
+public class LfsRepositoryNotFound extends LfsException {
+	private static final long serialVersionUID = 1L;
+
+	/**
+	 * @param name
+	 *            the repository name.
+	 *
+	 */
+	public LfsRepositoryNotFound(String name) {
+		super(MessageFormat.format(LfsText.get().repositoryNotFound, name));
+	}
+}
diff --git a/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/errors/LfsRepositoryReadOnly.java b/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/errors/LfsRepositoryReadOnly.java
new file mode 100644
index 0000000..3610377
--- /dev/null
+++ b/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/errors/LfsRepositoryReadOnly.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2016, David Pursehouse <david.pursehouse@gmail.com>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ *   copyright notice, this list of conditions and the following
+ *   disclaimer in the documentation and/or other materials provided
+ *   with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ *   names of its contributors may be used to endorse or promote
+ *   products derived from this software without specific prior
+ *   written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.lfs.errors;
+
+import java.text.MessageFormat;
+
+import org.eclipse.jgit.lfs.internal.LfsText;
+
+/**
+ * Thrown when the user has read, but not write access. Only applicable when the
+ * operation in the request is "upload".
+ *
+ * @since 4.5
+ */
+public class LfsRepositoryReadOnly extends LfsException {
+	private static final long serialVersionUID = 1L;
+
+	/**
+	 * @param name
+	 *            the repository name.
+	 */
+	public LfsRepositoryReadOnly(String name) {
+		super(MessageFormat.format(LfsText.get().repositoryReadOnly, name));
+	}
+}
diff --git a/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/errors/LfsUnavailable.java b/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/errors/LfsUnavailable.java
new file mode 100644
index 0000000..ecb5e9e
--- /dev/null
+++ b/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/errors/LfsUnavailable.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2016, David Pursehouse <david.pursehouse@gmail.com>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ *   copyright notice, this list of conditions and the following
+ *   disclaimer in the documentation and/or other materials provided
+ *   with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ *   names of its contributors may be used to endorse or promote
+ *   products derived from this software without specific prior
+ *   written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.lfs.errors;
+
+import java.text.MessageFormat;
+
+import org.eclipse.jgit.lfs.internal.LfsText;
+
+/**
+ * Thrown when LFS is not available.
+ *
+ * @since 4.5
+ */
+public class LfsUnavailable extends LfsException {
+	private static final long serialVersionUID = 1L;
+
+	/**
+	 * @param name
+	 *            the repository name.
+	 */
+	public LfsUnavailable(String name) {
+		super(MessageFormat.format(LfsText.get().lfsUnavailable, name));
+	}
+}
diff --git a/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/errors/LfsValidationError.java b/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/errors/LfsValidationError.java
new file mode 100644
index 0000000..5e445ec
--- /dev/null
+++ b/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/errors/LfsValidationError.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2016, David Pursehouse <david.pursehouse@gmail.com>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ *   copyright notice, this list of conditions and the following
+ *   disclaimer in the documentation and/or other materials provided
+ *   with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ *   names of its contributors may be used to endorse or promote
+ *   products derived from this software without specific prior
+ *   written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.lfs.errors;
+
+/**
+ * Thrown when there is a validation error with one or more of the objects in
+ * the request.
+ *
+ * @since 4.5
+ */
+public class LfsValidationError extends LfsException {
+	private static final long serialVersionUID = 1L;
+
+	/**
+	 * @param message
+	 *            error message, which may be shown to an end-user.
+	 */
+	public LfsValidationError(String message) {
+		super(message);
+	}
+}
diff --git a/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/internal/LfsText.java b/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/internal/LfsText.java
index eaffcc9..365eaa1 100644
--- a/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/internal/LfsText.java
+++ b/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/internal/LfsText.java
@@ -62,4 +62,7 @@
 	/***/ public String invalidLongId;
 	/***/ public String invalidLongIdLength;
 	/***/ public String requiredHashFunctionNotAvailable;
+	/***/ public String repositoryNotFound;
+	/***/ public String repositoryReadOnly;
+	/***/ public String lfsUnavailable;
 }
diff --git a/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/lib/Constants.java b/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/lib/Constants.java
index 3cadbf7..d246412 100644
--- a/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/lib/Constants.java
+++ b/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/lib/Constants.java
@@ -102,7 +102,7 @@
 
 	/**
 	 * Content type used by LFS REST API as defined in
-	 * {@link "https://github.com/github/git-lfs/blob/master/docs/api/http-v1-batch.md"}
+	 * {@link "https://github.com/github/git-lfs/blob/master/docs/api/v1/http-v1-batch.md"}
 	 */
 	public static String CONTENT_TYPE_GIT_LFS_JSON = "application/vnd.git-lfs+json";
 
@@ -111,4 +111,4 @@
 	 * {@link "https://www.ietf.org/rfc/rfc2046.txt"}
 	 */
 	public static String HDR_APPLICATION_OCTET_STREAM = "application/octet-stream";
-}
\ No newline at end of file
+}
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 5af0b63..cd27138 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.4.2.qualifier"
+      version="4.5.5.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 c91a46d..a2a86b2 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.4.2-SNAPSHOT</version>
+    <version>4.5.5-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 f94dce8..f1fae01 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.4.2.qualifier"
+      version="4.5.5.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 4f1cc80..7b7e626 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.4.2-SNAPSHOT</version>
+    <version>4.5.5-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 0014f60..a3bd7c9 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.4.2.qualifier"
+      version="4.5.5.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 a4c763b..296d3c5 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.4.2-SNAPSHOT</version>
+    <version>4.5.5-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 4f0ada2..a7ce75e 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.4.2.qualifier"
+      version="4.5.5.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 154df15..e7bbe2e 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.4.2-SNAPSHOT</version>
+    <version>4.5.5-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 a6ae6d3..60ca4ab 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.4.2.qualifier"
+      version="4.5.5.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.4.1" match="equivalent"/>
-      <import feature="org.eclipse.jgit.lfs" version="4.4.1" match="equivalent"/>
+      <import feature="org.eclipse.jgit" version="4.5.0" match="equivalent"/>
+      <import feature="org.eclipse.jgit.lfs" version="4.5.0" 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 2013fc4..c42fd96 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.4.2-SNAPSHOT</version>
+    <version>4.5.5-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 cc52233..39feedf 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.4.2.qualifier"
+      version="4.5.5.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 a295873..eed1686 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.4.2-SNAPSHOT</version>
+    <version>4.5.5-SNAPSHOT</version>
   </parent>
 
   <groupId>org.eclipse.jgit.feature</groupId>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.repository/category.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.repository/category.xml
index c2bfc92..15d10e6 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.repository/category.xml
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.repository/category.xml
@@ -24,9 +24,6 @@
    <feature url="features/org.eclipse.jgit.lfs_0.0.0.qualifier.jar" id="org.eclipse.jgit.lfs" version="0.0.0">
       <category name="JGit"/>
    </feature>
-   <feature url="features/org.eclipse.jgit.lfs_0.0.0.qualifier.jar" id="org.eclipse.jgit.lfs" version="0.0.0">
-      <category name="JGit"/>
-   </feature>
    <category-def name="JGit" label="JGit">
       <description>
          JGit
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 43e15e5..7a6d7ed 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.4.2-SNAPSHOT</version>
+    <version>4.5.5-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 4f99110..c63047c 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.4.2.qualifier"
+      version="4.5.5.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 f737251..2f07639 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.4.2-SNAPSHOT</version>
+    <version>4.5.5-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 a4ba267..c1615cd 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.4.2.qualifier
+Bundle-Version: 4.5.5.qualifier
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 f7d5622..a1f5abf 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.6.target
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.6.target
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
 <?pde?>
 <!-- generated with https://github.com/mbarbero/fr.obeo.releng.targetplatform -->
-<target name="jgit-4.6" sequenceNumber="1463612069">
+<target name="jgit-4.6" sequenceNumber="1465553981">
   <locations>
     <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
       <unit id="org.eclipse.jetty.client" version="9.2.13.v20150730"/>
@@ -58,7 +58,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/downloads/drops/S20160518051658/repository/"/>
+      <repository location="http://download.eclipse.org/tools/orbit/downloads/drops/R20160520211859/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/pom.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/pom.xml
index cd6b4a3..0c77c0e 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.4.2-SNAPSHOT</version>
+    <version>4.5.5-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.target</artifactId>
diff --git a/org.eclipse.jgit.packaging/pom.xml b/org.eclipse.jgit.packaging/pom.xml
index 71b859d..ef9fb98 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.4.2-SNAPSHOT</version>
+  <version>4.5.5-SNAPSHOT</version>
   <packaging>pom</packaging>
 
   <name>JGit Tycho Parent</name>
diff --git a/org.eclipse.jgit.pgm.test/.settings/org.eclipse.jdt.core.prefs b/org.eclipse.jgit.pgm.test/.settings/org.eclipse.jdt.core.prefs
index dcc0d3a..87210fb 100644
--- a/org.eclipse.jgit.pgm.test/.settings/org.eclipse.jdt.core.prefs
+++ b/org.eclipse.jgit.pgm.test/.settings/org.eclipse.jdt.core.prefs
@@ -99,6 +99,7 @@
 org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled
 org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled
 org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled
+org.eclipse.jdt.core.compiler.problem.unusedExceptionParameter=ignore
 org.eclipse.jdt.core.compiler.problem.unusedImport=error
 org.eclipse.jdt.core.compiler.problem.unusedLabel=error
 org.eclipse.jdt.core.compiler.problem.unusedLocal=error
diff --git a/org.eclipse.jgit.pgm.test/META-INF/MANIFEST.MF b/org.eclipse.jgit.pgm.test/META-INF/MANIFEST.MF
index 66d010c..e1eaf4a 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.4.2.qualifier
+Bundle-Version: 4.5.5.qualifier
 Bundle-Vendor: %provider_name
 Bundle-Localization: plugin
 Bundle-ActivationPolicy: lazy
 Bundle-RequiredExecutionEnvironment: JavaSE-1.7
-Import-Package: org.eclipse.jgit.api;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.api.errors;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.diff;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.dircache;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.internal.storage.file;version="4.4.2",
- org.eclipse.jgit.junit;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.lib;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.merge;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.pgm;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.pgm.internal;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.pgm.opt;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.revwalk;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.storage.file;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.transport;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.treewalk;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.util;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.util.io;version="[4.4.2,4.5.0)",
+Import-Package: org.eclipse.jgit.api;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.api.errors;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.diff;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.dircache;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.internal.storage.file;version="4.5.5",
+ org.eclipse.jgit.junit;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.lib;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.merge;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.pgm;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.pgm.internal;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.pgm.opt;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.revwalk;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.storage.file;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.transport;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.treewalk;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.util;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.util.io;version="[4.5.5,4.6.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 eeccff4..1cde872 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.4.2-SNAPSHOT</version>
+    <version>4.5.5-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.pgm.test</artifactId>
diff --git a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/CheckoutTest.java b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/CheckoutTest.java
index e690ad6..3651542 100644
--- a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/CheckoutTest.java
+++ b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/CheckoutTest.java
@@ -47,6 +47,7 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
 import java.io.File;
 import java.nio.file.Files;
@@ -236,8 +237,8 @@
 	 * <li>Checkout branch '1'
 	 * </ol>
 	 * <p>
-	 * The working tree should contain 'a' with FileMode.REGULAR_FILE after the
-	 * checkout.
+	 * The checkout has to delete folder but the workingtree contains a dirty
+	 * file at this path. The checkout should fail like in native git.
 	 *
 	 * @throws Exception
 	 */
@@ -266,11 +267,15 @@
 					db.getFS());
 			assertEquals(FileMode.REGULAR_FILE, entry.getMode());
 
-			git.checkout().setName(branch_1.getName()).call();
-
-			entry = new FileTreeIterator.FileEntry(new File(db.getWorkTree(), "a"),
-					db.getFS());
-			assertEquals(FileMode.REGULAR_FILE, entry.getMode());
+			try {
+				git.checkout().setName(branch_1.getName()).call();
+				fail("Don't get the expected conflict");
+			} catch (CheckoutConflictException e) {
+				assertEquals("[a]", e.getConflictingPaths().toString());
+				entry = new FileTreeIterator.FileEntry(
+						new File(db.getWorkTree(), "a"), db.getFS());
+				assertEquals(FileMode.REGULAR_FILE, entry.getMode());
+			}
 		}
 	}
 
diff --git a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/CleanTest.java b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/CleanTest.java
new file mode 100644
index 0000000..bbac296
--- /dev/null
+++ b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/CleanTest.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2016, Ned Twigg <ned.twigg@diffplug.com>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ *   copyright notice, this list of conditions and the following
+ *   disclaimer in the documentation and/or other materials provided
+ *   with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ *   names of its contributors may be used to endorse or promote
+ *   products derived from this software without specific prior
+ *   written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.eclipse.jgit.pgm;
+
+import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.lib.CLIRepositoryTestCase;
+import org.junit.Test;
+import static org.eclipse.jgit.junit.JGitTestUtil.check;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertFalse;
+
+public class CleanTest extends CLIRepositoryTestCase {
+	@Test
+	public void testCleanRequiresForce() throws Exception {
+		try (Git git = new Git(db)) {
+			assertArrayOfLinesEquals(
+					new String[] { "Removing a", "Removing b" },
+					execute("git clean"));
+		} catch (Die e) {
+			// TODO: should be "fatal: clean.requireForce defaults to true and
+			// neither -i, -n, nor -f given; refusing to clean" but we don't
+			// support -i yet. Fix this when/if we add support for -i.
+			assertEquals(
+					"fatal: clean.requireForce defaults to true and neither -n nor -f given; refusing to clean",
+					e.getMessage());
+		}
+	}
+
+	@Test
+	public void testCleanRequiresForceConfig() throws Exception {
+		try (Git git = new Git(db)) {
+			git.getRepository().getConfig().setBoolean("clean", null,
+					"requireForce", false);
+			assertArrayOfLinesEquals(
+					new String[] { "" },
+					execute("git clean"));
+		}
+	}
+
+	@Test
+	public void testCleanLeaveDirs() throws Exception {
+		try (Git git = new Git(db)) {
+			git.commit().setMessage("initial commit").call();
+
+			writeTrashFile("dir/file", "someData");
+			writeTrashFile("a", "someData");
+			writeTrashFile("b", "someData");
+
+			// all these files should be there
+			assertTrue(check(db, "a"));
+			assertTrue(check(db, "b"));
+			assertTrue(check(db, "dir/file"));
+
+			// dry run should make no change
+			assertArrayOfLinesEquals(
+					new String[] { "Removing a", "Removing b" },
+					execute("git clean -n"));
+			assertTrue(check(db, "a"));
+			assertTrue(check(db, "b"));
+			assertTrue(check(db, "dir/file"));
+
+			// force should make a change
+			assertArrayOfLinesEquals(
+					new String[] { "Removing a", "Removing b" },
+					execute("git clean -f"));
+			assertFalse(check(db, "a"));
+			assertFalse(check(db, "b"));
+			assertTrue(check(db, "dir/file"));
+		}
+	}
+
+	@Test
+	public void testCleanDeleteDirs() throws Exception {
+		try (Git git = new Git(db)) {
+			git.commit().setMessage("initial commit").call();
+
+			writeTrashFile("dir/file", "someData");
+			writeTrashFile("a", "someData");
+			writeTrashFile("b", "someData");
+
+			// all these files should be there
+			assertTrue(check(db, "a"));
+			assertTrue(check(db, "b"));
+			assertTrue(check(db, "dir/file"));
+
+			assertArrayOfLinesEquals(
+					new String[] { "Removing a", "Removing b",
+							"Removing dir/" },
+					execute("git clean -d -f"));
+			assertFalse(check(db, "a"));
+			assertFalse(check(db, "b"));
+			assertFalse(check(db, "dir/file"));
+		}
+	}
+}
diff --git a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/CloneTest.java b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/CloneTest.java
index fe80388..2c0abd7 100644
--- a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/CloneTest.java
+++ b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/CloneTest.java
@@ -135,6 +135,7 @@
 		assertEquals("expected 1 branch", 1, branches.size());
 	}
 
+	@Test
 	public void testCloneBare() throws Exception {
 		createInitialCommit();
 
diff --git a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/TagTest.java b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/TagTest.java
index 0fe25f5..03391a0 100644
--- a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/TagTest.java
+++ b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/TagTest.java
@@ -46,6 +46,7 @@
 
 import org.eclipse.jgit.api.Git;
 import org.eclipse.jgit.lib.CLIRepositoryTestCase;
+import org.eclipse.jgit.lib.Ref;
 import org.junit.Before;
 import org.junit.Test;
 
@@ -70,4 +71,26 @@
 		assertEquals("fatal: tag 'test' already exists",
 				executeUnchecked("git tag test")[0]);
 	}
+
+	@Test
+	public void testTagDelete() throws Exception {
+		git.tag().setName("test").call();
+
+		Ref ref = git.getRepository().getTags().get("test");
+		assertEquals("refs/tags/test", ref.getName());
+
+		assertEquals("", executeUnchecked("git tag -d test")[0]);
+		Ref deletedRef = git.getRepository().getTags().get("test");
+		assertEquals(null, deletedRef);
+	}
+
+	@Test
+	public void testTagDeleteFail() throws Exception {
+		try {
+			assertEquals("fatal: error: tag 'test' not found.",
+					executeUnchecked("git tag -d test")[0]);
+		} catch (Die e) {
+			assertEquals("fatal: error: tag 'test' not found", e.getMessage());
+		}
+	}
 }
diff --git a/org.eclipse.jgit.pgm/.settings/org.eclipse.jdt.core.prefs b/org.eclipse.jgit.pgm/.settings/org.eclipse.jdt.core.prefs
index 45d6d2c..bfaf736 100644
--- a/org.eclipse.jgit.pgm/.settings/org.eclipse.jdt.core.prefs
+++ b/org.eclipse.jgit.pgm/.settings/org.eclipse.jdt.core.prefs
@@ -99,6 +99,7 @@
 org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled
 org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled
 org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled
+org.eclipse.jdt.core.compiler.problem.unusedExceptionParameter=ignore
 org.eclipse.jdt.core.compiler.problem.unusedImport=error
 org.eclipse.jdt.core.compiler.problem.unusedLabel=error
 org.eclipse.jdt.core.compiler.problem.unusedLocal=error
diff --git a/org.eclipse.jgit.pgm/META-INF/MANIFEST.MF b/org.eclipse.jgit.pgm/META-INF/MANIFEST.MF
index cf2a03e..f31e63f 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.4.2.qualifier
+Bundle-Version: 4.5.5.qualifier
 Bundle-Vendor: %provider_name
 Bundle-ActivationPolicy: lazy
 Bundle-Localization: plugin
@@ -26,45 +26,45 @@
  org.eclipse.jetty.util.log;version="[9.0.0,10.0.0)",
  org.eclipse.jetty.util.security;version="[9.0.0,10.0.0)",
  org.eclipse.jetty.util.thread;version="[9.0.0,10.0.0)",
- org.eclipse.jgit.api;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.api.errors;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.archive;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.awtui;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.blame;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.diff;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.dircache;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.errors;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.gitrepo;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.internal.ketch;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.internal.storage.file;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.internal.storage.pack;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.internal.storage.reftree;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.lfs.lib;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.lfs.server;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.lfs.server.fs;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.lfs.server.s3;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.lib;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.merge;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.nls;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.notes;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.revplot;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.revwalk;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.revwalk.filter;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.storage.file;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.storage.pack;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.transport;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.transport.http.apache;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.transport.resolver;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.treewalk;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.treewalk.filter;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.util;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.util.io;version="[4.4.2,4.5.0)",
+ org.eclipse.jgit.api;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.api.errors;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.archive;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.awtui;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.blame;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.diff;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.dircache;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.errors;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.gitrepo;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.internal.ketch;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.internal.storage.file;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.internal.storage.pack;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.internal.storage.reftree;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.lfs.lib;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.lfs.server;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.lfs.server.fs;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.lfs.server.s3;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.lib;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.merge;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.nls;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.notes;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.revplot;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.revwalk;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.revwalk.filter;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.storage.file;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.storage.pack;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.transport;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.transport.http.apache;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.transport.resolver;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.treewalk;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.treewalk.filter;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.util;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.util.io;version="[4.5.5,4.6.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.4.2";
+Export-Package: org.eclipse.jgit.console;version="4.5.5";
   uses:="org.eclipse.jgit.transport,
    org.eclipse.jgit.util",
- org.eclipse.jgit.pgm;version="4.4.2";
+ org.eclipse.jgit.pgm;version="4.5.5";
   uses:="org.eclipse.jgit.revwalk,
    org.eclipse.jgit.treewalk.filter,
    org.eclipse.jgit.pgm.opt,
@@ -75,11 +75,11 @@
    org.eclipse.jgit.treewalk,
    javax.swing,
    org.eclipse.jgit.transport",
- org.eclipse.jgit.pgm.debug;version="4.4.2";
+ org.eclipse.jgit.pgm.debug;version="4.5.5";
   uses:="org.eclipse.jgit.util.io,
    org.eclipse.jgit.pgm",
- org.eclipse.jgit.pgm.internal;version="4.4.2";x-friends:="org.eclipse.jgit.pgm.test,org.eclipse.jgit.test",
- org.eclipse.jgit.pgm.opt;version="4.4.2";
+ org.eclipse.jgit.pgm.internal;version="4.5.5";x-friends:="org.eclipse.jgit.pgm.test,org.eclipse.jgit.test",
+ org.eclipse.jgit.pgm.opt;version="4.5.5";
   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 aeb8b37..3f395ad 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.4.2.qualifier
-Eclipse-SourceBundle: org.eclipse.jgit.pgm;version="4.4.2.qualifier";roots="."
+Bundle-Version: 4.5.5.qualifier
+Eclipse-SourceBundle: org.eclipse.jgit.pgm;version="4.5.5.qualifier";roots="."
diff --git a/org.eclipse.jgit.pgm/META-INF/services/org.eclipse.jgit.pgm.TextBuiltin b/org.eclipse.jgit.pgm/META-INF/services/org.eclipse.jgit.pgm.TextBuiltin
index c159f7d..5495be6 100644
--- a/org.eclipse.jgit.pgm/META-INF/services/org.eclipse.jgit.pgm.TextBuiltin
+++ b/org.eclipse.jgit.pgm/META-INF/services/org.eclipse.jgit.pgm.TextBuiltin
@@ -4,6 +4,7 @@
 org.eclipse.jgit.pgm.Blame
 org.eclipse.jgit.pgm.Branch
 org.eclipse.jgit.pgm.Checkout
+org.eclipse.jgit.pgm.Clean
 org.eclipse.jgit.pgm.Clone
 org.eclipse.jgit.pgm.Commit
 org.eclipse.jgit.pgm.Config
diff --git a/org.eclipse.jgit.pgm/pom.xml b/org.eclipse.jgit.pgm/pom.xml
index f469cc2..a8537b9 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.4.2-SNAPSHOT</version>
+    <version>4.5.5-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.pgm</artifactId>
@@ -129,17 +129,6 @@
     </dependency>
 
     <dependency>
-      <groupId>junit</groupId>
-      <artifactId>junit</artifactId>
-    </dependency>
-
-    <dependency>
-      <groupId>org.eclipse.jgit</groupId>
-      <artifactId>org.eclipse.jgit.junit.http</artifactId>
-      <version>${project.version}</version>
-    </dependency>
-
-    <dependency>
       <groupId>org.eclipse.jgit</groupId>
       <artifactId>org.eclipse.jgit.lfs</artifactId>
       <version>${project.version}</version>
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 abe04b8..1d4bf76 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
@@ -46,6 +46,7 @@
 changesToBeCommitted=Changes to be committed:
 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
 clonedEmptyRepository=warning: You appear to have cloned an empty repository.
 cloningInto=Cloning into ''{0}''...
 commitLabel=commit
@@ -113,6 +114,7 @@
 metaVar_filepattern=filepattern
 metaVar_gitDir=GIT_DIR
 metaVar_hostName=HOSTNAME
+metaVar_ketchServerType=SERVERTYPE
 metaVar_lfsStorage=STORAGE
 metaVar_linesOfContext=lines
 metaVar_message=message
@@ -177,6 +179,7 @@
 remoteMessage=remote: {0}
 remoteRefObjectChangedIsNotExpectedOne=remote ref object changed - is not expected one {0}
 remoteSideDoesNotSupportDeletingRefs=remote side does not support deleting refs
+removing=Removing {0}
 repaint=Repaint
 s3InvalidBucket=Invalid S3 bucket ''{0}''
 serviceNotSupported=Service ''{0}'' not supported
@@ -198,6 +201,7 @@
 switchedToBranch=Switched to branch ''{0}''
 tagAlreadyExists=tag ''{0}'' already exists
 tagLabel=tag
+tagNotFound=error: tag ''{0}'' not found.
 taggerInfo=Tagger: {0} <{1}>
 timeInMilliSeconds={0} ms
 treeIsRequired=argument tree is required
@@ -212,6 +216,7 @@
 usage_Aggressive=This option will cause gc to more aggressively optimize the repository at the expense of taking much more time
 usage_bareClone=Make a bare Git repository. That is, instead of creating [DIRECTORY] and placing the administrative files in [DIRECTORY]/.git, make the [DIRECTORY] itself the $GIT_DIR.
 usage_Blame=Show what revision and author last modified each line
+usage_Clean=Remove untracked files from the working tree
 usage_CommandLineClientForamazonsS3Service=Command line client for Amazon's S3 service
 usage_CommitAll=commit all modified and deleted files
 usage_CommitAuthor=Override the author name used in the commit. You can use the standard A U Thor <author@example.com> format.
@@ -228,6 +233,7 @@
 usage_Gc=Cleanup unnecessary files and optimize the local repository
 usage_Glog=View commit history as a graph
 usage_IndexPack=Build pack index file for an existing packed archive
+usage_ketchServerType=Ketch server type
 usage_LFSDirectory=Directory to store large objects
 usage_LFSPort=Server http port
 usage_LFSRunStore=Store (fs | s3), store lfs objects in file system or Amazon S3
@@ -323,6 +329,7 @@
 usage_fixAThinPackToBeComplete=fix a thin pack to be complete
 usage_forEachRefOutput=for-each-ref output
 usage_forceCheckout=when switching branches, proceed even if the index or the working tree differs from HEAD
+usage_forceClean=required to delete files or directories
 usage_forceCreateBranchEvenExists=force create branch even exists
 usage_forceReplacingAnExistingTag=force replacing an existing tag
 usage_getAndSetOptions=Get and set repository or global options
@@ -362,6 +369,7 @@
 usage_quiet=don't show progress messages
 usage_recordChangesToRepository=Record changes to the repository
 usage_recurseIntoSubtrees=recurse into subtrees
+usage_removeUntrackedDirectories=remove untracked directories
 usage_renameLimit=limit size of rename matrix
 usage_reset=Reset current HEAD to the specified state
 usage_resetReference=Reset to given reference name
@@ -381,6 +389,7 @@
 usage_symbolicVersionForTheProject=Symbolic version for the project
 usage_tags=fetch all tags
 usage_notags=do not fetch tags
+usage_tagDelete=delete tag
 usage_tagMessage=tag message
 usage_untrackedFilesMode=show untracked files
 usage_updateRef=reference to update
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Clean.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Clean.java
new file mode 100644
index 0000000..ce4faf8
--- /dev/null
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Clean.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2016, Ned Twigg <ned.twigg@diffplug.com>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ *   copyright notice, this list of conditions and the following
+ *   disclaimer in the documentation and/or other materials provided
+ *   with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ *   names of its contributors may be used to endorse or promote
+ *   products derived from this software without specific prior
+ *   written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.pgm;
+
+import java.text.MessageFormat;
+import java.util.Set;
+
+import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.pgm.internal.CLIText;
+import org.kohsuke.args4j.Option;
+
+@Command(common = true, usage = "usage_Clean")
+class Clean extends TextBuiltin {
+	@Option(name = "-d", usage = "usage_removeUntrackedDirectories")
+	private boolean dirs = false;
+
+	@Option(name = "--force", aliases = {
+			"-f" }, usage = "usage_forceClean")
+	private boolean force = false;
+
+	@Option(name = "--dryRun", aliases = { "-n" })
+	private boolean dryRun = false;
+
+	@Override
+	protected void run() throws Exception {
+		try (Git git = new Git(db)) {
+			boolean requireForce = git.getRepository().getConfig()
+					.getBoolean("clean", "requireForce", true); //$NON-NLS-1$ //$NON-NLS-2$
+			if (requireForce && !(force || dryRun)) {
+				throw die(CLIText.fatalError(CLIText.get().cleanRequireForce));
+			}
+			// Note that CleanCommand's setForce(true) will delete
+			// .git folders. In the cgit cli, this behavior
+			// requires setting "-f" twice, not sure how to do
+			// this with args4j, so this feature is unimplemented
+			// for now.
+			Set<String> removedFiles = git.clean().setCleanDirectories(dirs)
+					.setDryRun(dryRun).call();
+			for (String removedFile : removedFiles) {
+				outw.println(MessageFormat.format(CLIText.get().removing,
+						removedFile));
+			}
+		}
+	}
+}
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Daemon.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Daemon.java
index 03f3fac..a7bdde9 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Daemon.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Daemon.java
@@ -101,7 +101,7 @@
 	@Option(name = "--export-all", usage = "usage_exportWithoutGitDaemonExportOk")
 	boolean exportAll;
 
-	@Option(name = "--ketch")
+	@Option(name = "--ketch", metaVar = "metaVar_ketchServerType", usage = "usage_ketchServerType")
 	KetchServerType ketchServerType;
 
 	enum KetchServerType {
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Push.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Push.java
index 33ea1de..98af186 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Push.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Push.java
@@ -108,6 +108,9 @@
 	@Option(name = "--dry-run")
 	private boolean dryRun;
 
+	@Option(name = "--push-option", aliases = { "-t" })
+	private List<String> pushOptions = new ArrayList<>();
+
 	private boolean shownURI;
 
 	@Override
@@ -127,6 +130,9 @@
 			push.setThin(thin);
 			push.setAtomic(atomic);
 			push.setTimeout(timeout);
+			if (!pushOptions.isEmpty()) {
+				push.setPushOptions(pushOptions);
+			}
 			Iterable<PushResult> results = push.call();
 			for (PushResult result : results) {
 				try (ObjectReader reader = db.newObjectReader()) {
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Tag.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Tag.java
index 45fceb5..dc4b037 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Tag.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Tag.java
@@ -68,6 +68,9 @@
 	@Option(name = "-f", usage = "usage_forceReplacingAnExistingTag")
 	private boolean force;
 
+	@Option(name = "-d", usage = "usage_tagDelete")
+	private boolean delete;
+
 	@Option(name = "-m", metaVar = "metaVar_message", usage = "usage_tagMessage")
 	private String message = ""; //$NON-NLS-1$
 
@@ -81,19 +84,28 @@
 	protected void run() throws Exception {
 		try (Git git = new Git(db)) {
 			if (tagName != null) {
-				TagCommand command = git.tag().setForceUpdate(force)
-						.setMessage(message).setName(tagName);
-
-				if (object != null) {
-					try (RevWalk walk = new RevWalk(db)) {
-						command.setObjectId(walk.parseAny(object));
+				if (delete) {
+					List<String> deletedTags = git.tagDelete().setTags(tagName)
+							.call();
+					if (deletedTags.isEmpty()) {
+						throw die(MessageFormat
+								.format(CLIText.get().tagNotFound, tagName));
 					}
-				}
-				try {
-					command.call();
-				} catch (RefAlreadyExistsException e) {
-					throw die(MessageFormat.format(CLIText.get().tagAlreadyExists,
-							tagName));
+				} else {
+					TagCommand command = git.tag().setForceUpdate(force)
+							.setMessage(message).setName(tagName);
+
+					if (object != null) {
+						try (RevWalk walk = new RevWalk(db)) {
+							command.setObjectId(walk.parseAny(object));
+						}
+					}
+					try {
+						command.call();
+					} catch (RefAlreadyExistsException e) {
+						throw die(MessageFormat.format(
+								CLIText.get().tagAlreadyExists, tagName));
+					}
 				}
 			} else {
 				ListTagCommand command = git.tagList();
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/LfsStore.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/LfsStore.java
index 00bea86..c4d9548 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/LfsStore.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/LfsStore.java
@@ -253,10 +253,10 @@
 			private static final long serialVersionUID = 1L;
 
 			@Override
-			protected LargeFileRepository getLargeFileRepository() {
+			protected LargeFileRepository getLargeFileRepository(
+					LfsRequest request, String path) {
 				return repository;
 			}
-
 		};
 		app.addServlet(new ServletHolder(protocol), PROTOCOL_PATH);
 
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 e99fe00..90c03e9 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
@@ -125,6 +125,7 @@
 	/***/ public String changesToBeCommitted;
 	/***/ public String checkoutConflict;
 	/***/ public String checkoutConflictPathLine;
+	/***/ public String cleanRequireForce;
 	/***/ public String clonedEmptyRepository;
 	/***/ public String cloningInto;
 	/***/ public String commitLabel;
@@ -246,6 +247,7 @@
 	/***/ public String remoteMessage;
 	/***/ public String remoteRefObjectChangedIsNotExpectedOne;
 	/***/ public String remoteSideDoesNotSupportDeletingRefs;
+	/***/ public String removing;
 	/***/ public String repaint;
 	/***/ public String s3InvalidBucket;
 	/***/ public String serviceNotSupported;
@@ -267,6 +269,7 @@
 	/***/ public String switchedToBranch;
 	/***/ public String tagAlreadyExists;
 	/***/ public String tagLabel;
+	/***/ public String tagNotFound;
 	/***/ public String taggerInfo;
 	/***/ public String timeInMilliSeconds;
 	/***/ public String tooManyRefsGiven;
diff --git a/org.eclipse.jgit.test/.project b/org.eclipse.jgit.test/.project
index a7ac51e..80bb7da 100644
--- a/org.eclipse.jgit.test/.project
+++ b/org.eclipse.jgit.test/.project
@@ -2,9 +2,6 @@
 <projectDescription>
 	<name>org.eclipse.jgit.test</name>
 	<comment></comment>
-	<projects>
-		<project>org.eclipse.jgit.java7</project>
-	</projects>
 	<buildSpec>
 		<buildCommand>
 			<name>org.eclipse.jdt.core.javabuilder</name>
diff --git a/org.eclipse.jgit.test/.settings/org.eclipse.jdt.core.prefs b/org.eclipse.jgit.test/.settings/org.eclipse.jdt.core.prefs
index dcc0d3a..87210fb 100644
--- a/org.eclipse.jgit.test/.settings/org.eclipse.jdt.core.prefs
+++ b/org.eclipse.jgit.test/.settings/org.eclipse.jdt.core.prefs
@@ -99,6 +99,7 @@
 org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled
 org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled
 org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled
+org.eclipse.jdt.core.compiler.problem.unusedExceptionParameter=ignore
 org.eclipse.jdt.core.compiler.problem.unusedImport=error
 org.eclipse.jdt.core.compiler.problem.unusedLabel=error
 org.eclipse.jdt.core.compiler.problem.unusedLocal=error
diff --git a/org.eclipse.jgit.test/META-INF/MANIFEST.MF b/org.eclipse.jgit.test/META-INF/MANIFEST.MF
index 12620de..a7b1fa8 100644
--- a/org.eclipse.jgit.test/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.test/META-INF/MANIFEST.MF
@@ -2,52 +2,52 @@
 Bundle-ManifestVersion: 2
 Bundle-Name: %plugin_name
 Bundle-SymbolicName: org.eclipse.jgit.test
-Bundle-Version: 4.4.2.qualifier
+Bundle-Version: 4.5.5.qualifier
 Bundle-Localization: plugin
 Bundle-Vendor: %provider_name
 Bundle-ActivationPolicy: lazy
 Bundle-RequiredExecutionEnvironment: JavaSE-1.7
 Import-Package: com.googlecode.javaewah;version="[0.7.9,0.8.0)",
- org.eclipse.jgit.api;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.api.errors;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.attributes;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.awtui;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.blame;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.diff;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.dircache;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.errors;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.events;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.fnmatch;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.gitrepo;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.hooks;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.ignore;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.ignore.internal;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.internal;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.internal.storage.dfs;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.internal.storage.file;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.internal.storage.pack;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.internal.storage.reftree;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.junit;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.lib;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.merge;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.nls;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.notes;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.patch;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.pgm;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.pgm.internal;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.revplot;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.revwalk;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.revwalk.filter;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.storage.file;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.storage.pack;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.submodule;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.transport;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.transport.http;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.transport.resolver;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.treewalk;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.treewalk.filter;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.util;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.util.io;version="[4.4.2,4.5.0)",
+ org.eclipse.jgit.api;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.api.errors;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.attributes;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.awtui;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.blame;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.diff;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.dircache;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.errors;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.events;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.fnmatch;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.gitrepo;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.hooks;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.ignore;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.ignore.internal;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.internal;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.internal.storage.dfs;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.internal.storage.file;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.internal.storage.pack;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.internal.storage.reftree;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.junit;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.lib;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.merge;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.nls;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.notes;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.patch;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.pgm;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.pgm.internal;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.revplot;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.revwalk;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.revwalk.filter;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.storage.file;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.storage.pack;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.submodule;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.transport;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.transport.http;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.transport.resolver;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.treewalk;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.treewalk.filter;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.util;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.util.io;version="[4.5.5,4.6.0)",
  org.hamcrest;version="[1.1.0,2.0.0)",
  org.junit;version="[4.4.0,5.0.0)",
  org.junit.experimental.theories;version="[4.4.0,5.0.0)",
diff --git a/org.eclipse.jgit.test/build.properties b/org.eclipse.jgit.test/build.properties
index 786046c..e7b3b99 100644
--- a/org.eclipse.jgit.test/build.properties
+++ b/org.eclipse.jgit.test/build.properties
@@ -1,7 +1,9 @@
 source.. = tst/,\
            tst-rsrc/,\
-           exttst/
+           exttst/,\
+           src/
 bin.includes = META-INF/,\
                .,\
                plugin.properties
-additional.bundles = org.apache.log4j
+additional.bundles = org.apache.log4j,\
+                     org.slf4j.impl.log4j12
diff --git "a/org.eclipse.jgit.test/org.eclipse.jgit.core--All-Tests \050Java 7\051 \050de\051.launch" "b/org.eclipse.jgit.test/org.eclipse.jgit.core--All-Tests \050Java 7\051 \050de\051.launch"
new file mode 100644
index 0000000..86446f9
--- /dev/null
+++ "b/org.eclipse.jgit.test/org.eclipse.jgit.core--All-Tests \050Java 7\051 \050de\051.launch"
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<launchConfiguration type="org.eclipse.jdt.junit.launchconfig">
+<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
+<listEntry value="/org.eclipse.jgit.test/tst"/>
+</listAttribute>
+<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">
+<listEntry value="2"/>
+</listAttribute>
+<booleanAttribute key="org.eclipse.debug.core.appendEnvironmentVariables" value="true"/>
+<mapAttribute key="org.eclipse.debug.core.environmentVariables">
+<mapEntry key="LANG" value="de_DE.UTF-8"/>
+</mapAttribute>
+<listAttribute key="org.eclipse.debug.ui.favoriteGroups">
+<listEntry value="org.eclipse.debug.ui.launchGroup.debug"/>
+<listEntry value="org.eclipse.debug.ui.launchGroup.run"/>
+</listAttribute>
+<stringAttribute key="org.eclipse.jdt.junit.CONTAINER" value="=org.eclipse.jgit.test/tst"/>
+<booleanAttribute key="org.eclipse.jdt.junit.KEEPRUNNING_ATTR" value="false"/>
+<stringAttribute key="org.eclipse.jdt.junit.TESTNAME" value=""/>
+<stringAttribute key="org.eclipse.jdt.junit.TEST_KIND" value="org.eclipse.jdt.junit.loader.junit4"/>
+<listAttribute key="org.eclipse.jdt.launching.CLASSPATH">
+<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry containerPath=&quot;org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6&quot; path=&quot;1&quot; type=&quot;4&quot;/&gt;&#10;"/>
+<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry id=&quot;org.eclipse.jdt.launching.classpathentry.defaultClasspath&quot;&gt;&#10;&lt;memento exportedEntriesOnly=&quot;false&quot; project=&quot;org.eclipse.jgit.test&quot;/&gt;&#10;&lt;/runtimeClasspathEntry&gt;&#10;"/>
+</listAttribute>
+<booleanAttribute key="org.eclipse.jdt.launching.DEFAULT_CLASSPATH" value="false"/>
+<stringAttribute key="org.eclipse.jdt.launching.JRE_CONTAINER" value="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.7"/>
+<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value=""/>
+<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="org.eclipse.jgit.test"/>
+<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-Xmx256m"/>
+</launchConfiguration>
diff --git a/org.eclipse.jgit.test/pom.xml b/org.eclipse.jgit.test/pom.xml
index 81a3b09..cd4b853 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.4.2-SNAPSHOT</version>
+    <version>4.5.5-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.test</artifactId>
diff --git a/org.eclipse.jgit.test/src/org/eclipse/jgit/lib/Sets.java b/org.eclipse.jgit.test/src/org/eclipse/jgit/lib/Sets.java
index 7316857..4454e1a 100644
--- a/org.eclipse.jgit.test/src/org/eclipse/jgit/lib/Sets.java
+++ b/org.eclipse.jgit.test/src/org/eclipse/jgit/lib/Sets.java
@@ -47,6 +47,7 @@
 import java.util.Set;
 
 public class Sets {
+	@SafeVarargs
 	public static <T> Set<T> of(T... elements) {
 		Set<T> ret = new HashSet<T>();
 		for (T element : elements)
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/AddCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/AddCommandTest.java
index 126ca5c..42601aa 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/AddCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/AddCommandTest.java
@@ -45,7 +45,6 @@
 
 import static org.eclipse.jgit.util.FileUtils.RECURSIVE;
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
@@ -136,6 +135,29 @@
 	}
 
 	@Test
+	public void testAttributesWithTreeWalkFilter()
+			throws IOException, GitAPIException {
+		writeTrashFile(".gitattributes", "*.txt filter=lfs");
+		writeTrashFile("src/a.tmp", "foo");
+		writeTrashFile("src/a.txt", "foo\n");
+		File script = writeTempFile("sed s/o/e/g");
+
+		try (Git git = new Git(db)) {
+			StoredConfig config = git.getRepository().getConfig();
+			config.setString("filter", "lfs", "clean",
+					"sh " + slashify(script.getPath()));
+			config.save();
+
+			git.add().addFilepattern(".gitattributes").call();
+			git.commit().setMessage("attr").call();
+			git.add().addFilepattern("src/a.txt").addFilepattern("src/a.tmp")
+					.addFilepattern(".gitattributes").call();
+			git.commit().setMessage("c1").call();
+			assertTrue(git.status().call().isClean());
+		}
+	}
+
+	@Test
 	public void testCleanFilterEnvironment()
 			throws IOException, GitAPIException {
 		writeTrashFile(".gitattributes", "*.txt filter=tstFilter");
@@ -932,7 +954,11 @@
 			}
 
 			public boolean canExecute(File f) {
-				return true;
+				try {
+					return read(f).startsWith("binary:");
+				} catch (IOException e) {
+					return false;
+				}
 			}
 
 			@Override
@@ -943,61 +969,40 @@
 
 		Git git = Git.open(db.getDirectory(), executableFs);
 		String path = "a.txt";
+		String path2 = "a.sh";
 		writeTrashFile(path, "content");
-		git.add().addFilepattern(path).call();
+		writeTrashFile(path2, "binary: content");
+		git.add().addFilepattern(path).addFilepattern(path2).call();
 		RevCommit commit1 = git.commit().setMessage("commit").call();
-		TreeWalk walk = TreeWalk.forPath(db, path, commit1.getTree());
-		assertNotNull(walk);
-		assertEquals(FileMode.EXECUTABLE_FILE, walk.getFileMode(0));
-
-		FS nonExecutableFs = new FS() {
-
-			public boolean supportsExecute() {
-				return false;
-			}
-
-			public boolean setExecute(File f, boolean canExec) {
-				return false;
-			}
-
-			public ProcessBuilder runInShell(String cmd, String[] args) {
-				return null;
-			}
-
-			public boolean retryFailedLockFileCommit() {
-				return false;
-			}
-
-			public FS newInstance() {
-				return this;
-			}
-
-			protected File discoverGitExe() {
-				return null;
-			}
-
-			public boolean canExecute(File f) {
-				return false;
-			}
-
-			@Override
-			public boolean isCaseSensitive() {
-				return false;
-			}
-		};
+		try (TreeWalk walk = new TreeWalk(db)) {
+			walk.addTree(commit1.getTree());
+			walk.next();
+			assertEquals(path2, walk.getPathString());
+			assertEquals(FileMode.EXECUTABLE_FILE, walk.getFileMode(0));
+			walk.next();
+			assertEquals(path, walk.getPathString());
+			assertEquals(FileMode.REGULAR_FILE, walk.getFileMode(0));
+		}
 
 		config = db.getConfig();
 		config.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
 				ConfigConstants.CONFIG_KEY_FILEMODE, false);
 		config.save();
 
-		Git git2 = Git.open(db.getDirectory(), nonExecutableFs);
-		writeTrashFile(path, "content2");
-		git2.add().addFilepattern(path).call();
+		Git git2 = Git.open(db.getDirectory(), executableFs);
+		writeTrashFile(path2, "content2");
+		writeTrashFile(path, "binary: content2");
+		git2.add().addFilepattern(path).addFilepattern(path2).call();
 		RevCommit commit2 = git2.commit().setMessage("commit2").call();
-		walk = TreeWalk.forPath(db, path, commit2.getTree());
-		assertNotNull(walk);
-		assertEquals(FileMode.EXECUTABLE_FILE, walk.getFileMode(0));
+		try (TreeWalk walk = new TreeWalk(db)) {
+			walk.addTree(commit2.getTree());
+			walk.next();
+			assertEquals(path2, walk.getPathString());
+			assertEquals(FileMode.EXECUTABLE_FILE, walk.getFileMode(0));
+			walk.next();
+			assertEquals(path, walk.getPathString());
+			assertEquals(FileMode.REGULAR_FILE, walk.getFileMode(0));
+		}
 	}
 
 	@Test
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CleanCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CleanCommandTest.java
index 7cca4b9..68f5dd1 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CleanCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CleanCommandTest.java
@@ -45,13 +45,16 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.eclipse.jgit.lib.Constants.DOT_GIT_MODULES;
 
+import java.io.File;
 import java.util.Set;
 import java.util.TreeSet;
 
 import org.eclipse.jgit.api.errors.GitAPIException;
 import org.eclipse.jgit.errors.NoWorkTreeException;
 import org.eclipse.jgit.junit.RepositoryTestCase;
+import org.eclipse.jgit.lib.Repository;
 import org.junit.Before;
 import org.junit.Test;
 
@@ -227,4 +230,69 @@
 		assertTrue(cleanedFiles.contains("ignored-dir/"));
 	}
 
+	@Test
+	public void testCleanDirsWithSubmodule() throws Exception {
+		SubmoduleAddCommand command = new SubmoduleAddCommand(db);
+		String path = "sub";
+		command.setPath(path);
+		String uri = db.getDirectory().toURI().toString();
+		command.setURI(uri);
+		Repository repo = command.call();
+		repo.close();
+
+		Status beforeCleanStatus = git.status().call();
+		assertTrue(beforeCleanStatus.getAdded().contains(DOT_GIT_MODULES));
+		assertTrue(beforeCleanStatus.getAdded().contains(path));
+
+		Set<String> cleanedFiles = git.clean().setCleanDirectories(true).call();
+
+		// The submodule should not be cleaned.
+		assertTrue(!cleanedFiles.contains(path + "/"));
+
+		assertTrue(cleanedFiles.contains("File2.txt"));
+		assertTrue(cleanedFiles.contains("File3.txt"));
+		assertTrue(!cleanedFiles.contains("sub-noclean/File1.txt"));
+		assertTrue(cleanedFiles.contains("sub-noclean/File2.txt"));
+		assertTrue(cleanedFiles.contains("sub-clean/"));
+		assertTrue(cleanedFiles.size() == 4);
+	}
+
+	@Test
+	public void testCleanDirsWithRepository() throws Exception {
+		// Set up a repository inside the outer repository
+		String innerRepoName = "inner-repo";
+		File innerDir = new File(trash, innerRepoName);
+		innerDir.mkdir();
+		InitCommand initRepoCommand = new InitCommand();
+		initRepoCommand.setDirectory(innerDir);
+		initRepoCommand.call();
+
+		Status beforeCleanStatus = git.status().call();
+		Set<String> untrackedFolders = beforeCleanStatus.getUntrackedFolders();
+		Set<String> untrackedFiles = beforeCleanStatus.getUntracked();
+
+		// The inner repository should be listed as an untracked file
+		assertTrue(untrackedFiles.contains(innerRepoName));
+
+		// The inner repository should not be listed as an untracked folder
+		assertTrue(!untrackedFolders.contains(innerRepoName));
+
+		Set<String> cleanedFiles = git.clean().setCleanDirectories(true).call();
+
+		// The inner repository should not be cleaned.
+		assertTrue(!cleanedFiles.contains(innerRepoName + "/"));
+
+		assertTrue(cleanedFiles.contains("File2.txt"));
+		assertTrue(cleanedFiles.contains("File3.txt"));
+		assertTrue(!cleanedFiles.contains("sub-noclean/File1.txt"));
+		assertTrue(cleanedFiles.contains("sub-noclean/File2.txt"));
+		assertTrue(cleanedFiles.contains("sub-clean/"));
+		assertTrue(cleanedFiles.size() == 4);
+
+		Set<String> forceCleanedFiles = git.clean().setCleanDirectories(true)
+				.setForce(true).call();
+
+		// The inner repository should be cleaned this time
+		assertTrue(forceCleanedFiles.contains(innerRepoName + "/"));
+	}
 }
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CloneCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CloneCommandTest.java
index ce11e1b..8a728ca 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CloneCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CloneCommandTest.java
@@ -62,6 +62,7 @@
 import org.eclipse.jgit.errors.NoWorkTreeException;
 import org.eclipse.jgit.junit.RepositoryTestCase;
 import org.eclipse.jgit.junit.TestRepository;
+import org.eclipse.jgit.lib.BranchConfig.BranchRebaseMode;
 import org.eclipse.jgit.lib.ConfigConstants;
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.ObjectId;
@@ -605,11 +606,10 @@
 		command.setURI(fileUri());
 		Git git2 = command.call();
 		addRepoToClose(git2.getRepository());
-		assertFalse(git2
-				.getRepository()
-				.getConfig()
-				.getBoolean(ConfigConstants.CONFIG_BRANCH_SECTION, "test",
-						ConfigConstants.CONFIG_KEY_REBASE, false));
+		assertNull(git2.getRepository().getConfig().getEnum(
+				BranchRebaseMode.values(),
+				ConfigConstants.CONFIG_BRANCH_SECTION, "test",
+				ConfigConstants.CONFIG_KEY_REBASE, null));
 
 		FileBasedConfig userConfig = SystemReader.getInstance().openUserConfig(
 				null, git.getRepository().getFS());
@@ -623,11 +623,12 @@
 		command.setURI(fileUri());
 		git2 = command.call();
 		addRepoToClose(git2.getRepository());
-		assertTrue(git2
-				.getRepository()
-				.getConfig()
-				.getBoolean(ConfigConstants.CONFIG_BRANCH_SECTION, "test",
-						ConfigConstants.CONFIG_KEY_REBASE, false));
+		assertEquals(BranchRebaseMode.REBASE,
+				git2.getRepository().getConfig().getEnum(
+						BranchRebaseMode.values(),
+						ConfigConstants.CONFIG_BRANCH_SECTION, "test",
+						ConfigConstants.CONFIG_KEY_REBASE,
+						BranchRebaseMode.NONE));
 
 		userConfig.setString(ConfigConstants.CONFIG_BRANCH_SECTION, null,
 				ConfigConstants.CONFIG_KEY_AUTOSETUPREBASE,
@@ -639,11 +640,12 @@
 		command.setURI(fileUri());
 		git2 = command.call();
 		addRepoToClose(git2.getRepository());
-		assertTrue(git2
-				.getRepository()
-				.getConfig()
-				.getBoolean(ConfigConstants.CONFIG_BRANCH_SECTION, "test",
-						ConfigConstants.CONFIG_KEY_REBASE, false));
+		assertEquals(BranchRebaseMode.REBASE,
+				git2.getRepository().getConfig().getEnum(
+						BranchRebaseMode.values(),
+						ConfigConstants.CONFIG_BRANCH_SECTION, "test",
+						ConfigConstants.CONFIG_KEY_REBASE,
+						BranchRebaseMode.NONE));
 
 	}
 
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitCommandTest.java
index 8a07118..ba84081 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitCommandTest.java
@@ -76,6 +76,7 @@
 import org.eclipse.jgit.treewalk.TreeWalk;
 import org.eclipse.jgit.treewalk.filter.TreeFilter;
 import org.eclipse.jgit.util.FS;
+import org.junit.Ignore;
 import org.junit.Test;
 
 /**
@@ -291,6 +292,7 @@
 		}
 	}
 
+	@Ignore("very flaky when run with Hudson")
 	@Test
 	public void commitUpdatesSmudgedEntries() throws Exception {
 		try (Git git = new Git(db)) {
@@ -347,6 +349,7 @@
 		}
 	}
 
+	@Ignore("very flaky when run with Hudson")
 	@Test
 	public void commitIgnoresSmudgedEntryWithDifferentId() throws Exception {
 		try (Git git = new Git(db)) {
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PathCheckoutCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PathCheckoutCommandTest.java
index 3343af0..1250773 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PathCheckoutCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PathCheckoutCommandTest.java
@@ -271,6 +271,7 @@
 		}
 	}
 
+	@Test
 	public void testCheckoutMixedNewlines() throws Exception {
 		// "git config core.autocrlf true"
 		StoredConfig config = git.getRepository().getConfig();
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RenameBranchCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RenameBranchCommandTest.java
index 181e4a1..4c09a82 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RenameBranchCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RenameBranchCommandTest.java
@@ -43,11 +43,14 @@
 package org.eclipse.jgit.api;
 
 import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 
 import org.eclipse.jgit.junit.RepositoryTestCase;
+import org.eclipse.jgit.lib.BranchConfig.BranchRebaseMode;
 import org.eclipse.jgit.lib.ConfigConstants;
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.StoredConfig;
@@ -98,32 +101,40 @@
 	@Test
 	public void renameBranchSingleConfigValue() throws Exception {
 		StoredConfig config = git.getRepository().getConfig();
-		config.setBoolean(ConfigConstants.CONFIG_BRANCH_SECTION,
-				Constants.MASTER, ConfigConstants.CONFIG_KEY_REBASE, true);
+		config.setEnum(ConfigConstants.CONFIG_BRANCH_SECTION, Constants.MASTER,
+				ConfigConstants.CONFIG_KEY_REBASE, BranchRebaseMode.REBASE);
 		config.save();
 
 		String branch = "b1";
 
-		assertTrue(config.getBoolean(ConfigConstants.CONFIG_BRANCH_SECTION,
-				Constants.MASTER, ConfigConstants.CONFIG_KEY_REBASE, true));
-		assertFalse(config.getBoolean(ConfigConstants.CONFIG_BRANCH_SECTION,
-				branch, ConfigConstants.CONFIG_KEY_REBASE, false));
+		assertEquals(BranchRebaseMode.REBASE,
+				config.getEnum(BranchRebaseMode.values(),
+						ConfigConstants.CONFIG_BRANCH_SECTION, Constants.MASTER,
+						ConfigConstants.CONFIG_KEY_REBASE,
+						BranchRebaseMode.NONE));
+		assertNull(config.getEnum(BranchRebaseMode.values(),
+				ConfigConstants.CONFIG_BRANCH_SECTION, branch,
+				ConfigConstants.CONFIG_KEY_REBASE, null));
 
 		assertNotNull(git.branchRename().setNewName(branch).call());
 
 		config = git.getRepository().getConfig();
-		assertFalse(config.getBoolean(ConfigConstants.CONFIG_BRANCH_SECTION,
-				Constants.MASTER, ConfigConstants.CONFIG_KEY_REBASE, false));
-		assertTrue(config.getBoolean(ConfigConstants.CONFIG_BRANCH_SECTION,
-				branch, ConfigConstants.CONFIG_KEY_REBASE, false));
+		assertNull(config.getEnum(BranchRebaseMode.values(),
+				ConfigConstants.CONFIG_BRANCH_SECTION, Constants.MASTER,
+				ConfigConstants.CONFIG_KEY_REBASE, null));
+		assertEquals(BranchRebaseMode.REBASE,
+				config.getEnum(BranchRebaseMode.values(),
+						ConfigConstants.CONFIG_BRANCH_SECTION, branch,
+						ConfigConstants.CONFIG_KEY_REBASE,
+						BranchRebaseMode.NONE));
 	}
 
 	@Test
 	public void renameBranchExistingSection() throws Exception {
 		String branch = "b1";
 		StoredConfig config = git.getRepository().getConfig();
-		config.setBoolean(ConfigConstants.CONFIG_BRANCH_SECTION,
-				Constants.MASTER, ConfigConstants.CONFIG_KEY_REBASE, true);
+		config.setEnum(ConfigConstants.CONFIG_BRANCH_SECTION, Constants.MASTER,
+				ConfigConstants.CONFIG_KEY_REBASE, BranchRebaseMode.REBASE);
 		config.setString(ConfigConstants.CONFIG_BRANCH_SECTION,
 				Constants.MASTER, "a", "a");
 		config.setString(ConfigConstants.CONFIG_BRANCH_SECTION, branch, "a",
@@ -140,18 +151,22 @@
 	@Test
 	public void renameBranchMultipleConfigValues() throws Exception {
 		StoredConfig config = git.getRepository().getConfig();
-		config.setBoolean(ConfigConstants.CONFIG_BRANCH_SECTION,
-				Constants.MASTER, ConfigConstants.CONFIG_KEY_REBASE, true);
+		config.setEnum(ConfigConstants.CONFIG_BRANCH_SECTION, Constants.MASTER,
+				ConfigConstants.CONFIG_KEY_REBASE, BranchRebaseMode.REBASE);
 		config.setBoolean(ConfigConstants.CONFIG_BRANCH_SECTION,
 				Constants.MASTER, ConfigConstants.CONFIG_KEY_MERGE, true);
 		config.save();
 
 		String branch = "b1";
 
-		assertTrue(config.getBoolean(ConfigConstants.CONFIG_BRANCH_SECTION,
-				Constants.MASTER, ConfigConstants.CONFIG_KEY_REBASE, true));
-		assertFalse(config.getBoolean(ConfigConstants.CONFIG_BRANCH_SECTION,
-				branch, ConfigConstants.CONFIG_KEY_REBASE, false));
+		assertEquals(BranchRebaseMode.REBASE,
+				config.getEnum(BranchRebaseMode.values(),
+						ConfigConstants.CONFIG_BRANCH_SECTION, Constants.MASTER,
+						ConfigConstants.CONFIG_KEY_REBASE,
+						BranchRebaseMode.NONE));
+		assertNull(config.getEnum(BranchRebaseMode.values(),
+				ConfigConstants.CONFIG_BRANCH_SECTION, branch,
+				ConfigConstants.CONFIG_KEY_REBASE, null));
 		assertTrue(config.getBoolean(ConfigConstants.CONFIG_BRANCH_SECTION,
 				Constants.MASTER, ConfigConstants.CONFIG_KEY_MERGE, true));
 		assertFalse(config.getBoolean(ConfigConstants.CONFIG_BRANCH_SECTION,
@@ -160,10 +175,14 @@
 		assertNotNull(git.branchRename().setNewName(branch).call());
 
 		config = git.getRepository().getConfig();
-		assertFalse(config.getBoolean(ConfigConstants.CONFIG_BRANCH_SECTION,
-				Constants.MASTER, ConfigConstants.CONFIG_KEY_REBASE, false));
-		assertTrue(config.getBoolean(ConfigConstants.CONFIG_BRANCH_SECTION,
-				branch, ConfigConstants.CONFIG_KEY_REBASE, false));
+		assertNull(config.getEnum(BranchRebaseMode.values(),
+				ConfigConstants.CONFIG_BRANCH_SECTION, Constants.MASTER,
+				ConfigConstants.CONFIG_KEY_REBASE, null));
+		assertEquals(BranchRebaseMode.REBASE,
+				config.getEnum(BranchRebaseMode.values(),
+						ConfigConstants.CONFIG_BRANCH_SECTION, branch,
+						ConfigConstants.CONFIG_KEY_REBASE,
+						BranchRebaseMode.NONE));
 		assertFalse(config.getBoolean(ConfigConstants.CONFIG_BRANCH_SECTION,
 				Constants.MASTER, ConfigConstants.CONFIG_KEY_MERGE, false));
 		assertTrue(config.getBoolean(ConfigConstants.CONFIG_BRANCH_SECTION,
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/ResetCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/ResetCommandTest.java
index a4a699e..ba51881 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/ResetCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/ResetCommandTest.java
@@ -157,6 +157,28 @@
 	}
 
 	@Test
+	public void testHardResetReflogDisabled() throws Exception {
+		setupRepository();
+		ObjectId prevHead = db.resolve(Constants.HEAD);
+		ResetCommand reset = git.reset();
+		assertSameAsHead(reset.setMode(ResetType.HARD)
+				.setRef(initialCommit.getName()).disableRefLog(true).call());
+		assertTrue("reflog should be disabled", reset.isReflogDisabled());
+		// check if HEAD points to initial commit now
+		ObjectId head = db.resolve(Constants.HEAD);
+		assertEquals(initialCommit, head);
+		// check if files were removed
+		assertFalse(indexFile.exists());
+		assertTrue(untrackedFile.exists());
+		// fileInIndex must no longer be in HEAD and in the index
+		String fileInIndexPath = indexFile.getAbsolutePath();
+		assertFalse(inHead(fileInIndexPath));
+		assertFalse(inIndex(indexFile.getName()));
+		assertReflogDisabled(head);
+		assertEquals(prevHead, db.readOrigHead());
+	}
+
+	@Test
 	public void testHardResetWithConflicts_DoOverWriteUntrackedFile()
 			throws JGitInternalException,
 			AmbiguousObjectException, IOException, GitAPIException {
@@ -562,6 +584,24 @@
 				.getName());
 	}
 
+	private void assertReflogDisabled(ObjectId head)
+			throws IOException {
+		// Check the reflog for HEAD
+		String actualHeadMessage = db.getReflogReader(Constants.HEAD)
+				.getLastEntry().getComment();
+		String expectedHeadMessage = "commit: adding a.txt and dir/b.txt";
+		assertEquals(expectedHeadMessage, actualHeadMessage);
+		assertEquals(head.getName(), db.getReflogReader(Constants.HEAD)
+				.getLastEntry().getOldId().getName());
+
+		// The reflog for master contains the same as the one for HEAD
+		String actualMasterMessage = db.getReflogReader("refs/heads/master")
+				.getLastEntry().getComment();
+		String expectedMasterMessage = "commit: adding a.txt and dir/b.txt";
+		assertEquals(expectedMasterMessage, actualMasterMessage);
+		assertEquals(head.getName(), db.getReflogReader(Constants.HEAD)
+				.getLastEntry().getOldId().getName());
+	}
 	/**
 	 * Checks if a file with the given path exists in the HEAD tree
 	 *
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/BasicRuleTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/BasicRuleTest.java
index d8a6174..a6e48fe 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/BasicRuleTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/BasicRuleTest.java
@@ -47,6 +47,7 @@
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertTrue;
 
+import org.eclipse.jgit.ignore.internal.Strings;
 import org.junit.Test;
 
 public class BasicRuleTest {
@@ -73,4 +74,31 @@
 		assertNotEquals(rule1.toString(), rule3.toString());
 	}
 
+	@Test
+	public void testDirectoryPattern() {
+		assertTrue(Strings.isDirectoryPattern("/"));
+		assertTrue(Strings.isDirectoryPattern("/ "));
+		assertTrue(Strings.isDirectoryPattern("/     "));
+		assertFalse(Strings.isDirectoryPattern("     "));
+		assertFalse(Strings.isDirectoryPattern(""));
+	}
+
+	@Test
+	public void testStripTrailingChar() {
+		assertEquals("", Strings.stripTrailing("/", '/'));
+		assertEquals("", Strings.stripTrailing("///", '/'));
+		assertEquals("a", Strings.stripTrailing("a/", '/'));
+		assertEquals("a", Strings.stripTrailing("a///", '/'));
+		assertEquals("a/ ", Strings.stripTrailing("a/ ", '/'));
+	}
+
+	@Test
+	public void testStripTrailingWhitespace() {
+		assertEquals("", Strings.stripTrailingWhitespace(""));
+		assertEquals("", Strings.stripTrailingWhitespace("   "));
+		assertEquals("a", Strings.stripTrailingWhitespace("a"));
+		assertEquals("a", Strings.stripTrailingWhitespace("a "));
+		assertEquals("a", Strings.stripTrailingWhitespace("a  "));
+		assertEquals("a", Strings.stripTrailingWhitespace("a \t"));
+	}
 }
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/FastIgnoreRuleTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/FastIgnoreRuleTest.java
index 480e326..1863b80 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/FastIgnoreRuleTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/FastIgnoreRuleTest.java
@@ -123,6 +123,17 @@
 	}
 
 	@Test
+	public void testTrailingSpaces() {
+		assertMatched("a ", "a");
+		assertMatched("a/ ", "a/");
+		assertMatched("a/ ", "a/b");
+		assertMatched("a/\\ ", "a/ ");
+		assertNotMatched("a/\\ ", "a/");
+		assertNotMatched("a/\\ ", "a/b");
+		assertNotMatched("/ ", "a");
+	}
+
+	@Test
 	public void testAsteriskDot() {
 		assertMatched("*.a", ".a");
 		assertMatched("*.a", "/.a");
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/IgnoreNodeTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/IgnoreNodeTest.java
index c026efc..4a7dcd5 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/IgnoreNodeTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/IgnoreNodeTest.java
@@ -480,8 +480,9 @@
 		writeTrashFile("a/a", "");
 		writeTrashFile("a/a ", "");
 		writeTrashFile("a/a  ", "");
+		writeTrashFile("b/c", "");
 
-		writeIgnoreFile(".gitignore", "a\\ ", "a \\ ");
+		writeIgnoreFile(".gitignore", "a\\ ", "a \\ ", "b/ ");
 
 		beginWalk();
 		assertEntry(F, tracked, ".gitignore");
@@ -497,6 +498,8 @@
 		assertEntry(F, tracked, "a/a");
 		assertEntry(F, ignored, "a/a ");
 		assertEntry(F, ignored, "a/a  ");
+		assertEntry(D, ignored, "b");
+		assertEntry(F, ignored, "b/c");
 		endWalk();
 	}
 
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
new file mode 100644
index 0000000..c7125bb
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsGarbageCollectorTest.java
@@ -0,0 +1,239 @@
+package org.eclipse.jgit.internal.storage.dfs;
+
+import static org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource.GC;
+import static org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource.INSERT;
+import static org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource.UNREACHABLE_GARBAGE;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.io.IOException;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource;
+import org.eclipse.jgit.junit.TestRepository;
+import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.junit.Before;
+import org.junit.Test;
+
+public class DfsGarbageCollectorTest {
+	private TestRepository<InMemoryRepository> git;
+	private InMemoryRepository repo;
+	private DfsObjDatabase odb;
+
+	@Before
+	public void setUp() throws IOException {
+		DfsRepositoryDescription desc = new DfsRepositoryDescription("test");
+		git = new TestRepository<>(new InMemoryRepository(desc));
+		repo = git.getRepository();
+		odb = repo.getObjectDatabase();
+	}
+
+	@Test
+	public void testCollectionWithNoGarbage() throws Exception {
+		RevCommit commit0 = commit().message("0").create();
+		RevCommit commit1 = commit().message("1").parent(commit0).create();
+		git.update("master", commit1);
+
+		assertTrue("commit0 reachable", isReachable(repo, commit0));
+		assertTrue("commit1 reachable", isReachable(repo, commit1));
+
+		// Packs start out as INSERT.
+		assertEquals(2, odb.getPacks().length);
+		for (DfsPackFile pack : odb.getPacks()) {
+			assertEquals(INSERT, pack.getPackDescription().getPackSource());
+		}
+
+		gcNoTtl();
+
+		// Single GC pack present with all objects.
+		assertEquals(1, odb.getPacks().length);
+		DfsPackFile pack = odb.getPacks()[0];
+		assertEquals(GC, pack.getPackDescription().getPackSource());
+		assertTrue("commit0 in pack", isObjectInPack(commit0, pack));
+		assertTrue("commit1 in pack", isObjectInPack(commit1, pack));
+	}
+
+	@Test
+	public void testCollectionWithGarbage() throws Exception {
+		RevCommit commit0 = commit().message("0").create();
+		RevCommit commit1 = commit().message("1").parent(commit0).create();
+		git.update("master", commit0);
+
+		assertTrue("commit0 reachable", isReachable(repo, commit0));
+		assertFalse("commit1 garbage", isReachable(repo, commit1));
+		gcNoTtl();
+
+		assertEquals(2, odb.getPacks().length);
+		DfsPackFile gc = null;
+		DfsPackFile garbage = null;
+		for (DfsPackFile pack : odb.getPacks()) {
+			DfsPackDescription d = pack.getPackDescription();
+			if (d.getPackSource() == GC) {
+				gc = pack;
+			} else if (d.getPackSource() == UNREACHABLE_GARBAGE) {
+				garbage = pack;
+			} else {
+				fail("unexpected " + d.getPackSource());
+			}
+		}
+
+		assertNotNull("created GC pack", gc);
+		assertTrue(isObjectInPack(commit0, gc));
+
+		assertNotNull("created UNREACHABLE_GARBAGE pack", garbage);
+		assertTrue(isObjectInPack(commit1, garbage));
+	}
+
+	@Test
+	public void testCollectionWithGarbageAndGarbagePacksPurged()
+			throws Exception {
+		RevCommit commit0 = commit().message("0").create();
+		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;
+		for (DfsPackFile pack : odb.getPacks()) {
+			DfsPackDescription d = pack.getPackDescription();
+			if (d.getPackSource() == GC) {
+				assertTrue("has commit0", isObjectInPack(commit0, pack));
+				assertFalse("no commit1", isObjectInPack(commit1, pack));
+			} else if (d.getPackSource() == UNREACHABLE_GARBAGE) {
+				commit1Found |= 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);
+
+		gcWithTtl();
+		assertTrue("has packs", odb.getPacks().length > 0);
+		for (DfsPackFile pack : odb.getPacks()) {
+			assertNotEquals(oldestGarbagePack, pack.getPackDescription());
+		}
+	}
+
+	@Test
+	public void testCollectionWithGarbageCoalescence() throws Exception {
+		RevCommit commit0 = commit().message("0").create();
+		RevCommit commit1 = commit().message("1").parent(commit0).create();
+		git.update("master", commit0);
+
+		for (int i = 0; i < 3; i++) {
+			commit1 = commit().message("g" + i).parent(commit1).create();
+
+			// Make sure we don't have more than 1 UNREACHABLE_GARBAGE pack
+			// because they're coalesced.
+			gcNoTtl();
+			assertEquals(1, countPacks(UNREACHABLE_GARBAGE));
+		}
+	}
+
+	@Test
+	public void testCollectionWithGarbageNoCoalescence() throws Exception {
+		RevCommit commit0 = commit().message("0").create();
+		RevCommit commit1 = commit().message("1").parent(commit0).create();
+		git.update("master", commit0);
+
+		for (int i = 0; i < 3; i++) {
+			commit1 = commit().message("g" + i).parent(commit1).create();
+
+			DfsGarbageCollector gc = new DfsGarbageCollector(repo);
+			gc.setCoalesceGarbageLimit(0);
+			gc.setGarbageTtl(0, TimeUnit.MILLISECONDS);
+			run(gc);
+			assertEquals(1 + i, countPacks(UNREACHABLE_GARBAGE));
+		}
+	}
+
+	private TestRepository<InMemoryRepository>.CommitBuilder commit() {
+		return git.commit();
+	}
+
+	private void gcNoTtl() throws IOException {
+		DfsGarbageCollector gc = new DfsGarbageCollector(repo);
+		gc.setGarbageTtl(0, TimeUnit.MILLISECONDS); // disable TTL
+		run(gc);
+	}
+
+	private void gcWithTtl() throws InterruptedException, IOException {
+		// Wait for the system clock to move by at least 1 millisecond.
+		// This allows the DfsGarbageCollector to recognize the boundary.
+		long start = System.currentTimeMillis();
+		do {
+			Thread.sleep(10);
+		} while (System.currentTimeMillis() <= start);
+
+		DfsGarbageCollector gc = new DfsGarbageCollector(repo);
+		gc.setGarbageTtl(1, TimeUnit.MILLISECONDS);
+		run(gc);
+	}
+
+	private void run(DfsGarbageCollector gc) throws IOException {
+		assertTrue("gc repacked", gc.pack(null));
+		odb.clearCache();
+	}
+
+	private static boolean isReachable(Repository repo, AnyObjectId id)
+			throws IOException {
+		try (RevWalk rw = new RevWalk(repo)) {
+			for (Ref ref : repo.getAllRefs().values()) {
+				rw.markStart(rw.parseCommit(ref.getObjectId()));
+			}
+			for (RevCommit next; (next = rw.next()) != null;) {
+				if (AnyObjectId.equals(next, id)) {
+					return true;
+				}
+			}
+		}
+		return false;
+	}
+
+	private boolean isObjectInPack(AnyObjectId id, DfsPackFile pack)
+			throws IOException {
+		try (DfsReader reader = new DfsReader(odb)) {
+			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()) {
+			if (pack.getPackDescription().getPackSource() == source) {
+				cnt++;
+			}
+		}
+		return cnt;
+	}
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsInserterTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsInserterTest.java
index 74790f7..b738f7e 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsInserterTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsInserterTest.java
@@ -219,6 +219,37 @@
 		assertTrue(pack_sources.contains(PackSource.INSERT));
 	}
 
+	@Test
+	public void testNoCheckExisting() throws IOException {
+		byte[] contents = Constants.encode("foo");
+		ObjectId fooId;
+		try (ObjectInserter ins = db.newObjectInserter()) {
+			fooId = ins.insert(Constants.OBJ_BLOB, contents);
+			ins.flush();
+		}
+		assertEquals(1, db.getObjectDatabase().listPacks().size());
+
+		try (ObjectInserter ins = db.newObjectInserter()) {
+			((DfsInserter) ins).checkExisting(false);
+			assertEquals(fooId, ins.insert(Constants.OBJ_BLOB, contents));
+			ins.flush();
+		}
+		assertEquals(2, db.getObjectDatabase().listPacks().size());
+
+		// Verify that we have a foo in both INSERT packs.
+		DfsReader reader = new DfsReader(db.getObjectDatabase());
+		DfsPackFile packs[] = db.getObjectDatabase().getPacks();
+
+		assertEquals(2, packs.length);
+		DfsPackFile p1 = packs[0];
+		assertEquals(PackSource.INSERT, p1.getPackDescription().getPackSource());
+		assertTrue(p1.hasObject(reader, fooId));
+
+		DfsPackFile p2 = packs[1];
+		assertEquals(PackSource.INSERT, p2.getPackDescription().getPackSource());
+		assertTrue(p2.hasObject(reader, fooId));
+	}
+
 	private static String readString(ObjectLoader loader) throws IOException {
 		return RawParseUtils.decode(readStream(loader));
 	}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcConcurrentTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcConcurrentTest.java
index 07a7be7..776226b 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcConcurrentTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcConcurrentTest.java
@@ -45,8 +45,12 @@
 
 import static java.lang.Integer.valueOf;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
 
 import java.io.IOException;
+import java.util.Collection;
+import java.util.Collections;
 import java.util.concurrent.BrokenBarrierException;
 import java.util.concurrent.Callable;
 import java.util.concurrent.CyclicBarrier;
@@ -56,8 +60,14 @@
 import java.util.concurrent.TimeUnit;
 
 import org.eclipse.jgit.internal.JGitText;
+import org.eclipse.jgit.internal.storage.pack.PackWriter;
+import org.eclipse.jgit.junit.TestRepository;
 import org.eclipse.jgit.lib.EmptyProgressMonitor;
+import org.eclipse.jgit.lib.NullProgressMonitor;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Sets;
 import org.eclipse.jgit.revwalk.RevBlob;
+import org.eclipse.jgit.revwalk.RevCommit;
 import org.junit.Test;
 
 public class GcConcurrentTest extends GcTestCase {
@@ -116,4 +126,97 @@
 			pool.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS);
 		}
 	}
+
+	@Test
+	public void repackAndGetStats() throws Exception {
+		TestRepository<FileRepository>.BranchBuilder test = tr.branch("test");
+		test.commit().add("a", "a").create();
+		GC gc1 = new GC(tr.getRepository());
+		gc1.setPackExpireAgeMillis(0);
+		gc1.gc();
+		test.commit().add("b", "b").create();
+
+		// Create a new Repository instance and trigger a gc
+		// from that instance. Reusing the existing repo instance
+		// tr.getRepository() would not show the problem.
+		FileRepository r2 = new FileRepository(
+				tr.getRepository().getDirectory());
+		GC gc2 = new GC(r2);
+		gc2.setPackExpireAgeMillis(0);
+		gc2.gc();
+
+		new GC(tr.getRepository()).getStatistics();
+	}
+
+	@Test
+	public void repackAndUploadPack() throws Exception {
+		TestRepository<FileRepository>.BranchBuilder test = tr.branch("test");
+		// RevCommit a = test.commit().add("a", "a").create();
+		test.commit().add("a", "a").create();
+
+		GC gc1 = new GC(tr.getRepository());
+		gc1.setPackExpireAgeMillis(0);
+		gc1.gc();
+
+		RevCommit b = test.commit().add("b", "b").create();
+
+		FileRepository r2 = new FileRepository(
+				tr.getRepository().getDirectory());
+		GC gc2 = new GC(r2);
+		gc2.setPackExpireAgeMillis(0);
+		gc2.gc();
+
+		// Simulate parts of an UploadPack. This is the situation on
+		// server side (e.g. gerrit) when when clients are
+		// cloning/fetching while the server side repo's
+		// are gc'ed by an external process (e.g. scheduled
+		// native git gc)
+		try (PackWriter pw = new PackWriter(tr.getRepository())) {
+			pw.setUseBitmaps(true);
+			pw.preparePack(NullProgressMonitor.INSTANCE, Sets.of(b),
+					Collections.<ObjectId> emptySet());
+			new GC(tr.getRepository()).getStatistics();
+		}
+	}
+
+	PackFile getSinglePack(FileRepository r) {
+		Collection<PackFile> packs = r.getObjectDatabase().getPacks();
+		assertEquals(1, packs.size());
+		return packs.iterator().next();
+	}
+
+	@Test
+	public void repackAndCheckBitmapUsage() throws Exception {
+		// create a test repository with one commit and pack all objects. After
+		// packing create loose objects to trigger creation of a new packfile on
+		// the next gc
+		TestRepository<FileRepository>.BranchBuilder test = tr.branch("test");
+		test.commit().add("a", "a").create();
+		FileRepository repository = tr.getRepository();
+		GC gc1 = new GC(repository);
+		gc1.setPackExpireAgeMillis(0);
+		gc1.gc();
+		String oldPackName = getSinglePack(repository).getPackName();
+		RevCommit b = test.commit().add("b", "b").create();
+
+		// start the garbage collection on a new repository instance,
+		FileRepository repository2 = new FileRepository(repository.getDirectory());
+		GC gc2 = new GC(repository2);
+		gc2.setPackExpireAgeMillis(0);
+		gc2.gc();
+		String newPackName = getSinglePack(repository2).getPackName();
+		// make sure gc() has caused creation of a new packfile
+		assertNotEquals(oldPackName, newPackName);
+
+		// Even when asking again for the set of packfiles outdated data
+		// will be returned. As long as the repository can work on cached data
+		// it will do so and not detect that a new packfile exists.
+		assertNotEquals(getSinglePack(repository).getPackName(), newPackName);
+
+		// Only when accessing object content it is required to rescan the pack
+		// directory and the new packfile will be detected.
+		repository.getObjectDatabase().open(b).getSize();
+		assertEquals(getSinglePack(repository).getPackName(), newPackName);
+		assertNotNull(getSinglePack(repository).getBitmapIndex());
+	}
 }
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcTestCase.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcTestCase.java
index 5abf625..e463285 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcTestCase.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcTestCase.java
@@ -105,8 +105,9 @@
 		return tip;
 	}
 
-	protected long lastModified(AnyObjectId objectId) {
-		return repo.getObjectDatabase().fileFor(objectId).lastModified();
+	protected long lastModified(AnyObjectId objectId) throws IOException {
+		return repo.getFS().lastModified(
+				repo.getObjectDatabase().fileFor(objectId));
 	}
 
 	protected static void fsTick() throws InterruptedException, IOException {
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 63a3072..3cb4c39 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
@@ -75,6 +75,10 @@
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.ObjectIdSet;
 import org.eclipse.jgit.lib.ObjectInserter;
+import org.eclipse.jgit.lib.Sets;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.DepthWalk;
+import org.eclipse.jgit.revwalk.ObjectWalk;
 import org.eclipse.jgit.revwalk.RevBlob;
 import org.eclipse.jgit.revwalk.RevCommit;
 import org.eclipse.jgit.revwalk.RevObject;
@@ -92,6 +96,9 @@
 	private static final List<RevObject> EMPTY_LIST_REVS = Collections
 			.<RevObject> emptyList();
 
+	private static final Set<ObjectIdSet> EMPTY_ID_SET = Collections
+			.<ObjectIdSet> emptySet();
+
 	private PackConfig config;
 
 	private PackWriter writer;
@@ -104,6 +111,26 @@
 
 	private FileRepository dst;
 
+	private RevBlob contentA;
+
+	private RevBlob contentB;
+
+	private RevBlob contentC;
+
+	private RevBlob contentD;
+
+	private RevBlob contentE;
+
+	private RevCommit c1;
+
+	private RevCommit c2;
+
+	private RevCommit c3;
+
+	private RevCommit c4;
+
+	private RevCommit c5;
+
 	@Before
 	public void setUp() throws Exception {
 		super.setUp();
@@ -202,8 +229,7 @@
 		final ObjectId nonExisting = ObjectId
 				.fromString("0000000000000000000000000000000000000001");
 		try {
-			createVerifyOpenPack(NONE, Collections.singleton(nonExisting),
-					false, false);
+			createVerifyOpenPack(NONE, haves(nonExisting), false, false);
 			fail("Should have thrown MissingObjectException");
 		} catch (MissingObjectException x) {
 			// expected
@@ -219,8 +245,7 @@
 	public void testIgnoreNonExistingObjects() throws IOException {
 		final ObjectId nonExisting = ObjectId
 				.fromString("0000000000000000000000000000000000000001");
-		createVerifyOpenPack(NONE, Collections.singleton(nonExisting),
-				false, true);
+		createVerifyOpenPack(NONE, haves(nonExisting), false, true);
 		// shouldn't throw anything
 	}
 
@@ -238,8 +263,7 @@
 		final ObjectId nonExisting = ObjectId
 				.fromString("0000000000000000000000000000000000000001");
 		new GC(db).gc();
-		createVerifyOpenPack(NONE, Collections.singleton(nonExisting), false,
-				true, true);
+		createVerifyOpenPack(NONE, haves(nonExisting), false, true, true);
 		// shouldn't throw anything
 	}
 
@@ -513,20 +537,18 @@
 		TestRepository<FileRepository> testRepo = new TestRepository<FileRepository>(
 				repo);
 		BranchBuilder bb = testRepo.branch("refs/heads/master");
-		RevBlob contentA = testRepo.blob("A");
-		RevCommit c1 = bb.commit().add("f", contentA).create();
+		contentA = testRepo.blob("A");
+		c1 = bb.commit().add("f", contentA).create();
 		testRepo.getRevWalk().parseHeaders(c1);
-		PackIndex pf1 = writePack(repo, Collections.singleton(c1),
-				Collections.<ObjectIdSet> emptySet());
+		PackIndex pf1 = writePack(repo, wants(c1), EMPTY_ID_SET);
 		assertContent(
 				pf1,
 				Arrays.asList(c1.getId(), c1.getTree().getId(),
 						contentA.getId()));
-		RevBlob contentB = testRepo.blob("B");
-		RevCommit c2 = bb.commit().add("f", contentB).create();
+		contentB = testRepo.blob("B");
+		c2 = bb.commit().add("f", contentB).create();
 		testRepo.getRevWalk().parseHeaders(c2);
-		PackIndex pf2 = writePack(repo, Collections.singleton(c2),
-				Collections.<ObjectIdSet> singleton(pf1));
+		PackIndex pf2 = writePack(repo, wants(c2), Sets.of((ObjectIdSet) pf1));
 		assertContent(
 				pf2,
 				Arrays.asList(c2.getId(), c2.getTree().getId(),
@@ -543,15 +565,149 @@
 					expected.contains(pi.getObjectId(i)));
 	}
 
+	@Test
+	public void testShallowIsMinimalDepth1() throws Exception {
+		FileRepository repo = setupRepoForShallowFetch();
+
+		PackIndex idx = writeShallowPack(repo, 1, wants(c2), NONE, NONE);
+		assertContent(idx, Arrays.asList(c2.getId(), c2.getTree().getId(),
+				contentA.getId(), contentB.getId()));
+
+		// Client already has blobs A and B, verify those are not packed.
+		idx = writeShallowPack(repo, 1, wants(c5), haves(c2), shallows(c2));
+		assertContent(idx, Arrays.asList(c5.getId(), c5.getTree().getId(),
+				contentC.getId(), contentD.getId(), contentE.getId()));
+	}
+
+	@Test
+	public void testShallowIsMinimalDepth2() throws Exception {
+		FileRepository repo = setupRepoForShallowFetch();
+
+		PackIndex idx = writeShallowPack(repo, 2, wants(c2), NONE, NONE);
+		assertContent(idx,
+				Arrays.asList(c1.getId(), c2.getId(), c1.getTree().getId(),
+						c2.getTree().getId(), contentA.getId(),
+						contentB.getId()));
+
+		// Client already has blobs A and B, verify those are not packed.
+		idx = writeShallowPack(repo, 2, wants(c5), haves(c1, c2), shallows(c1));
+		assertContent(idx,
+				Arrays.asList(c4.getId(), c5.getId(), c4.getTree().getId(),
+						c5.getTree().getId(), contentC.getId(),
+						contentD.getId(), contentE.getId()));
+	}
+
+	@Test
+	public void testShallowFetchShallowParentDepth1() throws Exception {
+		FileRepository repo = setupRepoForShallowFetch();
+
+		PackIndex idx = writeShallowPack(repo, 1, wants(c5), NONE, NONE);
+		assertContent(idx,
+				Arrays.asList(c5.getId(), c5.getTree().getId(),
+						contentA.getId(), contentB.getId(), contentC.getId(),
+						contentD.getId(), contentE.getId()));
+
+		idx = writeShallowPack(repo, 1, wants(c4), haves(c5), shallows(c5));
+		assertContent(idx, Arrays.asList(c4.getId(), c4.getTree().getId()));
+	}
+
+	@Test
+	public void testShallowFetchShallowParentDepth2() throws Exception {
+		FileRepository repo = setupRepoForShallowFetch();
+
+		PackIndex idx = writeShallowPack(repo, 2, wants(c5), NONE, NONE);
+		assertContent(idx,
+				Arrays.asList(c4.getId(), c5.getId(), c4.getTree().getId(),
+						c5.getTree().getId(), contentA.getId(),
+						contentB.getId(), contentC.getId(), contentD.getId(),
+						contentE.getId()));
+
+		idx = writeShallowPack(repo, 2, wants(c3), haves(c4, c5), shallows(c4));
+		assertContent(idx, Arrays.asList(c2.getId(), c3.getId(),
+				c2.getTree().getId(), c3.getTree().getId()));
+	}
+
+	@Test
+	public void testShallowFetchShallowAncestorDepth1() throws Exception {
+		FileRepository repo = setupRepoForShallowFetch();
+
+		PackIndex idx = writeShallowPack(repo, 1, wants(c5), NONE, NONE);
+		assertContent(idx,
+				Arrays.asList(c5.getId(), c5.getTree().getId(),
+						contentA.getId(), contentB.getId(), contentC.getId(),
+						contentD.getId(), contentE.getId()));
+
+		idx = writeShallowPack(repo, 1, wants(c3), haves(c5), shallows(c5));
+		assertContent(idx, Arrays.asList(c3.getId(), c3.getTree().getId()));
+	}
+
+	@Test
+	public void testShallowFetchShallowAncestorDepth2() throws Exception {
+		FileRepository repo = setupRepoForShallowFetch();
+
+		PackIndex idx = writeShallowPack(repo, 2, wants(c5), NONE, NONE);
+		assertContent(idx,
+				Arrays.asList(c4.getId(), c5.getId(), c4.getTree().getId(),
+						c5.getTree().getId(), contentA.getId(),
+						contentB.getId(), contentC.getId(), contentD.getId(),
+						contentE.getId()));
+
+		idx = writeShallowPack(repo, 2, wants(c2), haves(c4, c5), shallows(c4));
+		assertContent(idx, Arrays.asList(c1.getId(), c2.getId(),
+				c1.getTree().getId(), c2.getTree().getId()));
+	}
+
+	private FileRepository setupRepoForShallowFetch() throws Exception {
+		FileRepository repo = createBareRepository();
+		TestRepository<Repository> r = new TestRepository<Repository>(repo);
+		BranchBuilder bb = r.branch("refs/heads/master");
+		contentA = r.blob("A");
+		contentB = r.blob("B");
+		contentC = r.blob("C");
+		contentD = r.blob("D");
+		contentE = r.blob("E");
+		c1 = bb.commit().add("a", contentA).create();
+		c2 = bb.commit().add("b", contentB).create();
+		c3 = bb.commit().add("c", contentC).create();
+		c4 = bb.commit().add("d", contentD).create();
+		c5 = bb.commit().add("e", contentE).create();
+		r.getRevWalk().parseHeaders(c5); // fully initialize the tip RevCommit
+		return repo;
+	}
+
 	private static PackIndex writePack(FileRepository repo,
 			Set<? extends ObjectId> want, Set<ObjectIdSet> excludeObjects)
-			throws IOException {
+					throws IOException {
+		RevWalk walk = new RevWalk(repo);
+		return writePack(repo, walk, 0, want, NONE, excludeObjects);
+	}
+
+	private static PackIndex writeShallowPack(FileRepository repo, int depth,
+			Set<? extends ObjectId> want, Set<? extends ObjectId> have,
+			Set<? extends ObjectId> shallow) throws IOException {
+		// During negotiation, UploadPack would have set up a DepthWalk and
+		// marked the client's "shallow" commits. Emulate that here.
+		DepthWalk.RevWalk walk = new DepthWalk.RevWalk(repo, depth - 1);
+		walk.assumeShallow(shallow);
+		return writePack(repo, walk, depth, want, have, EMPTY_ID_SET);
+	}
+
+	private static PackIndex writePack(FileRepository repo, RevWalk walk,
+			int depth, Set<? extends ObjectId> want,
+			Set<? extends ObjectId> have, Set<ObjectIdSet> excludeObjects)
+					throws IOException {
 		try (PackWriter pw = new PackWriter(repo)) {
 			pw.setDeltaBaseAsOffset(true);
 			pw.setReuseDeltaCommits(false);
-			for (ObjectIdSet idx : excludeObjects)
+			for (ObjectIdSet idx : excludeObjects) {
 				pw.excludeObjects(idx);
-			pw.preparePack(NullProgressMonitor.INSTANCE, want, NONE);
+			}
+			if (depth > 0) {
+				pw.setShallowPack(depth, null);
+			}
+			ObjectWalk ow = walk.toObjectWalkWithSameObjects();
+
+			pw.preparePack(NullProgressMonitor.INSTANCE, ow, want, have);
 			String id = pw.computeName().getName();
 			File packdir = new File(repo.getObjectsDirectory(), "pack");
 			File packFile = new File(packdir, "pack-" + id + ".pack");
@@ -730,4 +886,16 @@
 			assertEquals(objectsOrder[i++].toObjectId(), me.toObjectId());
 		}
 	}
+
+	private static Set<ObjectId> haves(ObjectId... objects) {
+		return Sets.of(objects);
+	}
+
+	private static Set<ObjectId> wants(ObjectId... objects) {
+		return Sets.of(objects);
+	}
+
+	private static Set<ObjectId> shallows(ObjectId... objects) {
+		return Sets.of(objects);
+	}
 }
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/junit/TestRepositoryTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/junit/TestRepositoryTest.java
index 8b54dab..b7027f3 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/junit/TestRepositoryTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/junit/TestRepositoryTest.java
@@ -62,6 +62,7 @@
 import org.eclipse.jgit.lib.ObjectLoader;
 import org.eclipse.jgit.lib.PersonIdent;
 import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.RefUpdate;
 import org.eclipse.jgit.revwalk.RevBlob;
 import org.eclipse.jgit.revwalk.RevCommit;
 import org.eclipse.jgit.revwalk.RevObject;
@@ -374,6 +375,44 @@
 		assertEquals(head, repo.exactRef("HEAD").getObjectId());
 	}
 
+	@Test
+	public void reattachToMaster_Race() throws Exception {
+		RevCommit commit = tr.branch("master").commit().create();
+		tr.branch("master").update(commit);
+		tr.branch("other").update(commit);
+		repo.updateRef("HEAD").link("refs/heads/master");
+
+		// Create a detached HEAD that is not an .
+		tr.reset(commit);
+		Ref head = repo.exactRef("HEAD");
+		assertEquals(commit, head.getObjectId());
+		assertFalse(head.isSymbolic());
+
+		// Try to reattach to master.
+		RefUpdate refUpdate = repo.updateRef("HEAD");
+
+		// Make a change during reattachment.
+		repo.updateRef("HEAD").link("refs/heads/other");
+
+		assertEquals(
+				RefUpdate.Result.LOCK_FAILURE, refUpdate.link("refs/heads/master"));
+	}
+
+	@Test
+	public void nonRacingChange() throws Exception {
+		tr.branch("master").update(tr.branch("master").commit().create());
+		tr.branch("other").update(tr.branch("other").commit().create());
+		repo.updateRef("HEAD").link("refs/heads/master");
+
+		// Try to update HEAD.
+		RefUpdate refUpdate = repo.updateRef("HEAD");
+
+		// Proceed a master. This should not affect changing HEAD.
+		tr.branch("master").update(tr.branch("master").commit().create());
+
+		assertEquals(RefUpdate.Result.FORCED, refUpdate.link("refs/heads/other"));
+	}
+
 	private String blobAsString(AnyObjectId treeish, String path)
 			throws Exception {
 		RevObject obj = tr.get(rw.parseTree(treeish), path);
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 fbe7dd0..bb553a4 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
@@ -1614,6 +1614,64 @@
 		assertNotNull(git.checkout().setName(Constants.MASTER).call());
 	}
 
+	@Test(expected = CheckoutConflictException.class)
+	public void testFolderFileConflict() throws Exception {
+		RevCommit headCommit = commitFile("f/a", "initial content", "master");
+		RevCommit checkoutCommit = commitFile("f/a", "side content", "side");
+		FileUtils.delete(new File(db.getWorkTree(), "f"), FileUtils.RECURSIVE);
+		writeTrashFile("f", "file instead of folder");
+		new DirCacheCheckout(db, headCommit.getTree(), db.lockDirCache(),
+				checkoutCommit.getTree()).checkout();
+	}
+
+	@Test
+	public void testMultipleContentConflicts() throws Exception {
+		commitFile("a", "initial content", "master");
+		RevCommit headCommit = commitFile("b", "initial content", "master");
+		commitFile("a", "side content", "side");
+		RevCommit checkoutCommit = commitFile("b", "side content", "side");
+		writeTrashFile("a", "changed content");
+		writeTrashFile("b", "changed content");
+
+		try {
+			new DirCacheCheckout(db, headCommit.getTree(), db.lockDirCache(),
+					checkoutCommit.getTree()).checkout();
+			fail();
+		} catch (CheckoutConflictException expected) {
+			assertEquals(2, expected.getConflictingFiles().length);
+			assertTrue(Arrays.asList(expected.getConflictingFiles())
+					.contains("a"));
+			assertTrue(Arrays.asList(expected.getConflictingFiles())
+					.contains("b"));
+			assertEquals("changed content", read("a"));
+			assertEquals("changed content", read("b"));
+		}
+	}
+
+	@Test
+	public void testFolderFileAndContentConflicts() throws Exception {
+		RevCommit headCommit = commitFile("f/a", "initial content", "master");
+		commitFile("b", "side content", "side");
+		RevCommit checkoutCommit = commitFile("f/a", "side content", "side");
+		FileUtils.delete(new File(db.getWorkTree(), "f"), FileUtils.RECURSIVE);
+		writeTrashFile("f", "file instead of a folder");
+		writeTrashFile("b", "changed content");
+
+		try {
+			new DirCacheCheckout(db, headCommit.getTree(), db.lockDirCache(),
+					checkoutCommit.getTree()).checkout();
+			fail();
+		} catch (CheckoutConflictException expected) {
+			assertEquals(2, expected.getConflictingFiles().length);
+			assertTrue(Arrays.asList(expected.getConflictingFiles())
+					.contains("b"));
+			assertTrue(Arrays.asList(expected.getConflictingFiles())
+					.contains("f"));
+			assertEquals("file instead of a folder", read("f"));
+			assertEquals("changed content", read("b"));
+		}
+	}
+
 	public void assertWorkDir(Map<String, String> i)
 			throws CorruptObjectException,
 			IOException {
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RepositoryCacheTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RepositoryCacheTest.java
index 6bea320..b44b4c3 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RepositoryCacheTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RepositoryCacheTest.java
@@ -210,6 +210,27 @@
 	}
 
 	@Test
+	public void testRepositoryUnregisteringWhenExpiredAndUsageCountNegative()
+			throws Exception {
+		Repository repoA = createBareRepository();
+		RepositoryCache.register(repoA);
+
+		assertEquals(1, RepositoryCache.getRegisteredKeys().size());
+		assertTrue(RepositoryCache.isCached(repoA));
+
+		// close the repo twice to make usage count negative
+		repoA.close();
+		repoA.close();
+		// fake that repoA was closed more than 1 hour ago (default expiration
+		// time)
+		repoA.closedAt.set(System.currentTimeMillis() - 65 * 60 * 1000);
+
+		RepositoryCache.clearExpired();
+
+		assertEquals(0, RepositoryCache.getRegisteredKeys().size());
+	}
+
+	@Test
 	public void testRepositoryUnregisteringWhenExpired() throws Exception {
 		Repository repoA = createBareRepository();
 		Repository repoB = createBareRepository();
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 55bb93a..a08dbbc 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
@@ -62,6 +62,7 @@
 import org.eclipse.jgit.merge.ResolveMerger.MergeFailureReason;
 import org.eclipse.jgit.revwalk.RevCommit;
 import org.eclipse.jgit.treewalk.FileTreeIterator;
+import org.eclipse.jgit.util.FS;
 import org.eclipse.jgit.util.FileUtils;
 import org.junit.Assert;
 import org.junit.experimental.theories.DataPoint;
@@ -694,7 +695,7 @@
 
 		// Create initial content and remember when the last file was written.
 		f = writeTrashFiles(false, "orig", "orig", "1\n2\n3", "orig", "orig");
-		lastTs4 = f.lastModified();
+		lastTs4 = FS.DETECTED.lastModified(f);
 
 		// add all files, commit and check this doesn't update any working tree
 		// files and that the index is in a new file system timer tick. Make
@@ -707,8 +708,8 @@
 		checkConsistentLastModified("0", "1", "2", "3", "4");
 		checkModificationTimeStampOrder("1", "2", "3", "4", "<.git/index");
 		assertEquals("Commit should not touch working tree file 4", lastTs4,
-				new File(db.getWorkTree(), "4").lastModified());
-		lastTsIndex = indexFile.lastModified();
+				FS.DETECTED.lastModified(new File(db.getWorkTree(), "4")));
+		lastTsIndex = FS.DETECTED.lastModified(indexFile);
 
 		// Do modifications on the master branch. Then add and commit. This
 		// should touch only "0", "2 and "3"
@@ -722,7 +723,7 @@
 		checkConsistentLastModified("0", "1", "2", "3", "4");
 		checkModificationTimeStampOrder("1", "4", "*" + lastTs4, "<*"
 				+ lastTsIndex, "<0", "2", "3", "<.git/index");
-		lastTsIndex = indexFile.lastModified();
+		lastTsIndex = FS.DETECTED.lastModified(indexFile);
 
 		// Checkout a side branch. This should touch only "0", "2 and "3"
 		fsTick(indexFile);
@@ -731,7 +732,7 @@
 		checkConsistentLastModified("0", "1", "2", "3", "4");
 		checkModificationTimeStampOrder("1", "4", "*" + lastTs4, "<*"
 				+ lastTsIndex, "<0", "2", "3", ".git/index");
-		lastTsIndex = indexFile.lastModified();
+		lastTsIndex = FS.DETECTED.lastModified(indexFile);
 
 		// This checkout may have populated worktree and index so fast that we
 		// may have smudged entries now. Check that we have the right content
@@ -744,13 +745,13 @@
 				indexState(CONTENT));
 		fsTick(indexFile);
 		f = writeTrashFiles(false, "orig", "orig", "1\n2\n3", "orig", "orig");
-		lastTs4 = f.lastModified();
+		lastTs4 = FS.DETECTED.lastModified(f);
 		fsTick(f);
 		git.add().addFilepattern(".").call();
 		checkConsistentLastModified("0", "1", "2", "3", "4");
 		checkModificationTimeStampOrder("*" + lastTsIndex, "<0", "1", "2", "3",
 				"4", "<.git/index");
-		lastTsIndex = indexFile.lastModified();
+		lastTsIndex = FS.DETECTED.lastModified(indexFile);
 
 		// Do modifications on the side branch. Touch only "1", "2 and "3"
 		fsTick(indexFile);
@@ -761,7 +762,7 @@
 		checkConsistentLastModified("0", "1", "2", "3", "4");
 		checkModificationTimeStampOrder("0", "4", "*" + lastTs4, "<*"
 				+ lastTsIndex, "<1", "2", "3", "<.git/index");
-		lastTsIndex = indexFile.lastModified();
+		lastTsIndex = FS.DETECTED.lastModified(indexFile);
 
 		// merge master and side. Should only touch "0," "2" and "3"
 		fsTick(indexFile);
@@ -789,7 +790,7 @@
 					"IndexEntry with path "
 							+ path
 							+ " has lastmodified with is different from the worktree file",
-					new File(workTree, path).lastModified(), dc.getEntry(path)
+					FS.DETECTED.lastModified(new File(workTree, path)), dc.getEntry(path)
 							.getLastModified());
 	}
 
@@ -799,14 +800,15 @@
 	// then this file must be younger then file i. A path "*<modtime>"
 	// represents a file with a modification time of <modtime>
 	// E.g. ("a", "b", "<c", "f/a.txt") means: a<=b<c<=f/a.txt
-	private void checkModificationTimeStampOrder(String... pathes) {
+	private void checkModificationTimeStampOrder(String... pathes)
+			throws IOException {
 		long lastMod = Long.MIN_VALUE;
 		for (String p : pathes) {
 			boolean strong = p.startsWith("<");
 			boolean fixed = p.charAt(strong ? 1 : 0) == '*';
 			p = p.substring((strong ? 1 : 0) + (fixed ? 1 : 0));
-			long curMod = fixed ? Long.valueOf(p).longValue() : new File(
-					db.getWorkTree(), p).lastModified();
+			long curMod = fixed ? Long.valueOf(p).longValue()
+					: FS.DETECTED.lastModified(new File(db.getWorkTree(), p));
 			if (strong)
 				assertTrue("path " + p + " is not younger than predecesssor",
 						curMod > lastMod);
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/notes/NoteMapTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/notes/NoteMapTest.java
index 47b08a7..325c9e2 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/notes/NoteMapTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/notes/NoteMapTest.java
@@ -449,11 +449,13 @@
 		assertEquals("empty tree", empty, n.getTree());
 	}
 
+	@Test
 	public void testIteratorEmptyMap() {
 		Iterator<Note> it = NoteMap.newEmptyMap().iterator();
 		assertFalse(it.hasNext());
 	}
 
+	@Test
 	public void testIteratorFlatTree() throws Exception {
 		RevBlob a = tr.blob("a");
 		RevBlob b = tr.blob("b");
@@ -472,6 +474,7 @@
 		assertEquals(2, count(it));
 	}
 
+	@Test
 	public void testIteratorFanoutTree2_38() throws Exception {
 		RevBlob a = tr.blob("a");
 		RevBlob b = tr.blob("b");
@@ -490,6 +493,7 @@
 		assertEquals(2, count(it));
 	}
 
+	@Test
 	public void testIteratorFanoutTree2_2_36() throws Exception {
 		RevBlob a = tr.blob("a");
 		RevBlob b = tr.blob("b");
@@ -508,6 +512,7 @@
 		assertEquals(2, count(it));
 	}
 
+	@Test
 	public void testIteratorFullyFannedOut() throws Exception {
 		RevBlob a = tr.blob("a");
 		RevBlob b = tr.blob("b");
@@ -526,12 +531,13 @@
 		assertEquals(2, count(it));
 	}
 
+	@Test
 	public void testShorteningNoteRefName() throws Exception {
 		String expectedShortName = "review";
 		String noteRefName = Constants.R_NOTES + expectedShortName;
 		assertEquals(expectedShortName, NoteMap.shortenRefName(noteRefName));
 		String nonNoteRefName = Constants.R_HEADS + expectedShortName;
-		assertEquals(nonNoteRefName, NoteMap.shortenRefName(expectedShortName));
+		assertEquals(nonNoteRefName, NoteMap.shortenRefName(nonNoteRefName));
 	}
 
 	private RevCommit commitNoteMap(NoteMap map) throws IOException {
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkCarryFlagsTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkCarryFlagsTest.java
new file mode 100644
index 0000000..66814b3
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkCarryFlagsTest.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2016, Christian Halstrick <christian.halstrick@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.revwalk;
+
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+
+public class RevWalkCarryFlagsTest extends RevWalkTestCase {
+	/**
+	 * Test that the uninteresting flag is carried over correctly. Every commit
+	 * should have the uninteresting flag resulting in a RevWalk returning no
+	 * commit.
+	 *
+	 * @throws Exception
+	 */
+	@Test
+	public void testRevWalkCarryUninteresting_fastClock() throws Exception {
+		final RevCommit a = commit();
+		final RevCommit b = commit(a);
+		final RevCommit c = commit(a);
+		final RevCommit d = commit(c);
+		final RevCommit e = commit(b, d);
+
+		markStart(d);
+		markUninteresting(e);
+		assertNull("Found an unexpected commit", rw.next());
+	}
+
+	/**
+	 * Similar to {@link #testRevWalkCarryUninteresting_fastClock()} but the
+	 * last merge commit is created so fast that he has the same creationdate as
+	 * the previous commit. This will cause the underlying {@link DateRevQueue}
+	 * is not able to sort the commits in a way matching the topology. A parent
+	 * (one of the commits which are merged) is handled before the child (the
+	 * merge commit). This makes carrying over flags more complicated
+	 *
+	 * @throws Exception
+	 */
+	@Test
+	public void testRevWalkCarryUninteresting_SlowClock() throws Exception {
+		final RevCommit a = commit();
+		final RevCommit b = commit(a);
+		final RevCommit c = commit(a);
+		final RevCommit d = commit(c);
+		final RevCommit e = commit(0, b, d);
+
+		markStart(d);
+		markUninteresting(e);
+		assertNull("Found an unexpected commit", rw.next());
+	}
+
+	/**
+	 * Similar to {@link #testRevWalkCarryUninteresting_SlowClock()} but the
+	 * last merge commit is created with a inconsistent creationdate. The merge
+	 * commit has a older creationdate then one of the commits he is merging.
+	 *
+	 * @throws Exception
+	 */
+	@Test
+	public void testRevWalkCarryUninteresting_WrongClock() throws Exception {
+		final RevCommit a = commit();
+		final RevCommit b = commit(a);
+		final RevCommit c = commit(a);
+		final RevCommit d = commit(c);
+		final RevCommit e = commit(-1, b, d);
+
+		markStart(d);
+		markUninteresting(e);
+		assertNull("Found an unexpected commit", rw.next());
+	}
+
+	/**
+	 * Same as {@link #testRevWalkCarryUninteresting_SlowClock()} but this time
+	 * we focus on the carrying over a custom flag.
+	 *
+	 * @throws Exception
+	 */
+	@Test
+	public void testRevWalkCarryCustom_SlowClock() throws Exception {
+		final RevCommit a = commit();
+		final RevCommit b = commit(a);
+		final RevCommit c = commit(a);
+		final RevCommit d = commit(c);
+		final RevCommit e = commit(0, b, d);
+
+		markStart(d);
+		markStart(e);
+		RevFlag customFlag = rw.newFlag("CUSTOM");
+		e.flags |= customFlag.mask;
+		rw.carry(customFlag);
+
+		// the merge commit has the flag and it should be carried over -> every
+		// commit should have this flag
+		int count = 0;
+		for (RevCommit cm : rw) {
+			assertTrue(
+					"Found a commit which doesn't have the custom flag: " + cm,
+					cm.has(customFlag));
+			count++;
+		}
+		assertTrue("Didn't walked over all commits", count == 5);
+	}
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/HttpAuthTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/HttpAuthTest.java
index 5233013..3dc022d 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/HttpAuthTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/HttpAuthTest.java
@@ -100,7 +100,7 @@
 		} catch (IOException e) {
 			fail("Couldn't instantiate AuthHeadersResponse: " + e.toString());
 		}
-		HttpAuthMethod authMethod = HttpAuthMethod.scanResponse(response);
+		HttpAuthMethod authMethod = HttpAuthMethod.scanResponse(response, null);
 
 		if (!expectedAuthMethod.equals(getAuthMethodName(authMethod))) {
 			fail("Wrong authentication method: expected " + expectedAuthMethod
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushConnectionTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushConnectionTest.java
index a3b4134..33a9105 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushConnectionTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushConnectionTest.java
@@ -43,13 +43,18 @@
 
 package org.eclipse.jgit.transport;
 
+import static org.eclipse.jgit.transport.BasePackPushConnection.CAPABILITY_REPORT_STATUS;
+import static org.eclipse.jgit.transport.BasePackPushConnection.CAPABILITY_SIDE_BAND_64K;
 import static org.eclipse.jgit.transport.RemoteRefUpdate.Status.REJECTED_OTHER_REASON;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
 
 import java.io.IOException;
+import java.io.StringWriter;
 import java.util.HashMap;
 import java.util.Map;
 
+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;
@@ -125,19 +130,47 @@
 		Map<String, RemoteRefUpdate> updates = new HashMap<>();
 		updates.put(rru.getRemoteName(), rru);
 
-		Transport tn = testProtocol.open(uri, client, "server");
-		try {
-			PushConnection connection = tn.openPush();
-			try {
-				connection.push(NullProgressMonitor.INSTANCE, updates);
-			} finally {
-				connection.close();
-			}
-		} finally {
-			tn.close();
+		try (Transport tn = testProtocol.open(uri, client, "server");
+				PushConnection connection = tn.openPush()) {
+			connection.push(NullProgressMonitor.INSTANCE, updates);
 		}
 
 		assertEquals(REJECTED_OTHER_REASON, rru.getStatus());
 		assertEquals("invalid old id sent", rru.getMessage());
 	}
+
+	@Test
+	public void invalidCommand() throws IOException {
+		try (Transport tn = testProtocol.open(uri, client, "server");
+				InternalPushConnection c = (InternalPushConnection) tn.openPush()) {
+			StringWriter msgs = new StringWriter();
+			PacketLineOut pckOut = c.pckOut;
+
+			@SuppressWarnings("resource")
+			SideBandInputStream in = new SideBandInputStream(c.in,
+					NullProgressMonitor.INSTANCE, msgs, null);
+
+			// Explicitly invalid command, but sane enough capabilities.
+			StringBuilder buf = new StringBuilder();
+			buf.append("42");
+			buf.append(' ');
+			buf.append(obj2.name());
+			buf.append(' ');
+			buf.append("refs/heads/A" + obj2.name());
+			buf.append('\0').append(CAPABILITY_SIDE_BAND_64K);
+			buf.append(' ').append(CAPABILITY_REPORT_STATUS);
+			buf.append('\n');
+			pckOut.writeString(buf.toString());
+			pckOut.end();
+
+			try {
+				in.read();
+				fail("expected TransportException");
+			} catch (TransportException e) {
+				assertEquals(
+						"remote: error: invalid protocol: wanted 'old new ref'",
+						e.getMessage());
+			}
+		}
+	}
 }
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushOptionsTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushOptionsTest.java
new file mode 100644
index 0000000..c346d79
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushOptionsTest.java
@@ -0,0 +1,361 @@
+/*
+ * 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.transport;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.fail;
+
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.api.PushCommand;
+import org.eclipse.jgit.api.errors.GitAPIException;
+import org.eclipse.jgit.api.errors.NoFilepatternException;
+import org.eclipse.jgit.api.errors.TransportException;
+import org.eclipse.jgit.internal.storage.dfs.DfsRepositoryDescription;
+import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
+import org.eclipse.jgit.junit.RepositoryTestCase;
+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.Repository;
+import org.eclipse.jgit.lib.StoredConfig;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.transport.resolver.ReceivePackFactory;
+import org.eclipse.jgit.transport.resolver.ServiceNotAuthorizedException;
+import org.eclipse.jgit.transport.resolver.ServiceNotEnabledException;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+public class PushOptionsTest extends RepositoryTestCase {
+	private URIish uri;
+	private TestProtocol<Object> testProtocol;
+	private Object ctx = new Object();
+	private InMemoryRepository server;
+	private InMemoryRepository client;
+	private ObjectId obj1;
+	private ObjectId obj2;
+	private ReceivePack receivePack;
+
+	@Before
+	public void setUp() throws Exception {
+		super.setUp();
+
+		server = newRepo("server");
+		client = newRepo("client");
+
+		testProtocol = new TestProtocol<>(null,
+				new ReceivePackFactory<Object>() {
+					@Override
+					public ReceivePack create(Object req, Repository git)
+							throws ServiceNotEnabledException,
+							ServiceNotAuthorizedException {
+						receivePack = new ReceivePack(git);
+						receivePack.setAllowPushOptions(true);
+						receivePack.setAtomic(true);
+						return receivePack;
+					}
+				});
+
+		uri = testProtocol.register(ctx, server);
+
+		try (ObjectInserter ins = client.newObjectInserter()) {
+			obj1 = ins.insert(Constants.OBJ_BLOB, Constants.encode("test"));
+			obj2 = ins.insert(Constants.OBJ_BLOB, Constants.encode("file"));
+			ins.flush();
+		}
+	}
+
+	@After
+	public void tearDown() {
+		Transport.unregister(testProtocol);
+	}
+
+	private static InMemoryRepository newRepo(String name) {
+		return new InMemoryRepository(new DfsRepositoryDescription(name));
+	}
+
+	private List<RemoteRefUpdate> commands(boolean atomicSafe)
+			throws IOException {
+		List<RemoteRefUpdate> cmds = new ArrayList<>();
+		cmds.add(new RemoteRefUpdate(null, null, obj1, "refs/heads/one",
+				true /* force update */, null /* no local tracking ref */,
+				ObjectId.zeroId()));
+		cmds.add(new RemoteRefUpdate(null, null, obj2, "refs/heads/two",
+				true /* force update */, null /* no local tracking ref */,
+				atomicSafe ? ObjectId.zeroId() : obj1));
+		return cmds;
+	}
+
+	private void connectLocalToRemote(Git local, Git remote)
+			throws URISyntaxException, IOException {
+		StoredConfig config = local.getRepository().getConfig();
+		RemoteConfig remoteConfig = new RemoteConfig(config, "test");
+		remoteConfig.addURI(new URIish(
+				remote.getRepository().getDirectory().toURI().toURL()));
+		remoteConfig.addFetchRefSpec(
+				new RefSpec("+refs/heads/*:refs/remotes/origin/*"));
+		remoteConfig.update(config);
+		config.save();
+	}
+
+	private RevCommit addCommit(Git git)
+			throws IOException, NoFilepatternException, GitAPIException {
+		writeTrashFile("f", "content of f");
+		git.add().addFilepattern("f").call();
+		return git.commit().setMessage("adding f").call();
+	}
+
+	@Test
+	public void testNonAtomicPushWithOptions() throws Exception {
+		PushResult r;
+		server.setPerformsAtomicTransactions(false);
+		List<String> pushOptions = Arrays.asList("Hello", "World!");
+
+		try (Transport tn = testProtocol.open(uri, client, "server")) {
+			tn.setPushAtomic(false);
+			tn.setPushOptions(pushOptions);
+
+			r = tn.push(NullProgressMonitor.INSTANCE, commands(false));
+		}
+
+		RemoteRefUpdate one = r.getRemoteUpdate("refs/heads/one");
+		RemoteRefUpdate two = r.getRemoteUpdate("refs/heads/two");
+
+		assertSame(RemoteRefUpdate.Status.OK, one.getStatus());
+		assertSame(RemoteRefUpdate.Status.REJECTED_REMOTE_CHANGED,
+				two.getStatus());
+		assertEquals(pushOptions, receivePack.getPushOptions());
+	}
+
+	@Test
+	public void testAtomicPushWithOptions() throws Exception {
+		PushResult r;
+		server.setPerformsAtomicTransactions(true);
+		List<String> pushOptions = Arrays.asList("Hello", "World!");
+
+		try (Transport tn = testProtocol.open(uri, client, "server")) {
+			tn.setPushAtomic(true);
+			tn.setPushOptions(pushOptions);
+
+			r = tn.push(NullProgressMonitor.INSTANCE, commands(true));
+		}
+
+		RemoteRefUpdate one = r.getRemoteUpdate("refs/heads/one");
+		RemoteRefUpdate two = r.getRemoteUpdate("refs/heads/two");
+
+		assertSame(RemoteRefUpdate.Status.OK, one.getStatus());
+		assertSame(RemoteRefUpdate.Status.OK, two.getStatus());
+		assertEquals(pushOptions, receivePack.getPushOptions());
+	}
+
+	@Test
+	public void testFailedAtomicPushWithOptions() throws Exception {
+		PushResult r;
+		server.setPerformsAtomicTransactions(true);
+		List<String> pushOptions = Arrays.asList("Hello", "World!");
+
+		try (Transport tn = testProtocol.open(uri, client, "server")) {
+			tn.setPushAtomic(true);
+			tn.setPushOptions(pushOptions);
+
+			r = tn.push(NullProgressMonitor.INSTANCE, commands(false));
+		}
+
+		RemoteRefUpdate one = r.getRemoteUpdate("refs/heads/one");
+		RemoteRefUpdate two = r.getRemoteUpdate("refs/heads/two");
+
+		assertSame(RemoteRefUpdate.Status.REJECTED_OTHER_REASON,
+				one.getStatus());
+		assertSame(RemoteRefUpdate.Status.REJECTED_REMOTE_CHANGED,
+				two.getStatus());
+		assertNull(receivePack.getPushOptions());
+	}
+
+	@Test
+	public void testThinPushWithOptions() throws Exception {
+		PushResult r;
+		List<String> pushOptions = Arrays.asList("Hello", "World!");
+
+		try (Transport tn = testProtocol.open(uri, client, "server")) {
+			tn.setPushThin(true);
+			tn.setPushOptions(pushOptions);
+
+			r = tn.push(NullProgressMonitor.INSTANCE, commands(false));
+		}
+
+		RemoteRefUpdate one = r.getRemoteUpdate("refs/heads/one");
+		RemoteRefUpdate two = r.getRemoteUpdate("refs/heads/two");
+
+		assertSame(RemoteRefUpdate.Status.OK, one.getStatus());
+		assertSame(RemoteRefUpdate.Status.REJECTED_REMOTE_CHANGED,
+				two.getStatus());
+		assertEquals(pushOptions, receivePack.getPushOptions());
+	}
+
+	@Test
+	public void testPushWithoutOptions() throws Exception {
+		try (Git local = new Git(db);
+				Git remote = new Git(createBareRepository())) {
+			connectLocalToRemote(local, remote);
+
+			final StoredConfig config2 = remote.getRepository().getConfig();
+			config2.setBoolean("receive", null, "pushoptions", true);
+			config2.save();
+
+			RevCommit commit = addCommit(local);
+
+			local.checkout().setName("not-pushed").setCreateBranch(true).call();
+			local.checkout().setName("branchtopush").setCreateBranch(true).call();
+
+			assertNull(remote.getRepository().resolve("refs/heads/branchtopush"));
+			assertNull(remote.getRepository().resolve("refs/heads/not-pushed"));
+			assertNull(remote.getRepository().resolve("refs/heads/master"));
+
+			PushCommand pushCommand = local.push().setRemote("test");
+			pushCommand.call();
+
+			assertEquals(commit.getId(),
+					remote.getRepository().resolve("refs/heads/branchtopush"));
+			assertNull(remote.getRepository().resolve("refs/heads/not-pushed"));
+			assertNull(remote.getRepository().resolve("refs/heads/master"));
+		}
+	}
+
+	@Test
+	public void testPushWithEmptyOptions() throws Exception {
+		try (Git local = new Git(db);
+				Git remote = new Git(createBareRepository())) {
+			connectLocalToRemote(local, remote);
+
+			final StoredConfig config2 = remote.getRepository().getConfig();
+			config2.setBoolean("receive", null, "pushoptions", true);
+			config2.save();
+
+			RevCommit commit = addCommit(local);
+
+			local.checkout().setName("not-pushed").setCreateBranch(true).call();
+			local.checkout().setName("branchtopush").setCreateBranch(true).call();
+			assertNull(remote.getRepository().resolve("refs/heads/branchtopush"));
+			assertNull(remote.getRepository().resolve("refs/heads/not-pushed"));
+			assertNull(remote.getRepository().resolve("refs/heads/master"));
+
+			List<String> pushOptions = new ArrayList<>();
+			PushCommand pushCommand = local.push().setRemote("test")
+					.setPushOptions(pushOptions);
+			pushCommand.call();
+
+			assertEquals(commit.getId(),
+					remote.getRepository().resolve("refs/heads/branchtopush"));
+			assertNull(remote.getRepository().resolve("refs/heads/not-pushed"));
+			assertNull(remote.getRepository().resolve("refs/heads/master"));
+		}
+	}
+
+	@Test
+	public void testAdvertisedButUnusedPushOptions() throws Exception {
+		try (Git local = new Git(db);
+				Git remote = new Git(createBareRepository())) {
+			connectLocalToRemote(local, remote);
+
+			final StoredConfig config2 = remote.getRepository().getConfig();
+			config2.setBoolean("receive", null, "pushoptions", true);
+			config2.save();
+
+			RevCommit commit = addCommit(local);
+
+			local.checkout().setName("not-pushed").setCreateBranch(true).call();
+			local.checkout().setName("branchtopush").setCreateBranch(true).call();
+
+			assertNull(remote.getRepository().resolve("refs/heads/branchtopush"));
+			assertNull(remote.getRepository().resolve("refs/heads/not-pushed"));
+			assertNull(remote.getRepository().resolve("refs/heads/master"));
+
+			PushCommand pushCommand = local.push().setRemote("test")
+					.setPushOptions(null);
+			pushCommand.call();
+
+			assertEquals(commit.getId(),
+					remote.getRepository().resolve("refs/heads/branchtopush"));
+			assertNull(remote.getRepository().resolve("refs/heads/not-pushed"));
+			assertNull(remote.getRepository().resolve("refs/heads/master"));
+		}
+	}
+
+	@Test(expected = TransportException.class)
+	public void testPushOptionsNotSupported() throws Exception {
+		try (Git local = new Git(db);
+				Git remote = new Git(createBareRepository())) {
+			connectLocalToRemote(local, remote);
+
+			final StoredConfig config2 = remote.getRepository().getConfig();
+			config2.setBoolean("receive", null, "pushoptions", false);
+			config2.save();
+
+			addCommit(local);
+
+			local.checkout().setName("not-pushed").setCreateBranch(true).call();
+			local.checkout().setName("branchtopush").setCreateBranch(true).call();
+
+			assertNull(remote.getRepository().resolve("refs/heads/branchtopush"));
+			assertNull(remote.getRepository().resolve("refs/heads/not-pushed"));
+			assertNull(remote.getRepository().resolve("refs/heads/master"));
+
+			List<String> pushOptions = new ArrayList<>();
+			PushCommand pushCommand = local.push().setRemote("test")
+					.setPushOptions(pushOptions);
+			pushCommand.call();
+
+			fail("should already have thrown TransportException");
+		}
+	}
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/RefAdvertiserTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/RefAdvertiserTest.java
new file mode 100644
index 0000000..ce69adf
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/RefAdvertiserTest.java
@@ -0,0 +1,116 @@
+/*
+ * 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.transport;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertSame;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectInserter;
+import org.eclipse.jgit.transport.RefAdvertiser.PacketLineOutRefAdvertiser;
+import org.eclipse.jgit.util.NB;
+import org.junit.Test;
+
+public class RefAdvertiserTest {
+	@Test
+	public void advertiser() throws IOException {
+		ByteArrayOutputStream buf = new ByteArrayOutputStream();
+		PacketLineOut pckOut = new PacketLineOut(buf);
+		PacketLineOutRefAdvertiser adv = new PacketLineOutRefAdvertiser(pckOut);
+
+		// Advertisement of capability and id both happen in order of call,
+		// which may not match Git standards. Callers are responsible for
+		// making calls in the correct ordering. Here in this test we do them
+		// in a "wrong" order to assert the method just writes to the network.
+
+		adv.advertiseCapability("test-1");
+		adv.advertiseCapability("sideband");
+		adv.advertiseId(id(1), "refs/heads/master");
+		adv.advertiseId(id(4), "refs/" + padStart("F", 987));
+		adv.advertiseId(id(2), "refs/heads/next");
+		adv.advertiseId(id(3), "refs/Iñtërnâtiônàlizætiøn☃💩");
+		adv.end();
+		assertFalse(adv.isEmpty());
+
+		PacketLineIn pckIn = new PacketLineIn(
+				new ByteArrayInputStream(buf.toByteArray()));
+		String s = pckIn.readStringRaw();
+		assertEquals(id(1).name() + " refs/heads/master\0 test-1 sideband\n",
+				s);
+
+		s = pckIn.readStringRaw();
+		assertEquals(id(4).name() + " refs/" + padStart("F", 987) + '\n', s);
+
+		s = pckIn.readStringRaw();
+		assertEquals(id(2).name() + " refs/heads/next\n", s);
+
+		s = pckIn.readStringRaw();
+		assertEquals(id(3).name() + " refs/Iñtërnâtiônàlizætiøn☃💩\n", s);
+
+		s = pckIn.readStringRaw();
+		assertSame(PacketLineIn.END, s);
+	}
+
+	private static ObjectId id(int i) {
+		try (ObjectInserter.Formatter f = new ObjectInserter.Formatter()) {
+			byte[] tmp = new byte[4];
+			NB.encodeInt32(tmp, 0, i);
+			return f.idFor(Constants.OBJ_BLOB, tmp);
+		}
+	}
+
+	private static String padStart(String s, int len) {
+		StringBuilder b = new StringBuilder(len);
+		for (int i = s.length(); i < len; i++) {
+			b.append((char) ('a' + (i % 26)));
+		}
+		return b.append(s).toString();
+	}
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/RefSpecTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/RefSpecTest.java
index 4f83350..c9e44e7 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/RefSpecTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/RefSpecTest.java
@@ -55,6 +55,7 @@
 
 import org.eclipse.jgit.lib.ObjectIdRef;
 import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.transport.RefSpec.WildcardMode;
 import org.junit.Test;
 
 public class RefSpecTest {
@@ -409,6 +410,16 @@
 	}
 
 	@Test(expected = IllegalArgumentException.class)
+	public void invalidWhenSourceEndsWithSlash() {
+		assertNotNull(new RefSpec("refs/heads/"));
+	}
+
+	@Test(expected = IllegalArgumentException.class)
+	public void invalidWhenDestinationEndsWithSlash() {
+		assertNotNull(new RefSpec("refs/heads/master:refs/heads/"));
+	}
+
+	@Test(expected = IllegalArgumentException.class)
 	public void invalidWhenSourceOnlyAndWildcard() {
 		assertNotNull(new RefSpec("refs/heads/*"));
 	}
@@ -464,4 +475,28 @@
 		RefSpec a = new RefSpec("refs/heads/*:refs/remotes/origin/*");
 		a.setDestination("refs/remotes/origin/*/*");
 	}
+
+	@Test
+	public void sourceOnlywithWildcard() {
+		RefSpec a = new RefSpec("refs/heads/*",
+				WildcardMode.ALLOW_MISMATCH);
+		assertTrue(a.matchSource("refs/heads/master"));
+		assertNull(a.getDestination());
+	}
+
+	@Test
+	public void destinationWithWildcard() {
+		RefSpec a = new RefSpec("refs/heads/master:refs/heads/*",
+				WildcardMode.ALLOW_MISMATCH);
+		assertTrue(a.matchSource("refs/heads/master"));
+		assertTrue(a.matchDestination("refs/heads/master"));
+		assertTrue(a.matchDestination("refs/heads/foo"));
+	}
+
+	@Test
+	public void onlyWildCard() {
+		RefSpec a = new RefSpec("*", WildcardMode.ALLOW_MISMATCH);
+		assertTrue(a.matchSource("refs/heads/master"));
+		assertNull(a.getDestination());
+	}
 }
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/FileTreeIteratorTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/FileTreeIteratorTest.java
index bf1d0e6..ef0f2d9 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/FileTreeIteratorTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/FileTreeIteratorTest.java
@@ -99,7 +99,7 @@
 		for (int i = paths.length - 1; i >= 0; i--) {
 			final String s = paths[i];
 			writeTrashFile(s, s);
-			mtime[i] = new File(trash, s).lastModified();
+			mtime[i] = FS.DETECTED.lastModified(new File(trash, s));
 		}
 	}
 
@@ -286,7 +286,7 @@
 
 	@Test
 	public void testTreewalkEnterSubtree() throws Exception {
-		try (Git git = new Git(db)) {
+		try (Git git = new Git(db); TreeWalk tw = new TreeWalk(db)) {
 			writeTrashFile("b/c", "b/c");
 			writeTrashFile("z/.git", "gitdir: /tmp/somewhere");
 			git.add().addFilepattern(".").call();
@@ -297,7 +297,6 @@
 			FileUtils.delete(new File(db.getWorkTree(), "b"),
 					FileUtils.RECURSIVE);
 
-			TreeWalk tw = new TreeWalk(db);
 			tw.addTree(new DirCacheIterator(db.readDirCache()));
 			tw.addTree(new FileTreeIterator(db));
 			assertTrue(tw.next());
@@ -617,39 +616,41 @@
 	public void testCustomFileModeStrategy() throws Exception {
 		Repository nestedRepo = createNestedRepo();
 
-		Git git = new Git(nestedRepo);
-		// validate that our custom strategy is honored
-		WorkingTreeIterator customIterator =
-				new FileTreeIterator(nestedRepo, NO_GITLINKS_STRATEGY);
-		git.add().setWorkingTreeIterator(customIterator)
-				.addFilepattern(".").call();
-		assertEquals(
-				"[sub/a.txt, mode:100644, content:content]" +
-				"[sub/nested/b.txt, mode:100644, content:content b]",
-				indexState(nestedRepo, CONTENT));
-
+		try (Git git = new Git(nestedRepo)) {
+			// validate that our custom strategy is honored
+			WorkingTreeIterator customIterator = new FileTreeIterator(
+					nestedRepo, NO_GITLINKS_STRATEGY);
+			git.add().setWorkingTreeIterator(customIterator).addFilepattern(".")
+					.call();
+			assertEquals(
+					"[sub/a.txt, mode:100644, content:content]"
+							+ "[sub/nested/b.txt, mode:100644, content:content b]",
+					indexState(nestedRepo, CONTENT));
+		}
 	}
 
 	@Test
 	public void testCustomFileModeStrategyFromParentIterator() throws Exception {
 		Repository nestedRepo = createNestedRepo();
 
-		Git git = new Git(nestedRepo);
+		try (Git git = new Git(nestedRepo)) {
+			FileTreeIterator customIterator = new FileTreeIterator(nestedRepo,
+					NO_GITLINKS_STRATEGY);
+			File r = new File(nestedRepo.getWorkTree(), "sub");
 
-		FileTreeIterator customIterator =
-				new FileTreeIterator(nestedRepo, NO_GITLINKS_STRATEGY);
-		File r = new File(nestedRepo.getWorkTree(), "sub");
-
-		// here we want to validate that if we create a new iterator using the
-		// constructor that accepts a parent iterator, that the child iterator
-		// correctly inherits the FileModeStrategy from the parent iterator.
-		FileTreeIterator childIterator =
-				new FileTreeIterator(customIterator, r, nestedRepo.getFS());
-		git.add().setWorkingTreeIterator(childIterator).addFilepattern(".").call();
-		assertEquals(
-				"[sub/a.txt, mode:100644, content:content]" +
-				"[sub/nested/b.txt, mode:100644, content:content b]",
-				indexState(nestedRepo, CONTENT));
+			// here we want to validate that if we create a new iterator using
+			// the constructor that accepts a parent iterator, that the child
+			// iterator correctly inherits the FileModeStrategy from the parent
+			// iterator.
+			FileTreeIterator childIterator = new FileTreeIterator(
+					customIterator, r, nestedRepo.getFS());
+			git.add().setWorkingTreeIterator(childIterator).addFilepattern(".")
+					.call();
+			assertEquals(
+					"[sub/a.txt, mode:100644, content:content]"
+							+ "[sub/nested/b.txt, mode:100644, content:content b]",
+					indexState(nestedRepo, CONTENT));
+		}
 	}
 
 
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FSJava7Test.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FSTest.java
similarity index 92%
rename from org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FSJava7Test.java
rename to org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FSTest.java
index 53b6fec..4061b56 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FSJava7Test.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FSTest.java
@@ -50,18 +50,20 @@
 
 import java.io.File;
 import java.io.IOException;
+import java.nio.charset.Charset;
 import java.nio.file.Files;
 import java.nio.file.attribute.PosixFileAttributeView;
 import java.nio.file.attribute.PosixFilePermission;
 import java.util.Set;
 
+import org.eclipse.jgit.errors.CommandFailedException;
 import org.eclipse.jgit.junit.RepositoryTestCase;
 import org.junit.After;
 import org.junit.Assume;
 import org.junit.Before;
 import org.junit.Test;
 
-public class FSJava7Test {
+public class FSTest {
 	private File trash;
 
 	@Before
@@ -164,4 +166,15 @@
 				.readAttributes().permissions();
 	}
 
+	@Test(expected = CommandFailedException.class)
+	public void testReadPipePosixCommandFailure()
+			throws CommandFailedException {
+		FS fs = FS.DETECTED.newInstance();
+		assumeTrue(fs instanceof FS_POSIX);
+
+		String r = FS.readPipe(fs.userHome(),
+				new String[] { "bash", "--login", "-c", "foobar" },
+				Charset.defaultCharset().name());
+		System.out.println(r);
+	}
 }
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/HookTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/HookTest.java
index e07076e..a680ef9 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/HookTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/HookTest.java
@@ -54,6 +54,7 @@
 import org.eclipse.jgit.api.Git;
 import org.eclipse.jgit.api.errors.AbortedByHookException;
 import org.eclipse.jgit.hooks.CommitMsgHook;
+import org.eclipse.jgit.hooks.PostCommitHook;
 import org.eclipse.jgit.hooks.PreCommitHook;
 import org.eclipse.jgit.junit.JGitTestUtil;
 import org.eclipse.jgit.junit.RepositoryTestCase;
@@ -76,6 +77,18 @@
 	}
 
 	@Test
+	public void testFindPostCommitHook() throws Exception {
+		assumeSupportedPlatform();
+
+		assertNull("no hook should be installed",
+				FS.DETECTED.findHook(db, PostCommitHook.NAME));
+		File hookFile = writeHookFile(PostCommitHook.NAME,
+				"#!/bin/bash\necho \"test $1 $2\"");
+		assertEquals("expected to find post-commit hook", hookFile,
+				FS.DETECTED.findHook(db, PostCommitHook.NAME));
+	}
+
+	@Test
 	public void testFailedCommitMsgHookBlocksCommit() throws Exception {
 		assumeSupportedPlatform();
 
@@ -133,6 +146,55 @@
 	}
 
 	@Test
+	public void testPostCommitRunHook() throws Exception {
+		assumeSupportedPlatform();
+
+		writeHookFile(PostCommitHook.NAME,
+				"#!/bin/sh\necho \"test $1 $2\"\nread INPUT\necho $INPUT\necho 1>&2 \"stderr\"");
+		ByteArrayOutputStream out = new ByteArrayOutputStream();
+		ByteArrayOutputStream err = new ByteArrayOutputStream();
+		ProcessResult res = FS.DETECTED.runHookIfPresent(db,
+				PostCommitHook.NAME,
+				new String[] {
+				"arg1", "arg2" },
+				new PrintStream(out), new PrintStream(err), "stdin");
+
+		assertEquals("unexpected hook output", "test arg1 arg2\nstdin\n",
+				out.toString("UTF-8"));
+		assertEquals("unexpected output on stderr stream", "stderr\n",
+				err.toString("UTF-8"));
+		assertEquals("unexpected exit code", 0, res.getExitCode());
+		assertEquals("unexpected process status", ProcessResult.Status.OK,
+				res.getStatus());
+	}
+
+	@Test
+	public void testAllCommitHooks() throws Exception {
+		assumeSupportedPlatform();
+
+		writeHookFile(PreCommitHook.NAME,
+				"#!/bin/sh\necho \"test pre-commit\"\n\necho 1>&2 \"stderr pre-commit\"\nexit 0");
+		writeHookFile(CommitMsgHook.NAME,
+				"#!/bin/sh\necho \"test commit-msg $1\"\n\necho 1>&2 \"stderr commit-msg\"\nexit 0");
+		writeHookFile(PostCommitHook.NAME,
+				"#!/bin/sh\necho \"test post-commit\"\necho 1>&2 \"stderr post-commit\"\nexit 0");
+		Git git = Git.wrap(db);
+		String path = "a.txt";
+		writeTrashFile(path, "content");
+		git.add().addFilepattern(path).call();
+		ByteArrayOutputStream out = new ByteArrayOutputStream();
+		try {
+			git.commit().setMessage("commit")
+					.setHookOutputStream(new PrintStream(out)).call();
+		} catch (AbortedByHookException e) {
+			fail("unexpected hook failure");
+		}
+		assertEquals("unexpected hook output",
+				"test pre-commit\ntest commit-msg .git/COMMIT_EDITMSG\ntest post-commit\n",
+				out.toString("UTF-8"));
+	}
+
+	@Test
 	public void testRunHook() throws Exception {
 		assumeSupportedPlatform();
 
diff --git a/org.eclipse.jgit.ui/.settings/org.eclipse.jdt.core.prefs b/org.eclipse.jgit.ui/.settings/org.eclipse.jdt.core.prefs
index ff39d16..1ce7cd0 100644
--- a/org.eclipse.jgit.ui/.settings/org.eclipse.jdt.core.prefs
+++ b/org.eclipse.jgit.ui/.settings/org.eclipse.jdt.core.prefs
@@ -99,6 +99,7 @@
 org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled
 org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled
 org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled
+org.eclipse.jdt.core.compiler.problem.unusedExceptionParameter=ignore
 org.eclipse.jdt.core.compiler.problem.unusedImport=error
 org.eclipse.jdt.core.compiler.problem.unusedLabel=error
 org.eclipse.jdt.core.compiler.problem.unusedLocal=error
diff --git a/org.eclipse.jgit.ui/META-INF/MANIFEST.MF b/org.eclipse.jgit.ui/META-INF/MANIFEST.MF
index 4d864d6..bac4ac9 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.4.2.qualifier
+Bundle-Version: 4.5.5.qualifier
 Bundle-Vendor: %provider_name
 Bundle-RequiredExecutionEnvironment: JavaSE-1.7
-Export-Package: org.eclipse.jgit.awtui;version="4.4.2"
-Import-Package: org.eclipse.jgit.errors;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.lib;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.nls;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.revplot;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.revwalk;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.transport;version="[4.4.2,4.5.0)",
- org.eclipse.jgit.util;version="[4.4.2,4.5.0)"
+Export-Package: org.eclipse.jgit.awtui;version="4.5.5"
+Import-Package: org.eclipse.jgit.errors;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.lib;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.nls;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.revplot;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.revwalk;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.transport;version="[4.5.5,4.6.0)",
+ org.eclipse.jgit.util;version="[4.5.5,4.6.0)"
diff --git a/org.eclipse.jgit.ui/pom.xml b/org.eclipse.jgit.ui/pom.xml
index a8d9b5d..4208d95 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.4.2-SNAPSHOT</version>
+    <version>4.5.5-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 bacfb33..3830563 100644
--- a/org.eclipse.jgit/.settings/.api_filters
+++ b/org.eclipse.jgit/.settings/.api_filters
@@ -3,45 +3,8 @@
     <resource path="META-INF/MANIFEST.MF">
         <filter id="924844039">
             <message_arguments>
-                <message_argument value="4.4.1"/>
-                <message_argument value="4.4.0"/>
-            </message_arguments>
-        </filter>
-    </resource>
-    <resource path="src/org/eclipse/jgit/attributes/AttributesNode.java" type="org.eclipse.jgit.attributes.AttributesNode">
-        <filter comment="moved to new AttributesManager" id="338792546">
-            <message_arguments>
-                <message_argument value="org.eclipse.jgit.attributes.AttributesNode"/>
-                <message_argument value="getAttributes(String, boolean, Attributes)"/>
-            </message_arguments>
-        </filter>
-    </resource>
-    <resource path="src/org/eclipse/jgit/attributes/AttributesRule.java" type="org.eclipse.jgit.attributes.AttributesRule">
-        <filter comment="used only in tests: bean naming" id="338792546">
-            <message_arguments>
-                <message_argument value="org.eclipse.jgit.attributes.AttributesRule"/>
-                <message_argument value="dirOnly()"/>
-            </message_arguments>
-        </filter>
-    </resource>
-    <resource path="src/org/eclipse/jgit/dircache/DirCacheCheckout.java" type="org.eclipse.jgit.dircache.DirCacheCheckout">
-        <filter comment="add eol stream type conversion" id="338792546">
-            <message_arguments>
-                <message_argument value="org.eclipse.jgit.dircache.DirCacheCheckout"/>
-                <message_argument value="checkoutEntry(Repository, DirCacheEntry, ObjectReader, boolean)"/>
-            </message_arguments>
-        </filter>
-        <filter comment="add eol stream type conversion" id="338792546">
-            <message_arguments>
-                <message_argument value="org.eclipse.jgit.dircache.DirCacheCheckout"/>
-                <message_argument value="checkoutEntry(Repository, DirCacheEntry, ObjectReader, boolean, String)"/>
-            </message_arguments>
-        </filter>
-        <filter comment="add eol stream type conversion" id="1141899266">
-            <message_arguments>
-                <message_argument value="4.2"/>
-                <message_argument value="4.3"/>
-                <message_argument value="checkoutEntry(Repository, DirCacheEntry, ObjectReader, boolean, DirCacheCheckout.CheckoutMetadata)"/>
+                <message_argument value="4.5.4"/>
+                <message_argument value="4.5.0"/>
             </message_arguments>
         </filter>
     </resource>
diff --git a/org.eclipse.jgit/.settings/org.eclipse.jdt.core.prefs b/org.eclipse.jgit/.settings/org.eclipse.jdt.core.prefs
index 45d6d2c..bfaf736 100644
--- a/org.eclipse.jgit/.settings/org.eclipse.jdt.core.prefs
+++ b/org.eclipse.jgit/.settings/org.eclipse.jdt.core.prefs
@@ -99,6 +99,7 @@
 org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled
 org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled
 org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled
+org.eclipse.jdt.core.compiler.problem.unusedExceptionParameter=ignore
 org.eclipse.jdt.core.compiler.problem.unusedImport=error
 org.eclipse.jdt.core.compiler.problem.unusedLabel=error
 org.eclipse.jdt.core.compiler.problem.unusedLocal=error
diff --git a/org.eclipse.jgit/BUCK b/org.eclipse.jgit/BUCK
index 73e2080..2bae6dc 100644
--- a/org.eclipse.jgit/BUCK
+++ b/org.eclipse.jgit/BUCK
@@ -9,6 +9,7 @@
     '//lib:javaewah',
     '//lib:jsch',
     '//lib:httpcomponents',
+    '//lib:servlet-api',
     '//lib:slf4j-api',
   ],
   visibility = ['PUBLIC'],
diff --git a/org.eclipse.jgit/META-INF/MANIFEST.MF b/org.eclipse.jgit/META-INF/MANIFEST.MF
index 82b27bf..aedf26a 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.4.2.qualifier
+Bundle-Version: 4.5.5.qualifier
 Bundle-Localization: plugin
 Bundle-Vendor: %provider_name
 Bundle-ActivationPolicy: lazy
-Export-Package: org.eclipse.jgit.annotations;version="4.4.2",
- org.eclipse.jgit.api;version="4.4.2";
+Export-Package: org.eclipse.jgit.annotations;version="4.5.5",
+ org.eclipse.jgit.api;version="4.5.5";
   uses:="org.eclipse.jgit.revwalk,
    org.eclipse.jgit.treewalk.filter,
    org.eclipse.jgit.diff,
@@ -21,50 +21,50 @@
    org.eclipse.jgit.submodule,
    org.eclipse.jgit.transport,
    org.eclipse.jgit.merge",
- org.eclipse.jgit.api.errors;version="4.4.2";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.errors",
- org.eclipse.jgit.attributes;version="4.4.2",
- org.eclipse.jgit.blame;version="4.4.2";
+ org.eclipse.jgit.api.errors;version="4.5.5";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.errors",
+ org.eclipse.jgit.attributes;version="4.5.5",
+ org.eclipse.jgit.blame;version="4.5.5";
   uses:="org.eclipse.jgit.lib,
    org.eclipse.jgit.revwalk,
    org.eclipse.jgit.treewalk.filter,
    org.eclipse.jgit.diff",
- org.eclipse.jgit.diff;version="4.4.2";
+ org.eclipse.jgit.diff;version="4.5.5";
   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.4.2";
+ org.eclipse.jgit.dircache;version="4.5.5";
   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.4.2";
+ org.eclipse.jgit.errors;version="4.5.5";
   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.4.2";uses:="org.eclipse.jgit.lib",
- org.eclipse.jgit.fnmatch;version="4.4.2",
- org.eclipse.jgit.gitrepo;version="4.4.2";
+ org.eclipse.jgit.events;version="4.5.5";uses:="org.eclipse.jgit.lib",
+ org.eclipse.jgit.fnmatch;version="4.5.5",
+ org.eclipse.jgit.gitrepo;version="4.5.5";
   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.4.2";x-internal:=true,
- org.eclipse.jgit.hooks;version="4.4.2";uses:="org.eclipse.jgit.lib",
- org.eclipse.jgit.ignore;version="4.4.2",
- org.eclipse.jgit.ignore.internal;version="4.4.2";x-friends:="org.eclipse.jgit.test",
- org.eclipse.jgit.internal;version="4.4.2";x-friends:="org.eclipse.jgit.test,org.eclipse.jgit.http.test",
- org.eclipse.jgit.internal.ketch;version="4.4.2";x-friends:="org.eclipse.jgit.junit,org.eclipse.jgit.test,org.eclipse.jgit.pgm",
- org.eclipse.jgit.internal.storage.dfs;version="4.4.2";
+ org.eclipse.jgit.gitrepo.internal;version="4.5.5";x-internal:=true,
+ org.eclipse.jgit.hooks;version="4.5.5";uses:="org.eclipse.jgit.lib",
+ org.eclipse.jgit.ignore;version="4.5.5",
+ org.eclipse.jgit.ignore.internal;version="4.5.5";x-friends:="org.eclipse.jgit.test",
+ org.eclipse.jgit.internal;version="4.5.5";x-friends:="org.eclipse.jgit.test,org.eclipse.jgit.http.test",
+ org.eclipse.jgit.internal.ketch;version="4.5.5";x-friends:="org.eclipse.jgit.junit,org.eclipse.jgit.test,org.eclipse.jgit.pgm",
+ org.eclipse.jgit.internal.storage.dfs;version="4.5.5";
   x-friends:="org.eclipse.jgit.test,
    org.eclipse.jgit.http.server,
    org.eclipse.jgit.http.test",
- org.eclipse.jgit.internal.storage.file;version="4.4.2";
+ org.eclipse.jgit.internal.storage.file;version="4.5.5";
   x-friends:="org.eclipse.jgit.test,
    org.eclipse.jgit.junit,
    org.eclipse.jgit.junit.http,
@@ -72,9 +72,9 @@
    org.eclipse.jgit.lfs.server,
    org.eclipse.jgit.pgm,
    org.eclipse.jgit.pgm.test",
- org.eclipse.jgit.internal.storage.pack;version="4.4.2";x-friends:="org.eclipse.jgit.junit,org.eclipse.jgit.test,org.eclipse.jgit.pgm",
- org.eclipse.jgit.internal.storage.reftree;version="4.4.2";x-friends:="org.eclipse.jgit.junit,org.eclipse.jgit.test,org.eclipse.jgit.pgm",
- org.eclipse.jgit.lib;version="4.4.2";
+ org.eclipse.jgit.internal.storage.pack;version="4.5.5";x-friends:="org.eclipse.jgit.junit,org.eclipse.jgit.test,org.eclipse.jgit.pgm",
+ org.eclipse.jgit.internal.storage.reftree;version="4.5.5";x-friends:="org.eclipse.jgit.junit,org.eclipse.jgit.test,org.eclipse.jgit.pgm",
+ org.eclipse.jgit.lib;version="4.5.5";
   uses:="org.eclipse.jgit.revwalk,
    org.eclipse.jgit.treewalk.filter,
    org.eclipse.jgit.util,
@@ -84,32 +84,32 @@
    org.eclipse.jgit.treewalk,
    org.eclipse.jgit.transport,
    org.eclipse.jgit.submodule",
- org.eclipse.jgit.merge;version="4.4.2";
+ org.eclipse.jgit.merge;version="4.5.5";
   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.4.2",
- org.eclipse.jgit.notes;version="4.4.2";
+ org.eclipse.jgit.nls;version="4.5.5",
+ org.eclipse.jgit.notes;version="4.5.5";
   uses:="org.eclipse.jgit.lib,
    org.eclipse.jgit.treewalk,
    org.eclipse.jgit.revwalk,
    org.eclipse.jgit.merge",
- org.eclipse.jgit.patch;version="4.4.2";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.diff",
- org.eclipse.jgit.revplot;version="4.4.2";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.revwalk",
- org.eclipse.jgit.revwalk;version="4.4.2";
+ org.eclipse.jgit.patch;version="4.5.5";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.diff",
+ org.eclipse.jgit.revplot;version="4.5.5";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.revwalk",
+ org.eclipse.jgit.revwalk;version="4.5.5";
   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.4.2";uses:="org.eclipse.jgit.revwalk,org.eclipse.jgit.lib,org.eclipse.jgit.util",
- org.eclipse.jgit.storage.file;version="4.4.2";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.util",
- org.eclipse.jgit.storage.pack;version="4.4.2";uses:="org.eclipse.jgit.lib",
- org.eclipse.jgit.submodule;version="4.4.2";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.treewalk.filter,org.eclipse.jgit.treewalk",
- org.eclipse.jgit.transport;version="4.4.2";
+ org.eclipse.jgit.revwalk.filter;version="4.5.5";uses:="org.eclipse.jgit.revwalk,org.eclipse.jgit.lib,org.eclipse.jgit.util",
+ org.eclipse.jgit.storage.file;version="4.5.5";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.util",
+ org.eclipse.jgit.storage.pack;version="4.5.5";uses:="org.eclipse.jgit.lib",
+ org.eclipse.jgit.submodule;version="4.5.5";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.treewalk.filter,org.eclipse.jgit.treewalk",
+ org.eclipse.jgit.transport;version="4.5.5";
   uses:="org.eclipse.jgit.transport.resolver,
    org.eclipse.jgit.revwalk,
    org.eclipse.jgit.internal.storage.pack,
@@ -121,27 +121,28 @@
    org.eclipse.jgit.transport.http,
    org.eclipse.jgit.errors,
    org.eclipse.jgit.storage.pack",
- org.eclipse.jgit.transport.http;version="4.4.2";uses:="javax.net.ssl",
- org.eclipse.jgit.transport.resolver;version="4.4.2";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.transport",
- org.eclipse.jgit.treewalk;version="4.4.2";
+ org.eclipse.jgit.transport.http;version="4.5.5";uses:="javax.net.ssl",
+ org.eclipse.jgit.transport.resolver;version="4.5.5";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.transport",
+ org.eclipse.jgit.treewalk;version="4.5.5";
   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.4.2";uses:="org.eclipse.jgit.treewalk",
- org.eclipse.jgit.util;version="4.4.2";
+ org.eclipse.jgit.treewalk.filter;version="4.5.5";uses:="org.eclipse.jgit.treewalk",
+ org.eclipse.jgit.util;version="4.5.5";
   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.4.2"
+ org.eclipse.jgit.util.io;version="4.5.5"
 Bundle-RequiredExecutionEnvironment: JavaSE-1.7
-Require-Bundle: com.jcraft.jsch;bundle-version="[0.1.37,0.2.0)"
 Import-Package: com.googlecode.javaewah;version="[0.7.9,0.8.0)",
+ com.jcraft.jsch;version="[0.1.37,0.2.0)",
  javax.crypto,
  javax.net.ssl,
+ javax.servlet.http;version="[2.5.0,3.2.0)",
  org.slf4j;version="[1.7.0,2.0.0)",
  org.xml.sax,
  org.xml.sax.helpers
diff --git a/org.eclipse.jgit/META-INF/SOURCE-MANIFEST.MF b/org.eclipse.jgit/META-INF/SOURCE-MANIFEST.MF
index b0365c7..ce5f778 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.4.2.qualifier
-Eclipse-SourceBundle: org.eclipse.jgit;version="4.4.2.qualifier";roots="."
+Bundle-Version: 4.5.5.qualifier
+Eclipse-SourceBundle: org.eclipse.jgit;version="4.5.5.qualifier";roots="."
diff --git a/org.eclipse.jgit/pom.xml b/org.eclipse.jgit/pom.xml
index 77bfba1..a55575f 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.4.2-SNAPSHOT</version>
+    <version>4.5.5-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit</artifactId>
@@ -88,6 +88,12 @@
       <groupId>org.slf4j</groupId>
       <artifactId>slf4j-api</artifactId>
     </dependency>
+
+    <dependency>
+      <groupId>javax.servlet</groupId>
+      <artifactId>javax.servlet-api</artifactId>
+      <scope>provided</scope>
+    </dependency>
   </dependencies>
 
   <build>
@@ -205,8 +211,8 @@
     <pluginManagement>
       <plugins>
         <plugin>
-          <groupId>org.codehaus.mojo</groupId>
-          <artifactId>findbugs-maven-plugin</artifactId>
+          <groupId>com.github.spotbugs</groupId>
+          <artifactId>spotbugs-maven-plugin</artifactId>
           <configuration>
             <excludeFilterFile>findBugs/FindBugsExcludeFilter.xml</excludeFilterFile>
           </configuration>
diff --git a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties
index 21fbaa4..c949c0a 100644
--- a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties
+++ b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties
@@ -123,6 +123,7 @@
 commitOnRepoWithoutHEADCurrentlyNotSupported=Commit on repo without HEAD currently not supported
 commitAmendOnInitialNotPossible=Amending is not possible on initial commit.
 compressingObjects=Compressing objects
+configHandleIsStale=config file handle is stale, {0}. retry
 connectionFailed=connection failed
 connectionTimeOut=Connection time out: {0}
 contextMustBeNonNegative=context must be >= 0
@@ -264,11 +265,11 @@
 exceptionCaughtDuringExecutionOfRevertCommand=Exception caught during execution of revert command. {0}
 exceptionCaughtDuringExecutionOfRmCommand=Exception caught during execution of rm command
 exceptionCaughtDuringExecutionOfTagCommand=Exception caught during execution of tag command
-exceptionCaughtDuringExcecutionOfCommand=Exception caught during execution of command {0} in {1}
+exceptionCaughtDuringExcecutionOfCommand=Exception caught during execution of command ''{0}'' in ''{1}'', return code ''{2}'', error message ''{3}''
 exceptionHookExecutionInterrupted=Execution of "{0}" hook interrupted.
 exceptionOccurredDuringAddingOfOptionToALogCommand=Exception occurred during adding of {0} as option to a Log command
 exceptionOccurredDuringReadingOfGIT_DIR=Exception occurred during reading of $GIT_DIR/{0}. {1}
-exceptionWhileReadingPack=ERROR: Exception caught while accessing pack file {0}, the pack file might be corrupt
+exceptionWhileReadingPack=ERROR: Exception caught while accessing pack file {0}, the pack file might be corrupt, {1}. Caught {2} consecutive errors while trying to read this pack.
 expectedACKNAKFoundEOF=Expected ACK/NAK, found EOF
 expectedACKNAKGot=Expected ACK/NAK, got: {0}
 expectedBooleanStringValue=Expected boolean string value
@@ -304,6 +305,7 @@
 hunkHeaderDoesNotMatchBodyLineCountOf=Hunk header {0} does not match body line count of {1}
 illegalArgumentNotA=Not {0}
 illegalCombinationOfArguments=The combination of arguments {0} and {1} is not allowed
+illegalHookName=Illegal hook name {0}
 illegalPackingPhase=Illegal packing phase {0}
 illegalStateExists=exists {0}
 improperlyPaddedBase64Input=Improperly padded Base64 input.
@@ -332,7 +334,9 @@
 invalidChannel=Invalid channel {0}
 invalidCharacterInBase64Data=Invalid character in Base64 data.
 invalidCommitParentNumber=Invalid commit parent number
+invalidDepth=Invalid depth: {0}
 invalidEncryption=Invalid encryption
+invalidExpandWildcard=ExpandFromSource on a refspec that can have mismatched wildcards does not make sense.
 invalidGitdirRef = Invalid .git reference in file ''{0}''
 invalidGitType=invalid git type: {0}
 invalidId=Invalid id: {0}
@@ -466,7 +470,7 @@
 packfileIsTruncatedNoParam=Packfile is truncated.
 packHandleIsStale=Pack file {0} handle is stale, removing it from pack list
 packHasUnresolvedDeltas=pack has unresolved deltas
-packInaccessible=Pack file {0} now inaccessible; removing it from pack list
+packInaccessible=Failed to access pack file {0},  caught {2} consecutive errors while trying to access this pack.
 packingCancelledDuringObjectsWriting=Packing cancelled during objects writing
 packObjectCountMismatch=Pack object count mismatch: pack {0} index {1}: {2}
 packRefs=Pack refs
@@ -498,7 +502,9 @@
 pushCertificateInvalidSignature=Push certificate has invalid signature format
 pushIsNotSupportedForBundleTransport=Push is not supported for bundle transport
 pushNotPermitted=push not permitted
+pushOptionsNotSupported=Push options not supported; received {0}
 rawLogMessageDoesNotParseAsLogEntry=Raw log message does not parse as log entry
+readerIsRequired=Reader is required
 readingObjectsFromLocalRepositoryFailed=reading objects from local repository failed: {0}
 readTimedOut=Read timed out after {0} ms
 receivePackObjectTooLarge1=Object too large, rejecting the pack. Max object size limit is {0} bytes.
@@ -527,7 +533,6 @@
 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
@@ -555,6 +560,7 @@
 serviceNotEnabledNoName=Service not enabled
 serviceNotPermitted={0} not permitted
 shallowCommitsAlreadyInitialized=Shallow commits have already been initialized
+shallowPacksRequireDepthWalk=Shallow packs require a DepthWalk
 shortCompressedStreamAt=Short compressed stream at {0}
 shortReadOfBlock=Short read of block.
 shortReadOfOptionalDIRCExtensionExpectedAnotherBytes=Short read of optional DIRC extension {0}; expected another {1} bytes within the section.
@@ -591,7 +597,7 @@
 submodulesNotSupported=Submodules are not supported
 supportOnlyPackIndexVersion2=Only support index version 2
 symlinkCannotBeWrittenAsTheLinkTarget=Symlink "{0}" cannot be written as the link target cannot be read from within Java.
-systemConfigFileInvalid=Systen wide config file {0} is invalid {1}
+systemConfigFileInvalid=System wide config file {0} is invalid {1}
 tagAlreadyExists=tag ''{0}'' already exists
 tagNameInvalid=tag name {0} is invalid
 tagOnRepoWithoutHEADCurrentlyNotSupported=Tag on repository without HEAD currently not supported
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/CleanCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/CleanCommand.java
index 5967128..7e331fd 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CleanCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CleanCommand.java
@@ -43,6 +43,8 @@
  */
 package org.eclipse.jgit.api;
 
+import static org.eclipse.jgit.lib.Constants.DOT_GIT;
+
 import java.io.File;
 import java.io.IOException;
 import java.util.Collections;
@@ -73,6 +75,8 @@
 
 	private boolean ignore = true;
 
+	private boolean force = false;
+
 	/**
 	 * @param repo
 	 */
@@ -121,25 +125,69 @@
 
 			for (String file : notIgnoredFiles)
 				if (paths.isEmpty() || paths.contains(file)) {
-					if (!dryRun)
-						FileUtils.delete(new File(repo.getWorkTree(), file));
-					files.add(file);
+					files = cleanPath(file, files);
 				}
 
-			if (directories)
-				for (String dir : notIgnoredDirs)
-					if (paths.isEmpty() || paths.contains(dir)) {
-						if (!dryRun)
-							FileUtils.delete(new File(repo.getWorkTree(), dir),
-									FileUtils.RECURSIVE);
-						files.add(dir + "/"); //$NON-NLS-1$
-					}
+			for (String dir : notIgnoredDirs)
+				if (paths.isEmpty() || paths.contains(dir)) {
+					files = cleanPath(dir, files);
+				}
 		} catch (IOException e) {
 			throw new JGitInternalException(e.getMessage(), e);
 		}
 		return files;
 	}
 
+	/**
+	 * When dryRun is false, deletes the specified path from disk. If dryRun
+	 * is true, no paths are actually deleted. In both cases, the paths that
+	 * would have been deleted are added to inFiles and returned.
+	 *
+	 * Paths that are directories are recursively deleted when
+	 * {@link #directories} is true.
+	 * Paths that are git repositories are recursively deleted when
+	 * {@link #directories} and {@link #force} are both true.
+	 *
+	 * @param path
+	 * 			The path to be cleaned
+	 * @param inFiles
+	 * 			A set of strings representing the files that have been cleaned
+	 * 			already, the path to be cleaned will be added to this set
+	 * 			before being returned.
+	 *
+	 * @return a set of strings with the cleaned path added to it
+	 * @throws IOException
+	 */
+	private Set<String> cleanPath(String path, Set<String> inFiles)
+			throws IOException {
+		File curFile = new File(repo.getWorkTree(), path);
+		if (curFile.isDirectory()) {
+			if (directories) {
+				// Is this directory a git repository?
+				if (new File(curFile, DOT_GIT).exists()) {
+					if (force) {
+						if (!dryRun) {
+							FileUtils.delete(curFile, FileUtils.RECURSIVE);
+						}
+						inFiles.add(path + "/"); //$NON-NLS-1$
+					}
+				} else {
+					if (!dryRun) {
+						FileUtils.delete(curFile, FileUtils.RECURSIVE);
+					}
+					inFiles.add(path + "/"); //$NON-NLS-1$
+				}
+			}
+		} else {
+			if (!dryRun) {
+				FileUtils.delete(curFile, FileUtils.NONE);
+			}
+			inFiles.add(path);
+		}
+
+		return inFiles;
+	}
+
 	private Set<String> filterIgnorePaths(Set<String> inputPaths,
 			Set<String> ignoredNotInIndex, boolean exact) {
 		if (ignore) {
@@ -196,6 +244,20 @@
 	}
 
 	/**
+	 * If force is set, directories that are git repositories will also be
+	 * deleted.
+	 *
+	 * @param force
+	 *            whether or not to delete git repositories
+	 * @return {@code this}
+	 * @since 4.5
+	 */
+	public CleanCommand setForce(boolean force) {
+		this.force = force;
+		return this;
+	}
+
+	/**
 	 * If dirs is set, in addition to files, also clean directories.
 	 *
 	 * @param dirs
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 ff15fd0..dd5da15 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CloneCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CloneCommand.java
@@ -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.BranchConfig.BranchRebaseMode;
 import org.eclipse.jgit.lib.ConfigConstants;
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.NullProgressMonitor;
@@ -326,9 +327,9 @@
 				ConfigConstants.CONFIG_KEY_AUTOSETUPREBASE);
 		if (ConfigConstants.CONFIG_KEY_ALWAYS.equals(autosetupRebase)
 				|| ConfigConstants.CONFIG_KEY_REMOTE.equals(autosetupRebase))
-			clonedRepo.getConfig().setBoolean(
+			clonedRepo.getConfig().setEnum(
 					ConfigConstants.CONFIG_BRANCH_SECTION, branchName,
-					ConfigConstants.CONFIG_KEY_REBASE, true);
+					ConfigConstants.CONFIG_KEY_REBASE, BranchRebaseMode.REBASE);
 		clonedRepo.getConfig().save();
 	}
 
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java
index 561319c..e1793f3 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java
@@ -48,6 +48,7 @@
 import java.text.MessageFormat;
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.LinkedList;
 import java.util.List;
 
@@ -67,7 +68,10 @@
 import org.eclipse.jgit.dircache.DirCacheEntry;
 import org.eclipse.jgit.dircache.DirCacheIterator;
 import org.eclipse.jgit.errors.UnmergedPathException;
+import org.eclipse.jgit.hooks.CommitMsgHook;
 import org.eclipse.jgit.hooks.Hooks;
+import org.eclipse.jgit.hooks.PostCommitHook;
+import org.eclipse.jgit.hooks.PreCommitHook;
 import org.eclipse.jgit.internal.JGitText;
 import org.eclipse.jgit.lib.CommitBuilder;
 import org.eclipse.jgit.lib.Constants;
@@ -131,7 +135,7 @@
 	 */
 	private boolean noVerify;
 
-	private PrintStream hookOutRedirect;
+	private HashMap<String, PrintStream> hookOutRedirect = new HashMap<>(3);
 
 	private Boolean allowEmpty;
 
@@ -179,7 +183,8 @@
 						state.name()));
 
 			if (!noVerify) {
-				Hooks.preCommit(repo, hookOutRedirect).call();
+				Hooks.preCommit(repo, hookOutRedirect.get(PreCommitHook.NAME))
+						.call();
 			}
 
 			processOptions(state, rw);
@@ -218,7 +223,9 @@
 				}
 
 			if (!noVerify) {
-				message = Hooks.commitMsg(repo, hookOutRedirect)
+				message = Hooks
+						.commitMsg(repo,
+								hookOutRedirect.get(CommitMsgHook.NAME))
 						.setCommitMessage(message).call();
 			}
 
@@ -292,6 +299,9 @@
 						repo.writeMergeCommitMsg(null);
 						repo.writeRevertHead(null);
 					}
+					Hooks.postCommit(repo,
+							hookOutRedirect.get(PostCommitHook.NAME)).call();
+
 					return revCommit;
 				}
 				case REJECTED:
@@ -822,8 +832,9 @@
 	}
 
 	/**
-	 * Set the output stream for hook scripts executed by this command. If not
-	 * set it defaults to {@code System.out}.
+	 * Set the output stream for all hook scripts executed by this command
+	 * (pre-commit, commit-msg, post-commit). If not set it defaults to
+	 * {@code System.out}.
 	 *
 	 * @param hookStdOut
 	 *            the output stream for hook scripts executed by this command
@@ -831,7 +842,34 @@
 	 * @since 3.7
 	 */
 	public CommitCommand setHookOutputStream(PrintStream hookStdOut) {
-		this.hookOutRedirect = hookStdOut;
+		setHookOutputStream(PreCommitHook.NAME, hookStdOut);
+		setHookOutputStream(CommitMsgHook.NAME, hookStdOut);
+		setHookOutputStream(PostCommitHook.NAME, hookStdOut);
+		return this;
+	}
+
+	/**
+	 * Set the output stream for a selected hook script executed by this command
+	 * (pre-commit, commit-msg, post-commit). If not set it defaults to
+	 * {@code System.out}.
+	 *
+	 * @param hookName
+	 *            name of the hook to set the output stream for
+	 * @param hookStdOut
+	 *            the output stream to use for the selected hook
+	 * @return {@code this}
+	 * @since 4.5
+	 */
+	public CommitCommand setHookOutputStream(String hookName,
+			PrintStream hookStdOut) {
+		if (!(PreCommitHook.NAME.equals(hookName)
+				|| CommitMsgHook.NAME.equals(hookName)
+				|| PostCommitHook.NAME.equals(hookName))) {
+			throw new IllegalArgumentException(
+					MessageFormat.format(JGitText.get().illegalHookName,
+							hookName));
+		}
+		hookOutRedirect.put(hookName, hookStdOut);
 		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 549ef6c..a4d9ec1 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/PullCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/PullCommand.java
@@ -60,6 +60,7 @@
 import org.eclipse.jgit.api.errors.WrongRepositoryStateException;
 import org.eclipse.jgit.internal.JGitText;
 import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.lib.BranchConfig.BranchRebaseMode;
 import org.eclipse.jgit.lib.Config;
 import org.eclipse.jgit.lib.ConfigConstants;
 import org.eclipse.jgit.lib.Constants;
@@ -83,7 +84,7 @@
 
 	private ProgressMonitor monitor = NullProgressMonitor.INSTANCE;
 
-	private PullRebaseMode pullRebaseMode = null;
+	private BranchRebaseMode pullRebaseMode = null;
 
 	private String remote;
 
@@ -91,33 +92,6 @@
 
 	private MergeStrategy strategy = MergeStrategy.RECURSIVE;
 
-	private enum PullRebaseMode implements Config.ConfigEnum {
-		REBASE_PRESERVE("preserve", true, true), //$NON-NLS-1$
-		REBASE("true", true, false), //$NON-NLS-1$
-		NO_REBASE("false", false, false); //$NON-NLS-1$
-
-		private final String configValue;
-
-		private final boolean rebase;
-
-		private final boolean preserveMerges;
-
-		PullRebaseMode(String configValue, boolean rebase,
-				boolean preserveMerges) {
-			this.configValue = configValue;
-			this.rebase = rebase;
-			this.preserveMerges = preserveMerges;
-		}
-
-		public String toConfigValue() {
-			return configValue;
-		}
-
-		public boolean matchConfigValue(String in) {
-			return in.equals(configValue);
-		}
-	}
-
 	/**
 	 * @param repo
 	 */
@@ -158,7 +132,46 @@
 	 */
 	public PullCommand setRebase(boolean useRebase) {
 		checkCallable();
-		pullRebaseMode = useRebase ? PullRebaseMode.REBASE : PullRebaseMode.NO_REBASE;
+		pullRebaseMode = useRebase ? BranchRebaseMode.REBASE
+				: BranchRebaseMode.NONE;
+		return this;
+	}
+
+	/**
+	 * Sets the {@link BranchRebaseMode} to use after fetching.
+	 *
+	 * <dl>
+	 * <dt>BranchRebaseMode.REBASE</dt>
+	 * <dd>Equivalent to {@code --rebase} on the command line: use rebase
+	 * instead of merge after fetching.</dd>
+	 * <dt>BranchRebaseMode.PRESERVE</dt>
+	 * <dd>Equivalent to {@code --preserve-merges} on the command line: rebase
+	 * preserving local merge commits.</dd>
+	 * <dt>BranchRebaseMode.INTERACTIVE</dt>
+	 * <dd>Equivalent to {@code --interactive} on the command line: use
+	 * interactive rebase.</dd>
+	 * <dt>BranchRebaseMode.NONE</dt>
+	 * <dd>Equivalent to {@code --no-rebase}: merge instead of rebasing.
+	 * <dt>{@code null}</dt>
+	 * <dd>Use the setting defined in the git configuration, either {@code
+	 * branch.[name].rebase} or, if not set, {@code pull.rebase}</dd>
+	 * </dl>
+	 *
+	 * This setting overrides the settings in the configuration file. By
+	 * default, the setting in the repository configuration file is used.
+	 * <p>
+	 * A branch can be configured to use rebase by default. See
+	 * {@code branch.[name].rebase}, {@code branch.autosetuprebase}, and
+	 * {@code pull.rebase}.
+	 *
+	 * @param rebaseMode
+	 *            the {@link BranchRebaseMode} to use
+	 * @return {@code this}
+	 * @since 4.5
+	 */
+	public PullCommand setRebase(BranchRebaseMode rebaseMode) {
+		checkCallable();
+		pullRebaseMode = rebaseMode;
 		return this;
 	}
 
@@ -315,12 +328,13 @@
 				Repository.shortenRefName(remoteBranchName), remoteUri);
 
 		PullResult result;
-		if (pullRebaseMode.rebase) {
+		if (pullRebaseMode != BranchRebaseMode.NONE) {
 			RebaseCommand rebase = new RebaseCommand(repo);
 			RebaseResult rebaseRes = rebase.setUpstream(commitToMerge)
 					.setUpstreamName(upstreamName).setProgressMonitor(monitor)
 					.setOperation(Operation.BEGIN).setStrategy(strategy)
-					.setPreserveMerges(pullRebaseMode.preserveMerges)
+					.setPreserveMerges(
+							pullRebaseMode == BranchRebaseMode.PRESERVE)
 					.call();
 			result = new PullResult(fetchRes, remote, rebaseRes);
 		} else {
@@ -397,13 +411,29 @@
 		return this;
 	}
 
-	private static PullRebaseMode getRebaseMode(String branchName, Config config) {
-		PullRebaseMode mode = config.getEnum(PullRebaseMode.values(),
-				ConfigConstants.CONFIG_PULL_SECTION, null,
-				ConfigConstants.CONFIG_KEY_REBASE, PullRebaseMode.NO_REBASE);
-		mode = config.getEnum(PullRebaseMode.values(),
+	/**
+	 * Reads the rebase mode to use for a pull command from the repository
+	 * configuration. This is the value defined for the configurations
+	 * {@code branch.[branchName].rebase}, or,if not set, {@code pull.rebase}.
+	 * If neither is set, yields {@link BranchRebaseMode#NONE}.
+	 *
+	 * @param branchName
+	 *            name of the local branch
+	 * @param config
+	 *            the {@link Config} to read the value from
+	 * @return the {@link BranchRebaseMode}
+	 * @since 4.5
+	 */
+	public static BranchRebaseMode getRebaseMode(String branchName,
+			Config config) {
+		BranchRebaseMode mode = config.getEnum(BranchRebaseMode.values(),
 				ConfigConstants.CONFIG_BRANCH_SECTION,
-				branchName, ConfigConstants.CONFIG_KEY_REBASE, mode);
+				branchName, ConfigConstants.CONFIG_KEY_REBASE, null);
+		if (mode == null) {
+			mode = config.getEnum(BranchRebaseMode.values(),
+					ConfigConstants.CONFIG_PULL_SECTION, null,
+					ConfigConstants.CONFIG_KEY_REBASE, BranchRebaseMode.NONE);
+		}
 		return mode;
 	}
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/PushCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/PushCommand.java
index 0a49f78..bd4521b 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/PushCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/PushCommand.java
@@ -96,6 +96,8 @@
 
 	private OutputStream out;
 
+	private List<String> pushOptions;
+
 	/**
 	 * @param repo
 	 */
@@ -149,6 +151,7 @@
 				if (receivePack != null)
 					transport.setOptionReceivePack(receivePack);
 				transport.setDryRun(dryRun);
+				transport.setPushOptions(pushOptions);
 				configure(transport);
 
 				final Collection<RemoteRefUpdate> toPush = transport
@@ -189,7 +192,6 @@
 		}
 
 		return pushResults;
-
 	}
 
 	/**
@@ -453,4 +455,24 @@
 		this.out = out;
 		return this;
 	}
+
+	/**
+	 * @return the option strings associated with the push operation
+	 * @since 4.5
+	 */
+	public List<String> getPushOptions() {
+		return pushOptions;
+	}
+
+	/**
+	 * Sets the option strings associated with the push operation.
+	 *
+	 * @param pushOptions
+	 * @return {@code this}
+	 * @since 4.5
+	 */
+	public PushCommand setPushOptions(List<String> pushOptions) {
+		this.pushOptions = pushOptions;
+		return this;
+	}
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/ResetCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/ResetCommand.java
index e385a5d..3ceff84 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/ResetCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/ResetCommand.java
@@ -123,6 +123,8 @@
 
 	private Collection<String> filepaths = new LinkedList<String>();
 
+	private boolean isReflogDisabled;
+
 	/**
 	 *
 	 * @param repo
@@ -181,8 +183,12 @@
 				ru.setNewObjectId(commitId);
 
 				String refName = Repository.shortenRefName(getRefOrHEAD());
-				String message = refName + ": updating " + Constants.HEAD; //$NON-NLS-1$
-				ru.setRefLogMessage(message, false);
+				if (isReflogDisabled) {
+					ru.disableRefLog();
+				} else {
+					String message = refName + ": updating " + Constants.HEAD; //$NON-NLS-1$
+					ru.setRefLogMessage(message, false);
+				}
 				if (ru.forceUpdate() == RefUpdate.Result.LOCK_FAILURE)
 					throw new JGitInternalException(MessageFormat.format(
 							JGitText.get().cannotLock, ru.getName()));
@@ -289,6 +295,26 @@
 		return this;
 	}
 
+	/**
+	 * @param disable
+	 *            if {@code true} disables writing a reflog entry for this reset
+	 *            command
+	 * @return this instance
+	 * @since 4.5
+	 */
+	public ResetCommand disableRefLog(boolean disable) {
+		this.isReflogDisabled = disable;
+		return this;
+	}
+
+	/**
+	 * @return {@code true} if writing reflog is disabled for this reset command
+	 * @since 4.5
+	 */
+	public boolean isReflogDisabled() {
+		return this.isReflogDisabled;
+	}
+
 	private String getRefOrHEAD() {
 		if (ref != null)
 			return ref;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffFormatter.java b/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffFormatter.java
index fc701f3..819442c 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffFormatter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffFormatter.java
@@ -73,6 +73,7 @@
 import org.eclipse.jgit.internal.JGitText;
 import org.eclipse.jgit.lib.AbbreviatedObjectId;
 import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.lib.Config;
 import org.eclipse.jgit.lib.ConfigConstants;
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.FileMode;
@@ -117,10 +118,10 @@
 
 	private final OutputStream out;
 
-	private Repository db;
-
 	private ObjectReader reader;
 
+	private boolean closeReader;
+
 	private DiffConfig diffCfg;
 
 	private int context = 3;
@@ -172,28 +173,42 @@
 	 *            source repository holding referenced objects.
 	 */
 	public void setRepository(Repository repository) {
-		if (reader != null)
-			reader.close();
+		setReader(repository.newObjectReader(), repository.getConfig(), true);
+	}
 
-		db = repository;
-		reader = db.newObjectReader();
-		diffCfg = db.getConfig().get(DiffConfig.KEY);
+	/**
+	 * Set the repository the formatter can load object contents from.
+	 *
+	 * @param reader
+	 *            source reader holding referenced objects. Caller is responsible
+	 *            for closing the reader.
+	 * @param cfg
+	 *            config specifying diff algorithm and rename detection options.
+	 * @since 4.5
+	 */
+	public void setReader(ObjectReader reader, Config cfg) {
+		setReader(reader, cfg, false);
+	}
+
+	private void setReader(ObjectReader reader, Config cfg, boolean closeReader) {
+		close();
+		this.closeReader = closeReader;
+		this.reader = reader;
+		this.diffCfg = cfg.get(DiffConfig.KEY);
 
 		ContentSource cs = ContentSource.create(reader);
 		source = new ContentSource.Pair(cs, cs);
 
-		DiffConfig dc = db.getConfig().get(DiffConfig.KEY);
-		if (dc.isNoPrefix()) {
+		if (diffCfg.isNoPrefix()) {
 			setOldPrefix(""); //$NON-NLS-1$
 			setNewPrefix(""); //$NON-NLS-1$
 		}
-		setDetectRenames(dc.isRenameDetectionEnabled());
+		setDetectRenames(diffCfg.isRenameDetectionEnabled());
 
-		diffAlgorithm = DiffAlgorithm.getAlgorithm(db.getConfig().getEnum(
+		diffAlgorithm = DiffAlgorithm.getAlgorithm(cfg.getEnum(
 				ConfigConstants.CONFIG_DIFF_SECTION, null,
 				ConfigConstants.CONFIG_KEY_ALGORITHM,
 				SupportedAlgorithm.HISTOGRAM));
-
 	}
 
 	/**
@@ -330,8 +345,8 @@
 	 */
 	public void setDetectRenames(boolean on) {
 		if (on && renameDetector == null) {
-			assertHaveRepository();
-			renameDetector = new RenameDetector(db);
+			assertHaveReader();
+			renameDetector = new RenameDetector(reader, diffCfg);
 		} else if (!on)
 			renameDetector = null;
 	}
@@ -387,8 +402,9 @@
 	 */
 	@Override
 	public void close() {
-		if (reader != null)
+		if (reader != null && closeReader) {
 			reader.close();
+		}
 	}
 
 	/**
@@ -412,7 +428,7 @@
 	 */
 	public List<DiffEntry> scan(AnyObjectId a, AnyObjectId b)
 			throws IOException {
-		assertHaveRepository();
+		assertHaveReader();
 
 		try (RevWalk rw = new RevWalk(reader)) {
 			RevTree aTree = a != null ? rw.parseTree(a) : null;
@@ -441,7 +457,7 @@
 	 *             trees cannot be read or file contents cannot be read.
 	 */
 	public List<DiffEntry> scan(RevTree a, RevTree b) throws IOException {
-		assertHaveRepository();
+		assertHaveReader();
 
 		AbstractTreeIterator aIterator = makeIteratorFromTreeOrNull(a);
 		AbstractTreeIterator bIterator = makeIteratorFromTreeOrNull(b);
@@ -476,7 +492,7 @@
 	 */
 	public List<DiffEntry> scan(AbstractTreeIterator a, AbstractTreeIterator b)
 			throws IOException {
-		assertHaveRepository();
+		assertHaveReader();
 
 		TreeWalk walk = new TreeWalk(reader);
 		walk.addTree(a);
@@ -674,7 +690,7 @@
 	}
 
 	private String format(AbbreviatedObjectId id) {
-		if (id.isComplete() && db != null) {
+		if (id.isComplete() && reader != null) {
 			try {
 				id = reader.abbreviate(id.toObjectId(), abbreviationLength);
 			} catch (IOException cannotAbbreviate) {
@@ -940,7 +956,7 @@
 			type = PatchType.UNIFIED;
 
 		} else {
-			assertHaveRepository();
+			assertHaveReader();
 
 			byte[] aRaw, bRaw;
 
@@ -987,9 +1003,10 @@
 		return diffAlgorithm.diff(comparator, a, b);
 	}
 
-	private void assertHaveRepository() {
-		if (db == null)
-			throw new IllegalStateException(JGitText.get().repositoryIsRequired);
+	private void assertHaveReader() {
+		if (reader == null) {
+			throw new IllegalStateException(JGitText.get().readerIsRequired);
+		}
 	}
 
 	private byte[] open(DiffEntry.Side side, DiffEntry entry)
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 12ceb74..8af7e46 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java
@@ -718,10 +718,23 @@
 			return;
 		}
 
-		// if we have no file at all then there is nothing to do
-		if ((ffMask & 0x222) == 0
-				&& (f == null || FileMode.TREE.equals(f.getEntryFileMode())))
-			return;
+		if ((ffMask & 0x222) == 0) {
+			// HEAD, MERGE and index don't contain a file (e.g. all contain a
+			// folder)
+			if (f == null || FileMode.TREE.equals(f.getEntryFileMode())) {
+				// the workingtree entry doesn't exist or also contains a folder
+				// -> no problem
+				return;
+			} else {
+				// the workingtree entry exists and is not a folder
+				if (!idEqual(h, m)) {
+					// Because HEAD and MERGE differ we will try to update the
+					// workingtree with a folder -> return a conflict
+					conflict(name, null, null, null);
+				}
+				return;
+			}
+		}
 
 		if ((ffMask == 0x00F) && f != null && FileMode.TREE.equals(f.getEntryFileMode())) {
 			// File/Directory conflict case #20
@@ -1004,6 +1017,17 @@
 		}
 	}
 
+	private static boolean idEqual(AbstractTreeIterator a,
+			AbstractTreeIterator b) {
+		if (a == b) {
+			return true;
+		}
+		if (a == null || b == null) {
+			return false;
+		}
+		return a.getEntryObjectId().equals(b.getEntryObjectId());
+	}
+
 	/**
 	 * A conflict is detected - add the three different stages to the index
 	 * @param path the path of the conflicting entry
@@ -1355,7 +1379,7 @@
 				FileUtils.delete(tmpFile);
 			}
 		}
-		entry.setLastModified(f.lastModified());
+		entry.setLastModified(fs.lastModified(f));
 	}
 
 	@SuppressWarnings("deprecation")
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/errors/CommandFailedException.java b/org.eclipse.jgit/src/org/eclipse/jgit/errors/CommandFailedException.java
new file mode 100644
index 0000000..93749f5
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/errors/CommandFailedException.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2016, 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.errors;
+
+/**
+ * Thrown when an external command failed
+ *
+ * @since 4.5
+ */
+public class CommandFailedException extends Exception {
+
+	private static final long serialVersionUID = 1L;
+
+	private int returnCode;
+
+	/**
+	 * @param returnCode
+	 *            return code returned by the command
+	 * @param message
+	 *            error message
+	 */
+	public CommandFailedException(int returnCode, String message) {
+		super(message);
+		this.returnCode = returnCode;
+	}
+
+	/**
+	 * @param returnCode
+	 *            return code returned by the command
+	 * @param message
+	 *            error message
+	 * @param cause
+	 *            exception causing this exception
+	 */
+	public CommandFailedException(int returnCode, String message,
+			Throwable cause) {
+		super(message, cause);
+		this.returnCode = returnCode;
+	}
+
+	/**
+	 * @return return code returned by the command
+	 */
+	public int getReturnCode() {
+		return returnCode;
+	}
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/errors/NoPackSignatureException.java b/org.eclipse.jgit/src/org/eclipse/jgit/errors/NoPackSignatureException.java
new file mode 100644
index 0000000..28e9788
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/errors/NoPackSignatureException.java
@@ -0,0 +1,66 @@
+/*
+ * 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.errors;
+
+import java.io.IOException;
+
+/**
+ * Thrown when a PackFile is found not to contain the pack signature defined by
+ * git.
+ *
+ * @since 4.5
+ */
+public class NoPackSignatureException extends IOException {
+	private static final long serialVersionUID = 1L;
+
+	/**
+	 * Construct an exception.
+	 *
+	 * @param why
+	 *            description of the type of error.
+	 */
+	public NoPackSignatureException(final String why) {
+		super(why);
+	}
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/errors/UnsupportedPackIndexVersionException.java b/org.eclipse.jgit/src/org/eclipse/jgit/errors/UnsupportedPackIndexVersionException.java
new file mode 100644
index 0000000..0b7ccf5
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/errors/UnsupportedPackIndexVersionException.java
@@ -0,0 +1,69 @@
+/*
+ * 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.errors;
+
+import java.io.IOException;
+import java.text.MessageFormat;
+
+import org.eclipse.jgit.internal.JGitText;
+
+/**
+ * Thrown when a PackIndex uses an index version not supported by JGit.
+ *
+ * @since 4.5
+ */
+public class UnsupportedPackIndexVersionException extends IOException {
+	private static final long serialVersionUID = 1L;
+
+	/**
+	 * Construct an exception.
+	 *
+	 * @param version
+	 *            pack index version
+	 */
+	public UnsupportedPackIndexVersionException(final int version) {
+		super(MessageFormat.format(JGitText.get().unsupportedPackIndexVersion,
+				Integer.valueOf(version)));
+	}
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/errors/UnsupportedPackVersionException.java b/org.eclipse.jgit/src/org/eclipse/jgit/errors/UnsupportedPackVersionException.java
new file mode 100644
index 0000000..2361186
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/errors/UnsupportedPackVersionException.java
@@ -0,0 +1,69 @@
+/*
+ * 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.errors;
+
+import java.io.IOException;
+import java.text.MessageFormat;
+
+import org.eclipse.jgit.internal.JGitText;
+
+/**
+ * Thrown when a PackFile uses a pack version not supported by JGit.
+ *
+ * @since 4.5
+ */
+public class UnsupportedPackVersionException extends IOException {
+	private static final long serialVersionUID = 1L;
+
+	/**
+	 * Construct an exception.
+	 *
+	 * @param version
+	 *            pack version
+	 */
+	public UnsupportedPackVersionException(final long version) {
+		super(MessageFormat.format(JGitText.get().unsupportedPackVersion,
+				Long.valueOf(version)));
+	}
+}
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 ca976a1..9b7f094 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoCommand.java
@@ -107,7 +107,7 @@
 public class RepoCommand extends GitCommand<RevCommit> {
 	private String path;
 	private String uri;
-	private String groups;
+	private String groupsParam;
 	private String branch;
 	private String targetBranch = Constants.HEAD;
 	private boolean recordRemoteBranch = false;
@@ -286,7 +286,7 @@
 	 * @return this command
 	 */
 	public RepoCommand setGroups(String groups) {
-		this.groups = groups;
+		this.groupsParam = groups;
 		return this;
 	}
 
@@ -478,7 +478,7 @@
 				git = new Git(repo);
 
 			ManifestParser parser = new ManifestParser(
-					includedReader, path, branch, uri, groups, repo);
+					includedReader, path, branch, uri, groupsParam, repo);
 			try {
 				parser.read(inputStream);
 				for (RepoProject proj : parser.getFilteredProjects()) {
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 d29f6c0..ff4a3ed 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoProject.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoProject.java
@@ -167,14 +167,14 @@
 	 *            a SHA-1 or branch name or tag name
 	 * @param remote
 	 *            name of the remote definition
-	 * @param groups
+	 * @param groupsParam
 	 *            comma separated group list
 	 */
 	public RepoProject(String name, String path, String revision,
-			String remote, String groups) {
+			String remote, String groupsParam) {
 		this(name, path, revision, remote, new HashSet<String>(), null);
-		if (groups != null && groups.length() > 0)
-			this.setGroups(groups);
+		if (groupsParam != null && groupsParam.length() > 0)
+			this.setGroups(groupsParam);
 	}
 
 	/**
@@ -191,14 +191,14 @@
 	/**
 	 * Set the url of the sub repo.
 	 *
-	 * @param groups
+	 * @param groupsParam
 	 *            comma separated group list
 	 * @return this for chaining.
 	 * @since 4.4
 	 */
-	public RepoProject setGroups(String groups) {
+	public RepoProject setGroups(String groupsParam) {
 		this.groups.clear();
-		this.groups.addAll(Arrays.asList(groups.split(","))); //$NON-NLS-1$
+		this.groups.addAll(Arrays.asList(groupsParam.split(","))); //$NON-NLS-1$
 		return this;
 	}
 
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/hooks/Hooks.java b/org.eclipse.jgit/src/org/eclipse/jgit/hooks/Hooks.java
index 6f7a21a..46e8840 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/hooks/Hooks.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/hooks/Hooks.java
@@ -68,6 +68,18 @@
 	 * @param repo
 	 * @param outputStream
 	 *            The output stream, or {@code null} to use {@code System.out}
+	 * @return The post-commit hook for the given repository.
+	 * @since 4.5
+	 */
+	public static PostCommitHook postCommit(Repository repo,
+			PrintStream outputStream) {
+		return new PostCommitHook(repo, outputStream);
+	}
+
+	/**
+	 * @param repo
+	 * @param outputStream
+	 *            The output stream, or {@code null} to use {@code System.out}
 	 * @return The commit-msg hook for the given repository.
 	 */
 	public static CommitMsgHook commitMsg(Repository repo,
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/hooks/PostCommitHook.java b/org.eclipse.jgit/src/org/eclipse/jgit/hooks/PostCommitHook.java
new file mode 100644
index 0000000..70679e0
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/hooks/PostCommitHook.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2015 Obeo.
+ * 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.hooks;
+
+import java.io.IOException;
+import java.io.PrintStream;
+
+import org.eclipse.jgit.api.errors.AbortedByHookException;
+import org.eclipse.jgit.lib.Repository;
+
+/**
+ * The <code>post-commit</code> hook implementation. This hook is run after the
+ * commit was successfully executed.
+ *
+ * @since 4.5
+ */
+public class PostCommitHook extends GitHook<Void> {
+
+	/** The post-commit hook name. */
+	public static final String NAME = "post-commit"; //$NON-NLS-1$
+
+	/**
+	 * @param repo
+	 *            The repository
+	 * @param outputStream
+	 *            The output stream the hook must use. {@code null} is allowed,
+	 *            in which case the hook will use {@code System.out}.
+	 */
+	protected PostCommitHook(Repository repo, PrintStream outputStream) {
+		super(repo, outputStream);
+	}
+
+	@Override
+	public Void call() throws IOException, AbortedByHookException {
+		doRun();
+		return null;
+	}
+
+	@Override
+	public String getHookName() {
+		return NAME;
+	}
+
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/FastIgnoreRule.java b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/FastIgnoreRule.java
index e376cbb..eb081ad 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/FastIgnoreRule.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/FastIgnoreRule.java
@@ -43,6 +43,8 @@
 package org.eclipse.jgit.ignore;
 
 import static org.eclipse.jgit.ignore.internal.Strings.stripTrailing;
+import static org.eclipse.jgit.ignore.internal.Strings.stripTrailingWhitespace;
+import static org.eclipse.jgit.ignore.internal.Strings.isDirectoryPattern;
 import static org.eclipse.jgit.ignore.internal.IMatcher.NO_MATCH;
 import org.eclipse.jgit.errors.InvalidPatternException;
 import org.eclipse.jgit.ignore.internal.IMatcher;
@@ -111,8 +113,9 @@
 				pattern = pattern.substring(1);
 			}
 		}
-		dirOnly = pattern.charAt(pattern.length() - 1) == PATH_SEPARATOR;
+		dirOnly = isDirectoryPattern(pattern);
 		if (dirOnly) {
+			pattern = stripTrailingWhitespace(pattern);
 			pattern = stripTrailing(pattern, PATH_SEPARATOR);
 			if (pattern.length() == 0) {
 				this.matcher = NO_MATCH;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/Strings.java b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/Strings.java
index e354c71..70c5199 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/Strings.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/Strings.java
@@ -76,10 +76,50 @@
 	 * @return new string with all trailing characters removed
 	 */
 	public static String stripTrailing(String pattern, char c) {
-		while (pattern.length() > 0
-				&& pattern.charAt(pattern.length() - 1) == c)
-			pattern = pattern.substring(0, pattern.length() - 1);
-		return pattern;
+		for (int i = pattern.length() - 1; i >= 0; i--) {
+			char charAt = pattern.charAt(i);
+			if (charAt != c) {
+				if (i == pattern.length() - 1) {
+					return pattern;
+				}
+				return pattern.substring(0, i + 1);
+			}
+		}
+		return ""; //$NON-NLS-1$
+	}
+
+	/**
+	 * @param pattern
+	 *            non null
+	 * @return new string with all trailing whitespace removed
+	 */
+	public static String stripTrailingWhitespace(String pattern) {
+		for (int i = pattern.length() - 1; i >= 0; i--) {
+			char charAt = pattern.charAt(i);
+			if (!Character.isWhitespace(charAt)) {
+				if (i == pattern.length() - 1) {
+					return pattern;
+				}
+				return pattern.substring(0, i + 1);
+			}
+		}
+		return ""; //$NON-NLS-1$
+	}
+
+	/**
+	 * @param pattern
+	 *            non null
+	 * @return true if the last character, which is not whitespace, is a path
+	 *         separator
+	 */
+	public static boolean isDirectoryPattern(String pattern) {
+		for (int i = pattern.length() - 1; i >= 0; i--) {
+			char charAt = pattern.charAt(i);
+			if (!Character.isWhitespace(charAt)) {
+				return charAt == FastIgnoreRule.PATH_SEPARATOR;
+			}
+		}
+		return false;
 	}
 
 	static int count(String s, char c, boolean ignoreFirstLast) {
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 b7ef085..d32e873 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java
@@ -182,6 +182,7 @@
 	/***/ public String commitOnRepoWithoutHEADCurrentlyNotSupported;
 	/***/ public String commitAmendOnInitialNotPossible;
 	/***/ public String compressingObjects;
+	/***/ public String configHandleIsStale;
 	/***/ public String connectionFailed;
 	/***/ public String connectionTimeOut;
 	/***/ public String contextMustBeNonNegative;
@@ -363,6 +364,7 @@
 	/***/ public String hunkHeaderDoesNotMatchBodyLineCountOf;
 	/***/ public String illegalArgumentNotA;
 	/***/ public String illegalCombinationOfArguments;
+	/***/ public String illegalHookName;
 	/***/ public String illegalPackingPhase;
 	/***/ public String illegalStateExists;
 	/***/ public String improperlyPaddedBase64Input;
@@ -391,7 +393,9 @@
 	/***/ public String invalidChannel;
 	/***/ public String invalidCharacterInBase64Data;
 	/***/ public String invalidCommitParentNumber;
+	/***/ public String invalidDepth;
 	/***/ public String invalidEncryption;
+	/***/ public String invalidExpandWildcard;
 	/***/ public String invalidGitdirRef;
 	/***/ public String invalidGitType;
 	/***/ public String invalidId;
@@ -557,7 +561,9 @@
 	/***/ public String pushCertificateInvalidSignature;
 	/***/ public String pushIsNotSupportedForBundleTransport;
 	/***/ public String pushNotPermitted;
+	/***/ public String pushOptionsNotSupported;
 	/***/ public String rawLogMessageDoesNotParseAsLogEntry;
+	/***/ public String readerIsRequired;
 	/***/ public String readingObjectsFromLocalRepositoryFailed;
 	/***/ public String readTimedOut;
 	/***/ public String receivePackObjectTooLarge1;
@@ -586,7 +592,6 @@
 	/***/ public String renamesRejoiningModifies;
 	/***/ public String repositoryAlreadyExists;
 	/***/ public String repositoryConfigFileInvalid;
-	/***/ public String repositoryIsRequired;
 	/***/ public String repositoryNotFound;
 	/***/ public String repositoryState_applyMailbox;
 	/***/ public String repositoryState_bare;
@@ -614,6 +619,7 @@
 	/***/ public String serviceNotEnabledNoName;
 	/***/ public String serviceNotPermitted;
 	/***/ public String shallowCommitsAlreadyInitialized;
+	/***/ public String shallowPacksRequireDepthWalk;
 	/***/ public String shortCompressedStreamAt;
 	/***/ public String shortReadOfBlock;
 	/***/ public String shortReadOfOptionalDIRCExtensionExpectedAnotherBytes;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlock.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlock.java
index 7926536..4a33fb8 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlock.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlock.java
@@ -88,9 +88,16 @@
 		return n;
 	}
 
-	int setInput(long pos, Inflater inf) {
+	int setInput(long pos, Inflater inf) throws DataFormatException {
 		int ptr = (int) (pos - start);
 		int cnt = block.length - ptr;
+		if (cnt <= 0) {
+			throw new DataFormatException(cnt + " bytes to inflate:" //$NON-NLS-1$
+					+ " at pos=" + pos //$NON-NLS-1$
+					+ "; block.start=" + start //$NON-NLS-1$
+					+ "; ptr=" + ptr //$NON-NLS-1$
+					+ "; block.length=" + block.length); //$NON-NLS-1$
+		}
 		inf.setInput(block, ptr, cnt);
 		return cnt;
 	}
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 dc91a70..6f760ca 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
@@ -44,6 +44,7 @@
 package org.eclipse.jgit.internal.storage.dfs;
 
 import static org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource.GC;
+import static org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource.GC_REST;
 import static org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource.GC_TXN;
 import static org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource.UNREACHABLE_GARBAGE;
 import static org.eclipse.jgit.internal.storage.pack.PackExt.BITMAP_INDEX;
@@ -53,9 +54,11 @@
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
+import java.util.concurrent.TimeUnit;
 
 import org.eclipse.jgit.internal.JGitText;
 import org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource;
@@ -92,9 +95,13 @@
 
 	private PackConfig packConfig;
 
+	// See pack(), below, for how these two variables interact.
 	private long coalesceGarbageLimit = 50 << 20;
+	private long garbageTtlMillis = TimeUnit.DAYS.toMillis(1);
 
+	private long startTimeMillis;
 	private List<DfsPackFile> packsBefore;
+	private List<DfsPackFile> expiredGarbagePacks;
 
 	private Set<ObjectId> allHeads;
 	private Set<ObjectId> nonHeads;
@@ -167,6 +174,34 @@
 	}
 
 	/**
+	 * @return garbage packs older than this limit (in milliseconds) will be
+	 *         pruned as part of the garbage collection process if the value is
+	 *         > 0, otherwise garbage packs are retained.
+	 */
+	public long getGarbageTtlMillis() {
+		return garbageTtlMillis;
+	}
+
+	/**
+	 * Set the time to live for garbage objects.
+	 * <p>
+	 * Any UNREACHABLE_GARBAGE older than this limit will be pruned at the end
+	 * of the run.
+	 * <p>
+	 * If timeToLiveMillis is set to 0, UNREACHABLE_GARBAGE purging is disabled.
+	 *
+	 * @param ttl
+	 *            Time to live whatever unit is specified.
+	 * @param unit
+	 *            The specified time unit.
+	 * @return {@code this}
+	 */
+	public DfsGarbageCollector setGarbageTtl(long ttl, TimeUnit unit) {
+		garbageTtlMillis = unit.toMillis(ttl);
+		return this;
+	}
+
+	/**
 	 * Create a single new pack file containing all of the live objects.
 	 * <p>
 	 * This method safely decides which packs can be expired after the new pack
@@ -188,16 +223,28 @@
 		if (packConfig.getIndexVersion() != 2)
 			throw new IllegalStateException(
 					JGitText.get().supportOnlyPackIndexVersion2);
+		if (garbageTtlMillis > 0) {
+			// We disable coalescing because the coalescing step will keep
+			// refreshing the UNREACHABLE_GARBAGE pack and we wouldn't
+			// actually prune anything.
+			coalesceGarbageLimit = 0;
+		}
 
+		startTimeMillis = System.currentTimeMillis();
 		ctx = (DfsReader) objdb.newReader();
 		try {
 			refdb.refresh();
 			objdb.clearCache();
 
 			Collection<Ref> refsBefore = getAllRefs();
-			packsBefore = packsToRebuild();
-			if (packsBefore.isEmpty())
+			readPacksBefore();
+
+			if (packsBefore.isEmpty()) {
+				if (!expiredGarbagePacks.isEmpty()) {
+					objdb.commitPack(noPacks(), toPrune());
+				}
 				return true;
+			}
 
 			allHeads = new HashSet<ObjectId>();
 			nonHeads = new HashSet<ObjectId>();
@@ -252,17 +299,60 @@
 		return refs;
 	}
 
-	private List<DfsPackFile> packsToRebuild() throws IOException {
+	private void readPacksBefore() throws IOException {
 		DfsPackFile[] packs = objdb.getPacks();
-		List<DfsPackFile> out = new ArrayList<DfsPackFile>(packs.length);
+		packsBefore = new ArrayList<DfsPackFile>(packs.length);
+		expiredGarbagePacks = new ArrayList<DfsPackFile>(packs.length);
+
+		long mostRecentGC = mostRecentGC(packs);
+		long now = System.currentTimeMillis();
 		for (DfsPackFile p : packs) {
 			DfsPackDescription d = p.getPackDescription();
-			if (d.getPackSource() != UNREACHABLE_GARBAGE)
-				out.add(p);
-			else if (d.getFileSize(PackExt.PACK) < coalesceGarbageLimit)
-				out.add(p);
+			if (d.getPackSource() != UNREACHABLE_GARBAGE) {
+				packsBefore.add(p);
+			} else if (packIsExpiredGarbage(d, mostRecentGC, now)) {
+				expiredGarbagePacks.add(p);
+			} else if (d.getFileSize(PackExt.PACK) < coalesceGarbageLimit) {
+				packsBefore.add(p);
+			}
 		}
-		return out;
+	}
+
+	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.
+		return d.getPackSource() == UNREACHABLE_GARBAGE
+				&& d.getLastModified() < mostRecentGC
+				&& garbageTtlMillis > 0
+				&& now - d.getLastModified() >= garbageTtlMillis;
 	}
 
 	/** @return all of the source packs that fed into this compaction. */
@@ -283,8 +373,12 @@
 	private List<DfsPackDescription> toPrune() {
 		int cnt = packsBefore.size();
 		List<DfsPackDescription> all = new ArrayList<DfsPackDescription>(cnt);
-		for (DfsPackFile pack : packsBefore)
+		for (DfsPackFile pack : packsBefore) {
 			all.add(pack.getPackDescription());
+		}
+		for (DfsPackFile pack : expiredGarbagePacks) {
+			all.add(pack.getPackDescription());
+		}
 		return all;
 	}
 
@@ -299,6 +393,7 @@
 				writePack(GC, pw, pm);
 		}
 	}
+
 	private void packRest(ProgressMonitor pm) throws IOException {
 		if (nonHeads.isEmpty())
 			return;
@@ -308,7 +403,7 @@
 				pw.excludeObjects(packedObjs);
 			pw.preparePack(pm, nonHeads, allHeads);
 			if (0 < pw.getObjectCount())
-				writePack(GC, pw, pm);
+				writePack(GC_REST, pw, pm);
 		}
 	}
 
@@ -326,7 +421,6 @@
 	}
 
 	private void packGarbage(ProgressMonitor pm) throws IOException {
-		// TODO(sop) This is ugly. The garbage pack needs to be deleted.
 		PackConfig cfg = new PackConfig(packConfig);
 		cfg.setReuseDeltas(true);
 		cfg.setReuseObjects(true);
@@ -383,47 +477,42 @@
 
 	private DfsPackDescription writePack(PackSource source, PackWriter pw,
 			ProgressMonitor pm) throws IOException {
-		DfsOutputStream out;
 		DfsPackDescription pack = repo.getObjectDatabase().newPack(source);
 		newPackDesc.add(pack);
 
-		out = objdb.writeFile(pack, PACK);
-		try {
+		try (DfsOutputStream out = objdb.writeFile(pack, PACK)) {
 			pw.writePack(pm, pm, out);
 			pack.addFileExt(PACK);
-		} finally {
-			out.close();
 		}
 
-		out = objdb.writeFile(pack, INDEX);
-		try {
-			CountingOutputStream cnt = new CountingOutputStream(out);
+		try (CountingOutputStream cnt =
+				new CountingOutputStream(objdb.writeFile(pack, INDEX))) {
 			pw.writeIndex(cnt);
 			pack.addFileExt(INDEX);
 			pack.setFileSize(INDEX, cnt.getCount());
 			pack.setIndexVersion(pw.getIndexVersion());
-		} finally {
-			out.close();
 		}
 
 		if (pw.prepareBitmapIndex(pm)) {
-			out = objdb.writeFile(pack, BITMAP_INDEX);
-			try {
-				CountingOutputStream cnt = new CountingOutputStream(out);
+			try (CountingOutputStream cnt = new CountingOutputStream(
+					objdb.writeFile(pack, BITMAP_INDEX))) {
 				pw.writeBitmapIndex(cnt);
 				pack.addFileExt(BITMAP_INDEX);
 				pack.setFileSize(BITMAP_INDEX, cnt.getCount());
-			} finally {
-				out.close();
 			}
 		}
 
 		PackStatistics stats = pw.getStatistics();
 		pack.setPackStats(stats);
+		pack.setLastModified(startTimeMillis);
 		newPackStats.add(stats);
 		newPackObj.add(pw.getObjectSet());
 
 		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 f5673e8..a5e920a 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
@@ -69,6 +69,7 @@
 import java.util.zip.InflaterInputStream;
 
 import org.eclipse.jgit.errors.CorruptObjectException;
+import org.eclipse.jgit.errors.IncorrectObjectTypeException;
 import org.eclipse.jgit.errors.LargeObjectException;
 import org.eclipse.jgit.internal.JGitText;
 import org.eclipse.jgit.internal.storage.file.PackIndex;
@@ -106,6 +107,7 @@
 	DfsPackDescription packDsc;
 	PackStream packOut;
 	private boolean rollback;
+	private boolean checkExisting = true;
 
 	/**
 	 * Initialize a new inserter.
@@ -117,6 +119,15 @@
 		this.db = db;
 	}
 
+	/**
+	 * @param check
+	 *            if false, will write out possibly-duplicate objects without
+	 *            first checking whether they exist in the repo; default is true.
+	 */
+	public void checkExisting(boolean check) {
+		checkExisting = check;
+	}
+
 	void setCompressionLevel(int compression) {
 		this.compression = compression;
 	}
@@ -138,7 +149,7 @@
 		if (objectMap != null && objectMap.contains(id))
 			return id;
 		// Ignore unreachable (garbage) objects here.
-		if (db.has(id, true))
+		if (checkExisting && db.has(id, true))
 			return id;
 
 		long offset = beginObject(type, len);
@@ -474,7 +485,8 @@
 			}
 		}
 
-		private int setInput(long pos, Inflater inf) throws IOException {
+		private int setInput(long pos, Inflater inf)
+				throws IOException, DataFormatException {
 			if (pos < currPos)
 				return getOrLoadBlock(pos).setInput(pos, inf);
 			if (pos < currPos + currPtr) {
@@ -559,6 +571,9 @@
 			if (type == OBJ_OFS_DELTA || type == OBJ_REF_DELTA)
 				throw new IOException(MessageFormat.format(
 						DfsText.get().cannotReadBackDelta, Integer.toString(type)));
+			if (typeHint != OBJ_ANY && type != typeHint) {
+				throw new IncorrectObjectTypeException(objectId.copy(), typeHint);
+			}
 
 			long sz = c & 0x0f;
 			int ptr = 1;
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 3641560..b1d6c0d 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
@@ -61,7 +61,22 @@
 
 /** Manages objects stored in {@link DfsPackFile} on a storage system. */
 public abstract class DfsObjDatabase extends ObjectDatabase {
-	private static final PackList NO_PACKS = new PackList(new DfsPackFile[0]);
+	private static final PackList NO_PACKS = new PackList(new DfsPackFile[0]) {
+		@Override
+		boolean dirty() {
+			return true;
+		}
+
+		@Override
+		void clearDirty() {
+			// Always dirty.
+		}
+
+		@Override
+		public void markDirty() {
+			// Always dirty.
+		}
+	};
 
 	/** Sources for a pack file. */
 	public static enum PackSource {
@@ -80,24 +95,6 @@
 		RECEIVE(0),
 
 		/**
-		 * Pack was created by Git garbage collection by this implementation.
-		 * <p>
-		 * This source is only used by the {@link DfsGarbageCollector} when it
-		 * builds a pack file by traversing the object graph and copying all
-		 * reachable objects into a new pack stream.
-		 *
-		 * @see DfsGarbageCollector
-		 */
-		GC(1),
-
-		/**
-		 * RefTreeGraph pack was created by Git garbage collection.
-		 *
-		 * @see DfsGarbageCollector
-		 */
-		GC_TXN(1),
-
-		/**
 		 * The pack was created by compacting multiple packs together.
 		 * <p>
 		 * Packs created by compacting multiple packs together aren't nearly as
@@ -109,13 +106,34 @@
 		COMPACT(1),
 
 		/**
+		 * Pack was created by Git garbage collection by this implementation.
+		 * <p>
+		 * This source is only used by the {@link DfsGarbageCollector} when it
+		 * builds a pack file by traversing the object graph and copying all
+		 * reachable objects into a new pack stream.
+		 *
+		 * @see DfsGarbageCollector
+		 */
+		GC(2),
+
+		/** Created from non-heads by {@link DfsGarbageCollector}. */
+		GC_REST(3),
+
+		/**
+		 * RefTreeGraph pack was created by Git garbage collection.
+		 *
+		 * @see DfsGarbageCollector
+		 */
+		GC_TXN(4),
+
+		/**
 		 * Pack was created by Git garbage collection.
 		 * <p>
 		 * This pack contains only unreachable garbage that was found during the
 		 * last GC pass. It is retained in a new pack until it is safe to prune
 		 * these objects from the repository.
 		 */
-		UNREACHABLE_GARBAGE(2);
+		UNREACHABLE_GARBAGE(5);
 
 		final int category;
 
@@ -170,7 +188,20 @@
 	 *             the pack list cannot be initialized.
 	 */
 	public DfsPackFile[] getPacks() throws IOException {
-		return scanPacks(NO_PACKS).packs;
+		return getPackList().packs;
+	}
+
+	/**
+	 * Scan and list all available pack files in the repository.
+	 *
+	 * @return list of available packs, with some additional metadata. The
+	 *         returned array is shared with the implementation and must not be
+	 *         modified by the caller.
+	 * @throws IOException
+	 *             the pack list cannot be initialized.
+	 */
+	public PackList getPackList() throws IOException {
+		return scanPacks(NO_PACKS);
 	}
 
 	/** @return repository owning this object database. */
@@ -185,7 +216,18 @@
 	 *         implementation and must not be modified by the caller.
 	 */
 	public DfsPackFile[] getCurrentPacks() {
-		return packList.get().packs;
+		return getCurrentPackList().packs;
+	}
+
+	/**
+	 * List currently known pack files in the repository, without scanning.
+	 *
+	 * @return list of available packs, with some additional metadata. The
+	 *         returned array is shared with the implementation and must not be
+	 *         modified by the caller.
+	 */
+	public PackList getCurrentPackList() {
+		return packList.get();
 	}
 
 	/**
@@ -360,11 +402,11 @@
 			DfsPackFile[] packs = new DfsPackFile[1 + o.packs.length];
 			packs[0] = newPack;
 			System.arraycopy(o.packs, 0, packs, 1, o.packs.length);
-			n = new PackList(packs);
+			n = new PackListImpl(packs);
 		} while (!packList.compareAndSet(o, n));
 	}
 
-	private PackList scanPacks(final PackList original) throws IOException {
+	PackList scanPacks(final PackList original) throws IOException {
 		PackList o, n;
 		synchronized (packList) {
 			do {
@@ -405,10 +447,12 @@
 		for (DfsPackFile p : forReuse.values())
 			p.close();
 		if (list.isEmpty())
-			return new PackList(NO_PACKS.packs);
-		if (!foundNew)
+			return new PackListImpl(NO_PACKS.packs);
+		if (!foundNew) {
+			old.clearDirty();
 			return old;
-		return new PackList(list.toArray(new DfsPackFile[list.size()]));
+		}
+		return new PackListImpl(list.toArray(new DfsPackFile[list.size()]));
 	}
 
 	private static Map<DfsPackDescription, DfsPackFile> reuseMap(PackList old) {
@@ -453,12 +497,62 @@
 		// p.close();
 	}
 
-	private static final class PackList {
+	/** Snapshot of packs scanned in a single pass. */
+	public static abstract class PackList {
 		/** All known packs, sorted. */
-		final DfsPackFile[] packs;
+		public final DfsPackFile[] packs;
 
-		PackList(final DfsPackFile[] packs) {
+		private long lastModified = -1;
+
+		PackList(DfsPackFile[] packs) {
 			this.packs = packs;
 		}
+
+		/** @return last modified time of all packs, in milliseconds. */
+		public long getLastModified() {
+			if (lastModified < 0) {
+				long max = 0;
+				for (DfsPackFile pack : packs) {
+					max = Math.max(max, pack.getPackDescription().getLastModified());
+				}
+				lastModified = max;
+			}
+			return lastModified;
+		}
+
+		abstract boolean dirty();
+		abstract void clearDirty();
+
+		/**
+		 * Mark pack list as dirty.
+		 * <p>
+		 * Used when the caller knows that new data might have been written to the
+		 * repository that could invalidate open readers depending on this pack list,
+		 * for example if refs are newly scanned.
+		 */
+		public abstract void markDirty();
+	}
+
+	private static final class PackListImpl extends PackList {
+		private volatile boolean dirty;
+
+		PackListImpl(DfsPackFile[] packs) {
+			super(packs);
+		}
+
+		@Override
+		boolean dirty() {
+			return dirty;
+		}
+
+		@Override
+		void clearDirty() {
+			dirty = false;
+		}
+
+		@Override
+		public void markDirty() {
+			dirty = true;
+		}
 	}
 }
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 66421e2..2f61dea 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
@@ -53,6 +53,7 @@
 import java.util.Comparator;
 import java.util.HashSet;
 import java.util.Iterator;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.Set;
 import java.util.zip.DataFormatException;
@@ -62,6 +63,7 @@
 import org.eclipse.jgit.errors.MissingObjectException;
 import org.eclipse.jgit.errors.StoredObjectRepresentationNotAvailableException;
 import org.eclipse.jgit.internal.JGitText;
+import org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackList;
 import org.eclipse.jgit.internal.storage.file.BitmapIndexImpl;
 import org.eclipse.jgit.internal.storage.file.PackBitmapIndex;
 import org.eclipse.jgit.internal.storage.file.PackIndex;
@@ -91,6 +93,8 @@
  * reader is not thread safe.
  */
 public final 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. */
 	final byte[] tempId = new byte[OBJECT_ID_LENGTH];
 
@@ -163,22 +167,44 @@
 			return Collections.singleton(id.toObjectId());
 		boolean noGarbage = avoidUnreachable;
 		HashSet<ObjectId> matches = new HashSet<ObjectId>(4);
-		for (DfsPackFile pack : db.getPacks()) {
-			if (noGarbage && pack.isGarbage())
-				continue;
-			pack.resolve(this, matches, id, 256);
-			if (256 <= matches.size())
-				break;
+		PackList packList = db.getPackList();
+		resolveImpl(packList, id, noGarbage, matches);
+		if (matches.size() < MAX_RESOLVE_MATCHES && packList.dirty()) {
+			resolveImpl(db.scanPacks(packList), id, noGarbage, matches);
 		}
 		return matches;
 	}
 
+	private void resolveImpl(PackList packList, AbbreviatedObjectId id,
+			boolean noGarbage, HashSet<ObjectId> matches) throws IOException {
+		for (DfsPackFile pack : packList.packs) {
+			if (noGarbage && pack.isGarbage()) {
+				continue;
+			}
+			pack.resolve(this, matches, id, MAX_RESOLVE_MATCHES);
+			if (matches.size() >= MAX_RESOLVE_MATCHES) {
+				break;
+			}
+		}
+	}
+
 	@Override
 	public boolean has(AnyObjectId objectId) throws IOException {
 		if (last != null && last.hasObject(this, objectId))
 			return true;
 		boolean noGarbage = avoidUnreachable;
-		for (DfsPackFile pack : db.getPacks()) {
+		PackList packList = db.getPackList();
+		if (hasImpl(packList, objectId, noGarbage)) {
+			return true;
+		} else if (packList.dirty()) {
+			return hasImpl(db.scanPacks(packList), objectId, noGarbage);
+		}
+		return false;
+	}
+
+	private boolean hasImpl(PackList packList, AnyObjectId objectId,
+			boolean noGarbage) throws IOException {
+		for (DfsPackFile pack : packList.packs) {
 			if (pack == last || (noGarbage && pack.isGarbage()))
 				continue;
 			if (pack.hasObject(this, objectId)) {
@@ -193,20 +219,24 @@
 	public ObjectLoader open(AnyObjectId objectId, int typeHint)
 			throws MissingObjectException, IncorrectObjectTypeException,
 			IOException {
+		ObjectLoader ldr;
 		if (last != null) {
-			ObjectLoader ldr = last.get(this, objectId);
-			if (ldr != null)
-				return ldr;
+			ldr = last.get(this, objectId);
+			if (ldr != null) {
+				return checkType(ldr, objectId, typeHint);
+			}
 		}
 
+		PackList packList = db.getPackList();
 		boolean noGarbage = avoidUnreachable;
-		for (DfsPackFile pack : db.getPacks()) {
-			if (pack == last || (noGarbage && pack.isGarbage()))
-				continue;
-			ObjectLoader ldr = pack.get(this, objectId);
+		ldr = openImpl(packList, objectId, noGarbage);
+		if (ldr != null) {
+			return checkType(ldr, objectId, typeHint);
+		}
+		if (packList.dirty()) {
+			ldr = openImpl(db.scanPacks(packList), objectId, noGarbage);
 			if (ldr != null) {
-				last = pack;
-				return ldr;
+				return checkType(ldr, objectId, typeHint);
 			}
 		}
 
@@ -216,6 +246,29 @@
 		throw new MissingObjectException(objectId.copy(), typeHint);
 	}
 
+	private static ObjectLoader checkType(ObjectLoader ldr, AnyObjectId id,
+			int typeHint) throws IncorrectObjectTypeException {
+		if (typeHint != OBJ_ANY && ldr.getType() != typeHint) {
+			throw new IncorrectObjectTypeException(id.copy(), typeHint);
+		}
+		return ldr;
+	}
+
+	private ObjectLoader openImpl(PackList packList, AnyObjectId objectId,
+			boolean noGarbage) throws IOException {
+		for (DfsPackFile pack : packList.packs) {
+			if (pack == last || (noGarbage && pack.isGarbage())) {
+				continue;
+			}
+			ObjectLoader ldr = pack.get(this, objectId);
+			if (ldr != null) {
+				last = pack;
+				return ldr;
+			}
+		}
+		return null;
+	}
+
 	@Override
 	public Set<ObjectId> getShallowCommits() {
 		return Collections.emptySet();
@@ -253,39 +306,58 @@
 
 	private <T extends ObjectId> Iterable<FoundObject<T>> findAll(
 			Iterable<T> objectIds) throws IOException {
-		ArrayList<FoundObject<T>> r = new ArrayList<FoundObject<T>>();
-		DfsPackFile[] packList = db.getPacks();
-		if (packList.length == 0) {
-			for (T t : objectIds)
-				r.add(new FoundObject<T>(t));
-			return r;
+		Collection<T> pending = new LinkedList<>();
+		for (T id : objectIds) {
+			pending.add(id);
 		}
 
+		PackList packList = db.getPackList();
+		List<FoundObject<T>> r = new ArrayList<>();
+		findAllImpl(packList, pending, r);
+		if (!pending.isEmpty() && packList.dirty()) {
+			findAllImpl(db.scanPacks(packList), pending, r);
+		}
+		for (T t : pending) {
+			r.add(new FoundObject<T>(t));
+		}
+		Collections.sort(r, FOUND_OBJECT_SORT);
+		return r;
+	}
+
+	private <T extends ObjectId> void findAllImpl(PackList packList,
+			Collection<T> pending, List<FoundObject<T>> r) {
+		DfsPackFile[] packs = packList.packs;
+		if (packs.length == 0) {
+			return;
+		}
 		int lastIdx = 0;
-		DfsPackFile lastPack = packList[lastIdx];
+		DfsPackFile lastPack = packs[lastIdx];
 		boolean noGarbage = avoidUnreachable;
 
-		OBJECT_SCAN: for (T t : objectIds) {
+		OBJECT_SCAN: for (Iterator<T> it = pending.iterator(); it.hasNext();) {
+			T t = it.next();
 			try {
 				long p = lastPack.findOffset(this, t);
 				if (0 < p) {
 					r.add(new FoundObject<T>(t, lastIdx, lastPack, p));
+					it.remove();
 					continue;
 				}
 			} catch (IOException e) {
 				// Fall though and try to examine other packs.
 			}
 
-			for (int i = 0; i < packList.length; i++) {
+			for (int i = 0; i < packs.length; i++) {
 				if (i == lastIdx)
 					continue;
-				DfsPackFile pack = packList[i];
+				DfsPackFile pack = packs[i];
 				if (noGarbage && pack.isGarbage())
 					continue;
 				try {
 					long p = pack.findOffset(this, t);
 					if (0 < p) {
 						r.add(new FoundObject<T>(t, i, pack, p));
+						it.remove();
 						lastIdx = i;
 						lastPack = pack;
 						continue OBJECT_SCAN;
@@ -294,13 +366,9 @@
 					// Examine other packs.
 				}
 			}
-
-			r.add(new FoundObject<T>(t));
 		}
 
-		Collections.sort(r, FOUND_OBJECT_SORT);
 		last = lastPack;
-		return r;
 	}
 
 	@Override
@@ -418,24 +486,43 @@
 			IOException {
 		if (last != null) {
 			long sz = last.getObjectSize(this, objectId);
-			if (0 <= sz)
+			if (0 <= sz) {
 				return sz;
+			}
 		}
 
-		for (DfsPackFile pack : db.getPacks()) {
-			if (pack == last)
+		PackList packList = db.getPackList();
+		long sz = getObjectSizeImpl(packList, objectId);
+		if (0 <= sz) {
+			return sz;
+		}
+		if (packList.dirty()) {
+			sz = getObjectSizeImpl(packList, objectId);
+			if (0 <= sz) {
+				return sz;
+			}
+		}
+
+		if (typeHint == OBJ_ANY) {
+			throw new MissingObjectException(objectId.copy(),
+					JGitText.get().unknownObjectType2);
+		}
+		throw new MissingObjectException(objectId.copy(), typeHint);
+	}
+
+	private long getObjectSizeImpl(PackList packList, AnyObjectId objectId)
+			throws IOException {
+		for (DfsPackFile pack : packList.packs) {
+			if (pack == last) {
 				continue;
+			}
 			long sz = pack.getObjectSize(this, objectId);
 			if (0 <= sz) {
 				last = pack;
 				return sz;
 			}
 		}
-
-		if (typeHint == OBJ_ANY)
-			throw new MissingObjectException(objectId.copy(),
-					JGitText.get().unknownObjectType2);
-		throw new MissingObjectException(objectId.copy(), typeHint);
+		return -1;
 	}
 
 	public DfsObjectToPack newObjectToPack(AnyObjectId objectId, int type) {
@@ -451,6 +538,8 @@
 	public void selectObjectRepresentation(PackWriter packer,
 			ProgressMonitor monitor, Iterable<ObjectToPack> objects)
 			throws IOException, MissingObjectException {
+		// Don't check dirty bit on PackList; assume ObjectToPacks all came from the
+		// current list.
 		for (DfsPackFile pack : db.getPacks()) {
 			List<DfsObjectToPack> tmp = findAllFromPack(pack, objects);
 			if (tmp.isEmpty())
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsRefDatabase.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsRefDatabase.java
index e5469f6..4ddcec1 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsRefDatabase.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsRefDatabase.java
@@ -217,10 +217,6 @@
 		else
 			detachingSymbolicRef = detach && ref.isSymbolic();
 
-		if (detachingSymbolicRef) {
-			ref = new ObjectIdRef.Unpeeled(NEW, refName, ref.getObjectId());
-		}
-
 		DfsRefUpdate update = new DfsRefUpdate(this, ref);
 		if (detachingSymbolicRef)
 			update.setDetachingSymbolicRef();
@@ -315,6 +311,15 @@
 
 	/**
 	 * Compare a reference, and put if it matches.
+	 * <p>
+	 * Two reference match if and only if they satisfy the following:
+	 *
+	 * <ul>
+	 * <li>If one reference is a symbolic ref, the other one should be a symbolic
+	 * ref.
+	 * <li>If both are symbolic refs, the target names should be same.
+	 * <li>If both are object ID refs, the object IDs should be same.
+	 * </ul>
 	 *
 	 * @param oldRef
 	 *            old value to compare to. If the reference is expected to not
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/InMemoryRepository.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/InMemoryRepository.java
index de18ead..6f390a4 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/InMemoryRepository.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/InMemoryRepository.java
@@ -24,7 +24,6 @@
 import org.eclipse.jgit.lib.Ref;
 import org.eclipse.jgit.lib.Ref.Storage;
 import org.eclipse.jgit.lib.RefDatabase;
-import org.eclipse.jgit.lib.SymbolicRef;
 import org.eclipse.jgit.revwalk.RevObject;
 import org.eclipse.jgit.revwalk.RevTag;
 import org.eclipse.jgit.revwalk.RevWalk;
@@ -310,6 +309,7 @@
 			}
 			ids.sort();
 			sym.sort();
+			objdb.getCurrentPackList().markDirty();
 			return new RefCache(ids.toRefList(), sym.toRefList());
 		}
 
@@ -400,27 +400,8 @@
 					return refs.putIfAbsent(name, newRef) == null;
 
 				Ref cur = refs.get(name);
-				Ref toCompare = cur;
-				if (toCompare != null) {
-					if (toCompare.isSymbolic()) {
-						// Arm's-length dereference symrefs before the compare, since
-						// DfsRefUpdate#doLink(String) stores them undereferenced.
-						Ref leaf = toCompare.getLeaf();
-						if (leaf.getObjectId() == null) {
-							leaf = refs.get(leaf.getName());
-							if (leaf.isSymbolic())
-								// Not supported at the moment.
-								throw new IllegalArgumentException();
-							toCompare = new SymbolicRef(
-									name,
-									new ObjectIdRef.Unpeeled(
-											Storage.NEW,
-											leaf.getName(),
-											leaf.getObjectId()));
-						} else
-							toCompare = toCompare.getLeaf();
-					}
-					if (eq(toCompare, oldRef))
+				if (cur != null) {
+					if (eq(cur, oldRef))
 						return refs.replace(name, cur, newRef);
 				}
 
@@ -451,10 +432,12 @@
 		private boolean eq(Ref a, Ref b) {
 			if (!Objects.equals(a.getName(), b.getName()))
 				return false;
-			// Compare leaf object IDs, since the oldRef passed into compareAndPut
-			// when detaching a symref is an ObjectIdRef.
-			return Objects.equals(a.getLeaf().getObjectId(),
-					b.getLeaf().getObjectId());
+			if (a.isSymbolic() != b.isSymbolic())
+				return false;
+			if (a.isSymbolic())
+				return Objects.equals(a.getTarget().getName(), b.getTarget().getName());
+			else
+				return Objects.equals(a.getObjectId(), b.getObjectId());
 		}
 	}
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileSnapshot.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileSnapshot.java
index e3f1e53..8926d79 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileSnapshot.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileSnapshot.java
@@ -44,6 +44,7 @@
 package org.eclipse.jgit.internal.storage.file;
 
 import java.io.File;
+import java.io.IOException;
 import java.text.DateFormat;
 import java.text.SimpleDateFormat;
 import java.util.Date;
@@ -102,8 +103,13 @@
 	 * @return the snapshot.
 	 */
 	public static FileSnapshot save(File path) {
-		final long read = System.currentTimeMillis();
-		final long modified = path.lastModified();
+		long read = System.currentTimeMillis();
+		long modified;
+		try {
+			modified = FS.DETECTED.lastModified(path);
+		} catch (IOException e) {
+			modified = path.lastModified();
+		}
 		return new FileSnapshot(read, modified);
 	}
 
@@ -153,7 +159,13 @@
 	 * @return true if the path needs to be read again.
 	 */
 	public boolean isModified(File path) {
-		return isModified(path.lastModified());
+		long currLastModified;
+		try {
+			currLastModified = FS.DETECTED.lastModified(path);
+		} catch (IOException e) {
+			currLastModified = path.lastModified();
+		}
+		return isModified(currLastModified);
 	}
 
 	/**
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 c998ebe..a3e9430 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
@@ -189,9 +189,10 @@
 	 * @param oldPacks
 	 * @param newPacks
 	 * @throws ParseException
+	 * @throws IOException
 	 */
 	private void deleteOldPacks(Collection<PackFile> oldPacks,
-			Collection<PackFile> newPacks) throws ParseException {
+			Collection<PackFile> newPacks) throws ParseException, IOException {
 		long packExpireDate = getPackExpireDate();
 		oldPackLoop: for (PackFile oldPack : oldPacks) {
 			String oldName = oldPack.getPackName();
@@ -202,7 +203,8 @@
 					continue oldPackLoop;
 
 			if (!oldPack.shouldBeKept()
-					&& oldPack.getPackFile().lastModified() < packExpireDate) {
+					&& repo.getFS().lastModified(
+							oldPack.getPackFile()) < packExpireDate) {
 				oldPack.close();
 				prunePack(oldName);
 			}
@@ -338,7 +340,7 @@
 						String fName = f.getName();
 						if (fName.length() != Constants.OBJECT_ID_STRING_LENGTH - 2)
 							continue;
-						if (f.lastModified() >= expireDate)
+						if (repo.getFS().lastModified(f) >= expireDate)
 							continue;
 						try {
 							ObjectId id = ObjectId.fromString(d + fName);
@@ -428,9 +430,14 @@
 			return;
 
 		// delete all candidates which have survived: these are unreferenced
-		// loose objects
-		for (File f : deletionCandidates.values())
-			f.delete();
+		// loose objects. Make a last check, though, to avoid deleting objects
+		// that could have been referenced while the candidates list was being
+		// built (by an incoming push, for example).
+		for (File f : deletionCandidates.values()) {
+			if (f.lastModified() < expireDate) {
+				f.delete();
+			}
+		}
 
 		repo.getObjectDatabase().close();
 	}
@@ -889,7 +896,7 @@
 	 * A class holding statistical data for a FileRepository regarding how many
 	 * objects are stored as loose or packed objects
 	 */
-	public class RepoStatistics {
+	public static class RepoStatistics {
 		/**
 		 * The number of objects stored in pack files. If the same object is
 		 * stored in multiple pack files then it is counted as often as it
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LockFile.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LockFile.java
index ce9677a..51af67e 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LockFile.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LockFile.java
@@ -168,7 +168,7 @@
 	 */
 	public boolean lock() throws IOException {
 		FileUtils.mkdirs(lck.getParentFile(), true);
-		if (lck.createNewFile()) {
+		if (FS.DETECTED.createNewFile(lck)) {
 			haveLck = true;
 			try {
 				os = new FileOutputStream(lck);
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 ea80528..6489415 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
@@ -337,6 +337,7 @@
 			for (PackFile p : pList.packs) {
 				try {
 					p.resolve(matches, id, RESOLVE_ABBREV_LIMIT);
+					p.resetTransientErrorCount();
 				} catch (IOException e) {
 					handlePackError(e, p);
 				}
@@ -418,6 +419,7 @@
 				for (PackFile p : pList.packs) {
 					try {
 						ObjectLoader ldr = p.get(curs, objectId);
+						p.resetTransientErrorCount();
 						if (ldr != null)
 							return ldr;
 					} catch (PackMismatchException e) {
@@ -496,6 +498,7 @@
 				for (PackFile p : pList.packs) {
 					try {
 						long len = p.getObjectSize(curs, id);
+						p.resetTransientErrorCount();
 						if (0 <= len)
 							return len;
 					} catch (PackMismatchException e) {
@@ -535,6 +538,7 @@
 			for (final PackFile p : pList.packs) {
 				try {
 					LocalObjectRepresentation rep = p.representation(curs, otp);
+					p.resetTransientErrorCount();
 					if (rep != null)
 						packer.select(otp, rep);
 				} catch (PackMismatchException e) {
@@ -555,6 +559,8 @@
 
 	private void handlePackError(IOException e, PackFile p) {
 		String warnTmpl = null;
+		int transientErrorCount = 0;
+		String errTmpl = JGitText.get().exceptionWhileReadingPack;
 		if ((e instanceof CorruptObjectException)
 				|| (e instanceof PackInvalidException)) {
 			warnTmpl = JGitText.get().corruptPack;
@@ -562,14 +568,17 @@
 			removePack(p);
 		} else if (e instanceof FileNotFoundException) {
 			if (p.getPackFile().exists()) {
-				warnTmpl = JGitText.get().packInaccessible;
+				errTmpl = JGitText.get().packInaccessible;
+				transientErrorCount = p.incrementTransientErrorCount();
 			} else {
 				warnTmpl = JGitText.get().packWasDeleted;
+				removePack(p);
 			}
-			removePack(p);
 		} else if (FileUtils.isStaleFileHandle(e)) {
 			warnTmpl = JGitText.get().packHandleIsStale;
 			removePack(p);
+		} else {
+			transientErrorCount = p.incrementTransientErrorCount();
 		}
 		if (warnTmpl != null) {
 			if (LOG.isDebugEnabled()) {
@@ -580,14 +589,25 @@
 						p.getPackFile().getAbsolutePath()));
 			}
 		} else {
-			// Don't remove the pack from the list, as the error may be
-			// transient.
-			LOG.error(MessageFormat.format(
-					JGitText.get().exceptionWhileReadingPack, p.getPackFile()
-							.getAbsolutePath()), e);
+			if (doLogExponentialBackoff(transientErrorCount)) {
+				// Don't remove the pack from the list, as the error may be
+				// transient.
+				LOG.error(MessageFormat.format(errTmpl,
+						p.getPackFile().getAbsolutePath()),
+						Integer.valueOf(transientErrorCount), e);
+			}
 		}
 	}
 
+	/**
+	 * @param n
+	 *            count of consecutive failures
+	 * @return @{code true} if i is a power of 2
+	 */
+	private boolean doLogExponentialBackoff(int n) {
+		return (n & (n - 1)) == 0;
+	}
+
 	@Override
 	InsertLooseObjectResult insertUnpackedObject(File tmp, ObjectId id,
 			boolean createDuplicate) throws IOException {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackFile.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackFile.java
index b385b8a..b5889f2 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackFile.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackFile.java
@@ -50,17 +50,21 @@
 
 import java.io.EOFException;
 import java.io.File;
+import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InterruptedIOException;
 import java.io.RandomAccessFile;
 import java.nio.MappedByteBuffer;
 import java.nio.channels.FileChannel.MapMode;
+import java.nio.file.AccessDeniedException;
+import java.nio.file.NoSuchFileException;
 import java.text.MessageFormat;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.Iterator;
 import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
 import java.util.zip.CRC32;
 import java.util.zip.DataFormatException;
 import java.util.zip.Inflater;
@@ -68,9 +72,13 @@
 import org.eclipse.jgit.errors.CorruptObjectException;
 import org.eclipse.jgit.errors.LargeObjectException;
 import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.errors.NoPackSignatureException;
 import org.eclipse.jgit.errors.PackInvalidException;
 import org.eclipse.jgit.errors.PackMismatchException;
 import org.eclipse.jgit.errors.StoredObjectRepresentationNotAvailableException;
+import org.eclipse.jgit.errors.UnpackException;
+import org.eclipse.jgit.errors.UnsupportedPackIndexVersionException;
+import org.eclipse.jgit.errors.UnsupportedPackVersionException;
 import org.eclipse.jgit.internal.JGitText;
 import org.eclipse.jgit.internal.storage.pack.BinaryDelta;
 import org.eclipse.jgit.internal.storage.pack.ObjectToPack;
@@ -125,6 +133,8 @@
 
 	private boolean invalidBitmap;
 
+	private AtomicInteger transientErrorCount = new AtomicInteger();
+
 	private byte[] packChecksum;
 
 	private PackIndex loadedIdx;
@@ -568,6 +578,14 @@
 		invalid = true;
 	}
 
+	int incrementTransientErrorCount() {
+		return transientErrorCount.incrementAndGet();
+	}
+
+	void resetTransientErrorCount() {
+		transientErrorCount.set(0);
+	}
+
 	private void readFully(final long position, final byte[] dstbuf,
 			int dstoff, final int cnt, final WindowCursor curs)
 			throws IOException {
@@ -624,15 +642,25 @@
 			// don't invalidate the pack, we are interrupted from another thread
 			openFail(false);
 			throw e;
-		} catch (IOException ioe) {
+		} catch (FileNotFoundException fn) {
+			// don't invalidate the pack if opening an existing file failed
+			// since it may be related to a temporary lack of resources (e.g.
+			// max open files)
+			openFail(!packFile.exists());
+			throw fn;
+		} catch (EOFException | AccessDeniedException | NoSuchFileException
+				| CorruptObjectException | NoPackSignatureException
+				| PackMismatchException | UnpackException
+				| UnsupportedPackIndexVersionException
+				| UnsupportedPackVersionException pe) {
+			// exceptions signaling permanent problems with a pack
 			openFail(true);
-			throw ioe;
-		} catch (RuntimeException re) {
-			openFail(true);
-			throw re;
-		} catch (Error re) {
-			openFail(true);
-			throw re;
+			throw pe;
+		} catch (IOException | RuntimeException ge) {
+			// generic exceptions could be transient so we should not mark the
+			// pack invalid to avoid false MissingObjectExceptions
+			openFail(false);
+			throw ge;
 		}
 	}
 
@@ -699,28 +727,31 @@
 
 		fd.seek(0);
 		fd.readFully(buf, 0, 12);
-		if (RawParseUtils.match(buf, 0, Constants.PACK_SIGNATURE) != 4)
-			throw new IOException(JGitText.get().notAPACKFile);
+		if (RawParseUtils.match(buf, 0, Constants.PACK_SIGNATURE) != 4) {
+			throw new NoPackSignatureException(JGitText.get().notAPACKFile);
+		}
 		final long vers = NB.decodeUInt32(buf, 4);
 		final long packCnt = NB.decodeUInt32(buf, 8);
-		if (vers != 2 && vers != 3)
-			throw new IOException(MessageFormat.format(
-					JGitText.get().unsupportedPackVersion, Long.valueOf(vers)));
+		if (vers != 2 && vers != 3) {
+			throw new UnsupportedPackVersionException(vers);
+		}
 
-		if (packCnt != idx.getObjectCount())
+		if (packCnt != idx.getObjectCount()) {
 			throw new PackMismatchException(MessageFormat.format(
 					JGitText.get().packObjectCountMismatch,
 					Long.valueOf(packCnt), Long.valueOf(idx.getObjectCount()),
 					getPackFile()));
+		}
 
 		fd.seek(length - 20);
 		fd.readFully(buf, 0, 20);
-		if (!Arrays.equals(buf, packChecksum))
+		if (!Arrays.equals(buf, packChecksum)) {
 			throw new PackMismatchException(MessageFormat.format(
 					JGitText.get().packObjectCountMismatch
 					, ObjectId.fromRaw(buf).name()
 					, ObjectId.fromRaw(idx.packChecksum).name()
 					, getPackFile()));
+		}
 	}
 
 	ObjectLoader load(final WindowCursor curs, long pos)
@@ -1074,8 +1105,17 @@
 		if (invalid || invalidBitmap)
 			return null;
 		if (bitmapIdx == null && hasExt(BITMAP_INDEX)) {
-			final PackBitmapIndex idx = PackBitmapIndex.open(
-					extFile(BITMAP_INDEX), idx(), getReverseIdx());
+			final PackBitmapIndex idx;
+			try {
+				idx = PackBitmapIndex.open(extFile(BITMAP_INDEX), idx(),
+						getReverseIdx());
+			} catch (FileNotFoundException e) {
+				// Once upon a time this bitmap file existed. Now it
+				// has been removed. Most likely an external gc  has
+				// removed this packfile and the bitmap
+				 invalidBitmap = true;
+				 return null;
+			}
 
 			// At this point, idx() will have set packChecksum.
 			if (Arrays.equals(packChecksum, idx.packChecksum))
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndex.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndex.java
index f36bd4d..5d4a30f 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndex.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndex.java
@@ -55,6 +55,7 @@
 
 import org.eclipse.jgit.errors.CorruptObjectException;
 import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.errors.UnsupportedPackIndexVersionException;
 import org.eclipse.jgit.internal.JGitText;
 import org.eclipse.jgit.lib.AbbreviatedObjectId;
 import org.eclipse.jgit.lib.AnyObjectId;
@@ -138,9 +139,7 @@
 			case 2:
 				return new PackIndexV2(fd);
 			default:
-				throw new IOException(MessageFormat.format(
-						JGitText.get().unsupportedPackIndexVersion,
-						Integer.valueOf(v)));
+				throw new UnsupportedPackIndexVersionException(v);
 			}
 		}
 		return new PackIndexV1(fd, hdr);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectory.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectory.java
index e5ca736..cd98539 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectory.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectory.java
@@ -80,6 +80,7 @@
 import org.eclipse.jgit.errors.ObjectWritingException;
 import org.eclipse.jgit.events.RefsChangedEvent;
 import org.eclipse.jgit.internal.JGitText;
+import org.eclipse.jgit.lib.ConfigConstants;
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.ObjectIdRef;
@@ -545,8 +546,6 @@
 			ref = new ObjectIdRef.Unpeeled(NEW, name, null);
 		else {
 			detachingSymbolicRef = detach && ref.isSymbolic();
-			if (detachingSymbolicRef)
-				ref = new ObjectIdRef.Unpeeled(LOOSE, name, ref.getObjectId());
 		}
 		RefDirectoryUpdate refDirUpdate = new RefDirectoryUpdate(this, ref);
 		if (detachingSymbolicRef)
@@ -579,7 +578,7 @@
 	}
 
 	void delete(RefDirectoryUpdate update) throws IOException {
-		Ref dst = update.getRef().getLeaf();
+		Ref dst = update.getRef();
 		String name = dst.getName();
 
 		// Write the packed-refs file using an atomic update. We might
@@ -767,14 +766,20 @@
 	}
 
 	private PackedRefList getPackedRefs() throws IOException {
+		boolean trustFolderStat = getRepository().getConfig().getBoolean(
+				ConfigConstants.CONFIG_CORE_SECTION,
+				ConfigConstants.CONFIG_KEY_TRUSTFOLDERSTAT, true);
+
 		final PackedRefList curList = packedRefs.get();
-		if (!curList.snapshot.isModified(packedRefsFile))
+		if (trustFolderStat && !curList.snapshot.isModified(packedRefsFile)) {
 			return curList;
+		}
 
 		final PackedRefList newList = readPackedRefs();
 		if (packedRefs.compareAndSet(curList, newList)
-				&& !curList.id.equals(newList.id))
+				&& !curList.id.equals(newList.id)) {
 			modCnt.incrementAndGet();
+		}
 		return newList;
 	}
 
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectoryUpdate.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectoryUpdate.java
index 0d16f79..3c1916b 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectoryUpdate.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectoryUpdate.java
@@ -56,6 +56,7 @@
 class RefDirectoryUpdate extends RefUpdate {
 	private final RefDirectory database;
 
+	private boolean shouldDeref;
 	private LockFile lock;
 
 	RefDirectoryUpdate(final RefDirectory r, final Ref ref) {
@@ -75,6 +76,7 @@
 
 	@Override
 	protected boolean tryLock(boolean deref) throws IOException {
+		shouldDeref = deref;
 		Ref dst = getRef();
 		if (deref)
 			dst = dst.getLeaf();
@@ -117,7 +119,7 @@
 						msg = strResult;
 				}
 			}
-			database.log(this, msg, true);
+			database.log(this, msg, shouldDeref);
 		}
 		if (!lock.commit())
 			return Result.LOCK_FAILURE;
@@ -140,7 +142,7 @@
 
 	@Override
 	protected Result doDelete(final Result status) throws IOException {
-		if (getRef().getLeaf().getStorage() != Ref.Storage.NEW)
+		if (getRef().getStorage() != Ref.Storage.NEW)
 			database.delete(this);
 		return status;
 	}
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 525f9ae..691867a 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
@@ -564,7 +564,8 @@
 	 * Configure this pack for a shallow clone.
 	 *
 	 * @param depth
-	 *            maximum depth to traverse the commit graph
+	 *            maximum depth of history to return. 1 means return only the
+	 *            "wants".
 	 * @param unshallow
 	 *            objects which used to be shallow on the client, but are being
 	 *            extended as part of this fetch
@@ -709,11 +710,52 @@
 	public void preparePack(ProgressMonitor countingMonitor,
 			@NonNull Set<? extends ObjectId> want,
 			@NonNull Set<? extends ObjectId> have) throws IOException {
+		preparePack(countingMonitor,
+				want, have, Collections.<ObjectId> emptySet());
+	}
+
+	/**
+	 * 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}.
+	 * @throws IOException
+	 *            an I/O problem occured while reading objects.
+	 *
+	 * @since 4.5
+	 */
+	public void preparePack(ProgressMonitor countingMonitor,
+			@NonNull Set<? extends ObjectId> want,
+			@NonNull Set<? extends ObjectId> have,
+			@NonNull Set<? extends ObjectId> shallow) throws IOException {
 		ObjectWalk ow;
-		if (shallowPack)
-			ow = new DepthWalk.ObjectWalk(reader, depth);
-		else
+		if (shallowPack) {
+			ow = new DepthWalk.ObjectWalk(reader, depth - 1);
+		} else {
 			ow = new ObjectWalk(reader);
+		}
+		ow.assumeShallow(shallow);
 		preparePack(countingMonitor, ow, want, have);
 	}
 
@@ -752,7 +794,8 @@
 		if (countingMonitor == null)
 			countingMonitor = NullProgressMonitor.INSTANCE;
 		if (shallowPack && !(walk instanceof DepthWalk.ObjectWalk))
-			walk = new DepthWalk.ObjectWalk(reader, depth);
+			throw new IllegalArgumentException(
+					JGitText.get().shallowPacksRequireDepthWalk);
 		findObjectsToPack(countingMonitor, walk, interestingObjects,
 				uninterestingObjects);
 	}
@@ -1653,6 +1696,8 @@
 		List<RevObject> haveObjs = new ArrayList<RevObject>(haveEst);
 		List<RevTag> wantTags = new ArrayList<RevTag>(want.size());
 
+		// Retrieve the RevWalk's versions of "want" and "have" objects to
+		// maintain any state previously set in the RevWalk.
 		AsyncRevObjectQueue q = walker.parseAny(all, true);
 		try {
 			for (;;) {
@@ -1695,11 +1740,25 @@
 
 		if (walker instanceof DepthWalk.ObjectWalk) {
 			DepthWalk.ObjectWalk depthWalk = (DepthWalk.ObjectWalk) walker;
-			for (RevObject obj : wantObjs)
+			for (RevObject obj : wantObjs) {
 				depthWalk.markRoot(obj);
+			}
+			// Mark the tree objects associated with "have" commits as
+			// uninteresting to avoid writing redundant blobs. A normal RevWalk
+			// lazily propagates the "uninteresting" state from a commit to its
+			// tree during the walk, but DepthWalks can terminate early so
+			// preemptively propagate that state here.
+			for (RevObject obj : haveObjs) {
+				if (obj instanceof RevCommit) {
+					RevTree t = ((RevCommit) obj).getTree();
+					depthWalk.markUninteresting(t);
+				}
+			}
+
 			if (unshallowObjects != null) {
-				for (ObjectId id : unshallowObjects)
+				for (ObjectId id : unshallowObjects) {
 					depthWalk.markUnshallow(walker.parseAny(id));
+				}
 			}
 		} else {
 			for (RevObject obj : wantObjs)
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BatchRefUpdate.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BatchRefUpdate.java
index 35cadd3..8550ec3 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BatchRefUpdate.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BatchRefUpdate.java
@@ -92,6 +92,9 @@
 	/** Whether updates should be atomic. */
 	private boolean atomic;
 
+	/** Push options associated with this update. */
+	private List<String> pushOptions;
+
 	/**
 	 * Initialize a new batch update.
 	 *
@@ -301,27 +304,40 @@
 	}
 
 	/**
+	 * Gets the list of option strings associated with this update.
+	 *
+	 * @return pushOptions
+	 * @since 4.5
+	 */
+	public List<String> getPushOptions() {
+		return pushOptions;
+	}
+
+	/**
 	 * Execute this batch update.
 	 * <p>
 	 * The default implementation of this method performs a sequential reference
 	 * update over each reference.
 	 * <p>
 	 * Implementations must respect the atomicity requirements of the underlying
-	 * database as described in {@link #setAtomic(boolean)} and {@link
-	 * RefDatabase#performsAtomicTransactions()}.
+	 * database as described in {@link #setAtomic(boolean)} and
+	 * {@link RefDatabase#performsAtomicTransactions()}.
 	 *
 	 * @param walk
 	 *            a RevWalk to parse tags in case the storage system wants to
 	 *            store them pre-peeled, a common performance optimization.
 	 * @param monitor
 	 *            progress monitor to receive update status on.
+	 * @param options
+	 *            a list of option strings; set null to execute without
 	 * @throws IOException
 	 *             the database is unable to accept the update. Individual
 	 *             command status must be tested to determine if there is a
 	 *             partial failure, or a total failure.
+	 * @since 4.5
 	 */
-	public void execute(RevWalk walk, ProgressMonitor monitor)
-			throws IOException {
+	public void execute(RevWalk walk, ProgressMonitor monitor,
+			List<String> options) throws IOException {
 
 		if (atomic && !refdb.performsAtomicTransactions()) {
 			for (ReceiveCommand c : commands) {
@@ -333,10 +349,13 @@
 			return;
 		}
 
+		if (options != null) {
+			pushOptions = options;
+		}
+
 		monitor.beginTask(JGitText.get().updatingReferences, commands.size());
 		List<ReceiveCommand> commands2 = new ArrayList<ReceiveCommand>(
 				commands.size());
-		List<String> namesToCheck = new ArrayList<String>(commands.size());
 		// First delete refs. This may free the name space for some of the
 		// updates.
 		for (ReceiveCommand cmd : commands) {
@@ -345,7 +364,6 @@
 					cmd.updateType(walk);
 					switch (cmd.getType()) {
 					case CREATE:
-						namesToCheck.add(cmd.getRefName());
 						commands2.add(cmd);
 						break;
 					case UPDATE:
@@ -414,6 +432,24 @@
 		monitor.endTask();
 	}
 
+	/**
+	 * Execute this batch update without option strings.
+	 *
+	 * @param walk
+	 *            a RevWalk to parse tags in case the storage system wants to
+	 *            store them pre-peeled, a common performance optimization.
+	 * @param monitor
+	 *            progress monitor to receive update status on.
+	 * @throws IOException
+	 *             the database is unable to accept the update. Individual
+	 *             command status must be tested to determine if there is a
+	 *             partial failure, or a total failure.
+	 */
+	public void execute(RevWalk walk, ProgressMonitor monitor)
+			throws IOException {
+		execute(walk, monitor, null);
+	}
+
 	private static Collection<String> getTakenPrefixes(
 			final Collection<String> names) {
 		Collection<String> ref = new HashSet<String>();
@@ -487,7 +523,11 @@
 		for (ReceiveCommand cmd : commands) {
 			r.append("  "); //$NON-NLS-1$
 			r.append(cmd);
-			r.append("  (").append(cmd.getResult()).append(")\n"); //$NON-NLS-1$ //$NON-NLS-2$
+			r.append("  (").append(cmd.getResult()); //$NON-NLS-1$
+			if (cmd.getMessage() != null) {
+				r.append(": ").append(cmd.getMessage()); //$NON-NLS-1$
+			}
+			r.append(")\n"); //$NON-NLS-1$
 		}
 		return r.append(']').toString();
 	}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BranchConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BranchConfig.java
index a62f6c3..f1b7fb2 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BranchConfig.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BranchConfig.java
@@ -55,6 +55,39 @@
 public class BranchConfig {
 
 	/**
+	 * Config values for branch.[name].rebase (and pull.rebase).
+	 *
+	 * @since 4.5
+	 */
+	public enum BranchRebaseMode implements Config.ConfigEnum {
+
+		/** Value for rebasing */
+		REBASE("true"), //$NON-NLS-1$
+		/** Value for rebasing preserving local merge commits */
+		PRESERVE("preserve"), //$NON-NLS-1$
+		/** Value for rebasing interactively */
+		INTERACTIVE("interactive"), //$NON-NLS-1$
+		/** Value for not rebasing at all but merging */
+		NONE("false"); //$NON-NLS-1$
+
+		private final String configValue;
+
+		private BranchRebaseMode(String configValue) {
+			this.configValue = configValue;
+		}
+
+		@Override
+		public String toConfigValue() {
+			return configValue;
+		}
+
+		@Override
+		public boolean matchConfigValue(String s) {
+			return configValue.equals(s);
+		}
+	}
+
+	/**
 	 * The value that means "local repository" for {@link #getRemote()}:
 	 * {@value}
 	 *
@@ -143,8 +176,19 @@
 	 * @since 3.5
 	 */
 	public boolean isRebase() {
-		return config.getBoolean(ConfigConstants.CONFIG_BRANCH_SECTION,
-				branchName, ConfigConstants.CONFIG_KEY_REBASE, false);
+		return getRebaseMode() != BranchRebaseMode.NONE;
+	}
+
+	/**
+	 * Retrieves the config value of branch.[name].rebase.
+	 *
+	 * @return the {@link BranchRebaseMode}
+	 * @since 4.5
+	 */
+	public BranchRebaseMode getRebaseMode() {
+		return config.getEnum(BranchRebaseMode.values(),
+				ConfigConstants.CONFIG_BRANCH_SECTION, branchName,
+				ConfigConstants.CONFIG_KEY_REBASE, BranchRebaseMode.NONE);
 	}
 
 	/**
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Config.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Config.java
index 1e1d147..8fe8a96 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Config.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Config.java
@@ -510,7 +510,7 @@
 	 *            indication of the units.
 	 * @return the value, or {@code defaultValue} if not set, expressed in
 	 *         {@code units}.
-	 * @since 4.4
+	 * @since 4.5
 	 */
 	public long getTimeUnit(String section, String subsection, String name,
 			long defaultValue, TimeUnit wantUnit) {
@@ -835,11 +835,11 @@
 		final String s;
 
 		if (value >= GiB && (value % GiB) == 0)
-			s = String.valueOf(value / GiB) + " g"; //$NON-NLS-1$
+			s = String.valueOf(value / GiB) + "g"; //$NON-NLS-1$
 		else if (value >= MiB && (value % MiB) == 0)
-			s = String.valueOf(value / MiB) + " m"; //$NON-NLS-1$
+			s = String.valueOf(value / MiB) + "m"; //$NON-NLS-1$
 		else if (value >= KiB && (value % KiB) == 0)
-			s = String.valueOf(value / KiB) + " k"; //$NON-NLS-1$
+			s = String.valueOf(value / KiB) + "k"; //$NON-NLS-1$
 		else
 			s = String.valueOf(value);
 
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java
index 9e3e0b7..e3f8ba5 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java
@@ -290,6 +290,13 @@
 	public static final String CONFIG_KEY_TRUSTFOLDERSTAT = "trustfolderstat";
 
 	/**
+	 * The "supportsAtomicFileCreation" key in the "core section"
+	 *
+	 * @since 4.5
+	 */
+	public static final String CONFIG_KEY_SUPPORTSATOMICFILECREATION = "supportsatomicfilecreation";
+
+	/**
 	 * The "noprefix" key in the "diff section"
 	 * @since 3.0
 	 */
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefUpdate.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefUpdate.java
index c1027f0..fc334f0 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefUpdate.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefUpdate.java
@@ -551,7 +551,9 @@
 	 * @throws IOException
 	 */
 	public Result delete(final RevWalk walk) throws IOException {
-		final String myName = getRef().getLeaf().getName();
+		final String myName = detachingSymbolicRef
+				? getRef().getName()
+				: getRef().getLeaf().getName();
 		if (myName.startsWith(Constants.R_HEADS) && !getRepository().isBare()) {
 			// Don't allow the currently checked out branch to be deleted.
 			Ref head = getRefDatabase().getRef(Constants.HEAD);
@@ -628,7 +630,10 @@
 		if (oldValue == null && checkConflicting && getRefDatabase().isNameConflicting(getName()))
 			return Result.LOCK_FAILURE;
 		try {
-			if (!tryLock(true))
+			// If we're detaching a symbolic reference, we should update the reference
+			// itself. Otherwise, we will update the leaf reference, which should be
+			// an ObjectIdRef.
+			if (!tryLock(!detachingSymbolicRef))
 				return Result.LOCK_FAILURE;
 			if (expValue != null) {
 				final ObjectId o;
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 7ec2499..aba5242 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java
@@ -880,10 +880,11 @@
 		} else if (newCount == -1) {
 			// should not happen, only log when useCnt became negative to
 			// minimize number of log entries
-			LOG.warn(JGitText.get().corruptUseCnt);
 			if (LOG.isDebugEnabled()) {
 				IllegalStateException e = new IllegalStateException();
-				LOG.debug("", e); //$NON-NLS-1$
+				LOG.debug(JGitText.get().corruptUseCnt, e);
+			} else {
+				LOG.warn(JGitText.get().corruptUseCnt);
 			}
 			if (RepositoryCache.isCached(this)) {
 				closedAt.set(System.currentTimeMillis());
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 29ef084..7a8d246 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryCache.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryCache.java
@@ -298,18 +298,16 @@
 	}
 
 	private boolean isExpired(Repository db) {
-		return db != null && db.useCnt.get() == 0
+		return db != null && db.useCnt.get() <= 0
 			&& (System.currentTimeMillis() - db.closedAt.get() > expireAfter);
 	}
 
 	private void unregisterAndCloseRepository(final Key location,
 			Repository db) {
 		synchronized (lockFor(location)) {
-			if (isExpired(db)) {
-				Repository oldDb = unregisterRepository(location);
-				if (oldDb != null) {
-					oldDb.close();
-				}
+			Repository oldDb = unregisterRepository(location);
+			if (oldDb != null) {
+				oldDb.doClose();
 			}
 		}
 	}
@@ -328,15 +326,9 @@
 	}
 
 	private void clearAll() {
-		for (int stage = 0; stage < 2; stage++) {
-			for (Iterator<Map.Entry<Key, Reference<Repository>>> i = cacheMap
-					.entrySet().iterator(); i.hasNext();) {
-				final Map.Entry<Key, Reference<Repository>> e = i.next();
-				final Repository db = e.getValue().get();
-				if (db != null)
-					db.close();
-				i.remove();
-			}
+		for (Iterator<Map.Entry<Key, Reference<Repository>>> i = cacheMap
+				.entrySet().iterator(); i.hasNext();) {
+			unregisterAndCloseRepository(i.next().getKey(), null);
 		}
 	}
 
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeFormatter.java b/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeFormatter.java
index 977f953..ee6095a 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeFormatter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeFormatter.java
@@ -102,6 +102,7 @@
 	 *            metadata
 	 * @throws IOException
 	 */
+	@SuppressWarnings("unchecked")
 	public void formatMerge(OutputStream out, MergeResult res, String baseName,
 			String oursName, String theirsName, String charsetName) throws IOException {
 		List<String> names = new ArrayList<String>(3);
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 e224d71..fd5e7ef 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java
@@ -761,7 +761,7 @@
 				: FileMode.fromBits(newMode));
 		if (mergedFile != null) {
 			long len = mergedFile.length();
-			dce.setLastModified(mergedFile.lastModified());
+			dce.setLastModified(FS.DETECTED.lastModified(mergedFile));
 			dce.setLength((int) len);
 			InputStream is = new FileInputStream(mergedFile);
 			try {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/DepthWalk.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/DepthWalk.java
index 89d05db..59a360f 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/DepthWalk.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/DepthWalk.java
@@ -149,6 +149,17 @@
 		public RevFlag getReinterestingFlag() {
 			return REINTERESTING;
 		}
+
+		/**
+		 * @since 4.5
+		 */
+		@Override
+		public ObjectWalk toObjectWalkWithSameObjects() {
+			ObjectWalk ow = new ObjectWalk(reader, depth);
+			ow.objects = objects;
+			ow.freeFlags = freeFlags;
+			return ow;
+		}
 	}
 
 	/** Subclass of ObjectWalk that performs depth filtering. */
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevCommit.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevCommit.java
index e67ada6..1e91006 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevCommit.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevCommit.java
@@ -244,7 +244,7 @@
 					if ((p.flags & carry) == carry)
 						continue;
 					p.flags |= carry;
-					FIFORevQueue q = carryFlags1(c, carry, depth + 1);
+					FIFORevQueue q = carryFlags1(p, carry, depth + 1);
 					if (q != null)
 						return defer(q, carry, pList, i + 1);
 				}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevWalk.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevWalk.java
index c850493..a7f7cd4 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevWalk.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevWalk.java
@@ -172,7 +172,7 @@
 
 	ObjectIdOwnerMap<RevObject> objects;
 
-	private int freeFlags = APP_FLAGS;
+	int freeFlags = APP_FLAGS;
 
 	private int delayFreeFlags;
 
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileBasedConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileBasedConfig.java
index 4b4822c..9a38846 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileBasedConfig.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileBasedConfig.java
@@ -65,13 +65,19 @@
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.StoredConfig;
 import org.eclipse.jgit.util.FS;
+import org.eclipse.jgit.util.FileUtils;
 import org.eclipse.jgit.util.IO;
 import org.eclipse.jgit.util.RawParseUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 /**
  * The configuration file that is stored in the file of the file system.
  */
 public class FileBasedConfig extends StoredConfig {
+	private final static Logger LOG = LoggerFactory
+			.getLogger(FileBasedConfig.class);
+
 	private final File configFile;
 
 	private boolean utf8Bom;
@@ -135,41 +141,58 @@
 	 */
 	@Override
 	public void load() throws IOException, ConfigInvalidException {
-		final FileSnapshot oldSnapshot = snapshot;
-		final FileSnapshot newSnapshot = FileSnapshot.save(getFile());
-		try {
-			final byte[] in = IO.readFully(getFile());
-			final ObjectId newHash = hash(in);
-			if (hash.equals(newHash)) {
-				if (oldSnapshot.equals(newSnapshot))
-					oldSnapshot.setClean(newSnapshot);
-				else
-					snapshot = newSnapshot;
-			} else {
-				final String decoded;
-				if (isUtf8(in)) {
-					decoded = RawParseUtils.decode(RawParseUtils.UTF8_CHARSET,
-							in, 3, in.length);
-					utf8Bom = true;
+		final int maxStaleRetries = 5;
+		int retries = 0;
+		while (true) {
+			final FileSnapshot oldSnapshot = snapshot;
+			final FileSnapshot newSnapshot = FileSnapshot.save(getFile());
+			try {
+				final byte[] in = IO.readFully(getFile());
+				final ObjectId newHash = hash(in);
+				if (hash.equals(newHash)) {
+					if (oldSnapshot.equals(newSnapshot)) {
+						oldSnapshot.setClean(newSnapshot);
+					} else {
+						snapshot = newSnapshot;
+					}
 				} else {
-					decoded = RawParseUtils.decode(in);
+					final String decoded;
+					if (isUtf8(in)) {
+						decoded = RawParseUtils.decode(
+								RawParseUtils.UTF8_CHARSET, in, 3, in.length);
+						utf8Bom = true;
+					} else {
+						decoded = RawParseUtils.decode(in);
+					}
+					fromText(decoded);
+					snapshot = newSnapshot;
+					hash = newHash;
 				}
-				fromText(decoded);
+				return;
+			} catch (FileNotFoundException noFile) {
+				if (configFile.exists()) {
+					throw noFile;
+				}
+				clear();
 				snapshot = newSnapshot;
-				hash = newHash;
+				return;
+			} catch (IOException e) {
+				if (FileUtils.isStaleFileHandle(e)
+						&& retries < maxStaleRetries) {
+					if (LOG.isDebugEnabled()) {
+						LOG.debug(MessageFormat.format(
+								JGitText.get().configHandleIsStale,
+								Integer.valueOf(retries)), e);
+					}
+					retries++;
+					continue;
+				}
+				throw new IOException(MessageFormat
+						.format(JGitText.get().cannotReadFile, getFile()), e);
+			} catch (ConfigInvalidException e) {
+				throw new ConfigInvalidException(MessageFormat
+						.format(JGitText.get().cannotReadFile, getFile()), e);
 			}
-		} catch (FileNotFoundException noFile) {
-			if (configFile.exists()) {
-				throw noFile;
-			}
-			clear();
-			snapshot = newSnapshot;
-		} catch (IOException e) {
-			final IOException e2 = new IOException(MessageFormat.format(JGitText.get().cannotReadFile, getFile()));
-			e2.initCause(e);
-			throw e2;
-		} catch (ConfigInvalidException e) {
-			throw new ConfigInvalidException(MessageFormat.format(JGitText.get().cannotReadFile, getFile()), e);
 		}
 	}
 
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackPushConnection.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackPushConnection.java
index 9b8ba80..86cc484 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackPushConnection.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackPushConnection.java
@@ -47,10 +47,12 @@
 import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_ATOMIC;
 
 import java.io.IOException;
+import java.io.InputStream;
 import java.io.OutputStream;
 import java.text.MessageFormat;
 import java.util.Collection;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
@@ -112,14 +114,24 @@
 	 */
 	public static final String CAPABILITY_SIDE_BAND_64K = GitProtocolConstants.CAPABILITY_SIDE_BAND_64K;
 
+	/**
+	 * The server supports the receiving of push options.
+	 * @since 4.5
+	 */
+	public static final String CAPABILITY_PUSH_OPTIONS = GitProtocolConstants.CAPABILITY_PUSH_OPTIONS;
+
 	private final boolean thinPack;
 	private final boolean atomic;
 
+	/** A list of option strings associated with this push. */
+	private List<String> pushOptions;
+
 	private boolean capableAtomic;
 	private boolean capableDeleteRefs;
 	private boolean capableReport;
 	private boolean capableSideBand;
 	private boolean capableOfsDelta;
+	private boolean capablePushOptions;
 
 	private boolean sentCommand;
 	private boolean writePack;
@@ -137,6 +149,7 @@
 		super(packTransport);
 		thinPack = transport.isPushThin();
 		atomic = transport.isPushAtomic();
+		pushOptions = transport.getPushOptions();
 	}
 
 	public void push(final ProgressMonitor monitor,
@@ -196,6 +209,9 @@
 			OutputStream outputStream) throws TransportException {
 		try {
 			writeCommands(refUpdates.values(), monitor, outputStream);
+
+			if (pushOptions != null && capablePushOptions)
+				transmitOptions();
 			if (writePack)
 				writePack(refUpdates, monitor);
 			if (sentCommand) {
@@ -231,6 +247,12 @@
 					JGitText.get().atomicPushNotSupported);
 		}
 
+		if (pushOptions != null && !capablePushOptions) {
+			throw new TransportException(uri,
+					MessageFormat.format(JGitText.get().pushOptionsNotSupported,
+							pushOptions.toString()));
+		}
+
 		for (final RemoteRefUpdate rru : refUpdates) {
 			if (!capableDeleteRefs && rru.isDelete()) {
 				rru.setStatus(Status.REJECTED_NODELETE);
@@ -268,6 +290,14 @@
 		outNeedsEnd = false;
 	}
 
+	private void transmitOptions() throws IOException {
+		for (final String pushOption : pushOptions) {
+			pckOut.writeString(pushOption);
+		}
+
+		pckOut.end();
+	}
+
 	private String enableCapabilities(final ProgressMonitor monitor,
 			OutputStream outputStream) {
 		final StringBuilder line = new StringBuilder();
@@ -277,6 +307,10 @@
 		capableDeleteRefs = wantCapability(line, CAPABILITY_DELETE_REFS);
 		capableOfsDelta = wantCapability(line, CAPABILITY_OFS_DELTA);
 
+		if (pushOptions != null) {
+			capablePushOptions = wantCapability(line, CAPABILITY_PUSH_OPTIONS);
+		}
+
 		capableSideBand = wantCapability(line, CAPABILITY_SIDE_BAND_64K);
 		if (capableSideBand) {
 			in = new SideBandInputStream(in, monitor, getMessageWriter(),
@@ -317,7 +351,12 @@
 			writer.setReuseValidatingObjects(false);
 			writer.setDeltaBaseAsOffset(capableOfsDelta);
 			writer.preparePack(monitor, newObjects, remoteObjects);
-			writer.writePack(monitor, monitor, out);
+
+			OutputStream packOut = out;
+			if (capableSideBand) {
+				packOut = new CheckingSideBandOutputStream(in, out);
+			}
+			writer.writePack(monitor, monitor, packOut);
 
 			packTransferTime = writer.getStatistics().getTimeWriting();
 		}
@@ -327,7 +366,8 @@
 			throws IOException {
 		final String unpackLine = readStringLongTimeout();
 		if (!unpackLine.startsWith("unpack ")) //$NON-NLS-1$
-			throw new PackProtocolException(uri, MessageFormat.format(JGitText.get().unexpectedReportLine, unpackLine));
+			throw new PackProtocolException(uri, MessageFormat
+					.format(JGitText.get().unexpectedReportLine, unpackLine));
 		final String unpackStatus = unpackLine.substring("unpack ".length()); //$NON-NLS-1$
 		if (unpackStatus.startsWith("error Pack exceeds the limit of")) {//$NON-NLS-1$
 			throw new TooLargePackException(uri,
@@ -397,4 +437,58 @@
 			timeoutIn.setTimeout(oldTimeout);
 		}
 	}
+
+	/**
+	 * Gets the list of option strings associated with this push.
+	 *
+	 * @return pushOptions
+	 * @since 4.5
+	 */
+	public List<String> getPushOptions() {
+		return pushOptions;
+	}
+
+	private static class CheckingSideBandOutputStream extends OutputStream {
+		private final InputStream in;
+		private final OutputStream out;
+
+		CheckingSideBandOutputStream(InputStream in, OutputStream out) {
+			this.in = in;
+			this.out = out;
+		}
+
+		@Override
+		public void write(int b) throws IOException {
+			write(new byte[] { (byte) b });
+		}
+
+		@Override
+		public void write(byte[] buf, int ptr, int cnt) throws IOException {
+			try {
+				out.write(buf, ptr, cnt);
+			} catch (IOException e) {
+				throw checkError(e);
+			}
+		}
+
+		@Override
+		public void flush() throws IOException {
+			try {
+				out.flush();
+			} catch (IOException e) {
+				throw checkError(e);
+			}
+		}
+
+		private IOException checkError(IOException e1) {
+			try {
+				in.read();
+			} catch (TransportException e2) {
+				return e2;
+			} catch (IOException e2) {
+				return e1;
+			}
+			return e1;
+		}
+	}
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseReceivePack.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseReceivePack.java
index b5f9e2f..0724eac 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseReceivePack.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseReceivePack.java
@@ -46,11 +46,13 @@
 import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_ATOMIC;
 import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_DELETE_REFS;
 import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_OFS_DELTA;
+import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_PUSH_OPTIONS;
 import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_QUIET;
 import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_REPORT_STATUS;
 import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_SIDE_BAND_64K;
 import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_AGENT;
 import static org.eclipse.jgit.transport.SideBandOutputStream.CH_DATA;
+import static org.eclipse.jgit.transport.SideBandOutputStream.CH_ERROR;
 import static org.eclipse.jgit.transport.SideBandOutputStream.CH_PROGRESS;
 import static org.eclipse.jgit.transport.SideBandOutputStream.MAX_BUF;
 
@@ -67,6 +69,7 @@
 import java.util.Set;
 import java.util.concurrent.TimeUnit;
 
+import org.eclipse.jgit.errors.InvalidObjectIdException;
 import org.eclipse.jgit.errors.MissingObjectException;
 import org.eclipse.jgit.errors.PackProtocolException;
 import org.eclipse.jgit.errors.TooLargePackException;
@@ -176,6 +179,9 @@
 	/** Should an incoming transfer permit non-fast-forward requests? */
 	private boolean allowNonFastForwards;
 
+	/** Should an incoming transfer permit push options? **/
+	private boolean allowPushOptions;
+
 	/**
 	 * Should the requested ref updates be performed as a single atomic
 	 * transaction?
@@ -214,6 +220,7 @@
 
 	/** Optional message output stream. */
 	protected OutputStream msgOut;
+	private SideBandOutputStream errOut;
 
 	/** Packet line input stream around {@link #rawIn}. */
 	protected PacketLineIn pckIn;
@@ -308,6 +315,7 @@
 		allowBranchDeletes = rc.allowDeletes;
 		allowNonFastForwards = rc.allowNonFastForwards;
 		allowOfsDelta = rc.allowOfsDelta;
+		allowPushOptions = rc.allowPushOptions;
 		advertiseRefsHook = AdvertiseRefsHook.DEFAULT;
 		refFilter = RefFilter.DEFAULT;
 		advertisedHaves = new HashSet<ObjectId>();
@@ -327,6 +335,8 @@
 		final boolean allowDeletes;
 		final boolean allowNonFastForwards;
 		final boolean allowOfsDelta;
+		final boolean allowPushOptions;
+
 		final SignedPushConfig signedPush;
 
 		ReceiveConfig(final Config config) {
@@ -336,6 +346,8 @@
 					"denynonfastforwards", false); //$NON-NLS-1$
 			allowOfsDelta = config.getBoolean("repack", "usedeltabaseoffset", //$NON-NLS-1$ //$NON-NLS-2$
 					true);
+			allowPushOptions = config.getBoolean("receive", "pushoptions", //$NON-NLS-1$ //$NON-NLS-2$
+					false);
 			signedPush = SignedPushConfig.KEY.parse(config);
 		}
 	}
@@ -757,8 +769,7 @@
 	 *             read.
 	 */
 	public boolean isSideBand() throws RequestNotYetReadException {
-		if (enabledCapabilities == null)
-			throw new RequestNotYetReadException();
+		checkRequestWasRead();
 		return enabledCapabilities.contains(CAPABILITY_SIDE_BAND_64K);
 	}
 
@@ -785,6 +796,25 @@
 	}
 
 	/**
+	 * @return true if the server supports receiving push options.
+	 * @since 4.5
+	 */
+	public boolean isAllowPushOptions() {
+		return allowPushOptions;
+	}
+
+	/**
+	 * Configure if the server supports receiving push options.
+	 *
+	 * @param allow
+	 *            true to optionally accept option strings from the client.
+	 * @since 4.5
+	 */
+	public void setAllowPushOptions(boolean allow) {
+		allowPushOptions = allow;
+	}
+
+	/**
 	 * True if the client wants less verbose output.
 	 *
 	 * @return true if the client has requested the server to be less verbose.
@@ -796,8 +826,7 @@
 	 * @since 4.0
 	 */
 	public boolean isQuiet() throws RequestNotYetReadException {
-		if (enabledCapabilities == null)
-			throw new RequestNotYetReadException();
+		checkRequestWasRead();
 		return quiet;
 	}
 
@@ -878,6 +907,19 @@
 		}
 	}
 
+	private void fatalError(String msg) {
+		if (errOut != null) {
+			try {
+				errOut.write(Constants.encode(msg));
+				errOut.flush();
+			} catch (IOException e) {
+				// Ignore write failures
+			}
+		} else {
+			sendError(msg);
+		}
+	}
+
 	/**
 	 * Send a message to the client, if it supports receiving them.
 	 * <p>
@@ -1060,6 +1102,9 @@
 			adv.advertiseCapability(CAPABILITY_ATOMIC);
 		if (allowOfsDelta)
 			adv.advertiseCapability(CAPABILITY_OFS_DELTA);
+		if (allowPushOptions) {
+			adv.advertiseCapability(CAPABILITY_PUSH_OPTIONS);
+		}
 		adv.advertiseCapability(OPTION_AGENT, UserAgent.get());
 		adv.send(getAdvertisedOrDefaultRefs());
 		for (ObjectId obj : advertisedHaves)
@@ -1076,7 +1121,7 @@
 	 */
 	protected void recvCommands() throws IOException {
 		PushCertificateParser certParser = getPushCertificateParser();
-		FirstLine firstLine = null;
+		boolean firstPkt = true;
 		try {
 			for (;;) {
 				String line;
@@ -1092,14 +1137,16 @@
 				}
 
 				if (line.length() >= 48 && line.startsWith("shallow ")) { //$NON-NLS-1$
-					clientShallowCommits.add(ObjectId.fromString(line.substring(8, 48)));
+					parseShallow(line.substring(8, 48));
 					continue;
 				}
 
-				if (firstLine == null) {
-					firstLine = new FirstLine(line);
+				if (firstPkt) {
+					firstPkt = false;
+					FirstLine firstLine = new FirstLine(line);
 					enabledCapabilities = firstLine.getCapabilities();
 					line = firstLine.getLine();
+					enableCapabilities();
 
 					if (line.equals(GitProtocolConstants.OPTION_PUSH_CERT)) {
 						certParser.receiveHeader(pckIn, !isBiDirectionalPipe());
@@ -1112,13 +1159,7 @@
 					continue;
 				}
 
-				ReceiveCommand cmd;
-				try {
-					cmd = parseCommand(line);
-				} catch (PackProtocolException e) {
-					sendError(e.getMessage());
-					throw e;
-				}
+				ReceiveCommand cmd = parseCommand(line);
 				if (cmd.getRefName().equals(Constants.HEAD)) {
 					cmd.setResult(Result.REJECTED_CURRENT_BRANCH);
 				} else {
@@ -1130,12 +1171,32 @@
 				}
 			}
 			pushCert = certParser.build();
+			if (hasCommands()) {
+				readPostCommands(pckIn);
+			}
 		} catch (PackProtocolException e) {
-			sendError(e.getMessage());
+			if (sideBand) {
+				try {
+					pckIn.discardUntilEnd();
+				} catch (IOException e2) {
+					// Ignore read failures attempting to discard.
+				}
+			}
+			fatalError(e.getMessage());
 			throw e;
 		}
 	}
 
+	private void parseShallow(String idStr) throws PackProtocolException {
+		ObjectId id;
+		try {
+			id = ObjectId.fromString(idStr);
+		} catch (InvalidObjectIdException e) {
+			throw new PackProtocolException(e.getMessage(), e);
+		}
+		clientShallowCommits.add(id);
+	}
+
 	static ReceiveCommand parseCommand(String line) throws PackProtocolException {
           if (line == null || line.length() < 83) {
 			throw new PackProtocolException(
@@ -1147,7 +1208,7 @@
 		try {
 			oldId = ObjectId.fromString(oldStr);
 			newId = ObjectId.fromString(newStr);
-		} catch (IllegalArgumentException e) {
+		} catch (InvalidObjectIdException e) {
 			throw new PackProtocolException(
 					JGitText.get().errorInvalidProtocolWantedOldNewRef, e);
 		}
@@ -1159,6 +1220,16 @@
 		return new ReceiveCommand(oldId, newId, name);
 	}
 
+	/**
+	 * @param in
+	 *            request stream.
+	 * @throws IOException
+	 *             request line cannot be read.
+	 */
+	void readPostCommands(PacketLineIn in) throws IOException {
+		// Do nothing by default.
+	}
+
 	/** Enable capabilities based on a previously read capabilities line. */
 	protected void enableCapabilities() {
 		sideBand = isCapabilityEnabled(CAPABILITY_SIDE_BAND_64K);
@@ -1168,6 +1239,7 @@
 
 			rawOut = new SideBandOutputStream(CH_DATA, MAX_BUF, out);
 			msgOut = new SideBandOutputStream(CH_PROGRESS, MAX_BUF, out);
+			errOut = new SideBandOutputStream(CH_ERROR, MAX_BUF, out);
 
 			pckOut = new PacketLineOut(rawOut);
 			pckOut.setFlushOnEnd(false);
@@ -1185,6 +1257,11 @@
 		return enabledCapabilities.contains(name);
 	}
 
+	void checkRequestWasRead() {
+		if (enabledCapabilities == null)
+			throw new RequestNotYetReadException();
+	}
+
 	/** @return true if a pack is expected based on the list of commands. */
 	protected boolean needPack() {
 		for (final ReceiveCommand cmd : commands) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/GitProtocolConstants.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/GitProtocolConstants.java
index efde062..2031147 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/GitProtocolConstants.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/GitProtocolConstants.java
@@ -208,6 +208,13 @@
 	 */
 	public static final String OPTION_AGENT = "agent"; //$NON-NLS-1$
 
+	/**
+	 * The server supports the receiving of push options.
+	 *
+	 * @since 4.5
+	 */
+	public static final String CAPABILITY_PUSH_OPTIONS = "push-options"; //$NON-NLS-1$
+
 	static enum MultiAck {
 		OFF, CONTINUE, DETAILED;
 	}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/HttpAuthMethod.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/HttpAuthMethod.java
index 998f280..81e6904 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/HttpAuthMethod.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/HttpAuthMethod.java
@@ -51,6 +51,7 @@
 import java.net.URL;
 import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.LinkedHashMap;
@@ -149,9 +150,12 @@
 	 *
 	 * @param conn
 	 *            the connection that failed.
+	 * @param ignoreTypes
+	 *            authentication types to be ignored.
 	 * @return new authentication method to try.
 	 */
-	static HttpAuthMethod scanResponse(final HttpConnection conn) {
+	static HttpAuthMethod scanResponse(final HttpConnection conn,
+			Collection<Type> ignoreTypes) {
 		final Map<String, List<String>> headers = conn.getHeaderFields();
 		HttpAuthMethod authentication = Type.NONE.method(EMPTY_STRING);
 
@@ -165,6 +169,12 @@
 
 							try {
 								Type methodType = Type.valueOf(valuePart[0].toUpperCase());
+
+								if ((ignoreTypes != null)
+										&& (ignoreTypes.contains(methodType))) {
+									continue;
+								}
+
 								if (authentication.getType().compareTo(methodType) >= 0) {
 									continue;
 								}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/JschConfigSessionFactory.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/JschConfigSessionFactory.java
index 308741e..d1cbd8d 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/JschConfigSessionFactory.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/JschConfigSessionFactory.java
@@ -1,4 +1,5 @@
 /*
+ * Copyright (C) 2016, Mark Ingram <markdingram@gmail.com>
  * Copyright (C) 2009, Constantine Plotnikov <constantine.plotnikov@gmail.com>
  * Copyright (C) 2008-2009, Google Inc.
  * Copyright (C) 2009, Google, Inc.
@@ -220,6 +221,19 @@
 	}
 
 	/**
+	 * Provide additional configuration for the JSch instance. This method could
+	 * be overridden to supply a preferred
+	 * {@link com.jcraft.jsch.IdentityRepository}.
+	 *
+	 * @param jsch
+	 *            jsch instance
+	 * @since 4.5
+	 */
+	protected void configureJSch(JSch jsch) {
+		// No additional configuration required.
+	}
+
+	/**
 	 * Provide additional configuration for the session based on the host
 	 * information. This method could be used to supply {@link UserInfo}.
 	 *
@@ -257,6 +271,7 @@
 		JSch jsch = byIdentityFile.get(identityKey);
 		if (jsch == null) {
 			jsch = new JSch();
+			configureJSch(jsch);
 			jsch.setHostKeyRepository(defaultJSch.getHostKeyRepository());
 			jsch.addIdentity(identityKey);
 			byIdentityFile.put(identityKey, jsch);
@@ -274,6 +289,7 @@
 	 */
 	protected JSch createDefaultJSch(FS fs) throws JSchException {
 		final JSch jsch = new JSch();
+		configureJSch(jsch);
 		knownHosts(jsch, fs);
 		identities(jsch, fs);
 		return jsch;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PacketLineIn.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PacketLineIn.java
index e1769f8..e142bab 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PacketLineIn.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PacketLineIn.java
@@ -55,6 +55,8 @@
 import org.eclipse.jgit.lib.MutableObjectId;
 import org.eclipse.jgit.util.IO;
 import org.eclipse.jgit.util.RawParseUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 /**
  * Read Git style pkt-line formatting from an input stream.
@@ -67,6 +69,8 @@
  * against the underlying InputStream.
  */
 public class PacketLineIn {
+	private static final Logger log = LoggerFactory.getLogger(PacketLineIn.class);
+
 	/** Magic return from {@link #readString()} when a flush packet is found. */
 	public static final String END = new StringBuilder(0).toString(); 	/* must not string pool */
 
@@ -136,12 +140,16 @@
 	 */
 	public String readString() throws IOException {
 		int len = readLength();
-		if (len == 0)
+		if (len == 0) {
+			log.debug("git< 0000"); //$NON-NLS-1$
 			return END;
+		}
 
 		len -= 4; // length header (4 bytes)
-		if (len == 0)
+		if (len == 0) {
+			log.debug("git< "); //$NON-NLS-1$
 			return ""; //$NON-NLS-1$
+		}
 
 		byte[] raw;
 		if (len <= lineBuffer.length)
@@ -152,7 +160,10 @@
 		IO.readFully(in, raw, 0, len);
 		if (raw[len - 1] == '\n')
 			len--;
-		return RawParseUtils.decode(Constants.CHARSET, raw, 0, len);
+
+		String s = RawParseUtils.decode(Constants.CHARSET, raw, 0, len);
+		log.debug("git< " + s); //$NON-NLS-1$
+		return s;
 	}
 
 	/**
@@ -167,8 +178,10 @@
 	 */
 	public String readStringRaw() throws IOException {
 		int len = readLength();
-		if (len == 0)
+		if (len == 0) {
+			log.debug("git< 0000"); //$NON-NLS-1$
 			return END;
+		}
 
 		len -= 4; // length header (4 bytes)
 
@@ -179,7 +192,20 @@
 			raw = new byte[len];
 
 		IO.readFully(in, raw, 0, len);
-		return RawParseUtils.decode(Constants.CHARSET, raw, 0, len);
+
+		String s = RawParseUtils.decode(Constants.CHARSET, raw, 0, len);
+		log.debug("git< " + s); //$NON-NLS-1$
+		return s;
+	}
+
+	void discardUntilEnd() throws IOException {
+		for (;;) {
+			int n = readLength();
+			if (n == 0) {
+				break;
+			}
+			IO.skipFully(in, n - 4);
+		}
 	}
 
 	int readLength() throws IOException {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PacketLineOut.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PacketLineOut.java
index bb83dce..a6bd342 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PacketLineOut.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PacketLineOut.java
@@ -49,6 +49,9 @@
 import java.io.OutputStream;
 
 import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.util.RawParseUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 /**
  * Write Git style pkt-line formatting to an output stream.
@@ -61,6 +64,8 @@
  * against the underlying OutputStream.
  */
 public class PacketLineOut {
+	private static final Logger log = LoggerFactory.getLogger(PacketLineOut.class);
+
 	private final OutputStream out;
 
 	private final byte[] lenbuffer;
@@ -113,10 +118,32 @@
 	 *             the packet could not be written, the stream is corrupted as
 	 *             the packet may have been only partially written.
 	 */
-	public void writePacket(final byte[] packet) throws IOException {
-		formatLength(packet.length + 4);
+	public void writePacket(byte[] packet) throws IOException {
+		writePacket(packet, 0, packet.length);
+	}
+
+	/**
+	 * Write a binary packet to the stream.
+	 *
+	 * @param buf
+	 *            the packet to write
+	 * @param pos
+	 *            first index within {@code buf}.
+	 * @param len
+	 *            number of bytes to write.
+	 * @throws IOException
+	 *             the packet could not be written, the stream is corrupted as
+	 *             the packet may have been only partially written.
+	 * @since 4.5
+	 */
+	public void writePacket(byte[] buf, int pos, int len) throws IOException {
+		formatLength(len + 4);
 		out.write(lenbuffer, 0, 4);
-		out.write(packet);
+		out.write(buf, pos, len);
+		if (log.isDebugEnabled()) {
+			String s = RawParseUtils.decode(Constants.CHARSET, buf, pos, len);
+			log.debug("git> " + s); //$NON-NLS-1$
+		}
 	}
 
 	/**
@@ -135,6 +162,7 @@
 	public void end() throws IOException {
 		formatLength(0);
 		out.write(lenbuffer, 0, 4);
+		log.debug("git> 0000"); //$NON-NLS-1$
 		if (flushOnEnd)
 			flush();
 	}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushProcess.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushProcess.java
index 5cea882..5590c2d 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushProcess.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushProcess.java
@@ -49,6 +49,7 @@
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 
 import org.eclipse.jgit.errors.MissingObjectException;
@@ -87,6 +88,9 @@
 	/** an outputstream to write messages to */
 	private final OutputStream out;
 
+	/** A list of option strings associated with this push */
+	private List<String> pushOptions;
+
 	/**
 	 * Create process for specified transport and refs updates specification.
 	 *
@@ -122,6 +126,7 @@
 		this.transport = transport;
 		this.toPush = new HashMap<String, RemoteRefUpdate>();
 		this.out = out;
+		this.pushOptions = transport.getPushOptions();
 		for (final RemoteRefUpdate rru : toPush) {
 			if (this.toPush.put(rru.getRemoteName(), rru) != null)
 				throw new TransportException(MessageFormat.format(
@@ -294,4 +299,14 @@
 			}
 		}
 	}
+
+	/**
+	 * Gets the list of option strings associated with this push.
+	 *
+	 * @return pushOptions
+	 * @since 4.5
+	 */
+	public List<String> getPushOptions() {
+		return pushOptions;
+	}
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivePack.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivePack.java
index f61e93f..cc20d50 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivePack.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivePack.java
@@ -44,12 +44,17 @@
 package org.eclipse.jgit.transport;
 
 import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_ATOMIC;
+import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_PUSH_OPTIONS;
 import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_REPORT_STATUS;
 
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
 
+import org.eclipse.jgit.annotations.Nullable;
 import org.eclipse.jgit.errors.UnpackException;
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.Repository;
@@ -71,6 +76,10 @@
 
 	private boolean echoCommandFailures;
 
+	/** Whether the client intends to use push options. */
+	private boolean usePushOptions;
+	private List<String> pushOptions;
+
 	/**
 	 * Create a new pack receive for an open repository.
 	 *
@@ -83,6 +92,42 @@
 		postReceive = PostReceiveHook.NULL;
 	}
 
+	/**
+	 * Gets an unmodifiable view of the option strings associated with the push.
+	 *
+	 * @return an unmodifiable view of pushOptions, or null (if pushOptions is).
+	 * @since 4.5
+	 */
+	@Nullable
+	public List<String> getPushOptions() {
+		if (isAllowPushOptions() && usePushOptions) {
+			return Collections.unmodifiableList(pushOptions);
+		}
+
+		// The client doesn't support push options. Return null to
+		// distinguish this from the case where the client declared support
+		// for push options and sent an empty list of them.
+		return null;
+	}
+
+	/**
+	 * Set the push options supplied by the client.
+	 * <p>
+	 * Should only be called if reconstructing an instance without going through
+	 * the normal {@link #recvCommands()} flow.
+	 *
+	 * @param options
+	 *            the list of options supplied by the client. The
+	 *            {@code ReceivePack} instance takes ownership of this list.
+	 *            Callers are encouraged to first create a copy if the list may
+	 *            be modified later.
+	 * @since 4.5
+	 */
+	public void setPushOptions(@Nullable List<String> options) {
+		usePushOptions = options != null;
+		pushOptions = options;
+	}
+
 	/** @return the hook invoked before updates occur. */
 	public PreReceiveHook getPreReceiveHook() {
 		return preReceive;
@@ -171,9 +216,24 @@
 	@Override
 	protected void enableCapabilities() {
 		reportStatus = isCapabilityEnabled(CAPABILITY_REPORT_STATUS);
+		usePushOptions = isCapabilityEnabled(CAPABILITY_PUSH_OPTIONS);
 		super.enableCapabilities();
 	}
 
+	@Override
+	void readPostCommands(PacketLineIn in) throws IOException {
+		if (usePushOptions) {
+			pushOptions = new ArrayList<>(4);
+			for (;;) {
+				String option = in.readString();
+				if (option == PacketLineIn.END) {
+					break;
+				}
+				pushOptions.add(option);
+			}
+		}
+	}
+
 	private void service() throws IOException {
 		if (isBiDirectionalPipe()) {
 			sendAdvertisedRefs(new PacketLineOutRefAdvertiser(pckOut));
@@ -184,17 +244,11 @@
 			return;
 		recvCommands();
 		if (hasCommands()) {
-			enableCapabilities();
-
 			Throwable unpackError = null;
 			if (needPack()) {
 				try {
 					receivePackAndCheckConnectivity();
-				} catch (IOException err) {
-					unpackError = err;
-				} catch (RuntimeException err) {
-					unpackError = err;
-				} catch (Error err) {
+				} catch (IOException | RuntimeException | Error err) {
 					unpackError = err;
 				}
 			}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefAdvertiser.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefAdvertiser.java
index f72a4b2..0cd720c 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefAdvertiser.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefAdvertiser.java
@@ -43,9 +43,16 @@
 
 package org.eclipse.jgit.transport;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.eclipse.jgit.lib.Constants.OBJECT_ID_STRING_LENGTH;
 import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_SYMREF;
 
 import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.CharacterCodingException;
+import java.nio.charset.CharsetEncoder;
+import java.nio.charset.CoderResult;
 import java.util.HashSet;
 import java.util.LinkedHashSet;
 import java.util.Map;
@@ -64,8 +71,15 @@
 public abstract class RefAdvertiser {
 	/** Advertiser which frames lines in a {@link PacketLineOut} format. */
 	public static class PacketLineOutRefAdvertiser extends RefAdvertiser {
+		private final CharsetEncoder utf8 = UTF_8.newEncoder();
 		private final PacketLineOut pckOut;
 
+		private byte[] binArr = new byte[256];
+		private ByteBuffer binBuf = ByteBuffer.wrap(binArr);
+
+		private char[] chArr = new char[256];
+		private CharBuffer chBuf = CharBuffer.wrap(chArr);
+
 		/**
 		 * Create a new advertiser for the supplied stream.
 		 *
@@ -77,6 +91,64 @@
 		}
 
 		@Override
+		public void advertiseId(AnyObjectId id, String refName)
+				throws IOException {
+			id.copyTo(binArr, 0);
+			binArr[OBJECT_ID_STRING_LENGTH] = ' ';
+			binBuf.position(OBJECT_ID_STRING_LENGTH + 1);
+			append(refName);
+			if (first) {
+				first = false;
+				if (!capablities.isEmpty()) {
+					append('\0');
+					for (String cap : capablities) {
+						append(' ');
+						append(cap);
+					}
+				}
+			}
+			append('\n');
+			pckOut.writePacket(binArr, 0, binBuf.position());
+		}
+
+		private void append(String str) throws CharacterCodingException {
+			int n = str.length();
+			if (n > chArr.length) {
+				chArr = new char[n + 256];
+				chBuf = CharBuffer.wrap(chArr);
+			}
+			str.getChars(0, n, chArr, 0);
+			chBuf.position(0).limit(n);
+			utf8.reset();
+			for (;;) {
+				CoderResult cr = utf8.encode(chBuf, binBuf, true);
+				if (cr.isOverflow()) {
+					grow();
+				} else if (cr.isUnderflow()) {
+					break;
+				} else {
+					cr.throwException();
+				}
+			}
+		}
+
+		private void append(int b) {
+			if (!binBuf.hasRemaining()) {
+				grow();
+			}
+			binBuf.put((byte) b);
+		}
+
+		private void grow() {
+			int cnt = binBuf.position();
+			byte[] tmp = new byte[binArr.length << 1];
+			System.arraycopy(binArr, 0, tmp, 0, cnt);
+			binArr = tmp;
+			binBuf = ByteBuffer.wrap(binArr);
+			binBuf.position(cnt);
+		}
+
+		@Override
 		protected void writeOne(final CharSequence line) throws IOException {
 			pckOut.writeString(line.toString());
 		}
@@ -91,7 +163,7 @@
 
 	private final char[] tmpId = new char[Constants.OBJECT_ID_STRING_LENGTH];
 
-	private final Set<String> capablities = new LinkedHashSet<String>();
+	final Set<String> capablities = new LinkedHashSet<String>();
 
 	private final Set<ObjectId> sent = new HashSet<ObjectId>();
 
@@ -99,7 +171,7 @@
 
 	private boolean derefTags;
 
-	private boolean first = true;
+	boolean first = true;
 
 	/**
 	 * Initialize this advertiser with a repository for peeling tags.
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefSpec.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefSpec.java
index 0e803bd..1440b83 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefSpec.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefSpec.java
@@ -82,6 +82,30 @@
 	/** Is this specification actually a wildcard match? */
 	private boolean wildcard;
 
+	/**
+	 * How strict to be about wildcards.
+	 *
+	 * @since 4.5
+	 */
+	public enum WildcardMode {
+		/**
+		 * Reject refspecs with an asterisk on the source side and not the
+		 * destination side or vice versa. This is the mode used by FetchCommand
+		 * and PushCommand to create a one-to-one mapping between source and
+		 * destination refs.
+		 */
+		REQUIRE_MATCH,
+		/**
+		 * Allow refspecs with an asterisk on only one side. This can create a
+		 * many-to-one mapping between source and destination refs, so
+		 * expandFromSource and expandFromDestination are not usable in this
+		 * mode.
+		 */
+		ALLOW_MISMATCH
+	}
+	/** Whether a wildcard is allowed on one side but not the other. */
+	private WildcardMode allowMismatchedWildcards;
+
 	/** Name of the ref(s) we would copy from. */
 	private String srcName;
 
@@ -99,6 +123,83 @@
 		wildcard = false;
 		srcName = Constants.HEAD;
 		dstName = null;
+		allowMismatchedWildcards = WildcardMode.REQUIRE_MATCH;
+	}
+
+	/**
+	 * Parse a ref specification for use during transport operations.
+	 * <p>
+	 * Specifications are typically one of the following forms:
+	 * <ul>
+	 * <li><code>refs/heads/master</code></li>
+	 * <li><code>refs/heads/master:refs/remotes/origin/master</code></li>
+	 * <li><code>refs/heads/*:refs/remotes/origin/*</code></li>
+	 * <li><code>+refs/heads/master</code></li>
+	 * <li><code>+refs/heads/master:refs/remotes/origin/master</code></li>
+	 * <li><code>+refs/heads/*:refs/remotes/origin/*</code></li>
+	 * <li><code>+refs/pull/&#42;/head:refs/remotes/origin/pr/*</code></li>
+	 * <li><code>:refs/heads/master</code></li>
+	 * </ul>
+	 *
+	 * If the wildcard mode allows mismatches, then these ref specs are also
+	 * valid:
+	 * <ul>
+	 * <li><code>refs/heads/*</code></li>
+	 * <li><code>refs/heads/*:refs/heads/master</code></li>
+	 * </ul>
+	 *
+	 * @param spec
+	 *            string describing the specification.
+	 * @param mode
+	 *            whether to allow a wildcard on one side without a wildcard on
+	 *            the other.
+	 * @throws IllegalArgumentException
+	 *             the specification is invalid.
+	 * @since 4.5
+	 */
+	public RefSpec(String spec, WildcardMode mode) {
+		this.allowMismatchedWildcards = mode;
+		String s = spec;
+		if (s.startsWith("+")) { //$NON-NLS-1$
+			force = true;
+			s = s.substring(1);
+		}
+
+		final int c = s.lastIndexOf(':');
+		if (c == 0) {
+			s = s.substring(1);
+			if (isWildcard(s)) {
+				wildcard = true;
+				if (mode == WildcardMode.REQUIRE_MATCH) {
+					throw new IllegalArgumentException(MessageFormat
+							.format(JGitText.get().invalidWildcards, spec));
+				}
+			}
+			dstName = checkValid(s);
+		} else if (c > 0) {
+			String src = s.substring(0, c);
+			String dst = s.substring(c + 1);
+			if (isWildcard(src) && isWildcard(dst)) {
+				// Both contain wildcard
+				wildcard = true;
+			} else if (isWildcard(src) || isWildcard(dst)) {
+				wildcard = true;
+				if (mode == WildcardMode.REQUIRE_MATCH)
+					throw new IllegalArgumentException(MessageFormat
+							.format(JGitText.get().invalidWildcards, spec));
+			}
+			srcName = checkValid(src);
+			dstName = checkValid(dst);
+		} else {
+			if (isWildcard(s)) {
+				if (mode == WildcardMode.REQUIRE_MATCH) {
+					throw new IllegalArgumentException(MessageFormat
+							.format(JGitText.get().invalidWildcards, spec));
+				}
+				wildcard = true;
+			}
+			srcName = checkValid(s);
+		}
 	}
 
 	/**
@@ -122,36 +223,7 @@
 	 *             the specification is invalid.
 	 */
 	public RefSpec(final String spec) {
-		String s = spec;
-		if (s.startsWith("+")) { //$NON-NLS-1$
-			force = true;
-			s = s.substring(1);
-		}
-
-		final int c = s.lastIndexOf(':');
-		if (c == 0) {
-			s = s.substring(1);
-			if (isWildcard(s))
-				throw new IllegalArgumentException(MessageFormat.format(JGitText.get().invalidWildcards, spec));
-			dstName = checkValid(s);
-		} else if (c > 0) {
-			String src = s.substring(0, c);
-			String dst = s.substring(c + 1);
-			if (isWildcard(src) && isWildcard(dst)) {
-				// Both contain wildcard
-				wildcard = true;
-			} else if (isWildcard(src) || isWildcard(dst)) {
-				// If either source or destination has wildcard, the other one
-				// must have as well.
-				throw new IllegalArgumentException(MessageFormat.format(JGitText.get().invalidWildcards, spec));
-			}
-			srcName = checkValid(src);
-			dstName = checkValid(dst);
-		} else {
-			if (isWildcard(s))
-				throw new IllegalArgumentException(MessageFormat.format(JGitText.get().invalidWildcards, spec));
-			srcName = checkValid(s);
-		}
+		this(spec, WildcardMode.REQUIRE_MATCH);
 	}
 
 	private RefSpec(final RefSpec p) {
@@ -159,6 +231,7 @@
 		wildcard = p.isWildcard();
 		srcName = p.getSource();
 		dstName = p.getDestination();
+		allowMismatchedWildcards = p.allowMismatchedWildcards;
 	}
 
 	/**
@@ -348,8 +421,15 @@
 	 * @return a new specification expanded from provided ref name. Result
 	 *         specification is wildcard if and only if provided ref name is
 	 *         wildcard.
+	 * @throws IllegalStateException
+	 *             when the RefSpec was constructed with wildcard mode that
+	 *             doesn't require matching wildcards.
 	 */
 	public RefSpec expandFromSource(final String r) {
+		if (allowMismatchedWildcards != WildcardMode.REQUIRE_MATCH) {
+			throw new IllegalStateException(
+					JGitText.get().invalidExpandWildcard);
+		}
 		return isWildcard() ? new RefSpec(this).expandFromSourceImp(r) : this;
 	}
 
@@ -373,6 +453,9 @@
 	 * @return a new specification expanded from provided ref name. Result
 	 *         specification is wildcard if and only if provided ref name is
 	 *         wildcard.
+	 * @throws IllegalStateException
+	 *             when the RefSpec was constructed with wildcard mode that
+	 *             doesn't require matching wildcards.
 	 */
 	public RefSpec expandFromSource(final Ref r) {
 		return expandFromSource(r.getName());
@@ -390,8 +473,15 @@
 	 * @return a new specification expanded from provided ref name. Result
 	 *         specification is wildcard if and only if provided ref name is
 	 *         wildcard.
+	 * @throws IllegalStateException
+	 *             when the RefSpec was constructed with wildcard mode that
+	 *             doesn't require matching wildcards.
 	 */
 	public RefSpec expandFromDestination(final String r) {
+		if (allowMismatchedWildcards != WildcardMode.REQUIRE_MATCH) {
+			throw new IllegalStateException(
+					JGitText.get().invalidExpandWildcard);
+		}
 		return isWildcard() ? new RefSpec(this).expandFromDstImp(r) : this;
 	}
 
@@ -414,6 +504,9 @@
 	 * @return a new specification expanded from provided ref name. Result
 	 *         specification is wildcard if and only if provided ref name is
 	 *         wildcard.
+	 * @throws IllegalStateException
+	 *             when the RefSpec was constructed with wildcard mode that
+	 *             doesn't require matching wildcards.
 	 */
 	public RefSpec expandFromDestination(final Ref r) {
 		return expandFromDestination(r.getName());
@@ -422,7 +515,7 @@
 	private boolean match(final String name, final String s) {
 		if (s == null)
 			return false;
-		if (isWildcard()) {
+		if (isWildcard(s)) {
 			int wildcardIndex = s.indexOf('*');
 			String prefix = s.substring(0, wildcardIndex);
 			String suffix = s.substring(wildcardIndex + 1);
@@ -453,6 +546,8 @@
 			return false;
 		if (s.contains("//")) //$NON-NLS-1$
 			return false;
+		if (s.endsWith("/")) //$NON-NLS-1$
+			return false;
 		int i = s.indexOf('*');
 		if (i != -1) {
 			if (s.indexOf('*', i + 1) > i)
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ServiceMayNotContinueException.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ServiceMayNotContinueException.java
index e000cfb..ce5ccaa 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ServiceMayNotContinueException.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ServiceMayNotContinueException.java
@@ -44,6 +44,7 @@
 package org.eclipse.jgit.transport;
 
 import java.io.IOException;
+import javax.servlet.http.HttpServletResponse;
 
 import org.eclipse.jgit.internal.JGitText;
 
@@ -55,11 +56,13 @@
 public class ServiceMayNotContinueException extends IOException {
 	private static final long serialVersionUID = 1L;
 
+	private final int statusCode;
 	private boolean output;
 
 	/** Initialize with no message. */
 	public ServiceMayNotContinueException() {
 		// Do not set a message.
+		statusCode = HttpServletResponse.SC_FORBIDDEN;
 	}
 
 	/**
@@ -69,6 +72,20 @@
 	 */
 	public ServiceMayNotContinueException(String msg) {
 		super(msg);
+		statusCode = HttpServletResponse.SC_FORBIDDEN;
+	}
+
+	/**
+	 * @param msg
+	 *            a message explaining why it cannot continue. This message may
+	 *            be shown to an end-user.
+	 * @param statusCode
+	 *            the HTTP status code.
+	 * @since 4.5
+	 */
+	public ServiceMayNotContinueException(String msg, int statusCode) {
+		super(msg);
+		this.statusCode = statusCode;
 	}
 
 	/**
@@ -80,8 +97,24 @@
 	 * @since 3.2
 	 */
 	public ServiceMayNotContinueException(String msg, Throwable cause) {
-		super(msg);
-		initCause(cause);
+		super(msg, cause);
+		statusCode = HttpServletResponse.SC_FORBIDDEN;
+	}
+
+	/**
+	 * @param msg
+	 *            a message explaining why it cannot continue. This message may
+	 *            be shown to an end-user.
+	 * @param cause
+	 *            the cause of the exception.
+	 * @param statusCode
+	 *            the HTTP status code.
+	 * @since 4.5
+	 */
+	public ServiceMayNotContinueException(
+			String msg, Throwable cause, int statusCode) {
+		super(msg, cause);
+		this.statusCode = statusCode;
 	}
 
 	/**
@@ -104,4 +137,12 @@
 	public void setOutput() {
 		output = true;
 	}
+
+	/**
+	 * @return true if the message was already output to the client.
+	 * @since 4.5
+	 */
+	public int getStatusCode() {
+		return statusCode;
+	}
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/Transport.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/Transport.java
index 862b3bd..bc4843a 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/Transport.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/Transport.java
@@ -773,6 +773,9 @@
 	/** Assists with authentication the connection. */
 	private CredentialsProvider credentialsProvider;
 
+	/** The option strings associated with the push operation. */
+	private List<String> pushOptions;
+
 	private PrintStream hookOutRedirect;
 
 	private PrePushHook prePush;
@@ -1121,6 +1124,25 @@
 	}
 
 	/**
+	 * @return the option strings associated with the push operation
+	 * @since 4.5
+	 */
+	public List<String> getPushOptions() {
+		return pushOptions;
+	}
+
+	/**
+	 * Sets the option strings associated with the push operation.
+	 *
+	 * @param pushOptions
+	 *            null if push options are unsupported
+	 * @since 4.5
+	 */
+	public void setPushOptions(final List<String> pushOptions) {
+		this.pushOptions = pushOptions;
+	}
+
+	/**
 	 * Fetch objects and refs from the remote repository to the local one.
 	 * <p>
 	 * This is a utility function providing standard fetch behavior. Local
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportHttp.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportHttp.java
index 414e879..1166080 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportHttp.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportHttp.java
@@ -73,6 +73,7 @@
 import java.util.Collection;
 import java.util.Collections;
 import java.util.EnumSet;
+import java.util.HashSet;
 import java.util.LinkedHashSet;
 import java.util.Map;
 import java.util.Set;
@@ -95,6 +96,7 @@
 import org.eclipse.jgit.lib.Ref;
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.lib.SymbolicRef;
+import org.eclipse.jgit.transport.HttpAuthMethod.Type;
 import org.eclipse.jgit.transport.http.HttpConnection;
 import org.eclipse.jgit.util.HttpSupport;
 import org.eclipse.jgit.util.IO;
@@ -448,9 +450,11 @@
 			throw new NotSupportedException(MessageFormat.format(JGitText.get().invalidURL, uri), e);
 		}
 
-		try {
-			int authAttempts = 1;
-			for (;;) {
+
+		int authAttempts = 1;
+		Collection<Type> ignoreTypes = null;
+		for (;;) {
+			try {
 				final HttpConnection conn = httpOpen(u);
 				if (useSmartHttp) {
 					String exp = "application/x-" + service + "-advertisement"; //$NON-NLS-1$ //$NON-NLS-2$
@@ -467,7 +471,7 @@
 					// explicit authentication would be required
 					if (authMethod.getType() == HttpAuthMethod.Type.NONE
 							&& conn.getHeaderField(HDR_WWW_AUTHENTICATE) != null)
-						authMethod = HttpAuthMethod.scanResponse(conn);
+						authMethod = HttpAuthMethod.scanResponse(conn, ignoreTypes);
 					return conn;
 
 				case HttpConnection.HTTP_NOT_FOUND:
@@ -475,7 +479,7 @@
 							MessageFormat.format(JGitText.get().uriNotFound, u));
 
 				case HttpConnection.HTTP_UNAUTHORIZED:
-					authMethod = HttpAuthMethod.scanResponse(conn);
+					authMethod = HttpAuthMethod.scanResponse(conn, ignoreTypes);
 					if (authMethod.getType() == HttpAuthMethod.Type.NONE)
 						throw new TransportException(uri, MessageFormat.format(
 								JGitText.get().authenticationNotSupported, uri));
@@ -501,13 +505,27 @@
 					String err = status + " " + conn.getResponseMessage(); //$NON-NLS-1$
 					throw new TransportException(uri, err);
 				}
+			} catch (NotSupportedException e) {
+				throw e;
+			} catch (TransportException e) {
+				throw e;
+			} catch (IOException e) {
+				if (authMethod.getType() != HttpAuthMethod.Type.NONE) {
+					if (ignoreTypes == null) {
+						ignoreTypes = new HashSet<Type>();
+					}
+
+					ignoreTypes.add(authMethod.getType());
+
+					// reset auth method & attempts for next authentication type
+					authMethod = HttpAuthMethod.Type.NONE.method(null);
+					authAttempts = 1;
+
+					continue;
+				}
+
+				throw new TransportException(uri, MessageFormat.format(JGitText.get().cannotOpenService, service), e);
 			}
-		} catch (NotSupportedException e) {
-			throw e;
-		} catch (TransportException e) {
-			throw e;
-		} catch (IOException e) {
-			throw new TransportException(uri, MessageFormat.format(JGitText.get().cannotOpenService, service), e);
 		}
 	}
 
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 e49ee87..d1fd67e 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java
@@ -682,7 +682,7 @@
 	 * Get the PackWriter's statistics if a pack was sent to the client.
 	 *
 	 * @return statistics about pack output, if a pack was sent. Null if no pack
-	 *         was sent, such as during the negotation phase of a smart HTTP
+	 *         was sent, such as during the negotiation phase of a smart HTTP
 	 *         connection, or if the client was already up-to-date.
 	 * @since 3.0
 	 * @deprecated Use {@link #getStatistics()}.
@@ -697,7 +697,7 @@
 	 * Get the PackWriter's statistics if a pack was sent to the client.
 	 *
 	 * @return statistics about pack output, if a pack was sent. Null if no pack
-	 *         was sent, such as during the negotation phase of a smart HTTP
+	 *         was sent, such as during the negotiation phase of a smart HTTP
 	 *         connection, or if the client was already up-to-date.
 	 * @since 4.1
 	 */
@@ -777,15 +777,22 @@
 	private static Set<ObjectId> refIdSet(Collection<Ref> refs) {
 		Set<ObjectId> ids = new HashSet<ObjectId>(refs.size());
 		for (Ref ref : refs) {
-			if (ref.getObjectId() != null)
-				ids.add(ref.getObjectId());
+			ObjectId id = ref.getObjectId();
+			if (id != null) {
+				ids.add(id);
+			}
+			id = ref.getPeeledObjectId();
+			if (id != null) {
+				ids.add(id);
+			}
 		}
 		return ids;
 	}
 
 	private void processShallow() throws IOException {
+		int walkDepth = depth - 1;
 		try (DepthWalk.RevWalk depthWalk = new DepthWalk.RevWalk(
-				walk.getObjectReader(), depth)) {
+				walk.getObjectReader(), walkDepth)) {
 
 			// Find all the commits which will be shallow
 			for (ObjectId o : wantIds) {
@@ -802,12 +809,14 @@
 
 				// Commits at the boundary which aren't already shallow in
 				// the client need to be marked as such
-				if (c.getDepth() == depth && !clientShallowCommits.contains(c))
+				if (c.getDepth() == walkDepth
+						&& !clientShallowCommits.contains(c))
 					pckOut.writeString("shallow " + o.name()); //$NON-NLS-1$
 
 				// Commits not on the boundary which are shallow in the client
 				// need to become unshallowed
-				if (c.getDepth() < depth && clientShallowCommits.remove(c)) {
+				if (c.getDepth() < walkDepth
+						&& clientShallowCommits.remove(c)) {
 					unshallowCommits.add(c.copy());
 					pckOut.writeString("unshallow " + c.name()); //$NON-NLS-1$
 				}
@@ -942,6 +951,11 @@
 
 			if (line.startsWith("deepen ")) { //$NON-NLS-1$
 				depth = Integer.parseInt(line.substring(7));
+				if (depth <= 0) {
+					throw new PackProtocolException(
+							MessageFormat.format(JGitText.get().invalidDepth,
+									Integer.valueOf(depth)));
+				}
 				continue;
 			}
 
@@ -968,7 +982,8 @@
 	}
 
 	/**
-	 * Returns the clone/fetch depth. Valid only after calling recvWants().
+	 * Returns the clone/fetch depth. Valid only after calling recvWants(). A
+	 * depth of 1 means return only the wants.
 	 *
 	 * @return the depth requested by the client, or 0 if unbounded.
 	 * @since 4.0
@@ -1142,7 +1157,6 @@
 
 		if (multiAck == MultiAck.DETAILED && !didOkToGiveUp && okToGiveUp()) {
 			ObjectId id = peerHas.get(peerHas.size() - 1);
-			sentReady = true;
 			pckOut.writeString("ACK " + id.name() + " ready\n"); //$NON-NLS-1$ //$NON-NLS-2$
 			sentReady = true;
 		}
@@ -1479,16 +1493,19 @@
 				pw.setTagTargets(tagTargets);
 			}
 
-			if (depth > 0)
-				pw.setShallowPack(depth, unshallowCommits);
-
 			RevWalk rw = walk;
+			if (depth > 0) {
+				pw.setShallowPack(depth, unshallowCommits);
+				rw = new DepthWalk.RevWalk(walk.getObjectReader(), depth - 1);
+				rw.assumeShallow(clientShallowCommits);
+			}
+
 			if (wantAll.isEmpty()) {
-				pw.preparePack(pm, wantIds, commonBase);
+				pw.preparePack(pm, wantIds, commonBase, clientShallowCommits);
 			} else {
 				walk.reset();
 
-				ObjectWalk ow = walk.toObjectWalkWithSameObjects();
+				ObjectWalk ow = rw.toObjectWalkWithSameObjects();
 				pw.preparePack(pm, ow, wantAll, commonBase);
 				rw = ow;
 			}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/TreeWalk.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/TreeWalk.java
index 7dac50a..911b7ff 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/TreeWalk.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/TreeWalk.java
@@ -795,7 +795,6 @@
 	public boolean next() throws MissingObjectException,
 			IncorrectObjectTypeException, CorruptObjectException, IOException {
 		try {
-			attrs = null;
 			if (advance) {
 				advance = false;
 				postChildren = false;
@@ -803,6 +802,7 @@
 			}
 
 			for (;;) {
+				attrs = null;
 				final AbstractTreeIterator t = min();
 				if (t.eof()) {
 					if (depth > 0) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java
index c8de3de..9a3fa80 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java
@@ -268,7 +268,9 @@
 							DirCacheIterator.class);
 			if (i != null) {
 				DirCacheEntry ent = i.getDirCacheEntry();
-				if (ent != null && compareMetadata(ent) == MetadataDiff.EQUAL) {
+				if (ent != null && compareMetadata(ent) == MetadataDiff.EQUAL
+						&& ((ent.getFileMode().getBits()
+								& FileMode.TYPE_MASK) != FileMode.TYPE_GITLINK)) {
 					contentIdOffset = i.idOffset();
 					contentIdFromPtr = ptr;
 					return contentId = i.idBuffer();
@@ -843,10 +845,15 @@
 		if (entry.isUpdateNeeded())
 			return MetadataDiff.DIFFER_BY_METADATA;
 
-		if (!entry.isSmudged() && entry.getLength() != (int) getEntryLength())
+		if (isModeDifferent(entry.getRawMode()))
 			return MetadataDiff.DIFFER_BY_METADATA;
 
-		if (isModeDifferent(entry.getRawMode()))
+		// Don't check for length or lastmodified on folders
+		int type = mode & FileMode.TYPE_MASK;
+		if (type == FileMode.TYPE_TREE || type == FileMode.TYPE_GITLINK)
+			return MetadataDiff.EQUAL;
+
+		if (!entry.isSmudged() && entry.getLength() != (int) getEntryLength())
 			return MetadataDiff.DIFFER_BY_METADATA;
 
 		// Git under windows only stores seconds so we round the timestamp
@@ -915,6 +922,9 @@
 			// Lets do a content check
 			return contentCheck(entry, reader);
 		case EQUAL:
+			if (mode == FileMode.SYMLINK.getBits()) {
+				return contentCheck(entry, reader);
+			}
 			return false;
 		case DIFFER_BY_METADATA:
 			if (mode == FileMode.SYMLINK.getBits())
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 253acbb..e1fd1cb 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java
@@ -64,9 +64,11 @@
 import java.util.concurrent.Executors;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
 
 import org.eclipse.jgit.annotations.Nullable;
 import org.eclipse.jgit.api.errors.JGitInternalException;
+import org.eclipse.jgit.errors.CommandFailedException;
 import org.eclipse.jgit.internal.JGitText;
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.Repository;
@@ -235,6 +237,21 @@
 	public abstract boolean supportsExecute();
 
 	/**
+	 * Does this file system support atomic file creation via
+	 * java.io.File#createNewFile()? In certain environments (e.g. on NFS) it is
+	 * not guaranteed that when two file system clients run createNewFile() in
+	 * parallel only one will succeed. In such cases both clients may think they
+	 * created a new file.
+	 *
+	 * @return true if this implementation support atomic creation of new
+	 *         Files by {@link File#createNewFile()}
+	 * @since 4.5
+	 */
+	public boolean supportsAtomicCreateNewFile() {
+		return true;
+	}
+
+	/**
 	 * Does this operating system and JRE supports symbolic links. The
 	 * capability to handle symbolic links is detected at runtime.
 	 *
@@ -453,9 +470,12 @@
 	 *            to be used to parse the command's output
 	 * @return the one-line output of the command or {@code null} if there is
 	 *         none
+	 * @throws CommandFailedException
+	 *             thrown when the command failed (return code was non-zero)
 	 */
 	@Nullable
-	protected static String readPipe(File dir, String[] command, String encoding) {
+	protected static String readPipe(File dir, String[] command,
+			String encoding) throws CommandFailedException {
 		return readPipe(dir, command, encoding, null);
 	}
 
@@ -473,10 +493,14 @@
 	 *            current process
 	 * @return the one-line output of the command or {@code null} if there is
 	 *         none
+	 * @throws CommandFailedException
+	 *             thrown when the command failed (return code was non-zero)
 	 * @since 4.0
 	 */
 	@Nullable
-	protected static String readPipe(File dir, String[] command, String encoding, Map<String, String> env) {
+	protected static String readPipe(File dir, String[] command,
+			String encoding, Map<String, String> env)
+			throws CommandFailedException {
 		final boolean debug = LOG.isDebugEnabled();
 		try {
 			if (debug) {
@@ -512,11 +536,14 @@
 					gobbler.join();
 					if (rc == 0 && !gobbler.fail.get()) {
 						return r;
+					} else {
+						if (debug) {
+							LOG.debug("readpipe rc=" + rc); //$NON-NLS-1$
+						}
+						throw new CommandFailedException(rc,
+								gobbler.errorMessage.get(),
+								gobbler.exception.get());
 					}
-					if (debug) {
-						LOG.debug("readpipe rc=" + rc); //$NON-NLS-1$
-					}
-					break;
 				} catch (InterruptedException ie) {
 					// Stop bothering me, I have a zombie to reap.
 				}
@@ -535,6 +562,8 @@
 		private final String desc;
 		private final String dir;
 		final AtomicBoolean fail = new AtomicBoolean();
+		final AtomicReference<String> errorMessage = new AtomicReference<>();
+		final AtomicReference<Throwable> exception = new AtomicReference<>();
 
 		GobblerThread(Process p, String[] command, File dir) {
 			this.p = p;
@@ -542,6 +571,7 @@
 			this.dir = Objects.toString(dir);
 		}
 
+		@Override
 		public void run() {
 			StringBuilder err = new StringBuilder();
 			try (InputStream is = p.getErrorStream()) {
@@ -551,22 +581,26 @@
 				}
 			} catch (IOException e) {
 				if (p.exitValue() != 0) {
-					logError(e);
+					setError(e, e.getMessage());
 					fail.set(true);
 				} else {
-					// ignore. git terminated faster and stream was just closed
+					// ignore. command terminated faster and stream was just closed
 				}
 			} finally {
 				if (err.length() > 0) {
-					LOG.error(err.toString());
+					setError(null, err.toString());
+					if (p.exitValue() != 0) {
+						fail.set(true);
+					}
 				}
 			}
 		}
 
-		private void logError(Throwable t) {
-			String msg = MessageFormat.format(
-					JGitText.get().exceptionCaughtDuringExcecutionOfCommand, desc, dir);
-			LOG.error(msg, t);
+		private void setError(IOException e, String message) {
+			exception.set(e);
+			errorMessage.set(MessageFormat.format(
+					JGitText.get().exceptionCaughtDuringExcecutionOfCommand,
+					desc, dir, Integer.valueOf(p.exitValue()), message));
 		}
 	}
 
@@ -589,10 +623,17 @@
 		}
 
 		// Bug 480782: Check if the discovered git executable is JGit CLI
-		String v = readPipe(gitExe.getParentFile(),
+		String v;
+		try {
+			v = readPipe(gitExe.getParentFile(),
 				new String[] { "git", "--version" }, //$NON-NLS-1$ //$NON-NLS-2$
 				Charset.defaultCharset().name());
-		if (v != null && v.startsWith("jgit")) { //$NON-NLS-1$
+		} catch (CommandFailedException e) {
+			LOG.warn(e.getMessage());
+			return null;
+		}
+		if (StringUtils.isEmptyOrNull(v)
+				|| (v != null && v.startsWith("jgit"))) { //$NON-NLS-1$
 			return null;
 		}
 
@@ -601,9 +642,15 @@
 		Map<String, String> env = new HashMap<>();
 		env.put("GIT_EDITOR", "echo"); //$NON-NLS-1$ //$NON-NLS-2$
 
-		String w = readPipe(gitExe.getParentFile(),
+		String w;
+		try {
+			w = readPipe(gitExe.getParentFile(),
 				new String[] { "git", "config", "--system", "--edit" }, //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
 				Charset.defaultCharset().name(), env);
+		} catch (CommandFailedException e) {
+			LOG.warn(e.getMessage());
+			return null;
+		}
 		if (StringUtils.isEmptyOrNull(w)) {
 			return null;
 		}
@@ -745,6 +792,22 @@
 	}
 
 	/**
+	 * Create a new file. See {@link File#createNewFile()}. Subclasses of this
+	 * class may take care to provide a safe implementation for this even if
+	 * {@link #supportsAtomicCreateNewFile()} is <code>false</code>
+	 *
+	 * @param path
+	 *            the file to be created
+	 * @return <code>true</code> if the file was created, <code>false</code> if
+	 *         the file already existed
+	 * @throws IOException
+	 * @since 4.5
+	 */
+	public boolean createNewFile(File path) throws IOException {
+		return path.createNewFile();
+	}
+
+	/**
 	 * See {@link FileUtils#relativize(String, String)}.
 	 *
 	 * @param base
@@ -990,7 +1053,16 @@
 				new StreamGobbler(inRedirect, outputStream)
 						.call();
 			}
-			outputStream.close();
+			try {
+				outputStream.close();
+			} catch (IOException e) {
+				// When the process exits before consuming the input, the OutputStream
+				// is replaced with the null output stream. This null output stream
+				// throws IOException for all write calls. When StreamGobbler fails to
+				// flush the buffer because of this, this close call tries to flush it
+				// again. This causes another IOException. Since we ignore the
+				// IOException in StreamGobbler, we also ignore the exception here.
+			}
 			return process.waitFor();
 		} catch (IOException e) {
 			ioException = e;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_POSIX.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_POSIX.java
index b4f12f4..4ecaf9c 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_POSIX.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_POSIX.java
@@ -50,6 +50,7 @@
 import java.nio.charset.Charset;
 import java.nio.file.Files;
 import java.nio.file.Path;
+import java.nio.file.Paths;
 import java.nio.file.attribute.PosixFilePermission;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -57,8 +58,14 @@
 import java.util.Set;
 
 import org.eclipse.jgit.api.errors.JGitInternalException;
+import org.eclipse.jgit.errors.CommandFailedException;
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.lib.ConfigConstants;
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.storage.file.FileBasedConfig;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 /**
  * Base FS for POSIX based systems
@@ -66,9 +73,15 @@
  * @since 3.0
  */
 public class FS_POSIX extends FS {
+	private final static Logger LOG = LoggerFactory.getLogger(FS_POSIX.class);
+
 	private static final int DEFAULT_UMASK = 0022;
 	private volatile int umask = -1;
 
+	private volatile boolean supportsUnixNLink = true;
+
+	private volatile Boolean supportsAtomicCreateNewFile;
+
 	/** Default constructor. */
 	protected FS_POSIX() {
 	}
@@ -86,6 +99,33 @@
 		}
 	}
 
+	private void determineAtomicFileCreationSupport() {
+		// @TODO: enhance SystemReader to support this without copying code
+		Boolean ret = getAtomicFileCreationSupportOption(
+				SystemReader.getInstance().openUserConfig(null, this));
+		if (ret == null && StringUtils.isEmptyOrNull(SystemReader.getInstance()
+				.getenv(Constants.GIT_CONFIG_NOSYSTEM_KEY))) {
+			ret = getAtomicFileCreationSupportOption(
+					SystemReader.getInstance().openSystemConfig(null, this));
+		}
+		supportsAtomicCreateNewFile = (ret == null) || ret;
+	}
+
+	private Boolean getAtomicFileCreationSupportOption(FileBasedConfig config) {
+		try {
+			config.load();
+			String value = config.getString(ConfigConstants.CONFIG_CORE_SECTION,
+					null,
+					ConfigConstants.CONFIG_KEY_SUPPORTSATOMICFILECREATION);
+			if (value == null) {
+				return null;
+			}
+			return Boolean.valueOf(StringUtils.toBoolean(value));
+		} catch (IOException | ConfigInvalidException e) {
+			return Boolean.TRUE;
+		}
+	}
+
 	@Override
 	public FS newInstance() {
 		return new FS_POSIX(this);
@@ -144,11 +184,18 @@
 					// On MacOSX, PATH is shorter when Eclipse is launched from the
 					// Finder than from a terminal. Therefore try to launch bash as a
 					// login shell and search using that.
-					String w = readPipe(userHome(),
+					String w;
+					try {
+						w = readPipe(userHome(),
 							new String[]{"bash", "--login", "-c", "which git"}, // //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
 							Charset.defaultCharset().name());
-					if (!StringUtils.isEmptyOrNull(w))
+					} catch (CommandFailedException e) {
+						LOG.warn(e.getMessage());
+						return null;
+					}
+					if (!StringUtils.isEmptyOrNull(w)) {
 						gitExe = new File(w);
+					}
 				}
 			}
 		}
@@ -289,4 +336,55 @@
 			return hookPath.toFile();
 		return null;
 	}
+
+	@Override
+	public boolean supportsAtomicCreateNewFile() {
+		if (supportsAtomicCreateNewFile == null) {
+			determineAtomicFileCreationSupport();
+		}
+		return supportsAtomicCreateNewFile.booleanValue();
+	}
+
+	@SuppressWarnings("boxing")
+	/**
+	 * An implementation of the File#createNewFile() semantics which works also
+	 * on NFS. If the config option
+	 * {@code core.supportsAtomicCreateNewFile = true} (which is the default)
+	 * then simply File#createNewFile() is called.
+	 *
+	 * But if {@code core.supportsAtomicCreateNewFile = false} then after
+	 * successful creation of the lock file a hardlink to that lock file is
+	 * created and the attribute nlink of the lock file is checked to be 2. If
+	 * multiple clients manage to create the same lock file nlink would be
+	 * greater than 2 showing the error.
+	 *
+	 * @see https://www.time-travellers.org/shane/papers/NFS_considered_harmful.html
+	 * @since 4.5
+	 */
+	public boolean createNewFile(File lock) throws IOException {
+		if (!lock.createNewFile()) {
+			return false;
+		}
+		if (supportsAtomicCreateNewFile() || !supportsUnixNLink) {
+			return true;
+		}
+		Path lockPath = lock.toPath();
+		Path link = Files.createLink(Paths.get(lock.getAbsolutePath() + ".lnk"), //$NON-NLS-1$
+				lockPath);
+		try {
+			Integer nlink = (Integer) (Files.getAttribute(lockPath,
+					"unix:nlink")); //$NON-NLS-1$
+			if (nlink != 2) {
+				LOG.warn("nlink of link to lock file {0} was not 2 but {1}", //$NON-NLS-1$
+						lock.getPath(), nlink);
+				return false;
+			}
+			return true;
+		} catch (UnsupportedOperationException | IllegalArgumentException e) {
+			supportsUnixNLink = false;
+			return true;
+		} finally {
+			Files.delete(link);
+		}
+	}
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_Win32.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_Win32.java
index defe14f..0e9172e 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_Win32.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_Win32.java
@@ -51,6 +51,10 @@
 import java.util.Arrays;
 import java.util.List;
 
+import org.eclipse.jgit.errors.CommandFailedException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
 
 /**
  * FS implementation for Windows
@@ -58,6 +62,7 @@
  * @since 3.0
  */
 public class FS_Win32 extends FS {
+	private final static Logger LOG = LoggerFactory.getLogger(FS_Win32.class);
 
 	private volatile Boolean supportSymlinks;
 
@@ -113,12 +118,19 @@
 			if (searchPath(path, "bash.exe") != null) { //$NON-NLS-1$
 				// This isn't likely to work, but its worth trying:
 				// If bash is in $PATH, git should also be in $PATH.
-				String w = readPipe(userHome(),
+				String w;
+				try {
+					w = readPipe(userHome(),
 						new String[]{"bash", "--login", "-c", "which git"}, // //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
 						Charset.defaultCharset().name());
-				if (!StringUtils.isEmptyOrNull(w))
+				} catch (CommandFailedException e) {
+					LOG.warn(e.getMessage());
+					return null;
+				}
+				if (!StringUtils.isEmptyOrNull(w)) {
 					// The path may be in cygwin/msys notation so resolve it right away
 					gitExe = resolve(null, w);
+				}
 			}
 		}
 
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_Win32_Cygwin.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_Win32_Cygwin.java
index ec581b3..f8ea5d0 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_Win32_Cygwin.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_Win32_Cygwin.java
@@ -54,8 +54,11 @@
 import java.util.List;
 
 import org.eclipse.jgit.api.errors.JGitInternalException;
+import org.eclipse.jgit.errors.CommandFailedException;
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.Repository;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 /**
  * FS implementation for Cygwin on Windows
@@ -63,6 +66,9 @@
  * @since 3.0
  */
 public class FS_Win32_Cygwin extends FS_Win32 {
+	private final static Logger LOG = LoggerFactory
+			.getLogger(FS_Win32_Cygwin.class);
+
 	private static String cygpath;
 
 	/**
@@ -107,11 +113,18 @@
 	public File resolve(final File dir, final String pn) {
 		String useCygPath = System.getProperty("jgit.usecygpath"); //$NON-NLS-1$
 		if (useCygPath != null && useCygPath.equals("true")) { //$NON-NLS-1$
-			String w = readPipe(dir, //
+			String w;
+			try {
+				w = readPipe(dir, //
 					new String[] { cygpath, "--windows", "--absolute", pn }, // //$NON-NLS-1$ //$NON-NLS-2$
 					"UTF-8"); //$NON-NLS-1$
-			if (w != null)
+			} catch (CommandFailedException e) {
+				LOG.warn(e.getMessage());
+				return null;
+			}
+			if (!StringUtils.isEmptyOrNull(w)) {
 				return new File(w);
+			}
 		}
 		return super.resolve(dir, pn);
 	}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/HttpSupport.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/HttpSupport.java
index 7cb2bf6..202645b 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/HttpSupport.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/HttpSupport.java
@@ -190,7 +190,7 @@
 			return c.getResponseCode();
 		} catch (ConnectException ce) {
 			final URL url = c.getURL();
-			final String host = (url == null) ? "<null>" : url.getHost();
+			final String host = (url == null) ? "<null>" : url.getHost(); //$NON-NLS-1$
 			// The standard J2SE error message is not very useful.
 			//
 			if ("Connection timed out: connect".equals(ce.getMessage())) //$NON-NLS-1$
@@ -218,7 +218,7 @@
 			return c.getResponseCode();
 		} catch (ConnectException ce) {
 			final URL url = c.getURL();
-			final String host = (url == null) ? "<null>" : url.getHost();
+			final String host = (url == null) ? "<null>" : url.getHost(); //$NON-NLS-1$
 			// The standard J2SE error message is not very useful.
 			//
 			if ("Connection timed out: connect".equals(ce.getMessage())) //$NON-NLS-1$
diff --git a/pom.xml b/pom.xml
index d38d156..0afffc1 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.4.2-SNAPSHOT</version>
+  <version>4.5.5-SNAPSHOT</version>
 
   <name>JGit - Parent</name>
   <url>${jgit-url}</url>
@@ -210,6 +210,7 @@
     <maven-javadoc-plugin-version>2.10.3</maven-javadoc-plugin-version>
     <tycho-extras-version>0.25.0</tycho-extras-version>
     <gson-version>2.2.4</gson-version>
+    <spotbugs-maven-plugin-version>3.1.6</spotbugs-maven-plugin-version>
 
     <!-- Properties to enable jacoco code coverage analysis -->
     <sonar.core.codeCoveragePlugin>jacoco</sonar.core.codeCoveragePlugin>
@@ -323,9 +324,9 @@
         </plugin>
 
         <plugin>
-          <groupId>org.codehaus.mojo</groupId>
-          <artifactId>findbugs-maven-plugin</artifactId>
-          <version>3.0.3</version>
+          <groupId>com.github.spotbugs</groupId>
+          <artifactId>spotbugs-maven-plugin</artifactId>
+          <version>${spotbugs-maven-plugin-version}</version>
           <configuration>
             <findbugsXmlOutput>true</findbugsXmlOutput>
             <failOnError>false</failOnError>
@@ -528,9 +529,9 @@
         <version>2.5</version>
       </plugin>
       <plugin>
-        <groupId>org.codehaus.mojo</groupId>
-        <artifactId>findbugs-maven-plugin</artifactId>
-        <version>3.0.3</version>
+        <groupId>com.github.spotbugs</groupId>
+        <artifactId>spotbugs-maven-plugin</artifactId>
+        <version>${spotbugs-maven-plugin-version}</version>
       </plugin>
       <plugin>
         <groupId>org.apache.maven.plugins</groupId>
@@ -689,14 +690,25 @@
           </plugin>
         </plugins>
       </build>
+      <reporting>
+        <plugins>
+          <plugin>
+            <groupId>org.apache.maven.plugins</groupId>
+            <artifactId>maven-javadoc-plugin</artifactId>
+            <configuration>
+              <additionalparam>-Xdoclint:-missing</additionalparam>
+            </configuration>
+          </plugin>
+        </plugins>
+      </reporting>
     </profile>
     <profile>
       <id>static-checks</id>
       <build>
         <plugins>
           <plugin>
-            <groupId>org.codehaus.mojo</groupId>
-            <artifactId>findbugs-maven-plugin</artifactId>
+            <groupId>com.github.spotbugs</groupId>
+            <artifactId>spotbugs-maven-plugin</artifactId>
           </plugin>
           <plugin>
             <groupId>org.apache.maven.plugins</groupId>