Merge branch 'stable-4.1' into stable-4.2

* stable-4.1:
  JGit v4.0.3.201509231615-r

Change-Id: I6cc5bcefad2e8dee3394770d36608f981bfc9a9e
Signed-off-by: David Pursehouse <david.pursehouse@gmail.com>
diff --git a/.buckconfig b/.buckconfig
new file mode 100644
index 0000000..b2e07ac
--- /dev/null
+++ b/.buckconfig
@@ -0,0 +1,15 @@
+[buildfile]
+  includes = //tools/default.defs
+
+[java]
+  src_roots = src, resources, tst
+
+[project]
+  ignore = .git
+
+[cache]
+  mode = dir
+
+[download]
+  maven_repo = http://repo1.maven.org/maven2
+  in_build = true
diff --git a/.buckversion b/.buckversion
new file mode 100644
index 0000000..9daac2c
--- /dev/null
+++ b/.buckversion
@@ -0,0 +1 @@
+1b03b4313b91b634bd604fc3487a05f877e59dee
diff --git a/.gitignore b/.gitignore
index ea8c4bf..6c62199 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,4 @@
 /target
+/.project
+/buck-cache
+/buck-out
diff --git a/.mailmap b/.mailmap
index 8701b19..8f11a1c 100644
--- a/.mailmap
+++ b/.mailmap
@@ -1,5 +1,6 @@
-Shawn Pearce <spearce@spearce.org>	Shawn O. Pearce <sop@google.com>
-Shawn Pearce <spearce@spearce.org>	Shawn Pearce <sop@google.com>
-Shawn Pearce <spearce@spearce.org>	Shawn O. Pearce <spearce@spearce.org>
-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>
+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>
+Shawn Pearce <spearce@spearce.org>           Shawn O. Pearce <sop@google.com>
+Shawn Pearce <spearce@spearce.org>           Shawn Pearce <sop@google.com>
+Shawn Pearce <spearce@spearce.org>           Shawn O. Pearce <spearce@spearce.org>
diff --git a/BUCK b/BUCK
new file mode 100644
index 0000000..f19b7bd
--- /dev/null
+++ b/BUCK
@@ -0,0 +1,45 @@
+java_library(
+  name = 'jgit',
+  exported_deps = ['//org.eclipse.jgit:jgit'],
+  visibility = ['PUBLIC'],
+)
+
+genrule(
+  name = 'jgit_src',
+  cmd = 'ln -s $(location //org.eclipse.jgit:jgit_src) $OUT',
+  out = 'jgit_src.zip',
+  visibility = ['PUBLIC'],
+)
+
+java_library(
+  name = 'jgit-servlet',
+  exported_deps = [
+    ':jgit',
+    '//org.eclipse.jgit.http.server:jgit-servlet'
+  ],
+  visibility = ['PUBLIC'],
+)
+
+java_library(
+  name = 'jgit-archive',
+  exported_deps = [
+    ':jgit',
+    '//org.eclipse.jgit.archive:jgit-archive'
+  ],
+  visibility = ['PUBLIC'],
+)
+
+java_library(
+  name = 'junit',
+  exported_deps = [
+    ':jgit',
+    '//org.eclipse.jgit.junit:junit'
+  ],
+  visibility = ['PUBLIC'],
+)
+
+genrule(
+  name = 'jgit_bin',
+  cmd = 'ln -s $(location //org.eclipse.jgit.pgm:jgit) $OUT',
+  out = 'jgit_bin',
+)
diff --git a/lib/BUCK b/lib/BUCK
new file mode 100644
index 0000000..524612b
--- /dev/null
+++ b/lib/BUCK
@@ -0,0 +1,125 @@
+maven_jar(
+  name = 'jsch',
+  bin_sha1 = '658b682d5c817b27ae795637dfec047c63d29935',
+  src_sha1 = '791359d94d6edcace686a56d0727ee093a2f7c33',
+  group = 'com.jcraft',
+  artifact = 'jsch',
+  version = '0.1.53',
+)
+
+maven_jar(
+  name = 'javaewah',
+  bin_sha1 = 'eceaf316a8faf0e794296ebe158ae110c7d72a5a',
+  src_sha1 = 'a50d78eb630e05439461f3130b94b3bcd1ea6f03',
+  group = 'com.googlecode.javaewah',
+  artifact = 'JavaEWAH',
+  version = '0.7.9',
+)
+
+maven_jar(
+  name = 'httpcomponents',
+  bin_sha1 = '4c47155e3e6c9a41a28db36680b828ced53b8af4',
+  src_sha1 = 'af4d76be0c46ee26b0d9d1d4a34d244a633cac84',
+  group = 'org.apache.httpcomponents',
+  artifact = 'httpclient',
+  version = '4.3.6',
+)
+
+maven_jar(
+  name = 'httpcore',
+  bin_sha1 = 'f91b7a4aadc5cf486df6e4634748d7dd7a73f06d',
+  src_sha1 = '1b0aa62a6a91e9fa00c16f0a4a2c874804ed3b1e',
+  group = 'org.apache.httpcomponents',
+  artifact = 'httpcore',
+  version = '4.3.3',
+)
+
+maven_jar(
+  name = 'commons-logging',
+  bin_sha1 = 'f6f66e966c70a83ffbdb6f17a0919eaf7c8aca7f',
+  src_sha1 = '28bb0405fddaf04f15058fbfbe01fe2780d7d3b6',
+  group = 'commons-logging',
+  artifact = 'commons-logging',
+  version = '1.1.3',
+)
+
+maven_jar(
+  name = 'slf4j-api',
+  bin_sha1 = '0081d61b7f33ebeab314e07de0cc596f8e858d97',
+  src_sha1 = '58d38f68d4a867d4552ae27960bb348d7eaa1297',
+  group = 'org.slf4j',
+  artifact = 'slf4j-api',
+  version = '1.7.2',
+)
+
+maven_jar(
+  name = 'slf4j-simple',
+  bin_sha1 = '760055906d7353ba4f7ce1b8908bc6b2e91f39fa',
+  src_sha1 = '09474919128b3a7fcf21a5f9c907f5251f234544',
+  group = 'org.slf4j',
+  artifact = 'slf4j-simple',
+  version = '1.7.2',
+)
+
+maven_jar(
+  name = 'servlet-api',
+  bin_sha1 = '3cd63d075497751784b2fa84be59432f4905bf7c',
+  src_sha1 = 'ab3976d4574c48d22dc1abf6a9e8bd0fdf928223',
+  group = 'javax.servlet',
+  artifact = 'javax.servlet-api',
+  version = '3.1.0',
+)
+
+maven_jar(
+  name = 'commons-compress',
+  bin_sha1 = 'c7d9b580aff9e9f1998361f16578e63e5c064699',
+  src_sha1 = '396b81bdfd0fb617178e1707ef64832215307c78',
+  group = 'org.apache.commons',
+  artifact = 'commons-compress',
+  version = '1.6',
+)
+
+maven_jar(
+  name = 'tukaani-xz',
+  bin_sha1 = '66db21c8484120cb6a51b5b3ea47b6f383942bec',
+  src_sha1 = '6396220725701d767c553902c41120d7bf38e9f5',
+  group = 'org.tukaani',
+  artifact = 'xz',
+  version = '1.3',
+)
+
+maven_jar(
+  name = 'args4j',
+  bin_sha1 = '139441471327b9cc6d56436cb2a31e60eb6ed2ba',
+  src_sha1 = '22631b78cc8f60a6918557e8cbdb33e90f63a77f',
+  group = 'args4j',
+  artifact = 'args4j',
+  version = '2.0.15',
+)
+
+maven_jar(
+  name = 'junit',
+  bin_sha1 = '4e031bb61df09069aeb2bffb4019e7a5034a4ee0',
+  src_sha1 = '28e0ad201304e4a4abf999ca0570b7cffc352c3c',
+  group = 'junit',
+  artifact = 'junit',
+  version = '4.11',
+)
+
+maven_jar(
+  name = 'hamcrest-library',
+  bin_sha1 = '4785a3c21320980282f9f33d0d1264a69040538f',
+  src_sha1 = '047a7ee46628ab7133129cd7cef1e92657bc275e',
+  group = 'org.hamcrest',
+  artifact = 'hamcrest-library',
+  version = '1.3',
+)
+
+maven_jar(
+  name = 'hamcrest-core',
+  bin_sha1 = '42a25dc3219429f0e5d060061f71acb49bf010a0',
+  src_sha1 = '1dc37250fbc78e23a65a67fbbaf71d2e9cbc3c0b',
+  group = 'org.hamcrest',
+  artifact = 'hamcrest-core',
+  version = '1.3',
+)
diff --git a/lib/jetty/BUCK b/lib/jetty/BUCK
new file mode 100644
index 0000000..6e7dec3
--- /dev/null
+++ b/lib/jetty/BUCK
@@ -0,0 +1,56 @@
+VERSION = '9.2.13.v20150730'
+GROUP = 'org.eclipse.jetty'
+
+maven_jar(
+  name = 'servlet',
+  bin_sha1 = '5ad6e38015a97ae9a60b6c2ad744ccfa9cf93a50',
+  src_sha1 = '78fbec19321150552d91f9e079c2f2ca33222b01',
+  group = GROUP,
+  artifact = 'jetty-servlet',
+  version = VERSION,
+)
+
+maven_jar(
+  name = 'security',
+  bin_sha1 = 'cc7c7f27ec4cc279253be1675d9e47e58b995943',
+  src_sha1 = '75632ebdf8bd651faafb97106c92496db59e165d',
+  group = GROUP,
+  artifact = 'jetty-security',
+  version = VERSION,
+)
+
+maven_jar(
+  name = 'server',
+  bin_sha1 = '5be7d1da0a7abffd142de3091d160717c120b6ab',
+  src_sha1 = '203e123f83efe2a5b8a9c74854c7897fe3563302',
+  group = GROUP,
+  artifact = 'jetty-server',
+  version = VERSION,
+)
+
+maven_jar(
+  name = 'http',
+  bin_sha1 = '23a745d9177ef67ef53cc46b9b70c5870082efc2',
+  src_sha1 = '5f87f7ff2057cd4b0995bc4fffe17b2aff64c130',
+  group = GROUP,
+  artifact = 'jetty-http',
+  version = VERSION,
+)
+
+maven_jar(
+  name = 'io',
+  bin_sha1 = '7a351e6a1b63dfd56b6632623f7ca2793ffb67ad',
+  src_sha1 = 'bbd61a84b748fc295456e1c5c3070aaf40a68f62',
+  group = GROUP,
+  artifact = 'jetty-io',
+  version = VERSION,
+)
+
+maven_jar(
+  name = 'util',
+  bin_sha1 = 'c101476360a7cdd0670462de04053507d5e70c97',
+  src_sha1 = '15ceecce141971b4e0facb861b3d10120ad6ce03',
+  group = GROUP,
+  artifact = 'jetty-util',
+  version = VERSION,
+)
diff --git a/org.eclipse.jgit.ant.test/META-INF/MANIFEST.MF b/org.eclipse.jgit.ant.test/META-INF/MANIFEST.MF
index 83051f9..cfaefcd 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.1.2.201602141800-r
+Bundle-Version: 4.2.1.qualifier
 Bundle-ActivationPolicy: lazy
 Bundle-RequiredExecutionEnvironment: JavaSE-1.7
 Import-Package: org.apache.tools.ant,
- org.eclipse.jgit.ant.tasks;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.internal.storage.file;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.junit;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.lib;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.util;version="[4.1.2,4.2.0)",
+ org.eclipse.jgit.ant.tasks;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.internal.storage.file;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.junit;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.lib;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.util;version="[4.2.1,4.3.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 67e6c38..f1e7e36 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.1.2.201602141800-r</version>
+    <version>4.2.1-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.ant.test</artifactId>
diff --git a/org.eclipse.jgit.ant/META-INF/MANIFEST.MF b/org.eclipse.jgit.ant/META-INF/MANIFEST.MF
index 5ff9fd5..74720fe 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.1.2.201602141800-r
+Bundle-Version: 4.2.1.qualifier
 Bundle-RequiredExecutionEnvironment: JavaSE-1.7
 Import-Package: org.apache.tools.ant,
-  org.eclipse.jgit.storage.file;version="[4.1.2,4.2.0)"
+  org.eclipse.jgit.storage.file;version="[4.2.1,4.3.0)"
 Bundle-Localization: plugin
 Bundle-Vendor: %Provider-Name
-Export-Package: org.eclipse.jgit.ant.tasks;version="4.1.2";
+Export-Package: org.eclipse.jgit.ant.tasks;version="4.2.1";
  uses:="org.apache.tools.ant.types,org.apache.tools.ant"
diff --git a/org.eclipse.jgit.ant/pom.xml b/org.eclipse.jgit.ant/pom.xml
index f40e0f7..0e69457 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.1.2.201602141800-r</version>
+		<version>4.2.1-SNAPSHOT</version>
 	</parent>
 
 	<artifactId>org.eclipse.jgit.ant</artifactId>
@@ -102,24 +102,92 @@
 				</configuration>
 			</plugin>
 
-			<plugin>
-				<groupId>org.codehaus.mojo</groupId>
-				<artifactId>clirr-maven-plugin</artifactId>
-			</plugin>
+      <plugin>
+          <groupId>com.github.siom79.japicmp</groupId>
+          <artifactId>japicmp-maven-plugin</artifactId>
+          <version>${japicmp-version}</version>
+          <configuration>
+              <oldVersion>
+                  <dependency>
+                      <groupId>${project.groupId}</groupId>
+                      <artifactId>${project.artifactId}</artifactId>
+                      <version>${jgit-last-release-version}</version>
+                  </dependency>
+              </oldVersion>
+              <newVersion>
+                  <file>
+                      <path>${project.build.directory}/${project.artifactId}-${project.version}.jar</path>
+                  </file>
+              </newVersion>
+              <parameter>
+                  <onlyModified>true</onlyModified>
+                  <includes>
+                      <include>org.eclipse.jgit.*</include>
+                  </includes>
+                  <accessModifier>public</accessModifier>
+                  <breakBuildOnModifications>false</breakBuildOnModifications>
+                  <breakBuildOnBinaryIncompatibleModifications>false</breakBuildOnBinaryIncompatibleModifications>
+                  <onlyBinaryIncompatible>false</onlyBinaryIncompatible>
+                  <includeSynthetic>false</includeSynthetic>
+                  <ignoreMissingClasses>false</ignoreMissingClasses>
+                  <skipPomModules>true</skipPomModules>
+              </parameter>
+              <skip>false</skip>
+          </configuration>
+          <executions>
+            <execution>
+             <phase>verify</phase>
+             <goals>
+               <goal>cmp</goal>
+             </goals>
+          </execution>
+        </executions>
+      </plugin>
 		</plugins>
 	</build>
 
 	<reporting>
-		<plugins>
-			<plugin>
-				<groupId>org.codehaus.mojo</groupId>
-				<artifactId>clirr-maven-plugin</artifactId>
-				<version>${clirr-version}</version>
-				<configuration>
-					<comparisonVersion>${jgit-last-release-version}</comparisonVersion>
-					<minSeverity>info</minSeverity>
-				</configuration>
-			</plugin>
-		</plugins>
-	</reporting>
+    <plugins>
+      <plugin>
+          <groupId>com.github.siom79.japicmp</groupId>
+          <artifactId>japicmp-maven-plugin</artifactId>
+          <version>${japicmp-version}</version>
+          <reportSets>
+              <reportSet>
+                  <reports>
+                      <report>cmp-report</report>
+                  </reports>
+              </reportSet>
+          </reportSets>
+          <configuration>
+              <oldVersion>
+                  <dependency>
+                      <groupId>${project.groupId}</groupId>
+                      <artifactId>${project.artifactId}</artifactId>
+                      <version>${jgit-last-release-version}</version>
+                  </dependency>
+              </oldVersion>
+              <newVersion>
+                  <file>
+                      <path>${project.build.directory}/${project.artifactId}-${project.version}.jar</path>
+                  </file>
+              </newVersion>
+              <parameter>
+                  <onlyModified>true</onlyModified>
+                  <includes>
+                      <include>org.eclipse.jgit.*</include>
+                  </includes>
+                  <accessModifier>public</accessModifier>
+                  <breakBuildOnModifications>false</breakBuildOnModifications>
+                  <breakBuildOnBinaryIncompatibleModifications>false</breakBuildOnBinaryIncompatibleModifications>
+                  <onlyBinaryIncompatible>false</onlyBinaryIncompatible>
+                  <includeSynthetic>false</includeSynthetic>
+                  <ignoreMissingClasses>false</ignoreMissingClasses>
+                  <skipPomModules>true</skipPomModules>
+              </parameter>
+              <skip>false</skip>
+          </configuration>
+      </plugin>
+    </plugins>
+  </reporting>
 </project>
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 4e28e0b..45d6d2c 100644
--- a/org.eclipse.jgit.archive/.settings/org.eclipse.jdt.core.prefs
+++ b/org.eclipse.jgit.archive/.settings/org.eclipse.jdt.core.prefs
@@ -1,9 +1,9 @@
 eclipse.preferences.version=1
 org.eclipse.jdt.core.compiler.annotation.inheritNullAnnotations=enabled
 org.eclipse.jdt.core.compiler.annotation.missingNonNullByDefaultAnnotation=ignore
-org.eclipse.jdt.core.compiler.annotation.nonnull=org.eclipse.jdt.annotation.NonNull
-org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=org.eclipse.jdt.annotation.NonNullByDefault
-org.eclipse.jdt.core.compiler.annotation.nullable=org.eclipse.jdt.annotation.Nullable
+org.eclipse.jdt.core.compiler.annotation.nonnull=org.eclipse.jgit.annotations.NonNull
+org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=org.eclipse.jgit.annotations.NonNullByDefault
+org.eclipse.jdt.core.compiler.annotation.nullable=org.eclipse.jgit.annotations.Nullable
 org.eclipse.jdt.core.compiler.annotation.nullanalysis=enabled
 org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
 org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate
diff --git a/org.eclipse.jgit.archive/BUCK b/org.eclipse.jgit.archive/BUCK
new file mode 100644
index 0000000..ae17032
--- /dev/null
+++ b/org.eclipse.jgit.archive/BUCK
@@ -0,0 +1,13 @@
+java_library(
+  name = 'jgit-archive',
+  srcs = glob(
+    ['src/**'],
+    excludes = ['src/org/eclipse/jgit/archive/FormatActivator.java'],
+  ),
+  resources = glob(['resources/**']),
+  provided_deps = [
+    '//org.eclipse.jgit:jgit',
+    '//lib:commons-compress',
+  ],
+  visibility = ['PUBLIC'],
+)
diff --git a/org.eclipse.jgit.archive/META-INF/MANIFEST.MF b/org.eclipse.jgit.archive/META-INF/MANIFEST.MF
index 3787f86..4f3a61f 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.1.2.201602141800-r
+Bundle-Version: 4.2.1.qualifier
 Bundle-Vendor: %provider_name
 Bundle-Localization: plugin
 Bundle-RequiredExecutionEnvironment: JavaSE-1.7
@@ -12,16 +12,15 @@
  org.apache.commons.compress.compressors.bzip2;version="[1.4,2.0)",
  org.apache.commons.compress.compressors.gzip;version="[1.4,2.0)",
  org.apache.commons.compress.compressors.xz;version="[1.4,2.0)",
- org.eclipse.jgit.api;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.lib;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.nls;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.util;version="[4.1.2,4.2.0)",
+ org.eclipse.jgit.api;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.lib;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.nls;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.util;version="[4.2.1,4.3.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.1.2";
+Export-Package: org.eclipse.jgit.archive;version="4.2.1";
   uses:="org.eclipse.jgit.lib,
    org.eclipse.jgit.api,
    org.apache.commons.compress.archivers,
    org.osgi.framework"
-Require-Bundle: org.eclipse.jdt.annotation;bundle-version="[1.1.0,2.0.0)";resolution:=optional
diff --git a/org.eclipse.jgit.archive/META-INF/SOURCE-MANIFEST.MF b/org.eclipse.jgit.archive/META-INF/SOURCE-MANIFEST.MF
index d1f674d..61fbb7e 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.1.2.201602141800-r
-Eclipse-SourceBundle: org.eclipse.jgit.archive;version="4.1.2.201602141800-r";roots="."
+Bundle-Version: 4.2.1.qualifier
+Eclipse-SourceBundle: org.eclipse.jgit.archive;version="4.2.1.qualifier";roots="."
diff --git a/org.eclipse.jgit.archive/pom.xml b/org.eclipse.jgit.archive/pom.xml
index 377d812..b9c509f 100644
--- a/org.eclipse.jgit.archive/pom.xml
+++ b/org.eclipse.jgit.archive/pom.xml
@@ -50,7 +50,7 @@
   <parent>
     <groupId>org.eclipse.jgit</groupId>
     <artifactId>org.eclipse.jgit-parent</artifactId>
-    <version>4.1.2.201602141800-r</version>
+    <version>4.2.1-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.archive</artifactId>
@@ -106,6 +106,93 @@
           </archive>
         </configuration>
       </plugin>
+
+      <plugin>
+          <groupId>com.github.siom79.japicmp</groupId>
+          <artifactId>japicmp-maven-plugin</artifactId>
+          <version>${japicmp-version}</version>
+          <configuration>
+              <oldVersion>
+                  <dependency>
+                      <groupId>${project.groupId}</groupId>
+                      <artifactId>${project.artifactId}</artifactId>
+                      <version>${jgit-last-release-version}</version>
+                  </dependency>
+              </oldVersion>
+              <newVersion>
+                  <file>
+                      <path>${project.build.directory}/${project.artifactId}-${project.version}.jar</path>
+                  </file>
+              </newVersion>
+              <parameter>
+                  <onlyModified>true</onlyModified>
+                  <includes>
+                      <include>org.eclipse.jgit.*</include>
+                  </includes>
+                  <accessModifier>public</accessModifier>
+                  <breakBuildOnModifications>false</breakBuildOnModifications>
+                  <breakBuildOnBinaryIncompatibleModifications>false</breakBuildOnBinaryIncompatibleModifications>
+                  <onlyBinaryIncompatible>false</onlyBinaryIncompatible>
+                  <includeSynthetic>false</includeSynthetic>
+                  <ignoreMissingClasses>false</ignoreMissingClasses>
+                  <skipPomModules>true</skipPomModules>
+              </parameter>
+              <skip>false</skip>
+          </configuration>
+          <executions>
+            <execution>
+             <phase>verify</phase>
+             <goals>
+               <goal>cmp</goal>
+             </goals>
+          </execution>
+        </executions>
+      </plugin>
     </plugins>
   </build>
+
+  <reporting>
+    <plugins>
+      <plugin>
+          <groupId>com.github.siom79.japicmp</groupId>
+          <artifactId>japicmp-maven-plugin</artifactId>
+          <version>${japicmp-version}</version>
+          <reportSets>
+              <reportSet>
+                  <reports>
+                      <report>cmp-report</report>
+                  </reports>
+              </reportSet>
+          </reportSets>
+          <configuration>
+              <oldVersion>
+                  <dependency>
+                      <groupId>${project.groupId}</groupId>
+                      <artifactId>${project.artifactId}</artifactId>
+                      <version>${jgit-last-release-version}</version>
+                  </dependency>
+              </oldVersion>
+              <newVersion>
+                  <file>
+                      <path>${project.build.directory}/${project.artifactId}-${project.version}.jar</path>
+                  </file>
+              </newVersion>
+              <parameter>
+                  <onlyModified>true</onlyModified>
+                  <includes>
+                      <include>org.eclipse.jgit.*</include>
+                  </includes>
+                  <accessModifier>public</accessModifier>
+                  <breakBuildOnModifications>false</breakBuildOnModifications>
+                  <breakBuildOnBinaryIncompatibleModifications>false</breakBuildOnBinaryIncompatibleModifications>
+                  <onlyBinaryIncompatible>false</onlyBinaryIncompatible>
+                  <includeSynthetic>false</includeSynthetic>
+                  <ignoreMissingClasses>false</ignoreMissingClasses>
+                  <skipPomModules>true</skipPomModules>
+              </parameter>
+              <skip>false</skip>
+          </configuration>
+      </plugin>
+    </plugins>
+  </reporting>
 </project>
diff --git a/org.eclipse.jgit.http.apache/BUCK b/org.eclipse.jgit.http.apache/BUCK
new file mode 100644
index 0000000..f48f33a
--- /dev/null
+++ b/org.eclipse.jgit.http.apache/BUCK
@@ -0,0 +1,12 @@
+java_library(
+  name = 'http-apache',
+  srcs = glob(['src/**']),
+  resources = glob(['resources/**']),
+  deps = [
+    '//org.eclipse.jgit:jgit',
+    '//lib:commons-logging',
+    '//lib:httpcomponents',
+    '//lib:httpcore',
+  ],
+  visibility = ['PUBLIC'],
+)
diff --git a/org.eclipse.jgit.http.apache/META-INF/MANIFEST.MF b/org.eclipse.jgit.http.apache/META-INF/MANIFEST.MF
index 182262f..b775549 100644
--- a/org.eclipse.jgit.http.apache/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.http.apache/META-INF/MANIFEST.MF
@@ -2,12 +2,13 @@
 Bundle-ManifestVersion: 2
 Bundle-Name: %Bundle-Name
 Bundle-SymbolicName: org.eclipse.jgit.http.apache
-Bundle-Version: 4.1.2.201602141800-r
+Bundle-Version: 4.2.1.qualifier
 Bundle-RequiredExecutionEnvironment: JavaSE-1.7
 Bundle-Localization: plugin
 Bundle-Vendor: %Provider-Name
 Bundle-ActivationPolicy: lazy
-Import-Package: org.apache.http;version="[4.1.0,5.0.0)",
+Import-Package: org.apache.commons.logging;version="[1.1.1,2.0.0)",
+ org.apache.http;version="[4.1.0,5.0.0)",
  org.apache.http.client;version="[4.1.0,5.0.0)",
  org.apache.http.client.methods;version="[4.1.0,5.0.0)",
  org.apache.http.client.params;version="[4.1.0,5.0.0)",
@@ -19,10 +20,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.1.2,4.2.0)",
- org.eclipse.jgit.transport.http;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.util;version="[4.1.2,4.2.0)"
-Export-Package: org.eclipse.jgit.transport.http.apache;version="4.1.2";
+ org.eclipse.jgit.nls;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.transport.http;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.util;version="[4.2.1,4.3.0)"
+Export-Package: org.eclipse.jgit.transport.http.apache;version="4.2.1";
   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 7510088..e2df65b 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.1.2.201602141800-r</version>
+		<version>4.2.1-SNAPSHOT</version>
 	</parent>
 
 	<artifactId>org.eclipse.jgit.http.apache</artifactId>
@@ -96,24 +96,92 @@
 				</configuration>
 			</plugin>
 
-			<plugin>
-				<groupId>org.codehaus.mojo</groupId>
-				<artifactId>clirr-maven-plugin</artifactId>
-			</plugin>
+      <plugin>
+          <groupId>com.github.siom79.japicmp</groupId>
+          <artifactId>japicmp-maven-plugin</artifactId>
+          <version>${japicmp-version}</version>
+          <configuration>
+              <oldVersion>
+                  <dependency>
+                      <groupId>${project.groupId}</groupId>
+                      <artifactId>${project.artifactId}</artifactId>
+                      <version>${jgit-last-release-version}</version>
+                  </dependency>
+              </oldVersion>
+              <newVersion>
+                  <file>
+                      <path>${project.build.directory}/${project.artifactId}-${project.version}.jar</path>
+                  </file>
+              </newVersion>
+              <parameter>
+                  <onlyModified>true</onlyModified>
+                  <includes>
+                      <include>org.eclipse.jgit.*</include>
+                  </includes>
+                  <accessModifier>public</accessModifier>
+                  <breakBuildOnModifications>false</breakBuildOnModifications>
+                  <breakBuildOnBinaryIncompatibleModifications>false</breakBuildOnBinaryIncompatibleModifications>
+                  <onlyBinaryIncompatible>false</onlyBinaryIncompatible>
+                  <includeSynthetic>false</includeSynthetic>
+                  <ignoreMissingClasses>false</ignoreMissingClasses>
+                  <skipPomModules>true</skipPomModules>
+              </parameter>
+              <skip>false</skip>
+          </configuration>
+          <executions>
+            <execution>
+             <phase>verify</phase>
+             <goals>
+               <goal>cmp</goal>
+             </goals>
+          </execution>
+        </executions>
+      </plugin>
 		</plugins>
 	</build>
 
 	<reporting>
-		<plugins>
-			<plugin>
-				<groupId>org.codehaus.mojo</groupId>
-				<artifactId>clirr-maven-plugin</artifactId>
-				<version>${clirr-version}</version>
-				<configuration>
-					<comparisonVersion>${jgit-last-release-version}</comparisonVersion>
-					<minSeverity>info</minSeverity>
-				</configuration>
-			</plugin>
-		</plugins>
-	</reporting>
+    <plugins>
+      <plugin>
+          <groupId>com.github.siom79.japicmp</groupId>
+          <artifactId>japicmp-maven-plugin</artifactId>
+          <version>${japicmp-version}</version>
+          <reportSets>
+              <reportSet>
+                  <reports>
+                      <report>cmp-report</report>
+                  </reports>
+              </reportSet>
+          </reportSets>
+          <configuration>
+              <oldVersion>
+                  <dependency>
+                      <groupId>${project.groupId}</groupId>
+                      <artifactId>${project.artifactId}</artifactId>
+                      <version>${jgit-last-release-version}</version>
+                  </dependency>
+              </oldVersion>
+              <newVersion>
+                  <file>
+                      <path>${project.build.directory}/${project.artifactId}-${project.version}.jar</path>
+                  </file>
+              </newVersion>
+              <parameter>
+                  <onlyModified>true</onlyModified>
+                  <includes>
+                      <include>org.eclipse.jgit.*</include>
+                  </includes>
+                  <accessModifier>public</accessModifier>
+                  <breakBuildOnModifications>false</breakBuildOnModifications>
+                  <breakBuildOnBinaryIncompatibleModifications>false</breakBuildOnBinaryIncompatibleModifications>
+                  <onlyBinaryIncompatible>false</onlyBinaryIncompatible>
+                  <includeSynthetic>false</includeSynthetic>
+                  <ignoreMissingClasses>false</ignoreMissingClasses>
+                  <skipPomModules>true</skipPomModules>
+              </parameter>
+              <skip>false</skip>
+          </configuration>
+      </plugin>
+    </plugins>
+  </reporting>
 </project>
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 d42d6f2..de81bf8 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
@@ -100,7 +100,7 @@
 public class HttpClientConnection implements HttpConnection {
 	HttpClient client;
 
-	String urlStr;
+	URL url;
 
 	HttpUriRequest req;
 
@@ -176,16 +176,19 @@
 
 	/**
 	 * @param urlStr
+	 * @throws MalformedURLException
 	 */
-	public HttpClientConnection(String urlStr) {
+	public HttpClientConnection(String urlStr) throws MalformedURLException {
 		this(urlStr, null);
 	}
 
 	/**
 	 * @param urlStr
 	 * @param proxy
+	 * @throws MalformedURLException
 	 */
-	public HttpClientConnection(String urlStr, Proxy proxy) {
+	public HttpClientConnection(String urlStr, Proxy proxy)
+			throws MalformedURLException {
 		this(urlStr, proxy, null);
 	}
 
@@ -193,10 +196,12 @@
 	 * @param urlStr
 	 * @param proxy
 	 * @param cl
+	 * @throws MalformedURLException
 	 */
-	public HttpClientConnection(String urlStr, Proxy proxy, HttpClient cl) {
+	public HttpClientConnection(String urlStr, Proxy proxy, HttpClient cl)
+			throws MalformedURLException {
 		this.client = cl;
-		this.urlStr = urlStr;
+		this.url = new URL(urlStr);
 		this.proxy = proxy;
 	}
 
@@ -206,11 +211,7 @@
 	}
 
 	public URL getURL() {
-		try {
-			return new URL(urlStr);
-		} catch (MalformedURLException e) {
-			return null;
-		}
+		return url;
 	}
 
 	public String getResponseMessage() throws IOException {
@@ -250,11 +251,11 @@
 	public void setRequestMethod(String method) throws ProtocolException {
 		this.method = method;
 		if ("GET".equalsIgnoreCase(method)) //$NON-NLS-1$
-			req = new HttpGet(urlStr);
+			req = new HttpGet(url.toString());
 		else if ("PUT".equalsIgnoreCase(method)) //$NON-NLS-1$
-			req = new HttpPut(urlStr);
+			req = new HttpPut(url.toString());
 		else if ("POST".equalsIgnoreCase(method)) //$NON-NLS-1$
-			req = new HttpPost(urlStr);
+			req = new HttpPost(url.toString());
 		else {
 			this.method = null;
 			throw new UnsupportedOperationException();
diff --git a/org.eclipse.jgit.http.server/BUCK b/org.eclipse.jgit.http.server/BUCK
new file mode 100644
index 0000000..3743557
--- /dev/null
+++ b/org.eclipse.jgit.http.server/BUCK
@@ -0,0 +1,10 @@
+java_library(
+  name = 'jgit-servlet',
+  srcs = glob(['src/**']),
+  resources = glob(['resources/**']),
+  provided_deps = [
+    '//org.eclipse.jgit:jgit',
+    '//lib:servlet-api',
+  ],
+  visibility = ['PUBLIC'],
+)
diff --git a/org.eclipse.jgit.http.server/META-INF/MANIFEST.MF b/org.eclipse.jgit.http.server/META-INF/MANIFEST.MF
index b66606c..9f87aab 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.1.2.201602141800-r
+Bundle-Version: 4.2.1.qualifier
 Bundle-Localization: plugin
 Bundle-Vendor: %provider_name
-Export-Package: org.eclipse.jgit.http.server;version="4.1.2",
- org.eclipse.jgit.http.server.glue;version="4.1.2";
+Export-Package: org.eclipse.jgit.http.server;version="4.2.1",
+ org.eclipse.jgit.http.server.glue;version="4.2.1";
   uses:="javax.servlet,javax.servlet.http",
- org.eclipse.jgit.http.server.resolver;version="4.1.2";
+ org.eclipse.jgit.http.server.resolver;version="4.2.1";
   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.1.2,4.2.0)",
- org.eclipse.jgit.internal.storage.dfs;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.internal.storage.file;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.lib;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.nls;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.revwalk;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.transport;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.transport.resolver;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.util;version="[4.1.2,4.2.0)"
+ org.eclipse.jgit.errors;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.internal.storage.dfs;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.internal.storage.file;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.lib;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.nls;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.revwalk;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.transport;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.transport.resolver;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.util;version="[4.2.1,4.3.0)"
diff --git a/org.eclipse.jgit.http.server/pom.xml b/org.eclipse.jgit.http.server/pom.xml
index ea60b00..e13e9fa 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.1.2.201602141800-r</version>
+    <version>4.2.1-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.http.server</artifactId>
@@ -127,8 +127,45 @@
       </plugin>
 
       <plugin>
-        <groupId>org.codehaus.mojo</groupId>
-        <artifactId>clirr-maven-plugin</artifactId>
+          <groupId>com.github.siom79.japicmp</groupId>
+          <artifactId>japicmp-maven-plugin</artifactId>
+          <version>${japicmp-version}</version>
+          <configuration>
+              <oldVersion>
+                  <dependency>
+                      <groupId>${project.groupId}</groupId>
+                      <artifactId>${project.artifactId}</artifactId>
+                      <version>${jgit-last-release-version}</version>
+                  </dependency>
+              </oldVersion>
+              <newVersion>
+                  <file>
+                      <path>${project.build.directory}/${project.artifactId}-${project.version}.jar</path>
+                  </file>
+              </newVersion>
+              <parameter>
+                  <onlyModified>true</onlyModified>
+                  <includes>
+                      <include>org.eclipse.jgit.*</include>
+                  </includes>
+                  <accessModifier>public</accessModifier>
+                  <breakBuildOnModifications>false</breakBuildOnModifications>
+                  <breakBuildOnBinaryIncompatibleModifications>false</breakBuildOnBinaryIncompatibleModifications>
+                  <onlyBinaryIncompatible>false</onlyBinaryIncompatible>
+                  <includeSynthetic>false</includeSynthetic>
+                  <ignoreMissingClasses>false</ignoreMissingClasses>
+                  <skipPomModules>true</skipPomModules>
+              </parameter>
+              <skip>false</skip>
+          </configuration>
+          <executions>
+            <execution>
+             <phase>verify</phase>
+             <goals>
+               <goal>cmp</goal>
+             </goals>
+          </execution>
+        </executions>
       </plugin>
     </plugins>
   </build>
@@ -136,13 +173,44 @@
   <reporting>
     <plugins>
       <plugin>
-        <groupId>org.codehaus.mojo</groupId>
-        <artifactId>clirr-maven-plugin</artifactId>
-        <version>${clirr-version}</version>
-        <configuration>
-          <comparisonVersion>${jgit-last-release-version}</comparisonVersion>
-          <minSeverity>info</minSeverity>
-        </configuration>
+          <groupId>com.github.siom79.japicmp</groupId>
+          <artifactId>japicmp-maven-plugin</artifactId>
+          <version>${japicmp-version}</version>
+          <reportSets>
+              <reportSet>
+                  <reports>
+                      <report>cmp-report</report>
+                  </reports>
+              </reportSet>
+          </reportSets>
+          <configuration>
+              <oldVersion>
+                  <dependency>
+                      <groupId>${project.groupId}</groupId>
+                      <artifactId>${project.artifactId}</artifactId>
+                      <version>${jgit-last-release-version}</version>
+                  </dependency>
+              </oldVersion>
+              <newVersion>
+                  <file>
+                      <path>${project.build.directory}/${project.artifactId}-${project.version}.jar</path>
+                  </file>
+              </newVersion>
+              <parameter>
+                  <onlyModified>true</onlyModified>
+                  <includes>
+                      <include>org.eclipse.jgit.*</include>
+                  </includes>
+                  <accessModifier>public</accessModifier>
+                  <breakBuildOnModifications>false</breakBuildOnModifications>
+                  <breakBuildOnBinaryIncompatibleModifications>false</breakBuildOnBinaryIncompatibleModifications>
+                  <onlyBinaryIncompatible>false</onlyBinaryIncompatible>
+                  <includeSynthetic>false</includeSynthetic>
+                  <ignoreMissingClasses>false</ignoreMissingClasses>
+                  <skipPomModules>true</skipPomModules>
+              </parameter>
+              <skip>false</skip>
+          </configuration>
       </plugin>
     </plugins>
   </reporting>
diff --git a/org.eclipse.jgit.http.test/BUCK b/org.eclipse.jgit.http.test/BUCK
new file mode 100644
index 0000000..d2ced7a
--- /dev/null
+++ b/org.eclipse.jgit.http.test/BUCK
@@ -0,0 +1,40 @@
+TESTS = glob(['tst/**/*.java'])
+
+for t in TESTS:
+  n = t[len('tst/'):len(t)-len('.java')].replace('/', '.')
+  java_test(
+    name = n,
+    labels = ['http'],
+    srcs = [t],
+    deps = [
+      ':helpers',
+      '//org.eclipse.jgit:jgit',
+      '//org.eclipse.jgit.http.apache:http-apache',
+      '//org.eclipse.jgit.http.server:jgit-servlet',
+      '//org.eclipse.jgit.junit:junit',
+      '//org.eclipse.jgit.junit.http:junit-http',
+      '//lib:hamcrest-core',
+      '//lib:hamcrest-library',
+      '//lib:junit',
+      '//lib:servlet-api',
+      '//lib/jetty:http',
+      '//lib/jetty:io',
+      '//lib/jetty:server',
+      '//lib/jetty:servlet',
+      '//lib/jetty:security',
+      '//lib/jetty:util',
+    ],
+    source_under_test = ['//org.eclipse.jgit.http.server:jgit-servlet'],
+  )
+
+java_library(
+  name = 'helpers',
+  srcs = glob(['src/**/*.java']),
+  deps = [
+    '//org.eclipse.jgit:jgit',
+    '//org.eclipse.jgit.http.server:jgit-servlet',
+    '//org.eclipse.jgit.junit:junit',
+    '//org.eclipse.jgit.junit.http:junit-http',
+    '//lib:junit',
+  ],
+)
diff --git a/org.eclipse.jgit.http.test/META-INF/MANIFEST.MF b/org.eclipse.jgit.http.test/META-INF/MANIFEST.MF
index a5bf31a..05e4f81 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.1.2.201602141800-r
+Bundle-Version: 4.2.1.qualifier
 Bundle-Vendor: %provider_name
 Bundle-Localization: plugin
 Bundle-RequiredExecutionEnvironment: JavaSE-1.7
@@ -22,23 +22,23 @@
  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.1.2,4.2.0)",
- org.eclipse.jgit.http.server;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.http.server.glue;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.http.server.resolver;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.internal;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.internal.storage.file;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.junit;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.junit.http;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.lib;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.nls;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.revwalk;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.storage.file;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.transport;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.transport.http;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.transport.http.apache;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.transport.resolver;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.util;version="[4.1.2,4.2.0)",
+ org.eclipse.jgit.errors;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.http.server;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.http.server.glue;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.http.server.resolver;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.internal;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.internal.storage.file;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.junit;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.junit.http;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.lib;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.nls;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.revwalk;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.storage.file;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.transport;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.transport.http;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.transport.http.apache;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.transport.resolver;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.util;version="[4.2.1,4.3.0)",
  org.hamcrest.core;version="[1.1.0,2.0.0)",
  org.junit;version="[4.0.0,5.0.0)",
  org.junit.runner;version="[4.0.0,5.0.0)",
diff --git a/org.eclipse.jgit.http.test/pom.xml b/org.eclipse.jgit.http.test/pom.xml
index 17128ed..44f7a38 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.1.2.201602141800-r</version>
+    <version>4.2.1-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.http.test</artifactId>
@@ -134,6 +134,10 @@
         <artifactId>maven-surefire-plugin</artifactId>
         <configuration>
           <argLine>-Djava.io.tmpdir=${project.build.directory}  -Xmx300m</argLine>
+          <includes>
+            <include>**/*Test.java</include>
+            <include>**/*Tests.java</include>
+          </includes>
         </configuration>
       </plugin>
     </plugins>
diff --git a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/DefaultReceivePackFactoryTest.java b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/DefaultReceivePackFactoryTest.java
index 772865d..f2879e0 100644
--- a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/DefaultReceivePackFactoryTest.java
+++ b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/DefaultReceivePackFactoryTest.java
@@ -79,6 +79,7 @@
 		factory = new DefaultReceivePackFactory();
 	}
 
+	@SuppressWarnings("unchecked")
 	@Test
 	public void testDisabledSingleton() throws ServiceNotAuthorizedException {
 		factory = (ReceivePackFactory<HttpServletRequest>) ReceivePackFactory.DISABLED;
diff --git a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/DefaultUploadPackFactoryTest.java b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/DefaultUploadPackFactoryTest.java
index c9d43cd..3bcb057 100644
--- a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/DefaultUploadPackFactoryTest.java
+++ b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/DefaultUploadPackFactoryTest.java
@@ -77,6 +77,7 @@
 		factory = new DefaultUploadPackFactory();
 	}
 
+	@SuppressWarnings("unchecked")
 	@Test
 	public void testDisabledSingleton() throws ServiceNotAuthorizedException {
 		factory = (UploadPackFactory<HttpServletRequest>) UploadPackFactory.DISABLED;
diff --git a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/DumbClientDumbServerTest.java b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/DumbClientDumbServerTest.java
index dec9b59..677132d 100644
--- a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/DumbClientDumbServerTest.java
+++ b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/DumbClientDumbServerTest.java
@@ -140,8 +140,7 @@
 		assertEquals("http", remoteURI.getScheme());
 
 		Map<String, Ref> map;
-		Transport t = Transport.open(dst, remoteURI);
-		try {
+		try (Transport t = Transport.open(dst, remoteURI)) {
 			// I didn't make up these public interface names, I just
 			// approved them for inclusion into the code base. Sorry.
 			// --spearce
@@ -149,14 +148,9 @@
 			assertTrue("isa TransportHttp", t instanceof TransportHttp);
 			assertTrue("isa HttpTransport", t instanceof HttpTransport);
 
-			FetchConnection c = t.openFetch();
-			try {
+			try (FetchConnection c = t.openFetch()) {
 				map = c.getRefsMap();
-			} finally {
-				c.close();
 			}
-		} finally {
-			t.close();
 		}
 
 		assertNotNull("have map of refs", map);
@@ -201,15 +195,12 @@
 		Repository dst = createBareRepository();
 		assertFalse(dst.hasObject(A_txt));
 
-		Transport t = Transport.open(dst, remoteURI);
-		try {
+		try (Transport t = Transport.open(dst, remoteURI)) {
 			t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
-		} finally {
-			t.close();
 		}
 
 		assertTrue(dst.hasObject(A_txt));
-		assertEquals(B, dst.getRef(master).getObjectId());
+		assertEquals(B, dst.exactRef(master).getObjectId());
 		fsck(dst, B);
 
 		List<AccessEvent> loose = getRequests(loose(remoteURI, A_txt));
@@ -226,15 +217,12 @@
 		Repository dst = createBareRepository();
 		assertFalse(dst.hasObject(A_txt));
 
-		Transport t = Transport.open(dst, remoteURI);
-		try {
+		try (Transport t = Transport.open(dst, remoteURI)) {
 			t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
-		} finally {
-			t.close();
 		}
 
 		assertTrue(dst.hasObject(A_txt));
-		assertEquals(B, dst.getRef(master).getObjectId());
+		assertEquals(B, dst.exactRef(master).getObjectId());
 		fsck(dst, B);
 
 		List<AccessEvent> req;
@@ -265,8 +253,7 @@
 		final RevCommit Q = src.commit().create();
 		final Repository db = src.getRepository();
 
-		Transport t = Transport.open(db, remoteURI);
-		try {
+		try (Transport t = Transport.open(db, remoteURI)) {
 			try {
 				t.push(NullProgressMonitor.INSTANCE, push(src, Q));
 				fail("push incorrectly completed against a dumb server");
@@ -274,8 +261,6 @@
 				String exp = "remote does not support smart HTTP push";
 				assertEquals(exp, nse.getMessage());
 			}
-		} finally {
-			t.close();
 		}
 	}
 }
diff --git a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/DumbClientSmartServerTest.java b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/DumbClientSmartServerTest.java
index e385b95..da3a098 100644
--- a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/DumbClientSmartServerTest.java
+++ b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/DumbClientSmartServerTest.java
@@ -225,7 +225,7 @@
 		}
 
 		assertTrue(dst.hasObject(A_txt));
-		assertEquals(B, dst.getRef(master).getObjectId());
+		assertEquals(B, dst.exactRef(master).getObjectId());
 		fsck(dst, B);
 
 		List<AccessEvent> loose = getRequests(loose(remoteURI, A_txt));
@@ -253,7 +253,7 @@
 		}
 
 		assertTrue(dst.hasObject(A_txt));
-		assertEquals(B, dst.getRef(master).getObjectId());
+		assertEquals(B, dst.exactRef(master).getObjectId());
 		fsck(dst, B);
 
 		List<AccessEvent> req;
diff --git a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/GitServletResponseTests.java b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/GitServletResponseTests.java
index fba1a52..4b15d4b 100644
--- a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/GitServletResponseTests.java
+++ b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/GitServletResponseTests.java
@@ -60,6 +60,7 @@
 import org.eclipse.jgit.http.server.resolver.DefaultReceivePackFactory;
 import org.eclipse.jgit.junit.TestRepository;
 import org.eclipse.jgit.junit.http.HttpTestCase;
+import org.eclipse.jgit.lib.AnyObjectId;
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.NullProgressMonitor;
 import org.eclipse.jgit.lib.ObjectChecker;
@@ -221,8 +222,9 @@
 		preHook = null;
 		oc = new ObjectChecker() {
 			@Override
-			public void checkCommit(byte[] raw) throws CorruptObjectException {
-				throw new IllegalStateException();
+			public void checkCommit(AnyObjectId id, byte[] raw)
+					throws CorruptObjectException {
+				throw new CorruptObjectException("refusing all commits");
 			}
 		};
 
diff --git a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/HookMessageTest.java b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/HookMessageTest.java
index f1056f2..d67c817 100644
--- a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/HookMessageTest.java
+++ b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/HookMessageTest.java
@@ -164,8 +164,8 @@
 		}
 
 		assertTrue(remoteRepository.hasObject(Q_txt));
-		assertNotNull("has " + dstName, remoteRepository.getRef(dstName));
-		assertEquals(Q, remoteRepository.getRef(dstName).getObjectId());
+		assertNotNull("has " + dstName, remoteRepository.exactRef(dstName));
+		assertEquals(Q, remoteRepository.exactRef(dstName).getObjectId());
 		fsck(remoteRepository, Q);
 
 		List<AccessEvent> requests = getRequests();
diff --git a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/HttpClientTests.java b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/HttpClientTests.java
index 6fb1302..ce78442 100644
--- a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/HttpClientTests.java
+++ b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/HttpClientTests.java
@@ -157,8 +157,7 @@
 	public void testRepositoryNotFound_Dumb() throws Exception {
 		URIish uri = toURIish("/dumb.none/not-found");
 		Repository dst = createBareRepository();
-		Transport t = Transport.open(dst, uri);
-		try {
+		try (Transport t = Transport.open(dst, uri)) {
 			try {
 				t.openFetch();
 				fail("connection opened to not found repository");
@@ -167,8 +166,6 @@
 						+ "/info/refs?service=git-upload-pack not found";
 				assertEquals(exp, err.getMessage());
 			}
-		} finally {
-			t.close();
 		}
 	}
 
@@ -176,8 +173,7 @@
 	public void testRepositoryNotFound_Smart() throws Exception {
 		URIish uri = toURIish("/smart.none/not-found");
 		Repository dst = createBareRepository();
-		Transport t = Transport.open(dst, uri);
-		try {
+		try (Transport t = Transport.open(dst, uri)) {
 			try {
 				t.openFetch();
 				fail("connection opened to not found repository");
@@ -186,8 +182,6 @@
 						+ "/info/refs?service=git-upload-pack not found";
 				assertEquals(exp, err.getMessage());
 			}
-		} finally {
-			t.close();
 		}
 	}
 
@@ -201,16 +195,9 @@
 
 		Repository dst = createBareRepository();
 		Ref head;
-		Transport t = Transport.open(dst, dumbAuthNoneURI);
-		try {
-			FetchConnection c = t.openFetch();
-			try {
-				head = c.getRef(Constants.HEAD);
-			} finally {
-				c.close();
-			}
-		} finally {
-			t.close();
+		try (Transport t = Transport.open(dst, dumbAuthNoneURI);
+				FetchConnection c = t.openFetch()) {
+			head = c.getRef(Constants.HEAD);
 		}
 		assertNotNull("has " + Constants.HEAD, head);
 		assertEquals(Q, head.getObjectId());
@@ -225,16 +212,9 @@
 
 		Repository dst = createBareRepository();
 		Ref head;
-		Transport t = Transport.open(dst, dumbAuthNoneURI);
-		try {
-			FetchConnection c = t.openFetch();
-			try {
-				head = c.getRef(Constants.HEAD);
-			} finally {
-				c.close();
-			}
-		} finally {
-			t.close();
+		try (Transport t = Transport.open(dst, dumbAuthNoneURI);
+				FetchConnection c = t.openFetch()) {
+			head = c.getRef(Constants.HEAD);
 		}
 		assertNull("has no " + Constants.HEAD, head);
 	}
@@ -249,16 +229,9 @@
 
 		Repository dst = createBareRepository();
 		Ref head;
-		Transport t = Transport.open(dst, smartAuthNoneURI);
-		try {
-			FetchConnection c = t.openFetch();
-			try {
-				head = c.getRef(Constants.HEAD);
-			} finally {
-				c.close();
-			}
-		} finally {
-			t.close();
+		try (Transport t = Transport.open(dst, smartAuthNoneURI);
+				FetchConnection c = t.openFetch()) {
+			head = c.getRef(Constants.HEAD);
 		}
 		assertNotNull("has " + Constants.HEAD, head);
 		assertEquals(Q, head.getObjectId());
@@ -268,16 +241,13 @@
 	public void testListRemote_Smart_WithQueryParameters() throws Exception {
 		URIish myURI = toURIish("/snone/do?r=1&p=test.git");
 		Repository dst = createBareRepository();
-		Transport t = Transport.open(dst, myURI);
-		try {
+		try (Transport t = Transport.open(dst, myURI)) {
 			try {
 				t.openFetch();
 				fail("test did not fail to find repository as expected");
 			} catch (NoRemoteRepositoryException err) {
 				// expected
 			}
-		} finally {
-			t.close();
 		}
 
 		List<AccessEvent> requests = getRequests();
@@ -296,62 +266,52 @@
 	@Test
 	public void testListRemote_Dumb_NeedsAuth() throws Exception {
 		Repository dst = createBareRepository();
-		Transport t = Transport.open(dst, dumbAuthBasicURI);
-		try {
+		try (Transport t = Transport.open(dst, dumbAuthBasicURI)) {
 			try {
 				t.openFetch();
 				fail("connection opened even info/refs needs auth basic");
 			} catch (TransportException err) {
 				String exp = dumbAuthBasicURI + ": "
-						+ JGitText.get().notAuthorized;
+						+ JGitText.get().noCredentialsProvider;
 				assertEquals(exp, err.getMessage());
 			}
-		} finally {
-			t.close();
 		}
 	}
 
 	@Test
 	public void testListRemote_Dumb_Auth() throws Exception {
 		Repository dst = createBareRepository();
-		Transport t = Transport.open(dst, dumbAuthBasicURI);
-		t.setCredentialsProvider(new UsernamePasswordCredentialsProvider(
-				AppServer.username, AppServer.password));
-		try {
-			t.openFetch();
-		} finally {
-			t.close();
+		try (Transport t = Transport.open(dst, dumbAuthBasicURI)) {
+			t.setCredentialsProvider(new UsernamePasswordCredentialsProvider(
+					AppServer.username, AppServer.password));
+			t.openFetch().close();
 		}
-		t = Transport.open(dst, dumbAuthBasicURI);
-		t.setCredentialsProvider(new UsernamePasswordCredentialsProvider(
-				AppServer.username, ""));
-		try {
-			t.openFetch();
-			fail("connection opened even info/refs needs auth basic and we provide wrong password");
-		} catch (TransportException err) {
-			String exp = dumbAuthBasicURI + ": "
-					+ JGitText.get().notAuthorized;
-			assertEquals(exp, err.getMessage());
-		} finally {
-			t.close();
+		try (Transport t = Transport.open(dst, dumbAuthBasicURI)) {
+			t.setCredentialsProvider(new UsernamePasswordCredentialsProvider(
+					AppServer.username, ""));
+			try {
+				t.openFetch();
+				fail("connection opened even info/refs needs auth basic and we provide wrong password");
+			} catch (TransportException err) {
+				String exp = dumbAuthBasicURI + ": "
+						+ JGitText.get().notAuthorized;
+				assertEquals(exp, err.getMessage());
+			}
 		}
 	}
 
 	@Test
 	public void testListRemote_Smart_UploadPackNeedsAuth() throws Exception {
 		Repository dst = createBareRepository();
-		Transport t = Transport.open(dst, smartAuthBasicURI);
-		try {
+		try (Transport t = Transport.open(dst, smartAuthBasicURI)) {
 			try {
 				t.openFetch();
 				fail("connection opened even though service disabled");
 			} catch (TransportException err) {
 				String exp = smartAuthBasicURI + ": "
-						+ JGitText.get().notAuthorized;
+						+ JGitText.get().noCredentialsProvider;
 				assertEquals(exp, err.getMessage());
 			}
-		} finally {
-			t.close();
 		}
 	}
 
@@ -363,33 +323,24 @@
 		cfg.save();
 
 		Repository dst = createBareRepository();
-		Transport t = Transport.open(dst, smartAuthNoneURI);
-		try {
+		try (Transport t = Transport.open(dst, smartAuthNoneURI)) {
 			try {
 				t.openFetch();
 				fail("connection opened even though service disabled");
 			} catch (TransportException err) {
-				String exp = smartAuthNoneURI + ": Git access forbidden";
+				String exp = smartAuthNoneURI + ": "
+						+ JGitText.get().serviceNotEnabledNoName;
 				assertEquals(exp, err.getMessage());
 			}
-		} finally {
-			t.close();
 		}
 	}
 
 	@Test
 	public void testListRemoteWithoutLocalRepository() throws Exception {
-		Transport t = Transport.open(smartAuthNoneURI);
-		try {
-			FetchConnection c = t.openFetch();
-			try {
-				Ref head = c.getRef(Constants.HEAD);
-				assertNotNull(head);
-			} finally {
-				c.close();
-			}
-		} finally {
-			t.close();
+		try (Transport t = Transport.open(smartAuthNoneURI);
+				FetchConnection c = t.openFetch()) {
+			Ref head = c.getRef(Constants.HEAD);
+			assertNotNull(head);
 		}
 	}
 }
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 1b6c552..82861ed 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
@@ -211,8 +211,7 @@
 		assertEquals("http", remoteURI.getScheme());
 
 		Map<String, Ref> map;
-		Transport t = Transport.open(dst, remoteURI);
-		try {
+		try (Transport t = Transport.open(dst, remoteURI)) {
 			// I didn't make up these public interface names, I just
 			// approved them for inclusion into the code base. Sorry.
 			// --spearce
@@ -226,8 +225,6 @@
 			} finally {
 				c.close();
 			}
-		} finally {
-			t.close();
 		}
 
 		assertNotNull("have map of refs", map);
@@ -257,8 +254,7 @@
 	public void testListRemote_BadName() throws IOException, URISyntaxException {
 		Repository dst = createBareRepository();
 		URIish uri = new URIish(this.remoteURI.toString() + ".invalid");
-		Transport t = Transport.open(dst, uri);
-		try {
+		try (Transport t = Transport.open(dst, uri)) {
 			try {
 				t.openFetch();
 				fail("fetch connection opened");
@@ -266,8 +262,6 @@
 				assertEquals(uri + ": Git repository not found",
 						notFound.getMessage());
 			}
-		} finally {
-			t.close();
 		}
 
 		List<AccessEvent> requests = getRequests();
@@ -288,15 +282,12 @@
 		Repository dst = createBareRepository();
 		assertFalse(dst.hasObject(A_txt));
 
-		Transport t = Transport.open(dst, remoteURI);
-		try {
+		try (Transport t = Transport.open(dst, remoteURI)) {
 			t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
-		} finally {
-			t.close();
 		}
 
 		assertTrue(dst.hasObject(A_txt));
-		assertEquals(B, dst.getRef(master).getObjectId());
+		assertEquals(B, dst.exactRef(master).getObjectId());
 		fsck(dst, B);
 
 		List<AccessEvent> requests = getRequests();
@@ -331,13 +322,10 @@
 		// Bootstrap by doing the clone.
 		//
 		TestRepository dst = createTestRepository();
-		Transport t = Transport.open(dst.getRepository(), remoteURI);
-		try {
+		try (Transport t = Transport.open(dst.getRepository(), remoteURI)) {
 			t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
-		} finally {
-			t.close();
 		}
-		assertEquals(B, dst.getRepository().getRef(master).getObjectId());
+		assertEquals(B, dst.getRepository().exactRef(master).getObjectId());
 		List<AccessEvent> cloneRequests = getRequests();
 
 		// Only create a few new commits.
@@ -352,13 +340,10 @@
 
 		// Now incrementally update.
 		//
-		t = Transport.open(dst.getRepository(), remoteURI);
-		try {
+		try (Transport t = Transport.open(dst.getRepository(), remoteURI)) {
 			t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
-		} finally {
-			t.close();
 		}
-		assertEquals(Z, dst.getRepository().getRef(master).getObjectId());
+		assertEquals(Z, dst.getRepository().exactRef(master).getObjectId());
 
 		List<AccessEvent> requests = getRequests();
 		requests.removeAll(cloneRequests);
@@ -394,13 +379,10 @@
 		// Bootstrap by doing the clone.
 		//
 		TestRepository dst = createTestRepository();
-		Transport t = Transport.open(dst.getRepository(), remoteURI);
-		try {
+		try (Transport t = Transport.open(dst.getRepository(), remoteURI)) {
 			t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
-		} finally {
-			t.close();
 		}
-		assertEquals(B, dst.getRepository().getRef(master).getObjectId());
+		assertEquals(B, dst.getRepository().exactRef(master).getObjectId());
 		List<AccessEvent> cloneRequests = getRequests();
 
 		// Force enough into the local client that enumeration will
@@ -418,13 +400,10 @@
 
 		// Now incrementally update.
 		//
-		t = Transport.open(dst.getRepository(), remoteURI);
-		try {
+		try (Transport t = Transport.open(dst.getRepository(), remoteURI)) {
 			t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
-		} finally {
-			t.close();
 		}
-		assertEquals(Z, dst.getRepository().getRef(master).getObjectId());
+		assertEquals(Z, dst.getRepository().exactRef(master).getObjectId());
 
 		List<AccessEvent> requests = getRequests();
 		requests.removeAll(cloneRequests);
@@ -474,8 +453,7 @@
 		Repository dst = createBareRepository();
 		assertFalse(dst.hasObject(A_txt));
 
-		Transport t = Transport.open(dst, brokenURI);
-		try {
+		try (Transport t = Transport.open(dst, brokenURI)) {
 			try {
 				t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
 				fail("fetch completed despite upload-pack being broken");
@@ -485,8 +463,6 @@
 						+ " received Content-Type text/plain; charset=UTF-8";
 				assertEquals(exp, err.getMessage());
 			}
-		} finally {
-			t.close();
 		}
 
 		List<AccessEvent> requests = getRequests();
@@ -517,12 +493,10 @@
 		final RevCommit Q = src.commit().add("Q", Q_txt).create();
 		final Repository db = src.getRepository();
 		final String dstName = Constants.R_HEADS + "new.branch";
-		Transport t;
 
 		// push anonymous shouldn't be allowed.
 		//
-		t = Transport.open(db, remoteURI);
-		try {
+		try (Transport t = Transport.open(db, remoteURI)) {
 			final String srcExpr = Q.name();
 			final boolean forceUpdate = false;
 			final String localName = null;
@@ -538,8 +512,6 @@
 						+ JGitText.get().authenticationNotSupported;
 				assertEquals(exp, e.getMessage());
 			}
-		} finally {
-			t.close();
 		}
 
 		List<AccessEvent> requests = getRequests();
@@ -560,12 +532,10 @@
 		final RevCommit Q = src.commit().add("Q", Q_txt).create();
 		final Repository db = src.getRepository();
 		final String dstName = Constants.R_HEADS + "new.branch";
-		Transport t;
 
 		enableReceivePack();
 
-		t = Transport.open(db, remoteURI);
-		try {
+		try (Transport t = Transport.open(db, remoteURI)) {
 			final String srcExpr = Q.name();
 			final boolean forceUpdate = false;
 			final String localName = null;
@@ -574,13 +544,11 @@
 			RemoteRefUpdate u = new RemoteRefUpdate(src.getRepository(),
 					srcExpr, dstName, forceUpdate, localName, oldId);
 			t.push(NullProgressMonitor.INSTANCE, Collections.singleton(u));
-		} finally {
-			t.close();
 		}
 
 		assertTrue(remoteRepository.hasObject(Q_txt));
-		assertNotNull("has " + dstName, remoteRepository.getRef(dstName));
-		assertEquals(Q, remoteRepository.getRef(dstName).getObjectId());
+		assertNotNull("has " + dstName, remoteRepository.exactRef(dstName));
+		assertEquals(Q, remoteRepository.exactRef(dstName).getObjectId());
 		fsck(remoteRepository, Q);
 
 		final ReflogReader log = remoteRepository.getReflogReader(dstName);
@@ -633,7 +601,6 @@
 		final RevCommit Q = src.commit().add("Q", Q_bin).create();
 		final Repository db = src.getRepository();
 		final String dstName = Constants.R_HEADS + "new.branch";
-		Transport t;
 
 		enableReceivePack();
 
@@ -642,8 +609,7 @@
 		cfg.setInt("http", null, "postbuffer", 8 * 1024);
 		cfg.save();
 
-		t = Transport.open(db, remoteURI);
-		try {
+		try (Transport t = Transport.open(db, remoteURI)) {
 			final String srcExpr = Q.name();
 			final boolean forceUpdate = false;
 			final String localName = null;
@@ -652,13 +618,11 @@
 			RemoteRefUpdate u = new RemoteRefUpdate(src.getRepository(),
 					srcExpr, dstName, forceUpdate, localName, oldId);
 			t.push(NullProgressMonitor.INSTANCE, Collections.singleton(u));
-		} finally {
-			t.close();
 		}
 
 		assertTrue(remoteRepository.hasObject(Q_bin));
-		assertNotNull("has " + dstName, remoteRepository.getRef(dstName));
-		assertEquals(Q, remoteRepository.getRef(dstName).getObjectId());
+		assertNotNull("has " + dstName, remoteRepository.exactRef(dstName));
+		assertEquals(Q, remoteRepository.exactRef(dstName).getObjectId());
 		fsck(remoteRepository, Q);
 
 		List<AccessEvent> requests = getRequests();
diff --git a/org.eclipse.jgit.junit.http/BUCK b/org.eclipse.jgit.junit.http/BUCK
new file mode 100644
index 0000000..68976a6
--- /dev/null
+++ b/org.eclipse.jgit.junit.http/BUCK
@@ -0,0 +1,18 @@
+java_library(
+  name = 'junit-http',
+  srcs = glob(['src/**']),
+  resources = glob(['resources/**']),
+  provided_deps = [
+    '//org.eclipse.jgit:jgit',
+    '//org.eclipse.jgit.http.server:jgit-servlet',
+    '//org.eclipse.jgit.junit:junit',
+    '//lib:junit',
+    '//lib:servlet-api',
+    '//lib/jetty:http',
+    '//lib/jetty:server',
+    '//lib/jetty:servlet',
+    '//lib/jetty:security',
+    '//lib/jetty:util',
+  ],
+  visibility = ['PUBLIC'],
+)
diff --git a/org.eclipse.jgit.junit.http/META-INF/MANIFEST.MF b/org.eclipse.jgit.junit.http/META-INF/MANIFEST.MF
index 8a5ee6c..bec4178 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.1.2.201602141800-r
+Bundle-Version: 4.2.1.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.1.2,4.2.0)",
- org.eclipse.jgit.http.server;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.internal.storage.file;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.junit;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.lib;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.revwalk;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.transport;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.transport.resolver;version="[4.1.2,4.2.0)",
+ org.eclipse.jgit.errors;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.http.server;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.internal.storage.file;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.junit;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.lib;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.revwalk;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.transport;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.transport.resolver;version="[4.2.1,4.3.0)",
  org.junit;version="[4.0.0,5.0.0)"
-Export-Package: org.eclipse.jgit.junit.http;version="4.1.2";
+Export-Package: org.eclipse.jgit.junit.http;version="4.2.1";
   uses:="org.eclipse.jgit.transport,
    org.eclipse.jgit.junit,
    javax.servlet.http,
diff --git a/org.eclipse.jgit.junit.http/pom.xml b/org.eclipse.jgit.junit.http/pom.xml
index d25ae00..47be76b 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.1.2.201602141800-r</version>
+    <version>4.2.1-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.junit.http</artifactId>
diff --git a/org.eclipse.jgit.junit.http/src/org/eclipse/jgit/junit/http/MockServletConfig.java b/org.eclipse.jgit.junit.http/src/org/eclipse/jgit/junit/http/MockServletConfig.java
index 0b4530d..5976589 100644
--- a/org.eclipse.jgit.junit.http/src/org/eclipse/jgit/junit/http/MockServletConfig.java
+++ b/org.eclipse.jgit.junit.http/src/org/eclipse/jgit/junit/http/MockServletConfig.java
@@ -62,7 +62,7 @@
 		return parameters.get(name);
 	}
 
-	public Enumeration getInitParameterNames() {
+	public Enumeration<String> getInitParameterNames() {
 		final Iterator<String> i = parameters.keySet().iterator();
 		return new Enumeration<String>() {
 			public boolean hasMoreElements() {
diff --git a/org.eclipse.jgit.junit/BUCK b/org.eclipse.jgit.junit/BUCK
new file mode 100644
index 0000000..7e25432
--- /dev/null
+++ b/org.eclipse.jgit.junit/BUCK
@@ -0,0 +1,10 @@
+java_library(
+  name = 'junit',
+  srcs = glob(['src/**']),
+  resources = glob(['resources/**']),
+  provided_deps = [
+    '//org.eclipse.jgit:jgit',
+    '//lib:junit',
+  ],
+  visibility = ['PUBLIC'],
+)
diff --git a/org.eclipse.jgit.junit/META-INF/MANIFEST.MF b/org.eclipse.jgit.junit/META-INF/MANIFEST.MF
index ea32b1b..794e3d3 100644
--- a/org.eclipse.jgit.junit/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.junit/META-INF/MANIFEST.MF
@@ -2,27 +2,27 @@
 Bundle-ManifestVersion: 2
 Bundle-Name: %plugin_name
 Bundle-SymbolicName: org.eclipse.jgit.junit
-Bundle-Version: 4.1.2.201602141800-r
+Bundle-Version: 4.2.1.qualifier
 Bundle-Localization: plugin
 Bundle-Vendor: %provider_name
 Bundle-ActivationPolicy: lazy
 Bundle-RequiredExecutionEnvironment: JavaSE-1.7
-Import-Package: org.eclipse.jgit.api;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.api.errors;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.dircache;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.errors;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.internal.storage.file;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.internal.storage.pack;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.lib;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.merge;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.revwalk;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.storage.file;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.treewalk;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.treewalk.filter;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.util;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.util.io;version="[4.1.2,4.2.0)",
+Import-Package: org.eclipse.jgit.api;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.api.errors;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.dircache;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.errors;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.internal.storage.file;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.internal.storage.pack;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.lib;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.merge;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.revwalk;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.storage.file;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.treewalk;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.treewalk.filter;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.util;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.util.io;version="[4.2.1,4.3.0)",
  org.junit;version="[4.0.0,5.0.0)"
-Export-Package: org.eclipse.jgit.junit;version="4.1.2";
+Export-Package: org.eclipse.jgit.junit;version="4.2.1";
   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 d3616a7..4c7a85b 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.1.2.201602141800-r</version>
+    <version>4.2.1-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.junit</artifactId>
diff --git a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/JGitTestUtil.java b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/JGitTestUtil.java
index 136c647..2962e71 100644
--- a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/JGitTestUtil.java
+++ b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/JGitTestUtil.java
@@ -55,6 +55,7 @@
 import java.lang.reflect.Method;
 import java.net.URISyntaxException;
 import java.net.URL;
+import java.nio.file.Path;
 
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.util.FileUtils;
@@ -240,4 +241,21 @@
 		FileUtils.delete(path);
 	}
 
+	/**
+	 * @param db
+	 *            the repository
+	 * @param link
+	 *            the path of the symbolic link to create
+	 * @param target
+	 *            the target of the symbolic link
+	 * @return the path to the symbolic link
+	 * @throws Exception
+	 * @since 4.2
+	 */
+	public static Path writeLink(Repository db, String link,
+			String target) throws Exception {
+		return FileUtils.createSymLink(new File(db.getWorkTree(), link),
+				target);
+	}
+
 }
diff --git a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/LocalDiskRepositoryTestCase.java b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/LocalDiskRepositoryTestCase.java
index b5348f9..4d713b5 100644
--- a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/LocalDiskRepositoryTestCase.java
+++ b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/LocalDiskRepositoryTestCase.java
@@ -51,7 +51,6 @@
 import java.io.File;
 import java.io.IOException;
 import java.util.*;
-import java.util.concurrent.TimeUnit;
 
 import org.eclipse.jgit.dircache.DirCache;
 import org.eclipse.jgit.dircache.DirCacheEntry;
@@ -92,11 +91,15 @@
 	/** A fake (but stable) identity for committer fields in the test. */
 	protected PersonIdent committer;
 
+	/**
+	 * A {@link SystemReader} used to coordinate time, envars, etc.
+	 * @since 4.2
+	 */
+	protected MockSystemReader mockSystemReader;
+
 	private final List<Repository> toClose = new ArrayList<Repository>();
 	private File tmp;
 
-	private MockSystemReader mockSystemReader;
-
 	@Before
 	public void setUp() throws Exception {
 		tmp = File.createTempFile("jgit_test_", "_tmp");
@@ -171,9 +174,8 @@
 
 	/** Increment the {@link #author} and {@link #committer} times. */
 	protected void tick() {
-		final long delta = TimeUnit.MILLISECONDS.convert(5 * 60,
-				TimeUnit.SECONDS);
-		final long now = author.getWhen().getTime() + delta;
+		mockSystemReader.tick(5 * 60);
+		final long now = mockSystemReader.getCurrentTime();
 		final int tz = mockSystemReader.getTimezone(now);
 
 		author = new PersonIdent(author, now, tz);
@@ -278,11 +280,10 @@
 			throws IllegalStateException, IOException {
 		DirCache dc = repo.readDirCache();
 		StringBuilder sb = new StringBuilder();
-		TreeSet<Long> timeStamps = null;
+		TreeSet<Long> timeStamps = new TreeSet<Long>();
 
 		// iterate once over the dircache just to collect all time stamps
 		if (0 != (includedOptions & MOD_TIME)) {
-			timeStamps = new TreeSet<Long>();
 			for (int i=0; i<dc.getEntryCount(); ++i)
 				timeStamps.add(Long.valueOf(dc.getEntry(i).getLastModified()));
 		}
diff --git a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/MockSystemReader.java b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/MockSystemReader.java
index d24dd44..28a9556 100644
--- a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/MockSystemReader.java
+++ b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/MockSystemReader.java
@@ -62,6 +62,9 @@
 import org.eclipse.jgit.util.FS;
 import org.eclipse.jgit.util.SystemReader;
 
+/**
+ * Mock {@link SystemReader} for tests.
+ */
 public class MockSystemReader extends SystemReader {
 	private final class MockConfig extends FileBasedConfig {
 		private MockConfig(File cfgLocation, FS fs) {
@@ -79,6 +82,8 @@
 		}
 	}
 
+	long now = 1250379778668L; // Sat Aug 15 20:12:58 GMT-03:30 2009
+
 	final Map<String, String> values = new HashMap<String, String>();
 
 	FileBasedConfig userGitConfig;
@@ -138,7 +143,18 @@
 
 	@Override
 	public long getCurrentTime() {
-		return 1250379778668L; // Sat Aug 15 20:12:58 GMT-03:30 2009
+		return now;
+	}
+
+	/**
+	 * Adjusts the current time in seconds.
+	 *
+	 * @param secDelta
+	 *            number of seconds to add to the current time.
+	 * @since 4.2
+	 */
+	public void tick(final int secDelta) {
+		now += secDelta * 1000L;
 	}
 
 	@Override
diff --git a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/RepositoryTestCase.java b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/RepositoryTestCase.java
index ac4539a..0db71f7 100644
--- a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/RepositoryTestCase.java
+++ b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/RepositoryTestCase.java
@@ -55,6 +55,7 @@
 import java.io.IOException;
 import java.io.InputStreamReader;
 import java.io.Reader;
+import java.nio.file.Path;
 import java.util.Map;
 
 import org.eclipse.jgit.api.Git;
@@ -107,6 +108,22 @@
 		return JGitTestUtil.writeTrashFile(db, name, data);
 	}
 
+	/**
+	 * Create a symbolic link
+	 *
+	 * @param link
+	 *            the path of the symbolic link to create
+	 * @param target
+	 *            the target of the symbolic link
+	 * @return the path to the symbolic link
+	 * @throws Exception
+	 * @since 4.2
+	 */
+	protected Path writeLink(final String link, final String target)
+			throws Exception {
+		return JGitTestUtil.writeLink(db, link, target);
+	}
+
 	protected File writeTrashFile(final String subdir, final String name,
 			final String data)
 			throws IOException {
@@ -266,6 +283,19 @@
 	}
 
 	/**
+	 * Replaces '\' by '/'
+	 *
+	 * @param str
+	 *            the string in which backslashes should be replaced
+	 * @return the resulting string with slashes
+         * @since 4.2
+	 */
+	public static String slashify(String str) {
+		str = str.replace('\\', '/');
+		return str;
+	}
+
+	/**
 	 * Waits until it is guaranteed that a subsequent file modification has a
 	 * younger modification timestamp than the modification timestamp of the
 	 * given file. This is done by touching a temporary file, reading the
@@ -371,8 +401,7 @@
 	 * @return the created commit
 	 */
 	protected RevCommit commitFile(String filename, String contents, String branch) {
-		try {
-			Git git = new Git(db);
+		try (Git git = new Git(db)) {
 			Repository repo = git.getRepository();
 			String originalBranch = repo.getFullBranch();
 			boolean empty = repo.resolve(Constants.HEAD) == null;
@@ -413,8 +442,10 @@
 			final int stage, final String content) {
 		final DirCacheEntry entry = new DirCacheEntry(path, stage);
 		entry.setFileMode(mode);
-		entry.setObjectId(new ObjectInserter.Formatter().idFor(
-				Constants.OBJ_BLOB, Constants.encode(content)));
+		try (ObjectInserter.Formatter formatter = new ObjectInserter.Formatter()) {
+			entry.setObjectId(formatter.idFor(
+					Constants.OBJ_BLOB, Constants.encode(content)));
+		}
 		return entry;
 	}
 
diff --git a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/TestRepository.java b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/TestRepository.java
index 925a6b0..e259156 100644
--- a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/TestRepository.java
+++ b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/TestRepository.java
@@ -138,7 +138,7 @@
 
 	private final ObjectInserter inserter;
 
-	private long now;
+	private final MockSystemReader mockSystemReader;
 
 	/**
 	 * Wrap a repository with test building tools.
@@ -148,7 +148,7 @@
 	 * @throws IOException
 	 */
 	public TestRepository(R db) throws IOException {
-		this(db, new RevWalk(db));
+		this(db, new RevWalk(db), new MockSystemReader());
 	}
 
 	/**
@@ -161,11 +161,29 @@
 	 * @throws IOException
 	 */
 	public TestRepository(R db, RevWalk rw) throws IOException {
+		this(db, rw, new MockSystemReader());
+	}
+
+	/**
+	 * Wrap a repository with test building tools.
+	 *
+	 * @param db
+	 *            the test repository to write into.
+	 * @param rw
+	 *            the RevObject pool to use for object lookup.
+	 * @param reader
+	 *            the MockSystemReader to use for clock and other system
+	 *            operations.
+	 * @throws IOException
+	 * @since 4.2
+	 */
+	public TestRepository(R db, RevWalk rw, MockSystemReader reader)
+			throws IOException {
 		this.db = db;
 		this.git = Git.wrap(db);
 		this.pool = rw;
 		this.inserter = db.newObjectInserter();
-		this.now = 1236977987000L;
+		this.mockSystemReader = reader;
 	}
 
 	/** @return the repository this helper class operates against. */
@@ -186,14 +204,28 @@
 		return git;
 	}
 
-	/** @return current time adjusted by {@link #tick(int)}. */
+	/**
+	 * @return current date.
+	 * @since 4.2
+	 */
+	public Date getDate() {
+		return new Date(mockSystemReader.getCurrentTime());
+	}
+
+	/**
+	 * @return current date.
+	 *
+	 * @deprecated Use {@link #getDate()} instead.
+	 */
+	@Deprecated
 	public Date getClock() {
-		return new Date(now);
+		// Remove once Gitiles and Gerrit are using the updated JGit.
+		return getDate();
 	}
 
 	/** @return timezone used for default identities. */
 	public TimeZone getTimeZone() {
-		return defaultCommitter.getTimeZone();
+		return mockSystemReader.getTimeZone();
 	}
 
 	/**
@@ -203,18 +235,18 @@
 	 *            number of seconds to add to the current time.
 	 */
 	public void tick(final int secDelta) {
-		now += secDelta * 1000L;
+		mockSystemReader.tick(secDelta);
 	}
 
 	/**
-	 * Set the author and committer using {@link #getClock()}.
+	 * Set the author and committer using {@link #getDate()}.
 	 *
 	 * @param c
 	 *            the commit builder to store.
 	 */
 	public void setAuthorAndCommitter(org.eclipse.jgit.lib.CommitBuilder c) {
-		c.setAuthor(new PersonIdent(defaultAuthor, new Date(now)));
-		c.setCommitter(new PersonIdent(defaultCommitter, new Date(now)));
+		c.setAuthor(new PersonIdent(defaultAuthor, getDate()));
+		c.setCommitter(new PersonIdent(defaultCommitter, getDate()));
 	}
 
 	/**
@@ -392,8 +424,8 @@
 		c = new org.eclipse.jgit.lib.CommitBuilder();
 		c.setTreeId(tree);
 		c.setParentIds(parents);
-		c.setAuthor(new PersonIdent(defaultAuthor, new Date(now)));
-		c.setCommitter(new PersonIdent(defaultCommitter, new Date(now)));
+		c.setAuthor(new PersonIdent(defaultAuthor, getDate()));
+		c.setCommitter(new PersonIdent(defaultCommitter, getDate()));
 		c.setMessage("");
 		ObjectId id;
 		try (ObjectInserter ins = inserter) {
@@ -428,7 +460,7 @@
 		final TagBuilder t = new TagBuilder();
 		t.setObjectId(dst);
 		t.setTag(name);
-		t.setTagger(new PersonIdent(defaultCommitter, new Date(now)));
+		t.setTagger(new PersonIdent(defaultCommitter, getDate()));
 		t.setMessage("");
 		ObjectId id;
 		try (ObjectInserter ins = inserter) {
@@ -663,7 +695,7 @@
 			b.setParentId(head);
 			b.setTreeId(merger.getResultTreeId());
 			b.setAuthor(commit.getAuthorIdent());
-			b.setCommitter(new PersonIdent(defaultCommitter, new Date(now)));
+			b.setCommitter(new PersonIdent(defaultCommitter, getDate()));
 			b.setMessage(commit.getFullMessage());
 			ObjectId result;
 			try (ObjectInserter ins = inserter) {
@@ -790,7 +822,7 @@
 					break;
 
 				final byte[] bin = db.open(o, o.getType()).getCachedBytes();
-				oc.checkCommit(bin);
+				oc.checkCommit(o, bin);
 				assertHash(o, bin);
 			}
 
@@ -800,7 +832,7 @@
 					break;
 
 				final byte[] bin = db.open(o, o.getType()).getCachedBytes();
-				oc.check(o.getType(), bin);
+				oc.check(o, o.getType(), bin);
 				assertHash(o, bin);
 			}
 		}
@@ -834,7 +866,7 @@
 				Set<ObjectId> all = new HashSet<ObjectId>();
 				for (Ref r : db.getAllRefs().values())
 					all.add(r.getObjectId());
-				pw.preparePack(m, all, Collections.<ObjectId> emptySet());
+				pw.preparePack(m, all, PackWriter.NONE);
 
 				final ObjectId name = pw.computeName();
 
@@ -873,7 +905,7 @@
 
 	private void writeFile(final File p, final byte[] bin) throws IOException,
 			ObjectWritingException {
-		final LockFile lck = new LockFile(p, db.getFS());
+		final LockFile lck = new LockFile(p);
 		if (!lck.lock())
 			throw new ObjectWritingException("Can't write " + p);
 		try {
@@ -1100,7 +1132,7 @@
 					c.setAuthor(author);
 				if (committer != null) {
 					if (updateCommitterTime)
-						committer = new PersonIdent(committer, new Date(now));
+						committer = new PersonIdent(committer, getDate());
 					c.setCommitter(committer);
 				}
 
@@ -1123,8 +1155,7 @@
 			return self;
 		}
 
-		private void insertChangeId(org.eclipse.jgit.lib.CommitBuilder c)
-				throws IOException {
+		private void insertChangeId(org.eclipse.jgit.lib.CommitBuilder c) {
 			if (changeId == null)
 				return;
 			int idx = ChangeIdUtil.indexOfChangeId(message, "\n");
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 09b59b0..c22b3bc 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.1.2.201602141800-r"
+      version="4.2.1.qualifier"
       provider-name="%providerName">
 
    <description url="http://www.eclipse.org/jgit/">
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.feature/pom.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.feature/pom.xml
index ce0f9b9..cbfa6b6 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.1.2.201602141800-r</version>
+    <version>4.2.1-SNAPSHOT</version>
   </parent>
 
   <groupId>org.eclipse.jgit.feature</groupId>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.http.apache.feature/feature.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.http.apache.feature/feature.xml
index 31295cf..73b6eb6 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.1.2.201602141800-r"
+      version="4.2.1.qualifier"
       provider-name="%providerName">
 
    <description url="http://www.eclipse.org/jgit/">
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.http.apache.feature/pom.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.http.apache.feature/pom.xml
index 350de3c..82dfb1f 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.1.2.201602141800-r</version>
+    <version>4.2.1-SNAPSHOT</version>
   </parent>
 
   <groupId>org.eclipse.jgit.feature</groupId>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.junit.feature/feature.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.junit.feature/feature.xml
index cb9fbf3..186b263 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.1.2.201602141800-r"
+      version="4.2.1.qualifier"
       provider-name="%providerName">
 
    <description url="http://www.eclipse.org/jgit/">
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.junit.feature/pom.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.junit.feature/pom.xml
index cb76911..4cee17e 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.1.2.201602141800-r</version>
+    <version>4.2.1-SNAPSHOT</version>
   </parent>
 
   <groupId>org.eclipse.jgit.feature</groupId>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.feature/feature.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.feature/feature.xml
index e1dcfcd..67ff3cf 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.1.2.201602141800-r"
+      version="4.2.1.qualifier"
       provider-name="%providerName">
 
    <description url="http://www.eclipse.org/jgit/">
@@ -27,7 +27,7 @@
          version="0.0.0"/>
 
    <requires>
-      <import feature="org.eclipse.jgit" version="4.1.0" match="equivalent"/>
+      <import feature="org.eclipse.jgit" version="4.2.1" match="equivalent"/>
    </requires>
 
    <plugin
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.feature/pom.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.feature/pom.xml
index 9076053..8cbbdc5 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.1.2.201602141800-r</version>
+    <version>4.2.1-SNAPSHOT</version>
   </parent>
 
   <groupId>org.eclipse.jgit.feature</groupId>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.source.feature/feature.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.source.feature/feature.xml
index bbd0aa0..d53df31 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.1.2.201602141800-r"
+      version="4.2.1.qualifier"
       provider-name="%providerName">
 
    <description url="http://www.eclipse.org/jgit/">
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.source.feature/pom.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.source.feature/pom.xml
index 0e64da4..8dd726d 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.1.2.201602141800-r</version>
+    <version>4.2.1-SNAPSHOT</version>
   </parent>
 
   <groupId>org.eclipse.jgit.feature</groupId>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.repository/pom.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.repository/pom.xml
index 88ad032..bf8c7cb 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.1.2.201602141800-r</version>
+    <version>4.2.1-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.repository</artifactId>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.source.feature/feature.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.source.feature/feature.xml
index 17030ff..38db4f6 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.1.2.201602141800-r"
+      version="4.2.1.qualifier"
       provider-name="%providerName">
 
    <description url="http://www.eclipse.org/jgit/">
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.source.feature/pom.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.source.feature/pom.xml
index aa7128e..ef6de2b 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.1.2.201602141800-r</version>
+    <version>4.2.1-SNAPSHOT</version>
   </parent>
 
   <groupId>org.eclipse.jgit.feature</groupId>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/META-INF/MANIFEST.MF b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/META-INF/MANIFEST.MF
index 38732b5..09e7c94 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.1.2.201602141800-r
+Bundle-Version: 4.2.1.qualifier
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 9b565ec..a4fcefa 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.1.2.201602141800-r</version>
+    <version>4.2.1-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 3b5b7d8..7231c9c 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.1.2.201602141800-r</version>
+  <version>4.2.1-SNAPSHOT</version>
   <packaging>pom</packaging>
 
   <name>JGit Tycho Parent</name>
diff --git a/org.eclipse.jgit.pgm.test/BUCK b/org.eclipse.jgit.pgm.test/BUCK
new file mode 100644
index 0000000..a3859c9
--- /dev/null
+++ b/org.eclipse.jgit.pgm.test/BUCK
@@ -0,0 +1,38 @@
+TESTS = glob(['tst/**/*.java'])
+
+for t in TESTS:
+  n = t[len('tst/'):len(t)-len('.java')].replace('/', '.')
+  java_test(
+    name = n,
+    labels = ['pgm'],
+    srcs = [t],
+    deps = [
+      ':helpers',
+      '//org.eclipse.jgit:jgit',
+      '//org.eclipse.jgit.archive:jgit-archive',
+      '//org.eclipse.jgit.junit:junit',
+      '//org.eclipse.jgit.pgm:pgm',
+      '//lib:hamcrest-core',
+      '//lib:hamcrest-library',
+      '//lib:javaewah',
+      '//lib:junit',
+      '//lib:slf4j-api',
+      '//lib:slf4j-simple',
+      '//lib:commons-compress',
+      '//lib:tukaani-xz',
+    ],
+    source_under_test = ['//org.eclipse.jgit.pgm:pgm'],
+    vm_args = ['-Xmx256m', '-Dfile.encoding=UTF-8'],
+  )
+
+java_library(
+  name = 'helpers',
+  srcs = glob(['src/**/*.java']),
+  deps = [
+    '//org.eclipse.jgit:jgit',
+    '//org.eclipse.jgit.pgm:pgm',
+    '//org.eclipse.jgit.junit:junit',
+    '//lib:args4j',
+    '//lib:junit',
+  ],
+)
diff --git a/org.eclipse.jgit.pgm.test/META-INF/MANIFEST.MF b/org.eclipse.jgit.pgm.test/META-INF/MANIFEST.MF
index 1693bee..b64f5f0 100644
--- a/org.eclipse.jgit.pgm.test/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.pgm.test/META-INF/MANIFEST.MF
@@ -2,27 +2,28 @@
 Bundle-ManifestVersion: 2
 Bundle-Name: %plugin_name
 Bundle-SymbolicName: org.eclipse.jgit.pgm.test
-Bundle-Version: 4.1.2.201602141800-r
+Bundle-Version: 4.2.1.qualifier
 Bundle-Vendor: %provider_name
 Bundle-Localization: plugin
 Bundle-ActivationPolicy: lazy
 Bundle-RequiredExecutionEnvironment: JavaSE-1.7
-Import-Package: org.eclipse.jgit.api;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.api.errors;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.diff;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.dircache;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.junit;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.lib;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.merge;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.pgm;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.pgm.internal;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.pgm.opt;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.revwalk;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.storage.file;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.transport;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.treewalk;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.util;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.util.io;version="[4.1.2,4.2.0)",
+Import-Package: org.eclipse.jgit.api;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.api.errors;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.diff;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.dircache;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.internal.storage.file;version="4.2.1",
+ org.eclipse.jgit.junit;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.lib;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.merge;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.pgm;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.pgm.internal;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.pgm.opt;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.revwalk;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.storage.file;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.transport;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.treewalk;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.util;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.util.io;version="[4.2.1,4.3.0)",
  org.hamcrest.core;bundle-version="[1.1.0,2.0.0)",
  org.junit;version="[4.4.0,5.0.0)",
  org.kohsuke.args4j;version="[2.0.12,2.1.0)"
diff --git "a/org.eclipse.jgit.pgm.test/org.eclipse.jgit.pgm--All-Tests \050Java7\051.launch" "b/org.eclipse.jgit.pgm.test/org.eclipse.jgit.pgm--All-Tests \050Java7\051.launch"
index 600ce7b..3df0dcb 100644
--- "a/org.eclipse.jgit.pgm.test/org.eclipse.jgit.pgm--All-Tests \050Java7\051.launch"
+++ "b/org.eclipse.jgit.pgm.test/org.eclipse.jgit.pgm--All-Tests \050Java7\051.launch"
@@ -15,12 +15,6 @@
 <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.7&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.pgm.test&quot;/&gt;&#10;&lt;/runtimeClasspathEntry&gt;&#10;"/>
-<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry path=&quot;3&quot; projectName=&quot;org.eclipse.jgit.java7&quot; type=&quot;1&quot;/&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.pgm.test"/>
diff --git "a/org.eclipse.jgit.pgm.test/org.eclipse.jgit.pgm--All-Tests \050Java8\051 \050de\051.launch" "b/org.eclipse.jgit.pgm.test/org.eclipse.jgit.pgm--All-Tests \050Java8\051 \050de\051.launch"
new file mode 100644
index 0000000..5c137f2
--- /dev/null
+++ "b/org.eclipse.jgit.pgm.test/org.eclipse.jgit.pgm--All-Tests \050Java8\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.pgm.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.pgm.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"/>
+<booleanAttribute key="org.eclipse.jdt.launching.ATTR_USE_START_ON_FIRST_THREAD" value="true"/>
+<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.7&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.pgm.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.8"/>
+<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value=""/>
+<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="org.eclipse.jgit.pgm.test"/>
+</launchConfiguration>
diff --git "a/org.eclipse.jgit.pgm.test/org.eclipse.jgit.pgm--All-Tests \050Java8\051.launch" "b/org.eclipse.jgit.pgm.test/org.eclipse.jgit.pgm--All-Tests \050Java8\051.launch"
index 1b61d3d..ce473ed 100644
--- "a/org.eclipse.jgit.pgm.test/org.eclipse.jgit.pgm--All-Tests \050Java8\051.launch"
+++ "b/org.eclipse.jgit.pgm.test/org.eclipse.jgit.pgm--All-Tests \050Java8\051.launch"
@@ -19,7 +19,6 @@
 <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.7&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.pgm.test&quot;/&gt;&#10;&lt;/runtimeClasspathEntry&gt;&#10;"/>
-<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry path=&quot;3&quot; projectName=&quot;org.eclipse.jgit.java7&quot; type=&quot;1&quot;/&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.8"/>
diff --git a/org.eclipse.jgit.pgm.test/pom.xml b/org.eclipse.jgit.pgm.test/pom.xml
index cca2e92..135bf65 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.1.2.201602141800-r</version>
+    <version>4.2.1-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.pgm.test</artifactId>
diff --git a/org.eclipse.jgit.pgm.test/src/org/eclipse/jgit/lib/CLIRepositoryTestCase.java b/org.eclipse.jgit.pgm.test/src/org/eclipse/jgit/lib/CLIRepositoryTestCase.java
index 50ddfe0..a6af077 100644
--- a/org.eclipse.jgit.pgm.test/src/org/eclipse/jgit/lib/CLIRepositoryTestCase.java
+++ b/org.eclipse.jgit.pgm.test/src/org/eclipse/jgit/lib/CLIRepositoryTestCase.java
@@ -46,12 +46,16 @@
 
 import java.io.File;
 import java.io.IOException;
+import java.nio.file.Path;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 
 import org.eclipse.jgit.junit.JGitTestUtil;
 import org.eclipse.jgit.junit.LocalDiskRepositoryTestCase;
 import org.eclipse.jgit.pgm.CLIGitCommand;
+import org.eclipse.jgit.pgm.CLIGitCommand.Result;
+import org.eclipse.jgit.pgm.TextBuiltin.TerminatedByHelpException;
 import org.junit.Before;
 
 public class CLIRepositoryTestCase extends LocalDiskRepositoryTestCase {
@@ -69,13 +73,59 @@
 		trash = db.getWorkTree();
 	}
 
+	/**
+	 * Executes specified git commands (with arguments)
+	 *
+	 * @param cmds
+	 *            each string argument must be a valid git command line, e.g.
+	 *            "git branch -h"
+	 * @return command output
+	 * @throws Exception
+	 */
+	protected String[] executeUnchecked(String... cmds) throws Exception {
+		List<String> result = new ArrayList<String>(cmds.length);
+		for (String cmd : cmds) {
+			result.addAll(CLIGitCommand.executeUnchecked(cmd, db));
+		}
+		return result.toArray(new String[0]);
+	}
+
+	/**
+	 * Executes specified git commands (with arguments), throws exception and
+	 * stops execution on first command which output contains a 'fatal:' error
+	 *
+	 * @param cmds
+	 *            each string argument must be a valid git command line, e.g.
+	 *            "git branch -h"
+	 * @return command output
+	 * @throws Exception
+	 */
 	protected String[] execute(String... cmds) throws Exception {
 		List<String> result = new ArrayList<String>(cmds.length);
-		for (String cmd : cmds)
-			result.addAll(CLIGitCommand.execute(cmd, db));
+		for (String cmd : cmds) {
+			Result r = CLIGitCommand.executeRaw(cmd, db);
+			if (r.ex instanceof TerminatedByHelpException) {
+				result.addAll(r.errLines());
+			} else if (r.ex != null) {
+				throw r.ex;
+			}
+			result.addAll(r.outLines());
+		}
 		return result.toArray(new String[0]);
 	}
 
+	/**
+	 * @param link
+	 *            the path of the symbolic link to create
+	 * @param target
+	 *            the target of the symbolic link
+	 * @return the path to the symbolic link
+	 * @throws Exception
+	 */
+	protected Path writeLink(String link, String target) throws Exception {
+		return JGitTestUtil.writeLink(db, link, target);
+	}
+
 	protected File writeTrashFile(final String name, final String data)
 			throws IOException {
 		return JGitTestUtil.writeTrashFile(db, name, data);
@@ -164,16 +214,45 @@
 				.replaceAll("\t", "\\\\t");
 	}
 
-	protected void assertArrayOfLinesEquals(String[] expected, String[] actual) {
-		assertEquals(toText(expected), toText(actual));
+	protected void assertStringArrayEquals(String expected, String[] actual) {
+		// if there is more than one line, ignore last one if empty
+		assertEquals(1,
+				actual.length > 1 && actual[actual.length - 1].equals("")
+						? actual.length - 1 : actual.length);
+		assertEquals(expected, actual[0]);
 	}
 
-	private static String toText(String[] lines) {
+	protected void assertArrayOfLinesEquals(String[] expected, String[] actual) {
+		assertEquals(toString(expected), toString(actual));
+	}
+
+	public static String toString(String... lines) {
+		return toString(Arrays.asList(lines));
+	}
+
+	public static String toString(List<String> lines) {
 		StringBuilder b = new StringBuilder();
 		for (String s : lines) {
-			b.append(s);
-			b.append('\n');
+			// trim indentation, to simplify tests
+			s = s.trim();
+			if (s != null && !s.isEmpty()) {
+				b.append(s);
+				b.append('\n');
+			}
+		}
+		// delete last line break to allow simpler tests with one line compare
+		if (b.length() > 0 && b.charAt(b.length() - 1) == '\n') {
+			b.deleteCharAt(b.length() - 1);
 		}
 		return b.toString();
 	}
+
+	public static boolean contains(List<String> lines, String str) {
+		for (String s : lines) {
+			if (s.contains(str)) {
+				return true;
+			}
+		}
+		return false;
+	}
 }
diff --git a/org.eclipse.jgit.pgm.test/src/org/eclipse/jgit/pgm/CLIGitCommand.java b/org.eclipse.jgit.pgm.test/src/org/eclipse/jgit/pgm/CLIGitCommand.java
index d77b150..3f39656 100644
--- a/org.eclipse.jgit.pgm.test/src/org/eclipse/jgit/pgm/CLIGitCommand.java
+++ b/org.eclipse.jgit.pgm.test/src/org/eclipse/jgit/pgm/CLIGitCommand.java
@@ -42,71 +42,140 @@
  */
 package org.eclipse.jgit.pgm;
 
+import static org.junit.Assert.assertNull;
+
 import java.io.ByteArrayOutputStream;
-import java.text.MessageFormat;
+import java.io.File;
+
+import java.io.IOException;
+import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.List;
 
+import org.eclipse.jgit.internal.storage.file.FileRepository;
 import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.pgm.internal.CLIText;
-import org.eclipse.jgit.pgm.opt.CmdLineParser;
-import org.eclipse.jgit.pgm.opt.SubcommandHandler;
+import org.eclipse.jgit.pgm.TextBuiltin.TerminatedByHelpException;
 import org.eclipse.jgit.util.IO;
-import org.kohsuke.args4j.Argument;
 
-public class CLIGitCommand {
-	@Argument(index = 0, metaVar = "metaVar_command", required = true, handler = SubcommandHandler.class)
-	private TextBuiltin subcommand;
+public class CLIGitCommand extends Main {
 
-	@Argument(index = 1, metaVar = "metaVar_arg")
-	private List<String> arguments = new ArrayList<String>();
+	private final Result result;
 
-	public TextBuiltin getSubcommand() {
-		return subcommand;
+	private final Repository db;
+
+	public CLIGitCommand(Repository db) {
+		super();
+		this.db = db;
+		result = new Result();
 	}
 
-	public List<String> getArguments() {
-		return arguments;
+	/**
+	 * Executes git commands (with arguments) specified on the command line. The
+	 * git repository (same for all commands) can be specified via system
+	 * property "-Dgit_work_tree=path_to_work_tree". If the property is not set,
+	 * current directory is used.
+	 *
+	 * @param args
+	 *            each element in the array must be a valid git command line,
+	 *            e.g. "git branch -h"
+	 * @throws Exception
+	 */
+	public static void main(String[] args) throws Exception {
+		String workDir = System.getProperty("git_work_tree");
+		if (workDir == null) {
+			workDir = ".";
+			System.out.println(
+					"System property 'git_work_tree' not specified, using current directory: "
+							+ new File(workDir).getAbsolutePath());
+		}
+		try (Repository db = new FileRepository(workDir + "/.git")) {
+			for (String cmd : args) {
+				List<String> result = execute(cmd, db);
+				for (String line : result) {
+					System.out.println(line);
+				}
+			}
+		}
 	}
 
 	public static List<String> execute(String str, Repository db)
 			throws Exception {
+		Result result = executeRaw(str, db);
+		return getOutput(result);
+	}
+
+	public static Result executeRaw(String str, Repository db)
+			throws Exception {
+		CLIGitCommand cmd = new CLIGitCommand(db);
+		cmd.run(str);
+		return cmd.result;
+	}
+
+	public static List<String> executeUnchecked(String str, Repository db)
+			throws Exception {
+		CLIGitCommand cmd = new CLIGitCommand(db);
 		try {
-			return IO.readLines(new String(rawExecute(str, db)));
-		} catch (Die e) {
-			return IO.readLines(MessageFormat.format(CLIText.get().fatalError,
-					e.getMessage()));
+			cmd.run(str);
+			return getOutput(cmd.result);
+		} catch (Throwable e) {
+			return cmd.result.errLines();
 		}
 	}
 
-	public static byte[] rawExecute(String str, Repository db)
+	private static List<String> getOutput(Result result) {
+		if (result.ex instanceof TerminatedByHelpException) {
+			return result.errLines();
+		}
+		return result.outLines();
+	}
+
+	private void run(String commandLine) throws Exception {
+		String[] argv = convertToMainArgs(commandLine);
+		try {
+			super.run(argv);
+		} catch (TerminatedByHelpException e) {
+			// this is not a failure, super called exit() on help
+		} finally {
+			writer.flush();
+		}
+	}
+
+	private static String[] convertToMainArgs(String str)
 			throws Exception {
 		String[] args = split(str);
-		if (!args[0].equalsIgnoreCase("git") || args.length < 2)
+		if (!args[0].equalsIgnoreCase("git") || args.length < 2) {
 			throw new IllegalArgumentException(
 					"Expected 'git <command> [<args>]', was:" + str);
+		}
 		String[] argv = new String[args.length - 1];
 		System.arraycopy(args, 1, argv, 0, args.length - 1);
+		return argv;
+	}
 
-		CLIGitCommand bean = new CLIGitCommand();
-		final CmdLineParser clp = new CmdLineParser(bean);
-		clp.parseArgument(argv);
+	@Override
+	PrintWriter createErrorWriter() {
+		return new PrintWriter(result.err);
+	}
 
-		final TextBuiltin cmd = bean.getSubcommand();
-		ByteArrayOutputStream baos = new ByteArrayOutputStream();
-		cmd.outs = baos;
-		if (cmd.requiresRepository())
-			cmd.init(db, null);
-		else
-			cmd.init(null, null);
-		try {
-			cmd.execute(bean.getArguments().toArray(
-					new String[bean.getArguments().size()]));
-		} finally {
-			if (cmd.outw != null)
-				cmd.outw.flush();
+	void init(final TextBuiltin cmd) throws IOException {
+		cmd.outs = result.out;
+		cmd.errs = result.err;
+		super.init(cmd);
+	}
+
+	@Override
+	protected Repository openGitDir(String aGitdir) throws IOException {
+		assertNull(aGitdir);
+		return db;
+	}
+
+	@Override
+	void exit(int status, Exception t) throws Exception {
+		if (t == null) {
+			t = new IllegalStateException(Integer.toString(status));
 		}
-		return baos.toByteArray();
+		result.ex = t;
+		throw t;
 	}
 
 	/**
@@ -164,4 +233,36 @@
 		return list.toArray(new String[list.size()]);
 	}
 
+	public static class Result {
+		public final ByteArrayOutputStream out = new ByteArrayOutputStream();
+
+		public final ByteArrayOutputStream err = new ByteArrayOutputStream();
+
+		public Exception ex;
+
+		public byte[] outBytes() {
+			return out.toByteArray();
+		}
+
+		public byte[] errBytes() {
+			return err.toByteArray();
+		}
+
+		public String outString() {
+			return out.toString();
+		}
+
+		public List<String> outLines() {
+			return IO.readLines(out.toString());
+		}
+
+		public String errString() {
+			return err.toString();
+		}
+
+		public List<String> errLines() {
+			return IO.readLines(err.toString());
+		}
+	}
+
 }
diff --git a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/AddTest.java b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/AddTest.java
index 4253080..3edd9b8 100644
--- a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/AddTest.java
+++ b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/AddTest.java
@@ -45,15 +45,12 @@
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
-
-import java.lang.Exception;
-import java.lang.String;
+import static org.junit.Assert.fail;
 
 import org.eclipse.jgit.api.Git;
 import org.eclipse.jgit.dircache.DirCache;
 import org.eclipse.jgit.lib.CLIRepositoryTestCase;
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Test;
 
 public class AddTest extends CLIRepositoryTestCase {
@@ -66,14 +63,16 @@
 		git = new Git(db);
 	}
 
-	@Ignore("args4j exit()s on error instead of throwing, JVM goes down")
 	@Test
 	public void testAddNothing() throws Exception {
-		assertEquals("fatal: Argument \"filepattern\" is required", //
-				execute("git add")[0]);
+		try {
+			execute("git add");
+			fail("Must die");
+		} catch (Die e) {
+			// expected, requires argument
+		}
 	}
 
-	@Ignore("args4j exit()s for --help, too")
 	@Test
 	public void testAddUsage() throws Exception {
 		execute("git add --help");
diff --git a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/ArchiveTest.java b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/ArchiveTest.java
index 4222a2d..a503ffd 100644
--- a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/ArchiveTest.java
+++ b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/ArchiveTest.java
@@ -52,17 +52,15 @@
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
-import java.io.InputStreamReader;
 import java.io.IOException;
+import java.io.InputStreamReader;
 import java.io.OutputStream;
-import java.lang.Object;
-import java.lang.String;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 import java.util.concurrent.Callable;
-import java.util.concurrent.Executors;
 import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
 import java.util.concurrent.Future;
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipInputStream;
@@ -71,9 +69,7 @@
 import org.eclipse.jgit.dircache.DirCache;
 import org.eclipse.jgit.lib.CLIRepositoryTestCase;
 import org.eclipse.jgit.lib.FileMode;
-import org.eclipse.jgit.pgm.CLIGitCommand;
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Test;
 
 public class ArchiveTest extends CLIRepositoryTestCase {
@@ -89,25 +85,26 @@
 		emptyTree = db.resolve("HEAD^{tree}").abbreviate(12).name();
 	}
 
-	@Ignore("Some versions of java.util.zip refuse to write an empty ZIP")
 	@Test
 	public void testEmptyArchive() throws Exception {
-		byte[] result = CLIGitCommand.rawExecute(
-				"git archive --format=zip " + emptyTree, db);
+		byte[] result = CLIGitCommand.executeRaw(
+				"git archive --format=zip " + emptyTree, db).outBytes();
 		assertArrayEquals(new String[0], listZipEntries(result));
 	}
 
 	@Test
 	public void testEmptyTar() throws Exception {
-		byte[] result = CLIGitCommand.rawExecute(
-				"git archive --format=tar " + emptyTree, db);
+		byte[] result = CLIGitCommand.executeRaw(
+				"git archive --format=tar " + emptyTree, db).outBytes();
 		assertArrayEquals(new String[0], listTarEntries(result));
 	}
 
 	@Test
 	public void testUnrecognizedFormat() throws Exception {
-		String[] expect = new String[] { "fatal: Unknown archive format 'nonsense'" };
-		String[] actual = execute("git archive --format=nonsense " + emptyTree);
+		String[] expect = new String[] {
+				"fatal: Unknown archive format 'nonsense'", "" };
+		String[] actual = executeUnchecked(
+				"git archive --format=nonsense " + emptyTree);
 		assertArrayEquals(expect, actual);
 	}
 
@@ -120,8 +117,8 @@
 		git.add().addFilepattern("c").call();
 		git.commit().setMessage("populate toplevel").call();
 
-		byte[] result = CLIGitCommand.rawExecute(
-				"git archive --format=zip HEAD", db);
+		byte[] result = CLIGitCommand.executeRaw(
+				"git archive --format=zip HEAD", db).outBytes();
 		assertArrayEquals(new String[] { "a", "c" },
 				listZipEntries(result));
 	}
@@ -135,8 +132,8 @@
 	@Test
 	public void testDefaultFormatIsTar() throws Exception {
 		commitGreeting();
-		byte[] result = CLIGitCommand.rawExecute(
-				"git archive HEAD", db);
+		byte[] result = CLIGitCommand.executeRaw(
+				"git archive HEAD", db).outBytes();
 		assertArrayEquals(new String[] { "greeting" },
 				listTarEntries(result));
 	}
@@ -302,8 +299,8 @@
 		git.add().addFilepattern("b").call();
 		git.commit().setMessage("add subdir").call();
 
-		byte[] result = CLIGitCommand.rawExecute(
-				"git archive --format=zip master", db);
+		byte[] result = CLIGitCommand.executeRaw(
+				"git archive --format=zip master", db).outBytes();
 		String[] expect = { "a", "b.c", "b0c", "b/", "b/a", "b/b", "c" };
 		String[] actual = listZipEntries(result);
 
@@ -328,8 +325,8 @@
 		git.add().addFilepattern("b").call();
 		git.commit().setMessage("add subdir").call();
 
-		byte[] result = CLIGitCommand.rawExecute(
-				"git archive --format=tar master", db);
+		byte[] result = CLIGitCommand.executeRaw(
+				"git archive --format=tar master", db).outBytes();
 		String[] expect = { "a", "b.c", "b0c", "b/", "b/a", "b/b", "c" };
 		String[] actual = listTarEntries(result);
 
@@ -349,8 +346,8 @@
 	@Test
 	public void testArchivePrefixOption() throws Exception {
 		commitBazAndFooSlashBar();
-		byte[] result = CLIGitCommand.rawExecute(
-				"git archive --prefix=x/ --format=zip master", db);
+		byte[] result = CLIGitCommand.executeRaw(
+				"git archive --prefix=x/ --format=zip master", db).outBytes();
 		String[] expect = { "x/baz", "x/foo/", "x/foo/bar" };
 		String[] actual = listZipEntries(result);
 
@@ -362,8 +359,8 @@
 	@Test
 	public void testTarPrefixOption() throws Exception {
 		commitBazAndFooSlashBar();
-		byte[] result = CLIGitCommand.rawExecute(
-				"git archive --prefix=x/ --format=tar master", db);
+		byte[] result = CLIGitCommand.executeRaw(
+				"git archive --prefix=x/ --format=tar master", db).outBytes();
 		String[] expect = { "x/baz", "x/foo/", "x/foo/bar" };
 		String[] actual = listTarEntries(result);
 
@@ -381,8 +378,8 @@
 	@Test
 	public void testPrefixDoesNotNormalizeDoubleSlash() throws Exception {
 		commitFoo();
-		byte[] result = CLIGitCommand.rawExecute(
-				"git archive --prefix=x// --format=zip master", db);
+		byte[] result = CLIGitCommand.executeRaw(
+				"git archive --prefix=x// --format=zip master", db).outBytes();
 		String[] expect = { "x//foo" };
 		assertArrayEquals(expect, listZipEntries(result));
 	}
@@ -390,8 +387,8 @@
 	@Test
 	public void testPrefixDoesNotNormalizeDoubleSlashInTar() throws Exception {
 		commitFoo();
-		byte[] result = CLIGitCommand.rawExecute(
-				"git archive --prefix=x// --format=tar master", db);
+		byte[] result = CLIGitCommand.executeRaw(
+				"git archive --prefix=x// --format=tar master", db).outBytes();
 		String[] expect = { "x//foo" };
 		assertArrayEquals(expect, listTarEntries(result));
 	}
@@ -408,8 +405,8 @@
 	@Test
 	public void testPrefixWithoutTrailingSlash() throws Exception {
 		commitBazAndFooSlashBar();
-		byte[] result = CLIGitCommand.rawExecute(
-				"git archive --prefix=my- --format=zip master", db);
+		byte[] result = CLIGitCommand.executeRaw(
+				"git archive --prefix=my- --format=zip master", db).outBytes();
 		String[] expect = { "my-baz", "my-foo/", "my-foo/bar" };
 		String[] actual = listZipEntries(result);
 
@@ -421,8 +418,8 @@
 	@Test
 	public void testTarPrefixWithoutTrailingSlash() throws Exception {
 		commitBazAndFooSlashBar();
-		byte[] result = CLIGitCommand.rawExecute(
-				"git archive --prefix=my- --format=tar master", db);
+		byte[] result = CLIGitCommand.executeRaw(
+				"git archive --prefix=my- --format=tar master", db).outBytes();
 		String[] expect = { "my-baz", "my-foo/", "my-foo/bar" };
 		String[] actual = listTarEntries(result);
 
@@ -441,8 +438,8 @@
 		git.submoduleAdd().setURI("./.").setPath("b").call().close();
 		git.commit().setMessage("add submodule").call();
 
-		byte[] result = CLIGitCommand.rawExecute(
-				"git archive --format=zip master", db);
+		byte[] result = CLIGitCommand.executeRaw(
+				"git archive --format=zip master", db).outBytes();
 		String[] expect = { ".gitmodules", "a", "b/", "c" };
 		String[] actual = listZipEntries(result);
 
@@ -461,8 +458,8 @@
 		git.submoduleAdd().setURI("./.").setPath("b").call().close();
 		git.commit().setMessage("add submodule").call();
 
-		byte[] result = CLIGitCommand.rawExecute(
-				"git archive --format=tar master", db);
+		byte[] result = CLIGitCommand.executeRaw(
+				"git archive --format=tar master", db).outBytes();
 		String[] expect = { ".gitmodules", "a", "b/", "c" };
 		String[] actual = listTarEntries(result);
 
@@ -491,8 +488,8 @@
 
 		git.commit().setMessage("three files with different modes").call();
 
-		byte[] zipData = CLIGitCommand.rawExecute(
-				"git archive --format=zip master", db);
+		byte[] zipData = CLIGitCommand.executeRaw(
+				"git archive --format=zip master", db).outBytes();
 		writeRaw("zip-with-modes.zip", zipData);
 		assertContainsEntryWithMode("zip-with-modes.zip", "-rw-", "plain");
 		assertContainsEntryWithMode("zip-with-modes.zip", "-rwx", "executable");
@@ -520,8 +517,8 @@
 
 		git.commit().setMessage("three files with different modes").call();
 
-		byte[] archive = CLIGitCommand.rawExecute(
-				"git archive --format=tar master", db);
+		byte[] archive = CLIGitCommand.executeRaw(
+				"git archive --format=tar master", db).outBytes();
 		writeRaw("with-modes.tar", archive);
 		assertTarContainsEntry("with-modes.tar", "-rw-r--r--", "plain");
 		assertTarContainsEntry("with-modes.tar", "-rwxr-xr-x", "executable");
@@ -543,8 +540,8 @@
 		git.add().addFilepattern("1234567890").call();
 		git.commit().setMessage("file with long name").call();
 
-		byte[] result = CLIGitCommand.rawExecute(
-				"git archive --format=zip HEAD", db);
+		byte[] result = CLIGitCommand.executeRaw(
+				"git archive --format=zip HEAD", db).outBytes();
 		assertArrayEquals(l.toArray(new String[l.size()]),
 				listZipEntries(result));
 	}
@@ -563,8 +560,8 @@
 		git.add().addFilepattern("1234567890").call();
 		git.commit().setMessage("file with long name").call();
 
-		byte[] result = CLIGitCommand.rawExecute(
-				"git archive --format=tar HEAD", db);
+		byte[] result = CLIGitCommand.executeRaw(
+				"git archive --format=tar HEAD", db).outBytes();
 		assertArrayEquals(l.toArray(new String[l.size()]),
 				listTarEntries(result));
 	}
@@ -576,8 +573,8 @@
 		git.add().addFilepattern("xyzzy").call();
 		git.commit().setMessage("add file with content").call();
 
-		byte[] result = CLIGitCommand.rawExecute(
-				"git archive --format=zip HEAD", db);
+		byte[] result = CLIGitCommand.executeRaw(
+				"git archive --format=zip HEAD", db).outBytes();
 		assertArrayEquals(new String[] { payload },
 				zipEntryContent(result, "xyzzy"));
 	}
@@ -589,8 +586,8 @@
 		git.add().addFilepattern("xyzzy").call();
 		git.commit().setMessage("add file with content").call();
 
-		byte[] result = CLIGitCommand.rawExecute(
-				"git archive --format=tar HEAD", db);
+		byte[] result = CLIGitCommand.executeRaw(
+				"git archive --format=tar HEAD", db).outBytes();
 		assertArrayEquals(new String[] { payload },
 				tarEntryContent(result, "xyzzy"));
 	}
diff --git a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/BranchTest.java b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/BranchTest.java
index 4200cd0..55f4d8b 100644
--- a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/BranchTest.java
+++ b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/BranchTest.java
@@ -43,11 +43,17 @@
 package org.eclipse.jgit.pgm;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.io.File;
 
 import org.eclipse.jgit.api.Git;
 import org.eclipse.jgit.lib.CLIRepositoryTestCase;
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.RefUpdate;
+import org.eclipse.jgit.pgm.internal.CLIText;
 import org.eclipse.jgit.revwalk.RevCommit;
 import org.junit.Before;
 import org.junit.Test;
@@ -57,13 +63,25 @@
 	@Before
 	public void setUp() throws Exception {
 		super.setUp();
-		new Git(db).commit().setMessage("initial commit").call();
+		try (Git git = new Git(db)) {
+			git.commit().setMessage("initial commit").call();
+		}
+	}
+
+	@Test
+	public void testHelpAfterDelete() throws Exception {
+		String err = toString(executeUnchecked("git branch -d"));
+		String help = toString(executeUnchecked("git branch -h"));
+		String errAndHelp = toString(executeUnchecked("git branch -d -h"));
+		assertEquals(CLIText.fatalError(CLIText.get().branchNameRequired), err);
+		assertEquals(toString(err, help), errAndHelp);
 	}
 
 	@Test
 	public void testList() throws Exception {
+		assertEquals("* master", toString(execute("git branch")));
 		assertEquals("* master 6fd41be initial commit",
-				execute("git branch -v")[0]);
+				toString(execute("git branch -v")));
 	}
 
 	@Test
@@ -71,24 +89,188 @@
 		RefUpdate updateRef = db.updateRef(Constants.HEAD, true);
 		updateRef.setNewObjectId(db.resolve("6fd41be"));
 		updateRef.update();
-		assertEquals("* (no branch) 6fd41be initial commit",
-				execute("git branch -v")[0]);
+		assertEquals(
+				toString("* (no branch) 6fd41be initial commit",
+						"master      6fd41be initial commit"),
+				toString(execute("git branch -v")));
 	}
 
 	@Test
 	public void testListContains() throws Exception {
-		new Git(db).branchCreate().setName("initial").call();
-		RevCommit second = new Git(db).commit().setMessage("second commit")
-				.call();
-		assertArrayOfLinesEquals(new String[] { "  initial", "* master", "" },
-				execute("git branch --contains 6fd41be"));
-		assertArrayOfLinesEquals(new String[] { "* master", "" },
-				execute("git branch --contains " + second.name()));
+		try (Git git = new Git(db)) {
+			git.branchCreate().setName("initial").call();
+			RevCommit second = git.commit().setMessage("second commit")
+					.call();
+			assertEquals(toString("  initial", "* master"),
+					toString(execute("git branch --contains 6fd41be")));
+			assertEquals("* master",
+					toString(execute("git branch --contains " + second.name())));
+		}
 	}
 
 	@Test
 	public void testExistingBranch() throws Exception {
 		assertEquals("fatal: A branch named 'master' already exists.",
-				execute("git branch master")[0]);
+				toString(executeUnchecked("git branch master")));
+	}
+
+	@Test
+	public void testRenameSingleArg() throws Exception {
+		try {
+			toString(execute("git branch -m"));
+			fail("Must die");
+		} catch (Die e) {
+			// expected, requires argument
+		}
+		String result = toString(execute("git branch -m slave"));
+		assertEquals("", result);
+		result = toString(execute("git branch -a"));
+		assertEquals("* slave", result);
+	}
+
+	@Test
+	public void testRenameTwoArgs() throws Exception {
+		String result = toString(execute("git branch -m master slave"));
+		assertEquals("", result);
+		result = toString(execute("git branch -a"));
+		assertEquals("* slave", result);
+	}
+
+	@Test
+	public void testCreate() throws Exception {
+		try {
+			toString(execute("git branch a b"));
+			fail("Must die");
+		} catch (Die e) {
+			// expected, too many arguments
+		}
+		String result = toString(execute("git branch second"));
+		assertEquals("", result);
+		result = toString(execute("git branch"));
+		assertEquals(toString("* master", "second"), result);
+		result = toString(execute("git branch -v"));
+		assertEquals(toString("* master 6fd41be initial commit",
+				"second 6fd41be initial commit"), result);
+	}
+
+	@Test
+	public void testDelete() throws Exception {
+		try {
+			toString(execute("git branch -d"));
+			fail("Must die");
+		} catch (Die e) {
+			// expected, requires argument
+		}
+		String result = toString(execute("git branch second"));
+		assertEquals("", result);
+		result = toString(execute("git branch -d second"));
+		assertEquals("", result);
+		result = toString(execute("git branch"));
+		assertEquals("* master", result);
+	}
+
+	@Test
+	public void testDeleteMultiple() throws Exception {
+		String result = toString(execute("git branch second",
+				"git branch third", "git branch fourth"));
+		assertEquals("", result);
+		result = toString(execute("git branch -d second third fourth"));
+		assertEquals("", result);
+		result = toString(execute("git branch"));
+		assertEquals("* master", result);
+	}
+
+	@Test
+	public void testDeleteForce() throws Exception {
+		try {
+			toString(execute("git branch -D"));
+			fail("Must die");
+		} catch (Die e) {
+			// expected, requires argument
+		}
+		String result = toString(execute("git branch second"));
+		assertEquals("", result);
+		result = toString(execute("git checkout second"));
+		assertEquals("Switched to branch 'second'", result);
+
+		File a = writeTrashFile("a", "a");
+		assertTrue(a.exists());
+		execute("git add a", "git commit -m 'added a'");
+
+		result = toString(execute("git checkout master"));
+		assertEquals("Switched to branch 'master'", result);
+
+		result = toString(execute("git branch"));
+		assertEquals(toString("* master", "second"), result);
+
+		try {
+			toString(execute("git branch -d second"));
+			fail("Must die");
+		} catch (Die e) {
+			// expected, the current HEAD is on second and not merged to master
+		}
+		result = toString(execute("git branch -D second"));
+		assertEquals("", result);
+
+		result = toString(execute("git branch"));
+		assertEquals("* master", result);
+	}
+
+	@Test
+	public void testDeleteForceMultiple() throws Exception {
+		String result = toString(execute("git branch second",
+				"git branch third", "git branch fourth"));
+
+		assertEquals("", result);
+		result = toString(execute("git checkout second"));
+		assertEquals("Switched to branch 'second'", result);
+
+		File a = writeTrashFile("a", "a");
+		assertTrue(a.exists());
+		execute("git add a", "git commit -m 'added a'");
+
+		result = toString(execute("git checkout master"));
+		assertEquals("Switched to branch 'master'", result);
+
+		result = toString(execute("git branch"));
+		assertEquals(toString("fourth", "* master", "second", "third"), result);
+
+		try {
+			toString(execute("git branch -d second third fourth"));
+			fail("Must die");
+		} catch (Die e) {
+			// expected, the current HEAD is on second and not merged to master
+		}
+		result = toString(execute("git branch"));
+		assertEquals(toString("fourth", "* master", "second", "third"), result);
+
+		result = toString(execute("git branch -D second third fourth"));
+		assertEquals("", result);
+
+		result = toString(execute("git branch"));
+		assertEquals("* master", result);
+	}
+
+	@Test
+	public void testCreateFromOldCommit() throws Exception {
+		File a = writeTrashFile("a", "a");
+		assertTrue(a.exists());
+		execute("git add a", "git commit -m 'added a'");
+		File b = writeTrashFile("b", "b");
+		assertTrue(b.exists());
+		execute("git add b", "git commit -m 'added b'");
+		String result = toString(execute("git log -n 1 --reverse"));
+		String firstCommitId = result.substring("commit ".length(),
+				result.indexOf('\n'));
+
+		result = toString(execute("git branch -f second " + firstCommitId));
+		assertEquals("", result);
+
+		result = toString(execute("git branch"));
+		assertEquals(toString("* master", "second"), result);
+
+		result = toString(execute("git checkout second"));
+		assertEquals("Switched to branch 'second'", result);
+		assertFalse(b.exists());
 	}
 }
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 387eb2b..e690ad6 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
@@ -44,9 +44,14 @@
 
 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.assertTrue;
 
 import java.io.File;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Arrays;
 import java.util.List;
 
 import org.eclipse.jgit.api.Git;
@@ -59,34 +64,42 @@
 import org.eclipse.jgit.treewalk.FileTreeIterator;
 import org.eclipse.jgit.treewalk.FileTreeIterator.FileEntry;
 import org.eclipse.jgit.treewalk.TreeWalk;
+import org.eclipse.jgit.util.FS;
 import org.eclipse.jgit.util.FileUtils;
+import org.junit.Assume;
 import org.junit.Test;
 
 public class CheckoutTest extends CLIRepositoryTestCase {
 
 	@Test
 	public void testCheckoutSelf() throws Exception {
-		new Git(db).commit().setMessage("initial commit").call();
+		try (Git git = new Git(db)) {
+			git.commit().setMessage("initial commit").call();
 
-		assertStringArrayEquals("Already on 'master'",
-				execute("git checkout master"));
+			assertStringArrayEquals("Already on 'master'",
+					execute("git checkout master"));
+		}
 	}
 
 	@Test
 	public void testCheckoutBranch() throws Exception {
-		new Git(db).commit().setMessage("initial commit").call();
-		new Git(db).branchCreate().setName("side").call();
+		try (Git git = new Git(db)) {
+			git.commit().setMessage("initial commit").call();
+			git.branchCreate().setName("side").call();
 
-		assertStringArrayEquals("Switched to branch 'side'",
-				execute("git checkout side"));
+			assertStringArrayEquals("Switched to branch 'side'",
+					execute("git checkout side"));
+		}
 	}
 
 	@Test
 	public void testCheckoutNewBranch() throws Exception {
-		new Git(db).commit().setMessage("initial commit").call();
+		try (Git git = new Git(db)) {
+			git.commit().setMessage("initial commit").call();
 
-		assertStringArrayEquals("Switched to a new branch 'side'",
-				execute("git checkout -b side"));
+			assertStringArrayEquals("Switched to a new branch 'side'",
+					execute("git checkout -b side"));
+		}
 	}
 
 	@Test
@@ -98,17 +111,19 @@
 
 	@Test
 	public void testCheckoutNewBranchThatAlreadyExists() throws Exception {
-		new Git(db).commit().setMessage("initial commit").call();
+		try (Git git = new Git(db)) {
+			git.commit().setMessage("initial commit").call();
 
-		assertStringArrayEquals(
-				"fatal: A branch named 'master' already exists.",
-				execute("git checkout -b master"));
+			assertStringArrayEquals(
+					"fatal: A branch named 'master' already exists.",
+				executeUnchecked("git checkout -b master"));
+		}
 	}
 
 	@Test
 	public void testCheckoutNewBranchOnBranchToBeBorn() throws Exception {
 		assertStringArrayEquals("fatal: You are on a branch yet to be born",
-				execute("git checkout -b side"));
+				executeUnchecked("git checkout -b side"));
 	}
 
 	@Test
@@ -120,32 +135,35 @@
 
 	@Test
 	public void testCheckoutHead() throws Exception {
-		new Git(db).commit().setMessage("initial commit").call();
+		try (Git git = new Git(db)) {
+			git.commit().setMessage("initial commit").call();
 
-		assertStringArrayEquals("", execute("git checkout HEAD"));
+			assertStringArrayEquals("", execute("git checkout HEAD"));
+		}
 	}
 
 	@Test
 	public void testCheckoutExistingBranchWithConflict() throws Exception {
-		Git git = new Git(db);
-		writeTrashFile("a", "Hello world a");
-		git.add().addFilepattern(".").call();
-		git.commit().setMessage("commit file a").call();
-		git.branchCreate().setName("branch_1").call();
-		git.rm().addFilepattern("a").call();
-		FileUtils.mkdirs(new File(db.getWorkTree(), "a"));
-		writeTrashFile("a/b", "Hello world b");
-		git.add().addFilepattern("a/b").call();
-		git.commit().setMessage("commit folder a").call();
-		git.rm().addFilepattern("a").call();
-		writeTrashFile("a", "New Hello world a");
-		git.add().addFilepattern(".").call();
+		try (Git git = new Git(db)) {
+			writeTrashFile("a", "Hello world a");
+			git.add().addFilepattern(".").call();
+			git.commit().setMessage("commit file a").call();
+			git.branchCreate().setName("branch_1").call();
+			git.rm().addFilepattern("a").call();
+			FileUtils.mkdirs(new File(db.getWorkTree(), "a"));
+			writeTrashFile("a/b", "Hello world b");
+			git.add().addFilepattern("a/b").call();
+			git.commit().setMessage("commit folder a").call();
+			git.rm().addFilepattern("a").call();
+			writeTrashFile("a", "New Hello world a");
+			git.add().addFilepattern(".").call();
 
-		String[] execute = execute("git checkout branch_1");
-		assertEquals(
-				"error: Your local changes to the following files would be overwritten by checkout:",
-				execute[0]);
-		assertEquals("\ta", execute[1]);
+			String[] execute = execute("git checkout branch_1");
+			assertEquals(
+					"error: Your local changes to the following files would be overwritten by checkout:",
+					execute[0]);
+			assertEquals("\ta", execute[1]);
+		}
 	}
 
 	/**
@@ -167,40 +185,43 @@
 	 */
 	@Test
 	public void testCheckoutWithMissingWorkingTreeFile() throws Exception {
-		Git git = new Git(db);
-		File fileA = writeTrashFile("a", "Hello world a");
-		writeTrashFile("b", "Hello world b");
-		git.add().addFilepattern(".").call();
-		git.commit().setMessage("add files a & b").call();
-		Ref branch_1 = git.branchCreate().setName("branch_1").call();
-		writeTrashFile("a", "b");
-		git.add().addFilepattern("a").call();
-		git.commit().setMessage("modify file a").call();
+		try (Git git = new Git(db)) {
+			File fileA = writeTrashFile("a", "Hello world a");
+			writeTrashFile("b", "Hello world b");
+			git.add().addFilepattern(".").call();
+			git.commit().setMessage("add files a & b").call();
+			Ref branch_1 = git.branchCreate().setName("branch_1").call();
+			writeTrashFile("a", "b");
+			git.add().addFilepattern("a").call();
+			git.commit().setMessage("modify file a").call();
 
-		FileEntry entry = new FileTreeIterator.FileEntry(new File(
-				db.getWorkTree(), "a"), db.getFS());
-		assertEquals(FileMode.REGULAR_FILE, entry.getMode());
+			FileEntry entry = new FileTreeIterator.FileEntry(new File(
+					db.getWorkTree(), "a"), db.getFS());
+			assertEquals(FileMode.REGULAR_FILE, entry.getMode());
 
-		FileUtils.delete(fileA);
+			FileUtils.delete(fileA);
 
-		git.checkout().setName(branch_1.getName()).call();
+			git.checkout().setName(branch_1.getName()).call();
 
-		entry = new FileTreeIterator.FileEntry(new File(db.getWorkTree(), "a"),
-				db.getFS());
-		assertEquals(FileMode.REGULAR_FILE, entry.getMode());
-		assertEquals("Hello world a", read(fileA));
+			entry = new FileTreeIterator.FileEntry(new File(db.getWorkTree(), "a"),
+					db.getFS());
+			assertEquals(FileMode.REGULAR_FILE, entry.getMode());
+			assertEquals("Hello world a", read(fileA));
+		}
 	}
 
 	@Test
 	public void testCheckoutOrphan() throws Exception {
-		Git git = new Git(db);
-		git.commit().setMessage("initial commit").call();
+		try (Git git = new Git(db)) {
+			git.commit().setMessage("initial commit").call();
 
-		assertStringArrayEquals("Switched to a new branch 'new_branch'",
-				execute("git checkout --orphan new_branch"));
-		assertEquals("refs/heads/new_branch", db.getRef("HEAD").getTarget().getName());
-		RevCommit commit = git.commit().setMessage("orphan commit").call();
-		assertEquals(0, commit.getParentCount());
+			assertStringArrayEquals("Switched to a new branch 'new_branch'",
+					execute("git checkout --orphan new_branch"));
+			assertEquals("refs/heads/new_branch",
+					db.exactRef("HEAD").getTarget().getName());
+			RevCommit commit = git.commit().setMessage("orphan commit").call();
+			assertEquals(0, commit.getParentCount());
+		}
 	}
 
 	/**
@@ -223,33 +244,34 @@
 	@Test
 	public void fileModeTestMissingThenFolderWithFileInWorkingTree()
 			throws Exception {
-		Git git = new Git(db);
-		writeTrashFile("b", "Hello world b");
-		git.add().addFilepattern(".").call();
-		git.commit().setMessage("add file b").call();
-		Ref branch_1 = git.branchCreate().setName("branch_1").call();
-		File folderA = new File(db.getWorkTree(), "a");
-		FileUtils.mkdirs(folderA);
-		writeTrashFile("a/c", "Hello world c");
-		git.add().addFilepattern(".").call();
-		git.commit().setMessage("add folder a").call();
+		try (Git git = new Git(db)) {
+			writeTrashFile("b", "Hello world b");
+			git.add().addFilepattern(".").call();
+			git.commit().setMessage("add file b").call();
+			Ref branch_1 = git.branchCreate().setName("branch_1").call();
+			File folderA = new File(db.getWorkTree(), "a");
+			FileUtils.mkdirs(folderA);
+			writeTrashFile("a/c", "Hello world c");
+			git.add().addFilepattern(".").call();
+			git.commit().setMessage("add folder a").call();
 
-		FileEntry entry = new FileTreeIterator.FileEntry(new File(
-				db.getWorkTree(), "a"), db.getFS());
-		assertEquals(FileMode.TREE, entry.getMode());
+			FileEntry entry = new FileTreeIterator.FileEntry(new File(
+					db.getWorkTree(), "a"), db.getFS());
+			assertEquals(FileMode.TREE, entry.getMode());
 
-		FileUtils.delete(folderA, FileUtils.RECURSIVE);
-		writeTrashFile("a", "b");
+			FileUtils.delete(folderA, FileUtils.RECURSIVE);
+			writeTrashFile("a", "b");
 
-		entry = new FileTreeIterator.FileEntry(new File(db.getWorkTree(), "a"),
-				db.getFS());
-		assertEquals(FileMode.REGULAR_FILE, entry.getMode());
+			entry = new FileTreeIterator.FileEntry(new File(db.getWorkTree(), "a"),
+					db.getFS());
+			assertEquals(FileMode.REGULAR_FILE, entry.getMode());
 
-		git.checkout().setName(branch_1.getName()).call();
+			git.checkout().setName(branch_1.getName()).call();
 
-		entry = new FileTreeIterator.FileEntry(new File(db.getWorkTree(), "a"),
-				db.getFS());
-		assertEquals(FileMode.REGULAR_FILE, entry.getMode());
+			entry = new FileTreeIterator.FileEntry(new File(db.getWorkTree(), "a"),
+					db.getFS());
+			assertEquals(FileMode.REGULAR_FILE, entry.getMode());
+		}
 	}
 
 	/**
@@ -271,30 +293,31 @@
 	 */
 	@Test
 	public void fileModeTestFolderWithMissingInWorkingTree() throws Exception {
-		Git git = new Git(db);
-		writeTrashFile("b", "Hello world b");
-		writeTrashFile("a", "b");
-		git.add().addFilepattern(".").call();
-		git.commit().setMessage("add file b & file a").call();
-		Ref branch_1 = git.branchCreate().setName("branch_1").call();
-		git.rm().addFilepattern("a").call();
-		File folderA = new File(db.getWorkTree(), "a");
-		FileUtils.mkdirs(folderA);
-		writeTrashFile("a/c", "Hello world c");
-		git.add().addFilepattern(".").call();
-		git.commit().setMessage("add folder a").call();
+		try (Git git = new Git(db)) {
+			writeTrashFile("b", "Hello world b");
+			writeTrashFile("a", "b");
+			git.add().addFilepattern(".").call();
+			git.commit().setMessage("add file b & file a").call();
+			Ref branch_1 = git.branchCreate().setName("branch_1").call();
+			git.rm().addFilepattern("a").call();
+			File folderA = new File(db.getWorkTree(), "a");
+			FileUtils.mkdirs(folderA);
+			writeTrashFile("a/c", "Hello world c");
+			git.add().addFilepattern(".").call();
+			git.commit().setMessage("add folder a").call();
 
-		FileEntry entry = new FileTreeIterator.FileEntry(new File(
-				db.getWorkTree(), "a"), db.getFS());
-		assertEquals(FileMode.TREE, entry.getMode());
+			FileEntry entry = new FileTreeIterator.FileEntry(new File(
+					db.getWorkTree(), "a"), db.getFS());
+			assertEquals(FileMode.TREE, entry.getMode());
 
-		FileUtils.delete(folderA, FileUtils.RECURSIVE);
+			FileUtils.delete(folderA, FileUtils.RECURSIVE);
 
-		git.checkout().setName(branch_1.getName()).call();
+			git.checkout().setName(branch_1.getName()).call();
 
-		entry = new FileTreeIterator.FileEntry(new File(db.getWorkTree(), "a"),
-				db.getFS());
-		assertEquals(FileMode.REGULAR_FILE, entry.getMode());
+			entry = new FileTreeIterator.FileEntry(new File(db.getWorkTree(), "a"),
+					db.getFS());
+			assertEquals(FileMode.REGULAR_FILE, entry.getMode());
+		}
 	}
 
 	/**
@@ -316,32 +339,33 @@
 	 */
 	@Test
 	public void fileModeTestMissingWithFolderInWorkingTree() throws Exception {
-		Git git = new Git(db);
-		writeTrashFile("b", "Hello world b");
-		writeTrashFile("a", "b");
-		git.add().addFilepattern(".").call();
-		git.commit().setMessage("add file b & file a").call();
-		Ref branch_1 = git.branchCreate().setName("branch_1").call();
-		git.rm().addFilepattern("a").call();
-		git.commit().setMessage("delete file a").call();
+		try (Git git = new Git(db)) {
+			writeTrashFile("b", "Hello world b");
+			writeTrashFile("a", "b");
+			git.add().addFilepattern(".").call();
+			git.commit().setMessage("add file b & file a").call();
+			Ref branch_1 = git.branchCreate().setName("branch_1").call();
+			git.rm().addFilepattern("a").call();
+			git.commit().setMessage("delete file a").call();
 
-		FileUtils.mkdirs(new File(db.getWorkTree(), "a"));
-		writeTrashFile("a/c", "Hello world c");
+			FileUtils.mkdirs(new File(db.getWorkTree(), "a"));
+			writeTrashFile("a/c", "Hello world c");
 
-		FileEntry entry = new FileTreeIterator.FileEntry(new File(
-				db.getWorkTree(), "a"), db.getFS());
-		assertEquals(FileMode.TREE, entry.getMode());
+			FileEntry entry = new FileTreeIterator.FileEntry(new File(
+					db.getWorkTree(), "a"), db.getFS());
+			assertEquals(FileMode.TREE, entry.getMode());
 
-		CheckoutConflictException exception = null;
-		try {
-			git.checkout().setName(branch_1.getName()).call();
-		} catch (CheckoutConflictException e) {
-			exception = e;
+			CheckoutConflictException exception = null;
+			try {
+				git.checkout().setName(branch_1.getName()).call();
+			} catch (CheckoutConflictException e) {
+				exception = e;
+			}
+			assertNotNull(exception);
+			assertEquals(2, exception.getConflictingPaths().size());
+			assertEquals("a", exception.getConflictingPaths().get(0));
+			assertEquals("a/c", exception.getConflictingPaths().get(1));
 		}
-		assertNotNull(exception);
-		assertEquals(2, exception.getConflictingPaths().size());
-		assertEquals("a", exception.getConflictingPaths().get(0));
-		assertEquals("a/c", exception.getConflictingPaths().get(1));
 	}
 
 	/**
@@ -363,40 +387,41 @@
 	@Test
 	public void fileModeTestFolderThenMissingWithFileInWorkingTree()
 			throws Exception {
-		Git git = new Git(db);
-		FileUtils.mkdirs(new File(db.getWorkTree(), "a"));
-		writeTrashFile("a/c", "Hello world c");
-		writeTrashFile("b", "Hello world b");
-		git.add().addFilepattern(".").call();
-		RevCommit commit1 = git.commit().setMessage("add folder a & file b")
-				.call();
-		Ref branch_1 = git.branchCreate().setName("branch_1").call();
-		git.rm().addFilepattern("a").call();
-		RevCommit commit2 = git.commit().setMessage("delete folder a").call();
+		try (Git git = new Git(db)) {
+			FileUtils.mkdirs(new File(db.getWorkTree(), "a"));
+			writeTrashFile("a/c", "Hello world c");
+			writeTrashFile("b", "Hello world b");
+			git.add().addFilepattern(".").call();
+			RevCommit commit1 = git.commit().setMessage("add folder a & file b")
+					.call();
+			Ref branch_1 = git.branchCreate().setName("branch_1").call();
+			git.rm().addFilepattern("a").call();
+			RevCommit commit2 = git.commit().setMessage("delete folder a").call();
 
-		TreeWalk tw = new TreeWalk(db);
-		tw.addTree(commit1.getTree());
-		tw.addTree(commit2.getTree());
-		List<DiffEntry> scan = DiffEntry.scan(tw);
-		assertEquals(1, scan.size());
-		assertEquals(FileMode.MISSING, scan.get(0).getNewMode());
-		assertEquals(FileMode.TREE, scan.get(0).getOldMode());
+			TreeWalk tw = new TreeWalk(db);
+			tw.addTree(commit1.getTree());
+			tw.addTree(commit2.getTree());
+			List<DiffEntry> scan = DiffEntry.scan(tw);
+			assertEquals(1, scan.size());
+			assertEquals(FileMode.MISSING, scan.get(0).getNewMode());
+			assertEquals(FileMode.TREE, scan.get(0).getOldMode());
 
-		writeTrashFile("a", "b");
+			writeTrashFile("a", "b");
 
-		FileEntry entry = new FileTreeIterator.FileEntry(new File(
-				db.getWorkTree(), "a"), db.getFS());
-		assertEquals(FileMode.REGULAR_FILE, entry.getMode());
+			FileEntry entry = new FileTreeIterator.FileEntry(new File(
+					db.getWorkTree(), "a"), db.getFS());
+			assertEquals(FileMode.REGULAR_FILE, entry.getMode());
 
-		CheckoutConflictException exception = null;
-		try {
-			git.checkout().setName(branch_1.getName()).call();
-		} catch (CheckoutConflictException e) {
-			exception = e;
+			CheckoutConflictException exception = null;
+			try {
+				git.checkout().setName(branch_1.getName()).call();
+			} catch (CheckoutConflictException e) {
+				exception = e;
+			}
+			assertNotNull(exception);
+			assertEquals(1, exception.getConflictingPaths().size());
+			assertEquals("a", exception.getConflictingPaths().get(0));
 		}
-		assertNotNull(exception);
-		assertEquals(1, exception.getConflictingPaths().size());
-		assertEquals("a", exception.getConflictingPaths().get(0));
 	}
 
 	/**
@@ -419,30 +444,31 @@
 	@Test
 	public void fileModeTestFolderThenFileWithMissingInWorkingTree()
 			throws Exception {
-		Git git = new Git(db);
-		FileUtils.mkdirs(new File(db.getWorkTree(), "a"));
-		writeTrashFile("a/c", "Hello world c");
-		writeTrashFile("b", "Hello world b");
-		git.add().addFilepattern(".").call();
-		git.commit().setMessage("add folder a & file b").call();
-		Ref branch_1 = git.branchCreate().setName("branch_1").call();
-		git.rm().addFilepattern("a").call();
-		File fileA = new File(db.getWorkTree(), "a");
-		writeTrashFile("a", "b");
-		git.add().addFilepattern("a").call();
-		git.commit().setMessage("add file a").call();
+		try (Git git = new Git(db)) {
+			FileUtils.mkdirs(new File(db.getWorkTree(), "a"));
+			writeTrashFile("a/c", "Hello world c");
+			writeTrashFile("b", "Hello world b");
+			git.add().addFilepattern(".").call();
+			git.commit().setMessage("add folder a & file b").call();
+			Ref branch_1 = git.branchCreate().setName("branch_1").call();
+			git.rm().addFilepattern("a").call();
+			File fileA = new File(db.getWorkTree(), "a");
+			writeTrashFile("a", "b");
+			git.add().addFilepattern("a").call();
+			git.commit().setMessage("add file a").call();
 
-		FileEntry entry = new FileTreeIterator.FileEntry(new File(
-				db.getWorkTree(), "a"), db.getFS());
-		assertEquals(FileMode.REGULAR_FILE, entry.getMode());
+			FileEntry entry = new FileTreeIterator.FileEntry(new File(
+					db.getWorkTree(), "a"), db.getFS());
+			assertEquals(FileMode.REGULAR_FILE, entry.getMode());
 
-		FileUtils.delete(fileA);
+			FileUtils.delete(fileA);
 
-		git.checkout().setName(branch_1.getName()).call();
+			git.checkout().setName(branch_1.getName()).call();
 
-		entry = new FileTreeIterator.FileEntry(new File(db.getWorkTree(), "a"),
-				db.getFS());
-		assertEquals(FileMode.TREE, entry.getMode());
+			entry = new FileTreeIterator.FileEntry(new File(db.getWorkTree(), "a"),
+					db.getFS());
+			assertEquals(FileMode.TREE, entry.getMode());
+		}
 	}
 
 	/**
@@ -463,38 +489,39 @@
 	 */
 	@Test
 	public void fileModeTestFileThenFileWithFolderInIndex() throws Exception {
-		Git git = new Git(db);
-		writeTrashFile("a", "Hello world a");
-		writeTrashFile("b", "Hello world b");
-		git.add().addFilepattern(".").call();
-		git.commit().setMessage("add files a & b").call();
-		Ref branch_1 = git.branchCreate().setName("branch_1").call();
-		writeTrashFile("a", "b");
-		git.add().addFilepattern("a").call();
-		git.commit().setMessage("add file a").call();
+		try (Git git = new Git(db)) {
+			writeTrashFile("a", "Hello world a");
+			writeTrashFile("b", "Hello world b");
+			git.add().addFilepattern(".").call();
+			git.commit().setMessage("add files a & b").call();
+			Ref branch_1 = git.branchCreate().setName("branch_1").call();
+			writeTrashFile("a", "b");
+			git.add().addFilepattern("a").call();
+			git.commit().setMessage("add file a").call();
 
-		FileEntry entry = new FileTreeIterator.FileEntry(new File(
-				db.getWorkTree(), "a"), db.getFS());
-		assertEquals(FileMode.REGULAR_FILE, entry.getMode());
+			FileEntry entry = new FileTreeIterator.FileEntry(new File(
+					db.getWorkTree(), "a"), db.getFS());
+			assertEquals(FileMode.REGULAR_FILE, entry.getMode());
 
-		git.rm().addFilepattern("a").call();
-		FileUtils.mkdirs(new File(db.getWorkTree(), "a"));
-		writeTrashFile("a/c", "Hello world c");
-		git.add().addFilepattern(".").call();
+			git.rm().addFilepattern("a").call();
+			FileUtils.mkdirs(new File(db.getWorkTree(), "a"));
+			writeTrashFile("a/c", "Hello world c");
+			git.add().addFilepattern(".").call();
 
-		entry = new FileTreeIterator.FileEntry(new File(db.getWorkTree(), "a"),
-				db.getFS());
-		assertEquals(FileMode.TREE, entry.getMode());
+			entry = new FileTreeIterator.FileEntry(new File(db.getWorkTree(), "a"),
+					db.getFS());
+			assertEquals(FileMode.TREE, entry.getMode());
 
-		CheckoutConflictException exception = null;
-		try {
-			git.checkout().setName(branch_1.getName()).call();
-		} catch (CheckoutConflictException e) {
-			exception = e;
+			CheckoutConflictException exception = null;
+			try {
+				git.checkout().setName(branch_1.getName()).call();
+			} catch (CheckoutConflictException e) {
+				exception = e;
+			}
+			assertNotNull(exception);
+			assertEquals(1, exception.getConflictingPaths().size());
+			assertEquals("a", exception.getConflictingPaths().get(0));
 		}
-		assertNotNull(exception);
-		assertEquals(1, exception.getConflictingPaths().size());
-		assertEquals("a", exception.getConflictingPaths().get(0));
 	}
 
 	/**
@@ -516,74 +543,97 @@
 	 */
 	@Test
 	public void fileModeTestFileWithFolderInIndex() throws Exception {
-		Git git = new Git(db);
-		writeTrashFile("b", "Hello world b");
-		writeTrashFile("a", "b");
-		git.add().addFilepattern(".").call();
-		git.commit().setMessage("add file b & file a").call();
-		Ref branch_1 = git.branchCreate().setName("branch_1").call();
-		git.rm().addFilepattern("a").call();
-		writeTrashFile("a", "Hello world a");
-		git.add().addFilepattern("a").call();
-		git.commit().setMessage("add file a").call();
+		try (Git git = new Git(db)) {
+			writeTrashFile("b", "Hello world b");
+			writeTrashFile("a", "b");
+			git.add().addFilepattern(".").call();
+			git.commit().setMessage("add file b & file a").call();
+			Ref branch_1 = git.branchCreate().setName("branch_1").call();
+			git.rm().addFilepattern("a").call();
+			writeTrashFile("a", "Hello world a");
+			git.add().addFilepattern("a").call();
+			git.commit().setMessage("add file a").call();
 
-		FileEntry entry = new FileTreeIterator.FileEntry(new File(
-				db.getWorkTree(), "a"), db.getFS());
-		assertEquals(FileMode.REGULAR_FILE, entry.getMode());
+			FileEntry entry = new FileTreeIterator.FileEntry(new File(
+					db.getWorkTree(), "a"), db.getFS());
+			assertEquals(FileMode.REGULAR_FILE, entry.getMode());
 
-		git.rm().addFilepattern("a").call();
-		FileUtils.mkdirs(new File(db.getWorkTree(), "a"));
-		writeTrashFile("a/c", "Hello world c");
-		git.add().addFilepattern(".").call();
+			git.rm().addFilepattern("a").call();
+			FileUtils.mkdirs(new File(db.getWorkTree(), "a"));
+			writeTrashFile("a/c", "Hello world c");
+			git.add().addFilepattern(".").call();
 
-		entry = new FileTreeIterator.FileEntry(new File(db.getWorkTree(), "a"),
-				db.getFS());
-		assertEquals(FileMode.TREE, entry.getMode());
+			entry = new FileTreeIterator.FileEntry(new File(db.getWorkTree(), "a"),
+					db.getFS());
+			assertEquals(FileMode.TREE, entry.getMode());
 
-		CheckoutConflictException exception = null;
-		try {
-			git.checkout().setName(branch_1.getName()).call();
-		} catch (CheckoutConflictException e) {
-			exception = e;
+			CheckoutConflictException exception = null;
+			try {
+				git.checkout().setName(branch_1.getName()).call();
+			} catch (CheckoutConflictException e) {
+				exception = e;
+			}
+			assertNotNull(exception);
+			assertEquals(1, exception.getConflictingPaths().size());
+			assertEquals("a", exception.getConflictingPaths().get(0));
+
+			// TODO: ideally we'd like to get two paths from this exception
+			// assertEquals(2, exception.getConflictingPaths().size());
+			// assertEquals("a", exception.getConflictingPaths().get(0));
+			// assertEquals("a/c", exception.getConflictingPaths().get(1));
 		}
-		assertNotNull(exception);
-		assertEquals(1, exception.getConflictingPaths().size());
-		assertEquals("a", exception.getConflictingPaths().get(0));
-
-		// TODO: ideally we'd like to get two paths from this exception
-		// assertEquals(2, exception.getConflictingPaths().size());
-		// assertEquals("a", exception.getConflictingPaths().get(0));
-		// assertEquals("a/c", exception.getConflictingPaths().get(1));
-	}
-
-	static private void assertStringArrayEquals(String expected, String[] actual) {
-		// if there is more than one line, ignore last one if empty
-		assertEquals(
-				1,
-				actual.length > 1 && actual[actual.length - 1].equals("") ? actual.length - 1
-						: actual.length);
-		assertEquals(expected, actual[0]);
 	}
 
 	@Test
 	public void testCheckoutPath() throws Exception {
-		Git git = new Git(db);
-		writeTrashFile("a", "Hello world a");
-		git.add().addFilepattern(".").call();
-		git.commit().setMessage("commit file a").call();
-		git.branchCreate().setName("branch_1").call();
-		git.checkout().setName("branch_1").call();
-		File b = writeTrashFile("b", "Hello world b");
-		git.add().addFilepattern("b").call();
-		git.commit().setMessage("commit file b").call();
-		File a = writeTrashFile("a", "New Hello world a");
-		git.add().addFilepattern(".").call();
-		git.commit().setMessage("modified a").call();
-		assertArrayEquals(new String[] { "" },
-				execute("git checkout HEAD~2 -- a"));
-		assertEquals("Hello world a", read(a));
-		assertArrayEquals(new String[] { "* branch_1", "  master", "" },
-				execute("git branch"));
-		assertEquals("Hello world b", read(b));
+		try (Git git = new Git(db)) {
+			writeTrashFile("a", "Hello world a");
+			git.add().addFilepattern(".").call();
+			git.commit().setMessage("commit file a").call();
+			git.branchCreate().setName("branch_1").call();
+			git.checkout().setName("branch_1").call();
+			File b = writeTrashFile("b", "Hello world b");
+			git.add().addFilepattern("b").call();
+			git.commit().setMessage("commit file b").call();
+			File a = writeTrashFile("a", "New Hello world a");
+			git.add().addFilepattern(".").call();
+			git.commit().setMessage("modified a").call();
+			assertArrayEquals(new String[] { "" },
+					execute("git checkout HEAD~2 -- a"));
+			assertEquals("Hello world a", read(a));
+			assertArrayEquals(new String[] { "* branch_1", "  master", "" },
+					execute("git branch"));
+			assertEquals("Hello world b", read(b));
+		}
+	}
+
+	@Test
+	public void testCheckouSingleFile() throws Exception {
+		try (Git git = new Git(db)) {
+			File a = writeTrashFile("a", "file a");
+			git.add().addFilepattern(".").call();
+			git.commit().setMessage("commit file a").call();
+			writeTrashFile("a", "b");
+			assertEquals("b", read(a));
+			assertEquals("[]", Arrays.toString(execute("git checkout -- a")));
+			assertEquals("file a", read(a));
+		}
+	}
+
+	@Test
+	public void testCheckoutLink() throws Exception {
+		Assume.assumeTrue(FS.DETECTED.supportsSymlinks());
+		try (Git git = new Git(db)) {
+			Path path = writeLink("a", "link_a");
+			assertTrue(Files.isSymbolicLink(path));
+			git.add().addFilepattern(".").call();
+			git.commit().setMessage("commit link a").call();
+			deleteTrashFile("a");
+			writeTrashFile("a", "Hello world a");
+			assertFalse(Files.isSymbolicLink(path));
+			assertEquals("[]", Arrays.toString(execute("git checkout -- a")));
+			assertEquals("link_a", FileUtils.readSymLink(path.toFile()));
+			assertTrue(Files.isSymbolicLink(path));
+		}
 	}
 }
diff --git a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/CommitTest.java b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/CommitTest.java
new file mode 100644
index 0000000..6bccb6d
--- /dev/null
+++ b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/CommitTest.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2015, Andrey Loskutov <loskutov@gmx.de>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ *   copyright notice, this list of conditions and the following
+ *   disclaimer in the documentation and/or other materials provided
+ *   with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ *   names of its contributors may be used to endorse or promote
+ *   products derived from this software without specific prior
+ *   written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.eclipse.jgit.pgm;
+
+import static org.junit.Assert.assertEquals;
+
+import org.eclipse.jgit.lib.CLIRepositoryTestCase;
+import org.junit.Test;
+
+public class CommitTest extends CLIRepositoryTestCase {
+
+	@Test
+	public void testCommitPath() throws Exception {
+		writeTrashFile("a", "a");
+		writeTrashFile("b", "a");
+		String result = toString(execute("git add a"));
+		assertEquals("", result);
+
+		result = toString(execute("git status -- a"));
+		assertEquals(toString("On branch master", "Changes to be committed:",
+				"new file:   a"), result);
+
+		result = toString(execute("git status -- b"));
+		assertEquals(toString("On branch master", "Untracked files:", "b"),
+				result);
+
+		result = toString(execute("git commit a -m 'added a'"));
+		assertEquals(
+				"[master 8cb3ef7e5171aaee1792df6302a5a0cd30425f7a] added a",
+				result);
+
+		result = toString(execute("git status -- a"));
+		assertEquals("On branch master", result);
+
+		result = toString(execute("git status -- b"));
+		assertEquals(toString("On branch master", "Untracked files:", "b"),
+				result);
+	}
+
+	@Test
+	public void testCommitAll() throws Exception {
+		writeTrashFile("a", "a");
+		writeTrashFile("b", "a");
+		String result = toString(execute("git add a b"));
+		assertEquals("", result);
+
+		result = toString(execute("git status -- a b"));
+		assertEquals(toString("On branch master", "Changes to be committed:",
+				"new file:   a", "new file:   b"), result);
+
+		result = toString(execute("git commit -m 'added a b'"));
+		assertEquals(
+				"[master 3c93fa8e3a28ee26690498be78016edcb3a38c73] added a b",
+				result);
+
+		result = toString(execute("git status -- a b"));
+		assertEquals("On branch master", result);
+	}
+
+}
diff --git a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/ConfigTest.java b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/ConfigTest.java
index aefdff1..23aa97e 100644
--- a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/ConfigTest.java
+++ b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/ConfigTest.java
@@ -60,7 +60,9 @@
 	@Before
 	public void setUp() throws Exception {
 		super.setUp();
-		new Git(db).commit().setMessage("initial commit").call();
+		try (Git git = new Git(db)) {
+			git.commit().setMessage("initial commit").call();
+		}
 	}
 
 	@Test
diff --git a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/DescribeTest.java b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/DescribeTest.java
index 6352a26..086e72e 100644
--- a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/DescribeTest.java
+++ b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/DescribeTest.java
@@ -43,9 +43,15 @@
 package org.eclipse.jgit.pgm;
 
 import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Arrays;
 
 import org.eclipse.jgit.api.Git;
 import org.eclipse.jgit.lib.CLIRepositoryTestCase;
+import org.eclipse.jgit.pgm.internal.CLIText;
 import org.junit.Before;
 import org.junit.Test;
 
@@ -67,17 +73,15 @@
 
 	@Test
 	public void testNoHead() throws Exception {
-		assertArrayEquals(
-				new String[] { "fatal: No names found, cannot describe anything." },
-				execute("git describe"));
+		assertEquals(CLIText.fatalError(CLIText.get().noNamesFound),
+				toString(executeUnchecked("git describe")));
 	}
 
 	@Test
 	public void testHeadNoTag() throws Exception {
 		git.commit().setMessage("initial commit").call();
-		assertArrayEquals(
-				new String[] { "fatal: No names found, cannot describe anything." },
-				execute("git describe"));
+		assertEquals(CLIText.fatalError(CLIText.get().noNamesFound),
+				toString(executeUnchecked("git describe")));
 	}
 
 	@Test
@@ -103,4 +107,22 @@
 		assertArrayEquals(new String[] { "v1.0-0-g6fd41be", "" },
 				execute("git describe --long HEAD"));
 	}
+
+	@Test
+	public void testHelpArgumentBeforeUnknown() throws Exception {
+		String[] output = execute("git describe -h -XYZ");
+		String all = Arrays.toString(output);
+		assertTrue("Unexpected help output: " + all,
+				all.contains("jgit describe"));
+		assertFalse("Unexpected help output: " + all, all.contains("fatal"));
+	}
+
+	@Test
+	public void testHelpArgumentAfterUnknown() throws Exception {
+		String[] output = executeUnchecked("git describe -XYZ -h");
+		String all = Arrays.toString(output);
+		assertTrue("Unexpected help output: " + all,
+				all.contains("jgit describe"));
+		assertTrue("Unexpected help output: " + all, all.contains("fatal"));
+	}
 }
diff --git a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/MergeTest.java b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/MergeTest.java
index 975e8c4..4719901 100644
--- a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/MergeTest.java
+++ b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/MergeTest.java
@@ -50,6 +50,7 @@
 import org.eclipse.jgit.api.Git;
 import org.eclipse.jgit.lib.CLIRepositoryTestCase;
 import org.eclipse.jgit.merge.MergeStrategy;
+import org.eclipse.jgit.pgm.internal.CLIText;
 import org.eclipse.jgit.revwalk.RevCommit;
 import org.junit.Before;
 import org.junit.Test;
@@ -194,8 +195,9 @@
 
 	@Test
 	public void testNoFastForwardAndSquash() throws Exception {
-		assertEquals("fatal: You cannot combine --squash with --no-ff.",
-				execute("git merge master --no-ff --squash")[0]);
+		assertEquals(
+				CLIText.fatalError(CLIText.get().cannotCombineSquashWithNoff),
+				executeUnchecked("git merge master --no-ff --squash")[0]);
 	}
 
 	@Test
@@ -209,8 +211,8 @@
 		git.add().addFilepattern("file").call();
 		git.commit().setMessage("commit#2").call();
 
-		assertEquals("fatal: Not possible to fast-forward, aborting.",
-				execute("git merge master --ff-only")[0]);
+		assertEquals(CLIText.fatalError(CLIText.get().ffNotPossibleAborting),
+				executeUnchecked("git merge master --ff-only")[0]);
 	}
 
 	@Test
diff --git a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/ReflogTest.java b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/ReflogTest.java
index ce80832..7330ee9 100644
--- a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/ReflogTest.java
+++ b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/ReflogTest.java
@@ -57,24 +57,27 @@
 
 	@Test
 	public void testSingleCommit() throws Exception {
-		new Git(db).commit().setMessage("initial commit").call();
+		try (Git git = new Git(db)) {
+			git.commit().setMessage("initial commit").call();
 
-		assertEquals("6fd41be HEAD@{0}: commit (initial): initial commit",
-				execute("git reflog")[0]);
+			assertEquals("6fd41be HEAD@{0}: commit (initial): initial commit",
+					execute("git reflog")[0]);
+		}
 	}
 
 	@Test
 	public void testBranch() throws Exception {
-		Git git = new Git(db);
-		git.commit().setMessage("first commit").call();
-		git.checkout().setCreateBranch(true).setName("side").call();
-		writeTrashFile("file", "side content");
-		git.add().addFilepattern("file").call();
-		git.commit().setMessage("side commit").call();
+		try (Git git = new Git(db)) {
+			git.commit().setMessage("first commit").call();
+			git.checkout().setCreateBranch(true).setName("side").call();
+			writeTrashFile("file", "side content");
+			git.add().addFilepattern("file").call();
+			git.commit().setMessage("side commit").call();
 
-		assertArrayEquals(new String[] {
-				"38890c7 side@{0}: commit: side commit",
-				"d216986 side@{1}: branch: Created from commit first commit",
-				"" }, execute("git reflog refs/heads/side"));
+			assertArrayEquals(new String[] {
+					"38890c7 side@{0}: commit: side commit",
+					"d216986 side@{1}: branch: Created from commit first commit",
+					"" }, execute("git reflog refs/heads/side"));
+		}
 	}
 }
\ No newline at end of file
diff --git a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/RemoteTest.java b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/RemoteTest.java
new file mode 100644
index 0000000..58e0e19
--- /dev/null
+++ b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/RemoteTest.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2015, Kaloyan Raev <kaloyan.r@zend.com>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ *   copyright notice, this list of conditions and the following
+ *   disclaimer in the documentation and/or other materials provided
+ *   with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ *   names of its contributors may be used to endorse or promote
+ *   products derived from this software without specific prior
+ *   written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.eclipse.jgit.pgm;
+
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.util.List;
+
+import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.lib.CLIRepositoryTestCase;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.lib.StoredConfig;
+import org.eclipse.jgit.transport.RefSpec;
+import org.eclipse.jgit.transport.RemoteConfig;
+import org.eclipse.jgit.transport.URIish;
+import org.junit.Before;
+import org.junit.Test;
+
+public class RemoteTest extends CLIRepositoryTestCase {
+
+	private StoredConfig config;
+
+	private RemoteConfig remote;
+
+	@Before
+	@Override
+	public void setUp() throws Exception {
+		super.setUp();
+
+		// create another repository
+		Repository remoteRepository = createWorkRepository();
+
+		// set it up as a remote to this repository
+		config = db.getConfig();
+		remote = new RemoteConfig(config, "test");
+		remote.addFetchRefSpec(
+				new RefSpec("+refs/heads/*:refs/remotes/test/*"));
+		URIish uri = new URIish(
+				remoteRepository.getDirectory().toURI().toURL());
+		remote.addURI(uri);
+		remote.update(config);
+		config.save();
+
+		Git.wrap(remoteRepository).commit().setMessage("initial commit").call();
+	}
+
+	@Test
+	public void testList() throws Exception {
+		assertArrayEquals(new String[] { remote.getName(), "" },
+				execute("git remote"));
+	}
+
+	@Test
+	public void testVerboseList() throws Exception {
+		assertArrayEquals(
+				new String[] {
+						String.format("%s\t%s (fetch)", remote.getName(),
+								remote.getURIs().get(0)),
+						String.format("%s\t%s (push)", remote.getName(),
+								remote.getURIs().get(0)),
+						"" },
+				execute("git remote -v"));
+	}
+
+	@Test
+	public void testAdd() throws Exception {
+		assertArrayEquals(new String[] { "" },
+				execute("git remote add second git://test.com/second"));
+
+		List<RemoteConfig> remotes = RemoteConfig.getAllRemoteConfigs(config);
+		assertEquals(2, remotes.size());
+		assertEquals("second", remotes.get(0).getName());
+		assertEquals("test", remotes.get(1).getName());
+	}
+
+	@Test
+	public void testRemove() throws Exception {
+		assertArrayEquals(new String[] { "" },
+				execute("git remote remove test"));
+
+		assertTrue(RemoteConfig.getAllRemoteConfigs(config).isEmpty());
+	}
+
+	@Test
+	public void testSetUrl() throws Exception {
+		assertArrayEquals(new String[] { "" },
+				execute("git remote set-url test git://test.com/test"));
+
+		RemoteConfig result = new RemoteConfig(config, "test");
+		assertEquals("test", result.getName());
+		assertArrayEquals(new URIish[] { new URIish("git://test.com/test") },
+				result.getURIs().toArray());
+		assertTrue(result.getPushURIs().isEmpty());
+	}
+
+	@Test
+	public void testSetUrlPush() throws Exception {
+		assertArrayEquals(new String[] { "" },
+				execute("git remote set-url --push test git://test.com/test"));
+
+		RemoteConfig result = new RemoteConfig(config, "test");
+		assertEquals("test", result.getName());
+		assertEquals(remote.getURIs(), result.getURIs());
+		assertArrayEquals(new URIish[] { new URIish("git://test.com/test") },
+				result.getPushURIs().toArray());
+	}
+
+	@Test
+	public void testUpdate() throws Exception {
+		assertArrayEquals(new String[] {
+				"From " + remote.getURIs().get(0).toString(),
+				" * [new branch]      master     -> test/master", "", "" },
+				execute("git remote update test"));
+	}
+
+}
diff --git a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/RepoTest.java b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/RepoTest.java
index 90efae2..ba62383 100644
--- a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/RepoTest.java
+++ b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/RepoTest.java
@@ -44,8 +44,11 @@
 
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
 import java.io.File;
+import java.util.Arrays;
+
 import org.eclipse.jgit.api.Git;
 import org.eclipse.jgit.junit.JGitTestUtil;
 import org.eclipse.jgit.lib.CLIRepositoryTestCase;
@@ -71,33 +74,62 @@
 		super.setUp();
 
 		defaultDb = createWorkRepository();
-		Git git = new Git(defaultDb);
-		JGitTestUtil.writeTrashFile(defaultDb, "hello.txt", "world");
-		git.add().addFilepattern("hello.txt").call();
-		git.commit().setMessage("Initial commit").call();
+		try (Git git = new Git(defaultDb)) {
+			JGitTestUtil.writeTrashFile(defaultDb, "hello.txt", "world");
+			git.add().addFilepattern("hello.txt").call();
+			git.commit().setMessage("Initial commit").call();
+		}
 
 		notDefaultDb = createWorkRepository();
-		git = new Git(notDefaultDb);
-		JGitTestUtil.writeTrashFile(notDefaultDb, "world.txt", "hello");
-		git.add().addFilepattern("world.txt").call();
-		git.commit().setMessage("Initial commit").call();
+		try (Git git = new Git(notDefaultDb)) {
+			JGitTestUtil.writeTrashFile(notDefaultDb, "world.txt", "hello");
+			git.add().addFilepattern("world.txt").call();
+			git.commit().setMessage("Initial commit").call();
+		}
 
 		groupADb = createWorkRepository();
-		git = new Git(groupADb);
-		JGitTestUtil.writeTrashFile(groupADb, "a.txt", "world");
-		git.add().addFilepattern("a.txt").call();
-		git.commit().setMessage("Initial commit").call();
+		try (Git git = new Git(groupADb)) {
+			JGitTestUtil.writeTrashFile(groupADb, "a.txt", "world");
+			git.add().addFilepattern("a.txt").call();
+			git.commit().setMessage("Initial commit").call();
+		}
 
 		groupBDb = createWorkRepository();
-		git = new Git(groupBDb);
-		JGitTestUtil.writeTrashFile(groupBDb, "b.txt", "world");
-		git.add().addFilepattern("b.txt").call();
-		git.commit().setMessage("Initial commit").call();
+		try (Git git = new Git(groupBDb)) {
+			JGitTestUtil.writeTrashFile(groupBDb, "b.txt", "world");
+			git.add().addFilepattern("b.txt").call();
+			git.commit().setMessage("Initial commit").call();
+		}
 
 		resolveRelativeUris();
 	}
 
 	@Test
+	public void testMissingPath() throws Exception {
+		try {
+			execute("git repo");
+			fail("Must die");
+		} catch (Die e) {
+			// expected, requires argument
+		}
+	}
+
+	/**
+	 * See bug 484951: "git repo -h" should not print unexpected values
+	 *
+	 * @throws Exception
+	 */
+	@Test
+	public void testZombieHelpArgument() throws Exception {
+		String[] output = execute("git repo -h");
+		String all = Arrays.toString(output);
+		assertTrue("Unexpected help output: " + all,
+				all.contains("jgit repo"));
+		assertFalse("Unexpected help output: " + all,
+				all.contains("jgit repo VAL"));
+	}
+
+	@Test
 	public void testAddRepoManifest() throws Exception {
 		StringBuilder xmlContent = new StringBuilder();
 		xmlContent.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n")
diff --git a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/ResetTest.java b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/ResetTest.java
new file mode 100644
index 0000000..16c5889
--- /dev/null
+++ b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/ResetTest.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2015, Kaloyan Raev <kaloyan.r@zend.com>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ *   copyright notice, this list of conditions and the following
+ *   disclaimer in the documentation and/or other materials provided
+ *   with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ *   names of its contributors may be used to endorse or promote
+ *   products derived from this software without specific prior
+ *   written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.eclipse.jgit.pgm;
+
+import static org.junit.Assert.*;
+
+import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.lib.CLIRepositoryTestCase;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+
+public class ResetTest extends CLIRepositoryTestCase {
+
+	private Git git;
+
+	@Override
+	@Before
+	public void setUp() throws Exception {
+		super.setUp();
+		git = new Git(db);
+	}
+
+	@Test
+	public void testPathOptionHelp() throws Exception {
+		String[] result = execute("git reset -h");
+		assertTrue("Unexpected argument: " + result[1],
+				result[1].endsWith("[-- path ... ...]"));
+	}
+
+	@Test
+	public void testZombieArgument_Bug484951() throws Exception {
+		String[] result = execute("git reset -h");
+		assertFalse("Unexpected argument: " + result[0],
+				result[0].contains("[VAL ...]"));
+	}
+
+	@Test
+	public void testResetSelf() throws Exception {
+		RevCommit commit = git.commit().setMessage("initial commit").call();
+		assertStringArrayEquals("",
+				execute("git reset --hard " + commit.getId().name()));
+		assertEquals(commit.getId(),
+				git.getRepository().exactRef("HEAD").getObjectId());
+	}
+
+	@Test
+	public void testResetPrevious() throws Exception {
+		RevCommit commit = git.commit().setMessage("initial commit").call();
+		git.commit().setMessage("second commit").call();
+		assertStringArrayEquals("",
+				execute("git reset --hard " + commit.getId().name()));
+		assertEquals(commit.getId(),
+				git.getRepository().exactRef("HEAD").getObjectId());
+	}
+
+	@Test
+	public void testResetEmptyPath() throws Exception {
+		RevCommit commit = git.commit().setMessage("initial commit").call();
+		assertStringArrayEquals("",
+				execute("git reset --hard " + commit.getId().name() + " --"));
+		assertEquals(commit.getId(),
+				git.getRepository().exactRef("HEAD").getObjectId());
+	}
+
+	@Test
+	public void testResetPathDoubleDash() throws Exception {
+		resetPath(true, true);
+	}
+
+	@Test
+	public void testResetPathNoDoubleDash() throws Exception {
+		resetPath(false, true);
+	}
+
+	@Test
+	public void testResetPathDoubleDashNoRef() throws Exception {
+		resetPath(true, false);
+	}
+
+	@Ignore("Currently we cannote recognize if a name is a commit-ish or a path, "
+			+ "so 'git reset a' will not work if 'a' is not a branch name but a file path")
+	@Test
+	public void testResetPathNoDoubleDashNoRef() throws Exception {
+		resetPath(false, false);
+	}
+
+	private void resetPath(boolean useDoubleDash, boolean supplyCommit)
+			throws Exception {
+		// create files a and b
+		writeTrashFile("a", "Hello world a");
+		writeTrashFile("b", "Hello world b");
+		// stage the files
+		git.add().addFilepattern(".").call();
+		// commit the files
+		RevCommit commit = git.commit().setMessage("files a & b").call();
+
+		// change both files a and b
+		writeTrashFile("a", "New Hello world a");
+		writeTrashFile("b", "New Hello world b");
+		// stage the files
+		git.add().addFilepattern(".").call();
+
+		// reset only file a
+		String cmd = String.format("git reset %s%s a",
+				supplyCommit ? commit.getId().name() : "",
+				useDoubleDash ? " --" : "");
+		assertStringArrayEquals("", execute(cmd));
+		assertEquals(commit.getId(),
+				git.getRepository().exactRef("HEAD").getObjectId());
+
+		org.eclipse.jgit.api.Status status = git.status().call();
+		// assert that file a is unstaged
+		assertArrayEquals(new String[] { "a" },
+				status.getModified().toArray());
+		// assert that file b is still staged
+		assertArrayEquals(new String[] { "b" },
+				status.getChanged().toArray());
+	}
+
+}
diff --git a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/StatusTest.java b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/StatusTest.java
index 793fc7d..368047c 100644
--- a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/StatusTest.java
+++ b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/StatusTest.java
@@ -42,19 +42,28 @@
  */
 package org.eclipse.jgit.pgm;
 
+import static org.eclipse.jgit.lib.Constants.MASTER;
+import static org.eclipse.jgit.lib.Constants.R_HEADS;
+import static org.junit.Assert.assertTrue;
 
 import java.io.IOException;
 
 import org.eclipse.jgit.api.Git;
 import org.eclipse.jgit.api.errors.GitAPIException;
 import org.eclipse.jgit.lib.CLIRepositoryTestCase;
-import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.revwalk.RevCommit;
 import org.junit.Test;
 
 public class StatusTest extends CLIRepositoryTestCase {
 
 	@Test
+	public void testPathOptionHelp() throws Exception {
+		String[] result = execute("git status -h");
+		assertTrue("Unexpected argument: " + result[1],
+				result[1].endsWith("[-- path ... ...]"));
+	}
+
+	@Test
 	public void testStatusDefault() throws Exception {
 		executeTest("git status", false, true);
 	}
@@ -254,7 +263,7 @@
 	}
 
 	private void detachHead(Git git) throws IOException, GitAPIException {
-		String commitId = db.getRef(Constants.MASTER).getObjectId().name();
+		String commitId = db.exactRef(R_HEADS + MASTER).getObjectId().name();
 		git.checkout().setName(commitId).call();
 	}
 
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 ab09db5..0fe25f5 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
@@ -68,6 +68,6 @@
 		git.commit().setMessage("commit").call();
 
 		assertEquals("fatal: tag 'test' already exists",
-				execute("git tag test")[0]);
+				executeUnchecked("git tag test")[0]);
 	}
 }
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 4e28e0b..45d6d2c 100644
--- a/org.eclipse.jgit.pgm/.settings/org.eclipse.jdt.core.prefs
+++ b/org.eclipse.jgit.pgm/.settings/org.eclipse.jdt.core.prefs
@@ -1,9 +1,9 @@
 eclipse.preferences.version=1
 org.eclipse.jdt.core.compiler.annotation.inheritNullAnnotations=enabled
 org.eclipse.jdt.core.compiler.annotation.missingNonNullByDefaultAnnotation=ignore
-org.eclipse.jdt.core.compiler.annotation.nonnull=org.eclipse.jdt.annotation.NonNull
-org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=org.eclipse.jdt.annotation.NonNullByDefault
-org.eclipse.jdt.core.compiler.annotation.nullable=org.eclipse.jdt.annotation.Nullable
+org.eclipse.jdt.core.compiler.annotation.nonnull=org.eclipse.jgit.annotations.NonNull
+org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=org.eclipse.jgit.annotations.NonNullByDefault
+org.eclipse.jdt.core.compiler.annotation.nullable=org.eclipse.jgit.annotations.Nullable
 org.eclipse.jdt.core.compiler.annotation.nullanalysis=enabled
 org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
 org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate
diff --git a/org.eclipse.jgit.pgm/BUCK b/org.eclipse.jgit.pgm/BUCK
new file mode 100644
index 0000000..edcf2fc
--- /dev/null
+++ b/org.eclipse.jgit.pgm/BUCK
@@ -0,0 +1,70 @@
+include_defs('//tools/git.defs')
+
+java_library(
+  name = 'pgm',
+  srcs = glob(['src/**']),
+  resources = glob(['resources/**']),
+  deps = [
+    ':services',
+    '//org.eclipse.jgit:jgit',
+    '//org.eclipse.jgit.archive:jgit-archive',
+    '//org.eclipse.jgit.http.apache:http-apache',
+    '//org.eclipse.jgit.ui:ui',
+    '//lib:args4j',
+  ],
+  visibility = ['PUBLIC'],
+)
+
+prebuilt_jar(
+  name = 'services',
+  binary_jar = ':services__jar',
+)
+
+genrule(
+  name = 'services__jar',
+  cmd = 'cd $SRCDIR ; zip -qr $OUT .',
+  srcs = glob(['META-INF/services/*']),
+  out = 'services.jar',
+)
+
+genrule(
+  name = 'jgit',
+  cmd = ''.join([
+    'mkdir $TMP/META-INF &&',
+    'cp $(location :binary_manifest) $TMP/META-INF/MANIFEST.MF &&',
+    'cp $(location :jgit_jar) $TMP/jgit.jar &&',
+    'cd $TMP && zip $TMP/jgit.jar META-INF/MANIFEST.MF &&',
+    'cat $SRCDIR/jgit.sh $TMP/jgit.jar >$OUT &&',
+    'chmod a+x $OUT',
+  ]),
+  srcs = ['jgit.sh'],
+  out = 'jgit',
+  visibility = ['PUBLIC'],
+)
+
+java_binary(
+  name = 'jgit_jar',
+  deps = [
+    ':pgm',
+    '//lib:slf4j-simple',
+    '//lib:tukaani-xz',
+  ],
+  blacklist = [
+    'META-INF/DEPENDENCIES',
+    'META-INF/maven/.*',
+  ],
+)
+
+genrule(
+  name = 'binary_manifest',
+  cmd = ';'.join(['echo "%s: %s" >>$OUT' % e for e in [
+    ('Manifest-Version', '1.0'),
+    ('Main-Class', 'org.eclipse.jgit.pgm.Main'),
+    ('Bundle-Version', git_version()),
+    ('Implementation-Title', 'JGit Command Line Interface'),
+    ('Implementation-Vendor', 'Eclipse.org - JGit'),
+    ('Implementation-Vendor-URL', 'http://www.eclipse.org/jgit/'),
+    ('Implementation-Vendor-Id', 'org.eclipse.jgit'),
+  ]] + ['echo >>$OUT']),
+  out = 'MANIFEST.MF',
+)
diff --git a/org.eclipse.jgit.pgm/META-INF/MANIFEST.MF b/org.eclipse.jgit.pgm/META-INF/MANIFEST.MF
index 7d00a96..5c6a16b 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.1.2.201602141800-r
+Bundle-Version: 4.2.1.qualifier
 Bundle-Vendor: %provider_name
 Bundle-ActivationPolicy: lazy
 Bundle-Localization: plugin
@@ -10,38 +10,40 @@
 Import-Package: org.apache.commons.compress.archivers;version="[1.3,2.0)",
  org.apache.commons.compress.archivers.tar;version="[1.3,2.0)",
  org.apache.commons.compress.archivers.zip;version="[1.3,2.0)",
- org.eclipse.jgit.api;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.api.errors;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.archive;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.awtui;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.blame;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.diff;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.dircache;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.errors;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.gitrepo;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.internal.storage.file;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.internal.storage.pack;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.lib;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.merge;version="4.1.2",
- org.eclipse.jgit.nls;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.notes;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.revplot;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.revwalk;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.revwalk.filter;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.storage.file;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.storage.pack;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.transport;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.transport.resolver;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.treewalk;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.treewalk.filter;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.util;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.util.io;version="[4.1.2,4.2.0)",
+ org.eclipse.jgit.api;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.api.errors;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.archive;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.awtui;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.blame;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.diff;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.dircache;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.errors;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.gitrepo;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.internal.storage.file;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.internal.storage.pack;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.internal.storage.reftree;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.lib;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.merge;version="4.2.1",
+ org.eclipse.jgit.nls;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.notes;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.revplot;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.revwalk;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.revwalk.filter;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.storage.file;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.storage.pack;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.transport;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.transport.http.apache;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.transport.resolver;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.treewalk;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.treewalk.filter;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.util;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.util.io;version="[4.2.1,4.3.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.1.2";
+Export-Package: org.eclipse.jgit.console;version="4.2.1";
   uses:="org.eclipse.jgit.transport,
    org.eclipse.jgit.util",
- org.eclipse.jgit.pgm;version="4.1.2";
+ org.eclipse.jgit.pgm;version="4.2.1";
   uses:="org.eclipse.jgit.revwalk,
    org.eclipse.jgit.treewalk.filter,
    org.eclipse.jgit.pgm.opt,
@@ -52,15 +54,14 @@
    org.eclipse.jgit.treewalk,
    javax.swing,
    org.eclipse.jgit.transport",
- org.eclipse.jgit.pgm.debug;version="4.1.2";
+ org.eclipse.jgit.pgm.debug;version="4.2.1";
   uses:="org.eclipse.jgit.util.io,
    org.eclipse.jgit.pgm",
- org.eclipse.jgit.pgm.internal;version="4.1.2";x-friends:="org.eclipse.jgit.pgm.test,org.eclipse.jgit.test",
- org.eclipse.jgit.pgm.opt;version="4.1.2";
+ org.eclipse.jgit.pgm.internal;version="4.2.1";x-friends:="org.eclipse.jgit.pgm.test,org.eclipse.jgit.test",
+ org.eclipse.jgit.pgm.opt;version="4.2.1";
   uses:="org.eclipse.jgit.lib,
    org.eclipse.jgit.revwalk,
    org.kohsuke.args4j.spi,
    org.kohsuke.args4j"
 Main-Class: org.eclipse.jgit.pgm.Main
 Implementation-Title: JGit Command Line Interface
-Require-Bundle: org.eclipse.jdt.annotation;bundle-version="[1.1.0,2.0.0)";resolution:=optional
diff --git a/org.eclipse.jgit.pgm/META-INF/SOURCE-MANIFEST.MF b/org.eclipse.jgit.pgm/META-INF/SOURCE-MANIFEST.MF
index 215f8e7..5df8f53 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.1.2.201602141800-r
-Eclipse-SourceBundle: org.eclipse.jgit.pgm;version="4.1.2.201602141800-r";roots="."
+Bundle-Version: 4.2.1.qualifier
+Eclipse-SourceBundle: org.eclipse.jgit.pgm;version="4.2.1.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 e1b0549..6aa2004 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
@@ -24,6 +24,7 @@
 org.eclipse.jgit.pgm.Push
 org.eclipse.jgit.pgm.ReceivePack
 org.eclipse.jgit.pgm.Reflog
+org.eclipse.jgit.pgm.Remote
 org.eclipse.jgit.pgm.Repo
 org.eclipse.jgit.pgm.Reset
 org.eclipse.jgit.pgm.RevList
@@ -40,6 +41,7 @@
 org.eclipse.jgit.pgm.debug.MakeCacheTree
 org.eclipse.jgit.pgm.debug.ReadDirCache
 org.eclipse.jgit.pgm.debug.RebuildCommitGraph
+org.eclipse.jgit.pgm.debug.RebuildRefTree
 org.eclipse.jgit.pgm.debug.ShowCacheTree
 org.eclipse.jgit.pgm.debug.ShowCommands
 org.eclipse.jgit.pgm.debug.ShowDirCache
diff --git a/org.eclipse.jgit.pgm/pom.xml b/org.eclipse.jgit.pgm/pom.xml
index f84e746..b9ad706 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.1.2.201602141800-r</version>
+    <version>4.2.1-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.pgm</artifactId>
@@ -95,6 +95,17 @@
     </dependency>
 
     <dependency>
+      <groupId>org.eclipse.jgit</groupId>
+      <artifactId>org.eclipse.jgit.http.apache</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+
+    <dependency>
+      <groupId>org.apache.httpcomponents</groupId>
+      <artifactId>httpclient</artifactId>
+    </dependency>
+
+    <dependency>
       <groupId>org.slf4j</groupId>
       <artifactId>slf4j-api</artifactId>
       <version>${slf4j-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 f7591fd..b4b1261 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
@@ -20,6 +20,7 @@
 branchCreatedFrom=branch: Created from {0}
 branchDetachedHEAD=detached HEAD
 branchIsNotAnAncestorOfYourCurrentHEAD=The branch ''{0}'' is not an ancestor of your current HEAD.\nIf you are sure you want to delete it, run ''jgit branch -D {0}''.
+branchNameRequired=branch name required
 branchNotFound=branch ''{0}'' not found.
 cacheTreePathInfo="{0}": {1} entries, {2} children
 cannotBeRenamed={0} cannot be renamed
@@ -89,7 +90,9 @@
 metaVar_base=base
 metaVar_blameL=START,END
 metaVar_blameReverse=START..END
+metaVar_branchAndStartPoint=branch [start-name]
 metaVar_branchName=branch
+metaVar_branchNames=branch ...
 metaVar_bucket=BUCKET
 metaVar_command=command
 metaVar_commandDetail=DETAIL
@@ -109,6 +112,7 @@
 metaVar_n=n
 metaVar_name=name
 metaVar_object=object
+metaVar_oldNewBranchNames=[oldbranch] newbranch
 metaVar_op=OP
 metaVar_pass=PASS
 metaVar_path=path
@@ -125,9 +129,11 @@
 metaVar_uriish=uri-ish
 metaVar_url=URL
 metaVar_user=USER
+metaVar_values=value ...
 metaVar_version=VERSION
 mostCommonlyUsedCommandsAre=The most commonly used commands are:
 needApprovalToDestroyCurrentRepository=Need approval to destroy current repository
+needSingleRevision=Needed a single revision
 noGitRepositoryConfigured=No Git repository configured.
 noNamesFound=No names found, cannot describe anything.
 noSuchFile=no such file: {0}
@@ -142,6 +148,7 @@
 notARevision=Not a revision: {0}
 notATree={0} is not a tree
 notAValidRefName={0} is not a valid ref name
+notAValidCommitName={0} is not a valid commit name
 notAnIndexFile={0} is not an index file
 notAnObject={0} is not an object
 notFound=!! NOT FOUND !!
@@ -186,12 +193,14 @@
 tooManyRefsGiven=Too many refs given
 unknownIoErrorStdout=An unknown I/O error occurred on standard output
 unknownMergeStrategy=unknown merge strategy {0} specified
+unknownSubcommand=Unknown subcommand: {0}
 unmergedPaths=Unmerged paths:
 unsupportedOperation=Unsupported operation: {0}
 untrackedFiles=Untracked files:
 updating=Updating {0}..{1}
 usage_Aggressive=This option will cause gc to more aggressively optimize the repository at the expense of taking much more time
 usage_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_branches=Set branch field in .gitmodules
 usage_Blame=Show what revision and author last modified each line
 usage_CommandLineClientForamazonsS3Service=Command line client for Amazon's S3 service
 usage_CommitAll=commit all modified and deleted files
@@ -219,6 +228,8 @@
 usage_MergesTwoDevelopmentHistories=Merges two development histories
 usage_ReadDirCache= Read the DirCache 100 times
 usage_RebuildCommitGraph=Recreate a repository from another one's commit graph
+usage_RebuildRefTree=Copy references into a RefTree
+usage_Remote=Manage set of tracked repositories
 usage_RepositoryToReadFrom=Repository to read from
 usage_RepositoryToReceiveInto=Repository to receive into
 usage_RevList=List commit objects in reverse chronological order
@@ -326,11 +337,13 @@
 usage_portNumberToListenOn=port number to listen on
 usage_printOnlyBranchesThatContainTheCommit=print only branches that contain the commit
 usage_pruneStaleTrackingRefs=prune stale tracking refs
+usage_pushUrls=push URLs are manipulated
 usage_quiet=don't show progress messages
 usage_recordChangesToRepository=Record changes to the repository
 usage_recurseIntoSubtrees=recurse into subtrees
 usage_renameLimit=limit size of rename matrix
 usage_reset=Reset current HEAD to the specified state
+usage_resetReference=Reset to given reference name
 usage_resetHard=Resets the index and working tree
 usage_resetSoft=Resets without touching the index file nor the working tree
 usage_resetMixed=Resets the index but not the working tree
@@ -347,6 +360,7 @@
 usage_notags=do not fetch tags
 usage_tagMessage=tag message
 usage_untrackedFilesMode=show untracked files
+usage_updateRef=reference to update
 usage_updateRemoteRefsFromAnotherRepository=Update remote refs from another repository
 usage_useNameInsteadOfOriginToTrackUpstream=use <name> instead of 'origin' to track upstream
 usage_checkoutBranchAfterClone=checkout named branch instead of remotes's HEAD
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/AbstractFetchCommand.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/AbstractFetchCommand.java
index fde0a78..2cee2cb 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/AbstractFetchCommand.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/AbstractFetchCommand.java
@@ -129,20 +129,20 @@
 			final TrackingRefUpdate u) {
 		final RefUpdate.Result r = u.getResult();
 		if (r == RefUpdate.Result.LOCK_FAILURE)
-			return "[lock fail]";
+			return "[lock fail]"; //$NON-NLS-1$
 		if (r == RefUpdate.Result.IO_FAILURE)
-			return "[i/o error]";
+			return "[i/o error]"; //$NON-NLS-1$
 		if (r == RefUpdate.Result.REJECTED)
-			return "[rejected]";
+			return "[rejected]"; //$NON-NLS-1$
 		if (ObjectId.zeroId().equals(u.getNewObjectId()))
-			return "[deleted]";
+			return "[deleted]"; //$NON-NLS-1$
 
 		if (r == RefUpdate.Result.NEW) {
 			if (u.getRemoteName().startsWith(Constants.R_HEADS))
-				return "[new branch]";
+				return "[new branch]"; //$NON-NLS-1$
 			else if (u.getLocalName().startsWith(Constants.R_TAGS))
-				return "[new tag]";
-			return "[new]";
+				return "[new tag]"; //$NON-NLS-1$
+			return "[new]"; //$NON-NLS-1$
 		}
 
 		if (r == RefUpdate.Result.FORCED) {
@@ -158,7 +158,7 @@
 		}
 
 		if (r == RefUpdate.Result.NO_CHANGE)
-			return "[up to date]";
+			return "[up to date]"; //$NON-NLS-1$
 		return "[" + r.name() + "]"; //$NON-NLS-1$//$NON-NLS-2$
 	}
 
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Branch.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Branch.java
index 83a1ca7..bf6ee3a 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Branch.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Branch.java
@@ -45,7 +45,6 @@
 
 import java.io.IOException;
 import java.text.MessageFormat;
-import java.util.ArrayList;
 import java.util.Collection;
 import java.util.LinkedHashMap;
 import java.util.List;
@@ -65,15 +64,18 @@
 import org.eclipse.jgit.lib.RefUpdate.Result;
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.pgm.internal.CLIText;
-import org.eclipse.jgit.pgm.opt.CmdLineParser;
+import org.eclipse.jgit.pgm.opt.OptionWithValuesListHandler;
 import org.eclipse.jgit.revwalk.RevWalk;
 import org.kohsuke.args4j.Argument;
-import org.kohsuke.args4j.ExampleMode;
 import org.kohsuke.args4j.Option;
 
 @Command(common = true, usage = "usage_listCreateOrDeleteBranches")
 class Branch extends TextBuiltin {
 
+	private String otherBranch;
+	private boolean createForce;
+	private boolean rename;
+
 	@Option(name = "--remote", aliases = { "-r" }, usage = "usage_actOnRemoteTrackingBranches")
 	private boolean remote = false;
 
@@ -83,23 +85,69 @@
 	@Option(name = "--contains", metaVar = "metaVar_commitish", usage = "usage_printOnlyBranchesThatContainTheCommit")
 	private String containsCommitish;
 
-	@Option(name = "--delete", aliases = { "-d" }, usage = "usage_deleteFullyMergedBranch")
-	private boolean delete = false;
+	private List<String> delete;
 
-	@Option(name = "--delete-force", aliases = { "-D" }, usage = "usage_deleteBranchEvenIfNotMerged")
-	private boolean deleteForce = false;
+	@Option(name = "--delete", aliases = {
+			"-d" }, metaVar = "metaVar_branchNames", usage = "usage_deleteFullyMergedBranch", handler = OptionWithValuesListHandler.class)
+	public void delete(List<String> names) {
+		if (names.isEmpty()) {
+			throw die(CLIText.get().branchNameRequired);
+		}
+		delete = names;
+	}
 
-	@Option(name = "--create-force", aliases = { "-f" }, usage = "usage_forceCreateBranchEvenExists")
-	private boolean createForce = false;
+	private List<String> deleteForce;
 
-	@Option(name = "-m", usage = "usage_moveRenameABranch")
-	private boolean rename = false;
+	@Option(name = "--delete-force", aliases = {
+			"-D" }, metaVar = "metaVar_branchNames", usage = "usage_deleteBranchEvenIfNotMerged", handler = OptionWithValuesListHandler.class)
+	public void deleteForce(List<String> names) {
+		if (names.isEmpty()) {
+			throw die(CLIText.get().branchNameRequired);
+		}
+		deleteForce = names;
+	}
+
+	@Option(name = "--create-force", aliases = {
+			"-f" }, metaVar = "metaVar_branchAndStartPoint", usage = "usage_forceCreateBranchEvenExists", handler = OptionWithValuesListHandler.class)
+	public void createForce(List<String> branchAndStartPoint) {
+		createForce = true;
+		if (branchAndStartPoint.isEmpty()) {
+			throw die(CLIText.get().branchNameRequired);
+		}
+		if (branchAndStartPoint.size() > 2) {
+			throw die(CLIText.get().tooManyRefsGiven);
+		}
+		if (branchAndStartPoint.size() == 1) {
+			branch = branchAndStartPoint.get(0);
+		} else {
+			branch = branchAndStartPoint.get(0);
+			otherBranch = branchAndStartPoint.get(1);
+		}
+	}
+
+	@Option(name = "--move", aliases = {
+			"-m" }, metaVar = "metaVar_oldNewBranchNames", usage = "usage_moveRenameABranch", handler = OptionWithValuesListHandler.class)
+	public void moveRename(List<String> currentAndNew) {
+		rename = true;
+		if (currentAndNew.isEmpty()) {
+			throw die(CLIText.get().branchNameRequired);
+		}
+		if (currentAndNew.size() > 2) {
+			throw die(CLIText.get().tooManyRefsGiven);
+		}
+		if (currentAndNew.size() == 1) {
+			branch = currentAndNew.get(0);
+		} else {
+			branch = currentAndNew.get(0);
+			otherBranch = currentAndNew.get(1);
+		}
+	}
 
 	@Option(name = "--verbose", aliases = { "-v" }, usage = "usage_beVerbose")
 	private boolean verbose = false;
 
-	@Argument
-	private List<String> branches = new ArrayList<String>();
+	@Argument(metaVar = "metaVar_name")
+	private String branch;
 
 	private final Map<String, Ref> printRefs = new LinkedHashMap<String, Ref>();
 
@@ -110,30 +158,33 @@
 
 	@Override
 	protected void run() throws Exception {
-		if (delete || deleteForce)
-			delete(deleteForce);
-		else {
-			if (branches.size() > 2)
-				throw die(CLIText.get().tooManyRefsGiven + new CmdLineParser(this).printExample(ExampleMode.ALL));
-
+		if (delete != null || deleteForce != null) {
+			if (delete != null) {
+				delete(delete, false);
+			}
+			if (deleteForce != null) {
+				delete(deleteForce, true);
+			}
+		} else {
 			if (rename) {
 				String src, dst;
-				if (branches.size() == 1) {
+				if (otherBranch == null) {
 					final Ref head = db.getRef(Constants.HEAD);
-					if (head != null && head.isSymbolic())
+					if (head != null && head.isSymbolic()) {
 						src = head.getLeaf().getName();
-					else
+					} else {
 						throw die(CLIText.get().cannotRenameDetachedHEAD);
-					dst = branches.get(0);
+					}
+					dst = branch;
 				} else {
-					src = branches.get(0);
+					src = branch;
 					final Ref old = db.getRef(src);
 					if (old == null)
 						throw die(MessageFormat.format(CLIText.get().doesNotExist, src));
 					if (!old.getName().startsWith(Constants.R_HEADS))
 						throw die(MessageFormat.format(CLIText.get().notABranch, src));
 					src = old.getName();
-					dst = branches.get(1);
+					dst = otherBranch;
 				}
 
 				if (!dst.startsWith(Constants.R_HEADS))
@@ -145,37 +196,47 @@
 				if (r.rename() != Result.RENAMED)
 					throw die(MessageFormat.format(CLIText.get().cannotBeRenamed, src));
 
-			} else if (branches.size() > 0) {
-				String newHead = branches.get(0);
+			} else if (createForce || branch != null) {
+				String newHead = branch;
 				String startBranch;
-				if (branches.size() == 2)
-					startBranch = branches.get(1);
-				else
+				if (createForce) {
+					startBranch = otherBranch;
+				} else {
 					startBranch = Constants.HEAD;
+				}
 				Ref startRef = db.getRef(startBranch);
 				ObjectId startAt = db.resolve(startBranch + "^0"); //$NON-NLS-1$
-				if (startRef != null)
+				if (startRef != null) {
 					startBranch = startRef.getName();
-				else
+				} else if (startAt != null) {
 					startBranch = startAt.name();
+				} else {
+					throw die(MessageFormat.format(
+							CLIText.get().notAValidCommitName, startBranch));
+				}
 				startBranch = Repository.shortenRefName(startBranch);
 				String newRefName = newHead;
-				if (!newRefName.startsWith(Constants.R_HEADS))
+				if (!newRefName.startsWith(Constants.R_HEADS)) {
 					newRefName = Constants.R_HEADS + newRefName;
-				if (!Repository.isValidRefName(newRefName))
+				}
+				if (!Repository.isValidRefName(newRefName)) {
 					throw die(MessageFormat.format(CLIText.get().notAValidRefName, newRefName));
-				if (!createForce && db.resolve(newRefName) != null)
+				}
+				if (!createForce && db.resolve(newRefName) != null) {
 					throw die(MessageFormat.format(CLIText.get().branchAlreadyExists, newHead));
+				}
 				RefUpdate updateRef = db.updateRef(newRefName);
 				updateRef.setNewObjectId(startAt);
 				updateRef.setForceUpdate(createForce);
 				updateRef.setRefLogMessage(MessageFormat.format(CLIText.get().branchCreatedFrom, startBranch), false);
 				Result update = updateRef.update();
-				if (update == Result.REJECTED)
+				if (update == Result.REJECTED) {
 					throw die(MessageFormat.format(CLIText.get().couldNotCreateBranch, newHead, update.toString()));
+				}
 			} else {
-				if (verbose)
+				if (verbose) {
 					rw = new RevWalk(db);
+				}
 				list();
 			}
 		}
@@ -245,27 +306,28 @@
 		outw.println();
 	}
 
-	private void delete(boolean force) throws IOException {
+	private void delete(List<String> branches, boolean force)
+			throws IOException {
 		String current = db.getBranch();
 		ObjectId head = db.resolve(Constants.HEAD);
-		for (String branch : branches) {
-			if (current.equals(branch)) {
-				throw die(MessageFormat.format(CLIText.get().cannotDeleteTheBranchWhichYouAreCurrentlyOn, branch));
+		for (String b : branches) {
+			if (b.equals(current)) {
+				throw die(MessageFormat.format(CLIText.get().cannotDeleteTheBranchWhichYouAreCurrentlyOn, b));
 			}
 			RefUpdate update = db.updateRef((remote ? Constants.R_REMOTES
 					: Constants.R_HEADS)
-					+ branch);
+					+ b);
 			update.setNewObjectId(head);
 			update.setForceUpdate(force || remote);
 			Result result = update.delete();
 			if (result == Result.REJECTED) {
-				throw die(MessageFormat.format(CLIText.get().branchIsNotAnAncestorOfYourCurrentHEAD, branch));
+				throw die(MessageFormat.format(CLIText.get().branchIsNotAnAncestorOfYourCurrentHEAD, b));
 			} else if (result == Result.NEW)
-				throw die(MessageFormat.format(CLIText.get().branchNotFound, branch));
+				throw die(MessageFormat.format(CLIText.get().branchNotFound, b));
 			if (remote)
-				outw.println(MessageFormat.format(CLIText.get().deletedRemoteBranch, branch));
+				outw.println(MessageFormat.format(CLIText.get().deletedRemoteBranch, b));
 			else if (verbose)
-				outw.println(MessageFormat.format(CLIText.get().deletedBranch, branch));
+				outw.println(MessageFormat.format(CLIText.get().deletedBranch, b));
 		}
 	}
 }
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Checkout.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Checkout.java
index 4579462..94517db 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Checkout.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Checkout.java
@@ -60,7 +60,7 @@
 import org.eclipse.jgit.pgm.internal.CLIText;
 import org.kohsuke.args4j.Argument;
 import org.kohsuke.args4j.Option;
-import org.kohsuke.args4j.spi.StopOptionHandler;
+import org.kohsuke.args4j.spi.RestOfArgumentsHandler;
 
 @Command(common = true, usage = "usage_checkout")
 class Checkout extends TextBuiltin {
@@ -74,11 +74,10 @@
 	@Option(name = "--orphan", usage = "usage_orphan")
 	private boolean orphan = false;
 
-	@Argument(required = true, index = 0, metaVar = "metaVar_name", usage = "usage_checkout")
+	@Argument(required = false, index = 0, metaVar = "metaVar_name", usage = "usage_checkout")
 	private String name;
 
-	@Argument(index = 1)
-	@Option(name = "--", metaVar = "metaVar_paths", multiValued = true, handler = StopOptionHandler.class)
+	@Option(name = "--", metaVar = "metaVar_paths", multiValued = true, handler = RestOfArgumentsHandler.class)
 	private List<String> paths = new ArrayList<String>();
 
 	@Override
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Clone.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Clone.java
index cd6953c..0407828 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Clone.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Clone.java
@@ -50,6 +50,7 @@
 import org.eclipse.jgit.api.Git;
 import org.eclipse.jgit.api.errors.InvalidRemoteException;
 import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.TextProgressMonitor;
 import org.eclipse.jgit.pgm.internal.CLIText;
 import org.eclipse.jgit.transport.URIish;
 import org.eclipse.jgit.util.SystemReader;
@@ -70,6 +71,9 @@
 	@Option(name = "--bare", usage = "usage_bareClone")
 	private boolean isBare;
 
+	@Option(name = "--quiet", usage = "usage_quiet")
+	private Boolean quiet;
+
 	@Argument(index = 0, required = true, metaVar = "metaVar_uriish")
 	private String sourceUri;
 
@@ -109,10 +113,16 @@
 
 		command.setGitDir(gitdir == null ? null : new File(gitdir));
 		command.setDirectory(localNameF);
-		outw.println(MessageFormat.format(CLIText.get().cloningInto, localName));
+		boolean msgs = quiet == null || !quiet.booleanValue();
+		if (msgs) {
+			command.setProgressMonitor(new TextProgressMonitor(errw));
+			outw.println(MessageFormat.format(
+					CLIText.get().cloningInto, localName));
+			outw.flush();
+		}
 		try {
 			db = command.call().getRepository();
-			if (db.resolve(Constants.HEAD) == null)
+			if (msgs && db.resolve(Constants.HEAD) == null)
 				outw.println(CLIText.get().clonedEmptyRepository);
 		} catch (InvalidRemoteException e) {
 			throw die(MessageFormat.format(CLIText.get().doesNotExist,
@@ -121,8 +131,9 @@
 			if (db != null)
 				db.close();
 		}
-
-		outw.println();
-		outw.flush();
+		if (msgs) {
+			outw.println();
+			outw.flush();
+		}
 	}
 }
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Commit.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Commit.java
index f18242d..38d8d70 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Commit.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Commit.java
@@ -96,6 +96,9 @@
 			commitCmd.setAmend(amend);
 			commitCmd.setAll(all);
 			Ref head = db.getRef(Constants.HEAD);
+			if (head == null) {
+				throw die(CLIText.get().onBranchToBeBorn);
+			}
 			RevCommit commit;
 			try {
 				commit = commitCmd.call();
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Config.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Config.java
index 8569e92..faae13a 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Config.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Config.java
@@ -74,7 +74,7 @@
 			list();
 		else
 			throw new NotSupportedException(
-					"only --list option is currently supported");
+					"only --list option is currently supported"); //$NON-NLS-1$
 	}
 
 	private void list() throws IOException, ConfigInvalidException {
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Die.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Die.java
index f07df1a..a25f1e9 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Die.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Die.java
@@ -86,6 +86,21 @@
 	 * @since 3.4
 	 */
 	public Die(boolean aborted) {
+		this(aborted, null);
+	}
+
+	/**
+	 * Construct a new exception reflecting the fact that the command execution
+	 * has been aborted before running.
+	 *
+	 * @param aborted
+	 *            boolean indicating the fact the execution has been aborted
+	 * @param cause
+	 *            can be null
+	 * @since 4.2
+	 */
+	public Die(boolean aborted, final Throwable cause) {
+		super(cause != null ? cause.getMessage() : null, cause);
 		this.aborted = aborted;
 	}
 
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Log.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Log.java
index d43424c..6947cdd 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Log.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Log.java
@@ -324,7 +324,7 @@
 			return false;
 		if (emptyLine)
 			outw.println();
-		outw.print("Notes");
+		outw.print("Notes"); //$NON-NLS-1$
 		if (label != null) {
 			outw.print(" ("); //$NON-NLS-1$
 			outw.print(label);
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Main.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Main.java
index ceb0d6b..d701f22 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Main.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Main.java
@@ -62,6 +62,8 @@
 import org.eclipse.jgit.pgm.internal.CLIText;
 import org.eclipse.jgit.pgm.opt.CmdLineParser;
 import org.eclipse.jgit.pgm.opt.SubcommandHandler;
+import org.eclipse.jgit.transport.HttpTransport;
+import org.eclipse.jgit.transport.http.apache.HttpClientConnectionFactory;
 import org.eclipse.jgit.util.CachedAuthenticator;
 import org.kohsuke.args4j.Argument;
 import org.kohsuke.args4j.CmdLineException;
@@ -88,13 +90,23 @@
 	@Argument(index = 1, metaVar = "metaVar_arg")
 	private List<String> arguments = new ArrayList<String>();
 
+	PrintWriter writer;
+
+	/**
+	 *
+	 */
+	public Main() {
+		HttpTransport.setConnectionFactory(new HttpClientConnectionFactory());
+	}
+
 	/**
 	 * Execute the command line.
 	 *
 	 * @param argv
 	 *            arguments.
+	 * @throws Exception
 	 */
-	public static void main(final String[] argv) {
+	public static void main(final String[] argv) throws Exception {
 		new Main().run(argv);
 	}
 
@@ -113,8 +125,10 @@
 	 *
 	 * @param argv
 	 *            arguments.
+	 * @throws Exception
 	 */
-	protected void run(final String[] argv) {
+	protected void run(final String[] argv) throws Exception {
+		writer = createErrorWriter();
 		try {
 			if (!installConsole()) {
 				AwtAuthenticator.install();
@@ -123,12 +137,14 @@
 			configureHttpProxy();
 			execute(argv);
 		} catch (Die err) {
-			if (err.isAborted())
-				System.exit(1);
-			System.err.println(MessageFormat.format(CLIText.get().fatalError, err.getMessage()));
-			if (showStackTrace)
-				err.printStackTrace();
-			System.exit(128);
+			if (err.isAborted()) {
+				exit(1, err);
+			}
+			writer.println(CLIText.fatalError(err.getMessage()));
+			if (showStackTrace) {
+				err.printStackTrace(writer);
+			}
+			exit(128, err);
 		} catch (Exception err) {
 			// Try to detect errno == EPIPE and exit normally if that happens
 			// There may be issues with operating system versions and locale,
@@ -136,46 +152,54 @@
 			// under other circumstances.
 			if (err.getClass() == IOException.class) {
 				// Linux, OS X
-				if (err.getMessage().equals("Broken pipe")) //$NON-NLS-1$
-					System.exit(0);
+				if (err.getMessage().equals("Broken pipe")) { //$NON-NLS-1$
+					exit(0, err);
+				}
 				// Windows
-				if (err.getMessage().equals("The pipe is being closed")) //$NON-NLS-1$
-					System.exit(0);
+				if (err.getMessage().equals("The pipe is being closed")) { //$NON-NLS-1$
+					exit(0, err);
+				}
 			}
 			if (!showStackTrace && err.getCause() != null
-					&& err instanceof TransportException)
-				System.err.println(MessageFormat.format(CLIText.get().fatalError, err.getCause().getMessage()));
+					&& err instanceof TransportException) {
+				writer.println(CLIText.fatalError(err.getCause().getMessage()));
+			}
 
 			if (err.getClass().getName().startsWith("org.eclipse.jgit.errors.")) { //$NON-NLS-1$
-				System.err.println(MessageFormat.format(CLIText.get().fatalError, err.getMessage()));
-				if (showStackTrace)
+				writer.println(CLIText.fatalError(err.getMessage()));
+				if (showStackTrace) {
 					err.printStackTrace();
-				System.exit(128);
+				}
+				exit(128, err);
 			}
 			err.printStackTrace();
-			System.exit(1);
+			exit(1, err);
 		}
 		if (System.out.checkError()) {
-			System.err.println(CLIText.get().unknownIoErrorStdout);
-			System.exit(1);
+			writer.println(CLIText.get().unknownIoErrorStdout);
+			exit(1, null);
 		}
-		if (System.err.checkError()) {
+		if (writer.checkError()) {
 			// No idea how to present an error here, most likely disk full or
 			// broken pipe
-			System.exit(1);
+			exit(1, null);
 		}
 	}
 
+	PrintWriter createErrorWriter() {
+		return new PrintWriter(System.err);
+	}
+
 	private void execute(final String[] argv) throws Exception {
-		final CmdLineParser clp = new CmdLineParser(this);
-		PrintWriter writer = new PrintWriter(System.err);
+		final CmdLineParser clp = new SubcommandLineParser(this);
+
 		try {
 			clp.parseArgument(argv);
 		} catch (CmdLineException err) {
 			if (argv.length > 0 && !help && !version) {
-				writer.println(MessageFormat.format(CLIText.get().fatalError, err.getMessage()));
+				writer.println(CLIText.fatalError(err.getMessage()));
 				writer.flush();
-				System.exit(1);
+				exit(1, err);
 			}
 		}
 
@@ -191,22 +215,24 @@
 				writer.println(CLIText.get().mostCommonlyUsedCommandsAre);
 				final CommandRef[] common = CommandCatalog.common();
 				int width = 0;
-				for (final CommandRef c : common)
+				for (final CommandRef c : common) {
 					width = Math.max(width, c.getName().length());
+				}
 				width += 2;
 
 				for (final CommandRef c : common) {
 					writer.print(' ');
 					writer.print(c.getName());
-					for (int i = c.getName().length(); i < width; i++)
+					for (int i = c.getName().length(); i < width; i++) {
 						writer.print(' ');
+					}
 					writer.print(CLIText.get().resourceBundle().getString(c.getUsage()));
 					writer.println();
 				}
 				writer.println();
 			}
 			writer.flush();
-			System.exit(1);
+			exit(1, null);
 		}
 
 		if (version) {
@@ -215,20 +241,38 @@
 		}
 
 		final TextBuiltin cmd = subcommand;
-		if (cmd.requiresRepository())
-			cmd.init(openGitDir(gitdir), null);
-		else
-			cmd.init(null, gitdir);
+		init(cmd);
 		try {
 			cmd.execute(arguments.toArray(new String[arguments.size()]));
 		} finally {
-			if (cmd.outw != null)
+			if (cmd.outw != null) {
 				cmd.outw.flush();
-			if (cmd.errw != null)
+			}
+			if (cmd.errw != null) {
 				cmd.errw.flush();
+			}
 		}
 	}
 
+	void init(final TextBuiltin cmd) throws IOException {
+		if (cmd.requiresRepository()) {
+			cmd.init(openGitDir(gitdir), null);
+		} else {
+			cmd.init(null, gitdir);
+		}
+	}
+
+	/**
+	 * @param status
+	 * @param t
+	 *            can be {@code null}
+	 * @throws Exception
+	 */
+	void exit(int status, Exception t) throws Exception {
+		writer.flush();
+		System.exit(status);
+	}
+
 	/**
 	 * Evaluate the {@code --git-dir} option and open the repository.
 	 *
@@ -278,7 +322,7 @@
 			throws IllegalAccessException, InvocationTargetException,
 			NoSuchMethodException, ClassNotFoundException {
 		try {
-		Class.forName(name).getMethod("install").invoke(null); //$NON-NLS-1$
+			Class.forName(name).getMethod("install").invoke(null); //$NON-NLS-1$
 		} catch (InvocationTargetException e) {
 			if (e.getCause() instanceof RuntimeException)
 				throw (RuntimeException) e.getCause();
@@ -332,4 +376,19 @@
 			}
 		}
 	}
+
+	/**
+	 * Parser for subcommands which doesn't stop parsing on help options and so
+	 * proceeds all specified options
+	 */
+	static class SubcommandLineParser extends CmdLineParser {
+		public SubcommandLineParser(Object bean) {
+			super(bean);
+		}
+
+		@Override
+		protected boolean containsHelp(String... args) {
+			return false;
+		}
+	}
 }
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Merge.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Merge.java
index e0ff058..e739b58 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Merge.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Merge.java
@@ -120,7 +120,7 @@
 			throw die(MessageFormat.format(
 					CLIText.get().refDoesNotExistOrNoCommit, ref));
 
-		Ref oldHead = db.getRef(Constants.HEAD);
+		Ref oldHead = getOldHead();
 		MergeResult result;
 		try (Git git = new Git(db)) {
 			MergeCommand mergeCmd = git.merge().setStrategy(mergeStrategy)
@@ -148,9 +148,12 @@
 			break;
 		case FAST_FORWARD:
 			ObjectId oldHeadId = oldHead.getObjectId();
-			outw.println(MessageFormat.format(CLIText.get().updating, oldHeadId
-					.abbreviate(7).name(), result.getNewHead().abbreviate(7)
-					.name()));
+			if (oldHeadId != null) {
+				String oldId = oldHeadId.abbreviate(7).name();
+				String newId = result.getNewHead().abbreviate(7).name();
+				outw.println(MessageFormat.format(CLIText.get().updating, oldId,
+						newId));
+			}
 			outw.println(result.getMergeStatus().toString());
 			break;
 		case CHECKOUT_CONFLICT:
@@ -205,6 +208,14 @@
 		}
 	}
 
+	private Ref getOldHead() throws IOException {
+		Ref oldHead = db.getRef(Constants.HEAD);
+		if (oldHead == null) {
+			throw die(CLIText.get().onBranchToBeBorn);
+		}
+		return oldHead;
+	}
+
 	private boolean isMergedInto(Ref oldHead, AnyObjectId src)
 			throws IOException {
 		try (RevWalk revWalk = new RevWalk(db)) {
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 1879ef5..33ea1de 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
@@ -82,6 +82,9 @@
 	@Option(name = "--all")
 	private boolean all;
 
+	@Option(name = "--atomic")
+	private boolean atomic;
+
 	@Option(name = "--tags")
 	private boolean tags;
 
@@ -122,6 +125,7 @@
 				push.setPushTags();
 			push.setRemote(remote);
 			push.setThin(thin);
+			push.setAtomic(atomic);
 			push.setTimeout(timeout);
 			Iterable<PushResult> results = push.call();
 			for (PushResult result : results) {
@@ -178,15 +182,15 @@
 		switch (rru.getStatus()) {
 		case OK:
 			if (rru.isDelete())
-				printUpdateLine('-', "[deleted]", null, remoteName, null);
+				printUpdateLine('-', "[deleted]", null, remoteName, null); //$NON-NLS-1$
 			else {
 				final Ref oldRef = result.getAdvertisedRef(remoteName);
 				if (oldRef == null) {
 					final String summary;
 					if (remoteName.startsWith(Constants.R_TAGS))
-						summary = "[new tag]";
+						summary = "[new tag]"; //$NON-NLS-1$
 					else
-						summary = "[new branch]";
+						summary = "[new branch]"; //$NON-NLS-1$
 					printUpdateLine('*', summary, srcRef, remoteName, null);
 				} else {
 					boolean fastForward = rru.isFastForward();
@@ -202,16 +206,16 @@
 			break;
 
 		case NON_EXISTING:
-			printUpdateLine('X', "[no match]", null, remoteName, null);
+			printUpdateLine('X', "[no match]", null, remoteName, null); //$NON-NLS-1$
 			break;
 
 		case REJECTED_NODELETE:
-			printUpdateLine('!', "[rejected]", null, remoteName,
+			printUpdateLine('!', "[rejected]", null, remoteName, //$NON-NLS-1$
 					CLIText.get().remoteSideDoesNotSupportDeletingRefs);
 			break;
 
 		case REJECTED_NONFASTFORWARD:
-			printUpdateLine('!', "[rejected]", srcRef, remoteName,
+			printUpdateLine('!', "[rejected]", srcRef, remoteName, //$NON-NLS-1$
 					CLIText.get().nonFastForward);
 			break;
 
@@ -219,22 +223,22 @@
 			final String message = MessageFormat.format(
 					CLIText.get().remoteRefObjectChangedIsNotExpectedOne,
 					safeAbbreviate(reader, rru.getExpectedOldObjectId()));
-			printUpdateLine('!', "[rejected]", srcRef, remoteName, message);
+			printUpdateLine('!', "[rejected]", srcRef, remoteName, message); //$NON-NLS-1$
 			break;
 
 		case REJECTED_OTHER_REASON:
-			printUpdateLine('!', "[remote rejected]", srcRef, remoteName, rru
+			printUpdateLine('!', "[remote rejected]", srcRef, remoteName, rru //$NON-NLS-1$
 					.getMessage());
 			break;
 
 		case UP_TO_DATE:
 			if (verbose)
-				printUpdateLine('=', "[up to date]", srcRef, remoteName, null);
+				printUpdateLine('=', "[up to date]", srcRef, remoteName, null); //$NON-NLS-1$
 			break;
 
 		case NOT_ATTEMPTED:
 		case AWAITING_REPORT:
-			printUpdateLine('?', "[unexpected push-process behavior]", srcRef,
+			printUpdateLine('?', "[unexpected push-process behavior]", srcRef, //$NON-NLS-1$
 					remoteName, rru.getMessage());
 			break;
 		}
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Remote.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Remote.java
new file mode 100644
index 0000000..24916bd
--- /dev/null
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Remote.java
@@ -0,0 +1,196 @@
+/*
+ * Copyright (C) 2015, Kaloyan Raev <kaloyan.r@zend.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.io.IOException;
+import java.io.StringWriter;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.api.RemoteAddCommand;
+import org.eclipse.jgit.api.RemoteListCommand;
+import org.eclipse.jgit.api.RemoteRemoveCommand;
+import org.eclipse.jgit.api.RemoteSetUrlCommand;
+import org.eclipse.jgit.api.errors.JGitInternalException;
+import org.eclipse.jgit.pgm.internal.CLIText;
+import org.eclipse.jgit.pgm.opt.CmdLineParser;
+import org.eclipse.jgit.transport.RemoteConfig;
+import org.eclipse.jgit.transport.URIish;
+import org.eclipse.jgit.util.io.ThrowingPrintWriter;
+import org.kohsuke.args4j.Argument;
+import org.kohsuke.args4j.Option;
+
+@Command(common = false, usage = "usage_Remote")
+class Remote extends TextBuiltin {
+
+	@Option(name = "--verbose", aliases = { "-v" }, usage = "usage_beVerbose")
+	private boolean verbose = false;
+
+	@Option(name = "--prune", aliases = {
+			"-p" }, usage = "usage_pruneStaleTrackingRefs")
+	private boolean prune;
+
+	@Option(name = "--push", usage = "usage_pushUrls")
+	private boolean push;
+
+	@Argument(index = 0, metaVar = "metaVar_command")
+	private String command;
+
+	@Argument(index = 1, metaVar = "metaVar_remoteName")
+	private String name;
+
+	@Argument(index = 2, metaVar = "metaVar_uriish")
+	private String uri;
+
+	@Override
+	protected void run() throws Exception {
+		try (Git git = new Git(db)) {
+			if (command == null) {
+				RemoteListCommand cmd = git.remoteList();
+				List<RemoteConfig> remotes = cmd.call();
+				print(remotes);
+			} else if ("add".equals(command)) { //$NON-NLS-1$
+				RemoteAddCommand cmd = git.remoteAdd();
+				cmd.setName(name);
+				cmd.setUri(new URIish(uri));
+				cmd.call();
+			} else if ("remove".equals(command) || "rm".equals(command)) { //$NON-NLS-1$ //$NON-NLS-2$
+				RemoteRemoveCommand cmd = git.remoteRemove();
+				cmd.setName(name);
+				cmd.call();
+			} else if ("set-url".equals(command)) { //$NON-NLS-1$
+				RemoteSetUrlCommand cmd = git.remoteSetUrl();
+				cmd.setName(name);
+				cmd.setUri(new URIish(uri));
+				cmd.setPush(push);
+				cmd.call();
+			} else if ("update".equals(command)) { //$NON-NLS-1$
+				// reuse fetch command for basic implementation of remote update
+				Fetch fetch = new Fetch();
+				fetch.init(db, gitdir);
+
+				// redirect the output stream
+				StringWriter osw = new StringWriter();
+				fetch.outw = new ThrowingPrintWriter(osw);
+				// redirect the error stream
+				StringWriter esw = new StringWriter();
+				fetch.errw = new ThrowingPrintWriter(esw);
+
+				List<String> fetchArgs = new ArrayList<>();
+				if (verbose) {
+					fetchArgs.add("--verbose"); //$NON-NLS-1$
+				}
+				if (prune) {
+					fetchArgs.add("--prune"); //$NON-NLS-1$
+				}
+				if (name != null) {
+					fetchArgs.add(name);
+				}
+
+				fetch.execute(fetchArgs.toArray(new String[fetchArgs.size()]));
+
+				// flush the streams
+				fetch.outw.flush();
+				fetch.errw.flush();
+				outw.println(osw.toString());
+				errw.println(esw.toString());
+			} else {
+				throw new JGitInternalException(MessageFormat
+						.format(CLIText.get().unknownSubcommand, command));
+			}
+		}
+	}
+
+	@Override
+	public void printUsage(final String message, final CmdLineParser clp)
+			throws IOException {
+		errw.println(message);
+		errw.println("jgit remote [--verbose (-v)] [--help (-h)]"); //$NON-NLS-1$
+		errw.println("jgit remote add name uri-ish [--help (-h)]"); //$NON-NLS-1$
+		errw.println("jgit remote remove name [--help (-h)]"); //$NON-NLS-1$
+		errw.println("jgit remote rm name [--help (-h)]"); //$NON-NLS-1$
+		errw.println(
+				"jgit remote [--verbose (-v)] update [name] [--prune (-p)] [--help (-h)]"); //$NON-NLS-1$
+		errw.println("jgit remote set-url name uri-ish [--push] [--help (-h)]"); //$NON-NLS-1$
+
+		errw.println();
+		clp.printUsage(errw, getResourceBundle());
+		errw.println();
+
+		errw.flush();
+	}
+
+	private void print(List<RemoteConfig> remotes) throws IOException {
+		for (RemoteConfig remote : remotes) {
+			String remoteName = remote.getName();
+			if (verbose) {
+				List<URIish> fetchURIs = remote.getURIs();
+				List<URIish> pushURIs = remote.getPushURIs();
+
+				String fetchURI = ""; //$NON-NLS-1$
+				if (!fetchURIs.isEmpty()) {
+					fetchURI = fetchURIs.get(0).toString();
+				} else if (!pushURIs.isEmpty()) {
+					fetchURI = pushURIs.get(0).toString();
+				}
+
+				String pushURI = ""; //$NON-NLS-1$
+				if (!pushURIs.isEmpty()) {
+					pushURI = pushURIs.get(0).toString();
+				} else if (!fetchURIs.isEmpty()) {
+					pushURI = fetchURIs.get(0).toString();
+				}
+
+				outw.println(
+						String.format("%s\t%s (fetch)", remoteName, fetchURI)); //$NON-NLS-1$
+				outw.println(
+						String.format("%s\t%s (push)", remoteName, pushURI)); //$NON-NLS-1$
+			} else {
+				outw.println(remoteName);
+			}
+		}
+	}
+
+}
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Repo.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Repo.java
index 9b191e6..ea59527 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Repo.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Repo.java
@@ -55,15 +55,19 @@
 	@Option(name = "--groups", aliases = { "-g" }, usage = "usage_groups")
 	private String groups = "default"; //$NON-NLS-1$
 
-	@Argument(required = true, usage = "usage_pathToXml")
+	@Argument(required = true, metaVar = "metaVar_path", usage = "usage_pathToXml")
 	private String path;
 
+	@Option(name = "--record-remote-branch", usage = "usage_branches")
+	private boolean branches;
+
 	@Override
 	protected void run() throws Exception {
 		new RepoCommand(db)
 			.setURI(uri)
 			.setPath(path)
 			.setGroups(groups)
+			.setRecordRemoteBranch(branches)
 			.call();
 	}
 }
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Reset.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Reset.java
index 6d1b1c5..9cee37b 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Reset.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Reset.java
@@ -43,11 +43,15 @@
 
 package org.eclipse.jgit.pgm;
 
+import java.util.ArrayList;
+import java.util.List;
+
 import org.eclipse.jgit.api.Git;
 import org.eclipse.jgit.api.ResetCommand;
 import org.eclipse.jgit.api.ResetCommand.ResetType;
 import org.kohsuke.args4j.Argument;
 import org.kohsuke.args4j.Option;
+import org.kohsuke.args4j.spi.RestOfArgumentsHandler;
 
 @Command(common = true, usage = "usage_reset")
 class Reset extends TextBuiltin {
@@ -61,31 +65,40 @@
 	@Option(name = "--hard", usage = "usage_resetHard")
 	private boolean hard = false;
 
-	@Argument(required = true, metaVar = "metaVar_name", usage = "usage_reset")
+	@Argument(required = false, index = 0, metaVar = "metaVar_commitish", usage = "usage_resetReference")
 	private String commit;
 
+	@Argument(required = false, index = 1, metaVar = "metaVar_paths")
+	@Option(name = "--", metaVar = "metaVar_paths", multiValued = true, handler = RestOfArgumentsHandler.class)
+	private List<String> paths = new ArrayList<>();
+
 	@Override
 	protected void run() throws Exception {
 		try (Git git = new Git(db)) {
 			ResetCommand command = git.reset();
 			command.setRef(commit);
-			ResetType mode = null;
-			if (soft)
-				mode = selectMode(mode, ResetType.SOFT);
-			if (mixed)
-				mode = selectMode(mode, ResetType.MIXED);
-			if (hard)
-				mode = selectMode(mode, ResetType.HARD);
-			if (mode == null)
-				throw die("no reset mode set");
-			command.setMode(mode);
+			if (paths.size() > 0) {
+				for (String path : paths)
+					command.addPath(path);
+			} else {
+				ResetType mode = null;
+				if (soft)
+					mode = selectMode(mode, ResetType.SOFT);
+				if (mixed)
+					mode = selectMode(mode, ResetType.MIXED);
+				if (hard)
+					mode = selectMode(mode, ResetType.HARD);
+				if (mode == null)
+					throw die("no reset mode set"); //$NON-NLS-1$
+				command.setMode(mode);
+			}
 			command.call();
 		}
 	}
 
 	private static ResetType selectMode(ResetType mode, ResetType want) {
 		if (mode != null)
-			throw die("reset modes are mutually exclusive, select one");
+			throw die("reset modes are mutually exclusive, select one"); //$NON-NLS-1$
 		return want;
 	}
 }
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/RevParse.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/RevParse.java
index 5530ac5..c5ecb84 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/RevParse.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/RevParse.java
@@ -1,6 +1,7 @@
 /*
  * Copyright (C) 2009, Daniel Cheng (aka SDiZ) <git@sdiz.net>
  * Copyright (C) 2009, Daniel Cheng (aka SDiZ) <j16sdiz+freenet@gmail.com>
+ * Copyright (C) 2015 Thomas Meyer <thomas@m3y3r.de>
  * and other copyright owners as documented in the project's IP log.
  *
  * This program and the accompanying materials are made available
@@ -51,14 +52,20 @@
 import java.util.Map;
 
 import org.kohsuke.args4j.Argument;
+import org.kohsuke.args4j.CmdLineException;
 import org.kohsuke.args4j.Option;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.pgm.internal.CLIText;
+import org.eclipse.jgit.pgm.opt.CmdLineParser;
 
 @Command(usage = "usage_RevParse")
 class RevParse extends TextBuiltin {
 	@Option(name = "--all", usage = "usage_RevParseAll")
-	boolean all = false;
+	boolean all;
+
+	@Option(name = "--verify", usage = "usage_RevParseVerify")
+	boolean verify;
 
 	@Argument(index = 0, metaVar = "metaVar_commitish")
 	private final List<ObjectId> commits = new ArrayList<ObjectId>();
@@ -67,11 +74,24 @@
 	protected void run() throws Exception {
 		if (all) {
 			Map<String, Ref> allRefs = db.getRefDatabase().getRefs(ALL);
-			for (final Ref r : allRefs.values())
-				outw.println(r.getObjectId().name());
+			for (final Ref r : allRefs.values()) {
+				ObjectId objectId = r.getObjectId();
+				// getRefs skips dangling symrefs, so objectId should never be
+				// null.
+				if (objectId == null) {
+					throw new NullPointerException();
+				}
+				outw.println(objectId.name());
+			}
 		} else {
-			for (final ObjectId o : commits)
+			if (verify && commits.size() > 1) {
+				final CmdLineParser clp = new CmdLineParser(this);
+				throw new CmdLineException(clp, CLIText.get().needSingleRevision);
+			}
+
+			for (final ObjectId o : commits) {
 				outw.println(o.name());
+			}
 		}
 	}
 }
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Status.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Status.java
index be82d07..6a63221 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Status.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Status.java
@@ -59,8 +59,9 @@
 import org.eclipse.jgit.lib.Ref;
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.pgm.internal.CLIText;
+import org.kohsuke.args4j.Argument;
 import org.kohsuke.args4j.Option;
-
+import org.kohsuke.args4j.spi.RestOfArgumentsHandler;
 import org.eclipse.jgit.pgm.opt.UntrackedFilesHandler;
 
 /**
@@ -83,7 +84,8 @@
 	@Option(name = "--untracked-files", aliases = { "-u", "-uno", "-uall" }, usage = "usage_untrackedFilesMode", handler = UntrackedFilesHandler.class)
 	protected String untrackedFilesMode = "all"; // default value //$NON-NLS-1$
 
-	@Option(name = "--", metaVar = "metaVar_path", multiValued = true)
+	@Argument(required = false, index = 0, metaVar = "metaVar_paths")
+	@Option(name = "--", metaVar = "metaVar_paths", multiValued = true, handler = RestOfArgumentsHandler.class)
 	protected List<String> filterPaths;
 
 	@Override
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/TextBuiltin.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/TextBuiltin.java
index 56cfc7e..0dc549c 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/TextBuiltin.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/TextBuiltin.java
@@ -212,17 +212,20 @@
 	 */
 	protected void parseArguments(final String[] args) throws IOException {
 		final CmdLineParser clp = new CmdLineParser(this);
+		help = containsHelp(args);
 		try {
 			clp.parseArgument(args);
 		} catch (CmdLineException err) {
-			if (!help) {
-				this.errw.println(MessageFormat.format(CLIText.get().fatalError, err.getMessage()));
-				throw die(true);
+			this.errw.println(CLIText.fatalError(err.getMessage()));
+			if (help) {
+				printUsage("", clp); //$NON-NLS-1$
 			}
+			throw die(true, err);
 		}
 
 		if (help) {
-			printUsageAndExit(clp);
+			printUsage("", clp); //$NON-NLS-1$
+			throw new TerminatedByHelpException();
 		}
 
 		argWalk = clp.getRevWalkGently();
@@ -246,6 +249,20 @@
 	 * @throws IOException
 	 */
 	public void printUsageAndExit(final String message, final CmdLineParser clp) throws IOException {
+		printUsage(message, clp);
+		throw die(true);
+	}
+
+	/**
+	 * @param message
+	 *            non null
+	 * @param clp
+	 *            parser used to print options
+	 * @throws IOException
+	 * @since 4.2
+	 */
+	protected void printUsage(final String message, final CmdLineParser clp)
+			throws IOException {
 		errw.println(message);
 		errw.print("jgit "); //$NON-NLS-1$
 		errw.print(commandName);
@@ -257,12 +274,19 @@
 		errw.println();
 
 		errw.flush();
-		throw die(true);
 	}
 
 	/**
-	 * @return the resource bundle that will be passed to args4j for purpose
-	 *         of string localization
+	 * @return error writer, typically this is standard error.
+	 * @since 4.2
+	 */
+	public ThrowingPrintWriter getErrorWriter() {
+		return errw;
+	}
+
+	/**
+	 * @return the resource bundle that will be passed to args4j for purpose of
+	 *         string localization
 	 */
 	protected ResourceBundle getResourceBundle() {
 		return CLIText.get().resourceBundle();
@@ -324,6 +348,19 @@
 		return new Die(aborted);
 	}
 
+	/**
+	 * @param aborted
+	 *            boolean indicating that the execution has been aborted before
+	 *            running
+	 * @param cause
+	 *            why the command has failed.
+	 * @return a runtime exception the caller is expected to throw
+	 * @since 4.2
+	 */
+	protected static Die die(boolean aborted, final Throwable cause) {
+		return new Die(aborted, cause);
+	}
+
 	String abbreviateRef(String dst, boolean abbreviateRemote) {
 		if (dst.startsWith(R_HEADS))
 			dst = dst.substring(R_HEADS.length());
@@ -333,4 +370,36 @@
 			dst = dst.substring(R_REMOTES.length());
 		return dst;
 	}
+
+	/**
+	 * @param args
+	 *            non null
+	 * @return true if the given array contains help option
+	 * @since 4.2
+	 */
+	public static boolean containsHelp(String[] args) {
+		for (String str : args) {
+			if (str.equals("-h") || str.equals("--help")) { //$NON-NLS-1$ //$NON-NLS-2$
+				return true;
+			}
+		}
+		return false;
+	}
+
+	/**
+	 * Exception thrown by {@link TextBuiltin} if it proceeds 'help' option
+	 *
+	 * @since 4.2
+	 */
+	public static class TerminatedByHelpException extends Die {
+		private static final long serialVersionUID = 1L;
+
+		/**
+		 * Default constructor
+		 */
+		public TerminatedByHelpException() {
+			super(true);
+		}
+
+	}
 }
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/DiffAlgorithms.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/DiffAlgorithms.java
index df7ebb7..05d094f 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/DiffAlgorithms.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/DiffAlgorithms.java
@@ -136,7 +136,7 @@
 	protected void run() throws Exception {
 		mxBean = ManagementFactory.getThreadMXBean();
 		if (!mxBean.isCurrentThreadCpuTimeSupported())
-			throw die("Current thread CPU time not supported on this JRE");
+			throw die("Current thread CPU time not supported on this JRE"); //$NON-NLS-1$
 
 		if (gitDirs.isEmpty()) {
 			RepositoryBuilder rb = new RepositoryBuilder() //
@@ -155,16 +155,16 @@
 			else
 				rb.findGitDir(dir);
 
-			Repository db = rb.build();
+			Repository repo = rb.build();
 			try {
-				run(db);
+				run(repo);
 			} finally {
-				db.close();
+				repo.close();
 			}
 		}
 	}
 
-	private void run(Repository db) throws Exception {
+	private void run(Repository repo) throws Exception {
 		List<Test> all = init();
 
 		long files = 0;
@@ -173,14 +173,14 @@
 		int maxN = 0;
 
 		AbbreviatedObjectId startId;
-		try (ObjectReader or = db.newObjectReader();
+		try (ObjectReader or = repo.newObjectReader();
 			RevWalk rw = new RevWalk(or)) {
 			final MutableObjectId id = new MutableObjectId();
 			TreeWalk tw = new TreeWalk(or);
 			tw.setFilter(TreeFilter.ANY_DIFF);
 			tw.setRecursive(true);
 
-			ObjectId start = db.resolve(Constants.HEAD);
+			ObjectId start = repo.resolve(Constants.HEAD);
 			startId = or.abbreviate(start);
 			rw.markStart(rw.parseCommit(start));
 			for (;;) {
@@ -235,30 +235,32 @@
 
 		Collections.sort(all, new Comparator<Test>() {
 			public int compare(Test a, Test b) {
-				int cmp = Long.signum(a.runningTimeNanos - b.runningTimeNanos);
-				if (cmp == 0)
-					cmp = a.algorithm.name.compareTo(b.algorithm.name);
-				return cmp;
+				int result = Long.signum(a.runningTimeNanos - b.runningTimeNanos);
+				if (result == 0) {
+					result = a.algorithm.name.compareTo(b.algorithm.name);
+				}
+				return result;
 			}
 		});
 
-		if (db.getDirectory() != null) {
-			String name = db.getDirectory().getName();
-			File parent = db.getDirectory().getParentFile();
+		File directory = repo.getDirectory();
+		if (directory != null) {
+			String name = directory.getName();
+			File parent = directory.getParentFile();
 			if (name.equals(Constants.DOT_GIT) && parent != null)
 				name = parent.getName();
-			outw.println(name + ": start at " + startId.name());
+			outw.println(name + ": start at " + startId.name()); //$NON-NLS-1$
 		}
 
-		outw.format("  %12d files,     %8d commits\n", valueOf(files),
+		outw.format("  %12d files,     %8d commits\n", valueOf(files), //$NON-NLS-1$
 				valueOf(commits));
-		outw.format("  N=%10d min lines, %8d max lines\n", valueOf(minN),
+		outw.format("  N=%10d min lines, %8d max lines\n", valueOf(minN), //$NON-NLS-1$
 				valueOf(maxN));
 
-		outw.format("%-25s %12s ( %12s  %12s )\n", //
-				"Algorithm", "Time(ns)", "Time(ns) on", "Time(ns) on");
-		outw.format("%-25s %12s ( %12s  %12s )\n", //
-				"", "", "N=" + minN, "N=" + maxN);
+		outw.format("%-25s %12s ( %12s  %12s )\n", //$NON-NLS-1$
+				"Algorithm", "Time(ns)", "Time(ns) on", "Time(ns) on"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
+		outw.format("%-25s %12s ( %12s  %12s )\n", //$NON-NLS-1$
+				"", "", "N=" + minN, "N=" + maxN); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
 		outw.println("-----------------------------------------------------" //$NON-NLS-1$
 				+ "----------------"); //$NON-NLS-1$
 
@@ -334,9 +336,9 @@
 				}
 			}
 		} catch (IllegalArgumentException e) {
-			throw die("Cannot determine names", e);
+			throw die("Cannot determine names", e); //$NON-NLS-1$
 		} catch (IllegalAccessException e) {
-			throw die("Cannot determine names", e);
+			throw die("Cannot determine names", e); //$NON-NLS-1$
 		}
 
 		return all;
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/RebuildCommitGraph.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/RebuildCommitGraph.java
index 494055a..8cfcba9 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/RebuildCommitGraph.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/RebuildCommitGraph.java
@@ -117,9 +117,12 @@
 	@Override
 	protected void run() throws Exception {
 		if (!really && !db.getRefDatabase().getRefs(ALL).isEmpty()) {
+			File directory = db.getDirectory();
+			String absolutePath = directory == null ? "null" //$NON-NLS-1$
+					: directory.getAbsolutePath();
 			errw.println(
 				MessageFormat.format(CLIText.get().fatalThisProgramWillDestroyTheRepository
-					, db.getDirectory().getAbsolutePath(), REALLY));
+					, absolutePath, REALLY));
 			throw die(CLIText.get().needApprovalToDestroyCurrentRepository);
 		}
 		if (!refList.isFile())
@@ -164,7 +167,7 @@
 			}
 		}
 
-		pm.beginTask("Rewriting commits", queue.size());
+		pm.beginTask("Rewriting commits", queue.size()); //$NON-NLS-1$
 		try (ObjectInserter oi = db.newObjectInserter()) {
 			final ObjectId emptyTree = oi.insert(Constants.OBJ_TREE,
 					new byte[] {});
@@ -200,7 +203,7 @@
 					newc.setAuthor(new PersonIdent(me, new Date(t.commitTime)));
 					newc.setCommitter(newc.getAuthor());
 					newc.setParentIds(newParents);
-					newc.setMessage("ORIGINAL " + t.oldId.name() + "\n"); //$NON-NLS-2$
+					newc.setMessage("ORIGINAL " + t.oldId.name() + "\n"); //$NON-NLS-1$ //$NON-NLS-2$
 					t.newId = oi.insert(newc);
 					rewrites.put(t.oldId, t.newId);
 					pm.update(1);
@@ -232,7 +235,7 @@
 		final ObjectId id = db.resolve(Constants.HEAD);
 		if (!ObjectId.isId(head) && id != null) {
 			final LockFile lf;
-			lf = new LockFile(new File(db.getDirectory(), Constants.HEAD), db.getFS());
+			lf = new LockFile(new File(db.getDirectory(), Constants.HEAD));
 			if (!lf.lock())
 				throw new IOException(MessageFormat.format(CLIText.get().cannotLock, Constants.HEAD));
 			lf.write(id);
@@ -260,7 +263,7 @@
 			protected void writeFile(final String name, final byte[] content)
 					throws IOException {
 				final File file = new File(db.getDirectory(), name);
-				final LockFile lck = new LockFile(file, db.getFS());
+				final LockFile lck = new LockFile(file);
 				if (!lck.lock())
 					throw new ObjectWritingException(MessageFormat.format(CLIText.get().cantWrite, file));
 				try {
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/RebuildRefTree.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/RebuildRefTree.java
new file mode 100644
index 0000000..78ca1a7
--- /dev/null
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/RebuildRefTree.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2015, 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.pgm.debug;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.jgit.internal.storage.reftree.RefTree;
+import org.eclipse.jgit.internal.storage.reftree.RefTreeDatabase;
+import org.eclipse.jgit.lib.CommitBuilder;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectInserter;
+import org.eclipse.jgit.lib.ObjectReader;
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.RefDatabase;
+import org.eclipse.jgit.lib.RefUpdate;
+import org.eclipse.jgit.pgm.Command;
+import org.eclipse.jgit.pgm.TextBuiltin;
+import org.eclipse.jgit.revwalk.RevWalk;
+
+@Command(usage = "usage_RebuildRefTree")
+class RebuildRefTree extends TextBuiltin {
+	private String txnNamespace;
+	private String txnCommitted;
+
+	@Override
+	protected void run() throws Exception {
+		try (ObjectReader reader = db.newObjectReader();
+				RevWalk rw = new RevWalk(reader);
+				ObjectInserter inserter = db.newObjectInserter()) {
+			RefDatabase refDb = db.getRefDatabase();
+			if (refDb instanceof RefTreeDatabase) {
+				RefTreeDatabase d = (RefTreeDatabase) refDb;
+				refDb = d.getBootstrap();
+				txnNamespace = d.getTxnNamespace();
+				txnCommitted = d.getTxnCommitted();
+			} else {
+				RefTreeDatabase d = new RefTreeDatabase(db, refDb);
+				txnNamespace = d.getTxnNamespace();
+				txnCommitted = d.getTxnCommitted();
+			}
+
+			errw.format("Rebuilding %s from %s", //$NON-NLS-1$
+					txnCommitted, refDb.getClass().getSimpleName());
+			errw.println();
+			errw.flush();
+
+			CommitBuilder b = new CommitBuilder();
+			Ref ref = refDb.exactRef(txnCommitted);
+			RefUpdate update = refDb.newUpdate(txnCommitted, true);
+			ObjectId oldTreeId;
+
+			if (ref != null && ref.getObjectId() != null) {
+				ObjectId oldId = ref.getObjectId();
+				update.setExpectedOldObjectId(oldId);
+				b.setParentId(oldId);
+				oldTreeId = rw.parseCommit(oldId).getTree();
+			} else {
+				update.setExpectedOldObjectId(ObjectId.zeroId());
+				oldTreeId = ObjectId.zeroId();
+			}
+
+			RefTree tree = rebuild(refDb.getRefs(RefDatabase.ALL));
+			b.setTreeId(tree.writeTree(inserter));
+			b.setAuthor(new PersonIdent(db));
+			b.setCommitter(b.getAuthor());
+			if (b.getTreeId().equals(oldTreeId)) {
+				return;
+			}
+
+			update.setNewObjectId(inserter.insert(b));
+			inserter.flush();
+
+			RefUpdate.Result result = update.update(rw);
+			switch (result) {
+			case NEW:
+			case FAST_FORWARD:
+				break;
+			default:
+				throw die(String.format("%s: %s", update.getName(), result)); //$NON-NLS-1$
+			}
+		}
+	}
+
+	private RefTree rebuild(Map<String, Ref> refMap) {
+		RefTree tree = RefTree.newEmptyTree();
+		List<org.eclipse.jgit.internal.storage.reftree.Command> cmds
+			= new ArrayList<>();
+
+		for (Ref r : refMap.values()) {
+			if (r.getName().equals(txnCommitted)
+					|| r.getName().startsWith(txnNamespace)) {
+				continue;
+			}
+			cmds.add(new org.eclipse.jgit.internal.storage.reftree.Command(
+					null,
+					db.peel(r)));
+		}
+		tree.apply(cmds);
+		return tree;
+	}
+}
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/ShowPackDelta.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/ShowPackDelta.java
index d3eb245..150fe6e 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/ShowPackDelta.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/ShowPackDelta.java
@@ -87,9 +87,9 @@
 		long size = reader.getObjectSize(obj, obj.getType());
 		try {
 			if (BinaryDelta.getResultSize(delta) != size)
-				throw die("Object " + obj.name() + " is not a delta");
+				throw die("Object " + obj.name() + " is not a delta"); //$NON-NLS-1$ //$NON-NLS-2$
 		} catch (ArrayIndexOutOfBoundsException bad) {
-			throw die("Object " + obj.name() + " is not a delta");
+			throw die("Object " + obj.name() + " is not a delta"); //$NON-NLS-1$ //$NON-NLS-2$
 		}
 
 		outw.println(BinaryDelta.format(delta));
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/TextHashFunctions.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/TextHashFunctions.java
index dcbc37b..28d92ae 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/TextHashFunctions.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/TextHashFunctions.java
@@ -286,25 +286,25 @@
 			else
 				rb.findGitDir(dir);
 
-			Repository db = rb.build();
+			Repository repo = rb.build();
 			try {
-				run(db);
+				run(repo);
 			} finally {
-				db.close();
+				repo.close();
 			}
 		}
 	}
 
-	private void run(Repository db) throws Exception {
+	private void run(Repository repo) throws Exception {
 		List<Function> all = init();
 
 		long fileCnt = 0;
 		long lineCnt = 0;
-		try (ObjectReader or = db.newObjectReader();
+		try (ObjectReader or = repo.newObjectReader();
 			RevWalk rw = new RevWalk(or);
 			TreeWalk tw = new TreeWalk(or)) {
 			final MutableObjectId id = new MutableObjectId();
-			tw.reset(rw.parseTree(db.resolve(Constants.HEAD)));
+			tw.reset(rw.parseTree(repo.resolve(Constants.HEAD)));
 			tw.setRecursive(true);
 
 			while (tw.next()) {
@@ -341,17 +341,18 @@
 			}
 		}
 
-		if (db.getDirectory() != null) {
-			String name = db.getDirectory().getName();
-			File parent = db.getDirectory().getParentFile();
+		File directory = repo.getDirectory();
+		if (directory != null) {
+			String name = directory.getName();
+			File parent = directory.getParentFile();
 			if (name.equals(Constants.DOT_GIT) && parent != null)
 				name = parent.getName();
 			outw.println(name + ":"); //$NON-NLS-1$
 		}
-		outw.format("  %6d files; %5d avg. unique lines/file\n", //
+		outw.format("  %6d files; %5d avg. unique lines/file\n", //$NON-NLS-1$
 				valueOf(fileCnt), //
 				valueOf(lineCnt / fileCnt));
-		outw.format("%-20s %-15s %9s\n", "Hash", "Fold", "Max Len");
+		outw.format("%-20s %-15s %9s\n", "Hash", "Fold", "Max Len"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
 		outw.println("-----------------------------------------------"); //$NON-NLS-1$
 		String lastHashName = null;
 		for (Function fun : all) {
@@ -404,9 +405,9 @@
 				}
 			}
 		} catch (IllegalArgumentException e) {
-			throw new RuntimeException("Cannot determine names", e);
+			throw new RuntimeException("Cannot determine names", e); //$NON-NLS-1$
 		} catch (IllegalAccessException e) {
-			throw new RuntimeException("Cannot determine names", e);
+			throw new RuntimeException("Cannot determine names", e); //$NON-NLS-1$
 		}
 
 		List<Function> all = new ArrayList<Function>();
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 433ddf2..2812137 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
@@ -74,6 +74,19 @@
 		return MessageFormat.format(get().lineFormat, line);
 	}
 
+	/**
+	 * Format the given argument as fatal error using the format defined by
+	 * {@link #fatalError} ("fatal: " by default).
+	 *
+	 * @param message
+	 *            the message to format
+	 * @return the formatted line
+	 * @since 4.2
+	 */
+	public static String fatalError(String message) {
+		return MessageFormat.format(get().fatalError, message);
+	}
+
 	// @formatter:off
 	/***/ public String alreadyOnBranch;
 	/***/ public String alreadyUpToDate;
@@ -85,6 +98,7 @@
 	/***/ public String branchCreatedFrom;
 	/***/ public String branchDetachedHEAD;
 	/***/ public String branchIsNotAnAncestorOfYourCurrentHEAD;
+	/***/ public String branchNameRequired;
 	/***/ public String branchNotFound;
 	/***/ public String cacheTreePathInfo;
 	/***/ public String configFileNotFound;
@@ -184,9 +198,11 @@
 	/***/ public String metaVar_uriish;
 	/***/ public String metaVar_url;
 	/***/ public String metaVar_user;
+	/***/ public String metaVar_values;
 	/***/ public String metaVar_version;
 	/***/ public String mostCommonlyUsedCommandsAre;
 	/***/ public String needApprovalToDestroyCurrentRepository;
+	/***/ public String needSingleRevision;
 	/***/ public String noGitRepositoryConfigured;
 	/***/ public String noNamesFound;
 	/***/ public String noSuchFile;
@@ -201,6 +217,7 @@
 	/***/ public String notARevision;
 	/***/ public String notATree;
 	/***/ public String notAValidRefName;
+	/***/ public String notAValidCommitName;
 	/***/ public String notAnIndexFile;
 	/***/ public String notAnObject;
 	/***/ public String notFound;
@@ -245,6 +262,7 @@
 	/***/ public String treeIsRequired;
 	/***/ public char[] unknownIoErrorStdout;
 	/***/ public String unknownMergeStrategy;
+	/***/ public String unknownSubcommand;
 	/***/ public String unmergedPaths;
 	/***/ public String unsupportedOperation;
 	/***/ public String untrackedFiles;
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/AbstractTreeIteratorHandler.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/AbstractTreeIteratorHandler.java
index 229fb67..6b8a61d 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/AbstractTreeIteratorHandler.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/AbstractTreeIteratorHandler.java
@@ -109,7 +109,7 @@
 			try {
 				dirc = DirCache.read(new File(name), FS.DETECTED);
 			} catch (IOException e) {
-				throw new CmdLineException(MessageFormat.format(CLIText.get().notAnIndexFile, name), e);
+				throw new CmdLineException(clp, MessageFormat.format(CLIText.get().notAnIndexFile, name), e);
 			}
 			setter.addValue(new DirCacheIterator(dirc));
 			return 1;
@@ -119,20 +119,20 @@
 		try {
 			id = clp.getRepository().resolve(name);
 		} catch (IOException e) {
-			throw new CmdLineException(e.getMessage());
+			throw new CmdLineException(clp, e.getMessage());
 		}
 		if (id == null)
-			throw new CmdLineException(MessageFormat.format(CLIText.get().notATree, name));
+			throw new CmdLineException(clp, MessageFormat.format(CLIText.get().notATree, name));
 
 		final CanonicalTreeParser p = new CanonicalTreeParser();
 		try (ObjectReader curs = clp.getRepository().newObjectReader()) {
 			p.reset(curs, clp.getRevWalk().parseTree(id));
 		} catch (MissingObjectException e) {
-			throw new CmdLineException(MessageFormat.format(CLIText.get().notATree, name));
+			throw new CmdLineException(clp, MessageFormat.format(CLIText.get().notATree, name));
 		} catch (IncorrectObjectTypeException e) {
-			throw new CmdLineException(MessageFormat.format(CLIText.get().notATree, name));
+			throw new CmdLineException(clp, MessageFormat.format(CLIText.get().notATree, name));
 		} catch (IOException e) {
-			throw new CmdLineException(MessageFormat.format(CLIText.get().cannotReadBecause, name, e.getMessage()));
+			throw new CmdLineException(clp, MessageFormat.format(CLIText.get().cannotReadBecause, name, e.getMessage()));
 		}
 
 		setter.addValue(p);
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/CmdLineParser.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/CmdLineParser.java
index 3f77aa6..b531ba6 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/CmdLineParser.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/CmdLineParser.java
@@ -43,19 +43,18 @@
 
 package org.eclipse.jgit.pgm.opt;
 
+import java.io.IOException;
+import java.io.Writer;
 import java.lang.reflect.Field;
 import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.ResourceBundle;
 
-import org.kohsuke.args4j.Argument;
-import org.kohsuke.args4j.CmdLineException;
-import org.kohsuke.args4j.IllegalAnnotationError;
-import org.kohsuke.args4j.NamedOptionDef;
-import org.kohsuke.args4j.Option;
-import org.kohsuke.args4j.OptionDef;
-import org.kohsuke.args4j.spi.OptionHandler;
-import org.kohsuke.args4j.spi.Setter;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.pgm.Die;
 import org.eclipse.jgit.pgm.TextBuiltin;
 import org.eclipse.jgit.pgm.internal.CLIText;
 import org.eclipse.jgit.revwalk.RevCommit;
@@ -63,6 +62,15 @@
 import org.eclipse.jgit.revwalk.RevWalk;
 import org.eclipse.jgit.transport.RefSpec;
 import org.eclipse.jgit.treewalk.AbstractTreeIterator;
+import org.kohsuke.args4j.Argument;
+import org.kohsuke.args4j.CmdLineException;
+import org.kohsuke.args4j.IllegalAnnotationError;
+import org.kohsuke.args4j.NamedOptionDef;
+import org.kohsuke.args4j.Option;
+import org.kohsuke.args4j.OptionDef;
+import org.kohsuke.args4j.spi.OptionHandler;
+import org.kohsuke.args4j.spi.RestOfArgumentsHandler;
+import org.kohsuke.args4j.spi.Setter;
 
 /**
  * Extended command line parser which handles --foo=value arguments.
@@ -80,12 +88,17 @@
 		registerHandler(RefSpec.class, RefSpecHandler.class);
 		registerHandler(RevCommit.class, RevCommitHandler.class);
 		registerHandler(RevTree.class, RevTreeHandler.class);
+		registerHandler(List.class, OptionWithValuesListHandler.class);
 	}
 
 	private final Repository db;
 
 	private RevWalk walk;
 
+	private boolean seenHelp;
+
+	private TextBuiltin cmd;
+
 	/**
 	 * Creates a new command line owner that parses arguments/options and set
 	 * them into the given object.
@@ -117,8 +130,12 @@
 	 */
 	public CmdLineParser(final Object bean, Repository repo) {
 		super(bean);
-		if (repo == null && bean instanceof TextBuiltin)
-			repo = ((TextBuiltin) bean).getRepository();
+		if (bean instanceof TextBuiltin) {
+			cmd = (TextBuiltin) bean;
+		}
+		if (repo == null && cmd != null) {
+			repo = cmd.getRepository();
+		}
 		this.db = repo;
 	}
 
@@ -143,9 +160,75 @@
 			}
 
 			tmp.add(str);
+
+			if (containsHelp(args)) {
+				// suppress exceptions on required parameters if help is present
+				seenHelp = true;
+				// stop argument parsing here
+				break;
+			}
+		}
+		List<OptionHandler> backup = null;
+		if (seenHelp) {
+			backup = unsetRequiredOptions();
 		}
 
-		super.parseArgument(tmp.toArray(new String[tmp.size()]));
+		try {
+			super.parseArgument(tmp.toArray(new String[tmp.size()]));
+		} catch (Die e) {
+			if (!seenHelp) {
+				throw e;
+			}
+			printToErrorWriter(CLIText.fatalError(e.getMessage()));
+		} finally {
+			// reset "required" options to defaults for correct command printout
+			if (backup != null && !backup.isEmpty()) {
+				restoreRequiredOptions(backup);
+			}
+			seenHelp = false;
+		}
+	}
+
+	private void printToErrorWriter(String error) {
+		if (cmd == null) {
+			System.err.println(error);
+		} else {
+			try {
+				cmd.getErrorWriter().println(error);
+			} catch (IOException e1) {
+				System.err.println(error);
+			}
+		}
+	}
+
+	private List<OptionHandler> unsetRequiredOptions() {
+		List<OptionHandler> options = getOptions();
+		List<OptionHandler> backup = new ArrayList<>(options);
+		for (Iterator<OptionHandler> iterator = options.iterator(); iterator
+				.hasNext();) {
+			OptionHandler handler = iterator.next();
+			if (handler.option instanceof NamedOptionDef
+					&& handler.option.required()) {
+				iterator.remove();
+			}
+		}
+		return backup;
+	}
+
+	private void restoreRequiredOptions(List<OptionHandler> backup) {
+		List<OptionHandler> options = getOptions();
+		options.clear();
+		options.addAll(backup);
+	}
+
+	/**
+	 * @param args
+	 *            non null
+	 * @return true if the given array contains help option
+	 * @since 4.2
+	 */
+	protected boolean containsHelp(final String... args) {
+		return TextBuiltin.containsHelp(args);
 	}
 
 	/**
@@ -181,7 +264,7 @@
 		return walk;
 	}
 
-	static class MyOptionDef extends OptionDef {
+	class MyOptionDef extends OptionDef {
 
 		public MyOptionDef(OptionDef o) {
 			super(o.usage(), o.metaVar(), o.required(), o.handler(), o
@@ -201,6 +284,11 @@
 				return metaVar();
 			}
 		}
+
+		@Override
+		public boolean required() {
+			return seenHelp ? false : super.required();
+		}
 	}
 
 	@Override
@@ -211,4 +299,55 @@
 			return super.createOptionHandler(new MyOptionDef(o), setter);
 
 	}
+
+	@SuppressWarnings("unchecked")
+	private List<OptionHandler> getOptions() {
+		List<OptionHandler> options = null;
+		try {
+			Field field = org.kohsuke.args4j.CmdLineParser.class
+					.getDeclaredField("options"); //$NON-NLS-1$
+			field.setAccessible(true);
+			options = (List<OptionHandler>) field.get(this);
+		} catch (NoSuchFieldException | SecurityException
+				| IllegalArgumentException | IllegalAccessException e) {
+			// ignore
+		}
+		if (options == null) {
+			return Collections.emptyList();
+		}
+		return options;
+	}
+
+	@Override
+	public void printSingleLineUsage(Writer w, ResourceBundle rb) {
+		List<OptionHandler> options = getOptions();
+		if (options.isEmpty()) {
+			super.printSingleLineUsage(w, rb);
+			return;
+		}
+		List<OptionHandler> backup = new ArrayList<>(options);
+		boolean changed = sortRestOfArgumentsHandlerToTheEnd(options);
+		try {
+			super.printSingleLineUsage(w, rb);
+		} finally {
+			if (changed) {
+				options.clear();
+				options.addAll(backup);
+			}
+		}
+	}
+
+	private boolean sortRestOfArgumentsHandlerToTheEnd(
+			List<OptionHandler> options) {
+		for (int i = 0; i < options.size(); i++) {
+			OptionHandler handler = options.get(i);
+			if (handler instanceof RestOfArgumentsHandler
+					|| handler instanceof PathTreeFilterHandler) {
+				options.remove(i);
+				options.add(handler);
+				return true;
+			}
+		}
+		return false;
+	}
 }
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/ObjectIdHandler.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/ObjectIdHandler.java
index fa24d4b0..364809d 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/ObjectIdHandler.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/ObjectIdHandler.java
@@ -86,14 +86,14 @@
 		try {
 			id = clp.getRepository().resolve(name);
 		} catch (IOException e) {
-			throw new CmdLineException(e.getMessage());
+			throw new CmdLineException(clp, e.getMessage());
 		}
 		if (id != null) {
 			setter.addValue(id);
 			return 1;
 		}
 
-		throw new CmdLineException(MessageFormat.format(CLIText.get().notAnObject, name));
+		throw new CmdLineException(clp, MessageFormat.format(CLIText.get().notAnObject, name));
 	}
 
 	@Override
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/OptionWithValuesListHandler.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/OptionWithValuesListHandler.java
new file mode 100644
index 0000000..3de7a81
--- /dev/null
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/OptionWithValuesListHandler.java
@@ -0,0 +1,52 @@
+package org.eclipse.jgit.pgm.opt;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.jgit.pgm.internal.CLIText;
+import org.kohsuke.args4j.CmdLineException;
+import org.kohsuke.args4j.CmdLineParser;
+import org.kohsuke.args4j.OptionDef;
+import org.kohsuke.args4j.spi.OptionHandler;
+import org.kohsuke.args4j.spi.Parameters;
+import org.kohsuke.args4j.spi.Setter;
+
+/**
+ * Handler which allows to parse option with few values
+ *
+ * @since 4.2
+ */
+public class OptionWithValuesListHandler extends OptionHandler<List<?>> {
+
+	/**
+	 * @param parser
+	 * @param option
+	 * @param setter
+	 */
+	public OptionWithValuesListHandler(CmdLineParser parser,
+			OptionDef option, Setter<List<?>> setter) {
+		super(parser, option, setter);
+	}
+
+	@Override
+	public int parseArguments(Parameters params) throws CmdLineException {
+		final List<String> list = new ArrayList<>();
+		for (int idx = 0; idx < params.size(); idx++) {
+			final String p;
+			try {
+				p = params.getParameter(idx);
+			} catch (CmdLineException cle) {
+				break;
+			}
+			list.add(p);
+		}
+		setter.addValue(list);
+		return list.size();
+	}
+
+	@Override
+	public String getDefaultMetaVariable() {
+		return CLIText.get().metaVar_values;
+	}
+
+}
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/RevCommitHandler.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/RevCommitHandler.java
index b1be128..9ae56e4 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/RevCommitHandler.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/RevCommitHandler.java
@@ -96,8 +96,10 @@
 		final int dot2 = name.indexOf(".."); //$NON-NLS-1$
 		if (dot2 != -1) {
 			if (!option.isMultiValued())
-				throw new CmdLineException(MessageFormat.format(CLIText.get().onlyOneMetaVarExpectedIn
-					, option.metaVar(), name));
+				throw new CmdLineException(clp,
+						MessageFormat.format(
+								CLIText.get().onlyOneMetaVarExpectedIn,
+								option.metaVar(), name));
 
 			final String left = name.substring(0, dot2);
 			final String right = name.substring(dot2 + 2);
@@ -116,20 +118,20 @@
 		try {
 			id = clp.getRepository().resolve(name);
 		} catch (IOException e) {
-			throw new CmdLineException(e.getMessage());
+			throw new CmdLineException(clp, e.getMessage());
 		}
 		if (id == null)
-			throw new CmdLineException(MessageFormat.format(CLIText.get().notACommit, name));
+			throw new CmdLineException(clp, MessageFormat.format(CLIText.get().notACommit, name));
 
 		final RevCommit c;
 		try {
 			c = clp.getRevWalk().parseCommit(id);
 		} catch (MissingObjectException e) {
-			throw new CmdLineException(MessageFormat.format(CLIText.get().notACommit, name));
+			throw new CmdLineException(clp, MessageFormat.format(CLIText.get().notACommit, name));
 		} catch (IncorrectObjectTypeException e) {
-			throw new CmdLineException(MessageFormat.format(CLIText.get().notACommit, name));
+			throw new CmdLineException(clp, MessageFormat.format(CLIText.get().notACommit, name));
 		} catch (IOException e) {
-			throw new CmdLineException(MessageFormat.format(CLIText.get().cannotReadBecause, name, e.getMessage()));
+			throw new CmdLineException(clp, MessageFormat.format(CLIText.get().cannotReadBecause, name, e.getMessage()));
 		}
 
 		if (interesting)
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/RevTreeHandler.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/RevTreeHandler.java
index eb155af..e2879e0 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/RevTreeHandler.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/RevTreeHandler.java
@@ -89,20 +89,20 @@
 		try {
 			id = clp.getRepository().resolve(name);
 		} catch (IOException e) {
-			throw new CmdLineException(e.getMessage());
+			throw new CmdLineException(clp, e.getMessage());
 		}
 		if (id == null)
-			throw new CmdLineException(MessageFormat.format(CLIText.get().notATree, name));
+			throw new CmdLineException(clp, MessageFormat.format(CLIText.get().notATree, name));
 
 		final RevTree c;
 		try {
 			c = clp.getRevWalk().parseTree(id);
 		} catch (MissingObjectException e) {
-			throw new CmdLineException(MessageFormat.format(CLIText.get().notATree, name));
+			throw new CmdLineException(clp, MessageFormat.format(CLIText.get().notATree, name));
 		} catch (IncorrectObjectTypeException e) {
-			throw new CmdLineException(MessageFormat.format(CLIText.get().notATree, name));
+			throw new CmdLineException(clp, MessageFormat.format(CLIText.get().notATree, name));
 		} catch (IOException e) {
-			throw new CmdLineException(MessageFormat.format(CLIText.get().cannotReadBecause, name, e.getMessage()));
+			throw new CmdLineException(clp, MessageFormat.format(CLIText.get().cannotReadBecause, name, e.getMessage()));
 		}
 		setter.addValue(c);
 		return 1;
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/SubcommandHandler.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/SubcommandHandler.java
index c62ef0d..96f3ed0 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/SubcommandHandler.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/SubcommandHandler.java
@@ -63,6 +63,8 @@
  * we can execute at runtime with the remaining arguments of the parser.
  */
 public class SubcommandHandler extends OptionHandler<TextBuiltin> {
+	private final org.eclipse.jgit.pgm.opt.CmdLineParser clp;
+
 	/**
 	 * Create a new handler for the command name.
 	 * <p>
@@ -75,6 +77,7 @@
 	public SubcommandHandler(final CmdLineParser parser,
 			final OptionDef option, final Setter<? super TextBuiltin> setter) {
 		super(parser, option, setter);
+		clp = (org.eclipse.jgit.pgm.opt.CmdLineParser) parser;
 	}
 
 	@Override
@@ -82,7 +85,7 @@
 		final String name = params.getParameter(0);
 		final CommandRef cr = CommandCatalog.get(name);
 		if (cr == null)
-			throw new CmdLineException(MessageFormat.format(
+			throw new CmdLineException(clp, MessageFormat.format(
 					CLIText.get().notAJgitCommand, name));
 
 		// Force option parsing to stop. Everything after us should
diff --git a/org.eclipse.jgit.test/BUCK b/org.eclipse.jgit.test/BUCK
new file mode 100644
index 0000000..3df3336
--- /dev/null
+++ b/org.eclipse.jgit.test/BUCK
@@ -0,0 +1,95 @@
+PKG = 'tst/org/eclipse/jgit/'
+HELPERS = glob(['src/**/*.java']) + [PKG + c for c in [
+  'api/AbstractRemoteCommandTest.java',
+  'diff/AbstractDiffTestCase.java',
+  'internal/storage/file/GcTestCase.java',
+  'internal/storage/file/PackIndexTestCase.java',
+  'internal/storage/file/XInputStream.java',
+  'nls/GermanTranslatedBundle.java',
+  'nls/MissingPropertyBundle.java',
+  'nls/NoPropertiesBundle.java',
+  'nls/NonTranslatedBundle.java',
+  'revwalk/RevQueueTestCase.java',
+  'revwalk/RevWalkTestCase.java',
+  'transport/SpiTransport.java',
+  'treewalk/FileTreeIteratorWithTimeControl.java',
+  'treewalk/filter/AlwaysCloneTreeFilter.java',
+  'test/resources/SampleDataRepositoryTestCase.java',
+  'util/CPUTimeStopWatch.java',
+  'util/io/Strings.java',
+]]
+
+DATA = [
+  PKG + 'lib/empty.gitindex.dat',
+  PKG + 'lib/sorttest.gitindex.dat',
+]
+
+TESTS = glob(
+  ['tst/**/*.java'],
+  excludes = HELPERS + DATA,
+)
+
+DEPS = {
+  PKG + 'nls/RootLocaleTest.java': [
+    '//org.eclipse.jgit.pgm:pgm',
+    '//org.eclipse.jgit.ui:ui',
+  ],
+}
+
+for src in TESTS:
+  name = src[len('tst/'):len(src)-len('.java')].replace('/', '.')
+  labels = []
+  if name.startswith('org.eclipse.jgit.'):
+    l = name[len('org.eclipse.jgit.'):]
+    if l.startswith('internal.storage.'):
+      l = l[len('internal.storage.'):]
+    i = l.find('.')
+    if i > 0:
+      labels.append(l[:i])
+    else:
+      labels.append(i)
+  if 'lib' not in labels:
+    labels.append('lib')
+
+  java_test(
+    name = name,
+    labels = labels,
+    srcs = [src],
+    deps = [
+      ':helpers',
+      ':tst_rsrc',
+      '//org.eclipse.jgit:jgit',
+      '//org.eclipse.jgit.junit:junit',
+      '//lib:hamcrest-core',
+      '//lib:hamcrest-library',
+      '//lib:javaewah',
+      '//lib:junit',
+      '//lib:slf4j-api',
+      '//lib:slf4j-simple',
+    ] + DEPS.get(src, []),
+    source_under_test = ['//org.eclipse.jgit:jgit'],
+    vm_args = ['-Xmx256m', '-Dfile.encoding=UTF-8'],
+  )
+
+java_library(
+  name = 'helpers',
+  srcs = HELPERS,
+  resources = DATA,
+  deps = [
+    '//org.eclipse.jgit:jgit',
+    '//org.eclipse.jgit.junit:junit',
+    '//lib:junit',
+  ],
+)
+
+prebuilt_jar(
+  name = 'tst_rsrc',
+  binary_jar = ':tst_rsrc_jar',
+)
+
+genrule(
+  name = 'tst_rsrc_jar',
+  cmd = 'cd $SRCDIR/tst-rsrc ; zip -qr $OUT .',
+  srcs = glob(['tst-rsrc/**']),
+  out = 'tst_rsrc.jar',
+)
diff --git a/org.eclipse.jgit.test/META-INF/MANIFEST.MF b/org.eclipse.jgit.test/META-INF/MANIFEST.MF
index f059fdb..abb86e8 100644
--- a/org.eclipse.jgit.test/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.test/META-INF/MANIFEST.MF
@@ -2,54 +2,56 @@
 Bundle-ManifestVersion: 2
 Bundle-Name: %plugin_name
 Bundle-SymbolicName: org.eclipse.jgit.test
-Bundle-Version: 4.1.2.201602141800-r
+Bundle-Version: 4.2.1.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.1.2,4.2.0)",
- org.eclipse.jgit.api.errors;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.attributes;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.awtui;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.blame;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.diff;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.dircache;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.errors;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.events;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.fnmatch;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.gitrepo;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.hooks;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.ignore;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.ignore.internal;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.internal;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.internal.storage.dfs;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.internal.storage.file;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.internal.storage.pack;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.junit;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.lib;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.merge;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.nls;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.notes;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.patch;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.pgm;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.pgm.internal;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.revplot;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.revwalk;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.revwalk.filter;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.storage.file;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.storage.pack;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.submodule;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.transport;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.transport.http;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.transport.resolver;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.treewalk;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.treewalk.filter;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.util;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.util.io;version="[4.1.2,4.2.0)",
+ org.eclipse.jgit.api;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.api.errors;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.attributes;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.awtui;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.blame;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.diff;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.dircache;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.errors;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.events;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.fnmatch;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.gitrepo;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.hooks;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.ignore;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.ignore.internal;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.internal;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.internal.storage.dfs;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.internal.storage.file;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.internal.storage.pack;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.internal.storage.reftree;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.junit;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.lib;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.merge;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.nls;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.notes;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.patch;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.pgm;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.pgm.internal;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.revplot;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.revwalk;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.revwalk.filter;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.storage.file;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.storage.pack;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.submodule;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.transport;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.transport.http;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.transport.resolver;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.treewalk;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.treewalk.filter;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.util;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.util.io;version="[4.2.1,4.3.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)",
  org.junit.runner;version="[4.4.0,5.0.0)",
- org.junit.runners;version="[4.11.0,5.0.0)"
+ org.junit.runners;version="[4.11.0,5.0.0)",
+ org.slf4j;version="[1.7.2,2.0.0)"
 Require-Bundle: org.hamcrest.core;bundle-version="[1.1.0,2.0.0)"
diff --git a/org.eclipse.jgit.test/build.properties b/org.eclipse.jgit.test/build.properties
index afc4855..786046c 100644
--- a/org.eclipse.jgit.test/build.properties
+++ b/org.eclipse.jgit.test/build.properties
@@ -4,3 +4,4 @@
 bin.includes = META-INF/,\
                .,\
                plugin.properties
+additional.bundles = org.apache.log4j
diff --git a/org.eclipse.jgit.test/exttst/org/eclipse/jgit/ignore/CGitVsJGitRandomIgnorePatternTest.java b/org.eclipse.jgit.test/exttst/org/eclipse/jgit/ignore/CGitVsJGitRandomIgnorePatternTest.java
new file mode 100644
index 0000000..db5f1b2
--- /dev/null
+++ b/org.eclipse.jgit.test/exttst/org/eclipse/jgit/ignore/CGitVsJGitRandomIgnorePatternTest.java
@@ -0,0 +1,275 @@
+/*
+ * Copyright (C) 2015, Sebastien Arod <sebastien.arod@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.ignore;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+import java.nio.file.Files;
+import java.nio.file.StandardOpenOption;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Random;
+
+import org.eclipse.jgit.api.Git;
+import org.junit.Assert;
+import org.junit.Test;
+
+/**
+ * This test generates random ignore patterns and random path and compares the
+ * output of Cgit check-ignore to the output of {@link FastIgnoreRule}.
+ */
+public class CGitVsJGitRandomIgnorePatternTest {
+
+	private static class PseudoRandomPatternGenerator {
+
+		private static final int DEFAULT_MAX_FRAGMENTS_PER_PATTERN = 15;
+
+		/**
+		 * Generates 75% Special fragments and 25% "standard" characters
+		 */
+		private static final double DEFAULT_SPECIAL_FRAGMENTS_FREQUENCY = 0.75d;
+
+		private static final List<String> SPECIAL_FRAGMENTS = Arrays.asList(
+				"\\", "!", "#", "[", "]", "|", "/", "*", "?", "{", "}", "(",
+				")", "\\d", "(", "**", "[a\\]]", "\\ ", "+", "-", "^", "$", ".",
+				":", "=", "[[:", ":]]"
+
+		);
+
+		private static final String STANDARD_CHARACTERS = new String(
+				"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789");
+
+		private final Random random = new Random();
+
+		private final int maxFragmentsPerPattern;
+
+		private final double specialFragmentsFrequency;
+
+		public PseudoRandomPatternGenerator() {
+			this(DEFAULT_MAX_FRAGMENTS_PER_PATTERN,
+					DEFAULT_SPECIAL_FRAGMENTS_FREQUENCY);
+		}
+
+		public PseudoRandomPatternGenerator(int maxFragmentsPerPattern,
+				double specialFragmentsFrequency) {
+			this.maxFragmentsPerPattern = maxFragmentsPerPattern;
+			this.specialFragmentsFrequency = specialFragmentsFrequency;
+		}
+
+		public String nextRandomString() {
+			StringBuilder builder = new StringBuilder();
+			int length = randomFragmentCount();
+			for (int i = 0; i < length; i++) {
+				if (useSpecialFragment()) {
+					builder.append(randomSpecialFragment());
+				} else {
+					builder.append(randomStandardCharacters());
+				}
+
+			}
+			return builder.toString();
+		}
+
+		private int randomFragmentCount() {
+			// We want at least one fragment
+			return 1 + random.nextInt(maxFragmentsPerPattern - 1);
+		}
+
+		private char randomStandardCharacters() {
+			return STANDARD_CHARACTERS
+					.charAt(random.nextInt(STANDARD_CHARACTERS.length()));
+		}
+
+		private boolean useSpecialFragment() {
+			return random.nextDouble() < specialFragmentsFrequency;
+		}
+
+		private String randomSpecialFragment() {
+			return SPECIAL_FRAGMENTS
+					.get(random.nextInt(SPECIAL_FRAGMENTS.size()));
+		}
+	}
+
+	@SuppressWarnings("serial")
+	public static class CgitFatalException extends Exception {
+
+		public CgitFatalException(int cgitExitCode, String pattern, String path,
+				String cgitStdError) {
+			super("CgitFatalException (" + cgitExitCode + ") for pattern:["
+					+ pattern + "] and path:[" + path + "]\n" + cgitStdError);
+		}
+
+	}
+
+	public static class CGitIgnoreRule {
+
+		private File gitDir;
+
+		private String pattern;
+
+		public CGitIgnoreRule(File gitDir, String pattern)
+				throws UnsupportedEncodingException, IOException {
+			this.gitDir = gitDir;
+			this.pattern = pattern;
+			Files.write(new File(gitDir, ".gitignore").toPath(),
+					(pattern + "\n").getBytes("UTF-8"),
+					StandardOpenOption.CREATE,
+					StandardOpenOption.TRUNCATE_EXISTING,
+					StandardOpenOption.WRITE);
+		}
+
+		public boolean isMatch(String path)
+				throws IOException, InterruptedException, CgitFatalException {
+			Process proc = startCgitCheckIgnore(path);
+
+			String cgitStdOutput = readProcessStream(proc.getInputStream());
+			String cgitStdError = readProcessStream(proc.getErrorStream());
+
+			int cgitExitCode = proc.waitFor();
+
+			if (cgitExitCode == 128) {
+				throw new CgitFatalException(cgitExitCode, pattern, path,
+						cgitStdError);
+			}
+			return !cgitStdOutput.startsWith("::");
+		}
+
+		private Process startCgitCheckIgnore(String path) throws IOException {
+			// Use --stdin instead of using argument otherwise paths starting
+			// with "-" were interpreted as
+			// options by git check-ignore
+			String[] command = new String[] { "git", "check-ignore",
+					"--no-index", "-v", "-n", "--stdin" };
+			Process proc = Runtime.getRuntime().exec(command, new String[0],
+					gitDir);
+			OutputStream out = proc.getOutputStream();
+			out.write((path + "\n").getBytes("UTF-8"));
+			out.flush();
+			out.close();
+			return proc;
+		}
+
+		private String readProcessStream(InputStream processStream)
+				throws IOException {
+			try (BufferedReader stdOut = new BufferedReader(
+					new InputStreamReader(processStream))) {
+
+				StringBuilder out = new StringBuilder();
+				String s;
+				while ((s = stdOut.readLine()) != null) {
+					out.append(s);
+				}
+				return out.toString();
+			}
+		}
+	}
+
+	private static final int NB_PATTERN = 1000;
+
+	private static final int PATH_PER_PATTERN = 1000;
+
+	@Test
+	public void testRandomPatterns() throws Exception {
+		// Initialize new git repo
+		File gitDir = Files.createTempDirectory("jgit").toFile();
+		Git.init().setDirectory(gitDir).call();
+		PseudoRandomPatternGenerator generator = new PseudoRandomPatternGenerator();
+
+		// Generate random patterns and paths
+		for (int i = 0; i < NB_PATTERN; i++) {
+			String pattern = generator.nextRandomString();
+
+			FastIgnoreRule jgitIgnoreRule = new FastIgnoreRule(pattern);
+			CGitIgnoreRule cgitIgnoreRule = new CGitIgnoreRule(gitDir, pattern);
+
+			// Test path with pattern as path
+			assertCgitAndJgitMatch(pattern, jgitIgnoreRule, cgitIgnoreRule,
+					pattern);
+
+			for (int p = 0; p < PATH_PER_PATTERN; p++) {
+				String path = generator.nextRandomString();
+				assertCgitAndJgitMatch(pattern, jgitIgnoreRule, cgitIgnoreRule,
+						path);
+			}
+		}
+	}
+
+	@SuppressWarnings({ "boxing" })
+	private void assertCgitAndJgitMatch(String pattern,
+			FastIgnoreRule jgitIgnoreRule, CGitIgnoreRule cgitIgnoreRule,
+			String pathToTest) throws IOException, InterruptedException {
+
+		try {
+			boolean cgitMatch = cgitIgnoreRule.isMatch(pathToTest);
+			boolean jgitMatch = jgitIgnoreRule.isMatch(pathToTest,
+					pathToTest.endsWith("/"));
+			if (cgitMatch != jgitMatch) {
+				System.err.println(
+						buildAssertionToAdd(pattern, pathToTest, cgitMatch));
+			}
+			Assert.assertEquals("jgit:" + jgitMatch + " <> cgit:" + cgitMatch
+					+ " for pattern:[" + pattern + "] and path:[" + pathToTest
+					+ "]", cgitMatch, jgitMatch);
+		} catch (CgitFatalException e) {
+			// Lots of generated patterns or path are rejected by Cgit with a
+			// fatal error. We want to ignore them.
+		}
+	}
+
+	private String buildAssertionToAdd(String pattern, String pathToTest,
+			boolean cgitMatch) {
+		return "assertMatch(" + toJavaString(pattern) + ", "
+				+ toJavaString(pathToTest) + ", " + cgitMatch
+				+ " /*cgit result*/);";
+	}
+
+	private String toJavaString(String pattern2) {
+		return "\"" + pattern2.replace("\\", "\\\\").replace("\"", "\\\"")
+				+ "\"";
+	}
+}
diff --git "a/org.eclipse.jgit.test/org.eclipse.jgit.core--All-Tests \050Java 7\051.launch" "b/org.eclipse.jgit.test/org.eclipse.jgit.core--All-Tests \050Java 7\051.launch"
index af009c0..a83fabb 100644
--- "a/org.eclipse.jgit.test/org.eclipse.jgit.core--All-Tests \050Java 7\051.launch"
+++ "b/org.eclipse.jgit.test/org.eclipse.jgit.core--All-Tests \050Java 7\051.launch"
@@ -18,7 +18,6 @@
 <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;"/>
-<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry path=&quot;3&quot; projectName=&quot;org.eclipse.jgit.java7&quot; type=&quot;1&quot;/&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"/>
diff --git "a/org.eclipse.jgit.test/org.eclipse.jgit.core--All-Tests \050Java 8\051 \050de\051.launch" "b/org.eclipse.jgit.test/org.eclipse.jgit.core--All-Tests \050Java 8\051 \050de\051.launch"
new file mode 100644
index 0000000..f12a529
--- /dev/null
+++ "b/org.eclipse.jgit.test/org.eclipse.jgit.core--All-Tests \050Java 8\051 \050de\051.launch"
@@ -0,0 +1,31 @@
+<?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"/>
+<booleanAttribute key="org.eclipse.jdt.launching.ATTR_USE_START_ON_FIRST_THREAD" value="true"/>
+<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.8"/>
+<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/org.eclipse.jgit.core--All-Tests \050Java 8\051.launch" "b/org.eclipse.jgit.test/org.eclipse.jgit.core--All-Tests \050Java 8\051.launch"
index 04aa3ea..b221a11 100644
--- "a/org.eclipse.jgit.test/org.eclipse.jgit.core--All-Tests \050Java 8\051.launch"
+++ "b/org.eclipse.jgit.test/org.eclipse.jgit.core--All-Tests \050Java 8\051.launch"
@@ -19,7 +19,6 @@
 <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;"/>
-<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry path=&quot;3&quot; projectName=&quot;org.eclipse.jgit.java7&quot; type=&quot;1&quot;/&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.8"/>
diff --git a/org.eclipse.jgit.test/org.eclipse.jgit.test-WalkEncryptionTest-Proxy.launch b/org.eclipse.jgit.test/org.eclipse.jgit.test-WalkEncryptionTest-Proxy.launch
new file mode 100644
index 0000000..fe3a013
--- /dev/null
+++ b/org.eclipse.jgit.test/org.eclipse.jgit.test-WalkEncryptionTest-Proxy.launch
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<launchConfiguration type="org.eclipse.m2e.Maven2LaunchConfigurationType">
+<booleanAttribute key="M2_DEBUG_OUTPUT" value="false"/>
+<stringAttribute key="M2_GOALS" value="test --define test=WalkEncryptionTest --define http_proxy=http://proxy:3128"/>
+<booleanAttribute key="M2_NON_RECURSIVE" value="false"/>
+<booleanAttribute key="M2_OFFLINE" value="false"/>
+<stringAttribute key="M2_PROFILES" value=""/>
+<listAttribute key="M2_PROPERTIES"/>
+<stringAttribute key="M2_RUNTIME" value="EMBEDDED"/>
+<booleanAttribute key="M2_SKIP_TESTS" value="false"/>
+<intAttribute key="M2_THREADS" value="1"/>
+<booleanAttribute key="M2_UPDATE_SNAPSHOTS" value="false"/>
+<stringAttribute key="M2_USER_SETTINGS" value=""/>
+<booleanAttribute key="M2_WORKSPACE_RESOLUTION" value="false"/>
+<listAttribute key="org.eclipse.debug.ui.favoriteGroups">
+<listEntry value="org.eclipse.debug.ui.launchGroup.run"/>
+</listAttribute>
+<stringAttribute key="org.eclipse.jdt.launching.JRE_CONTAINER" value="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/java-8-oracle"/>
+<stringAttribute key="org.eclipse.jdt.launching.WORKING_DIRECTORY" value="${workspace_loc:/org.eclipse.jgit.test}"/>
+</launchConfiguration>
diff --git a/org.eclipse.jgit.test/org.eclipse.jgit.test-WalkEncryptionTest.launch b/org.eclipse.jgit.test/org.eclipse.jgit.test-WalkEncryptionTest.launch
new file mode 100644
index 0000000..3b4a5a2
--- /dev/null
+++ b/org.eclipse.jgit.test/org.eclipse.jgit.test-WalkEncryptionTest.launch
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<launchConfiguration type="org.eclipse.m2e.Maven2LaunchConfigurationType">
+<booleanAttribute key="M2_DEBUG_OUTPUT" value="false"/>
+<stringAttribute key="M2_GOALS" value="test --define test=WalkEncryptionTest --activate-profiles test.long"/>
+<booleanAttribute key="M2_NON_RECURSIVE" value="false"/>
+<booleanAttribute key="M2_OFFLINE" value="false"/>
+<stringAttribute key="M2_PROFILES" value=""/>
+<listAttribute key="M2_PROPERTIES"/>
+<stringAttribute key="M2_RUNTIME" value="EMBEDDED"/>
+<booleanAttribute key="M2_SKIP_TESTS" value="false"/>
+<intAttribute key="M2_THREADS" value="1"/>
+<booleanAttribute key="M2_UPDATE_SNAPSHOTS" value="false"/>
+<stringAttribute key="M2_USER_SETTINGS" value=""/>
+<booleanAttribute key="M2_WORKSPACE_RESOLUTION" value="false"/>
+<listAttribute key="org.eclipse.debug.ui.favoriteGroups">
+<listEntry value="org.eclipse.debug.ui.launchGroup.run"/>
+</listAttribute>
+<stringAttribute key="org.eclipse.jdt.launching.JRE_CONTAINER" value="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/java-8-oracle"/>
+<stringAttribute key="org.eclipse.jdt.launching.WORKING_DIRECTORY" value="${workspace_loc:/org.eclipse.jgit.test}"/>
+</launchConfiguration>
diff --git a/org.eclipse.jgit.test/pom.xml b/org.eclipse.jgit.test/pom.xml
index be72211..0985c3d 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.1.2.201602141800-r</version>
+    <version>4.2.1-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.test</artifactId>
@@ -69,6 +69,16 @@
       <scope>test</scope>
     </dependency>
 
+    <!-- Optional security provider for encryption tests. -->
+    <!-- See https://dev.eclipse.org/ipzilla/show_bug.cgi?id=9554 -->
+    <!-- See https://bugs.eclipse.org/bugs/show_bug.cgi?id=467064 -->
+    <dependency>
+      <groupId>org.bouncycastle</groupId>
+      <artifactId>bcprov-jdk15on</artifactId>
+      <version>1.52</version>
+      <scope>test</scope>
+     </dependency>
+
     <dependency>
       <groupId>org.hamcrest</groupId>
       <artifactId>hamcrest-library</artifactId>
@@ -101,6 +111,24 @@
     </dependency>
   </dependencies>
 
+  <profiles>
+    <!-- Profile provides a property which enables long running tests. -->
+    <profile>
+      <id>test.long</id>
+      <build>
+        <plugins>
+          <plugin>
+            <groupId>org.apache.maven.plugins</groupId>
+            <artifactId>maven-surefire-plugin</artifactId>
+            <configuration>
+              <argLine>-Djgit.test.long=true</argLine>
+            </configuration>
+          </plugin>
+        </plugins>
+      </build>
+    </profile>
+  </profiles>
+
   <build>
     <sourceDirectory>src/</sourceDirectory>
     <testSourceDirectory>tst/</testSourceDirectory>
diff --git a/org.eclipse.jgit.test/tst-rsrc/jgit-s3-config.disabled.properties b/org.eclipse.jgit.test/tst-rsrc/jgit-s3-config.disabled.properties
new file mode 100644
index 0000000..d540977
--- /dev/null
+++ b/org.eclipse.jgit.test/tst-rsrc/jgit-s3-config.disabled.properties
@@ -0,0 +1,48 @@
+#
+# See WalkEncryptionTest.java
+#
+# This file is a template for test configuration file used by WalkEncryptionTest.
+# To be active, this file must have the following hard coded name: jgit-s3-config.properties
+# To be active, this file must be discovered by WalkEncryptionTest from one of these locations:
+# * ${user.home}/jgit-s3-config.properties
+# * ${user.dir}/jgit-s3-config.properties
+# * ${user.dir}/tst-rsrc/jgit-s3-config.properties
+# When this file is missing, tests in WalkEncryptionTest will not run, only report a warning.
+#
+
+#
+# WalkEncryptionTest requires amazon s3 test bucket setup.
+#
+# Test bucket setup instructions:
+#
+# Create IAM user:
+# http://docs.aws.amazon.com/IAM/latest/UserGuide/id_users_create.html
+# * user name: jgit.eclipse.org
+#
+# Configure IAM user S3 bucket access
+# http://docs.aws.amazon.com/AmazonS3/latest/dev/example-policies-s3.html
+# * attach S3 user policy to user account: jgit-s3-config.policy.user.json
+#
+# Create S3 bucket:
+# http://docs.aws.amazon.com/AmazonS3/latest/gsg/CreatingABucket.html
+# * bucket name: jgit.eclipse.org
+#
+# Configure S3 bucket source address/mask access:
+# http://docs.aws.amazon.com/AmazonS3/latest/dev/example-bucket-policies.html
+# * attach bucket policy to the test bucket: jgit-s3-config.policy.bucket.json
+# * verify that any required source address/mask is included in the bucket policy:
+# * see https://wiki.eclipse.org/Hudson
+# * see http://www.tcpiputils.com/browse/ip-address/198.41.30.200
+# * proxy.eclipse.org	198.41.30.0/24
+# * Andrei Pozolotin	67.175.188.187/32
+#
+# Configure bucket 1 day expiration in object life cycle management:
+# * https://docs.aws.amazon.com/AmazonS3/latest/dev/manage-lifecycle-using-console.html
+#
+
+# Test bucket name
+test.bucket=jgit.eclipse.org
+
+# IAM credentials for user jgit.eclipse.org
+accesskey=AKIAIYWXB4ETREBRMZDQ
+secretkey=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
diff --git a/org.eclipse.jgit.test/tst-rsrc/jgit-s3-config.policy.bucket.json b/org.eclipse.jgit.test/tst-rsrc/jgit-s3-config.policy.bucket.json
new file mode 100644
index 0000000..3020b09
--- /dev/null
+++ b/org.eclipse.jgit.test/tst-rsrc/jgit-s3-config.policy.bucket.json
@@ -0,0 +1,20 @@
+{
+    "Version": "2012-10-17",
+    "Statement": [
+        {
+            "Sid": "DenyAllButKnownSourceAddressWithMask",
+            "Effect": "Deny",
+            "Principal": "*",
+            "Action": "s3:*",
+            "Resource": "arn:aws:s3:::jgit.eclipse.org/*",
+            "Condition": {
+                "NotIpAddress": {
+                    "aws:SourceIp": [
+                        "198.41.30.0/24",
+                        "67.175.188.187/32"
+                    ]
+                }
+            }
+        }
+    ]
+}
diff --git a/org.eclipse.jgit.test/tst-rsrc/jgit-s3-config.policy.user.json b/org.eclipse.jgit.test/tst-rsrc/jgit-s3-config.policy.user.json
new file mode 100644
index 0000000..830d088
--- /dev/null
+++ b/org.eclipse.jgit.test/tst-rsrc/jgit-s3-config.policy.user.json
@@ -0,0 +1,24 @@
+{
+    "Version": "2012-10-17",
+    "Statement": [
+        {
+            "Sid": "BucketList",
+            "Effect": "Allow",
+            "Action": "s3:ListAllMyBuckets",
+            "Resource": [
+                "arn:aws:s3:::jgit.eclipse.org"
+            ]
+        },
+        {
+            "Sid": "BucketFullControl",
+            "Effect": "Allow",
+            "Action": [
+                "s3:*"
+            ],
+            "Resource": [
+                "arn:aws:s3:::jgit.eclipse.org",
+                "arn:aws:s3:::jgit.eclipse.org/*"
+            ]
+        }
+    ]
+}
diff --git a/org.eclipse.jgit.test/tst-rsrc/jgit-s3-connection-v-0.properties b/org.eclipse.jgit.test/tst-rsrc/jgit-s3-connection-v-0.properties
new file mode 100644
index 0000000..2402a49
--- /dev/null
+++ b/org.eclipse.jgit.test/tst-rsrc/jgit-s3-connection-v-0.properties
@@ -0,0 +1,11 @@
+#
+# Sample Amazon S3 connection configuration file, Version 0.
+# Version 0 (or lack of version) will produce JetS3tV2 compatible encryption.
+# JetS3tV2 supports only PBE algorithms, with partially compromised AES mode.
+#
+
+accesskey = AKIAIYWXB4ETREBRM123
+secretkey = ozCuIsqxsARoPe3FFyv3F/jiMSc3Yqay7B9UF234
+
+crypto.algorithm = PBEWithMD5AndDES
+password = secret
diff --git a/org.eclipse.jgit.test/tst-rsrc/jgit-s3-connection-v-1.properties b/org.eclipse.jgit.test/tst-rsrc/jgit-s3-connection-v-1.properties
new file mode 100644
index 0000000..d0d1611
--- /dev/null
+++ b/org.eclipse.jgit.test/tst-rsrc/jgit-s3-connection-v-1.properties
@@ -0,0 +1,14 @@
+#
+# Sample Amazon S3 connection configuration file, Version 1.
+# Version 1 will produce JGitV1 compatible encryption.
+# It is JetS3tV2-like mode with proper AES support.
+# JGitV1 uses hard coded encryption parameters.
+# JGitV1 supports only PBE algorithms.
+#
+
+accesskey = AKIAIYWXB4ETREBRM123
+secretkey = ozCuIsqxsARoPe3FFyv3F/jiMSc3Yqay7B9UF234
+
+crypto.algorithm = PBEWithHmacSHA1AndAES_128
+crypto.version = 1
+password = secret
diff --git a/org.eclipse.jgit.test/tst-rsrc/jgit-s3-connection-v-2.properties b/org.eclipse.jgit.test/tst-rsrc/jgit-s3-connection-v-2.properties
new file mode 100644
index 0000000..731b324
--- /dev/null
+++ b/org.eclipse.jgit.test/tst-rsrc/jgit-s3-connection-v-2.properties
@@ -0,0 +1,48 @@
+#
+# Sample Amazon S3 connection configuration file, Version 2.
+# Version 2 will produce JGitV2 compatible encryption.
+# JGitV2 introduces more flexible control over cipher and key factory parameters.
+# JGitV2 hides actual cipher/key algorithms inside the encryption profile.
+# JGitV2 does not use any hard coded encryption parameters.
+# JGitV2 supports both PBE and Non-PBE algorithms.
+
+accesskey = AKIAIYWXB4ETREBRM123
+secretkey = ozCuIsqxsARoPe3FFyv3F/jiMSc3Yqay7B9UF234
+
+# In Version 2 "crypto.algorithm" is a reference to the encryption "profile".
+crypto.algorithm = custom
+crypto.version = 2
+password = secret
+
+#
+# Encryption profile is a collection of related properties,
+# all having common property root name, or prefix:
+#
+# Cipher algorithm.
+custom.algo = AES/CBC/PKCS5Padding
+# Key factory algorithm.
+custom.key.algo = PBKDF2WithHmacSHA512
+# Key size, bits.
+custom.key.size = 256
+# Number of key generation iterations.
+custom.key.iter = 50000
+# Salt used in key generation (hex value, white space OK).
+custom.key.salt = e2 55 89 67 8e 8d e8 4c
+
+# Same file can store multiple profiles.
+# Only one profile can be active at a time.
+# Active profile is selected via "crypto.algorithm"
+
+#
+# Here is how to create V1 encryption in V2 format:
+#
+# Cipher algorithm.
+legacy.algo = PBEWithHmacSHA1AndAES_128
+# Key factory algorithm.
+legacy.key.algo = PBEWithHmacSHA1AndAES_128
+# Key size, bits.
+legacy.key.size = 32
+# Number of key generation iterations.
+legacy.key.iter = 5000
+# Salt used in key generation (hex value, white space OK).
+legacy.key.salt = A40BC834D695F313
diff --git a/org.eclipse.jgit.test/tst-rsrc/log4j.properties b/org.eclipse.jgit.test/tst-rsrc/log4j.properties
new file mode 100644
index 0000000..14620ff
--- /dev/null
+++ b/org.eclipse.jgit.test/tst-rsrc/log4j.properties
@@ -0,0 +1,9 @@
+
+# Root logger option
+log4j.rootLogger=INFO, stdout
+
+# Direct log messages to stdout
+log4j.appender.stdout=org.apache.log4j.ConsoleAppender
+log4j.appender.stdout.Target=System.out
+log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
+log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/AbstractRemoteCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/AbstractRemoteCommandTest.java
new file mode 100644
index 0000000..d6a6342
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/AbstractRemoteCommandTest.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2015, Kaloyan Raev <kaloyan.r@zend.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.api;
+
+import static org.junit.Assert.assertEquals;
+
+import java.io.IOException;
+import java.net.URISyntaxException;
+
+import org.eclipse.jgit.junit.RepositoryTestCase;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.lib.StoredConfig;
+import org.eclipse.jgit.transport.RefSpec;
+import org.eclipse.jgit.transport.RemoteConfig;
+import org.eclipse.jgit.transport.URIish;
+
+public class AbstractRemoteCommandTest extends RepositoryTestCase {
+
+	protected static final String REMOTE_NAME = "test";
+
+	protected RemoteConfig setupRemote()
+			throws IOException, URISyntaxException {
+		// create another repository
+		Repository remoteRepository = createWorkRepository();
+
+		// set it up as a remote to this repository
+		final StoredConfig config = db.getConfig();
+		RemoteConfig remoteConfig = new RemoteConfig(config, REMOTE_NAME);
+
+		RefSpec refSpec = new RefSpec();
+		refSpec = refSpec.setForceUpdate(true);
+		refSpec = refSpec.setSourceDestination(Constants.R_HEADS + "*",
+				Constants.R_REMOTES + REMOTE_NAME + "/*");
+		remoteConfig.addFetchRefSpec(refSpec);
+
+		URIish uri = new URIish(
+				remoteRepository.getDirectory().toURI().toURL());
+		remoteConfig.addURI(uri);
+
+		remoteConfig.update(config);
+		config.save();
+
+		return remoteConfig;
+	}
+
+	protected void assertRemoteConfigEquals(RemoteConfig expected,
+			RemoteConfig actual) {
+		assertEquals(expected.getName(), actual.getName());
+		assertEquals(expected.getURIs(), actual.getURIs());
+		assertEquals(expected.getPushURIs(), actual.getPushURIs());
+		assertEquals(expected.getFetchRefSpecs(), actual.getFetchRefSpecs());
+		assertEquals(expected.getPushRefSpecs(), actual.getPushRefSpecs());
+	}
+
+}
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 2abed3a..4fefdfd 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
@@ -43,8 +43,10 @@
  */
 package org.eclipse.jgit.api;
 
+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;
 
 import java.io.File;
@@ -52,11 +54,13 @@
 import java.io.IOException;
 import java.io.PrintWriter;
 
+import org.eclipse.jgit.api.errors.FilterFailedException;
 import org.eclipse.jgit.api.errors.GitAPIException;
 import org.eclipse.jgit.api.errors.NoFilepatternException;
 import org.eclipse.jgit.dircache.DirCache;
 import org.eclipse.jgit.dircache.DirCacheBuilder;
 import org.eclipse.jgit.dircache.DirCacheEntry;
+import org.eclipse.jgit.junit.JGitTestUtil;
 import org.eclipse.jgit.junit.RepositoryTestCase;
 import org.eclipse.jgit.lib.ConfigConstants;
 import org.eclipse.jgit.lib.Constants;
@@ -74,9 +78,7 @@
 
 	@Test
 	public void testAddNothing() throws GitAPIException {
-		Git git = new Git(db);
-
-		try {
+		try (Git git = new Git(db)) {
 			git.add().call();
 			fail("Expected IllegalArgumentException");
 		} catch (NoFilepatternException e) {
@@ -87,11 +89,10 @@
 
 	@Test
 	public void testAddNonExistingSingleFile() throws GitAPIException {
-		Git git = new Git(db);
-
-		DirCache dc = git.add().addFilepattern("a.txt").call();
-		assertEquals(0, dc.getEntryCount());
-
+		try (Git git = new Git(db)) {
+			DirCache dc = git.add().addFilepattern("a.txt").call();
+			assertEquals(0, dc.getEntryCount());
+		}
 	}
 
 	@Test
@@ -102,13 +103,207 @@
 		writer.print("content");
 		writer.close();
 
-		Git git = new Git(db);
+		try (Git git = new Git(db)) {
+			git.add().addFilepattern("a.txt").call();
 
-		git.add().addFilepattern("a.txt").call();
+			assertEquals(
+					"[a.txt, mode:100644, content:content]",
+					indexState(CONTENT));
+		}
+	}
 
-		assertEquals(
-				"[a.txt, mode:100644, content:content]",
-				indexState(CONTENT));
+	@Test
+	public void testCleanFilter() throws IOException,
+			GitAPIException {
+		writeTrashFile(".gitattributes", "*.txt filter=tstFilter");
+		writeTrashFile("src/a.tmp", "foo");
+		// Caution: we need a trailing '\n' since sed on mac always appends
+		// linefeeds if missing
+		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", "tstFilter", "clean",
+					"sh " + slashify(script.getPath()));
+			config.save();
+
+			git.add().addFilepattern("src/a.txt").addFilepattern("src/a.tmp")
+					.call();
+
+			assertEquals(
+					"[src/a.tmp, mode:100644, content:foo][src/a.txt, mode:100644, content:fee\n]",
+					indexState(CONTENT));
+		}
+	}
+
+	@Test
+	public void testCleanFilterEnvironment()
+			throws IOException, GitAPIException {
+		writeTrashFile(".gitattributes", "*.txt filter=tstFilter");
+		writeTrashFile("src/a.txt", "foo");
+		File script = writeTempFile("echo $GIT_DIR; echo 1 >xyz");
+
+		try (Git git = new Git(db)) {
+			StoredConfig config = git.getRepository().getConfig();
+			config.setString("filter", "tstFilter", "clean",
+					"sh " + slashify(script.getPath()));
+			config.save();
+			git.add().addFilepattern("src/a.txt").call();
+
+			String gitDir = db.getDirectory().getAbsolutePath();
+			assertEquals("[src/a.txt, mode:100644, content:" + gitDir
+					+ "\n]", indexState(CONTENT));
+			assertTrue(new File(db.getWorkTree(), "xyz").exists());
+		}
+	}
+
+	@Test
+	public void testMultipleCleanFilter() throws IOException, GitAPIException {
+		writeTrashFile(".gitattributes",
+				"*.txt filter=tstFilter\n*.tmp filter=tstFilter2");
+		// Caution: we need a trailing '\n' since sed on mac always appends
+		// linefeeds if missing
+		writeTrashFile("src/a.tmp", "foo\n");
+		writeTrashFile("src/a.txt", "foo\n");
+		File script = writeTempFile("sed s/o/e/g");
+		File script2 = writeTempFile("sed s/f/x/g");
+
+		try (Git git = new Git(db)) {
+			StoredConfig config = git.getRepository().getConfig();
+			config.setString("filter", "tstFilter", "clean",
+					"sh " + slashify(script.getPath()));
+			config.setString("filter", "tstFilter2", "clean",
+					"sh " + slashify(script2.getPath()));
+			config.save();
+
+			git.add().addFilepattern("src/a.txt").addFilepattern("src/a.tmp")
+					.call();
+
+			assertEquals(
+					"[src/a.tmp, mode:100644, content:xoo\n][src/a.txt, mode:100644, content:fee\n]",
+					indexState(CONTENT));
+
+			// TODO: multiple clean filters for one file???
+		}
+	}
+
+	/**
+	 * The path of an added file name contains ';' and afterwards malicious
+	 * commands. Make sure when calling filter commands to properly escape the
+	 * filenames
+	 *
+	 * @throws IOException
+	 * @throws GitAPIException
+	 */
+	@Test
+	public void testCommandInjection() throws IOException, GitAPIException {
+		// Caution: we need a trailing '\n' since sed on mac always appends
+		// linefeeds if missing
+		writeTrashFile("; echo virus", "foo\n");
+		File script = writeTempFile("sed s/o/e/g");
+
+		try (Git git = new Git(db)) {
+			StoredConfig config = git.getRepository().getConfig();
+			config.setString("filter", "tstFilter", "clean",
+					"sh " + slashify(script.getPath()) + " %f");
+			writeTrashFile(".gitattributes", "* filter=tstFilter");
+
+			git.add().addFilepattern("; echo virus").call();
+			// Without proper escaping the content would be "feovirus". The sed
+			// command and the "echo virus" would contribute to the content
+			assertEquals("[; echo virus, mode:100644, content:fee\n]",
+					indexState(CONTENT));
+		}
+	}
+
+	@Test
+	public void testBadCleanFilter() throws IOException, GitAPIException {
+		writeTrashFile("a.txt", "foo");
+		File script = writeTempFile("sedfoo s/o/e/g");
+
+		try (Git git = new Git(db)) {
+			StoredConfig config = git.getRepository().getConfig();
+			config.setString("filter", "tstFilter", "clean",
+					"sh " + script.getPath());
+			config.save();
+			writeTrashFile(".gitattributes", "*.txt filter=tstFilter");
+
+			try {
+				git.add().addFilepattern("a.txt").call();
+				fail("Didn't received the expected exception");
+			} catch (FilterFailedException e) {
+				assertEquals(127, e.getReturnCode());
+			}
+		}
+	}
+
+	@Test
+	public void testBadCleanFilter2() throws IOException, GitAPIException {
+		writeTrashFile("a.txt", "foo");
+		File script = writeTempFile("sed s/o/e/g");
+
+		try (Git git = new Git(db)) {
+			StoredConfig config = git.getRepository().getConfig();
+			config.setString("filter", "tstFilter", "clean",
+					"shfoo " + script.getPath());
+			config.save();
+			writeTrashFile(".gitattributes", "*.txt filter=tstFilter");
+
+			try {
+				git.add().addFilepattern("a.txt").call();
+				fail("Didn't received the expected exception");
+			} catch (FilterFailedException e) {
+				assertEquals(127, e.getReturnCode());
+			}
+		}
+	}
+
+	@Test
+	public void testCleanFilterReturning12() throws IOException,
+			GitAPIException {
+		writeTrashFile("a.txt", "foo");
+		File script = writeTempFile("exit 12");
+
+		try (Git git = new Git(db)) {
+			StoredConfig config = git.getRepository().getConfig();
+			config.setString("filter", "tstFilter", "clean",
+					"sh " + slashify(script.getPath()));
+			config.save();
+			writeTrashFile(".gitattributes", "*.txt filter=tstFilter");
+
+			try {
+				git.add().addFilepattern("a.txt").call();
+				fail("Didn't received the expected exception");
+			} catch (FilterFailedException e) {
+				assertEquals(12, e.getReturnCode());
+			}
+		}
+	}
+
+	@Test
+	public void testNotApplicableFilter() throws IOException, GitAPIException {
+		writeTrashFile("a.txt", "foo");
+		File script = writeTempFile("sed s/o/e/g");
+
+		try (Git git = new Git(db)) {
+			StoredConfig config = git.getRepository().getConfig();
+			config.setString("filter", "tstFilter", "something",
+					"sh " + script.getPath());
+			config.save();
+			writeTrashFile(".gitattributes", "*.txt filter=tstFilter");
+
+			git.add().addFilepattern("a.txt").call();
+
+			assertEquals("[a.txt, mode:100644, content:foo]",
+					indexState(CONTENT));
+		}
+	}
+
+	private File writeTempFile(String body) throws IOException {
+		File f = File.createTempFile("AddCommandTest_", "");
+		JGitTestUtil.write(f, body);
+		return f;
 	}
 
 	@Test
@@ -120,19 +315,20 @@
 		writer.print("row1\r\nrow2");
 		writer.close();
 
-		Git git = new Git(db);
-		db.getConfig().setString("core", null, "autocrlf", "false");
-		git.add().addFilepattern("a.txt").call();
-		assertEquals("[a.txt, mode:100644, content:row1\r\nrow2]",
-				indexState(CONTENT));
-		db.getConfig().setString("core", null, "autocrlf", "true");
-		git.add().addFilepattern("a.txt").call();
-		assertEquals("[a.txt, mode:100644, content:row1\nrow2]",
-				indexState(CONTENT));
-		db.getConfig().setString("core", null, "autocrlf", "input");
-		git.add().addFilepattern("a.txt").call();
-		assertEquals("[a.txt, mode:100644, content:row1\nrow2]",
-				indexState(CONTENT));
+		try (Git git = new Git(db)) {
+			db.getConfig().setString("core", null, "autocrlf", "false");
+			git.add().addFilepattern("a.txt").call();
+			assertEquals("[a.txt, mode:100644, content:row1\r\nrow2]",
+					indexState(CONTENT));
+			db.getConfig().setString("core", null, "autocrlf", "true");
+			git.add().addFilepattern("a.txt").call();
+			assertEquals("[a.txt, mode:100644, content:row1\nrow2]",
+					indexState(CONTENT));
+			db.getConfig().setString("core", null, "autocrlf", "input");
+			git.add().addFilepattern("a.txt").call();
+			assertEquals("[a.txt, mode:100644, content:row1\nrow2]",
+					indexState(CONTENT));
+		}
 	}
 
 	@Test
@@ -149,19 +345,20 @@
 		writer.print(crData);
 		writer.close();
 		String lfData = data.toString().replaceAll("\r", "");
-		Git git = new Git(db);
-		db.getConfig().setString("core", null, "autocrlf", "false");
-		git.add().addFilepattern("a.txt").call();
-		assertEquals("[a.txt, mode:100644, content:" + data + "]",
-				indexState(CONTENT));
-		db.getConfig().setString("core", null, "autocrlf", "true");
-		git.add().addFilepattern("a.txt").call();
-		assertEquals("[a.txt, mode:100644, content:" + lfData + "]",
-				indexState(CONTENT));
-		db.getConfig().setString("core", null, "autocrlf", "input");
-		git.add().addFilepattern("a.txt").call();
-		assertEquals("[a.txt, mode:100644, content:" + lfData + "]",
-				indexState(CONTENT));
+		try (Git git = new Git(db)) {
+			db.getConfig().setString("core", null, "autocrlf", "false");
+			git.add().addFilepattern("a.txt").call();
+			assertEquals("[a.txt, mode:100644, content:" + data + "]",
+					indexState(CONTENT));
+			db.getConfig().setString("core", null, "autocrlf", "true");
+			git.add().addFilepattern("a.txt").call();
+			assertEquals("[a.txt, mode:100644, content:" + lfData + "]",
+					indexState(CONTENT));
+			db.getConfig().setString("core", null, "autocrlf", "input");
+			git.add().addFilepattern("a.txt").call();
+			assertEquals("[a.txt, mode:100644, content:" + lfData + "]",
+					indexState(CONTENT));
+		}
 	}
 
 	@Test
@@ -173,19 +370,20 @@
 		writer.print("row1\r\nrow2\u0000");
 		writer.close();
 
-		Git git = new Git(db);
-		db.getConfig().setString("core", null, "autocrlf", "false");
-		git.add().addFilepattern("a.txt").call();
-		assertEquals("[a.txt, mode:100644, content:row1\r\nrow2\u0000]",
-				indexState(CONTENT));
-		db.getConfig().setString("core", null, "autocrlf", "true");
-		git.add().addFilepattern("a.txt").call();
-		assertEquals("[a.txt, mode:100644, content:row1\r\nrow2\u0000]",
-				indexState(CONTENT));
-		db.getConfig().setString("core", null, "autocrlf", "input");
-		git.add().addFilepattern("a.txt").call();
-		assertEquals("[a.txt, mode:100644, content:row1\r\nrow2\u0000]",
-				indexState(CONTENT));
+		try (Git git = new Git(db)) {
+			db.getConfig().setString("core", null, "autocrlf", "false");
+			git.add().addFilepattern("a.txt").call();
+			assertEquals("[a.txt, mode:100644, content:row1\r\nrow2\u0000]",
+					indexState(CONTENT));
+			db.getConfig().setString("core", null, "autocrlf", "true");
+			git.add().addFilepattern("a.txt").call();
+			assertEquals("[a.txt, mode:100644, content:row1\r\nrow2\u0000]",
+					indexState(CONTENT));
+			db.getConfig().setString("core", null, "autocrlf", "input");
+			git.add().addFilepattern("a.txt").call();
+			assertEquals("[a.txt, mode:100644, content:row1\r\nrow2\u0000]",
+					indexState(CONTENT));
+		}
 	}
 
 	@Test
@@ -198,13 +396,13 @@
 		writer.print("content");
 		writer.close();
 
-		Git git = new Git(db);
+		try (Git git = new Git(db)) {
+			git.add().addFilepattern("sub/a.txt").call();
 
-		git.add().addFilepattern("sub/a.txt").call();
-
-		assertEquals(
-				"[sub/a.txt, mode:100644, content:content]",
-				indexState(CONTENT));
+			assertEquals(
+					"[sub/a.txt, mode:100644, content:content]",
+					indexState(CONTENT));
+		}
 	}
 
 	@Test
@@ -216,20 +414,21 @@
 		writer.print("content");
 		writer.close();
 
-		Git git = new Git(db);
-		DirCache dc = git.add().addFilepattern("a.txt").call();
+		try (Git git = new Git(db)) {
+			DirCache dc = git.add().addFilepattern("a.txt").call();
 
-		dc.getEntry(0).getObjectId();
+			dc.getEntry(0).getObjectId();
 
-		writer = new PrintWriter(file);
-		writer.print("other content");
-		writer.close();
+			writer = new PrintWriter(file);
+			writer.print("other content");
+			writer.close();
 
-		dc = git.add().addFilepattern("a.txt").call();
+			dc = git.add().addFilepattern("a.txt").call();
 
-		assertEquals(
-				"[a.txt, mode:100644, content:other content]",
-				indexState(CONTENT));
+			assertEquals(
+					"[a.txt, mode:100644, content:other content]",
+					indexState(CONTENT));
+		}
 	}
 
 	@Test
@@ -240,22 +439,23 @@
 		writer.print("content");
 		writer.close();
 
-		Git git = new Git(db);
-		DirCache dc = git.add().addFilepattern("a.txt").call();
+		try (Git git = new Git(db)) {
+			DirCache dc = git.add().addFilepattern("a.txt").call();
 
-		dc.getEntry(0).getObjectId();
+			dc.getEntry(0).getObjectId();
 
-		git.commit().setMessage("commit a.txt").call();
+			git.commit().setMessage("commit a.txt").call();
 
-		writer = new PrintWriter(file);
-		writer.print("other content");
-		writer.close();
+			writer = new PrintWriter(file);
+			writer.print("other content");
+			writer.close();
 
-		dc = git.add().addFilepattern("a.txt").call();
+			dc = git.add().addFilepattern("a.txt").call();
 
-		assertEquals(
-				"[a.txt, mode:100644, content:other content]",
-				indexState(CONTENT));
+			assertEquals(
+					"[a.txt, mode:100644, content:other content]",
+					indexState(CONTENT));
+		}
 	}
 
 	@Test
@@ -266,18 +466,19 @@
 		writer.print("content");
 		writer.close();
 
-		Git git = new Git(db);
-		DirCache dc = git.add().addFilepattern("a.txt").call();
+		try (Git git = new Git(db)) {
+			DirCache dc = git.add().addFilepattern("a.txt").call();
 
-		dc.getEntry(0).getObjectId();
-		FileUtils.delete(file);
+			dc.getEntry(0).getObjectId();
+			FileUtils.delete(file);
 
-		// is supposed to do nothing
-		dc = git.add().addFilepattern("a.txt").call();
+			// is supposed to do nothing
+			dc = git.add().addFilepattern("a.txt").call();
 
-		assertEquals(
-				"[a.txt, mode:100644, content:content]",
-				indexState(CONTENT));
+			assertEquals(
+					"[a.txt, mode:100644, content:content]",
+					indexState(CONTENT));
+		}
 	}
 
 	@Test
@@ -288,20 +489,21 @@
 		writer.print("content");
 		writer.close();
 
-		Git git = new Git(db);
-		DirCache dc = git.add().addFilepattern("a.txt").call();
+		try (Git git = new Git(db)) {
+			DirCache dc = git.add().addFilepattern("a.txt").call();
 
-		git.commit().setMessage("commit a.txt").call();
+			git.commit().setMessage("commit a.txt").call();
 
-		dc.getEntry(0).getObjectId();
-		FileUtils.delete(file);
+			dc.getEntry(0).getObjectId();
+			FileUtils.delete(file);
 
-		// is supposed to do nothing
-		dc = git.add().addFilepattern("a.txt").call();
+			// is supposed to do nothing
+			dc = git.add().addFilepattern("a.txt").call();
 
-		assertEquals(
-				"[a.txt, mode:100644, content:content]",
-				indexState(CONTENT));
+			assertEquals(
+					"[a.txt, mode:100644, content:content]",
+					indexState(CONTENT));
+		}
 	}
 
 	@Test
@@ -349,13 +551,14 @@
 
 		// now the test begins
 
-		Git git = new Git(db);
-		dc = git.add().addFilepattern("a.txt").call();
+		try (Git git = new Git(db)) {
+			dc = git.add().addFilepattern("a.txt").call();
 
-		assertEquals(
-				"[a.txt, mode:100644, content:our content]" +
-				"[b.txt, mode:100644, content:content b]",
-				indexState(CONTENT));
+			assertEquals(
+					"[a.txt, mode:100644, content:our content]" +
+					"[b.txt, mode:100644, content:content b]",
+					indexState(CONTENT));
+		}
 	}
 
 	@Test
@@ -372,12 +575,13 @@
 		writer.print("content b");
 		writer.close();
 
-		Git git = new Git(db);
-		git.add().addFilepattern("a.txt").addFilepattern("b.txt").call();
-		assertEquals(
-				"[a.txt, mode:100644, content:content]" +
-				"[b.txt, mode:100644, content:content b]",
-				indexState(CONTENT));
+		try (Git git = new Git(db)) {
+			git.add().addFilepattern("a.txt").addFilepattern("b.txt").call();
+			assertEquals(
+					"[a.txt, mode:100644, content:content]" +
+					"[b.txt, mode:100644, content:content b]",
+					indexState(CONTENT));
+		}
 	}
 
 	@Test
@@ -395,12 +599,13 @@
 		writer.print("content b");
 		writer.close();
 
-		Git git = new Git(db);
-		git.add().addFilepattern("sub").call();
-		assertEquals(
-				"[sub/a.txt, mode:100644, content:content]" +
-				"[sub/b.txt, mode:100644, content:content b]",
-				indexState(CONTENT));
+		try (Git git = new Git(db)) {
+			git.add().addFilepattern("sub").call();
+			assertEquals(
+					"[sub/a.txt, mode:100644, content:content]" +
+					"[sub/b.txt, mode:100644, content:content b]",
+					indexState(CONTENT));
+		}
 	}
 
 	@Test
@@ -424,12 +629,13 @@
 		writer.print("content b");
 		writer.close();
 
-		Git git = new Git(db);
-		git.add().addFilepattern("sub").call();
+		try (Git git = new Git(db)) {
+			git.add().addFilepattern("sub").call();
 
-		assertEquals(
-				"[sub/a.txt, mode:100644, content:content]",
-				indexState(CONTENT));
+			assertEquals(
+					"[sub/a.txt, mode:100644, content:content]",
+					indexState(CONTENT));
+		}
 	}
 
 	@Test
@@ -447,12 +653,13 @@
 		writer.print("content b");
 		writer.close();
 
-		Git git = new Git(db);
-		git.add().addFilepattern(".").call();
-		assertEquals(
-				"[sub/a.txt, mode:100644, content:content]" +
-				"[sub/b.txt, mode:100644, content:content b]",
-				indexState(CONTENT));
+		try (Git git = new Git(db)) {
+			git.add().addFilepattern(".").call();
+			assertEquals(
+					"[sub/a.txt, mode:100644, content:content]" +
+					"[sub/b.txt, mode:100644, content:content b]",
+					indexState(CONTENT));
+		}
 	}
 
 	// the same three cases as in testAddWithParameterUpdate
@@ -474,40 +681,41 @@
 		writer.print("content b");
 		writer.close();
 
-		Git git = new Git(db);
-		git.add().addFilepattern("sub").call();
+		try (Git git = new Git(db)) {
+			git.add().addFilepattern("sub").call();
 
-		assertEquals(
-				"[sub/a.txt, mode:100644, content:content]" +
-				"[sub/b.txt, mode:100644, content:content b]",
-				indexState(CONTENT));
+			assertEquals(
+					"[sub/a.txt, mode:100644, content:content]" +
+					"[sub/b.txt, mode:100644, content:content b]",
+					indexState(CONTENT));
 
-		git.commit().setMessage("commit").call();
+			git.commit().setMessage("commit").call();
 
-		// new unstaged file sub/c.txt
-		File file3 = new File(db.getWorkTree(), "sub/c.txt");
-		FileUtils.createNewFile(file3);
-		writer = new PrintWriter(file3);
-		writer.print("content c");
-		writer.close();
+			// new unstaged file sub/c.txt
+			File file3 = new File(db.getWorkTree(), "sub/c.txt");
+			FileUtils.createNewFile(file3);
+			writer = new PrintWriter(file3);
+			writer.print("content c");
+			writer.close();
 
-		// file sub/a.txt is modified
-		writer = new PrintWriter(file);
-		writer.print("modified content");
-		writer.close();
+			// file sub/a.txt is modified
+			writer = new PrintWriter(file);
+			writer.print("modified content");
+			writer.close();
 
-		// file sub/b.txt is deleted
-		FileUtils.delete(file2);
+			// file sub/b.txt is deleted
+			FileUtils.delete(file2);
 
-		git.add().addFilepattern("sub").call();
-		// change in sub/a.txt is staged
-		// deletion of sub/b.txt is not staged
-		// sub/c.txt is staged
-		assertEquals(
-				"[sub/a.txt, mode:100644, content:modified content]" +
-				"[sub/b.txt, mode:100644, content:content b]" +
-				"[sub/c.txt, mode:100644, content:content c]",
-				indexState(CONTENT));
+			git.add().addFilepattern("sub").call();
+			// change in sub/a.txt is staged
+			// deletion of sub/b.txt is not staged
+			// sub/c.txt is staged
+			assertEquals(
+					"[sub/a.txt, mode:100644, content:modified content]" +
+					"[sub/b.txt, mode:100644, content:content b]" +
+					"[sub/c.txt, mode:100644, content:content c]",
+					indexState(CONTENT));
+		}
 	}
 
 	// file a exists in workdir and in index -> added
@@ -528,71 +736,168 @@
 		writer.print("content b");
 		writer.close();
 
-		Git git = new Git(db);
-		git.add().addFilepattern("sub").call();
+		try (Git git = new Git(db)) {
+			git.add().addFilepattern("sub").call();
 
-		assertEquals(
-				"[sub/a.txt, mode:100644, content:content]" +
-				"[sub/b.txt, mode:100644, content:content b]",
-				indexState(CONTENT));
+			assertEquals(
+					"[sub/a.txt, mode:100644, content:content]" +
+					"[sub/b.txt, mode:100644, content:content b]",
+					indexState(CONTENT));
 
-		git.commit().setMessage("commit").call();
+			git.commit().setMessage("commit").call();
 
-		// new unstaged file sub/c.txt
-		File file3 = new File(db.getWorkTree(), "sub/c.txt");
-		FileUtils.createNewFile(file3);
-		writer = new PrintWriter(file3);
-		writer.print("content c");
-		writer.close();
+			// new unstaged file sub/c.txt
+			File file3 = new File(db.getWorkTree(), "sub/c.txt");
+			FileUtils.createNewFile(file3);
+			writer = new PrintWriter(file3);
+			writer.print("content c");
+			writer.close();
 
-		// file sub/a.txt is modified
-		writer = new PrintWriter(file);
-		writer.print("modified content");
-		writer.close();
+			// file sub/a.txt is modified
+			writer = new PrintWriter(file);
+			writer.print("modified content");
+			writer.close();
 
-		FileUtils.delete(file2);
+			FileUtils.delete(file2);
 
-		// change in sub/a.txt is staged
-		// deletion of sub/b.txt is staged
-		// sub/c.txt is not staged
-		git.add().addFilepattern("sub").setUpdate(true).call();
-		// change in sub/a.txt is staged
-		assertEquals(
-				"[sub/a.txt, mode:100644, content:modified content]",
-				indexState(CONTENT));
+			// change in sub/a.txt is staged
+			// deletion of sub/b.txt is staged
+			// sub/c.txt is not staged
+			git.add().addFilepattern("sub").setUpdate(true).call();
+			// change in sub/a.txt is staged
+			assertEquals(
+					"[sub/a.txt, mode:100644, content:modified content]",
+					indexState(CONTENT));
+		}
 	}
 
 	@Test
 	public void testAssumeUnchanged() throws Exception {
-		Git git = new Git(db);
-		String path = "a.txt";
-		writeTrashFile(path, "content");
-		git.add().addFilepattern(path).call();
-		String path2 = "b.txt";
-		writeTrashFile(path2, "content");
-		git.add().addFilepattern(path2).call();
-		git.commit().setMessage("commit").call();
-		assertEquals("[a.txt, mode:100644, content:"
-				+ "content, assume-unchanged:false]"
-				+ "[b.txt, mode:100644, content:content, "
-				+ "assume-unchanged:false]", indexState(CONTENT
-				| ASSUME_UNCHANGED));
-		assumeUnchanged(path2);
-		assertEquals("[a.txt, mode:100644, content:content, "
-				+ "assume-unchanged:false][b.txt, mode:100644, "
-				+ "content:content, assume-unchanged:true]", indexState(CONTENT
-				| ASSUME_UNCHANGED));
-		writeTrashFile(path, "more content");
-		writeTrashFile(path2, "more content");
+		try (Git git = new Git(db)) {
+			String path = "a.txt";
+			writeTrashFile(path, "content");
+			git.add().addFilepattern(path).call();
+			String path2 = "b.txt";
+			writeTrashFile(path2, "content");
+			git.add().addFilepattern(path2).call();
+			git.commit().setMessage("commit").call();
+			assertEquals("[a.txt, mode:100644, content:"
+					+ "content, assume-unchanged:false]"
+					+ "[b.txt, mode:100644, content:content, "
+					+ "assume-unchanged:false]", indexState(CONTENT
+					| ASSUME_UNCHANGED));
+			assumeUnchanged(path2);
+			assertEquals("[a.txt, mode:100644, content:content, "
+					+ "assume-unchanged:false][b.txt, mode:100644, "
+					+ "content:content, assume-unchanged:true]", indexState(CONTENT
+					| ASSUME_UNCHANGED));
+			writeTrashFile(path, "more content");
+			writeTrashFile(path2, "more content");
 
-		git.add().addFilepattern(".").call();
+			git.add().addFilepattern(".").call();
 
-		assertEquals("[a.txt, mode:100644, content:more content,"
-				+ " assume-unchanged:false][b.txt, mode:100644,"
- + "" + ""
-				+ " content:content, assume-unchanged:true]",
-				indexState(CONTENT
-				| ASSUME_UNCHANGED));
+			assertEquals("[a.txt, mode:100644, content:more content,"
+					+ " assume-unchanged:false][b.txt, mode:100644,"
+					+ " content:content, assume-unchanged:true]",
+					indexState(CONTENT
+					| ASSUME_UNCHANGED));
+		}
+	}
+
+	@Test
+	public void testReplaceFileWithDirectory()
+			throws IOException, NoFilepatternException, GitAPIException {
+		try (Git git = new Git(db)) {
+			writeTrashFile("df", "before replacement");
+			git.add().addFilepattern("df").call();
+			assertEquals("[df, mode:100644, content:before replacement]",
+					indexState(CONTENT));
+			FileUtils.delete(new File(db.getWorkTree(), "df"));
+			writeTrashFile("df/f", "after replacement");
+			git.add().addFilepattern("df").call();
+			assertEquals("[df/f, mode:100644, content:after replacement]",
+					indexState(CONTENT));
+		}
+	}
+
+	@Test
+	public void testReplaceDirectoryWithFile()
+			throws IOException, NoFilepatternException, GitAPIException {
+		try (Git git = new Git(db)) {
+			writeTrashFile("df/f", "before replacement");
+			git.add().addFilepattern("df").call();
+			assertEquals("[df/f, mode:100644, content:before replacement]",
+					indexState(CONTENT));
+			FileUtils.delete(new File(db.getWorkTree(), "df"), RECURSIVE);
+			writeTrashFile("df", "after replacement");
+			git.add().addFilepattern("df").call();
+			assertEquals("[df, mode:100644, content:after replacement]",
+					indexState(CONTENT));
+		}
+	}
+
+	@Test
+	public void testReplaceFileByPartOfDirectory()
+			throws IOException, NoFilepatternException, GitAPIException {
+		try (Git git = new Git(db)) {
+			writeTrashFile("src/main", "df", "before replacement");
+			writeTrashFile("src/main", "z", "z");
+			writeTrashFile("z", "z2");
+			git.add().addFilepattern("src/main/df")
+				.addFilepattern("src/main/z")
+				.addFilepattern("z")
+				.call();
+			assertEquals(
+					"[src/main/df, mode:100644, content:before replacement]" +
+					"[src/main/z, mode:100644, content:z]" +
+					"[z, mode:100644, content:z2]",
+					indexState(CONTENT));
+			FileUtils.delete(new File(db.getWorkTree(), "src/main/df"));
+			writeTrashFile("src/main/df", "a", "after replacement");
+			writeTrashFile("src/main/df", "b", "unrelated file");
+			git.add().addFilepattern("src/main/df/a").call();
+			assertEquals(
+					"[src/main/df/a, mode:100644, content:after replacement]" +
+					"[src/main/z, mode:100644, content:z]" +
+					"[z, mode:100644, content:z2]",
+					indexState(CONTENT));
+		}
+	}
+
+	@Test
+	public void testReplaceDirectoryConflictsWithFile()
+			throws IOException, NoFilepatternException, GitAPIException {
+		DirCache dc = db.lockDirCache();
+		try (ObjectInserter oi = db.newObjectInserter()) {
+			DirCacheBuilder builder = dc.builder();
+			File f = writeTrashFile("a", "df", "content");
+			addEntryToBuilder("a", f, oi, builder, 1);
+
+			f = writeTrashFile("a", "df", "other content");
+			addEntryToBuilder("a/df", f, oi, builder, 3);
+
+			f = writeTrashFile("a", "df", "our content");
+			addEntryToBuilder("a/df", f, oi, builder, 2);
+
+			f = writeTrashFile("z", "z");
+			addEntryToBuilder("z", f, oi, builder, 0);
+			builder.commit();
+		}
+		assertEquals(
+				"[a, mode:100644, stage:1, content:content]" +
+				"[a/df, mode:100644, stage:2, content:our content]" +
+				"[a/df, mode:100644, stage:3, content:other content]" +
+				"[z, mode:100644, content:z]",
+				indexState(CONTENT));
+
+		try (Git git = new Git(db)) {
+			FileUtils.delete(new File(db.getWorkTree(), "a"), RECURSIVE);
+			writeTrashFile("a", "merged");
+			git.add().addFilepattern("a").call();
+			assertEquals("[a, mode:100644, content:merged]" +
+					"[z, mode:100644, content:z]",
+					indexState(CONTENT));
+		}
 	}
 
 	@Test
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/ApplyCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/ApplyCommandTest.java
index ad3ff60..d842046 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/ApplyCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/ApplyCommandTest.java
@@ -69,23 +69,24 @@
 
 	private ApplyResult init(final String name, final boolean preExists,
 			final boolean postExists) throws Exception {
-		Git git = new Git(db);
+		try (Git git = new Git(db)) {
+			if (preExists) {
+				a = new RawText(readFile(name + "_PreImage"));
+				write(new File(db.getDirectory().getParent(), name),
+						a.getString(0, a.size(), false));
 
-		if (preExists) {
-			a = new RawText(readFile(name + "_PreImage"));
-			write(new File(db.getDirectory().getParent(), name),
-					a.getString(0, a.size(), false));
+				git.add().addFilepattern(name).call();
+				git.commit().setMessage("PreImage").call();
+			}
 
-			git.add().addFilepattern(name).call();
-			git.commit().setMessage("PreImage").call();
+			if (postExists) {
+				b = new RawText(readFile(name + "_PostImage"));
+			}
+
+			return git
+					.apply()
+					.setPatch(getTestResource(name + ".patch")).call();
 		}
-
-		if (postExists)
-			b = new RawText(readFile(name + "_PostImage"));
-
-		return git
-				.apply()
-				.setPatch(getTestResource(name + ".patch")).call();
 	}
 
 	@Test
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/ArchiveCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/ArchiveCommandTest.java
index 44e7088..fc8df42 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/ArchiveCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/ArchiveCommandTest.java
@@ -85,103 +85,107 @@
 
 	@Test
 	public void archiveHeadAllFiles() throws IOException, GitAPIException {
-		Git git = new Git(db);
-		writeTrashFile("file_1.txt", "content_1_1");
-		git.add().addFilepattern("file_1.txt").call();
-		git.commit().setMessage("create file").call();
+		try (Git git = new Git(db)) {
+			writeTrashFile("file_1.txt", "content_1_1");
+			git.add().addFilepattern("file_1.txt").call();
+			git.commit().setMessage("create file").call();
 
-		writeTrashFile("file_1.txt", "content_1_2");
-		writeTrashFile("file_2.txt", "content_2_2");
-		git.add().addFilepattern(".").call();
-		git.commit().setMessage("updated file").call();
+			writeTrashFile("file_1.txt", "content_1_2");
+			writeTrashFile("file_2.txt", "content_2_2");
+			git.add().addFilepattern(".").call();
+			git.commit().setMessage("updated file").call();
 
-		git.archive().setOutputStream(new MockOutputStream())
-				.setFormat(format.SUFFIXES.get(0))
-				.setTree(git.getRepository().resolve("HEAD")).call();
+			git.archive().setOutputStream(new MockOutputStream())
+					.setFormat(format.SUFFIXES.get(0))
+					.setTree(git.getRepository().resolve("HEAD")).call();
 
-		assertEquals(UNEXPECTED_ARCHIVE_SIZE, 2, format.size());
-		assertEquals(UNEXPECTED_FILE_CONTENTS, "content_1_2", format.getByPath("file_1.txt"));
-		assertEquals(UNEXPECTED_FILE_CONTENTS, "content_2_2", format.getByPath("file_2.txt"));
+			assertEquals(UNEXPECTED_ARCHIVE_SIZE, 2, format.size());
+			assertEquals(UNEXPECTED_FILE_CONTENTS, "content_1_2", format.getByPath("file_1.txt"));
+			assertEquals(UNEXPECTED_FILE_CONTENTS, "content_2_2", format.getByPath("file_2.txt"));
+		}
 	}
 
 	@Test
 	public void archiveHeadSpecificPath() throws IOException, GitAPIException {
-		Git git = new Git(db);
-		writeTrashFile("file_1.txt", "content_1_1");
-		git.add().addFilepattern("file_1.txt").call();
-		git.commit().setMessage("create file").call();
+		try (Git git = new Git(db)) {
+			writeTrashFile("file_1.txt", "content_1_1");
+			git.add().addFilepattern("file_1.txt").call();
+			git.commit().setMessage("create file").call();
 
-		writeTrashFile("file_1.txt", "content_1_2");
-		String expectedFilePath = "some_directory/file_2.txt";
-		writeTrashFile(expectedFilePath, "content_2_2");
-		git.add().addFilepattern(".").call();
-		git.commit().setMessage("updated file").call();
+			writeTrashFile("file_1.txt", "content_1_2");
+			String expectedFilePath = "some_directory/file_2.txt";
+			writeTrashFile(expectedFilePath, "content_2_2");
+			git.add().addFilepattern(".").call();
+			git.commit().setMessage("updated file").call();
 
-		git.archive().setOutputStream(new MockOutputStream())
-				.setFormat(format.SUFFIXES.get(0))
-				.setTree(git.getRepository().resolve("HEAD"))
-				.setPaths(expectedFilePath).call();
+			git.archive().setOutputStream(new MockOutputStream())
+					.setFormat(format.SUFFIXES.get(0))
+					.setTree(git.getRepository().resolve("HEAD"))
+					.setPaths(expectedFilePath).call();
 
-		assertEquals(UNEXPECTED_ARCHIVE_SIZE, 2, format.size());
-		assertEquals(UNEXPECTED_FILE_CONTENTS, "content_2_2", format.getByPath(expectedFilePath));
-		assertNull(UNEXPECTED_TREE_CONTENTS, format.getByPath("some_directory"));
+			assertEquals(UNEXPECTED_ARCHIVE_SIZE, 2, format.size());
+			assertEquals(UNEXPECTED_FILE_CONTENTS, "content_2_2", format.getByPath(expectedFilePath));
+			assertNull(UNEXPECTED_TREE_CONTENTS, format.getByPath("some_directory"));
+		}
 	}
 
 	@Test
 	public void archiveByIdSpecificFile() throws IOException, GitAPIException {
-		Git git = new Git(db);
-		writeTrashFile("file_1.txt", "content_1_1");
-		git.add().addFilepattern("file_1.txt").call();
-		RevCommit first = git.commit().setMessage("create file").call();
+		try (Git git = new Git(db)) {
+			writeTrashFile("file_1.txt", "content_1_1");
+			git.add().addFilepattern("file_1.txt").call();
+			RevCommit first = git.commit().setMessage("create file").call();
 
-		writeTrashFile("file_1.txt", "content_1_2");
-		String expectedFilePath = "some_directory/file_2.txt";
-		writeTrashFile(expectedFilePath, "content_2_2");
-		git.add().addFilepattern(".").call();
-		git.commit().setMessage("updated file").call();
+			writeTrashFile("file_1.txt", "content_1_2");
+			String expectedFilePath = "some_directory/file_2.txt";
+			writeTrashFile(expectedFilePath, "content_2_2");
+			git.add().addFilepattern(".").call();
+			git.commit().setMessage("updated file").call();
 
-		Map<String, Object> options = new HashMap<>();
-		Integer opt = Integer.valueOf(42);
-		options.put("foo", opt);
-		MockOutputStream out = new MockOutputStream();
-		git.archive().setOutputStream(out)
-				.setFormat(format.SUFFIXES.get(0))
-				.setFormatOptions(options)
-				.setTree(first)
-				.setPaths("file_1.txt").call();
+			Map<String, Object> options = new HashMap<>();
+			Integer opt = Integer.valueOf(42);
+			options.put("foo", opt);
+			MockOutputStream out = new MockOutputStream();
+			git.archive().setOutputStream(out)
+					.setFormat(format.SUFFIXES.get(0))
+					.setFormatOptions(options)
+					.setTree(first)
+					.setPaths("file_1.txt").call();
 
-		assertEquals(opt.intValue(), out.getFoo());
-		assertEquals(UNEXPECTED_ARCHIVE_SIZE, 1, format.size());
-		assertEquals(UNEXPECTED_FILE_CONTENTS, "content_1_1", format.getByPath("file_1.txt"));
+			assertEquals(opt.intValue(), out.getFoo());
+			assertEquals(UNEXPECTED_ARCHIVE_SIZE, 1, format.size());
+			assertEquals(UNEXPECTED_FILE_CONTENTS, "content_1_1", format.getByPath("file_1.txt"));
+		}
 	}
 
 	@Test
 	public void archiveByDirectoryPath() throws GitAPIException, IOException {
-		Git git = new Git(db);
-		writeTrashFile("file_0.txt", "content_0_1");
-		git.add().addFilepattern("file_0.txt").call();
-		git.commit().setMessage("commit_1").call();
+		try (Git git = new Git(db)) {
+			writeTrashFile("file_0.txt", "content_0_1");
+			git.add().addFilepattern("file_0.txt").call();
+			git.commit().setMessage("commit_1").call();
 
-		writeTrashFile("file_0.txt", "content_0_2");
-		String expectedFilePath1 = "some_directory/file_1.txt";
-		writeTrashFile(expectedFilePath1, "content_1_2");
-		String expectedFilePath2 = "some_directory/file_2.txt";
-		writeTrashFile(expectedFilePath2, "content_2_2");
-	        String expectedFilePath3 = "some_directory/nested_directory/file_3.txt";
-		writeTrashFile(expectedFilePath3, "content_3_2");
-		git.add().addFilepattern(".").call();
-		git.commit().setMessage("commit_2").call();
-		git.archive().setOutputStream(new MockOutputStream())
-				.setFormat(format.SUFFIXES.get(0))
-				.setTree(git.getRepository().resolve("HEAD"))
-				.setPaths("some_directory/").call();
+			writeTrashFile("file_0.txt", "content_0_2");
+			String expectedFilePath1 = "some_directory/file_1.txt";
+			writeTrashFile(expectedFilePath1, "content_1_2");
+			String expectedFilePath2 = "some_directory/file_2.txt";
+			writeTrashFile(expectedFilePath2, "content_2_2");
+		        String expectedFilePath3 = "some_directory/nested_directory/file_3.txt";
+			writeTrashFile(expectedFilePath3, "content_3_2");
+			git.add().addFilepattern(".").call();
+			git.commit().setMessage("commit_2").call();
+			git.archive().setOutputStream(new MockOutputStream())
+					.setFormat(format.SUFFIXES.get(0))
+					.setTree(git.getRepository().resolve("HEAD"))
+					.setPaths("some_directory/").call();
 
-		assertEquals(UNEXPECTED_ARCHIVE_SIZE, 5, format.size());
-		assertEquals(UNEXPECTED_FILE_CONTENTS, "content_1_2", format.getByPath(expectedFilePath1));
-		assertEquals(UNEXPECTED_FILE_CONTENTS, "content_2_2", format.getByPath(expectedFilePath2));
-		assertEquals(UNEXPECTED_FILE_CONTENTS, "content_3_2", format.getByPath(expectedFilePath3));
-		assertNull(UNEXPECTED_TREE_CONTENTS, format.getByPath("some_directory"));
-		assertNull(UNEXPECTED_TREE_CONTENTS, format.getByPath("some_directory/nested_directory"));
+			assertEquals(UNEXPECTED_ARCHIVE_SIZE, 5, format.size());
+			assertEquals(UNEXPECTED_FILE_CONTENTS, "content_1_2", format.getByPath(expectedFilePath1));
+			assertEquals(UNEXPECTED_FILE_CONTENTS, "content_2_2", format.getByPath(expectedFilePath2));
+			assertEquals(UNEXPECTED_FILE_CONTENTS, "content_3_2", format.getByPath(expectedFilePath3));
+			assertNull(UNEXPECTED_TREE_CONTENTS, format.getByPath("some_directory"));
+			assertNull(UNEXPECTED_TREE_CONTENTS, format.getByPath("some_directory/nested_directory"));
+		}
 	}
 
 	private class MockFormat implements ArchiveCommand.Format<MockOutputStream> {
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/BlameCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/BlameCommandTest.java
index 0745eb6..f37aa13 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/BlameCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/BlameCommandTest.java
@@ -72,53 +72,53 @@
 
 	@Test
 	public void testSingleRevision() throws Exception {
-		Git git = new Git(db);
+		try (Git git = new Git(db)) {
+			String[] content = new String[] { "first", "second", "third" };
 
-		String[] content = new String[] { "first", "second", "third" };
+			writeTrashFile("file.txt", join(content));
+			git.add().addFilepattern("file.txt").call();
+			RevCommit commit = git.commit().setMessage("create file").call();
 
-		writeTrashFile("file.txt", join(content));
-		git.add().addFilepattern("file.txt").call();
-		RevCommit commit = git.commit().setMessage("create file").call();
+			BlameCommand command = new BlameCommand(db);
+			command.setFilePath("file.txt");
+			BlameResult lines = command.call();
+			assertNotNull(lines);
+			assertEquals(3, lines.getResultContents().size());
 
-		BlameCommand command = new BlameCommand(db);
-		command.setFilePath("file.txt");
-		BlameResult lines = command.call();
-		assertNotNull(lines);
-		assertEquals(3, lines.getResultContents().size());
-
-		for (int i = 0; i < 3; i++) {
-			assertEquals(commit, lines.getSourceCommit(i));
-			assertEquals(i, lines.getSourceLine(i));
+			for (int i = 0; i < 3; i++) {
+				assertEquals(commit, lines.getSourceCommit(i));
+				assertEquals(i, lines.getSourceLine(i));
+			}
 		}
 	}
 
 	@Test
 	public void testTwoRevisions() throws Exception {
-		Git git = new Git(db);
+		try (Git git = new Git(db)) {
+			String[] content1 = new String[] { "first", "second" };
+			writeTrashFile("file.txt", join(content1));
+			git.add().addFilepattern("file.txt").call();
+			RevCommit commit1 = git.commit().setMessage("create file").call();
 
-		String[] content1 = new String[] { "first", "second" };
-		writeTrashFile("file.txt", join(content1));
-		git.add().addFilepattern("file.txt").call();
-		RevCommit commit1 = git.commit().setMessage("create file").call();
+			String[] content2 = new String[] { "first", "second", "third" };
+			writeTrashFile("file.txt", join(content2));
+			git.add().addFilepattern("file.txt").call();
+			RevCommit commit2 = git.commit().setMessage("create file").call();
 
-		String[] content2 = new String[] { "first", "second", "third" };
-		writeTrashFile("file.txt", join(content2));
-		git.add().addFilepattern("file.txt").call();
-		RevCommit commit2 = git.commit().setMessage("create file").call();
+			BlameCommand command = new BlameCommand(db);
+			command.setFilePath("file.txt");
+			BlameResult lines = command.call();
+			assertEquals(3, lines.getResultContents().size());
 
-		BlameCommand command = new BlameCommand(db);
-		command.setFilePath("file.txt");
-		BlameResult lines = command.call();
-		assertEquals(3, lines.getResultContents().size());
+			assertEquals(commit1, lines.getSourceCommit(0));
+			assertEquals(0, lines.getSourceLine(0));
 
-		assertEquals(commit1, lines.getSourceCommit(0));
-		assertEquals(0, lines.getSourceLine(0));
+			assertEquals(commit1, lines.getSourceCommit(1));
+			assertEquals(1, lines.getSourceLine(1));
 
-		assertEquals(commit1, lines.getSourceCommit(1));
-		assertEquals(1, lines.getSourceLine(1));
-
-		assertEquals(commit2, lines.getSourceCommit(2));
-		assertEquals(2, lines.getSourceLine(2));
+			assertEquals(commit2, lines.getSourceCommit(2));
+			assertEquals(2, lines.getSourceLine(2));
+		}
 	}
 
 	@Test
@@ -138,200 +138,200 @@
 
 	private void testRename(final String sourcePath, final String destPath)
 			throws Exception {
-		Git git = new Git(db);
+		try (Git git = new Git(db)) {
+			String[] content1 = new String[] { "a", "b", "c" };
+			writeTrashFile(sourcePath, join(content1));
+			git.add().addFilepattern(sourcePath).call();
+			RevCommit commit1 = git.commit().setMessage("create file").call();
 
-		String[] content1 = new String[] { "a", "b", "c" };
-		writeTrashFile(sourcePath, join(content1));
-		git.add().addFilepattern(sourcePath).call();
-		RevCommit commit1 = git.commit().setMessage("create file").call();
+			writeTrashFile(destPath, join(content1));
+			git.add().addFilepattern(destPath).call();
+			git.rm().addFilepattern(sourcePath).call();
+			git.commit().setMessage("moving file").call();
 
-		writeTrashFile(destPath, join(content1));
-		git.add().addFilepattern(destPath).call();
-		git.rm().addFilepattern(sourcePath).call();
-		git.commit().setMessage("moving file").call();
+			String[] content2 = new String[] { "a", "b", "c2" };
+			writeTrashFile(destPath, join(content2));
+			git.add().addFilepattern(destPath).call();
+			RevCommit commit3 = git.commit().setMessage("editing file").call();
 
-		String[] content2 = new String[] { "a", "b", "c2" };
-		writeTrashFile(destPath, join(content2));
-		git.add().addFilepattern(destPath).call();
-		RevCommit commit3 = git.commit().setMessage("editing file").call();
+			BlameCommand command = new BlameCommand(db);
+			command.setFollowFileRenames(true);
+			command.setFilePath(destPath);
+			BlameResult lines = command.call();
 
-		BlameCommand command = new BlameCommand(db);
-		command.setFollowFileRenames(true);
-		command.setFilePath(destPath);
-		BlameResult lines = command.call();
+			assertEquals(commit1, lines.getSourceCommit(0));
+			assertEquals(0, lines.getSourceLine(0));
+			assertEquals(sourcePath, lines.getSourcePath(0));
 
-		assertEquals(commit1, lines.getSourceCommit(0));
-		assertEquals(0, lines.getSourceLine(0));
-		assertEquals(sourcePath, lines.getSourcePath(0));
+			assertEquals(commit1, lines.getSourceCommit(1));
+			assertEquals(1, lines.getSourceLine(1));
+			assertEquals(sourcePath, lines.getSourcePath(1));
 
-		assertEquals(commit1, lines.getSourceCommit(1));
-		assertEquals(1, lines.getSourceLine(1));
-		assertEquals(sourcePath, lines.getSourcePath(1));
-
-		assertEquals(commit3, lines.getSourceCommit(2));
-		assertEquals(2, lines.getSourceLine(2));
-		assertEquals(destPath, lines.getSourcePath(2));
+			assertEquals(commit3, lines.getSourceCommit(2));
+			assertEquals(2, lines.getSourceLine(2));
+			assertEquals(destPath, lines.getSourcePath(2));
+		}
 	}
 
 	@Test
 	public void testTwoRenames() throws Exception {
-		Git git = new Git(db);
+		try (Git git = new Git(db)) {
+			// Commit 1: Add file.txt
+			String[] content1 = new String[] { "a" };
+			writeTrashFile("file.txt", join(content1));
+			git.add().addFilepattern("file.txt").call();
+			RevCommit commit1 = git.commit().setMessage("create file").call();
 
-		// Commit 1: Add file.txt
-		String[] content1 = new String[] { "a" };
-		writeTrashFile("file.txt", join(content1));
-		git.add().addFilepattern("file.txt").call();
-		RevCommit commit1 = git.commit().setMessage("create file").call();
+			// Commit 2: Rename to file1.txt
+			writeTrashFile("file1.txt", join(content1));
+			git.add().addFilepattern("file1.txt").call();
+			git.rm().addFilepattern("file.txt").call();
+			git.commit().setMessage("moving file").call();
 
-		// Commit 2: Rename to file1.txt
-		writeTrashFile("file1.txt", join(content1));
-		git.add().addFilepattern("file1.txt").call();
-		git.rm().addFilepattern("file.txt").call();
-		git.commit().setMessage("moving file").call();
+			// Commit 3: Edit file1.txt
+			String[] content2 = new String[] { "a", "b" };
+			writeTrashFile("file1.txt", join(content2));
+			git.add().addFilepattern("file1.txt").call();
+			RevCommit commit3 = git.commit().setMessage("editing file").call();
 
-		// Commit 3: Edit file1.txt
-		String[] content2 = new String[] { "a", "b" };
-		writeTrashFile("file1.txt", join(content2));
-		git.add().addFilepattern("file1.txt").call();
-		RevCommit commit3 = git.commit().setMessage("editing file").call();
+			// Commit 4: Rename to file2.txt
+			writeTrashFile("file2.txt", join(content2));
+			git.add().addFilepattern("file2.txt").call();
+			git.rm().addFilepattern("file1.txt").call();
+			git.commit().setMessage("moving file again").call();
 
-		// Commit 4: Rename to file2.txt
-		writeTrashFile("file2.txt", join(content2));
-		git.add().addFilepattern("file2.txt").call();
-		git.rm().addFilepattern("file1.txt").call();
-		git.commit().setMessage("moving file again").call();
+			BlameCommand command = new BlameCommand(db);
+			command.setFollowFileRenames(true);
+			command.setFilePath("file2.txt");
+			BlameResult lines = command.call();
 
-		BlameCommand command = new BlameCommand(db);
-		command.setFollowFileRenames(true);
-		command.setFilePath("file2.txt");
-		BlameResult lines = command.call();
+			assertEquals(commit1, lines.getSourceCommit(0));
+			assertEquals(0, lines.getSourceLine(0));
+			assertEquals("file.txt", lines.getSourcePath(0));
 
-		assertEquals(commit1, lines.getSourceCommit(0));
-		assertEquals(0, lines.getSourceLine(0));
-		assertEquals("file.txt", lines.getSourcePath(0));
-
-		assertEquals(commit3, lines.getSourceCommit(1));
-		assertEquals(1, lines.getSourceLine(1));
-		assertEquals("file1.txt", lines.getSourcePath(1));
+			assertEquals(commit3, lines.getSourceCommit(1));
+			assertEquals(1, lines.getSourceLine(1));
+			assertEquals("file1.txt", lines.getSourcePath(1));
+		}
 	}
 
 	@Test
 	public void testDeleteTrailingLines() throws Exception {
-		Git git = new Git(db);
+		try (Git git = new Git(db)) {
+			String[] content1 = new String[] { "a", "b", "c", "d" };
+			String[] content2 = new String[] { "a", "b" };
 
-		String[] content1 = new String[] { "a", "b", "c", "d" };
-		String[] content2 = new String[] { "a", "b" };
+			writeTrashFile("file.txt", join(content2));
+			git.add().addFilepattern("file.txt").call();
+			RevCommit commit1 = git.commit().setMessage("create file").call();
 
-		writeTrashFile("file.txt", join(content2));
-		git.add().addFilepattern("file.txt").call();
-		RevCommit commit1 = git.commit().setMessage("create file").call();
+			writeTrashFile("file.txt", join(content1));
+			git.add().addFilepattern("file.txt").call();
+			git.commit().setMessage("edit file").call();
 
-		writeTrashFile("file.txt", join(content1));
-		git.add().addFilepattern("file.txt").call();
-		git.commit().setMessage("edit file").call();
+			writeTrashFile("file.txt", join(content2));
+			git.add().addFilepattern("file.txt").call();
+			git.commit().setMessage("edit file").call();
 
-		writeTrashFile("file.txt", join(content2));
-		git.add().addFilepattern("file.txt").call();
-		git.commit().setMessage("edit file").call();
+			BlameCommand command = new BlameCommand(db);
 
-		BlameCommand command = new BlameCommand(db);
+			command.setFilePath("file.txt");
+			BlameResult lines = command.call();
+			assertEquals(content2.length, lines.getResultContents().size());
 
-		command.setFilePath("file.txt");
-		BlameResult lines = command.call();
-		assertEquals(content2.length, lines.getResultContents().size());
+			assertEquals(commit1, lines.getSourceCommit(0));
+			assertEquals(commit1, lines.getSourceCommit(1));
 
-		assertEquals(commit1, lines.getSourceCommit(0));
-		assertEquals(commit1, lines.getSourceCommit(1));
-
-		assertEquals(0, lines.getSourceLine(0));
-		assertEquals(1, lines.getSourceLine(1));
+			assertEquals(0, lines.getSourceLine(0));
+			assertEquals(1, lines.getSourceLine(1));
+		}
 	}
 
 	@Test
 	public void testDeleteMiddleLines() throws Exception {
-		Git git = new Git(db);
+		try (Git git = new Git(db)) {
+			String[] content1 = new String[] { "a", "b", "c", "d", "e" };
+			String[] content2 = new String[] { "a", "c", "e" };
 
-		String[] content1 = new String[] { "a", "b", "c", "d", "e" };
-		String[] content2 = new String[] { "a", "c", "e" };
+			writeTrashFile("file.txt", join(content2));
+			git.add().addFilepattern("file.txt").call();
+			RevCommit commit1 = git.commit().setMessage("edit file").call();
 
-		writeTrashFile("file.txt", join(content2));
-		git.add().addFilepattern("file.txt").call();
-		RevCommit commit1 = git.commit().setMessage("edit file").call();
+			writeTrashFile("file.txt", join(content1));
+			git.add().addFilepattern("file.txt").call();
+			git.commit().setMessage("edit file").call();
 
-		writeTrashFile("file.txt", join(content1));
-		git.add().addFilepattern("file.txt").call();
-		git.commit().setMessage("edit file").call();
+			writeTrashFile("file.txt", join(content2));
+			git.add().addFilepattern("file.txt").call();
+			git.commit().setMessage("edit file").call();
 
-		writeTrashFile("file.txt", join(content2));
-		git.add().addFilepattern("file.txt").call();
-		git.commit().setMessage("edit file").call();
+			BlameCommand command = new BlameCommand(db);
 
-		BlameCommand command = new BlameCommand(db);
+			command.setFilePath("file.txt");
+			BlameResult lines = command.call();
+			assertEquals(content2.length, lines.getResultContents().size());
 
-		command.setFilePath("file.txt");
-		BlameResult lines = command.call();
-		assertEquals(content2.length, lines.getResultContents().size());
+			assertEquals(commit1, lines.getSourceCommit(0));
+			assertEquals(0, lines.getSourceLine(0));
 
-		assertEquals(commit1, lines.getSourceCommit(0));
-		assertEquals(0, lines.getSourceLine(0));
+			assertEquals(commit1, lines.getSourceCommit(1));
+			assertEquals(1, lines.getSourceLine(1));
 
-		assertEquals(commit1, lines.getSourceCommit(1));
-		assertEquals(1, lines.getSourceLine(1));
-
-		assertEquals(commit1, lines.getSourceCommit(2));
-		assertEquals(2, lines.getSourceLine(2));
+			assertEquals(commit1, lines.getSourceCommit(2));
+			assertEquals(2, lines.getSourceLine(2));
+		}
 	}
 
 	@Test
 	public void testEditAllLines() throws Exception {
-		Git git = new Git(db);
+		try (Git git = new Git(db)) {
+			String[] content1 = new String[] { "a", "1" };
+			String[] content2 = new String[] { "b", "2" };
 
-		String[] content1 = new String[] { "a", "1" };
-		String[] content2 = new String[] { "b", "2" };
+			writeTrashFile("file.txt", join(content1));
+			git.add().addFilepattern("file.txt").call();
+			git.commit().setMessage("edit file").call();
 
-		writeTrashFile("file.txt", join(content1));
-		git.add().addFilepattern("file.txt").call();
-		git.commit().setMessage("edit file").call();
+			writeTrashFile("file.txt", join(content2));
+			git.add().addFilepattern("file.txt").call();
+			RevCommit commit2 = git.commit().setMessage("create file").call();
 
-		writeTrashFile("file.txt", join(content2));
-		git.add().addFilepattern("file.txt").call();
-		RevCommit commit2 = git.commit().setMessage("create file").call();
+			BlameCommand command = new BlameCommand(db);
 
-		BlameCommand command = new BlameCommand(db);
-
-		command.setFilePath("file.txt");
-		BlameResult lines = command.call();
-		assertEquals(content2.length, lines.getResultContents().size());
-		assertEquals(commit2, lines.getSourceCommit(0));
-		assertEquals(commit2, lines.getSourceCommit(1));
+			command.setFilePath("file.txt");
+			BlameResult lines = command.call();
+			assertEquals(content2.length, lines.getResultContents().size());
+			assertEquals(commit2, lines.getSourceCommit(0));
+			assertEquals(commit2, lines.getSourceCommit(1));
+		}
 	}
 
 	@Test
 	public void testMiddleClearAllLines() throws Exception {
-		Git git = new Git(db);
+		try (Git git = new Git(db)) {
+			String[] content1 = new String[] { "a", "b", "c" };
 
-		String[] content1 = new String[] { "a", "b", "c" };
+			writeTrashFile("file.txt", join(content1));
+			git.add().addFilepattern("file.txt").call();
+			git.commit().setMessage("edit file").call();
 
-		writeTrashFile("file.txt", join(content1));
-		git.add().addFilepattern("file.txt").call();
-		git.commit().setMessage("edit file").call();
+			writeTrashFile("file.txt", "");
+			git.add().addFilepattern("file.txt").call();
+			git.commit().setMessage("create file").call();
 
-		writeTrashFile("file.txt", "");
-		git.add().addFilepattern("file.txt").call();
-		git.commit().setMessage("create file").call();
+			writeTrashFile("file.txt", join(content1));
+			git.add().addFilepattern("file.txt").call();
+			RevCommit commit3 = git.commit().setMessage("edit file").call();
 
-		writeTrashFile("file.txt", join(content1));
-		git.add().addFilepattern("file.txt").call();
-		RevCommit commit3 = git.commit().setMessage("edit file").call();
+			BlameCommand command = new BlameCommand(db);
 
-		BlameCommand command = new BlameCommand(db);
-
-		command.setFilePath("file.txt");
-		BlameResult lines = command.call();
-		assertEquals(content1.length, lines.getResultContents().size());
-		assertEquals(commit3, lines.getSourceCommit(0));
-		assertEquals(commit3, lines.getSourceCommit(1));
-		assertEquals(commit3, lines.getSourceCommit(2));
+			command.setFilePath("file.txt");
+			BlameResult lines = command.call();
+			assertEquals(content1.length, lines.getResultContents().size());
+			assertEquals(commit3, lines.getSourceCommit(0));
+			assertEquals(commit3, lines.getSourceCommit(1));
+			assertEquals(commit3, lines.getSourceCommit(2));
+		}
 	}
 
 	@Test
@@ -361,130 +361,132 @@
 
 	private void testCoreAutoCrlf(AutoCRLF modeForCommitting,
 			AutoCRLF modeForReset) throws Exception {
-		Git git = new Git(db);
-		FileBasedConfig config = db.getConfig();
-		config.setEnum(ConfigConstants.CONFIG_CORE_SECTION, null,
-				ConfigConstants.CONFIG_KEY_AUTOCRLF, modeForCommitting);
-		config.save();
+		try (Git git = new Git(db)) {
+			FileBasedConfig config = db.getConfig();
+			config.setEnum(ConfigConstants.CONFIG_CORE_SECTION, null,
+					ConfigConstants.CONFIG_KEY_AUTOCRLF, modeForCommitting);
+			config.save();
 
-		String joinedCrlf = "a\r\nb\r\nc\r\n";
-		File trashFile = writeTrashFile("file.txt", joinedCrlf);
-		git.add().addFilepattern("file.txt").call();
-		RevCommit commit = git.commit().setMessage("create file").call();
+			String joinedCrlf = "a\r\nb\r\nc\r\n";
+			File trashFile = writeTrashFile("file.txt", joinedCrlf);
+			git.add().addFilepattern("file.txt").call();
+			RevCommit commit = git.commit().setMessage("create file").call();
 
-		// re-create file from the repo
-		trashFile.delete();
-		config.setEnum(ConfigConstants.CONFIG_CORE_SECTION, null,
-				ConfigConstants.CONFIG_KEY_AUTOCRLF, modeForReset);
-		config.save();
-		git.reset().setMode(ResetType.HARD).call();
+			// re-create file from the repo
+			trashFile.delete();
+			config.setEnum(ConfigConstants.CONFIG_CORE_SECTION, null,
+					ConfigConstants.CONFIG_KEY_AUTOCRLF, modeForReset);
+			config.save();
+			git.reset().setMode(ResetType.HARD).call();
 
-		BlameCommand command = new BlameCommand(db);
-		command.setFilePath("file.txt");
-		BlameResult lines = command.call();
+			BlameCommand command = new BlameCommand(db);
+			command.setFilePath("file.txt");
+			BlameResult lines = command.call();
 
-		assertEquals(3, lines.getResultContents().size());
-		assertEquals(commit, lines.getSourceCommit(0));
-		assertEquals(commit, lines.getSourceCommit(1));
-		assertEquals(commit, lines.getSourceCommit(2));
+			assertEquals(3, lines.getResultContents().size());
+			assertEquals(commit, lines.getSourceCommit(0));
+			assertEquals(commit, lines.getSourceCommit(1));
+			assertEquals(commit, lines.getSourceCommit(2));
+		}
 	}
 
 	@Test
 	public void testConflictingMerge1() throws Exception {
-		Git git = new Git(db);
+		try (Git git = new Git(db)) {
+			RevCommit base = commitFile("file.txt", join("0", "1", "2", "3", "4"),
+					"master");
 
-		RevCommit base = commitFile("file.txt", join("0", "1", "2", "3", "4"),
-				"master");
+			git.checkout().setName("side").setCreateBranch(true)
+					.setStartPoint(base).call();
+			RevCommit side = commitFile("file.txt",
+					join("0", "1 side", "2", "3 on side", "4"), "side");
 
-		git.checkout().setName("side").setCreateBranch(true)
-				.setStartPoint(base).call();
-		RevCommit side = commitFile("file.txt",
-				join("0", "1 side", "2", "3 on side", "4"), "side");
+			commitFile("file.txt", join("0", "1", "2"), "master");
 
-		commitFile("file.txt", join("0", "1", "2"), "master");
+			checkoutBranch("refs/heads/master");
+			git.merge().include(side).call();
 
-		checkoutBranch("refs/heads/master");
-		git.merge().include(side).call();
+			// The merge results in a conflict, which we resolve using mostly the
+			// side branch contents. Especially the "4" survives.
+			RevCommit merge = commitFile("file.txt",
+					join("0", "1 side", "2", "3 resolved", "4"), "master");
 
-		// The merge results in a conflict, which we resolve using mostly the
-		// side branch contents. Especially the "4" survives.
-		RevCommit merge = commitFile("file.txt",
-				join("0", "1 side", "2", "3 resolved", "4"), "master");
+			BlameCommand command = new BlameCommand(db);
+			command.setFilePath("file.txt");
+			BlameResult lines = command.call();
 
-		BlameCommand command = new BlameCommand(db);
-		command.setFilePath("file.txt");
-		BlameResult lines = command.call();
-
-		assertEquals(5, lines.getResultContents().size());
-		assertEquals(base, lines.getSourceCommit(0));
-		assertEquals(side, lines.getSourceCommit(1));
-		assertEquals(base, lines.getSourceCommit(2));
-		assertEquals(merge, lines.getSourceCommit(3));
-		assertEquals(base, lines.getSourceCommit(4));
+			assertEquals(5, lines.getResultContents().size());
+			assertEquals(base, lines.getSourceCommit(0));
+			assertEquals(side, lines.getSourceCommit(1));
+			assertEquals(base, lines.getSourceCommit(2));
+			assertEquals(merge, lines.getSourceCommit(3));
+			assertEquals(base, lines.getSourceCommit(4));
+		}
 	}
 
 	// this test inverts the order of the master and side commit and is
 	// otherwise identical to testConflictingMerge1
 	@Test
 	public void testConflictingMerge2() throws Exception {
-		Git git = new Git(db);
+		try (Git git = new Git(db)) {
+			RevCommit base = commitFile("file.txt", join("0", "1", "2", "3", "4"),
+					"master");
 
-		RevCommit base = commitFile("file.txt", join("0", "1", "2", "3", "4"),
-				"master");
+			commitFile("file.txt", join("0", "1", "2"), "master");
 
-		commitFile("file.txt", join("0", "1", "2"), "master");
+			git.checkout().setName("side").setCreateBranch(true)
+					.setStartPoint(base).call();
+			RevCommit side = commitFile("file.txt",
+					join("0", "1 side", "2", "3 on side", "4"), "side");
 
-		git.checkout().setName("side").setCreateBranch(true)
-				.setStartPoint(base).call();
-		RevCommit side = commitFile("file.txt",
-				join("0", "1 side", "2", "3 on side", "4"), "side");
+			checkoutBranch("refs/heads/master");
+			git.merge().include(side).call();
 
-		checkoutBranch("refs/heads/master");
-		git.merge().include(side).call();
+			// The merge results in a conflict, which we resolve using mostly the
+			// side branch contents. Especially the "4" survives.
+			RevCommit merge = commitFile("file.txt",
+					join("0", "1 side", "2", "3 resolved", "4"), "master");
 
-		// The merge results in a conflict, which we resolve using mostly the
-		// side branch contents. Especially the "4" survives.
-		RevCommit merge = commitFile("file.txt",
-				join("0", "1 side", "2", "3 resolved", "4"), "master");
+			BlameCommand command = new BlameCommand(db);
+			command.setFilePath("file.txt");
+			BlameResult lines = command.call();
 
-		BlameCommand command = new BlameCommand(db);
-		command.setFilePath("file.txt");
-		BlameResult lines = command.call();
-
-		assertEquals(5, lines.getResultContents().size());
-		assertEquals(base, lines.getSourceCommit(0));
-		assertEquals(side, lines.getSourceCommit(1));
-		assertEquals(base, lines.getSourceCommit(2));
-		assertEquals(merge, lines.getSourceCommit(3));
-		assertEquals(base, lines.getSourceCommit(4));
+			assertEquals(5, lines.getResultContents().size());
+			assertEquals(base, lines.getSourceCommit(0));
+			assertEquals(side, lines.getSourceCommit(1));
+			assertEquals(base, lines.getSourceCommit(2));
+			assertEquals(merge, lines.getSourceCommit(3));
+			assertEquals(base, lines.getSourceCommit(4));
+		}
 	}
 
 	@Test
 	public void testWhitespaceMerge() throws Exception {
-		Git git = new Git(db);
-		RevCommit base = commitFile("file.txt", join("0", "1", "2"), "master");
-		RevCommit side = commitFile("file.txt", join("0", "1", "   2 side  "),
-				"side");
+		try (Git git = new Git(db)) {
+			RevCommit base = commitFile("file.txt", join("0", "1", "2"), "master");
+			RevCommit side = commitFile("file.txt", join("0", "1", "   2 side  "),
+					"side");
 
-		checkoutBranch("refs/heads/master");
-		git.merge().setFastForward(FastForwardMode.NO_FF).include(side).call();
+			checkoutBranch("refs/heads/master");
+			git.merge().setFastForward(FastForwardMode.NO_FF).include(side).call();
 
-		// change whitespace, so the merge content is not identical to side, but
-		// is the same when ignoring whitespace
-		writeTrashFile("file.txt", join("0", "1", "2 side"));
-		RevCommit merge = git.commit().setAll(true).setMessage("merge")
-				.setAmend(true)
-				.call();
+			// change whitespace, so the merge content is not identical to side, but
+			// is the same when ignoring whitespace
+			writeTrashFile("file.txt", join("0", "1", "2 side"));
+			RevCommit merge = git.commit().setAll(true).setMessage("merge")
+					.setAmend(true)
+					.call();
 
-		BlameCommand command = new BlameCommand(db);
-		command.setFilePath("file.txt")
-				.setTextComparator(RawTextComparator.WS_IGNORE_ALL)
-				.setStartCommit(merge.getId());
-		BlameResult lines = command.call();
+			BlameCommand command = new BlameCommand(db);
+			command.setFilePath("file.txt")
+					.setTextComparator(RawTextComparator.WS_IGNORE_ALL)
+					.setStartCommit(merge.getId());
+			BlameResult lines = command.call();
 
-		assertEquals(3, lines.getResultContents().size());
-		assertEquals(base, lines.getSourceCommit(0));
-		assertEquals(base, lines.getSourceCommit(1));
-		assertEquals(side, lines.getSourceCommit(2));
+			assertEquals(3, lines.getResultContents().size());
+			assertEquals(base, lines.getSourceCommit(0));
+			assertEquals(base, lines.getSourceCommit(1));
+			assertEquals(side, lines.getSourceCommit(2));
+		}
 	}
 }
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/BranchCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/BranchCommandTest.java
index 910a645..2fe40b9 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/BranchCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/BranchCommandTest.java
@@ -104,37 +104,38 @@
 
 	private Git setUpRepoWithRemote() throws Exception {
 		Repository remoteRepository = createWorkRepository();
-		Git remoteGit = new Git(remoteRepository);
-		// commit something
-		writeTrashFile("Test.txt", "Hello world");
-		remoteGit.add().addFilepattern("Test.txt").call();
-		initialCommit = remoteGit.commit().setMessage("Initial commit").call();
-		writeTrashFile("Test.txt", "Some change");
-		remoteGit.add().addFilepattern("Test.txt").call();
-		secondCommit = remoteGit.commit().setMessage("Second commit").call();
-		// create a master branch
-		RefUpdate rup = remoteRepository.updateRef("refs/heads/master");
-		rup.setNewObjectId(initialCommit.getId());
-		rup.forceUpdate();
+		try (Git remoteGit = new Git(remoteRepository)) {
+			// commit something
+			writeTrashFile("Test.txt", "Hello world");
+			remoteGit.add().addFilepattern("Test.txt").call();
+			initialCommit = remoteGit.commit().setMessage("Initial commit").call();
+			writeTrashFile("Test.txt", "Some change");
+			remoteGit.add().addFilepattern("Test.txt").call();
+			secondCommit = remoteGit.commit().setMessage("Second commit").call();
+			// create a master branch
+			RefUpdate rup = remoteRepository.updateRef("refs/heads/master");
+			rup.setNewObjectId(initialCommit.getId());
+			rup.forceUpdate();
 
-		Repository localRepository = createWorkRepository();
-		Git localGit = new Git(localRepository);
-		StoredConfig config = localRepository.getConfig();
-		RemoteConfig rc = new RemoteConfig(config, "origin");
-		rc.addURI(new URIish(remoteRepository.getDirectory().getAbsolutePath()));
-		rc.addFetchRefSpec(new RefSpec("+refs/heads/*:refs/remotes/origin/*"));
-		rc.update(config);
-		config.save();
-		FetchResult res = localGit.fetch().setRemote("origin").call();
-		assertFalse(res.getTrackingRefUpdates().isEmpty());
-		rup = localRepository.updateRef("refs/heads/master");
-		rup.setNewObjectId(initialCommit.getId());
-		rup.forceUpdate();
-		rup = localRepository.updateRef(Constants.HEAD);
-		rup.link("refs/heads/master");
-		rup.setNewObjectId(initialCommit.getId());
-		rup.update();
-		return localGit;
+			Repository localRepository = createWorkRepository();
+			Git localGit = new Git(localRepository);
+			StoredConfig config = localRepository.getConfig();
+			RemoteConfig rc = new RemoteConfig(config, "origin");
+			rc.addURI(new URIish(remoteRepository.getDirectory().getAbsolutePath()));
+			rc.addFetchRefSpec(new RefSpec("+refs/heads/*:refs/remotes/origin/*"));
+			rc.update(config);
+			config.save();
+			FetchResult res = localGit.fetch().setRemote("origin").call();
+			assertFalse(res.getTrackingRefUpdates().isEmpty());
+			rup = localRepository.updateRef("refs/heads/master");
+			rup.setNewObjectId(initialCommit.getId());
+			rup.forceUpdate();
+			rup = localRepository.updateRef(Constants.HEAD);
+			rup.link("refs/heads/master");
+			rup.setNewObjectId(initialCommit.getId());
+			rup.update();
+			return localGit;
+		}
 	}
 
 	@Test
@@ -192,8 +193,7 @@
 
 	@Test
 	public void testListAllBranchesShouldNotDie() throws Exception {
-		Git git = setUpRepoWithRemote();
-		git.branchList().setListMode(ListMode.ALL).call();
+		setUpRepoWithRemote().branchList().setListMode(ListMode.ALL).call();
 	}
 
 	@Test
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CheckoutCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CheckoutCommandTest.java
index f7a50df..362d7ac 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CheckoutCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CheckoutCommandTest.java
@@ -43,6 +43,8 @@
  */
 package org.eclipse.jgit.api;
 
+import static org.eclipse.jgit.lib.Constants.MASTER;
+import static org.eclipse.jgit.lib.Constants.R_HEADS;
 import static org.hamcrest.CoreMatchers.is;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
@@ -70,12 +72,14 @@
 import org.eclipse.jgit.api.errors.TransportException;
 import org.eclipse.jgit.dircache.DirCache;
 import org.eclipse.jgit.dircache.DirCacheEntry;
+import org.eclipse.jgit.junit.JGitTestUtil;
 import org.eclipse.jgit.junit.RepositoryTestCase;
 import org.eclipse.jgit.lib.ConfigConstants;
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.Ref;
 import org.eclipse.jgit.lib.RefUpdate;
 import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.lib.Sets;
 import org.eclipse.jgit.lib.StoredConfig;
 import org.eclipse.jgit.revwalk.RevCommit;
 import org.eclipse.jgit.storage.file.FileBasedConfig;
@@ -84,6 +88,7 @@
 import org.eclipse.jgit.transport.URIish;
 import org.eclipse.jgit.util.FileUtils;
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Test;
 
 public class CheckoutCommandTest extends RepositoryTestCase {
@@ -134,7 +139,7 @@
 	@Test
 	public void testCreateBranchOnCheckout() throws Exception {
 		git.checkout().setCreateBranch(true).setName("test2").call();
-		assertNotNull(db.getRef("test2"));
+		assertNotNull(db.exactRef("refs/heads/test2"));
 	}
 
 	@Test
@@ -237,8 +242,8 @@
 				.setStartPoint("origin/test")
 				.setUpstreamMode(SetupUpstreamMode.TRACK).call();
 
-		assertEquals("refs/heads/test", db2.getRef(Constants.HEAD).getTarget()
-				.getName());
+		assertEquals("refs/heads/test",
+				db2.exactRef(Constants.HEAD).getTarget().getName());
 		StoredConfig config = db2.getConfig();
 		assertEquals("origin", config.getString(
 				ConfigConstants.CONFIG_BRANCH_SECTION, "test",
@@ -345,7 +350,7 @@
 		CheckoutCommand co = git.checkout();
 		co.setName("master").call();
 
-		String commitId = db.getRef(Constants.MASTER).getObjectId().name();
+		String commitId = db.exactRef(R_HEADS + MASTER).getObjectId().name();
 		co = git.checkout();
 		co.setName(commitId).call();
 
@@ -412,20 +417,20 @@
 			InvalidRemoteException, TransportException {
 		// create second repository
 		Repository db2 = createWorkRepository();
-		Git git2 = new Git(db2);
+		try (Git git2 = new Git(db2)) {
+			// setup the second repository to fetch from the first repository
+			final StoredConfig config = db2.getConfig();
+			RemoteConfig remoteConfig = new RemoteConfig(config, "origin");
+			URIish uri = new URIish(db.getDirectory().toURI().toURL());
+			remoteConfig.addURI(uri);
+			remoteConfig.update(config);
+			config.save();
 
-		// setup the second repository to fetch from the first repository
-		final StoredConfig config = db2.getConfig();
-		RemoteConfig remoteConfig = new RemoteConfig(config, "origin");
-		URIish uri = new URIish(db.getDirectory().toURI().toURL());
-		remoteConfig.addURI(uri);
-		remoteConfig.update(config);
-		config.save();
-
-		// fetch from first repository
-		RefSpec spec = new RefSpec("+refs/heads/*:refs/remotes/origin/*");
-		git2.fetch().setRemote("origin").setRefSpecs(spec).call();
-		return db2;
+			// fetch from first repository
+			RefSpec spec = new RefSpec("+refs/heads/*:refs/remotes/origin/*");
+			git2.fetch().setRemote("origin").setRefSpecs(spec).call();
+			return db2;
+		}
 	}
 
 	private CheckoutCommand newOrphanBranchCommand() {
@@ -443,7 +448,7 @@
 	}
 
 	private void assertHeadDetached() throws IOException {
-		Ref head = db.getRef(Constants.HEAD);
+		Ref head = db.exactRef(Constants.HEAD);
 		assertFalse(head.isSymbolic());
 		assertSame(head, head.getTarget());
 	}
@@ -554,4 +559,126 @@
 		}
 		org.junit.Assume.assumeTrue(foundUnsmudged);
 	}
+
+	@Test
+	public void testSmudgeFilter_modifyExisting() throws IOException, GitAPIException {
+		File script = writeTempFile("sed s/o/e/g");
+		StoredConfig config = git.getRepository().getConfig();
+		config.setString("filter", "tstFilter", "smudge",
+				"sh " + slashify(script.getPath()));
+		config.save();
+
+		writeTrashFile(".gitattributes", "*.txt filter=tstFilter");
+		git.add().addFilepattern(".gitattributes").call();
+		git.commit().setMessage("add filter").call();
+
+		writeTrashFile("src/a.tmp", "x");
+		// Caution: we need a trailing '\n' since sed on mac always appends
+		// linefeeds if missing
+		writeTrashFile("src/a.txt", "x\n");
+		git.add().addFilepattern("src/a.tmp").addFilepattern("src/a.txt")
+				.call();
+		RevCommit content1 = git.commit().setMessage("add content").call();
+
+		writeTrashFile("src/a.tmp", "foo");
+		writeTrashFile("src/a.txt", "foo\n");
+		git.add().addFilepattern("src/a.tmp").addFilepattern("src/a.txt")
+				.call();
+		RevCommit content2 = git.commit().setMessage("changed content").call();
+
+		git.checkout().setName(content1.getName()).call();
+		git.checkout().setName(content2.getName()).call();
+
+		assertEquals(
+				"[.gitattributes, mode:100644, content:*.txt filter=tstFilter][Test.txt, mode:100644, content:Some change][src/a.tmp, mode:100644, content:foo][src/a.txt, mode:100644, content:foo\n]",
+				indexState(CONTENT));
+		assertEquals(Sets.of("src/a.txt"), git.status().call().getModified());
+		assertEquals("foo", read("src/a.tmp"));
+		assertEquals("fee\n", read("src/a.txt"));
+	}
+
+	@Test
+	public void testSmudgeFilter_createNew()
+			throws IOException, GitAPIException {
+		File script = writeTempFile("sed s/o/e/g");
+		StoredConfig config = git.getRepository().getConfig();
+		config.setString("filter", "tstFilter", "smudge",
+				"sh " + slashify(script.getPath()));
+		config.save();
+
+		writeTrashFile("foo", "foo");
+		git.add().addFilepattern("foo").call();
+		RevCommit initial = git.commit().setMessage("initial").call();
+
+		writeTrashFile(".gitattributes", "*.txt filter=tstFilter");
+		git.add().addFilepattern(".gitattributes").call();
+		git.commit().setMessage("add filter").call();
+
+		writeTrashFile("src/a.tmp", "foo");
+		// Caution: we need a trailing '\n' since sed on mac always appends
+		// linefeeds if missing
+		writeTrashFile("src/a.txt", "foo\n");
+		git.add().addFilepattern("src/a.tmp").addFilepattern("src/a.txt")
+				.call();
+		RevCommit content = git.commit().setMessage("added content").call();
+
+		git.checkout().setName(initial.getName()).call();
+		git.checkout().setName(content.getName()).call();
+
+		assertEquals(
+				"[.gitattributes, mode:100644, content:*.txt filter=tstFilter][Test.txt, mode:100644, content:Some change][foo, mode:100644, content:foo][src/a.tmp, mode:100644, content:foo][src/a.txt, mode:100644, content:foo\n]",
+				indexState(CONTENT));
+		assertEquals("foo", read("src/a.tmp"));
+		assertEquals("fee\n", read("src/a.txt"));
+	}
+
+	@Test
+	@Ignore
+	public void testSmudgeAndClean() throws IOException, GitAPIException {
+		// @TODO: fix this test
+		File clean_filter = writeTempFile("sed s/V1/@version/g -");
+		File smudge_filter = writeTempFile("sed s/@version/V1/g -");
+
+		try (Git git2 = new Git(db)) {
+			StoredConfig config = git.getRepository().getConfig();
+			config.setString("filter", "tstFilter", "smudge",
+					"sh " + slashify(smudge_filter.getPath()));
+			config.setString("filter", "tstFilter", "clean",
+					"sh " + slashify(clean_filter.getPath()));
+			config.save();
+			writeTrashFile(".gitattributes", "*.txt filter=tstFilter");
+			git2.add().addFilepattern(".gitattributes").call();
+			git2.commit().setMessage("add attributes").call();
+
+			writeTrashFile("filterTest.txt", "hello world, V1");
+			git2.add().addFilepattern("filterTest.txt").call();
+			git2.commit().setMessage("add filterText.txt").call();
+			assertEquals(
+					"[.gitattributes, mode:100644, content:*.txt filter=tstFilter][Test.txt, mode:100644, content:Some other change][filterTest.txt, mode:100644, content:hello world, @version]",
+					indexState(CONTENT));
+
+			git2.checkout().setCreateBranch(true).setName("test2").call();
+			writeTrashFile("filterTest.txt", "bon giorno world, V1");
+			git2.add().addFilepattern("filterTest.txt").call();
+			git2.commit().setMessage("modified filterText.txt").call();
+
+			assertTrue(git2.status().call().isClean());
+			assertEquals(
+					"[.gitattributes, mode:100644, content:*.txt filter=tstFilter][Test.txt, mode:100644, content:Some other change][filterTest.txt, mode:100644, content:bon giorno world, @version]",
+					indexState(CONTENT));
+
+			git2.checkout().setName("refs/heads/test").call();
+			assertTrue(git2.status().call().isClean());
+			assertEquals(
+					"[.gitattributes, mode:100644, content:*.txt filter=tstFilter][Test.txt, mode:100644, content:Some other change][filterTest.txt, mode:100644, content:hello world, @version]",
+					indexState(CONTENT));
+			assertEquals("hello world, V1", read("filterTest.txt"));
+		}
+	}
+
+	private File writeTempFile(String body) throws IOException {
+		File f = File.createTempFile("AddCommandTest_", "");
+		JGitTestUtil.write(f, body);
+		return f;
+	}
 }
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CherryPickCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CherryPickCommandTest.java
index a999337..354b9c6 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CherryPickCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CherryPickCommandTest.java
@@ -88,136 +88,139 @@
 	private void doTestCherryPick(boolean noCommit) throws IOException,
 			JGitInternalException,
 			GitAPIException {
-		Git git = new Git(db);
+		try (Git git = new Git(db)) {
+			writeTrashFile("a", "first line\nsec. line\nthird line\n");
+			git.add().addFilepattern("a").call();
+			RevCommit firstCommit = git.commit().setMessage("create a").call();
 
-		writeTrashFile("a", "first line\nsec. line\nthird line\n");
-		git.add().addFilepattern("a").call();
-		RevCommit firstCommit = git.commit().setMessage("create a").call();
+			writeTrashFile("b", "content\n");
+			git.add().addFilepattern("b").call();
+			git.commit().setMessage("create b").call();
 
-		writeTrashFile("b", "content\n");
-		git.add().addFilepattern("b").call();
-		git.commit().setMessage("create b").call();
+			writeTrashFile("a", "first line\nsec. line\nthird line\nfourth line\n");
+			git.add().addFilepattern("a").call();
+			git.commit().setMessage("enlarged a").call();
 
-		writeTrashFile("a", "first line\nsec. line\nthird line\nfourth line\n");
-		git.add().addFilepattern("a").call();
-		git.commit().setMessage("enlarged a").call();
+			writeTrashFile("a",
+					"first line\nsecond line\nthird line\nfourth line\n");
+			git.add().addFilepattern("a").call();
+			RevCommit fixingA = git.commit().setMessage("fixed a").call();
 
-		writeTrashFile("a",
-				"first line\nsecond line\nthird line\nfourth line\n");
-		git.add().addFilepattern("a").call();
-		RevCommit fixingA = git.commit().setMessage("fixed a").call();
+			git.branchCreate().setName("side").setStartPoint(firstCommit).call();
+			checkoutBranch("refs/heads/side");
 
-		git.branchCreate().setName("side").setStartPoint(firstCommit).call();
-		checkoutBranch("refs/heads/side");
+			writeTrashFile("a", "first line\nsec. line\nthird line\nfeature++\n");
+			git.add().addFilepattern("a").call();
+			git.commit().setMessage("enhanced a").call();
 
-		writeTrashFile("a", "first line\nsec. line\nthird line\nfeature++\n");
-		git.add().addFilepattern("a").call();
-		git.commit().setMessage("enhanced a").call();
+			CherryPickResult pickResult = git.cherryPick().include(fixingA)
+					.setNoCommit(noCommit).call();
 
-		CherryPickResult pickResult = git.cherryPick().include(fixingA)
-				.setNoCommit(noCommit).call();
-
-		assertEquals(CherryPickStatus.OK, pickResult.getStatus());
-		assertFalse(new File(db.getWorkTree(), "b").exists());
-		checkFile(new File(db.getWorkTree(), "a"),
-				"first line\nsecond line\nthird line\nfeature++\n");
-		Iterator<RevCommit> history = git.log().call().iterator();
-		if (!noCommit)
-			assertEquals("fixed a", history.next().getFullMessage());
-		assertEquals("enhanced a", history.next().getFullMessage());
-		assertEquals("create a", history.next().getFullMessage());
-		assertFalse(history.hasNext());
+			assertEquals(CherryPickStatus.OK, pickResult.getStatus());
+			assertFalse(new File(db.getWorkTree(), "b").exists());
+			checkFile(new File(db.getWorkTree(), "a"),
+					"first line\nsecond line\nthird line\nfeature++\n");
+			Iterator<RevCommit> history = git.log().call().iterator();
+			if (!noCommit)
+				assertEquals("fixed a", history.next().getFullMessage());
+			assertEquals("enhanced a", history.next().getFullMessage());
+			assertEquals("create a", history.next().getFullMessage());
+			assertFalse(history.hasNext());
+		}
 	}
 
     @Test
     public void testSequentialCherryPick() throws IOException, JGitInternalException,
             GitAPIException {
-        Git git = new Git(db);
+        try (Git git = new Git(db)) {
+	        writeTrashFile("a", "first line\nsec. line\nthird line\n");
+	        git.add().addFilepattern("a").call();
+	        RevCommit firstCommit = git.commit().setMessage("create a").call();
 
-        writeTrashFile("a", "first line\nsec. line\nthird line\n");
-        git.add().addFilepattern("a").call();
-        RevCommit firstCommit = git.commit().setMessage("create a").call();
+	        writeTrashFile("a", "first line\nsec. line\nthird line\nfourth line\n");
+	        git.add().addFilepattern("a").call();
+	        RevCommit enlargingA = git.commit().setMessage("enlarged a").call();
 
-        writeTrashFile("a", "first line\nsec. line\nthird line\nfourth line\n");
-        git.add().addFilepattern("a").call();
-        RevCommit enlargingA = git.commit().setMessage("enlarged a").call();
+	        writeTrashFile("a",
+	                "first line\nsecond line\nthird line\nfourth line\n");
+	        git.add().addFilepattern("a").call();
+	        RevCommit fixingA = git.commit().setMessage("fixed a").call();
 
-        writeTrashFile("a",
-                "first line\nsecond line\nthird line\nfourth line\n");
-        git.add().addFilepattern("a").call();
-        RevCommit fixingA = git.commit().setMessage("fixed a").call();
+	        git.branchCreate().setName("side").setStartPoint(firstCommit).call();
+	        checkoutBranch("refs/heads/side");
 
-        git.branchCreate().setName("side").setStartPoint(firstCommit).call();
-        checkoutBranch("refs/heads/side");
+	        writeTrashFile("b", "nothing to do with a");
+	        git.add().addFilepattern("b").call();
+	        git.commit().setMessage("create b").call();
 
-        writeTrashFile("b", "nothing to do with a");
-        git.add().addFilepattern("b").call();
-        git.commit().setMessage("create b").call();
+	        CherryPickResult result = git.cherryPick().include(enlargingA).include(fixingA).call();
+	        assertEquals(CherryPickResult.CherryPickStatus.OK, result.getStatus());
 
-        CherryPickResult result = git.cherryPick().include(enlargingA).include(fixingA).call();
-        assertEquals(CherryPickResult.CherryPickStatus.OK, result.getStatus());
-
-        Iterator<RevCommit> history = git.log().call().iterator();
-        assertEquals("fixed a", history.next().getFullMessage());
-        assertEquals("enlarged a", history.next().getFullMessage());
-        assertEquals("create b", history.next().getFullMessage());
-        assertEquals("create a", history.next().getFullMessage());
-        assertFalse(history.hasNext());
+	        Iterator<RevCommit> history = git.log().call().iterator();
+	        assertEquals("fixed a", history.next().getFullMessage());
+	        assertEquals("enlarged a", history.next().getFullMessage());
+	        assertEquals("create b", history.next().getFullMessage());
+	        assertEquals("create a", history.next().getFullMessage());
+	        assertFalse(history.hasNext());
+        }
     }
 
 	@Test
 	public void testCherryPickDirtyIndex() throws Exception {
-		Git git = new Git(db);
-		RevCommit sideCommit = prepareCherryPick(git);
+		try (Git git = new Git(db)) {
+			RevCommit sideCommit = prepareCherryPick(git);
 
-		// modify and add file a
-		writeTrashFile("a", "a(modified)");
-		git.add().addFilepattern("a").call();
-		// do not commit
+			// modify and add file a
+			writeTrashFile("a", "a(modified)");
+			git.add().addFilepattern("a").call();
+			// do not commit
 
-		doCherryPickAndCheckResult(git, sideCommit,
-				MergeFailureReason.DIRTY_INDEX);
+			doCherryPickAndCheckResult(git, sideCommit,
+					MergeFailureReason.DIRTY_INDEX);
+		}
 	}
 
 	@Test
 	public void testCherryPickDirtyWorktree() throws Exception {
-		Git git = new Git(db);
-		RevCommit sideCommit = prepareCherryPick(git);
+		try (Git git = new Git(db)) {
+			RevCommit sideCommit = prepareCherryPick(git);
 
-		// modify file a
-		writeTrashFile("a", "a(modified)");
-		// do not add and commit
+			// modify file a
+			writeTrashFile("a", "a(modified)");
+			// do not add and commit
 
-		doCherryPickAndCheckResult(git, sideCommit,
-				MergeFailureReason.DIRTY_WORKTREE);
+			doCherryPickAndCheckResult(git, sideCommit,
+					MergeFailureReason.DIRTY_WORKTREE);
+		}
 	}
 
 	@Test
 	public void testCherryPickConflictResolution() throws Exception {
-		Git git = new Git(db);
-		RevCommit sideCommit = prepareCherryPick(git);
+		try (Git git = new Git(db)) {
+			RevCommit sideCommit = prepareCherryPick(git);
 
-		CherryPickResult result = git.cherryPick().include(sideCommit.getId())
-				.call();
+			CherryPickResult result = git.cherryPick().include(sideCommit.getId())
+					.call();
 
-		assertEquals(CherryPickStatus.CONFLICTING, result.getStatus());
-		assertTrue(new File(db.getDirectory(), Constants.MERGE_MSG).exists());
-		assertEquals("side\n\nConflicts:\n\ta\n", db.readMergeCommitMsg());
-		assertTrue(new File(db.getDirectory(), Constants.CHERRY_PICK_HEAD)
-				.exists());
-		assertEquals(sideCommit.getId(), db.readCherryPickHead());
-		assertEquals(RepositoryState.CHERRY_PICKING, db.getRepositoryState());
+			assertEquals(CherryPickStatus.CONFLICTING, result.getStatus());
+			assertTrue(new File(db.getDirectory(), Constants.MERGE_MSG).exists());
+			assertEquals("side\n\nConflicts:\n\ta\n", db.readMergeCommitMsg());
+			assertTrue(new File(db.getDirectory(), Constants.CHERRY_PICK_HEAD)
+					.exists());
+			assertEquals(sideCommit.getId(), db.readCherryPickHead());
+			assertEquals(RepositoryState.CHERRY_PICKING, db.getRepositoryState());
 
-		// Resolve
-		writeTrashFile("a", "a");
-		git.add().addFilepattern("a").call();
+			// Resolve
+			writeTrashFile("a", "a");
+			git.add().addFilepattern("a").call();
 
-		assertEquals(RepositoryState.CHERRY_PICKING_RESOLVED,
-				db.getRepositoryState());
+			assertEquals(RepositoryState.CHERRY_PICKING_RESOLVED,
+					db.getRepositoryState());
 
-		git.commit().setOnly("a").setMessage("resolve").call();
+			git.commit().setOnly("a").setMessage("resolve").call();
 
-		assertEquals(RepositoryState.SAFE, db.getRepositoryState());
+			assertEquals(RepositoryState.SAFE, db.getRepositoryState());
+		}
 	}
 
 	@Test
@@ -251,85 +254,88 @@
 
 	@Test
 	public void testCherryPickConflictReset() throws Exception {
-		Git git = new Git(db);
+		try (Git git = new Git(db)) {
+			RevCommit sideCommit = prepareCherryPick(git);
 
-		RevCommit sideCommit = prepareCherryPick(git);
+			CherryPickResult result = git.cherryPick().include(sideCommit.getId())
+					.call();
 
-		CherryPickResult result = git.cherryPick().include(sideCommit.getId())
-				.call();
+			assertEquals(CherryPickStatus.CONFLICTING, result.getStatus());
+			assertEquals(RepositoryState.CHERRY_PICKING, db.getRepositoryState());
+			assertTrue(new File(db.getDirectory(), Constants.CHERRY_PICK_HEAD)
+					.exists());
 
-		assertEquals(CherryPickStatus.CONFLICTING, result.getStatus());
-		assertEquals(RepositoryState.CHERRY_PICKING, db.getRepositoryState());
-		assertTrue(new File(db.getDirectory(), Constants.CHERRY_PICK_HEAD)
-				.exists());
+			git.reset().setMode(ResetType.MIXED).setRef("HEAD").call();
 
-		git.reset().setMode(ResetType.MIXED).setRef("HEAD").call();
-
-		assertEquals(RepositoryState.SAFE, db.getRepositoryState());
-		assertFalse(new File(db.getDirectory(), Constants.CHERRY_PICK_HEAD)
-				.exists());
+			assertEquals(RepositoryState.SAFE, db.getRepositoryState());
+			assertFalse(new File(db.getDirectory(), Constants.CHERRY_PICK_HEAD)
+					.exists());
+		}
 	}
 
 	@Test
 	public void testCherryPickOverExecutableChangeOnNonExectuableFileSystem()
 			throws Exception {
-		Git git = new Git(db);
-		File file = writeTrashFile("test.txt", "a");
-		assertNotNull(git.add().addFilepattern("test.txt").call());
-		assertNotNull(git.commit().setMessage("commit1").call());
+		try (Git git = new Git(db)) {
+			File file = writeTrashFile("test.txt", "a");
+			assertNotNull(git.add().addFilepattern("test.txt").call());
+			assertNotNull(git.commit().setMessage("commit1").call());
 
-		assertNotNull(git.checkout().setCreateBranch(true).setName("a").call());
+			assertNotNull(git.checkout().setCreateBranch(true).setName("a").call());
 
-		writeTrashFile("test.txt", "b");
-		assertNotNull(git.add().addFilepattern("test.txt").call());
-		RevCommit commit2 = git.commit().setMessage("commit2").call();
-		assertNotNull(commit2);
+			writeTrashFile("test.txt", "b");
+			assertNotNull(git.add().addFilepattern("test.txt").call());
+			RevCommit commit2 = git.commit().setMessage("commit2").call();
+			assertNotNull(commit2);
 
-		assertNotNull(git.checkout().setName(Constants.MASTER).call());
+			assertNotNull(git.checkout().setName(Constants.MASTER).call());
 
-		DirCache cache = db.lockDirCache();
-		cache.getEntry("test.txt").setFileMode(FileMode.EXECUTABLE_FILE);
-		cache.write();
-		assertTrue(cache.commit());
-		cache.unlock();
+			DirCache cache = db.lockDirCache();
+			cache.getEntry("test.txt").setFileMode(FileMode.EXECUTABLE_FILE);
+			cache.write();
+			assertTrue(cache.commit());
+			cache.unlock();
 
-		assertNotNull(git.commit().setMessage("commit3").call());
+			assertNotNull(git.commit().setMessage("commit3").call());
 
-		db.getFS().setExecute(file, false);
-		git.getRepository()
-				.getConfig()
-				.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
-						ConfigConstants.CONFIG_KEY_FILEMODE, false);
+			db.getFS().setExecute(file, false);
+			git.getRepository()
+					.getConfig()
+					.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
+							ConfigConstants.CONFIG_KEY_FILEMODE, false);
 
-		CherryPickResult result = git.cherryPick().include(commit2).call();
-		assertNotNull(result);
-		assertEquals(CherryPickStatus.OK, result.getStatus());
+			CherryPickResult result = git.cherryPick().include(commit2).call();
+			assertNotNull(result);
+			assertEquals(CherryPickStatus.OK, result.getStatus());
+		}
 	}
 
 	@Test
 	public void testCherryPickConflictMarkers() throws Exception {
-		Git git = new Git(db);
-		RevCommit sideCommit = prepareCherryPick(git);
+		try (Git git = new Git(db)) {
+			RevCommit sideCommit = prepareCherryPick(git);
 
-		CherryPickResult result = git.cherryPick().include(sideCommit.getId())
-				.call();
-		assertEquals(CherryPickStatus.CONFLICTING, result.getStatus());
+			CherryPickResult result = git.cherryPick().include(sideCommit.getId())
+					.call();
+			assertEquals(CherryPickStatus.CONFLICTING, result.getStatus());
 
-		String expected = "<<<<<<< master\na(master)\n=======\na(side)\n>>>>>>> 527460a side\n";
-		checkFile(new File(db.getWorkTree(), "a"), expected);
+			String expected = "<<<<<<< master\na(master)\n=======\na(side)\n>>>>>>> 527460a side\n";
+			checkFile(new File(db.getWorkTree(), "a"), expected);
+		}
 	}
 
 	@Test
 	public void testCherryPickOurCommitName() throws Exception {
-		Git git = new Git(db);
-		RevCommit sideCommit = prepareCherryPick(git);
+		try (Git git = new Git(db)) {
+			RevCommit sideCommit = prepareCherryPick(git);
 
-		CherryPickResult result = git.cherryPick().include(sideCommit.getId())
-				.setOurCommitName("custom name").call();
-		assertEquals(CherryPickStatus.CONFLICTING, result.getStatus());
+			CherryPickResult result = git.cherryPick().include(sideCommit.getId())
+					.setOurCommitName("custom name").call();
+			assertEquals(CherryPickStatus.CONFLICTING, result.getStatus());
 
-		String expected = "<<<<<<< custom name\na(master)\n=======\na(side)\n>>>>>>> 527460a side\n";
-		checkFile(new File(db.getWorkTree(), "a"), expected);
+			String expected = "<<<<<<< custom name\na(master)\n=======\na(side)\n>>>>>>> 527460a side\n";
+			checkFile(new File(db.getWorkTree(), "a"), expected);
+		}
 	}
 
 	private RevCommit prepareCherryPick(final Git git) throws Exception {
@@ -399,43 +405,43 @@
 	 */
 	@Test
 	public void testCherryPickMerge() throws Exception {
-		Git git = new Git(db);
+		try (Git git = new Git(db)) {
+			commitFile("file", "1\n2\n3\n", "master");
+			commitFile("file", "1\n2\n3\n", "side");
+			checkoutBranch("refs/heads/side");
+			RevCommit commitD = commitFile("file", "1\n2\n3\n4\n5\n", "side2");
+			commitFile("file", "a\n2\n3\n", "side");
+			MergeResult mergeResult = git.merge().include(commitD).call();
+			ObjectId commitM = mergeResult.getNewHead();
+			checkoutBranch("refs/heads/master");
+			RevCommit commitT = commitFile("another", "t", "master");
 
-		commitFile("file", "1\n2\n3\n", "master");
-		commitFile("file", "1\n2\n3\n", "side");
-		checkoutBranch("refs/heads/side");
-		RevCommit commitD = commitFile("file", "1\n2\n3\n4\n5\n", "side2");
-		commitFile("file", "a\n2\n3\n", "side");
-		MergeResult mergeResult = git.merge().include(commitD).call();
-		ObjectId commitM = mergeResult.getNewHead();
-		checkoutBranch("refs/heads/master");
-		RevCommit commitT = commitFile("another", "t", "master");
+			try {
+				git.cherryPick().include(commitM).call();
+				fail("merges should not be cherry-picked by default");
+			} catch (MultipleParentsNotAllowedException e) {
+				// expected
+			}
+			try {
+				git.cherryPick().include(commitM).setMainlineParentNumber(3).call();
+				fail("specifying a non-existent parent should fail");
+			} catch (JGitInternalException e) {
+				// expected
+				assertTrue(e.getMessage().endsWith(
+						"does not have a parent number 3."));
+			}
 
-		try {
-			git.cherryPick().include(commitM).call();
-			fail("merges should not be cherry-picked by default");
-		} catch (MultipleParentsNotAllowedException e) {
-			// expected
+			CherryPickResult result = git.cherryPick().include(commitM)
+					.setMainlineParentNumber(1).call();
+			assertEquals(CherryPickStatus.OK, result.getStatus());
+			checkFile(new File(db.getWorkTree(), "file"), "1\n2\n3\n4\n5\n");
+
+			git.reset().setMode(ResetType.HARD).setRef(commitT.getName()).call();
+
+			CherryPickResult result2 = git.cherryPick().include(commitM)
+					.setMainlineParentNumber(2).call();
+			assertEquals(CherryPickStatus.OK, result2.getStatus());
+			checkFile(new File(db.getWorkTree(), "file"), "a\n2\n3\n");
 		}
-		try {
-			git.cherryPick().include(commitM).setMainlineParentNumber(3).call();
-			fail("specifying a non-existent parent should fail");
-		} catch (JGitInternalException e) {
-			// expected
-			assertTrue(e.getMessage().endsWith(
-					"does not have a parent number 3."));
-		}
-
-		CherryPickResult result = git.cherryPick().include(commitM)
-				.setMainlineParentNumber(1).call();
-		assertEquals(CherryPickStatus.OK, result.getStatus());
-		checkFile(new File(db.getWorkTree(), "file"), "1\n2\n3\n4\n5\n");
-
-		git.reset().setMode(ResetType.HARD).setRef(commitT.getName()).call();
-
-		CherryPickResult result2 = git.cherryPick().include(commitM)
-				.setMainlineParentNumber(2).call();
-		assertEquals(CherryPickStatus.OK, result2.getStatus());
-		checkFile(new File(db.getWorkTree(), "file"), "a\n2\n3\n");
 	}
 }
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitAndLogCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitAndLogCommandTest.java
index 7b3d4f6..1d5c674 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitAndLogCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitAndLogCommandTest.java
@@ -46,6 +46,7 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeFalse;
 
 import java.io.File;
 import java.io.IOException;
@@ -78,96 +79,96 @@
 			GitAPIException {
 
 		// do 4 commits
-		Git git = new Git(db);
-		git.commit().setMessage("initial commit").call();
-		git.commit().setMessage("second commit").setCommitter(committer).call();
-		git.commit().setMessage("third commit").setAuthor(author).call();
-		git.commit().setMessage("fourth commit").setAuthor(author)
-				.setCommitter(committer).call();
-		Iterable<RevCommit> commits = git.log().call();
+		try (Git git = new Git(db)) {
+			git.commit().setMessage("initial commit").call();
+			git.commit().setMessage("second commit").setCommitter(committer).call();
+			git.commit().setMessage("third commit").setAuthor(author).call();
+			git.commit().setMessage("fourth commit").setAuthor(author)
+					.setCommitter(committer).call();
+			Iterable<RevCommit> commits = git.log().call();
 
-		// check that all commits came in correctly
-		PersonIdent defaultCommitter = new PersonIdent(db);
-		PersonIdent expectedAuthors[] = new PersonIdent[] { defaultCommitter,
-				committer, author, author };
-		PersonIdent expectedCommitters[] = new PersonIdent[] {
-				defaultCommitter, committer, defaultCommitter, committer };
-		String expectedMessages[] = new String[] { "initial commit",
-				"second commit", "third commit", "fourth commit" };
-		int l = expectedAuthors.length - 1;
-		for (RevCommit c : commits) {
-			assertEquals(expectedAuthors[l].getName(), c.getAuthorIdent()
-					.getName());
-			assertEquals(expectedCommitters[l].getName(), c.getCommitterIdent()
-					.getName());
-			assertEquals(c.getFullMessage(), expectedMessages[l]);
-			l--;
+			// check that all commits came in correctly
+			PersonIdent defaultCommitter = new PersonIdent(db);
+			PersonIdent expectedAuthors[] = new PersonIdent[] { defaultCommitter,
+					committer, author, author };
+			PersonIdent expectedCommitters[] = new PersonIdent[] {
+					defaultCommitter, committer, defaultCommitter, committer };
+			String expectedMessages[] = new String[] { "initial commit",
+					"second commit", "third commit", "fourth commit" };
+			int l = expectedAuthors.length - 1;
+			for (RevCommit c : commits) {
+				assertEquals(expectedAuthors[l].getName(), c.getAuthorIdent()
+						.getName());
+				assertEquals(expectedCommitters[l].getName(), c.getCommitterIdent()
+						.getName());
+				assertEquals(c.getFullMessage(), expectedMessages[l]);
+				l--;
+			}
+			assertEquals(l, -1);
+			ReflogReader reader = db.getReflogReader(Constants.HEAD);
+			assertTrue(reader.getLastEntry().getComment().startsWith("commit:"));
+			reader = db.getReflogReader(db.getBranch());
+			assertTrue(reader.getLastEntry().getComment().startsWith("commit:"));
 		}
-		assertEquals(l, -1);
-		ReflogReader reader = db.getReflogReader(Constants.HEAD);
-		assertTrue(reader.getLastEntry().getComment().startsWith("commit:"));
-		reader = db.getReflogReader(db.getBranch());
-		assertTrue(reader.getLastEntry().getComment().startsWith("commit:"));
 	}
 
 	@Test
 	public void testLogWithFilter() throws IOException, JGitInternalException,
 			GitAPIException {
 
-		Git git = new Git(db);
+		try (Git git = new Git(db)) {
+			// create first file
+			File file = new File(db.getWorkTree(), "a.txt");
+			FileUtils.createNewFile(file);
+			PrintWriter writer = new PrintWriter(file);
+			writer.print("content1");
+			writer.close();
 
-		// create first file
-		File file = new File(db.getWorkTree(), "a.txt");
-		FileUtils.createNewFile(file);
-		PrintWriter writer = new PrintWriter(file);
-		writer.print("content1");
-		writer.close();
+			// First commit - a.txt file
+			git.add().addFilepattern("a.txt").call();
+			git.commit().setMessage("commit1").setCommitter(committer).call();
 
-		// First commit - a.txt file
-		git.add().addFilepattern("a.txt").call();
-		git.commit().setMessage("commit1").setCommitter(committer).call();
+			// create second file
+			file = new File(db.getWorkTree(), "b.txt");
+			FileUtils.createNewFile(file);
+			writer = new PrintWriter(file);
+			writer.print("content2");
+			writer.close();
 
-		// create second file
-		file = new File(db.getWorkTree(), "b.txt");
-		FileUtils.createNewFile(file);
-		writer = new PrintWriter(file);
-		writer.print("content2");
-		writer.close();
+			// Second commit - b.txt file
+			git.add().addFilepattern("b.txt").call();
+			git.commit().setMessage("commit2").setCommitter(committer).call();
 
-		// Second commit - b.txt file
-		git.add().addFilepattern("b.txt").call();
-		git.commit().setMessage("commit2").setCommitter(committer).call();
+			// First log - a.txt filter
+			int count = 0;
+			for (RevCommit c : git.log().addPath("a.txt").call()) {
+				assertEquals("commit1", c.getFullMessage());
+				count++;
+			}
+			assertEquals(1, count);
 
-		// First log - a.txt filter
-		int count = 0;
-		for (RevCommit c : git.log().addPath("a.txt").call()) {
-			assertEquals("commit1", c.getFullMessage());
-			count++;
+			// Second log - b.txt filter
+			count = 0;
+			for (RevCommit c : git.log().addPath("b.txt").call()) {
+				assertEquals("commit2", c.getFullMessage());
+				count++;
+			}
+			assertEquals(1, count);
+
+			// Third log - without filter
+			count = 0;
+			for (RevCommit c : git.log().call()) {
+				assertEquals(committer, c.getCommitterIdent());
+				count++;
+			}
+			assertEquals(2, count);
 		}
-		assertEquals(1, count);
-
-		// Second log - b.txt filter
-		count = 0;
-		for (RevCommit c : git.log().addPath("b.txt").call()) {
-			assertEquals("commit2", c.getFullMessage());
-			count++;
-		}
-		assertEquals(1, count);
-
-		// Third log - without filter
-		count = 0;
-		for (RevCommit c : git.log().call()) {
-			assertEquals(committer, c.getCommitterIdent());
-			count++;
-		}
-		assertEquals(2, count);
 	}
 
 	// try to do a commit without specifying a message. Should fail!
 	@Test
 	public void testWrongParams() throws GitAPIException {
-		Git git = new Git(db);
-		try {
+		try (Git git = new Git(db)) {
 			git.commit().setAuthor(author).call();
 			fail("Didn't get the expected exception");
 		} catch (NoMessageException e) {
@@ -179,48 +180,50 @@
 	// exceptions
 	@Test
 	public void testMultipleInvocations() throws GitAPIException {
-		Git git = new Git(db);
-		CommitCommand commitCmd = git.commit();
-		commitCmd.setMessage("initial commit").call();
-		try {
-			// check that setters can't be called after invocation
-			commitCmd.setAuthor(author);
-			fail("didn't catch the expected exception");
-		} catch (IllegalStateException e) {
-			// expected
-		}
-		LogCommand logCmd = git.log();
-		logCmd.call();
-		try {
-			// check that call can't be called twice
+		try (Git git = new Git(db)) {
+			CommitCommand commitCmd = git.commit();
+			commitCmd.setMessage("initial commit").call();
+			try {
+				// check that setters can't be called after invocation
+				commitCmd.setAuthor(author);
+				fail("didn't catch the expected exception");
+			} catch (IllegalStateException e) {
+				// expected
+			}
+			LogCommand logCmd = git.log();
 			logCmd.call();
-			fail("didn't catch the expected exception");
-		} catch (IllegalStateException e) {
-			// expected
+			try {
+				// check that call can't be called twice
+				logCmd.call();
+				fail("didn't catch the expected exception");
+			} catch (IllegalStateException e) {
+				// expected
+			}
 		}
 	}
 
 	@Test
 	public void testMergeEmptyBranches() throws IOException,
 			JGitInternalException, GitAPIException {
-		Git git = new Git(db);
-		git.commit().setMessage("initial commit").call();
-		RefUpdate r = db.updateRef("refs/heads/side");
-		r.setNewObjectId(db.resolve(Constants.HEAD));
-		assertEquals(r.forceUpdate(), RefUpdate.Result.NEW);
-		RevCommit second = git.commit().setMessage("second commit").setCommitter(committer).call();
-		db.updateRef(Constants.HEAD).link("refs/heads/side");
-		RevCommit firstSide = git.commit().setMessage("first side commit").setAuthor(author).call();
+		try (Git git = new Git(db)) {
+			git.commit().setMessage("initial commit").call();
+			RefUpdate r = db.updateRef("refs/heads/side");
+			r.setNewObjectId(db.resolve(Constants.HEAD));
+			assertEquals(r.forceUpdate(), RefUpdate.Result.NEW);
+			RevCommit second = git.commit().setMessage("second commit").setCommitter(committer).call();
+			db.updateRef(Constants.HEAD).link("refs/heads/side");
+			RevCommit firstSide = git.commit().setMessage("first side commit").setAuthor(author).call();
 
-		write(new File(db.getDirectory(), Constants.MERGE_HEAD), ObjectId
-				.toString(db.resolve("refs/heads/master")));
-		write(new File(db.getDirectory(), Constants.MERGE_MSG), "merging");
+			write(new File(db.getDirectory(), Constants.MERGE_HEAD), ObjectId
+					.toString(db.resolve("refs/heads/master")));
+			write(new File(db.getDirectory(), Constants.MERGE_MSG), "merging");
 
-		RevCommit commit = git.commit().call();
-		RevCommit[] parents = commit.getParents();
-		assertEquals(parents[0], firstSide);
-		assertEquals(parents[1], second);
-		assertEquals(2, parents.length);
+			RevCommit commit = git.commit().call();
+			RevCommit[] parents = commit.getParents();
+			assertEquals(parents[0], firstSide);
+			assertEquals(parents[1], second);
+			assertEquals(2, parents.length);
+		}
 	}
 
 	@Test
@@ -232,56 +235,56 @@
 		writer.print("content");
 		writer.close();
 
-		Git git = new Git(db);
-		git.add().addFilepattern("a.txt").call();
-		RevCommit commit = git.commit().setMessage("initial commit").call();
-		TreeWalk tw = TreeWalk.forPath(db, "a.txt", commit.getTree());
-		assertEquals("6b584e8ece562ebffc15d38808cd6b98fc3d97ea",
-				tw.getObjectId(0).getName());
+		try (Git git = new Git(db)) {
+			git.add().addFilepattern("a.txt").call();
+			RevCommit commit = git.commit().setMessage("initial commit").call();
+			TreeWalk tw = TreeWalk.forPath(db, "a.txt", commit.getTree());
+			assertEquals("6b584e8ece562ebffc15d38808cd6b98fc3d97ea",
+					tw.getObjectId(0).getName());
 
-		writer = new PrintWriter(file);
-		writer.print("content2");
-		writer.close();
-		commit = git.commit().setMessage("second commit").call();
-		tw = TreeWalk.forPath(db, "a.txt", commit.getTree());
-		assertEquals("6b584e8ece562ebffc15d38808cd6b98fc3d97ea",
-				tw.getObjectId(0).getName());
+			writer = new PrintWriter(file);
+			writer.print("content2");
+			writer.close();
+			commit = git.commit().setMessage("second commit").call();
+			tw = TreeWalk.forPath(db, "a.txt", commit.getTree());
+			assertEquals("6b584e8ece562ebffc15d38808cd6b98fc3d97ea",
+					tw.getObjectId(0).getName());
 
-		commit = git.commit().setAll(true).setMessage("third commit")
-				.setAll(true).call();
-		tw = TreeWalk.forPath(db, "a.txt", commit.getTree());
-		assertEquals("db00fd65b218578127ea51f3dffac701f12f486a",
-				tw.getObjectId(0).getName());
+			commit = git.commit().setAll(true).setMessage("third commit")
+					.setAll(true).call();
+			tw = TreeWalk.forPath(db, "a.txt", commit.getTree());
+			assertEquals("db00fd65b218578127ea51f3dffac701f12f486a",
+					tw.getObjectId(0).getName());
+		}
 	}
 
 	@Test
 	public void testModeChange() throws IOException, GitAPIException {
-		if (System.getProperty("os.name").startsWith("Windows"))
-			return; // SKIP
-		Git git = new Git(db);
+		assumeFalse(System.getProperty("os.name").startsWith("Windows"));// SKIP
+		try (Git git = new Git(db)) {
+			// create file
+			File file = new File(db.getWorkTree(), "a.txt");
+			FileUtils.createNewFile(file);
+			PrintWriter writer = new PrintWriter(file);
+			writer.print("content1");
+			writer.close();
 
-		// create file
-		File file = new File(db.getWorkTree(), "a.txt");
-		FileUtils.createNewFile(file);
-		PrintWriter writer = new PrintWriter(file);
-		writer.print("content1");
-		writer.close();
+			// First commit - a.txt file
+			git.add().addFilepattern("a.txt").call();
+			git.commit().setMessage("commit1").setCommitter(committer).call();
 
-		// First commit - a.txt file
-		git.add().addFilepattern("a.txt").call();
-		git.commit().setMessage("commit1").setCommitter(committer).call();
+			// pure mode change should be committable
+			FS fs = db.getFS();
+			fs.setExecute(file, true);
+			git.add().addFilepattern("a.txt").call();
+			git.commit().setMessage("mode change").setCommitter(committer).call();
 
-		// pure mode change should be committable
-		FS fs = db.getFS();
-		fs.setExecute(file, true);
-		git.add().addFilepattern("a.txt").call();
-		git.commit().setMessage("mode change").setCommitter(committer).call();
-
-		// pure mode change should be committable with -o option
-		fs.setExecute(file, false);
-		git.add().addFilepattern("a.txt").call();
-		git.commit().setMessage("mode change").setCommitter(committer)
-				.setOnly("a.txt").call();
+			// pure mode change should be committable with -o option
+			fs.setExecute(file, false);
+			git.add().addFilepattern("a.txt").call();
+			git.commit().setMessage("mode change").setCommitter(committer)
+					.setOnly("a.txt").call();
+		}
 	}
 
 	@Test
@@ -289,112 +292,115 @@
 			JGitInternalException, MissingObjectException,
 			IncorrectObjectTypeException {
 		// do 4 commits and set the range to the second and fourth one
-		Git git = new Git(db);
-		git.commit().setMessage("first commit").call();
-		RevCommit second = git.commit().setMessage("second commit")
-				.setCommitter(committer).call();
-		git.commit().setMessage("third commit").setAuthor(author).call();
-		RevCommit last = git.commit().setMessage("fourth commit").setAuthor(
-				author)
-				.setCommitter(committer).call();
-		Iterable<RevCommit> commits = git.log().addRange(second.getId(),
-				last.getId()).call();
+		try (Git git = new Git(db)) {
+			git.commit().setMessage("first commit").call();
+			RevCommit second = git.commit().setMessage("second commit")
+					.setCommitter(committer).call();
+			git.commit().setMessage("third commit").setAuthor(author).call();
+			RevCommit last = git.commit().setMessage("fourth commit").setAuthor(
+					author)
+					.setCommitter(committer).call();
+			Iterable<RevCommit> commits = git.log().addRange(second.getId(),
+					last.getId()).call();
 
-		// check that we have the third and fourth commit
-		PersonIdent defaultCommitter = new PersonIdent(db);
-		PersonIdent expectedAuthors[] = new PersonIdent[] { author, author };
-		PersonIdent expectedCommitters[] = new PersonIdent[] {
-				defaultCommitter, committer };
-		String expectedMessages[] = new String[] { "third commit",
-				"fourth commit" };
-		int l = expectedAuthors.length - 1;
-		for (RevCommit c : commits) {
-			assertEquals(expectedAuthors[l].getName(), c.getAuthorIdent()
-					.getName());
-			assertEquals(expectedCommitters[l].getName(), c.getCommitterIdent()
-					.getName());
-			assertEquals(c.getFullMessage(), expectedMessages[l]);
-			l--;
+			// check that we have the third and fourth commit
+			PersonIdent defaultCommitter = new PersonIdent(db);
+			PersonIdent expectedAuthors[] = new PersonIdent[] { author, author };
+			PersonIdent expectedCommitters[] = new PersonIdent[] {
+					defaultCommitter, committer };
+			String expectedMessages[] = new String[] { "third commit",
+					"fourth commit" };
+			int l = expectedAuthors.length - 1;
+			for (RevCommit c : commits) {
+				assertEquals(expectedAuthors[l].getName(), c.getAuthorIdent()
+						.getName());
+				assertEquals(expectedCommitters[l].getName(), c.getCommitterIdent()
+						.getName());
+				assertEquals(c.getFullMessage(), expectedMessages[l]);
+				l--;
+			}
+			assertEquals(l, -1);
 		}
-		assertEquals(l, -1);
 	}
 
 	@Test
 	public void testCommitAmend() throws JGitInternalException, IOException,
 			GitAPIException {
-		Git git = new Git(db);
-		git.commit().setMessage("first comit").call(); // typo
-		git.commit().setAmend(true).setMessage("first commit").call();
+		try (Git git = new Git(db)) {
+			git.commit().setMessage("first comit").call(); // typo
+			git.commit().setAmend(true).setMessage("first commit").call();
 
-		Iterable<RevCommit> commits = git.log().call();
-		int c = 0;
-		for (RevCommit commit : commits) {
-			assertEquals("first commit", commit.getFullMessage());
-			c++;
+			Iterable<RevCommit> commits = git.log().call();
+			int c = 0;
+			for (RevCommit commit : commits) {
+				assertEquals("first commit", commit.getFullMessage());
+				c++;
+			}
+			assertEquals(1, c);
+			ReflogReader reader = db.getReflogReader(Constants.HEAD);
+			assertTrue(reader.getLastEntry().getComment()
+					.startsWith("commit (amend):"));
+			reader = db.getReflogReader(db.getBranch());
+			assertTrue(reader.getLastEntry().getComment()
+					.startsWith("commit (amend):"));
 		}
-		assertEquals(1, c);
-		ReflogReader reader = db.getReflogReader(Constants.HEAD);
-		assertTrue(reader.getLastEntry().getComment()
-				.startsWith("commit (amend):"));
-		reader = db.getReflogReader(db.getBranch());
-		assertTrue(reader.getLastEntry().getComment()
-				.startsWith("commit (amend):"));
 	}
 
 	@Test
 	public void testInsertChangeId() throws JGitInternalException,
 			GitAPIException {
-		Git git = new Git(db);
-		String messageHeader = "Some header line\n\nSome detail explanation\n";
-		String changeIdTemplate = "\nChange-Id: I"
-				+ ObjectId.zeroId().getName() + "\n";
-		String messageFooter = "Some foooter lines\nAnother footer line\n";
-		RevCommit commit = git.commit().setMessage(
-				messageHeader + messageFooter)
-				.setInsertChangeId(true).call();
-		// we should find a real change id (at the end of the file)
-		byte[] chars = commit.getFullMessage().getBytes();
-		int lastLineBegin = RawParseUtils.prevLF(chars, chars.length - 2);
-		String lastLine = RawParseUtils.decode(chars, lastLineBegin + 1,
-				chars.length);
-		assertTrue(lastLine.contains("Change-Id:"));
-		assertFalse(lastLine.contains(
-				"Change-Id: I" + ObjectId.zeroId().getName()));
+		try (Git git = new Git(db)) {
+			String messageHeader = "Some header line\n\nSome detail explanation\n";
+			String changeIdTemplate = "\nChange-Id: I"
+					+ ObjectId.zeroId().getName() + "\n";
+			String messageFooter = "Some foooter lines\nAnother footer line\n";
+			RevCommit commit = git.commit().setMessage(
+					messageHeader + messageFooter)
+					.setInsertChangeId(true).call();
+			// we should find a real change id (at the end of the file)
+			byte[] chars = commit.getFullMessage().getBytes();
+			int lastLineBegin = RawParseUtils.prevLF(chars, chars.length - 2);
+			String lastLine = RawParseUtils.decode(chars, lastLineBegin + 1,
+					chars.length);
+			assertTrue(lastLine.contains("Change-Id:"));
+			assertFalse(lastLine.contains(
+					"Change-Id: I" + ObjectId.zeroId().getName()));
 
-		commit = git.commit().setMessage(
-				messageHeader + changeIdTemplate + messageFooter)
-				.setInsertChangeId(true).call();
-		// we should find a real change id (in the line as dictated by the
-		// template)
-		chars = commit.getFullMessage().getBytes();
-		int lineStart = 0;
-		int lineEnd = 0;
-		for (int i = 0; i < 4; i++) {
-			lineStart = RawParseUtils.nextLF(chars, lineStart);
+			commit = git.commit().setMessage(
+					messageHeader + changeIdTemplate + messageFooter)
+					.setInsertChangeId(true).call();
+			// we should find a real change id (in the line as dictated by the
+			// template)
+			chars = commit.getFullMessage().getBytes();
+			int lineStart = 0;
+			int lineEnd = 0;
+			for (int i = 0; i < 4; i++) {
+				lineStart = RawParseUtils.nextLF(chars, lineStart);
+			}
+			lineEnd = RawParseUtils.nextLF(chars, lineStart);
+
+			String line = RawParseUtils.decode(chars, lineStart, lineEnd);
+
+			assertTrue(line.contains("Change-Id:"));
+			assertFalse(line.contains(
+					"Change-Id: I" + ObjectId.zeroId().getName()));
+
+			commit = git.commit().setMessage(
+					messageHeader + changeIdTemplate + messageFooter)
+					.setInsertChangeId(false).call();
+			// we should find the untouched template
+			chars = commit.getFullMessage().getBytes();
+			lineStart = 0;
+			lineEnd = 0;
+			for (int i = 0; i < 4; i++) {
+				lineStart = RawParseUtils.nextLF(chars, lineStart);
+			}
+			lineEnd = RawParseUtils.nextLF(chars, lineStart);
+
+			line = RawParseUtils.decode(chars, lineStart, lineEnd);
+
+			assertTrue(commit.getFullMessage().contains(
+					"Change-Id: I" + ObjectId.zeroId().getName()));
 		}
-		lineEnd = RawParseUtils.nextLF(chars, lineStart);
-
-		String line = RawParseUtils.decode(chars, lineStart, lineEnd);
-
-		assertTrue(line.contains("Change-Id:"));
-		assertFalse(line.contains(
-				"Change-Id: I" + ObjectId.zeroId().getName()));
-
-		commit = git.commit().setMessage(
-				messageHeader + changeIdTemplate + messageFooter)
-				.setInsertChangeId(false).call();
-		// we should find the untouched template
-		chars = commit.getFullMessage().getBytes();
-		lineStart = 0;
-		lineEnd = 0;
-		for (int i = 0; i < 4; i++) {
-			lineStart = RawParseUtils.nextLF(chars, lineStart);
-		}
-		lineEnd = RawParseUtils.nextLF(chars, lineStart);
-
-		line = RawParseUtils.decode(chars, lineStart, lineEnd);
-
-		assertTrue(commit.getFullMessage().contains(
-				"Change-Id: I" + ObjectId.zeroId().getName()));
 	}
 }
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 060a5b65c..9d87f0c 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
@@ -46,12 +46,15 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.fail;
 
 import java.io.File;
 import java.util.Date;
 import java.util.List;
 import java.util.TimeZone;
 
+import org.eclipse.jgit.api.errors.EmtpyCommitException;
 import org.eclipse.jgit.api.errors.WrongRepositoryStateException;
 import org.eclipse.jgit.diff.DiffEntry;
 import org.eclipse.jgit.dircache.DirCache;
@@ -183,295 +186,330 @@
 
 	@Test
 	public void commitNewSubmodule() throws Exception {
-		Git git = new Git(db);
-		writeTrashFile("file.txt", "content");
-		git.add().addFilepattern("file.txt").call();
-		RevCommit commit = git.commit().setMessage("create file").call();
+		try (Git git = new Git(db)) {
+			writeTrashFile("file.txt", "content");
+			git.add().addFilepattern("file.txt").call();
+			RevCommit commit = git.commit().setMessage("create file").call();
 
-		SubmoduleAddCommand command = new SubmoduleAddCommand(db);
-		String path = "sub";
-		command.setPath(path);
-		String uri = db.getDirectory().toURI().toString();
-		command.setURI(uri);
-		Repository repo = command.call();
-		assertNotNull(repo);
-		addRepoToClose(repo);
+			SubmoduleAddCommand command = new SubmoduleAddCommand(db);
+			String path = "sub";
+			command.setPath(path);
+			String uri = db.getDirectory().toURI().toString();
+			command.setURI(uri);
+			Repository repo = command.call();
+			assertNotNull(repo);
+			addRepoToClose(repo);
 
-		SubmoduleWalk generator = SubmoduleWalk.forIndex(db);
-		assertTrue(generator.next());
-		assertEquals(path, generator.getPath());
-		assertEquals(commit, generator.getObjectId());
-		assertEquals(uri, generator.getModulesUrl());
-		assertEquals(path, generator.getModulesPath());
-		assertEquals(uri, generator.getConfigUrl());
-		Repository subModRepo = generator.getRepository();
-		assertNotNull(subModRepo);
-		subModRepo.close();
-		assertEquals(commit, repo.resolve(Constants.HEAD));
+			SubmoduleWalk generator = SubmoduleWalk.forIndex(db);
+			assertTrue(generator.next());
+			assertEquals(path, generator.getPath());
+			assertEquals(commit, generator.getObjectId());
+			assertEquals(uri, generator.getModulesUrl());
+			assertEquals(path, generator.getModulesPath());
+			assertEquals(uri, generator.getConfigUrl());
+			Repository subModRepo = generator.getRepository();
+			assertNotNull(subModRepo);
+			subModRepo.close();
+			assertEquals(commit, repo.resolve(Constants.HEAD));
 
-		RevCommit submoduleCommit = git.commit().setMessage("submodule add")
-				.setOnly(path).call();
-		assertNotNull(submoduleCommit);
-		TreeWalk walk = new TreeWalk(db);
-		walk.addTree(commit.getTree());
-		walk.addTree(submoduleCommit.getTree());
-		walk.setFilter(TreeFilter.ANY_DIFF);
-		List<DiffEntry> diffs = DiffEntry.scan(walk);
-		assertEquals(1, diffs.size());
-		DiffEntry subDiff = diffs.get(0);
-		assertEquals(FileMode.MISSING, subDiff.getOldMode());
-		assertEquals(FileMode.GITLINK, subDiff.getNewMode());
-		assertEquals(ObjectId.zeroId(), subDiff.getOldId().toObjectId());
-		assertEquals(commit, subDiff.getNewId().toObjectId());
-		assertEquals(path, subDiff.getNewPath());
+			RevCommit submoduleCommit = git.commit().setMessage("submodule add")
+					.setOnly(path).call();
+			assertNotNull(submoduleCommit);
+			try (TreeWalk walk = new TreeWalk(db)) {
+				walk.addTree(commit.getTree());
+				walk.addTree(submoduleCommit.getTree());
+				walk.setFilter(TreeFilter.ANY_DIFF);
+				List<DiffEntry> diffs = DiffEntry.scan(walk);
+				assertEquals(1, diffs.size());
+				DiffEntry subDiff = diffs.get(0);
+				assertEquals(FileMode.MISSING, subDiff.getOldMode());
+				assertEquals(FileMode.GITLINK, subDiff.getNewMode());
+				assertEquals(ObjectId.zeroId(), subDiff.getOldId().toObjectId());
+				assertEquals(commit, subDiff.getNewId().toObjectId());
+				assertEquals(path, subDiff.getNewPath());
+			}
+		}
 	}
 
 	@Test
 	public void commitSubmoduleUpdate() throws Exception {
-		Git git = new Git(db);
-		writeTrashFile("file.txt", "content");
-		git.add().addFilepattern("file.txt").call();
-		RevCommit commit = git.commit().setMessage("create file").call();
-		writeTrashFile("file.txt", "content2");
-		git.add().addFilepattern("file.txt").call();
-		RevCommit commit2 = git.commit().setMessage("edit file").call();
+		try (Git git = new Git(db)) {
+			writeTrashFile("file.txt", "content");
+			git.add().addFilepattern("file.txt").call();
+			RevCommit commit = git.commit().setMessage("create file").call();
+			writeTrashFile("file.txt", "content2");
+			git.add().addFilepattern("file.txt").call();
+			RevCommit commit2 = git.commit().setMessage("edit file").call();
 
-		SubmoduleAddCommand command = new SubmoduleAddCommand(db);
-		String path = "sub";
-		command.setPath(path);
-		String uri = db.getDirectory().toURI().toString();
-		command.setURI(uri);
-		Repository repo = command.call();
-		assertNotNull(repo);
-		addRepoToClose(repo);
+			SubmoduleAddCommand command = new SubmoduleAddCommand(db);
+			String path = "sub";
+			command.setPath(path);
+			String uri = db.getDirectory().toURI().toString();
+			command.setURI(uri);
+			Repository repo = command.call();
+			assertNotNull(repo);
+			addRepoToClose(repo);
 
-		SubmoduleWalk generator = SubmoduleWalk.forIndex(db);
-		assertTrue(generator.next());
-		assertEquals(path, generator.getPath());
-		assertEquals(commit2, generator.getObjectId());
-		assertEquals(uri, generator.getModulesUrl());
-		assertEquals(path, generator.getModulesPath());
-		assertEquals(uri, generator.getConfigUrl());
-		Repository subModRepo = generator.getRepository();
-		assertNotNull(subModRepo);
-		subModRepo.close();
-		assertEquals(commit2, repo.resolve(Constants.HEAD));
+			SubmoduleWalk generator = SubmoduleWalk.forIndex(db);
+			assertTrue(generator.next());
+			assertEquals(path, generator.getPath());
+			assertEquals(commit2, generator.getObjectId());
+			assertEquals(uri, generator.getModulesUrl());
+			assertEquals(path, generator.getModulesPath());
+			assertEquals(uri, generator.getConfigUrl());
+			Repository subModRepo = generator.getRepository();
+			assertNotNull(subModRepo);
+			subModRepo.close();
+			assertEquals(commit2, repo.resolve(Constants.HEAD));
 
-		RevCommit submoduleAddCommit = git.commit().setMessage("submodule add")
-				.setOnly(path).call();
-		assertNotNull(submoduleAddCommit);
+			RevCommit submoduleAddCommit = git.commit().setMessage("submodule add")
+					.setOnly(path).call();
+			assertNotNull(submoduleAddCommit);
 
-		RefUpdate update = repo.updateRef(Constants.HEAD);
-		update.setNewObjectId(commit);
-		assertEquals(Result.FORCED, update.forceUpdate());
+			RefUpdate update = repo.updateRef(Constants.HEAD);
+			update.setNewObjectId(commit);
+			assertEquals(Result.FORCED, update.forceUpdate());
 
-		RevCommit submoduleEditCommit = git.commit()
-				.setMessage("submodule add").setOnly(path).call();
-		assertNotNull(submoduleEditCommit);
-		TreeWalk walk = new TreeWalk(db);
-		walk.addTree(submoduleAddCommit.getTree());
-		walk.addTree(submoduleEditCommit.getTree());
-		walk.setFilter(TreeFilter.ANY_DIFF);
-		List<DiffEntry> diffs = DiffEntry.scan(walk);
-		assertEquals(1, diffs.size());
-		DiffEntry subDiff = diffs.get(0);
-		assertEquals(FileMode.GITLINK, subDiff.getOldMode());
-		assertEquals(FileMode.GITLINK, subDiff.getNewMode());
-		assertEquals(commit2, subDiff.getOldId().toObjectId());
-		assertEquals(commit, subDiff.getNewId().toObjectId());
-		assertEquals(path, subDiff.getNewPath());
-		assertEquals(path, subDiff.getOldPath());
+			RevCommit submoduleEditCommit = git.commit()
+					.setMessage("submodule add").setOnly(path).call();
+			assertNotNull(submoduleEditCommit);
+			try (TreeWalk walk = new TreeWalk(db)) {
+				walk.addTree(submoduleAddCommit.getTree());
+				walk.addTree(submoduleEditCommit.getTree());
+				walk.setFilter(TreeFilter.ANY_DIFF);
+				List<DiffEntry> diffs = DiffEntry.scan(walk);
+				assertEquals(1, diffs.size());
+				DiffEntry subDiff = diffs.get(0);
+				assertEquals(FileMode.GITLINK, subDiff.getOldMode());
+				assertEquals(FileMode.GITLINK, subDiff.getNewMode());
+				assertEquals(commit2, subDiff.getOldId().toObjectId());
+				assertEquals(commit, subDiff.getNewId().toObjectId());
+				assertEquals(path, subDiff.getNewPath());
+				assertEquals(path, subDiff.getOldPath());
+			}
+		}
 	}
 
 	@Test
 	public void commitUpdatesSmudgedEntries() throws Exception {
-		Git git = new Git(db);
+		try (Git git = new Git(db)) {
+			File file1 = writeTrashFile("file1.txt", "content1");
+			assertTrue(file1.setLastModified(file1.lastModified() - 5000));
+			File file2 = writeTrashFile("file2.txt", "content2");
+			assertTrue(file2.setLastModified(file2.lastModified() - 5000));
+			File file3 = writeTrashFile("file3.txt", "content3");
+			assertTrue(file3.setLastModified(file3.lastModified() - 5000));
 
-		File file1 = writeTrashFile("file1.txt", "content1");
-		assertTrue(file1.setLastModified(file1.lastModified() - 5000));
-		File file2 = writeTrashFile("file2.txt", "content2");
-		assertTrue(file2.setLastModified(file2.lastModified() - 5000));
-		File file3 = writeTrashFile("file3.txt", "content3");
-		assertTrue(file3.setLastModified(file3.lastModified() - 5000));
+			assertNotNull(git.add().addFilepattern("file1.txt")
+					.addFilepattern("file2.txt").addFilepattern("file3.txt").call());
+			RevCommit commit = git.commit().setMessage("add files").call();
+			assertNotNull(commit);
 
-		assertNotNull(git.add().addFilepattern("file1.txt")
-				.addFilepattern("file2.txt").addFilepattern("file3.txt").call());
-		RevCommit commit = git.commit().setMessage("add files").call();
-		assertNotNull(commit);
+			DirCache cache = DirCache.read(db.getIndexFile(), db.getFS());
+			int file1Size = cache.getEntry("file1.txt").getLength();
+			int file2Size = cache.getEntry("file2.txt").getLength();
+			int file3Size = cache.getEntry("file3.txt").getLength();
+			ObjectId file2Id = cache.getEntry("file2.txt").getObjectId();
+			ObjectId file3Id = cache.getEntry("file3.txt").getObjectId();
+			assertTrue(file1Size > 0);
+			assertTrue(file2Size > 0);
+			assertTrue(file3Size > 0);
 
-		DirCache cache = DirCache.read(db.getIndexFile(), db.getFS());
-		int file1Size = cache.getEntry("file1.txt").getLength();
-		int file2Size = cache.getEntry("file2.txt").getLength();
-		int file3Size = cache.getEntry("file3.txt").getLength();
-		ObjectId file2Id = cache.getEntry("file2.txt").getObjectId();
-		ObjectId file3Id = cache.getEntry("file3.txt").getObjectId();
-		assertTrue(file1Size > 0);
-		assertTrue(file2Size > 0);
-		assertTrue(file3Size > 0);
+			// Smudge entries
+			cache = DirCache.lock(db.getIndexFile(), db.getFS());
+			cache.getEntry("file1.txt").setLength(0);
+			cache.getEntry("file2.txt").setLength(0);
+			cache.getEntry("file3.txt").setLength(0);
+			cache.write();
+			assertTrue(cache.commit());
 
-		// Smudge entries
-		cache = DirCache.lock(db.getIndexFile(), db.getFS());
-		cache.getEntry("file1.txt").setLength(0);
-		cache.getEntry("file2.txt").setLength(0);
-		cache.getEntry("file3.txt").setLength(0);
-		cache.write();
-		assertTrue(cache.commit());
+			// Verify entries smudged
+			cache = DirCache.read(db.getIndexFile(), db.getFS());
+			assertEquals(0, cache.getEntry("file1.txt").getLength());
+			assertEquals(0, cache.getEntry("file2.txt").getLength());
+			assertEquals(0, cache.getEntry("file3.txt").getLength());
 
-		// Verify entries smudged
-		cache = DirCache.read(db.getIndexFile(), db.getFS());
-		assertEquals(0, cache.getEntry("file1.txt").getLength());
-		assertEquals(0, cache.getEntry("file2.txt").getLength());
-		assertEquals(0, cache.getEntry("file3.txt").getLength());
+			long indexTime = db.getIndexFile().lastModified();
+			db.getIndexFile().setLastModified(indexTime - 5000);
 
-		long indexTime = db.getIndexFile().lastModified();
-		db.getIndexFile().setLastModified(indexTime - 5000);
+			write(file1, "content4");
+			assertTrue(file1.setLastModified(file1.lastModified() + 2500));
+			assertNotNull(git.commit().setMessage("edit file").setOnly("file1.txt")
+					.call());
 
-		write(file1, "content4");
-		assertTrue(file1.setLastModified(file1.lastModified() + 2500));
-		assertNotNull(git.commit().setMessage("edit file").setOnly("file1.txt")
-				.call());
-
-		cache = db.readDirCache();
-		assertEquals(file1Size, cache.getEntry("file1.txt").getLength());
-		assertEquals(file2Size, cache.getEntry("file2.txt").getLength());
-		assertEquals(file3Size, cache.getEntry("file3.txt").getLength());
-		assertEquals(file2Id, cache.getEntry("file2.txt").getObjectId());
-		assertEquals(file3Id, cache.getEntry("file3.txt").getObjectId());
+			cache = db.readDirCache();
+			assertEquals(file1Size, cache.getEntry("file1.txt").getLength());
+			assertEquals(file2Size, cache.getEntry("file2.txt").getLength());
+			assertEquals(file3Size, cache.getEntry("file3.txt").getLength());
+			assertEquals(file2Id, cache.getEntry("file2.txt").getObjectId());
+			assertEquals(file3Id, cache.getEntry("file3.txt").getObjectId());
+		}
 	}
 
 	@Test
 	public void commitIgnoresSmudgedEntryWithDifferentId() throws Exception {
-		Git git = new Git(db);
+		try (Git git = new Git(db)) {
+			File file1 = writeTrashFile("file1.txt", "content1");
+			assertTrue(file1.setLastModified(file1.lastModified() - 5000));
+			File file2 = writeTrashFile("file2.txt", "content2");
+			assertTrue(file2.setLastModified(file2.lastModified() - 5000));
 
-		File file1 = writeTrashFile("file1.txt", "content1");
-		assertTrue(file1.setLastModified(file1.lastModified() - 5000));
-		File file2 = writeTrashFile("file2.txt", "content2");
-		assertTrue(file2.setLastModified(file2.lastModified() - 5000));
+			assertNotNull(git.add().addFilepattern("file1.txt")
+					.addFilepattern("file2.txt").call());
+			RevCommit commit = git.commit().setMessage("add files").call();
+			assertNotNull(commit);
 
-		assertNotNull(git.add().addFilepattern("file1.txt")
-				.addFilepattern("file2.txt").call());
-		RevCommit commit = git.commit().setMessage("add files").call();
-		assertNotNull(commit);
+			DirCache cache = DirCache.read(db.getIndexFile(), db.getFS());
+			int file1Size = cache.getEntry("file1.txt").getLength();
+			int file2Size = cache.getEntry("file2.txt").getLength();
+			assertTrue(file1Size > 0);
+			assertTrue(file2Size > 0);
 
-		DirCache cache = DirCache.read(db.getIndexFile(), db.getFS());
-		int file1Size = cache.getEntry("file1.txt").getLength();
-		int file2Size = cache.getEntry("file2.txt").getLength();
-		assertTrue(file1Size > 0);
-		assertTrue(file2Size > 0);
+			writeTrashFile("file2.txt", "content3");
+			assertNotNull(git.add().addFilepattern("file2.txt").call());
+			writeTrashFile("file2.txt", "content4");
 
-		writeTrashFile("file2.txt", "content3");
-		assertNotNull(git.add().addFilepattern("file2.txt").call());
-		writeTrashFile("file2.txt", "content4");
+			// Smudge entries
+			cache = DirCache.lock(db.getIndexFile(), db.getFS());
+			cache.getEntry("file1.txt").setLength(0);
+			cache.getEntry("file2.txt").setLength(0);
+			cache.write();
+			assertTrue(cache.commit());
 
-		// Smudge entries
-		cache = DirCache.lock(db.getIndexFile(), db.getFS());
-		cache.getEntry("file1.txt").setLength(0);
-		cache.getEntry("file2.txt").setLength(0);
-		cache.write();
-		assertTrue(cache.commit());
+			// Verify entries smudged
+			cache = db.readDirCache();
+			assertEquals(0, cache.getEntry("file1.txt").getLength());
+			assertEquals(0, cache.getEntry("file2.txt").getLength());
 
-		// Verify entries smudged
-		cache = db.readDirCache();
-		assertEquals(0, cache.getEntry("file1.txt").getLength());
-		assertEquals(0, cache.getEntry("file2.txt").getLength());
+			long indexTime = db.getIndexFile().lastModified();
+			db.getIndexFile().setLastModified(indexTime - 5000);
 
-		long indexTime = db.getIndexFile().lastModified();
-		db.getIndexFile().setLastModified(indexTime - 5000);
+			write(file1, "content5");
+			assertTrue(file1.setLastModified(file1.lastModified() + 1000));
 
-		write(file1, "content5");
-		assertTrue(file1.setLastModified(file1.lastModified() + 1000));
+			assertNotNull(git.commit().setMessage("edit file").setOnly("file1.txt")
+					.call());
 
-		assertNotNull(git.commit().setMessage("edit file").setOnly("file1.txt")
-				.call());
-
-		cache = db.readDirCache();
-		assertEquals(file1Size, cache.getEntry("file1.txt").getLength());
-		assertEquals(0, cache.getEntry("file2.txt").getLength());
+			cache = db.readDirCache();
+			assertEquals(file1Size, cache.getEntry("file1.txt").getLength());
+			assertEquals(0, cache.getEntry("file2.txt").getLength());
+		}
 	}
 
 	@Test
 	public void commitAfterSquashMerge() throws Exception {
-		Git git = new Git(db);
+		try (Git git = new Git(db)) {
+			writeTrashFile("file1", "file1");
+			git.add().addFilepattern("file1").call();
+			RevCommit first = git.commit().setMessage("initial commit").call();
 
-		writeTrashFile("file1", "file1");
-		git.add().addFilepattern("file1").call();
-		RevCommit first = git.commit().setMessage("initial commit").call();
+			assertTrue(new File(db.getWorkTree(), "file1").exists());
+			createBranch(first, "refs/heads/branch1");
+			checkoutBranch("refs/heads/branch1");
 
-		assertTrue(new File(db.getWorkTree(), "file1").exists());
-		createBranch(first, "refs/heads/branch1");
-		checkoutBranch("refs/heads/branch1");
+			writeTrashFile("file2", "file2");
+			git.add().addFilepattern("file2").call();
+			git.commit().setMessage("second commit").call();
+			assertTrue(new File(db.getWorkTree(), "file2").exists());
 
-		writeTrashFile("file2", "file2");
-		git.add().addFilepattern("file2").call();
-		git.commit().setMessage("second commit").call();
-		assertTrue(new File(db.getWorkTree(), "file2").exists());
+			checkoutBranch("refs/heads/master");
 
-		checkoutBranch("refs/heads/master");
+			MergeResult result = git.merge()
+					.include(db.exactRef("refs/heads/branch1"))
+					.setSquash(true)
+					.call();
 
-		MergeResult result = git.merge().include(db.getRef("branch1"))
-				.setSquash(true).call();
+			assertTrue(new File(db.getWorkTree(), "file1").exists());
+			assertTrue(new File(db.getWorkTree(), "file2").exists());
+			assertEquals(MergeResult.MergeStatus.FAST_FORWARD_SQUASHED,
+					result.getMergeStatus());
 
-		assertTrue(new File(db.getWorkTree(), "file1").exists());
-		assertTrue(new File(db.getWorkTree(), "file2").exists());
-		assertEquals(MergeResult.MergeStatus.FAST_FORWARD_SQUASHED,
-				result.getMergeStatus());
+			// comment not set, should be inferred from SQUASH_MSG
+			RevCommit squashedCommit = git.commit().call();
 
-		// comment not set, should be inferred from SQUASH_MSG
-		RevCommit squashedCommit = git.commit().call();
-
-		assertEquals(1, squashedCommit.getParentCount());
-		assertNull(db.readSquashCommitMsg());
-		assertEquals("commit: Squashed commit of the following:", db
-				.getReflogReader(Constants.HEAD).getLastEntry().getComment());
-		assertEquals("commit: Squashed commit of the following:", db
-				.getReflogReader(db.getBranch()).getLastEntry().getComment());
+			assertEquals(1, squashedCommit.getParentCount());
+			assertNull(db.readSquashCommitMsg());
+			assertEquals("commit: Squashed commit of the following:", db
+					.getReflogReader(Constants.HEAD).getLastEntry().getComment());
+			assertEquals("commit: Squashed commit of the following:", db
+					.getReflogReader(db.getBranch()).getLastEntry().getComment());
+		}
 	}
 
 	@Test(expected = WrongRepositoryStateException.class)
 	public void commitAmendOnInitialShouldFail() throws Exception {
-		Git git = new Git(db);
-		git.commit().setAmend(true).setMessage("initial commit").call();
+		try (Git git = new Git(db)) {
+			git.commit().setAmend(true).setMessage("initial commit").call();
+		}
 	}
 
 	@Test
 	public void commitAmendWithoutAuthorShouldSetOriginalAuthorAndAuthorTime()
 			throws Exception {
-		Git git = new Git(db);
+		try (Git git = new Git(db)) {
+			writeTrashFile("file1", "file1");
+			git.add().addFilepattern("file1").call();
 
-		writeTrashFile("file1", "file1");
-		git.add().addFilepattern("file1").call();
+			final String authorName = "First Author";
+			final String authorEmail = "author@example.org";
+			final Date authorDate = new Date(1349621117000L);
+			PersonIdent firstAuthor = new PersonIdent(authorName, authorEmail,
+					authorDate, TimeZone.getTimeZone("UTC"));
+			git.commit().setMessage("initial commit").setAuthor(firstAuthor).call();
 
-		final String authorName = "First Author";
-		final String authorEmail = "author@example.org";
-		final Date authorDate = new Date(1349621117000L);
-		PersonIdent firstAuthor = new PersonIdent(authorName, authorEmail,
-				authorDate, TimeZone.getTimeZone("UTC"));
-		git.commit().setMessage("initial commit").setAuthor(firstAuthor).call();
+			RevCommit amended = git.commit().setAmend(true)
+					.setMessage("amend commit").call();
 
-		RevCommit amended = git.commit().setAmend(true)
-				.setMessage("amend commit").call();
-
-		PersonIdent amendedAuthor = amended.getAuthorIdent();
-		assertEquals(authorName, amendedAuthor.getName());
-		assertEquals(authorEmail, amendedAuthor.getEmailAddress());
-		assertEquals(authorDate.getTime(), amendedAuthor.getWhen().getTime());
+			PersonIdent amendedAuthor = amended.getAuthorIdent();
+			assertEquals(authorName, amendedAuthor.getName());
+			assertEquals(authorEmail, amendedAuthor.getEmailAddress());
+			assertEquals(authorDate.getTime(), amendedAuthor.getWhen().getTime());
+		}
 	}
 
 	@Test
 	public void commitAmendWithAuthorShouldUseIt() throws Exception {
-		Git git = new Git(db);
+		try (Git git = new Git(db)) {
+			writeTrashFile("file1", "file1");
+			git.add().addFilepattern("file1").call();
+			git.commit().setMessage("initial commit").call();
 
-		writeTrashFile("file1", "file1");
-		git.add().addFilepattern("file1").call();
-		git.commit().setMessage("initial commit").call();
+			RevCommit amended = git.commit().setAmend(true)
+					.setAuthor("New Author", "newauthor@example.org")
+					.setMessage("amend commit").call();
 
-		RevCommit amended = git.commit().setAmend(true)
-				.setAuthor("New Author", "newauthor@example.org")
-				.setMessage("amend commit").call();
+			PersonIdent amendedAuthor = amended.getAuthorIdent();
+			assertEquals("New Author", amendedAuthor.getName());
+			assertEquals("newauthor@example.org", amendedAuthor.getEmailAddress());
+		}
+	}
 
-		PersonIdent amendedAuthor = amended.getAuthorIdent();
-		assertEquals("New Author", amendedAuthor.getName());
-		assertEquals("newauthor@example.org", amendedAuthor.getEmailAddress());
+	@Test
+	public void commitEmptyCommits() throws Exception {
+		try (Git git = new Git(db)) {
+
+			writeTrashFile("file1", "file1");
+			git.add().addFilepattern("file1").call();
+			RevCommit initial = git.commit().setMessage("initial commit")
+					.call();
+
+			RevCommit emptyFollowUp = git.commit()
+					.setAuthor("New Author", "newauthor@example.org")
+					.setMessage("no change").call();
+
+			assertNotEquals(initial.getId(), emptyFollowUp.getId());
+			assertEquals(initial.getTree().getId(),
+					emptyFollowUp.getTree().getId());
+
+			try {
+				git.commit().setAuthor("New Author", "newauthor@example.org")
+						.setMessage("again no change").setAllowEmpty(false)
+						.call();
+				fail("Didn't get the expected EmtpyCommitException");
+			} catch (EmtpyCommitException e) {
+				// expect this exception
+			}
+		}
 	}
 
 	@Test
@@ -499,18 +537,19 @@
 				+ "[unmerged2, mode:100644, stage:3]",
 				indexState(0));
 
-		Git git = new Git(db);
-		RevCommit commit = git.commit().setOnly("unmerged1")
-				.setMessage("Only one file").call();
+		try (Git git = new Git(db)) {
+			RevCommit commit = git.commit().setOnly("unmerged1")
+					.setMessage("Only one file").call();
 
-		assertEquals("[other, mode:100644]" + "[unmerged1, mode:100644]"
-				+ "[unmerged2, mode:100644, stage:1]"
-				+ "[unmerged2, mode:100644, stage:2]"
-				+ "[unmerged2, mode:100644, stage:3]",
-				indexState(0));
+			assertEquals("[other, mode:100644]" + "[unmerged1, mode:100644]"
+					+ "[unmerged2, mode:100644, stage:1]"
+					+ "[unmerged2, mode:100644, stage:2]"
+					+ "[unmerged2, mode:100644, stage:3]",
+					indexState(0));
 
-		try (TreeWalk walk = TreeWalk.forPath(db, "unmerged1", commit.getTree())) {
-			assertEquals(FileMode.REGULAR_FILE, walk.getFileMode(0));
+			try (TreeWalk walk = TreeWalk.forPath(db, "unmerged1", commit.getTree())) {
+				assertEquals(FileMode.REGULAR_FILE, walk.getFileMode(0));
+			}
 		}
 	}
 
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitOnlyTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitOnlyTest.java
index 336a335..5f7434b 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitOnlyTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitOnlyTest.java
@@ -1294,10 +1294,12 @@
 		try {
 			final Repository repo = git.getRepository();
 			final ObjectId headId = repo.resolve(Constants.HEAD + "^{commit}");
-			final TreeWalk tw = TreeWalk.forPath(repo, path,
-					new RevWalk(repo).parseTree(headId));
-			return new String(tw.getObjectReader().open(tw.getObjectId(0))
-					.getBytes());
+			try (RevWalk rw = new RevWalk(repo)) {
+				final TreeWalk tw = TreeWalk.forPath(repo, path,
+						rw.parseTree(headId));
+				return new String(tw.getObjectReader().open(tw.getObjectId(0))
+						.getBytes());
+			}
 		} catch (Exception e) {
 			return "";
 		}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/DiffCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/DiffCommandTest.java
index 4ad01cf..4f5b50d 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/DiffCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/DiffCommandTest.java
@@ -70,32 +70,33 @@
 		File folder = new File(db.getWorkTree(), "folder");
 		folder.mkdir();
 		write(new File(folder, "folder.txt"), "folder");
-		Git git = new Git(db);
-		git.add().addFilepattern(".").call();
-		git.commit().setMessage("Initial commit").call();
-		write(new File(folder, "folder.txt"), "folder change");
+		try (Git git = new Git(db)) {
+			git.add().addFilepattern(".").call();
+			git.commit().setMessage("Initial commit").call();
+			write(new File(folder, "folder.txt"), "folder change");
 
-		OutputStream out = new ByteArrayOutputStream();
-		List<DiffEntry> entries = git.diff().setOutputStream(out).call();
-		assertEquals(1, entries.size());
-		assertEquals(ChangeType.MODIFY, entries.get(0)
-				.getChangeType());
-		assertEquals("folder/folder.txt", entries.get(0)
-				.getOldPath());
-		assertEquals("folder/folder.txt", entries.get(0)
-				.getNewPath());
+			OutputStream out = new ByteArrayOutputStream();
+			List<DiffEntry> entries = git.diff().setOutputStream(out).call();
+			assertEquals(1, entries.size());
+			assertEquals(ChangeType.MODIFY, entries.get(0)
+					.getChangeType());
+			assertEquals("folder/folder.txt", entries.get(0)
+					.getOldPath());
+			assertEquals("folder/folder.txt", entries.get(0)
+					.getNewPath());
 
-		String actual = out.toString();
-		String expected = "diff --git a/folder/folder.txt b/folder/folder.txt\n"
-				+ "index 0119635..95c4c65 100644\n"
-				+ "--- a/folder/folder.txt\n"
-				+ "+++ b/folder/folder.txt\n"
-				+ "@@ -1 +1 @@\n"
-				+ "-folder\n"
-				+ "\\ No newline at end of file\n"
-				+ "+folder change\n"
-				+ "\\ No newline at end of file\n";
-		assertEquals(expected.toString(), actual);
+			String actual = out.toString();
+			String expected = "diff --git a/folder/folder.txt b/folder/folder.txt\n"
+					+ "index 0119635..95c4c65 100644\n"
+					+ "--- a/folder/folder.txt\n"
+					+ "+++ b/folder/folder.txt\n"
+					+ "@@ -1 +1 @@\n"
+					+ "-folder\n"
+					+ "\\ No newline at end of file\n"
+					+ "+folder change\n"
+					+ "\\ No newline at end of file\n";
+			assertEquals(expected.toString(), actual);
+		}
 	}
 
 	@Test
@@ -103,33 +104,34 @@
 		write(new File(db.getWorkTree(), "test.txt"), "test");
 		File folder = new File(db.getWorkTree(), "folder");
 		folder.mkdir();
-		Git git = new Git(db);
-		git.add().addFilepattern(".").call();
-		git.commit().setMessage("Initial commit").call();
-		write(new File(folder, "folder.txt"), "folder");
-		git.add().addFilepattern(".").call();
+		try (Git git = new Git(db)) {
+			git.add().addFilepattern(".").call();
+			git.commit().setMessage("Initial commit").call();
+			write(new File(folder, "folder.txt"), "folder");
+			git.add().addFilepattern(".").call();
 
-		OutputStream out = new ByteArrayOutputStream();
-		List<DiffEntry> entries = git.diff().setOutputStream(out)
-				.setCached(true).call();
-		assertEquals(1, entries.size());
-		assertEquals(ChangeType.ADD, entries.get(0)
-				.getChangeType());
-		assertEquals("/dev/null", entries.get(0)
-				.getOldPath());
-		assertEquals("folder/folder.txt", entries.get(0)
-				.getNewPath());
+			OutputStream out = new ByteArrayOutputStream();
+			List<DiffEntry> entries = git.diff().setOutputStream(out)
+					.setCached(true).call();
+			assertEquals(1, entries.size());
+			assertEquals(ChangeType.ADD, entries.get(0)
+					.getChangeType());
+			assertEquals("/dev/null", entries.get(0)
+					.getOldPath());
+			assertEquals("folder/folder.txt", entries.get(0)
+					.getNewPath());
 
-		String actual = out.toString();
-		String expected = "diff --git a/folder/folder.txt b/folder/folder.txt\n"
-				+ "new file mode 100644\n"
-				+ "index 0000000..0119635\n"
-				+ "--- /dev/null\n"
-				+ "+++ b/folder/folder.txt\n"
-				+ "@@ -0,0 +1 @@\n"
-				+ "+folder\n"
-				+ "\\ No newline at end of file\n";
-		assertEquals(expected.toString(), actual);
+			String actual = out.toString();
+			String expected = "diff --git a/folder/folder.txt b/folder/folder.txt\n"
+					+ "new file mode 100644\n"
+					+ "index 0000000..0119635\n"
+					+ "--- /dev/null\n"
+					+ "+++ b/folder/folder.txt\n"
+					+ "@@ -0,0 +1 @@\n"
+					+ "+folder\n"
+					+ "\\ No newline at end of file\n";
+			assertEquals(expected.toString(), actual);
+		}
 	}
 
 	@Test
@@ -138,107 +140,109 @@
 		File folder = new File(db.getWorkTree(), "folder");
 		folder.mkdir();
 		write(new File(folder, "folder.txt"), "folder");
-		Git git = new Git(db);
-		git.add().addFilepattern(".").call();
-		git.commit().setMessage("Initial commit").call();
-		write(new File(folder, "folder.txt"), "folder change");
-		git.add().addFilepattern(".").call();
-		git.commit().setMessage("second commit").call();
-		write(new File(folder, "folder.txt"), "second folder change");
-		git.add().addFilepattern(".").call();
-		git.commit().setMessage("third commit").call();
+		try (Git git = new Git(db)) {
+			git.add().addFilepattern(".").call();
+			git.commit().setMessage("Initial commit").call();
+			write(new File(folder, "folder.txt"), "folder change");
+			git.add().addFilepattern(".").call();
+			git.commit().setMessage("second commit").call();
+			write(new File(folder, "folder.txt"), "second folder change");
+			git.add().addFilepattern(".").call();
+			git.commit().setMessage("third commit").call();
 
-		// bad filter
-		DiffCommand diff = git.diff().setShowNameAndStatusOnly(true)
-				.setPathFilter(PathFilter.create("test.txt"))
-				.setOldTree(getTreeIterator("HEAD^^"))
-				.setNewTree(getTreeIterator("HEAD^"));
-		List<DiffEntry> entries = diff.call();
-		assertEquals(0, entries.size());
+			// bad filter
+			DiffCommand diff = git.diff().setShowNameAndStatusOnly(true)
+					.setPathFilter(PathFilter.create("test.txt"))
+					.setOldTree(getTreeIterator("HEAD^^"))
+					.setNewTree(getTreeIterator("HEAD^"));
+			List<DiffEntry> entries = diff.call();
+			assertEquals(0, entries.size());
 
-		// no filter, two commits
-		OutputStream out = new ByteArrayOutputStream();
-		diff = git.diff().setOutputStream(out)
-				.setOldTree(getTreeIterator("HEAD^^"))
-				.setNewTree(getTreeIterator("HEAD^"));
-		entries = diff.call();
-		assertEquals(1, entries.size());
-		assertEquals(ChangeType.MODIFY, entries.get(0).getChangeType());
-		assertEquals("folder/folder.txt", entries.get(0).getOldPath());
-		assertEquals("folder/folder.txt", entries.get(0).getNewPath());
+			// no filter, two commits
+			OutputStream out = new ByteArrayOutputStream();
+			diff = git.diff().setOutputStream(out)
+					.setOldTree(getTreeIterator("HEAD^^"))
+					.setNewTree(getTreeIterator("HEAD^"));
+			entries = diff.call();
+			assertEquals(1, entries.size());
+			assertEquals(ChangeType.MODIFY, entries.get(0).getChangeType());
+			assertEquals("folder/folder.txt", entries.get(0).getOldPath());
+			assertEquals("folder/folder.txt", entries.get(0).getNewPath());
 
-		String actual = out.toString();
-		String expected = "diff --git a/folder/folder.txt b/folder/folder.txt\n"
-				+ "index 0119635..95c4c65 100644\n"
-				+ "--- a/folder/folder.txt\n"
-				+ "+++ b/folder/folder.txt\n"
-				+ "@@ -1 +1 @@\n"
-				+ "-folder\n"
-				+ "\\ No newline at end of file\n"
-				+ "+folder change\n"
-				+ "\\ No newline at end of file\n";
-		assertEquals(expected.toString(), actual);
+			String actual = out.toString();
+			String expected = "diff --git a/folder/folder.txt b/folder/folder.txt\n"
+					+ "index 0119635..95c4c65 100644\n"
+					+ "--- a/folder/folder.txt\n"
+					+ "+++ b/folder/folder.txt\n"
+					+ "@@ -1 +1 @@\n"
+					+ "-folder\n"
+					+ "\\ No newline at end of file\n"
+					+ "+folder change\n"
+					+ "\\ No newline at end of file\n";
+			assertEquals(expected.toString(), actual);
+		}
 	}
 
 	@Test
 	public void testDiffWithPrefixes() throws Exception {
 		write(new File(db.getWorkTree(), "test.txt"), "test");
-		Git git = new Git(db);
-		git.add().addFilepattern(".").call();
-		git.commit().setMessage("Initial commit").call();
-		write(new File(db.getWorkTree(), "test.txt"), "test change");
+		try (Git git = new Git(db)) {
+			git.add().addFilepattern(".").call();
+			git.commit().setMessage("Initial commit").call();
+			write(new File(db.getWorkTree(), "test.txt"), "test change");
 
-		OutputStream out = new ByteArrayOutputStream();
-		git.diff().setOutputStream(out).setSourcePrefix("old/")
-				.setDestinationPrefix("new/")
-				.call();
+			OutputStream out = new ByteArrayOutputStream();
+			git.diff().setOutputStream(out).setSourcePrefix("old/")
+					.setDestinationPrefix("new/").call();
 
-		String actual = out.toString();
-		String expected = "diff --git old/test.txt new/test.txt\n"
-				+ "index 30d74d2..4dba797 100644\n" + "--- old/test.txt\n"
-				+ "+++ new/test.txt\n" + "@@ -1 +1 @@\n" + "-test\n"
-				+ "\\ No newline at end of file\n" + "+test change\n"
-				+ "\\ No newline at end of file\n";
-		assertEquals(expected.toString(), actual);
+			String actual = out.toString();
+			String expected = "diff --git old/test.txt new/test.txt\n"
+					+ "index 30d74d2..4dba797 100644\n" + "--- old/test.txt\n"
+					+ "+++ new/test.txt\n" + "@@ -1 +1 @@\n" + "-test\n"
+					+ "\\ No newline at end of file\n" + "+test change\n"
+					+ "\\ No newline at end of file\n";
+			assertEquals(expected.toString(), actual);
+		}
 	}
 
 	@Test
 	public void testDiffWithNegativeLineCount() throws Exception {
 		write(new File(db.getWorkTree(), "test.txt"),
 				"0\n1\n2\n3\n4\n5\n6\n7\n8\n9");
-		Git git = new Git(db);
-		git.add().addFilepattern(".").call();
-		git.commit().setMessage("Initial commit").call();
-		write(new File(db.getWorkTree(), "test.txt"),
-				"0\n1\n2\n3\n4a\n5\n6\n7\n8\n9");
+		try (Git git = new Git(db)) {
+			git.add().addFilepattern(".").call();
+			git.commit().setMessage("Initial commit").call();
+			write(new File(db.getWorkTree(), "test.txt"),
+					"0\n1\n2\n3\n4a\n5\n6\n7\n8\n9");
 
-		OutputStream out = new ByteArrayOutputStream();
-		git.diff().setOutputStream(out).setContextLines(1)
-				.call();
+			OutputStream out = new ByteArrayOutputStream();
+			git.diff().setOutputStream(out).setContextLines(1).call();
 
-		String actual = out.toString();
-		String expected = "diff --git a/test.txt b/test.txt\n"
-				+ "index f55b5c9..c5ec8fd 100644\n" + "--- a/test.txt\n"
-				+ "+++ b/test.txt\n" + "@@ -4,3 +4,3 @@\n" + " 3\n" + "-4\n"
-				+ "+4a\n" + " 5\n";
-		assertEquals(expected.toString(), actual);
+			String actual = out.toString();
+			String expected = "diff --git a/test.txt b/test.txt\n"
+					+ "index f55b5c9..c5ec8fd 100644\n" + "--- a/test.txt\n"
+					+ "+++ b/test.txt\n" + "@@ -4,3 +4,3 @@\n" + " 3\n" + "-4\n"
+					+ "+4a\n" + " 5\n";
+			assertEquals(expected.toString(), actual);
+		}
 	}
 
 	@Test
 	public void testNoOutputStreamSet() throws Exception {
 		File file = writeTrashFile("test.txt", "a");
 		assertTrue(file.setLastModified(file.lastModified() - 5000));
-		Git git = new Git(db);
-		git.add().addFilepattern(".").call();
-		write(file, "b");
+		try (Git git = new Git(db)) {
+			git.add().addFilepattern(".").call();
+			write(file, "b");
 
-		List<DiffEntry> diffs = git.diff().call();
-		assertNotNull(diffs);
-		assertEquals(1, diffs.size());
-		DiffEntry diff = diffs.get(0);
-		assertEquals(ChangeType.MODIFY, diff.getChangeType());
-		assertEquals("test.txt", diff.getOldPath());
-		assertEquals("test.txt", diff.getNewPath());
+			List<DiffEntry> diffs = git.diff().call();
+			assertNotNull(diffs);
+			assertEquals(1, diffs.size());
+			DiffEntry diff = diffs.get(0);
+			assertEquals(ChangeType.MODIFY, diff.getChangeType());
+			assertEquals("test.txt", diff.getOldPath());
+			assertEquals("test.txt", diff.getNewPath());
+		}
 	}
 
 	private AbstractTreeIterator getTreeIterator(String name)
@@ -247,8 +251,9 @@
 		if (id == null)
 			throw new IllegalArgumentException(name);
 		final CanonicalTreeParser p = new CanonicalTreeParser();
-		try (ObjectReader or = db.newObjectReader()) {
-			p.reset(or, new RevWalk(db).parseTree(id));
+		try (ObjectReader or = db.newObjectReader();
+				RevWalk rw = new RevWalk(db)) {
+			p.reset(or, rw.parseTree(id));
 			return p;
 		}
 	}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/GitConstructionTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/GitConstructionTest.java
index 2220a53..3bff8f2 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/GitConstructionTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/GitConstructionTest.java
@@ -66,11 +66,12 @@
 	@Before
 	public void setUp() throws Exception {
 		super.setUp();
-		Git git = new Git(db);
-		git.commit().setMessage("initial commit").call();
-		writeTrashFile("Test.txt", "Hello world");
-		git.add().addFilepattern("Test.txt").call();
-		git.commit().setMessage("Initial commit").call();
+		try (Git git = new Git(db)) {
+			git.commit().setMessage("initial commit").call();
+			writeTrashFile("Test.txt", "Hello world");
+			git.add().addFilepattern("Test.txt").call();
+			git.commit().setMessage("Initial commit").call();
+		}
 
 		bareRepo = Git.cloneRepository().setBare(true)
 				.setURI(db.getDirectory().toURI().toString())
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/HugeFileTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/HugeFileTest.java
index 5449d02..4208f4d 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/HugeFileTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/HugeFileTest.java
@@ -51,6 +51,8 @@
 
 import org.eclipse.jgit.api.ResetCommand.ResetType;
 import org.eclipse.jgit.junit.RepositoryTestCase;
+import org.junit.After;
+import org.junit.Before;
 import org.junit.Ignore;
 import org.junit.Test;
 
@@ -60,12 +62,26 @@
 
 	private long lastt = t;
 
+	private Git git;
+
 	private void measure(String name) {
 		long c = System.currentTimeMillis();
 		System.out.println(name + ", dt=" + (c - lastt) / 1000.0 + "s");
 		lastt = c;
 	}
 
+	@Before
+	public void before() {
+		git = new Git(db);
+	}
+
+	@After
+	public void after() {
+		if (git != null) {
+			git.close();
+		}
+	}
+
 	@Ignore("Test takes way too long (~10 minutes) to be part of the standard suite")
 	@Test
 	public void testAddHugeFile() throws Exception {
@@ -75,7 +91,6 @@
 		rf.setLength(4429185024L);
 		rf.close();
 		measure("Created file");
-		Git git = new Git(db);
 
 		git.add().addFilepattern("a.txt").call();
 		measure("Added file");
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/MergeCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/MergeCommandTest.java
index 0c0c6e5..cb3dbf1 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/MergeCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/MergeCommandTest.java
@@ -43,11 +43,14 @@
  */
 package org.eclipse.jgit.api;
 
+import static org.eclipse.jgit.lib.Constants.MASTER;
+import static org.eclipse.jgit.lib.Constants.R_HEADS;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
 
 import java.io.File;
 import java.util.Iterator;
@@ -94,11 +97,12 @@
 
 	@Test
 	public void testMergeInItself() throws Exception {
-		Git git = new Git(db);
-		git.commit().setMessage("initial commit").call();
+		try (Git git = new Git(db)) {
+			git.commit().setMessage("initial commit").call();
 
-		MergeResult result = git.merge().include(db.getRef(Constants.HEAD)).call();
-		assertEquals(MergeResult.MergeStatus.ALREADY_UP_TO_DATE, result.getMergeStatus());
+			MergeResult result = git.merge().include(db.exactRef(Constants.HEAD)).call();
+			assertEquals(MergeResult.MergeStatus.ALREADY_UP_TO_DATE, result.getMergeStatus());
+		}
 		// no reflog entry written by merge
 		assertEquals("commit (initial): initial commit",
 				db
@@ -110,14 +114,15 @@
 
 	@Test
 	public void testAlreadyUpToDate() throws Exception {
-		Git git = new Git(db);
-		RevCommit first = git.commit().setMessage("initial commit").call();
-		createBranch(first, "refs/heads/branch1");
+		try (Git git = new Git(db)) {
+			RevCommit first = git.commit().setMessage("initial commit").call();
+			createBranch(first, "refs/heads/branch1");
 
-		RevCommit second = git.commit().setMessage("second commit").call();
-		MergeResult result = git.merge().include(db.getRef("refs/heads/branch1")).call();
-		assertEquals(MergeResult.MergeStatus.ALREADY_UP_TO_DATE, result.getMergeStatus());
-		assertEquals(second, result.getNewHead());
+			RevCommit second = git.commit().setMessage("second commit").call();
+			MergeResult result = git.merge().include(db.exactRef("refs/heads/branch1")).call();
+			assertEquals(MergeResult.MergeStatus.ALREADY_UP_TO_DATE, result.getMergeStatus());
+			assertEquals(second, result.getNewHead());
+		}
 		// no reflog entry written by merge
 		assertEquals("commit: second commit", db
 				.getReflogReader(Constants.HEAD).getLastEntry().getComment());
@@ -127,18 +132,19 @@
 
 	@Test
 	public void testFastForward() throws Exception {
-		Git git = new Git(db);
-		RevCommit first = git.commit().setMessage("initial commit").call();
-		createBranch(first, "refs/heads/branch1");
+		try (Git git = new Git(db)) {
+			RevCommit first = git.commit().setMessage("initial commit").call();
+			createBranch(first, "refs/heads/branch1");
 
-		RevCommit second = git.commit().setMessage("second commit").call();
+			RevCommit second = git.commit().setMessage("second commit").call();
 
-		checkoutBranch("refs/heads/branch1");
+			checkoutBranch("refs/heads/branch1");
 
-		MergeResult result = git.merge().include(db.getRef(Constants.MASTER)).call();
+			MergeResult result = git.merge().include(db.exactRef(R_HEADS + MASTER)).call();
 
-		assertEquals(MergeResult.MergeStatus.FAST_FORWARD, result.getMergeStatus());
-		assertEquals(second, result.getNewHead());
+			assertEquals(MergeResult.MergeStatus.FAST_FORWARD, result.getMergeStatus());
+			assertEquals(second, result.getNewHead());
+		}
 		assertEquals("merge refs/heads/master: Fast-forward",
 				db.getReflogReader(Constants.HEAD).getLastEntry().getComment());
 		assertEquals("merge refs/heads/master: Fast-forward",
@@ -147,20 +153,21 @@
 
 	@Test
 	public void testFastForwardNoCommit() throws Exception {
-		Git git = new Git(db);
-		RevCommit first = git.commit().setMessage("initial commit").call();
-		createBranch(first, "refs/heads/branch1");
+		try (Git git = new Git(db)) {
+			RevCommit first = git.commit().setMessage("initial commit").call();
+			createBranch(first, "refs/heads/branch1");
 
-		RevCommit second = git.commit().setMessage("second commit").call();
+			RevCommit second = git.commit().setMessage("second commit").call();
 
-		checkoutBranch("refs/heads/branch1");
+			checkoutBranch("refs/heads/branch1");
 
-		MergeResult result = git.merge().include(db.getRef(Constants.MASTER))
-				.setCommit(false).call();
+			MergeResult result = git.merge().include(db.exactRef(R_HEADS + MASTER))
+					.setCommit(false).call();
 
-		assertEquals(MergeResult.MergeStatus.FAST_FORWARD,
-				result.getMergeStatus());
-		assertEquals(second, result.getNewHead());
+			assertEquals(MergeResult.MergeStatus.FAST_FORWARD,
+					result.getMergeStatus());
+			assertEquals(second, result.getNewHead());
+		}
 		assertEquals("merge refs/heads/master: Fast-forward", db
 				.getReflogReader(Constants.HEAD).getLastEntry().getComment());
 		assertEquals("merge refs/heads/master: Fast-forward", db
@@ -169,29 +176,29 @@
 
 	@Test
 	public void testFastForwardWithFiles() throws Exception {
-		Git git = new Git(db);
+		try (Git git = new Git(db)) {
+			writeTrashFile("file1", "file1");
+			git.add().addFilepattern("file1").call();
+			RevCommit first = git.commit().setMessage("initial commit").call();
 
-		writeTrashFile("file1", "file1");
-		git.add().addFilepattern("file1").call();
-		RevCommit first = git.commit().setMessage("initial commit").call();
+			assertTrue(new File(db.getWorkTree(), "file1").exists());
+			createBranch(first, "refs/heads/branch1");
 
-		assertTrue(new File(db.getWorkTree(), "file1").exists());
-		createBranch(first, "refs/heads/branch1");
+			writeTrashFile("file2", "file2");
+			git.add().addFilepattern("file2").call();
+			RevCommit second = git.commit().setMessage("second commit").call();
+			assertTrue(new File(db.getWorkTree(), "file2").exists());
 
-		writeTrashFile("file2", "file2");
-		git.add().addFilepattern("file2").call();
-		RevCommit second = git.commit().setMessage("second commit").call();
-		assertTrue(new File(db.getWorkTree(), "file2").exists());
+			checkoutBranch("refs/heads/branch1");
+			assertFalse(new File(db.getWorkTree(), "file2").exists());
 
-		checkoutBranch("refs/heads/branch1");
-		assertFalse(new File(db.getWorkTree(), "file2").exists());
+			MergeResult result = git.merge().include(db.exactRef(R_HEADS + MASTER)).call();
 
-		MergeResult result = git.merge().include(db.getRef(Constants.MASTER)).call();
-
-		assertTrue(new File(db.getWorkTree(), "file1").exists());
-		assertTrue(new File(db.getWorkTree(), "file2").exists());
-		assertEquals(MergeResult.MergeStatus.FAST_FORWARD, result.getMergeStatus());
-		assertEquals(second, result.getNewHead());
+			assertTrue(new File(db.getWorkTree(), "file1").exists());
+			assertTrue(new File(db.getWorkTree(), "file2").exists());
+			assertEquals(MergeResult.MergeStatus.FAST_FORWARD, result.getMergeStatus());
+			assertEquals(second, result.getNewHead());
+		}
 		assertEquals("merge refs/heads/master: Fast-forward",
 				db.getReflogReader(Constants.HEAD).getLastEntry().getComment());
 		assertEquals("merge refs/heads/master: Fast-forward",
@@ -200,56 +207,56 @@
 
 	@Test
 	public void testMultipleHeads() throws Exception {
-		Git git = new Git(db);
+		try (Git git = new Git(db)) {
+			writeTrashFile("file1", "file1");
+			git.add().addFilepattern("file1").call();
+			RevCommit first = git.commit().setMessage("initial commit").call();
+			createBranch(first, "refs/heads/branch1");
 
-		writeTrashFile("file1", "file1");
-		git.add().addFilepattern("file1").call();
-		RevCommit first = git.commit().setMessage("initial commit").call();
-		createBranch(first, "refs/heads/branch1");
+			writeTrashFile("file2", "file2");
+			git.add().addFilepattern("file2").call();
+			RevCommit second = git.commit().setMessage("second commit").call();
 
-		writeTrashFile("file2", "file2");
-		git.add().addFilepattern("file2").call();
-		RevCommit second = git.commit().setMessage("second commit").call();
+			writeTrashFile("file3", "file3");
+			git.add().addFilepattern("file3").call();
+			git.commit().setMessage("third commit").call();
 
-		writeTrashFile("file3", "file3");
-		git.add().addFilepattern("file3").call();
-		git.commit().setMessage("third commit").call();
+			checkoutBranch("refs/heads/branch1");
+			assertFalse(new File(db.getWorkTree(), "file2").exists());
+			assertFalse(new File(db.getWorkTree(), "file3").exists());
 
-		checkoutBranch("refs/heads/branch1");
-		assertFalse(new File(db.getWorkTree(), "file2").exists());
-		assertFalse(new File(db.getWorkTree(), "file3").exists());
-
-		MergeCommand merge = git.merge();
-		merge.include(second.getId());
-		merge.include(db.getRef(Constants.MASTER));
-		try {
-			merge.call();
-			fail("Expected exception not thrown when merging multiple heads");
-		} catch (InvalidMergeHeadsException e) {
-			// expected this exception
+			MergeCommand merge = git.merge();
+			merge.include(second.getId());
+			merge.include(db.exactRef(R_HEADS + MASTER));
+			try {
+				merge.call();
+				fail("Expected exception not thrown when merging multiple heads");
+			} catch (InvalidMergeHeadsException e) {
+				// expected this exception
+			}
 		}
 	}
 
 	@Theory
 	public void testMergeSuccessAllStrategies(MergeStrategy mergeStrategy)
 			throws Exception {
-		Git git = new Git(db);
+		try (Git git = new Git(db)) {
+			RevCommit first = git.commit().setMessage("first").call();
+			createBranch(first, "refs/heads/side");
 
-		RevCommit first = git.commit().setMessage("first").call();
-		createBranch(first, "refs/heads/side");
+			writeTrashFile("a", "a");
+			git.add().addFilepattern("a").call();
+			git.commit().setMessage("second").call();
 
-		writeTrashFile("a", "a");
-		git.add().addFilepattern("a").call();
-		git.commit().setMessage("second").call();
+			checkoutBranch("refs/heads/side");
+			writeTrashFile("b", "b");
+			git.add().addFilepattern("b").call();
+			git.commit().setMessage("third").call();
 
-		checkoutBranch("refs/heads/side");
-		writeTrashFile("b", "b");
-		git.add().addFilepattern("b").call();
-		git.commit().setMessage("third").call();
-
-		MergeResult result = git.merge().setStrategy(mergeStrategy)
-				.include(db.getRef(Constants.MASTER)).call();
-		assertEquals(MergeStatus.MERGED, result.getMergeStatus());
+			MergeResult result = git.merge().setStrategy(mergeStrategy)
+					.include(db.exactRef(R_HEADS + MASTER)).call();
+			assertEquals(MergeStatus.MERGED, result.getMergeStatus());
+		}
 		assertEquals(
 				"merge refs/heads/master: Merge made by "
 						+ mergeStrategy.getName() + ".",
@@ -263,604 +270,604 @@
 	@Theory
 	public void testMergeSuccessAllStrategiesNoCommit(
 			MergeStrategy mergeStrategy) throws Exception {
-		Git git = new Git(db);
+		try (Git git = new Git(db)) {
+			RevCommit first = git.commit().setMessage("first").call();
+			createBranch(first, "refs/heads/side");
 
-		RevCommit first = git.commit().setMessage("first").call();
-		createBranch(first, "refs/heads/side");
+			writeTrashFile("a", "a");
+			git.add().addFilepattern("a").call();
+			git.commit().setMessage("second").call();
 
-		writeTrashFile("a", "a");
-		git.add().addFilepattern("a").call();
-		git.commit().setMessage("second").call();
+			checkoutBranch("refs/heads/side");
+			writeTrashFile("b", "b");
+			git.add().addFilepattern("b").call();
+			RevCommit thirdCommit = git.commit().setMessage("third").call();
 
-		checkoutBranch("refs/heads/side");
-		writeTrashFile("b", "b");
-		git.add().addFilepattern("b").call();
-		RevCommit thirdCommit = git.commit().setMessage("third").call();
-
-		MergeResult result = git.merge().setStrategy(mergeStrategy)
-				.setCommit(false)
-				.include(db.getRef(Constants.MASTER)).call();
-		assertEquals(MergeStatus.MERGED_NOT_COMMITTED, result.getMergeStatus());
-		assertEquals(db.getRef(Constants.HEAD).getTarget().getObjectId(),
-				thirdCommit.getId());
+			MergeResult result = git.merge().setStrategy(mergeStrategy)
+					.setCommit(false)
+					.include(db.exactRef(R_HEADS + MASTER)).call();
+			assertEquals(MergeStatus.MERGED_NOT_COMMITTED, result.getMergeStatus());
+			assertEquals(db.exactRef(Constants.HEAD).getTarget().getObjectId(),
+					thirdCommit.getId());
+		}
 	}
 
 	@Test
 	public void testContentMerge() throws Exception {
-		Git git = new Git(db);
+		try (Git git = new Git(db)) {
+			writeTrashFile("a", "1\na\n3\n");
+			writeTrashFile("b", "1\nb\n3\n");
+			writeTrashFile("c/c/c", "1\nc\n3\n");
+			git.add().addFilepattern("a").addFilepattern("b")
+					.addFilepattern("c/c/c").call();
+			RevCommit initialCommit = git.commit().setMessage("initial").call();
 
-		writeTrashFile("a", "1\na\n3\n");
-		writeTrashFile("b", "1\nb\n3\n");
-		writeTrashFile("c/c/c", "1\nc\n3\n");
-		git.add().addFilepattern("a").addFilepattern("b")
-				.addFilepattern("c/c/c").call();
-		RevCommit initialCommit = git.commit().setMessage("initial").call();
+			createBranch(initialCommit, "refs/heads/side");
+			checkoutBranch("refs/heads/side");
 
-		createBranch(initialCommit, "refs/heads/side");
-		checkoutBranch("refs/heads/side");
+			writeTrashFile("a", "1\na(side)\n3\n");
+			writeTrashFile("b", "1\nb(side)\n3\n");
+			git.add().addFilepattern("a").addFilepattern("b").call();
+			RevCommit secondCommit = git.commit().setMessage("side").call();
 
-		writeTrashFile("a", "1\na(side)\n3\n");
-		writeTrashFile("b", "1\nb(side)\n3\n");
-		git.add().addFilepattern("a").addFilepattern("b").call();
-		RevCommit secondCommit = git.commit().setMessage("side").call();
+			assertEquals("1\nb(side)\n3\n", read(new File(db.getWorkTree(), "b")));
+			checkoutBranch("refs/heads/master");
+			assertEquals("1\nb\n3\n", read(new File(db.getWorkTree(), "b")));
 
-		assertEquals("1\nb(side)\n3\n", read(new File(db.getWorkTree(), "b")));
-		checkoutBranch("refs/heads/master");
-		assertEquals("1\nb\n3\n", read(new File(db.getWorkTree(), "b")));
+			writeTrashFile("a", "1\na(main)\n3\n");
+			writeTrashFile("c/c/c", "1\nc(main)\n3\n");
+			git.add().addFilepattern("a").addFilepattern("c/c/c").call();
+			git.commit().setMessage("main").call();
 
-		writeTrashFile("a", "1\na(main)\n3\n");
-		writeTrashFile("c/c/c", "1\nc(main)\n3\n");
-		git.add().addFilepattern("a").addFilepattern("c/c/c").call();
-		git.commit().setMessage("main").call();
+			MergeResult result = git.merge().include(secondCommit.getId())
+					.setStrategy(MergeStrategy.RESOLVE).call();
+			assertEquals(MergeStatus.CONFLICTING, result.getMergeStatus());
 
-		MergeResult result = git.merge().include(secondCommit.getId())
-				.setStrategy(MergeStrategy.RESOLVE).call();
-		assertEquals(MergeStatus.CONFLICTING, result.getMergeStatus());
+			assertEquals(
+					"1\n<<<<<<< HEAD\na(main)\n=======\na(side)\n>>>>>>> 86503e7e397465588cc267b65d778538bffccb83\n3\n",
+					read(new File(db.getWorkTree(), "a")));
+			assertEquals("1\nb(side)\n3\n", read(new File(db.getWorkTree(), "b")));
+			assertEquals("1\nc(main)\n3\n",
+					read(new File(db.getWorkTree(), "c/c/c")));
 
-		assertEquals(
-				"1\n<<<<<<< HEAD\na(main)\n=======\na(side)\n>>>>>>> 86503e7e397465588cc267b65d778538bffccb83\n3\n",
-				read(new File(db.getWorkTree(), "a")));
-		assertEquals("1\nb(side)\n3\n", read(new File(db.getWorkTree(), "b")));
-		assertEquals("1\nc(main)\n3\n",
-				read(new File(db.getWorkTree(), "c/c/c")));
+			assertEquals(1, result.getConflicts().size());
+			assertEquals(3, result.getConflicts().get("a")[0].length);
 
-		assertEquals(1, result.getConflicts().size());
-		assertEquals(3, result.getConflicts().get("a")[0].length);
-
-		assertEquals(RepositoryState.MERGING, db.getRepositoryState());
+			assertEquals(RepositoryState.MERGING, db.getRepositoryState());
+		}
 	}
 
 	@Test
 	public void testMergeTag() throws Exception {
-		Git git = new Git(db);
+		try (Git git = new Git(db)) {
+			writeTrashFile("a", "a");
+			git.add().addFilepattern("a").call();
+			RevCommit initialCommit = git.commit().setMessage("initial").call();
 
-		writeTrashFile("a", "a");
-		git.add().addFilepattern("a").call();
-		RevCommit initialCommit = git.commit().setMessage("initial").call();
+			createBranch(initialCommit, "refs/heads/side");
+			checkoutBranch("refs/heads/side");
 
-		createBranch(initialCommit, "refs/heads/side");
-		checkoutBranch("refs/heads/side");
+			writeTrashFile("b", "b");
+			git.add().addFilepattern("b").call();
+			RevCommit secondCommit = git.commit().setMessage("side").call();
+			Ref tag = git.tag().setAnnotated(true).setMessage("my tag 01")
+					.setName("tag01").setObjectId(secondCommit).call();
 
-		writeTrashFile("b", "b");
-		git.add().addFilepattern("b").call();
-		RevCommit secondCommit = git.commit().setMessage("side").call();
-		Ref tag = git.tag().setAnnotated(true).setMessage("my tag 01")
-				.setName("tag01").setObjectId(secondCommit).call();
+			checkoutBranch("refs/heads/master");
 
-		checkoutBranch("refs/heads/master");
+			writeTrashFile("a", "a2");
+			git.add().addFilepattern("a").call();
+			git.commit().setMessage("main").call();
 
-		writeTrashFile("a", "a2");
-		git.add().addFilepattern("a").call();
-		git.commit().setMessage("main").call();
-
-		MergeResult result = git.merge().include(tag).setStrategy(MergeStrategy.RESOLVE).call();
-		assertEquals(MergeStatus.MERGED, result.getMergeStatus());
+			MergeResult result = git.merge().include(tag).setStrategy(MergeStrategy.RESOLVE).call();
+			assertEquals(MergeStatus.MERGED, result.getMergeStatus());
+		}
 	}
 
 	@Test
 	public void testMergeMessage() throws Exception {
-		Git git = new Git(db);
+		try (Git git = new Git(db)) {
+			writeTrashFile("a", "1\na\n3\n");
+			git.add().addFilepattern("a").call();
+			RevCommit initialCommit = git.commit().setMessage("initial").call();
 
-		writeTrashFile("a", "1\na\n3\n");
-		git.add().addFilepattern("a").call();
-		RevCommit initialCommit = git.commit().setMessage("initial").call();
+			createBranch(initialCommit, "refs/heads/side");
+			checkoutBranch("refs/heads/side");
 
-		createBranch(initialCommit, "refs/heads/side");
-		checkoutBranch("refs/heads/side");
+			writeTrashFile("a", "1\na(side)\n3\n");
+			git.add().addFilepattern("a").call();
+			git.commit().setMessage("side").call();
 
-		writeTrashFile("a", "1\na(side)\n3\n");
-		git.add().addFilepattern("a").call();
-		git.commit().setMessage("side").call();
+			checkoutBranch("refs/heads/master");
 
-		checkoutBranch("refs/heads/master");
+			writeTrashFile("a", "1\na(main)\n3\n");
+			git.add().addFilepattern("a").call();
+			git.commit().setMessage("main").call();
 
-		writeTrashFile("a", "1\na(main)\n3\n");
-		git.add().addFilepattern("a").call();
-		git.commit().setMessage("main").call();
+			Ref sideBranch = db.exactRef("refs/heads/side");
 
-		Ref sideBranch = db.getRef("side");
+			git.merge().include(sideBranch)
+					.setStrategy(MergeStrategy.RESOLVE).call();
 
-		git.merge().include(sideBranch)
-				.setStrategy(MergeStrategy.RESOLVE).call();
-
-		assertEquals("Merge branch 'side'\n\nConflicts:\n\ta\n",
-				db.readMergeCommitMsg());
+			assertEquals("Merge branch 'side'\n\nConflicts:\n\ta\n",
+					db.readMergeCommitMsg());
+		}
 
 	}
 
 	@Test
 	public void testMergeNonVersionedPaths() throws Exception {
-		Git git = new Git(db);
+		try (Git git = new Git(db)) {
+			writeTrashFile("a", "1\na\n3\n");
+			writeTrashFile("b", "1\nb\n3\n");
+			writeTrashFile("c/c/c", "1\nc\n3\n");
+			git.add().addFilepattern("a").addFilepattern("b")
+					.addFilepattern("c/c/c").call();
+			RevCommit initialCommit = git.commit().setMessage("initial").call();
 
-		writeTrashFile("a", "1\na\n3\n");
-		writeTrashFile("b", "1\nb\n3\n");
-		writeTrashFile("c/c/c", "1\nc\n3\n");
-		git.add().addFilepattern("a").addFilepattern("b")
-				.addFilepattern("c/c/c").call();
-		RevCommit initialCommit = git.commit().setMessage("initial").call();
+			createBranch(initialCommit, "refs/heads/side");
+			checkoutBranch("refs/heads/side");
 
-		createBranch(initialCommit, "refs/heads/side");
-		checkoutBranch("refs/heads/side");
+			writeTrashFile("a", "1\na(side)\n3\n");
+			writeTrashFile("b", "1\nb(side)\n3\n");
+			git.add().addFilepattern("a").addFilepattern("b").call();
+			RevCommit secondCommit = git.commit().setMessage("side").call();
 
-		writeTrashFile("a", "1\na(side)\n3\n");
-		writeTrashFile("b", "1\nb(side)\n3\n");
-		git.add().addFilepattern("a").addFilepattern("b").call();
-		RevCommit secondCommit = git.commit().setMessage("side").call();
+			assertEquals("1\nb(side)\n3\n", read(new File(db.getWorkTree(), "b")));
+			checkoutBranch("refs/heads/master");
+			assertEquals("1\nb\n3\n", read(new File(db.getWorkTree(), "b")));
 
-		assertEquals("1\nb(side)\n3\n", read(new File(db.getWorkTree(), "b")));
-		checkoutBranch("refs/heads/master");
-		assertEquals("1\nb\n3\n", read(new File(db.getWorkTree(), "b")));
+			writeTrashFile("a", "1\na(main)\n3\n");
+			writeTrashFile("c/c/c", "1\nc(main)\n3\n");
+			git.add().addFilepattern("a").addFilepattern("c/c/c").call();
+			git.commit().setMessage("main").call();
 
-		writeTrashFile("a", "1\na(main)\n3\n");
-		writeTrashFile("c/c/c", "1\nc(main)\n3\n");
-		git.add().addFilepattern("a").addFilepattern("c/c/c").call();
-		git.commit().setMessage("main").call();
+			writeTrashFile("d", "1\nd\n3\n");
+			assertTrue(new File(db.getWorkTree(), "e").mkdir());
 
-		writeTrashFile("d", "1\nd\n3\n");
-		assertTrue(new File(db.getWorkTree(), "e").mkdir());
+			MergeResult result = git.merge().include(secondCommit.getId())
+					.setStrategy(MergeStrategy.RESOLVE).call();
+			assertEquals(MergeStatus.CONFLICTING, result.getMergeStatus());
 
-		MergeResult result = git.merge().include(secondCommit.getId())
-				.setStrategy(MergeStrategy.RESOLVE).call();
-		assertEquals(MergeStatus.CONFLICTING, result.getMergeStatus());
+			assertEquals(
+					"1\n<<<<<<< HEAD\na(main)\n=======\na(side)\n>>>>>>> 86503e7e397465588cc267b65d778538bffccb83\n3\n",
+					read(new File(db.getWorkTree(), "a")));
+			assertEquals("1\nb(side)\n3\n", read(new File(db.getWorkTree(), "b")));
+			assertEquals("1\nc(main)\n3\n",
+					read(new File(db.getWorkTree(), "c/c/c")));
+			assertEquals("1\nd\n3\n", read(new File(db.getWorkTree(), "d")));
+			File dir = new File(db.getWorkTree(), "e");
+			assertTrue(dir.isDirectory());
 
-		assertEquals(
-				"1\n<<<<<<< HEAD\na(main)\n=======\na(side)\n>>>>>>> 86503e7e397465588cc267b65d778538bffccb83\n3\n",
-				read(new File(db.getWorkTree(), "a")));
-		assertEquals("1\nb(side)\n3\n", read(new File(db.getWorkTree(), "b")));
-		assertEquals("1\nc(main)\n3\n",
-				read(new File(db.getWorkTree(), "c/c/c")));
-		assertEquals("1\nd\n3\n", read(new File(db.getWorkTree(), "d")));
-		File dir = new File(db.getWorkTree(), "e");
-		assertTrue(dir.isDirectory());
+			assertEquals(1, result.getConflicts().size());
+			assertEquals(3, result.getConflicts().get("a")[0].length);
 
-		assertEquals(1, result.getConflicts().size());
-		assertEquals(3, result.getConflicts().get("a")[0].length);
-
-		assertEquals(RepositoryState.MERGING, db.getRepositoryState());
+			assertEquals(RepositoryState.MERGING, db.getRepositoryState());
+		}
 	}
 
 	@Test
 	public void testMultipleCreations() throws Exception {
-		Git git = new Git(db);
+		try (Git git = new Git(db)) {
+			writeTrashFile("a", "1\na\n3\n");
+			git.add().addFilepattern("a").call();
+			RevCommit initialCommit = git.commit().setMessage("initial").call();
 
-		writeTrashFile("a", "1\na\n3\n");
-		git.add().addFilepattern("a").call();
-		RevCommit initialCommit = git.commit().setMessage("initial").call();
+			createBranch(initialCommit, "refs/heads/side");
+			checkoutBranch("refs/heads/side");
 
-		createBranch(initialCommit, "refs/heads/side");
-		checkoutBranch("refs/heads/side");
+			writeTrashFile("b", "1\nb(side)\n3\n");
+			git.add().addFilepattern("b").call();
+			RevCommit secondCommit = git.commit().setMessage("side").call();
 
-		writeTrashFile("b", "1\nb(side)\n3\n");
-		git.add().addFilepattern("b").call();
-		RevCommit secondCommit = git.commit().setMessage("side").call();
+			checkoutBranch("refs/heads/master");
 
-		checkoutBranch("refs/heads/master");
+			writeTrashFile("b", "1\nb(main)\n3\n");
+			git.add().addFilepattern("b").call();
+			git.commit().setMessage("main").call();
 
-		writeTrashFile("b", "1\nb(main)\n3\n");
-		git.add().addFilepattern("b").call();
-		git.commit().setMessage("main").call();
-
-		MergeResult result = git.merge().include(secondCommit.getId())
-				.setStrategy(MergeStrategy.RESOLVE).call();
-		assertEquals(MergeStatus.CONFLICTING, result.getMergeStatus());
+			MergeResult result = git.merge().include(secondCommit.getId())
+					.setStrategy(MergeStrategy.RESOLVE).call();
+			assertEquals(MergeStatus.CONFLICTING, result.getMergeStatus());
+		}
 	}
 
 	@Test
 	public void testMultipleCreationsSameContent() throws Exception {
-		Git git = new Git(db);
+		try (Git git = new Git(db)) {
+			writeTrashFile("a", "1\na\n3\n");
+			git.add().addFilepattern("a").call();
+			RevCommit initialCommit = git.commit().setMessage("initial").call();
 
-		writeTrashFile("a", "1\na\n3\n");
-		git.add().addFilepattern("a").call();
-		RevCommit initialCommit = git.commit().setMessage("initial").call();
+			createBranch(initialCommit, "refs/heads/side");
+			checkoutBranch("refs/heads/side");
 
-		createBranch(initialCommit, "refs/heads/side");
-		checkoutBranch("refs/heads/side");
+			writeTrashFile("b", "1\nb(1)\n3\n");
+			git.add().addFilepattern("b").call();
+			RevCommit secondCommit = git.commit().setMessage("side").call();
 
-		writeTrashFile("b", "1\nb(1)\n3\n");
-		git.add().addFilepattern("b").call();
-		RevCommit secondCommit = git.commit().setMessage("side").call();
+			checkoutBranch("refs/heads/master");
 
-		checkoutBranch("refs/heads/master");
+			writeTrashFile("b", "1\nb(1)\n3\n");
+			git.add().addFilepattern("b").call();
+			git.commit().setMessage("main").call();
 
-		writeTrashFile("b", "1\nb(1)\n3\n");
-		git.add().addFilepattern("b").call();
-		git.commit().setMessage("main").call();
-
-		MergeResult result = git.merge().include(secondCommit.getId())
-				.setStrategy(MergeStrategy.RESOLVE).call();
-		assertEquals(MergeStatus.MERGED, result.getMergeStatus());
-		assertEquals("1\nb(1)\n3\n", read(new File(db.getWorkTree(), "b")));
-		assertEquals("merge " + secondCommit.getId().getName()
-				+ ": Merge made by resolve.", db
-				.getReflogReader(Constants.HEAD)
-				.getLastEntry().getComment());
-		assertEquals("merge " + secondCommit.getId().getName()
-				+ ": Merge made by resolve.", db
-				.getReflogReader(db.getBranch())
-				.getLastEntry().getComment());
+			MergeResult result = git.merge().include(secondCommit.getId())
+					.setStrategy(MergeStrategy.RESOLVE).call();
+			assertEquals(MergeStatus.MERGED, result.getMergeStatus());
+			assertEquals("1\nb(1)\n3\n", read(new File(db.getWorkTree(), "b")));
+			assertEquals("merge " + secondCommit.getId().getName()
+					+ ": Merge made by resolve.", db
+					.getReflogReader(Constants.HEAD)
+					.getLastEntry().getComment());
+			assertEquals("merge " + secondCommit.getId().getName()
+					+ ": Merge made by resolve.", db
+					.getReflogReader(db.getBranch())
+					.getLastEntry().getComment());
+		}
 	}
 
 	@Test
 	public void testSuccessfulContentMerge() throws Exception {
-		Git git = new Git(db);
+		try (Git git = new Git(db)) {
+			writeTrashFile("a", "1\na\n3\n");
+			writeTrashFile("b", "1\nb\n3\n");
+			writeTrashFile("c/c/c", "1\nc\n3\n");
+			git.add().addFilepattern("a").addFilepattern("b")
+					.addFilepattern("c/c/c").call();
+			RevCommit initialCommit = git.commit().setMessage("initial").call();
 
-		writeTrashFile("a", "1\na\n3\n");
-		writeTrashFile("b", "1\nb\n3\n");
-		writeTrashFile("c/c/c", "1\nc\n3\n");
-		git.add().addFilepattern("a").addFilepattern("b")
-				.addFilepattern("c/c/c").call();
-		RevCommit initialCommit = git.commit().setMessage("initial").call();
+			createBranch(initialCommit, "refs/heads/side");
+			checkoutBranch("refs/heads/side");
 
-		createBranch(initialCommit, "refs/heads/side");
-		checkoutBranch("refs/heads/side");
+			writeTrashFile("a", "1(side)\na\n3\n");
+			writeTrashFile("b", "1\nb(side)\n3\n");
+			git.add().addFilepattern("a").addFilepattern("b").call();
+			RevCommit secondCommit = git.commit().setMessage("side").call();
 
-		writeTrashFile("a", "1(side)\na\n3\n");
-		writeTrashFile("b", "1\nb(side)\n3\n");
-		git.add().addFilepattern("a").addFilepattern("b").call();
-		RevCommit secondCommit = git.commit().setMessage("side").call();
+			assertEquals("1\nb(side)\n3\n", read(new File(db.getWorkTree(), "b")));
+			checkoutBranch("refs/heads/master");
+			assertEquals("1\nb\n3\n", read(new File(db.getWorkTree(), "b")));
 
-		assertEquals("1\nb(side)\n3\n", read(new File(db.getWorkTree(), "b")));
-		checkoutBranch("refs/heads/master");
-		assertEquals("1\nb\n3\n", read(new File(db.getWorkTree(), "b")));
+			writeTrashFile("a", "1\na\n3(main)\n");
+			writeTrashFile("c/c/c", "1\nc(main)\n3\n");
+			git.add().addFilepattern("a").addFilepattern("c/c/c").call();
+			RevCommit thirdCommit = git.commit().setMessage("main").call();
 
-		writeTrashFile("a", "1\na\n3(main)\n");
-		writeTrashFile("c/c/c", "1\nc(main)\n3\n");
-		git.add().addFilepattern("a").addFilepattern("c/c/c").call();
-		RevCommit thirdCommit = git.commit().setMessage("main").call();
+			MergeResult result = git.merge().include(secondCommit.getId())
+					.setStrategy(MergeStrategy.RESOLVE).call();
+			assertEquals(MergeStatus.MERGED, result.getMergeStatus());
 
-		MergeResult result = git.merge().include(secondCommit.getId())
-				.setStrategy(MergeStrategy.RESOLVE).call();
-		assertEquals(MergeStatus.MERGED, result.getMergeStatus());
+			assertEquals("1(side)\na\n3(main)\n", read(new File(db.getWorkTree(),
+					"a")));
+			assertEquals("1\nb(side)\n3\n", read(new File(db.getWorkTree(), "b")));
+			assertEquals("1\nc(main)\n3\n", read(new File(db.getWorkTree(),
+					"c/c/c")));
 
-		assertEquals("1(side)\na\n3(main)\n", read(new File(db.getWorkTree(),
-				"a")));
-		assertEquals("1\nb(side)\n3\n", read(new File(db.getWorkTree(), "b")));
-		assertEquals("1\nc(main)\n3\n", read(new File(db.getWorkTree(),
-				"c/c/c")));
+			assertEquals(null, result.getConflicts());
 
-		assertEquals(null, result.getConflicts());
+			assertEquals(2, result.getMergedCommits().length);
+			assertEquals(thirdCommit, result.getMergedCommits()[0]);
+			assertEquals(secondCommit, result.getMergedCommits()[1]);
 
-		assertEquals(2, result.getMergedCommits().length);
-		assertEquals(thirdCommit, result.getMergedCommits()[0]);
-		assertEquals(secondCommit, result.getMergedCommits()[1]);
-
-		Iterator<RevCommit> it = git.log().call().iterator();
-		RevCommit newHead = it.next();
-		assertEquals(newHead, result.getNewHead());
-		assertEquals(2, newHead.getParentCount());
-		assertEquals(thirdCommit, newHead.getParent(0));
-		assertEquals(secondCommit, newHead.getParent(1));
-		assertEquals(
-				"Merge commit '3fa334456d236a92db020289fe0bf481d91777b4'",
-				newHead.getFullMessage());
-		// @TODO fix me
-		assertEquals(RepositoryState.SAFE, db.getRepositoryState());
-		// test index state
+			Iterator<RevCommit> it = git.log().call().iterator();
+			RevCommit newHead = it.next();
+			assertEquals(newHead, result.getNewHead());
+			assertEquals(2, newHead.getParentCount());
+			assertEquals(thirdCommit, newHead.getParent(0));
+			assertEquals(secondCommit, newHead.getParent(1));
+			assertEquals(
+					"Merge commit '3fa334456d236a92db020289fe0bf481d91777b4'",
+					newHead.getFullMessage());
+			// @TODO fix me
+			assertEquals(RepositoryState.SAFE, db.getRepositoryState());
+			// test index state
+		}
 	}
 
 	@Test
 	public void testSuccessfulContentMergeNoCommit() throws Exception {
-		Git git = new Git(db);
+		try (Git git = new Git(db)) {
+			writeTrashFile("a", "1\na\n3\n");
+			writeTrashFile("b", "1\nb\n3\n");
+			writeTrashFile("c/c/c", "1\nc\n3\n");
+			git.add().addFilepattern("a").addFilepattern("b")
+					.addFilepattern("c/c/c").call();
+			RevCommit initialCommit = git.commit().setMessage("initial").call();
 
-		writeTrashFile("a", "1\na\n3\n");
-		writeTrashFile("b", "1\nb\n3\n");
-		writeTrashFile("c/c/c", "1\nc\n3\n");
-		git.add().addFilepattern("a").addFilepattern("b")
-				.addFilepattern("c/c/c").call();
-		RevCommit initialCommit = git.commit().setMessage("initial").call();
+			createBranch(initialCommit, "refs/heads/side");
+			checkoutBranch("refs/heads/side");
 
-		createBranch(initialCommit, "refs/heads/side");
-		checkoutBranch("refs/heads/side");
+			writeTrashFile("a", "1(side)\na\n3\n");
+			writeTrashFile("b", "1\nb(side)\n3\n");
+			git.add().addFilepattern("a").addFilepattern("b").call();
+			RevCommit secondCommit = git.commit().setMessage("side").call();
 
-		writeTrashFile("a", "1(side)\na\n3\n");
-		writeTrashFile("b", "1\nb(side)\n3\n");
-		git.add().addFilepattern("a").addFilepattern("b").call();
-		RevCommit secondCommit = git.commit().setMessage("side").call();
+			assertEquals("1\nb(side)\n3\n", read(new File(db.getWorkTree(), "b")));
+			checkoutBranch("refs/heads/master");
+			assertEquals("1\nb\n3\n", read(new File(db.getWorkTree(), "b")));
 
-		assertEquals("1\nb(side)\n3\n", read(new File(db.getWorkTree(), "b")));
-		checkoutBranch("refs/heads/master");
-		assertEquals("1\nb\n3\n", read(new File(db.getWorkTree(), "b")));
+			writeTrashFile("a", "1\na\n3(main)\n");
+			writeTrashFile("c/c/c", "1\nc(main)\n3\n");
+			git.add().addFilepattern("a").addFilepattern("c/c/c").call();
+			RevCommit thirdCommit = git.commit().setMessage("main").call();
 
-		writeTrashFile("a", "1\na\n3(main)\n");
-		writeTrashFile("c/c/c", "1\nc(main)\n3\n");
-		git.add().addFilepattern("a").addFilepattern("c/c/c").call();
-		RevCommit thirdCommit = git.commit().setMessage("main").call();
+			MergeResult result = git.merge().include(secondCommit.getId())
+					.setCommit(false)
+					.setStrategy(MergeStrategy.RESOLVE).call();
+			assertEquals(MergeStatus.MERGED_NOT_COMMITTED, result.getMergeStatus());
+			assertEquals(db.exactRef(Constants.HEAD).getTarget().getObjectId(),
+					thirdCommit.getId());
 
-		MergeResult result = git.merge().include(secondCommit.getId())
-				.setCommit(false)
-				.setStrategy(MergeStrategy.RESOLVE).call();
-		assertEquals(MergeStatus.MERGED_NOT_COMMITTED, result.getMergeStatus());
-		assertEquals(db.getRef(Constants.HEAD).getTarget().getObjectId(),
-				thirdCommit.getId());
+			assertEquals("1(side)\na\n3(main)\n", read(new File(db.getWorkTree(),
+					"a")));
+			assertEquals("1\nb(side)\n3\n", read(new File(db.getWorkTree(), "b")));
+			assertEquals("1\nc(main)\n3\n",
+					read(new File(db.getWorkTree(), "c/c/c")));
 
-		assertEquals("1(side)\na\n3(main)\n", read(new File(db.getWorkTree(),
-				"a")));
-		assertEquals("1\nb(side)\n3\n", read(new File(db.getWorkTree(), "b")));
-		assertEquals("1\nc(main)\n3\n",
-				read(new File(db.getWorkTree(), "c/c/c")));
+			assertEquals(null, result.getConflicts());
 
-		assertEquals(null, result.getConflicts());
-
-		assertEquals(2, result.getMergedCommits().length);
-		assertEquals(thirdCommit, result.getMergedCommits()[0]);
-		assertEquals(secondCommit, result.getMergedCommits()[1]);
-		assertNull(result.getNewHead());
-		assertEquals(RepositoryState.MERGING_RESOLVED, db.getRepositoryState());
+			assertEquals(2, result.getMergedCommits().length);
+			assertEquals(thirdCommit, result.getMergedCommits()[0]);
+			assertEquals(secondCommit, result.getMergedCommits()[1]);
+			assertNull(result.getNewHead());
+			assertEquals(RepositoryState.MERGING_RESOLVED, db.getRepositoryState());
+		}
 	}
 
 	@Test
 	public void testSuccessfulContentMergeAndDirtyworkingTree()
 			throws Exception {
-		Git git = new Git(db);
+		try (Git git = new Git(db)) {
+			writeTrashFile("a", "1\na\n3\n");
+			writeTrashFile("b", "1\nb\n3\n");
+			writeTrashFile("d", "1\nd\n3\n");
+			writeTrashFile("c/c/c", "1\nc\n3\n");
+			git.add().addFilepattern("a").addFilepattern("b")
+					.addFilepattern("c/c/c").addFilepattern("d").call();
+			RevCommit initialCommit = git.commit().setMessage("initial").call();
 
-		writeTrashFile("a", "1\na\n3\n");
-		writeTrashFile("b", "1\nb\n3\n");
-		writeTrashFile("d", "1\nd\n3\n");
-		writeTrashFile("c/c/c", "1\nc\n3\n");
-		git.add().addFilepattern("a").addFilepattern("b")
-				.addFilepattern("c/c/c").addFilepattern("d").call();
-		RevCommit initialCommit = git.commit().setMessage("initial").call();
+			createBranch(initialCommit, "refs/heads/side");
+			checkoutBranch("refs/heads/side");
 
-		createBranch(initialCommit, "refs/heads/side");
-		checkoutBranch("refs/heads/side");
+			writeTrashFile("a", "1(side)\na\n3\n");
+			writeTrashFile("b", "1\nb(side)\n3\n");
+			git.add().addFilepattern("a").addFilepattern("b").call();
+			RevCommit secondCommit = git.commit().setMessage("side").call();
 
-		writeTrashFile("a", "1(side)\na\n3\n");
-		writeTrashFile("b", "1\nb(side)\n3\n");
-		git.add().addFilepattern("a").addFilepattern("b").call();
-		RevCommit secondCommit = git.commit().setMessage("side").call();
+			assertEquals("1\nb(side)\n3\n", read(new File(db.getWorkTree(), "b")));
+			checkoutBranch("refs/heads/master");
+			assertEquals("1\nb\n3\n", read(new File(db.getWorkTree(), "b")));
 
-		assertEquals("1\nb(side)\n3\n", read(new File(db.getWorkTree(), "b")));
-		checkoutBranch("refs/heads/master");
-		assertEquals("1\nb\n3\n", read(new File(db.getWorkTree(), "b")));
+			writeTrashFile("a", "1\na\n3(main)\n");
+			writeTrashFile("c/c/c", "1\nc(main)\n3\n");
+			git.add().addFilepattern("a").addFilepattern("c/c/c").call();
+			RevCommit thirdCommit = git.commit().setMessage("main").call();
 
-		writeTrashFile("a", "1\na\n3(main)\n");
-		writeTrashFile("c/c/c", "1\nc(main)\n3\n");
-		git.add().addFilepattern("a").addFilepattern("c/c/c").call();
-		RevCommit thirdCommit = git.commit().setMessage("main").call();
+			writeTrashFile("d", "--- dirty ---");
+			MergeResult result = git.merge().include(secondCommit.getId())
+					.setStrategy(MergeStrategy.RESOLVE).call();
+			assertEquals(MergeStatus.MERGED, result.getMergeStatus());
 
-		writeTrashFile("d", "--- dirty ---");
-		MergeResult result = git.merge().include(secondCommit.getId())
-				.setStrategy(MergeStrategy.RESOLVE).call();
-		assertEquals(MergeStatus.MERGED, result.getMergeStatus());
+			assertEquals("1(side)\na\n3(main)\n", read(new File(db.getWorkTree(),
+					"a")));
+			assertEquals("1\nb(side)\n3\n", read(new File(db.getWorkTree(), "b")));
+			assertEquals("1\nc(main)\n3\n", read(new File(db.getWorkTree(),
+					"c/c/c")));
+			assertEquals("--- dirty ---", read(new File(db.getWorkTree(), "d")));
 
-		assertEquals("1(side)\na\n3(main)\n", read(new File(db.getWorkTree(),
-				"a")));
-		assertEquals("1\nb(side)\n3\n", read(new File(db.getWorkTree(), "b")));
-		assertEquals("1\nc(main)\n3\n", read(new File(db.getWorkTree(),
-				"c/c/c")));
-		assertEquals("--- dirty ---", read(new File(db.getWorkTree(), "d")));
+			assertEquals(null, result.getConflicts());
 
-		assertEquals(null, result.getConflicts());
+			assertEquals(2, result.getMergedCommits().length);
+			assertEquals(thirdCommit, result.getMergedCommits()[0]);
+			assertEquals(secondCommit, result.getMergedCommits()[1]);
 
-		assertEquals(2, result.getMergedCommits().length);
-		assertEquals(thirdCommit, result.getMergedCommits()[0]);
-		assertEquals(secondCommit, result.getMergedCommits()[1]);
+			Iterator<RevCommit> it = git.log().call().iterator();
+			RevCommit newHead = it.next();
+			assertEquals(newHead, result.getNewHead());
+			assertEquals(2, newHead.getParentCount());
+			assertEquals(thirdCommit, newHead.getParent(0));
+			assertEquals(secondCommit, newHead.getParent(1));
+			assertEquals(
+					"Merge commit '064d54d98a4cdb0fed1802a21c656bfda67fe879'",
+					newHead.getFullMessage());
 
-		Iterator<RevCommit> it = git.log().call().iterator();
-		RevCommit newHead = it.next();
-		assertEquals(newHead, result.getNewHead());
-		assertEquals(2, newHead.getParentCount());
-		assertEquals(thirdCommit, newHead.getParent(0));
-		assertEquals(secondCommit, newHead.getParent(1));
-		assertEquals(
-				"Merge commit '064d54d98a4cdb0fed1802a21c656bfda67fe879'",
-				newHead.getFullMessage());
-
-		assertEquals(RepositoryState.SAFE, db.getRepositoryState());
+			assertEquals(RepositoryState.SAFE, db.getRepositoryState());
+		}
 	}
 
 	@Test
 	public void testSingleDeletion() throws Exception {
-		Git git = new Git(db);
+		try (Git git = new Git(db)) {
+			writeTrashFile("a", "1\na\n3\n");
+			writeTrashFile("b", "1\nb\n3\n");
+			writeTrashFile("d", "1\nd\n3\n");
+			writeTrashFile("c/c/c", "1\nc\n3\n");
+			git.add().addFilepattern("a").addFilepattern("b")
+					.addFilepattern("c/c/c").addFilepattern("d").call();
+			RevCommit initialCommit = git.commit().setMessage("initial").call();
 
-		writeTrashFile("a", "1\na\n3\n");
-		writeTrashFile("b", "1\nb\n3\n");
-		writeTrashFile("d", "1\nd\n3\n");
-		writeTrashFile("c/c/c", "1\nc\n3\n");
-		git.add().addFilepattern("a").addFilepattern("b")
-				.addFilepattern("c/c/c").addFilepattern("d").call();
-		RevCommit initialCommit = git.commit().setMessage("initial").call();
+			createBranch(initialCommit, "refs/heads/side");
+			checkoutBranch("refs/heads/side");
 
-		createBranch(initialCommit, "refs/heads/side");
-		checkoutBranch("refs/heads/side");
+			assertTrue(new File(db.getWorkTree(), "b").delete());
+			git.add().addFilepattern("b").setUpdate(true).call();
+			RevCommit secondCommit = git.commit().setMessage("side").call();
 
-		assertTrue(new File(db.getWorkTree(), "b").delete());
-		git.add().addFilepattern("b").setUpdate(true).call();
-		RevCommit secondCommit = git.commit().setMessage("side").call();
+			assertFalse(new File(db.getWorkTree(), "b").exists());
+			checkoutBranch("refs/heads/master");
+			assertTrue(new File(db.getWorkTree(), "b").exists());
 
-		assertFalse(new File(db.getWorkTree(), "b").exists());
-		checkoutBranch("refs/heads/master");
-		assertTrue(new File(db.getWorkTree(), "b").exists());
+			writeTrashFile("a", "1\na\n3(main)\n");
+			writeTrashFile("c/c/c", "1\nc(main)\n3\n");
+			git.add().addFilepattern("a").addFilepattern("c/c/c").call();
+			RevCommit thirdCommit = git.commit().setMessage("main").call();
 
-		writeTrashFile("a", "1\na\n3(main)\n");
-		writeTrashFile("c/c/c", "1\nc(main)\n3\n");
-		git.add().addFilepattern("a").addFilepattern("c/c/c").call();
-		RevCommit thirdCommit = git.commit().setMessage("main").call();
+			// We are merging a deletion into our branch
+			MergeResult result = git.merge().include(secondCommit.getId())
+					.setStrategy(MergeStrategy.RESOLVE).call();
+			assertEquals(MergeStatus.MERGED, result.getMergeStatus());
 
-		// We are merging a deletion into our branch
-		MergeResult result = git.merge().include(secondCommit.getId())
-				.setStrategy(MergeStrategy.RESOLVE).call();
-		assertEquals(MergeStatus.MERGED, result.getMergeStatus());
+			assertEquals("1\na\n3(main)\n", read(new File(db.getWorkTree(), "a")));
+			assertFalse(new File(db.getWorkTree(), "b").exists());
+			assertEquals("1\nc(main)\n3\n",
+					read(new File(db.getWorkTree(), "c/c/c")));
+			assertEquals("1\nd\n3\n", read(new File(db.getWorkTree(), "d")));
 
-		assertEquals("1\na\n3(main)\n", read(new File(db.getWorkTree(), "a")));
-		assertFalse(new File(db.getWorkTree(), "b").exists());
-		assertEquals("1\nc(main)\n3\n",
-				read(new File(db.getWorkTree(), "c/c/c")));
-		assertEquals("1\nd\n3\n", read(new File(db.getWorkTree(), "d")));
+			// Do the opposite, be on a branch where we have deleted a file and
+			// merge in a old commit where this file was not deleted
+			checkoutBranch("refs/heads/side");
+			assertFalse(new File(db.getWorkTree(), "b").exists());
 
-		// Do the opposite, be on a branch where we have deleted a file and
-		// merge in a old commit where this file was not deleted
-		checkoutBranch("refs/heads/side");
-		assertFalse(new File(db.getWorkTree(), "b").exists());
+			result = git.merge().include(thirdCommit.getId())
+					.setStrategy(MergeStrategy.RESOLVE).call();
+			assertEquals(MergeStatus.MERGED, result.getMergeStatus());
 
-		result = git.merge().include(thirdCommit.getId())
-				.setStrategy(MergeStrategy.RESOLVE).call();
-		assertEquals(MergeStatus.MERGED, result.getMergeStatus());
-
-		assertEquals("1\na\n3(main)\n", read(new File(db.getWorkTree(), "a")));
-		assertFalse(new File(db.getWorkTree(), "b").exists());
-		assertEquals("1\nc(main)\n3\n",
-				read(new File(db.getWorkTree(), "c/c/c")));
-		assertEquals("1\nd\n3\n", read(new File(db.getWorkTree(), "d")));
+			assertEquals("1\na\n3(main)\n", read(new File(db.getWorkTree(), "a")));
+			assertFalse(new File(db.getWorkTree(), "b").exists());
+			assertEquals("1\nc(main)\n3\n",
+					read(new File(db.getWorkTree(), "c/c/c")));
+			assertEquals("1\nd\n3\n", read(new File(db.getWorkTree(), "d")));
+		}
 	}
 
 	@Test
 	public void testMultipleDeletions() throws Exception {
-		Git git = new Git(db);
+		try (Git git = new Git(db)) {
+			writeTrashFile("a", "1\na\n3\n");
+			git.add().addFilepattern("a").call();
+			RevCommit initialCommit = git.commit().setMessage("initial").call();
 
-		writeTrashFile("a", "1\na\n3\n");
-		git.add().addFilepattern("a").call();
-		RevCommit initialCommit = git.commit().setMessage("initial").call();
+			createBranch(initialCommit, "refs/heads/side");
+			checkoutBranch("refs/heads/side");
 
-		createBranch(initialCommit, "refs/heads/side");
-		checkoutBranch("refs/heads/side");
+			assertTrue(new File(db.getWorkTree(), "a").delete());
+			git.add().addFilepattern("a").setUpdate(true).call();
+			RevCommit secondCommit = git.commit().setMessage("side").call();
 
-		assertTrue(new File(db.getWorkTree(), "a").delete());
-		git.add().addFilepattern("a").setUpdate(true).call();
-		RevCommit secondCommit = git.commit().setMessage("side").call();
+			assertFalse(new File(db.getWorkTree(), "a").exists());
+			checkoutBranch("refs/heads/master");
+			assertTrue(new File(db.getWorkTree(), "a").exists());
 
-		assertFalse(new File(db.getWorkTree(), "a").exists());
-		checkoutBranch("refs/heads/master");
-		assertTrue(new File(db.getWorkTree(), "a").exists());
+			assertTrue(new File(db.getWorkTree(), "a").delete());
+			git.add().addFilepattern("a").setUpdate(true).call();
+			git.commit().setMessage("main").call();
 
-		assertTrue(new File(db.getWorkTree(), "a").delete());
-		git.add().addFilepattern("a").setUpdate(true).call();
-		git.commit().setMessage("main").call();
-
-		// We are merging a deletion into our branch
-		MergeResult result = git.merge().include(secondCommit.getId())
-				.setStrategy(MergeStrategy.RESOLVE).call();
-		assertEquals(MergeStatus.MERGED, result.getMergeStatus());
+			// We are merging a deletion into our branch
+			MergeResult result = git.merge().include(secondCommit.getId())
+					.setStrategy(MergeStrategy.RESOLVE).call();
+			assertEquals(MergeStatus.MERGED, result.getMergeStatus());
+		}
 	}
 
 	@Test
 	public void testDeletionAndConflict() throws Exception {
-		Git git = new Git(db);
+		try (Git git = new Git(db)) {
+			writeTrashFile("a", "1\na\n3\n");
+			writeTrashFile("b", "1\nb\n3\n");
+			writeTrashFile("d", "1\nd\n3\n");
+			writeTrashFile("c/c/c", "1\nc\n3\n");
+			git.add().addFilepattern("a").addFilepattern("b")
+					.addFilepattern("c/c/c").addFilepattern("d").call();
+			RevCommit initialCommit = git.commit().setMessage("initial").call();
 
-		writeTrashFile("a", "1\na\n3\n");
-		writeTrashFile("b", "1\nb\n3\n");
-		writeTrashFile("d", "1\nd\n3\n");
-		writeTrashFile("c/c/c", "1\nc\n3\n");
-		git.add().addFilepattern("a").addFilepattern("b")
-				.addFilepattern("c/c/c").addFilepattern("d").call();
-		RevCommit initialCommit = git.commit().setMessage("initial").call();
+			createBranch(initialCommit, "refs/heads/side");
+			checkoutBranch("refs/heads/side");
 
-		createBranch(initialCommit, "refs/heads/side");
-		checkoutBranch("refs/heads/side");
+			assertTrue(new File(db.getWorkTree(), "b").delete());
+			writeTrashFile("a", "1\na\n3(side)\n");
+			git.add().addFilepattern("b").setUpdate(true).call();
+			git.add().addFilepattern("a").setUpdate(true).call();
+			RevCommit secondCommit = git.commit().setMessage("side").call();
 
-		assertTrue(new File(db.getWorkTree(), "b").delete());
-		writeTrashFile("a", "1\na\n3(side)\n");
-		git.add().addFilepattern("b").setUpdate(true).call();
-		git.add().addFilepattern("a").setUpdate(true).call();
-		RevCommit secondCommit = git.commit().setMessage("side").call();
+			assertFalse(new File(db.getWorkTree(), "b").exists());
+			checkoutBranch("refs/heads/master");
+			assertTrue(new File(db.getWorkTree(), "b").exists());
 
-		assertFalse(new File(db.getWorkTree(), "b").exists());
-		checkoutBranch("refs/heads/master");
-		assertTrue(new File(db.getWorkTree(), "b").exists());
+			writeTrashFile("a", "1\na\n3(main)\n");
+			writeTrashFile("c/c/c", "1\nc(main)\n3\n");
+			git.add().addFilepattern("a").addFilepattern("c/c/c").call();
+			git.commit().setMessage("main").call();
 
-		writeTrashFile("a", "1\na\n3(main)\n");
-		writeTrashFile("c/c/c", "1\nc(main)\n3\n");
-		git.add().addFilepattern("a").addFilepattern("c/c/c").call();
-		git.commit().setMessage("main").call();
+			// We are merging a deletion into our branch
+			MergeResult result = git.merge().include(secondCommit.getId())
+					.setStrategy(MergeStrategy.RESOLVE).call();
+			assertEquals(MergeStatus.CONFLICTING, result.getMergeStatus());
 
-		// We are merging a deletion into our branch
-		MergeResult result = git.merge().include(secondCommit.getId())
-				.setStrategy(MergeStrategy.RESOLVE).call();
-		assertEquals(MergeStatus.CONFLICTING, result.getMergeStatus());
-
-		assertEquals(
-				"1\na\n<<<<<<< HEAD\n3(main)\n=======\n3(side)\n>>>>>>> 54ffed45d62d252715fc20e41da92d44c48fb0ff\n",
-				read(new File(db.getWorkTree(), "a")));
-		assertFalse(new File(db.getWorkTree(), "b").exists());
-		assertEquals("1\nc(main)\n3\n",
-				read(new File(db.getWorkTree(), "c/c/c")));
-		assertEquals("1\nd\n3\n", read(new File(db.getWorkTree(), "d")));
+			assertEquals(
+					"1\na\n<<<<<<< HEAD\n3(main)\n=======\n3(side)\n>>>>>>> 54ffed45d62d252715fc20e41da92d44c48fb0ff\n",
+					read(new File(db.getWorkTree(), "a")));
+			assertFalse(new File(db.getWorkTree(), "b").exists());
+			assertEquals("1\nc(main)\n3\n",
+					read(new File(db.getWorkTree(), "c/c/c")));
+			assertEquals("1\nd\n3\n", read(new File(db.getWorkTree(), "d")));
+		}
 	}
 
 	@Test
 	public void testDeletionOnMasterConflict() throws Exception {
-		Git git = new Git(db);
+		try (Git git = new Git(db)) {
+			writeTrashFile("a", "1\na\n3\n");
+			writeTrashFile("b", "1\nb\n3\n");
+			git.add().addFilepattern("a").addFilepattern("b").call();
+			RevCommit initialCommit = git.commit().setMessage("initial").call();
 
-		writeTrashFile("a", "1\na\n3\n");
-		writeTrashFile("b", "1\nb\n3\n");
-		git.add().addFilepattern("a").addFilepattern("b").call();
-		RevCommit initialCommit = git.commit().setMessage("initial").call();
+			// create side branch and modify "a"
+			createBranch(initialCommit, "refs/heads/side");
+			checkoutBranch("refs/heads/side");
+			writeTrashFile("a", "1\na(side)\n3\n");
+			git.add().addFilepattern("a").call();
+			RevCommit secondCommit = git.commit().setMessage("side").call();
 
-		// create side branch and modify "a"
-		createBranch(initialCommit, "refs/heads/side");
-		checkoutBranch("refs/heads/side");
-		writeTrashFile("a", "1\na(side)\n3\n");
-		git.add().addFilepattern("a").call();
-		RevCommit secondCommit = git.commit().setMessage("side").call();
+			// delete a on master to generate conflict
+			checkoutBranch("refs/heads/master");
+			git.rm().addFilepattern("a").call();
+			git.commit().setMessage("main").call();
 
-		// delete a on master to generate conflict
-		checkoutBranch("refs/heads/master");
-		git.rm().addFilepattern("a").call();
-		git.commit().setMessage("main").call();
+			// merge side with master
+			MergeResult result = git.merge().include(secondCommit.getId())
+					.setStrategy(MergeStrategy.RESOLVE).call();
+			assertEquals(MergeStatus.CONFLICTING, result.getMergeStatus());
 
-		// merge side with master
-		MergeResult result = git.merge().include(secondCommit.getId())
-				.setStrategy(MergeStrategy.RESOLVE).call();
-		assertEquals(MergeStatus.CONFLICTING, result.getMergeStatus());
-
-		// result should be 'a' conflicting with workspace content from side
-		assertTrue(new File(db.getWorkTree(), "a").exists());
-		assertEquals("1\na(side)\n3\n", read(new File(db.getWorkTree(), "a")));
-		assertEquals("1\nb\n3\n", read(new File(db.getWorkTree(), "b")));
+			// result should be 'a' conflicting with workspace content from side
+			assertTrue(new File(db.getWorkTree(), "a").exists());
+			assertEquals("1\na(side)\n3\n", read(new File(db.getWorkTree(), "a")));
+			assertEquals("1\nb\n3\n", read(new File(db.getWorkTree(), "b")));
+		}
 	}
 
 	@Test
 	public void testDeletionOnSideConflict() throws Exception {
-		Git git = new Git(db);
+		try (Git git = new Git(db)) {
+			writeTrashFile("a", "1\na\n3\n");
+			writeTrashFile("b", "1\nb\n3\n");
+			git.add().addFilepattern("a").addFilepattern("b").call();
+			RevCommit initialCommit = git.commit().setMessage("initial").call();
 
-		writeTrashFile("a", "1\na\n3\n");
-		writeTrashFile("b", "1\nb\n3\n");
-		git.add().addFilepattern("a").addFilepattern("b").call();
-		RevCommit initialCommit = git.commit().setMessage("initial").call();
+			// create side branch and delete "a"
+			createBranch(initialCommit, "refs/heads/side");
+			checkoutBranch("refs/heads/side");
+			git.rm().addFilepattern("a").call();
+			RevCommit secondCommit = git.commit().setMessage("side").call();
 
-		// create side branch and delete "a"
-		createBranch(initialCommit, "refs/heads/side");
-		checkoutBranch("refs/heads/side");
-		git.rm().addFilepattern("a").call();
-		RevCommit secondCommit = git.commit().setMessage("side").call();
+			// update a on master to generate conflict
+			checkoutBranch("refs/heads/master");
+			writeTrashFile("a", "1\na(main)\n3\n");
+			git.add().addFilepattern("a").call();
+			git.commit().setMessage("main").call();
 
-		// update a on master to generate conflict
-		checkoutBranch("refs/heads/master");
-		writeTrashFile("a", "1\na(main)\n3\n");
-		git.add().addFilepattern("a").call();
-		git.commit().setMessage("main").call();
+			// merge side with master
+			MergeResult result = git.merge().include(secondCommit.getId())
+					.setStrategy(MergeStrategy.RESOLVE).call();
+			assertEquals(MergeStatus.CONFLICTING, result.getMergeStatus());
 
-		// merge side with master
-		MergeResult result = git.merge().include(secondCommit.getId())
-				.setStrategy(MergeStrategy.RESOLVE).call();
-		assertEquals(MergeStatus.CONFLICTING, result.getMergeStatus());
+			assertTrue(new File(db.getWorkTree(), "a").exists());
+			assertEquals("1\na(main)\n3\n", read(new File(db.getWorkTree(), "a")));
+			assertEquals("1\nb\n3\n", read(new File(db.getWorkTree(), "b")));
 
-		assertTrue(new File(db.getWorkTree(), "a").exists());
-		assertEquals("1\na(main)\n3\n", read(new File(db.getWorkTree(), "a")));
-		assertEquals("1\nb\n3\n", read(new File(db.getWorkTree(), "b")));
-
-		assertEquals(1, result.getConflicts().size());
-		assertEquals(3, result.getConflicts().get("a")[0].length);
+			assertEquals(1, result.getConflicts().size());
+			assertEquals(3, result.getConflicts().get("a")[0].length);
+		}
 	}
 
 	@Test
@@ -868,262 +875,262 @@
 		// this test is essentially the same as testDeletionOnSideConflict,
 		// however if once rename support is added this test should result in a
 		// successful merge instead of a conflict
-		Git git = new Git(db);
+		try (Git git = new Git(db)) {
+			writeTrashFile("x", "add x");
+			git.add().addFilepattern("x").call();
+			RevCommit initial = git.commit().setMessage("add x").call();
 
-		writeTrashFile("x", "add x");
-		git.add().addFilepattern("x").call();
-		RevCommit initial = git.commit().setMessage("add x").call();
+			createBranch(initial, "refs/heads/d1");
+			createBranch(initial, "refs/heads/d2");
 
-		createBranch(initial, "refs/heads/d1");
-		createBranch(initial, "refs/heads/d2");
+			// rename x to y on d1
+			checkoutBranch("refs/heads/d1");
+			new File(db.getWorkTree(), "x")
+					.renameTo(new File(db.getWorkTree(), "y"));
+			git.rm().addFilepattern("x").call();
+			git.add().addFilepattern("y").call();
+			RevCommit d1Commit = git.commit().setMessage("d1 rename x -> y").call();
 
-		// rename x to y on d1
-		checkoutBranch("refs/heads/d1");
-		new File(db.getWorkTree(), "x")
-				.renameTo(new File(db.getWorkTree(), "y"));
-		git.rm().addFilepattern("x").call();
-		git.add().addFilepattern("y").call();
-		RevCommit d1Commit = git.commit().setMessage("d1 rename x -> y").call();
+			checkoutBranch("refs/heads/d2");
+			writeTrashFile("x", "d2 change");
+			git.add().addFilepattern("x").call();
+			RevCommit d2Commit = git.commit().setMessage("d2 change in x").call();
 
-		checkoutBranch("refs/heads/d2");
-		writeTrashFile("x", "d2 change");
-		git.add().addFilepattern("x").call();
-		RevCommit d2Commit = git.commit().setMessage("d2 change in x").call();
+			checkoutBranch("refs/heads/master");
+			MergeResult d1Merge = git.merge().include(d1Commit).call();
+			assertEquals(MergeResult.MergeStatus.FAST_FORWARD,
+					d1Merge.getMergeStatus());
 
-		checkoutBranch("refs/heads/master");
-		MergeResult d1Merge = git.merge().include(d1Commit).call();
-		assertEquals(MergeResult.MergeStatus.FAST_FORWARD,
-				d1Merge.getMergeStatus());
-
-		MergeResult d2Merge = git.merge().include(d2Commit).call();
-		assertEquals(MergeResult.MergeStatus.CONFLICTING,
-				d2Merge.getMergeStatus());
-		assertEquals(1, d2Merge.getConflicts().size());
-		assertEquals(3, d2Merge.getConflicts().get("x")[0].length);
+			MergeResult d2Merge = git.merge().include(d2Commit).call();
+			assertEquals(MergeResult.MergeStatus.CONFLICTING,
+					d2Merge.getMergeStatus());
+			assertEquals(1, d2Merge.getConflicts().size());
+			assertEquals(3, d2Merge.getConflicts().get("x")[0].length);
+		}
 	}
 
 	@Test
 	public void testMergeFailingWithDirtyWorkingTree() throws Exception {
-		Git git = new Git(db);
+		try (Git git = new Git(db)) {
+			writeTrashFile("a", "1\na\n3\n");
+			writeTrashFile("b", "1\nb\n3\n");
+			git.add().addFilepattern("a").addFilepattern("b").call();
+			RevCommit initialCommit = git.commit().setMessage("initial").call();
 
-		writeTrashFile("a", "1\na\n3\n");
-		writeTrashFile("b", "1\nb\n3\n");
-		git.add().addFilepattern("a").addFilepattern("b").call();
-		RevCommit initialCommit = git.commit().setMessage("initial").call();
+			createBranch(initialCommit, "refs/heads/side");
+			checkoutBranch("refs/heads/side");
 
-		createBranch(initialCommit, "refs/heads/side");
-		checkoutBranch("refs/heads/side");
+			writeTrashFile("a", "1(side)\na\n3\n");
+			writeTrashFile("b", "1\nb(side)\n3\n");
+			git.add().addFilepattern("a").addFilepattern("b").call();
+			RevCommit secondCommit = git.commit().setMessage("side").call();
 
-		writeTrashFile("a", "1(side)\na\n3\n");
-		writeTrashFile("b", "1\nb(side)\n3\n");
-		git.add().addFilepattern("a").addFilepattern("b").call();
-		RevCommit secondCommit = git.commit().setMessage("side").call();
+			assertEquals("1\nb(side)\n3\n", read(new File(db.getWorkTree(), "b")));
+			checkoutBranch("refs/heads/master");
+			assertEquals("1\nb\n3\n", read(new File(db.getWorkTree(), "b")));
 
-		assertEquals("1\nb(side)\n3\n", read(new File(db.getWorkTree(), "b")));
-		checkoutBranch("refs/heads/master");
-		assertEquals("1\nb\n3\n", read(new File(db.getWorkTree(), "b")));
+			writeTrashFile("a", "1\na\n3(main)\n");
+			git.add().addFilepattern("a").call();
+			git.commit().setMessage("main").call();
 
-		writeTrashFile("a", "1\na\n3(main)\n");
-		git.add().addFilepattern("a").call();
-		git.commit().setMessage("main").call();
+			writeTrashFile("a", "--- dirty ---");
+			MergeResult result = git.merge().include(secondCommit.getId())
+					.setStrategy(MergeStrategy.RESOLVE).call();
 
-		writeTrashFile("a", "--- dirty ---");
-		MergeResult result = git.merge().include(secondCommit.getId())
-				.setStrategy(MergeStrategy.RESOLVE).call();
+			assertEquals(MergeStatus.FAILED, result.getMergeStatus());
 
-		assertEquals(MergeStatus.FAILED, result.getMergeStatus());
+			assertEquals("--- dirty ---", read(new File(db.getWorkTree(), "a")));
+			assertEquals("1\nb\n3\n", read(new File(db.getWorkTree(), "b")));
 
-		assertEquals("--- dirty ---", read(new File(db.getWorkTree(), "a")));
-		assertEquals("1\nb\n3\n", read(new File(db.getWorkTree(), "b")));
+			assertEquals(null, result.getConflicts());
 
-		assertEquals(null, result.getConflicts());
-
-		assertEquals(RepositoryState.SAFE, db.getRepositoryState());
+			assertEquals(RepositoryState.SAFE, db.getRepositoryState());
+		}
 	}
 
 	@Test
 	public void testMergeConflictFileFolder() throws Exception {
-		Git git = new Git(db);
+		try (Git git = new Git(db)) {
+			writeTrashFile("a", "1\na\n3\n");
+			writeTrashFile("b", "1\nb\n3\n");
+			git.add().addFilepattern("a").addFilepattern("b").call();
+			RevCommit initialCommit = git.commit().setMessage("initial").call();
 
-		writeTrashFile("a", "1\na\n3\n");
-		writeTrashFile("b", "1\nb\n3\n");
-		git.add().addFilepattern("a").addFilepattern("b").call();
-		RevCommit initialCommit = git.commit().setMessage("initial").call();
+			createBranch(initialCommit, "refs/heads/side");
+			checkoutBranch("refs/heads/side");
 
-		createBranch(initialCommit, "refs/heads/side");
-		checkoutBranch("refs/heads/side");
+			writeTrashFile("c/c/c", "1\nc(side)\n3\n");
+			writeTrashFile("d", "1\nd(side)\n3\n");
+			git.add().addFilepattern("c/c/c").addFilepattern("d").call();
+			RevCommit secondCommit = git.commit().setMessage("side").call();
 
-		writeTrashFile("c/c/c", "1\nc(side)\n3\n");
-		writeTrashFile("d", "1\nd(side)\n3\n");
-		git.add().addFilepattern("c/c/c").addFilepattern("d").call();
-		RevCommit secondCommit = git.commit().setMessage("side").call();
+			checkoutBranch("refs/heads/master");
 
-		checkoutBranch("refs/heads/master");
+			writeTrashFile("c", "1\nc(main)\n3\n");
+			writeTrashFile("d/d/d", "1\nd(main)\n3\n");
+			git.add().addFilepattern("c").addFilepattern("d/d/d").call();
+			git.commit().setMessage("main").call();
 
-		writeTrashFile("c", "1\nc(main)\n3\n");
-		writeTrashFile("d/d/d", "1\nd(main)\n3\n");
-		git.add().addFilepattern("c").addFilepattern("d/d/d").call();
-		git.commit().setMessage("main").call();
+			MergeResult result = git.merge().include(secondCommit.getId())
+					.setStrategy(MergeStrategy.RESOLVE).call();
 
-		MergeResult result = git.merge().include(secondCommit.getId())
-				.setStrategy(MergeStrategy.RESOLVE).call();
+			assertEquals(MergeStatus.CONFLICTING, result.getMergeStatus());
 
-		assertEquals(MergeStatus.CONFLICTING, result.getMergeStatus());
+			assertEquals("1\na\n3\n", read(new File(db.getWorkTree(), "a")));
+			assertEquals("1\nb\n3\n", read(new File(db.getWorkTree(), "b")));
+			assertEquals("1\nc(main)\n3\n", read(new File(db.getWorkTree(), "c")));
+			assertEquals("1\nd(main)\n3\n", read(new File(db.getWorkTree(), "d/d/d")));
 
-		assertEquals("1\na\n3\n", read(new File(db.getWorkTree(), "a")));
-		assertEquals("1\nb\n3\n", read(new File(db.getWorkTree(), "b")));
-		assertEquals("1\nc(main)\n3\n", read(new File(db.getWorkTree(), "c")));
-		assertEquals("1\nd(main)\n3\n", read(new File(db.getWorkTree(), "d/d/d")));
+			assertEquals(null, result.getConflicts());
 
-		assertEquals(null, result.getConflicts());
-
-		assertEquals(RepositoryState.MERGING, db.getRepositoryState());
+			assertEquals(RepositoryState.MERGING, db.getRepositoryState());
+		}
 	}
 
 	@Test
 	public void testSuccessfulMergeFailsDueToDirtyIndex() throws Exception {
-		Git git = new Git(db);
+		try (Git git = new Git(db)) {
+			File fileA = writeTrashFile("a", "a");
+			RevCommit initialCommit = addAllAndCommit(git);
 
-		File fileA = writeTrashFile("a", "a");
-		RevCommit initialCommit = addAllAndCommit(git);
+			// switch branch
+			createBranch(initialCommit, "refs/heads/side");
+			checkoutBranch("refs/heads/side");
+			// modify file a
+			write(fileA, "a(side)");
+			writeTrashFile("b", "b");
+			RevCommit sideCommit = addAllAndCommit(git);
 
-		// switch branch
-		createBranch(initialCommit, "refs/heads/side");
-		checkoutBranch("refs/heads/side");
-		// modify file a
-		write(fileA, "a(side)");
-		writeTrashFile("b", "b");
-		RevCommit sideCommit = addAllAndCommit(git);
+			// switch branch
+			checkoutBranch("refs/heads/master");
+			writeTrashFile("c", "c");
+			addAllAndCommit(git);
 
-		// switch branch
-		checkoutBranch("refs/heads/master");
-		writeTrashFile("c", "c");
-		addAllAndCommit(git);
+			// modify and add file a
+			write(fileA, "a(modified)");
+			git.add().addFilepattern("a").call();
+			// do not commit
 
-		// modify and add file a
-		write(fileA, "a(modified)");
-		git.add().addFilepattern("a").call();
-		// do not commit
+			// get current index state
+			String indexState = indexState(CONTENT);
 
-		// get current index state
-		String indexState = indexState(CONTENT);
+			// merge
+			MergeResult result = git.merge().include(sideCommit.getId())
+					.setStrategy(MergeStrategy.RESOLVE).call();
 
-		// merge
-		MergeResult result = git.merge().include(sideCommit.getId())
-				.setStrategy(MergeStrategy.RESOLVE).call();
-
-		checkMergeFailedResult(result, MergeFailureReason.DIRTY_INDEX,
-				indexState, fileA);
+			checkMergeFailedResult(result, MergeFailureReason.DIRTY_INDEX,
+					indexState, fileA);
+		}
 	}
 
 	@Test
 	public void testConflictingMergeFailsDueToDirtyIndex() throws Exception {
-		Git git = new Git(db);
+		try (Git git = new Git(db)) {
+			File fileA = writeTrashFile("a", "a");
+			RevCommit initialCommit = addAllAndCommit(git);
 
-		File fileA = writeTrashFile("a", "a");
-		RevCommit initialCommit = addAllAndCommit(git);
+			// switch branch
+			createBranch(initialCommit, "refs/heads/side");
+			checkoutBranch("refs/heads/side");
+			// modify file a
+			write(fileA, "a(side)");
+			writeTrashFile("b", "b");
+			RevCommit sideCommit = addAllAndCommit(git);
 
-		// switch branch
-		createBranch(initialCommit, "refs/heads/side");
-		checkoutBranch("refs/heads/side");
-		// modify file a
-		write(fileA, "a(side)");
-		writeTrashFile("b", "b");
-		RevCommit sideCommit = addAllAndCommit(git);
+			// switch branch
+			checkoutBranch("refs/heads/master");
+			// modify file a - this will cause a conflict during merge
+			write(fileA, "a(master)");
+			writeTrashFile("c", "c");
+			addAllAndCommit(git);
 
-		// switch branch
-		checkoutBranch("refs/heads/master");
-		// modify file a - this will cause a conflict during merge
-		write(fileA, "a(master)");
-		writeTrashFile("c", "c");
-		addAllAndCommit(git);
+			// modify and add file a
+			write(fileA, "a(modified)");
+			git.add().addFilepattern("a").call();
+			// do not commit
 
-		// modify and add file a
-		write(fileA, "a(modified)");
-		git.add().addFilepattern("a").call();
-		// do not commit
+			// get current index state
+			String indexState = indexState(CONTENT);
 
-		// get current index state
-		String indexState = indexState(CONTENT);
+			// merge
+			MergeResult result = git.merge().include(sideCommit.getId())
+					.setStrategy(MergeStrategy.RESOLVE).call();
 
-		// merge
-		MergeResult result = git.merge().include(sideCommit.getId())
-				.setStrategy(MergeStrategy.RESOLVE).call();
-
-		checkMergeFailedResult(result, MergeFailureReason.DIRTY_INDEX,
-				indexState, fileA);
+			checkMergeFailedResult(result, MergeFailureReason.DIRTY_INDEX,
+					indexState, fileA);
+		}
 	}
 
 	@Test
 	public void testSuccessfulMergeFailsDueToDirtyWorktree() throws Exception {
-		Git git = new Git(db);
+		try (Git git = new Git(db)) {
+			File fileA = writeTrashFile("a", "a");
+			RevCommit initialCommit = addAllAndCommit(git);
 
-		File fileA = writeTrashFile("a", "a");
-		RevCommit initialCommit = addAllAndCommit(git);
+			// switch branch
+			createBranch(initialCommit, "refs/heads/side");
+			checkoutBranch("refs/heads/side");
+			// modify file a
+			write(fileA, "a(side)");
+			writeTrashFile("b", "b");
+			RevCommit sideCommit = addAllAndCommit(git);
 
-		// switch branch
-		createBranch(initialCommit, "refs/heads/side");
-		checkoutBranch("refs/heads/side");
-		// modify file a
-		write(fileA, "a(side)");
-		writeTrashFile("b", "b");
-		RevCommit sideCommit = addAllAndCommit(git);
+			// switch branch
+			checkoutBranch("refs/heads/master");
+			writeTrashFile("c", "c");
+			addAllAndCommit(git);
 
-		// switch branch
-		checkoutBranch("refs/heads/master");
-		writeTrashFile("c", "c");
-		addAllAndCommit(git);
+			// modify file a
+			write(fileA, "a(modified)");
+			// do not add and commit
 
-		// modify file a
-		write(fileA, "a(modified)");
-		// do not add and commit
+			// get current index state
+			String indexState = indexState(CONTENT);
 
-		// get current index state
-		String indexState = indexState(CONTENT);
+			// merge
+			MergeResult result = git.merge().include(sideCommit.getId())
+					.setStrategy(MergeStrategy.RESOLVE).call();
 
-		// merge
-		MergeResult result = git.merge().include(sideCommit.getId())
-				.setStrategy(MergeStrategy.RESOLVE).call();
-
-		checkMergeFailedResult(result, MergeFailureReason.DIRTY_WORKTREE,
-				indexState, fileA);
+			checkMergeFailedResult(result, MergeFailureReason.DIRTY_WORKTREE,
+					indexState, fileA);
+		}
 	}
 
 	@Test
 	public void testConflictingMergeFailsDueToDirtyWorktree() throws Exception {
-		Git git = new Git(db);
+		try (Git git = new Git(db)) {
+			File fileA = writeTrashFile("a", "a");
+			RevCommit initialCommit = addAllAndCommit(git);
 
-		File fileA = writeTrashFile("a", "a");
-		RevCommit initialCommit = addAllAndCommit(git);
+			// switch branch
+			createBranch(initialCommit, "refs/heads/side");
+			checkoutBranch("refs/heads/side");
+			// modify file a
+			write(fileA, "a(side)");
+			writeTrashFile("b", "b");
+			RevCommit sideCommit = addAllAndCommit(git);
 
-		// switch branch
-		createBranch(initialCommit, "refs/heads/side");
-		checkoutBranch("refs/heads/side");
-		// modify file a
-		write(fileA, "a(side)");
-		writeTrashFile("b", "b");
-		RevCommit sideCommit = addAllAndCommit(git);
+			// switch branch
+			checkoutBranch("refs/heads/master");
+			// modify file a - this will cause a conflict during merge
+			write(fileA, "a(master)");
+			writeTrashFile("c", "c");
+			addAllAndCommit(git);
 
-		// switch branch
-		checkoutBranch("refs/heads/master");
-		// modify file a - this will cause a conflict during merge
-		write(fileA, "a(master)");
-		writeTrashFile("c", "c");
-		addAllAndCommit(git);
+			// modify file a
+			write(fileA, "a(modified)");
+			// do not add and commit
 
-		// modify file a
-		write(fileA, "a(modified)");
-		// do not add and commit
+			// get current index state
+			String indexState = indexState(CONTENT);
 
-		// get current index state
-		String indexState = indexState(CONTENT);
+			// merge
+			MergeResult result = git.merge().include(sideCommit.getId())
+					.setStrategy(MergeStrategy.RESOLVE).call();
 
-		// merge
-		MergeResult result = git.merge().include(sideCommit.getId())
-				.setStrategy(MergeStrategy.RESOLVE).call();
-
-		checkMergeFailedResult(result, MergeFailureReason.DIRTY_WORKTREE,
-				indexState, fileA);
+			checkMergeFailedResult(result, MergeFailureReason.DIRTY_WORKTREE,
+					indexState, fileA);
+		}
 	}
 
 	@Test
@@ -1141,29 +1148,30 @@
 		file = new File(folder2, "file2.txt");
 		write(file, "folder2--file2.txt");
 
-		Git git = new Git(db);
-		git.add().addFilepattern(folder1.getName())
-				.addFilepattern(folder2.getName()).call();
-		RevCommit commit1 = git.commit().setMessage("adding folders").call();
+		try (Git git = new Git(db)) {
+			git.add().addFilepattern(folder1.getName())
+					.addFilepattern(folder2.getName()).call();
+			RevCommit commit1 = git.commit().setMessage("adding folders").call();
 
-		recursiveDelete(folder1);
-		recursiveDelete(folder2);
-		git.rm().addFilepattern("folder1/file1.txt")
-				.addFilepattern("folder1/file2.txt")
-				.addFilepattern("folder2/file1.txt")
-				.addFilepattern("folder2/file2.txt").call();
-		RevCommit commit2 = git.commit()
-				.setMessage("removing folders on 'branch'").call();
+			recursiveDelete(folder1);
+			recursiveDelete(folder2);
+			git.rm().addFilepattern("folder1/file1.txt")
+					.addFilepattern("folder1/file2.txt")
+					.addFilepattern("folder2/file1.txt")
+					.addFilepattern("folder2/file2.txt").call();
+			RevCommit commit2 = git.commit()
+					.setMessage("removing folders on 'branch'").call();
 
-		git.checkout().setName(commit1.name()).call();
+			git.checkout().setName(commit1.name()).call();
 
-		MergeResult result = git.merge().include(commit2.getId())
-				.setStrategy(MergeStrategy.RESOLVE).call();
-		assertEquals(MergeResult.MergeStatus.FAST_FORWARD,
-				result.getMergeStatus());
-		assertEquals(commit2, result.getNewHead());
-		assertFalse(folder1.exists());
-		assertFalse(folder2.exists());
+			MergeResult result = git.merge().include(commit2.getId())
+					.setStrategy(MergeStrategy.RESOLVE).call();
+			assertEquals(MergeResult.MergeStatus.FAST_FORWARD,
+					result.getMergeStatus());
+			assertEquals(commit2, result.getNewHead());
+			assertFalse(folder1.exists());
+			assertFalse(folder2.exists());
+		}
 	}
 
 	@Test
@@ -1181,360 +1189,369 @@
 		file = new File(folder2, "file2.txt");
 		write(file, "folder2--file2.txt");
 
-		Git git = new Git(db);
-		git.add().addFilepattern(folder1.getName())
-				.addFilepattern(folder2.getName()).call();
-		RevCommit base = git.commit().setMessage("adding folders").call();
+		try (Git git = new Git(db)) {
+			git.add().addFilepattern(folder1.getName())
+					.addFilepattern(folder2.getName()).call();
+			RevCommit base = git.commit().setMessage("adding folders").call();
 
-		recursiveDelete(folder1);
-		recursiveDelete(folder2);
-		git.rm().addFilepattern("folder1/file1.txt")
-				.addFilepattern("folder1/file2.txt")
-				.addFilepattern("folder2/file1.txt")
-				.addFilepattern("folder2/file2.txt").call();
-		RevCommit other = git.commit()
-				.setMessage("removing folders on 'branch'").call();
+			recursiveDelete(folder1);
+			recursiveDelete(folder2);
+			git.rm().addFilepattern("folder1/file1.txt")
+					.addFilepattern("folder1/file2.txt")
+					.addFilepattern("folder2/file1.txt")
+					.addFilepattern("folder2/file2.txt").call();
+			RevCommit other = git.commit()
+					.setMessage("removing folders on 'branch'").call();
 
-		git.checkout().setName(base.name()).call();
+			git.checkout().setName(base.name()).call();
 
-		file = new File(folder2, "file3.txt");
-		write(file, "folder2--file3.txt");
+			file = new File(folder2, "file3.txt");
+			write(file, "folder2--file3.txt");
 
-		git.add().addFilepattern(folder2.getName()).call();
-		git.commit().setMessage("adding another file").call();
+			git.add().addFilepattern(folder2.getName()).call();
+			git.commit().setMessage("adding another file").call();
 
-		MergeResult result = git.merge().include(other.getId())
-				.setStrategy(MergeStrategy.RESOLVE).call();
+			MergeResult result = git.merge().include(other.getId())
+					.setStrategy(MergeStrategy.RESOLVE).call();
 
-		assertEquals(MergeResult.MergeStatus.MERGED,
-				result.getMergeStatus());
-		assertFalse(folder1.exists());
+			assertEquals(MergeResult.MergeStatus.MERGED,
+					result.getMergeStatus());
+			assertFalse(folder1.exists());
+		}
 	}
 
 	@Test
 	public void testFileModeMerge() throws Exception {
-		if (!FS.DETECTED.supportsExecute())
-			return;
 		// Only Java6
-		Git git = new Git(db);
+		assumeTrue(FS.DETECTED.supportsExecute());
+		try (Git git = new Git(db)) {
+			writeTrashFile("mergeableMode", "a");
+			setExecutable(git, "mergeableMode", false);
+			writeTrashFile("conflictingModeWithBase", "a");
+			setExecutable(git, "conflictingModeWithBase", false);
+			RevCommit initialCommit = addAllAndCommit(git);
 
-		writeTrashFile("mergeableMode", "a");
-		setExecutable(git, "mergeableMode", false);
-		writeTrashFile("conflictingModeWithBase", "a");
-		setExecutable(git, "conflictingModeWithBase", false);
-		RevCommit initialCommit = addAllAndCommit(git);
+			// switch branch
+			createBranch(initialCommit, "refs/heads/side");
+			checkoutBranch("refs/heads/side");
+			setExecutable(git, "mergeableMode", true);
+			writeTrashFile("conflictingModeNoBase", "b");
+			setExecutable(git, "conflictingModeNoBase", true);
+			RevCommit sideCommit = addAllAndCommit(git);
 
-		// switch branch
-		createBranch(initialCommit, "refs/heads/side");
-		checkoutBranch("refs/heads/side");
-		setExecutable(git, "mergeableMode", true);
-		writeTrashFile("conflictingModeNoBase", "b");
-		setExecutable(git, "conflictingModeNoBase", true);
-		RevCommit sideCommit = addAllAndCommit(git);
+			// switch branch
+			createBranch(initialCommit, "refs/heads/side2");
+			checkoutBranch("refs/heads/side2");
+			setExecutable(git, "mergeableMode", false);
+			assertFalse(new File(git.getRepository().getWorkTree(),
+					"conflictingModeNoBase").exists());
+			writeTrashFile("conflictingModeNoBase", "b");
+			setExecutable(git, "conflictingModeNoBase", false);
+			addAllAndCommit(git);
 
-		// switch branch
-		createBranch(initialCommit, "refs/heads/side2");
-		checkoutBranch("refs/heads/side2");
-		setExecutable(git, "mergeableMode", false);
-		assertFalse(new File(git.getRepository().getWorkTree(),
-				"conflictingModeNoBase").exists());
-		writeTrashFile("conflictingModeNoBase", "b");
-		setExecutable(git, "conflictingModeNoBase", false);
-		addAllAndCommit(git);
-
-		// merge
-		MergeResult result = git.merge().include(sideCommit.getId())
-				.setStrategy(MergeStrategy.RESOLVE).call();
-		assertEquals(MergeStatus.CONFLICTING, result.getMergeStatus());
-		assertTrue(canExecute(git, "mergeableMode"));
-		assertFalse(canExecute(git, "conflictingModeNoBase"));
+			// merge
+			MergeResult result = git.merge().include(sideCommit.getId())
+					.setStrategy(MergeStrategy.RESOLVE).call();
+			assertEquals(MergeStatus.CONFLICTING, result.getMergeStatus());
+			assertTrue(canExecute(git, "mergeableMode"));
+			assertFalse(canExecute(git, "conflictingModeNoBase"));
+		}
 	}
 
 	@Test
 	public void testFileModeMergeWithDirtyWorkTree() throws Exception {
-		if (!FS.DETECTED.supportsExecute())
-			return;
 		// Only Java6 (or set x bit in index)
+		assumeTrue(FS.DETECTED.supportsExecute());
 
-		Git git = new Git(db);
+		try (Git git = new Git(db)) {
+			writeTrashFile("mergeableButDirty", "a");
+			setExecutable(git, "mergeableButDirty", false);
+			RevCommit initialCommit = addAllAndCommit(git);
 
-		writeTrashFile("mergeableButDirty", "a");
-		setExecutable(git, "mergeableButDirty", false);
-		RevCommit initialCommit = addAllAndCommit(git);
+			// switch branch
+			createBranch(initialCommit, "refs/heads/side");
+			checkoutBranch("refs/heads/side");
+			setExecutable(git, "mergeableButDirty", true);
+			RevCommit sideCommit = addAllAndCommit(git);
 
-		// switch branch
-		createBranch(initialCommit, "refs/heads/side");
-		checkoutBranch("refs/heads/side");
-		setExecutable(git, "mergeableButDirty", true);
-		RevCommit sideCommit = addAllAndCommit(git);
+			// switch branch
+			createBranch(initialCommit, "refs/heads/side2");
+			checkoutBranch("refs/heads/side2");
+			setExecutable(git, "mergeableButDirty", false);
+			addAllAndCommit(git);
 
-		// switch branch
-		createBranch(initialCommit, "refs/heads/side2");
-		checkoutBranch("refs/heads/side2");
-		setExecutable(git, "mergeableButDirty", false);
-		addAllAndCommit(git);
+			writeTrashFile("mergeableButDirty", "b");
 
-		writeTrashFile("mergeableButDirty", "b");
-
-		// merge
-		MergeResult result = git.merge().include(sideCommit.getId())
-				.setStrategy(MergeStrategy.RESOLVE).call();
-		assertEquals(MergeStatus.FAILED, result.getMergeStatus());
-		assertFalse(canExecute(git, "mergeableButDirty"));
+			// merge
+			MergeResult result = git.merge().include(sideCommit.getId())
+					.setStrategy(MergeStrategy.RESOLVE).call();
+			assertEquals(MergeStatus.FAILED, result.getMergeStatus());
+			assertFalse(canExecute(git, "mergeableButDirty"));
+		}
 	}
 
 	@Test
 	public void testSquashFastForward() throws Exception {
-		Git git = new Git(db);
+		try (Git git = new Git(db)) {
+			writeTrashFile("file1", "file1");
+			git.add().addFilepattern("file1").call();
+			RevCommit first = git.commit().setMessage("initial commit").call();
 
-		writeTrashFile("file1", "file1");
-		git.add().addFilepattern("file1").call();
-		RevCommit first = git.commit().setMessage("initial commit").call();
+			assertTrue(new File(db.getWorkTree(), "file1").exists());
+			createBranch(first, "refs/heads/branch1");
+			checkoutBranch("refs/heads/branch1");
 
-		assertTrue(new File(db.getWorkTree(), "file1").exists());
-		createBranch(first, "refs/heads/branch1");
-		checkoutBranch("refs/heads/branch1");
+			writeTrashFile("file2", "file2");
+			git.add().addFilepattern("file2").call();
+			RevCommit second = git.commit().setMessage("second commit").call();
+			assertTrue(new File(db.getWorkTree(), "file2").exists());
 
-		writeTrashFile("file2", "file2");
-		git.add().addFilepattern("file2").call();
-		RevCommit second = git.commit().setMessage("second commit").call();
-		assertTrue(new File(db.getWorkTree(), "file2").exists());
+			writeTrashFile("file3", "file3");
+			git.add().addFilepattern("file3").call();
+			RevCommit third = git.commit().setMessage("third commit").call();
+			assertTrue(new File(db.getWorkTree(), "file3").exists());
 
-		writeTrashFile("file3", "file3");
-		git.add().addFilepattern("file3").call();
-		RevCommit third = git.commit().setMessage("third commit").call();
-		assertTrue(new File(db.getWorkTree(), "file3").exists());
+			checkoutBranch("refs/heads/master");
+			assertTrue(new File(db.getWorkTree(), "file1").exists());
+			assertFalse(new File(db.getWorkTree(), "file2").exists());
+			assertFalse(new File(db.getWorkTree(), "file3").exists());
 
-		checkoutBranch("refs/heads/master");
-		assertTrue(new File(db.getWorkTree(), "file1").exists());
-		assertFalse(new File(db.getWorkTree(), "file2").exists());
-		assertFalse(new File(db.getWorkTree(), "file3").exists());
+			MergeResult result = git.merge()
+					.include(db.exactRef("refs/heads/branch1"))
+					.setSquash(true)
+					.call();
 
-		MergeResult result = git.merge().include(db.getRef("branch1"))
-				.setSquash(true).call();
+			assertTrue(new File(db.getWorkTree(), "file1").exists());
+			assertTrue(new File(db.getWorkTree(), "file2").exists());
+			assertTrue(new File(db.getWorkTree(), "file3").exists());
+			assertEquals(MergeResult.MergeStatus.FAST_FORWARD_SQUASHED,
+					result.getMergeStatus());
+			assertEquals(first, result.getNewHead()); // HEAD didn't move
+			assertEquals(first, db.resolve(Constants.HEAD + "^{commit}"));
 
-		assertTrue(new File(db.getWorkTree(), "file1").exists());
-		assertTrue(new File(db.getWorkTree(), "file2").exists());
-		assertTrue(new File(db.getWorkTree(), "file3").exists());
-		assertEquals(MergeResult.MergeStatus.FAST_FORWARD_SQUASHED,
-				result.getMergeStatus());
-		assertEquals(first, result.getNewHead()); // HEAD didn't move
-		assertEquals(first, db.resolve(Constants.HEAD + "^{commit}"));
+			assertEquals(
+					"Squashed commit of the following:\n\ncommit "
+							+ third.getName()
+							+ "\nAuthor: "
+							+ third.getAuthorIdent().getName()
+							+ " <"
+							+ third.getAuthorIdent().getEmailAddress()
+							+ ">\nDate:   "
+							+ dateFormatter.formatDate(third
+									.getAuthorIdent())
+							+ "\n\n\tthird commit\n\ncommit "
+							+ second.getName()
+							+ "\nAuthor: "
+							+ second.getAuthorIdent().getName()
+							+ " <"
+							+ second.getAuthorIdent().getEmailAddress()
+							+ ">\nDate:   "
+							+ dateFormatter.formatDate(second
+									.getAuthorIdent()) + "\n\n\tsecond commit\n",
+					db.readSquashCommitMsg());
+			assertNull(db.readMergeCommitMsg());
 
-		assertEquals(
-				"Squashed commit of the following:\n\ncommit "
-						+ third.getName()
-						+ "\nAuthor: "
-						+ third.getAuthorIdent().getName()
-						+ " <"
-						+ third.getAuthorIdent().getEmailAddress()
-						+ ">\nDate:   "
-						+ dateFormatter.formatDate(third
-								.getAuthorIdent())
-						+ "\n\n\tthird commit\n\ncommit "
-						+ second.getName()
-						+ "\nAuthor: "
-						+ second.getAuthorIdent().getName()
-						+ " <"
-						+ second.getAuthorIdent().getEmailAddress()
-						+ ">\nDate:   "
-						+ dateFormatter.formatDate(second
-								.getAuthorIdent()) + "\n\n\tsecond commit\n",
-				db.readSquashCommitMsg());
-		assertNull(db.readMergeCommitMsg());
-
-		Status stat = git.status().call();
-		assertEquals(Sets.of("file2", "file3"), stat.getAdded());
+			Status stat = git.status().call();
+			assertEquals(Sets.of("file2", "file3"), stat.getAdded());
+		}
 	}
 
 	@Test
 	public void testSquashMerge() throws Exception {
-		Git git = new Git(db);
+		try (Git git = new Git(db)) {
+			writeTrashFile("file1", "file1");
+			git.add().addFilepattern("file1").call();
+			RevCommit first = git.commit().setMessage("initial commit").call();
 
-		writeTrashFile("file1", "file1");
-		git.add().addFilepattern("file1").call();
-		RevCommit first = git.commit().setMessage("initial commit").call();
+			assertTrue(new File(db.getWorkTree(), "file1").exists());
+			createBranch(first, "refs/heads/branch1");
 
-		assertTrue(new File(db.getWorkTree(), "file1").exists());
-		createBranch(first, "refs/heads/branch1");
+			writeTrashFile("file2", "file2");
+			git.add().addFilepattern("file2").call();
+			RevCommit second = git.commit().setMessage("second commit").call();
+			assertTrue(new File(db.getWorkTree(), "file2").exists());
 
-		writeTrashFile("file2", "file2");
-		git.add().addFilepattern("file2").call();
-		RevCommit second = git.commit().setMessage("second commit").call();
-		assertTrue(new File(db.getWorkTree(), "file2").exists());
+			checkoutBranch("refs/heads/branch1");
 
-		checkoutBranch("refs/heads/branch1");
+			writeTrashFile("file3", "file3");
+			git.add().addFilepattern("file3").call();
+			RevCommit third = git.commit().setMessage("third commit").call();
+			assertTrue(new File(db.getWorkTree(), "file3").exists());
 
-		writeTrashFile("file3", "file3");
-		git.add().addFilepattern("file3").call();
-		RevCommit third = git.commit().setMessage("third commit").call();
-		assertTrue(new File(db.getWorkTree(), "file3").exists());
+			checkoutBranch("refs/heads/master");
+			assertTrue(new File(db.getWorkTree(), "file1").exists());
+			assertTrue(new File(db.getWorkTree(), "file2").exists());
+			assertFalse(new File(db.getWorkTree(), "file3").exists());
 
-		checkoutBranch("refs/heads/master");
-		assertTrue(new File(db.getWorkTree(), "file1").exists());
-		assertTrue(new File(db.getWorkTree(), "file2").exists());
-		assertFalse(new File(db.getWorkTree(), "file3").exists());
+			MergeResult result = git.merge()
+					.include(db.exactRef("refs/heads/branch1"))
+					.setSquash(true)
+					.call();
 
-		MergeResult result = git.merge().include(db.getRef("branch1"))
-				.setSquash(true).call();
+			assertTrue(new File(db.getWorkTree(), "file1").exists());
+			assertTrue(new File(db.getWorkTree(), "file2").exists());
+			assertTrue(new File(db.getWorkTree(), "file3").exists());
+			assertEquals(MergeResult.MergeStatus.MERGED_SQUASHED,
+					result.getMergeStatus());
+			assertEquals(second, result.getNewHead()); // HEAD didn't move
+			assertEquals(second, db.resolve(Constants.HEAD + "^{commit}"));
 
-		assertTrue(new File(db.getWorkTree(), "file1").exists());
-		assertTrue(new File(db.getWorkTree(), "file2").exists());
-		assertTrue(new File(db.getWorkTree(), "file3").exists());
-		assertEquals(MergeResult.MergeStatus.MERGED_SQUASHED,
-				result.getMergeStatus());
-		assertEquals(second, result.getNewHead()); // HEAD didn't move
-		assertEquals(second, db.resolve(Constants.HEAD + "^{commit}"));
+			assertEquals(
+					"Squashed commit of the following:\n\ncommit "
+							+ third.getName()
+							+ "\nAuthor: "
+							+ third.getAuthorIdent().getName()
+							+ " <"
+							+ third.getAuthorIdent().getEmailAddress()
+							+ ">\nDate:   "
+							+ dateFormatter.formatDate(third
+									.getAuthorIdent()) + "\n\n\tthird commit\n",
+					db.readSquashCommitMsg());
+			assertNull(db.readMergeCommitMsg());
 
-		assertEquals(
-				"Squashed commit of the following:\n\ncommit "
-						+ third.getName()
-						+ "\nAuthor: "
-						+ third.getAuthorIdent().getName()
-						+ " <"
-						+ third.getAuthorIdent().getEmailAddress()
-						+ ">\nDate:   "
-						+ dateFormatter.formatDate(third
-								.getAuthorIdent()) + "\n\n\tthird commit\n",
-				db.readSquashCommitMsg());
-		assertNull(db.readMergeCommitMsg());
-
-		Status stat = git.status().call();
-		assertEquals(Sets.of("file3"), stat.getAdded());
+			Status stat = git.status().call();
+			assertEquals(Sets.of("file3"), stat.getAdded());
+		}
 	}
 
 	@Test
 	public void testSquashMergeConflict() throws Exception {
-		Git git = new Git(db);
+		try (Git git = new Git(db)) {
+			writeTrashFile("file1", "file1");
+			git.add().addFilepattern("file1").call();
+			RevCommit first = git.commit().setMessage("initial commit").call();
 
-		writeTrashFile("file1", "file1");
-		git.add().addFilepattern("file1").call();
-		RevCommit first = git.commit().setMessage("initial commit").call();
+			assertTrue(new File(db.getWorkTree(), "file1").exists());
+			createBranch(first, "refs/heads/branch1");
 
-		assertTrue(new File(db.getWorkTree(), "file1").exists());
-		createBranch(first, "refs/heads/branch1");
+			writeTrashFile("file2", "master");
+			git.add().addFilepattern("file2").call();
+			RevCommit second = git.commit().setMessage("second commit").call();
+			assertTrue(new File(db.getWorkTree(), "file2").exists());
 
-		writeTrashFile("file2", "master");
-		git.add().addFilepattern("file2").call();
-		RevCommit second = git.commit().setMessage("second commit").call();
-		assertTrue(new File(db.getWorkTree(), "file2").exists());
+			checkoutBranch("refs/heads/branch1");
 
-		checkoutBranch("refs/heads/branch1");
+			writeTrashFile("file2", "branch");
+			git.add().addFilepattern("file2").call();
+			RevCommit third = git.commit().setMessage("third commit").call();
+			assertTrue(new File(db.getWorkTree(), "file2").exists());
 
-		writeTrashFile("file2", "branch");
-		git.add().addFilepattern("file2").call();
-		RevCommit third = git.commit().setMessage("third commit").call();
-		assertTrue(new File(db.getWorkTree(), "file2").exists());
+			checkoutBranch("refs/heads/master");
+			assertTrue(new File(db.getWorkTree(), "file1").exists());
+			assertTrue(new File(db.getWorkTree(), "file2").exists());
 
-		checkoutBranch("refs/heads/master");
-		assertTrue(new File(db.getWorkTree(), "file1").exists());
-		assertTrue(new File(db.getWorkTree(), "file2").exists());
+			MergeResult result = git.merge()
+					.include(db.exactRef("refs/heads/branch1"))
+					.setSquash(true)
+					.call();
 
-		MergeResult result = git.merge().include(db.getRef("branch1"))
-				.setSquash(true).call();
+			assertTrue(new File(db.getWorkTree(), "file1").exists());
+			assertTrue(new File(db.getWorkTree(), "file2").exists());
+			assertEquals(MergeResult.MergeStatus.CONFLICTING,
+					result.getMergeStatus());
+			assertNull(result.getNewHead());
+			assertEquals(second, db.resolve(Constants.HEAD + "^{commit}"));
 
-		assertTrue(new File(db.getWorkTree(), "file1").exists());
-		assertTrue(new File(db.getWorkTree(), "file2").exists());
-		assertEquals(MergeResult.MergeStatus.CONFLICTING,
-				result.getMergeStatus());
-		assertNull(result.getNewHead());
-		assertEquals(second, db.resolve(Constants.HEAD + "^{commit}"));
+			assertEquals(
+					"Squashed commit of the following:\n\ncommit "
+							+ third.getName()
+							+ "\nAuthor: "
+							+ third.getAuthorIdent().getName()
+							+ " <"
+							+ third.getAuthorIdent().getEmailAddress()
+							+ ">\nDate:   "
+							+ dateFormatter.formatDate(third
+									.getAuthorIdent()) + "\n\n\tthird commit\n",
+					db.readSquashCommitMsg());
+			assertEquals("\nConflicts:\n\tfile2\n", db.readMergeCommitMsg());
 
-		assertEquals(
-				"Squashed commit of the following:\n\ncommit "
-						+ third.getName()
-						+ "\nAuthor: "
-						+ third.getAuthorIdent().getName()
-						+ " <"
-						+ third.getAuthorIdent().getEmailAddress()
-						+ ">\nDate:   "
-						+ dateFormatter.formatDate(third
-								.getAuthorIdent()) + "\n\n\tthird commit\n",
-				db.readSquashCommitMsg());
-		assertEquals("\nConflicts:\n\tfile2\n", db.readMergeCommitMsg());
-
-		Status stat = git.status().call();
-		assertEquals(Sets.of("file2"), stat.getConflicting());
+			Status stat = git.status().call();
+			assertEquals(Sets.of("file2"), stat.getConflicting());
+		}
 	}
 
 	@Test
 	public void testFastForwardOnly() throws Exception {
-		Git git = new Git(db);
-		RevCommit initialCommit = git.commit().setMessage("initial commit")
-				.call();
-		createBranch(initialCommit, "refs/heads/branch1");
-		git.commit().setMessage("second commit").call();
-		checkoutBranch("refs/heads/branch1");
+		try (Git git = new Git(db)) {
+			RevCommit initialCommit = git.commit().setMessage("initial commit")
+					.call();
+			createBranch(initialCommit, "refs/heads/branch1");
+			git.commit().setMessage("second commit").call();
+			checkoutBranch("refs/heads/branch1");
 
-		MergeCommand merge = git.merge();
-		merge.setFastForward(FastForwardMode.FF_ONLY);
-		merge.include(db.getRef(Constants.MASTER));
-		MergeResult result = merge.call();
+			MergeCommand merge = git.merge();
+			merge.setFastForward(FastForwardMode.FF_ONLY);
+			merge.include(db.exactRef(R_HEADS + MASTER));
+			MergeResult result = merge.call();
 
-		assertEquals(MergeStatus.FAST_FORWARD, result.getMergeStatus());
+			assertEquals(MergeStatus.FAST_FORWARD, result.getMergeStatus());
+		}
 	}
 
 	@Test
 	public void testNoFastForward() throws Exception {
-		Git git = new Git(db);
-		RevCommit initialCommit = git.commit().setMessage("initial commit")
-				.call();
-		createBranch(initialCommit, "refs/heads/branch1");
-		git.commit().setMessage("second commit").call();
-		checkoutBranch("refs/heads/branch1");
+		try (Git git = new Git(db)) {
+			RevCommit initialCommit = git.commit().setMessage("initial commit")
+					.call();
+			createBranch(initialCommit, "refs/heads/branch1");
+			git.commit().setMessage("second commit").call();
+			checkoutBranch("refs/heads/branch1");
 
-		MergeCommand merge = git.merge();
-		merge.setFastForward(FastForwardMode.NO_FF);
-		merge.include(db.getRef(Constants.MASTER));
-		MergeResult result = merge.call();
+			MergeCommand merge = git.merge();
+			merge.setFastForward(FastForwardMode.NO_FF);
+			merge.include(db.exactRef(R_HEADS + MASTER));
+			MergeResult result = merge.call();
 
-		assertEquals(MergeStatus.MERGED, result.getMergeStatus());
+			assertEquals(MergeStatus.MERGED, result.getMergeStatus());
+		}
 	}
 
 	@Test
 	public void testNoFastForwardNoCommit() throws Exception {
 		// given
-		Git git = new Git(db);
-		RevCommit initialCommit = git.commit().setMessage("initial commit")
-				.call();
-		createBranch(initialCommit, "refs/heads/branch1");
-		RevCommit secondCommit = git.commit().setMessage("second commit")
-				.call();
-		checkoutBranch("refs/heads/branch1");
+		try (Git git = new Git(db)) {
+			RevCommit initialCommit = git.commit().setMessage("initial commit")
+					.call();
+			createBranch(initialCommit, "refs/heads/branch1");
+			RevCommit secondCommit = git.commit().setMessage("second commit")
+					.call();
+			checkoutBranch("refs/heads/branch1");
 
-		// when
-		MergeCommand merge = git.merge();
-		merge.setFastForward(FastForwardMode.NO_FF);
-		merge.include(db.getRef(Constants.MASTER));
-		merge.setCommit(false);
-		MergeResult result = merge.call();
+			// when
+			MergeCommand merge = git.merge();
+			merge.setFastForward(FastForwardMode.NO_FF);
+			merge.include(db.exactRef(R_HEADS + MASTER));
+			merge.setCommit(false);
+			MergeResult result = merge.call();
 
-		// then
-		assertEquals(MergeStatus.MERGED_NOT_COMMITTED, result.getMergeStatus());
-		assertEquals(2, result.getMergedCommits().length);
-		assertEquals(initialCommit, result.getMergedCommits()[0]);
-		assertEquals(secondCommit, result.getMergedCommits()[1]);
-		assertNull(result.getNewHead());
-		assertEquals(RepositoryState.MERGING_RESOLVED, db.getRepositoryState());
+			// then
+			assertEquals(MergeStatus.MERGED_NOT_COMMITTED, result.getMergeStatus());
+			assertEquals(2, result.getMergedCommits().length);
+			assertEquals(initialCommit, result.getMergedCommits()[0]);
+			assertEquals(secondCommit, result.getMergedCommits()[1]);
+			assertNull(result.getNewHead());
+			assertEquals(RepositoryState.MERGING_RESOLVED, db.getRepositoryState());
+		}
 	}
 
 	@Test
 	public void testFastForwardOnlyNotPossible() throws Exception {
-		Git git = new Git(db);
-		RevCommit initialCommit = git.commit().setMessage("initial commit")
-				.call();
-		createBranch(initialCommit, "refs/heads/branch1");
-		git.commit().setMessage("second commit").call();
-		checkoutBranch("refs/heads/branch1");
-		writeTrashFile("file1", "branch1");
-		git.add().addFilepattern("file").call();
-		git.commit().setMessage("second commit on branch1").call();
-		MergeCommand merge = git.merge();
-		merge.setFastForward(FastForwardMode.FF_ONLY);
-		merge.include(db.getRef(Constants.MASTER));
-		MergeResult result = merge.call();
+		try (Git git = new Git(db)) {
+			RevCommit initialCommit = git.commit().setMessage("initial commit")
+					.call();
+			createBranch(initialCommit, "refs/heads/branch1");
+			git.commit().setMessage("second commit").call();
+			checkoutBranch("refs/heads/branch1");
+			writeTrashFile("file1", "branch1");
+			git.add().addFilepattern("file").call();
+			git.commit().setMessage("second commit on branch1").call();
+			MergeCommand merge = git.merge();
+			merge.setFastForward(FastForwardMode.FF_ONLY);
+			merge.include(db.exactRef(R_HEADS + MASTER));
+			MergeResult result = merge.call();
 
-		assertEquals(MergeStatus.ABORTED, result.getMergeStatus());
+			assertEquals(MergeStatus.ABORTED, result.getMergeStatus());
+		}
 	}
 
 	@Test
@@ -1569,65 +1586,65 @@
 
 	@Test
 	public void testMergeWithMessageOption() throws Exception {
-		Git git = new Git(db);
+		try (Git git = new Git(db)) {
+			writeTrashFile("a", "1\na\n3\n");
+			git.add().addFilepattern("a").call();
+			RevCommit initialCommit = git.commit().setMessage("initial").call();
 
-		writeTrashFile("a", "1\na\n3\n");
-		git.add().addFilepattern("a").call();
-		RevCommit initialCommit = git.commit().setMessage("initial").call();
+			createBranch(initialCommit, "refs/heads/side");
+			checkoutBranch("refs/heads/side");
 
-		createBranch(initialCommit, "refs/heads/side");
-		checkoutBranch("refs/heads/side");
+			writeTrashFile("b", "1\nb\n3\n");
+			git.add().addFilepattern("b").call();
+			git.commit().setMessage("side").call();
 
-		writeTrashFile("b", "1\nb\n3\n");
-		git.add().addFilepattern("b").call();
-		git.commit().setMessage("side").call();
+			checkoutBranch("refs/heads/master");
 
-		checkoutBranch("refs/heads/master");
+			writeTrashFile("c", "1\nc\n3\n");
+			git.add().addFilepattern("c").call();
+			git.commit().setMessage("main").call();
 
-		writeTrashFile("c", "1\nc\n3\n");
-		git.add().addFilepattern("c").call();
-		git.commit().setMessage("main").call();
+			Ref sideBranch = db.exactRef("refs/heads/side");
 
-		Ref sideBranch = db.getRef("side");
+			git.merge().include(sideBranch).setStrategy(MergeStrategy.RESOLVE)
+					.setMessage("user message").call();
 
-		git.merge().include(sideBranch).setStrategy(MergeStrategy.RESOLVE)
-				.setMessage("user message").call();
+			assertNull(db.readMergeCommitMsg());
 
-		assertNull(db.readMergeCommitMsg());
-
-		Iterator<RevCommit> it = git.log().call().iterator();
-		RevCommit newHead = it.next();
-		assertEquals("user message", newHead.getFullMessage());
+			Iterator<RevCommit> it = git.log().call().iterator();
+			RevCommit newHead = it.next();
+			assertEquals("user message", newHead.getFullMessage());
+		}
 	}
 
 	@Test
 	public void testMergeConflictWithMessageOption() throws Exception {
-		Git git = new Git(db);
+		try (Git git = new Git(db)) {
+			writeTrashFile("a", "1\na\n3\n");
+			git.add().addFilepattern("a").call();
+			RevCommit initialCommit = git.commit().setMessage("initial").call();
 
-		writeTrashFile("a", "1\na\n3\n");
-		git.add().addFilepattern("a").call();
-		RevCommit initialCommit = git.commit().setMessage("initial").call();
+			createBranch(initialCommit, "refs/heads/side");
+			checkoutBranch("refs/heads/side");
 
-		createBranch(initialCommit, "refs/heads/side");
-		checkoutBranch("refs/heads/side");
+			writeTrashFile("a", "1\na(side)\n3\n");
+			git.add().addFilepattern("a").call();
+			git.commit().setMessage("side").call();
 
-		writeTrashFile("a", "1\na(side)\n3\n");
-		git.add().addFilepattern("a").call();
-		git.commit().setMessage("side").call();
+			checkoutBranch("refs/heads/master");
 
-		checkoutBranch("refs/heads/master");
+			writeTrashFile("a", "1\na(main)\n3\n");
+			git.add().addFilepattern("a").call();
+			git.commit().setMessage("main").call();
 
-		writeTrashFile("a", "1\na(main)\n3\n");
-		git.add().addFilepattern("a").call();
-		git.commit().setMessage("main").call();
+			Ref sideBranch = db.exactRef("refs/heads/side");
 
-		Ref sideBranch = db.getRef("side");
+			git.merge().include(sideBranch).setStrategy(MergeStrategy.RESOLVE)
+					.setMessage("user message").call();
 
-		git.merge().include(sideBranch).setStrategy(MergeStrategy.RESOLVE)
-				.setMessage("user message").call();
-
-		assertEquals("user message\n\nConflicts:\n\ta\n",
-				db.readMergeCommitMsg());
+			assertEquals("user message\n\nConflicts:\n\ta\n",
+					db.readMergeCommitMsg());
+		}
 	}
 
 	private static void setExecutable(Git git, String path, boolean executable) {
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/NameRevCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/NameRevCommandTest.java
index 4915954..bd62200 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/NameRevCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/NameRevCommandTest.java
@@ -95,9 +95,9 @@
 		tr.update("refs/heads/master", c);
 		tr.update("refs/tags/tag", c);
 		assertOneResult("master",
-				git.nameRev().addRef(db.getRef("refs/heads/master")), c);
+				git.nameRev().addRef(db.exactRef("refs/heads/master")), c);
 		assertOneResult("tag",
-				git.nameRev().addRef(db.getRef("refs/tags/tag")), c);
+				git.nameRev().addRef(db.exactRef("refs/tags/tag")), c);
 	}
 
 	@Test
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 db811cd..3343af0 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
@@ -43,10 +43,12 @@
 package org.eclipse.jgit.api;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
 import java.io.File;
 import java.io.IOException;
+import java.nio.file.Path;
 
 import org.eclipse.jgit.api.CheckoutCommand.Stage;
 import org.eclipse.jgit.api.errors.JGitInternalException;
@@ -59,6 +61,9 @@
 import org.eclipse.jgit.lib.RepositoryState;
 import org.eclipse.jgit.lib.StoredConfig;
 import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.util.FS;
+import org.eclipse.jgit.util.FileUtils;
+import org.junit.Assume;
 import org.junit.Before;
 import org.junit.Test;
 
@@ -73,6 +78,8 @@
 
 	private static final String FILE3 = "Test3.txt";
 
+	private static final String LINK = "link";
+
 	Git git;
 
 	RevCommit initialCommit;
@@ -99,6 +106,64 @@
 	}
 
 	@Test
+	public void testUpdateSymLink() throws Exception {
+		Assume.assumeTrue(FS.DETECTED.supportsSymlinks());
+
+		Path path = writeLink(LINK, FILE1);
+		git.add().addFilepattern(LINK).call();
+		git.commit().setMessage("Added link").call();
+		assertEquals("3", read(path.toFile()));
+
+		writeLink(LINK, FILE2);
+		assertEquals("c", read(path.toFile()));
+
+		CheckoutCommand co = git.checkout();
+		co.addPath(LINK).call();
+
+		assertEquals("3", read(path.toFile()));
+	}
+
+	@Test
+	public void testUpdateBrokenSymLinkToDirectory() throws Exception {
+		Assume.assumeTrue(FS.DETECTED.supportsSymlinks());
+
+		Path path = writeLink(LINK, "f");
+		git.add().addFilepattern(LINK).call();
+		git.commit().setMessage("Added link").call();
+		assertEquals("f", FileUtils.readSymLink(path.toFile()));
+		assertTrue(path.toFile().exists());
+
+		writeLink(LINK, "link_to_nowhere");
+		assertFalse(path.toFile().exists());
+		assertEquals("link_to_nowhere", FileUtils.readSymLink(path.toFile()));
+
+		CheckoutCommand co = git.checkout();
+		co.addPath(LINK).call();
+
+		assertEquals("f", FileUtils.readSymLink(path.toFile()));
+	}
+
+	@Test
+	public void testUpdateBrokenSymLink() throws Exception {
+		Assume.assumeTrue(FS.DETECTED.supportsSymlinks());
+
+		Path path = writeLink(LINK, FILE1);
+		git.add().addFilepattern(LINK).call();
+		git.commit().setMessage("Added link").call();
+		assertEquals("3", read(path.toFile()));
+		assertEquals(FILE1, FileUtils.readSymLink(path.toFile()));
+
+		writeLink(LINK, "link_to_nowhere");
+		assertFalse(path.toFile().exists());
+		assertEquals("link_to_nowhere", FileUtils.readSymLink(path.toFile()));
+
+		CheckoutCommand co = git.checkout();
+		co.addPath(LINK).call();
+
+		assertEquals("3", read(path.toFile()));
+	}
+
+	@Test
 	public void testUpdateWorkingDirectory() throws Exception {
 		CheckoutCommand co = git.checkout();
 		File written = writeTrashFile(FILE1, "");
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PullCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PullCommandTest.java
index 57888e7..ff7066e 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PullCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PullCommandTest.java
@@ -140,11 +140,12 @@
 		ObjectId[] mergedCommits = mergeResult.getMergedCommits();
 		assertEquals(targetCommit.getId(), mergedCommits[0]);
 		assertEquals(sourceCommit.getId(), mergedCommits[1]);
-		RevCommit mergeCommit = new RevWalk(dbTarget).parseCommit(mergeResult
-				.getNewHead());
-		String message = "Merge branch 'master' of "
-				+ db.getWorkTree().getAbsolutePath();
-		assertEquals(message, mergeCommit.getShortMessage());
+		try (RevWalk rw = new RevWalk(dbTarget)) {
+			RevCommit mergeCommit = rw.parseCommit(mergeResult.getNewHead());
+			String message = "Merge branch 'master' of "
+					+ db.getWorkTree().getAbsolutePath();
+			assertEquals(message, mergeCommit.getShortMessage());
+		}
 	}
 
 	@Test
@@ -259,11 +260,12 @@
 		ObjectId[] mergedCommits = mergeResult.getMergedCommits();
 		assertEquals(targetCommit.getId(), mergedCommits[0]);
 		assertEquals(sourceCommit.getId(), mergedCommits[1]);
-		RevCommit mergeCommit = new RevWalk(dbTarget).parseCommit(mergeResult
-				.getNewHead());
-		String message = "Merge branch 'other' of "
-				+ db.getWorkTree().getAbsolutePath();
-		assertEquals(message, mergeCommit.getShortMessage());
+		try (RevWalk rw = new RevWalk(dbTarget)) {
+			RevCommit mergeCommit = rw.parseCommit(mergeResult.getNewHead());
+			String message = "Merge branch 'other' of "
+					+ db.getWorkTree().getAbsolutePath();
+			assertEquals(message, mergeCommit.getShortMessage());
+		}
 	}
 
 	@Test
@@ -293,11 +295,12 @@
 		ObjectId[] mergedCommits = mergeResult.getMergedCommits();
 		assertEquals(targetCommit.getId(), mergedCommits[0]);
 		assertEquals(sourceCommit.getId(), mergedCommits[1]);
-		RevCommit mergeCommit = new RevWalk(dbTarget).parseCommit(mergeResult
-				.getNewHead());
-		String message = "Merge branch 'other' of "
-				+ db.getWorkTree().getAbsolutePath() + " into other";
-		assertEquals(message, mergeCommit.getShortMessage());
+		try (RevWalk rw = new RevWalk(dbTarget)) {
+			RevCommit mergeCommit = rw.parseCommit(mergeResult.getNewHead());
+			String message = "Merge branch 'other' of "
+					+ db.getWorkTree().getAbsolutePath() + " into other";
+			assertEquals(message, mergeCommit.getShortMessage());
+		}
 	}
 
 	private enum TestPullMode {
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PullCommandWithRebaseTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PullCommandWithRebaseTest.java
index 9ad845b..b405f6a 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PullCommandWithRebaseTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PullCommandWithRebaseTest.java
@@ -273,26 +273,27 @@
 
 		// Get the HEAD and HEAD~1 commits
 		Repository targetRepo = target.getRepository();
-		RevWalk revWalk = new RevWalk(targetRepo);
-		ObjectId headId = targetRepo.resolve(Constants.HEAD);
-		RevCommit root = revWalk.parseCommit(headId);
-		revWalk.markStart(root);
-		// HEAD
-		RevCommit head = revWalk.next();
-		// HEAD~1
-		RevCommit beforeHead = revWalk.next();
+		try (RevWalk revWalk = new RevWalk(targetRepo)) {
+			ObjectId headId = targetRepo.resolve(Constants.HEAD);
+			RevCommit root = revWalk.parseCommit(headId);
+			revWalk.markStart(root);
+			// HEAD
+			RevCommit head = revWalk.next();
+			// HEAD~1
+			RevCommit beforeHead = revWalk.next();
 
-		// verify the commit message on the HEAD commit
-		assertEquals(TARGET_COMMIT_MESSAGE, head.getFullMessage());
-		// verify the commit just before HEAD
-		assertEquals(SOURCE_COMMIT_MESSAGE, beforeHead.getFullMessage());
+			// verify the commit message on the HEAD commit
+			assertEquals(TARGET_COMMIT_MESSAGE, head.getFullMessage());
+			// verify the commit just before HEAD
+			assertEquals(SOURCE_COMMIT_MESSAGE, beforeHead.getFullMessage());
 
-		// verify file states
-		assertFileContentsEqual(sourceFile, SOURCE_FILE_CONTENTS);
-		assertFileContentsEqual(newFile, NEW_FILE_CONTENTS);
-		// verify repository state
-		assertEquals(RepositoryState.SAFE, target
-			.getRepository().getRepositoryState());
+			// verify file states
+			assertFileContentsEqual(sourceFile, SOURCE_FILE_CONTENTS);
+			assertFileContentsEqual(newFile, NEW_FILE_CONTENTS);
+			// verify repository state
+			assertEquals(RepositoryState.SAFE, target
+				.getRepository().getRepositoryState());
+		}
 	}
 
 	@Override
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PushCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PushCommandTest.java
index 19f074e..2a32540 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PushCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PushCommandTest.java
@@ -47,6 +47,7 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
+import java.io.File;
 import java.io.IOException;
 import java.net.URISyntaxException;
 import java.util.Properties;
@@ -55,7 +56,10 @@
 import org.eclipse.jgit.api.errors.JGitInternalException;
 import org.eclipse.jgit.api.errors.TransportException;
 import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.hooks.PrePushHook;
+import org.eclipse.jgit.junit.JGitTestUtil;
 import org.eclipse.jgit.junit.RepositoryTestCase;
+import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.Ref;
 import org.eclipse.jgit.lib.RefUpdate;
 import org.eclipse.jgit.lib.Repository;
@@ -66,6 +70,7 @@
 import org.eclipse.jgit.transport.RemoteConfig;
 import org.eclipse.jgit.transport.TrackingRefUpdate;
 import org.eclipse.jgit.transport.URIish;
+import org.eclipse.jgit.util.FS;
 import org.junit.Test;
 
 public class PushCommandTest extends RepositoryTestCase {
@@ -85,29 +90,71 @@
 		remoteConfig.update(config);
 		config.save();
 
-		Git git1 = new Git(db);
-		// create some refs via commits and tag
-		RevCommit commit = git1.commit().setMessage("initial commit").call();
-		Ref tagRef = git1.tag().setName("tag").call();
+		try (Git git1 = new Git(db)) {
+			// create some refs via commits and tag
+			RevCommit commit = git1.commit().setMessage("initial commit").call();
+			Ref tagRef = git1.tag().setName("tag").call();
 
-		try {
-			db2.resolve(commit.getId().getName() + "^{commit}");
-			fail("id shouldn't exist yet");
-		} catch (MissingObjectException e) {
-			// we should get here
+			try {
+				db2.resolve(commit.getId().getName() + "^{commit}");
+				fail("id shouldn't exist yet");
+			} catch (MissingObjectException e) {
+				// we should get here
+			}
+
+			RefSpec spec = new RefSpec("refs/heads/master:refs/heads/x");
+			git1.push().setRemote("test").setRefSpecs(spec)
+					.call();
+
+			assertEquals(commit.getId(),
+					db2.resolve(commit.getId().getName() + "^{commit}"));
+			assertEquals(tagRef.getObjectId(),
+					db2.resolve(tagRef.getObjectId().getName()));
 		}
-
-		RefSpec spec = new RefSpec("refs/heads/master:refs/heads/x");
-		git1.push().setRemote("test").setRefSpecs(spec)
-				.call();
-
-		assertEquals(commit.getId(),
-				db2.resolve(commit.getId().getName() + "^{commit}"));
-		assertEquals(tagRef.getObjectId(),
-				db2.resolve(tagRef.getObjectId().getName()));
 	}
 
 	@Test
+	public void testPrePushHook() throws JGitInternalException, IOException,
+			GitAPIException, URISyntaxException {
+
+		// create other repository
+		Repository db2 = createWorkRepository();
+
+		// setup the first repository
+		final StoredConfig config = db.getConfig();
+		RemoteConfig remoteConfig = new RemoteConfig(config, "test");
+		URIish uri = new URIish(db2.getDirectory().toURI().toURL());
+		remoteConfig.addURI(uri);
+		remoteConfig.update(config);
+		config.save();
+
+		File hookOutput = new File(getTemporaryDirectory(), "hookOutput");
+		writeHookFile(PrePushHook.NAME, "#!/bin/sh\necho 1:$1, 2:$2, 3:$3 >\""
+				+ hookOutput.toPath() + "\"\ncat - >>\"" + hookOutput.toPath()
+				+ "\"\nexit 0");
+
+		try (Git git1 = new Git(db)) {
+			// create some refs via commits and tag
+			RevCommit commit = git1.commit().setMessage("initial commit").call();
+
+			RefSpec spec = new RefSpec("refs/heads/master:refs/heads/x");
+			git1.push().setRemote("test").setRefSpecs(spec).call();
+			assertEquals("1:test, 2:" + uri + ", 3:\n" + "refs/heads/master "
+					+ commit.getName() + " refs/heads/x "
+					+ ObjectId.zeroId().name(), read(hookOutput));
+		}
+	}
+
+	private File writeHookFile(final String name, final String data)
+			throws IOException {
+		File path = new File(db.getWorkTree() + "/.git/hooks/", name);
+		JGitTestUtil.write(path, data);
+		FS.DETECTED.setExecute(path, true);
+		return path;
+	}
+
+
+	@Test
 	public void testTrackingUpdate() throws Exception {
 		Repository db2 = createBareRepository();
 
@@ -115,45 +162,45 @@
 		String branch = "refs/heads/master";
 		String trackingBranch = "refs/remotes/" + remote + "/master";
 
-		Git git = new Git(db);
+		try (Git git = new Git(db)) {
+			RevCommit commit1 = git.commit().setMessage("Initial commit")
+					.call();
 
-		RevCommit commit1 = git.commit().setMessage("Initial commit")
-				.call();
+			RefUpdate branchRefUpdate = db.updateRef(branch);
+			branchRefUpdate.setNewObjectId(commit1.getId());
+			branchRefUpdate.update();
 
-		RefUpdate branchRefUpdate = db.updateRef(branch);
-		branchRefUpdate.setNewObjectId(commit1.getId());
-		branchRefUpdate.update();
+			RefUpdate trackingBranchRefUpdate = db.updateRef(trackingBranch);
+			trackingBranchRefUpdate.setNewObjectId(commit1.getId());
+			trackingBranchRefUpdate.update();
 
-		RefUpdate trackingBranchRefUpdate = db.updateRef(trackingBranch);
-		trackingBranchRefUpdate.setNewObjectId(commit1.getId());
-		trackingBranchRefUpdate.update();
-
-		final StoredConfig config = db.getConfig();
-		RemoteConfig remoteConfig = new RemoteConfig(config, remote);
-		URIish uri = new URIish(db2.getDirectory().toURI().toURL());
-		remoteConfig.addURI(uri);
-		remoteConfig.addFetchRefSpec(new RefSpec("+refs/heads/*:refs/remotes/"
-				+ remote + "/*"));
-		remoteConfig.update(config);
-		config.save();
+			final StoredConfig config = db.getConfig();
+			RemoteConfig remoteConfig = new RemoteConfig(config, remote);
+			URIish uri = new URIish(db2.getDirectory().toURI().toURL());
+			remoteConfig.addURI(uri);
+			remoteConfig.addFetchRefSpec(new RefSpec("+refs/heads/*:refs/remotes/"
+					+ remote + "/*"));
+			remoteConfig.update(config);
+			config.save();
 
 
-		RevCommit commit2 = git.commit().setMessage("Commit to push").call();
+			RevCommit commit2 = git.commit().setMessage("Commit to push").call();
 
-		RefSpec spec = new RefSpec(branch + ":" + branch);
-		Iterable<PushResult> resultIterable = git.push().setRemote(remote)
-				.setRefSpecs(spec).call();
+			RefSpec spec = new RefSpec(branch + ":" + branch);
+			Iterable<PushResult> resultIterable = git.push().setRemote(remote)
+					.setRefSpecs(spec).call();
 
-		PushResult result = resultIterable.iterator().next();
-		TrackingRefUpdate trackingRefUpdate = result
-				.getTrackingRefUpdate(trackingBranch);
+			PushResult result = resultIterable.iterator().next();
+			TrackingRefUpdate trackingRefUpdate = result
+					.getTrackingRefUpdate(trackingBranch);
 
-		assertNotNull(trackingRefUpdate);
-		assertEquals(trackingBranch, trackingRefUpdate.getLocalName());
-		assertEquals(branch, trackingRefUpdate.getRemoteName());
-		assertEquals(commit2.getId(), trackingRefUpdate.getNewObjectId());
-		assertEquals(commit2.getId(), db.resolve(trackingBranch));
-		assertEquals(commit2.getId(), db2.resolve(branch));
+			assertNotNull(trackingRefUpdate);
+			assertEquals(trackingBranch, trackingRefUpdate.getLocalName());
+			assertEquals(branch, trackingRefUpdate.getRemoteName());
+			assertEquals(commit2.getId(), trackingRefUpdate.getNewObjectId());
+			assertEquals(commit2.getId(), db.resolve(trackingBranch));
+			assertEquals(commit2.getId(), db2.resolve(branch));
+		}
 	}
 
 	/**
@@ -163,40 +210,38 @@
 	 */
 	@Test
 	public void testPushRefUpdate() throws Exception {
-		Git git = new Git(db);
-		Git git2 = new Git(createBareRepository());
+		try (Git git = new Git(db);
+				Git git2 = new Git(createBareRepository())) {
+			final StoredConfig config = git.getRepository().getConfig();
+			RemoteConfig remoteConfig = new RemoteConfig(config, "test");
+			URIish uri = new URIish(git2.getRepository().getDirectory().toURI()
+					.toURL());
+			remoteConfig.addURI(uri);
+			remoteConfig.addPushRefSpec(new RefSpec("+refs/heads/*:refs/heads/*"));
+			remoteConfig.update(config);
+			config.save();
 
-		final StoredConfig config = git.getRepository().getConfig();
-		RemoteConfig remoteConfig = new RemoteConfig(config, "test");
-		URIish uri = new URIish(git2.getRepository().getDirectory().toURI()
-				.toURL());
-		remoteConfig.addURI(uri);
-		remoteConfig.addPushRefSpec(new RefSpec("+refs/heads/*:refs/heads/*"));
-		remoteConfig.update(config);
-		config.save();
+			writeTrashFile("f", "content of f");
+			git.add().addFilepattern("f").call();
+			RevCommit commit = git.commit().setMessage("adding f").call();
 
-		writeTrashFile("f", "content of f");
-		git.add().addFilepattern("f").call();
-		RevCommit commit = git.commit().setMessage("adding f").call();
-
-		assertEquals(null, git2.getRepository().resolve("refs/heads/master"));
-		git.push().setRemote("test").call();
-		assertEquals(commit.getId(),
-				git2.getRepository().resolve("refs/heads/master"));
-
-		git.branchCreate().setName("refs/heads/test").call();
-		git.checkout().setName("refs/heads/test").call();
-
-
-		for (int i = 0; i < 6; i++) {
-			writeTrashFile("f" + i, "content of f" + i);
-			git.add().addFilepattern("f" + i).call();
-			commit = git.commit().setMessage("adding f" + i).call();
+			assertEquals(null, git2.getRepository().resolve("refs/heads/master"));
 			git.push().setRemote("test").call();
-			git2.getRepository().getAllRefs();
-			assertEquals("failed to update on attempt " + i, commit.getId(),
-					git2.getRepository().resolve("refs/heads/test"));
+			assertEquals(commit.getId(),
+					git2.getRepository().resolve("refs/heads/master"));
 
+			git.branchCreate().setName("refs/heads/test").call();
+			git.checkout().setName("refs/heads/test").call();
+
+			for (int i = 0; i < 6; i++) {
+				writeTrashFile("f" + i, "content of f" + i);
+				git.add().addFilepattern("f" + i).call();
+				commit = git.commit().setMessage("adding f" + i).call();
+				git.push().setRemote("test").call();
+				git2.getRepository().getAllRefs();
+				assertEquals("failed to update on attempt " + i, commit.getId(),
+						git2.getRepository().resolve("refs/heads/test"));
+			}
 		}
 	}
 
@@ -207,28 +252,26 @@
 	 */
 	@Test
 	public void testPushWithRefSpecFromConfig() throws Exception {
-		Git git = new Git(db);
-		Git git2 = new Git(createBareRepository());
+		try (Git git = new Git(db);
+				Git git2 = new Git(createBareRepository())) {
+			final StoredConfig config = git.getRepository().getConfig();
+			RemoteConfig remoteConfig = new RemoteConfig(config, "test");
+			URIish uri = new URIish(git2.getRepository().getDirectory().toURI()
+					.toURL());
+			remoteConfig.addURI(uri);
+			remoteConfig.addPushRefSpec(new RefSpec("HEAD:refs/heads/newbranch"));
+			remoteConfig.update(config);
+			config.save();
 
-		final StoredConfig config = git.getRepository().getConfig();
-		RemoteConfig remoteConfig = new RemoteConfig(config, "test");
-		URIish uri = new URIish(git2.getRepository().getDirectory().toURI()
-				.toURL());
-		remoteConfig.addURI(uri);
-		remoteConfig.addPushRefSpec(new RefSpec("HEAD:refs/heads/newbranch"));
-		remoteConfig.update(config);
-		config.save();
+			writeTrashFile("f", "content of f");
+			git.add().addFilepattern("f").call();
+			RevCommit commit = git.commit().setMessage("adding f").call();
 
-		writeTrashFile("f", "content of f");
-		git.add().addFilepattern("f").call();
-		RevCommit commit = git.commit().setMessage("adding f").call();
-
-		assertEquals(null, git2.getRepository().resolve("refs/heads/master"));
-		git.push().setRemote("test").call();
-		assertEquals(commit.getId(),
-				git2.getRepository().resolve("refs/heads/newbranch"));
-
-
+			assertEquals(null, git2.getRepository().resolve("refs/heads/master"));
+			git.push().setRemote("test").call();
+			assertEquals(commit.getId(),
+					git2.getRepository().resolve("refs/heads/newbranch"));
+		}
 	}
 
 	/**
@@ -238,38 +281,37 @@
 	 */
 	@Test
 	public void testPushWithoutPushRefSpec() throws Exception {
-		Git git = new Git(db);
-		Git git2 = new Git(createBareRepository());
+		try (Git git = new Git(db);
+				Git git2 = new Git(createBareRepository())) {
+			final StoredConfig config = git.getRepository().getConfig();
+			RemoteConfig remoteConfig = new RemoteConfig(config, "test");
+			URIish uri = new URIish(git2.getRepository().getDirectory().toURI()
+					.toURL());
+			remoteConfig.addURI(uri);
+			remoteConfig.addFetchRefSpec(new RefSpec(
+					"+refs/heads/*:refs/remotes/origin/*"));
+			remoteConfig.update(config);
+			config.save();
 
-		final StoredConfig config = git.getRepository().getConfig();
-		RemoteConfig remoteConfig = new RemoteConfig(config, "test");
-		URIish uri = new URIish(git2.getRepository().getDirectory().toURI()
-				.toURL());
-		remoteConfig.addURI(uri);
-		remoteConfig.addFetchRefSpec(new RefSpec(
-				"+refs/heads/*:refs/remotes/origin/*"));
-		remoteConfig.update(config);
-		config.save();
+			writeTrashFile("f", "content of f");
+			git.add().addFilepattern("f").call();
+			RevCommit commit = git.commit().setMessage("adding f").call();
 
-		writeTrashFile("f", "content of f");
-		git.add().addFilepattern("f").call();
-		RevCommit commit = git.commit().setMessage("adding f").call();
+			git.checkout().setName("not-pushed").setCreateBranch(true).call();
+			git.checkout().setName("branchtopush").setCreateBranch(true).call();
 
-		git.checkout().setName("not-pushed").setCreateBranch(true).call();
-		git.checkout().setName("branchtopush").setCreateBranch(true).call();
-
-		assertEquals(null,
-				git2.getRepository().resolve("refs/heads/branchtopush"));
-		assertEquals(null, git2.getRepository()
-				.resolve("refs/heads/not-pushed"));
-		assertEquals(null, git2.getRepository().resolve("refs/heads/master"));
-		git.push().setRemote("test").call();
-		assertEquals(commit.getId(),
-				git2.getRepository().resolve("refs/heads/branchtopush"));
-		assertEquals(null, git2.getRepository()
-				.resolve("refs/heads/not-pushed"));
-		assertEquals(null, git2.getRepository().resolve("refs/heads/master"));
-
+			assertEquals(null,
+					git2.getRepository().resolve("refs/heads/branchtopush"));
+			assertEquals(null, git2.getRepository()
+					.resolve("refs/heads/not-pushed"));
+			assertEquals(null, git2.getRepository().resolve("refs/heads/master"));
+			git.push().setRemote("test").call();
+			assertEquals(commit.getId(),
+					git2.getRepository().resolve("refs/heads/branchtopush"));
+			assertEquals(null, git2.getRepository()
+					.resolve("refs/heads/not-pushed"));
+			assertEquals(null, git2.getRepository().resolve("refs/heads/master"));
+		}
 	}
 
 	/**
@@ -290,51 +332,51 @@
 		remoteConfig.update(config);
 		config.save();
 
-		Git git1 = new Git(db);
-		Git git2 = new Git(db2);
+		try (Git git1 = new Git(db);
+				Git git2 = new Git(db2)) {
+			// push master (with a new commit) to the remote
+			git1.commit().setMessage("initial commit").call();
 
-		// push master (with a new commit) to the remote
-		git1.commit().setMessage("initial commit").call();
-
-		RefSpec spec = new RefSpec("refs/heads/*:refs/heads/*");
-		git1.push().setRemote("test").setRefSpecs(spec).call();
-
-		// create an unrelated ref and a commit on our remote
-		git2.branchCreate().setName("refs/heads/other").call();
-		git2.checkout().setName("refs/heads/other").call();
-
-		writeTrashFile("a", "content of a");
-		git2.add().addFilepattern("a").call();
-		RevCommit commit2 = git2.commit().setMessage("adding a").call();
-
-		// run a gc to ensure we have a bitmap index
-		Properties res = git1.gc().setExpire(null).call();
-		assertEquals(7, res.size());
-
-		// create another commit so we have something else to push
-		writeTrashFile("b", "content of b");
-		git1.add().addFilepattern("b").call();
-		RevCommit commit3 = git1.commit().setMessage("adding b").call();
-
-		try {
-			// Re-run the push.  Failure may happen here.
+			RefSpec spec = new RefSpec("refs/heads/*:refs/heads/*");
 			git1.push().setRemote("test").setRefSpecs(spec).call();
-		} catch (TransportException e) {
-			assertTrue("should be caused by a MissingObjectException", e
-					.getCause().getCause() instanceof MissingObjectException);
-			fail("caught MissingObjectException for a change we don't have");
-		}
 
-		// Remote will have both a and b.  Master will have only b
-		try {
-			db.resolve(commit2.getId().getName() + "^{commit}");
-			fail("id shouldn't exist locally");
-		} catch (MissingObjectException e) {
-			// we should get here
+			// create an unrelated ref and a commit on our remote
+			git2.branchCreate().setName("refs/heads/other").call();
+			git2.checkout().setName("refs/heads/other").call();
+
+			writeTrashFile("a", "content of a");
+			git2.add().addFilepattern("a").call();
+			RevCommit commit2 = git2.commit().setMessage("adding a").call();
+
+			// run a gc to ensure we have a bitmap index
+			Properties res = git1.gc().setExpire(null).call();
+			assertEquals(7, res.size());
+
+			// create another commit so we have something else to push
+			writeTrashFile("b", "content of b");
+			git1.add().addFilepattern("b").call();
+			RevCommit commit3 = git1.commit().setMessage("adding b").call();
+
+			try {
+				// Re-run the push.  Failure may happen here.
+				git1.push().setRemote("test").setRefSpecs(spec).call();
+			} catch (TransportException e) {
+				assertTrue("should be caused by a MissingObjectException", e
+						.getCause().getCause() instanceof MissingObjectException);
+				fail("caught MissingObjectException for a change we don't have");
+			}
+
+			// Remote will have both a and b.  Master will have only b
+			try {
+				db.resolve(commit2.getId().getName() + "^{commit}");
+				fail("id shouldn't exist locally");
+			} catch (MissingObjectException e) {
+				// we should get here
+			}
+			assertEquals(commit2.getId(),
+					db2.resolve(commit2.getId().getName() + "^{commit}"));
+			assertEquals(commit3.getId(),
+					db2.resolve(commit3.getId().getName() + "^{commit}"));
 		}
-		assertEquals(commit2.getId(),
-				db2.resolve(commit2.getId().getName() + "^{commit}"));
-		assertEquals(commit3.getId(),
-				db2.resolve(commit3.getId().getName() + "^{commit}"));
 	}
 }
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RebaseCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RebaseCommandTest.java
index 8b0ed5f..24cb522 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RebaseCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RebaseCommandTest.java
@@ -288,13 +288,14 @@
 		RebaseResult res = git.rebase().setUpstream("refs/heads/master").call();
 		assertEquals(Status.OK, res.getStatus());
 
-		RevWalk rw = new RevWalk(db);
-		rw.markStart(rw.parseCommit(db.resolve("refs/heads/topic")));
-		assertDerivedFrom(rw.next(), e);
-		assertDerivedFrom(rw.next(), d);
-		assertDerivedFrom(rw.next(), c);
-		assertEquals(b, rw.next());
-		assertEquals(a, rw.next());
+		try (RevWalk rw = new RevWalk(db)) {
+			rw.markStart(rw.parseCommit(db.resolve("refs/heads/topic")));
+			assertDerivedFrom(rw.next(), e);
+			assertDerivedFrom(rw.next(), d);
+			assertDerivedFrom(rw.next(), c);
+			assertEquals(b, rw.next());
+			assertEquals(a, rw.next());
+		}
 
 		List<ReflogEntry> headLog = db.getReflogReader(Constants.HEAD)
 				.getReverseEntries();
@@ -354,8 +355,6 @@
 	 */
 	private void doTestRebasePreservingMerges(boolean testConflict)
 			throws Exception {
-		RevWalk rw = new RevWalk(db);
-
 		// create file1 on master
 		writeTrashFile(FILE1, FILE1);
 		git.add().addFilepattern(FILE1).call();
@@ -409,7 +408,9 @@
 			f = git.commit().setMessage("commit f").call();
 		} else {
 			assertEquals(MergeStatus.MERGED, result.getMergeStatus());
-			f = rw.parseCommit(result.getNewHead());
+			try (RevWalk rw = new RevWalk(db)) {
+				f = rw.parseCommit(result.getNewHead());
+			}
 		}
 
 		RebaseResult res = git.rebase().setUpstream("refs/heads/master")
@@ -453,23 +454,25 @@
 		assertEquals("file2", read("file2"));
 		assertEquals("more change", read("file3"));
 
-		rw.markStart(rw.parseCommit(db.resolve("refs/heads/topic")));
-		RevCommit newF = rw.next();
-		assertDerivedFrom(newF, f);
-		assertEquals(2, newF.getParentCount());
-		RevCommit newD = rw.next();
-		assertDerivedFrom(newD, d);
-		if (testConflict)
-			assertEquals("d new", readFile("conflict", newD));
-		RevCommit newE = rw.next();
-		assertDerivedFrom(newE, e);
-		if (testConflict)
-			assertEquals("e new", readFile("conflict", newE));
-		assertEquals(newD, newF.getParent(0));
-		assertEquals(newE, newF.getParent(1));
-		assertDerivedFrom(rw.next(), c);
-		assertEquals(b, rw.next());
-		assertEquals(a, rw.next());
+		try (RevWalk rw = new RevWalk(db)) {
+			rw.markStart(rw.parseCommit(db.resolve("refs/heads/topic")));
+			RevCommit newF = rw.next();
+			assertDerivedFrom(newF, f);
+			assertEquals(2, newF.getParentCount());
+			RevCommit newD = rw.next();
+			assertDerivedFrom(newD, d);
+			if (testConflict)
+				assertEquals("d new", readFile("conflict", newD));
+			RevCommit newE = rw.next();
+			assertDerivedFrom(newE, e);
+			if (testConflict)
+				assertEquals("e new", readFile("conflict", newE));
+			assertEquals(newD, newF.getParent(0));
+			assertEquals(newE, newF.getParent(1));
+			assertDerivedFrom(rw.next(), c);
+			assertEquals(b, rw.next());
+			assertEquals(a, rw.next());
+		}
 	}
 
 	private String readFile(String path, RevCommit commit) throws IOException {
@@ -517,88 +520,89 @@
 	 */
 	private void doTestRebasePreservingMergesWithUnrelatedSide(
 			boolean testConflict) throws Exception {
-		RevWalk rw = new RevWalk(db);
-		rw.sort(RevSort.TOPO);
+		try (RevWalk rw = new RevWalk(db)) {
+			rw.sort(RevSort.TOPO);
 
-		writeTrashFile(FILE1, FILE1);
-		git.add().addFilepattern(FILE1).call();
-		RevCommit a = git.commit().setMessage("commit a").call();
+			writeTrashFile(FILE1, FILE1);
+			git.add().addFilepattern(FILE1).call();
+			RevCommit a = git.commit().setMessage("commit a").call();
 
-		writeTrashFile("file2", "blah");
-		git.add().addFilepattern("file2").call();
-		RevCommit b = git.commit().setMessage("commit b").call();
+			writeTrashFile("file2", "blah");
+			git.add().addFilepattern("file2").call();
+			RevCommit b = git.commit().setMessage("commit b").call();
 
-		// create a topic branch
-		createBranch(b, "refs/heads/topic");
-		checkoutBranch("refs/heads/topic");
+			// create a topic branch
+			createBranch(b, "refs/heads/topic");
+			checkoutBranch("refs/heads/topic");
 
-		writeTrashFile("file3", "more changess");
-		writeTrashFile(FILE1, "preparing conflict");
-		git.add().addFilepattern("file3").addFilepattern(FILE1).call();
-		RevCommit c = git.commit().setMessage("commit c").call();
+			writeTrashFile("file3", "more changess");
+			writeTrashFile(FILE1, "preparing conflict");
+			git.add().addFilepattern("file3").addFilepattern(FILE1).call();
+			RevCommit c = git.commit().setMessage("commit c").call();
 
-		createBranch(a, "refs/heads/side");
-		checkoutBranch("refs/heads/side");
-		writeTrashFile("conflict", "e");
-		writeTrashFile(FILE1, FILE1 + "\n" + "line 2");
-		git.add().addFilepattern(".").call();
-		RevCommit e = git.commit().setMessage("commit e").call();
-
-		// switch back to topic and merge in side, creating d
-		checkoutBranch("refs/heads/topic");
-		MergeResult result = git.merge().include(e)
-				.setStrategy(MergeStrategy.RESOLVE).call();
-
-		assertEquals(MergeStatus.CONFLICTING, result.getMergeStatus());
-		assertEquals(result.getConflicts().keySet(),
-				Collections.singleton(FILE1));
-		writeTrashFile(FILE1, "merge resolution");
-		git.add().addFilepattern(FILE1).call();
-		RevCommit d = git.commit().setMessage("commit d").call();
-
-		RevCommit f = commitFile("file2", "new content two", "topic");
-
-		checkoutBranch("refs/heads/master");
-		writeTrashFile("fileg", "fileg");
-		if (testConflict)
-			writeTrashFile("conflict", "g");
-		git.add().addFilepattern(".").call();
-		RevCommit g = git.commit().setMessage("commit g").call();
-
-		checkoutBranch("refs/heads/topic");
-		RebaseResult res = git.rebase().setUpstream("refs/heads/master")
-				.setPreserveMerges(true).call();
-		if (testConflict) {
-			assertEquals(Status.STOPPED, res.getStatus());
-			assertEquals(Collections.singleton("conflict"), git.status().call()
-					.getConflicting());
-			// resolve
+			createBranch(a, "refs/heads/side");
+			checkoutBranch("refs/heads/side");
 			writeTrashFile("conflict", "e");
-			git.add().addFilepattern("conflict").call();
-			res = git.rebase().setOperation(Operation.CONTINUE).call();
+			writeTrashFile(FILE1, FILE1 + "\n" + "line 2");
+			git.add().addFilepattern(".").call();
+			RevCommit e = git.commit().setMessage("commit e").call();
+
+			// switch back to topic and merge in side, creating d
+			checkoutBranch("refs/heads/topic");
+			MergeResult result = git.merge().include(e)
+					.setStrategy(MergeStrategy.RESOLVE).call();
+
+			assertEquals(MergeStatus.CONFLICTING, result.getMergeStatus());
+			assertEquals(result.getConflicts().keySet(),
+					Collections.singleton(FILE1));
+			writeTrashFile(FILE1, "merge resolution");
+			git.add().addFilepattern(FILE1).call();
+			RevCommit d = git.commit().setMessage("commit d").call();
+
+			RevCommit f = commitFile("file2", "new content two", "topic");
+
+			checkoutBranch("refs/heads/master");
+			writeTrashFile("fileg", "fileg");
+			if (testConflict)
+				writeTrashFile("conflict", "g");
+			git.add().addFilepattern(".").call();
+			RevCommit g = git.commit().setMessage("commit g").call();
+
+			checkoutBranch("refs/heads/topic");
+			RebaseResult res = git.rebase().setUpstream("refs/heads/master")
+					.setPreserveMerges(true).call();
+			if (testConflict) {
+				assertEquals(Status.STOPPED, res.getStatus());
+				assertEquals(Collections.singleton("conflict"), git.status().call()
+						.getConflicting());
+				// resolve
+				writeTrashFile("conflict", "e");
+				git.add().addFilepattern("conflict").call();
+				res = git.rebase().setOperation(Operation.CONTINUE).call();
+			}
+			assertEquals(Status.OK, res.getStatus());
+
+			assertEquals("merge resolution", read(FILE1));
+			assertEquals("new content two", read("file2"));
+			assertEquals("more changess", read("file3"));
+			assertEquals("fileg", read("fileg"));
+
+			rw.markStart(rw.parseCommit(db.resolve("refs/heads/topic")));
+			RevCommit newF = rw.next();
+			assertDerivedFrom(newF, f);
+			RevCommit newD = rw.next();
+			assertDerivedFrom(newD, d);
+			assertEquals(2, newD.getParentCount());
+			RevCommit newC = rw.next();
+			assertDerivedFrom(newC, c);
+			RevCommit newE = rw.next();
+			assertEquals(e, newE);
+			assertEquals(newC, newD.getParent(0));
+			assertEquals(e, newD.getParent(1));
+			assertEquals(g, rw.next());
+			assertEquals(b, rw.next());
+			assertEquals(a, rw.next());
 		}
-		assertEquals(Status.OK, res.getStatus());
-
-		assertEquals("merge resolution", read(FILE1));
-		assertEquals("new content two", read("file2"));
-		assertEquals("more changess", read("file3"));
-		assertEquals("fileg", read("fileg"));
-
-		rw.markStart(rw.parseCommit(db.resolve("refs/heads/topic")));
-		RevCommit newF = rw.next();
-		assertDerivedFrom(newF, f);
-		RevCommit newD = rw.next();
-		assertDerivedFrom(newD, d);
-		assertEquals(2, newD.getParentCount());
-		RevCommit newC = rw.next();
-		assertDerivedFrom(newC, c);
-		RevCommit newE = rw.next();
-		assertEquals(e, newE);
-		assertEquals(newC, newD.getParent(0));
-		assertEquals(e, newD.getParent(1));
-		assertEquals(g, rw.next());
-		assertEquals(b, rw.next());
-		assertEquals(a, rw.next());
 	}
 
 	@Test
@@ -687,8 +691,10 @@
 		checkFile(theFile, "1master\n2\n3\ntopic\n");
 		// our old branch should be checked out again
 		assertEquals("refs/heads/topic", db.getFullBranch());
-		assertEquals(lastMasterChange, new RevWalk(db).parseCommit(
-				db.resolve(Constants.HEAD)).getParent(0));
+		try (RevWalk rw = new RevWalk(db)) {
+			assertEquals(lastMasterChange, rw.parseCommit(
+					db.resolve(Constants.HEAD)).getParent(0));
+		}
 		assertEquals(origHead, db.readOrigHead());
 		List<ReflogEntry> headLog = db.getReflogReader(Constants.HEAD)
 				.getReverseEntries();
@@ -737,8 +743,10 @@
 		RebaseResult res = git.rebase().setUpstream("refs/heads/master").call();
 		assertEquals(Status.OK, res.getStatus());
 		checkFile(theFile, "1master\n2\n3\ntopic\n");
-		assertEquals(lastMasterChange, new RevWalk(db).parseCommit(
-				db.resolve(Constants.HEAD)).getParent(0));
+		try (RevWalk rw = new RevWalk(db)) {
+			assertEquals(lastMasterChange, rw.parseCommit(
+					db.resolve(Constants.HEAD)).getParent(0));
+		}
 
 		List<ReflogEntry> headLog = db.getReflogReader(Constants.HEAD)
 				.getReverseEntries();
@@ -785,8 +793,10 @@
 
 		// our old branch should be checked out again
 		assertEquals("refs/heads/file3", db.getFullBranch());
-		assertEquals(addFile2, new RevWalk(db).parseCommit(
-				db.resolve(Constants.HEAD)).getParent(0));
+		try (RevWalk rw = new RevWalk(db)) {
+			assertEquals(addFile2, rw.parseCommit(
+					db.resolve(Constants.HEAD)).getParent(0));
+		}
 
 		checkoutBranch("refs/heads/file2");
 		assertTrue(new File(db.getWorkTree(), FILE1).exists());
@@ -846,9 +856,10 @@
 		assertEquals(res.getStatus(), Status.ABORTED);
 		assertEquals("refs/heads/topic", db.getFullBranch());
 		checkFile(FILE1, "1topic", "2", "3", "topic4");
-		RevWalk rw = new RevWalk(db);
-		assertEquals(lastTopicCommit, rw
-				.parseCommit(db.resolve(Constants.HEAD)));
+		try (RevWalk rw = new RevWalk(db)) {
+			assertEquals(lastTopicCommit,
+					rw.parseCommit(db.resolve(Constants.HEAD)));
+		}
 		assertEquals(RepositoryState.SAFE, db.getRepositoryState());
 
 		// rebase- dir in .git must be deleted
@@ -909,9 +920,10 @@
 		assertEquals(res.getStatus(), Status.ABORTED);
 		assertEquals(lastTopicCommit.getName(), db.getFullBranch());
 		checkFile(FILE1, "1topic", "2", "3", "topic4");
-		RevWalk rw = new RevWalk(db);
-		assertEquals(lastTopicCommit,
-				rw.parseCommit(db.resolve(Constants.HEAD)));
+		try (RevWalk rw = new RevWalk(db)) {
+			assertEquals(lastTopicCommit,
+					rw.parseCommit(db.resolve(Constants.HEAD)));
+		}
 		assertEquals(RepositoryState.SAFE, db.getRepositoryState());
 
 		// rebase- dir in .git must be deleted
@@ -966,11 +978,12 @@
 		assertEquals(RepositoryState.SAFE, db.getRepositoryState());
 
 		ObjectId headId = db.resolve(Constants.HEAD);
-		RevWalk rw = new RevWalk(db);
-		RevCommit rc = rw.parseCommit(headId);
-		RevCommit parent = rw.parseCommit(rc.getParent(0));
-		assertEquals("change file1 in topic\n\nThis is conflicting", parent
-				.getFullMessage());
+		try (RevWalk rw = new RevWalk(db)) {
+			RevCommit rc = rw.parseCommit(headId);
+			RevCommit parent = rw.parseCommit(rc.getParent(0));
+			assertEquals("change file1 in topic\n\nThis is conflicting", parent
+					.getFullMessage());
+		}
 	}
 
 	@Test
@@ -1017,9 +1030,10 @@
 		git.rebase().setOperation(Operation.SKIP).call();
 
 		ObjectId headId = db.resolve(Constants.HEAD);
-		RevWalk rw = new RevWalk(db);
-		RevCommit rc = rw.parseCommit(headId);
-		assertEquals("change file1 in master", rc.getFullMessage());
+		try (RevWalk rw = new RevWalk(db)) {
+			RevCommit rc = rw.parseCommit(headId);
+			assertEquals("change file1 in master", rc.getFullMessage());
+		}
 	}
 
 	@Test
@@ -1308,10 +1322,11 @@
 		git.rebase().setOperation(Operation.SKIP).call();
 
 		ObjectId headId = db.resolve(Constants.HEAD);
-		RevWalk rw = new RevWalk(db);
-		RevCommit rc = rw.parseCommit(headId);
-		RevCommit parent = rw.parseCommit(rc.getParent(0));
-		assertEquals("A different commit message", parent.getFullMessage());
+		try (RevWalk rw = new RevWalk(db)) {
+			RevCommit rc = rw.parseCommit(headId);
+			RevCommit parent = rw.parseCommit(rc.getParent(0));
+			assertEquals("A different commit message", parent.getFullMessage());
+		}
 	}
 
 	private RevCommit writeFileAndCommit(String fileName, String commitMessage,
@@ -1420,9 +1435,10 @@
 		res = git.rebase().setOperation(Operation.ABORT).call();
 		assertEquals(res.getStatus(), Status.ABORTED);
 		assertEquals("refs/heads/topic", db.getFullBranch());
-		RevWalk rw = new RevWalk(db);
-		assertEquals(conflicting, rw.parseCommit(db.resolve(Constants.HEAD)));
-		assertEquals(RepositoryState.SAFE, db.getRepositoryState());
+		try (RevWalk rw = new RevWalk(db)) {
+			assertEquals(conflicting, rw.parseCommit(db.resolve(Constants.HEAD)));
+			assertEquals(RepositoryState.SAFE, db.getRepositoryState());
+		}
 
 		// rebase- dir in .git must be deleted
 		assertFalse(new File(db.getDirectory(), "rebase-merge").exists());
@@ -2286,14 +2302,15 @@
 		assertEquals(RebaseResult.Status.OK, res2.getStatus());
 
 		ObjectId headId = db.resolve(Constants.HEAD);
-		RevWalk rw = new RevWalk(db);
-		RevCommit rc = rw.parseCommit(headId);
+		try (RevWalk rw = new RevWalk(db)) {
+			RevCommit rc = rw.parseCommit(headId);
 
-		ObjectId head1Id = db.resolve(Constants.HEAD + "~1");
-		RevCommit rc1 = rw.parseCommit(head1Id);
+			ObjectId head1Id = db.resolve(Constants.HEAD + "~1");
+			RevCommit rc1 = rw.parseCommit(head1Id);
 
-		assertEquals(rc.getFullMessage(), c4.getFullMessage());
-		assertEquals(rc1.getFullMessage(), c2.getFullMessage());
+			assertEquals(rc.getFullMessage(), c4.getFullMessage());
+			assertEquals(rc1.getFullMessage(), c2.getFullMessage());
+		}
 	}
 
 	@Test
@@ -2643,15 +2660,16 @@
 					}
 				}).call();
 
-		RevWalk walk = new RevWalk(db);
-		ObjectId headId = db.resolve(Constants.HEAD);
-		RevCommit headCommit = walk.parseCommit(headId);
-		assertEquals(headCommit.getFullMessage(),
-				"update file2 on master\nnew line");
+		try (RevWalk walk = new RevWalk(db)) {
+			ObjectId headId = db.resolve(Constants.HEAD);
+			RevCommit headCommit = walk.parseCommit(headId);
+			assertEquals(headCommit.getFullMessage(),
+					"update file2 on master\nnew line");
 
-		ObjectId head2Id = db.resolve(Constants.HEAD + "^1");
-		RevCommit head1Commit = walk.parseCommit(head2Id);
-		assertEquals("changed", head1Commit.getFullMessage());
+			ObjectId head2Id = db.resolve(Constants.HEAD + "^1");
+			RevCommit head1Commit = walk.parseCommit(head2Id);
+			assertEquals("changed", head1Commit.getFullMessage());
+		}
 	}
 
 	@Test
@@ -2722,17 +2740,18 @@
 					}
 				}).call();
 
-		RevWalk walk = new RevWalk(db);
-		ObjectId headId = db.resolve(Constants.HEAD);
-		RevCommit headCommit = walk.parseCommit(headId);
-		assertEquals(headCommit.getFullMessage(),
-				"update file2 on master\nnew line");
+		try (RevWalk walk = new RevWalk(db)) {
+			ObjectId headId = db.resolve(Constants.HEAD);
+			RevCommit headCommit = walk.parseCommit(headId);
+			assertEquals(headCommit.getFullMessage(),
+					"update file2 on master\nnew line");
 
-		ObjectId head2Id = db.resolve(Constants.HEAD + "^1");
-		RevCommit head1Commit = walk.parseCommit(head2Id);
-		assertEquals(
-				"Add file1\nnew line\nAdd file2\nnew line\nupdated file1 on master\nnew line",
-				head1Commit.getFullMessage());
+			ObjectId head2Id = db.resolve(Constants.HEAD + "^1");
+			RevCommit head1Commit = walk.parseCommit(head2Id);
+			assertEquals(
+					"Add file1\nnew line\nAdd file2\nnew line\nupdated file1 on master\nnew line",
+					head1Commit.getFullMessage());
+		}
 	}
 
 	@Test
@@ -2804,15 +2823,16 @@
 					}
 				}).call();
 
-		RevWalk walk = new RevWalk(db);
-		ObjectId headId = db.resolve(Constants.HEAD);
-		RevCommit headCommit = walk.parseCommit(headId);
-		assertEquals(headCommit.getFullMessage(),
-				"update file2 on master\nnew line");
+		try (RevWalk walk = new RevWalk(db)) {
+			ObjectId headId = db.resolve(Constants.HEAD);
+			RevCommit headCommit = walk.parseCommit(headId);
+			assertEquals(headCommit.getFullMessage(),
+					"update file2 on master\nnew line");
 
-		ObjectId head2Id = db.resolve(Constants.HEAD + "^1");
-		RevCommit head1Commit = walk.parseCommit(head2Id);
-		assertEquals("changed", head1Commit.getFullMessage());
+			ObjectId head2Id = db.resolve(Constants.HEAD + "^1");
+			RevCommit head1Commit = walk.parseCommit(head2Id);
+			assertEquals("changed", head1Commit.getFullMessage());
+		}
 	}
 
 	@Test
@@ -2855,16 +2875,17 @@
 					}
 				}).call();
 
-		RevWalk walk = new RevWalk(db);
-		ObjectId headId = db.resolve(Constants.HEAD);
-		RevCommit headCommit = walk.parseCommit(headId);
-		assertEquals("update file2 on master\nnew line",
-				headCommit.getFullMessage());
+		try (RevWalk walk = new RevWalk(db)) {
+			ObjectId headId = db.resolve(Constants.HEAD);
+			RevCommit headCommit = walk.parseCommit(headId);
+			assertEquals("update file2 on master\nnew line",
+					headCommit.getFullMessage());
 
-		ObjectId head1Id = db.resolve(Constants.HEAD + "^1");
-		RevCommit head1Commit = walk.parseCommit(head1Id);
-		assertEquals("Add file2\nnew line",
-				head1Commit.getFullMessage());
+			ObjectId head1Id = db.resolve(Constants.HEAD + "^1");
+			RevCommit head1Commit = walk.parseCommit(head1Id);
+			assertEquals("Add file2\nnew line",
+					head1Commit.getFullMessage());
+		}
 	}
 
 	@Test
@@ -2903,11 +2924,12 @@
 					}
 				}).call();
 
-		RevWalk walk = new RevWalk(db);
-		ObjectId headId = db.resolve(Constants.HEAD);
-		RevCommit headCommit = walk.parseCommit(headId);
-		assertEquals("Add file2",
-				headCommit.getFullMessage());
+		try (RevWalk walk = new RevWalk(db)) {
+			ObjectId headId = db.resolve(Constants.HEAD);
+			RevCommit headCommit = walk.parseCommit(headId);
+			assertEquals("Add file2",
+					headCommit.getFullMessage());
+		}
 	}
 
 	@Test(expected = InvalidRebaseStepException.class)
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RemoteAddCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RemoteAddCommandTest.java
new file mode 100644
index 0000000..ed09446
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RemoteAddCommandTest.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2015, Kaloyan Raev <kaloyan.r@zend.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.api;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.transport.RemoteConfig;
+import org.eclipse.jgit.transport.URIish;
+import org.junit.Test;
+
+public class RemoteAddCommandTest extends AbstractRemoteCommandTest {
+
+	@Test
+	public void testAdd() throws Exception {
+		// create another repository
+		Repository remoteRepository = createWorkRepository();
+		URIish uri = new URIish(
+				remoteRepository.getDirectory().toURI().toURL());
+
+		// execute the command to add a new remote
+		RemoteAddCommand cmd = Git.wrap(db).remoteAdd();
+		cmd.setName(REMOTE_NAME);
+		cmd.setUri(uri);
+		RemoteConfig remote = cmd.call();
+
+		// assert that the added remote represents the remote repository
+		assertEquals(REMOTE_NAME, remote.getName());
+		assertArrayEquals(new URIish[] { uri }, remote.getURIs().toArray());
+		assertEquals(1, remote.getFetchRefSpecs().size());
+		assertEquals(
+				String.format("+refs/heads/*:refs/remotes/%s/*", REMOTE_NAME),
+				remote.getFetchRefSpecs().get(0).toString());
+
+		// assert that the added remote is available in the git configuration
+		assertRemoteConfigEquals(remote,
+				new RemoteConfig(db.getConfig(), REMOTE_NAME));
+	}
+
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RemoteDeleteCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RemoteDeleteCommandTest.java
new file mode 100644
index 0000000..7055daf
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RemoteDeleteCommandTest.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2015, Kaloyan Raev <kaloyan.r@zend.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.api;
+
+import static org.junit.Assert.assertTrue;
+
+import org.eclipse.jgit.transport.RemoteConfig;
+import org.junit.Test;
+
+public class RemoteDeleteCommandTest extends AbstractRemoteCommandTest {
+
+	@Test
+	public void testDelete() throws Exception {
+		// setup an initial remote
+		RemoteConfig remoteConfig = setupRemote();
+
+		// execute the command to remove the remote
+		RemoteRemoveCommand cmd = Git.wrap(db).remoteRemove();
+		cmd.setName(REMOTE_NAME);
+		RemoteConfig remote = cmd.call();
+
+		// assert that the removed remote is the initial remote
+		assertRemoteConfigEquals(remoteConfig, remote);
+		// assert that there are no remotes left
+		assertTrue(RemoteConfig.getAllRemoteConfigs(db.getConfig()).isEmpty());
+	}
+
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/SymlinkTreeEntry.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RemoteListCommandTest.java
similarity index 60%
copy from org.eclipse.jgit/src/org/eclipse/jgit/lib/SymlinkTreeEntry.java
copy to org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RemoteListCommandTest.java
index c7e41bc..cf522ff 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/SymlinkTreeEntry.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RemoteListCommandTest.java
@@ -1,6 +1,5 @@
 /*
- * Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com>
- * Copyright (C) 2006-2007, Shawn O. Pearce <spearce@spearce.org>
+ * Copyright (C) 2015, Kaloyan Raev <kaloyan.r@zend.com>
  * and other copyright owners as documented in the project's IP log.
  *
  * This program and the accompanying materials are made available
@@ -41,45 +40,29 @@
  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
+package org.eclipse.jgit.api;
 
-package org.eclipse.jgit.lib;
+import static org.junit.Assert.assertEquals;
 
-/**
- * A tree entry representing a symbolic link.
- *
- * Note. Java cannot really handle these as file system objects.
- *
- * @deprecated To look up information about a single path, use
- * {@link org.eclipse.jgit.treewalk.TreeWalk#forPath(Repository, String, org.eclipse.jgit.revwalk.RevTree)}.
- * To lookup information about multiple paths at once, use a
- * {@link org.eclipse.jgit.treewalk.TreeWalk} and obtain the current entry's
- * information from its getter methods.
- */
-@Deprecated
-public class SymlinkTreeEntry extends TreeEntry {
+import java.util.List;
 
-	/**
-	 * Construct a {@link SymlinkTreeEntry} with the specified name and SHA-1 in
-	 * the specified parent
-	 *
-	 * @param parent
-	 * @param id
-	 * @param nameUTF8
-	 */
-	public SymlinkTreeEntry(final Tree parent, final ObjectId id,
-			final byte[] nameUTF8) {
-		super(parent, id, nameUTF8);
+import org.eclipse.jgit.transport.RemoteConfig;
+import org.junit.Test;
+
+public class RemoteListCommandTest extends AbstractRemoteCommandTest {
+
+	@Test
+	public void testList() throws Exception {
+		// setup an initial remote
+		RemoteConfig remoteConfig = setupRemote();
+
+		// execute the command to list the remotes
+		List<RemoteConfig> remotes = Git.wrap(db).remoteList().call();
+
+		// assert that there is only one remote
+		assertEquals(1, remotes.size());
+		// assert that the available remote is the initial remote
+		assertRemoteConfigEquals(remoteConfig, remotes.get(0));
 	}
 
-	public FileMode getMode() {
-		return FileMode.SYMLINK;
-	}
-
-	public String toString() {
-		final StringBuilder r = new StringBuilder();
-		r.append(ObjectId.toString(getId()));
-		r.append(" S "); //$NON-NLS-1$
-		r.append(getFullName());
-		return r.toString();
-	}
 }
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RemoteSetUrlCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RemoteSetUrlCommandTest.java
new file mode 100644
index 0000000..6969c3d
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RemoteSetUrlCommandTest.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2015, Kaloyan Raev <kaloyan.r@zend.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.api;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+
+import org.eclipse.jgit.transport.RemoteConfig;
+import org.eclipse.jgit.transport.URIish;
+import org.junit.Test;
+
+public class RemoteSetUrlCommandTest extends AbstractRemoteCommandTest {
+
+	@Test
+	public void testSetUrl() throws Exception {
+		// setup an initial remote
+		setupRemote();
+
+		// execute the command to change the fetch url
+		RemoteSetUrlCommand cmd = Git.wrap(db).remoteSetUrl();
+		cmd.setName(REMOTE_NAME);
+		URIish newUri = new URIish("git://test.com/test");
+		cmd.setUri(newUri);
+		RemoteConfig remote = cmd.call();
+
+		// assert that the changed remote has the new fetch url
+		assertEquals(REMOTE_NAME, remote.getName());
+		assertArrayEquals(new URIish[] { newUri }, remote.getURIs().toArray());
+
+		// assert that the changed remote is available in the git configuration
+		assertRemoteConfigEquals(remote,
+				new RemoteConfig(db.getConfig(), REMOTE_NAME));
+	}
+
+	@Test
+	public void testSetPushUrl() throws Exception {
+		// setup an initial remote
+		RemoteConfig remoteConfig = setupRemote();
+
+		// execute the command to change the push url
+		RemoteSetUrlCommand cmd = Git.wrap(db).remoteSetUrl();
+		cmd.setName(REMOTE_NAME);
+		URIish newUri = new URIish("git://test.com/test");
+		cmd.setUri(newUri);
+		cmd.setPush(true);
+		RemoteConfig remote = cmd.call();
+
+		// assert that the changed remote has the old fetch url and the new push
+		// url
+		assertEquals(REMOTE_NAME, remote.getName());
+		assertEquals(remoteConfig.getURIs(), remote.getURIs());
+		assertArrayEquals(new URIish[] { newUri },
+				remote.getPushURIs().toArray());
+
+		// assert that the changed remote is available in the git configuration
+		assertRemoteConfigEquals(remote,
+				new RemoteConfig(db.getConfig(), REMOTE_NAME));
+	}
+
+}
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 9997c8c..40d8458 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
@@ -65,6 +65,7 @@
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.FileMode;
 import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Ref;
 import org.eclipse.jgit.revwalk.RevCommit;
 import org.eclipse.jgit.revwalk.RevWalk;
 import org.eclipse.jgit.treewalk.TreeWalk;
@@ -139,8 +140,8 @@
 			AmbiguousObjectException, IOException, GitAPIException {
 		setupRepository();
 		ObjectId prevHead = db.resolve(Constants.HEAD);
-		git.reset().setMode(ResetType.HARD).setRef(initialCommit.getName())
-				.call();
+		assertSameAsHead(git.reset().setMode(ResetType.HARD)
+				.setRef(initialCommit.getName()).call());
 		// check if HEAD points to initial commit now
 		ObjectId head = db.resolve(Constants.HEAD);
 		assertEquals(initialCommit, head);
@@ -176,8 +177,8 @@
 			AmbiguousObjectException, IOException, GitAPIException {
 		setupRepository();
 		ObjectId prevHead = db.resolve(Constants.HEAD);
-		git.reset().setMode(ResetType.SOFT).setRef(initialCommit.getName())
-				.call();
+		assertSameAsHead(git.reset().setMode(ResetType.SOFT)
+				.setRef(initialCommit.getName()).call());
 		// check if HEAD points to initial commit now
 		ObjectId head = db.resolve(Constants.HEAD);
 		assertEquals(initialCommit, head);
@@ -197,8 +198,8 @@
 			AmbiguousObjectException, IOException, GitAPIException {
 		setupRepository();
 		ObjectId prevHead = db.resolve(Constants.HEAD);
-		git.reset().setMode(ResetType.MIXED).setRef(initialCommit.getName())
-				.call();
+		assertSameAsHead(git.reset().setMode(ResetType.MIXED)
+				.setRef(initialCommit.getName()).call());
 		// check if HEAD points to initial commit now
 		ObjectId head = db.resolve(Constants.HEAD);
 		assertEquals(initialCommit, head);
@@ -241,7 +242,8 @@
 		assertTrue(bEntry.getLength() > 0);
 		assertTrue(bEntry.getLastModified() > 0);
 
-		git.reset().setMode(ResetType.MIXED).setRef(commit2.getName()).call();
+		assertSameAsHead(git.reset().setMode(ResetType.MIXED)
+				.setRef(commit2.getName()).call());
 
 		cache = db.readDirCache();
 
@@ -280,7 +282,7 @@
 				+ "[a.txt, mode:100644, stage:3]",
 				indexState(0));
 
-		git.reset().setMode(ResetType.MIXED).call();
+		assertSameAsHead(git.reset().setMode(ResetType.MIXED).call());
 
 		assertEquals("[a.txt, mode:100644]" + "[b.txt, mode:100644]",
 				indexState(0));
@@ -298,8 +300,8 @@
 
 		// 'a.txt' has already been modified in setupRepository
 		// 'notAddedToIndex.txt' has been added to repository
-		git.reset().addPath(indexFile.getName())
-				.addPath(untrackedFile.getName()).call();
+		assertSameAsHead(git.reset().addPath(indexFile.getName())
+				.addPath(untrackedFile.getName()).call());
 
 		DirCacheEntry postReset = DirCache.read(db.getIndexFile(), db.getFS())
 				.getEntry(indexFile.getName());
@@ -329,7 +331,7 @@
 		git.add().addFilepattern(untrackedFile.getName()).call();
 
 		// 'dir/b.txt' has already been modified in setupRepository
-		git.reset().addPath("dir").call();
+		assertSameAsHead(git.reset().addPath("dir").call());
 
 		DirCacheEntry postReset = DirCache.read(db.getIndexFile(), db.getFS())
 				.getEntry("dir/b.txt");
@@ -358,9 +360,9 @@
 		// 'a.txt' has already been modified in setupRepository
 		// 'notAddedToIndex.txt' has been added to repository
 		// reset to the inital commit
-		git.reset().setRef(initialCommit.getName())
-				.addPath(indexFile.getName())
-				.addPath(untrackedFile.getName()).call();
+		assertSameAsHead(git.reset().setRef(initialCommit.getName())
+				.addPath(indexFile.getName()).addPath(untrackedFile.getName())
+				.call());
 
 		// check that HEAD hasn't moved
 		ObjectId head = db.resolve(Constants.HEAD);
@@ -397,7 +399,7 @@
 				+ "[b.txt, mode:100644]",
 				indexState(0));
 
-		git.reset().addPath(file).call();
+		assertSameAsHead(git.reset().addPath(file).call());
 
 		assertEquals("[a.txt, mode:100644]" + "[b.txt, mode:100644]",
 				indexState(0));
@@ -409,7 +411,7 @@
 		writeTrashFile("a.txt", "content");
 		git.add().addFilepattern("a.txt").call();
 		// Should assume an empty tree, like in C Git 1.8.2
-		git.reset().addPath("a.txt").call();
+		assertSameAsHead(git.reset().addPath("a.txt").call());
 
 		DirCache cache = db.readDirCache();
 		DirCacheEntry aEntry = cache.getEntry("a.txt");
@@ -421,7 +423,8 @@
 		git = new Git(db);
 		writeTrashFile("a.txt", "content");
 		git.add().addFilepattern("a.txt").call();
-		git.reset().setRef("doesnotexist").addPath("a.txt").call();
+		assertSameAsHead(
+				git.reset().setRef("doesnotexist").addPath("a.txt").call());
 	}
 
 	@Test
@@ -431,7 +434,7 @@
 		git.add().addFilepattern("a.txt").call();
 		writeTrashFile("a.txt", "modified");
 		// should use default mode MIXED
-		git.reset().call();
+		assertSameAsHead(git.reset().call());
 
 		DirCache cache = db.readDirCache();
 		DirCacheEntry aEntry = cache.getEntry("a.txt");
@@ -452,7 +455,7 @@
 
 		git.add().addFilepattern(untrackedFile.getName()).call();
 
-		git.reset().setRef(tagName).setMode(HARD).call();
+		assertSameAsHead(git.reset().setRef(tagName).setMode(HARD).call());
 
 		ObjectId head = db.resolve(Constants.HEAD);
 		assertEquals(secondCommit, head);
@@ -460,31 +463,34 @@
 
 	@Test
 	public void testHardResetAfterSquashMerge() throws Exception {
-		Git g = new Git(db);
+		git = new Git(db);
 
 		writeTrashFile("file1", "file1");
-		g.add().addFilepattern("file1").call();
-		RevCommit first = g.commit().setMessage("initial commit").call();
+		git.add().addFilepattern("file1").call();
+		RevCommit first = git.commit().setMessage("initial commit").call();
 
 		assertTrue(new File(db.getWorkTree(), "file1").exists());
 		createBranch(first, "refs/heads/branch1");
 		checkoutBranch("refs/heads/branch1");
 
 		writeTrashFile("file2", "file2");
-		g.add().addFilepattern("file2").call();
-		g.commit().setMessage("second commit").call();
+		git.add().addFilepattern("file2").call();
+		git.commit().setMessage("second commit").call();
 		assertTrue(new File(db.getWorkTree(), "file2").exists());
 
 		checkoutBranch("refs/heads/master");
 
-		MergeResult result = g.merge().include(db.getRef("branch1"))
-				.setSquash(true).call();
+		MergeResult result = git.merge()
+				.include(db.exactRef("refs/heads/branch1"))
+				.setSquash(true)
+				.call();
 
 		assertEquals(MergeResult.MergeStatus.FAST_FORWARD_SQUASHED,
 				result.getMergeStatus());
 		assertNotNull(db.readSquashCommitMsg());
 
-		g.reset().setMode(ResetType.HARD).setRef(first.getName()).call();
+		assertSameAsHead(git.reset().setMode(ResetType.HARD)
+				.setRef(first.getName()).call());
 
 		assertNull(db.readSquashCommitMsg());
 	}
@@ -495,7 +501,7 @@
 		File fileA = writeTrashFile("a.txt", "content");
 		git.add().addFilepattern("a.txt").call();
 		// Should assume an empty tree, like in C Git 1.8.2
-		git.reset().setMode(ResetType.HARD).call();
+		assertSameAsHead(git.reset().setMode(ResetType.HARD).call());
 
 		DirCache cache = db.readDirCache();
 		DirCacheEntry aEntry = cache.getEntry("a.txt");
@@ -556,4 +562,14 @@
 		return dc.getEntry(path) != null;
 	}
 
+	/**
+	 * Asserts that a certain ref is similar to repos HEAD.
+	 * @param ref
+	 * @throws IOException
+	 */
+	private void assertSameAsHead(Ref ref) throws IOException {
+		Ref headRef = db.getRef(Constants.HEAD);
+		assertEquals(headRef.getName(), ref.getName());
+		assertEquals(headRef.getObjectId(), ref.getObjectId());
+	}
 }
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RevertCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RevertCommandTest.java
index 060168c..ea63104 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RevertCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RevertCommandTest.java
@@ -74,303 +74,309 @@
 	@Test
 	public void testRevert() throws IOException, JGitInternalException,
 			GitAPIException {
-		Git git = new Git(db);
+		try (Git git = new Git(db)) {
+			writeTrashFile("a", "first line\nsec. line\nthird line\n");
+			git.add().addFilepattern("a").call();
+			git.commit().setMessage("create a").call();
 
-		writeTrashFile("a", "first line\nsec. line\nthird line\n");
-		git.add().addFilepattern("a").call();
-		git.commit().setMessage("create a").call();
+			writeTrashFile("b", "content\n");
+			git.add().addFilepattern("b").call();
+			git.commit().setMessage("create b").call();
 
-		writeTrashFile("b", "content\n");
-		git.add().addFilepattern("b").call();
-		git.commit().setMessage("create b").call();
+			writeTrashFile("a", "first line\nsec. line\nthird line\nfourth line\n");
+			git.add().addFilepattern("a").call();
+			git.commit().setMessage("enlarged a").call();
 
-		writeTrashFile("a", "first line\nsec. line\nthird line\nfourth line\n");
-		git.add().addFilepattern("a").call();
-		git.commit().setMessage("enlarged a").call();
+			writeTrashFile("a",
+					"first line\nsecond line\nthird line\nfourth line\n");
+			git.add().addFilepattern("a").call();
+			RevCommit fixingA = git.commit().setMessage("fixed a").call();
 
-		writeTrashFile("a",
-				"first line\nsecond line\nthird line\nfourth line\n");
-		git.add().addFilepattern("a").call();
-		RevCommit fixingA = git.commit().setMessage("fixed a").call();
+			writeTrashFile("b", "first line\n");
+			git.add().addFilepattern("b").call();
+			git.commit().setMessage("fixed b").call();
 
-		writeTrashFile("b", "first line\n");
-		git.add().addFilepattern("b").call();
-		git.commit().setMessage("fixed b").call();
+			git.revert().include(fixingA).call();
 
-		git.revert().include(fixingA).call();
+			assertEquals(RepositoryState.SAFE, db.getRepositoryState());
 
-		assertEquals(RepositoryState.SAFE, db.getRepositoryState());
+			assertTrue(new File(db.getWorkTree(), "b").exists());
+			checkFile(new File(db.getWorkTree(), "a"),
+					"first line\nsec. line\nthird line\nfourth line\n");
+			Iterator<RevCommit> history = git.log().call().iterator();
+			RevCommit revertCommit = history.next();
+			String expectedMessage = "Revert \"fixed a\"\n\n"
+					+ "This reverts commit " + fixingA.getId().getName() + ".\n";
+			assertEquals(expectedMessage, revertCommit.getFullMessage());
+			assertEquals("fixed b", history.next().getFullMessage());
+			assertEquals("fixed a", history.next().getFullMessage());
+			assertEquals("enlarged a", history.next().getFullMessage());
+			assertEquals("create b", history.next().getFullMessage());
+			assertEquals("create a", history.next().getFullMessage());
+			assertFalse(history.hasNext());
 
-		assertTrue(new File(db.getWorkTree(), "b").exists());
-		checkFile(new File(db.getWorkTree(), "a"),
-				"first line\nsec. line\nthird line\nfourth line\n");
-		Iterator<RevCommit> history = git.log().call().iterator();
-		RevCommit revertCommit = history.next();
-		String expectedMessage = "Revert \"fixed a\"\n\n"
-				+ "This reverts commit " + fixingA.getId().getName() + ".\n";
-		assertEquals(expectedMessage, revertCommit.getFullMessage());
-		assertEquals("fixed b", history.next().getFullMessage());
-		assertEquals("fixed a", history.next().getFullMessage());
-		assertEquals("enlarged a", history.next().getFullMessage());
-		assertEquals("create b", history.next().getFullMessage());
-		assertEquals("create a", history.next().getFullMessage());
-		assertFalse(history.hasNext());
-
-		ReflogReader reader = db.getReflogReader(Constants.HEAD);
-		assertTrue(reader.getLastEntry().getComment()
-				.startsWith("revert: Revert \""));
-		reader = db.getReflogReader(db.getBranch());
-		assertTrue(reader.getLastEntry().getComment()
-				.startsWith("revert: Revert \""));
+			ReflogReader reader = db.getReflogReader(Constants.HEAD);
+			assertTrue(reader.getLastEntry().getComment()
+					.startsWith("revert: Revert \""));
+			reader = db.getReflogReader(db.getBranch());
+			assertTrue(reader.getLastEntry().getComment()
+					.startsWith("revert: Revert \""));
+		}
 
 	}
 
 	@Test
 	public void testRevertMultiple() throws IOException, JGitInternalException,
 			GitAPIException {
-		Git git = new Git(db);
+		try (Git git = new Git(db)) {
+			writeTrashFile("a", "first\n");
+			git.add().addFilepattern("a").call();
+			git.commit().setMessage("add first").call();
 
-		writeTrashFile("a", "first\n");
-		git.add().addFilepattern("a").call();
-		git.commit().setMessage("add first").call();
+			writeTrashFile("a", "first\nsecond\n");
+			git.add().addFilepattern("a").call();
+			RevCommit secondCommit = git.commit().setMessage("add second").call();
 
-		writeTrashFile("a", "first\nsecond\n");
-		git.add().addFilepattern("a").call();
-		RevCommit secondCommit = git.commit().setMessage("add second").call();
+			writeTrashFile("a", "first\nsecond\nthird\n");
+			git.add().addFilepattern("a").call();
+			RevCommit thirdCommit = git.commit().setMessage("add third").call();
 
-		writeTrashFile("a", "first\nsecond\nthird\n");
-		git.add().addFilepattern("a").call();
-		RevCommit thirdCommit = git.commit().setMessage("add third").call();
+			git.revert().include(thirdCommit).include(secondCommit).call();
 
-		git.revert().include(thirdCommit).include(secondCommit).call();
+			assertEquals(RepositoryState.SAFE, db.getRepositoryState());
 
-		assertEquals(RepositoryState.SAFE, db.getRepositoryState());
+			checkFile(new File(db.getWorkTree(), "a"), "first\n");
+			Iterator<RevCommit> history = git.log().call().iterator();
+			RevCommit revertCommit = history.next();
+			String expectedMessage = "Revert \"add second\"\n\n"
+					+ "This reverts commit "
+					+ secondCommit.getId().getName() + ".\n";
+			assertEquals(expectedMessage, revertCommit.getFullMessage());
+			revertCommit = history.next();
+			expectedMessage = "Revert \"add third\"\n\n"
+					+ "This reverts commit " + thirdCommit.getId().getName()
+					+ ".\n";
+			assertEquals(expectedMessage, revertCommit.getFullMessage());
+			assertEquals("add third", history.next().getFullMessage());
+			assertEquals("add second", history.next().getFullMessage());
+			assertEquals("add first", history.next().getFullMessage());
+			assertFalse(history.hasNext());
 
-		checkFile(new File(db.getWorkTree(), "a"), "first\n");
-		Iterator<RevCommit> history = git.log().call().iterator();
-		RevCommit revertCommit = history.next();
-		String expectedMessage = "Revert \"add second\"\n\n"
-				+ "This reverts commit "
-				+ secondCommit.getId().getName() + ".\n";
-		assertEquals(expectedMessage, revertCommit.getFullMessage());
-		revertCommit = history.next();
-		expectedMessage = "Revert \"add third\"\n\n"
-				+ "This reverts commit " + thirdCommit.getId().getName()
-				+ ".\n";
-		assertEquals(expectedMessage, revertCommit.getFullMessage());
-		assertEquals("add third", history.next().getFullMessage());
-		assertEquals("add second", history.next().getFullMessage());
-		assertEquals("add first", history.next().getFullMessage());
-		assertFalse(history.hasNext());
-
-		ReflogReader reader = db.getReflogReader(Constants.HEAD);
-		assertTrue(reader.getLastEntry().getComment()
-				.startsWith("revert: Revert \""));
-		reader = db.getReflogReader(db.getBranch());
-		assertTrue(reader.getLastEntry().getComment()
-				.startsWith("revert: Revert \""));
+			ReflogReader reader = db.getReflogReader(Constants.HEAD);
+			assertTrue(reader.getLastEntry().getComment()
+					.startsWith("revert: Revert \""));
+			reader = db.getReflogReader(db.getBranch());
+			assertTrue(reader.getLastEntry().getComment()
+					.startsWith("revert: Revert \""));
+		}
 
 	}
 
 	@Test
 	public void testRevertMultipleWithFail() throws IOException,
 			JGitInternalException, GitAPIException {
-		Git git = new Git(db);
+		try (Git git = new Git(db)) {
+			writeTrashFile("a", "first\n");
+			git.add().addFilepattern("a").call();
+			git.commit().setMessage("add first").call();
 
-		writeTrashFile("a", "first\n");
-		git.add().addFilepattern("a").call();
-		git.commit().setMessage("add first").call();
+			writeTrashFile("a", "first\nsecond\n");
+			git.add().addFilepattern("a").call();
+			RevCommit secondCommit = git.commit().setMessage("add second").call();
 
-		writeTrashFile("a", "first\nsecond\n");
-		git.add().addFilepattern("a").call();
-		RevCommit secondCommit = git.commit().setMessage("add second").call();
+			writeTrashFile("a", "first\nsecond\nthird\n");
+			git.add().addFilepattern("a").call();
+			git.commit().setMessage("add third").call();
 
-		writeTrashFile("a", "first\nsecond\nthird\n");
-		git.add().addFilepattern("a").call();
-		git.commit().setMessage("add third").call();
+			writeTrashFile("a", "first\nsecond\nthird\nfourth\n");
+			git.add().addFilepattern("a").call();
+			RevCommit fourthCommit = git.commit().setMessage("add fourth").call();
 
-		writeTrashFile("a", "first\nsecond\nthird\nfourth\n");
-		git.add().addFilepattern("a").call();
-		RevCommit fourthCommit = git.commit().setMessage("add fourth").call();
+			git.revert().include(fourthCommit).include(secondCommit).call();
 
-		git.revert().include(fourthCommit).include(secondCommit).call();
+			// not SAFE because it failed
+			assertEquals(RepositoryState.REVERTING, db.getRepositoryState());
 
-		// not SAFE because it failed
-		assertEquals(RepositoryState.REVERTING, db.getRepositoryState());
+			checkFile(new File(db.getWorkTree(), "a"), "first\n"
+					+ "<<<<<<< master\n" + "second\n" + "third\n" + "=======\n"
+					+ ">>>>>>> " + secondCommit.getId().abbreviate(7).name()
+					+ " add second\n");
+			Iterator<RevCommit> history = git.log().call().iterator();
+			RevCommit revertCommit = history.next();
+			String expectedMessage = "Revert \"add fourth\"\n\n"
+					+ "This reverts commit " + fourthCommit.getId().getName()
+					+ ".\n";
+			assertEquals(expectedMessage, revertCommit.getFullMessage());
+			assertEquals("add fourth", history.next().getFullMessage());
+			assertEquals("add third", history.next().getFullMessage());
+			assertEquals("add second", history.next().getFullMessage());
+			assertEquals("add first", history.next().getFullMessage());
+			assertFalse(history.hasNext());
 
-		checkFile(new File(db.getWorkTree(), "a"), "first\n"
-				+ "<<<<<<< master\n" + "second\n" + "third\n" + "=======\n"
-				+ ">>>>>>> " + secondCommit.getId().abbreviate(7).name()
-				+ " add second\n");
-		Iterator<RevCommit> history = git.log().call().iterator();
-		RevCommit revertCommit = history.next();
-		String expectedMessage = "Revert \"add fourth\"\n\n"
-				+ "This reverts commit " + fourthCommit.getId().getName()
-				+ ".\n";
-		assertEquals(expectedMessage, revertCommit.getFullMessage());
-		assertEquals("add fourth", history.next().getFullMessage());
-		assertEquals("add third", history.next().getFullMessage());
-		assertEquals("add second", history.next().getFullMessage());
-		assertEquals("add first", history.next().getFullMessage());
-		assertFalse(history.hasNext());
-
-		ReflogReader reader = db.getReflogReader(Constants.HEAD);
-		assertTrue(reader.getLastEntry().getComment()
-				.startsWith("revert: Revert \""));
-		reader = db.getReflogReader(db.getBranch());
-		assertTrue(reader.getLastEntry().getComment()
-				.startsWith("revert: Revert \""));
+			ReflogReader reader = db.getReflogReader(Constants.HEAD);
+			assertTrue(reader.getLastEntry().getComment()
+					.startsWith("revert: Revert \""));
+			reader = db.getReflogReader(db.getBranch());
+			assertTrue(reader.getLastEntry().getComment()
+					.startsWith("revert: Revert \""));
+		}
 
 	}
 
 	@Test
 	public void testRevertDirtyIndex() throws Exception {
-		Git git = new Git(db);
-		RevCommit sideCommit = prepareRevert(git);
+		try (Git git = new Git(db)) {
+			RevCommit sideCommit = prepareRevert(git);
 
-		// modify and add file a
-		writeTrashFile("a", "a(modified)");
-		git.add().addFilepattern("a").call();
-		// do not commit
+			// modify and add file a
+			writeTrashFile("a", "a(modified)");
+			git.add().addFilepattern("a").call();
+			// do not commit
 
-		doRevertAndCheckResult(git, sideCommit,
-				MergeFailureReason.DIRTY_INDEX);
+			doRevertAndCheckResult(git, sideCommit,
+					MergeFailureReason.DIRTY_INDEX);
+		}
 }
 
 	@Test
 	public void testRevertDirtyWorktree() throws Exception {
-		Git git = new Git(db);
-		RevCommit sideCommit = prepareRevert(git);
+		try (Git git = new Git(db)) {
+			RevCommit sideCommit = prepareRevert(git);
 
-		// modify file a
-		writeTrashFile("a", "a(modified)");
-		// do not add and commit
+			// modify file a
+			writeTrashFile("a", "a(modified)");
+			// do not add and commit
 
-		doRevertAndCheckResult(git, sideCommit,
-				MergeFailureReason.DIRTY_WORKTREE);
+			doRevertAndCheckResult(git, sideCommit,
+					MergeFailureReason.DIRTY_WORKTREE);
+		}
 	}
 
 	@Test
 	public void testRevertConflictResolution() throws Exception {
-		Git git = new Git(db);
-		RevCommit sideCommit = prepareRevert(git);
+		try (Git git = new Git(db)) {
+			RevCommit sideCommit = prepareRevert(git);
 
-		RevertCommand revert = git.revert();
-		RevCommit newHead = revert.include(sideCommit.getId()).call();
-		assertNull(newHead);
-		MergeResult result = revert.getFailingResult();
-		assertEquals(MergeStatus.CONFLICTING, result.getMergeStatus());
-		assertTrue(new File(db.getDirectory(), Constants.MERGE_MSG).exists());
-		assertEquals("Revert \"" + sideCommit.getShortMessage()
-				+ "\"\n\nThis reverts commit " + sideCommit.getId().getName()
-				+ ".\n\nConflicts:\n\ta\n",
-				db.readMergeCommitMsg());
-		assertTrue(new File(db.getDirectory(), Constants.REVERT_HEAD)
-				.exists());
-		assertEquals(sideCommit.getId(), db.readRevertHead());
-		assertEquals(RepositoryState.REVERTING, db.getRepositoryState());
+			RevertCommand revert = git.revert();
+			RevCommit newHead = revert.include(sideCommit.getId()).call();
+			assertNull(newHead);
+			MergeResult result = revert.getFailingResult();
+			assertEquals(MergeStatus.CONFLICTING, result.getMergeStatus());
+			assertTrue(new File(db.getDirectory(), Constants.MERGE_MSG).exists());
+			assertEquals("Revert \"" + sideCommit.getShortMessage()
+					+ "\"\n\nThis reverts commit " + sideCommit.getId().getName()
+					+ ".\n\nConflicts:\n\ta\n",
+					db.readMergeCommitMsg());
+			assertTrue(new File(db.getDirectory(), Constants.REVERT_HEAD)
+					.exists());
+			assertEquals(sideCommit.getId(), db.readRevertHead());
+			assertEquals(RepositoryState.REVERTING, db.getRepositoryState());
 
-		// Resolve
-		writeTrashFile("a", "a");
-		git.add().addFilepattern("a").call();
+			// Resolve
+			writeTrashFile("a", "a");
+			git.add().addFilepattern("a").call();
 
-		assertEquals(RepositoryState.REVERTING_RESOLVED,
-				db.getRepositoryState());
+			assertEquals(RepositoryState.REVERTING_RESOLVED,
+					db.getRepositoryState());
 
-		git.commit().setOnly("a").setMessage("resolve").call();
+			git.commit().setOnly("a").setMessage("resolve").call();
 
-		assertEquals(RepositoryState.SAFE, db.getRepositoryState());
+			assertEquals(RepositoryState.SAFE, db.getRepositoryState());
+		}
 	}
 
 	@Test
 	public void testRevertkConflictReset() throws Exception {
-		Git git = new Git(db);
+		try (Git git = new Git(db)) {
+			RevCommit sideCommit = prepareRevert(git);
 
-		RevCommit sideCommit = prepareRevert(git);
+			RevertCommand revert = git.revert();
+			RevCommit newHead = revert.include(sideCommit.getId()).call();
+			assertNull(newHead);
+			MergeResult result = revert.getFailingResult();
 
-		RevertCommand revert = git.revert();
-		RevCommit newHead = revert.include(sideCommit.getId()).call();
-		assertNull(newHead);
-		MergeResult result = revert.getFailingResult();
+			assertEquals(MergeStatus.CONFLICTING, result.getMergeStatus());
+			assertEquals(RepositoryState.REVERTING, db.getRepositoryState());
+			assertTrue(new File(db.getDirectory(), Constants.REVERT_HEAD)
+					.exists());
 
-		assertEquals(MergeStatus.CONFLICTING, result.getMergeStatus());
-		assertEquals(RepositoryState.REVERTING, db.getRepositoryState());
-		assertTrue(new File(db.getDirectory(), Constants.REVERT_HEAD)
-				.exists());
+			git.reset().setMode(ResetType.MIXED).setRef("HEAD").call();
 
-		git.reset().setMode(ResetType.MIXED).setRef("HEAD").call();
-
-		assertEquals(RepositoryState.SAFE, db.getRepositoryState());
-		assertFalse(new File(db.getDirectory(), Constants.REVERT_HEAD)
-				.exists());
+			assertEquals(RepositoryState.SAFE, db.getRepositoryState());
+			assertFalse(new File(db.getDirectory(), Constants.REVERT_HEAD)
+					.exists());
+		}
 	}
 
 	@Test
 	public void testRevertOverExecutableChangeOnNonExectuableFileSystem()
 			throws Exception {
-		Git git = new Git(db);
-		File file = writeTrashFile("test.txt", "a");
-		assertNotNull(git.add().addFilepattern("test.txt").call());
-		assertNotNull(git.commit().setMessage("commit1").call());
+		try (Git git = new Git(db)) {
+			File file = writeTrashFile("test.txt", "a");
+			assertNotNull(git.add().addFilepattern("test.txt").call());
+			assertNotNull(git.commit().setMessage("commit1").call());
 
-		assertNotNull(git.checkout().setCreateBranch(true).setName("a").call());
+			assertNotNull(git.checkout().setCreateBranch(true).setName("a").call());
 
-		writeTrashFile("test.txt", "b");
-		assertNotNull(git.add().addFilepattern("test.txt").call());
-		RevCommit commit2 = git.commit().setMessage("commit2").call();
-		assertNotNull(commit2);
+			writeTrashFile("test.txt", "b");
+			assertNotNull(git.add().addFilepattern("test.txt").call());
+			RevCommit commit2 = git.commit().setMessage("commit2").call();
+			assertNotNull(commit2);
 
-		assertNotNull(git.checkout().setName(Constants.MASTER).call());
+			assertNotNull(git.checkout().setName(Constants.MASTER).call());
 
-		DirCache cache = db.lockDirCache();
-		cache.getEntry("test.txt").setFileMode(FileMode.EXECUTABLE_FILE);
-		cache.write();
-		assertTrue(cache.commit());
-		cache.unlock();
+			DirCache cache = db.lockDirCache();
+			cache.getEntry("test.txt").setFileMode(FileMode.EXECUTABLE_FILE);
+			cache.write();
+			assertTrue(cache.commit());
+			cache.unlock();
 
-		assertNotNull(git.commit().setMessage("commit3").call());
+			assertNotNull(git.commit().setMessage("commit3").call());
 
-		db.getFS().setExecute(file, false);
-		git.getRepository()
-				.getConfig()
-				.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
-						ConfigConstants.CONFIG_KEY_FILEMODE, false);
+			db.getFS().setExecute(file, false);
+			git.getRepository()
+					.getConfig()
+					.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
+							ConfigConstants.CONFIG_KEY_FILEMODE, false);
 
-		RevertCommand revert = git.revert();
-		RevCommit newHead = revert.include(commit2).call();
-		assertNotNull(newHead);
+			RevertCommand revert = git.revert();
+			RevCommit newHead = revert.include(commit2).call();
+			assertNotNull(newHead);
+		}
 	}
 
 	@Test
 	public void testRevertConflictMarkers() throws Exception {
-		Git git = new Git(db);
-		RevCommit sideCommit = prepareRevert(git);
+		try (Git git = new Git(db)) {
+			RevCommit sideCommit = prepareRevert(git);
 
-		RevertCommand revert = git.revert();
-		RevCommit newHead = revert.include(sideCommit.getId())
-				.call();
-		assertNull(newHead);
-		MergeResult result = revert.getFailingResult();
-		assertEquals(MergeStatus.CONFLICTING, result.getMergeStatus());
+			RevertCommand revert = git.revert();
+			RevCommit newHead = revert.include(sideCommit.getId())
+					.call();
+			assertNull(newHead);
+			MergeResult result = revert.getFailingResult();
+			assertEquals(MergeStatus.CONFLICTING, result.getMergeStatus());
 
-		String expected = "<<<<<<< master\na(latest)\n=======\na\n>>>>>>> ca96c31 second master\n";
-		checkFile(new File(db.getWorkTree(), "a"), expected);
+			String expected = "<<<<<<< master\na(latest)\n=======\na\n>>>>>>> ca96c31 second master\n";
+			checkFile(new File(db.getWorkTree(), "a"), expected);
+		}
 	}
 
 	@Test
 	public void testRevertOurCommitName() throws Exception {
-		Git git = new Git(db);
-		RevCommit sideCommit = prepareRevert(git);
+		try (Git git = new Git(db)) {
+			RevCommit sideCommit = prepareRevert(git);
 
-		RevertCommand revert = git.revert();
-		RevCommit newHead = revert.include(sideCommit.getId())
-				.setOurCommitName("custom name").call();
-		assertNull(newHead);
-		MergeResult result = revert.getFailingResult();
-		assertEquals(MergeStatus.CONFLICTING, result.getMergeStatus());
+			RevertCommand revert = git.revert();
+			RevCommit newHead = revert.include(sideCommit.getId())
+					.setOurCommitName("custom name").call();
+			assertNull(newHead);
+			MergeResult result = revert.getFailingResult();
+			assertEquals(MergeStatus.CONFLICTING, result.getMergeStatus());
 
-		String expected = "<<<<<<< custom name\na(latest)\n=======\na\n>>>>>>> ca96c31 second master\n";
-		checkFile(new File(db.getWorkTree(), "a"), expected);
+			String expected = "<<<<<<< custom name\na(latest)\n=======\na\n>>>>>>> ca96c31 second master\n";
+			checkFile(new File(db.getWorkTree(), "a"), expected);
+		}
 	}
 
 	private RevCommit prepareRevert(final Git git) throws Exception {
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/StashCreateCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/StashCreateCommandTest.java
index c317e3b..ae8551e 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/StashCreateCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/StashCreateCommandTest.java
@@ -110,7 +110,7 @@
 			int parentCount)
 			throws IOException {
 		assertNotNull(commit);
-		Ref stashRef = db.getRef(Constants.R_STASH);
+		Ref stashRef = db.exactRef(Constants.R_STASH);
 		assertNotNull(stashRef);
 		assertEquals(commit, stashRef.getObjectId());
 		assertNotNull(commit.getAuthorIdent());
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/StashDropCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/StashDropCommandTest.java
index cfad817..859277e 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/StashDropCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/StashDropCommandTest.java
@@ -96,13 +96,13 @@
 	@Test
 	public void dropWithInvalidLogIndex() throws Exception {
 		write(committedFile, "content2");
-		Ref stashRef = git.getRepository().getRef(Constants.R_STASH);
+		Ref stashRef = git.getRepository().exactRef(Constants.R_STASH);
 		assertNull(stashRef);
 		RevCommit stashed = git.stashCreate().call();
 		assertNotNull(stashed);
-		stashRef = git.getRepository().getRef(Constants.R_STASH);
-		assertEquals(stashed, git.getRepository().getRef(Constants.R_STASH)
-				.getObjectId());
+		stashRef = git.getRepository().exactRef(Constants.R_STASH);
+		assertEquals(stashed,
+				git.getRepository().exactRef(Constants.R_STASH).getObjectId());
 		try {
 			assertNull(git.stashDrop().setStashRef(100).call());
 			fail("Exception not thrown");
@@ -115,15 +115,15 @@
 	@Test
 	public void dropSingleStashedCommit() throws Exception {
 		write(committedFile, "content2");
-		Ref stashRef = git.getRepository().getRef(Constants.R_STASH);
+		Ref stashRef = git.getRepository().exactRef(Constants.R_STASH);
 		assertNull(stashRef);
 		RevCommit stashed = git.stashCreate().call();
 		assertNotNull(stashed);
-		stashRef = git.getRepository().getRef(Constants.R_STASH);
-		assertEquals(stashed, git.getRepository().getRef(Constants.R_STASH)
-				.getObjectId());
+		stashRef = git.getRepository().exactRef(Constants.R_STASH);
+		assertEquals(stashed,
+				git.getRepository().exactRef(Constants.R_STASH).getObjectId());
 		assertNull(git.stashDrop().call());
-		stashRef = git.getRepository().getRef(Constants.R_STASH);
+		stashRef = git.getRepository().exactRef(Constants.R_STASH);
 		assertNull(stashRef);
 
 		ReflogReader reader = git.getRepository().getReflogReader(
@@ -134,25 +134,25 @@
 	@Test
 	public void dropAll() throws Exception {
 		write(committedFile, "content2");
-		Ref stashRef = git.getRepository().getRef(Constants.R_STASH);
+		Ref stashRef = git.getRepository().exactRef(Constants.R_STASH);
 		assertNull(stashRef);
 		RevCommit firstStash = git.stashCreate().call();
 		assertNotNull(firstStash);
-		stashRef = git.getRepository().getRef(Constants.R_STASH);
+		stashRef = git.getRepository().exactRef(Constants.R_STASH);
 		assertNotNull(stashRef);
-		assertEquals(firstStash, git.getRepository().getRef(Constants.R_STASH)
-				.getObjectId());
+		assertEquals(firstStash,
+				git.getRepository().exactRef(Constants.R_STASH).getObjectId());
 
 		write(committedFile, "content3");
 		RevCommit secondStash = git.stashCreate().call();
 		assertNotNull(secondStash);
-		stashRef = git.getRepository().getRef(Constants.R_STASH);
+		stashRef = git.getRepository().exactRef(Constants.R_STASH);
 		assertNotNull(stashRef);
-		assertEquals(secondStash, git.getRepository().getRef(Constants.R_STASH)
-				.getObjectId());
+		assertEquals(secondStash,
+				git.getRepository().exactRef(Constants.R_STASH).getObjectId());
 
 		assertNull(git.stashDrop().setAll(true).call());
-		assertNull(git.getRepository().getRef(Constants.R_STASH));
+		assertNull(git.getRepository().exactRef(Constants.R_STASH));
 
 		ReflogReader reader = git.getRepository().getReflogReader(
 				Constants.R_STASH);
@@ -162,25 +162,25 @@
 	@Test
 	public void dropFirstStashedCommit() throws Exception {
 		write(committedFile, "content2");
-		Ref stashRef = git.getRepository().getRef(Constants.R_STASH);
+		Ref stashRef = git.getRepository().exactRef(Constants.R_STASH);
 		assertNull(stashRef);
 		RevCommit firstStash = git.stashCreate().call();
 		assertNotNull(firstStash);
-		stashRef = git.getRepository().getRef(Constants.R_STASH);
+		stashRef = git.getRepository().exactRef(Constants.R_STASH);
 		assertNotNull(stashRef);
-		assertEquals(firstStash, git.getRepository().getRef(Constants.R_STASH)
-				.getObjectId());
+		assertEquals(firstStash,
+				git.getRepository().exactRef(Constants.R_STASH).getObjectId());
 
 		write(committedFile, "content3");
 		RevCommit secondStash = git.stashCreate().call();
 		assertNotNull(secondStash);
-		stashRef = git.getRepository().getRef(Constants.R_STASH);
+		stashRef = git.getRepository().exactRef(Constants.R_STASH);
 		assertNotNull(stashRef);
-		assertEquals(secondStash, git.getRepository().getRef(Constants.R_STASH)
-				.getObjectId());
+		assertEquals(secondStash,
+				git.getRepository().exactRef(Constants.R_STASH).getObjectId());
 
 		assertEquals(firstStash, git.stashDrop().call());
-		stashRef = git.getRepository().getRef(Constants.R_STASH);
+		stashRef = git.getRepository().exactRef(Constants.R_STASH);
 		assertNotNull(stashRef);
 		assertEquals(firstStash, stashRef.getObjectId());
 
@@ -196,33 +196,33 @@
 	@Test
 	public void dropMiddleStashCommit() throws Exception {
 		write(committedFile, "content2");
-		Ref stashRef = git.getRepository().getRef(Constants.R_STASH);
+		Ref stashRef = git.getRepository().exactRef(Constants.R_STASH);
 		assertNull(stashRef);
 		RevCommit firstStash = git.stashCreate().call();
 		assertNotNull(firstStash);
-		stashRef = git.getRepository().getRef(Constants.R_STASH);
+		stashRef = git.getRepository().exactRef(Constants.R_STASH);
 		assertNotNull(stashRef);
-		assertEquals(firstStash, git.getRepository().getRef(Constants.R_STASH)
-				.getObjectId());
+		assertEquals(firstStash,
+				git.getRepository().exactRef(Constants.R_STASH).getObjectId());
 
 		write(committedFile, "content3");
 		RevCommit secondStash = git.stashCreate().call();
 		assertNotNull(secondStash);
-		stashRef = git.getRepository().getRef(Constants.R_STASH);
+		stashRef = git.getRepository().exactRef(Constants.R_STASH);
 		assertNotNull(stashRef);
-		assertEquals(secondStash, git.getRepository().getRef(Constants.R_STASH)
-				.getObjectId());
+		assertEquals(secondStash,
+				git.getRepository().exactRef(Constants.R_STASH).getObjectId());
 
 		write(committedFile, "content4");
 		RevCommit thirdStash = git.stashCreate().call();
 		assertNotNull(thirdStash);
-		stashRef = git.getRepository().getRef(Constants.R_STASH);
+		stashRef = git.getRepository().exactRef(Constants.R_STASH);
 		assertNotNull(stashRef);
-		assertEquals(thirdStash, git.getRepository().getRef(Constants.R_STASH)
-				.getObjectId());
+		assertEquals(thirdStash,
+				git.getRepository().exactRef(Constants.R_STASH).getObjectId());
 
 		assertEquals(thirdStash, git.stashDrop().setStashRef(1).call());
-		stashRef = git.getRepository().getRef(Constants.R_STASH);
+		stashRef = git.getRepository().exactRef(Constants.R_STASH);
 		assertNotNull(stashRef);
 		assertEquals(thirdStash, stashRef.getObjectId());
 
@@ -241,46 +241,46 @@
 	@Test
 	public void dropBoundaryStashedCommits() throws Exception {
 		write(committedFile, "content2");
-		Ref stashRef = git.getRepository().getRef(Constants.R_STASH);
+		Ref stashRef = git.getRepository().exactRef(Constants.R_STASH);
 		assertNull(stashRef);
 		RevCommit firstStash = git.stashCreate().call();
 		assertNotNull(firstStash);
-		stashRef = git.getRepository().getRef(Constants.R_STASH);
+		stashRef = git.getRepository().exactRef(Constants.R_STASH);
 		assertNotNull(stashRef);
-		assertEquals(firstStash, git.getRepository().getRef(Constants.R_STASH)
-				.getObjectId());
+		assertEquals(firstStash,
+				git.getRepository().exactRef(Constants.R_STASH).getObjectId());
 
 		write(committedFile, "content3");
 		RevCommit secondStash = git.stashCreate().call();
 		assertNotNull(secondStash);
-		stashRef = git.getRepository().getRef(Constants.R_STASH);
+		stashRef = git.getRepository().exactRef(Constants.R_STASH);
 		assertNotNull(stashRef);
-		assertEquals(secondStash, git.getRepository().getRef(Constants.R_STASH)
-				.getObjectId());
+		assertEquals(secondStash,
+				git.getRepository().exactRef(Constants.R_STASH).getObjectId());
 
 		write(committedFile, "content4");
 		RevCommit thirdStash = git.stashCreate().call();
 		assertNotNull(thirdStash);
-		stashRef = git.getRepository().getRef(Constants.R_STASH);
+		stashRef = git.getRepository().exactRef(Constants.R_STASH);
 		assertNotNull(stashRef);
-		assertEquals(thirdStash, git.getRepository().getRef(Constants.R_STASH)
-				.getObjectId());
+		assertEquals(thirdStash,
+				git.getRepository().exactRef(Constants.R_STASH).getObjectId());
 
 		write(committedFile, "content5");
 		RevCommit fourthStash = git.stashCreate().call();
 		assertNotNull(fourthStash);
-		stashRef = git.getRepository().getRef(Constants.R_STASH);
+		stashRef = git.getRepository().exactRef(Constants.R_STASH);
 		assertNotNull(stashRef);
-		assertEquals(fourthStash, git.getRepository().getRef(Constants.R_STASH)
-				.getObjectId());
+		assertEquals(fourthStash,
+				git.getRepository().exactRef(Constants.R_STASH).getObjectId());
 
 		assertEquals(thirdStash, git.stashDrop().call());
-		stashRef = git.getRepository().getRef(Constants.R_STASH);
+		stashRef = git.getRepository().exactRef(Constants.R_STASH);
 		assertNotNull(stashRef);
 		assertEquals(thirdStash, stashRef.getObjectId());
 
 		assertEquals(thirdStash, git.stashDrop().setStashRef(2).call());
-		stashRef = git.getRepository().getRef(Constants.R_STASH);
+		stashRef = git.getRepository().exactRef(Constants.R_STASH);
 		assertNotNull(stashRef);
 		assertEquals(thirdStash, stashRef.getObjectId());
 
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/StatusCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/StatusCommandTest.java
index c70604e..f3ac65c 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/StatusCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/StatusCommandTest.java
@@ -61,109 +61,111 @@
 	@Test
 	public void testEmptyStatus() throws NoWorkTreeException,
 			GitAPIException {
-		Git git = new Git(db);
-
-		Status stat = git.status().call();
-		assertEquals(0, stat.getAdded().size());
-		assertEquals(0, stat.getChanged().size());
-		assertEquals(0, stat.getMissing().size());
-		assertEquals(0, stat.getModified().size());
-		assertEquals(0, stat.getRemoved().size());
-		assertEquals(0, stat.getUntracked().size());
+		try (Git git = new Git(db)) {
+			Status stat = git.status().call();
+			assertEquals(0, stat.getAdded().size());
+			assertEquals(0, stat.getChanged().size());
+			assertEquals(0, stat.getMissing().size());
+			assertEquals(0, stat.getModified().size());
+			assertEquals(0, stat.getRemoved().size());
+			assertEquals(0, stat.getUntracked().size());
+		}
 	}
 
 	@Test
 	public void testDifferentStates() throws IOException,
 			NoFilepatternException, GitAPIException {
-		Git git = new Git(db);
-		writeTrashFile("a", "content of a");
-		writeTrashFile("b", "content of b");
-		writeTrashFile("c", "content of c");
-		git.add().addFilepattern("a").addFilepattern("b").call();
-		Status stat = git.status().call();
-		assertEquals(Sets.of("a", "b"), stat.getAdded());
-		assertEquals(0, stat.getChanged().size());
-		assertEquals(0, stat.getMissing().size());
-		assertEquals(0, stat.getModified().size());
-		assertEquals(0, stat.getRemoved().size());
-		assertEquals(Sets.of("c"), stat.getUntracked());
-		git.commit().setMessage("initial").call();
+		try (Git git = new Git(db)) {
+			writeTrashFile("a", "content of a");
+			writeTrashFile("b", "content of b");
+			writeTrashFile("c", "content of c");
+			git.add().addFilepattern("a").addFilepattern("b").call();
+			Status stat = git.status().call();
+			assertEquals(Sets.of("a", "b"), stat.getAdded());
+			assertEquals(0, stat.getChanged().size());
+			assertEquals(0, stat.getMissing().size());
+			assertEquals(0, stat.getModified().size());
+			assertEquals(0, stat.getRemoved().size());
+			assertEquals(Sets.of("c"), stat.getUntracked());
+			git.commit().setMessage("initial").call();
 
-		writeTrashFile("a", "modified content of a");
-		writeTrashFile("b", "modified content of b");
-		writeTrashFile("d", "content of d");
-		git.add().addFilepattern("a").addFilepattern("d").call();
-		writeTrashFile("a", "again modified content of a");
-		stat = git.status().call();
-		assertEquals(Sets.of("d"), stat.getAdded());
-		assertEquals(Sets.of("a"), stat.getChanged());
-		assertEquals(0, stat.getMissing().size());
-		assertEquals(Sets.of("b", "a"), stat.getModified());
-		assertEquals(0, stat.getRemoved().size());
-		assertEquals(Sets.of("c"), stat.getUntracked());
-		git.add().addFilepattern(".").call();
-		git.commit().setMessage("second").call();
+			writeTrashFile("a", "modified content of a");
+			writeTrashFile("b", "modified content of b");
+			writeTrashFile("d", "content of d");
+			git.add().addFilepattern("a").addFilepattern("d").call();
+			writeTrashFile("a", "again modified content of a");
+			stat = git.status().call();
+			assertEquals(Sets.of("d"), stat.getAdded());
+			assertEquals(Sets.of("a"), stat.getChanged());
+			assertEquals(0, stat.getMissing().size());
+			assertEquals(Sets.of("b", "a"), stat.getModified());
+			assertEquals(0, stat.getRemoved().size());
+			assertEquals(Sets.of("c"), stat.getUntracked());
+			git.add().addFilepattern(".").call();
+			git.commit().setMessage("second").call();
 
-		stat = git.status().call();
-		assertEquals(0, stat.getAdded().size());
-		assertEquals(0, stat.getChanged().size());
-		assertEquals(0, stat.getMissing().size());
-		assertEquals(0, stat.getModified().size());
-		assertEquals(0, stat.getRemoved().size());
-		assertEquals(0, stat.getUntracked().size());
+			stat = git.status().call();
+			assertEquals(0, stat.getAdded().size());
+			assertEquals(0, stat.getChanged().size());
+			assertEquals(0, stat.getMissing().size());
+			assertEquals(0, stat.getModified().size());
+			assertEquals(0, stat.getRemoved().size());
+			assertEquals(0, stat.getUntracked().size());
 
-		deleteTrashFile("a");
-		assertFalse(new File(git.getRepository().getWorkTree(), "a").exists());
-		git.add().addFilepattern("a").setUpdate(true).call();
-		writeTrashFile("a", "recreated content of a");
-		stat = git.status().call();
-		assertEquals(0, stat.getAdded().size());
-		assertEquals(0, stat.getChanged().size());
-		assertEquals(0, stat.getMissing().size());
-		assertEquals(0, stat.getModified().size());
-		assertEquals(Sets.of("a"), stat.getRemoved());
-		assertEquals(Sets.of("a"), stat.getUntracked());
-		git.commit().setMessage("t").call();
+			deleteTrashFile("a");
+			assertFalse(new File(git.getRepository().getWorkTree(), "a").exists());
+			git.add().addFilepattern("a").setUpdate(true).call();
+			writeTrashFile("a", "recreated content of a");
+			stat = git.status().call();
+			assertEquals(0, stat.getAdded().size());
+			assertEquals(0, stat.getChanged().size());
+			assertEquals(0, stat.getMissing().size());
+			assertEquals(0, stat.getModified().size());
+			assertEquals(Sets.of("a"), stat.getRemoved());
+			assertEquals(Sets.of("a"), stat.getUntracked());
+			git.commit().setMessage("t").call();
 
-		writeTrashFile("sub/a", "sub-file");
-		stat = git.status().call();
-		assertEquals(1, stat.getUntrackedFolders().size());
-		assertTrue(stat.getUntrackedFolders().contains("sub"));
+			writeTrashFile("sub/a", "sub-file");
+			stat = git.status().call();
+			assertEquals(1, stat.getUntrackedFolders().size());
+			assertTrue(stat.getUntrackedFolders().contains("sub"));
+		}
 	}
 
 	@Test
 	public void testDifferentStatesWithPaths() throws IOException,
 			NoFilepatternException, GitAPIException {
-		Git git = new Git(db);
-		writeTrashFile("a", "content of a");
-		writeTrashFile("D/b", "content of b");
-		writeTrashFile("D/c", "content of c");
-		writeTrashFile("D/D/d", "content of d");
-		git.add().addFilepattern(".").call();
+		try (Git git = new Git(db)) {
+			writeTrashFile("a", "content of a");
+			writeTrashFile("D/b", "content of b");
+			writeTrashFile("D/c", "content of c");
+			writeTrashFile("D/D/d", "content of d");
+			git.add().addFilepattern(".").call();
 
-		writeTrashFile("a", "new content of a");
-		writeTrashFile("D/b", "new content of b");
-		writeTrashFile("D/D/d", "new content of d");
+			writeTrashFile("a", "new content of a");
+			writeTrashFile("D/b", "new content of b");
+			writeTrashFile("D/D/d", "new content of d");
 
 
-		// filter on an not existing path
-		Status stat = git.status().addPath("x").call();
-		assertEquals(0, stat.getModified().size());
+			// filter on an not existing path
+			Status stat = git.status().addPath("x").call();
+			assertEquals(0, stat.getModified().size());
 
-		// filter on an existing file
-		stat = git.status().addPath("a").call();
-		assertEquals(Sets.of("a"), stat.getModified());
+			// filter on an existing file
+			stat = git.status().addPath("a").call();
+			assertEquals(Sets.of("a"), stat.getModified());
 
-		// filter on an existing folder
-		stat = git.status().addPath("D").call();
-		assertEquals(Sets.of("D/b", "D/D/d"), stat.getModified());
+			// filter on an existing folder
+			stat = git.status().addPath("D").call();
+			assertEquals(Sets.of("D/b", "D/D/d"), stat.getModified());
 
-		// filter on an existing folder and file
-		stat = git.status().addPath("D/D").addPath("a").call();
-		assertEquals(Sets.of("a", "D/D/d"), stat.getModified());
+			// filter on an existing folder and file
+			stat = git.status().addPath("D/D").addPath("a").call();
+			assertEquals(Sets.of("a", "D/D/d"), stat.getModified());
 
-		// do not filter at all
-		stat = git.status().call();
-		assertEquals(Sets.of("a", "D/b", "D/D/d"), stat.getModified());
+			// do not filter at all
+			stat = git.status().call();
+			assertEquals(Sets.of("a", "D/b", "D/D/d"), stat.getModified());
+		}
 	}
 }
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/TagCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/TagCommandTest.java
index 061d29f..87098b6 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/TagCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/TagCommandTest.java
@@ -62,171 +62,185 @@
 
 	@Test
 	public void testTaggingOnHead() throws GitAPIException, IOException {
-		Git git = new Git(db);
-		RevCommit commit = git.commit().setMessage("initial commit").call();
-		Ref tagRef = git.tag().setName("tag").call();
-		assertEquals(commit.getId(), db.peel(tagRef).getPeeledObjectId());
-		RevWalk walk = new RevWalk(db);
-		assertEquals("tag", walk.parseTag(tagRef.getObjectId()).getTagName());
+		try (Git git = new Git(db);
+				RevWalk walk = new RevWalk(db)) {
+			RevCommit commit = git.commit().setMessage("initial commit").call();
+			Ref tagRef = git.tag().setName("tag").call();
+			assertEquals(commit.getId(), db.peel(tagRef).getPeeledObjectId());
+			assertEquals("tag", walk.parseTag(tagRef.getObjectId()).getTagName());
+		}
 	}
 
 	@Test
 	public void testTagging() throws GitAPIException, JGitInternalException {
-		Git git = new Git(db);
-		git.commit().setMessage("initial commit").call();
-		RevCommit commit = git.commit().setMessage("second commit").call();
-		git.commit().setMessage("third commit").call();
-		Ref tagRef = git.tag().setObjectId(commit).setName("tag").call();
-		assertEquals(commit.getId(), db.peel(tagRef).getPeeledObjectId());
+		try (Git git = new Git(db)) {
+			git.commit().setMessage("initial commit").call();
+			RevCommit commit = git.commit().setMessage("second commit").call();
+			git.commit().setMessage("third commit").call();
+			Ref tagRef = git.tag().setObjectId(commit).setName("tag").call();
+			assertEquals(commit.getId(), db.peel(tagRef).getPeeledObjectId());
+		}
 	}
 
 	@Test
 	public void testUnannotatedTagging() throws GitAPIException,
 			JGitInternalException {
-		Git git = new Git(db);
-		git.commit().setMessage("initial commit").call();
-		RevCommit commit = git.commit().setMessage("second commit").call();
-		git.commit().setMessage("third commit").call();
-		Ref tagRef = git.tag().setObjectId(commit).setName("tag")
-				.setAnnotated(false).call();
-		assertEquals(commit.getId(), tagRef.getObjectId());
+		try (Git git = new Git(db)) {
+			git.commit().setMessage("initial commit").call();
+			RevCommit commit = git.commit().setMessage("second commit").call();
+			git.commit().setMessage("third commit").call();
+			Ref tagRef = git.tag().setObjectId(commit).setName("tag")
+					.setAnnotated(false).call();
+			assertEquals(commit.getId(), tagRef.getObjectId());
+		}
 	}
 
 	@Test
 	public void testEmptyTagName() throws GitAPIException {
-		Git git = new Git(db);
-		git.commit().setMessage("initial commit").call();
-		try {
-			// forget to tag name
-			git.tag().setMessage("some message").call();
-			fail("We should have failed without a tag name");
-		} catch (InvalidTagNameException e) {
-			// should hit here
+		try (Git git = new Git(db)) {
+			git.commit().setMessage("initial commit").call();
+			try {
+				// forget to tag name
+				git.tag().setMessage("some message").call();
+				fail("We should have failed without a tag name");
+			} catch (InvalidTagNameException e) {
+				// should hit here
+			}
 		}
 	}
 
 	@Test
 	public void testInvalidTagName() throws GitAPIException {
-		Git git = new Git(db);
-		git.commit().setMessage("initial commit").call();
-		try {
-			git.tag().setName("bad~tag~name").setMessage("some message").call();
-			fail("We should have failed due to a bad tag name");
-		} catch (InvalidTagNameException e) {
-			// should hit here
+		try (Git git = new Git(db)) {
+			git.commit().setMessage("initial commit").call();
+			try {
+				git.tag().setName("bad~tag~name").setMessage("some message").call();
+				fail("We should have failed due to a bad tag name");
+			} catch (InvalidTagNameException e) {
+				// should hit here
+			}
 		}
 	}
 
 	@Test
 	public void testFailureOnSignedTags() throws GitAPIException {
-		Git git = new Git(db);
-		git.commit().setMessage("initial commit").call();
-		try {
-			git.tag().setSigned(true).setName("tag").call();
-			fail("We should have failed with an UnsupportedOperationException due to signed tag");
-		} catch (UnsupportedOperationException e) {
-			// should hit here
+		try (Git git = new Git(db)) {
+			git.commit().setMessage("initial commit").call();
+			try {
+				git.tag().setSigned(true).setName("tag").call();
+				fail("We should have failed with an UnsupportedOperationException due to signed tag");
+			} catch (UnsupportedOperationException e) {
+				// should hit here
+			}
 		}
 	}
 
 	@Test
 	public void testDelete() throws Exception {
-		Git git = new Git(db);
-		git.commit().setMessage("initial commit").call();
-		Ref tagRef = git.tag().setName("tag").call();
-		assertEquals(1, db.getTags().size());
+		try (Git git = new Git(db)) {
+			git.commit().setMessage("initial commit").call();
+			Ref tagRef = git.tag().setName("tag").call();
+			assertEquals(1, db.getTags().size());
 
-		List<String> deleted = git.tagDelete().setTags(tagRef.getName())
-				.call();
-		assertEquals(1, deleted.size());
-		assertEquals(tagRef.getName(), deleted.get(0));
-		assertEquals(0, db.getTags().size());
+			List<String> deleted = git.tagDelete().setTags(tagRef.getName())
+					.call();
+			assertEquals(1, deleted.size());
+			assertEquals(tagRef.getName(), deleted.get(0));
+			assertEquals(0, db.getTags().size());
 
-		Ref tagRef1 = git.tag().setName("tag1").call();
-		Ref tagRef2 = git.tag().setName("tag2").call();
-		assertEquals(2, db.getTags().size());
-		deleted = git.tagDelete().setTags(tagRef1.getName(), tagRef2.getName())
-				.call();
-		assertEquals(2, deleted.size());
-		assertEquals(0, db.getTags().size());
+			Ref tagRef1 = git.tag().setName("tag1").call();
+			Ref tagRef2 = git.tag().setName("tag2").call();
+			assertEquals(2, db.getTags().size());
+			deleted = git.tagDelete().setTags(tagRef1.getName(), tagRef2.getName())
+					.call();
+			assertEquals(2, deleted.size());
+			assertEquals(0, db.getTags().size());
+		}
 	}
 
 	@Test
 	public void testDeleteFullName() throws Exception {
-		Git git = new Git(db);
-		git.commit().setMessage("initial commit").call();
-		Ref tagRef = git.tag().setName("tag").call();
-		assertEquals(1, db.getTags().size());
+		try (Git git = new Git(db)) {
+			git.commit().setMessage("initial commit").call();
+			Ref tagRef = git.tag().setName("tag").call();
+			assertEquals(1, db.getTags().size());
 
-		List<String> deleted = git.tagDelete()
-				.setTags(Repository.shortenRefName(tagRef.getName())).call();
-		assertEquals(1, deleted.size());
-		assertEquals(tagRef.getName(), deleted.get(0));
-		assertEquals(0, db.getTags().size());
+			List<String> deleted = git.tagDelete()
+					.setTags(Repository.shortenRefName(tagRef.getName())).call();
+			assertEquals(1, deleted.size());
+			assertEquals(tagRef.getName(), deleted.get(0));
+			assertEquals(0, db.getTags().size());
+		}
 	}
 
 	@Test
 	public void testDeleteEmptyTagNames() throws Exception {
-		Git git = new Git(db);
-		git.commit().setMessage("initial commit").call();
+		try (Git git = new Git(db)) {
+			git.commit().setMessage("initial commit").call();
 
-		List<String> deleted = git.tagDelete().setTags().call();
-		assertEquals(0, deleted.size());
+			List<String> deleted = git.tagDelete().setTags().call();
+			assertEquals(0, deleted.size());
+		}
 	}
 
 	@Test
 	public void testDeleteNonExisting() throws Exception {
-		Git git = new Git(db);
-		git.commit().setMessage("initial commit").call();
+		try (Git git = new Git(db)) {
+			git.commit().setMessage("initial commit").call();
 
-		List<String> deleted = git.tagDelete().setTags("tag").call();
-		assertEquals(0, deleted.size());
+			List<String> deleted = git.tagDelete().setTags("tag").call();
+			assertEquals(0, deleted.size());
+		}
 	}
 
 	@Test
 	public void testDeleteBadName() throws Exception {
-		Git git = new Git(db);
-		git.commit().setMessage("initial commit").call();
+		try (Git git = new Git(db)) {
+			git.commit().setMessage("initial commit").call();
 
-		List<String> deleted = git.tagDelete().setTags("bad~tag~name")
-				.call();
-		assertEquals(0, deleted.size());
+			List<String> deleted = git.tagDelete().setTags("bad~tag~name")
+					.call();
+			assertEquals(0, deleted.size());
+		}
 	}
 
 	@Test
 	public void testShouldNotBlowUpIfThereAreNoTagsInRepository()
 			throws Exception {
-		Git git = new Git(db);
-		git.add().addFilepattern("*").call();
-		git.commit().setMessage("initial commit").call();
-		List<Ref> list = git.tagList().call();
-		assertEquals(0, list.size());
+		try (Git git = new Git(db)) {
+			git.add().addFilepattern("*").call();
+			git.commit().setMessage("initial commit").call();
+			List<Ref> list = git.tagList().call();
+			assertEquals(0, list.size());
+		}
 	}
 
 	@Test
 	public void testShouldNotBlowUpIfThereAreNoCommitsInRepository()
 			throws Exception {
-		Git git = new Git(db);
-		List<Ref> list = git.tagList().call();
-		assertEquals(0, list.size());
+		try (Git git = new Git(db)) {
+			List<Ref> list = git.tagList().call();
+			assertEquals(0, list.size());
+		}
 	}
 
 	@Test
 	public void testListAllTagsInRepositoryInOrder() throws Exception {
-		Git git = new Git(db);
-		git.add().addFilepattern("*").call();
-		git.commit().setMessage("initial commit").call();
+		try (Git git = new Git(db)) {
+			git.add().addFilepattern("*").call();
+			git.commit().setMessage("initial commit").call();
 
-		git.tag().setName("v3").call();
-		git.tag().setName("v2").call();
-		git.tag().setName("v10").call();
+			git.tag().setName("v3").call();
+			git.tag().setName("v2").call();
+			git.tag().setName("v10").call();
 
-		List<Ref> list = git.tagList().call();
+			List<Ref> list = git.tagList().call();
 
-		assertEquals(3, list.size());
-		assertEquals("refs/tags/v10", list.get(0).getName());
-		assertEquals("refs/tags/v2", list.get(1).getName());
-		assertEquals("refs/tags/v3", list.get(2).getName());
+			assertEquals(3, list.size());
+			assertEquals("refs/tags/v10", list.get(0).getName());
+			assertEquals("refs/tags/v2", list.get(1).getName());
+			assertEquals("refs/tags/v3", list.get(2).getName());
+		}
 	}
 
 }
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/blame/BlameGeneratorTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/blame/BlameGeneratorTest.java
index 42909f0..fa9848b 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/blame/BlameGeneratorTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/blame/BlameGeneratorTest.java
@@ -58,141 +58,142 @@
 public class BlameGeneratorTest extends RepositoryTestCase {
 	@Test
 	public void testBoundLineDelete() throws Exception {
-		Git git = new Git(db);
+		try (Git git = new Git(db)) {
+			String[] content1 = new String[] { "first", "second" };
+			writeTrashFile("file.txt", join(content1));
+			git.add().addFilepattern("file.txt").call();
+			RevCommit c1 = git.commit().setMessage("create file").call();
 
-		String[] content1 = new String[] { "first", "second" };
-		writeTrashFile("file.txt", join(content1));
-		git.add().addFilepattern("file.txt").call();
-		RevCommit c1 = git.commit().setMessage("create file").call();
+			String[] content2 = new String[] { "third", "first", "second" };
+			writeTrashFile("file.txt", join(content2));
+			git.add().addFilepattern("file.txt").call();
+			RevCommit c2 = git.commit().setMessage("create file").call();
 
-		String[] content2 = new String[] { "third", "first", "second" };
-		writeTrashFile("file.txt", join(content2));
-		git.add().addFilepattern("file.txt").call();
-		RevCommit c2 = git.commit().setMessage("create file").call();
+			try (BlameGenerator generator = new BlameGenerator(db, "file.txt")) {
+				generator.push(null, db.resolve(Constants.HEAD));
+				assertEquals(3, generator.getResultContents().size());
 
-		try (BlameGenerator generator = new BlameGenerator(db, "file.txt")) {
-			generator.push(null, db.resolve(Constants.HEAD));
-			assertEquals(3, generator.getResultContents().size());
+				assertTrue(generator.next());
+				assertEquals(c2, generator.getSourceCommit());
+				assertEquals(1, generator.getRegionLength());
+				assertEquals(0, generator.getResultStart());
+				assertEquals(1, generator.getResultEnd());
+				assertEquals(0, generator.getSourceStart());
+				assertEquals(1, generator.getSourceEnd());
+				assertEquals("file.txt", generator.getSourcePath());
 
-			assertTrue(generator.next());
-			assertEquals(c2, generator.getSourceCommit());
-			assertEquals(1, generator.getRegionLength());
-			assertEquals(0, generator.getResultStart());
-			assertEquals(1, generator.getResultEnd());
-			assertEquals(0, generator.getSourceStart());
-			assertEquals(1, generator.getSourceEnd());
-			assertEquals("file.txt", generator.getSourcePath());
+				assertTrue(generator.next());
+				assertEquals(c1, generator.getSourceCommit());
+				assertEquals(2, generator.getRegionLength());
+				assertEquals(1, generator.getResultStart());
+				assertEquals(3, generator.getResultEnd());
+				assertEquals(0, generator.getSourceStart());
+				assertEquals(2, generator.getSourceEnd());
+				assertEquals("file.txt", generator.getSourcePath());
 
-			assertTrue(generator.next());
-			assertEquals(c1, generator.getSourceCommit());
-			assertEquals(2, generator.getRegionLength());
-			assertEquals(1, generator.getResultStart());
-			assertEquals(3, generator.getResultEnd());
-			assertEquals(0, generator.getSourceStart());
-			assertEquals(2, generator.getSourceEnd());
-			assertEquals("file.txt", generator.getSourcePath());
-
-			assertFalse(generator.next());
+				assertFalse(generator.next());
+			}
 		}
 	}
 
 	@Test
 	public void testRenamedBoundLineDelete() throws Exception {
-		Git git = new Git(db);
-		final String FILENAME_1 = "subdir/file1.txt";
-		final String FILENAME_2 = "subdir/file2.txt";
+		try (Git git = new Git(db)) {
+			final String FILENAME_1 = "subdir/file1.txt";
+			final String FILENAME_2 = "subdir/file2.txt";
 
-		String[] content1 = new String[] { "first", "second" };
-		writeTrashFile(FILENAME_1, join(content1));
-		git.add().addFilepattern(FILENAME_1).call();
-		RevCommit c1 = git.commit().setMessage("create file1").call();
+			String[] content1 = new String[] { "first", "second" };
+			writeTrashFile(FILENAME_1, join(content1));
+			git.add().addFilepattern(FILENAME_1).call();
+			RevCommit c1 = git.commit().setMessage("create file1").call();
 
-		// rename it
-		writeTrashFile(FILENAME_2, join(content1));
-		git.add().addFilepattern(FILENAME_2).call();
-		deleteTrashFile(FILENAME_1);
-		git.rm().addFilepattern(FILENAME_1).call();
-		git.commit().setMessage("rename file1.txt to file2.txt").call();
+			// rename it
+			writeTrashFile(FILENAME_2, join(content1));
+			git.add().addFilepattern(FILENAME_2).call();
+			deleteTrashFile(FILENAME_1);
+			git.rm().addFilepattern(FILENAME_1).call();
+			git.commit().setMessage("rename file1.txt to file2.txt").call();
 
-		// and change the new file
-		String[] content2 = new String[] { "third", "first", "second" };
-		writeTrashFile(FILENAME_2, join(content2));
-		git.add().addFilepattern(FILENAME_2).call();
-		RevCommit c2 = git.commit().setMessage("change file2").call();
+			// and change the new file
+			String[] content2 = new String[] { "third", "first", "second" };
+			writeTrashFile(FILENAME_2, join(content2));
+			git.add().addFilepattern(FILENAME_2).call();
+			RevCommit c2 = git.commit().setMessage("change file2").call();
 
-		try (BlameGenerator generator = new BlameGenerator(db, FILENAME_2)) {
-			generator.push(null, db.resolve(Constants.HEAD));
-			assertEquals(3, generator.getResultContents().size());
+			try (BlameGenerator generator = new BlameGenerator(db, FILENAME_2)) {
+				generator.push(null, db.resolve(Constants.HEAD));
+				assertEquals(3, generator.getResultContents().size());
 
-			assertTrue(generator.next());
-			assertEquals(c2, generator.getSourceCommit());
-			assertEquals(1, generator.getRegionLength());
-			assertEquals(0, generator.getResultStart());
-			assertEquals(1, generator.getResultEnd());
-			assertEquals(0, generator.getSourceStart());
-			assertEquals(1, generator.getSourceEnd());
-			assertEquals(FILENAME_2, generator.getSourcePath());
+				assertTrue(generator.next());
+				assertEquals(c2, generator.getSourceCommit());
+				assertEquals(1, generator.getRegionLength());
+				assertEquals(0, generator.getResultStart());
+				assertEquals(1, generator.getResultEnd());
+				assertEquals(0, generator.getSourceStart());
+				assertEquals(1, generator.getSourceEnd());
+				assertEquals(FILENAME_2, generator.getSourcePath());
 
-			assertTrue(generator.next());
-			assertEquals(c1, generator.getSourceCommit());
-			assertEquals(2, generator.getRegionLength());
-			assertEquals(1, generator.getResultStart());
-			assertEquals(3, generator.getResultEnd());
-			assertEquals(0, generator.getSourceStart());
-			assertEquals(2, generator.getSourceEnd());
-			assertEquals(FILENAME_1, generator.getSourcePath());
+				assertTrue(generator.next());
+				assertEquals(c1, generator.getSourceCommit());
+				assertEquals(2, generator.getRegionLength());
+				assertEquals(1, generator.getResultStart());
+				assertEquals(3, generator.getResultEnd());
+				assertEquals(0, generator.getSourceStart());
+				assertEquals(2, generator.getSourceEnd());
+				assertEquals(FILENAME_1, generator.getSourcePath());
 
-			assertFalse(generator.next());
-		}
+				assertFalse(generator.next());
+			}
 
-		// and test again with other BlameGenerator API:
-		try (BlameGenerator generator = new BlameGenerator(db, FILENAME_2)) {
-			generator.push(null, db.resolve(Constants.HEAD));
-			BlameResult result = generator.computeBlameResult();
+			// and test again with other BlameGenerator API:
+			try (BlameGenerator generator = new BlameGenerator(db, FILENAME_2)) {
+				generator.push(null, db.resolve(Constants.HEAD));
+				BlameResult result = generator.computeBlameResult();
 
-			assertEquals(3, result.getResultContents().size());
+				assertEquals(3, result.getResultContents().size());
 
-			assertEquals(c2, result.getSourceCommit(0));
-			assertEquals(FILENAME_2, result.getSourcePath(0));
+				assertEquals(c2, result.getSourceCommit(0));
+				assertEquals(FILENAME_2, result.getSourcePath(0));
 
-			assertEquals(c1, result.getSourceCommit(1));
-			assertEquals(FILENAME_1, result.getSourcePath(1));
+				assertEquals(c1, result.getSourceCommit(1));
+				assertEquals(FILENAME_1, result.getSourcePath(1));
 
-			assertEquals(c1, result.getSourceCommit(2));
-			assertEquals(FILENAME_1, result.getSourcePath(2));
+				assertEquals(c1, result.getSourceCommit(2));
+				assertEquals(FILENAME_1, result.getSourcePath(2));
+			}
 		}
 	}
 
 	@Test
 	public void testLinesAllDeletedShortenedWalk() throws Exception {
-		Git git = new Git(db);
+		try (Git git = new Git(db)) {
+			String[] content1 = new String[] { "first", "second", "third" };
 
-		String[] content1 = new String[] { "first", "second", "third" };
+			writeTrashFile("file.txt", join(content1));
+			git.add().addFilepattern("file.txt").call();
+			git.commit().setMessage("create file").call();
 
-		writeTrashFile("file.txt", join(content1));
-		git.add().addFilepattern("file.txt").call();
-		git.commit().setMessage("create file").call();
+			String[] content2 = new String[] { "" };
 
-		String[] content2 = new String[] { "" };
+			writeTrashFile("file.txt", join(content2));
+			git.add().addFilepattern("file.txt").call();
+			git.commit().setMessage("create file").call();
 
-		writeTrashFile("file.txt", join(content2));
-		git.add().addFilepattern("file.txt").call();
-		git.commit().setMessage("create file").call();
+			writeTrashFile("file.txt", join(content1));
+			git.add().addFilepattern("file.txt").call();
+			RevCommit c3 = git.commit().setMessage("create file").call();
 
-		writeTrashFile("file.txt", join(content1));
-		git.add().addFilepattern("file.txt").call();
-		RevCommit c3 = git.commit().setMessage("create file").call();
+			try (BlameGenerator generator = new BlameGenerator(db, "file.txt")) {
+				generator.push(null, db.resolve(Constants.HEAD));
+				assertEquals(3, generator.getResultContents().size());
 
-		try (BlameGenerator generator = new BlameGenerator(db, "file.txt")) {
-			generator.push(null, db.resolve(Constants.HEAD));
-			assertEquals(3, generator.getResultContents().size());
+				assertTrue(generator.next());
+				assertEquals(c3, generator.getSourceCommit());
+				assertEquals(0, generator.getResultStart());
+				assertEquals(3, generator.getResultEnd());
 
-			assertTrue(generator.next());
-			assertEquals(c3, generator.getSourceCommit());
-			assertEquals(0, generator.getResultStart());
-			assertEquals(3, generator.getResultEnd());
-
-			assertFalse(generator.next());
+				assertFalse(generator.next());
+			}
 		}
 	}
 
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesNodeDirCacheIteratorTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesNodeDirCacheIteratorTest.java
index 49279e6..0e595e6 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesNodeDirCacheIteratorTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesNodeDirCacheIteratorTest.java
@@ -52,9 +52,7 @@
 
 import java.io.IOException;
 import java.util.Collections;
-import java.util.LinkedHashMap;
 import java.util.List;
-import java.util.Map;
 
 import org.eclipse.jgit.api.Git;
 import org.eclipse.jgit.attributes.Attribute.State;
@@ -243,27 +241,29 @@
 		DirCacheIterator itr = walk.getTree(0, DirCacheIterator.class);
 		assertNotNull("has tree", itr);
 
-		AttributesNode attributeNode = itr.getEntryAttributesNode(db
+		AttributesNode attributesNode = itr.getEntryAttributesNode(db
 				.newObjectReader());
-		assertAttributeNode(pathName, attributeNode, nodeAttrs);
+		assertAttributesNode(pathName, attributesNode, nodeAttrs);
 
 		if (D.equals(type))
 			walk.enterSubtree();
 
 	}
 
-	private void assertAttributeNode(String pathName,
-			AttributesNode attributeNode, List<Attribute> nodeAttrs) {
-		if (attributeNode == null)
+	private void assertAttributesNode(String pathName,
+			AttributesNode attributesNode, List<Attribute> nodeAttrs) {
+		if (attributesNode == null)
 			assertTrue(nodeAttrs == null || nodeAttrs.isEmpty());
 		else {
 
-			Map<String, Attribute> entryAttributes = new LinkedHashMap<String, Attribute>();
-			attributeNode.getAttributes(pathName, false, entryAttributes);
+			Attributes entryAttributes = new Attributes();
+			attributesNode.getAttributes(pathName,
+					false, entryAttributes);
 
 			if (nodeAttrs != null && !nodeAttrs.isEmpty()) {
 				for (Attribute attribute : nodeAttrs) {
-					assertThat(entryAttributes.values(), hasItem(attribute));
+					assertThat(entryAttributes.getAll(),
+							hasItem(attribute));
 				}
 			} else {
 				assertTrue(
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributeNodeTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesNodeTest.java
similarity index 86%
rename from org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributeNodeTest.java
rename to org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesNodeTest.java
index ea25036..d478a7c 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributeNodeTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesNodeTest.java
@@ -49,10 +49,6 @@
 import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.io.InputStream;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Set;
 
 import org.junit.After;
 import org.junit.Test;
@@ -60,7 +56,7 @@
 /**
  * Test {@link AttributesNode}
  */
-public class AttributeNodeTest {
+public class AttributesNodeTest {
 
 	private static final Attribute A_SET_ATTR = new Attribute("A", SET);
 
@@ -104,8 +100,8 @@
 		is = new ByteArrayInputStream(attributeFileContent.getBytes());
 		AttributesNode node = new AttributesNode();
 		node.parse(is);
-		assertAttribute("file.type1", node, Collections.<Attribute> emptySet());
-		assertAttribute("file.type2", node, Collections.<Attribute> emptySet());
+		assertAttribute("file.type1", node, new Attributes());
+		assertAttribute("file.type2", node, new Attributes());
 	}
 
 	@Test
@@ -115,7 +111,7 @@
 		is = new ByteArrayInputStream(attributeFileContent.getBytes());
 		AttributesNode node = new AttributesNode();
 		node.parse(is);
-		assertAttribute("file.type1", node, Collections.<Attribute> emptySet());
+		assertAttribute("file.type1", node, new Attributes());
 		assertAttribute("file.type2", node, asSet(A_UNSET_ATTR));
 	}
 
@@ -127,8 +123,8 @@
 		is = new ByteArrayInputStream(attributeFileContent.getBytes());
 		AttributesNode node = new AttributesNode();
 		node.parse(is);
-		assertAttribute("file.type1", node, Collections.<Attribute> emptySet());
-		assertAttribute("file.type2", node, Collections.<Attribute> emptySet());
+		assertAttribute("file.type1", node, new Attributes());
+		assertAttribute("file.type2", node, new Attributes());
 		assertAttribute("file.type3", node, asSet(new Attribute("attr", "")));
 	}
 
@@ -166,17 +162,14 @@
 	}
 
 	private void assertAttribute(String path, AttributesNode node,
-			Set<Attribute> attrs) {
-		HashMap<String, Attribute> attributes = new HashMap<String, Attribute>();
+			Attributes attrs) {
+		Attributes attributes = new Attributes();
 		node.getAttributes(path, false, attributes);
-		assertEquals(attrs, new HashSet<Attribute>(attributes.values()));
+		assertEquals(attrs, attributes);
 	}
 
-	static Set<Attribute> asSet(Attribute... attrs) {
-		Set<Attribute> result = new HashSet<Attribute>();
-		for (Attribute attr : attrs)
-			result.add(attr);
-		return result;
+	static Attributes asSet(Attribute... attrs) {
+		return new Attributes(attrs);
 	}
 
 }
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesNodeWorkingTreeIteratorTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesNodeWorkingTreeIteratorTest.java
index 64b0535..4215ba2 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesNodeWorkingTreeIteratorTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesNodeWorkingTreeIteratorTest.java
@@ -53,12 +53,9 @@
 import java.io.File;
 import java.io.IOException;
 import java.util.Collections;
-import java.util.LinkedHashMap;
 import java.util.List;
-import java.util.Map;
 
 import org.eclipse.jgit.attributes.Attribute.State;
-import org.eclipse.jgit.errors.CorruptObjectException;
 import org.eclipse.jgit.junit.JGitTestUtil;
 import org.eclipse.jgit.junit.RepositoryTestCase;
 import org.eclipse.jgit.lib.FileMode;
@@ -76,14 +73,10 @@
 
 	private static final FileMode F = FileMode.REGULAR_FILE;
 
-	private static Attribute EOL_CRLF = new Attribute("eol", "crlf");
-
 	private static Attribute EOL_LF = new Attribute("eol", "lf");
 
 	private static Attribute DELTA_UNSET = new Attribute("delta", State.UNSET);
 
-	private static Attribute CUSTOM_VALUE = new Attribute("custom", "value");
-
 	private TreeWalk walk;
 
 	@Test
@@ -112,25 +105,19 @@
 		walk = beginWalk();
 
 		assertIteration(F, ".gitattributes");
-		assertIteration(F, "global.txt", asList(EOL_LF), null,
-				asList(CUSTOM_VALUE));
-		assertIteration(F, "readme.txt", asList(EOL_LF), null,
-				asList(CUSTOM_VALUE));
+		assertIteration(F, "global.txt", asList(EOL_LF));
+		assertIteration(F, "readme.txt", asList(EOL_LF));
 
 		assertIteration(D, "src");
 
 		assertIteration(D, "src/config");
 		assertIteration(F, "src/config/.gitattributes");
-		assertIteration(F, "src/config/readme.txt", asList(DELTA_UNSET), null,
-				asList(CUSTOM_VALUE));
-		assertIteration(F, "src/config/windows.file", null, asList(EOL_CRLF),
-				null);
-		assertIteration(F, "src/config/windows.txt", asList(DELTA_UNSET),
-				asList(EOL_CRLF), asList(CUSTOM_VALUE));
+		assertIteration(F, "src/config/readme.txt", asList(DELTA_UNSET));
+		assertIteration(F, "src/config/windows.file", null);
+		assertIteration(F, "src/config/windows.txt", asList(DELTA_UNSET));
 
-		assertIteration(F, "windows.file", null, asList(EOL_CRLF), null);
-		assertIteration(F, "windows.txt", asList(EOL_LF), asList(EOL_CRLF),
-				asList(CUSTOM_VALUE));
+		assertIteration(F, "windows.file", null);
+		assertIteration(F, "windows.txt", asList(EOL_LF));
 
 		endWalk();
 	}
@@ -212,14 +199,11 @@
 
 	private void assertIteration(FileMode type, String pathName)
 			throws IOException {
-		assertIteration(type, pathName, Collections.<Attribute> emptyList(),
-				Collections.<Attribute> emptyList(),
-				Collections.<Attribute> emptyList());
+		assertIteration(type, pathName, Collections.<Attribute> emptyList());
 	}
 
 	private void assertIteration(FileMode type, String pathName,
-			List<Attribute> nodeAttrs, List<Attribute> infoAttrs,
-			List<Attribute> globalAttrs)
+			List<Attribute> nodeAttrs)
 			throws IOException {
 		assertTrue("walk has entry", walk.next());
 		assertEquals(pathName, walk.getPathString());
@@ -227,29 +211,27 @@
 		WorkingTreeIterator itr = walk.getTree(0, WorkingTreeIterator.class);
 		assertNotNull("has tree", itr);
 
-		AttributesNode attributeNode = itr.getEntryAttributesNode();
-		assertAttributeNode(pathName, attributeNode, nodeAttrs);
-		AttributesNode infoAttributeNode = itr.getInfoAttributesNode();
-		assertAttributeNode(pathName, infoAttributeNode, infoAttrs);
-		AttributesNode globalAttributeNode = itr.getGlobalAttributesNode();
-		assertAttributeNode(pathName, globalAttributeNode, globalAttrs);
+		AttributesNode attributesNode = itr.getEntryAttributesNode();
+		assertAttributesNode(pathName, attributesNode, nodeAttrs);
 		if (D.equals(type))
 			walk.enterSubtree();
 
 	}
 
-	private void assertAttributeNode(String pathName,
-			AttributesNode attributeNode, List<Attribute> nodeAttrs) {
-		if (attributeNode == null)
+	private void assertAttributesNode(String pathName,
+			AttributesNode attributesNode, List<Attribute> nodeAttrs) {
+		if (attributesNode == null)
 			assertTrue(nodeAttrs == null || nodeAttrs.isEmpty());
 		else {
 
-			Map<String, Attribute> entryAttributes = new LinkedHashMap<String, Attribute>();
-			attributeNode.getAttributes(pathName, false, entryAttributes);
+			Attributes entryAttributes = new Attributes();
+			attributesNode.getAttributes(pathName,
+					false, entryAttributes);
 
 			if (nodeAttrs != null && !nodeAttrs.isEmpty()) {
 				for (Attribute attribute : nodeAttrs) {
-					assertThat(entryAttributes.values(), hasItem(attribute));
+					assertThat(entryAttributes.getAll(),
+							hasItem(attribute));
 				}
 			} else {
 				assertTrue(
@@ -270,7 +252,7 @@
 		writeTrashFile(name, data.toString());
 	}
 
-	private TreeWalk beginWalk() throws CorruptObjectException {
+	private TreeWalk beginWalk() {
 		TreeWalk newWalk = new TreeWalk(db);
 		newWalk.addTree(new FileTreeIterator(db));
 		return newWalk;
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/TreeWalkAttributeTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/TreeWalkAttributeTest.java
new file mode 100644
index 0000000..b044c01
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/TreeWalkAttributeTest.java
@@ -0,0 +1,866 @@
+/*
+ * Copyright (C) 2014, 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.attributes;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.io.File;
+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 org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.api.errors.GitAPIException;
+import org.eclipse.jgit.api.errors.NoFilepatternException;
+import org.eclipse.jgit.attributes.Attribute.State;
+import org.eclipse.jgit.dircache.DirCacheIterator;
+import org.eclipse.jgit.errors.NoWorkTreeException;
+import org.eclipse.jgit.junit.JGitTestUtil;
+import org.eclipse.jgit.junit.RepositoryTestCase;
+import org.eclipse.jgit.lib.FileMode;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.treewalk.FileTreeIterator;
+import org.eclipse.jgit.treewalk.TreeWalk;
+import org.eclipse.jgit.treewalk.TreeWalk.OperationType;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Tests the attributes are correctly computed in a {@link TreeWalk}.
+ *
+ * @see TreeWalk#getAttributes()
+ */
+public class TreeWalkAttributeTest extends RepositoryTestCase {
+
+	private static final FileMode M = FileMode.MISSING;
+
+	private static final FileMode D = FileMode.TREE;
+
+	private static final FileMode F = FileMode.REGULAR_FILE;
+
+	private static Attribute EOL_CRLF = new Attribute("eol", "crlf");
+
+	private static Attribute EOL_LF = new Attribute("eol", "lf");
+
+	private static Attribute TEXT_SET = new Attribute("text", State.SET);
+
+	private static Attribute TEXT_UNSET = new Attribute("text", State.UNSET);
+
+	private static Attribute DELTA_UNSET = new Attribute("delta", State.UNSET);
+
+	private static Attribute DELTA_SET = new Attribute("delta", State.SET);
+
+	private static Attribute CUSTOM_GLOBAL = new Attribute("custom", "global");
+
+	private static Attribute CUSTOM_INFO = new Attribute("custom", "info");
+
+	private static Attribute CUSTOM_ROOT = new Attribute("custom", "root");
+
+	private static Attribute CUSTOM_PARENT = new Attribute("custom", "parent");
+
+	private static Attribute CUSTOM_CURRENT = new Attribute("custom", "current");
+
+	private static Attribute CUSTOM2_UNSET = new Attribute("custom2",
+			State.UNSET);
+
+	private static Attribute CUSTOM2_SET = new Attribute("custom2", State.SET);
+
+	private TreeWalk walk;
+
+	private TreeWalk ci_walk;
+
+	private Git git;
+
+	private File customAttributeFile;
+
+	@Override
+	@Before
+	public void setUp() throws Exception {
+		super.setUp();
+		git = new Git(db);
+	}
+
+	@Override
+	@After
+	public void tearDown() throws Exception {
+		super.tearDown();
+		if (customAttributeFile != null)
+			customAttributeFile.delete();
+	}
+
+	/**
+	 * Checks that the attributes are computed correctly depending on the
+	 * operation type.
+	 * <p>
+	 * In this test we changed the content of the attribute files in the working
+	 * tree compared to the one in the index.
+	 * </p>
+	 *
+	 * @throws IOException
+	 * @throws NoFilepatternException
+	 * @throws GitAPIException
+	 */
+	@Test
+	public void testCheckinCheckoutDifferences() throws IOException,
+			NoFilepatternException, GitAPIException {
+
+		writeGlobalAttributeFile("globalAttributesFile", "*.txt -custom2");
+		writeAttributesFile(".git/info/attributes", "*.txt eol=crlf");
+		writeAttributesFile(".gitattributes", "*.txt custom=root");
+		writeAttributesFile("level1/.gitattributes", "*.txt text");
+		writeAttributesFile("level1/level2/.gitattributes", "*.txt -delta");
+
+		writeTrashFile("l0.txt", "");
+
+		writeTrashFile("level1/l1.txt", "");
+
+		writeTrashFile("level1/level2/l2.txt", "");
+
+		git.add().addFilepattern(".").call();
+
+		beginWalk();
+
+		// Modify all attributes
+		writeGlobalAttributeFile("globalAttributesFile", "*.txt custom2");
+		writeAttributesFile(".git/info/attributes", "*.txt eol=lf");
+		writeAttributesFile(".gitattributes", "*.txt custom=info");
+		writeAttributesFile("level1/.gitattributes", "*.txt -text");
+		writeAttributesFile("level1/level2/.gitattributes", "*.txt delta");
+
+		assertEntry(F, ".gitattributes");
+		assertEntry(F, "l0.txt", asSet(EOL_LF, CUSTOM_INFO, CUSTOM2_SET),
+				asSet(EOL_LF, CUSTOM_ROOT, CUSTOM2_SET));
+
+		assertEntry(D, "level1");
+		assertEntry(F, "level1/.gitattributes");
+		assertEntry(F, "level1/l1.txt",
+				asSet(EOL_LF, CUSTOM_INFO, CUSTOM2_SET, TEXT_UNSET),
+				asSet(EOL_LF, CUSTOM_ROOT, CUSTOM2_SET, TEXT_SET));
+
+		assertEntry(D, "level1/level2");
+		assertEntry(F, "level1/level2/.gitattributes");
+		assertEntry(F, "level1/level2/l2.txt",
+				asSet(EOL_LF, CUSTOM_INFO, CUSTOM2_SET, TEXT_UNSET, DELTA_SET),
+				asSet(EOL_LF, CUSTOM_ROOT, CUSTOM2_SET, TEXT_SET, DELTA_UNSET));
+
+		endWalk();
+	}
+
+	/**
+	 * Checks that the index is used as fallback when the git attributes file
+	 * are missing in the working tree.
+	 *
+	 * @throws IOException
+	 * @throws NoFilepatternException
+	 * @throws GitAPIException
+	 */
+	@Test
+	public void testIndexOnly() throws IOException, NoFilepatternException,
+			GitAPIException {
+		List<File> attrFiles = new ArrayList<File>();
+		attrFiles.add(writeGlobalAttributeFile("globalAttributesFile",
+				"*.txt -custom2"));
+		attrFiles.add(writeAttributesFile(".git/info/attributes",
+				"*.txt eol=crlf"));
+		attrFiles
+				.add(writeAttributesFile(".gitattributes", "*.txt custom=root"));
+		attrFiles
+				.add(writeAttributesFile("level1/.gitattributes", "*.txt text"));
+		attrFiles.add(writeAttributesFile("level1/level2/.gitattributes",
+				"*.txt -delta"));
+
+		writeTrashFile("l0.txt", "");
+
+		writeTrashFile("level1/l1.txt", "");
+
+		writeTrashFile("level1/level2/l2.txt", "");
+
+		git.add().addFilepattern(".").call();
+
+		// Modify all attributes
+		for (File attrFile : attrFiles)
+			attrFile.delete();
+
+		beginWalk();
+
+		assertEntry(M, ".gitattributes");
+		assertEntry(F, "l0.txt", asSet(CUSTOM_ROOT));
+
+		assertEntry(D, "level1");
+		assertEntry(M, "level1/.gitattributes");
+		assertEntry(F, "level1/l1.txt",
+
+		asSet(CUSTOM_ROOT, TEXT_SET));
+
+		assertEntry(D, "level1/level2");
+		assertEntry(M, "level1/level2/.gitattributes");
+		assertEntry(F, "level1/level2/l2.txt",
+
+		asSet(CUSTOM_ROOT, TEXT_SET, DELTA_UNSET));
+
+		endWalk();
+	}
+
+	/**
+	 * Check that we search in the working tree for attributes although the file
+	 * we are currently inspecting does not exist anymore in the working tree.
+	 *
+	 * @throws IOException
+	 * @throws NoFilepatternException
+	 * @throws GitAPIException
+	 */
+	@Test
+	public void testIndexOnly2()
+			throws IOException, NoFilepatternException, GitAPIException {
+		File l2 = writeTrashFile("level1/level2/l2.txt", "");
+		writeTrashFile("level1/level2/l1.txt", "");
+
+		git.add().addFilepattern(".").call();
+
+		writeAttributesFile(".gitattributes", "*.txt custom=root");
+		assertTrue(l2.delete());
+
+		beginWalk();
+
+		assertEntry(F, ".gitattributes");
+		assertEntry(D, "level1");
+		assertEntry(D, "level1/level2");
+		assertEntry(F, "level1/level2/l1.txt", asSet(CUSTOM_ROOT));
+		assertEntry(M, "level1/level2/l2.txt", asSet(CUSTOM_ROOT));
+
+		endWalk();
+	}
+
+	/**
+	 * Basic test for git attributes.
+	 * <p>
+	 * In this use case files are present in both the working tree and the index
+	 * </p>
+	 *
+	 * @throws IOException
+	 * @throws NoFilepatternException
+	 * @throws GitAPIException
+	 */
+	@Test
+	public void testRules() throws IOException, NoFilepatternException,
+			GitAPIException {
+		writeAttributesFile(".git/info/attributes", "windows* eol=crlf");
+
+		writeAttributesFile(".gitattributes", "*.txt eol=lf");
+		writeTrashFile("windows.file", "");
+		writeTrashFile("windows.txt", "");
+		writeTrashFile("readme.txt", "");
+
+		writeAttributesFile("src/config/.gitattributes", "*.txt -delta");
+		writeTrashFile("src/config/readme.txt", "");
+		writeTrashFile("src/config/windows.file", "");
+		writeTrashFile("src/config/windows.txt", "");
+
+		beginWalk();
+
+		git.add().addFilepattern(".").call();
+
+		assertEntry(F, ".gitattributes");
+		assertEntry(F, "readme.txt", asSet(EOL_LF));
+
+		assertEntry(D, "src");
+		assertEntry(D, "src/config");
+		assertEntry(F, "src/config/.gitattributes");
+		assertEntry(F, "src/config/readme.txt", asSet(DELTA_UNSET, EOL_LF));
+		assertEntry(F, "src/config/windows.file", asSet(EOL_CRLF));
+		assertEntry(F, "src/config/windows.txt", asSet(DELTA_UNSET, EOL_CRLF));
+
+		assertEntry(F, "windows.file", asSet(EOL_CRLF));
+		assertEntry(F, "windows.txt", asSet(EOL_CRLF));
+
+		endWalk();
+	}
+
+	/**
+	 * Checks that if there is no .gitattributes file in the repository
+	 * everything still work fine.
+	 *
+	 * @throws IOException
+	 */
+	@Test
+	public void testNoAttributes() throws IOException {
+		writeTrashFile("l0.txt", "");
+		writeTrashFile("level1/l1.txt", "");
+		writeTrashFile("level1/level2/l2.txt", "");
+
+		beginWalk();
+
+		assertEntry(F, "l0.txt");
+
+		assertEntry(D, "level1");
+		assertEntry(F, "level1/l1.txt");
+
+		assertEntry(D, "level1/level2");
+		assertEntry(F, "level1/level2/l2.txt");
+
+		endWalk();
+	}
+
+	/**
+	 * Checks that an empty .gitattribute file does not return incorrect value.
+	 *
+	 * @throws IOException
+	 */
+	@Test
+	public void testEmptyGitAttributeFile() throws IOException {
+		writeAttributesFile(".git/info/attributes", "");
+		writeTrashFile("l0.txt", "");
+		writeAttributesFile(".gitattributes", "");
+		writeTrashFile("level1/l1.txt", "");
+		writeTrashFile("level1/level2/l2.txt", "");
+
+		beginWalk();
+
+		assertEntry(F, ".gitattributes");
+		assertEntry(F, "l0.txt");
+
+		assertEntry(D, "level1");
+		assertEntry(F, "level1/l1.txt");
+
+		assertEntry(D, "level1/level2");
+		assertEntry(F, "level1/level2/l2.txt");
+
+		endWalk();
+	}
+
+	@Test
+	public void testNoMatchingAttributes() throws IOException {
+		writeAttributesFile(".git/info/attributes", "*.java delta");
+		writeAttributesFile(".gitattributes", "*.java -delta");
+		writeAttributesFile("levelA/.gitattributes", "*.java eol=lf");
+		writeAttributesFile("levelB/.gitattributes", "*.txt eol=lf");
+
+		writeTrashFile("levelA/lA.txt", "");
+
+		beginWalk();
+
+		assertEntry(F, ".gitattributes");
+
+		assertEntry(D, "levelA");
+		assertEntry(F, "levelA/.gitattributes");
+		assertEntry(F, "levelA/lA.txt");
+
+		assertEntry(D, "levelB");
+		assertEntry(F, "levelB/.gitattributes");
+
+		endWalk();
+	}
+
+	/**
+	 * Checks that $GIT_DIR/info/attributes file has the highest precedence.
+	 *
+	 * @throws IOException
+	 */
+	@Test
+	public void testPrecedenceInfo() throws IOException {
+		writeGlobalAttributeFile("globalAttributesFile", "*.txt custom=global");
+		writeAttributesFile(".git/info/attributes", "*.txt custom=info");
+		writeAttributesFile(".gitattributes", "*.txt custom=root");
+		writeAttributesFile("level1/.gitattributes", "*.txt custom=parent");
+		writeAttributesFile("level1/level2/.gitattributes",
+				"*.txt custom=current");
+
+		writeTrashFile("level1/level2/file.txt", "");
+
+		beginWalk();
+
+		assertEntry(F, ".gitattributes");
+
+		assertEntry(D, "level1");
+		assertEntry(F, "level1/.gitattributes");
+
+		assertEntry(D, "level1/level2");
+		assertEntry(F, "level1/level2/.gitattributes");
+		assertEntry(F, "level1/level2/file.txt", asSet(CUSTOM_INFO));
+
+		endWalk();
+	}
+
+	/**
+	 * Checks that a subfolder ".gitattributes" file has precedence over its
+	 * parent.
+	 *
+	 * @throws IOException
+	 */
+	@Test
+	public void testPrecedenceCurrent() throws IOException {
+		writeGlobalAttributeFile("globalAttributesFile", "*.txt custom=global");
+		writeAttributesFile(".gitattributes", "*.txt custom=root");
+		writeAttributesFile("level1/.gitattributes", "*.txt custom=parent");
+		writeAttributesFile("level1/level2/.gitattributes",
+				"*.txt custom=current");
+
+		writeTrashFile("level1/level2/file.txt", "");
+
+		beginWalk();
+
+		assertEntry(F, ".gitattributes");
+
+		assertEntry(D, "level1");
+		assertEntry(F, "level1/.gitattributes");
+
+		assertEntry(D, "level1/level2");
+		assertEntry(F, "level1/level2/.gitattributes");
+		assertEntry(F, "level1/level2/file.txt", asSet(CUSTOM_CURRENT));
+
+		endWalk();
+	}
+
+	/**
+	 * Checks that the parent ".gitattributes" file is used as fallback.
+	 *
+	 * @throws IOException
+	 */
+	@Test
+	public void testPrecedenceParent() throws IOException {
+		writeGlobalAttributeFile("globalAttributesFile", "*.txt custom=global");
+		writeAttributesFile(".gitattributes", "*.txt custom=root");
+		writeAttributesFile("level1/.gitattributes", "*.txt custom=parent");
+
+		writeTrashFile("level1/level2/file.txt", "");
+
+		beginWalk();
+
+		assertEntry(F, ".gitattributes");
+
+		assertEntry(D, "level1");
+		assertEntry(F, "level1/.gitattributes");
+
+		assertEntry(D, "level1/level2");
+		assertEntry(F, "level1/level2/file.txt", asSet(CUSTOM_PARENT));
+
+		endWalk();
+	}
+
+	/**
+	 * Checks that the grand parent ".gitattributes" file is used as fallback.
+	 *
+	 * @throws IOException
+	 */
+	@Test
+	public void testPrecedenceRoot() throws IOException {
+		writeGlobalAttributeFile("globalAttributesFile", "*.txt custom=global");
+		writeAttributesFile(".gitattributes", "*.txt custom=root");
+
+		writeTrashFile("level1/level2/file.txt", "");
+
+		beginWalk();
+
+		assertEntry(F, ".gitattributes");
+
+		assertEntry(D, "level1");
+
+		assertEntry(D, "level1/level2");
+		assertEntry(F, "level1/level2/file.txt", asSet(CUSTOM_ROOT));
+
+		endWalk();
+	}
+
+	/**
+	 * Checks that the global attribute file is used as fallback.
+	 *
+	 * @throws IOException
+	 */
+	@Test
+	public void testPrecedenceGlobal() throws IOException {
+		writeGlobalAttributeFile("globalAttributesFile", "*.txt custom=global");
+
+		writeTrashFile("level1/level2/file.txt", "");
+
+		beginWalk();
+
+		assertEntry(D, "level1");
+
+		assertEntry(D, "level1/level2");
+		assertEntry(F, "level1/level2/file.txt", asSet(CUSTOM_GLOBAL));
+
+		endWalk();
+	}
+
+	/**
+	 * Checks the precedence on a hierarchy with multiple attributes.
+	 * <p>
+	 * In this test all file are present in both the working tree and the index.
+	 * </p>
+	 *
+	 * @throws IOException
+	 * @throws GitAPIException
+	 * @throws NoFilepatternException
+	 */
+	@Test
+	public void testHierarchyBothIterator() throws IOException,
+			NoFilepatternException, GitAPIException {
+		writeAttributesFile(".git/info/attributes", "*.global eol=crlf");
+		writeAttributesFile(".gitattributes", "*.local eol=lf");
+		writeAttributesFile("level1/.gitattributes", "*.local text");
+		writeAttributesFile("level1/level2/.gitattributes", "*.local -text");
+
+		writeTrashFile("l0.global", "");
+		writeTrashFile("l0.local", "");
+
+		writeTrashFile("level1/l1.global", "");
+		writeTrashFile("level1/l1.local", "");
+
+		writeTrashFile("level1/level2/l2.global", "");
+		writeTrashFile("level1/level2/l2.local", "");
+
+		beginWalk();
+
+		git.add().addFilepattern(".").call();
+
+		assertEntry(F, ".gitattributes");
+		assertEntry(F, "l0.global", asSet(EOL_CRLF));
+		assertEntry(F, "l0.local", asSet(EOL_LF));
+
+		assertEntry(D, "level1");
+		assertEntry(F, "level1/.gitattributes");
+		assertEntry(F, "level1/l1.global", asSet(EOL_CRLF));
+		assertEntry(F, "level1/l1.local", asSet(EOL_LF, TEXT_SET));
+
+		assertEntry(D, "level1/level2");
+		assertEntry(F, "level1/level2/.gitattributes");
+		assertEntry(F, "level1/level2/l2.global", asSet(EOL_CRLF));
+		assertEntry(F, "level1/level2/l2.local", asSet(EOL_LF, TEXT_UNSET));
+
+		endWalk();
+
+	}
+
+	/**
+	 * Checks the precedence on a hierarchy with multiple attributes.
+	 * <p>
+	 * In this test all file are present only in the working tree.
+	 * </p>
+	 *
+	 * @throws IOException
+	 * @throws GitAPIException
+	 * @throws NoFilepatternException
+	 */
+	@Test
+	public void testHierarchyWorktreeOnly()
+			throws IOException, NoFilepatternException, GitAPIException {
+		writeAttributesFile(".git/info/attributes", "*.global eol=crlf");
+		writeAttributesFile(".gitattributes", "*.local eol=lf");
+		writeAttributesFile("level1/.gitattributes", "*.local text");
+		writeAttributesFile("level1/level2/.gitattributes", "*.local -text");
+
+		writeTrashFile("l0.global", "");
+		writeTrashFile("l0.local", "");
+
+		writeTrashFile("level1/l1.global", "");
+		writeTrashFile("level1/l1.local", "");
+
+		writeTrashFile("level1/level2/l2.global", "");
+		writeTrashFile("level1/level2/l2.local", "");
+
+		beginWalk();
+
+		assertEntry(F, ".gitattributes");
+		assertEntry(F, "l0.global", asSet(EOL_CRLF));
+		assertEntry(F, "l0.local", asSet(EOL_LF));
+
+		assertEntry(D, "level1");
+		assertEntry(F, "level1/.gitattributes");
+		assertEntry(F, "level1/l1.global", asSet(EOL_CRLF));
+		assertEntry(F, "level1/l1.local", asSet(EOL_LF, TEXT_SET));
+
+		assertEntry(D, "level1/level2");
+		assertEntry(F, "level1/level2/.gitattributes");
+		assertEntry(F, "level1/level2/l2.global", asSet(EOL_CRLF));
+		assertEntry(F, "level1/level2/l2.local", asSet(EOL_LF, TEXT_UNSET));
+
+		endWalk();
+
+	}
+
+	/**
+	 * Checks that the list of attributes is an aggregation of all the
+	 * attributes from the attributes files hierarchy.
+	 *
+	 * @throws IOException
+	 */
+	@Test
+	public void testAggregation() throws IOException {
+		writeGlobalAttributeFile("globalAttributesFile", "*.txt -custom2");
+		writeAttributesFile(".git/info/attributes", "*.txt eol=crlf");
+		writeAttributesFile(".gitattributes", "*.txt custom=root");
+		writeAttributesFile("level1/.gitattributes", "*.txt text");
+		writeAttributesFile("level1/level2/.gitattributes", "*.txt -delta");
+
+		writeTrashFile("l0.txt", "");
+
+		writeTrashFile("level1/l1.txt", "");
+
+		writeTrashFile("level1/level2/l2.txt", "");
+
+		beginWalk();
+
+		assertEntry(F, ".gitattributes");
+		assertEntry(F, "l0.txt", asSet(EOL_CRLF, CUSTOM_ROOT, CUSTOM2_UNSET));
+
+		assertEntry(D, "level1");
+		assertEntry(F, "level1/.gitattributes");
+		assertEntry(F, "level1/l1.txt",
+				asSet(EOL_CRLF, CUSTOM_ROOT, TEXT_SET, CUSTOM2_UNSET));
+
+		assertEntry(D, "level1/level2");
+		assertEntry(F, "level1/level2/.gitattributes");
+		assertEntry(
+				F,
+				"level1/level2/l2.txt",
+				asSet(EOL_CRLF, CUSTOM_ROOT, TEXT_SET, DELTA_UNSET,
+						CUSTOM2_UNSET));
+
+		endWalk();
+
+	}
+
+	/**
+	 * Checks that the last entry in .gitattributes is used if 2 lines match the
+	 * same attribute
+	 *
+	 * @throws IOException
+	 */
+	@Test
+	public void testOverriding() throws IOException {
+		writeAttributesFile(".git/info/attributes",//
+				//
+				"*.txt custom=current",//
+				"*.txt custom=parent",//
+				"*.txt custom=root",//
+				"*.txt custom=info",
+				//
+				"*.txt delta",//
+				"*.txt -delta",
+				//
+				"*.txt eol=lf",//
+				"*.txt eol=crlf",
+				//
+				"*.txt text",//
+				"*.txt -text");
+
+		writeTrashFile("l0.txt", "");
+		beginWalk();
+
+		assertEntry(F, "l0.txt",
+				asSet(TEXT_UNSET, EOL_CRLF, DELTA_UNSET, CUSTOM_INFO));
+
+		endWalk();
+	}
+
+	/**
+	 * Checks that the last value of an attribute is used if in the same line an
+	 * attribute is defined several time.
+	 *
+	 * @throws IOException
+	 */
+	@Test
+	public void testOverriding2() throws IOException {
+		writeAttributesFile(".git/info/attributes",
+				"*.txt custom=current custom=parent custom=root custom=info",//
+				"*.txt delta -delta",//
+				"*.txt eol=lf eol=crlf",//
+				"*.txt text -text");
+		writeTrashFile("l0.txt", "");
+		beginWalk();
+
+		assertEntry(F, "l0.txt",
+				asSet(TEXT_UNSET, EOL_CRLF, DELTA_UNSET, CUSTOM_INFO));
+
+		endWalk();
+	}
+
+	@Test
+	public void testRulesInherited() throws Exception {
+		writeAttributesFile(".gitattributes", "**/*.txt eol=lf");
+		writeTrashFile("src/config/readme.txt", "");
+		writeTrashFile("src/config/windows.file", "");
+
+		beginWalk();
+
+		assertEntry(F, ".gitattributes");
+		assertEntry(D, "src");
+		assertEntry(D, "src/config");
+
+		assertEntry(F, "src/config/readme.txt", asSet(EOL_LF));
+		assertEntry(F, "src/config/windows.file",
+				Collections.<Attribute> emptySet());
+
+		endWalk();
+	}
+
+	private void beginWalk() throws NoWorkTreeException, IOException {
+		walk = new TreeWalk(db);
+		walk.addTree(new FileTreeIterator(db));
+		walk.addTree(new DirCacheIterator(db.readDirCache()));
+
+		ci_walk = new TreeWalk(db);
+		ci_walk.setOperationType(OperationType.CHECKIN_OP);
+		ci_walk.addTree(new FileTreeIterator(db));
+		ci_walk.addTree(new DirCacheIterator(db.readDirCache()));
+	}
+
+	/**
+	 * Assert an entry in which checkin and checkout attributes are expected to
+	 * be the same.
+	 *
+	 * @param type
+	 * @param pathName
+	 * @param forBothOperaiton
+	 * @throws IOException
+	 */
+	private void assertEntry(FileMode type, String pathName,
+			Set<Attribute> forBothOperaiton) throws IOException {
+		assertEntry(type, pathName, forBothOperaiton, forBothOperaiton);
+	}
+
+	/**
+	 * Assert an entry with no attribute expected.
+	 *
+	 * @param type
+	 * @param pathName
+	 * @throws IOException
+	 */
+	private void assertEntry(FileMode type, String pathName) throws IOException {
+		assertEntry(type, pathName, Collections.<Attribute> emptySet(),
+				Collections.<Attribute> emptySet());
+	}
+
+	/**
+	 * Assert that an entry;
+	 * <ul>
+	 * <li>Has the correct type</li>
+	 * <li>Exist in the tree walk</li>
+	 * <li>Has the expected attributes on a checkin operation</li>
+	 * <li>Has the expected attributes on a checkout operation</li>
+	 * </ul>
+	 *
+	 * @param type
+	 * @param pathName
+	 * @param checkinAttributes
+	 * @param checkoutAttributes
+	 * @throws IOException
+	 */
+	private void assertEntry(FileMode type, String pathName,
+			Set<Attribute> checkinAttributes, Set<Attribute> checkoutAttributes)
+			throws IOException {
+		assertTrue("walk has entry", walk.next());
+		assertTrue("walk has entry", ci_walk.next());
+		assertEquals(pathName, walk.getPathString());
+		assertEquals(type, walk.getFileMode(0));
+
+		assertEquals(checkinAttributes,
+				asSet(ci_walk.getAttributes().getAll()));
+		assertEquals(checkoutAttributes,
+				asSet(walk.getAttributes().getAll()));
+
+		if (D.equals(type)) {
+			walk.enterSubtree();
+			ci_walk.enterSubtree();
+		}
+	}
+
+	private static Set<Attribute> asSet(Collection<Attribute> attributes) {
+		Set<Attribute> ret = new HashSet<Attribute>();
+		for (Attribute a : attributes) {
+			ret.add(a);
+		}
+		return (ret);
+	}
+
+	private File writeAttributesFile(String name, String... rules)
+			throws IOException {
+		StringBuilder data = new StringBuilder();
+		for (String line : rules)
+			data.append(line + "\n");
+		return writeTrashFile(name, data.toString());
+	}
+
+	/**
+	 * Creates an attributes file and set its location in the git configuration.
+	 *
+	 * @param fileName
+	 * @param attributes
+	 * @return The attribute file
+	 * @throws IOException
+	 * @see Repository#getConfig()
+	 */
+	private File writeGlobalAttributeFile(String fileName, String... attributes)
+			throws IOException {
+		customAttributeFile = File.createTempFile("tmp_", fileName, null);
+		customAttributeFile.deleteOnExit();
+		StringBuilder attributesFileContent = new StringBuilder();
+		for (String attr : attributes) {
+			attributesFileContent.append(attr).append("\n");
+		}
+		JGitTestUtil.write(customAttributeFile,
+				attributesFileContent.toString());
+		db.getConfig().setString("core", null, "attributesfile",
+				customAttributeFile.getAbsolutePath());
+		return customAttributeFile;
+	}
+
+	static Set<Attribute> asSet(Attribute... attrs) {
+		HashSet<Attribute> result = new HashSet<Attribute>();
+		for (Attribute attr : attrs)
+			result.add(attr);
+		return result;
+	}
+
+	private void endWalk() throws IOException {
+		assertFalse("Not all files tested", walk.next());
+		assertFalse("Not all files tested", ci_walk.next());
+	}
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/DiffEntryTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/DiffEntryTest.java
index 5694e83..443c956 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/DiffEntryTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/DiffEntryTest.java
@@ -77,260 +77,268 @@
 	public void shouldListAddedFileInInitialCommit() throws Exception {
 		// given
 		writeTrashFile("a.txt", "content");
-		Git git = new Git(db);
-		git.add().addFilepattern("a.txt").call();
-		RevCommit c = git.commit().setMessage("initial commit").call();
+		try (Git git = new Git(db);
+				TreeWalk walk = new TreeWalk(db)) {
+			git.add().addFilepattern("a.txt").call();
+			RevCommit c = git.commit().setMessage("initial commit").call();
 
-		// when
-		TreeWalk walk = new TreeWalk(db);
-		walk.addTree(new EmptyTreeIterator());
-		walk.addTree(c.getTree());
-		List<DiffEntry> result = DiffEntry.scan(walk);
+			// when
+			walk.addTree(new EmptyTreeIterator());
+			walk.addTree(c.getTree());
+			List<DiffEntry> result = DiffEntry.scan(walk);
 
-		// then
-		assertThat(result, notNullValue());
-		assertThat(Integer.valueOf(result.size()), is(Integer.valueOf(1)));
+			// then
+			assertThat(result, notNullValue());
+			assertThat(Integer.valueOf(result.size()), is(Integer.valueOf(1)));
 
-		DiffEntry entry = result.get(0);
-		assertThat(entry.getChangeType(), is(ChangeType.ADD));
-		assertThat(entry.getNewPath(), is("a.txt"));
-		assertThat(entry.getOldPath(), is(DEV_NULL));
+			DiffEntry entry = result.get(0);
+			assertThat(entry.getChangeType(), is(ChangeType.ADD));
+			assertThat(entry.getNewPath(), is("a.txt"));
+			assertThat(entry.getOldPath(), is(DEV_NULL));
+		}
 	}
 
 	@Test
 	public void shouldListAddedFileBetweenTwoCommits() throws Exception {
 		// given
-		Git git = new Git(db);
-		RevCommit c1 = git.commit().setMessage("initial commit").call();
-		writeTrashFile("a.txt", "content");
-		git.add().addFilepattern("a.txt").call();
-		RevCommit c2 = git.commit().setMessage("second commit").call();
+		try (Git git = new Git(db);
+				TreeWalk walk = new TreeWalk(db)) {
+			RevCommit c1 = git.commit().setMessage("initial commit").call();
+			writeTrashFile("a.txt", "content");
+			git.add().addFilepattern("a.txt").call();
+			RevCommit c2 = git.commit().setMessage("second commit").call();
 
-		// when
-		TreeWalk walk = new TreeWalk(db);
-		walk.addTree(c1.getTree());
-		walk.addTree(c2.getTree());
-		List<DiffEntry> result = DiffEntry.scan(walk);
+			// when
+			walk.addTree(c1.getTree());
+			walk.addTree(c2.getTree());
+			List<DiffEntry> result = DiffEntry.scan(walk);
 
-		// then
-		assertThat(result, notNullValue());
-		assertThat(Integer.valueOf(result.size()), is(Integer.valueOf(1)));
+			// then
+			assertThat(result, notNullValue());
+			assertThat(Integer.valueOf(result.size()), is(Integer.valueOf(1)));
 
-		DiffEntry entry = result.get(0);
-		assertThat(entry.getChangeType(), is(ChangeType.ADD));
-		assertThat(entry.getNewPath(), is("a.txt"));
-		assertThat(entry.getOldPath(), is(DEV_NULL));
+			DiffEntry entry = result.get(0);
+			assertThat(entry.getChangeType(), is(ChangeType.ADD));
+			assertThat(entry.getNewPath(), is("a.txt"));
+			assertThat(entry.getOldPath(), is(DEV_NULL));
+		}
 	}
 
 	@Test
 	public void shouldListModificationBetweenTwoCommits() throws Exception {
 		// given
-		Git git = new Git(db);
-		File file = writeTrashFile("a.txt", "content");
-		git.add().addFilepattern("a.txt").call();
-		RevCommit c1 = git.commit().setMessage("initial commit").call();
-		write(file, "new content");
-		RevCommit c2 = git.commit().setAll(true).setMessage("second commit")
-				.call();
+		try (Git git = new Git(db);
+				TreeWalk walk = new TreeWalk(db)) {
+			File file = writeTrashFile("a.txt", "content");
+			git.add().addFilepattern("a.txt").call();
+			RevCommit c1 = git.commit().setMessage("initial commit").call();
+			write(file, "new content");
+			RevCommit c2 = git.commit().setAll(true).setMessage("second commit")
+					.call();
 
-		// when
-		TreeWalk walk = new TreeWalk(db);
-		walk.addTree(c1.getTree());
-		walk.addTree(c2.getTree());
-		List<DiffEntry> result = DiffEntry.scan(walk);
+			// when
+			walk.addTree(c1.getTree());
+			walk.addTree(c2.getTree());
+			List<DiffEntry> result = DiffEntry.scan(walk);
 
-		// then
-		assertThat(result, notNullValue());
-		assertThat(Integer.valueOf(result.size()), is(Integer.valueOf(1)));
+			// then
+			assertThat(result, notNullValue());
+			assertThat(Integer.valueOf(result.size()), is(Integer.valueOf(1)));
 
-		DiffEntry entry = result.get(0);
-		assertThat(entry.getChangeType(), is(ChangeType.MODIFY));
-		assertThat(entry.getNewPath(), is("a.txt"));
+			DiffEntry entry = result.get(0);
+			assertThat(entry.getChangeType(), is(ChangeType.MODIFY));
+			assertThat(entry.getNewPath(), is("a.txt"));
+		}
 	}
 
 	@Test
 	public void shouldListDeletionBetweenTwoCommits() throws Exception {
 		// given
-		Git git = new Git(db);
-		File file = writeTrashFile("a.txt", "content");
-		git.add().addFilepattern("a.txt").call();
-		RevCommit c1 = git.commit().setMessage("initial commit").call();
-		delete(file);
-		RevCommit c2 = git.commit().setAll(true).setMessage("delete a.txt")
-				.call();
+		try (Git git = new Git(db);
+				TreeWalk walk = new TreeWalk(db)) {
+			File file = writeTrashFile("a.txt", "content");
+			git.add().addFilepattern("a.txt").call();
+			RevCommit c1 = git.commit().setMessage("initial commit").call();
+			delete(file);
+			RevCommit c2 = git.commit().setAll(true).setMessage("delete a.txt")
+					.call();
 
-		// when
-		TreeWalk walk = new TreeWalk(db);
-		walk.addTree(c1.getTree());
-		walk.addTree(c2.getTree());
-		List<DiffEntry> result = DiffEntry.scan(walk);
+			// when
+			walk.addTree(c1.getTree());
+			walk.addTree(c2.getTree());
+			List<DiffEntry> result = DiffEntry.scan(walk);
 
-		// then
-		assertThat(result, notNullValue());
-		assertThat(Integer.valueOf(result.size()), is(Integer.valueOf(1)));
+			// then
+			assertThat(result, notNullValue());
+			assertThat(Integer.valueOf(result.size()), is(Integer.valueOf(1)));
 
-		DiffEntry entry = result.get(0);
-		assertThat(entry.getOldPath(), is("a.txt"));
-		assertThat(entry.getNewPath(), is(DEV_NULL));
-		assertThat(entry.getChangeType(), is(ChangeType.DELETE));
+			DiffEntry entry = result.get(0);
+			assertThat(entry.getOldPath(), is("a.txt"));
+			assertThat(entry.getNewPath(), is(DEV_NULL));
+			assertThat(entry.getChangeType(), is(ChangeType.DELETE));
+		}
 	}
 
 	@Test
 	public void shouldListModificationInDirWithoutModifiedTrees()
 			throws Exception {
 		// given
-		Git git = new Git(db);
-		File tree = new File(new File(db.getWorkTree(), "a"), "b");
-		FileUtils.mkdirs(tree);
-		File file = new File(tree, "c.txt");
-		FileUtils.createNewFile(file);
-		write(file, "content");
-		git.add().addFilepattern("a").call();
-		RevCommit c1 = git.commit().setMessage("initial commit").call();
-		write(file, "new line");
-		RevCommit c2 = git.commit().setAll(true).setMessage("second commit")
-				.call();
+		try (Git git = new Git(db);
+				TreeWalk walk = new TreeWalk(db)) {
+			File tree = new File(new File(db.getWorkTree(), "a"), "b");
+			FileUtils.mkdirs(tree);
+			File file = new File(tree, "c.txt");
+			FileUtils.createNewFile(file);
+			write(file, "content");
+			git.add().addFilepattern("a").call();
+			RevCommit c1 = git.commit().setMessage("initial commit").call();
+			write(file, "new line");
+			RevCommit c2 = git.commit().setAll(true).setMessage("second commit")
+					.call();
 
-		// when
-		TreeWalk walk = new TreeWalk(db);
-		walk.addTree(c1.getTree());
-		walk.addTree(c2.getTree());
-		walk.setRecursive(true);
-		List<DiffEntry> result = DiffEntry.scan(walk);
+			// when
+			walk.addTree(c1.getTree());
+			walk.addTree(c2.getTree());
+			walk.setRecursive(true);
+			List<DiffEntry> result = DiffEntry.scan(walk);
 
-		// then
-		assertThat(result, notNullValue());
-		assertThat(Integer.valueOf(result.size()), is(Integer.valueOf(1)));
+			// then
+			assertThat(result, notNullValue());
+			assertThat(Integer.valueOf(result.size()), is(Integer.valueOf(1)));
 
-		DiffEntry entry = result.get(0);
-		assertThat(entry.getChangeType(), is(ChangeType.MODIFY));
-		assertThat(entry.getNewPath(), is("a/b/c.txt"));
+			DiffEntry entry = result.get(0);
+			assertThat(entry.getChangeType(), is(ChangeType.MODIFY));
+			assertThat(entry.getNewPath(), is("a/b/c.txt"));
+		}
 	}
 
 	@Test
 	public void shouldListModificationInDirWithModifiedTrees() throws Exception {
 		// given
-		Git git = new Git(db);
-		File tree = new File(new File(db.getWorkTree(), "a"), "b");
-		FileUtils.mkdirs(tree);
-		File file = new File(tree, "c.txt");
-		FileUtils.createNewFile(file);
-		write(file, "content");
-		git.add().addFilepattern("a").call();
-		RevCommit c1 = git.commit().setMessage("initial commit").call();
-		write(file, "new line");
-		RevCommit c2 = git.commit().setAll(true).setMessage("second commit")
-				.call();
+		try (Git git = new Git(db);
+				TreeWalk walk = new TreeWalk(db)) {
+			File tree = new File(new File(db.getWorkTree(), "a"), "b");
+			FileUtils.mkdirs(tree);
+			File file = new File(tree, "c.txt");
+			FileUtils.createNewFile(file);
+			write(file, "content");
+			git.add().addFilepattern("a").call();
+			RevCommit c1 = git.commit().setMessage("initial commit").call();
+			write(file, "new line");
+			RevCommit c2 = git.commit().setAll(true).setMessage("second commit")
+					.call();
 
-		// when
-		TreeWalk walk = new TreeWalk(db);
-		walk.addTree(c1.getTree());
-		walk.addTree(c2.getTree());
-		List<DiffEntry> result = DiffEntry.scan(walk, true);
+			// when
+			walk.addTree(c1.getTree());
+			walk.addTree(c2.getTree());
+			List<DiffEntry> result = DiffEntry.scan(walk, true);
 
-		// then
-		assertThat(result, notNullValue());
-		assertThat(Integer.valueOf(result.size()), is(Integer.valueOf(3)));
+			// then
+			assertThat(result, notNullValue());
+			assertThat(Integer.valueOf(result.size()), is(Integer.valueOf(3)));
 
-		DiffEntry entry = result.get(0);
-		assertThat(entry.getChangeType(), is(ChangeType.MODIFY));
-		assertThat(entry.getNewPath(), is("a"));
+			DiffEntry entry = result.get(0);
+			assertThat(entry.getChangeType(), is(ChangeType.MODIFY));
+			assertThat(entry.getNewPath(), is("a"));
 
-		entry = result.get(1);
-		assertThat(entry.getChangeType(), is(ChangeType.MODIFY));
-		assertThat(entry.getNewPath(), is("a/b"));
+			entry = result.get(1);
+			assertThat(entry.getChangeType(), is(ChangeType.MODIFY));
+			assertThat(entry.getNewPath(), is("a/b"));
 
-		entry = result.get(2);
-		assertThat(entry.getChangeType(), is(ChangeType.MODIFY));
-		assertThat(entry.getNewPath(), is("a/b/c.txt"));
+			entry = result.get(2);
+			assertThat(entry.getChangeType(), is(ChangeType.MODIFY));
+			assertThat(entry.getNewPath(), is("a/b/c.txt"));
+		}
 	}
 
 	@Test
 	public void shouldListChangesInWorkingTree() throws Exception {
 		// given
 		writeTrashFile("a.txt", "content");
-		Git git = new Git(db);
-		git.add().addFilepattern("a.txt").call();
-		RevCommit c = git.commit().setMessage("initial commit").call();
-		writeTrashFile("b.txt", "new line");
+		try (Git git = new Git(db);
+				TreeWalk walk = new TreeWalk(db)) {
+			git.add().addFilepattern("a.txt").call();
+			RevCommit c = git.commit().setMessage("initial commit").call();
+			writeTrashFile("b.txt", "new line");
 
-		// when
-		TreeWalk walk = new TreeWalk(db);
-		walk.addTree(c.getTree());
-		walk.addTree(new FileTreeIterator(db));
-		List<DiffEntry> result = DiffEntry.scan(walk, true);
+			// when
+			walk.addTree(c.getTree());
+			walk.addTree(new FileTreeIterator(db));
+			List<DiffEntry> result = DiffEntry.scan(walk, true);
 
-		// then
-		assertThat(Integer.valueOf(result.size()), is(Integer.valueOf(1)));
-		DiffEntry entry = result.get(0);
+			// then
+			assertThat(Integer.valueOf(result.size()), is(Integer.valueOf(1)));
+			DiffEntry entry = result.get(0);
 
-		assertThat(entry.getChangeType(), is(ChangeType.ADD));
-		assertThat(entry.getNewPath(), is("b.txt"));
+			assertThat(entry.getChangeType(), is(ChangeType.ADD));
+			assertThat(entry.getNewPath(), is("b.txt"));
+		}
 	}
 
 	@Test
 	public void shouldMarkEntriesWhenGivenMarkTreeFilter() throws Exception {
 		// given
-		Git git = new Git(db);
-		RevCommit c1 = git.commit().setMessage("initial commit").call();
-		FileUtils.mkdir(new File(db.getWorkTree(), "b"));
-		writeTrashFile("a.txt", "a");
-		writeTrashFile("b/1.txt", "b1");
-		writeTrashFile("b/2.txt", "b2");
-		writeTrashFile("c.txt", "c");
-		git.add().addFilepattern("a.txt").addFilepattern("b")
-				.addFilepattern("c.txt").call();
-		RevCommit c2 = git.commit().setMessage("second commit").call();
-		TreeFilter filterA = PathFilterGroup.createFromStrings("a.txt");
-		TreeFilter filterB = PathFilterGroup.createFromStrings("b");
-		TreeFilter filterB2 = PathFilterGroup.createFromStrings("b/2.txt");
+		try (Git git = new Git(db);
+				TreeWalk walk = new TreeWalk(db)) {
+			RevCommit c1 = git.commit().setMessage("initial commit").call();
+			FileUtils.mkdir(new File(db.getWorkTree(), "b"));
+			writeTrashFile("a.txt", "a");
+			writeTrashFile("b/1.txt", "b1");
+			writeTrashFile("b/2.txt", "b2");
+			writeTrashFile("c.txt", "c");
+			git.add().addFilepattern("a.txt").addFilepattern("b")
+					.addFilepattern("c.txt").call();
+			RevCommit c2 = git.commit().setMessage("second commit").call();
+			TreeFilter filterA = PathFilterGroup.createFromStrings("a.txt");
+			TreeFilter filterB = PathFilterGroup.createFromStrings("b");
+			TreeFilter filterB2 = PathFilterGroup.createFromStrings("b/2.txt");
 
-		// when
-		TreeWalk walk = new TreeWalk(db);
-		walk.addTree(c1.getTree());
-		walk.addTree(c2.getTree());
-		List<DiffEntry> result = DiffEntry.scan(walk, true, new TreeFilter[] {
-				filterA, filterB, filterB2 });
+			// when
+			walk.addTree(c1.getTree());
+			walk.addTree(c2.getTree());
+			List<DiffEntry> result = DiffEntry.scan(walk, true, new TreeFilter[] {
+					filterA, filterB, filterB2 });
 
-		// then
-		assertThat(result, notNullValue());
-		assertEquals(5, result.size());
+			// then
+			assertThat(result, notNullValue());
+			assertEquals(5, result.size());
 
-		DiffEntry entryA = result.get(0);
-		DiffEntry entryB = result.get(1);
-		DiffEntry entryB1 = result.get(2);
-		DiffEntry entryB2 = result.get(3);
-		DiffEntry entryC = result.get(4);
+			DiffEntry entryA = result.get(0);
+			DiffEntry entryB = result.get(1);
+			DiffEntry entryB1 = result.get(2);
+			DiffEntry entryB2 = result.get(3);
+			DiffEntry entryC = result.get(4);
 
-		assertThat(entryA.getNewPath(), is("a.txt"));
-		assertTrue(entryA.isMarked(0));
-		assertFalse(entryA.isMarked(1));
-		assertFalse(entryA.isMarked(2));
-		assertEquals(1, entryA.getTreeFilterMarks());
+			assertThat(entryA.getNewPath(), is("a.txt"));
+			assertTrue(entryA.isMarked(0));
+			assertFalse(entryA.isMarked(1));
+			assertFalse(entryA.isMarked(2));
+			assertEquals(1, entryA.getTreeFilterMarks());
 
-		assertThat(entryB.getNewPath(), is("b"));
-		assertFalse(entryB.isMarked(0));
-		assertTrue(entryB.isMarked(1));
-		assertTrue(entryB.isMarked(2));
-		assertEquals(6, entryB.getTreeFilterMarks());
+			assertThat(entryB.getNewPath(), is("b"));
+			assertFalse(entryB.isMarked(0));
+			assertTrue(entryB.isMarked(1));
+			assertTrue(entryB.isMarked(2));
+			assertEquals(6, entryB.getTreeFilterMarks());
 
-		assertThat(entryB1.getNewPath(), is("b/1.txt"));
-		assertFalse(entryB1.isMarked(0));
-		assertTrue(entryB1.isMarked(1));
-		assertFalse(entryB1.isMarked(2));
-		assertEquals(2, entryB1.getTreeFilterMarks());
+			assertThat(entryB1.getNewPath(), is("b/1.txt"));
+			assertFalse(entryB1.isMarked(0));
+			assertTrue(entryB1.isMarked(1));
+			assertFalse(entryB1.isMarked(2));
+			assertEquals(2, entryB1.getTreeFilterMarks());
 
-		assertThat(entryB2.getNewPath(), is("b/2.txt"));
-		assertFalse(entryB2.isMarked(0));
-		assertTrue(entryB2.isMarked(1));
-		assertTrue(entryB2.isMarked(2));
-		assertEquals(6, entryB2.getTreeFilterMarks());
+			assertThat(entryB2.getNewPath(), is("b/2.txt"));
+			assertFalse(entryB2.isMarked(0));
+			assertTrue(entryB2.isMarked(1));
+			assertTrue(entryB2.isMarked(2));
+			assertEquals(6, entryB2.getTreeFilterMarks());
 
-		assertThat(entryC.getNewPath(), is("c.txt"));
-		assertFalse(entryC.isMarked(0));
-		assertFalse(entryC.isMarked(1));
-		assertFalse(entryC.isMarked(2));
-		assertEquals(0, entryC.getTreeFilterMarks());
+			assertThat(entryC.getNewPath(), is("c.txt"));
+			assertFalse(entryC.isMarked(0));
+			assertFalse(entryC.isMarked(1));
+			assertFalse(entryC.isMarked(2));
+			assertEquals(0, entryC.getTreeFilterMarks());
+		}
 	}
 
 	@Test(expected = IllegalArgumentException.class)
@@ -339,9 +347,10 @@
 		// given - we don't need anything here
 
 		// when
-		TreeWalk walk = new TreeWalk(db);
-		walk.addTree(new EmptyTreeIterator());
-		DiffEntry.scan(walk);
+		try (TreeWalk walk = new TreeWalk(db)) {
+			walk.addTree(new EmptyTreeIterator());
+			DiffEntry.scan(walk);
+		}
 	}
 
 	@Test(expected = IllegalArgumentException.class)
@@ -350,11 +359,12 @@
 		// given - we don't need anything here
 
 		// when
-		TreeWalk walk = new TreeWalk(db);
-		walk.addTree(new EmptyTreeIterator());
-		walk.addTree(new EmptyTreeIterator());
-		walk.addTree(new EmptyTreeIterator());
-		DiffEntry.scan(walk);
+		try (TreeWalk walk = new TreeWalk(db)) {
+			walk.addTree(new EmptyTreeIterator());
+			walk.addTree(new EmptyTreeIterator());
+			walk.addTree(new EmptyTreeIterator());
+			DiffEntry.scan(walk);
+		}
 	}
 
 	@Test(expected = IllegalArgumentException.class)
@@ -363,46 +373,47 @@
 		// given - we don't need anything here
 
 		// when
-		TreeWalk walk = new TreeWalk(db);
-		walk.addTree(new EmptyTreeIterator());
-		walk.addTree(new EmptyTreeIterator());
-		walk.setRecursive(true);
-		DiffEntry.scan(walk, true);
+		try (TreeWalk walk = new TreeWalk(db)) {
+			walk.addTree(new EmptyTreeIterator());
+			walk.addTree(new EmptyTreeIterator());
+			walk.setRecursive(true);
+			DiffEntry.scan(walk, true);
+		}
 	}
 
 	@Test
 	public void shouldReportFileModeChange() throws Exception {
 		writeTrashFile("a.txt", "content");
-		Git git = new Git(db);
-		git.add().addFilepattern("a.txt").call();
-		RevCommit c1 = git.commit().setMessage("initial commit").call();
-		DirCache cache = db.lockDirCache();
-		DirCacheEditor editor = cache.editor();
-		final TreeWalk walk = new TreeWalk(db);
-		walk.addTree(c1.getTree());
-		walk.setRecursive(true);
-		assertTrue(walk.next());
+		try (Git git = new Git(db);
+				TreeWalk walk = new TreeWalk(db)) {
+			git.add().addFilepattern("a.txt").call();
+			RevCommit c1 = git.commit().setMessage("initial commit").call();
+			DirCache cache = db.lockDirCache();
+			DirCacheEditor editor = cache.editor();
+			walk.addTree(c1.getTree());
+			walk.setRecursive(true);
+			assertTrue(walk.next());
 
-		editor.add(new PathEdit("a.txt") {
-
-			public void apply(DirCacheEntry ent) {
-				ent.setFileMode(FileMode.EXECUTABLE_FILE);
-				ent.setObjectId(walk.getObjectId(0));
-			}
-		});
-		assertTrue(editor.commit());
-		RevCommit c2 = git.commit().setMessage("second commit").call();
-		walk.reset();
-		walk.addTree(c1.getTree());
-		walk.addTree(c2.getTree());
-		List<DiffEntry> diffs = DiffEntry.scan(walk, false);
-		assertEquals(1, diffs.size());
-		DiffEntry diff = diffs.get(0);
-		assertEquals(ChangeType.MODIFY,diff.getChangeType());
-		assertEquals(diff.getOldId(), diff.getNewId());
-		assertEquals("a.txt", diff.getOldPath());
-		assertEquals(diff.getOldPath(), diff.getNewPath());
-		assertEquals(FileMode.EXECUTABLE_FILE, diff.getNewMode());
-		assertEquals(FileMode.REGULAR_FILE, diff.getOldMode());
+			editor.add(new PathEdit("a.txt") {
+				public void apply(DirCacheEntry ent) {
+					ent.setFileMode(FileMode.EXECUTABLE_FILE);
+					ent.setObjectId(walk.getObjectId(0));
+				}
+			});
+			assertTrue(editor.commit());
+			RevCommit c2 = git.commit().setMessage("second commit").call();
+			walk.reset();
+			walk.addTree(c1.getTree());
+			walk.addTree(c2.getTree());
+			List<DiffEntry> diffs = DiffEntry.scan(walk, false);
+			assertEquals(1, diffs.size());
+			DiffEntry diff = diffs.get(0);
+			assertEquals(ChangeType.MODIFY,diff.getChangeType());
+			assertEquals(diff.getOldId(), diff.getNewId());
+			assertEquals("a.txt", diff.getOldPath());
+			assertEquals(diff.getOldPath(), diff.getNewPath());
+			assertEquals(FileMode.EXECUTABLE_FILE, diff.getNewMode());
+			assertEquals(FileMode.REGULAR_FILE, diff.getOldMode());
+		}
 	}
 }
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/DiffFormatterTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/DiffFormatterTest.java
index 58348d5..638a163 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/DiffFormatterTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/DiffFormatterTest.java
@@ -379,30 +379,31 @@
 		File folder = new File(db.getDirectory().getParent(), "folder");
 		FileUtils.mkdir(folder);
 		write(new File(folder, "folder.txt"), "folder");
-		Git git = new Git(db);
-		git.add().addFilepattern(".").call();
-		git.commit().setMessage("Initial commit").call();
-		write(new File(folder, "folder.txt"), "folder change");
+		try (Git git = new Git(db);
+				ByteArrayOutputStream os = new ByteArrayOutputStream();
+				DiffFormatter dfmt = new DiffFormatter(new SafeBufferedOutputStream(os))) {
+			git.add().addFilepattern(".").call();
+			git.commit().setMessage("Initial commit").call();
+			write(new File(folder, "folder.txt"), "folder change");
+			dfmt.setRepository(db);
+			dfmt.setPathFilter(PathFilter.create("folder"));
+			DirCacheIterator oldTree = new DirCacheIterator(db.readDirCache());
+			FileTreeIterator newTree = new FileTreeIterator(db);
 
-		ByteArrayOutputStream os = new ByteArrayOutputStream();
-		DiffFormatter dfmt = new DiffFormatter(new SafeBufferedOutputStream(os));
-		dfmt.setRepository(db);
-		dfmt.setPathFilter(PathFilter.create("folder"));
-		DirCacheIterator oldTree = new DirCacheIterator(db.readDirCache());
-		FileTreeIterator newTree = new FileTreeIterator(db);
-		dfmt.format(oldTree, newTree);
-		dfmt.flush();
+			dfmt.format(oldTree, newTree);
+			dfmt.flush();
 
-		String actual = os.toString("UTF-8");
-		String expected =
- "diff --git a/folder/folder.txt b/folder/folder.txt\n"
-				+ "index 0119635..95c4c65 100644\n"
-				+ "--- a/folder/folder.txt\n" + "+++ b/folder/folder.txt\n"
-				+ "@@ -1 +1 @@\n" + "-folder\n"
-				+ "\\ No newline at end of file\n" + "+folder change\n"
-				+ "\\ No newline at end of file\n";
+			String actual = os.toString("UTF-8");
+			String expected =
+					"diff --git a/folder/folder.txt b/folder/folder.txt\n"
+					+ "index 0119635..95c4c65 100644\n"
+					+ "--- a/folder/folder.txt\n" + "+++ b/folder/folder.txt\n"
+					+ "@@ -1 +1 @@\n" + "-folder\n"
+					+ "\\ No newline at end of file\n" + "+folder change\n"
+					+ "\\ No newline at end of file\n";
 
-		assertEquals(expected, actual);
+			assertEquals(expected, actual);
+		}
 	}
 
 	@Test
@@ -411,29 +412,30 @@
 		File folder = new File(db.getDirectory().getParent(), "folder");
 		FileUtils.mkdir(folder);
 		write(new File(folder, "folder.txt"), "folder");
-		Git git = new Git(db);
-		git.add().addFilepattern(".").call();
-		RevCommit commit = git.commit().setMessage("Initial commit").call();
-		write(new File(folder, "folder.txt"), "folder change");
+		try (Git git = new Git(db);
+				ByteArrayOutputStream os = new ByteArrayOutputStream();
+				DiffFormatter dfmt = new DiffFormatter(new SafeBufferedOutputStream(os))) {
+			git.add().addFilepattern(".").call();
+			RevCommit commit = git.commit().setMessage("Initial commit").call();
+			write(new File(folder, "folder.txt"), "folder change");
 
-		ByteArrayOutputStream os = new ByteArrayOutputStream();
-		DiffFormatter dfmt = new DiffFormatter(new SafeBufferedOutputStream(os));
-		dfmt.setRepository(db);
-		dfmt.setPathFilter(PathFilter.create("folder"));
-		dfmt.format(null, commit.getTree().getId());
-		dfmt.flush();
+			dfmt.setRepository(db);
+			dfmt.setPathFilter(PathFilter.create("folder"));
+			dfmt.format(null, commit.getTree().getId());
+			dfmt.flush();
 
-		String actual = os.toString("UTF-8");
-		String expected = "diff --git a/folder/folder.txt b/folder/folder.txt\n"
-				+ "new file mode 100644\n"
-				+ "index 0000000..0119635\n"
-				+ "--- /dev/null\n"
-				+ "+++ b/folder/folder.txt\n"
-				+ "@@ -0,0 +1 @@\n"
-				+ "+folder\n"
-				+ "\\ No newline at end of file\n";
+			String actual = os.toString("UTF-8");
+			String expected = "diff --git a/folder/folder.txt b/folder/folder.txt\n"
+					+ "new file mode 100644\n"
+					+ "index 0000000..0119635\n"
+					+ "--- /dev/null\n"
+					+ "+++ b/folder/folder.txt\n"
+					+ "@@ -0,0 +1 @@\n"
+					+ "+folder\n"
+					+ "\\ No newline at end of file\n";
 
-		assertEquals(expected, actual);
+			assertEquals(expected, actual);
+		}
 	}
 
 	@Test
@@ -442,43 +444,45 @@
 		File folder = new File(db.getDirectory().getParent(), "folder");
 		FileUtils.mkdir(folder);
 		write(new File(folder, "folder.txt"), "folder");
-		Git git = new Git(db);
-		git.add().addFilepattern(".").call();
-		RevCommit commit = git.commit().setMessage("Initial commit").call();
-		write(new File(folder, "folder.txt"), "folder change");
+		try (Git git = new Git(db);
+				ByteArrayOutputStream os = new ByteArrayOutputStream();
+				DiffFormatter dfmt = new DiffFormatter(new SafeBufferedOutputStream(os));) {
+			git.add().addFilepattern(".").call();
+			RevCommit commit = git.commit().setMessage("Initial commit").call();
+			write(new File(folder, "folder.txt"), "folder change");
 
-		ByteArrayOutputStream os = new ByteArrayOutputStream();
-		DiffFormatter dfmt = new DiffFormatter(new SafeBufferedOutputStream(os));
-		dfmt.setRepository(db);
-		dfmt.setPathFilter(PathFilter.create("folder"));
-		dfmt.format(commit.getTree().getId(), null);
-		dfmt.flush();
+			dfmt.setRepository(db);
+			dfmt.setPathFilter(PathFilter.create("folder"));
+			dfmt.format(commit.getTree().getId(), null);
+			dfmt.flush();
 
-		String actual = os.toString("UTF-8");
-		String expected = "diff --git a/folder/folder.txt b/folder/folder.txt\n"
-				+ "deleted file mode 100644\n"
-				+ "index 0119635..0000000\n"
-				+ "--- a/folder/folder.txt\n"
-				+ "+++ /dev/null\n"
-				+ "@@ -1 +0,0 @@\n"
-				+ "-folder\n"
-				+ "\\ No newline at end of file\n";
+			String actual = os.toString("UTF-8");
+			String expected = "diff --git a/folder/folder.txt b/folder/folder.txt\n"
+					+ "deleted file mode 100644\n"
+					+ "index 0119635..0000000\n"
+					+ "--- a/folder/folder.txt\n"
+					+ "+++ /dev/null\n"
+					+ "@@ -1 +0,0 @@\n"
+					+ "-folder\n"
+					+ "\\ No newline at end of file\n";
 
-		assertEquals(expected, actual);
+			assertEquals(expected, actual);
+		}
 	}
 
 	@Test
 	public void testDiffNullToNull() throws Exception {
-		ByteArrayOutputStream os = new ByteArrayOutputStream();
-		DiffFormatter dfmt = new DiffFormatter(new SafeBufferedOutputStream(os));
-		dfmt.setRepository(db);
-		dfmt.format((AnyObjectId) null, null);
-		dfmt.flush();
+		try (ByteArrayOutputStream os = new ByteArrayOutputStream();
+				DiffFormatter dfmt = new DiffFormatter(new SafeBufferedOutputStream(os))) {
+			dfmt.setRepository(db);
+			dfmt.format((AnyObjectId) null, null);
+			dfmt.flush();
 
-		String actual = os.toString("UTF-8");
-		String expected = "";
+			String actual = os.toString("UTF-8");
+			String expected = "";
 
-		assertEquals(expected, actual);
+			assertEquals(expected, actual);
+		}
 	}
 
 	private static String makeDiffHeader(String pathA, String pathB,
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/PatchIdDiffFormatterTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/PatchIdDiffFormatterTest.java
index ce283ae..024aaa3 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/PatchIdDiffFormatterTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/PatchIdDiffFormatterTest.java
@@ -61,21 +61,22 @@
 		File folder = new File(db.getDirectory().getParent(), "folder");
 		folder.mkdir();
 		write(new File(folder, "folder.txt"), "folder");
-		Git git = new Git(db);
-		git.add().addFilepattern(".").call();
-		git.commit().setMessage("Initial commit").call();
-		write(new File(folder, "folder.txt"), "folder change");
+		try (Git git = new Git(db);
+				PatchIdDiffFormatter df = new PatchIdDiffFormatter()) {
+			git.add().addFilepattern(".").call();
+			git.commit().setMessage("Initial commit").call();
+			write(new File(folder, "folder.txt"), "folder change");
 
-		PatchIdDiffFormatter df = new PatchIdDiffFormatter();
-		df.setRepository(db);
-		df.setPathFilter(PathFilter.create("folder"));
-		DirCacheIterator oldTree = new DirCacheIterator(db.readDirCache());
-		FileTreeIterator newTree = new FileTreeIterator(db);
-		df.format(oldTree, newTree);
-		df.flush();
+			df.setRepository(db);
+			df.setPathFilter(PathFilter.create("folder"));
+			DirCacheIterator oldTree = new DirCacheIterator(db.readDirCache());
+			FileTreeIterator newTree = new FileTreeIterator(db);
+			df.format(oldTree, newTree);
+			df.flush();
 
-		assertEquals("1ff64e0f9333e9b81967c3e8d7a81362b14d5441", df
-				.getCalulatedPatchId().name());
+			assertEquals("1ff64e0f9333e9b81967c3e8d7a81362b14d5441", df
+					.getCalulatedPatchId().name());
+		}
 	}
 
 	@Test
@@ -84,37 +85,40 @@
 		File folder = new File(db.getDirectory().getParent(), "folder");
 		folder.mkdir();
 		write(new File(folder, "folder.txt"), "\n\n\n\nfolder");
-		Git git = new Git(db);
-		git.add().addFilepattern(".").call();
-		git.commit().setMessage("Initial commit").call();
-		write(new File(folder, "folder.txt"), "\n\n\n\nfolder change");
+		try (Git git = new Git(db)) {
+			git.add().addFilepattern(".").call();
+			git.commit().setMessage("Initial commit").call();
+			write(new File(folder, "folder.txt"), "\n\n\n\nfolder change");
 
-		PatchIdDiffFormatter df = new PatchIdDiffFormatter();
-		df.setRepository(db);
-		df.setPathFilter(PathFilter.create("folder"));
-		DirCacheIterator oldTree = new DirCacheIterator(db.readDirCache());
-		FileTreeIterator newTree = new FileTreeIterator(db);
-		df.format(oldTree, newTree);
-		df.flush();
+			try (PatchIdDiffFormatter df = new PatchIdDiffFormatter()) {
+				df.setRepository(db);
+				df.setPathFilter(PathFilter.create("folder"));
+				DirCacheIterator oldTree = new DirCacheIterator(db.readDirCache());
+				FileTreeIterator newTree = new FileTreeIterator(db);
+				df.format(oldTree, newTree);
+				df.flush();
 
-		assertEquals("08fca5ac531383eb1da8bf6b6f7cf44411281407", df
-				.getCalulatedPatchId().name());
+				assertEquals("08fca5ac531383eb1da8bf6b6f7cf44411281407", df
+						.getCalulatedPatchId().name());
+			}
 
-		write(new File(folder, "folder.txt"), "a\n\n\n\nfolder");
-		git.add().addFilepattern(".").call();
-		git.commit().setMessage("Initial commit").call();
-		write(new File(folder, "folder.txt"), "a\n\n\n\nfolder change");
+			write(new File(folder, "folder.txt"), "a\n\n\n\nfolder");
+			git.add().addFilepattern(".").call();
+			git.commit().setMessage("Initial commit").call();
+			write(new File(folder, "folder.txt"), "a\n\n\n\nfolder change");
 
-		df = new PatchIdDiffFormatter();
-		df.setRepository(db);
-		df.setPathFilter(PathFilter.create("folder"));
-		oldTree = new DirCacheIterator(db.readDirCache());
-		newTree = new FileTreeIterator(db);
-		df.format(oldTree, newTree);
-		df.flush();
+			try (PatchIdDiffFormatter df = new PatchIdDiffFormatter()) {
+				df.setRepository(db);
+				df.setPathFilter(PathFilter.create("folder"));
+				DirCacheIterator oldTree = new DirCacheIterator(db.readDirCache());
+				FileTreeIterator newTree = new FileTreeIterator(db);
+				df.format(oldTree, newTree);
+				df.flush();
 
-		assertEquals("08fca5ac531383eb1da8bf6b6f7cf44411281407", df
-				.getCalulatedPatchId().name());
+				assertEquals("08fca5ac531383eb1da8bf6b6f7cf44411281407", df
+						.getCalulatedPatchId().name());
+			}
+		}
 	}
 
 }
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/RenameDetectorTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/RenameDetectorTest.java
index e83ef77..4315be9 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/RenameDetectorTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/RenameDetectorTest.java
@@ -48,6 +48,7 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
+import java.util.Arrays;
 import java.util.List;
 
 import org.eclipse.jgit.diff.DiffEntry.ChangeType;
@@ -227,6 +228,19 @@
 	}
 
 	@Test
+	public void testExactRename_UnstagedFile() throws Exception {
+		ObjectId aId = blob("foo");
+		DiffEntry a = DiffEntry.delete(PATH_A, aId);
+		DiffEntry b = DiffEntry.add(PATH_B, aId);
+
+		rd.addAll(Arrays.asList(a, b));
+		List<DiffEntry> entries = rd.compute();
+
+		assertEquals(1, entries.size());
+		assertRename(a, b, 100, entries.get(0));
+	}
+
+	@Test
 	public void testInexactRename_OnePair() throws Exception {
 		ObjectId aId = blob("foo\nbar\nbaz\nblarg\n");
 		ObjectId bId = blob("foo\nbar\nbaz\nblah\n");
@@ -430,6 +444,23 @@
 	}
 
 	@Test
+	public void testNoRenames_UntrackedFile() throws Exception {
+		ObjectId aId = blob("foo");
+		ObjectId bId = ObjectId
+				.fromString("3049eb6eee7e1318f4e78e799bf33f1e54af9cbf");
+
+		DiffEntry a = DiffEntry.delete(PATH_A, aId);
+		DiffEntry b = DiffEntry.add(PATH_B, bId);
+
+		rd.addAll(Arrays.asList(a, b));
+		List<DiffEntry> entries = rd.compute();
+
+		assertEquals(2, entries.size());
+		assertSame(a, entries.get(0));
+		assertSame(b, entries.get(1));
+	}
+
+	@Test
 	public void testBreakModify_BreakAll() throws Exception {
 		ObjectId aId = blob("foo");
 		ObjectId bId = blob("bar");
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheBasicTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheBasicTest.java
index 0c1baab..014406e 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheBasicTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheBasicTest.java
@@ -255,9 +255,11 @@
 		DirCacheBuilder b = dc.builder();
 		DirCacheEntry e = new DirCacheEntry(path);
 		e.setFileMode(FileMode.REGULAR_FILE);
-		e.setObjectId(new ObjectInserter.Formatter().idFor(
-				Constants.OBJ_BLOB,
-				Constants.encode(path)));
+		try (ObjectInserter.Formatter formatter = new ObjectInserter.Formatter()) {
+			e.setObjectId(formatter.idFor(
+					Constants.OBJ_BLOB,
+					Constants.encode(path)));
+		}
 		b.add(e);
 		b.commit();
 		db.readDirCache();
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheBuilderIteratorTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheBuilderIteratorTest.java
index 8561fdf..2b10887 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheBuilderIteratorTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheBuilderIteratorTest.java
@@ -78,23 +78,24 @@
 
 		final int expIdx = 2;
 		final DirCacheBuilder b = dc.builder();
-		final TreeWalk tw = new TreeWalk(db);
-		tw.addTree(new DirCacheBuildIterator(b));
-		tw.setRecursive(true);
-		tw.setFilter(PathFilterGroup.createFromStrings(Collections
-				.singleton(paths[expIdx])));
+		try (final TreeWalk tw = new TreeWalk(db)) {
+			tw.addTree(new DirCacheBuildIterator(b));
+			tw.setRecursive(true);
+			tw.setFilter(PathFilterGroup.createFromStrings(Collections
+					.singleton(paths[expIdx])));
 
-		assertTrue("found " + paths[expIdx], tw.next());
-		final DirCacheIterator c = tw.getTree(0, DirCacheIterator.class);
-		assertNotNull(c);
-		assertEquals(expIdx, c.ptr);
-		assertSame(ents[expIdx], c.getDirCacheEntry());
-		assertEquals(paths[expIdx], tw.getPathString());
-		assertEquals(mode.getBits(), tw.getRawMode(0));
-		assertSame(mode, tw.getFileMode(0));
-		b.add(c.getDirCacheEntry());
+			assertTrue("found " + paths[expIdx], tw.next());
+			final DirCacheIterator c = tw.getTree(0, DirCacheIterator.class);
+			assertNotNull(c);
+			assertEquals(expIdx, c.ptr);
+			assertSame(ents[expIdx], c.getDirCacheEntry());
+			assertEquals(paths[expIdx], tw.getPathString());
+			assertEquals(mode.getBits(), tw.getRawMode(0));
+			assertSame(mode, tw.getFileMode(0));
+			b.add(c.getDirCacheEntry());
 
-		assertFalse("no more entries", tw.next());
+			assertFalse("no more entries", tw.next());
+		}
 
 		b.finish();
 		assertEquals(ents.length, dc.getEntryCount());
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheCGitCompatabilityTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheCGitCompatabilityTest.java
index 7f58a1c..3e78046 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheCGitCompatabilityTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheCGitCompatabilityTest.java
@@ -98,17 +98,18 @@
 		assertEquals(ls.size(), dc.getEntryCount());
 		{
 			final Iterator<CGitIndexRecord> rItr = ls.values().iterator();
-			final TreeWalk tw = new TreeWalk(db);
-			tw.setRecursive(true);
-			tw.addTree(new DirCacheIterator(dc));
-			while (rItr.hasNext()) {
-				final DirCacheIterator dcItr;
+			try (final TreeWalk tw = new TreeWalk(db)) {
+				tw.setRecursive(true);
+				tw.addTree(new DirCacheIterator(dc));
+				while (rItr.hasNext()) {
+					final DirCacheIterator dcItr;
 
-				assertTrue(tw.next());
-				dcItr = tw.getTree(0, DirCacheIterator.class);
-				assertNotNull(dcItr);
+					assertTrue(tw.next());
+					dcItr = tw.getTree(0, DirCacheIterator.class);
+					assertNotNull(dcItr);
 
-				assertEqual(rItr.next(), dcItr.getDirCacheEntry());
+					assertEqual(rItr.next(), dcItr.getDirCacheEntry());
+				}
 			}
 		}
 	}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheEntryTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheEntryTest.java
index e159ed9..05fa007 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheEntryTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheEntryTest.java
@@ -69,15 +69,17 @@
 		assertFalse(isValidPath("a\u0000b"));
 	}
 
-	private static boolean isValidPath(final String path) {
+	@SuppressWarnings("unused")
+	private static boolean isValidPath(String path) {
 		try {
-			DirCacheCheckout.checkValidPath(path);
+			new DirCacheEntry(path);
 			return true;
 		} catch (InvalidPathException e) {
 			return false;
 		}
 	}
 
+	@SuppressWarnings("unused")
 	@Test
 	public void testCreate_ByStringPath() {
 		assertEquals("a", new DirCacheEntry("a").getPathString());
@@ -91,6 +93,7 @@
 		}
 	}
 
+	@SuppressWarnings("unused")
 	@Test
 	public void testCreate_ByStringPathAndStage() {
 		DirCacheEntry e;
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheIteratorTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheIteratorTest.java
index af1c8a3..dd242e5 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheIteratorTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheIteratorTest.java
@@ -76,9 +76,10 @@
 		final DirCache dc = DirCache.newInCore();
 		assertEquals(0, dc.getEntryCount());
 
-		final TreeWalk tw = new TreeWalk(db);
-		tw.addTree(new DirCacheIterator(dc));
-		assertFalse(tw.next());
+		try (final TreeWalk tw = new TreeWalk(db)) {
+			tw.addTree(new DirCacheIterator(dc));
+			assertFalse(tw.next());
+		}
 	}
 
 	@Test
@@ -125,19 +126,20 @@
 		b.finish();
 
 		final DirCacheIterator i = new DirCacheIterator(dc);
-		final TreeWalk tw = new TreeWalk(db);
-		tw.addTree(i);
-		int pathIdx = 0;
-		while (tw.next()) {
-			assertSame(i, tw.getTree(0, DirCacheIterator.class));
-			assertEquals(pathIdx, i.ptr);
-			assertSame(ents[pathIdx], i.getDirCacheEntry());
-			assertEquals(paths[pathIdx], tw.getPathString());
-			assertEquals(modes[pathIdx].getBits(), tw.getRawMode(0));
-			assertSame(modes[pathIdx], tw.getFileMode(0));
-			pathIdx++;
+		try (final TreeWalk tw = new TreeWalk(db)) {
+			tw.addTree(i);
+			int pathIdx = 0;
+			while (tw.next()) {
+				assertSame(i, tw.getTree(0, DirCacheIterator.class));
+				assertEquals(pathIdx, i.ptr);
+				assertSame(ents[pathIdx], i.getDirCacheEntry());
+				assertEquals(paths[pathIdx], tw.getPathString());
+				assertEquals(modes[pathIdx].getBits(), tw.getRawMode(0));
+				assertSame(modes[pathIdx], tw.getFileMode(0));
+				pathIdx++;
+			}
+			assertEquals(paths.length, pathIdx);
 		}
-		assertEquals(paths.length, pathIdx);
 	}
 
 	@Test
@@ -162,26 +164,27 @@
 		final int expPos[] = { 0, -1, 4 };
 
 		final DirCacheIterator i = new DirCacheIterator(dc);
-		final TreeWalk tw = new TreeWalk(db);
-		tw.addTree(i);
-		tw.setRecursive(false);
-		int pathIdx = 0;
-		while (tw.next()) {
-			assertSame(i, tw.getTree(0, DirCacheIterator.class));
-			assertEquals(expModes[pathIdx].getBits(), tw.getRawMode(0));
-			assertSame(expModes[pathIdx], tw.getFileMode(0));
-			assertEquals(expPaths[pathIdx], tw.getPathString());
+		try (final TreeWalk tw = new TreeWalk(db)) {
+			tw.addTree(i);
+			tw.setRecursive(false);
+			int pathIdx = 0;
+			while (tw.next()) {
+				assertSame(i, tw.getTree(0, DirCacheIterator.class));
+				assertEquals(expModes[pathIdx].getBits(), tw.getRawMode(0));
+				assertSame(expModes[pathIdx], tw.getFileMode(0));
+				assertEquals(expPaths[pathIdx], tw.getPathString());
 
-			if (expPos[pathIdx] >= 0) {
-				assertEquals(expPos[pathIdx], i.ptr);
-				assertSame(ents[expPos[pathIdx]], i.getDirCacheEntry());
-			} else {
-				assertSame(FileMode.TREE, tw.getFileMode(0));
+				if (expPos[pathIdx] >= 0) {
+					assertEquals(expPos[pathIdx], i.ptr);
+					assertSame(ents[expPos[pathIdx]], i.getDirCacheEntry());
+				} else {
+					assertSame(FileMode.TREE, tw.getFileMode(0));
+				}
+
+				pathIdx++;
 			}
-
-			pathIdx++;
+			assertEquals(expPaths.length, pathIdx);
 		}
-		assertEquals(expPaths.length, pathIdx);
 	}
 
 	@Test
@@ -202,21 +205,22 @@
 		b.finish();
 
 		final DirCacheIterator i = new DirCacheIterator(dc);
-		final TreeWalk tw = new TreeWalk(db);
-		tw.addTree(i);
-		tw.setRecursive(true);
-		int pathIdx = 0;
-		while (tw.next()) {
-			final DirCacheIterator c = tw.getTree(0, DirCacheIterator.class);
-			assertNotNull(c);
-			assertEquals(pathIdx, c.ptr);
-			assertSame(ents[pathIdx], c.getDirCacheEntry());
-			assertEquals(paths[pathIdx], tw.getPathString());
-			assertEquals(mode.getBits(), tw.getRawMode(0));
-			assertSame(mode, tw.getFileMode(0));
-			pathIdx++;
+		try (final TreeWalk tw = new TreeWalk(db)) {
+			tw.addTree(i);
+			tw.setRecursive(true);
+			int pathIdx = 0;
+			while (tw.next()) {
+				final DirCacheIterator c = tw.getTree(0, DirCacheIterator.class);
+				assertNotNull(c);
+				assertEquals(pathIdx, c.ptr);
+				assertSame(ents[pathIdx], c.getDirCacheEntry());
+				assertEquals(paths[pathIdx], tw.getPathString());
+				assertEquals(mode.getBits(), tw.getRawMode(0));
+				assertSame(mode, tw.getFileMode(0));
+				pathIdx++;
+			}
+			assertEquals(paths.length, pathIdx);
 		}
-		assertEquals(paths.length, pathIdx);
 	}
 
 	@Test
@@ -236,21 +240,22 @@
 			b.add(ents[i]);
 		b.finish();
 
-		final TreeWalk tw = new TreeWalk(db);
-		tw.addTree(new DirCacheIterator(dc));
-		tw.setRecursive(true);
-		int pathIdx = 0;
-		while (tw.next()) {
-			final DirCacheIterator c = tw.getTree(0, DirCacheIterator.class);
-			assertNotNull(c);
-			assertEquals(pathIdx, c.ptr);
-			assertSame(ents[pathIdx], c.getDirCacheEntry());
-			assertEquals(paths[pathIdx], tw.getPathString());
-			assertEquals(mode.getBits(), tw.getRawMode(0));
-			assertSame(mode, tw.getFileMode(0));
-			pathIdx++;
+		try (final TreeWalk tw = new TreeWalk(db)) {
+			tw.addTree(new DirCacheIterator(dc));
+			tw.setRecursive(true);
+			int pathIdx = 0;
+			while (tw.next()) {
+				final DirCacheIterator c = tw.getTree(0, DirCacheIterator.class);
+				assertNotNull(c);
+				assertEquals(pathIdx, c.ptr);
+				assertSame(ents[pathIdx], c.getDirCacheEntry());
+				assertEquals(paths[pathIdx], tw.getPathString());
+				assertEquals(mode.getBits(), tw.getRawMode(0));
+				assertSame(mode, tw.getFileMode(0));
+				pathIdx++;
+			}
+			assertEquals(paths.length, pathIdx);
 		}
-		assertEquals(paths.length, pathIdx);
 	}
 
 	@Test
@@ -397,22 +402,23 @@
 			b.add(ents[i]);
 		b.finish();
 
-		final TreeWalk tw = new TreeWalk(db);
-		for (int victimIdx = 0; victimIdx < paths.length; victimIdx++) {
-			tw.reset();
-			tw.addTree(new DirCacheIterator(dc));
-			tw.setFilter(PathFilterGroup.createFromStrings(Collections
-					.singleton(paths[victimIdx])));
-			tw.setRecursive(tw.getFilter().shouldBeRecursive());
-			assertTrue(tw.next());
-			final DirCacheIterator c = tw.getTree(0, DirCacheIterator.class);
-			assertNotNull(c);
-			assertEquals(victimIdx, c.ptr);
-			assertSame(ents[victimIdx], c.getDirCacheEntry());
-			assertEquals(paths[victimIdx], tw.getPathString());
-			assertEquals(mode.getBits(), tw.getRawMode(0));
-			assertSame(mode, tw.getFileMode(0));
-			assertFalse(tw.next());
+		try (final TreeWalk tw = new TreeWalk(db)) {
+			for (int victimIdx = 0; victimIdx < paths.length; victimIdx++) {
+				tw.reset();
+				tw.addTree(new DirCacheIterator(dc));
+				tw.setFilter(PathFilterGroup.createFromStrings(Collections
+						.singleton(paths[victimIdx])));
+				tw.setRecursive(tw.getFilter().shouldBeRecursive());
+				assertTrue(tw.next());
+				final DirCacheIterator c = tw.getTree(0, DirCacheIterator.class);
+				assertNotNull(c);
+				assertEquals(victimIdx, c.ptr);
+				assertSame(ents[victimIdx], c.getDirCacheEntry());
+				assertEquals(paths[victimIdx], tw.getPathString());
+				assertEquals(mode.getBits(), tw.getRawMode(0));
+				assertSame(mode, tw.getFileMode(0));
+				assertFalse(tw.next());
+			}
 		}
 	}
 
@@ -424,18 +430,19 @@
 		final DirCache dc = DirCache.read(path, FS.DETECTED);
 		assertEquals(2, dc.getEntryCount());
 
-		final TreeWalk tw = new TreeWalk(db);
-		tw.setRecursive(true);
-		tw.addTree(new DirCacheIterator(dc));
+		try (final TreeWalk tw = new TreeWalk(db)) {
+			tw.setRecursive(true);
+			tw.addTree(new DirCacheIterator(dc));
 
-		assertTrue(tw.next());
-		assertEquals("a/a", tw.getPathString());
-		assertSame(FileMode.REGULAR_FILE, tw.getFileMode(0));
+			assertTrue(tw.next());
+			assertEquals("a/a", tw.getPathString());
+			assertSame(FileMode.REGULAR_FILE, tw.getFileMode(0));
 
-		assertTrue(tw.next());
-		assertEquals("q", tw.getPathString());
-		assertSame(FileMode.REGULAR_FILE, tw.getFileMode(0));
+			assertTrue(tw.next());
+			assertEquals("q", tw.getPathString());
+			assertSame(FileMode.REGULAR_FILE, tw.getFileMode(0));
 
-		assertFalse(tw.next());
+			assertFalse(tw.next());
+		}
 	}
 }
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCachePathEditTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCachePathEditTest.java
index 63ec858..c85e156 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCachePathEditTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCachePathEditTest.java
@@ -43,11 +43,13 @@
 package org.eclipse.jgit.dircache;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
 
 import java.util.ArrayList;
 import java.util.List;
 
 import org.eclipse.jgit.dircache.DirCacheEditor.PathEdit;
+import org.eclipse.jgit.errors.DirCacheNameConflictException;
 import org.eclipse.jgit.lib.FileMode;
 import org.eclipse.jgit.lib.ObjectId;
 import org.junit.Test;
@@ -154,6 +156,125 @@
 		assertEquals(DirCacheEntry.STAGE_3, entries.get(2).getStage());
 	}
 
+	@Test
+	public void testFileReplacesTree() throws Exception {
+		DirCache dc = DirCache.newInCore();
+		DirCacheEditor editor = dc.editor();
+		editor.add(new AddEdit("a"));
+		editor.add(new AddEdit("b/c"));
+		editor.add(new AddEdit("b/d"));
+		editor.add(new AddEdit("e"));
+		editor.finish();
+
+		editor = dc.editor();
+		editor.add(new AddEdit("b"));
+		editor.finish();
+
+		assertEquals(3, dc.getEntryCount());
+		assertEquals("a", dc.getEntry(0).getPathString());
+		assertEquals("b", dc.getEntry(1).getPathString());
+		assertEquals("e", dc.getEntry(2).getPathString());
+
+		dc.clear();
+		editor = dc.editor();
+		editor.add(new AddEdit("A.c"));
+		editor.add(new AddEdit("A/c"));
+		editor.add(new AddEdit("A0c"));
+		editor.finish();
+
+		editor = dc.editor();
+		editor.add(new AddEdit("A"));
+		editor.finish();
+		assertEquals(3, dc.getEntryCount());
+		assertEquals("A", dc.getEntry(0).getPathString());
+		assertEquals("A.c", dc.getEntry(1).getPathString());
+		assertEquals("A0c", dc.getEntry(2).getPathString());
+	}
+
+	@Test
+	public void testTreeReplacesFile() throws Exception {
+		DirCache dc = DirCache.newInCore();
+		DirCacheEditor editor = dc.editor();
+		editor.add(new AddEdit("a"));
+		editor.add(new AddEdit("ab"));
+		editor.add(new AddEdit("b"));
+		editor.add(new AddEdit("e"));
+		editor.finish();
+
+		editor = dc.editor();
+		editor.add(new AddEdit("b/c/d/f"));
+		editor.add(new AddEdit("b/g/h/i"));
+		editor.finish();
+
+		assertEquals(5, dc.getEntryCount());
+		assertEquals("a", dc.getEntry(0).getPathString());
+		assertEquals("ab", dc.getEntry(1).getPathString());
+		assertEquals("b/c/d/f", dc.getEntry(2).getPathString());
+		assertEquals("b/g/h/i", dc.getEntry(3).getPathString());
+		assertEquals("e", dc.getEntry(4).getPathString());
+	}
+
+	@Test
+	public void testDuplicateFiles() throws Exception {
+		DirCache dc = DirCache.newInCore();
+		DirCacheEditor editor = dc.editor();
+		editor.add(new AddEdit("a"));
+		editor.add(new AddEdit("a"));
+
+		try {
+			editor.finish();
+			fail("Expected DirCacheNameConflictException to be thrown");
+		} catch (DirCacheNameConflictException e) {
+			assertEquals("a a", e.getMessage());
+			assertEquals("a", e.getPath1());
+			assertEquals("a", e.getPath2());
+		}
+	}
+
+	@Test
+	public void testFileOverlapsTree() throws Exception {
+		DirCache dc = DirCache.newInCore();
+		DirCacheEditor editor = dc.editor();
+		editor.add(new AddEdit("a"));
+		editor.add(new AddEdit("a/b").setReplace(false));
+		try {
+			editor.finish();
+			fail("Expected DirCacheNameConflictException to be thrown");
+		} catch (DirCacheNameConflictException e) {
+			assertEquals("a a/b", e.getMessage());
+			assertEquals("a", e.getPath1());
+			assertEquals("a/b", e.getPath2());
+		}
+
+		editor = dc.editor();
+		editor.add(new AddEdit("A.c"));
+		editor.add(new AddEdit("A/c").setReplace(false));
+		editor.add(new AddEdit("A0c"));
+		editor.add(new AddEdit("A"));
+		try {
+			editor.finish();
+			fail("Expected DirCacheNameConflictException to be thrown");
+		} catch (DirCacheNameConflictException e) {
+			assertEquals("A A/c", e.getMessage());
+			assertEquals("A", e.getPath1());
+			assertEquals("A/c", e.getPath2());
+		}
+
+		editor = dc.editor();
+		editor.add(new AddEdit("A.c"));
+		editor.add(new AddEdit("A/b/c/d").setReplace(false));
+		editor.add(new AddEdit("A/b/c"));
+		editor.add(new AddEdit("A0c"));
+		try {
+			editor.finish();
+			fail("Expected DirCacheNameConflictException to be thrown");
+		} catch (DirCacheNameConflictException e) {
+			assertEquals("A/b/c A/b/c/d", e.getMessage());
+			assertEquals("A/b/c", e.getPath1());
+			assertEquals("A/b/c/d", e.getPath2());
+		}
+	}
+
 	private static DirCacheEntry createEntry(String path, int stage) {
 		DirCacheEntry entry = new DirCacheEntry(path, stage);
 		entry.setFileMode(FileMode.REGULAR_FILE);
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/gitrepo/ManifestParserTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/gitrepo/ManifestParserTest.java
index 5ed4268..30b3df1 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/gitrepo/ManifestParserTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/gitrepo/ManifestParserTest.java
@@ -43,8 +43,9 @@
 package org.eclipse.jgit.gitrepo;
 
 import static org.junit.Assert.assertTrue;
+import static java.nio.charset.StandardCharsets.UTF_8;
 
-import java.io.StringBufferInputStream;
+import java.io.ByteArrayInputStream;
 import java.util.HashSet;
 import java.util.Set;
 
@@ -77,7 +78,7 @@
 
 		ManifestParser parser = new ManifestParser(
 				null, null, "master", baseUrl, null, null);
-		parser.read(new StringBufferInputStream(xmlContent.toString()));
+		parser.read(new ByteArrayInputStream(xmlContent.toString().getBytes(UTF_8)));
 		// Unfiltered projects should have them all.
 		results.clear();
 		results.add("foo");
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/gitrepo/RepoCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/gitrepo/RepoCommandTest.java
index 66e7256..77ef1a6 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/gitrepo/RepoCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/gitrepo/RepoCommandTest.java
@@ -56,6 +56,8 @@
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.storage.file.FileBasedConfig;
+import org.eclipse.jgit.util.FS;
 import org.junit.Test;
 
 public class RepoCommandTest extends RepositoryTestCase {
@@ -80,38 +82,42 @@
 		super.setUp();
 
 		defaultDb = createWorkRepository();
-		Git git = new Git(defaultDb);
-		JGitTestUtil.writeTrashFile(defaultDb, "hello.txt", "branch world");
-		git.add().addFilepattern("hello.txt").call();
-		oldCommitId = git.commit().setMessage("Initial commit").call().getId();
-		git.checkout().setName(BRANCH).setCreateBranch(true).call();
-		git.checkout().setName("master").call();
-		git.tag().setName(TAG).call();
-		JGitTestUtil.writeTrashFile(defaultDb, "hello.txt", "master world");
-		git.add().addFilepattern("hello.txt").call();
-		git.commit().setMessage("Second commit").call();
-		addRepoToClose(defaultDb);
+		try (Git git = new Git(defaultDb)) {
+			JGitTestUtil.writeTrashFile(defaultDb, "hello.txt", "branch world");
+			git.add().addFilepattern("hello.txt").call();
+			oldCommitId = git.commit().setMessage("Initial commit").call().getId();
+			git.checkout().setName(BRANCH).setCreateBranch(true).call();
+			git.checkout().setName("master").call();
+			git.tag().setName(TAG).call();
+			JGitTestUtil.writeTrashFile(defaultDb, "hello.txt", "master world");
+			git.add().addFilepattern("hello.txt").call();
+			git.commit().setMessage("Second commit").call();
+			addRepoToClose(defaultDb);
+		}
 
 		notDefaultDb = createWorkRepository();
-		git = new Git(notDefaultDb);
-		JGitTestUtil.writeTrashFile(notDefaultDb, "world.txt", "hello");
-		git.add().addFilepattern("world.txt").call();
-		git.commit().setMessage("Initial commit").call();
-		addRepoToClose(notDefaultDb);
+		try (Git git = new Git(notDefaultDb)) {
+			JGitTestUtil.writeTrashFile(notDefaultDb, "world.txt", "hello");
+			git.add().addFilepattern("world.txt").call();
+			git.commit().setMessage("Initial commit").call();
+			addRepoToClose(notDefaultDb);
+		}
 
 		groupADb = createWorkRepository();
-		git = new Git(groupADb);
-		JGitTestUtil.writeTrashFile(groupADb, "a.txt", "world");
-		git.add().addFilepattern("a.txt").call();
-		git.commit().setMessage("Initial commit").call();
-		addRepoToClose(groupADb);
+		try (Git git = new Git(groupADb)) {
+			JGitTestUtil.writeTrashFile(groupADb, "a.txt", "world");
+			git.add().addFilepattern("a.txt").call();
+			git.commit().setMessage("Initial commit").call();
+			addRepoToClose(groupADb);
+		}
 
 		groupBDb = createWorkRepository();
-		git = new Git(groupBDb);
-		JGitTestUtil.writeTrashFile(groupBDb, "b.txt", "world");
-		git.add().addFilepattern("b.txt").call();
-		git.commit().setMessage("Initial commit").call();
-		addRepoToClose(groupBDb);
+		try (Git git = new Git(groupBDb)) {
+			JGitTestUtil.writeTrashFile(groupBDb, "b.txt", "world");
+			git.add().addFilepattern("b.txt").call();
+			git.commit().setMessage("Initial commit").call();
+			addRepoToClose(groupBDb);
+		}
 
 		resolveRelativeUris();
 	}
@@ -407,6 +413,7 @@
 					.append("<project path=\"foo\" name=\"").append(defaultUri)
 					.append("\" revision=\"").append(BRANCH).append("\" >")
 					.append("<copyfile src=\"hello.txt\" dest=\"Hello\" />")
+					.append("<copyfile src=\"hello.txt\" dest=\"foo/Hello\" />")
 					.append("</project>").append("</manifest>");
 			JGitTestUtil.writeTrashFile(tempDb, "manifest.xml",
 					xmlContent.toString());
@@ -421,8 +428,12 @@
 					.getRepository();
 			// The Hello file should exist
 			File hello = new File(localDb.getWorkTree(), "Hello");
-			localDb.close();
 			assertTrue("The Hello file should exist", hello.exists());
+			// The foo/Hello file should be skipped.
+			File foohello = new File(localDb.getWorkTree(), "foo/Hello");
+			assertFalse(
+					"The foo/Hello file should be skipped", foohello.exists());
+			localDb.close();
 			// The content of Hello file should be expected
 			BufferedReader reader = new BufferedReader(new FileReader(hello));
 			String content = reader.readLine();
@@ -692,6 +703,54 @@
 		}
 	}
 
+	@Test
+	public void testRecordRemoteBranch() throws Exception {
+		try (
+				Repository remoteDb = createBareRepository();
+				Repository tempDb = createWorkRepository()) {
+			StringBuilder xmlContent = new StringBuilder();
+			xmlContent
+				.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n")
+				.append("<manifest>")
+				.append("<remote name=\"remote1\" fetch=\".\" />")
+				.append("<default revision=\"master\" remote=\"remote1\" />")
+				.append("<project path=\"with-branch\" ")
+					.append("revision=\"master\" ")
+					.append("name=\"").append(notDefaultUri).append("\" />")
+				.append("<project path=\"with-long-branch\" ")
+					.append("revision=\"refs/heads/master\" ")
+					.append("name=\"").append(defaultUri).append("\" />")
+				.append("</manifest>");
+			JGitTestUtil.writeTrashFile(tempDb, "manifest.xml",
+				xmlContent.toString());
+
+			RepoCommand command = new RepoCommand(remoteDb);
+			command.setPath(tempDb.getWorkTree().getAbsolutePath() + "/manifest.xml")
+				.setURI(rootUri)
+				.setRecordRemoteBranch(true)
+				.call();
+			// Clone it
+			File directory = createTempDirectory("testBareRepo");
+			try (Repository localDb = Git.cloneRepository()
+					.setDirectory(directory)
+					.setURI(remoteDb.getDirectory().toURI().toString()).call()
+					.getRepository();) {
+				// The .gitmodules file should exist
+				File gitmodules = new File(localDb.getWorkTree(),
+						".gitmodules");
+				assertTrue("The .gitmodules file should exist",
+						gitmodules.exists());
+				FileBasedConfig c = new FileBasedConfig(gitmodules,
+						FS.DETECTED);
+				c.load();
+				assertEquals("standard branches work", "master",
+						c.getString("submodule", "with-branch", "branch"));
+				assertEquals("long branches work", "refs/heads/master",
+						c.getString("submodule", "with-long-branch", "branch"));
+			}
+		}
+	}
+
 	private void resolveRelativeUris() {
 		// Find the longest common prefix ends with "/" as rootUri.
 		defaultUri = defaultDb.getDirectory().toURI().toString();
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/FastIgnoreRuleTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/FastIgnoreRuleTest.java
index 2c04787..480e326 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
@@ -54,6 +54,7 @@
 
 	@Test
 	public void testSimpleCharClass() {
+		assertMatched("][a]", "]a");
 		assertMatched("[a]", "a");
 		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 9722ac6..c026efc 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
@@ -55,7 +55,6 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 
-import org.eclipse.jgit.errors.CorruptObjectException;
 import org.eclipse.jgit.ignore.IgnoreNode.MatchResult;
 import org.eclipse.jgit.junit.RepositoryTestCase;
 import org.eclipse.jgit.lib.FileMode;
@@ -63,6 +62,7 @@
 import org.eclipse.jgit.treewalk.TreeWalk;
 import org.eclipse.jgit.treewalk.WorkingTreeIterator;
 import org.eclipse.jgit.util.FileUtils;
+import org.eclipse.jgit.util.SystemReader;
 import org.junit.Test;
 
 /**
@@ -468,6 +468,9 @@
 
 	@Test
 	public void testTrailingSpaces() throws IOException {
+		// Windows can't create files with trailing spaces
+		// If this assumption fails the test is halted and ignored.
+		org.junit.Assume.assumeFalse(SystemReader.getInstance().isWindows());
 		writeTrashFile("a  /a", "");
 		writeTrashFile("a  /a ", "");
 		writeTrashFile("a  /a  ", "");
@@ -505,7 +508,7 @@
 						.toString());
 	}
 
-	private void beginWalk() throws CorruptObjectException {
+	private void beginWalk() {
 		walk = new TreeWalk(db);
 		walk.addTree(new FileTreeIterator(db));
 	}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/IgnoreRuleSpecialCasesTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/IgnoreRuleSpecialCasesTest.java
index f8eb126..567f3d8 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/IgnoreRuleSpecialCasesTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/IgnoreRuleSpecialCasesTest.java
@@ -768,7 +768,7 @@
 
 	@Test
 	public void testSpecialGroupCase9() throws Exception {
-		assertMatch("][", "][", true);
+		assertMatch("][", "][", false);
 	}
 
 	@Test
@@ -968,6 +968,59 @@
 		assertMatch("[a{}()b][a{}()b]?[a{}()b][a{}()b]", "{}x()", true);
 		assertMatch("x*{x}3", "xa{x}3", true);
 		assertMatch("a*{x}3", "axxx", false);
+
+		assertMatch("?", "[", true);
+		assertMatch("*", "[", true);
+
+		// Escaped bracket matches, but see weird things below...
+		assertMatch("\\[", "[", true);
+	}
+
+	/**
+	 * The ignore rules here <b>do not match</b> any paths because single '['
+	 * begins character group and the entire rule cannot be parsed due the
+	 * invalid glob pattern. See
+	 * http://article.gmane.org/gmane.comp.version-control.git/278699.
+	 *
+	 * @throws Exception
+	 */
+	@Test
+	public void testBracketsUnmatched1() throws Exception {
+		assertMatch("[", "[", false);
+		assertMatch("[*", "[", false);
+		assertMatch("*[", "[", false);
+		assertMatch("*[", "a[", false);
+		assertMatch("[a][", "a[", false);
+		assertMatch("*[", "a", false);
+		assertMatch("[a", "a", false);
+		assertMatch("[*", "a", false);
+		assertMatch("[*a", "a", false);
+	}
+
+	/**
+	 * Single ']' is treated here literally, not as an and of a character group
+	 *
+	 * @throws Exception
+	 */
+	@Test
+	public void testBracketsUnmatched2() throws Exception {
+		assertMatch("*]", "a", false);
+		assertMatch("]a", "a", false);
+		assertMatch("]*", "a", false);
+		assertMatch("]*a", "a", false);
+
+		assertMatch("]", "]", true);
+		assertMatch("]*", "]", true);
+		assertMatch("]*", "]a", true);
+		assertMatch("*]", "]", true);
+		assertMatch("*]", "a]", true);
+	}
+
+	@Test
+	public void testBracketsRandom() throws Exception {
+		assertMatch("[\\]", "[$0+//r4a\\d]", false);
+		assertMatch("[:]]sZX]", "[:]]sZX]", false);
+		assertMatch("[:]]:]]]", "[:]]:]]]", false);
 	}
 
 	@Test
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 fc8cbaa..11a0924 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
@@ -51,9 +51,12 @@
 import java.nio.ByteBuffer;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 import java.util.zip.Deflater;
 
+import org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource;
 import org.eclipse.jgit.internal.storage.pack.PackExt;
 import org.eclipse.jgit.junit.JGitTestUtil;
 import org.eclipse.jgit.junit.TestRng;
@@ -161,6 +164,56 @@
 		assertEquals(id2, objs.iterator().next());
 	}
 
+	@Test
+	public void testGarbageSelectivelyVisible() throws IOException {
+		ObjectInserter ins = db.newObjectInserter();
+		ObjectId fooId = ins.insert(Constants.OBJ_BLOB, Constants.encode("foo"));
+		ins.flush();
+		assertEquals(1, db.getObjectDatabase().listPacks().size());
+
+		// Make pack 0 garbage.
+		db.getObjectDatabase().listPacks().get(0).setPackSource(PackSource.UNREACHABLE_GARBAGE);
+
+		// Default behavior should be that the database has foo, because we allow garbage objects.
+		assertTrue(db.getObjectDatabase().has(fooId));
+		// But we should not be able to see it if we pass the right args.
+		assertFalse(db.getObjectDatabase().has(fooId, true));
+	}
+
+	@Test
+	public void testInserterIgnoresUnreachable() throws IOException {
+		ObjectInserter ins = db.newObjectInserter();
+		ObjectId fooId = ins.insert(Constants.OBJ_BLOB, Constants.encode("foo"));
+		ins.flush();
+		assertEquals(1, db.getObjectDatabase().listPacks().size());
+
+		// Make pack 0 garbage.
+		db.getObjectDatabase().listPacks().get(0).setPackSource(PackSource.UNREACHABLE_GARBAGE);
+
+		// We shouldn't be able to see foo because it's garbage.
+		assertFalse(db.getObjectDatabase().has(fooId, true));
+
+		// But if we re-insert foo, it should become visible again.
+		ins.insert(Constants.OBJ_BLOB, Constants.encode("foo"));
+		ins.flush();
+		assertTrue(db.getObjectDatabase().has(fooId, true));
+
+		// Verify that we have a foo in both packs, and 1 of them is garbage.
+		DfsReader reader = new DfsReader(db.getObjectDatabase());
+		DfsPackFile packs[] = db.getObjectDatabase().getPacks();
+		Set<PackSource> pack_sources = new HashSet<PackSource>();
+
+		assertEquals(2, packs.length);
+
+		pack_sources.add(packs[0].getPackDescription().getPackSource());
+		pack_sources.add(packs[1].getPackDescription().getPackSource());
+
+		assertTrue(packs[0].hasObject(reader, fooId));
+		assertTrue(packs[1].hasObject(reader, fooId));
+		assertTrue(pack_sources.contains(PackSource.UNREACHABLE_GARBAGE));
+		assertTrue(pack_sources.contains(PackSource.INSERT));
+	}
+
 	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/ConcurrentRepackTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/ConcurrentRepackTest.java
index 514e00f..9d7a482 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/ConcurrentRepackTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/ConcurrentRepackTest.java
@@ -81,15 +81,14 @@
 	public void setUp() throws Exception {
 		WindowCacheConfig windowCacheConfig = new WindowCacheConfig();
 		windowCacheConfig.setPackedGitOpenFiles(1);
-		WindowCache.reconfigure(windowCacheConfig);
+		windowCacheConfig.install();
 		super.setUp();
 	}
 
 	@After
 	public void tearDown() throws Exception {
 		super.tearDown();
-		WindowCacheConfig windowCacheConfig = new WindowCacheConfig();
-		WindowCache.reconfigure(windowCacheConfig);
+		new WindowCacheConfig().install();
 	}
 
 	@Test
@@ -206,12 +205,14 @@
 	private static void whackCache() {
 		final WindowCacheConfig config = new WindowCacheConfig();
 		config.setPackedGitOpenFiles(1);
-		WindowCache.reconfigure(config);
+		config.install();
 	}
 
 	private RevObject parse(final AnyObjectId id)
 			throws MissingObjectException, IOException {
-		return new RevWalk(db).parseAny(id);
+		try (RevWalk rw = new RevWalk(db)) {
+			return rw.parseAny(id);
+		}
 	}
 
 	private File[] pack(final Repository src, final RevObject... list)
@@ -280,7 +281,6 @@
 
 	private RevObject writeBlob(final Repository repo, final String data)
 			throws IOException {
-		final RevWalk revWalk = new RevWalk(repo);
 		final byte[] bytes = Constants.encode(data);
 		final ObjectId id;
 		try (ObjectInserter inserter = repo.newObjectInserter()) {
@@ -293,6 +293,8 @@
 		} catch (MissingObjectException e) {
 			// Ok
 		}
-		return revWalk.lookupBlob(id);
+		try (RevWalk revWalk = new RevWalk(repo)) {
+			return revWalk.lookupBlob(id);
+		}
 	}
 }
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/FileRepositoryBuilderTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/FileRepositoryBuilderTest.java
index 280d604..deffa04 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/FileRepositoryBuilderTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/FileRepositoryBuilderTest.java
@@ -80,7 +80,9 @@
 				ConfigConstants.CONFIG_KEY_REPO_FORMAT_VERSION, "");
 		config.save();
 
-		new FileRepository(r.getDirectory());
+		try (FileRepository repo = new FileRepository(r.getDirectory())) {
+			// Unused
+		}
 	}
 
 	@Test
@@ -91,8 +93,7 @@
 				ConfigConstants.CONFIG_KEY_REPO_FORMAT_VERSION, "notanumber");
 		config.save();
 
-		try {
-			new FileRepository(r.getDirectory());
+		try (FileRepository repo = new FileRepository(r.getDirectory())) {
 			fail("IllegalArgumentException not thrown");
 		} catch (IllegalArgumentException e) {
 			assertNotNull(e.getMessage());
@@ -104,78 +105,78 @@
 		Repository r = createWorkRepository();
 		StoredConfig config = r.getConfig();
 		config.setLong(ConfigConstants.CONFIG_CORE_SECTION, null,
-				ConfigConstants.CONFIG_KEY_REPO_FORMAT_VERSION, 1);
+				ConfigConstants.CONFIG_KEY_REPO_FORMAT_VERSION, 999999);
 		config.save();
 
-		try {
-			new FileRepository(r.getDirectory());
+		try (FileRepository repo = new FileRepository(r.getDirectory())) {
 			fail("IOException not thrown");
 		} catch (IOException e) {
 			assertNotNull(e.getMessage());
 		}
 	}
 
-	@SuppressWarnings("resource" /* java 7 */)
 	@Test
 	public void absoluteGitDirRef() throws Exception {
 		Repository repo1 = createWorkRepository();
 		File dir = createTempDirectory("dir");
 		File dotGit = new File(dir, Constants.DOT_GIT);
-		new FileWriter(dotGit).append(
-				"gitdir: " + repo1.getDirectory().getAbsolutePath()).close();
-		FileRepositoryBuilder builder = new FileRepositoryBuilder();
+		try (FileWriter writer = new FileWriter(dotGit)) {
+			writer.append("gitdir: " + repo1.getDirectory().getAbsolutePath()).close();
+			FileRepositoryBuilder builder = new FileRepositoryBuilder();
 
-		builder.setWorkTree(dir);
-		builder.setMustExist(true);
-		Repository repo2 = builder.build();
+			builder.setWorkTree(dir);
+			builder.setMustExist(true);
+			Repository repo2 = builder.build();
 
-		assertEquals(repo1.getDirectory().getAbsolutePath(), repo2
-				.getDirectory().getAbsolutePath());
-		assertEquals(dir, repo2.getWorkTree());
+			assertEquals(repo1.getDirectory().getAbsolutePath(), repo2
+					.getDirectory().getAbsolutePath());
+			assertEquals(dir, repo2.getWorkTree());
+		}
 	}
 
-	@SuppressWarnings("resource" /* java 7 */)
 	@Test
 	public void relativeGitDirRef() throws Exception {
 		Repository repo1 = createWorkRepository();
 		File dir = new File(repo1.getWorkTree(), "dir");
 		assertTrue(dir.mkdir());
 		File dotGit = new File(dir, Constants.DOT_GIT);
-		new FileWriter(dotGit).append("gitdir: ../" + Constants.DOT_GIT)
-				.close();
+		try (FileWriter writer = new FileWriter(dotGit)) {
+			writer.append("gitdir: ../" + Constants.DOT_GIT).close();
 
-		FileRepositoryBuilder builder = new FileRepositoryBuilder();
-		builder.setWorkTree(dir);
-		builder.setMustExist(true);
-		Repository repo2 = builder.build();
+			FileRepositoryBuilder builder = new FileRepositoryBuilder();
+			builder.setWorkTree(dir);
+			builder.setMustExist(true);
+			Repository repo2 = builder.build();
 
-		// The tmp directory may be a symlink so the actual path
-		// may not
-		assertEquals(repo1.getDirectory().getCanonicalPath(), repo2
-				.getDirectory().getCanonicalPath());
-		assertEquals(dir, repo2.getWorkTree());
+			// The tmp directory may be a symlink so the actual path
+			// may not
+			assertEquals(repo1.getDirectory().getCanonicalPath(), repo2
+					.getDirectory().getCanonicalPath());
+			assertEquals(dir, repo2.getWorkTree());
+		}
 	}
 
-	@SuppressWarnings("resource" /* java 7 */)
 	@Test
 	public void scanWithGitDirRef() throws Exception {
 		Repository repo1 = createWorkRepository();
 		File dir = createTempDirectory("dir");
 		File dotGit = new File(dir, Constants.DOT_GIT);
-		new FileWriter(dotGit).append(
-				"gitdir: " + repo1.getDirectory().getAbsolutePath()).close();
-		FileRepositoryBuilder builder = new FileRepositoryBuilder();
+		try (FileWriter writer = new FileWriter(dotGit)) {
+			writer.append(
+					"gitdir: " + repo1.getDirectory().getAbsolutePath()).close();
+			FileRepositoryBuilder builder = new FileRepositoryBuilder();
 
-		builder.setWorkTree(dir);
-		builder.findGitDir(dir);
-		assertEquals(repo1.getDirectory().getAbsolutePath(), builder
-				.getGitDir().getAbsolutePath());
-		builder.setMustExist(true);
-		Repository repo2 = builder.build();
+			builder.setWorkTree(dir);
+			builder.findGitDir(dir);
+			assertEquals(repo1.getDirectory().getAbsolutePath(), builder
+					.getGitDir().getAbsolutePath());
+			builder.setMustExist(true);
+			Repository repo2 = builder.build();
 
-		// The tmp directory may be a symlink
-		assertEquals(repo1.getDirectory().getCanonicalPath(), repo2
-				.getDirectory().getCanonicalPath());
-		assertEquals(dir, repo2.getWorkTree());
+			// The tmp directory may be a symlink
+			assertEquals(repo1.getDirectory().getCanonicalPath(), repo2
+					.getDirectory().getCanonicalPath());
+			assertEquals(dir, repo2.getWorkTree());
+		}
 	}
 }
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcBasicPackingTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcBasicPackingTest.java
index bbd4123..f549fb5 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcBasicPackingTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcBasicPackingTest.java
@@ -85,6 +85,7 @@
 		assertEquals(4, stats.numberOfLooseObjects);
 		assertEquals(0, stats.numberOfPackedObjects);
 		assertEquals(0, stats.numberOfPackFiles);
+		assertEquals(0, stats.numberOfBitmaps);
 	}
 
 	@Theory
@@ -102,6 +103,7 @@
 		assertEquals(0, stats.numberOfLooseObjects);
 		assertEquals(8, stats.numberOfPackedObjects);
 		assertEquals(1, stats.numberOfPackFiles);
+		assertEquals(2, stats.numberOfBitmaps);
 	}
 
 	@Theory
@@ -118,6 +120,7 @@
 		assertEquals(0, stats.numberOfLooseObjects);
 		assertEquals(4, stats.numberOfPackedObjects);
 		assertEquals(1, stats.numberOfPackFiles);
+		assertEquals(1, stats.numberOfBitmaps);
 
 		// Do the gc again and check that it hasn't changed anything
 		gc.gc();
@@ -125,10 +128,12 @@
 		assertEquals(0, stats.numberOfLooseObjects);
 		assertEquals(4, stats.numberOfPackedObjects);
 		assertEquals(1, stats.numberOfPackFiles);
+		assertEquals(1, stats.numberOfBitmaps);
 	}
 
 	@Theory
-	public void testPackCommitsAndLooseOne(boolean aggressive) throws Exception {
+	public void testPackCommitsAndLooseOne(boolean aggressive)
+			throws Exception {
 		BranchBuilder bb = tr.branch("refs/heads/master");
 		RevCommit first = bb.commit().add("A", "A").add("B", "B").create();
 		bb.commit().add("A", "A2").add("B", "B2").create();
@@ -143,6 +148,7 @@
 		assertEquals(0, stats.numberOfLooseObjects);
 		assertEquals(8, stats.numberOfPackedObjects);
 		assertEquals(2, stats.numberOfPackFiles);
+		assertEquals(1, stats.numberOfBitmaps);
 	}
 
 	@Theory
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcPackRefsTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcPackRefsTest.java
index c7336da..48ea13b 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcPackRefsTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcPackRefsTest.java
@@ -77,7 +77,7 @@
 		tr.lightweightTag("t", a);
 
 		gc.packRefs();
-		assertSame(repo.getRef("t").getStorage(), Storage.PACKED);
+		assertSame(repo.exactRef("refs/tags/t").getStorage(), Storage.PACKED);
 	}
 
 	@Test
@@ -118,7 +118,7 @@
 		tr.lightweightTag("t1", a);
 		tr.lightweightTag("t2", a);
 		LockFile refLock = new LockFile(new File(repo.getDirectory(),
-				"refs/tags/t1"), repo.getFS());
+				"refs/tags/t1"));
 		try {
 			refLock.lock();
 			gc.packRefs();
@@ -126,8 +126,8 @@
 			refLock.unlock();
 		}
 
-		assertSame(repo.getRef("refs/tags/t1").getStorage(), Storage.LOOSE);
-		assertSame(repo.getRef("refs/tags/t2").getStorage(), Storage.PACKED);
+		assertSame(repo.exactRef("refs/tags/t1").getStorage(), Storage.LOOSE);
+		assertSame(repo.exactRef("refs/tags/t2").getStorage(), Storage.PACKED);
 	}
 
 	@Test
@@ -146,7 +146,7 @@
 				public Result call() throws Exception {
 					RefUpdate update = new RefDirectoryUpdate(
 							(RefDirectory) repo.getRefDatabase(),
-							repo.getRef("refs/tags/t")) {
+							repo.exactRef("refs/tags/t")) {
 						@Override
 						public boolean isForceUpdate() {
 							try {
@@ -182,7 +182,7 @@
 			pool.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS);
 		}
 
-		assertEquals(repo.getRef("refs/tags/t").getObjectId(), b);
+		assertEquals(repo.exactRef("refs/tags/t").getObjectId(), b);
 	}
 
 	@Test
@@ -194,23 +194,23 @@
 
 		// check for the unborn branch master. HEAD should point to master and
 		// master doesn't exist.
-		assertEquals(repo.getRef("HEAD").getTarget().getName(),
+		assertEquals(repo.exactRef("HEAD").getTarget().getName(),
 				"refs/heads/master");
-		assertNull(repo.getRef("HEAD").getTarget().getObjectId());
+		assertNull(repo.exactRef("HEAD").getTarget().getObjectId());
 		gc.packRefs();
-		assertSame(repo.getRef("HEAD").getStorage(), Storage.LOOSE);
-		assertEquals(repo.getRef("HEAD").getTarget().getName(),
+		assertSame(repo.exactRef("HEAD").getStorage(), Storage.LOOSE);
+		assertEquals(repo.exactRef("HEAD").getTarget().getName(),
 				"refs/heads/master");
-		assertNull(repo.getRef("HEAD").getTarget().getObjectId());
+		assertNull(repo.exactRef("HEAD").getTarget().getObjectId());
 
 		git.checkout().setName("refs/heads/side").call();
 		gc.packRefs();
-		assertSame(repo.getRef("HEAD").getStorage(), Storage.LOOSE);
+		assertSame(repo.exactRef("HEAD").getStorage(), Storage.LOOSE);
 
 		// check for detached HEAD
 		git.checkout().setName(first.getName()).call();
 		gc.packRefs();
-		assertSame(repo.getRef("HEAD").getStorage(), Storage.LOOSE);
+		assertSame(repo.exactRef("HEAD").getStorage(), Storage.LOOSE);
 	}
 
 	@Test
@@ -229,20 +229,20 @@
 
 		// check for the unborn branch master. HEAD should point to master and
 		// master doesn't exist.
-		assertEquals(repo.getRef("HEAD").getTarget().getName(),
+		assertEquals(repo.exactRef("HEAD").getTarget().getName(),
 				"refs/heads/master");
-		assertNull(repo.getRef("HEAD").getTarget().getObjectId());
+		assertNull(repo.exactRef("HEAD").getTarget().getObjectId());
 		gc.packRefs();
-		assertSame(repo.getRef("HEAD").getStorage(), Storage.LOOSE);
-		assertEquals(repo.getRef("HEAD").getTarget().getName(),
+		assertSame(repo.exactRef("HEAD").getStorage(), Storage.LOOSE);
+		assertEquals(repo.exactRef("HEAD").getTarget().getName(),
 				"refs/heads/master");
-		assertNull(repo.getRef("HEAD").getTarget().getObjectId());
+		assertNull(repo.exactRef("HEAD").getTarget().getObjectId());
 
 		// check for non-detached HEAD
 		repo.updateRef(Constants.HEAD).link("refs/heads/side");
 		gc.packRefs();
-		assertSame(repo.getRef("HEAD").getStorage(), Storage.LOOSE);
-		assertEquals(repo.getRef("HEAD").getTarget().getObjectId(),
+		assertSame(repo.exactRef("HEAD").getStorage(), Storage.LOOSE);
+		assertEquals(repo.exactRef("HEAD").getTarget().getObjectId(),
 				second.getId());
 	}
 }
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcReflogTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcReflogTest.java
index 2a096fd..3c781a9 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcReflogTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcReflogTest.java
@@ -75,6 +75,7 @@
 		tr.blob("x");
 		stats = gc.getStatistics();
 		assertEquals(9, stats.numberOfLooseObjects);
+		fsTick();
 		gc.prune(Collections.<ObjectId> emptySet());
 		stats = gc.getStatistics();
 		assertEquals(8, stats.numberOfLooseObjects);
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 a764f0f..5abf625 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
@@ -52,6 +52,7 @@
 import org.eclipse.jgit.junit.TestRepository.CommitBuilder;
 import org.eclipse.jgit.lib.AnyObjectId;
 import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevWalk;
 import org.junit.After;
 import org.junit.Before;
 
@@ -65,7 +66,8 @@
 	public void setUp() throws Exception {
 		super.setUp();
 		repo = createWorkRepository();
-		tr = new TestRepository<FileRepository>((repo));
+		tr = new TestRepository<FileRepository>(repo, new RevWalk(repo),
+				mockSystemReader);
 		gc = new GC(repo);
 	}
 
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/LockFileTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/LockFileTest.java
index 1a3a567..f1bc7c8 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/LockFileTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/LockFileTest.java
@@ -61,25 +61,26 @@
 
 	@Test
 	public void lockFailedExceptionRecovery() throws Exception {
-		Git git = new Git(db);
-		writeTrashFile("file.txt", "content");
-		git.add().addFilepattern("file.txt").call();
-		RevCommit commit1 = git.commit().setMessage("create file").call();
+		try (Git git = new Git(db)) {
+			writeTrashFile("file.txt", "content");
+			git.add().addFilepattern("file.txt").call();
+			RevCommit commit1 = git.commit().setMessage("create file").call();
 
-		assertNotNull(commit1);
-		writeTrashFile("file.txt", "content2");
-		git.add().addFilepattern("file.txt").call();
-		assertNotNull(git.commit().setMessage("edit file").call());
+			assertNotNull(commit1);
+			writeTrashFile("file.txt", "content2");
+			git.add().addFilepattern("file.txt").call();
+			assertNotNull(git.commit().setMessage("edit file").call());
 
-		LockFile lf = new LockFile(db.getIndexFile(), db.getFS());
-		assertTrue(lf.lock());
-		try {
-			git.checkout().setName(commit1.name()).call();
-			fail("JGitInternalException not thrown");
-		} catch (JGitInternalException e) {
-			assertTrue(e.getCause() instanceof LockFailedException);
-			lf.unlock();
-			git.checkout().setName(commit1.name()).call();
+			LockFile lf = new LockFile(db.getIndexFile());
+			assertTrue(lf.lock());
+			try {
+				git.checkout().setName(commit1.name()).call();
+				fail("JGitInternalException not thrown");
+			} catch (JGitInternalException e) {
+				assertTrue(e.getCause() instanceof LockFailedException);
+				lf.unlock();
+				git.checkout().setName(commit1.name()).call();
+			}
 		}
 	}
 }
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/ObjectDirectoryTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/ObjectDirectoryTest.java
index 3226f42..923f283 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/ObjectDirectoryTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/ObjectDirectoryTest.java
@@ -62,18 +62,18 @@
 			throws Exception {
 		ExecutorService e = Executors.newCachedThreadPool();
 		for (int i=0; i < 100; ++i) {
-			ObjectDirectory db = createBareRepository().getObjectDatabase();
-			for (Future f : e.invokeAll(blobInsertersForTheSameFanOutDir(db))) {
+			ObjectDirectory dir = createBareRepository().getObjectDatabase();
+			for (Future f : e.invokeAll(blobInsertersForTheSameFanOutDir(dir))) {
 				f.get();
 			}
 		}
 	}
 
 	private Collection<Callable<ObjectId>> blobInsertersForTheSameFanOutDir(
-			final ObjectDirectory db) {
+			final ObjectDirectory dir) {
 		Callable<ObjectId> callable = new Callable<ObjectId>() {
 			public ObjectId call() throws Exception {
-				return db.newInserter().insert(Constants.OBJ_BLOB, new byte[0]);
+				return dir.newInserter().insert(Constants.OBJ_BLOB, new byte[0]);
 			}
 		};
 		return Collections.nCopies(4, callable);
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackFileTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackFileTest.java
index cb80768..ba07d68 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackFileTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackFileTest.java
@@ -191,119 +191,122 @@
 
 	@Test
 	public void testDelta_SmallObjectChain() throws Exception {
-		ObjectInserter.Formatter fmt = new ObjectInserter.Formatter();
-		byte[] data0 = new byte[512];
-		Arrays.fill(data0, (byte) 0xf3);
-		ObjectId id0 = fmt.idFor(Constants.OBJ_BLOB, data0);
+		try (ObjectInserter.Formatter fmt = new ObjectInserter.Formatter()) {
+			byte[] data0 = new byte[512];
+			Arrays.fill(data0, (byte) 0xf3);
+			ObjectId id0 = fmt.idFor(Constants.OBJ_BLOB, data0);
 
-		TemporaryBuffer.Heap pack = new TemporaryBuffer.Heap(64 * 1024);
-		packHeader(pack, 4);
-		objectHeader(pack, Constants.OBJ_BLOB, data0.length);
-		deflate(pack, data0);
+			TemporaryBuffer.Heap pack = new TemporaryBuffer.Heap(64 * 1024);
+			packHeader(pack, 4);
+			objectHeader(pack, Constants.OBJ_BLOB, data0.length);
+			deflate(pack, data0);
 
-		byte[] data1 = clone(0x01, data0);
-		byte[] delta1 = delta(data0, data1);
-		ObjectId id1 = fmt.idFor(Constants.OBJ_BLOB, data1);
-		objectHeader(pack, Constants.OBJ_REF_DELTA, delta1.length);
-		id0.copyRawTo(pack);
-		deflate(pack, delta1);
+			byte[] data1 = clone(0x01, data0);
+			byte[] delta1 = delta(data0, data1);
+			ObjectId id1 = fmt.idFor(Constants.OBJ_BLOB, data1);
+			objectHeader(pack, Constants.OBJ_REF_DELTA, delta1.length);
+			id0.copyRawTo(pack);
+			deflate(pack, delta1);
 
-		byte[] data2 = clone(0x02, data1);
-		byte[] delta2 = delta(data1, data2);
-		ObjectId id2 = fmt.idFor(Constants.OBJ_BLOB, data2);
-		objectHeader(pack, Constants.OBJ_REF_DELTA, delta2.length);
-		id1.copyRawTo(pack);
-		deflate(pack, delta2);
+			byte[] data2 = clone(0x02, data1);
+			byte[] delta2 = delta(data1, data2);
+			ObjectId id2 = fmt.idFor(Constants.OBJ_BLOB, data2);
+			objectHeader(pack, Constants.OBJ_REF_DELTA, delta2.length);
+			id1.copyRawTo(pack);
+			deflate(pack, delta2);
 
-		byte[] data3 = clone(0x03, data2);
-		byte[] delta3 = delta(data2, data3);
-		ObjectId id3 = fmt.idFor(Constants.OBJ_BLOB, data3);
-		objectHeader(pack, Constants.OBJ_REF_DELTA, delta3.length);
-		id2.copyRawTo(pack);
-		deflate(pack, delta3);
+			byte[] data3 = clone(0x03, data2);
+			byte[] delta3 = delta(data2, data3);
+			ObjectId id3 = fmt.idFor(Constants.OBJ_BLOB, data3);
+			objectHeader(pack, Constants.OBJ_REF_DELTA, delta3.length);
+			id2.copyRawTo(pack);
+			deflate(pack, delta3);
 
-		digest(pack);
-		PackParser ip = index(pack.toByteArray());
-		ip.setAllowThin(true);
-		ip.parse(NullProgressMonitor.INSTANCE);
+			digest(pack);
+			PackParser ip = index(pack.toByteArray());
+			ip.setAllowThin(true);
+			ip.parse(NullProgressMonitor.INSTANCE);
 
-		assertTrue("has blob", wc.has(id3));
+			assertTrue("has blob", wc.has(id3));
 
-		ObjectLoader ol = wc.open(id3);
-		assertNotNull("created loader", ol);
-		assertEquals(Constants.OBJ_BLOB, ol.getType());
-		assertEquals(data3.length, ol.getSize());
-		assertFalse("is large", ol.isLarge());
-		assertNotNull(ol.getCachedBytes());
-		assertArrayEquals(data3, ol.getCachedBytes());
+			ObjectLoader ol = wc.open(id3);
+			assertNotNull("created loader", ol);
+			assertEquals(Constants.OBJ_BLOB, ol.getType());
+			assertEquals(data3.length, ol.getSize());
+			assertFalse("is large", ol.isLarge());
+			assertNotNull(ol.getCachedBytes());
+			assertArrayEquals(data3, ol.getCachedBytes());
 
-		ObjectStream in = ol.openStream();
-		assertNotNull("have stream", in);
-		assertEquals(Constants.OBJ_BLOB, in.getType());
-		assertEquals(data3.length, in.getSize());
-		byte[] act = new byte[data3.length];
-		IO.readFully(in, act, 0, data3.length);
-		assertTrue("same content", Arrays.equals(act, data3));
-		assertEquals("stream at EOF", -1, in.read());
-		in.close();
+			ObjectStream in = ol.openStream();
+			assertNotNull("have stream", in);
+			assertEquals(Constants.OBJ_BLOB, in.getType());
+			assertEquals(data3.length, in.getSize());
+			byte[] act = new byte[data3.length];
+			IO.readFully(in, act, 0, data3.length);
+			assertTrue("same content", Arrays.equals(act, data3));
+			assertEquals("stream at EOF", -1, in.read());
+			in.close();
+		}
 	}
 
 	@Test
 	public void testDelta_FailsOver2GiB() throws Exception {
-		ObjectInserter.Formatter fmt = new ObjectInserter.Formatter();
-		byte[] base = new byte[] { 'a' };
-		ObjectId idA = fmt.idFor(Constants.OBJ_BLOB, base);
-		ObjectId idB = fmt.idFor(Constants.OBJ_BLOB, new byte[] { 'b' });
+		try (ObjectInserter.Formatter fmt = new ObjectInserter.Formatter()) {
+			byte[] base = new byte[] { 'a' };
+			ObjectId idA = fmt.idFor(Constants.OBJ_BLOB, base);
+			ObjectId idB = fmt.idFor(Constants.OBJ_BLOB, new byte[] { 'b' });
 
-		PackedObjectInfo a = new PackedObjectInfo(idA);
-		PackedObjectInfo b = new PackedObjectInfo(idB);
+			PackedObjectInfo a = new PackedObjectInfo(idA);
+			PackedObjectInfo b = new PackedObjectInfo(idB);
 
-		TemporaryBuffer.Heap pack = new TemporaryBuffer.Heap(64 * 1024);
-		packHeader(pack, 2);
-		a.setOffset(pack.length());
-		objectHeader(pack, Constants.OBJ_BLOB, base.length);
-		deflate(pack, base);
+			TemporaryBuffer.Heap pack = new TemporaryBuffer.Heap(64 * 1024);
+			packHeader(pack, 2);
+			a.setOffset(pack.length());
+			objectHeader(pack, Constants.OBJ_BLOB, base.length);
+			deflate(pack, base);
 
-		ByteArrayOutputStream tmp = new ByteArrayOutputStream();
-		DeltaEncoder de = new DeltaEncoder(tmp, base.length, 3L << 30);
-		de.copy(0, 1);
-		byte[] delta = tmp.toByteArray();
-		b.setOffset(pack.length());
-		objectHeader(pack, Constants.OBJ_REF_DELTA, delta.length);
-		idA.copyRawTo(pack);
-		deflate(pack, delta);
-		byte[] footer = digest(pack);
+			ByteArrayOutputStream tmp = new ByteArrayOutputStream();
+			DeltaEncoder de = new DeltaEncoder(tmp, base.length, 3L << 30);
+			de.copy(0, 1);
+			byte[] delta = tmp.toByteArray();
+			b.setOffset(pack.length());
+			objectHeader(pack, Constants.OBJ_REF_DELTA, delta.length);
+			idA.copyRawTo(pack);
+			deflate(pack, delta);
+			byte[] footer = digest(pack);
 
-		File dir = new File(repo.getObjectDatabase().getDirectory(), "pack");
-		File packName = new File(dir, idA.name() + ".pack");
-		File idxName = new File(dir, idA.name() + ".idx");
+			File dir = new File(repo.getObjectDatabase().getDirectory(),
+					"pack");
+			File packName = new File(dir, idA.name() + ".pack");
+			File idxName = new File(dir, idA.name() + ".idx");
 
-		FileOutputStream f = new FileOutputStream(packName);
-		try {
-			f.write(pack.toByteArray());
-		} finally {
-			f.close();
-		}
+			FileOutputStream f = new FileOutputStream(packName);
+			try {
+				f.write(pack.toByteArray());
+			} finally {
+				f.close();
+			}
 
-		f = new FileOutputStream(idxName);
-		try {
-			List<PackedObjectInfo> list = new ArrayList<PackedObjectInfo>();
-			list.add(a);
-			list.add(b);
-			Collections.sort(list);
-			new PackIndexWriterV1(f).write(list, footer);
-		} finally {
-			f.close();
-		}
+			f = new FileOutputStream(idxName);
+			try {
+				List<PackedObjectInfo> list = new ArrayList<PackedObjectInfo>();
+				list.add(a);
+				list.add(b);
+				Collections.sort(list);
+				new PackIndexWriterV1(f).write(list, footer);
+			} finally {
+				f.close();
+			}
 
-		PackFile packFile = new PackFile(packName, PackExt.INDEX.getBit());
-		try {
-			packFile.get(wc, b);
-			fail("expected LargeObjectException.ExceedsByteArrayLimit");
-		} catch (LargeObjectException.ExceedsByteArrayLimit bad) {
-			assertNull(bad.getObjectId());
-		} finally {
-			packFile.close();
+			PackFile packFile = new PackFile(packName, PackExt.INDEX.getBit());
+			try {
+				packFile.get(wc, b);
+				fail("expected LargeObjectException.ExceedsByteArrayLimit");
+			} catch (LargeObjectException.ExceedsByteArrayLimit bad) {
+				assertNull(bad.getObjectId());
+			} finally {
+				packFile.close();
+			}
 		}
 	}
 
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 bc880a1..63a3072 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
@@ -43,11 +43,13 @@
 
 package org.eclipse.jgit.internal.storage.file;
 
+import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
+import static org.eclipse.jgit.internal.storage.pack.PackWriter.NONE;
 
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
@@ -66,19 +68,19 @@
 import org.eclipse.jgit.errors.MissingObjectException;
 import org.eclipse.jgit.internal.storage.file.PackIndex.MutableEntry;
 import org.eclipse.jgit.internal.storage.pack.PackWriter;
-import org.eclipse.jgit.internal.storage.pack.PackWriter.ObjectIdSet;
 import org.eclipse.jgit.junit.JGitTestUtil;
 import org.eclipse.jgit.junit.TestRepository;
 import org.eclipse.jgit.junit.TestRepository.BranchBuilder;
-import org.eclipse.jgit.lib.AnyObjectId;
 import org.eclipse.jgit.lib.NullProgressMonitor;
 import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectIdSet;
 import org.eclipse.jgit.lib.ObjectInserter;
 import org.eclipse.jgit.revwalk.RevBlob;
 import org.eclipse.jgit.revwalk.RevCommit;
 import org.eclipse.jgit.revwalk.RevObject;
 import org.eclipse.jgit.revwalk.RevWalk;
 import org.eclipse.jgit.storage.pack.PackConfig;
+import org.eclipse.jgit.storage.pack.PackStatistics;
 import org.eclipse.jgit.test.resources.SampleDataRepositoryTestCase;
 import org.eclipse.jgit.transport.PackParser;
 import org.junit.After;
@@ -87,9 +89,6 @@
 
 public class PackWriterTest extends SampleDataRepositoryTestCase {
 
-	private static final Set<ObjectId> EMPTY_SET_OBJECT = Collections
-			.<ObjectId> emptySet();
-
 	private static final List<RevObject> EMPTY_LIST_REVS = Collections
 			.<RevObject> emptyList();
 
@@ -170,7 +169,7 @@
 	 */
 	@Test
 	public void testWriteEmptyPack1() throws IOException {
-		createVerifyOpenPack(EMPTY_SET_OBJECT, EMPTY_SET_OBJECT, false, false);
+		createVerifyOpenPack(NONE, NONE, false, false);
 
 		assertEquals(0, writer.getObjectCount());
 		assertEquals(0, pack.getObjectCount());
@@ -203,8 +202,8 @@
 		final ObjectId nonExisting = ObjectId
 				.fromString("0000000000000000000000000000000000000001");
 		try {
-			createVerifyOpenPack(EMPTY_SET_OBJECT, Collections.singleton(
-					nonExisting), false, false);
+			createVerifyOpenPack(NONE, Collections.singleton(nonExisting),
+					false, false);
 			fail("Should have thrown MissingObjectException");
 		} catch (MissingObjectException x) {
 			// expected
@@ -220,8 +219,8 @@
 	public void testIgnoreNonExistingObjects() throws IOException {
 		final ObjectId nonExisting = ObjectId
 				.fromString("0000000000000000000000000000000000000001");
-		createVerifyOpenPack(EMPTY_SET_OBJECT, Collections.singleton(
-				nonExisting), false, true);
+		createVerifyOpenPack(NONE, Collections.singleton(nonExisting),
+				false, true);
 		// shouldn't throw anything
 	}
 
@@ -239,8 +238,8 @@
 		final ObjectId nonExisting = ObjectId
 				.fromString("0000000000000000000000000000000000000001");
 		new GC(db).gc();
-		createVerifyOpenPack(EMPTY_SET_OBJECT,
-				Collections.singleton(nonExisting), false, true, true);
+		createVerifyOpenPack(NONE, Collections.singleton(nonExisting), false,
+				true, true);
 		// shouldn't throw anything
 	}
 
@@ -343,12 +342,13 @@
 				ObjectId.fromString("902d5476fa249b7abc9d84c611577a81381f0327"),
 				ObjectId.fromString("5b6e7c66c276e7610d4a73c70ec1a1f7c1003259"),
 				ObjectId.fromString("6ff87c4664981e4397625791c8ea3bbb5f2279a3") };
-		final RevWalk parser = new RevWalk(db);
-		final RevObject forcedOrderRevs[] = new RevObject[forcedOrder.length];
-		for (int i = 0; i < forcedOrder.length; i++)
-			forcedOrderRevs[i] = parser.parseAny(forcedOrder[i]);
+		try (final RevWalk parser = new RevWalk(db)) {
+			final RevObject forcedOrderRevs[] = new RevObject[forcedOrder.length];
+			for (int i = 0; i < forcedOrder.length; i++)
+				forcedOrderRevs[i] = parser.parseAny(forcedOrder[i]);
 
-		createVerifyOpenPack(Arrays.asList(forcedOrderRevs));
+			createVerifyOpenPack(Arrays.asList(forcedOrderRevs));
+		}
 
 		assertEquals(forcedOrder.length, writer.getObjectCount());
 		verifyObjectsOrder(forcedOrder);
@@ -438,6 +438,38 @@
 	}
 
 	@Test
+	public void testDeltaStatistics() throws Exception {
+		config.setDeltaCompress(true);
+		FileRepository repo = createBareRepository();
+		TestRepository<FileRepository> testRepo = new TestRepository<FileRepository>(repo);
+		ArrayList<RevObject> blobs = new ArrayList<>();
+		blobs.add(testRepo.blob(genDeltableData(1000)));
+		blobs.add(testRepo.blob(genDeltableData(1005)));
+
+		try (PackWriter pw = new PackWriter(repo)) {
+			NullProgressMonitor m = NullProgressMonitor.INSTANCE;
+			pw.preparePack(blobs.iterator());
+			pw.writePack(m, m, os);
+			PackStatistics stats = pw.getStatistics();
+			assertEquals(1, stats.getTotalDeltas());
+			assertTrue("Delta bytes not set.",
+					stats.byObjectType(OBJ_BLOB).getDeltaBytes() > 0);
+		}
+	}
+
+	// Generate consistent junk data for building files that delta well
+	private String genDeltableData(int length) {
+		assertTrue("Generated data must have a length > 0", length > 0);
+		char[] data = {'a', 'b', 'c', '\n'};
+		StringBuilder builder = new StringBuilder(length);
+		for (int i = 0; i < length; i++) {
+			builder.append(data[i % 4]);
+		}
+		return builder.toString();
+	}
+
+
+	@Test
 	public void testWriteIndex() throws Exception {
 		config.setIndexVersion(2);
 		writeVerifyPack4(false);
@@ -494,7 +526,7 @@
 		RevCommit c2 = bb.commit().add("f", contentB).create();
 		testRepo.getRevWalk().parseHeaders(c2);
 		PackIndex pf2 = writePack(repo, Collections.singleton(c2),
-				Collections.singleton(objectIdSet(pf1)));
+				Collections.<ObjectIdSet> singleton(pf1));
 		assertContent(
 				pf2,
 				Arrays.asList(c2.getId(), c2.getTree().getId(),
@@ -519,8 +551,7 @@
 			pw.setReuseDeltaCommits(false);
 			for (ObjectIdSet idx : excludeObjects)
 				pw.excludeObjects(idx);
-			pw.preparePack(NullProgressMonitor.INSTANCE, want,
-					Collections.<ObjectId> emptySet());
+			pw.preparePack(NullProgressMonitor.INSTANCE, want, NONE);
 			String id = pw.computeName().getName();
 			File packdir = new File(repo.getObjectsDirectory(), "pack");
 			File packFile = new File(packdir, "pack-" + id + ".pack");
@@ -543,7 +574,7 @@
 		final HashSet<ObjectId> interestings = new HashSet<ObjectId>();
 		interestings.add(ObjectId
 				.fromString("82c6b885ff600be425b4ea96dee75dca255b69e7"));
-		createVerifyOpenPack(interestings, EMPTY_SET_OBJECT, false, false);
+		createVerifyOpenPack(interestings, NONE, false, false);
 
 		final ObjectId expectedOrder[] = new ObjectId[] {
 				ObjectId.fromString("82c6b885ff600be425b4ea96dee75dca255b69e7"),
@@ -699,12 +730,4 @@
 			assertEquals(objectsOrder[i++].toObjectId(), me.toObjectId());
 		}
 	}
-
-	private static ObjectIdSet objectIdSet(final PackIndex idx) {
-		return new ObjectIdSet() {
-			public boolean contains(AnyObjectId objectId) {
-				return idx.hasObject(objectId);
-			}
-		};
-	}
 }
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefDirectoryTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefDirectoryTest.java
index d66753d..ef5dfcd 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefDirectoryTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefDirectoryTest.java
@@ -858,6 +858,36 @@
 	}
 
 	@Test
+	public void testGetRef_CycleInSymbolicRef() throws IOException {
+		Ref r;
+
+		writeLooseRef("refs/1", "ref: refs/2\n");
+		writeLooseRef("refs/2", "ref: refs/3\n");
+		writeLooseRef("refs/3", "ref: refs/4\n");
+		writeLooseRef("refs/4", "ref: refs/5\n");
+		writeLooseRef("refs/5", "ref: refs/end\n");
+		writeLooseRef("refs/end", A);
+
+		r = refdir.getRef("1");
+		assertEquals("refs/1", r.getName());
+		assertEquals(A, r.getObjectId());
+		assertTrue(r.isSymbolic());
+
+		writeLooseRef("refs/5", "ref: refs/6\n");
+		writeLooseRef("refs/6", "ref: refs/end\n");
+
+		r = refdir.getRef("1");
+		assertNull("missing 1 due to cycle", r);
+
+		writeLooseRef("refs/heads/1", B);
+
+		r = refdir.getRef("1");
+		assertEquals("refs/heads/1", r.getName());
+		assertEquals(B, r.getObjectId());
+		assertFalse(r.isSymbolic());
+	}
+
+	@Test
 	public void testGetRefs_PackedNotPeeled_Sorted() throws IOException {
 		Map<String, Ref> all;
 
@@ -1414,8 +1444,8 @@
 			// empty
 		}
 
-		public void beginTask(String title, int totalWork) {
-			this.totalWork = totalWork;
+		public void beginTask(String title, int total) {
+			this.totalWork = total;
 			lastWork = 0;
 		}
 
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefUpdateTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefUpdateTest.java
index 098b31f..7adf074 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefUpdateTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefUpdateTest.java
@@ -347,7 +347,7 @@
 		Result update = updateRef.update();
 		assertEquals(Result.FORCED, update);
 		assertEquals(ppid, db.resolve("HEAD"));
-		Ref ref = db.getRef("HEAD");
+		Ref ref = db.exactRef("HEAD");
 		assertEquals("HEAD", ref.getName());
 		assertTrue("is detached", !ref.isSymbolic());
 
@@ -377,7 +377,7 @@
 		Result update = updateRef.update();
 		assertEquals(Result.NEW, update);
 		assertEquals(ppid, db.resolve("HEAD"));
-		Ref ref = db.getRef("HEAD");
+		Ref ref = db.exactRef("HEAD");
 		assertEquals("HEAD", ref.getName());
 		assertTrue("is detached", !ref.isSymbolic());
 
@@ -558,13 +558,15 @@
 		assertEquals(ppid, db.resolve("refs/heads/master"));
 
 		// real test
-		RevCommit old = new RevWalk(db).parseCommit(ppid);
-		RefUpdate updateRef2 = db.updateRef("refs/heads/master");
-		updateRef2.setExpectedOldObjectId(old);
-		updateRef2.setNewObjectId(pid);
-		Result update2 = updateRef2.update();
-		assertEquals(Result.FAST_FORWARD, update2);
-		assertEquals(pid, db.resolve("refs/heads/master"));
+		try (RevWalk rw = new RevWalk(db)) {
+			RevCommit old = rw.parseCommit(ppid);
+			RefUpdate updateRef2 = db.updateRef("refs/heads/master");
+			updateRef2.setExpectedOldObjectId(old);
+			updateRef2.setNewObjectId(pid);
+			Result update2 = updateRef2.update();
+			assertEquals(Result.FAST_FORWARD, update2);
+			assertEquals(pid, db.resolve("refs/heads/master"));
+		}
 	}
 
 	/**
@@ -579,14 +581,13 @@
 		RefUpdate updateRef = db.updateRef("refs/heads/master");
 		updateRef.setNewObjectId(pid);
 		LockFile lockFile1 = new LockFile(new File(db.getDirectory(),
-				"refs/heads/master"), db.getFS());
+				"refs/heads/master"));
 		try {
 			assertTrue(lockFile1.lock()); // precondition to test
 			Result update = updateRef.update();
 			assertEquals(Result.LOCK_FAILURE, update);
 			assertEquals(opid, db.resolve("refs/heads/master"));
-			LockFile lockFile2 = new LockFile(new File(db.getDirectory(),"refs/heads/master"),
-					db.getFS());
+			LockFile lockFile2 = new LockFile(new File(db.getDirectory(),"refs/heads/master"));
 			assertFalse(lockFile2.lock()); // was locked, still is
 		} finally {
 			lockFile1.unlock();
@@ -681,13 +682,13 @@
 	public void testRenameBranchAlsoInPack() throws IOException {
 		ObjectId rb = db.resolve("refs/heads/b");
 		ObjectId rb2 = db.resolve("refs/heads/b~1");
-		assertEquals(Ref.Storage.PACKED, db.getRef("refs/heads/b").getStorage());
+		assertEquals(Ref.Storage.PACKED, db.exactRef("refs/heads/b").getStorage());
 		RefUpdate updateRef = db.updateRef("refs/heads/b");
 		updateRef.setNewObjectId(rb2);
 		updateRef.setForceUpdate(true);
 		Result update = updateRef.update();
 		assertEquals("internal check new ref is loose", Result.FORCED, update);
-		assertEquals(Ref.Storage.LOOSE, db.getRef("refs/heads/b").getStorage());
+		assertEquals(Ref.Storage.LOOSE, db.exactRef("refs/heads/b").getStorage());
 		writeReflog(db, rb, "Just a message", "refs/heads/b");
 		assertTrue("log on old branch", new File(db.getDirectory(),
 				"logs/refs/heads/b").exists());
@@ -707,9 +708,10 @@
 
 		// Create new Repository instance, to reread caches and make sure our
 		// assumptions are persistent.
-		Repository ndb = new FileRepository(db.getDirectory());
-		assertEquals(rb2, ndb.resolve("refs/heads/new/name"));
-		assertNull(ndb.resolve("refs/heads/b"));
+		try (Repository ndb = new FileRepository(db.getDirectory())) {
+			assertEquals(rb2, ndb.resolve("refs/heads/new/name"));
+			assertNull(ndb.resolve("refs/heads/b"));
+		}
 	}
 
 	public void tryRenameWhenLocked(String toLock, String fromName,
@@ -728,8 +730,7 @@
 				"logs/" + fromName).exists());
 
 		// "someone" has branch X locked
-		LockFile lockFile = new LockFile(new File(db.getDirectory(), toLock),
-				db.getFS());
+		LockFile lockFile = new LockFile(new File(db.getDirectory(), toLock));
 		try {
 			assertTrue(lockFile.lock());
 
@@ -751,7 +752,7 @@
 			assertNull(db.resolve(toName));
 			assertEquals(oldFromLog.toString(), db.getReflogReader(fromName)
 					.getReverseEntries().toString());
-			if (oldHeadId != null)
+			if (oldHeadId != null && oldHeadLog != null)
 				assertEquals(oldHeadLog.toString(), db.getReflogReader(
 						Constants.HEAD).getReverseEntries().toString());
 		} finally {
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RepositorySetupWorkDirTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RepositorySetupWorkDirTest.java
index 295ef45..84c2543 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RepositorySetupWorkDirTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RepositorySetupWorkDirTest.java
@@ -73,13 +73,14 @@
 	public void testIsBare_CreateRepositoryFromArbitraryGitDir()
 			throws Exception {
 		File gitDir = getFile("workdir");
-		assertTrue(new FileRepository(gitDir).isBare());
+		Repository repo = new FileRepositoryBuilder().setGitDir(gitDir).build();
+		assertTrue(repo.isBare());
 	}
 
 	@Test
 	public void testNotBare_CreateRepositoryFromDotGitGitDir() throws Exception {
 		File gitDir = getFile("workdir", Constants.DOT_GIT);
-		Repository repo = new FileRepository(gitDir);
+		Repository repo = new FileRepositoryBuilder().setGitDir(gitDir).build();
 		assertFalse(repo.isBare());
 		assertWorkdirPath(repo, "workdir");
 		assertGitdirPath(repo, "workdir", Constants.DOT_GIT);
@@ -89,7 +90,7 @@
 	public void testWorkdirIsParentDir_CreateRepositoryFromDotGitGitDir()
 			throws Exception {
 		File gitDir = getFile("workdir", Constants.DOT_GIT);
-		Repository repo = new FileRepository(gitDir);
+		Repository repo = new FileRepositoryBuilder().setGitDir(gitDir).build();
 		String workdir = repo.getWorkTree().getName();
 		assertEquals(workdir, "workdir");
 	}
@@ -157,8 +158,8 @@
 	@Test
 	public void testExceptionThrown_BareRepoGetWorkDir() throws Exception {
 		File gitDir = getFile("workdir");
-		try {
-			new FileRepository(gitDir).getWorkTree();
+		try (Repository repo = new FileRepository(gitDir)) {
+			repo.getWorkTree();
 			fail("Expected NoWorkTreeException missing");
 		} catch (NoWorkTreeException e) {
 			// expected
@@ -168,8 +169,8 @@
 	@Test
 	public void testExceptionThrown_BareRepoGetIndex() throws Exception {
 		File gitDir = getFile("workdir");
-		try {
-			new FileRepository(gitDir).readDirCache();
+		try (Repository repo = new FileRepository(gitDir)) {
+			repo.readDirCache();
 			fail("Expected NoWorkTreeException missing");
 		} catch (NoWorkTreeException e) {
 			// expected
@@ -179,8 +180,8 @@
 	@Test
 	public void testExceptionThrown_BareRepoGetIndexFile() throws Exception {
 		File gitDir = getFile("workdir");
-		try {
-			new FileRepository(gitDir).getIndexFile();
+		try (Repository repo = new FileRepository(gitDir)) {
+			repo.getIndexFile();
 			fail("Expected NoWorkTreeException missing");
 		} catch (NoWorkTreeException e) {
 			// expected
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/T0003_BasicTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/T0003_BasicTest.java
index 5670a96..b6ad22b 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/T0003_BasicTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/T0003_BasicTest.java
@@ -67,7 +67,6 @@
 import org.eclipse.jgit.lib.CommitBuilder;
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.FileMode;
-import org.eclipse.jgit.lib.FileTreeEntry;
 import org.eclipse.jgit.lib.ObjectDatabase;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.ObjectInserter;
@@ -75,7 +74,6 @@
 import org.eclipse.jgit.lib.RefUpdate;
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.lib.TagBuilder;
-import org.eclipse.jgit.lib.Tree;
 import org.eclipse.jgit.lib.TreeFormatter;
 import org.eclipse.jgit.revwalk.RevCommit;
 import org.eclipse.jgit.revwalk.RevTag;
@@ -86,7 +84,6 @@
 import org.eclipse.jgit.util.FileUtils;
 import org.junit.Test;
 
-@SuppressWarnings("deprecation")
 public class T0003_BasicTest extends SampleDataRepositoryTestCase {
 
 	@Test
@@ -357,11 +354,12 @@
 
 	@Test
 	public void test007_Open() throws IOException {
-		final FileRepository db2 = new FileRepository(db.getDirectory());
-		assertEquals(db.getDirectory(), db2.getDirectory());
-		assertEquals(db.getObjectDatabase().getDirectory(), db2
-				.getObjectDatabase().getDirectory());
-		assertNotSame(db.getConfig(), db2.getConfig());
+		try (final FileRepository db2 = new FileRepository(db.getDirectory())) {
+			assertEquals(db.getDirectory(), db2.getDirectory());
+			assertEquals(db.getObjectDatabase().getDirectory(), db2
+					.getObjectDatabase().getDirectory());
+			assertNotSame(db.getConfig(), db2.getConfig());
+		}
 	}
 
 	@Test
@@ -372,8 +370,7 @@
 				+ badvers + "\n";
 		write(cfg, configStr);
 
-		try {
-			new FileRepository(db.getDirectory());
+		try (FileRepository unused = new FileRepository(db.getDirectory())) {
 			fail("incorrectly opened a bad repository");
 		} catch (IllegalArgumentException ioe) {
 			assertNotNull(ioe.getMessage());
@@ -419,29 +416,6 @@
 	}
 
 	@Test
-	public void test012_SubtreeExternalSorting() throws IOException {
-		final ObjectId emptyBlob = insertEmptyBlob();
-		final Tree t = new Tree(db);
-		final FileTreeEntry e0 = t.addFile("a-");
-		final FileTreeEntry e1 = t.addFile("a-b");
-		final FileTreeEntry e2 = t.addFile("a/b");
-		final FileTreeEntry e3 = t.addFile("a=");
-		final FileTreeEntry e4 = t.addFile("a=b");
-
-		e0.setId(emptyBlob);
-		e1.setId(emptyBlob);
-		e2.setId(emptyBlob);
-		e3.setId(emptyBlob);
-		e4.setId(emptyBlob);
-
-		final Tree a = (Tree) t.findTreeMember("a");
-		a.setId(insertTree(a));
-		assertEquals(ObjectId
-				.fromString("b47a8f0a4190f7572e11212769090523e23eb1ea"),
-				insertTree(t));
-	}
-
-	@Test
 	public void test020_createBlobTag() throws IOException {
 		final ObjectId emptyId = insertEmptyBlob();
 		final TagBuilder t = new TagBuilder();
@@ -464,9 +438,8 @@
 	@Test
 	public void test021_createTreeTag() throws IOException {
 		final ObjectId emptyId = insertEmptyBlob();
-		final Tree almostEmptyTree = new Tree(db);
-		almostEmptyTree.addEntry(new FileTreeEntry(almostEmptyTree, emptyId,
-				"empty".getBytes(), false));
+		TreeFormatter almostEmptyTree = new TreeFormatter();
+		almostEmptyTree.append("empty", FileMode.REGULAR_FILE, emptyId);
 		final ObjectId almostEmptyTreeId = insertTree(almostEmptyTree);
 		final TagBuilder t = new TagBuilder();
 		t.setObjectId(almostEmptyTreeId, Constants.OBJ_TREE);
@@ -488,9 +461,8 @@
 	@Test
 	public void test022_createCommitTag() throws IOException {
 		final ObjectId emptyId = insertEmptyBlob();
-		final Tree almostEmptyTree = new Tree(db);
-		almostEmptyTree.addEntry(new FileTreeEntry(almostEmptyTree, emptyId,
-				"empty".getBytes(), false));
+		TreeFormatter almostEmptyTree = new TreeFormatter();
+		almostEmptyTree.append("empty", FileMode.REGULAR_FILE, emptyId);
 		final ObjectId almostEmptyTreeId = insertTree(almostEmptyTree);
 		final CommitBuilder almostEmptyCommit = new CommitBuilder();
 		almostEmptyCommit.setAuthor(new PersonIdent(author, 1154236443000L,
@@ -520,9 +492,8 @@
 	@Test
 	public void test023_createCommitNonAnullii() throws IOException {
 		final ObjectId emptyId = insertEmptyBlob();
-		final Tree almostEmptyTree = new Tree(db);
-		almostEmptyTree.addEntry(new FileTreeEntry(almostEmptyTree, emptyId,
-				"empty".getBytes(), false));
+		TreeFormatter almostEmptyTree = new TreeFormatter();
+		almostEmptyTree.append("empty", FileMode.REGULAR_FILE, emptyId);
 		final ObjectId almostEmptyTreeId = insertTree(almostEmptyTree);
 		CommitBuilder commit = new CommitBuilder();
 		commit.setTreeId(almostEmptyTreeId);
@@ -542,9 +513,8 @@
 	@Test
 	public void test024_createCommitNonAscii() throws IOException {
 		final ObjectId emptyId = insertEmptyBlob();
-		final Tree almostEmptyTree = new Tree(db);
-		almostEmptyTree.addEntry(new FileTreeEntry(almostEmptyTree, emptyId,
-				"empty".getBytes(), false));
+		TreeFormatter almostEmptyTree = new TreeFormatter();
+		almostEmptyTree.append("empty", FileMode.REGULAR_FILE, emptyId);
 		final ObjectId almostEmptyTreeId = insertTree(almostEmptyTree);
 		CommitBuilder commit = new CommitBuilder();
 		commit.setTreeId(almostEmptyTreeId);
@@ -562,9 +532,10 @@
 	public void test025_computeSha1NoStore() throws IOException {
 		byte[] data = "test025 some data, more than 16 bytes to get good coverage"
 				.getBytes("ISO-8859-1");
-		final ObjectId id = new ObjectInserter.Formatter().idFor(
-				Constants.OBJ_BLOB, data);
-		assertEquals("4f561df5ecf0dfbd53a0dc0f37262fef075d9dde", id.name());
+		try (ObjectInserter.Formatter formatter = new ObjectInserter.Formatter()) {
+			final ObjectId id = formatter.idFor(Constants.OBJ_BLOB, data);
+			assertEquals("4f561df5ecf0dfbd53a0dc0f37262fef075d9dde", id.name());
+		}
 	}
 
 	@Test
@@ -746,14 +717,6 @@
 		return emptyId;
 	}
 
-	private ObjectId insertTree(Tree tree) throws IOException {
-		try (ObjectInserter oi = db.newObjectInserter()) {
-			ObjectId id = oi.insert(Constants.OBJ_TREE, tree.format());
-			oi.flush();
-			return id;
-		}
-	}
-
 	private ObjectId insertTree(TreeFormatter tree) throws IOException {
 		try (ObjectInserter oi = db.newObjectInserter()) {
 			ObjectId id = oi.insert(tree);
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/UnpackedObjectTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/UnpackedObjectTest.java
index 8c8c6c6..c6653bf 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/UnpackedObjectTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/UnpackedObjectTest.java
@@ -143,7 +143,7 @@
 	public void testStandardFormat_LargeObject() throws Exception {
 		final int type = Constants.OBJ_BLOB;
 		byte[] data = getRng().nextBytes(streamThreshold + 5);
-		ObjectId id = new ObjectInserter.Formatter().idFor(type, data);
+		ObjectId id = getId(type, data);
 		write(id, compressStandardFormat(type, data));
 
 		ObjectLoader ol;
@@ -306,7 +306,7 @@
 			throws Exception {
 		final int type = Constants.OBJ_BLOB;
 		byte[] data = getRng().nextBytes(streamThreshold + 5);
-		ObjectId id = new ObjectInserter.Formatter().idFor(type, data);
+		ObjectId id = getId(type, data);
 		byte[] gz = compressStandardFormat(type, data);
 		gz[gz.length - 1] = 0;
 		gz[gz.length - 2] = 0;
@@ -344,7 +344,7 @@
 			throws Exception {
 		final int type = Constants.OBJ_BLOB;
 		byte[] data = getRng().nextBytes(streamThreshold + 5);
-		ObjectId id = new ObjectInserter.Formatter().idFor(type, data);
+		ObjectId id = getId(type, data);
 		byte[] gz = compressStandardFormat(type, data);
 		byte[] tr = new byte[gz.length - 1];
 		System.arraycopy(gz, 0, tr, 0, tr.length);
@@ -379,7 +379,7 @@
 			throws Exception {
 		final int type = Constants.OBJ_BLOB;
 		byte[] data = getRng().nextBytes(streamThreshold + 5);
-		ObjectId id = new ObjectInserter.Formatter().idFor(type, data);
+		ObjectId id = getId(type, data);
 		byte[] gz = compressStandardFormat(type, data);
 		byte[] tr = new byte[gz.length + 1];
 		System.arraycopy(gz, 0, tr, 0, gz.length);
@@ -438,7 +438,7 @@
 	public void testPackFormat_LargeObject() throws Exception {
 		final int type = Constants.OBJ_BLOB;
 		byte[] data = getRng().nextBytes(streamThreshold + 5);
-		ObjectId id = new ObjectInserter.Formatter().idFor(type, data);
+		ObjectId id = getId(type, data);
 		write(id, compressPackFormat(type, data));
 
 		ObjectLoader ol;
@@ -578,4 +578,10 @@
 			out.close();
 		}
 	}
+
+	private ObjectId getId(int type, byte[] data) {
+		try (ObjectInserter.Formatter formatter = new ObjectInserter.Formatter()) {
+			return formatter.idFor(type, data);
+		}
+	}
 }
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/pack/GcCommitSelectionTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/pack/GcCommitSelectionTest.java
new file mode 100644
index 0000000..5fda070
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/pack/GcCommitSelectionTest.java
@@ -0,0 +1,271 @@
+/*
+ * Copyright (C) 2015, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ *   copyright notice, this list of conditions and the following
+ *   disclaimer in the documentation and/or other materials provided
+ *   with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ *   names of its contributors may be used to endorse or promote
+ *   products derived from this software without specific prior
+ *   written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.internal.storage.pack;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+import org.eclipse.jgit.internal.storage.file.GcTestCase;
+import org.eclipse.jgit.internal.storage.file.PackBitmapIndexBuilder;
+import org.eclipse.jgit.internal.storage.pack.ObjectToPack;
+import org.eclipse.jgit.internal.storage.pack.PackWriterBitmapPreparer;
+import org.eclipse.jgit.internal.storage.pack.PackWriterBitmapPreparer.BitmapCommit;
+import org.eclipse.jgit.junit.TestRepository.BranchBuilder;
+import org.eclipse.jgit.junit.TestRepository.CommitBuilder;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.NullProgressMonitor;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.storage.pack.PackConfig;
+import org.junit.Test;
+
+public class GcCommitSelectionTest extends GcTestCase {
+
+	@Test
+	public void testBitmapSpansNoMerges() throws Exception {
+		/*
+		 * Commit counts -> expected bitmap counts for history without merges.
+		 * The top 100 contiguous commits should always have bitmaps, and the
+		 * "recent" bitmaps beyond that are spaced out every 100-200 commits.
+		 * (Starting at 100, the next 100 commits are searched for a merge
+		 * commit. Since one is not found, the spacing between commits is 200.
+		 */
+		int[][] bitmapCounts = { //
+				{ 1, 1 }, { 50, 50 }, { 99, 99 }, { 100, 100 }, { 101, 100 },
+				{ 200, 100 }, { 201, 100 }, { 299, 100 }, { 300, 101 },
+				{ 301, 101 }, { 401, 101 }, { 499, 101 }, { 500, 102 }, };
+		int currentCommits = 0;
+		BranchBuilder bb = tr.branch("refs/heads/main");
+
+		for (int[] counts : bitmapCounts) {
+			int nextCommitCount = counts[0];
+			int expectedBitmapCount = counts[1];
+			assertTrue(nextCommitCount > currentCommits); // programming error
+			for (int i = currentCommits; i < nextCommitCount; i++) {
+				String str = "A" + i;
+				bb.commit().message(str).add(str, str).create();
+			}
+			currentCommits = nextCommitCount;
+
+			gc.setExpireAgeMillis(0); // immediately delete old packs
+			gc.gc();
+			assertEquals(currentCommits * 3, // commit/tree/object
+					gc.getStatistics().numberOfPackedObjects);
+			assertEquals(currentCommits + " commits: ", expectedBitmapCount,
+					gc.getStatistics().numberOfBitmaps);
+		}
+	}
+
+	@Test
+	public void testBitmapSpansWithMerges() throws Exception {
+		/*
+		 * Commits that are merged. Since 55 is in the oldest history it is
+		 * never considered. Searching goes from oldest to newest so 115 is the
+		 * first merge commit found. After that the range 116-216 is ignored so
+		 * 175 is never considered.
+		 */
+		List<Integer> merges = Arrays.asList(Integer.valueOf(55),
+				Integer.valueOf(115), Integer.valueOf(175),
+				Integer.valueOf(235));
+		/*
+		 * Commit counts -> expected bitmap counts for history with merges. The
+		 * top 100 contiguous commits should always have bitmaps, and the
+		 * "recent" bitmaps beyond that are spaced out every 100-200 commits.
+		 * Merges in the < 100 range have no effect and merges in the > 100
+		 * range will only be considered for commit counts > 200.
+		 */
+		int[][] bitmapCounts = { //
+				{ 1, 1 }, { 55, 55 }, { 56, 57 }, // +1 bitmap from branch A55
+				{ 99, 100 }, // still +1 branch @55
+				{ 100, 100 }, // 101 commits, only 100 newest
+				{ 116, 100 }, // @55 still in 100 newest bitmaps
+				{ 176, 101 }, // @55 branch tip is not in 100 newest
+				{ 213, 101 }, // 216 commits, @115&@175 in 100 newest
+				{ 214, 102 }, // @55 branch tip, merge @115, @177 in newest
+				{ 236, 102 }, // all 4 merge points in history
+				{ 273, 102 }, // 277 commits, @175&@235 in newest
+				{ 274, 103 }, // @55, @115, merge @175, @235 in newest
+				{ 334, 103 }, // @55,@115,@175, @235 in newest
+				{ 335, 104 }, // @55,@115,@175, merge @235
+				{ 435, 104 }, // @55,@115,@175,@235 tips
+				{ 436, 104 }, // force @236
+		};
+
+		int currentCommits = 0;
+		BranchBuilder bb = tr.branch("refs/heads/main");
+
+		for (int[] counts : bitmapCounts) {
+			int nextCommitCount = counts[0];
+			int expectedBitmapCount = counts[1];
+			assertTrue(nextCommitCount > currentCommits); // programming error
+			for (int i = currentCommits; i < nextCommitCount; i++) {
+				String str = "A" + i;
+				if (!merges.contains(Integer.valueOf(i))) {
+					bb.commit().message(str).add(str, str).create();
+				} else {
+					BranchBuilder bbN = tr.branch("refs/heads/A" + i);
+					bb.commit().message(str).add(str, str)
+							.parent(bbN.commit().create()).create();
+				}
+			}
+			currentCommits = nextCommitCount;
+
+			gc.setExpireAgeMillis(0); // immediately delete old packs
+			gc.gc();
+			assertEquals(currentCommits + " commits: ", expectedBitmapCount,
+					gc.getStatistics().numberOfBitmaps);
+		}
+	}
+
+	@Test
+	public void testBitmapsForExcessiveBranches() throws Exception {
+		int oneDayInSeconds = 60 * 60 * 24;
+
+		// All of branch A is committed on day1
+		BranchBuilder bbA = tr.branch("refs/heads/A");
+		for (int i = 0; i < 1001; i++) {
+			String msg = "A" + i;
+			bbA.commit().message(msg).add(msg, msg).create();
+		}
+		// All of in branch B is committed on day91
+		tr.tick(oneDayInSeconds * 90);
+		BranchBuilder bbB = tr.branch("refs/heads/B");
+		for (int i = 0; i < 1001; i++) {
+			String msg = "B" + i;
+			bbB.commit().message(msg).add(msg, msg).create();
+		}
+		// Create 100 other branches with a single commit
+		for (int i = 0; i < 100; i++) {
+			BranchBuilder bb = tr.branch("refs/heads/N" + i);
+			String msg = "singlecommit" + i;
+			bb.commit().message(msg).add(msg, msg).create();
+		}
+		// now is day92
+		tr.tick(oneDayInSeconds);
+
+		// Since there are no merges, commits in recent history are selected
+		// every 200 commits.
+		final int commitsForSparseBranch = 1 + (1001 / 200);
+		final int commitsForFullBranch = 100 + (901 / 200);
+		final int commitsForShallowBranches = 100;
+
+		// Excessive branch history pruning, one old branch.
+		gc.setExpireAgeMillis(0); // immediately delete old packs
+		gc.gc();
+		assertEquals(
+				commitsForSparseBranch + commitsForFullBranch
+						+ commitsForShallowBranches,
+				gc.getStatistics().numberOfBitmaps);
+	}
+
+	@Test
+	public void testSelectionOrderingWithChains() throws Exception {
+		/*-
+		 * Create a history like this, where 'N' is the number of seconds from
+		 * the first commit in the branch:
+		 *
+		 *      ---o---o---o        commits b3,b5,b7
+		 *     /            \
+		 * o--o--o---o---o---o--o   commits m0,m1,m2,m4,m6,m8,m9
+		 */
+		BranchBuilder bb = tr.branch("refs/heads/main");
+		RevCommit m0 = addCommit(bb, "m0");
+		RevCommit m1 = addCommit(bb, "m1", m0);
+		RevCommit m2 = addCommit(bb, "m2", m1);
+		RevCommit b3 = addCommit(bb, "b3", m1);
+		RevCommit m4 = addCommit(bb, "m4", m2);
+		RevCommit b5 = addCommit(bb, "m5", b3);
+		RevCommit m6 = addCommit(bb, "m6", m4);
+		RevCommit b7 = addCommit(bb, "m7", b5);
+		RevCommit m8 = addCommit(bb, "m8", m6, b7);
+		RevCommit m9 = addCommit(bb, "m9", m8);
+
+		List<RevCommit> commits = Arrays.asList(m0, m1, m2, b3, m4, b5, m6, b7,
+				m8, m9);
+		PackWriterBitmapPreparer preparer = newPeparer(m9, commits);
+		List<BitmapCommit> selection = new ArrayList<>(
+				preparer.selectCommits(commits.size()));
+
+		// Verify that the output is ordered by the separate "chains"
+		String[] expected = { m0.name(), m1.name(), m2.name(), m4.name(),
+				m6.name(), m8.name(), m9.name(), b3.name(), b5.name(),
+				b7.name() };
+		assertEquals(expected.length, selection.size());
+		for (int i = 0; i < expected.length; i++) {
+			assertEquals("Entry " + i, expected[i], selection.get(i).getName());
+		}
+	}
+
+	private RevCommit addCommit(BranchBuilder bb, String msg,
+			RevCommit... parents) throws Exception {
+		CommitBuilder commit = bb.commit().message(msg).add(msg, msg).tick(1)
+				.noParents();
+		for (RevCommit parent : parents) {
+			commit.parent(parent);
+		}
+		return commit.create();
+	}
+
+	private PackWriterBitmapPreparer newPeparer(RevCommit want,
+			List<RevCommit> commits)
+			throws IOException {
+		List<ObjectToPack> objects = new ArrayList<>(commits.size());
+		for (RevCommit commit : commits) {
+			objects.add(new ObjectToPack(commit, Constants.OBJ_COMMIT));
+		}
+		Set<ObjectId> wants = Collections.singleton((ObjectId) want);
+		PackConfig config = new PackConfig();
+		PackBitmapIndexBuilder builder = new PackBitmapIndexBuilder(objects);
+		return new PackWriterBitmapPreparer(
+				tr.getRepository().newObjectReader(), builder,
+				NullProgressMonitor.INSTANCE, wants, config);
+	}
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/pack/PackWriterBitmapPreparerTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/pack/PackWriterBitmapPreparerTest.java
new file mode 100644
index 0000000..b0f92ff
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/pack/PackWriterBitmapPreparerTest.java
@@ -0,0 +1,136 @@
+package org.eclipse.jgit.internal.storage.pack;
+
+import static org.eclipse.jgit.storage.pack.PackConfig.DEFAULT_BITMAP_DISTANT_COMMIT_SPAN;
+import static org.eclipse.jgit.storage.pack.PackConfig.DEFAULT_BITMAP_RECENT_COMMIT_COUNT;
+import static org.eclipse.jgit.storage.pack.PackConfig.DEFAULT_BITMAP_RECENT_COMMIT_SPAN;
+import static org.junit.Assert.assertEquals;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+import org.eclipse.jgit.errors.IncorrectObjectTypeException;
+import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.internal.storage.file.PackBitmapIndexBuilder;
+import org.eclipse.jgit.lib.AbbreviatedObjectId;
+import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectLoader;
+import org.eclipse.jgit.lib.ObjectReader;
+import org.eclipse.jgit.storage.pack.PackConfig;
+import org.junit.Test;
+
+/** Tests for the {@link PackWriterBitmapPreparer}. */
+public class PackWriterBitmapPreparerTest {
+	private static class StubObjectReader extends ObjectReader {
+		@Override
+		public ObjectReader newReader() {
+			return null;
+		}
+
+		@Override
+		public Collection<ObjectId> resolve(AbbreviatedObjectId id)
+				throws IOException {
+			return null;
+		}
+
+		@Override
+		public ObjectLoader open(AnyObjectId objectId, int typeHint)
+				throws MissingObjectException, IncorrectObjectTypeException,
+				IOException {
+			return null;
+		}
+
+		@Override
+		public Set<ObjectId> getShallowCommits() throws IOException {
+			return null;
+		}
+
+		@Override
+		public void close() {
+			// stub
+		}
+	}
+
+	@Test
+	public void testNextSelectionDistanceForActiveBranch() throws Exception {
+		PackWriterBitmapPreparer preparer = newPeparer(
+				DEFAULT_BITMAP_RECENT_COMMIT_COUNT, // 20000
+				DEFAULT_BITMAP_RECENT_COMMIT_SPAN, // 100
+				DEFAULT_BITMAP_DISTANT_COMMIT_SPAN); // 5000
+		int[][] distancesAndSpans = { { 0, 100 }, { 100, 100 }, { 10000, 100 },
+				{ 20000, 100 }, { 20100, 100 }, { 20102, 102 }, { 20200, 200 },
+				{ 22200, 2200 }, { 24999, 4999 }, { 25000, 5000 },
+				{ 50000, 5000 }, { 1000000, 5000 }, };
+
+		for (int[] pair : distancesAndSpans) {
+			assertEquals(pair[1], preparer.nextSpan(pair[0]));
+		}
+	}
+
+	@Test
+	public void testNextSelectionDistanceWithFewerRecentCommits()
+			throws Exception {
+		PackWriterBitmapPreparer preparer = newPeparer(1000,
+				DEFAULT_BITMAP_RECENT_COMMIT_SPAN, // 100
+				DEFAULT_BITMAP_DISTANT_COMMIT_SPAN); // 5000
+		int[][] distancesAndSpans = { { 0, 100 }, { 100, 100 }, { 1000, 100 },
+				{ 1100, 100 }, { 1111, 111 }, { 2000, 1000 }, { 5999, 4999 },
+				{ 6000, 5000 }, { 10000, 5000 }, { 50000, 5000 },
+				{ 1000000, 5000 } };
+
+		for (int[] pair : distancesAndSpans) {
+			assertEquals(pair[1], preparer.nextSpan(pair[0]));
+		}
+	}
+
+	@Test
+	public void testNextSelectionDistanceWithSmallerRecentSpan()
+			throws Exception {
+		PackWriterBitmapPreparer preparer = newPeparer(
+				DEFAULT_BITMAP_RECENT_COMMIT_COUNT, // 20000
+				10, // recent span
+				DEFAULT_BITMAP_DISTANT_COMMIT_SPAN); // 5000
+		int[][] distancesAndSpans = { { 0, 10 }, { 100, 10 }, { 10000, 10 },
+				{ 20000, 10 }, { 20010, 10 }, { 20012, 12 }, { 20050, 50 },
+				{ 20200, 200 }, { 22200, 2200 }, { 24999, 4999 },
+				{ 25000, 5000 }, { 50000, 5000 }, { 1000000, 5000 } };
+
+		for (int[] pair : distancesAndSpans) {
+			assertEquals(pair[1], preparer.nextSpan(pair[0]));
+		}
+	}
+
+	@Test
+	public void testNextSelectionDistanceWithSmallerDistantSpan()
+			throws Exception {
+		PackWriterBitmapPreparer preparer = newPeparer(
+				DEFAULT_BITMAP_RECENT_COMMIT_COUNT, // 20000
+				DEFAULT_BITMAP_RECENT_COMMIT_SPAN, // 100
+				1000);
+		int[][] distancesAndSpans = { { 0, 100 }, { 100, 100 }, { 10000, 100 },
+				{ 20000, 100 }, { 20100, 100 }, { 20102, 102 }, { 20200, 200 },
+				{ 20999, 999 }, { 21000, 1000 }, { 22000, 1000 },
+				{ 25000, 1000 }, { 50000, 1000 }, { 1000000, 1000 } };
+
+		for (int[] pair : distancesAndSpans) {
+			assertEquals(pair[1], preparer.nextSpan(pair[0]));
+		}
+	}
+
+	private PackWriterBitmapPreparer newPeparer(int recentCount, int recentSpan,
+			int distantSpan) throws IOException {
+		List<ObjectToPack> objects = Collections.emptyList();
+		Set<ObjectId> wants = Collections.emptySet();
+		PackConfig config = new PackConfig();
+		config.setBitmapRecentCommitCount(recentCount);
+		config.setBitmapRecentCommitSpan(recentSpan);
+		config.setBitmapDistantCommitSpan(distantSpan);
+		PackBitmapIndexBuilder indexBuilder = new PackBitmapIndexBuilder(
+				objects);
+		return new PackWriterBitmapPreparer(new StubObjectReader(),
+				indexBuilder, null, wants, config);
+	}
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftree/RefTreeDatabaseTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftree/RefTreeDatabaseTest.java
new file mode 100644
index 0000000..020d1b1
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftree/RefTreeDatabaseTest.java
@@ -0,0 +1,685 @@
+/*
+ * Copyright (C) 2010, 2013, 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.internal.storage.reftree;
+
+import static org.eclipse.jgit.lib.Constants.HEAD;
+import static org.eclipse.jgit.lib.Constants.R_HEADS;
+import static org.eclipse.jgit.lib.Constants.R_TAGS;
+import static org.eclipse.jgit.lib.Ref.Storage.LOOSE;
+import static org.eclipse.jgit.lib.Ref.Storage.PACKED;
+import static org.eclipse.jgit.lib.RefDatabase.ALL;
+import static org.eclipse.jgit.transport.ReceiveCommand.Result.LOCK_FAILURE;
+import static org.eclipse.jgit.transport.ReceiveCommand.Result.OK;
+import static org.eclipse.jgit.transport.ReceiveCommand.Result.REJECTED_NONFASTFORWARD;
+import static org.eclipse.jgit.transport.ReceiveCommand.Result.REJECTED_OTHER_REASON;
+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.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.io.IOException;
+import java.text.MessageFormat;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.jgit.internal.JGitText;
+import org.eclipse.jgit.internal.storage.dfs.DfsRepositoryDescription;
+import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
+import org.eclipse.jgit.junit.TestRepository;
+import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.lib.BatchRefUpdate;
+import org.eclipse.jgit.lib.CommitBuilder;
+import org.eclipse.jgit.lib.NullProgressMonitor;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectIdRef;
+import org.eclipse.jgit.lib.ObjectInserter;
+import org.eclipse.jgit.lib.ObjectReader;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.RefDatabase;
+import org.eclipse.jgit.lib.RefUpdate;
+import org.eclipse.jgit.lib.SymbolicRef;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevTag;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.transport.ReceiveCommand;
+import org.junit.Before;
+import org.junit.Test;
+
+public class RefTreeDatabaseTest {
+	private InMemRefTreeRepo repo;
+	private RefTreeDatabase refdb;
+	private RefDatabase bootstrap;
+
+	private TestRepository<InMemRefTreeRepo> testRepo;
+	private RevCommit A;
+	private RevCommit B;
+	private RevTag v1_0;
+
+	@Before
+	public void setUp() throws Exception {
+		repo = new InMemRefTreeRepo(new DfsRepositoryDescription("test"));
+		bootstrap = refdb.getBootstrap();
+
+		testRepo = new TestRepository<>(repo);
+		A = testRepo.commit().create();
+		B = testRepo.commit(testRepo.getRevWalk().parseCommit(A));
+		v1_0 = testRepo.tag("v1_0", B);
+		testRepo.getRevWalk().parseBody(v1_0);
+	}
+
+	@Test
+	public void testSupportsAtomic() {
+		assertTrue(refdb.performsAtomicTransactions());
+	}
+
+	@Test
+	public void testGetRefs_EmptyDatabase() throws IOException {
+		assertTrue("no references", refdb.getRefs(ALL).isEmpty());
+		assertTrue("no references", refdb.getRefs(R_HEADS).isEmpty());
+		assertTrue("no references", refdb.getRefs(R_TAGS).isEmpty());
+	}
+
+	@Test
+	public void testGetRefs_HeadOnOneBranch() throws IOException {
+		symref(HEAD, "refs/heads/master");
+		update("refs/heads/master", A);
+
+		Map<String, Ref> all = refdb.getRefs(ALL);
+		assertEquals(2, all.size());
+		assertTrue("has HEAD", all.containsKey(HEAD));
+		assertTrue("has master", all.containsKey("refs/heads/master"));
+
+		Ref head = all.get(HEAD);
+		Ref master = all.get("refs/heads/master");
+
+		assertEquals(HEAD, head.getName());
+		assertTrue(head.isSymbolic());
+		assertSame(LOOSE, head.getStorage());
+		assertSame("uses same ref as target", master, head.getTarget());
+
+		assertEquals("refs/heads/master", master.getName());
+		assertFalse(master.isSymbolic());
+		assertSame(PACKED, master.getStorage());
+		assertEquals(A, master.getObjectId());
+	}
+
+	@Test
+	public void testGetRefs_DetachedHead() throws IOException {
+		update(HEAD, A);
+
+		Map<String, Ref> all = refdb.getRefs(ALL);
+		assertEquals(1, all.size());
+		assertTrue("has HEAD", all.containsKey(HEAD));
+
+		Ref head = all.get(HEAD);
+		assertEquals(HEAD, head.getName());
+		assertFalse(head.isSymbolic());
+		assertSame(PACKED, head.getStorage());
+		assertEquals(A, head.getObjectId());
+	}
+
+	@Test
+	public void testGetRefs_DeeplyNestedBranch() throws IOException {
+		String name = "refs/heads/a/b/c/d/e/f/g/h/i/j/k";
+		update(name, A);
+
+		Map<String, Ref> all = refdb.getRefs(ALL);
+		assertEquals(1, all.size());
+
+		Ref r = all.get(name);
+		assertEquals(name, r.getName());
+		assertFalse(r.isSymbolic());
+		assertSame(PACKED, r.getStorage());
+		assertEquals(A, r.getObjectId());
+	}
+
+	@Test
+	public void testGetRefs_HeadBranchNotBorn() throws IOException {
+		update("refs/heads/A", A);
+		update("refs/heads/B", B);
+
+		Map<String, Ref> all = refdb.getRefs(ALL);
+		assertEquals(2, all.size());
+		assertFalse("no HEAD", all.containsKey(HEAD));
+
+		Ref a = all.get("refs/heads/A");
+		Ref b = all.get("refs/heads/B");
+
+		assertEquals(A, a.getObjectId());
+		assertEquals(B, b.getObjectId());
+
+		assertEquals("refs/heads/A", a.getName());
+		assertEquals("refs/heads/B", b.getName());
+	}
+
+	@Test
+	public void testGetRefs_HeadsOnly() throws IOException {
+		update("refs/heads/A", A);
+		update("refs/heads/B", B);
+		update("refs/tags/v1.0", v1_0);
+
+		Map<String, Ref> heads = refdb.getRefs(R_HEADS);
+		assertEquals(2, heads.size());
+
+		Ref a = heads.get("A");
+		Ref b = heads.get("B");
+
+		assertEquals("refs/heads/A", a.getName());
+		assertEquals("refs/heads/B", b.getName());
+
+		assertEquals(A, a.getObjectId());
+		assertEquals(B, b.getObjectId());
+	}
+
+	@Test
+	public void testGetRefs_TagsOnly() throws IOException {
+		update("refs/heads/A", A);
+		update("refs/heads/B", B);
+		update("refs/tags/v1.0", v1_0);
+
+		Map<String, Ref> tags = refdb.getRefs(R_TAGS);
+		assertEquals(1, tags.size());
+
+		Ref a = tags.get("v1.0");
+		assertEquals("refs/tags/v1.0", a.getName());
+		assertEquals(v1_0, a.getObjectId());
+		assertTrue(a.isPeeled());
+		assertEquals(v1_0.getObject(), a.getPeeledObjectId());
+	}
+
+	@Test
+	public void testGetRefs_HeadsSymref() throws IOException {
+		symref("refs/heads/other", "refs/heads/master");
+		update("refs/heads/master", A);
+
+		Map<String, Ref> heads = refdb.getRefs(R_HEADS);
+		assertEquals(2, heads.size());
+
+		Ref master = heads.get("master");
+		Ref other = heads.get("other");
+
+		assertEquals("refs/heads/master", master.getName());
+		assertEquals(A, master.getObjectId());
+
+		assertEquals("refs/heads/other", other.getName());
+		assertEquals(A, other.getObjectId());
+		assertSame(master, other.getTarget());
+	}
+
+	@Test
+	public void testGetRefs_InvalidPrefixes() throws IOException {
+		update("refs/heads/A", A);
+
+		assertTrue("empty refs/heads", refdb.getRefs("refs/heads").isEmpty());
+		assertTrue("empty objects", refdb.getRefs("objects").isEmpty());
+		assertTrue("empty objects/", refdb.getRefs("objects/").isEmpty());
+	}
+
+	@Test
+	public void testGetRefs_DiscoversNew() throws IOException {
+		update("refs/heads/master", A);
+		Map<String, Ref> orig = refdb.getRefs(ALL);
+
+		update("refs/heads/next", B);
+		Map<String, Ref> next = refdb.getRefs(ALL);
+
+		assertEquals(1, orig.size());
+		assertEquals(2, next.size());
+
+		assertFalse(orig.containsKey("refs/heads/next"));
+		assertTrue(next.containsKey("refs/heads/next"));
+
+		assertEquals(A, next.get("refs/heads/master").getObjectId());
+		assertEquals(B, next.get("refs/heads/next").getObjectId());
+	}
+
+	@Test
+	public void testGetRefs_DiscoversModified() throws IOException {
+		symref(HEAD, "refs/heads/master");
+		update("refs/heads/master", A);
+
+		Map<String, Ref> all = refdb.getRefs(ALL);
+		assertEquals(A, all.get(HEAD).getObjectId());
+
+		update("refs/heads/master", B);
+		all = refdb.getRefs(ALL);
+		assertEquals(B, all.get(HEAD).getObjectId());
+		assertEquals(B, refdb.exactRef(HEAD).getObjectId());
+	}
+
+	@Test
+	public void testGetRefs_CycleInSymbolicRef() throws IOException {
+		symref("refs/1", "refs/2");
+		symref("refs/2", "refs/3");
+		symref("refs/3", "refs/4");
+		symref("refs/4", "refs/5");
+		symref("refs/5", "refs/end");
+		update("refs/end", A);
+
+		Map<String, Ref> all = refdb.getRefs(ALL);
+		Ref r = all.get("refs/1");
+		assertNotNull("has 1", r);
+
+		assertEquals("refs/1", r.getName());
+		assertEquals(A, r.getObjectId());
+		assertTrue(r.isSymbolic());
+
+		r = r.getTarget();
+		assertEquals("refs/2", r.getName());
+		assertEquals(A, r.getObjectId());
+		assertTrue(r.isSymbolic());
+
+		r = r.getTarget();
+		assertEquals("refs/3", r.getName());
+		assertEquals(A, r.getObjectId());
+		assertTrue(r.isSymbolic());
+
+		r = r.getTarget();
+		assertEquals("refs/4", r.getName());
+		assertEquals(A, r.getObjectId());
+		assertTrue(r.isSymbolic());
+
+		r = r.getTarget();
+		assertEquals("refs/5", r.getName());
+		assertEquals(A, r.getObjectId());
+		assertTrue(r.isSymbolic());
+
+		r = r.getTarget();
+		assertEquals("refs/end", r.getName());
+		assertEquals(A, r.getObjectId());
+		assertFalse(r.isSymbolic());
+
+		symref("refs/5", "refs/6");
+		symref("refs/6", "refs/end");
+		all = refdb.getRefs(ALL);
+		assertNull("mising 1 due to cycle", all.get("refs/1"));
+		assertEquals(A, all.get("refs/2").getObjectId());
+		assertEquals(A, all.get("refs/3").getObjectId());
+		assertEquals(A, all.get("refs/4").getObjectId());
+		assertEquals(A, all.get("refs/5").getObjectId());
+		assertEquals(A, all.get("refs/6").getObjectId());
+		assertEquals(A, all.get("refs/end").getObjectId());
+	}
+
+	@Test
+	public void testGetRef_NonExistingBranchConfig() throws IOException {
+		assertNull("find branch config", refdb.getRef("config"));
+		assertNull("find branch config", refdb.getRef("refs/heads/config"));
+	}
+
+	@Test
+	public void testGetRef_FindBranchConfig() throws IOException {
+		update("refs/heads/config", A);
+
+		for (String t : new String[] { "config", "refs/heads/config" }) {
+			Ref r = refdb.getRef(t);
+			assertNotNull("find branch config (" + t + ")", r);
+			assertEquals("for " + t, "refs/heads/config", r.getName());
+			assertEquals("for " + t, A, r.getObjectId());
+		}
+	}
+
+	@Test
+	public void testFirstExactRef() throws IOException {
+		update("refs/heads/A", A);
+		update("refs/tags/v1.0", v1_0);
+
+		Ref a = refdb.firstExactRef("refs/heads/A", "refs/tags/v1.0");
+		Ref one = refdb.firstExactRef("refs/tags/v1.0", "refs/heads/A");
+
+		assertEquals("refs/heads/A", a.getName());
+		assertEquals("refs/tags/v1.0", one.getName());
+
+		assertEquals(A, a.getObjectId());
+		assertEquals(v1_0, one.getObjectId());
+	}
+
+	@Test
+	public void testExactRef_DiscoversModified() throws IOException {
+		symref(HEAD, "refs/heads/master");
+		update("refs/heads/master", A);
+		assertEquals(A, refdb.exactRef(HEAD).getObjectId());
+
+		update("refs/heads/master", B);
+		assertEquals(B, refdb.exactRef(HEAD).getObjectId());
+	}
+
+	@Test
+	public void testIsNameConflicting() throws IOException {
+		update("refs/heads/a/b", A);
+		update("refs/heads/q", B);
+
+		// new references cannot replace an existing container
+		assertTrue(refdb.isNameConflicting("refs"));
+		assertTrue(refdb.isNameConflicting("refs/heads"));
+		assertTrue(refdb.isNameConflicting("refs/heads/a"));
+
+		// existing reference is not conflicting
+		assertFalse(refdb.isNameConflicting("refs/heads/a/b"));
+
+		// new references are not conflicting
+		assertFalse(refdb.isNameConflicting("refs/heads/a/d"));
+		assertFalse(refdb.isNameConflicting("refs/heads/master"));
+
+		// existing reference must not be used as a container
+		assertTrue(refdb.isNameConflicting("refs/heads/a/b/c"));
+		assertTrue(refdb.isNameConflicting("refs/heads/q/master"));
+
+		// refs/txn/ names always conflict.
+		assertTrue(refdb.isNameConflicting(refdb.getTxnCommitted()));
+		assertTrue(refdb.isNameConflicting("refs/txn/foo"));
+	}
+
+	@Test
+	public void testUpdate_RefusesRefsTxnNamespace() throws IOException {
+		ObjectId txnId = getTxnCommitted();
+
+		RefUpdate u = refdb.newUpdate("refs/txn/tmp", false);
+		u.setNewObjectId(B);
+		assertEquals(RefUpdate.Result.LOCK_FAILURE, u.update());
+		assertEquals(txnId, getTxnCommitted());
+
+		ReceiveCommand cmd = command(null, B, "refs/txn/tmp");
+		BatchRefUpdate batch = refdb.newBatchUpdate();
+		batch.addCommand(cmd);
+		batch.execute(new RevWalk(repo), NullProgressMonitor.INSTANCE);
+
+		assertEquals(REJECTED_OTHER_REASON, cmd.getResult());
+		assertEquals(MessageFormat.format(JGitText.get().invalidRefName,
+				"refs/txn/tmp"), cmd.getMessage());
+		assertEquals(txnId, getTxnCommitted());
+	}
+
+	@Test
+	public void testUpdate_RefusesDotLockInRefName() throws IOException {
+		ObjectId txnId = getTxnCommitted();
+
+		RefUpdate u = refdb.newUpdate("refs/heads/pu.lock", false);
+		u.setNewObjectId(B);
+		assertEquals(RefUpdate.Result.REJECTED, u.update());
+		assertEquals(txnId, getTxnCommitted());
+
+		ReceiveCommand cmd = command(null, B, "refs/heads/pu.lock");
+		BatchRefUpdate batch = refdb.newBatchUpdate();
+		batch.addCommand(cmd);
+		batch.execute(new RevWalk(repo), NullProgressMonitor.INSTANCE);
+
+		assertEquals(REJECTED_OTHER_REASON, cmd.getResult());
+		assertEquals(JGitText.get().funnyRefname, cmd.getMessage());
+		assertEquals(txnId, getTxnCommitted());
+	}
+
+	@Test
+	public void testBatchRefUpdate_NonFastForwardAborts() throws IOException {
+		update("refs/heads/master", A);
+		update("refs/heads/masters", B);
+		ObjectId txnId = getTxnCommitted();
+
+		List<ReceiveCommand> commands = Arrays.asList(
+				command(A, B, "refs/heads/master"),
+				command(B, A, "refs/heads/masters"));
+		BatchRefUpdate batchUpdate = refdb.newBatchUpdate();
+		batchUpdate.addCommand(commands);
+		batchUpdate.execute(new RevWalk(repo), NullProgressMonitor.INSTANCE);
+		assertEquals(txnId, getTxnCommitted());
+
+		assertEquals(REJECTED_NONFASTFORWARD,
+				commands.get(1).getResult());
+		assertEquals(REJECTED_OTHER_REASON,
+				commands.get(0).getResult());
+		assertEquals(JGitText.get().transactionAborted,
+				commands.get(0).getMessage());
+	}
+
+	@Test
+	public void testBatchRefUpdate_ForceUpdate() throws IOException {
+		update("refs/heads/master", A);
+		update("refs/heads/masters", B);
+		ObjectId txnId = getTxnCommitted();
+
+		List<ReceiveCommand> commands = Arrays.asList(
+				command(A, B, "refs/heads/master"),
+				command(B, A, "refs/heads/masters"));
+		BatchRefUpdate batchUpdate = refdb.newBatchUpdate();
+		batchUpdate.setAllowNonFastForwards(true);
+		batchUpdate.addCommand(commands);
+		batchUpdate.execute(new RevWalk(repo), NullProgressMonitor.INSTANCE);
+		assertNotEquals(txnId, getTxnCommitted());
+
+		Map<String, Ref> refs = refdb.getRefs(ALL);
+		assertEquals(OK, commands.get(0).getResult());
+		assertEquals(OK, commands.get(1).getResult());
+		assertEquals(
+				"[refs/heads/master, refs/heads/masters]",
+				refs.keySet().toString());
+		assertEquals(B.getId(), refs.get("refs/heads/master").getObjectId());
+		assertEquals(A.getId(), refs.get("refs/heads/masters").getObjectId());
+	}
+
+	@Test
+	public void testBatchRefUpdate_NonFastForwardDoesNotDoExpensiveMergeCheck()
+			throws IOException {
+		update("refs/heads/master", B);
+		ObjectId txnId = getTxnCommitted();
+
+		List<ReceiveCommand> commands = Arrays.asList(
+				command(B, A, "refs/heads/master"));
+		BatchRefUpdate batchUpdate = refdb.newBatchUpdate();
+		batchUpdate.setAllowNonFastForwards(true);
+		batchUpdate.addCommand(commands);
+		batchUpdate.execute(new RevWalk(repo) {
+			@Override
+			public boolean isMergedInto(RevCommit base, RevCommit tip) {
+				fail("isMergedInto() should not be called");
+				return false;
+			}
+		}, NullProgressMonitor.INSTANCE);
+		assertNotEquals(txnId, getTxnCommitted());
+
+		Map<String, Ref> refs = refdb.getRefs(ALL);
+		assertEquals(OK, commands.get(0).getResult());
+		assertEquals(A.getId(), refs.get("refs/heads/master").getObjectId());
+	}
+
+	@Test
+	public void testBatchRefUpdate_ConflictCausesAbort() throws IOException {
+		update("refs/heads/master", A);
+		update("refs/heads/masters", B);
+		ObjectId txnId = getTxnCommitted();
+
+		List<ReceiveCommand> commands = Arrays.asList(
+				command(A, B, "refs/heads/master"),
+				command(null, A, "refs/heads/master/x"),
+				command(null, A, "refs/heads"));
+		BatchRefUpdate batchUpdate = refdb.newBatchUpdate();
+		batchUpdate.setAllowNonFastForwards(true);
+		batchUpdate.addCommand(commands);
+		batchUpdate.execute(new RevWalk(repo), NullProgressMonitor.INSTANCE);
+		assertEquals(txnId, getTxnCommitted());
+
+		assertEquals(LOCK_FAILURE, commands.get(0).getResult());
+
+		assertEquals(REJECTED_OTHER_REASON, commands.get(1).getResult());
+		assertEquals(JGitText.get().transactionAborted,
+				commands.get(1).getMessage());
+
+		assertEquals(REJECTED_OTHER_REASON, commands.get(2).getResult());
+		assertEquals(JGitText.get().transactionAborted,
+				commands.get(2).getMessage());
+	}
+
+	@Test
+	public void testBatchRefUpdate_NoConflictIfDeleted() throws IOException {
+		update("refs/heads/master", A);
+		update("refs/heads/masters", B);
+		ObjectId txnId = getTxnCommitted();
+
+		List<ReceiveCommand> commands = Arrays.asList(
+				command(A, B, "refs/heads/master"),
+				command(null, A, "refs/heads/masters/x"),
+				command(B, null, "refs/heads/masters"));
+		BatchRefUpdate batchUpdate = refdb.newBatchUpdate();
+		batchUpdate.setAllowNonFastForwards(true);
+		batchUpdate.addCommand(commands);
+		batchUpdate.execute(new RevWalk(repo), NullProgressMonitor.INSTANCE);
+		assertNotEquals(txnId, getTxnCommitted());
+
+		assertEquals(OK, commands.get(0).getResult());
+		assertEquals(OK, commands.get(1).getResult());
+		assertEquals(OK, commands.get(2).getResult());
+
+		Map<String, Ref> refs = refdb.getRefs(ALL);
+		assertEquals(
+				"[refs/heads/master, refs/heads/masters/x]",
+				refs.keySet().toString());
+		assertEquals(A.getId(), refs.get("refs/heads/masters/x").getObjectId());
+	}
+
+	private ObjectId getTxnCommitted() throws IOException {
+		Ref r = bootstrap.exactRef(refdb.getTxnCommitted());
+		if (r != null && r.getObjectId() != null) {
+			return r.getObjectId();
+		}
+		return ObjectId.zeroId();
+	}
+
+	private static ReceiveCommand command(AnyObjectId a, AnyObjectId b,
+			String name) {
+		return new ReceiveCommand(
+				a != null ? a.copy() : ObjectId.zeroId(),
+				b != null ? b.copy() : ObjectId.zeroId(),
+				name);
+	}
+
+	private void symref(final String name, final String dst)
+			throws IOException {
+		commit(new Function() {
+			@Override
+			public boolean apply(ObjectReader reader, RefTree tree)
+					throws IOException {
+				Ref old = tree.exactRef(reader, name);
+				Command n = new Command(
+					old,
+					new SymbolicRef(
+						name,
+						new ObjectIdRef.Unpeeled(Ref.Storage.NEW, dst, null)));
+				return tree.apply(Collections.singleton(n));
+			}
+		});
+	}
+
+	private void update(final String name, final ObjectId id)
+			throws IOException {
+		commit(new Function() {
+			@Override
+			public boolean apply(ObjectReader reader, RefTree tree)
+					throws IOException {
+				Ref old = tree.exactRef(reader, name);
+				Command n;
+				try (RevWalk rw = new RevWalk(repo)) {
+					n = new Command(old, Command.toRef(rw, id, name, true));
+				}
+				return tree.apply(Collections.singleton(n));
+			}
+		});
+	}
+
+	interface Function {
+		boolean apply(ObjectReader reader, RefTree tree) throws IOException;
+	}
+
+	private void commit(Function fun) throws IOException {
+		try (ObjectReader reader = repo.newObjectReader();
+				ObjectInserter inserter = repo.newObjectInserter();
+				RevWalk rw = new RevWalk(reader)) {
+			RefUpdate u = bootstrap.newUpdate(refdb.getTxnCommitted(), false);
+			CommitBuilder cb = new CommitBuilder();
+			testRepo.setAuthorAndCommitter(cb);
+
+			Ref ref = bootstrap.exactRef(refdb.getTxnCommitted());
+			RefTree tree;
+			if (ref != null && ref.getObjectId() != null) {
+				tree = RefTree.read(reader, rw.parseTree(ref.getObjectId()));
+				cb.setParentId(ref.getObjectId());
+				u.setExpectedOldObjectId(ref.getObjectId());
+			} else {
+				tree = RefTree.newEmptyTree();
+				u.setExpectedOldObjectId(ObjectId.zeroId());
+			}
+
+			assertTrue(fun.apply(reader, tree));
+			cb.setTreeId(tree.writeTree(inserter));
+			u.setNewObjectId(inserter.insert(cb));
+			inserter.flush();
+			switch (u.update(rw)) {
+			case NEW:
+			case FAST_FORWARD:
+				break;
+			default:
+				fail("Expected " + u.getName() + " to update");
+			}
+		}
+	}
+
+	private class InMemRefTreeRepo extends InMemoryRepository {
+		private final RefTreeDatabase refs;
+
+		InMemRefTreeRepo(DfsRepositoryDescription repoDesc) {
+			super(repoDesc);
+			refs = new RefTreeDatabase(this, super.getRefDatabase(),
+					"refs/txn/committed");
+			RefTreeDatabaseTest.this.refdb = refs;
+		}
+
+		public RefDatabase getRefDatabase() {
+			return refs;
+		}
+	}
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftree/RefTreeTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftree/RefTreeTest.java
new file mode 100644
index 0000000..8e0f38c
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftree/RefTreeTest.java
@@ -0,0 +1,303 @@
+/*
+ * 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.internal.storage.reftree;
+
+import static org.eclipse.jgit.lib.Constants.HEAD;
+import static org.eclipse.jgit.lib.Constants.R_HEADS;
+import static org.eclipse.jgit.lib.Constants.R_TAGS;
+import static org.eclipse.jgit.lib.Ref.Storage.LOOSE;
+import static org.eclipse.jgit.lib.Ref.Storage.NEW;
+import static org.eclipse.jgit.transport.ReceiveCommand.Result.LOCK_FAILURE;
+import static org.eclipse.jgit.transport.ReceiveCommand.Result.NOT_ATTEMPTED;
+import static org.eclipse.jgit.transport.ReceiveCommand.Result.REJECTED_OTHER_REASON;
+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.assertSame;
+import static org.junit.Assert.assertTrue;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collections;
+
+import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.internal.JGitText;
+import org.eclipse.jgit.internal.storage.dfs.DfsRepositoryDescription;
+import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
+import org.eclipse.jgit.junit.TestRepository;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectIdRef;
+import org.eclipse.jgit.lib.ObjectInserter;
+import org.eclipse.jgit.lib.ObjectReader;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.SymbolicRef;
+import org.eclipse.jgit.revwalk.RevBlob;
+import org.eclipse.jgit.revwalk.RevTag;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.transport.ReceiveCommand;
+import org.junit.Before;
+import org.junit.Test;
+
+public class RefTreeTest {
+	private static final String R_MASTER = R_HEADS + "master";
+	private InMemoryRepository repo;
+	private TestRepository<InMemoryRepository> git;
+
+	@Before
+	public void setUp() throws IOException {
+		repo = new InMemoryRepository(new DfsRepositoryDescription("RefTree"));
+		git = new TestRepository<>(repo);
+	}
+
+	@Test
+	public void testEmptyTree() throws IOException {
+		RefTree tree = RefTree.newEmptyTree();
+		try (ObjectReader reader = repo.newObjectReader()) {
+			assertNull(HEAD, tree.exactRef(reader, HEAD));
+			assertNull("master", tree.exactRef(reader, R_MASTER));
+		}
+	}
+
+	@Test
+	public void testApplyThenReadMaster() throws Exception {
+		RefTree tree = RefTree.newEmptyTree();
+		RevBlob id = git.blob("A");
+		Command cmd = new Command(null, ref(R_MASTER, id));
+		assertTrue(tree.apply(Collections.singletonList(cmd)));
+		assertSame(NOT_ATTEMPTED, cmd.getResult());
+
+		try (ObjectReader reader = repo.newObjectReader()) {
+			Ref m = tree.exactRef(reader, R_MASTER);
+			assertNotNull(R_MASTER, m);
+			assertEquals(R_MASTER, m.getName());
+			assertEquals(id, m.getObjectId());
+			assertTrue("peeled", m.isPeeled());
+		}
+	}
+
+	@Test
+	public void testUpdateMaster() throws Exception {
+		RefTree tree = RefTree.newEmptyTree();
+		RevBlob id1 = git.blob("A");
+		Command cmd1 = new Command(null, ref(R_MASTER, id1));
+		assertTrue(tree.apply(Collections.singletonList(cmd1)));
+		assertSame(NOT_ATTEMPTED, cmd1.getResult());
+
+		RevBlob id2 = git.blob("B");
+		Command cmd2 = new Command(ref(R_MASTER, id1), ref(R_MASTER, id2));
+		assertTrue(tree.apply(Collections.singletonList(cmd2)));
+		assertSame(NOT_ATTEMPTED, cmd2.getResult());
+
+		try (ObjectReader reader = repo.newObjectReader()) {
+			Ref m = tree.exactRef(reader, R_MASTER);
+			assertNotNull(R_MASTER, m);
+			assertEquals(R_MASTER, m.getName());
+			assertEquals(id2, m.getObjectId());
+			assertTrue("peeled", m.isPeeled());
+		}
+	}
+
+	@Test
+	public void testHeadSymref() throws Exception {
+		RefTree tree = RefTree.newEmptyTree();
+		RevBlob id = git.blob("A");
+		Command cmd1 = new Command(null, ref(R_MASTER, id));
+		Command cmd2 = new Command(null, symref(HEAD, R_MASTER));
+		assertTrue(tree.apply(Arrays.asList(new Command[] { cmd1, cmd2 })));
+		assertSame(NOT_ATTEMPTED, cmd1.getResult());
+		assertSame(NOT_ATTEMPTED, cmd2.getResult());
+
+		try (ObjectReader reader = repo.newObjectReader()) {
+			Ref m = tree.exactRef(reader, HEAD);
+			assertNotNull(HEAD, m);
+			assertEquals(HEAD, m.getName());
+			assertTrue("symbolic", m.isSymbolic());
+			assertNotNull(m.getTarget());
+			assertEquals(R_MASTER, m.getTarget().getName());
+			assertEquals(id, m.getTarget().getObjectId());
+		}
+
+		// Writing flushes some buffers, re-read from blob.
+		ObjectId newId = write(tree);
+		try (ObjectReader reader = repo.newObjectReader();
+				RevWalk rw = new RevWalk(reader)) {
+			tree = RefTree.read(reader, rw.parseTree(newId));
+			Ref m = tree.exactRef(reader, HEAD);
+			assertEquals(R_MASTER, m.getTarget().getName());
+		}
+	}
+
+	@Test
+	public void testTagIsPeeled() throws Exception {
+		String name = "v1.0";
+		RefTree tree = RefTree.newEmptyTree();
+		RevBlob id = git.blob("A");
+		RevTag tag = git.tag(name, id);
+
+		String ref = R_TAGS + name;
+		Command cmd = create(ref, tag);
+		assertTrue(tree.apply(Collections.singletonList(cmd)));
+		assertSame(NOT_ATTEMPTED, cmd.getResult());
+
+		try (ObjectReader reader = repo.newObjectReader()) {
+			Ref m = tree.exactRef(reader, ref);
+			assertNotNull(ref, m);
+			assertEquals(ref, m.getName());
+			assertEquals(tag, m.getObjectId());
+			assertTrue("peeled", m.isPeeled());
+			assertEquals(id, m.getPeeledObjectId());
+		}
+	}
+
+	@Test
+	public void testApplyAlreadyExists() throws Exception {
+		RefTree tree = RefTree.newEmptyTree();
+		RevBlob a = git.blob("A");
+		Command cmd = new Command(null, ref(R_MASTER, a));
+		assertTrue(tree.apply(Collections.singletonList(cmd)));
+		ObjectId treeId = write(tree);
+
+		RevBlob b = git.blob("B");
+		Command cmd1 = create(R_MASTER, b);
+		Command cmd2 = create(R_MASTER, b);
+		assertFalse(tree.apply(Arrays.asList(new Command[] { cmd1, cmd2 })));
+		assertSame(LOCK_FAILURE, cmd1.getResult());
+		assertSame(REJECTED_OTHER_REASON, cmd2.getResult());
+		assertEquals(JGitText.get().transactionAborted, cmd2.getMessage());
+		assertEquals(treeId, write(tree));
+	}
+
+	@Test
+	public void testApplyWrongOldId() throws Exception {
+		RefTree tree = RefTree.newEmptyTree();
+		RevBlob a = git.blob("A");
+		Command cmd = new Command(null, ref(R_MASTER, a));
+		assertTrue(tree.apply(Collections.singletonList(cmd)));
+		ObjectId treeId = write(tree);
+
+		RevBlob b = git.blob("B");
+		RevBlob c = git.blob("C");
+		Command cmd1 = update(R_MASTER, b, c);
+		Command cmd2 = create(R_MASTER, b);
+		assertFalse(tree.apply(Arrays.asList(new Command[] { cmd1, cmd2 })));
+		assertSame(LOCK_FAILURE, cmd1.getResult());
+		assertSame(REJECTED_OTHER_REASON, cmd2.getResult());
+		assertEquals(JGitText.get().transactionAborted, cmd2.getMessage());
+		assertEquals(treeId, write(tree));
+	}
+
+	@Test
+	public void testApplyWrongOldIdButAlreadyCurrentIsNoOp() throws Exception {
+		RefTree tree = RefTree.newEmptyTree();
+		RevBlob a = git.blob("A");
+		Command cmd = new Command(null, ref(R_MASTER, a));
+		assertTrue(tree.apply(Collections.singletonList(cmd)));
+		ObjectId treeId = write(tree);
+
+		RevBlob b = git.blob("B");
+		cmd = update(R_MASTER, b, a);
+		assertTrue(tree.apply(Collections.singletonList(cmd)));
+		assertEquals(treeId, write(tree));
+	}
+
+	@Test
+	public void testApplyCannotCreateSubdirectory() throws Exception {
+		RefTree tree = RefTree.newEmptyTree();
+		RevBlob a = git.blob("A");
+		Command cmd = new Command(null, ref(R_MASTER, a));
+		assertTrue(tree.apply(Collections.singletonList(cmd)));
+		ObjectId treeId = write(tree);
+
+		RevBlob b = git.blob("B");
+		Command cmd1 = create(R_MASTER + "/fail", b);
+		assertFalse(tree.apply(Collections.singletonList(cmd1)));
+		assertSame(LOCK_FAILURE, cmd1.getResult());
+		assertEquals(treeId, write(tree));
+	}
+
+	@Test
+	public void testApplyCannotCreateParentRef() throws Exception {
+		RefTree tree = RefTree.newEmptyTree();
+		RevBlob a = git.blob("A");
+		Command cmd = new Command(null, ref(R_MASTER, a));
+		assertTrue(tree.apply(Collections.singletonList(cmd)));
+		ObjectId treeId = write(tree);
+
+		RevBlob b = git.blob("B");
+		Command cmd1 = create("refs/heads", b);
+		assertFalse(tree.apply(Collections.singletonList(cmd1)));
+		assertSame(LOCK_FAILURE, cmd1.getResult());
+		assertEquals(treeId, write(tree));
+	}
+
+	private static Ref ref(String name, ObjectId id) {
+		return new ObjectIdRef.PeeledNonTag(LOOSE, name, id);
+	}
+
+	private static Ref symref(String name, String dest) {
+		Ref d = new ObjectIdRef.PeeledNonTag(NEW, dest, null);
+		return new SymbolicRef(name, d);
+	}
+
+	private Command create(String name, ObjectId id)
+			throws MissingObjectException, IOException {
+		return update(name, ObjectId.zeroId(), id);
+	}
+
+	private Command update(String name, ObjectId oldId, ObjectId newId)
+			throws MissingObjectException, IOException {
+		try (RevWalk rw = new RevWalk(repo)) {
+			return new Command(rw, new ReceiveCommand(oldId, newId, name));
+		}
+	}
+
+	private ObjectId write(RefTree tree) throws IOException {
+		try (ObjectInserter ins = repo.newObjectInserter()) {
+			ObjectId id = tree.writeTree(ins);
+			ins.flush();
+			return id;
+		}
+	}
+}
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 fbb9eec..8b54dab 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
@@ -127,25 +127,25 @@
 	@Test
 	public void resetFromSymref() throws Exception {
 		repo.updateRef("HEAD").link("refs/heads/master");
-		Ref head = repo.getRef("HEAD");
+		Ref head = repo.exactRef("HEAD");
 		RevCommit master = tr.branch("master").commit().create();
 		RevCommit branch = tr.branch("branch").commit().create();
 		RevCommit detached = tr.commit().create();
 
 		assertTrue(head.isSymbolic());
 		assertEquals("refs/heads/master", head.getTarget().getName());
-		assertEquals(master, repo.getRef("refs/heads/master").getObjectId());
-		assertEquals(branch, repo.getRef("refs/heads/branch").getObjectId());
+		assertEquals(master, repo.exactRef("refs/heads/master").getObjectId());
+		assertEquals(branch, repo.exactRef("refs/heads/branch").getObjectId());
 
 		// Reset to branches preserves symref.
 		tr.reset("master");
-		head = repo.getRef("HEAD");
+		head = repo.exactRef("HEAD");
 		assertEquals(master, head.getObjectId());
 		assertTrue(head.isSymbolic());
 		assertEquals("refs/heads/master", head.getTarget().getName());
 
 		tr.reset("branch");
-		head = repo.getRef("HEAD");
+		head = repo.exactRef("HEAD");
 		assertEquals(branch, head.getObjectId());
 		assertTrue(head.isSymbolic());
 		assertEquals("refs/heads/master", head.getTarget().getName());
@@ -153,50 +153,50 @@
 
 		// Reset to a SHA-1 detaches.
 		tr.reset(detached);
-		head = repo.getRef("HEAD");
+		head = repo.exactRef("HEAD");
 		assertEquals(detached, head.getObjectId());
 		assertFalse(head.isSymbolic());
 
 		tr.reset(detached.name());
-		head = repo.getRef("HEAD");
+		head = repo.exactRef("HEAD");
 		assertEquals(detached, head.getObjectId());
 		assertFalse(head.isSymbolic());
 
 		// Reset back to a branch remains detached.
 		tr.reset("master");
-		head = repo.getRef("HEAD");
+		head = repo.exactRef("HEAD");
 		assertEquals(lastHeadBeforeDetach, head.getObjectId());
 		assertFalse(head.isSymbolic());
 	}
 
 	@Test
 	public void resetFromDetachedHead() throws Exception {
-		Ref head = repo.getRef("HEAD");
+		Ref head = repo.exactRef("HEAD");
 		RevCommit master = tr.branch("master").commit().create();
 		RevCommit branch = tr.branch("branch").commit().create();
 		RevCommit detached = tr.commit().create();
 
 		assertNull(head);
-		assertEquals(master, repo.getRef("refs/heads/master").getObjectId());
-		assertEquals(branch, repo.getRef("refs/heads/branch").getObjectId());
+		assertEquals(master, repo.exactRef("refs/heads/master").getObjectId());
+		assertEquals(branch, repo.exactRef("refs/heads/branch").getObjectId());
 
 		tr.reset("master");
-		head = repo.getRef("HEAD");
+		head = repo.exactRef("HEAD");
 		assertEquals(master, head.getObjectId());
 		assertFalse(head.isSymbolic());
 
 		tr.reset("branch");
-		head = repo.getRef("HEAD");
+		head = repo.exactRef("HEAD");
 		assertEquals(branch, head.getObjectId());
 		assertFalse(head.isSymbolic());
 
 		tr.reset(detached);
-		head = repo.getRef("HEAD");
+		head = repo.exactRef("HEAD");
 		assertEquals(detached, head.getObjectId());
 		assertFalse(head.isSymbolic());
 
 		tr.reset(detached.name());
-		head = repo.getRef("HEAD");
+		head = repo.exactRef("HEAD");
 		assertEquals(detached, head.getObjectId());
 		assertFalse(head.isSymbolic());
 	}
@@ -222,7 +222,7 @@
 				.tick(3)
 				.add("bar", "fixed bar contents")
 				.create();
-		assertEquals(amended, repo.getRef("refs/heads/master").getObjectId());
+		assertEquals(amended, repo.exactRef("refs/heads/master").getObjectId());
 		rw.parseBody(amended);
 
 		assertEquals(1, amended.getParentCount());
@@ -257,7 +257,7 @@
 				.add("foo", "fixed foo contents")
 				.create();
 
-		Ref head = repo.getRef(Constants.HEAD);
+		Ref head = repo.exactRef(Constants.HEAD);
 		assertEquals(amended, head.getObjectId());
 		assertTrue(head.isSymbolic());
 		assertEquals("refs/heads/master", head.getTarget().getName());
@@ -291,7 +291,7 @@
 	public void commitToUnbornHead() throws Exception {
 		repo.updateRef("HEAD").link("refs/heads/master");
 		RevCommit root = tr.branch("HEAD").commit().create();
-		Ref ref = repo.getRef(Constants.HEAD);
+		Ref ref = repo.exactRef(Constants.HEAD);
 		assertEquals(root, ref.getObjectId());
 		assertTrue(ref.isSymbolic());
 		assertEquals("refs/heads/master", ref.getTarget().getName());
@@ -307,16 +307,16 @@
 		RevCommit toPick = tr.commit()
 				.parent(tr.commit().create()) // Can't cherry-pick root.
 				.author(new PersonIdent("Cherrypick Author", "cpa@example.com",
-						tr.getClock(), tr.getTimeZone()))
+						tr.getDate(), tr.getTimeZone()))
 				.author(new PersonIdent("Cherrypick Committer", "cpc@example.com",
-						tr.getClock(), tr.getTimeZone()))
+						tr.getDate(), tr.getTimeZone()))
 				.message("message to cherry-pick")
 				.add("bar", "bar contents\n")
 				.create();
 		RevCommit result = tr.cherryPick(toPick);
 		rw.parseBody(result);
 
-		Ref headRef = tr.getRepository().getRef("HEAD");
+		Ref headRef = tr.getRepository().exactRef("HEAD");
 		assertEquals(result, headRef.getObjectId());
 		assertTrue(headRef.isSymbolic());
 		assertEquals("refs/heads/master", headRef.getLeaf().getName());
@@ -371,7 +371,7 @@
 				.create();
 		assertNotEquals(head, toPick);
 		assertNull(tr.cherryPick(toPick));
-		assertEquals(head, repo.getRef("HEAD").getObjectId());
+		assertEquals(head, repo.exactRef("HEAD").getObjectId());
 	}
 
 	private String blobAsString(AnyObjectId treeish, String path)
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/DirCacheCheckoutMaliciousPathTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/DirCacheCheckoutMaliciousPathTest.java
index ca3e066..59a4699 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/DirCacheCheckoutMaliciousPathTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/DirCacheCheckoutMaliciousPathTest.java
@@ -338,68 +338,69 @@
 	 */
 	private void testMaliciousPath(boolean good, boolean secondCheckout,
 			String... path) throws GitAPIException, IOException {
-		Git git = new Git(db);
-		ObjectInserter newObjectInserter;
-		newObjectInserter = git.getRepository().newObjectInserter();
-		ObjectId blobId = newObjectInserter.insert(Constants.OBJ_BLOB,
-				"data".getBytes());
-		newObjectInserter = git.getRepository().newObjectInserter();
-		FileMode mode = FileMode.REGULAR_FILE;
-		ObjectId insertId = blobId;
-		for (int i = path.length - 1; i >= 0; --i) {
-			TreeFormatter treeFormatter = new TreeFormatter();
-			treeFormatter.append("goodpath", mode, insertId);
-			insertId = newObjectInserter.insert(treeFormatter);
-			mode = FileMode.TREE;
-		}
-		newObjectInserter = git.getRepository().newObjectInserter();
-		CommitBuilder commitBuilder = new CommitBuilder();
-		commitBuilder.setAuthor(author);
-		commitBuilder.setCommitter(committer);
-		commitBuilder.setMessage("foo#1");
-		commitBuilder.setTreeId(insertId);
-		ObjectId firstCommitId = newObjectInserter.insert(commitBuilder);
-
-		newObjectInserter = git.getRepository().newObjectInserter();
-		mode = FileMode.REGULAR_FILE;
-		insertId = blobId;
-		for (int i = path.length - 1; i >= 0; --i) {
-			TreeFormatter treeFormatter = new TreeFormatter();
-			treeFormatter.append(path[i], mode, insertId);
-			insertId = newObjectInserter.insert(treeFormatter);
-			mode = FileMode.TREE;
-		}
-
-		// Create another commit
-		commitBuilder = new CommitBuilder();
-		commitBuilder.setAuthor(author);
-		commitBuilder.setCommitter(committer);
-		commitBuilder.setMessage("foo#2");
-		commitBuilder.setTreeId(insertId);
-		commitBuilder.setParentId(firstCommitId);
-		ObjectId commitId = newObjectInserter.insert(commitBuilder);
-
-		RevWalk revWalk = new RevWalk(git.getRepository());
-		if (!secondCheckout)
-			git.checkout().setStartPoint(revWalk.parseCommit(firstCommitId))
-					.setName("refs/heads/master").setCreateBranch(true).call();
-		try {
-			if (secondCheckout) {
-				git.checkout().setStartPoint(revWalk.parseCommit(commitId))
-						.setName("refs/heads/master").setCreateBranch(true)
-						.call();
-			} else {
-				git.branchCreate().setName("refs/heads/next")
-						.setStartPoint(commitId.name()).call();
-				git.checkout().setName("refs/heads/next")
-						.call();
+		try (Git git = new Git(db);
+				RevWalk revWalk = new RevWalk(git.getRepository())) {
+			ObjectInserter newObjectInserter;
+			newObjectInserter = git.getRepository().newObjectInserter();
+			ObjectId blobId = newObjectInserter.insert(Constants.OBJ_BLOB,
+					"data".getBytes());
+			newObjectInserter = git.getRepository().newObjectInserter();
+			FileMode mode = FileMode.REGULAR_FILE;
+			ObjectId insertId = blobId;
+			for (int i = path.length - 1; i >= 0; --i) {
+				TreeFormatter treeFormatter = new TreeFormatter();
+				treeFormatter.append("goodpath", mode, insertId);
+				insertId = newObjectInserter.insert(treeFormatter);
+				mode = FileMode.TREE;
 			}
-			if (!good)
-				fail("Checkout of Tree " + Arrays.asList(path) + " should fail");
-		} catch (InvalidPathException e) {
-			if (good)
-				throw e;
-			assertTrue(e.getMessage().startsWith("Invalid path"));
+			newObjectInserter = git.getRepository().newObjectInserter();
+			CommitBuilder commitBuilder = new CommitBuilder();
+			commitBuilder.setAuthor(author);
+			commitBuilder.setCommitter(committer);
+			commitBuilder.setMessage("foo#1");
+			commitBuilder.setTreeId(insertId);
+			ObjectId firstCommitId = newObjectInserter.insert(commitBuilder);
+
+			newObjectInserter = git.getRepository().newObjectInserter();
+			mode = FileMode.REGULAR_FILE;
+			insertId = blobId;
+			for (int i = path.length - 1; i >= 0; --i) {
+				TreeFormatter treeFormatter = new TreeFormatter();
+				treeFormatter.append(path[i], mode, insertId);
+				insertId = newObjectInserter.insert(treeFormatter);
+				mode = FileMode.TREE;
+			}
+
+			// Create another commit
+			commitBuilder = new CommitBuilder();
+			commitBuilder.setAuthor(author);
+			commitBuilder.setCommitter(committer);
+			commitBuilder.setMessage("foo#2");
+			commitBuilder.setTreeId(insertId);
+			commitBuilder.setParentId(firstCommitId);
+			ObjectId commitId = newObjectInserter.insert(commitBuilder);
+
+			if (!secondCheckout)
+				git.checkout().setStartPoint(revWalk.parseCommit(firstCommitId))
+						.setName("refs/heads/master").setCreateBranch(true).call();
+			try {
+				if (secondCheckout) {
+					git.checkout().setStartPoint(revWalk.parseCommit(commitId))
+							.setName("refs/heads/master").setCreateBranch(true)
+							.call();
+				} else {
+					git.branchCreate().setName("refs/heads/next")
+							.setStartPoint(commitId.name()).call();
+					git.checkout().setName("refs/heads/next")
+							.call();
+				}
+				if (!good)
+					fail("Checkout of Tree " + Arrays.asList(path) + " should fail");
+			} catch (InvalidPathException e) {
+				if (good)
+					throw e;
+				assertTrue(e.getMessage().startsWith("Invalid 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 7a115e1..c1b882a 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
@@ -78,6 +78,8 @@
 import org.eclipse.jgit.treewalk.FileTreeIterator;
 import org.eclipse.jgit.treewalk.TreeWalk;
 import org.eclipse.jgit.util.FS;
+import org.eclipse.jgit.util.FileUtils;
+import org.junit.Assume;
 import org.junit.Test;
 
 public class DirCacheCheckoutTest extends RepositoryTestCase {
@@ -111,7 +113,7 @@
 		return dco.getRemoved();
 	}
 
-	private Map<String, ObjectId> getUpdated() {
+	private Map<String, String> getUpdated() {
 		return dco.getUpdated();
 	}
 
@@ -138,52 +140,53 @@
 	@Test
 	public void testResetHard() throws IOException, NoFilepatternException,
 			GitAPIException {
-		Git git = new Git(db);
-		writeTrashFile("f", "f()");
-		writeTrashFile("D/g", "g()");
-		git.add().addFilepattern(".").call();
-		git.commit().setMessage("inital").call();
-		assertIndex(mkmap("f", "f()", "D/g", "g()"));
+		try (Git git = new Git(db)) {
+			writeTrashFile("f", "f()");
+			writeTrashFile("D/g", "g()");
+			git.add().addFilepattern(".").call();
+			git.commit().setMessage("inital").call();
+			assertIndex(mkmap("f", "f()", "D/g", "g()"));
 
-		git.branchCreate().setName("topic").call();
+			git.branchCreate().setName("topic").call();
 
-		writeTrashFile("f", "f()\nmaster");
-		writeTrashFile("D/g", "g()\ng2()");
-		writeTrashFile("E/h", "h()");
-		git.add().addFilepattern(".").call();
-		RevCommit master = git.commit().setMessage("master-1").call();
-		assertIndex(mkmap("f", "f()\nmaster", "D/g", "g()\ng2()", "E/h", "h()"));
+			writeTrashFile("f", "f()\nmaster");
+			writeTrashFile("D/g", "g()\ng2()");
+			writeTrashFile("E/h", "h()");
+			git.add().addFilepattern(".").call();
+			RevCommit master = git.commit().setMessage("master-1").call();
+			assertIndex(mkmap("f", "f()\nmaster", "D/g", "g()\ng2()", "E/h", "h()"));
 
-		checkoutBranch("refs/heads/topic");
-		assertIndex(mkmap("f", "f()", "D/g", "g()"));
+			checkoutBranch("refs/heads/topic");
+			assertIndex(mkmap("f", "f()", "D/g", "g()"));
 
-		writeTrashFile("f", "f()\nside");
-		assertTrue(new File(db.getWorkTree(), "D/g").delete());
-		writeTrashFile("G/i", "i()");
-		git.add().addFilepattern(".").call();
-		git.add().addFilepattern(".").setUpdate(true).call();
-		RevCommit topic = git.commit().setMessage("topic-1").call();
-		assertIndex(mkmap("f", "f()\nside", "G/i", "i()"));
+			writeTrashFile("f", "f()\nside");
+			assertTrue(new File(db.getWorkTree(), "D/g").delete());
+			writeTrashFile("G/i", "i()");
+			git.add().addFilepattern(".").call();
+			git.add().addFilepattern(".").setUpdate(true).call();
+			RevCommit topic = git.commit().setMessage("topic-1").call();
+			assertIndex(mkmap("f", "f()\nside", "G/i", "i()"));
 
-		writeTrashFile("untracked", "untracked");
+			writeTrashFile("untracked", "untracked");
 
-		resetHard(master);
-		assertIndex(mkmap("f", "f()\nmaster", "D/g", "g()\ng2()", "E/h", "h()"));
-		resetHard(topic);
-		assertIndex(mkmap("f", "f()\nside", "G/i", "i()"));
-		assertWorkDir(mkmap("f", "f()\nside", "G/i", "i()", "untracked",
-				"untracked"));
+			resetHard(master);
+			assertIndex(mkmap("f", "f()\nmaster", "D/g", "g()\ng2()", "E/h", "h()"));
+			resetHard(topic);
+			assertIndex(mkmap("f", "f()\nside", "G/i", "i()"));
+			assertWorkDir(mkmap("f", "f()\nside", "G/i", "i()", "untracked",
+					"untracked"));
 
-		assertEquals(MergeStatus.CONFLICTING, git.merge().include(master)
-				.call().getMergeStatus());
-		assertEquals(
-				"[D/g, mode:100644, stage:1][D/g, mode:100644, stage:3][E/h, mode:100644][G/i, mode:100644][f, mode:100644, stage:1][f, mode:100644, stage:2][f, mode:100644, stage:3]",
-				indexState(0));
+			assertEquals(MergeStatus.CONFLICTING, git.merge().include(master)
+					.call().getMergeStatus());
+			assertEquals(
+					"[D/g, mode:100644, stage:1][D/g, mode:100644, stage:3][E/h, mode:100644][G/i, mode:100644][f, mode:100644, stage:1][f, mode:100644, stage:2][f, mode:100644, stage:3]",
+					indexState(0));
 
-		resetHard(master);
-		assertIndex(mkmap("f", "f()\nmaster", "D/g", "g()\ng2()", "E/h", "h()"));
-		assertWorkDir(mkmap("f", "f()\nmaster", "D/g", "g()\ng2()", "E/h",
-				"h()", "untracked", "untracked"));
+			resetHard(master);
+			assertIndex(mkmap("f", "f()\nmaster", "D/g", "g()\ng2()", "E/h", "h()"));
+			assertWorkDir(mkmap("f", "f()\nmaster", "D/g", "g()\ng2()", "E/h",
+					"h()", "untracked", "untracked"));
+		}
 	}
 
 	/**
@@ -198,21 +201,22 @@
 	@Test
 	public void testResetHardFromIndexEntryWithoutFileToTreeWithoutFile()
 			throws Exception {
-		Git git = new Git(db);
-		writeTrashFile("x", "x");
-		git.add().addFilepattern("x").call();
-		RevCommit id1 = git.commit().setMessage("c1").call();
+		try (Git git = new Git(db)) {
+			writeTrashFile("x", "x");
+			git.add().addFilepattern("x").call();
+			RevCommit id1 = git.commit().setMessage("c1").call();
 
-		writeTrashFile("f/g", "f/g");
-		git.rm().addFilepattern("x").call();
-		git.add().addFilepattern("f/g").call();
-		git.commit().setMessage("c2").call();
-		deleteTrashFile("f/g");
-		deleteTrashFile("f");
+			writeTrashFile("f/g", "f/g");
+			git.rm().addFilepattern("x").call();
+			git.add().addFilepattern("f/g").call();
+			git.commit().setMessage("c2").call();
+			deleteTrashFile("f/g");
+			deleteTrashFile("f");
 
-		// The actual test
-		git.reset().setMode(ResetType.HARD).setRef(id1.getName()).call();
-		assertIndex(mkmap("x", "x"));
+			// The actual test
+			git.reset().setMode(ResetType.HARD).setRef(id1.getName()).call();
+			assertIndex(mkmap("x", "x"));
+		}
 	}
 
 	/**
@@ -222,14 +226,14 @@
 	 */
 	@Test
 	public void testInitialCheckout() throws Exception {
-		Git git = new Git(db);
-
-		TestRepository<Repository> db_t = new TestRepository<Repository>(db);
-		BranchBuilder master = db_t.branch("master");
-		master.commit().add("f", "1").message("m0").create();
-		assertFalse(new File(db.getWorkTree(), "f").exists());
-		git.checkout().setName("master").call();
-		assertTrue(new File(db.getWorkTree(), "f").exists());
+		try (Git git = new Git(db)) {
+			TestRepository<Repository> db_t = new TestRepository<Repository>(db);
+			BranchBuilder master = db_t.branch("master");
+			master.commit().add("f", "1").message("m0").create();
+			assertFalse(new File(db.getWorkTree(), "f").exists());
+			git.checkout().setName("master").call();
+			assertTrue(new File(db.getWorkTree(), "f").exists());
+		}
 	}
 
 	private DirCacheCheckout resetHard(RevCommit commit)
@@ -266,8 +270,6 @@
 	@Test
 	public void testRules1thru3_NoIndexEntry() throws IOException {
 		ObjectId head = buildTree(mk("foo"));
-		TreeWalk tw = TreeWalk.forPath(db, "foo", head);
-		ObjectId objectId = tw.getObjectId(0);
 		ObjectId merge = db.newObjectInserter().insert(Constants.OBJ_TREE,
 				new byte[0]);
 
@@ -277,10 +279,9 @@
 
 		prescanTwoTrees(merge, head);
 
-		assertEquals(objectId, getUpdated().get("foo"));
+		assertTrue(getUpdated().containsKey("foo"));
 
 		merge = buildTree(mkmap("foo", "a"));
-		tw = TreeWalk.forPath(db, "foo", merge);
 
 		prescanTwoTrees(head, merge);
 
@@ -923,6 +924,301 @@
 	}
 
 	@Test
+	public void testCheckoutChangeLinkToEmptyDir() throws Exception {
+		Assume.assumeTrue(FS.DETECTED.supportsSymlinks());
+		String fname = "was_file";
+		Git git = Git.wrap(db);
+
+		// Add a file
+		writeTrashFile(fname, "a");
+		git.add().addFilepattern(fname).call();
+
+		// Add a link to file
+		String linkName = "link";
+		File link = writeLink(linkName, fname).toFile();
+		git.add().addFilepattern(linkName).call();
+		git.commit().setMessage("Added file and link").call();
+
+		assertWorkDir(mkmap(linkName, "a", fname, "a"));
+
+		// replace link with empty directory
+		FileUtils.delete(link);
+		FileUtils.mkdir(link);
+		assertTrue("Link must be a directory now", link.isDirectory());
+
+		// modify file
+		writeTrashFile(fname, "b");
+		assertWorkDir(mkmap(fname, "b", linkName, "/"));
+
+		// revert both paths to HEAD state
+		git.checkout().setStartPoint(Constants.HEAD)
+				.addPath(fname).addPath(linkName).call();
+
+		assertWorkDir(mkmap(fname, "a", linkName, "a"));
+
+		Status st = git.status().call();
+		assertTrue(st.isClean());
+	}
+
+	@Test
+	public void testCheckoutChangeLinkToEmptyDirs() throws Exception {
+		Assume.assumeTrue(FS.DETECTED.supportsSymlinks());
+		String fname = "was_file";
+		Git git = Git.wrap(db);
+
+		// Add a file
+		writeTrashFile(fname, "a");
+		git.add().addFilepattern(fname).call();
+
+		// Add a link to file
+		String linkName = "link";
+		File link = writeLink(linkName, fname).toFile();
+		git.add().addFilepattern(linkName).call();
+		git.commit().setMessage("Added file and link").call();
+
+		assertWorkDir(mkmap(linkName, "a", fname, "a"));
+
+		// replace link with directory containing only directories, no files
+		FileUtils.delete(link);
+		FileUtils.mkdirs(new File(link, "dummyDir"));
+		assertTrue("Link must be a directory now", link.isDirectory());
+
+		assertFalse("Must not delete non empty directory", link.delete());
+
+		// modify file
+		writeTrashFile(fname, "b");
+		assertWorkDir(mkmap(fname, "b", linkName + "/dummyDir", "/"));
+
+		// revert both paths to HEAD state
+		git.checkout().setStartPoint(Constants.HEAD)
+				.addPath(fname).addPath(linkName).call();
+
+		assertWorkDir(mkmap(fname, "a", linkName, "a"));
+
+		Status st = git.status().call();
+		assertTrue(st.isClean());
+	}
+
+	@Test
+	public void testCheckoutChangeLinkToNonEmptyDirs() throws Exception {
+		Assume.assumeTrue(FS.DETECTED.supportsSymlinks());
+		String fname = "file";
+		Git git = Git.wrap(db);
+
+		// Add a file
+		writeTrashFile(fname, "a");
+		git.add().addFilepattern(fname).call();
+
+		// Add a link to file
+		String linkName = "link";
+		File link = writeLink(linkName, fname).toFile();
+		git.add().addFilepattern(linkName).call();
+		git.commit().setMessage("Added file and link").call();
+
+		assertWorkDir(mkmap(linkName, "a", fname, "a"));
+
+		// replace link with directory containing only directories, no files
+		FileUtils.delete(link);
+
+		// create but do not add a file in the new directory to the index
+		writeTrashFile(linkName + "/dir1", "file1", "c");
+
+		// create but do not add a file in the new directory to the index
+		writeTrashFile(linkName + "/dir2", "file2", "d");
+
+		assertTrue("File must be a directory now", link.isDirectory());
+		assertFalse("Must not delete non empty directory", link.delete());
+
+		// 2 extra files are created
+		assertWorkDir(mkmap(fname, "a", linkName + "/dir1/file1", "c",
+				linkName + "/dir2/file2", "d"));
+
+		// revert path to HEAD state
+		git.checkout().setStartPoint(Constants.HEAD).addPath(linkName).call();
+
+		// expect only the one added to the index
+		assertWorkDir(mkmap(linkName, "a", fname, "a"));
+
+		Status st = git.status().call();
+		assertTrue(st.isClean());
+	}
+
+	@Test
+	public void testCheckoutChangeLinkToNonEmptyDirsAndNewIndexEntry()
+			throws Exception {
+		Assume.assumeTrue(FS.DETECTED.supportsSymlinks());
+		String fname = "file";
+		Git git = Git.wrap(db);
+
+		// Add a file
+		writeTrashFile(fname, "a");
+		git.add().addFilepattern(fname).call();
+
+		// Add a link to file
+		String linkName = "link";
+		File link = writeLink(linkName, fname).toFile();
+		git.add().addFilepattern(linkName).call();
+		git.commit().setMessage("Added file and link").call();
+
+		assertWorkDir(mkmap(linkName, "a", fname, "a"));
+
+		// replace link with directory containing only directories, no files
+		FileUtils.delete(link);
+
+		// create and add a file in the new directory to the index
+		writeTrashFile(linkName + "/dir1", "file1", "c");
+		git.add().addFilepattern(linkName + "/dir1/file1").call();
+
+		// create but do not add a file in the new directory to the index
+		writeTrashFile(linkName + "/dir2", "file2", "d");
+
+		assertTrue("File must be a directory now", link.isDirectory());
+		assertFalse("Must not delete non empty directory", link.delete());
+
+		// 2 extra files are created
+		assertWorkDir(mkmap(fname, "a", linkName + "/dir1/file1", "c",
+				linkName + "/dir2/file2", "d"));
+
+		// revert path to HEAD state
+		git.checkout().setStartPoint(Constants.HEAD).addPath(linkName).call();
+
+		// original file and link
+		assertWorkDir(mkmap(linkName, "a", fname, "a"));
+
+		Status st = git.status().call();
+		assertTrue(st.isClean());
+	}
+
+	@Test
+	public void testCheckoutChangeFileToEmptyDir() throws Exception {
+		String fname = "was_file";
+		Git git = Git.wrap(db);
+
+		// Add a file
+		File file = writeTrashFile(fname, "a");
+		git.add().addFilepattern(fname).call();
+		git.commit().setMessage("Added file").call();
+
+		// replace file with empty directory
+		FileUtils.delete(file);
+		FileUtils.mkdir(file);
+		assertTrue("File must be a directory now", file.isDirectory());
+
+		assertWorkDir(mkmap(fname, "/"));
+
+		// revert path to HEAD state
+		git.checkout().setStartPoint(Constants.HEAD).addPath(fname).call();
+
+		assertWorkDir(mkmap(fname, "a"));
+
+		Status st = git.status().call();
+		assertTrue(st.isClean());
+	}
+
+	@Test
+	public void testCheckoutChangeFileToEmptyDirs() throws Exception {
+		String fname = "was_file";
+		Git git = Git.wrap(db);
+
+		// Add a file
+		File file = writeTrashFile(fname, "a");
+		git.add().addFilepattern(fname).call();
+		git.commit().setMessage("Added file").call();
+
+		// replace file with directory containing only directories, no files
+		FileUtils.delete(file);
+		FileUtils.mkdirs(new File(file, "dummyDir"));
+		assertTrue("File must be a directory now", file.isDirectory());
+		assertFalse("Must not delete non empty directory", file.delete());
+
+		assertWorkDir(mkmap(fname + "/dummyDir", "/"));
+
+		// revert path to HEAD state
+		git.checkout().setStartPoint(Constants.HEAD).addPath(fname).call();
+
+		assertWorkDir(mkmap(fname, "a"));
+
+		Status st = git.status().call();
+		assertTrue(st.isClean());
+	}
+
+	@Test
+	public void testCheckoutChangeFileToNonEmptyDirs() throws Exception {
+		String fname = "was_file";
+		Git git = Git.wrap(db);
+
+		// Add a file
+		File file = writeTrashFile(fname, "a");
+		git.add().addFilepattern(fname).call();
+		git.commit().setMessage("Added file").call();
+
+		assertWorkDir(mkmap(fname, "a"));
+
+		// replace file with directory containing only directories, no files
+		FileUtils.delete(file);
+
+		// create but do not add a file in the new directory to the index
+		writeTrashFile(fname + "/dir1", "file1", "c");
+
+		// create but do not add a file in the new directory to the index
+		writeTrashFile(fname + "/dir2", "file2", "d");
+
+		assertTrue("File must be a directory now", file.isDirectory());
+		assertFalse("Must not delete non empty directory", file.delete());
+
+		// 2 extra files are created
+		assertWorkDir(
+				mkmap(fname + "/dir1/file1", "c", fname + "/dir2/file2", "d"));
+
+		// revert path to HEAD state
+		git.checkout().setStartPoint(Constants.HEAD).addPath(fname).call();
+
+		// expect only the one added to the index
+		assertWorkDir(mkmap(fname, "a"));
+
+		Status st = git.status().call();
+		assertTrue(st.isClean());
+	}
+
+	@Test
+	public void testCheckoutChangeFileToNonEmptyDirsAndNewIndexEntry()
+			throws Exception {
+		String fname = "was_file";
+		Git git = Git.wrap(db);
+
+		// Add a file
+		File file = writeTrashFile(fname, "a");
+		git.add().addFilepattern(fname).call();
+		git.commit().setMessage("Added file").call();
+
+		assertWorkDir(mkmap(fname, "a"));
+
+		// replace file with directory containing only directories, no files
+		FileUtils.delete(file);
+
+		// create and add a file in the new directory to the index
+		writeTrashFile(fname + "/dir", "file1", "c");
+		git.add().addFilepattern(fname + "/dir/file1").call();
+
+		// create but do not add a file in the new directory to the index
+		writeTrashFile(fname + "/dir", "file2", "d");
+
+		assertTrue("File must be a directory now", file.isDirectory());
+		assertFalse("Must not delete non empty directory", file.delete());
+
+		// 2 extra files are created
+		assertWorkDir(
+				mkmap(fname + "/dir/file1", "c", fname + "/dir/file2", "d"));
+
+		// revert path to HEAD state
+		git.checkout().setStartPoint(Constants.HEAD).addPath(fname).call();
+		assertWorkDir(mkmap(fname, "a"));
+
+		Status st = git.status().call();
+		assertTrue(st.isClean());
+	}
+
+	@Test
 	public void testCheckoutOutChangesAutoCRLFfalse() throws IOException {
 		setupCase(mk("foo"), mkmap("foo/bar", "foo\nbar"), mk("foo"));
 		checkout();
@@ -983,6 +1279,14 @@
 	}
 
 	@Test
+	public void testDontOverwriteEmptyFolder() throws IOException {
+		setupCase(mk("foo"), mk("foo"), mk("foo"));
+		FileUtils.mkdir(new File(db.getWorkTree(), "d"));
+		checkout();
+		assertWorkDir(mkmap("foo", "foo", "d", "/"));
+	}
+
+	@Test
 	public void testOverwriteUntrackedIgnoredFile() throws IOException,
 			GitAPIException {
 		String fname="file.txt";
@@ -1015,6 +1319,103 @@
 	}
 
 	@Test
+	public void testOverwriteUntrackedFileModeChange()
+			throws IOException, GitAPIException {
+		String fname = "file.txt";
+		Git git = Git.wrap(db);
+
+		// Add a file
+		File file = writeTrashFile(fname, "a");
+		git.add().addFilepattern(fname).call();
+		git.commit().setMessage("create file").call();
+		assertWorkDir(mkmap(fname, "a"));
+
+		// Create branch
+		git.branchCreate().setName("side").call();
+
+		// Switch branches
+		git.checkout().setName("side").call();
+
+		// replace file with directory containing files
+		FileUtils.delete(file);
+
+		// create and add a file in the new directory to the index
+		writeTrashFile(fname + "/dir1", "file1", "c");
+		git.add().addFilepattern(fname + "/dir1/file1").call();
+
+		// create but do not add a file in the new directory to the index
+		writeTrashFile(fname + "/dir2", "file2", "d");
+
+		assertTrue("File must be a directory now", file.isDirectory());
+		assertFalse("Must not delete non empty directory", file.delete());
+
+		// 2 extra files are created
+		assertWorkDir(
+				mkmap(fname + "/dir1/file1", "c", fname + "/dir2/file2", "d"));
+
+		try {
+			git.checkout().setName("master").call();
+			fail("did not throw exception");
+		} catch (Exception e) {
+			// 2 extra files are still there
+			assertWorkDir(mkmap(fname + "/dir1/file1", "c",
+					fname + "/dir2/file2", "d"));
+		}
+	}
+
+	@Test
+	public void testOverwriteUntrackedLinkModeChange()
+			throws Exception {
+		Assume.assumeTrue(FS.DETECTED.supportsSymlinks());
+		String fname = "file.txt";
+		Git git = Git.wrap(db);
+
+		// Add a file
+		writeTrashFile(fname, "a");
+		git.add().addFilepattern(fname).call();
+
+		// Add a link to file
+		String linkName = "link";
+		File link = writeLink(linkName, fname).toFile();
+		git.add().addFilepattern(linkName).call();
+		git.commit().setMessage("Added file and link").call();
+
+		assertWorkDir(mkmap(linkName, "a", fname, "a"));
+
+		// Create branch
+		git.branchCreate().setName("side").call();
+
+		// Switch branches
+		git.checkout().setName("side").call();
+
+		// replace link with directory containing files
+		FileUtils.delete(link);
+
+		// create and add a file in the new directory to the index
+		writeTrashFile(linkName + "/dir1", "file1", "c");
+		git.add().addFilepattern(linkName + "/dir1/file1").call();
+
+		// create but do not add a file in the new directory to the index
+		writeTrashFile(linkName + "/dir2", "file2", "d");
+
+		assertTrue("Link must be a directory now", link.isDirectory());
+		assertFalse("Must not delete non empty directory", link.delete());
+
+		// 2 extra files are created
+		assertWorkDir(mkmap(fname, "a", linkName + "/dir1/file1", "c",
+				linkName + "/dir2/file2", "d"));
+
+		try {
+			git.checkout().setName("master").call();
+			fail("did not throw exception");
+		} catch (Exception e) {
+			// 2 extra files are still there
+			assertWorkDir(mkmap(fname, "a", linkName + "/dir1/file1", "c",
+					linkName + "/dir2/file2", "d"));
+		}
+	}
+
+	@Test
 	public void testFileModeChangeWithNoContentChangeUpdate() throws Exception {
 		if (!FS.DETECTED.supportsExecute())
 			return;
@@ -1210,39 +1611,50 @@
 		assertNotNull(git.checkout().setName(Constants.MASTER).call());
 	}
 
-	public void assertWorkDir(HashMap<String, String> i) throws CorruptObjectException,
+	public void assertWorkDir(Map<String, String> i)
+			throws CorruptObjectException,
 			IOException {
-		TreeWalk walk = new TreeWalk(db);
-		walk.setRecursive(true);
-		walk.addTree(new FileTreeIterator(db));
-		String expectedValue;
-		String path;
-		int nrFiles = 0;
-		FileTreeIterator ft;
-		while (walk.next()) {
-			ft = walk.getTree(0, FileTreeIterator.class);
-			path = ft.getEntryPathString();
-			expectedValue = i.get(path);
-			assertNotNull("found unexpected file for path " + path
-					+ " in workdir", expectedValue);
-			File file = new File(db.getWorkTree(), path);
-			assertTrue(file.exists());
-			if (file.isFile()) {
-				FileInputStream is = new FileInputStream(file);
-				byte[] buffer = new byte[(int) file.length()];
-				int offset = 0;
-				int numRead = 0;
-				while (offset < buffer.length
-						&& (numRead = is.read(buffer, offset, buffer.length
-								- offset)) >= 0) {
-					offset += numRead;
+		try (TreeWalk walk = new TreeWalk(db)) {
+			walk.setRecursive(false);
+			walk.addTree(new FileTreeIterator(db));
+			String expectedValue;
+			String path;
+			int nrFiles = 0;
+			FileTreeIterator ft;
+			while (walk.next()) {
+				ft = walk.getTree(0, FileTreeIterator.class);
+				path = ft.getEntryPathString();
+				expectedValue = i.get(path);
+				File file = new File(db.getWorkTree(), path);
+				assertTrue(file.exists());
+				if (file.isFile()) {
+					assertNotNull("found unexpected file for path " + path
+							+ " in workdir", expectedValue);
+					FileInputStream is = new FileInputStream(file);
+					byte[] buffer = new byte[(int) file.length()];
+					int offset = 0;
+					int numRead = 0;
+					while (offset < buffer.length
+							&& (numRead = is.read(buffer, offset, buffer.length
+									- offset)) >= 0) {
+						offset += numRead;
+					}
+					is.close();
+					assertArrayEquals("unexpected content for path " + path
+							+ " in workDir. ", buffer, i.get(path).getBytes());
+					nrFiles++;
+				} else if (file.isDirectory()) {
+					if (file.list().length == 0) {
+						assertEquals("found unexpected empty folder for path "
+								+ path + " in workDir. ", "/", i.get(path));
+						nrFiles++;
+					}
 				}
-				is.close();
-				assertArrayEquals("unexpected content for path " + path
-						+ " in workDir. ", buffer, i.get(path).getBytes());
-				nrFiles++;
+				if (walk.isSubtree()) {
+					walk.enterSubtree();
+				}
 			}
+			assertEquals("WorkDir has not the right size.", i.size(), nrFiles);
 		}
-		assertEquals("WorkDir has not the right size.", i.size(), nrFiles);
 	}
 }
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/IndexDiffSubmoduleTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/IndexDiffSubmoduleTest.java
index 863d79d..3259f62 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/IndexDiffSubmoduleTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/IndexDiffSubmoduleTest.java
@@ -87,7 +87,7 @@
 				.call();
 
 		submodule_db = (FileRepository) Git.wrap(db).submoduleAdd()
-				.setPath("submodule")
+				.setPath("modules/submodule")
 				.setURI(submoduleStandalone.getDirectory().toURI().toString())
 				.call();
 		submoduleStandalone.close();
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/IndexDiffTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/IndexDiffTest.java
index a5cd7b5..733f166 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/IndexDiffTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/IndexDiffTest.java
@@ -77,7 +77,6 @@
 import org.eclipse.jgit.util.IO;
 import org.junit.Test;
 
-@SuppressWarnings("deprecation")
 public class IndexDiffTest extends RepositoryTestCase {
 
 	static PathEdit add(final Repository db, final File workdir,
@@ -99,8 +98,7 @@
 	public void testAdded() throws IOException {
 		writeTrashFile("file1", "file1");
 		writeTrashFile("dir/subfile", "dir/subfile");
-		Tree tree = new Tree(db);
-		tree.setId(insertTree(tree));
+		ObjectId tree = insertTree(new TreeFormatter());
 
 		DirCache index = db.lockDirCache();
 		DirCacheEditor editor = index.editor();
@@ -108,7 +106,7 @@
 		editor.add(add(db, trash, "dir/subfile"));
 		editor.commit();
 		FileTreeIterator iterator = new FileTreeIterator(db);
-		IndexDiff diff = new IndexDiff(db, tree.getId(), iterator);
+		IndexDiff diff = new IndexDiff(db, tree, iterator);
 		diff.diff();
 		assertEquals(2, diff.getAdded().size());
 		assertTrue(diff.getAdded().contains("file1"));
@@ -124,18 +122,16 @@
 		writeTrashFile("file2", "file2");
 		writeTrashFile("dir/file3", "dir/file3");
 
-		Tree tree = new Tree(db);
-		tree.addFile("file2");
-		tree.addFile("dir/file3");
-		assertEquals(2, tree.memberCount());
-		tree.findBlobMember("file2").setId(ObjectId.fromString("30d67d4672d5c05833b7192cc77a79eaafb5c7ad"));
-		Tree tree2 = (Tree) tree.findTreeMember("dir");
-		tree2.findBlobMember("file3").setId(ObjectId.fromString("873fb8d667d05436d728c52b1d7a09528e6eb59b"));
-		tree2.setId(insertTree(tree2));
-		tree.setId(insertTree(tree));
+		TreeFormatter dir = new TreeFormatter();
+		dir.append("file3", FileMode.REGULAR_FILE, ObjectId.fromString("873fb8d667d05436d728c52b1d7a09528e6eb59b"));
+
+		TreeFormatter tree = new TreeFormatter();
+		tree.append("file2", FileMode.REGULAR_FILE, ObjectId.fromString("30d67d4672d5c05833b7192cc77a79eaafb5c7ad"));
+		tree.append("dir", FileMode.TREE, insertTree(dir));
+		ObjectId treeId = insertTree(tree);
 
 		FileTreeIterator iterator = new FileTreeIterator(db);
-		IndexDiff diff = new IndexDiff(db, tree.getId(), iterator);
+		IndexDiff diff = new IndexDiff(db, treeId, iterator);
 		diff.diff();
 		assertEquals(2, diff.getRemoved().size());
 		assertTrue(diff.getRemoved().contains("file2"));
@@ -152,21 +148,22 @@
 		writeTrashFile("file2", "file2");
 		writeTrashFile("dir/file3", "dir/file3");
 
-		Git git = new Git(db);
-		git.add().addFilepattern("file2").addFilepattern("dir/file3").call();
+		try (Git git = new Git(db)) {
+			git.add().addFilepattern("file2").addFilepattern("dir/file3").call();
+		}
 
 		writeTrashFile("dir/file3", "changed");
 
-		Tree tree = new Tree(db);
-		tree.addFile("file2").setId(ObjectId.fromString("0123456789012345678901234567890123456789"));
-		tree.addFile("dir/file3").setId(ObjectId.fromString("0123456789012345678901234567890123456789"));
-		assertEquals(2, tree.memberCount());
+		TreeFormatter dir = new TreeFormatter();
+		dir.append("file3", FileMode.REGULAR_FILE, ObjectId.fromString("0123456789012345678901234567890123456789"));
 
-		Tree tree2 = (Tree) tree.findTreeMember("dir");
-		tree2.setId(insertTree(tree2));
-		tree.setId(insertTree(tree));
+		TreeFormatter tree = new TreeFormatter();
+		tree.append("dir", FileMode.TREE, insertTree(dir));
+		tree.append("file2", FileMode.REGULAR_FILE, ObjectId.fromString("0123456789012345678901234567890123456789"));
+		ObjectId treeId = insertTree(tree);
+
 		FileTreeIterator iterator = new FileTreeIterator(db);
-		IndexDiff diff = new IndexDiff(db, tree.getId(), iterator);
+		IndexDiff diff = new IndexDiff(db, treeId, iterator);
 		diff.diff();
 		assertEquals(2, diff.getChanged().size());
 		assertTrue(diff.getChanged().contains("file2"));
@@ -181,31 +178,31 @@
 
 	@Test
 	public void testConflicting() throws Exception {
-		Git git = new Git(db);
+		try (Git git = new Git(db)) {
+			writeTrashFile("a", "1\na\n3\n");
+			writeTrashFile("b", "1\nb\n3\n");
+			git.add().addFilepattern("a").addFilepattern("b").call();
+			RevCommit initialCommit = git.commit().setMessage("initial").call();
 
-		writeTrashFile("a", "1\na\n3\n");
-		writeTrashFile("b", "1\nb\n3\n");
-		git.add().addFilepattern("a").addFilepattern("b").call();
-		RevCommit initialCommit = git.commit().setMessage("initial").call();
+			// create side branch with two modifications
+			createBranch(initialCommit, "refs/heads/side");
+			checkoutBranch("refs/heads/side");
+			writeTrashFile("a", "1\na(side)\n3\n");
+			writeTrashFile("b", "1\nb\n3\n(side)");
+			git.add().addFilepattern("a").addFilepattern("b").call();
+			RevCommit secondCommit = git.commit().setMessage("side").call();
 
-		// create side branch with two modifications
-		createBranch(initialCommit, "refs/heads/side");
-		checkoutBranch("refs/heads/side");
-		writeTrashFile("a", "1\na(side)\n3\n");
-		writeTrashFile("b", "1\nb\n3\n(side)");
-		git.add().addFilepattern("a").addFilepattern("b").call();
-		RevCommit secondCommit = git.commit().setMessage("side").call();
+			// update a on master to generate conflict
+			checkoutBranch("refs/heads/master");
+			writeTrashFile("a", "1\na(main)\n3\n");
+			git.add().addFilepattern("a").call();
+			git.commit().setMessage("main").call();
 
-		// update a on master to generate conflict
-		checkoutBranch("refs/heads/master");
-		writeTrashFile("a", "1\na(main)\n3\n");
-		git.add().addFilepattern("a").call();
-		git.commit().setMessage("main").call();
-
-		// merge side with master
-		MergeResult result = git.merge().include(secondCommit.getId())
-				.setStrategy(MergeStrategy.RESOLVE).call();
-		assertEquals(MergeStatus.CONFLICTING, result.getMergeStatus());
+			// merge side with master
+			MergeResult result = git.merge().include(secondCommit.getId())
+					.setStrategy(MergeStrategy.RESOLVE).call();
+			assertEquals(MergeStatus.CONFLICTING, result.getMergeStatus());
+		}
 
 		FileTreeIterator iterator = new FileTreeIterator(db);
 		IndexDiff diff = new IndexDiff(db, Constants.HEAD, iterator);
@@ -225,29 +222,29 @@
 
 	@Test
 	public void testConflictingDeletedAndModified() throws Exception {
-		Git git = new Git(db);
+		try (Git git = new Git(db)) {
+			writeTrashFile("a", "1\na\n3\n");
+			writeTrashFile("b", "1\nb\n3\n");
+			git.add().addFilepattern("a").addFilepattern("b").call();
+			RevCommit initialCommit = git.commit().setMessage("initial").call();
 
-		writeTrashFile("a", "1\na\n3\n");
-		writeTrashFile("b", "1\nb\n3\n");
-		git.add().addFilepattern("a").addFilepattern("b").call();
-		RevCommit initialCommit = git.commit().setMessage("initial").call();
+			// create side branch and delete "a"
+			createBranch(initialCommit, "refs/heads/side");
+			checkoutBranch("refs/heads/side");
+			git.rm().addFilepattern("a").call();
+			RevCommit secondCommit = git.commit().setMessage("side").call();
 
-		// create side branch and delete "a"
-		createBranch(initialCommit, "refs/heads/side");
-		checkoutBranch("refs/heads/side");
-		git.rm().addFilepattern("a").call();
-		RevCommit secondCommit = git.commit().setMessage("side").call();
+			// update a on master to generate conflict
+			checkoutBranch("refs/heads/master");
+			writeTrashFile("a", "1\na(main)\n3\n");
+			git.add().addFilepattern("a").call();
+			git.commit().setMessage("main").call();
 
-		// update a on master to generate conflict
-		checkoutBranch("refs/heads/master");
-		writeTrashFile("a", "1\na(main)\n3\n");
-		git.add().addFilepattern("a").call();
-		git.commit().setMessage("main").call();
-
-		// merge side with master
-		MergeResult result = git.merge().include(secondCommit.getId())
-				.setStrategy(MergeStrategy.RESOLVE).call();
-		assertEquals(MergeStatus.CONFLICTING, result.getMergeStatus());
+			// merge side with master
+			MergeResult result = git.merge().include(secondCommit.getId())
+					.setStrategy(MergeStrategy.RESOLVE).call();
+			assertEquals(MergeStatus.CONFLICTING, result.getMergeStatus());
+		}
 
 		FileTreeIterator iterator = new FileTreeIterator(db);
 		IndexDiff diff = new IndexDiff(db, Constants.HEAD, iterator);
@@ -266,28 +263,28 @@
 
 	@Test
 	public void testConflictingFromMultipleCreations() throws Exception {
-		Git git = new Git(db);
+		try (Git git = new Git(db)) {
+			writeTrashFile("a", "1\na\n3\n");
+			git.add().addFilepattern("a").call();
+			RevCommit initialCommit = git.commit().setMessage("initial").call();
 
-		writeTrashFile("a", "1\na\n3\n");
-		git.add().addFilepattern("a").call();
-		RevCommit initialCommit = git.commit().setMessage("initial").call();
+			createBranch(initialCommit, "refs/heads/side");
+			checkoutBranch("refs/heads/side");
 
-		createBranch(initialCommit, "refs/heads/side");
-		checkoutBranch("refs/heads/side");
+			writeTrashFile("b", "1\nb(side)\n3\n");
+			git.add().addFilepattern("b").call();
+			RevCommit secondCommit = git.commit().setMessage("side").call();
 
-		writeTrashFile("b", "1\nb(side)\n3\n");
-		git.add().addFilepattern("b").call();
-		RevCommit secondCommit = git.commit().setMessage("side").call();
+			checkoutBranch("refs/heads/master");
 
-		checkoutBranch("refs/heads/master");
+			writeTrashFile("b", "1\nb(main)\n3\n");
+			git.add().addFilepattern("b").call();
+			git.commit().setMessage("main").call();
 
-		writeTrashFile("b", "1\nb(main)\n3\n");
-		git.add().addFilepattern("b").call();
-		git.commit().setMessage("main").call();
-
-		MergeResult result = git.merge().include(secondCommit.getId())
-				.setStrategy(MergeStrategy.RESOLVE).call();
-		assertEquals(MergeStatus.CONFLICTING, result.getMergeStatus());
+			MergeResult result = git.merge().include(secondCommit.getId())
+					.setStrategy(MergeStrategy.RESOLVE).call();
+			assertEquals(MergeStatus.CONFLICTING, result.getMergeStatus());
+		}
 
 		FileTreeIterator iterator = new FileTreeIterator(db);
 		IndexDiff diff = new IndexDiff(db, Constants.HEAD, iterator);
@@ -308,23 +305,23 @@
 		writeTrashFile("a.c", "a.c");
 		writeTrashFile("a=c", "a=c");
 		writeTrashFile("a=d", "a=d");
-		Git git = new Git(db);
-		git.add().addFilepattern("a.b").call();
-		git.add().addFilepattern("a.c").call();
-		git.add().addFilepattern("a=c").call();
-		git.add().addFilepattern("a=d").call();
+		try (Git git = new Git(db)) {
+			git.add().addFilepattern("a.b").call();
+			git.add().addFilepattern("a.c").call();
+			git.add().addFilepattern("a=c").call();
+			git.add().addFilepattern("a=d").call();
+		}
 
-		Tree tree = new Tree(db);
+		TreeFormatter tree = new TreeFormatter();
 		// got the hash id'd from the data using echo -n a.b|git hash-object -t blob --stdin
-		tree.addFile("a.b").setId(ObjectId.fromString("f6f28df96c2b40c951164286e08be7c38ec74851"));
-		tree.addFile("a.c").setId(ObjectId.fromString("6bc0e647512d2a0bef4f26111e484dc87df7f5ca"));
-		tree.addFile("a=c").setId(ObjectId.fromString("06022365ddbd7fb126761319633bf73517770714"));
-		tree.addFile("a=d").setId(ObjectId.fromString("fa6414df3da87840700e9eeb7fc261dd77ccd5c2"));
-
-		tree.setId(insertTree(tree));
+		tree.append("a.b", FileMode.REGULAR_FILE, ObjectId.fromString("f6f28df96c2b40c951164286e08be7c38ec74851"));
+		tree.append("a.c", FileMode.REGULAR_FILE, ObjectId.fromString("6bc0e647512d2a0bef4f26111e484dc87df7f5ca"));
+		tree.append("a=c", FileMode.REGULAR_FILE, ObjectId.fromString("06022365ddbd7fb126761319633bf73517770714"));
+		tree.append("a=d", FileMode.REGULAR_FILE, ObjectId.fromString("fa6414df3da87840700e9eeb7fc261dd77ccd5c2"));
+		ObjectId treeId = insertTree(tree);
 
 		FileTreeIterator iterator = new FileTreeIterator(db);
-		IndexDiff diff = new IndexDiff(db, tree.getId(), iterator);
+		IndexDiff diff = new IndexDiff(db, treeId, iterator);
 		diff.diff();
 		assertEquals(0, diff.getChanged().size());
 		assertEquals(0, diff.getAdded().size());
@@ -343,7 +340,6 @@
 	 */
 	@Test
 	public void testUnchangedComplex() throws IOException, GitAPIException {
-		Git git = new Git(db);
 		writeTrashFile("a.b", "a.b");
 		writeTrashFile("a.c", "a.c");
 		writeTrashFile("a/b.b/b", "a/b.b/b");
@@ -351,29 +347,34 @@
 		writeTrashFile("a/c", "a/c");
 		writeTrashFile("a=c", "a=c");
 		writeTrashFile("a=d", "a=d");
-		git.add().addFilepattern("a.b").addFilepattern("a.c")
-				.addFilepattern("a/b.b/b").addFilepattern("a/b")
-				.addFilepattern("a/c").addFilepattern("a=c")
-				.addFilepattern("a=d").call();
+		try (Git git = new Git(db)) {
+			git.add().addFilepattern("a.b").addFilepattern("a.c")
+					.addFilepattern("a/b.b/b").addFilepattern("a/b")
+					.addFilepattern("a/c").addFilepattern("a=c")
+					.addFilepattern("a=d").call();
+		}
 
-		Tree tree = new Tree(db);
+
 		// got the hash id'd from the data using echo -n a.b|git hash-object -t blob --stdin
-		tree.addFile("a.b").setId(ObjectId.fromString("f6f28df96c2b40c951164286e08be7c38ec74851"));
-		tree.addFile("a.c").setId(ObjectId.fromString("6bc0e647512d2a0bef4f26111e484dc87df7f5ca"));
-		tree.addFile("a/b.b/b").setId(ObjectId.fromString("8d840bd4e2f3a48ff417c8e927d94996849933fd"));
-		tree.addFile("a/b").setId(ObjectId.fromString("db89c972fc57862eae378f45b74aca228037d415"));
-		tree.addFile("a/c").setId(ObjectId.fromString("52ad142a008aeb39694bafff8e8f1be75ed7f007"));
-		tree.addFile("a=c").setId(ObjectId.fromString("06022365ddbd7fb126761319633bf73517770714"));
-		tree.addFile("a=d").setId(ObjectId.fromString("fa6414df3da87840700e9eeb7fc261dd77ccd5c2"));
+		TreeFormatter bb = new TreeFormatter();
+		bb.append("b", FileMode.REGULAR_FILE, ObjectId.fromString("8d840bd4e2f3a48ff417c8e927d94996849933fd"));
 
-		Tree tree3 = (Tree) tree.findTreeMember("a/b.b");
-		tree3.setId(insertTree(tree3));
-		Tree tree2 = (Tree) tree.findTreeMember("a");
-		tree2.setId(insertTree(tree2));
-		tree.setId(insertTree(tree));
+		TreeFormatter a = new TreeFormatter();
+		a.append("b", FileMode.REGULAR_FILE, ObjectId
+				.fromString("db89c972fc57862eae378f45b74aca228037d415"));
+		a.append("b.b", FileMode.TREE, insertTree(bb));
+		a.append("c", FileMode.REGULAR_FILE, ObjectId.fromString("52ad142a008aeb39694bafff8e8f1be75ed7f007"));
+
+		TreeFormatter tree = new TreeFormatter();
+		tree.append("a.b", FileMode.REGULAR_FILE, ObjectId.fromString("f6f28df96c2b40c951164286e08be7c38ec74851"));
+		tree.append("a.c", FileMode.REGULAR_FILE, ObjectId.fromString("6bc0e647512d2a0bef4f26111e484dc87df7f5ca"));
+		tree.append("a", FileMode.TREE, insertTree(a));
+		tree.append("a=c", FileMode.REGULAR_FILE, ObjectId.fromString("06022365ddbd7fb126761319633bf73517770714"));
+		tree.append("a=d", FileMode.REGULAR_FILE, ObjectId.fromString("fa6414df3da87840700e9eeb7fc261dd77ccd5c2"));
+		ObjectId treeId = insertTree(tree);
 
 		FileTreeIterator iterator = new FileTreeIterator(db);
-		IndexDiff diff = new IndexDiff(db, tree.getId(), iterator);
+		IndexDiff diff = new IndexDiff(db, treeId, iterator);
 		diff.diff();
 		assertEquals(0, diff.getChanged().size());
 		assertEquals(0, diff.getAdded().size());
@@ -383,9 +384,9 @@
 		assertEquals(Collections.EMPTY_SET, diff.getUntrackedFolders());
 	}
 
-	private ObjectId insertTree(Tree tree) throws IOException {
+	private ObjectId insertTree(TreeFormatter tree) throws IOException {
 		try (ObjectInserter oi = db.newObjectInserter()) {
-			ObjectId id = oi.insert(Constants.OBJ_TREE, tree.format());
+			ObjectId id = oi.insert(tree);
 			oi.flush();
 			return id;
 		}
@@ -399,11 +400,12 @@
 	 */
 	@Test
 	public void testRemovedUntracked() throws Exception{
-		Git git = new Git(db);
 		String path = "file";
-		writeTrashFile(path, "content");
-		git.add().addFilepattern(path).call();
-		git.commit().setMessage("commit").call();
+		try (Git git = new Git(db)) {
+			writeTrashFile(path, "content");
+			git.add().addFilepattern(path).call();
+			git.commit().setMessage("commit").call();
+		}
 		removeFromIndex(path);
 		FileTreeIterator iterator = new FileTreeIterator(db);
 		IndexDiff diff = new IndexDiff(db, Constants.HEAD, iterator);
@@ -419,51 +421,51 @@
 	 */
 	@Test
 	public void testUntrackedFolders() throws Exception {
-		Git git = new Git(db);
+		try (Git git = new Git(db)) {
+			IndexDiff diff = new IndexDiff(db, Constants.HEAD,
+					new FileTreeIterator(db));
+			diff.diff();
+			assertEquals(Collections.EMPTY_SET, diff.getUntrackedFolders());
 
-		IndexDiff diff = new IndexDiff(db, Constants.HEAD,
-				new FileTreeIterator(db));
-		diff.diff();
-		assertEquals(Collections.EMPTY_SET, diff.getUntrackedFolders());
+			writeTrashFile("readme", "");
+			writeTrashFile("src/com/A.java", "");
+			writeTrashFile("src/com/B.java", "");
+			writeTrashFile("src/org/A.java", "");
+			writeTrashFile("src/org/B.java", "");
+			writeTrashFile("target/com/A.java", "");
+			writeTrashFile("target/com/B.java", "");
+			writeTrashFile("target/org/A.java", "");
+			writeTrashFile("target/org/B.java", "");
 
-		writeTrashFile("readme", "");
-		writeTrashFile("src/com/A.java", "");
-		writeTrashFile("src/com/B.java", "");
-		writeTrashFile("src/org/A.java", "");
-		writeTrashFile("src/org/B.java", "");
-		writeTrashFile("target/com/A.java", "");
-		writeTrashFile("target/com/B.java", "");
-		writeTrashFile("target/org/A.java", "");
-		writeTrashFile("target/org/B.java", "");
+			git.add().addFilepattern("src").addFilepattern("readme").call();
+			git.commit().setMessage("initial").call();
 
-		git.add().addFilepattern("src").addFilepattern("readme").call();
-		git.commit().setMessage("initial").call();
+			diff = new IndexDiff(db, Constants.HEAD,
+					new FileTreeIterator(db));
+			diff.diff();
+			assertEquals(new HashSet<String>(Arrays.asList("target")),
+					diff.getUntrackedFolders());
 
-		diff = new IndexDiff(db, Constants.HEAD,
-				new FileTreeIterator(db));
-		diff.diff();
-		assertEquals(new HashSet<String>(Arrays.asList("target")),
-				diff.getUntrackedFolders());
+			writeTrashFile("src/tst/A.java", "");
+			writeTrashFile("src/tst/B.java", "");
 
-		writeTrashFile("src/tst/A.java", "");
-		writeTrashFile("src/tst/B.java", "");
+			diff = new IndexDiff(db, Constants.HEAD, new FileTreeIterator(db));
+			diff.diff();
+			assertEquals(new HashSet<String>(Arrays.asList("target", "src/tst")),
+					diff.getUntrackedFolders());
 
-		diff = new IndexDiff(db, Constants.HEAD, new FileTreeIterator(db));
-		diff.diff();
-		assertEquals(new HashSet<String>(Arrays.asList("target", "src/tst")),
-				diff.getUntrackedFolders());
+			git.rm().addFilepattern("src/com/B.java").addFilepattern("src/org")
+					.call();
+			git.commit().setMessage("second").call();
+			writeTrashFile("src/org/C.java", "");
 
-		git.rm().addFilepattern("src/com/B.java").addFilepattern("src/org")
-				.call();
-		git.commit().setMessage("second").call();
-		writeTrashFile("src/org/C.java", "");
-
-		diff = new IndexDiff(db, Constants.HEAD, new FileTreeIterator(db));
-		diff.diff();
-		assertEquals(
-				new HashSet<String>(Arrays.asList("src/org", "src/tst",
-						"target")),
-				diff.getUntrackedFolders());
+			diff = new IndexDiff(db, Constants.HEAD, new FileTreeIterator(db));
+			diff.diff();
+			assertEquals(
+					new HashSet<String>(Arrays.asList("src/org", "src/tst",
+							"target")),
+					diff.getUntrackedFolders());
+		}
 	}
 
 	/**
@@ -473,85 +475,86 @@
 	 */
 	@Test
 	public void testUntrackedNotIgnoredFolders() throws Exception {
-		Git git = new Git(db);
+		try (Git git = new Git(db)) {
+			IndexDiff diff = new IndexDiff(db, Constants.HEAD,
+					new FileTreeIterator(db));
+			diff.diff();
+			assertEquals(Collections.EMPTY_SET, diff.getUntrackedFolders());
 
-		IndexDiff diff = new IndexDiff(db, Constants.HEAD,
-				new FileTreeIterator(db));
-		diff.diff();
-		assertEquals(Collections.EMPTY_SET, diff.getUntrackedFolders());
+			writeTrashFile("readme", "");
+			writeTrashFile("sr/com/X.java", "");
+			writeTrashFile("src/com/A.java", "");
+			writeTrashFile("src/org/B.java", "");
+			writeTrashFile("srcs/org/Y.java", "");
+			writeTrashFile("target/com/A.java", "");
+			writeTrashFile("target/org/B.java", "");
+			writeTrashFile(".gitignore", "/target\n/sr");
 
-		writeTrashFile("readme", "");
-		writeTrashFile("sr/com/X.java", "");
-		writeTrashFile("src/com/A.java", "");
-		writeTrashFile("src/org/B.java", "");
-		writeTrashFile("srcs/org/Y.java", "");
-		writeTrashFile("target/com/A.java", "");
-		writeTrashFile("target/org/B.java", "");
-		writeTrashFile(".gitignore", "/target\n/sr");
+			git.add().addFilepattern("readme").addFilepattern(".gitignore")
+					.addFilepattern("srcs/").call();
+			git.commit().setMessage("initial").call();
 
-		git.add().addFilepattern("readme").addFilepattern(".gitignore")
-				.addFilepattern("srcs/").call();
-		git.commit().setMessage("initial").call();
+			diff = new IndexDiff(db, Constants.HEAD, new FileTreeIterator(db));
+			diff.diff();
+			assertEquals(new HashSet<String>(Arrays.asList("src")),
+					diff.getUntrackedFolders());
 
-		diff = new IndexDiff(db, Constants.HEAD, new FileTreeIterator(db));
-		diff.diff();
-		assertEquals(new HashSet<String>(Arrays.asList("src")),
-				diff.getUntrackedFolders());
+			git.add().addFilepattern("src").call();
+			writeTrashFile("sr/com/X1.java", "");
+			writeTrashFile("src/tst/A.java", "");
+			writeTrashFile("src/tst/B.java", "");
+			writeTrashFile("srcs/com/Y1.java", "");
+			deleteTrashFile(".gitignore");
 
-		git.add().addFilepattern("src").call();
-		writeTrashFile("sr/com/X1.java", "");
-		writeTrashFile("src/tst/A.java", "");
-		writeTrashFile("src/tst/B.java", "");
-		writeTrashFile("srcs/com/Y1.java", "");
-		deleteTrashFile(".gitignore");
-
-		diff = new IndexDiff(db, Constants.HEAD, new FileTreeIterator(db));
-		diff.diff();
-		assertEquals(
-				new HashSet<String>(Arrays.asList("srcs/com", "sr", "src/tst",
-						"target")),
-				diff.getUntrackedFolders());
+			diff = new IndexDiff(db, Constants.HEAD, new FileTreeIterator(db));
+			diff.diff();
+			assertEquals(
+					new HashSet<String>(Arrays.asList("srcs/com", "sr", "src/tst",
+							"target")),
+					diff.getUntrackedFolders());
+		}
 	}
 
 	@Test
 	public void testAssumeUnchanged() throws Exception {
-		Git git = new Git(db);
-		String path = "file";
-		writeTrashFile(path, "content");
-		git.add().addFilepattern(path).call();
-		String path2 = "file2";
-		writeTrashFile(path2, "content");
-		String path3 = "file3";
-		writeTrashFile(path3, "some content");
-		git.add().addFilepattern(path2).addFilepattern(path3).call();
-		git.commit().setMessage("commit").call();
-		assumeUnchanged(path2);
-		assumeUnchanged(path3);
-		writeTrashFile(path, "more content");
-		deleteTrashFile(path3);
+		try (Git git = new Git(db)) {
+			String path = "file";
+			writeTrashFile(path, "content");
+			git.add().addFilepattern(path).call();
+			String path2 = "file2";
+			writeTrashFile(path2, "content");
+			String path3 = "file3";
+			writeTrashFile(path3, "some content");
+			git.add().addFilepattern(path2).addFilepattern(path3).call();
+			git.commit().setMessage("commit").call();
+			assumeUnchanged(path2);
+			assumeUnchanged(path3);
+			writeTrashFile(path, "more content");
+			deleteTrashFile(path3);
 
-		FileTreeIterator iterator = new FileTreeIterator(db);
-		IndexDiff diff = new IndexDiff(db, Constants.HEAD, iterator);
-		diff.diff();
-		assertEquals(2, diff.getAssumeUnchanged().size());
-		assertEquals(1, diff.getModified().size());
-		assertEquals(0, diff.getChanged().size());
-		assertTrue(diff.getAssumeUnchanged().contains("file2"));
-		assertTrue(diff.getAssumeUnchanged().contains("file3"));
-		assertTrue(diff.getModified().contains("file"));
+			FileTreeIterator iterator = new FileTreeIterator(db);
+			IndexDiff diff = new IndexDiff(db, Constants.HEAD, iterator);
+			diff.diff();
+			assertEquals(2, diff.getAssumeUnchanged().size());
+			assertEquals(1, diff.getModified().size());
+			assertEquals(0, diff.getChanged().size());
+			assertTrue(diff.getAssumeUnchanged().contains("file2"));
+			assertTrue(diff.getAssumeUnchanged().contains("file3"));
+			assertTrue(diff.getModified().contains("file"));
 
-		git.add().addFilepattern(".").call();
+			git.add().addFilepattern(".").call();
 
-		iterator = new FileTreeIterator(db);
-		diff = new IndexDiff(db, Constants.HEAD, iterator);
-		diff.diff();
-		assertEquals(2, diff.getAssumeUnchanged().size());
-		assertEquals(0, diff.getModified().size());
-		assertEquals(1, diff.getChanged().size());
-		assertTrue(diff.getAssumeUnchanged().contains("file2"));
-		assertTrue(diff.getAssumeUnchanged().contains("file3"));
-		assertTrue(diff.getChanged().contains("file"));
-		assertEquals(Collections.EMPTY_SET, diff.getUntrackedFolders());
+			iterator = new FileTreeIterator(db);
+			diff = new IndexDiff(db, Constants.HEAD, iterator);
+			diff.diff();
+			assertEquals(2, diff.getAssumeUnchanged().size());
+			assertEquals(0, diff.getModified().size());
+			assertEquals(1, diff.getChanged().size());
+			assertTrue(diff.getAssumeUnchanged().contains("file2"));
+			assertTrue(diff.getAssumeUnchanged().contains("file3"));
+			assertTrue(diff.getChanged().contains("file"));
+			assertEquals(Collections.EMPTY_SET, diff.getUntrackedFolders());
+		}
 	}
 
 	@Test
@@ -577,147 +580,148 @@
 
 	@Test
 	public void testStageState_mergeAndReset_bug() throws Exception {
-		Git git = new Git(db);
+		try (Git git = new Git(db)) {
+			writeTrashFile("a", "content");
+			git.add().addFilepattern("a").call();
+			RevCommit initialCommit = git.commit().setMessage("initial commit")
+					.call();
 
-		writeTrashFile("a", "content");
-		git.add().addFilepattern("a").call();
-		RevCommit initialCommit = git.commit().setMessage("initial commit")
-				.call();
+			// create branch and add a new file
+			final String branchName = Constants.R_HEADS + "branch";
+			createBranch(initialCommit, branchName);
+			checkoutBranch(branchName);
+			writeTrashFile("b", "second file content - branch");
+			git.add().addFilepattern("b").call();
+			RevCommit branchCommit = git.commit().setMessage("branch commit")
+					.call();
 
-		// create branch and add a new file
-		final String branchName = Constants.R_HEADS + "branch";
-		createBranch(initialCommit, branchName);
-		checkoutBranch(branchName);
-		writeTrashFile("b", "second file content - branch");
-		git.add().addFilepattern("b").call();
-		RevCommit branchCommit = git.commit().setMessage("branch commit")
-				.call();
+			// checkout master and add the same new file
+			checkoutBranch(Constants.R_HEADS + Constants.MASTER);
+			writeTrashFile("b", "second file content - master");
+			git.add().addFilepattern("b").call();
+			git.commit().setMessage("master commit").call();
 
-		// checkout master and add the same new file
-		checkoutBranch(Constants.R_HEADS + Constants.MASTER);
-		writeTrashFile("b", "second file content - master");
-		git.add().addFilepattern("b").call();
-		git.commit().setMessage("master commit").call();
+			// try and merge
+			MergeResult result = git.merge().include(branchCommit).call();
+			assertEquals(MergeStatus.CONFLICTING, result.getMergeStatus());
 
-		// try and merge
-		MergeResult result = git.merge().include(branchCommit).call();
-		assertEquals(MergeStatus.CONFLICTING, result.getMergeStatus());
+			FileTreeIterator iterator = new FileTreeIterator(db);
+			IndexDiff diff = new IndexDiff(db, Constants.HEAD, iterator);
+			diff.diff();
 
-		FileTreeIterator iterator = new FileTreeIterator(db);
-		IndexDiff diff = new IndexDiff(db, Constants.HEAD, iterator);
-		diff.diff();
+			assertTrue(diff.getChanged().isEmpty());
+			assertTrue(diff.getAdded().isEmpty());
+			assertTrue(diff.getRemoved().isEmpty());
+			assertTrue(diff.getMissing().isEmpty());
+			assertTrue(diff.getModified().isEmpty());
+			assertEquals(1, diff.getConflicting().size());
+			assertTrue(diff.getConflicting().contains("b"));
+			assertEquals(StageState.BOTH_ADDED, diff.getConflictingStageStates()
+					.get("b"));
+			assertTrue(diff.getUntrackedFolders().isEmpty());
 
-		assertTrue(diff.getChanged().isEmpty());
-		assertTrue(diff.getAdded().isEmpty());
-		assertTrue(diff.getRemoved().isEmpty());
-		assertTrue(diff.getMissing().isEmpty());
-		assertTrue(diff.getModified().isEmpty());
-		assertEquals(1, diff.getConflicting().size());
-		assertTrue(diff.getConflicting().contains("b"));
-		assertEquals(StageState.BOTH_ADDED, diff.getConflictingStageStates()
-				.get("b"));
-		assertTrue(diff.getUntrackedFolders().isEmpty());
+			// reset file b to its master state without altering the index
+			writeTrashFile("b", "second file content - master");
 
-		// reset file b to its master state without altering the index
-		writeTrashFile("b", "second file content - master");
+			// we should have the same result
+			iterator = new FileTreeIterator(db);
+			diff = new IndexDiff(db, Constants.HEAD, iterator);
+			diff.diff();
 
-		// we should have the same result
-		iterator = new FileTreeIterator(db);
-		diff = new IndexDiff(db, Constants.HEAD, iterator);
-		diff.diff();
-
-		assertTrue(diff.getChanged().isEmpty());
-		assertTrue(diff.getAdded().isEmpty());
-		assertTrue(diff.getRemoved().isEmpty());
-		assertTrue(diff.getMissing().isEmpty());
-		assertTrue(diff.getModified().isEmpty());
-		assertEquals(1, diff.getConflicting().size());
-		assertTrue(diff.getConflicting().contains("b"));
-		assertEquals(StageState.BOTH_ADDED, diff.getConflictingStageStates()
-				.get("b"));
-		assertTrue(diff.getUntrackedFolders().isEmpty());
+			assertTrue(diff.getChanged().isEmpty());
+			assertTrue(diff.getAdded().isEmpty());
+			assertTrue(diff.getRemoved().isEmpty());
+			assertTrue(diff.getMissing().isEmpty());
+			assertTrue(diff.getModified().isEmpty());
+			assertEquals(1, diff.getConflicting().size());
+			assertTrue(diff.getConflicting().contains("b"));
+			assertEquals(StageState.BOTH_ADDED, diff.getConflictingStageStates()
+					.get("b"));
+			assertTrue(diff.getUntrackedFolders().isEmpty());
+		}
 	}
 
 	@Test
 	public void testStageState_simulated_bug() throws Exception {
-		Git git = new Git(db);
+		try (Git git = new Git(db)) {
+			writeTrashFile("a", "content");
+			git.add().addFilepattern("a").call();
+			RevCommit initialCommit = git.commit().setMessage("initial commit")
+					.call();
 
-		writeTrashFile("a", "content");
-		git.add().addFilepattern("a").call();
-		RevCommit initialCommit = git.commit().setMessage("initial commit")
-				.call();
+			// create branch and add a new file
+			final String branchName = Constants.R_HEADS + "branch";
+			createBranch(initialCommit, branchName);
+			checkoutBranch(branchName);
+			writeTrashFile("b", "second file content - branch");
+			git.add().addFilepattern("b").call();
+			git.commit().setMessage("branch commit")
+					.call();
 
-		// create branch and add a new file
-		final String branchName = Constants.R_HEADS + "branch";
-		createBranch(initialCommit, branchName);
-		checkoutBranch(branchName);
-		writeTrashFile("b", "second file content - branch");
-		git.add().addFilepattern("b").call();
-		git.commit().setMessage("branch commit")
-				.call();
+			// checkout master and add the same new file
+			checkoutBranch(Constants.R_HEADS + Constants.MASTER);
+			writeTrashFile("b", "second file content - master");
+			git.add().addFilepattern("b").call();
+			git.commit().setMessage("master commit").call();
 
-		// checkout master and add the same new file
-		checkoutBranch(Constants.R_HEADS + Constants.MASTER);
-		writeTrashFile("b", "second file content - master");
-		git.add().addFilepattern("b").call();
-		git.commit().setMessage("master commit").call();
+			// Simulate a failed merge of branch into master
+			DirCacheBuilder builder = db.lockDirCache().builder();
+			DirCacheEntry entry = createEntry("a", FileMode.REGULAR_FILE, 0,
+					"content");
+			builder.add(entry);
+			entry = createEntry("b", FileMode.REGULAR_FILE, 2,
+					"second file content - master");
+			builder.add(entry);
+			entry = createEntry("b", FileMode.REGULAR_FILE, 3,
+					"second file content - branch");
+			builder.add(entry);
+			builder.commit();
 
-		// Simulate a failed merge of branch into master
-		DirCacheBuilder builder = db.lockDirCache().builder();
-		DirCacheEntry entry = createEntry("a", FileMode.REGULAR_FILE, 0,
-				"content");
-		builder.add(entry);
-		entry = createEntry("b", FileMode.REGULAR_FILE, 2,
-				"second file content - master");
-		builder.add(entry);
-		entry = createEntry("b", FileMode.REGULAR_FILE, 3,
-				"second file content - branch");
-		builder.add(entry);
-		builder.commit();
+			FileTreeIterator iterator = new FileTreeIterator(db);
+			IndexDiff diff = new IndexDiff(db, Constants.HEAD, iterator);
+			diff.diff();
 
-		FileTreeIterator iterator = new FileTreeIterator(db);
-		IndexDiff diff = new IndexDiff(db, Constants.HEAD, iterator);
-		diff.diff();
-
-		assertTrue(diff.getChanged().isEmpty());
-		assertTrue(diff.getAdded().isEmpty());
-		assertTrue(diff.getRemoved().isEmpty());
-		assertTrue(diff.getMissing().isEmpty());
-		assertTrue(diff.getModified().isEmpty());
-		assertEquals(1, diff.getConflicting().size());
-		assertTrue(diff.getConflicting().contains("b"));
-		assertEquals(StageState.BOTH_ADDED, diff.getConflictingStageStates()
-				.get("b"));
-		assertTrue(diff.getUntrackedFolders().isEmpty());
+			assertTrue(diff.getChanged().isEmpty());
+			assertTrue(diff.getAdded().isEmpty());
+			assertTrue(diff.getRemoved().isEmpty());
+			assertTrue(diff.getMissing().isEmpty());
+			assertTrue(diff.getModified().isEmpty());
+			assertEquals(1, diff.getConflicting().size());
+			assertTrue(diff.getConflicting().contains("b"));
+			assertEquals(StageState.BOTH_ADDED, diff.getConflictingStageStates()
+					.get("b"));
+			assertTrue(diff.getUntrackedFolders().isEmpty());
+		}
 	}
 
 	@Test
 	public void testAutoCRLFInput() throws Exception {
-		Git git = new Git(db);
-		FileBasedConfig config = db.getConfig();
+		try (Git git = new Git(db)) {
+			FileBasedConfig config = db.getConfig();
 
-		// Make sure core.autocrlf is false before adding
-		config.setEnum(ConfigConstants.CONFIG_CORE_SECTION, null,
-				ConfigConstants.CONFIG_KEY_AUTOCRLF, AutoCRLF.FALSE);
-		config.save();
+			// Make sure core.autocrlf is false before adding
+			config.setEnum(ConfigConstants.CONFIG_CORE_SECTION, null,
+					ConfigConstants.CONFIG_KEY_AUTOCRLF, AutoCRLF.FALSE);
+			config.save();
 
-		// File is already in repository with CRLF
-		writeTrashFile("crlf.txt", "this\r\ncontains\r\ncrlf\r\n");
-		git.add().addFilepattern("crlf.txt").call();
-		git.commit().setMessage("Add crlf.txt").call();
+			// File is already in repository with CRLF
+			writeTrashFile("crlf.txt", "this\r\ncontains\r\ncrlf\r\n");
+			git.add().addFilepattern("crlf.txt").call();
+			git.commit().setMessage("Add crlf.txt").call();
 
-		// Now set core.autocrlf to input
-		config.setEnum(ConfigConstants.CONFIG_CORE_SECTION, null,
-				ConfigConstants.CONFIG_KEY_AUTOCRLF, AutoCRLF.INPUT);
-		config.save();
+			// Now set core.autocrlf to input
+			config.setEnum(ConfigConstants.CONFIG_CORE_SECTION, null,
+					ConfigConstants.CONFIG_KEY_AUTOCRLF, AutoCRLF.INPUT);
+			config.save();
 
-		FileTreeIterator iterator = new FileTreeIterator(db);
-		IndexDiff diff = new IndexDiff(db, Constants.HEAD, iterator);
-		diff.diff();
+			FileTreeIterator iterator = new FileTreeIterator(db);
+			IndexDiff diff = new IndexDiff(db, Constants.HEAD, iterator);
+			diff.diff();
 
-		assertTrue(
-				"Expected no modified files, but there were: "
-						+ diff.getModified(), diff.getModified().isEmpty());
+			assertTrue(
+					"Expected no modified files, but there were: "
+							+ diff.getModified(), diff.getModified().isEmpty());
+		}
 	}
 
 	private void verifyStageState(StageState expected, int... stages)
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/IndexModificationTimesTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/IndexModificationTimesTest.java
index c6f02f4..b9bbbeb 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/IndexModificationTimesTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/IndexModificationTimesTest.java
@@ -49,80 +49,81 @@
 
 	@Test
 	public void testLastModifiedTimes() throws Exception {
-		Git git = new Git(db);
-		String path = "file";
-		writeTrashFile(path, "content");
-		String path2 = "file2";
-		writeTrashFile(path2, "content2");
+		try (Git git = new Git(db)) {
+			String path = "file";
+			writeTrashFile(path, "content");
+			String path2 = "file2";
+			writeTrashFile(path2, "content2");
 
-		git.add().addFilepattern(path).call();
-		git.add().addFilepattern(path2).call();
-		git.commit().setMessage("commit").call();
+			git.add().addFilepattern(path).call();
+			git.add().addFilepattern(path2).call();
+			git.commit().setMessage("commit").call();
 
-		DirCache dc = db.readDirCache();
-		DirCacheEntry entry = dc.getEntry(path);
-		DirCacheEntry entry2 = dc.getEntry(path);
+			DirCache dc = db.readDirCache();
+			DirCacheEntry entry = dc.getEntry(path);
+			DirCacheEntry entry2 = dc.getEntry(path);
 
-		assertTrue("last modified shall not be zero!",
-				entry.getLastModified() != 0);
+			assertTrue("last modified shall not be zero!",
+					entry.getLastModified() != 0);
 
-		assertTrue("last modified shall not be zero!",
-				entry2.getLastModified() != 0);
+			assertTrue("last modified shall not be zero!",
+					entry2.getLastModified() != 0);
 
-		writeTrashFile(path, "new content");
-		git.add().addFilepattern(path).call();
-		git.commit().setMessage("commit2").call();
+			writeTrashFile(path, "new content");
+			git.add().addFilepattern(path).call();
+			git.commit().setMessage("commit2").call();
 
-		dc = db.readDirCache();
-		entry = dc.getEntry(path);
-		entry2 = dc.getEntry(path);
+			dc = db.readDirCache();
+			entry = dc.getEntry(path);
+			entry2 = dc.getEntry(path);
 
-		assertTrue("last modified shall not be zero!",
-				entry.getLastModified() != 0);
+			assertTrue("last modified shall not be zero!",
+					entry.getLastModified() != 0);
 
-		assertTrue("last modified shall not be zero!",
-				entry2.getLastModified() != 0);
+			assertTrue("last modified shall not be zero!",
+					entry2.getLastModified() != 0);
+		}
 	}
 
 	@Test
 	public void testModify() throws Exception {
-		Git git = new Git(db);
-		String path = "file";
-		writeTrashFile(path, "content");
+		try (Git git = new Git(db)) {
+			String path = "file";
+			writeTrashFile(path, "content");
 
-		git.add().addFilepattern(path).call();
-		git.commit().setMessage("commit").call();
+			git.add().addFilepattern(path).call();
+			git.commit().setMessage("commit").call();
 
-		DirCache dc = db.readDirCache();
-		DirCacheEntry entry = dc.getEntry(path);
+			DirCache dc = db.readDirCache();
+			DirCacheEntry entry = dc.getEntry(path);
 
-		long masterLastMod = entry.getLastModified();
+			long masterLastMod = entry.getLastModified();
 
-		git.checkout().setCreateBranch(true).setName("side").call();
+			git.checkout().setCreateBranch(true).setName("side").call();
 
-		Thread.sleep(10);
-		String path2 = "file2";
-		writeTrashFile(path2, "side content");
-		git.add().addFilepattern(path2).call();
-		git.commit().setMessage("commit").call();
+			Thread.sleep(10);
+			String path2 = "file2";
+			writeTrashFile(path2, "side content");
+			git.add().addFilepattern(path2).call();
+			git.commit().setMessage("commit").call();
 
-		dc = db.readDirCache();
-		entry = dc.getEntry(path);
+			dc = db.readDirCache();
+			entry = dc.getEntry(path);
 
-		long sideLastMode = entry.getLastModified();
+			long sideLastMode = entry.getLastModified();
 
-		Thread.sleep(2000);
+			Thread.sleep(2000);
 
-		writeTrashFile(path, "uncommitted content");
-		git.checkout().setName("master").call();
+			writeTrashFile(path, "uncommitted content");
+			git.checkout().setName("master").call();
 
-		dc = db.readDirCache();
-		entry = dc.getEntry(path);
+			dc = db.readDirCache();
+			entry = dc.getEntry(path);
 
-		assertTrue("shall have equal mod time!", masterLastMod == sideLastMode);
-		assertTrue("shall not equal master timestamp!",
-				entry.getLastModified() == masterLastMod);
-
+			assertTrue("shall have equal mod time!", masterLastMod == sideLastMode);
+			assertTrue("shall not equal master timestamp!",
+					entry.getLastModified() == masterLastMod);
+		}
 	}
 
 }
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ObjectCheckerTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ObjectCheckerTest.java
index 3abe81c..43160fb 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ObjectCheckerTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ObjectCheckerTest.java
@@ -45,8 +45,25 @@
 package org.eclipse.jgit.lib;
 
 import static java.lang.Integer.valueOf;
-import static java.lang.Long.valueOf;
+import static org.eclipse.jgit.lib.Constants.OBJECT_ID_LENGTH;
+import static org.eclipse.jgit.lib.Constants.OBJ_BAD;
+import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
+import static org.eclipse.jgit.lib.Constants.OBJ_COMMIT;
+import static org.eclipse.jgit.lib.Constants.OBJ_TAG;
+import static org.eclipse.jgit.lib.Constants.OBJ_TREE;
+import static org.eclipse.jgit.lib.Constants.encode;
+import static org.eclipse.jgit.lib.Constants.encodeASCII;
+import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.DUPLICATE_ENTRIES;
+import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.EMPTY_NAME;
+import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.FULL_PATHNAME;
+import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.HAS_DOT;
+import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.HAS_DOTDOT;
+import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.HAS_DOTGIT;
+import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.NULL_SHA1;
+import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.TREE_NOT_SORTED;
+import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.ZERO_PADDED_FILEMODE;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
 import static org.junit.Assert.fail;
 
 import java.io.UnsupportedEncodingException;
@@ -67,15 +84,10 @@
 
 	@Test
 	public void testInvalidType() {
-		try {
-			checker.check(Constants.OBJ_BAD, new byte[0]);
-			fail("Did not throw CorruptObjectException");
-		} catch (CorruptObjectException e) {
-			final String m = e.getMessage();
-			assertEquals(MessageFormat.format(
-					JGitText.get().corruptObjectInvalidType2,
-					valueOf(Constants.OBJ_BAD)), m);
-		}
+		String msg = MessageFormat.format(
+				JGitText.get().corruptObjectInvalidType2,
+				valueOf(OBJ_BAD));
+		assertCorrupt(msg, OBJ_BAD, new byte[0]);
 	}
 
 	@Test
@@ -84,13 +96,13 @@
 		checker.checkBlob(new byte[0]);
 		checker.checkBlob(new byte[1]);
 
-		checker.check(Constants.OBJ_BLOB, new byte[0]);
-		checker.check(Constants.OBJ_BLOB, new byte[1]);
+		checker.check(OBJ_BLOB, new byte[0]);
+		checker.check(OBJ_BLOB, new byte[1]);
 	}
 
 	@Test
 	public void testValidCommitNoParent() throws CorruptObjectException {
-		final StringBuilder b = new StringBuilder();
+		StringBuilder b = new StringBuilder();
 
 		b.append("tree ");
 		b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189");
@@ -99,14 +111,14 @@
 		b.append("author A. U. Thor <author@localhost> 1 +0000\n");
 		b.append("committer A. U. Thor <author@localhost> 1 +0000\n");
 
-		final byte[] data = Constants.encodeASCII(b.toString());
+		byte[] data = encodeASCII(b.toString());
 		checker.checkCommit(data);
-		checker.check(Constants.OBJ_COMMIT, data);
+		checker.check(OBJ_COMMIT, data);
 	}
 
 	@Test
 	public void testValidCommitBlankAuthor() throws CorruptObjectException {
-		final StringBuilder b = new StringBuilder();
+		StringBuilder b = new StringBuilder();
 
 		b.append("tree ");
 		b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189");
@@ -115,9 +127,9 @@
 		b.append("author <> 0 +0000\n");
 		b.append("committer <> 0 +0000\n");
 
-		final byte[] data = Constants.encodeASCII(b.toString());
+		byte[] data = encodeASCII(b.toString());
 		checker.checkCommit(data);
-		checker.check(Constants.OBJ_COMMIT, data);
+		checker.check(OBJ_COMMIT, data);
 	}
 
 	@Test
@@ -127,15 +139,13 @@
 		b.append("author b <b@c> <b@c> 0 +0000\n");
 		b.append("committer <> 0 +0000\n");
 
-		byte[] data = Constants.encodeASCII(b.toString());
-		try {
-			checker.checkCommit(data);
-			fail("Did not catch corrupt object");
-		} catch (CorruptObjectException e) {
-			assertEquals("invalid author", e.getMessage());
-		}
+		byte[] data = encodeASCII(b.toString());
+		assertCorrupt("bad date", OBJ_COMMIT, data);
 		checker.setAllowInvalidPersonIdent(true);
 		checker.checkCommit(data);
+
+		checker.setAllowInvalidPersonIdent(false);
+		assertSkipListAccepts(OBJ_COMMIT, data);
 	}
 
 	@Test
@@ -145,20 +155,18 @@
 		b.append("author <> 0 +0000\n");
 		b.append("committer b <b@c> <b@c> 0 +0000\n");
 
-		byte[] data = Constants.encodeASCII(b.toString());
-		try {
-			checker.checkCommit(data);
-			fail("Did not catch corrupt object");
-		} catch (CorruptObjectException e) {
-			assertEquals("invalid committer", e.getMessage());
-		}
+		byte[] data = encodeASCII(b.toString());
+		assertCorrupt("bad date", OBJ_COMMIT, data);
 		checker.setAllowInvalidPersonIdent(true);
 		checker.checkCommit(data);
+
+		checker.setAllowInvalidPersonIdent(false);
+		assertSkipListAccepts(OBJ_COMMIT, data);
 	}
 
 	@Test
 	public void testValidCommit1Parent() throws CorruptObjectException {
-		final StringBuilder b = new StringBuilder();
+		StringBuilder b = new StringBuilder();
 
 		b.append("tree ");
 		b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189");
@@ -171,14 +179,14 @@
 		b.append("author A. U. Thor <author@localhost> 1 +0000\n");
 		b.append("committer A. U. Thor <author@localhost> 1 +0000\n");
 
-		final byte[] data = Constants.encodeASCII(b.toString());
+		byte[] data = encodeASCII(b.toString());
 		checker.checkCommit(data);
-		checker.check(Constants.OBJ_COMMIT, data);
+		checker.check(OBJ_COMMIT, data);
 	}
 
 	@Test
 	public void testValidCommit2Parent() throws CorruptObjectException {
-		final StringBuilder b = new StringBuilder();
+		StringBuilder b = new StringBuilder();
 
 		b.append("tree ");
 		b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189");
@@ -195,14 +203,14 @@
 		b.append("author A. U. Thor <author@localhost> 1 +0000\n");
 		b.append("committer A. U. Thor <author@localhost> 1 +0000\n");
 
-		final byte[] data = Constants.encodeASCII(b.toString());
+		byte[] data = encodeASCII(b.toString());
 		checker.checkCommit(data);
-		checker.check(Constants.OBJ_COMMIT, data);
+		checker.check(OBJ_COMMIT, data);
 	}
 
 	@Test
 	public void testValidCommit128Parent() throws CorruptObjectException {
-		final StringBuilder b = new StringBuilder();
+		StringBuilder b = new StringBuilder();
 
 		b.append("tree ");
 		b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189");
@@ -217,15 +225,15 @@
 		b.append("author A. U. Thor <author@localhost> 1 +0000\n");
 		b.append("committer A. U. Thor <author@localhost> 1 +0000\n");
 
-		final byte[] data = Constants.encodeASCII(b.toString());
+		byte[] data = encodeASCII(b.toString());
 		checker.checkCommit(data);
-		checker.check(Constants.OBJ_COMMIT, data);
+		checker.check(OBJ_COMMIT, data);
 	}
 
 	@Test
 	public void testValidCommitNormalTime() throws CorruptObjectException {
-		final StringBuilder b = new StringBuilder();
-		final String when = "1222757360 -0730";
+		StringBuilder b = new StringBuilder();
+		String when = "1222757360 -0730";
 
 		b.append("tree ");
 		b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189");
@@ -234,843 +242,539 @@
 		b.append("author A. U. Thor <author@localhost> " + when + "\n");
 		b.append("committer A. U. Thor <author@localhost> " + when + "\n");
 
-		final byte[] data = Constants.encodeASCII(b.toString());
+		byte[] data = encodeASCII(b.toString());
 		checker.checkCommit(data);
-		checker.check(Constants.OBJ_COMMIT, data);
+		checker.check(OBJ_COMMIT, data);
 	}
 
 	@Test
 	public void testInvalidCommitNoTree1() {
-		final StringBuilder b = new StringBuilder();
-
+		StringBuilder b = new StringBuilder();
 		b.append("parent ");
 		b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189");
 		b.append('\n');
-
-		final byte[] data = Constants.encodeASCII(b.toString());
-		try {
-			checker.checkCommit(data);
-			fail("Did not catch corrupt object");
-		} catch (CorruptObjectException e) {
-			assertEquals("no tree header", e.getMessage());
-		}
+		assertCorrupt("no tree header", OBJ_COMMIT, b);
 	}
 
 	@Test
 	public void testInvalidCommitNoTree2() {
-		final StringBuilder b = new StringBuilder();
-
+		StringBuilder b = new StringBuilder();
 		b.append("trie ");
 		b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189");
 		b.append('\n');
-
-		final byte[] data = Constants.encodeASCII(b.toString());
-		try {
-			checker.checkCommit(data);
-			fail("Did not catch corrupt object");
-		} catch (CorruptObjectException e) {
-			assertEquals("no tree header", e.getMessage());
-		}
+		assertCorrupt("no tree header", OBJ_COMMIT, b);
 	}
 
 	@Test
 	public void testInvalidCommitNoTree3() {
-		final StringBuilder b = new StringBuilder();
-
+		StringBuilder b = new StringBuilder();
 		b.append("tree");
 		b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189");
 		b.append('\n');
-
-		final byte[] data = Constants.encodeASCII(b.toString());
-		try {
-			checker.checkCommit(data);
-			fail("Did not catch corrupt object");
-		} catch (CorruptObjectException e) {
-			assertEquals("no tree header", e.getMessage());
-		}
+		assertCorrupt("no tree header", OBJ_COMMIT, b);
 	}
 
 	@Test
 	public void testInvalidCommitNoTree4() {
-		final StringBuilder b = new StringBuilder();
-
+		StringBuilder b = new StringBuilder();
 		b.append("tree\t");
 		b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189");
 		b.append('\n');
-
-		final byte[] data = Constants.encodeASCII(b.toString());
-		try {
-			checker.checkCommit(data);
-			fail("Did not catch corrupt object");
-		} catch (CorruptObjectException e) {
-			assertEquals("no tree header", e.getMessage());
-		}
+		assertCorrupt("no tree header", OBJ_COMMIT, b);
 	}
 
 	@Test
 	public void testInvalidCommitInvalidTree1() {
-		final StringBuilder b = new StringBuilder();
-
+		StringBuilder b = new StringBuilder();
 		b.append("tree ");
 		b.append("zzzzfa841874ccc9f2ef7c48d0c76226f89b7189");
 		b.append('\n');
-
-		final byte[] data = Constants.encodeASCII(b.toString());
-		try {
-			checker.checkCommit(data);
-			fail("Did not catch corrupt object");
-		} catch (CorruptObjectException e) {
-			assertEquals("invalid tree", e.getMessage());
-		}
+		assertCorrupt("invalid tree", OBJ_COMMIT, b);
 	}
 
 	@Test
 	public void testInvalidCommitInvalidTree2() {
-		final StringBuilder b = new StringBuilder();
-
+		StringBuilder b = new StringBuilder();
 		b.append("tree ");
 		b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189");
 		b.append("z\n");
-
-		final byte[] data = Constants.encodeASCII(b.toString());
-		try {
-			checker.checkCommit(data);
-			fail("Did not catch corrupt object");
-		} catch (CorruptObjectException e) {
-			assertEquals("invalid tree", e.getMessage());
-		}
+		assertCorrupt("invalid tree", OBJ_COMMIT, b);
 	}
 
 	@Test
 	public void testInvalidCommitInvalidTree3() {
-		final StringBuilder b = new StringBuilder();
-
+		StringBuilder b = new StringBuilder();
 		b.append("tree ");
 		b.append("be9b");
 		b.append("\n");
 
-		final byte[] data = Constants.encodeASCII(b.toString());
-		try {
-			checker.checkCommit(data);
-			fail("Did not catch corrupt object");
-		} catch (CorruptObjectException e) {
-			assertEquals("invalid tree", e.getMessage());
-		}
+		byte[] data = encodeASCII(b.toString());
+		assertCorrupt("invalid tree", OBJ_COMMIT, data);
 	}
 
 	@Test
 	public void testInvalidCommitInvalidTree4() {
-		final StringBuilder b = new StringBuilder();
-
+		StringBuilder b = new StringBuilder();
 		b.append("tree  ");
 		b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189");
 		b.append('\n');
-
-		final byte[] data = Constants.encodeASCII(b.toString());
-		try {
-			checker.checkCommit(data);
-			fail("Did not catch corrupt object");
-		} catch (CorruptObjectException e) {
-			assertEquals("invalid tree", e.getMessage());
-		}
+		assertCorrupt("invalid tree", OBJ_COMMIT, b);
 	}
 
 	@Test
 	public void testInvalidCommitInvalidParent1() {
-		final StringBuilder b = new StringBuilder();
-
+		StringBuilder b = new StringBuilder();
 		b.append("tree ");
 		b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189");
 		b.append('\n');
-
 		b.append("parent ");
 		b.append("\n");
-
-		final byte[] data = Constants.encodeASCII(b.toString());
-		try {
-			checker.checkCommit(data);
-			fail("Did not catch corrupt object");
-		} catch (CorruptObjectException e) {
-			assertEquals("invalid parent", e.getMessage());
-		}
+		assertCorrupt("invalid parent", OBJ_COMMIT, b);
 	}
 
 	@Test
 	public void testInvalidCommitInvalidParent2() {
-		final StringBuilder b = new StringBuilder();
-
+		StringBuilder b = new StringBuilder();
 		b.append("tree ");
 		b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189");
 		b.append('\n');
-
 		b.append("parent ");
 		b.append("zzzzfa841874ccc9f2ef7c48d0c76226f89b7189");
 		b.append("\n");
-
-		final byte[] data = Constants.encodeASCII(b.toString());
-		try {
-			checker.checkCommit(data);
-			fail("Did not catch corrupt object");
-		} catch (CorruptObjectException e) {
-			assertEquals("invalid parent", e.getMessage());
-		}
+		assertCorrupt("invalid parent", OBJ_COMMIT, b);
 	}
 
 	@Test
 	public void testInvalidCommitInvalidParent3() {
-		final StringBuilder b = new StringBuilder();
-
+		StringBuilder b = new StringBuilder();
 		b.append("tree ");
 		b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189");
 		b.append('\n');
-
 		b.append("parent  ");
 		b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189");
 		b.append("\n");
-
-		final byte[] data = Constants.encodeASCII(b.toString());
-		try {
-			checker.checkCommit(data);
-			fail("Did not catch corrupt object");
-		} catch (CorruptObjectException e) {
-			assertEquals("invalid parent", e.getMessage());
-		}
+		assertCorrupt("invalid parent", OBJ_COMMIT, b);
 	}
 
 	@Test
 	public void testInvalidCommitInvalidParent4() {
-		final StringBuilder b = new StringBuilder();
-
+		StringBuilder b = new StringBuilder();
 		b.append("tree ");
 		b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189");
 		b.append('\n');
-
 		b.append("parent  ");
 		b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189");
 		b.append("z\n");
-
-		final byte[] data = Constants.encodeASCII(b.toString());
-		try {
-			checker.checkCommit(data);
-			fail("Did not catch corrupt object");
-		} catch (CorruptObjectException e) {
-			assertEquals("invalid parent", e.getMessage());
-		}
+		assertCorrupt("invalid parent", OBJ_COMMIT, b);
 	}
 
 	@Test
 	public void testInvalidCommitInvalidParent5() {
-		final StringBuilder b = new StringBuilder();
-
+		StringBuilder b = new StringBuilder();
 		b.append("tree ");
 		b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189");
 		b.append('\n');
-
 		b.append("parent\t");
 		b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189");
 		b.append("\n");
 
-		final byte[] data = Constants.encodeASCII(b.toString());
-		try {
-			checker.checkCommit(data);
-			fail("Did not catch corrupt object");
-		} catch (CorruptObjectException e) {
-			// Yes, really, we complain about author not being
-			// found as the invalid parent line wasn't consumed.
-			assertEquals("no author", e.getMessage());
-		}
+		byte[] data = encodeASCII(b.toString());
+		// Yes, really, we complain about author not being
+		// found as the invalid parent line wasn't consumed.
+		assertCorrupt("no author", OBJ_COMMIT, data);
 	}
 
 	@Test
-	public void testInvalidCommitNoAuthor() {
-		final StringBuilder b = new StringBuilder();
-
+	public void testInvalidCommitNoAuthor() throws CorruptObjectException {
+		StringBuilder b = new StringBuilder();
 		b.append("tree ");
 		b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189");
 		b.append('\n');
-
 		b.append("committer A. U. Thor <author@localhost> 1 +0000\n");
 
-		final byte[] data = Constants.encodeASCII(b.toString());
-		try {
-			checker.checkCommit(data);
-			fail("Did not catch corrupt object");
-		} catch (CorruptObjectException e) {
-			// Yes, really, we complain about author not being
-			// found as the invalid parent line wasn't consumed.
-			assertEquals("no author", e.getMessage());
-		}
+		byte[] data = encodeASCII(b.toString());
+		assertCorrupt("no author", OBJ_COMMIT, data);
+		assertSkipListAccepts(OBJ_COMMIT, data);
 	}
 
 	@Test
-	public void testInvalidCommitNoCommitter1() {
-		final StringBuilder b = new StringBuilder();
-
+	public void testInvalidCommitNoCommitter1() throws CorruptObjectException {
+		StringBuilder b = new StringBuilder();
 		b.append("tree ");
 		b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189");
 		b.append('\n');
-
 		b.append("author A. U. Thor <author@localhost> 1 +0000\n");
 
-		final byte[] data = Constants.encodeASCII(b.toString());
-		try {
-			checker.checkCommit(data);
-			fail("Did not catch corrupt object");
-		} catch (CorruptObjectException e) {
-			// Yes, really, we complain about author not being
-			// found as the invalid parent line wasn't consumed.
-			assertEquals("no committer", e.getMessage());
-		}
+		byte[] data = encodeASCII(b.toString());
+		assertCorrupt("no committer", OBJ_COMMIT, data);
+		assertSkipListAccepts(OBJ_COMMIT, data);
 	}
 
 	@Test
-	public void testInvalidCommitNoCommitter2() {
-		final StringBuilder b = new StringBuilder();
-
+	public void testInvalidCommitNoCommitter2() throws CorruptObjectException {
+		StringBuilder b = new StringBuilder();
 		b.append("tree ");
 		b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189");
 		b.append('\n');
-
 		b.append("author A. U. Thor <author@localhost> 1 +0000\n");
 		b.append("\n");
 
-		final byte[] data = Constants.encodeASCII(b.toString());
-		try {
-			checker.checkCommit(data);
-			fail("Did not catch corrupt object");
-		} catch (CorruptObjectException e) {
-			// Yes, really, we complain about author not being
-			// found as the invalid parent line wasn't consumed.
-			assertEquals("no committer", e.getMessage());
-		}
+		byte[] data = encodeASCII(b.toString());
+		assertCorrupt("no committer", OBJ_COMMIT, data);
+		assertSkipListAccepts(OBJ_COMMIT, data);
 	}
 
 	@Test
-	public void testInvalidCommitInvalidAuthor1() {
-		final StringBuilder b = new StringBuilder();
-
+	public void testInvalidCommitInvalidAuthor1()
+			throws CorruptObjectException {
+		StringBuilder b = new StringBuilder();
 		b.append("tree ");
 		b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189");
 		b.append('\n');
-
 		b.append("author A. U. Thor <foo 1 +0000\n");
 
-		final byte[] data = Constants.encodeASCII(b.toString());
-		try {
-			checker.checkCommit(data);
-			fail("Did not catch corrupt object");
-		} catch (CorruptObjectException e) {
-			// Yes, really, we complain about author not being
-			// found as the invalid parent line wasn't consumed.
-			assertEquals("invalid author", e.getMessage());
-		}
+		byte[] data = encodeASCII(b.toString());
+		assertCorrupt("bad email", OBJ_COMMIT, data);
+		assertSkipListAccepts(OBJ_COMMIT, data);
 	}
 
 	@Test
-	public void testInvalidCommitInvalidAuthor2() {
-		final StringBuilder b = new StringBuilder();
-
+	public void testInvalidCommitInvalidAuthor2()
+			throws CorruptObjectException {
+		StringBuilder b = new StringBuilder();
 		b.append("tree ");
 		b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189");
 		b.append('\n');
-
 		b.append("author A. U. Thor foo> 1 +0000\n");
 
-		final byte[] data = Constants.encodeASCII(b.toString());
-		try {
-			checker.checkCommit(data);
-			fail("Did not catch corrupt object");
-		} catch (CorruptObjectException e) {
-			// Yes, really, we complain about author not being
-			// found as the invalid parent line wasn't consumed.
-			assertEquals("invalid author", e.getMessage());
-		}
+		byte[] data = encodeASCII(b.toString());
+		assertCorrupt("missing email", OBJ_COMMIT, data);
+		assertSkipListAccepts(OBJ_COMMIT, data);
 	}
 
 	@Test
-	public void testInvalidCommitInvalidAuthor3() {
-		final StringBuilder b = new StringBuilder();
-
+	public void testInvalidCommitInvalidAuthor3()
+			throws CorruptObjectException {
+		StringBuilder b = new StringBuilder();
 		b.append("tree ");
 		b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189");
 		b.append('\n');
-
 		b.append("author 1 +0000\n");
 
-		final byte[] data = Constants.encodeASCII(b.toString());
-		try {
-			checker.checkCommit(data);
-			fail("Did not catch corrupt object");
-		} catch (CorruptObjectException e) {
-			// Yes, really, we complain about author not being
-			// found as the invalid parent line wasn't consumed.
-			assertEquals("invalid author", e.getMessage());
-		}
+		byte[] data = encodeASCII(b.toString());
+		assertCorrupt("missing email", OBJ_COMMIT, data);
+		assertSkipListAccepts(OBJ_COMMIT, data);
 	}
 
 	@Test
-	public void testInvalidCommitInvalidAuthor4() {
-		final StringBuilder b = new StringBuilder();
-
+	public void testInvalidCommitInvalidAuthor4()
+			throws CorruptObjectException {
+		StringBuilder b = new StringBuilder();
 		b.append("tree ");
 		b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189");
 		b.append('\n');
-
 		b.append("author a <b> +0000\n");
 
-		final byte[] data = Constants.encodeASCII(b.toString());
-		try {
-			checker.checkCommit(data);
-			fail("Did not catch corrupt object");
-		} catch (CorruptObjectException e) {
-			// Yes, really, we complain about author not being
-			// found as the invalid parent line wasn't consumed.
-			assertEquals("invalid author", e.getMessage());
-		}
+		byte[] data = encodeASCII(b.toString());
+		assertCorrupt("bad date", OBJ_COMMIT, data);
+		assertSkipListAccepts(OBJ_COMMIT, data);
 	}
 
 	@Test
-	public void testInvalidCommitInvalidAuthor5() {
-		final StringBuilder b = new StringBuilder();
-
+	public void testInvalidCommitInvalidAuthor5()
+			throws CorruptObjectException {
+		StringBuilder b = new StringBuilder();
 		b.append("tree ");
 		b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189");
 		b.append('\n');
-
 		b.append("author a <b>\n");
 
-		final byte[] data = Constants.encodeASCII(b.toString());
-		try {
-			checker.checkCommit(data);
-			fail("Did not catch corrupt object");
-		} catch (CorruptObjectException e) {
-			// Yes, really, we complain about author not being
-			// found as the invalid parent line wasn't consumed.
-			assertEquals("invalid author", e.getMessage());
-		}
+		byte[] data = encodeASCII(b.toString());
+		assertCorrupt("bad date", OBJ_COMMIT, data);
+		assertSkipListAccepts(OBJ_COMMIT, data);
 	}
 
 	@Test
-	public void testInvalidCommitInvalidAuthor6() {
-		final StringBuilder b = new StringBuilder();
-
+	public void testInvalidCommitInvalidAuthor6()
+			throws CorruptObjectException {
+		StringBuilder b = new StringBuilder();
 		b.append("tree ");
 		b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189");
 		b.append('\n');
-
 		b.append("author a <b> z");
 
-		final byte[] data = Constants.encodeASCII(b.toString());
-		try {
-			checker.checkCommit(data);
-			fail("Did not catch corrupt object");
-		} catch (CorruptObjectException e) {
-			// Yes, really, we complain about author not being
-			// found as the invalid parent line wasn't consumed.
-			assertEquals("invalid author", e.getMessage());
-		}
+		byte[] data = encodeASCII(b.toString());
+		assertCorrupt("bad date", OBJ_COMMIT, data);
+		assertSkipListAccepts(OBJ_COMMIT, data);
 	}
 
 	@Test
-	public void testInvalidCommitInvalidAuthor7() {
-		final StringBuilder b = new StringBuilder();
-
+	public void testInvalidCommitInvalidAuthor7()
+			throws CorruptObjectException {
+		StringBuilder b = new StringBuilder();
 		b.append("tree ");
 		b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189");
 		b.append('\n');
-
 		b.append("author a <b> 1 z");
 
-		final byte[] data = Constants.encodeASCII(b.toString());
-		try {
-			checker.checkCommit(data);
-			fail("Did not catch corrupt object");
-		} catch (CorruptObjectException e) {
-			// Yes, really, we complain about author not being
-			// found as the invalid parent line wasn't consumed.
-			assertEquals("invalid author", e.getMessage());
-		}
+		byte[] data = encodeASCII(b.toString());
+		assertCorrupt("bad time zone", OBJ_COMMIT, data);
+		assertSkipListAccepts(OBJ_COMMIT, data);
 	}
 
 	@Test
-	public void testInvalidCommitInvalidCommitter() {
-		final StringBuilder b = new StringBuilder();
-
+	public void testInvalidCommitInvalidCommitter()
+			throws CorruptObjectException {
+		StringBuilder b = new StringBuilder();
 		b.append("tree ");
 		b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189");
 		b.append('\n');
-
 		b.append("author a <b> 1 +0000\n");
 		b.append("committer a <");
 
-		final byte[] data = Constants.encodeASCII(b.toString());
-		try {
-			checker.checkCommit(data);
-			fail("Did not catch corrupt object");
-		} catch (CorruptObjectException e) {
-			// Yes, really, we complain about author not being
-			// found as the invalid parent line wasn't consumed.
-			assertEquals("invalid committer", e.getMessage());
-		}
+		byte[] data = encodeASCII(b.toString());
+		assertCorrupt("bad email", OBJ_COMMIT, data);
+		assertSkipListAccepts(OBJ_COMMIT, data);
 	}
 
 	@Test
 	public void testValidTag() throws CorruptObjectException {
-		final StringBuilder b = new StringBuilder();
-
+		StringBuilder b = new StringBuilder();
 		b.append("object ");
 		b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189");
 		b.append('\n');
-
 		b.append("type commit\n");
 		b.append("tag test-tag\n");
 		b.append("tagger A. U. Thor <author@localhost> 1 +0000\n");
 
-		final byte[] data = Constants.encodeASCII(b.toString());
+		byte[] data = encodeASCII(b.toString());
 		checker.checkTag(data);
-		checker.check(Constants.OBJ_TAG, data);
+		checker.check(OBJ_TAG, data);
 	}
 
 	@Test
 	public void testInvalidTagNoObject1() {
-		final StringBuilder b = new StringBuilder();
-
-		final byte[] data = Constants.encodeASCII(b.toString());
-		try {
-			checker.checkTag(data);
-			fail("incorrectly accepted invalid tag");
-		} catch (CorruptObjectException e) {
-			assertEquals("no object header", e.getMessage());
-		}
+		assertCorrupt("no object header", OBJ_TAG, new byte[0]);
 	}
 
 	@Test
 	public void testInvalidTagNoObject2() {
-		final StringBuilder b = new StringBuilder();
-
+		StringBuilder b = new StringBuilder();
 		b.append("object\t");
 		b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189");
 		b.append('\n');
-
-		final byte[] data = Constants.encodeASCII(b.toString());
-		try {
-			checker.checkTag(data);
-			fail("incorrectly accepted invalid tag");
-		} catch (CorruptObjectException e) {
-			assertEquals("no object header", e.getMessage());
-		}
+		assertCorrupt("no object header", OBJ_TAG, b);
 	}
 
 	@Test
 	public void testInvalidTagNoObject3() {
-		final StringBuilder b = new StringBuilder();
-
+		StringBuilder b = new StringBuilder();
 		b.append("obejct ");
 		b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189");
 		b.append('\n');
-
-		final byte[] data = Constants.encodeASCII(b.toString());
-		try {
-			checker.checkTag(data);
-			fail("incorrectly accepted invalid tag");
-		} catch (CorruptObjectException e) {
-			assertEquals("no object header", e.getMessage());
-		}
+		assertCorrupt("no object header", OBJ_TAG, b);
 	}
 
 	@Test
 	public void testInvalidTagNoObject4() {
-		final StringBuilder b = new StringBuilder();
-
+		StringBuilder b = new StringBuilder();
 		b.append("object ");
 		b.append("zz9bfa841874ccc9f2ef7c48d0c76226f89b7189");
 		b.append('\n');
-
-		final byte[] data = Constants.encodeASCII(b.toString());
-		try {
-			checker.checkTag(data);
-			fail("incorrectly accepted invalid tag");
-		} catch (CorruptObjectException e) {
-			assertEquals("invalid object", e.getMessage());
-		}
+		assertCorrupt("invalid object", OBJ_TAG, b);
 	}
 
 	@Test
 	public void testInvalidTagNoObject5() {
-		final StringBuilder b = new StringBuilder();
-
+		StringBuilder b = new StringBuilder();
 		b.append("object ");
 		b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189");
 		b.append(" \n");
-
-		final byte[] data = Constants.encodeASCII(b.toString());
-		try {
-			checker.checkTag(data);
-			fail("incorrectly accepted invalid tag");
-		} catch (CorruptObjectException e) {
-			assertEquals("invalid object", e.getMessage());
-		}
+		assertCorrupt("invalid object", OBJ_TAG, b);
 	}
 
 	@Test
 	public void testInvalidTagNoObject6() {
-		final StringBuilder b = new StringBuilder();
-
+		StringBuilder b = new StringBuilder();
 		b.append("object ");
 		b.append("be9");
-
-		final byte[] data = Constants.encodeASCII(b.toString());
-		try {
-			checker.checkTag(data);
-			fail("incorrectly accepted invalid tag");
-		} catch (CorruptObjectException e) {
-			assertEquals("invalid object", e.getMessage());
-		}
+		assertCorrupt("invalid object", OBJ_TAG, b);
 	}
 
 	@Test
 	public void testInvalidTagNoType1() {
-		final StringBuilder b = new StringBuilder();
-
+		StringBuilder b = new StringBuilder();
 		b.append("object ");
 		b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189");
 		b.append('\n');
-
-		final byte[] data = Constants.encodeASCII(b.toString());
-		try {
-			checker.checkTag(data);
-			fail("incorrectly accepted invalid tag");
-		} catch (CorruptObjectException e) {
-			assertEquals("no type header", e.getMessage());
-		}
+		assertCorrupt("no type header", OBJ_TAG, b);
 	}
 
 	@Test
 	public void testInvalidTagNoType2() {
-		final StringBuilder b = new StringBuilder();
-
+		StringBuilder b = new StringBuilder();
 		b.append("object ");
 		b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189");
 		b.append('\n');
-
 		b.append("type\tcommit\n");
-
-		final byte[] data = Constants.encodeASCII(b.toString());
-		try {
-			checker.checkTag(data);
-			fail("incorrectly accepted invalid tag");
-		} catch (CorruptObjectException e) {
-			assertEquals("no type header", e.getMessage());
-		}
+		assertCorrupt("no type header", OBJ_TAG, b);
 	}
 
 	@Test
 	public void testInvalidTagNoType3() {
-		final StringBuilder b = new StringBuilder();
-
+		StringBuilder b = new StringBuilder();
 		b.append("object ");
 		b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189");
 		b.append('\n');
-
 		b.append("tpye commit\n");
-
-		final byte[] data = Constants.encodeASCII(b.toString());
-		try {
-			checker.checkTag(data);
-			fail("incorrectly accepted invalid tag");
-		} catch (CorruptObjectException e) {
-			assertEquals("no type header", e.getMessage());
-		}
+		assertCorrupt("no type header", OBJ_TAG, b);
 	}
 
 	@Test
 	public void testInvalidTagNoType4() {
-		final StringBuilder b = new StringBuilder();
-
+		StringBuilder b = new StringBuilder();
 		b.append("object ");
 		b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189");
 		b.append('\n');
-
 		b.append("type commit");
-
-		final byte[] data = Constants.encodeASCII(b.toString());
-		try {
-			checker.checkTag(data);
-			fail("incorrectly accepted invalid tag");
-		} catch (CorruptObjectException e) {
-			assertEquals("no tag header", e.getMessage());
-		}
+		assertCorrupt("no tag header", OBJ_TAG, b);
 	}
 
 	@Test
 	public void testInvalidTagNoTagHeader1() {
-		final StringBuilder b = new StringBuilder();
-
+		StringBuilder b = new StringBuilder();
 		b.append("object ");
 		b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189");
 		b.append('\n');
-
 		b.append("type commit\n");
-
-		final byte[] data = Constants.encodeASCII(b.toString());
-		try {
-			checker.checkTag(data);
-			fail("incorrectly accepted invalid tag");
-		} catch (CorruptObjectException e) {
-			assertEquals("no tag header", e.getMessage());
-		}
+		assertCorrupt("no tag header", OBJ_TAG, b);
 	}
 
 	@Test
 	public void testInvalidTagNoTagHeader2() {
-		final StringBuilder b = new StringBuilder();
-
+		StringBuilder b = new StringBuilder();
 		b.append("object ");
 		b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189");
 		b.append('\n');
-
 		b.append("type commit\n");
 		b.append("tag\tfoo\n");
-
-		final byte[] data = Constants.encodeASCII(b.toString());
-		try {
-			checker.checkTag(data);
-			fail("incorrectly accepted invalid tag");
-		} catch (CorruptObjectException e) {
-			assertEquals("no tag header", e.getMessage());
-		}
+		assertCorrupt("no tag header", OBJ_TAG, b);
 	}
 
 	@Test
 	public void testInvalidTagNoTagHeader3() {
-		final StringBuilder b = new StringBuilder();
-
+		StringBuilder b = new StringBuilder();
 		b.append("object ");
 		b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189");
 		b.append('\n');
-
 		b.append("type commit\n");
 		b.append("tga foo\n");
-
-		final byte[] data = Constants.encodeASCII(b.toString());
-		try {
-			checker.checkTag(data);
-			fail("incorrectly accepted invalid tag");
-		} catch (CorruptObjectException e) {
-			assertEquals("no tag header", e.getMessage());
-		}
+		assertCorrupt("no tag header", OBJ_TAG, b);
 	}
 
 	@Test
 	public void testValidTagHasNoTaggerHeader() throws CorruptObjectException {
-		final StringBuilder b = new StringBuilder();
-
+		StringBuilder b = new StringBuilder();
 		b.append("object ");
 		b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189");
 		b.append('\n');
-
 		b.append("type commit\n");
 		b.append("tag foo\n");
-
-		checker.checkTag(Constants.encodeASCII(b.toString()));
+		checker.checkTag(encodeASCII(b.toString()));
 	}
 
 	@Test
 	public void testInvalidTagInvalidTaggerHeader1()
 			throws CorruptObjectException {
-		final StringBuilder b = new StringBuilder();
-
+		StringBuilder b = new StringBuilder();
 		b.append("object ");
 		b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189");
 		b.append('\n');
-
 		b.append("type commit\n");
 		b.append("tag foo\n");
 		b.append("tagger \n");
 
-		final byte[] data = Constants.encodeASCII(b.toString());
-		try {
-			checker.checkTag(data);
-			fail("incorrectly accepted invalid tag");
-		} catch (CorruptObjectException e) {
-			assertEquals("invalid tagger", e.getMessage());
-		}
+		byte[] data = encodeASCII(b.toString());
+		assertCorrupt("missing email", OBJ_TAG, data);
 		checker.setAllowInvalidPersonIdent(true);
 		checker.checkTag(data);
+
+		checker.setAllowInvalidPersonIdent(false);
+		assertSkipListAccepts(OBJ_TAG, data);
 	}
 
 	@Test
-	public void testInvalidTagInvalidTaggerHeader3() {
-		final StringBuilder b = new StringBuilder();
-
+	public void testInvalidTagInvalidTaggerHeader3()
+			throws CorruptObjectException {
+		StringBuilder b = new StringBuilder();
 		b.append("object ");
 		b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189");
 		b.append('\n');
-
 		b.append("type commit\n");
 		b.append("tag foo\n");
 		b.append("tagger a < 1 +000\n");
 
-		final byte[] data = Constants.encodeASCII(b.toString());
-		try {
-			checker.checkTag(data);
-			fail("incorrectly accepted invalid tag");
-		} catch (CorruptObjectException e) {
-			assertEquals("invalid tagger", e.getMessage());
-		}
+		byte[] data = encodeASCII(b.toString());
+		assertCorrupt("bad email", OBJ_TAG, data);
+		assertSkipListAccepts(OBJ_TAG, data);
 	}
 
 	@Test
 	public void testValidEmptyTree() throws CorruptObjectException {
 		checker.checkTree(new byte[0]);
-		checker.check(Constants.OBJ_TREE, new byte[0]);
+		checker.check(OBJ_TREE, new byte[0]);
 	}
 
 	@Test
 	public void testValidTree1() throws CorruptObjectException {
-		final StringBuilder b = new StringBuilder();
+		StringBuilder b = new StringBuilder();
 		entry(b, "100644 regular-file");
-		final byte[] data = Constants.encodeASCII(b.toString());
-		checker.checkTree(data);
+		checker.checkTree(encodeASCII(b.toString()));
 	}
 
 	@Test
 	public void testValidTree2() throws CorruptObjectException {
-		final StringBuilder b = new StringBuilder();
+		StringBuilder b = new StringBuilder();
 		entry(b, "100755 executable");
-		final byte[] data = Constants.encodeASCII(b.toString());
-		checker.checkTree(data);
+		checker.checkTree(encodeASCII(b.toString()));
 	}
 
 	@Test
 	public void testValidTree3() throws CorruptObjectException {
-		final StringBuilder b = new StringBuilder();
+		StringBuilder b = new StringBuilder();
 		entry(b, "40000 tree");
-		final byte[] data = Constants.encodeASCII(b.toString());
-		checker.checkTree(data);
+		checker.checkTree(encodeASCII(b.toString()));
 	}
 
 	@Test
 	public void testValidTree4() throws CorruptObjectException {
-		final StringBuilder b = new StringBuilder();
+		StringBuilder b = new StringBuilder();
 		entry(b, "120000 symlink");
-		final byte[] data = Constants.encodeASCII(b.toString());
-		checker.checkTree(data);
+		checker.checkTree(encodeASCII(b.toString()));
 	}
 
 	@Test
 	public void testValidTree5() throws CorruptObjectException {
-		final StringBuilder b = new StringBuilder();
+		StringBuilder b = new StringBuilder();
 		entry(b, "160000 git link");
-		final byte[] data = Constants.encodeASCII(b.toString());
-		checker.checkTree(data);
+		checker.checkTree(encodeASCII(b.toString()));
 	}
 
 	@Test
 	public void testValidTree6() throws CorruptObjectException {
-		final StringBuilder b = new StringBuilder();
+		StringBuilder b = new StringBuilder();
 		entry(b, "100644 .a");
-		final byte[] data = Constants.encodeASCII(b.toString());
+		checker.checkTree(encodeASCII(b.toString()));
+	}
+
+	@Test
+	public void testNullSha1InTreeEntry() throws CorruptObjectException {
+		byte[] data = concat(
+				encodeASCII("100644 A"), new byte[] { '\0' },
+				new byte[OBJECT_ID_LENGTH]);
+		assertCorrupt("entry points to null SHA-1", OBJ_TREE, data);
+		assertSkipListAccepts(OBJ_TREE, data);
+		checker.setIgnore(NULL_SHA1, true);
 		checker.checkTree(data);
 	}
 
@@ -1084,357 +788,326 @@
 
 	@Test
 	public void testValidTreeSorting1() throws CorruptObjectException {
-		final StringBuilder b = new StringBuilder();
+		StringBuilder b = new StringBuilder();
 		entry(b, "100644 fooaaa");
 		entry(b, "100755 foobar");
-		final byte[] data = Constants.encodeASCII(b.toString());
-		checker.checkTree(data);
+		checker.checkTree(encodeASCII(b.toString()));
 	}
 
 	@Test
 	public void testValidTreeSorting2() throws CorruptObjectException {
-		final StringBuilder b = new StringBuilder();
+		StringBuilder b = new StringBuilder();
 		entry(b, "100755 fooaaa");
 		entry(b, "100644 foobar");
-		final byte[] data = Constants.encodeASCII(b.toString());
-		checker.checkTree(data);
+		checker.checkTree(encodeASCII(b.toString()));
 	}
 
 	@Test
 	public void testValidTreeSorting3() throws CorruptObjectException {
-		final StringBuilder b = new StringBuilder();
+		StringBuilder b = new StringBuilder();
 		entry(b, "40000 a");
 		entry(b, "100644 b");
-		final byte[] data = Constants.encodeASCII(b.toString());
-		checker.checkTree(data);
+		checker.checkTree(encodeASCII(b.toString()));
 	}
 
 	@Test
 	public void testValidTreeSorting4() throws CorruptObjectException {
-		final StringBuilder b = new StringBuilder();
+		StringBuilder b = new StringBuilder();
 		entry(b, "100644 a");
 		entry(b, "40000 b");
-		final byte[] data = Constants.encodeASCII(b.toString());
-		checker.checkTree(data);
+		checker.checkTree(encodeASCII(b.toString()));
 	}
 
 	@Test
 	public void testValidTreeSorting5() throws CorruptObjectException {
-		final StringBuilder b = new StringBuilder();
+		StringBuilder b = new StringBuilder();
 		entry(b, "100644 a.c");
 		entry(b, "40000 a");
 		entry(b, "100644 a0c");
-		final byte[] data = Constants.encodeASCII(b.toString());
-		checker.checkTree(data);
+		checker.checkTree(encodeASCII(b.toString()));
 	}
 
 	@Test
 	public void testValidTreeSorting6() throws CorruptObjectException {
-		final StringBuilder b = new StringBuilder();
+		StringBuilder b = new StringBuilder();
 		entry(b, "40000 a");
 		entry(b, "100644 apple");
-		final byte[] data = Constants.encodeASCII(b.toString());
-		checker.checkTree(data);
+		checker.checkTree(encodeASCII(b.toString()));
 	}
 
 	@Test
 	public void testValidTreeSorting7() throws CorruptObjectException {
-		final StringBuilder b = new StringBuilder();
+		StringBuilder b = new StringBuilder();
 		entry(b, "40000 an orang");
 		entry(b, "40000 an orange");
-		final byte[] data = Constants.encodeASCII(b.toString());
-		checker.checkTree(data);
+		checker.checkTree(encodeASCII(b.toString()));
 	}
 
 	@Test
 	public void testValidTreeSorting8() throws CorruptObjectException {
-		final StringBuilder b = new StringBuilder();
+		StringBuilder b = new StringBuilder();
 		entry(b, "100644 a");
 		entry(b, "100644 a0c");
 		entry(b, "100644 b");
-		final byte[] data = Constants.encodeASCII(b.toString());
-		checker.checkTree(data);
+		checker.checkTree(encodeASCII(b.toString()));
 	}
 
 	@Test
 	public void testAcceptTreeModeWithZero() throws CorruptObjectException {
 		StringBuilder b = new StringBuilder();
 		entry(b, "040000 a");
+		byte[] data = encodeASCII(b.toString());
 		checker.setAllowLeadingZeroFileMode(true);
-		checker.checkTree(Constants.encodeASCII(b.toString()));
+		checker.checkTree(data);
+
+		checker.setAllowLeadingZeroFileMode(false);
+		assertSkipListAccepts(OBJ_TREE, data);
+
+		checker.setIgnore(ZERO_PADDED_FILEMODE, true);
+		checker.checkTree(data);
 	}
 
 	@Test
 	public void testInvalidTreeModeStartsWithZero1() {
-		final StringBuilder b = new StringBuilder();
+		StringBuilder b = new StringBuilder();
 		entry(b, "0 a");
-		final byte[] data = Constants.encodeASCII(b.toString());
-		try {
-			checker.checkTree(data);
-			fail("incorrectly accepted an invalid tree");
-		} catch (CorruptObjectException e) {
-			assertEquals("mode starts with '0'", e.getMessage());
-		}
+		assertCorrupt("mode starts with '0'", OBJ_TREE, b);
 	}
 
 	@Test
 	public void testInvalidTreeModeStartsWithZero2() {
-		final StringBuilder b = new StringBuilder();
+		StringBuilder b = new StringBuilder();
 		entry(b, "0100644 a");
-		final byte[] data = Constants.encodeASCII(b.toString());
-		try {
-			checker.checkTree(data);
-			fail("incorrectly accepted an invalid tree");
-		} catch (CorruptObjectException e) {
-			assertEquals("mode starts with '0'", e.getMessage());
-		}
+		assertCorrupt("mode starts with '0'", OBJ_TREE, b);
 	}
 
 	@Test
 	public void testInvalidTreeModeStartsWithZero3() {
-		final StringBuilder b = new StringBuilder();
+		StringBuilder b = new StringBuilder();
 		entry(b, "040000 a");
-		final byte[] data = Constants.encodeASCII(b.toString());
-		try {
-			checker.checkTree(data);
-			fail("incorrectly accepted an invalid tree");
-		} catch (CorruptObjectException e) {
-			assertEquals("mode starts with '0'", e.getMessage());
-		}
+		assertCorrupt("mode starts with '0'", OBJ_TREE, b);
 	}
 
 	@Test
 	public void testInvalidTreeModeNotOctal1() {
-		final StringBuilder b = new StringBuilder();
+		StringBuilder b = new StringBuilder();
 		entry(b, "8 a");
-		final byte[] data = Constants.encodeASCII(b.toString());
-		try {
-			checker.checkTree(data);
-			fail("incorrectly accepted an invalid tree");
-		} catch (CorruptObjectException e) {
-			assertEquals("invalid mode character", e.getMessage());
-		}
+		assertCorrupt("invalid mode character", OBJ_TREE, b);
 	}
 
 	@Test
 	public void testInvalidTreeModeNotOctal2() {
-		final StringBuilder b = new StringBuilder();
+		StringBuilder b = new StringBuilder();
 		entry(b, "Z a");
-		final byte[] data = Constants.encodeASCII(b.toString());
-		try {
-			checker.checkTree(data);
-			fail("incorrectly accepted an invalid tree");
-		} catch (CorruptObjectException e) {
-			assertEquals("invalid mode character", e.getMessage());
-		}
+		byte[] data = encodeASCII(b.toString());
+		assertCorrupt("invalid mode character", OBJ_TREE, data);
+		assertSkipListRejects("invalid mode character", OBJ_TREE, data);
 	}
 
 	@Test
 	public void testInvalidTreeModeNotSupportedMode1() {
-		final StringBuilder b = new StringBuilder();
+		StringBuilder b = new StringBuilder();
 		entry(b, "1 a");
-		final byte[] data = Constants.encodeASCII(b.toString());
-		try {
-			checker.checkTree(data);
-			fail("incorrectly accepted an invalid tree");
-		} catch (CorruptObjectException e) {
-			assertEquals("invalid mode 1", e.getMessage());
-		}
+		byte[] data = encodeASCII(b.toString());
+		assertCorrupt("invalid mode 1", OBJ_TREE, data);
+		assertSkipListRejects("invalid mode 1", OBJ_TREE, data);
 	}
 
 	@Test
 	public void testInvalidTreeModeNotSupportedMode2() {
-		final StringBuilder b = new StringBuilder();
+		StringBuilder b = new StringBuilder();
 		entry(b, "170000 a");
-		final byte[] data = Constants.encodeASCII(b.toString());
-		try {
-			checker.checkTree(data);
-			fail("incorrectly accepted an invalid tree");
-		} catch (CorruptObjectException e) {
-			assertEquals("invalid mode " + 0170000, e.getMessage());
-		}
+		assertCorrupt("invalid mode " + 0170000, OBJ_TREE, b);
 	}
 
 	@Test
 	public void testInvalidTreeModeMissingName() {
-		final StringBuilder b = new StringBuilder();
+		StringBuilder b = new StringBuilder();
 		b.append("100644");
-		final byte[] data = Constants.encodeASCII(b.toString());
-		try {
-			checker.checkTree(data);
-			fail("incorrectly accepted an invalid tree");
-		} catch (CorruptObjectException e) {
-			assertEquals("truncated in mode", e.getMessage());
-		}
+		assertCorrupt("truncated in mode", OBJ_TREE, b);
 	}
 
 	@Test
-	public void testInvalidTreeNameContainsSlash() {
-		final StringBuilder b = new StringBuilder();
+	public void testInvalidTreeNameContainsSlash()
+			throws CorruptObjectException {
+		StringBuilder b = new StringBuilder();
 		entry(b, "100644 a/b");
-		final byte[] data = Constants.encodeASCII(b.toString());
-		try {
-			checker.checkTree(data);
-			fail("incorrectly accepted an invalid tree");
-		} catch (CorruptObjectException e) {
-			assertEquals("name contains '/'", e.getMessage());
-		}
+		byte[] data = encodeASCII(b.toString());
+		assertCorrupt("name contains '/'", OBJ_TREE, data);
+		assertSkipListAccepts(OBJ_TREE, data);
+		checker.setIgnore(FULL_PATHNAME, true);
+		checker.checkTree(data);
 	}
 
 	@Test
-	public void testInvalidTreeNameIsEmpty() {
-		final StringBuilder b = new StringBuilder();
+	public void testInvalidTreeNameIsEmpty() throws CorruptObjectException {
+		StringBuilder b = new StringBuilder();
 		entry(b, "100644 ");
-		final byte[] data = Constants.encodeASCII(b.toString());
-		try {
-			checker.checkTree(data);
-			fail("incorrectly accepted an invalid tree");
-		} catch (CorruptObjectException e) {
-			assertEquals("zero length name", e.getMessage());
-		}
+		byte[] data = encodeASCII(b.toString());
+		assertCorrupt("zero length name", OBJ_TREE, data);
+		assertSkipListAccepts(OBJ_TREE, data);
+		checker.setIgnore(EMPTY_NAME, true);
+		checker.checkTree(data);
 	}
 
 	@Test
-	public void testInvalidTreeNameIsDot() {
-		final StringBuilder b = new StringBuilder();
+	public void testInvalidTreeNameIsDot() throws CorruptObjectException {
+		StringBuilder b = new StringBuilder();
 		entry(b, "100644 .");
-		final byte[] data = Constants.encodeASCII(b.toString());
-		try {
-			checker.checkTree(data);
-			fail("incorrectly accepted an invalid tree");
-		} catch (CorruptObjectException e) {
-			assertEquals("invalid name '.'", e.getMessage());
-		}
+		byte[] data = encodeASCII(b.toString());
+		assertCorrupt("invalid name '.'", OBJ_TREE, data);
+		assertSkipListAccepts(OBJ_TREE, data);
+		checker.setIgnore(HAS_DOT, true);
+		checker.checkTree(data);
 	}
 
 	@Test
-	public void testInvalidTreeNameIsDotDot() {
-		final StringBuilder b = new StringBuilder();
+	public void testInvalidTreeNameIsDotDot() throws CorruptObjectException {
+		StringBuilder b = new StringBuilder();
 		entry(b, "100644 ..");
-		final byte[] data = Constants.encodeASCII(b.toString());
-		try {
-			checker.checkTree(data);
-			fail("incorrectly accepted an invalid tree");
-		} catch (CorruptObjectException e) {
-			assertEquals("invalid name '..'", e.getMessage());
-		}
+		byte[] data = encodeASCII(b.toString());
+		assertCorrupt("invalid name '..'", OBJ_TREE, data);
+		assertSkipListAccepts(OBJ_TREE, data);
+		checker.setIgnore(HAS_DOTDOT, true);
+		checker.checkTree(data);
 	}
 
 	@Test
-	public void testInvalidTreeNameIsGit() {
+	public void testInvalidTreeNameIsGit() throws CorruptObjectException {
 		StringBuilder b = new StringBuilder();
 		entry(b, "100644 .git");
-		byte[] data = Constants.encodeASCII(b.toString());
-		try {
-			checker.checkTree(data);
-			fail("incorrectly accepted an invalid tree");
-		} catch (CorruptObjectException e) {
-			assertEquals("invalid name '.git'", e.getMessage());
-		}
+		byte[] data = encodeASCII(b.toString());
+		assertCorrupt("invalid name '.git'", OBJ_TREE, data);
+		assertSkipListAccepts(OBJ_TREE, data);
+		checker.setIgnore(HAS_DOTGIT, true);
+		checker.checkTree(data);
 	}
 
 	@Test
-	public void testInvalidTreeNameIsMixedCaseGit() {
+	public void testInvalidTreeNameIsMixedCaseGit()
+			throws CorruptObjectException {
 		StringBuilder b = new StringBuilder();
 		entry(b, "100644 .GiT");
-		byte[] data = Constants.encodeASCII(b.toString());
-		try {
-			checker.checkTree(data);
-			fail("incorrectly accepted an invalid tree");
-		} catch (CorruptObjectException e) {
-			assertEquals("invalid name '.GiT'", e.getMessage());
-		}
+		byte[] data = encodeASCII(b.toString());
+		assertCorrupt("invalid name '.GiT'", OBJ_TREE, data);
+		assertSkipListAccepts(OBJ_TREE, data);
+		checker.setIgnore(HAS_DOTGIT, true);
+		checker.checkTree(data);
 	}
 
 	@Test
-	public void testInvalidTreeNameIsMacHFSGit() {
+	public void testInvalidTreeNameIsMacHFSGit() throws CorruptObjectException {
 		StringBuilder b = new StringBuilder();
 		entry(b, "100644 .gi\u200Ct");
-		byte[] data = Constants.encode(b.toString());
-		try {
-			checker.setSafeForMacOS(true);
-			checker.checkTree(data);
-			fail("incorrectly accepted an invalid tree");
-		} catch (CorruptObjectException e) {
-			assertEquals(
-					"invalid name '.gi\u200Ct' contains ignorable Unicode characters",
-					e.getMessage());
-		}
+		byte[] data = encode(b.toString());
+
+		// Fine on POSIX.
+		checker.checkTree(data);
+
+		// Rejected on Mac OS.
+		checker.setSafeForMacOS(true);
+		assertCorrupt(
+				"invalid name '.gi\u200Ct' contains ignorable Unicode characters",
+				OBJ_TREE, data);
+		assertSkipListAccepts(OBJ_TREE, data);
+		checker.setIgnore(HAS_DOTGIT, true);
+		checker.checkTree(data);
 	}
 
 	@Test
-	public void testInvalidTreeNameIsMacHFSGit2() {
+	public void testInvalidTreeNameIsMacHFSGit2()
+			throws CorruptObjectException {
 		StringBuilder b = new StringBuilder();
 		entry(b, "100644 \u206B.git");
-		byte[] data = Constants.encode(b.toString());
-		try {
-			checker.setSafeForMacOS(true);
-			checker.checkTree(data);
-			fail("incorrectly accepted an invalid tree");
-		} catch (CorruptObjectException e) {
-			assertEquals(
-					"invalid name '\u206B.git' contains ignorable Unicode characters",
-					e.getMessage());
-		}
+		byte[] data = encode(b.toString());
+
+		// Fine on POSIX.
+		checker.checkTree(data);
+
+		// Rejected on Mac OS.
+		checker.setSafeForMacOS(true);
+		assertCorrupt(
+				"invalid name '\u206B.git' contains ignorable Unicode characters",
+				OBJ_TREE, data);
+		assertSkipListAccepts(OBJ_TREE, data);
+		checker.setIgnore(HAS_DOTGIT, true);
+		checker.checkTree(data);
 	}
 
 	@Test
-	public void testInvalidTreeNameIsMacHFSGit3() {
+	public void testInvalidTreeNameIsMacHFSGit3()
+			throws CorruptObjectException {
 		StringBuilder b = new StringBuilder();
 		entry(b, "100644 .git\uFEFF");
-		byte[] data = Constants.encode(b.toString());
-		try {
-			checker.setSafeForMacOS(true);
-			checker.checkTree(data);
-			fail("incorrectly accepted an invalid tree");
-		} catch (CorruptObjectException e) {
-			assertEquals(
-					"invalid name '.git\uFEFF' contains ignorable Unicode characters",
-					e.getMessage());
-		}
+		byte[] data = encode(b.toString());
+
+		// Fine on POSIX.
+		checker.checkTree(data);
+
+		// Rejected on Mac OS.
+		checker.setSafeForMacOS(true);
+		assertCorrupt(
+				"invalid name '.git\uFEFF' contains ignorable Unicode characters",
+				OBJ_TREE, data);
+		assertSkipListAccepts(OBJ_TREE, data);
+		checker.setIgnore(HAS_DOTGIT, true);
+		checker.checkTree(data);
 	}
 
-	private static byte[] concat(byte[] b1, byte[] b2) {
-		byte[] data = new byte[b1.length + b2.length];
-		System.arraycopy(b1, 0, data, 0, b1.length);
-		System.arraycopy(b2, 0, data, b1.length, b2.length);
+	private static byte[] concat(byte[]... b) {
+		int n = 0;
+		for (byte[] a : b) {
+			n += a.length;
+		}
+
+		byte[] data = new byte[n];
+		n = 0;
+		for (byte[] a : b) {
+			System.arraycopy(a, 0, data, n, a.length);
+			n += a.length;
+		}
 		return data;
 	}
 
 	@Test
-	public void testInvalidTreeNameIsMacHFSGitCorruptUTF8AtEnd() {
-		byte[] data = concat(Constants.encode("100644 .git"),
+	public void testInvalidTreeNameIsMacHFSGitCorruptUTF8AtEnd()
+			throws CorruptObjectException {
+		byte[] data = concat(encode("100644 .git"),
 				new byte[] { (byte) 0xef });
 		StringBuilder b = new StringBuilder();
 		entry(b, "");
-		data = concat(data, Constants.encode(b.toString()));
-		try {
-			checker.setSafeForMacOS(true);
-			checker.checkTree(data);
-			fail("incorrectly accepted an invalid tree");
-		} catch (CorruptObjectException e) {
-			assertEquals(
-					"invalid name contains byte sequence '0xef' which is not a valid UTF-8 character",
-					e.getMessage());
-		}
+		data = concat(data, encode(b.toString()));
+
+		// Fine on POSIX.
+		checker.checkTree(data);
+
+		// Rejected on Mac OS.
+		checker.setSafeForMacOS(true);
+		assertCorrupt(
+				"invalid name contains byte sequence '0xef' which is not a valid UTF-8 character",
+				OBJ_TREE, data);
+		assertSkipListAccepts(OBJ_TREE, data);
 	}
 
 	@Test
-	public void testInvalidTreeNameIsMacHFSGitCorruptUTF8AtEnd2() {
-		byte[] data = concat(Constants.encode("100644 .git"), new byte[] {
+	public void testInvalidTreeNameIsMacHFSGitCorruptUTF8AtEnd2()
+			throws CorruptObjectException {
+		byte[] data = concat(encode("100644 .git"),
+				new byte[] {
 				(byte) 0xe2, (byte) 0xab });
 		StringBuilder b = new StringBuilder();
 		entry(b, "");
-		data = concat(data, Constants.encode(b.toString()));
-		try {
-			checker.setSafeForMacOS(true);
-			checker.checkTree(data);
-			fail("incorrectly accepted an invalid tree");
-		} catch (CorruptObjectException e) {
-			assertEquals(
-					"invalid name contains byte sequence '0xe2ab' which is not a valid UTF-8 character",
-					e.getMessage());
-		}
+		data = concat(data, encode(b.toString()));
+
+		// Fine on POSIX.
+		checker.checkTree(data);
+
+		// Rejected on Mac OS.
+		checker.setSafeForMacOS(true);
+		assertCorrupt(
+				"invalid name contains byte sequence '0xe2ab' which is not a valid UTF-8 character",
+				OBJ_TREE, data);
+		assertSkipListAccepts(OBJ_TREE, data);
 	}
 
 	@Test
@@ -1442,7 +1115,7 @@
 			throws CorruptObjectException {
 		StringBuilder b = new StringBuilder();
 		entry(b, "100644 .git\u200Cx");
-		byte[] data = Constants.encode(b.toString());
+		byte[] data = encode(b.toString());
 		checker.setSafeForMacOS(true);
 		checker.checkTree(data);
 	}
@@ -1452,7 +1125,7 @@
 			throws CorruptObjectException {
 		StringBuilder b = new StringBuilder();
 		entry(b, "100644 .kit\u200C");
-		byte[] data = Constants.encode(b.toString());
+		byte[] data = encode(b.toString());
 		checker.setSafeForMacOS(true);
 		checker.checkTree(data);
 	}
@@ -1462,21 +1135,19 @@
 			throws CorruptObjectException {
 		StringBuilder b = new StringBuilder();
 		entry(b, "100644 .git\u200C");
-		byte[] data = Constants.encode(b.toString());
+		byte[] data = encode(b.toString());
 		checker.checkTree(data);
 	}
 
 	@Test
-	public void testInvalidTreeNameIsDotGitDot() {
+	public void testInvalidTreeNameIsDotGitDot() throws CorruptObjectException {
 		StringBuilder b = new StringBuilder();
 		entry(b, "100644 .git.");
-		byte[] data = Constants.encodeASCII(b.toString());
-		try {
-			checker.checkTree(data);
-			fail("incorrectly accepted an invalid tree");
-		} catch (CorruptObjectException e) {
-			assertEquals("invalid name '.git.'", e.getMessage());
-		}
+		byte[] data = encodeASCII(b.toString());
+		assertCorrupt("invalid name '.git.'", OBJ_TREE, data);
+		assertSkipListAccepts(OBJ_TREE, data);
+		checker.setIgnore(HAS_DOTGIT, true);
+		checker.checkTree(data);
 	}
 
 	@Test
@@ -1484,20 +1155,19 @@
 			throws CorruptObjectException {
 		StringBuilder b = new StringBuilder();
 		entry(b, "100644 .git..");
-		checker.checkTree(Constants.encodeASCII(b.toString()));
+		checker.checkTree(encodeASCII(b.toString()));
 	}
 
 	@Test
-	public void testInvalidTreeNameIsDotGitSpace() {
+	public void testInvalidTreeNameIsDotGitSpace()
+			throws CorruptObjectException {
 		StringBuilder b = new StringBuilder();
 		entry(b, "100644 .git ");
-		byte[] data = Constants.encodeASCII(b.toString());
-		try {
-			checker.checkTree(data);
-			fail("incorrectly accepted an invalid tree");
-		} catch (CorruptObjectException e) {
-			assertEquals("invalid name '.git '", e.getMessage());
-		}
+		byte[] data = encodeASCII(b.toString());
+		assertCorrupt("invalid name '.git '", OBJ_TREE, data);
+		assertSkipListAccepts(OBJ_TREE, data);
+		checker.setIgnore(HAS_DOTGIT, true);
+		checker.checkTree(data);
 	}
 
 	@Test
@@ -1505,7 +1175,7 @@
 			throws CorruptObjectException {
 		StringBuilder b = new StringBuilder();
 		entry(b, "100644 .gitfoobar");
-		byte[] data = Constants.encodeASCII(b.toString());
+		byte[] data = encodeASCII(b.toString());
 		checker.checkTree(data);
 	}
 
@@ -1514,7 +1184,7 @@
 			throws CorruptObjectException {
 		StringBuilder b = new StringBuilder();
 		entry(b, "100644 .gitfoo bar");
-		byte[] data = Constants.encodeASCII(b.toString());
+		byte[] data = encodeASCII(b.toString());
 		checker.checkTree(data);
 	}
 
@@ -1523,7 +1193,7 @@
 			throws CorruptObjectException {
 		StringBuilder b = new StringBuilder();
 		entry(b, "100644 .gitfoobar.");
-		byte[] data = Constants.encodeASCII(b.toString());
+		byte[] data = encodeASCII(b.toString());
 		checker.checkTree(data);
 	}
 
@@ -1532,251 +1202,236 @@
 			throws CorruptObjectException {
 		StringBuilder b = new StringBuilder();
 		entry(b, "100644 .gitfoobar..");
-		byte[] data = Constants.encodeASCII(b.toString());
+		byte[] data = encodeASCII(b.toString());
 		checker.checkTree(data);
 	}
 
 	@Test
-	public void testInvalidTreeNameIsDotGitDotSpace() {
+	public void testInvalidTreeNameIsDotGitDotSpace()
+			throws CorruptObjectException {
 		StringBuilder b = new StringBuilder();
 		entry(b, "100644 .git. ");
-		byte[] data = Constants.encodeASCII(b.toString());
-		try {
-			checker.checkTree(data);
-			fail("incorrectly accepted an invalid tree");
-		} catch (CorruptObjectException e) {
-			assertEquals("invalid name '.git. '", e.getMessage());
-		}
+		byte[] data = encodeASCII(b.toString());
+		assertCorrupt("invalid name '.git. '", OBJ_TREE, data);
+		assertSkipListAccepts(OBJ_TREE, data);
+		checker.setIgnore(HAS_DOTGIT, true);
+		checker.checkTree(data);
 	}
 
 	@Test
-	public void testInvalidTreeNameIsDotGitSpaceDot() {
+	public void testInvalidTreeNameIsDotGitSpaceDot()
+			throws CorruptObjectException {
 		StringBuilder b = new StringBuilder();
 		entry(b, "100644 .git . ");
-		byte[] data = Constants.encodeASCII(b.toString());
-		try {
-			checker.checkTree(data);
-			fail("incorrectly accepted an invalid tree");
-		} catch (CorruptObjectException e) {
-			assertEquals("invalid name '.git . '", e.getMessage());
-		}
+		byte[] data = encodeASCII(b.toString());
+		assertCorrupt("invalid name '.git . '", OBJ_TREE, data);
+		assertSkipListAccepts(OBJ_TREE, data);
+		checker.setIgnore(HAS_DOTGIT, true);
+		checker.checkTree(data);
 	}
 
 	@Test
-	public void testInvalidTreeNameIsGITTilde1() {
+	public void testInvalidTreeNameIsGITTilde1() throws CorruptObjectException {
 		StringBuilder b = new StringBuilder();
 		entry(b, "100644 GIT~1");
-		byte[] data = Constants.encodeASCII(b.toString());
-		try {
-			checker.checkTree(data);
-			fail("incorrectly accepted an invalid tree");
-		} catch (CorruptObjectException e) {
-			assertEquals("invalid name 'GIT~1'", e.getMessage());
-		}
+		byte[] data = encodeASCII(b.toString());
+		assertCorrupt("invalid name 'GIT~1'", OBJ_TREE, data);
+		assertSkipListAccepts(OBJ_TREE, data);
+		checker.setIgnore(HAS_DOTGIT, true);
+		checker.checkTree(data);
 	}
 
 	@Test
-	public void testInvalidTreeNameIsGiTTilde1() {
+	public void testInvalidTreeNameIsGiTTilde1() throws CorruptObjectException {
 		StringBuilder b = new StringBuilder();
 		entry(b, "100644 GiT~1");
-		byte[] data = Constants.encodeASCII(b.toString());
-		try {
-			checker.checkTree(data);
-			fail("incorrectly accepted an invalid tree");
-		} catch (CorruptObjectException e) {
-			assertEquals("invalid name 'GiT~1'", e.getMessage());
-		}
+		byte[] data = encodeASCII(b.toString());
+		assertCorrupt("invalid name 'GiT~1'", OBJ_TREE, data);
+		assertSkipListAccepts(OBJ_TREE, data);
+		checker.setIgnore(HAS_DOTGIT, true);
+		checker.checkTree(data);
 	}
 
 	@Test
 	public void testValidTreeNameIsGitTilde11() throws CorruptObjectException {
 		StringBuilder b = new StringBuilder();
 		entry(b, "100644 GIT~11");
-		byte[] data = Constants.encodeASCII(b.toString());
+		byte[] data = encodeASCII(b.toString());
 		checker.checkTree(data);
 	}
 
 	@Test
 	public void testInvalidTreeTruncatedInName() {
-		final StringBuilder b = new StringBuilder();
+		StringBuilder b = new StringBuilder();
 		b.append("100644 b");
-		final byte[] data = Constants.encodeASCII(b.toString());
-		try {
-			checker.checkTree(data);
-			fail("incorrectly accepted an invalid tree");
-		} catch (CorruptObjectException e) {
-			assertEquals("truncated in name", e.getMessage());
-		}
+		byte[] data = encodeASCII(b.toString());
+		assertCorrupt("truncated in name", OBJ_TREE, data);
+		assertSkipListRejects("truncated in name", OBJ_TREE, data);
 	}
 
 	@Test
 	public void testInvalidTreeTruncatedInObjectId() {
-		final StringBuilder b = new StringBuilder();
+		StringBuilder b = new StringBuilder();
 		b.append("100644 b\0\1\2");
-		final byte[] data = Constants.encodeASCII(b.toString());
-		try {
-			checker.checkTree(data);
-			fail("incorrectly accepted an invalid tree");
-		} catch (CorruptObjectException e) {
-			assertEquals("truncated in object id", e.getMessage());
-		}
+		byte[] data = encodeASCII(b.toString());
+		assertCorrupt("truncated in object id", OBJ_TREE, data);
+		assertSkipListRejects("truncated in object id", OBJ_TREE, data);
 	}
 
 	@Test
-	public void testInvalidTreeBadSorting1() {
-		final StringBuilder b = new StringBuilder();
+	public void testInvalidTreeBadSorting1() throws CorruptObjectException {
+		StringBuilder b = new StringBuilder();
 		entry(b, "100644 foobar");
 		entry(b, "100644 fooaaa");
-		final byte[] data = Constants.encodeASCII(b.toString());
+		byte[] data = encodeASCII(b.toString());
+
+		assertCorrupt("incorrectly sorted", OBJ_TREE, data);
+
+		ObjectId id = idFor(OBJ_TREE, data);
 		try {
-			checker.checkTree(data);
-			fail("incorrectly accepted an invalid tree");
+			checker.check(id, OBJ_TREE, data);
+			fail("Did not throw CorruptObjectException");
 		} catch (CorruptObjectException e) {
-			assertEquals("incorrectly sorted", e.getMessage());
+			assertSame(TREE_NOT_SORTED, e.getErrorType());
+			assertEquals("treeNotSorted: object " + id.name()
+					+ ": incorrectly sorted", e.getMessage());
 		}
+
+		assertSkipListAccepts(OBJ_TREE, data);
+		checker.setIgnore(TREE_NOT_SORTED, true);
+		checker.checkTree(data);
 	}
 
 	@Test
-	public void testInvalidTreeBadSorting2() {
-		final StringBuilder b = new StringBuilder();
+	public void testInvalidTreeBadSorting2() throws CorruptObjectException {
+		StringBuilder b = new StringBuilder();
 		entry(b, "40000 a");
 		entry(b, "100644 a.c");
-		final byte[] data = Constants.encodeASCII(b.toString());
-		try {
-			checker.checkTree(data);
-			fail("incorrectly accepted an invalid tree");
-		} catch (CorruptObjectException e) {
-			assertEquals("incorrectly sorted", e.getMessage());
-		}
+		byte[] data = encodeASCII(b.toString());
+		assertCorrupt("incorrectly sorted", OBJ_TREE, data);
+		assertSkipListAccepts(OBJ_TREE, data);
+		checker.setIgnore(TREE_NOT_SORTED, true);
+		checker.checkTree(data);
 	}
 
 	@Test
-	public void testInvalidTreeBadSorting3() {
-		final StringBuilder b = new StringBuilder();
+	public void testInvalidTreeBadSorting3() throws CorruptObjectException {
+		StringBuilder b = new StringBuilder();
 		entry(b, "100644 a0c");
 		entry(b, "40000 a");
-		final byte[] data = Constants.encodeASCII(b.toString());
-		try {
-			checker.checkTree(data);
-			fail("incorrectly accepted an invalid tree");
-		} catch (CorruptObjectException e) {
-			assertEquals("incorrectly sorted", e.getMessage());
-		}
+		byte[] data = encodeASCII(b.toString());
+		assertCorrupt("incorrectly sorted", OBJ_TREE, data);
+		assertSkipListAccepts(OBJ_TREE, data);
+		checker.setIgnore(TREE_NOT_SORTED, true);
+		checker.checkTree(data);
 	}
 
 	@Test
-	public void testInvalidTreeDuplicateNames1() {
-		final StringBuilder b = new StringBuilder();
+	public void testInvalidTreeDuplicateNames1_File()
+			throws CorruptObjectException {
+		StringBuilder b = new StringBuilder();
 		entry(b, "100644 a");
 		entry(b, "100644 a");
-		final byte[] data = Constants.encodeASCII(b.toString());
-		try {
-			checker.checkTree(data);
-			fail("incorrectly accepted an invalid tree");
-		} catch (CorruptObjectException e) {
-			assertEquals("duplicate entry names", e.getMessage());
-		}
+		byte[] data = encodeASCII(b.toString());
+		assertCorrupt("duplicate entry names", OBJ_TREE, data);
+		assertSkipListAccepts(OBJ_TREE, data);
+		checker.setIgnore(DUPLICATE_ENTRIES, true);
+		checker.checkTree(data);
 	}
 
 	@Test
-	public void testInvalidTreeDuplicateNames2() {
-		final StringBuilder b = new StringBuilder();
+	public void testInvalidTreeDuplicateNames1_Tree()
+			throws CorruptObjectException {
+		StringBuilder b = new StringBuilder();
+		entry(b, "40000 a");
+		entry(b, "40000 a");
+		byte[] data = encodeASCII(b.toString());
+		assertCorrupt("duplicate entry names", OBJ_TREE, data);
+		assertSkipListAccepts(OBJ_TREE, data);
+		checker.setIgnore(DUPLICATE_ENTRIES, true);
+		checker.checkTree(data);
+	}
+
+	@Test
+	public void testInvalidTreeDuplicateNames2() throws CorruptObjectException {
+		StringBuilder b = new StringBuilder();
 		entry(b, "100644 a");
 		entry(b, "100755 a");
-		final byte[] data = Constants.encodeASCII(b.toString());
-		try {
-			checker.checkTree(data);
-			fail("incorrectly accepted an invalid tree");
-		} catch (CorruptObjectException e) {
-			assertEquals("duplicate entry names", e.getMessage());
-		}
+		byte[] data = encodeASCII(b.toString());
+		assertCorrupt("duplicate entry names", OBJ_TREE, data);
+		assertSkipListAccepts(OBJ_TREE, data);
+		checker.setIgnore(DUPLICATE_ENTRIES, true);
+		checker.checkTree(data);
 	}
 
 	@Test
-	public void testInvalidTreeDuplicateNames3() {
-		final StringBuilder b = new StringBuilder();
+	public void testInvalidTreeDuplicateNames3() throws CorruptObjectException {
+		StringBuilder b = new StringBuilder();
 		entry(b, "100644 a");
 		entry(b, "40000 a");
-		final byte[] data = Constants.encodeASCII(b.toString());
-		try {
-			checker.checkTree(data);
-			fail("incorrectly accepted an invalid tree");
-		} catch (CorruptObjectException e) {
-			assertEquals("duplicate entry names", e.getMessage());
-		}
+		byte[] data = encodeASCII(b.toString());
+		assertCorrupt("duplicate entry names", OBJ_TREE, data);
+		assertSkipListAccepts(OBJ_TREE, data);
+		checker.setIgnore(DUPLICATE_ENTRIES, true);
+		checker.checkTree(data);
 	}
 
 	@Test
-	public void testInvalidTreeDuplicateNames4() {
-		final StringBuilder b = new StringBuilder();
+	public void testInvalidTreeDuplicateNames4() throws CorruptObjectException {
+		StringBuilder b = new StringBuilder();
 		entry(b, "100644 a");
 		entry(b, "100644 a.c");
 		entry(b, "100644 a.d");
 		entry(b, "100644 a.e");
 		entry(b, "40000 a");
 		entry(b, "100644 zoo");
-		final byte[] data = Constants.encodeASCII(b.toString());
-		try {
-			checker.checkTree(data);
-			fail("incorrectly accepted an invalid tree");
-		} catch (CorruptObjectException e) {
-			assertEquals("duplicate entry names", e.getMessage());
-		}
+		byte[] data = encodeASCII(b.toString());
+		assertCorrupt("duplicate entry names", OBJ_TREE, data);
+		assertSkipListAccepts(OBJ_TREE, data);
+		checker.setIgnore(DUPLICATE_ENTRIES, true);
+		checker.checkTree(data);
 	}
 
 	@Test
 	public void testInvalidTreeDuplicateNames5()
-			throws UnsupportedEncodingException {
+			throws UnsupportedEncodingException, CorruptObjectException {
 		StringBuilder b = new StringBuilder();
-		entry(b, "100644 a");
 		entry(b, "100644 A");
+		entry(b, "100644 a");
 		byte[] data = b.toString().getBytes("UTF-8");
-		try {
-			checker.setSafeForWindows(true);
-			checker.checkTree(data);
-			fail("incorrectly accepted an invalid tree");
-		} catch (CorruptObjectException e) {
-			assertEquals("duplicate entry names", e.getMessage());
-		}
+		checker.setSafeForWindows(true);
+		assertCorrupt("duplicate entry names", OBJ_TREE, data);
+		assertSkipListAccepts(OBJ_TREE, data);
+		checker.setIgnore(DUPLICATE_ENTRIES, true);
+		checker.checkTree(data);
 	}
 
 	@Test
 	public void testInvalidTreeDuplicateNames6()
-			throws UnsupportedEncodingException {
+			throws UnsupportedEncodingException, CorruptObjectException {
 		StringBuilder b = new StringBuilder();
-		entry(b, "100644 a");
 		entry(b, "100644 A");
+		entry(b, "100644 a");
 		byte[] data = b.toString().getBytes("UTF-8");
-		try {
-			checker.setSafeForMacOS(true);
-			checker.checkTree(data);
-			fail("incorrectly accepted an invalid tree");
-		} catch (CorruptObjectException e) {
-			assertEquals("duplicate entry names", e.getMessage());
-		}
+		checker.setSafeForMacOS(true);
+		assertCorrupt("duplicate entry names", OBJ_TREE, data);
+		assertSkipListAccepts(OBJ_TREE, data);
+		checker.setIgnore(DUPLICATE_ENTRIES, true);
+		checker.checkTree(data);
 	}
 
 	@Test
 	public void testInvalidTreeDuplicateNames7()
-			throws UnsupportedEncodingException {
-		try {
-			Class.forName("java.text.Normalizer");
-		} catch (ClassNotFoundException e) {
-			// Ignore this test on Java 5 platform.
-			return;
-		}
-
+			throws UnsupportedEncodingException, CorruptObjectException {
 		StringBuilder b = new StringBuilder();
 		entry(b, "100644 \u0065\u0301");
 		entry(b, "100644 \u00e9");
 		byte[] data = b.toString().getBytes("UTF-8");
-		try {
-			checker.setSafeForMacOS(true);
-			checker.checkTree(data);
-			fail("incorrectly accepted an invalid tree");
-		} catch (CorruptObjectException e) {
-			assertEquals("duplicate entry names", e.getMessage());
-		}
+		checker.setSafeForMacOS(true);
+		assertCorrupt("duplicate entry names", OBJ_TREE, data);
+		assertSkipListAccepts(OBJ_TREE, data);
+		checker.setIgnore(DUPLICATE_ENTRIES, true);
+		checker.checkTree(data);
 	}
 
 	@Test
@@ -1791,7 +1446,7 @@
 	@Test
 	public void testRejectNulInPathSegment() {
 		try {
-			checker.checkPathSegment(Constants.encodeASCII("a\u0000b"), 0, 3);
+			checker.checkPathSegment(encodeASCII("a\u0000b"), 0, 3);
 			fail("incorrectly accepted NUL in middle of name");
 		} catch (CorruptObjectException e) {
 			assertEquals("name contains byte 0x00", e.getMessage());
@@ -1893,13 +1548,65 @@
 	private void checkOneName(String name) throws CorruptObjectException {
 		StringBuilder b = new StringBuilder();
 		entry(b, "100644 " + name);
-		checker.checkTree(Constants.encodeASCII(b.toString()));
+		checker.checkTree(encodeASCII(b.toString()));
 	}
 
-	private static void entry(final StringBuilder b, final String modeName) {
+	private static void entry(StringBuilder b, final String modeName) {
 		b.append(modeName);
 		b.append('\0');
-		for (int i = 0; i < Constants.OBJECT_ID_LENGTH; i++)
+		for (int i = 0; i < OBJECT_ID_LENGTH; i++)
 			b.append((char) i);
 	}
+
+	private void assertCorrupt(String msg, int type, StringBuilder b) {
+		assertCorrupt(msg, type, encodeASCII(b.toString()));
+	}
+
+	private void assertCorrupt(String msg, int type, byte[] data) {
+		try {
+			checker.check(type, data);
+			fail("Did not throw CorruptObjectException");
+		} catch (CorruptObjectException e) {
+			assertEquals(msg, e.getMessage());
+		}
+	}
+
+	private void assertSkipListAccepts(int type, byte[] data)
+			throws CorruptObjectException {
+		ObjectId id = idFor(type, data);
+		checker.setSkipList(set(id));
+		checker.check(id, type, data);
+		checker.setSkipList(null);
+	}
+
+	private void assertSkipListRejects(String msg, int type, byte[] data) {
+		ObjectId id = idFor(type, data);
+		checker.setSkipList(set(id));
+		try {
+			checker.check(id, type, data);
+			fail("Did not throw CorruptObjectException");
+		} catch (CorruptObjectException e) {
+			assertEquals(msg, e.getMessage());
+		}
+		checker.setSkipList(null);
+	}
+
+	private static ObjectIdSet set(final ObjectId... ids) {
+		return new ObjectIdSet() {
+			@Override
+			public boolean contains(AnyObjectId objectId) {
+				for (ObjectId id : ids) {
+					if (id.equals(objectId)) {
+						return true;
+					}
+				}
+				return false;
+			}
+		};
+	}
+
+	@SuppressWarnings("resource")
+	private static ObjectId idFor(int type, byte[] raw) {
+		return new ObjectInserter.Formatter().idFor(type, raw);
+	}
 }
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RacyGitTests.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RacyGitTests.java
index 99d6801..aa46d1a 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RacyGitTests.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RacyGitTests.java
@@ -82,38 +82,42 @@
 		}
 		FileTreeIteratorWithTimeControl fileIt = new FileTreeIteratorWithTimeControl(
 				db, modTimes);
-		NameConflictTreeWalk tw = new NameConflictTreeWalk(db);
-		tw.addTree(fileIt);
-		tw.setRecursive(true);
-		FileTreeIterator t;
-		long t0 = 0;
-		for (int i = 0; i < 10; i++) {
-			assertTrue(tw.next());
-			t = tw.getTree(0, FileTreeIterator.class);
-			if (i == 0)
-				t0 = t.getEntryLastModified();
-			else
-				assertEquals(t0, t.getEntryLastModified());
-		}
-		long t1 = 0;
-		for (int i = 0; i < 10; i++) {
-			assertTrue(tw.next());
-			t = tw.getTree(0, FileTreeIterator.class);
-			if (i == 0) {
-				t1 = t.getEntryLastModified();
-				assertTrue(t1 > t0);
-			} else
-				assertEquals(t1, t.getEntryLastModified());
-		}
-		long t2 = 0;
-		for (int i = 0; i < 10; i++) {
-			assertTrue(tw.next());
-			t = tw.getTree(0, FileTreeIterator.class);
-			if (i == 0) {
-				t2 = t.getEntryLastModified();
-				assertTrue(t2 > t1);
-			} else
-				assertEquals(t2, t.getEntryLastModified());
+		try (NameConflictTreeWalk tw = new NameConflictTreeWalk(db)) {
+			tw.addTree(fileIt);
+			tw.setRecursive(true);
+			FileTreeIterator t;
+			long t0 = 0;
+			for (int i = 0; i < 10; i++) {
+				assertTrue(tw.next());
+				t = tw.getTree(0, FileTreeIterator.class);
+				if (i == 0) {
+					t0 = t.getEntryLastModified();
+				} else {
+					assertEquals(t0, t.getEntryLastModified());
+				}
+			}
+			long t1 = 0;
+			for (int i = 0; i < 10; i++) {
+				assertTrue(tw.next());
+				t = tw.getTree(0, FileTreeIterator.class);
+				if (i == 0) {
+					t1 = t.getEntryLastModified();
+					assertTrue(t1 > t0);
+				} else {
+					assertEquals(t1, t.getEntryLastModified());
+				}
+			}
+			long t2 = 0;
+			for (int i = 0; i < 10; i++) {
+				assertTrue(tw.next());
+				t = tw.getTree(0, FileTreeIterator.class);
+				if (i == 0) {
+					t2 = t.getEntryLastModified();
+					assertTrue(t2 > t1);
+				} else {
+					assertEquals(t2, t.getEntryLastModified());
+				}
+			}
 		}
 	}
 
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RefTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RefTest.java
index 109f401..707757b 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RefTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RefTest.java
@@ -163,7 +163,7 @@
 	@Test
 	public void testReadSymRefToPacked() throws IOException {
 		writeSymref("HEAD", "refs/heads/b");
-		Ref ref = db.getRef("HEAD");
+		Ref ref = db.exactRef("HEAD");
 		assertEquals(Ref.Storage.LOOSE, ref.getStorage());
 		assertTrue("is symref", ref.isSymbolic());
 		ref = ref.getTarget();
@@ -181,7 +181,7 @@
 		assertEquals(Result.FORCED, update); // internal
 
 		writeSymref("HEAD", "refs/heads/master");
-		Ref ref = db.getRef("HEAD");
+		Ref ref = db.exactRef("HEAD");
 		assertEquals(Ref.Storage.LOOSE, ref.getStorage());
 		ref = ref.getTarget();
 		assertEquals("refs/heads/master", ref.getName());
@@ -194,13 +194,13 @@
 		updateRef.setNewObjectId(db.resolve("refs/heads/master"));
 		Result update = updateRef.update();
 		assertEquals(Result.NEW, update);
-		Ref ref = db.getRef("ref/heads/new");
+		Ref ref = db.exactRef("ref/heads/new");
 		assertEquals(Storage.LOOSE, ref.getStorage());
 	}
 
 	@Test
 	public void testGetShortRef() throws IOException {
-		Ref ref = db.getRef("master");
+		Ref ref = db.exactRef("refs/heads/master");
 		assertEquals("refs/heads/master", ref.getName());
 		assertEquals(db.resolve("refs/heads/master"), ref.getObjectId());
 	}
@@ -222,7 +222,7 @@
 
 		assertNull(db.getRefDatabase().exactRef("refs/foo/bar"));
 
-		Ref ref = db.getRef("refs/foo/bar");
+		Ref ref = db.findRef("refs/foo/bar");
 		assertEquals("refs/heads/refs/foo/bar", ref.getName());
 		assertEquals(db.resolve("refs/heads/master"), ref.getObjectId());
 	}
@@ -237,7 +237,7 @@
 		assertEquals("refs/foo/bar", exactRef.getName());
 		assertEquals(masterId, exactRef.getObjectId());
 
-		Ref ref = db.getRef("refs/foo/bar");
+		Ref ref = db.findRef("refs/foo/bar");
 		assertEquals("refs/foo/bar", ref.getName());
 		assertEquals(masterId, ref.getObjectId());
 	}
@@ -251,7 +251,7 @@
 	@Test
 	public void testReadLoosePackedRef() throws IOException,
 			InterruptedException {
-		Ref ref = db.getRef("refs/heads/master");
+		Ref ref = db.exactRef("refs/heads/master");
 		assertEquals(Storage.PACKED, ref.getStorage());
 		FileOutputStream os = new FileOutputStream(new File(db.getDirectory(),
 				"refs/heads/master"));
@@ -259,7 +259,7 @@
 		os.write('\n');
 		os.close();
 
-		ref = db.getRef("refs/heads/master");
+		ref = db.exactRef("refs/heads/master");
 		assertEquals(Storage.LOOSE, ref.getStorage());
 	}
 
@@ -271,7 +271,7 @@
 	 */
 	@Test
 	public void testReadSimplePackedRefSameRepo() throws IOException {
-		Ref ref = db.getRef("refs/heads/master");
+		Ref ref = db.exactRef("refs/heads/master");
 		ObjectId pid = db.resolve("refs/heads/master^");
 		assertEquals(Storage.PACKED, ref.getStorage());
 		RefUpdate updateRef = db.updateRef("refs/heads/master");
@@ -280,19 +280,19 @@
 		Result update = updateRef.update();
 		assertEquals(Result.FORCED, update);
 
-		ref = db.getRef("refs/heads/master");
+		ref = db.exactRef("refs/heads/master");
 		assertEquals(Storage.LOOSE, ref.getStorage());
 	}
 
 	@Test
 	public void testResolvedNamesBranch() throws IOException {
-		Ref ref = db.getRef("a");
+		Ref ref = db.findRef("a");
 		assertEquals("refs/heads/a", ref.getName());
 	}
 
 	@Test
 	public void testResolvedSymRef() throws IOException {
-		Ref ref = db.getRef(Constants.HEAD);
+		Ref ref = db.exactRef(Constants.HEAD);
 		assertEquals(Constants.HEAD, ref.getName());
 		assertTrue("is symbolic ref", ref.isSymbolic());
 		assertSame(Ref.Storage.LOOSE, ref.getStorage());
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ReflogConfigTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ReflogConfigTest.java
index 7b6d7d4..df1a52d 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ReflogConfigTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ReflogConfigTest.java
@@ -70,8 +70,7 @@
 
 		// do one commit and check that reflog size is 0: no reflogs should be
 		// written
-		commit("A Commit\n", new PersonIdent(author, commitTime, tz),
-				new PersonIdent(committer, commitTime, tz));
+		commit("A Commit\n", commitTime, tz);
 		commitTime += 60 * 1000;
 		assertTrue(
 				"Reflog for HEAD still contain no entry",
@@ -83,8 +82,7 @@
 		assertTrue(cfg.get(CoreConfig.KEY).isLogAllRefUpdates());
 
 		// do one commit and check that reflog size is increased to 1
-		commit("A Commit\n", new PersonIdent(author, commitTime, tz),
-				new PersonIdent(committer, commitTime, tz));
+		commit("A Commit\n", commitTime, tz);
 		commitTime += 60 * 1000;
 		assertTrue(
 				"Reflog for HEAD should contain one entry",
@@ -96,18 +94,17 @@
 		assertFalse(cfg.get(CoreConfig.KEY).isLogAllRefUpdates());
 
 		// do one commit and check that reflog size is 2
-		commit("A Commit\n", new PersonIdent(author, commitTime, tz),
-				new PersonIdent(committer, commitTime, tz));
+		commit("A Commit\n", commitTime, tz);
 		assertTrue(
 				"Reflog for HEAD should contain two entries",
 				db.getReflogReader(Constants.HEAD).getReverseEntries().size() == 2);
 	}
 
-	private void commit(String commitMsg, PersonIdent author,
-			PersonIdent committer) throws IOException {
+	private void commit(String commitMsg, long commitTime, int tz)
+			throws IOException {
 		final CommitBuilder commit = new CommitBuilder();
-		commit.setAuthor(author);
-		commit.setCommitter(committer);
+		commit.setAuthor(new PersonIdent(author, commitTime, tz));
+		commit.setCommitter(new PersonIdent(committer, commitTime, tz));
 		commit.setMessage(commitMsg);
 		ObjectId id;
 		try (ObjectInserter inserter = db.newObjectInserter()) {
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ReflogResolveTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ReflogResolveTest.java
index 75d74fc..7db9f60 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ReflogResolveTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ReflogResolveTest.java
@@ -60,117 +60,123 @@
 
 	@Test
 	public void resolveMasterCommits() throws Exception {
-		Git git = new Git(db);
-		writeTrashFile("file.txt", "content");
-		git.add().addFilepattern("file.txt").call();
-		RevCommit c1 = git.commit().setMessage("create file").call();
-		writeTrashFile("file.txt", "content2");
-		git.add().addFilepattern("file.txt").call();
-		RevCommit c2 = git.commit().setMessage("edit file").call();
+		try (Git git = new Git(db)) {
+			writeTrashFile("file.txt", "content");
+			git.add().addFilepattern("file.txt").call();
+			RevCommit c1 = git.commit().setMessage("create file").call();
+			writeTrashFile("file.txt", "content2");
+			git.add().addFilepattern("file.txt").call();
+			RevCommit c2 = git.commit().setMessage("edit file").call();
 
-		assertEquals(c2, db.resolve("master@{0}"));
-		assertEquals(c1, db.resolve("master@{1}"));
+			assertEquals(c2, db.resolve("master@{0}"));
+			assertEquals(c1, db.resolve("master@{1}"));
+		}
 	}
 
 	@Test
 	public void resolveUnnamedCurrentBranchCommits() throws Exception {
-		Git git = new Git(db);
-		writeTrashFile("file.txt", "content");
-		git.add().addFilepattern("file.txt").call();
-		RevCommit c1 = git.commit().setMessage("create file").call();
-		writeTrashFile("file.txt", "content2");
-		git.add().addFilepattern("file.txt").call();
-		RevCommit c2 = git.commit().setMessage("edit file").call();
+		try (Git git = new Git(db)) {
+			writeTrashFile("file.txt", "content");
+			git.add().addFilepattern("file.txt").call();
+			RevCommit c1 = git.commit().setMessage("create file").call();
+			writeTrashFile("file.txt", "content2");
+			git.add().addFilepattern("file.txt").call();
+			RevCommit c2 = git.commit().setMessage("edit file").call();
 
-		assertEquals(c2, db.resolve("master@{0}"));
-		assertEquals(c1, db.resolve("master@{1}"));
+			assertEquals(c2, db.resolve("master@{0}"));
+			assertEquals(c1, db.resolve("master@{1}"));
 
-		git.checkout().setCreateBranch(true).setName("newbranch")
-				.setStartPoint(c1).call();
+			git.checkout().setCreateBranch(true).setName("newbranch")
+					.setStartPoint(c1).call();
 
-		// same as current branch, e.g. master
-		assertEquals(c1, db.resolve("@{0}"));
-		try {
+			// same as current branch, e.g. master
+			assertEquals(c1, db.resolve("@{0}"));
+			try {
+				assertEquals(c1, db.resolve("@{1}"));
+				fail(); // Looking at wrong ref, e.g HEAD
+			} catch (RevisionSyntaxException e) {
+				assertNotNull(e);
+			}
+
+			// detached head, read HEAD reflog
+			git.checkout().setName(c2.getName()).call();
+			assertEquals(c2, db.resolve("@{0}"));
 			assertEquals(c1, db.resolve("@{1}"));
-			fail(); // Looking at wrong ref, e.g HEAD
-		} catch (RevisionSyntaxException e) {
-			assertNotNull(e);
+			assertEquals(c2, db.resolve("@{2}"));
 		}
-
-		// detached head, read HEAD reflog
-		git.checkout().setName(c2.getName()).call();
-		assertEquals(c2, db.resolve("@{0}"));
-		assertEquals(c1, db.resolve("@{1}"));
-		assertEquals(c2, db.resolve("@{2}"));
 	}
 
 	@Test
 	public void resolveReflogParent() throws Exception {
-		Git git = new Git(db);
-		writeTrashFile("file.txt", "content");
-		git.add().addFilepattern("file.txt").call();
-		RevCommit c1 = git.commit().setMessage("create file").call();
-		writeTrashFile("file.txt", "content2");
-		git.add().addFilepattern("file.txt").call();
-		git.commit().setMessage("edit file").call();
+		try (Git git = new Git(db)) {
+			writeTrashFile("file.txt", "content");
+			git.add().addFilepattern("file.txt").call();
+			RevCommit c1 = git.commit().setMessage("create file").call();
+			writeTrashFile("file.txt", "content2");
+			git.add().addFilepattern("file.txt").call();
+			git.commit().setMessage("edit file").call();
 
-		assertEquals(c1, db.resolve("master@{0}~1"));
+			assertEquals(c1, db.resolve("master@{0}~1"));
+		}
 	}
 
 	@Test
 	public void resolveNonExistingBranch() throws Exception {
-		Git git = new Git(db);
-		writeTrashFile("file.txt", "content");
-		git.add().addFilepattern("file.txt").call();
-		git.commit().setMessage("create file").call();
-		assertNull(db.resolve("notabranch@{7}"));
+		try (Git git = new Git(db)) {
+			writeTrashFile("file.txt", "content");
+			git.add().addFilepattern("file.txt").call();
+			git.commit().setMessage("create file").call();
+			assertNull(db.resolve("notabranch@{7}"));
+		}
 	}
 
 	@Test
 	public void resolvePreviousBranch() throws Exception {
-		Git git = new Git(db);
-		writeTrashFile("file.txt", "content");
-		git.add().addFilepattern("file.txt").call();
-		RevCommit c1 = git.commit().setMessage("create file").call();
-		writeTrashFile("file.txt", "content2");
-		git.add().addFilepattern("file.txt").call();
-		RevCommit c2 = git.commit().setMessage("edit file").call();
+		try (Git git = new Git(db)) {
+			writeTrashFile("file.txt", "content");
+			git.add().addFilepattern("file.txt").call();
+			RevCommit c1 = git.commit().setMessage("create file").call();
+			writeTrashFile("file.txt", "content2");
+			git.add().addFilepattern("file.txt").call();
+			RevCommit c2 = git.commit().setMessage("edit file").call();
 
-		git.checkout().setCreateBranch(true).setName("newbranch")
-				.setStartPoint(c1).call();
+			git.checkout().setCreateBranch(true).setName("newbranch")
+					.setStartPoint(c1).call();
 
-		git.checkout().setName(c1.getName()).call();
+			git.checkout().setName(c1.getName()).call();
 
-		git.checkout().setName("master").call();
+			git.checkout().setName("master").call();
 
-		assertEquals(c1.getName(), db.simplify("@{-1}"));
-		assertEquals("newbranch", db.simplify("@{-2}"));
-		assertEquals("master", db.simplify("@{-3}"));
+			assertEquals(c1.getName(), db.simplify("@{-1}"));
+			assertEquals("newbranch", db.simplify("@{-2}"));
+			assertEquals("master", db.simplify("@{-3}"));
 
-		// chained expression
-		try {
-			// Cannot refer to reflog of detached head
-			db.resolve("@{-1}@{0}");
-			fail();
-		} catch (RevisionSyntaxException e) {
-			// good
+			// chained expression
+			try {
+				// Cannot refer to reflog of detached head
+				db.resolve("@{-1}@{0}");
+				fail();
+			} catch (RevisionSyntaxException e) {
+				// good
+			}
+			assertEquals(c1.getName(), db.resolve("@{-2}@{0}").getName());
+
+			assertEquals(c2.getName(), db.resolve("@{-3}@{0}").getName());
 		}
-		assertEquals(c1.getName(), db.resolve("@{-2}@{0}").getName());
-
-		assertEquals(c2.getName(), db.resolve("@{-3}@{0}").getName());
 	}
 
 	@Test
 	public void resolveDate() throws Exception {
-		Git git = new Git(db);
-		writeTrashFile("file.txt", "content");
-		git.add().addFilepattern("file.txt").call();
-		git.commit().setMessage("create file").call();
-		try {
-			db.resolve("master@{yesterday}");
-			fail("Exception not thrown");
-		} catch (RevisionSyntaxException e) {
-			assertNotNull(e);
+		try (Git git = new Git(db)) {
+			writeTrashFile("file.txt", "content");
+			git.add().addFilepattern("file.txt").call();
+			git.commit().setMessage("create file").call();
+			try {
+				db.resolve("master@{yesterday}");
+				fail("Exception not thrown");
+			} catch (RevisionSyntaxException e) {
+				assertNotNull(e);
+			}
 		}
 	}
 }
\ No newline at end of file
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RepositoryResolveTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RepositoryResolveTest.java
index c9ea286..1d2a4e9 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RepositoryResolveTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RepositoryResolveTest.java
@@ -267,35 +267,37 @@
 
 	@Test
 	public void resolveExprSimple() throws Exception {
-		Git git = new Git(db);
-		writeTrashFile("file.txt", "content");
-		git.add().addFilepattern("file.txt").call();
-		git.commit().setMessage("create file").call();
-		assertEquals("master", db.simplify("master"));
-		assertEquals("refs/heads/master", db.simplify("refs/heads/master"));
-		assertEquals("HEAD", db.simplify("HEAD"));
+		try (Git git = new Git(db)) {
+			writeTrashFile("file.txt", "content");
+			git.add().addFilepattern("file.txt").call();
+			git.commit().setMessage("create file").call();
+			assertEquals("master", db.simplify("master"));
+			assertEquals("refs/heads/master", db.simplify("refs/heads/master"));
+			assertEquals("HEAD", db.simplify("HEAD"));
+		}
 	}
 
 	@Test
 	public void resolveUpstream() throws Exception {
-		Git git = new Git(db);
-		writeTrashFile("file.txt", "content");
-		git.add().addFilepattern("file.txt").call();
-		RevCommit c1 = git.commit().setMessage("create file").call();
-		writeTrashFile("file2.txt", "content");
-		RefUpdate updateRemoteRef = db.updateRef("refs/remotes/origin/main");
-		updateRemoteRef.setNewObjectId(c1);
-		updateRemoteRef.update();
-		db.getConfig().setString("branch", "master", "remote", "origin");
-		db.getConfig()
-				.setString("branch", "master", "merge", "refs/heads/main");
-		db.getConfig().setString("remote", "origin", "url",
-				"git://example.com/here");
-		db.getConfig().setString("remote", "origin", "fetch",
-				"+refs/heads/*:refs/remotes/origin/*");
-		git.add().addFilepattern("file2.txt").call();
-		git.commit().setMessage("create file").call();
-		assertEquals("refs/remotes/origin/main", db.simplify("@{upstream}"));
+		try (Git git = new Git(db)) {
+			writeTrashFile("file.txt", "content");
+			git.add().addFilepattern("file.txt").call();
+			RevCommit c1 = git.commit().setMessage("create file").call();
+			writeTrashFile("file2.txt", "content");
+			RefUpdate updateRemoteRef = db.updateRef("refs/remotes/origin/main");
+			updateRemoteRef.setNewObjectId(c1);
+			updateRemoteRef.update();
+			db.getConfig().setString("branch", "master", "remote", "origin");
+			db.getConfig()
+					.setString("branch", "master", "merge", "refs/heads/main");
+			db.getConfig().setString("remote", "origin", "url",
+					"git://example.com/here");
+			db.getConfig().setString("remote", "origin", "fetch",
+					"+refs/heads/*:refs/remotes/origin/*");
+			git.add().addFilepattern("file2.txt").call();
+			git.commit().setMessage("create file").call();
+			assertEquals("refs/remotes/origin/main", db.simplify("@{upstream}"));
+		}
 	}
 
 	@Test
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0001_PersonIdentTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0001_PersonIdentTest.java
index 315c495..1515a07 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0001_PersonIdentTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0001_PersonIdentTest.java
@@ -75,11 +75,13 @@
 				p.toExternalString());
 	}
 
+	@SuppressWarnings("unused")
 	@Test(expected = IllegalArgumentException.class)
 	public void nullForNameShouldThrowIllegalArgumentException() {
 		new PersonIdent(null, "author@example.com");
 	}
 
+	@SuppressWarnings("unused")
 	@Test(expected = IllegalArgumentException.class)
 	public void nullForEmailShouldThrowIllegalArgumentException() {
 		new PersonIdent("A U Thor", null);
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0002_TreeTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0002_TreeTest.java
deleted file mode 100644
index 651e62c..0000000
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0002_TreeTest.java
+++ /dev/null
@@ -1,319 +0,0 @@
-/*
- * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
- * Copyright (C) 2006-2008, Shawn O. Pearce <spearce@spearce.org>
- * and other copyright owners as documented in the project's IP log.
- *
- * This program and the accompanying materials are made available
- * under the terms of the Eclipse Distribution License v1.0 which
- * accompanies this distribution, is reproduced below, and is
- * available at http://www.eclipse.org/org/documents/edl-v10.php
- *
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or
- * without modification, are permitted provided that the following
- * conditions are met:
- *
- * - Redistributions of source code must retain the above copyright
- *   notice, this list of conditions and the following disclaimer.
- *
- * - Redistributions in binary form must reproduce the above
- *   copyright notice, this list of conditions and the following
- *   disclaimer in the documentation and/or other materials provided
- *   with the distribution.
- *
- * - Neither the name of the Eclipse Foundation, Inc. nor the
- *   names of its contributors may be used to endorse or promote
- *   products derived from this software without specific prior
- *   written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
- * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
- * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
- * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
- * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
- * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
- * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
- * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-package org.eclipse.jgit.lib;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertSame;
-import static org.junit.Assert.assertTrue;
-
-import java.io.IOException;
-import java.io.UnsupportedEncodingException;
-import java.util.ArrayList;
-import java.util.List;
-
-import org.eclipse.jgit.test.resources.SampleDataRepositoryTestCase;
-import org.junit.Test;
-
-@SuppressWarnings("deprecation")
-public class T0002_TreeTest extends SampleDataRepositoryTestCase {
-	private static final ObjectId SOME_FAKE_ID = ObjectId.fromString(
-			"0123456789abcdef0123456789abcdef01234567");
-
-	private static int compareNamesUsingSpecialCompare(String a, String b)
-			throws UnsupportedEncodingException {
-		char lasta = '\0';
-		byte[] abytes;
-		if (a.length() > 0 && a.charAt(a.length()-1) == '/') {
-			lasta = '/';
-			a = a.substring(0, a.length() - 1);
-		}
-		abytes = a.getBytes("ISO-8859-1");
-		char lastb = '\0';
-		byte[] bbytes;
-		if (b.length() > 0 && b.charAt(b.length()-1) == '/') {
-			lastb = '/';
-			b = b.substring(0, b.length() - 1);
-		}
-		bbytes = b.getBytes("ISO-8859-1");
-		return Tree.compareNames(abytes, bbytes, lasta, lastb);
-	}
-
-	@Test
-	public void test000_sort_01() throws UnsupportedEncodingException {
-		assertEquals(0, compareNamesUsingSpecialCompare("a","a"));
-	}
-
-	@Test
-	public void test000_sort_02() throws UnsupportedEncodingException {
-		assertEquals(-1, compareNamesUsingSpecialCompare("a","b"));
-		assertEquals(1, compareNamesUsingSpecialCompare("b","a"));
-	}
-
-	@Test
-	public void test000_sort_03() throws UnsupportedEncodingException {
-		assertEquals(1, compareNamesUsingSpecialCompare("a:","a"));
-		assertEquals(1, compareNamesUsingSpecialCompare("a/","a"));
-		assertEquals(-1, compareNamesUsingSpecialCompare("a","a/"));
-		assertEquals(-1, compareNamesUsingSpecialCompare("a","a:"));
-		assertEquals(1, compareNamesUsingSpecialCompare("a:","a/"));
-		assertEquals(-1, compareNamesUsingSpecialCompare("a/","a:"));
-	}
-
-	@Test
-	public void test000_sort_04() throws UnsupportedEncodingException {
-		assertEquals(-1, compareNamesUsingSpecialCompare("a.a","a/a"));
-		assertEquals(1, compareNamesUsingSpecialCompare("a/a","a.a"));
-	}
-
-	@Test
-	public void test000_sort_05() throws UnsupportedEncodingException {
-		assertEquals(-1, compareNamesUsingSpecialCompare("a.","a/"));
-		assertEquals(1, compareNamesUsingSpecialCompare("a/","a."));
-
-	}
-
-	@Test
-	public void test001_createEmpty() throws IOException {
-		final Tree t = new Tree(db);
-		assertTrue("isLoaded", t.isLoaded());
-		assertTrue("isModified", t.isModified());
-		assertTrue("no parent", t.getParent() == null);
-		assertTrue("isRoot", t.isRoot());
-		assertTrue("no name", t.getName() == null);
-		assertTrue("no nameUTF8", t.getNameUTF8() == null);
-		assertTrue("has entries array", t.members() != null);
-		assertEquals("entries is empty", 0, t.members().length);
-		assertEquals("full name is empty", "", t.getFullName());
-		assertTrue("no id", t.getId() == null);
-		assertTrue("database is r", t.getRepository() == db);
-		assertTrue("no foo child", t.findTreeMember("foo") == null);
-		assertTrue("no foo child", t.findBlobMember("foo") == null);
-	}
-
-	@Test
-	public void test002_addFile() throws IOException {
-		final Tree t = new Tree(db);
-		t.setId(SOME_FAKE_ID);
-		assertTrue("has id", t.getId() != null);
-		assertFalse("not modified", t.isModified());
-
-		final String n = "bob";
-		final FileTreeEntry f = t.addFile(n);
-		assertNotNull("have file", f);
-		assertEquals("name matches", n, f.getName());
-		assertEquals("name matches", f.getName(), new String(f.getNameUTF8(),
-				"UTF-8"));
-		assertEquals("full name matches", n, f.getFullName());
-		assertTrue("no id", f.getId() == null);
-		assertTrue("is modified", t.isModified());
-		assertTrue("has no id", t.getId() == null);
-		assertTrue("found bob", t.findBlobMember(f.getName()) == f);
-
-		final TreeEntry[] i = t.members();
-		assertNotNull("members array not null", i);
-		assertTrue("iterator is not empty", i != null && i.length > 0);
-		assertTrue("iterator returns file", i != null && i[0] == f);
-		assertTrue("iterator is empty", i != null && i.length == 1);
-	}
-
-	@Test
-	public void test004_addTree() throws IOException {
-		final Tree t = new Tree(db);
-		t.setId(SOME_FAKE_ID);
-		assertTrue("has id", t.getId() != null);
-		assertFalse("not modified", t.isModified());
-
-		final String n = "bob";
-		final Tree f = t.addTree(n);
-		assertNotNull("have tree", f);
-		assertEquals("name matches", n, f.getName());
-		assertEquals("name matches", f.getName(), new String(f.getNameUTF8(),
-				"UTF-8"));
-		assertEquals("full name matches", n, f.getFullName());
-		assertTrue("no id", f.getId() == null);
-		assertTrue("parent matches", f.getParent() == t);
-		assertTrue("repository matches", f.getRepository() == db);
-		assertTrue("isLoaded", f.isLoaded());
-		assertFalse("has items", f.members().length > 0);
-		assertFalse("is root", f.isRoot());
-		assertTrue("parent is modified", t.isModified());
-		assertTrue("parent has no id", t.getId() == null);
-		assertTrue("found bob child", t.findTreeMember(f.getName()) == f);
-
-		final TreeEntry[] i = t.members();
-		assertTrue("iterator is not empty", i.length > 0);
-		assertTrue("iterator returns file", i[0] == f);
-		assertEquals("iterator is empty", 1, i.length);
-	}
-
-	@Test
-	public void test005_addRecursiveFile() throws IOException {
-		final Tree t = new Tree(db);
-		final FileTreeEntry f = t.addFile("a/b/c");
-		assertNotNull("created f", f);
-		assertEquals("c", f.getName());
-		assertEquals("b", f.getParent().getName());
-		assertEquals("a", f.getParent().getParent().getName());
-		assertTrue("t is great-grandparent", t == f.getParent().getParent()
-				.getParent());
-	}
-
-	@Test
-	public void test005_addRecursiveTree() throws IOException {
-		final Tree t = new Tree(db);
-		final Tree f = t.addTree("a/b/c");
-		assertNotNull("created f", f);
-		assertEquals("c", f.getName());
-		assertEquals("b", f.getParent().getName());
-		assertEquals("a", f.getParent().getParent().getName());
-		assertTrue("t is great-grandparent", t == f.getParent().getParent()
-				.getParent());
-	}
-
-	@Test
-	public void test006_addDeepTree() throws IOException {
-		final Tree t = new Tree(db);
-
-		final Tree e = t.addTree("e");
-		assertNotNull("have e", e);
-		assertTrue("e.parent == t", e.getParent() == t);
-		final Tree f = t.addTree("f");
-		assertNotNull("have f", f);
-		assertTrue("f.parent == t", f.getParent() == t);
-		final Tree g = f.addTree("g");
-		assertNotNull("have g", g);
-		assertTrue("g.parent == f", g.getParent() == f);
-		final Tree h = g.addTree("h");
-		assertNotNull("have h", h);
-		assertTrue("h.parent = g", h.getParent() == g);
-
-		h.setId(SOME_FAKE_ID);
-		assertTrue("h not modified", !h.isModified());
-		g.setId(SOME_FAKE_ID);
-		assertTrue("g not modified", !g.isModified());
-		f.setId(SOME_FAKE_ID);
-		assertTrue("f not modified", !f.isModified());
-		e.setId(SOME_FAKE_ID);
-		assertTrue("e not modified", !e.isModified());
-		t.setId(SOME_FAKE_ID);
-		assertTrue("t not modified.", !t.isModified());
-
-		assertEquals("full path of h ok", "f/g/h", h.getFullName());
-		assertTrue("Can find h", t.findTreeMember(h.getFullName()) == h);
-		assertTrue("Can't find f/z", t.findBlobMember("f/z") == null);
-		assertTrue("Can't find y/z", t.findBlobMember("y/z") == null);
-
-		final FileTreeEntry i = h.addFile("i");
-		assertNotNull(i);
-		assertEquals("full path of i ok", "f/g/h/i", i.getFullName());
-		assertTrue("Can find i", t.findBlobMember(i.getFullName()) == i);
-		assertTrue("h modified", h.isModified());
-		assertTrue("g modified", g.isModified());
-		assertTrue("f modified", f.isModified());
-		assertTrue("e not modified", !e.isModified());
-		assertTrue("t modified", t.isModified());
-
-		assertTrue("h no id", h.getId() == null);
-		assertTrue("g no id", g.getId() == null);
-		assertTrue("f no id", f.getId() == null);
-		assertTrue("e has id", e.getId() != null);
-		assertTrue("t no id", t.getId() == null);
-	}
-
-	@Test
-	public void test007_manyFileLookup() throws IOException {
-		final Tree t = new Tree(db);
-		final List<FileTreeEntry> files = new ArrayList<FileTreeEntry>(26 * 26);
-		for (char level1 = 'a'; level1 <= 'z'; level1++) {
-			for (char level2 = 'a'; level2 <= 'z'; level2++) {
-				final String n = "." + level1 + level2 + "9";
-				final FileTreeEntry f = t.addFile(n);
-				assertNotNull("File " + n + " added.", f);
-				assertEquals(n, f.getName());
-				files.add(f);
-			}
-		}
-		assertEquals(files.size(), t.memberCount());
-		final TreeEntry[] ents = t.members();
-		assertNotNull(ents);
-		assertEquals(files.size(), ents.length);
-		for (int k = 0; k < ents.length; k++) {
-			assertTrue("File " + files.get(k).getName()
-					+ " is at " + k + ".", files.get(k) == ents[k]);
-		}
-	}
-
-	@Test
-	public void test008_SubtreeInternalSorting() throws IOException {
-		final Tree t = new Tree(db);
-		final FileTreeEntry e0 = t.addFile("a-b");
-		final FileTreeEntry e1 = t.addFile("a-");
-		final FileTreeEntry e2 = t.addFile("a=b");
-		final Tree e3 = t.addTree("a");
-		final FileTreeEntry e4 = t.addFile("a=");
-
-		final TreeEntry[] ents = t.members();
-		assertSame(e1, ents[0]);
-		assertSame(e0, ents[1]);
-		assertSame(e3, ents[2]);
-		assertSame(e4, ents[3]);
-		assertSame(e2, ents[4]);
-	}
-
-	@Test
-	public void test009_SymlinkAndGitlink() throws IOException {
-		final Tree symlinkTree = mapTree("symlink");
-		assertTrue("Symlink entry exists", symlinkTree.existsBlob("symlink.txt"));
-		final Tree gitlinkTree = mapTree("gitlink");
-		assertTrue("Gitlink entry exists", gitlinkTree.existsBlob("submodule"));
-	}
-
-	private Tree mapTree(String name) throws IOException {
-		ObjectId id = db.resolve(name + "^{tree}");
-		return new Tree(db, id, db.open(id).getCachedBytes());
-	}
-}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/MergeMessageFormatterTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/MergeMessageFormatterTest.java
index 21ef747..6c90f7d 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/MergeMessageFormatterTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/MergeMessageFormatterTest.java
@@ -84,44 +84,44 @@
 
 	@Test
 	public void testOneBranch() throws IOException {
-		Ref a = db.getRef("refs/heads/a");
-		Ref master = db.getRef("refs/heads/master");
+		Ref a = db.exactRef("refs/heads/a");
+		Ref master = db.exactRef("refs/heads/master");
 		String message = formatter.format(Arrays.asList(a), master);
 		assertEquals("Merge branch 'a'", message);
 	}
 
 	@Test
 	public void testTwoBranches() throws IOException {
-		Ref a = db.getRef("refs/heads/a");
-		Ref b = db.getRef("refs/heads/b");
-		Ref master = db.getRef("refs/heads/master");
+		Ref a = db.exactRef("refs/heads/a");
+		Ref b = db.exactRef("refs/heads/b");
+		Ref master = db.exactRef("refs/heads/master");
 		String message = formatter.format(Arrays.asList(a, b), master);
 		assertEquals("Merge branches 'a' and 'b'", message);
 	}
 
 	@Test
 	public void testThreeBranches() throws IOException {
-		Ref c = db.getRef("refs/heads/c");
-		Ref b = db.getRef("refs/heads/b");
-		Ref a = db.getRef("refs/heads/a");
-		Ref master = db.getRef("refs/heads/master");
+		Ref c = db.exactRef("refs/heads/c");
+		Ref b = db.exactRef("refs/heads/b");
+		Ref a = db.exactRef("refs/heads/a");
+		Ref master = db.exactRef("refs/heads/master");
 		String message = formatter.format(Arrays.asList(c, b, a), master);
 		assertEquals("Merge branches 'c', 'b' and 'a'", message);
 	}
 
 	@Test
 	public void testRemoteBranch() throws Exception {
-		Ref remoteA = db.getRef("refs/remotes/origin/remote-a");
-		Ref master = db.getRef("refs/heads/master");
+		Ref remoteA = db.exactRef("refs/remotes/origin/remote-a");
+		Ref master = db.exactRef("refs/heads/master");
 		String message = formatter.format(Arrays.asList(remoteA), master);
 		assertEquals("Merge remote-tracking branch 'origin/remote-a'", message);
 	}
 
 	@Test
 	public void testMixed() throws IOException {
-		Ref c = db.getRef("refs/heads/c");
-		Ref remoteA = db.getRef("refs/remotes/origin/remote-a");
-		Ref master = db.getRef("refs/heads/master");
+		Ref c = db.exactRef("refs/heads/c");
+		Ref remoteA = db.exactRef("refs/remotes/origin/remote-a");
+		Ref master = db.exactRef("refs/heads/master");
 		String message = formatter.format(Arrays.asList(c, remoteA), master);
 		assertEquals("Merge branch 'c', remote-tracking branch 'origin/remote-a'",
 				message);
@@ -129,8 +129,8 @@
 
 	@Test
 	public void testTag() throws IOException {
-		Ref tagA = db.getRef("refs/tags/A");
-		Ref master = db.getRef("refs/heads/master");
+		Ref tagA = db.exactRef("refs/tags/A");
+		Ref master = db.exactRef("refs/heads/master");
 		String message = formatter.format(Arrays.asList(tagA), master);
 		assertEquals("Merge tag 'A'", message);
 	}
@@ -141,7 +141,7 @@
 				.fromString("6db9c2ebf75590eef973081736730a9ea169a0c4");
 		Ref commit = new ObjectIdRef.Unpeeled(Storage.LOOSE,
 				objectId.getName(), objectId);
-		Ref master = db.getRef("refs/heads/master");
+		Ref master = db.exactRef("refs/heads/master");
 		String message = formatter.format(Arrays.asList(commit), master);
 		assertEquals("Merge commit '6db9c2ebf75590eef973081736730a9ea169a0c4'",
 				message);
@@ -154,7 +154,7 @@
 				.fromString("6db9c2ebf75590eef973081736730a9ea169a0c4");
 		Ref remoteBranch = new ObjectIdRef.Unpeeled(Storage.LOOSE, name,
 				objectId);
-		Ref master = db.getRef("refs/heads/master");
+		Ref master = db.exactRef("refs/heads/master");
 		String message = formatter.format(Arrays.asList(remoteBranch), master);
 		assertEquals("Merge branch 'test' of http://egit.eclipse.org/jgit.git",
 				message);
@@ -162,16 +162,16 @@
 
 	@Test
 	public void testIntoOtherThanMaster() throws IOException {
-		Ref a = db.getRef("refs/heads/a");
-		Ref b = db.getRef("refs/heads/b");
+		Ref a = db.exactRef("refs/heads/a");
+		Ref b = db.exactRef("refs/heads/b");
 		String message = formatter.format(Arrays.asList(a), b);
 		assertEquals("Merge branch 'a' into b", message);
 	}
 
 	@Test
 	public void testIntoHeadOtherThanMaster() throws IOException {
-		Ref a = db.getRef("refs/heads/a");
-		Ref b = db.getRef("refs/heads/b");
+		Ref a = db.exactRef("refs/heads/a");
+		Ref b = db.exactRef("refs/heads/b");
 		SymbolicRef head = new SymbolicRef("HEAD", b);
 		String message = formatter.format(Arrays.asList(a), head);
 		assertEquals("Merge branch 'a' into b", message);
@@ -179,8 +179,8 @@
 
 	@Test
 	public void testIntoSymbolicRefHeadPointingToMaster() throws IOException {
-		Ref a = db.getRef("refs/heads/a");
-		Ref master = db.getRef("refs/heads/master");
+		Ref a = db.exactRef("refs/heads/a");
+		Ref master = db.exactRef("refs/heads/master");
 		SymbolicRef head = new SymbolicRef("HEAD", master);
 		String message = formatter.format(Arrays.asList(a), head);
 		assertEquals("Merge branch 'a'", message);
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/RecursiveMergerTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/RecursiveMergerTest.java
index 7ef6448..0e7109c 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/RecursiveMergerTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/RecursiveMergerTest.java
@@ -872,32 +872,31 @@
 
 	private String contentAsString(Repository r, ObjectId treeId, String path)
 			throws MissingObjectException, IOException {
-		TreeWalk tw = new TreeWalk(r);
-		tw.addTree(treeId);
-		tw.setFilter(PathFilter.create(path));
-		tw.setRecursive(true);
-		if (!tw.next())
-			return null;
-		AnyObjectId blobId = tw.getObjectId(0);
+		AnyObjectId blobId;
+		try (TreeWalk tw = new TreeWalk(r)) {
+			tw.addTree(treeId);
+			tw.setFilter(PathFilter.create(path));
+			tw.setRecursive(true);
+			if (!tw.next()) {
+				return null;
+			}
+			blobId = tw.getObjectId(0);
+		}
 
 		StringBuilder result = new StringBuilder();
-		BufferedReader br = null;
 		ObjectReader or = r.newObjectReader();
-		try {
-			br = new BufferedReader(new InputStreamReader(or.open(blobId)
-					.openStream()));
+		try (BufferedReader br = new BufferedReader(
+				new InputStreamReader(or.open(blobId).openStream()))) {
 			String line;
 			boolean first = true;
 			while ((line = br.readLine()) != null) {
-				if (!first)
+				if (!first) {
 					result.append('\n');
+				}
 				result.append(line);
 				first = false;
 			}
 			return result.toString();
-		} finally {
-			if (br != null)
-				br.close();
 		}
 	}
 }
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 cd6a4be..55bb93a 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
@@ -88,35 +88,36 @@
 		file = new File(folder1, "file2.txt");
 		write(file, "folder1--file2.txt");
 
-		Git git = new Git(db);
-		git.add().addFilepattern(folder1.getName()).call();
-		RevCommit base = git.commit().setMessage("adding folder").call();
+		try (Git git = new Git(db)) {
+			git.add().addFilepattern(folder1.getName()).call();
+			RevCommit base = git.commit().setMessage("adding folder").call();
 
-		recursiveDelete(folder1);
-		git.rm().addFilepattern("folder1/file1.txt")
-				.addFilepattern("folder1/file2.txt").call();
-		RevCommit other = git.commit()
-				.setMessage("removing folders on 'other'").call();
+			recursiveDelete(folder1);
+			git.rm().addFilepattern("folder1/file1.txt")
+					.addFilepattern("folder1/file2.txt").call();
+			RevCommit other = git.commit()
+					.setMessage("removing folders on 'other'").call();
 
-		git.checkout().setName(base.name()).call();
+			git.checkout().setName(base.name()).call();
 
-		file = new File(db.getWorkTree(), "unrelated.txt");
-		write(file, "unrelated");
+			file = new File(db.getWorkTree(), "unrelated.txt");
+			write(file, "unrelated");
 
-		git.add().addFilepattern("unrelated.txt").call();
-		RevCommit head = git.commit().setMessage("Adding another file").call();
+			git.add().addFilepattern("unrelated.txt").call();
+			RevCommit head = git.commit().setMessage("Adding another file").call();
 
-		// Untracked file to cause failing path for delete() of folder1
-		// but that's ok.
-		file = new File(folder1, "file3.txt");
-		write(file, "folder1--file3.txt");
+			// Untracked file to cause failing path for delete() of folder1
+			// but that's ok.
+			file = new File(folder1, "file3.txt");
+			write(file, "folder1--file3.txt");
 
-		ResolveMerger merger = (ResolveMerger) strategy.newMerger(db, false);
-		merger.setCommitNames(new String[] { "BASE", "HEAD", "other" });
-		merger.setWorkingTreeIterator(new FileTreeIterator(db));
-		boolean ok = merger.merge(head.getId(), other.getId());
-		assertTrue(ok);
-		assertTrue(file.exists());
+			ResolveMerger merger = (ResolveMerger) strategy.newMerger(db, false);
+			merger.setCommitNames(new String[] { "BASE", "HEAD", "other" });
+			merger.setWorkingTreeIterator(new FileTreeIterator(db));
+			boolean ok = merger.merge(head.getId(), other.getId());
+			assertTrue(ok);
+			assertTrue(file.exists());
+		}
 	}
 
 	/**
@@ -630,7 +631,7 @@
 		// ResolveMerge
 		try {
 			MergeResult mergeResult = git.merge().setStrategy(strategy)
-					.include(git.getRepository().getRef("refs/heads/side"))
+					.include(git.getRepository().exactRef("refs/heads/side"))
 					.call();
 			assertEquals(MergeStrategy.RECURSIVE, strategy);
 			assertEquals(MergeResult.MergeStatus.MERGED,
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/SquashMessageFormatterTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/SquashMessageFormatterTest.java
index b7b2291..7a2586d 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/SquashMessageFormatterTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/SquashMessageFormatterTest.java
@@ -73,17 +73,18 @@
 
 	@Test
 	public void testCommit() throws Exception {
-		Git git = new Git(db);
-		revCommit = git.commit().setMessage("squash_me").call();
+		try (Git git = new Git(db)) {
+			revCommit = git.commit().setMessage("squash_me").call();
 
-		Ref master = db.getRef("refs/heads/master");
-		String message = msgFormatter.format(Arrays.asList(revCommit), master);
-		assertEquals(
-				"Squashed commit of the following:\n\ncommit "
-						+ revCommit.getName() + "\nAuthor: "
-						+ revCommit.getAuthorIdent().getName() + " <"
-						+ revCommit.getAuthorIdent().getEmailAddress()
-						+ ">\nDate:   " + dateFormatter.formatDate(author)
-						+ "\n\n\tsquash_me\n", message);
+			Ref master = db.exactRef("refs/heads/master");
+			String message = msgFormatter.format(Arrays.asList(revCommit), master);
+			assertEquals(
+					"Squashed commit of the following:\n\ncommit "
+							+ revCommit.getName() + "\nAuthor: "
+							+ revCommit.getAuthorIdent().getName() + " <"
+							+ revCommit.getAuthorIdent().getEmailAddress()
+							+ ">\nDate:   " + dateFormatter.formatDate(author)
+							+ "\n\n\tsquash_me\n", message);
+		}
 	}
 }
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 4539a01..47b08a7 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
@@ -403,10 +403,12 @@
 		}
 
 		RevCommit n = commitNoteMap(map);
-		TreeWalk tw = new TreeWalk(reader);
-		tw.reset(n.getTree());
-		while (tw.next())
-			assertFalse("no fan-out subtree", tw.isSubtree());
+		try (TreeWalk tw = new TreeWalk(reader)) {
+			tw.reset(n.getTree());
+			while (tw.next()) {
+				assertFalse("no fan-out subtree", tw.isSubtree());
+			}
+		}
 
 		for (int i = 254; i < 256; i++) {
 			idBuf.setByte(Constants.OBJECT_ID_LENGTH - 1, i);
@@ -418,13 +420,15 @@
 
 		// The 00 bucket is fully split.
 		String path = fanout(38, idBuf.name());
-		tw = TreeWalk.forPath(reader, path, n.getTree());
-		assertNotNull("has " + path, tw);
+		try (TreeWalk tw = TreeWalk.forPath(reader, path, n.getTree())) {
+			assertNotNull("has " + path, tw);
+		}
 
 		// The other bucket is not.
 		path = fanout(2, data1.name());
-		tw = TreeWalk.forPath(reader, path, n.getTree());
-		assertNotNull("has " + path, tw);
+		try (TreeWalk tw = TreeWalk.forPath(reader, path, n.getTree())) {
+			assertNotNull("has " + path, tw);
+		}
 	}
 
 	@Test
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/ObjectWalkTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/ObjectWalkTest.java
index 2a59f58..e5ad313 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/ObjectWalkTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/ObjectWalkTest.java
@@ -47,14 +47,12 @@
 import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertTrue;
 
-import org.eclipse.jgit.lib.Constants;
-import org.eclipse.jgit.lib.FileTreeEntry;
+import org.eclipse.jgit.lib.FileMode;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.ObjectInserter;
-import org.eclipse.jgit.lib.Tree;
+import org.eclipse.jgit.lib.TreeFormatter;
 import org.junit.Test;
 
-@SuppressWarnings("deprecation")
 public class ObjectWalkTest extends RevWalkTestCase {
 	protected ObjectWalk objw;
 
@@ -220,28 +218,24 @@
 				.fromString("abbbfafe3129f85747aba7bfac992af77134c607");
 		final RevTree tree_root, tree_A, tree_AB;
 		final RevCommit b;
-		{
-			Tree root = new Tree(db);
-			Tree A = root.addTree("A");
-			FileTreeEntry B = root.addFile("B");
-			B.setId(bId);
+		try (ObjectInserter inserter = db.newObjectInserter()) {
+			ObjectId empty = inserter.insert(new TreeFormatter());
 
-			Tree A_A = A.addTree("A");
-			Tree A_B = A.addTree("B");
+			TreeFormatter A = new TreeFormatter();
+			A.append("A", FileMode.TREE, empty);
+			A.append("B", FileMode.TREE, empty);
+			ObjectId idA = inserter.insert(A);
 
-			try (final ObjectInserter inserter = db.newObjectInserter()) {
-				A_A.setId(inserter.insert(Constants.OBJ_TREE, A_A.format()));
-				A_B.setId(inserter.insert(Constants.OBJ_TREE, A_B.format()));
-				A.setId(inserter.insert(Constants.OBJ_TREE, A.format()));
-				root.setId(inserter.insert(Constants.OBJ_TREE, root.format()));
-				inserter.flush();
-			}
+			TreeFormatter root = new TreeFormatter();
+			root.append("A", FileMode.TREE, idA);
+			root.append("B", FileMode.REGULAR_FILE, bId);
+			ObjectId idRoot = inserter.insert(root);
+			inserter.flush();
 
-			tree_root = rw.parseTree(root.getId());
-			tree_A = rw.parseTree(A.getId());
-			tree_AB = rw.parseTree(A_A.getId());
-			assertSame(tree_AB, rw.parseTree(A_B.getId()));
-			b = commit(rw.parseTree(root.getId()));
+			tree_root = objw.parseTree(idRoot);
+			tree_A = objw.parseTree(idA);
+			tree_AB = objw.parseTree(empty);
+			b = commit(tree_root);
 		}
 
 		markStart(b);
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevCommitListTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevCommitListTest.java
index 5ec6eb3..4d75322 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevCommitListTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevCommitListTest.java
@@ -57,14 +57,15 @@
 	private RevCommitList<RevCommit> list;
 
 	public void setup(int count) throws Exception {
-		Git git = new Git(db);
-		for (int i = 0; i < count; i++)
-			git.commit().setCommitter(committer).setAuthor(author)
-					.setMessage("commit " + i).call();
-		list = new RevCommitList<RevCommit>();
-		RevWalk w = new RevWalk(db);
-		w.markStart(w.lookupCommit(db.resolve(Constants.HEAD)));
-		list.source(w);
+		try (Git git = new Git(db);
+				RevWalk w = new RevWalk(db);) {
+			for (int i = 0; i < count; i++)
+				git.commit().setCommitter(committer).setAuthor(author)
+						.setMessage("commit " + i).call();
+			list = new RevCommitList<RevCommit>();
+			w.markStart(w.lookupCommit(db.resolve(Constants.HEAD)));
+			list.source(w);
+		}
 	}
 
 	@Test
@@ -107,17 +108,18 @@
 	public void testFillToCommit() throws Exception {
 		setup(3);
 
-		RevWalk w = new RevWalk(db);
-		w.markStart(w.lookupCommit(db.resolve(Constants.HEAD)));
+		try (RevWalk w = new RevWalk(db)) {
+			w.markStart(w.lookupCommit(db.resolve(Constants.HEAD)));
 
-		w.next();
-		RevCommit c = w.next();
-		assertNotNull("should have found 2. commit", c);
+			w.next();
+			RevCommit c = w.next();
+			assertNotNull("should have found 2. commit", c);
 
-		list.fillTo(c, 5);
-		assertEquals(2, list.size());
-		assertEquals("commit 1", list.get(1).getFullMessage());
-		assertNull(list.get(3));
+			list.fillTo(c, 5);
+			assertEquals(2, list.size());
+			assertEquals("commit 1", list.get(1).getFullMessage());
+			assertNull(list.get(3));
+		}
 	}
 
 	@Test
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevCommitParseTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevCommitParseTest.java
index beda2a7..1a15842 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevCommitParseTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevCommitParseTest.java
@@ -43,13 +43,18 @@
 
 package org.eclipse.jgit.revwalk;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
 import java.io.ByteArrayOutputStream;
 import java.io.UnsupportedEncodingException;
+import java.nio.charset.IllegalCharsetNameException;
+import java.nio.charset.UnsupportedCharsetException;
 import java.util.TimeZone;
 
 import org.eclipse.jgit.junit.RepositoryTestCase;
@@ -304,6 +309,86 @@
 	}
 
 	@Test
+	public void testParse_incorrectUtf8Name() throws Exception {
+		ByteArrayOutputStream b = new ByteArrayOutputStream();
+		b.write("tree 9788669ad918b6fcce64af8882fc9a81cb6aba67\n"
+				.getBytes(UTF_8));
+		b.write("author au <a@example.com> 1218123387 +0700\n".getBytes(UTF_8));
+		b.write("committer co <c@example.com> 1218123390 -0500\n"
+				.getBytes(UTF_8));
+		b.write("encoding 'utf8'\n".getBytes(UTF_8));
+		b.write("\n".getBytes(UTF_8));
+		b.write("Sm\u00f6rg\u00e5sbord\n".getBytes(UTF_8));
+
+		RevCommit c = new RevCommit(
+				id("9473095c4cb2f12aefe1db8a355fe3fafba42f67"));
+		c.parseCanonical(new RevWalk(db), b.toByteArray());
+		assertEquals("'utf8'", c.getEncodingName());
+		assertEquals("Sm\u00f6rg\u00e5sbord\n", c.getFullMessage());
+
+		try {
+			c.getEncoding();
+			fail("Expected " + IllegalCharsetNameException.class);
+		} catch (IllegalCharsetNameException badName) {
+			assertEquals("'utf8'", badName.getMessage());
+		}
+	}
+
+	@Test
+	public void testParse_illegalEncoding() throws Exception {
+		ByteArrayOutputStream b = new ByteArrayOutputStream();
+		b.write("tree 9788669ad918b6fcce64af8882fc9a81cb6aba67\n".getBytes(UTF_8));
+		b.write("author au <a@example.com> 1218123387 +0700\n".getBytes(UTF_8));
+		b.write("committer co <c@example.com> 1218123390 -0500\n".getBytes(UTF_8));
+		b.write("encoding utf-8logoutputencoding=gbk\n".getBytes(UTF_8));
+		b.write("\n".getBytes(UTF_8));
+		b.write("message\n".getBytes(UTF_8));
+
+		RevCommit c = new RevCommit(
+				id("9473095c4cb2f12aefe1db8a355fe3fafba42f67"));
+		c.parseCanonical(new RevWalk(db), b.toByteArray());
+		assertEquals("utf-8logoutputencoding=gbk", c.getEncodingName());
+		assertEquals("message\n", c.getFullMessage());
+		assertEquals("message", c.getShortMessage());
+		assertTrue(c.getFooterLines().isEmpty());
+		assertEquals("au", c.getAuthorIdent().getName());
+
+		try {
+			c.getEncoding();
+			fail("Expected " + IllegalCharsetNameException.class);
+		} catch (IllegalCharsetNameException badName) {
+			assertEquals("utf-8logoutputencoding=gbk", badName.getMessage());
+		}
+	}
+
+	@Test
+	public void testParse_unsupportedEncoding() throws Exception {
+		ByteArrayOutputStream b = new ByteArrayOutputStream();
+		b.write("tree 9788669ad918b6fcce64af8882fc9a81cb6aba67\n".getBytes(UTF_8));
+		b.write("author au <a@example.com> 1218123387 +0700\n".getBytes(UTF_8));
+		b.write("committer co <c@example.com> 1218123390 -0500\n".getBytes(UTF_8));
+		b.write("encoding it_IT.UTF8\n".getBytes(UTF_8));
+		b.write("\n".getBytes(UTF_8));
+		b.write("message\n".getBytes(UTF_8));
+
+		RevCommit c = new RevCommit(
+				id("9473095c4cb2f12aefe1db8a355fe3fafba42f67"));
+		c.parseCanonical(new RevWalk(db), b.toByteArray());
+		assertEquals("it_IT.UTF8", c.getEncodingName());
+		assertEquals("message\n", c.getFullMessage());
+		assertEquals("message", c.getShortMessage());
+		assertTrue(c.getFooterLines().isEmpty());
+		assertEquals("au", c.getAuthorIdent().getName());
+
+		try {
+			c.getEncoding();
+			fail("Expected " + UnsupportedCharsetException.class);
+		} catch (UnsupportedCharsetException badName) {
+			assertEquals("it_IT.UTF8", badName.getMessage());
+		}
+	}
+
+	@Test
 	public void testParse_NoMessage() throws Exception {
 		final String msg = "";
 		final RevCommit c = create(msg);
@@ -367,9 +452,10 @@
 	@Test
 	public void testParse_PublicParseMethod()
 			throws UnsupportedEncodingException {
-		ObjectInserter.Formatter fmt = new ObjectInserter.Formatter();
 		CommitBuilder src = new CommitBuilder();
-		src.setTreeId(fmt.idFor(Constants.OBJ_TREE, new byte[] {}));
+		try (ObjectInserter.Formatter fmt = new ObjectInserter.Formatter()) {
+			src.setTreeId(fmt.idFor(Constants.OBJ_TREE, new byte[] {}));
+		}
 		src.setAuthor(author);
 		src.setCommitter(committer);
 		src.setMessage("Test commit\n\nThis is a test.\n");
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevObjectTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevObjectTest.java
index a768bef..95e7ca6 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevObjectTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevObjectTest.java
@@ -73,9 +73,12 @@
 		assertTrue(a1.equals((Object) a1));
 		assertFalse(a1.equals(""));
 
-		final RevWalk rw2 = new RevWalk(db);
-		final RevCommit a2 = rw2.parseCommit(a1);
-		final RevCommit b2 = rw2.parseCommit(b1);
+		final RevCommit a2;
+		final RevCommit b2;
+		try (final RevWalk rw2 = new RevWalk(db)) {
+			a2 = rw2.parseCommit(a1);
+			b2 = rw2.parseCommit(b1);
+		}
 		assertNotSame(a1, a2);
 		assertNotSame(b1, b2);
 
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevTagParseTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevTagParseTest.java
index 614f49b..f97043b 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevTagParseTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevTagParseTest.java
@@ -43,6 +43,7 @@
 
 package org.eclipse.jgit.revwalk;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
@@ -362,6 +363,44 @@
 	}
 
 	@Test
+	public void testParse_illegalEncoding() throws Exception {
+		ByteArrayOutputStream b = new ByteArrayOutputStream();
+		b.write("object 9788669ad918b6fcce64af8882fc9a81cb6aba67\n".getBytes(UTF_8));
+		b.write("type tree\n".getBytes(UTF_8));
+		b.write("tag v1.0\n".getBytes(UTF_8));
+		b.write("tagger t <t@example.com> 1218123387 +0700\n".getBytes(UTF_8));
+		b.write("encoding utf-8logoutputencoding=gbk\n".getBytes(UTF_8));
+		b.write("\n".getBytes(UTF_8));
+		b.write("message\n".getBytes(UTF_8));
+
+		RevTag t = new RevTag(id("9473095c4cb2f12aefe1db8a355fe3fafba42f67"));
+		t.parseCanonical(new RevWalk(db), b.toByteArray());
+
+		assertEquals("t", t.getTaggerIdent().getName());
+		assertEquals("message", t.getShortMessage());
+		assertEquals("message\n", t.getFullMessage());
+	}
+
+	@Test
+	public void testParse_unsupportedEncoding() throws Exception {
+		ByteArrayOutputStream b = new ByteArrayOutputStream();
+		b.write("object 9788669ad918b6fcce64af8882fc9a81cb6aba67\n".getBytes(UTF_8));
+		b.write("type tree\n".getBytes(UTF_8));
+		b.write("tag v1.0\n".getBytes(UTF_8));
+		b.write("tagger t <t@example.com> 1218123387 +0700\n".getBytes(UTF_8));
+		b.write("encoding it_IT.UTF8\n".getBytes(UTF_8));
+		b.write("\n".getBytes(UTF_8));
+		b.write("message\n".getBytes(UTF_8));
+
+		RevTag t = new RevTag(id("9473095c4cb2f12aefe1db8a355fe3fafba42f67"));
+		t.parseCanonical(new RevWalk(db), b.toByteArray());
+
+		assertEquals("t", t.getTaggerIdent().getName());
+		assertEquals("message", t.getShortMessage());
+		assertEquals("message\n", t.getFullMessage());
+	}
+
+	@Test
 	public void testParse_NoMessage() throws Exception {
 		final String msg = "";
 		final RevTag c = create(msg);
@@ -424,10 +463,11 @@
 
 	@Test
 	public void testParse_PublicParseMethod() throws CorruptObjectException {
-		ObjectInserter.Formatter fmt = new ObjectInserter.Formatter();
 		TagBuilder src = new TagBuilder();
-		src.setObjectId(fmt.idFor(Constants.OBJ_TREE, new byte[] {}),
-				Constants.OBJ_TREE);
+		try (ObjectInserter.Formatter fmt = new ObjectInserter.Formatter()) {
+			src.setObjectId(fmt.idFor(Constants.OBJ_TREE, new byte[] {}),
+					Constants.OBJ_TREE);
+		}
 		src.setTagger(committer);
 		src.setTag("a.test");
 		src.setMessage("Test tag\n\nThis is a test.\n");
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkFilterTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkFilterTest.java
index 643ba26..8ab9728 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkFilterTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkFilterTest.java
@@ -250,14 +250,14 @@
 		final RevCommit b = commit(a);
 		tick(100);
 
-		Date since = getClock();
+		Date since = getDate();
 		final RevCommit c1 = commit(b);
 		tick(100);
 
 		final RevCommit c2 = commit(b);
 		tick(100);
 
-		Date until = getClock();
+		Date until = getDate();
 		final RevCommit d = commit(c1, c2);
 		tick(100);
 
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkTestCase.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkTestCase.java
index 881deb1..30586ee 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkTestCase.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkTestCase.java
@@ -70,8 +70,8 @@
 		return new RevWalk(db);
 	}
 
-	protected Date getClock() {
-		return util.getClock();
+	protected Date getDate() {
+		return util.getDate();
 	}
 
 	protected void tick(final int secDelta) {
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleAddTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleAddTest.java
index b13c4cd..a131e5e 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleAddTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleAddTest.java
@@ -119,36 +119,37 @@
 
 	@Test
 	public void addSubmodule() throws Exception {
-		Git git = new Git(db);
-		writeTrashFile("file.txt", "content");
-		git.add().addFilepattern("file.txt").call();
-		RevCommit commit = git.commit().setMessage("create file").call();
+		try (Git git = new Git(db)) {
+			writeTrashFile("file.txt", "content");
+			git.add().addFilepattern("file.txt").call();
+			RevCommit commit = git.commit().setMessage("create file").call();
 
-		SubmoduleAddCommand command = new SubmoduleAddCommand(db);
-		String path = "sub";
-		command.setPath(path);
-		String uri = db.getDirectory().toURI().toString();
-		command.setURI(uri);
-		Repository repo = command.call();
-		assertNotNull(repo);
-		ObjectId subCommit = repo.resolve(Constants.HEAD);
-		repo.close();
+			SubmoduleAddCommand command = new SubmoduleAddCommand(db);
+			String path = "sub";
+			command.setPath(path);
+			String uri = db.getDirectory().toURI().toString();
+			command.setURI(uri);
+			Repository repo = command.call();
+			assertNotNull(repo);
+			ObjectId subCommit = repo.resolve(Constants.HEAD);
+			repo.close();
 
-		SubmoduleWalk generator = SubmoduleWalk.forIndex(db);
-		assertTrue(generator.next());
-		assertEquals(path, generator.getPath());
-		assertEquals(commit, generator.getObjectId());
-		assertEquals(uri, generator.getModulesUrl());
-		assertEquals(path, generator.getModulesPath());
-		assertEquals(uri, generator.getConfigUrl());
-		Repository subModRepo = generator.getRepository();
-		assertNotNull(subModRepo);
-		assertEquals(subCommit, commit);
-		subModRepo.close();
+			SubmoduleWalk generator = SubmoduleWalk.forIndex(db);
+			assertTrue(generator.next());
+			assertEquals(path, generator.getPath());
+			assertEquals(commit, generator.getObjectId());
+			assertEquals(uri, generator.getModulesUrl());
+			assertEquals(path, generator.getModulesPath());
+			assertEquals(uri, generator.getConfigUrl());
+			Repository subModRepo = generator.getRepository();
+			assertNotNull(subModRepo);
+			assertEquals(subCommit, commit);
+			subModRepo.close();
 
-		Status status = Git.wrap(db).status().call();
-		assertTrue(status.getAdded().contains(Constants.DOT_GIT_MODULES));
-		assertTrue(status.getAdded().contains(path));
+			Status status = Git.wrap(db).status().call();
+			assertTrue(status.getAdded().contains(Constants.DOT_GIT_MODULES));
+			assertTrue(status.getAdded().contains(path));
+		}
 	}
 
 	@Test
@@ -182,45 +183,47 @@
 
 	@Test
 	public void addSubmoduleWithRelativeUri() throws Exception {
-		Git git = new Git(db);
-		writeTrashFile("file.txt", "content");
-		git.add().addFilepattern("file.txt").call();
-		RevCommit commit = git.commit().setMessage("create file").call();
+		try (Git git = new Git(db)) {
+			writeTrashFile("file.txt", "content");
+			git.add().addFilepattern("file.txt").call();
+			RevCommit commit = git.commit().setMessage("create file").call();
 
-		SubmoduleAddCommand command = new SubmoduleAddCommand(db);
-		String path = "sub";
-		String uri = "./.git";
-		command.setPath(path);
-		command.setURI(uri);
-		Repository repo = command.call();
-		assertNotNull(repo);
-		addRepoToClose(repo);
+			SubmoduleAddCommand command = new SubmoduleAddCommand(db);
+			String path = "sub";
+			String uri = "./.git";
+			command.setPath(path);
+			command.setURI(uri);
+			Repository repo = command.call();
+			assertNotNull(repo);
+			addRepoToClose(repo);
 
-		SubmoduleWalk generator = SubmoduleWalk.forIndex(db);
-		assertTrue(generator.next());
-		assertEquals(path, generator.getPath());
-		assertEquals(commit, generator.getObjectId());
-		assertEquals(uri, generator.getModulesUrl());
-		assertEquals(path, generator.getModulesPath());
-		String fullUri = db.getDirectory().getAbsolutePath();
-		if (File.separatorChar == '\\')
-			fullUri = fullUri.replace('\\', '/');
-		assertEquals(fullUri, generator.getConfigUrl());
-		Repository subModRepo = generator.getRepository();
-		assertNotNull(subModRepo);
-		assertEquals(
-				fullUri,
-				subModRepo
-						.getConfig()
-						.getString(ConfigConstants.CONFIG_REMOTE_SECTION,
-								Constants.DEFAULT_REMOTE_NAME,
-								ConfigConstants.CONFIG_KEY_URL));
-		subModRepo.close();
-		assertEquals(commit, repo.resolve(Constants.HEAD));
+			SubmoduleWalk generator = SubmoduleWalk.forIndex(db);
+			assertTrue(generator.next());
+			assertEquals(path, generator.getPath());
+			assertEquals(commit, generator.getObjectId());
+			assertEquals(uri, generator.getModulesUrl());
+			assertEquals(path, generator.getModulesPath());
+			String fullUri = db.getDirectory().getAbsolutePath();
+			if (File.separatorChar == '\\') {
+				fullUri = fullUri.replace('\\', '/');
+			}
+			assertEquals(fullUri, generator.getConfigUrl());
+			Repository subModRepo = generator.getRepository();
+			assertNotNull(subModRepo);
+			assertEquals(
+					fullUri,
+					subModRepo
+							.getConfig()
+							.getString(ConfigConstants.CONFIG_REMOTE_SECTION,
+									Constants.DEFAULT_REMOTE_NAME,
+									ConfigConstants.CONFIG_KEY_URL));
+			subModRepo.close();
+			assertEquals(commit, repo.resolve(Constants.HEAD));
 
-		Status status = Git.wrap(db).status().call();
-		assertTrue(status.getAdded().contains(Constants.DOT_GIT_MODULES));
-		assertTrue(status.getAdded().contains(path));
+			Status status = Git.wrap(db).status().call();
+			assertTrue(status.getAdded().contains(Constants.DOT_GIT_MODULES));
+			assertTrue(status.getAdded().contains(path));
+		}
 	}
 
 	@Test
@@ -237,31 +240,32 @@
 				path1, ConfigConstants.CONFIG_KEY_URL, url1);
 		modulesConfig.save();
 
-		Git git = new Git(db);
-		writeTrashFile("file.txt", "content");
-		git.add().addFilepattern("file.txt").call();
-		assertNotNull(git.commit().setMessage("create file").call());
+		try (Git git = new Git(db)) {
+			writeTrashFile("file.txt", "content");
+			git.add().addFilepattern("file.txt").call();
+			assertNotNull(git.commit().setMessage("create file").call());
 
-		SubmoduleAddCommand command = new SubmoduleAddCommand(db);
-		command.setPath(path2);
-		String url2 = db.getDirectory().toURI().toString();
-		command.setURI(url2);
-		Repository r = command.call();
-		assertNotNull(r);
-		addRepoToClose(r);
+			SubmoduleAddCommand command = new SubmoduleAddCommand(db);
+			command.setPath(path2);
+			String url2 = db.getDirectory().toURI().toString();
+			command.setURI(url2);
+			Repository r = command.call();
+			assertNotNull(r);
+			addRepoToClose(r);
 
-		modulesConfig.load();
-		assertEquals(path1, modulesConfig.getString(
-				ConfigConstants.CONFIG_SUBMODULE_SECTION, path1,
-				ConfigConstants.CONFIG_KEY_PATH));
-		assertEquals(url1, modulesConfig.getString(
-				ConfigConstants.CONFIG_SUBMODULE_SECTION, path1,
-				ConfigConstants.CONFIG_KEY_URL));
-		assertEquals(path2, modulesConfig.getString(
-				ConfigConstants.CONFIG_SUBMODULE_SECTION, path2,
-				ConfigConstants.CONFIG_KEY_PATH));
-		assertEquals(url2, modulesConfig.getString(
-				ConfigConstants.CONFIG_SUBMODULE_SECTION, path2,
-				ConfigConstants.CONFIG_KEY_URL));
+			modulesConfig.load();
+			assertEquals(path1, modulesConfig.getString(
+					ConfigConstants.CONFIG_SUBMODULE_SECTION, path1,
+					ConfigConstants.CONFIG_KEY_PATH));
+			assertEquals(url1, modulesConfig.getString(
+					ConfigConstants.CONFIG_SUBMODULE_SECTION, path1,
+					ConfigConstants.CONFIG_KEY_URL));
+			assertEquals(path2, modulesConfig.getString(
+					ConfigConstants.CONFIG_SUBMODULE_SECTION, path2,
+					ConfigConstants.CONFIG_KEY_PATH));
+			assertEquals(url2, modulesConfig.getString(
+					ConfigConstants.CONFIG_SUBMODULE_SECTION, path2,
+					ConfigConstants.CONFIG_KEY_URL));
+		}
 	}
 }
\ No newline at end of file
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/symlinks/SymlinksTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/symlinks/SymlinksTest.java
index 3ddc3de..274fa53 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/symlinks/SymlinksTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/symlinks/SymlinksTest.java
@@ -75,26 +75,27 @@
 	 */
 	@Test
 	public void fileModeTestFileThenSymlink() throws Exception {
-		Git git = new Git(db);
-		writeTrashFile("a", "Hello world a");
-		writeTrashFile("b", "Hello world b");
-		git.add().addFilepattern(".").call();
-		git.commit().setMessage("add files a & b").call();
-		Ref branch_1 = git.branchCreate().setName("branch_1").call();
-		git.rm().addFilepattern("a").call();
-		FileUtils.createSymLink(new File(db.getWorkTree(), "a"), "b");
-		git.add().addFilepattern("a").call();
-		git.commit().setMessage("add symlink a").call();
+		try (Git git = new Git(db)) {
+			writeTrashFile("a", "Hello world a");
+			writeTrashFile("b", "Hello world b");
+			git.add().addFilepattern(".").call();
+			git.commit().setMessage("add files a & b").call();
+			Ref branch_1 = git.branchCreate().setName("branch_1").call();
+			git.rm().addFilepattern("a").call();
+			FileUtils.createSymLink(new File(db.getWorkTree(), "a"), "b");
+			git.add().addFilepattern("a").call();
+			git.commit().setMessage("add symlink a").call();
 
-		FileEntry entry = new FileTreeIterator.FileEntry(new File(
-				db.getWorkTree(), "a"), db.getFS());
-		assertEquals(FileMode.SYMLINK, entry.getMode());
+			FileEntry entry = new FileTreeIterator.FileEntry(new File(
+					db.getWorkTree(), "a"), db.getFS());
+			assertEquals(FileMode.SYMLINK, entry.getMode());
 
-		git.checkout().setName(branch_1.getName()).call();
+			git.checkout().setName(branch_1.getName()).call();
 
-		entry = new FileTreeIterator.FileEntry(new File(db.getWorkTree(), "a"),
-				db.getFS());
-		assertEquals(FileMode.REGULAR_FILE, entry.getMode());
+			entry = new FileTreeIterator.FileEntry(new File(db.getWorkTree(), "a"),
+					db.getFS());
+			assertEquals(FileMode.REGULAR_FILE, entry.getMode());
+		}
 	}
 
 	/**
@@ -108,26 +109,27 @@
 	 */
 	@Test
 	public void fileModeTestSymlinkThenFile() throws Exception {
-		Git git = new Git(db);
-		writeTrashFile("b", "Hello world b");
-		FileUtils.createSymLink(new File(db.getWorkTree(), "a"), "b");
-		git.add().addFilepattern(".").call();
-		git.commit().setMessage("add file b & symlink a").call();
-		Ref branch_1 = git.branchCreate().setName("branch_1").call();
-		git.rm().addFilepattern("a").call();
-		writeTrashFile("a", "Hello world a");
-		git.add().addFilepattern("a").call();
-		git.commit().setMessage("add file a").call();
+		try (Git git = new Git(db)) {
+			writeTrashFile("b", "Hello world b");
+			FileUtils.createSymLink(new File(db.getWorkTree(), "a"), "b");
+			git.add().addFilepattern(".").call();
+			git.commit().setMessage("add file b & symlink a").call();
+			Ref branch_1 = git.branchCreate().setName("branch_1").call();
+			git.rm().addFilepattern("a").call();
+			writeTrashFile("a", "Hello world a");
+			git.add().addFilepattern("a").call();
+			git.commit().setMessage("add file a").call();
 
-		FileEntry entry = new FileTreeIterator.FileEntry(new File(
-				db.getWorkTree(), "a"), db.getFS());
-		assertEquals(FileMode.REGULAR_FILE, entry.getMode());
+			FileEntry entry = new FileTreeIterator.FileEntry(new File(
+					db.getWorkTree(), "a"), db.getFS());
+			assertEquals(FileMode.REGULAR_FILE, entry.getMode());
 
-		git.checkout().setName(branch_1.getName()).call();
+			git.checkout().setName(branch_1.getName()).call();
 
-		entry = new FileTreeIterator.FileEntry(new File(db.getWorkTree(), "a"),
-				db.getFS());
-		assertEquals(FileMode.SYMLINK, entry.getMode());
+			entry = new FileTreeIterator.FileEntry(new File(db.getWorkTree(), "a"),
+					db.getFS());
+			assertEquals(FileMode.SYMLINK, entry.getMode());
+		}
 	}
 
 	/**
@@ -141,27 +143,28 @@
 	 */
 	@Test
 	public void fileModeTestFolderThenSymlink() throws Exception {
-		Git git = new Git(db);
-		FileUtils.mkdirs(new File(db.getWorkTree(), "a"));
-		writeTrashFile("a/b", "Hello world b");
-		writeTrashFile("c", "Hello world c");
-		git.add().addFilepattern(".").call();
-		git.commit().setMessage("add folder a").call();
-		Ref branch_1 = git.branchCreate().setName("branch_1").call();
-		git.rm().addFilepattern("a").call();
-		FileUtils.createSymLink(new File(db.getWorkTree(), "a"), "c");
-		git.add().addFilepattern("a").call();
-		git.commit().setMessage("add symlink a").call();
+		try (Git git = new Git(db)) {
+			FileUtils.mkdirs(new File(db.getWorkTree(), "a"));
+			writeTrashFile("a/b", "Hello world b");
+			writeTrashFile("c", "Hello world c");
+			git.add().addFilepattern(".").call();
+			git.commit().setMessage("add folder a").call();
+			Ref branch_1 = git.branchCreate().setName("branch_1").call();
+			git.rm().addFilepattern("a").call();
+			FileUtils.createSymLink(new File(db.getWorkTree(), "a"), "c");
+			git.add().addFilepattern("a").call();
+			git.commit().setMessage("add symlink a").call();
 
-		FileEntry entry = new FileTreeIterator.FileEntry(new File(
-				db.getWorkTree(), "a"), db.getFS());
-		assertEquals(FileMode.SYMLINK, entry.getMode());
+			FileEntry entry = new FileTreeIterator.FileEntry(new File(
+					db.getWorkTree(), "a"), db.getFS());
+			assertEquals(FileMode.SYMLINK, entry.getMode());
 
-		git.checkout().setName(branch_1.getName()).call();
+			git.checkout().setName(branch_1.getName()).call();
 
-		entry = new FileTreeIterator.FileEntry(new File(db.getWorkTree(), "a"),
-				db.getFS());
-		assertEquals(FileMode.TREE, entry.getMode());
+			entry = new FileTreeIterator.FileEntry(new File(db.getWorkTree(), "a"),
+					db.getFS());
+			assertEquals(FileMode.TREE, entry.getMode());
+		}
 	}
 
 	/**
@@ -175,27 +178,28 @@
 	 */
 	@Test
 	public void fileModeTestSymlinkThenFolder() throws Exception {
-		Git git = new Git(db);
-		writeTrashFile("c", "Hello world c");
-		FileUtils.createSymLink(new File(db.getWorkTree(), "a"), "c");
-		git.add().addFilepattern(".").call();
-		git.commit().setMessage("add symlink a").call();
-		Ref branch_1 = git.branchCreate().setName("branch_1").call();
-		git.rm().addFilepattern("a").call();
-		FileUtils.mkdirs(new File(db.getWorkTree(), "a"));
-		writeTrashFile("a/b", "Hello world b");
-		git.add().addFilepattern("a").call();
-		git.commit().setMessage("add folder a").call();
+		try (Git git = new Git(db)) {
+			writeTrashFile("c", "Hello world c");
+			FileUtils.createSymLink(new File(db.getWorkTree(), "a"), "c");
+			git.add().addFilepattern(".").call();
+			git.commit().setMessage("add symlink a").call();
+			Ref branch_1 = git.branchCreate().setName("branch_1").call();
+			git.rm().addFilepattern("a").call();
+			FileUtils.mkdirs(new File(db.getWorkTree(), "a"));
+			writeTrashFile("a/b", "Hello world b");
+			git.add().addFilepattern("a").call();
+			git.commit().setMessage("add folder a").call();
 
-		FileEntry entry = new FileTreeIterator.FileEntry(new File(
-				db.getWorkTree(), "a"), db.getFS());
-		assertEquals(FileMode.TREE, entry.getMode());
+			FileEntry entry = new FileTreeIterator.FileEntry(new File(
+					db.getWorkTree(), "a"), db.getFS());
+			assertEquals(FileMode.TREE, entry.getMode());
 
-		git.checkout().setName(branch_1.getName()).call();
+			git.checkout().setName(branch_1.getName()).call();
 
-		entry = new FileTreeIterator.FileEntry(new File(db.getWorkTree(), "a"),
-				db.getFS());
-		assertEquals(FileMode.SYMLINK, entry.getMode());
+			entry = new FileTreeIterator.FileEntry(new File(db.getWorkTree(), "a"),
+					db.getFS());
+			assertEquals(FileMode.SYMLINK, entry.getMode());
+		}
 	}
 
 	/**
@@ -209,24 +213,25 @@
 	 */
 	@Test
 	public void fileModeTestMissingThenSymlink() throws Exception {
-		Git git = new Git(db);
-		writeTrashFile("b", "Hello world b");
-		git.add().addFilepattern(".").call();
-		RevCommit commit1 = git.commit().setMessage("add file b").call();
-		Ref branch_1 = git.branchCreate().setName("branch_1").call();
-		FileUtils.createSymLink(new File(db.getWorkTree(), "a"), "b");
-		git.add().addFilepattern("a").call();
-		RevCommit commit2 = git.commit().setMessage("add symlink a").call();
+		try (Git git = new Git(db);
+				TreeWalk tw = new TreeWalk(db);) {
+			writeTrashFile("b", "Hello world b");
+			git.add().addFilepattern(".").call();
+			RevCommit commit1 = git.commit().setMessage("add file b").call();
+			Ref branch_1 = git.branchCreate().setName("branch_1").call();
+			FileUtils.createSymLink(new File(db.getWorkTree(), "a"), "b");
+			git.add().addFilepattern("a").call();
+			RevCommit commit2 = git.commit().setMessage("add symlink a").call();
 
-		git.checkout().setName(branch_1.getName()).call();
+			git.checkout().setName(branch_1.getName()).call();
 
-		TreeWalk tw = new TreeWalk(db);
-		tw.addTree(commit1.getTree());
-		tw.addTree(commit2.getTree());
-		List<DiffEntry> scan = DiffEntry.scan(tw);
-		assertEquals(1, scan.size());
-		assertEquals(FileMode.SYMLINK, scan.get(0).getNewMode());
-		assertEquals(FileMode.MISSING, scan.get(0).getOldMode());
+			tw.addTree(commit1.getTree());
+			tw.addTree(commit2.getTree());
+			List<DiffEntry> scan = DiffEntry.scan(tw);
+			assertEquals(1, scan.size());
+			assertEquals(FileMode.SYMLINK, scan.get(0).getNewMode());
+			assertEquals(FileMode.MISSING, scan.get(0).getOldMode());
+		}
 	}
 
 	/**
@@ -240,97 +245,101 @@
 	 */
 	@Test
 	public void fileModeTestSymlinkThenMissing() throws Exception {
-		Git git = new Git(db);
-		writeTrashFile("b", "Hello world b");
-		FileUtils.createSymLink(new File(db.getWorkTree(), "a"), "b");
-		git.add().addFilepattern(".").call();
-		RevCommit commit1 = git.commit().setMessage("add file b & symlink a")
-				.call();
-		Ref branch_1 = git.branchCreate().setName("branch_1").call();
-		git.rm().addFilepattern("a").call();
-		RevCommit commit2 = git.commit().setMessage("delete symlink a").call();
+		try (Git git = new Git(db);
+				TreeWalk tw = new TreeWalk(db);) {
+			writeTrashFile("b", "Hello world b");
+			FileUtils.createSymLink(new File(db.getWorkTree(), "a"), "b");
+			git.add().addFilepattern(".").call();
+			RevCommit commit1 = git.commit().setMessage("add file b & symlink a")
+					.call();
+			Ref branch_1 = git.branchCreate().setName("branch_1").call();
+			git.rm().addFilepattern("a").call();
+			RevCommit commit2 = git.commit().setMessage("delete symlink a").call();
 
-		git.checkout().setName(branch_1.getName()).call();
+			git.checkout().setName(branch_1.getName()).call();
 
-		TreeWalk tw = new TreeWalk(db);
-		tw.addTree(commit1.getTree());
-		tw.addTree(commit2.getTree());
-		List<DiffEntry> scan = DiffEntry.scan(tw);
-		assertEquals(1, scan.size());
-		assertEquals(FileMode.MISSING, scan.get(0).getNewMode());
-		assertEquals(FileMode.SYMLINK, scan.get(0).getOldMode());
+			tw.addTree(commit1.getTree());
+			tw.addTree(commit2.getTree());
+			List<DiffEntry> scan = DiffEntry.scan(tw);
+			assertEquals(1, scan.size());
+			assertEquals(FileMode.MISSING, scan.get(0).getNewMode());
+			assertEquals(FileMode.SYMLINK, scan.get(0).getOldMode());
+		}
 	}
 
 	@Test
 	public void createSymlinkAfterTarget() throws Exception {
-		Git git = new Git(db);
-		writeTrashFile("a", "start");
-		git.add().addFilepattern("a").call();
-		RevCommit base = git.commit().setMessage("init").call();
-		writeTrashFile("target", "someData");
-		FileUtils.createSymLink(new File(db.getWorkTree(), "link"), "target");
-		git.add().addFilepattern("target").addFilepattern("link").call();
-		git.commit().setMessage("add target").call();
-		assertEquals(4, db.getWorkTree().list().length); // self-check
-		git.checkout().setName(base.name()).call();
-		assertEquals(2, db.getWorkTree().list().length); // self-check
-		git.checkout().setName("master").call();
-		assertEquals(4, db.getWorkTree().list().length);
-		String data = read(new File(db.getWorkTree(), "target"));
-		assertEquals(8, new File(db.getWorkTree(), "target").length());
-		assertEquals("someData", data);
-		data = read(new File(db.getWorkTree(), "link"));
-		assertEquals("target",
-				FileUtils.readSymLink(new File(db.getWorkTree(), "link")));
-		assertEquals("someData", data);
+		try (Git git = new Git(db)) {
+			writeTrashFile("a", "start");
+			git.add().addFilepattern("a").call();
+			RevCommit base = git.commit().setMessage("init").call();
+			writeTrashFile("target", "someData");
+			FileUtils.createSymLink(new File(db.getWorkTree(), "link"), "target");
+			git.add().addFilepattern("target").addFilepattern("link").call();
+			git.commit().setMessage("add target").call();
+			assertEquals(4, db.getWorkTree().list().length); // self-check
+			git.checkout().setName(base.name()).call();
+			assertEquals(2, db.getWorkTree().list().length); // self-check
+			git.checkout().setName("master").call();
+			assertEquals(4, db.getWorkTree().list().length);
+			String data = read(new File(db.getWorkTree(), "target"));
+			assertEquals(8, new File(db.getWorkTree(), "target").length());
+			assertEquals("someData", data);
+			data = read(new File(db.getWorkTree(), "link"));
+			assertEquals("target",
+					FileUtils.readSymLink(new File(db.getWorkTree(), "link")));
+			assertEquals("someData", data);
+		}
 	}
 
 	@Test
 	public void createFileSymlinkBeforeTarget() throws Exception {
-		Git git = new Git(db);
-		writeTrashFile("a", "start");
-		git.add().addFilepattern("a").call();
-		RevCommit base = git.commit().setMessage("init").call();
-		writeTrashFile("target", "someData");
-		FileUtils.createSymLink(new File(db.getWorkTree(), "tlink"), "target");
-		git.add().addFilepattern("target").addFilepattern("tlink").call();
-		git.commit().setMessage("add target").call();
-		assertEquals(4, db.getWorkTree().list().length); // self-check
-		git.checkout().setName(base.name()).call();
-		assertEquals(2, db.getWorkTree().list().length); // self-check
-		git.checkout().setName("master").call();
-		assertEquals(4, db.getWorkTree().list().length);
-		String data = read(new File(db.getWorkTree(), "target"));
-		assertEquals(8, new File(db.getWorkTree(), "target").length());
-		assertEquals("someData", data);
-		data = read(new File(db.getWorkTree(), "tlink"));
-		assertEquals("target",
-				FileUtils.readSymLink(new File(db.getWorkTree(), "tlink")));
-		assertEquals("someData", data);
+		try (Git git = new Git(db)) {
+			writeTrashFile("a", "start");
+			git.add().addFilepattern("a").call();
+			RevCommit base = git.commit().setMessage("init").call();
+			writeTrashFile("target", "someData");
+			FileUtils.createSymLink(new File(db.getWorkTree(), "tlink"), "target");
+			git.add().addFilepattern("target").addFilepattern("tlink").call();
+			git.commit().setMessage("add target").call();
+			assertEquals(4, db.getWorkTree().list().length); // self-check
+			git.checkout().setName(base.name()).call();
+			assertEquals(2, db.getWorkTree().list().length); // self-check
+			git.checkout().setName("master").call();
+			assertEquals(4, db.getWorkTree().list().length);
+			String data = read(new File(db.getWorkTree(), "target"));
+			assertEquals(8, new File(db.getWorkTree(), "target").length());
+			assertEquals("someData", data);
+			data = read(new File(db.getWorkTree(), "tlink"));
+			assertEquals("target",
+					FileUtils.readSymLink(new File(db.getWorkTree(), "tlink")));
+			assertEquals("someData", data);
+		}
 	}
 
 	@Test
 	public void createDirSymlinkBeforeTarget() throws Exception {
-		Git git = new Git(db);
-		writeTrashFile("a", "start");
-		git.add().addFilepattern("a").call();
-		RevCommit base = git.commit().setMessage("init").call();
-		FileUtils.createSymLink(new File(db.getWorkTree(), "link"), "target");
-		FileUtils.mkdir(new File(db.getWorkTree(), "target"));
-		writeTrashFile("target/file", "someData");
-		git.add().addFilepattern("target").addFilepattern("link").call();
-		git.commit().setMessage("add target").call();
-		assertEquals(4, db.getWorkTree().list().length); // self-check
-		git.checkout().setName(base.name()).call();
-		assertEquals(2, db.getWorkTree().list().length); // self-check
-		git.checkout().setName("master").call();
-		assertEquals(4, db.getWorkTree().list().length);
-		String data = read(new File(db.getWorkTree(), "target/file"));
-		assertEquals(8, new File(db.getWorkTree(), "target/file").length());
-		assertEquals("someData", data);
-		data = read(new File(db.getWorkTree(), "link/file"));
-		assertEquals("target",
-				FileUtils.readSymLink(new File(db.getWorkTree(), "link")));
-		assertEquals("someData", data);
+		try (Git git = new Git(db)) {
+			writeTrashFile("a", "start");
+			git.add().addFilepattern("a").call();
+			RevCommit base = git.commit().setMessage("init").call();
+			FileUtils.createSymLink(new File(db.getWorkTree(), "link"), "target");
+			FileUtils.mkdir(new File(db.getWorkTree(), "target"));
+			writeTrashFile("target/file", "someData");
+			git.add().addFilepattern("target").addFilepattern("link").call();
+			git.commit().setMessage("add target").call();
+			assertEquals(4, db.getWorkTree().list().length); // self-check
+			git.checkout().setName(base.name()).call();
+			assertEquals(2, db.getWorkTree().list().length); // self-check
+			git.checkout().setName("master").call();
+			assertEquals(4, db.getWorkTree().list().length);
+			String data = read(new File(db.getWorkTree(), "target/file"));
+			assertEquals(8, new File(db.getWorkTree(), "target/file").length());
+			assertEquals("someData", data);
+			data = read(new File(db.getWorkTree(), "link/file"));
+			assertEquals("target",
+					FileUtils.readSymLink(new File(db.getWorkTree(), "link")));
+			assertEquals("someData", data);
+		}
 	}
 }
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/AtomicPushTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/AtomicPushTest.java
new file mode 100644
index 0000000..c1e078d
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/AtomicPushTest.java
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2015, 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.assertSame;
+import static org.junit.Assert.fail;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.jgit.errors.TransportException;
+import org.eclipse.jgit.internal.JGitText;
+import org.eclipse.jgit.internal.storage.dfs.DfsRepositoryDescription;
+import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.NullProgressMonitor;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectInserter;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.transport.resolver.ReceivePackFactory;
+import org.eclipse.jgit.transport.resolver.ServiceNotAuthorizedException;
+import org.eclipse.jgit.transport.resolver.ServiceNotEnabledException;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+public class AtomicPushTest {
+	private URIish uri;
+	private TestProtocol<Object> testProtocol;
+	private Object ctx = new Object();
+	private InMemoryRepository server;
+	private InMemoryRepository client;
+	private ObjectId obj1;
+	private ObjectId obj2;
+
+	@Before
+	public void setUp() throws Exception {
+		server = newRepo("server");
+		client = newRepo("client");
+		testProtocol = new TestProtocol<>(
+				null,
+				new ReceivePackFactory<Object>() {
+					@Override
+					public ReceivePack create(Object req, Repository db)
+							throws ServiceNotEnabledException,
+							ServiceNotAuthorizedException {
+						return new ReceivePack(db);
+					}
+				});
+		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));
+	}
+
+	@Test
+	public void pushNonAtomic() throws Exception {
+		PushResult r;
+		server.setPerformsAtomicTransactions(false);
+		try (Transport tn = testProtocol.open(uri, client, "server")) {
+			tn.setPushAtomic(false);
+			r = tn.push(NullProgressMonitor.INSTANCE, commands());
+		}
+
+		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());
+	}
+
+	@Test
+	public void pushAtomicClientGivesUpEarly() throws Exception {
+		PushResult r;
+		try (Transport tn = testProtocol.open(uri, client, "server")) {
+			tn.setPushAtomic(true);
+			r = tn.push(NullProgressMonitor.INSTANCE, commands());
+		}
+
+		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());
+		assertEquals(JGitText.get().transactionAborted, one.getMessage());
+	}
+
+	@Test
+	public void pushAtomicDisabled() throws Exception {
+		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 */,
+				ObjectId.zeroId()));
+
+		server.setPerformsAtomicTransactions(false);
+		try (Transport tn = testProtocol.open(uri, client, "server")) {
+			tn.setPushAtomic(true);
+			tn.push(NullProgressMonitor.INSTANCE, cmds);
+			fail("did not throw TransportException");
+		} catch (TransportException e) {
+			assertEquals(
+					uri + ": " + JGitText.get().atomicPushNotSupported,
+					e.getMessage());
+		}
+	}
+
+	private List<RemoteRefUpdate> commands() 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 */,
+				obj1));
+		return cmds;
+	}
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/BundleWriterTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/BundleWriterTest.java
index ba89d2d..a83a993 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/BundleWriterTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/BundleWriterTest.java
@@ -126,24 +126,26 @@
 		assertNull(newRepo.resolve("refs/heads/a"));
 
 		// Next an incremental bundle
-		bundle = makeBundle("refs/heads/cc", db.resolve("c").name(),
-				new RevWalk(db).parseCommit(db.resolve("a").toObjectId()));
-		fetchResult = fetchFromBundle(newRepo, bundle);
-		advertisedRef = fetchResult.getAdvertisedRef("refs/heads/cc");
-		assertEquals(db.resolve("c").name(), advertisedRef.getObjectId().name());
-		assertEquals(db.resolve("c").name(), newRepo.resolve("refs/heads/cc")
-				.name());
-		assertNull(newRepo.resolve("refs/heads/c"));
-		assertNull(newRepo.resolve("refs/heads/a")); // still unknown
+		try (RevWalk rw = new RevWalk(db)) {
+			bundle = makeBundle("refs/heads/cc", db.resolve("c").name(),
+					rw.parseCommit(db.resolve("a").toObjectId()));
+			fetchResult = fetchFromBundle(newRepo, bundle);
+			advertisedRef = fetchResult.getAdvertisedRef("refs/heads/cc");
+			assertEquals(db.resolve("c").name(), advertisedRef.getObjectId().name());
+			assertEquals(db.resolve("c").name(), newRepo.resolve("refs/heads/cc")
+					.name());
+			assertNull(newRepo.resolve("refs/heads/c"));
+			assertNull(newRepo.resolve("refs/heads/a")); // still unknown
 
-		try {
-			// Check that we actually needed the first bundle
-			Repository newRepo2 = createBareRepository();
-			fetchResult = fetchFromBundle(newRepo2, bundle);
-			fail("We should not be able to fetch from bundle with prerequisites that are not fulfilled");
-		} catch (MissingBundlePrerequisiteException e) {
-			assertTrue(e.getMessage()
-					.indexOf(db.resolve("refs/heads/a").name()) >= 0);
+			try {
+				// Check that we actually needed the first bundle
+				Repository newRepo2 = createBareRepository();
+				fetchResult = fetchFromBundle(newRepo2, bundle);
+				fail("We should not be able to fetch from bundle with prerequisites that are not fulfilled");
+			} catch (MissingBundlePrerequisiteException e) {
+				assertTrue(e.getMessage()
+						.indexOf(db.resolve("refs/heads/a").name()) >= 0);
+			}
 		}
 	}
 
@@ -166,8 +168,10 @@
 		final ByteArrayInputStream in = new ByteArrayInputStream(bundle);
 		final RefSpec rs = new RefSpec("refs/heads/*:refs/heads/*");
 		final Set<RefSpec> refs = Collections.singleton(rs);
-		return new TransportBundleStream(newRepo, uri, in).fetch(
-				NullProgressMonitor.INSTANCE, refs);
+		try (TransportBundleStream transport = new TransportBundleStream(
+				newRepo, uri, in)) {
+			return transport.fetch(NullProgressMonitor.INSTANCE, refs);
+		}
 	}
 
 	private byte[] makeBundle(final String name,
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
new file mode 100644
index 0000000..a3b4134
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushConnectionTest.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2015, 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.eclipse.jgit.transport.RemoteRefUpdate.Status.REJECTED_OTHER_REASON;
+import static org.junit.Assert.assertEquals;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.eclipse.jgit.internal.storage.dfs.DfsRepositoryDescription;
+import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.NullProgressMonitor;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectInserter;
+import org.eclipse.jgit.lib.RefUpdate;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.transport.resolver.ReceivePackFactory;
+import org.eclipse.jgit.transport.resolver.ServiceNotAuthorizedException;
+import org.eclipse.jgit.transport.resolver.ServiceNotEnabledException;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+public class PushConnectionTest {
+	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 ObjectId obj3;
+	private String refName = "refs/tags/blob";
+
+	@Before
+	public void setUp() throws Exception {
+		server = newRepo("server");
+		client = newRepo("client");
+		testProtocol = new TestProtocol<>(
+				null,
+				new ReceivePackFactory<Object>() {
+					@Override
+					public ReceivePack create(Object req, Repository db)
+							throws ServiceNotEnabledException,
+							ServiceNotAuthorizedException {
+						return new ReceivePack(db);
+					}
+				});
+		uri = testProtocol.register(ctx, server);
+
+		try (ObjectInserter ins = server.newObjectInserter()) {
+			obj1 = ins.insert(Constants.OBJ_BLOB, Constants.encode("test"));
+			obj3 = ins.insert(Constants.OBJ_BLOB, Constants.encode("not"));
+			ins.flush();
+
+			RefUpdate u = server.updateRef(refName);
+			u.setNewObjectId(obj1);
+			assertEquals(RefUpdate.Result.NEW, u.update());
+		}
+
+		try (ObjectInserter ins = client.newObjectInserter()) {
+			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));
+	}
+
+	@Test
+	public void testWrongOldIdDoesNotReplace() throws IOException {
+		RemoteRefUpdate rru = new RemoteRefUpdate(null, null, obj2, refName,
+				false, null, obj3);
+
+		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();
+		}
+
+		assertEquals(REJECTED_OTHER_REASON, rru.getStatus());
+		assertEquals("invalid old id sent", rru.getMessage());
+	}
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/ReceivePackAdvertiseRefsHookTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/ReceivePackAdvertiseRefsHookTest.java
index aa5914f..94bc383 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/ReceivePackAdvertiseRefsHookTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/ReceivePackAdvertiseRefsHookTest.java
@@ -116,12 +116,9 @@
 
 		// Clone from dst into src
 		//
-		Transport t = Transport.open(src, uriOf(dst));
-		try {
+		try (Transport t = Transport.open(src, uriOf(dst))) {
 			t.fetch(PM, Collections.singleton(new RefSpec("+refs/*:refs/*")));
 			assertEquals(B, src.resolve(R_MASTER));
-		} finally {
-			t.close();
 		}
 
 		// Now put private stuff into dst.
@@ -144,7 +141,8 @@
 	@Test
 	public void testFilterHidesPrivate() throws Exception {
 		Map<String, Ref> refs;
-		TransportLocal t = new TransportLocal(src, uriOf(dst), dst.getDirectory()) {
+		try (TransportLocal t = new TransportLocal(src, uriOf(dst),
+				dst.getDirectory()) {
 			@Override
 			ReceivePack createReceivePack(final Repository db) {
 				db.close();
@@ -154,16 +152,10 @@
 				rp.setAdvertiseRefsHook(new HidePrivateHook());
 				return rp;
 			}
-		};
-		try {
-			PushConnection c = t.openPush();
-			try {
+		}) {
+			try (PushConnection c = t.openPush()) {
 				refs = c.getRefsMap();
-			} finally {
-				c.close();
 			}
-		} finally {
-			t.close();
 		}
 
 		assertNotNull(refs);
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 3f5fcbb..4f83350 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
@@ -341,6 +341,41 @@
 	}
 
 	@Test
+	public void testWildcardAfterText1() {
+		RefSpec a = new RefSpec("refs/heads/*/for-linus:refs/remotes/mine/*-blah");
+		assertTrue(a.isWildcard());
+		assertTrue(a.matchDestination("refs/remotes/mine/x-blah"));
+		assertTrue(a.matchDestination("refs/remotes/mine/foo-blah"));
+		assertTrue(a.matchDestination("refs/remotes/mine/foo/x-blah"));
+		assertFalse(a.matchDestination("refs/remotes/origin/foo/x-blah"));
+
+		RefSpec b = a.expandFromSource("refs/heads/foo/for-linus");
+		assertEquals("refs/remotes/mine/foo-blah", b.getDestination());
+		RefSpec c = a.expandFromDestination("refs/remotes/mine/foo-blah");
+		assertEquals("refs/heads/foo/for-linus", c.getSource());
+	}
+
+	@Test
+	public void testWildcardAfterText2() {
+		RefSpec a = new RefSpec("refs/heads*/for-linus:refs/remotes/mine/*");
+		assertTrue(a.isWildcard());
+		assertTrue(a.matchSource("refs/headsx/for-linus"));
+		assertTrue(a.matchSource("refs/headsfoo/for-linus"));
+		assertTrue(a.matchSource("refs/headsx/foo/for-linus"));
+		assertFalse(a.matchSource("refs/headx/for-linus"));
+
+		RefSpec b = a.expandFromSource("refs/headsx/for-linus");
+		assertEquals("refs/remotes/mine/x", b.getDestination());
+		RefSpec c = a.expandFromDestination("refs/remotes/mine/x");
+		assertEquals("refs/headsx/for-linus", c.getSource());
+
+		RefSpec d = a.expandFromSource("refs/headsx/foo/for-linus");
+		assertEquals("refs/remotes/mine/x/foo", d.getDestination());
+		RefSpec e = a.expandFromDestination("refs/remotes/mine/x/foo");
+		assertEquals("refs/headsx/foo/for-linus", e.getSource());
+	}
+
+	@Test
 	public void testWildcardMirror() {
 		RefSpec a = new RefSpec("*:*");
 		assertTrue(a.isWildcard());
@@ -404,21 +439,6 @@
 	}
 
 	@Test(expected = IllegalArgumentException.class)
-	public void invalidWhenWildcardAfterText() {
-		assertNotNull(new RefSpec("refs/heads/wrong*:refs/heads/right/*"));
-	}
-
-	@Test(expected = IllegalArgumentException.class)
-	public void invalidWhenWildcardBeforeText() {
-		assertNotNull(new RefSpec("*wrong:right/*"));
-	}
-
-	@Test(expected = IllegalArgumentException.class)
-	public void invalidWhenWildcardBeforeTextAtEnd() {
-		assertNotNull(new RefSpec("refs/heads/*wrong:right/*"));
-	}
-
-	@Test(expected = IllegalArgumentException.class)
 	public void invalidSourceDoubleSlashes() {
 		assertNotNull(new RefSpec("refs/heads//wrong"));
 	}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/SideBandOutputStreamTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/SideBandOutputStreamTest.java
index e9ae190..d2c3a0b 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/SideBandOutputStreamTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/SideBandOutputStreamTest.java
@@ -195,24 +195,31 @@
 		assertEquals(1, flushCnt[0]);
 	}
 
+	private void createSideBandOutputStream(int chan, int sz, OutputStream os)
+			throws Exception {
+		try (SideBandOutputStream s = new SideBandOutputStream(chan, sz, os)) {
+			// Unused
+		}
+	}
+
 	@Test
-	public void testConstructor_RejectsBadChannel() {
+	public void testConstructor_RejectsBadChannel() throws Exception {
 		try {
-			new SideBandOutputStream(-1, MAX_BUF, rawOut);
+			createSideBandOutputStream(-1, MAX_BUF, rawOut);
 			fail("Accepted -1 channel number");
 		} catch (IllegalArgumentException e) {
 			assertEquals("channel -1 must be in range [1, 255]", e.getMessage());
 		}
 
 		try {
-			new SideBandOutputStream(0, MAX_BUF, rawOut);
+			createSideBandOutputStream(0, MAX_BUF, rawOut);
 			fail("Accepted 0 channel number");
 		} catch (IllegalArgumentException e) {
 			assertEquals("channel 0 must be in range [1, 255]", e.getMessage());
 		}
 
 		try {
-			new SideBandOutputStream(256, MAX_BUF, rawOut);
+			createSideBandOutputStream(256, MAX_BUF, rawOut);
 			fail("Accepted 256 channel number");
 		} catch (IllegalArgumentException e) {
 			assertEquals("channel 256 must be in range [1, 255]", e
@@ -221,30 +228,30 @@
 	}
 
 	@Test
-	public void testConstructor_RejectsBadBufferSize() {
+	public void testConstructor_RejectsBadBufferSize() throws Exception {
 		try {
-			new SideBandOutputStream(CH_DATA, -1, rawOut);
+			createSideBandOutputStream(CH_DATA, -1, rawOut);
 			fail("Accepted -1 for buffer size");
 		} catch (IllegalArgumentException e) {
 			assertEquals("packet size -1 must be >= 5", e.getMessage());
 		}
 
 		try {
-			new SideBandOutputStream(CH_DATA, 0, rawOut);
+			createSideBandOutputStream(CH_DATA, 0, rawOut);
 			fail("Accepted 0 for buffer size");
 		} catch (IllegalArgumentException e) {
 			assertEquals("packet size 0 must be >= 5", e.getMessage());
 		}
 
 		try {
-			new SideBandOutputStream(CH_DATA, 1, rawOut);
+			createSideBandOutputStream(CH_DATA, 1, rawOut);
 			fail("Accepted 1 for buffer size");
 		} catch (IllegalArgumentException e) {
 			assertEquals("packet size 1 must be >= 5", e.getMessage());
 		}
 
 		try {
-			new SideBandOutputStream(CH_DATA, Integer.MAX_VALUE, rawOut);
+			createSideBandOutputStream(CH_DATA, Integer.MAX_VALUE, rawOut);
 			fail("Accepted " + Integer.MAX_VALUE + " for buffer size");
 		} catch (IllegalArgumentException e) {
 			assertEquals(MessageFormat.format(
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/TestProtocolTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/TestProtocolTest.java
index 7f86526..31b6418 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/TestProtocolTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/TestProtocolTest.java
@@ -125,7 +125,7 @@
 					.setRefSpecs(HEADS)
 					.call();
 			assertEquals(master,
-					local.getRepository().getRef("master").getObjectId());
+					local.getRepository().exactRef("refs/heads/master").getObjectId());
 		}
 	}
 
@@ -142,7 +142,7 @@
 					.setRefSpecs(HEADS)
 					.call();
 			assertEquals(master,
-					remote.getRepository().getRef("master").getObjectId());
+					remote.getRepository().exactRef("refs/heads/master").getObjectId());
 		}
 	}
 
@@ -177,7 +177,7 @@
 				// Expected.
 			}
 			assertEquals(1, rejected.get());
-			assertNull(local.getRepository().getRef("master"));
+			assertNull(local.getRepository().exactRef("refs/heads/master"));
 
 			git.fetch()
 					.setRemote(user2Uri.toString())
@@ -185,7 +185,7 @@
 					.call();
 			assertEquals(1, rejected.get());
 			assertEquals(master,
-					local.getRepository().getRef("master").getObjectId());
+					local.getRepository().exactRef("refs/heads/master").getObjectId());
 		}
 	}
 
@@ -222,7 +222,7 @@
 						JGitText.get().pushNotPermitted));
 			}
 			assertEquals(1, rejected.get());
-			assertNull(remote.getRepository().getRef("master"));
+			assertNull(remote.getRepository().exactRef("refs/heads/master"));
 
 			git.push()
 					.setRemote(user2Uri.toString())
@@ -230,7 +230,7 @@
 					.call();
 			assertEquals(1, rejected.get());
 			assertEquals(master,
-					remote.getRepository().getRef("master").getObjectId());
+					remote.getRepository().exactRef("refs/heads/master").getObjectId());
 		}
 	}
 
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/TransportTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/TransportTest.java
index 55e1e44..5519f61 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/TransportTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/TransportTest.java
@@ -61,13 +61,10 @@
 import org.eclipse.jgit.lib.Ref;
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.test.resources.SampleDataRepositoryTestCase;
-import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 
 public class TransportTest extends SampleDataRepositoryTestCase {
-	private Transport transport;
-
 	private RemoteConfig remoteConfig;
 
 	@Override
@@ -77,17 +74,6 @@
 		final Config config = db.getConfig();
 		remoteConfig = new RemoteConfig(config, "test");
 		remoteConfig.addURI(new URIish("http://everyones.loves.git/u/2"));
-		transport = null;
-	}
-
-	@Override
-	@After
-	public void tearDown() throws Exception {
-		if (transport != null) {
-			transport.close();
-			transport = null;
-		}
-		super.tearDown();
 	}
 
 	/**
@@ -99,10 +85,11 @@
 	@Test
 	public void testFindRemoteRefUpdatesNoWildcardNoTracking()
 			throws IOException {
-		transport = Transport.open(db, remoteConfig);
-		final Collection<RemoteRefUpdate> result = transport
-				.findRemoteRefUpdatesFor(Collections.nCopies(1, new RefSpec(
-						"refs/heads/master:refs/heads/x")));
+		Collection<RemoteRefUpdate> result;
+		try (Transport transport = Transport.open(db, remoteConfig)) {
+			result = transport.findRemoteRefUpdatesFor(Collections.nCopies(1,
+					new RefSpec("refs/heads/master:refs/heads/x")));
+		}
 
 		assertEquals(1, result.size());
 		final RemoteRefUpdate rru = result.iterator().next();
@@ -122,10 +109,11 @@
 	@Test
 	public void testFindRemoteRefUpdatesNoWildcardNoDestination()
 			throws IOException {
-		transport = Transport.open(db, remoteConfig);
-		final Collection<RemoteRefUpdate> result = transport
-				.findRemoteRefUpdatesFor(Collections.nCopies(1, new RefSpec(
-						"+refs/heads/master")));
+		Collection<RemoteRefUpdate> result;
+		try (Transport transport = Transport.open(db, remoteConfig)) {
+			result = transport.findRemoteRefUpdatesFor(
+					Collections.nCopies(1, new RefSpec("+refs/heads/master")));
+		}
 
 		assertEquals(1, result.size());
 		final RemoteRefUpdate rru = result.iterator().next();
@@ -143,10 +131,11 @@
 	 */
 	@Test
 	public void testFindRemoteRefUpdatesWildcardNoTracking() throws IOException {
-		transport = Transport.open(db, remoteConfig);
-		final Collection<RemoteRefUpdate> result = transport
-				.findRemoteRefUpdatesFor(Collections.nCopies(1, new RefSpec(
-						"+refs/heads/*:refs/heads/test/*")));
+		Collection<RemoteRefUpdate> result;
+		try (Transport transport = Transport.open(db, remoteConfig)) {
+			result = transport.findRemoteRefUpdatesFor(Collections.nCopies(1,
+					new RefSpec("+refs/heads/*:refs/heads/test/*")));
+		}
 
 		assertEquals(12, result.size());
 		boolean foundA = false;
@@ -171,12 +160,14 @@
 	 */
 	@Test
 	public void testFindRemoteRefUpdatesTwoRefSpecs() throws IOException {
-		transport = Transport.open(db, remoteConfig);
 		final RefSpec specA = new RefSpec("+refs/heads/a:refs/heads/b");
 		final RefSpec specC = new RefSpec("+refs/heads/c:refs/heads/d");
 		final Collection<RefSpec> specs = Arrays.asList(specA, specC);
-		final Collection<RemoteRefUpdate> result = transport
-				.findRemoteRefUpdatesFor(specs);
+
+		Collection<RemoteRefUpdate> result;
+		try (Transport transport = Transport.open(db, remoteConfig)) {
+			result = transport.findRemoteRefUpdatesFor(specs);
+		}
 
 		assertEquals(2, result.size());
 		boolean foundA = false;
@@ -202,10 +193,12 @@
 	public void testFindRemoteRefUpdatesTrackingRef() throws IOException {
 		remoteConfig.addFetchRefSpec(new RefSpec(
 				"refs/heads/*:refs/remotes/test/*"));
-		transport = Transport.open(db, remoteConfig);
-		final Collection<RemoteRefUpdate> result = transport
-				.findRemoteRefUpdatesFor(Collections.nCopies(1, new RefSpec(
-						"+refs/heads/a:refs/heads/a")));
+
+		Collection<RemoteRefUpdate> result;
+		try (Transport transport = Transport.open(db, remoteConfig)) {
+			result = transport.findRemoteRefUpdatesFor(Collections.nCopies(1,
+					new RefSpec("+refs/heads/a:refs/heads/a")));
+		}
 
 		assertEquals(1, result.size());
 		final TrackingRefUpdate tru = result.iterator().next()
@@ -225,20 +218,18 @@
 		config.addURI(new URIish("../" + otherDir));
 
 		// Should not throw NoRemoteRepositoryException
-		transport = Transport.open(db, config);
+		Transport.open(db, config).close();
 	}
 
 	@Test
 	public void testLocalTransportFetchWithoutLocalRepository()
 			throws Exception {
 		URIish uri = new URIish("file://" + db.getWorkTree().getAbsolutePath());
-		transport = Transport.open(uri);
-		FetchConnection fetchConnection = transport.openFetch();
-		try {
-			Ref head = fetchConnection.getRef(Constants.HEAD);
-			assertNotNull(head);
-		} finally {
-			fetchConnection.close();
+		try (Transport transport = Transport.open(uri)) {
+			try (FetchConnection fetchConnection = transport.openFetch()) {
+				Ref head = fetchConnection.getRef(Constants.HEAD);
+				assertNotNull(head);
+			}
 		}
 	}
 
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/URIishTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/URIishTest.java
index 745c322..e55d373 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/URIishTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/URIishTest.java
@@ -51,7 +51,6 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
 
 import java.io.File;
 import java.io.IOException;
@@ -64,24 +63,16 @@
 
 	private static final String GIT_SCHEME = "git://";
 
-	@Test
+	@SuppressWarnings("unused")
+	@Test(expected = URISyntaxException.class)
 	public void shouldRaiseErrorOnEmptyURI() throws Exception {
-		try {
-			new URIish("");
-			fail("expecting an exception");
-		} catch (URISyntaxException e) {
-			// expected
-		}
+		new URIish("");
 	}
 
-	@Test
+	@SuppressWarnings("unused")
+	@Test(expected = URISyntaxException.class)
 	public void shouldRaiseErrorOnNullURI() throws Exception {
-		try {
-			new URIish((String) null);
-			fail("expecting an exception");
-		} catch (URISyntaxException e) {
-			// expected
-		}
+		new URIish((String) null);
 	}
 
 	@Test
@@ -469,6 +460,48 @@
 	}
 
 	@Test
+	public void testSshProtoWithEmailUserAndPort() throws Exception {
+		final String str = "ssh://user.name@email.com@example.com:33/some/p ath";
+		URIish u = new URIish(str);
+		assertEquals("ssh", u.getScheme());
+		assertTrue(u.isRemote());
+		assertEquals("/some/p ath", u.getRawPath());
+		assertEquals("/some/p ath", u.getPath());
+		assertEquals("example.com", u.getHost());
+		assertEquals("user.name@email.com", u.getUser());
+		assertNull(u.getPass());
+		assertEquals(33, u.getPort());
+		assertEquals("ssh://user.name%40email.com@example.com:33/some/p ath",
+				u.toPrivateString());
+		assertEquals("ssh://user.name%40email.com@example.com:33/some/p%20ath",
+				u.toPrivateASCIIString());
+		assertEquals(u.setPass(null).toPrivateString(), u.toString());
+		assertEquals(u.setPass(null).toPrivateASCIIString(), u.toASCIIString());
+		assertEquals(u, new URIish(str));
+	}
+
+	@Test
+	public void testSshProtoWithEmailUserPassAndPort() throws Exception {
+		final String str = "ssh://user.name@email.com:pass@wor:d@example.com:33/some/p ath";
+		URIish u = new URIish(str);
+		assertEquals("ssh", u.getScheme());
+		assertTrue(u.isRemote());
+		assertEquals("/some/p ath", u.getRawPath());
+		assertEquals("/some/p ath", u.getPath());
+		assertEquals("example.com", u.getHost());
+		assertEquals("user.name@email.com", u.getUser());
+		assertEquals("pass@wor:d", u.getPass());
+		assertEquals(33, u.getPort());
+		assertEquals("ssh://user.name%40email.com:pass%40wor%3ad@example.com:33/some/p ath",
+				u.toPrivateString());
+		assertEquals("ssh://user.name%40email.com:pass%40wor%3ad@example.com:33/some/p%20ath",
+				u.toPrivateASCIIString());
+		assertEquals(u.setPass(null).toPrivateString(), u.toString());
+		assertEquals(u.setPass(null).toPrivateASCIIString(), u.toASCIIString());
+		assertEquals(u, new URIish(str));
+	}
+
+	@Test
 	public void testSshProtoWithADUserPassAndPort() throws Exception {
 		final String str = "ssh://DOMAIN\\user:pass@example.com:33/some/p ath";
 		URIish u = new URIish(str);
@@ -592,34 +625,19 @@
 		assertEquals(u, new URIish(str));
 	}
 
-	@Test
+	@Test(expected = IllegalArgumentException.class)
 	public void testGetNullHumanishName() {
-		try {
-			new URIish().getHumanishName();
-			fail("path must be not null");
-		} catch (IllegalArgumentException e) {
-			// expected
-		}
+		new URIish().getHumanishName();
 	}
 
-	@Test
+	@Test(expected = IllegalArgumentException.class)
 	public void testGetEmptyHumanishName() throws URISyntaxException {
-		try {
-			new URIish(GIT_SCHEME).getHumanishName();
-			fail("empty path is useless");
-		} catch (IllegalArgumentException e) {
-			// expected
-		}
+		new URIish(GIT_SCHEME).getHumanishName();
 	}
 
-	@Test
+	@Test(expected = IllegalArgumentException.class)
 	public void testGetAbsEmptyHumanishName() {
-		try {
-			new URIish().getHumanishName();
-			fail("empty path is useless");
-		} catch (IllegalArgumentException e) {
-			// expected
-		}
+		new URIish().getHumanishName();
 	}
 
 	@Test
@@ -928,4 +946,19 @@
 			}
 		}
 	}
+
+	@Test
+	public void testStringConstructor() throws Exception {
+		String str = "http://example.com/";
+		URIish u = new URIish(str);
+		assertEquals("example.com", u.getHost());
+		assertEquals("/", u.getPath());
+		assertEquals(str, u.toString());
+
+		str = "http://example.com";
+		u = new URIish(str);
+		assertEquals("example.com", u.getHost());
+		assertEquals("", u.getPath());
+		assertEquals(str, u.toString());
+	}
 }
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/WalkEncryptionTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/WalkEncryptionTest.java
new file mode 100644
index 0000000..ac2bfd1
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/WalkEncryptionTest.java
@@ -0,0 +1,1318 @@
+/*
+ * Copyright (C) 2015, Andrei Pozolotin.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ *   copyright notice, this list of conditions and the following
+ *   disclaimer in the documentation and/or other materials provided
+ *   with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ *   names of its contributors may be used to endorse or promote
+ *   products derived from this software without specific prior
+ *   written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.transport;
+
+import static org.eclipse.jgit.transport.WalkEncryptionTest.Util.UTF_8;
+import static org.eclipse.jgit.transport.WalkEncryptionTest.Util.cryptoCipherListPBE;
+import static org.eclipse.jgit.transport.WalkEncryptionTest.Util.cryptoCipherListTrans;
+import static org.eclipse.jgit.transport.WalkEncryptionTest.Util.folderDelete;
+import static org.eclipse.jgit.transport.WalkEncryptionTest.Util.permitLongTests;
+import static org.eclipse.jgit.transport.WalkEncryptionTest.Util.policySetup;
+import static org.eclipse.jgit.transport.WalkEncryptionTest.Util.product;
+import static org.eclipse.jgit.transport.WalkEncryptionTest.Util.proxySetup;
+import static org.eclipse.jgit.transport.WalkEncryptionTest.Util.publicAddress;
+import static org.eclipse.jgit.transport.WalkEncryptionTest.Util.reportPolicy;
+import static org.eclipse.jgit.transport.WalkEncryptionTest.Util.securityProviderName;
+import static org.eclipse.jgit.transport.WalkEncryptionTest.Util.textWrite;
+import static org.eclipse.jgit.transport.WalkEncryptionTest.Util.transferStream;
+import static org.eclipse.jgit.transport.WalkEncryptionTest.Util.verifyFileContent;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.net.SocketTimeoutException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.net.UnknownHostException;
+import java.nio.charset.Charset;
+import java.nio.file.Files;
+import java.security.GeneralSecurityException;
+import java.security.Provider;
+import java.security.Security;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Properties;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.UUID;
+
+import javax.crypto.Cipher;
+import javax.crypto.SecretKeyFactory;
+
+import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.lib.StoredConfig;
+import org.eclipse.jgit.test.resources.SampleDataRepositoryTestCase;
+import org.eclipse.jgit.util.FileUtils;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.MethodSorters;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+import org.junit.runners.Suite;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Amazon S3 encryption pipeline test.
+ *
+ * See {@link AmazonS3} {@link WalkEncryption}
+ *
+ * Note: CI server must provide amazon credentials (access key, secret key,
+ * bucket name) via one of methods available in {@link Names}.
+ *
+ * Note: long running tests are activated by Maven profile "test.long". There is
+ * also a separate Eclipse m2e launcher for that. See 'pom.xml' and
+ * 'WalkEncryptionTest.launch'.
+ */
+@RunWith(Suite.class)
+@Suite.SuiteClasses({ //
+		WalkEncryptionTest.Required.class, //
+		WalkEncryptionTest.MinimalSet.class, //
+		WalkEncryptionTest.TestablePBE.class, //
+		WalkEncryptionTest.TestableTransformation.class, //
+})
+public class WalkEncryptionTest {
+
+	/**
+	 * Logger setup: ${project_loc}/tst-rsrc/log4j.properties
+	 */
+	static final Logger logger = LoggerFactory.getLogger(WalkEncryptionTest.class);
+
+	/**
+	 * Property names used in test session.
+	 */
+	interface Names {
+
+		// Names of discovered test properties.
+
+		String TEST_BUCKET = "test.bucket";
+
+		// Names of test environment variables for CI.
+
+		String ENV_ACCESS_KEY = "JGIT_S3_ACCESS_KEY";
+
+		String ENV_SECRET_KEY = "JGIT_S3_SECRET_KEY";
+
+		String ENV_BUCKET_NAME = "JGIT_S3_BUCKET_NAME";
+
+		// Name of test environment variable file path for CI.
+
+		String ENV_CONFIG_FILE = "JGIT_S3_CONFIG_FILE";
+
+		// Names of test system properties for CI.
+
+		String SYS_ACCESS_KEY = "jgit.s3.access.key";
+
+		String SYS_SECRET_KEY = "jgit.s3.secret.key";
+
+		String SYS_BUCKET_NAME = "jgit.s3.bucket.name";
+
+		// Name of test system property file path for CI.
+		String SYS_CONFIG_FILE = "jgit.s3.config.file";
+
+		// Hard coded name of test properties file for CI.
+		// File format follows AmazonS3.Keys:
+		// #
+		// # Required entries:
+		// #
+		// accesskey = your-amazon-access-key # default AmazonS3.Keys
+		// secretkey = your-amazon-secret-key # default AmazonS3.Keys
+		// test.bucket = your-bucket-for-testing # custom name, for this test
+		String CONFIG_FILE = "jgit-s3-config.properties";
+
+		// Test properties file in [user home] of CI.
+		String HOME_CONFIG_FILE = System.getProperty("user.home")
+				+ File.separator + CONFIG_FILE;
+
+		// Test properties file in [project work directory] of CI.
+		String WORK_CONFIG_FILE = System.getProperty("user.dir")
+				+ File.separator + CONFIG_FILE;
+
+		// Test properties file in [project test source directory] of CI.
+		String TEST_CONFIG_FILE = System.getProperty("user.dir")
+				+ File.separator + "tst-rsrc" + File.separator + CONFIG_FILE;
+
+	}
+
+	/**
+	 * Find test properties from various sources in order of priority.
+	 */
+	static class Props implements WalkEncryptionTest.Names, AmazonS3.Keys {
+
+		static boolean haveEnvVar(String name) {
+			return System.getenv(name) != null;
+		}
+
+		static boolean haveEnvVarFile(String name) {
+			return haveEnvVar(name) && new File(name).exists();
+		}
+
+		static boolean haveSysProp(String name) {
+			return System.getProperty(name) != null;
+		}
+
+		static boolean haveSysPropFile(String name) {
+			return haveSysProp(name) && new File(name).exists();
+		}
+
+		static void loadEnvVar(String source, String target, Properties props) {
+			props.put(target, System.getenv(source));
+		}
+
+		static void loadSysProp(String source, String target,
+				Properties props) {
+			props.put(target, System.getProperty(source));
+		}
+
+		static boolean haveProp(String name, Properties props) {
+			return props.containsKey(name);
+		}
+
+		static boolean checkTestProps(Properties props) {
+			return haveProp(ACCESS_KEY, props) && haveProp(SECRET_KEY, props)
+					&& haveProp(TEST_BUCKET, props);
+		}
+
+		static Properties fromEnvVars() {
+			if (haveEnvVar(ENV_ACCESS_KEY) && haveEnvVar(ENV_SECRET_KEY)
+					&& haveEnvVar(ENV_BUCKET_NAME)) {
+				Properties props = new Properties();
+				loadEnvVar(ENV_ACCESS_KEY, ACCESS_KEY, props);
+				loadEnvVar(ENV_SECRET_KEY, SECRET_KEY, props);
+				loadEnvVar(ENV_BUCKET_NAME, TEST_BUCKET, props);
+				return props;
+			} else {
+				return null;
+			}
+		}
+
+		static Properties fromEnvFile() throws Exception {
+			if (haveEnvVarFile(ENV_CONFIG_FILE)) {
+				Properties props = new Properties();
+				props.load(new FileInputStream(ENV_CONFIG_FILE));
+				if (checkTestProps(props)) {
+					return props;
+				} else {
+					throw new Error("Environment config file is incomplete.");
+				}
+			} else {
+				return null;
+			}
+		}
+
+		static Properties fromSysProps() {
+			if (haveSysProp(SYS_ACCESS_KEY) && haveSysProp(SYS_SECRET_KEY)
+					&& haveSysProp(SYS_BUCKET_NAME)) {
+				Properties props = new Properties();
+				loadSysProp(SYS_ACCESS_KEY, ACCESS_KEY, props);
+				loadSysProp(SYS_SECRET_KEY, SECRET_KEY, props);
+				loadSysProp(SYS_BUCKET_NAME, TEST_BUCKET, props);
+				return props;
+			} else {
+				return null;
+			}
+		}
+
+		static Properties fromSysFile() throws Exception {
+			if (haveSysPropFile(SYS_CONFIG_FILE)) {
+				Properties props = new Properties();
+				props.load(new FileInputStream(SYS_CONFIG_FILE));
+				if (checkTestProps(props)) {
+					return props;
+				} else {
+					throw new Error("System props config file is incomplete.");
+				}
+			} else {
+				return null;
+			}
+		}
+
+		static Properties fromConfigFile(String path) throws Exception {
+			File file = new File(path);
+			if (file.exists()) {
+				Properties props = new Properties();
+				props.load(new FileInputStream(file));
+				if (checkTestProps(props)) {
+					return props;
+				} else {
+					throw new Error("Props config file is incomplete: " + path);
+				}
+			} else {
+				return null;
+			}
+		}
+
+		/**
+		 * Find test properties from various sources in order of priority.
+		 *
+		 * @return result
+		 * @throws Exception
+		 */
+		static Properties discover() throws Exception {
+			Properties props;
+			if ((props = fromEnvVars()) != null) {
+				logger.debug(
+						"Using test properties from environment variables.");
+				return props;
+			}
+			if ((props = fromEnvFile()) != null) {
+				logger.debug(
+						"Using test properties from environment variable config file.");
+				return props;
+			}
+			if ((props = fromSysProps()) != null) {
+				logger.debug("Using test properties from system properties.");
+				return props;
+			}
+			if ((props = fromSysFile()) != null) {
+				logger.debug(
+						"Using test properties from system property config file.");
+				return props;
+			}
+			if ((props = fromConfigFile(HOME_CONFIG_FILE)) != null) {
+				logger.debug(
+						"Using test properties from hard coded ${user.home} file.");
+				return props;
+			}
+			if ((props = fromConfigFile(WORK_CONFIG_FILE)) != null) {
+				logger.debug(
+						"Using test properties from hard coded ${user.dir} file.");
+				return props;
+			}
+			if ((props = fromConfigFile(TEST_CONFIG_FILE)) != null) {
+				logger.debug(
+						"Using test properties from hard coded ${project.source} file.");
+				return props;
+			}
+			throw new Error("Can not load test properties form any source.");
+		}
+
+	}
+
+	/**
+	 * Collection of test utility methods.
+	 */
+	static class Util {
+
+		static final Charset UTF_8 = Charset.forName("UTF-8");
+
+		/**
+		 * Read UTF-8 encoded text file into string.
+		 *
+		 * @param file
+		 * @return result
+		 * @throws Exception
+		 */
+		static String textRead(File file) throws Exception {
+			return new String(Files.readAllBytes(file.toPath()), UTF_8);
+		}
+
+		/**
+		 * Write string into UTF-8 encoded file.
+		 *
+		 * @param file
+		 * @param text
+		 * @throws Exception
+		 */
+		static void textWrite(File file, String text) throws Exception {
+			Files.write(file.toPath(), text.getBytes(UTF_8));
+		}
+
+		static void verifyFileContent(File fileOne, File fileTwo)
+				throws Exception {
+			assertTrue(fileOne.length() > 0);
+			assertTrue(fileTwo.length() > 0);
+			String textOne = textRead(fileOne);
+			String textTwo = textRead(fileTwo);
+			assertEquals(textOne, textTwo);
+		}
+
+		/**
+		 * Create local folder.
+		 *
+		 * @param folder
+		 * @throws Exception
+		 */
+		static void folderCreate(String folder) throws Exception {
+			File path = new File(folder);
+			assertTrue(path.mkdirs());
+		}
+
+		/**
+		 * Delete local folder.
+		 *
+		 * @param folder
+		 * @throws Exception
+		 */
+		static void folderDelete(String folder) throws Exception {
+			File path = new File(folder);
+			FileUtils.delete(path,
+					FileUtils.RECURSIVE | FileUtils.SKIP_MISSING);
+		}
+
+		/**
+		 * Discover public address of CI server.
+		 *
+		 * @return result
+		 * @throws Exception
+		 */
+		static String publicAddress() throws Exception {
+			try {
+				String service = "http://checkip.amazonaws.com";
+				URL url = new URL(service);
+				URLConnection c = url.openConnection();
+				c.setConnectTimeout(500);
+				c.setReadTimeout(500);
+				BufferedReader reader = new BufferedReader(
+						new InputStreamReader(c.getInputStream()));
+				try {
+					return reader.readLine();
+				} finally {
+					reader.close();
+				}
+			} catch (UnknownHostException | SocketTimeoutException e) {
+				return "Can't reach http://checkip.amazonaws.com to"
+						+ " determine public address";
+			}
+		}
+
+		/**
+		 * Discover Password-Based Encryption (PBE) engines providing both
+		 * [SecretKeyFactory] and [AlgorithmParameters].
+		 *
+		 * @return result
+		 */
+		// https://www.bouncycastle.org/specifications.html
+		// https://docs.oracle.com/javase/8/docs/technotes/guides/security/SunProviders.html
+		static List<String> cryptoCipherListPBE() {
+			return cryptoCipherList(WalkEncryption.Vals.REGEX_PBE);
+		}
+
+		// TODO returns inconsistent list.
+		static List<String> cryptoCipherListTrans() {
+			return cryptoCipherList(WalkEncryption.Vals.REGEX_TRANS);
+		}
+
+		static String securityProviderName(String algorithm) throws Exception {
+			return SecretKeyFactory.getInstance(algorithm).getProvider()
+					.getName();
+		}
+
+		static List<String> cryptoCipherList(String regex) {
+			Set<String> source = Security.getAlgorithms("Cipher");
+			Set<String> target = new TreeSet<String>();
+			for (String algo : source) {
+				algo = algo.toUpperCase();
+				if (algo.matches(regex)) {
+					target.add(algo);
+				}
+			}
+			return new ArrayList<String>(target);
+		}
+
+		/**
+		 * Stream copy.
+		 *
+		 * @param from
+		 * @param into
+		 * @return count
+		 * @throws IOException
+		 */
+		static long transferStream(InputStream from, OutputStream into)
+				throws IOException {
+			byte[] array = new byte[1 * 1024];
+			long total = 0;
+			while (true) {
+				int count = from.read(array);
+				if (count == -1) {
+					break;
+				}
+				into.write(array, 0, count);
+				total += count;
+			}
+			return total;
+		}
+
+		/**
+		 * Setup proxy during CI build.
+		 *
+		 * @throws Exception
+		 */
+		// https://wiki.eclipse.org/Hudson#Accessing_the_Internet_using_Proxy
+		// http://docs.oracle.com/javase/7/docs/api/java/net/doc-files/net-properties.html
+		static void proxySetup() throws Exception {
+			String keyNoProxy = "no_proxy";
+			String keyHttpProxy = "http_proxy";
+			String keyHttpsProxy = "https_proxy";
+
+			String no_proxy = System.getProperty(keyNoProxy,
+					System.getenv(keyNoProxy));
+			if (no_proxy != null) {
+				System.setProperty("http.nonProxyHosts", no_proxy);
+				logger.info("Proxy NOT: " + no_proxy);
+			}
+
+			String http_proxy = System.getProperty(keyHttpProxy,
+					System.getenv(keyHttpProxy));
+			if (http_proxy != null) {
+				URL url = new URL(http_proxy);
+				System.setProperty("http.proxyHost", url.getHost());
+				System.setProperty("http.proxyPort", "" + url.getPort());
+				logger.info("Proxy HTTP: " + http_proxy);
+			}
+
+			String https_proxy = System.getProperty(keyHttpsProxy,
+					System.getenv(keyHttpsProxy));
+			if (https_proxy != null) {
+				URL url = new URL(https_proxy);
+				System.setProperty("https.proxyHost", url.getHost());
+				System.setProperty("https.proxyPort", "" + url.getPort());
+				logger.info("Proxy HTTPS: " + https_proxy);
+			}
+
+			if (no_proxy == null && http_proxy == null && https_proxy == null) {
+				logger.info("Proxy not used.");
+			}
+
+		}
+
+		/**
+		 * Permit long tests on CI or with manual activation.
+		 *
+		 * @return result
+		 */
+		static boolean permitLongTests() {
+			return isBuildCI() || isProfileActive();
+		}
+
+		/**
+		 * Using Maven profile activation, see pom.xml
+		 *
+		 * @return result
+		 */
+		static boolean isProfileActive() {
+			return Boolean.parseBoolean(System.getProperty("jgit.test.long"));
+		}
+
+		/**
+		 * Detect if build is running on CI.
+		 *
+		 * @return result
+		 */
+		static boolean isBuildCI() {
+			return System.getenv("HUDSON_HOME") != null;
+		}
+
+		/**
+		 * Setup JCE security policy restrictions. Can remove restrictions when
+		 * restrictions are present, but can not impose them when restrictions
+		 * are missing.
+		 *
+		 * @param restrictedOn
+		 */
+		// http://www.docjar.com/html/api/javax/crypto/JceSecurity.java.html
+		static void policySetup(boolean restrictedOn) {
+			try {
+				java.lang.reflect.Field isRestricted = Class
+						.forName("javax.crypto.JceSecurity")
+						.getDeclaredField("isRestricted");
+				isRestricted.setAccessible(true);
+				isRestricted.set(null, new Boolean(restrictedOn));
+			} catch (Throwable e) {
+				logger.info(
+						"Could not setup JCE security policy restrictions.");
+			}
+		}
+
+		static void reportPolicy() {
+			try {
+				java.lang.reflect.Field isRestricted = Class
+						.forName("javax.crypto.JceSecurity")
+						.getDeclaredField("isRestricted");
+				isRestricted.setAccessible(true);
+				logger.info("JCE security policy restricted="
+						+ isRestricted.get(null));
+			} catch (Throwable e) {
+				logger.info(
+						"Could not report JCE security policy restrictions.");
+			}
+		}
+
+		static List<Object[]> product(List<String> one, List<String> two) {
+			List<Object[]> result = new ArrayList<Object[]>();
+			for (String s1 : one) {
+				for (String s2 : two) {
+					result.add(new Object[] { s1, s2 });
+				}
+			}
+			return result;
+		}
+
+	}
+
+	/**
+	 * Common base for encryption tests.
+	 */
+	@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+	public abstract static class Base extends SampleDataRepositoryTestCase {
+
+		/**
+		 * S3 URI user used by JGIT to discover connection configuration file.
+		 */
+		static final String JGIT_USER = "tester-" + System.currentTimeMillis();
+
+		/**
+		 * S3 content encoding password used for this test session.
+		 */
+		static final String JGIT_PASS = "secret-" + System.currentTimeMillis();
+
+		/**
+		 * S3 repository configuration file expected by {@link AmazonS3}.
+		 */
+		static final String JGIT_CONF_FILE = System.getProperty("user.home")
+				+ "/" + JGIT_USER;
+
+		/**
+		 * Name representing remote or local JGIT repository.
+		 */
+		static final String JGIT_REPO_DIR = JGIT_USER + ".jgit";
+
+		/**
+		 * Local JGIT repository for this test session.
+		 */
+		static final String JGIT_LOCAL_DIR = System.getProperty("user.dir")
+				+ "/target/" + JGIT_REPO_DIR;
+
+		/**
+		 * Remote JGIT repository for this test session.
+		 */
+		static final String JGIT_REMOTE_DIR = JGIT_REPO_DIR;
+
+		/**
+		 * Generate JGIT S3 connection configuration file.
+		 *
+		 * @param algorithm
+		 * @throws Exception
+		 */
+		static void configCreate(String algorithm) throws Exception {
+			Properties props = Props.discover();
+			props.put(AmazonS3.Keys.PASSWORD, JGIT_PASS);
+			props.put(AmazonS3.Keys.CRYPTO_ALG, algorithm);
+			PrintWriter writer = new PrintWriter(JGIT_CONF_FILE);
+			props.store(writer, "JGIT S3 connection configuration file.");
+			writer.close();
+		}
+
+		/**
+		 * Generate JGIT S3 connection configuration file.
+		 *
+		 * @param source
+		 * @throws Exception
+		 */
+		static void configCreate(Properties source) throws Exception {
+			Properties target = Props.discover();
+			target.putAll(source);
+			PrintWriter writer = new PrintWriter(JGIT_CONF_FILE);
+			target.store(writer, "JGIT S3 connection configuration file.");
+			writer.close();
+		}
+
+		/**
+		 * Remove JGIT connection configuration file.
+		 *
+		 * @throws Exception
+		 */
+		static void configDelete() throws Exception {
+			File path = new File(JGIT_CONF_FILE);
+			FileUtils.delete(path, FileUtils.SKIP_MISSING);
+		}
+
+		/**
+		 * Generate remote URI for the test session.
+		 *
+		 * @return result
+		 * @throws Exception
+		 */
+		static String amazonURI() throws Exception {
+			Properties props = Props.discover();
+			String bucket = props.getProperty(Names.TEST_BUCKET);
+			assertNotNull(bucket);
+			return TransportAmazonS3.S3_SCHEME + "://" + JGIT_USER + "@"
+					+ bucket + "/" + JGIT_REPO_DIR;
+		}
+
+		/**
+		 * Create S3 repository folder.
+		 *
+		 * @throws Exception
+		 */
+		static void remoteCreate() throws Exception {
+			Properties props = Props.discover();
+			props.remove(AmazonS3.Keys.PASSWORD); // Disable encryption.
+			String bucket = props.getProperty(Names.TEST_BUCKET);
+			AmazonS3 s3 = new AmazonS3(props);
+			String path = JGIT_REMOTE_DIR + "/";
+			s3.put(bucket, path, new byte[0]);
+			logger.debug("remote create: " + JGIT_REMOTE_DIR);
+		}
+
+		/**
+		 * Delete S3 repository folder.
+		 *
+		 * @throws Exception
+		 */
+		static void remoteDelete() throws Exception {
+			Properties props = Props.discover();
+			props.remove(AmazonS3.Keys.PASSWORD); // Disable encryption.
+			String bucket = props.getProperty(Names.TEST_BUCKET);
+			AmazonS3 s3 = new AmazonS3(props);
+			List<String> list = s3.list(bucket, JGIT_REMOTE_DIR);
+			for (String path : list) {
+				path = JGIT_REMOTE_DIR + "/" + path;
+				s3.delete(bucket, path);
+			}
+			logger.debug("remote delete: " + JGIT_REMOTE_DIR);
+		}
+
+		/**
+		 * Verify if we can create/delete remote file.
+		 *
+		 * @throws Exception
+		 */
+		static void remoteVerify() throws Exception {
+			Properties props = Props.discover();
+			String bucket = props.getProperty(Names.TEST_BUCKET);
+			AmazonS3 s3 = new AmazonS3(props);
+			String file = JGIT_USER + "-" + UUID.randomUUID().toString();
+			String path = JGIT_REMOTE_DIR + "/" + file;
+			s3.put(bucket, path, file.getBytes(UTF_8));
+			s3.delete(bucket, path);
+		}
+
+		/**
+		 * Verify if any security provider published the algorithm.
+		 *
+		 * @param algorithm
+		 * @return result
+		 */
+		static boolean isAlgorithmPresent(String algorithm) {
+			Set<String> cipherSet = Security.getAlgorithms("Cipher");
+			for (String source : cipherSet) {
+				// Standard names are not case-sensitive.
+				// http://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html
+				String target = algorithm.toUpperCase();
+				if (source.equalsIgnoreCase(target)) {
+					return true;
+				}
+			}
+			return false;
+		}
+
+		static boolean isAlgorithmPresent(Properties props) {
+			String profile = props.getProperty(AmazonS3.Keys.CRYPTO_ALG);
+			String version = props.getProperty(AmazonS3.Keys.CRYPTO_VER,
+					WalkEncryption.Vals.DEFAULT_VERS);
+			String crytoAlgo;
+			String keyAlgo;
+			switch (version) {
+			case WalkEncryption.Vals.DEFAULT_VERS:
+			case WalkEncryption.JGitV1.VERSION:
+				crytoAlgo = profile;
+				keyAlgo = profile;
+				break;
+			case WalkEncryption.JGitV2.VERSION:
+				crytoAlgo = props
+						.getProperty(profile + WalkEncryption.Keys.X_ALGO);
+				keyAlgo = props
+						.getProperty(profile + WalkEncryption.Keys.X_KEY_ALGO);
+				break;
+			default:
+				return false;
+			}
+			try {
+				Cipher.getInstance(crytoAlgo);
+				SecretKeyFactory.getInstance(keyAlgo);
+				return true;
+			} catch (Throwable e) {
+				return false;
+			}
+		}
+
+		/**
+		 * Verify if JRE security policy allows the algorithm.
+		 *
+		 * @param algorithm
+		 * @return result
+		 */
+		static boolean isAlgorithmAllowed(String algorithm) {
+			try {
+				WalkEncryption crypto = new WalkEncryption.JetS3tV2(
+						algorithm, JGIT_PASS);
+				verifyCrypto(crypto);
+				return true;
+			} catch (IOException e) {
+				return false; // Encryption failure.
+			} catch (GeneralSecurityException e) {
+				throw new Error(e); // Construction failure.
+			}
+		}
+
+		static boolean isAlgorithmAllowed(Properties props) {
+			try {
+				WalkEncryption.instance(props);
+				return true;
+			} catch (GeneralSecurityException e) {
+				return false;
+			}
+		}
+
+		/**
+		 * Verify round trip encryption.
+		 *
+		 * @param crypto
+		 * @throws IOException
+		 */
+		static void verifyCrypto(WalkEncryption crypto) throws IOException {
+			String charset = "UTF-8";
+			String sourceText = "secret-message Свобода 老子";
+			String targetText;
+			byte[] cipherText;
+			{
+				byte[] origin = sourceText.getBytes(charset);
+				ByteArrayOutputStream target = new ByteArrayOutputStream();
+				OutputStream source = crypto.encrypt(target);
+				source.write(origin);
+				source.flush();
+				source.close();
+				cipherText = target.toByteArray();
+			}
+			{
+				InputStream source = new ByteArrayInputStream(cipherText);
+				InputStream target = crypto.decrypt(source);
+				ByteArrayOutputStream result = new ByteArrayOutputStream();
+				transferStream(target, result);
+				targetText = result.toString(charset);
+			}
+			assertEquals(sourceText, targetText);
+		}
+
+		/**
+		 * Algorithm is testable when it is present and allowed by policy.
+		 *
+		 * @param algorithm
+		 * @return result
+		 */
+		static boolean isAlgorithmTestable(String algorithm) {
+			return isAlgorithmPresent(algorithm)
+					&& isAlgorithmAllowed(algorithm);
+		}
+
+		static boolean isAlgorithmTestable(Properties props) {
+			return isAlgorithmPresent(props) && isAlgorithmAllowed(props);
+		}
+
+		/**
+		 * Log algorithm, provider, testability.
+		 *
+		 * @param algorithm
+		 * @throws Exception
+		 */
+		static void reportAlgorithmStatus(String algorithm) throws Exception {
+			final boolean present = isAlgorithmPresent(algorithm);
+			final boolean allowed = present && isAlgorithmAllowed(algorithm);
+			final String provider = present ? securityProviderName(algorithm)
+					: "N/A";
+			String status = "Algorithm: " + algorithm + " @ " + provider + "; "
+					+ "present/allowed : " + present + "/" + allowed;
+			if (allowed) {
+				logger.info("Testing " + status);
+			} else {
+				logger.warn("Missing " + status);
+			}
+		}
+
+		static void reportAlgorithmStatus(Properties props) throws Exception {
+			final boolean present = isAlgorithmPresent(props);
+			final boolean allowed = present && isAlgorithmAllowed(props);
+
+			String profile = props.getProperty(AmazonS3.Keys.CRYPTO_ALG);
+			String version = props.getProperty(AmazonS3.Keys.CRYPTO_VER);
+
+			StringBuilder status = new StringBuilder();
+			status.append(" Version: " + version);
+			status.append(" Profile: " + profile);
+			status.append(" Present: " + present);
+			status.append(" Allowed: " + allowed);
+
+			if (allowed) {
+				logger.info("Testing " + status);
+			} else {
+				logger.warn("Missing " + status);
+			}
+		}
+
+		/**
+		 * Verify if we can perform remote tests.
+		 *
+		 * @return result
+		 */
+		static boolean isTestConfigPresent() {
+			try {
+				Props.discover();
+				return true;
+			} catch (Throwable e) {
+				return false;
+			}
+		}
+
+		static void reportTestConfigPresent() {
+			if (isTestConfigPresent()) {
+				logger.info("Amazon S3 test configuration is present.");
+			} else {
+				logger.error(
+						"Amazon S3 test configuration is missing, tests will not run.");
+			}
+		}
+
+		/**
+		 * Log public address of CI.
+		 *
+		 * @throws Exception
+		 */
+		static void reportPublicAddress() throws Exception {
+			logger.info("Public address: " + publicAddress());
+		}
+
+		/**
+		 * BouncyCastle provider class.
+		 *
+		 * Needs extra dependency, see pom.xml
+		 */
+		// http://search.maven.org/#artifactdetails%7Corg.bouncycastle%7Cbcprov-jdk15on%7C1.52%7Cjar
+		static final String PROVIDER_BC = "org.bouncycastle.jce.provider.BouncyCastleProvider";
+
+		/**
+		 * Load BouncyCastle provider if present.
+		 */
+		static void loadBouncyCastle() {
+			try {
+				Class<?> provider = Class.forName(PROVIDER_BC);
+				Provider instance = (Provider) provider
+						.getConstructor(new Class[] {})
+						.newInstance(new Object[] {});
+				Security.addProvider(instance);
+				logger.info("Loaded " + PROVIDER_BC);
+			} catch (Throwable e) {
+				logger.warn("Failed to load " + PROVIDER_BC);
+			}
+		}
+
+		static void reportLongTests() {
+			if (permitLongTests()) {
+				logger.info("Long running tests are enabled.");
+			} else {
+				logger.warn("Long running tests are disabled.");
+			}
+		}
+
+		/**
+		 * Non-PBE algorithm, for error check.
+		 */
+		static final String ALGO_ERROR = "PBKDF2WithHmacSHA1";
+
+		/**
+		 * Default JetS3t algorithm present in most JRE.
+		 */
+		static final String ALGO_JETS3T = "PBEWithMD5AndDES";
+
+		/**
+		 * Minimal strength AES based algorithm present in most JRE.
+		 */
+		static final String ALGO_MINIMAL_AES = "PBEWithHmacSHA1AndAES_128";
+
+		/**
+		 * Selected non-AES algorithm present in BouncyCastle provider.
+		 */
+		static final String ALGO_BOUNCY_CASTLE_CBC = "PBEWithSHAAndTwofish-CBC";
+
+		//////////////////////////////////////////////////
+
+		@BeforeClass
+		public static void initialize() throws Exception {
+			Transport.register(TransportAmazonS3.PROTO_S3);
+			proxySetup();
+			reportPolicy();
+			reportLongTests();
+			reportPublicAddress();
+			reportTestConfigPresent();
+			loadBouncyCastle();
+			if (isTestConfigPresent()) {
+				remoteCreate();
+			}
+		}
+
+		@AfterClass
+		public static void terminate() throws Exception {
+			configDelete();
+			folderDelete(JGIT_LOCAL_DIR);
+			if (isTestConfigPresent()) {
+				remoteDelete();
+			}
+		}
+
+		@Before
+		@Override
+		public void setUp() throws Exception {
+			super.setUp();
+		}
+
+		@After
+		@Override
+		public void tearDown() throws Exception {
+			super.tearDown();
+		}
+
+		/**
+		 * Optional encrypted amazon remote JGIT life cycle test.
+		 *
+		 * @param props
+		 * @throws Exception
+		 */
+		void cryptoTestIfCan(Properties props) throws Exception {
+			reportAlgorithmStatus(props);
+			assumeTrue(isTestConfigPresent());
+			assumeTrue(isAlgorithmTestable(props));
+			cryptoTest(props);
+		}
+
+		/**
+		 * Required encrypted amazon remote JGIT life cycle test.
+		 *
+		 * @param props
+		 * @throws Exception
+		 */
+		void cryptoTest(Properties props) throws Exception {
+
+			remoteDelete();
+			configCreate(props);
+			folderDelete(JGIT_LOCAL_DIR);
+
+			String uri = amazonURI();
+
+			// Local repositories.
+			File dirOne = db.getWorkTree(); // Provided by setup.
+			File dirTwo = new File(JGIT_LOCAL_DIR);
+
+			// Local verification files.
+			String nameStatic = "master.txt"; // Provided by setup.
+			String nameDynamic = JGIT_USER + "-" + UUID.randomUUID().toString();
+
+			String remote = "remote";
+			RefSpec specs = new RefSpec("refs/heads/master:refs/heads/master");
+
+			{ // Push into remote from local one.
+
+				StoredConfig config = db.getConfig();
+				RemoteConfig remoteConfig = new RemoteConfig(config, remote);
+				remoteConfig.addURI(new URIish(uri));
+				remoteConfig.update(config);
+				config.save();
+
+				Git git = Git.open(dirOne);
+				git.checkout().setName("master").call();
+				git.push().setRemote(remote).setRefSpecs(specs).call();
+				git.close();
+
+				File fileStatic = new File(dirOne, nameStatic);
+				assertTrue("Provided by setup", fileStatic.exists());
+
+			}
+
+			{ // Clone from remote into local two.
+
+				File fileStatic = new File(dirTwo, nameStatic);
+				assertFalse("Not Provided by setup", fileStatic.exists());
+
+				Git git = Git.cloneRepository().setURI(uri).setDirectory(dirTwo)
+						.call();
+				git.close();
+
+				assertTrue("Provided by clone", fileStatic.exists());
+			}
+
+			{ // Verify static file content.
+				File fileOne = new File(dirOne, nameStatic);
+				File fileTwo = new File(dirTwo, nameStatic);
+				verifyFileContent(fileOne, fileTwo);
+			}
+
+			{ // Verify new file commit and push from local one.
+
+				File fileDynamic = new File(dirOne, nameDynamic);
+				assertFalse("Not Provided by setup", fileDynamic.exists());
+				FileUtils.createNewFile(fileDynamic);
+				textWrite(fileDynamic, nameDynamic);
+				assertTrue("Provided by create", fileDynamic.exists());
+				assertTrue("Need content to encrypt", fileDynamic.length() > 0);
+
+				Git git = Git.open(dirOne);
+				git.add().addFilepattern(nameDynamic).call();
+				git.commit().setMessage(nameDynamic).call();
+				git.push().setRemote(remote).setRefSpecs(specs).call();
+				git.close();
+
+			}
+
+			{ // Verify new file pull from remote into local two.
+
+				File fileDynamic = new File(dirTwo, nameDynamic);
+				assertFalse("Not Provided by setup", fileDynamic.exists());
+
+				Git git = Git.open(dirTwo);
+				git.pull().call();
+				git.close();
+
+				assertTrue("Provided by pull", fileDynamic.exists());
+			}
+
+			{ // Verify dynamic file content.
+				File fileOne = new File(dirOne, nameDynamic);
+				File fileTwo = new File(dirTwo, nameDynamic);
+				verifyFileContent(fileOne, fileTwo);
+			}
+
+		}
+
+	}
+
+	/**
+	 * Verify prerequisites.
+	 */
+	@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+	public static class Required extends Base {
+
+		@Test
+		public void test_A1_ValidURI() throws Exception {
+			assumeTrue(isTestConfigPresent());
+			URIish uri = new URIish(amazonURI());
+			assertTrue("uri=" + uri, TransportAmazonS3.PROTO_S3.canHandle(uri));
+		}
+
+		@Test(expected = Exception.class)
+		public void test_A2_CryptoError() throws Exception {
+			assumeTrue(isTestConfigPresent());
+			Properties props = new Properties();
+			props.put(AmazonS3.Keys.CRYPTO_ALG, ALGO_ERROR);
+			props.put(AmazonS3.Keys.PASSWORD, JGIT_PASS);
+			cryptoTest(props);
+		}
+
+	}
+
+	/**
+	 * Test minimal set of algorithms.
+	 */
+	@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+	public static class MinimalSet extends Base {
+
+		@Test
+		public void test_V0_Java7_JET() throws Exception {
+			assumeTrue(isTestConfigPresent());
+			Properties props = new Properties();
+			props.put(AmazonS3.Keys.CRYPTO_ALG, ALGO_JETS3T);
+			// Do not set version.
+			props.put(AmazonS3.Keys.PASSWORD, JGIT_PASS);
+			cryptoTestIfCan(props);
+		}
+
+		@Test
+		public void test_V1_Java7_GIT() throws Exception {
+			assumeTrue(isTestConfigPresent());
+			Properties props = new Properties();
+			props.put(AmazonS3.Keys.CRYPTO_ALG, ALGO_JETS3T);
+			props.put(AmazonS3.Keys.CRYPTO_VER, "1");
+			props.put(AmazonS3.Keys.PASSWORD, JGIT_PASS);
+			cryptoTestIfCan(props);
+		}
+
+		@Test
+		public void test_V2_Java7_AES() throws Exception {
+			assumeTrue(isTestConfigPresent());
+			// String profile = "default";
+			String profile = "AES/CBC/PKCS5Padding+PBKDF2WithHmacSHA1";
+			Properties props = new Properties();
+			props.put(AmazonS3.Keys.CRYPTO_ALG, profile);
+			props.put(AmazonS3.Keys.CRYPTO_VER, "2");
+			props.put(AmazonS3.Keys.PASSWORD, JGIT_PASS);
+			props.put(profile + WalkEncryption.Keys.X_ALGO, "AES/CBC/PKCS5Padding");
+			props.put(profile + WalkEncryption.Keys.X_KEY_ALGO, "PBKDF2WithHmacSHA1");
+			props.put(profile + WalkEncryption.Keys.X_KEY_SIZE, "128");
+			props.put(profile + WalkEncryption.Keys.X_KEY_ITER, "10000");
+			props.put(profile + WalkEncryption.Keys.X_KEY_SALT, "e2 55 89 67 8e 8d e8 4c");
+			cryptoTestIfCan(props);
+		}
+
+		@Test
+		public void test_V2_Java8_PBE_AES() throws Exception {
+			assumeTrue(isTestConfigPresent());
+			String profile = "PBEWithHmacSHA512AndAES_256";
+			Properties props = new Properties();
+			props.put(AmazonS3.Keys.CRYPTO_ALG, profile);
+			props.put(AmazonS3.Keys.CRYPTO_VER, "2");
+			props.put(AmazonS3.Keys.PASSWORD, JGIT_PASS);
+			props.put(profile + WalkEncryption.Keys.X_ALGO, "PBEWithHmacSHA512AndAES_256");
+			props.put(profile + WalkEncryption.Keys.X_KEY_ALGO, "PBEWithHmacSHA512AndAES_256");
+			props.put(profile + WalkEncryption.Keys.X_KEY_SIZE, "256");
+			props.put(profile + WalkEncryption.Keys.X_KEY_ITER, "10000");
+			props.put(profile + WalkEncryption.Keys.X_KEY_SALT, "e2 55 89 67 8e 8d e8 4c");
+			policySetup(false);
+			cryptoTestIfCan(props);
+		}
+
+	}
+
+	/**
+	 * Test all present and allowed PBE algorithms.
+	 */
+	// https://github.com/junit-team/junit/wiki/Parameterized-tests
+	@RunWith(Parameterized.class)
+	@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+	public static class TestablePBE extends Base {
+
+		@Parameters(name = "Profile: {0}   Version: {1}")
+		public static Collection<Object[]> argsList() {
+			List<String> algorithmList = new ArrayList<String>();
+			algorithmList.addAll(cryptoCipherListPBE());
+
+			List<String> versionList = new ArrayList<String>();
+			versionList.add("0");
+			versionList.add("1");
+
+			return product(algorithmList, versionList);
+		}
+
+		final String profile;
+
+		final String version;
+
+		final String password = JGIT_PASS;
+
+		public TestablePBE(String profile, String version) {
+			this.profile = profile;
+			this.version = version;
+		}
+
+		@Test
+		public void testCrypto() throws Exception {
+			assumeTrue(permitLongTests());
+			Properties props = new Properties();
+			props.put(AmazonS3.Keys.CRYPTO_ALG, profile);
+			props.put(AmazonS3.Keys.CRYPTO_VER, version);
+			props.put(AmazonS3.Keys.PASSWORD, password);
+			cryptoTestIfCan(props);
+		}
+
+	}
+
+	/**
+	 * Test all present and allowed transformation algorithms.
+	 */
+	// https://github.com/junit-team/junit/wiki/Parameterized-tests
+	@RunWith(Parameterized.class)
+	@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+	public static class TestableTransformation extends Base {
+
+		@Parameters(name = "Profile: {0}   Version: {1}")
+		public static Collection<Object[]> argsList() {
+			List<String> algorithmList = new ArrayList<String>();
+			algorithmList.addAll(cryptoCipherListTrans());
+
+			List<String> versionList = new ArrayList<String>();
+			versionList.add("1");
+
+			return product(algorithmList, versionList);
+		}
+
+		final String profile;
+
+		final String version;
+
+		final String password = JGIT_PASS;
+
+		public TestableTransformation(String profile, String version) {
+			this.profile = profile;
+			this.version = version;
+		}
+
+		@Test
+		public void testCrypto() throws Exception {
+			assumeTrue(permitLongTests());
+			Properties props = new Properties();
+			props.put(AmazonS3.Keys.CRYPTO_ALG, profile);
+			props.put(AmazonS3.Keys.CRYPTO_VER, version);
+			props.put(AmazonS3.Keys.PASSWORD, password);
+			cryptoTestIfCan(props);
+		}
+
+	}
+
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/CanonicalTreeParserTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/CanonicalTreeParserTest.java
index 52da69e..f5e97c2 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/CanonicalTreeParserTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/CanonicalTreeParserTest.java
@@ -43,6 +43,8 @@
 
 package org.eclipse.jgit.treewalk;
 
+import static org.eclipse.jgit.lib.FileMode.REGULAR_FILE;
+import static org.eclipse.jgit.lib.FileMode.SYMLINK;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertSame;
@@ -50,9 +52,11 @@
 
 import java.io.ByteArrayOutputStream;
 
+import org.eclipse.jgit.errors.CorruptObjectException;
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.FileMode;
 import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.TreeFormatter;
 import org.eclipse.jgit.util.RawParseUtils;
 import org.junit.Before;
 import org.junit.Test;
@@ -369,4 +373,41 @@
 		assertEquals(name, RawParseUtils.decode(Constants.CHARSET, ctp.path,
 				ctp.pathOffset, ctp.pathLen));
 	}
+
+	@Test
+	public void testFindAttributesWhenFirst() throws CorruptObjectException {
+		TreeFormatter tree = new TreeFormatter();
+		tree.append(".gitattributes", REGULAR_FILE, hash_a);
+		ctp.reset(tree.toByteArray());
+
+		assertTrue(ctp.findFile(".gitattributes"));
+		assertEquals(REGULAR_FILE.getBits(), ctp.getEntryRawMode());
+		assertEquals(".gitattributes", ctp.getEntryPathString());
+		assertEquals(hash_a, ctp.getEntryObjectId());
+	}
+
+	@Test
+	public void testFindAttributesWhenSecond() throws CorruptObjectException {
+		TreeFormatter tree = new TreeFormatter();
+		tree.append(".config", SYMLINK, hash_a);
+		tree.append(".gitattributes", REGULAR_FILE, hash_foo);
+		ctp.reset(tree.toByteArray());
+
+		assertTrue(ctp.findFile(".gitattributes"));
+		assertEquals(REGULAR_FILE.getBits(), ctp.getEntryRawMode());
+		assertEquals(".gitattributes", ctp.getEntryPathString());
+		assertEquals(hash_foo, ctp.getEntryObjectId());
+	}
+
+	@Test
+	public void testFindAttributesWhenMissing() throws CorruptObjectException {
+		TreeFormatter tree = new TreeFormatter();
+		tree.append("src", REGULAR_FILE, hash_a);
+		tree.append("zoo", REGULAR_FILE, hash_foo);
+		ctp.reset(tree.toByteArray());
+
+		assertFalse(ctp.findFile(".gitattributes"));
+		assertEquals(11, ctp.idOffset()); // Did not walk the entire tree.
+		assertEquals("src", ctp.getEntryPathString());
+	}
 }
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/FileTreeIteratorJava7Test.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/FileTreeIteratorJava7Test.java
index 8875410..cd55cba 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/FileTreeIteratorJava7Test.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/FileTreeIteratorJava7Test.java
@@ -67,6 +67,7 @@
 public class FileTreeIteratorJava7Test extends RepositoryTestCase {
 	@Test
 	public void testFileModeSymLinkIsNotATree() throws IOException {
+		org.junit.Assume.assumeTrue(FS.DETECTED.supportsSymlinks());
 		FS fs = db.getFS();
 		// mål = target in swedish, just to get som unicode in here
 		writeTrashFile("mål/data", "targetdata");
@@ -102,18 +103,20 @@
 			});
 			assertTrue(dce.commit());
 		}
-		new Git(db).commit().setMessage("Adding link").call();
-		new Git(db).reset().setMode(ResetType.HARD).call();
-		DirCacheIterator dci = new DirCacheIterator(db.readDirCache());
-		FileTreeIterator fti = new FileTreeIterator(db);
+		try (Git git = new Git(db)) {
+			git.commit().setMessage("Adding link").call();
+			git.reset().setMode(ResetType.HARD).call();
+			DirCacheIterator dci = new DirCacheIterator(db.readDirCache());
+			FileTreeIterator fti = new FileTreeIterator(db);
 
-		// self-check
-		assertEquals("link", fti.getEntryPathString());
-		assertEquals("link", dci.getEntryPathString());
+			// self-check
+			assertEquals("link", fti.getEntryPathString());
+			assertEquals("link", dci.getEntryPathString());
 
-		// test
-		assertFalse(fti.isModified(dci.getDirCacheEntry(), true,
-				db.newObjectReader()));
+			// test
+			assertFalse(fti.isModified(dci.getDirCacheEntry(), true,
+					db.newObjectReader()));
+		}
 	}
 
 	/**
@@ -141,18 +144,20 @@
 			});
 			assertTrue(dce.commit());
 		}
-		new Git(db).commit().setMessage("Adding link").call();
-		new Git(db).reset().setMode(ResetType.HARD).call();
-		DirCacheIterator dci = new DirCacheIterator(db.readDirCache());
-		FileTreeIterator fti = new FileTreeIterator(db);
+		try (Git git = new Git(db)) {
+			git.commit().setMessage("Adding link").call();
+			git.reset().setMode(ResetType.HARD).call();
+			DirCacheIterator dci = new DirCacheIterator(db.readDirCache());
+			FileTreeIterator fti = new FileTreeIterator(db);
 
-		// self-check
-		assertEquals("link", fti.getEntryPathString());
-		assertEquals("link", dci.getEntryPathString());
+			// self-check
+			assertEquals("link", fti.getEntryPathString());
+			assertEquals("link", dci.getEntryPathString());
 
-		// test
-		assertFalse(fti.isModified(dci.getDirCacheEntry(), true,
-				db.newObjectReader()));
+			// test
+			assertFalse(fti.isModified(dci.getDirCacheEntry(), true,
+					db.newObjectReader()));
+		}
 	}
 
 	/**
@@ -163,6 +168,7 @@
 	 */
 	@Test
 	public void testSymlinkActuallyModified() throws Exception {
+		org.junit.Assume.assumeTrue(FS.DETECTED.supportsSymlinks());
 		final String NORMALIZED = "target";
 		final byte[] NORMALIZED_BYTES = Constants.encode(NORMALIZED);
 		try (ObjectInserter oi = db.newObjectInserter()) {
@@ -180,20 +186,22 @@
 			});
 			assertTrue(dce.commit());
 		}
-		new Git(db).commit().setMessage("Adding link").call();
-		new Git(db).reset().setMode(ResetType.HARD).call();
+		try (Git git = new Git(db)) {
+			git.commit().setMessage("Adding link").call();
+			git.reset().setMode(ResetType.HARD).call();
 
-		FileUtils.delete(new File(trash, "link"), FileUtils.NONE);
-		FS.DETECTED.createSymLink(new File(trash, "link"), "newtarget");
-		DirCacheIterator dci = new DirCacheIterator(db.readDirCache());
-		FileTreeIterator fti = new FileTreeIterator(db);
+			FileUtils.delete(new File(trash, "link"), FileUtils.NONE);
+			FS.DETECTED.createSymLink(new File(trash, "link"), "newtarget");
+			DirCacheIterator dci = new DirCacheIterator(db.readDirCache());
+			FileTreeIterator fti = new FileTreeIterator(db);
 
-		// self-check
-		assertEquals("link", fti.getEntryPathString());
-		assertEquals("link", dci.getEntryPathString());
+			// self-check
+			assertEquals("link", fti.getEntryPathString());
+			assertEquals("link", dci.getEntryPathString());
 
-		// test
-		assertTrue(fti.isModified(dci.getDirCacheEntry(), true,
-				db.newObjectReader()));
+			// test
+			assertTrue(fti.isModified(dci.getDirCacheEntry(), true,
+					db.newObjectReader()));
+		}
 	}
 }
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 767e13d..df17a3e 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
@@ -255,10 +255,11 @@
 	@Test
 	public void testDirCacheMatchingId() throws Exception {
 		File f = writeTrashFile("file", "content");
-		Git git = new Git(db);
-		writeTrashFile("file", "content");
-		fsTick(f);
-		git.add().addFilepattern("file").call();
+		try (Git git = new Git(db)) {
+			writeTrashFile("file", "content");
+			fsTick(f);
+			git.add().addFilepattern("file").call();
+		}
 		DirCacheEntry dce = db.readDirCache().getEntry("file");
 		TreeWalk tw = new TreeWalk(db);
 		FileTreeIterator fti = new FileTreeIterator(trash, db.getFS(), db
@@ -282,11 +283,12 @@
 	@Test
 	public void testIsModifiedSymlinkAsFile() throws Exception {
 		writeTrashFile("symlink", "content");
-		Git git = new Git(db);
-		db.getConfig().setString(ConfigConstants.CONFIG_CORE_SECTION, null,
-				ConfigConstants.CONFIG_KEY_SYMLINKS, "false");
-		git.add().addFilepattern("symlink").call();
-		git.commit().setMessage("commit").call();
+		try (Git git = new Git(db)) {
+			db.getConfig().setString(ConfigConstants.CONFIG_CORE_SECTION, null,
+					ConfigConstants.CONFIG_KEY_SYMLINKS, "false");
+			git.add().addFilepattern("symlink").call();
+			git.commit().setMessage("commit").call();
+		}
 
 		// Modify previously committed DirCacheEntry and write it back to disk
 		DirCacheEntry dce = db.readDirCache().getEntry("symlink");
@@ -305,20 +307,21 @@
 	@Test
 	public void testIsModifiedFileSmudged() throws Exception {
 		File f = writeTrashFile("file", "content");
-		Git git = new Git(db);
-		// The idea of this test is to check the smudged handling
-		// Hopefully fsTick will make sure our entry gets smudged
-		fsTick(f);
-		writeTrashFile("file", "content");
-		long lastModified = f.lastModified();
-		git.add().addFilepattern("file").call();
-		writeTrashFile("file", "conten2");
-		f.setLastModified(lastModified);
-		// We cannot trust this to go fast enough on
-		// a system with less than one-second lastModified
-		// resolution, so we force the index to have the
-		// same timestamp as the file we look at.
-		db.getIndexFile().setLastModified(lastModified);
+		try (Git git = new Git(db)) {
+			// The idea of this test is to check the smudged handling
+			// Hopefully fsTick will make sure our entry gets smudged
+			fsTick(f);
+			writeTrashFile("file", "content");
+			long lastModified = f.lastModified();
+			git.add().addFilepattern("file").call();
+			writeTrashFile("file", "conten2");
+			f.setLastModified(lastModified);
+			// We cannot trust this to go fast enough on
+			// a system with less than one-second lastModified
+			// resolution, so we force the index to have the
+			// same timestamp as the file we look at.
+			db.getIndexFile().setLastModified(lastModified);
+		}
 		DirCacheEntry dce = db.readDirCache().getEntry("file");
 		FileTreeIterator fti = new FileTreeIterator(trash, db.getFS(), db
 				.getConfig().get(WorkingTreeOptions.KEY));
@@ -334,198 +337,204 @@
 
 	@Test
 	public void submoduleHeadMatchesIndex() throws Exception {
-		Git git = new Git(db);
-		writeTrashFile("file.txt", "content");
-		git.add().addFilepattern("file.txt").call();
-		final RevCommit id = git.commit().setMessage("create file").call();
-		final String path = "sub";
-		DirCache cache = db.lockDirCache();
-		DirCacheEditor editor = cache.editor();
-		editor.add(new PathEdit(path) {
+		try (Git git = new Git(db);
+				TreeWalk walk = new TreeWalk(db)) {
+			writeTrashFile("file.txt", "content");
+			git.add().addFilepattern("file.txt").call();
+			final RevCommit id = git.commit().setMessage("create file").call();
+			final String path = "sub";
+			DirCache cache = db.lockDirCache();
+			DirCacheEditor editor = cache.editor();
+			editor.add(new PathEdit(path) {
+	
+				public void apply(DirCacheEntry ent) {
+					ent.setFileMode(FileMode.GITLINK);
+					ent.setObjectId(id);
+				}
+			});
+			editor.commit();
 
-			public void apply(DirCacheEntry ent) {
-				ent.setFileMode(FileMode.GITLINK);
-				ent.setObjectId(id);
-			}
-		});
-		editor.commit();
+			Git.cloneRepository().setURI(db.getDirectory().toURI().toString())
+					.setDirectory(new File(db.getWorkTree(), path)).call()
+					.getRepository().close();
 
-		Git.cloneRepository().setURI(db.getDirectory().toURI().toString())
-				.setDirectory(new File(db.getWorkTree(), path)).call()
-				.getRepository().close();
-
-		TreeWalk walk = new TreeWalk(db);
-		DirCacheIterator indexIter = new DirCacheIterator(db.readDirCache());
-		FileTreeIterator workTreeIter = new FileTreeIterator(db);
-		walk.addTree(indexIter);
-		walk.addTree(workTreeIter);
-		walk.setFilter(PathFilter.create(path));
-
-		assertTrue(walk.next());
-		assertTrue(indexIter.idEqual(workTreeIter));
+			DirCacheIterator indexIter = new DirCacheIterator(db.readDirCache());
+			FileTreeIterator workTreeIter = new FileTreeIterator(db);
+			walk.addTree(indexIter);
+			walk.addTree(workTreeIter);
+			walk.setFilter(PathFilter.create(path));
+	
+			assertTrue(walk.next());
+			assertTrue(indexIter.idEqual(workTreeIter));
+		}
 	}
 
 	@Test
 	public void submoduleWithNoGitDirectory() throws Exception {
-		Git git = new Git(db);
-		writeTrashFile("file.txt", "content");
-		git.add().addFilepattern("file.txt").call();
-		final RevCommit id = git.commit().setMessage("create file").call();
-		final String path = "sub";
-		DirCache cache = db.lockDirCache();
-		DirCacheEditor editor = cache.editor();
-		editor.add(new PathEdit(path) {
+		try (Git git = new Git(db);
+				TreeWalk walk = new TreeWalk(db)) {
+			writeTrashFile("file.txt", "content");
+			git.add().addFilepattern("file.txt").call();
+			final RevCommit id = git.commit().setMessage("create file").call();
+			final String path = "sub";
+			DirCache cache = db.lockDirCache();
+			DirCacheEditor editor = cache.editor();
+			editor.add(new PathEdit(path) {
 
-			public void apply(DirCacheEntry ent) {
-				ent.setFileMode(FileMode.GITLINK);
-				ent.setObjectId(id);
-			}
-		});
-		editor.commit();
+				public void apply(DirCacheEntry ent) {
+					ent.setFileMode(FileMode.GITLINK);
+					ent.setObjectId(id);
+				}
+			});
+			editor.commit();
 
-		File submoduleRoot = new File(db.getWorkTree(), path);
-		assertTrue(submoduleRoot.mkdir());
-		assertTrue(new File(submoduleRoot, Constants.DOT_GIT).mkdir());
+			File submoduleRoot = new File(db.getWorkTree(), path);
+			assertTrue(submoduleRoot.mkdir());
+			assertTrue(new File(submoduleRoot, Constants.DOT_GIT).mkdir());
 
-		TreeWalk walk = new TreeWalk(db);
-		DirCacheIterator indexIter = new DirCacheIterator(db.readDirCache());
-		FileTreeIterator workTreeIter = new FileTreeIterator(db);
-		walk.addTree(indexIter);
-		walk.addTree(workTreeIter);
-		walk.setFilter(PathFilter.create(path));
+			DirCacheIterator indexIter = new DirCacheIterator(db.readDirCache());
+			FileTreeIterator workTreeIter = new FileTreeIterator(db);
+			walk.addTree(indexIter);
+			walk.addTree(workTreeIter);
+			walk.setFilter(PathFilter.create(path));
 
-		assertTrue(walk.next());
-		assertFalse(indexIter.idEqual(workTreeIter));
-		assertEquals(ObjectId.zeroId(), workTreeIter.getEntryObjectId());
+			assertTrue(walk.next());
+			assertFalse(indexIter.idEqual(workTreeIter));
+			assertEquals(ObjectId.zeroId(), workTreeIter.getEntryObjectId());
+		}
 	}
 
 	@Test
 	public void submoduleWithNoHead() throws Exception {
-		Git git = new Git(db);
-		writeTrashFile("file.txt", "content");
-		git.add().addFilepattern("file.txt").call();
-		final RevCommit id = git.commit().setMessage("create file").call();
-		final String path = "sub";
-		DirCache cache = db.lockDirCache();
-		DirCacheEditor editor = cache.editor();
-		editor.add(new PathEdit(path) {
+		try (Git git = new Git(db);
+				TreeWalk walk = new TreeWalk(db)) {
+			writeTrashFile("file.txt", "content");
+			git.add().addFilepattern("file.txt").call();
+			final RevCommit id = git.commit().setMessage("create file").call();
+			final String path = "sub";
+			DirCache cache = db.lockDirCache();
+			DirCacheEditor editor = cache.editor();
+			editor.add(new PathEdit(path) {
 
-			public void apply(DirCacheEntry ent) {
-				ent.setFileMode(FileMode.GITLINK);
-				ent.setObjectId(id);
-			}
-		});
-		editor.commit();
+				public void apply(DirCacheEntry ent) {
+					ent.setFileMode(FileMode.GITLINK);
+					ent.setObjectId(id);
+				}
+			});
+			editor.commit();
 
-		assertNotNull(Git.init().setDirectory(new File(db.getWorkTree(), path))
-				.call().getRepository());
+			assertNotNull(Git.init().setDirectory(new File(db.getWorkTree(), path))
+					.call().getRepository());
 
-		TreeWalk walk = new TreeWalk(db);
-		DirCacheIterator indexIter = new DirCacheIterator(db.readDirCache());
-		FileTreeIterator workTreeIter = new FileTreeIterator(db);
-		walk.addTree(indexIter);
-		walk.addTree(workTreeIter);
-		walk.setFilter(PathFilter.create(path));
+			DirCacheIterator indexIter = new DirCacheIterator(db.readDirCache());
+			FileTreeIterator workTreeIter = new FileTreeIterator(db);
+			walk.addTree(indexIter);
+			walk.addTree(workTreeIter);
+			walk.setFilter(PathFilter.create(path));
 
-		assertTrue(walk.next());
-		assertFalse(indexIter.idEqual(workTreeIter));
-		assertEquals(ObjectId.zeroId(), workTreeIter.getEntryObjectId());
+			assertTrue(walk.next());
+			assertFalse(indexIter.idEqual(workTreeIter));
+			assertEquals(ObjectId.zeroId(), workTreeIter.getEntryObjectId());
+		}
 	}
 
 	@Test
 	public void submoduleDirectoryIterator() throws Exception {
-		Git git = new Git(db);
-		writeTrashFile("file.txt", "content");
-		git.add().addFilepattern("file.txt").call();
-		final RevCommit id = git.commit().setMessage("create file").call();
-		final String path = "sub";
-		DirCache cache = db.lockDirCache();
-		DirCacheEditor editor = cache.editor();
-		editor.add(new PathEdit(path) {
+		try (Git git = new Git(db);
+				TreeWalk walk = new TreeWalk(db)) {
+			writeTrashFile("file.txt", "content");
+			git.add().addFilepattern("file.txt").call();
+			final RevCommit id = git.commit().setMessage("create file").call();
+			final String path = "sub";
+			DirCache cache = db.lockDirCache();
+			DirCacheEditor editor = cache.editor();
+			editor.add(new PathEdit(path) {
 
-			public void apply(DirCacheEntry ent) {
-				ent.setFileMode(FileMode.GITLINK);
-				ent.setObjectId(id);
-			}
-		});
-		editor.commit();
+				public void apply(DirCacheEntry ent) {
+					ent.setFileMode(FileMode.GITLINK);
+					ent.setObjectId(id);
+				}
+			});
+			editor.commit();
 
-		Git.cloneRepository().setURI(db.getDirectory().toURI().toString())
-				.setDirectory(new File(db.getWorkTree(), path)).call()
-				.getRepository().close();
+			Git.cloneRepository().setURI(db.getDirectory().toURI().toString())
+					.setDirectory(new File(db.getWorkTree(), path)).call()
+					.getRepository().close();
 
-		TreeWalk walk = new TreeWalk(db);
-		DirCacheIterator indexIter = new DirCacheIterator(db.readDirCache());
-		FileTreeIterator workTreeIter = new FileTreeIterator(db.getWorkTree(),
-				db.getFS(), db.getConfig().get(WorkingTreeOptions.KEY));
-		walk.addTree(indexIter);
-		walk.addTree(workTreeIter);
-		walk.setFilter(PathFilter.create(path));
+			DirCacheIterator indexIter = new DirCacheIterator(db.readDirCache());
+			FileTreeIterator workTreeIter = new FileTreeIterator(db.getWorkTree(),
+					db.getFS(), db.getConfig().get(WorkingTreeOptions.KEY));
+			walk.addTree(indexIter);
+			walk.addTree(workTreeIter);
+			walk.setFilter(PathFilter.create(path));
 
-		assertTrue(walk.next());
-		assertTrue(indexIter.idEqual(workTreeIter));
+			assertTrue(walk.next());
+			assertTrue(indexIter.idEqual(workTreeIter));
+		}
 	}
 
 	@Test
 	public void submoduleNestedWithHeadMatchingIndex() throws Exception {
-		Git git = new Git(db);
-		writeTrashFile("file.txt", "content");
-		git.add().addFilepattern("file.txt").call();
-		final RevCommit id = git.commit().setMessage("create file").call();
-		final String path = "sub/dir1/dir2";
-		DirCache cache = db.lockDirCache();
-		DirCacheEditor editor = cache.editor();
-		editor.add(new PathEdit(path) {
+		try (Git git = new Git(db);
+				TreeWalk walk = new TreeWalk(db)) {
+			writeTrashFile("file.txt", "content");
+			git.add().addFilepattern("file.txt").call();
+			final RevCommit id = git.commit().setMessage("create file").call();
+			final String path = "sub/dir1/dir2";
+			DirCache cache = db.lockDirCache();
+			DirCacheEditor editor = cache.editor();
+			editor.add(new PathEdit(path) {
 
-			public void apply(DirCacheEntry ent) {
-				ent.setFileMode(FileMode.GITLINK);
-				ent.setObjectId(id);
-			}
-		});
-		editor.commit();
+				public void apply(DirCacheEntry ent) {
+					ent.setFileMode(FileMode.GITLINK);
+					ent.setObjectId(id);
+				}
+			});
+			editor.commit();
 
-		Git.cloneRepository().setURI(db.getDirectory().toURI().toString())
-				.setDirectory(new File(db.getWorkTree(), path)).call()
-				.getRepository().close();
+			Git.cloneRepository().setURI(db.getDirectory().toURI().toString())
+					.setDirectory(new File(db.getWorkTree(), path)).call()
+					.getRepository().close();
 
-		TreeWalk walk = new TreeWalk(db);
-		DirCacheIterator indexIter = new DirCacheIterator(db.readDirCache());
-		FileTreeIterator workTreeIter = new FileTreeIterator(db);
-		walk.addTree(indexIter);
-		walk.addTree(workTreeIter);
-		walk.setFilter(PathFilter.create(path));
+			DirCacheIterator indexIter = new DirCacheIterator(db.readDirCache());
+			FileTreeIterator workTreeIter = new FileTreeIterator(db);
+			walk.addTree(indexIter);
+			walk.addTree(workTreeIter);
+			walk.setFilter(PathFilter.create(path));
 
-		assertTrue(walk.next());
-		assertTrue(indexIter.idEqual(workTreeIter));
+			assertTrue(walk.next());
+			assertTrue(indexIter.idEqual(workTreeIter));
+		}
 	}
 
 	@Test
 	public void idOffset() throws Exception {
-		Git git = new Git(db);
-		writeTrashFile("fileAinfsonly", "A");
-		File fileBinindex = writeTrashFile("fileBinindex", "B");
-		fsTick(fileBinindex);
-		git.add().addFilepattern("fileBinindex").call();
-		writeTrashFile("fileCinfsonly", "C");
-		TreeWalk tw = new TreeWalk(db);
-		DirCacheIterator indexIter = new DirCacheIterator(db.readDirCache());
-		FileTreeIterator workTreeIter = new FileTreeIterator(db);
-		tw.addTree(indexIter);
-		tw.addTree(workTreeIter);
-		workTreeIter.setDirCacheIterator(tw, 0);
-		assertEntry("d46c305e85b630558ee19cc47e73d2e5c8c64cdc", "a,", tw);
-		assertEntry("58ee403f98538ec02409538b3f80adf610accdec", "a,b", tw);
-		assertEntry("0000000000000000000000000000000000000000", "a", tw);
-		assertEntry("b8d30ff397626f0f1d3538d66067edf865e201d6", "a0b", tw);
-		// The reason for adding this test. Check that the id is correct for
-		// mixed
-		assertEntry("8c7e5a667f1b771847fe88c01c3de34413a1b220",
-				"fileAinfsonly", tw);
-		assertEntry("7371f47a6f8bd23a8fa1a8b2a9479cdd76380e54", "fileBinindex",
-				tw);
-		assertEntry("96d80cd6c4e7158dbebd0849f4fb7ce513e5828c",
-				"fileCinfsonly", tw);
-		assertFalse(tw.next());
+		try (Git git = new Git(db);
+				TreeWalk tw = new TreeWalk(db)) {
+			writeTrashFile("fileAinfsonly", "A");
+			File fileBinindex = writeTrashFile("fileBinindex", "B");
+			fsTick(fileBinindex);
+			git.add().addFilepattern("fileBinindex").call();
+			writeTrashFile("fileCinfsonly", "C");
+			DirCacheIterator indexIter = new DirCacheIterator(db.readDirCache());
+			FileTreeIterator workTreeIter = new FileTreeIterator(db);
+			tw.addTree(indexIter);
+			tw.addTree(workTreeIter);
+			workTreeIter.setDirCacheIterator(tw, 0);
+			assertEntry("d46c305e85b630558ee19cc47e73d2e5c8c64cdc", "a,", tw);
+			assertEntry("58ee403f98538ec02409538b3f80adf610accdec", "a,b", tw);
+			assertEntry("0000000000000000000000000000000000000000", "a", tw);
+			assertEntry("b8d30ff397626f0f1d3538d66067edf865e201d6", "a0b", tw);
+			// The reason for adding this test. Check that the id is correct for
+			// mixed
+			assertEntry("8c7e5a667f1b771847fe88c01c3de34413a1b220",
+					"fileAinfsonly", tw);
+			assertEntry("7371f47a6f8bd23a8fa1a8b2a9479cdd76380e54", "fileBinindex",
+					tw);
+			assertEntry("96d80cd6c4e7158dbebd0849f4fb7ce513e5828c",
+					"fileCinfsonly", tw);
+			assertFalse(tw.next());
+		}
 	}
 
 	private static void assertEntry(String sha1string, String path, TreeWalk tw)
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/ForPathTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/ForPathTest.java
index eaee8bb..3d9af35 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/ForPathTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/ForPathTest.java
@@ -84,21 +84,22 @@
 			ObjectId tree = tree0.writeTree(oi);
 
 			// Find the directories that were implicitly created above.
-			TreeWalk tw = new TreeWalk(or);
-			tw.addTree(tree);
 			ObjectId a = null;
 			ObjectId aSlashC = null;
-			while (tw.next()) {
-				if (tw.getPathString().equals("a")) {
-					a = tw.getObjectId(0);
-					tw.enterSubtree();
-					while (tw.next()) {
-						if (tw.getPathString().equals("a/c")) {
-							aSlashC = tw.getObjectId(0);
-							break;
+			try (TreeWalk tw = new TreeWalk(or)) {
+				tw.addTree(tree);
+				while (tw.next()) {
+					if (tw.getPathString().equals("a")) {
+						a = tw.getObjectId(0);
+						tw.enterSubtree();
+						while (tw.next()) {
+							if (tw.getPathString().equals("a/c")) {
+								aSlashC = tw.getObjectId(0);
+								break;
+							}
 						}
+						break;
 					}
-					break;
 				}
 			}
 
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/PostOrderTreeWalkTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/PostOrderTreeWalkTest.java
index acbbb39..6ad47c2 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/PostOrderTreeWalkTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/PostOrderTreeWalkTest.java
@@ -60,124 +60,124 @@
 public class PostOrderTreeWalkTest extends RepositoryTestCase {
 	@Test
 	public void testInitialize_NoPostOrder() throws Exception {
-		final TreeWalk tw = new TreeWalk(db);
-		assertFalse(tw.isPostOrderTraversal());
+		try (final TreeWalk tw = new TreeWalk(db)) {
+			assertFalse(tw.isPostOrderTraversal());
+		}
 	}
 
 	@Test
 	public void testInitialize_TogglePostOrder() throws Exception {
-		final TreeWalk tw = new TreeWalk(db);
-		assertFalse(tw.isPostOrderTraversal());
-		tw.setPostOrderTraversal(true);
-		assertTrue(tw.isPostOrderTraversal());
-		tw.setPostOrderTraversal(false);
-		assertFalse(tw.isPostOrderTraversal());
+		try (final TreeWalk tw = new TreeWalk(db)) {
+			assertFalse(tw.isPostOrderTraversal());
+			tw.setPostOrderTraversal(true);
+			assertTrue(tw.isPostOrderTraversal());
+			tw.setPostOrderTraversal(false);
+			assertFalse(tw.isPostOrderTraversal());
+		}
 	}
 
 	@Test
 	public void testResetDoesNotAffectPostOrder() throws Exception {
-		final TreeWalk tw = new TreeWalk(db);
-		tw.setPostOrderTraversal(true);
-		assertTrue(tw.isPostOrderTraversal());
-		tw.reset();
-		assertTrue(tw.isPostOrderTraversal());
+		try (final TreeWalk tw = new TreeWalk(db)) {
+			tw.setPostOrderTraversal(true);
+			assertTrue(tw.isPostOrderTraversal());
+			tw.reset();
+			assertTrue(tw.isPostOrderTraversal());
 
-		tw.setPostOrderTraversal(false);
-		assertFalse(tw.isPostOrderTraversal());
-		tw.reset();
-		assertFalse(tw.isPostOrderTraversal());
+			tw.setPostOrderTraversal(false);
+			assertFalse(tw.isPostOrderTraversal());
+			tw.reset();
+			assertFalse(tw.isPostOrderTraversal());
+		}
 	}
 
 	@Test
 	public void testNoPostOrder() throws Exception {
 		final DirCache tree = db.readDirCache();
-		{
-			final DirCacheBuilder b = tree.builder();
+		final DirCacheBuilder b = tree.builder();
 
-			b.add(makeFile("a"));
-			b.add(makeFile("b/c"));
-			b.add(makeFile("b/d"));
-			b.add(makeFile("q"));
+		b.add(makeFile("a"));
+		b.add(makeFile("b/c"));
+		b.add(makeFile("b/d"));
+		b.add(makeFile("q"));
 
-			b.finish();
-			assertEquals(4, tree.getEntryCount());
+		b.finish();
+		assertEquals(4, tree.getEntryCount());
+
+		try (final TreeWalk tw = new TreeWalk(db)) {
+			tw.setPostOrderTraversal(false);
+			tw.addTree(new DirCacheIterator(tree));
+
+			assertModes("a", REGULAR_FILE, tw);
+			assertModes("b", TREE, tw);
+			assertTrue(tw.isSubtree());
+			assertFalse(tw.isPostChildren());
+			tw.enterSubtree();
+			assertModes("b/c", REGULAR_FILE, tw);
+			assertModes("b/d", REGULAR_FILE, tw);
+			assertModes("q", REGULAR_FILE, tw);
 		}
-
-		final TreeWalk tw = new TreeWalk(db);
-		tw.setPostOrderTraversal(false);
-		tw.addTree(new DirCacheIterator(tree));
-
-		assertModes("a", REGULAR_FILE, tw);
-		assertModes("b", TREE, tw);
-		assertTrue(tw.isSubtree());
-		assertFalse(tw.isPostChildren());
-		tw.enterSubtree();
-		assertModes("b/c", REGULAR_FILE, tw);
-		assertModes("b/d", REGULAR_FILE, tw);
-		assertModes("q", REGULAR_FILE, tw);
 	}
 
 	@Test
 	public void testWithPostOrder_EnterSubtree() throws Exception {
 		final DirCache tree = db.readDirCache();
-		{
-			final DirCacheBuilder b = tree.builder();
+		final DirCacheBuilder b = tree.builder();
 
-			b.add(makeFile("a"));
-			b.add(makeFile("b/c"));
-			b.add(makeFile("b/d"));
-			b.add(makeFile("q"));
+		b.add(makeFile("a"));
+		b.add(makeFile("b/c"));
+		b.add(makeFile("b/d"));
+		b.add(makeFile("q"));
 
-			b.finish();
-			assertEquals(4, tree.getEntryCount());
+		b.finish();
+		assertEquals(4, tree.getEntryCount());
+
+		try (final TreeWalk tw = new TreeWalk(db)) {
+			tw.setPostOrderTraversal(true);
+			tw.addTree(new DirCacheIterator(tree));
+
+			assertModes("a", REGULAR_FILE, tw);
+
+			assertModes("b", TREE, tw);
+			assertTrue(tw.isSubtree());
+			assertFalse(tw.isPostChildren());
+			tw.enterSubtree();
+			assertModes("b/c", REGULAR_FILE, tw);
+			assertModes("b/d", REGULAR_FILE, tw);
+
+			assertModes("b", TREE, tw);
+			assertTrue(tw.isSubtree());
+			assertTrue(tw.isPostChildren());
+
+			assertModes("q", REGULAR_FILE, tw);
 		}
-
-		final TreeWalk tw = new TreeWalk(db);
-		tw.setPostOrderTraversal(true);
-		tw.addTree(new DirCacheIterator(tree));
-
-		assertModes("a", REGULAR_FILE, tw);
-
-		assertModes("b", TREE, tw);
-		assertTrue(tw.isSubtree());
-		assertFalse(tw.isPostChildren());
-		tw.enterSubtree();
-		assertModes("b/c", REGULAR_FILE, tw);
-		assertModes("b/d", REGULAR_FILE, tw);
-
-		assertModes("b", TREE, tw);
-		assertTrue(tw.isSubtree());
-		assertTrue(tw.isPostChildren());
-
-		assertModes("q", REGULAR_FILE, tw);
 	}
 
 	@Test
 	public void testWithPostOrder_NoEnterSubtree() throws Exception {
 		final DirCache tree = db.readDirCache();
-		{
-			final DirCacheBuilder b = tree.builder();
+		final DirCacheBuilder b = tree.builder();
 
-			b.add(makeFile("a"));
-			b.add(makeFile("b/c"));
-			b.add(makeFile("b/d"));
-			b.add(makeFile("q"));
+		b.add(makeFile("a"));
+		b.add(makeFile("b/c"));
+		b.add(makeFile("b/d"));
+		b.add(makeFile("q"));
 
-			b.finish();
-			assertEquals(4, tree.getEntryCount());
+		b.finish();
+		assertEquals(4, tree.getEntryCount());
+
+		try (final TreeWalk tw = new TreeWalk(db)) {
+			tw.setPostOrderTraversal(true);
+			tw.addTree(new DirCacheIterator(tree));
+
+			assertModes("a", REGULAR_FILE, tw);
+
+			assertModes("b", TREE, tw);
+			assertTrue(tw.isSubtree());
+			assertFalse(tw.isPostChildren());
+
+			assertModes("q", REGULAR_FILE, tw);
 		}
-
-		final TreeWalk tw = new TreeWalk(db);
-		tw.setPostOrderTraversal(true);
-		tw.addTree(new DirCacheIterator(tree));
-
-		assertModes("a", REGULAR_FILE, tw);
-
-		assertModes("b", TREE, tw);
-		assertTrue(tw.isSubtree());
-		assertFalse(tw.isPostChildren());
-
-		assertModes("q", REGULAR_FILE, tw);
 	}
 
 	private DirCacheEntry makeFile(final String path) throws Exception {
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/TreeWalkBasicDiffTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/TreeWalkBasicDiffTest.java
index aca7c80..c3ff7df 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/TreeWalkBasicDiffTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/TreeWalkBasicDiffTest.java
@@ -44,7 +44,6 @@
 package org.eclipse.jgit.treewalk;
 
 import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
-import static org.eclipse.jgit.lib.Constants.OBJ_TREE;
 import static org.eclipse.jgit.lib.Constants.encode;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -54,11 +53,10 @@
 import org.eclipse.jgit.lib.FileMode;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.ObjectInserter;
-import org.eclipse.jgit.lib.Tree;
+import org.eclipse.jgit.lib.TreeFormatter;
 import org.eclipse.jgit.treewalk.filter.TreeFilter;
 import org.junit.Test;
 
-@SuppressWarnings("deprecation")
 public class TreeWalkBasicDiffTest extends RepositoryTestCase {
 	@Test
 	public void testMissingSubtree_DetectFileAdded_FileModified()
@@ -72,62 +70,63 @@
 
 			// Create sub-a/empty, sub-c/empty = hello.
 			{
-				final Tree root = new Tree(db);
+				TreeFormatter root = new TreeFormatter();
 				{
-					final Tree subA = root.addTree("sub-a");
-					subA.addFile("empty").setId(aFileId);
-					subA.setId(inserter.insert(OBJ_TREE, subA.format()));
+					TreeFormatter subA = new TreeFormatter();
+					subA.append("empty", FileMode.REGULAR_FILE, aFileId);
+					root.append("sub-a", FileMode.TREE, inserter.insert(subA));
 				}
 				{
-					final Tree subC = root.addTree("sub-c");
-					subC.addFile("empty").setId(cFileId1);
-					subC.setId(inserter.insert(OBJ_TREE, subC.format()));
+					TreeFormatter subC = new TreeFormatter();
+					subC.append("empty", FileMode.REGULAR_FILE, cFileId1);
+					root.append("sub-c", FileMode.TREE, inserter.insert(subC));
 				}
-				oldTree = inserter.insert(OBJ_TREE, root.format());
+				oldTree = inserter.insert(root);
 			}
 
 			// Create sub-a/empty, sub-b/empty, sub-c/empty.
 			{
-				final Tree root = new Tree(db);
+				TreeFormatter root = new TreeFormatter();
 				{
-					final Tree subA = root.addTree("sub-a");
-					subA.addFile("empty").setId(aFileId);
-					subA.setId(inserter.insert(OBJ_TREE, subA.format()));
+					TreeFormatter subA = new TreeFormatter();
+					subA.append("empty", FileMode.REGULAR_FILE, aFileId);
+					root.append("sub-a", FileMode.TREE, inserter.insert(subA));
 				}
 				{
-					final Tree subB = root.addTree("sub-b");
-					subB.addFile("empty").setId(bFileId);
-					subB.setId(inserter.insert(OBJ_TREE, subB.format()));
+					TreeFormatter subB = new TreeFormatter();
+					subB.append("empty", FileMode.REGULAR_FILE, bFileId);
+					root.append("sub-b", FileMode.TREE, inserter.insert(subB));
 				}
 				{
-					final Tree subC = root.addTree("sub-c");
-					subC.addFile("empty").setId(cFileId2);
-					subC.setId(inserter.insert(OBJ_TREE, subC.format()));
+					TreeFormatter subC = new TreeFormatter();
+					subC.append("empty", FileMode.REGULAR_FILE, cFileId2);
+					root.append("sub-c", FileMode.TREE, inserter.insert(subC));
 				}
-				newTree = inserter.insert(OBJ_TREE, root.format());
+				newTree = inserter.insert(root);
 			}
 			inserter.flush();
 		}
 
-		final TreeWalk tw = new TreeWalk(db);
-		tw.reset(oldTree, newTree);
-		tw.setRecursive(true);
-		tw.setFilter(TreeFilter.ANY_DIFF);
+		try (TreeWalk tw = new TreeWalk(db)) {
+			tw.reset(oldTree, newTree);
+			tw.setRecursive(true);
+			tw.setFilter(TreeFilter.ANY_DIFF);
 
-		assertTrue(tw.next());
-		assertEquals("sub-b/empty", tw.getPathString());
-		assertEquals(FileMode.MISSING, tw.getFileMode(0));
-		assertEquals(FileMode.REGULAR_FILE, tw.getFileMode(1));
-		assertEquals(ObjectId.zeroId(), tw.getObjectId(0));
-		assertEquals(bFileId, tw.getObjectId(1));
+			assertTrue(tw.next());
+			assertEquals("sub-b/empty", tw.getPathString());
+			assertEquals(FileMode.MISSING, tw.getFileMode(0));
+			assertEquals(FileMode.REGULAR_FILE, tw.getFileMode(1));
+			assertEquals(ObjectId.zeroId(), tw.getObjectId(0));
+			assertEquals(bFileId, tw.getObjectId(1));
 
-		assertTrue(tw.next());
-		assertEquals("sub-c/empty", tw.getPathString());
-		assertEquals(FileMode.REGULAR_FILE, tw.getFileMode(0));
-		assertEquals(FileMode.REGULAR_FILE, tw.getFileMode(1));
-		assertEquals(cFileId1, tw.getObjectId(0));
-		assertEquals(cFileId2, tw.getObjectId(1));
+			assertTrue(tw.next());
+			assertEquals("sub-c/empty", tw.getPathString());
+			assertEquals(FileMode.REGULAR_FILE, tw.getFileMode(0));
+			assertEquals(FileMode.REGULAR_FILE, tw.getFileMode(1));
+			assertEquals(cFileId1, tw.getObjectId(0));
+			assertEquals(cFileId2, tw.getObjectId(1));
 
-		assertFalse(tw.next());
+			assertFalse(tw.next());
+		}
 	}
 }
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/TreeWalkJava7Test.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/TreeWalkJava7Test.java
index bb1f2a6..ba8f194 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/TreeWalkJava7Test.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/TreeWalkJava7Test.java
@@ -55,17 +55,19 @@
 public class TreeWalkJava7Test extends RepositoryTestCase {
 	@Test
 	public void testSymlinkToDirNotRecursingViaSymlink() throws Exception {
+		org.junit.Assume.assumeTrue(FS.DETECTED.supportsSymlinks());
 		FS fs = db.getFS();
 		assertTrue(fs.supportsSymlinks());
 		writeTrashFile("target/data", "targetdata");
 		fs.createSymLink(new File(trash, "link"), "target");
-		TreeWalk tw = new TreeWalk(db);
-		tw.setRecursive(true);
-		tw.addTree(new FileTreeIterator(db));
-		assertTrue(tw.next());
-		assertEquals("link", tw.getPathString());
-		assertTrue(tw.next());
-		assertEquals("target/data", tw.getPathString());
-		assertFalse(tw.next());
+		try (TreeWalk tw = new TreeWalk(db)) {
+			tw.setRecursive(true);
+			tw.addTree(new FileTreeIterator(db));
+			assertTrue(tw.next());
+			assertEquals("link", tw.getPathString());
+			assertTrue(tw.next());
+			assertEquals("target/data", tw.getPathString());
+			assertFalse(tw.next());
+		}
 	}
 }
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/filter/InterIndexDiffFilterTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/filter/InterIndexDiffFilterTest.java
index b3aa0ee..9f0f067 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/filter/InterIndexDiffFilterTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/filter/InterIndexDiffFilterTest.java
@@ -76,10 +76,11 @@
 	public void testEmpty() throws IOException {
 		DirCache dc1 = DirCache.newInCore();
 		DirCache dc2 = DirCache.newInCore();
-		TreeWalk tw = new TreeWalk(db);
-		tw.addTree(new DirCacheIterator(dc1));
-		tw.addTree(new DirCacheIterator(dc2));
-		assertFalse(tw.next());
+		try (TreeWalk tw = new TreeWalk(db)) {
+			tw.addTree(new DirCacheIterator(dc1));
+			tw.addTree(new DirCacheIterator(dc2));
+			assertFalse(tw.next());
+		}
 	}
 
 	static final class AddEdit extends PathEdit {
@@ -124,14 +125,15 @@
 		editor.add(new AddEdit("a/a", FileMode.REGULAR_FILE, id("a"), 1, false));
 		editor.finish();
 
-		TreeWalk tw = new TreeWalk(db);
-		tw.setRecursive(true);
-		tw.addTree(new DirCacheIterator(dc1));
-		tw.addTree(new DirCacheIterator(dc2));
-		tw.setFilter(InterIndexDiffFilter.INSTANCE);
-		assertTrue(tw.next());
-		assertEquals("a/a", tw.getPathString());
-		assertFalse(tw.next());
+		try (TreeWalk tw = new TreeWalk(db)) {
+			tw.setRecursive(true);
+			tw.addTree(new DirCacheIterator(dc1));
+			tw.addTree(new DirCacheIterator(dc2));
+			tw.setFilter(InterIndexDiffFilter.INSTANCE);
+			assertTrue(tw.next());
+			assertEquals("a/a", tw.getPathString());
+			assertFalse(tw.next());
+		}
 	}
 
 	@Test
@@ -145,13 +147,14 @@
 		ed2.add(new AddEdit("a/a", FileMode.REGULAR_FILE, id("a"), 1, false));
 		ed2.finish();
 
-		TreeWalk tw = new TreeWalk(db);
-		tw.setRecursive(true);
-		tw.addTree(new DirCacheIterator(dc1));
-		tw.addTree(new DirCacheIterator(dc2));
-		tw.setFilter(InterIndexDiffFilter.INSTANCE);
+		try (TreeWalk tw = new TreeWalk(db)) {
+			tw.setRecursive(true);
+			tw.addTree(new DirCacheIterator(dc1));
+			tw.addTree(new DirCacheIterator(dc2));
+			tw.setFilter(InterIndexDiffFilter.INSTANCE);
 
-		assertFalse(tw.next());
+			assertFalse(tw.next());
+		}
 	}
 
 	@Test
@@ -165,15 +168,16 @@
 		ed2.add(new AddEdit("a/a", FileMode.REGULAR_FILE, id("a"), 1, true));
 		ed2.finish();
 
-		TreeWalk tw = new TreeWalk(db);
-		tw.setRecursive(true);
-		tw.addTree(new DirCacheIterator(dc1));
-		tw.addTree(new DirCacheIterator(dc2));
-		tw.setFilter(InterIndexDiffFilter.INSTANCE);
+		try (TreeWalk tw = new TreeWalk(db)) {
+			tw.setRecursive(true);
+			tw.addTree(new DirCacheIterator(dc1));
+			tw.addTree(new DirCacheIterator(dc2));
+			tw.setFilter(InterIndexDiffFilter.INSTANCE);
 
-		assertTrue(tw.next());
-		assertEquals("a/a", tw.getPathString());
-		assertFalse(tw.next());
+			assertTrue(tw.next());
+			assertEquals("a/a", tw.getPathString());
+			assertFalse(tw.next());
+		}
 	}
 
 	@Test
@@ -188,12 +192,13 @@
 		ed2.add(new AddEdit("a/a", FileMode.REGULAR_FILE, id("b"), 1, true));
 		ed2.finish();
 
-		TreeWalk tw = new TreeWalk(db);
-		tw.setRecursive(true);
-		tw.addTree(new DirCacheIterator(dc1));
-		tw.addTree(new DirCacheIterator(dc2));
-		tw.setFilter(InterIndexDiffFilter.INSTANCE);
+		try (TreeWalk tw = new TreeWalk(db)) {
+			tw.setRecursive(true);
+			tw.addTree(new DirCacheIterator(dc1));
+			tw.addTree(new DirCacheIterator(dc2));
+			tw.setFilter(InterIndexDiffFilter.INSTANCE);
 
-		assertFalse(tw.next());
+			assertFalse(tw.next());
+		}
 	}
 }
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/filter/PathFilterGroupTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/filter/PathFilterGroupTest.java
index d0062e1..5edc192 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/filter/PathFilterGroupTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/filter/PathFilterGroupTest.java
@@ -43,11 +43,18 @@
 
 package org.eclipse.jgit.treewalk.filter;
 
+import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
 import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
 
 import org.eclipse.jgit.dircache.DirCache;
 import org.eclipse.jgit.dircache.DirCacheEditor;
@@ -58,6 +65,7 @@
 import org.eclipse.jgit.errors.StopWalkException;
 import org.eclipse.jgit.lib.FileMode;
 import org.eclipse.jgit.lib.ObjectReader;
+import org.eclipse.jgit.lib.Sets;
 import org.eclipse.jgit.treewalk.TreeWalk;
 import org.junit.Before;
 import org.junit.Test;
@@ -66,6 +74,8 @@
 
 	private TreeFilter filter;
 
+	private Map<String, TreeFilter> singles;
+
 	@Before
 	public void setup() {
 		// @formatter:off
@@ -81,64 +91,75 @@
 				};
 		// @formatter:on
 		filter = PathFilterGroup.createFromStrings(paths);
+		singles = new HashMap<>();
+		for (String path : paths) {
+			singles.put(path, PathFilterGroup.createFromStrings(path));
+		}
 	}
 
 	@Test
 	public void testExact() throws MissingObjectException,
 			IncorrectObjectTypeException, IOException {
-		assertTrue(filter.include(fakeWalk("a")));
-		assertTrue(filter.include(fakeWalk("b/c")));
-		assertTrue(filter.include(fakeWalk("c/d/e")));
-		assertTrue(filter.include(fakeWalk("c/d/f")));
-		assertTrue(filter.include(fakeWalk("d/e/f/g")));
-		assertTrue(filter.include(fakeWalk("d/e/f/g.x")));
+		assertMatches(Sets.of("a"), fakeWalk("a"));
+		assertMatches(Sets.of("b/c"), fakeWalk("b/c"));
+		assertMatches(Sets.of("c/d/e"), fakeWalk("c/d/e"));
+		assertMatches(Sets.of("c/d/f"), fakeWalk("c/d/f"));
+		assertMatches(Sets.of("d/e/f/g"), fakeWalk("d/e/f/g"));
+		assertMatches(Sets.of("d/e/f/g.x"), fakeWalk("d/e/f/g.x"));
 	}
 
 	@Test
 	public void testNoMatchButClose() throws MissingObjectException,
 			IncorrectObjectTypeException, IOException {
-		assertFalse(filter.include(fakeWalk("a+")));
-		assertFalse(filter.include(fakeWalk("b+/c")));
-		assertFalse(filter.include(fakeWalk("c+/d/e")));
-		assertFalse(filter.include(fakeWalk("c+/d/f")));
-		assertFalse(filter.include(fakeWalk("c/d.a")));
-		assertFalse(filter.include(fakeWalk("d+/e/f/g")));
+		assertNoMatches(fakeWalk("a+"));
+		assertNoMatches(fakeWalk("b+/c"));
+		assertNoMatches(fakeWalk("c+/d/e"));
+		assertNoMatches(fakeWalk("c+/d/f"));
+		assertNoMatches(fakeWalk("c/d.a"));
+		assertNoMatches(fakeWalk("d+/e/f/g"));
 	}
 
 	@Test
 	public void testJustCommonPrefixIsNotMatch() throws MissingObjectException,
 			IncorrectObjectTypeException, IOException {
-		assertFalse(filter.include(fakeWalk("b/a")));
-		assertFalse(filter.include(fakeWalk("b/d")));
-		assertFalse(filter.include(fakeWalk("c/d/a")));
-		assertFalse(filter.include(fakeWalk("d/e/e")));
+		assertNoMatches(fakeWalk("b/a"));
+		assertNoMatches(fakeWalk("b/d"));
+		assertNoMatches(fakeWalk("c/d/a"));
+		assertNoMatches(fakeWalk("d/e/e"));
+		assertNoMatches(fakeWalk("d/e/f/g.y"));
 	}
 
 	@Test
 	public void testKeyIsPrefixOfFilter() throws MissingObjectException,
 			IncorrectObjectTypeException, IOException {
-		assertTrue(filter.include(fakeWalk("b")));
-		assertTrue(filter.include(fakeWalk("c/d")));
-		assertTrue(filter.include(fakeWalk("c/d")));
-		assertTrue(filter.include(fakeWalk("c")));
-		assertTrue(filter.include(fakeWalk("d/e/f")));
-		assertTrue(filter.include(fakeWalk("d/e")));
-		assertTrue(filter.include(fakeWalk("d")));
+		assertMatches(Sets.of("b/c"), fakeWalkAtSubtree("b"));
+		assertMatches(Sets.of("c/d/e", "c/d/f"), fakeWalkAtSubtree("c/d"));
+		assertMatches(Sets.of("c/d/e", "c/d/f"), fakeWalkAtSubtree("c"));
+		assertMatches(Sets.of("d/e/f/g", "d/e/f/g.x"),
+				fakeWalkAtSubtree("d/e/f"));
+		assertMatches(Sets.of("d/e/f/g", "d/e/f/g.x"),
+				fakeWalkAtSubtree("d/e"));
+		assertMatches(Sets.of("d/e/f/g", "d/e/f/g.x"), fakeWalkAtSubtree("d"));
+
+		assertNoMatches(fakeWalk("b"));
+		assertNoMatches(fakeWalk("c/d"));
+		assertNoMatches(fakeWalk("c"));
+		assertNoMatches(fakeWalk("d/e/f"));
+		assertNoMatches(fakeWalk("d/e"));
+		assertNoMatches(fakeWalk("d"));
+
 	}
 
 	@Test
 	public void testFilterIsPrefixOfKey() throws MissingObjectException,
 			IncorrectObjectTypeException, IOException {
-		assertTrue(filter.include(fakeWalk("a/b")));
-		assertTrue(filter.include(fakeWalk("b/c/d")));
-		assertTrue(filter.include(fakeWalk("c/d/e/f")));
-		assertTrue(filter.include(fakeWalk("c/d/f/g")));
-		assertTrue(filter.include(fakeWalk("d/e/f/g/h")));
-		assertTrue(filter.include(fakeWalk("d/e/f/g/y")));
-		assertTrue(filter.include(fakeWalk("d/e/f/g.x/h")));
-		// listed before g/y, so can't StopWalk here, but it's not included
-		// either
-		assertFalse(filter.include(fakeWalk("d/e/f/g.y")));
+		assertMatches(Sets.of("a"), fakeWalk("a/b"));
+		assertMatches(Sets.of("b/c"), fakeWalk("b/c/d"));
+		assertMatches(Sets.of("c/d/e"), fakeWalk("c/d/e/f"));
+		assertMatches(Sets.of("c/d/f"), fakeWalk("c/d/f/g"));
+		assertMatches(Sets.of("d/e/f/g"), fakeWalk("d/e/f/g/h"));
+		assertMatches(Sets.of("d/e/f/g"), fakeWalk("d/e/f/g/y"));
+		assertMatches(Sets.of("d/e/f/g.x"), fakeWalk("d/e/f/g.x/h"));
 	}
 
 	@Test
@@ -182,6 +203,10 @@
 		// less obvious #2 due to git sorting order
 		filter.include(fakeWalk("d/e/f/g/h.txt"));
 
+		// listed before g/y, so can't StopWalk here
+		filter.include(fakeWalk("d/e/f/g.y"));
+		singles.get("d/e/f/g").include(fakeWalk("d/e/f/g.y"));
+
 		// non-ascii
 		try {
 			filter.include(fakeWalk("\u00C0"));
@@ -191,6 +216,44 @@
 		}
 	}
 
+	private void assertNoMatches(TreeWalk tw) throws MissingObjectException,
+			IncorrectObjectTypeException, IOException {
+		assertMatches(Sets.<String> of(), tw);
+	}
+
+	private void assertMatches(Set<String> expect, TreeWalk tw)
+			throws MissingObjectException, IncorrectObjectTypeException,
+			IOException {
+		List<String> actual = new ArrayList<>();
+		for (String path : singles.keySet()) {
+			if (includes(singles.get(path), tw)) {
+				actual.add(path);
+			}
+		}
+
+		String[] e = expect.toArray(new String[expect.size()]);
+		String[] a = actual.toArray(new String[actual.size()]);
+		Arrays.sort(e);
+		Arrays.sort(a);
+		assertArrayEquals(e, a);
+
+		if (expect.isEmpty()) {
+			assertFalse(includes(filter, tw));
+		} else {
+			assertTrue(includes(filter, tw));
+		}
+	}
+
+	private static boolean includes(TreeFilter f, TreeWalk tw)
+			throws MissingObjectException, IncorrectObjectTypeException,
+			IOException {
+		try {
+			return f.include(tw);
+		} catch (StopWalkException e) {
+			return false;
+		}
+	}
+
 	TreeWalk fakeWalk(final String path) throws IOException {
 		DirCache dc = DirCache.newInCore();
 		DirCacheEditor dce = dc.editor();
@@ -210,4 +273,25 @@
 		return ret;
 	}
 
+	TreeWalk fakeWalkAtSubtree(final String path) throws IOException {
+		DirCache dc = DirCache.newInCore();
+		DirCacheEditor dce = dc.editor();
+		dce.add(new DirCacheEditor.PathEdit(path + "/README") {
+			public void apply(DirCacheEntry ent) {
+				ent.setFileMode(FileMode.REGULAR_FILE);
+			}
+		});
+		dce.finish();
+
+		TreeWalk ret = new TreeWalk((ObjectReader) null);
+		ret.addTree(new DirCacheIterator(dc));
+		ret.next();
+		while (!path.equals(ret.getPathString())) {
+			if (ret.isSubtree()) {
+				ret.enterSubtree();
+			}
+			ret.next();
+		}
+		return ret;
+	}
 }
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/filter/PathSuffixFilterTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/filter/PathSuffixFilterTest.java
index d871c5e..3885c41 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/filter/PathSuffixFilterTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/filter/PathSuffixFilterTest.java
@@ -113,15 +113,16 @@
 
 	private List<String> getMatchingPaths(String suffixFilter,
 			final ObjectId treeId, boolean recursiveWalk) throws IOException {
-		final TreeWalk tw = new TreeWalk(db);
-		tw.setFilter(PathSuffixFilter.create(suffixFilter));
-		tw.setRecursive(recursiveWalk);
-		tw.addTree(treeId);
+		try (final TreeWalk tw = new TreeWalk(db)) {
+			tw.setFilter(PathSuffixFilter.create(suffixFilter));
+			tw.setRecursive(recursiveWalk);
+			tw.addTree(treeId);
 
-		List<String> paths = new ArrayList<String>();
-		while (tw.next())
-			paths.add(tw.getPathString());
-		return paths;
+			List<String> paths = new ArrayList<String>();
+			while (tw.next())
+				paths.add(tw.getPathString());
+			return paths;
+		}
 	}
 
 }
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/filter/TreeFilterTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/filter/TreeFilterTest.java
index 09007e7..c3423b6 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/filter/TreeFilterTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/filter/TreeFilterTest.java
@@ -55,9 +55,10 @@
 public class TreeFilterTest extends RepositoryTestCase {
 	@Test
 	public void testALL_IncludesAnything() throws Exception {
-		final TreeWalk tw = new TreeWalk(db);
-		tw.addTree(new EmptyTreeIterator());
-		assertTrue(TreeFilter.ALL.include(tw));
+		try (final TreeWalk tw = new TreeWalk(db)) {
+			tw.addTree(new EmptyTreeIterator());
+			assertTrue(TreeFilter.ALL.include(tw));
+		}
 	}
 
 	@Test
@@ -72,16 +73,18 @@
 
 	@Test
 	public void testNotALL_IncludesNothing() throws Exception {
-		final TreeWalk tw = new TreeWalk(db);
-		tw.addTree(new EmptyTreeIterator());
-		assertFalse(TreeFilter.ALL.negate().include(tw));
+		try (final TreeWalk tw = new TreeWalk(db)) {
+			tw.addTree(new EmptyTreeIterator());
+			assertFalse(TreeFilter.ALL.negate().include(tw));
+		}
 	}
 
 	@Test
 	public void testANY_DIFF_IncludesSingleTreeCase() throws Exception {
-		final TreeWalk tw = new TreeWalk(db);
-		tw.addTree(new EmptyTreeIterator());
-		assertTrue(TreeFilter.ANY_DIFF.include(tw));
+		try (final TreeWalk tw = new TreeWalk(db)) {
+			tw.addTree(new EmptyTreeIterator());
+			assertTrue(TreeFilter.ANY_DIFF.include(tw));
+		}
 	}
 
 	@Test
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/ChangeIdUtilTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/ChangeIdUtilTest.java
index 7273cdb..aaeb79c 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/ChangeIdUtilTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/ChangeIdUtilTest.java
@@ -45,7 +45,6 @@
 
 import static org.junit.Assert.assertEquals;
 
-import java.io.IOException;
 import java.util.concurrent.TimeUnit;
 
 import org.eclipse.jgit.junit.MockSystemReader;
@@ -113,7 +112,7 @@
 	}
 
 	@Test
-	public void testId() throws IOException {
+	public void testId() {
 		String msg = "A\nMessage\n";
 		ObjectId id = ChangeIdUtil.computeChangeId(treeId, parentId, p, q, msg);
 		assertEquals("73f3751208ac92cbb76f9a26ac4a0d9d472e381b", ObjectId
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FSJava7Test.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FSJava7Test.java
index e6a244e..53b6fec 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FSJava7Test.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FSJava7Test.java
@@ -57,6 +57,7 @@
 
 import org.eclipse.jgit.junit.RepositoryTestCase;
 import org.junit.After;
+import org.junit.Assume;
 import org.junit.Before;
 import org.junit.Test;
 
@@ -87,6 +88,7 @@
 	 */
 	@Test
 	public void testSymlinkAttributes() throws IOException, InterruptedException {
+		Assume.assumeTrue(FS.DETECTED.supportsSymlinks());
 		FS fs = FS.DETECTED;
 		File link = new File(trash, "ä");
 		File target = new File(trash, "å");
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FileUtilTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FileUtilTest.java
index 0d7d31b..1f78e02 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FileUtilTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FileUtilTest.java
@@ -54,6 +54,7 @@
 
 import org.eclipse.jgit.junit.JGitTestUtil;
 import org.junit.After;
+import org.junit.Assume;
 import org.junit.Before;
 import org.junit.Test;
 
@@ -424,19 +425,28 @@
 	@Test
 	public void testCreateSymlink() throws IOException {
 		FS fs = FS.DETECTED;
-		try {
-			fs.createSymLink(new File(trash, "x"), "y");
-		} catch (IOException e) {
-			if (fs.supportsSymlinks())
-				fail("FS claims to support symlinks but attempt to create symlink failed");
-			return;
-		}
-		assertTrue(fs.supportsSymlinks());
+		// show test as ignored if the FS doesn't support symlinks
+		Assume.assumeTrue(fs.supportsSymlinks());
+		fs.createSymLink(new File(trash, "x"), "y");
 		String target = fs.readSymLink(new File(trash, "x"));
 		assertEquals("y", target);
 	}
 
 	@Test
+	public void testCreateSymlinkOverrideExisting() throws IOException {
+		FS fs = FS.DETECTED;
+		// show test as ignored if the FS doesn't support symlinks
+		Assume.assumeTrue(fs.supportsSymlinks());
+		File file = new File(trash, "x");
+		fs.createSymLink(file, "y");
+		String target = fs.readSymLink(file);
+		assertEquals("y", target);
+		fs.createSymLink(file, "z");
+		target = fs.readSymLink(file);
+		assertEquals("z", target);
+	}
+
+	@Test
 	public void testRelativize_doc() {
 		// This is the javadoc example
 		String base = toOSPathString("c:\\Users\\jdoe\\eclipse\\git\\project");
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FileUtils7Test.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FileUtils7Test.java
index 4625f30..cc1fdc2 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FileUtils7Test.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FileUtils7Test.java
@@ -73,6 +73,7 @@
 	@Test
 	public void testDeleteSymlinkToDirectoryDoesNotDeleteTarget()
 			throws IOException {
+		org.junit.Assume.assumeTrue(FS.DETECTED.supportsSymlinks());
 		FS fs = FS.DETECTED;
 		File dir = new File(trash, "dir");
 		File file = new File(dir, "file");
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 b14a9bf..e07076e 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
@@ -112,7 +112,8 @@
 		ByteArrayOutputStream out = new ByteArrayOutputStream();
 		git.commit().setMessage("commit")
 				.setHookOutputStream(new PrintStream(out)).call();
-		assertEquals(".git/COMMIT_EDITMSG\n", out.toString("UTF-8"));
+		assertEquals(".git/COMMIT_EDITMSG\n",
+				out.toString("UTF-8"));
 	}
 
 	@Test
@@ -144,6 +145,7 @@
 				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",
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/PathsTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/PathsTest.java
new file mode 100644
index 0000000..7542ec8
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/PathsTest.java
@@ -0,0 +1,118 @@
+/*
+ * 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.util;
+
+import static org.eclipse.jgit.util.Paths.compare;
+import static org.eclipse.jgit.util.Paths.compareSameName;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.FileMode;
+import org.junit.Test;
+
+public class PathsTest {
+	@Test
+	public void testStripTrailingSeparator() {
+		assertNull(Paths.stripTrailingSeparator(null));
+		assertEquals("", Paths.stripTrailingSeparator(""));
+		assertEquals("a", Paths.stripTrailingSeparator("a"));
+		assertEquals("a/boo", Paths.stripTrailingSeparator("a/boo"));
+		assertEquals("a/boo", Paths.stripTrailingSeparator("a/boo/"));
+		assertEquals("a/boo", Paths.stripTrailingSeparator("a/boo//"));
+		assertEquals("a/boo", Paths.stripTrailingSeparator("a/boo///"));
+	}
+
+	@Test
+	public void testPathCompare() {
+		byte[] a = Constants.encode("afoo/bar.c");
+		byte[] b = Constants.encode("bfoo/bar.c");
+
+		assertEquals(0, compare(a, 1, a.length, 0, b, 1, b.length, 0));
+		assertEquals(-1, compare(a, 0, a.length, 0, b, 0, b.length, 0));
+		assertEquals(1, compare(b, 0, b.length, 0, a, 0, a.length, 0));
+
+		a = Constants.encode("a");
+		b = Constants.encode("aa");
+		assertEquals(-97, compare(a, 0, a.length, 0, b, 0, b.length, 0));
+		assertEquals(0, compare(a, 0, a.length, 0, b, 0, 1, 0));
+		assertEquals(0, compare(a, 0, a.length, 0, b, 1, 2, 0));
+		assertEquals(0, compareSameName(a, 0, a.length, b, 1, b.length, 0));
+		assertEquals(0, compareSameName(a, 0, a.length, b, 0, 1, 0));
+		assertEquals(-50, compareSameName(a, 0, a.length, b, 0, b.length, 0));
+		assertEquals(97, compareSameName(b, 0, b.length, a, 0, a.length, 0));
+
+		a = Constants.encode("a");
+		b = Constants.encode("a");
+		assertEquals(0, compare(
+				a, 0, a.length, FileMode.TREE.getBits(),
+				b, 0, b.length, FileMode.TREE.getBits()));
+		assertEquals(0, compare(
+				a, 0, a.length, FileMode.REGULAR_FILE.getBits(),
+				b, 0, b.length, FileMode.REGULAR_FILE.getBits()));
+		assertEquals(-47, compare(
+				a, 0, a.length, FileMode.REGULAR_FILE.getBits(),
+				b, 0, b.length, FileMode.TREE.getBits()));
+		assertEquals(47, compare(
+				a, 0, a.length, FileMode.TREE.getBits(),
+				b, 0, b.length, FileMode.REGULAR_FILE.getBits()));
+
+		assertEquals(0, compareSameName(
+				a, 0, a.length,
+				b, 0, b.length, FileMode.TREE.getBits()));
+		assertEquals(0, compareSameName(
+				a, 0, a.length,
+				b, 0, b.length, FileMode.REGULAR_FILE.getBits()));
+
+		a = Constants.encode("a.c");
+		b = Constants.encode("a");
+		byte[] c = Constants.encode("a0c");
+		assertEquals(-1, compare(
+				a, 0, a.length, FileMode.REGULAR_FILE.getBits(),
+				b, 0, b.length, FileMode.TREE.getBits()));
+		assertEquals(-1, compare(
+				b, 0, b.length, FileMode.TREE.getBits(),
+				c, 0, c.length, FileMode.REGULAR_FILE.getBits()));
+	}
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RunExternalScriptTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RunExternalScriptTest.java
new file mode 100644
index 0000000..7c0985e
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RunExternalScriptTest.java
@@ -0,0 +1,212 @@
+/*
+ * Copyright (C) 2015, 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.util;
+
+import static org.junit.Assert.assertEquals;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.eclipse.jgit.junit.JGitTestUtil;
+import org.eclipse.jgit.util.FS.ExecutionResult;
+import org.junit.Before;
+import org.junit.Test;
+
+public class RunExternalScriptTest {
+	private static final String LF = "\n";
+
+	private ByteArrayOutputStream out;
+
+	private ByteArrayOutputStream err;
+
+	@Before
+	public void setUp() throws Exception {
+		out = new ByteArrayOutputStream();
+		err = new ByteArrayOutputStream();
+	}
+
+	@Test
+	public void testCopyStdIn() throws IOException, InterruptedException {
+		String inputStr = "a\nb\rc\r\nd";
+		File script = writeTempFile("cat -");
+		int rc = FS.DETECTED.runProcess(
+				new ProcessBuilder("sh", script.getPath()), out, err,
+				new ByteArrayInputStream(inputStr.getBytes()));
+		assertEquals(0, rc);
+		assertEquals(inputStr, new String(out.toByteArray()));
+		assertEquals("", new String(err.toByteArray()));
+	}
+
+	@Test
+	public void testCopyNullStdIn() throws IOException, InterruptedException {
+		File script = writeTempFile("cat -");
+		int rc = FS.DETECTED.runProcess(
+				new ProcessBuilder("sh", script.getPath()), out, err,
+				(InputStream) null);
+		assertEquals(0, rc);
+		assertEquals("", new String(out.toByteArray()));
+		assertEquals("", new String(err.toByteArray()));
+	}
+
+	@Test
+	public void testArguments() throws IOException, InterruptedException {
+		File script = writeTempFile("echo $#,$1,$2,$3,$4,$5,$6");
+		int rc = FS.DETECTED.runProcess(
+				new ProcessBuilder("sh",
+				script.getPath(), "a", "b", "c"), out, err, (InputStream) null);
+		assertEquals(0, rc);
+		assertEquals("3,a,b,c,,,\n", new String(out.toByteArray()));
+		assertEquals("", new String(err.toByteArray()));
+	}
+
+	@Test
+	public void testRc() throws IOException, InterruptedException {
+		File script = writeTempFile("exit 3");
+		int rc = FS.DETECTED.runProcess(
+				new ProcessBuilder("sh", script.getPath(), "a", "b", "c"),
+				out, err, (InputStream) null);
+		assertEquals(3, rc);
+		assertEquals("", new String(out.toByteArray()));
+		assertEquals("", new String(err.toByteArray()));
+	}
+
+	@Test
+	public void testNullStdout() throws IOException, InterruptedException {
+		File script = writeTempFile("echo hi");
+		int rc = FS.DETECTED.runProcess(
+				new ProcessBuilder("sh", script.getPath()), null, err,
+				(InputStream) null);
+		assertEquals(0, rc);
+		assertEquals("", new String(out.toByteArray()));
+		assertEquals("", new String(err.toByteArray()));
+	}
+
+	@Test
+	public void testStdErr() throws IOException, InterruptedException {
+		File script = writeTempFile("echo hi >&2");
+		int rc = FS.DETECTED.runProcess(
+				new ProcessBuilder("sh", script.getPath()), null, err,
+				(InputStream) null);
+		assertEquals(0, rc);
+		assertEquals("", new String(out.toByteArray()));
+		assertEquals("hi" + LF, new String(err.toByteArray()));
+	}
+
+	@Test
+	public void testAllTogetherBin() throws IOException, InterruptedException {
+		String inputStr = "a\nb\rc\r\nd";
+		File script = writeTempFile("echo $#,$1,$2,$3,$4,$5,$6 >&2 ; cat -; exit 5");
+		int rc = FS.DETECTED.runProcess(
+				new ProcessBuilder("sh", script.getPath(), "a", "b", "c"),
+				out, err, new ByteArrayInputStream(inputStr.getBytes()));
+		assertEquals(5, rc);
+		assertEquals(inputStr, new String(out.toByteArray()));
+		assertEquals("3,a,b,c,,," + LF, new String(err.toByteArray()));
+	}
+
+	@Test(expected = IOException.class)
+	public void testWrongSh() throws IOException, InterruptedException {
+		File script = writeTempFile("cat -");
+		FS.DETECTED.runProcess(
+				new ProcessBuilder("/bin/sh-foo", script.getPath(), "a", "b",
+						"c"), out, err, (InputStream) null);
+	}
+
+	@Test
+	public void testWrongScript() throws IOException, InterruptedException {
+		File script = writeTempFile("cat-foo -");
+		int rc = FS.DETECTED.runProcess(
+				new ProcessBuilder("sh", script.getPath(), "a", "b", "c"),
+				out, err, (InputStream) null);
+		assertEquals(127, rc);
+	}
+
+	@Test
+	public void testCopyStdInExecute()
+			throws IOException, InterruptedException {
+		String inputStr = "a\nb\rc\r\nd";
+		File script = writeTempFile("cat -");
+		ProcessBuilder pb = new ProcessBuilder("sh", script.getPath());
+		ExecutionResult res = FS.DETECTED.execute(pb,
+				new ByteArrayInputStream(inputStr.getBytes()));
+		assertEquals(0, res.getRc());
+		assertEquals(inputStr, new String(res.getStdout().toByteArray()));
+		assertEquals("", new String(res.getStderr().toByteArray()));
+	}
+
+	@Test
+	public void testStdErrExecute() throws IOException, InterruptedException {
+		File script = writeTempFile("echo hi >&2");
+		ProcessBuilder pb = new ProcessBuilder("sh", script.getPath());
+		ExecutionResult res = FS.DETECTED.execute(pb, null);
+		assertEquals(0, res.getRc());
+		assertEquals("", new String(res.getStdout().toByteArray()));
+		assertEquals("hi" + LF, new String(res.getStderr().toByteArray()));
+	}
+
+	@Test
+	public void testAllTogetherBinExecute()
+			throws IOException, InterruptedException {
+		String inputStr = "a\nb\rc\r\nd";
+		File script = writeTempFile(
+				"echo $#,$1,$2,$3,$4,$5,$6 >&2 ; cat -; exit 5");
+		ProcessBuilder pb = new ProcessBuilder("sh", script.getPath(), "a",
+				"b", "c");
+		ExecutionResult res = FS.DETECTED.execute(pb,
+				new ByteArrayInputStream(inputStr.getBytes()));
+		assertEquals(5, res.getRc());
+		assertEquals(inputStr, new String(res.getStdout().toByteArray()));
+		assertEquals("3,a,b,c,,," + LF,
+				new String(res.getStderr().toByteArray()));
+	}
+
+	private File writeTempFile(String body) throws IOException {
+		File f = File.createTempFile("RunProcessTestScript_", "");
+		JGitTestUtil.write(f, body);
+		return f;
+	}
+}
diff --git a/org.eclipse.jgit.ui/BUCK b/org.eclipse.jgit.ui/BUCK
new file mode 100644
index 0000000..fcd87cf
--- /dev/null
+++ b/org.eclipse.jgit.ui/BUCK
@@ -0,0 +1,7 @@
+java_library(
+  name = 'ui',
+  srcs = glob(['src/**']),
+  resources = glob(['resources/**']),
+  deps = ['//org.eclipse.jgit:jgit'],
+  visibility = ['PUBLIC'],
+)
diff --git a/org.eclipse.jgit.ui/META-INF/MANIFEST.MF b/org.eclipse.jgit.ui/META-INF/MANIFEST.MF
index 3586cbc..f9e1a39 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.1.2.201602141800-r
+Bundle-Version: 4.2.1.qualifier
 Bundle-Vendor: %provider_name
 Bundle-RequiredExecutionEnvironment: JavaSE-1.7
-Export-Package: org.eclipse.jgit.awtui;version="4.1.2"
-Import-Package: org.eclipse.jgit.errors;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.lib;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.nls;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.revplot;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.revwalk;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.transport;version="[4.1.2,4.2.0)",
- org.eclipse.jgit.util;version="[4.1.2,4.2.0)"
+Export-Package: org.eclipse.jgit.awtui;version="4.2.1"
+Import-Package: org.eclipse.jgit.errors;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.lib;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.nls;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.revplot;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.revwalk;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.transport;version="[4.2.1,4.3.0)",
+ org.eclipse.jgit.util;version="[4.2.1,4.3.0)"
diff --git a/org.eclipse.jgit.ui/pom.xml b/org.eclipse.jgit.ui/pom.xml
index 776cb8b..c80f31f 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.1.2.201602141800-r</version>
+    <version>4.2.1-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.ui</artifactId>
diff --git a/org.eclipse.jgit.ui/src/org/eclipse/jgit/awtui/AwtCredentialsProvider.java b/org.eclipse.jgit.ui/src/org/eclipse/jgit/awtui/AwtCredentialsProvider.java
index fd26bfa..a9967ae 100644
--- a/org.eclipse.jgit.ui/src/org/eclipse/jgit/awtui/AwtCredentialsProvider.java
+++ b/org.eclipse.jgit.ui/src/org/eclipse/jgit/awtui/AwtCredentialsProvider.java
@@ -56,15 +56,20 @@
 import javax.swing.JTextField;
 
 import org.eclipse.jgit.errors.UnsupportedCredentialItem;
+import org.eclipse.jgit.transport.ChainingCredentialsProvider;
 import org.eclipse.jgit.transport.CredentialItem;
 import org.eclipse.jgit.transport.CredentialsProvider;
+import org.eclipse.jgit.transport.NetRCCredentialsProvider;
 import org.eclipse.jgit.transport.URIish;
 
 /** Interacts with the user during authentication by using AWT/Swing dialogs. */
 public class AwtCredentialsProvider extends CredentialsProvider {
 	/** Install this implementation as the default. */
 	public static void install() {
-		CredentialsProvider.setDefault(new AwtCredentialsProvider());
+		final AwtCredentialsProvider c = new AwtCredentialsProvider();
+		CredentialsProvider cp = new ChainingCredentialsProvider(
+				new NetRCCredentialsProvider(), c);
+		CredentialsProvider.setDefault(cp);
 	}
 
 	@Override
diff --git a/org.eclipse.jgit/.settings/.api_filters b/org.eclipse.jgit/.settings/.api_filters
index a1e79e2..36041f8 100644
--- a/org.eclipse.jgit/.settings/.api_filters
+++ b/org.eclipse.jgit/.settings/.api_filters
@@ -1,5 +1,75 @@
 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
 <component id="org.eclipse.jgit" version="2">
+    <resource path="META-INF/MANIFEST.MF" type="org.eclipse.jgit.lib.FileTreeEntry">
+        <filter id="305324134">
+            <message_arguments>
+                <message_argument value="org.eclipse.jgit.lib.FileTreeEntry"/>
+                <message_argument value="org.eclipse.jgit_4.2.0"/>
+            </message_arguments>
+        </filter>
+    </resource>
+    <resource path="META-INF/MANIFEST.MF" type="org.eclipse.jgit.lib.GitlinkTreeEntry">
+        <filter id="305324134">
+            <message_arguments>
+                <message_argument value="org.eclipse.jgit.lib.GitlinkTreeEntry"/>
+                <message_argument value="org.eclipse.jgit_4.2.0"/>
+            </message_arguments>
+        </filter>
+    </resource>
+    <resource path="META-INF/MANIFEST.MF" type="org.eclipse.jgit.lib.SymlinkTreeEntry">
+        <filter id="305324134">
+            <message_arguments>
+                <message_argument value="org.eclipse.jgit.lib.SymlinkTreeEntry"/>
+                <message_argument value="org.eclipse.jgit_4.2.0"/>
+            </message_arguments>
+        </filter>
+    </resource>
+    <resource path="META-INF/MANIFEST.MF" type="org.eclipse.jgit.lib.Tree">
+        <filter id="305324134">
+            <message_arguments>
+                <message_argument value="org.eclipse.jgit.lib.Tree"/>
+                <message_argument value="org.eclipse.jgit_4.2.0"/>
+            </message_arguments>
+        </filter>
+    </resource>
+    <resource path="META-INF/MANIFEST.MF" type="org.eclipse.jgit.lib.TreeEntry">
+        <filter id="305324134">
+            <message_arguments>
+                <message_argument value="org.eclipse.jgit.lib.TreeEntry"/>
+                <message_argument value="org.eclipse.jgit_4.2.0"/>
+            </message_arguments>
+        </filter>
+    </resource>
+    <resource path="src/org/eclipse/jgit/attributes/AttributesNode.java" type="org.eclipse.jgit.attributes.AttributesNode">
+        <filter comment="attributes weren't really usable in earlier versions" id="338792546">
+            <message_arguments>
+                <message_argument value="org.eclipse.jgit.attributes.AttributesNode"/>
+                <message_argument value="getAttributes(String, boolean, Map&lt;String,Attribute&gt;)"/>
+            </message_arguments>
+        </filter>
+    </resource>
+    <resource path="src/org/eclipse/jgit/lib/BitmapIndex.java" type="org.eclipse.jgit.lib.BitmapIndex$BitmapBuilder">
+        <filter comment="interface is implemented by extenders but not clients of the API" id="403804204">
+            <message_arguments>
+                <message_argument value="org.eclipse.jgit.lib.BitmapIndex.BitmapBuilder"/>
+                <message_argument value="addObject(AnyObjectId, int)"/>
+            </message_arguments>
+        </filter>
+        <filter comment="interface is implemented by extenders but not clients of the API" id="403804204">
+            <message_arguments>
+                <message_argument value="org.eclipse.jgit.lib.BitmapIndex.BitmapBuilder"/>
+                <message_argument value="getBitmapIndex()"/>
+            </message_arguments>
+        </filter>
+    </resource>
+    <resource path="src/org/eclipse/jgit/lib/Repository.java" type="org.eclipse.jgit.lib.Repository">
+        <filter comment="Only implementors of Repository are affected. That should be allowed" id="336695337">
+            <message_arguments>
+                <message_argument value="org.eclipse.jgit.lib.Repository"/>
+                <message_argument value="createAttributesNodeProvider()"/>
+            </message_arguments>
+        </filter>
+    </resource>
     <resource path="src/org/eclipse/jgit/transport/PushCertificate.java" type="org.eclipse.jgit.transport.PushCertificate">
         <filter comment="PushCertificate wasn't really usable in 4.0" id="338722907">
             <message_arguments>
@@ -21,4 +91,26 @@
             </message_arguments>
         </filter>
     </resource>
+    <resource path="src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java" type="org.eclipse.jgit.treewalk.WorkingTreeIterator">
+        <filter comment="attributes weren't really usable in earlier versions" id="338792546">
+            <message_arguments>
+                <message_argument value="org.eclipse.jgit.treewalk.WorkingTreeIterator"/>
+                <message_argument value="getGlobalAttributesNode()"/>
+            </message_arguments>
+        </filter>
+        <filter comment="attributes weren't really usable in earlier versions" id="338792546">
+            <message_arguments>
+                <message_argument value="org.eclipse.jgit.treewalk.WorkingTreeIterator"/>
+                <message_argument value="getInfoAttributesNode()"/>
+            </message_arguments>
+        </filter>
+    </resource>
+    <resource path="src/org/eclipse/jgit/util/FileUtils.java" type="org.eclipse.jgit.util.FileUtils">
+        <filter id="338792546">
+            <message_arguments>
+                <message_argument value="org.eclipse.jgit.util.FileUtils"/>
+                <message_argument value="createSymLink(File, String)"/>
+            </message_arguments>
+        </filter>
+    </resource>
 </component>
diff --git a/org.eclipse.jgit/.settings/org.eclipse.jdt.core.prefs b/org.eclipse.jgit/.settings/org.eclipse.jdt.core.prefs
index 4e28e0b..45d6d2c 100644
--- a/org.eclipse.jgit/.settings/org.eclipse.jdt.core.prefs
+++ b/org.eclipse.jgit/.settings/org.eclipse.jdt.core.prefs
@@ -1,9 +1,9 @@
 eclipse.preferences.version=1
 org.eclipse.jdt.core.compiler.annotation.inheritNullAnnotations=enabled
 org.eclipse.jdt.core.compiler.annotation.missingNonNullByDefaultAnnotation=ignore
-org.eclipse.jdt.core.compiler.annotation.nonnull=org.eclipse.jdt.annotation.NonNull
-org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=org.eclipse.jdt.annotation.NonNullByDefault
-org.eclipse.jdt.core.compiler.annotation.nullable=org.eclipse.jdt.annotation.Nullable
+org.eclipse.jdt.core.compiler.annotation.nonnull=org.eclipse.jgit.annotations.NonNull
+org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=org.eclipse.jgit.annotations.NonNullByDefault
+org.eclipse.jdt.core.compiler.annotation.nullable=org.eclipse.jgit.annotations.Nullable
 org.eclipse.jdt.core.compiler.annotation.nullanalysis=enabled
 org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
 org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate
diff --git a/org.eclipse.jgit/BUCK b/org.eclipse.jgit/BUCK
new file mode 100644
index 0000000..73e2080
--- /dev/null
+++ b/org.eclipse.jgit/BUCK
@@ -0,0 +1,20 @@
+SRCS = glob(['src/**'])
+RESOURCES = glob(['resources/**'])
+
+java_library(
+  name = 'jgit',
+  srcs = SRCS,
+  resources = RESOURCES,
+  deps = [
+    '//lib:javaewah',
+    '//lib:jsch',
+    '//lib:httpcomponents',
+    '//lib:slf4j-api',
+  ],
+  visibility = ['PUBLIC'],
+)
+
+java_sources(
+  name = 'jgit_src',
+  srcs = SRCS + RESOURCES,
+)
diff --git a/org.eclipse.jgit/META-INF/MANIFEST.MF b/org.eclipse.jgit/META-INF/MANIFEST.MF
index a51bee8..d0f03ff 100644
--- a/org.eclipse.jgit/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit/META-INF/MANIFEST.MF
@@ -2,11 +2,12 @@
 Bundle-ManifestVersion: 2
 Bundle-Name: %plugin_name
 Bundle-SymbolicName: org.eclipse.jgit
-Bundle-Version: 4.1.2.201602141800-r
+Bundle-Version: 4.2.1.qualifier
 Bundle-Localization: plugin
 Bundle-Vendor: %provider_name
 Bundle-ActivationPolicy: lazy
-Export-Package: org.eclipse.jgit.api;version="4.1.2";
+Export-Package: org.eclipse.jgit.annotations;version="4.2.1",
+ org.eclipse.jgit.api;version="4.2.1";
   uses:="org.eclipse.jgit.revwalk,
    org.eclipse.jgit.treewalk.filter,
    org.eclipse.jgit.diff,
@@ -20,60 +21,55 @@
    org.eclipse.jgit.submodule,
    org.eclipse.jgit.transport,
    org.eclipse.jgit.merge",
- org.eclipse.jgit.api.errors;version="4.1.2";
-  uses:="org.eclipse.jgit.lib,
-   org.eclipse.jgit.errors",
- org.eclipse.jgit.attributes;version="4.1.2",
- org.eclipse.jgit.blame;version="4.1.2";
+ org.eclipse.jgit.api.errors;version="4.2.1";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.errors",
+ org.eclipse.jgit.attributes;version="4.2.1",
+ org.eclipse.jgit.blame;version="4.2.1";
   uses:="org.eclipse.jgit.lib,
    org.eclipse.jgit.revwalk,
    org.eclipse.jgit.treewalk.filter,
    org.eclipse.jgit.diff",
- org.eclipse.jgit.diff;version="4.1.2";
+ org.eclipse.jgit.diff;version="4.2.1";
   uses:="org.eclipse.jgit.patch,
    org.eclipse.jgit.lib,
    org.eclipse.jgit.treewalk,
    org.eclipse.jgit.revwalk,
    org.eclipse.jgit.treewalk.filter,
    org.eclipse.jgit.util",
- org.eclipse.jgit.dircache;version="4.1.2";
+ org.eclipse.jgit.dircache;version="4.2.1";
   uses:="org.eclipse.jgit.lib,
    org.eclipse.jgit.treewalk,
    org.eclipse.jgit.util,
    org.eclipse.jgit.events,
    org.eclipse.jgit.attributes",
- org.eclipse.jgit.errors;version="4.1.2";
+ org.eclipse.jgit.errors;version="4.2.1";
   uses:="org.eclipse.jgit.lib,
    org.eclipse.jgit.internal.storage.pack,
    org.eclipse.jgit.transport,
    org.eclipse.jgit.dircache",
- org.eclipse.jgit.events;version="4.1.2";
-  uses:="org.eclipse.jgit.lib",
- org.eclipse.jgit.fnmatch;version="4.1.2",
- org.eclipse.jgit.gitrepo;version="4.1.2";
+ org.eclipse.jgit.events;version="4.2.1";uses:="org.eclipse.jgit.lib",
+ org.eclipse.jgit.fnmatch;version="4.2.1",
+ org.eclipse.jgit.gitrepo;version="4.2.1";
   uses:="org.eclipse.jgit.api,
    org.eclipse.jgit.lib,
    org.eclipse.jgit.revwalk,
    org.xml.sax.helpers,
    org.xml.sax",
- org.eclipse.jgit.gitrepo.internal;version="4.1.2";x-internal:=true,
- org.eclipse.jgit.hooks;version="4.1.2";
-  uses:="org.eclipse.jgit.lib",
- org.eclipse.jgit.ignore;version="4.1.2",
- org.eclipse.jgit.ignore.internal;version="4.1.2";x-friends:="org.eclipse.jgit.test",
- org.eclipse.jgit.internal;version="4.1.2";x-friends:="org.eclipse.jgit.test,org.eclipse.jgit.http.test",
- org.eclipse.jgit.internal.storage.dfs;version="4.1.2";
-  x-friends:="org.eclipse.jgit.test,
-   org.eclipse.jgit.http.server",
- org.eclipse.jgit.internal.storage.file;version="4.1.2";
+ org.eclipse.jgit.gitrepo.internal;version="4.2.1";x-internal:=true,
+ org.eclipse.jgit.hooks;version="4.2.1";uses:="org.eclipse.jgit.lib",
+ org.eclipse.jgit.ignore;version="4.2.1",
+ org.eclipse.jgit.ignore.internal;version="4.2.1";x-friends:="org.eclipse.jgit.test",
+ org.eclipse.jgit.internal;version="4.2.1";x-friends:="org.eclipse.jgit.test,org.eclipse.jgit.http.test",
+ org.eclipse.jgit.internal.storage.dfs;version="4.2.1";x-friends:="org.eclipse.jgit.test,org.eclipse.jgit.http.server",
+ org.eclipse.jgit.internal.storage.file;version="4.2.1";
   x-friends:="org.eclipse.jgit.test,
    org.eclipse.jgit.junit,
    org.eclipse.jgit.junit.http,
    org.eclipse.jgit.http.server,
-   org.eclipse.jgit.java7.test,
+   org.eclipse.jgit.pgm.test,
    org.eclipse.jgit.pgm",
- org.eclipse.jgit.internal.storage.pack;version="4.1.2";x-friends:="org.eclipse.jgit.junit,org.eclipse.jgit.test,org.eclipse.jgit.pgm",
- org.eclipse.jgit.lib;version="4.1.2";
+ org.eclipse.jgit.internal.storage.pack;version="4.2.1";x-friends:="org.eclipse.jgit.junit,org.eclipse.jgit.test,org.eclipse.jgit.pgm",
+ org.eclipse.jgit.internal.storage.reftree;version="4.2.1";x-friends:="org.eclipse.jgit.junit,org.eclipse.jgit.test,org.eclipse.jgit.pgm",
+ org.eclipse.jgit.lib;version="4.2.1";
   uses:="org.eclipse.jgit.revwalk,
    org.eclipse.jgit.treewalk.filter,
    org.eclipse.jgit.util,
@@ -83,45 +79,32 @@
    org.eclipse.jgit.treewalk,
    org.eclipse.jgit.transport,
    org.eclipse.jgit.submodule",
- org.eclipse.jgit.merge;version="4.1.2";
+ org.eclipse.jgit.merge;version="4.2.1";
   uses:="org.eclipse.jgit.lib,
    org.eclipse.jgit.treewalk,
    org.eclipse.jgit.revwalk,
    org.eclipse.jgit.diff,
    org.eclipse.jgit.dircache,
    org.eclipse.jgit.api",
- org.eclipse.jgit.nls;version="4.1.2",
- org.eclipse.jgit.notes;version="4.1.2";
+ org.eclipse.jgit.nls;version="4.2.1",
+ org.eclipse.jgit.notes;version="4.2.1";
   uses:="org.eclipse.jgit.lib,
    org.eclipse.jgit.treewalk,
    org.eclipse.jgit.revwalk,
    org.eclipse.jgit.merge",
- org.eclipse.jgit.patch;version="4.1.2";
-  uses:="org.eclipse.jgit.lib,
-   org.eclipse.jgit.diff",
- org.eclipse.jgit.revplot;version="4.1.2";
-  uses:="org.eclipse.jgit.lib,
-   org.eclipse.jgit.revwalk",
- org.eclipse.jgit.revwalk;version="4.1.2";
+ org.eclipse.jgit.patch;version="4.2.1";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.diff",
+ org.eclipse.jgit.revplot;version="4.2.1";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.revwalk",
+ org.eclipse.jgit.revwalk;version="4.2.1";
   uses:="org.eclipse.jgit.lib,
    org.eclipse.jgit.treewalk,
    org.eclipse.jgit.treewalk.filter,
    org.eclipse.jgit.diff,
    org.eclipse.jgit.revwalk.filter",
- org.eclipse.jgit.revwalk.filter;version="4.1.2";
-  uses:="org.eclipse.jgit.revwalk,
-   org.eclipse.jgit.lib,
-   org.eclipse.jgit.util",
- org.eclipse.jgit.storage.file;version="4.1.2";
-  uses:="org.eclipse.jgit.lib,
-   org.eclipse.jgit.util",
- org.eclipse.jgit.storage.pack;version="4.1.2";
-  uses:="org.eclipse.jgit.lib",
- org.eclipse.jgit.submodule;version="4.1.2";
-  uses:="org.eclipse.jgit.lib,
-   org.eclipse.jgit.treewalk.filter,
-   org.eclipse.jgit.treewalk",
- org.eclipse.jgit.transport;version="4.1.2";
+ org.eclipse.jgit.revwalk.filter;version="4.2.1";uses:="org.eclipse.jgit.revwalk,org.eclipse.jgit.lib,org.eclipse.jgit.util",
+ org.eclipse.jgit.storage.file;version="4.2.1";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.util",
+ org.eclipse.jgit.storage.pack;version="4.2.1";uses:="org.eclipse.jgit.lib",
+ org.eclipse.jgit.submodule;version="4.2.1";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.treewalk.filter,org.eclipse.jgit.treewalk",
+ org.eclipse.jgit.transport;version="4.2.1";
   uses:="org.eclipse.jgit.transport.resolver,
    org.eclipse.jgit.revwalk,
    org.eclipse.jgit.internal.storage.pack,
@@ -133,29 +116,24 @@
    org.eclipse.jgit.transport.http,
    org.eclipse.jgit.errors,
    org.eclipse.jgit.storage.pack",
- org.eclipse.jgit.transport.http;version="4.1.2";
-  uses:="javax.net.ssl",
- org.eclipse.jgit.transport.resolver;version="4.1.2";
-  uses:="org.eclipse.jgit.lib,
-   org.eclipse.jgit.transport",
- org.eclipse.jgit.treewalk;version="4.1.2";
+ org.eclipse.jgit.transport.http;version="4.2.1";uses:="javax.net.ssl",
+ org.eclipse.jgit.transport.resolver;version="4.2.1";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.transport",
+ org.eclipse.jgit.treewalk;version="4.2.1";
   uses:="org.eclipse.jgit.lib,
    org.eclipse.jgit.revwalk,
    org.eclipse.jgit.attributes,
    org.eclipse.jgit.treewalk.filter,
    org.eclipse.jgit.util,
    org.eclipse.jgit.dircache",
- org.eclipse.jgit.treewalk.filter;version="4.1.2";
-  uses:="org.eclipse.jgit.treewalk",
- org.eclipse.jgit.util;version="4.1.2";
+ org.eclipse.jgit.treewalk.filter;version="4.2.1";uses:="org.eclipse.jgit.treewalk",
+ org.eclipse.jgit.util;version="4.2.1";
   uses:="org.eclipse.jgit.lib,
    org.eclipse.jgit.transport.http,
    org.eclipse.jgit.storage.file,
    org.ietf.jgss",
- org.eclipse.jgit.util.io;version="4.1.2"
+ org.eclipse.jgit.util.io;version="4.2.1"
 Bundle-RequiredExecutionEnvironment: JavaSE-1.7
-Require-Bundle: com.jcraft.jsch;bundle-version="[0.1.37,0.2.0)",
- org.eclipse.jdt.annotation;bundle-version="[1.1.0,2.0.0)";resolution:=optional
+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)",
  javax.crypto,
  javax.net.ssl,
diff --git a/org.eclipse.jgit/META-INF/SOURCE-MANIFEST.MF b/org.eclipse.jgit/META-INF/SOURCE-MANIFEST.MF
index c7d9753..98ede31 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.1.2.201602141800-r
-Eclipse-SourceBundle: org.eclipse.jgit;version="4.1.2.201602141800-r";roots="."
+Bundle-Version: 4.2.1.qualifier
+Eclipse-SourceBundle: org.eclipse.jgit;version="4.2.1.qualifier";roots="."
diff --git a/org.eclipse.jgit/pom.xml b/org.eclipse.jgit/pom.xml
index 945b135..e931990 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.1.2.201602141800-r</version>
+    <version>4.2.1-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit</artifactId>
@@ -88,12 +88,6 @@
       <groupId>org.slf4j</groupId>
       <artifactId>slf4j-api</artifactId>
     </dependency>
-
-	<dependency>
-	    <groupId>org.eclipse.jdt</groupId>
-	    <artifactId>org.eclipse.jdt.annotation</artifactId>
-	    <version>1.1.0</version>
-	</dependency>
   </dependencies>
 
   <build>
@@ -166,8 +160,45 @@
       </plugin>
 
       <plugin>
-        <groupId>org.codehaus.mojo</groupId>
-        <artifactId>clirr-maven-plugin</artifactId>
+          <groupId>com.github.siom79.japicmp</groupId>
+          <artifactId>japicmp-maven-plugin</artifactId>
+          <version>${japicmp-version}</version>
+          <configuration>
+              <oldVersion>
+                  <dependency>
+                      <groupId>${project.groupId}</groupId>
+                      <artifactId>${project.artifactId}</artifactId>
+                      <version>${jgit-last-release-version}</version>
+                  </dependency>
+              </oldVersion>
+              <newVersion>
+                  <file>
+                      <path>${project.build.directory}/${project.artifactId}-${project.version}.jar</path>
+                  </file>
+              </newVersion>
+              <parameter>
+                  <onlyModified>true</onlyModified>
+                  <includes>
+                      <include>org.eclipse.jgit.*</include>
+                  </includes>
+                  <accessModifier>public</accessModifier>
+                  <breakBuildOnModifications>false</breakBuildOnModifications>
+                  <breakBuildOnBinaryIncompatibleModifications>false</breakBuildOnBinaryIncompatibleModifications>
+                  <onlyBinaryIncompatible>false</onlyBinaryIncompatible>
+                  <includeSynthetic>false</includeSynthetic>
+                  <ignoreMissingClasses>false</ignoreMissingClasses>
+                  <skipPomModules>true</skipPomModules>
+              </parameter>
+              <skip>false</skip>
+          </configuration>
+          <executions>
+            <execution>
+             <phase>verify</phase>
+             <goals>
+               <goal>cmp</goal>
+             </goals>
+          </execution>
+        </executions>
       </plugin>
     </plugins>
 
@@ -187,13 +218,44 @@
   <reporting>
     <plugins>
       <plugin>
-        <groupId>org.codehaus.mojo</groupId>
-        <artifactId>clirr-maven-plugin</artifactId>
-        <version>${clirr-version}</version>
-        <configuration>
-          <comparisonVersion>${jgit-last-release-version}</comparisonVersion>
-          <minSeverity>info</minSeverity>
-        </configuration>
+          <groupId>com.github.siom79.japicmp</groupId>
+          <artifactId>japicmp-maven-plugin</artifactId>
+          <version>${japicmp-version}</version>
+          <reportSets>
+              <reportSet>
+                  <reports>
+                      <report>cmp-report</report>
+                  </reports>
+              </reportSet>
+          </reportSets>
+          <configuration>
+              <oldVersion>
+                  <dependency>
+                      <groupId>${project.groupId}</groupId>
+                      <artifactId>${project.artifactId}</artifactId>
+                      <version>${jgit-last-release-version}</version>
+                  </dependency>
+              </oldVersion>
+              <newVersion>
+                  <file>
+                      <path>${project.build.directory}/${project.artifactId}-${project.version}.jar</path>
+                  </file>
+              </newVersion>
+              <parameter>
+                  <onlyModified>true</onlyModified>
+                  <includes>
+                      <include>org.eclipse.jgit.*</include>
+                  </includes>
+                  <accessModifier>public</accessModifier>
+                  <breakBuildOnModifications>false</breakBuildOnModifications>
+                  <breakBuildOnBinaryIncompatibleModifications>false</breakBuildOnBinaryIncompatibleModifications>
+                  <onlyBinaryIncompatible>false</onlyBinaryIncompatible>
+                  <includeSynthetic>false</includeSynthetic>
+                  <ignoreMissingClasses>false</ignoreMissingClasses>
+                  <skipPomModules>true</skipPomModules>
+              </parameter>
+              <skip>false</skip>
+          </configuration>
       </plugin>
     </plugins>
   </reporting>
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 34bbb41..992e10b 100644
--- a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties
+++ b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties
@@ -20,6 +20,7 @@
 atLeastOnePathIsRequired=At least one path is required.
 atLeastOnePatternIsRequired=At least one pattern is required.
 atLeastTwoFiltersNeeded=At least two filters needed.
+atomicPushNotSupported=Atomic push not supported.
 authenticationNotSupported=authentication not supported
 badBase64InputCharacterAt=Bad Base64 input character at {0} : {1} (decimal)
 badEntryDelimiter=Bad entry delimiter
@@ -45,6 +46,7 @@
 cannotBeRecursiveWhenTreesAreIncluded=TreeWalk shouldn't be recursive when tree objects are included.
 cannotChangeActionOnComment=Cannot change action on comment line in git-rebase-todo file, old action: {0}, new action: {1}.
 cannotChangeToComment=Cannot change a non-comment line to a comment line.
+cannotCheckoutFromUnbornBranch=Cannot checkout from unborn branch
 cannotCheckoutOursSwitchBranch=Checking out ours/theirs is only possible when checking out index, not when switching branches.
 cannotCombineSquashWithNoff=Cannot combine --squash with --no-ff.
 cannotCombineTreeFilterWithRevFilter=Cannot combine TreeFilter {0} with RevFilter {1}.
@@ -87,6 +89,7 @@
 cannotReadCommit=Cannot read commit {0}
 cannotReadFile=Cannot read file {0}
 cannotReadHEAD=cannot read HEAD: {0} {1}
+cannotReadIndex=The index file {0} exists but cannot be read
 cannotReadObject=Cannot read object
 cannotReadObjectsPath=Cannot read {0}/{1}: {2}
 cannotReadTree=Cannot read tree {0}
@@ -96,6 +99,7 @@
 cannotStoreObjects=cannot store objects
 cannotResolveUniquelyAbbrevObjectId=Could not resolve uniquely the abbreviated object ID
 cannotUnloadAModifiedTree=Cannot unload a modified tree.
+cannotUpdateUnbornBranch=Cannot update unborn branch
 cannotWorkWithOtherStagesThanZeroRightNow=Cannot work with other stages than zero right now. Won't write corrupt index.
 cannotWriteObjectsPath=Cannot write {0}/{1}: {2}
 canOnlyCherryPickCommitsWithOneParent=Cannot cherry-pick commit ''{0}'' because it has {1} parents, only commits with exactly one parent are supported.
@@ -122,14 +126,15 @@
 connectionTimeOut=Connection time out: {0}
 contextMustBeNonNegative=context must be >= 0
 corruptionDetectedReReadingAt=Corruption detected re-reading at {0}
+corruptObjectBadDate=bad date
+corruptObjectBadEmail=bad email
 corruptObjectBadStream=bad stream
 corruptObjectBadStreamCorruptHeader=bad stream, corrupt header
+corruptObjectBadTimezone=bad time zone
 corruptObjectDuplicateEntryNames=duplicate entry names
 corruptObjectGarbageAfterSize=garbage after size
 corruptObjectIncorrectLength=incorrect length
 corruptObjectIncorrectSorting=incorrectly sorted
-corruptObjectInvalidAuthor=invalid author
-corruptObjectInvalidCommitter=invalid committer
 corruptObjectInvalidEntryMode=invalid entry mode
 corruptObjectInvalidMode=invalid mode
 corruptObjectInvalidModeChar=invalid mode character
@@ -148,11 +153,11 @@
 corruptObjectInvalidNamePrn=invalid name 'PRN'
 corruptObjectInvalidObject=invalid object
 corruptObjectInvalidParent=invalid parent
-corruptObjectInvalidTagger=invalid tagger
 corruptObjectInvalidTree=invalid tree
 corruptObjectInvalidType=invalid type
 corruptObjectInvalidType2=invalid type {0}
 corruptObjectMalformedHeader=malformed header: {0}
+corruptObjectMissingEmail=missing email
 corruptObjectNameContainsByte=name contains byte 0x%x
 corruptObjectNameContainsChar=name contains '%c'
 corruptObjectNameContainsNullByte=name contains byte 0x00
@@ -178,6 +183,7 @@
 corruptObjectTruncatedInMode=truncated in mode
 corruptObjectTruncatedInName=truncated in name
 corruptObjectTruncatedInObjectId=truncated in object id
+corruptObjectZeroId=entry points to null SHA-1
 couldNotCheckOutBecauseOfConflicts=Could not check out because of conflicts
 couldNotDeleteLockFileShouldNotHappen=Could not delete lock file. Should not happen
 couldNotDeleteTemporaryIndexFileShouldNotHappen=Could not delete temporary index file. Should not happen
@@ -228,6 +234,7 @@
 emptyPathNotPermitted=Empty path not permitted.
 emptyRef=Empty ref: {0}
 encryptionError=Encryption error: {0}
+encryptionOnlyPBE=Encryption error: only password-based encryption (PBE) algorithms are supported.
 endOfFileInEscape=End of file in escape
 entryNotFoundByPath=Entry not found by path: {0}
 enumValueNotSupported2=Invalid value: {0}.{1}={2}
@@ -278,6 +285,8 @@
 fileIsTooBigForThisConvenienceMethod=File is too big for this convenience method ({0} bytes).
 fileIsTooLarge=File is too large: {0}
 fileModeNotSetForPath=FileMode not set for path {0}
+filterExecutionFailed=Execution of filter command ''{0}'' on file ''{1}'' failed
+filterExecutionFailedRc=Execution of filter command ''{0}'' on file ''{1}'' failed with return code ''{2}'', message on stderr: ''{3}''
 findingGarbage=Finding garbage
 flagIsDisposed={0} is disposed.
 flagNotFromThis={0} not from this.
@@ -343,6 +352,7 @@
 invalidReflogRevision=Invalid reflog revision: {0}
 invalidRefName=Invalid ref name: {0}
 invalidRemote=Invalid remote: {0}
+invalidRepositoryStateNoHead=Invalid repository --- cannot read HEAD
 invalidShallowObject=invalid shallow object {0}, expected commit
 invalidStageForPath=Invalid stage {0} for path {1}
 invalidTagOption=Invalid tag option: {0}
@@ -425,6 +435,7 @@
 objectAtHasBadZlibStream=Object at {0} in {1} has bad zlib stream
 objectAtPathDoesNotHaveId=Object at path "{0}" does not have an id assigned. All object ids must be assigned prior to writing a tree.
 objectIsCorrupt=Object {0} is corrupt: {1}
+objectIsCorrupt3={0}: object {1}: {2}
 objectIsNotA=Object {0} is not a {1}.
 objectNotFound=Object {0} not found.
 objectNotFoundIn=Object {0} not found in {1}.
@@ -449,6 +460,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
 packingCancelledDuringObjectsWriting=Packing cancelled during objects writing
 packObjectCountMismatch=Pack object count mismatch: pack {0} index {1}: {2}
 packRefs=Pack refs
@@ -587,6 +599,7 @@
 transportExceptionMissingAssumed=Missing assumed {0}
 transportExceptionReadRef=read {0}
 transportNeedsRepository=Transport needs repository
+transportProvidedRefWithNoObjectId=Transport provided ref {0} with no object id
 transportProtoAmazonS3=Amazon S3
 transportProtoBundleFile=Git Bundle File
 transportProtoFTP=FTP
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/SymlinkTreeEntry.java b/org.eclipse.jgit/src/org/eclipse/jgit/annotations/NonNull.java
similarity index 60%
copy from org.eclipse.jgit/src/org/eclipse/jgit/lib/SymlinkTreeEntry.java
copy to org.eclipse.jgit/src/org/eclipse/jgit/annotations/NonNull.java
index c7e41bc..08f81cb 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/SymlinkTreeEntry.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/annotations/NonNull.java
@@ -1,6 +1,5 @@
 /*
- * Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com>
- * Copyright (C) 2006-2007, Shawn O. Pearce <spearce@spearce.org>
+ * Copyright (C) 2015, Andrey Loskutov <loskutov@gmx.de>
  * and other copyright owners as documented in the project's IP log.
  *
  * This program and the accompanying materials are made available
@@ -42,44 +41,29 @@
  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-package org.eclipse.jgit.lib;
+package org.eclipse.jgit.annotations;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
 
 /**
- * A tree entry representing a symbolic link.
+ * JGit's replacement for the {@code javax.annotation.Nonnull}.
+ * <p>
+ * Denotes that a local variable, parameter, field, method return value expected
+ * to be non {@code null}.
  *
- * Note. Java cannot really handle these as file system objects.
- *
- * @deprecated To look up information about a single path, use
- * {@link org.eclipse.jgit.treewalk.TreeWalk#forPath(Repository, String, org.eclipse.jgit.revwalk.RevTree)}.
- * To lookup information about multiple paths at once, use a
- * {@link org.eclipse.jgit.treewalk.TreeWalk} and obtain the current entry's
- * information from its getter methods.
+ * @since 4.2
  */
-@Deprecated
-public class SymlinkTreeEntry extends TreeEntry {
-
-	/**
-	 * Construct a {@link SymlinkTreeEntry} with the specified name and SHA-1 in
-	 * the specified parent
-	 *
-	 * @param parent
-	 * @param id
-	 * @param nameUTF8
-	 */
-	public SymlinkTreeEntry(final Tree parent, final ObjectId id,
-			final byte[] nameUTF8) {
-		super(parent, id, nameUTF8);
-	}
-
-	public FileMode getMode() {
-		return FileMode.SYMLINK;
-	}
-
-	public String toString() {
-		final StringBuilder r = new StringBuilder();
-		r.append(ObjectId.toString(getId()));
-		r.append(" S "); //$NON-NLS-1$
-		r.append(getFullName());
-		return r.toString();
-	}
+@Documented
+@Retention(RetentionPolicy.CLASS)
+@Target({ FIELD, METHOD, PARAMETER, LOCAL_VARIABLE })
+public @interface NonNull {
+	// marker annotation with no members
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/annotations/Nullable.java b/org.eclipse.jgit/src/org/eclipse/jgit/annotations/Nullable.java
new file mode 100644
index 0000000..7b91567
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/annotations/Nullable.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2015, 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.annotations;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Marks types that can hold the value {@code null} at run time.
+ * <p>
+ * Unlike {@code org.eclipse.jdt.annotation.Nullable}, this has run-time
+ * retention, allowing the annotation to be recognized by
+ * <a href="https://github.com/google/guice/wiki/UseNullable">Guice</a>. Unlike
+ * {@code javax.annotation.Nullable}, this does not involve importing new classes
+ * to a standard (Java EE) package, so it can be deployed in an OSGi container
+ * without running into
+ * <a href="http://wiki.osgi.org/wiki/Split_Packages">split-package</a>
+ * <a href="https://gerrit-review.googlesource.com/50112">problems</a>.
+ * <p>
+ * You can use this annotation to qualify a type in a method signature or local
+ * variable declaration. The entity whose type has this annotation is allowed to
+ * hold the value {@code null} at run time. This allows annotation based null
+ * analysis to infer that
+ * <ul>
+ * <li>Binding a {@code null} value to the entity is legal.
+ * <li>Dereferencing the entity is unsafe and can trigger a
+ * {@code NullPointerException}.
+ * </ul>
+ * <p>
+ * To avoid a dependency on Java 8, this annotation does not use
+ * {@link Target @Target} {@code TYPE_USE}. That may change when JGit starts
+ * requiring Java 8.
+ * <p>
+ * <b>Warning:</b> Please do not use this annotation on arrays. Different
+ * annotation processors treat {@code @Nullable Object[]} differently: some
+ * treat it as an array of nullable objects, for consistency with versions of
+ * {@code Nullable} defined with {@code @Target} {@code TYPE_USE}, while others
+ * treat it as a nullable array of objects. JGit therefore avoids using this
+ * annotation on arrays altogether.
+ *
+ * @see <a href=
+ *      "http://types.cs.washington.edu/checker-framework/current/checker-framework-manual.html#faq-array-syntax-meaning">
+ *      The checker-framework manual</a>
+ *
+ * @since 4.2
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ FIELD, METHOD, PARAMETER, LOCAL_VARIABLE })
+public @interface Nullable {
+	// marker annotation with no members
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/AddCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/AddCommand.java
index de6c32a..3b94f16 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/AddCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/AddCommand.java
@@ -43,11 +43,16 @@
  */
 package org.eclipse.jgit.api;
 
+import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
+import static org.eclipse.jgit.lib.FileMode.GITLINK;
+import static org.eclipse.jgit.lib.FileMode.TYPE_TREE;
+
 import java.io.IOException;
 import java.io.InputStream;
 import java.util.Collection;
 import java.util.LinkedList;
 
+import org.eclipse.jgit.api.errors.FilterFailedException;
 import org.eclipse.jgit.api.errors.GitAPIException;
 import org.eclipse.jgit.api.errors.JGitInternalException;
 import org.eclipse.jgit.api.errors.NoFilepatternException;
@@ -57,12 +62,13 @@
 import org.eclipse.jgit.dircache.DirCacheEntry;
 import org.eclipse.jgit.dircache.DirCacheIterator;
 import org.eclipse.jgit.internal.JGitText;
-import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.FileMode;
+import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.ObjectInserter;
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.treewalk.FileTreeIterator;
-import org.eclipse.jgit.treewalk.TreeWalk;
+import org.eclipse.jgit.treewalk.NameConflictTreeWalk;
+import org.eclipse.jgit.treewalk.TreeWalk.OperationType;
 import org.eclipse.jgit.treewalk.WorkingTreeIterator;
 import org.eclipse.jgit.treewalk.filter.PathFilterGroup;
 
@@ -133,81 +139,106 @@
 			throw new NoFilepatternException(JGitText.get().atLeastOnePatternIsRequired);
 		checkCallable();
 		DirCache dc = null;
-		boolean addAll = false;
-		if (filepatterns.contains(".")) //$NON-NLS-1$
-			addAll = true;
+		boolean addAll = filepatterns.contains("."); //$NON-NLS-1$
 
 		try (ObjectInserter inserter = repo.newObjectInserter();
-				final TreeWalk tw = new TreeWalk(repo)) {
+				NameConflictTreeWalk tw = new NameConflictTreeWalk(repo)) {
+			tw.setOperationType(OperationType.CHECKIN_OP);
 			dc = repo.lockDirCache();
-			DirCacheIterator c;
 
 			DirCacheBuilder builder = dc.builder();
 			tw.addTree(new DirCacheBuildIterator(builder));
 			if (workingTreeIterator == null)
 				workingTreeIterator = new FileTreeIterator(repo);
+			workingTreeIterator.setDirCacheIterator(tw, 0);
 			tw.addTree(workingTreeIterator);
-			tw.setRecursive(true);
 			if (!addAll)
 				tw.setFilter(PathFilterGroup.createFromStrings(filepatterns));
 
-			String lastAddedFile = null;
+			byte[] lastAdded = null;
 
 			while (tw.next()) {
-				String path = tw.getPathString();
-
+				DirCacheIterator c = tw.getTree(0, DirCacheIterator.class);
 				WorkingTreeIterator f = tw.getTree(1, WorkingTreeIterator.class);
-				if (tw.getTree(0, DirCacheIterator.class) == null &&
-						f != null && f.isEntryIgnored()) {
+				if (c == null && f != null && f.isEntryIgnored()) {
 					// file is not in index but is ignored, do nothing
+					continue;
+				} else if (c == null && update) {
+					// Only update of existing entries was requested.
+					continue;
 				}
-				// In case of an existing merge conflict the
-				// DirCacheBuildIterator iterates over all stages of
-				// this path, we however want to add only one
-				// new DirCacheEntry per path.
-				else if (!(path.equals(lastAddedFile))) {
-					if (!(update && tw.getTree(0, DirCacheIterator.class) == null)) {
-						c = tw.getTree(0, DirCacheIterator.class);
-						if (f != null) { // the file exists
-							long sz = f.getEntryLength();
-							DirCacheEntry entry = new DirCacheEntry(path);
-							if (c == null || c.getDirCacheEntry() == null
-									|| !c.getDirCacheEntry().isAssumeValid()) {
-								FileMode mode = f.getIndexFileMode(c);
-								entry.setFileMode(mode);
 
-								if (FileMode.GITLINK != mode) {
-									entry.setLength(sz);
-									entry.setLastModified(f
-											.getEntryLastModified());
-									long contentSize = f
-											.getEntryContentLength();
-									InputStream in = f.openEntryStream();
-									try {
-										entry.setObjectId(inserter.insert(
-												Constants.OBJ_BLOB, contentSize, in));
-									} finally {
-										in.close();
-									}
-								} else
-									entry.setObjectId(f.getEntryObjectId());
-								builder.add(entry);
-								lastAddedFile = path;
-							} else {
-								builder.add(c.getDirCacheEntry());
-							}
+				DirCacheEntry entry = c != null ? c.getDirCacheEntry() : null;
+				if (entry != null && entry.getStage() > 0
+						&& lastAdded != null
+						&& lastAdded.length == tw.getPathLength()
+						&& tw.isPathPrefix(lastAdded, lastAdded.length) == 0) {
+					// In case of an existing merge conflict the
+					// DirCacheBuildIterator iterates over all stages of
+					// this path, we however want to add only one
+					// new DirCacheEntry per path.
+					continue;
+				}
 
-						} else if (c != null
-								&& (!update || FileMode.GITLINK == c
-										.getEntryFileMode()))
-							builder.add(c.getDirCacheEntry());
+				if (tw.isSubtree() && !tw.isDirectoryFileConflict()) {
+					tw.enterSubtree();
+					continue;
+				}
+
+				if (f == null) { // working tree file does not exist
+					if (entry != null
+							&& (!update || GITLINK == entry.getFileMode())) {
+						builder.add(entry);
 					}
+					continue;
 				}
+
+				if (entry != null && entry.isAssumeValid()) {
+					// Index entry is marked assume valid. Even though
+					// the user specified the file to be added JGit does
+					// not consider the file for addition.
+					builder.add(entry);
+					continue;
+				}
+
+				if (f.getEntryRawMode() == TYPE_TREE) {
+					// Index entry exists and is symlink, gitlink or file,
+					// otherwise the tree would have been entered above.
+					// Replace the index entry by diving into tree of files.
+					tw.enterSubtree();
+					continue;
+				}
+
+				byte[] path = tw.getRawPath();
+				if (entry == null || entry.getStage() > 0) {
+					entry = new DirCacheEntry(path);
+				}
+				FileMode mode = f.getIndexFileMode(c);
+				entry.setFileMode(mode);
+
+				if (GITLINK != mode) {
+					entry.setLength(f.getEntryLength());
+					entry.setLastModified(f.getEntryLastModified());
+					long len = f.getEntryContentLength();
+					try (InputStream in = f.openEntryStream()) {
+						ObjectId id = inserter.insert(OBJ_BLOB, len, in);
+						entry.setObjectId(id);
+					}
+				} else {
+					entry.setLength(0);
+					entry.setLastModified(0);
+					entry.setObjectId(f.getEntryObjectId());
+				}
+				builder.add(entry);
+				lastAdded = path;
 			}
 			inserter.flush();
 			builder.commit();
 			setCallable(false);
 		} catch (IOException e) {
+			Throwable cause = e.getCause();
+			if (cause != null && cause instanceof FilterFailedException)
+				throw (FilterFailedException) cause;
 			throw new JGitInternalException(
 					JGitText.get().exceptionCaughtDuringExecutionOfAddCommand, e);
 		} finally {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/ApplyCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/ApplyCommand.java
index 6a945e4..676ae03 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/ApplyCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/ApplyCommand.java
@@ -47,6 +47,7 @@
 import java.io.FileWriter;
 import java.io.IOException;
 import java.io.InputStream;
+import java.nio.file.StandardCopyOption;
 import java.text.MessageFormat;
 import java.util.ArrayList;
 import java.util.List;
@@ -141,9 +142,13 @@
 				case RENAME:
 					f = getFile(fh.getOldPath(), false);
 					File dest = getFile(fh.getNewPath(), false);
-					if (!f.renameTo(dest))
+					try {
+						FileUtils.rename(f, dest,
+								StandardCopyOption.ATOMIC_MOVE);
+					} catch (IOException e) {
 						throw new PatchApplyException(MessageFormat.format(
-								JGitText.get().renameFileFailed, f, dest));
+								JGitText.get().renameFileFailed, f, dest), e);
+					}
 					break;
 				case COPY:
 					f = getFile(fh.getOldPath(), false);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutCommand.java
index 8d8aada..4f918fa 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutCommand.java
@@ -222,6 +222,12 @@
 			}
 
 			Ref headRef = repo.getRef(Constants.HEAD);
+			if (headRef == null) {
+				// TODO Git CLI supports checkout from unborn branch, we should
+				// also allow this
+				throw new UnsupportedOperationException(
+						JGitText.get().cannotCheckoutFromUnbornBranch);
+			}
 			String shortHeadRef = getShortBranchName(headRef);
 			String refLogMessage = "checkout: moving from " + shortHeadRef; //$NON-NLS-1$
 			ObjectId branch;
@@ -325,9 +331,16 @@
 	}
 
 	private String getShortBranchName(Ref headRef) {
-		if (headRef.getTarget().getName().equals(headRef.getName()))
-			return headRef.getTarget().getObjectId().getName();
-		return Repository.shortenRefName(headRef.getTarget().getName());
+		if (headRef.isSymbolic()) {
+			return Repository.shortenRefName(headRef.getTarget().getName());
+		}
+		// Detached HEAD. Every non-symbolic ref in the ref database has an
+		// object id, so this cannot be null.
+		ObjectId id = headRef.getObjectId();
+		if (id == null) {
+			throw new NullPointerException();
+		}
+		return id.getName();
 	}
 
 	/**
@@ -457,7 +470,7 @@
 
 	private void checkoutPath(DirCacheEntry entry, ObjectReader reader) {
 		try {
-			DirCacheCheckout.checkoutEntry(repo, entry, reader);
+			DirCacheCheckout.checkoutEntry(repo, entry, reader, true);
 		} catch (IOException e) {
 			throw new JGitInternalException(MessageFormat.format(
 					JGitText.get().checkoutConflictWithFile,
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 b3bc319..2ac8729 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CloneCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CloneCommand.java
@@ -61,6 +61,7 @@
 import org.eclipse.jgit.lib.ConfigConstants;
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.NullProgressMonitor;
+import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.ProgressMonitor;
 import org.eclipse.jgit.lib.Ref;
 import org.eclipse.jgit.lib.RefUpdate;
@@ -235,7 +236,7 @@
 		}
 
 		if (head == null || head.getObjectId() == null)
-			return; // throw exception?
+			return; // TODO throw exception?
 
 		if (head.getName().startsWith(Constants.R_HEADS)) {
 			final RefUpdate newHead = clonedRepo.updateRef(Constants.HEAD);
@@ -287,20 +288,24 @@
 
 	private Ref findBranchToCheckout(FetchResult result) {
 		final Ref idHEAD = result.getAdvertisedRef(Constants.HEAD);
-		if (idHEAD == null)
+		ObjectId headId = idHEAD != null ? idHEAD.getObjectId() : null;
+		if (headId == null) {
 			return null;
+		}
 
 		Ref master = result.getAdvertisedRef(Constants.R_HEADS
 				+ Constants.MASTER);
-		if (master != null && master.getObjectId().equals(idHEAD.getObjectId()))
+		ObjectId objectId = master != null ? master.getObjectId() : null;
+		if (headId.equals(objectId)) {
 			return master;
+		}
 
 		Ref foundBranch = null;
 		for (final Ref r : result.getAdvertisedRefs()) {
 			final String n = r.getName();
 			if (!n.startsWith(Constants.R_HEADS))
 				continue;
-			if (r.getObjectId().equals(idHEAD.getObjectId())) {
+			if (headId.equals(r.getObjectId())) {
 				foundBranch = r;
 				break;
 			}
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 6174d48..0abb8ba 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java
@@ -53,6 +53,7 @@
 
 import org.eclipse.jgit.api.errors.AbortedByHookException;
 import org.eclipse.jgit.api.errors.ConcurrentRefUpdateException;
+import org.eclipse.jgit.api.errors.EmtpyCommitException;
 import org.eclipse.jgit.api.errors.GitAPIException;
 import org.eclipse.jgit.api.errors.JGitInternalException;
 import org.eclipse.jgit.api.errors.NoFilepatternException;
@@ -86,6 +87,7 @@
 import org.eclipse.jgit.treewalk.CanonicalTreeParser;
 import org.eclipse.jgit.treewalk.FileTreeIterator;
 import org.eclipse.jgit.treewalk.TreeWalk;
+import org.eclipse.jgit.treewalk.TreeWalk.OperationType;
 import org.eclipse.jgit.util.ChangeIdUtil;
 
 /**
@@ -129,6 +131,8 @@
 
 	private PrintStream hookOutRedirect;
 
+	private Boolean allowEmpty;
+
 	/**
 	 * @param repo
 	 */
@@ -178,7 +182,7 @@
 
 			processOptions(state, rw);
 
-			if (all && !repo.isBare() && repo.getWorkTree() != null) {
+			if (all && !repo.isBare()) {
 				try (Git git = new Git(repo)) {
 					git.add()
 							.addFilepattern(".") //$NON-NLS-1$
@@ -230,6 +234,16 @@
 				if (insertChangeId)
 					insertChangeId(indexTreeId);
 
+				// Check for empty commits
+				if (headId != null && !allowEmpty.booleanValue()) {
+					RevCommit headCommit = rw.parseCommit(headId);
+					headCommit.getTree();
+					if (indexTreeId.equals(headCommit.getTree())) {
+						throw new EmtpyCommitException(
+								JGitText.get().emptyCommit);
+					}
+				}
+
 				// Create a Commit object, populate it and write it
 				CommitBuilder commit = new CommitBuilder();
 				commit.setCommitter(committer);
@@ -298,7 +312,7 @@
 		}
 	}
 
-	private void insertChangeId(ObjectId treeId) throws IOException {
+	private void insertChangeId(ObjectId treeId) {
 		ObjectId firstParentId = null;
 		if (!parents.isEmpty())
 			firstParentId = parents.get(0);
@@ -328,9 +342,12 @@
 		boolean emptyCommit = true;
 
 		try (TreeWalk treeWalk = new TreeWalk(repo)) {
+			treeWalk.setOperationType(OperationType.CHECKIN_OP);
 			int dcIdx = treeWalk
 					.addTree(new DirCacheBuildIterator(existingBuilder));
-			int fIdx = treeWalk.addTree(new FileTreeIterator(repo));
+			FileTreeIterator fti = new FileTreeIterator(repo);
+			fti.setDirCacheIterator(treeWalk, 0);
+			int fIdx = treeWalk.addTree(fti);
 			int hIdx = -1;
 			if (headId != null)
 				hIdx = treeWalk.addTree(rw.parseTree(headId));
@@ -453,6 +470,8 @@
 
 		// there must be at least one change
 		if (emptyCommit)
+			// Would like to throw a EmptyCommitException. But this would break the API
+			// TODO(ch): Change this in the next release
 			throw new JGitInternalException(JGitText.get().emptyCommit);
 
 		// update index
@@ -506,6 +525,12 @@
 			committer = new PersonIdent(repo);
 		if (author == null && !amend)
 			author = committer;
+		if (allowEmpty == null)
+			// JGit allows empty commits by default. Only when pathes are
+			// specified the commit should not be empty. This behaviour differs
+			// from native git but can only be adapted in the next release.
+			// TODO(ch) align the defaults with native git
+			allowEmpty = (only.isEmpty()) ? Boolean.TRUE : Boolean.FALSE;
 
 		// when doing a merge commit parse MERGE_HEAD and MERGE_MSG files
 		if (state == RepositoryState.MERGING_RESOLVED
@@ -575,6 +600,27 @@
 	}
 
 	/**
+	 * @param allowEmpty
+	 *            whether it should be allowed to create a commit which has the
+	 *            same tree as it's sole predecessor (a commit which doesn't
+	 *            change anything). By default when creating standard commits
+	 *            (without specifying paths) JGit allows to create such commits.
+	 *            When this flag is set to false an attempt to create an "empty"
+	 *            standard commit will lead to an EmptyCommitException.
+	 *            <p>
+	 *            By default when creating a commit containing only specified
+	 *            paths an attempt to create an empty commit leads to a
+	 *            {@link JGitInternalException}. By setting this flag to
+	 *            <code>true</code> this exception will not be thrown.
+	 * @return {@code this}
+	 * @since 4.2
+	 */
+	public CommitCommand setAllowEmpty(boolean allowEmpty) {
+		this.allowEmpty = Boolean.valueOf(allowEmpty);
+		return this;
+	}
+
+	/**
 	 * @return the commit message used for the <code>commit</code>
 	 */
 	public String getMessage() {
@@ -677,7 +723,7 @@
 	 */
 	public CommitCommand setAll(boolean all) {
 		checkCallable();
-		if (!only.isEmpty())
+		if (all && !only.isEmpty())
 			throw new JGitInternalException(MessageFormat.format(
 					JGitText.get().illegalCombinationOfArguments, "--all", //$NON-NLS-1$
 					"--only")); //$NON-NLS-1$
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/FetchCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/FetchCommand.java
index 9620089..de51276 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/FetchCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/FetchCommand.java
@@ -116,22 +116,17 @@
 			org.eclipse.jgit.api.errors.TransportException {
 		checkCallable();
 
-		try {
-			Transport transport = Transport.open(repo, remote);
-			try {
-				transport.setCheckFetchedObjects(checkFetchedObjects);
-				transport.setRemoveDeletedRefs(isRemoveDeletedRefs());
-				transport.setDryRun(dryRun);
-				if (tagOption != null)
-					transport.setTagOpt(tagOption);
-				transport.setFetchThin(thin);
-				configure(transport);
+		try (Transport transport = Transport.open(repo, remote)) {
+			transport.setCheckFetchedObjects(checkFetchedObjects);
+			transport.setRemoveDeletedRefs(isRemoveDeletedRefs());
+			transport.setDryRun(dryRun);
+			if (tagOption != null)
+				transport.setTagOpt(tagOption);
+			transport.setFetchThin(thin);
+			configure(transport);
 
-				FetchResult result = transport.fetch(monitor, refSpecs);
-				return result;
-			} finally {
-				transport.close();
-			}
+			FetchResult result = transport.fetch(monitor, refSpecs);
+			return result;
 		} catch (NoRemoteRepositoryException e) {
 			throw new InvalidRemoteException(MessageFormat.format(
 					JGitText.get().invalidRemote, remote), e);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/Git.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/Git.java
index addca4c..2cd5f59 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/Git.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/Git.java
@@ -713,8 +713,48 @@
 	}
 
 	/**
-	 * @return the git repository this class is interacting with; see {@link
-	 *         #close()} for notes on closing this repository.
+	 * Return a command used to list the available remotes.
+	 *
+	 * @return a {@link RemoteListCommand}
+	 * @since 4.2
+	 */
+	public RemoteListCommand remoteList() {
+		return new RemoteListCommand(repo);
+	}
+
+	/**
+	 * Return a command used to add a new remote.
+	 *
+	 * @return a {@link RemoteAddCommand}
+	 * @since 4.2
+	 */
+	public RemoteAddCommand remoteAdd() {
+		return new RemoteAddCommand(repo);
+	}
+
+	/**
+	 * Return a command used to remove an existing remote.
+	 *
+	 * @return a {@link RemoteRemoveCommand}
+	 * @since 4.2
+	 */
+	public RemoteRemoveCommand remoteRemove() {
+		return new RemoteRemoveCommand(repo);
+	}
+
+	/**
+	 * Return a command used to change the URL of an existing remote.
+	 *
+	 * @return a {@link RemoteSetUrlCommand}
+	 * @since 4.2
+	 */
+	public RemoteSetUrlCommand remoteSetUrl() {
+		return new RemoteSetUrlCommand(repo);
+	}
+
+	/**
+	 * @return the git repository this class is interacting with; see
+	 *         {@link #close()} for notes on closing this repository.
 	 */
 	public Repository getRepository() {
 		return repo;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/LsRemoteCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/LsRemoteCommand.java
index 3363a0f..f3527fd 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/LsRemoteCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/LsRemoteCommand.java
@@ -182,13 +182,9 @@
 			org.eclipse.jgit.api.errors.TransportException {
 		checkCallable();
 
-		Transport transport = null;
-		FetchConnection fc = null;
-		try {
-			if (repo != null)
-				transport = Transport.open(repo, remote);
-			else
-				transport = Transport.open(new URIish(remote));
+		try (Transport transport = repo != null
+				? Transport.open(repo, remote)
+				: Transport.open(new URIish(remote))) {
 			transport.setOptionUploadPack(uploadPack);
 			configure(transport);
 			Collection<RefSpec> refSpecs = new ArrayList<RefSpec>(1);
@@ -199,19 +195,20 @@
 				refSpecs.add(new RefSpec("refs/heads/*:refs/remotes/origin/*")); //$NON-NLS-1$
 			Collection<Ref> refs;
 			Map<String, Ref> refmap = new HashMap<String, Ref>();
-			fc = transport.openFetch();
-			refs = fc.getRefs();
-			if (refSpecs.isEmpty())
-				for (Ref r : refs)
-					refmap.put(r.getName(), r);
-			else
-				for (Ref r : refs)
-					for (RefSpec rs : refSpecs)
-						if (rs.matchSource(r)) {
-							refmap.put(r.getName(), r);
-							break;
-						}
-			return refmap;
+			try (FetchConnection fc = transport.openFetch()) {
+				refs = fc.getRefs();
+				if (refSpecs.isEmpty())
+					for (Ref r : refs)
+						refmap.put(r.getName(), r);
+				else
+					for (Ref r : refs)
+						for (RefSpec rs : refSpecs)
+							if (rs.matchSource(r)) {
+								refmap.put(r.getName(), r);
+								break;
+							}
+				return refmap;
+			}
 		} catch (URISyntaxException e) {
 			throw new InvalidRemoteException(MessageFormat.format(
 					JGitText.get().invalidRemote, remote));
@@ -223,11 +220,6 @@
 			throw new org.eclipse.jgit.api.errors.TransportException(
 					e.getMessage(),
 					e);
-		} finally {
-			if (fc != null)
-				fc.close();
-			if (transport != null)
-				transport.close();
 		}
 	}
 
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/MergeCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/MergeCommand.java
index d2075a7..bfe90a3 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/MergeCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/MergeCommand.java
@@ -1,6 +1,7 @@
 /*
  * Copyright (C) 2010, Christian Halstrick <christian.halstrick@sap.com>
  * Copyright (C) 2010-2014, Stefan Lay <stefan.lay@sap.com>
+ * Copyright (C) 2016, Laurent Delaigue <laurent.delaigue@obeo.fr>
  * and other copyright owners as documented in the project's IP log.
  *
  * This program and the accompanying materials are made available
@@ -65,8 +66,10 @@
 import org.eclipse.jgit.lib.AnyObjectId;
 import org.eclipse.jgit.lib.Config.ConfigEnum;
 import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.NullProgressMonitor;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.ObjectIdRef;
+import org.eclipse.jgit.lib.ProgressMonitor;
 import org.eclipse.jgit.lib.Ref;
 import org.eclipse.jgit.lib.Ref.Storage;
 import org.eclipse.jgit.lib.RefUpdate;
@@ -106,6 +109,8 @@
 
 	private String message;
 
+	private ProgressMonitor monitor = NullProgressMonitor.INSTANCE;
+
 	/**
 	 * The modes available for fast forward merges corresponding to the
 	 * <code>--ff</code>, <code>--no-ff</code> and <code>--ff-only</code>
@@ -330,6 +335,7 @@
 					repo.writeSquashCommitMsg(squashMessage);
 				}
 				Merger merger = mergeStrategy.newMerger(repo);
+				merger.setProgressMonitor(monitor);
 				boolean noProblems;
 				Map<String, org.eclipse.jgit.merge.MergeResult<?>> lowLevelResults = null;
 				Map<String, MergeFailureReason> failingPaths = null;
@@ -586,4 +592,23 @@
 		this.message = message;
 		return this;
 	}
+
+	/**
+	 * The progress monitor associated with the diff operation. By default, this
+	 * is set to <code>NullProgressMonitor</code>
+	 *
+	 * @see NullProgressMonitor
+	 *
+	 * @param monitor
+	 *            A progress monitor
+	 * @return this instance
+	 * @since 4.2
+	 */
+	public MergeCommand setProgressMonitor(ProgressMonitor monitor) {
+		if (monitor == null) {
+			monitor = NullProgressMonitor.INSTANCE;
+		}
+		this.monitor = monitor;
+		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 2783edd..549ef6c 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/PullCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/PullCommand.java
@@ -1,6 +1,7 @@
 /*
  * Copyright (C) 2010, Christian Halstrick <christian.halstrick@sap.com>
  * Copyright (C) 2010, Mathias Kinzler <mathias.kinzler@sap.com>
+ * Copyright (C) 2016, Laurent Delaigue <laurent.delaigue@obeo.fr>
  * and other copyright owners as documented in the project's IP log.
  *
  * This program and the accompanying materials are made available
@@ -326,6 +327,7 @@
 			MergeCommand merge = new MergeCommand(repo);
 			merge.include(upstreamName, commitToMerge);
 			merge.setStrategy(strategy);
+			merge.setProgressMonitor(monitor);
 			MergeResult mergeRes = merge.call();
 			monitor.update(1);
 			result = new PullResult(fetchRes, remote, mergeRes);
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 227e322..f5b82bd 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/PushCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/PushCommand.java
@@ -89,9 +89,8 @@
 	private String receivePack = RemoteConfig.DEFAULT_RECEIVE_PACK;
 
 	private boolean dryRun;
-
+	private boolean atomic;
 	private boolean force;
-
 	private boolean thin = Transport.DEFAULT_PUSH_THIN;
 
 	private OutputStream out;
@@ -145,6 +144,7 @@
 			transports = Transport.openAll(repo, remote, Transport.Operation.PUSH);
 			for (final Transport transport : transports) {
 				transport.setPushThin(thin);
+				transport.setPushAtomic(atomic);
 				if (receivePack != null)
 					transport.setOptionReceivePack(receivePack);
 				transport.setDryRun(dryRun);
@@ -397,6 +397,29 @@
 	}
 
 	/**
+	 * @return true if all-or-nothing behavior is requested.
+	 * @since 4.2
+	 */
+	public boolean isAtomic() {
+		return atomic;
+	}
+
+	/**
+	 * Requests atomic push (all references updated, or no updates).
+	 *
+	 * Default setting is false.
+	 *
+	 * @param atomic
+	 * @return {@code this}
+	 * @since 4.2
+	 */
+	public PushCommand setAtomic(boolean atomic) {
+		checkCallable();
+		this.atomic = atomic;
+		return this;
+	}
+
+	/**
 	 * @return the force preference for push operation
 	 */
 	public boolean isForce() {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java
index ff29008..643ec7a 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java
@@ -1,5 +1,6 @@
 /*
  * Copyright (C) 2010, 2013 Mathias Kinzler <mathias.kinzler@sap.com>
+ * Copyright (C) 2016, Laurent Delaigue <laurent.delaigue@obeo.fr>
  * and other copyright owners as documented in the project's IP log.
  *
  * This program and the accompanying materials are made available
@@ -560,6 +561,8 @@
 		lastStepWasForward = newHead != null;
 		if (!lastStepWasForward) {
 			ObjectId headId = getHead().getObjectId();
+			// getHead() checks for null
+			assert headId != null;
 			if (!AnyObjectId.equals(headId, newParents.get(0)))
 				checkoutCommit(headId.getName(), newParents.get(0));
 
@@ -609,6 +612,7 @@
 					// their non-first parents rewritten
 					MergeCommand merge = git.merge()
 							.setFastForward(MergeCommand.FastForwardMode.NO_FF)
+							.setProgressMonitor(monitor)
 							.setCommit(false);
 					for (int i = 1; i < commitToPick.getParentCount(); i++)
 						merge.include(newParents.get(i));
@@ -668,12 +672,15 @@
 	}
 
 	private void writeRewrittenHashes() throws RevisionSyntaxException,
-			IOException {
+			IOException, RefNotFoundException {
 		File currentCommitFile = rebaseState.getFile(CURRENT_COMMIT);
 		if (!currentCommitFile.exists())
 			return;
 
-		String head = repo.resolve(Constants.HEAD).getName();
+		ObjectId headId = getHead().getObjectId();
+		// getHead() checks for null
+		assert headId != null;
+		String head = headId.getName();
 		String currentCommits = rebaseState.readFile(CURRENT_COMMIT);
 		for (String current : currentCommits.split("\n")) //$NON-NLS-1$
 			RebaseState
@@ -743,8 +750,8 @@
 
 	private void resetSoftToParent() throws IOException,
 			GitAPIException, CheckoutConflictException {
-		Ref orig_head = repo.getRef(Constants.ORIG_HEAD);
-		ObjectId orig_headId = orig_head.getObjectId();
+		Ref ref = repo.getRef(Constants.ORIG_HEAD);
+		ObjectId orig_head = ref == null ? null : ref.getObjectId();
 		try {
 			// we have already commited the cherry-picked commit.
 			// what we need is to have changes introduced by this
@@ -755,7 +762,7 @@
 		} finally {
 			// set ORIG_HEAD back to where we started because soft
 			// reset moved it
-			repo.writeOrigHead(orig_headId);
+			repo.writeOrigHead(orig_head);
 		}
 	}
 
@@ -980,6 +987,9 @@
 		try {
 			raw = IO.readFully(authorScriptFile);
 		} catch (FileNotFoundException notFound) {
+			if (authorScriptFile.exists()) {
+				throw notFound;
+			}
 			return null;
 		}
 		return parseAuthor(raw);
@@ -1069,11 +1079,12 @@
 
 		Ref head = getHead();
 
-		String headName = getHeadName(head);
 		ObjectId headId = head.getObjectId();
-		if (headId == null)
+		if (headId == null) {
 			throw new RefNotFoundException(MessageFormat.format(
 					JGitText.get().refNotResolved, Constants.HEAD));
+		}
+		String headName = getHeadName(head);
 		RevCommit headCommit = walk.lookupCommit(headId);
 		RevCommit upstream = walk.lookupCommit(upstreamCommit.getId());
 
@@ -1184,10 +1195,14 @@
 
 	private static String getHeadName(Ref head) {
 		String headName;
-		if (head.isSymbolic())
+		if (head.isSymbolic()) {
 			headName = head.getTarget().getName();
-		else
-			headName = head.getObjectId().getName();
+		} else {
+			ObjectId headId = head.getObjectId();
+			// the callers are checking this already
+			assert headId != null;
+			headName = headId.getName();
+		}
 		return headName;
 	}
 
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/RemoteAddCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/RemoteAddCommand.java
new file mode 100644
index 0000000..6795669
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/RemoteAddCommand.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2015, Kaloyan Raev <kaloyan.r@zend.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.api;
+
+import java.io.IOException;
+import java.net.URISyntaxException;
+
+import org.eclipse.jgit.api.errors.GitAPIException;
+import org.eclipse.jgit.api.errors.JGitInternalException;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.lib.StoredConfig;
+import org.eclipse.jgit.transport.RefSpec;
+import org.eclipse.jgit.transport.RemoteConfig;
+import org.eclipse.jgit.transport.URIish;
+
+/**
+ * Used to add a new remote.
+ *
+ * This class has setters for all supported options and arguments of this
+ * command and a {@link #call()} method to finally execute the command.
+ *
+ * @see <a href=
+ *      "http://www.kernel.org/pub/software/scm/git/docs/git-remote.html" > Git
+ *      documentation about Remote</a>
+ *
+ * @since 4.2
+ */
+public class RemoteAddCommand extends GitCommand<RemoteConfig> {
+
+	private String name;
+
+	private URIish uri;
+
+	/**
+	 * @param repo
+	 */
+	protected RemoteAddCommand(Repository repo) {
+		super(repo);
+	}
+
+	/**
+	 * The name of the remote to add.
+	 *
+	 * @param name
+	 *            a remote name
+	 */
+	public void setName(String name) {
+		this.name = name;
+	}
+
+	/**
+	 * The URL of the repository for the new remote.
+	 *
+	 * @param uri
+	 *            an URL for the remote
+	 */
+	public void setUri(URIish uri) {
+		this.uri = uri;
+	}
+
+	/**
+	 * Executes the {@code remote add} command with all the options and
+	 * parameters collected by the setter methods of this class.
+	 *
+	 * @return the {@link RemoteConfig} object of the added remote
+	 */
+	@Override
+	public RemoteConfig call() throws GitAPIException {
+		checkCallable();
+
+		try {
+			StoredConfig config = repo.getConfig();
+			RemoteConfig remote = new RemoteConfig(config, name);
+
+			RefSpec refSpec = new RefSpec();
+			refSpec = refSpec.setForceUpdate(true);
+			refSpec = refSpec.setSourceDestination(Constants.R_HEADS + "*", //$NON-NLS-1$
+					Constants.R_REMOTES + name + "/*"); //$NON-NLS-1$
+			remote.addFetchRefSpec(refSpec);
+
+			remote.addURI(uri);
+
+			remote.update(config);
+			config.save();
+			return remote;
+		} catch (IOException | URISyntaxException e) {
+			throw new JGitInternalException(e.getMessage(), e);
+		}
+
+	}
+
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/SymlinkTreeEntry.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/RemoteListCommand.java
similarity index 60%
copy from org.eclipse.jgit/src/org/eclipse/jgit/lib/SymlinkTreeEntry.java
copy to org.eclipse.jgit/src/org/eclipse/jgit/api/RemoteListCommand.java
index c7e41bc..f778eaa 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/SymlinkTreeEntry.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/RemoteListCommand.java
@@ -1,6 +1,5 @@
 /*
- * Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com>
- * Copyright (C) 2006-2007, Shawn O. Pearce <spearce@spearce.org>
+ * Copyright (C) 2015, Kaloyan Raev <kaloyan.r@zend.com>
  * and other copyright owners as documented in the project's IP log.
  *
  * This program and the accompanying materials are made available
@@ -41,45 +40,52 @@
  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
+package org.eclipse.jgit.api;
 
-package org.eclipse.jgit.lib;
+import java.net.URISyntaxException;
+import java.util.List;
+
+import org.eclipse.jgit.api.errors.GitAPIException;
+import org.eclipse.jgit.api.errors.JGitInternalException;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.transport.RemoteConfig;
 
 /**
- * A tree entry representing a symbolic link.
+ * Used to obtain the list of remotes.
  *
- * Note. Java cannot really handle these as file system objects.
+ * This class has setters for all supported options and arguments of this
+ * command and a {@link #call()} method to finally execute the command.
  *
- * @deprecated To look up information about a single path, use
- * {@link org.eclipse.jgit.treewalk.TreeWalk#forPath(Repository, String, org.eclipse.jgit.revwalk.RevTree)}.
- * To lookup information about multiple paths at once, use a
- * {@link org.eclipse.jgit.treewalk.TreeWalk} and obtain the current entry's
- * information from its getter methods.
+ * @see <a href=
+ *      "http://www.kernel.org/pub/software/scm/git/docs/git-remote.html" > Git
+ *      documentation about Remote</a>
+ *
+ * @since 4.2
  */
-@Deprecated
-public class SymlinkTreeEntry extends TreeEntry {
+public class RemoteListCommand extends GitCommand<List<RemoteConfig>> {
 
 	/**
-	 * Construct a {@link SymlinkTreeEntry} with the specified name and SHA-1 in
-	 * the specified parent
-	 *
-	 * @param parent
-	 * @param id
-	 * @param nameUTF8
+	 * @param repo
 	 */
-	public SymlinkTreeEntry(final Tree parent, final ObjectId id,
-			final byte[] nameUTF8) {
-		super(parent, id, nameUTF8);
+	protected RemoteListCommand(Repository repo) {
+		super(repo);
 	}
 
-	public FileMode getMode() {
-		return FileMode.SYMLINK;
+	/**
+	 * Executes the {@code remote} command with all the options and parameters
+	 * collected by the setter methods of this class.
+	 *
+	 * @return a list of {@link RemoteConfig} objects.
+	 */
+	@Override
+	public List<RemoteConfig> call() throws GitAPIException {
+		checkCallable();
+
+		try {
+			return RemoteConfig.getAllRemoteConfigs(repo.getConfig());
+		} catch (URISyntaxException e) {
+			throw new JGitInternalException(e.getMessage(), e);
+		}
 	}
 
-	public String toString() {
-		final StringBuilder r = new StringBuilder();
-		r.append(ObjectId.toString(getId()));
-		r.append(" S "); //$NON-NLS-1$
-		r.append(getFullName());
-		return r.toString();
-	}
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/RemoteRemoveCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/RemoteRemoveCommand.java
new file mode 100644
index 0000000..5782bf6
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/RemoteRemoveCommand.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2015, Kaloyan Raev <kaloyan.r@zend.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.api;
+
+import java.io.IOException;
+import java.net.URISyntaxException;
+
+import org.eclipse.jgit.api.errors.GitAPIException;
+import org.eclipse.jgit.api.errors.JGitInternalException;
+import org.eclipse.jgit.lib.ConfigConstants;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.lib.StoredConfig;
+import org.eclipse.jgit.transport.RemoteConfig;
+
+/**
+ * Used to remove an existing remote.
+ *
+ * This class has setters for all supported options and arguments of this
+ * command and a {@link #call()} method to finally execute the command.
+ *
+ * @see <a href=
+ *      "http://www.kernel.org/pub/software/scm/git/docs/git-remote.html" > Git
+ *      documentation about Remote</a>
+ *
+ * @since 4.2
+ */
+public class RemoteRemoveCommand extends GitCommand<RemoteConfig> {
+
+	private String name;
+
+	/**
+	 * @param repo
+	 */
+	protected RemoteRemoveCommand(Repository repo) {
+		super(repo);
+	}
+
+	/**
+	 * The name of the remote to remove.
+	 *
+	 * @param name
+	 *            a remote name
+	 */
+	public void setName(String name) {
+		this.name = name;
+	}
+
+	/**
+	 * Executes the {@code remote} command with all the options and parameters
+	 * collected by the setter methods of this class.
+	 *
+	 * @return the {@link RemoteConfig} object of the removed remote
+	 */
+	@Override
+	public RemoteConfig call() throws GitAPIException {
+		checkCallable();
+
+		try {
+			StoredConfig config = repo.getConfig();
+			RemoteConfig remote = new RemoteConfig(config, name);
+			config.unsetSection(ConfigConstants.CONFIG_KEY_REMOTE, name);
+			config.save();
+			return remote;
+		} catch (IOException | URISyntaxException e) {
+			throw new JGitInternalException(e.getMessage(), e);
+		}
+
+	}
+
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/RemoteSetUrlCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/RemoteSetUrlCommand.java
new file mode 100644
index 0000000..6bd2ac7
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/RemoteSetUrlCommand.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2015, Kaloyan Raev <kaloyan.r@zend.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.api;
+
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.util.List;
+
+import org.eclipse.jgit.api.errors.GitAPIException;
+import org.eclipse.jgit.api.errors.JGitInternalException;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.lib.StoredConfig;
+import org.eclipse.jgit.transport.RemoteConfig;
+import org.eclipse.jgit.transport.URIish;
+
+/**
+ * Used to to change the URL of a remote.
+ *
+ * This class has setters for all supported options and arguments of this
+ * command and a {@link #call()} method to finally execute the command.
+ *
+ * @see <a href=
+ *      "http://www.kernel.org/pub/software/scm/git/docs/git-remote.html" > Git
+ *      documentation about Remote</a>
+ *
+ * @since 4.2
+ */
+public class RemoteSetUrlCommand extends GitCommand<RemoteConfig> {
+
+	private String name;
+
+	private URIish uri;
+
+	private boolean push;
+
+	/**
+	 * @param repo
+	 */
+	protected RemoteSetUrlCommand(Repository repo) {
+		super(repo);
+	}
+
+	/**
+	 * The name of the remote to change the URL for.
+	 *
+	 * @param name
+	 *            a remote name
+	 */
+	public void setName(String name) {
+		this.name = name;
+	}
+
+	/**
+	 * The new URL for the remote.
+	 *
+	 * @param uri
+	 *            an URL for the remote
+	 */
+	public void setUri(URIish uri) {
+		this.uri = uri;
+	}
+
+	/**
+	 * Whether to change the push URL of the remote instead of the fetch URL.
+	 *
+	 * @param push
+	 *            <code>true</code> to set the push url, <code>false</code> to
+	 *            set the fetch url
+	 */
+	public void setPush(boolean push) {
+		this.push = push;
+	}
+
+	/**
+	 * Executes the {@code remote} command with all the options and parameters
+	 * collected by the setter methods of this class.
+	 *
+	 * @return the {@link RemoteConfig} object of the modified remote
+	 */
+	@Override
+	public RemoteConfig call() throws GitAPIException {
+		checkCallable();
+
+		try {
+			StoredConfig config = repo.getConfig();
+			RemoteConfig remote = new RemoteConfig(config, name);
+			if (push) {
+				List<URIish> uris = remote.getPushURIs();
+				if (uris.size() > 1) {
+					throw new JGitInternalException(
+							"remote.newtest.pushurl has multiple values"); //$NON-NLS-1$
+				} else if (uris.size() == 1) {
+					remote.removePushURI(uris.get(0));
+				}
+				remote.addPushURI(uri);
+			} else {
+				List<URIish> uris = remote.getURIs();
+				if (uris.size() > 1) {
+					throw new JGitInternalException(
+							"remote.newtest.url has multiple values"); //$NON-NLS-1$
+				} else if (uris.size() == 1) {
+					remote.removeURI(uris.get(0));
+				}
+				remote.addURI(uri);
+			}
+
+			remote.update(config);
+			config.save();
+			return remote;
+		} catch (IOException | URISyntaxException e) {
+			throw new JGitInternalException(e.getMessage(), e);
+		}
+	}
+
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/RenameBranchCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/RenameBranchCommand.java
index 607253b..0731dd4 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/RenameBranchCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/RenameBranchCommand.java
@@ -51,6 +51,7 @@
 import org.eclipse.jgit.api.errors.GitAPIException;
 import org.eclipse.jgit.api.errors.InvalidRefNameException;
 import org.eclipse.jgit.api.errors.JGitInternalException;
+import org.eclipse.jgit.api.errors.NoHeadException;
 import org.eclipse.jgit.api.errors.RefAlreadyExistsException;
 import org.eclipse.jgit.api.errors.RefNotFoundException;
 import org.eclipse.jgit.internal.JGitText;
@@ -121,6 +122,10 @@
 				fullOldName = ref.getName();
 			} else {
 				fullOldName = repo.getFullBranch();
+				if (fullOldName == null) {
+					throw new NoHeadException(
+							JGitText.get().invalidRepositoryStateNoHead);
+				}
 				if (ObjectId.isId(fullOldName))
 					throw new DetachedHeadException();
 			}
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 8f4bc4f..4c91e6c 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/ResetCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/ResetCommand.java
@@ -190,10 +190,8 @@
 				ObjectId origHead = ru.getOldObjectId();
 				if (origHead != null)
 					repo.writeOrigHead(origHead);
-				result = ru.getRef();
-			} else {
-				result = repo.getRef(Constants.HEAD);
 			}
+			result = repo.exactRef(Constants.HEAD);
 
 			if (mode == null)
 				mode = ResetType.MIXED;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/StashApplyCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/StashApplyCommand.java
index 6de25a0..8ef5508 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/StashApplyCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/StashApplyCommand.java
@@ -357,7 +357,7 @@
 
 	private void checkoutPath(DirCacheEntry entry, ObjectReader reader) {
 		try {
-			DirCacheCheckout.checkoutEntry(repo, entry, reader);
+			DirCacheCheckout.checkoutEntry(repo, entry, reader, true);
 		} catch (IOException e) {
 			throw new JGitInternalException(MessageFormat.format(
 					JGitText.get().checkoutConflictWithFile,
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/StashDropCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/StashDropCommand.java
index 7923fd4..f6903be 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/StashDropCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/StashDropCommand.java
@@ -46,6 +46,7 @@
 
 import java.io.File;
 import java.io.IOException;
+import java.nio.file.StandardCopyOption;
 import java.text.MessageFormat;
 import java.util.List;
 
@@ -220,12 +221,14 @@
 						entry.getWho(), entry.getComment());
 				entryId = entry.getNewId();
 			}
-			if (!stashLockFile.renameTo(stashFile)) {
-				FileUtils.delete(stashFile);
-				if (!stashLockFile.renameTo(stashFile))
+			try {
+				FileUtils.rename(stashLockFile, stashFile,
+						StandardCopyOption.ATOMIC_MOVE);
+			} catch (IOException e) {
 					throw new JGitInternalException(MessageFormat.format(
 							JGitText.get().renameFileFailed,
-							stashLockFile.getPath(), stashFile.getPath()));
+								stashLockFile.getPath(), stashFile.getPath()),
+						e);
 			}
 		} catch (IOException e) {
 			throw new JGitInternalException(JGitText.get().stashDropFailed, e);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleUpdateCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleUpdateCommand.java
index e288d77..342d7f4 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleUpdateCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleUpdateCommand.java
@@ -1,5 +1,6 @@
 /*
  * Copyright (C) 2011, GitHub Inc.
+ * Copyright (C) 2016, Laurent Delaigue <laurent.delaigue@obeo.fr>
  * and other copyright owners as documented in the project's IP log.
  *
  * This program and the accompanying materials are made available
@@ -178,11 +179,13 @@
 					if (ConfigConstants.CONFIG_KEY_MERGE.equals(update)) {
 						MergeCommand merge = new MergeCommand(submoduleRepo);
 						merge.include(commit);
+						merge.setProgressMonitor(monitor);
 						merge.setStrategy(strategy);
 						merge.call();
 					} else if (ConfigConstants.CONFIG_KEY_REBASE.equals(update)) {
 						RebaseCommand rebase = new RebaseCommand(submoduleRepo);
 						rebase.setUpstream(commit);
+						rebase.setProgressMonitor(monitor);
 						rebase.setStrategy(strategy);
 						rebase.call();
 					} else {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/TransportCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/TransportCommand.java
index 1aeb610..3d2e46b 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/TransportCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/TransportCommand.java
@@ -79,6 +79,7 @@
 	 */
 	protected TransportCommand(final Repository repo) {
 		super(repo);
+		setCredentialsProvider(CredentialsProvider.getDefault());
 	}
 
 	/**
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/EmtpyCommitException.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/EmtpyCommitException.java
new file mode 100644
index 0000000..b3cc1bf
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/EmtpyCommitException.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2015, 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.api.errors;
+
+/**
+ * Exception thrown when a newly created commit does not contain any changes
+ *
+ * @since 4.2
+ */
+public class EmtpyCommitException extends GitAPIException {
+	private static final long serialVersionUID = 1L;
+
+	/**
+	 * @param message
+	 * @param cause
+	 */
+	public EmtpyCommitException(String message, Throwable cause) {
+		super(message, cause);
+	}
+
+	/**
+	 * @param message
+	 */
+	public EmtpyCommitException(String message) {
+		super(message);
+	}
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/FilterFailedException.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/FilterFailedException.java
new file mode 100644
index 0000000..fbc30ef
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/FilterFailedException.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2015, 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.api.errors;
+
+import java.text.MessageFormat;
+
+import org.eclipse.jgit.internal.JGitText;
+
+/**
+ * Exception thrown when the execution of a filter command failed
+ *
+ * @since 4.2
+ */
+public class FilterFailedException extends GitAPIException {
+	private static final long serialVersionUID = 1L;
+
+	private String filterCommand;
+
+	private String path;
+
+	private byte[] stdout;
+
+	private String stderr;
+
+	private int rc;
+
+	/**
+	 * Thrown if during execution of filter command an exception occurred
+	 *
+	 * @param cause
+	 *            the exception
+	 * @param filterCommand
+	 *            the command which failed
+	 * @param path
+	 *            the path processed by the filter
+	 */
+	public FilterFailedException(Exception cause, String filterCommand,
+			String path) {
+		super(MessageFormat.format(JGitText.get().filterExecutionFailed,
+				filterCommand, path), cause);
+		this.filterCommand = filterCommand;
+		this.path = path;
+	}
+
+	/**
+	 * Thrown if a filter command returns a non-zero return code
+	 *
+	 * @param rc
+	 *            the return code
+	 * @param filterCommand
+	 *            the command which failed
+	 * @param path
+	 *            the path processed by the filter
+	 * @param stdout
+	 *            the output the filter generated so far. This should be limited
+	 *            to reasonable size.
+	 * @param stderr
+	 *            the stderr output of the filter
+	 */
+	@SuppressWarnings("boxing")
+	public FilterFailedException(int rc, String filterCommand, String path,
+			byte[] stdout, String stderr) {
+		super(MessageFormat.format(JGitText.get().filterExecutionFailedRc,
+				filterCommand, path, rc, stderr));
+		this.rc = rc;
+		this.filterCommand = filterCommand;
+		this.path = path;
+		this.stdout = stdout;
+		this.stderr = stderr;
+	}
+
+	/**
+	 * @return the filterCommand
+	 */
+	public String getFilterCommand() {
+		return filterCommand;
+	}
+
+	/**
+	 * @return the path of the file processed by the filter command
+	 */
+	public String getPath() {
+		return path;
+	}
+
+	/**
+	 * @return the output generated by the filter command. Might be truncated to
+	 *         limit memory consumption.
+	 */
+	public byte[] getOutput() {
+		return stdout;
+	}
+
+	/**
+	 * @return the error output returned by the filter command
+	 */
+	public String getError() {
+		return stderr;
+	}
+
+	/**
+	 * @return the return code returned by the filter command
+	 */
+	public int getReturnCode() {
+		return rc;
+	}
+
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/attributes/Attribute.java b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/Attribute.java
index d3ce685..905ad76 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/attributes/Attribute.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/Attribute.java
@@ -50,8 +50,10 @@
  * <li>Set - represented by {@link State#SET}</li>
  * <li>Unset - represented by {@link State#UNSET}</li>
  * <li>Set to a value - represented by {@link State#CUSTOM}</li>
- * <li>Unspecified - <code>null</code> is used instead of an instance of this
- * class</li>
+ * <li>Unspecified - used to revert an attribute . This is crucial in order to
+ * mark an attribute as unspecified in the attributes map and thus preventing
+ * following (with lower priority) nodes from setting the attribute to a value
+ * at all</li>
  * </ul>
  * </p>
  *
@@ -61,6 +63,7 @@
 
 	/**
 	 * The attribute value state
+	 * see also https://www.kernel.org/pub/software/scm/git/docs/gitattributes.html
 	 */
 	public static enum State {
 		/** the attribute is set */
@@ -69,6 +72,13 @@
 		/** the attribute is unset */
 		UNSET,
 
+		/**
+		 * the attribute appears as if it would not be defined at all
+		 *
+		 * @since 4.2
+		 */
+		UNSPECIFIED,
+
 		/** the attribute is set to a custom value */
 		CUSTOM
 	}
@@ -176,6 +186,8 @@
 			return key;
 		case UNSET:
 			return "-" + key; //$NON-NLS-1$
+		case UNSPECIFIED:
+			return "!" + key; //$NON-NLS-1$
 		case CUSTOM:
 		default:
 			return key + "=" + value; //$NON-NLS-1$
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/attributes/Attributes.java b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/Attributes.java
new file mode 100644
index 0000000..0810e31
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/Attributes.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2015, Ivan Motsch <ivan.motsch@bsiag.com>
+ *
+ * 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.attributes;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import org.eclipse.jgit.attributes.Attribute.State;
+
+/**
+ * Represents a set of attributes for a path
+ * <p>
+ *
+ * @since 4.2
+ */
+public final class Attributes {
+	private final Map<String, Attribute> map = new LinkedHashMap<>();
+
+	/**
+	 * Creates a new instance
+	 *
+	 * @param attributes
+	 */
+	public Attributes(Attribute... attributes) {
+		if (attributes != null) {
+			for (Attribute a : attributes) {
+				put(a);
+			}
+		}
+	}
+
+	/**
+	 * @return true if the set does not contain any attributes
+	 */
+	public boolean isEmpty() {
+		return map.isEmpty();
+	}
+
+	/**
+	 * @param key
+	 * @return the attribute or null
+	 */
+	public Attribute get(String key) {
+		return map.get(key);
+	}
+
+	/**
+	 * @return all attributes
+	 */
+	public Collection<Attribute> getAll() {
+		return new ArrayList<>(map.values());
+	}
+
+	/**
+	 * @param a
+	 */
+	public void put(Attribute a) {
+		map.put(a.getKey(), a);
+	}
+
+	/**
+	 * @param key
+	 */
+	public void remove(String key) {
+		map.remove(key);
+	}
+
+	/**
+	 * @param key
+	 * @return true if the {@link Attributes} contains this key
+	 */
+	public boolean containsKey(String key) {
+		return map.containsKey(key);
+	}
+
+	/**
+	 * Returns the state.
+	 *
+	 * @param key
+	 *
+	 * @return the state (never returns <code>null</code>)
+	 */
+	public Attribute.State getState(String key) {
+		Attribute a = map.get(key);
+		return a != null ? a.getState() : Attribute.State.UNSPECIFIED;
+	}
+
+	/**
+	 * @param key
+	 * @return true if the key is {@link State#SET}, false in all other cases
+	 */
+	public boolean isSet(String key) {
+		return (getState(key) == State.SET);
+	}
+
+	/**
+	 * @param key
+	 * @return true if the key is {@link State#UNSET}, false in all other cases
+	 */
+	public boolean isUnset(String key) {
+		return (getState(key) == State.UNSET);
+	}
+
+	/**
+	 * @param key
+	 * @return true if the key is {@link State#UNSPECIFIED}, false in all other
+	 *         cases
+	 */
+	public boolean isUnspecified(String key) {
+		return (getState(key) == State.UNSPECIFIED);
+	}
+
+	/**
+	 * @param key
+	 * @return true if the key is {@link State#CUSTOM}, false in all other cases
+	 *         see {@link #getValue(String)} for the value of the key
+	 */
+	public boolean isCustom(String key) {
+		return (getState(key) == State.CUSTOM);
+	}
+
+	/**
+	 * @param key
+	 * @return the attribute value (may be <code>null</code>)
+	 */
+	public String getValue(String key) {
+		Attribute a = map.get(key);
+		return a != null ? a.getValue() : null;
+	}
+
+	@Override
+	public String toString() {
+		StringBuilder buf = new StringBuilder();
+		buf.append(getClass().getSimpleName());
+		buf.append("["); //$NON-NLS-1$
+		buf.append(" "); //$NON-NLS-1$
+		for (Attribute a : map.values()) {
+			buf.append(a.toString());
+			buf.append(" "); //$NON-NLS-1$
+		}
+		buf.append("]"); //$NON-NLS-1$
+		return buf.toString();
+	}
+
+	@Override
+	public int hashCode() {
+		return map.hashCode();
+	}
+
+	@Override
+	public boolean equals(Object obj) {
+		if (this == obj)
+			return true;
+		if (!(obj instanceof Attributes))
+			return false;
+		Attributes other = (Attributes) obj;
+		return this.map.equals(other.map);
+	}
+
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesNode.java b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesNode.java
index 70f56ff..5c0aba2 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesNode.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesNode.java
@@ -50,7 +50,6 @@
 import java.util.Collections;
 import java.util.List;
 import java.util.ListIterator;
-import java.util.Map;
 
 import org.eclipse.jgit.lib.Constants;
 
@@ -134,11 +133,12 @@
 	 *            true if the target item is a directory.
 	 * @param attributes
 	 *            Map that will hold the attributes matching this entry path. If
-	 *            it is not empty, this method will NOT override any
-	 *            existing entry.
+	 *            it is not empty, this method will NOT override any existing
+	 *            entry.
+	 * @since 4.2
 	 */
-	public void getAttributes(String entryPath, boolean isDirectory,
-			Map<String, Attribute> attributes) {
+	public void getAttributes(String entryPath,
+			boolean isDirectory, Attributes attributes) {
 		// Parse rules in the reverse order that they were read since the last
 		// entry should be used
 		ListIterator<AttributesRule> ruleIterator = rules.listIterator(rules
@@ -153,7 +153,7 @@
 				while (attributeIte.hasPrevious()) {
 					Attribute attr = attributeIte.previous();
 					if (!attributes.containsKey(attr.getKey()))
-						attributes.put(attr.getKey(), attr);
+						attributes.put(attr);
 				}
 			}
 		}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/SymlinkTreeEntry.java b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesNodeProvider.java
similarity index 60%
copy from org.eclipse.jgit/src/org/eclipse/jgit/lib/SymlinkTreeEntry.java
copy to org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesNodeProvider.java
index c7e41bc..6f2ebad 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/SymlinkTreeEntry.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesNodeProvider.java
@@ -1,6 +1,5 @@
 /*
- * Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com>
- * Copyright (C) 2006-2007, Shawn O. Pearce <spearce@spearce.org>
+ * Copyright (C) 2014, Arthur Daussy <arthur.daussy@obeo.fr>
  * and other copyright owners as documented in the project's IP log.
  *
  * This program and the accompanying materials are made available
@@ -41,45 +40,42 @@
  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
+package org.eclipse.jgit.attributes;
 
-package org.eclipse.jgit.lib;
+import java.io.IOException;
+
+import org.eclipse.jgit.lib.CoreConfig;
 
 /**
- * A tree entry representing a symbolic link.
+ * An interface used to retrieve the global and info {@link AttributesNode}s.
  *
- * Note. Java cannot really handle these as file system objects.
+ * @since 4.2
  *
- * @deprecated To look up information about a single path, use
- * {@link org.eclipse.jgit.treewalk.TreeWalk#forPath(Repository, String, org.eclipse.jgit.revwalk.RevTree)}.
- * To lookup information about multiple paths at once, use a
- * {@link org.eclipse.jgit.treewalk.TreeWalk} and obtain the current entry's
- * information from its getter methods.
  */
-@Deprecated
-public class SymlinkTreeEntry extends TreeEntry {
+public interface AttributesNodeProvider {
 
 	/**
-	 * Construct a {@link SymlinkTreeEntry} with the specified name and SHA-1 in
-	 * the specified parent
+	 * Retrieve the {@link AttributesNode} that holds the information located
+	 * in $GIT_DIR/info/attributes file.
 	 *
-	 * @param parent
-	 * @param id
-	 * @param nameUTF8
+	 * @return the {@link AttributesNode} that holds the information located in
+	 *         $GIT_DIR/info/attributes file.
+	 * @throws IOException
+	 *             if an error is raised while parsing the attributes file
 	 */
-	public SymlinkTreeEntry(final Tree parent, final ObjectId id,
-			final byte[] nameUTF8) {
-		super(parent, id, nameUTF8);
-	}
+	public AttributesNode getInfoAttributesNode() throws IOException;
 
-	public FileMode getMode() {
-		return FileMode.SYMLINK;
-	}
+	/**
+	 * Retrieve the {@link AttributesNode} that holds the information located
+	 * in the global gitattributes file.
+	 *
+	 * @return the {@link AttributesNode} that holds the information located in
+	 *         the global gitattributes file.
+	 * @throws IOException
+	 *             IOException if an error is raised while parsing the
+	 *             attributes file
+	 * @see CoreConfig#getAttributesFile()
+	 */
+	public AttributesNode getGlobalAttributesNode() throws IOException;
 
-	public String toString() {
-		final StringBuilder r = new StringBuilder();
-		r.append(ObjectId.toString(getId()));
-		r.append(" S "); //$NON-NLS-1$
-		r.append(getFullName());
-		return r.toString();
-	}
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/SymlinkTreeEntry.java b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesProvider.java
similarity index 60%
copy from org.eclipse.jgit/src/org/eclipse/jgit/lib/SymlinkTreeEntry.java
copy to org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesProvider.java
index c7e41bc..1037f69 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/SymlinkTreeEntry.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesProvider.java
@@ -1,6 +1,5 @@
 /*
- * Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com>
- * Copyright (C) 2006-2007, Shawn O. Pearce <spearce@spearce.org>
+ * Copyright (C) 2015, 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
@@ -41,45 +40,16 @@
  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
-
-package org.eclipse.jgit.lib;
+package org.eclipse.jgit.attributes;
 
 /**
- * A tree entry representing a symbolic link.
+ * Interface for classes which provide git attributes
  *
- * Note. Java cannot really handle these as file system objects.
- *
- * @deprecated To look up information about a single path, use
- * {@link org.eclipse.jgit.treewalk.TreeWalk#forPath(Repository, String, org.eclipse.jgit.revwalk.RevTree)}.
- * To lookup information about multiple paths at once, use a
- * {@link org.eclipse.jgit.treewalk.TreeWalk} and obtain the current entry's
- * information from its getter methods.
+ * @since 4.2
  */
-@Deprecated
-public class SymlinkTreeEntry extends TreeEntry {
-
+public interface AttributesProvider {
 	/**
-	 * Construct a {@link SymlinkTreeEntry} with the specified name and SHA-1 in
-	 * the specified parent
-	 *
-	 * @param parent
-	 * @param id
-	 * @param nameUTF8
+	 * @return the currently active attributes
 	 */
-	public SymlinkTreeEntry(final Tree parent, final ObjectId id,
-			final byte[] nameUTF8) {
-		super(parent, id, nameUTF8);
-	}
-
-	public FileMode getMode() {
-		return FileMode.SYMLINK;
-	}
-
-	public String toString() {
-		final StringBuilder r = new StringBuilder();
-		r.append(ObjectId.toString(getId()));
-		r.append(" S "); //$NON-NLS-1$
-		r.append(getFullName());
-		return r.toString();
-	}
+	public Attributes getAttributes();
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesRule.java b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesRule.java
index bcac14b..35d18c4 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesRule.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesRule.java
@@ -84,6 +84,13 @@
 				continue;
 			}
 
+			if (attribute.startsWith("!")) {//$NON-NLS-1$
+				if (attribute.length() > 1)
+					result.add(new Attribute(attribute.substring(1),
+							State.UNSPECIFIED));
+				continue;
+			}
+
 			final int equalsIndex = attribute.indexOf("="); //$NON-NLS-1$
 			if (equalsIndex == -1)
 				result.add(new Attribute(attribute, State.SET));
@@ -200,4 +207,16 @@
 		boolean match = matcher.matches(relativeTarget, isDirectory);
 		return match;
 	}
+
+	@Override
+	public String toString() {
+		StringBuilder sb = new StringBuilder();
+		sb.append(pattern);
+		for (Attribute a : attributes) {
+			sb.append(" "); //$NON-NLS-1$
+			sb.append(a);
+		}
+		return sb.toString();
+
+	}
 }
\ No newline at end of file
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/diff/ContentSource.java b/org.eclipse.jgit/src/org/eclipse/jgit/diff/ContentSource.java
index 3c780e7..444ab1c 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/diff/ContentSource.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/diff/ContentSource.java
@@ -132,7 +132,11 @@
 
 		@Override
 		public long size(String path, ObjectId id) throws IOException {
-			return reader.getObjectSize(id, Constants.OBJ_BLOB);
+			try {
+				return reader.getObjectSize(id, Constants.OBJ_BLOB);
+			} catch (MissingObjectException ignore) {
+				return 0;
+			}
 		}
 
 		@Override
@@ -148,7 +152,7 @@
 
 		private String current;
 
-		private WorkingTreeIterator ptr;
+		WorkingTreeIterator ptr;
 
 		WorkingTreeSource(WorkingTreeIterator iterator) {
 			this.tw = new TreeWalk((ObjectReader) null);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/diff/HistogramDiff.java b/org.eclipse.jgit/src/org/eclipse/jgit/diff/HistogramDiff.java
index e57faaf..2f5c9ea 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/diff/HistogramDiff.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/diff/HistogramDiff.java
@@ -94,7 +94,7 @@
  */
 public class HistogramDiff extends LowLevelDiffAlgorithm {
 	/** Algorithm to use when there are too many element occurrences. */
-	private DiffAlgorithm fallback = MyersDiff.INSTANCE;
+	DiffAlgorithm fallback = MyersDiff.INSTANCE;
 
 	/**
 	 * Maximum number of positions to consider for a given element hash.
@@ -103,7 +103,7 @@
 	 * size is capped to ensure search is linear time at O(len_A + len_B) rather
 	 * than quadratic at O(len_A * len_B).
 	 */
-	private int maxChainLength = 64;
+	int maxChainLength = 64;
 
 	/**
 	 * Set the algorithm used when there are too many element occurrences.
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/BaseDirCacheEditor.java b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/BaseDirCacheEditor.java
index 70f80ae..0fbc1f8 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/BaseDirCacheEditor.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/BaseDirCacheEditor.java
@@ -44,8 +44,13 @@
 
 package org.eclipse.jgit.dircache;
 
+import static org.eclipse.jgit.lib.FileMode.TYPE_TREE;
+import static org.eclipse.jgit.util.Paths.compareSameName;
+
 import java.io.IOException;
 
+import org.eclipse.jgit.errors.DirCacheNameConflictException;
+
 /**
  * Generic update/editing support for {@link DirCache}.
  * <p>
@@ -168,6 +173,7 @@
 	 * {@link #finish()}, and only after {@link #entries} is sorted.
 	 */
 	protected void replace() {
+		checkNameConflicts();
 		if (entryCnt < entries.length / 2) {
 			final DirCacheEntry[] n = new DirCacheEntry[entryCnt];
 			System.arraycopy(entries, 0, n, 0, entryCnt);
@@ -176,6 +182,76 @@
 		cache.replace(entries, entryCnt);
 	}
 
+	private void checkNameConflicts() {
+		int end = entryCnt - 1;
+		for (int eIdx = 0; eIdx < end; eIdx++) {
+			DirCacheEntry e = entries[eIdx];
+			if (e.getStage() != 0) {
+				continue;
+			}
+
+			byte[] ePath = e.path;
+			int prefixLen = lastSlash(ePath) + 1;
+
+			for (int nIdx = eIdx + 1; nIdx < entryCnt; nIdx++) {
+				DirCacheEntry n = entries[nIdx];
+				if (n.getStage() != 0) {
+					continue;
+				}
+
+				byte[] nPath = n.path;
+				if (!startsWith(ePath, nPath, prefixLen)) {
+					// Different prefix; this entry is in another directory.
+					break;
+				}
+
+				int s = nextSlash(nPath, prefixLen);
+				int m = s < nPath.length ? TYPE_TREE : n.getRawMode();
+				int cmp = compareSameName(
+						ePath, prefixLen, ePath.length,
+						nPath, prefixLen, s, m);
+				if (cmp < 0) {
+					break;
+				} else if (cmp == 0) {
+					throw new DirCacheNameConflictException(
+							e.getPathString(),
+							n.getPathString());
+				}
+			}
+		}
+	}
+
+	private static int lastSlash(byte[] path) {
+		for (int i = path.length - 1; i >= 0; i--) {
+			if (path[i] == '/') {
+				return i;
+			}
+		}
+		return -1;
+	}
+
+	private static int nextSlash(byte[] b, int p) {
+		final int n = b.length;
+		for (; p < n; p++) {
+			if (b[p] == '/') {
+				return p;
+			}
+		}
+		return n;
+	}
+
+	private static boolean startsWith(byte[] a, byte[] b, int n) {
+		if (b.length < n) {
+			return false;
+		}
+		for (n--; n >= 0; n--) {
+			if (a[n] != b[n]) {
+				return false;
+			}
+		}
+		return true;
+	}
+
 	/**
 	 * Finish, write, commit this change, and release the index lock.
 	 * <p>
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCache.java b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCache.java
index 6d9a32d..b9101c0 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCache.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCache.java
@@ -63,6 +63,7 @@
 import java.util.List;
 
 import org.eclipse.jgit.errors.CorruptObjectException;
+import org.eclipse.jgit.errors.IndexReadException;
 import org.eclipse.jgit.errors.LockFailedException;
 import org.eclipse.jgit.errors.UnmergedPathException;
 import org.eclipse.jgit.events.IndexChangedEvent;
@@ -70,12 +71,15 @@
 import org.eclipse.jgit.internal.JGitText;
 import org.eclipse.jgit.internal.storage.file.FileSnapshot;
 import org.eclipse.jgit.internal.storage.file.LockFile;
+import org.eclipse.jgit.lib.AnyObjectId;
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.ObjectInserter;
+import org.eclipse.jgit.lib.ObjectReader;
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.treewalk.FileTreeIterator;
 import org.eclipse.jgit.treewalk.TreeWalk;
+import org.eclipse.jgit.treewalk.TreeWalk.OperationType;
 import org.eclipse.jgit.treewalk.filter.PathFilterGroup;
 import org.eclipse.jgit.util.FS;
 import org.eclipse.jgit.util.IO;
@@ -145,6 +149,28 @@
 	}
 
 	/**
+	 * Create a new in memory index read from the contents of a tree.
+	 *
+	 * @param reader
+	 *            reader to access the tree objects from a repository.
+	 * @param treeId
+	 *            tree to read. Must identify a tree, not a tree-ish.
+	 * @return a new cache which has no backing store file, but contains the
+	 *         contents of {@code treeId}.
+	 * @throws IOException
+	 *             one or more trees not available from the ObjectReader.
+	 * @since 4.2
+	 */
+	public static DirCache read(ObjectReader reader, AnyObjectId treeId)
+			throws IOException {
+		DirCache d = newInCore();
+		DirCacheBuilder b = d.builder();
+		b.addTree(null, DirCacheEntry.STAGE_0, reader, treeId);
+		b.finish();
+		return d;
+	}
+
+	/**
 	 * Create a new in-core index representation and read an index from disk.
 	 * <p>
 	 * The new index will be read before it is returned to the caller. Read
@@ -318,9 +344,6 @@
 	/** Our active lock (if we hold it); null if we don't have it locked. */
 	private LockFile myLock;
 
-	/** file system abstraction **/
-	private final FS fs;
-
 	/** Keep track of whether the index has changed or not */
 	private FileSnapshot snapshot;
 
@@ -350,7 +373,6 @@
 	 */
 	public DirCache(final File indexLocation, final FS fs) {
 		liveFile = indexLocation;
-		this.fs = fs;
 		clear();
 	}
 
@@ -417,6 +439,12 @@
 					}
 				}
 			} catch (FileNotFoundException fnfe) {
+				if (liveFile.exists()) {
+					// Panic: the index file exists but we can't read it
+					throw new IndexReadException(
+							MessageFormat.format(JGitText.get().cannotReadIndex,
+									liveFile.getAbsolutePath(), fnfe));
+				}
 				// Someone must have deleted it between our exists test
 				// and actually opening the path. That's fine, its empty.
 				//
@@ -579,7 +607,7 @@
 	public boolean lock() throws IOException {
 		if (liveFile == null)
 			throw new IOException(JGitText.get().dirCacheDoesNotHaveABackingFile);
-		final LockFile tmp = new LockFile(liveFile, fs);
+		final LockFile tmp = new LockFile(liveFile);
 		if (tmp.lock()) {
 			tmp.setNeedStatInformation(true);
 			myLock = tmp;
@@ -768,8 +796,11 @@
 	 *         information. If &lt; 0 the entry does not exist in the index.
 	 * @since 3.4
 	 */
-	public int findEntry(final byte[] p, final int pLen) {
-		int low = 0;
+	public int findEntry(byte[] p, int pLen) {
+		return findEntry(0, p, pLen);
+	}
+
+	int findEntry(int low, byte[] p, int pLen) {
 		int high = entryCnt;
 		while (low < high) {
 			int mid = (low + high) >>> 1;
@@ -869,8 +900,8 @@
 	 */
 	public DirCacheEntry[] getEntriesWithin(String path) {
 		if (path.length() == 0) {
-			final DirCacheEntry[] r = new DirCacheEntry[sortedEntries.length];
-			System.arraycopy(sortedEntries, 0, r, 0, sortedEntries.length);
+			DirCacheEntry[] r = new DirCacheEntry[entryCnt];
+			System.arraycopy(sortedEntries, 0, r, 0, entryCnt);
 			return r;
 		}
 		if (!path.endsWith("/")) //$NON-NLS-1$
@@ -963,6 +994,7 @@
 	private void updateSmudgedEntries() throws IOException {
 		List<String> paths = new ArrayList<String>(128);
 		try (TreeWalk walk = new TreeWalk(repository)) {
+			walk.setOperationType(OperationType.CHECKIN_OP);
 			for (int i = 0; i < entryCnt; i++)
 				if (sortedEntries[i].isSmudged())
 					paths.add(sortedEntries[i].getPathString());
@@ -974,6 +1006,7 @@
 			FileTreeIterator fIter = new FileTreeIterator(repository);
 			walk.addTree(iIter);
 			walk.addTree(fIter);
+			fIter.setDirCacheIterator(walk, 0);
 			walk.setRecursive(true);
 			while (walk.next()) {
 				iIter = walk.getTree(0, DirCacheIterator.class);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheBuildIterator.java b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheBuildIterator.java
index da55306..c10e416 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheBuildIterator.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheBuildIterator.java
@@ -130,4 +130,9 @@
 		if (cur < cnt)
 			builder.keep(cur, cnt - cur);
 	}
+
+	@Override
+	protected boolean needsStopWalk() {
+		return ptr < cache.getEntryCount();
+	}
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheBuilder.java b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheBuilder.java
index 6c7a70c..cfebe2d 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheBuilder.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheBuilder.java
@@ -44,6 +44,9 @@
 
 package org.eclipse.jgit.dircache;
 
+import static org.eclipse.jgit.lib.FileMode.TYPE_MASK;
+import static org.eclipse.jgit.lib.FileMode.TYPE_TREE;
+
 import java.io.IOException;
 import java.text.MessageFormat;
 import java.util.Arrays;
@@ -51,9 +54,7 @@
 import org.eclipse.jgit.internal.JGitText;
 import org.eclipse.jgit.lib.AnyObjectId;
 import org.eclipse.jgit.lib.ObjectReader;
-import org.eclipse.jgit.treewalk.AbstractTreeIterator;
 import org.eclipse.jgit.treewalk.CanonicalTreeParser;
-import org.eclipse.jgit.treewalk.TreeWalk;
 
 /**
  * Updates a {@link DirCache} by adding individual {@link DirCacheEntry}s.
@@ -102,8 +103,9 @@
 	 */
 	public void add(final DirCacheEntry newEntry) {
 		if (newEntry.getRawMode() == 0)
-			throw new IllegalArgumentException(MessageFormat.format(JGitText.get().fileModeNotSetForPath
-					, newEntry.getPathString()));
+			throw new IllegalArgumentException(MessageFormat.format(
+					JGitText.get().fileModeNotSetForPath,
+					newEntry.getPathString()));
 		beforeAdd(newEntry);
 		fastAdd(newEntry);
 	}
@@ -162,27 +164,56 @@
 	 * @throws IOException
 	 *             a tree cannot be read to iterate through its entries.
 	 */
-	public void addTree(final byte[] pathPrefix, final int stage,
-			final ObjectReader reader, final AnyObjectId tree) throws IOException {
-		final TreeWalk tw = new TreeWalk(reader);
-		tw.addTree(new CanonicalTreeParser(pathPrefix, reader, tree
-				.toObjectId()));
-		tw.setRecursive(true);
-		if (tw.next()) {
-			final DirCacheEntry newEntry = toEntry(stage, tw);
-			beforeAdd(newEntry);
-			fastAdd(newEntry);
-			while (tw.next())
-				fastAdd(toEntry(stage, tw));
+	public void addTree(byte[] pathPrefix, int stage, ObjectReader reader,
+			AnyObjectId tree) throws IOException {
+		CanonicalTreeParser p = createTreeParser(pathPrefix, reader, tree);
+		while (!p.eof()) {
+			if (isTree(p)) {
+				p = enterTree(p, reader);
+				continue;
+			}
+
+			DirCacheEntry first = toEntry(stage, p);
+			beforeAdd(first);
+			fastAdd(first);
+			p = p.next();
+			break;
+		}
+
+		// Rest of tree entries are correctly sorted; use fastAdd().
+		while (!p.eof()) {
+			if (isTree(p)) {
+				p = enterTree(p, reader);
+			} else {
+				fastAdd(toEntry(stage, p));
+				p = p.next();
+			}
 		}
 	}
 
-	private DirCacheEntry toEntry(final int stage, final TreeWalk tw) {
-		final DirCacheEntry e = new DirCacheEntry(tw.getRawPath(), stage);
-		final AbstractTreeIterator i;
+	private static CanonicalTreeParser createTreeParser(byte[] pathPrefix,
+			ObjectReader reader, AnyObjectId tree) throws IOException {
+		return new CanonicalTreeParser(pathPrefix, reader, tree);
+	}
 
-		i = tw.getTree(0, AbstractTreeIterator.class);
-		e.setFileMode(tw.getFileMode(0));
+	private static boolean isTree(CanonicalTreeParser p) {
+		return (p.getEntryRawMode() & TYPE_MASK) == TYPE_TREE;
+	}
+
+	private static CanonicalTreeParser enterTree(CanonicalTreeParser p,
+			ObjectReader reader) throws IOException {
+		p = p.createSubtreeIterator(reader);
+		return p.eof() ? p.next() : p;
+	}
+
+	private static DirCacheEntry toEntry(int stage, CanonicalTreeParser i) {
+		byte[] buf = i.getEntryPathBuffer();
+		int len = i.getEntryPathLength();
+		byte[] path = new byte[len];
+		System.arraycopy(buf, 0, path, 0, len);
+
+		DirCacheEntry e = new DirCacheEntry(path, stage);
+		e.setFileMode(i.getEntryRawMode());
 		e.setObjectIdFromRaw(i.idBuffer(), i.idOffset());
 		return e;
 	}
@@ -242,9 +273,9 @@
 		sorted = true;
 	}
 
-	private static IllegalStateException bad(final DirCacheEntry a,
-			final String msg) {
-		return new IllegalStateException(msg + ": " + a.getStage() + " " //$NON-NLS-1$ //$NON-NLS-2$
-				+ a.getPathString());
+	private static IllegalStateException bad(DirCacheEntry a, String msg) {
+		return new IllegalStateException(String.format(
+				"%s: %d %s", //$NON-NLS-1$
+				msg, Integer.valueOf(a.getStage()), a.getPathString()));
 	}
 }
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 0025254..a1e1d15 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java
@@ -46,21 +46,25 @@
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.OutputStream;
+import java.nio.file.StandardCopyOption;
 import java.text.MessageFormat;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
+import org.eclipse.jgit.api.errors.FilterFailedException;
 import org.eclipse.jgit.errors.CheckoutConflictException;
 import org.eclipse.jgit.errors.CorruptObjectException;
 import org.eclipse.jgit.errors.IncorrectObjectTypeException;
 import org.eclipse.jgit.errors.IndexWriteException;
 import org.eclipse.jgit.errors.MissingObjectException;
 import org.eclipse.jgit.internal.JGitText;
+import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.CoreConfig.AutoCRLF;
 import org.eclipse.jgit.lib.CoreConfig.SymLinks;
 import org.eclipse.jgit.lib.FileMode;
+import org.eclipse.jgit.lib.NullProgressMonitor;
 import org.eclipse.jgit.lib.ObjectChecker;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.ObjectLoader;
@@ -76,6 +80,7 @@
 import org.eclipse.jgit.treewalk.WorkingTreeOptions;
 import org.eclipse.jgit.treewalk.filter.PathFilter;
 import org.eclipse.jgit.util.FS;
+import org.eclipse.jgit.util.FS.ExecutionResult;
 import org.eclipse.jgit.util.FileUtils;
 import org.eclipse.jgit.util.RawParseUtils;
 import org.eclipse.jgit.util.SystemReader;
@@ -85,9 +90,10 @@
  * This class handles checking out one or two trees merging with the index.
  */
 public class DirCacheCheckout {
+	private static final int MAX_EXCEPTION_TEXT_SIZE = 10 * 1024;
 	private Repository repo;
 
-	private HashMap<String, ObjectId> updated = new HashMap<String, ObjectId>();
+	private HashMap<String, String> updated = new HashMap<String, String>();
 
 	private ArrayList<String> conflicts = new ArrayList<String>();
 
@@ -112,9 +118,9 @@
 	private boolean emptyDirCache;
 
 	/**
-	 * @return a list of updated paths and objectIds
+	 * @return a list of updated paths and smudgeFilterCommands
 	 */
-	public Map<String, ObjectId> getUpdated() {
+	public Map<String, String> getUpdated() {
 		return updated;
 	}
 
@@ -447,7 +453,8 @@
 			for (String path : updated.keySet()) {
 				DirCacheEntry entry = dc.getEntry(path);
 				if (!FileMode.GITLINK.equals(entry.getRawMode()))
-					checkoutEntry(repo, entry, objectReader);
+					checkoutEntry(repo, entry, objectReader, false,
+							updated.get(path));
 			}
 
 			// commit the index builder - a new index is persisted
@@ -996,9 +1003,12 @@
 		removed.add(path);
 	}
 
-	private void update(String path, ObjectId mId, FileMode mode) {
+	private void update(String path, ObjectId mId, FileMode mode)
+			throws IOException {
 		if (!FileMode.TREE.equals(mode)) {
-			updated.put(path, mId);
+			updated.put(path,
+					walk.getFilterCommand(Constants.ATTR_FILTER_TYPE_SMUDGE));
+
 			DirCacheEntry entry = new DirCacheEntry(path, DirCacheEntry.STAGE_0);
 			entry.setObjectId(mId);
 			entry.setFileMode(mode);
@@ -1127,6 +1137,13 @@
 	 * final filename.
 	 *
 	 * <p>
+	 * <b>Note:</b> if the entry path on local file system exists as a non-empty
+	 * directory, and the target entry type is a link or file, the checkout will
+	 * fail with {@link IOException} since existing non-empty directory cannot
+	 * be renamed to file or link without deleting it recursively.
+	 * </p>
+	 *
+	 * <p>
 	 * TODO: this method works directly on File IO, we may need another
 	 * abstraction (like WorkingTreeIterator). This way we could tell e.g.
 	 * Eclipse that Files in the workspace got changed
@@ -1143,6 +1160,82 @@
 	 */
 	public static void checkoutEntry(Repository repo, DirCacheEntry entry,
 			ObjectReader or) throws IOException {
+		checkoutEntry(repo, entry, or, false, null);
+	}
+
+	/**
+	 * Updates the file in the working tree with content and mode from an entry
+	 * in the index. The new content is first written to a new temporary file in
+	 * the same directory as the real file. Then that new file is renamed to the
+	 * final filename.
+	 *
+	 * <p>
+	 * <b>Note:</b> if the entry path on local file system exists as a file, it
+	 * will be deleted and if it exists as a directory, it will be deleted
+	 * recursively, independently if has any content.
+	 * </p>
+	 *
+	 * <p>
+	 * TODO: this method works directly on File IO, we may need another
+	 * abstraction (like WorkingTreeIterator). This way we could tell e.g.
+	 * Eclipse that Files in the workspace got changed
+	 * </p>
+	 *
+	 * @param repo
+	 *            repository managing the destination work tree.
+	 * @param entry
+	 *            the entry containing new mode and content
+	 * @param or
+	 *            object reader to use for checkout
+	 * @param deleteRecursive
+	 *            true to recursively delete final path if it exists on the file
+	 *            system
+	 *
+	 * @throws IOException
+	 * @since 4.2
+	 */
+	public static void checkoutEntry(Repository repo, DirCacheEntry entry,
+			ObjectReader or, boolean deleteRecursive) throws IOException {
+		checkoutEntry(repo, entry, or, deleteRecursive, null);
+	}
+
+	/**
+	 * Updates the file in the working tree with content and mode from an entry
+	 * in the index. The new content is first written to a new temporary file in
+	 * the same directory as the real file. Then that new file is renamed to the
+	 * final filename.
+	 *
+	 * <p>
+	 * <b>Note:</b> if the entry path on local file system exists as a file, it
+	 * will be deleted and if it exists as a directory, it will be deleted
+	 * recursively, independently if has any content.
+	 * </p>
+	 *
+	 * <p>
+	 * TODO: this method works directly on File IO, we may need another
+	 * abstraction (like WorkingTreeIterator). This way we could tell e.g.
+	 * Eclipse that Files in the workspace got changed
+	 * </p>
+	 *
+	 * @param repo
+	 *            repository managing the destination work tree.
+	 * @param entry
+	 *            the entry containing new mode and content
+	 * @param or
+	 *            object reader to use for checkout
+	 * @param deleteRecursive
+	 *            true to recursively delete final path if it exists on the file
+	 *            system
+	 * @param smudgeFilterCommand
+	 *            the filter command to be run for smudging the entry to be
+	 *            checked out
+	 *
+	 * @throws IOException
+	 * @since 4.2
+	 */
+	public static void checkoutEntry(Repository repo, DirCacheEntry entry,
+			ObjectReader or, boolean deleteRecursive,
+			String smudgeFilterCommand) throws IOException {
 		ObjectLoader ol = or.open(entry.getObjectId());
 		File f = new File(repo.getWorkTree(), entry.getPathString());
 		File parentDir = f.getParentFile();
@@ -1153,6 +1246,9 @@
 				&& opt.getSymLinks() == SymLinks.TRUE) {
 			byte[] bytes = ol.getBytes();
 			String target = RawParseUtils.decode(bytes);
+			if (deleteRecursive && f.isDirectory()) {
+				FileUtils.delete(f, FileUtils.RECURSIVE);
+			}
 			fs.createSymLink(f, target);
 			entry.setLength(bytes.length);
 			entry.setLastModified(fs.lastModified(f));
@@ -1164,14 +1260,52 @@
 		OutputStream channel = new FileOutputStream(tmpFile);
 		if (opt.getAutoCRLF() == AutoCRLF.TRUE)
 			channel = new AutoCRLFOutputStream(channel);
-		try {
-			ol.copyTo(channel);
-		} finally {
-			channel.close();
+		if (smudgeFilterCommand != null) {
+			ProcessBuilder filterProcessBuilder = fs
+					.runInShell(smudgeFilterCommand, new String[0]);
+			filterProcessBuilder.directory(repo.getWorkTree());
+			filterProcessBuilder.environment().put(Constants.GIT_DIR_KEY,
+					repo.getDirectory().getAbsolutePath());
+			ExecutionResult result;
+			int rc;
+			try {
+				// TODO: wire correctly with AUTOCRLF
+				result = fs.execute(filterProcessBuilder, ol.openStream());
+				rc = result.getRc();
+				if (rc == 0) {
+					result.getStdout().writeTo(channel,
+							NullProgressMonitor.INSTANCE);
+				}
+			} catch (IOException | InterruptedException e) {
+				throw new IOException(new FilterFailedException(e,
+						smudgeFilterCommand, entry.getPathString()));
+
+			} finally {
+				channel.close();
+			}
+			if (rc != 0) {
+				throw new IOException(new FilterFailedException(rc,
+						smudgeFilterCommand, entry.getPathString(),
+						result.getStdout().toByteArray(MAX_EXCEPTION_TEXT_SIZE),
+						RawParseUtils.decode(result.getStderr()
+								.toByteArray(MAX_EXCEPTION_TEXT_SIZE))));
+			}
+		} else {
+			try {
+				ol.copyTo(channel);
+			} finally {
+				channel.close();
+			}
 		}
-		entry.setLength(opt.getAutoCRLF() == AutoCRLF.TRUE ? //
-				tmpFile.length() // AutoCRLF wants on-disk-size
-				: (int) ol.getSize());
+		// The entry needs to correspond to the on-disk filesize. If the content
+		// was filtered (either by autocrlf handling or smudge filters) ask the
+		// filesystem again for the length. Otherwise the objectloader knows the
+		// size
+		if (opt.getAutoCRLF() == AutoCRLF.TRUE || smudgeFilterCommand != null) {
+			entry.setLength(tmpFile.length());
+		} else {
+			entry.setLength(ol.getSize());
+		}
 
 		if (opt.isFileMode() && fs.supportsExecute()) {
 			if (FileMode.EXECUTABLE_FILE.equals(entry.getRawMode())) {
@@ -1183,11 +1317,19 @@
 			}
 		}
 		try {
-			FileUtils.rename(tmpFile, f);
+			if (deleteRecursive && f.isDirectory()) {
+				FileUtils.delete(f, FileUtils.RECURSIVE);
+			}
+			FileUtils.rename(tmpFile, f, StandardCopyOption.ATOMIC_MOVE);
 		} catch (IOException e) {
-			throw new IOException(MessageFormat.format(
-					JGitText.get().renameFileFailed, tmpFile.getPath(),
-					f.getPath()));
+			throw new IOException(
+					MessageFormat.format(JGitText.get().renameFileFailed,
+							tmpFile.getPath(), f.getPath()),
+					e);
+		} finally {
+			if (tmpFile.exists()) {
+				FileUtils.delete(tmpFile);
+			}
 		}
 		entry.setLastModified(f.lastModified());
 	}
@@ -1202,24 +1344,6 @@
 			checkValidPathSegment(chk, i);
 	}
 
-	/**
-	 * Check if path is a valid path for a checked out file name or ref name.
-	 *
-	 * @param path
-	 * @throws InvalidPathException
-	 *             if the path is invalid
-	 * @since 3.3
-	 */
-	static void checkValidPath(String path) throws InvalidPathException {
-		try {
-			SystemReader.getInstance().checkPath(path);
-		} catch (CorruptObjectException e) {
-			InvalidPathException p = new InvalidPathException(path);
-			p.initCause(e);
-			throw p;
-		}
-	}
-
 	private static void checkValidPathSegment(ObjectChecker chk,
 			CanonicalTreeParser t) throws InvalidPathException {
 		try {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEditor.java b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEditor.java
index 13885d3..c987c96 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEditor.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEditor.java
@@ -44,6 +44,10 @@
 
 package org.eclipse.jgit.dircache;
 
+import static org.eclipse.jgit.dircache.DirCache.cmp;
+import static org.eclipse.jgit.dircache.DirCacheTree.peq;
+import static org.eclipse.jgit.lib.FileMode.TYPE_TREE;
+
 import java.io.IOException;
 import java.text.MessageFormat;
 import java.util.ArrayList;
@@ -53,6 +57,7 @@
 
 import org.eclipse.jgit.internal.JGitText;
 import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.util.Paths;
 
 /**
  * Updates a {@link DirCache} by supplying discrete edit commands.
@@ -72,11 +77,12 @@
 		public int compare(final PathEdit o1, final PathEdit o2) {
 			final byte[] a = o1.path;
 			final byte[] b = o2.path;
-			return DirCache.cmp(a, a.length, b, b.length);
+			return cmp(a, a.length, b, b.length);
 		}
 	};
 
 	private final List<PathEdit> edits;
+	private int editIdx;
 
 	/**
 	 * Construct a new editor.
@@ -126,35 +132,44 @@
 
 	private void applyEdits() {
 		Collections.sort(edits, EDIT_CMP);
+		editIdx = 0;
 
 		final int maxIdx = cache.getEntryCount();
 		int lastIdx = 0;
-		for (final PathEdit e : edits) {
-			int eIdx = cache.findEntry(e.path, e.path.length);
+		while (editIdx < edits.size()) {
+			PathEdit e = edits.get(editIdx++);
+			int eIdx = cache.findEntry(lastIdx, e.path, e.path.length);
 			final boolean missing = eIdx < 0;
 			if (eIdx < 0)
 				eIdx = -(eIdx + 1);
 			final int cnt = Math.min(eIdx, maxIdx) - lastIdx;
 			if (cnt > 0)
 				fastKeep(lastIdx, cnt);
-			lastIdx = missing ? eIdx : cache.nextEntry(eIdx);
 
-			if (e instanceof DeletePath)
+			if (e instanceof DeletePath) {
+				lastIdx = missing ? eIdx : cache.nextEntry(eIdx);
 				continue;
+			}
 			if (e instanceof DeleteTree) {
 				lastIdx = cache.nextEntry(e.path, e.path.length, eIdx);
 				continue;
 			}
 
 			if (missing) {
-				final DirCacheEntry ent = new DirCacheEntry(e.path);
+				DirCacheEntry ent = new DirCacheEntry(e.path);
 				e.apply(ent);
-				if (ent.getRawMode() == 0)
-					throw new IllegalArgumentException(MessageFormat.format(JGitText.get().fileModeNotSetForPath
-							, ent.getPathString()));
+				if (ent.getRawMode() == 0) {
+					throw new IllegalArgumentException(MessageFormat.format(
+							JGitText.get().fileModeNotSetForPath,
+							ent.getPathString()));
+				}
+				lastIdx = e.replace
+					? deleteOverlappingSubtree(ent, eIdx)
+					: eIdx;
 				fastAdd(ent);
 			} else {
 				// Apply to all entries of the current path (different stages)
+				lastIdx = cache.nextEntry(eIdx);
 				for (int i = eIdx; i < lastIdx; i++) {
 					final DirCacheEntry ent = cache.getEntry(i);
 					e.apply(ent);
@@ -168,6 +183,102 @@
 			fastKeep(lastIdx, cnt);
 	}
 
+	private int deleteOverlappingSubtree(DirCacheEntry ent, int eIdx) {
+		byte[] entPath = ent.path;
+		int entLen = entPath.length;
+
+		// Delete any file that was previously processed and overlaps
+		// the parent directory for the new entry. Since the editor
+		// always processes entries in path order, binary search back
+		// for the overlap for each parent directory.
+		for (int p = pdir(entPath, entLen); p > 0; p = pdir(entPath, p)) {
+			int i = findEntry(entPath, p);
+			if (i >= 0) {
+				// A file does overlap, delete the file from the array.
+				// No other parents can have overlaps as the file should
+				// have taken care of that itself.
+				int n = --entryCnt - i;
+				System.arraycopy(entries, i + 1, entries, i, n);
+				break;
+			}
+
+			// If at least one other entry already exists in this parent
+			// directory there is no need to continue searching up the tree.
+			i = -(i + 1);
+			if (i < entryCnt && inDir(entries[i], entPath, p)) {
+				break;
+			}
+		}
+
+		int maxEnt = cache.getEntryCount();
+		if (eIdx >= maxEnt) {
+			return maxEnt;
+		}
+
+		DirCacheEntry next = cache.getEntry(eIdx);
+		if (Paths.compare(next.path, 0, next.path.length, 0,
+				entPath, 0, entLen, TYPE_TREE) < 0) {
+			// Next DirCacheEntry sorts before new entry as tree. Defer a
+			// DeleteTree command to delete any entries if they exist. This
+			// case only happens for A, A.c, A/c type of conflicts (rare).
+			insertEdit(new DeleteTree(entPath));
+			return eIdx;
+		}
+
+		// Next entry may be contained by the entry-as-tree, skip if so.
+		while (eIdx < maxEnt && inDir(cache.getEntry(eIdx), entPath, entLen)) {
+			eIdx++;
+		}
+		return eIdx;
+	}
+
+	private int findEntry(byte[] p, int pLen) {
+		int low = 0;
+		int high = entryCnt;
+		while (low < high) {
+			int mid = (low + high) >>> 1;
+			int cmp = cmp(p, pLen, entries[mid]);
+			if (cmp < 0) {
+				high = mid;
+			} else if (cmp == 0) {
+				while (mid > 0 && cmp(p, pLen, entries[mid - 1]) == 0) {
+					mid--;
+				}
+				return mid;
+			} else {
+				low = mid + 1;
+			}
+		}
+		return -(low + 1);
+	}
+
+	private void insertEdit(DeleteTree d) {
+		for (int i = editIdx; i < edits.size(); i++) {
+			int cmp = EDIT_CMP.compare(d, edits.get(i));
+			if (cmp < 0) {
+				edits.add(i, d);
+				return;
+			} else if (cmp == 0) {
+				return;
+			}
+		}
+		edits.add(d);
+	}
+
+	private static boolean inDir(DirCacheEntry e, byte[] path, int pLen) {
+		return e.path.length > pLen && e.path[pLen] == '/'
+				&& peq(path, e.path, pLen);
+	}
+
+	private static int pdir(byte[] path, int e) {
+		for (e--; e > 0; e--) {
+			if (path[e] == '/') {
+				return e;
+			}
+		}
+		return 0;
+	}
+
 	/**
 	 * Any index record update.
 	 * <p>
@@ -179,6 +290,7 @@
 	 */
 	public abstract static class PathEdit {
 		final byte[] path;
+		boolean replace = true;
 
 		/**
 		 * Create a new update command by path name.
@@ -190,6 +302,10 @@
 			path = Constants.encode(entryPath);
 		}
 
+		PathEdit(byte[] path) {
+			this.path = path;
+		}
+
 		/**
 		 * Create a new update command for an existing entry instance.
 		 *
@@ -202,6 +318,22 @@
 		}
 
 		/**
+		 * Configure if a file can replace a directory (or vice versa).
+		 * <p>
+		 * Default is {@code true} as this is usually the desired behavior.
+		 *
+		 * @param ok
+		 *            if true a file can replace a directory, or a directory can
+		 *            replace a file.
+		 * @return {@code this}
+		 * @since 4.2
+		 */
+		public PathEdit setReplace(boolean ok) {
+			replace = ok;
+			return this;
+		}
+
+		/**
 		 * Apply the update to a single cache entry matching the path.
 		 * <p>
 		 * After apply is invoked the entry is added to the output table, and
@@ -212,6 +344,12 @@
 		 *            the path is a new path in the index.
 		 */
 		public abstract void apply(DirCacheEntry ent);
+
+		@Override
+		public String toString() {
+			String p = DirCacheEntry.toString(path);
+			return getClass().getSimpleName() + '[' + p + ']';
+		}
 	}
 
 	/**
@@ -272,10 +410,26 @@
 		 *            only the subtree's contents are matched by the command.
 		 *            The special case "" (not "/"!) deletes all entries.
 		 */
-		public DeleteTree(final String entryPath) {
-			super(
-					(entryPath.endsWith("/") || entryPath.length() == 0) ? entryPath //$NON-NLS-1$
-							: entryPath + "/"); //$NON-NLS-1$
+		public DeleteTree(String entryPath) {
+			super(entryPath.isEmpty()
+					|| entryPath.charAt(entryPath.length() - 1) == '/'
+					? entryPath
+					: entryPath + '/');
+		}
+
+		DeleteTree(byte[] path) {
+			super(appendSlash(path));
+		}
+
+		private static byte[] appendSlash(byte[] path) {
+			int n = path.length;
+			if (n > 0 && path[n - 1] != '/') {
+				byte[] r = new byte[n + 1];
+				System.arraycopy(path, 0, r, 0, n);
+				r[n] = '/';
+				return r;
+			}
+			return path;
 		}
 
 		public void apply(final DirCacheEntry ent) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEntry.java b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEntry.java
index eef2e6d..4ebf2e0 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEntry.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEntry.java
@@ -65,6 +65,7 @@
 import org.eclipse.jgit.util.IO;
 import org.eclipse.jgit.util.MutableInteger;
 import org.eclipse.jgit.util.NB;
+import org.eclipse.jgit.util.SystemReader;
 
 /**
  * A single file (or stage of a file) in a {@link DirCache}.
@@ -191,7 +192,7 @@
 		}
 
 		try {
-			DirCacheCheckout.checkValidPath(toString(path));
+			checkPath(path);
 		} catch (InvalidPathException e) {
 			CorruptObjectException p =
 				new CorruptObjectException(e.getMessage());
@@ -263,7 +264,7 @@
 	/**
 	 * Create an empty entry at the specified stage.
 	 *
-	 * @param newPath
+	 * @param path
 	 *            name of the cache entry, in the standard encoding.
 	 * @param stage
 	 *            the stage index of the new entry.
@@ -274,16 +275,16 @@
 	 *             range 0..3, inclusive.
 	 */
 	@SuppressWarnings("boxing")
-	public DirCacheEntry(final byte[] newPath, final int stage) {
-		DirCacheCheckout.checkValidPath(toString(newPath));
+	public DirCacheEntry(byte[] path, final int stage) {
+		checkPath(path);
 		if (stage < 0 || 3 < stage)
 			throw new IllegalArgumentException(MessageFormat.format(
 					JGitText.get().invalidStageForPath,
-					stage, toString(newPath)));
+					stage, toString(path)));
 
 		info = new byte[INFO_LEN];
 		infoOffset = 0;
-		path = newPath;
+		this.path = path;
 
 		int flags = ((stage & 0x3) << 12);
 		if (path.length < NAME_MASK)
@@ -293,6 +294,23 @@
 		NB.encodeInt16(info, infoOffset + P_FLAGS, flags);
 	}
 
+	/**
+	 * Duplicate DirCacheEntry with same path and copied info.
+	 * <p>
+	 * The same path buffer is reused (avoiding copying), however a new info
+	 * buffer is created and its contents are copied.
+	 *
+	 * @param src
+	 *            entry to clone.
+	 * @since 4.2
+	 */
+	public DirCacheEntry(DirCacheEntry src) {
+		path = src.path;
+		info = new byte[INFO_LEN];
+		infoOffset = 0;
+		System.arraycopy(src.info, src.infoOffset, info, 0, INFO_LEN);
+	}
+
 	void write(final OutputStream os) throws IOException {
 		final int len = isExtended() ? INFO_LEN_EXTENDED : INFO_LEN;
 		final int pathLen = path.length;
@@ -498,12 +516,16 @@
 		switch (mode.getBits() & FileMode.TYPE_MASK) {
 		case FileMode.TYPE_MISSING:
 		case FileMode.TYPE_TREE:
-			throw new IllegalArgumentException(MessageFormat.format(JGitText.get().invalidModeForPath
-					, mode, getPathString()));
+			throw new IllegalArgumentException(MessageFormat.format(
+					JGitText.get().invalidModeForPath, mode, getPathString()));
 		}
 		NB.encodeInt32(info, infoOffset + P_MODE, mode.getBits());
 	}
 
+	void setFileMode(int mode) {
+		NB.encodeInt32(info, infoOffset + P_MODE, mode);
+	}
+
 	/**
 	 * Get the cached creation time of this file, in milliseconds.
 	 *
@@ -730,7 +752,17 @@
 			return 0;
 	}
 
-	private static String toString(final byte[] path) {
+	private static void checkPath(byte[] path) {
+		try {
+			SystemReader.getInstance().checkPath(path);
+		} catch (CorruptObjectException e) {
+			InvalidPathException p = new InvalidPathException(toString(path));
+			p.initCause(e);
+			throw p;
+		}
+	}
+
+	static String toString(final byte[] path) {
 		return Constants.CHARSET.decode(ByteBuffer.wrap(path)).toString();
 	}
 
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheIterator.java b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheIterator.java
index 354a074..ad93f72 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheIterator.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheIterator.java
@@ -103,9 +103,6 @@
 	/** The subtree containing {@link #currentEntry} if this is first entry. */
 	protected DirCacheTree currentSubtree;
 
-	/** Holds an {@link AttributesNode} for the current entry */
-	private AttributesNode attributesNode;
-
 	/**
 	 * Create a new iterator for an already loaded DirCache instance.
 	 * <p>
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheTree.java b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheTree.java
index f139afc..0cbb83d 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheTree.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheTree.java
@@ -103,7 +103,7 @@
 	private DirCacheTree parent;
 
 	/** Name of this tree within its parent. */
-	private byte[] encodedName;
+	byte[] encodedName;
 
 	/** Number of {@link DirCacheEntry} records that belong to this tree. */
 	private int entrySpan;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/errors/CorruptObjectException.java b/org.eclipse.jgit/src/org/eclipse/jgit/errors/CorruptObjectException.java
index c6ea093..e4db40b 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/errors/CorruptObjectException.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/errors/CorruptObjectException.java
@@ -49,8 +49,10 @@
 import java.io.IOException;
 import java.text.MessageFormat;
 
+import org.eclipse.jgit.annotations.Nullable;
 import org.eclipse.jgit.internal.JGitText;
 import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.lib.ObjectChecker;
 import org.eclipse.jgit.lib.ObjectId;
 
 /**
@@ -59,15 +61,24 @@
 public class CorruptObjectException extends IOException {
 	private static final long serialVersionUID = 1L;
 
+	private ObjectChecker.ErrorType errorType;
+
 	/**
-	 * Construct a CorruptObjectException for reporting a problem specified
-	 * object id
+	 * Report a specific error condition discovered in an object.
 	 *
+	 * @param type
+	 *            type of error
 	 * @param id
+	 *            identity of the bad object
 	 * @param why
+	 *            description of the error.
+	 * @since 4.2
 	 */
-	public CorruptObjectException(final AnyObjectId id, final String why) {
-		this(id.toObjectId(), why);
+	public CorruptObjectException(ObjectChecker.ErrorType type, AnyObjectId id,
+			String why) {
+		super(MessageFormat.format(JGitText.get().objectIsCorrupt3,
+				type.getMessageId(), id.name(), why));
+		this.errorType = type;
 	}
 
 	/**
@@ -77,7 +88,18 @@
 	 * @param id
 	 * @param why
 	 */
-	public CorruptObjectException(final ObjectId id, final String why) {
+	public CorruptObjectException(AnyObjectId id, String why) {
+		super(MessageFormat.format(JGitText.get().objectIsCorrupt, id.name(), why));
+	}
+
+	/**
+	 * Construct a CorruptObjectException for reporting a problem specified
+	 * object id
+	 *
+	 * @param id
+	 * @param why
+	 */
+	public CorruptObjectException(ObjectId id, String why) {
 		super(MessageFormat.format(JGitText.get().objectIsCorrupt, id.name(), why));
 	}
 
@@ -87,7 +109,7 @@
 	 *
 	 * @param why
 	 */
-	public CorruptObjectException(final String why) {
+	public CorruptObjectException(String why) {
 		super(why);
 	}
 
@@ -105,4 +127,15 @@
 		super(why);
 		initCause(cause);
 	}
+
+	/**
+	 * Specific error condition identified by {@link ObjectChecker}.
+	 *
+	 * @return error condition or null.
+	 * @since 4.2
+	 */
+	@Nullable
+	public ObjectChecker.ErrorType getErrorType() {
+		return errorType;
+	}
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/SymlinkTreeEntry.java b/org.eclipse.jgit/src/org/eclipse/jgit/errors/DirCacheNameConflictException.java
similarity index 61%
copy from org.eclipse.jgit/src/org/eclipse/jgit/lib/SymlinkTreeEntry.java
copy to org.eclipse.jgit/src/org/eclipse/jgit/errors/DirCacheNameConflictException.java
index c7e41bc..5f67e34 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/SymlinkTreeEntry.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/errors/DirCacheNameConflictException.java
@@ -1,6 +1,5 @@
 /*
- * Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com>
- * Copyright (C) 2006-2007, Shawn O. Pearce <spearce@spearce.org>
+ * Copyright (C) 2015, Google Inc.
  * and other copyright owners as documented in the project's IP log.
  *
  * This program and the accompanying materials are made available
@@ -42,44 +41,40 @@
  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-package org.eclipse.jgit.lib;
+package org.eclipse.jgit.errors;
 
 /**
- * A tree entry representing a symbolic link.
+ * Thrown by DirCache code when entries overlap in impossible way.
  *
- * Note. Java cannot really handle these as file system objects.
- *
- * @deprecated To look up information about a single path, use
- * {@link org.eclipse.jgit.treewalk.TreeWalk#forPath(Repository, String, org.eclipse.jgit.revwalk.RevTree)}.
- * To lookup information about multiple paths at once, use a
- * {@link org.eclipse.jgit.treewalk.TreeWalk} and obtain the current entry's
- * information from its getter methods.
+ * @since 4.2
  */
-@Deprecated
-public class SymlinkTreeEntry extends TreeEntry {
+public class DirCacheNameConflictException extends IllegalStateException {
+	private static final long serialVersionUID = 1L;
+
+	private final String path1;
+	private final String path2;
 
 	/**
-	 * Construct a {@link SymlinkTreeEntry} with the specified name and SHA-1 in
-	 * the specified parent
+	 * Construct an exception for a specific path.
 	 *
-	 * @param parent
-	 * @param id
-	 * @param nameUTF8
+	 * @param path1
+	 *            one path that conflicts.
+	 * @param path2
+	 *            another path that conflicts.
 	 */
-	public SymlinkTreeEntry(final Tree parent, final ObjectId id,
-			final byte[] nameUTF8) {
-		super(parent, id, nameUTF8);
+	public DirCacheNameConflictException(String path1, String path2) {
+		super(path1 + ' ' + path2);
+		this.path1 = path1;
+		this.path2 = path2;
 	}
 
-	public FileMode getMode() {
-		return FileMode.SYMLINK;
+	/** @return one of the paths that has a conflict. */
+	public String getPath1() {
+		return path1;
 	}
 
-	public String toString() {
-		final StringBuilder r = new StringBuilder();
-		r.append(ObjectId.toString(getId()));
-		r.append(" S "); //$NON-NLS-1$
-		r.append(getFullName());
-		return r.toString();
+	/** @return another path that has a conflict. */
+	public String getPath2() {
+		return path2;
 	}
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/errors/IndexReadException.java b/org.eclipse.jgit/src/org/eclipse/jgit/errors/IndexReadException.java
new file mode 100644
index 0000000..70f650d
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/errors/IndexReadException.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2015, 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.errors;
+
+import java.io.IOException;
+
+import org.eclipse.jgit.internal.JGitText;
+
+/**
+ * Cannot read the index. This is a serious error that users need to be made
+ * aware of.
+ *
+ * @since 4.2
+ */
+public class IndexReadException extends IOException {
+	private static final long serialVersionUID = 1L;
+
+	/**
+	 * Constructs an IndexReadException with the default message.
+	 */
+	public IndexReadException() {
+		super(JGitText.get().indexWriteException);
+	}
+
+	/**
+	 * Constructs an IndexReadException with the specified detail message.
+	 *
+	 * @param s
+	 *            message
+	 */
+	public IndexReadException(final String s) {
+		super(s);
+	}
+
+	/**
+	 * Constructs an IndexReadException with the specified detail message.
+	 *
+	 * @param s
+	 *            message
+	 * @param cause
+	 *            root cause exception
+	 */
+	public IndexReadException(final String s, final Throwable cause) {
+		super(s);
+		initCause(cause);
+	}
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/ManifestParser.java b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/ManifestParser.java
index 891479d..7eb9550 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/ManifestParser.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/ManifestParser.java
@@ -338,6 +338,20 @@
 			else
 				last = p;
 		}
+		removeNestedCopyfiles();
+	}
+
+	/** Remove copyfiles that sit in a subdirectory of any other project. */
+	void removeNestedCopyfiles() {
+		for (RepoProject proj : filteredProjects) {
+			List<CopyFile> copyfiles = new ArrayList<>(proj.getCopyFiles());
+			proj.clearCopyFiles();
+			for (CopyFile copyfile : copyfiles) {
+				if (!isNestedCopyfile(copyfile)) {
+					proj.addCopyFile(copyfile);
+				}
+			}
+		}
 	}
 
 	boolean inGroups(RepoProject proj) {
@@ -357,4 +371,22 @@
 		}
 		return false;
 	}
+
+	private boolean isNestedCopyfile(CopyFile copyfile) {
+		if (copyfile.dest.indexOf('/') == -1) {
+			// If the copyfile is at root level then it won't be nested.
+			return false;
+		}
+		for (RepoProject proj : filteredProjects) {
+			if (proj.getPath().compareTo(copyfile.dest) > 0) {
+				// Early return as remaining projects can't be ancestor of this
+				// copyfile config (filteredProjects is sorted).
+				return false;
+			}
+			if (proj.isAncestorOf(copyfile.dest)) {
+				return true;
+			}
+		}
+		return false;
+	}
 }
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 790f4db..ff9f233 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoCommand.java
@@ -42,6 +42,9 @@
  */
 package org.eclipse.jgit.gitrepo;
 
+import static org.eclipse.jgit.lib.Constants.DEFAULT_REMOTE_NAME;
+import static org.eclipse.jgit.lib.Constants.R_REMOTES;
+
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.IOException;
@@ -106,6 +109,7 @@
 	private String groups;
 	private String branch;
 	private String targetBranch = Constants.HEAD;
+	private boolean recordRemoteBranch = false;
 	private PersonIdent author;
 	private RemoteReader callback;
 	private InputStream inputStream;
@@ -314,6 +318,30 @@
 	}
 
 	/**
+	 * Set whether the branch name should be recorded in .gitmodules
+	 * <p>
+	 * Submodule entries in .gitmodules can include a "branch" field
+	 * to indicate what remote branch each submodule tracks.
+	 * <p>
+	 * That field is used by "git submodule update --remote" to update
+	 * to the tip of the tracked branch when asked and by Gerrit to
+	 * update the superproject when a change on that branch is merged.
+	 * <p>
+	 * Subprojects that request a specific commit or tag will not have
+	 * a branch name recorded.
+	 * <p>
+	 * Not implemented for non-bare repositories.
+	 *
+	 * @param enable Whether to record the branch name
+	 * @return this command
+	 * @since 4.2
+	 */
+	public RepoCommand setRecordRemoteBranch(boolean enable) {
+		this.recordRemoteBranch = enable;
+		return this;
+	}
+
+	/**
 	 * The progress monitor associated with the clone operation. By default,
 	 * this is set to <code>NullProgressMonitor</code>
 	 *
@@ -429,10 +457,14 @@
 					// create gitlink
 					DirCacheEntry dcEntry = new DirCacheEntry(name);
 					ObjectId objectId;
-					if (ObjectId.isId(proj.getRevision()))
+					if (ObjectId.isId(proj.getRevision())) {
 						objectId = ObjectId.fromString(proj.getRevision());
-					else {
+					} else {
 						objectId = callback.sha1(nameUri, proj.getRevision());
+						if (recordRemoteBranch)
+							// can be branch or tag
+							cfg.setString("submodule", name, "branch", //$NON-NLS-1$ //$NON-NLS-2$
+									proj.getRevision());
 					}
 					if (objectId == null)
 						throw new RemoteUnavailableException(nameUri);
@@ -545,7 +577,7 @@
 	private static String findRef(String ref, Repository repo)
 			throws IOException {
 		if (!ObjectId.isId(ref)) {
-			Ref r = repo.getRef(Constants.DEFAULT_REMOTE_NAME + "/" + ref); //$NON-NLS-1$
+			Ref r = repo.exactRef(R_REMOTES + DEFAULT_REMOTE_NAME + "/" + ref); //$NON-NLS-1$
 			if (r != null)
 				return r.getName();
 		}
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 9a07211..f6d1209 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoProject.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoProject.java
@@ -252,10 +252,19 @@
 	/**
 	 * Add a bunch of copyfile configurations.
 	 *
-	 * @param copyfiles
+	 * @param copyFiles
 	 */
-	public void addCopyFiles(Collection<CopyFile> copyfiles) {
-		this.copyfiles.addAll(copyfiles);
+	public void addCopyFiles(Collection<CopyFile> copyFiles) {
+		this.copyfiles.addAll(copyFiles);
+	}
+
+	/**
+	 * Clear all the copyfiles.
+	 *
+	 * @since 4.2
+	 */
+	public void clearCopyFiles() {
+		this.copyfiles.clear();
 	}
 
 	private String getPathWithSlash() {
@@ -273,7 +282,19 @@
 	 * @return true if this sub repo is the ancestor of given sub repo.
 	 */
 	public boolean isAncestorOf(RepoProject that) {
-		return that.getPathWithSlash().startsWith(this.getPathWithSlash());
+		return isAncestorOf(that.getPathWithSlash());
+	}
+
+	/**
+	 * Check if this sub repo is an ancestor of the given path.
+	 *
+	 * @param thatPath
+	 *            path to be checked to see if it is within this repository
+	 * @return true if this sub repo is an ancestor of the given path.
+	 * @since 4.2
+	 */
+	public boolean isAncestorOf(String thatPath) {
+		return thatPath.startsWith(getPathWithSlash());
 	}
 
 	@Override
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 1494576..6f7a21a 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/hooks/Hooks.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/hooks/Hooks.java
@@ -74,4 +74,15 @@
 			PrintStream outputStream) {
 		return new CommitMsgHook(repo, outputStream);
 	}
+
+	/**
+	 * @param repo
+	 * @param outputStream
+	 *            The output stream, or {@code null} to use {@code System.out}
+	 * @return The pre-push hook for the given repository.
+	 * @since 4.2
+	 */
+	public static PrePushHook prePush(Repository repo, PrintStream outputStream) {
+		return new PrePushHook(repo, outputStream);
+	}
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/hooks/PrePushHook.java b/org.eclipse.jgit/src/org/eclipse/jgit/hooks/PrePushHook.java
new file mode 100644
index 0000000..a501fee
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/hooks/PrePushHook.java
@@ -0,0 +1,159 @@
+/*
+ * 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 java.util.Collection;
+
+import org.eclipse.jgit.api.errors.AbortedByHookException;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.transport.RemoteRefUpdate;
+
+/**
+ * The <code>pre-push</code> hook implementation. The pre-push hook runs during
+ * git push, after the remote refs have been updated but before any objects have
+ * been transferred.
+ *
+ * @since 4.2
+ */
+public class PrePushHook extends GitHook<String> {
+
+	/**
+	 * Constant indicating the name of the pre-push hook.
+	 */
+	public static final String NAME = "pre-push"; //$NON-NLS-1$
+
+	private String remoteName;
+
+	private String remoteLocation;
+
+	private String refs;
+
+	/**
+	 * @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 PrePushHook(Repository repo, PrintStream outputStream) {
+		super(repo, outputStream);
+	}
+
+	@Override
+	protected String getStdinArgs() {
+		return refs;
+	}
+
+	@Override
+	public String call() throws IOException, AbortedByHookException {
+		if (canRun()) {
+			doRun();
+		}
+		return ""; //$NON-NLS-1$
+	}
+
+	/**
+	 * @return {@code true}
+	 */
+	private boolean canRun() {
+		return true;
+	}
+
+	@Override
+	public String getHookName() {
+		return NAME;
+	}
+
+	/**
+	 * This hook receives two parameters, which is the name and the location of
+	 * the remote repository.
+	 */
+	@Override
+	protected String[] getParameters() {
+		if (remoteName == null) {
+			remoteName = remoteLocation;
+		}
+		return new String[] { remoteName, remoteLocation };
+	}
+
+	/**
+	 * @param name
+	 */
+	public void setRemoteName(String name) {
+		remoteName = name;
+	}
+
+	/**
+	 * @param location
+	 */
+	public void setRemoteLocation(String location) {
+		remoteLocation = location;
+	}
+
+	/**
+	 * @param toRefs
+	 */
+	public void setRefs(Collection<RemoteRefUpdate> toRefs) {
+		StringBuilder b = new StringBuilder();
+		boolean first = true;
+		for (RemoteRefUpdate u : toRefs) {
+			if (!first)
+				b.append("\n"); //$NON-NLS-1$
+			else
+				first = false;
+			b.append(u.getSrcRef());
+			b.append(" "); //$NON-NLS-1$
+			b.append(u.getNewObjectId().getName());
+			b.append(" "); //$NON-NLS-1$
+			b.append(u.getRemoteName());
+			b.append(" "); //$NON-NLS-1$
+			ObjectId ooid = u.getExpectedOldObjectId();
+			b.append((ooid == null) ? ObjectId.zeroId().getName() : ooid
+					.getName());
+		}
+		refs = b.toString();
+	}
+}
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 f972828..e354c71 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
@@ -141,9 +141,7 @@
 	private static boolean isComplexWildcard(String pattern) {
 		int idx1 = pattern.indexOf('[');
 		if (idx1 != -1) {
-			int idx2 = pattern.indexOf(']', idx1);
-			if (idx2 > idx1)
-				return true;
+			return true;
 		}
 		if (pattern.indexOf('?') != -1) {
 			return true;
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 9067e82..7740a2b 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java
@@ -79,6 +79,7 @@
 	/***/ public String atLeastOnePathIsRequired;
 	/***/ public String atLeastOnePatternIsRequired;
 	/***/ public String atLeastTwoFiltersNeeded;
+	/***/ public String atomicPushNotSupported;
 	/***/ public String authenticationNotSupported;
 	/***/ public String badBase64InputCharacterAt;
 	/***/ public String badEntryDelimiter;
@@ -104,6 +105,7 @@
 	/***/ public String cannotBeRecursiveWhenTreesAreIncluded;
 	/***/ public String cannotChangeActionOnComment;
 	/***/ public String cannotChangeToComment;
+	/***/ public String cannotCheckoutFromUnbornBranch;
 	/***/ public String cannotCheckoutOursSwitchBranch;
 	/***/ public String cannotCombineSquashWithNoff;
 	/***/ public String cannotCombineTreeFilterWithRevFilter;
@@ -146,6 +148,7 @@
 	/***/ public String cannotReadCommit;
 	/***/ public String cannotReadFile;
 	/***/ public String cannotReadHEAD;
+	/***/ public String cannotReadIndex;
 	/***/ public String cannotReadObject;
 	/***/ public String cannotReadObjectsPath;
 	/***/ public String cannotReadTree;
@@ -155,6 +158,7 @@
 	/***/ public String cannotStoreObjects;
 	/***/ public String cannotResolveUniquelyAbbrevObjectId;
 	/***/ public String cannotUnloadAModifiedTree;
+	/***/ public String cannotUpdateUnbornBranch;
 	/***/ public String cannotWorkWithOtherStagesThanZeroRightNow;
 	/***/ public String cannotWriteObjectsPath;
 	/***/ public String canOnlyCherryPickCommitsWithOneParent;
@@ -181,14 +185,15 @@
 	/***/ public String connectionTimeOut;
 	/***/ public String contextMustBeNonNegative;
 	/***/ public String corruptionDetectedReReadingAt;
+	/***/ public String corruptObjectBadDate;
+	/***/ public String corruptObjectBadEmail;
 	/***/ public String corruptObjectBadStream;
 	/***/ public String corruptObjectBadStreamCorruptHeader;
+	/***/ public String corruptObjectBadTimezone;
 	/***/ public String corruptObjectDuplicateEntryNames;
 	/***/ public String corruptObjectGarbageAfterSize;
 	/***/ public String corruptObjectIncorrectLength;
 	/***/ public String corruptObjectIncorrectSorting;
-	/***/ public String corruptObjectInvalidAuthor;
-	/***/ public String corruptObjectInvalidCommitter;
 	/***/ public String corruptObjectInvalidEntryMode;
 	/***/ public String corruptObjectInvalidMode;
 	/***/ public String corruptObjectInvalidModeChar;
@@ -207,11 +212,11 @@
 	/***/ public String corruptObjectInvalidNamePrn;
 	/***/ public String corruptObjectInvalidObject;
 	/***/ public String corruptObjectInvalidParent;
-	/***/ public String corruptObjectInvalidTagger;
 	/***/ public String corruptObjectInvalidTree;
 	/***/ public String corruptObjectInvalidType;
 	/***/ public String corruptObjectInvalidType2;
 	/***/ public String corruptObjectMalformedHeader;
+	/***/ public String corruptObjectMissingEmail;
 	/***/ public String corruptObjectNameContainsByte;
 	/***/ public String corruptObjectNameContainsChar;
 	/***/ public String corruptObjectNameContainsNullByte;
@@ -237,6 +242,7 @@
 	/***/ public String corruptObjectTruncatedInMode;
 	/***/ public String corruptObjectTruncatedInName;
 	/***/ public String corruptObjectTruncatedInObjectId;
+	/***/ public String corruptObjectZeroId;
 	/***/ public String corruptPack;
 	/***/ public String couldNotCheckOutBecauseOfConflicts;
 	/***/ public String couldNotDeleteLockFileShouldNotHappen;
@@ -287,6 +293,7 @@
 	/***/ public String emptyPathNotPermitted;
 	/***/ public String emptyRef;
 	/***/ public String encryptionError;
+	/***/ public String encryptionOnlyPBE;
 	/***/ public String endOfFileInEscape;
 	/***/ public String entryNotFoundByPath;
 	/***/ public String enumValueNotSupported2;
@@ -337,6 +344,8 @@
 	/***/ public String fileIsTooBigForThisConvenienceMethod;
 	/***/ public String fileIsTooLarge;
 	/***/ public String fileModeNotSetForPath;
+	/***/ public String filterExecutionFailed;
+	/***/ public String filterExecutionFailedRc;
 	/***/ public String findingGarbage;
 	/***/ public String flagIsDisposed;
 	/***/ public String flagNotFromThis;
@@ -409,6 +418,7 @@
 	/***/ public String invalidURL;
 	/***/ public String invalidWildcards;
 	/***/ public String invalidRefSpec;
+	/***/ public String invalidRepositoryStateNoHead;
 	/***/ public String invalidWindowSize;
 	/***/ public String isAStaticFlagAndHasNorevWalkInstance;
 	/***/ public String JRELacksMD5Implementation;
@@ -484,6 +494,7 @@
 	/***/ public String objectAtHasBadZlibStream;
 	/***/ public String objectAtPathDoesNotHaveId;
 	/***/ public String objectIsCorrupt;
+	/***/ public String objectIsCorrupt3;
 	/***/ public String objectIsNotA;
 	/***/ public String objectNotFound;
 	/***/ public String objectNotFoundIn;
@@ -508,6 +519,7 @@
 	/***/ public String packfileIsTruncatedNoParam;
 	/***/ public String packHandleIsStale;
 	/***/ public String packHasUnresolvedDeltas;
+	/***/ public String packInaccessible;
 	/***/ public String packingCancelledDuringObjectsWriting;
 	/***/ public String packObjectCountMismatch;
 	/***/ public String packRefs;
@@ -655,6 +667,7 @@
 	/***/ public String transportProtoSFTP;
 	/***/ public String transportProtoSSH;
 	/***/ public String transportProtoTest;
+	/***/ public String transportProvidedRefWithNoObjectId;
 	/***/ public String transportSSHRetryInterrupt;
 	/***/ public String treeEntryAlreadyExists;
 	/***/ public String treeFilterMarkerTooManyFilters;
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 faf27e3..784507d 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,18 +44,17 @@
 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_TXN;
 import static org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource.UNREACHABLE_GARBAGE;
 import static org.eclipse.jgit.internal.storage.pack.PackExt.BITMAP_INDEX;
 import static org.eclipse.jgit.internal.storage.pack.PackExt.INDEX;
 import static org.eclipse.jgit.internal.storage.pack.PackExt.PACK;
-import static org.eclipse.jgit.lib.RefDatabase.ALL;
 
 import java.io.IOException;
 import java.util.ArrayList;
-import java.util.Collections;
+import java.util.Collection;
 import java.util.HashSet;
 import java.util.List;
-import java.util.Map;
 import java.util.Set;
 
 import org.eclipse.jgit.internal.JGitText;
@@ -63,13 +62,15 @@
 import org.eclipse.jgit.internal.storage.file.PackIndex;
 import org.eclipse.jgit.internal.storage.pack.PackExt;
 import org.eclipse.jgit.internal.storage.pack.PackWriter;
+import org.eclipse.jgit.internal.storage.reftree.RefTreeNames;
 import org.eclipse.jgit.lib.AnyObjectId;
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.NullProgressMonitor;
 import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.ObjectIdOwnerMap;
+import org.eclipse.jgit.lib.ObjectIdSet;
 import org.eclipse.jgit.lib.ProgressMonitor;
 import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.RefDatabase;
 import org.eclipse.jgit.revwalk.RevWalk;
 import org.eclipse.jgit.storage.pack.PackConfig;
 import org.eclipse.jgit.storage.pack.PackStatistics;
@@ -78,16 +79,14 @@
 /** Repack and garbage collect a repository. */
 public class DfsGarbageCollector {
 	private final DfsRepository repo;
-
-	private final DfsRefDatabase refdb;
-
+	private final RefDatabase refdb;
 	private final DfsObjDatabase objdb;
 
 	private final List<DfsPackDescription> newPackDesc;
 
 	private final List<PackStatistics> newPackStats;
 
-	private final List<PackWriter.ObjectIdSet> newPackObj;
+	private final List<ObjectIdSet> newPackObj;
 
 	private DfsReader ctx;
 
@@ -95,14 +94,11 @@
 
 	private long coalesceGarbageLimit = 50 << 20;
 
-	private Map<String, Ref> refsBefore;
-
 	private List<DfsPackFile> packsBefore;
 
 	private Set<ObjectId> allHeads;
-
 	private Set<ObjectId> nonHeads;
-
+	private Set<ObjectId> txnHeads;
 	private Set<ObjectId> tagTargets;
 
 	/**
@@ -117,7 +113,7 @@
 		objdb = repo.getObjectDatabase();
 		newPackDesc = new ArrayList<DfsPackDescription>(4);
 		newPackStats = new ArrayList<PackStatistics>(4);
-		newPackObj = new ArrayList<PackWriter.ObjectIdSet>(4);
+		newPackObj = new ArrayList<ObjectIdSet>(4);
 
 		packConfig = new PackConfig(repo);
 		packConfig.setIndexVersion(2);
@@ -195,22 +191,25 @@
 
 		ctx = (DfsReader) objdb.newReader();
 		try {
-			refdb.clearCache();
+			refdb.refresh();
 			objdb.clearCache();
 
-			refsBefore = refdb.getRefs(ALL);
+			Collection<Ref> refsBefore = RefTreeNames.allRefs(refdb);
 			packsBefore = packsToRebuild();
 			if (packsBefore.isEmpty())
 				return true;
 
 			allHeads = new HashSet<ObjectId>();
 			nonHeads = new HashSet<ObjectId>();
+			txnHeads = new HashSet<ObjectId>();
 			tagTargets = new HashSet<ObjectId>();
-			for (Ref ref : refsBefore.values()) {
+			for (Ref ref : refsBefore) {
 				if (ref.isSymbolic() || ref.getObjectId() == null)
 					continue;
 				if (isHead(ref))
 					allHeads.add(ref.getObjectId());
+				else if (RefTreeNames.isRefTree(refdb, ref.getName()))
+					txnHeads.add(ref.getObjectId());
 				else
 					nonHeads.add(ref.getObjectId());
 				if (ref.getPeeledObjectId() != null)
@@ -222,6 +221,7 @@
 			try {
 				packHeads(pm);
 				packRest(pm);
+				packRefTreeGraph(pm);
 				packGarbage(pm);
 				objdb.commitPack(newPackDesc, toPrune());
 				rollback = false;
@@ -277,18 +277,17 @@
 
 		try (PackWriter pw = newPackWriter()) {
 			pw.setTagTargets(tagTargets);
-			pw.preparePack(pm, allHeads, Collections.<ObjectId> emptySet());
+			pw.preparePack(pm, allHeads, PackWriter.NONE);
 			if (0 < pw.getObjectCount())
 				writePack(GC, pw, pm);
 		}
 	}
-
 	private void packRest(ProgressMonitor pm) throws IOException {
 		if (nonHeads.isEmpty())
 			return;
 
 		try (PackWriter pw = newPackWriter()) {
-			for (PackWriter.ObjectIdSet packedObjs : newPackObj)
+			for (ObjectIdSet packedObjs : newPackObj)
 				pw.excludeObjects(packedObjs);
 			pw.preparePack(pm, nonHeads, allHeads);
 			if (0 < pw.getObjectCount())
@@ -296,6 +295,19 @@
 		}
 	}
 
+	private void packRefTreeGraph(ProgressMonitor pm) throws IOException {
+		if (txnHeads.isEmpty())
+			return;
+
+		try (PackWriter pw = newPackWriter()) {
+			for (ObjectIdSet packedObjs : newPackObj)
+				pw.excludeObjects(packedObjs);
+			pw.preparePack(pm, txnHeads, PackWriter.NONE);
+			if (0 < pw.getObjectCount())
+				writePack(GC_TXN, pw, pm);
+		}
+	}
+
 	private void packGarbage(ProgressMonitor pm) throws IOException {
 		// TODO(sop) This is ugly. The garbage pack needs to be deleted.
 		PackConfig cfg = new PackConfig(packConfig);
@@ -328,7 +340,7 @@
 	}
 
 	private boolean anyPackHas(AnyObjectId id) {
-		for (PackWriter.ObjectIdSet packedObjs : newPackObj)
+		for (ObjectIdSet packedObjs : newPackObj)
 			if (packedObjs.contains(id))
 				return true;
 		return false;
@@ -389,17 +401,10 @@
 			}
 		}
 
-		final ObjectIdOwnerMap<ObjectIdOwnerMap.Entry> packedObjs = pw
-				.getObjectSet();
-		newPackObj.add(new PackWriter.ObjectIdSet() {
-			public boolean contains(AnyObjectId objectId) {
-				return packedObjs.contains(objectId);
-			}
-		});
-
 		PackStatistics stats = pw.getStatistics();
 		pack.setPackStats(stats);
 		newPackStats.add(stats);
+		newPackObj.add(pw.getObjectSet());
 
 		DfsBlockCache.getInstance().getOrCreate(pack, null);
 		return pack;
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 488eee9..e5ae980 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
@@ -95,16 +95,16 @@
 	/** Always produce version 2 indexes, to get CRC data. */
 	private static final int INDEX_VERSION = 2;
 
-	private final DfsObjDatabase db;
-	private int compression = Deflater.BEST_COMPRESSION;
+	final DfsObjDatabase db;
+	int compression = Deflater.BEST_COMPRESSION;
 
-	private List<PackedObjectInfo> objectList;
-	private ObjectIdOwnerMap<PackedObjectInfo> objectMap;
+	List<PackedObjectInfo> objectList;
+	ObjectIdOwnerMap<PackedObjectInfo> objectMap;
 
-	private DfsBlockCache cache;
-	private DfsPackKey packKey;
-	private DfsPackDescription packDsc;
-	private PackStream packOut;
+	DfsBlockCache cache;
+	DfsPackKey packKey;
+	DfsPackDescription packDsc;
+	PackStream packOut;
 	private boolean rollback;
 
 	/**
@@ -137,7 +137,8 @@
 		ObjectId id = idFor(type, data, off, len);
 		if (objectMap != null && objectMap.contains(id))
 			return id;
-		if (db.has(id))
+		// Ignore unreachable (garbage) objects here.
+		if (db.has(id, true))
 			return id;
 
 		long offset = beginObject(type, len);
@@ -322,7 +323,7 @@
 	private class PackStream extends OutputStream {
 		private final DfsOutputStream out;
 		private final MessageDigest md;
-		private final byte[] hdrBuf;
+		final byte[] hdrBuf;
 		private final Deflater deflater;
 		private final int blockSize;
 
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 b92f784..3641560 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
@@ -54,6 +54,7 @@
 import java.util.concurrent.atomic.AtomicReference;
 
 import org.eclipse.jgit.internal.storage.pack.PackExt;
+import org.eclipse.jgit.lib.AnyObjectId;
 import org.eclipse.jgit.lib.ObjectDatabase;
 import org.eclipse.jgit.lib.ObjectInserter;
 import org.eclipse.jgit.lib.ObjectReader;
@@ -90,6 +91,13 @@
 		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
@@ -181,6 +189,28 @@
 	}
 
 	/**
+	 * Does the requested object exist in this database?
+	 * <p>
+	 * This differs from ObjectDatabase's implementation in that we can selectively
+	 * ignore unreachable (garbage) objects.
+	 *
+	 * @param objectId
+	 *            identity of the object to test for existence of.
+	 * @param avoidUnreachableObjects
+	 *            if true, ignore objects that are unreachable.
+	 * @return true if the specified object is stored in this database.
+	 * @throws IOException
+	 *             the object store cannot be accessed.
+	 */
+	public boolean has(AnyObjectId objectId, boolean avoidUnreachableObjects)
+			throws IOException {
+		try (ObjectReader or = newReader()) {
+			or.setAvoidUnreachableObjects(avoidUnreachableObjects);
+			return or.has(objectId);
+		}
+	}
+
+	/**
 	 * Generate a new unique name for a pack file.
 	 *
 	 * @param source
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackCompactor.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackCompactor.java
index 7073763..11aef7f 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackCompactor.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackCompactor.java
@@ -62,6 +62,7 @@
 import org.eclipse.jgit.lib.AnyObjectId;
 import org.eclipse.jgit.lib.NullProgressMonitor;
 import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectIdSet;
 import org.eclipse.jgit.lib.ProgressMonitor;
 import org.eclipse.jgit.revwalk.RevFlag;
 import org.eclipse.jgit.revwalk.RevObject;
@@ -91,7 +92,7 @@
 
 	private final List<DfsPackFile> srcPacks;
 
-	private final List<PackWriter.ObjectIdSet> exclude;
+	private final List<ObjectIdSet> exclude;
 
 	private final List<DfsPackDescription> newPacks;
 
@@ -113,7 +114,7 @@
 		repo = repository;
 		autoAddSize = 5 * 1024 * 1024; // 5 MiB
 		srcPacks = new ArrayList<DfsPackFile>();
-		exclude = new ArrayList<PackWriter.ObjectIdSet>(4);
+		exclude = new ArrayList<ObjectIdSet>(4);
 		newPacks = new ArrayList<DfsPackDescription>(1);
 		newStats = new ArrayList<PackStatistics>(1);
 	}
@@ -164,7 +165,7 @@
 	 *            objects to not include.
 	 * @return {@code this}.
 	 */
-	public DfsPackCompactor exclude(PackWriter.ObjectIdSet set) {
+	public DfsPackCompactor exclude(ObjectIdSet set) {
 		exclude.add(set);
 		return this;
 	}
@@ -183,11 +184,7 @@
 		try (DfsReader ctx = (DfsReader) repo.newObjectReader()) {
 			idx = pack.getPackIndex(ctx);
 		}
-		return exclude(new PackWriter.ObjectIdSet() {
-			public boolean contains(AnyObjectId id) {
-				return idx.hasObject(id);
-			}
-		});
+		return exclude(idx);
 	}
 
 	/**
@@ -343,7 +340,7 @@
 			RevObject obj = rw.lookupOrNull(id);
 			if (obj != null && (obj.has(added) || obj.has(isBase)))
 				continue;
-			for (PackWriter.ObjectIdSet e : exclude)
+			for (ObjectIdSet e : exclude)
 				if (e.contains(id))
 					continue SCAN;
 			want.add(new ObjectIdWithOffset(id, ent.getOffset()));
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 a1035a1..e5469f6 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
@@ -262,6 +262,11 @@
 	}
 
 	@Override
+	public void refresh() {
+		clearCache();
+	}
+
+	@Override
 	public void close() {
 		clearCache();
 	}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsRepository.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsRepository.java
index 122f6d3..ef88450 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsRepository.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsRepository.java
@@ -44,8 +44,13 @@
 package org.eclipse.jgit.internal.storage.dfs;
 
 import java.io.IOException;
+import java.io.InputStream;
 import java.text.MessageFormat;
+import java.util.Collections;
 
+import org.eclipse.jgit.attributes.AttributesNode;
+import org.eclipse.jgit.attributes.AttributesNodeProvider;
+import org.eclipse.jgit.attributes.AttributesRule;
 import org.eclipse.jgit.internal.JGitText;
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.RefUpdate;
@@ -74,9 +79,6 @@
 	@Override
 	public abstract DfsObjDatabase getObjectDatabase();
 
-	@Override
-	public abstract DfsRefDatabase getRefDatabase();
-
 	/** @return a description of this repository. */
 	public DfsRepositoryDescription getDescription() {
 		return description;
@@ -90,7 +92,10 @@
 	 *             the repository cannot be checked.
 	 */
 	public boolean exists() throws IOException {
-		return getRefDatabase().exists();
+		if (getRefDatabase() instanceof DfsRefDatabase) {
+			return ((DfsRefDatabase) getRefDatabase()).exists();
+		}
+		return true;
 	}
 
 	@Override
@@ -112,7 +117,7 @@
 
 	@Override
 	public void scanForRepoChanges() throws IOException {
-		getRefDatabase().clearCache();
+		getRefDatabase().refresh();
 		getObjectDatabase().clearCache();
 	}
 
@@ -126,4 +131,36 @@
 	public ReflogReader getReflogReader(String refName) throws IOException {
 		throw new UnsupportedOperationException();
 	}
+
+	@Override
+	public AttributesNodeProvider createAttributesNodeProvider() {
+		// TODO Check if the implementation used in FileRepository can be used
+		// for this kind of repository
+		return new EmptyAttributesNodeProvider();
+	}
+
+	private static class EmptyAttributesNodeProvider implements
+			AttributesNodeProvider {
+		private EmptyAttributesNode emptyAttributesNode = new EmptyAttributesNode();
+
+		public AttributesNode getInfoAttributesNode() throws IOException {
+			return emptyAttributesNode;
+		}
+
+		public AttributesNode getGlobalAttributesNode() throws IOException {
+			return emptyAttributesNode;
+		}
+
+		private static class EmptyAttributesNode extends AttributesNode {
+
+			public EmptyAttributesNode() {
+				super(Collections.<AttributesRule> emptyList());
+			}
+
+			@Override
+			public void parse(InputStream in) throws IOException {
+				// Do nothing
+			}
+		}
+	}
 }
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 8e7af0d..b312835 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
@@ -13,14 +13,22 @@
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
 import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.locks.ReadWriteLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
 
 import org.eclipse.jgit.internal.storage.pack.PackExt;
+import org.eclipse.jgit.lib.BatchRefUpdate;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.ObjectIdRef;
+import org.eclipse.jgit.lib.ProgressMonitor;
 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;
+import org.eclipse.jgit.transport.ReceiveCommand;
 import org.eclipse.jgit.util.RefList;
 
 /**
@@ -43,11 +51,11 @@
 		}
 	}
 
-	private static final AtomicInteger packId = new AtomicInteger();
+	static final AtomicInteger packId = new AtomicInteger();
 
 	private final DfsObjDatabase objdb;
-
-	private final DfsRefDatabase refdb;
+	private final RefDatabase refdb;
+	private boolean performsAtomicTransactions = true;
 
 	/**
 	 * Initialize a new in-memory repository.
@@ -60,7 +68,7 @@
 		this(new Builder().setRepositoryDescription(repoDesc));
 	}
 
-	private InMemoryRepository(Builder builder) {
+	InMemoryRepository(Builder builder) {
 		super(builder);
 		objdb = new MemObjDatabase(this);
 		refdb = new MemRefDatabase();
@@ -72,10 +80,21 @@
 	}
 
 	@Override
-	public DfsRefDatabase getRefDatabase() {
+	public RefDatabase getRefDatabase() {
 		return refdb;
 	}
 
+	/**
+	 * Enable (or disable) the atomic reference transaction support.
+	 * <p>
+	 * Useful for testing atomic support enabled or disabled.
+	 *
+	 * @param atomic
+	 */
+	public void setPerformsAtomicTransactions(boolean atomic) {
+		performsAtomicTransactions = atomic;
+	}
+
 	private class MemObjDatabase extends DfsObjDatabase {
 		private List<DfsPackDescription> packs = new ArrayList<DfsPackDescription>();
 
@@ -139,7 +158,7 @@
 	}
 
 	private static class MemPack extends DfsPackDescription {
-		private final Map<PackExt, byte[]>
+		final Map<PackExt, byte[]>
 				fileMap = new HashMap<PackExt, byte[]>();
 
 		MemPack(String name, DfsRepositoryDescription repoDesc) {
@@ -235,41 +254,142 @@
 
 	private class MemRefDatabase extends DfsRefDatabase {
 		private final ConcurrentMap<String, Ref> refs = new ConcurrentHashMap<String, Ref>();
+		private final ReadWriteLock lock = new ReentrantReadWriteLock(true /* fair */);
 
 		MemRefDatabase() {
 			super(InMemoryRepository.this);
 		}
 
 		@Override
+		public boolean performsAtomicTransactions() {
+			return performsAtomicTransactions;
+		}
+
+		@Override
+		public BatchRefUpdate newBatchUpdate() {
+			return new BatchRefUpdate(this) {
+				@Override
+				public void execute(RevWalk walk, ProgressMonitor monitor)
+						throws IOException {
+					if (performsAtomicTransactions()) {
+						try {
+							lock.writeLock().lock();
+							batch(getCommands());
+						} finally {
+							lock.writeLock().unlock();
+						}
+					} else {
+						super.execute(walk, monitor);
+					}
+				}
+			};
+		}
+
+		@Override
 		protected RefCache scanAllRefs() throws IOException {
 			RefList.Builder<Ref> ids = new RefList.Builder<Ref>();
 			RefList.Builder<Ref> sym = new RefList.Builder<Ref>();
-			for (Ref ref : refs.values()) {
-				if (ref.isSymbolic())
-					sym.add(ref);
-				ids.add(ref);
+			try {
+				lock.readLock().lock();
+				for (Ref ref : refs.values()) {
+					if (ref.isSymbolic())
+						sym.add(ref);
+					ids.add(ref);
+				}
+			} finally {
+				lock.readLock().unlock();
 			}
 			ids.sort();
 			sym.sort();
 			return new RefCache(ids.toRefList(), sym.toRefList());
 		}
 
+		private void batch(List<ReceiveCommand> cmds) {
+			// Validate that the target exists in a new RevWalk, as the RevWalk
+			// from the RefUpdate might be reading back unflushed objects.
+			Map<ObjectId, ObjectId> peeled = new HashMap<>();
+			try (RevWalk rw = new RevWalk(getRepository())) {
+				for (ReceiveCommand c : cmds) {
+					if (c.getResult() != ReceiveCommand.Result.NOT_ATTEMPTED) {
+						ReceiveCommand.abort(cmds);
+						return;
+					}
+
+					if (!ObjectId.zeroId().equals(c.getNewId())) {
+						try {
+							RevObject o = rw.parseAny(c.getNewId());
+							if (o instanceof RevTag) {
+								peeled.put(o, rw.peel(o).copy());
+							}
+						} catch (IOException e) {
+							c.setResult(ReceiveCommand.Result.REJECTED_MISSING_OBJECT);
+							ReceiveCommand.abort(cmds);
+							return;
+						}
+					}
+				}
+			}
+
+			// Check all references conform to expected old value.
+			for (ReceiveCommand c : cmds) {
+				Ref r = refs.get(c.getRefName());
+				if (r == null) {
+					if (c.getType() != ReceiveCommand.Type.CREATE) {
+						c.setResult(ReceiveCommand.Result.LOCK_FAILURE);
+						ReceiveCommand.abort(cmds);
+						return;
+					}
+				} else {
+					ObjectId objectId = r.getObjectId();
+					if (r.isSymbolic() || objectId == null
+							|| !objectId.equals(c.getOldId())) {
+						c.setResult(ReceiveCommand.Result.LOCK_FAILURE);
+						ReceiveCommand.abort(cmds);
+						return;
+					}
+				}
+			}
+
+			// Write references.
+			for (ReceiveCommand c : cmds) {
+				if (c.getType() == ReceiveCommand.Type.DELETE) {
+					refs.remove(c.getRefName());
+					c.setResult(ReceiveCommand.Result.OK);
+					continue;
+				}
+
+				ObjectId p = peeled.get(c.getNewId());
+				Ref r;
+				if (p != null) {
+					r = new ObjectIdRef.PeeledTag(Storage.PACKED,
+							c.getRefName(), c.getNewId(), p);
+				} else {
+					r = new ObjectIdRef.PeeledNonTag(Storage.PACKED,
+							c.getRefName(), c.getNewId());
+				}
+				refs.put(r.getName(), r);
+				c.setResult(ReceiveCommand.Result.OK);
+			}
+			clearCache();
+		}
+
 		@Override
 		protected boolean compareAndPut(Ref oldRef, Ref newRef)
 				throws IOException {
-			ObjectId id = newRef.getObjectId();
-			if (id != null) {
-				try (RevWalk rw = new RevWalk(getRepository())) {
-					// Validate that the target exists in a new RevWalk, as the RevWalk
-					// from the RefUpdate might be reading back unflushed objects.
-					rw.parseAny(id);
+			try {
+				lock.writeLock().lock();
+				ObjectId id = newRef.getObjectId();
+				if (id != null) {
+					try (RevWalk rw = new RevWalk(getRepository())) {
+						// Validate that the target exists in a new RevWalk, as the RevWalk
+						// from the RefUpdate might be reading back unflushed objects.
+						rw.parseAny(id);
+					}
 				}
-			}
-			String name = newRef.getName();
-			if (oldRef == null)
-				return refs.putIfAbsent(name, newRef) == null;
+				String name = newRef.getName();
+				if (oldRef == null)
+					return refs.putIfAbsent(name, newRef) == null;
 
-			synchronized (refs) {
 				Ref cur = refs.get(name);
 				Ref toCompare = cur;
 				if (toCompare != null) {
@@ -294,22 +414,29 @@
 					if (eq(toCompare, oldRef))
 						return refs.replace(name, cur, newRef);
 				}
+
+				if (oldRef.getStorage() == Storage.NEW)
+					return refs.putIfAbsent(name, newRef) == null;
+
+				return false;
+			} finally {
+				lock.writeLock().unlock();
 			}
-
-			if (oldRef.getStorage() == Storage.NEW)
-				return refs.putIfAbsent(name, newRef) == null;
-
-			return false;
 		}
 
 		@Override
 		protected boolean compareAndRemove(Ref oldRef) throws IOException {
-			String name = oldRef.getName();
-			Ref cur = refs.get(name);
-			if (cur != null && eq(cur, oldRef))
-				return refs.remove(name, cur);
-			else
-				return false;
+			try {
+				lock.writeLock().lock();
+				String name = oldRef.getName();
+				Ref cur = refs.get(name);
+				if (cur != null && eq(cur, oldRef))
+					return refs.remove(name, cur);
+				else
+					return false;
+			} finally {
+				lock.writeLock().unlock();
+			}
 		}
 
 		private boolean eq(Ref a, Ref b) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/BitmapIndexImpl.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/BitmapIndexImpl.java
index 0c3c736..b27bcc4 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/BitmapIndexImpl.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/BitmapIndexImpl.java
@@ -63,11 +63,11 @@
 public class BitmapIndexImpl implements BitmapIndex {
 	private static final int EXTRA_BITS = 10 * 1024;
 
-	private final PackBitmapIndex packIndex;
+	final PackBitmapIndex packIndex;
 
-	private final MutableBitmapIndex mutableIndex;
+	final MutableBitmapIndex mutableIndex;
 
-	private final int indexObjectCount;
+	final int indexObjectCount;
 
 	/**
 	 * Creates a BitmapIndex that is back by Compressed bitmaps.
@@ -85,18 +85,20 @@
 		return packIndex;
 	}
 
+	@Override
 	public CompressedBitmap getBitmap(AnyObjectId objectId) {
 		EWAHCompressedBitmap compressed = packIndex.getBitmap(objectId);
 		if (compressed == null)
 			return null;
-		return new CompressedBitmap(compressed);
+		return new CompressedBitmap(compressed, this);
 	}
 
+	@Override
 	public CompressedBitmapBuilder newBitmapBuilder() {
-		return new CompressedBitmapBuilder();
+		return new CompressedBitmapBuilder(this);
 	}
 
-	private int findPosition(AnyObjectId objectId) {
+	int findPosition(AnyObjectId objectId) {
 		int position = packIndex.findPosition(objectId);
 		if (position < 0) {
 			position = mutableIndex.findPosition(objectId);
@@ -106,10 +108,10 @@
 		return position;
 	}
 
-	private int addObject(AnyObjectId objectId, int type) {
+	int findOrInsert(AnyObjectId objectId, int type) {
 		int position = findPosition(objectId);
 		if (position < 0) {
-			position = mutableIndex.addObject(objectId, type);
+			position = mutableIndex.findOrInsert(objectId, type);
 			position += indexObjectCount;
 		}
 		return position;
@@ -122,11 +124,11 @@
 
 		private BitSet toRemove;
 
-		private ComboBitset() {
+		ComboBitset() {
 			this(new EWAHCompressedBitmap());
 		}
 
-		private ComboBitset(EWAHCompressedBitmap bitmap) {
+		ComboBitset(EWAHCompressedBitmap bitmap) {
 			this.inflatingBitmap = new InflatingBitSet(bitmap);
 		}
 
@@ -197,15 +199,22 @@
 		}
 	}
 
-	private final class CompressedBitmapBuilder implements BitmapBuilder {
-		private ComboBitset bitset = new ComboBitset();
+	private static final class CompressedBitmapBuilder implements BitmapBuilder {
+		private ComboBitset bitset;
+		private final BitmapIndexImpl bitmapIndex;
 
+		CompressedBitmapBuilder(BitmapIndexImpl bitmapIndex) {
+			this.bitset = new ComboBitset();
+			this.bitmapIndex = bitmapIndex;
+		}
+
+		@Override
 		public boolean add(AnyObjectId objectId, int type) {
-			int position = addObject(objectId, type);
+			int position = bitmapIndex.findOrInsert(objectId, type);
 			if (bitset.contains(position))
 				return false;
 
-			Bitmap entry = getBitmap(objectId);
+			Bitmap entry = bitmapIndex.getBitmap(objectId);
 			if (entry != null) {
 				or(entry);
 				return false;
@@ -215,120 +224,142 @@
 			return true;
 		}
 
+		@Override
 		public boolean contains(AnyObjectId objectId) {
-			int position = findPosition(objectId);
+			int position = bitmapIndex.findPosition(objectId);
 			return 0 <= position && bitset.contains(position);
 		}
 
+		@Override
+		public BitmapBuilder addObject(AnyObjectId objectId, int type) {
+			bitset.set(bitmapIndex.findOrInsert(objectId, type));
+			return this;
+		}
+
+		@Override
 		public void remove(AnyObjectId objectId) {
-			int position = findPosition(objectId);
+			int position = bitmapIndex.findPosition(objectId);
 			if (0 <= position)
 				bitset.remove(position);
 		}
 
+		@Override
 		public CompressedBitmapBuilder or(Bitmap other) {
-			if (isSameCompressedBitmap(other)) {
-				bitset.or(((CompressedBitmap) other).bitmap);
-			} else if (isSameCompressedBitmapBuilder(other)) {
-				CompressedBitmapBuilder b = (CompressedBitmapBuilder) other;
-				bitset.or(b.bitset.combine());
-			} else {
-				throw new IllegalArgumentException();
-			}
+			bitset.or(ewahBitmap(other));
 			return this;
 		}
 
+		@Override
 		public CompressedBitmapBuilder andNot(Bitmap other) {
-			if (isSameCompressedBitmap(other)) {
-				bitset.andNot(((CompressedBitmap) other).bitmap);
-			} else if (isSameCompressedBitmapBuilder(other)) {
-				CompressedBitmapBuilder b = (CompressedBitmapBuilder) other;
-				bitset.andNot(b.bitset.combine());
-			} else {
-				throw new IllegalArgumentException();
-			}
+			bitset.andNot(ewahBitmap(other));
 			return this;
 		}
 
+		@Override
 		public CompressedBitmapBuilder xor(Bitmap other) {
-			if (isSameCompressedBitmap(other)) {
-				bitset.xor(((CompressedBitmap) other).bitmap);
-			} else if (isSameCompressedBitmapBuilder(other)) {
-				CompressedBitmapBuilder b = (CompressedBitmapBuilder) other;
-				bitset.xor(b.bitset.combine());
-			} else {
-				throw new IllegalArgumentException();
-			}
+			bitset.xor(ewahBitmap(other));
 			return this;
 		}
 
 		/** @return the fully built immutable bitmap */
+		@Override
 		public CompressedBitmap build() {
-			return new CompressedBitmap(bitset.combine());
+			return new CompressedBitmap(bitset.combine(), bitmapIndex);
 		}
 
+		@Override
 		public Iterator<BitmapObject> iterator() {
 			return build().iterator();
 		}
 
+		@Override
 		public int cardinality() {
 			return bitset.combine().cardinality();
 		}
 
+		@Override
 		public boolean removeAllOrNone(PackBitmapIndex index) {
-			if (!packIndex.equals(index))
+			if (!bitmapIndex.packIndex.equals(index))
 				return false;
 
 			EWAHCompressedBitmap curr = bitset.combine()
-					.xor(ones(indexObjectCount));
+					.xor(ones(bitmapIndex.indexObjectCount));
 
 			IntIterator ii = curr.intIterator();
-			if (ii.hasNext() && ii.next() < indexObjectCount)
+			if (ii.hasNext() && ii.next() < bitmapIndex.indexObjectCount)
 				return false;
 			bitset = new ComboBitset(curr);
 			return true;
 		}
 
-		private BitmapIndexImpl getBitmapIndex() {
-			return BitmapIndexImpl.this;
+		@Override
+		public BitmapIndexImpl getBitmapIndex() {
+			return bitmapIndex;
+		}
+
+		private EWAHCompressedBitmap ewahBitmap(Bitmap other) {
+			if (other instanceof CompressedBitmap) {
+				CompressedBitmap b = (CompressedBitmap) other;
+				if (b.bitmapIndex != bitmapIndex) {
+					throw new IllegalArgumentException();
+				}
+				return b.bitmap;
+			}
+			if (other instanceof CompressedBitmapBuilder) {
+				CompressedBitmapBuilder b = (CompressedBitmapBuilder) other;
+				if (b.bitmapIndex != bitmapIndex) {
+					throw new IllegalArgumentException();
+				}
+				return b.bitset.combine();
+			}
+			throw new IllegalArgumentException();
 		}
 	}
 
-	final class CompressedBitmap implements Bitmap {
-		private final EWAHCompressedBitmap bitmap;
+	/**
+	 * Wrapper for a {@link EWAHCompressedBitmap} and {@link PackBitmapIndex}.
+	 * <p>
+	 * For a EWAHCompressedBitmap {@code bitmap} representing a vector of
+	 * bits, {@code new CompressedBitmap(bitmap, bitmapIndex)} represents the
+	 * objects at those positions in {@code bitmapIndex.packIndex}.
+	 */
+	public static final class CompressedBitmap implements Bitmap {
+		final EWAHCompressedBitmap bitmap;
+		final BitmapIndexImpl bitmapIndex;
 
-		private CompressedBitmap(EWAHCompressedBitmap bitmap) {
+		/**
+		 * Construct compressed bitmap for given bitmap and bitmap index
+		 *
+		 * @param bitmap
+		 * @param bitmapIndex
+		 */
+		public CompressedBitmap(EWAHCompressedBitmap bitmap, BitmapIndexImpl bitmapIndex) {
 			this.bitmap = bitmap;
+			this.bitmapIndex = bitmapIndex;
 		}
 
+		@Override
 		public CompressedBitmap or(Bitmap other) {
-			return new CompressedBitmap(bitmap.or(bitmapOf(other)));
+			return new CompressedBitmap(bitmap.or(ewahBitmap(other)), bitmapIndex);
 		}
 
+		@Override
 		public CompressedBitmap andNot(Bitmap other) {
-			return new CompressedBitmap(bitmap.andNot(bitmapOf(other)));
+			return new CompressedBitmap(bitmap.andNot(ewahBitmap(other)), bitmapIndex);
 		}
 
+		@Override
 		public CompressedBitmap xor(Bitmap other) {
-			return new CompressedBitmap(bitmap.xor(bitmapOf(other)));
-		}
-
-		private EWAHCompressedBitmap bitmapOf(Bitmap other) {
-			if (isSameCompressedBitmap(other))
-				return ((CompressedBitmap) other).bitmap;
-			if (isSameCompressedBitmapBuilder(other))
-				return ((CompressedBitmapBuilder) other).build().bitmap;
-			CompressedBitmapBuilder builder = newBitmapBuilder();
-			builder.or(other);
-			return builder.build().bitmap;
+			return new CompressedBitmap(bitmap.xor(ewahBitmap(other)), bitmapIndex);
 		}
 
 		private final IntIterator ofObjectType(int type) {
-			return packIndex.ofObjectType(bitmap, type).intIterator();
+			return bitmapIndex.packIndex.ofObjectType(bitmap, type).intIterator();
 		}
 
+		@Override
 		public Iterator<BitmapObject> iterator() {
-			final IntIterator dynamic = bitmap.andNot(ones(indexObjectCount))
+			final IntIterator dynamic = bitmap.andNot(ones(bitmapIndex.indexObjectCount))
 					.intIterator();
 			final IntIterator commits = ofObjectType(Constants.OBJ_COMMIT);
 			final IntIterator trees = ofObjectType(Constants.OBJ_TREE);
@@ -365,12 +396,12 @@
 						throw new NoSuchElementException();
 
 					int position = cached.next();
-					if (position < indexObjectCount) {
+					if (position < bitmapIndex.indexObjectCount) {
 						out.type = type;
-						out.objectId = packIndex.getObject(position);
+						out.objectId = bitmapIndex.packIndex.getObject(position);
 					} else {
-						position -= indexObjectCount;
-						MutableEntry entry = mutableIndex.getObject(position);
+						position -= bitmapIndex.indexObjectCount;
+						MutableEntry entry = bitmapIndex.mutableIndex.getObject(position);
 						out.type = entry.type;
 						out.objectId = entry;
 					}
@@ -387,8 +418,22 @@
 			return bitmap;
 		}
 
-		private BitmapIndexImpl getPackBitmapIndex() {
-			return BitmapIndexImpl.this;
+		private EWAHCompressedBitmap ewahBitmap(Bitmap other) {
+			if (other instanceof CompressedBitmap) {
+				CompressedBitmap b = (CompressedBitmap) other;
+				if (b.bitmapIndex != bitmapIndex) {
+					throw new IllegalArgumentException();
+				}
+				return b.bitmap;
+			}
+			if (other instanceof CompressedBitmapBuilder) {
+				CompressedBitmapBuilder b = (CompressedBitmapBuilder) other;
+				if (b.bitmapIndex != bitmapIndex) {
+					throw new IllegalArgumentException();
+				}
+				return b.bitset.combine();
+			}
+			throw new IllegalArgumentException();
 		}
 	}
 
@@ -419,7 +464,7 @@
 			}
 		}
 
-		int addObject(AnyObjectId objectId, int type) {
+		int findOrInsert(AnyObjectId objectId, int type) {
 			MutableEntry entry = new MutableEntry(
 					objectId, type, revList.size());
 			revList.add(entry);
@@ -429,9 +474,9 @@
 	}
 
 	private static final class MutableEntry extends ObjectIdOwnerMap.Entry {
-		private final int type;
+		final int type;
 
-		private final int position;
+		final int position;
 
 		MutableEntry(AnyObjectId objectId, int type, int position) {
 			super(objectId);
@@ -456,23 +501,7 @@
 		}
 	}
 
-	private boolean isSameCompressedBitmap(Bitmap other) {
-		if (other instanceof CompressedBitmap) {
-			CompressedBitmap b = (CompressedBitmap) other;
-			return this == b.getPackBitmapIndex();
-		}
-		return false;
-	}
-
-	private boolean isSameCompressedBitmapBuilder(Bitmap other) {
-		if (other instanceof CompressedBitmapBuilder) {
-			CompressedBitmapBuilder b = (CompressedBitmapBuilder) other;
-			return this == b.getBitmapIndex();
-		}
-		return false;
-	}
-
-	private static final EWAHCompressedBitmap ones(int sizeInBits) {
+	static final EWAHCompressedBitmap ones(int sizeInBits) {
 		EWAHCompressedBitmap mask = new EWAHCompressedBitmap();
 		mask.addStreamOfEmptyWords(
 				true, sizeInBits / EWAHCompressedBitmap.wordinbits);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/DeltaBaseCache.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/DeltaBaseCache.java
index 2f30496..a95dea7 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/DeltaBaseCache.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/DeltaBaseCache.java
@@ -50,7 +50,7 @@
 class DeltaBaseCache {
 	private static final int CACHE_SZ = 1024;
 
-	private static final SoftReference<Entry> DEAD;
+	static final SoftReference<Entry> DEAD;
 
 	private static int hash(final long position) {
 		return (((int) position) << 22) >>> 22;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileRepository.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileRepository.java
index 995621e..b02efed 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileRepository.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileRepository.java
@@ -49,16 +49,21 @@
 import static org.eclipse.jgit.lib.RefDatabase.ALL;
 
 import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.text.MessageFormat;
 import java.util.HashSet;
 import java.util.Set;
 
+import org.eclipse.jgit.attributes.AttributesNode;
+import org.eclipse.jgit.attributes.AttributesNodeProvider;
 import org.eclipse.jgit.errors.ConfigInvalidException;
 import org.eclipse.jgit.events.ConfigChangedEvent;
 import org.eclipse.jgit.events.ConfigChangedListener;
 import org.eclipse.jgit.events.IndexChangedEvent;
 import org.eclipse.jgit.internal.JGitText;
+import org.eclipse.jgit.internal.storage.reftree.RefTreeDatabase;
 import org.eclipse.jgit.internal.storage.file.ObjectDirectory.AlternateHandle;
 import org.eclipse.jgit.internal.storage.file.ObjectDirectory.AlternateRepository;
 import org.eclipse.jgit.lib.BaseRepositoryBuilder;
@@ -197,7 +202,22 @@
 			}
 		});
 
-		refs = new RefDirectory(this);
+		final long repositoryFormatVersion = getConfig().getLong(
+				ConfigConstants.CONFIG_CORE_SECTION, null,
+				ConfigConstants.CONFIG_KEY_REPO_FORMAT_VERSION, 0);
+
+		String reftype = repoConfig.getString(
+				"extensions", null, "refsStorage"); //$NON-NLS-1$ //$NON-NLS-2$
+		if (repositoryFormatVersion >= 1 && reftype != null) {
+			if (StringUtils.equalsIgnoreCase(reftype, "reftree")) { //$NON-NLS-1$
+				refs = new RefTreeDatabase(this, new RefDirectory(this));
+			} else {
+				throw new IOException(JGitText.get().unknownRepositoryFormat);
+			}
+		} else {
+			refs = new RefDirectory(this);
+		}
+
 		objectDatabase = new ObjectDirectory(repoConfig, //
 				options.getObjectDirectory(), //
 				options.getAlternateObjectDirectories(), //
@@ -205,10 +225,7 @@
 				new File(getDirectory(), Constants.SHALLOW));
 
 		if (objectDatabase.exists()) {
-			final long repositoryFormatVersion = getConfig().getLong(
-					ConfigConstants.CONFIG_CORE_SECTION, null,
-					ConfigConstants.CONFIG_KEY_REPO_FORMAT_VERSION, 0);
-			if (repositoryFormatVersion > 0)
+			if (repositoryFormatVersion > 1)
 				throw new IOException(MessageFormat.format(
 						JGitText.get().unknownRepositoryFormat2,
 						Long.valueOf(repositoryFormatVersion)));
@@ -337,7 +354,7 @@
 						ConfigConstants.CONFIG_KEY_WORKTREE, getWorkTree()
 								.getAbsolutePath());
 				LockFile dotGitLockFile = new LockFile(new File(workTree,
-						Constants.DOT_GIT), getFS());
+						Constants.DOT_GIT));
 				try {
 					if (dotGitLockFile.lock()) {
 						dotGitLockFile.write(Constants.encode(Constants.GITDIR
@@ -479,4 +496,63 @@
 			return new ReflogReaderImpl(this, ref.getName());
 		return null;
 	}
+
+	@Override
+	public AttributesNodeProvider createAttributesNodeProvider() {
+		return new AttributesNodeProviderImpl(this);
+	}
+
+	/**
+	 * Implementation a {@link AttributesNodeProvider} for a
+	 * {@link FileRepository}.
+	 *
+	 * @author <a href="mailto:arthur.daussy@obeo.fr">Arthur Daussy</a>
+	 *
+	 */
+	static class AttributesNodeProviderImpl implements
+			AttributesNodeProvider {
+
+		private AttributesNode infoAttributesNode;
+
+		private AttributesNode globalAttributesNode;
+
+		/**
+		 * Constructor.
+		 *
+		 * @param repo
+		 *            {@link Repository} that will provide the attribute nodes.
+		 */
+		protected AttributesNodeProviderImpl(Repository repo) {
+			infoAttributesNode = new InfoAttributesNode(repo);
+			globalAttributesNode = new GlobalAttributesNode(repo);
+		}
+
+		public AttributesNode getInfoAttributesNode() throws IOException {
+			if (infoAttributesNode instanceof InfoAttributesNode)
+				infoAttributesNode = ((InfoAttributesNode) infoAttributesNode)
+						.load();
+			return infoAttributesNode;
+		}
+
+		public AttributesNode getGlobalAttributesNode() throws IOException {
+			if (globalAttributesNode instanceof GlobalAttributesNode)
+				globalAttributesNode = ((GlobalAttributesNode) globalAttributesNode)
+						.load();
+			return globalAttributesNode;
+		}
+
+		static void loadRulesFromFile(AttributesNode r, File attrs)
+				throws FileNotFoundException, IOException {
+			if (attrs.exists()) {
+				FileInputStream in = new FileInputStream(attrs);
+				try {
+					r.parse(in);
+				} finally {
+					in.close();
+				}
+			}
+		}
+
+	}
+
 }
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 c5723c0..2ce0d47 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
@@ -45,7 +45,6 @@
 
 import static org.eclipse.jgit.internal.storage.pack.PackExt.BITMAP_INDEX;
 import static org.eclipse.jgit.internal.storage.pack.PackExt.INDEX;
-import static org.eclipse.jgit.lib.RefDatabase.ALL;
 
 import java.io.File;
 import java.io.FileOutputStream;
@@ -53,6 +52,7 @@
 import java.io.OutputStream;
 import java.nio.channels.Channels;
 import java.nio.channels.FileChannel;
+import java.nio.file.StandardCopyOption;
 import java.text.MessageFormat;
 import java.text.ParseException;
 import java.util.ArrayList;
@@ -62,14 +62,14 @@
 import java.util.Date;
 import java.util.HashMap;
 import java.util.HashSet;
-import java.util.Iterator;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
-import java.util.Map.Entry;
+import java.util.Objects;
 import java.util.Set;
 import java.util.TreeMap;
 
+import org.eclipse.jgit.annotations.NonNull;
 import org.eclipse.jgit.dircache.DirCacheIterator;
 import org.eclipse.jgit.errors.CorruptObjectException;
 import org.eclipse.jgit.errors.IncorrectObjectTypeException;
@@ -78,18 +78,19 @@
 import org.eclipse.jgit.internal.JGitText;
 import org.eclipse.jgit.internal.storage.pack.PackExt;
 import org.eclipse.jgit.internal.storage.pack.PackWriter;
-import org.eclipse.jgit.internal.storage.pack.PackWriter.ObjectIdSet;
-import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.internal.storage.reftree.RefTreeNames;
 import org.eclipse.jgit.lib.ConfigConstants;
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.FileMode;
 import org.eclipse.jgit.lib.NullProgressMonitor;
 import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectIdSet;
 import org.eclipse.jgit.lib.ProgressMonitor;
 import org.eclipse.jgit.lib.Ref;
 import org.eclipse.jgit.lib.Ref.Storage;
 import org.eclipse.jgit.lib.RefDatabase;
 import org.eclipse.jgit.lib.ReflogEntry;
+import org.eclipse.jgit.lib.ReflogReader;
 import org.eclipse.jgit.revwalk.ObjectWalk;
 import org.eclipse.jgit.revwalk.RevObject;
 import org.eclipse.jgit.revwalk.RevWalk;
@@ -126,7 +127,7 @@
 	 * difference between the current refs and the refs which existed during
 	 * last {@link #repack()}.
 	 */
-	private Map<String, Ref> lastPackedRefs;
+	private Collection<Ref> lastPackedRefs;
 
 	/**
 	 * Holds the starting time of the last repack() execution. This is needed in
@@ -360,17 +361,20 @@
 		// during last repack(). Only those refs will survive which have been
 		// added or modified since the last repack. Only these can save existing
 		// loose refs from being pruned.
-		Map<String, Ref> newRefs;
+		Collection<Ref> newRefs;
 		if (lastPackedRefs == null || lastPackedRefs.isEmpty())
 			newRefs = getAllRefs();
 		else {
-			newRefs = new HashMap<String, Ref>();
-			for (Iterator<Map.Entry<String, Ref>> i = getAllRefs().entrySet()
-					.iterator(); i.hasNext();) {
-				Entry<String, Ref> newEntry = i.next();
-				Ref old = lastPackedRefs.get(newEntry.getKey());
-				if (!equals(newEntry.getValue(), old))
-					newRefs.put(newEntry.getKey(), newEntry.getValue());
+			Map<String, Ref> last = new HashMap<>();
+			for (Ref r : lastPackedRefs) {
+				last.put(r.getName(), r);
+			}
+			newRefs = new ArrayList<>();
+			for (Ref r : getAllRefs()) {
+				Ref old = last.get(r.getName());
+				if (!equals(r, old)) {
+					newRefs.add(r);
+				}
 			}
 		}
 
@@ -382,10 +386,10 @@
 			// leave this method.
 			ObjectWalk w = new ObjectWalk(repo);
 			try {
-				for (Ref cr : newRefs.values())
+				for (Ref cr : newRefs)
 					w.markStart(w.parseAny(cr.getObjectId()));
 				if (lastPackedRefs != null)
-					for (Ref lpr : lastPackedRefs.values())
+					for (Ref lpr : lastPackedRefs)
 						w.markUninteresting(w.parseAny(lpr.getObjectId()));
 				removeReferenced(deletionCandidates, w);
 			} finally {
@@ -403,11 +407,11 @@
 		// additional reflog entries not handled during last repack()
 		ObjectWalk w = new ObjectWalk(repo);
 		try {
-			for (Ref ar : getAllRefs().values())
+			for (Ref ar : getAllRefs())
 				for (ObjectId id : listRefLogObjects(ar, lastRepackTime))
 					w.markStart(w.parseAny(id));
 			if (lastPackedRefs != null)
-				for (Ref lpr : lastPackedRefs.values())
+				for (Ref lpr : lastPackedRefs)
 					w.markUninteresting(w.parseAny(lpr.getObjectId()));
 			removeReferenced(deletionCandidates, w);
 		} finally {
@@ -482,9 +486,10 @@
 				return false;
 			return r1.getTarget().getName().equals(r2.getTarget().getName());
 		} else {
-			if (r2.isSymbolic())
+			if (r2.isSymbolic()) {
 				return false;
-			return r1.getObjectId().equals(r2.getObjectId());
+			}
+			return Objects.equals(r1.getObjectId(), r2.getObjectId());
 		}
 	}
 
@@ -527,19 +532,23 @@
 		Collection<PackFile> toBeDeleted = repo.getObjectDatabase().getPacks();
 
 		long time = System.currentTimeMillis();
-		Map<String, Ref> refsBefore = getAllRefs();
+		Collection<Ref> refsBefore = getAllRefs();
 
 		Set<ObjectId> allHeads = new HashSet<ObjectId>();
 		Set<ObjectId> nonHeads = new HashSet<ObjectId>();
+		Set<ObjectId> txnHeads = new HashSet<ObjectId>();
 		Set<ObjectId> tagTargets = new HashSet<ObjectId>();
 		Set<ObjectId> indexObjects = listNonHEADIndexObjects();
+		RefDatabase refdb = repo.getRefDatabase();
 
-		for (Ref ref : refsBefore.values()) {
+		for (Ref ref : refsBefore) {
 			nonHeads.addAll(listRefLogObjects(ref, 0));
 			if (ref.isSymbolic() || ref.getObjectId() == null)
 				continue;
 			if (ref.getName().startsWith(Constants.R_HEADS))
 				allHeads.add(ref.getObjectId());
+			else if (RefTreeNames.isRefTree(refdb, ref.getName()))
+				txnHeads.add(ref.getObjectId());
 			else
 				nonHeads.add(ref.getObjectId());
 			if (ref.getPeeledObjectId() != null)
@@ -549,7 +558,7 @@
 		List<ObjectIdSet> excluded = new LinkedList<ObjectIdSet>();
 		for (final PackFile f : repo.getObjectDatabase().getPacks())
 			if (f.shouldBeKept())
-				excluded.add(objectIdSet(f.getIndex()));
+				excluded.add(f.getIndex());
 
 		tagTargets.addAll(allHeads);
 		nonHeads.addAll(indexObjects);
@@ -561,7 +570,7 @@
 					tagTargets, excluded);
 			if (heads != null) {
 				ret.add(heads);
-				excluded.add(0, objectIdSet(heads.getIndex()));
+				excluded.add(0, heads.getIndex());
 			}
 		}
 		if (!nonHeads.isEmpty()) {
@@ -569,6 +578,11 @@
 			if (rest != null)
 				ret.add(rest);
 		}
+		if (!txnHeads.isEmpty()) {
+			PackFile txn = writePack(txnHeads, PackWriter.NONE, null, excluded);
+			if (txn != null)
+				ret.add(txn);
+		}
 		try {
 			deleteOldPacks(toBeDeleted, ret);
 		} catch (ParseException e) {
@@ -592,7 +606,11 @@
 	 * @throws IOException
 	 */
 	private Set<ObjectId> listRefLogObjects(Ref ref, long minTime) throws IOException {
-		List<ReflogEntry> rlEntries = repo.getReflogReader(ref.getName())
+		ReflogReader reflogReader = repo.getReflogReader(ref.getName());
+		if (reflogReader == null) {
+			return Collections.emptySet();
+		}
+		List<ReflogEntry> rlEntries = reflogReader
 				.getReverseEntries();
 		if (rlEntries == null || rlEntries.isEmpty())
 			return Collections.<ObjectId> emptySet();
@@ -617,11 +635,16 @@
 	 * @return a map where names of refs point to ref objects
 	 * @throws IOException
 	 */
-	private Map<String, Ref> getAllRefs() throws IOException {
-		Map<String, Ref> ret = repo.getRefDatabase().getRefs(ALL);
-		for (Ref ref : repo.getRefDatabase().getAdditionalRefs())
-			ret.put(ref.getName(), ref);
-		return ret;
+	private Collection<Ref> getAllRefs() throws IOException {
+		Collection<Ref> refs = RefTreeNames.allRefs(repo.getRefDatabase());
+		List<Ref> addl = repo.getRefDatabase().getAdditionalRefs();
+		if (!addl.isEmpty()) {
+			List<Ref> all = new ArrayList<>(refs.size() + addl.size());
+			all.addAll(refs);
+			all.addAll(addl);
+			return all;
+		}
+		return refs;
 	}
 
 	/**
@@ -635,10 +658,7 @@
 	 */
 	private Set<ObjectId> listNonHEADIndexObjects()
 			throws CorruptObjectException, IOException {
-		try {
-			if (repo.getIndexFile() == null)
-				return Collections.emptySet();
-		} catch (NoWorkTreeException e) {
+		if (repo.isBare()) {
 			return Collections.emptySet();
 		}
 		try (TreeWalk treeWalk = new TreeWalk(repo)) {
@@ -679,8 +699,8 @@
 		}
 	}
 
-	private PackFile writePack(Set<? extends ObjectId> want,
-			Set<? extends ObjectId> have, Set<ObjectId> tagTargets,
+	private PackFile writePack(@NonNull Set<? extends ObjectId> want,
+			@NonNull Set<? extends ObjectId> have, Set<ObjectId> tagTargets,
 			List<ObjectIdSet> excludeObjects) throws IOException {
 		File tmpPack = null;
 		Map<PackExt, File> tmpExts = new TreeMap<PackExt, File>(
@@ -786,39 +806,33 @@
 						break;
 					}
 			tmpPack.setReadOnly();
-			boolean delete = true;
-			try {
-				FileUtils.rename(tmpPack, realPack);
-				delete = false;
-				for (Map.Entry<PackExt, File> tmpEntry : tmpExts.entrySet()) {
-					File tmpExt = tmpEntry.getValue();
-					tmpExt.setReadOnly();
 
-					File realExt = nameFor(
-							id, "." + tmpEntry.getKey().getExtension()); //$NON-NLS-1$
+			FileUtils.rename(tmpPack, realPack, StandardCopyOption.ATOMIC_MOVE);
+			for (Map.Entry<PackExt, File> tmpEntry : tmpExts.entrySet()) {
+				File tmpExt = tmpEntry.getValue();
+				tmpExt.setReadOnly();
+
+				File realExt = nameFor(id,
+						"." + tmpEntry.getKey().getExtension()); //$NON-NLS-1$
+				try {
+					FileUtils.rename(tmpExt, realExt,
+							StandardCopyOption.ATOMIC_MOVE);
+				} catch (IOException e) {
+					File newExt = new File(realExt.getParentFile(),
+							realExt.getName() + ".new"); //$NON-NLS-1$
 					try {
-						FileUtils.rename(tmpExt, realExt);
-					} catch (IOException e) {
-						File newExt = new File(realExt.getParentFile(),
-								realExt.getName() + ".new"); //$NON-NLS-1$
-						if (!tmpExt.renameTo(newExt))
-							newExt = tmpExt;
-						throw new IOException(MessageFormat.format(
-								JGitText.get().panicCantRenameIndexFile, newExt,
-								realExt));
+						FileUtils.rename(tmpExt, newExt,
+								StandardCopyOption.ATOMIC_MOVE);
+					} catch (IOException e2) {
+						newExt = tmpExt;
+						e = e2;
 					}
-				}
-
-			} finally {
-				if (delete) {
-					if (tmpPack.exists())
-						tmpPack.delete();
-					for (File tmpExt : tmpExts.values()) {
-						if (tmpExt.exists())
-							tmpExt.delete();
-					}
+					throw new IOException(MessageFormat.format(
+							JGitText.get().panicCantRenameIndexFile, newExt,
+							realExt), e);
 				}
 			}
+
 			return repo.getObjectDatabase().openPack(realPack);
 		} finally {
 			if (tmpPack != null && tmpPack.exists())
@@ -877,6 +891,11 @@
 		 */
 		public long numberOfPackedRefs;
 
+		/**
+		 * The number of bitmaps in the bitmap indices.
+		 */
+		public long numberOfBitmaps;
+
 		public String toString() {
 			final StringBuilder b = new StringBuilder();
 			b.append("numberOfPackedObjects=").append(numberOfPackedObjects); //$NON-NLS-1$
@@ -886,15 +905,15 @@
 			b.append(", numberOfPackedRefs=").append(numberOfPackedRefs); //$NON-NLS-1$
 			b.append(", sizeOfLooseObjects=").append(sizeOfLooseObjects); //$NON-NLS-1$
 			b.append(", sizeOfPackedObjects=").append(sizeOfPackedObjects); //$NON-NLS-1$
+			b.append(", numberOfBitmaps=").append(numberOfBitmaps); //$NON-NLS-1$
 			return b.toString();
 		}
 	}
 
 	/**
-	 * Returns the number of objects stored in pack files. If an object is
-	 * contained in multiple pack files it is counted as often as it occurs.
+	 * Returns information about objects and pack files for a FileRepository.
 	 *
-	 * @return the number of objects stored in pack files
+	 * @return information about objects and pack files for a FileRepository
 	 * @throws IOException
 	 */
 	public RepoStatistics getStatistics() throws IOException {
@@ -904,6 +923,8 @@
 			ret.numberOfPackedObjects += f.getIndex().getObjectCount();
 			ret.numberOfPackFiles++;
 			ret.sizeOfPackedObjects += f.getPackFile().length();
+			if (f.getBitmapIndex() != null)
+				ret.numberOfBitmaps += f.getBitmapIndex().getBitmapCount();
 		}
 		File objDir = repo.getObjectsDirectory();
 		String[] fanout = objDir.list();
@@ -989,12 +1010,4 @@
 		this.expire = expire;
 		expireAgeMillis = -1;
 	}
-
-	private static ObjectIdSet objectIdSet(final PackIndex idx) {
-		return new ObjectIdSet() {
-			public boolean contains(AnyObjectId objectId) {
-				return idx.hasObject(objectId);
-			}
-		};
-	}
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GlobalAttributesNode.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GlobalAttributesNode.java
new file mode 100644
index 0000000..454d3bf
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GlobalAttributesNode.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2014, Arthur Daussy <arthur.daussy@obeo.fr>
+ * Copyright (C) 2015, 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.internal.storage.file;
+
+import java.io.File;
+import java.io.IOException;
+
+import org.eclipse.jgit.attributes.AttributesNode;
+import org.eclipse.jgit.lib.CoreConfig;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.util.FS;
+
+/** Attribute node loaded from global system-wide file. */
+public class GlobalAttributesNode extends AttributesNode {
+	final Repository repository;
+
+	/**
+	 * @param repository
+	 */
+	public GlobalAttributesNode(Repository repository) {
+		this.repository = repository;
+	}
+
+	/**
+	 * @return the attributes node
+	 * @throws IOException
+	 */
+	public AttributesNode load() throws IOException {
+		AttributesNode r = new AttributesNode();
+
+		FS fs = repository.getFS();
+		String path = repository.getConfig().get(CoreConfig.KEY)
+				.getAttributesFile();
+		if (path != null) {
+			File attributesFile;
+			if (path.startsWith("~/")) { //$NON-NLS-1$
+				attributesFile = fs.resolve(fs.userHome(),
+						path.substring(2));
+			} else {
+				attributesFile = fs.resolve(null, path);
+			}
+			FileRepository.AttributesNodeProviderImpl.loadRulesFromFile(r, attributesFile);
+		}
+		return r.getRules().isEmpty() ? null : r;
+	}
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/SymlinkTreeEntry.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/InfoAttributesNode.java
similarity index 60%
copy from org.eclipse.jgit/src/org/eclipse/jgit/lib/SymlinkTreeEntry.java
copy to org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/InfoAttributesNode.java
index c7e41bc..bda5cbe 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/SymlinkTreeEntry.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/InfoAttributesNode.java
@@ -1,6 +1,6 @@
 /*
- * Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com>
- * Copyright (C) 2006-2007, Shawn O. Pearce <spearce@spearce.org>
+ * Copyright (C) 2014, Arthur Daussy <arthur.daussy@obeo.fr>
+ * Copyright (C) 2015, 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
@@ -41,45 +41,41 @@
  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
+package org.eclipse.jgit.internal.storage.file;
 
-package org.eclipse.jgit.lib;
+import java.io.File;
+import java.io.IOException;
 
-/**
- * A tree entry representing a symbolic link.
- *
- * Note. Java cannot really handle these as file system objects.
- *
- * @deprecated To look up information about a single path, use
- * {@link org.eclipse.jgit.treewalk.TreeWalk#forPath(Repository, String, org.eclipse.jgit.revwalk.RevTree)}.
- * To lookup information about multiple paths at once, use a
- * {@link org.eclipse.jgit.treewalk.TreeWalk} and obtain the current entry's
- * information from its getter methods.
- */
-@Deprecated
-public class SymlinkTreeEntry extends TreeEntry {
+import org.eclipse.jgit.attributes.AttributesNode;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.util.FS;
+
+/** Attribute node loaded from the $GIT_DIR/info/attributes file. */
+public class InfoAttributesNode extends AttributesNode {
+	final Repository repository;
 
 	/**
-	 * Construct a {@link SymlinkTreeEntry} with the specified name and SHA-1 in
-	 * the specified parent
-	 *
-	 * @param parent
-	 * @param id
-	 * @param nameUTF8
+	 * @param repository
 	 */
-	public SymlinkTreeEntry(final Tree parent, final ObjectId id,
-			final byte[] nameUTF8) {
-		super(parent, id, nameUTF8);
+	public InfoAttributesNode(Repository repository) {
+		this.repository = repository;
 	}
 
-	public FileMode getMode() {
-		return FileMode.SYMLINK;
+	/**
+	 * @return the attributes node
+	 * @throws IOException
+	 */
+	public AttributesNode load() throws IOException {
+		AttributesNode r = new AttributesNode();
+
+		FS fs = repository.getFS();
+
+		File attributes = fs.resolve(repository.getDirectory(),
+				Constants.INFO_ATTRIBUTES);
+		FileRepository.AttributesNodeProviderImpl.loadRulesFromFile(r, attributes);
+
+		return r.getRules().isEmpty() ? null : r;
 	}
 
-	public String toString() {
-		final StringBuilder r = new StringBuilder();
-		r.append(ObjectId.toString(getId()));
-		r.append(" S "); //$NON-NLS-1$
-		r.append(getFullName());
-		return r.toString();
-	}
-}
+}
\ No newline at end of file
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LazyObjectIdSetFile.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LazyObjectIdSetFile.java
new file mode 100644
index 0000000..1e2617c
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LazyObjectIdSetFile.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2015, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ *   copyright notice, this list of conditions and the following
+ *   disclaimer in the documentation and/or other materials provided
+ *   with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ *   names of its contributors may be used to endorse or promote
+ *   products derived from this software without specific prior
+ *   written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.internal.storage.file;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.Reader;
+
+import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.lib.MutableObjectId;
+import org.eclipse.jgit.lib.ObjectIdOwnerMap;
+import org.eclipse.jgit.lib.ObjectIdSet;
+
+/** Lazily loads a set of ObjectIds, one per line. */
+public class LazyObjectIdSetFile implements ObjectIdSet {
+	private final File src;
+	private ObjectIdOwnerMap<Entry> set;
+
+	/**
+	 * Create a new lazy set from a file.
+	 *
+	 * @param src
+	 *            the source file.
+	 */
+	public LazyObjectIdSetFile(File src) {
+		this.src = src;
+	}
+
+	@Override
+	public boolean contains(AnyObjectId objectId) {
+		if (set == null) {
+			set = load();
+		}
+		return set.contains(objectId);
+	}
+
+	private ObjectIdOwnerMap<Entry> load() {
+		ObjectIdOwnerMap<Entry> r = new ObjectIdOwnerMap<>();
+		try (FileInputStream fin = new FileInputStream(src);
+				Reader rin = new InputStreamReader(fin, UTF_8);
+				BufferedReader br = new BufferedReader(rin)) {
+			MutableObjectId id = new MutableObjectId();
+			for (String line; (line = br.readLine()) != null;) {
+				id.fromString(line);
+				if (!r.contains(id)) {
+					r.add(new Entry(id));
+				}
+			}
+		} catch (IOException e) {
+			// Ignore IO errors accessing the lazy set.
+		}
+		return r;
+	}
+
+	static class Entry extends ObjectIdOwnerMap.Entry {
+		Entry(AnyObjectId id) {
+			super(id);
+		}
+	}
+}
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 06eb42c..ce9677a 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
@@ -54,6 +54,7 @@
 import java.nio.ByteBuffer;
 import java.nio.channels.Channels;
 import java.nio.channels.FileChannel;
+import java.nio.file.StandardCopyOption;
 import java.text.MessageFormat;
 
 import org.eclipse.jgit.errors.LockFailedException;
@@ -120,16 +121,14 @@
 
 	private boolean haveLck;
 
-	private FileOutputStream os;
+	FileOutputStream os;
 
 	private boolean needSnapshot;
 
-	private boolean fsync;
+	boolean fsync;
 
 	private FileSnapshot commitSnapshot;
 
-	private final FS fs;
-
 	/**
 	 * Create a new lock for any file.
 	 *
@@ -138,11 +137,24 @@
 	 * @param fs
 	 *            the file system abstraction which will be necessary to perform
 	 *            certain file system operations.
+	 * @deprecated use {@link LockFile#LockFile(File)} instead
 	 */
+	@Deprecated
 	public LockFile(final File f, final FS fs) {
 		ref = f;
 		lck = getLockFile(ref);
-		this.fs = fs;
+	}
+
+	/**
+	 * Create a new lock for any file.
+	 *
+	 * @param f
+	 *            the file that will be locked.
+	 * @since 4.2
+	 */
+	public LockFile(final File f) {
+		ref = f;
+		lck = getLockFile(ref);
 	}
 
 	/**
@@ -227,6 +239,10 @@
 				fis.close();
 			}
 		} catch (FileNotFoundException fnfe) {
+			if (ref.exists()) {
+				unlock();
+				throw fnfe;
+			}
 			// Don't worry about a file that doesn't exist yet, it
 			// conceptually has no current content to copy.
 			//
@@ -437,56 +453,14 @@
 		}
 
 		saveStatInformation();
-		if (lck.renameTo(ref)) {
+		try {
+			FileUtils.rename(lck, ref, StandardCopyOption.ATOMIC_MOVE);
 			haveLck = false;
 			return true;
+		} catch (IOException e) {
+			unlock();
+			return false;
 		}
-		if (!ref.exists() || deleteRef()) {
-			if (renameLock()) {
-				haveLck = false;
-				return true;
-			}
-		}
-		unlock();
-		return false;
-	}
-
-	private boolean deleteRef() {
-		if (!fs.retryFailedLockFileCommit())
-			return ref.delete();
-
-		// File deletion fails on windows if another thread is
-		// concurrently reading the same file. So try a few times.
-		//
-		for (int attempts = 0; attempts < 10; attempts++) {
-			if (ref.delete())
-				return true;
-			try {
-				Thread.sleep(100);
-			} catch (InterruptedException e) {
-				return false;
-			}
-		}
-		return false;
-	}
-
-	private boolean renameLock() {
-		if (!fs.retryFailedLockFileCommit())
-			return lck.renameTo(ref);
-
-		// File renaming fails on windows if another thread is
-		// concurrently reading the same file. So try a few times.
-		//
-		for (int attempts = 0; attempts < 10; attempts++) {
-			if (lck.renameTo(ref))
-				return true;
-			try {
-				Thread.sleep(100);
-			} catch (InterruptedException e) {
-				return false;
-			}
-		}
-		return false;
 	}
 
 	private void saveStatInformation() {
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 e7ef127..ea80528 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
@@ -52,6 +52,9 @@
 import java.io.FileNotFoundException;
 import java.io.FileReader;
 import java.io.IOException;
+import java.nio.file.AtomicMoveNotSupportedException;
+import java.nio.file.Files;
+import java.nio.file.StandardCopyOption;
 import java.text.MessageFormat;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -433,16 +436,14 @@
 
 	ObjectLoader openLooseObject(WindowCursor curs, AnyObjectId id)
 			throws IOException {
-		try {
-			File path = fileFor(id);
-			FileInputStream in = new FileInputStream(path);
-			try {
-				unpackedObjectCache.add(id);
-				return UnpackedObject.open(in, path, id, curs);
-			} finally {
-				in.close();
-			}
+		File path = fileFor(id);
+		try (FileInputStream in = new FileInputStream(path)) {
+			unpackedObjectCache.add(id);
+			return UnpackedObject.open(in, path, id, curs);
 		} catch (FileNotFoundException noFile) {
+			if (path.exists()) {
+				throw noFile;
+			}
 			unpackedObjectCache.remove(id);
 			return null;
 		}
@@ -513,15 +514,14 @@
 
 	private long getLooseObjectSize(WindowCursor curs, AnyObjectId id)
 			throws IOException {
-		try {
-			FileInputStream in = new FileInputStream(fileFor(id));
-			try {
-				unpackedObjectCache.add(id);
-				return UnpackedObject.getSize(in, id, curs);
-			} finally {
-				in.close();
-			}
+		File f = fileFor(id);
+		try (FileInputStream in = new FileInputStream(f)) {
+			unpackedObjectCache.add(id);
+			return UnpackedObject.getSize(in, id, curs);
 		} catch (FileNotFoundException noFile) {
+			if (f.exists()) {
+				throw noFile;
+			}
 			unpackedObjectCache.remove(id);
 			return -1;
 		}
@@ -561,7 +561,11 @@
 			// Assume the pack is corrupted, and remove it from the list.
 			removePack(p);
 		} else if (e instanceof FileNotFoundException) {
-			warnTmpl = JGitText.get().packWasDeleted;
+			if (p.getPackFile().exists()) {
+				warnTmpl = JGitText.get().packInaccessible;
+			} else {
+				warnTmpl = JGitText.get().packWasDeleted;
+			}
 			removePack(p);
 		} else if (FileUtils.isStaleFileHandle(e)) {
 			warnTmpl = JGitText.get().packHandleIsStale;
@@ -607,10 +611,16 @@
 			FileUtils.delete(tmp, FileUtils.RETRY);
 			return InsertLooseObjectResult.EXISTS_LOOSE;
 		}
-		if (tmp.renameTo(dst)) {
+		try {
+			Files.move(tmp.toPath(), dst.toPath(),
+					StandardCopyOption.ATOMIC_MOVE);
 			dst.setReadOnly();
 			unpackedObjectCache.add(id);
 			return InsertLooseObjectResult.INSERTED;
+		} catch (AtomicMoveNotSupportedException e) {
+			LOG.error(e.getMessage(), e);
+		} catch (IOException e) {
+			// ignore
 		}
 
 		// Maybe the directory doesn't exist yet as the object
@@ -618,10 +628,16 @@
 		// try the rename first as the directory likely does exist.
 		//
 		FileUtils.mkdir(dst.getParentFile(), true);
-		if (tmp.renameTo(dst)) {
+		try {
+			Files.move(tmp.toPath(), dst.toPath(),
+					StandardCopyOption.ATOMIC_MOVE);
 			dst.setReadOnly();
 			unpackedObjectCache.add(id);
 			return InsertLooseObjectResult.INSERTED;
+		} catch (AtomicMoveNotSupportedException e) {
+			LOG.error(e.getMessage(), e);
+		} catch (IOException e) {
+			LOG.debug(e.getMessage(), e);
 		}
 
 		if (!createDuplicate && has(id)) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectoryPackParser.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectoryPackParser.java
index 1c076ee..2e6c245 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectoryPackParser.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectoryPackParser.java
@@ -50,6 +50,7 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.RandomAccessFile;
+import java.nio.file.StandardCopyOption;
 import java.security.MessageDigest;
 import java.text.MessageFormat;
 import java.util.Arrays;
@@ -476,20 +477,25 @@
 			}
 		}
 
-		if (!tmpPack.renameTo(finalPack)) {
+		try {
+			FileUtils.rename(tmpPack, finalPack,
+					StandardCopyOption.ATOMIC_MOVE);
+		} catch (IOException e) {
 			cleanupTemporaryFiles();
 			keep.unlock();
 			throw new IOException(MessageFormat.format(
-					JGitText.get().cannotMovePackTo, finalPack));
+					JGitText.get().cannotMovePackTo, finalPack), e);
 		}
 
-		if (!tmpIdx.renameTo(finalIdx)) {
+		try {
+			FileUtils.rename(tmpIdx, finalIdx, StandardCopyOption.ATOMIC_MOVE);
+		} catch (IOException e) {
 			cleanupTemporaryFiles();
 			keep.unlock();
 			if (!finalPack.delete())
 				finalPack.deleteOnExit();
 			throw new IOException(MessageFormat.format(
-					JGitText.get().cannotMoveIndexTo, finalIdx));
+					JGitText.get().cannotMoveIndexTo, finalIdx), e);
 		}
 
 		try {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackBitmapIndex.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackBitmapIndex.java
index ae4de84..e743cb4 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackBitmapIndex.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackBitmapIndex.java
@@ -193,4 +193,11 @@
 	 *         pack that this index was generated from.
 	 */
 	public abstract int getObjectCount();
+
+	/**
+	 * Returns the number of bitmaps in this bitmap index.
+	 *
+	 * @return the number of bitmaps in this bitmap index.
+	 */
+	public abstract int getBitmapCount();
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackBitmapIndexBuilder.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackBitmapIndexBuilder.java
index 93f8918..4ff09a1 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackBitmapIndexBuilder.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackBitmapIndexBuilder.java
@@ -74,9 +74,9 @@
 	private final EWAHCompressedBitmap blobs;
 	private final EWAHCompressedBitmap tags;
 	private final BlockList<PositionEntry> byOffset;
-	private final BlockList<StoredBitmap>
+	final BlockList<StoredBitmap>
 			byAddOrder = new BlockList<StoredBitmap>();
-	private final ObjectIdOwnerMap<PositionEntry>
+	final ObjectIdOwnerMap<PositionEntry>
 			positionEntries = new ObjectIdOwnerMap<PositionEntry>();
 
 	/**
@@ -253,7 +253,7 @@
 		return PackBitmapIndexV1.OPT_FULL;
 	}
 
-	/** @return the number of bitmaps. */
+	@Override
 	public int getBitmapCount() {
 		return getBitmaps().size();
 	}
@@ -330,7 +330,7 @@
 		private final int xorOffset;
 		private final int flags;
 
-		private StoredEntry(long objectId, EWAHCompressedBitmap bitmap,
+		StoredEntry(long objectId, EWAHCompressedBitmap bitmap,
 				int xorOffset, int flags) {
 			this.objectId = objectId;
 			this.bitmap = bitmap;
@@ -360,11 +360,11 @@
 	}
 
 	private static final class PositionEntry extends ObjectIdOwnerMap.Entry {
-		private final int namePosition;
+		final int namePosition;
 
-		private int offsetPosition;
+		int offsetPosition;
 
-		private PositionEntry(AnyObjectId objectId, int namePosition) {
+		PositionEntry(AnyObjectId objectId, int namePosition) {
 			super(objectId);
 			this.namePosition = namePosition;
 		}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackBitmapIndexRemapper.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackBitmapIndexRemapper.java
index 6b96b07..7cd68b6 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackBitmapIndexRemapper.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackBitmapIndexRemapper.java
@@ -66,7 +66,7 @@
 		implements Iterable<PackBitmapIndexRemapper.Entry> {
 
 	private final BasePackBitmapIndex oldPackIndex;
-	private final PackBitmapIndex newPackIndex;
+	final PackBitmapIndex newPackIndex;
 	private final ObjectIdOwnerMap<StoredBitmap> convertedBitmaps;
 	private final BitSet inflated;
 	private final int[] prevToNewMapping;
@@ -199,7 +199,7 @@
 	public final class Entry extends ObjectId {
 		private final int flags;
 
-		private Entry(AnyObjectId src, int flags) {
+		Entry(AnyObjectId src, int flags) {
 			super(src);
 			this.flags = flags;
 		}
@@ -209,4 +209,10 @@
 			return flags;
 		}
 	}
+
+	@Override
+	public int getBitmapCount() {
+		// The count is only useful for the end index, not the remapper.
+		return 0;
+	}
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackBitmapIndexV1.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackBitmapIndexV1.java
index a38a26d..a7ab00d 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackBitmapIndexV1.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackBitmapIndexV1.java
@@ -213,6 +213,11 @@
 	}
 
 	@Override
+	public int getBitmapCount() {
+		return bitmaps.size();
+	}
+
+	@Override
 	public boolean equals(Object o) {
 		// TODO(cranger): compare the pack checksum?
 		if (o instanceof PackBitmapIndexV1)
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 589a811..b385b8a 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
@@ -119,7 +119,7 @@
 
 	private int activeCopyRawData;
 
-	private int packLastModified;
+	int packLastModified;
 
 	private volatile boolean invalid;
 
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 0040aea..f36bd4d 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
@@ -60,6 +60,7 @@
 import org.eclipse.jgit.lib.AnyObjectId;
 import org.eclipse.jgit.lib.MutableObjectId;
 import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectIdSet;
 import org.eclipse.jgit.util.IO;
 import org.eclipse.jgit.util.NB;
 
@@ -72,7 +73,8 @@
  * by ObjectId.
  * </p>
  */
-public abstract class PackIndex implements Iterable<PackIndex.MutableEntry> {
+public abstract class PackIndex
+		implements Iterable<PackIndex.MutableEntry>, ObjectIdSet {
 	/**
 	 * Open an existing pack <code>.idx</code> file for reading.
 	 * <p>
@@ -166,6 +168,11 @@
 		return findOffset(id) != -1;
 	}
 
+	@Override
+	public boolean contains(AnyObjectId id) {
+		return findOffset(id) != -1;
+	}
+
 	/**
 	 * Provide iterator that gives access to index entries. Note, that iterator
 	 * returns reference to mutable object, the same reference in each call -
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndexV1.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndexV1.java
index ab3297a..e5a729d 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndexV1.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndexV1.java
@@ -67,7 +67,7 @@
 
 	private final long[] idxHeader;
 
-	private byte[][] idxdata;
+	byte[][] idxdata;
 
 	private long objectCnt;
 
@@ -233,9 +233,9 @@
 	}
 
 	private class IndexV1Iterator extends EntriesIterator {
-		private int levelOne;
+		int levelOne;
 
-		private int levelTwo;
+		int levelTwo;
 
 		@Override
 		protected MutableEntry initEntry() {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndexV2.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndexV2.java
index cb8c91a..d87336f 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndexV2.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndexV2.java
@@ -75,16 +75,16 @@
 	private final long[] fanoutTable;
 
 	/** 256 arrays of contiguous object names. */
-	private int[][] names;
+	int[][] names;
 
 	/** 256 arrays of the 32 bit offset data, matching {@link #names}. */
-	private byte[][] offset32;
+	byte[][] offset32;
 
 	/** 256 arrays of the CRC-32 of objects, matching {@link #names}. */
 	private byte[][] crc32;
 
 	/** 64 bit offset table. */
-	private byte[] offset64;
+	byte[] offset64;
 
 	PackIndexV2(final InputStream fd) throws IOException {
 		final byte[] fanoutRaw = new byte[4 * FANOUT];
@@ -304,9 +304,9 @@
 	}
 
 	private class EntriesIteratorV2 extends EntriesIterator {
-		private int levelOne;
+		int levelOne;
 
-		private int levelTwo;
+		int levelTwo;
 
 		@Override
 		protected MutableEntry initEntry() {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackLock.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackLock.java
index b671b03..a433dec 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackLock.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackLock.java
@@ -53,7 +53,6 @@
 /** Keeps track of a {@link PackFile}'s associated <code>.keep</code> file. */
 public class PackLock {
 	private final File keepFile;
-	private final FS fs;
 
 	/**
 	 * Create a new lock for a pack file.
@@ -67,7 +66,6 @@
 		final File p = packFile.getParentFile();
 		final String n = packFile.getName();
 		keepFile = new File(p, n.substring(0, n.length() - 5) + ".keep"); //$NON-NLS-1$
-		this.fs = fs;
 	}
 
 	/**
@@ -84,7 +82,7 @@
 			return false;
 		if (!msg.endsWith("\n")) //$NON-NLS-1$
 			msg += "\n"; //$NON-NLS-1$
-		final LockFile lf = new LockFile(keepFile, fs);
+		final LockFile lf = new LockFile(keepFile);
 		if (!lf.lock())
 			return false;
 		lf.write(Constants.encode(msg));
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 bb5b044..b8c2fb4 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
@@ -73,6 +73,7 @@
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.atomic.AtomicReference;
 
+import org.eclipse.jgit.annotations.NonNull;
 import org.eclipse.jgit.errors.InvalidObjectIdException;
 import org.eclipse.jgit.errors.LockFailedException;
 import org.eclipse.jgit.errors.MissingObjectException;
@@ -138,7 +139,7 @@
 
 	private final File gitDir;
 
-	private final File refsDir;
+	final File refsDir;
 
 	private final ReflogWriter logWriter;
 
@@ -155,7 +156,7 @@
 	private final AtomicReference<RefList<LooseRef>> looseRefs = new AtomicReference<RefList<LooseRef>>();
 
 	/** Immutable sorted list of packed references. */
-	private final AtomicReference<PackedRefList> packedRefs = new AtomicReference<PackedRefList>();
+	final AtomicReference<PackedRefList> packedRefs = new AtomicReference<PackedRefList>();
 
 	/**
 	 * Number of modifications made to this database.
@@ -294,6 +295,8 @@
 				ref = readRef(prefix + needle, packed);
 				if (ref != null) {
 					ref = resolve(ref, 0, null, null, packed);
+				}
+				if (ref != null) {
 					break;
 				}
 			} catch (IOException e) {
@@ -585,8 +588,7 @@
 		// we don't miss an edit made externally.
 		final PackedRefList packed = getPackedRefs();
 		if (packed.contains(name)) {
-			LockFile lck = new LockFile(packedRefsFile,
-					update.getRepository().getFS());
+			LockFile lck = new LockFile(packedRefsFile);
 			if (!lck.lock())
 				throw new LockFailedException(packedRefsFile);
 			try {
@@ -636,7 +638,7 @@
 		FS fs = parent.getFS();
 
 		// Lock the packed refs file and read the content
-		LockFile lck = new LockFile(packedRefsFile, fs);
+		LockFile lck = new LockFile(packedRefsFile);
 		if (!lck.lock())
 			throw new IOException(MessageFormat.format(
 					JGitText.get().cannotLock, packedRefsFile));
@@ -667,8 +669,7 @@
 				File refFile = fileFor(refName);
 				if (!fs.exists(refFile))
 					continue;
-				LockFile rLck = new LockFile(refFile,
-						parent.getFS());
+				LockFile rLck = new LockFile(refFile);
 				if (!rLck.lock())
 					continue;
 				try {
@@ -713,16 +714,20 @@
 	 */
 	private Ref peeledPackedRef(Ref f)
 			throws MissingObjectException, IOException {
-		if (f.getStorage().isPacked() && f.isPeeled())
+		if (f.getStorage().isPacked() && f.isPeeled()) {
 			return f;
-		if (!f.isPeeled())
+		}
+		if (!f.isPeeled()) {
 			f = peel(f);
-		if (f.getPeeledObjectId() != null)
+		}
+		ObjectId peeledObjectId = f.getPeeledObjectId();
+		if (peeledObjectId != null) {
 			return new ObjectIdRef.PeeledTag(PACKED, f.getName(),
-					f.getObjectId(), f.getPeeledObjectId());
-		else
+					f.getObjectId(), peeledObjectId);
+		} else {
 			return new ObjectIdRef.PeeledNonTag(PACKED, f.getName(),
 					f.getObjectId());
+		}
 	}
 
 	void log(final RefUpdate update, final String msg, final boolean deref)
@@ -786,6 +791,9 @@
 						new DigestInputStream(new FileInputStream(packedRefsFile),
 								digest), CHARSET));
 			} catch (FileNotFoundException noPackedRefs) {
+				if (packedRefsFile.exists()) {
+					throw noPackedRefs;
+				}
 				// Ignore it and leave the new list empty.
 				return PackedRefList.NO_PACKED_REFS;
 			}
@@ -925,7 +933,7 @@
 		return n;
 	}
 
-	private LooseRef scanRef(LooseRef ref, String name) throws IOException {
+	LooseRef scanRef(LooseRef ref, String name) throws IOException {
 		final File path = fileFor(name);
 		FileSnapshot currentSnapshot = null;
 
@@ -942,7 +950,10 @@
 		try {
 			buf = IO.readSome(path, limit);
 		} catch (FileNotFoundException noFile) {
-			return null; // doesn't exist; not a reference.
+			if (path.exists() && path.isFile()) {
+				throw noFile;
+			}
+			return null; // doesn't exist or no file; not a reference.
 		}
 
 		int n = buf.length;
@@ -977,7 +988,7 @@
 		try {
 			id = ObjectId.fromString(buf, 0);
 			if (ref != null && !ref.isSymbolic()
-					&& ref.getTarget().getObjectId().equals(id)) {
+					&& id.equals(ref.getTarget().getObjectId())) {
 				assert(currentSnapshot != null);
 				currentSnapshot.setClean(otherSnapshot);
 				return ref;
@@ -1095,8 +1106,8 @@
 			implements LooseRef {
 		private final FileSnapshot snapShot;
 
-		LoosePeeledTag(FileSnapshot snapshot, String refName, ObjectId id,
-				ObjectId p) {
+		LoosePeeledTag(FileSnapshot snapshot, @NonNull String refName,
+				@NonNull ObjectId id, @NonNull ObjectId p) {
 			super(LOOSE, refName, id, p);
 			this.snapShot = snapshot;
 		}
@@ -1114,7 +1125,8 @@
 			implements LooseRef {
 		private final FileSnapshot snapShot;
 
-		LooseNonTag(FileSnapshot snapshot, String refName, ObjectId id) {
+		LooseNonTag(FileSnapshot snapshot, @NonNull String refName,
+				@NonNull ObjectId id) {
 			super(LOOSE, refName, id);
 			this.snapShot = snapshot;
 		}
@@ -1132,7 +1144,8 @@
 			implements LooseRef {
 		private FileSnapshot snapShot;
 
-		LooseUnpeeled(FileSnapshot snapShot, String refName, ObjectId id) {
+		LooseUnpeeled(FileSnapshot snapShot, @NonNull String refName,
+				@NonNull ObjectId id) {
 			super(LOOSE, refName, id);
 			this.snapShot = snapShot;
 		}
@@ -1141,13 +1154,24 @@
 			return snapShot;
 		}
 
+		@NonNull
+		@Override
+		public ObjectId getObjectId() {
+			ObjectId id = super.getObjectId();
+			assert id != null; // checked in constructor
+			return id;
+		}
+
 		public LooseRef peel(ObjectIdRef newLeaf) {
-			if (newLeaf.getPeeledObjectId() != null)
+			ObjectId peeledObjectId = newLeaf.getPeeledObjectId();
+			ObjectId objectId = getObjectId();
+			if (peeledObjectId != null) {
 				return new LoosePeeledTag(snapShot, getName(),
-						getObjectId(), newLeaf.getPeeledObjectId());
-			else
+						objectId, peeledObjectId);
+			} else {
 				return new LooseNonTag(snapShot, getName(),
-						getObjectId());
+						objectId);
+			}
 		}
 	}
 
@@ -1155,7 +1179,8 @@
 			LooseRef {
 		private final FileSnapshot snapShot;
 
-		LooseSymbolicRef(FileSnapshot snapshot, String refName, Ref target) {
+		LooseSymbolicRef(FileSnapshot snapshot, @NonNull String refName,
+				@NonNull Ref target) {
 			super(refName, target);
 			this.snapShot = snapshot;
 		}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectoryRename.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectoryRename.java
index ba4a63d..4b803a5 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectoryRename.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectoryRename.java
@@ -46,6 +46,8 @@
 
 import java.io.File;
 import java.io.IOException;
+import java.nio.file.AtomicMoveNotSupportedException;
+import java.nio.file.StandardCopyOption;
 
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.ObjectId;
@@ -54,6 +56,8 @@
 import org.eclipse.jgit.lib.RefUpdate.Result;
 import org.eclipse.jgit.revwalk.RevWalk;
 import org.eclipse.jgit.util.FileUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 /**
  * Rename any reference stored by {@link RefDirectory}.
@@ -66,6 +70,9 @@
  * directory that happens to match the source name.
  */
 class RefDirectoryRename extends RefRename {
+	private static final Logger LOG = LoggerFactory
+			.getLogger(RefDirectoryRename.class);
+
 	private final RefDirectory refdb;
 
 	/**
@@ -201,13 +208,25 @@
 	}
 
 	private static boolean rename(File src, File dst) {
-		if (src.renameTo(dst))
+		try {
+			FileUtils.rename(src, dst, StandardCopyOption.ATOMIC_MOVE);
 			return true;
+		} catch (AtomicMoveNotSupportedException e) {
+			LOG.error(e.getMessage(), e);
+		} catch (IOException e) {
+			// ignore
+		}
 
 		File dir = dst.getParentFile();
 		if ((dir.exists() || !dir.mkdirs()) && !dir.isDirectory())
 			return false;
-		return src.renameTo(dst);
+		try {
+			FileUtils.rename(src, dst, StandardCopyOption.ATOMIC_MOVE);
+			return true;
+		} catch (IOException e) {
+			LOG.error(e.getMessage(), e);
+			return false;
+		}
 	}
 
 	private boolean linkHEAD(RefUpdate target) {
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 7858ee1..0d16f79 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
@@ -79,7 +79,7 @@
 		if (deref)
 			dst = dst.getLeaf();
 		String name = dst.getName();
-		lock = new LockFile(database.fileFor(name), getRepository().getFS());
+		lock = new LockFile(database.fileFor(name));
 		if (lock.lock()) {
 			dst = database.getRef(name);
 			setOldObjectId(dst != null ? dst.getObjectId() : null);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ReflogReaderImpl.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ReflogReaderImpl.java
index dadc631..2f583b2 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ReflogReaderImpl.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ReflogReaderImpl.java
@@ -96,6 +96,9 @@
 		try {
 			log = IO.readFully(logName);
 		} catch (FileNotFoundException e) {
+			if (logName.exists()) {
+				throw e;
+			}
 			return null;
 		}
 
@@ -118,6 +121,9 @@
 		try {
 			log = IO.readFully(logName);
 		} catch (FileNotFoundException e) {
+			if (logName.exists()) {
+				throw e;
+			}
 			return Collections.emptyList();
 		}
 
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/UnpackedObject.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/UnpackedObject.java
index e4cc697..a027437 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/UnpackedObject.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/UnpackedObject.java
@@ -232,7 +232,7 @@
 		}
 	}
 
-	private static void checkValidEndOfStream(InputStream in, Inflater inf,
+	static void checkValidEndOfStream(InputStream in, Inflater inf,
 			AnyObjectId id, final byte[] buf) throws IOException,
 			CorruptObjectException {
 		for (;;) {
@@ -266,7 +266,7 @@
 		}
 	}
 
-	private static boolean isStandardFormat(final byte[] hdr) {
+	static boolean isStandardFormat(final byte[] hdr) {
 		/*
 		 * We must determine if the buffer contains the standard
 		 * zlib-deflated stream or the experimental format based
@@ -298,7 +298,7 @@
 		return (fb & 0x8f) == 0x08 && (((fb << 8) | hdr[1] & 0xff) % 31) == 0;
 	}
 
-	private static InputStream inflate(final InputStream in, final long size,
+	static InputStream inflate(final InputStream in, final long size,
 			final ObjectId id) {
 		final Inflater inf = InflaterCache.get();
 		return new InflaterInputStream(in, inf) {
@@ -334,11 +334,11 @@
 		return new InflaterInputStream(in, inf, BUFFER_SIZE);
 	}
 
-	private static BufferedInputStream buffer(InputStream in) {
+	static BufferedInputStream buffer(InputStream in) {
 		return new BufferedInputStream(in, BUFFER_SIZE);
 	}
 
-	private static int readSome(InputStream in, final byte[] hdr, int off,
+	static int readSome(InputStream in, final byte[] hdr, int off,
 			int cnt) throws IOException {
 		int avail = 0;
 		while (0 < cnt) {
@@ -363,7 +363,7 @@
 
 		private final FileObjectDatabase source;
 
-		private LargeObject(int type, long size, File path, AnyObjectId id,
+		LargeObject(int type, long size, File path, AnyObjectId id,
 				FileObjectDatabase db) {
 			this.type = type;
 			this.size = size;
@@ -399,6 +399,9 @@
 			try {
 				in = buffer(new FileInputStream(path));
 			} catch (FileNotFoundException gone) {
+				if (path.exists()) {
+					throw gone;
+				}
 				// If the loose file no longer exists, it may have been
 				// moved into a pack file in the mean time. Try again
 				// to locate the object.
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/DeltaTask.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/DeltaTask.java
index 8ea0c23..4292742 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/DeltaTask.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/DeltaTask.java
@@ -73,7 +73,7 @@
 		final int endIndex;
 
 		private long totalWeight;
-		private long bytesPerUnit;
+		long bytesPerUnit;
 
 		Block(int threads, PackConfig config, ObjectReader reader,
 				DeltaCache dc, ThreadSafeProgressMonitor pm,
@@ -110,10 +110,12 @@
 						maxWork = s.size();
 					}
 				}
-				if (maxTask == null)
+				if (maxTask == null) {
 					return null;
-				if (maxTask.tryStealWork(maxSlice))
+				}
+				if (maxTask.tryStealWork(maxSlice)) {
 					return forThread.initWindow(maxSlice);
+				}
 			}
 		}
 
@@ -138,26 +140,30 @@
 				for (; w < weightPerThread && i < endIndex;) {
 					if (nextTop < topPaths.size()
 							&& i == topPaths.get(nextTop).slice.beginIndex) {
-						if (s < i)
+						if (s < i) {
 							task.add(new Slice(s, i));
+						}
 						s = i = topPaths.get(nextTop++).slice.endIndex;
-					} else
-						w += list[i++].getWeight();
+					} else {
+						w += getAdjustedWeight(list[i++]);
+					}
 				}
 
 				// Round up the slice to the end of a path.
 				if (s < i) {
 					int h = list[i - 1].getPathHash();
 					while (i < endIndex) {
-						if (h == list[i].getPathHash())
+						if (h == list[i].getPathHash()) {
 							i++;
-						else
+						} else {
 							break;
+						}
 					}
 					task.add(new Slice(s, i));
 				}
-				if (!task.slices.isEmpty())
+				if (!task.slices.isEmpty()) {
 					tasks.add(task);
+				}
 			}
 			while (topPathItr.hasNext()) {
 				WeightedPath p = topPathItr.next();
@@ -174,8 +180,8 @@
 					threads);
 			int cp = beginIndex;
 			int ch = list[cp].getPathHash();
-			long cw = list[cp].getWeight();
-			totalWeight = list[cp].getWeight();
+			long cw = getAdjustedWeight(list[cp]);
+			totalWeight = cw;
 
 			for (int i = cp + 1; i < endIndex; i++) {
 				ObjectToPack o = list[i];
@@ -184,24 +190,25 @@
 						if (topPaths.size() < threads) {
 							Slice s = new Slice(cp, i);
 							topPaths.add(new WeightedPath(cw, s));
-							if (topPaths.size() == threads)
+							if (topPaths.size() == threads) {
 								Collections.sort(topPaths);
+							}
 						} else if (topPaths.get(0).weight < cw) {
 							Slice s = new Slice(cp, i);
 							WeightedPath p = new WeightedPath(cw, s);
 							topPaths.set(0, p);
-							if (p.compareTo(topPaths.get(1)) > 0)
+							if (p.compareTo(topPaths.get(1)) > 0) {
 								Collections.sort(topPaths);
+							}
 						}
 					}
 					cp = i;
 					ch = o.getPathHash();
 					cw = 0;
 				}
-				if (o.isEdge() || o.doNotAttemptDelta())
-					continue;
-				cw += o.getWeight();
-				totalWeight += o.getWeight();
+				int weight = getAdjustedWeight(o);
+				cw += weight;
+				totalWeight += weight;
 			}
 
 			// Sort by starting index to identify gaps later.
@@ -212,12 +219,22 @@
 			});
 
 			bytesPerUnit = 1;
-			while (MAX_METER <= (totalWeight / bytesPerUnit))
+			while (MAX_METER <= (totalWeight / bytesPerUnit)) {
 				bytesPerUnit <<= 10;
+			}
 			return topPaths;
 		}
 	}
 
+	static int getAdjustedWeight(ObjectToPack o) {
+		// Edge objects and those with reused deltas do not need to be
+		// compressed. For compression calculations, ignore their weights.
+		if (o.isEdge() || o.doNotAttemptDelta()) {
+			return 0;
+		}
+		return o.getWeight();
+	}
+
 	static final class WeightedPath implements Comparable<WeightedPath> {
 		final long weight;
 		final Slice slice;
@@ -229,8 +246,9 @@
 
 		public int compareTo(WeightedPath o) {
 			int cmp = Long.signum(weight - o.weight);
-			if (cmp != 0)
+			if (cmp != 0) {
 				return cmp;
+			}
 			return slice.beginIndex - o.slice.beginIndex;
 		}
 	}
@@ -250,7 +268,7 @@
 	}
 
 	private final Block block;
-	private final LinkedList<Slice> slices;
+	final LinkedList<Slice> slices;
 
 	private ObjectReader or;
 	private DeltaWindow dw;
@@ -278,14 +296,16 @@
 			DeltaWindow w;
 			for (;;) {
 				synchronized (this) {
-					if (slices.isEmpty())
+					if (slices.isEmpty()) {
 						break;
+					}
 					w = initWindow(slices.removeFirst());
 				}
 				runWindow(w);
 			}
-			while ((w = block.stealWork(this)) != null)
+			while ((w = block.stealWork(this)) != null) {
 				runWindow(w);
+			}
 		} finally {
 			block.pm.endWorker();
 			or.close();
@@ -315,8 +335,9 @@
 	}
 
 	synchronized Slice remaining() {
-		if (!slices.isEmpty())
+		if (!slices.isEmpty()) {
 			return slices.getLast();
+		}
 		DeltaWindow d = dw;
 		return d != null ? d.remaining() : null;
 	}
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 683d1cd..525f9ae 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
@@ -80,6 +80,7 @@
 import java.util.zip.Deflater;
 import java.util.zip.DeflaterOutputStream;
 
+import org.eclipse.jgit.annotations.NonNull;
 import org.eclipse.jgit.errors.CorruptObjectException;
 import org.eclipse.jgit.errors.IncorrectObjectTypeException;
 import org.eclipse.jgit.errors.LargeObjectException;
@@ -99,6 +100,7 @@
 import org.eclipse.jgit.lib.NullProgressMonitor;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.ObjectIdOwnerMap;
+import org.eclipse.jgit.lib.ObjectIdSet;
 import org.eclipse.jgit.lib.ObjectLoader;
 import org.eclipse.jgit.lib.ObjectReader;
 import org.eclipse.jgit.lib.ProgressMonitor;
@@ -161,17 +163,8 @@
 public class PackWriter implements AutoCloseable {
 	private static final int PACK_VERSION_GENERATED = 2;
 
-	/** A collection of object ids. */
-	public interface ObjectIdSet {
-		/**
-		 * Returns true if the objectId is contained within the collection.
-		 *
-		 * @param objectId
-		 *            the objectId to find
-		 * @return whether the collection contains the objectId.
-		 */
-		boolean contains(AnyObjectId objectId);
-	}
+	/** Empty set of objects for {@code preparePack()}. */
+	public static Set<ObjectId> NONE = Collections.emptySet();
 
 	private static final Map<WeakReference<PackWriter>, Boolean> instances =
 			new ConcurrentHashMap<WeakReference<PackWriter>, Boolean>();
@@ -218,7 +211,7 @@
 	}
 
 	@SuppressWarnings("unchecked")
-	private BlockList<ObjectToPack> objectsLists[] = new BlockList[OBJ_TAG + 1];
+	BlockList<ObjectToPack> objectsLists[] = new BlockList[OBJ_TAG + 1];
 	{
 		objectsLists[OBJ_COMMIT] = new BlockList<ObjectToPack>();
 		objectsLists[OBJ_TREE] = new BlockList<ObjectToPack>();
@@ -249,7 +242,7 @@
 	/** {@link #reader} recast to the reuse interface, if it supports it. */
 	private final ObjectReuseAsIs reuseSupport;
 
-	private final PackConfig config;
+	final PackConfig config;
 
 	private final PackStatistics.Accumulator stats;
 
@@ -681,7 +674,7 @@
 	 * @throws IOException
 	 *             when some I/O problem occur during reading objects.
 	 */
-	public void preparePack(final Iterator<RevObject> objectsSource)
+	public void preparePack(@NonNull Iterator<RevObject> objectsSource)
 			throws IOException {
 		while (objectsSource.hasNext()) {
 			addObject(objectsSource.next());
@@ -704,16 +697,18 @@
 	 *            progress during object enumeration.
 	 * @param want
 	 *            collection of objects to be marked as interesting (start
-	 *            points of graph traversal).
+	 *            points of graph traversal). Must not be {@code null}.
 	 * @param have
 	 *            collection of objects to be marked as uninteresting (end
-	 *            points of graph traversal).
+	 *            points of graph traversal). Pass {@link #NONE} if all objects
+	 *            reachable from {@code want} are desired, such as when serving
+	 *            a clone.
 	 * @throws IOException
 	 *             when some I/O problem occur during reading objects.
 	 */
 	public void preparePack(ProgressMonitor countingMonitor,
-			Set<? extends ObjectId> want,
-			Set<? extends ObjectId> have) throws IOException {
+			@NonNull Set<? extends ObjectId> want,
+			@NonNull Set<? extends ObjectId> have) throws IOException {
 		ObjectWalk ow;
 		if (shallowPack)
 			ow = new DepthWalk.ObjectWalk(reader, depth);
@@ -740,17 +735,19 @@
 	 *            ObjectWalk to perform enumeration.
 	 * @param interestingObjects
 	 *            collection of objects to be marked as interesting (start
-	 *            points of graph traversal).
+	 *            points of graph traversal). Must not be {@code null}.
 	 * @param uninterestingObjects
 	 *            collection of objects to be marked as uninteresting (end
-	 *            points of graph traversal).
+	 *            points of graph traversal). Pass {@link #NONE} if all objects
+	 *            reachable from {@code want} are desired, such as when serving
+	 *            a clone.
 	 * @throws IOException
 	 *             when some I/O problem occur during reading objects.
 	 */
 	public void preparePack(ProgressMonitor countingMonitor,
-			ObjectWalk walk,
-			final Set<? extends ObjectId> interestingObjects,
-			final Set<? extends ObjectId> uninterestingObjects)
+			@NonNull ObjectWalk walk,
+			@NonNull Set<? extends ObjectId> interestingObjects,
+			@NonNull Set<? extends ObjectId> uninterestingObjects)
 			throws IOException {
 		if (countingMonitor == null)
 			countingMonitor = NullProgressMonitor.INSTANCE;
@@ -1306,8 +1303,7 @@
 		long totalWeight = 0;
 		for (int i = 0; i < cnt; i++) {
 			ObjectToPack o = list[i];
-			if (!o.isEdge() && !o.doNotAttemptDelta())
-				totalWeight += o.getWeight();
+			totalWeight += DeltaTask.getAdjustedWeight(o);
 		}
 
 		long bytesPerUnit = 1;
@@ -1552,6 +1548,8 @@
 			if (zbuf != null) {
 				out.writeHeader(otp, otp.getCachedSize());
 				out.write(zbuf);
+				typeStats.cntDeltas++;
+				typeStats.deltaBytes += out.length() - otp.getOffset();
 				return;
 			}
 		}
@@ -1607,17 +1605,12 @@
 		out.write(packcsum);
 	}
 
-	private void findObjectsToPack(final ProgressMonitor countingMonitor,
-			final ObjectWalk walker, final Set<? extends ObjectId> want,
-			Set<? extends ObjectId> have)
-			throws MissingObjectException, IOException,
-			IncorrectObjectTypeException {
+	private void findObjectsToPack(@NonNull ProgressMonitor countingMonitor,
+			@NonNull ObjectWalk walker, @NonNull Set<? extends ObjectId> want,
+			@NonNull Set<? extends ObjectId> have) throws IOException {
 		final long countingStart = System.currentTimeMillis();
 		beginPhase(PackingPhase.COUNTING, countingMonitor, ProgressMonitor.UNKNOWN);
 
-		if (have == null)
-			have = Collections.emptySet();
-
 		stats.interestingObjects = Collections.unmodifiableSet(new HashSet<ObjectId>(want));
 		stats.uninterestingObjects = Collections.unmodifiableSet(new HashSet<ObjectId>(have));
 
@@ -2015,10 +2008,10 @@
 		byName = null;
 
 		PackWriterBitmapPreparer bitmapPreparer = new PackWriterBitmapPreparer(
-				reader, writeBitmaps, pm, stats.interestingObjects);
+				reader, writeBitmaps, pm, stats.interestingObjects, config);
 
 		Collection<PackWriterBitmapPreparer.BitmapCommit> selectedCommits =
-				bitmapPreparer.doCommitSelection(numCommits);
+				bitmapPreparer.selectCommits(numCommits);
 
 		beginPhase(PackingPhase.BUILDING_BITMAPS, pm, selectedCommits.size());
 
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriterBitmapPreparer.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriterBitmapPreparer.java
index 756d4b0..77311ab 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriterBitmapPreparer.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriterBitmapPreparer.java
@@ -44,6 +44,7 @@
 package org.eclipse.jgit.internal.storage.pack;
 
 import static org.eclipse.jgit.internal.storage.file.PackBitmapIndex.FLAG_REUSE;
+import static org.eclipse.jgit.revwalk.RevFlag.SEEN;
 
 import java.io.IOException;
 import java.util.ArrayList;
@@ -55,34 +56,44 @@
 import java.util.List;
 import java.util.Set;
 
-import com.googlecode.javaewah.EWAHCompressedBitmap;
-
 import org.eclipse.jgit.errors.IncorrectObjectTypeException;
 import org.eclipse.jgit.errors.MissingObjectException;
 import org.eclipse.jgit.internal.JGitText;
 import org.eclipse.jgit.internal.storage.file.BitmapIndexImpl;
+import org.eclipse.jgit.internal.storage.file.BitmapIndexImpl.CompressedBitmap;
 import org.eclipse.jgit.internal.storage.file.PackBitmapIndex;
 import org.eclipse.jgit.internal.storage.file.PackBitmapIndexBuilder;
 import org.eclipse.jgit.internal.storage.file.PackBitmapIndexRemapper;
+import org.eclipse.jgit.internal.storage.pack.PackWriterBitmapWalker.AddUnseenToBitmapFilter;
 import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.lib.BitmapIndex.BitmapBuilder;
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.ObjectReader;
 import org.eclipse.jgit.lib.ProgressMonitor;
-import org.eclipse.jgit.lib.BitmapIndex.BitmapBuilder;
 import org.eclipse.jgit.revwalk.ObjectWalk;
 import org.eclipse.jgit.revwalk.RevCommit;
 import org.eclipse.jgit.revwalk.RevObject;
 import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.revwalk.filter.RevFilter;
+import org.eclipse.jgit.storage.pack.PackConfig;
 import org.eclipse.jgit.util.BlockList;
+import org.eclipse.jgit.util.SystemReader;
 
-/** Helper class for the PackWriter to select commits for pack index bitmaps. */
+import com.googlecode.javaewah.EWAHCompressedBitmap;
+
+/**
+ * Helper class for the {@link PackWriter} to select commits for which to build
+ * pack index bitmaps.
+ */
 class PackWriterBitmapPreparer {
 
-	private static final Comparator<BitmapBuilder> BUILDER_BY_CARDINALITY_DSC =
-			new Comparator<BitmapBuilder>() {
-		public int compare(BitmapBuilder a, BitmapBuilder b) {
-			return Integer.signum(b.cardinality() - a.cardinality());
+	private static final int DAY_IN_SECONDS = 24 * 60 * 60;
+
+	private static final Comparator<BitmapBuilderEntry> ORDER_BY_CARDINALITY = new Comparator<BitmapBuilderEntry>() {
+		public int compare(BitmapBuilderEntry a, BitmapBuilderEntry b) {
+			return Integer.signum(a.getBuilder().cardinality()
+					- b.getBuilder().cardinality());
 		}
 	};
 
@@ -93,12 +104,18 @@
 	private final BitmapIndexImpl commitBitmapIndex;
 	private final PackBitmapIndexRemapper bitmapRemapper;
 	private final BitmapIndexImpl bitmapIndex;
-	private final int minCommits = 100;
-	private final int maxCommits = 5000;
+
+	private final int contiguousCommitCount;
+	private final int recentCommitCount;
+	private final int recentCommitSpan;
+	private final int distantCommitSpan;
+	private final int excessiveBranchCount;
+	private final long inactiveBranchTimestamp;
 
 	PackWriterBitmapPreparer(ObjectReader reader,
 			PackBitmapIndexBuilder writeBitmaps, ProgressMonitor pm,
-			Set<? extends ObjectId> want) throws IOException {
+			Set<? extends ObjectId> want, PackConfig config)
+					throws IOException {
 		this.reader = reader;
 		this.writeBitmaps = writeBitmaps;
 		this.pm = pm;
@@ -107,208 +124,379 @@
 		this.bitmapRemapper = PackBitmapIndexRemapper.newPackBitmapIndex(
 				reader.getBitmapIndex(), writeBitmaps);
 		this.bitmapIndex = new BitmapIndexImpl(bitmapRemapper);
+		this.contiguousCommitCount = config.getBitmapContiguousCommitCount();
+		this.recentCommitCount = config.getBitmapRecentCommitCount();
+		this.recentCommitSpan = config.getBitmapRecentCommitSpan();
+		this.distantCommitSpan = config.getBitmapDistantCommitSpan();
+		this.excessiveBranchCount = config.getBitmapExcessiveBranchCount();
+		long now = SystemReader.getInstance().getCurrentTime();
+		long ageInSeconds = config.getBitmapInactiveBranchAgeInDays()
+				* DAY_IN_SECONDS;
+		this.inactiveBranchTimestamp = (now / 1000) - ageInSeconds;
 	}
 
-	Collection<BitmapCommit> doCommitSelection(int expectedNumCommits)
-			throws MissingObjectException, IncorrectObjectTypeException,
-			IOException {
+	/**
+	 * Returns the commit objects for which bitmap indices should be built.
+	 *
+	 * @param expectedCommitCount
+	 *            count of commits in the pack
+	 * @return commit objects for which bitmap indices should be built
+	 * @throws IncorrectObjectTypeException
+	 *             if any of the processed objects is not a commit
+	 * @throws IOException
+	 *             on errors reading pack or index files
+	 * @throws MissingObjectException
+	 *             if an expected object is missing
+	 */
+	Collection<BitmapCommit> selectCommits(int expectedCommitCount)
+			throws IncorrectObjectTypeException, IOException,
+			MissingObjectException {
+		/*
+		 * Thinking of bitmap indices as a cache, if we find bitmaps at or at a
+		 * close ancestor to 'old' and 'new' when calculating old..new, then all
+		 * objects can be calculated with minimal graph walking. A distribution
+		 * that favors creating bitmaps for the most recent commits maximizes
+		 * the cache hits for clients that are close to HEAD, which is the
+		 * majority of calculations performed.
+		 */
 		pm.beginTask(JGitText.get().selectingCommits, ProgressMonitor.UNKNOWN);
 		RevWalk rw = new RevWalk(reader);
 		rw.setRetainBody(false);
-		WalkResult result = findPaths(rw, expectedNumCommits);
+		CommitSelectionHelper selectionHelper = setupTipCommitBitmaps(rw,
+				expectedCommitCount);
 		pm.endTask();
 
-		int totCommits = result.commitsByOldest.length - result.commitStartPos;
+		int totCommits = selectionHelper.getCommitCount();
 		BlockList<BitmapCommit> selections = new BlockList<BitmapCommit>(
-				totCommits / minCommits + 1);
-		for (BitmapCommit reuse : result.reuse)
+				totCommits / recentCommitSpan + 1);
+		for (BitmapCommit reuse : selectionHelper.reusedCommits) {
 			selections.add(reuse);
+		}
 
 		if (totCommits == 0) {
-			for (AnyObjectId id : result.peeledWant)
+			for (AnyObjectId id : selectionHelper.peeledWants) {
 				selections.add(new BitmapCommit(id, false, 0));
+			}
 			return selections;
 		}
 
 		pm.beginTask(JGitText.get().selectingCommits, totCommits);
+		int totalWants = selectionHelper.peeledWants.size();
 
-		for (BitmapBuilder bitmapableCommits : result.paths) {
-			int cardinality = bitmapableCommits.cardinality();
+		for (BitmapBuilderEntry entry : selectionHelper.tipCommitBitmaps) {
+			BitmapBuilder bitmap = entry.getBuilder();
+			int cardinality = bitmap.cardinality();
 
-			List<List<BitmapCommit>> running = new ArrayList<
-					List<BitmapCommit>>();
+			// Within this branch, keep ordered lists of commits representing
+			// chains in its history, where each chain is a "sub-branch".
+			// Ordering commits by these chains makes for fewer differences
+			// between consecutive selected commits, which in turn provides
+			// better compression/on the run-length encoding of the XORs between
+			// them.
+			List<List<BitmapCommit>> chains =
+					new ArrayList<List<BitmapCommit>>();
+
+			// Mark the current branch as inactive if its tip commit isn't
+			// recent and there are an excessive number of branches, to
+			// prevent memory bloat of computing too many bitmaps for stale
+			// branches.
+			boolean isActiveBranch = true;
+			if (totalWants > excessiveBranchCount
+					&& !isRecentCommit(entry.getCommit())) {
+				isActiveBranch = false;
+			}
 
 			// Insert bitmaps at the offsets suggested by the
-			// nextSelectionDistance() heuristic.
+			// nextSelectionDistance() heuristic. Only reuse bitmaps created
+			// for more distant commits.
 			int index = -1;
-			int nextIn = nextSelectionDistance(0, cardinality);
-			int nextFlg = nextIn == maxCommits ? PackBitmapIndex.FLAG_REUSE : 0;
-			boolean mustPick = nextIn == 0;
-			for (RevCommit c : result) {
-				if (!bitmapableCommits.contains(c))
+			int nextIn = nextSpan(cardinality);
+			int nextFlg = nextIn == distantCommitSpan
+					? PackBitmapIndex.FLAG_REUSE : 0;
+
+			// For the current branch, iterate through all commits from oldest
+			// to newest.
+			for (RevCommit c : selectionHelper) {
+				// Optimization: if we have found all the commits for this
+				// branch, stop searching
+				int distanceFromTip = cardinality - index - 1;
+				if (distanceFromTip == 0) {
+					break;
+				}
+
+				// Ignore commits that are not in this branch
+				if (!bitmap.contains(c)) {
 					continue;
+				}
 
 				index++;
 				nextIn--;
 				pm.update(1);
 
-				// Always pick the items in want and prefer merge commits.
-				if (result.peeledWant.remove(c)) {
-					if (nextIn > 0)
+				// Always pick the items in wants, prefer merge commits.
+				if (selectionHelper.peeledWants.remove(c)) {
+					if (nextIn > 0) {
 						nextFlg = 0;
-				} else if (!mustPick && ((nextIn > 0)
-						|| (c.getParentCount() <= 1 && nextIn > -minCommits))) {
-					continue;
+					}
+				} else {
+					boolean stillInSpan = nextIn >= 0;
+					boolean isMergeCommit = c.getParentCount() > 1;
+					// Force selection if:
+					// a) we have exhausted the window looking for merges
+					// b) we are in the top commits of an active branch
+					// c) we are at a branch tip
+					boolean mustPick = (nextIn <= -recentCommitSpan)
+							|| (isActiveBranch
+									&& (distanceFromTip <= contiguousCommitCount))
+							|| (distanceFromTip == 1); // most recent commit
+					if (!mustPick && (stillInSpan || !isMergeCommit)) {
+						continue;
+					}
 				}
 
+				// This commit is selected.
+				// Calculate where to look for the next one.
 				int flags = nextFlg;
-				nextIn = nextSelectionDistance(index, cardinality);
-				nextFlg = nextIn == maxCommits ? PackBitmapIndex.FLAG_REUSE : 0;
-				mustPick = nextIn == 0;
+				nextIn = nextSpan(distanceFromTip);
+				nextFlg = nextIn == distantCommitSpan
+						? PackBitmapIndex.FLAG_REUSE : 0;
 
 				BitmapBuilder fullBitmap = commitBitmapIndex.newBitmapBuilder();
 				rw.reset();
 				rw.markStart(c);
-				for (AnyObjectId objectId : result.reuse)
-					rw.markUninteresting(rw.parseCommit(objectId));
-				rw.setRevFilter(
-						PackWriterBitmapWalker.newRevFilter(null, fullBitmap));
+				rw.setRevFilter(new AddUnseenToBitmapFilter(
+						selectionHelper.reusedCommitsBitmap, fullBitmap));
 
 				while (rw.next() != null) {
-					// Work is done in the RevFilter.
+					// The RevFilter adds the reachable commits from this
+					// selected commit to fullBitmap.
 				}
 
-				List<List<BitmapCommit>> matches = new ArrayList<
-						List<BitmapCommit>>();
-				for (List<BitmapCommit> list : running) {
-					BitmapCommit last = list.get(list.size() - 1);
-					if (fullBitmap.contains(last))
-						matches.add(list);
-				}
-
-				List<BitmapCommit> match;
-				if (matches.isEmpty()) {
-					match = new ArrayList<BitmapCommit>();
-					running.add(match);
-				} else {
-					match = matches.get(0);
-					// Append to longest
-					for (List<BitmapCommit> list : matches) {
-						if (list.size() > match.size())
-							match = list;
+				// Sort the commits by independent chains in this branch's
+				// history, yielding better compression when building bitmaps.
+				List<BitmapCommit> longestAncestorChain = null;
+				for (List<BitmapCommit> chain : chains) {
+					BitmapCommit mostRecentCommit = chain.get(chain.size() - 1);
+					if (fullBitmap.contains(mostRecentCommit)) {
+						if (longestAncestorChain == null
+								|| longestAncestorChain.size() < chain.size()) {
+							longestAncestorChain = chain;
+						}
 					}
 				}
-				match.add(new BitmapCommit(c, !match.isEmpty(), flags));
+
+				if (longestAncestorChain == null) {
+					longestAncestorChain = new ArrayList<BitmapCommit>();
+					chains.add(longestAncestorChain);
+				}
+				longestAncestorChain.add(new BitmapCommit(
+						c, !longestAncestorChain.isEmpty(), flags));
 				writeBitmaps.addBitmap(c, fullBitmap, 0);
 			}
 
-			for (List<BitmapCommit> list : running)
-				selections.addAll(list);
+			for (List<BitmapCommit> chain : chains) {
+				selections.addAll(chain);
+			}
 		}
 		writeBitmaps.clearBitmaps(); // Remove the temporary commit bitmaps.
 
 		// Add the remaining peeledWant
-		for (AnyObjectId remainingWant : result.peeledWant)
+		for (AnyObjectId remainingWant : selectionHelper.peeledWants) {
 			selections.add(new BitmapCommit(remainingWant, false, 0));
+		}
 
 		pm.endTask();
 		return selections;
 	}
 
-	private WalkResult findPaths(RevWalk rw, int expectedNumCommits)
-			throws MissingObjectException, IOException {
-		BitmapBuilder reuseBitmap = commitBitmapIndex.newBitmapBuilder();
-		List<BitmapCommit> reuse = new ArrayList<BitmapCommit>();
+	private boolean isRecentCommit(RevCommit revCommit) {
+		return revCommit.getCommitTime() > inactiveBranchTimestamp;
+	}
+
+	/**
+	 * A RevFilter that excludes the commits named in a bitmap from the walk.
+	 * <p>
+	 * If a commit is in {@code bitmap} then that commit is not emitted by the
+	 * walk and its parents are marked as SEEN so the walk can skip them.  The
+	 * bitmaps passed in have the property that the parents of any commit in
+	 * {@code bitmap} are also in {@code bitmap}, so marking the parents as
+	 * SEEN speeds up the RevWalk by saving it from walking down blind alleys
+	 * and does not change the commits emitted.
+	 */
+	private static class NotInBitmapFilter extends RevFilter {
+		private final BitmapBuilder bitmap;
+
+		NotInBitmapFilter(BitmapBuilder bitmap) {
+			this.bitmap = bitmap;
+		}
+
+		@Override
+		public final boolean include(RevWalk rw, RevCommit c) {
+			if (!bitmap.contains(c)) {
+				return true;
+			}
+			for (RevCommit p : c.getParents()) {
+				p.add(SEEN);
+			}
+			return false;
+		}
+
+		@Override
+		public final NotInBitmapFilter clone() {
+			throw new UnsupportedOperationException();
+		}
+
+		@Override
+		public final boolean requiresCommitBody() {
+			return false;
+		}
+	}
+
+	/**
+	 * For each of the {@code want}s, which represent the tip commit of each
+	 * branch, set up an initial {@link BitmapBuilder}. Reuse previously built
+	 * bitmaps if possible.
+	 *
+	 * @param rw
+	 *            a {@link RevWalk} to find reachable objects in this repository
+	 * @param expectedCommitCount
+	 *            expected count of commits. The actual count may be less due to
+	 *            unreachable garbage.
+	 * @return a {@link CommitSelectionHelper} containing bitmaps for the tip
+	 *         commits
+	 * @throws IncorrectObjectTypeException
+	 *             if any of the processed objects is not a commit
+	 * @throws IOException
+	 *             on errors reading pack or index files
+	 * @throws MissingObjectException
+	 *             if an expected object is missing
+	 */
+	private CommitSelectionHelper setupTipCommitBitmaps(RevWalk rw,
+			int expectedCommitCount) throws IncorrectObjectTypeException,
+					IOException, MissingObjectException {
+		BitmapBuilder reuse = commitBitmapIndex.newBitmapBuilder();
+		List<BitmapCommit> reuseCommits = new ArrayList<BitmapCommit>();
 		for (PackBitmapIndexRemapper.Entry entry : bitmapRemapper) {
-			if ((entry.getFlags() & FLAG_REUSE) != FLAG_REUSE)
+			// More recent commits did not have the reuse flag set, so skip them
+			if ((entry.getFlags() & FLAG_REUSE) != FLAG_REUSE) {
 				continue;
-
+			}
 			RevObject ro = rw.peel(rw.parseAny(entry));
-			if (ro instanceof RevCommit) {
-				RevCommit rc = (RevCommit) ro;
-				reuse.add(new BitmapCommit(rc, false, entry.getFlags()));
-				rw.markUninteresting(rc);
+			if (!(ro instanceof RevCommit)) {
+				continue;
+			}
 
+			RevCommit rc = (RevCommit) ro;
+			reuseCommits.add(new BitmapCommit(rc, false, entry.getFlags()));
+			if (!reuse.contains(rc)) {
 				EWAHCompressedBitmap bitmap = bitmapRemapper.ofObjectType(
 						bitmapRemapper.getBitmap(rc), Constants.OBJ_COMMIT);
-				writeBitmaps.addBitmap(rc, bitmap, 0);
-				reuseBitmap.add(rc, Constants.OBJ_COMMIT);
+				reuse.or(new CompressedBitmap(bitmap, commitBitmapIndex));
 			}
 		}
-		writeBitmaps.clearBitmaps(); // Remove temporary bitmaps
 
-		// Do a RevWalk by commit time descending. Keep track of all the paths
-		// from the wants.
-		List<BitmapBuilder> paths = new ArrayList<BitmapBuilder>(want.size());
+		// Add branch tips that are not represented in old bitmap indices. Set
+		// up the RevWalk to walk the new commits not in the old packs.
+		List<BitmapBuilderEntry> tipCommitBitmaps = new ArrayList<BitmapBuilderEntry>(
+				want.size());
 		Set<RevCommit> peeledWant = new HashSet<RevCommit>(want.size());
 		for (AnyObjectId objectId : want) {
 			RevObject ro = rw.peel(rw.parseAny(objectId));
-			if (ro instanceof RevCommit && !reuseBitmap.contains(ro)) {
-				RevCommit rc = (RevCommit) ro;
-				peeledWant.add(rc);
-				rw.markStart(rc);
-
-				BitmapBuilder bitmap = commitBitmapIndex.newBitmapBuilder();
-				bitmap.or(reuseBitmap);
-				bitmap.add(rc, Constants.OBJ_COMMIT);
-				paths.add(bitmap);
+			if (!(ro instanceof RevCommit) || reuse.contains(ro)) {
+				continue;
 			}
+
+			RevCommit rc = (RevCommit) ro;
+			peeledWant.add(rc);
+			rw.markStart(rc);
+
+			BitmapBuilder bitmap = commitBitmapIndex.newBitmapBuilder();
+			bitmap.addObject(rc, Constants.OBJ_COMMIT);
+			tipCommitBitmaps.add(new BitmapBuilderEntry(rc, bitmap));
 		}
 
-		// Update the paths from the wants and create a list of commits in
-		// reverse iteration order.
-		RevCommit[] commits = new RevCommit[expectedNumCommits];
+		// Create a list of commits in reverse order (older to newer).
+		// For each branch that contains the commit, mark its parents as being
+		// in the bitmap.
+		rw.setRevFilter(new NotInBitmapFilter(reuse));
+		RevCommit[] commits = new RevCommit[expectedCommitCount];
 		int pos = commits.length;
 		RevCommit rc;
-		while ((rc = rw.next()) != null) {
+		while ((rc = rw.next()) != null && pos > 0) {
 			commits[--pos] = rc;
-			for (BitmapBuilder path : paths) {
-				if (path.contains(rc)) {
-					for (RevCommit c : rc.getParents())
-						path.add(c, Constants.OBJ_COMMIT);
+			for (BitmapBuilderEntry entry : tipCommitBitmaps) {
+				BitmapBuilder bitmap = entry.getBuilder();
+				if (!bitmap.contains(rc)) {
+					continue;
+				}
+				for (RevCommit c : rc.getParents()) {
+					if (reuse.contains(c)) {
+						continue;
+					}
+					bitmap.addObject(c, Constants.OBJ_COMMIT);
 				}
 			}
-
 			pm.update(1);
 		}
 
-		// Remove the reused bitmaps from the paths
-		if (!reuse.isEmpty())
-			for (BitmapBuilder bitmap : paths)
-				bitmap.andNot(reuseBitmap);
-
-		// Sort the paths
-		List<BitmapBuilder> distinctPaths = new ArrayList<BitmapBuilder>(paths.size());
-		while (!paths.isEmpty()) {
-			Collections.sort(paths, BUILDER_BY_CARDINALITY_DSC);
-			BitmapBuilder largest = paths.remove(0);
-			distinctPaths.add(largest);
+		// Sort the tip commit bitmaps. Find the one containing the most
+		// commits, remove those commits from the remaining bitmaps, resort and
+		// repeat.
+		List<BitmapBuilderEntry> orderedTipCommitBitmaps = new ArrayList<>(
+				tipCommitBitmaps.size());
+		while (!tipCommitBitmaps.isEmpty()) {
+			BitmapBuilderEntry largest =
+					Collections.max(tipCommitBitmaps, ORDER_BY_CARDINALITY);
+			tipCommitBitmaps.remove(largest);
+			orderedTipCommitBitmaps.add(largest);
 
 			// Update the remaining paths, by removing the objects from
 			// the path that was just added.
-			for (int i = paths.size() - 1; i >= 0; i--)
-				paths.get(i).andNot(largest);
+			for (int i = tipCommitBitmaps.size() - 1; i >= 0; i--) {
+				tipCommitBitmaps.get(i).getBuilder()
+						.andNot(largest.getBuilder());
+			}
 		}
 
-		return new WalkResult(peeledWant, commits, pos, distinctPaths, reuse);
+		return new CommitSelectionHelper(peeledWant, commits, pos,
+				orderedTipCommitBitmaps, reuse, reuseCommits);
 	}
 
-	private int nextSelectionDistance(int idx, int cardinality) {
-		if (idx > cardinality)
+	/*-
+	 * Returns the desired distance to the next bitmap based on the distance
+	 * from the tip commit. Only differentiates recent from distant spans,
+	 * selectCommits() handles the contiguous commits at the tip for active
+	 * or inactive branches.
+	 *
+	 * A graph of this function looks like this, where
+	 * the X axis is the distance from the tip commit and the Y axis is the
+	 * bitmap selection distance.
+	 *
+	 * 5000                ____...
+	 *                    /
+	 *                  /
+	 *                /
+	 *              /
+	 *  100  _____/
+	 *       0  20100  25000
+	 *
+	 * Linear scaling between 20100 and 25000 prevents spans >100 for distances
+	 * <20000 (otherwise, a span of 5000 would be returned for a distance of
+	 * 21000, and the range 16000-20000 would have no selections).
+	 */
+	int nextSpan(int distanceFromTip) {
+		if (distanceFromTip < 0) {
 			throw new IllegalArgumentException();
-		int idxFromStart = cardinality - idx;
-		int mustRegionEnd = 100;
-		if (idxFromStart <= mustRegionEnd)
-			return 0;
+		}
 
 		// Commits more toward the start will have more bitmaps.
-		int minRegionEnd = 20000;
-		if (idxFromStart <= minRegionEnd)
-			return Math.min(idxFromStart - mustRegionEnd, minCommits);
+		if (distanceFromTip <= recentCommitCount) {
+			return recentCommitSpan;
+		}
 
-		// Commits more toward the end will have fewer.
-		int next = Math.min(idxFromStart - minRegionEnd, maxCommits);
-		return Math.max(next, minCommits);
+		int next = Math.min(distanceFromTip - recentCommitCount,
+				distantCommitSpan);
+		return Math.max(next, recentCommitSpan);
 	}
 
 	PackWriterBitmapWalker newBitmapWalker() {
@@ -316,12 +504,14 @@
 				new ObjectWalk(reader), bitmapIndex, null);
 	}
 
+	/**
+	 * A commit object for which a bitmap index should be built.
+	 */
 	static final class BitmapCommit extends ObjectId {
 		private final boolean reuseWalker;
 		private final int flags;
 
-		private BitmapCommit(
-				AnyObjectId objectId, boolean reuseWalker, int flags) {
+		BitmapCommit(AnyObjectId objectId, boolean reuseWalker, int flags) {
 			super(objectId);
 			this.reuseWalker = reuseWalker;
 			this.flags = flags;
@@ -336,24 +526,62 @@
 		}
 	}
 
-	private static final class WalkResult implements Iterable<RevCommit> {
-		private final Set<? extends ObjectId> peeledWant;
-		private final RevCommit[] commitsByOldest;
-		private final int commitStartPos;
-		private final List<BitmapBuilder> paths;
-		private final Iterable<BitmapCommit> reuse;
+	/**
+	 * A POJO representing a Pair<RevCommit, BitmapBuidler>.
+	 */
+	private static final class BitmapBuilderEntry {
+		private final RevCommit commit;
+		private final BitmapBuilder builder;
 
-		private WalkResult(Set<? extends ObjectId> peeledWant,
+		BitmapBuilderEntry(RevCommit commit, BitmapBuilder builder) {
+			this.commit = commit;
+			this.builder = builder;
+		}
+
+		RevCommit getCommit() {
+			return commit;
+		}
+
+		BitmapBuilder getBuilder() {
+			return builder;
+		}
+	}
+
+	/**
+	 * Container for state used in the first phase of selecting commits, which
+	 * walks all of the reachable commits via the branch tips (
+	 * {@code peeledWants}), stores them in {@code commitsByOldest}, and sets up
+	 * bitmaps for each branch tip ({@code tipCommitBitmaps}).
+	 * {@code commitsByOldest} is initialized with an expected size of all
+	 * commits, but may be smaller if some commits are unreachable, in which
+	 * case {@code commitStartPos} will contain a positive offset to the root
+	 * commit.
+	 */
+	private static final class CommitSelectionHelper implements Iterable<RevCommit> {
+		final Set<? extends ObjectId> peeledWants;
+		final List<BitmapBuilderEntry> tipCommitBitmaps;
+
+		final BitmapBuilder reusedCommitsBitmap;
+		final Iterable<BitmapCommit> reusedCommits;
+		final RevCommit[] commitsByOldest;
+		final int commitStartPos;
+
+		CommitSelectionHelper(Set<? extends ObjectId> peeledWant,
 				RevCommit[] commitsByOldest, int commitStartPos,
-				List<BitmapBuilder> paths, Iterable<BitmapCommit> reuse) {
-			this.peeledWant = peeledWant;
+				List<BitmapBuilderEntry> bitmapEntries,
+				BitmapBuilder reusedCommitsBitmap,
+				Iterable<BitmapCommit> reuse) {
+			this.peeledWants = peeledWant;
 			this.commitsByOldest = commitsByOldest;
 			this.commitStartPos = commitStartPos;
-			this.paths = paths;
-			this.reuse = reuse;
+			this.tipCommitBitmaps = bitmapEntries;
+			this.reusedCommitsBitmap = reusedCommitsBitmap;
+			this.reusedCommits = reuse;
 		}
 
 		public Iterator<RevCommit> iterator() {
+			// Member variables referenced by this iterator will have synthetic
+			// accessors generated for them if they are made private.
 			return new Iterator<RevCommit>() {
 				int pos = commitStartPos;
 
@@ -370,5 +598,9 @@
 				}
 			};
 		}
+
+		int getCommitCount() {
+			return commitsByOldest.length - commitStartPos;
+		}
 	}
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriterBitmapWalker.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriterBitmapWalker.java
index debb2f2..d9ac9ef 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriterBitmapWalker.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriterBitmapWalker.java
@@ -110,21 +110,32 @@
 		}
 
 		if (marked) {
-			BitmapRevFilter filter = newRevFilter(seen, bitmapResult);
-			walker.setRevFilter(filter);
+			if (seen == null) {
+				walker.setRevFilter(new AddToBitmapFilter(bitmapResult));
+			} else {
+				walker.setRevFilter(
+						new AddUnseenToBitmapFilter(seen, bitmapResult));
+			}
 
 			while (walker.next() != null) {
 				// Iterate through all of the commits. The BitmapRevFilter does
 				// the work.
+				//
+				// filter.include returns true for commits that do not have
+				// a bitmap in bitmapIndex and are not reachable from a
+				// bitmap in bitmapIndex encountered earlier in the walk.
+				// Thus the number of commits returned by next() measures how
+				// much history was traversed without being able to make use
+				// of bitmaps.
 				pm.update(1);
+				countOfBitmapIndexMisses++;
 			}
 
 			RevObject ro;
 			while ((ro = walker.nextObject()) != null) {
-				bitmapResult.add(ro, ro.getType());
+				bitmapResult.addObject(ro, ro.getType());
 				pm.update(1);
 			}
-			countOfBitmapIndexMisses += filter.getCountOfLoadedCommits();
 		}
 
 		return bitmapResult;
@@ -134,53 +145,105 @@
 		walker.reset();
 	}
 
-	static BitmapRevFilter newRevFilter(
-			final BitmapBuilder seen, final BitmapBuilder bitmapResult) {
-		if (seen != null) {
-			return new BitmapRevFilter() {
-				protected boolean load(RevCommit cmit) {
-					if (seen.contains(cmit))
-						return false;
-					return bitmapResult.add(cmit, Constants.OBJ_COMMIT);
-				}
-			};
+	/**
+	 * A RevFilter that adds the visited commits to {@code bitmap} as a side
+	 * effect.
+	 * <p>
+	 * When the walk hits a commit that is part of {@code bitmap}'s
+	 * BitmapIndex, that entire bitmap is ORed into {@code bitmap} and the
+	 * commit and its parents are marked as SEEN so that the walk does not
+	 * have to visit its ancestors.  This ensures the walk is very short if
+	 * there is good bitmap coverage.
+	 */
+	static class AddToBitmapFilter extends RevFilter {
+		private final BitmapBuilder bitmap;
+
+		AddToBitmapFilter(BitmapBuilder bitmap) {
+			this.bitmap = bitmap;
 		}
-		return new BitmapRevFilter() {
-			@Override
-			protected boolean load(RevCommit cmit) {
-				return bitmapResult.add(cmit, Constants.OBJ_COMMIT);
-			}
-		};
-	}
-
-	static abstract class BitmapRevFilter extends RevFilter {
-		private long countOfLoadedCommits;
-
-		protected abstract boolean load(RevCommit cmit);
 
 		@Override
 		public final boolean include(RevWalk walker, RevCommit cmit) {
-			if (load(cmit)) {
-				countOfLoadedCommits++;
+			Bitmap visitedBitmap;
+
+			if (bitmap.contains(cmit)) {
+				// already included
+			} else if ((visitedBitmap = bitmap.getBitmapIndex()
+					.getBitmap(cmit)) != null) {
+				bitmap.or(visitedBitmap);
+			} else {
+				bitmap.addObject(cmit, Constants.OBJ_COMMIT);
 				return true;
 			}
-			for (RevCommit p : cmit.getParents())
+
+			for (RevCommit p : cmit.getParents()) {
 				p.add(RevFlag.SEEN);
+			}
 			return false;
 		}
 
 		@Override
 		public final RevFilter clone() {
-			return this;
+			throw new UnsupportedOperationException();
 		}
 
 		@Override
 		public final boolean requiresCommitBody() {
 			return false;
 		}
+	}
 
-		long getCountOfLoadedCommits() {
-			return countOfLoadedCommits;
+	/**
+	 * A RevFilter that adds the visited commits to {@code bitmap} as a side
+	 * effect.
+	 * <p>
+	 * When the walk hits a commit that is part of {@code bitmap}'s
+	 * BitmapIndex, that entire bitmap is ORed into {@code bitmap} and the
+	 * commit and its parents are marked as SEEN so that the walk does not
+	 * have to visit its ancestors.  This ensures the walk is very short if
+	 * there is good bitmap coverage.
+	 * <p>
+	 * Commits named in {@code seen} are considered already seen.  If one is
+	 * encountered, that commit and its parents will be marked with the SEEN
+	 * flag to prevent the walk from visiting its ancestors.
+	 */
+	static class AddUnseenToBitmapFilter extends RevFilter {
+		private final BitmapBuilder seen;
+		private final BitmapBuilder bitmap;
+
+		AddUnseenToBitmapFilter(BitmapBuilder seen, BitmapBuilder bitmapResult) {
+			this.seen = seen;
+			this.bitmap = bitmapResult;
+		}
+
+		@Override
+		public final boolean include(RevWalk walker, RevCommit cmit) {
+			Bitmap visitedBitmap;
+
+			if (seen.contains(cmit) || bitmap.contains(cmit)) {
+				// already seen or included
+			} else if ((visitedBitmap = bitmap.getBitmapIndex()
+					.getBitmap(cmit)) != null) {
+				bitmap.or(visitedBitmap);
+			} else {
+				bitmap.addObject(cmit, Constants.OBJ_COMMIT);
+				return true;
+			}
+
+			for (RevCommit p : cmit.getParents()) {
+				p.add(RevFlag.SEEN);
+			}
+			return false;
+		}
+
+		@Override
+		public final RevFilter clone() {
+			throw new UnsupportedOperationException();
+		}
+
+		@Override
+		public final boolean requiresCommitBody() {
+			return false;
 		}
 	}
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/SymlinkTreeEntry.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/AlwaysFailUpdate.java
similarity index 60%
copy from org.eclipse.jgit/src/org/eclipse/jgit/lib/SymlinkTreeEntry.java
copy to org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/AlwaysFailUpdate.java
index c7e41bc..12ef873 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/SymlinkTreeEntry.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/AlwaysFailUpdate.java
@@ -1,6 +1,5 @@
 /*
- * Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com>
- * Copyright (C) 2006-2007, Shawn O. Pearce <spearce@spearce.org>
+ * 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
@@ -42,44 +41,58 @@
  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-package org.eclipse.jgit.lib;
+package org.eclipse.jgit.internal.storage.reftree;
 
-/**
- * A tree entry representing a symbolic link.
- *
- * Note. Java cannot really handle these as file system objects.
- *
- * @deprecated To look up information about a single path, use
- * {@link org.eclipse.jgit.treewalk.TreeWalk#forPath(Repository, String, org.eclipse.jgit.revwalk.RevTree)}.
- * To lookup information about multiple paths at once, use a
- * {@link org.eclipse.jgit.treewalk.TreeWalk} and obtain the current entry's
- * information from its getter methods.
- */
-@Deprecated
-public class SymlinkTreeEntry extends TreeEntry {
+import java.io.IOException;
 
-	/**
-	 * Construct a {@link SymlinkTreeEntry} with the specified name and SHA-1 in
-	 * the specified parent
-	 *
-	 * @param parent
-	 * @param id
-	 * @param nameUTF8
-	 */
-	public SymlinkTreeEntry(final Tree parent, final ObjectId id,
-			final byte[] nameUTF8) {
-		super(parent, id, nameUTF8);
+import org.eclipse.jgit.lib.ObjectIdRef;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.RefDatabase;
+import org.eclipse.jgit.lib.RefUpdate;
+import org.eclipse.jgit.lib.Repository;
+
+/** Update that always rejects with {@code LOCK_FAILURE}. */
+class AlwaysFailUpdate extends RefUpdate {
+	private final RefTreeDatabase refdb;
+
+	AlwaysFailUpdate(RefTreeDatabase refdb, String name) {
+		super(new ObjectIdRef.Unpeeled(Ref.Storage.NEW, name, null));
+		this.refdb = refdb;
+		setCheckConflicting(false);
 	}
 
-	public FileMode getMode() {
-		return FileMode.SYMLINK;
+	@Override
+	protected RefDatabase getRefDatabase() {
+		return refdb;
 	}
 
-	public String toString() {
-		final StringBuilder r = new StringBuilder();
-		r.append(ObjectId.toString(getId()));
-		r.append(" S "); //$NON-NLS-1$
-		r.append(getFullName());
-		return r.toString();
+	@Override
+	protected Repository getRepository() {
+		return refdb.getRepository();
+	}
+
+	@Override
+	protected boolean tryLock(boolean deref) throws IOException {
+		return false;
+	}
+
+	@Override
+	protected void unlock() {
+		// No locks are held here.
+	}
+
+	@Override
+	protected Result doUpdate(Result desiredResult) {
+		return Result.LOCK_FAILURE;
+	}
+
+	@Override
+	protected Result doDelete(Result desiredResult) {
+		return Result.LOCK_FAILURE;
+	}
+
+	@Override
+	protected Result doLink(String target) {
+		return Result.LOCK_FAILURE;
 	}
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/Command.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/Command.java
new file mode 100644
index 0000000..dd08375
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/Command.java
@@ -0,0 +1,316 @@
+/*
+ * 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.internal.storage.reftree;
+
+import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
+import static org.eclipse.jgit.lib.Constants.encode;
+import static org.eclipse.jgit.lib.FileMode.TYPE_GITLINK;
+import static org.eclipse.jgit.lib.FileMode.TYPE_SYMLINK;
+import static org.eclipse.jgit.lib.Ref.Storage.NETWORK;
+import static org.eclipse.jgit.transport.ReceiveCommand.Result.NOT_ATTEMPTED;
+import static org.eclipse.jgit.transport.ReceiveCommand.Result.REJECTED_OTHER_REASON;
+
+import java.io.IOException;
+
+import org.eclipse.jgit.annotations.Nullable;
+import org.eclipse.jgit.dircache.DirCacheEntry;
+import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.internal.JGitText;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectIdRef;
+import org.eclipse.jgit.lib.ObjectInserter;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.revwalk.RevObject;
+import org.eclipse.jgit.revwalk.RevTag;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.transport.ReceiveCommand;
+import org.eclipse.jgit.transport.ReceiveCommand.Result;
+
+/**
+ * Command to create, update or delete an entry inside a {@link RefTree}.
+ * <p>
+ * Unlike {@link ReceiveCommand} (which can only update a reference to an
+ * {@link ObjectId}), a RefTree Command can also create, modify or delete
+ * symbolic references to a target reference.
+ * <p>
+ * RefTree Commands may wrap a {@code ReceiveCommand} to allow callers to
+ * process an existing ReceiveCommand against a RefTree.
+ * <p>
+ * Commands should be passed into {@link RefTree#apply(java.util.Collection)}
+ * for processing.
+ */
+public class Command {
+	/**
+	 * Set unprocessed commands as failed due to transaction aborted.
+	 * <p>
+	 * If a command is still {@link Result#NOT_ATTEMPTED} it will be set to
+	 * {@link Result#REJECTED_OTHER_REASON}. If {@code why} is non-null its
+	 * contents will be used as the message for the first command status.
+	 *
+	 * @param commands
+	 *            commands to mark as failed.
+	 * @param why
+	 *            optional message to set on the first aborted command.
+	 */
+	public static void abort(Iterable<Command> commands, @Nullable String why) {
+		if (why == null || why.isEmpty()) {
+			why = JGitText.get().transactionAborted;
+		}
+		for (Command c : commands) {
+			if (c.getResult() == NOT_ATTEMPTED) {
+				c.setResult(REJECTED_OTHER_REASON, why);
+				why = JGitText.get().transactionAborted;
+			}
+		}
+	}
+
+	private final Ref oldRef;
+	private final Ref newRef;
+	private final ReceiveCommand cmd;
+	private Result result;
+
+	/**
+	 * Create a command to create, update or delete a reference.
+	 * <p>
+	 * At least one of {@code oldRef} or {@code newRef} must be supplied.
+	 *
+	 * @param oldRef
+	 *            expected value. Null if the ref should not exist.
+	 * @param newRef
+	 *            desired value, must be peeled if not null and not symbolic.
+	 *            Null to delete the ref.
+	 */
+	public Command(@Nullable Ref oldRef, @Nullable Ref newRef) {
+		this.oldRef = oldRef;
+		this.newRef = newRef;
+		this.cmd = null;
+		this.result = NOT_ATTEMPTED;
+
+		if (oldRef == null && newRef == null) {
+			throw new IllegalArgumentException();
+		}
+		if (newRef != null && !newRef.isPeeled() && !newRef.isSymbolic()) {
+			throw new IllegalArgumentException();
+		}
+		if (oldRef != null && newRef != null
+				&& !oldRef.getName().equals(newRef.getName())) {
+			throw new IllegalArgumentException();
+		}
+	}
+
+	/**
+	 * Construct a RefTree command wrapped around a ReceiveCommand.
+	 *
+	 * @param rw
+	 *            walk instance to peel the {@code newId}.
+	 * @param cmd
+	 *            command received from a push client.
+	 * @throws MissingObjectException
+	 *             {@code oldId} or {@code newId} is missing.
+	 * @throws IOException
+	 *             {@code oldId} or {@code newId} cannot be peeled.
+	 */
+	public Command(RevWalk rw, ReceiveCommand cmd)
+			throws MissingObjectException, IOException {
+		this.oldRef = toRef(rw, cmd.getOldId(), cmd.getRefName(), false);
+		this.newRef = toRef(rw, cmd.getNewId(), cmd.getRefName(), true);
+		this.cmd = cmd;
+	}
+
+	static Ref toRef(RevWalk rw, ObjectId id, String name,
+			boolean mustExist) throws MissingObjectException, IOException {
+		if (ObjectId.zeroId().equals(id)) {
+			return null;
+		}
+
+		try {
+			RevObject o = rw.parseAny(id);
+			if (o instanceof RevTag) {
+				RevObject p = rw.peel(o);
+				return new ObjectIdRef.PeeledTag(NETWORK, name, id, p.copy());
+			}
+			return new ObjectIdRef.PeeledNonTag(NETWORK, name, id);
+		} catch (MissingObjectException e) {
+			if (mustExist) {
+				throw e;
+			}
+			return new ObjectIdRef.Unpeeled(NETWORK, name, id);
+		}
+	}
+
+	/** @return name of the reference affected by this command. */
+	public String getRefName() {
+		if (cmd != null) {
+			return cmd.getRefName();
+		} else if (newRef != null) {
+			return newRef.getName();
+		}
+		return oldRef.getName();
+	}
+
+	/**
+	 * Set the result of this command.
+	 *
+	 * @param result
+	 *            the command result.
+	 */
+	public void setResult(Result result) {
+		setResult(result, null);
+	}
+
+	/**
+	 * Set the result of this command.
+	 *
+	 * @param result
+	 *            the command result.
+	 * @param why
+	 *            optional message explaining the result status.
+	 */
+	public void setResult(Result result, @Nullable String why) {
+		if (cmd != null) {
+			cmd.setResult(result, why);
+		} else {
+			this.result = result;
+		}
+	}
+
+	/** @return result of executing this command. */
+	public Result getResult() {
+		return cmd != null ? cmd.getResult() : result;
+	}
+
+	/** @return optional message explaining command failure. */
+	@Nullable
+	public String getMessage() {
+		return cmd != null ? cmd.getMessage() : null;
+	}
+
+	/**
+	 * Old peeled reference.
+	 *
+	 * @return the old reference; null if the command is creating the reference.
+	 */
+	@Nullable
+	public Ref getOldRef() {
+		return oldRef;
+	}
+
+	/**
+	 * New peeled reference.
+	 *
+	 * @return the new reference; null if the command is deleting the reference.
+	 */
+	@Nullable
+	public Ref getNewRef() {
+		return newRef;
+	}
+
+	@Override
+	public String toString() {
+		StringBuilder s = new StringBuilder();
+		append(s, oldRef, "CREATE"); //$NON-NLS-1$
+		s.append(' ');
+		append(s, newRef, "DELETE"); //$NON-NLS-1$
+		s.append(' ').append(getRefName());
+		s.append(' ').append(getResult());
+		if (getMessage() != null) {
+			s.append(' ').append(getMessage());
+		}
+		return s.toString();
+	}
+
+	private static void append(StringBuilder s, Ref r, String nullName) {
+		if (r == null) {
+			s.append(nullName);
+		} else if (r.isSymbolic()) {
+			s.append(r.getTarget().getName());
+		} else {
+			ObjectId id = r.getObjectId();
+			if (id != null) {
+				s.append(id.name());
+			}
+		}
+	}
+
+	/**
+	 * Check the entry is consistent with either the old or the new ref.
+	 *
+	 * @param entry
+	 *            current entry; null if the entry does not exist.
+	 * @return true if entry matches {@link #getOldRef()} or
+	 *         {@link #getNewRef()}; otherwise false.
+	 */
+	boolean checkRef(@Nullable DirCacheEntry entry) {
+		if (entry != null && entry.getRawMode() == 0) {
+			entry = null;
+		}
+		return check(entry, oldRef) || check(entry, newRef);
+	}
+
+	private static boolean check(@Nullable DirCacheEntry cur,
+			@Nullable Ref exp) {
+		if (cur == null) {
+			// Does not exist, ok if oldRef does not exist.
+			return exp == null;
+		} else if (exp == null) {
+			// Expected to not exist, but currently exists, fail.
+			return false;
+		}
+
+		if (exp.isSymbolic()) {
+			String dst = exp.getTarget().getName();
+			return cur.getRawMode() == TYPE_SYMLINK
+					&& cur.getObjectId().equals(symref(dst));
+		}
+
+		return cur.getRawMode() == TYPE_GITLINK
+				&& cur.getObjectId().equals(exp.getObjectId());
+	}
+
+	static ObjectId symref(String s) {
+		@SuppressWarnings("resource")
+		ObjectInserter.Formatter fmt = new ObjectInserter.Formatter();
+		return fmt.idFor(OBJ_BLOB, encode(s));
+	}
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/RefTree.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/RefTree.java
new file mode 100644
index 0000000..85690c8
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/RefTree.java
@@ -0,0 +1,411 @@
+/*
+ * 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.internal.storage.reftree;
+
+import static org.eclipse.jgit.lib.Constants.HEAD;
+import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
+import static org.eclipse.jgit.lib.Constants.R_REFS;
+import static org.eclipse.jgit.lib.Constants.encode;
+import static org.eclipse.jgit.lib.FileMode.GITLINK;
+import static org.eclipse.jgit.lib.FileMode.SYMLINK;
+import static org.eclipse.jgit.lib.FileMode.TYPE_GITLINK;
+import static org.eclipse.jgit.lib.FileMode.TYPE_SYMLINK;
+import static org.eclipse.jgit.lib.Ref.Storage.NEW;
+import static org.eclipse.jgit.lib.Ref.Storage.PACKED;
+import static org.eclipse.jgit.lib.RefDatabase.MAX_SYMBOLIC_REF_DEPTH;
+import static org.eclipse.jgit.transport.ReceiveCommand.Result.LOCK_FAILURE;
+import static org.eclipse.jgit.transport.ReceiveCommand.Result.REJECTED_OTHER_REASON;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.eclipse.jgit.annotations.Nullable;
+import org.eclipse.jgit.dircache.DirCache;
+import org.eclipse.jgit.dircache.DirCacheBuilder;
+import org.eclipse.jgit.dircache.DirCacheEditor;
+import org.eclipse.jgit.dircache.DirCacheEditor.DeletePath;
+import org.eclipse.jgit.dircache.DirCacheEditor.PathEdit;
+import org.eclipse.jgit.dircache.DirCacheEntry;
+import org.eclipse.jgit.errors.CorruptObjectException;
+import org.eclipse.jgit.errors.DirCacheNameConflictException;
+import org.eclipse.jgit.errors.IncorrectObjectTypeException;
+import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.internal.JGitText;
+import org.eclipse.jgit.lib.FileMode;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectIdRef;
+import org.eclipse.jgit.lib.ObjectInserter;
+import org.eclipse.jgit.lib.ObjectReader;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.lib.SymbolicRef;
+import org.eclipse.jgit.revwalk.RevTree;
+import org.eclipse.jgit.util.RawParseUtils;
+
+/**
+ * Tree of references in the reference graph.
+ * <p>
+ * The root corresponds to the {@code "refs/"} subdirectory, for example the
+ * default reference {@code "refs/heads/master"} is stored at path
+ * {@code "heads/master"} in a {@code RefTree}.
+ * <p>
+ * Normal references are stored as {@link FileMode#GITLINK} tree entries. The
+ * ObjectId in the tree entry is the ObjectId the reference refers to.
+ * <p>
+ * Symbolic references are stored as {@link FileMode#SYMLINK} entries, with the
+ * blob storing the name of the target reference.
+ * <p>
+ * Annotated tags also store the peeled object using a {@code GITLINK} entry
+ * with the suffix <code>" ^"</code> (space carrot), for example
+ * {@code "tags/v1.0"} stores the annotated tag object, while
+ * <code>"tags/v1.0 ^"</code> stores the commit the tag annotates.
+ * <p>
+ * {@code HEAD} is a special case and stored as {@code "..HEAD"}.
+ */
+public class RefTree {
+	/** Suffix applied to GITLINK to indicate its the peeled value of a tag. */
+	public static final String PEELED_SUFFIX = " ^"; //$NON-NLS-1$
+	static final String ROOT_DOTDOT = ".."; //$NON-NLS-1$
+
+	/**
+	 * Create an empty reference tree.
+	 *
+	 * @return a new empty reference tree.
+	 */
+	public static RefTree newEmptyTree() {
+		return new RefTree(DirCache.newInCore());
+	}
+
+	/**
+	 * Load a reference tree.
+	 *
+	 * @param reader
+	 *            reader to scan the reference tree with.
+	 * @param tree
+	 *            the tree to read.
+	 * @return the ref tree read from the commit.
+	 * @throws IOException
+	 *             the repository cannot be accessed through the reader.
+	 * @throws CorruptObjectException
+	 *             a tree object is corrupt and cannot be read.
+	 * @throws IncorrectObjectTypeException
+	 *             a tree object wasn't actually a tree.
+	 * @throws MissingObjectException
+	 *             a reference tree object doesn't exist.
+	 */
+	public static RefTree read(ObjectReader reader, RevTree tree)
+			throws MissingObjectException, IncorrectObjectTypeException,
+			CorruptObjectException, IOException {
+		return new RefTree(DirCache.read(reader, tree));
+	}
+
+	private DirCache contents;
+	private Map<ObjectId, String> pendingBlobs;
+
+	private RefTree(DirCache dc) {
+		this.contents = dc;
+	}
+
+	/**
+	 * Read one reference.
+	 * <p>
+	 * References are always returned peeled ({@link Ref#isPeeled()} is true).
+	 * If the reference points to an annotated tag, the returned reference will
+	 * be peeled and contain {@link Ref#getPeeledObjectId()}.
+	 * <p>
+	 * If the reference is a symbolic reference and the chain depth is less than
+	 * {@link org.eclipse.jgit.lib.RefDatabase#MAX_SYMBOLIC_REF_DEPTH} the
+	 * returned reference is resolved. If the chain depth is longer, the
+	 * symbolic reference is returned without resolving.
+	 *
+	 * @param reader
+	 *            to access objects necessary to read the requested reference.
+	 * @param name
+	 *            name of the reference to read.
+	 * @return the reference; null if it does not exist.
+	 * @throws IOException
+	 *             cannot read a symbolic reference target.
+	 */
+	@Nullable
+	public Ref exactRef(ObjectReader reader, String name) throws IOException {
+		Ref r = readRef(reader, name);
+		if (r == null) {
+			return null;
+		} else if (r.isSymbolic()) {
+			return resolve(reader, r, 0);
+		}
+
+		DirCacheEntry p = contents.getEntry(peeledPath(name));
+		if (p != null && p.getRawMode() == TYPE_GITLINK) {
+			return new ObjectIdRef.PeeledTag(PACKED, r.getName(),
+					r.getObjectId(), p.getObjectId());
+		}
+		return r;
+	}
+
+	private Ref readRef(ObjectReader reader, String name) throws IOException {
+		DirCacheEntry e = contents.getEntry(refPath(name));
+		return e != null ? toRef(reader, e, name) : null;
+	}
+
+	private Ref toRef(ObjectReader reader, DirCacheEntry e, String name)
+			throws IOException {
+		int mode = e.getRawMode();
+		if (mode == TYPE_GITLINK) {
+			ObjectId id = e.getObjectId();
+			return new ObjectIdRef.PeeledNonTag(PACKED, name, id);
+		}
+
+		if (mode == TYPE_SYMLINK) {
+			ObjectId id = e.getObjectId();
+			String n = pendingBlobs != null ? pendingBlobs.get(id) : null;
+			if (n == null) {
+				byte[] bin = reader.open(id, OBJ_BLOB).getCachedBytes();
+				n = RawParseUtils.decode(bin);
+			}
+			Ref dst = new ObjectIdRef.Unpeeled(NEW, n, null);
+			return new SymbolicRef(name, dst);
+		}
+
+		return null; // garbage file or something; not a reference.
+	}
+
+	private Ref resolve(ObjectReader reader, Ref ref, int depth)
+			throws IOException {
+		if (ref.isSymbolic() && depth < MAX_SYMBOLIC_REF_DEPTH) {
+			Ref r = readRef(reader, ref.getTarget().getName());
+			if (r == null) {
+				return ref;
+			}
+			Ref dst = resolve(reader, r, depth + 1);
+			return new SymbolicRef(ref.getName(), dst);
+		}
+		return ref;
+	}
+
+	/**
+	 * Attempt a batch of commands against this RefTree.
+	 * <p>
+	 * The batch is applied atomically, either all commands apply at once, or
+	 * they all reject and the RefTree is left unmodified.
+	 * <p>
+	 * On success (when this method returns {@code true}) the command results
+	 * are left as-is (probably {@code NOT_ATTEMPTED}). Result fields are set
+	 * only when this method returns {@code false} to indicate failure.
+	 *
+	 * @param cmdList
+	 *            to apply. All commands should still have result NOT_ATTEMPTED.
+	 * @return true if the commands applied; false if they were rejected.
+	 */
+	public boolean apply(Collection<Command> cmdList) {
+		try {
+			DirCacheEditor ed = contents.editor();
+			for (Command cmd : cmdList) {
+				if (!isValidRef(cmd)) {
+					cmd.setResult(REJECTED_OTHER_REASON,
+							JGitText.get().funnyRefname);
+					Command.abort(cmdList, null);
+					return false;
+				}
+				apply(ed, cmd);
+			}
+			ed.finish();
+			return true;
+		} catch (DirCacheNameConflictException e) {
+			String r1 = refName(e.getPath1());
+			String r2 = refName(e.getPath2());
+			for (Command cmd : cmdList) {
+				if (r1.equals(cmd.getRefName())
+						|| r2.equals(cmd.getRefName())) {
+					cmd.setResult(LOCK_FAILURE);
+					break;
+				}
+			}
+			Command.abort(cmdList, null);
+			return false;
+		} catch (LockFailureException e) {
+			Command.abort(cmdList, null);
+			return false;
+		}
+	}
+
+	private static boolean isValidRef(Command cmd) {
+		String n = cmd.getRefName();
+		return HEAD.equals(n) || Repository.isValidRefName(n);
+	}
+
+	private void apply(DirCacheEditor ed, final Command cmd) {
+		String path = refPath(cmd.getRefName());
+		Ref oldRef = cmd.getOldRef();
+		final Ref newRef = cmd.getNewRef();
+
+		if (newRef == null) {
+			checkRef(contents.getEntry(path), cmd);
+			ed.add(new DeletePath(path));
+			cleanupPeeledRef(ed, oldRef);
+			return;
+		}
+
+		if (newRef.isSymbolic()) {
+			final String dst = newRef.getTarget().getName();
+			ed.add(new PathEdit(path) {
+				@Override
+				public void apply(DirCacheEntry ent) {
+					checkRef(ent, cmd);
+					ObjectId id = Command.symref(dst);
+					ent.setFileMode(SYMLINK);
+					ent.setObjectId(id);
+					if (pendingBlobs == null) {
+						pendingBlobs = new HashMap<>(4);
+					}
+					pendingBlobs.put(id, dst);
+				}
+			}.setReplace(false));
+			cleanupPeeledRef(ed, oldRef);
+			return;
+		}
+
+		ed.add(new PathEdit(path) {
+			@Override
+			public void apply(DirCacheEntry ent) {
+				checkRef(ent, cmd);
+				ent.setFileMode(GITLINK);
+				ent.setObjectId(newRef.getObjectId());
+			}
+		}.setReplace(false));
+
+		if (newRef.getPeeledObjectId() != null) {
+			ed.add(new PathEdit(peeledPath(newRef.getName())) {
+				@Override
+				public void apply(DirCacheEntry ent) {
+					ent.setFileMode(GITLINK);
+					ent.setObjectId(newRef.getPeeledObjectId());
+				}
+			}.setReplace(false));
+		} else {
+			cleanupPeeledRef(ed, oldRef);
+		}
+	}
+
+	private static void checkRef(@Nullable DirCacheEntry ent, Command cmd) {
+		if (!cmd.checkRef(ent)) {
+			cmd.setResult(LOCK_FAILURE);
+			throw new LockFailureException();
+		}
+	}
+
+	private static void cleanupPeeledRef(DirCacheEditor ed, Ref ref) {
+		if (ref != null && !ref.isSymbolic()
+				&& (!ref.isPeeled() || ref.getPeeledObjectId() != null)) {
+			ed.add(new DeletePath(peeledPath(ref.getName())));
+		}
+	}
+
+	/**
+	 * Convert a path name in a RefTree to the reference name known by Git.
+	 *
+	 * @param path
+	 *            name read from the RefTree structure, for example
+	 *            {@code "heads/master"}.
+	 * @return reference name for the path, {@code "refs/heads/master"}.
+	 */
+	public static String refName(String path) {
+		if (path.startsWith(ROOT_DOTDOT)) {
+			return path.substring(2);
+		}
+		return R_REFS + path;
+	}
+
+	static String refPath(String name) {
+		if (name.startsWith(R_REFS)) {
+			return name.substring(R_REFS.length());
+		}
+		return ROOT_DOTDOT + name;
+	}
+
+	private static String peeledPath(String name) {
+		return refPath(name) + PEELED_SUFFIX;
+	}
+
+	/**
+	 * Write this reference tree.
+	 *
+	 * @param inserter
+	 *            inserter to use when writing trees to the object database.
+	 *            Caller is responsible for flushing the inserter before trying
+	 *            to read the objects, or exposing them through a reference.
+	 * @return the top level tree.
+	 * @throws IOException
+	 *             a tree could not be written.
+	 */
+	public ObjectId writeTree(ObjectInserter inserter) throws IOException {
+		if (pendingBlobs != null) {
+			for (String s : pendingBlobs.values()) {
+				inserter.insert(OBJ_BLOB, encode(s));
+			}
+			pendingBlobs = null;
+		}
+		return contents.writeTree(inserter);
+	}
+
+	/** @return a deep copy of this RefTree. */
+	public RefTree copy() {
+		RefTree r = new RefTree(DirCache.newInCore());
+		DirCacheBuilder b = r.contents.builder();
+		for (int i = 0; i < contents.getEntryCount(); i++) {
+			b.add(new DirCacheEntry(contents.getEntry(i)));
+		}
+		b.finish();
+		if (pendingBlobs != null) {
+			r.pendingBlobs = new HashMap<>(pendingBlobs);
+		}
+		return r;
+	}
+
+	private static class LockFailureException extends RuntimeException {
+		private static final long serialVersionUID = 1L;
+	}
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/RefTreeBatch.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/RefTreeBatch.java
new file mode 100644
index 0000000..a55a9f5
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/RefTreeBatch.java
@@ -0,0 +1,222 @@
+/*
+ * 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.internal.storage.reftree;
+
+import static org.eclipse.jgit.lib.Constants.OBJ_TREE;
+import static org.eclipse.jgit.transport.ReceiveCommand.Result.NOT_ATTEMPTED;
+import static org.eclipse.jgit.transport.ReceiveCommand.Result.OK;
+import static org.eclipse.jgit.transport.ReceiveCommand.Result.REJECTED_NONFASTFORWARD;
+import static org.eclipse.jgit.transport.ReceiveCommand.Result.REJECTED_OTHER_REASON;
+import static org.eclipse.jgit.transport.ReceiveCommand.Type.UPDATE;
+import static org.eclipse.jgit.transport.ReceiveCommand.Type.UPDATE_NONFASTFORWARD;
+
+import java.io.IOException;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.jgit.annotations.Nullable;
+import org.eclipse.jgit.internal.JGitText;
+import org.eclipse.jgit.lib.BatchRefUpdate;
+import org.eclipse.jgit.lib.CommitBuilder;
+import org.eclipse.jgit.lib.NullProgressMonitor;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectInserter;
+import org.eclipse.jgit.lib.ObjectReader;
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.lib.ProgressMonitor;
+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.eclipse.jgit.transport.ReceiveCommand;
+
+/** Batch update a {@link RefTreeDatabase}. */
+class RefTreeBatch extends BatchRefUpdate {
+	private final RefTreeDatabase refdb;
+	private Ref src;
+	private ObjectId parentCommitId;
+	private ObjectId parentTreeId;
+	private RefTree tree;
+	private PersonIdent author;
+	private ObjectId newCommitId;
+
+	RefTreeBatch(RefTreeDatabase refdb) {
+		super(refdb);
+		this.refdb = refdb;
+	}
+
+	@Override
+	public void execute(RevWalk rw, ProgressMonitor monitor)
+			throws IOException {
+		List<Command> todo = new ArrayList<>(getCommands().size());
+		for (ReceiveCommand c : getCommands()) {
+			if (!isAllowNonFastForwards()) {
+				if (c.getType() == UPDATE) {
+					c.updateType(rw);
+				}
+				if (c.getType() == UPDATE_NONFASTFORWARD) {
+					c.setResult(REJECTED_NONFASTFORWARD);
+					ReceiveCommand.abort(getCommands());
+					return;
+				}
+			}
+			todo.add(new Command(rw, c));
+		}
+		init(rw);
+		execute(rw, todo);
+	}
+
+	void init(RevWalk rw) throws IOException {
+		src = refdb.getBootstrap().exactRef(refdb.getTxnCommitted());
+		if (src != null && src.getObjectId() != null) {
+			RevCommit c = rw.parseCommit(src.getObjectId());
+			parentCommitId = c;
+			parentTreeId = c.getTree();
+			tree = RefTree.read(rw.getObjectReader(), c.getTree());
+		} else {
+			parentCommitId = ObjectId.zeroId();
+			parentTreeId = new ObjectInserter.Formatter()
+					.idFor(OBJ_TREE, new byte[] {});
+			tree = RefTree.newEmptyTree();
+		}
+	}
+
+	@Nullable
+	Ref exactRef(ObjectReader reader, String name) throws IOException {
+		return tree.exactRef(reader, name);
+	}
+
+	/**
+	 * Execute an update from {@link RefTreeUpdate} or {@link RefTreeRename}.
+	 *
+	 * @param rw
+	 *            current RevWalk handling the update or rename.
+	 * @param todo
+	 *            commands to execute. Must never be a bootstrap reference name.
+	 * @throws IOException
+	 *             the storage system is unable to read or write data.
+	 */
+	void execute(RevWalk rw, List<Command> todo) throws IOException {
+		for (Command c : todo) {
+			if (c.getResult() != NOT_ATTEMPTED) {
+				Command.abort(todo, null);
+				return;
+			}
+			if (refdb.conflictsWithBootstrap(c.getRefName())) {
+				c.setResult(REJECTED_OTHER_REASON, MessageFormat
+						.format(JGitText.get().invalidRefName, c.getRefName()));
+				Command.abort(todo, null);
+				return;
+			}
+		}
+
+		if (apply(todo) && newCommitId != null) {
+			commit(rw, todo);
+		}
+	}
+
+	private boolean apply(List<Command> todo) throws IOException {
+		if (!tree.apply(todo)) {
+			// apply set rejection information on commands.
+			return false;
+		}
+
+		Repository repo = refdb.getRepository();
+		try (ObjectInserter ins = repo.newObjectInserter()) {
+			CommitBuilder b = new CommitBuilder();
+			b.setTreeId(tree.writeTree(ins));
+			if (parentTreeId.equals(b.getTreeId())) {
+				for (Command c : todo) {
+					c.setResult(OK);
+				}
+				return true;
+			}
+			if (!parentCommitId.equals(ObjectId.zeroId())) {
+				b.setParentId(parentCommitId);
+			}
+
+			author = getRefLogIdent();
+			if (author == null) {
+				author = new PersonIdent(repo);
+			}
+			b.setAuthor(author);
+			b.setCommitter(author);
+			b.setMessage(getRefLogMessage());
+			newCommitId = ins.insert(b);
+			ins.flush();
+		}
+		return true;
+	}
+
+	private void commit(RevWalk rw, List<Command> todo) throws IOException {
+		ReceiveCommand commit = new ReceiveCommand(
+				parentCommitId, newCommitId,
+				refdb.getTxnCommitted());
+		updateBootstrap(rw, commit);
+
+		if (commit.getResult() == OK) {
+			for (Command c : todo) {
+				c.setResult(OK);
+			}
+		} else {
+			Command.abort(todo, commit.getResult().name());
+		}
+	}
+
+	private void updateBootstrap(RevWalk rw, ReceiveCommand commit)
+			throws IOException {
+		BatchRefUpdate u = refdb.getBootstrap().newBatchUpdate();
+		u.setAllowNonFastForwards(true);
+		u.setPushCertificate(getPushCertificate());
+		if (isRefLogDisabled()) {
+			u.disableRefLog();
+		} else {
+			u.setRefLogIdent(author);
+			u.setRefLogMessage(getRefLogMessage(), false);
+		}
+		u.addCommand(commit);
+		u.execute(rw, NullProgressMonitor.INSTANCE);
+	}
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/RefTreeDatabase.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/RefTreeDatabase.java
new file mode 100644
index 0000000..dc60311
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/RefTreeDatabase.java
@@ -0,0 +1,337 @@
+/*
+ * 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.internal.storage.reftree;
+
+import static org.eclipse.jgit.lib.Ref.Storage.LOOSE;
+import static org.eclipse.jgit.lib.Ref.Storage.PACKED;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.jgit.annotations.Nullable;
+import org.eclipse.jgit.lib.BatchRefUpdate;
+import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectIdRef;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.Ref.Storage;
+import org.eclipse.jgit.lib.RefDatabase;
+import org.eclipse.jgit.lib.RefRename;
+import org.eclipse.jgit.lib.RefUpdate;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.lib.SymbolicRef;
+import org.eclipse.jgit.revwalk.RevObject;
+import org.eclipse.jgit.revwalk.RevTag;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.util.RefList;
+import org.eclipse.jgit.util.RefMap;
+
+/**
+ * Reference database backed by a {@link RefTree}.
+ * <p>
+ * The storage for RefTreeDatabase has two parts. The main part is a native Git
+ * tree object stored under the {@code refs/txn} namespace. To avoid cycles,
+ * references to {@code refs/txn} are not stored in that tree object, but
+ * instead in a "bootstrap" layer, which is a separate {@link RefDatabase} such
+ * as {@link org.eclipse.jgit.internal.storage.file.RefDirectory} using local
+ * reference files inside of {@code $GIT_DIR/refs}.
+ */
+public class RefTreeDatabase extends RefDatabase {
+	private final Repository repo;
+	private final RefDatabase bootstrap;
+	private final String txnCommitted;
+
+	@Nullable
+	private final String txnNamespace;
+	private volatile Scanner.Result refs;
+
+	/**
+	 * Create a RefTreeDb for a repository.
+	 *
+	 * @param repo
+	 *            the repository using references in this database.
+	 * @param bootstrap
+	 *            bootstrap reference database storing the references that
+	 *            anchor the {@link RefTree}.
+	 */
+	public RefTreeDatabase(Repository repo, RefDatabase bootstrap) {
+		Config cfg = repo.getConfig();
+		String committed = cfg.getString("reftree", null, "committedRef"); //$NON-NLS-1$ //$NON-NLS-2$
+		if (committed == null || committed.isEmpty()) {
+			committed = "refs/txn/committed"; //$NON-NLS-1$
+		}
+
+		this.repo = repo;
+		this.bootstrap = bootstrap;
+		this.txnNamespace = initNamespace(committed);
+		this.txnCommitted = committed;
+	}
+
+	/**
+	 * Create a RefTreeDb for a repository.
+	 *
+	 * @param repo
+	 *            the repository using references in this database.
+	 * @param bootstrap
+	 *            bootstrap reference database storing the references that
+	 *            anchor the {@link RefTree}.
+	 * @param txnCommitted
+	 *            name of the bootstrap reference holding the committed RefTree.
+	 */
+	public RefTreeDatabase(Repository repo, RefDatabase bootstrap,
+			String txnCommitted) {
+		this.repo = repo;
+		this.bootstrap = bootstrap;
+		this.txnNamespace = initNamespace(txnCommitted);
+		this.txnCommitted = txnCommitted;
+	}
+
+	private static String initNamespace(String committed) {
+		int s = committed.lastIndexOf('/');
+		if (s < 0) {
+			return null;
+		}
+		return committed.substring(0, s + 1); // Keep trailing '/'.
+	}
+
+	Repository getRepository() {
+		return repo;
+	}
+
+	/**
+	 * @return the bootstrap reference database, which must be used to access
+	 *         {@link #getTxnCommitted()}, {@link #getTxnNamespace()}.
+	 */
+	public RefDatabase getBootstrap() {
+		return bootstrap;
+	}
+
+	/** @return name of bootstrap reference anchoring committed RefTree. */
+	public String getTxnCommitted() {
+		return txnCommitted;
+	}
+
+	/**
+	 * @return namespace used by bootstrap layer, e.g. {@code refs/txn/}.
+	 *         Always ends in {@code '/'}.
+	 */
+	@Nullable
+	public String getTxnNamespace() {
+		return txnNamespace;
+	}
+
+	@Override
+	public void create() throws IOException {
+		bootstrap.create();
+	}
+
+	@Override
+	public boolean performsAtomicTransactions() {
+		return true;
+	}
+
+	@Override
+	public void refresh() {
+		bootstrap.refresh();
+	}
+
+	@Override
+	public void close() {
+		refs = null;
+		bootstrap.close();
+	}
+
+	@Override
+	public Ref getRef(String name) throws IOException {
+		return findRef(getRefs(ALL), name);
+	}
+
+	@Override
+	public Ref exactRef(String name) throws IOException {
+		if (conflictsWithBootstrap(name)) {
+			return null;
+		}
+
+		boolean partial = false;
+		Ref src = bootstrap.exactRef(txnCommitted);
+		Scanner.Result c = refs;
+		if (c == null || !c.refTreeId.equals(idOf(src))) {
+			c = Scanner.scanRefTree(repo, src, prefixOf(name), false);
+			partial = true;
+		}
+
+		Ref r = c.all.get(name);
+		if (r != null && r.isSymbolic()) {
+			r = c.sym.get(name);
+			if (partial && r.getObjectId() == null) {
+				// Attempting exactRef("HEAD") with partial scan will leave
+				// an unresolved symref as its target e.g. refs/heads/master
+				// was not read by the partial scan. Scan everything instead.
+				return getRefs(ALL).get(name);
+			}
+		}
+		return r;
+	}
+
+	private static String prefixOf(String name) {
+		int s = name.lastIndexOf('/');
+		if (s >= 0) {
+			return name.substring(0, s);
+		}
+		return ""; //$NON-NLS-1$
+	}
+
+	@Override
+	public Map<String, Ref> getRefs(String prefix) throws IOException {
+		if (!prefix.isEmpty() && prefix.charAt(prefix.length() - 1) != '/') {
+			return new HashMap<>(0);
+		}
+
+		Ref src = bootstrap.exactRef(txnCommitted);
+		Scanner.Result c = refs;
+		if (c == null || !c.refTreeId.equals(idOf(src))) {
+			c = Scanner.scanRefTree(repo, src, prefix, true);
+			if (prefix.isEmpty()) {
+				refs = c;
+			}
+		}
+		return new RefMap(prefix, RefList.<Ref> emptyList(), c.all, c.sym);
+	}
+
+	private static ObjectId idOf(@Nullable Ref src) {
+		return src != null && src.getObjectId() != null
+				? src.getObjectId()
+				: ObjectId.zeroId();
+	}
+
+	@Override
+	public List<Ref> getAdditionalRefs() throws IOException {
+		return Collections.emptyList();
+	}
+
+	@Override
+	public Ref peel(Ref ref) throws IOException {
+		Ref i = ref.getLeaf();
+		ObjectId id = i.getObjectId();
+		if (i.isPeeled() || id == null) {
+			return ref;
+		}
+		try (RevWalk rw = new RevWalk(repo)) {
+			RevObject obj = rw.parseAny(id);
+			if (obj instanceof RevTag) {
+				ObjectId p = rw.peel(obj).copy();
+				i = new ObjectIdRef.PeeledTag(PACKED, i.getName(), id, p);
+			} else {
+				i = new ObjectIdRef.PeeledNonTag(PACKED, i.getName(), id);
+			}
+		}
+		return recreate(ref, i);
+	}
+
+	private static Ref recreate(Ref old, Ref leaf) {
+		if (old.isSymbolic()) {
+			Ref dst = recreate(old.getTarget(), leaf);
+			return new SymbolicRef(old.getName(), dst);
+		}
+		return leaf;
+	}
+
+	@Override
+	public boolean isNameConflicting(String name) throws IOException {
+		return conflictsWithBootstrap(name)
+				|| !getConflictingNames(name).isEmpty();
+	}
+
+	@Override
+	public BatchRefUpdate newBatchUpdate() {
+		return new RefTreeBatch(this);
+	}
+
+	@Override
+	public RefUpdate newUpdate(String name, boolean detach) throws IOException {
+		if (conflictsWithBootstrap(name)) {
+			return new AlwaysFailUpdate(this, name);
+		}
+
+		Ref r = exactRef(name);
+		if (r == null) {
+			r = new ObjectIdRef.Unpeeled(Storage.NEW, name, null);
+		}
+
+		boolean detaching = detach && r.isSymbolic();
+		if (detaching) {
+			r = new ObjectIdRef.Unpeeled(LOOSE, name, r.getObjectId());
+		}
+
+		RefTreeUpdate u = new RefTreeUpdate(this, r);
+		if (detaching) {
+			u.setDetachingSymbolicRef();
+		}
+		return u;
+	}
+
+	@Override
+	public RefRename newRename(String fromName, String toName)
+			throws IOException {
+		RefUpdate from = newUpdate(fromName, true);
+		RefUpdate to = newUpdate(toName, true);
+		return new RefTreeRename(this, from, to);
+	}
+
+	boolean conflictsWithBootstrap(String name) {
+		if (txnNamespace != null && name.startsWith(txnNamespace)) {
+			return true;
+		} else if (txnCommitted.equals(name)) {
+			return true;
+		} else if (name.length() > txnCommitted.length()
+				&& name.charAt(txnCommitted.length()) == '/'
+				&& name.startsWith(txnCommitted)) {
+			return true;
+		}
+		return false;
+	}
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/RefTreeNames.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/RefTreeNames.java
new file mode 100644
index 0000000..239a745
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/RefTreeNames.java
@@ -0,0 +1,124 @@
+/*
+ * 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.internal.storage.reftree;
+
+import static org.eclipse.jgit.lib.RefDatabase.ALL;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.RefDatabase;
+
+/** Magic reference name logic for RefTrees. */
+public class RefTreeNames {
+	/**
+	 * Suffix used on a {@link RefTreeDatabase#getTxnNamespace()} for user data.
+	 * <p>
+	 * A {@link RefTreeDatabase}'s namespace may include a subspace (e.g.
+	 * {@code "refs/txn/stage/"}) containing commit objects from the usual user
+	 * portion of the repository (e.g. {@code "refs/heads/"}). These should be
+	 * packed by the garbage collector alongside other user content rather than
+	 * with the RefTree.
+	 */
+	private static final String STAGE = "stage/"; //$NON-NLS-1$
+
+	/**
+	 * Determine if the reference is likely to be a RefTree.
+	 *
+	 * @param refdb
+	 *            database instance.
+	 * @param ref
+	 *            reference name.
+	 * @return {@code true} if the reference is a RefTree.
+	 */
+	public static boolean isRefTree(RefDatabase refdb, String ref) {
+		if (refdb instanceof RefTreeDatabase) {
+			RefTreeDatabase b = (RefTreeDatabase) refdb;
+			if (ref.equals(b.getTxnCommitted())) {
+				return true;
+			}
+
+			String namespace = b.getTxnNamespace();
+			if (namespace != null
+					&& ref.startsWith(namespace)
+					&& !ref.startsWith(namespace + STAGE)) {
+				return true;
+			}
+		}
+		return false;
+	}
+
+	/**
+	 * Snapshot all references from a RefTreeDatabase and its bootstrap.
+	 * <p>
+	 * There may be name conflicts with multiple {@link Ref} objects containing
+	 * the same name in the returned collection.
+	 *
+	 * @param refdb
+	 *            database instance.
+	 * @return all known references.
+	 * @throws IOException
+	 *             references cannot be enumerated.
+	 */
+	public static Collection<Ref> allRefs(RefDatabase refdb)
+			throws IOException {
+		Collection<Ref> refs = refdb.getRefs(ALL).values();
+		if (!(refdb instanceof RefTreeDatabase)) {
+			return refs;
+		}
+
+		RefDatabase bootstrap = ((RefTreeDatabase) refdb).getBootstrap();
+		Collection<Ref> br = bootstrap.getRefs(ALL).values();
+		List<Ref> all = new ArrayList<>(refs.size() + br.size());
+		all.addAll(refs);
+		all.addAll(br);
+		return all;
+	}
+
+	private RefTreeNames() {
+	}
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/RefTreeRename.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/RefTreeRename.java
new file mode 100644
index 0000000..5fd7ecd
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/RefTreeRename.java
@@ -0,0 +1,121 @@
+/*
+ * 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.internal.storage.reftree;
+
+import static org.eclipse.jgit.lib.Constants.HEAD;
+import static org.eclipse.jgit.lib.RefUpdate.Result.REJECTED;
+import static org.eclipse.jgit.lib.RefUpdate.Result.RENAMED;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectIdRef;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.RefRename;
+import org.eclipse.jgit.lib.RefUpdate;
+import org.eclipse.jgit.lib.RefUpdate.Result;
+import org.eclipse.jgit.lib.SymbolicRef;
+import org.eclipse.jgit.revwalk.RevWalk;
+
+/** Single reference rename to {@link RefTreeDatabase}. */
+class RefTreeRename extends RefRename {
+	private final RefTreeDatabase refdb;
+
+	RefTreeRename(RefTreeDatabase refdb, RefUpdate src, RefUpdate dst) {
+		super(src, dst);
+		this.refdb = refdb;
+	}
+
+	@Override
+	protected Result doRename() throws IOException {
+		try (RevWalk rw = new RevWalk(refdb.getRepository())) {
+			RefTreeBatch batch = new RefTreeBatch(refdb);
+			batch.setRefLogIdent(getRefLogIdent());
+			batch.setRefLogMessage(getRefLogMessage(), false);
+			batch.init(rw);
+
+			Ref head = batch.exactRef(rw.getObjectReader(), HEAD);
+			Ref oldRef = batch.exactRef(rw.getObjectReader(), source.getName());
+			if (oldRef == null) {
+				return REJECTED;
+			}
+
+			Ref newRef = asNew(oldRef);
+			List<Command> mv = new ArrayList<>(3);
+			mv.add(new Command(oldRef, null));
+			mv.add(new Command(null, newRef));
+			if (head != null && head.isSymbolic()
+					&& head.getTarget().getName().equals(oldRef.getName())) {
+				mv.add(new Command(
+					head,
+					new SymbolicRef(head.getName(), newRef)));
+			}
+			batch.execute(rw, mv);
+			return RefTreeUpdate.translate(mv.get(1).getResult(), RENAMED);
+		}
+	}
+
+	private Ref asNew(Ref src) {
+		String name = destination.getName();
+		if (src.isSymbolic()) {
+			return new SymbolicRef(name, src.getTarget());
+		}
+
+		ObjectId peeled = src.getPeeledObjectId();
+		if (peeled != null) {
+			return new ObjectIdRef.PeeledTag(
+					src.getStorage(),
+					name,
+					src.getObjectId(),
+					peeled);
+		}
+
+		return new ObjectIdRef.PeeledNonTag(
+				src.getStorage(),
+				name,
+				src.getObjectId());
+	}
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/RefTreeUpdate.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/RefTreeUpdate.java
new file mode 100644
index 0000000..8829c11
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/RefTreeUpdate.java
@@ -0,0 +1,176 @@
+/*
+ * 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.internal.storage.reftree;
+
+import static org.eclipse.jgit.lib.Ref.Storage.LOOSE;
+import static org.eclipse.jgit.lib.Ref.Storage.NEW;
+
+import java.io.IOException;
+import java.util.Collections;
+
+import org.eclipse.jgit.annotations.Nullable;
+import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectIdRef;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.RefDatabase;
+import org.eclipse.jgit.lib.RefUpdate;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.lib.SymbolicRef;
+import org.eclipse.jgit.revwalk.RevObject;
+import org.eclipse.jgit.revwalk.RevTag;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.transport.ReceiveCommand;
+
+/** Single reference update to {@link RefTreeDatabase}. */
+class RefTreeUpdate extends RefUpdate {
+	private final RefTreeDatabase refdb;
+	private RevWalk rw;
+	private RefTreeBatch batch;
+	private Ref oldRef;
+
+	RefTreeUpdate(RefTreeDatabase refdb, Ref ref) {
+		super(ref);
+		this.refdb = refdb;
+		setCheckConflicting(false); // Done automatically by doUpdate.
+	}
+
+	@Override
+	protected RefDatabase getRefDatabase() {
+		return refdb;
+	}
+
+	@Override
+	protected Repository getRepository() {
+		return refdb.getRepository();
+	}
+
+	@Override
+	protected boolean tryLock(boolean deref) throws IOException {
+		rw = new RevWalk(getRepository());
+		batch = new RefTreeBatch(refdb);
+		batch.init(rw);
+		oldRef = batch.exactRef(rw.getObjectReader(), getName());
+		if (oldRef != null && oldRef.getObjectId() != null) {
+			setOldObjectId(oldRef.getObjectId());
+		} else if (oldRef == null && getExpectedOldObjectId() != null) {
+			setOldObjectId(ObjectId.zeroId());
+		}
+		return true;
+	}
+
+	@Override
+	protected void unlock() {
+		batch = null;
+		if (rw != null) {
+			rw.close();
+			rw = null;
+		}
+	}
+
+	@Override
+	protected Result doUpdate(Result desiredResult) throws IOException {
+		return run(newRef(getName(), getNewObjectId()), desiredResult);
+	}
+
+	private Ref newRef(String name, ObjectId id)
+			throws MissingObjectException, IOException {
+		RevObject o = rw.parseAny(id);
+		if (o instanceof RevTag) {
+			RevObject p = rw.peel(o);
+			return new ObjectIdRef.PeeledTag(LOOSE, name, id, p.copy());
+		}
+		return new ObjectIdRef.PeeledNonTag(LOOSE, name, id);
+	}
+
+	@Override
+	protected Result doDelete(Result desiredResult) throws IOException {
+		return run(null, desiredResult);
+	}
+
+	@Override
+	protected Result doLink(String target) throws IOException {
+		Ref dst = new ObjectIdRef.Unpeeled(NEW, target, null);
+		SymbolicRef n = new SymbolicRef(getName(), dst);
+		Result desiredResult = getRef().getStorage() == NEW
+			? Result.NEW
+			: Result.FORCED;
+		return run(n, desiredResult);
+	}
+
+	private Result run(@Nullable Ref newRef, Result desiredResult)
+			throws IOException {
+		Command c = new Command(oldRef, newRef);
+		batch.setRefLogIdent(getRefLogIdent());
+		batch.setRefLogMessage(getRefLogMessage(), isRefLogIncludingResult());
+		batch.execute(rw, Collections.singletonList(c));
+		return translate(c.getResult(), desiredResult);
+	}
+
+	static Result translate(ReceiveCommand.Result r, Result desiredResult) {
+		switch (r) {
+		case OK:
+			return desiredResult;
+
+		case LOCK_FAILURE:
+			return Result.LOCK_FAILURE;
+
+		case NOT_ATTEMPTED:
+			return Result.NOT_ATTEMPTED;
+
+		case REJECTED_MISSING_OBJECT:
+			return Result.IO_FAILURE;
+
+		case REJECTED_CURRENT_BRANCH:
+			return Result.REJECTED_CURRENT_BRANCH;
+
+		case REJECTED_OTHER_REASON:
+		case REJECTED_NOCREATE:
+		case REJECTED_NODELETE:
+		case REJECTED_NONFASTFORWARD:
+		default:
+			return Result.REJECTED;
+		}
+	}
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/Scanner.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/Scanner.java
new file mode 100644
index 0000000..d383abf
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/Scanner.java
@@ -0,0 +1,286 @@
+/*
+ * 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.internal.storage.reftree;
+
+import static org.eclipse.jgit.lib.RefDatabase.MAX_SYMBOLIC_REF_DEPTH;
+import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
+import static org.eclipse.jgit.lib.Constants.R_REFS;
+import static org.eclipse.jgit.lib.Constants.encode;
+import static org.eclipse.jgit.lib.FileMode.TYPE_GITLINK;
+import static org.eclipse.jgit.lib.FileMode.TYPE_SYMLINK;
+import static org.eclipse.jgit.lib.FileMode.TYPE_TREE;
+import static org.eclipse.jgit.lib.Ref.Storage.NEW;
+import static org.eclipse.jgit.lib.Ref.Storage.PACKED;
+
+import java.io.IOException;
+
+import org.eclipse.jgit.annotations.Nullable;
+import org.eclipse.jgit.errors.IncorrectObjectTypeException;
+import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectIdRef;
+import org.eclipse.jgit.lib.ObjectReader;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.lib.SymbolicRef;
+import org.eclipse.jgit.revwalk.RevTree;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.treewalk.AbstractTreeIterator;
+import org.eclipse.jgit.treewalk.CanonicalTreeParser;
+import org.eclipse.jgit.treewalk.TreeWalk;
+import org.eclipse.jgit.util.Paths;
+import org.eclipse.jgit.util.RawParseUtils;
+import org.eclipse.jgit.util.RefList;
+
+/** A tree parser that extracts references from a {@link RefTree}. */
+class Scanner {
+	private static final int MAX_SYMLINK_BYTES = 10 << 10;
+	private static final byte[] BINARY_R_REFS = encode(R_REFS);
+	private static final byte[] REFS_DOT_DOT = encode("refs/.."); //$NON-NLS-1$
+
+	static class Result {
+		final ObjectId refTreeId;
+		final RefList<Ref> all;
+		final RefList<Ref> sym;
+
+		Result(ObjectId id, RefList<Ref> all, RefList<Ref> sym) {
+			this.refTreeId = id;
+			this.all = all;
+			this.sym = sym;
+		}
+	}
+
+	/**
+	 * Scan a {@link RefTree} and parse entries into {@link Ref} instances.
+	 *
+	 * @param repo
+	 *            source repository containing the commit and tree objects that
+	 *            make up the RefTree.
+	 * @param src
+	 *            bootstrap reference such as {@code refs/txn/committed} to read
+	 *            the reference tree tip from. The current ObjectId will be
+	 *            included in {@link Result#refTreeId}.
+	 * @param prefix
+	 *            if non-empty a reference prefix to scan only a subdirectory.
+	 *            For example {@code prefix = "refs/heads/"} will limit the scan
+	 *            to only the {@code "heads"} directory of the RefTree, avoiding
+	 *            other directories like {@code "tags"}. Empty string reads all
+	 *            entries in the RefTree.
+	 * @param recursive
+	 *            if true recurse into subdirectories of the reference tree;
+	 *            false to read only one level. Callers may use false during an
+	 *            implementation of {@code exactRef(String)} where only one
+	 *            reference is needed out of a specific subtree.
+	 * @return sorted list of references after parsing.
+	 * @throws IOException
+	 *             tree cannot be accessed from the repository.
+	 */
+	static Result scanRefTree(Repository repo, @Nullable Ref src, String prefix,
+			boolean recursive) throws IOException {
+		RefList.Builder<Ref> all = new RefList.Builder<>();
+		RefList.Builder<Ref> sym = new RefList.Builder<>();
+
+		ObjectId srcId;
+		if (src != null && src.getObjectId() != null) {
+			try (ObjectReader reader = repo.newObjectReader()) {
+				srcId = src.getObjectId();
+				scan(reader, srcId, prefix, recursive, all, sym);
+			}
+		} else {
+			srcId = ObjectId.zeroId();
+		}
+
+		RefList<Ref> aList = all.toRefList();
+		for (int idx = 0; idx < sym.size();) {
+			Ref s = sym.get(idx);
+			Ref r = resolve(s, 0, aList);
+			if (r != null) {
+				sym.set(idx++, r);
+			} else {
+				// Remove broken symbolic reference, they don't exist.
+				sym.remove(idx);
+				int rm = aList.find(s.getName());
+				if (0 <= rm) {
+					aList = aList.remove(rm);
+				}
+			}
+		}
+		return new Result(srcId, aList, sym.toRefList());
+	}
+
+	private static void scan(ObjectReader reader, AnyObjectId srcId,
+			String prefix, boolean recursive,
+			RefList.Builder<Ref> all, RefList.Builder<Ref> sym)
+					throws IncorrectObjectTypeException, IOException {
+		CanonicalTreeParser p = createParserAtPath(reader, srcId, prefix);
+		if (p == null) {
+			return;
+		}
+
+		while (!p.eof()) {
+			int mode = p.getEntryRawMode();
+			if (mode == TYPE_TREE) {
+				if (recursive) {
+					p = p.createSubtreeIterator(reader);
+				} else {
+					p = p.next();
+				}
+				continue;
+			}
+
+			if (!curElementHasPeelSuffix(p)) {
+				Ref r = toRef(reader, mode, p);
+				if (r != null) {
+					all.add(r);
+					if (r.isSymbolic()) {
+						sym.add(r);
+					}
+				}
+			} else if (mode == TYPE_GITLINK) {
+				peel(all, p);
+			}
+			p = p.next();
+		}
+	}
+
+	private static CanonicalTreeParser createParserAtPath(ObjectReader reader,
+			AnyObjectId srcId, String prefix) throws IOException {
+		ObjectId root = toTree(reader, srcId);
+		if (prefix.isEmpty()) {
+			return new CanonicalTreeParser(BINARY_R_REFS, reader, root);
+		}
+
+		String dir = RefTree.refPath(Paths.stripTrailingSeparator(prefix));
+		TreeWalk tw = TreeWalk.forPath(reader, dir, root);
+		if (tw == null || !tw.isSubtree()) {
+			return null;
+		}
+
+		ObjectId id = tw.getObjectId(0);
+		return new CanonicalTreeParser(encode(prefix), reader, id);
+	}
+
+	private static Ref resolve(Ref ref, int depth, RefList<Ref> refs)
+			throws IOException {
+		if (!ref.isSymbolic()) {
+			return ref;
+		} else if (MAX_SYMBOLIC_REF_DEPTH <= depth) {
+			return null;
+		}
+
+		Ref r = refs.get(ref.getTarget().getName());
+		if (r == null) {
+			return ref;
+		}
+
+		Ref dst = resolve(r, depth + 1, refs);
+		if (dst == null) {
+			return null;
+		}
+		return new SymbolicRef(ref.getName(), dst);
+	}
+
+	@SuppressWarnings("resource")
+	private static RevTree toTree(ObjectReader reader, AnyObjectId id)
+			throws IOException {
+		return new RevWalk(reader).parseTree(id);
+	}
+
+	private static boolean curElementHasPeelSuffix(AbstractTreeIterator itr) {
+		int n = itr.getEntryPathLength();
+		byte[] c = itr.getEntryPathBuffer();
+		return n > 2 && c[n - 2] == ' ' && c[n - 1] == '^';
+	}
+
+	private static void peel(RefList.Builder<Ref> all, CanonicalTreeParser p) {
+		String name = refName(p, true);
+		for (int idx = all.size() - 1; 0 <= idx; idx--) {
+			Ref r = all.get(idx);
+			int cmp = r.getName().compareTo(name);
+			if (cmp == 0) {
+				all.set(idx, new ObjectIdRef.PeeledTag(PACKED, r.getName(),
+						r.getObjectId(), p.getEntryObjectId()));
+				break;
+			} else if (cmp < 0) {
+				// Stray peeled name without matching base name; skip entry.
+				break;
+			}
+		}
+	}
+
+	private static Ref toRef(ObjectReader reader, int mode,
+			CanonicalTreeParser p) throws IOException {
+		if (mode == TYPE_GITLINK) {
+			String name = refName(p, false);
+			ObjectId id = p.getEntryObjectId();
+			return new ObjectIdRef.PeeledNonTag(PACKED, name, id);
+
+		} else if (mode == TYPE_SYMLINK) {
+			ObjectId id = p.getEntryObjectId();
+			byte[] bin = reader.open(id, OBJ_BLOB)
+					.getCachedBytes(MAX_SYMLINK_BYTES);
+			String dst = RawParseUtils.decode(bin);
+			Ref trg = new ObjectIdRef.Unpeeled(NEW, dst, null);
+			String name = refName(p, false);
+			return new SymbolicRef(name, trg);
+		}
+		return null;
+	}
+
+	private static String refName(CanonicalTreeParser p, boolean peel) {
+		byte[] buf = p.getEntryPathBuffer();
+		int len = p.getEntryPathLength();
+		if (peel) {
+			len -= 2;
+		}
+		int ptr = 0;
+		if (RawParseUtils.match(buf, ptr, REFS_DOT_DOT) > 0) {
+			ptr = 7;
+		}
+		return RawParseUtils.decode(buf, ptr, len);
+	}
+
+	private Scanner() {
+	}
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BaseRepositoryBuilder.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BaseRepositoryBuilder.java
index 45dd7ee..670f9a9 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BaseRepositoryBuilder.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BaseRepositoryBuilder.java
@@ -109,7 +109,8 @@
 
 		int pathStart = 8;
 		int lineEnd = RawParseUtils.nextLF(content, pathStart);
-		if (content[lineEnd - 1] == '\n')
+		while (content[lineEnd - 1] == '\n' ||
+		       (content[lineEnd - 1] == '\r' && SystemReader.getInstance().isWindows()))
 			lineEnd--;
 		if (lineEnd == pathStart)
 			throw new IOException(MessageFormat.format(
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BitmapIndex.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BitmapIndex.java
index 3c0e2c1..9ddff25 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BitmapIndex.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BitmapIndex.java
@@ -125,7 +125,9 @@
 		 * @param type
 		 *            the Git object type. See {@link Constants}.
 		 * @return true if the value was not contained or able to be loaded.
+		 * @deprecated use {@link #or} or {@link #addObject} instead.
 		 */
+		@Deprecated
 		boolean add(AnyObjectId objectId, int type);
 
 		/**
@@ -138,6 +140,18 @@
 		boolean contains(AnyObjectId objectId);
 
 		/**
+		 * Adds the id to the bitmap.
+		 *
+		 * @param objectId
+		 *            the object ID
+		 * @param type
+		 *            the Git object type. See {@link Constants}.
+		 * @return the current builder.
+		 * @since 4.2
+		 */
+		BitmapBuilder addObject(AnyObjectId objectId, int type);
+
+		/**
 		 * Remove the id from the bitmap.
 		 *
 		 * @param objectId
@@ -190,5 +204,14 @@
 
 		/** @return the number of elements in the bitmap. */
 		int cardinality();
+
+		/**
+		 * Get the BitmapIndex for this BitmapBuilder.
+		 *
+		 * @return the BitmapIndex for this BitmapBuilder
+		 *
+		 * @since 4.2
+		 */
+		BitmapIndex getBitmapIndex();
 	}
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BlobBasedConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BlobBasedConfig.java
index cbb2f5b..7d52991 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BlobBasedConfig.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BlobBasedConfig.java
@@ -79,7 +79,15 @@
 	public BlobBasedConfig(Config base, final byte[] blob)
 			throws ConfigInvalidException {
 		super(base);
-		fromText(RawParseUtils.decode(blob));
+		final String decoded;
+		if (blob.length >= 3 && blob[0] == (byte) 0xEF
+				&& blob[1] == (byte) 0xBB && blob[2] == (byte) 0xBF) {
+			decoded = RawParseUtils.decode(RawParseUtils.UTF8_CHARSET,
+					blob, 3, blob.length);
+		} else {
+			decoded = RawParseUtils.decode(blob);
+		}
+		fromText(decoded);
 	}
 
 	/**
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java
index 535a6ee..d30edaf 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java
@@ -273,6 +273,13 @@
 	public static final String INFO_EXCLUDE = "info/exclude";
 
 	/**
+	 * Attributes-override-file
+	 *
+	 * @since 4.2
+	 */
+	public static final String INFO_ATTRIBUTES = "info/attributes";
+
+	/**
 	 * The system property that contains the system user name
 	 *
 	 * @since 3.6
@@ -363,6 +370,27 @@
 	 */
 	public static final String DOT_GIT_ATTRIBUTES = ".gitattributes";
 
+	/**
+	 * Key for filters in .gitattributes
+	 *
+	 * @since 4.2
+	 */
+	public static final String ATTR_FILTER = "filter";
+
+	/**
+	 * clean command name, used to call filter driver
+	 *
+	 * @since 4.2
+	 */
+	public static final String ATTR_FILTER_TYPE_CLEAN = "clean";
+
+	/**
+	 * smudge command name, used to call filter driver
+	 *
+	 * @since 4.2
+	 */
+	public static final String ATTR_FILTER_TYPE_SMUDGE = "smudge";
+
 	/** Name of the ignore file */
 	public static final String DOT_GIT_IGNORE = ".gitignore";
 
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileTreeEntry.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileTreeEntry.java
deleted file mode 100644
index 6811417..0000000
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileTreeEntry.java
+++ /dev/null
@@ -1,115 +0,0 @@
-/*
- * Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com>
- * Copyright (C) 2006-2007, Shawn O. Pearce <spearce@spearce.org>
- * and other copyright owners as documented in the project's IP log.
- *
- * This program and the accompanying materials are made available
- * under the terms of the Eclipse Distribution License v1.0 which
- * accompanies this distribution, is reproduced below, and is
- * available at http://www.eclipse.org/org/documents/edl-v10.php
- *
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or
- * without modification, are permitted provided that the following
- * conditions are met:
- *
- * - Redistributions of source code must retain the above copyright
- *   notice, this list of conditions and the following disclaimer.
- *
- * - Redistributions in binary form must reproduce the above
- *   copyright notice, this list of conditions and the following
- *   disclaimer in the documentation and/or other materials provided
- *   with the distribution.
- *
- * - Neither the name of the Eclipse Foundation, Inc. nor the
- *   names of its contributors may be used to endorse or promote
- *   products derived from this software without specific prior
- *   written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
- * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
- * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
- * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
- * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
- * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
- * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
- * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-package org.eclipse.jgit.lib;
-
-import java.io.IOException;
-
-/**
- * A representation of a file (blob) object in a {@link Tree}.
- *
- * @deprecated To look up information about a single path, use
- * {@link org.eclipse.jgit.treewalk.TreeWalk#forPath(Repository, String, org.eclipse.jgit.revwalk.RevTree)}.
- * To lookup information about multiple paths at once, use a
- * {@link org.eclipse.jgit.treewalk.TreeWalk} and obtain the current entry's
- * information from its getter methods.
- */
-@Deprecated
-public class FileTreeEntry extends TreeEntry {
-	private FileMode mode;
-
-	/**
-	 * Constructor for a File (blob) object.
-	 *
-	 * @param parent
-	 *            The {@link Tree} holding this object (or null)
-	 * @param id
-	 *            the SHA-1 of the blob (or null for a yet unhashed file)
-	 * @param nameUTF8
-	 *            raw object name in the parent tree
-	 * @param execute
-	 *            true if the executable flag is set
-	 */
-	public FileTreeEntry(final Tree parent, final ObjectId id,
-			final byte[] nameUTF8, final boolean execute) {
-		super(parent, id, nameUTF8);
-		setExecutable(execute);
-	}
-
-	public FileMode getMode() {
-		return mode;
-	}
-
-	/**
-	 * @return true if this file is executable
-	 */
-	public boolean isExecutable() {
-		return getMode().equals(FileMode.EXECUTABLE_FILE);
-	}
-
-	/**
-	 * @param execute set/reset the executable flag
-	 */
-	public void setExecutable(final boolean execute) {
-		mode = execute ? FileMode.EXECUTABLE_FILE : FileMode.REGULAR_FILE;
-	}
-
-	/**
-	 * @return an {@link ObjectLoader} that will return the data
-	 * @throws IOException
-	 */
-	public ObjectLoader openReader() throws IOException {
-		return getRepository().open(getId(), Constants.OBJ_BLOB);
-	}
-
-	public String toString() {
-		final StringBuilder r = new StringBuilder();
-		r.append(ObjectId.toString(getId()));
-		r.append(' ');
-		r.append(isExecutable() ? 'X' : 'F');
-		r.append(' ');
-		r.append(getFullName());
-		return r.toString();
-	}
-}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/GitlinkTreeEntry.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/GitlinkTreeEntry.java
deleted file mode 100644
index 936fd82..0000000
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/GitlinkTreeEntry.java
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- * Copyright (C) 2009, Jonas Fonseca <fonseca@diku.dk>
- * Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com>
- * Copyright (C) 2007, Shawn O. Pearce <spearce@spearce.org>
- * and other copyright owners as documented in the project's IP log.
- *
- * This program and the accompanying materials are made available
- * under the terms of the Eclipse Distribution License v1.0 which
- * accompanies this distribution, is reproduced below, and is
- * available at http://www.eclipse.org/org/documents/edl-v10.php
- *
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or
- * without modification, are permitted provided that the following
- * conditions are met:
- *
- * - Redistributions of source code must retain the above copyright
- *   notice, this list of conditions and the following disclaimer.
- *
- * - Redistributions in binary form must reproduce the above
- *   copyright notice, this list of conditions and the following
- *   disclaimer in the documentation and/or other materials provided
- *   with the distribution.
- *
- * - Neither the name of the Eclipse Foundation, Inc. nor the
- *   names of its contributors may be used to endorse or promote
- *   products derived from this software without specific prior
- *   written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
- * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
- * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
- * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
- * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
- * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
- * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
- * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-package org.eclipse.jgit.lib;
-
-/**
- * A tree entry representing a gitlink entry used for submodules.
- *
- * Note. Java cannot really handle these as file system objects.
- *
- * @deprecated To look up information about a single path, use
- * {@link org.eclipse.jgit.treewalk.TreeWalk#forPath(Repository, String, org.eclipse.jgit.revwalk.RevTree)}.
- * To lookup information about multiple paths at once, use a
- * {@link org.eclipse.jgit.treewalk.TreeWalk} and obtain the current entry's
- * information from its getter methods.
- */
-@Deprecated
-public class GitlinkTreeEntry extends TreeEntry {
-
-	/**
-	 * Construct a {@link GitlinkTreeEntry} with the specified name and SHA-1 in
-	 * the specified parent
-	 *
-	 * @param parent
-	 * @param id
-	 * @param nameUTF8
-	 */
-	public GitlinkTreeEntry(final Tree parent, final ObjectId id,
-			final byte[] nameUTF8) {
-		super(parent, id, nameUTF8);
-	}
-
-	public FileMode getMode() {
-		return FileMode.GITLINK;
-	}
-
-	@Override
-	public String toString() {
-		final StringBuilder r = new StringBuilder();
-		r.append(ObjectId.toString(getId()));
-		r.append(" G "); //$NON-NLS-1$
-		r.append(getFullName());
-		return r.toString();
-	}
-}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/IndexDiff.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/IndexDiff.java
index 3fde2f9..9e474f8 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/IndexDiff.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/IndexDiff.java
@@ -74,6 +74,7 @@
 import org.eclipse.jgit.treewalk.FileTreeIterator;
 import org.eclipse.jgit.treewalk.TreeWalk;
 import org.eclipse.jgit.treewalk.WorkingTreeIterator;
+import org.eclipse.jgit.treewalk.TreeWalk.OperationType;
 import org.eclipse.jgit.treewalk.filter.AndTreeFilter;
 import org.eclipse.jgit.treewalk.filter.IndexDiffFilter;
 import org.eclipse.jgit.treewalk.filter.SkipWorkTreeFilter;
@@ -403,6 +404,7 @@
 		dirCache = repository.readDirCache();
 
 		try (TreeWalk treeWalk = new TreeWalk(repository)) {
+			treeWalk.setOperationType(OperationType.CHECKIN_OP);
 			treeWalk.setRecursive(true);
 			// add the trees (tree, dirchache, workdir)
 			if (tree != null)
@@ -411,6 +413,7 @@
 				treeWalk.addTree(new EmptyTreeIterator());
 			treeWalk.addTree(new DirCacheIterator(dirCache));
 			treeWalk.addTree(initialWorkingTreeIterator);
+			initialWorkingTreeIterator.setDirCacheIterator(treeWalk, 1);
 			Collection<TreeFilter> filters = new ArrayList<TreeFilter>(4);
 
 			if (monitor != null) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectChecker.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectChecker.java
index a7a67a8..0b5efd7 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectChecker.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectChecker.java
@@ -44,21 +44,58 @@
 
 package org.eclipse.jgit.lib;
 
-import static org.eclipse.jgit.util.RawParseUtils.match;
+import static org.eclipse.jgit.lib.Constants.OBJECT_ID_LENGTH;
+import static org.eclipse.jgit.lib.Constants.OBJECT_ID_STRING_LENGTH;
+import static org.eclipse.jgit.lib.Constants.OBJ_BAD;
+import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
+import static org.eclipse.jgit.lib.Constants.OBJ_COMMIT;
+import static org.eclipse.jgit.lib.Constants.OBJ_TAG;
+import static org.eclipse.jgit.lib.Constants.OBJ_TREE;
+import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.BAD_DATE;
+import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.BAD_EMAIL;
+import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.BAD_OBJECT_SHA1;
+import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.BAD_PARENT_SHA1;
+import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.BAD_TIMEZONE;
+import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.BAD_TREE_SHA1;
+import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.BAD_UTF8;
+import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.DUPLICATE_ENTRIES;
+import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.EMPTY_NAME;
+import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.FULL_PATHNAME;
+import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.HAS_DOT;
+import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.HAS_DOTDOT;
+import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.HAS_DOTGIT;
+import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.MISSING_AUTHOR;
+import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.MISSING_COMMITTER;
+import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.MISSING_EMAIL;
+import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.MISSING_OBJECT;
+import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.MISSING_SPACE_BEFORE_DATE;
+import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.MISSING_TAG_ENTRY;
+import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.MISSING_TREE;
+import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.MISSING_TYPE_ENTRY;
+import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.NULL_SHA1;
+import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.TREE_NOT_SORTED;
+import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.UNKNOWN_TYPE;
+import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.WIN32_BAD_NAME;
+import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.ZERO_PADDED_FILEMODE;
+import static org.eclipse.jgit.util.Paths.compare;
+import static org.eclipse.jgit.util.Paths.compareSameName;
 import static org.eclipse.jgit.util.RawParseUtils.nextLF;
 import static org.eclipse.jgit.util.RawParseUtils.parseBase10;
 
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
 import java.text.MessageFormat;
+import java.text.Normalizer;
+import java.util.EnumSet;
 import java.util.HashSet;
 import java.util.Locale;
 import java.util.Set;
 
+import org.eclipse.jgit.annotations.NonNull;
+import org.eclipse.jgit.annotations.Nullable;
 import org.eclipse.jgit.errors.CorruptObjectException;
 import org.eclipse.jgit.internal.JGitText;
 import org.eclipse.jgit.util.MutableInteger;
 import org.eclipse.jgit.util.RawParseUtils;
+import org.eclipse.jgit.util.StringUtils;
 
 /**
  * Verifies that an object is formatted correctly.
@@ -99,31 +136,135 @@
 	/** Header "tagger " */
 	public static final byte[] tagger = Constants.encodeASCII("tagger "); //$NON-NLS-1$
 
+	/**
+	 * Potential issues identified by the checker.
+	 *
+	 * @since 4.2
+	 */
+	public enum ErrorType {
+		// @formatter:off
+		// These names match git-core so that fsck section keys also match.
+		/***/ NULL_SHA1,
+		/***/ DUPLICATE_ENTRIES,
+		/***/ TREE_NOT_SORTED,
+		/***/ ZERO_PADDED_FILEMODE,
+		/***/ EMPTY_NAME,
+		/***/ FULL_PATHNAME,
+		/***/ HAS_DOT,
+		/***/ HAS_DOTDOT,
+		/***/ HAS_DOTGIT,
+		/***/ BAD_OBJECT_SHA1,
+		/***/ BAD_PARENT_SHA1,
+		/***/ BAD_TREE_SHA1,
+		/***/ MISSING_AUTHOR,
+		/***/ MISSING_COMMITTER,
+		/***/ MISSING_OBJECT,
+		/***/ MISSING_TREE,
+		/***/ MISSING_TYPE_ENTRY,
+		/***/ MISSING_TAG_ENTRY,
+		/***/ BAD_DATE,
+		/***/ BAD_EMAIL,
+		/***/ BAD_TIMEZONE,
+		/***/ MISSING_EMAIL,
+		/***/ MISSING_SPACE_BEFORE_DATE,
+		/***/ UNKNOWN_TYPE,
+
+		// These are unique to JGit.
+		/***/ WIN32_BAD_NAME,
+		/***/ BAD_UTF8;
+		// @formatter:on
+
+		/** @return camelCaseVersion of the name. */
+		public String getMessageId() {
+			String n = name();
+			StringBuilder r = new StringBuilder(n.length());
+			for (int i = 0; i < n.length(); i++) {
+				char c = n.charAt(i);
+				if (c != '_') {
+					r.append(StringUtils.toLowerCase(c));
+				} else {
+					r.append(n.charAt(++i));
+				}
+			}
+			return r.toString();
+		}
+	}
+
 	private final MutableObjectId tempId = new MutableObjectId();
+	private final MutableInteger bufPtr = new MutableInteger();
 
-	private final MutableInteger ptrout = new MutableInteger();
-
-	private boolean allowZeroMode;
-
+	private EnumSet<ErrorType> errors = EnumSet.allOf(ErrorType.class);
+	private ObjectIdSet skipList;
 	private boolean allowInvalidPersonIdent;
 	private boolean windows;
 	private boolean macosx;
 
 	/**
+	 * Enable accepting specific malformed (but not horribly broken) objects.
+	 *
+	 * @param objects
+	 *            collection of object names known to be broken in a non-fatal
+	 *            way that should be ignored by the checker.
+	 * @return {@code this}
+	 * @since 4.2
+	 */
+	public ObjectChecker setSkipList(@Nullable ObjectIdSet objects) {
+		skipList = objects;
+		return this;
+	}
+
+	/**
+	 * Configure error types to be ignored across all objects.
+	 *
+	 * @param ids
+	 *            error types to ignore. The caller's set is copied.
+	 * @return {@code this}
+	 * @since 4.2
+	 */
+	public ObjectChecker setIgnore(@Nullable Set<ErrorType> ids) {
+		errors = EnumSet.allOf(ErrorType.class);
+		if (ids != null) {
+			errors.removeAll(ids);
+		}
+		return this;
+	}
+
+	/**
+	 * Add message type to be ignored across all objects.
+	 *
+	 * @param id
+	 *            error type to ignore.
+	 * @param ignore
+	 *            true to ignore this error; false to treat the error as an
+	 *            error and throw.
+	 * @return {@code this}
+	 * @since 4.2
+	 */
+	public ObjectChecker setIgnore(ErrorType id, boolean ignore) {
+		if (ignore) {
+			errors.remove(id);
+		} else {
+			errors.add(id);
+		}
+		return this;
+	}
+
+	/**
 	 * Enable accepting leading zero mode in tree entries.
 	 * <p>
 	 * Some broken Git libraries generated leading zeros in the mode part of
 	 * tree entries. This is technically incorrect but gracefully allowed by
 	 * git-core. JGit rejects such trees by default, but may need to accept
 	 * them on broken histories.
+	 * <p>
+	 * Same as {@code setIgnore(ZERO_PADDED_FILEMODE, allow)}.
 	 *
 	 * @param allow allow leading zero mode.
 	 * @return {@code this}.
 	 * @since 3.4
 	 */
 	public ObjectChecker setAllowLeadingZeroFileMode(boolean allow) {
-		allowZeroMode = allow;
-		return this;
+		return setIgnore(ZERO_PADDED_FILEMODE, allow);
 	}
 
 	/**
@@ -184,62 +325,117 @@
 	 * @throws CorruptObjectException
 	 *             if an error is identified.
 	 */
-	public void check(final int objType, final byte[] raw)
+	public void check(int objType, byte[] raw)
+			throws CorruptObjectException {
+		check(idFor(objType, raw), objType, raw);
+	}
+
+	/**
+	 * Check an object for parsing errors.
+	 *
+	 * @param id
+	 *            identify of the object being checked.
+	 * @param objType
+	 *            type of the object. Must be a valid object type code in
+	 *            {@link Constants}.
+	 * @param raw
+	 *            the raw data which comprises the object. This should be in the
+	 *            canonical format (that is the format used to generate the
+	 *            ObjectId of the object). The array is never modified.
+	 * @throws CorruptObjectException
+	 *             if an error is identified.
+	 * @since 4.2
+	 */
+	public void check(@Nullable AnyObjectId id, int objType, byte[] raw)
 			throws CorruptObjectException {
 		switch (objType) {
-		case Constants.OBJ_COMMIT:
-			checkCommit(raw);
+		case OBJ_COMMIT:
+			checkCommit(id, raw);
 			break;
-		case Constants.OBJ_TAG:
-			checkTag(raw);
+		case OBJ_TAG:
+			checkTag(id, raw);
 			break;
-		case Constants.OBJ_TREE:
-			checkTree(raw);
+		case OBJ_TREE:
+			checkTree(id, raw);
 			break;
-		case Constants.OBJ_BLOB:
+		case OBJ_BLOB:
 			checkBlob(raw);
 			break;
 		default:
-			throw new CorruptObjectException(MessageFormat.format(
+			report(UNKNOWN_TYPE, id, MessageFormat.format(
 					JGitText.get().corruptObjectInvalidType2,
 					Integer.valueOf(objType)));
 		}
 	}
 
-	private int id(final byte[] raw, final int ptr) {
+	private boolean checkId(byte[] raw) {
+		int p = bufPtr.value;
 		try {
-			tempId.fromString(raw, ptr);
-			return ptr + Constants.OBJECT_ID_STRING_LENGTH;
+			tempId.fromString(raw, p);
 		} catch (IllegalArgumentException e) {
-			return -1;
+			bufPtr.value = nextLF(raw, p);
+			return false;
 		}
+
+		p += OBJECT_ID_STRING_LENGTH;
+		if (raw[p] == '\n') {
+			bufPtr.value = p + 1;
+			return true;
+		}
+		bufPtr.value = nextLF(raw, p);
+		return false;
 	}
 
-	private int personIdent(final byte[] raw, int ptr) {
-		if (allowInvalidPersonIdent)
-			return nextLF(raw, ptr) - 1;
+	private void checkPersonIdent(byte[] raw, @Nullable AnyObjectId id)
+			throws CorruptObjectException {
+		if (allowInvalidPersonIdent) {
+			bufPtr.value = nextLF(raw, bufPtr.value);
+			return;
+		}
 
-		final int emailB = nextLF(raw, ptr, '<');
-		if (emailB == ptr || raw[emailB - 1] != '<')
-			return -1;
+		final int emailB = nextLF(raw, bufPtr.value, '<');
+		if (emailB == bufPtr.value || raw[emailB - 1] != '<') {
+			report(MISSING_EMAIL, id, JGitText.get().corruptObjectMissingEmail);
+			bufPtr.value = nextLF(raw, bufPtr.value);
+			return;
+		}
 
 		final int emailE = nextLF(raw, emailB, '>');
-		if (emailE == emailB || raw[emailE - 1] != '>')
-			return -1;
-		if (emailE == raw.length || raw[emailE] != ' ')
-			return -1;
+		if (emailE == emailB || raw[emailE - 1] != '>') {
+			report(BAD_EMAIL, id, JGitText.get().corruptObjectBadEmail);
+			bufPtr.value = nextLF(raw, bufPtr.value);
+			return;
+		}
+		if (emailE == raw.length || raw[emailE] != ' ') {
+			report(MISSING_SPACE_BEFORE_DATE, id,
+					JGitText.get().corruptObjectBadDate);
+			bufPtr.value = nextLF(raw, bufPtr.value);
+			return;
+		}
 
-		parseBase10(raw, emailE + 1, ptrout); // when
-		ptr = ptrout.value;
-		if (emailE + 1 == ptr)
-			return -1;
-		if (ptr == raw.length || raw[ptr] != ' ')
-			return -1;
+		parseBase10(raw, emailE + 1, bufPtr); // when
+		if (emailE + 1 == bufPtr.value || bufPtr.value == raw.length
+				|| raw[bufPtr.value] != ' ') {
+			report(BAD_DATE, id, JGitText.get().corruptObjectBadDate);
+			bufPtr.value = nextLF(raw, bufPtr.value);
+			return;
+		}
 
-		parseBase10(raw, ptr + 1, ptrout); // tz offset
-		if (ptr + 1 == ptrout.value)
-			return -1;
-		return ptrout.value;
+		int p = bufPtr.value + 1;
+		parseBase10(raw, p, bufPtr); // tz offset
+		if (p == bufPtr.value) {
+			report(BAD_TIMEZONE, id, JGitText.get().corruptObjectBadTimezone);
+			bufPtr.value = nextLF(raw, bufPtr.value);
+			return;
+		}
+
+		p = bufPtr.value;
+		if (raw[p] == '\n') {
+			bufPtr.value = p + 1;
+		} else {
+			report(BAD_TIMEZONE, id, JGitText.get().corruptObjectBadTimezone);
+			bufPtr.value = nextLF(raw, p);
+		}
 	}
 
 	/**
@@ -250,36 +446,50 @@
 	 * @throws CorruptObjectException
 	 *             if any error was detected.
 	 */
-	public void checkCommit(final byte[] raw) throws CorruptObjectException {
-		int ptr = 0;
+	public void checkCommit(byte[] raw) throws CorruptObjectException {
+		checkCommit(idFor(OBJ_COMMIT, raw), raw);
+	}
 
-		if ((ptr = match(raw, ptr, tree)) < 0)
-			throw new CorruptObjectException(
-					JGitText.get().corruptObjectNotreeHeader);
-		if ((ptr = id(raw, ptr)) < 0 || raw[ptr++] != '\n')
-			throw new CorruptObjectException(
-					JGitText.get().corruptObjectInvalidTree);
+	/**
+	 * Check a commit for errors.
+	 *
+	 * @param id
+	 *            identity of the object being checked.
+	 * @param raw
+	 *            the commit data. The array is never modified.
+	 * @throws CorruptObjectException
+	 *             if any error was detected.
+	 * @since 4.2
+	 */
+	public void checkCommit(@Nullable AnyObjectId id, byte[] raw)
+			throws CorruptObjectException {
+		bufPtr.value = 0;
 
-		while (match(raw, ptr, parent) >= 0) {
-			ptr += parent.length;
-			if ((ptr = id(raw, ptr)) < 0 || raw[ptr++] != '\n')
-				throw new CorruptObjectException(
-						JGitText.get().corruptObjectInvalidParent);
+		if (!match(raw, tree)) {
+			report(MISSING_TREE, id, JGitText.get().corruptObjectNotreeHeader);
+		} else if (!checkId(raw)) {
+			report(BAD_TREE_SHA1, id, JGitText.get().corruptObjectInvalidTree);
 		}
 
-		if ((ptr = match(raw, ptr, author)) < 0)
-			throw new CorruptObjectException(
-					JGitText.get().corruptObjectNoAuthor);
-		if ((ptr = personIdent(raw, ptr)) < 0 || raw[ptr++] != '\n')
-			throw new CorruptObjectException(
-					JGitText.get().corruptObjectInvalidAuthor);
+		while (match(raw, parent)) {
+			if (!checkId(raw)) {
+				report(BAD_PARENT_SHA1, id,
+						JGitText.get().corruptObjectInvalidParent);
+			}
+		}
 
-		if ((ptr = match(raw, ptr, committer)) < 0)
-			throw new CorruptObjectException(
+		if (match(raw, author)) {
+			checkPersonIdent(raw, id);
+		} else {
+			report(MISSING_AUTHOR, id, JGitText.get().corruptObjectNoAuthor);
+		}
+
+		if (match(raw, committer)) {
+			checkPersonIdent(raw, id);
+		} else {
+			report(MISSING_COMMITTER, id,
 					JGitText.get().corruptObjectNoCommitter);
-		if ((ptr = personIdent(raw, ptr)) < 0 || raw[ptr++] != '\n')
-			throw new CorruptObjectException(
-					JGitText.get().corruptObjectInvalidCommitter);
+		}
 	}
 
 	/**
@@ -290,50 +500,47 @@
 	 * @throws CorruptObjectException
 	 *             if any error was detected.
 	 */
-	public void checkTag(final byte[] raw) throws CorruptObjectException {
-		int ptr = 0;
+	public void checkTag(byte[] raw) throws CorruptObjectException {
+		checkTag(idFor(OBJ_TAG, raw), raw);
+	}
 
-		if ((ptr = match(raw, ptr, object)) < 0)
-			throw new CorruptObjectException(
+	/**
+	 * Check an annotated tag for errors.
+	 *
+	 * @param id
+	 *            identity of the object being checked.
+	 * @param raw
+	 *            the tag data. The array is never modified.
+	 * @throws CorruptObjectException
+	 *             if any error was detected.
+	 * @since 4.2
+	 */
+	public void checkTag(@Nullable AnyObjectId id, byte[] raw)
+			throws CorruptObjectException {
+		bufPtr.value = 0;
+		if (!match(raw, object)) {
+			report(MISSING_OBJECT, id,
 					JGitText.get().corruptObjectNoObjectHeader);
-		if ((ptr = id(raw, ptr)) < 0 || raw[ptr++] != '\n')
-			throw new CorruptObjectException(
+		} else if (!checkId(raw)) {
+			report(BAD_OBJECT_SHA1, id,
 					JGitText.get().corruptObjectInvalidObject);
+		}
 
-		if ((ptr = match(raw, ptr, type)) < 0)
-			throw new CorruptObjectException(
+		if (!match(raw, type)) {
+			report(MISSING_TYPE_ENTRY, id,
 					JGitText.get().corruptObjectNoTypeHeader);
-		ptr = nextLF(raw, ptr);
+		}
+		bufPtr.value = nextLF(raw, bufPtr.value);
 
-		if ((ptr = match(raw, ptr, tag)) < 0)
-			throw new CorruptObjectException(
+		if (!match(raw, tag)) {
+			report(MISSING_TAG_ENTRY, id,
 					JGitText.get().corruptObjectNoTagHeader);
-		ptr = nextLF(raw, ptr);
-
-		if ((ptr = match(raw, ptr, tagger)) > 0) {
-			if ((ptr = personIdent(raw, ptr)) < 0 || raw[ptr++] != '\n')
-				throw new CorruptObjectException(
-						JGitText.get().corruptObjectInvalidTagger);
 		}
-	}
+		bufPtr.value = nextLF(raw, bufPtr.value);
 
-	private static int lastPathChar(final int mode) {
-		return FileMode.TREE.equals(mode) ? '/' : '\0';
-	}
-
-	private static int pathCompare(final byte[] raw, int aPos, final int aEnd,
-			final int aMode, int bPos, final int bEnd, final int bMode) {
-		while (aPos < aEnd && bPos < bEnd) {
-			final int cmp = (raw[aPos++] & 0xff) - (raw[bPos++] & 0xff);
-			if (cmp != 0)
-				return cmp;
+		if (match(raw, tagger)) {
+			checkPersonIdent(raw, id);
 		}
-
-		if (aPos < aEnd)
-			return (raw[aPos] & 0xff) - lastPathChar(bMode);
-		if (bPos < bEnd)
-			return lastPathChar(aMode) - (raw[bPos] & 0xff);
-		return 0;
 	}
 
 	private static boolean duplicateName(final byte[] raw,
@@ -363,8 +570,9 @@
 			if (nextNamePos + 1 == nextPtr)
 				return false;
 
-			final int cmp = pathCompare(raw, thisNamePos, thisNameEnd,
-					FileMode.TREE.getBits(), nextNamePos, nextPtr - 1, nextMode);
+			int cmp = compareSameName(
+					raw, thisNamePos, thisNameEnd,
+					raw, nextNamePos, nextPtr - 1, nextMode);
 			if (cmp < 0)
 				return false;
 			else if (cmp == 0)
@@ -382,7 +590,23 @@
 	 * @throws CorruptObjectException
 	 *             if any error was detected.
 	 */
-	public void checkTree(final byte[] raw) throws CorruptObjectException {
+	public void checkTree(byte[] raw) throws CorruptObjectException {
+		checkTree(idFor(OBJ_TREE, raw), raw);
+	}
+
+	/**
+	 * Check a canonical formatted tree for errors.
+	 *
+	 * @param id
+	 *            identity of the object being checked.
+	 * @param raw
+	 *            the raw tree data. The array is never modified.
+	 * @throws CorruptObjectException
+	 *             if any error was detected.
+	 * @since 4.2
+	 */
+	public void checkTree(@Nullable AnyObjectId id, byte[] raw)
+			throws CorruptObjectException {
 		final int sz = raw.length;
 		int ptr = 0;
 		int lastNameB = 0, lastNameE = 0, lastMode = 0;
@@ -393,74 +617,90 @@
 		while (ptr < sz) {
 			int thisMode = 0;
 			for (;;) {
-				if (ptr == sz)
+				if (ptr == sz) {
 					throw new CorruptObjectException(
 							JGitText.get().corruptObjectTruncatedInMode);
+				}
 				final byte c = raw[ptr++];
 				if (' ' == c)
 					break;
-				if (c < '0' || c > '7')
+				if (c < '0' || c > '7') {
 					throw new CorruptObjectException(
 							JGitText.get().corruptObjectInvalidModeChar);
-				if (thisMode == 0 && c == '0' && !allowZeroMode)
-					throw new CorruptObjectException(
+				}
+				if (thisMode == 0 && c == '0') {
+					report(ZERO_PADDED_FILEMODE, id,
 							JGitText.get().corruptObjectInvalidModeStartsZero);
+				}
 				thisMode <<= 3;
 				thisMode += c - '0';
 			}
 
-			if (FileMode.fromBits(thisMode).getObjectType() == Constants.OBJ_BAD)
+			if (FileMode.fromBits(thisMode).getObjectType() == OBJ_BAD) {
 				throw new CorruptObjectException(MessageFormat.format(
 						JGitText.get().corruptObjectInvalidMode2,
 						Integer.valueOf(thisMode)));
+			}
 
 			final int thisNameB = ptr;
-			ptr = scanPathSegment(raw, ptr, sz);
-			if (ptr == sz || raw[ptr] != 0)
+			ptr = scanPathSegment(raw, ptr, sz, id);
+			if (ptr == sz || raw[ptr] != 0) {
 				throw new CorruptObjectException(
 						JGitText.get().corruptObjectTruncatedInName);
-			checkPathSegment2(raw, thisNameB, ptr);
+			}
+			checkPathSegment2(raw, thisNameB, ptr, id);
 			if (normalized != null) {
-				if (!normalized.add(normalize(raw, thisNameB, ptr)))
-					throw new CorruptObjectException(
+				if (!normalized.add(normalize(raw, thisNameB, ptr))) {
+					report(DUPLICATE_ENTRIES, id,
 							JGitText.get().corruptObjectDuplicateEntryNames);
-			} else if (duplicateName(raw, thisNameB, ptr))
-				throw new CorruptObjectException(
+				}
+			} else if (duplicateName(raw, thisNameB, ptr)) {
+				report(DUPLICATE_ENTRIES, id,
 						JGitText.get().corruptObjectDuplicateEntryNames);
+			}
 
 			if (lastNameB != 0) {
-				final int cmp = pathCompare(raw, lastNameB, lastNameE,
-						lastMode, thisNameB, ptr, thisMode);
-				if (cmp > 0)
-					throw new CorruptObjectException(
+				int cmp = compare(
+						raw, lastNameB, lastNameE, lastMode,
+						raw, thisNameB, ptr, thisMode);
+				if (cmp > 0) {
+					report(TREE_NOT_SORTED, id,
 							JGitText.get().corruptObjectIncorrectSorting);
+				}
 			}
 
 			lastNameB = thisNameB;
 			lastNameE = ptr;
 			lastMode = thisMode;
 
-			ptr += 1 + Constants.OBJECT_ID_LENGTH;
-			if (ptr > sz)
+			ptr += 1 + OBJECT_ID_LENGTH;
+			if (ptr > sz) {
 				throw new CorruptObjectException(
 						JGitText.get().corruptObjectTruncatedInObjectId);
+			}
+			if (ObjectId.zeroId().compareTo(raw, ptr - OBJECT_ID_LENGTH) == 0) {
+				report(NULL_SHA1, id, JGitText.get().corruptObjectZeroId);
+			}
 		}
 	}
 
-	private int scanPathSegment(byte[] raw, int ptr, int end)
-			throws CorruptObjectException {
+	private int scanPathSegment(byte[] raw, int ptr, int end,
+			@Nullable AnyObjectId id) throws CorruptObjectException {
 		for (; ptr < end; ptr++) {
 			byte c = raw[ptr];
-			if (c == 0)
+			if (c == 0) {
 				return ptr;
-			if (c == '/')
-				throw new CorruptObjectException(
+			}
+			if (c == '/') {
+				report(FULL_PATHNAME, id,
 						JGitText.get().corruptObjectNameContainsSlash);
+			}
 			if (windows && isInvalidOnWindows(c)) {
-				if (c > 31)
+				if (c > 31) {
 					throw new CorruptObjectException(String.format(
 							JGitText.get().corruptObjectNameContainsChar,
 							Byte.valueOf(c)));
+				}
 				throw new CorruptObjectException(String.format(
 						JGitText.get().corruptObjectNameContainsByte,
 						Integer.valueOf(c & 0xff)));
@@ -469,6 +709,26 @@
 		return ptr;
 	}
 
+	@SuppressWarnings("resource")
+	@Nullable
+	private ObjectId idFor(int objType, byte[] raw) {
+		if (skipList != null) {
+			return new ObjectInserter.Formatter().idFor(objType, raw);
+		}
+		return null;
+	}
+
+	private void report(@NonNull ErrorType err, @Nullable AnyObjectId id,
+			String why) throws CorruptObjectException {
+		if (errors.contains(err)
+				&& (id == null || skipList == null || !skipList.contains(id))) {
+			if (id != null) {
+				throw new CorruptObjectException(err, id, why);
+			}
+			throw new CorruptObjectException(why);
+		}
+	}
+
 	/**
 	 * Check tree path entry for validity.
 	 * <p>
@@ -519,73 +779,82 @@
 	 */
 	public void checkPathSegment(byte[] raw, int ptr, int end)
 			throws CorruptObjectException {
-		int e = scanPathSegment(raw, ptr, end);
+		int e = scanPathSegment(raw, ptr, end, null);
 		if (e < end && raw[e] == 0)
 			throw new CorruptObjectException(
 					JGitText.get().corruptObjectNameContainsNullByte);
-		checkPathSegment2(raw, ptr, end);
+		checkPathSegment2(raw, ptr, end, null);
 	}
 
-	private void checkPathSegment2(byte[] raw, int ptr, int end)
-			throws CorruptObjectException {
-		if (ptr == end)
-			throw new CorruptObjectException(
-					JGitText.get().corruptObjectNameZeroLength);
+	private void checkPathSegment2(byte[] raw, int ptr, int end,
+			@Nullable AnyObjectId id) throws CorruptObjectException {
+		if (ptr == end) {
+			report(EMPTY_NAME, id, JGitText.get().corruptObjectNameZeroLength);
+			return;
+		}
+
 		if (raw[ptr] == '.') {
 			switch (end - ptr) {
 			case 1:
-				throw new CorruptObjectException(
-						JGitText.get().corruptObjectNameDot);
+				report(HAS_DOT, id, JGitText.get().corruptObjectNameDot);
+				break;
 			case 2:
-				if (raw[ptr + 1] == '.')
-					throw new CorruptObjectException(
+				if (raw[ptr + 1] == '.') {
+					report(HAS_DOTDOT, id,
 							JGitText.get().corruptObjectNameDotDot);
+				}
 				break;
 			case 4:
-				if (isGit(raw, ptr + 1))
-					throw new CorruptObjectException(String.format(
+				if (isGit(raw, ptr + 1)) {
+					report(HAS_DOTGIT, id, String.format(
 							JGitText.get().corruptObjectInvalidName,
 							RawParseUtils.decode(raw, ptr, end)));
+				}
 				break;
 			default:
-				if (end - ptr > 4 && isNormalizedGit(raw, ptr + 1, end))
-					throw new CorruptObjectException(String.format(
+				if (end - ptr > 4 && isNormalizedGit(raw, ptr + 1, end)) {
+					report(HAS_DOTGIT, id, String.format(
 							JGitText.get().corruptObjectInvalidName,
 							RawParseUtils.decode(raw, ptr, end)));
+				}
 			}
 		} else if (isGitTilde1(raw, ptr, end)) {
-			throw new CorruptObjectException(String.format(
+			report(HAS_DOTGIT, id, String.format(
 					JGitText.get().corruptObjectInvalidName,
 					RawParseUtils.decode(raw, ptr, end)));
 		}
-
-		if (macosx && isMacHFSGit(raw, ptr, end))
-			throw new CorruptObjectException(String.format(
+		if (macosx && isMacHFSGit(raw, ptr, end, id)) {
+			report(HAS_DOTGIT, id, String.format(
 					JGitText.get().corruptObjectInvalidNameIgnorableUnicode,
 					RawParseUtils.decode(raw, ptr, end)));
+		}
 
 		if (windows) {
 			// Windows ignores space and dot at end of file name.
-			if (raw[end - 1] == ' ' || raw[end - 1] == '.')
-				throw new CorruptObjectException(String.format(
+			if (raw[end - 1] == ' ' || raw[end - 1] == '.') {
+				report(WIN32_BAD_NAME, id, String.format(
 						JGitText.get().corruptObjectInvalidNameEnd,
 						Character.valueOf(((char) raw[end - 1]))));
-			if (end - ptr >= 3)
-				checkNotWindowsDevice(raw, ptr, end);
+			}
+			if (end - ptr >= 3) {
+				checkNotWindowsDevice(raw, ptr, end, id);
+			}
 		}
 	}
 
 	// Mac's HFS+ folds permutations of ".git" and Unicode ignorable characters
 	// to ".git" therefore we should prevent such names
-	private static boolean isMacHFSGit(byte[] raw, int ptr, int end)
-			throws CorruptObjectException {
+	private boolean isMacHFSGit(byte[] raw, int ptr, int end,
+			@Nullable AnyObjectId id) throws CorruptObjectException {
 		boolean ignorable = false;
 		byte[] git = new byte[] { '.', 'g', 'i', 't' };
 		int g = 0;
 		while (ptr < end) {
 			switch (raw[ptr]) {
 			case (byte) 0xe2: // http://www.utf8-chartable.de/unicode-utf8-table.pl?start=8192
-				checkTruncatedIgnorableUTF8(raw, ptr, end);
+				if (!checkTruncatedIgnorableUTF8(raw, ptr, end, id)) {
+					return false;
+				}
 				switch (raw[ptr + 1]) {
 				case (byte) 0x80:
 					switch (raw[ptr + 2]) {
@@ -622,7 +891,9 @@
 					return false;
 				}
 			case (byte) 0xef: // http://www.utf8-chartable.de/unicode-utf8-table.pl?start=65024
-				checkTruncatedIgnorableUTF8(raw, ptr, end);
+				if (!checkTruncatedIgnorableUTF8(raw, ptr, end, id)) {
+					return false;
+				}
 				// U+FEFF 0xefbbbf ZERO WIDTH NO-BREAK SPACE
 				if ((raw[ptr + 1] == (byte) 0xbb)
 						&& (raw[ptr + 2] == (byte) 0xbf)) {
@@ -643,12 +914,15 @@
 		return false;
 	}
 
-	private static void checkTruncatedIgnorableUTF8(byte[] raw, int ptr, int end)
-			throws CorruptObjectException {
-		if ((ptr + 2) >= end)
-			throw new CorruptObjectException(MessageFormat.format(
+	private boolean checkTruncatedIgnorableUTF8(byte[] raw, int ptr, int end,
+			@Nullable AnyObjectId id) throws CorruptObjectException {
+		if ((ptr + 2) >= end) {
+			report(BAD_UTF8, id, MessageFormat.format(
 					JGitText.get().corruptObjectInvalidNameInvalidUtf8,
 					toHexString(raw, ptr, end)));
+			return false;
+		}
+		return true;
 	}
 
 	private static String toHexString(byte[] raw, int ptr, int end) {
@@ -658,33 +932,36 @@
 		return b.toString();
 	}
 
-	private static void checkNotWindowsDevice(byte[] raw, int ptr, int end)
-			throws CorruptObjectException {
+	private void checkNotWindowsDevice(byte[] raw, int ptr, int end,
+			@Nullable AnyObjectId id) throws CorruptObjectException {
 		switch (toLower(raw[ptr])) {
 		case 'a': // AUX
 			if (end - ptr >= 3
 					&& toLower(raw[ptr + 1]) == 'u'
 					&& toLower(raw[ptr + 2]) == 'x'
-					&& (end - ptr == 3 || raw[ptr + 3] == '.'))
-				throw new CorruptObjectException(
+					&& (end - ptr == 3 || raw[ptr + 3] == '.')) {
+				report(WIN32_BAD_NAME, id,
 						JGitText.get().corruptObjectInvalidNameAux);
+			}
 			break;
 
 		case 'c': // CON, COM[1-9]
 			if (end - ptr >= 3
 					&& toLower(raw[ptr + 2]) == 'n'
 					&& toLower(raw[ptr + 1]) == 'o'
-					&& (end - ptr == 3 || raw[ptr + 3] == '.'))
-				throw new CorruptObjectException(
+					&& (end - ptr == 3 || raw[ptr + 3] == '.')) {
+				report(WIN32_BAD_NAME, id,
 						JGitText.get().corruptObjectInvalidNameCon);
+			}
 			if (end - ptr >= 4
 					&& toLower(raw[ptr + 2]) == 'm'
 					&& toLower(raw[ptr + 1]) == 'o'
 					&& isPositiveDigit(raw[ptr + 3])
-					&& (end - ptr == 4 || raw[ptr + 4] == '.'))
-				throw new CorruptObjectException(String.format(
+					&& (end - ptr == 4 || raw[ptr + 4] == '.')) {
+				report(WIN32_BAD_NAME, id, String.format(
 						JGitText.get().corruptObjectInvalidNameCom,
 						Character.valueOf(((char) raw[ptr + 3]))));
+			}
 			break;
 
 		case 'l': // LPT[1-9]
@@ -692,28 +969,31 @@
 					&& toLower(raw[ptr + 1]) == 'p'
 					&& toLower(raw[ptr + 2]) == 't'
 					&& isPositiveDigit(raw[ptr + 3])
-					&& (end - ptr == 4 || raw[ptr + 4] == '.'))
-				throw new CorruptObjectException(String.format(
+					&& (end - ptr == 4 || raw[ptr + 4] == '.')) {
+				report(WIN32_BAD_NAME, id, String.format(
 						JGitText.get().corruptObjectInvalidNameLpt,
 						Character.valueOf(((char) raw[ptr + 3]))));
+			}
 			break;
 
 		case 'n': // NUL
 			if (end - ptr >= 3
 					&& toLower(raw[ptr + 1]) == 'u'
 					&& toLower(raw[ptr + 2]) == 'l'
-					&& (end - ptr == 3 || raw[ptr + 3] == '.'))
-				throw new CorruptObjectException(
+					&& (end - ptr == 3 || raw[ptr + 3] == '.')) {
+				report(WIN32_BAD_NAME, id,
 						JGitText.get().corruptObjectInvalidNameNul);
+			}
 			break;
 
 		case 'p': // PRN
 			if (end - ptr >= 3
 					&& toLower(raw[ptr + 1]) == 'r'
 					&& toLower(raw[ptr + 2]) == 'n'
-					&& (end - ptr == 3 || raw[ptr + 3] == '.'))
-				throw new CorruptObjectException(
+					&& (end - ptr == 3 || raw[ptr + 3] == '.')) {
+				report(WIN32_BAD_NAME, id,
 						JGitText.get().corruptObjectInvalidNamePrn);
+			}
 			break;
 		}
 	}
@@ -766,6 +1046,15 @@
 		return false;
 	}
 
+	private boolean match(byte[] b, byte[] src) {
+		int r = RawParseUtils.match(b, bufPtr.value, src);
+		if (r < 0) {
+			return false;
+		}
+		bufPtr.value = r;
+		return true;
+	}
+
 	private static char toLower(byte b) {
 		if ('A' <= b && b <= 'Z')
 			return (char) (b + ('a' - 'A'));
@@ -790,58 +1079,6 @@
 
 	private String normalize(byte[] raw, int ptr, int end) {
 		String n = RawParseUtils.decode(raw, ptr, end).toLowerCase(Locale.US);
-		return macosx ? Normalizer.normalize(n) : n;
-	}
-
-	private static class Normalizer {
-		// TODO Simplify invocation to Normalizer after dropping Java 5.
-		private static final Method normalize;
-		private static final Object nfc;
-		static {
-			Method method;
-			Object formNfc;
-			try {
-				Class<?> formClazz = Class.forName("java.text.Normalizer$Form"); //$NON-NLS-1$
-				formNfc = formClazz.getField("NFC").get(null); //$NON-NLS-1$
-				method = Class.forName("java.text.Normalizer") //$NON-NLS-1$
-					.getMethod("normalize", CharSequence.class, formClazz); //$NON-NLS-1$
-			} catch (ClassNotFoundException e) {
-				method = null;
-				formNfc = null;
-			} catch (NoSuchFieldException e) {
-				method = null;
-				formNfc = null;
-			} catch (NoSuchMethodException e) {
-				method = null;
-				formNfc = null;
-			} catch (SecurityException e) {
-				method = null;
-				formNfc = null;
-			} catch (IllegalArgumentException e) {
-				method = null;
-				formNfc = null;
-			} catch (IllegalAccessException e) {
-				method = null;
-				formNfc = null;
-			}
-			normalize = method;
-			nfc = formNfc;
-		}
-
-		static String normalize(String in) {
-			if (normalize == null)
-				return in;
-			try {
-				return (String) normalize.invoke(null, in, nfc);
-			} catch (IllegalAccessException e) {
-				return in;
-			} catch (InvocationTargetException e) {
-				if (e.getCause() instanceof RuntimeException)
-					throw (RuntimeException) e.getCause();
-				if (e.getCause() instanceof Error)
-					throw (Error) e.getCause();
-				return in;
-			}
-		}
+		return macosx ? Normalizer.normalize(n, Normalizer.Form.NFC) : n;
 	}
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdOwnerMap.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdOwnerMap.java
index c9b483f..442261c 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdOwnerMap.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdOwnerMap.java
@@ -67,8 +67,8 @@
  * @param <V>
  *            type of subclass of ObjectId that will be stored in the map.
  */
-public class ObjectIdOwnerMap<V extends ObjectIdOwnerMap.Entry> implements
-		Iterable<V> {
+public class ObjectIdOwnerMap<V extends ObjectIdOwnerMap.Entry>
+		implements Iterable<V>, ObjectIdSet {
 	/** Size of the initial directory, will grow as necessary. */
 	private static final int INITIAL_DIRECTORY = 1024;
 
@@ -83,16 +83,16 @@
 	 * The low {@link #bits} of the SHA-1 are used to select the segment from
 	 * this directory. Each segment is constant sized at 2^SEGMENT_BITS.
 	 */
-	private V[][] directory;
+	V[][] directory;
 
 	/** Total number of objects in this map. */
-	private int size;
+	int size;
 
 	/** The map doubles in capacity when {@link #size} reaches this target. */
 	private int grow;
 
 	/** Number of low bits used to form the index into {@link #directory}. */
-	private int bits;
+	int bits;
 
 	/** Low bit mask to index into {@link #directory}, {@code 2^bits-1}. */
 	private int mask;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdRef.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdRef.java
index f481c77..c286f5e 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdRef.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdRef.java
@@ -44,6 +44,9 @@
 
 package org.eclipse.jgit.lib;
 
+import org.eclipse.jgit.annotations.NonNull;
+import org.eclipse.jgit.annotations.Nullable;
+
 /** A {@link Ref} that points directly at an {@link ObjectId}. */
 public abstract class ObjectIdRef implements Ref {
 	/** Any reference whose peeled value is not yet known. */
@@ -56,13 +59,15 @@
 		 * @param name
 		 *            name of this ref.
 		 * @param id
-		 *            current value of the ref. May be null to indicate a ref
-		 *            that does not exist yet.
+		 *            current value of the ref. May be {@code null} to indicate
+		 *            a ref that does not exist yet.
 		 */
-		public Unpeeled(Storage st, String name, ObjectId id) {
+		public Unpeeled(@NonNull Storage st, @NonNull String name,
+				@Nullable ObjectId id) {
 			super(st, name, id);
 		}
 
+		@Nullable
 		public ObjectId getPeeledObjectId() {
 			return null;
 		}
@@ -88,11 +93,13 @@
 		 * @param p
 		 *            the first non-tag object that tag {@code id} points to.
 		 */
-		public PeeledTag(Storage st, String name, ObjectId id, ObjectId p) {
+		public PeeledTag(@NonNull Storage st, @NonNull String name,
+				@Nullable ObjectId id, @NonNull ObjectId p) {
 			super(st, name, id);
 			peeledObjectId = p;
 		}
 
+		@NonNull
 		public ObjectId getPeeledObjectId() {
 			return peeledObjectId;
 		}
@@ -112,13 +119,15 @@
 		 * @param name
 		 *            name of this ref.
 		 * @param id
-		 *            current value of the ref. May be null to indicate a ref
-		 *            that does not exist yet.
+		 *            current value of the ref. May be {@code null} to indicate
+		 *            a ref that does not exist yet.
 		 */
-		public PeeledNonTag(Storage st, String name, ObjectId id) {
+		public PeeledNonTag(@NonNull Storage st, @NonNull String name,
+				@Nullable ObjectId id) {
 			super(st, name, id);
 		}
 
+		@Nullable
 		public ObjectId getPeeledObjectId() {
 			return null;
 		}
@@ -142,15 +151,17 @@
 	 * @param name
 	 *            name of this ref.
 	 * @param id
-	 *            current value of the ref. May be null to indicate a ref that
-	 *            does not exist yet.
+	 *            current value of the ref. May be {@code null} to indicate a
+	 *            ref that does not exist yet.
 	 */
-	protected ObjectIdRef(Storage st, String name, ObjectId id) {
+	protected ObjectIdRef(@NonNull Storage st, @NonNull String name,
+			@Nullable ObjectId id) {
 		this.name = name;
 		this.storage = st;
 		this.objectId = id;
 	}
 
+	@NonNull
 	public String getName() {
 		return name;
 	}
@@ -159,22 +170,27 @@
 		return false;
 	}
 
+	@NonNull
 	public Ref getLeaf() {
 		return this;
 	}
 
+	@NonNull
 	public Ref getTarget() {
 		return this;
 	}
 
+	@Nullable
 	public ObjectId getObjectId() {
 		return objectId;
 	}
 
+	@NonNull
 	public Storage getStorage() {
 		return storage;
 	}
 
+	@NonNull
 	@Override
 	public String toString() {
 		StringBuilder r = new StringBuilder();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/SymlinkTreeEntry.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdSet.java
similarity index 61%
rename from org.eclipse.jgit/src/org/eclipse/jgit/lib/SymlinkTreeEntry.java
rename to org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdSet.java
index c7e41bc..0b58484 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/SymlinkTreeEntry.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdSet.java
@@ -1,6 +1,5 @@
 /*
- * Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com>
- * Copyright (C) 2006-2007, Shawn O. Pearce <spearce@spearce.org>
+ * Copyright (C) 2015, Google Inc.
  * and other copyright owners as documented in the project's IP log.
  *
  * This program and the accompanying materials are made available
@@ -45,41 +44,21 @@
 package org.eclipse.jgit.lib;
 
 /**
- * A tree entry representing a symbolic link.
+ * Simple set of ObjectIds.
+ * <p>
+ * Usually backed by a read-only data structure such as
+ * {@link org.eclipse.jgit.internal.storage.file.PackIndex}. Mutable types like
+ * {@link ObjectIdOwnerMap} also implement the interface by checking keys.
  *
- * Note. Java cannot really handle these as file system objects.
- *
- * @deprecated To look up information about a single path, use
- * {@link org.eclipse.jgit.treewalk.TreeWalk#forPath(Repository, String, org.eclipse.jgit.revwalk.RevTree)}.
- * To lookup information about multiple paths at once, use a
- * {@link org.eclipse.jgit.treewalk.TreeWalk} and obtain the current entry's
- * information from its getter methods.
+ * @since 4.2
  */
-@Deprecated
-public class SymlinkTreeEntry extends TreeEntry {
-
+public interface ObjectIdSet {
 	/**
-	 * Construct a {@link SymlinkTreeEntry} with the specified name and SHA-1 in
-	 * the specified parent
+	 * Returns true if the objectId is contained within the collection.
 	 *
-	 * @param parent
-	 * @param id
-	 * @param nameUTF8
+	 * @param objectId
+	 *            the objectId to find
+	 * @return whether the collection contains the objectId.
 	 */
-	public SymlinkTreeEntry(final Tree parent, final ObjectId id,
-			final byte[] nameUTF8) {
-		super(parent, id, nameUTF8);
-	}
-
-	public FileMode getMode() {
-		return FileMode.SYMLINK;
-	}
-
-	public String toString() {
-		final StringBuilder r = new StringBuilder();
-		r.append(ObjectId.toString(getId()));
-		r.append(" S "); //$NON-NLS-1$
-		r.append(getFullName());
-		return r.toString();
-	}
+	boolean contains(AnyObjectId objectId);
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdSubclassMap.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdSubclassMap.java
index 69972dc..faed64b 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdSubclassMap.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdSubclassMap.java
@@ -60,16 +60,17 @@
  * @param <V>
  *            type of subclass of ObjectId that will be stored in the map.
  */
-public class ObjectIdSubclassMap<V extends ObjectId> implements Iterable<V> {
+public class ObjectIdSubclassMap<V extends ObjectId>
+		implements Iterable<V>, ObjectIdSet {
 	private static final int INITIAL_TABLE_SIZE = 2048;
 
-	private int size;
+	int size;
 
 	private int grow;
 
 	private int mask;
 
-	private V[] table;
+	V[] table;
 
 	/** Create an empty map. */
 	public ObjectIdSubclassMap() {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Ref.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Ref.java
index f119c44..a78a90f 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Ref.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Ref.java
@@ -43,6 +43,9 @@
 
 package org.eclipse.jgit.lib;
 
+import org.eclipse.jgit.annotations.NonNull;
+import org.eclipse.jgit.annotations.Nullable;
+
 /**
  * Pairing of a name and the {@link ObjectId} it currently has.
  * <p>
@@ -126,6 +129,7 @@
 	 *
 	 * @return name of this ref.
 	 */
+	@NonNull
 	public String getName();
 
 	/**
@@ -156,6 +160,7 @@
 	 *
 	 * @return the reference that actually stores the ObjectId value.
 	 */
+	@NonNull
 	public abstract Ref getLeaf();
 
 	/**
@@ -170,22 +175,27 @@
 	 *
 	 * @return the target reference, or {@code this}.
 	 */
+	@NonNull
 	public abstract Ref getTarget();
 
 	/**
 	 * Cached value of this ref.
 	 *
-	 * @return the value of this ref at the last time we read it.
+	 * @return the value of this ref at the last time we read it. May be
+	 *         {@code null} to indicate a ref that does not exist yet or a
+	 *         symbolic ref pointing to an unborn branch.
 	 */
+	@Nullable
 	public abstract ObjectId getObjectId();
 
 	/**
 	 * Cached value of <code>ref^{}</code> (the ref peeled to commit).
 	 *
 	 * @return if this ref is an annotated tag the id of the commit (or tree or
-	 *         blob) that the annotated tag refers to; null if this ref does not
-	 *         refer to an annotated tag.
+	 *         blob) that the annotated tag refers to; {@code null} if this ref
+	 *         does not refer to an annotated tag.
 	 */
+	@Nullable
 	public abstract ObjectId getPeeledObjectId();
 
 	/**
@@ -201,5 +211,6 @@
 	 *
 	 * @return type of ref.
 	 */
+	@NonNull
 	public abstract Storage getStorage();
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDatabase.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDatabase.java
index b62033c..c0c3862 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDatabase.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDatabase.java
@@ -51,6 +51,9 @@
 import java.util.List;
 import java.util.Map;
 
+import org.eclipse.jgit.annotations.NonNull;
+import org.eclipse.jgit.annotations.Nullable;
+
 /**
  * Abstraction of name to {@link ObjectId} mapping.
  * <p>
@@ -79,8 +82,10 @@
 	 * <p>
 	 * If the reference is nested deeper than this depth, the implementation
 	 * should either fail, or at least claim the reference does not exist.
+	 *
+	 * @since 4.2
 	 */
-	protected static final int MAX_SYMBOLIC_REF_DEPTH = 5;
+	public static final int MAX_SYMBOLIC_REF_DEPTH = 5;
 
 	/** Magic value for {@link #getRefs(String)} to return all references. */
 	public static final String ALL = "";//$NON-NLS-1$
@@ -132,6 +137,7 @@
 	 * @since 2.3
 	 * @see #isNameConflicting(String)
 	 */
+	@NonNull
 	public Collection<String> getConflictingNames(String name)
 			throws IOException {
 		Map<String, Ref> allRefs = getRefs(ALL);
@@ -169,6 +175,7 @@
 	 * @throws IOException
 	 *             the reference space cannot be accessed.
 	 */
+	@NonNull
 	public abstract RefUpdate newUpdate(String name, boolean detach)
 			throws IOException;
 
@@ -183,6 +190,7 @@
 	 * @throws IOException
 	 *             the reference space cannot be accessed.
 	 */
+	@NonNull
 	public abstract RefRename newRename(String fromName, String toName)
 			throws IOException;
 
@@ -193,6 +201,7 @@
 	 *
 	 * @return a new batch update object.
 	 */
+	@NonNull
 	public BatchRefUpdate newBatchUpdate() {
 		return new BatchRefUpdate(this);
 	}
@@ -223,6 +232,7 @@
 	 * @throws IOException
 	 *             the reference space cannot be accessed.
 	 */
+	@Nullable
 	public abstract Ref getRef(String name) throws IOException;
 
 	/**
@@ -238,6 +248,7 @@
 	 *             the reference space cannot be accessed.
 	 * @since 4.1
 	 */
+	@Nullable
 	public Ref exactRef(String name) throws IOException {
 		Ref ref = getRef(name);
 		if (ref == null || !name.equals(ref.getName())) {
@@ -261,6 +272,7 @@
 	 *             the reference space cannot be accessed.
 	 * @since 4.1
 	 */
+	@NonNull
 	public Map<String, Ref> exactRef(String... refs) throws IOException {
 		Map<String, Ref> result = new HashMap<>(refs.length);
 		for (String name : refs) {
@@ -285,6 +297,7 @@
 	 *             the reference space cannot be accessed.
 	 * @since 4.1
 	 */
+	@Nullable
 	public Ref firstExactRef(String... refs) throws IOException {
 		for (String name : refs) {
 			Ref ref = exactRef(name);
@@ -308,6 +321,7 @@
 	 * @throws IOException
 	 *             the reference space cannot be accessed.
 	 */
+	@NonNull
 	public abstract Map<String, Ref> getRefs(String prefix) throws IOException;
 
 	/**
@@ -322,6 +336,7 @@
 	 * @throws IOException
 	 *             the reference space cannot be accessed.
 	 */
+	@NonNull
 	public abstract List<Ref> getAdditionalRefs() throws IOException;
 
 	/**
@@ -338,10 +353,11 @@
 	 * @return {@code ref} if {@code ref.isPeeled()} is true; otherwise a new
 	 *         Ref object representing the same data as Ref, but isPeeled() will
 	 *         be true and getPeeledObjectId() will contain the peeled object
-	 *         (or null).
+	 *         (or {@code null}).
 	 * @throws IOException
 	 *             the reference space or object space cannot be accessed.
 	 */
+	@NonNull
 	public abstract Ref peel(Ref ref) throws IOException;
 
 	/**
@@ -365,9 +381,10 @@
 	 * @param name
 	 *            short name of ref to find, e.g. "master" to find
 	 *            "refs/heads/master" in map.
-	 * @return The first ref matching the name, or null if not found.
+	 * @return The first ref matching the name, or {@code null} if not found.
 	 * @since 3.4
 	 */
+	@Nullable
 	public static Ref findRef(Map<String, Ref> map, String name) {
 		for (String prefix : SEARCH_PATH) {
 			String fullname = prefix + name;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefRename.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefRename.java
index 05eb311..59f852b 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefRename.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefRename.java
@@ -170,7 +170,7 @@
 	 */
 	protected boolean needToUpdateHEAD() throws IOException {
 		Ref head = source.getRefDatabase().getRef(Constants.HEAD);
-		if (head.isSymbolic()) {
+		if (head != null && head.isSymbolic()) {
 			head = head.getTarget();
 			return head.getName().equals(source.getName());
 		}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefWriter.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefWriter.java
index 747fa62..3a02b22 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefWriter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefWriter.java
@@ -119,13 +119,20 @@
 				continue;
 			}
 
-			r.getObjectId().copyTo(tmp, w);
+			ObjectId objectId = r.getObjectId();
+			if (objectId == null) {
+				// Symrefs to unborn branches aren't advertised in the info/refs
+				// file.
+				continue;
+			}
+			objectId.copyTo(tmp, w);
 			w.write('\t');
 			w.write(r.getName());
 			w.write('\n');
 
-			if (r.getPeeledObjectId() != null) {
-				r.getPeeledObjectId().copyTo(tmp, w);
+			ObjectId peeledObjectId = r.getPeeledObjectId();
+			if (peeledObjectId != null) {
+				peeledObjectId.copyTo(tmp, w);
 				w.write('\t');
 				w.write(r.getName());
 				w.write("^{}\n"); //$NON-NLS-1$
@@ -167,14 +174,21 @@
 			if (r.getStorage() != Ref.Storage.PACKED)
 				continue;
 
-			r.getObjectId().copyTo(tmp, w);
+			ObjectId objectId = r.getObjectId();
+			if (objectId == null) {
+				// A packed ref cannot be a symref, let alone a symref
+				// to an unborn branch.
+				throw new NullPointerException();
+			}
+			objectId.copyTo(tmp, w);
 			w.write(' ');
 			w.write(r.getName());
 			w.write('\n');
 
-			if (r.getPeeledObjectId() != null) {
+			ObjectId peeledObjectId = r.getPeeledObjectId();
+			if (peeledObjectId != null) {
 				w.write('^');
-				r.getPeeledObjectId().copyTo(tmp, w);
+				peeledObjectId.copyTo(tmp, w);
 				w.write('\n');
 			}
 		}
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 d4c72cb..f826613 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java
@@ -64,6 +64,9 @@
 import java.util.Set;
 import java.util.concurrent.atomic.AtomicInteger;
 
+import org.eclipse.jgit.annotations.NonNull;
+import org.eclipse.jgit.annotations.Nullable;
+import org.eclipse.jgit.attributes.AttributesNodeProvider;
 import org.eclipse.jgit.dircache.DirCache;
 import org.eclipse.jgit.errors.AmbiguousObjectException;
 import org.eclipse.jgit.errors.CorruptObjectException;
@@ -137,6 +140,7 @@
 	}
 
 	/** @return listeners observing only events on this repository. */
+	@NonNull
 	public ListenerList getListenerList() {
 		return myListeners;
 	}
@@ -181,7 +185,16 @@
 	 */
 	public abstract void create(boolean bare) throws IOException;
 
-	/** @return local metadata directory; null if repository isn't local. */
+	/**
+	 * @return local metadata directory; {@code null} if repository isn't local.
+	 */
+	/*
+	 * TODO This method should be annotated as Nullable, because in some
+	 * specific configurations metadata is not located in the local file system
+	 * (for example in memory databases). In "usual" repositories this
+	 * annotation would only cause compiler errors at places where the actual
+	 * directory can never be null.
+	 */
 	public File getDirectory() {
 		return gitDir;
 	}
@@ -189,28 +202,52 @@
 	/**
 	 * @return the object database which stores this repository's data.
 	 */
+	@NonNull
 	public abstract ObjectDatabase getObjectDatabase();
 
 	/** @return a new inserter to create objects in {@link #getObjectDatabase()} */
+	@NonNull
 	public ObjectInserter newObjectInserter() {
 		return getObjectDatabase().newInserter();
 	}
 
 	/** @return a new reader to read objects from {@link #getObjectDatabase()} */
+	@NonNull
 	public ObjectReader newObjectReader() {
 		return getObjectDatabase().newReader();
 	}
 
 	/** @return the reference database which stores the reference namespace. */
+	@NonNull
 	public abstract RefDatabase getRefDatabase();
 
 	/**
 	 * @return the configuration of this repository
 	 */
+	@NonNull
 	public abstract StoredConfig getConfig();
 
 	/**
-	 * @return the used file system abstraction
+	 * @return a new {@link AttributesNodeProvider}. This
+	 *         {@link AttributesNodeProvider} is lazy loaded only once. It means
+	 *         that it will not be updated after loading. Prefer creating new
+	 *         instance for each use.
+	 * @since 4.2
+	 */
+	@NonNull
+	public abstract AttributesNodeProvider createAttributesNodeProvider();
+
+
+	/**
+	 * @return the used file system abstraction, or or {@code null} if
+	 *         repository isn't local.
+	 */
+	/*
+	 * TODO This method should be annotated as Nullable, because in some
+	 * specific configurations metadata is not located in the local file system
+	 * (for example in memory databases). In "usual" repositories this
+	 * annotation would only cause compiler errors at places where the actual
+	 * directory can never be null.
 	 */
 	public FS getFS() {
 		return fs;
@@ -244,6 +281,7 @@
 	 * @throws IOException
 	 *             the object store cannot be accessed.
 	 */
+	@NonNull
 	public ObjectLoader open(final AnyObjectId objectId)
 			throws MissingObjectException, IOException {
 		return getObjectDatabase().open(objectId);
@@ -271,6 +309,7 @@
 	 * @throws IOException
 	 *             the object store cannot be accessed.
 	 */
+	@NonNull
 	public ObjectLoader open(AnyObjectId objectId, int typeHint)
 			throws MissingObjectException, IncorrectObjectTypeException,
 			IOException {
@@ -289,6 +328,7 @@
 	 *             a symbolic ref was passed in and could not be resolved back
 	 *             to the base ref, as the symbolic ref could not be read.
 	 */
+	@NonNull
 	public RefUpdate updateRef(final String ref) throws IOException {
 		return updateRef(ref, false);
 	}
@@ -307,6 +347,7 @@
 	 *             a symbolic ref was passed in and could not be resolved back
 	 *             to the base ref, as the symbolic ref could not be read.
 	 */
+	@NonNull
 	public RefUpdate updateRef(final String ref, final boolean detach) throws IOException {
 		return getRefDatabase().newUpdate(ref, detach);
 	}
@@ -323,6 +364,7 @@
 	 *             the rename could not be performed.
 	 *
 	 */
+	@NonNull
 	public RefRename renameRef(final String fromRef, final String toRef) throws IOException {
 		return getRefDatabase().newRename(fromRef, toRef);
 	}
@@ -362,7 +404,8 @@
 	 *
 	 * @param revstr
 	 *            A git object references expression
-	 * @return an ObjectId or null if revstr can't be resolved to any ObjectId
+	 * @return an ObjectId or {@code null} if revstr can't be resolved to any
+	 *         ObjectId
 	 * @throws AmbiguousObjectException
 	 *             {@code revstr} contains an abbreviated ObjectId and this
 	 *             repository contains more than one object which match to the
@@ -376,6 +419,7 @@
 	 * @throws IOException
 	 *             on serious errors
 	 */
+	@Nullable
 	public ObjectId resolve(final String revstr)
 			throws AmbiguousObjectException, IncorrectObjectTypeException,
 			RevisionSyntaxException, IOException {
@@ -397,10 +441,12 @@
 	 * expects a branch or revision id.
 	 *
 	 * @param revstr
-	 * @return object id or ref name from resolved expression
+	 * @return object id or ref name from resolved expression or {@code null} if
+	 *         given expression cannot be resolved
 	 * @throws AmbiguousObjectException
 	 * @throws IOException
 	 */
+	@Nullable
 	public String simplify(final String revstr)
 			throws AmbiguousObjectException, IOException {
 		try (RevWalk rw = new RevWalk(this)) {
@@ -414,6 +460,7 @@
 		}
 	}
 
+	@Nullable
 	private Object resolve(final RevWalk rw, final String revstr)
 			throws IOException {
 		char[] revChars = revstr.toCharArray();
@@ -717,11 +764,13 @@
 		return true;
 	}
 
+	@Nullable
 	private RevObject parseSimple(RevWalk rw, String revstr) throws IOException {
 		ObjectId id = resolveSimple(revstr);
 		return id != null ? rw.parseAny(id) : null;
 	}
 
+	@Nullable
 	private ObjectId resolveSimple(final String revstr) throws IOException {
 		if (ObjectId.isId(revstr))
 			return ObjectId.fromString(revstr);
@@ -749,6 +798,7 @@
 		return null;
 	}
 
+	@Nullable
 	private String resolveReflogCheckout(int checkoutNo)
 			throws IOException {
 		ReflogReader reader = getReflogReader(Constants.HEAD);
@@ -790,6 +840,7 @@
 		return rw.parseCommit(entry.getNewId());
 	}
 
+	@Nullable
 	private ObjectId resolveAbbreviation(final String revstr) throws IOException,
 			AmbiguousObjectException {
 		AbbreviatedObjectId id = AbbreviatedObjectId.fromString(revstr);
@@ -826,11 +877,13 @@
 		getRefDatabase().close();
 	}
 
+	@NonNull
 	@SuppressWarnings("nls")
 	public String toString() {
 		String desc;
-		if (getDirectory() != null)
-			desc = getDirectory().getPath();
+		File directory = getDirectory();
+		if (directory != null)
+			desc = directory.getPath();
 		else
 			desc = getClass().getSimpleName() + "-" //$NON-NLS-1$
 					+ System.identityHashCode(this);
@@ -850,18 +903,24 @@
 	 * current ObjectId in hexadecimal string format.
 	 *
 	 * @return name of current branch (for example {@code refs/heads/master}),
-	 *         an ObjectId in hex format if the current branch is detached,
-	 *         or null if the repository is corrupt and has no HEAD reference.
+	 *         an ObjectId in hex format if the current branch is detached, or
+	 *         {@code null} if the repository is corrupt and has no HEAD
+	 *         reference.
 	 * @throws IOException
 	 */
+	@Nullable
 	public String getFullBranch() throws IOException {
 		Ref head = getRef(Constants.HEAD);
-		if (head == null)
+		if (head == null) {
 			return null;
-		if (head.isSymbolic())
+		}
+		if (head.isSymbolic()) {
 			return head.getTarget().getName();
-		if (head.getObjectId() != null)
-			return head.getObjectId().name();
+		}
+		ObjectId objectId = head.getObjectId();
+		if (objectId != null) {
+			return objectId.name();
+		}
 		return null;
 	}
 
@@ -872,16 +931,17 @@
 	 * leading prefix {@code refs/heads/} is removed from the reference before
 	 * it is returned to the caller.
 	 *
-	 * @return name of current branch (for example {@code master}), an
-	 *         ObjectId in hex format if the current branch is detached,
-	 *         or null if the repository is corrupt and has no HEAD reference.
+	 * @return name of current branch (for example {@code master}), an ObjectId
+	 *         in hex format if the current branch is detached, or {@code null}
+	 *         if the repository is corrupt and has no HEAD reference.
 	 * @throws IOException
 	 */
+	@Nullable
 	public String getBranch() throws IOException {
 		String name = getFullBranch();
 		if (name != null)
 			return shortenRefName(name);
-		return name;
+		return null;
 	}
 
 	/**
@@ -894,6 +954,7 @@
 	 *
 	 * @return unmodifiable collection of other known objects.
 	 */
+	@NonNull
 	public Set<ObjectId> getAdditionalHaves() {
 		return Collections.emptySet();
 	}
@@ -905,16 +966,53 @@
 	 *            the name of the ref to lookup. May be a short-hand form, e.g.
 	 *            "master" which is is automatically expanded to
 	 *            "refs/heads/master" if "refs/heads/master" already exists.
-	 * @return the Ref with the given name, or null if it does not exist
+	 * @return the Ref with the given name, or {@code null} if it does not exist
 	 * @throws IOException
+	 * @deprecated Use {@link #exactRef(String)} or {@link #findRef(String)}
+	 * instead.
 	 */
+	@Deprecated
+	@Nullable
 	public Ref getRef(final String name) throws IOException {
+		return findRef(name);
+	}
+
+	/**
+	 * Get a ref by name.
+	 *
+	 * @param name
+	 *            the name of the ref to lookup. Must not be a short-hand
+	 *            form; e.g., "master" is not automatically expanded to
+	 *            "refs/heads/master".
+	 * @return the Ref with the given name, or {@code null} if it does not exist
+	 * @throws IOException
+	 * @since 4.2
+	 */
+	@Nullable
+	public Ref exactRef(String name) throws IOException {
+		return getRefDatabase().exactRef(name);
+	}
+
+	/**
+	 * Search for a ref by (possibly abbreviated) name.
+	 *
+	 * @param name
+	 *            the name of the ref to lookup. May be a short-hand form, e.g.
+	 *            "master" which is is automatically expanded to
+	 *            "refs/heads/master" if "refs/heads/master" already exists.
+	 * @return the Ref with the given name, or {@code null} if it does not exist
+	 * @throws IOException
+	 * @since 4.2
+	 */
+	@Nullable
+	public Ref findRef(String name) throws IOException {
 		return getRefDatabase().getRef(name);
 	}
 
 	/**
 	 * @return mutable map of all known refs (heads, tags, remotes).
 	 */
+	@NonNull
 	public Map<String, Ref> getAllRefs() {
 		try {
 			return getRefDatabase().getRefs(RefDatabase.ALL);
@@ -928,6 +1026,7 @@
 	 *         of the entry contains the ref with the full tag name
 	 *         ("refs/tags/v1.0").
 	 */
+	@NonNull
 	public Map<String, Ref> getTags() {
 		try {
 			return getRefDatabase().getRefs(Constants.R_TAGS);
@@ -949,6 +1048,7 @@
 	 *         will be true and getPeeledObjectId will contain the peeled object
 	 *         (or null).
 	 */
+	@NonNull
 	public Ref peel(final Ref ref) {
 		try {
 			return getRefDatabase().peel(ref);
@@ -963,6 +1063,7 @@
 	/**
 	 * @return a map with all objects referenced by a peeled ref.
 	 */
+	@NonNull
 	public Map<AnyObjectId, Set<Ref>> getAllRefsByPeeledObjectId() {
 		Map<String, Ref> allRefs = getAllRefs();
 		Map<AnyObjectId, Set<Ref>> ret = new HashMap<AnyObjectId, Set<Ref>>(allRefs.size());
@@ -987,11 +1088,13 @@
 	}
 
 	/**
-	 * @return the index file location
+	 * @return the index file location or {@code null} if repository isn't
+	 *         local.
 	 * @throws NoWorkTreeException
 	 *             if this is bare, which implies it has no working directory.
 	 *             See {@link #isBare()}.
 	 */
+	@NonNull
 	public File getIndexFile() throws NoWorkTreeException {
 		if (isBare())
 			throw new NoWorkTreeException();
@@ -1016,6 +1119,7 @@
 	 *             the index file is using a format or extension that this
 	 *             library does not support.
 	 */
+	@NonNull
 	public DirCache readDirCache() throws NoWorkTreeException,
 			CorruptObjectException, IOException {
 		return DirCache.read(this);
@@ -1040,6 +1144,7 @@
 	 *             the index file is using a format or extension that this
 	 *             library does not support.
 	 */
+	@NonNull
 	public DirCache lockDirCache() throws NoWorkTreeException,
 			CorruptObjectException, IOException {
 		// we want DirCache to inform us so that we can inform registered
@@ -1065,6 +1170,7 @@
 	/**
 	 * @return an important state
 	 */
+	@NonNull
 	public RepositoryState getRepositoryState() {
 		if (isBare() || getDirectory() == null)
 			return RepositoryState.BARE;
@@ -1207,6 +1313,7 @@
 	 * @return normalized repository relative path or the empty
 	 *         string if the file is not relative to the work directory.
 	 */
+	@NonNull
 	public static String stripWorkDir(File workDir, File file) {
 		final String filePath = file.getPath();
 		final String workDirPath = workDir.getPath();
@@ -1241,6 +1348,7 @@
 	 *             if this is bare, which implies it has no working directory.
 	 *             See {@link #isBare()}.
 	 */
+	@NonNull
 	public File getWorkTree() throws NoWorkTreeException {
 		if (isBare())
 			throw new NoWorkTreeException();
@@ -1264,6 +1372,7 @@
 	 *
 	 * @return a more user friendly ref name
 	 */
+	@NonNull
 	public static String shortenRefName(String refName) {
 		if (refName.startsWith(Constants.R_HEADS))
 			return refName.substring(Constants.R_HEADS.length());
@@ -1279,9 +1388,10 @@
 	 * @return the remote branch name part of <code>refName</code>, i.e. without
 	 *         the <code>refs/remotes/&lt;remote&gt;</code> prefix, if
 	 *         <code>refName</code> represents a remote tracking branch;
-	 *         otherwise null.
+	 *         otherwise {@code null}.
 	 * @since 3.4
 	 */
+	@Nullable
 	public String shortenRemoteBranchName(String refName) {
 		for (String remote : getRemoteNames()) {
 			String remotePrefix = Constants.R_REMOTES + remote + "/"; //$NON-NLS-1$
@@ -1296,9 +1406,10 @@
 	 * @return the remote name part of <code>refName</code>, i.e. without the
 	 *         <code>refs/remotes/&lt;remote&gt;</code> prefix, if
 	 *         <code>refName</code> represents a remote tracking branch;
-	 *         otherwise null.
+	 *         otherwise {@code null}.
 	 * @since 3.4
 	 */
+	@Nullable
 	public String getRemoteName(String refName) {
 		for (String remote : getRemoteNames()) {
 			String remotePrefix = Constants.R_REMOTES + remote + "/"; //$NON-NLS-1$
@@ -1310,12 +1421,13 @@
 
 	/**
 	 * @param refName
-	 * @return a {@link ReflogReader} for the supplied refname, or null if the
-	 *         named ref does not exist.
+	 * @return a {@link ReflogReader} for the supplied refname, or {@code null}
+	 *         if the named ref does not exist.
 	 * @throws IOException
 	 *             the ref could not be accessed.
 	 * @since 3.0
 	 */
+	@Nullable
 	public abstract ReflogReader getReflogReader(String refName)
 			throws IOException;
 
@@ -1331,6 +1443,7 @@
 	 *             if this is bare, which implies it has no working directory.
 	 *             See {@link #isBare()}.
 	 */
+	@Nullable
 	public String readMergeCommitMsg() throws IOException, NoWorkTreeException {
 		return readCommitMsgFile(Constants.MERGE_MSG);
 	}
@@ -1365,6 +1478,7 @@
 	 *             See {@link #isBare()}.
 	 * @since 4.0
 	 */
+	@Nullable
 	public String readCommitEditMsg() throws IOException, NoWorkTreeException {
 		return readCommitMsgFile(Constants.COMMIT_EDITMSG);
 	}
@@ -1399,6 +1513,7 @@
 	 *             if this is bare, which implies it has no working directory.
 	 *             See {@link #isBare()}.
 	 */
+	@Nullable
 	public List<ObjectId> readMergeHeads() throws IOException, NoWorkTreeException {
 		if (isBare() || getDirectory() == null)
 			throw new NoWorkTreeException();
@@ -1442,6 +1557,7 @@
 	 *             if this is bare, which implies it has no working directory.
 	 *             See {@link #isBare()}.
 	 */
+	@Nullable
 	public ObjectId readCherryPickHead() throws IOException,
 			NoWorkTreeException {
 		if (isBare() || getDirectory() == null)
@@ -1465,6 +1581,7 @@
 	 *             if this is bare, which implies it has no working directory.
 	 *             See {@link #isBare()}.
 	 */
+	@Nullable
 	public ObjectId readRevertHead() throws IOException, NoWorkTreeException {
 		if (isBare() || getDirectory() == null)
 			throw new NoWorkTreeException();
@@ -1530,6 +1647,7 @@
 	 *             if this is bare, which implies it has no working directory.
 	 *             See {@link #isBare()}.
 	 */
+	@Nullable
 	public ObjectId readOrigHead() throws IOException, NoWorkTreeException {
 		if (isBare() || getDirectory() == null)
 			throw new NoWorkTreeException();
@@ -1550,6 +1668,7 @@
 	 *             if this is bare, which implies it has no working directory.
 	 *             See {@link #isBare()}.
 	 */
+	@Nullable
 	public String readSquashCommitMsg() throws IOException {
 		return readCommitMsgFile(Constants.SQUASH_MSG);
 	}
@@ -1571,6 +1690,7 @@
 		writeCommitMsg(squashMsgFile, msg);
 	}
 
+	@Nullable
 	private String readCommitMsgFile(String msgFilename) throws IOException {
 		if (isBare() || getDirectory() == null)
 			throw new NoWorkTreeException();
@@ -1579,6 +1699,9 @@
 		try {
 			return RawParseUtils.decode(IO.readFully(mergeMsgFile));
 		} catch (FileNotFoundException e) {
+			if (mergeMsgFile.exists()) {
+				throw e;
+			}
 			// the file has disappeared in the meantime ignore it
 			return null;
 		}
@@ -1601,15 +1724,20 @@
 	 * Read a file from the git directory.
 	 *
 	 * @param filename
-	 * @return the raw contents or null if the file doesn't exist or is empty
+	 * @return the raw contents or {@code null} if the file doesn't exist or is
+	 *         empty
 	 * @throws IOException
 	 */
+	@Nullable
 	private byte[] readGitDirectoryFile(String filename) throws IOException {
 		File file = new File(getDirectory(), filename);
 		try {
 			byte[] raw = IO.readFully(file);
 			return raw.length > 0 ? raw : null;
 		} catch (FileNotFoundException notFound) {
+			if (file.exists()) {
+				throw notFound;
+			}
 			return null;
 		}
 	}
@@ -1657,6 +1785,7 @@
 	 * @throws IOException
 	 * @since 3.2
 	 */
+	@NonNull
 	public List<RebaseTodoLine> readRebaseTodo(String path,
 			boolean includeComments)
 			throws IOException {
@@ -1686,6 +1815,7 @@
 	 * @return the names of all known remotes
 	 * @since 3.4
 	 */
+	@NonNull
 	public Set<String> getRemoteNames() {
 		return getConfig()
 				.getSubsections(ConfigConstants.CONFIG_REMOTE_SECTION);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/SymbolicRef.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/SymbolicRef.java
index 43b1510..eeab921 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/SymbolicRef.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/SymbolicRef.java
@@ -43,6 +43,9 @@
 
 package org.eclipse.jgit.lib;
 
+import org.eclipse.jgit.annotations.NonNull;
+import org.eclipse.jgit.annotations.Nullable;
+
 /**
  * A reference that indirectly points at another {@link Ref}.
  * <p>
@@ -62,11 +65,12 @@
 	 * @param target
 	 *            the ref we reference and derive our value from.
 	 */
-	public SymbolicRef(String refName, Ref target) {
+	public SymbolicRef(@NonNull String refName, @NonNull Ref target) {
 		this.name = refName;
 		this.target = target;
 	}
 
+	@NonNull
 	public String getName() {
 		return name;
 	}
@@ -75,6 +79,7 @@
 		return true;
 	}
 
+	@NonNull
 	public Ref getLeaf() {
 		Ref dst = getTarget();
 		while (dst.isSymbolic())
@@ -82,18 +87,22 @@
 		return dst;
 	}
 
+	@NonNull
 	public Ref getTarget() {
 		return target;
 	}
 
+	@Nullable
 	public ObjectId getObjectId() {
 		return getLeaf().getObjectId();
 	}
 
+	@NonNull
 	public Storage getStorage() {
 		return Storage.LOOSE;
 	}
 
+	@Nullable
 	public ObjectId getPeeledObjectId() {
 		return getLeaf().getPeeledObjectId();
 	}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Tree.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Tree.java
deleted file mode 100644
index 43bd489..0000000
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Tree.java
+++ /dev/null
@@ -1,601 +0,0 @@
-/*
- * Copyright (C) 2007, Robin Rosenberg <me@lathund.dewire.com>
- * Copyright (C) 2007-2008, Robin Rosenberg <robin.rosenberg@dewire.com>
- * Copyright (C) 2006-2008, Shawn O. Pearce <spearce@spearce.org>
- * and other copyright owners as documented in the project's IP log.
- *
- * This program and the accompanying materials are made available
- * under the terms of the Eclipse Distribution License v1.0 which
- * accompanies this distribution, is reproduced below, and is
- * available at http://www.eclipse.org/org/documents/edl-v10.php
- *
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or
- * without modification, are permitted provided that the following
- * conditions are met:
- *
- * - Redistributions of source code must retain the above copyright
- *   notice, this list of conditions and the following disclaimer.
- *
- * - Redistributions in binary form must reproduce the above
- *   copyright notice, this list of conditions and the following
- *   disclaimer in the documentation and/or other materials provided
- *   with the distribution.
- *
- * - Neither the name of the Eclipse Foundation, Inc. nor the
- *   names of its contributors may be used to endorse or promote
- *   products derived from this software without specific prior
- *   written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
- * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
- * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
- * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
- * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
- * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
- * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
- * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-package org.eclipse.jgit.lib;
-
-import java.io.IOException;
-import java.text.MessageFormat;
-
-import org.eclipse.jgit.errors.CorruptObjectException;
-import org.eclipse.jgit.errors.EntryExistsException;
-import org.eclipse.jgit.errors.MissingObjectException;
-import org.eclipse.jgit.errors.ObjectWritingException;
-import org.eclipse.jgit.internal.JGitText;
-import org.eclipse.jgit.util.RawParseUtils;
-
-/**
- * A representation of a Git tree entry. A Tree is a directory in Git.
- *
- * @deprecated To look up information about a single path, use
- * {@link org.eclipse.jgit.treewalk.TreeWalk#forPath(Repository, String, org.eclipse.jgit.revwalk.RevTree)}.
- * To lookup information about multiple paths at once, use a
- * {@link org.eclipse.jgit.treewalk.TreeWalk} and obtain the current entry's
- * information from its getter methods.
- */
-@Deprecated
-public class Tree extends TreeEntry {
-	private static final TreeEntry[] EMPTY_TREE = {};
-
-	/**
-	 * Compare two names represented as bytes. Since git treats names of trees and
-	 * blobs differently we have one parameter that represents a '/' for trees. For
-	 * other objects the value should be NUL. The names are compare by their positive
-	 * byte value (0..255).
-	 *
-	 * A blob and a tree with the same name will not compare equal.
-	 *
-	 * @param a name
-	 * @param b name
-	 * @param lasta '/' if a is a tree, else NUL
-	 * @param lastb '/' if b is a tree, else NUL
-	 *
-	 * @return &lt; 0 if a is sorted before b, 0 if they are the same, else b
-	 */
-	public static final int compareNames(final byte[] a, final byte[] b, final int lasta,final int lastb) {
-		return compareNames(a, b, 0, b.length, lasta, lastb);
-	}
-
-	private static final int compareNames(final byte[] a, final byte[] nameUTF8,
-			final int nameStart, final int nameEnd, final int lasta, int lastb) {
-		int j,k;
-		for (j = 0, k = nameStart; j < a.length && k < nameEnd; j++, k++) {
-			final int aj = a[j] & 0xff;
-			final int bk = nameUTF8[k] & 0xff;
-			if (aj < bk)
-				return -1;
-			else if (aj > bk)
-				return 1;
-		}
-		if (j < a.length) {
-			int aj = a[j]&0xff;
-			if (aj < lastb)
-				return -1;
-			else if (aj > lastb)
-				return 1;
-			else
-				if (j == a.length - 1)
-					return 0;
-				else
-					return -1;
-		}
-		if (k < nameEnd) {
-			int bk = nameUTF8[k] & 0xff;
-			if (lasta < bk)
-				return -1;
-			else if (lasta > bk)
-				return 1;
-			else
-				if (k == nameEnd - 1)
-					return 0;
-				else
-					return 1;
-		}
-		if (lasta < lastb)
-			return -1;
-		else if (lasta > lastb)
-			return 1;
-
-		final int namelength = nameEnd - nameStart;
-		if (a.length == namelength)
-			return 0;
-		else if (a.length < namelength)
-			return -1;
-		else
-			return 1;
-	}
-
-	private static final byte[] substring(final byte[] s, final int nameStart,
-			final int nameEnd) {
-		if (nameStart == 0 && nameStart == s.length)
-			return s;
-		final byte[] n = new byte[nameEnd - nameStart];
-		System.arraycopy(s, nameStart, n, 0, n.length);
-		return n;
-	}
-
-	private static final int binarySearch(final TreeEntry[] entries,
-			final byte[] nameUTF8, final int nameUTF8last, final int nameStart, final int nameEnd) {
-		if (entries.length == 0)
-			return -1;
-		int high = entries.length;
-		int low = 0;
-		do {
-			final int mid = (low + high) >>> 1;
-			final int cmp = compareNames(entries[mid].getNameUTF8(), nameUTF8,
-					nameStart, nameEnd, TreeEntry.lastChar(entries[mid]), nameUTF8last);
-			if (cmp < 0)
-				low = mid + 1;
-			else if (cmp == 0)
-				return mid;
-			else
-				high = mid;
-		} while (low < high);
-		return -(low + 1);
-	}
-
-	private final Repository db;
-
-	private TreeEntry[] contents;
-
-	/**
-	 * Constructor for a new Tree
-	 *
-	 * @param repo The repository that owns the Tree.
-	 */
-	public Tree(final Repository repo) {
-		super(null, null, null);
-		db = repo;
-		contents = EMPTY_TREE;
-	}
-
-	/**
-	 * Construct a Tree object with known content and hash value
-	 *
-	 * @param repo
-	 * @param myId
-	 * @param raw
-	 * @throws IOException
-	 */
-	public Tree(final Repository repo, final ObjectId myId, final byte[] raw)
-			throws IOException {
-		super(null, myId, null);
-		db = repo;
-		readTree(raw);
-	}
-
-	/**
-	 * Construct a new Tree under another Tree
-	 *
-	 * @param parent
-	 * @param nameUTF8
-	 */
-	public Tree(final Tree parent, final byte[] nameUTF8) {
-		super(parent, null, nameUTF8);
-		db = parent.getRepository();
-		contents = EMPTY_TREE;
-	}
-
-	/**
-	 * Construct a Tree with a known SHA-1 under another tree. Data is not yet
-	 * specified and will have to be loaded on demand.
-	 *
-	 * @param parent
-	 * @param id
-	 * @param nameUTF8
-	 */
-	public Tree(final Tree parent, final ObjectId id, final byte[] nameUTF8) {
-		super(parent, id, nameUTF8);
-		db = parent.getRepository();
-	}
-
-	public FileMode getMode() {
-		return FileMode.TREE;
-	}
-
-	/**
-	 * @return true if this Tree is the top level Tree.
-	 */
-	public boolean isRoot() {
-		return getParent() == null;
-	}
-
-	public Repository getRepository() {
-		return db;
-	}
-
-	/**
-	 * @return true of the data of this Tree is loaded
-	 */
-	public boolean isLoaded() {
-		return contents != null;
-	}
-
-	/**
-	 * Forget the in-memory data for this tree.
-	 */
-	public void unload() {
-		if (isModified())
-			throw new IllegalStateException(JGitText.get().cannotUnloadAModifiedTree);
-		contents = null;
-	}
-
-	/**
-	 * Adds a new or existing file with the specified name to this tree.
-	 * Trees are added if necessary as the name may contain '/':s.
-	 *
-	 * @param name Name
-	 * @return a {@link FileTreeEntry} for the added file.
-	 * @throws IOException
-	 */
-	public FileTreeEntry addFile(final String name) throws IOException {
-		return addFile(Repository.gitInternalSlash(Constants.encode(name)), 0);
-	}
-
-	/**
-	 * Adds a new or existing file with the specified name to this tree.
-	 * Trees are added if necessary as the name may contain '/':s.
-	 *
-	 * @param s an array containing the name
-	 * @param offset when the name starts in the tree.
-	 *
-	 * @return a {@link FileTreeEntry} for the added file.
-	 * @throws IOException
-	 */
-	public FileTreeEntry addFile(final byte[] s, final int offset)
-			throws IOException {
-		int slash;
-		int p;
-
-		for (slash = offset; slash < s.length && s[slash] != '/'; slash++) {
-			// search for path component terminator
-		}
-
-		ensureLoaded();
-		byte xlast = slash<s.length ? (byte)'/' : 0;
-		p = binarySearch(contents, s, xlast, offset, slash);
-		if (p >= 0 && slash < s.length && contents[p] instanceof Tree)
-			return ((Tree) contents[p]).addFile(s, slash + 1);
-
-		final byte[] newName = substring(s, offset, slash);
-		if (p >= 0)
-			throw new EntryExistsException(RawParseUtils.decode(newName));
-		else if (slash < s.length) {
-			final Tree t = new Tree(this, newName);
-			insertEntry(p, t);
-			return t.addFile(s, slash + 1);
-		} else {
-			final FileTreeEntry f = new FileTreeEntry(this, null, newName,
-					false);
-			insertEntry(p, f);
-			return f;
-		}
-	}
-
-	/**
-	 * Adds a new or existing Tree with the specified name to this tree.
-	 * Trees are added if necessary as the name may contain '/':s.
-	 *
-	 * @param name Name
-	 * @return a {@link FileTreeEntry} for the added tree.
-	 * @throws IOException
-	 */
-	public Tree addTree(final String name) throws IOException {
-		return addTree(Repository.gitInternalSlash(Constants.encode(name)), 0);
-	}
-
-	/**
-	 * Adds a new or existing Tree with the specified name to this tree.
-	 * Trees are added if necessary as the name may contain '/':s.
-	 *
-	 * @param s an array containing the name
-	 * @param offset when the name starts in the tree.
-	 *
-	 * @return a {@link FileTreeEntry} for the added tree.
-	 * @throws IOException
-	 */
-	public Tree addTree(final byte[] s, final int offset) throws IOException {
-		int slash;
-		int p;
-
-		for (slash = offset; slash < s.length && s[slash] != '/'; slash++) {
-			// search for path component terminator
-		}
-
-		ensureLoaded();
-		p = binarySearch(contents, s, (byte)'/', offset, slash);
-		if (p >= 0 && slash < s.length && contents[p] instanceof Tree)
-			return ((Tree) contents[p]).addTree(s, slash + 1);
-
-		final byte[] newName = substring(s, offset, slash);
-		if (p >= 0)
-			throw new EntryExistsException(RawParseUtils.decode(newName));
-
-		final Tree t = new Tree(this, newName);
-		insertEntry(p, t);
-		return slash == s.length ? t : t.addTree(s, slash + 1);
-	}
-
-	/**
-	 * Add the specified tree entry to this tree.
-	 *
-	 * @param e
-	 * @throws IOException
-	 */
-	public void addEntry(final TreeEntry e) throws IOException {
-		final int p;
-
-		ensureLoaded();
-		p = binarySearch(contents, e.getNameUTF8(), TreeEntry.lastChar(e), 0, e.getNameUTF8().length);
-		if (p < 0) {
-			e.attachParent(this);
-			insertEntry(p, e);
-		} else {
-			throw new EntryExistsException(e.getName());
-		}
-	}
-
-	private void insertEntry(int p, final TreeEntry e) {
-		final TreeEntry[] c = contents;
-		final TreeEntry[] n = new TreeEntry[c.length + 1];
-		p = -(p + 1);
-		for (int k = c.length - 1; k >= p; k--)
-			n[k + 1] = c[k];
-		n[p] = e;
-		for (int k = p - 1; k >= 0; k--)
-			n[k] = c[k];
-		contents = n;
-		setModified();
-	}
-
-	void removeEntry(final TreeEntry e) {
-		final TreeEntry[] c = contents;
-		final int p = binarySearch(c, e.getNameUTF8(), TreeEntry.lastChar(e), 0,
-				e.getNameUTF8().length);
-		if (p >= 0) {
-			final TreeEntry[] n = new TreeEntry[c.length - 1];
-			for (int k = c.length - 1; k > p; k--)
-				n[k - 1] = c[k];
-			for (int k = p - 1; k >= 0; k--)
-				n[k] = c[k];
-			contents = n;
-			setModified();
-		}
-	}
-
-	/**
-	 * @return number of members in this tree
-	 * @throws IOException
-	 */
-	public int memberCount() throws IOException {
-		ensureLoaded();
-		return contents.length;
-	}
-
-	/**
-	 * Return all members of the tree sorted in Git order.
-	 *
-	 * Entries are sorted by the numerical unsigned byte
-	 * values with (sub)trees having an implicit '/'. An
-	 * example of a tree with three entries. a:b is an
-	 * actual file name here.
-	 *
-	 * <p>
-	 * 100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391    a.b
-	 * 040000 tree 4277b6e69d25e5efa77c455340557b384a4c018a    a
-	 * 100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391    a:b
-	 *
-	 * @return all entries in this Tree, sorted.
-	 * @throws IOException
-	 */
-	public TreeEntry[] members() throws IOException {
-		ensureLoaded();
-		final TreeEntry[] c = contents;
-		if (c.length != 0) {
-			final TreeEntry[] r = new TreeEntry[c.length];
-			for (int k = c.length - 1; k >= 0; k--)
-				r[k] = c[k];
-			return r;
-		} else
-			return c;
-	}
-
-	private boolean exists(final String s, byte slast) throws IOException {
-		return findMember(s, slast) != null;
-	}
-
-	/**
-	 * @param path to the tree.
-	 * @return true if a tree with the specified path can be found under this
-	 *         tree.
-	 * @throws IOException
-	 */
-	public boolean existsTree(String path) throws IOException {
-		return exists(path,(byte)'/');
-	}
-
-	/**
-	 * @param path of the non-tree entry.
-	 * @return true if a blob, symlink, or gitlink with the specified name
-	 *         can be found under this tree.
-	 * @throws IOException
-	 */
-	public boolean existsBlob(String path) throws IOException {
-		return exists(path,(byte)0);
-	}
-
-	private TreeEntry findMember(final String s, byte slast) throws IOException {
-		return findMember(Repository.gitInternalSlash(Constants.encode(s)), slast, 0);
-	}
-
-	private TreeEntry findMember(final byte[] s, final byte slast, final int offset)
-			throws IOException {
-		int slash;
-		int p;
-
-		for (slash = offset; slash < s.length && s[slash] != '/'; slash++) {
-			// search for path component terminator
-		}
-
-		ensureLoaded();
-		byte xlast = slash<s.length ? (byte)'/' : slast;
-		p = binarySearch(contents, s, xlast, offset, slash);
-		if (p >= 0) {
-			final TreeEntry r = contents[p];
-			if (slash < s.length-1)
-				return r instanceof Tree ? ((Tree) r).findMember(s, slast, slash + 1)
-						: null;
-			return r;
-		}
-		return null;
-	}
-
-	/**
-	 * @param s
-	 *            blob name
-	 * @return a {@link TreeEntry} representing an object with the specified
-	 *         relative path.
-	 * @throws IOException
-	 */
-	public TreeEntry findBlobMember(String s) throws IOException {
-		return findMember(s,(byte)0);
-	}
-
-	/**
-	 * @param s Tree Name
-	 * @return a Tree with the name s or null
-	 * @throws IOException
-	 */
-	public TreeEntry findTreeMember(String s) throws IOException {
-		return findMember(s,(byte)'/');
-	}
-
-	private void ensureLoaded() throws IOException, MissingObjectException {
-		if (!isLoaded()) {
-			ObjectLoader ldr = db.open(getId(), Constants.OBJ_TREE);
-			readTree(ldr.getCachedBytes());
-		}
-	}
-
-	private void readTree(final byte[] raw) throws IOException {
-		final int rawSize = raw.length;
-		int rawPtr = 0;
-		TreeEntry[] temp;
-		int nextIndex = 0;
-
-		while (rawPtr < rawSize) {
-			while (rawPtr < rawSize && raw[rawPtr] != 0)
-				rawPtr++;
-			rawPtr++;
-			rawPtr += Constants.OBJECT_ID_LENGTH;
-			nextIndex++;
-		}
-
-		temp = new TreeEntry[nextIndex];
-		rawPtr = 0;
-		nextIndex = 0;
-		while (rawPtr < rawSize) {
-			int c = raw[rawPtr++];
-			if (c < '0' || c > '7')
-				throw new CorruptObjectException(getId(), JGitText.get().corruptObjectInvalidEntryMode);
-			int mode = c - '0';
-			for (;;) {
-				c = raw[rawPtr++];
-				if (' ' == c)
-					break;
-				else if (c < '0' || c > '7')
-					throw new CorruptObjectException(getId(), JGitText.get().corruptObjectInvalidMode);
-				mode <<= 3;
-				mode += c - '0';
-			}
-
-			int nameLen = 0;
-			while (raw[rawPtr + nameLen] != 0)
-				nameLen++;
-			final byte[] name = new byte[nameLen];
-			System.arraycopy(raw, rawPtr, name, 0, nameLen);
-			rawPtr += nameLen + 1;
-
-			final ObjectId id = ObjectId.fromRaw(raw, rawPtr);
-			rawPtr += Constants.OBJECT_ID_LENGTH;
-
-			final TreeEntry ent;
-			if (FileMode.REGULAR_FILE.equals(mode))
-				ent = new FileTreeEntry(this, id, name, false);
-			else if (FileMode.EXECUTABLE_FILE.equals(mode))
-				ent = new FileTreeEntry(this, id, name, true);
-			else if (FileMode.TREE.equals(mode))
-				ent = new Tree(this, id, name);
-			else if (FileMode.SYMLINK.equals(mode))
-				ent = new SymlinkTreeEntry(this, id, name);
-			else if (FileMode.GITLINK.equals(mode))
-				ent = new GitlinkTreeEntry(this, id, name);
-			else
-				throw new CorruptObjectException(getId(), MessageFormat.format(
-						JGitText.get().corruptObjectInvalidMode2, Integer.toOctalString(mode)));
-			temp[nextIndex++] = ent;
-		}
-
-		contents = temp;
-	}
-
-	/**
-	 * Format this Tree in canonical format.
-	 *
-	 * @return canonical encoding of the tree object.
-	 * @throws IOException
-	 *             the tree cannot be loaded, or its not in a writable state.
-	 */
-	public byte[] format() throws IOException {
-		TreeFormatter fmt = new TreeFormatter();
-		for (TreeEntry e : members()) {
-			ObjectId id = e.getId();
-			if (id == null)
-				throw new ObjectWritingException(MessageFormat.format(JGitText
-						.get().objectAtPathDoesNotHaveId, e.getFullName()));
-
-			fmt.append(e.getNameUTF8(), e.getMode(), id);
-		}
-		return fmt.toByteArray();
-	}
-
-	public String toString() {
-		final StringBuilder r = new StringBuilder();
-		r.append(ObjectId.toString(getId()));
-		r.append(" T "); //$NON-NLS-1$
-		r.append(getFullName());
-		return r.toString();
-	}
-
-}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/TreeEntry.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/TreeEntry.java
deleted file mode 100644
index a1ffa68..0000000
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/TreeEntry.java
+++ /dev/null
@@ -1,256 +0,0 @@
-/*
- * Copyright (C) 2007-2008, Robin Rosenberg <robin.rosenberg@dewire.com>
- * Copyright (C) 2006-2007, Shawn O. Pearce <spearce@spearce.org>
- * and other copyright owners as documented in the project's IP log.
- *
- * This program and the accompanying materials are made available
- * under the terms of the Eclipse Distribution License v1.0 which
- * accompanies this distribution, is reproduced below, and is
- * available at http://www.eclipse.org/org/documents/edl-v10.php
- *
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or
- * without modification, are permitted provided that the following
- * conditions are met:
- *
- * - Redistributions of source code must retain the above copyright
- *   notice, this list of conditions and the following disclaimer.
- *
- * - Redistributions in binary form must reproduce the above
- *   copyright notice, this list of conditions and the following
- *   disclaimer in the documentation and/or other materials provided
- *   with the distribution.
- *
- * - Neither the name of the Eclipse Foundation, Inc. nor the
- *   names of its contributors may be used to endorse or promote
- *   products derived from this software without specific prior
- *   written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
- * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
- * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
- * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
- * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
- * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
- * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
- * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-package org.eclipse.jgit.lib;
-
-import java.io.IOException;
-
-import org.eclipse.jgit.util.RawParseUtils;
-
-/**
- * This class represents an entry in a tree, like a blob or another tree.
- *
- * @deprecated To look up information about a single path, use
- * {@link org.eclipse.jgit.treewalk.TreeWalk#forPath(Repository, String, org.eclipse.jgit.revwalk.RevTree)}.
- * To lookup information about multiple paths at once, use a
- * {@link org.eclipse.jgit.treewalk.TreeWalk} and obtain the current entry's
- * information from its getter methods.
- */
-@Deprecated
-public abstract class TreeEntry implements Comparable {
-	private byte[] nameUTF8;
-
-	private Tree parent;
-
-	private ObjectId id;
-
-	/**
-	 * Construct a named tree entry.
-	 *
-	 * @param myParent
-	 * @param myId
-	 * @param myNameUTF8
-	 */
-	protected TreeEntry(final Tree myParent, final ObjectId myId,
-			final byte[] myNameUTF8) {
-		nameUTF8 = myNameUTF8;
-		parent = myParent;
-		id = myId;
-	}
-
-	/**
-	 * @return parent of this tree.
-	 */
-	public Tree getParent() {
-		return parent;
-	}
-
-	/**
-	 * Delete this entry.
-	 */
-	public void delete() {
-		getParent().removeEntry(this);
-		detachParent();
-	}
-
-	/**
-	 * Detach this entry from it's parent.
-	 */
-	public void detachParent() {
-		parent = null;
-	}
-
-	void attachParent(final Tree p) {
-		parent = p;
-	}
-
-	/**
-	 * @return the repository owning this entry.
-	 */
-	public Repository getRepository() {
-		return getParent().getRepository();
-	}
-
-	/**
-	 * @return the raw byte name of this entry.
-	 */
-	public byte[] getNameUTF8() {
-		return nameUTF8;
-	}
-
-	/**
-	 * @return the name of this entry.
-	 */
-	public String getName() {
-		if (nameUTF8 != null)
-			return RawParseUtils.decode(nameUTF8);
-		return null;
-	}
-
-	/**
-	 * Rename this entry.
-	 *
-	 * @param n The new name
-	 * @throws IOException
-	 */
-	public void rename(final String n) throws IOException {
-		rename(Constants.encode(n));
-	}
-
-	/**
-	 * Rename this entry.
-	 *
-	 * @param n The new name
-	 * @throws IOException
-	 */
-	public void rename(final byte[] n) throws IOException {
-		final Tree t = getParent();
-		if (t != null) {
-			delete();
-		}
-		nameUTF8 = n;
-		if (t != null) {
-			t.addEntry(this);
-		}
-	}
-
-	/**
-	 * @return true if this entry is new or modified since being loaded.
-	 */
-	public boolean isModified() {
-		return getId() == null;
-	}
-
-	/**
-	 * Mark this entry as modified.
-	 */
-	public void setModified() {
-		setId(null);
-	}
-
-	/**
-	 * @return SHA-1 of this tree entry (null for new unhashed entries)
-	 */
-	public ObjectId getId() {
-		return id;
-	}
-
-	/**
-	 * Set (update) the SHA-1 of this entry. Invalidates the id's of all
-	 * entries above this entry as they will have to be recomputed.
-	 *
-	 * @param n SHA-1 for this entry.
-	 */
-	public void setId(final ObjectId n) {
-		// If we have a parent and our id is being cleared or changed then force
-		// the parent's id to become unset as it depends on our id.
-		//
-		final Tree p = getParent();
-		if (p != null && id != n) {
-			if ((id == null && n != null) || (id != null && n == null)
-					|| !id.equals(n)) {
-				p.setId(null);
-			}
-		}
-
-		id = n;
-	}
-
-	/**
-	 * @return repository relative name of this entry
-	 */
-	public String getFullName() {
-		final StringBuilder r = new StringBuilder();
-		appendFullName(r);
-		return r.toString();
-	}
-
-	/**
-	 * @return repository relative name of the entry
-	 * FIXME better encoding
-	 */
-	public byte[] getFullNameUTF8() {
-		return getFullName().getBytes();
-	}
-
-	public int compareTo(final Object o) {
-		if (this == o)
-			return 0;
-		if (o instanceof TreeEntry)
-			return Tree.compareNames(nameUTF8, ((TreeEntry) o).nameUTF8, lastChar(this), lastChar((TreeEntry)o));
-		return -1;
-	}
-
-	/**
-	 * Helper for accessing tree/blob methods.
-	 *
-	 * @param treeEntry
-	 * @return '/' for Tree entries and NUL for non-treeish objects.
-	 */
-	final public static int lastChar(TreeEntry treeEntry) {
-		if (!(treeEntry instanceof Tree))
-			return '\0';
-		else
-			return '/';
-	}
-
-	/**
-	 * @return mode (type of object)
-	 */
-	public abstract FileMode getMode();
-
-	private void appendFullName(final StringBuilder r) {
-		final TreeEntry p = getParent();
-		final String n = getName();
-		if (p != null) {
-			p.appendFullName(r);
-			if (r.length() > 0) {
-				r.append('/');
-			}
-		}
-		if (n != null) {
-			r.append(n);
-		}
-	}
-}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeMessageFormatter.java b/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeMessageFormatter.java
index 191f3d8..82cbf36 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeMessageFormatter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeMessageFormatter.java
@@ -46,6 +46,7 @@
 import java.util.List;
 
 import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.Ref;
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.util.ChangeIdUtil;
@@ -76,22 +77,22 @@
 		List<String> commits = new ArrayList<String>();
 		List<String> others = new ArrayList<String>();
 		for (Ref ref : refsToMerge) {
-			if (ref.getName().startsWith(Constants.R_HEADS))
+			if (ref.getName().startsWith(Constants.R_HEADS)) {
 				branches.add("'" + Repository.shortenRefName(ref.getName()) //$NON-NLS-1$
 						+ "'"); //$NON-NLS-1$
-
-			else if (ref.getName().startsWith(Constants.R_REMOTES))
+			} else if (ref.getName().startsWith(Constants.R_REMOTES)) {
 				remoteBranches.add("'" //$NON-NLS-1$
 						+ Repository.shortenRefName(ref.getName()) + "'"); //$NON-NLS-1$
-
-			else if (ref.getName().startsWith(Constants.R_TAGS))
+			} else if (ref.getName().startsWith(Constants.R_TAGS)) {
 				tags.add("'" + Repository.shortenRefName(ref.getName()) + "'"); //$NON-NLS-1$ //$NON-NLS-2$
-
-			else if (ref.getName().equals(ref.getObjectId().getName()))
-				commits.add("'" + ref.getName() + "'"); //$NON-NLS-1$ //$NON-NLS-2$
-
-			else
-				others.add(ref.getName());
+			} else {
+				ObjectId objectId = ref.getObjectId();
+				if (objectId != null && ref.getName().equals(objectId.getName())) {
+					commits.add("'" + ref.getName() + "'"); //$NON-NLS-1$ //$NON-NLS-2$
+				} else {
+					others.add(ref.getName());
+				}
+			}
 		}
 
 		List<String> listings = new ArrayList<String>();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeResult.java b/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeResult.java
index dc3c772..106f9c7 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeResult.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeResult.java
@@ -70,7 +70,7 @@
 public class MergeResult<S extends Sequence> implements Iterable<MergeChunk> {
 	private final List<S> sequences;
 
-	private final IntList chunks = new IntList();
+	final IntList chunks = new IntList();
 
 	private boolean containsConflicts = false;
 
@@ -127,7 +127,7 @@
 		return sequences;
 	}
 
-	private static final ConflictState[] states = ConflictState.values();
+	static final ConflictState[] states = ConflictState.values();
 
 	/**
 	 * @return an iterator over the MergeChunks. The iterator does not support
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/merge/Merger.java b/org.eclipse.jgit/src/org/eclipse/jgit/merge/Merger.java
index 983bf5c..bee2d03 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/Merger.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/Merger.java
@@ -1,5 +1,6 @@
 /*
  * Copyright (C) 2008-2013, Google Inc.
+ * Copyright (C) 2016, Laurent Delaigue <laurent.delaigue@obeo.fr>
  * and other copyright owners as documented in the project's IP log.
  *
  * This program and the accompanying materials are made available
@@ -51,9 +52,11 @@
 import org.eclipse.jgit.errors.NoMergeBaseException.MergeBaseFailureReason;
 import org.eclipse.jgit.internal.JGitText;
 import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.lib.NullProgressMonitor;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.ObjectInserter;
 import org.eclipse.jgit.lib.ObjectReader;
+import org.eclipse.jgit.lib.ProgressMonitor;
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.revwalk.RevCommit;
 import org.eclipse.jgit.revwalk.RevObject;
@@ -88,6 +91,13 @@
 	protected RevTree[] sourceTrees;
 
 	/**
+	 * A progress monitor.
+	 *
+	 * @since 4.2
+	 */
+	protected ProgressMonitor monitor = NullProgressMonitor.INSTANCE;
+
+	/**
 	 * Create a new merge instance for a repository.
 	 *
 	 * @param local
@@ -290,4 +300,20 @@
 	 * @return resulting tree, if {@link #merge(AnyObjectId[])} returned true.
 	 */
 	public abstract ObjectId getResultTreeId();
+
+	/**
+	 * Set a progress monitor.
+	 *
+	 * @param monitor
+	 *            Monitor to use, can be null to indicate no progress reporting
+	 *            is desired.
+	 * @since 4.2
+	 */
+	public void setProgressMonitor(ProgressMonitor monitor) {
+		if (monitor == null) {
+			this.monitor = NullProgressMonitor.INSTANCE;
+		} else {
+			this.monitor = monitor;
+		}
+	}
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/merge/RecursiveMerger.java b/org.eclipse.jgit/src/org/eclipse/jgit/merge/RecursiveMerger.java
index aef47c5..e055644 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/RecursiveMerger.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/RecursiveMerger.java
@@ -57,8 +57,6 @@
 import java.util.TimeZone;
 
 import org.eclipse.jgit.dircache.DirCache;
-import org.eclipse.jgit.dircache.DirCacheBuilder;
-import org.eclipse.jgit.dircache.DirCacheEntry;
 import org.eclipse.jgit.errors.IncorrectObjectTypeException;
 import org.eclipse.jgit.errors.NoMergeBaseException;
 import org.eclipse.jgit.internal.JGitText;
@@ -70,7 +68,6 @@
 import org.eclipse.jgit.revwalk.filter.RevFilter;
 import org.eclipse.jgit.treewalk.AbstractTreeIterator;
 import org.eclipse.jgit.treewalk.EmptyTreeIterator;
-import org.eclipse.jgit.treewalk.TreeWalk;
 import org.eclipse.jgit.treewalk.WorkingTreeIterator;
 
 /**
@@ -181,7 +178,7 @@
 		WorkingTreeIterator oldWTreeIt = workingTreeIterator;
 		workingTreeIterator = null;
 		try {
-			dircache = dircacheFromTree(currentBase.getTree());
+			dircache = DirCache.read(reader, currentBase.getTree());
 			inCore = true;
 
 			List<RevCommit> parents = new ArrayList<RevCommit>();
@@ -256,30 +253,4 @@
 				new Date((time + 1) * 1000L),
 				TimeZone.getTimeZone("GMT+0000")); //$NON-NLS-1$
 	}
-
-	/**
-	 * Create a new in memory dircache which has the same content as a given
-	 * tree.
-	 *
-	 * @param treeId
-	 *            the tree which should be used to fill the dircache
-	 * @return a new in memory dircache
-	 * @throws IOException
-	 */
-	private DirCache dircacheFromTree(ObjectId treeId) throws IOException {
-		DirCache ret = DirCache.newInCore();
-		DirCacheBuilder aBuilder = ret.builder();
-		try (TreeWalk atw = new TreeWalk(reader)) {
-			atw.addTree(treeId);
-			atw.setRecursive(true);
-			while (atw.next()) {
-				DirCacheEntry e = new DirCacheEntry(atw.getRawPath());
-				e.setFileMode(atw.getFileMode(0));
-				e.setObjectId(atw.getObjectId(0));
-				aBuilder.add(e);
-			}
-		}
-		aBuilder.finish();
-		return ret;
-	}
 }
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 8a6343c..de08e4b 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java
@@ -786,11 +786,6 @@
 	private File writeMergedFile(MergeResult<RawText> result)
 			throws FileNotFoundException, IOException {
 		File workTree = db.getWorkTree();
-		if (workTree == null)
-			// TODO: This should be handled by WorkingTreeIterators which
-			// support write operations
-			throw new UnsupportedOperationException();
-
 		FS fs = db.getFS();
 		File of = new File(workTree, tw.getPathString());
 		File parentFolder = of.getParentFile();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/notes/NonNoteEntry.java b/org.eclipse.jgit/src/org/eclipse/jgit/notes/NonNoteEntry.java
index 6a2d44b..362328a 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/notes/NonNoteEntry.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/notes/NonNoteEntry.java
@@ -47,6 +47,7 @@
 import org.eclipse.jgit.lib.FileMode;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.TreeFormatter;
+import org.eclipse.jgit.util.Paths;
 
 /** A tree entry found in a note branch that isn't a valid note. */
 class NonNoteEntry extends ObjectId {
@@ -74,27 +75,8 @@
 	}
 
 	int pathCompare(byte[] bBuf, int bPos, int bLen, FileMode bMode) {
-		return pathCompare(name, 0, name.length, mode, //
-				bBuf, bPos, bLen, bMode);
-	}
-
-	private static int pathCompare(final byte[] aBuf, int aPos, final int aEnd,
-			final FileMode aMode, final byte[] bBuf, int bPos, final int bEnd,
-			final FileMode bMode) {
-		while (aPos < aEnd && bPos < bEnd) {
-			int cmp = (aBuf[aPos++] & 0xff) - (bBuf[bPos++] & 0xff);
-			if (cmp != 0)
-				return cmp;
-		}
-
-		if (aPos < aEnd)
-			return (aBuf[aPos] & 0xff) - lastPathChar(bMode);
-		if (bPos < bEnd)
-			return lastPathChar(aMode) - (bBuf[bPos] & 0xff);
-		return 0;
-	}
-
-	private static int lastPathChar(final FileMode mode) {
-		return FileMode.TREE.equals(mode.getBits()) ? '/' : '\0';
+		return Paths.compare(
+				name, 0, name.length, mode.getBits(),
+				bBuf, bPos, bLen, bMode.getBits());
 	}
 }
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 c23e4e3..e67ada6 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevCommit.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevCommit.java
@@ -44,12 +44,17 @@
 
 package org.eclipse.jgit.revwalk;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
+
 import java.io.IOException;
 import java.nio.charset.Charset;
+import java.nio.charset.IllegalCharsetNameException;
+import java.nio.charset.UnsupportedCharsetException;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 
+import org.eclipse.jgit.annotations.Nullable;
 import org.eclipse.jgit.errors.IncorrectObjectTypeException;
 import org.eclipse.jgit.errors.MissingObjectException;
 import org.eclipse.jgit.lib.AnyObjectId;
@@ -441,12 +446,12 @@
 	 * @return decoded commit message as a string. Never null.
 	 */
 	public final String getFullMessage() {
-		final byte[] raw = buffer;
-		final int msgB = RawParseUtils.commitMessage(raw, 0);
-		if (msgB < 0)
+		byte[] raw = buffer;
+		int msgB = RawParseUtils.commitMessage(raw, 0);
+		if (msgB < 0) {
 			return ""; //$NON-NLS-1$
-		final Charset enc = RawParseUtils.parseEncoding(raw);
-		return RawParseUtils.decode(enc, raw, msgB, raw.length);
+		}
+		return RawParseUtils.decode(guessEncoding(), raw, msgB, raw.length);
 	}
 
 	/**
@@ -465,16 +470,17 @@
 	 *         spanned multiple lines. Embedded LFs are converted to spaces.
 	 */
 	public final String getShortMessage() {
-		final byte[] raw = buffer;
-		final int msgB = RawParseUtils.commitMessage(raw, 0);
-		if (msgB < 0)
+		byte[] raw = buffer;
+		int msgB = RawParseUtils.commitMessage(raw, 0);
+		if (msgB < 0) {
 			return ""; //$NON-NLS-1$
+		}
 
-		final Charset enc = RawParseUtils.parseEncoding(raw);
-		final int msgE = RawParseUtils.endOfParagraph(raw, msgB);
-		String str = RawParseUtils.decode(enc, raw, msgB, msgE);
-		if (hasLF(raw, msgB, msgE))
+		int msgE = RawParseUtils.endOfParagraph(raw, msgB);
+		String str = RawParseUtils.decode(guessEncoding(), raw, msgB, msgE);
+		if (hasLF(raw, msgB, msgE)) {
 			str = StringUtils.replaceLineBreaksWithSpace(str);
+		}
 		return str;
 	}
 
@@ -488,18 +494,49 @@
 	/**
 	 * Determine the encoding of the commit message buffer.
 	 * <p>
+	 * Locates the "encoding" header (if present) and returns its value. Due to
+	 * corruption in the wild this may be an invalid encoding name that is not
+	 * recognized by any character encoding library.
+	 * <p>
+	 * If no encoding header is present, null.
+	 *
+	 * @return the preferred encoding of {@link #getRawBuffer()}; or null.
+	 * @since 4.2
+	 */
+	@Nullable
+	public final String getEncodingName() {
+		return RawParseUtils.parseEncodingName(buffer);
+	}
+
+	/**
+	 * Determine the encoding of the commit message buffer.
+	 * <p>
 	 * Locates the "encoding" header (if present) and then returns the proper
 	 * character set to apply to this buffer to evaluate its contents as
 	 * character data.
 	 * <p>
-	 * If no encoding header is present, {@link Constants#CHARSET} is assumed.
+	 * If no encoding header is present {@code UTF-8} is assumed.
 	 *
 	 * @return the preferred encoding of {@link #getRawBuffer()}.
+	 * @throws IllegalCharsetNameException
+	 *             if the character set requested by the encoding header is
+	 *             malformed and unsupportable.
+	 * @throws UnsupportedCharsetException
+	 *             if the JRE does not support the character set requested by
+	 *             the encoding header.
 	 */
 	public final Charset getEncoding() {
 		return RawParseUtils.parseEncoding(buffer);
 	}
 
+	private Charset guessEncoding() {
+		try {
+			return getEncoding();
+		} catch (IllegalCharsetNameException | UnsupportedCharsetException e) {
+			return UTF_8;
+		}
+	}
+
 	/**
 	 * Parse the footer lines (e.g. "Signed-off-by") for machine processing.
 	 * <p>
@@ -529,7 +566,7 @@
 
 		final int msgB = RawParseUtils.commitMessage(raw, 0);
 		final ArrayList<FooterLine> r = new ArrayList<FooterLine>(4);
-		final Charset enc = getEncoding();
+		final Charset enc = guessEncoding();
 		for (;;) {
 			ptr = RawParseUtils.prevLF(raw, ptr);
 			if (ptr <= msgB)
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevTag.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevTag.java
index bf2785e..81a54bf 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevTag.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevTag.java
@@ -45,8 +45,12 @@
 
 package org.eclipse.jgit.revwalk;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
+
 import java.io.IOException;
 import java.nio.charset.Charset;
+import java.nio.charset.IllegalCharsetNameException;
+import java.nio.charset.UnsupportedCharsetException;
 
 import org.eclipse.jgit.errors.CorruptObjectException;
 import org.eclipse.jgit.errors.IncorrectObjectTypeException;
@@ -162,7 +166,7 @@
 
 		int p = pos.value += 4; // "tag "
 		final int nameEnd = RawParseUtils.nextLF(rawTag, p) - 1;
-		tagName = RawParseUtils.decode(Constants.CHARSET, rawTag, p, nameEnd);
+		tagName = RawParseUtils.decode(UTF_8, rawTag, p, nameEnd);
 
 		if (walk.isRetainBody())
 			buffer = rawTag;
@@ -207,12 +211,12 @@
 	 * @return decoded tag message as a string. Never null.
 	 */
 	public final String getFullMessage() {
-		final byte[] raw = buffer;
-		final int msgB = RawParseUtils.tagMessage(raw, 0);
-		if (msgB < 0)
+		byte[] raw = buffer;
+		int msgB = RawParseUtils.tagMessage(raw, 0);
+		if (msgB < 0) {
 			return ""; //$NON-NLS-1$
-		final Charset enc = RawParseUtils.parseEncoding(raw);
-		return RawParseUtils.decode(enc, raw, msgB, raw.length);
+		}
+		return RawParseUtils.decode(guessEncoding(), raw, msgB, raw.length);
 	}
 
 	/**
@@ -231,19 +235,28 @@
 	 *         multiple lines. Embedded LFs are converted to spaces.
 	 */
 	public final String getShortMessage() {
-		final byte[] raw = buffer;
-		final int msgB = RawParseUtils.tagMessage(raw, 0);
-		if (msgB < 0)
+		byte[] raw = buffer;
+		int msgB = RawParseUtils.tagMessage(raw, 0);
+		if (msgB < 0) {
 			return ""; //$NON-NLS-1$
+		}
 
-		final Charset enc = RawParseUtils.parseEncoding(raw);
-		final int msgE = RawParseUtils.endOfParagraph(raw, msgB);
-		String str = RawParseUtils.decode(enc, raw, msgB, msgE);
-		if (RevCommit.hasLF(raw, msgB, msgE))
+		int msgE = RawParseUtils.endOfParagraph(raw, msgB);
+		String str = RawParseUtils.decode(guessEncoding(), raw, msgB, msgE);
+		if (RevCommit.hasLF(raw, msgB, msgE)) {
 			str = StringUtils.replaceLineBreaksWithSpace(str);
+		}
 		return str;
 	}
 
+	private Charset guessEncoding() {
+		try {
+			return RawParseUtils.parseEncoding(buffer);
+		} catch (IllegalCharsetNameException | UnsupportedCharsetException e) {
+			return UTF_8;
+		}
+	}
+
 	/**
 	 * Get a reference to the object this tag was placed on.
 	 * <p>
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 1176d95..c850493 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevWalk.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevWalk.java
@@ -1295,7 +1295,6 @@
 		retainOnReset = 0;
 		carryFlags = UNINTERESTING;
 		objects.clear();
-		reader.close();
 		roots.clear();
 		queue = new DateRevQueue();
 		pending = new StartGenerator(this);
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 5509fc6..702fd70 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
@@ -74,8 +74,6 @@
 public class FileBasedConfig extends StoredConfig {
 	private final File configFile;
 
-	private final FS fs;
-
 	private boolean utf8Bom;
 
 	private volatile FileSnapshot snapshot;
@@ -109,7 +107,6 @@
 	public FileBasedConfig(Config base, File cfgLocation, FS fs) {
 		super(base);
 		configFile = cfgLocation;
-		this.fs = fs;
 		this.snapshot = FileSnapshot.DIRTY;
 		this.hash = ObjectId.zeroId();
 	}
@@ -163,6 +160,9 @@
 				hash = newHash;
 			}
 		} catch (FileNotFoundException noFile) {
+			if (configFile.exists()) {
+				throw noFile;
+			}
 			clear();
 			snapshot = newSnapshot;
 		} catch (IOException e) {
@@ -200,7 +200,7 @@
 			out = Constants.encode(text);
 		}
 
-		final LockFile lf = new LockFile(getFile(), fs);
+		final LockFile lf = new LockFile(getFile());
 		if (!lf.lock())
 			throw new LockFailedException(getFile());
 		try {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackConfig.java
index a8835b7..d594e97 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackConfig.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackConfig.java
@@ -138,6 +138,65 @@
 	 */
 	public static final boolean DEFAULT_BUILD_BITMAPS = true;
 
+	/**
+	 * Default count of most recent commits to select for bitmaps. Only applies
+	 * when bitmaps are enabled: {@value}
+	 *
+	 * @see #setBitmapContiguousCommitCount(int)
+	 * @since 4.2
+	 */
+	public static final int DEFAULT_BITMAP_CONTIGUOUS_COMMIT_COUNT = 100;
+
+	/**
+	 * Count at which the span between selected commits changes from
+	 * "bitmapRecentCommitSpan" to "bitmapDistantCommitSpan". Only applies when
+	 * bitmaps are enabled: {@value}
+	 *
+	 * @see #setBitmapRecentCommitCount(int)
+	 * @since 4.2
+	 */
+	public static final int DEFAULT_BITMAP_RECENT_COMMIT_COUNT = 20000;
+
+	/**
+	 * Default spacing between commits in recent history when selecting commits
+	 * for bitmaps. Only applies when bitmaps are enabled: {@value}
+	 *
+	 * @see #setBitmapRecentCommitSpan(int)
+	 * @since 4.2
+	 */
+	public static final int DEFAULT_BITMAP_RECENT_COMMIT_SPAN = 100;
+
+	/**
+	 * Default spacing between commits in distant history when selecting commits
+	 * for bitmaps. Only applies when bitmaps are enabled: {@value}
+	 *
+	 * @see #setBitmapDistantCommitSpan(int)
+	 * @since 4.2
+	 */
+	public static final int DEFAULT_BITMAP_DISTANT_COMMIT_SPAN = 5000;
+
+	/**
+	 * Default count of branches required to activate inactive branch commit
+	 * selection. If the number of branches is less than this then bitmaps for
+	 * the entire commit history of all branches will be created, otherwise
+	 * branches marked as "inactive" will have coverage for only partial
+	 * history: {@value}
+	 *
+	 * @see #setBitmapExcessiveBranchCount(int)
+	 * @since 4.2
+	 */
+	public static final int DEFAULT_BITMAP_EXCESSIVE_BRANCH_COUNT = 100;
+
+	/**
+	 * Default age at which a branch is considered inactive. Age is taken as the
+	 * number of days ago that the most recent commit was made to a branch. Only
+	 * affects bitmap processing if bitmaps are enabled and the
+	 * "excessive branch count" has been exceeded: {@value}
+	 *
+	 * @see #setBitmapInactiveBranchAgeInDays(int)
+	 * @since 4.2
+	 */
+	public static final int DEFAULT_BITMAP_INACTIVE_BRANCH_AGE_IN_DAYS = 90;
 
 	private int compressionLevel = Deflater.DEFAULT_COMPRESSION;
 
@@ -169,6 +228,18 @@
 
 	private boolean buildBitmaps = DEFAULT_BUILD_BITMAPS;
 
+	private int bitmapContiguousCommitCount = DEFAULT_BITMAP_CONTIGUOUS_COMMIT_COUNT;
+
+	private int bitmapRecentCommitCount = DEFAULT_BITMAP_RECENT_COMMIT_COUNT;
+
+	private int bitmapRecentCommitSpan = DEFAULT_BITMAP_RECENT_COMMIT_SPAN;
+
+	private int bitmapDistantCommitSpan = DEFAULT_BITMAP_DISTANT_COMMIT_SPAN;
+
+	private int bitmapExcessiveBranchCount = DEFAULT_BITMAP_EXCESSIVE_BRANCH_COUNT;
+
+	private int bitmapInactiveBranchAgeInDays = DEFAULT_BITMAP_INACTIVE_BRANCH_AGE_IN_DAYS;
+
 	private boolean cutDeltaChains;
 
 	/** Create a default configuration. */
@@ -222,6 +293,12 @@
 		this.executor = cfg.executor;
 		this.indexVersion = cfg.indexVersion;
 		this.buildBitmaps = cfg.buildBitmaps;
+		this.bitmapContiguousCommitCount = cfg.bitmapContiguousCommitCount;
+		this.bitmapRecentCommitCount = cfg.bitmapRecentCommitCount;
+		this.bitmapRecentCommitSpan = cfg.bitmapRecentCommitSpan;
+		this.bitmapDistantCommitSpan = cfg.bitmapDistantCommitSpan;
+		this.bitmapExcessiveBranchCount = cfg.bitmapExcessiveBranchCount;
+		this.bitmapInactiveBranchAgeInDays = cfg.bitmapInactiveBranchAgeInDays;
 		this.cutDeltaChains = cfg.cutDeltaChains;
 	}
 
@@ -650,7 +727,7 @@
 	 *            oldest (most compatible) format available for the objects.
 	 * @see PackIndexWriter
 	 */
-	public void setIndexVersion(final int version) {
+	public void setIndexVersion(int version) {
 		indexVersion = version;
 	}
 
@@ -684,6 +761,162 @@
 	}
 
 	/**
+	 * Get the count of most recent commits for which to build bitmaps.
+	 *
+	 * Default setting: {@value #DEFAULT_BITMAP_CONTIGUOUS_COMMIT_COUNT}
+	 *
+	 * @return the count of most recent commits for which to build bitmaps
+	 * @since 4.2
+	 */
+	public int getBitmapContiguousCommitCount() {
+		return bitmapContiguousCommitCount;
+	}
+
+	/**
+	 * Set the count of most recent commits for which to build bitmaps.
+	 *
+	 * Default setting: {@value #DEFAULT_BITMAP_CONTIGUOUS_COMMIT_COUNT}
+	 *
+	 * @param count
+	 *            the count of most recent commits for which to build bitmaps
+	 * @since 4.2
+	 */
+	public void setBitmapContiguousCommitCount(int count) {
+		bitmapContiguousCommitCount = count;
+	}
+
+	/**
+	 * Get the count at which to switch from "bitmapRecentCommitSpan" to
+	 * "bitmapDistantCommitSpan".
+	 *
+	 * Default setting: {@value #DEFAULT_BITMAP_RECENT_COMMIT_COUNT}
+	 *
+	 * @return the count for switching between recent and distant spans
+	 * @since 4.2
+	 */
+	public int getBitmapRecentCommitCount() {
+		return bitmapRecentCommitCount;
+	}
+
+	/**
+	 * Set the count at which to switch from "bitmapRecentCommitSpan" to
+	 * "bitmapDistantCommitSpan".
+	 *
+	 * Default setting: {@value #DEFAULT_BITMAP_RECENT_COMMIT_COUNT}
+	 *
+	 * @param count
+	 *            the count for switching between recent and distant spans
+	 * @since 4.2
+	 */
+	public void setBitmapRecentCommitCount(int count) {
+		bitmapRecentCommitCount = count;
+	}
+
+	/**
+	 * Get the span of commits when building bitmaps for recent history.
+	 *
+	 * Default setting: {@value #DEFAULT_BITMAP_RECENT_COMMIT_SPAN}
+	 *
+	 * @return the span of commits when building bitmaps for recent history
+	 * @since 4.2
+	 */
+	public int getBitmapRecentCommitSpan() {
+		return bitmapRecentCommitSpan;
+	}
+
+	/**
+	 * Set the span of commits when building bitmaps for recent history.
+	 *
+	 * Default setting: {@value #DEFAULT_BITMAP_RECENT_COMMIT_SPAN}
+	 *
+	 * @param span
+	 *            the span of commits when building bitmaps for recent history
+	 * @since 4.2
+	 */
+	public void setBitmapRecentCommitSpan(int span) {
+		bitmapRecentCommitSpan = span;
+	}
+
+	/**
+	 * Get the span of commits when building bitmaps for distant history.
+	 *
+	 * Default setting: {@value #DEFAULT_BITMAP_DISTANT_COMMIT_SPAN}
+	 *
+	 * @return the span of commits when building bitmaps for distant history
+	 * @since 4.2
+	 */
+	public int getBitmapDistantCommitSpan() {
+		return bitmapDistantCommitSpan;
+	}
+
+	/**
+	 * Set the span of commits when building bitmaps for distant history.
+	 *
+	 * Default setting: {@value #DEFAULT_BITMAP_DISTANT_COMMIT_SPAN}
+	 *
+	 * @param span
+	 *            the span of commits when building bitmaps for distant history
+	 * @since 4.2
+	 */
+	public void setBitmapDistantCommitSpan(int span) {
+		bitmapDistantCommitSpan = span;
+	}
+
+	/**
+	 * Get the count of branches deemed "excessive". If the count of branches in
+	 * a repository exceeds this number and bitmaps are enabled, "inactive"
+	 * branches will have fewer bitmaps than "active" branches.
+	 *
+	 * Default setting: {@value #DEFAULT_BITMAP_EXCESSIVE_BRANCH_COUNT}
+	 *
+	 * @return the count of branches deemed "excessive"
+	 * @since 4.2
+	 */
+	public int getBitmapExcessiveBranchCount() {
+		return bitmapExcessiveBranchCount;
+	}
+
+	/**
+	 * Set the count of branches deemed "excessive". If the count of branches in
+	 * a repository exceeds this number and bitmaps are enabled, "inactive"
+	 * branches will have fewer bitmaps than "active" branches.
+	 *
+	 * Default setting: {@value #DEFAULT_BITMAP_EXCESSIVE_BRANCH_COUNT}
+	 *
+	 * @param count
+	 *            the count of branches deemed "excessive"
+	 * @since 4.2
+	 */
+	public void setBitmapExcessiveBranchCount(int count) {
+		bitmapExcessiveBranchCount = count;
+	}
+
+	/**
+	 * Get the the age in days that marks a branch as "inactive".
+	 *
+	 * Default setting: {@value #DEFAULT_BITMAP_INACTIVE_BRANCH_AGE_IN_DAYS}
+	 *
+	 * @return the age in days that marks a branch as "inactive"
+	 * @since 4.2
+	 */
+	public int getBitmapInactiveBranchAgeInDays() {
+		return bitmapInactiveBranchAgeInDays;
+	}
+
+	/**
+	 * Set the the age in days that marks a branch as "inactive".
+	 *
+	 * Default setting: {@value #DEFAULT_BITMAP_INACTIVE_BRANCH_AGE_IN_DAYS}
+	 *
+	 * @param ageInDays
+	 *            the age in days that marks a branch as "inactive"
+	 * @since 4.2
+	 */
+	public void setBitmapInactiveBranchAgeInDays(int ageInDays) {
+		bitmapInactiveBranchAgeInDays = ageInDays;
+	}
+
+	/**
 	 * Update properties by setting fields from the configuration.
 	 *
 	 * If a property's corresponding variable is not defined in the supplied
@@ -712,19 +945,36 @@
 		// These variables aren't standardized
 		//
 		setReuseDeltas(rc.getBoolean("pack", "reusedeltas", isReuseDeltas())); //$NON-NLS-1$ //$NON-NLS-2$
-		setReuseObjects(rc.getBoolean("pack", "reuseobjects", isReuseObjects())); //$NON-NLS-1$ //$NON-NLS-2$
-		setDeltaCompress(rc.getBoolean(
-				"pack", "deltacompression", isDeltaCompress())); //$NON-NLS-1$ //$NON-NLS-2$
-		setCutDeltaChains(rc.getBoolean(
-				"pack", "cutdeltachains", getCutDeltaChains())); //$NON-NLS-1$ //$NON-NLS-2$
-		setBuildBitmaps(rc.getBoolean("pack", "buildbitmaps", isBuildBitmaps())); //$NON-NLS-1$ //$NON-NLS-2$
+		setReuseObjects(
+				rc.getBoolean("pack", "reuseobjects", isReuseObjects())); //$NON-NLS-1$ //$NON-NLS-2$
+		setDeltaCompress(
+				rc.getBoolean("pack", "deltacompression", isDeltaCompress())); //$NON-NLS-1$ //$NON-NLS-2$
+		setCutDeltaChains(
+				rc.getBoolean("pack", "cutdeltachains", getCutDeltaChains())); //$NON-NLS-1$ //$NON-NLS-2$
+		setBuildBitmaps(
+				rc.getBoolean("pack", "buildbitmaps", isBuildBitmaps())); //$NON-NLS-1$ //$NON-NLS-2$
+		setBitmapContiguousCommitCount(
+				rc.getInt("pack", "bitmapcontiguouscommitcount", //$NON-NLS-1$ //$NON-NLS-2$
+						getBitmapContiguousCommitCount()));
+		setBitmapRecentCommitCount(rc.getInt("pack", "bitmaprecentcommitcount", //$NON-NLS-1$ //$NON-NLS-2$
+				getBitmapRecentCommitCount()));
+		setBitmapRecentCommitSpan(rc.getInt("pack", "bitmaprecentcommitspan", //$NON-NLS-1$ //$NON-NLS-2$
+				getBitmapRecentCommitSpan()));
+		setBitmapDistantCommitSpan(rc.getInt("pack", "bitmapdistantcommitspan", //$NON-NLS-1$ //$NON-NLS-2$
+				getBitmapDistantCommitSpan()));
+		setBitmapExcessiveBranchCount(rc.getInt("pack", //$NON-NLS-1$
+				"bitmapexcessivebranchcount", getBitmapExcessiveBranchCount())); //$NON-NLS-1$
+		setBitmapInactiveBranchAgeInDays(
+				rc.getInt("pack", "bitmapinactivebranchageindays", //$NON-NLS-1$ //$NON-NLS-2$
+						getBitmapInactiveBranchAgeInDays()));
 	}
 
 	public String toString() {
 		final StringBuilder b = new StringBuilder();
 		b.append("maxDeltaDepth=").append(getMaxDeltaDepth()); //$NON-NLS-1$
 		b.append(", deltaSearchWindowSize=").append(getDeltaSearchWindowSize()); //$NON-NLS-1$
-		b.append(", deltaSearchMemoryLimit=").append(getDeltaSearchMemoryLimit()); //$NON-NLS-1$
+		b.append(", deltaSearchMemoryLimit=") //$NON-NLS-1$
+				.append(getDeltaSearchMemoryLimit());
 		b.append(", deltaCacheSize=").append(getDeltaCacheSize()); //$NON-NLS-1$
 		b.append(", deltaCacheLimit=").append(getDeltaCacheLimit()); //$NON-NLS-1$
 		b.append(", compressionLevel=").append(getCompressionLevel()); //$NON-NLS-1$
@@ -735,6 +985,18 @@
 		b.append(", reuseObjects=").append(isReuseObjects()); //$NON-NLS-1$
 		b.append(", deltaCompress=").append(isDeltaCompress()); //$NON-NLS-1$
 		b.append(", buildBitmaps=").append(isBuildBitmaps()); //$NON-NLS-1$
+		b.append(", bitmapContiguousCommitCount=") //$NON-NLS-1$
+				.append(getBitmapContiguousCommitCount());
+		b.append(", bitmapRecentCommitCount=") //$NON-NLS-1$
+				.append(getBitmapRecentCommitCount());
+		b.append(", bitmapRecentCommitSpan=") //$NON-NLS-1$
+				.append(getBitmapRecentCommitSpan());
+		b.append(", bitmapDistantCommitSpan=") //$NON-NLS-1$
+				.append(getBitmapDistantCommitSpan());
+		b.append(", bitmapExcessiveBranchCount=") //$NON-NLS-1$
+				.append(getBitmapExcessiveBranchCount());
+		b.append(", bitmapInactiveBranchAge=") //$NON-NLS-1$
+				.append(getBitmapInactiveBranchAgeInDays());
 		return b.toString();
 	}
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/AmazonS3.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/AmazonS3.java
index d3cdba5..4069a64 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/AmazonS3.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/AmazonS3.java
@@ -56,10 +56,10 @@
 import java.net.URL;
 import java.net.URLConnection;
 import java.security.DigestOutputStream;
+import java.security.GeneralSecurityException;
 import java.security.InvalidKeyException;
 import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
-import java.security.spec.InvalidKeySpecException;
 import java.text.MessageFormat;
 import java.text.SimpleDateFormat;
 import java.util.ArrayList;
@@ -175,7 +175,7 @@
 	private final String acl;
 
 	/** Maximum number of times to try an operation. */
-	private final int maxAttempts;
+	final int maxAttempts;
 
 	/** Encryption algorithm, may be a null instance that provides pass-through. */
 	private final WalkEncryption encryption;
@@ -186,6 +186,19 @@
 	/** S3 Bucket Domain. */
 	private final String domain;
 
+	/** Property names used in amazon connection configuration file. */
+	interface Keys {
+		String ACCESS_KEY = "accesskey"; //$NON-NLS-1$
+		String SECRET_KEY = "secretkey"; //$NON-NLS-1$
+		String PASSWORD = "password"; //$NON-NLS-1$
+		String CRYPTO_ALG = "crypto.algorithm"; //$NON-NLS-1$
+		String CRYPTO_VER = "crypto.version"; //$NON-NLS-1$
+		String ACL = "acl"; //$NON-NLS-1$
+		String DOMAIN = "domain"; //$NON-NLS-1$
+		String HTTP_RETRY = "httpclient.retry-max"; //$NON-NLS-1$
+		String TMP_DIR = "tmpdir"; //$NON-NLS-1$
+	}
+
 	/**
 	 * Create a new S3 client for the supplied user information.
 	 * <p>
@@ -219,17 +232,18 @@
 	 *
 	 */
 	public AmazonS3(final Properties props) {
-		domain = props.getProperty("domain", "s3.amazonaws.com"); //$NON-NLS-1$ //$NON-NLS-2$
-		publicKey = props.getProperty("accesskey"); //$NON-NLS-1$
+		domain = props.getProperty(Keys.DOMAIN, "s3.amazonaws.com"); //$NON-NLS-1$
+
+		publicKey = props.getProperty(Keys.ACCESS_KEY);
 		if (publicKey == null)
 			throw new IllegalArgumentException(JGitText.get().missingAccesskey);
 
-		final String secret = props.getProperty("secretkey"); //$NON-NLS-1$
+		final String secret = props.getProperty(Keys.SECRET_KEY);
 		if (secret == null)
 			throw new IllegalArgumentException(JGitText.get().missingSecretkey);
 		privateKey = new SecretKeySpec(Constants.encodeASCII(secret), HMAC);
 
-		final String pacl = props.getProperty("acl", "PRIVATE"); //$NON-NLS-1$ //$NON-NLS-2$
+		final String pacl = props.getProperty(Keys.ACL, "PRIVATE"); //$NON-NLS-1$
 		if (StringUtils.equalsIgnoreCase("PRIVATE", pacl)) //$NON-NLS-1$
 			acl = "private"; //$NON-NLS-1$
 		else if (StringUtils.equalsIgnoreCase("PUBLIC", pacl)) //$NON-NLS-1$
@@ -242,26 +256,16 @@
 			throw new IllegalArgumentException("Invalid acl: " + pacl); //$NON-NLS-1$
 
 		try {
-			final String cPas = props.getProperty("password"); //$NON-NLS-1$
-			if (cPas != null) {
-				String cAlg = props.getProperty("crypto.algorithm"); //$NON-NLS-1$
-				if (cAlg == null)
-					cAlg = "PBEWithMD5AndDES"; //$NON-NLS-1$
-				encryption = new WalkEncryption.ObjectEncryptionV2(cAlg, cPas);
-			} else {
-				encryption = WalkEncryption.NONE;
-			}
-		} catch (InvalidKeySpecException e) {
-			throw new IllegalArgumentException(JGitText.get().invalidEncryption, e);
-		} catch (NoSuchAlgorithmException e) {
+			encryption = WalkEncryption.instance(props);
+		} catch (GeneralSecurityException e) {
 			throw new IllegalArgumentException(JGitText.get().invalidEncryption, e);
 		}
 
-		maxAttempts = Integer.parseInt(props.getProperty(
-				"httpclient.retry-max", "3")); //$NON-NLS-1$ //$NON-NLS-2$
+		maxAttempts = Integer
+				.parseInt(props.getProperty(Keys.HTTP_RETRY, "3")); //$NON-NLS-1$
 		proxySelector = ProxySelector.getDefault();
 
-		String tmp = props.getProperty("tmpdir"); //$NON-NLS-1$
+		String tmp = props.getProperty(Keys.TMP_DIR);
 		tmpDir = tmp != null && tmp.length() > 0 ? new File(tmp) : null;
 	}
 
@@ -479,7 +483,7 @@
 		return encryption.encrypt(new DigestOutputStream(buffer, md5));
 	}
 
-	private void putImpl(final String bucket, final String key,
+	void putImpl(final String bucket, final String key,
 			final byte[] csum, final TemporaryBuffer buf,
 			ProgressMonitor monitor, String monitorTask) throws IOException {
 		if (monitor == null)
@@ -518,7 +522,7 @@
 		throw maxAttempts(JGitText.get().s3ActionWriting, key);
 	}
 
-	private IOException error(final String action, final String key,
+	IOException error(final String action, final String key,
 			final HttpURLConnection c) throws IOException {
 		final IOException err = new IOException(MessageFormat.format(
 				JGitText.get().amazonS3ActionFailed, action, key,
@@ -543,7 +547,7 @@
 		return err;
 	}
 
-	private IOException maxAttempts(final String action, final String key) {
+	IOException maxAttempts(final String action, final String key) {
 		return new IOException(MessageFormat.format(
 				JGitText.get().amazonS3ActionFailedGivingUp, action, key,
 				Integer.valueOf(maxAttempts)));
@@ -555,7 +559,7 @@
 		return open(method, bucket, key, noArgs);
 	}
 
-	private HttpURLConnection open(final String method, final String bucket,
+	HttpURLConnection open(final String method, final String bucket,
 			final String key, final Map<String, String> args)
 			throws IOException {
 		final StringBuilder urlstr = new StringBuilder();
@@ -592,7 +596,7 @@
 		return c;
 	}
 
-	private void authorize(final HttpURLConnection c) throws IOException {
+	void authorize(final HttpURLConnection c) throws IOException {
 		final Map<String, List<String>> reqHdr = c.getRequestProperties();
 		final SortedMap<String, String> sigHdr = new TreeMap<String, String>();
 		for (final Map.Entry<String, List<String>> entry : reqHdr.entrySet()) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackConnection.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackConnection.java
index 7f9cec7..aa36aeb 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackConnection.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackConnection.java
@@ -143,7 +143,9 @@
 		final int timeout = transport.getTimeout();
 		if (timeout > 0) {
 			final Thread caller = Thread.currentThread();
-			myTimer = new InterruptTimer(caller.getName() + "-Timer"); //$NON-NLS-1$
+			if (myTimer == null) {
+				myTimer = new InterruptTimer(caller.getName() + "-Timer"); //$NON-NLS-1$
+			}
 			timeoutIn = new TimeoutInputStream(myIn, myTimer);
 			timeoutOut = new TimeoutOutputStream(myOut, myTimer);
 			timeoutIn.setTimeout(timeout * 1000);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackFetchConnection.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackFetchConnection.java
index cf13582..754cf36 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackFetchConnection.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackFetchConnection.java
@@ -464,8 +464,12 @@
 		final PacketLineOut p = statelessRPC ? pckState : pckOut;
 		boolean first = true;
 		for (final Ref r : want) {
+			ObjectId objectId = r.getObjectId();
+			if (objectId == null) {
+				continue;
+			}
 			try {
-				if (walk.parseAny(r.getObjectId()).has(REACHABLE)) {
+				if (walk.parseAny(objectId).has(REACHABLE)) {
 					// We already have this object. Asking for it is
 					// not a very good idea.
 					//
@@ -478,7 +482,7 @@
 
 			final StringBuilder line = new StringBuilder(46);
 			line.append("want "); //$NON-NLS-1$
-			line.append(r.getObjectId().name());
+			line.append(objectId.name());
 			if (first) {
 				line.append(enableCapabilities());
 				first = false;
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 24fb3be..963de35 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackPushConnection.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackPushConnection.java
@@ -44,6 +44,8 @@
 
 package org.eclipse.jgit.transport;
 
+import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_ATOMIC;
+
 import java.io.IOException;
 import java.io.OutputStream;
 import java.text.MessageFormat;
@@ -110,17 +112,15 @@
 	public static final String CAPABILITY_SIDE_BAND_64K = GitProtocolConstants.CAPABILITY_SIDE_BAND_64K;
 
 	private final boolean thinPack;
+	private final boolean atomic;
 
+	private boolean capableAtomic;
 	private boolean capableDeleteRefs;
-
 	private boolean capableReport;
-
 	private boolean capableSideBand;
-
 	private boolean capableOfsDelta;
 
 	private boolean sentCommand;
-
 	private boolean writePack;
 
 	/** Time in milliseconds spent transferring the pack data. */
@@ -135,6 +135,7 @@
 	public BasePackPushConnection(final PackTransport packTransport) {
 		super(packTransport);
 		thinPack = transport.isPushThin();
+		atomic = transport.isPushAtomic();
 	}
 
 	public void push(final ProgressMonitor monitor,
@@ -224,6 +225,11 @@
 	private void writeCommands(final Collection<RemoteRefUpdate> refUpdates,
 			final ProgressMonitor monitor, OutputStream outputStream) throws IOException {
 		final String capabilities = enableCapabilities(monitor, outputStream);
+		if (atomic && !capableAtomic) {
+			throw new TransportException(uri,
+					JGitText.get().atomicPushNotSupported);
+		}
+
 		for (final RemoteRefUpdate rru : refUpdates) {
 			if (!capableDeleteRefs && rru.isDelete()) {
 				rru.setStatus(Status.REJECTED_NODELETE);
@@ -231,9 +237,14 @@
 			}
 
 			final StringBuilder sb = new StringBuilder();
-			final Ref advertisedRef = getRef(rru.getRemoteName());
-			final ObjectId oldId = (advertisedRef == null ? ObjectId.zeroId()
-					: advertisedRef.getObjectId());
+			ObjectId oldId = rru.getExpectedOldObjectId();
+			if (oldId == null) {
+				final Ref advertised = getRef(rru.getRemoteName());
+				oldId = advertised != null ? advertised.getObjectId() : null;
+				if (oldId == null) {
+					oldId = ObjectId.zeroId();
+				}
+			}
 			sb.append(oldId.name());
 			sb.append(' ');
 			sb.append(rru.getNewObjectId().name());
@@ -259,6 +270,8 @@
 	private String enableCapabilities(final ProgressMonitor monitor,
 			OutputStream outputStream) {
 		final StringBuilder line = new StringBuilder();
+		if (atomic)
+			capableAtomic = wantCapability(line, CAPABILITY_ATOMIC);
 		capableReport = wantCapability(line, CAPABILITY_REPORT_STATUS);
 		capableDeleteRefs = wantCapability(line, CAPABILITY_DELETE_REFS);
 		capableOfsDelta = wantCapability(line, CAPABILITY_OFS_DELTA);
@@ -372,7 +385,8 @@
 		final int oldTimeout = timeoutIn.getTimeout();
 		final int sendTime = (int) Math.min(packTransferTime, 28800000L);
 		try {
-			timeoutIn.setTimeout(10 * Math.max(sendTime, oldTimeout));
+			int timeout = 10 * Math.max(sendTime, oldTimeout);
+			timeoutIn.setTimeout((timeout < 0) ? Integer.MAX_VALUE : timeout);
 			return pckIn.readString();
 		} finally {
 			timeoutIn.setTimeout(oldTimeout);
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 776a9f6..a20e652 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseReceivePack.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseReceivePack.java
@@ -293,18 +293,20 @@
 		db = into;
 		walk = new RevWalk(db);
 
-		final ReceiveConfig cfg = db.getConfig().get(ReceiveConfig.KEY);
-		objectChecker = cfg.newObjectChecker();
-		allowCreates = cfg.allowCreates;
+		TransferConfig tc = db.getConfig().get(TransferConfig.KEY);
+		objectChecker = tc.newReceiveObjectChecker();
+
+		ReceiveConfig rc = db.getConfig().get(ReceiveConfig.KEY);
+		allowCreates = rc.allowCreates;
 		allowAnyDeletes = true;
-		allowBranchDeletes = cfg.allowDeletes;
-		allowNonFastForwards = cfg.allowNonFastForwards;
-		allowOfsDelta = cfg.allowOfsDelta;
+		allowBranchDeletes = rc.allowDeletes;
+		allowNonFastForwards = rc.allowNonFastForwards;
+		allowOfsDelta = rc.allowOfsDelta;
 		advertiseRefsHook = AdvertiseRefsHook.DEFAULT;
 		refFilter = RefFilter.DEFAULT;
 		advertisedHaves = new HashSet<ObjectId>();
 		clientShallowCommits = new HashSet<ObjectId>();
-		signedPushConfig = cfg.signedPush;
+		signedPushConfig = rc.signedPush;
 	}
 
 	/** Configuration for receive operations. */
@@ -315,32 +317,13 @@
 			}
 		};
 
-		final boolean checkReceivedObjects;
-		final boolean allowLeadingZeroFileMode;
-		final boolean allowInvalidPersonIdent;
-		final boolean safeForWindows;
-		final boolean safeForMacOS;
-
 		final boolean allowCreates;
 		final boolean allowDeletes;
 		final boolean allowNonFastForwards;
 		final boolean allowOfsDelta;
-
 		final SignedPushConfig signedPush;
 
 		ReceiveConfig(final Config config) {
-			checkReceivedObjects = config.getBoolean(
-					"receive", "fsckobjects", //$NON-NLS-1$ //$NON-NLS-2$
-					config.getBoolean("transfer", "fsckobjects", false)); //$NON-NLS-1$ //$NON-NLS-2$
-			allowLeadingZeroFileMode = checkReceivedObjects
-					&& config.getBoolean("fsck", "allowLeadingZeroFileMode", false); //$NON-NLS-1$ //$NON-NLS-2$
-			allowInvalidPersonIdent = checkReceivedObjects
-					&& config.getBoolean("fsck", "allowInvalidPersonIdent", false); //$NON-NLS-1$ //$NON-NLS-2$
-			safeForWindows = checkReceivedObjects
-					&& config.getBoolean("fsck", "safeForWindows", false); //$NON-NLS-1$ //$NON-NLS-2$
-			safeForMacOS = checkReceivedObjects
-					&& config.getBoolean("fsck", "safeForMacOS", false); //$NON-NLS-1$ //$NON-NLS-2$
-
 			allowCreates = true;
 			allowDeletes = !config.getBoolean("receive", "denydeletes", false); //$NON-NLS-1$ //$NON-NLS-2$
 			allowNonFastForwards = !config.getBoolean("receive", //$NON-NLS-1$
@@ -349,16 +332,6 @@
 					true);
 			signedPush = SignedPushConfig.KEY.parse(config);
 		}
-
-		ObjectChecker newObjectChecker() {
-			if (!checkReceivedObjects)
-				return null;
-			return new ObjectChecker()
-				.setAllowLeadingZeroFileMode(allowLeadingZeroFileMode)
-				.setAllowInvalidPersonIdent(allowInvalidPersonIdent)
-				.setSafeForWindows(safeForWindows)
-				.setSafeForMacOS(safeForMacOS);
-		}
 	}
 
 	/**
@@ -1372,16 +1345,21 @@
 				}
 			}
 
-			if (cmd.getType() == ReceiveCommand.Type.DELETE && ref != null
-					&& !ObjectId.zeroId().equals(cmd.getOldId())
-					&& !ref.getObjectId().equals(cmd.getOldId())) {
-				// Delete commands can be sent with the old id matching our
-				// advertised value, *OR* with the old id being 0{40}. Any
-				// other requested old id is invalid.
-				//
-				cmd.setResult(Result.REJECTED_OTHER_REASON,
-						JGitText.get().invalidOldIdSent);
-				continue;
+			if (cmd.getType() == ReceiveCommand.Type.DELETE && ref != null) {
+				ObjectId id = ref.getObjectId();
+				if (id == null) {
+					id = ObjectId.zeroId();
+				}
+				if (!ObjectId.zeroId().equals(cmd.getOldId())
+						&& !id.equals(cmd.getOldId())) {
+					// Delete commands can be sent with the old id matching our
+					// advertised value, *OR* with the old id being 0{40}. Any
+					// other requested old id is invalid.
+					//
+					cmd.setResult(Result.REJECTED_OTHER_REASON,
+							JGitText.get().invalidOldIdSent);
+					continue;
+				}
 			}
 
 			if (cmd.getType() == ReceiveCommand.Type.UPDATE) {
@@ -1391,8 +1369,15 @@
 					cmd.setResult(Result.REJECTED_OTHER_REASON, JGitText.get().noSuchRef);
 					continue;
 				}
+				ObjectId id = ref.getObjectId();
+				if (id == null) {
+					// We cannot update unborn branch
+					cmd.setResult(Result.REJECTED_OTHER_REASON,
+							JGitText.get().cannotUpdateUnbornBranch);
+					continue;
+				}
 
-				if (!ref.getObjectId().equals(cmd.getOldId())) {
+				if (!id.equals(cmd.getOldId())) {
 					// A properly functioning client will send the same
 					// object id we advertised.
 					//
@@ -1468,10 +1453,7 @@
 	 * @since 3.6
 	 */
 	protected void failPendingCommands() {
-		for (ReceiveCommand cmd : commands) {
-			if (cmd.getResult() == Result.NOT_ATTEMPTED)
-				cmd.setResult(Result.REJECTED_OTHER_REASON, JGitText.get().transactionAborted);
-		}
+		ReceiveCommand.abort(commands);
 	}
 
 	/**
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleFetchConnection.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleFetchConnection.java
index e53c04b..8038fa4 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleFetchConnection.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleFetchConnection.java
@@ -161,16 +161,23 @@
 	}
 
 	private String readLine(final byte[] hdrbuf) throws IOException {
-		bin.mark(hdrbuf.length);
-		final int cnt = bin.read(hdrbuf);
-		int lf = 0;
-		while (lf < cnt && hdrbuf[lf] != '\n')
-			lf++;
-		bin.reset();
-		IO.skipFully(bin, lf);
-		if (lf < cnt && hdrbuf[lf] == '\n')
-			IO.skipFully(bin, 1);
-		return RawParseUtils.decode(Constants.CHARSET, hdrbuf, 0, lf);
+		StringBuilder line = new StringBuilder();
+		boolean done = false;
+		while (!done) {
+			bin.mark(hdrbuf.length);
+			final int cnt = bin.read(hdrbuf);
+			int lf = 0;
+			while (lf < cnt && hdrbuf[lf] != '\n')
+				lf++;
+			bin.reset();
+			IO.skipFully(bin, lf);
+			if (lf < cnt && hdrbuf[lf] == '\n') {
+				IO.skipFully(bin, 1);
+				done = true;
+			}
+			line.append(RawParseUtils.decode(Constants.CHARSET, hdrbuf, 0, lf));
+		}
+		return line.toString();
 	}
 
 	public boolean didFetchTestConnectivity() {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ChainingCredentialsProvider.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ChainingCredentialsProvider.java
index 3e0ee2f..3941d3c 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ChainingCredentialsProvider.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ChainingCredentialsProvider.java
@@ -113,19 +113,18 @@
 			throws UnsupportedCredentialItem {
 		for (CredentialsProvider p : credentialProviders) {
 			if (p.supports(items)) {
-				p.get(uri, items);
-				if (isAnyNull(items))
+				if (!p.get(uri, items)) {
+					if (p.isInteractive()) {
+						return false; // user cancelled the request
+					}
 					continue;
+				}
+				if (isAnyNull(items)) {
+					continue;
+				}
 				return true;
 			}
 		}
 		return false;
 	}
-
-	private boolean isAnyNull(CredentialItem... items) {
-		for (CredentialItem i : items)
-			if (i == null)
-				return true;
-		return false;
-	}
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/Connection.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/Connection.java
index 0ff9fce..da288ec 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/Connection.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/Connection.java
@@ -59,8 +59,7 @@
  *
  * @see Transport
  */
-public interface Connection {
-
+public interface Connection extends AutoCloseable {
 	/**
 	 * Get the complete map of refs advertised as available for fetching or
 	 * pushing.
@@ -108,6 +107,10 @@
 	 * <p>
 	 * If additional messages were produced by the remote peer, these should
 	 * still be retained in the connection instance for {@link #getMessages()}.
+	 * <p>
+	 * {@code AutoClosable.close()} declares that it throws {@link Exception}.
+	 * Implementers shouldn't throw checked exceptions. This override narrows
+	 * the signature to prevent them from doing so.
 	 */
 	public void close();
 
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/CredentialsProvider.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/CredentialsProvider.java
index 464d0f9..4800f68 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/CredentialsProvider.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/CredentialsProvider.java
@@ -81,6 +81,20 @@
 	}
 
 	/**
+	 * @param items
+	 *            credential items to check
+	 * @return {@code true} if any of the passed items is null, {@code false}
+	 *         otherwise
+	 * @since 4.2
+	 */
+	protected static boolean isAnyNull(CredentialItem... items) {
+		for (CredentialItem i : items)
+			if (i == null)
+				return true;
+		return false;
+	}
+
+	/**
 	 * Check if the provider is interactive with the end-user.
 	 *
 	 * An interactive provider may try to open a dialog box, or prompt for input
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/Daemon.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/Daemon.java
index 03f7c72..d9e0b93 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/Daemon.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/Daemon.java
@@ -79,7 +79,7 @@
 
 	private boolean run;
 
-	private Thread acceptThread;
+	Thread acceptThread;
 
 	private int timeout;
 
@@ -87,9 +87,9 @@
 
 	private volatile RepositoryResolver<DaemonClient> repositoryResolver;
 
-	private volatile UploadPackFactory<DaemonClient> uploadPackFactory;
+	volatile UploadPackFactory<DaemonClient> uploadPackFactory;
 
-	private volatile ReceivePackFactory<DaemonClient> receivePackFactory;
+	volatile ReceivePackFactory<DaemonClient> receivePackFactory;
 
 	/** Configure a daemon to listen on any available network port. */
 	public Daemon() {
@@ -326,7 +326,7 @@
 		}
 	}
 
-	private void startClient(final Socket s) {
+	void startClient(final Socket s) {
 		final DaemonClient dc = new DaemonClient(this);
 
 		final SocketAddress peer = s.getRemoteSocketAddress();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchProcess.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchProcess.java
index 9aae1c3..8cb36c7 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchProcess.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchProcess.java
@@ -314,8 +314,7 @@
 		File meta = transport.local.getDirectory();
 		if (meta == null)
 			return;
-		final LockFile lock = new LockFile(new File(meta, "FETCH_HEAD"), //$NON-NLS-1$
-				transport.local.getFS());
+		final LockFile lock = new LockFile(new File(meta, "FETCH_HEAD")); //$NON-NLS-1$
 		try {
 			if (lock.lock()) {
 				final Writer w = new OutputStreamWriter(lock.getOutputStream());
@@ -397,11 +396,17 @@
 	private void expandFetchTags() throws TransportException {
 		final Map<String, Ref> haveRefs = localRefs();
 		for (final Ref r : conn.getRefs()) {
-			if (!isTag(r))
+			if (!isTag(r)) {
 				continue;
+			}
+			ObjectId id = r.getObjectId();
+			if (id == null) {
+				continue;
+			}
 			final Ref local = haveRefs.get(r.getName());
-			if (local == null || !r.getObjectId().equals(local.getObjectId()))
+			if (local == null || !id.equals(local.getObjectId())) {
 				wantTag(r);
+			}
 		}
 	}
 
@@ -413,6 +418,11 @@
 	private void want(final Ref src, final RefSpec spec)
 			throws TransportException {
 		final ObjectId newId = src.getObjectId();
+		if (newId == null) {
+			throw new NullPointerException(MessageFormat.format(
+					JGitText.get().transportProvidedRefWithNoObjectId,
+					src.getName()));
+		}
 		if (spec.getDestination() != null) {
 			final TrackingRefUpdate tru = createUpdate(spec, newId);
 			if (newId.equals(tru.getOldObjectId()))
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/HMACSHA1NonceGenerator.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/HMACSHA1NonceGenerator.java
index 7e9434a..622680a 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/HMACSHA1NonceGenerator.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/HMACSHA1NonceGenerator.java
@@ -42,6 +42,7 @@
  */
 package org.eclipse.jgit.transport;
 
+import java.io.File;
 import java.io.UnsupportedEncodingException;
 import java.security.InvalidKeyException;
 import java.security.NoSuchAlgorithmException;
@@ -85,12 +86,16 @@
 	public synchronized String createNonce(Repository repo, long timestamp)
 			throws IllegalStateException {
 		String path;
-		if (repo instanceof DfsRepository)
+		if (repo instanceof DfsRepository) {
 			path = ((DfsRepository) repo).getDescription().getRepositoryName();
-		else if (repo.getDirectory() != null)
-			path = repo.getDirectory().getPath();
-		else
-			throw new IllegalStateException();
+		} else {
+			File directory = repo.getDirectory();
+			if (directory != null) {
+				path = directory.getPath();
+			} else {
+				throw new IllegalStateException();
+			}
+		}
 
 		String input = path + ":" + String.valueOf(timestamp); //$NON-NLS-1$
 		byte[] rawHmac;
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 3594ea9..998f280 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/HttpAuthMethod.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/HttpAuthMethod.java
@@ -219,7 +219,8 @@
 			if (credentialsProvider.supports(u, p)
 					&& credentialsProvider.get(uri, u, p)) {
 				username = u.getValue();
-				password = new String(p.getValue());
+				char[] v = p.getValue();
+				password = (v == null) ? null : new String(p.getValue());
 				p.clear();
 			} else
 				return false;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/JschSession.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/JschSession.java
index b4a0902..1dfe5d9 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/JschSession.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/JschSession.java
@@ -71,8 +71,8 @@
  * to the constructor.
  */
 public class JschSession implements RemoteSession {
-	private final Session sock;
-	private final URIish uri;
+	final Session sock;
+	final URIish uri;
 
 	/**
 	 * Create a new session object by passing the real Jsch session and the URI
@@ -119,7 +119,7 @@
 	private class JschProcess extends Process {
 		private ChannelExec channel;
 
-		private final int timeout;
+		final int timeout;
 
 		private InputStream inputStream;
 
@@ -141,7 +141,7 @@
 		 * @throws IOException
 		 *             on problems opening streams
 		 */
-		private JschProcess(final String commandName, int tms)
+		JschProcess(final String commandName, int tms)
 				throws TransportException, IOException {
 			timeout = tms;
 			try {
@@ -149,14 +149,27 @@
 				channel.setCommand(commandName);
 				setupStreams();
 				channel.connect(timeout > 0 ? timeout * 1000 : 0);
-				if (!channel.isConnected())
+				if (!channel.isConnected()) {
+					closeOutputStream();
 					throw new TransportException(uri,
 							JGitText.get().connectionFailed);
+				}
 			} catch (JSchException e) {
+				closeOutputStream();
 				throw new TransportException(uri, e.getMessage(), e);
 			}
 		}
 
+		private void closeOutputStream() {
+			if (outputStream != null) {
+				try {
+					outputStream.close();
+				} catch (IOException ioe) {
+					// ignore
+				}
+			}
+		}
+
 		private void setupStreams() throws IOException {
 			inputStream = channel.getInputStream();
 
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/NetRCCredentialsProvider.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/NetRCCredentialsProvider.java
index 7490999..4037545 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/NetRCCredentialsProvider.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/NetRCCredentialsProvider.java
@@ -105,12 +105,11 @@
 			throw new UnsupportedCredentialItem(uri, i.getClass().getName()
 					+ ":" + i.getPromptText()); //$NON-NLS-1$
 		}
-		return true;
+		return !isAnyNull(items);
 	}
 
 	@Override
 	public boolean isInteractive() {
 		return false;
 	}
-
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PackParser.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PackParser.java
index 918df94..b96fe88 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PackParser.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PackParser.java
@@ -122,14 +122,14 @@
 
 	private InputStream in;
 
-	private byte[] buf;
+	byte[] buf;
 
 	/** Position in the input stream of {@code buf[0]}. */
 	private long bBase;
 
 	private int bOffset;
 
-	private int bAvail;
+	int bAvail;
 
 	private ObjectChecker objCheck;
 
@@ -1049,8 +1049,11 @@
 			final byte[] data) throws IOException {
 		if (objCheck != null) {
 			try {
-				objCheck.check(type, data);
+				objCheck.check(id, type, data);
 			} catch (CorruptObjectException e) {
+				if (e.getErrorType() != null) {
+					throw e;
+				}
 				throw new CorruptObjectException(MessageFormat.format(
 						JGitText.get().invalidObject,
 						Constants.typeString(type),
@@ -1141,13 +1144,13 @@
 	}
 
 	// Consume cnt bytes from the buffer.
-	private void use(final int cnt) {
+	void use(final int cnt) {
 		bOffset += cnt;
 		bAvail -= cnt;
 	}
 
 	// Ensure at least need bytes are available in in {@link #buf}.
-	private int fill(final Source src, final int need) throws IOException {
+	int fill(final Source src, final int need) throws IOException {
 		while (bAvail < need) {
 			int next = bOffset + bAvail;
 			int free = buf.length - next;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ProgressSpinner.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ProgressSpinner.java
new file mode 100644
index 0000000..ac048a1
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ProgressSpinner.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2015, 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 java.nio.charset.StandardCharsets.UTF_8;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A simple spinner connected to an {@code OutputStream}.
+ * <p>
+ * This is class is not thread-safe. The update method may only be used from a
+ * single thread. Updates are sent only as frequently as {@link #update()} is
+ * invoked by the caller, and are capped at no more than 2 times per second by
+ * requiring at least 500 milliseconds between updates.
+ *
+ * @since 4.2
+ */
+public class ProgressSpinner {
+	private static final long MIN_REFRESH_MILLIS = 500;
+	private static final char[] STATES = new char[] { '-', '\\', '|', '/' };
+
+	private final OutputStream out;
+	private String msg;
+	private int state;
+	private boolean write;
+	private boolean shown;
+	private long nextUpdateMillis;
+
+	/**
+	 * Initialize a new spinner.
+	 *
+	 * @param out
+	 *            where to send output to.
+	 */
+	public ProgressSpinner(OutputStream out) {
+		this.out = out;
+		this.write = true;
+	}
+
+	/**
+	 * Begin a time consuming task.
+	 *
+	 * @param title
+	 *            description of the task, suitable for human viewing.
+	 * @param delay
+	 *            delay to wait before displaying anything at all.
+	 * @param delayUnits
+	 *            unit for {@code delay}.
+	 */
+	public void beginTask(String title, long delay, TimeUnit delayUnits) {
+		msg = title;
+		state = 0;
+		shown = false;
+
+		long now = System.currentTimeMillis();
+		if (delay > 0) {
+			nextUpdateMillis = now + delayUnits.toMillis(delay);
+		} else {
+			send(now);
+		}
+	}
+
+	/** Update the spinner if it is showing. */
+	public void update() {
+		long now = System.currentTimeMillis();
+		if (now >= nextUpdateMillis) {
+			send(now);
+			state = (state + 1) % STATES.length;
+		}
+	}
+
+	private void send(long now) {
+		StringBuilder buf = new StringBuilder(msg.length() + 16);
+		buf.append('\r').append(msg).append("... ("); //$NON-NLS-1$
+		buf.append(STATES[state]);
+		buf.append(")  "); //$NON-NLS-1$
+		shown = true;
+		write(buf.toString());
+		nextUpdateMillis = now + MIN_REFRESH_MILLIS;
+	}
+
+	/**
+	 * Denote the current task completed.
+	 *
+	 * @param result
+	 *            text to print after the task's title
+	 *            {@code "$title ... $result"}.
+	 */
+	public void endTask(String result) {
+		if (shown) {
+			write('\r' + msg + "... " + result + "\n"); //$NON-NLS-1$ //$NON-NLS-2$
+		}
+	}
+
+	private void write(String s) {
+		if (write) {
+			try {
+				out.write(s.getBytes(UTF_8));
+				out.flush();
+			} catch (IOException e) {
+				write = false;
+			}
+		}
+	}
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushCertificateStore.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushCertificateStore.java
index d8672d5..d436e08 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushCertificateStore.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushCertificateStore.java
@@ -65,7 +65,6 @@
 import java.util.NoSuchElementException;
 
 import org.eclipse.jgit.dircache.DirCache;
-import org.eclipse.jgit.dircache.DirCacheBuilder;
 import org.eclipse.jgit.dircache.DirCacheEditor;
 import org.eclipse.jgit.dircache.DirCacheEditor.PathEdit;
 import org.eclipse.jgit.dircache.DirCacheEntry;
@@ -107,11 +106,11 @@
 			Constants.R_REFS + "meta/push-certs"; //$NON-NLS-1$
 
 	private static class PendingCert {
-		private PushCertificate cert;
-		private PersonIdent ident;
-		private Collection<ReceiveCommand> matching;
+		PushCertificate cert;
+		PersonIdent ident;
+		Collection<ReceiveCommand> matching;
 
-		private PendingCert(PushCertificate cert, PersonIdent ident,
+		PendingCert(PushCertificate cert, PersonIdent ident,
 				Collection<ReceiveCommand> matching) {
 			this.cert = cert;
 			this.ident = ident;
@@ -121,8 +120,8 @@
 
 	private final Repository db;
 	private final List<PendingCert> pending;
-	private ObjectReader reader;
-	private RevCommit commit;
+	ObjectReader reader;
+	RevCommit commit;
 
 	/**
 	 * Create a new store backed by the given repository.
@@ -270,7 +269,7 @@
 		};
 	}
 
-	private void load() throws IOException {
+	void load() throws IOException {
 		close();
 		reader = db.newObjectReader();
 		Ref ref = db.getRefDatabase().exactRef(REF_NAME);
@@ -283,7 +282,7 @@
 		}
 	}
 
-	private static PushCertificate read(TreeWalk tw) throws IOException {
+	static PushCertificate read(TreeWalk tw) throws IOException {
 		if (tw == null || (tw.getRawMode(0) & TYPE_FILE) != TYPE_FILE) {
 			return null;
 		}
@@ -448,13 +447,10 @@
 	}
 
 	private DirCache newDirCache() throws IOException {
-		DirCache dc = DirCache.newInCore();
 		if (commit != null) {
-			DirCacheBuilder b = dc.builder();
-			b.addTree(new byte[0], DirCacheEntry.STAGE_0, reader, commit.getTree());
-			b.finish();
+			return DirCache.read(reader, commit.getTree());
 		}
-		return dc;
+		return DirCache.newInCore();
 	}
 
 	private ObjectId saveCert(ObjectInserter inserter, DirCache dc,
@@ -532,7 +528,7 @@
 		return TreeWalk.forPath(reader, pathName(refName), commit.getTree());
 	}
 
-	private static String pathName(String refName) {
+	static String pathName(String refName) {
 		return refName + "@{cert}"; //$NON-NLS-1$
 	}
 
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 9721ee9..5cea882 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushProcess.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushProcess.java
@@ -47,6 +47,7 @@
 import java.io.OutputStream;
 import java.text.MessageFormat;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
 
@@ -183,11 +184,17 @@
 
 	private Map<String, RemoteRefUpdate> prepareRemoteUpdates()
 			throws TransportException {
+		boolean atomic = transport.isPushAtomic();
 		final Map<String, RemoteRefUpdate> result = new HashMap<String, RemoteRefUpdate>();
 		for (final RemoteRefUpdate rru : toPush.values()) {
 			final Ref advertisedRef = connection.getRef(rru.getRemoteName());
-			final ObjectId advertisedOld = (advertisedRef == null ? ObjectId
-					.zeroId() : advertisedRef.getObjectId());
+			ObjectId advertisedOld = null;
+			if (advertisedRef != null) {
+				advertisedOld = advertisedRef.getObjectId();
+			}
+			if (advertisedOld == null) {
+				advertisedOld = ObjectId.zeroId();
+			}
 
 			if (rru.getNewObjectId().equals(advertisedOld)) {
 				if (rru.isDelete()) {
@@ -205,8 +212,14 @@
 			if (rru.isExpectingOldObjectId()
 					&& !rru.getExpectedOldObjectId().equals(advertisedOld)) {
 				rru.setStatus(Status.REJECTED_REMOTE_CHANGED);
+				if (atomic) {
+					return rejectAll();
+				}
 				continue;
 			}
+			if (!rru.isExpectingOldObjectId()) {
+				rru.setExpectedOldObjectId(advertisedOld);
+			}
 
 			// create ref (hasn't existed on remote side) and delete ref
 			// are always fast-forward commands, feasible at this level
@@ -236,14 +249,28 @@
 						JGitText.get().readingObjectsFromLocalRepositoryFailed, x.getMessage()), x);
 			}
 			rru.setFastForward(fastForward);
-			if (!fastForward && !rru.isForceUpdate())
+			if (!fastForward && !rru.isForceUpdate()) {
 				rru.setStatus(Status.REJECTED_NONFASTFORWARD);
-			else
+				if (atomic) {
+					return rejectAll();
+				}
+			} else {
 				result.put(rru.getRemoteName(), rru);
+			}
 		}
 		return result;
 	}
 
+	private Map<String, RemoteRefUpdate> rejectAll() {
+		for (RemoteRefUpdate rru : toPush.values()) {
+			if (rru.getStatus() == Status.NOT_ATTEMPTED) {
+				rru.setStatus(RemoteRefUpdate.Status.REJECTED_OTHER_REASON);
+				rru.setMessage(JGitText.get().transactionAborted);
+			}
+		}
+		return Collections.emptyMap();
+	}
+
 	private void modifyUpdatesForDryRun() {
 		for (final RemoteRefUpdate rru : toPush.values())
 			if (rru.getStatus() == Status.NOT_ATTEMPTED)
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceiveCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceiveCommand.java
index 7c44dba..2b21c4a 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceiveCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceiveCommand.java
@@ -43,9 +43,13 @@
 
 package org.eclipse.jgit.transport;
 
+import static org.eclipse.jgit.transport.ReceiveCommand.Result.NOT_ATTEMPTED;
+import static org.eclipse.jgit.transport.ReceiveCommand.Result.REJECTED_OTHER_REASON;
+
 import java.io.IOException;
 import java.text.MessageFormat;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.List;
 
 import org.eclipse.jgit.internal.JGitText;
@@ -127,6 +131,31 @@
 	}
 
 	/**
+	 * Filter a collection of commands according to result.
+	 *
+	 * @param in
+	 *            commands to filter.
+	 * @param want
+	 *            desired status to filter by.
+	 * @return a copy of the command list containing only those commands with
+	 *         the desired status.
+	 * @since 4.2
+	 */
+	public static List<ReceiveCommand> filter(Iterable<ReceiveCommand> in,
+			Result want) {
+		List<ReceiveCommand> r;
+		if (in instanceof Collection)
+			r = new ArrayList<>(((Collection<?>) in).size());
+		else
+			r = new ArrayList<>();
+		for (ReceiveCommand cmd : in) {
+			if (cmd.getResult() == want)
+				r.add(cmd);
+		}
+		return r;
+	}
+
+	/**
 	 * Filter a list of commands according to result.
 	 *
 	 * @param commands
@@ -138,13 +167,27 @@
 	 * @since 2.0
 	 */
 	public static List<ReceiveCommand> filter(List<ReceiveCommand> commands,
-			final Result want) {
-		List<ReceiveCommand> r = new ArrayList<ReceiveCommand>(commands.size());
-		for (final ReceiveCommand cmd : commands) {
-			if (cmd.getResult() == want)
-				r.add(cmd);
+			Result want) {
+		return filter((Iterable<ReceiveCommand>) commands, want);
+	}
+
+	/**
+	 * Set unprocessed commands as failed due to transaction aborted.
+	 * <p>
+	 * If a command is still {@link Result#NOT_ATTEMPTED} it will be set to
+	 * {@link Result#REJECTED_OTHER_REASON}.
+	 *
+	 * @param commands
+	 *            commands to mark as failed.
+	 * @since 4.2
+	 */
+	public static void abort(Iterable<ReceiveCommand> commands) {
+		for (ReceiveCommand c : commands) {
+			if (c.getResult() == NOT_ATTEMPTED) {
+				c.setResult(REJECTED_OTHER_REASON,
+						JGitText.get().transactionAborted);
+			}
 		}
-		return r;
 	}
 
 	private final ObjectId oldId;
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 66ffc3a..0e803bd 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefSpec.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefSpec.java
@@ -457,10 +457,6 @@
 		if (i != -1) {
 			if (s.indexOf('*', i + 1) > i)
 				return false;
-			if (i > 0 && s.charAt(i - 1) != '/')
-				return false;
-			if (i < s.length() - 1 && s.charAt(i + 1) != '/')
-				return false;
 		}
 		return true;
 	}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteRefUpdate.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteRefUpdate.java
index 1b82a36..5c58346 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteRefUpdate.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteRefUpdate.java
@@ -125,7 +125,7 @@
 		OK;
 	}
 
-	private final ObjectId expectedOldObjectId;
+	private ObjectId expectedOldObjectId;
 
 	private final ObjectId newObjectId;
 
@@ -440,6 +440,10 @@
 		return message;
 	}
 
+	void setExpectedOldObjectId(ObjectId id) {
+		expectedOldObjectId = id;
+	}
+
 	void setStatus(final Status status) {
 		this.status = status;
 	}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/SideBandInputStream.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/SideBandInputStream.java
index cf388e2..fe9f2a3 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/SideBandInputStream.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/SideBandInputStream.java
@@ -78,12 +78,8 @@
  * @see SideBandOutputStream
  */
 class SideBandInputStream extends InputStream {
-	private static final String PFX_REMOTE = JGitText.get().prefixRemote;
-
 	static final int CH_DATA = 1;
-
 	static final int CH_PROGRESS = 2;
-
 	static final int CH_ERROR = 3;
 
 	private static Pattern P_UNBOUNDED = Pattern
@@ -174,7 +170,7 @@
 				continue;
 			case CH_ERROR:
 				eof = true;
-				throw new TransportException(PFX_REMOTE + readString(available));
+				throw new TransportException(remote(readString(available)));
 			default:
 				throw new PackProtocolException(
 						MessageFormat.format(JGitText.get().invalidChannel,
@@ -241,7 +237,18 @@
 	}
 
 	private void beginTask(final int totalWorkUnits) {
-		monitor.beginTask(PFX_REMOTE + currentTask, totalWorkUnits);
+		monitor.beginTask(remote(currentTask), totalWorkUnits);
+	}
+
+	private static String remote(String msg) {
+		String prefix = JGitText.get().prefixRemote;
+		StringBuilder r = new StringBuilder(prefix.length() + msg.length() + 1);
+		r.append(prefix);
+		if (prefix.length() > 0 && prefix.charAt(prefix.length() - 1) != ' ') {
+			r.append(' ');
+		}
+		r.append(msg);
+		return r.toString();
 	}
 
 	private String readString(final int len) throws IOException {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TestProtocol.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TestProtocol.java
index 5243010..5fd2f84 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TestProtocol.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TestProtocol.java
@@ -78,17 +78,17 @@
 	private static final String SCHEME = "test"; //$NON-NLS-1$
 
 	private class Handle {
-		private final C req;
-		private final Repository remote;
+		final C req;
+		final Repository remote;
 
-		private Handle(C req, Repository remote) {
+		Handle(C req, Repository remote) {
 			this.req = req;
 			this.remote = remote;
 		}
 	}
 
-	private final UploadPackFactory<C> uploadPackFactory;
-	private final ReceivePackFactory<C> receivePackFactory;
+	final UploadPackFactory<C> uploadPackFactory;
+	final ReceivePackFactory<C> receivePackFactory;
 	private final HashMap<URIish, Handle> handles;
 
 	/**
@@ -165,7 +165,7 @@
 	private class TransportInternal extends Transport implements PackTransport {
 		private final Handle handle;
 
-		private TransportInternal(Repository local, URIish uri, Handle handle) {
+		TransportInternal(Repository local, URIish uri, Handle handle) {
 			super(local, uri);
 			this.handle = handle;
 		}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TrackingRefUpdate.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TrackingRefUpdate.java
index 1ef3fbf..5aae5ea 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TrackingRefUpdate.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TrackingRefUpdate.java
@@ -52,10 +52,10 @@
 /** Update of a locally stored tracking branch. */
 public class TrackingRefUpdate {
 	private final String remoteName;
-	private final String localName;
-	private boolean forceUpdate;
-	private ObjectId oldObjectId;
-	private ObjectId newObjectId;
+	final String localName;
+	boolean forceUpdate;
+	ObjectId oldObjectId;
+	ObjectId newObjectId;
 
 	private RefUpdate.Result result;
 	private ReceiveCommand cmd;
@@ -142,7 +142,7 @@
 	}
 
 	final class Command extends ReceiveCommand {
-		private Command() {
+		Command() {
 			super(oldObjectId, newObjectId, localName);
 		}
 
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransferConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransferConfig.java
index f4de821..72c9c8b 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransferConfig.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransferConfig.java
@@ -43,12 +43,20 @@
 
 package org.eclipse.jgit.transport;
 
+import static org.eclipse.jgit.util.StringUtils.equalsIgnoreCase;
+import static org.eclipse.jgit.util.StringUtils.toLowerCase;
+
+import java.io.File;
+import java.util.EnumSet;
 import java.util.HashMap;
 import java.util.Map;
 
+import org.eclipse.jgit.annotations.Nullable;
+import org.eclipse.jgit.internal.storage.file.LazyObjectIdSetFile;
 import org.eclipse.jgit.lib.Config;
 import org.eclipse.jgit.lib.Config.SectionParser;
 import org.eclipse.jgit.lib.ObjectChecker;
+import org.eclipse.jgit.lib.ObjectIdSet;
 import org.eclipse.jgit.lib.Ref;
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.util.SystemReader;
@@ -58,6 +66,8 @@
  * parameters.
  */
 public class TransferConfig {
+	private static final String FSCK = "fsck"; //$NON-NLS-1$
+
 	/** Key for {@link Config#get(SectionParser)}. */
 	public static final Config.SectionParser<TransferConfig> KEY = new SectionParser<TransferConfig>() {
 		public TransferConfig parse(final Config cfg) {
@@ -65,34 +75,67 @@
 		}
 	};
 
-	private final boolean checkReceivedObjects;
-	private final boolean allowLeadingZeroFileMode;
+	enum FsckMode {
+		ERROR, WARN, IGNORE;
+	}
+
+	private final boolean fetchFsck;
+	private final boolean receiveFsck;
+	private final String fsckSkipList;
+	private final EnumSet<ObjectChecker.ErrorType> ignore;
 	private final boolean allowInvalidPersonIdent;
 	private final boolean safeForWindows;
 	private final boolean safeForMacOS;
 	private final boolean allowTipSha1InWant;
 	private final boolean allowReachableSha1InWant;
-	private final String[] hideRefs;
+	final String[] hideRefs;
 
 	TransferConfig(final Repository db) {
 		this(db.getConfig());
 	}
 
-	private TransferConfig(final Config rc) {
-		checkReceivedObjects = rc.getBoolean(
-				"fetch", "fsckobjects", //$NON-NLS-1$ //$NON-NLS-2$
-				rc.getBoolean("transfer", "fsckobjects", false)); //$NON-NLS-1$ //$NON-NLS-2$
-		allowLeadingZeroFileMode = checkReceivedObjects
-				&& rc.getBoolean("fsck", "allowLeadingZeroFileMode", false); //$NON-NLS-1$ //$NON-NLS-2$
-		allowInvalidPersonIdent = checkReceivedObjects
-				&& rc.getBoolean("fsck", "allowInvalidPersonIdent", false); //$NON-NLS-1$ //$NON-NLS-2$
-		safeForWindows = checkReceivedObjects
-				&& rc.getBoolean("fsck", "safeForWindows", //$NON-NLS-1$ //$NON-NLS-2$
+	TransferConfig(final Config rc) {
+		boolean fsck = rc.getBoolean("transfer", "fsckobjects", false); //$NON-NLS-1$ //$NON-NLS-2$
+		fetchFsck = rc.getBoolean("fetch", "fsckobjects", fsck); //$NON-NLS-1$ //$NON-NLS-2$
+		receiveFsck = rc.getBoolean("receive", "fsckobjects", fsck); //$NON-NLS-1$ //$NON-NLS-2$
+		fsckSkipList = rc.getString(FSCK, null, "skipList"); //$NON-NLS-1$
+		allowInvalidPersonIdent = rc.getBoolean(FSCK, "allowInvalidPersonIdent", false); //$NON-NLS-1$
+		safeForWindows = rc.getBoolean(FSCK, "safeForWindows", //$NON-NLS-1$
 						SystemReader.getInstance().isWindows());
-		safeForMacOS = checkReceivedObjects
-				&& rc.getBoolean("fsck", "safeForMacOS", //$NON-NLS-1$ //$NON-NLS-2$
+		safeForMacOS = rc.getBoolean(FSCK, "safeForMacOS", //$NON-NLS-1$
 						SystemReader.getInstance().isMacOS());
 
+		ignore = EnumSet.noneOf(ObjectChecker.ErrorType.class);
+		EnumSet<ObjectChecker.ErrorType> set = EnumSet
+				.noneOf(ObjectChecker.ErrorType.class);
+		for (String key : rc.getNames(FSCK)) {
+			if (equalsIgnoreCase(key, "skipList") //$NON-NLS-1$
+					|| equalsIgnoreCase(key, "allowLeadingZeroFileMode") //$NON-NLS-1$
+					|| equalsIgnoreCase(key, "allowInvalidPersonIdent") //$NON-NLS-1$
+					|| equalsIgnoreCase(key, "safeForWindows") //$NON-NLS-1$
+					|| equalsIgnoreCase(key, "safeForMacOS")) { //$NON-NLS-1$
+				continue;
+			}
+
+			ObjectChecker.ErrorType id = FsckKeyNameHolder.parse(key);
+			if (id != null) {
+				switch (rc.getEnum(FSCK, null, key, FsckMode.ERROR)) {
+				case ERROR:
+					ignore.remove(id);
+					break;
+				case WARN:
+				case IGNORE:
+					ignore.add(id);
+					break;
+				}
+				set.add(id);
+			}
+		}
+		if (!set.contains(ObjectChecker.ErrorType.ZERO_PADDED_FILEMODE)
+				&& rc.getBoolean(FSCK, "allowLeadingZeroFileMode", false)) { //$NON-NLS-1$
+			ignore.add(ObjectChecker.ErrorType.ZERO_PADDED_FILEMODE);
+		}
+
 		allowTipSha1InWant = rc.getBoolean(
 				"uploadpack", "allowtipsha1inwant", false); //$NON-NLS-1$ //$NON-NLS-2$
 		allowReachableSha1InWant = rc.getBoolean(
@@ -105,14 +148,38 @@
 	 *         enabled in the repository configuration.
 	 * @since 3.6
 	 */
+	@Nullable
 	public ObjectChecker newObjectChecker() {
-		if (!checkReceivedObjects)
+		return newObjectChecker(fetchFsck);
+	}
+
+	/**
+	 * @return checker to verify objects pushed into this repository, or null if
+	 *         checking is not enabled in the repository configuration.
+	 * @since 4.2
+	 */
+	@Nullable
+	public ObjectChecker newReceiveObjectChecker() {
+		return newObjectChecker(receiveFsck);
+	}
+
+	private ObjectChecker newObjectChecker(boolean check) {
+		if (!check) {
 			return null;
+		}
 		return new ObjectChecker()
-			.setAllowLeadingZeroFileMode(allowLeadingZeroFileMode)
+			.setIgnore(ignore)
 			.setAllowInvalidPersonIdent(allowInvalidPersonIdent)
 			.setSafeForWindows(safeForWindows)
-			.setSafeForMacOS(safeForMacOS);
+			.setSafeForMacOS(safeForMacOS)
+			.setSkipList(skipList());
+	}
+
+	private ObjectIdSet skipList() {
+		if (fsckSkipList != null && !fsckSkipList.isEmpty()) {
+			return new LazyObjectIdSetFile(new File(fsckSkipList));
+		}
+		return null;
 	}
 
 	/**
@@ -161,4 +228,34 @@
 			}
 		};
 	}
+
+	static class FsckKeyNameHolder {
+		private static final Map<String, ObjectChecker.ErrorType> errors;
+
+		static {
+			errors = new HashMap<>();
+			for (ObjectChecker.ErrorType m : ObjectChecker.ErrorType.values()) {
+				errors.put(keyNameFor(m.name()), m);
+			}
+		}
+
+		@Nullable
+		static ObjectChecker.ErrorType parse(String key) {
+			return errors.get(toLowerCase(key));
+		}
+
+		private static String keyNameFor(String name) {
+			StringBuilder r = new StringBuilder(name.length());
+			for (int i = 0; i < name.length(); i++) {
+				char c = name.charAt(i);
+				if (c != '_') {
+					r.append(c);
+				}
+			}
+			return toLowerCase(r.toString());
+		}
+
+		private FsckKeyNameHolder() {
+		}
+	}
 }
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 2185622..9e6d1f6 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/Transport.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/Transport.java
@@ -53,6 +53,7 @@
 import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.io.OutputStream;
+import java.io.PrintStream;
 import java.lang.ref.WeakReference;
 import java.lang.reflect.Field;
 import java.lang.reflect.Modifier;
@@ -70,8 +71,11 @@
 import java.util.Vector;
 import java.util.concurrent.CopyOnWriteArrayList;
 
+import org.eclipse.jgit.api.errors.AbortedByHookException;
 import org.eclipse.jgit.errors.NotSupportedException;
 import org.eclipse.jgit.errors.TransportException;
+import org.eclipse.jgit.hooks.Hooks;
+import org.eclipse.jgit.hooks.PrePushHook;
 import org.eclipse.jgit.internal.JGitText;
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.NullProgressMonitor;
@@ -94,7 +98,7 @@
  * Transport instances and the connections they create are not thread-safe.
  * Callers must ensure a transport is accessed by only one thread at a time.
  */
-public abstract class Transport {
+public abstract class Transport implements AutoCloseable {
 	/** Type of operation a Transport is being opened for. */
 	public enum Operation {
 		/** Transport is to fetch objects locally. */
@@ -557,8 +561,13 @@
 				continue;
 			}
 
-			if (proto.canHandle(uri, local, remoteName))
-				return proto.open(uri, local, remoteName);
+			if (proto.canHandle(uri, local, remoteName)) {
+				Transport tn = proto.open(uri, local, remoteName);
+				tn.prePush = Hooks.prePush(local, tn.hookOutRedirect);
+				tn.prePush.setRemoteLocation(uri.toString());
+				tn.prePush.setRemoteName(remoteName);
+				return tn;
+			}
 		}
 
 		throw new NotSupportedException(MessageFormat.format(JGitText.get().URINotSupported, uri));
@@ -743,6 +752,9 @@
 	/** Should push produce thin-pack when sending objects to remote repository. */
 	private boolean pushThin = DEFAULT_PUSH_THIN;
 
+	/** Should push be all-or-nothing atomic behavior? */
+	private boolean pushAtomic;
+
 	/** Should push just check for operation result, not really push. */
 	private boolean dryRun;
 
@@ -761,6 +773,9 @@
 	/** Assists with authentication the connection. */
 	private CredentialsProvider credentialsProvider;
 
+	private PrintStream hookOutRedirect;
+
+	private PrePushHook prePush;
 	/**
 	 * Create a new transport instance.
 	 *
@@ -778,6 +793,7 @@
 		this.uri = uri;
 		this.objectChecker = tc.newObjectChecker();
 		this.credentialsProvider = CredentialsProvider.getDefault();
+		prePush = Hooks.prePush(local, hookOutRedirect);
 	}
 
 	/**
@@ -957,6 +973,31 @@
 	}
 
 	/**
+	 * Default setting is false.
+	 *
+	 * @return true if push requires all-or-nothing atomic behavior.
+	 * @since 4.2
+	 */
+	public boolean isPushAtomic() {
+		return pushAtomic;
+	}
+
+	/**
+	 * Request atomic push (all references succeed, or none do).
+	 * <p>
+	 * Server must also support atomic push. If the server does not support the
+	 * feature the push will abort without making changes.
+	 *
+	 * @param atomic
+	 *            true when push should be an all-or-nothing operation.
+	 * @see PackTransport
+	 * @since 4.2
+	 */
+	public void setPushAtomic(final boolean atomic) {
+		this.pushAtomic = atomic;
+	}
+
+	/**
 	 * @return true if destination refs should be removed if they no longer
 	 *         exist at the source repository.
 	 */
@@ -1196,6 +1237,15 @@
 			if (toPush.isEmpty())
 				throw new TransportException(JGitText.get().nothingToPush);
 		}
+		if (prePush != null) {
+			try {
+				prePush.setRefs(toPush);
+				prePush.call();
+			} catch (AbortedByHookException | IOException e) {
+				throw new TransportException(e.getMessage(), e);
+			}
+		}
+
 		final PushProcess pushProcess = new PushProcess(this, toPush, out);
 		return pushProcess.execute(monitor);
 	}
@@ -1303,6 +1353,10 @@
 	 * must close that network socket, disconnecting the two peers. If the
 	 * remote repository is actually local (same system) this method must close
 	 * any open file handles used to read the "remote" repository.
+	 * <p>
+	 * {@code AutoClosable.close()} declares that it throws {@link Exception}.
+	 * Implementers shouldn't throw checked exceptions. This override narrows
+	 * the signature to prevent them from doing so.
 	 */
 	public abstract void close();
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportAmazonS3.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportAmazonS3.java
index 745cdb7..23c506b 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportAmazonS3.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportAmazonS3.java
@@ -125,10 +125,10 @@
 	};
 
 	/** User information necessary to connect to S3. */
-	private final AmazonS3 s3;
+	final AmazonS3 s3;
 
 	/** Bucket the remote repository is stored in. */
-	private final String bucket;
+	final String bucket;
 
 	/**
 	 * Key prefix which all objects related to the repository start with.
@@ -148,8 +148,9 @@
 		super(local, uri);
 
 		Properties props = loadProperties();
-		if (!props.containsKey("tmpdir") && local.getDirectory() != null) //$NON-NLS-1$
-			props.put("tmpdir", local.getDirectory().getPath()); //$NON-NLS-1$
+		File directory = local.getDirectory();
+		if (!props.containsKey("tmpdir") && directory != null) //$NON-NLS-1$
+			props.put("tmpdir", directory.getPath()); //$NON-NLS-1$
 
 		s3 = new AmazonS3(props);
 		bucket = uri.getHost();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportGitSsh.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportGitSsh.java
index b27fa0d..52f0f04 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportGitSsh.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportGitSsh.java
@@ -46,6 +46,7 @@
 
 package org.eclipse.jgit.transport;
 
+import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
 import java.text.MessageFormat;
@@ -235,9 +236,10 @@
 			ProcessBuilder pb = new ProcessBuilder();
 			pb.command(args);
 
-			if (local.getDirectory() != null)
+			File directory = local.getDirectory();
+			if (directory != null)
 				pb.environment().put(Constants.GIT_DIR_KEY,
-						local.getDirectory().getPath());
+						directory.getPath());
 
 			try {
 				return pb.start();
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 b23771e..5948278 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportHttp.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportHttp.java
@@ -218,16 +218,16 @@
 			sslVerify = rc.getBoolean("http", "sslVerify", true); //$NON-NLS-1$ //$NON-NLS-2$
 		}
 
-		private HttpConfig() {
+		HttpConfig() {
 			this(new Config());
 		}
 	}
 
-	private final URL baseUrl;
+	final URL baseUrl;
 
 	private final URL objectsUrl;
 
-	private final HttpConfig http;
+	final HttpConfig http;
 
 	private final ProxySelector proxySelector;
 
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/URIish.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/URIish.java
index 3700b49..3ee2feb 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/URIish.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/URIish.java
@@ -83,7 +83,7 @@
 	 * capturing groups: the first containing the user and the second containing
 	 * the password
 	 */
-	private static final String OPT_USER_PWD_P = "(?:([^/:@]+)(?::([^\\\\/]+))?@)?"; //$NON-NLS-1$
+	private static final String OPT_USER_PWD_P = "(?:([^/:]+)(?::([^\\\\/]+))?@)?"; //$NON-NLS-1$
 
 	/**
 	 * Part of a pattern which matches the host part of URIs. Defines one
@@ -137,7 +137,11 @@
 			+ OPT_PORT_P //
 			+ "(" // open a group capturing the user-home-dir-part //$NON-NLS-1$
 			+ (USER_HOME_P + "?") //$NON-NLS-1$
-			+ "[\\\\/])" //$NON-NLS-1$
+			+ "(?:" // start non capturing group for host //$NON-NLS-1$
+					// separator or end of line
+			+ "[\\\\/])|$" //$NON-NLS-1$
+			+ ")" // close non capturing group for the host//$NON-NLS-1$
+					// separator or end of line
 			+ ")?" // close the optional group containing hostname //$NON-NLS-1$
 			+ "(.+)?" //$NON-NLS-1$
 			+ "$"); //$NON-NLS-1$
@@ -593,6 +597,8 @@
 	private static boolean eq(final String a, final String b) {
 		if (a == b)
 			return true;
+		if (StringUtils.isEmptyOrNull(a) && StringUtils.isEmptyOrNull(b))
+			return true;
 		if (a == null || b == null)
 			return false;
 		return a.equals(b);
@@ -638,7 +644,7 @@
 
 		if (getPath() != null) {
 			if (getScheme() != null) {
-				if (!getPath().startsWith("/")) //$NON-NLS-1$
+				if (!getPath().startsWith("/") && !getPath().isEmpty()) //$NON-NLS-1$
 					r.append('/');
 			} else if (getHost() != null)
 				r.append(':');
@@ -709,9 +715,9 @@
 	 */
 	public String getHumanishName() throws IllegalArgumentException {
 		String s = getPath();
-		if ("/".equals(s)) //$NON-NLS-1$
+		if ("/".equals(s) || "".equals(s)) //$NON-NLS-1$ //$NON-NLS-2$
 			s = getHost();
-		if ("".equals(s) || s == null) //$NON-NLS-1$
+		if (s == null) // $NON-NLS-1$
 			throw new IllegalArgumentException();
 
 		String[] elements;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkEncryption.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkEncryption.java
index e55b984..fe03bdc 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkEncryption.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkEncryption.java
@@ -47,22 +47,28 @@
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.net.HttpURLConnection;
-import java.security.InvalidAlgorithmParameterException;
-import java.security.InvalidKeyException;
-import java.security.NoSuchAlgorithmException;
-import java.security.spec.InvalidKeySpecException;
+import java.security.AlgorithmParameters;
+import java.security.GeneralSecurityException;
+import java.security.spec.AlgorithmParameterSpec;
+import java.security.spec.KeySpec;
 import java.text.MessageFormat;
+import java.util.Properties;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 import javax.crypto.Cipher;
 import javax.crypto.CipherInputStream;
 import javax.crypto.CipherOutputStream;
-import javax.crypto.NoSuchPaddingException;
 import javax.crypto.SecretKey;
 import javax.crypto.SecretKeyFactory;
+import javax.crypto.spec.IvParameterSpec;
 import javax.crypto.spec.PBEKeySpec;
 import javax.crypto.spec.PBEParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+import javax.xml.bind.DatatypeConverter;
 
 import org.eclipse.jgit.internal.JGitText;
+import org.eclipse.jgit.util.Base64;
 
 abstract class WalkEncryption {
 	static final WalkEncryption NONE = new NoEncryption();
@@ -71,29 +77,40 @@
 
 	static final String JETS3T_CRYPTO_ALG = "jets3t-crypto-alg"; //$NON-NLS-1$
 
-	abstract OutputStream encrypt(OutputStream os) throws IOException;
+	// Note: encrypt -> request state machine, step 1.
+	abstract OutputStream encrypt(OutputStream output) throws IOException;
 
-	abstract InputStream decrypt(InputStream in) throws IOException;
+	// Note: encrypt -> request state machine, step 2.
+	abstract void request(HttpURLConnection conn, String prefix) throws IOException;
 
-	abstract void request(HttpURLConnection u, String prefix);
+	// Note: validate -> decrypt state machine, step 1.
+	abstract void validate(HttpURLConnection conn, String prefix) throws IOException;
 
-	abstract void validate(HttpURLConnection u, String p) throws IOException;
+	// Note: validate -> decrypt state machine, step 2.
+	abstract InputStream decrypt(InputStream input) throws IOException;
 
-	protected void validateImpl(final HttpURLConnection u, final String p,
+
+	// TODO mixed ciphers
+	// consider permitting mixed ciphers to facilitate algorithm migration
+	// i.e. user keeps the password, but changes the algorithm
+	// then existing remote entries will still be readable
+	protected void validateImpl(final HttpURLConnection u, final String prefix,
 			final String version, final String name) throws IOException {
 		String v;
 
-		v = u.getHeaderField(p + JETS3T_CRYPTO_VER);
+		v = u.getHeaderField(prefix + JETS3T_CRYPTO_VER);
 		if (v == null)
 			v = ""; //$NON-NLS-1$
 		if (!version.equals(v))
 			throw new IOException(MessageFormat.format(JGitText.get().unsupportedEncryptionVersion, v));
 
-		v = u.getHeaderField(p + JETS3T_CRYPTO_ALG);
+		v = u.getHeaderField(prefix + JETS3T_CRYPTO_ALG);
 		if (v == null)
 			v = ""; //$NON-NLS-1$
-		if (!name.equals(v))
-			throw new IOException(JGitText.get().unsupportedEncryptionAlgorithm + v);
+		// Standard names are not case-sensitive.
+		// http://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html
+		if (!name.equalsIgnoreCase(v))
+			throw new IOException(MessageFormat.format(JGitText.get().unsupportedEncryptionAlgorithm, v));
 	}
 
 	IOException error(final Throwable why) {
@@ -110,9 +127,9 @@
 		}
 
 		@Override
-		void validate(final HttpURLConnection u, final String p)
+		void validate(final HttpURLConnection u, final String prefix)
 				throws IOException {
-			validateImpl(u, p, "", ""); //$NON-NLS-1$ //$NON-NLS-2$
+			validateImpl(u, prefix, "", ""); //$NON-NLS-1$ //$NON-NLS-2$
 		}
 
 		@Override
@@ -126,53 +143,139 @@
 		}
 	}
 
-	static class ObjectEncryptionV2 extends WalkEncryption {
-		private static int ITERATION_COUNT = 5000;
+	// PBEParameterSpec factory for Java (version <= 7).
+	// Does not support AlgorithmParameterSpec.
+	static PBEParameterSpec java7PBEParameterSpec(byte[] salt,
+			int iterationCount) {
+		return new PBEParameterSpec(salt, iterationCount);
+	}
 
-		private static byte[] salt = { (byte) 0xA4, (byte) 0x0B, (byte) 0xC8,
-				(byte) 0x34, (byte) 0xD6, (byte) 0x95, (byte) 0xF3, (byte) 0x13 };
+	// PBEParameterSpec factory for Java (version >= 8).
+	// Adds support for AlgorithmParameterSpec.
+	static PBEParameterSpec java8PBEParameterSpec(byte[] salt,
+			int iterationCount, AlgorithmParameterSpec paramSpec) {
+		try {
+			@SuppressWarnings("boxing")
+			PBEParameterSpec instance = PBEParameterSpec.class
+					.getConstructor(byte[].class, int.class,
+							AlgorithmParameterSpec.class)
+					.newInstance(salt, iterationCount, paramSpec);
+			return instance;
+		} catch (Exception e) {
+			throw new RuntimeException(e);
+		}
+	}
 
-		private final String algorithmName;
+	// Current runtime version.
+	// https://docs.oracle.com/javase/7/docs/technotes/guides/versioning/spec/versioning2.html
+	static double javaVersion() {
+		return Double.parseDouble(System.getProperty("java.specification.version")); //$NON-NLS-1$
+	}
 
-		private final SecretKey skey;
+	/**
+	 * JetS3t compatibility reference: <a href=
+	 * "https://bitbucket.org/jmurty/jets3t/src/156c00eb160598c2e9937fd6873f00d3190e28ca/src/org/jets3t/service/security/EncryptionUtil.java">
+	 * EncryptionUtil.java</a>
+	 * <p>
+	 * Note: EncryptionUtil is inadequate:
+	 * <li>EncryptionUtil.isCipherAvailableForUse checks encryption only which
+	 * "always works", but in JetS3t both encryption and decryption use non-IV
+	 * aware algorithm parameters for all PBE specs, which breaks in case of AES
+	 * <li>that means that only non-IV algorithms will work round trip in
+	 * JetS3t, such as PBEWithMD5AndDES and PBEWithSHAAndTwofish-CBC
+	 * <li>any AES based algorithms such as "PBE...With...And...AES" will not
+	 * work, since they need proper IV setup
+	 */
+	static class JetS3tV2 extends WalkEncryption {
 
-		private final PBEParameterSpec aspec;
+		static final String VERSION = "2"; //$NON-NLS-1$
 
-		ObjectEncryptionV2(final String algo, final String key)
-				throws InvalidKeySpecException, NoSuchAlgorithmException {
-			algorithmName = algo;
+		static final String ALGORITHM = "PBEWithMD5AndDES"; //$NON-NLS-1$
 
-			final PBEKeySpec s;
-			s = new PBEKeySpec(key.toCharArray(), salt, ITERATION_COUNT, 32);
-			skey = SecretKeyFactory.getInstance(algo).generateSecret(s);
-			aspec = new PBEParameterSpec(salt, ITERATION_COUNT);
+		static final int ITERATIONS = 5000;
+
+		static final int KEY_SIZE = 32;
+
+		static final byte[] SALT = { //
+				(byte) 0xA4, (byte) 0x0B, (byte) 0xC8, (byte) 0x34, //
+				(byte) 0xD6, (byte) 0x95, (byte) 0xF3, (byte) 0x13 //
+		};
+
+		// Size 16, see com.sun.crypto.provider.AESConstants.AES_BLOCK_SIZE
+		static final byte[] ZERO_AES_IV = new byte[16];
+
+		private static final String cryptoVer = VERSION;
+
+		private final String cryptoAlg;
+
+		private final SecretKey secretKey;
+
+		private final AlgorithmParameterSpec paramSpec;
+
+		JetS3tV2(final String algo, final String key)
+				throws GeneralSecurityException {
+			cryptoAlg = algo;
+
+			// Verify if cipher is present.
+			Cipher cipher = Cipher.getInstance(cryptoAlg);
+
+			// Standard names are not case-sensitive.
+			// http://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html
+			String cryptoName = cryptoAlg.toUpperCase();
+
+			if (!cryptoName.startsWith("PBE")) //$NON-NLS-1$
+				throw new GeneralSecurityException(JGitText.get().encryptionOnlyPBE);
+
+			PBEKeySpec keySpec = new PBEKeySpec(key.toCharArray(), SALT, ITERATIONS, KEY_SIZE);
+			secretKey = SecretKeyFactory.getInstance(algo).generateSecret(keySpec);
+
+			// Detect algorithms which require initialization vector.
+			boolean useIV = cryptoName.contains("AES"); //$NON-NLS-1$
+
+			// PBEParameterSpec algorithm parameters are supported from Java 8.
+			boolean isJava8 = javaVersion() >= 1.8;
+
+			if (useIV && isJava8) {
+				// Support IV where possible:
+				// * since JCE provider uses random IV for PBE/AES
+				// * and there is no place to store dynamic IV in JetS3t V2
+				// * we use static IV, and tolerate increased security risk
+				// TODO back port this change to JetS3t V2
+				// See:
+				// https://bitbucket.org/jmurty/jets3t/raw/156c00eb160598c2e9937fd6873f00d3190e28ca/src/org/jets3t/service/security/EncryptionUtil.java
+				// http://cr.openjdk.java.net/~mullan/webrevs/ascarpin/webrev.00/raw_files/new/src/share/classes/com/sun/crypto/provider/PBES2Core.java
+				IvParameterSpec paramIV = new IvParameterSpec(ZERO_AES_IV);
+				paramSpec = java8PBEParameterSpec(SALT, ITERATIONS, paramIV);
+			} else {
+				// Strict legacy JetS3t V2 compatibility, with no IV support.
+				paramSpec = java7PBEParameterSpec(SALT, ITERATIONS);
+			}
+
+			// Verify if cipher + key are allowed by policy.
+			cipher.init(Cipher.ENCRYPT_MODE, secretKey, paramSpec);
+			cipher.doFinal();
+
 		}
 
 		@Override
 		void request(final HttpURLConnection u, final String prefix) {
-			u.setRequestProperty(prefix + JETS3T_CRYPTO_VER, "2"); //$NON-NLS-1$
-			u.setRequestProperty(prefix + JETS3T_CRYPTO_ALG, algorithmName);
+			u.setRequestProperty(prefix + JETS3T_CRYPTO_VER, cryptoVer);
+			u.setRequestProperty(prefix + JETS3T_CRYPTO_ALG, cryptoAlg);
 		}
 
 		@Override
-		void validate(final HttpURLConnection u, final String p)
+		void validate(final HttpURLConnection u, final String prefix)
 				throws IOException {
-			validateImpl(u, p, "2", algorithmName); //$NON-NLS-1$
+			validateImpl(u, prefix, cryptoVer, cryptoAlg);
 		}
 
 		@Override
 		OutputStream encrypt(final OutputStream os) throws IOException {
 			try {
-				final Cipher c = Cipher.getInstance(algorithmName);
-				c.init(Cipher.ENCRYPT_MODE, skey, aspec);
-				return new CipherOutputStream(os, c);
-			} catch (NoSuchAlgorithmException e) {
-				throw error(e);
-			} catch (NoSuchPaddingException e) {
-				throw error(e);
-			} catch (InvalidKeyException e) {
-				throw error(e);
-			} catch (InvalidAlgorithmParameterException e) {
+				final Cipher cipher = Cipher.getInstance(cryptoAlg);
+				cipher.init(Cipher.ENCRYPT_MODE, secretKey, paramSpec);
+				return new CipherOutputStream(os, cipher);
+			} catch (GeneralSecurityException e) {
 				throw error(e);
 			}
 		}
@@ -180,18 +283,311 @@
 		@Override
 		InputStream decrypt(final InputStream in) throws IOException {
 			try {
-				final Cipher c = Cipher.getInstance(algorithmName);
-				c.init(Cipher.DECRYPT_MODE, skey, aspec);
-				return new CipherInputStream(in, c);
-			} catch (NoSuchAlgorithmException e) {
-				throw error(e);
-			} catch (NoSuchPaddingException e) {
-				throw error(e);
-			} catch (InvalidKeyException e) {
-				throw error(e);
-			} catch (InvalidAlgorithmParameterException e) {
+				final Cipher cipher = Cipher.getInstance(cryptoAlg);
+				cipher.init(Cipher.DECRYPT_MODE, secretKey, paramSpec);
+				return new CipherInputStream(in, cipher);
+			} catch (GeneralSecurityException e) {
 				throw error(e);
 			}
 		}
 	}
+
+	/** Encryption property names. */
+	interface Keys {
+		// Remote S3 meta: V1 algorithm name or V2 profile name.
+		String JGIT_PROFILE = "jgit-crypto-profile"; //$NON-NLS-1$
+
+		// Remote S3 meta: JGit encryption implementation version.
+		String JGIT_VERSION = "jgit-crypto-version"; //$NON-NLS-1$
+
+		// Remote S3 meta: base-64 encoded cipher algorithm parameters.
+		String JGIT_CONTEXT = "jgit-crypto-context"; //$NON-NLS-1$
+
+		// Amazon S3 connection configuration file profile property suffixes:
+		String X_ALGO = ".algo"; //$NON-NLS-1$
+		String X_KEY_ALGO = ".key.algo"; //$NON-NLS-1$
+		String X_KEY_SIZE = ".key.size"; //$NON-NLS-1$
+		String X_KEY_ITER = ".key.iter"; //$NON-NLS-1$
+		String X_KEY_SALT = ".key.salt"; //$NON-NLS-1$
+	}
+
+	/** Encryption constants and defaults. */
+	interface Vals {
+		// Compatibility defaults.
+		String DEFAULT_VERS = "0"; //$NON-NLS-1$
+		String DEFAULT_ALGO = JetS3tV2.ALGORITHM;
+		String DEFAULT_KEY_ALGO = JetS3tV2.ALGORITHM;
+		String DEFAULT_KEY_SIZE = Integer.toString(JetS3tV2.KEY_SIZE);
+		String DEFAULT_KEY_ITER = Integer.toString(JetS3tV2.ITERATIONS);
+		String DEFAULT_KEY_SALT = DatatypeConverter.printHexBinary(JetS3tV2.SALT);
+
+		String EMPTY = ""; //$NON-NLS-1$
+
+		// Match white space.
+		String REGEX_WS = "\\s+"; //$NON-NLS-1$
+
+		// Match PBE ciphers, i.e: PBEWithMD5AndDES
+		String REGEX_PBE = "(PBE).*(WITH).+(AND).+"; //$NON-NLS-1$
+
+		// Match transformation ciphers, i.e: AES/CBC/PKCS5Padding
+		String REGEX_TRANS = "(.+)/(.+)/(.+)"; //$NON-NLS-1$
+	}
+
+	static GeneralSecurityException securityError(String message) {
+		return new GeneralSecurityException(
+				MessageFormat.format(JGitText.get().encryptionError, message));
+	}
+
+	/**
+	 * Base implementation of JGit symmetric encryption. Supports V2 properties
+	 * format.
+	 */
+	static abstract class SymmetricEncryption extends WalkEncryption
+			implements Keys, Vals {
+
+		/** Encryption profile, root name of group of related properties. */
+		final String profile;
+
+		/** Encryption version, reflects actual implementation class. */
+		final String version;
+
+		/** Full cipher algorithm name. */
+		final String cipherAlgo;
+
+		/** Cipher algorithm name for parameters lookup. */
+		final String paramsAlgo;
+
+		/** Generated secret key. */
+		final SecretKey secretKey;
+
+		SymmetricEncryption(Properties props) throws GeneralSecurityException {
+
+			profile = props.getProperty(AmazonS3.Keys.CRYPTO_ALG);
+			version = props.getProperty(AmazonS3.Keys.CRYPTO_VER);
+			String pass = props.getProperty(AmazonS3.Keys.PASSWORD);
+
+			cipherAlgo = props.getProperty(profile + X_ALGO, DEFAULT_ALGO);
+
+			String keyAlgo = props.getProperty(profile + X_KEY_ALGO, DEFAULT_KEY_ALGO);
+			String keySize = props.getProperty(profile + X_KEY_SIZE, DEFAULT_KEY_SIZE);
+			String keyIter = props.getProperty(profile + X_KEY_ITER, DEFAULT_KEY_ITER);
+			String keySalt = props.getProperty(profile + X_KEY_SALT, DEFAULT_KEY_SALT);
+
+			// Verify if cipher is present.
+			Cipher cipher = Cipher.getInstance(cipherAlgo);
+
+			// Verify if key factory is present.
+			SecretKeyFactory factory = SecretKeyFactory.getInstance(keyAlgo);
+
+			final int size;
+			try {
+				size = Integer.parseInt(keySize);
+			} catch (Exception e) {
+				throw securityError(X_KEY_SIZE + EMPTY + keySize);
+			}
+
+			final int iter;
+			try {
+				iter = Integer.parseInt(keyIter);
+			} catch (Exception e) {
+				throw securityError(X_KEY_ITER + EMPTY + keyIter);
+			}
+
+			final byte[] salt;
+			try {
+				salt = DatatypeConverter
+						.parseHexBinary(keySalt.replaceAll(REGEX_WS, EMPTY));
+			} catch (Exception e) {
+				throw securityError(X_KEY_SALT + EMPTY + keySalt);
+			}
+
+			KeySpec keySpec = new PBEKeySpec(pass.toCharArray(), salt, iter, size);
+
+			SecretKey keyBase = factory.generateSecret(keySpec);
+
+			String name = cipherAlgo.toUpperCase();
+			Matcher matcherPBE = Pattern.compile(REGEX_PBE).matcher(name);
+			Matcher matcherTrans = Pattern.compile(REGEX_TRANS).matcher(name);
+			if (matcherPBE.matches()) {
+				paramsAlgo = cipherAlgo;
+				secretKey = keyBase;
+			} else if (matcherTrans.find()) {
+				paramsAlgo = matcherTrans.group(1);
+				secretKey = new SecretKeySpec(keyBase.getEncoded(), paramsAlgo);
+			} else {
+				throw new GeneralSecurityException(MessageFormat.format(
+						JGitText.get().unsupportedEncryptionAlgorithm,
+						cipherAlgo));
+			}
+
+			// Verify if cipher + key are allowed by policy.
+			cipher.init(Cipher.ENCRYPT_MODE, secretKey);
+			cipher.doFinal();
+
+		}
+
+		// Shared state encrypt -> request.
+		volatile String context;
+
+		@Override
+		OutputStream encrypt(OutputStream output) throws IOException {
+			try {
+				Cipher cipher = Cipher.getInstance(cipherAlgo);
+				cipher.init(Cipher.ENCRYPT_MODE, secretKey);
+				AlgorithmParameters params = cipher.getParameters();
+				if (params == null) {
+					context = EMPTY;
+				} else {
+					context = Base64.encodeBytes(params.getEncoded());
+				}
+				return new CipherOutputStream(output, cipher);
+			} catch (Exception e) {
+				throw error(e);
+			}
+		}
+
+		@Override
+		void request(HttpURLConnection conn, String prefix) throws IOException {
+			conn.setRequestProperty(prefix + JGIT_PROFILE, profile);
+			conn.setRequestProperty(prefix + JGIT_VERSION, version);
+			conn.setRequestProperty(prefix + JGIT_CONTEXT, context);
+			// No cleanup:
+			// single encrypt can be followed by several request
+			// from the AmazonS3.putImpl() multiple retry attempts
+			// context = null; // Cleanup encrypt -> request transition.
+			// TODO re-factor AmazonS3.putImpl to be more transaction-like
+		}
+
+		// Shared state validate -> decrypt.
+		volatile Cipher decryptCipher;
+
+		@Override
+		void validate(HttpURLConnection conn, String prefix)
+				throws IOException {
+			String prof = conn.getHeaderField(prefix + JGIT_PROFILE);
+			String vers = conn.getHeaderField(prefix + JGIT_VERSION);
+			String cont = conn.getHeaderField(prefix + JGIT_CONTEXT);
+
+			if (prof == null) {
+				throw new IOException(MessageFormat
+						.format(JGitText.get().encryptionError, JGIT_PROFILE));
+			}
+			if (vers == null) {
+				throw new IOException(MessageFormat
+						.format(JGitText.get().encryptionError, JGIT_VERSION));
+			}
+			if (cont == null) {
+				throw new IOException(MessageFormat
+						.format(JGitText.get().encryptionError, JGIT_CONTEXT));
+			}
+			if (!profile.equals(prof)) {
+				throw new IOException(MessageFormat.format(
+						JGitText.get().unsupportedEncryptionAlgorithm, prof));
+			}
+			if (!version.equals(vers)) {
+				throw new IOException(MessageFormat.format(
+						JGitText.get().unsupportedEncryptionVersion, vers));
+			}
+			try {
+				decryptCipher = Cipher.getInstance(cipherAlgo);
+				if (cont.isEmpty()) {
+					decryptCipher.init(Cipher.DECRYPT_MODE, secretKey);
+				} else {
+					AlgorithmParameters params = AlgorithmParameters
+							.getInstance(paramsAlgo);
+					params.init(Base64.decode(cont));
+					decryptCipher.init(Cipher.DECRYPT_MODE, secretKey, params);
+				}
+			} catch (Exception e) {
+				throw error(e);
+			}
+		}
+
+		@Override
+		InputStream decrypt(InputStream input) throws IOException {
+			try {
+				return new CipherInputStream(input, decryptCipher);
+			} finally {
+				decryptCipher = null; // Cleanup validate -> decrypt transition.
+			}
+		}
+	}
+
+	/**
+	 * Provides JetS3t-like encryption with AES support. Uses V1 connection file
+	 * format. For reference, see: 'jgit-s3-connection-v-1.properties'.
+	 */
+	static class JGitV1 extends SymmetricEncryption {
+
+		static final String VERSION = "1"; //$NON-NLS-1$
+
+		// Re-map connection properties V1 -> V2.
+		static Properties wrap(String algo, String pass) {
+			Properties props = new Properties();
+			props.put(AmazonS3.Keys.CRYPTO_ALG, algo);
+			props.put(AmazonS3.Keys.CRYPTO_VER, VERSION);
+			props.put(AmazonS3.Keys.PASSWORD, pass);
+			props.put(algo + Keys.X_ALGO, algo);
+			props.put(algo + Keys.X_KEY_ALGO, algo);
+			props.put(algo + Keys.X_KEY_ITER, DEFAULT_KEY_ITER);
+			props.put(algo + Keys.X_KEY_SIZE, DEFAULT_KEY_SIZE);
+			props.put(algo + Keys.X_KEY_SALT, DEFAULT_KEY_SALT);
+			return props;
+		}
+
+		JGitV1(String algo, String pass)
+				throws GeneralSecurityException {
+			super(wrap(algo, pass));
+			String name = cipherAlgo.toUpperCase();
+			Matcher matcherPBE = Pattern.compile(REGEX_PBE).matcher(name);
+			if (!matcherPBE.matches())
+				throw new GeneralSecurityException(
+						JGitText.get().encryptionOnlyPBE);
+		}
+
+	}
+
+	/**
+	 * Supports both PBE and non-PBE algorithms. Uses V2 connection file format.
+	 * For reference, see: 'jgit-s3-connection-v-2.properties'.
+	 */
+	static class JGitV2 extends SymmetricEncryption {
+
+		static final String VERSION = "2"; //$NON-NLS-1$
+
+		JGitV2(Properties props)
+				throws GeneralSecurityException {
+			super(props);
+		}
+	}
+
+	/**
+	 * Encryption factory.
+	 *
+	 * @param props
+	 * @return instance
+	 * @throws GeneralSecurityException
+	 */
+	static WalkEncryption instance(Properties props)
+			throws GeneralSecurityException {
+
+		String algo = props.getProperty(AmazonS3.Keys.CRYPTO_ALG, Vals.DEFAULT_ALGO);
+		String vers = props.getProperty(AmazonS3.Keys.CRYPTO_VER, Vals.DEFAULT_VERS);
+		String pass = props.getProperty(AmazonS3.Keys.PASSWORD);
+
+		if (pass == null) // Disable encryption.
+			return WalkEncryption.NONE;
+
+		switch (vers) {
+		case Vals.DEFAULT_VERS:
+			return new JetS3tV2(algo, pass);
+		case JGitV1.VERSION:
+			return new JGitV1(algo, pass);
+		case JGitV2.VERSION:
+			return new JGitV2(props);
+		default:
+			throw new GeneralSecurityException(MessageFormat.format(
+					JGitText.get().unsupportedEncryptionVersion, vers));
+		}
+	}
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkFetchConnection.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkFetchConnection.java
index dc9dee5..17edfdc 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkFetchConnection.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkFetchConnection.java
@@ -115,10 +115,10 @@
  */
 class WalkFetchConnection extends BaseFetchConnection {
 	/** The repository this transport fetches into, or pushes out of. */
-	private final Repository local;
+	final Repository local;
 
 	/** If not null the validator for received objects. */
-	private final ObjectChecker objCheck;
+	final ObjectChecker objCheck;
 
 	/**
 	 * List of all remote repositories we may need to get objects out of.
@@ -180,12 +180,12 @@
 	 */
 	private final HashMap<ObjectId, List<Throwable>> fetchErrors;
 
-	private String lockMessage;
+	String lockMessage;
 
-	private final List<PackLock> packLocks;
+	final List<PackLock> packLocks;
 
 	/** Inserter to write objects onto {@link #local}. */
-	private final ObjectInserter inserter;
+	final ObjectInserter inserter;
 
 	/** Inserter to read objects from {@link #local}. */
 	private final ObjectReader reader;
@@ -267,6 +267,10 @@
 		final HashSet<ObjectId> inWorkQueue = new HashSet<ObjectId>();
 		for (final Ref r : want) {
 			final ObjectId id = r.getObjectId();
+			if (id == null) {
+				throw new NullPointerException(MessageFormat.format(
+						JGitText.get().transportProvidedRefWithNoObjectId, r.getName()));
+			}
 			try {
 				final RevObject obj = revWalk.parseAny(id);
 				if (obj.has(COMPLETE))
@@ -633,10 +637,11 @@
 		final byte[] raw = uol.getCachedBytes();
 		if (objCheck != null) {
 			try {
-				objCheck.check(type, raw);
+				objCheck.check(id, type, raw);
 			} catch (CorruptObjectException e) {
-				throw new TransportException(MessageFormat.format(JGitText.get().transportExceptionInvalid
-						, Constants.typeString(type), id.name(), e.getMessage()));
+				throw new TransportException(MessageFormat.format(
+						JGitText.get().transportExceptionInvalid,
+						Constants.typeString(type), id.name(), e.getMessage()));
 			}
 		}
 
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkPushConnection.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkPushConnection.java
index deecb8e..4eaf3f9 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkPushConnection.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkPushConnection.java
@@ -103,7 +103,7 @@
 	private final URIish uri;
 
 	/** Database connection to the remote repository. */
-	private final WalkRemoteObjectDatabase dest;
+	final WalkRemoteObjectDatabase dest;
 
 	/** The configured transport we were constructed by. */
 	private final Transport transport;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/AbstractTreeIterator.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/AbstractTreeIterator.java
index 41e593e..5813635 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/AbstractTreeIterator.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/AbstractTreeIterator.java
@@ -49,6 +49,7 @@
 import java.nio.ByteBuffer;
 import java.nio.CharBuffer;
 
+import org.eclipse.jgit.attributes.AttributesNode;
 import org.eclipse.jgit.dircache.DirCacheCheckout;
 import org.eclipse.jgit.errors.CorruptObjectException;
 import org.eclipse.jgit.errors.IncorrectObjectTypeException;
@@ -58,6 +59,7 @@
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.ObjectReader;
 import org.eclipse.jgit.treewalk.filter.TreeFilter;
+import org.eclipse.jgit.util.Paths;
 
 /**
  * Walks a Git tree (directory) in Git sort order.
@@ -93,6 +95,13 @@
 	AbstractTreeIterator matches;
 
 	/**
+	 * Parsed rules of .gitattributes file if it exists.
+	 *
+	 * @since 4.2
+	 */
+	protected AttributesNode attributesNode;
+
+	/**
 	 * Number of entries we moved forward to force a D/F conflict match.
 	 *
 	 * @see NameConflictTreeWalk
@@ -320,6 +329,42 @@
 	}
 
 	/**
+	 * Seek the iterator on a file, if present.
+	 *
+	 * @param name
+	 *            file name to find (will not find a directory).
+	 * @return true if the file exists in this tree; false otherwise.
+	 * @throws CorruptObjectException
+	 *             tree is invalid.
+	 * @since 4.2
+	 */
+	public boolean findFile(String name) throws CorruptObjectException {
+		return findFile(Constants.encode(name));
+	}
+
+	/**
+	 * Seek the iterator on a file, if present.
+	 *
+	 * @param name
+	 *            file name to find (will not find a directory).
+	 * @return true if the file exists in this tree; false otherwise.
+	 * @throws CorruptObjectException
+	 *             tree is invalid.
+	 * @since 4.2
+	 */
+	public boolean findFile(byte[] name) throws CorruptObjectException {
+		for (; !eof(); next(1)) {
+			int cmp = pathCompare(name, 0, name.length, 0, pathOffset);
+			if (cmp == 0) {
+				return true;
+			} else if (cmp > 0) {
+				return false;
+			}
+		}
+		return false;
+	}
+
+	/**
 	 * Compare the path of this current entry to a raw buffer.
 	 *
 	 * @param buf
@@ -338,20 +383,9 @@
 	}
 
 	private int pathCompare(byte[] b, int bPos, int bEnd, int bMode, int aPos) {
-		final byte[] a = path;
-		final int aEnd = pathLen;
-
-		for (; aPos < aEnd && bPos < bEnd; aPos++, bPos++) {
-			final int cmp = (a[aPos] & 0xff) - (b[bPos] & 0xff);
-			if (cmp != 0)
-				return cmp;
-		}
-
-		if (aPos < aEnd)
-			return (a[aPos] & 0xff) - lastPathChar(bMode);
-		if (bPos < bEnd)
-			return lastPathChar(mode) - (b[bPos] & 0xff);
-		return lastPathChar(mode) - lastPathChar(bMode);
+		return Paths.compare(
+				path, aPos, pathLen, mode,
+				b, bPos, bEnd, bMode);
 	}
 
 	private static int alreadyMatch(AbstractTreeIterator a,
@@ -368,10 +402,6 @@
 		}
 	}
 
-	private static int lastPathChar(final int mode) {
-		return FileMode.TREE.equals(mode) ? '/' : '\0';
-	}
-
 	/**
 	 * Check if the current entry of both iterators has the same id.
 	 * <p>
@@ -648,6 +678,14 @@
 	}
 
 	/**
+	 * @return true if the iterator implements {@link #stopWalk()}.
+	 * @since 4.2
+	 */
+	protected boolean needsStopWalk() {
+		return false;
+	}
+
+	/**
 	 * @return the length of the name component of the path for the current entry
 	 */
 	public int getNameLength() {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/CanonicalTreeParser.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/CanonicalTreeParser.java
index 0805e50..c038f07 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/CanonicalTreeParser.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/CanonicalTreeParser.java
@@ -44,13 +44,23 @@
 
 package org.eclipse.jgit.treewalk;
 
-import java.io.IOException;
-import java.util.Arrays;
+import static org.eclipse.jgit.lib.Constants.DOT_GIT_ATTRIBUTES;
+import static org.eclipse.jgit.lib.Constants.OBJECT_ID_LENGTH;
+import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
+import static org.eclipse.jgit.lib.Constants.OBJ_TREE;
+import static org.eclipse.jgit.lib.Constants.TYPE_TREE;
+import static org.eclipse.jgit.lib.Constants.encode;
 
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
+import java.util.Collections;
+
+import org.eclipse.jgit.attributes.AttributesNode;
+import org.eclipse.jgit.attributes.AttributesRule;
 import org.eclipse.jgit.errors.IncorrectObjectTypeException;
 import org.eclipse.jgit.errors.MissingObjectException;
 import org.eclipse.jgit.lib.AnyObjectId;
-import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.FileMode;
 import org.eclipse.jgit.lib.MutableObjectId;
 import org.eclipse.jgit.lib.ObjectId;
@@ -59,6 +69,7 @@
 /** Parses raw Git trees from the canonical semi-text/semi-binary format. */
 public class CanonicalTreeParser extends AbstractTreeIterator {
 	private static final byte[] EMPTY = {};
+	private static final byte[] ATTRS = encode(DOT_GIT_ATTRIBUTES);
 
 	private byte[] raw;
 
@@ -124,6 +135,7 @@
 	 *            the raw tree content.
 	 */
 	public void reset(final byte[] treeData) {
+		attributesNode = null;
 		raw = treeData;
 		prevPtr = -1;
 		currPtr = 0;
@@ -199,7 +211,7 @@
 	 */
 	public void reset(final ObjectReader reader, final AnyObjectId id)
 			throws IncorrectObjectTypeException, IOException {
-		reset(reader.open(id, Constants.OBJ_TREE).getCachedBytes());
+		reset(reader.open(id, OBJ_TREE).getCachedBytes());
 	}
 
 	@Override
@@ -209,7 +221,7 @@
 		idBuffer.fromRaw(idBuffer(), idOffset());
 		if (!FileMode.TREE.equals(mode)) {
 			final ObjectId me = idBuffer.toObjectId();
-			throw new IncorrectObjectTypeException(me, Constants.TYPE_TREE);
+			throw new IncorrectObjectTypeException(me, TYPE_TREE);
 		}
 		return createSubtreeIterator0(reader, idBuffer);
 	}
@@ -254,7 +266,7 @@
 
 	@Override
 	public int idOffset() {
-		return nextPtr - Constants.OBJECT_ID_LENGTH;
+		return nextPtr - OBJECT_ID_LENGTH;
 	}
 
 	@Override
@@ -292,7 +304,7 @@
 			prevPtr = ptr;
 			while (raw[ptr] != 0)
 				ptr++;
-			ptr += Constants.OBJECT_ID_LENGTH + 1;
+			ptr += OBJECT_ID_LENGTH + 1;
 		}
 		if (delta != 0)
 			throw new ArrayIndexOutOfBoundsException(delta);
@@ -328,7 +340,7 @@
 			trace[delta] = ptr;
 			while (raw[ptr] != 0)
 				ptr++;
-			ptr += Constants.OBJECT_ID_LENGTH + 1;
+			ptr += OBJECT_ID_LENGTH + 1;
 		}
 		if (trace[1] == -1)
 			throw new ArrayIndexOutOfBoundsException(delta);
@@ -363,6 +375,46 @@
 			}
 		}
 		pathLen = tmp;
-		nextPtr = ptr + Constants.OBJECT_ID_LENGTH;
+		nextPtr = ptr + OBJECT_ID_LENGTH;
+	}
+
+	/**
+	 * Retrieve the {@link AttributesNode} for the current entry.
+	 *
+	 * @param reader
+	 *            {@link ObjectReader} used to parse the .gitattributes entry.
+	 * @return {@link AttributesNode} for the current entry.
+	 * @throws IOException
+	 * @since 4.2
+	 */
+	public AttributesNode getEntryAttributesNode(ObjectReader reader)
+			throws IOException {
+		if (attributesNode == null) {
+			attributesNode = findAttributes(reader);
+		}
+		return attributesNode.getRules().isEmpty() ? null : attributesNode;
+	}
+
+	private AttributesNode findAttributes(ObjectReader reader)
+			throws IOException {
+		CanonicalTreeParser itr = new CanonicalTreeParser();
+		itr.reset(raw);
+		if (itr.findFile(ATTRS)) {
+			return loadAttributes(reader, itr.getEntryObjectId());
+		}
+		return noAttributes();
+	}
+
+	private static AttributesNode loadAttributes(ObjectReader reader,
+			AnyObjectId id) throws IOException {
+		AttributesNode r = new AttributesNode();
+		try (InputStream in = reader.open(id, OBJ_BLOB).openStream()) {
+			r.parse(in);
+		}
+		return r.getRules().isEmpty() ? noAttributes() : r;
+	}
+
+	private static AttributesNode noAttributes() {
+		return new AttributesNode(Collections.<AttributesRule> emptyList());
 	}
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/EmptyTreeIterator.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/EmptyTreeIterator.java
index 8dbf80e..ec4a84e 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/EmptyTreeIterator.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/EmptyTreeIterator.java
@@ -142,4 +142,9 @@
 		if (parent != null)
 			parent.stopWalk();
 	}
+
+	@Override
+	protected boolean needsStopWalk() {
+		return parent != null && parent.needsStopWalk();
+	}
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/FileTreeIterator.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/FileTreeIterator.java
index 8d2cb1d..accf495 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/FileTreeIterator.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/FileTreeIterator.java
@@ -67,8 +67,8 @@
  */
 public class FileTreeIterator extends WorkingTreeIterator {
 	/**
-	 * the starting directory. This directory should correspond to the root of
-	 * the repository.
+	 * the starting directory of this Iterator. All entries are located directly
+	 * in this directory.
 	 */
 	protected final File directory;
 
@@ -238,8 +238,6 @@
 
 	@Override
 	protected byte[] idSubmodule(final Entry e) {
-		if (repository == null)
-			return idSubmodule(getDirectory(), e);
-		return super.idSubmodule(e);
+		return idSubmodule(getDirectory(), e);
 	}
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/NameConflictTreeWalk.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/NameConflictTreeWalk.java
index 2d6acbd..d2195a8 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/NameConflictTreeWalk.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/NameConflictTreeWalk.java
@@ -43,6 +43,8 @@
 
 package org.eclipse.jgit.treewalk;
 
+import java.io.IOException;
+
 import org.eclipse.jgit.dircache.DirCacheBuilder;
 import org.eclipse.jgit.errors.CorruptObjectException;
 import org.eclipse.jgit.lib.FileMode;
@@ -96,7 +98,7 @@
 	 *            the repository the walker will obtain data from.
 	 */
 	public NameConflictTreeWalk(final Repository repo) {
-		this(repo.newObjectReader());
+		super(repo);
 	}
 
 	/**
@@ -338,6 +340,41 @@
 			dfConflict = null;
 	}
 
+	void stopWalk() throws IOException {
+		if (!needsStopWalk()) {
+			return;
+		}
+
+		// Name conflicts make aborting early difficult. Multiple paths may
+		// exist between the file and directory versions of a name. To ensure
+		// the directory version is skipped over (as it was previously visited
+		// during the file version step) requires popping up the stack and
+		// finishing out each subtree that the walker dove into. Siblings in
+		// parents do not need to be recursed into, bounding the cost.
+		for (;;) {
+			AbstractTreeIterator t = min();
+			if (t.eof()) {
+				if (depth > 0) {
+					exitSubtree();
+					popEntriesEqual();
+					continue;
+				}
+				return;
+			}
+			currentHead = t;
+			skipEntriesEqual();
+		}
+	}
+
+	private boolean needsStopWalk() {
+		for (AbstractTreeIterator t : trees) {
+			if (t.needsStopWalk()) {
+				return true;
+			}
+		}
+		return false;
+	}
+
 	/**
 	 * True if the current entry is covered by a directory/file conflict.
 	 *
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 06e8284..5cd713d 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/TreeWalk.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/TreeWalk.java
@@ -45,12 +45,26 @@
 package org.eclipse.jgit.treewalk;
 
 import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
 
+import org.eclipse.jgit.annotations.Nullable;
+import org.eclipse.jgit.api.errors.JGitInternalException;
+import org.eclipse.jgit.attributes.Attribute;
+import org.eclipse.jgit.attributes.Attribute.State;
+import org.eclipse.jgit.attributes.Attributes;
+import org.eclipse.jgit.attributes.AttributesNode;
+import org.eclipse.jgit.attributes.AttributesNodeProvider;
+import org.eclipse.jgit.attributes.AttributesProvider;
+import org.eclipse.jgit.dircache.DirCacheBuildIterator;
+import org.eclipse.jgit.dircache.DirCacheIterator;
 import org.eclipse.jgit.errors.CorruptObjectException;
 import org.eclipse.jgit.errors.IncorrectObjectTypeException;
 import org.eclipse.jgit.errors.MissingObjectException;
 import org.eclipse.jgit.errors.StopWalkException;
 import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.lib.Config;
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.FileMode;
 import org.eclipse.jgit.lib.MutableObjectId;
@@ -60,6 +74,7 @@
 import org.eclipse.jgit.revwalk.RevTree;
 import org.eclipse.jgit.treewalk.filter.PathFilter;
 import org.eclipse.jgit.treewalk.filter.TreeFilter;
+import org.eclipse.jgit.util.QuotedString;
 import org.eclipse.jgit.util.RawParseUtils;
 
 /**
@@ -82,10 +97,45 @@
  * Multiple simultaneous TreeWalk instances per {@link Repository} are
  * permitted, even from concurrent threads.
  */
-public class TreeWalk implements AutoCloseable {
+public class TreeWalk implements AutoCloseable, AttributesProvider {
 	private static final AbstractTreeIterator[] NO_TREES = {};
 
 	/**
+	 * @since 4.2
+	 */
+	public static enum OperationType {
+		/**
+		 * Represents a checkout operation (for example a checkout or reset
+		 * operation).
+		 */
+		CHECKOUT_OP,
+
+		/**
+		 * Represents a checkin operation (for example an add operation)
+		 */
+		CHECKIN_OP
+	}
+
+	/**
+	 *            Type of operation you want to retrieve the git attributes for.
+	 */
+	private OperationType operationType = OperationType.CHECKOUT_OP;
+
+	/**
+	 * The filter command as defined in gitattributes. The keys are
+	 * filterName+"."+filterCommandType. E.g. "lfs.clean"
+	 */
+	private Map<String, String> filterCommandsByNameDotType = new HashMap<String, String>();
+
+	/**
+	 * @param operationType
+	 * @since 4.2
+	 */
+	public void setOperationType(OperationType operationType) {
+		this.operationType = operationType;
+	}
+
+	/**
 	 * Open a tree walk and filter to exactly one path.
 	 * <p>
 	 * The returned tree walk is already positioned on the requested path, so
@@ -207,14 +257,21 @@
 
 	private boolean postOrderTraversal;
 
-	private int depth;
+	int depth;
 
 	private boolean advance;
 
 	private boolean postChildren;
 
+	private AttributesNodeProvider attributesNodeProvider;
+
 	AbstractTreeIterator currentHead;
 
+	/** Cached attribute for the current entry */
+	private Attributes attrs = null;
+
+	private Config config;
+
 	/**
 	 * Create a new tree walker for a given repository.
 	 *
@@ -225,6 +282,8 @@
 	 */
 	public TreeWalk(final Repository repo) {
 		this(repo.newObjectReader(), true);
+		config = repo.getConfig();
+		attributesNodeProvider = repo.createAttributesNodeProvider();
 	}
 
 	/**
@@ -356,8 +415,29 @@
 		postOrderTraversal = b;
 	}
 
+	/**
+	 * Sets the {@link AttributesNodeProvider} for this {@link TreeWalk}.
+	 * <p>
+	 * This is a requirement for a correct computation of the git attributes.
+	 * If this {@link TreeWalk} has been built using
+	 * {@link #TreeWalk(Repository)} constructor, the
+	 * {@link AttributesNodeProvider} has already been set. Indeed,the
+	 * {@link Repository} can provide an {@link AttributesNodeProvider} using
+	 * {@link Repository#createAttributesNodeProvider()} method. Otherwise you
+	 * should provide one.
+	 * </p>
+	 *
+	 * @see Repository#createAttributesNodeProvider()
+	 * @param provider
+	 * @since 4.2
+	 */
+	public void setAttributesNodeProvider(AttributesNodeProvider provider) {
+		attributesNodeProvider = provider;
+	}
+
 	/** Reset this walker so new tree iterators can be added to it. */
 	public void reset() {
+		attrs = null;
 		trees = NO_TREES;
 		advance = false;
 		depth = 0;
@@ -401,6 +481,7 @@
 
 		advance = false;
 		depth = 0;
+		attrs = null;
 	}
 
 	/**
@@ -450,6 +531,7 @@
 		trees = r;
 		advance = false;
 		depth = 0;
+		attrs = null;
 	}
 
 	/**
@@ -492,18 +574,13 @@
 	 * @param p
 	 *            an iterator to walk over. The iterator should be new, with no
 	 *            parent, and should still be positioned before the first entry.
-	 *            The tree which the iterator operates on must have the same root
-	 *            as other trees in the walk.
-	 *
+	 *            The tree which the iterator operates on must have the same
+	 *            root as other trees in the walk.
 	 * @return position of this tree within the walker.
-	 * @throws CorruptObjectException
-	 *             the iterator was unable to obtain its first entry, due to
-	 *             possible data corruption within the backing data store.
 	 */
-	public int addTree(final AbstractTreeIterator p)
-			throws CorruptObjectException {
-		final int n = trees.length;
-		final AbstractTreeIterator[] newTrees = new AbstractTreeIterator[n + 1];
+	public int addTree(AbstractTreeIterator p) {
+		int n = trees.length;
+		AbstractTreeIterator[] newTrees = new AbstractTreeIterator[n + 1];
 
 		System.arraycopy(trees, 0, newTrees, 0, n);
 		newTrees[n] = p;
@@ -546,6 +623,7 @@
 	public boolean next() throws MissingObjectException,
 			IncorrectObjectTypeException, CorruptObjectException, IOException {
 		try {
+			attrs = null;
 			if (advance) {
 				advance = false;
 				postChildren = false;
@@ -583,13 +661,30 @@
 				return true;
 			}
 		} catch (StopWalkException stop) {
-			for (final AbstractTreeIterator t : trees)
-				t.stopWalk();
+			stopWalk();
 			return false;
 		}
 	}
 
 	/**
+	 * Notify iterators the walk is aborting.
+	 * <p>
+	 * Primarily to notify {@link DirCacheBuildIterator} the walk is aborting so
+	 * that it can copy any remaining entries.
+	 *
+	 * @throws IOException
+	 *             if traversal of remaining entries throws an exception during
+	 *             object access. This should never occur as remaining trees
+	 *             should already be in memory, however the methods used to
+	 *             finish traversal are declared to throw IOException.
+	 */
+	void stopWalk() throws IOException {
+		for (AbstractTreeIterator t : trees) {
+			t.stopWalk();
+		}
+	}
+
+	/**
 	 * Obtain the tree iterator for the current entry.
 	 * <p>
 	 * Entering into (or exiting out of) a subtree causes the current tree
@@ -779,10 +874,13 @@
 	 * Test if the supplied path matches the current entry's path.
 	 * <p>
 	 * This method tests that the supplied path is exactly equal to the current
-	 * entry, or is one of its parent directories. It is faster to use this
+	 * entry or is one of its parent directories. It is faster to use this
 	 * method then to use {@link #getPathString()} to first create a String
 	 * object, then test <code>startsWith</code> or some other type of string
 	 * match function.
+	 * <p>
+	 * If the current entry is a subtree, then all paths within the subtree
+	 * are considered to match it.
 	 *
 	 * @param p
 	 *            path buffer to test. Callers should ensure the path does not
@@ -818,7 +916,7 @@
 			// If p[ci] == '/' then pattern matches this subtree,
 			// otherwise we cannot be certain so we return -1.
 			//
-			return p[ci] == '/' ? 0 : -1;
+			return p[ci] == '/' && FileMode.TREE.equals(t.mode) ? 0 : -1;
 		}
 
 		// Both strings are identical.
@@ -915,6 +1013,7 @@
 	 */
 	public void enterSubtree() throws MissingObjectException,
 			IncorrectObjectTypeException, CorruptObjectException, IOException {
+		attrs = null;
 		final AbstractTreeIterator ch = currentHead;
 		final AbstractTreeIterator[] tmp = new AbstractTreeIterator[trees.length];
 		for (int i = 0; i < trees.length; i++) {
@@ -979,7 +1078,7 @@
 		}
 	}
 
-	private void exitSubtree() {
+	void exitSubtree() {
 		depth--;
 		for (int i = 0; i < trees.length; i++)
 			trees[i] = trees[i].parent;
@@ -1008,4 +1107,296 @@
 	static String pathOf(final byte[] buf, int pos, int end) {
 		return RawParseUtils.decode(Constants.CHARSET, buf, pos, end);
 	}
+
+	/**
+	 * Retrieve the git attributes for the current entry.
+	 *
+	 * <h4>Git attribute computation</h4>
+	 *
+	 * <ul>
+	 * <li>Get the attributes matching the current path entry from the info file
+	 * (see {@link AttributesNodeProvider#getInfoAttributesNode()}).</li>
+	 * <li>Completes the list of attributes using the .gitattributes files
+	 * located on the current path (the further the directory that contains
+	 * .gitattributes is from the path in question, the lower its precedence).
+	 * For a checkin operation, it will look first on the working tree (if any).
+	 * If there is no attributes file, it will fallback on the index. For a
+	 * checkout operation, it will first use the index entry and then fallback
+	 * on the working tree if none.</li>
+	 * <li>In the end, completes the list of matching attributes using the
+	 * global attribute file define in the configuration (see
+	 * {@link AttributesNodeProvider#getGlobalAttributesNode()})</li>
+	 *
+	 * </ul>
+	 *
+	 *
+	 * <h4>Iterator constraints</h4>
+	 *
+	 * <p>
+	 * In order to have a correct list of attributes for the current entry, this
+	 * {@link TreeWalk} requires to have at least one
+	 * {@link AttributesNodeProvider} and a {@link DirCacheIterator} set up. An
+	 * {@link AttributesNodeProvider} is used to retrieve the attributes from
+	 * the info attributes file and the global attributes file. The
+	 * {@link DirCacheIterator} is used to retrieve the .gitattributes files
+	 * stored in the index. A {@link WorkingTreeIterator} can also be provided
+	 * to access the local version of the .gitattributes files. If none is
+	 * provided it will fallback on the {@link DirCacheIterator}.
+	 * </p>
+	 *
+	 * @return a {@link Set} of {@link Attribute}s that match the current entry.
+	 * @since 4.2
+	 */
+	public Attributes getAttributes() {
+		if (attrs != null)
+			return attrs;
+
+		if (attributesNodeProvider == null) {
+			// The work tree should have a AttributesNodeProvider to be able to
+			// retrieve the info and global attributes node
+			throw new IllegalStateException(
+					"The tree walk should have one AttributesNodeProvider set in order to compute the git attributes."); //$NON-NLS-1$
+		}
+
+		WorkingTreeIterator workingTreeIterator = getTree(WorkingTreeIterator.class);
+		DirCacheIterator dirCacheIterator = getTree(DirCacheIterator.class);
+		CanonicalTreeParser other = getTree(CanonicalTreeParser.class);
+
+		if (workingTreeIterator == null && dirCacheIterator == null
+				&& other == null) {
+			// Can not retrieve the attributes without at least one of the above
+			// iterators.
+			return new Attributes();
+		}
+
+		String path = currentHead.getEntryPathString();
+		final boolean isDir = FileMode.TREE.equals(currentHead.mode);
+		Attributes attributes = new Attributes();
+		try {
+			// Gets the global attributes node
+			AttributesNode globalNodeAttr = attributesNodeProvider
+					.getGlobalAttributesNode();
+			// Gets the info attributes node
+			AttributesNode infoNodeAttr = attributesNodeProvider
+					.getInfoAttributesNode();
+
+			// Gets the info attributes
+			if (infoNodeAttr != null) {
+				infoNodeAttr.getAttributes(path, isDir, attributes);
+			}
+
+			// Gets the attributes located on the current entry path
+			getPerDirectoryEntryAttributes(path, isDir, operationType,
+					workingTreeIterator, dirCacheIterator, other, attributes);
+
+			// Gets the attributes located in the global attribute file
+			if (globalNodeAttr != null) {
+				globalNodeAttr.getAttributes(path, isDir, attributes);
+			}
+		} catch (IOException e) {
+			throw new JGitInternalException("Error while parsing attributes", e); //$NON-NLS-1$
+		}
+		// now after all attributes are collected - in the correct hierarchy
+		// order - remove all unspecified entries (the ! marker)
+		for (Attribute a : attributes.getAll()) {
+			if (a.getState() == State.UNSPECIFIED)
+				attributes.remove(a.getKey());
+		}
+		return attributes;
+	}
+
+	/**
+	 * Get the attributes located on the current entry path.
+	 *
+	 * @param path
+	 *            current entry path
+	 * @param isDir
+	 *            holds true if the current entry is a directory
+	 * @param opType
+	 *            type of operation
+	 * @param workingTreeIterator
+	 *            a {@link WorkingTreeIterator} matching the current entry
+	 * @param dirCacheIterator
+	 *            a {@link DirCacheIterator} matching the current entry
+	 * @param other
+	 *            a {@link CanonicalTreeParser} matching the current entry
+	 * @param attributes
+	 *            Non null map holding the existing attributes. This map will be
+	 *            augmented with new entry. None entry will be overrided.
+	 * @throws IOException
+	 *             It raises an {@link IOException} if a problem appears while
+	 *             parsing one on the attributes file.
+	 */
+	private void getPerDirectoryEntryAttributes(String path, boolean isDir,
+			OperationType opType, WorkingTreeIterator workingTreeIterator,
+			DirCacheIterator dirCacheIterator, CanonicalTreeParser other,
+			Attributes attributes)
+			throws IOException {
+		// Prevents infinite recurrence
+		if (workingTreeIterator != null || dirCacheIterator != null
+				|| other != null) {
+			AttributesNode currentAttributesNode = getCurrentAttributesNode(
+					opType, workingTreeIterator, dirCacheIterator, other);
+			if (currentAttributesNode != null) {
+				currentAttributesNode.getAttributes(path, isDir, attributes);
+			}
+			getPerDirectoryEntryAttributes(path, isDir, opType,
+					getParent(workingTreeIterator, WorkingTreeIterator.class),
+					getParent(dirCacheIterator, DirCacheIterator.class),
+					getParent(other, CanonicalTreeParser.class), attributes);
+		}
+	}
+
+	private static <T extends AbstractTreeIterator> T getParent(T current,
+			Class<T> type) {
+		if (current != null) {
+			AbstractTreeIterator parent = current.parent;
+			if (type.isInstance(parent)) {
+				return type.cast(parent);
+			}
+		}
+		return null;
+	}
+
+	private <T extends AbstractTreeIterator> T getTree(Class<T> type) {
+		for (int i = 0; i < trees.length; i++) {
+			AbstractTreeIterator tree = trees[i];
+			if (type.isInstance(tree)) {
+				return type.cast(tree);
+			}
+		}
+		return null;
+	}
+
+	/**
+	 * Get the {@link AttributesNode} for the current entry.
+	 * <p>
+	 * This method implements the fallback mechanism between the index and the
+	 * working tree depending on the operation type
+	 * </p>
+	 *
+	 * @param opType
+	 * @param workingTreeIterator
+	 * @param dirCacheIterator
+	 * @param other
+	 * @return a {@link AttributesNode} of the current entry,
+	 *         {@link NullPointerException} otherwise.
+	 * @throws IOException
+	 *             It raises an {@link IOException} if a problem appears while
+	 *             parsing one on the attributes file.
+	 */
+	private AttributesNode getCurrentAttributesNode(OperationType opType,
+			@Nullable WorkingTreeIterator workingTreeIterator,
+			@Nullable DirCacheIterator dirCacheIterator,
+			@Nullable CanonicalTreeParser other)
+					throws IOException {
+		AttributesNode attributesNode = null;
+		switch (opType) {
+		case CHECKIN_OP:
+			if (workingTreeIterator != null) {
+				attributesNode = workingTreeIterator.getEntryAttributesNode();
+			}
+			if (attributesNode == null && dirCacheIterator != null) {
+				attributesNode = getAttributesNode(dirCacheIterator
+						.getEntryAttributesNode(getObjectReader()),
+						attributesNode);
+			}
+			if (attributesNode == null && other != null) {
+				attributesNode = getAttributesNode(
+						other.getEntryAttributesNode(getObjectReader()),
+						attributesNode);
+			}
+			break;
+		case CHECKOUT_OP:
+			if (other != null) {
+				attributesNode = other
+						.getEntryAttributesNode(getObjectReader());
+			}
+			if (dirCacheIterator != null) {
+				attributesNode = getAttributesNode(dirCacheIterator
+						.getEntryAttributesNode(getObjectReader()),
+						attributesNode);
+			}
+			if (attributesNode == null && workingTreeIterator != null) {
+				attributesNode = getAttributesNode(
+						workingTreeIterator.getEntryAttributesNode(),
+						attributesNode);
+			}
+			break;
+		default:
+			throw new IllegalStateException(
+					"The only supported operation types are:" //$NON-NLS-1$
+							+ OperationType.CHECKIN_OP + "," //$NON-NLS-1$
+							+ OperationType.CHECKOUT_OP);
+		}
+
+		return attributesNode;
+	}
+
+	private static AttributesNode getAttributesNode(AttributesNode value,
+			AttributesNode defaultValue) {
+		return (value == null) ? defaultValue : value;
+	}
+
+	/**
+	 * Inspect config and attributes to return a filtercommand applicable for
+	 * the current path
+	 *
+	 * @param filterCommandType
+	 *            which type of filterCommand should be executed. E.g. "clean",
+	 *            "smudge"
+	 * @return a filter command
+	 * @throws IOException
+	 * @since 4.2
+	 */
+	public String getFilterCommand(String filterCommandType)
+			throws IOException {
+		Attributes attributes = getAttributes();
+
+		Attribute f = attributes.get(Constants.ATTR_FILTER);
+		if (f == null) {
+			return null;
+		}
+		String filterValue = f.getValue();
+		if (filterValue == null) {
+			return null;
+		}
+
+		String filterCommand = getFilterCommandDefinition(filterValue,
+				filterCommandType);
+		if (filterCommand == null) {
+			return null;
+		}
+		return filterCommand.replaceAll("%f", //$NON-NLS-1$
+				QuotedString.BOURNE.quote((getPathString())));
+	}
+
+	/**
+	 * Get the filter command how it is defined in gitconfig. The returned
+	 * string may contain "%f" which needs to be replaced by the current path
+	 * before executing the filter command. These filter definitions are cached
+	 * for better performance.
+	 *
+	 * @param filterDriverName
+	 *            The name of the filter driver as it is referenced in the
+	 *            gitattributes file. E.g. "lfs". For each filter driver there
+	 *            may be many commands defined in the .gitconfig
+	 * @param filterCommandType
+	 *            The type of the filter command for a specific filter driver.
+	 *            May be "clean" or "smudge".
+	 * @return the definition of the command to be executed for this filter
+	 *         driver and filter command
+	 */
+	private String getFilterCommandDefinition(String filterDriverName,
+			String filterCommandType) {
+		String key = filterDriverName + "." + filterCommandType; //$NON-NLS-1$
+		String filterCommand = filterCommandsByNameDotType.get(key);
+		if (filterCommand != null)
+			return filterCommand;
+		filterCommand = config.getString(Constants.ATTR_FILTER,
+				filterDriverName, filterCommandType);
+		if (filterCommand != null)
+			filterCommandsByNameDotType.put(key, filterCommand);
+		return filterCommand;
+	}
 }
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 73ab04f..0d617ee 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java
@@ -62,6 +62,7 @@
 import java.util.Collections;
 import java.util.Comparator;
 
+import org.eclipse.jgit.api.errors.FilterFailedException;
 import org.eclipse.jgit.attributes.AttributesNode;
 import org.eclipse.jgit.attributes.AttributesRule;
 import org.eclipse.jgit.diff.RawText;
@@ -76,6 +77,7 @@
 import org.eclipse.jgit.internal.JGitText;
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.CoreConfig;
+import org.eclipse.jgit.lib.CoreConfig.AutoCRLF;
 import org.eclipse.jgit.lib.CoreConfig.CheckStat;
 import org.eclipse.jgit.lib.CoreConfig.SymLinks;
 import org.eclipse.jgit.lib.FileMode;
@@ -85,7 +87,9 @@
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.submodule.SubmoduleWalk;
 import org.eclipse.jgit.util.FS;
+import org.eclipse.jgit.util.FS.ExecutionResult;
 import org.eclipse.jgit.util.IO;
+import org.eclipse.jgit.util.Paths;
 import org.eclipse.jgit.util.RawParseUtils;
 import org.eclipse.jgit.util.io.EolCanonicalizingInputStream;
 
@@ -101,6 +105,8 @@
  * @see FileTreeIterator
  */
 public abstract class WorkingTreeIterator extends AbstractTreeIterator {
+	private static final int MAX_EXCEPTION_TEXT_SIZE = 10 * 1024;
+
 	/** An empty entry array, suitable for {@link #init(Entry[])}. */
 	protected static final Entry[] EOF = {};
 
@@ -134,8 +140,7 @@
 	/** If there is a .gitignore file present, the parsed rules from it. */
 	private IgnoreNode ignoreNode;
 
-	/** If there is a .gitattributes file present, the parsed rules from it. */
-	private AttributesNode attributesNode;
+	private String cleanFilterCommand;
 
 	/** Repository that is the root level being iterated over */
 	protected Repository repository;
@@ -147,19 +152,6 @@
 	private int contentIdOffset;
 
 	/**
-	 * Holds the {@link AttributesNode} that is stored in
-	 * $GIT_DIR/info/attributes file.
-	 */
-	private AttributesNode infoAttributeNode;
-
-	/**
-	 * Holds the {@link AttributesNode} that is stored in global attribute file.
-	 *
-	 * @see CoreConfig#getAttributesFile()
-	 */
-	private AttributesNode globalAttributeNode;
-
-	/**
 	 * Create a new iterator with no parent.
 	 *
 	 * @param options
@@ -202,8 +194,7 @@
 	protected WorkingTreeIterator(final WorkingTreeIterator p) {
 		super(p);
 		state = p.state;
-		infoAttributeNode = p.infoAttributeNode;
-		globalAttributeNode = p.globalAttributeNode;
+		repository = p.repository;
 	}
 
 	/**
@@ -223,10 +214,6 @@
 		else
 			entry = null;
 		ignoreNode = new RootIgnoreNode(entry, repo);
-
-		infoAttributeNode = new InfoAttributesNode(repo);
-
-		globalAttributeNode = new GlobalAttributesNode(repo);
 	}
 
 	/**
@@ -370,7 +357,8 @@
 
 	private InputStream possiblyFilteredInputStream(final Entry e,
 			final InputStream is, final long len) throws IOException {
-		if (!mightNeedCleaning()) {
+		boolean mightNeedCleaning = mightNeedCleaning();
+		if (!mightNeedCleaning) {
 			canonLen = len;
 			return is;
 		}
@@ -388,7 +376,8 @@
 			return new ByteArrayInputStream(raw, 0, n);
 		}
 
-		if (isBinary(e)) {
+		// TODO: fix autocrlf causing mightneedcleaning
+		if (!mightNeedCleaning && isBinary(e)) {
 			canonLen = len;
 			return is;
 		}
@@ -412,10 +401,12 @@
 		}
 	}
 
-	private boolean mightNeedCleaning() {
+	private boolean mightNeedCleaning() throws IOException {
 		switch (getOptions().getAutoCRLF()) {
 		case FALSE:
 		default:
+			if (getCleanFilterCommand() != null)
+				return true;
 			return false;
 
 		case TRUE:
@@ -437,8 +428,7 @@
 		}
 	}
 
-	private static ByteBuffer filterClean(byte[] src, int n)
-			throws IOException {
+	private ByteBuffer filterClean(byte[] src, int n) throws IOException {
 		InputStream in = new ByteArrayInputStream(src);
 		try {
 			return IO.readWholeStream(filterClean(in), n);
@@ -447,8 +437,42 @@
 		}
 	}
 
-	private static InputStream filterClean(InputStream in) {
-		return new EolCanonicalizingInputStream(in, true);
+	private InputStream filterClean(InputStream in) throws IOException {
+		in = handleAutoCRLF(in);
+		String filterCommand = getCleanFilterCommand();
+		if (filterCommand != null) {
+			FS fs = repository.getFS();
+			ProcessBuilder filterProcessBuilder = fs.runInShell(filterCommand,
+					new String[0]);
+			filterProcessBuilder.directory(repository.getWorkTree());
+			filterProcessBuilder.environment().put(Constants.GIT_DIR_KEY,
+					repository.getDirectory().getAbsolutePath());
+			ExecutionResult result;
+			try {
+				result = fs.execute(filterProcessBuilder, in);
+			} catch (IOException | InterruptedException e) {
+				throw new IOException(new FilterFailedException(e,
+						filterCommand, getEntryPathString()));
+			}
+			int rc = result.getRc();
+			if (rc != 0) {
+				throw new IOException(new FilterFailedException(rc,
+						filterCommand, getEntryPathString(),
+						result.getStdout().toByteArray(MAX_EXCEPTION_TEXT_SIZE),
+						RawParseUtils.decode(result.getStderr()
+								.toByteArray(MAX_EXCEPTION_TEXT_SIZE))));
+			}
+			return result.getStdout().openInputStream();
+		}
+		return in;
+	}
+
+	private InputStream handleAutoCRLF(InputStream in) {
+		AutoCRLF autoCRLF = getOptions().getAutoCRLF();
+		if (autoCRLF == AutoCRLF.TRUE || autoCRLF == AutoCRLF.INPUT) {
+			in = new EolCanonicalizingInputStream(in, true);
+		}
+		return in;
 	}
 
 	/**
@@ -507,6 +531,7 @@
 		System.arraycopy(e.encodedName, 0, path, pathOffset, nameLen);
 		pathLen = pathOffset + nameLen;
 		canonLen = -1;
+		cleanFilterCommand = null;
 	}
 
 	/**
@@ -667,67 +692,14 @@
 		return attributesNode;
 	}
 
-	/**
-	 * Retrieves the {@link AttributesNode} that holds the information located
-	 * in $GIT_DIR/info/attributes file.
-	 *
-	 * @return the {@link AttributesNode} that holds the information located in
-	 *         $GIT_DIR/info/attributes file.
-	 * @throws IOException
-	 *             if an error is raised while parsing the attributes file
-	 * @since 3.7
-	 */
-	public AttributesNode getInfoAttributesNode() throws IOException {
-		if (infoAttributeNode instanceof InfoAttributesNode)
-			infoAttributeNode = ((InfoAttributesNode) infoAttributeNode).load();
-		return infoAttributeNode;
-	}
-
-	/**
-	 * Retrieves the {@link AttributesNode} that holds the information located
-	 * in system-wide file.
-	 *
-	 * @return the {@link AttributesNode} that holds the information located in
-	 *         system-wide file.
-	 * @throws IOException
-	 *             IOException if an error is raised while parsing the
-	 *             attributes file
-	 * @see CoreConfig#getAttributesFile()
-	 * @since 3.7
-	 */
-	public AttributesNode getGlobalAttributesNode() throws IOException {
-		if (globalAttributeNode instanceof GlobalAttributesNode)
-			globalAttributeNode = ((GlobalAttributesNode) globalAttributeNode)
-					.load();
-		return globalAttributeNode;
-	}
-
 	private static final Comparator<Entry> ENTRY_CMP = new Comparator<Entry>() {
-		public int compare(final Entry o1, final Entry o2) {
-			final byte[] a = o1.encodedName;
-			final byte[] b = o2.encodedName;
-			final int aLen = o1.encodedNameLen;
-			final int bLen = o2.encodedNameLen;
-			int cPos;
-
-			for (cPos = 0; cPos < aLen && cPos < bLen; cPos++) {
-				final int cmp = (a[cPos] & 0xff) - (b[cPos] & 0xff);
-				if (cmp != 0)
-					return cmp;
-			}
-
-			if (cPos < aLen)
-				return (a[cPos] & 0xff) - lastPathChar(o2);
-			if (cPos < bLen)
-				return lastPathChar(o1) - (b[cPos] & 0xff);
-			return lastPathChar(o1) - lastPathChar(o2);
+		public int compare(Entry a, Entry b) {
+			return Paths.compare(
+					a.encodedName, 0, a.encodedNameLen, a.getMode().getBits(),
+					b.encodedName, 0, b.encodedNameLen, b.getMode().getBits());
 		}
 	};
 
-	static int lastPathChar(final Entry e) {
-		return e.getMode() == FileMode.TREE ? '/' : '\0';
-	}
-
 	/**
 	 * Constructor helper.
 	 *
@@ -1296,68 +1268,6 @@
 		}
 	}
 
-	/**
-	 * Attributes node loaded from global system-wide file.
-	 */
-	private static class GlobalAttributesNode extends AttributesNode {
-		final Repository repository;
-
-		GlobalAttributesNode(Repository repository) {
-			this.repository = repository;
-		}
-
-		AttributesNode load() throws IOException {
-			AttributesNode r = new AttributesNode();
-
-			FS fs = repository.getFS();
-			String path = repository.getConfig().get(CoreConfig.KEY)
-					.getAttributesFile();
-			if (path != null) {
-				File attributesFile;
-				if (path.startsWith("~/")) //$NON-NLS-1$
-					attributesFile = fs.resolve(fs.userHome(),
-							path.substring(2));
-				else
-					attributesFile = fs.resolve(null, path);
-				loadRulesFromFile(r, attributesFile);
-			}
-			return r.getRules().isEmpty() ? null : r;
-		}
-	}
-
-	/** Magic type indicating there may be rules for the top level. */
-	private static class InfoAttributesNode extends AttributesNode {
-		final Repository repository;
-
-		InfoAttributesNode(Repository repository) {
-			this.repository = repository;
-		}
-
-		AttributesNode load() throws IOException {
-			AttributesNode r = new AttributesNode();
-
-			FS fs = repository.getFS();
-
-			File attributes = fs.resolve(repository.getDirectory(),
-					"info/attributes"); //$NON-NLS-1$
-			loadRulesFromFile(r, attributes);
-
-			return r.getRules().isEmpty() ? null : r;
-		}
-
-	}
-
-	private static void loadRulesFromFile(AttributesNode r, File attrs)
-			throws FileNotFoundException, IOException {
-		if (attrs.exists()) {
-			FileInputStream in = new FileInputStream(attrs);
-			try {
-				r.parse(in);
-			} finally {
-				in.close();
-			}
-		}
-	}
 
 	private static final class IteratorState {
 		/** Options used to process the working tree. */
@@ -1390,4 +1300,18 @@
 			}
 		}
 	}
+
+	/**
+	 * @return the clean filter command for the current entry or
+	 *         <code>null</code> if no such command is defined
+	 * @throws IOException
+	 * @since 4.2
+	 */
+	public String getCleanFilterCommand() throws IOException {
+		if (cleanFilterCommand == null && state.walk != null) {
+			cleanFilterCommand = state.walk
+					.getFilterCommand(Constants.ATTR_FILTER_TYPE_CLEAN);
+		}
+		return cleanFilterCommand;
+	}
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/PathFilterGroup.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/PathFilterGroup.java
index bdfde0b..7601956 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/PathFilterGroup.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/PathFilterGroup.java
@@ -245,9 +245,9 @@
 				int hash = hasher.nextHash();
 				if (fullpaths.contains(rp, hasher.length(), hash))
 					return true;
-				if (!hasher.hasNext())
-					if (prefixes.contains(rp, hasher.length(), hash))
-						return true;
+				if (!hasher.hasNext() && walker.isSubtree()
+						&& prefixes.contains(rp, hasher.length(), hash))
+					return true;
 			}
 
 			final int cmp = walker.isPathPrefix(max, max.length);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/BlockList.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/BlockList.java
index 0454e7e..9d0ad73 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/BlockList.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/BlockList.java
@@ -74,9 +74,9 @@
 
 	private static final int BLOCK_MASK = BLOCK_SIZE - 1;
 
-	private T[][] directory;
+	T[][] directory;
 
-	private int size;
+	int size;
 
 	private int tailDirIdx;
 
@@ -282,11 +282,11 @@
 		return new MyIterator();
 	}
 
-	private static final int toDirectoryIndex(int index) {
+	static final int toDirectoryIndex(int index) {
 		return index >>> BLOCK_BITS;
 	}
 
-	private static final int toBlockIndex(int index) {
+	static final int toBlockIndex(int index) {
 		return index & BLOCK_MASK;
 	}
 
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/ChangeIdUtil.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/ChangeIdUtil.java
index 35fc99e..e14096e 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/ChangeIdUtil.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/ChangeIdUtil.java
@@ -42,7 +42,6 @@
  */
 package org.eclipse.jgit.util;
 
-import java.io.IOException;
 import java.util.regex.Pattern;
 
 import org.eclipse.jgit.lib.Constants;
@@ -90,12 +89,10 @@
 	 *            The commit message
 	 * @return the change id SHA1 string (without the 'I') or null if the
 	 *         message is not complete enough
-	 * @throws IOException
 	 */
 	public static ObjectId computeChangeId(final ObjectId treeId,
 			final ObjectId firstParentId, final PersonIdent author,
-			final PersonIdent committer, final String message)
-			throws IOException {
+			final PersonIdent committer, final String message) {
 		String cleanMessage = clean(message);
 		if (cleanMessage.length() == 0)
 			return null;
@@ -116,8 +113,7 @@
 		b.append("\n\n"); //$NON-NLS-1$
 		b.append(cleanMessage);
 		try (ObjectInserter f = new ObjectInserter.Formatter()) {
-			return f.idFor(Constants.OBJ_COMMIT, //
-					b.toString().getBytes(Constants.CHARACTER_ENCODING));
+			return f.idFor(Constants.OBJ_COMMIT, Constants.encode(b.toString()));
 		}
 	}
 
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 e5219b2..253acbb 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java
@@ -44,15 +44,13 @@
 package org.eclipse.jgit.util;
 
 import java.io.BufferedReader;
-import java.io.BufferedWriter;
+import java.io.ByteArrayInputStream;
 import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.io.OutputStream;
-import java.io.OutputStreamWriter;
 import java.io.PrintStream;
-import java.io.PrintWriter;
 import java.nio.charset.Charset;
 import java.security.AccessController;
 import java.security.PrivilegedAction;
@@ -60,12 +58,14 @@
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.Map;
+import java.util.Objects;
 import java.util.concurrent.Callable;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
 
+import org.eclipse.jgit.annotations.Nullable;
 import org.eclipse.jgit.api.errors.JGitInternalException;
 import org.eclipse.jgit.internal.JGitText;
 import org.eclipse.jgit.lib.Constants;
@@ -110,6 +110,53 @@
 		}
 	}
 
+	/**
+	 * Result of an executed process. The caller is responsible to close the
+	 * contained {@link TemporaryBuffer}s
+	 *
+	 * @since 4.2
+	 */
+	public static class ExecutionResult {
+		private TemporaryBuffer stdout;
+
+		private TemporaryBuffer stderr;
+
+		private int rc;
+
+		/**
+		 * @param stdout
+		 * @param stderr
+		 * @param rc
+		 */
+		public ExecutionResult(TemporaryBuffer stdout, TemporaryBuffer stderr,
+				int rc) {
+			this.stdout = stdout;
+			this.stderr = stderr;
+			this.rc = rc;
+		}
+
+		/**
+		 * @return buffered standard output stream
+		 */
+		public TemporaryBuffer getStdout() {
+			return stdout;
+		}
+
+		/**
+		 * @return buffered standard error stream
+		 */
+		public TemporaryBuffer getStderr() {
+			return stderr;
+		}
+
+		/**
+		 * @return the return code of the process
+		 */
+		public int getRc() {
+			return rc;
+		}
+	}
+
 	private final static Logger LOG = LoggerFactory.getLogger(FS.class);
 
 	/** The auto-detected implementation selected for this operating system and JRE. */
@@ -404,8 +451,10 @@
 	 *            as component array
 	 * @param encoding
 	 *            to be used to parse the command's output
-	 * @return the one-line output of the command
+	 * @return the one-line output of the command or {@code null} if there is
+	 *         none
 	 */
+	@Nullable
 	protected static String readPipe(File dir, String[] command, String encoding) {
 		return readPipe(dir, command, encoding, null);
 	}
@@ -422,9 +471,11 @@
 	 * @param env
 	 *            Map of environment variables to be merged with those of the
 	 *            current process
-	 * @return the one-line output of the command
+	 * @return the one-line output of the command or {@code null} if there is
+	 *         none
 	 * @since 4.0
 	 */
+	@Nullable
 	protected static String readPipe(File dir, String[] command, String encoding, Map<String, String> env) {
 		final boolean debug = LOG.isDebugEnabled();
 		try {
@@ -438,36 +489,30 @@
 				pb.environment().putAll(env);
 			}
 			Process p = pb.start();
-			BufferedReader lineRead = new BufferedReader(
-					new InputStreamReader(p.getInputStream(), encoding));
 			p.getOutputStream().close();
 			GobblerThread gobbler = new GobblerThread(p, command, dir);
 			gobbler.start();
 			String r = null;
-			try {
+			try (BufferedReader lineRead = new BufferedReader(
+					new InputStreamReader(p.getInputStream(), encoding))) {
 				r = lineRead.readLine();
 				if (debug) {
 					LOG.debug("readpipe may return '" + r + "'"); //$NON-NLS-1$ //$NON-NLS-2$
-					LOG.debug("(ignoring remaing output:"); //$NON-NLS-1$
-				}
-				String l;
-				while ((l = lineRead.readLine()) != null) {
-					if (debug) {
+					LOG.debug("remaining output:\n"); //$NON-NLS-1$
+					String l;
+					while ((l = lineRead.readLine()) != null) {
 						LOG.debug(l);
 					}
 				}
-			} finally {
-				p.getErrorStream().close();
-				lineRead.close();
 			}
 
 			for (;;) {
 				try {
 					int rc = p.waitFor();
 					gobbler.join();
-					if (rc == 0 && r != null && r.length() > 0
-							&& !gobbler.fail.get())
+					if (rc == 0 && !gobbler.fail.get()) {
 						return r;
+					}
 					if (debug) {
 						LOG.debug("readpipe rc=" + rc); //$NON-NLS-1$
 					}
@@ -477,7 +522,7 @@
 				}
 			}
 		} catch (IOException e) {
-			LOG.debug("Caught exception in FS.readPipe()", e); //$NON-NLS-1$
+			LOG.error("Caught exception in FS.readPipe()", e); //$NON-NLS-1$
 		}
 		if (debug) {
 			LOG.debug("readpipe returns null"); //$NON-NLS-1$
@@ -489,52 +534,39 @@
 		private final Process p;
 		private final String desc;
 		private final String dir;
-		private final boolean debug = LOG.isDebugEnabled();
-		private final AtomicBoolean fail = new AtomicBoolean();
+		final AtomicBoolean fail = new AtomicBoolean();
 
-		private GobblerThread(Process p, String[] command, File dir) {
+		GobblerThread(Process p, String[] command, File dir) {
 			this.p = p;
-			if (debug) {
-				this.desc = Arrays.asList(command).toString();
-				this.dir = dir.toString();
-			} else {
-				this.desc = null;
-				this.dir = null;
-			}
+			this.desc = Arrays.toString(command);
+			this.dir = Objects.toString(dir);
 		}
 
 		public void run() {
-			InputStream is = p.getErrorStream();
-			try {
+			StringBuilder err = new StringBuilder();
+			try (InputStream is = p.getErrorStream()) {
 				int ch;
-				if (debug) {
-					while ((ch = is.read()) != -1) {
-						System.err.print((char) ch);
-					}
-				} else {
-					while (is.read() != -1) {
-						// ignore
-					}
+				while ((ch = is.read()) != -1) {
+					err.append((char) ch);
 				}
 			} catch (IOException e) {
-				logError(e);
-				fail.set(true);
-			}
-			try {
-				is.close();
-			} catch (IOException e) {
-				logError(e);
-				fail.set(true);
+				if (p.exitValue() != 0) {
+					logError(e);
+					fail.set(true);
+				} else {
+					// ignore. git terminated faster and stream was just closed
+				}
+			} finally {
+				if (err.length() > 0) {
+					LOG.error(err.toString());
+				}
 			}
 		}
 
 		private void logError(Throwable t) {
-			if (!debug) {
-				return;
-			}
 			String msg = MessageFormat.format(
 					JGitText.get().exceptionCaughtDuringExcecutionOfCommand, desc, dir);
-			LOG.debug(msg, t);
+			LOG.error(msg, t);
 		}
 	}
 
@@ -556,6 +588,14 @@
 			return null;
 		}
 
+		// Bug 480782: Check if the discovered git executable is JGit CLI
+		String 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$
+			return null;
+		}
+
 		// Trick Git into printing the path to the config file by using "echo"
 		// as the editor.
 		Map<String, String> env = new HashMap<>();
@@ -871,52 +911,86 @@
 	 * Runs the given process until termination, clearing its stdout and stderr
 	 * streams on-the-fly.
 	 *
-	 * @param hookProcessBuilder
-	 *            The process builder configured for this hook.
+	 * @param processBuilder
+	 *            The process builder configured for this process.
 	 * @param outRedirect
-	 *            A print stream on which to redirect the hook's stdout. Can be
-	 *            <code>null</code>, in which case the hook's standard output
-	 *            will be lost.
+	 *            A OutputStream on which to redirect the processes stdout. Can
+	 *            be <code>null</code>, in which case the processes standard
+	 *            output will be lost.
 	 * @param errRedirect
-	 *            A print stream on which to redirect the hook's stderr. Can be
-	 *            <code>null</code>, in which case the hook's standard error
-	 *            will be lost.
+	 *            A OutputStream on which to redirect the processes stderr. Can
+	 *            be <code>null</code>, in which case the processes standard
+	 *            error will be lost.
 	 * @param stdinArgs
 	 *            A string to pass on to the standard input of the hook. Can be
 	 *            <code>null</code>.
-	 * @return the exit value of this hook.
+	 * @return the exit value of this process.
 	 * @throws IOException
-	 *             if an I/O error occurs while executing this hook.
+	 *             if an I/O error occurs while executing this process.
 	 * @throws InterruptedException
 	 *             if the current thread is interrupted while waiting for the
 	 *             process to end.
-	 * @since 3.7
+	 * @since 4.2
 	 */
-	protected int runProcess(ProcessBuilder hookProcessBuilder,
+	public int runProcess(ProcessBuilder processBuilder,
 			OutputStream outRedirect, OutputStream errRedirect, String stdinArgs)
 			throws IOException, InterruptedException {
+		InputStream in = (stdinArgs == null) ? null : new ByteArrayInputStream(
+				stdinArgs.getBytes(Constants.CHARACTER_ENCODING));
+		return runProcess(processBuilder, outRedirect, errRedirect, in);
+	}
+
+	/**
+	 * Runs the given process until termination, clearing its stdout and stderr
+	 * streams on-the-fly.
+	 *
+	 * @param processBuilder
+	 *            The process builder configured for this process.
+	 * @param outRedirect
+	 *            An OutputStream on which to redirect the processes stdout. Can
+	 *            be <code>null</code>, in which case the processes standard
+	 *            output will be lost.
+	 * @param errRedirect
+	 *            An OutputStream on which to redirect the processes stderr. Can
+	 *            be <code>null</code>, in which case the processes standard
+	 *            error will be lost.
+	 * @param inRedirect
+	 *            An InputStream from which to redirect the processes stdin. Can
+	 *            be <code>null</code>, in which case the process doesn't get
+	 *            any data over stdin. It is assumed that the whole InputStream
+	 *            will be consumed by the process. The method will close the
+	 *            inputstream after all bytes are read.
+	 * @return the return code of this process.
+	 * @throws IOException
+	 *             if an I/O error occurs while executing this process.
+	 * @throws InterruptedException
+	 *             if the current thread is interrupted while waiting for the
+	 *             process to end.
+	 * @since 4.2
+	 */
+	public int runProcess(ProcessBuilder processBuilder,
+			OutputStream outRedirect, OutputStream errRedirect,
+			InputStream inRedirect) throws IOException,
+			InterruptedException {
 		final ExecutorService executor = Executors.newFixedThreadPool(2);
 		Process process = null;
 		// We'll record the first I/O exception that occurs, but keep on trying
 		// to dispose of our open streams and file handles
 		IOException ioException = null;
 		try {
-			process = hookProcessBuilder.start();
+			process = processBuilder.start();
 			final Callable<Void> errorGobbler = new StreamGobbler(
 					process.getErrorStream(), errRedirect);
 			final Callable<Void> outputGobbler = new StreamGobbler(
 					process.getInputStream(), outRedirect);
 			executor.submit(errorGobbler);
 			executor.submit(outputGobbler);
-			if (stdinArgs != null) {
-				final PrintWriter stdinWriter = new PrintWriter(
-						process.getOutputStream());
-				stdinWriter.print(stdinArgs);
-				stdinWriter.flush();
-				// We are done with this hook's input. Explicitly close its
-				// stdin now to kick off any blocking read the hook might have.
-				stdinWriter.close();
+			OutputStream outputStream = process.getOutputStream();
+			if (inRedirect != null) {
+				new StreamGobbler(inRedirect, outputStream)
+						.call();
 			}
+			outputStream.close();
 			return process.waitFor();
 		} catch (IOException e) {
 			ioException = e;
@@ -935,6 +1009,9 @@
 				// A process doesn't clean its own resources even when destroyed
 				// Explicitly try and close all three streams, preserving the
 				// outer I/O exception if any.
+				if (inRedirect != null) {
+					inRedirect.close();
+				}
 				try {
 					process.getErrorStream().close();
 				} catch (IOException e) {
@@ -975,10 +1052,10 @@
 		pool.shutdown(); // Disable new tasks from being submitted
 		try {
 			// Wait a while for existing tasks to terminate
-			if (!pool.awaitTermination(5, TimeUnit.SECONDS)) {
+			if (!pool.awaitTermination(60, TimeUnit.SECONDS)) {
 				pool.shutdownNow(); // Cancel currently executing tasks
 				// Wait a while for tasks to respond to being canceled
-				if (!pool.awaitTermination(5, TimeUnit.SECONDS))
+				if (!pool.awaitTermination(60, TimeUnit.SECONDS))
 					hasShutdown = false;
 			}
 		} catch (InterruptedException ie) {
@@ -1005,6 +1082,31 @@
 	 */
 	public abstract ProcessBuilder runInShell(String cmd, String[] args);
 
+	/**
+	 * Execute a command defined by a {@link ProcessBuilder}.
+	 *
+	 * @param pb
+	 *            The command to be executed
+	 * @param in
+	 *            The standard input stream passed to the process
+	 * @return The result of the executed command
+	 * @throws InterruptedException
+	 * @throws IOException
+	 * @since 4.2
+	 */
+	public ExecutionResult execute(ProcessBuilder pb, InputStream in)
+			throws IOException, InterruptedException {
+		TemporaryBuffer stdout = new TemporaryBuffer.LocalFile(null);
+		TemporaryBuffer stderr = new TemporaryBuffer.Heap(1024, 1024 * 1024);
+		try {
+			int rc = runProcess(pb, stdout, stderr, in);
+			return new ExecutionResult(stdout, stderr, rc);
+		} finally {
+			stdout.close();
+			stderr.close();
+		}
+	}
+
 	private static class Holder<V> {
 		final V value;
 
@@ -1194,30 +1296,27 @@
 	 * </p>
 	 */
 	private static class StreamGobbler implements Callable<Void> {
-		private final BufferedReader reader;
+		private InputStream in;
 
-		private final BufferedWriter writer;
+		private OutputStream out;
 
 		public StreamGobbler(InputStream stream, OutputStream output) {
-			this.reader = new BufferedReader(new InputStreamReader(stream));
-			if (output == null)
-				this.writer = null;
-			else
-				this.writer = new BufferedWriter(new OutputStreamWriter(output));
+			this.in = stream;
+			this.out = output;
 		}
 
 		public Void call() throws IOException {
 			boolean writeFailure = false;
-
-			String line = null;
-			while ((line = reader.readLine()) != null) {
-				// Do not try to write again after a failure, but keep reading
-				// as long as possible to prevent the input stream from choking.
-				if (!writeFailure && writer != null) {
+			byte buffer[] = new byte[4096];
+			int readBytes;
+			while ((readBytes = in.read(buffer)) != -1) {
+				// Do not try to write again after a failure, but keep
+				// reading as long as possible to prevent the input stream
+				// from choking.
+				if (!writeFailure && out != null) {
 					try {
-						writer.write(line);
-						writer.newLine();
-						writer.flush();
+						out.write(buffer, 0, readBytes);
+						out.flush();
 					} catch (IOException e) {
 						writeFailure = true;
 					}
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 f0a2e72..defe14f 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
@@ -171,7 +171,8 @@
 			createSymLink(linkName, tempFile.getPath());
 			supportSymlinks = Boolean.TRUE;
 			linkName.delete();
-		} catch (IOException | UnsupportedOperationException e) {
+		} catch (IOException | UnsupportedOperationException
+				| InternalError e) {
 			supportSymlinks = Boolean.FALSE;
 		} finally {
 			if (tempFile != null)
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 2450be4..ec581b3 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
@@ -162,17 +162,15 @@
 				errRedirect, stdinArgs);
 	}
 
-	@Override
-	public boolean supportsSymlinks() {
-		return true;
-	}
-
 	/**
 	 * @since 3.7
 	 */
 	@Override
 	public File findHook(Repository repository, String hookName) {
 		final File gitdir = repository.getDirectory();
+		if (gitdir == null) {
+			return null;
+		}
 		final Path hookPath = gitdir.toPath().resolve(Constants.HOOKS)
 				.resolve(hookName);
 		if (Files.isExecutable(hookPath))
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/FileUtils.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/FileUtils.java
index 548d239..aa101f7 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/FileUtils.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/FileUtils.java
@@ -398,21 +398,30 @@
 	 * Create a symbolic link
 	 *
 	 * @param path
+	 *            the path of the symbolic link to create
 	 * @param target
+	 *            the target of the symbolic link
+	 * @return the path to the symbolic link
 	 * @throws IOException
-	 * @since 3.0
+	 * @since 4.2
 	 */
-	public static void createSymLink(File path, String target)
+	public static Path createSymLink(File path, String target)
 			throws IOException {
 		Path nioPath = path.toPath();
 		if (Files.exists(nioPath, LinkOption.NOFOLLOW_LINKS)) {
-			Files.delete(nioPath);
+			BasicFileAttributes attrs = Files.readAttributes(nioPath,
+					BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS);
+			if (attrs.isRegularFile() || attrs.isSymbolicLink()) {
+				delete(path);
+			} else {
+				delete(path, EMPTY_DIRECTORIES_ONLY | RECURSIVE);
+			}
 		}
 		if (SystemReader.getInstance().isWindows()) {
 			target = target.replace('/', '\\');
 		}
 		Path nioTarget = new File(target).toPath();
-		Files.createSymbolicLink(nioPath, nioTarget);
+		return Files.createSymbolicLink(nioPath, nioTarget);
 	}
 
 	/**
@@ -730,4 +739,29 @@
 		}
 		return name;
 	}
+
+	/**
+	 * Best-effort variation of {@link File#getCanonicalFile()} returning the
+	 * input file if the file cannot be canonicalized instead of throwing
+	 * {@link IOException}.
+	 *
+	 * @param file
+	 *            to be canonicalized; may be {@code null}
+	 * @return canonicalized file, or the unchanged input file if
+	 *         canonicalization failed or if {@code file == null}
+	 * @throws SecurityException
+	 *             if {@link File#getCanonicalFile()} throws one
+	 * @since 4.2
+	 */
+	public static File canonicalize(File file) {
+		if (file == null) {
+			return null;
+		}
+		try {
+			return file.getCanonicalFile();
+		} catch (IOException e) {
+			return file;
+		}
+	}
+
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/Paths.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/Paths.java
new file mode 100644
index 0000000..6be7ddb
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/Paths.java
@@ -0,0 +1,188 @@
+/*
+ * 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.util;
+
+import static org.eclipse.jgit.lib.FileMode.TYPE_MASK;
+import static org.eclipse.jgit.lib.FileMode.TYPE_TREE;
+
+/**
+ * Utility functions for paths inside of a Git repository.
+ *
+ * @since 4.2
+ */
+public class Paths {
+	/**
+	 * Remove trailing {@code '/'} if present.
+	 *
+	 * @param path
+	 *            input path to potentially remove trailing {@code '/'} from.
+	 * @return null if {@code path == null}; {@code path} after removing a
+	 *         trailing {@code '/'}.
+	 */
+	public static String stripTrailingSeparator(String path) {
+		if (path == null || path.isEmpty()) {
+			return path;
+		}
+
+		int i = path.length();
+		if (path.charAt(path.length() - 1) != '/') {
+			return path;
+		}
+		do {
+			i--;
+		} while (path.charAt(i - 1) == '/');
+		return path.substring(0, i);
+	}
+
+	/**
+	 * Compare two paths according to Git path sort ordering rules.
+	 *
+	 * @param aPath
+	 *            first path buffer. The range {@code [aPos, aEnd)} is used.
+	 * @param aPos
+	 *            index into {@code aPath} where the first path starts.
+	 * @param aEnd
+	 *            1 past last index of {@code aPath}.
+	 * @param aMode
+	 *            mode of the first file. Trees are sorted as though
+	 *            {@code aPath[aEnd] == '/'}, even if aEnd does not exist.
+	 * @param bPath
+	 *            second path buffer. The range {@code [bPos, bEnd)} is used.
+	 * @param bPos
+	 *            index into {@code bPath} where the second path starts.
+	 * @param bEnd
+	 *            1 past last index of {@code bPath}.
+	 * @param bMode
+	 *            mode of the second file. Trees are sorted as though
+	 *            {@code bPath[bEnd] == '/'}, even if bEnd does not exist.
+	 * @return &lt;0 if {@code aPath} sorts before {@code bPath};
+	 *         0 if the paths are the same;
+	 *         &gt;0 if {@code aPath} sorts after {@code bPath}.
+	 */
+	public static int compare(byte[] aPath, int aPos, int aEnd, int aMode,
+			byte[] bPath, int bPos, int bEnd, int bMode) {
+		int cmp = coreCompare(
+				aPath, aPos, aEnd, aMode,
+				bPath, bPos, bEnd, bMode);
+		if (cmp == 0) {
+			cmp = lastPathChar(aMode) - lastPathChar(bMode);
+		}
+		return cmp;
+	}
+
+	/**
+	 * Compare two paths, checking for identical name.
+	 * <p>
+	 * Unlike {@code compare} this method returns {@code 0} when the paths have
+	 * the same characters in their names, even if the mode differs. It is
+	 * intended for use in validation routines detecting duplicate entries.
+	 * <p>
+	 * Returns {@code 0} if the names are identical and a conflict exists
+	 * between {@code aPath} and {@code bPath}, as they share the same name.
+	 * <p>
+	 * Returns {@code <0} if all possibles occurrences of {@code aPath} sort
+	 * before {@code bPath} and no conflict can happen. In a properly sorted
+	 * tree there are no other occurrences of {@code aPath} and therefore there
+	 * are no duplicate names.
+	 * <p>
+	 * Returns {@code >0} when it is possible for a duplicate occurrence of
+	 * {@code aPath} to appear later, after {@code bPath}. Callers should
+	 * continue to examine candidates for {@code bPath} until the method returns
+	 * one of the other return values.
+	 *
+	 * @param aPath
+	 *            first path buffer. The range {@code [aPos, aEnd)} is used.
+	 * @param aPos
+	 *            index into {@code aPath} where the first path starts.
+	 * @param aEnd
+	 *            1 past last index of {@code aPath}.
+	 * @param bPath
+	 *            second path buffer. The range {@code [bPos, bEnd)} is used.
+	 * @param bPos
+	 *            index into {@code bPath} where the second path starts.
+	 * @param bEnd
+	 *            1 past last index of {@code bPath}.
+	 * @param bMode
+	 *            mode of the second file. Trees are sorted as though
+	 *            {@code bPath[bEnd] == '/'}, even if bEnd does not exist.
+	 * @return &lt;0 if no duplicate name could exist;
+	 *         0 if the paths have the same name;
+	 *         &gt;0 other {@code bPath} should still be checked by caller.
+	 */
+	public static int compareSameName(
+			byte[] aPath, int aPos, int aEnd,
+			byte[] bPath, int bPos, int bEnd, int bMode) {
+		return coreCompare(
+				aPath, aPos, aEnd, TYPE_TREE,
+				bPath, bPos, bEnd, bMode);
+	}
+
+	private static int coreCompare(
+			byte[] aPath, int aPos, int aEnd, int aMode,
+			byte[] bPath, int bPos, int bEnd, int bMode) {
+		while (aPos < aEnd && bPos < bEnd) {
+			int cmp = (aPath[aPos++] & 0xff) - (bPath[bPos++] & 0xff);
+			if (cmp != 0) {
+				return cmp;
+			}
+		}
+		if (aPos < aEnd) {
+			return (aPath[aPos] & 0xff) - lastPathChar(bMode);
+		}
+		if (bPos < bEnd) {
+			return lastPathChar(aMode) - (bPath[bPos] & 0xff);
+		}
+		return 0;
+	}
+
+	private static int lastPathChar(int mode) {
+		if ((mode & TYPE_MASK) == TYPE_TREE) {
+			return '/';
+		}
+		return 0;
+	}
+
+	private Paths() {
+	}
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/RawParseUtils.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/RawParseUtils.java
index 45c339f..f2955f7 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/RawParseUtils.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/RawParseUtils.java
@@ -44,6 +44,8 @@
 
 package org.eclipse.jgit.util;
 
+import static java.nio.charset.StandardCharsets.ISO_8859_1;
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.eclipse.jgit.lib.ObjectChecker.author;
 import static org.eclipse.jgit.lib.ObjectChecker.committer;
 import static org.eclipse.jgit.lib.ObjectChecker.encoding;
@@ -60,6 +62,7 @@
 import java.util.HashMap;
 import java.util.Map;
 
+import org.eclipse.jgit.annotations.Nullable;
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.PersonIdent;
 
@@ -70,7 +73,7 @@
 	 *
 	 * @since 2.2
 	 */
-	public static final Charset UTF8_CHARSET = Charset.forName("UTF-8"); //$NON-NLS-1$
+	public static final Charset UTF8_CHARSET = UTF_8;
 
 	private static final byte[] digits10;
 
@@ -81,8 +84,9 @@
 	private static final Map<String, Charset> encodingAliases;
 
 	static {
-		encodingAliases = new HashMap<String, Charset>();
-		encodingAliases.put("latin-1", Charset.forName("ISO-8859-1")); //$NON-NLS-1$ //$NON-NLS-2$
+		encodingAliases = new HashMap<>();
+		encodingAliases.put("latin-1", ISO_8859_1); //$NON-NLS-1$
+		encodingAliases.put("iso-latin-1", ISO_8859_1); //$NON-NLS-1$
 
 		digits10 = new byte['9' + 1];
 		Arrays.fill(digits10, (byte) -1);
@@ -671,35 +675,60 @@
 	}
 
 	/**
+	 * Parse the "encoding " header as a string.
+	 * <p>
+	 * Locates the "encoding " header (if present) and returns its value.
+	 *
+	 * @param b
+	 *            buffer to scan.
+	 * @return the encoding header as specified in the commit; null if the
+	 *         header was not present and should be assumed.
+	 * @since 4.2
+	 */
+	@Nullable
+	public static String parseEncodingName(final byte[] b) {
+		int enc = encoding(b, 0);
+		if (enc < 0) {
+			return null;
+		}
+		int lf = nextLF(b, enc);
+		return decode(UTF_8, b, enc, lf - 1);
+	}
+
+	/**
 	 * Parse the "encoding " header into a character set reference.
 	 * <p>
 	 * Locates the "encoding " header (if present) by first calling
 	 * {@link #encoding(byte[], int)} and then returns the proper character set
 	 * to apply to this buffer to evaluate its contents as character data.
 	 * <p>
-	 * If no encoding header is present, {@link Constants#CHARSET} is assumed.
+	 * If no encoding header is present {@code UTF-8} is assumed.
 	 *
 	 * @param b
 	 *            buffer to scan.
 	 * @return the Java character set representation. Never null.
+	 * @throws IllegalCharsetNameException
+	 *             if the character set requested by the encoding header is
+	 *             malformed and unsupportable.
+	 * @throws UnsupportedCharsetException
+	 *             if the JRE does not support the character set requested by
+	 *             the encoding header.
 	 */
 	public static Charset parseEncoding(final byte[] b) {
-		final int enc = encoding(b, 0);
-		if (enc < 0)
-			return Constants.CHARSET;
-		final int lf = nextLF(b, enc);
-		String decoded = decode(Constants.CHARSET, b, enc, lf - 1);
+		String enc = parseEncodingName(b);
+		if (enc == null) {
+			return UTF_8;
+		}
+
+		String name = enc.trim();
 		try {
-			return Charset.forName(decoded);
-		} catch (IllegalCharsetNameException badName) {
-			Charset aliased = charsetForAlias(decoded);
-			if (aliased != null)
+			return Charset.forName(name);
+		} catch (IllegalCharsetNameException
+				| UnsupportedCharsetException badName) {
+			Charset aliased = charsetForAlias(name);
+			if (aliased != null) {
 				return aliased;
-			throw badName;
-		} catch (UnsupportedCharsetException badName) {
-			Charset aliased = charsetForAlias(decoded);
-			if (aliased != null)
-				return aliased;
+			}
 			throw badName;
 		}
 	}
@@ -738,7 +767,15 @@
 	 *         parsed.
 	 */
 	public static PersonIdent parsePersonIdent(final byte[] raw, final int nameB) {
-		final Charset cs = parseEncoding(raw);
+		Charset cs;
+		try {
+			cs = parseEncoding(raw);
+		} catch (IllegalCharsetNameException | UnsupportedCharsetException e) {
+			// Assume UTF-8 for person identities, usually this is correct.
+			// If not decode() will fall back to the ISO-8859-1 encoding.
+			cs = UTF_8;
+		}
+
 		final int emailB = nextLF(raw, nameB, '<');
 		final int emailE = nextLF(raw, emailB, '>');
 		if (emailB >= raw.length || raw[emailB] == '\n' ||
@@ -886,7 +923,7 @@
 	 */
 	public static String decode(final byte[] buffer, final int start,
 			final int end) {
-		return decode(Constants.CHARSET, buffer, start, end);
+		return decode(UTF_8, buffer, start, end);
 	}
 
 	/**
@@ -960,23 +997,21 @@
 	public static String decodeNoFallback(final Charset cs,
 			final byte[] buffer, final int start, final int end)
 			throws CharacterCodingException {
-		final ByteBuffer b = ByteBuffer.wrap(buffer, start, end - start);
+		ByteBuffer b = ByteBuffer.wrap(buffer, start, end - start);
 		b.mark();
 
 		// Try our built-in favorite. The assumption here is that
 		// decoding will fail if the data is not actually encoded
 		// using that encoder.
-		//
 		try {
-			return decode(b, Constants.CHARSET);
+			return decode(b, UTF_8);
 		} catch (CharacterCodingException e) {
 			b.reset();
 		}
 
-		if (!cs.equals(Constants.CHARSET)) {
+		if (!cs.equals(UTF_8)) {
 			// Try the suggested encoding, it might be right since it was
 			// provided by the caller.
-			//
 			try {
 				return decode(b, cs);
 			} catch (CharacterCodingException e) {
@@ -986,9 +1021,8 @@
 
 		// Try the default character set. A small group of people
 		// might actually use the same (or very similar) locale.
-		//
-		final Charset defcs = Charset.defaultCharset();
-		if (!defcs.equals(cs) && !defcs.equals(Constants.CHARSET)) {
+		Charset defcs = Charset.defaultCharset();
+		if (!defcs.equals(cs) && !defcs.equals(UTF_8)) {
 			try {
 				return decode(b, defcs);
 			} catch (CharacterCodingException e) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/RefList.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/RefList.java
index 4695111..0853e95 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/RefList.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/RefList.java
@@ -80,9 +80,9 @@
 		return (RefList<T>) EMPTY;
 	}
 
-	private final Ref[] list;
+	final Ref[] list;
 
-	private final int cnt;
+	final int cnt;
 
 	RefList(Ref[] list, int cnt) {
 		this.list = list;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/RefMap.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/RefMap.java
index 5cc7e92..c72727b 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/RefMap.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/RefMap.java
@@ -78,10 +78,10 @@
 	 * All reference names in this map must start with this prefix. If the
 	 * prefix is not the empty string, it must end with a '/'.
 	 */
-	private final String prefix;
+	final String prefix;
 
 	/** Immutable collection of the packed references at construction time. */
-	private RefList<Ref> packed;
+	RefList<Ref> packed;
 
 	/**
 	 * Immutable collection of the loose references at construction time.
@@ -91,7 +91,7 @@
 	 * are typically unresolved, so they only tell us who their target is, but
 	 * not the current value of the target.
 	 */
-	private RefList<Ref> loose;
+	RefList<Ref> loose;
 
 	/**
 	 * Immutable collection of resolved symbolic references.
@@ -101,11 +101,11 @@
 	 * from {@link #loose}. Every entry in this list must be matched by an entry
 	 * in {@code loose}, otherwise it might be omitted by the map.
 	 */
-	private RefList<Ref> resolved;
+	RefList<Ref> resolved;
 
-	private int size;
+	int size;
 
-	private boolean sizeIsValid;
+	boolean sizeIsValid;
 
 	private Set<Entry<String, Ref>> entrySet;
 
@@ -280,7 +280,7 @@
 		return name;
 	}
 
-	private String toMapKey(Ref ref) {
+	String toMapKey(Ref ref) {
 		String name = ref.getName();
 		if (0 < prefix.length())
 			name = name.substring(prefix.length());
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/SystemReader.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/SystemReader.java
index 4795c89..9860ef0 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/SystemReader.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/SystemReader.java
@@ -339,4 +339,19 @@
 	public void checkPath(String path) throws CorruptObjectException {
 		platformChecker.checkPath(path);
 	}
+
+	/**
+	 * Check tree path entry for validity.
+	 * <p>
+	 * Scans a multi-directory path string such as {@code "src/main.c"}.
+	 *
+	 * @param path
+	 *            path string to scan.
+	 * @throws CorruptObjectException
+	 *             path is invalid.
+	 * @since 4.2
+	 */
+	public void checkPath(byte[] path) throws CorruptObjectException {
+		platformChecker.checkPath(path, 0, path.length);
+	}
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/TemporaryBuffer.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/TemporaryBuffer.java
index e2738c0..3cd5929 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/TemporaryBuffer.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/TemporaryBuffer.java
@@ -69,7 +69,7 @@
 	protected static final int DEFAULT_IN_CORE_LIMIT = 1024 * 1024;
 
 	/** Chain of data, if we are still completely in-core; otherwise null. */
-	private ArrayList<Block> blocks;
+	ArrayList<Block> blocks;
 
 	/**
 	 * Maximum number of bytes we will permit storing in memory.
@@ -247,6 +247,37 @@
 	}
 
 	/**
+	 * Convert this buffer's contents into a contiguous byte array. If this size
+	 * of the buffer exceeds the limit only return the first {@code limit} bytes
+	 * <p>
+	 * The buffer is only complete after {@link #close()} has been invoked.
+	 *
+	 * @param limit
+	 *            the maximum number of bytes to be returned
+	 *
+	 * @return the byte array limited to {@code limit} bytes.
+	 * @throws IOException
+	 *             an error occurred reading from a local temporary file
+	 * @throws OutOfMemoryError
+	 *             the buffer cannot fit in memory
+	 *
+	 * @since 4.2
+	 */
+	public byte[] toByteArray(int limit) throws IOException {
+		final long len = Math.min(length(), limit);
+		if (Integer.MAX_VALUE < len)
+			throw new OutOfMemoryError(
+					JGitText.get().lengthExceedsMaximumArraySize);
+		final byte[] out = new byte[(int) len];
+		int outPtr = 0;
+		for (final Block b : blocks) {
+			System.arraycopy(b.buffer, 0, out, outPtr, b.count);
+			outPtr += b.count;
+		}
+		return out;
+	}
+
+	/**
 	 * Send this buffer to an output stream.
 	 * <p>
 	 * This method may only be invoked after {@link #close()} has completed
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/StreamCopyThread.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/StreamCopyThread.java
index 24b8b53..8d39a22 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/StreamCopyThread.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/StreamCopyThread.java
@@ -47,6 +47,7 @@
 import java.io.InputStream;
 import java.io.InterruptedIOException;
 import java.io.OutputStream;
+import java.util.concurrent.atomic.AtomicInteger;
 
 /** Thread to copy from an input stream to an output stream. */
 public class StreamCopyThread extends Thread {
@@ -58,6 +59,8 @@
 
 	private volatile boolean done;
 
+	private final AtomicInteger flushCount = new AtomicInteger(0);
+
 	/**
 	 * Create a thread to copy data from an input stream to an output stream.
 	 *
@@ -82,6 +85,7 @@
 	 * the request.
 	 */
 	public void flush() {
+		flushCount.incrementAndGet();
 		interrupt();
 	}
 
@@ -109,22 +113,30 @@
 	public void run() {
 		try {
 			final byte[] buf = new byte[BUFFER_SIZE];
-			int interruptCounter = 0;
+			int flushCountBeforeRead = 0;
+			boolean readInterrupted = false;
 			for (;;) {
 				try {
-					if (interruptCounter > 0) {
+					if (readInterrupted) {
 						dst.flush();
-						interruptCounter--;
+						readInterrupted = false;
+						if (!flushCount.compareAndSet(flushCountBeforeRead, 0)) {
+							// There was a flush() call since last blocked read.
+							// Set interrupt status, so next blocked read will throw
+							// an InterruptedIOException and we will flush again.
+							interrupt();
+						}
 					}
 
 					if (done)
 						break;
 
+					flushCountBeforeRead = flushCount.get();
 					final int n;
 					try {
 						n = src.read(buf);
 					} catch (InterruptedIOException wakey) {
-						interruptCounter++;
+						readInterrupted = true;
 						continue;
 					}
 					if (n < 0)
@@ -141,7 +153,7 @@
 
 						// set interrupt status, which will be checked
 						// when we block in src.read
-						if (writeInterrupted)
+						if (writeInterrupted || flushCount.get() > 0)
 							interrupt();
 						break;
 					}
diff --git a/pom.xml b/pom.xml
index fa77335..38060d0 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.1.2.201602141800-r</version>
+  <version>4.2.1-SNAPSHOT</version>
 
   <name>JGit - Parent</name>
   <url>${jgit-url}</url>
@@ -192,7 +192,7 @@
     <maven.build.timestamp.format>yyyyMMddHHmm</maven.build.timestamp.format>
     <bundle-manifest>${project.build.directory}/META-INF/MANIFEST.MF</bundle-manifest>
 
-    <jgit-last-release-version>4.0.0.201506090130-r</jgit-last-release-version>
+    <jgit-last-release-version>4.1.0.201509280440-r</jgit-last-release-version>
     <jsch-version>0.1.53</jsch-version>
     <javaewah-version>0.7.9</javaewah-version>
     <junit-version>4.11</junit-version>
@@ -202,7 +202,7 @@
     <osgi-core-version>4.3.1</osgi-core-version>
     <servlet-api-version>3.1.0</servlet-api-version>
     <jetty-version>9.2.13.v20150730</jetty-version>
-    <clirr-version>2.6.1</clirr-version>
+    <japicmp-version>0.5.3</japicmp-version>
     <httpclient-version>4.3.6</httpclient-version>
     <slf4j-version>1.7.2</slf4j-version>
     <log4j-version>1.2.15</log4j-version>
@@ -355,16 +355,6 @@
         </plugin>
 
         <plugin>
-          <groupId>org.codehaus.mojo</groupId>
-          <artifactId>clirr-maven-plugin</artifactId>
-          <version>${clirr-version}</version>
-          <configuration>
-            <comparisonVersion>${jgit-last-release-version}</comparisonVersion>
-            <minSeverity>info</minSeverity>
-          </configuration>
-        </plugin>
-
-        <plugin>
           <groupId>org.eclipse.cbi.maven.plugins</groupId>
           <artifactId>eclipse-jarsigner-plugin</artifactId>
           <version>1.1.2</version>
@@ -529,18 +519,17 @@
       <plugin>
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-jxr-plugin</artifactId>
-      </plugin>
-      <plugin>
-        <groupId>org.codehaus.mojo</groupId>
-        <artifactId>clirr-maven-plugin</artifactId>
+        <version>2.5</version>
       </plugin>
       <plugin>
         <groupId>org.codehaus.mojo</groupId>
         <artifactId>findbugs-maven-plugin</artifactId>
+        <version>3.0.0</version>
       </plugin>
       <plugin>
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-surefire-report-plugin</artifactId>
+        <version>2.18.1</version>
         <configuration>
           <aggregate>true</aggregate>
           <alwaysGenerateSurefireReport>false</alwaysGenerateSurefireReport>
diff --git a/tools/default.defs b/tools/default.defs
new file mode 100644
index 0000000..3481fa1
--- /dev/null
+++ b/tools/default.defs
@@ -0,0 +1,42 @@
+def java_sources(
+    name,
+    srcs,
+    visibility = ['PUBLIC']
+  ):
+  java_library(
+    name = name,
+    resources = srcs,
+    visibility = visibility,
+  )
+
+def maven_jar(
+    name,
+    group,
+    artifact,
+    version,
+    bin_sha1,
+    src_sha1,
+    visibility = ['PUBLIC']):
+  jar_name = '%s__jar' % name
+  src_name = '%s__src' % name
+
+  remote_file(
+    name = jar_name,
+    sha1 = bin_sha1,
+    url = 'mvn:%s:%s:jar:%s' % (group, artifact, version),
+    out = '%s.jar' % jar_name,
+  )
+
+  remote_file(
+    name = src_name,
+    sha1 = src_sha1,
+    url = 'mvn:%s:%s:src:%s' % (group, artifact, version),
+    out = '%s.jar' % src_name,
+  )
+
+  prebuilt_jar(
+    name = name,
+    binary_jar = ':' + jar_name,
+    source_jar = ':' + src_name,
+    visibility = visibility)
+
diff --git a/tools/git.defs b/tools/git.defs
new file mode 100644
index 0000000..557dff2
--- /dev/null
+++ b/tools/git.defs
@@ -0,0 +1,9 @@
+def git_version():
+  import subprocess
+  cmd = ['git', 'describe', '--always', '--match', 'v[0-9].*', '--dirty']
+  p = subprocess.Popen(cmd, stdout = subprocess.PIPE)
+  v = p.communicate()[0].strip()
+  r = p.returncode
+  if r != 0:
+    raise subprocess.CalledProcessError(r, ' '.join(cmd))
+  return v
diff --git a/tools/maven-central/deploy.rb b/tools/maven-central/deploy.rb
index d620a88..8e7adc8 100755
--- a/tools/maven-central/deploy.rb
+++ b/tools/maven-central/deploy.rb
@@ -53,7 +53,6 @@
              group + '.archive',
              group + '.http.apache',
              group + '.http.server',
-             group + '.java7',
              group + '.junit',
              group + '.junit.http',
              group + '.pgm',
diff --git a/tools/maven-central/download.rb b/tools/maven-central/download.rb
index de4ecd4..b7ea215 100755
--- a/tools/maven-central/download.rb
+++ b/tools/maven-central/download.rb
@@ -13,7 +13,6 @@
              group + '.archive',
              group + '.http.apache',
              group + '.http.server',
-             group + '.java7',
              group + '.junit',
              group + '.junit.http',
              group + '.pgm',
diff --git a/tools/version.sh b/tools/version.sh
index a2f8129..81ffe06 100755
--- a/tools/version.sh
+++ b/tools/version.sh
@@ -149,17 +149,6 @@
 		$seen_version = 0;
 		$old_argv = $ARGV;
 	}
-	if ($seen_version < 5) {
-		$seen_version++ if
-		s{<(version)>.*</\1>}{<${1}>'"$POM_V"'</${1}>};
-	}
-	' org.eclipse.jgit.packaging/org.eclipse.jgit.updatesite/pom.xml
-
-perl -pi~ -e '
-	if ($ARGV ne $old_argv) {
-		$seen_version = 0;
-		$old_argv = $ARGV;
-	}
 	if (!$seen_version) {
 		$seen_version = 1 if
 		s{<(version)>.*</\1>}{<${1}>'"$POM_V"'</${1}>};