Merge branch 'master' into stable-4.0

Change-Id: I962461630384b76e7f387f4e1c1248833fbc4673
Signed-off-by: Matthias Sohn <matthias.sohn@sap.com>
diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/ReceivePackServlet.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/ReceivePackServlet.java
index d1c2580..a8e312d 100644
--- a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/ReceivePackServlet.java
+++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/ReceivePackServlet.java
@@ -76,6 +76,7 @@
 
 import org.eclipse.jgit.errors.UnpackException;
 import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.transport.InternalHttpServerGlue;
 import org.eclipse.jgit.transport.ReceivePack;
 import org.eclipse.jgit.transport.RefAdvertiser.PacketLineOutRefAdvertiser;
 import org.eclipse.jgit.transport.resolver.ReceivePackFactory;
@@ -100,6 +101,9 @@ protected void begin(HttpServletRequest req, Repository db)
 				throws IOException, ServiceNotEnabledException,
 				ServiceNotAuthorizedException {
 			ReceivePack rp = receivePackFactory.create(req, db);
+			InternalHttpServerGlue.setPeerUserAgent(
+					rp,
+					req.getHeader(HDR_USER_AGENT));
 			req.setAttribute(ATTRIBUTE_HANDLER, rp);
 		}
 
diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/SmartServiceInfoRefs.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/SmartServiceInfoRefs.java
index 4810753..5133219 100644
--- a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/SmartServiceInfoRefs.java
+++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/SmartServiceInfoRefs.java
@@ -101,7 +101,7 @@ public void doFilter(ServletRequest request, ServletResponse response,
 				res.sendError(SC_UNAUTHORIZED);
 				return;
 			} catch (ServiceNotEnabledException e) {
-				sendError(req, res, SC_FORBIDDEN);
+				sendError(req, res, SC_FORBIDDEN, e.getMessage());
 				return;
 			}
 
diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/UploadPackServlet.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/UploadPackServlet.java
index c5272b5..7aefcbd 100644
--- a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/UploadPackServlet.java
+++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/UploadPackServlet.java
@@ -74,6 +74,7 @@
 import javax.servlet.http.HttpServletResponse;
 
 import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.transport.InternalHttpServerGlue;
 import org.eclipse.jgit.transport.RefAdvertiser.PacketLineOutRefAdvertiser;
 import org.eclipse.jgit.transport.UploadPack;
 import org.eclipse.jgit.transport.UploadPackInternalServerErrorException;
@@ -100,6 +101,9 @@ protected void begin(HttpServletRequest req, Repository db)
 				throws IOException, ServiceNotEnabledException,
 				ServiceNotAuthorizedException {
 			UploadPack up = uploadPackFactory.create(req, db);
+			InternalHttpServerGlue.setPeerUserAgent(
+					up,
+					req.getHeader(HDR_USER_AGENT));
 			req.setAttribute(ATTRIBUTE_HANDLER, up);
 		}
 
diff --git a/org.eclipse.jgit.junit/META-INF/MANIFEST.MF b/org.eclipse.jgit.junit/META-INF/MANIFEST.MF
index b20464e..327a697 100644
--- a/org.eclipse.jgit.junit/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.junit/META-INF/MANIFEST.MF
@@ -14,6 +14,7 @@
  org.eclipse.jgit.internal.storage.file;version="[4.0.0,4.1.0)",
  org.eclipse.jgit.internal.storage.pack;version="[4.0.0,4.1.0)",
  org.eclipse.jgit.lib;version="[4.0.0,4.1.0)",
+ org.eclipse.jgit.merge;version="[4.0.0,4.1.0)",
  org.eclipse.jgit.revwalk;version="[4.0.0,4.1.0)",
  org.eclipse.jgit.storage.file;version="[4.0.0,4.1.0)",
  org.eclipse.jgit.treewalk;version="[4.0.0,4.1.0)",
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 ce33ec7..925a6b0 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
@@ -52,11 +52,13 @@
 import java.io.OutputStream;
 import java.security.MessageDigest;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.Date;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
+import java.util.TimeZone;
 
 import org.eclipse.jgit.api.Git;
 import org.eclipse.jgit.dircache.DirCache;
@@ -88,6 +90,8 @@
 import org.eclipse.jgit.lib.RefWriter;
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.lib.TagBuilder;
+import org.eclipse.jgit.merge.MergeStrategy;
+import org.eclipse.jgit.merge.ThreeWayMerger;
 import org.eclipse.jgit.revwalk.ObjectWalk;
 import org.eclipse.jgit.revwalk.RevBlob;
 import org.eclipse.jgit.revwalk.RevCommit;
@@ -187,6 +191,11 @@ public Date getClock() {
 		return new Date(now);
 	}
 
