Add Gerrit multi-primary utilities

Change-Id: I99d58dd6a1bf14edb8456b8454f287b71287e0a4
diff --git a/README.md b/README.md
index ec4eb15..f52eb95 100644
--- a/README.md
+++ b/README.md
@@ -20,3 +20,6 @@
 
 [Administration Scripts](/admin/)
 ------------------------
+
+[Gerrit multi-primary Scripts](/multi-primary/)
+------------------------
diff --git a/multi-primary/README.md b/multi-primary/README.md
new file mode 100644
index 0000000..cb7d092
--- /dev/null
+++ b/multi-primary/README.md
@@ -0,0 +1,11 @@
+Gerrit multi-primary utilities
+==============================
+
+Overview
+--------
+Set of utilities to manage the setup and administration of Gerrit multi-primary
+installation and BAU operations.
+
+Index
+-----
+* [globalrefdb](globalrefdb.md) - Checks the repository local-refdb vs. global-refdb
diff --git a/multi-primary/globalrefdb.groovy b/multi-primary/globalrefdb.groovy
new file mode 100644
index 0000000..b47cdb8
--- /dev/null
+++ b/multi-primary/globalrefdb.groovy
@@ -0,0 +1,108 @@
+// Copyright (C) 2023 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.
+
+import com.google.gerrit.common.data.*
+import com.google.gerrit.sshd.*
+import com.google.gerrit.extensions.annotations.*
+import com.google.gerrit.server.project.*
+import com.google.gerrit.server.account.*
+import com.google.gerrit.server.IdentifiedUser
+import com.google.inject.*
+import org.kohsuke.args4j.*
+import com.google.gerrit.server.git.*
+import com.google.gerrit.entities.*
+import org.eclipse.jgit.errors.*
+import com.gerritforge.gerrit.globalrefdb.*
+import com.google.gerrit.extensions.registration.*
+import org.eclipse.jgit.lib.*
+
+abstract class BaseSshCommand extends SshCommand {
+  void println(String msg) {
+    stdout.println msg
+    stdout.flush()
+  }
+
+  void error(String msg) {
+    stderr.println msg
+    stderr.flush()
+  }
+}
+
+@Export("check")
+@CommandMetaData(description = "Check local refs alignment against the global-refdb for a project")
+@RequiresCapability(GlobalCapability.ADMINISTRATE_SERVER)
+class ProjectRefsCheck extends BaseSshCommand {
+
+  @Argument(index = 0, usage = "Project name", metaVar = "PROJECT", required = true)
+  String project
+
+  @Option(name = "--verbose", usage = "Display verbose check results for all refs")
+  boolean verbose = false
+
+  @Option(name = "--ref", usage = "Check only one ref")
+  String singleRef
+
+  @Inject
+  GitRepositoryManager repoMgr
+
+  @Inject
+  DynamicItem<GlobalRefDatabase> globalRefDb
+
+  public void run() {
+    try {
+      def projectName = Project.nameKey(project)
+
+      repoMgr.openRepository(projectName).with { repo ->
+        def upToDate = true
+
+        if (singleRef) {
+          println "Checking project $project:$singleRef ..."
+          def ref = repo.refDatabase.exactRef(singleRef)
+          if (!ref) {
+            error "Project $project does not have $singleRef"
+            return
+          }
+
+          upToDate = checkRef(projectName, repo, ref)
+        } else {
+          println "Checking project $project ..."
+          repo.refDatabase.refs.each { ref ->
+            checkRef(projectName, repo, ref)
+          }
+        }
+
+        println "Result: ${upToDate ? 'UP-TO-DATE':'OUTDATED'}"
+      }
+    } catch (RepositoryNotFoundException e) {
+      error "Project $project not found"
+    }
+  }
+
+  boolean checkRef(Project.NameKey projectName, Repository repo, Ref ref)
+  {
+    def isUpToDate = globalRefDb.get().isUpToDate(projectName, ref)
+
+    if (verbose && isUpToDate) {
+      println "[UP-TO-DATE] ${ref.name}"
+    }
+
+    if (!isUpToDate) {
+      def globalRef = globalRefDb.get().get(projectName, ref.name, String.class)
+      println "[OUTDATED] ${ref.name}:${ref.objectId.name} <> ${globalRef.orElse('MISSING')}"
+    }
+  }
+}
+
+commands = [ ProjectRefsCheck ]
+
diff --git a/multi-primary/globalrefdb.md b/multi-primary/globalrefdb.md
new file mode 100644
index 0000000..17f74f6
--- /dev/null
+++ b/multi-primary/globalrefdb.md
@@ -0,0 +1,30 @@
+Global-refdb Check utility
+==============================
+
+NAME
+----
+`globalrefdb check` utility compare the repository refs status against the global-refdb.
+
+SYNOPSIS
+--------
+>     ssh -p <port> <host> globalrefdb check PROJECT [--ref <ref name>] [--verbose]
+
+DESCRIPTION
+-----------
+Verify if the local with the global refs for a project, reporting all differences
+between the two SHA1s. The operation can operate on the individual ref passed as
+a parameter.
+
+ACCESS
+------
+Any user who has been granted the 'Administrate Server' capability.
+
+SCRIPTING
+---------
+This command is intended to be used in scripts.
+
+EXAMPLES
+--------
+Check if all refs of the All-Users project are up-to-date with the global-refdb:
+
+>     $ ssh -p 29418 review.example.com globalrefdb check All-Users