Migrate project watches to git (part 2)

This is the second part of migrating project watches from database to
git.

This change:
* bumps the database schema version
* migrates the project watches from database to git (for single
  instance Gerrit servers)
* deletes the database table
* deletes the user.readProjectWatchesFromGit config option

Change-Id: I9dc99f5483fdd72cf6e237236ed0b23f14b60249
Signed-off-by: Edwin Kempin <ekempin@google.com>
This commit is contained in:
Edwin Kempin
2016-07-06 13:40:23 +02:00
parent 10aa4e2bbf
commit b043d448a7
36 changed files with 236 additions and 450 deletions

View File

@@ -60,9 +60,9 @@ import com.google.gerrit.gpg.server.GpgKeys;
import com.google.gerrit.gpg.testutil.TestKey;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountExternalId;
import com.google.gerrit.reviewdb.client.AccountProjectWatch.NotifyType;
import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.account.WatchConfig;
import com.google.gerrit.server.account.WatchConfig.NotifyType;
import com.google.gerrit.server.config.AllUsersName;
import com.google.gerrit.server.git.ProjectConfig;
import com.google.gerrit.server.project.RefPattern;

View File

@@ -23,8 +23,8 @@ import com.google.gerrit.acceptance.Sandboxed;
import com.google.gerrit.acceptance.TestAccount;
import com.google.gerrit.extensions.client.ProjectWatchInfo;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.reviewdb.client.AccountProjectWatch.NotifyType;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.account.WatchConfig.NotifyType;
import com.google.gerrit.server.git.NotifyConfig;
import com.google.gerrit.server.git.ProjectConfig;
import com.google.gerrit.server.mail.Address;

View File

@@ -42,9 +42,6 @@ import java.sql.Timestamp;
* managed {@link AccountGroup}. Multiple records can exist when the user is a
* member of more than one group.</li>
*
* <li>{@link AccountProjectWatch}: user's email settings related to a specific
* {@link Project}. One record per project the user is interested in tracking.</li>
*
* <li>{@link AccountSshKey}: user's public SSH keys, for authentication through
* the internal SSH daemon. One record per SSH key uploaded by the user, keys
* are checked in random order until a match is found.</li>

View File

@@ -1,202 +0,0 @@
// Copyright (C) 2008 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.reviewdb.client;
import com.google.gwtorm.client.Column;
import com.google.gwtorm.client.CompoundKey;
import com.google.gwtorm.client.StringKey;
/** An {@link Account} interested in a {@link Project}. */
public final class AccountProjectWatch {
public enum NotifyType {
// sort by name, except 'ALL' which should stay last
ABANDONED_CHANGES,
ALL_COMMENTS,
NEW_CHANGES,
NEW_PATCHSETS,
SUBMITTED_CHANGES,
ALL
}
public static final String FILTER_ALL = "*";
public static class Key extends CompoundKey<Account.Id> {
private static final long serialVersionUID = 1L;
@Column(id = 1)
protected Account.Id accountId;
@Column(id = 2)
protected Project.NameKey projectName;
@Column(id = 3)
protected Filter filter;
protected Key() {
accountId = new Account.Id();
projectName = new Project.NameKey();
filter = new Filter();
}
public Key(Account.Id a, Project.NameKey g, String f) {
accountId = a;
projectName = g;
filter = new Filter(f);
}
@Override
public Account.Id getParentKey() {
return accountId;
}
public Project.NameKey getProjectName() {
return projectName;
}
public Filter getFilter() {
return filter;
}
@Override
public com.google.gwtorm.client.Key<?>[] members() {
return new com.google.gwtorm.client.Key<?>[] {projectName, filter};
}
}
public static class Filter extends StringKey<com.google.gwtorm.client.Key<?>> {
private static final long serialVersionUID = 1L;
@Column(id = 1)
protected String filter;
protected Filter() {
}
public Filter(String f) {
filter = f != null && !f.isEmpty() ? f : FILTER_ALL;
}
@Override
public String get() {
return filter;
}
@Override
protected void set(String newValue) {
filter = newValue;
}
}
@Column(id = 1, name = Column.NONE)
protected Key key;
/** Automatically send email notifications of new changes? */
@Column(id = 2)
protected boolean notifyNewChanges;
/** Automatically receive comments published to this project */
@Column(id = 3)
protected boolean notifyAllComments;
/** Automatically receive changes submitted to this project */
@Column(id = 4)
protected boolean notifySubmittedChanges;
@Column(id = 5)
protected boolean notifyNewPatchSets;
@Column(id = 6)
protected boolean notifyAbandonedChanges;
protected AccountProjectWatch() {
}
public AccountProjectWatch(final AccountProjectWatch.Key k) {
key = k;
}
public AccountProjectWatch.Key getKey() {
return key;
}
public Account.Id getAccountId() {
return key.accountId;
}
public Project.NameKey getProjectNameKey() {
return key.projectName;
}
public String getFilter() {
return FILTER_ALL.equals(key.filter.get()) ? null : key.filter.get();
}
public boolean isNotify(final NotifyType type) {
switch (type) {
case NEW_CHANGES:
return notifyNewChanges;
case NEW_PATCHSETS:
return notifyNewPatchSets;
case ALL_COMMENTS:
return notifyAllComments;
case SUBMITTED_CHANGES:
return notifySubmittedChanges;
case ABANDONED_CHANGES:
return notifyAbandonedChanges;
case ALL:
break;
}
return false;
}
public void setNotify(final NotifyType type, final boolean v) {
switch (type) {
case NEW_CHANGES:
notifyNewChanges = v;
break;
case NEW_PATCHSETS:
notifyNewPatchSets = v;
break;
case ALL_COMMENTS:
notifyAllComments = v;
break;
case SUBMITTED_CHANGES:
notifySubmittedChanges = v;
break;
case ABANDONED_CHANGES:
notifyAbandonedChanges = v;
break;
case ALL:
notifyNewChanges = v;
notifyNewPatchSets = v;
notifyAllComments = v;
notifySubmittedChanges = v;
notifyAbandonedChanges = v;
break;
}
}
}