+	/** @return timezone used for default identities. */
+	public TimeZone getTimeZone() {
+		return defaultCommitter.getTimeZone();
+	}
+
 	/**
 	 * Adjust the current time that will used by the next commit.
 	 *
@@ -616,6 +625,59 @@ public void reset(String name) throws Exception {
 	}
 
 	/**
+	 * Cherry-pick a commit onto HEAD.
+	 * <p>
+	 * This differs from {@code git cherry-pick} in that it works in a bare
+	 * repository. As a result, any merge failure results in an exception, as
+	 * there is no way to recover.
+	 *
+	 * @param id
+	 *            commit-ish to cherry-pick.
+	 * @return newly created commit, or null if no work was done due to the
+	 *         resulting tree being identical.
+	 * @throws Exception
+	 */
+	public RevCommit cherryPick(AnyObjectId id) throws Exception {
+		RevCommit commit = pool.parseCommit(id);
+		pool.parseBody(commit);
+		if (commit.getParentCount() != 1)
+			throw new IOException(String.format(
+					"Expected 1 parent for %s, found: %s",
+					id.name(), Arrays.asList(commit.getParents())));
+		RevCommit parent = commit.getParent(0);
+		pool.parseHeaders(parent);
+
+		Ref headRef = db.getRef(Constants.HEAD);
+		if (headRef == null)
+			throw new IOException("Missing HEAD");
+		RevCommit head = pool.parseCommit(headRef.getObjectId());
+
+		ThreeWayMerger merger = MergeStrategy.RECURSIVE.newMerger(db, true);
+		merger.setBase(parent.getTree());
+		if (merger.merge(head, commit)) {
+			if (AnyObjectId.equals(head.getTree(), merger.getResultTreeId()))
+				return null;
+			tick(1);
+			org.eclipse.jgit.lib.CommitBuilder b =
+					new org.eclipse.jgit.lib.CommitBuilder();
+			b.setParentId(head);
+			b.setTreeId(merger.getResultTreeId());
+			b.setAuthor(commit.getAuthorIdent());
+			b.setCommitter(new PersonIdent(defaultCommitter, new Date(now)));
+			b.setMessage(commit.getFullMessage());
+			ObjectId result;
+			try (ObjectInserter ins = inserter) {
+				result = ins.insert(b);
+				ins.flush();
+			}
+			update(Constants.HEAD, result);
+			return pool.parseCommit(result);
+		} else {
+			throw new IOException("Merge conflict");
+		}
+	}
+
+	/**
 	 * Update the dumb client server info files.
 	 *
 	 * @throws Exception
diff --git a/org.eclipse.jgit.packaging/pom.xml b/org.eclipse.jgit.packaging/pom.xml
index 3f552b6..5c6d51a 100644
--- a/org.eclipse.jgit.packaging/pom.xml
+++ b/org.eclipse.jgit.packaging/pom.xml
@@ -68,6 +68,10 @@
       <id>repo.eclipse.org.cbi-releases</id>
       <url>https://repo.eclipse.org/content/repositories/cbi-releases/</url>
     </pluginRepository>
+    <pluginRepository>
+      <id>repo.eclipse.org.cbi-snapshots</id>
+      <url>https://repo.eclipse.org/content/repositories/cbi-snapshots/</url>
+    </pluginRepository>
   </pluginRepositories>
 
   <modules>
@@ -210,7 +214,7 @@
         <plugin>
           <groupId>org.eclipse.cbi.maven.plugins</groupId>
           <artifactId>eclipse-jarsigner-plugin</artifactId>
-          <version>1.1.1</version>
+          <version>1.1.2-SNAPSHOT</version>
         </plugin>
         <plugin>
           <groupId>org.codehaus.mojo</groupId>
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 38c3a2f..7151de7 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
@@ -47,14 +47,15 @@
 import java.io.File;
 import java.io.IOException;
 import java.io.PrintWriter;
+import java.lang.reflect.InvocationTargetException;
 import java.net.MalformedURLException;
 import java.net.URL;
 import java.text.MessageFormat;
 import java.util.ArrayList;
 import java.util.List;
 
-import org.eclipse.jgit.console.ConsoleAuthenticator;
-import org.eclipse.jgit.console.ConsoleCredentialsProvider;
+import org.eclipse.jgit.awtui.AwtAuthenticator;
+import org.eclipse.jgit.awtui.AwtCredentialsProvider;
 import org.eclipse.jgit.errors.TransportException;
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.lib.RepositoryBuilder;
@@ -115,8 +116,10 @@ public static void main(final String[] argv) {
 	 */
 	protected void run(final String[] argv) {
 		try {
-			ConsoleAuthenticator.install();
-			ConsoleCredentialsProvider.install();
+			if (!installConsole()) {
+				AwtAuthenticator.install();
+				AwtCredentialsProvider.install();
+			}
 			configureHttpProxy();
 			execute(argv);
 		} catch (Die err) {
@@ -246,6 +249,45 @@ protected Repository openGitDir(String aGitdir) throws IOException {
 		return rb.build();
 	}
 
+	private static boolean installConsole() {
+		try {
+			install("org.eclipse.jgit.console.ConsoleAuthenticator"); //$NON-NLS-1$
+			install("org.eclipse.jgit.console.ConsoleCredentialsProvider"); //$NON-NLS-1$
+			return true;
+		} catch (ClassNotFoundException e) {
+			return false;
+		} catch (NoClassDefFoundError e) {
+			return false;
+		} catch (UnsupportedClassVersionError e) {
+			return false;
+
+		} catch (IllegalArgumentException e) {
+			throw new RuntimeException(CLIText.get().cannotSetupConsole, e);
+		} catch (SecurityException e) {
+			throw new RuntimeException(CLIText.get().cannotSetupConsole, e);
+		} catch (IllegalAccessException e) {
+			throw new RuntimeException(CLIText.get().cannotSetupConsole, e);
+		} catch (InvocationTargetException e) {
+			throw new RuntimeException(CLIText.get().cannotSetupConsole, e);
+		} catch (NoSuchMethodException e) {
+			throw new RuntimeException(CLIText.get().cannotSetupConsole, e);
+		}
+	}
+
+	private static void install(final String name)
+			throws IllegalAccessException, InvocationTargetException,
+			NoSuchMethodException, ClassNotFoundException {
+		try {
+		Class.forName(name).getMethod("install").invoke(null); //$NON-NLS-1$
+		} catch (InvocationTargetException e) {
+			if (e.getCause() instanceof RuntimeException)
+				throw (RuntimeException) e.getCause();
+			if (e.getCause() instanceof Error)
+				throw (Error) e.getCause();
+			throw e;
+		}
+	}
+
 	/**
 	 * Configure the JRE's standard HTTP based on <code>http_proxy</code>.
 	 * <p>
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 25534fd..9ad845b 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
@@ -180,7 +180,7 @@ public void testPullConflict() throws Exception {
 				+ remoteUri
 				+ "\nSource change\n=======\nTarget change\n>>>>>>> 42453fd Target change in local\n";
 		assertFileContentsEqual(targetFile, result);
-		assertEquals(RepositoryState.REBASING_INTERACTIVE, target
+		assertEquals(RepositoryState.REBASING_MERGE, target
 				.getRepository().getRepositoryState());
 	}
 
@@ -225,7 +225,7 @@ public void testPullLocalConflict() throws Exception {
 		String result = "<<<<<<< Upstream, based on branch 'master' of local repository\n"
 				+ "Master change\n=======\nSlave change\n>>>>>>> 4049c9e Source change in based on master\n";
 		assertFileContentsEqual(targetFile, result);
-		assertEquals(RepositoryState.REBASING_INTERACTIVE, target
+		assertEquals(RepositoryState.REBASING_MERGE, target
 				.getRepository().getRepositoryState());
 	}
 
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 8e64776..6b641c4 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
@@ -824,7 +824,7 @@ public void testStopOnConflict() throws Exception {
 				"<<<<<<< Upstream, based on master\n1master\n=======\n1topic",
 				">>>>>>> e0d1dea change file1 in topic\n2\n3\ntopic4");
 
-		assertEquals(RepositoryState.REBASING_INTERACTIVE, db
+		assertEquals(RepositoryState.REBASING_MERGE, db
 				.getRepositoryState());
 		assertTrue(new File(db.getDirectory(), "rebase-merge").exists());
 		// the first one should be included, so we should have left two picks in
@@ -887,7 +887,7 @@ public void testStopOnConflictAndAbortWithDetachedHEAD() throws Exception {
 				"<<<<<<< Upstream, based on master\n1master\n=======\n1topic",
 				">>>>>>> e0d1dea change file1 in topic\n2\n3\ntopic4");
 
-		assertEquals(RepositoryState.REBASING_INTERACTIVE,
+		assertEquals(RepositoryState.REBASING_MERGE,
 				db.getRepositoryState());
 		assertTrue(new File(db.getDirectory(), "rebase-merge").exists());
 		// the first one should be included, so we should have left two picks in
@@ -1009,7 +1009,7 @@ public void testStopOnConflictAndContinueWithNoDeltaToMaster()
 		res = git.rebase().setOperation(Operation.CONTINUE).call();
 		assertNotNull(res);
 		assertEquals(Status.NOTHING_TO_COMMIT, res.getStatus());
-		assertEquals(RepositoryState.REBASING_INTERACTIVE,
+		assertEquals(RepositoryState.REBASING_MERGE,
 				db.getRepositoryState());
 
 		git.rebase().setOperation(Operation.SKIP).call();
@@ -1300,7 +1300,7 @@ public void testStopOnConflictCommitAndContinue() throws Exception {
 		// user can decide what to do. if he accidentally committed, reset soft,
 		// and continue, if he really has nothing to commit, skip.
 		assertEquals(Status.NOTHING_TO_COMMIT, res.getStatus());
-		assertEquals(RepositoryState.REBASING_INTERACTIVE,
+		assertEquals(RepositoryState.REBASING_MERGE,
 				db.getRepositoryState());
 
 		git.rebase().setOperation(Operation.SKIP).call();
@@ -1401,7 +1401,7 @@ public void testStopOnConflictFileCreationAndDeletion() throws Exception {
 		assertEquals(Status.STOPPED, res.getStatus());
 		assertEquals(conflicting, res.getCurrentCommit());
 
-		assertEquals(RepositoryState.REBASING_INTERACTIVE, db
+		assertEquals(RepositoryState.REBASING_MERGE, db
 				.getRepositoryState());
 		assertTrue(new File(db.getDirectory(), "rebase-merge").exists());
 		// the first one should be included, so we should have left two picks in
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 cefc779..fbb9eec 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
@@ -46,6 +46,7 @@
 import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertTrue;
@@ -55,9 +56,9 @@
 
 import org.eclipse.jgit.internal.storage.dfs.DfsRepositoryDescription;
 import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
-import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.AnyObjectId;
 import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.ObjectLoader;
 import org.eclipse.jgit.lib.PersonIdent;
 import org.eclipse.jgit.lib.Ref;
@@ -296,6 +297,83 @@ public void commitToUnbornHead() throws Exception {
 		assertEquals("refs/heads/master", ref.getTarget().getName());
 	}
 
+	@Test
+	public void cherryPick() throws Exception {
+		repo.updateRef("HEAD").link("refs/heads/master");
+		RevCommit head = tr.branch("master").commit()
+				.add("foo", "foo contents\n")
+				.create();
+		rw.parseBody(head);
+		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()))
+				.author(new PersonIdent("Cherrypick Committer", "cpc@example.com",
+						tr.getClock(), 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");
+		assertEquals(result, headRef.getObjectId());
+		assertTrue(headRef.isSymbolic());
+		assertEquals("refs/heads/master", headRef.getLeaf().getName());
+
+		assertEquals(1, result.getParentCount());
+		assertEquals(head, result.getParent(0));
+		assertEquals(toPick.getAuthorIdent(), result.getAuthorIdent());
+
+		// Committer name/email matches default, and time was incremented.
+		assertEquals(new PersonIdent(head.getCommitterIdent(), new Date(0)),
+				new PersonIdent(result.getCommitterIdent(), new Date(0)));
+		assertTrue(toPick.getCommitTime() < result.getCommitTime());
+
+		assertEquals("message to cherry-pick", result.getFullMessage());
+		assertEquals("foo contents\n", blobAsString(result, "foo"));
+		assertEquals("bar contents\n", blobAsString(result, "bar"));
+	}
+
+	@Test
+	public void cherryPickWithContentMerge() throws Exception {
+		RevCommit base = tr.branch("HEAD").commit()
+				.add("foo", "foo contents\n\n")
+				.create();
+		tr.branch("HEAD").commit()
+				.add("foo", "foo contents\n\nlast line\n")
+				.create();
+		RevCommit toPick = tr.commit()
+				.message("message to cherry-pick")
+				.parent(base)
+				.add("foo", "changed foo contents\n\n")
+				.create();
+		RevCommit result = tr.cherryPick(toPick);
+		rw.parseBody(result);
+
+		assertEquals("message to cherry-pick", result.getFullMessage());
+		assertEquals("changed foo contents\n\nlast line\n",
+				blobAsString(result, "foo"));
+	}
+
+	@Test
+	public void cherryPickWithIdenticalContents() throws Exception {
+		RevCommit base = tr.branch("HEAD").commit().add("foo", "foo contents\n")
+				.create();
+		RevCommit head = tr.branch("HEAD").commit()
+				.parent(base)
+				.add("bar", "bar contents\n")
+				.create();
+		RevCommit toPick = tr.commit()
+				.parent(base)
+				.message("message to cherry-pick")
+				.add("bar", "bar contents\n")
+				.create();
+		assertNotEquals(head, toPick);
+		assertNull(tr.cherryPick(toPick));
+		assertEquals(head, repo.getRef("HEAD").getObjectId());
+	}
+
 	private String blobAsString(AnyObjectId treeish, String path)
 			throws Exception {
 		RevObject obj = tr.get(rw.parseTree(treeish), path);
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ObjectCheckerTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ObjectCheckerTest.java
index c6578cc..274757d 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
@@ -121,6 +121,42 @@ public void testValidCommitBlankAuthor() throws CorruptObjectException {
 	}
 
 	@Test
+	public void testCommitCorruptAuthor() throws CorruptObjectException {
+		StringBuilder b = new StringBuilder();
+		b.append("tree be9bfa841874ccc9f2ef7c48d0c76226f89b7189\n");
+		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());
+		}
+		checker.setAllowInvalidPersonIdent(true);
+		checker.checkCommit(data);
+	}
+
+	@Test
+	public void testCommitCorruptCommitter() throws CorruptObjectException {
+		StringBuilder b = new StringBuilder();
+		b.append("tree be9bfa841874ccc9f2ef7c48d0c76226f89b7189\n");
+		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());
+		}
+		checker.setAllowInvalidPersonIdent(true);
+		checker.checkCommit(data);
+	}
+
+	@Test
 	public void testValidCommit1Parent() throws CorruptObjectException {
 		final StringBuilder b = new StringBuilder();
 
@@ -940,7 +976,8 @@ public void testValidTagHasNoTaggerHeader() throws CorruptObjectException {
 	}
 
 	@Test
-	public void testInvalidTagInvalidTaggerHeader1() {
+	public void testInvalidTagInvalidTaggerHeader1()
+			throws CorruptObjectException {
 		final StringBuilder b = new StringBuilder();
 
 		b.append("object ");
@@ -958,6 +995,8 @@ public void testInvalidTagInvalidTaggerHeader1() {
 		} catch (CorruptObjectException e) {
 			assertEquals("invalid tagger", e.getMessage());
 		}
+		checker.setAllowInvalidPersonIdent(true);
+		checker.checkTag(data);
 	}
 
 	@Test
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/MergeAlgorithmTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/MergeAlgorithmTest.java
index 8a33425..d4a3d62 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/MergeAlgorithmTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/MergeAlgorithmTest.java
@@ -51,11 +51,25 @@
 import org.eclipse.jgit.diff.RawText;
 import org.eclipse.jgit.diff.RawTextComparator;
 import org.eclipse.jgit.lib.Constants;
+import org.junit.Assume;
 import org.junit.Test;
+import org.junit.experimental.theories.DataPoints;
+import org.junit.experimental.theories.Theories;
+import org.junit.runner.RunWith;
 
+@RunWith(Theories.class)
 public class MergeAlgorithmTest {
 	MergeFormatter fmt=new MergeFormatter();
 
+	private final boolean newlineAtEnd;
+
+	@DataPoints
+	public static boolean[] newlineAtEndDataPoints = { false, true };
+
+	public MergeAlgorithmTest(boolean newlineAtEnd) {
+		this.newlineAtEnd = newlineAtEnd;
+	}
+
 	/**
 	 * Check for a conflict where the second text was changed similar to the
 	 * first one, but the second texts modification covers one more line.
@@ -174,28 +188,55 @@ public void testAdjacentModifications() throws IOException {
 	}
 
 	@Test
-	public void testSeperateModifications() throws IOException {
+	public void testSeparateModifications() throws IOException {
 		assertEquals(t("aZcYe"), merge("abcde", "aZcde", "abcYe"));
 	}
 
+	@Test
+	public void testBlankLines() throws IOException {
+		assertEquals(t("aZc\nYe"), merge("abc\nde", "aZc\nde", "abc\nYe"));
+	}
+
 	/**
 	 * Test merging two contents which do one similar modification and one
-	 * insertion is only done by one side. Between modification and insertion is
-	 * a block which is common between the two contents and the common base
+	 * insertion is only done by one side, in the middle. Between modification
+	 * and insertion is a block which is common between the two contents and the
+	 * common base
 	 *
 	 * @throws IOException
 	 */
 	@Test
 	public void testTwoSimilarModsAndOneInsert() throws IOException {
-		assertEquals(t("IAAJ"), merge("iA", "IA", "IAAJ"));
 		assertEquals(t("aBcDde"), merge("abcde", "aBcde", "aBcDde"));
-		assertEquals(t("IAJ"), merge("iA", "IA", "IAJ"));
-		assertEquals(t("IAAAJ"), merge("iA", "IA", "IAAAJ"));
 		assertEquals(t("IAAAJCAB"), merge("iACAB", "IACAB", "IAAAJCAB"));
 		assertEquals(t("HIAAAJCAB"), merge("HiACAB", "HIACAB", "HIAAAJCAB"));
 		assertEquals(t("AGADEFHIAAAJCAB"),
 				merge("AGADEFHiACAB", "AGADEFHIACAB", "AGADEFHIAAAJCAB"));
+	}
 
+	/**
+	 * Test merging two contents which do one similar modification and one
+	 * insertion is only done by one side, at the end. Between modification and
+	 * insertion is a block which is common between the two contents and the
+	 * common base
+	 *
+	 * @throws IOException
+	 */
+	@Test
+	public void testTwoSimilarModsAndOneInsertAtEnd() throws IOException {
+		Assume.assumeTrue(newlineAtEnd);
+		assertEquals(t("IAAJ"), merge("iA", "IA", "IAAJ"));
+		assertEquals(t("IAJ"), merge("iA", "IA", "IAJ"));
+		assertEquals(t("IAAAJ"), merge("iA", "IA", "IAAAJ"));
+	}
+
+	@Test
+	public void testTwoSimilarModsAndOneInsertAtEndNoNewlineAtEnd()
+			throws IOException {
+		Assume.assumeFalse(newlineAtEnd);
+		assertEquals(t("I<A=AAJ>"), merge("iA", "IA", "IAAJ"));
+		assertEquals(t("I<A=AJ>"), merge("iA", "IA", "IAJ"));
+		assertEquals(t("I<A=AAAJ>"), merge("iA", "IA", "IAAAJ"));
 	}
 
 	/**
@@ -225,7 +266,7 @@ private String merge(String commonBase, String ours, String theirs) throws IOExc
 		return new String(bo.toByteArray(), Constants.CHARACTER_ENCODING);
 	}
 
-	public static String t(String text) {
+	public String t(String text) {
 		StringBuilder r = new StringBuilder();
 		for (int i = 0; i < text.length(); i++) {
 			char c = text.charAt(i);
@@ -241,13 +282,14 @@ public static String t(String text) {
 				break;
 			default:
 				r.append(c);
-				r.append('\n');
+				if (newlineAtEnd || i < text.length() - 1)
+					r.append('\n');
 			}
 		}
 		return r.toString();
 	}
 
-	public static RawText T(String text) {
+	public RawText T(String text) {
 		return new RawText(Constants.encode(t(text)));
 	}
 }
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 37cc88b..19e495b 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
@@ -177,6 +177,69 @@ public void crissCrossMerge(MergeStrategy strategy, IndexState indexState,
 
 	@Theory
 	/**
+	 * Merging m2,s2 from the following topology. m1 and s1 are the two root
+	 * commits of the repo. In master and side different files are touched.
+	 * No need to do a real content merge.
+	 *
+	 * <pre>
+	 * m1--m2
+	 *   \/
+	 *   /\
+	 * s1--s2
+	 * </pre>
+	 */
+	public void crissCrossMerge_twoRoots(MergeStrategy strategy,
+			IndexState indexState, WorktreeState worktreeState)
+			throws Exception {
+		if (!validateStates(indexState, worktreeState))
+			return;
+		// fill the repo
+		BranchBuilder master = db_t.branch("master");
+		BranchBuilder side = db_t.branch("side");
+		RevCommit m1 = master.commit().add("m", "m1").message("m1").create();
+		db_t.getRevWalk().parseCommit(m1);
+
+		RevCommit s1 = side.commit().add("s", "s1").message("s1").create();
+		RevCommit s2 = side.commit().parent(m1).add("m", "m1")
+				.message("s2(merge)").create();
+		RevCommit m2 = master.commit().parent(s1).add("s", "s1")
+				.message("m2(merge)").create();
+
+		Git git = Git.wrap(db);
+		git.checkout().setName("master").call();
+		modifyWorktree(worktreeState, "m", "side");
+		modifyWorktree(worktreeState, "s", "side");
+		modifyIndex(indexState, "m", "side");
+		modifyIndex(indexState, "s", "side");
+
+		ResolveMerger merger = (ResolveMerger) strategy.newMerger(db,
+				worktreeState == WorktreeState.Bare);
+		if (worktreeState != WorktreeState.Bare)
+			merger.setWorkingTreeIterator(new FileTreeIterator(db));
+		try {
+			boolean expectSuccess = true;
+			if (!(indexState == IndexState.Bare
+					|| indexState == IndexState.Missing
+					|| indexState == IndexState.SameAsHead || indexState == IndexState.SameAsOther))
+				// index is dirty
+				expectSuccess = false;
+
+			assertEquals(Boolean.valueOf(expectSuccess),
+					Boolean.valueOf(merger.merge(new RevCommit[] { m2, s2 })));
+			assertEquals(MergeStrategy.RECURSIVE, strategy);
+			assertEquals("m1",
+					contentAsString(db, merger.getResultTreeId(), "m"));
+			assertEquals("s1",
+					contentAsString(db, merger.getResultTreeId(), "s"));
+		} catch (NoMergeBaseException e) {
+			assertEquals(MergeStrategy.RESOLVE, strategy);
+			assertEquals(e.getReason(),
+					MergeBaseFailureReason.MULTIPLE_MERGE_BASES_NOT_SUPPORTED);
+		}
+	}
+
+	@Theory
+	/**
 	 * Merging m2,s2 from the following topology. The same file is modified
 	 * in both branches. The modifications should be mergeable. m2 and s2
 	 * contain branch specific conflict resolutions. Therefore m2 and s2 don't contain the same content.
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 dd06168..478a93b 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
@@ -184,7 +184,7 @@ public void checkMergeMergeableTreesWithoutIndex(MergeStrategy strategy)
 		MergeResult mergeRes = git.merge().setStrategy(strategy)
 				.include(masterCommit).call();
 		assertEquals(MergeStatus.MERGED, mergeRes.getMergeStatus());
-		assertEquals("[d/1, mode:100644, content:1master\n2\n3side\n]",
+		assertEquals("[d/1, mode:100644, content:1master\n2\n3side]",
 				indexState(CONTENT));
 	}
 
@@ -561,7 +561,7 @@ public void checkMergeCrissCross(MergeStrategy strategy) throws Exception {
 			assertEquals(MergeStrategy.RECURSIVE, strategy);
 			assertEquals(MergeResult.MergeStatus.MERGED,
 					mergeResult.getMergeStatus());
-			assertEquals("1master2\n2\n3side2\n", read("1"));
+			assertEquals("1master2\n2\n3side2", read("1"));
 		} catch (JGitInternalException e) {
 			assertEquals(MergeStrategy.RESOLVE, strategy);
 			assertTrue(e.getCause() instanceof NoMergeBaseException);
@@ -697,7 +697,7 @@ public void checkForCorrectIndex(MergeStrategy strategy) throws Exception {
 		assertEquals(
 				"[0, mode:100644, content:master]" //
 						+ "[1, mode:100644, content:side]" //
-						+ "[2, mode:100644, content:1master\n2\n3side\n]" //
+						+ "[2, mode:100644, content:1master\n2\n3side]" //
 						+ "[3, mode:100644, stage:1, content:orig][3, mode:100644, stage:2, content:side][3, mode:100644, stage:3, content:master]" //
 						+ "[4, mode:100644, content:orig]", //
 				indexState(CONTENT));
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 9cbb1c8..dfde7fc 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
@@ -215,21 +215,6 @@ public void testCull() throws Exception {
 	}
 
 	@Test
-	public void testMarkUninterestingPropagation() throws Exception {
-		final RevBlob f = blob("1");
-		final RevTree t = tree(file("f", f));
-		final RevCommit c1 = commit(t);
-		final RevCommit c2 = commit(t);
-
-		markUninteresting(c1);
-		markStart(c2);
-
-		assertSame(c2, objw.next());
-		assertNull(objw.next());
-		assertNull(objw.nextObject());
-	}
-
-	@Test
 	public void testEmptyTreeCorruption() throws Exception {
 		ObjectId bId = ObjectId
 				.fromString("abbbfafe3129f85747aba7bfac992af77134c607");
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
new file mode 100644
index 0000000..fd26bfa
--- /dev/null
+++ b/org.eclipse.jgit.ui/src/org/eclipse/jgit/awtui/AwtCredentialsProvider.java
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) 2010, Google Inc.
+ * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * 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.awtui;
+
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Insets;
+
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JPasswordField;
+import javax.swing.JTextField;
+
+import org.eclipse.jgit.errors.UnsupportedCredentialItem;
+import org.eclipse.jgit.transport.CredentialItem;
+import org.eclipse.jgit.transport.CredentialsProvider;
+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());
+	}
+
+	@Override
+	public boolean isInteractive() {
+		return true;
+	}
+
+	@Override
+	public boolean supports(CredentialItem... items) {
+		for (CredentialItem i : items) {
+			if (i instanceof CredentialItem.StringType)
+				continue;
+
+			else if (i instanceof CredentialItem.CharArrayType)
+				continue;
+
+			else if (i instanceof CredentialItem.YesNoType)
+				continue;
+
+			else if (i instanceof CredentialItem.InformationalMessage)
+				continue;
+
+			else
+				return false;
+		}
+		return true;
+	}
+
+	@Override
+	public boolean get(URIish uri, CredentialItem... items)
+			throws UnsupportedCredentialItem {
+		if (items.length == 0) {
+			return true;
+
+		} else if (items.length == 1) {
+			final CredentialItem item = items[0];
+
+			if (item instanceof CredentialItem.InformationalMessage) {
+				JOptionPane.showMessageDialog(null, item.getPromptText(),
+						UIText.get().warning, JOptionPane.INFORMATION_MESSAGE);
+				return true;
+
+			} else if (item instanceof CredentialItem.YesNoType) {
+				CredentialItem.YesNoType v = (CredentialItem.YesNoType) item;
+				int r = JOptionPane.showConfirmDialog(null, v.getPromptText(),
+						UIText.get().warning, JOptionPane.YES_NO_OPTION);
+				switch (r) {
+				case JOptionPane.YES_OPTION:
+					v.setValue(true);
+					return true;
+
+				case JOptionPane.NO_OPTION:
+					v.setValue(false);
+					return true;
+
+				case JOptionPane.CANCEL_OPTION:
+				case JOptionPane.CLOSED_OPTION:
+				default:
+					return false;
+				}
+
+			} else {
+				return interactive(uri, items);
+			}
+
+		} else {
+			return interactive(uri, items);
+		}
+	}
+
+	private static boolean interactive(URIish uri, CredentialItem[] items) {
+		final GridBagConstraints gbc = new GridBagConstraints(0, 0, 1, 1, 1, 1,
+				GridBagConstraints.NORTHWEST, GridBagConstraints.NONE,
+				new Insets(0, 0, 0, 0), 0, 0);
+		final JPanel panel = new JPanel();
+		panel.setLayout(new GridBagLayout());
+
+		final JTextField[] texts = new JTextField[items.length];
+		for (int i = 0; i < items.length; i++) {
+			CredentialItem item = items[i];
+
+			if (item instanceof CredentialItem.StringType
+					|| item instanceof CredentialItem.CharArrayType) {
+				gbc.fill = GridBagConstraints.NONE;
+				gbc.gridwidth = GridBagConstraints.RELATIVE;
+				gbc.gridx = 0;
+				panel.add(new JLabel(item.getPromptText()), gbc);
+
+				gbc.fill = GridBagConstraints.HORIZONTAL;
+				gbc.gridwidth = GridBagConstraints.RELATIVE;
+				gbc.gridx = 1;
+				if (item.isValueSecure())
+					texts[i] = new JPasswordField(20);
+				else
+					texts[i] = new JTextField(20);
+				panel.add(texts[i], gbc);
+				gbc.gridy++;
+
+			} else if (item instanceof CredentialItem.InformationalMessage) {
+				gbc.fill = GridBagConstraints.NONE;
+				gbc.gridwidth = GridBagConstraints.REMAINDER;
+				gbc.gridx = 0;
+				panel.add(new JLabel(item.getPromptText()), gbc);
+				gbc.gridy++;
+
+			} else {
+				throw new UnsupportedCredentialItem(uri, item.getPromptText());
+			}
+		}
+
+		if (JOptionPane.showConfirmDialog(null, panel,
+				UIText.get().authenticationRequired,
+				JOptionPane.OK_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE) != JOptionPane.OK_OPTION)
+			return false; // cancel
+
+		for (int i = 0; i < items.length; i++) {
+			CredentialItem item = items[i];
+			JTextField f = texts[i];
+
+			if (item instanceof CredentialItem.StringType) {
+				CredentialItem.StringType v = (CredentialItem.StringType) item;
+				if (f instanceof JPasswordField)
+					v.setValue(new String(((JPasswordField) f).getPassword()));
+				else
+					v.setValue(f.getText());
+
+			} else if (item instanceof CredentialItem.CharArrayType) {
+				CredentialItem.CharArrayType v = (CredentialItem.CharArrayType) item;
+				if (f instanceof JPasswordField)
+					v.setValueNoCopy(((JPasswordField) f).getPassword());
+				else
+					v.setValueNoCopy(f.getText().toCharArray());
+			}
+		}
+		return true;
+	}
+}
diff --git a/org.eclipse.jgit/.settings/.api_filters b/org.eclipse.jgit/.settings/.api_filters
index 3dfa7b4..124435a 100644
--- a/org.eclipse.jgit/.settings/.api_filters
+++ b/org.eclipse.jgit/.settings/.api_filters
@@ -1,50 +1,5 @@
 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
 <component id="org.eclipse.jgit" version="2">
-    <resource path="META-INF/MANIFEST.MF">
-        <filter comment="minor addition" id="924844039">
-            <message_arguments>
-                <message_argument value="3.4.0"/>
-                <message_argument value="3.4.0"/>
-            </message_arguments>
-        </filter>
-    </resource>
-    <resource path="src/org/eclipse/jgit/lib/ObjectInserter.java" type="org.eclipse.jgit.lib.ObjectInserter">
-        <filter id="336695337">
-            <message_arguments>
-                <message_argument value="org.eclipse.jgit.lib.ObjectInserter"/>
-                <message_argument value="newReader()"/>
-            </message_arguments>
-        </filter>
-    </resource>
-    <resource path="src/org/eclipse/jgit/merge/ResolveMerger.java" type="org.eclipse.jgit.merge.ResolveMerger">
-        <filter comment="Doesn't break consumers. Breaking providers is allowed also in minor versions." id="338792546">
-            <message_arguments>
-                <message_argument value="org.eclipse.jgit.merge.ResolveMerger"/>
-                <message_argument value="mergeTreeWalk(TreeWalk)"/>
-            </message_arguments>
-        </filter>
-        <filter comment="Doesn't break consumers. Breaking providers is allowed also in minor versions." id="338792546">
-            <message_arguments>
-                <message_argument value="org.eclipse.jgit.merge.ResolveMerger"/>
-                <message_argument value="mergeTrees(AbstractTreeIterator, RevTree, RevTree)"/>
-            </message_arguments>
-        </filter>
-        <filter comment="Doesn't break consumers. Breaking providers is allowed also in minor versions." id="338792546">
-            <message_arguments>
-                <message_argument value="org.eclipse.jgit.merge.ResolveMerger"/>
-                <message_argument value="processEntry(CanonicalTreeParser, CanonicalTreeParser, CanonicalTreeParser, DirCacheBuildIterator, WorkingTreeIterator)"/>
-            </message_arguments>
-        </filter>
-    </resource>
-    <resource path="src/org/eclipse/jgit/transport/GitProtocolConstants.java" type="org.eclipse.jgit.transport.GitProtocolConstants">
-        <filter id="388194388">
-            <message_arguments>
-                <message_argument value="org.eclipse.jgit.transport.GitProtocolConstants"/>
-                <message_argument value="CAPABILITY_ATOMIC"/>
-                <message_argument value="atomic-push"/>
-            </message_arguments>
-        </filter>
-    </resource>
     <resource path="src/org/eclipse/jgit/util/FileUtil.java" type="org.eclipse.jgit.util.FileUtil">
         <filter comment="moved into another bundle keeping original package" id="1110441988">
             <message_arguments>
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 a003b02..6028618 100644
--- a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties
+++ b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties
@@ -44,6 +44,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.
+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}.
 cannotCommitOnARepoWithState=Cannot commit on a repo with state: {0}
@@ -231,6 +232,7 @@
 fileIsTooBigForThisConvenienceMethod=File is too big for this convenience method ({0} bytes).
 fileIsTooLarge=File is too large: {0}
 fileModeNotSetForPath=FileMode not set for path {0}
+findingGarbage=Finding garbage
 flagIsDisposed={0} is disposed.
 flagNotFromThis={0} not from this.
 flagsAlreadyCreated={0} flags already created.
@@ -258,6 +260,7 @@
 initFailedBareRepoDifferentDirs=When initializing a bare repo with directory {0} and separate git-dir {1} specified both folders must point to the same location
 initFailedNonBareRepoSameDirs=When initializing a non-bare repo with directory {0} and separate git-dir {1} specified both folders should not point to the same location
 inMemoryBufferLimitExceeded=In-memory buffer limit exceeded
+inputDidntMatchLength=Input did not match supplied length. {0} bytes are missing.
 inputStreamMustSupportMark=InputStream must support mark()
 integerValueOutOfRange=Integer value {0}.{1} out of range
 internalRevisionError=internal revision error
@@ -391,6 +394,7 @@
 packFileInvalid=Pack file invalid: {0}
 packfileIsTruncated=Packfile {0} is truncated.
 packfileIsTruncatedNoParam=Packfile is truncated.
+packHandleIsStale=Pack file {0} handle is stale, removing it from pack list
 packHasUnresolvedDeltas=pack has unresolved deltas
 packingCancelledDuringObjectsWriting=Packing cancelled during objects writing
 packObjectCountMismatch=Pack object count mismatch: pack {0} index {1}: {2}
@@ -502,6 +506,7 @@
 submoduleExists=Submodule ''{0}'' already exists in the index
 submoduleParentRemoteUrlInvalid=Cannot remove segment from remote url ''{0}''
 submodulesNotSupported=Submodules are not supported
+supportOnlyPackIndexVersion2=Only support index version 2
 symlinkCannotBeWrittenAsTheLinkTarget=Symlink "{0}" cannot be written as the link target cannot be read from within Java.
 systemConfigFileInvalid=Systen wide config file {0} is invalid {1}
 tagAlreadyExists=tag ''{0}'' already exists
@@ -536,6 +541,7 @@
 truncatedHunkOldLinesMissing=Truncated hunk, at least {0} old lines is missing
 tSizeMustBeGreaterOrEqual1=tSize must be >= 1
 unableToCheckConnectivity=Unable to check connectivity.
+unableToCreateNewObject=Unable to create new object: {0}
 unableToStore=Unable to store {0}.
 unableToWrite=Unable to write {0}
 unencodeableFile=Unencodable file: {0}
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 c23256c..de6c32a 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/AddCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/AddCommand.java
@@ -137,13 +137,12 @@ public DirCache call() throws GitAPIException, NoFilepatternException {
 		if (filepatterns.contains(".")) //$NON-NLS-1$
 			addAll = true;
 
-		ObjectInserter inserter = repo.newObjectInserter();
-		try {
+		try (ObjectInserter inserter = repo.newObjectInserter();
+				final TreeWalk tw = new TreeWalk(repo)) {
 			dc = repo.lockDirCache();
 			DirCacheIterator c;
 
 			DirCacheBuilder builder = dc.builder();
-			final TreeWalk tw = new TreeWalk(repo);
 			tw.addTree(new DirCacheBuildIterator(builder));
 			if (workingTreeIterator == null)
 				workingTreeIterator = new FileTreeIterator(repo);
@@ -212,7 +211,6 @@ else if (!(path.equals(lastAddedFile))) {
 			throw new JGitInternalException(
 					JGitText.get().exceptionCaughtDuringExecutionOfAddCommand, e);
 		} finally {
-			inserter.release();
 			if (dc != null)
 				dc.unlock();
 		}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/AddNoteCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/AddNoteCommand.java
index 3af8695..9cf8881 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/AddNoteCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/AddNoteCommand.java
@@ -83,11 +83,10 @@ protected AddNoteCommand(Repository repo) {
 
 	public Note call() throws GitAPIException {
 		checkCallable();
-		RevWalk walk = new RevWalk(repo);
-		ObjectInserter inserter = repo.newObjectInserter();
 		NoteMap map = NoteMap.newEmptyMap();
 		RevCommit notesCommit = null;
-		try {
+		try (RevWalk walk = new RevWalk(repo);
+				ObjectInserter inserter = repo.newObjectInserter()) {
 			Ref ref = repo.getRef(notesRef);
 			// if we have a notes ref, use it
 			if (ref != null) {
@@ -96,13 +95,10 @@ public Note call() throws GitAPIException {
 			}
 			map.set(id, message, inserter);
 			commitNoteMap(walk, map, notesCommit, inserter,
-					"Notes added by 'git notes add'");
+					"Notes added by 'git notes add'"); //$NON-NLS-1$
 			return map.getNote(id);
 		} catch (IOException e) {
 			throw new JGitInternalException(e.getMessage(), e);
-		} finally {
-			inserter.release();
-			walk.release();
 		}
 	}
 
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/ArchiveCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/ArchiveCommand.java
index 9014aaf..713866d 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/ArchiveCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/ArchiveCommand.java
@@ -365,13 +365,11 @@ public ArchiveCommand(Repository repo) {
 
 	private <T extends Closeable> OutputStream writeArchive(Format<T> fmt) {
 		final String pfx = prefix == null ? "" : prefix; //$NON-NLS-1$
-		final TreeWalk walk = new TreeWalk(repo);
-		try {
+		try (final TreeWalk walk = new TreeWalk(repo)) {
 			final T outa = fmt.createArchiveOutputStream(out, formatOptions);
-			try {
+			try (final RevWalk rw = new RevWalk(walk.getObjectReader())) {
 				final MutableObjectId idBuf = new MutableObjectId();
 				final ObjectReader reader = walk.getObjectReader();
-				final RevWalk rw = new RevWalk(walk.getObjectReader());
 
 				walk.reset(rw.parseTree(tree));
 				if (!paths.isEmpty())
@@ -405,8 +403,6 @@ private <T extends Closeable> OutputStream writeArchive(Format<T> fmt) {
 			// TODO(jrn): Throw finer-grained errors.
 			throw new JGitInternalException(
 					JGitText.get().exceptionCaughtDuringExecutionOfArchiveCommand, e);
-		} finally {
-			walk.release();
 		}
 	}
 
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/BlameCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/BlameCommand.java
index f7ce835..a83814e 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/BlameCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/BlameCommand.java
@@ -200,8 +200,7 @@ public BlameCommand reverse(AnyObjectId start, Collection<ObjectId> end)
 	 */
 	public BlameResult call() throws GitAPIException {
 		checkCallable();
-		BlameGenerator gen = new BlameGenerator(repo, path);
-		try {
+		try (BlameGenerator gen = new BlameGenerator(repo, path)) {
 			if (diffAlgorithm != null)
 				gen.setDiffAlgorithm(diffAlgorithm);
 			if (textComparator != null)
@@ -231,8 +230,6 @@ else if (startCommit != null)
 			return gen.computeBlameResult();
 		} catch (IOException e) {
 			throw new JGitInternalException(e.getMessage(), e);
-		} finally {
-			gen.release();
 		}
 	}
 
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 3787ac5..8d8aada 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutCommand.java
@@ -208,16 +208,17 @@ public Ref call() throws GitAPIException, RefAlreadyExistsException,
 			}
 
 			if (createBranch) {
-				Git git = new Git(repo);
-				CreateBranchCommand command = git.branchCreate();
-				command.setName(name);
-				if (startCommit != null)
-					command.setStartPoint(startCommit);
-				else
-					command.setStartPoint(startPoint);
-				if (upstreamMode != null)
-					command.setUpstreamMode(upstreamMode);
-				command.call();
+				try (Git git = new Git(repo)) {
+					CreateBranchCommand command = git.branchCreate();
+					command.setName(name);
+					if (startCommit != null)
+						command.setStartPoint(startCommit);
+					else
+						command.setStartPoint(startPoint);
+					if (upstreamMode != null)
+						command.setUpstreamMode(upstreamMode);
+					command.call();
+				}
 			}
 
 			Ref headRef = repo.getRef(Constants.HEAD);
@@ -243,11 +244,14 @@ public Ref call() throws GitAPIException, RefAlreadyExistsException,
 							JGitText.get().refNotResolved, name));
 			}
 
