Emit refUpdate event upon copy-approvals Change I493b9d7ee0 refactored the copy-approvals code to make it multi-threaded and slice-based. In doing so, it replaced `BatchUpdate.execute()` calls with with low-level `BatchRefUpdate.execute()` calls, introducing a regression whereby `refUpdate` events were no longer emitted. Explicitly fire `refUpdate` events upon the successful copy-approval update of the change meta ref and introduce a test to ensure this behavior is kept. Release-Notes: skip Forward-Compatible: checked Change-Id: I21ac623bd0f1b4a493f7e7984a93bd20911cd3d8
diff --git a/java/com/google/gerrit/server/approval/RecursiveApprovalCopier.java b/java/com/google/gerrit/server/approval/RecursiveApprovalCopier.java index 24595674..d5ee143 100644 --- a/java/com/google/gerrit/server/approval/RecursiveApprovalCopier.java +++ b/java/com/google/gerrit/server/approval/RecursiveApprovalCopier.java
@@ -31,6 +31,7 @@ import com.google.gerrit.git.RefUpdateUtil; import com.google.gerrit.server.FanOutExecutor; import com.google.gerrit.server.InternalUser; +import com.google.gerrit.server.extensions.events.GitReferenceUpdated; import com.google.gerrit.server.git.GitRepositoryManager; import com.google.gerrit.server.notedb.ChangeNotes; import com.google.gerrit.server.notedb.ChangeUpdate; @@ -68,6 +69,7 @@ private final InternalUser.Factory internalUserFactory; private final ApprovalsUtil approvalsUtil; private final ChangeNotes.Factory changeNotesFactory; + private final GitReferenceUpdated gitRefUpdated; private final ListeningExecutorService executor; private final ConcurrentHashMap<Project.NameKey, List<ReceiveCommand>> pendingRefUpdates = @@ -88,12 +90,14 @@ InternalUser.Factory internalUserFactory, ApprovalsUtil approvalsUtil, ChangeNotes.Factory changeNotesFactory, + GitReferenceUpdated gitRefUpdated, @FanOutExecutor ExecutorService executor) { this.batchUpdateFactory = batchUpdateFactory; this.repositoryManager = repositoryManager; this.internalUserFactory = internalUserFactory; this.approvalsUtil = approvalsUtil; this.changeNotesFactory = changeNotesFactory; + this.gitRefUpdated = gitRefUpdated; this.executor = MoreExecutors.listeningDecorator(executor); } @@ -240,6 +244,7 @@ } bu.addCommand(updates); RefUpdateUtil.executeChecked(bu, repository); + gitRefUpdated.fire(project, bu, null); finishedRefUpdates.addAndGet(updates.size()); logProgress();
diff --git a/javatests/com/google/gerrit/acceptance/api/change/CopyApprovalsIT.java b/javatests/com/google/gerrit/acceptance/api/change/CopyApprovalsIT.java index c2f7771..4d1b032 100644 --- a/javatests/com/google/gerrit/acceptance/api/change/CopyApprovalsIT.java +++ b/javatests/com/google/gerrit/acceptance/api/change/CopyApprovalsIT.java
@@ -19,6 +19,7 @@ import com.google.common.collect.ImmutableListMultimap; import com.google.common.collect.Iterables; +import com.google.common.util.concurrent.AtomicLongMap; import com.google.gerrit.acceptance.AbstractDaemonTest; import com.google.gerrit.acceptance.PushOneCommit; import com.google.gerrit.acceptance.PushOneCommit.Result; @@ -30,9 +31,13 @@ import com.google.gerrit.entities.RefNames; import com.google.gerrit.extensions.api.changes.ReviewInput; import com.google.gerrit.extensions.common.ApprovalInfo; +import com.google.gerrit.extensions.events.GitReferenceUpdatedListener; +import com.google.gerrit.extensions.registration.DynamicSet; import com.google.gerrit.server.approval.RecursiveApprovalCopier; import com.google.gerrit.server.notedb.ChangeNotes; +import com.google.inject.AbstractModule; import com.google.inject.Inject; +import com.google.inject.Module; import java.io.IOException; import java.util.ArrayList; import java.util.List; @@ -46,6 +51,21 @@ @Inject private ProjectOperations projectOperations; @Inject private RecursiveApprovalCopier recursiveApprovalCopier; + @Override + public Module createModule() { + return new AbstractModule() { + @Override + protected void configure() { + CopyApprovalsReferenceUpdateListener referenceUpdateListener = + new CopyApprovalsReferenceUpdateListener(); + + bind(CopyApprovalsReferenceUpdateListener.class).toInstance(referenceUpdateListener); + DynamicSet.bind(binder(), GitReferenceUpdatedListener.class) + .toInstance(referenceUpdateListener); + } + }; + } + @Test public void multipleProjects() throws Exception { Project.NameKey secondProject = projectOperations.newProject().name("secondProject").create(); @@ -222,6 +242,37 @@ } @Test + public void refUpdateNotified() throws Exception { + PushOneCommit.Result change = createChange(); + gApi.changes().id(change.getChangeId()).current().review(ReviewInput.recommend()); + + // this amend is a rework so votes will not be copied. + amendChange(change.getChangeId()); + + // votes don't exist on the new patch-set for all changes. + assertThat(gApi.changes().id(change.getChangeId()).current().votes()).isEmpty(); + + // change the project config to make the vote that was not copied to be copied once we do the + // schema upgrade. + try (ProjectConfigUpdate u = updateProject(allProjects)) { + u.getConfig().updateLabelType(LabelId.CODE_REVIEW, b -> b.setCopyAnyScore(true)); + u.save(); + } + + ObjectId metaId = change.getChange().notes().getMetaId(); + recursiveApprovalCopier.persist(project, null); + + ApprovalInfo vote1 = + Iterables.getOnlyElement( + gApi.changes().id(change.getChangeId()).current().votes().values()); + assertThat(vote1.value).isEqualTo(1); + assertThat(vote1._accountId).isEqualTo(admin.id().get()); + + CopyApprovalsReferenceUpdateListener testListener = testListener(); + assertThat(testListener.refUpdateFor(metaId)).isTrue(); + } + + @Test public void oneCorruptChange_otherChangesProcessed() throws Exception { PushOneCommit.Result good = createChange(); gApi.changes().id(good.getChangeId()).current().review(ReviewInput.recommend()); @@ -254,4 +305,22 @@ assertThat(vote1.value).isEqualTo(1); assertThat(vote1._accountId).isEqualTo(admin.id().get()); } + + private CopyApprovalsReferenceUpdateListener testListener() { + return server.getTestInjector().getInstance(CopyApprovalsReferenceUpdateListener.class); + } + + private static class CopyApprovalsReferenceUpdateListener implements GitReferenceUpdatedListener { + private final AtomicLongMap<String> countsByOldObjectId = AtomicLongMap.create(); + + @Override + public void onGitReferenceUpdated(Event event) { + String oldObjectId = event.getOldObjectId(); + countsByOldObjectId.incrementAndGet(oldObjectId); + } + + boolean refUpdateFor(ObjectId metaRef) { + return countsByOldObjectId.containsKey(metaRef.getName()); + } + } }