Merge branch 'stable-2.14' into stable-2.15
* stable-2.14:
Bazel: Migrate workspace status script to python
Change-Id: Ic4877ce81c885f1b36c96745b7b9ef8772522a61
diff --git a/.gitignore b/.gitignore
index 1c54bab..681ddba 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,8 @@
+# `LC_COLLATE=C sort`
+/.apt_generated/
/.classpath
/.project
/.settings/
/bazel-*
+/bin/
/eclipse-out/
diff --git a/README.md b/README.md
index b133981..1ed0bf5 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,5 @@
# Rename project plugin for Gerrit Code Review
-This plugin currently only supports Gerrit version 2.14.X.
+This plugin currently supports Gerrit version 2.14.X and 2.15.X with changes in reviewDb, noteDB is not yet supported.
For more information, see: `src/main/resources/Documentation/about.md`
diff --git a/WORKSPACE b/WORKSPACE
index b2c7568..72d4b7a 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -3,10 +3,19 @@
load("//:bazlets.bzl", "load_bazlets")
load_bazlets(
- commit = "e48c6a61d5a3f9ab4589f324fc85b8e3777d8854",
+ commit = "d735ef54dfd38f99106d5635228156139c0b57fd",
#local_path = "/home/<user>/projects/bazlets",
)
+# Snapshot Plugin API
+#load(
+# "@com_googlesource_gerrit_bazlets//:gerrit_api_maven_local.bzl",
+# "gerrit_api_maven_local",
+#)
+
+# Load snapshot Plugin API
+#gerrit_api_maven_local()
+
# Release Plugin API
load(
"@com_googlesource_gerrit_bazlets//:gerrit_api.bzl",
diff --git a/external_plugin_deps.bzl b/external_plugin_deps.bzl
index 1d8851b..c1932a9 100644
--- a/external_plugin_deps.bzl
+++ b/external_plugin_deps.bzl
@@ -3,8 +3,8 @@
def external_plugin_deps():
maven_jar(
name = "mockito",
- artifact = "org.mockito:mockito-core:2.27.0",
- sha1 = "835fc3283b481f4758b8ef464cd560c649c08b00",
+ artifact = "org.mockito:mockito-core:2.28.2",
+ sha1 = "91110215a8cb9b77a46e045ee758f77d79167cc0",
deps = [
"@byte-buddy//jar",
"@byte-buddy-agent//jar",
diff --git a/src/main/java/com/googlesource/gerrit/plugins/renameproject/RenameProject.java b/src/main/java/com/googlesource/gerrit/plugins/renameproject/RenameProject.java
index 3afc396..e6a320d 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/renameproject/RenameProject.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/renameproject/RenameProject.java
@@ -18,7 +18,9 @@
import static com.googlesource.gerrit.plugins.renameproject.RenameProjectCapability.RENAME_PROJECT;
import com.google.common.base.Strings;
+import com.google.common.cache.Cache;
import com.google.gerrit.extensions.annotations.PluginName;
+import com.google.gerrit.extensions.api.access.PluginPermission;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
@@ -26,13 +28,15 @@
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
-import com.google.gerrit.server.account.CapabilityControl;
import com.google.gerrit.server.extensions.events.PluginEvent;
+import com.google.gerrit.server.permissions.GlobalPermission;
+import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.gerrit.server.project.ProjectResource;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
+import com.google.inject.name.Named;
import com.googlesource.gerrit.plugins.renameproject.cache.CacheRenameHandler;
import com.googlesource.gerrit.plugins.renameproject.conditions.RenamePreconditions;
import com.googlesource.gerrit.plugins.renameproject.database.DatabaseRenameHandler;
@@ -54,6 +58,7 @@
static final int WARNING_LIMIT = 5000;
private static final Logger log = LoggerFactory.getLogger(RenameProject.class);
+ private static final String CACHE_NAME = "changeid_project";
private final DatabaseRenameHandler dbHandler;
private final FilesystemRenameHandler fsHandler;
@@ -65,6 +70,8 @@
private final PluginEvent pluginEvent;
private final String pluginName;
private final RenameLog renameLog;
+ private final PermissionBackend permissionBackend;
+ private final Cache<Change.Id, String> changeIdProjectCache;
@Inject
RenameProject(
@@ -77,7 +84,9 @@
LockUnlockProject lockUnlockProject,
PluginEvent pluginEvent,
@PluginName String pluginName,
- RenameLog renameLog) {
+ RenameLog renameLog,
+ PermissionBackend permissionBackend,
+ @Named(CACHE_NAME) Cache<Change.Id, String> changeIdProjectCache) {
this.dbHandler = dbHandler;
this.fsHandler = fsHandler;
this.cacheHandler = cacheHandler;
@@ -88,6 +97,8 @@
this.pluginEvent = pluginEvent;
this.pluginName = pluginName;
this.renameLog = renameLog;
+ this.permissionBackend = permissionBackend;
+ this.changeIdProjectCache = changeIdProjectCache;
}
private void assertNewNameNotNull(Input input) throws BadRequestException {
@@ -103,10 +114,11 @@
}
protected boolean canRename(ProjectResource rsrc) {
- CapabilityControl ctl = userProvider.get().getCapabilities();
- return ctl.canAdministrateServer()
- || ctl.canPerform(pluginName + "-" + RENAME_PROJECT)
- || (ctl.canPerform(pluginName + "-" + RENAME_OWN_PROJECT) && rsrc.getControl().isOwner());
+ PermissionBackend.WithUser userPermission = permissionBackend.user(userProvider);
+ return userPermission.testOrFalse(GlobalPermission.ADMINISTRATE_SERVER)
+ || userPermission.testOrFalse(new PluginPermission(pluginName, RENAME_PROJECT))
+ || (userPermission.testOrFalse(new PluginPermission(pluginName, RENAME_OWN_PROJECT))
+ && rsrc.getControl().isOwner());
}
void assertCanRename(ProjectResource rsrc, Input input, ProgressMonitor pm)
@@ -143,6 +155,9 @@
lockUnlockProject.unlock(newProjectKey);
log.debug("Unlocked the repo {} after rename operation.", newProjectKey.get());
+ // flush old changeId -> Project cache for given changeIds
+ changeIdProjectCache.invalidateAll(changeIds);
+
pluginEvent.fire(pluginName, pluginName, oldProjectKey.get() + ":" + newProjectKey.get());
} catch (Exception e) {
ex = e;
diff --git a/src/main/java/com/googlesource/gerrit/plugins/renameproject/conditions/RenamePreconditions.java b/src/main/java/com/googlesource/gerrit/plugins/renameproject/conditions/RenamePreconditions.java
index e1104dc..2246d50 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/renameproject/conditions/RenamePreconditions.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/renameproject/conditions/RenamePreconditions.java
@@ -22,7 +22,9 @@
import com.google.gerrit.server.config.AllUsersName;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.git.MergeOpRepoManager;
+import com.google.gerrit.server.git.SubmoduleException;
import com.google.gerrit.server.git.SubmoduleOp;
+import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.project.ListChildProjects;
import com.google.gerrit.server.project.ProjectResource;
import com.google.inject.Inject;
@@ -87,14 +89,19 @@
}
private void assertHasNoChildProjects(ProjectResource rsrc) throws CannotRenameProjectException {
- List<ProjectInfo> children = listChildProjectsProvider.get().apply(rsrc);
- if (!children.isEmpty()) {
- String childrenString =
- String.join(", ", children.stream().map(info -> info.name).collect(Collectors.toList()));
- String message =
- String.format("Cannot rename project because it has children: %s", childrenString);
- log.error(message);
- throw new CannotRenameProjectException(message);
+ try {
+ List<ProjectInfo> children = listChildProjectsProvider.get().apply(rsrc);
+ if (!children.isEmpty()) {
+ String childrenString =
+ String.join(
+ ", ", children.stream().map(info -> info.name).collect(Collectors.toList()));
+ String message =
+ String.format("Cannot rename project because it has children: %s", childrenString);
+ log.error(message);
+ throw new CannotRenameProjectException(message);
+ }
+ } catch (PermissionBackendException e) {
+ throw new CannotRenameProjectException(e);
}
}
@@ -113,7 +120,7 @@
throw new CannotRenameProjectException(message);
}
}
- } catch (IOException e) {
+ } catch (IOException | SubmoduleException e) {
throw new CannotRenameProjectException(e);
}
}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/renameproject/database/DatabaseRenameHandler.java b/src/main/java/com/googlesource/gerrit/plugins/renameproject/database/DatabaseRenameHandler.java
index 3215c42..fb6c69f 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/renameproject/database/DatabaseRenameHandler.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/renameproject/database/DatabaseRenameHandler.java
@@ -21,6 +21,7 @@
import com.google.gerrit.server.account.AccountState;
import com.google.gerrit.server.account.WatchConfig;
import com.google.gerrit.server.account.WatchConfig.Accessor;
+import com.google.gerrit.server.account.WatchConfig.NotifyType;
import com.google.gerrit.server.account.WatchConfig.ProjectWatchKey;
import com.google.gerrit.server.query.account.InternalAccountQuery;
import com.google.gwtorm.jdbc.JdbcSchema;
@@ -37,6 +38,8 @@
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
+import java.util.Map;
+import java.util.Set;
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -61,9 +64,13 @@
public List<Change.Id> getChangeIds(Project.NameKey oldProjectKey) throws OrmException {
log.debug("Starting to retrieve changes from the DB for project {}", oldProjectKey.get());
+ return getChangeIdsFromReviewDb(oldProjectKey, schemaFactory.open());
+ }
+ private List<Change.Id> getChangeIdsFromReviewDb(Project.NameKey oldProjectKey, ReviewDb db)
+ throws OrmException {
List<Change.Id> changeIds = new ArrayList<>();
- Connection conn = ((JdbcSchema) schemaFactory.open()).getConnection();
+ Connection conn = ((JdbcSchema) db).getConnection();
String query =
"select change_id from changes where dest_project_name ='" + oldProjectKey.get() + "';";
try (Statement stmt = conn.createStatement();
@@ -89,7 +96,16 @@
ProgressMonitor pm)
throws OrmException {
pm.beginTask("Updating changes in the database");
- Connection conn = ((JdbcSchema) schemaFactory.open()).getConnection();
+ return renameInReviewDb(changes, oldProjectKey, newProjectKey, schemaFactory.open());
+ }
+
+ private List<Change.Id> renameInReviewDb(
+ List<Change.Id> changes,
+ Project.NameKey oldProjectKey,
+ Project.NameKey newProjectKey,
+ ReviewDb db)
+ throws OrmException {
+ Connection conn = ((JdbcSchema) db).getConnection();
try (Statement stmt = conn.createStatement()) {
conn.setAutoCommit(false);
try {
@@ -127,14 +143,22 @@
private void updateWatchEntries(Project.NameKey oldProjectKey, Project.NameKey newProjectKey)
throws OrmException {
- for (AccountState a : accountQueryProvider.get().byWatchedProject(newProjectKey)) {
+ for (AccountState a : accountQueryProvider.get().byWatchedProject(oldProjectKey)) {
Account.Id accountId = a.getAccount().getId();
for (ProjectWatchKey watchKey : a.getProjectWatches().keySet()) {
if (oldProjectKey.equals(watchKey.project())) {
try {
- watchConfig
- .get()
- .upsertProjectWatches(accountId, watchConfig.get().getProjectWatches(accountId));
+ Map<ProjectWatchKey, Set<NotifyType>> newProjectWatches =
+ watchConfig.get().getProjectWatches(accountId);
+
+ newProjectWatches.put(
+ ProjectWatchKey.create(newProjectKey, watchKey.filter()),
+ a.getProjectWatches().get(watchKey));
+
+ newProjectWatches.remove(watchKey);
+
+ watchConfig.get().deleteAllProjectWatches(accountId);
+ watchConfig.get().upsertProjectWatches(accountId, newProjectWatches);
} catch (ConfigInvalidException e) {
log.error(
"Updating watch entry for user {} in project {} failed. Watch config found invalid.",
diff --git a/src/main/resources/Documentation/about.md b/src/main/resources/Documentation/about.md
index e151be1..da04a05 100644
--- a/src/main/resources/Documentation/about.md
+++ b/src/main/resources/Documentation/about.md
@@ -27,12 +27,6 @@
If choosing to rename "All-Users", you cannot rename the project as this action is prohibited.
-* You cannot rename projects that are watched by users
-
- If you rename a project that is actively watched by users, the watches are
- not updated to the new project name, leading to the loss of the notifications
- by all users that were watching the original project.
-
* You should limit project renames to administrator users
Because of all the above caveats, it is not recommended to allow any non-admin
diff --git a/src/test/java/com/googlesource/gerrit/plugins/renameproject/RenameIT.java b/src/test/java/com/googlesource/gerrit/plugins/renameproject/RenameIT.java
index 879e660..c0ae8f0 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/renameproject/RenameIT.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/renameproject/RenameIT.java
@@ -16,13 +16,20 @@
import static com.google.common.truth.Truth.assertThat;
+import com.google.common.cache.Cache;
import com.google.gerrit.acceptance.LightweightPluginDaemonTest;
+import com.google.gerrit.acceptance.PushOneCommit.Result;
import com.google.gerrit.acceptance.TestPlugin;
import com.google.gerrit.acceptance.UseLocalDisk;
import com.google.gerrit.acceptance.UseSsh;
+import com.google.gerrit.extensions.client.ProjectWatchInfo;
+import com.google.gerrit.reviewdb.client.Change.Id;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.client.Project.NameKey;
import com.google.gerrit.server.project.ProjectState;
+import com.google.inject.Inject;
+import java.util.List;
+import javax.inject.Named;
import org.eclipse.jgit.junit.TestRepository;
import org.junit.Test;
@@ -35,6 +42,11 @@
private static final String PLUGIN_NAME = "rename-project";
private static final String NEW_PROJECT_NAME = "newProject";
+ private static final String CACHE_NAME = "changeid_project";
+
+ @Inject
+ @Named(CACHE_NAME)
+ private Cache<Id, String> changeIdProjectCache;
@Test
@UseLocalDisk
@@ -100,4 +112,37 @@
adminSshSession.exec(PLUGIN_NAME + " " + subProject.get() + " " + NEW_PROJECT_NAME);
adminSshSession.assertFailure();
}
+
+ @Test
+ @UseLocalDisk
+ public void testRenameWatchedProject() throws Exception {
+ String oldProject = project.get();
+ watch(oldProject);
+
+ List<ProjectWatchInfo> watchedProjects = gApi.accounts().self().getWatchedProjects();
+ assertThat(watchedProjects.stream().allMatch(pwi -> pwi.project.equals(oldProject))).isTrue();
+
+ adminSshSession.exec(PLUGIN_NAME + " " + oldProject + " " + NEW_PROJECT_NAME);
+ adminSshSession.assertSuccess();
+
+ watchedProjects = gApi.accounts().self().getWatchedProjects();
+ assertThat(watchedProjects.stream().allMatch(pwi -> pwi.project.equals(NEW_PROJECT_NAME)))
+ .isTrue();
+ assertThat(watchedProjects.size()).isEqualTo(1);
+ }
+
+ @Test
+ @UseLocalDisk
+ public void testRenameClearedOldChangeIdLinkInCaches() throws Exception {
+ Result result = createChange();
+ String oldProject = project.get();
+
+ Id changeID = result.getChange().getId();
+ changeIdProjectCache.put(changeID, oldProject);
+
+ adminSshSession.exec(PLUGIN_NAME + " " + oldProject + " " + NEW_PROJECT_NAME);
+ adminSshSession.assertSuccess();
+
+ assertThat(changeIdProjectCache.getIfPresent(changeID)).isNull();
+ }
}
diff --git a/tools/BUILD b/tools/BUILD
new file mode 100644
index 0000000..c5ed0b7
--- /dev/null
+++ b/tools/BUILD
@@ -0,0 +1 @@
+# Empty file required by Bazel
diff --git a/tools/eclipse/project.sh b/tools/eclipse/project.sh
index 2b895ff..3003cc2 100755
--- a/tools/eclipse/project.sh
+++ b/tools/eclipse/project.sh
@@ -13,4 +13,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-`bazel query @com_googlesource_gerrit_bazlets//tools/eclipse:project --output location | sed s/BUILD:.*//`project.py -n rename-project -r .
+path=$(bazel query @com_googlesource_gerrit_bazlets//tools/eclipse:project \
+ --output location | sed s/BUILD:.*//)
+${path}project.py -n rename-project -r . "$@"