Introduce all-project-changes-deleted-from-index event When deleting a project, its related changes are also removed from the index. This used to be notified via single change-index events, for each change deleted from the index. This however was potentially causing a large number of events to be published and consumed, making it impractical and inefficient to process one-by-one. Leverage a new hook introduced at I4c8a53629 to trigger a new dedicated event that can be processed by consumers to delete changes in bulk from their index. Depends-On: I4c8a536290800d7b93b9f62d2e7ed959fceebb6b Bug: Issue 440670678 Change-Id: I7a171837bca5103094caa9c302afb6409f075094
diff --git a/src/main/java/com/googlesource/gerrit/plugins/deleteproject/AllProjectChangesDeletedFromIndexEvent.java b/src/main/java/com/googlesource/gerrit/plugins/deleteproject/AllProjectChangesDeletedFromIndexEvent.java new file mode 100644 index 0000000..e94c5fb --- /dev/null +++ b/src/main/java/com/googlesource/gerrit/plugins/deleteproject/AllProjectChangesDeletedFromIndexEvent.java
@@ -0,0 +1,33 @@ +// Copyright (C) 2025 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.googlesource.gerrit.plugins.deleteproject; + +import com.google.gerrit.entities.Project; +import com.google.gerrit.entities.Project.NameKey; +import com.google.gerrit.server.events.ProjectEvent; + +public class AllProjectChangesDeletedFromIndexEvent extends ProjectEvent { + public static final String TYPE = "all-project-changes-deleted-from-index"; + public String projectName; + + public AllProjectChangesDeletedFromIndexEvent() { + super(TYPE); + } + + @Override + public NameKey getProjectNameKey() { + return Project.nameKey(projectName); + } +}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/deleteproject/DeleteProject.java b/src/main/java/com/googlesource/gerrit/plugins/deleteproject/DeleteProject.java index 75a948a..255fe3d 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/deleteproject/DeleteProject.java +++ b/src/main/java/com/googlesource/gerrit/plugins/deleteproject/DeleteProject.java
@@ -16,6 +16,7 @@ import com.google.gerrit.common.Nullable; import com.google.gerrit.entities.Project; +import com.google.gerrit.extensions.events.ChangeIndexedListener; import com.google.gerrit.extensions.registration.DynamicItem; import com.google.gerrit.extensions.restapi.ResourceNotFoundException; import com.google.gerrit.extensions.restapi.Response; @@ -37,7 +38,8 @@ import org.eclipse.jgit.errors.RepositoryNotFoundException; @Singleton -class DeleteProject implements RestModifyView<ProjectResource, Input> { +class DeleteProject implements RestModifyView<ProjectResource, Input>, ChangeIndexedListener { + static class Input { boolean preserve; boolean force; @@ -125,4 +127,19 @@ deleteLog.onDelete((IdentifiedUser) userProvider.get(), project.getNameKey(), input, ex); } } + + @Override + public void onChangeIndexed(String projectName, int id) {} + + @Override + public void onChangeDeleted(int id) {} + + @Override + public void onAllChangesDeletedForProject(String projectName) { + AllProjectChangesDeletedFromIndexEvent event = new AllProjectChangesDeletedFromIndexEvent(); + event.projectName = projectName; + event.instanceId = instanceId; + + dispatcher.get().postEvent(Project.nameKey(projectName), event); + } }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/deleteproject/PluginModule.java b/src/main/java/com/googlesource/gerrit/plugins/deleteproject/PluginModule.java index a2e3f15..cd0510e 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/deleteproject/PluginModule.java +++ b/src/main/java/com/googlesource/gerrit/plugins/deleteproject/PluginModule.java
@@ -20,7 +20,9 @@ import com.google.gerrit.extensions.annotations.Exports; import com.google.gerrit.extensions.config.CapabilityDefinition; +import com.google.gerrit.extensions.events.ChangeIndexedListener; import com.google.gerrit.extensions.events.LifecycleListener; +import com.google.gerrit.extensions.registration.DynamicSet; import com.google.gerrit.extensions.restapi.RestApiModule; import com.google.gerrit.server.events.EventTypes; import com.google.inject.AbstractModule; @@ -62,6 +64,10 @@ } EventTypes.register(ProjectDeletedEvent.TYPE, ProjectDeletedEvent.class); + EventTypes.register( + AllProjectChangesDeletedFromIndexEvent.TYPE, AllProjectChangesDeletedFromIndexEvent.class); + + DynamicSet.bind(binder(), ChangeIndexedListener.class).to(DeleteProject.class); install( new RestApiModule() {
diff --git a/src/main/java/com/googlesource/gerrit/plugins/deleteproject/database/DatabaseDeleteHandler.java b/src/main/java/com/googlesource/gerrit/plugins/deleteproject/database/DatabaseDeleteHandler.java index 99dc164..a40cab3 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/deleteproject/database/DatabaseDeleteHandler.java +++ b/src/main/java/com/googlesource/gerrit/plugins/deleteproject/database/DatabaseDeleteHandler.java
@@ -27,8 +27,7 @@ import com.google.gerrit.server.account.AccountState; import com.google.gerrit.server.account.AccountsUpdate; import com.google.gerrit.server.git.GitRepositoryManager; -import com.google.gerrit.server.index.change.ChangeIndex; -import com.google.gerrit.server.index.change.ChangeIndexCollection; +import com.google.gerrit.server.index.change.ChangeIndexer; import com.google.gerrit.server.notedb.ChangeNotes; import com.google.gerrit.server.notedb.ChangeNotes.Factory.ChangeNotesResult; import com.google.gerrit.server.project.NoSuchChangeException; @@ -44,7 +43,7 @@ private static final FluentLogger log = FluentLogger.forEnclosingClass(); private final StarredChangesWriter starredChangesWriter; - private final ChangeIndexCollection indexes; + private final ChangeIndexer indexer; private final Provider<InternalAccountQuery> accountQueryProvider; private final Provider<AccountsUpdate> accountsUpdateProvider; private final ChangeNotes.Factory schemaFactoryNoteDb; @@ -53,13 +52,13 @@ @Inject public DatabaseDeleteHandler( StarredChangesWriter starredChangesWriter, - ChangeIndexCollection indexes, + ChangeIndexer indexer, ChangeNotes.Factory schemaFactoryNoteDb, GitRepositoryManager repoManager, Provider<InternalAccountQuery> accountQueryProvider, @UserInitiated Provider<AccountsUpdate> accountsUpdateProvider) { this.starredChangesWriter = starredChangesWriter; - this.indexes = indexes; + this.indexer = indexer; this.accountQueryProvider = accountQueryProvider; this.accountsUpdateProvider = accountsUpdateProvider; this.schemaFactoryNoteDb = schemaFactoryNoteDb; @@ -67,7 +66,7 @@ } public void delete(Project project) throws IOException { - deleteChangesFromIndex(project); + indexer.deleteAllForProject(project.getNameKey()); unstarChanges(getChangesListFromNoteDb(project)); deleteProjectWatches(project); } @@ -84,12 +83,6 @@ } } - private void deleteChangesFromIndex(Project project) { - for (ChangeIndex i : indexes.getWriteIndexes()) { - i.deleteAllForProject(project.getNameKey()); - } - } - private void unstarChanges(List<Change.Id> changeIds) { for (Change.Id id : changeIds) { try {
diff --git a/src/test/java/com/googlesource/gerrit/plugins/deleteproject/DeleteProjectIT.java b/src/test/java/com/googlesource/gerrit/plugins/deleteproject/DeleteProjectIT.java index 9c684ef..fa63d85 100644 --- a/src/test/java/com/googlesource/gerrit/plugins/deleteproject/DeleteProjectIT.java +++ b/src/test/java/com/googlesource/gerrit/plugins/deleteproject/DeleteProjectIT.java
@@ -23,6 +23,7 @@ import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; import com.google.common.base.Joiner; +import com.google.common.collect.ImmutableList; import com.google.gerrit.acceptance.LightweightPluginDaemonTest; import com.google.gerrit.acceptance.RestResponse; import com.google.gerrit.acceptance.TestPlugin; @@ -37,6 +38,7 @@ import com.google.gerrit.entities.RefNames; import com.google.gerrit.extensions.client.ProjectState; import com.google.gerrit.extensions.restapi.RestApiException; +import com.google.gerrit.server.events.Event; import com.google.inject.Inject; import com.googlesource.gerrit.plugins.deleteproject.DeleteProject.Input; import java.io.File; @@ -286,6 +288,31 @@ assertThat(parentFolder.toFile().exists()).isFalse(); } + @Test + @UseLocalDisk + public void testHttpDeleteProjectEmitsAllProjectChangesDeletedFromIndex() throws Exception { + RestResponse r = httpDeleteProjectHelper(false); + r.assertNoContent(); + + ImmutableList<Event> events = + eventRecorder.getGenericEvents(AllProjectChangesDeletedFromIndexEvent.TYPE, 1); + + assertThat(((AllProjectChangesDeletedFromIndexEvent) events.getFirst()).getProjectNameKey()) + .isEqualTo(project); + } + + @Test + @UseLocalDisk + public void testSshDeleteProjectEmitsAllProjectChangesDeletedFromIndex() throws Exception { + adminSshSession.exec(createDeleteCommand(project.get())); + + ImmutableList<Event> events = + eventRecorder.getGenericEvents(AllProjectChangesDeletedFromIndexEvent.TYPE, 1); + + assertThat(((AllProjectChangesDeletedFromIndexEvent) events.getFirst()).getProjectNameKey()) + .isEqualTo(project); + } + private File verifyProjectRepoExists(Project.NameKey name) throws IOException { File projectDir; try (Repository projectRepo = repoManager.openRepository(name)) {