Write packed-refs directly when cloning

When we are cloning we have no refs at all yet, and there cannot
(or at least should not) be any other thread doing something with
refs yet.

Locking loose refs is thus not needed, since there are no loose
refs yet and nothing should be trying to create them concurrently.

Let's skip the whole loose ref locking when we are cloning a repository.
As a result, JGit will write the refs directly to the packed-refs
file, and will not create the refs/remotes/ directories nor the
lock files underneath when cloning and packed refs are used. Since
no lock files are created, any problems on case-insensitive file
systems with tag or branch names that differ only in case are avoided
during cloning.

Detect if we are cloning based on the following heuristics:
* HEAD is a dangling symref
* There is no loose ref
* There is no packed-refs file

Note, however, that there may still be problems with such tag or
branch names later on. This is primarily a five-minutes-past-twelve
stop-gap measure to resolve the referenced bug, which affects the
Oxygen.2 release.

Bug: 528497
Change-Id: I57860c29c210568165276a123b855e462b6a107a
Signed-off-by: Thomas Wolf <thomas.wolf@paranor.ch>
Signed-off-by: Matthias Sohn <matthias.sohn@sap.com>
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 b328eb8..c1f5476 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
@@ -179,11 +179,19 @@ public void execute(RevWalk walk, ProgressMonitor monitor,
 		Map<String, LockFile> locks = null;
 		refdb.inProcessPackedRefsLock.lock();
 		try {
-			locks = lockLooseRefs(pending);
-			if (locks == null) {
-				return;
+			PackedRefList oldPackedList;
+			if (!refdb.isInClone()) {
+				locks = lockLooseRefs(pending);
+				if (locks == null) {
+					return;
+				}
+				oldPackedList = refdb.pack(locks);
+			} else {
+				// During clone locking isn't needed since no refs exist yet.
+				// This also helps to avoid problems with refs only differing in
+				// case on a case insensitive filesystem (bug 528497)
+				oldPackedList = refdb.getPackedRefs();
 			}
-			PackedRefList oldPackedList = refdb.pack(locks);
 			RefList<Ref> newRefs = applyUpdates(walk, oldPackedList, pending);
 			if (newRefs == 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 a44acac..bd39e65 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
@@ -65,6 +65,7 @@
 import java.io.IOException;
 import java.io.InputStreamReader;
 import java.io.InterruptedIOException;
+import java.nio.file.Files;
 import java.security.DigestInputStream;
 import java.security.MessageDigest;
 import java.text.MessageFormat;
@@ -901,7 +902,7 @@ else if (0 <= (idx = packed.find(dst.getName())))
 		return ref;
 	}
 
-	private PackedRefList getPackedRefs() throws IOException {
+	PackedRefList getPackedRefs() throws IOException {
 		boolean trustFolderStat = getRepository().getConfig().getBoolean(
 				ConfigConstants.CONFIG_CORE_SECTION,
 				ConfigConstants.CONFIG_KEY_TRUSTFOLDERSTAT, true);
@@ -1188,6 +1189,29 @@ private static boolean isSymRef(final byte[] buf, int n) {
 				&& buf[4] == ' ';
 	}
 
+	/**
+	 * Detect if we are in a clone command execution
+	 *
+	 * @return {@code true} if we are currently cloning a repository
+	 * @throws IOException
+	 */
+	boolean isInClone() throws IOException {
+		return hasDanglingHead() && !packedRefsFile.exists() && !hasLooseRef();
+	}
+
+	private boolean hasDanglingHead() throws IOException {
+		Ref head = exactRef(Constants.HEAD);
+		if (head != null) {
+			ObjectId id = head.getObjectId();
+			return id == null || id.equals(ObjectId.zeroId());
+		}
+		return false;
+	}
+
+	private boolean hasLooseRef() throws IOException {
+		return Files.walk(refsDir.toPath()).anyMatch(Files::isRegularFile);
+	}
+
 	/** If the parent should fire listeners, fires them. */
 	void fireRefsChanged() {
 		final int last = lastNotifiedModCnt.get();