Merge branch 'stable-3.0' into stable-3.1
* stable-3.0:
Fix so that GWTUI js file is not executed under PolyGerrit
DeleteProjectIT: Check that project gets unwatched after deletion
DeleteProjectIT: Assert that after forced delete reindexing happened
DeleteProjectIT: Add ssh delete watched project test similar to http
Fix reindex after project deletion
Upgrade bazlets to latest stable-2.16 to build with 2.16.13 API
Upgrade bazlets to latest stable-2.16
Upgrade bazlets to latest stable-2.15 to build with 2.15.18 API
Upgrade bazlets to latest stable-2.15
Upgrade bazlets to latest stable-2.14
Bazel: Migrate workspace status script to python
Upgrade bazlets to latest stable-2.16
Upgrade bazlets to latest stable-2.15
Upgrade bazlets to latest stable-2.14
Bump Bazel version to 1.1.0
build.md: Correct in-tree test command
Replace bazel-genfiles with bazel-bin in documentation
Adjust DeleteProjectIT and DatabaseDeleteHandler to API changes.
Change-Id: I7dd082b728fa7a98b225c10ef6d2ecf11e7daa09
diff --git a/src/main/java/com/googlesource/gerrit/plugins/deleteproject/DeleteAction.java b/src/main/java/com/googlesource/gerrit/plugins/deleteproject/DeleteAction.java
index d9f427c..459d819 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/deleteproject/DeleteAction.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/deleteproject/DeleteAction.java
@@ -20,6 +20,7 @@
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.googlesource.gerrit.plugins.deleteproject.cache.CacheDeleteHandler;
+import com.googlesource.gerrit.plugins.deleteproject.database.DatabaseDeleteHandler;
import com.googlesource.gerrit.plugins.deleteproject.fs.FilesystemDeleteHandler;
public class DeleteAction extends DeleteProject implements UiAction<ProjectResource> {
@@ -28,6 +29,7 @@
@Inject
DeleteAction(
ProtectedProjects protectedProjects,
+ DatabaseDeleteHandler dbHandler,
FilesystemDeleteHandler fsHandler,
CacheDeleteHandler cacheHandler,
Provider<CurrentUser> userProvider,
@@ -35,7 +37,15 @@
DeletePreconditions preConditions,
Configuration cfg,
HideProject hideProject) {
- super(fsHandler, cacheHandler, userProvider, deleteLog, preConditions, cfg, hideProject);
+ super(
+ dbHandler,
+ fsHandler,
+ cacheHandler,
+ userProvider,
+ deleteLog,
+ preConditions,
+ cfg,
+ hideProject);
this.protectedProjects = protectedProjects;
}
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 b1de7fa..f8e4c2b 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/deleteproject/DeleteProject.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/deleteproject/DeleteProject.java
@@ -27,6 +27,7 @@
import com.google.inject.Singleton;
import com.googlesource.gerrit.plugins.deleteproject.DeleteProject.Input;
import com.googlesource.gerrit.plugins.deleteproject.cache.CacheDeleteHandler;
+import com.googlesource.gerrit.plugins.deleteproject.database.DatabaseDeleteHandler;
import com.googlesource.gerrit.plugins.deleteproject.fs.FilesystemDeleteHandler;
import java.io.IOException;
import org.eclipse.jgit.errors.RepositoryNotFoundException;
@@ -40,6 +41,7 @@
protected final DeletePreconditions preConditions;
+ private final DatabaseDeleteHandler dbHandler;
private final FilesystemDeleteHandler fsHandler;
private final CacheDeleteHandler cacheHandler;
private final Provider<CurrentUser> userProvider;
@@ -49,6 +51,7 @@
@Inject
DeleteProject(
+ DatabaseDeleteHandler dbHandler,
FilesystemDeleteHandler fsHandler,
CacheDeleteHandler cacheHandler,
Provider<CurrentUser> userProvider,
@@ -56,6 +59,7 @@
DeletePreconditions preConditions,
Configuration cfg,
HideProject hideProject) {
+ this.dbHandler = dbHandler;
this.fsHandler = fsHandler;
this.cacheHandler = cacheHandler;
this.userProvider = userProvider;
@@ -80,6 +84,7 @@
Exception ex = null;
try {
if (!preserve || !cfg.projectOnPreserveHidden()) {
+ dbHandler.delete(project);
try {
fsHandler.delete(project, preserve);
} catch (RepositoryNotFoundException e) {
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
new file mode 100644
index 0000000..9ee3cbb
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/deleteproject/database/DatabaseDeleteHandler.java
@@ -0,0 +1,121 @@
+// Copyright (C) 2013 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.database;
+
+import static java.util.Collections.singleton;
+import static java.util.stream.Collectors.toList;
+
+import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.server.StarredChangesUtil;
+import com.google.gerrit.server.UserInitiated;
+import com.google.gerrit.server.account.AccountState;
+import com.google.gerrit.server.account.AccountsUpdate;
+import com.google.gerrit.server.account.ProjectWatches.ProjectWatchKey;
+import com.google.gerrit.server.git.GitRepositoryManager;
+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;
+import com.google.gerrit.server.query.account.InternalAccountQuery;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import java.io.IOException;
+import java.util.List;
+import org.eclipse.jgit.errors.ConfigInvalidException;
+
+public class DatabaseDeleteHandler {
+ private static final FluentLogger log = FluentLogger.forEnclosingClass();
+
+ private final StarredChangesUtil starredChangesUtil;
+ private final ChangeIndexer indexer;
+ private final Provider<InternalAccountQuery> accountQueryProvider;
+ private final Provider<AccountsUpdate> accountsUpdateProvider;
+ private final ChangeNotes.Factory schemaFactoryNoteDb;
+ private final GitRepositoryManager repoManager;
+
+ @Inject
+ public DatabaseDeleteHandler(
+ StarredChangesUtil starredChangesUtil,
+ ChangeIndexer indexer,
+ ChangeNotes.Factory schemaFactoryNoteDb,
+ GitRepositoryManager repoManager,
+ Provider<InternalAccountQuery> accountQueryProvider,
+ @UserInitiated Provider<AccountsUpdate> accountsUpdateProvider) {
+ this.starredChangesUtil = starredChangesUtil;
+ this.indexer = indexer;
+ this.accountQueryProvider = accountQueryProvider;
+ this.accountsUpdateProvider = accountsUpdateProvider;
+ this.schemaFactoryNoteDb = schemaFactoryNoteDb;
+ this.repoManager = repoManager;
+ }
+
+ public void delete(Project project) throws IOException {
+ atomicDelete(project, getChangesListFromNoteDb(project));
+ }
+
+ private List<Change.Id> getChangesListFromNoteDb(Project project) throws IOException {
+ Project.NameKey projectKey = project.getNameKey();
+ List<Change.Id> changeIds =
+ schemaFactoryNoteDb
+ .scan(repoManager.openRepository(projectKey), projectKey)
+ .map(ChangeNotesResult::id)
+ .collect(toList());
+ log.atFine().log(
+ "Number of changes in noteDb related to project %s are %d",
+ projectKey.get(), changeIds.size());
+ return changeIds;
+ }
+
+ private void deleteChanges(List<Change.Id> changeIds) {
+
+ for (Change.Id id : changeIds) {
+ try {
+ starredChangesUtil.unstarAllForChangeDeletion(id);
+ } catch (NoSuchChangeException | IOException e) {
+ // we can ignore the exception during delete
+ }
+ // Delete from the secondary index
+ indexer.delete(id);
+ }
+ }
+
+ public void atomicDelete(Project project, List<Change.Id> changeIds) {
+
+ deleteChanges(changeIds);
+
+ for (AccountState a : accountQueryProvider.get().byWatchedProject(project.getNameKey())) {
+ Account.Id accountId = a.account().id();
+ for (ProjectWatchKey watchKey : a.projectWatches().keySet()) {
+ if (project.getNameKey().equals(watchKey.project())) {
+ try {
+ accountsUpdateProvider
+ .get()
+ .update(
+ "Delete Project Watches via API",
+ accountId,
+ u -> u.deleteProjectWatches(singleton(watchKey)));
+ } catch (IOException | ConfigInvalidException e) {
+ log.atSevere().withCause(e).log(
+ "Removing watch entry for user %s in project %s failed.",
+ a.userName().orElse("[unknown]"), project.getName());
+ }
+ }
+ }
+ }
+ }
+}
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 745ae46..d83fac7 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/deleteproject/DeleteProjectIT.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/deleteproject/DeleteProjectIT.java
@@ -34,6 +34,7 @@
import com.google.gerrit.entities.Project;
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.project.ProjectConfig;
import com.google.inject.Inject;
import com.googlesource.gerrit.plugins.deleteproject.DeleteProject.Input;
@@ -87,6 +88,7 @@
RestResponse r = httpDeleteProjectHelper(true);
r.assertNoContent();
assertThat(projectDir.exists()).isFalse();
+ assertAllChangesDeletedInIndex();
}
@Test
@@ -105,6 +107,8 @@
RestResponse r = httpDeleteProjectHelper(true);
r.assertNoContent();
assertThat(projectDir.exists()).isFalse();
+ assertAllChangesDeletedInIndex();
+ assertWatchRemoved();
}
@Test
@@ -147,6 +151,19 @@
adminSshSession.exec(cmd);
assertThat(adminSshSession.getError()).isNull();
assertThat(projectDir.exists()).isFalse();
+ assertAllChangesDeletedInIndex();
+ }
+
+ @Test
+ @UseLocalDisk
+ public void testSshDeleteProjectWithWatches() throws Exception {
+ watch(project.get());
+ String cmd = createDeleteCommand(project.get());
+ adminSshSession.exec(cmd);
+ assertThat(adminSshSession.getError()).isNull();
+ assertThat(projectDir.exists()).isFalse();
+ assertAllChangesDeletedInIndex();
+ assertWatchRemoved();
}
@Test
@@ -224,6 +241,7 @@
assertThat(isEmpty(archiveFolder.toPath())).isFalse();
assertThat(containsDeletedProject(archiveFolder.toPath(), project.get())).isTrue();
assertThat(projectDir.exists()).isFalse();
+ assertAllChangesDeletedInIndex();
}
@Test
@@ -255,7 +273,7 @@
assertThat(containsDeletedProject(archiveFolder.toPath().resolve(PARENT_FOLDER), name))
.isTrue();
assertThat(projectDir.exists()).isFalse();
-
+ assertAllChangesDeletedInIndex();
assertThat(parentFolder.toFile().exists()).isFalse();
}
@@ -312,4 +330,12 @@
return dirStream.anyMatch(d -> d.toString().contains(projectName));
}
}
+
+ private void assertAllChangesDeletedInIndex() {
+ assertThat(queryProvider.get().byProject(project)).isEmpty();
+ }
+
+ private void assertWatchRemoved() throws RestApiException {
+ assertThat(gApi.accounts().self().getWatchedProjects()).isEmpty();
+ }
}