-			RevWalk revWalk = new RevWalk(repo);
-			AnyObjectId headId = headRef.getObjectId();
-			RevCommit headCommit = headId == null ? null : revWalk
-					.parseCommit(headId);
-			RevCommit newCommit = revWalk.parseCommit(branch);
+			RevCommit headCommit = null;
+			RevCommit newCommit = null;
+			try (RevWalk revWalk = new RevWalk(repo)) {
+				AnyObjectId headId = headRef.getObjectId();
+				headCommit = headId == null ? null
+						: revWalk.parseCommit(headId);
+				newCommit = revWalk.parseCommit(branch);
+			}
 			RevTree headTree = headCommit == null ? null : headCommit.getTree();
 			DirCacheCheckout dco;
 			DirCache dc = repo.lockDirCache();
@@ -376,26 +380,20 @@ public CheckoutCommand setAllPaths(boolean all) {
 	 */
 	protected CheckoutCommand checkoutPaths() throws IOException,
 			RefNotFoundException {
-		RevWalk revWalk = new RevWalk(repo);
 		DirCache dc = repo.lockDirCache();
-		try {
-			TreeWalk treeWalk = new TreeWalk(revWalk.getObjectReader());
+		try (RevWalk revWalk = new RevWalk(repo);
+				TreeWalk treeWalk = new TreeWalk(revWalk.getObjectReader())) {
 			treeWalk.setRecursive(true);
 			if (!checkoutAllPaths)
 				treeWalk.setFilter(PathFilterGroup.createFromStrings(paths));
-			try {
-				if (isCheckoutIndex())
-					checkoutPathsFromIndex(treeWalk, dc);
-				else {
-					RevCommit commit = revWalk.parseCommit(getStartPointObjectId());
-					checkoutPathsFromCommit(treeWalk, dc, commit);
-				}
-			} finally {
-				treeWalk.release();
+			if (isCheckoutIndex())
+				checkoutPathsFromIndex(treeWalk, dc);
+			else {
+				RevCommit commit = revWalk.parseCommit(getStartPointObjectId());
+				checkoutPathsFromCommit(treeWalk, dc, commit);
 			}
 		} finally {
 			dc.unlock();
-			revWalk.release();
 		}
 		return this;
 	}
@@ -675,7 +673,6 @@ public CheckoutResult getResult() {
 	private void checkOptions() {
 		if (checkoutStage != null && !isCheckoutIndex())
 			throw new IllegalStateException(
-					"Checking out ours/theirs is only possible when checking out index, "
-							+ "not when switching branches.");
+					JGitText.get().cannotCheckoutOursSwitchBranch);
 	}
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/CherryPickCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/CherryPickCommand.java
index d4eb0b3..d6e930a 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CherryPickCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CherryPickCommand.java
@@ -123,8 +123,7 @@ public CherryPickResult call() throws GitAPIException, NoMessageException,
 		List<Ref> cherryPickedRefs = new LinkedList<Ref>();
 		checkCallable();
 
-		RevWalk revWalk = new RevWalk(repo);
-		try {
+		try (RevWalk revWalk = new RevWalk(repo)) {
 
 			// get the head commit
 			Ref headRef = repo.getRef(Constants.HEAD);
@@ -153,7 +152,7 @@ public CherryPickResult call() throws GitAPIException, NoMessageException,
 				ResolveMerger merger = (ResolveMerger) strategy.newMerger(repo);
 				merger.setWorkingTreeIterator(new FileTreeIterator(repo));
 				merger.setBase(srcParent.getTree());
-				merger.setCommitNames(new String[] { "BASE", ourName,
+				merger.setCommitNames(new String[] { "BASE", ourName, //$NON-NLS-1$
 						cherryPickName });
 				if (merger.merge(newHead, srcCommit)) {
 					if (AnyObjectId.equals(newHead.getTree().getId(), merger
@@ -194,8 +193,6 @@ public CherryPickResult call() throws GitAPIException, NoMessageException,
 					MessageFormat.format(
 							JGitText.get().exceptionCaughtDuringExecutionOfCherryPickCommand,
 							e), e);
-		} finally {
-			revWalk.release();
 		}
 		return new CherryPickResult(newHead, cherryPickedRefs);
 	}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/CherryPickResult.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/CherryPickResult.java
index 5b3c5d4..b121291 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CherryPickResult.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CherryPickResult.java
@@ -63,21 +63,21 @@ public enum CherryPickStatus {
 		OK {
 			@Override
 			public String toString() {
-				return "Ok";
+				return "Ok"; //$NON-NLS-1$
 			}
 		},
 		/** */
 		FAILED {
 			@Override
 			public String toString() {
-				return "Failed";
+				return "Failed"; //$NON-NLS-1$
 			}
 		},
 		/** */
 		CONFLICTING {
 			@Override
 			public String toString() {
-				return "Conflicting";
+				return "Conflicting"; //$NON-NLS-1$
 			}
 		}
 	}
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 f058f79..53901f5 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CloneCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CloneCommand.java
@@ -322,12 +322,9 @@ private void addMergeConfig(Repository clonedRepo, Ref head)
 	private RevCommit parseCommit(final Repository clonedRepo, final Ref ref)
 			throws MissingObjectException, IncorrectObjectTypeException,
 			IOException {
-		final RevWalk rw = new RevWalk(clonedRepo);
 		final RevCommit commit;
-		try {
+		try (final RevWalk rw = new RevWalk(clonedRepo)) {
 			commit = rw.parseCommit(ref.getObjectId());
-		} finally {
-			rw.release();
 		}
 		return commit;
 	}
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 b57cff7..87efd79 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java
@@ -165,9 +165,7 @@ public RevCommit call() throws GitAPIException, NoHeadException,
 		checkCallable();
 		Collections.sort(only);
 
-		RevWalk rw = new RevWalk(repo);
-
-		try {
+		try (RevWalk rw = new RevWalk(repo)) {
 			RepositoryState state = repo.getRepositoryState();
 			if (!state.canCommit())
 				throw new WrongRepositoryStateException(MessageFormat.format(
@@ -181,8 +179,7 @@ public RevCommit call() throws GitAPIException, NoHeadException,
 			processOptions(state, rw);
 
 			if (all && !repo.isBare() && repo.getWorkTree() != null) {
-				Git git = new Git(repo);
-				try {
+				try (Git git = new Git(repo)) {
 					git.add()
 							.addFilepattern(".") //$NON-NLS-1$
 							.setUpdate(true).call();
@@ -221,80 +218,74 @@ public RevCommit call() throws GitAPIException, NoHeadException,
 
 			// lock the index
 			DirCache index = repo.lockDirCache();
-			try {
+			try (ObjectInserter odi = repo.newObjectInserter()) {
 				if (!only.isEmpty())
 					index = createTemporaryIndex(headId, index, rw);
 
-				ObjectInserter odi = repo.newObjectInserter();
-				try {
-					// Write the index as tree to the object database. This may
-					// fail for example when the index contains unmerged paths
-					// (unresolved conflicts)
-					ObjectId indexTreeId = index.writeTree(odi);
+				// Write the index as tree to the object database. This may
+				// fail for example when the index contains unmerged paths
+				// (unresolved conflicts)
+				ObjectId indexTreeId = index.writeTree(odi);
 
-					if (insertChangeId)
-						insertChangeId(indexTreeId);
+				if (insertChangeId)
+					insertChangeId(indexTreeId);
 
-					// Create a Commit object, populate it and write it
-					CommitBuilder commit = new CommitBuilder();
-					commit.setCommitter(committer);
-					commit.setAuthor(author);
-					commit.setMessage(message);
+				// Create a Commit object, populate it and write it
+				CommitBuilder commit = new CommitBuilder();
+				commit.setCommitter(committer);
+				commit.setAuthor(author);
+				commit.setMessage(message);
 
-					commit.setParentIds(parents);
-					commit.setTreeId(indexTreeId);
-					ObjectId commitId = odi.insert(commit);
-					odi.flush();
+				commit.setParentIds(parents);
+				commit.setTreeId(indexTreeId);
+				ObjectId commitId = odi.insert(commit);
+				odi.flush();
 
-					RevCommit revCommit = rw.parseCommit(commitId);
-					RefUpdate ru = repo.updateRef(Constants.HEAD);
-					ru.setNewObjectId(commitId);
-					if (reflogComment != null) {
-						ru.setRefLogMessage(reflogComment, false);
-					} else {
-						String prefix = amend ? "commit (amend): " //$NON-NLS-1$
-								: parents.size() == 0 ? "commit (initial): " //$NON-NLS-1$
-										: "commit: "; //$NON-NLS-1$
-						ru.setRefLogMessage(
-								prefix + revCommit.getShortMessage(), false);
+				RevCommit revCommit = rw.parseCommit(commitId);
+				RefUpdate ru = repo.updateRef(Constants.HEAD);
+				ru.setNewObjectId(commitId);
+				if (reflogComment != null) {
+					ru.setRefLogMessage(reflogComment, false);
+				} else {
+					String prefix = amend ? "commit (amend): " //$NON-NLS-1$
+							: parents.size() == 0 ? "commit (initial): " //$NON-NLS-1$
+									: "commit: "; //$NON-NLS-1$
+					ru.setRefLogMessage(prefix + revCommit.getShortMessage(),
+							false);
+				}
+				if (headId != null)
+					ru.setExpectedOldObjectId(headId);
+				else
+					ru.setExpectedOldObjectId(ObjectId.zeroId());
+				Result rc = ru.forceUpdate();
+				switch (rc) {
+				case NEW:
+				case FORCED:
+				case FAST_FORWARD: {
+					setCallable(false);
+					if (state == RepositoryState.MERGING_RESOLVED
+							|| isMergeDuringRebase(state)) {
+						// Commit was successful. Now delete the files
+						// used for merge commits
+						repo.writeMergeCommitMsg(null);
+						repo.writeMergeHeads(null);
+					} else if (state == RepositoryState.CHERRY_PICKING_RESOLVED) {
+						repo.writeMergeCommitMsg(null);
+						repo.writeCherryPickHead(null);
+					} else if (state == RepositoryState.REVERTING_RESOLVED) {
+						repo.writeMergeCommitMsg(null);
+						repo.writeRevertHead(null);
 					}
-					if (headId != null)
-						ru.setExpectedOldObjectId(headId);
-					else
-						ru.setExpectedOldObjectId(ObjectId.zeroId());
-					Result rc = ru.forceUpdate();
-					switch (rc) {
-					case NEW:
-					case FORCED:
-					case FAST_FORWARD: {
-						setCallable(false);
-						if (state == RepositoryState.MERGING_RESOLVED
-								|| isMergeDuringRebase(state)) {
-							// Commit was successful. Now delete the files
-							// used for merge commits
-							repo.writeMergeCommitMsg(null);
-							repo.writeMergeHeads(null);
-						} else if (state == RepositoryState.CHERRY_PICKING_RESOLVED) {
-							repo.writeMergeCommitMsg(null);
-							repo.writeCherryPickHead(null);
-						} else if (state == RepositoryState.REVERTING_RESOLVED) {
-							repo.writeMergeCommitMsg(null);
-							repo.writeRevertHead(null);
-						}
-						return revCommit;
-					}
-					case REJECTED:
-					case LOCK_FAILURE:
-						throw new ConcurrentRefUpdateException(
-								JGitText.get().couldNotLockHEAD, ru.getRef(),
-								rc);
-					default:
-						throw new JGitInternalException(MessageFormat.format(
-								JGitText.get().updatingRefFailed,
-								Constants.HEAD, commitId.toString(), rc));
-					}
-				} finally {
-					odi.release();
+					return revCommit;
+				}
+				case REJECTED:
+				case LOCK_FAILURE:
+					throw new ConcurrentRefUpdateException(
+							JGitText.get().couldNotLockHEAD, ru.getRef(), rc);
+				default:
+					throw new JGitInternalException(MessageFormat.format(
+							JGitText.get().updatingRefFailed, Constants.HEAD,
+							commitId.toString(), rc));
 				}
 			} finally {
 				index.unlock();
@@ -304,8 +295,6 @@ public RevCommit call() throws GitAPIException, NoHeadException,
 		} catch (IOException e) {
 			throw new JGitInternalException(
 					JGitText.get().exceptionCaughtDuringExecutionOfCommitCommand, e);
-		} finally {
-			rw.dispose();
 		}
 	}
 
@@ -338,114 +327,120 @@ private DirCache createTemporaryIndex(ObjectId headId, DirCache index,
 		onlyProcessed = new boolean[only.size()];
 		boolean emptyCommit = true;
 
-		TreeWalk treeWalk = new TreeWalk(repo);
-		int dcIdx = treeWalk.addTree(new DirCacheBuildIterator(existingBuilder));
-		int fIdx = treeWalk.addTree(new FileTreeIterator(repo));
-		int hIdx = -1;
-		if (headId != null)
-			hIdx = treeWalk.addTree(rw.parseTree(headId));
-		treeWalk.setRecursive(true);
+		try (TreeWalk treeWalk = new TreeWalk(repo)) {
+			int dcIdx = treeWalk
+					.addTree(new DirCacheBuildIterator(existingBuilder));
+			int fIdx = treeWalk.addTree(new FileTreeIterator(repo));
+			int hIdx = -1;
+			if (headId != null)
+				hIdx = treeWalk.addTree(rw.parseTree(headId));
+			treeWalk.setRecursive(true);
 
-		String lastAddedFile = null;
-		while (treeWalk.next()) {
-			String path = treeWalk.getPathString();
-			// check if current entry's path matches a specified path
-			int pos = lookupOnly(path);
+			String lastAddedFile = null;
+			while (treeWalk.next()) {
+				String path = treeWalk.getPathString();
+				// check if current entry's path matches a specified path
+				int pos = lookupOnly(path);
 
-			CanonicalTreeParser hTree = null;
-			if (hIdx != -1)
-				hTree = treeWalk.getTree(hIdx, CanonicalTreeParser.class);
+				CanonicalTreeParser hTree = null;
+				if (hIdx != -1)
+					hTree = treeWalk.getTree(hIdx, CanonicalTreeParser.class);
 
-			DirCacheIterator dcTree = treeWalk.getTree(dcIdx,
-					DirCacheIterator.class);
+				DirCacheIterator dcTree = treeWalk.getTree(dcIdx,
+						DirCacheIterator.class);
 
-			if (pos >= 0) {
-				// include entry in commit
+				if (pos >= 0) {
+					// include entry in commit
 
-				FileTreeIterator fTree = treeWalk.getTree(fIdx,
-						FileTreeIterator.class);
+					FileTreeIterator fTree = treeWalk.getTree(fIdx,
+							FileTreeIterator.class);
 
-				// check if entry refers to a tracked file
-				boolean tracked = dcTree != null || hTree != null;
-				if (!tracked)
-					break;
+					// check if entry refers to a tracked file
+					boolean tracked = dcTree != null || hTree != null;
+					if (!tracked)
+						break;
 
-				// for an unmerged path, DirCacheBuildIterator will yield 3
-				// entries, we only want to add one
-				if (path.equals(lastAddedFile))
-					continue;
+					// for an unmerged path, DirCacheBuildIterator will yield 3
+					// entries, we only want to add one
+					if (path.equals(lastAddedFile))
+						continue;
 
-				lastAddedFile = path;
+					lastAddedFile = path;
 
-				if (fTree != null) {
-					// create a new DirCacheEntry with data retrieved from disk
-					final DirCacheEntry dcEntry = new DirCacheEntry(path);
-					long entryLength = fTree.getEntryLength();
-					dcEntry.setLength(entryLength);
-					dcEntry.setLastModified(fTree.getEntryLastModified());
-					dcEntry.setFileMode(fTree.getIndexFileMode(dcTree));
+					if (fTree != null) {
+						// create a new DirCacheEntry with data retrieved from
+						// disk
+						final DirCacheEntry dcEntry = new DirCacheEntry(path);
+						long entryLength = fTree.getEntryLength();
+						dcEntry.setLength(entryLength);
+						dcEntry.setLastModified(fTree.getEntryLastModified());
+						dcEntry.setFileMode(fTree.getIndexFileMode(dcTree));
 
-					boolean objectExists = (dcTree != null && fTree
-							.idEqual(dcTree))
-							|| (hTree != null && fTree.idEqual(hTree));
-					if (objectExists) {
-						dcEntry.setObjectId(fTree.getEntryObjectId());
-					} else {
-						if (FileMode.GITLINK.equals(dcEntry.getFileMode()))
+						boolean objectExists = (dcTree != null
+								&& fTree.idEqual(dcTree))
+								|| (hTree != null && fTree.idEqual(hTree));
+						if (objectExists) {
 							dcEntry.setObjectId(fTree.getEntryObjectId());
-						else {
-							// insert object
-							if (inserter == null)
-								inserter = repo.newObjectInserter();
-							long contentLength = fTree.getEntryContentLength();
-							InputStream inputStream = fTree.openEntryStream();
-							try {
-								dcEntry.setObjectId(inserter.insert(
-										Constants.OBJ_BLOB, contentLength,
-										inputStream));
-							} finally {
-								inputStream.close();
+						} else {
+							if (FileMode.GITLINK.equals(dcEntry.getFileMode()))
+								dcEntry.setObjectId(fTree.getEntryObjectId());
+							else {
+								// insert object
+								if (inserter == null)
+									inserter = repo.newObjectInserter();
+								long contentLength = fTree
+										.getEntryContentLength();
+								InputStream inputStream = fTree
+										.openEntryStream();
+								try {
+									dcEntry.setObjectId(inserter.insert(
+											Constants.OBJ_BLOB, contentLength,
+											inputStream));
+								} finally {
+									inputStream.close();
+								}
 							}
 						}
+
+						// add to existing index
+						existingBuilder.add(dcEntry);
+						// add to temporary in-core index
+						tempBuilder.add(dcEntry);
+
+						if (emptyCommit
+								&& (hTree == null || !hTree.idEqual(fTree)
+										|| hTree.getEntryRawMode() != fTree
+												.getEntryRawMode()))
+							// this is a change
+							emptyCommit = false;
+					} else {
+						// if no file exists on disk, neither add it to
+						// index nor to temporary in-core index
+
+						if (emptyCommit && hTree != null)
+							// this is a change
+							emptyCommit = false;
 					}
 
-					// add to existing index
-					existingBuilder.add(dcEntry);
-					// add to temporary in-core index
-					tempBuilder.add(dcEntry);
-
-					if (emptyCommit
-							&& (hTree == null || !hTree.idEqual(fTree) || hTree
-									.getEntryRawMode() != fTree
-									.getEntryRawMode()))
-						// this is a change
-						emptyCommit = false;
+					// keep track of processed path
+					onlyProcessed[pos] = true;
 				} else {
-					// if no file exists on disk, neither add it to
-					// index nor to temporary in-core index
+					// add entries from HEAD for all other paths
+					if (hTree != null) {
+						// create a new DirCacheEntry with data retrieved from
+						// HEAD
+						final DirCacheEntry dcEntry = new DirCacheEntry(path);
+						dcEntry.setObjectId(hTree.getEntryObjectId());
+						dcEntry.setFileMode(hTree.getEntryFileMode());
 
-					if (emptyCommit && hTree != null)
-						// this is a change
-						emptyCommit = false;
+						// add to temporary in-core index
+						tempBuilder.add(dcEntry);
+					}
+
+					// preserve existing entry in index
+					if (dcTree != null)
+						existingBuilder.add(dcTree.getDirCacheEntry());
 				}
-
-				// keep track of processed path
-				onlyProcessed[pos] = true;
-			} else {
-				// add entries from HEAD for all other paths
-				if (hTree != null) {
-					// create a new DirCacheEntry with data retrieved from HEAD
-					final DirCacheEntry dcEntry = new DirCacheEntry(path);
-					dcEntry.setObjectId(hTree.getEntryObjectId());
-					dcEntry.setFileMode(hTree.getEntryFileMode());
-
-					// add to temporary in-core index
-					tempBuilder.add(dcEntry);
-				}
-
-				// preserve existing entry in index
-				if (dcTree != null)
-					existingBuilder.add(dcTree.getDirCacheEntry());
 			}
 		}
 
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/DescribeCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/DescribeCommand.java
index bf6da45..be45666 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/DescribeCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/DescribeCommand.java
@@ -315,7 +315,7 @@ public int compare(Candidate o1, Candidate o2) {
 			throw new JGitInternalException(e.getMessage(), e);
 		} finally {
 			setCallable(false);
-			w.release();
+			w.close();
 		}
 	}
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/DiffCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/DiffCommand.java
index f31198f..527daef 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/DiffCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/DiffCommand.java
@@ -124,11 +124,8 @@ public List<DiffEntry> call() throws GitAPIException {
 					if (head == null)
 						throw new NoHeadException(JGitText.get().cannotReadTree);
 					CanonicalTreeParser p = new CanonicalTreeParser();
-					ObjectReader reader = repo.newObjectReader();
-					try {
+					try (ObjectReader reader = repo.newObjectReader()) {
 						p.reset(reader, head);
-					} finally {
-						reader.release();
 					}
 					oldTree = p;
 				}
@@ -159,7 +156,7 @@ public List<DiffEntry> call() throws GitAPIException {
 		} catch (IOException e) {
 			throw new JGitInternalException(e.getMessage(), e);
 		} finally {
-			diffFmt.release();
+			diffFmt.close();
 		}
 	}
 
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/NameRevCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/NameRevCommand.java
index cce42fc..fd28d0e 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/NameRevCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/NameRevCommand.java
@@ -185,11 +185,11 @@ public Map<ObjectId, String> call() throws GitAPIException {
 			}
 
 			setCallable(false);
-			walk.release();
 			return result;
 		} catch (IOException e) {
-			walk.reset();
 			throw new JGitInternalException(e.getMessage(), e);
+		} finally {
+			walk.close();
 		}
 	}
 
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 7d3e823..62001d0 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java
@@ -1088,7 +1088,9 @@ else if (!isInteractive() && walk.isMergedInto(headCommit, upstream)) {
 		rebaseState.createFile(HEAD_NAME, headName);
 		rebaseState.createFile(ONTO, upstreamCommit.name());
 		rebaseState.createFile(ONTO_NAME, upstreamCommitName);
-		rebaseState.createFile(INTERACTIVE, ""); //$NON-NLS-1$
+		if (isInteractive()) {
+			rebaseState.createFile(INTERACTIVE, ""); //$NON-NLS-1$
+		}
 		rebaseState.createFile(QUIET, ""); //$NON-NLS-1$
 
 		ArrayList<RebaseTodoLine> toDoSteps = new ArrayList<RebaseTodoLine>();
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 17b1242..ac67037 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/ResetCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/ResetCommand.java
@@ -227,9 +227,9 @@ else if (repo.readSquashCommitMsg() != null)
 			setCallable(false);
 			return result;
 		} catch (IOException e) {
-			throw new JGitInternalException(
+			throw new JGitInternalException(MessageFormat.format(
 					JGitText.get().exceptionCaughtDuringExecutionOfResetCommand,
-					e);
+					e.getMessage()), e);
 		}
 	}
 
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/blame/BlameResult.java b/org.eclipse.jgit/src/org/eclipse/jgit/blame/BlameResult.java
index 735eef7..e34db38 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/blame/BlameResult.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/blame/BlameResult.java
@@ -85,7 +85,7 @@ public static BlameResult create(BlameGenerator gen) throws IOException {
 		String path = gen.getResultPath();
 		RawText contents = gen.getResultContents();
 		if (contents == null) {
-			gen.release();
+			gen.close();
 			return null;
 		}
 		return new BlameResult(gen, path, contents);
@@ -239,7 +239,7 @@ public void computeAll() throws IOException {
 			while (gen.next())
 				loadFrom(gen);
 		} finally {
-			gen.release();
+			gen.close();
 			generator = null;
 		}
 	}
@@ -265,7 +265,7 @@ public int computeNext() throws IOException {
 			lastLength = gen.getRegionLength();
 			return gen.getResultStart();
 		} else {
-			gen.release();
+			gen.close();
 			generator = null;
 			return -1;
 		}
@@ -300,7 +300,7 @@ public void computeRange(int start, int end) throws IOException {
 				return;
 
 			if (!gen.next()) {
-				gen.release();
+				gen.close();
 				generator = null;
 				return;
 			}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffFormatter.java b/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffFormatter.java
index 1aab51e..b71e990 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffFormatter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffFormatter.java
@@ -173,7 +173,7 @@ protected OutputStream getOutputStream() {
 	 */
 	public void setRepository(Repository repository) {
 		if (reader != null)
-			reader.release();
+			reader.close();
 
 		db = repository;
 		reader = db.newObjectReader();
@@ -422,10 +422,11 @@ public List<DiffEntry> scan(AnyObjectId a, AnyObjectId b)
 			throws IOException {
 		assertHaveRepository();
 
-		RevWalk rw = new RevWalk(reader);
-		RevTree aTree = a != null ? rw.parseTree(a) : null;
-		RevTree bTree = b != null ? rw.parseTree(b) : null;
-		return scan(aTree, bTree);
+		try (RevWalk rw = new RevWalk(reader)) {
+			RevTree aTree = a != null ? rw.parseTree(a) : null;
+			RevTree bTree = b != null ? rw.parseTree(b) : null;
+			return scan(aTree, bTree);
+		}
 	}
 
 	/**
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 98a1c8c..6d9a32d 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCache.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCache.java
@@ -961,9 +961,8 @@ private void registerIndexChangedListener(IndexChangedListener listener) {
 	 * @throws IOException
 	 */
 	private void updateSmudgedEntries() throws IOException {
-		TreeWalk walk = new TreeWalk(repository);
 		List<String> paths = new ArrayList<String>(128);
-		try {
+		try (TreeWalk walk = new TreeWalk(repository)) {
 			for (int i = 0; i < entryCnt; i++)
 				if (sortedEntries[i].isSmudged())
 					paths.add(sortedEntries[i].getPathString());
@@ -989,8 +988,6 @@ private void updateSmudgedEntries() throws IOException {
 					entry.setLastModified(fIter.getEntryLastModified());
 				}
 			}
-		} finally {
-			walk.release();
 		}
 	}
 }
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 015d9d6..99e022b 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java
@@ -1284,10 +1284,8 @@ private static void checkValidPath(CanonicalTreeParser t)
 	 * @throws InvalidPathException
 	 *             if the path is invalid
 	 * @since 3.3
-	 * @deprecated Use {@link SystemReader#checkPath(String)}.
 	 */
-	@Deprecated
-	public static void checkValidPath(String path) throws InvalidPathException {
+	static void checkValidPath(String path) throws InvalidPathException {
 		try {
 			SystemReader.getInstance().checkPath(path);
 		} catch (CorruptObjectException e) {
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 63b788f..3077e18 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java
@@ -103,6 +103,7 @@ public static JGitText get() {
 	/***/ public String cannotBeRecursiveWhenTreesAreIncluded;
 	/***/ public String cannotChangeActionOnComment;
 	/***/ public String cannotChangeToComment;
+	/***/ public String cannotCheckoutOursSwitchBranch;
 	/***/ public String cannotCombineSquashWithNoff;
 	/***/ public String cannotCombineTreeFilterWithRevFilter;
 	/***/ public String cannotCommitOnARepoWithState;
@@ -290,6 +291,7 @@ public static JGitText get() {
 	/***/ public String fileIsTooBigForThisConvenienceMethod;
 	/***/ public String fileIsTooLarge;
 	/***/ public String fileModeNotSetForPath;
+	/***/ public String findingGarbage;
 	/***/ public String flagIsDisposed;
 	/***/ public String flagNotFromThis;
 	/***/ public String flagsAlreadyCreated;
@@ -317,6 +319,7 @@ public static JGitText get() {
 	/***/ public String initFailedBareRepoDifferentDirs;
 	/***/ public String initFailedNonBareRepoSameDirs;
 	/***/ public String inMemoryBufferLimitExceeded;
+	/***/ public String inputDidntMatchLength;
 	/***/ public String inputStreamMustSupportMark;
 	/***/ public String integerValueOutOfRange;
 	/***/ public String internalRevisionError;
@@ -450,6 +453,7 @@ public static JGitText get() {
 	/***/ public String packFileInvalid;
 	/***/ public String packfileIsTruncated;
 	/***/ public String packfileIsTruncatedNoParam;
+	/***/ public String packHandleIsStale;
 	/***/ public String packHasUnresolvedDeltas;
 	/***/ public String packingCancelledDuringObjectsWriting;
 	/***/ public String packObjectCountMismatch;
@@ -561,6 +565,7 @@ public static JGitText get() {
 	/***/ public String submoduleExists;
 	/***/ public String submodulesNotSupported;
 	/***/ public String submoduleParentRemoteUrlInvalid;
+	/***/ public String supportOnlyPackIndexVersion2;
 	/***/ public String symlinkCannotBeWrittenAsTheLinkTarget;
 	/***/ public String systemConfigFileInvalid;
 	/***/ public String tagAlreadyExists;
@@ -595,6 +600,7 @@ public static JGitText get() {
 	/***/ public String truncatedHunkOldLinesMissing;
 	/***/ public String tSizeMustBeGreaterOrEqual1;
 	/***/ public String unableToCheckConnectivity;
+	/***/ public String unableToCreateNewObject;
 	/***/ public String unableToStore;
 	/***/ public String unableToWrite;
 	/***/ public String unencodeableFile;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlock.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlock.java
index c185332..7926536 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlock.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlock.java
@@ -46,7 +46,6 @@
 package org.eclipse.jgit.internal.storage.dfs;
 
 import java.io.IOException;
-import java.security.MessageDigest;
 import java.util.zip.CRC32;
 import java.util.zip.DataFormatException;
 import java.util.zip.Inflater;
@@ -101,12 +100,9 @@ void crc32(CRC32 out, long pos, int cnt) {
 		out.update(block, ptr, cnt);
 	}
 
-	void write(PackOutputStream out, long pos, int cnt, MessageDigest digest)
+	void write(PackOutputStream out, long pos, int cnt)
 			throws IOException {
-		int ptr = (int) (pos - start);
-		out.write(block, ptr, cnt);
-		if (digest != null)
-			digest.update(block, ptr, cnt);
+		out.write(block, (int) (pos - start), cnt);
 	}
 
 	void check(Inflater inf, byte[] tmp, long pos, int cnt)
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlockCache.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlockCache.java
index 748a4a3..2e170a5 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlockCache.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlockCache.java
@@ -135,6 +135,9 @@ public static DfsBlockCache getInstance() {
 	/** Maximum number of bytes the cache should hold. */
 	private final long maxBytes;
 
+	/** Pack files smaller than this size can be copied through the cache. */
+	private final long maxStreamThroughCache;
+
 	/**
 	 * Suggested block size to read from pack files in.
 	 * <p>
@@ -191,6 +194,7 @@ else if (eb < 4)
 			eb = tableSize;
 
 		maxBytes = cfg.getBlockLimit();
+		maxStreamThroughCache = (long) (maxBytes * cfg.getStreamRatio());
 		blockSize = cfg.getBlockSize();
 		blockSizeShift = Integer.numberOfTrailingZeros(blockSize);
 
@@ -206,6 +210,10 @@ else if (eb < 4)
 		statMiss = new AtomicLong();
 	}
 
+	boolean shouldCopyThroughCache(long length) {
+		return length <= maxStreamThroughCache;
+	}
+
 	/** @return total number of bytes in the cache. */
 	public long getCurrentSize() {
 		return liveBytes;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlockCacheConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlockCacheConfig.java
index ca1451a..a7d13de 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlockCacheConfig.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlockCacheConfig.java
@@ -47,7 +47,11 @@
 import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_DFS_SECTION;
 import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_BLOCK_LIMIT;
 import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_BLOCK_SIZE;
+import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_STREAM_RATIO;
 
+import java.text.MessageFormat;
+
+import org.eclipse.jgit.internal.JGitText;
 import org.eclipse.jgit.lib.Config;
 
 /** Configuration parameters for {@link DfsBlockCache}. */
@@ -59,13 +63,14 @@ public class DfsBlockCacheConfig {
 	public static final int MB = 1024 * KB;
 
 	private long blockLimit;
-
 	private int blockSize;
+	private double streamRatio;
 
 	/** Create a default configuration. */
 	public DfsBlockCacheConfig() {
 		setBlockLimit(32 * MB);
 		setBlockSize(64 * KB);
+		setStreamRatio(0.30);
 	}
 
 	/**
@@ -106,6 +111,27 @@ public DfsBlockCacheConfig setBlockSize(final int newSize) {
 	}
 
 	/**
+	 * @return highest percentage of {@link #getBlockLimit()} a single pack can
+	 *         occupy while being copied by the pack reuse strategy. <b>Default
+	 *         is 0.30, or 30%</b>.
+	 * @since 4.0
+	 */
+	public double getStreamRatio() {
+		return streamRatio;
+	}
+
+	/**
+	 * @param ratio
+	 *            percentage of cache to occupy with a copied pack.
+	 * @return {@code this}
+	 * @since 4.0
+	 */
+	public DfsBlockCacheConfig setStreamRatio(double ratio) {
+		streamRatio = Math.max(0, Math.min(ratio, 1.0));
+		return this;
+	}
+
+	/**
 	 * Update properties by setting fields from the configuration.
 	 * <p>
 	 * If a property is not defined in the configuration, then it is left
@@ -127,6 +153,22 @@ public DfsBlockCacheConfig fromConfig(final Config rc) {
 				CONFIG_DFS_SECTION,
 				CONFIG_KEY_BLOCK_SIZE,
 				getBlockSize()));
+
+		String v = rc.getString(
+				CONFIG_CORE_SECTION,
+				CONFIG_DFS_SECTION,
+				CONFIG_KEY_STREAM_RATIO);
+		if (v != null) {
+			try {
+				setStreamRatio(Double.parseDouble(v));
+			} catch (NumberFormatException e) {
+				throw new IllegalArgumentException(MessageFormat.format(
+						JGitText.get().enumValueNotSupported3,
+						CONFIG_CORE_SECTION,
+						CONFIG_DFS_SECTION,
+						CONFIG_KEY_STREAM_RATIO, v));
+			}
+		}
 		return this;
 	}
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsCachedPack.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsCachedPack.java
index 3da5184..a5308f6 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsCachedPack.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsCachedPack.java
@@ -78,8 +78,7 @@ public boolean hasObject(ObjectToPack obj, StoredObjectRepresentation rep) {
 		return ((DfsObjectRepresentation) rep).pack == pack;
 	}
 
-	void copyAsIs(PackOutputStream out, boolean validate, DfsReader ctx)
-			throws IOException {
-		pack.copyPackAsIs(out, validate, ctx);
+	void copyAsIs(PackOutputStream out, DfsReader ctx) throws IOException {
+		pack.copyPackAsIs(out, ctx);
 	}
 }
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 deb6b7f..fed5338 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
@@ -58,6 +58,7 @@
 import java.util.Map;
 import java.util.Set;
 
+import org.eclipse.jgit.internal.JGitText;
 import org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource;
 import org.eclipse.jgit.internal.storage.file.PackIndex;
 import org.eclipse.jgit.internal.storage.pack.PackExt;
@@ -188,7 +189,8 @@ public boolean pack(ProgressMonitor pm) throws IOException {
 		if (pm == null)
 			pm = NullProgressMonitor.INSTANCE;
 		if (packConfig.getIndexVersion() != 2)
-			throw new IllegalStateException("Only index version 2");
+			throw new IllegalStateException(
+					JGitText.get().supportOnlyPackIndexVersion2);
 
 		ctx = (DfsReader) objdb.newReader();
 		try {
@@ -272,14 +274,11 @@ private void packHeads(ProgressMonitor pm) throws IOException {
 		if (allHeads.isEmpty())
 			return;
 
-		PackWriter pw = newPackWriter();
-		try {
+		try (PackWriter pw = newPackWriter()) {
 			pw.setTagTargets(tagTargets);
 			pw.preparePack(pm, allHeads, Collections.<ObjectId> emptySet());
 			if (0 < pw.getObjectCount())
 				writePack(GC, pw, pm);
-		} finally {
-			pw.release();
 		}
 	}
 
@@ -287,15 +286,12 @@ private void packRest(ProgressMonitor pm) throws IOException {
 		if (nonHeads.isEmpty())
 			return;
 
-		PackWriter pw = newPackWriter();
-		try {
+		try (PackWriter pw = newPackWriter()) {
 			for (PackWriter.ObjectIdSet packedObjs : newPackObj)
 				pw.excludeObjects(packedObjs);
 			pw.preparePack(pm, nonHeads, allHeads);
 			if (0 < pw.getObjectCount())
 				writePack(GC, pw, pm);
-		} finally {
-			pw.release();
 		}
 	}
 
@@ -307,12 +303,11 @@ private void packGarbage(ProgressMonitor pm) throws IOException {
 		cfg.setDeltaCompress(false);
 		cfg.setBuildBitmaps(false);
 
-		PackWriter pw = new PackWriter(cfg, ctx);
-		pw.setDeltaBaseAsOffset(true);
-		pw.setReuseDeltaCommits(true);
-		try {
-			RevWalk pool = new RevWalk(ctx);
-			pm.beginTask("Finding garbage", objectsBefore());
+		try (PackWriter pw = new PackWriter(cfg, ctx);
+				RevWalk pool = new RevWalk(ctx)) {
+			pw.setDeltaBaseAsOffset(true);
+			pw.setReuseDeltaCommits(true);
+			pm.beginTask(JGitText.get().findingGarbage, objectsBefore());
 			for (DfsPackFile oldPack : packsBefore) {
 				PackIndex oldIdx = oldPack.getPackIndex(ctx);
 				for (PackIndex.MutableEntry ent : oldIdx) {
@@ -328,8 +323,6 @@ private void packGarbage(ProgressMonitor pm) throws IOException {
 			pm.endTask();
 			if (0 < pw.getObjectCount())
 				writePack(UNREACHABLE_GARBAGE, pw, pm);
-		} finally {
-			pw.release();
 		}
 	}
 
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackFile.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackFile.java
index 58df895..e03488b 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackFile.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackFile.java
@@ -54,6 +54,7 @@
 import java.io.EOFException;
 import java.io.IOException;
 import java.io.InputStream;
+import java.nio.ByteBuffer;
 import java.nio.channels.Channels;
 import java.text.MessageFormat;
 import java.util.Set;
@@ -80,7 +81,6 @@
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.ObjectLoader;
 import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.util.IO;
 import org.eclipse.jgit.util.LongList;
 
 /**
@@ -464,11 +464,73 @@ long getObjectCount(DfsReader ctx) throws IOException {
 		return dstbuf;
 	}
 
-	void copyPackAsIs(PackOutputStream out, boolean validate, DfsReader ctx)
+	void copyPackAsIs(PackOutputStream out, DfsReader ctx)
 			throws IOException {
-		// Pin the first window, this ensures the length is accurate.
-		ctx.pin(this, 0);
-		ctx.copyPackAsIs(this, length, validate, out);
+		// If the length hasn't been determined yet, pin to set it.
+		if (length == -1) {
+			ctx.pin(this, 0);
+			ctx.unpin();
+		}
+		if (cache.shouldCopyThroughCache(length))
+			copyPackThroughCache(out, ctx);
+		else
+			copyPackBypassCache(out, ctx);
+	}
+
+	private void copyPackThroughCache(PackOutputStream out, DfsReader ctx)
+			throws IOException {
+		long position = 12;
+		long remaining = length - (12 + 20);
+		while (0 < remaining) {
+			DfsBlock b = cache.getOrLoad(this, position, ctx);
+			int ptr = (int) (position - b.start);
+			int n = (int) Math.min(b.size() - ptr, remaining);
+			b.write(out, position, n);
+			position += n;
+			remaining -= n;
+		}
+	}
+
+	private long copyPackBypassCache(PackOutputStream out, DfsReader ctx)
+			throws IOException {
+		try (ReadableChannel rc = ctx.db.openFile(packDesc, PACK)) {
+			ByteBuffer buf = newCopyBuffer(out, rc);
+			if (ctx.getOptions().getStreamPackBufferSize() > 0)
+				rc.setReadAheadBytes(ctx.getOptions().getStreamPackBufferSize());
+			long position = 12;
+			long remaining = length - (12 + 20);
+			while (0 < remaining) {
+				DfsBlock b = cache.get(key, alignToBlock(position));
+				if (b != null) {
+					int ptr = (int) (position - b.start);
+					int n = (int) Math.min(b.size() - ptr, remaining);
+					b.write(out, position, n);
+					position += n;
+					remaining -= n;
+					rc.position(position);
+					continue;
+				}
+
+				buf.position(0);
+				int n = read(rc, buf);
+				if (n <= 0)
+					throw packfileIsTruncated();
+				else if (n > remaining)
+					n = (int) remaining;
+				out.write(buf.array(), 0, n);
+				position += n;
+				remaining -= n;
+			}
+			return position;
+		}
+	}
+
+	private ByteBuffer newCopyBuffer(PackOutputStream out, ReadableChannel rc) {
+		int bs = blockSize(rc);
+		byte[] copyBuf = out.getCopyBuffer();
+		if (bs > copyBuf.length)
+			copyBuf = new byte[bs];
+		return ByteBuffer.wrap(copyBuf, 0, bs);
 	}
 
 	void copyAsIs(PackOutputStream out, DfsObjectToPack src,
@@ -617,7 +679,7 @@ void copyAsIs(PackOutputStream out, DfsObjectToPack src,
 			// and we have it pinned.  Write this out without copying.
 			//
 			out.writeHeader(src, inflatedLength);
-			quickCopy.write(out, dataOffset, (int) dataLength, null);
+			quickCopy.write(out, dataOffset, (int) dataLength);
 
 		} else if (dataLength <= buf.length) {
 			// Tiny optimization: Lots of objects are very small deltas or
@@ -668,6 +730,12 @@ void setInvalid() {
 		invalid = true;
 	}
 
+	private IOException packfileIsTruncated() {
+		invalid = true;
+		return new IOException(MessageFormat.format(
+				JGitText.get().packfileIsTruncated, getPackName()));
+	}
+
 	private void readFully(long position, byte[] dstbuf, int dstoff, int cnt,
 			DfsReader ctx) throws IOException {
 		if (ctx.copy(this, position, dstbuf, dstoff, cnt) != cnt)
@@ -692,18 +760,8 @@ DfsBlock readOneBlock(long pos, DfsReader ctx)
 
 		ReadableChannel rc = ctx.db.openFile(packDesc, PACK);
 		try {
-			// If the block alignment is not yet known, discover it. Prefer the
-			// larger size from either the cache or the file itself.
-			int size = blockSize;
-			if (size == 0) {
-				size = rc.blockSize();
-				if (size <= 0)
-					size = cache.getBlockSize();
-				else if (size < cache.getBlockSize())
-					size = (cache.getBlockSize() / size) * size;
-				blockSize = size;
-				pos = (pos / size) * size;
-			}
+			int size = blockSize(rc);
+			pos = (pos / size) * size;
 
 			// If the size of the file is not yet known, try to discover it.
 			// Channels may choose to return -1 to indicate they don't
@@ -725,7 +783,7 @@ else if (size < cache.getBlockSize())
 
 			byte[] buf = new byte[size];
 			rc.position(pos);
-			int cnt = IO.read(rc, buf, 0, size);
+			int cnt = read(rc, ByteBuffer.wrap(buf, 0, size));
 			if (cnt != size) {
 				if (0 <= len) {
 					throw new EOFException(MessageFormat.format(
@@ -754,6 +812,30 @@ else if (size < cache.getBlockSize())
 		}
 	}
 
+	private int blockSize(ReadableChannel rc) {
+		// If the block alignment is not yet known, discover it. Prefer the
+		// larger size from either the cache or the file itself.
+		int size = blockSize;
+		if (size == 0) {
+			size = rc.blockSize();
+			if (size <= 0)
+				size = cache.getBlockSize();
+			else if (size < cache.getBlockSize())
+				size = (cache.getBlockSize() / size) * size;
+			blockSize = size;
+		}
+		return size;
+	}
+
+	private static int read(ReadableChannel rc, ByteBuffer buf)
+			throws IOException {
+		int n;
+		do {
+			n = rc.read(buf);
+		} while (0 < n && buf.hasRemaining());
+		return buf.position();
+	}
+
 	ObjectLoader load(DfsReader ctx, long pos)
 			throws IOException {
 		try {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReader.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReader.java
index 4cf7cbe..f5f3375 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReader.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReader.java
@@ -44,14 +44,10 @@
 
 package org.eclipse.jgit.internal.storage.dfs;
 
-import static org.eclipse.jgit.internal.storage.pack.PackExt.PACK;
 import static org.eclipse.jgit.lib.Constants.OBJECT_ID_LENGTH;
 
 import java.io.IOException;
-import java.security.MessageDigest;
-import java.text.MessageFormat;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Comparator;
@@ -65,7 +61,6 @@
 import org.eclipse.jgit.errors.IncorrectObjectTypeException;
 import org.eclipse.jgit.errors.MissingObjectException;
 import org.eclipse.jgit.errors.StoredObjectRepresentationNotAvailableException;
-import org.eclipse.jgit.internal.JGitText;
 import org.eclipse.jgit.internal.storage.file.BitmapIndexImpl;
 import org.eclipse.jgit.internal.storage.file.PackBitmapIndex;
 import org.eclipse.jgit.internal.storage.file.PackIndex;
@@ -81,7 +76,6 @@
 import org.eclipse.jgit.lib.AsyncObjectSizeQueue;
 import org.eclipse.jgit.lib.BitmapIndex;
 import org.eclipse.jgit.lib.BitmapIndex.BitmapBuilder;
-import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.InflaterCache;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.ObjectLoader;
@@ -498,9 +492,9 @@ public void writeObjects(PackOutputStream out, List<ObjectToPack> list)
 			out.writeObject(otp);
 	}
 
-	public void copyPackAsIs(PackOutputStream out, CachedPack pack,
-			boolean validate) throws IOException {
-		((DfsCachedPack) pack).copyAsIs(out, validate, this);
+	public void copyPackAsIs(PackOutputStream out, CachedPack pack)
+			throws IOException {
+		((DfsCachedPack) pack).copyAsIs(out, this);
 	}
 
 	/**
@@ -547,52 +541,6 @@ int copy(DfsPackFile pack, long position, byte[] dstbuf, int dstoff, int cnt)
 		return cnt - need;
 	}
 
-	void copyPackAsIs(DfsPackFile pack, long length, boolean validate,
-			PackOutputStream out) throws IOException {
-		MessageDigest md = null;
-		if (validate) {
-			md = Constants.newMessageDigest();
-			byte[] buf = out.getCopyBuffer();
-			pin(pack, 0);
-			if (block.copy(0, buf, 0, 12) != 12) {
-				pack.setInvalid();
-				throw new IOException(MessageFormat.format(
-						JGitText.get().packfileIsTruncated, pack.getPackName()));
-			}
-			md.update(buf, 0, 12);
-		}
-
-		long position = 12;
-		long remaining = length - (12 + 20);
-		while (0 < remaining) {
-			pin(pack, position);
-
-			int ptr = (int) (position - block.start);
-			int n = (int) Math.min(block.size() - ptr, remaining);
-			block.write(out, position, n, md);
-			position += n;
-			remaining -= n;
-		}
-
-		if (md != null) {
-			byte[] buf = new byte[20];
-			byte[] actHash = md.digest();
-
-			pin(pack, position);
-			if (block.copy(position, buf, 0, 20) != 20) {
-				pack.setInvalid();
-				throw new IOException(MessageFormat.format(
-						JGitText.get().packfileIsTruncated, pack.getPackName()));
-			}
-			if (!Arrays.equals(actHash, buf)) {
-				pack.setInvalid();
-				throw new IOException(MessageFormat.format(
-						JGitText.get().packfileCorruptionDetected,
-						pack.getPackDescription().getFileName(PACK)));
-			}
-		}
-	}
-
 	/**
 	 * Inflate a region of the pack starting at {@code position}.
 	 *
@@ -664,6 +612,10 @@ void pin(DfsPackFile pack, long position) throws IOException {
 		}
 	}
 
+	void unpin() {
+		block = null;
+	}
+
 	/** Release the current window cursor. */
 	@Override
 	public void release() {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReaderOptions.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReaderOptions.java
index 2a62547..8419807 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReaderOptions.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReaderOptions.java
@@ -46,6 +46,7 @@
 import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_CORE_SECTION;
 import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_DFS_SECTION;
 import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_DELTA_BASE_CACHE_LIMIT;
+import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_STREAM_BUFFER;
 import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_STREAM_FILE_TRESHOLD;
 
 import org.eclipse.jgit.lib.Config;
@@ -60,9 +61,10 @@ public class DfsReaderOptions {
 	public static final int MiB = 1024 * KiB;
 
 	private int deltaBaseCacheLimit;
-
 	private int streamFileThreshold;
 
+	private int streamPackBufferSize;
+
 	/** Create a default reader configuration. */
 	public DfsReaderOptions() {
 		setDeltaBaseCacheLimit(10 * MiB);
@@ -105,6 +107,27 @@ public DfsReaderOptions setStreamFileThreshold(final int newLimit) {
 	}
 
 	/**
+	 * @return number of bytes to use for buffering when streaming a pack file
+	 *         during copying. If 0 the block size of the pack is used.
+	 * @since 4.0
+	 */
+	public int getStreamPackBufferSize() {
+		return streamPackBufferSize;
+	}
+
+	/**
+	 * @param bufsz
+	 *            new buffer size in bytes for buffers used when streaming pack
+	 *            files during copying.
+	 * @return {@code this}
+	 * @since 4.0
+	 */
+	public DfsReaderOptions setStreamPackBufferSize(int bufsz) {
+		streamPackBufferSize = Math.max(0, bufsz);
+		return this;
+	}
+
+	/**
 	 * Update properties by setting fields from the configuration.
 	 * <p>
 	 * If a property is not defined in the configuration, then it is left
@@ -130,6 +153,12 @@ public DfsReaderOptions fromConfig(Config rc) {
 		sft = Math.min(sft, maxMem / 4); // don't use more than 1/4 of the heap
 		sft = Math.min(sft, Integer.MAX_VALUE); // cannot exceed array length
 		setStreamFileThreshold((int) sft);
+
+		setStreamPackBufferSize(rc.getInt(
+				CONFIG_CORE_SECTION,
+				CONFIG_DFS_SECTION,
+				CONFIG_KEY_STREAM_BUFFER,
+				getStreamPackBufferSize()));
 		return this;
 	}
 }
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 ae05536..8e7af0d 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
@@ -227,6 +227,10 @@ public long size() {
 		public int blockSize() {
 			return 0;
 		}
+
+		public void setReadAheadBytes(int b) {
+			// Unnecessary on a byte array.
+		}
 	}
 
 	private class MemRefDatabase extends DfsRefDatabase {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/ReadableChannel.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/ReadableChannel.java
index 5ec7079..240d552 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/ReadableChannel.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/ReadableChannel.java
@@ -100,4 +100,33 @@ public interface ReadableChannel extends ReadableByteChannel {
 	 *         not need to be a power of 2.
 	 */
 	public int blockSize();
+
+	/**
+	 * Recommend the channel maintain a read-ahead buffer.
+	 * <p>
+	 * A read-ahead buffer of approximately {@code bufferSize} in bytes may be
+	 * allocated and used by the channel to smooth out latency for read.
+	 * <p>
+	 * Callers can continue to read in smaller than {@code bufferSize} chunks.
+	 * With read-ahead buffering enabled read latency may fluctuate in a pattern
+	 * of one slower read followed by {@code (bufferSize / readSize) - 1} fast
+	 * reads satisfied by the read-ahead buffer. When summed up overall time to
+	 * read the same contiguous range should be lower than if read-ahead was not
+	 * enabled, as the implementation can combine reads to increase throughput.
+	 * <p>
+	 * To avoid unnecessary IO callers should only enable read-ahead if the
+	 * majority of the channel will be accessed in order.
+	 * <p>
+	 * Implementations may chose to read-ahead using asynchronous APIs or
+	 * background threads, or may simply aggregate reads using a buffer.
+	 * <p>
+	 * This read ahead stays in effect until the channel is closed or the buffer
+	 * size is set to 0.
+	 *
+	 * @param bufferSize
+	 *            requested size of the read ahead buffer, in bytes.
+	 * @throws IOException
+	 *             if the read ahead cannot be adjusted.
+	 */
+	public void setReadAheadBytes(int bufferSize) throws IOException;
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ByteArrayWindow.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ByteArrayWindow.java
index 863c553..dc720bc 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ByteArrayWindow.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ByteArrayWindow.java
@@ -46,7 +46,6 @@
 package org.eclipse.jgit.internal.storage.file;
 
 import java.io.IOException;
-import java.security.MessageDigest;
 import java.util.zip.CRC32;
 import java.util.zip.DataFormatException;
 import java.util.zip.Inflater;
@@ -84,12 +83,10 @@ void crc32(CRC32 out, long pos, int cnt) {
 	}
 
 	@Override
-	void write(PackOutputStream out, long pos, int cnt, MessageDigest digest)
+	void write(PackOutputStream out, long pos, int cnt)
 			throws IOException {
 		int ptr = (int) (pos - start);
 		out.write(array, ptr, cnt);
-		if (digest != null)
-			digest.update(array, ptr, cnt);
 	}
 
 	void check(Inflater inf, byte[] tmp, long pos, int cnt)
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ByteBufferWindow.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ByteBufferWindow.java
index 31925d2..05ddd69 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ByteBufferWindow.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ByteBufferWindow.java
@@ -47,7 +47,6 @@
 
 import java.io.IOException;
 import java.nio.ByteBuffer;
-import java.security.MessageDigest;
 import java.util.zip.DataFormatException;
 import java.util.zip.Inflater;
 
@@ -76,7 +75,7 @@ protected int copy(final int p, final byte[] b, final int o, int n) {
 	}
 
 	@Override
-	void write(PackOutputStream out, long pos, int cnt, MessageDigest digest)
+	void write(PackOutputStream out, long pos, int cnt)
 			throws IOException {
 		final ByteBuffer s = buffer.slice();
 		s.position((int) (pos - start));
@@ -86,8 +85,6 @@ void write(PackOutputStream out, long pos, int cnt, MessageDigest digest)
 			int n = Math.min(cnt, buf.length);
 			s.get(buf, 0, n);
 			out.write(buf, 0, n);
-			if (digest != null)
-				digest.update(buf, 0, n);
 			cnt -= n;
 		}
 	}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ByteWindow.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ByteWindow.java
index ab5eb7c..e774a14 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ByteWindow.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ByteWindow.java
@@ -45,7 +45,6 @@
 package org.eclipse.jgit.internal.storage.file;
 
 import java.io.IOException;
-import java.security.MessageDigest;
 import java.util.zip.DataFormatException;
 import java.util.zip.Inflater;
 
@@ -121,8 +120,8 @@ final int copy(long pos, byte[] dstbuf, int dstoff, int cnt) {
 	 */
 	protected abstract int copy(int pos, byte[] dstbuf, int dstoff, int cnt);
 
-	abstract void write(PackOutputStream out, long pos, int cnt,
-			MessageDigest md) throws IOException;
+	abstract void write(PackOutputStream out, long pos, int cnt)
+			throws IOException;
 
 	final int setInput(long pos, Inflater inf) throws DataFormatException {
 		return setInput((int) (pos - start), inf);
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 48335e4..338106f 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
@@ -618,22 +618,19 @@ private Map<String, Ref> getAllRefs() throws IOException {
 	 */
 	private Set<ObjectId> listNonHEADIndexObjects()
 			throws CorruptObjectException, IOException {
-		RevWalk revWalk = null;
 		try {
 			if (repo.getIndexFile() == null)
 				return Collections.emptySet();
 		} catch (NoWorkTreeException e) {
 			return Collections.emptySet();
 		}
-		TreeWalk treeWalk = new TreeWalk(repo);
-		try {
+		try (TreeWalk treeWalk = new TreeWalk(repo)) {
 			treeWalk.addTree(new DirCacheIterator(repo.readDirCache()));
 			ObjectId headID = repo.resolve(Constants.HEAD);
 			if (headID != null) {
-				revWalk = new RevWalk(repo);
-				treeWalk.addTree(revWalk.parseTree(headID));
-				revWalk.dispose();
-				revWalk = null;
+				try (RevWalk revWalk = new RevWalk(repo)) {
+					treeWalk.addTree(revWalk.parseTree(headID));
+				}
 			}
 
 			treeWalk.setFilter(TreeFilter.ANY_DIFF);
@@ -662,10 +659,6 @@ private Set<ObjectId> listNonHEADIndexObjects()
 				}
 			}
 			return ret;
-		} finally {
-			if (revWalk != null)
-				revWalk.dispose();
-			treeWalk.release();
 		}
 	}
 
@@ -689,8 +682,9 @@ public int compare(PackExt o1, PackExt o2) {
 					}
 
 				});
-		PackWriter pw = new PackWriter((pconfig == null) ? new PackConfig(repo) : pconfig, repo.newObjectReader());
-		try {
+		try (PackWriter pw = new PackWriter(
+				(pconfig == null) ? new PackConfig(repo) : pconfig,
+				repo.newObjectReader())) {
 			// prepare the PackWriter
 			pw.setDeltaBaseAsOffset(true);
 			pw.setReuseDeltaCommits(false);
@@ -810,7 +804,6 @@ public int compare(PackExt o1, PackExt o2) {
 			}
 			return repo.getObjectDatabase().openPack(realPack);
 		} finally {
-			pw.release();
 			if (tmpPack != null && tmpPack.exists())
 				tmpPack.delete();
 			for (File tmpExt : tmpExts.values()) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LocalCachedPack.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LocalCachedPack.java
index b70ebcf..fd9dcda 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LocalCachedPack.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LocalCachedPack.java
@@ -79,10 +79,10 @@ public long getObjectCount() throws IOException {
 		return cnt;
 	}
 
-	void copyAsIs(PackOutputStream out, boolean validate, WindowCursor wc)
+	void copyAsIs(PackOutputStream out, WindowCursor wc)
 			throws IOException {
 		for (PackFile pack : getPacks())
-			pack.copyPackAsIs(out, validate, wc);
+			pack.copyPackAsIs(out, wc);
 	}
 
 	@Override
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 687408e..796109a 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
@@ -114,6 +114,8 @@ public class ObjectDirectory extends FileObjectDatabase {
 	/** Maximum number of candidates offered as resolutions of abbreviation. */
 	private static final int RESOLVE_ABBREV_LIMIT = 256;
 
+	private static final String STALE_FILE_HANDLE_MSG = "stale file handle"; //$NON-NLS-1$
+
 	private final Config config;
 
 	private final File objects;
@@ -554,22 +556,35 @@ void selectObjectRepresentation(PackWriter packer, ObjectToPack otp,
 	}
 
 	private void handlePackError(IOException e, PackFile p) {
-		String tmpl;
+		String warnTmpl = null;
 		if ((e instanceof CorruptObjectException)
 				|| (e instanceof PackInvalidException)) {
-			tmpl = JGitText.get().corruptPack;
+			warnTmpl = JGitText.get().corruptPack;
 			// Assume the pack is corrupted, and remove it from the list.
 			removePack(p);
 		} else if (e instanceof FileNotFoundException) {
-			tmpl = JGitText.get().packWasDeleted;
+			warnTmpl = JGitText.get().packWasDeleted;
 			removePack(p);
+		} else if (e.getMessage() != null
+				&& e.getMessage().toLowerCase().contains(STALE_FILE_HANDLE_MSG)) {
+			warnTmpl = JGitText.get().packHandleIsStale;
+			removePack(p);
+		}
+		if (warnTmpl != null) {
+			if (LOG.isDebugEnabled()) {
+				LOG.debug(MessageFormat.format(warnTmpl,
+						p.getPackFile().getAbsolutePath()), e);
+			} else {
+				LOG.warn(MessageFormat.format(warnTmpl,
+						p.getPackFile().getAbsolutePath()));
+			}
 		} else {
-			tmpl = JGitText.get().exceptionWhileReadingPack;
 			// Don't remove the pack from the list, as the error may be
 			// transient.
+			LOG.error(MessageFormat.format(
+					JGitText.get().exceptionWhileReadingPack, p.getPackFile()
+							.getAbsolutePath()), e);
 		}
-		LOG.error(MessageFormat.format(tmpl,
-				p.getPackFile().getAbsolutePath()), e);
 	}
 
 	@Override
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectoryInserter.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectoryInserter.java
index 812c899..eb87460 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectoryInserter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectoryInserter.java
@@ -55,10 +55,12 @@
 import java.nio.channels.Channels;
 import java.security.DigestOutputStream;
 import java.security.MessageDigest;
+import java.text.MessageFormat;
 import java.util.zip.Deflater;
 import java.util.zip.DeflaterOutputStream;
 
 import org.eclipse.jgit.errors.ObjectWritingException;
+import org.eclipse.jgit.internal.JGitText;
 import org.eclipse.jgit.lib.Config;
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.ObjectId;
@@ -123,7 +125,8 @@ private ObjectId insertOneObject(final File tmp, final ObjectId id)
 		}
 
 		final File dst = db.fileFor(id);
-		throw new ObjectWritingException("Unable to create new object: " + dst);
+		throw new ObjectWritingException(MessageFormat
+				.format(JGitText.get().unableToCreateNewObject, dst));
 	}
 
 	@Override
@@ -242,7 +245,7 @@ DeflaterOutputStream compress(final OutputStream out) {
 	}
 
 	private static EOFException shortInput(long missing) {
-		return new EOFException("Input did not match supplied length. "
-				+ missing + " bytes are missing.");
+		return new EOFException(MessageFormat.format(
+				JGitText.get().inputDidntMatchLength, Long.valueOf(missing)));
 	}
 }
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 eb22938..75c361e 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
@@ -344,11 +344,11 @@ ObjectId findObjectForOffset(final long offset) throws IOException {
 		return dstbuf;
 	}
 
-	void copyPackAsIs(PackOutputStream out, boolean validate, WindowCursor curs)
+	void copyPackAsIs(PackOutputStream out, WindowCursor curs)
 			throws IOException {
 		// Pin the first window, this ensures the length is accurate.
 		curs.pin(this, 0);
-		curs.copyPackAsIs(this, length, validate, out);
+		curs.copyPackAsIs(this, length, out);
 	}
 
 	final void copyAsIs(PackOutputStream out, LocalObjectToPack src,
@@ -362,6 +362,7 @@ final void copyAsIs(PackOutputStream out, LocalObjectToPack src,
 		}
 	}
 
+	@SuppressWarnings("null")
 	private void copyAsIs2(PackOutputStream out, LocalObjectToPack src,
 			boolean validate, WindowCursor curs) throws IOException,
 			StoredObjectRepresentationNotAvailableException {
@@ -501,7 +502,7 @@ private void copyAsIs2(PackOutputStream out, LocalObjectToPack src,
 			// and we have it pinned.  Write this out without copying.
 			//
 			out.writeHeader(src, inflatedLength);
-			quickCopy.write(out, dataOffset, (int) dataLength, null);
+			quickCopy.write(out, dataOffset, (int) dataLength);
 
 		} else if (dataLength <= buf.length) {
 			// Tiny optimization: Lots of objects are very small deltas or
@@ -703,6 +704,7 @@ private void onOpenPack() throws IOException {
 					, getPackFile()));
 	}
 
+	@SuppressWarnings("null")
 	ObjectLoader load(final WindowCursor curs, long pos)
 			throws IOException, LargeObjectException {
 		try {
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 70cf20f..cb8c91a 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
@@ -232,7 +232,7 @@ public long findCRC32(AnyObjectId objId) throws MissingObjectException {
 		final int levelOne = objId.getFirstByte();
 		final int levelTwo = binarySearchLevelTwo(objId, levelOne);
 		if (levelTwo == -1)
-			throw new MissingObjectException(objId.copy(), "unknown");
+			throw new MissingObjectException(objId.copy(), "unknown"); //$NON-NLS-1$
 		return NB.decodeUInt32(crc32[levelOne], levelTwo << 2);
 	}
 
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/WindowCursor.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/WindowCursor.java
index 85c3c74..21d6cd2 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/WindowCursor.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/WindowCursor.java
@@ -45,9 +45,6 @@
 package org.eclipse.jgit.internal.storage.file;
 
 import java.io.IOException;
-import java.security.MessageDigest;
-import java.text.MessageFormat;
-import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashSet;
@@ -59,7 +56,6 @@
 import org.eclipse.jgit.errors.IncorrectObjectTypeException;
 import org.eclipse.jgit.errors.MissingObjectException;
 import org.eclipse.jgit.errors.StoredObjectRepresentationNotAvailableException;
-import org.eclipse.jgit.internal.JGitText;
 import org.eclipse.jgit.internal.storage.pack.CachedPack;
 import org.eclipse.jgit.internal.storage.pack.ObjectReuseAsIs;
 import org.eclipse.jgit.internal.storage.pack.ObjectToPack;
@@ -232,27 +228,13 @@ int copy(final PackFile pack, long position, final byte[] dstbuf,
 		return cnt - need;
 	}
 
-	public void copyPackAsIs(PackOutputStream out, CachedPack pack,
-			boolean validate) throws IOException {
-		((LocalCachedPack) pack).copyAsIs(out, validate, this);
+	public void copyPackAsIs(PackOutputStream out, CachedPack pack)
+			throws IOException {
+		((LocalCachedPack) pack).copyAsIs(out, this);
 	}
 
-	void copyPackAsIs(final PackFile pack, final long length, boolean validate,
+	void copyPackAsIs(final PackFile pack, final long length,
 			final PackOutputStream out) throws IOException {
-		MessageDigest md = null;
-		if (validate) {
-			md = Constants.newMessageDigest();
-			byte[] buf = out.getCopyBuffer();
-			pin(pack, 0);
-			if (window.copy(0, buf, 0, 12) != 12) {
-				pack.setInvalid();
-				throw new IOException(MessageFormat.format(
-						JGitText.get().packfileIsTruncated, pack.getPackFile()
-								.getPath()));
-			}
-			md.update(buf, 0, 12);
-		}
-
 		long position = 12;
 		long remaining = length - (12 + 20);
 		while (0 < remaining) {
@@ -260,29 +242,10 @@ void copyPackAsIs(final PackFile pack, final long length, boolean validate,
 
 			int ptr = (int) (position - window.start);
 			int n = (int) Math.min(window.size() - ptr, remaining);
-			window.write(out, position, n, md);
+			window.write(out, position, n);
 			position += n;
 			remaining -= n;
 		}
-
-		if (md != null) {
-			byte[] buf = new byte[20];
-			byte[] actHash = md.digest();
-
-			pin(pack, position);
-			if (window.copy(position, buf, 0, 20) != 20) {
-				pack.setInvalid();
-				throw new IOException(MessageFormat.format(
-						JGitText.get().packfileIsTruncated, pack.getPackFile()
-								.getPath()));
-			}
-			if (!Arrays.equals(actHash, buf)) {
-				pack.setInvalid();
-				throw new IOException(MessageFormat.format(
-						JGitText.get().packfileCorruptionDetected, pack
-								.getPackFile().getPath()));
-			}
-		}
 	}
 
 	/**
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/ObjectReuseAsIs.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/ObjectReuseAsIs.java
index 00b6b65..2e5d599 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/ObjectReuseAsIs.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/ObjectReuseAsIs.java
@@ -209,16 +209,11 @@ public void copyObjectAsIs(PackOutputStream out, ObjectToPack otp,
 	 *            stream to append the pack onto.
 	 * @param pack
 	 *            the cached pack to send.
-	 * @param validate
-	 *            if true the representation must be validated and not be
-	 *            corrupt before being reused. If false, validation may be
-	 *            skipped as it will be performed elsewhere in the processing
-	 *            pipeline.
 	 * @throws IOException
 	 *             the pack cannot be read, or stream did not accept a write.
 	 */
-	public abstract void copyPackAsIs(PackOutputStream out, CachedPack pack,
-			boolean validate) throws IOException;
+	public abstract void copyPackAsIs(PackOutputStream out, CachedPack pack)
+			throws IOException;
 
 	/**
 	 * Obtain the available cached packs that match the bitmap and update
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 510538d..6d0c8e6 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
@@ -1048,7 +1048,7 @@ public void writePack(ProgressMonitor compressMonitor,
 				stats.reusedObjects += pack.getObjectCount();
 				stats.reusedDeltas += deltaCnt;
 				stats.totalDeltas += deltaCnt;
-				reuseSupport.copyPackAsIs(out, pack, reuseValidate);
+				reuseSupport.copyPackAsIs(out, pack);
 			}
 			writeChecksum(out);
 			out.flush();
@@ -1866,7 +1866,7 @@ private void findObjectsToPackUsingBitmaps(
 				false);
 		BitmapBuilder needBitmap = wantBitmap.andNot(haveBitmap);
 
-		if (useCachedPacks && reuseSupport != null
+		if (useCachedPacks && reuseSupport != null && !reuseValidate
 				&& (excludeInPacks == null || excludeInPacks.length == 0))
 			cachedPacks.addAll(
 					reuseSupport.getCachedPacksAndUpdate(needBitmap));
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 a0197d0..cbb2f5b 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BlobBasedConfig.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BlobBasedConfig.java
@@ -104,11 +104,8 @@ public BlobBasedConfig(Config base, Repository db, AnyObjectId objectId)
 	private static byte[] read(Repository db, AnyObjectId blobId)
 			throws MissingObjectException, IncorrectObjectTypeException,
 			IOException {
-		ObjectReader or = db.newObjectReader();
-		try {
+		try (ObjectReader or = db.newObjectReader()) {
 			return read(or, blobId);
-		} finally {
-			or.release();
 		}
 	}
 
@@ -146,15 +143,12 @@ public BlobBasedConfig(Config base, Repository db, AnyObjectId treeish,
 	private static byte[] read(Repository db, AnyObjectId treeish, String path)
 			throws MissingObjectException, IncorrectObjectTypeException,
 			IOException {
-		ObjectReader or = db.newObjectReader();
-		try {
+		try (ObjectReader or = db.newObjectReader()) {
 			TreeWalk tree = TreeWalk.forPath(or, path, asTree(or, treeish));
 			if (tree == null)
 				throw new FileNotFoundException(MessageFormat.format(JGitText
 						.get().entryNotFoundByPath, path));
 			return read(or, tree.getObjectId(0));
-		} finally {
-			or.release();
 		}
 	}
 
@@ -168,6 +162,8 @@ private static AnyObjectId asTree(ObjectReader or, AnyObjectId treeish)
 				&& ((RevCommit) treeish).getTree() != null)
 			return ((RevCommit) treeish).getTree();
 
-		return new RevWalk(or).parseTree(treeish).getId();
+		try (RevWalk rw = new RevWalk(or)) {
+			return rw.parseTree(treeish).getId();
+		}
 	}
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java
index 8a2080b..a89bcee 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java
@@ -299,4 +299,16 @@ public class ConfigConstants {
 	 * @since 3.3
 	 */
 	public static final String CONFIG_KEY_PRUNE = "prune";
+
+	/**
+	 * The "streamBuffer" key
+	 * @since 4.0
+	 */
+	public static final String CONFIG_KEY_STREAM_BUFFER = "streamBuffer";
+
+	/**
+	 * The "streamRatio" key
+	 * @since 4.0
+	 */
+	public static final String CONFIG_KEY_STREAM_RATIO = "streamRatio";
 }
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 8435c9a..359b592 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectChecker.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectChecker.java
@@ -104,6 +104,8 @@ public class ObjectChecker {
 	private final MutableInteger ptrout = new MutableInteger();
 
 	private boolean allowZeroMode;
+
+	private boolean allowInvalidPersonIdent;
 	private boolean windows;
 	private boolean macosx;
 
@@ -125,6 +127,22 @@ public ObjectChecker setAllowLeadingZeroFileMode(boolean allow) {
 	}
 
 	/**
+	 * Enable accepting invalid author, committer and tagger identities.
+	 * <p>
+	 * Some broken Git versions/libraries allowed users to create commits and
+	 * tags with invalid formatting between the name, email and timestamp.
+	 *
+	 * @param allow
+	 *            if true accept invalid person identity strings.
+	 * @return {@code this}.
+	 * @since 4.0
+	 */
+	public ObjectChecker setAllowInvalidPersonIdent(boolean allow) {
+		allowInvalidPersonIdent = allow;
+		return this;
+	}
+
+	/**
 	 * Restrict trees to only names legal on Windows platforms.
 	 * <p>
 	 * Also rejects any mixed case forms of reserved names ({@code .git}).
@@ -198,6 +216,9 @@ private int id(final byte[] raw, final int ptr) {
 	}
 
 	private int personIdent(final byte[] raw, int ptr) {
+		if (allowInvalidPersonIdent)
+			return nextLF(raw, ptr) - 1;
+
 		final int emailB = nextLF(raw, ptr, '<');
 		if (emailB == ptr || raw[emailB - 1] != '<')
 			return -1;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDatabase.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDatabase.java
index 0cc51d1..2abd6da 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDatabase.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDatabase.java
@@ -120,11 +120,8 @@ public void create() throws IOException {
 	 *             the object store cannot be accessed.
 	 */
 	public boolean has(final AnyObjectId objectId) throws IOException {
-		final ObjectReader or = newReader();
-		try {
+		try (final ObjectReader or = newReader()) {
 			return or.has(objectId);
-		} finally {
-			or.release();
 		}
 	}
 
@@ -172,11 +169,8 @@ public ObjectLoader open(final AnyObjectId objectId)
 	public ObjectLoader open(AnyObjectId objectId, int typeHint)
 			throws MissingObjectException, IncorrectObjectTypeException,
 			IOException {
-		final ObjectReader or = newReader();
-		try {
+		try (final ObjectReader or = newReader()) {
 			return or.open(objectId, typeHint);
-		} finally {
-			or.release();
 		}
 	}
 
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/merge/EolAwareOutputStream.java b/org.eclipse.jgit/src/org/eclipse/jgit/merge/EolAwareOutputStream.java
new file mode 100644
index 0000000..1ddac18
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/EolAwareOutputStream.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2014, André de Oliveira <andre.oliveira@liferay.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.merge;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * An output stream which is aware of newlines and can be asked to begin a new
+ * line if not already in one.
+ */
+class EolAwareOutputStream extends OutputStream {
+	private final OutputStream out;
+
+	private boolean bol = true;
+
+	/**
+	 * Initialize a new EOL aware stream.
+	 *
+	 * @param out
+	 *            stream to output all writes to.
+	 */
+	EolAwareOutputStream(OutputStream out) {
+		this.out = out;
+	}
+
+	/**
+	 * Begin a new line if not already in one.
+	 *
+	 * @exception IOException
+	 *                if an I/O error occurs.
+	 */
+	void beginln() throws IOException {
+		if (!bol)
+			write('\n');
+	}
+
+	/** @return true if a new line has just begun. */
+	boolean isBeginln() {
+		return bol;
+	}
+
+	@Override
+	public void write(int val) throws IOException {
+		out.write(val);
+		bol = (val == '\n');
+	}
+
+	@Override
+	public void write(byte[] buf, int pos, int cnt) throws IOException {
+		if (cnt > 0) {
+			out.write(buf, pos, cnt);
+			bol = (buf[pos + (cnt - 1)] == '\n');
+		}
+	}
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeFormatter.java b/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeFormatter.java
index eed1dcf..977f953 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeFormatter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeFormatter.java
@@ -49,7 +49,6 @@
 import java.util.List;
 
 import org.eclipse.jgit.diff.RawText;
-import org.eclipse.jgit.merge.MergeChunk.ConflictState;
 
 /**
  * A class to convert merge results into a Git conformant textual presentation
@@ -78,47 +77,7 @@ public class MergeFormatter {
 	 */
 	public void formatMerge(OutputStream out, MergeResult<RawText> res,
 			List<String> seqName, String charsetName) throws IOException {
-		String lastConflictingName = null; // is set to non-null whenever we are
-		// in a conflict
-		boolean threeWayMerge = (res.getSequences().size() == 3);
-		for (MergeChunk chunk : res) {
-			RawText seq = res.getSequences().get(chunk.getSequenceIndex());
-			if (lastConflictingName != null
-					&& chunk.getConflictState() != ConflictState.NEXT_CONFLICTING_RANGE) {
-				// found the end of an conflict
-				out.write((">>>>>>> " + lastConflictingName + "\n").getBytes(charsetName)); //$NON-NLS-1$ //$NON-NLS-2$
-				lastConflictingName = null;
-			}
-			if (chunk.getConflictState() == ConflictState.FIRST_CONFLICTING_RANGE) {
-				// found the start of an conflict
-				out.write(("<<<<<<< " + seqName.get(chunk.getSequenceIndex()) + //$NON-NLS-1$
-						"\n").getBytes(charsetName)); //$NON-NLS-1$
-				lastConflictingName = seqName.get(chunk.getSequenceIndex());
-			} else if (chunk.getConflictState() == ConflictState.NEXT_CONFLICTING_RANGE) {
-				// found another conflicting chunk
-
-				/*
-				 * In case of a non-three-way merge I'll add the name of the
-				 * conflicting chunk behind the equal signs. I also append the
-				 * name of the last conflicting chunk after the ending
-				 * greater-than signs. If somebody knows a better notation to
-				 * present non-three-way merges - feel free to correct here.
-				 */
-				lastConflictingName = seqName.get(chunk.getSequenceIndex());
-				out.write((threeWayMerge ? "=======\n" : "======= " //$NON-NLS-1$ //$NON-NLS-2$
-						+ lastConflictingName + "\n").getBytes(charsetName)); //$NON-NLS-1$
-			}
-			// the lines with conflict-metadata are written. Now write the chunk
-			for (int i = chunk.getBegin(); i < chunk.getEnd(); i++) {
-				seq.writeLine(out, i);
-				out.write('\n');
-			}
-		}
-		// one possible leftover: if the merge result ended with a conflict we
-		// have to close the last conflict here
-		if (lastConflictingName != null) {
-			out.write((">>>>>>> " + lastConflictingName + "\n").getBytes(charsetName)); //$NON-NLS-1$ //$NON-NLS-2$
-		}
+		new MergeFormatterPass(out, res, seqName, charsetName).formatMerge();
 	}
 
 	/**
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeFormatterPass.java b/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeFormatterPass.java
new file mode 100644
index 0000000..0345921
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeFormatterPass.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2009, Christian Halstrick <christian.halstrick@sap.com>
+ * Copyright (C) 2014, André de Oliveira <andre.oliveira@liferay.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.merge;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.List;
+
+import org.eclipse.jgit.diff.RawText;
+import org.eclipse.jgit.merge.MergeChunk.ConflictState;
+
+class MergeFormatterPass {
+
+	private final EolAwareOutputStream out;
+
+	private final MergeResult<RawText> res;
+
+	private final List<String> seqName;
+
+	private final String charsetName;
+
+	private final boolean threeWayMerge;
+
+	private String lastConflictingName; // is set to non-null whenever we are in
+										// a conflict
+
+	MergeFormatterPass(OutputStream out, MergeResult<RawText> res, List<String> seqName,
+			String charsetName) {
+		this.out = new EolAwareOutputStream(out);
+		this.res = res;
+		this.seqName = seqName;
+		this.charsetName = charsetName;
+		this.threeWayMerge = (res.getSequences().size() == 3);
+	}
+
+	void formatMerge() throws IOException {
+		boolean missingNewlineAtEnd = false;
+		for (MergeChunk chunk : res) {
+			RawText seq = res.getSequences().get(chunk.getSequenceIndex());
+			writeConflictMetadata(chunk);
+			// the lines with conflict-metadata are written. Now write the chunk
+			for (int i = chunk.getBegin(); i < chunk.getEnd(); i++)
+				writeLine(seq, i);
+			missingNewlineAtEnd = seq.isMissingNewlineAtEnd();
+		}
+		// one possible leftover: if the merge result ended with a conflict we
+		// have to close the last conflict here
+		if (lastConflictingName != null)
+			writeConflictEnd();
+		if (!missingNewlineAtEnd)
+			out.beginln();
+	}
+
+	private void writeConflictMetadata(MergeChunk chunk) throws IOException {
+		if (lastConflictingName != null
+				&& chunk.getConflictState() != ConflictState.NEXT_CONFLICTING_RANGE) {
+			// found the end of an conflict
+			writeConflictEnd();
+		}
+		if (chunk.getConflictState() == ConflictState.FIRST_CONFLICTING_RANGE) {
+			// found the start of an conflict
+			writeConflictStart(chunk);
+		} else if (chunk.getConflictState() == ConflictState.NEXT_CONFLICTING_RANGE) {
+			// found another conflicting chunk
+			writeConflictChange(chunk);
+		}
+	}
+
+	private void writeConflictEnd() throws IOException {
+		writeln(">>>>>>> " + lastConflictingName); //$NON-NLS-1$
+		lastConflictingName = null;
+	}
+
+	private void writeConflictStart(MergeChunk chunk) throws IOException {
+		lastConflictingName = seqName.get(chunk.getSequenceIndex());
+		writeln("<<<<<<< " + lastConflictingName); //$NON-NLS-1$
+	}
+
+	private void writeConflictChange(MergeChunk chunk) throws IOException {
+		/*
+		 * In case of a non-three-way merge I'll add the name of the conflicting
+		 * chunk behind the equal signs. I also append the name of the last
+		 * conflicting chunk after the ending greater-than signs. If somebody
+		 * knows a better notation to present non-three-way merges - feel free
+		 * to correct here.
+		 */
+		lastConflictingName = seqName.get(chunk.getSequenceIndex());
+		writeln(threeWayMerge ? "=======" : "======= " //$NON-NLS-1$ //$NON-NLS-2$
+				+ lastConflictingName);
+	}
+
+	private void writeln(String s) throws IOException {
+		out.beginln();
+		out.write((s + "\n").getBytes(charsetName)); //$NON-NLS-1$
+	}
+
+	private void writeLine(RawText seq, int i) throws IOException {
+		out.beginln();
+		seq.writeLine(out, i);
+		// still BOL? It was a blank line. But writeLine won't lf, so we do.
+		if (out.isBeginln())
+			out.write('\n');
+	}
+}
\ No newline at end of file
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 d786a18..a76dd35 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/Merger.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/Merger.java
@@ -125,9 +125,9 @@ public ObjectInserter getObjectInserter() {
 	 *            repository instance returned by {@link #getRepository()}.
 	 */
 	public void setObjectInserter(ObjectInserter oi) {
-		walk.release();
-		reader.release();
-		inserter.release();
+		walk.close();
+		reader.close();
+		inserter.close();
 		inserter = oi;
 		reader = oi.newReader();
 		walk = new RevWalk(reader);
@@ -206,8 +206,8 @@ public boolean merge(final boolean flush, final AnyObjectId... tips)
 			return ok;
 		} finally {
 			if (flush)
-				inserter.release();
-			reader.release();
+				inserter.close();
+			reader.close();
 		}
 	}
 
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 85cdb76..36ffe7a 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/RecursiveMerger.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/RecursiveMerger.java
@@ -68,6 +68,8 @@
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.revwalk.RevCommit;
 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;
 
@@ -194,10 +196,12 @@ protected RevCommit getBaseCommit(RevCommit a, RevCommit b, int callDepth)
 							Integer.valueOf(MAX_BASES), a.name(), b.name(),
 									Integer.valueOf(baseCommits.size())));
 				parents.add(nextBase);
-				if (mergeTrees(
-						openTree(getBaseCommit(currentBase, nextBase,
-								callDepth + 1).getTree()),
-						currentBase.getTree(), nextBase.getTree(), true))
+				RevCommit bc = getBaseCommit(currentBase, nextBase,
+						callDepth + 1);
+				AbstractTreeIterator bcTree = (bc == null) ? new EmptyTreeIterator()
+						: openTree(bc.getTree());
+				if (mergeTrees(bcTree, currentBase.getTree(),
+						nextBase.getTree(), true))
 					currentBase = createCommitForTree(resultTree, parents);
 				else
 					throw new NoMergeBaseException(
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 8e70f57..953d3a2 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java
@@ -47,12 +47,14 @@
 import static org.eclipse.jgit.lib.Constants.CHARACTER_ENCODING;
 import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
 
+import java.io.BufferedOutputStream;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.OutputStream;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
@@ -791,25 +793,25 @@ private File writeMergedFile(MergeResult<RawText> result)
 		File parentFolder = of.getParentFile();
 		if (!fs.exists(parentFolder))
 			parentFolder.mkdirs();
-		FileOutputStream fos = new FileOutputStream(of);
-		try {
-			new MergeFormatter().formatMerge(fos, result,
+		try (OutputStream os = new BufferedOutputStream(
+				new FileOutputStream(of))) {
+			new MergeFormatter().formatMerge(os, result,
 					Arrays.asList(commitNames), CHARACTER_ENCODING);
-		} finally {
-			fos.close();
 		}
 		return of;
 	}
 
 	private ObjectId insertMergeResult(MergeResult<RawText> result)
 			throws IOException {
-		TemporaryBuffer.LocalFile buf = new TemporaryBuffer.LocalFile(10 << 20);
+		TemporaryBuffer.LocalFile buf = new TemporaryBuffer.LocalFile(
+				db.getDirectory(), 10 << 20);
 		try {
 			new MergeFormatter().formatMerge(buf, result,
 					Arrays.asList(commitNames), CHARACTER_ENCODING);
 			buf.close();
-			return getObjectInserter().insert(OBJ_BLOB, buf.length(),
-					buf.openInputStream());
+			try (InputStream in = buf.openInputStream()) {
+				return getObjectInserter().insert(OBJ_BLOB, buf.length(), in);
+			}
 		} finally {
 			buf.destroy();
 		}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/notes/NoteMapMerger.java b/org.eclipse.jgit/src/org/eclipse/jgit/notes/NoteMapMerger.java
index 0614476..19ec1a1 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/notes/NoteMapMerger.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/notes/NoteMapMerger.java
@@ -135,8 +135,8 @@ public NoteMap merge(NoteMap base, NoteMap ours, NoteMap theirs)
 			inserter.flush();
 			return NoteMap.newMap(mergedBucket, reader);
 		} finally {
-			reader.release();
-			inserter.release();
+			reader.close();
+			inserter.close();
 		}
 	}
 
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/ObjectWalk.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/ObjectWalk.java
index b73ccb1..a0af067 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/ObjectWalk.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/ObjectWalk.java
@@ -232,7 +232,7 @@ public void markUninteresting(RevObject o) throws MissingObjectException,
 		}
 
 		if (o instanceof RevCommit)
-			markUninteresting((RevCommit) o);
+			super.markUninteresting((RevCommit) o);
 		else if (o instanceof RevTree)
 			markTreeUninteresting((RevTree) o);
 		else
@@ -242,18 +242,6 @@ else if (o instanceof RevTree)
 			addObject(o);
 	}
 
-	@Override
-	public void markUninteresting(RevCommit c) throws MissingObjectException,
-			IncorrectObjectTypeException, IOException {
-		super.markUninteresting(c);
-		try {
-			markTreeUninteresting(c.getTree());
-		} catch (MissingObjectException e) {
-			// we don't care if the tree of the commit does not exist locally
-		}
-	}
-
-	@Override
 	public void sort(RevSort s) {
 		super.sort(s);
 		boundary = hasRevSort(RevSort.BOUNDARY);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseConnection.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseConnection.java
index 1072d58..59ff1bd 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseConnection.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseConnection.java
@@ -65,6 +65,8 @@
 public abstract class BaseConnection implements Connection {
 	private Map<String, Ref> advertisedRefs = Collections.emptyMap();
 
+	private String peerUserAgent;
+
 	private boolean startedOperation;
 
 	private Writer messageWriter;
@@ -85,6 +87,28 @@ public String getMessages() {
 		return messageWriter != null ? messageWriter.toString() : ""; //$NON-NLS-1$
 	}
 
+	/**
+	 * User agent advertised by the remote server.
+	 *
+	 * @return agent (version of Git) running on the remote server. Null if the
+	 *         server does not advertise this version.
+	 * @since 4.0
+	 */
+	public String getPeerUserAgent() {
+		return peerUserAgent;
+	}
+
+	/**
+	 * Remember the remote peer's agent.
+	 *
+	 * @param agent
+	 *            remote peer agent string.
+	 * @since 4.0
+	 */
+	protected void setPeerUserAgent(String agent) {
+		peerUserAgent = agent;
+	}
+
 	public abstract void close();
 
 	/**
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 8f825ea..7f9cec7 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackConnection.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackConnection.java
@@ -46,6 +46,8 @@
 
 package org.eclipse.jgit.transport;
 
+import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_AGENT;
+
 import java.io.EOFException;
 import java.io.IOException;
 import java.io.InputStream;
@@ -275,6 +277,18 @@ protected boolean wantCapability(final StringBuilder b, final String option) {
 		return true;
 	}
 
+	protected void addUserAgentCapability(StringBuilder b) {
+		String a = UserAgent.get();
+		if (a != null && UserAgent.hasAgent(remoteCapablities)) {
+			b.append(' ').append(OPTION_AGENT).append('=').append(a);
+		}
+	}
+
+	@Override
+	public String getPeerUserAgent() {
+		return UserAgent.getAgent(remoteCapablities, super.getPeerUserAgent());
+	}
+
 	private PackProtocolException duplicateAdvertisement(final String name) {
 		return new PackProtocolException(uri, MessageFormat.format(JGitText.get().duplicateAdvertisementsOf, name));
 	}
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 f907891..a6fc633 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackFetchConnection.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackFetchConnection.java
@@ -377,7 +377,7 @@ protected void doFetch(final ProgressMonitor monitor,
 	@Override
 	public void close() {
 		if (walk != null)
-			walk.release();
+			walk.close();
 		super.close();
 	}
 
@@ -521,6 +521,7 @@ else if (wantCapability(line, OPTION_SIDE_BAND))
 					OPTION_MULTI_ACK_DETAILED));
 		}
 
+		addUserAgentCapability(line);
 		return line.toString();
 	}
 
@@ -753,16 +754,13 @@ private void receivePack(final ProgressMonitor monitor,
 			input = new SideBandInputStream(input, monitor, getMessageWriter(),
 					outputStream);
 
-		ObjectInserter ins = local.newObjectInserter();
-		try {
+		try (ObjectInserter ins = local.newObjectInserter()) {
 			PackParser parser = ins.newPackParser(input);
 			parser.setAllowThin(thinPack);
 			parser.setObjectChecker(transport.getObjectChecker());
 			parser.setLockMessage(lockMessage);
 			packLock = parser.parse(monitor);
 			ins.flush();
-		} finally {
-			ins.release();
 		}
 	}
 
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 e367ab4..1e5b8e8 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackPushConnection.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackPushConnection.java
@@ -268,6 +268,7 @@ private String enableCapabilities(final ProgressMonitor monitor,
 					outputStream);
 			pckIn = new PacketLineIn(in);
 		}
+		addUserAgentCapability(line);
 
 		if (line.length() > 0)
 			line.setCharAt(0, '\0');
@@ -279,9 +280,8 @@ private void writePack(final Map<String, RemoteRefUpdate> refUpdates,
 		Set<ObjectId> remoteObjects = new HashSet<ObjectId>();
 		Set<ObjectId> newObjects = new HashSet<ObjectId>();
 
-		final PackWriter writer = new PackWriter(transport.getPackConfig(),
-				local.newObjectReader());
-		try {
+		try (final PackWriter writer = new PackWriter(transport.getPackConfig(),
+				local.newObjectReader())) {
 
 			for (final Ref r : getRefs()) {
 				// only add objects that we actually have
@@ -303,10 +303,9 @@ private void writePack(final Map<String, RemoteRefUpdate> refUpdates,
 			writer.setDeltaBaseAsOffset(capableOfsDelta);
 			writer.preparePack(monitor, newObjects, remoteObjects);
 			writer.writePack(monitor, monitor, out);
-		} finally {
-			writer.release();
+
+			packTransferTime = writer.getStatistics().getTimeWriting();
 		}
-		packTransferTime = writer.getStatistics().getTimeWriting();
 	}
 
 	private void readStatusReport(final Map<String, RemoteRefUpdate> refUpdates)
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 dfb8ca9..cf6b2fd 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseReceivePack.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseReceivePack.java
@@ -48,6 +48,7 @@
 import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_OFS_DELTA;
 import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_REPORT_STATUS;
 import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_SIDE_BAND_64K;
+import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_AGENT;
 import static org.eclipse.jgit.transport.SideBandOutputStream.CH_DATA;
 import static org.eclipse.jgit.transport.SideBandOutputStream.CH_PROGRESS;
 import static org.eclipse.jgit.transport.SideBandOutputStream.MAX_BUF;
@@ -224,6 +225,7 @@ public Set<String> getCapabilities() {
 
 	/** Capabilities requested by the client. */
 	private Set<String> enabledCapabilities;
+	String userAgent;
 	private Set<ObjectId> clientShallowCommits;
 	private List<ReceiveCommand> commands;
 
@@ -289,6 +291,7 @@ public ReceiveConfig parse(final Config cfg) {
 
 		final boolean checkReceivedObjects;
 		final boolean allowLeadingZeroFileMode;
+		final boolean allowInvalidPersonIdent;
 		final boolean safeForWindows;
 		final boolean safeForMacOS;
 
@@ -306,6 +309,8 @@ public ReceiveConfig parse(final Config cfg) {
 					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
@@ -317,7 +322,7 @@ public ReceiveConfig parse(final Config cfg) {
 					"denynonfastforwards", false); //$NON-NLS-1$
 			allowOfsDelta = config.getBoolean("repack", "usedeltabaseoffset", //$NON-NLS-1$ //$NON-NLS-2$
 					true);
-			certNonceSeed = config.getString("receive", null, "certnonceseed"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+			certNonceSeed = config.getString("receive", null, "certnonceseed"); //$NON-NLS-1$ //$NON-NLS-2$
 			certNonceSlopLimit = config.getInt("receive", "certnonceslop", 0); //$NON-NLS-1$ //$NON-NLS-2$
 		}
 
@@ -326,6 +331,7 @@ ObjectChecker newObjectChecker() {
 				return null;
 			return new ObjectChecker()
 				.setAllowLeadingZeroFileMode(allowLeadingZeroFileMode)
+				.setAllowInvalidPersonIdent(allowInvalidPersonIdent)
 				.setSafeForWindows(safeForWindows)
 				.setSafeForMacOS(safeForMacOS);
 		}
@@ -734,6 +740,25 @@ public boolean isSideBand() throws RequestNotYetReadException {
 		return enabledCapabilities.contains(CAPABILITY_SIDE_BAND_64K);
 	}
 
+	/**
+	 * Get the user agent of the client.
+	 * <p>
+	 * If the client is new enough to use {@code agent=} capability that value
+	 * will be returned. Older HTTP clients may also supply their version using
+	 * the HTTP {@code User-Agent} header. The capability overrides the HTTP
+	 * header if both are available.
+	 * <p>
+	 * When an HTTP request has been received this method returns the HTTP
+	 * {@code User-Agent} header value until capabilities have been parsed.
+	 *
+	 * @return user agent supplied by the client. Available only if the client
+	 *         is new enough to advertise its user agent.
+	 * @since 4.0
+	 */
+	public String getPeerUserAgent() {
+		return UserAgent.getAgent(enabledCapabilities, userAgent);
+	}
+
 	/** @return all of the command received by the current request. */
 	public List<ReceiveCommand> getAllCommands() {
 		return Collections.unmodifiableList(commands);
@@ -951,6 +976,7 @@ public void sendAdvertisedRefs(final RefAdvertiser adv)
 			adv.advertiseCapability(CAPABILITY_ATOMIC);
 		if (allowOfsDelta)
 			adv.advertiseCapability(CAPABILITY_OFS_DELTA);
+		adv.advertiseCapability(OPTION_AGENT, UserAgent.get());
 		adv.send(getAdvertisedOrDefaultRefs());
 		for (ObjectId obj : advertisedHaves)
 			adv.advertiseHave(obj);
@@ -1070,8 +1096,7 @@ private void receivePack() throws IOException {
 		if (sideBand)
 			resolving = new SideBandProgressMonitor(msgOut);
 
-		ObjectInserter ins = db.newObjectInserter();
-		try {
+		try (ObjectInserter ins = db.newObjectInserter()) {
 			String lockMsg = "jgit receive-pack"; //$NON-NLS-1$
 			if (getRefLogIdent() != null)
 				lockMsg += " from " + getRefLogIdent().toExternalString(); //$NON-NLS-1$
@@ -1089,8 +1114,6 @@ private void receivePack() throws IOException {
 			packLock = parser.parse(receiving, resolving);
 			packSize = Long.valueOf(parser.getPackSize());
 			ins.flush();
-		} finally {
-			ins.release();
 		}
 
 		if (timeoutIn != null)
@@ -1119,67 +1142,69 @@ private void checkConnectivity() throws IOException {
 		}
 		parser = null;
 
-		final ObjectWalk ow = new ObjectWalk(db);
-		ow.setRetainBody(false);
-		if (baseObjects != null) {
-			ow.sort(RevSort.TOPO);
-			if (!baseObjects.isEmpty())
-				ow.sort(RevSort.BOUNDARY, true);
-		}
-
-		for (final ReceiveCommand cmd : commands) {
-			if (cmd.getResult() != Result.NOT_ATTEMPTED)
-				continue;
-			if (cmd.getType() == ReceiveCommand.Type.DELETE)
-				continue;
-			ow.markStart(ow.parseAny(cmd.getNewId()));
-		}
-		for (final ObjectId have : advertisedHaves) {
-			RevObject o = ow.parseAny(have);
-			ow.markUninteresting(o);
-
-			if (baseObjects != null && !baseObjects.isEmpty()) {
-				o = ow.peel(o);
-				if (o instanceof RevCommit)
-					o = ((RevCommit) o).getTree();
-				if (o instanceof RevTree)
-					ow.markUninteresting(o);
+		try (final ObjectWalk ow = new ObjectWalk(db)) {
+			ow.setRetainBody(false);
+			if (baseObjects != null) {
+				ow.sort(RevSort.TOPO);
+				if (!baseObjects.isEmpty())
+					ow.sort(RevSort.BOUNDARY, true);
 			}
-		}
 
-		checking.beginTask(JGitText.get().countingObjects, ProgressMonitor.UNKNOWN);
-		RevCommit c;
-		while ((c = ow.next()) != null) {
-			checking.update(1);
-			if (providedObjects != null //
-					&& !c.has(RevFlag.UNINTERESTING) //
-					&& !providedObjects.contains(c))
-				throw new MissingObjectException(c, Constants.TYPE_COMMIT);
-		}
-
-		RevObject o;
-		while ((o = ow.nextObject()) != null) {
-			checking.update(1);
-			if (o.has(RevFlag.UNINTERESTING))
-				continue;
-
-			if (providedObjects != null) {
-				if (providedObjects.contains(o))
+			for (final ReceiveCommand cmd : commands) {
+				if (cmd.getResult() != Result.NOT_ATTEMPTED)
 					continue;
-				else
-					throw new MissingObjectException(o, o.getType());
+				if (cmd.getType() == ReceiveCommand.Type.DELETE)
+					continue;
+				ow.markStart(ow.parseAny(cmd.getNewId()));
+			}
+			for (final ObjectId have : advertisedHaves) {
+				RevObject o = ow.parseAny(have);
+				ow.markUninteresting(o);
+
+				if (baseObjects != null && !baseObjects.isEmpty()) {
+					o = ow.peel(o);
+					if (o instanceof RevCommit)
+						o = ((RevCommit) o).getTree();
+					if (o instanceof RevTree)
+						ow.markUninteresting(o);
+				}
 			}
 
-			if (o instanceof RevBlob && !db.hasObject(o))
-				throw new MissingObjectException(o, Constants.TYPE_BLOB);
-		}
-		checking.endTask();
+			checking.beginTask(JGitText.get().countingObjects,
+					ProgressMonitor.UNKNOWN);
+			RevCommit c;
+			while ((c = ow.next()) != null) {
+				checking.update(1);
+				if (providedObjects != null //
+						&& !c.has(RevFlag.UNINTERESTING) //
+						&& !providedObjects.contains(c))
+					throw new MissingObjectException(c, Constants.TYPE_COMMIT);
+			}
 
-		if (baseObjects != null) {
-			for (ObjectId id : baseObjects) {
-				o = ow.parseAny(id);
-				if (!o.has(RevFlag.UNINTERESTING))
-					throw new MissingObjectException(o, o.getType());
+			RevObject o;
+			while ((o = ow.nextObject()) != null) {
+				checking.update(1);
+				if (o.has(RevFlag.UNINTERESTING))
+					continue;
+
+				if (providedObjects != null) {
+					if (providedObjects.contains(o))
+						continue;
+					else
+						throw new MissingObjectException(o, o.getType());
+				}
+
+				if (o instanceof RevBlob && !db.hasObject(o))
+					throw new MissingObjectException(o, Constants.TYPE_BLOB);
+			}
+			checking.endTask();
+
+			if (baseObjects != null) {
+				for (ObjectId id : baseObjects) {
+					o = ow.parseAny(id);
+					if (!o.has(RevFlag.UNINTERESTING))
+						throw new MissingObjectException(o, o.getType());
+				}
 			}
 		}
 	}
@@ -1502,7 +1527,7 @@ protected void close() throws IOException {
 	 *             the pack could not be unlocked.
 	 */
 	protected void release() throws IOException {
-		walk.release();
+		walk.close();
 		unlockPack();
 		timeoutIn = null;
 		rawIn = null;
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 e3cfd22..e53c04b 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleFetchConnection.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleFetchConnection.java
@@ -183,16 +183,13 @@ protected void doFetch(final ProgressMonitor monitor,
 			throws TransportException {
 		verifyPrerequisites();
 		try {
-			ObjectInserter ins = transport.local.newObjectInserter();
-			try {
+			try (ObjectInserter ins = transport.local.newObjectInserter()) {
 				PackParser parser = ins.newPackParser(bin);
 				parser.setAllowThin(true);
 				parser.setObjectChecker(transport.getObjectChecker());
 				parser.setLockMessage(lockMessage);
 				packLock = parser.parse(NullProgressMonitor.INSTANCE);
 				ins.flush();
-			} finally {
-				ins.release();
 			}
 		} catch (IOException err) {
 			close();
@@ -217,8 +214,7 @@ private void verifyPrerequisites() throws TransportException {
 		if (prereqs.isEmpty())
 			return;
 
-		final RevWalk rw = new RevWalk(transport.local);
-		try {
+		try (final RevWalk rw = new RevWalk(transport.local)) {
 			final RevFlag PREREQ = rw.newFlag("PREREQ"); //$NON-NLS-1$
 			final RevFlag SEEN = rw.newFlag("SEEN"); //$NON-NLS-1$
 
@@ -281,8 +277,6 @@ private void verifyPrerequisites() throws TransportException {
 				throw new MissingBundlePrerequisiteException(transport.uri,
 						missing);
 			}
-		} finally {
-			rw.release();
 		}
 	}
 
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleWriter.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleWriter.java
index d0f005c..81ad981 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleWriter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleWriter.java
@@ -194,8 +194,7 @@ public void writeBundle(ProgressMonitor monitor, OutputStream os)
 		PackConfig pc = packConfig;
 		if (pc == null)
 			pc = new PackConfig(db);
-		PackWriter packWriter = new PackWriter(pc, db.newObjectReader());
-		try {
+		try (PackWriter packWriter = new PackWriter(pc, db.newObjectReader())) {
 			final HashSet<ObjectId> inc = new HashSet<ObjectId>();
 			final HashSet<ObjectId> exc = new HashSet<ObjectId>();
 			inc.addAll(include.values());
@@ -233,8 +232,6 @@ public void writeBundle(ProgressMonitor monitor, OutputStream os)
 			w.write('\n');
 			w.flush();
 			packWriter.writePack(monitor, monitor, os);
-		} finally {
-			packWriter.release();
 		}
 	}
 }
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 e386c26..0ff9fce 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/Connection.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/Connection.java
@@ -127,4 +127,13 @@ public interface Connection {
 	 *         remote produced no additional messages.
 	 */
 	public String getMessages();
+
+	/**
+	 * User agent advertised by the remote server.
+	 *
+	 * @return agent (version of Git) running on the remote server. Null if the
+	 *         server does not advertise this version.
+	 * @since 4.0
+	 */
+	public String getPeerUserAgent();
 }
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 52a9bab..9aae1c3 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchProcess.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchProcess.java
@@ -136,6 +136,7 @@ private void executeImp(final ProgressMonitor monitor,
 		conn = transport.openFetch();
 		try {
 			result.setAdvertisedRefs(transport.getURI(), conn.getRefsMap());
+			result.peerUserAgent = conn.getPeerUserAgent();
 			final Set<Ref> matched = new HashSet<Ref>();
 			for (final RefSpec spec : toFetch) {
 				if (spec.getSource() == null)
@@ -196,8 +197,7 @@ else if (tagopt == TagOpt.FETCH_TAGS)
 				.newBatchUpdate()
 				.setAllowNonFastForwards(true)
 				.setRefLogMessage("fetch", true); //$NON-NLS-1$
-		final RevWalk walk = new RevWalk(transport.local);
-		try {
+		try (final RevWalk walk = new RevWalk(transport.local)) {
 			if (monitor instanceof BatchingProgressMonitor) {
 				((BatchingProgressMonitor) monitor).setDelayStart(
 						250, TimeUnit.MILLISECONDS);
@@ -226,8 +226,6 @@ else if (tagopt == TagOpt.FETCH_TAGS)
 			throw new TransportException(MessageFormat.format(
 					JGitText.get().failureUpdatingTrackingRef,
 					getFirstFailedRefName(batch), err.getMessage()), err);
-		} finally {
-			walk.release();
 		}
 
 		if (!fetchHeadUpdates.isEmpty()) {
@@ -338,15 +336,12 @@ private void updateFETCH_HEAD(final FetchResult result) throws IOException {
 
 	private boolean askForIsComplete() throws TransportException {
 		try {
-			final ObjectWalk ow = new ObjectWalk(transport.local);
-			try {
+			try (final ObjectWalk ow = new ObjectWalk(transport.local)) {
 				for (final ObjectId want : askFor.keySet())
 					ow.markStart(ow.parseAny(want));
 				for (final Ref ref : localRefs().values())
 					ow.markUninteresting(ow.parseAny(ref.getObjectId()));
 				ow.checkConnectivity();
-			} finally {
-				ow.release();
 			}
 			return true;
 		} catch (MissingObjectException e) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/GitProtocolConstants.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/GitProtocolConstants.java
index 27052db..8d9d2b7 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/GitProtocolConstants.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/GitProtocolConstants.java
@@ -186,6 +186,13 @@ public class GitProtocolConstants {
 	 */
 	public static final String CAPABILITY_PUSH_CERT = "push-cert"; //$NON-NLS-1$
 
+	/**
+	 * Implementation name and version of the client or server.
+	 *
+	 * @since 4.0
+	 */
+	public static final String OPTION_AGENT = "agent"; //$NON-NLS-1$
+
 	static enum MultiAck {
 		OFF, CONTINUE, DETAILED;
 	}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/InternalHttpServerGlue.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/InternalHttpServerGlue.java
new file mode 100644
index 0000000..fe7aaf7
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/InternalHttpServerGlue.java
@@ -0,0 +1,80 @@
+/*
+ * 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;
+
+/**
+ * Internal API to to assist {@code org.eclipse.jgit.http.server}.
+ * <p>
+ * <b>Do not call.</b>
+ *
+ * @since 4.0
+ */
+public class InternalHttpServerGlue {
+	/**
+	 * Apply a default user agent for a request.
+	 *
+	 * @param up
+	 *            current UploadPack instance.
+	 * @param agent
+	 *            user agent string from the HTTP headers.
+	 */
+	public static void setPeerUserAgent(UploadPack up, String agent) {
+		up.userAgent = agent;
+	}
+
+	/**
+	 * Apply a default user agent for a request.
+	 *
+	 * @param rp
+	 *            current ReceivePack instance.
+	 * @param agent
+	 *            user agent string from the HTTP headers.
+	 */
+	public static void setPeerUserAgent(ReceivePack rp, String agent) {
+		rp.userAgent = agent;
+	}
+
+	private InternalHttpServerGlue() {
+	}
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/OperationResult.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/OperationResult.java
index b4a48b0..ad51f3e 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/OperationResult.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/OperationResult.java
@@ -68,6 +68,8 @@ public abstract class OperationResult {
 
 	StringBuilder messageBuffer;
 
+	String peerUserAgent;
+
 	/**
 	 * Get the URI this result came from.
 	 * <p>
@@ -165,4 +167,15 @@ void addMessages(final String msg) {
 				messageBuffer.append('\n');
 		}
 	}
+
+	/**
+	 * Get the user agent advertised by the peer server, if available.
+	 *
+	 * @return advertised user agent, e.g. {@code "JGit/4.0"}. Null if the peer
+	 *         did not advertise version information.
+	 * @since 4.0
+	 */
+	public String getPeerUserAgent() {
+		return peerUserAgent;
+	}
 }
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 5b54891..04abe22 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PackParser.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PackParser.java
@@ -529,7 +529,7 @@ public PackLock parse(ProgressMonitor receiving, ProgressMonitor resolving)
 		} finally {
 			try {
 				if (readCurs != null)
-					readCurs.release();
+					readCurs.close();
 			} finally {
 				readCurs = null;
 			}
@@ -812,7 +812,7 @@ private void resolveDeltasWithExternalBases(final ProgressMonitor progress)
 
 		for (final DeltaChain base : missing) {
 			if (base.head != null)
-				throw new MissingObjectException(base, "delta base");
+				throw new MissingObjectException(base, "delta base"); //$NON-NLS-1$
 		}
 
 		onEndThinPack();
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 53fba55..00f84f7 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushProcess.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushProcess.java
@@ -155,6 +155,7 @@ PushResult execute(final ProgressMonitor monitor)
 			try {
 				res.setAdvertisedRefs(transport.getURI(), connection
 						.getRefsMap());
+				res.peerUserAgent = connection.getPeerUserAgent();
 				res.setRemoteUpdates(toPush);
 				monitor.endTask();
 
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefAdvertiser.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefAdvertiser.java
index 76547a6..f72a4b2 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefAdvertiser.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefAdvertiser.java
@@ -148,6 +148,21 @@ public void advertiseCapability(String name) {
 	}
 
 	/**
+	 * Add one protocol capability with a value ({@code "name=value"}).
+	 *
+	 * @param name
+	 *            name of the capability.
+	 * @param value
+	 *            value. If null the capability will not be added.
+	 * @since 4.0
+	 */
+	public void advertiseCapability(String name, String value) {
+		if (value != null) {
+			capablities.add(name + '=' + value);
+		}
+	}
+
+	/**
 	 * Add a symbolic ref to capabilities.
 	 * <p>
 	 * This method must be invoked prior to any of the following:
@@ -164,8 +179,7 @@ public void advertiseCapability(String name) {
 	 * @since 3.6
 	 */
 	public void addSymref(String from, String to) {
-		String symref = String.format("%s=%s:%s", OPTION_SYMREF, from, to); //$NON-NLS-1$
-		advertiseCapability(symref);
+		advertiseCapability(OPTION_SYMREF, from + ':' + to);
 	}
 
 	/**
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 1de91a5..60043ff 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransferConfig.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransferConfig.java
@@ -67,6 +67,7 @@ public TransferConfig parse(final Config cfg) {
 
 	private final boolean checkReceivedObjects;
 	private final boolean allowLeadingZeroFileMode;
+	private final boolean allowInvalidPersonIdent;
 	private final boolean safeForWindows;
 	private final boolean safeForMacOS;
 	private final boolean allowTipSha1InWant;
@@ -82,6 +83,8 @@ private TransferConfig(final Config rc) {
 				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$
 						SystemReader.getInstance().isWindows());
@@ -113,6 +116,7 @@ public ObjectChecker newObjectChecker() {
 			return null;
 		return new ObjectChecker()
 			.setAllowLeadingZeroFileMode(allowLeadingZeroFileMode)
+			.setAllowInvalidPersonIdent(allowInvalidPersonIdent)
 			.setSafeForWindows(safeForWindows)
 			.setSafeForMacOS(safeForMacOS);
 	}
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 82d1737..b23771e 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportHttp.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportHttp.java
@@ -134,8 +134,6 @@ public class TransportHttp extends HttpTransport implements WalkTransport,
 
 	private static final String SVC_RECEIVE_PACK = "git-receive-pack"; //$NON-NLS-1$
 
-	private static final String userAgent = computeUserAgent();
-
 	static final TransportProtocol PROTO_HTTP = new TransportProtocol() {
 		private final String[] schemeNames = { "http", "https" }; //$NON-NLS-1$ //$NON-NLS-2$
 
@@ -204,17 +202,6 @@ public Transport open(URIish uri, Repository local, String remoteName)
 		}
 	};
 
-	private static String computeUserAgent() {
-		String version;
-		final Package pkg = TransportHttp.class.getPackage();
-		if (pkg != null && pkg.getImplementationVersion() != null) {
-			version = pkg.getImplementationVersion();
-		} else {
-			version = "unknown"; //$NON-NLS-1$
-		}
-		return "JGit/" + version; //$NON-NLS-1$
-	}
-
 	private static final Config.SectionParser<HttpConfig> HTTP_KEY = new SectionParser<HttpConfig>() {
 		public HttpConfig parse(final Config cfg) {
 			return new HttpConfig(cfg);
@@ -309,16 +296,17 @@ public FetchConnection openFetch() throws TransportException,
 			final HttpConnection c = connect(service);
 			final InputStream in = openInputStream(c);
 			try {
+				BaseConnection f;
 				if (isSmartHttp(c, service)) {
 					readSmartHeaders(in, service);
-					return new SmartHttpFetchConnection(in);
-
+					f = new SmartHttpFetchConnection(in);
 				} else {
 					// Assume this server doesn't support smart HTTP fetch
 					// and fall back on dumb object walking.
-					//
-					return newDumbConnection(in);
+					f = newDumbConnection(in);
 				}
+				f.setPeerUserAgent(c.getHeaderField(HttpSupport.HDR_SERVER));
+				return (FetchConnection) f;
 			} finally {
 				in.close();
 			}
@@ -331,7 +319,7 @@ public FetchConnection openFetch() throws TransportException,
 		}
 	}
 
-	private FetchConnection newDumbConnection(InputStream in)
+	private WalkFetchConnection newDumbConnection(InputStream in)
 			throws IOException, PackProtocolException {
 		HttpObjectDB d = new HttpObjectDB(objectsUrl);
 		BufferedReader br = toBufferedReader(in);
@@ -400,9 +388,7 @@ public PushConnection openPush() throws NotSupportedException,
 			final InputStream in = openInputStream(c);
 			try {
 				if (isSmartHttp(c, service)) {
-					readSmartHeaders(in, service);
-					return new SmartHttpPushConnection(in);
-
+					return smartPush(service, c, in);
 				} else if (!useSmartHttp) {
 					final String msg = JGitText.get().smartHTTPPushDisabled;
 					throw new NotSupportedException(msg);
@@ -423,6 +409,14 @@ public PushConnection openPush() throws NotSupportedException,
 		}
 	}
 
+	private PushConnection smartPush(String service, HttpConnection c,
+			InputStream in) throws IOException, TransportException {
+		readSmartHeaders(in, service);
+		SmartHttpPushConnection p = new SmartHttpPushConnection(in);
+		p.setPeerUserAgent(c.getHeaderField(HttpSupport.HDR_SERVER));
+		return p;
+	}
+
 	@Override
 	public void close() {
 		// No explicit connections are maintained.
@@ -551,7 +545,9 @@ protected HttpConnection httpOpen(String method, URL u)
 		conn.setUseCaches(false);
 		conn.setRequestProperty(HDR_ACCEPT_ENCODING, ENCODING_GZIP);
 		conn.setRequestProperty(HDR_PRAGMA, "no-cache"); //$NON-NLS-1$
-		conn.setRequestProperty(HDR_USER_AGENT, userAgent);
+		if (UserAgent.get() != null) {
+			conn.setRequestProperty(HDR_USER_AGENT, UserAgent.get());
+		}
 		int timeOut = getTimeout();
 		if (timeOut != -1) {
 			int effTimeOut = timeOut * 1000;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java
index 1a653bd..3afdb61 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java
@@ -44,6 +44,7 @@
 package org.eclipse.jgit.transport;
 
 import static org.eclipse.jgit.lib.RefDatabase.ALL;
+import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_AGENT;
 import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_ALLOW_TIP_SHA1_IN_WANT;
 import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_INCLUDE_TAG;
 import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_MULTI_ACK;
@@ -253,6 +254,7 @@ public Set<String> getOptions() {
 
 	/** Capabilities requested by the client. */
 	private Set<String> options;
+	String userAgent;
 
 	/** Raw ObjectIds the client has asked for, before validating them. */
 	private final Set<ObjectId> wantIds = new HashSet<ObjectId>();
@@ -806,6 +808,7 @@ public void sendAdvertisedRefs(final RefAdvertiser adv) throws IOException,
 				|| policy == RequestPolicy.REACHABLE_COMMIT_TIP
 				|| policy == null)
 			adv.advertiseCapability(OPTION_ALLOW_TIP_SHA1_IN_WANT);
+		adv.advertiseCapability(OPTION_AGENT, UserAgent.get());
 		adv.setDerefTags(true);
 		Map<String, Ref> refs = getAdvertisedOrDefaultRefs();
 		findSymrefs(adv, refs);
@@ -884,6 +887,37 @@ private void recvWants() throws IOException {
 		}
 	}
 
+	/**
+	 * Returns the clone/fetch depth. Valid only after calling recvWants().
+	 *
+	 * @return the depth requested by the client, or 0 if unbounded.
+	 * @since 4.0
+	 */
+	public int getDepth() {
+		if (options == null)
+			throw new RequestNotYetReadException();
+		return depth;
+	}
+
+	/**
+	 * Get the user agent of the client.
+	 * <p>
+	 * If the client is new enough to use {@code agent=} capability that value
+	 * will be returned. Older HTTP clients may also supply their version using
+	 * the HTTP {@code User-Agent} header. The capability overrides the HTTP
+	 * header if both are available.
+	 * <p>
+	 * When an HTTP request has been received this method returns the HTTP
+	 * {@code User-Agent} header value until capabilities have been parsed.
+	 *
+	 * @return user agent supplied by the client. Available only if the client
+	 *         is new enough to advertise its user agent.
+	 * @since 4.0
+	 */
+	public String getPeerUserAgent() {
+		return UserAgent.getAgent(options, userAgent);
+	}
+
 	private boolean negotiate() throws IOException {
 		okToGiveUp = Boolean.FALSE;
 
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/UserAgent.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/UserAgent.java
new file mode 100644
index 0000000..eadb92d
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/UserAgent.java
@@ -0,0 +1,147 @@
+/*
+ * 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.GitProtocolConstants.OPTION_AGENT;
+
+import java.util.Set;
+
+import org.eclipse.jgit.util.StringUtils;
+
+/**
+ * User agent to be reported by this JGit client and server on the network.
+ * <p>
+ * On HTTP transports this user agent string is always supplied by the JGit
+ * client in the {@code User-Agent} HTTP header.
+ * <p>
+ * On native transports this user agent string is always sent when JGit is a
+ * server. When JGit is a client the user agent string will be supplied to the
+ * remote server only if the remote server advertises its own agent identity.
+ *
+ * @since 4.0
+ */
+public class UserAgent {
+	private static volatile String userAgent = computeUserAgent();
+
+	private static String computeUserAgent() {
+		return clean("JGit/" + computeVersion()); //$NON-NLS-1$
+	}
+
+	private static String computeVersion() {
+		Package pkg = UserAgent.class.getPackage();
+		if (pkg != null) {
+			String ver = pkg.getImplementationVersion();
+			if (!StringUtils.isEmptyOrNull(ver)) {
+				return ver;
+			}
+		}
+		return "unknown"; //$NON-NLS-1$
+	}
+
+	private static String clean(String s) {
+		s = s.trim();
+		StringBuilder b = new StringBuilder(s.length());
+		for (int i = 0; i < s.length(); i++) {
+			char c = s.charAt(i);
+			if (c <= 32 || c >= 127) {
+				if (b.length() > 0 && b.charAt(b.length() - 1) == '.')
+					continue;
+				c = '.';
+			}
+			b.append(c);
+		}
+		return b.length() > 0 ? b.toString() : null;
+	}
+
+	/**
+	 * Get the user agent string advertised by JGit.
+	 *
+	 * @return a string similar to {@code "JGit/4.0"}; null if the agent has
+	 *         been cleared and should not be shared with a peer.
+	 */
+	public static String get() {
+		return userAgent;
+	}
+
+	/**
+	 * Change the user agent string advertised by JGit.
+	 * <p>
+	 * The new string should start with {@code "JGit/"} (for example
+	 * {@code "JGit/4.0"}) to advertise the implementation as JGit based.
+	 * <p>
+	 * Spaces and other whitespace should be avoided as these will be
+	 * automatically converted to {@code "."}.
+	 * <p>
+	 * User agent strings are restricted to printable ASCII.
+	 *
+	 * @param agent
+	 *            new user agent string for this running JGit library. Setting
+	 *            to null or empty string will avoid sending any identification
+	 *            to the peer.
+	 */
+	public static void set(String agent) {
+		userAgent = StringUtils.isEmptyOrNull(agent) ? null : clean(agent);
+	}
+
+	static String getAgent(Set<String> options, String transportAgent) {
+		if (options == null || options.isEmpty()) {
+			return transportAgent;
+		}
+		for (String o : options) {
+			if (o.startsWith(OPTION_AGENT)
+					&& o.length() > OPTION_AGENT.length()
+					&& o.charAt(OPTION_AGENT.length()) == '=') {
+				return o.substring(OPTION_AGENT.length() + 1);
+			}
+		}
+		return transportAgent;
+	}
+
+	static boolean hasAgent(Set<String> options) {
+		return getAgent(options, null) != null;
+	}
+
+	private UserAgent() {
+	}
+}
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 35850dc..35fc99e 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/ChangeIdUtil.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/ChangeIdUtil.java
@@ -115,8 +115,10 @@ public static ObjectId computeChangeId(final ObjectId treeId,
 		b.append(committer.toExternalString());
 		b.append("\n\n"); //$NON-NLS-1$
 		b.append(cleanMessage);
-		return new ObjectInserter.Formatter().idFor(Constants.OBJ_COMMIT, //
-				b.toString().getBytes(Constants.CHARACTER_ENCODING));
+		try (ObjectInserter f = new ObjectInserter.Formatter()) {
+			return f.idFor(Constants.OBJ_COMMIT, //
+					b.toString().getBytes(Constants.CHARACTER_ENCODING));
+		}
 	}
 
 	private static final Pattern issuePattern = Pattern
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 706b54e..d11b03e 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java
@@ -481,7 +481,7 @@ public void run() {
 				}
 			}
 		} catch (IOException e) {
-			LOG.error("Caught exception in FS.readPipe()", e); //$NON-NLS-1$
+			LOG.debug("Caught exception in FS.readPipe()", e); //$NON-NLS-1$
 		}
 		if (debug) {
 			LOG.debug("readpipe returns null"); //$NON-NLS-1$
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/HttpSupport.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/HttpSupport.java
index 37c9f7b..8b4ad0a 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/HttpSupport.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/HttpSupport.java
@@ -74,6 +74,12 @@ public class HttpSupport {
 	/** The {@code User-Agent} header. */
 	public static final String HDR_USER_AGENT = "User-Agent"; //$NON-NLS-1$
 
+	/**
+	 * The {@code Server} header.
+	 * @since 4.0
+	 */
+	public static final String HDR_SERVER = "Server"; //$NON-NLS-1$
+
 	/** The {@code Date} header. */
 	public static final String HDR_DATE = "Date"; //$NON-NLS-1$
 
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 006c3c0..3719bf7 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/TemporaryBuffer.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/TemporaryBuffer.java
@@ -44,6 +44,7 @@
 
 package org.eclipse.jgit.util;
 
+import java.io.BufferedOutputStream;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
@@ -435,7 +436,7 @@ public LocalFile(final File directory, final int inCoreLimit) {
 
 		protected OutputStream overflow() throws IOException {
 			onDiskFile = File.createTempFile("jgit_", ".buf", directory); //$NON-NLS-1$ //$NON-NLS-2$
-			return new FileOutputStream(onDiskFile);
+			return new BufferedOutputStream(new FileOutputStream(onDiskFile));
 		}
 
 		public long length() {
diff --git a/pom.xml b/pom.xml
index 4b8ac43..1e00f24 100644
--- a/pom.xml
+++ b/pom.xml
@@ -210,6 +210,10 @@
       <id>repo.eclipse.org.cbi-releases</id>
       <url>https://repo.eclipse.org/content/repositories/cbi-releases/</url>
     </pluginRepository>
+    <pluginRepository>
+      <id>repo.eclipse.org.cbi-snapshots</id>
+      <url>https://repo.eclipse.org/content/repositories/cbi-snapshots/</url>
+    </pluginRepository>
   </pluginRepositories>
 
   <build>
@@ -346,7 +350,7 @@
         <plugin>
           <groupId>org.eclipse.cbi.maven.plugins</groupId>
           <artifactId>eclipse-jarsigner-plugin</artifactId>
-          <version>1.1.1</version>
+          <version>1.1.2-SNAPSHOT</version>
         </plugin>
         <plugin>
           <groupId>org.eclipse.tycho.extras</groupId>