Merge "Merge branch 'stable-6.0' into stable-6.1" into stable-6.1
diff --git a/org.eclipse.jgit.http.server/.settings/.api_filters b/org.eclipse.jgit.http.server/.settings/.api_filters
new file mode 100644
index 0000000..2c32c98
--- /dev/null
+++ b/org.eclipse.jgit.http.server/.settings/.api_filters
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<component id="org.eclipse.jgit.http.server" version="2">
+    <resource path="src/org/eclipse/jgit/http/server/UploadPackErrorHandler.java" type="org.eclipse.jgit.http.server.UploadPackErrorHandler">
+        <filter id="1210056707">
+            <message_arguments>
+                <message_argument value="6.1.1"/>
+                <message_argument value="statusCodeForThrowable(Throwable)"/>
+            </message_arguments>
+        </filter>
+    </resource>
+</component>
diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/UploadPackErrorHandler.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/UploadPackErrorHandler.java
index 03be087..2aadbbc 100644
--- a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/UploadPackErrorHandler.java
+++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/UploadPackErrorHandler.java
@@ -9,13 +9,19 @@
  */
 package org.eclipse.jgit.http.server;
 
+import static javax.servlet.http.HttpServletResponse.SC_FORBIDDEN;
+import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
+import static javax.servlet.http.HttpServletResponse.SC_OK;
+
 import java.io.IOException;
 
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
+import org.eclipse.jgit.errors.PackProtocolException;
 import org.eclipse.jgit.transport.ServiceMayNotContinueException;
 import org.eclipse.jgit.transport.UploadPack;
