summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLuca Milanesio <luca.milanesio@gmail.com>2022-05-23 14:18:13 +0100
committerLuca Milanesio <luca.milanesio@gmail.com>2022-05-23 18:27:21 +0000
commit2849b7a1806ea8a66538a6ccac88d20a50feddb2 (patch)
tree2a74696fc88f5f1d279f07612c9e4c4bbd4e9194
parente7b6b42dfd5ad727080c1e48fee5d79d490011f4 (diff)
Expose the CopyApprovals site program as a command
Allow Gerrit admins to copy the approval labels on running server by exposing the functionality as a Gerrit SSH command. The copy-approvals command is limited to the Gerrit admin because may potentially cause a significant load on the server and generate a substantial (e.g. hundreds of thousands) ref-update events. NOTE: This command is going to be dropped in v3.6 because the logic for calculating the inferred labels has been removed. Release-Notes: Introduce copy-approvals SSH command Change-Id: I13f469b950e2de0b366f28e089cb114781dbdb11
-rw-r--r--Documentation/cmd-copy-approvals.txt60
-rw-r--r--java/com/google/gerrit/server/approval/RecursiveApprovalCopier.java27
-rw-r--r--java/com/google/gerrit/sshd/commands/CopyApprovalsCommand.java93
-rw-r--r--java/com/google/gerrit/sshd/commands/DefaultCommandModule.java1
-rw-r--r--javatests/com/google/gerrit/acceptance/api/change/CopyApprovalsIT.java2
-rw-r--r--javatests/com/google/gerrit/acceptance/ssh/SshCommandsIT.java1
6 files changed, 176 insertions, 8 deletions
diff --git a/Documentation/cmd-copy-approvals.txt b/Documentation/cmd-copy-approvals.txt
new file mode 100644
index 0000000000..ba5344f259
--- /dev/null
+++ b/Documentation/cmd-copy-approvals.txt
@@ -0,0 +1,60 @@
+= gerrit copy-approvals
+
+== NAME
+gerrit copy-approvals - Copy all inferred approvals labels to the latest patch-set.
+
+== SYNOPSIS
+[verse]
+--
+_ssh_ -p <port> <host> _gerrit copy-approvals_
+ [--verbose | -v]
+ [PROJECT]...
+--
+
+== DESCRIPTION
+Gerrit has historically computed votes using an inference algorithm that
+was cumulating them from all the patch-sets. That was not efficient since
+it had to take into account copied votes from very old patchsets.
+E.g, votes sometimes need to be copied from ps1 to ps10.
+
+Gerrit copy the approvals from the inferred votes to the latest patch-sets
+once a change receives a new label update.
+
+The copy-approval command scans all the changes of a project and looks for
+all votes that have not been copied yet, calculate the inferred score and
+apply that as copied label to the latest patch-set.
+
+NOTE: The label copied as part of this process receives the grant date of
+the timestamp of the copy-approval command execution, not the one associated
+with the inferred vote.
+
+== OPTIONS
+
+--verbose::
+-v::
+ Display projects/changes impacted by the label copy operation.
+
+== ACCESS
+Only the user with MAINTAIN_SERVER permissions can run this command.
+
+== SCRIPTING
+This command is intended to be used in scripts.
+
+== EXAMPLES
+
+Copy all inferred labels on the project 'foo'
+----
+$ ssh -p 29418 review.example.com gerrit copy-approvals foo
+----
+
+Copy all inferred labels on all projects
+----
+$ ssh -p 29418 review.example.com gerrit copy-approvals
+----
+
+GERRIT
+------
+Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/java/com/google/gerrit/server/approval/RecursiveApprovalCopier.java b/java/com/google/gerrit/server/approval/RecursiveApprovalCopier.java
index 53c2241a6e..87df46523e 100644
--- a/java/com/google/gerrit/server/approval/RecursiveApprovalCopier.java
+++ b/java/com/google/gerrit/server/approval/RecursiveApprovalCopier.java
@@ -16,6 +16,7 @@ package com.google.gerrit.server.approval;
import static com.google.common.collect.ImmutableList.toImmutableList;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.entities.Change;
import com.google.gerrit.entities.Project;
import com.google.gerrit.entities.RefNames;
@@ -30,6 +31,7 @@ import com.google.gerrit.server.update.UpdateException;
import com.google.gerrit.server.util.time.TimeUtil;
import com.google.inject.Inject;
import java.io.IOException;
+import java.util.function.Consumer;
import org.eclipse.jgit.errors.RepositoryNotFoundException;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
@@ -56,11 +58,11 @@ public class RecursiveApprovalCopier {
public void persist()
throws UpdateException, RestApiException, RepositoryNotFoundException, IOException {
for (Project.NameKey project : repositoryManager.list()) {
- persist(project);
+ persist(project, null);
}
}
- public void persist(Project.NameKey project)
+ public void persist(Project.NameKey project, @Nullable Consumer<Change> labelsCopiedListener)
throws IOException, UpdateException, RestApiException, RepositoryNotFoundException {
try (BatchUpdate bu =
batchUpdateFactory.create(project, internalUserFactory.create(), TimeUtil.nowTs());
@@ -70,7 +72,7 @@ public class RecursiveApprovalCopier {
.filter(r -> r.getName().endsWith(RefNames.META_SUFFIX))
.collect(toImmutableList())) {
Change.Id changeId = Change.Id.fromRef(changeMetaRef.getName());
- bu.addOp(changeId, new PersistCopiedVotesOp(approvalsUtil));
+ bu.addOp(changeId, new PersistCopiedVotesOp(approvalsUtil, labelsCopiedListener));
}
bu.execute();
}
@@ -81,28 +83,39 @@ public class RecursiveApprovalCopier {
try (BatchUpdate bu =
batchUpdateFactory.create(project, internalUserFactory.create(), TimeUtil.nowTs())) {
Change.Id changeId = change.getId();
- bu.addOp(changeId, new PersistCopiedVotesOp(approvalsUtil));
+ bu.addOp(changeId, new PersistCopiedVotesOp(approvalsUtil, null));
bu.execute();
}
}
private static class PersistCopiedVotesOp implements BatchUpdateOp {
private final ApprovalsUtil approvalsUtil;
+ private final Consumer<Change> listener;
- PersistCopiedVotesOp(ApprovalsUtil approvalsUtil) {
+ PersistCopiedVotesOp(
+ ApprovalsUtil approvalsUtil, @Nullable Consumer<Change> labelsCopiedListener) {
this.approvalsUtil = approvalsUtil;
+ this.listener = labelsCopiedListener;
}
@Override
public boolean updateChange(ChangeContext ctx) throws IOException {
- ChangeUpdate update = ctx.getUpdate(ctx.getChange().currentPatchSetId());
+ Change change = ctx.getChange();
+ ChangeUpdate update = ctx.getUpdate(change.currentPatchSetId());
approvalsUtil.persistCopiedApprovals(
ctx.getNotes(),
ctx.getNotes().getCurrentPatchSet(),
ctx.getRevWalk(),
ctx.getRepoView().getConfig(),
update);
- return update.hasCopiedApprovals();
+
+ boolean labelsCopied = update.hasCopiedApprovals();
+
+ if (labelsCopied && listener != null) {
+ listener.accept(change);
+ }
+
+ return labelsCopied;
}
}
}
diff --git a/java/com/google/gerrit/sshd/commands/CopyApprovalsCommand.java b/java/com/google/gerrit/sshd/commands/CopyApprovalsCommand.java
new file mode 100644
index 0000000000..eacec289bb
--- /dev/null
+++ b/java/com/google/gerrit/sshd/commands/CopyApprovalsCommand.java
@@ -0,0 +1,93 @@
+// Copyright (C) 2022 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.sshd.commands;
+
+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.approval.RecursiveApprovalCopier;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.sshd.CommandMetaData;
+import com.google.gerrit.sshd.SshCommand;
+import com.google.inject.Inject;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
+import org.kohsuke.args4j.Argument;
+import org.kohsuke.args4j.Option;
+
+@CommandMetaData(
+ name = "copy-approvals",
+ description = "Copy inferred approvals labels to the latest patch-set")
+@RequiresCapability(GlobalCapability.MAINTAIN_SERVER)
+public class CopyApprovalsCommand extends SshCommand {
+
+ private final Set<Project.NameKey> projects = new HashSet<>();
+ private final RecursiveApprovalCopier recursiveApprovalCopier;
+ private final GitRepositoryManager repositoryManager;
+
+ @Argument(
+ index = 0,
+ required = false,
+ multiValued = true,
+ metaVar = "PROJECT",
+ usage = "list of projects to scan for approvals (default: all projects)")
+ void addProject(String project) {
+ projects.add(Project.nameKey(project));
+ }
+
+ @Option(
+ name = "--verbose",
+ aliases = "-v",
+ usage = "display projects/changes impacted by the label copy operation",
+ metaVar = "VERBOSE")
+ private boolean verbose;
+
+ @Inject
+ public CopyApprovalsCommand(
+ RecursiveApprovalCopier recursiveApprovalCopier, GitRepositoryManager repositoryManager) {
+ this.recursiveApprovalCopier = recursiveApprovalCopier;
+ this.repositoryManager = repositoryManager;
+ }
+
+ @Override
+ protected void run() throws Exception {
+ AtomicInteger changesCounter = new AtomicInteger();
+ stdout.println(
+ "Copying inferred approvals labels on " + (projects.isEmpty() ? "all projects" : projects));
+
+ Set<Project.NameKey> projectsList = projects.isEmpty() ? repositoryManager.list() : projects;
+
+ for (Project.NameKey project : projectsList) {
+ stdout.print("> " + project + " : ");
+ recursiveApprovalCopier.persist(
+ project,
+ c -> {
+ if (verbose) {
+ stdout.println(" [" + c.getProject() + "," + c.getChangeId() + "] updated");
+ }
+ changesCounter.incrementAndGet();
+ });
+ stdout.println("DONE");
+ }
+
+ stdout.println(
+ "Labels copied for "
+ + projectsList.size()
+ + " project(s) have impacted "
+ + changesCounter.get()
+ + " change(s)");
+ }
+}
diff --git a/java/com/google/gerrit/sshd/commands/DefaultCommandModule.java b/java/com/google/gerrit/sshd/commands/DefaultCommandModule.java
index e7fe22fe1b..1a08c43b5c 100644
--- a/java/com/google/gerrit/sshd/commands/DefaultCommandModule.java
+++ b/java/com/google/gerrit/sshd/commands/DefaultCommandModule.java
@@ -109,6 +109,7 @@ public class DefaultCommandModule extends CommandModule {
command(gerrit, RenameGroupCommand.class);
command(gerrit, ReviewCommand.class);
+ command(gerrit, CopyApprovalsCommand.class);
command(gerrit, SetProjectCommand.class);
command(gerrit, SetReviewersCommand.class);
command(gerrit, SetTopicCommand.class);
diff --git a/javatests/com/google/gerrit/acceptance/api/change/CopyApprovalsIT.java b/javatests/com/google/gerrit/acceptance/api/change/CopyApprovalsIT.java
index 4b8862e51a..90ee13ef4d 100644
--- a/javatests/com/google/gerrit/acceptance/api/change/CopyApprovalsIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/change/CopyApprovalsIT.java
@@ -147,7 +147,7 @@ public class CopyApprovalsIT extends AbstractDaemonTest {
u.save();
}
- recursiveApprovalCopier.persist(project);
+ recursiveApprovalCopier.persist(project, null);
for (PushOneCommit.Result change : changes) {
ApprovalInfo vote1 =
diff --git a/javatests/com/google/gerrit/acceptance/ssh/SshCommandsIT.java b/javatests/com/google/gerrit/acceptance/ssh/SshCommandsIT.java
index 7224e194cf..469630f64a 100644
--- a/javatests/com/google/gerrit/acceptance/ssh/SshCommandsIT.java
+++ b/javatests/com/google/gerrit/acceptance/ssh/SshCommandsIT.java
@@ -63,6 +63,7 @@ public class SshCommandsIT extends AbstractDaemonTest {
private static final ImmutableList<String> MASTER_ONLY_ROOT_COMMANDS =
ImmutableList.of(
"ban-commit",
+ "copy-approvals",
"create-account",
"create-branch",
"create-group",