Merge branch 'stable-2.15'
* stable-2.15: (43 commits)
Simplify client module
Split reviewers plugin into GWT and non-GWT variants
Update bazlets to latest revision on stable-2.15
Add modify reviewers config plugin capability
Fix classpath generation for Eclipse project
Update bazlets to latest revision on stable-2.15
ReviewersResolver: Log failure to list accounts for group as error
ReviewersResolver: Don't log stack trace when group does not exist
ReviewersResolver: Emit a warning to the log when account is inactive
Update bazlets to latest stable-2.15 to use 2.15.1 API
Update bazlets to latest stable-2.14 and switch to 2.14.8 release API
Fix compilation warnings flagged by Eclipse
Upgrade bazlets and use 2.15 release API
Reviewers: Restrict index query to the given change
Build with 2.15-SNAPSHOT API
Update bazlets to latest on stable-2.15
Add integration test for adding reviewers
ReviewersConfig: Add unit test for merged project inheritance
ReviewerFilterSection: Add equals and hashCode methods
ReviewersResolver#resolveGroup: Simplify error handling
Remove stale documentation how to build with maven
Add reviewers resolver test
ReviewersResolver#resolve: Extract logic in small methods
Reviewers: Extract account resolver logic to it own class
Add members of all matching groups
ReviewersConfig: merge parent reviewers.config
Update bazlets to latest stable-2.14 to use 2.14.8-SNAPSHOT API
Update bazlets to the latest on stable-2.14 to use 2.14.7 release API
Reviewers: Don't create ChangeData instances
Reviewers: Actually use change index for filter query
Refactor ReviewersConfig to also handle the global config
Rename DefaultReviewers to AddReviewersByConfiguration
Factor addition of reviewers into own Runnable class
Reviewers: Don't add change uploader as reviewer
Reviewers: Only consider active accounts
Reviewers: Pass RevisionEvent to onEvent method
Add project name & change Id to the logs
Reviewers: Use log built-in string formatting
Handle the QueryParseException when querying a group that does not exist
Reviewers#suggestReviewers: Add null check on changeId
Add support for reviewer suggestion
ChangeEventListener: Remove unnecessary final modifiers
ChangeEventListener: Don't unnecessarily open the repository
Change-Id: I74e11c3f770ecf923b78a1e792a59365e8a062a5
diff --git a/BUILD b/BUILD
index 96f7151..fc5a471 100644
--- a/BUILD
+++ b/BUILD
@@ -1,4 +1,10 @@
-load("//tools/bzl:plugin.bzl", "gerrit_plugin")
+load("//tools/bzl:junit.bzl", "junit_tests")
+load(
+ "//tools/bzl:plugin.bzl",
+ "PLUGIN_DEPS",
+ "PLUGIN_TEST_DEPS",
+ "gerrit_plugin",
+)
SRC = "src/main/java/com/googlesource/gerrit/plugins/reviewers/"
@@ -32,3 +38,21 @@
],
resources = glob(["src/main/**/*"]),
)
+
+junit_tests(
+ name = "reviewers_tests",
+ srcs = glob(["src/test/java/**/*.java"]),
+ tags = ["reviewers"],
+ deps = [
+ ":reviewers__plugin_test_deps",
+ ],
+)
+
+java_library(
+ name = "reviewers__plugin_test_deps",
+ testonly = 1,
+ visibility = ["//visibility:public"],
+ exports = PLUGIN_DEPS + PLUGIN_TEST_DEPS + [
+ ":reviewers__plugin",
+ ],
+)
diff --git a/src/main/java/com/googlesource/gerrit/plugins/reviewers/ClientModule.java b/src/main/java/com/googlesource/gerrit/plugins/reviewers/ClientModule.java
index 88c79bd..4aac38f 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/reviewers/ClientModule.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/reviewers/ClientModule.java
@@ -14,38 +14,16 @@
package com.googlesource.gerrit.plugins.reviewers;
-import com.google.gerrit.extensions.annotations.PluginName;
-import com.google.gerrit.extensions.config.FactoryModule;
import com.google.gerrit.extensions.registration.DynamicSet;
import com.google.gerrit.extensions.webui.GwtPlugin;
import com.google.gerrit.extensions.webui.TopMenu;
import com.google.gerrit.extensions.webui.WebUiPlugin;
-import com.google.gerrit.server.config.PluginConfigFactory;
-import com.google.inject.Inject;
-import org.eclipse.jgit.lib.Config;
+import com.google.inject.AbstractModule;
-public class ClientModule extends FactoryModule {
- private final boolean enableUI;
-
- @Inject
- public ClientModule(@PluginName String pluginName, PluginConfigFactory pluginCfgFactory) {
- this(pluginCfgFactory.getGlobalPluginConfig(pluginName));
- }
-
- public ClientModule(Config c) {
- boolean enableREST = c.getBoolean("reviewers", null, "enableREST", true);
- this.enableUI = enableREST ? c.getBoolean("reviewers", null, "enableUI", true) : false;
- }
-
- public ClientModule(boolean enableUI) {
- this.enableUI = enableUI;
- }
-
+public class ClientModule extends AbstractModule {
@Override
protected void configure() {
- if (enableUI) {
- DynamicSet.bind(binder(), TopMenu.class).to(ReviewersTopMenu.class);
- DynamicSet.bind(binder(), WebUiPlugin.class).toInstance(new GwtPlugin("reviewers"));
- }
+ DynamicSet.bind(binder(), TopMenu.class).to(ReviewersTopMenu.class);
+ DynamicSet.bind(binder(), WebUiPlugin.class).toInstance(new GwtPlugin("reviewers"));
}
}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/reviewers/Module.java b/src/main/java/com/googlesource/gerrit/plugins/reviewers/Module.java
index ea140b5..55b663c 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/reviewers/Module.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/reviewers/Module.java
@@ -14,24 +14,22 @@
package com.googlesource.gerrit.plugins.reviewers;
-import com.google.gerrit.extensions.annotations.PluginName;
-import com.google.gerrit.server.config.PluginConfigFactory;
import com.google.inject.AbstractModule;
import com.google.inject.Inject;
import com.googlesource.gerrit.plugins.reviewers.server.BackendModule;
-import org.eclipse.jgit.lib.Config;
+import com.googlesource.gerrit.plugins.reviewers.server.ReviewersConfig;
public class Module extends AbstractModule {
- private final Config c;
+ private final ReviewersConfig cfg;
@Inject
- public Module(@PluginName String pluginName, PluginConfigFactory pluginCfgFactory) {
- c = pluginCfgFactory.getGlobalPluginConfig(pluginName);
+ public Module(ReviewersConfig cfg) {
+ this.cfg = cfg;
}
@Override
protected void configure() {
- install(new BackendModule(c));
- install(new ClientModule(c));
+ install(new BackendModule(cfg.enableREST(), cfg.suggestOnly()));
+ install(new ClientModule());
}
}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/reviewers/client/AccountCapabilities.java b/src/main/java/com/googlesource/gerrit/plugins/reviewers/client/AccountCapabilities.java
new file mode 100644
index 0000000..397ffcb
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/reviewers/client/AccountCapabilities.java
@@ -0,0 +1,31 @@
+// Copyright (C) 2018 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.googlesource.gerrit.plugins.reviewers.client;
+
+import com.google.gerrit.plugin.client.rpc.RestApi;
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.user.client.rpc.AsyncCallback;
+
+public class AccountCapabilities extends JavaScriptObject {
+ static String MODIFY_REVIEWERS_CONFIG = "reviewers-modifyReviewersConfig";
+
+ public static void queryPluginCapability(AsyncCallback<AccountCapabilities> cb) {
+ new RestApi("/accounts/self/capabilities").addParameter("q", MODIFY_REVIEWERS_CONFIG).get(cb);
+ }
+
+ protected AccountCapabilities() {}
+
+ public final native boolean canPerform(String name) /*-{ return this[name] ? true : false; }-*/;
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/reviewers/client/ReviewersScreen.java b/src/main/java/com/googlesource/gerrit/plugins/reviewers/client/ReviewersScreen.java
index ba601ae..47bedd7 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/reviewers/client/ReviewersScreen.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/reviewers/client/ReviewersScreen.java
@@ -14,6 +14,8 @@
package com.googlesource.gerrit.plugins.reviewers.client;
+import static com.googlesource.gerrit.plugins.reviewers.client.AccountCapabilities.MODIFY_REVIEWERS_CONFIG;
+
import com.google.gerrit.client.rpc.NativeMap;
import com.google.gerrit.client.rpc.Natives;
import com.google.gerrit.plugin.client.rpc.RestApi;
@@ -48,10 +50,11 @@
}
private boolean isOwner;
+ private boolean hasModifyReviewersConfigCapability;
private String projectName;
private Set<ReviewerEntry> rEntries;
- ReviewersScreen(final String projectName) {
+ ReviewersScreen(String projectName) {
setStyleName("reviewers-panel");
this.projectName = projectName;
this.rEntries = new HashSet<>();
@@ -64,7 +67,24 @@
@Override
public void onSuccess(NativeMap<ProjectAccessInfo> result) {
isOwner = result.get(projectName).isOwner();
- display();
+ if (isOwner) {
+ display();
+ } else {
+ // TODO(davido): Find a way to run above and below requests in parallel
+ AccountCapabilities.queryPluginCapability(
+ new AsyncCallback<AccountCapabilities>() {
+
+ @Override
+ public void onSuccess(AccountCapabilities result) {
+ hasModifyReviewersConfigCapability =
+ result.canPerform(MODIFY_REVIEWERS_CONFIG);
+ display();
+ }
+
+ @Override
+ public void onFailure(Throwable caught) {}
+ });
+ }
}
@Override
@@ -124,7 +144,7 @@
doSave(Action.REMOVE, e);
}
});
- removeButton.setVisible(isOwner);
+ removeButton.setVisible(isModifiable());
HorizontalPanel p = new HorizontalPanel();
p.add(l);
@@ -161,9 +181,9 @@
reviewerBox.setText("");
}
});
- filterBox.setEnabled(isOwner);
- reviewerBox.setEnabled(isOwner);
- addButton.setEnabled(isOwner);
+ filterBox.setEnabled(isModifiable());
+ reviewerBox.setEnabled(isModifiable());
+ addButton.setEnabled(isModifiable());
Panel p = new VerticalPanel();
p.setStyleName("reviewers-inputPanel");
@@ -172,6 +192,10 @@
return p;
}
+ boolean isModifiable() {
+ return isOwner || hasModifyReviewersConfigCapability;
+ }
+
void doSave(Action action, ReviewerEntry entry) {
ChangeReviewersInput in = ChangeReviewersInput.create();
in.setAction(action);
diff --git a/src/main/java/com/googlesource/gerrit/plugins/reviewers/server/AddReviewers.java b/src/main/java/com/googlesource/gerrit/plugins/reviewers/server/AddReviewers.java
new file mode 100644
index 0000000..be04ea4
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/reviewers/server/AddReviewers.java
@@ -0,0 +1,118 @@
+// Copyright (C) 2018 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.googlesource.gerrit.plugins.reviewers.server;
+
+import com.google.gerrit.extensions.api.GerritApi;
+import com.google.gerrit.extensions.api.changes.AddReviewerInput;
+import com.google.gerrit.extensions.api.changes.ReviewInput;
+import com.google.gerrit.extensions.common.ChangeInfo;
+import com.google.gerrit.extensions.restapi.RestApiException;
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.util.RequestContext;
+import com.google.gerrit.server.util.ThreadLocalRequestContext;
+import com.google.gwtorm.server.OrmException;
+import com.google.gwtorm.server.SchemaFactory;
+import com.google.inject.Provider;
+import com.google.inject.ProvisionException;
+import java.util.ArrayList;
+import java.util.Set;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+abstract class AddReviewers implements Runnable {
+ private static final Logger log = LoggerFactory.getLogger(AddReviewers.class);
+
+ private final ThreadLocalRequestContext tl;
+ protected final GerritApi gApi;
+ protected final IdentifiedUser.GenericFactory identifiedUserFactory;
+ protected final SchemaFactory<ReviewDb> schemaFactory;
+ protected final ChangeInfo changeInfo;
+
+ private ReviewDb db = null;
+
+ AddReviewers(
+ ThreadLocalRequestContext tl,
+ GerritApi gApi,
+ IdentifiedUser.GenericFactory identifiedUserFactory,
+ SchemaFactory<ReviewDb> schemaFactory,
+ ChangeInfo changeInfo) {
+ this.tl = tl;
+ this.gApi = gApi;
+ this.identifiedUserFactory = identifiedUserFactory;
+ this.schemaFactory = schemaFactory;
+ this.changeInfo = changeInfo;
+ }
+
+ abstract Set<Account.Id> getReviewers();
+
+ @Override
+ public void run() {
+ RequestContext old =
+ tl.setContext(
+ new RequestContext() {
+
+ @Override
+ public CurrentUser getUser() {
+ return identifiedUserFactory.create(new Account.Id(changeInfo.owner._accountId));
+ }
+
+ @Override
+ public Provider<ReviewDb> getReviewDbProvider() {
+ return new Provider<ReviewDb>() {
+ @Override
+ public ReviewDb get() {
+ if (db == null) {
+ try {
+ db = schemaFactory.open();
+ } catch (OrmException e) {
+ throw new ProvisionException("Cannot open ReviewDb", e);
+ }
+ }
+ return db;
+ }
+ };
+ }
+ });
+ try {
+ addReviewers(getReviewers(), changeInfo);
+ } finally {
+ tl.setContext(old);
+ if (db != null) {
+ db.close();
+ db = null;
+ }
+ }
+ }
+
+ private void addReviewers(Set<Account.Id> reviewers, ChangeInfo changeInfo) {
+ try {
+ // TODO(davido): Switch back to using changes API again,
+ // when it supports batch mode for adding reviewers
+ ReviewInput in = new ReviewInput();
+ in.reviewers = new ArrayList<>(reviewers.size());
+ for (Account.Id account : reviewers) {
+ AddReviewerInput addReviewerInput = new AddReviewerInput();
+ addReviewerInput.reviewer = account.toString();
+ in.reviewers.add(addReviewerInput);
+ }
+ gApi.changes().id(changeInfo._number).current().review(in);
+ } catch (RestApiException e) {
+ log.error("Couldn't add reviewers to the change", e);
+ }
+ }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/reviewers/server/AddReviewersByConfiguration.java b/src/main/java/com/googlesource/gerrit/plugins/reviewers/server/AddReviewersByConfiguration.java
new file mode 100644
index 0000000..1ef93f2
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/reviewers/server/AddReviewersByConfiguration.java
@@ -0,0 +1,51 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.googlesource.gerrit.plugins.reviewers.server;
+
+import com.google.gerrit.extensions.api.GerritApi;
+import com.google.gerrit.extensions.common.ChangeInfo;
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.util.ThreadLocalRequestContext;
+import com.google.gwtorm.server.SchemaFactory;
+import com.google.inject.Inject;
+import com.google.inject.assistedinject.Assisted;
+import java.util.Set;
+
+class AddReviewersByConfiguration extends AddReviewers {
+ private final Set<Account.Id> reviewers;
+
+ interface Factory {
+ AddReviewersByConfiguration create(ChangeInfo changeInfo, Set<Account.Id> reviewers);
+ }
+
+ @Inject
+ AddReviewersByConfiguration(
+ ThreadLocalRequestContext tl,
+ GerritApi gApi,
+ IdentifiedUser.GenericFactory identifiedUserFactory,
+ SchemaFactory<ReviewDb> schemaFactory,
+ @Assisted ChangeInfo changeInfo,
+ @Assisted Set<Account.Id> reviewers) {
+ super(tl, gApi, identifiedUserFactory, schemaFactory, changeInfo);
+ this.reviewers = reviewers;
+ }
+
+ @Override
+ Set<Account.Id> getReviewers() {
+ return reviewers;
+ }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/reviewers/server/BackendModule.java b/src/main/java/com/googlesource/gerrit/plugins/reviewers/server/BackendModule.java
index 08b49e0..c233d2b 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/reviewers/server/BackendModule.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/reviewers/server/BackendModule.java
@@ -15,37 +15,53 @@
package com.googlesource.gerrit.plugins.reviewers.server;
import static com.google.gerrit.server.project.ProjectResource.PROJECT_KIND;
+import static com.googlesource.gerrit.plugins.reviewers.server.ModifyReviewersConfigCapability.MODIFY_REVIEWERS_CONFIG;
-import com.google.gerrit.extensions.annotations.PluginName;
+import com.google.gerrit.extensions.annotations.Exports;
+import com.google.gerrit.extensions.config.CapabilityDefinition;
import com.google.gerrit.extensions.config.FactoryModule;
import com.google.gerrit.extensions.events.RevisionCreatedListener;
import com.google.gerrit.extensions.registration.DynamicSet;
import com.google.gerrit.extensions.restapi.RestApiModule;
-import com.google.gerrit.server.config.PluginConfigFactory;
+import com.google.gerrit.server.change.ReviewerSuggestion;
+import com.google.inject.AbstractModule;
import com.google.inject.Inject;
-import org.eclipse.jgit.lib.Config;
public class BackendModule extends FactoryModule {
- public final boolean enableREST;
+ private final boolean enableREST;
+ private final boolean suggestOnly;
@Inject
- public BackendModule(@PluginName String pluginName, PluginConfigFactory pluginCfgFactory) {
- this(pluginCfgFactory.getGlobalPluginConfig(pluginName));
+ public BackendModule(ReviewersConfig cfg) {
+ this(cfg.enableREST(), cfg.suggestOnly());
}
- public BackendModule(Config c) {
- this.enableREST = c.getBoolean("reviewers", null, "enableREST", true);
- }
-
- public BackendModule(boolean enableREST) {
+ public BackendModule(boolean enableREST, boolean suggestOnly) {
this.enableREST = enableREST;
+ this.suggestOnly = suggestOnly;
}
@Override
protected void configure() {
- DynamicSet.bind(binder(), RevisionCreatedListener.class).to(ChangeEventListener.class);
- factory(DefaultReviewers.Factory.class);
- factory(ReviewersConfig.Factory.class);
+ bind(CapabilityDefinition.class)
+ .annotatedWith(Exports.named(MODIFY_REVIEWERS_CONFIG))
+ .to(ModifyReviewersConfigCapability.class);
+
+ if (suggestOnly) {
+ install(
+ new AbstractModule() {
+ @Override
+ protected void configure() {
+ bind(ReviewerSuggestion.class)
+ .annotatedWith(Exports.named("reviewer-suggest"))
+ .to(Reviewers.class);
+ }
+ });
+ } else {
+ DynamicSet.bind(binder(), RevisionCreatedListener.class).to(Reviewers.class);
+ }
+
+ factory(AddReviewersByConfiguration.Factory.class);
if (enableREST) {
install(
diff --git a/src/main/java/com/googlesource/gerrit/plugins/reviewers/server/ChangeEventListener.java b/src/main/java/com/googlesource/gerrit/plugins/reviewers/server/ChangeEventListener.java
deleted file mode 100644
index 2bf1295..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/reviewers/server/ChangeEventListener.java
+++ /dev/null
@@ -1,245 +0,0 @@
-// Copyright (C) 2013 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.googlesource.gerrit.plugins.reviewers.server;
-
-import com.google.common.base.Preconditions;
-import com.google.common.base.Strings;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Sets;
-import com.google.gerrit.extensions.common.ChangeInfo;
-import com.google.gerrit.extensions.events.RevisionCreatedListener;
-import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
-import com.google.gerrit.index.query.Predicate;
-import com.google.gerrit.index.query.QueryParseException;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Change;
-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.AccountResolver;
-import com.google.gerrit.server.account.GroupMembers;
-import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gerrit.server.git.WorkQueue;
-import com.google.gerrit.server.project.NoSuchProjectException;
-import com.google.gerrit.server.query.change.ChangeData;
-import com.google.gerrit.server.query.change.ChangeQueryBuilder;
-import com.google.gerrit.server.restapi.group.GroupsCollection;
-import com.google.gerrit.server.util.RequestContext;
-import com.google.gerrit.server.util.ThreadLocalRequestContext;
-import com.google.gwtorm.server.OrmException;
-import com.google.gwtorm.server.SchemaFactory;
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-import com.google.inject.ProvisionException;
-import com.google.inject.Singleton;
-import java.io.IOException;
-import java.util.List;
-import java.util.Set;
-import org.eclipse.jgit.errors.ConfigInvalidException;
-import org.eclipse.jgit.lib.Repository;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-@Singleton
-class ChangeEventListener implements RevisionCreatedListener {
- private static final Logger log = LoggerFactory.getLogger(ChangeEventListener.class);
-
- private final AccountResolver accountResolver;
- private final Provider<GroupsCollection> groupsCollection;
- private final GroupMembers groupMembers;
- private final DefaultReviewers.Factory reviewersFactory;
- private final GitRepositoryManager repoManager;
- private final WorkQueue workQueue;
- private final IdentifiedUser.GenericFactory identifiedUserFactory;
- private final ThreadLocalRequestContext tl;
- private final SchemaFactory<ReviewDb> schemaFactory;
- private final ChangeData.Factory changeDataFactory;
- private final ReviewersConfig.Factory configFactory;
- private final Provider<CurrentUser> user;
- private final ChangeQueryBuilder queryBuilder;
-
- @Inject
- ChangeEventListener(
- final AccountResolver accountResolver,
- final Provider<GroupsCollection> groupsCollection,
- final GroupMembers groupMembers,
- final DefaultReviewers.Factory reviewersFactory,
- final GitRepositoryManager repoManager,
- final WorkQueue workQueue,
- final IdentifiedUser.GenericFactory identifiedUserFactory,
- final ThreadLocalRequestContext tl,
- final SchemaFactory<ReviewDb> schemaFactory,
- final ChangeData.Factory changeDataFactory,
- final ReviewersConfig.Factory configFactory,
- final Provider<CurrentUser> user,
- final ChangeQueryBuilder queryBuilder) {
- this.accountResolver = accountResolver;
- this.groupsCollection = groupsCollection;
- this.groupMembers = groupMembers;
- this.reviewersFactory = reviewersFactory;
- this.repoManager = repoManager;
- this.workQueue = workQueue;
- this.identifiedUserFactory = identifiedUserFactory;
- this.tl = tl;
- this.schemaFactory = schemaFactory;
- this.changeDataFactory = changeDataFactory;
- this.configFactory = configFactory;
- this.user = user;
- this.queryBuilder = queryBuilder;
- }
-
- @Override
- public void onRevisionCreated(RevisionCreatedListener.Event event) {
- ChangeInfo c = event.getChange();
- onEvent(new Project.NameKey(c.project), c._number);
- }
-
- private void onEvent(Project.NameKey projectName, int changeNumber) {
- // TODO(davido): we have to cache per project configuration
- ReviewersConfig config = configFactory.create(projectName);
- List<ReviewerFilterSection> sections = config.getReviewerFilterSections();
-
- if (sections.isEmpty()) {
- return;
- }
-
- try (Repository git = repoManager.openRepository(projectName);
- ReviewDb reviewDb = schemaFactory.open()) {
- ChangeData changeData =
- changeDataFactory.create(reviewDb, projectName, new Change.Id(changeNumber));
- Set<String> reviewers = findReviewers(sections, changeData);
- if (reviewers.isEmpty()) {
- return;
- }
-
- final Change change = changeData.change();
- final Runnable task = reviewersFactory.create(change, toAccounts(reviewers, projectName));
-
- workQueue
- .getDefaultQueue()
- .submit(
- new Runnable() {
- ReviewDb db = null;
-
- @Override
- public void run() {
- RequestContext old =
- tl.setContext(
- new RequestContext() {
-
- @Override
- public CurrentUser getUser() {
- return identifiedUserFactory.create(change.getOwner());
- }
-
- @Override
- public Provider<ReviewDb> getReviewDbProvider() {
- return new Provider<ReviewDb>() {
- @Override
- public ReviewDb get() {
- if (db == null) {
- try {
- db = schemaFactory.open();
- } catch (OrmException e) {
- throw new ProvisionException("Cannot open ReviewDb", e);
- }
- }
- return db;
- }
- };
- }
- });
- try {
- task.run();
- } finally {
- tl.setContext(old);
- if (db != null) {
- db.close();
- db = null;
- }
- }
- }
- });
- } catch (OrmException | IOException | QueryParseException x) {
- log.error(x.getMessage(), x);
- }
- }
-
- private Set<String> findReviewers(List<ReviewerFilterSection> sections, ChangeData changeData)
- throws OrmException, QueryParseException {
- ImmutableSet.Builder<String> reviewers = ImmutableSet.builder();
- List<ReviewerFilterSection> found = findReviewerSections(sections, changeData);
- for (ReviewerFilterSection s : found) {
- reviewers.addAll(s.getReviewers());
- }
- return reviewers.build();
- }
-
- private List<ReviewerFilterSection> findReviewerSections(
- List<ReviewerFilterSection> sections, ChangeData changeData)
- throws OrmException, QueryParseException {
- ImmutableList.Builder<ReviewerFilterSection> found = ImmutableList.builder();
- for (ReviewerFilterSection s : sections) {
- if (Strings.isNullOrEmpty(s.getFilter()) || s.getFilter().equals("*")) {
- found.add(s);
- } else if (filterMatch(s.getFilter(), changeData)) {
- found.add(s);
- }
- }
- return found.build();
- }
-
- boolean filterMatch(String filter, ChangeData changeData)
- throws OrmException, QueryParseException {
- Preconditions.checkNotNull(filter);
- ChangeQueryBuilder qb = queryBuilder.asUser(user.get());
- Predicate<ChangeData> filterPredicate = qb.parse(filter);
- // TODO(davido): check that the potential review can see this change
- // by adding AND is_visible() predicate? Or is it OK to assume
- // that reviewers always can see it?
- return filterPredicate.asMatchable().match(changeData);
- }
-
- private Set<Account> toAccounts(Set<String> in, Project.NameKey p) {
- Set<Account> reviewers = Sets.newHashSetWithExpectedSize(in.size());
- for (String r : in) {
- try {
- Account account = accountResolver.find(r);
- if (account != null) {
- reviewers.add(account);
- continue;
- }
- } catch (OrmException | IOException | ConfigInvalidException e) {
- // If the account doesn't exist, find() will return null. We only
- // get here if something went wrong accessing the database
- log.error("Failed to resolve account " + r, e);
- continue;
- }
- try {
- reviewers.addAll(
- groupMembers.listAccounts(groupsCollection.get().parse(r).getGroupUUID(), p));
- } catch (UnprocessableEntityException e) {
- log.warn(String.format("Reviewer %s is neither an account nor a group", r));
- } catch (NoSuchProjectException e) {
- log.warn(String.format("Failed to list accounts for group %s and project %s", r, p));
- } catch (IOException e) {
- log.warn(String.format("Failed to list accounts for group %s", r), e);
- }
- }
- return reviewers;
- }
-}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/reviewers/server/DefaultReviewers.java b/src/main/java/com/googlesource/gerrit/plugins/reviewers/server/DefaultReviewers.java
deleted file mode 100644
index 8d4cb30..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/reviewers/server/DefaultReviewers.java
+++ /dev/null
@@ -1,77 +0,0 @@
-// Copyright (C) 2013 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.googlesource.gerrit.plugins.reviewers.server;
-
-import com.google.gerrit.extensions.api.GerritApi;
-import com.google.gerrit.extensions.api.changes.AddReviewerInput;
-import com.google.gerrit.extensions.api.changes.ReviewInput;
-import com.google.gerrit.extensions.restapi.RestApiException;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.inject.Inject;
-import com.google.inject.assistedinject.Assisted;
-import java.util.ArrayList;
-import java.util.Set;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-class DefaultReviewers implements Runnable {
- private static final Logger log = LoggerFactory.getLogger(DefaultReviewers.class);
-
- private final GerritApi gApi;
- private final Change change;
- private final Set<Account> reviewers;
-
- interface Factory {
- DefaultReviewers create(Change change, Set<Account> reviewers);
- }
-
- @Inject
- DefaultReviewers(GerritApi gApi, @Assisted Change change, @Assisted Set<Account> reviewers) {
- this.gApi = gApi;
- this.change = change;
- this.reviewers = reviewers;
- }
-
- @Override
- public void run() {
- addReviewers(reviewers, change);
- }
-
- /**
- * Append the reviewers to change#{@link Change}
- *
- * @param reviewers Set of reviewers to add
- * @param change {@link Change} to add the reviewers to
- */
- private void addReviewers(Set<Account> reviewers, Change change) {
- try {
- // TODO(davido): Switch back to using changes API again,
- // when it supports batch mode for adding reviewers
- ReviewInput in = new ReviewInput();
- in.reviewers = new ArrayList<>(reviewers.size());
- for (Account account : reviewers) {
- AddReviewerInput addReviewerInput = new AddReviewerInput();
- if (account.isActive()) {
- addReviewerInput.reviewer = account.getId().toString();
- in.reviewers.add(addReviewerInput);
- }
- }
- gApi.changes().id(change.getId().get()).current().review(in);
- } catch (RestApiException e) {
- log.error("Couldn't add reviewers to the change", e);
- }
- }
-}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/reviewers/server/GetReviewers.java b/src/main/java/com/googlesource/gerrit/plugins/reviewers/server/GetReviewers.java
index dccc000..d8ce0ca 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/reviewers/server/GetReviewers.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/reviewers/server/GetReviewers.java
@@ -14,9 +14,7 @@
package com.googlesource.gerrit.plugins.reviewers.server;
-import com.google.gerrit.extensions.restapi.AuthException;
-import com.google.gerrit.extensions.restapi.BadRequestException;
-import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.server.project.ProjectResource;
import com.google.inject.Inject;
@@ -25,16 +23,15 @@
@Singleton
class GetReviewers implements RestReadView<ProjectResource> {
- private final ReviewersConfig.Factory configFactory;
+ private final ReviewersConfig config;
@Inject
- GetReviewers(ReviewersConfig.Factory configFactory) {
- this.configFactory = configFactory;
+ GetReviewers(ReviewersConfig config) {
+ this.config = config;
}
@Override
- public List<ReviewerFilterSection> apply(ProjectResource resource)
- throws AuthException, BadRequestException, ResourceConflictException, Exception {
- return configFactory.create(resource.getNameKey()).getReviewerFilterSections();
+ public List<ReviewerFilterSection> apply(ProjectResource resource) throws RestApiException {
+ return config.forProject(resource.getNameKey()).getReviewerFilterSections();
}
}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/reviewers/server/ModifyReviewersConfigCapability.java b/src/main/java/com/googlesource/gerrit/plugins/reviewers/server/ModifyReviewersConfigCapability.java
new file mode 100644
index 0000000..b97e422
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/reviewers/server/ModifyReviewersConfigCapability.java
@@ -0,0 +1,26 @@
+// Copyright (C) 2018 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.googlesource.gerrit.plugins.reviewers.server;
+
+import com.google.gerrit.extensions.config.CapabilityDefinition;
+
+public class ModifyReviewersConfigCapability extends CapabilityDefinition {
+ static final String MODIFY_REVIEWERS_CONFIG = "modifyReviewersConfig";
+
+ @Override
+ public String getDescription() {
+ return "Modify Reviewers Config";
+ }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/reviewers/server/PutReviewers.java b/src/main/java/com/googlesource/gerrit/plugins/reviewers/server/PutReviewers.java
index 3d17659..1026fbb 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/reviewers/server/PutReviewers.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/reviewers/server/PutReviewers.java
@@ -14,7 +14,10 @@
package com.googlesource.gerrit.plugins.reviewers.server;
+import static com.googlesource.gerrit.plugins.reviewers.server.ModifyReviewersConfigCapability.MODIFY_REVIEWERS_CONFIG;
+
import com.google.gerrit.extensions.annotations.PluginName;
+import com.google.gerrit.extensions.api.access.PluginPermission;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
@@ -55,7 +58,7 @@
}
private final String pluginName;
- private final ReviewersConfig.Factory configFactory;
+ private final ReviewersConfig config;
private final Provider<MetaDataUpdate.User> metaDataUpdateFactory;
private final ProjectCache projectCache;
private final AccountResolver accountResolver;
@@ -65,14 +68,14 @@
@Inject
PutReviewers(
@PluginName String pluginName,
- ReviewersConfig.Factory configFactory,
+ ReviewersConfig config,
Provider<MetaDataUpdate.User> metaDataUpdateFactory,
ProjectCache projectCache,
AccountResolver accountResolver,
Provider<GroupsCollection> groupsCollection,
PermissionBackend permissionBackend) {
this.pluginName = pluginName;
- this.configFactory = configFactory;
+ this.config = config;
this.metaDataUpdateFactory = metaDataUpdateFactory;
this.projectCache = projectCache;
this.accountResolver = accountResolver;
@@ -84,18 +87,15 @@
public List<ReviewerFilterSection> apply(ProjectResource rsrc, Input input)
throws RestApiException, PermissionBackendException {
Project.NameKey projectName = rsrc.getNameKey();
- ReviewersConfig cfg = configFactory.create(projectName);
- try {
- permissionBackend
- .user(rsrc.getUser())
- .project(rsrc.getNameKey())
- .check(ProjectPermission.WRITE_CONFIG);
- } catch (AuthException e) {
+ ReviewersConfig.ForProject cfg = config.forProject(projectName);
+ if (cfg == null) {
throw new ResourceNotFoundException("Project" + projectName.get() + " not found");
}
- if (cfg == null) {
- throw new ResourceNotFoundException("Project" + projectName.get() + " not found");
+ PermissionBackend.WithUser userPermission = permissionBackend.user(rsrc.getUser());
+ if (!userPermission.project(rsrc.getNameKey()).testOrFalse(ProjectPermission.WRITE_CONFIG)
+ && !userPermission.testOrFalse(new PluginPermission(pluginName, MODIFY_REVIEWERS_CONFIG))) {
+ throw new AuthException("not allowed to modify reviewers config");
}
try (MetaDataUpdate md = metaDataUpdateFactory.get().create(projectName)) {
diff --git a/src/main/java/com/googlesource/gerrit/plugins/reviewers/server/ReviewerFilterSection.java b/src/main/java/com/googlesource/gerrit/plugins/reviewers/server/ReviewerFilterSection.java
index 0dd354e..7f0000a 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/reviewers/server/ReviewerFilterSection.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/reviewers/server/ReviewerFilterSection.java
@@ -14,6 +14,7 @@
package com.googlesource.gerrit.plugins.reviewers.server;
+import java.util.Objects;
import java.util.Set;
class ReviewerFilterSection {
@@ -32,4 +33,18 @@
Set<String> getReviewers() {
return reviewers;
}
+
+ @Override
+ public boolean equals(Object o) {
+ if (o instanceof ReviewerFilterSection) {
+ ReviewerFilterSection other = ((ReviewerFilterSection) o);
+ return Objects.equals(filter, other.filter) && Objects.equals(reviewers, other.reviewers);
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(filter, reviewers);
+ }
}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/reviewers/server/Reviewers.java b/src/main/java/com/googlesource/gerrit/plugins/reviewers/server/Reviewers.java
new file mode 100644
index 0000000..430a588
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/reviewers/server/Reviewers.java
@@ -0,0 +1,186 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.googlesource.gerrit.plugins.reviewers.server;
+
+import static java.util.stream.Collectors.toSet;
+
+import com.google.common.base.Preconditions;
+import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.gerrit.common.Nullable;
+import com.google.gerrit.extensions.common.AccountInfo;
+import com.google.gerrit.extensions.common.ChangeInfo;
+import com.google.gerrit.extensions.events.RevisionCreatedListener;
+import com.google.gerrit.extensions.events.RevisionEvent;
+import com.google.gerrit.index.query.QueryParseException;
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.change.ReviewerSuggestion;
+import com.google.gerrit.server.change.SuggestedReviewer;
+import com.google.gerrit.server.git.WorkQueue;
+import com.google.gerrit.server.query.change.ChangeQueryBuilder;
+import com.google.gerrit.server.query.change.InternalChangeQuery;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+import java.util.List;
+import java.util.Set;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@Singleton
+class Reviewers implements RevisionCreatedListener, ReviewerSuggestion {
+ private static final Logger log = LoggerFactory.getLogger(Reviewers.class);
+
+ private final ReviewersResolver resolver;
+ private final AddReviewersByConfiguration.Factory byConfigFactory;
+ private final WorkQueue workQueue;
+ private final ReviewersConfig config;
+ private final Provider<CurrentUser> user;
+ private final ChangeQueryBuilder queryBuilder;
+ private final Provider<InternalChangeQuery> queryProvider;
+
+ @Inject
+ Reviewers(
+ ReviewersResolver resolver,
+ AddReviewersByConfiguration.Factory byConfigFactory,
+ WorkQueue workQueue,
+ ReviewersConfig config,
+ Provider<CurrentUser> user,
+ ChangeQueryBuilder queryBuilder,
+ Provider<InternalChangeQuery> queryProvider) {
+ this.resolver = resolver;
+ this.byConfigFactory = byConfigFactory;
+ this.workQueue = workQueue;
+ this.config = config;
+ this.user = user;
+ this.queryBuilder = queryBuilder;
+ this.queryProvider = queryProvider;
+ }
+
+ @Override
+ public void onRevisionCreated(RevisionCreatedListener.Event event) {
+ onEvent(event);
+ }
+
+ @Override
+ public Set<SuggestedReviewer> suggestReviewers(
+ Project.NameKey projectName,
+ @Nullable Change.Id changeId,
+ @Nullable String query,
+ Set<Account.Id> candidates) {
+ List<ReviewerFilterSection> sections = getSections(projectName);
+
+ if (sections.isEmpty() || changeId == null) {
+ return ImmutableSet.of();
+ }
+
+ try {
+ Set<String> reviewers = findReviewers(changeId.get(), sections);
+ if (!reviewers.isEmpty()) {
+ return resolver
+ .resolve(reviewers, projectName, changeId.get(), null)
+ .stream()
+ .map(a -> suggestedReviewer(a))
+ .collect(toSet());
+ }
+ } catch (OrmException | QueryParseException x) {
+ log.error(x.getMessage(), x);
+ }
+ return ImmutableSet.of();
+ }
+
+ private SuggestedReviewer suggestedReviewer(Account.Id account) {
+ SuggestedReviewer reviewer = new SuggestedReviewer();
+ reviewer.account = account;
+ reviewer.score = 1;
+ return reviewer;
+ }
+
+ private List<ReviewerFilterSection> getSections(Project.NameKey projectName) {
+ // TODO(davido): we have to cache per project configuration
+ return config.forProject(projectName).getReviewerFilterSections();
+ }
+
+ private void onEvent(RevisionEvent event) {
+ ChangeInfo c = event.getChange();
+ Project.NameKey projectName = new Project.NameKey(c.project);
+
+ List<ReviewerFilterSection> sections = getSections(projectName);
+
+ if (sections.isEmpty()) {
+ return;
+ }
+
+ AccountInfo uploader = event.getWho();
+ int changeNumber = c._number;
+ try {
+ Set<String> reviewers = findReviewers(changeNumber, sections);
+ if (reviewers.isEmpty()) {
+ return;
+ }
+ final Runnable task =
+ byConfigFactory.create(
+ c, resolver.resolve(reviewers, projectName, changeNumber, uploader));
+
+ workQueue.getDefaultQueue().submit(task);
+ } catch (QueryParseException e) {
+ log.warn(
+ "Could not add default reviewers for change {} of project {}, filter is invalid: {}",
+ changeNumber,
+ projectName.get(),
+ e.getMessage());
+ } catch (OrmException x) {
+ log.error(x.getMessage(), x);
+ }
+ }
+
+ private Set<String> findReviewers(int change, List<ReviewerFilterSection> sections)
+ throws OrmException, QueryParseException {
+ ImmutableSet.Builder<String> reviewers = ImmutableSet.builder();
+ List<ReviewerFilterSection> found = findReviewerSections(change, sections);
+ for (ReviewerFilterSection s : found) {
+ reviewers.addAll(s.getReviewers());
+ }
+ return reviewers.build();
+ }
+
+ private List<ReviewerFilterSection> findReviewerSections(
+ int change, List<ReviewerFilterSection> sections) throws OrmException, QueryParseException {
+ ImmutableList.Builder<ReviewerFilterSection> found = ImmutableList.builder();
+ for (ReviewerFilterSection s : sections) {
+ if (Strings.isNullOrEmpty(s.getFilter()) || s.getFilter().equals("*")) {
+ found.add(s);
+ } else if (filterMatch(change, s.getFilter())) {
+ found.add(s);
+ }
+ }
+ return found.build();
+ }
+
+ boolean filterMatch(int change, String filter) throws OrmException, QueryParseException {
+ Preconditions.checkNotNull(filter);
+ ChangeQueryBuilder qb = queryBuilder.asUser(user.get());
+ return !queryProvider
+ .get()
+ .noFields()
+ .query(qb.parse(String.format("change:%s %s", change, filter)))
+ .isEmpty();
+ }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/reviewers/server/ReviewersConfig.java b/src/main/java/com/googlesource/gerrit/plugins/reviewers/server/ReviewersConfig.java
index b7daadf..a70fd6f 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/reviewers/server/ReviewersConfig.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/reviewers/server/ReviewersConfig.java
@@ -24,7 +24,7 @@
import com.google.gerrit.server.git.meta.VersionedMetaData;
import com.google.gerrit.server.project.NoSuchProjectException;
import com.google.inject.Inject;
-import com.google.inject.assistedinject.Assisted;
+import com.google.inject.Singleton;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
@@ -32,80 +32,122 @@
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.lib.CommitBuilder;
import org.eclipse.jgit.lib.Config;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
-class ReviewersConfig extends VersionedMetaData {
- private static final String FILENAME = "reviewers.config";
- private static final String FILTER = "filter";
- private static final String REVIEWER = "reviewer";
- private Config cfg;
+@Singleton
+public class ReviewersConfig {
+ private static final Logger log = LoggerFactory.getLogger(ReviewersConfig.class);
- interface Factory {
- ReviewersConfig create(Project.NameKey projectName);
- }
+ static final String FILENAME = "reviewers.config";
+ static final String SECTION_FILTER = "filter";
+ static final String KEY_REVIEWER = "reviewer";
+ private static final String KEY_IGNORE_DRAFTS = "ignoreDrafts";
+ private static final String KEY_ENABLE_REST = "enableREST";
+ private static final String KEY_SUGGEST_ONLY = "suggestOnly";
+
+ private final PluginConfigFactory cfgFactory;
+ private final String pluginName;
+
+ private final boolean enableREST;
+ private final boolean suggestOnly;
+ private final boolean ignoreDrafts;
@Inject
- ReviewersConfig(
- PluginConfigFactory cfgFactory,
- @PluginName String pluginName,
- @Assisted Project.NameKey projectName)
- throws NoSuchProjectException {
- cfg = cfgFactory.getProjectPluginConfigWithInheritance(projectName, pluginName);
+ ReviewersConfig(PluginConfigFactory cfgFactory, @PluginName String pluginName) {
+ this.cfgFactory = cfgFactory;
+ this.pluginName = pluginName;
+ Config cfg = cfgFactory.getGlobalPluginConfig(pluginName);
+ this.ignoreDrafts = cfg.getBoolean(pluginName, null, KEY_IGNORE_DRAFTS, false);
+ this.enableREST = cfg.getBoolean(pluginName, null, KEY_ENABLE_REST, true);
+ this.suggestOnly = cfg.getBoolean(pluginName, null, KEY_SUGGEST_ONLY, false);
}
- List<ReviewerFilterSection> getReviewerFilterSections() {
- ImmutableList.Builder<ReviewerFilterSection> b = ImmutableList.builder();
- for (String f : cfg.getSubsections(FILTER)) {
- b.add(newReviewerFilterSection(f));
+ public ForProject forProject(Project.NameKey projectName) {
+ Config cfg;
+ try {
+ cfg = cfgFactory.getProjectPluginConfigWithMergedInheritance(projectName, pluginName);
+ } catch (NoSuchProjectException e) {
+ log.error("Unable to get config for project {}", projectName.get());
+ cfg = new Config();
}
- return b.build();
+ return new ForProject(cfg);
}
- void addReviewer(String filter, String reviewer) {
- if (!newReviewerFilterSection(filter).getReviewers().contains(reviewer)) {
- List<String> values =
- new ArrayList<>(Arrays.asList(cfg.getStringList(FILTER, filter, REVIEWER)));
- values.add(reviewer);
- cfg.setStringList(FILTER, filter, REVIEWER, values);
+ public boolean ignoreDrafts() {
+ return ignoreDrafts;
+ }
+
+ public boolean enableREST() {
+ return enableREST;
+ }
+
+ public boolean suggestOnly() {
+ return suggestOnly;
+ }
+
+ static class ForProject extends VersionedMetaData {
+ private Config cfg;
+
+ ForProject(Config cfg) {
+ this.cfg = cfg;
}
- }
- void removeReviewer(String filter, String reviewer) {
- if (newReviewerFilterSection(filter).getReviewers().contains(reviewer)) {
- List<String> values =
- new ArrayList<>(Arrays.asList(cfg.getStringList(FILTER, filter, REVIEWER)));
- values.remove(reviewer);
- if (values.isEmpty()) {
- cfg.unsetSection(FILTER, filter);
- } else {
- cfg.setStringList(FILTER, filter, REVIEWER, values);
+ List<ReviewerFilterSection> getReviewerFilterSections() {
+ ImmutableList.Builder<ReviewerFilterSection> b = ImmutableList.builder();
+ for (String f : cfg.getSubsections(SECTION_FILTER)) {
+ b.add(newReviewerFilterSection(f));
+ }
+ return b.build();
+ }
+
+ void addReviewer(String filter, String reviewer) {
+ if (!newReviewerFilterSection(filter).getReviewers().contains(reviewer)) {
+ List<String> values =
+ new ArrayList<>(Arrays.asList(cfg.getStringList(SECTION_FILTER, filter, KEY_REVIEWER)));
+ values.add(reviewer);
+ cfg.setStringList(SECTION_FILTER, filter, KEY_REVIEWER, values);
}
}
- }
- private ReviewerFilterSection newReviewerFilterSection(String filter) {
- ImmutableSet.Builder<String> b = ImmutableSet.builder();
- for (String reviewer : cfg.getStringList(FILTER, filter, REVIEWER)) {
- b.add(reviewer);
+ void removeReviewer(String filter, String reviewer) {
+ if (newReviewerFilterSection(filter).getReviewers().contains(reviewer)) {
+ List<String> values =
+ new ArrayList<>(Arrays.asList(cfg.getStringList(SECTION_FILTER, filter, KEY_REVIEWER)));
+ values.remove(reviewer);
+ if (values.isEmpty()) {
+ cfg.unsetSection(SECTION_FILTER, filter);
+ } else {
+ cfg.setStringList(SECTION_FILTER, filter, KEY_REVIEWER, values);
+ }
+ }
}
- return new ReviewerFilterSection(filter, b.build());
- }
- @Override
- protected String getRefName() {
- return RefNames.REFS_CONFIG;
- }
-
- @Override
- protected void onLoad() throws IOException, ConfigInvalidException {
- cfg = readConfig(FILENAME);
- }
-
- @Override
- protected boolean onSave(CommitBuilder commit) throws IOException, ConfigInvalidException {
- if (Strings.isNullOrEmpty(commit.getMessage())) {
- commit.setMessage("Update reviewers configuration\n");
+ private ReviewerFilterSection newReviewerFilterSection(String filter) {
+ ImmutableSet.Builder<String> b = ImmutableSet.builder();
+ for (String reviewer : cfg.getStringList(SECTION_FILTER, filter, KEY_REVIEWER)) {
+ b.add(reviewer);
+ }
+ return new ReviewerFilterSection(filter, b.build());
}
- saveConfig(FILENAME, cfg);
- return true;
+
+ @Override
+ protected String getRefName() {
+ return RefNames.REFS_CONFIG;
+ }
+
+ @Override
+ protected void onLoad() throws IOException, ConfigInvalidException {
+ cfg = readConfig(FILENAME);
+ }
+
+ @Override
+ protected boolean onSave(CommitBuilder commit) throws IOException, ConfigInvalidException {
+ if (Strings.isNullOrEmpty(commit.getMessage())) {
+ commit.setMessage("Update reviewers configuration\n");
+ }
+ saveConfig(FILENAME, cfg);
+ return true;
+ }
}
}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/reviewers/server/ReviewersResolver.java b/src/main/java/com/googlesource/gerrit/plugins/reviewers/server/ReviewersResolver.java
new file mode 100644
index 0000000..82c3139
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/reviewers/server/ReviewersResolver.java
@@ -0,0 +1,155 @@
+// Copyright (C) 2018 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.googlesource.gerrit.plugins.reviewers.server;
+
+import static java.util.stream.Collectors.toSet;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.Sets;
+import com.google.gerrit.common.Nullable;
+import com.google.gerrit.extensions.common.AccountInfo;
+import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.account.AccountResolver;
+import com.google.gerrit.server.account.GroupMembers;
+import com.google.gerrit.server.project.NoSuchProjectException;
+import com.google.gerrit.server.restapi.group.GroupsCollection;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+import java.io.IOException;
+import java.util.Set;
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/* Resolve account and group names to account ids */
+@Singleton
+class ReviewersResolver {
+ private static final Logger log = LoggerFactory.getLogger(ReviewersResolver.class);
+
+ private final AccountResolver accountResolver;
+ private final Provider<GroupsCollection> groupsCollection;
+ private final GroupMembers groupMembers;
+ private final IdentifiedUser.GenericFactory identifiedUserFactory;
+
+ @Inject
+ ReviewersResolver(
+ AccountResolver accountResolver,
+ Provider<GroupsCollection> groupsCollection,
+ GroupMembers groupMembers,
+ IdentifiedUser.GenericFactory identifiedUserFactory) {
+ this.accountResolver = accountResolver;
+ this.groupsCollection = groupsCollection;
+ this.groupMembers = groupMembers;
+ this.identifiedUserFactory = identifiedUserFactory;
+ }
+
+ /**
+ * Resolve a set of account names to {@link com.google.gerrit.reviewdb.client.Account.Id}s. Group
+ * names are resolved to their account members.
+ *
+ * @param names the set of account names to convert
+ * @param project the project name
+ * @param changeNumber the change Id
+ * @param uploader account to use to look up groups, or null if groups are not needed
+ * @return set of {@link com.google.gerrit.reviewdb.client.Account.Id}s.
+ */
+ @VisibleForTesting
+ Set<Account.Id> resolve(
+ Set<String> names,
+ Project.NameKey project,
+ int changeNumber,
+ @Nullable AccountInfo uploader) {
+ Set<Account.Id> reviewers = Sets.newHashSetWithExpectedSize(names.size());
+ for (String name : names) {
+ if (resolveAccount(project, changeNumber, uploader, reviewers, name)) {
+ continue;
+ }
+
+ resolveGroup(project, changeNumber, reviewers, groupMembers, name);
+ }
+ return reviewers;
+ }
+
+ private boolean resolveAccount(
+ Project.NameKey project,
+ int changeNumber,
+ AccountInfo uploader,
+ Set<Account.Id> reviewers,
+ String accountName) {
+ try {
+ Account account = accountResolver.find(accountName);
+ if (account != null) {
+ if (account.isActive()) {
+ if (uploader == null || uploader._accountId != account.getId().get()) {
+ reviewers.add(account.getId());
+ }
+ return true;
+ }
+ log.warn(
+ "For the change {} of project {}: account {} is inactive.",
+ changeNumber,
+ project,
+ accountName);
+ }
+ } catch (OrmException | IOException | ConfigInvalidException e) {
+ // If the account doesn't exist, find() will return null. We only
+ // get here if something went wrong accessing the database
+ log.error(
+ "For the change {} of project {}: failed to resolve account {}.",
+ changeNumber,
+ project,
+ accountName,
+ e);
+ return true;
+ }
+ return false;
+ }
+
+ private void resolveGroup(
+ Project.NameKey project,
+ int changeNumber,
+ Set<Account.Id> reviewers,
+ GroupMembers groupMembers,
+ String group) {
+ try {
+ Set<Account.Id> accounts =
+ groupMembers
+ .listAccounts(groupsCollection.get().parse(group).getGroupUUID(), project)
+ .stream()
+ .filter(Account::isActive)
+ .map(Account::getId)
+ .collect(toSet());
+ reviewers.addAll(accounts);
+ } catch (UnprocessableEntityException e) {
+ log.warn(
+ "For the change {} of project {}: reviewer {} is neither an account nor a group.",
+ changeNumber,
+ project,
+ group);
+ } catch (NoSuchProjectException | IOException e) {
+ log.error(
+ "For the change {} of project {}: failed to list accounts for group {}.",
+ changeNumber,
+ project,
+ group,
+ e);
+ }
+ }
+}
diff --git a/src/main/resources/Documentation/build.md b/src/main/resources/Documentation/build.md
index b2f5da6..1aac8d5 100644
--- a/src/main/resources/Documentation/build.md
+++ b/src/main/resources/Documentation/build.md
@@ -19,6 +19,12 @@
bazel-genfiles/@PLUGIN@.jar
```
+To execute the tests run:
+
+```
+ bazel test //...
+```
+
This project can be imported into the Eclipse IDE:
```
@@ -37,6 +43,12 @@
bazel-genfiles/plugins/@PLUGIN@/@PLUGIN@.jar
```
+To execute the tests run:
+
+```
+ bazel test --test_tag_filters=@PLUGIN@
+```
+
This project can be imported into the Eclipse IDE.
Add the plugin name to the `CUSTOM_PLUGINS` set in
Gerrit core in `tools/bzl/plugins.bzl`, and execute:
@@ -52,3 +64,6 @@
UI plugin is only compatible with the GWT UI, and does not work with PolyGerrit.
Both build instructions will work with either `reviewers` or
`reviewers-backend`.
+
+How to build the Gerrit Plugin API is described in the [Gerrit
+documentation](../../../Documentation/dev-bazel.html#_extension_and_plugin_api_jar_files).
diff --git a/src/main/resources/Documentation/config.md b/src/main/resources/Documentation/config.md
index e2112fb..2c3627a 100644
--- a/src/main/resources/Documentation/config.md
+++ b/src/main/resources/Documentation/config.md
@@ -7,17 +7,20 @@
```
[reviewers]
enableREST = true
- enableUI = false
+ suggestOnly = false
```
reviewers.enableREST
: Enable the REST API. When set to false, the REST API is not available.
Defaults to true.
-reviewers.enableUI
-: Enable the UI. When set to false, the 'Reviewers' menu is not displayed
- on the project screen. Defaults to true, or false when `enableREST` is false.
-
+reviewers.suggestOnly
+: Provide the configured reviewers as suggestions in the "Add Reviewer" dialog
+ instead of automatically adding them to the change. Only supports accounts;
+ groups are not suggested. Defaults to false. By default Gerrit will consider
+ the suggestions with a weight of 1. To force the suggestions higher in the
+ list, set a higher value (like 1000) in `addReviewer.@PLUGIN@-reviewer-suggestion.weight`
+ in `gerrit.config`.
Per project configuration of the @PLUGIN@ plugin is done in the
`reviewers.config` file of the project. Missing values are inherited
from the parent projects. This means a global default configuration can
@@ -58,4 +61,4 @@
```
1. Push a change for review involving file "build/modules/GLOBAL.pm".
-2. Both john.doe@example.com and jane.doe@example.com get added as reviewers.
+2. Both john.doe@example.com and jane.doe@example.com get added or suggested as reviewers.
diff --git a/src/main/resources/Documentation/rest-api.md b/src/main/resources/Documentation/rest-api.md
index 6fe3733..ebd04d4 100644
--- a/src/main/resources/Documentation/rest-api.md
+++ b/src/main/resources/Documentation/rest-api.md
@@ -56,6 +56,9 @@
The change to reviewers must be provided in the request body inside
a [ConfigReviewersInput](#config-reviewers-input) entity.
+Caller must be a member of a group that is granted the 'Modify Reviewers Config'
+capability (provided by this plugin) or be a Project Owner for the project.
+
#### Request
```
diff --git a/src/test/java/com/googlesource/gerrit/plugins/reviewers/server/ReviewersConfigIT.java b/src/test/java/com/googlesource/gerrit/plugins/reviewers/server/ReviewersConfigIT.java
new file mode 100644
index 0000000..32b2243
--- /dev/null
+++ b/src/test/java/com/googlesource/gerrit/plugins/reviewers/server/ReviewersConfigIT.java
@@ -0,0 +1,116 @@
+// Copyright (C) 2018 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.googlesource.gerrit.plugins.reviewers.server;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.acceptance.GitUtil.fetch;
+import static com.googlesource.gerrit.plugins.reviewers.server.ReviewersConfig.FILENAME;
+import static com.googlesource.gerrit.plugins.reviewers.server.ReviewersConfig.KEY_REVIEWER;
+import static com.googlesource.gerrit.plugins.reviewers.server.ReviewersConfig.SECTION_FILTER;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.gerrit.acceptance.LightweightPluginDaemonTest;
+import com.google.gerrit.acceptance.NoHttpd;
+import com.google.gerrit.acceptance.TestPlugin;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.reviewdb.client.RefNames;
+import org.eclipse.jgit.junit.TestRepository;
+import org.eclipse.jgit.lib.Config;
+import org.junit.Before;
+import org.junit.Test;
+
+@NoHttpd
+@TestPlugin(name = "reviewers", sysModule = "com.googlesource.gerrit.plugins.reviewers.Module")
+public class ReviewersConfigIT extends LightweightPluginDaemonTest {
+ private static final String BRANCH_MAIN = "branch:main";
+ private static final String NO_FILTER = "*";
+ private static final String JANE_DOE = "jane.doe@example.com";
+ private static final String JOHN_DOE = "john.doe@example.com";
+
+ @Before
+ public void setUp() throws Exception {
+ fetch(testRepo, RefNames.REFS_CONFIG + ":refs/heads/config");
+ testRepo.reset("refs/heads/config");
+ }
+
+ @Test
+ public void reviewersConfigSingle() throws Exception {
+ Config cfg = new Config();
+ cfg.setString(SECTION_FILTER, NO_FILTER, KEY_REVIEWER, JOHN_DOE);
+ cfg.setString(SECTION_FILTER, BRANCH_MAIN, KEY_REVIEWER, JANE_DOE);
+
+ pushFactory
+ .create(db, admin.getIdent(), testRepo, "Add reviewers", FILENAME, cfg.toText())
+ .to(RefNames.REFS_CONFIG)
+ .assertOkStatus();
+
+ assertThat(reviewersConfig().forProject(project).getReviewerFilterSections())
+ .containsExactlyElementsIn(
+ ImmutableList.of(
+ new ReviewerFilterSection(NO_FILTER, ImmutableSet.of(JOHN_DOE)),
+ new ReviewerFilterSection(BRANCH_MAIN, ImmutableSet.of(JANE_DOE))))
+ .inOrder();
+ }
+
+ @Test
+ public void reviewersConfigWithMergedInheritance() throws Exception {
+ Config parentCfg = new Config();
+ parentCfg.setString(SECTION_FILTER, NO_FILTER, KEY_REVIEWER, JOHN_DOE);
+ parentCfg.setString(SECTION_FILTER, BRANCH_MAIN, KEY_REVIEWER, JOHN_DOE);
+
+ pushFactory
+ .create(
+ db,
+ admin.getIdent(),
+ testRepo,
+ "Add reviewers parent project",
+ FILENAME,
+ parentCfg.toText())
+ .to(RefNames.REFS_CONFIG)
+ .assertOkStatus();
+
+ Project.NameKey childProject = createProject("child", project);
+ TestRepository<?> childTestRepo = cloneProject(childProject);
+ fetch(childTestRepo, RefNames.REFS_CONFIG + ":refs/heads/config");
+ childTestRepo.reset("refs/heads/config");
+
+ Config cfg = new Config();
+ cfg.setString(SECTION_FILTER, NO_FILTER, KEY_REVIEWER, JANE_DOE);
+ cfg.setString(SECTION_FILTER, BRANCH_MAIN, KEY_REVIEWER, JANE_DOE);
+
+ pushFactory
+ .create(
+ db,
+ admin.getIdent(),
+ childTestRepo,
+ "Add reviewers child project",
+ FILENAME,
+ cfg.toText())
+ .to(RefNames.REFS_CONFIG)
+ .assertOkStatus();
+
+ assertThat(reviewersConfig().forProject(childProject).getReviewerFilterSections())
+ .containsExactlyElementsIn(
+ ImmutableList.of(
+ new ReviewerFilterSection(NO_FILTER, ImmutableSet.of(JOHN_DOE, JANE_DOE)),
+ new ReviewerFilterSection(BRANCH_MAIN, ImmutableSet.of(JOHN_DOE, JANE_DOE))))
+ .inOrder();
+ }
+
+ private ReviewersConfig reviewersConfig() {
+ return plugin.getSysInjector().getInstance(ReviewersConfig.class);
+ }
+}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/reviewers/server/ReviewersIT.java b/src/test/java/com/googlesource/gerrit/plugins/reviewers/server/ReviewersIT.java
new file mode 100644
index 0000000..c445e81
--- /dev/null
+++ b/src/test/java/com/googlesource/gerrit/plugins/reviewers/server/ReviewersIT.java
@@ -0,0 +1,151 @@
+// Copyright (C) 2018 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.googlesource.gerrit.plugins.reviewers.server;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assert_;
+import static com.google.gerrit.acceptance.GitUtil.fetch;
+import static com.google.gerrit.extensions.client.ReviewerState.REVIEWER;
+import static com.googlesource.gerrit.plugins.reviewers.server.ReviewersConfig.FILENAME;
+import static com.googlesource.gerrit.plugins.reviewers.server.ReviewersConfig.KEY_REVIEWER;
+import static com.googlesource.gerrit.plugins.reviewers.server.ReviewersConfig.SECTION_FILTER;
+import static java.util.stream.Collectors.toSet;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.gerrit.acceptance.LightweightPluginDaemonTest;
+import com.google.gerrit.acceptance.NoHttpd;
+import com.google.gerrit.acceptance.TestAccount;
+import com.google.gerrit.acceptance.TestPlugin;
+import com.google.gerrit.extensions.common.AccountInfo;
+import com.google.gerrit.reviewdb.client.Branch;
+import com.google.gerrit.reviewdb.client.RefNames;
+import java.util.Collection;
+import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.junit.Before;
+import org.junit.Test;
+
+@NoHttpd
+@TestPlugin(name = "reviewers", sysModule = "com.googlesource.gerrit.plugins.reviewers.Module")
+public class ReviewersIT extends LightweightPluginDaemonTest {
+ @Before
+ public void setUp() throws Exception {
+ fetch(testRepo, RefNames.REFS_CONFIG + ":refs/heads/config");
+ testRepo.reset("refs/heads/config");
+ }
+
+ @Test
+ public void addReviewers() throws Exception {
+ RevCommit oldHead = getRemoteHead();
+ TestAccount user2 = accountCreator.user2();
+
+ Config cfg = new Config();
+ cfg.setStringList(SECTION_FILTER, "*", KEY_REVIEWER, ImmutableList.of(user.email, user2.email));
+
+ pushFactory
+ .create(db, admin.getIdent(), testRepo, "Add reviewers", FILENAME, cfg.toText())
+ .to(RefNames.REFS_CONFIG)
+ .assertOkStatus();
+
+ testRepo.reset(oldHead);
+ String changeId = createChange().getChangeId();
+
+ Collection<AccountInfo> reviewers;
+ // Wait for 100 ms until the create patch set event
+ // is processed by the reviewers plugin
+ long wait = 0;
+ do {
+ reviewers = gApi.changes().id(changeId).get().reviewers.get(REVIEWER);
+ if (reviewers == null) {
+ Thread.sleep(10);
+ wait += 10;
+ if (wait > 100) {
+ assert_().fail("Timeout of 100 ms exceeded");
+ }
+ }
+ } while (reviewers == null);
+
+ assertThat(reviewers.stream().map(a -> a._accountId).collect(toSet()))
+ .containsExactlyElementsIn(ImmutableSet.of(admin.id.get(), user.id.get(), user2.id.get()));
+ }
+
+ @Test
+ public void doNotAddReviewersFromNonMatchingFilters() throws Exception {
+ RevCommit oldHead = getRemoteHead();
+
+ Config cfg = new Config();
+ cfg.setString(SECTION_FILTER, "branch:master", KEY_REVIEWER, user.email);
+
+ pushFactory
+ .create(db, admin.getIdent(), testRepo, "Add reviewers", FILENAME, cfg.toText())
+ .to(RefNames.REFS_CONFIG)
+ .assertOkStatus();
+
+ testRepo.reset(oldHead);
+
+ createBranch(new Branch.NameKey(project, "other-branch"));
+
+ // Create a change that matches the filter section.
+ createChange("refs/for/master");
+
+ // The actual change we want to test
+ String changeId = createChange("refs/for/other-branch").getChangeId();
+
+ Collection<AccountInfo> reviewers;
+ long wait = 0;
+ do {
+ Thread.sleep(10);
+ wait += 10;
+ reviewers = gApi.changes().id(changeId).get().reviewers.get(REVIEWER);
+ } while (reviewers == null && wait < 100);
+
+ assertThat(reviewers).isNull();
+ }
+
+ @Test
+ public void addReviewersFromMatchingFilters() throws Exception {
+ RevCommit oldHead = getRemoteHead();
+
+ Config cfg = new Config();
+ cfg.setString(SECTION_FILTER, "branch:other-branch", KEY_REVIEWER, user.email);
+
+ pushFactory
+ .create(db, admin.getIdent(), testRepo, "Add reviewers", FILENAME, cfg.toText())
+ .to(RefNames.REFS_CONFIG)
+ .assertOkStatus();
+
+ testRepo.reset(oldHead);
+
+ createBranch(new Branch.NameKey(project, "other-branch"));
+
+ // Create a change that doesn't match the filter section.
+ createChange("refs/for/master");
+
+ // The actual change we want to test
+ String changeId = createChange("refs/for/other-branch").getChangeId();
+
+ Collection<AccountInfo> reviewers;
+ long wait = 0;
+ do {
+ Thread.sleep(10);
+ wait += 10;
+ reviewers = gApi.changes().id(changeId).get().reviewers.get(REVIEWER);
+ } while (reviewers == null && wait < 100);
+
+ assertThat(reviewers.stream().map(a -> a._accountId).collect(toSet()))
+ .containsExactlyElementsIn(ImmutableSet.of(admin.id.get(), user.id.get()));
+ }
+}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/reviewers/server/ReviewersResolverIT.java b/src/test/java/com/googlesource/gerrit/plugins/reviewers/server/ReviewersResolverIT.java
new file mode 100644
index 0000000..c893a6c
--- /dev/null
+++ b/src/test/java/com/googlesource/gerrit/plugins/reviewers/server/ReviewersResolverIT.java
@@ -0,0 +1,88 @@
+// Copyright (C) 2018 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.googlesource.gerrit.plugins.reviewers.server;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.NoHttpd;
+import com.google.gerrit.acceptance.TestAccount;
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.inject.Inject;
+import java.util.Collections;
+import java.util.Set;
+import org.junit.Before;
+import org.junit.Test;
+
+@NoHttpd
+public class ReviewersResolverIT extends AbstractDaemonTest {
+
+ @Inject private ReviewersResolver resolver;
+ private int change;
+
+ @Before
+ public void setUp() {
+ change = 1;
+ }
+
+ @Test
+ public void testUploaderSkippedAsReviewer() throws Exception {
+ Set<Account.Id> reviewers =
+ resolver.resolve(
+ Collections.singleton(user.email),
+ project,
+ change,
+ gApi.accounts().id(user.id.get()).get());
+ assertThat(reviewers).isEmpty();
+ }
+
+ @Test
+ public void testAccountResolve() throws Exception {
+ Set<Account.Id> reviewers =
+ resolver.resolve(
+ ImmutableSet.of(user.email, admin.email),
+ project,
+ change,
+ gApi.accounts().id(admin.id.get()).get());
+ assertThat(reviewers).containsExactly(user.id);
+ }
+
+ @Test
+ public void testAccountGroupResolve() throws Exception {
+ String group1 = createGroup("group1");
+ TestAccount foo = createTestAccount("foo", group1);
+ TestAccount bar = createTestAccount("bar", group1);
+
+ String group2 = createGroup("group2");
+ TestAccount baz = createTestAccount("baz", group2);
+ TestAccount qux = createTestAccount("qux", group2);
+
+ TestAccount system = createTestAccount("system", "Administrators");
+
+ Set<Account.Id> reviewers =
+ resolver.resolve(
+ ImmutableSet.of(system.email, group1, group2),
+ project,
+ change,
+ gApi.accounts().id(admin.id.get()).get());
+ assertThat(reviewers).containsExactly(system.id, foo.id, bar.id, baz.id, qux.id);
+ }
+
+ private TestAccount createTestAccount(String name, String group) throws Exception {
+ name = name(name);
+ return accountCreator.create(name, name + "@example.com", name + " full name", group);
+ }
+}
diff --git a/tools/bzl/junit.bzl b/tools/bzl/junit.bzl
new file mode 100644
index 0000000..3af7e58
--- /dev/null
+++ b/tools/bzl/junit.bzl
@@ -0,0 +1,4 @@
+load(
+ "@com_googlesource_gerrit_bazlets//tools:junit.bzl",
+ "junit_tests",
+)
diff --git a/tools/bzl/plugin.bzl b/tools/bzl/plugin.bzl
index 74d4ce1..2ee6681 100644
--- a/tools/bzl/plugin.bzl
+++ b/tools/bzl/plugin.bzl
@@ -2,5 +2,6 @@
"@com_googlesource_gerrit_bazlets//:gerrit_plugin.bzl",
"gerrit_plugin",
"PLUGIN_DEPS",
+ "PLUGIN_TEST_DEPS",
"GWT_PLUGIN_DEPS",
)
diff --git a/tools/eclipse/BUILD b/tools/eclipse/BUILD
index 25b845e..a06bb85 100644
--- a/tools/eclipse/BUILD
+++ b/tools/eclipse/BUILD
@@ -1,13 +1,15 @@
load(
"//tools/bzl:plugin.bzl",
- "PLUGIN_DEPS",
"GWT_PLUGIN_DEPS",
)
load("//tools/bzl:classpath.bzl", "classpath_collector")
classpath_collector(
name = "main_classpath_collect",
- deps = PLUGIN_DEPS + GWT_PLUGIN_DEPS + [
- "//:reviewers__plugin",
+ testonly = 1,
+ deps = GWT_PLUGIN_DEPS + [
+ "//external:gwt-dev",
+ "//external:gwt-user",
+ "//:reviewers__plugin_test_deps",
],
)