Merge "Refuse convert-ref-storage to reftable when usePerRequestRefCache is set"
diff --git a/Documentation/cmd-convert-ref-storage.txt b/Documentation/cmd-convert-ref-storage.txt
index cbb5457..d5f5507 100644
--- a/Documentation/cmd-convert-ref-storage.txt
+++ b/Documentation/cmd-convert-ref-storage.txt
@@ -21,6 +21,10 @@
 == DESCRIPTION
 Convert ref storage to reftable.
 
+Converting to `reftable` additionally requires the server to run with
+link:config-gerrit.html#core.usePerRequestRefCache[core.usePerRequestRefCache]
+set to `false` (reftable is otherwise unservable); the command fails otherwise.
+
 == ACCESS
 Administrators
 
diff --git a/Documentation/config-gerrit.txt b/Documentation/config-gerrit.txt
index ab54297..6521e3b 100644
--- a/Documentation/config-gerrit.txt
+++ b/Documentation/config-gerrit.txt
@@ -2677,6 +2677,10 @@
 (lazily) if needed. This helps reduce the overhead of checking if
 the packed-refs file is outdated.
 +
+Repositories using the reftable format cannot be served while this cache
+is enabled. Set this option to `false` and restart Gerrit before serving
+reftable repositories.
++
 Default is `true`.
 
 [[core.useFileKeyByProjectCache]]core.useFileKeyByProjectCache::
diff --git a/java/com/google/gerrit/sshd/commands/ConvertRefStorage.java b/java/com/google/gerrit/sshd/commands/ConvertRefStorage.java
index 56cf5c8..511339a 100644
--- a/java/com/google/gerrit/sshd/commands/ConvertRefStorage.java
+++ b/java/com/google/gerrit/sshd/commands/ConvertRefStorage.java
@@ -20,6 +20,7 @@
 import com.google.gerrit.common.data.GlobalCapability;
 import com.google.gerrit.entities.Project;
 import com.google.gerrit.extensions.annotations.RequiresCapability;
+import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gerrit.server.git.DelegateRepository;
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.project.ProjectState;
@@ -29,6 +30,7 @@
 import java.io.IOException;
 import org.eclipse.jgit.errors.RepositoryNotFoundException;
 import org.eclipse.jgit.internal.storage.file.FileRepository;
+import org.eclipse.jgit.lib.Config;
 import org.eclipse.jgit.lib.Repository;
 import org.kohsuke.args4j.Option;
 import org.kohsuke.args4j.spi.ExplicitBooleanOptionHandler;
@@ -41,6 +43,8 @@
 public class ConvertRefStorage extends SshCommand {
   @Inject private GitRepositoryManager repoManager;
 
+  @Inject @GerritServerConfig private Config cfg;
+
   private enum StorageFormatOption {
     reftable,
     refdir,
@@ -74,6 +78,11 @@
   @Override
   public void run() throws Exception {
     enableGracefulStop();
+    // reftable is unservable with core.usePerRequestRefCache; refuse converting to it.
+    if (storageFormat == StorageFormatOption.reftable
+        && cfg.getBoolean("core", null, "usePerRequestRefCache", true)) {
+      throw die("Converting to reftable requires core.usePerRequestRefCache=false");
+    }
     Project.NameKey projectName = projectState.getNameKey();
     try (Repository repo = repoManager.openRepository(projectName)) {
       if (repo instanceof DelegateRepository delegateRepository) {
diff --git a/javatests/com/google/gerrit/acceptance/ssh/ConvertRefStorageIT.java b/javatests/com/google/gerrit/acceptance/ssh/ConvertRefStorageIT.java
new file mode 100644
index 0000000..dadbc69
--- /dev/null
+++ b/javatests/com/google/gerrit/acceptance/ssh/ConvertRefStorageIT.java
@@ -0,0 +1,34 @@
+// Copyright (C) 2026 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.acceptance.ssh;
+
+import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.NoHttpd;
+import com.google.gerrit.acceptance.UseSsh;
+import com.google.gerrit.acceptance.config.GerritConfig;
+import org.junit.Test;
+
+@NoHttpd
+@UseSsh
+public class ConvertRefStorageIT extends AbstractDaemonTest {
+  @Test
+  @GerritConfig(name = "core.usePerRequestRefCache", value = "true")
+  public void refusesConversionToReftableWhenPerRequestRefCacheEnabled() throws Exception {
+    adminSshSession.exec(
+        "gerrit convert-ref-storage --project " + project.get() + " --format reftable");
+    adminSshSession.assertFailure(
+        "Converting to reftable requires core.usePerRequestRefCache=false");
+  }
+}