Merge changes I4f8d4e8a,Ib54dcf81,I8fe3adc0
* changes: Migrate project watches to git (part 1) AccountManager: Use account index to lookup external IDs AccountCacheImpl#ByNameLoader: Use account index to lookup usernames
This commit is contained in:
commit
a10fd547e1
@ -118,6 +118,22 @@ public class WatchedProjectsIT extends AbstractDaemonTest {
|
||||
gApi.accounts().self().setWatchedProjects(projectsToWatch);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setAndGetEmptyWatch() throws Exception {
|
||||
String projectName = createProject(NEW_PROJECT_NAME).get();
|
||||
|
||||
List<ProjectWatchInfo> projectsToWatch = new LinkedList<>();
|
||||
|
||||
ProjectWatchInfo pwi = new ProjectWatchInfo();
|
||||
pwi.project = projectName;
|
||||
projectsToWatch.add(pwi);
|
||||
|
||||
gApi.accounts().self().setWatchedProjects(projectsToWatch);
|
||||
List<ProjectWatchInfo> persistedWatchedProjects =
|
||||
gApi.accounts().self().getWatchedProjects();
|
||||
assertThat(persistedWatchedProjects).containsAllIn(projectsToWatch);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void watchNonExistingProject() throws Exception {
|
||||
String projectName = NEW_PROJECT_NAME + "3";
|
||||
|
@ -47,4 +47,30 @@ public class ProjectWatchInfo {
|
||||
.hash(project, filter, notifyNewChanges, notifyNewPatchSets,
|
||||
notifyAllComments, notifySubmittedChanges, notifyAbandonedChanges);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder b = new StringBuilder();
|
||||
b.append(project);
|
||||
if (filter != null) {
|
||||
b.append("%filter=")
|
||||
.append(filter);
|
||||
}
|
||||
b.append("(notifyAbandonedChanges=")
|
||||
.append(toBoolean(notifyAbandonedChanges))
|
||||
.append(", notifyAllComments=")
|
||||
.append(toBoolean(notifyAllComments))
|
||||
.append(", notifyNewChanges=")
|
||||
.append(toBoolean(notifyNewChanges))
|
||||
.append(", notifyNewPatchSets=")
|
||||
.append(toBoolean(notifyNewPatchSets))
|
||||
.append(", notifySubmittedChanges=")
|
||||
.append(toBoolean(notifySubmittedChanges))
|
||||
.append(")");
|
||||
return b.toString();
|
||||
}
|
||||
|
||||
private boolean toBoolean(Boolean b) {
|
||||
return b == null ? false : b;
|
||||
}
|
||||
}
|
||||
|
@ -294,12 +294,7 @@ public class PostGpgKeys implements RestModifyView<AccountResource, Input> {
|
||||
msg.append("GPG key ").append(externalId)
|
||||
.append(" associated with multiple accounts: ");
|
||||
Joiner.on(", ").appendTo(msg,
|
||||
Lists.transform(accountStates, new Function<AccountState, String>() {
|
||||
@Override
|
||||
public String apply(AccountState accountState) {
|
||||
return accountState.getAccount().getId().toString();
|
||||
}
|
||||
}));
|
||||
Lists.transform(accountStates, AccountState.ACCOUNT_ID_FUNCTION));
|
||||
log.error(msg.toString());
|
||||
throw new IllegalStateException(msg.toString());
|
||||
}
|
||||
|
@ -22,8 +22,14 @@ import com.google.gwtorm.client.StringKey;
|
||||
public final class AccountProjectWatch {
|
||||
|
||||
public enum NotifyType {
|
||||
NEW_CHANGES, NEW_PATCHSETS, ALL_COMMENTS, SUBMITTED_CHANGES,
|
||||
ABANDONED_CHANGES, ALL
|
||||
// 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 = "*";
|
||||
|
@ -24,10 +24,14 @@ 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;
|
||||
import com.google.gerrit.reviewdb.client.AccountProjectWatch.NotifyType;
|
||||
import com.google.gerrit.reviewdb.server.ReviewDb;
|
||||
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.AccountIndexCollection;
|
||||
import com.google.gerrit.server.index.account.AccountIndexer;
|
||||
import com.google.gerrit.server.query.account.InternalAccountQuery;
|
||||
import com.google.gwtorm.server.OrmException;
|
||||
import com.google.gwtorm.server.SchemaFactory;
|
||||
import com.google.inject.Inject;
|
||||
@ -38,13 +42,16 @@ 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;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
@ -133,9 +140,9 @@ public class AccountCacheImpl implements AccountCache {
|
||||
Account account = new Account(accountId, TimeUtil.nowTs());
|
||||
account.setActive(false);
|
||||
Collection<AccountExternalId> ids = Collections.emptySet();
|
||||
Collection<AccountProjectWatch> projectWatches = Collections.emptySet();
|
||||
Set<AccountGroup.UUID> anon = ImmutableSet.of();
|
||||
return new AccountState(account, anon, ids, projectWatches);
|
||||
return new AccountState(account, anon, ids,
|
||||
new HashMap<ProjectWatchKey, Set<NotifyType>>());
|
||||
}
|
||||
|
||||
static class ByIdLoader extends CacheLoader<Account.Id, AccountState> {
|
||||
@ -143,17 +150,24 @@ 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
|
||||
ByIdLoader(SchemaFactory<ReviewDb> sf,
|
||||
GroupCache groupCache,
|
||||
GeneralPreferencesLoader loader,
|
||||
@Named(BYUSER_NAME) LoadingCache<String,
|
||||
Optional<Account.Id>> byUsername) {
|
||||
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", true);
|
||||
this.watchConfig = watchConfig;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -169,7 +183,7 @@ public class AccountCacheImpl implements AccountCache {
|
||||
}
|
||||
|
||||
private AccountState load(final ReviewDb db, final Account.Id who)
|
||||
throws OrmException {
|
||||
throws OrmException, IOException, ConfigInvalidException {
|
||||
Account account = db.accounts().get(who);
|
||||
if (account == null) {
|
||||
// Account no longer exists? They are anonymous.
|
||||
@ -198,9 +212,10 @@ public class AccountCacheImpl implements AccountCache {
|
||||
account.setGeneralPreferences(GeneralPreferencesInfo.defaults());
|
||||
}
|
||||
|
||||
Collection<AccountProjectWatch> projectWatches =
|
||||
Collections.unmodifiableCollection(
|
||||
db.accountProjectWatches().byAccount(who).toList());
|
||||
Map<ProjectWatchKey, Set<NotifyType>> projectWatches =
|
||||
readFromGit
|
||||
? watchConfig.get().getProjectWatches(who)
|
||||
: GetWatchedProjects.readProjectWatchesFromDb(db, who);
|
||||
|
||||
return new AccountState(account, internalGroups, externalIds,
|
||||
projectWatches);
|
||||
@ -209,19 +224,33 @@ public class AccountCacheImpl implements AccountCache {
|
||||
|
||||
static class ByNameLoader extends CacheLoader<String, Optional<Account.Id>> {
|
||||
private final SchemaFactory<ReviewDb> schema;
|
||||
private final AccountIndexCollection accountIndexes;
|
||||
private final Provider<InternalAccountQuery> accountQueryProvider;
|
||||
|
||||
@Inject
|
||||
ByNameLoader(final SchemaFactory<ReviewDb> sf) {
|
||||
ByNameLoader(SchemaFactory<ReviewDb> sf,
|
||||
AccountIndexCollection accountIndexes,
|
||||
Provider<InternalAccountQuery> accountQueryProvider) {
|
||||
this.schema = sf;
|
||||
this.accountIndexes = accountIndexes;
|
||||
this.accountQueryProvider = accountQueryProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Account.Id> load(String username) throws Exception {
|
||||
try (ReviewDb db = schema.open()) {
|
||||
final AccountExternalId.Key key = new AccountExternalId.Key( //
|
||||
AccountExternalId.Key key = new AccountExternalId.Key( //
|
||||
AccountExternalId.SCHEME_USERNAME, //
|
||||
username);
|
||||
final AccountExternalId id = db.accountExternalIds().get(key);
|
||||
if (accountIndexes.getSearchIndex() != null) {
|
||||
AccountState accountState =
|
||||
accountQueryProvider.get().oneByExternalId(key.get());
|
||||
return accountState != null
|
||||
? Optional.of(accountState.getAccount().getId())
|
||||
: Optional.<Account.Id>absent();
|
||||
}
|
||||
|
||||
try (ReviewDb db = schema.open()) {
|
||||
AccountExternalId id = db.accountExternalIds().get(key);
|
||||
if (id != null) {
|
||||
return Optional.of(id.getAccountId());
|
||||
}
|
||||
|
@ -27,11 +27,14 @@ import com.google.gerrit.reviewdb.client.AccountGroup;
|
||||
import com.google.gerrit.reviewdb.client.AccountGroupMember;
|
||||
import com.google.gerrit.reviewdb.server.ReviewDb;
|
||||
import com.google.gerrit.server.IdentifiedUser;
|
||||
import com.google.gerrit.server.index.account.AccountIndexCollection;
|
||||
import com.google.gerrit.server.project.ProjectCache;
|
||||
import com.google.gerrit.server.query.account.InternalAccountQuery;
|
||||
import com.google.gwtorm.server.OrmException;
|
||||
import com.google.gwtorm.server.ResultSet;
|
||||
import com.google.gwtorm.server.SchemaFactory;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Provider;
|
||||
import com.google.inject.Singleton;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
@ -58,6 +61,8 @@ public class AccountManager {
|
||||
private final ProjectCache projectCache;
|
||||
private final AtomicBoolean awaitsFirstAccountCheck;
|
||||
private final AuditService auditService;
|
||||
private final AccountIndexCollection accountIndexes;
|
||||
private final Provider<InternalAccountQuery> accountQueryProvider;
|
||||
|
||||
@Inject
|
||||
AccountManager(SchemaFactory<ReviewDb> schema,
|
||||
@ -67,7 +72,9 @@ public class AccountManager {
|
||||
IdentifiedUser.GenericFactory userFactory,
|
||||
ChangeUserName.Factory changeUserNameFactory,
|
||||
ProjectCache projectCache,
|
||||
AuditService auditService) {
|
||||
AuditService auditService,
|
||||
AccountIndexCollection accountIndexes,
|
||||
Provider<InternalAccountQuery> accountQueryProvider) {
|
||||
this.schema = schema;
|
||||
this.byIdCache = byIdCache;
|
||||
this.byEmailCache = byEmailCache;
|
||||
@ -77,6 +84,8 @@ public class AccountManager {
|
||||
this.projectCache = projectCache;
|
||||
this.awaitsFirstAccountCheck = new AtomicBoolean(true);
|
||||
this.auditService = auditService;
|
||||
this.accountIndexes = accountIndexes;
|
||||
this.accountQueryProvider = accountQueryProvider;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -84,6 +93,14 @@ public class AccountManager {
|
||||
*/
|
||||
public Account.Id lookup(String externalId) throws AccountException {
|
||||
try {
|
||||
if (accountIndexes.getSearchIndex() != null) {
|
||||
AccountState accountState =
|
||||
accountQueryProvider.get().oneByExternalId(externalId);
|
||||
return accountState != null
|
||||
? accountState.getAccount().getId()
|
||||
: null;
|
||||
}
|
||||
|
||||
try (ReviewDb db = schema.open()) {
|
||||
AccountExternalId ext =
|
||||
db.accountExternalIds().get(new AccountExternalId.Key(externalId));
|
||||
@ -132,16 +149,26 @@ public class AccountManager {
|
||||
|
||||
private AccountExternalId getAccountExternalId(ReviewDb db,
|
||||
AccountExternalId.Key key) throws OrmException {
|
||||
String keyValue = key.get();
|
||||
String keyScheme = keyValue.substring(0, keyValue.indexOf(':') + 1);
|
||||
if (accountIndexes.getSearchIndex() != null) {
|
||||
AccountState accountState =
|
||||
accountQueryProvider.get().oneByExternalId(key.get());
|
||||
if (accountState != null) {
|
||||
for (AccountExternalId extId : accountState.getExternalIds()) {
|
||||
if (extId.getKey().equals(key)) {
|
||||
return extId;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// We don't have at the moment an account_by_external_id cache
|
||||
// but by using the accounts cache we get the list of external_ids
|
||||
// without having to query the DB every time
|
||||
if (keyScheme.equals(AccountExternalId.SCHEME_GERRIT)
|
||||
|| keyScheme.equals(AccountExternalId.SCHEME_USERNAME)) {
|
||||
if (key.getScheme().equals(AccountExternalId.SCHEME_GERRIT)
|
||||
|| key.getScheme().equals(AccountExternalId.SCHEME_USERNAME)) {
|
||||
AccountState state = byIdCache.getByUsername(
|
||||
keyValue.substring(keyScheme.length()));
|
||||
key.get().substring(key.getScheme().length()));
|
||||
if (state != null) {
|
||||
for (AccountExternalId accountExternalId : state.getExternalIds()) {
|
||||
if (accountExternalId.getKey().equals(key)) {
|
||||
|
@ -17,31 +17,42 @@ package com.google.gerrit.server.account;
|
||||
import static com.google.gerrit.reviewdb.client.AccountExternalId.SCHEME_MAILTO;
|
||||
import static com.google.gerrit.reviewdb.client.AccountExternalId.SCHEME_USERNAME;
|
||||
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.cache.Cache;
|
||||
import com.google.common.cache.CacheBuilder;
|
||||
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;
|
||||
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.ProjectWatchKey;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
public class AccountState {
|
||||
public static Function<AccountState, Account.Id> ACCOUNT_ID_FUNCTION =
|
||||
new Function<AccountState, Account.Id>() {
|
||||
@Override
|
||||
public Account.Id apply(AccountState in) {
|
||||
return in.getAccount().getId();
|
||||
}
|
||||
};
|
||||
|
||||
private final Account account;
|
||||
private final Set<AccountGroup.UUID> internalGroups;
|
||||
private final Collection<AccountExternalId> externalIds;
|
||||
private final Collection<AccountProjectWatch> projectWatches;
|
||||
private final Map<ProjectWatchKey, Set<NotifyType>> projectWatches;
|
||||
private Cache<IdentifiedUser.PropertyKey<Object>, Object> properties;
|
||||
|
||||
public AccountState(Account account,
|
||||
Set<AccountGroup.UUID> actualGroups,
|
||||
Collection<AccountExternalId> externalIds,
|
||||
Collection<AccountProjectWatch> projectWatches) {
|
||||
Map<ProjectWatchKey, Set<NotifyType>> projectWatches) {
|
||||
this.account = account;
|
||||
this.internalGroups = actualGroups;
|
||||
this.externalIds = externalIds;
|
||||
@ -81,7 +92,7 @@ public class AccountState {
|
||||
}
|
||||
|
||||
/** The project watches of the account. */
|
||||
public Collection<AccountProjectWatch> getProjectWatches() {
|
||||
public Map<ProjectWatchKey, Set<NotifyType>> getProjectWatches() {
|
||||
return projectWatches;
|
||||
}
|
||||
|
||||
|
@ -14,6 +14,8 @@
|
||||
|
||||
package com.google.gerrit.server.account;
|
||||
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.gerrit.extensions.client.ProjectWatchInfo;
|
||||
import com.google.gerrit.extensions.restapi.AuthException;
|
||||
import com.google.gerrit.extensions.restapi.Response;
|
||||
@ -24,12 +26,15 @@ 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;
|
||||
|
||||
import org.eclipse.jgit.errors.ConfigInvalidException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
@ -38,52 +43,75 @@ 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,
|
||||
AccountCache accountCache) {
|
||||
AccountCache accountCache,
|
||||
WatchConfig.Accessor watchConfig) {
|
||||
this.dbProvider = dbProvider;
|
||||
this.self = self;
|
||||
this.accountCache = accountCache;
|
||||
this.watchConfig = watchConfig;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Response<?> apply(AccountResource rsrc, List<ProjectWatchInfo> input)
|
||||
throws AuthException, UnprocessableEntityException, OrmException,
|
||||
IOException {
|
||||
if (self.get() != rsrc.getUser()
|
||||
&& !self.get().getCapabilities().canAdministrateServer()) {
|
||||
IOException, ConfigInvalidException {
|
||||
if (self.get() != rsrc.getUser()
|
||||
&& !self.get().getCapabilities().canAdministrateServer()) {
|
||||
throw new AuthException("It is not allowed to edit project watches "
|
||||
+ "of other users");
|
||||
}
|
||||
if (input == null) {
|
||||
return Response.none();
|
||||
}
|
||||
|
||||
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<>();
|
||||
HashMap<AccountProjectWatch.Key, AccountProjectWatch> watchedProjectsMap =
|
||||
new HashMap<>();
|
||||
for (AccountProjectWatch watchedProject : watchedProjects) {
|
||||
watchedProjectsMap.put(watchedProject.getKey(), watchedProject);
|
||||
}
|
||||
|
||||
if (input != null) {
|
||||
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);
|
||||
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));
|
||||
}
|
||||
}
|
||||
return Response.none();
|
||||
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, Lists.transform(input,
|
||||
new Function<ProjectWatchInfo, ProjectWatchKey>() {
|
||||
@Override
|
||||
public ProjectWatchKey apply(ProjectWatchInfo info) {
|
||||
return ProjectWatchKey.create(new Project.NameKey(info.project),
|
||||
info.filter);
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
@ -14,67 +14,120 @@
|
||||
|
||||
package com.google.gerrit.server.account;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.collect.ComparisonChain;
|
||||
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.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;
|
||||
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) {
|
||||
Provider<IdentifiedUser> self,
|
||||
@GerritServerConfig Config cfg,
|
||||
WatchConfig.Accessor watchConfig) {
|
||||
this.dbProvider = dbProvider;
|
||||
this.self = self;
|
||||
this.readFromGit =
|
||||
cfg.getBoolean("user", null, "readProjectWatchesFromGit", true);
|
||||
this.watchConfig = watchConfig;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ProjectWatchInfo> apply(AccountResource rsrc)
|
||||
throws OrmException, AuthException {
|
||||
throws OrmException, AuthException, IOException, ConfigInvalidException {
|
||||
if (self.get() != rsrc.getUser()
|
||||
&& !self.get().getCapabilities().canAdministrateServer()) {
|
||||
&& !self.get().getCapabilities().canAdministrateServer()) {
|
||||
throw new AuthException("It is not allowed to list project watches "
|
||||
+ "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<>();
|
||||
Iterable<AccountProjectWatch> projectWatches =
|
||||
dbProvider.get().accountProjectWatches()
|
||||
.byAccount(rsrc.getUser().getAccountId());
|
||||
for (AccountProjectWatch a : projectWatches) {
|
||||
for (Map.Entry<ProjectWatchKey, Set<NotifyType>> e : projectWatches
|
||||
.entrySet()) {
|
||||
ProjectWatchInfo pwi = new ProjectWatchInfo();
|
||||
pwi.filter = a.getFilter();
|
||||
pwi.project = a.getProjectNameKey().get();
|
||||
pwi.filter = e.getKey().filter();
|
||||
pwi.project = e.getKey().project().get();
|
||||
pwi.notifyAbandonedChanges =
|
||||
toBoolean(
|
||||
a.isNotify(AccountProjectWatch.NotifyType.ABANDONED_CHANGES));
|
||||
toBoolean(e.getValue().contains(NotifyType.ABANDONED_CHANGES));
|
||||
pwi.notifyNewChanges =
|
||||
toBoolean(a.isNotify(AccountProjectWatch.NotifyType.NEW_CHANGES));
|
||||
toBoolean(e.getValue().contains(NotifyType.NEW_CHANGES));
|
||||
pwi.notifyNewPatchSets =
|
||||
toBoolean(a.isNotify(AccountProjectWatch.NotifyType.NEW_PATCHSETS));
|
||||
toBoolean(e.getValue().contains(NotifyType.NEW_PATCHSETS));
|
||||
pwi.notifySubmittedChanges =
|
||||
toBoolean(
|
||||
a.isNotify(AccountProjectWatch.NotifyType.SUBMITTED_CHANGES));
|
||||
toBoolean(e.getValue().contains(NotifyType.SUBMITTED_CHANGES));
|
||||
pwi.notifyAllComments =
|
||||
toBoolean(a.isNotify(AccountProjectWatch.NotifyType.ALL_COMMENTS));
|
||||
toBoolean(e.getValue().contains(NotifyType.ALL_COMMENTS));
|
||||
projectWatchInfos.add(pwi);
|
||||
}
|
||||
Collections.sort(projectWatchInfos, new Comparator<ProjectWatchInfo>() {
|
||||
@Override
|
||||
public int compare(ProjectWatchInfo pwi1, ProjectWatchInfo pwi2) {
|
||||
return ComparisonChain.start()
|
||||
.compare(pwi1.project, pwi2.project)
|
||||
.compare(Strings.nullToEmpty(pwi1.filter),
|
||||
Strings.nullToEmpty(pwi2.filter))
|
||||
.result();
|
||||
}
|
||||
});
|
||||
return projectWatchInfos;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -22,19 +22,26 @@ 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.ProjectWatchKey;
|
||||
import com.google.gerrit.server.project.ProjectsCollection;
|
||||
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 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;
|
||||
|
||||
@Singleton
|
||||
@ -45,39 +52,41 @@ public class PostWatchedProjects
|
||||
private final GetWatchedProjects getWatchedProjects;
|
||||
private final ProjectsCollection projectsCollection;
|
||||
private final AccountCache accountCache;
|
||||
private final WatchConfig.Accessor watchConfig;
|
||||
|
||||
@Inject
|
||||
public PostWatchedProjects(Provider<ReviewDb> dbProvider,
|
||||
Provider<IdentifiedUser> self,
|
||||
GetWatchedProjects getWatchedProjects,
|
||||
ProjectsCollection projectsCollection,
|
||||
AccountCache accountCache) {
|
||||
AccountCache accountCache,
|
||||
WatchConfig.Accessor watchConfig) {
|
||||
this.dbProvider = dbProvider;
|
||||
this.self = self;
|
||||
this.getWatchedProjects = getWatchedProjects;
|
||||
this.projectsCollection = projectsCollection;
|
||||
this.accountCache = accountCache;
|
||||
this.watchConfig = watchConfig;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ProjectWatchInfo> apply(AccountResource rsrc,
|
||||
List<ProjectWatchInfo> input)
|
||||
throws OrmException, RestApiException, IOException {
|
||||
List<ProjectWatchInfo> input) throws OrmException, RestApiException,
|
||||
IOException, ConfigInvalidException {
|
||||
if (self.get() != rsrc.getUser()
|
||||
&& !self.get().getCapabilities().canAdministrateServer()) {
|
||||
&& !self.get().getCapabilities().canAdministrateServer()) {
|
||||
throw new AuthException("not allowed to edit project watches");
|
||||
}
|
||||
Account.Id accountId = rsrc.getUser().getAccountId();
|
||||
List<AccountProjectWatch> accountProjectWatchList =
|
||||
getAccountProjectWatchList(input, accountId);
|
||||
dbProvider.get().accountProjectWatches().upsert(accountProjectWatchList);
|
||||
updateInDb(accountId, input);
|
||||
updateInGit(accountId, input);
|
||||
accountCache.evict(accountId);
|
||||
return getWatchedProjects.apply(rsrc);
|
||||
}
|
||||
|
||||
private List<AccountProjectWatch> getAccountProjectWatchList(
|
||||
List<ProjectWatchInfo> input, Account.Id accountId)
|
||||
throws UnprocessableEntityException, BadRequestException, IOException {
|
||||
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) {
|
||||
@ -87,15 +96,11 @@ public class PostWatchedProjects
|
||||
|
||||
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 " + key.getProjectName().get()
|
||||
+ (!AccountProjectWatch.FILTER_ALL.equals(key.getFilter().get())
|
||||
? " and filter " + key.getFilter().get()
|
||||
: ""));
|
||||
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,
|
||||
@ -110,10 +115,61 @@ public class PostWatchedProjects
|
||||
toBoolean(a.notifySubmittedChanges));
|
||||
watchedProjects.add(apw);
|
||||
}
|
||||
return watchedProjects;
|
||||
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 {
|
||||
Map<ProjectWatchKey, Set<NotifyType>> m = new HashMap<>();
|
||||
for (ProjectWatchInfo info : input) {
|
||||
if (info.project == null) {
|
||||
throw new BadRequestException("project name must be specified");
|
||||
}
|
||||
|
||||
ProjectWatchKey key = ProjectWatchKey.create(
|
||||
projectsCollection.parse(info.project).getNameKey(), info.filter);
|
||||
if (m.containsKey(key)) {
|
||||
throw new BadRequestException(
|
||||
"duplicate entry for project " + format(info.project, info.filter));
|
||||
}
|
||||
|
||||
Set<NotifyType> notifyValues = EnumSet.noneOf(NotifyType.class);
|
||||
if (toBoolean(info.notifyAbandonedChanges)) {
|
||||
notifyValues.add(NotifyType.ABANDONED_CHANGES);
|
||||
}
|
||||
if (toBoolean(info.notifyAllComments)) {
|
||||
notifyValues.add(NotifyType.ALL_COMMENTS);
|
||||
}
|
||||
if (toBoolean(info.notifyNewChanges)) {
|
||||
notifyValues.add(NotifyType.NEW_CHANGES);
|
||||
}
|
||||
if (toBoolean(info.notifyNewPatchSets)) {
|
||||
notifyValues.add(NotifyType.NEW_PATCHSETS);
|
||||
}
|
||||
if (toBoolean(info.notifySubmittedChanges)) {
|
||||
notifyValues.add(NotifyType.SUBMITTED_CHANGES);
|
||||
}
|
||||
|
||||
m.put(key, notifyValues);
|
||||
}
|
||||
return m;
|
||||
}
|
||||
|
||||
private boolean toBoolean(Boolean b) {
|
||||
return b == null ? false : b;
|
||||
}
|
||||
|
||||
private static String format(String project, String filter) {
|
||||
return project
|
||||
+ (filter != null && !AccountProjectWatch.FILTER_ALL.equals(filter)
|
||||
? " and filter " + filter
|
||||
: "");
|
||||
}
|
||||
}
|
||||
|
@ -14,7 +14,6 @@
|
||||
|
||||
package com.google.gerrit.server.account;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static com.google.common.base.Preconditions.checkState;
|
||||
|
||||
import com.google.common.base.Function;
|
||||
@ -322,6 +321,6 @@ public class VersionedAuthorizedKeys extends VersionedMetaData
|
||||
}
|
||||
|
||||
private void checkLoaded() {
|
||||
checkNotNull(keys, "SSH keys not loaded yet");
|
||||
checkState(keys != null, "SSH keys not loaded yet");
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,340 @@
|
||||
// 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.account;
|
||||
|
||||
import static com.google.common.base.MoreObjects.firstNonNull;
|
||||
import static com.google.common.base.Preconditions.checkState;
|
||||
|
||||
import com.google.auto.value.AutoValue;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.base.Enums;
|
||||
import com.google.common.base.Joiner;
|
||||
import com.google.common.base.Optional;
|
||||
import com.google.common.base.Splitter;
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.collect.ArrayListMultimap;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.Multimap;
|
||||
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;
|
||||
import com.google.gerrit.server.config.AllUsersName;
|
||||
import com.google.gerrit.server.git.GitRepositoryManager;
|
||||
import com.google.gerrit.server.git.MetaDataUpdate;
|
||||
import com.google.gerrit.server.git.VersionedMetaData;
|
||||
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.CommitBuilder;
|
||||
import org.eclipse.jgit.lib.Config;
|
||||
import org.eclipse.jgit.lib.Repository;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.EnumSet;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* ‘watch.config’ file in the user branch in the All-Users repository that
|
||||
* contains the watch configuration of the user.
|
||||
* <p>
|
||||
* The 'watch.config' file is a git config file that has one 'project' section
|
||||
* for all project watches of a project.
|
||||
* <p>
|
||||
* The project name is used as subsection name and the filters with the notify
|
||||
* types that decide for which events email notifications should be sent are
|
||||
* represented as 'notify' values in the subsection. A 'notify' value is
|
||||
* formatted as '<filter> [<comma-separated-list-of-notify-types>]':
|
||||
*
|
||||
* <pre>
|
||||
* [project "foo"]
|
||||
* notify = * [ALL_COMMENTS]
|
||||
* notify = branch:master [ALL_COMMENTS, NEW_PATCHSETS]
|
||||
* notify = branch:master owner:self [SUBMITTED_CHANGES]
|
||||
* </pre>
|
||||
* <p>
|
||||
* If two notify values in the same subsection have the same filter they are
|
||||
* merged on the next save, taking the union of the notify types.
|
||||
* <p>
|
||||
* For watch configurations that notify on no event the list of notify types is
|
||||
* empty:
|
||||
*
|
||||
* <pre>
|
||||
* [project "foo"]
|
||||
* notify = branch:master []
|
||||
* </pre>
|
||||
* <p>
|
||||
* Unknown notify types are ignored and removed on save.
|
||||
*/
|
||||
public class WatchConfig extends VersionedMetaData implements AutoCloseable {
|
||||
@Singleton
|
||||
public static class Accessor {
|
||||
private final GitRepositoryManager repoManager;
|
||||
private final AllUsersName allUsersName;
|
||||
private final Provider<MetaDataUpdate.User> metaDataUpdateFactory;
|
||||
private final IdentifiedUser.GenericFactory userFactory;
|
||||
|
||||
@Inject
|
||||
Accessor(
|
||||
GitRepositoryManager repoManager,
|
||||
AllUsersName allUsersName,
|
||||
Provider<MetaDataUpdate.User> metaDataUpdateFactory,
|
||||
IdentifiedUser.GenericFactory userFactory) {
|
||||
this.repoManager = repoManager;
|
||||
this.allUsersName = allUsersName;
|
||||
this.metaDataUpdateFactory = metaDataUpdateFactory;
|
||||
this.userFactory = userFactory;
|
||||
}
|
||||
|
||||
public Map<ProjectWatchKey, Set<NotifyType>> getProjectWatches(
|
||||
Account.Id accountId) throws IOException, ConfigInvalidException {
|
||||
try (Repository git = repoManager.openRepository(allUsersName);
|
||||
WatchConfig watchConfig = new WatchConfig(accountId)) {
|
||||
watchConfig.load(git);
|
||||
return watchConfig.getProjectWatches();
|
||||
}
|
||||
}
|
||||
|
||||
public void upsertProjectWatches(Account.Id accountId,
|
||||
Map<ProjectWatchKey, Set<NotifyType>> newProjectWatches)
|
||||
throws IOException, ConfigInvalidException {
|
||||
try (WatchConfig watchConfig = open(accountId)) {
|
||||
Map<ProjectWatchKey, Set<NotifyType>> projectWatches =
|
||||
watchConfig.getProjectWatches();
|
||||
projectWatches.putAll(newProjectWatches);
|
||||
commit(watchConfig);
|
||||
}
|
||||
}
|
||||
|
||||
public void deleteProjectWatches(Account.Id accountId,
|
||||
Collection<ProjectWatchKey> projectWatchKeys)
|
||||
throws IOException, ConfigInvalidException {
|
||||
try (WatchConfig watchConfig = open(accountId)) {
|
||||
Map<ProjectWatchKey, Set<NotifyType>> projectWatches =
|
||||
watchConfig.getProjectWatches();
|
||||
boolean commit = false;
|
||||
for (ProjectWatchKey key : projectWatchKeys) {
|
||||
if (projectWatches.remove(key) != null) {
|
||||
commit = true;
|
||||
}
|
||||
}
|
||||
if (commit) {
|
||||
commit(watchConfig);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private WatchConfig open(Account.Id accountId)
|
||||
throws IOException, ConfigInvalidException {
|
||||
Repository git = repoManager.openRepository(allUsersName);
|
||||
WatchConfig watchConfig = new WatchConfig(accountId);
|
||||
watchConfig.load(git);
|
||||
return watchConfig;
|
||||
}
|
||||
|
||||
private void commit(WatchConfig watchConfig)
|
||||
throws IOException {
|
||||
try (MetaDataUpdate md = metaDataUpdateFactory.get().create(allUsersName,
|
||||
userFactory.create(watchConfig.accountId))) {
|
||||
watchConfig.commit(md);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@AutoValue
|
||||
public abstract static class ProjectWatchKey {
|
||||
public static ProjectWatchKey create(Project.NameKey project,
|
||||
@Nullable String filter) {
|
||||
return new AutoValue_WatchConfig_ProjectWatchKey(project,
|
||||
Strings.emptyToNull(filter));
|
||||
}
|
||||
|
||||
public abstract Project.NameKey project();
|
||||
public abstract @Nullable String filter();
|
||||
}
|
||||
|
||||
private static final String WATCH_CONFIG = "watch.config";
|
||||
private static final String PROJECT = "project";
|
||||
private static final String KEY_NOTIFY = "notify";
|
||||
|
||||
private final Account.Id accountId;
|
||||
private final String ref;
|
||||
|
||||
private Repository git;
|
||||
private Map<ProjectWatchKey, Set<NotifyType>> projectWatches;
|
||||
|
||||
public WatchConfig(Account.Id accountId) {
|
||||
this.accountId = accountId;
|
||||
this.ref = RefNames.refsUsers(accountId);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getRefName() {
|
||||
return ref;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void load(Repository git) throws IOException, ConfigInvalidException {
|
||||
checkState(this.git == null);
|
||||
this.git = git;
|
||||
super.load(git);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLoad() throws IOException, ConfigInvalidException {
|
||||
Config cfg = readConfig(WATCH_CONFIG);
|
||||
projectWatches = parse(accountId, cfg);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public static Map<ProjectWatchKey, Set<NotifyType>> parse(
|
||||
Account.Id accountId, Config cfg) throws ConfigInvalidException {
|
||||
Map<ProjectWatchKey, Set<NotifyType>> projectWatches = new HashMap<>();
|
||||
for (String projectName : cfg.getSubsections(PROJECT)) {
|
||||
String[] notifyValues =
|
||||
cfg.getStringList(PROJECT, projectName, KEY_NOTIFY);
|
||||
for (String nv : notifyValues) {
|
||||
if (Strings.isNullOrEmpty(nv)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
NotifyValue notifyValue = NotifyValue.parse(accountId, projectName, nv);
|
||||
ProjectWatchKey key = ProjectWatchKey
|
||||
.create(new Project.NameKey(projectName), notifyValue.filter());
|
||||
if (!projectWatches.containsKey(key)) {
|
||||
projectWatches.put(key, EnumSet.noneOf(NotifyType.class));
|
||||
}
|
||||
projectWatches.get(key).addAll(notifyValue.notifyTypes());
|
||||
}
|
||||
}
|
||||
return projectWatches;
|
||||
}
|
||||
|
||||
Map<ProjectWatchKey, Set<NotifyType>> getProjectWatches() {
|
||||
checkLoaded();
|
||||
return projectWatches;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean onSave(CommitBuilder commit)
|
||||
throws IOException, ConfigInvalidException {
|
||||
checkLoaded();
|
||||
|
||||
if (Strings.isNullOrEmpty(commit.getMessage())) {
|
||||
commit.setMessage("Updated watch configuration\n");
|
||||
}
|
||||
|
||||
Config cfg = readConfig(WATCH_CONFIG);
|
||||
|
||||
for (String projectName : cfg.getSubsections(PROJECT)) {
|
||||
cfg.unset(PROJECT, projectName, KEY_NOTIFY);
|
||||
}
|
||||
|
||||
Multimap<String, String> notifyValuesByProject = ArrayListMultimap.create();
|
||||
for (Map.Entry<ProjectWatchKey, Set<NotifyType>> e : projectWatches
|
||||
.entrySet()) {
|
||||
NotifyValue notifyValue =
|
||||
NotifyValue.create(e.getKey().filter(), e.getValue());
|
||||
notifyValuesByProject.put(e.getKey().project().get(),
|
||||
notifyValue.toString());
|
||||
}
|
||||
|
||||
for (Map.Entry<String, Collection<String>> e : notifyValuesByProject.asMap()
|
||||
.entrySet()) {
|
||||
cfg.setStringList(PROJECT, e.getKey(), KEY_NOTIFY,
|
||||
new ArrayList<>(e.getValue()));
|
||||
}
|
||||
|
||||
saveConfig(WATCH_CONFIG, cfg);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
if (git != null) {
|
||||
git.close();
|
||||
}
|
||||
}
|
||||
|
||||
private void checkLoaded() {
|
||||
checkState(projectWatches != null, "project watches not loaded yet");
|
||||
}
|
||||
|
||||
@AutoValue
|
||||
abstract static class NotifyValue {
|
||||
public static NotifyValue parse(Account.Id accountId, String project,
|
||||
String notifyValue) throws ConfigInvalidException {
|
||||
notifyValue = notifyValue.trim();
|
||||
int i = notifyValue.lastIndexOf('[');
|
||||
if (i < 0 || notifyValue.charAt(notifyValue.length() - 1) != ']') {
|
||||
throw new ConfigInvalidException(String.format(
|
||||
"Invalid project watch of account %d for project %s: %s",
|
||||
accountId.get(), project, notifyValue));
|
||||
}
|
||||
String filter = notifyValue.substring(0, i).trim();
|
||||
if (filter.isEmpty() || AccountProjectWatch.FILTER_ALL.equals(filter)) {
|
||||
filter = null;
|
||||
}
|
||||
|
||||
Set<NotifyType> notifyTypes = EnumSet.noneOf(NotifyType.class);
|
||||
if (i + 1 < notifyValue.length() - 2) {
|
||||
for (String nt : Splitter.on(',').trimResults().splitToList(
|
||||
notifyValue.substring(i + 1, notifyValue.length() - 1))) {
|
||||
Optional<NotifyType> notifyType =
|
||||
Enums.getIfPresent(NotifyType.class, nt);
|
||||
if (!notifyType.isPresent()) {
|
||||
throw new ConfigInvalidException(String.format(
|
||||
"Invalid notify type %s in project watch "
|
||||
+ "of account %d for project %s: %s",
|
||||
nt, accountId.get(), project, notifyValue));
|
||||
}
|
||||
notifyTypes.add(notifyType.get());
|
||||
}
|
||||
}
|
||||
return create(filter, notifyTypes);
|
||||
}
|
||||
|
||||
public static NotifyValue create(@Nullable String filter,
|
||||
Set<NotifyType> notifyTypes) {
|
||||
return new AutoValue_WatchConfig_NotifyValue(Strings.emptyToNull(filter),
|
||||
Sets.immutableEnumSet(notifyTypes));
|
||||
}
|
||||
|
||||
public abstract @Nullable String filter();
|
||||
public abstract ImmutableSet<NotifyType> notifyTypes();
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
List<NotifyType> notifyTypes = new ArrayList<>(notifyTypes());
|
||||
StringBuilder notifyValue = new StringBuilder();
|
||||
notifyValue.append(firstNonNull(filter(), AccountProjectWatch.FILTER_ALL))
|
||||
.append(" [");
|
||||
Joiner.on(", ").appendTo(notifyValue, notifyTypes);
|
||||
notifyValue.append("]");
|
||||
return notifyValue.toString();
|
||||
}
|
||||
}
|
||||
}
|
@ -221,7 +221,7 @@ public class AccountApiImpl implements AccountApi {
|
||||
public List<ProjectWatchInfo> getWatchedProjects() throws RestApiException {
|
||||
try {
|
||||
return getWatchedProjects.apply(account);
|
||||
} catch (OrmException e) {
|
||||
} catch (OrmException | IOException | ConfigInvalidException e) {
|
||||
throw new RestApiException("Cannot get watched projects", e);
|
||||
}
|
||||
}
|
||||
@ -231,7 +231,7 @@ public class AccountApiImpl implements AccountApi {
|
||||
List<ProjectWatchInfo> in) throws RestApiException {
|
||||
try {
|
||||
return postWatchedProjects.apply(account, in);
|
||||
} catch (OrmException | IOException e) {
|
||||
} catch (OrmException | IOException | ConfigInvalidException e) {
|
||||
throw new RestApiException("Cannot update watched projects", e);
|
||||
}
|
||||
}
|
||||
@ -241,7 +241,7 @@ public class AccountApiImpl implements AccountApi {
|
||||
throws RestApiException {
|
||||
try {
|
||||
deleteWatchedProjects.apply(account, in);
|
||||
} catch (OrmException | IOException e) {
|
||||
} catch (OrmException | IOException | ConfigInvalidException e) {
|
||||
throw new RestApiException("Cannot delete watched projects", e);
|
||||
}
|
||||
}
|
||||
|
@ -20,8 +20,8 @@ import com.google.common.base.Strings;
|
||||
import com.google.common.collect.FluentIterable;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.gerrit.reviewdb.client.AccountExternalId;
|
||||
import com.google.gerrit.reviewdb.client.AccountProjectWatch;
|
||||
import com.google.gerrit.server.account.AccountState;
|
||||
import com.google.gerrit.server.account.WatchConfig.ProjectWatchKey;
|
||||
import com.google.gerrit.server.index.FieldDef;
|
||||
import com.google.gerrit.server.index.FieldType;
|
||||
import com.google.gerrit.server.index.SchemaUtil;
|
||||
@ -152,11 +152,11 @@ public class AccountField {
|
||||
"watchedproject", FieldType.EXACT, false) {
|
||||
@Override
|
||||
public Iterable<String> get(AccountState input, FillArgs args) {
|
||||
return FluentIterable.from(input.getProjectWatches())
|
||||
.transform(new Function<AccountProjectWatch, String>() {
|
||||
return FluentIterable.from(input.getProjectWatches().keySet())
|
||||
.transform(new Function<ProjectWatchKey, String>() {
|
||||
@Override
|
||||
public String apply(AccountProjectWatch in) {
|
||||
return in.getProjectNameKey().get();
|
||||
public String apply(ProjectWatchKey in) {
|
||||
return in.project().get();
|
||||
}
|
||||
}).toSet();
|
||||
}
|
||||
|
@ -28,6 +28,7 @@ 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.ProjectWatchKey;
|
||||
import com.google.gerrit.server.git.NotifyConfig;
|
||||
import com.google.gerrit.server.project.ProjectState;
|
||||
import com.google.gerrit.server.query.Predicate;
|
||||
@ -43,6 +44,7 @@ import org.slf4j.LoggerFactory;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
public class ProjectWatch {
|
||||
@ -94,19 +96,23 @@ public class ProjectWatch {
|
||||
|
||||
for (AccountState a : args.accountQueryProvider.get()
|
||||
.byWatchedProject(project)) {
|
||||
for (AccountProjectWatch w : a.getProjectWatches()) {
|
||||
if (add(matching, w, type)) {
|
||||
Account.Id accountId = a.getAccount().getId();
|
||||
for (Map.Entry<ProjectWatchKey, Set<NotifyType>> e :
|
||||
a.getProjectWatches().entrySet()) {
|
||||
if (add(matching, accountId, e.getKey(), e.getValue(), type)) {
|
||||
// We only want to prevent matching All-Projects if this filter hits
|
||||
projectWatchers.add(w.getAccountId());
|
||||
projectWatchers.add(accountId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (AccountState a : args.accountQueryProvider.get()
|
||||
.byWatchedProject(args.allProjectsName)) {
|
||||
for (AccountProjectWatch w : a.getProjectWatches()) {
|
||||
if (!projectWatchers.contains(w.getAccountId())) {
|
||||
add(matching, w, type);
|
||||
for (Map.Entry<ProjectWatchKey, Set<NotifyType>> e :
|
||||
a.getProjectWatches().entrySet()) {
|
||||
Account.Id accountId = a.getAccount().getId();
|
||||
if (!projectWatchers.contains(accountId)) {
|
||||
add(matching, accountId, e.getKey(), e.getValue(), type);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -210,6 +216,26 @@ public class ProjectWatch {
|
||||
}
|
||||
}
|
||||
|
||||
private boolean add(Watchers matching, Account.Id accountId,
|
||||
ProjectWatchKey key, Set<NotifyType> watchedTypes, NotifyType type)
|
||||
throws OrmException {
|
||||
IdentifiedUser user = args.identifiedUserFactory.create(accountId);
|
||||
|
||||
try {
|
||||
if (filterMatch(user, key.filter())) {
|
||||
// If we are set to notify on this type, add the user.
|
||||
// Otherwise, still return true to stop notifications for this user.
|
||||
if (watchedTypes.contains(type)) {
|
||||
matching.bcc.accounts.add(accountId);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
} catch (QueryParseException e) {
|
||||
// Ignore broken filter expressions.
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean add(Watchers matching, AccountProjectWatch w, NotifyType type)
|
||||
throws OrmException {
|
||||
IdentifiedUser user = args.identifiedUserFactory.create(w.getAccountId());
|
||||
|
@ -14,6 +14,8 @@
|
||||
|
||||
package com.google.gerrit.server.query.account;
|
||||
|
||||
import com.google.common.base.Joiner;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.gerrit.reviewdb.client.Project;
|
||||
import com.google.gerrit.server.account.AccountState;
|
||||
import com.google.gerrit.server.index.IndexConfig;
|
||||
@ -22,10 +24,16 @@ import com.google.gerrit.server.query.InternalQuery;
|
||||
import com.google.gwtorm.server.OrmException;
|
||||
import com.google.inject.Inject;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
public class InternalAccountQuery extends InternalQuery<AccountState> {
|
||||
private static final Logger log =
|
||||
LoggerFactory.getLogger(InternalAccountQuery.class);
|
||||
|
||||
@Inject
|
||||
InternalAccountQuery(AccountQueryProcessor queryProcessor,
|
||||
AccountIndexCollection indexes,
|
||||
@ -67,6 +75,22 @@ public class InternalAccountQuery extends InternalQuery<AccountState> {
|
||||
return query(AccountPredicates.externalId(externalId));
|
||||
}
|
||||
|
||||
public AccountState oneByExternalId(String externalId) throws OrmException {
|
||||
List<AccountState> accountStates = byExternalId(externalId);
|
||||
if (accountStates.size() == 1) {
|
||||
return accountStates.get(0);
|
||||
} else if (accountStates.size() > 0) {
|
||||
StringBuilder msg = new StringBuilder();
|
||||
msg.append("Ambiguous external ID ")
|
||||
.append(externalId)
|
||||
.append("for accounts: ");
|
||||
Joiner.on(", ").appendTo(msg,
|
||||
Lists.transform(accountStates, AccountState.ACCOUNT_ID_FUNCTION));
|
||||
log.warn(msg.toString());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public List<AccountState> byFullName(String fullName)
|
||||
throws OrmException {
|
||||
return query(AccountPredicates.fullName(fullName));
|
||||
|
@ -15,8 +15,8 @@
|
||||
package com.google.gerrit.server.query.change;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.gerrit.reviewdb.client.AccountProjectWatch;
|
||||
import com.google.gerrit.server.CurrentUser;
|
||||
import com.google.gerrit.server.account.WatchConfig.ProjectWatchKey;
|
||||
import com.google.gerrit.server.query.AndPredicate;
|
||||
import com.google.gerrit.server.query.Predicate;
|
||||
import com.google.gerrit.server.query.QueryBuilder;
|
||||
@ -48,11 +48,11 @@ class IsWatchedByPredicate extends AndPredicate<ChangeData> {
|
||||
boolean checkIsVisible) throws QueryParseException {
|
||||
List<Predicate<ChangeData>> r = new ArrayList<>();
|
||||
ChangeQueryBuilder builder = new ChangeQueryBuilder(args);
|
||||
for (AccountProjectWatch w : getWatches(args)) {
|
||||
for (ProjectWatchKey w : getWatches(args)) {
|
||||
Predicate<ChangeData> f = null;
|
||||
if (w.getFilter() != null) {
|
||||
if (w.filter() != null) {
|
||||
try {
|
||||
f = builder.parse(w.getFilter());
|
||||
f = builder.parse(w.filter());
|
||||
if (QueryBuilder.find(f, IsWatchedByPredicate.class) != null) {
|
||||
// If the query is going to infinite loop, assume it
|
||||
// will never match and return null. Yes this test
|
||||
@ -66,10 +66,10 @@ class IsWatchedByPredicate extends AndPredicate<ChangeData> {
|
||||
}
|
||||
|
||||
Predicate<ChangeData> p;
|
||||
if (w.getProjectNameKey().equals(args.allProjectsName)) {
|
||||
if (w.project().equals(args.allProjectsName)) {
|
||||
p = null;
|
||||
} else {
|
||||
p = builder.project(w.getProjectNameKey().get());
|
||||
p = builder.project(w.project().get());
|
||||
}
|
||||
|
||||
if (p != null && f != null) {
|
||||
@ -91,14 +91,14 @@ class IsWatchedByPredicate extends AndPredicate<ChangeData> {
|
||||
}
|
||||
}
|
||||
|
||||
private static Collection<AccountProjectWatch> getWatches(
|
||||
private static Collection<ProjectWatchKey> getWatches(
|
||||
ChangeQueryBuilder.Arguments args) throws QueryParseException {
|
||||
CurrentUser user = args.getUser();
|
||||
if (user.isIdentifiedUser()) {
|
||||
return args.accountCache.get(args.getUser().getAccountId())
|
||||
.getProjectWatches();
|
||||
.getProjectWatches().keySet();
|
||||
}
|
||||
return Collections.<AccountProjectWatch> emptySet();
|
||||
return Collections.<ProjectWatchKey> emptySet();
|
||||
}
|
||||
|
||||
private static List<Predicate<ChangeData>> none() {
|
||||
|
@ -0,0 +1,166 @@
|
||||
// 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.account;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import com.google.gerrit.reviewdb.client.Account;
|
||||
import com.google.gerrit.reviewdb.client.Project;
|
||||
import com.google.gerrit.reviewdb.client.AccountProjectWatch.NotifyType;
|
||||
import com.google.gerrit.server.account.WatchConfig.NotifyValue;
|
||||
import com.google.gerrit.server.account.WatchConfig.ProjectWatchKey;
|
||||
|
||||
import org.eclipse.jgit.errors.ConfigInvalidException;
|
||||
import org.eclipse.jgit.lib.Config;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
|
||||
import java.util.EnumSet;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
public class WatchConfigTest {
|
||||
@Rule
|
||||
public ExpectedException exception = ExpectedException.none();
|
||||
|
||||
@Test
|
||||
public void parseWatchConfig() throws Exception {
|
||||
Config cfg = new Config();
|
||||
cfg.fromText("[project \"myProject\"]\n"
|
||||
+ " notify = * [ALL_COMMENTS, NEW_PATCHSETS]\n"
|
||||
+ " notify = branch:master [NEW_CHANGES]\n"
|
||||
+ " notify = branch:master [NEW_PATCHSETS]\n"
|
||||
+ " notify = branch:foo []\n"
|
||||
+ "[project \"otherProject\"]\n"
|
||||
+ " notify = [NEW_PATCHSETS]\n"
|
||||
+ " notify = * [NEW_PATCHSETS, ALL_COMMENTS]\n");
|
||||
Map<ProjectWatchKey, Set<NotifyType>> projectWatches =
|
||||
WatchConfig.parse(new Account.Id(1000000), cfg);
|
||||
|
||||
Project.NameKey myProject = new Project.NameKey("myProject");
|
||||
Project.NameKey otherProject = new Project.NameKey("otherProject");
|
||||
Map<ProjectWatchKey, Set<NotifyType>> expectedProjectWatches =
|
||||
new HashMap<>();
|
||||
expectedProjectWatches.put(ProjectWatchKey.create(myProject, null),
|
||||
EnumSet.of(NotifyType.ALL_COMMENTS, NotifyType.NEW_PATCHSETS));
|
||||
expectedProjectWatches.put(
|
||||
ProjectWatchKey.create(myProject, "branch:master"),
|
||||
EnumSet.of(NotifyType.NEW_CHANGES, NotifyType.NEW_PATCHSETS));
|
||||
expectedProjectWatches.put(ProjectWatchKey.create(myProject, "branch:foo"),
|
||||
EnumSet.noneOf(NotifyType.class));
|
||||
expectedProjectWatches.put(ProjectWatchKey.create(otherProject, null),
|
||||
EnumSet.of(NotifyType.NEW_PATCHSETS));
|
||||
expectedProjectWatches.put(ProjectWatchKey.create(otherProject, null),
|
||||
EnumSet.of(NotifyType.ALL_COMMENTS, NotifyType.NEW_PATCHSETS));
|
||||
assertThat(projectWatches).containsExactlyEntriesIn(expectedProjectWatches);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseInvalidWatchConfig() throws Exception {
|
||||
Config cfg = new Config();
|
||||
cfg.fromText("[project \"myProject\"]\n"
|
||||
+ " notify = * [ALL_COMMENTS, NEW_PATCHSETS]\n"
|
||||
+ " notify = branch:master [INVALID, NEW_CHANGES]\n"
|
||||
+ "[project \"otherProject\"]\n"
|
||||
+ " notify = [NEW_PATCHSETS]\n");
|
||||
|
||||
exception.expect(ConfigInvalidException.class);
|
||||
exception.expectMessage(
|
||||
"Invalid notify type INVALID in project watch of account 1000000"
|
||||
+ " for project myProject: branch:master [INVALID, NEW_CHANGES]");
|
||||
WatchConfig.parse(new Account.Id(1000000), cfg);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseNotifyValue() throws Exception {
|
||||
assertParseNotifyValue("* []", null, EnumSet.noneOf(NotifyType.class));
|
||||
assertParseNotifyValue("* [ALL_COMMENTS]", null,
|
||||
EnumSet.of(NotifyType.ALL_COMMENTS));
|
||||
assertParseNotifyValue("[]", null, EnumSet.noneOf(NotifyType.class));
|
||||
assertParseNotifyValue("[ALL_COMMENTS, NEW_PATCHSETS]", null,
|
||||
EnumSet.of(NotifyType.ALL_COMMENTS, NotifyType.NEW_PATCHSETS));
|
||||
assertParseNotifyValue("branch:master []", "branch:master",
|
||||
EnumSet.noneOf(NotifyType.class));
|
||||
assertParseNotifyValue("branch:master || branch:stable []",
|
||||
"branch:master || branch:stable", EnumSet.noneOf(NotifyType.class));
|
||||
assertParseNotifyValue("branch:master [ALL_COMMENTS]", "branch:master",
|
||||
EnumSet.of(NotifyType.ALL_COMMENTS));
|
||||
assertParseNotifyValue("branch:master [ALL_COMMENTS, NEW_PATCHSETS]",
|
||||
"branch:master",
|
||||
EnumSet.of(NotifyType.ALL_COMMENTS, NotifyType.NEW_PATCHSETS));
|
||||
assertParseNotifyValue("* [ALL]", null, EnumSet.of(NotifyType.ALL));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseInvalidNotifyValue() {
|
||||
assertParseNotifyValueFails("* [] illegal-characters-at-the-end");
|
||||
assertParseNotifyValueFails("* [INVALID]");
|
||||
assertParseNotifyValueFails("* [ALL_COMMENTS, UNKNOWN]");
|
||||
assertParseNotifyValueFails("* [ALL_COMMENTS NEW_CHANGES]");
|
||||
assertParseNotifyValueFails("* [ALL_COMMENTS, NEW_CHANGES");
|
||||
assertParseNotifyValueFails("* ALL_COMMENTS, NEW_CHANGES]");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toNotifyValue() throws Exception {
|
||||
assertToNotifyValue(null, EnumSet.noneOf(NotifyType.class), "* []");
|
||||
assertToNotifyValue("*", EnumSet.noneOf(NotifyType.class), "* []");
|
||||
assertToNotifyValue(null, EnumSet.of(NotifyType.ALL_COMMENTS),
|
||||
"* [ALL_COMMENTS]");
|
||||
assertToNotifyValue("branch:master", EnumSet.noneOf(NotifyType.class),
|
||||
"branch:master []");
|
||||
assertToNotifyValue("branch:master",
|
||||
EnumSet.of(NotifyType.ALL_COMMENTS, NotifyType.NEW_PATCHSETS),
|
||||
"branch:master [ALL_COMMENTS, NEW_PATCHSETS]");
|
||||
assertToNotifyValue("branch:master",
|
||||
EnumSet.of(NotifyType.ABANDONED_CHANGES, NotifyType.ALL_COMMENTS,
|
||||
NotifyType.NEW_CHANGES, NotifyType.NEW_PATCHSETS,
|
||||
NotifyType.SUBMITTED_CHANGES),
|
||||
"branch:master [ABANDONED_CHANGES, ALL_COMMENTS, NEW_CHANGES,"
|
||||
+ " NEW_PATCHSETS, SUBMITTED_CHANGES]");
|
||||
assertToNotifyValue("*", EnumSet.of(NotifyType.ALL), "* [ALL]");
|
||||
}
|
||||
|
||||
private static void assertParseNotifyValue(String notifyValue,
|
||||
String expectedFilter, Set<NotifyType> expectedNotifyTypes)
|
||||
throws ConfigInvalidException {
|
||||
NotifyValue nv = parseNotifyValue(notifyValue);
|
||||
assertThat(nv.filter()).isEqualTo(expectedFilter);
|
||||
assertThat(nv.notifyTypes()).containsExactlyElementsIn(expectedNotifyTypes);
|
||||
}
|
||||
|
||||
private static void assertToNotifyValue(String filter,
|
||||
Set<NotifyType> notifyTypes, String expectedNotifyValue) {
|
||||
NotifyValue nv = NotifyValue.create(filter, notifyTypes);
|
||||
assertThat(nv.toString()).isEqualTo(expectedNotifyValue);
|
||||
}
|
||||
|
||||
private static void assertParseNotifyValueFails(String notifyValue) {
|
||||
try {
|
||||
parseNotifyValue(notifyValue);
|
||||
fail("expected ConfigInvalidException for notifyValue: " + notifyValue);
|
||||
} catch (ConfigInvalidException e) {
|
||||
// Expected.
|
||||
}
|
||||
}
|
||||
|
||||
private static NotifyValue parseNotifyValue(String notifyValue)
|
||||
throws ConfigInvalidException {
|
||||
return NotifyValue.parse(new Account.Id(1000000), "project", notifyValue);
|
||||
}
|
||||
}
|
@ -25,9 +25,10 @@ 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;
|
||||
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.ProjectWatchKey;
|
||||
|
||||
import org.eclipse.jgit.lib.Config;
|
||||
import org.eclipse.jgit.lib.PersonIdent;
|
||||
@ -35,6 +36,8 @@ import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Set;
|
||||
|
||||
public class FromAddressGeneratorProviderTest {
|
||||
private Config config;
|
||||
@ -300,6 +303,6 @@ public class FromAddressGeneratorProviderTest {
|
||||
account.setPreferredEmail(email);
|
||||
return new AccountState(account, Collections.<AccountGroup.UUID> emptySet(),
|
||||
Collections.<AccountExternalId> emptySet(),
|
||||
Collections.<AccountProjectWatch> emptySet());
|
||||
new HashMap<ProjectWatchKey, Set<NotifyType>>());
|
||||
}
|
||||
}
|
||||
|
@ -19,12 +19,14 @@ 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;
|
||||
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.ProjectWatchKey;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/** Fake implementation of {@link AccountCache} for testing. */
|
||||
public class FakeAccountCache implements AccountCache {
|
||||
@ -76,6 +78,6 @@ public class FakeAccountCache implements AccountCache {
|
||||
private static AccountState newState(Account account) {
|
||||
return new AccountState(account, ImmutableSet.<AccountGroup.UUID> of(),
|
||||
ImmutableSet.<AccountExternalId> of(),
|
||||
ImmutableSet.<AccountProjectWatch> of());
|
||||
new HashMap<ProjectWatchKey, Set<NotifyType>>());
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user