+import org.eclipse.jgit.transport.resolver.ServiceNotEnabledException;
 
 /**
  * Handle git-upload-pack errors.
@@ -35,6 +41,24 @@
  */
 public interface UploadPackErrorHandler {
 	/**
+	 * Maps a thrown git related Exception to an appropriate HTTP status code.
+	 *
+	 * @param error
+	 *            The thrown Exception.
+	 * @return the HTTP status code as an int
+	 * @since 6.1.1
+	 */
+	public static int statusCodeForThrowable(Throwable error) {
+		if (error instanceof ServiceNotEnabledException) {
+			return SC_FORBIDDEN;
+		}
+		if (error instanceof PackProtocolException) {
+			// Internal git errors are not errors from an HTTP standpoint.
+			return SC_OK;
+		}
+		return SC_INTERNAL_SERVER_ERROR;
+	}
+	/**
 	 * @param req
 	 *            The HTTP request
 	 * @param rsp
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 23a398f..b0a07f1 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
@@ -10,9 +10,7 @@
 
 package org.eclipse.jgit.http.server;
 
-import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
 import static javax.servlet.http.HttpServletResponse.SC_FORBIDDEN;
-import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
 import static javax.servlet.http.HttpServletResponse.SC_UNAUTHORIZED;
 import static javax.servlet.http.HttpServletResponse.SC_UNSUPPORTED_MEDIA_TYPE;
 import static org.eclipse.jgit.http.server.GitSmartHttpTools.UPLOAD_PACK;
@@ -23,6 +21,7 @@
 import static org.eclipse.jgit.http.server.ServletUtils.consumeRequestBody;
 import static org.eclipse.jgit.http.server.ServletUtils.getInputStream;
 import static org.eclipse.jgit.http.server.ServletUtils.getRepository;
+import static org.eclipse.jgit.http.server.UploadPackErrorHandler.statusCodeForThrowable;
 import static org.eclipse.jgit.util.HttpSupport.HDR_USER_AGENT;
 
 import java.io.IOException;
@@ -49,7 +48,6 @@
 import org.eclipse.jgit.transport.ServiceMayNotContinueException;
 import org.eclipse.jgit.transport.UploadPack;
 import org.eclipse.jgit.transport.UploadPackInternalServerErrorException;
-import org.eclipse.jgit.transport.WantNotValidException;
 import org.eclipse.jgit.transport.resolver.ServiceNotAuthorizedException;
 import org.eclipse.jgit.transport.resolver.ServiceNotEnabledException;
 import org.eclipse.jgit.transport.resolver.UploadPackFactory;
@@ -153,16 +151,6 @@ public void destroy() {
 		}
 	}
 
-	private static int statusCodeForThrowable(Throwable error) {
-		if (error instanceof ServiceNotEnabledException) {
-			return SC_FORBIDDEN;
-		}
-		if (error instanceof WantNotValidException) {
-			return SC_BAD_REQUEST;
-		}
-		return SC_INTERNAL_SERVER_ERROR;
-	}
-
 	private final UploadPackErrorHandler handler;
 
 	UploadPackServlet(@Nullable UploadPackErrorHandler handler) {
diff --git a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/SmartClientSmartServerTest.java b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/SmartClientSmartServerTest.java
index 8f3888e..b9b10b4 100644
--- a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/SmartClientSmartServerTest.java
+++ b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/SmartClientSmartServerTest.java
@@ -537,9 +537,9 @@ public void testFetchBySHA1Unreachable() throws Exception {
 							Collections.singletonList(
 									new RefSpec(unreachableCommit.name()))));
 			assertTrue(e.getMessage().contains(
-					"Bad Request"));
+					"want " + unreachableCommit.name() + " not valid"));
 		}
-		assertLastRequestStatusCode(400);
+		assertLastRequestStatusCode(200);
 	}
 
 	@Test
@@ -560,9 +560,9 @@ protected Map<String, Ref> getAdvertisedRefs(Repository repository,
 					() -> t.fetch(NullProgressMonitor.INSTANCE,
 							Collections.singletonList(new RefSpec(A.name()))));
 			assertTrue(
-					e.getMessage().contains("Bad Request"));
+					e.getMessage().contains("want " + A.name() + " not valid"));
 		}
-		assertLastRequestStatusCode(400);
+		assertLastRequestStatusCode(200);
 	}
 
 	@Test
@@ -1610,9 +1610,9 @@ public void testInvalidWant() throws Exception {
 			fail("Server accepted want " + id.name());
 		} catch (TransportException err) {
 			assertTrue(err.getMessage()
-					.contains("Bad Request"));
+					.contains("want " + id.name() + " not valid"));
 		}
-		assertLastRequestStatusCode(400);
+		assertLastRequestStatusCode(200);
 	}
 
 	@Test
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackedBatchRefUpdate.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackedBatchRefUpdate.java
index 9c1d33d..8b0ea4f 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackedBatchRefUpdate.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackedBatchRefUpdate.java
@@ -86,10 +86,16 @@
  */
 class PackedBatchRefUpdate extends BatchRefUpdate {
 	private RefDirectory refdb;
+	private boolean shouldLockLooseRefs;
 
 	PackedBatchRefUpdate(RefDirectory refdb) {
-		super(refdb);
-		this.refdb = refdb;
+		this(refdb, true);
+	}
+
+	PackedBatchRefUpdate(RefDirectory refdb, boolean shouldLockLooseRefs) {
+	  super(refdb);
+	  this.refdb = refdb;
+	  this.shouldLockLooseRefs = shouldLockLooseRefs;
 	}
 
 	/** {@inheritDoc} */
@@ -155,7 +161,7 @@ public void execute(RevWalk walk, ProgressMonitor monitor,
 		refdb.inProcessPackedRefsLock.lock();
 		try {
 			PackedRefList oldPackedList;
-			if (!refdb.isInClone()) {
+			if (!refdb.isInClone() && shouldLockLooseRefs) {
 				locks = lockLooseRefs(pending);
 				if (locks == null) {
 					return;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectory.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectory.java
index 07e3814..b46ffe3 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectory.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectory.java
@@ -587,6 +587,21 @@ public PackedBatchRefUpdate newBatchUpdate() {
 		return new PackedBatchRefUpdate(this);
 	}
 
+	/**
+	 * Create a new batch update to attempt on this database.
+	 *
+	 * @param shouldLockLooseRefs
+	 *            whether loose refs should be locked during the batch ref
+	 *            update. Note that this should only be set to {@code false} if
+	 *            the application using this ensures that no other ref updates
+	 *            run concurrently to avoid lost updates caused by a race. In
+	 *            such cases it can improve performance.
+	 * @return a new batch update object
+	 */
+	public PackedBatchRefUpdate newBatchUpdate(boolean shouldLockLooseRefs) {
+		return new PackedBatchRefUpdate(this, shouldLockLooseRefs);
+	}
+
 	/** {@inheritDoc} */
 	@Override
 	public boolean performsAtomicTransactions() {