View File

@@ -1,37 +0,0 @@
// Copyright (C) 2008 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.reviewdb.server;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountProjectWatch;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gwtorm.server.Access;
import com.google.gwtorm.server.OrmException;
import com.google.gwtorm.server.PrimaryKey;
import com.google.gwtorm.server.Query;
import com.google.gwtorm.server.ResultSet;
public interface AccountProjectWatchAccess extends
Access<AccountProjectWatch, AccountProjectWatch.Key> {
@Override
@PrimaryKey("key")
AccountProjectWatch get(AccountProjectWatch.Key key) throws OrmException;
@Query("WHERE key.accountId = ?")
ResultSet<AccountProjectWatch> byAccount(Account.Id id) throws OrmException;
@Query("WHERE key.projectName = ?")
ResultSet<AccountProjectWatch> byProject(Project.NameKey name) throws OrmException;
}

View File

@@ -71,8 +71,7 @@ public interface ReviewDb extends Schema {
// Deleted @Relation(id = 18)
@Relation(id = 19)
AccountProjectWatchAccess accountProjectWatches();
// Deleted @Relation(id = 19)
// Deleted @Relation(id = 20)

View File

@@ -108,11 +108,6 @@ public class ReviewDbWrapper implements ReviewDb {
return delegate.accountGroupMembersAudit();
}
@Override
public AccountProjectWatchAccess accountProjectWatches() {
return delegate.accountProjectWatches();
}
@Override
public ChangeAccess changes() {
return delegate.changes();

View File

@@ -35,13 +35,6 @@ ON account_group_members (group_id);
CREATE INDEX account_group_id_byInclude
ON account_group_by_id (include_uuid);
-- *********************************************************************
-- AccountProjectWatchAccess
-- @PrimaryKey covers: byAccount
-- covers: byProject
CREATE INDEX account_project_watches_byP
ON account_project_watches (project_name);
-- *********************************************************************
-- ApprovalCategoryAccess

View File

@@ -41,14 +41,6 @@ ON account_group_by_id (include_uuid)
#
-- *********************************************************************
-- AccountProjectWatchAccess
-- @PrimaryKey covers: byAccount
-- covers: byProject
CREATE INDEX acc_project_watches_byProject
ON account_project_watches (project_name)
#
-- *********************************************************************
-- ApprovalCategoryAccess
-- too small to bother indexing

View File

@@ -82,13 +82,6 @@ ON account_group_members (group_id);
CREATE INDEX account_group_id_byInclude
ON account_group_by_id (include_uuid);
-- *********************************************************************
-- AccountProjectWatchAccess
-- @PrimaryKey covers: byAccount
-- covers: byProject
CREATE INDEX account_project_watches_byP
ON account_project_watches (project_name);
-- *********************************************************************
-- ApprovalCategoryAccess

View File

@@ -23,11 +23,10 @@ import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountExternalId;
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.reviewdb.client.AccountGroupMember;
import com.google.gerrit.reviewdb.client.AccountProjectWatch.NotifyType;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.account.WatchConfig.NotifyType;
import com.google.gerrit.server.account.WatchConfig.ProjectWatchKey;
import com.google.gerrit.server.cache.CacheModule;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.index.account.AccountIndexer;
import com.google.gerrit.server.query.account.InternalAccountQuery;
import com.google.gwtorm.server.OrmException;
@@ -40,7 +39,6 @@ import com.google.inject.TypeLiteral;
import com.google.inject.name.Named;
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.lib.Config;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -49,7 +47,6 @@ import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ExecutionException;
@@ -157,7 +154,6 @@ public class AccountCacheImpl implements AccountCache {
private final GroupCache groupCache;
private final GeneralPreferencesLoader loader;
private final LoadingCache<String, Optional<Account.Id>> byName;
private final boolean readFromGit;
private final Provider<WatchConfig.Accessor> watchConfig;
@Inject
@@ -166,14 +162,11 @@ public class AccountCacheImpl implements AccountCache {
GeneralPreferencesLoader loader,
@Named(BYUSER_NAME) LoadingCache<String,
Optional<Account.Id>> byUsername,
@GerritServerConfig Config cfg,
Provider<WatchConfig.Accessor> watchConfig) {
this.schema = sf;
this.groupCache = groupCache;
this.loader = loader;
this.byName = byUsername;
this.readFromGit =
cfg.getBoolean("user", null, "readProjectWatchesFromGit", false);
this.watchConfig = watchConfig;
}
@@ -219,13 +212,8 @@ public class AccountCacheImpl implements AccountCache {
account.setGeneralPreferences(GeneralPreferencesInfo.defaults());
}
Map<ProjectWatchKey, Set<NotifyType>> projectWatches =
readFromGit
? watchConfig.get().getProjectWatches(who)
: GetWatchedProjects.readProjectWatchesFromDb(db, who);
return new AccountState(account, internalGroups, externalIds,
projectWatches);
watchConfig.get().getProjectWatches(who));
}
}

