Gc#writePack: write the reverse index file to disk

The reverse index is currently created in-memory when needed. A writer
for reverse index files was already implemented.

Make garbage collection write the reverse index file when the PackConfig
enables it. Write it during #writePack, which mirrors how the primary
index is written.

Change-Id: I50131af6622c41a7b24534aaaf2a423ab4178981
Signed-off-by: Anna Papitto <annapapitto@google.com>
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcReverseIndexTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcReverseIndexTest.java
new file mode 100644
index 0000000..cbb0943
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcReverseIndexTest.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2023, Google LLC and others
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0 which is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.eclipse.jgit.internal.storage.file;
+
+import static org.eclipse.jgit.internal.storage.pack.PackExt.REVERSE_INDEX;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.util.Collections;
+
+import org.eclipse.jgit.junit.TestRepository;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.storage.pack.PackConfig;
+import org.eclipse.jgit.util.IO;
+import org.junit.Test;
+
+public class GcReverseIndexTest extends GcTestCase {
+
+	@Test
+	public void testWriteDefault() throws Exception {
+		PackConfig config = new PackConfig(repo);
+		gc.setPackConfig(config);
+
+		RevCommit tip = commitChain(10);
+		TestRepository.BranchBuilder bb = tr.branch("refs/heads/main");
+		bb.update(tip);
+
+		gc.gc().get();
+		assertRidxDoesNotExist(repo);
+	}
+
+	@Test
+	public void testWriteDisabled() throws Exception {
+		PackConfig config = new PackConfig(repo);
+		config.setWriteReverseIndex(false);
+		gc.setPackConfig(config);
+
+		RevCommit tip = commitChain(10);
+		TestRepository.BranchBuilder bb = tr.branch("refs/heads/main");
+		bb.update(tip);
+
+		gc.gc().get();
+		assertRidxDoesNotExist(repo);
+	}
+
+	@Test
+	public void testWriteEmptyRepo() throws Exception {
+		PackConfig config = new PackConfig(repo);
+		config.setWriteReverseIndex(true);
+		gc.setPackConfig(config);
+
+		gc.gc().get();
+		assertRidxDoesNotExist(repo);
+	}
+
+	@Test
+	public void testWriteShallowRepo() throws Exception {
+		PackConfig config = new PackConfig(repo);
+		config.setWriteReverseIndex(true);
+		gc.setPackConfig(config);
+
+		RevCommit tip = commitChain(2);
+		TestRepository.BranchBuilder bb = tr.branch("refs/heads/main");
+		bb.update(tip);
+		repo.getObjectDatabase().setShallowCommits(Collections.singleton(tip));
+
+		gc.gc().get();
+		assertValidRidxExists(repo);
+	}
+
+	@Test
+	public void testWriteEnabled() throws Exception {
+		PackConfig config = new PackConfig(repo);
+		config.setWriteReverseIndex(true);
+		gc.setPackConfig(config);
+
+		RevCommit tip = commitChain(10);
+		TestRepository.BranchBuilder bb = tr.branch("refs/heads/main");
+		bb.update(tip);
+
+		gc.gc().get();
+		assertValidRidxExists(repo);
+	}
+
+	private static void assertValidRidxExists(FileRepository repo)
+			throws Exception {
+		PackFile packFile = repo.getObjectDatabase().getPacks().iterator()
+				.next().getPackFile();
+		File file = packFile.create(REVERSE_INDEX);
+		assertTrue(file.exists());
+		try (InputStream os = new FileInputStream(file)) {
+			byte[] magic = new byte[4];
+			IO.readFully(os, magic, 0, 4);
+			assertArrayEquals(new byte[] { 'R', 'I', 'D', 'X' }, magic);
+		}
+	}
+
+	private static void assertRidxDoesNotExist(FileRepository repo) {
+		File packDir = repo.getObjectDatabase().getPackDirectory();
+		String[] reverseIndexFilenames = packDir.list(
+				(dir, name) -> name.endsWith(REVERSE_INDEX.getExtension()));
+		assertEquals(0, reverseIndexFilenames.length);
+	}
+}
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 11757aa..10b53b6 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
@@ -1323,6 +1323,25 @@
 				idxChannel.force(true);
 			}
 
+			if (pw.isReverseIndexEnabled()) {
+				File tmpReverseIndexFile = new File(packdir,
+						tmpBase + REVERSE_INDEX.getTmpExtension());
+				tmpExts.put(REVERSE_INDEX, tmpReverseIndexFile);
+				if (!tmpReverseIndexFile.createNewFile()) {
+					throw new IOException(MessageFormat.format(
+							JGitText.get().cannotCreateIndexfile,
+							tmpReverseIndexFile.getPath()));
+				}
+				try (FileOutputStream fos = new FileOutputStream(
+						tmpReverseIndexFile);
+						FileChannel channel = fos.getChannel();
+						OutputStream stream = Channels
+								.newOutputStream(channel)) {
+					pw.writeReverseIndex(stream);
+					channel.force(true);
+				}
+			}
+
 			if (pw.prepareBitmapIndex(pm)) {
 				File tmpBitmapIdx = new File(packdir,
 						tmpBase + BITMAP_INDEX.getTmpExtension());