ProjectRepairer: Refactor to select actions through an enum

repair() takes a 'copyPacks' boolean, which can only describe the one
thing it knows how to do. A second kind of repair would mean a second
boolean at every call site, with the order the two run in implicit in
how repair() tests them.

Take a collection of Action values instead. Individual flags are kept in
flag order. '--full' and the no-flag default use Action.all() so actions
run in enum declaration order. Action has only COPY_PACKS, so behaviour
is unchanged.

Change-Id: Ie7c0d9af40cab893a01a754bc984b8593ff6a5bc
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/AutoRepairHandler.java b/src/main/java/com/googlesource/gerrit/plugins/replication/AutoRepairHandler.java
index b1462c4..fa97cf2 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/AutoRepairHandler.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/AutoRepairHandler.java
@@ -21,6 +21,7 @@
 import com.google.gerrit.server.git.WorkQueue;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
+import com.googlesource.gerrit.plugins.replication.ProjectRepairer.Action;
 import com.googlesource.gerrit.plugins.replication.api.ReplicationConfig;
 import com.googlesource.gerrit.plugins.replication.events.dispatcher.EventDispatcher;
 import java.io.ByteArrayOutputStream;
@@ -92,7 +93,7 @@
       ByteArrayOutputStream buf = new ByteArrayOutputStream();
       boolean isRepaired;
       try {
-        isRepaired = projectRepairer.repair(project, uri, buf, true);
+        isRepaired = projectRepairer.repair(project, uri, buf, Action.all());
       } catch (InterruptedIOException e) {
         repLog.atWarning().withCause(e).log(
             "Auto-repair interrupted for project %s to %s", project.get(), uri);
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/ProjectRepairer.java b/src/main/java/com/googlesource/gerrit/plugins/replication/ProjectRepairer.java
index 68a2c9d..317e6b4 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/ProjectRepairer.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/ProjectRepairer.java
@@ -29,6 +29,7 @@
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.List;
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.transport.URIish;
@@ -37,6 +38,14 @@
 
 @Singleton
 public class ProjectRepairer {
+  public enum Action {
+    COPY_PACKS;
+
+    public static List<Action> all() {
+      return List.of(values());
+    }
+  }
+
   private static final String PACK_DIR = "objects/pack/";
 
   private final GitRepositoryManager gitManager;
@@ -48,9 +57,10 @@
     this.replicationConfig = replicationConfig;
   }
 
-  public boolean repair(Project.NameKey project, URIish uri, OutputStream out, boolean copyPacks)
+  public boolean repair(
+      Project.NameKey project, URIish uri, OutputStream out, Collection<Action> actions)
       throws InterruptedIOException {
-    if (!copyPacks) {
+    if (actions.isEmpty()) {
       return true;
     }
 
@@ -59,11 +69,24 @@
       return false;
     }
 
-    if (!copyPacksTo(objectsDir.resolve("pack"), uri, out)) {
-      repLog.atSevere().log("Repair failed for %s on %s", project.get(), uri);
-      return false;
+    boolean isRepaired = true;
+    for (Action action : actions) {
+      isRepaired &= repair(project, uri, out, objectsDir, action);
     }
-    return true;
+    return isRepaired;
+  }
+
+  private boolean repair(
+      Project.NameKey project, URIish uri, OutputStream out, Path objectsDir, Action action)
+      throws InterruptedIOException {
+    boolean isRepaired =
+        switch (action) {
+          case COPY_PACKS -> copyPacksTo(objectsDir.resolve("pack"), uri, out);
+        };
+    if (!isRepaired) {
+      repLog.atSevere().log("Repair (%s) failed for %s on %s", action, project.get(), uri);
+    }
+    return isRepaired;
   }
 
   public static boolean canCopy(URIish uri) {
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/RepairCommand.java b/src/main/java/com/googlesource/gerrit/plugins/replication/RepairCommand.java
index ad6648a..6e04f8a 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/RepairCommand.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/RepairCommand.java
@@ -21,10 +21,12 @@
 import com.google.gerrit.sshd.CommandMetaData;
 import com.google.gerrit.sshd.SshCommand;
 import com.google.inject.Inject;
+import com.googlesource.gerrit.plugins.replication.ProjectRepairer.Action;
 import com.googlesource.gerrit.plugins.replication.api.ReplicationConfig;
 import java.io.IOException;
 import java.io.InterruptedIOException;
 import java.io.OutputStream;
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashSet;
@@ -47,13 +49,19 @@
       usage = "substring URL must match (or * to match everything)")
   private String urlMatch;
 
+  private final List<Action> actions = new ArrayList<>();
+
   @Option(
       name = "--copy-packs",
       usage = "rsync objects/pack files to SSH destinations before triggering replication")
-  private boolean copyPacks;
+  void setCopyPacks(@SuppressWarnings("unused") boolean arg) {
+    actions.add(Action.COPY_PACKS);
+  }
 
   @Option(name = "--full", usage = "run all supported repair actions (default)")
-  private boolean full;
+  void setFull(@SuppressWarnings("unused") boolean arg) {
+    actions.addAll(Action.all());
+  }
 
   @Inject private ProjectCache projectCache;
   @Inject private ReplicationDestinations destinations;
@@ -73,17 +81,18 @@
       throw die(e);
     }
 
-    if (!copyPacks) {
-      full = true;
-    }
-
-    Set<URIish> failedUris = repair(project);
+    Set<URIish> failedUris = repair(project, repairActions());
     if (!failedUris.isEmpty()) {
       throw new UnloggedFailure(1, "Repair failed for " + failedUris.size() + " destination(s)");
     }
   }
 
-  private Set<URIish> repair(Project.NameKey project) throws Failure, InterruptedIOException {
+  private Collection<Action> repairActions() {
+    return actions.isEmpty() ? Action.all() : actions;
+  }
+
+  private Set<URIish> repair(Project.NameKey project, Collection<Action> actions)
+      throws Failure, InterruptedIOException {
     Set<URIish> copyTargets = new HashSet<>();
     Collection<URIish> destUris =
         destinations
@@ -92,7 +101,7 @@
     for (URIish uri : destUris) {
       if (!ProjectRepairer.canCopy(uri)) {
         writeStdErrSync(
-            "Warning: skipping " + uri + " as copy-packs only supports plain SSH destinations");
+            "Warning: skipping " + uri + " as repair only supports plain SSH destinations");
         continue;
       }
       copyTargets.add(uri);
@@ -106,7 +115,7 @@
     OutputStream out = getFlushingOutputStream();
     for (URIish uri : copyTargets) {
       writeStdOutSync("\nRepairing " + uri + " ...");
-      if (projectRepairer.repair(project, uri, out, full || copyPacks)) {
+      if (projectRepairer.repair(project, uri, out, actions)) {
         writeStdOutSync(
             "\nRunning replication start for " + project.get() + " to " + uri.toString() + " ...");
         replicationStarter.start(