View File

@@ -24,9 +24,9 @@ import com.google.gerrit.common.Nullable;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountExternalId;
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.reviewdb.client.AccountProjectWatch.NotifyType;
import com.google.gerrit.server.CurrentUser.PropertyKey;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.WatchConfig.NotifyType;
import com.google.gerrit.server.account.WatchConfig.ProjectWatchKey;
import java.util.Collection;

View File

@@ -22,13 +22,10 @@ import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountProjectWatch;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.WatchConfig.ProjectWatchKey;
import com.google.gwtorm.server.OrmException;
import com.google.gwtorm.server.ResultSet;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
@@ -36,24 +33,19 @@ import com.google.inject.Singleton;
import org.eclipse.jgit.errors.ConfigInvalidException;
import java.io.IOException;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
@Singleton
public class DeleteWatchedProjects
implements RestModifyView<AccountResource, List<ProjectWatchInfo>> {
private final Provider<ReviewDb> dbProvider;
private final Provider<IdentifiedUser> self;
private final AccountCache accountCache;
private final WatchConfig.Accessor watchConfig;
@Inject
DeleteWatchedProjects(Provider<ReviewDb> dbProvider,
Provider<IdentifiedUser> self,
DeleteWatchedProjects(Provider<IdentifiedUser> self,
AccountCache accountCache,
WatchConfig.Accessor watchConfig) {
this.dbProvider = dbProvider;
this.self = self;
this.accountCache = accountCache;
this.watchConfig = watchConfig;
@@ -73,42 +65,12 @@ public class DeleteWatchedProjects
}
Account.Id accountId = rsrc.getUser().getAccountId();
deleteFromDb(accountId, input);
deleteFromGit(accountId, input);
accountCache.evict(accountId);
return Response.none();
}
private void deleteFromDb(Account.Id accountId, List<ProjectWatchInfo> input)
throws OrmException, IOException {
ResultSet<AccountProjectWatch> watchedProjects =
dbProvider.get().accountProjectWatches().byAccount(accountId);
HashMap<AccountProjectWatch.Key, AccountProjectWatch> watchedProjectsMap =
new HashMap<>();
for (AccountProjectWatch watchedProject : watchedProjects) {
watchedProjectsMap.put(watchedProject.getKey(), watchedProject);
}
List<AccountProjectWatch> watchesToDelete = new LinkedList<>();
for (ProjectWatchInfo projectInfo : input) {
AccountProjectWatch.Key key = new AccountProjectWatch.Key(accountId,
new Project.NameKey(projectInfo.project), projectInfo.filter);
if (watchedProjectsMap.containsKey(key)) {
watchesToDelete.add(watchedProjectsMap.get(key));
}
}
if (!watchesToDelete.isEmpty()) {
dbProvider.get().accountProjectWatches().delete(watchesToDelete);
accountCache.evict(accountId);
}
}
private void deleteFromGit(Account.Id accountId, List<ProjectWatchInfo> input)
throws IOException, ConfigInvalidException {
watchConfig.deleteProjectWatches(
accountId,
input.stream().map(w -> ProjectWatchKey.create(
new Project.NameKey(w.project), w.filter))
.collect(toList()));
accountCache.evict(accountId);
return Response.none();
}
}

