RefDirectory: fix pack() unlocking held locks prematurely

When packing loose refs, if the caller already holds a lock on a ref
being packed, pack() releases that lock as a side effect of deleting
the loose ref file. This causes the caller to lose exclusive access
without being aware of it. We have observed ref rewinds in production
that we believe were caused by this bug.

The algorithm described in the Javadoc for PackedBatchRefUpdate
explicitly requires these locks to prevent concurrent writes. Releasing
a caller-held lock from pack() violates that requirement.

Preserve locks held by the caller when deleting loose refs during
packing. Add a test verifying that a caller-held lock remains locked
after pack() returns. The test fails without this fix.

Bug: jgit-281
Co-Authored-By: Kaushik Lingarkar <klingarkar@nvidia.com>
Signed-off-by: Adithya Chakilam <achakilam@nvidia.com>
Change-Id: I7fad28c5fdacc172b5023c066d321f7a2f8b275a
(cherry picked from commit 9d7ec6911e3d46a4621eac0958afc4e4e881406c)
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefDirectoryTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefDirectoryTest.java
index 2bafde6..488de5e 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefDirectoryTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefDirectoryTest.java
@@ -1362,6 +1362,25 @@ public void testCommonRefPrefix() {
 				StringUtils.commonPrefix("refs/heads/", "refs/heads/main"));
 	}
 
+	@Test
+	public void testPackDoesNotUnlockHeldLocks() throws IOException {
+		String ref = "refs/heads/master";
+		writeLooseRef(ref, A);
+
+		File refFile = refdir.fileFor(ref);
+		LockFile held = new LockFile(refFile);
+		assertTrue("must acquire lock", held.lock());
+		try {
+			refdir.pack(Map.of(ref, held));
+			assertFalse("loose ref must be deleted after packing",
+					refFile.exists());
+			assertTrue("held lock must still be locked after pack()",
+					held.isLocked());
+		} finally {
+			held.unlock();
+		}
+	}
+
 	void writePackedRef(String name, AnyObjectId id) throws IOException {
 		writePackedRefs(id.name() + " " + name + "\n");
 	}
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 29630b7..40b456c 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
@@ -842,8 +842,13 @@ private void pack(Collection<String> refs,
 								}
 								newLoose = curLoose.remove(idx);
 							} while (!looseRefs.compareAndSet(curLoose, newLoose));
-							int levels = levelsIn(refName) - 2;
-							deleteAndUnlock(refFile, levels, rLck);
+							if (shouldUnlock) {
+								int levels = levelsIn(refName) - 2;
+								deleteAndUnlock(refFile, levels, rLck);
+								shouldUnlock = false;
+							} else {
+								delete(refFile);
+							}
 						}
 					} finally {
 						if (shouldUnlock) {