View File

@@ -20,25 +20,19 @@ import com.google.gerrit.extensions.client.ProjectWatchInfo;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountProjectWatch;
import com.google.gerrit.reviewdb.client.AccountProjectWatch.NotifyType;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.WatchConfig.NotifyType;
import com.google.gerrit.server.account.WatchConfig.ProjectWatchKey;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.lib.Config;
import java.io.IOException;
import java.util.Collections;
import java.util.Comparator;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
@@ -47,20 +41,13 @@ import java.util.Set;
@Singleton
public class GetWatchedProjects implements RestReadView<AccountResource> {
private final Provider<ReviewDb> dbProvider;
private final Provider<IdentifiedUser> self;
private final boolean readFromGit;
private final WatchConfig.Accessor watchConfig;
@Inject
public GetWatchedProjects(Provider<ReviewDb> dbProvider,
Provider<IdentifiedUser> self,
@GerritServerConfig Config cfg,
public GetWatchedProjects(Provider<IdentifiedUser> self,
WatchConfig.Accessor watchConfig) {
this.dbProvider = dbProvider;
this.self = self;
this.readFromGit =
cfg.getBoolean("user", null, "readProjectWatchesFromGit", false);
this.watchConfig = watchConfig;
}
@@ -73,14 +60,9 @@ public class GetWatchedProjects implements RestReadView<AccountResource> {
+ "of other users");
}
Account.Id accountId = rsrc.getUser().getAccountId();
Map<ProjectWatchKey, Set<NotifyType>> projectWatches =
readFromGit
? watchConfig.getProjectWatches(accountId)
: readProjectWatchesFromDb(dbProvider.get(), accountId);
List<ProjectWatchInfo> projectWatchInfos = new LinkedList<>();
for (Map.Entry<ProjectWatchKey, Set<NotifyType>> e : projectWatches
.entrySet()) {
for (Map.Entry<ProjectWatchKey, Set<NotifyType>> e : watchConfig
.getProjectWatches(accountId).entrySet()) {
ProjectWatchInfo pwi = new ProjectWatchInfo();
pwi.filter = e.getKey().filter();
pwi.project = e.getKey().project().get();
@@ -112,22 +94,4 @@ public class GetWatchedProjects implements RestReadView<AccountResource> {
private static Boolean toBoolean(boolean value) {
return value ? true : null;
}
public static Map<ProjectWatchKey, Set<NotifyType>> readProjectWatchesFromDb(
ReviewDb db, Account.Id who) throws OrmException {
Map<ProjectWatchKey, Set<NotifyType>> projectWatches =
new HashMap<>();
for (AccountProjectWatch apw : db.accountProjectWatches().byAccount(who)) {
ProjectWatchKey key =
ProjectWatchKey.create(apw.getProjectNameKey(), apw.getFilter());
Set<NotifyType> notifyValues = EnumSet.noneOf(NotifyType.class);
for (NotifyType notifyType : NotifyType.values()) {
if (apw.isNotify(notifyType)) {
notifyValues.add(notifyType);
}
}
projectWatches.put(key, notifyValues);
}
return projectWatches;
}
}

View File

@@ -21,11 +21,8 @@ import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountProjectWatch;
import com.google.gerrit.reviewdb.client.AccountProjectWatch.NotifyType;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.WatchConfig.NotifyType;
import com.google.gerrit.server.account.WatchConfig.ProjectWatchKey;
import com.google.gerrit.server.project.ProjectsCollection;
import com.google.gwtorm.server.OrmException;
@@ -38,8 +35,6 @@ import org.eclipse.jgit.errors.ConfigInvalidException;
import java.io.IOException;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -47,7 +42,6 @@ import java.util.Set;
@Singleton
public class PostWatchedProjects
implements RestModifyView<AccountResource, List<ProjectWatchInfo>> {
private final Provider<ReviewDb> dbProvider;
private final Provider<IdentifiedUser> self;
private final GetWatchedProjects getWatchedProjects;
private final ProjectsCollection projectsCollection;
@@ -55,13 +49,11 @@ public class PostWatchedProjects
private final WatchConfig.Accessor watchConfig;
@Inject
public PostWatchedProjects(Provider<ReviewDb> dbProvider,
Provider<IdentifiedUser> self,
public PostWatchedProjects(Provider<IdentifiedUser> self,
GetWatchedProjects getWatchedProjects,
ProjectsCollection projectsCollection,
AccountCache accountCache,
WatchConfig.Accessor watchConfig) {
this.dbProvider = dbProvider;
this.self = self;
this.getWatchedProjects = getWatchedProjects;
this.projectsCollection = projectsCollection;
@@ -78,52 +70,11 @@ public class PostWatchedProjects
throw new AuthException("not allowed to edit project watches");
}
Account.Id accountId = rsrc.getUser().getAccountId();
updateInDb(accountId, input);
updateInGit(accountId, input);
watchConfig.upsertProjectWatches(accountId, asMap(input));
accountCache.evict(accountId);
return getWatchedProjects.apply(rsrc);
}
private void updateInDb(Account.Id accountId, List<ProjectWatchInfo> input)
throws BadRequestException, UnprocessableEntityException, IOException,
OrmException {
Set<AccountProjectWatch.Key> keys = new HashSet<>();
List<AccountProjectWatch> watchedProjects = new LinkedList<>();
for (ProjectWatchInfo a : input) {
if (a.project == null) {
throw new BadRequestException("project name must be specified");
}
Project.NameKey projectKey =
projectsCollection.parse(a.project).getNameKey();
AccountProjectWatch.Key key =
new AccountProjectWatch.Key(accountId, projectKey, a.filter);
if (!keys.add(key)) {
throw new BadRequestException("duplicate entry for project "
+ format(key.getProjectName().get(), key.getFilter().get()));
}
AccountProjectWatch apw = new AccountProjectWatch(key);
apw.setNotify(AccountProjectWatch.NotifyType.ABANDONED_CHANGES,
toBoolean(a.notifyAbandonedChanges));
apw.setNotify(AccountProjectWatch.NotifyType.ALL_COMMENTS,
toBoolean(a.notifyAllComments));
apw.setNotify(AccountProjectWatch.NotifyType.NEW_CHANGES,
toBoolean(a.notifyNewChanges));
apw.setNotify(AccountProjectWatch.NotifyType.NEW_PATCHSETS,
toBoolean(a.notifyNewPatchSets));
apw.setNotify(AccountProjectWatch.NotifyType.SUBMITTED_CHANGES,
toBoolean(a.notifySubmittedChanges));
watchedProjects.add(apw);
}
dbProvider.get().accountProjectWatches().upsert(watchedProjects);
}
private void updateInGit(Account.Id accountId, List<ProjectWatchInfo> input)
throws BadRequestException, UnprocessableEntityException, IOException,
ConfigInvalidException {
watchConfig.upsertProjectWatches(accountId, asMap(input));
}
private Map<ProjectWatchKey, Set<NotifyType>> asMap(
List<ProjectWatchInfo> input) throws BadRequestException,
UnprocessableEntityException, IOException {
@@ -168,7 +119,7 @@ public class PostWatchedProjects
private static String format(String project, String filter) {
return project
+ (filter != null && !AccountProjectWatch.FILTER_ALL.equals(filter)
+ (filter != null && !WatchConfig.FILTER_ALL.equals(filter)
? " and filter " + filter
: "");
}

View File

@@ -30,8 +30,6 @@ import com.google.common.collect.MultimapBuilder;
import com.google.common.collect.Sets;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountProjectWatch;
import com.google.gerrit.reviewdb.client.AccountProjectWatch.NotifyType;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.IdentifiedUser;
@@ -177,6 +175,19 @@ public class WatchConfig extends VersionedMetaData
public abstract @Nullable String filter();
}
public enum NotifyType {
// sort by name, except 'ALL' which should stay last
ABANDONED_CHANGES,
ALL_COMMENTS,
NEW_CHANGES,
NEW_PATCHSETS,
SUBMITTED_CHANGES,
ALL
}
public static final String FILTER_ALL = "*";
public static final String WATCH_CONFIG = "watch.config";
public static final String PROJECT = "project";
public static final String KEY_NOTIFY = "notify";
@@ -316,7 +327,7 @@ public class WatchConfig extends VersionedMetaData
return null;
}
String filter = notifyValue.substring(0, i).trim();
if (filter.isEmpty() || AccountProjectWatch.FILTER_ALL.equals(filter)) {
if (filter.isEmpty() || FILTER_ALL.equals(filter)) {
filter = null;
}
@@ -353,7 +364,7 @@ public class WatchConfig extends VersionedMetaData
public String toString() {
List<NotifyType> notifyTypes = new ArrayList<>(notifyTypes());
StringBuilder notifyValue = new StringBuilder();
notifyValue.append(firstNonNull(filter(), AccountProjectWatch.FILTER_ALL))
notifyValue.append(firstNonNull(filter(), FILTER_ALL))
.append(" [");
Joiner.on(", ").appendTo(notifyValue, notifyTypes);
notifyValue.append("]");

View File

@@ -16,7 +16,7 @@ package com.google.gerrit.server.git;
import com.google.common.base.Strings;
import com.google.gerrit.common.data.GroupReference;
import com.google.gerrit.reviewdb.client.AccountProjectWatch.NotifyType;
import com.google.gerrit.server.account.WatchConfig.NotifyType;
import com.google.gerrit.server.mail.Address;
import java.util.EnumSet;

View File

@@ -45,11 +45,11 @@ import com.google.gerrit.extensions.client.InheritableBoolean;
import com.google.gerrit.extensions.client.ProjectState;
import com.google.gerrit.extensions.client.SubmitType;
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.reviewdb.client.AccountProjectWatch.NotifyType;
import com.google.gerrit.reviewdb.client.Branch;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.account.GroupBackend;
import com.google.gerrit.server.account.WatchConfig.NotifyType;
import com.google.gerrit.server.config.ConfigUtil;
import com.google.gerrit.server.config.PluginConfig;
import com.google.gerrit.server.mail.Address;

View File

@@ -15,9 +15,9 @@
package com.google.gerrit.server.mail.send;
import com.google.gerrit.common.errors.EmailException;
import com.google.gerrit.reviewdb.client.AccountProjectWatch.NotifyType;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.account.WatchConfig.NotifyType;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;

View File

@@ -20,7 +20,6 @@ import com.google.gerrit.extensions.api.changes.NotifyHandling;
import com.google.gerrit.extensions.api.changes.RecipientType;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.reviewdb.client.AccountProjectWatch.NotifyType;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.ChangeMessage;
import com.google.gerrit.reviewdb.client.Patch;
@@ -30,6 +29,7 @@ import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.StarredChangesUtil;
import com.google.gerrit.server.account.AccountState;
import com.google.gerrit.server.account.WatchConfig.NotifyType;
import com.google.gerrit.server.mail.send.ProjectWatch.Watchers;
import com.google.gerrit.server.notedb.ReviewerStateInternal;
import com.google.gerrit.server.patch.PatchList;

View File

@@ -20,13 +20,13 @@ import com.google.gerrit.common.data.FilenameComparator;
import com.google.gerrit.common.errors.EmailException;
import com.google.gerrit.common.errors.NoSuchEntityException;
import com.google.gerrit.extensions.api.changes.NotifyHandling;
import com.google.gerrit.reviewdb.client.AccountProjectWatch.NotifyType;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.Comment;
import com.google.gerrit.reviewdb.client.Patch;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.client.RobotComment;
import com.google.gerrit.server.CommentsUtil;
import com.google.gerrit.server.account.WatchConfig.NotifyType;
import com.google.gerrit.server.mail.MailUtil;
import com.google.gerrit.server.patch.PatchFile;
import com.google.gerrit.server.patch.PatchList;

View File

@@ -18,9 +18,9 @@ import com.google.common.collect.Iterables;
import com.google.gerrit.common.errors.EmailException;
import com.google.gerrit.extensions.api.changes.RecipientType;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountProjectWatch.NotifyType;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.account.WatchConfig.NotifyType;
import com.google.gerrit.server.mail.send.ProjectWatch.Watchers;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;

View File

@@ -17,9 +17,9 @@ package com.google.gerrit.server.mail.send;
import com.google.gerrit.common.errors.EmailException;
import com.google.gerrit.extensions.api.changes.RecipientType;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountProjectWatch.NotifyType;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.account.WatchConfig.NotifyType;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;

View File

@@ -15,9 +15,9 @@
package com.google.gerrit.server.mail.send;
import com.google.gerrit.common.errors.EmailException;
import com.google.gerrit.reviewdb.client.AccountProjectWatch.NotifyType;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.account.WatchConfig.NotifyType;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;

View File

@@ -21,10 +21,10 @@ import com.google.gerrit.common.data.LabelTypes;
import com.google.gerrit.common.data.LabelValue;
import com.google.gerrit.common.errors.EmailException;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountProjectWatch.NotifyType;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.PatchSetApproval;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.account.WatchConfig.NotifyType;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;

View File

@@ -18,8 +18,8 @@ import com.google.common.collect.Iterables;
import com.google.gerrit.common.errors.EmailException;
import com.google.gerrit.extensions.api.changes.RecipientType;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountProjectWatch.NotifyType;
import com.google.gerrit.reviewdb.client.Branch;
import com.google.gerrit.server.account.WatchConfig.NotifyType;
import com.google.gerrit.server.mail.Address;
import com.google.gerrit.server.mail.send.ProjectWatch.Watchers;
import com.google.gwtorm.server.OrmException;

View File

@@ -21,12 +21,12 @@ import com.google.gerrit.common.data.GroupReference;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.reviewdb.client.AccountGroupMember;
import com.google.gerrit.reviewdb.client.AccountProjectWatch.NotifyType;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.AccountState;
import com.google.gerrit.server.account.WatchConfig.NotifyType;
import com.google.gerrit.server.account.WatchConfig.ProjectWatchKey;
import com.google.gerrit.server.git.NotifyConfig;
import com.google.gerrit.server.mail.Address;

View File

@@ -17,9 +17,9 @@ package com.google.gerrit.server.mail.send;
import com.google.gerrit.common.errors.EmailException;
import com.google.gerrit.extensions.api.changes.RecipientType;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountProjectWatch.NotifyType;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.account.WatchConfig.NotifyType;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;

View File

@@ -15,9 +15,9 @@
package com.google.gerrit.server.mail.send;
import com.google.gerrit.common.errors.EmailException;
import com.google.gerrit.reviewdb.client.AccountProjectWatch.NotifyType;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.account.WatchConfig.NotifyType;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;

View File

@@ -15,9 +15,9 @@
package com.google.gerrit.server.mail.send;
import com.google.gerrit.common.errors.EmailException;
import com.google.gerrit.reviewdb.client.AccountProjectWatch.NotifyType;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.account.WatchConfig.NotifyType;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;

View File

@@ -36,7 +36,7 @@ import java.util.concurrent.TimeUnit;
/** A version of the database schema. */
public abstract class SchemaVersion {
/** The current schema version. */
public static final Class<Schema_138> C = Schema_138.class;
public static final Class<Schema_139> C = Schema_139.class;
public static int getBinaryVersion() {
return guessVersion(C);

View File

@@ -0,0 +1,186 @@
//Copyright (C) 2016 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.server.schema;
import com.google.auto.value.AutoValue;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Multimap;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.GerritPersonIdent;
import com.google.gerrit.server.account.WatchConfig;
import com.google.gerrit.server.account.WatchConfig.NotifyType;
import com.google.gerrit.server.account.WatchConfig.ProjectWatchKey;
import com.google.gerrit.server.config.AllUsersName;
import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.git.MetaDataUpdate;
import com.google.gwtorm.jdbc.JdbcSchema;
import com.google.gwtorm.server.OrmDuplicateKeyException;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.lib.BatchRefUpdate;
import org.eclipse.jgit.lib.NullProgressMonitor;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevWalk;
import java.io.IOException;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Collection;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class Schema_139 extends SchemaVersion {
private static final String MSG = "Migrate project watches to git";
private final GitRepositoryManager repoManager;
private final AllUsersName allUsersName;
private final PersonIdent serverUser;
@Inject
Schema_139(Provider<Schema_138> prior,
GitRepositoryManager repoManager,
AllUsersName allUsersName,
@GerritPersonIdent PersonIdent serverUser) {
super(prior);
this.repoManager = repoManager;
this.allUsersName = allUsersName;
this.serverUser = serverUser;
}
@Override
protected void migrateData(ReviewDb db, UpdateUI ui)
throws OrmException, SQLException {
Multimap<Account.Id, ProjectWatch> imports = ArrayListMultimap.create();
try (Statement stmt = ((JdbcSchema) db).getConnection().createStatement();
ResultSet rs = stmt.executeQuery(
"SELECT "
+ "account_id, "
+ "project_name, "
+ "filter, "
+ "notify_abandoned_changes, "
+ "notify_all_comments, "
+ "notify_new_changes, "
+ "notify_new_patch_sets, "
+ "notify_submitted_changes "
+ "FROM account_project_watches")) {
while (rs.next()) {
Account.Id accountId = new Account.Id(rs.getInt(1));
ProjectWatch.Builder b = ProjectWatch.builder()
.project(new Project.NameKey(rs.getString(2)))
.filter(rs.getString(3))
.notifyAbandonedChanges(rs.getBoolean(4))
.notifyAllComments(rs.getBoolean(5))
.notifyNewChanges(rs.getBoolean(6))
.notifyNewPatchSets(rs.getBoolean(7))
.notifySubmittedChanges(rs.getBoolean(8));
imports.put(accountId, b.build());
}
}
if (imports.isEmpty()) {
return;
}
try (Repository git = repoManager.openRepository(allUsersName);
RevWalk rw = new RevWalk(git)) {
BatchRefUpdate bru = git.getRefDatabase().newBatchUpdate();
bru.setRefLogIdent(serverUser);
bru.setRefLogMessage(MSG, false);
for (Map.Entry<Account.Id, Collection<ProjectWatch>> e : imports.asMap()
.entrySet()) {
Map<ProjectWatchKey, Set<NotifyType>> projectWatches = new HashMap<>();
for (ProjectWatch projectWatch : e.getValue()) {
ProjectWatchKey key = ProjectWatchKey.create(projectWatch.project(),
projectWatch.filter());
if (projectWatches.containsKey(key)) {
throw new OrmDuplicateKeyException(
"Duplicate key for watched project: " + key.toString());
}
Set<NotifyType> notifyValues = EnumSet.noneOf(NotifyType.class);
if (projectWatch.notifyAbandonedChanges()) {
notifyValues.add(NotifyType.ABANDONED_CHANGES);
}
if (projectWatch.notifyAllComments()) {
notifyValues.add(NotifyType.ALL_COMMENTS);
}
if (projectWatch.notifyNewChanges()) {
notifyValues.add(NotifyType.NEW_CHANGES);
}
if (projectWatch.notifyNewPatchSets()) {
notifyValues.add(NotifyType.NEW_PATCHSETS);
}
if (projectWatch.notifySubmittedChanges()) {
notifyValues.add(NotifyType.SUBMITTED_CHANGES);
}
projectWatches.put(key, notifyValues);
}
try (MetaDataUpdate md = new MetaDataUpdate(GitReferenceUpdated.DISABLED,
allUsersName, git, bru)) {
md.getCommitBuilder().setAuthor(serverUser);
md.getCommitBuilder().setCommitter(serverUser);
md.setMessage(MSG);
WatchConfig watchConfig = new WatchConfig(e.getKey());
watchConfig.load(md);
watchConfig.setProjectWatches(projectWatches);
watchConfig.commit(md);
}
}
bru.execute(rw, NullProgressMonitor.INSTANCE);
} catch (IOException | ConfigInvalidException ex) {
throw new OrmException(ex);
}
}
@AutoValue
abstract static class ProjectWatch {
abstract Project.NameKey project();
abstract @Nullable String filter();
abstract boolean notifyAbandonedChanges();
abstract boolean notifyAllComments();
abstract boolean notifyNewChanges();
abstract boolean notifyNewPatchSets();
abstract boolean notifySubmittedChanges();
static Builder builder() {
return new AutoValue_Schema_139_ProjectWatch.Builder();
}
@AutoValue.Builder
abstract static class Builder {
abstract Builder project(Project.NameKey project);
abstract Builder filter(@Nullable String filter);
abstract Builder notifyAbandonedChanges(boolean notifyAbandonedChanges);
abstract Builder notifyAllComments(boolean notifyAllComments);
abstract Builder notifyNewChanges(boolean notifyNewChanges);
abstract Builder notifyNewPatchSets(boolean notifyNewPatchSets);
abstract Builder notifySubmittedChanges(boolean notifySubmittedChanges);
abstract ProjectWatch build();
}
}
}

View File

@@ -17,8 +17,8 @@ package com.google.gerrit.server.account;
import static com.google.common.truth.Truth.assertThat;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountProjectWatch.NotifyType;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.account.WatchConfig.NotifyType;
import com.google.gerrit.server.account.WatchConfig.NotifyValue;
import com.google.gerrit.server.account.WatchConfig.ProjectWatchKey;
import com.google.gerrit.server.git.ValidationError;

View File

@@ -25,9 +25,9 @@ import com.google.gerrit.common.TimeUtil;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountExternalId;
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.reviewdb.client.AccountProjectWatch.NotifyType;
import com.google.gerrit.server.account.AccountCache;
import com.google.gerrit.server.account.AccountState;
import com.google.gerrit.server.account.WatchConfig.NotifyType;
import com.google.gerrit.server.account.WatchConfig.ProjectWatchKey;
import com.google.gerrit.server.mail.Address;

View File

@@ -22,7 +22,6 @@ import com.google.gerrit.reviewdb.server.AccountGroupByIdAudAccess;
import com.google.gerrit.reviewdb.server.AccountGroupMemberAccess;
import com.google.gerrit.reviewdb.server.AccountGroupMemberAuditAccess;
import com.google.gerrit.reviewdb.server.AccountGroupNameAccess;
import com.google.gerrit.reviewdb.server.AccountProjectWatchAccess;
import com.google.gerrit.reviewdb.server.ChangeAccess;
import com.google.gerrit.reviewdb.server.ChangeMessageAccess;
import com.google.gerrit.reviewdb.server.PatchLineCommentAccess;
@@ -114,11 +113,6 @@ public class DisabledReviewDb implements ReviewDb {
throw new Disabled();
}
@Override
public AccountProjectWatchAccess accountProjectWatches() {
throw new Disabled();
}
@Override
public ChangeAccess changes() {
throw new Disabled();

View File

@@ -19,9 +19,9 @@ import com.google.gerrit.common.TimeUtil;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountExternalId;
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.reviewdb.client.AccountProjectWatch.NotifyType;
import com.google.gerrit.server.account.AccountCache;
import com.google.gerrit.server.account.AccountState;
import com.google.gerrit.server.account.WatchConfig.NotifyType;
import com.google.gerrit.server.account.WatchConfig.ProjectWatchKey;
import java.util.HashMap;