Merge branch 'stable-3.4'
* stable-3.4:
Rename Autoassign to AutoAssign camel-case naming convention
Add new documentation for the owners-autoassign plugin
Allow async assignment of reviewers
Change-Id: Iff9b121a6483bc2c5744953371846af281907903
diff --git a/owners-autoassign/src/main/java/com/googlesource/gerrit/owners/common/AsyncReviewerManager.java b/owners-autoassign/src/main/java/com/googlesource/gerrit/owners/common/AsyncReviewerManager.java
new file mode 100644
index 0000000..6ea35e4
--- /dev/null
+++ b/owners-autoassign/src/main/java/com/googlesource/gerrit/owners/common/AsyncReviewerManager.java
@@ -0,0 +1,111 @@
+// Copyright (C) 2021 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.owners.common;
+
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.Account.Id;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.entities.Project.NameKey;
+import com.google.gerrit.extensions.api.changes.ChangeApi;
+import com.google.gerrit.extensions.restapi.RestApiException;
+import com.google.gerrit.server.git.WorkQueue;
+import com.google.gerrit.server.util.ManualRequestContext;
+import com.google.gerrit.server.util.OneOffRequestContext;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import java.util.Collection;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@Singleton
+class AsyncReviewerManager implements ReviewerManager {
+
+ private static final Logger log = LoggerFactory.getLogger(AsyncReviewerManager.class);
+
+ private final SyncReviewerManager syncReviewerManager;
+ private final AutoAssignConfig config;
+ private final ScheduledExecutorService executor;
+ private final OneOffRequestContext requestContext;
+
+ class AddReviewersTask implements Runnable {
+ private final NameKey projectName;
+ private final ChangeApi cApi;
+ private final Collection<Id> reviewers;
+ private final int changeNum;
+ private int retryNum;
+
+ public AddReviewersTask(
+ Project.NameKey projectName, ChangeApi cApi, Collection<Account.Id> reviewers)
+ throws RestApiException {
+ this.projectName = projectName;
+ this.cApi = cApi;
+ this.changeNum = cApi.get()._number;
+ this.reviewers = reviewers;
+ }
+
+ @Override
+ public String toString() {
+ return "auto-assign reviewers to change "
+ + changeNum
+ + (retryNum > 0 ? "(retry #" + retryNum + ")" : "");
+ }
+
+ @Override
+ public void run() {
+ try (ManualRequestContext ctx = requestContext.open()) {
+ syncReviewerManager.addReviewers(projectName, cApi, reviewers);
+ } catch (Exception e) {
+ retryNum++;
+
+ if (retryNum > config.retryCount()) {
+ log.error("{} *FAILED*", this, e);
+ } else {
+ long retryInterval = config.retryInterval();
+ log.warn("{} *FAILED*: retrying after {} msec", this, retryInterval, e);
+ executor.schedule(this, retryInterval, TimeUnit.MILLISECONDS);
+ }
+ }
+ }
+ }
+
+ @Inject
+ public AsyncReviewerManager(
+ AutoAssignConfig config,
+ WorkQueue executorFactory,
+ SyncReviewerManager syncReviewerManager,
+ OneOffRequestContext requestContext) {
+ this.config = config;
+ this.syncReviewerManager = syncReviewerManager;
+ this.executor = executorFactory.createQueue(config.asyncThreads(), "AsyncReviewerManager");
+ this.requestContext = requestContext;
+ }
+
+ @Override
+ public void addReviewers(
+ Project.NameKey projectName, ChangeApi cApi, Collection<Account.Id> reviewers)
+ throws ReviewerManagerException {
+ try {
+ executor.schedule(
+ new AddReviewersTask(projectName, cApi, reviewers),
+ config.asyncDelay(),
+ TimeUnit.MILLISECONDS);
+ } catch (RestApiException e) {
+ throw new ReviewerManagerException(e);
+ }
+ }
+}
diff --git a/owners-autoassign/src/main/java/com/googlesource/gerrit/owners/common/AutoAssignConfig.java b/owners-autoassign/src/main/java/com/googlesource/gerrit/owners/common/AutoAssignConfig.java
new file mode 100644
index 0000000..c381f19
--- /dev/null
+++ b/owners-autoassign/src/main/java/com/googlesource/gerrit/owners/common/AutoAssignConfig.java
@@ -0,0 +1,142 @@
+// Copyright (C) 2021 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.owners.common;
+
+import static com.google.gerrit.extensions.client.InheritableBoolean.TRUE;
+import static com.googlesource.gerrit.owners.common.AutoassignConfigModule.PROJECT_CONFIG_AUTOASSIGN_FIELD;
+import static com.googlesource.gerrit.owners.common.AutoassignConfigModule.PROJECT_CONFIG_AUTOASSIGN_WIP_CHANGES;
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.extensions.annotations.PluginName;
+import com.google.gerrit.extensions.client.ReviewerState;
+import com.google.gerrit.server.config.ConfigUtil;
+import com.google.gerrit.server.config.PluginConfig;
+import com.google.gerrit.server.config.PluginConfigFactory;
+import com.google.gerrit.server.project.NoSuchProjectException;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import org.eclipse.jgit.lib.Config;
+
+@Singleton
+public class AutoAssignConfig {
+ public static final String REVIEWERS_SECTION = "reviewers";
+ public static final String ASYNC = "async";
+ public static final boolean ASYNC_DEF = false;
+ public static final String DELAY = "delay";
+ public static final long DELAY_MSEC_DEF = 1500L;
+ public static final String RETRY_COUNT = "retryCount";
+ public static final int RETRY_COUNT_DEF = 2;
+ public static final String RETRY_INTERVAL = "retryInterval";
+ public static final long RETRY_INTERVAL_MSEC_DEF = DELAY_MSEC_DEF;
+ public static final String THREADS = "threads";
+ public static final int THREADS_DEF = 1;
+
+ private final boolean asyncReviewers;
+ private final int asyncThreads;
+ private final int retryCount;
+ private final long retryInterval;
+ private final long asyncDelay;
+
+ private final PluginConfigFactory cfgFactory;
+ private final String pluginName;
+
+ private final PluginSettings settings;
+
+ @Inject
+ AutoAssignConfig(
+ PluginSettings settings, PluginConfigFactory configFactory, @PluginName String pluginName) {
+ this.settings = settings;
+ this.pluginName = pluginName;
+ this.cfgFactory = configFactory;
+ Config config = configFactory.getGlobalPluginConfig(pluginName);
+ asyncReviewers = config.getBoolean(REVIEWERS_SECTION, ASYNC, ASYNC_DEF);
+ asyncThreads = config.getInt(REVIEWERS_SECTION, THREADS, THREADS_DEF);
+ asyncDelay =
+ ConfigUtil.getTimeUnit(
+ config, REVIEWERS_SECTION, null, DELAY, DELAY_MSEC_DEF, MILLISECONDS);
+
+ retryCount = config.getInt(REVIEWERS_SECTION, RETRY_COUNT, RETRY_COUNT_DEF);
+ retryInterval =
+ ConfigUtil.getTimeUnit(
+ config, REVIEWERS_SECTION, null, RETRY_INTERVAL, asyncDelay, MILLISECONDS);
+ }
+
+ @VisibleForTesting
+ public AutoAssignConfig() {
+ this.settings = null;
+ this.pluginName = "owners-autoassign";
+ cfgFactory = null;
+ Config config = new Config();
+ asyncReviewers = config.getBoolean(REVIEWERS_SECTION, ASYNC, ASYNC_DEF);
+ asyncThreads = config.getInt(REVIEWERS_SECTION, THREADS, THREADS_DEF);
+ asyncDelay =
+ ConfigUtil.getTimeUnit(
+ config, REVIEWERS_SECTION, null, DELAY, DELAY_MSEC_DEF, MILLISECONDS);
+
+ retryCount = config.getInt(REVIEWERS_SECTION, RETRY_COUNT, RETRY_COUNT_DEF);
+ retryInterval =
+ ConfigUtil.getTimeUnit(
+ config, REVIEWERS_SECTION, null, RETRY_INTERVAL, asyncDelay, MILLISECONDS);
+ }
+
+ public boolean isAsyncReviewers() {
+ return asyncReviewers;
+ }
+
+ public int asyncThreads() {
+ return asyncThreads;
+ }
+
+ public int retryCount() {
+ return retryCount;
+ }
+
+ public long retryInterval() {
+ return retryInterval;
+ }
+
+ public long asyncDelay() {
+ return asyncDelay;
+ }
+
+ private PluginConfig cfg(Project.NameKey projectKey) throws NoSuchProjectException {
+ return cfgFactory.getFromProjectConfigWithInheritance(projectKey, pluginName);
+ }
+
+ public boolean autoAssignWip(Project.NameKey projectKey) throws NoSuchProjectException {
+ return settings
+ .projectSpecificConfig(projectKey)
+ .getEnum(PROJECT_CONFIG_AUTOASSIGN_WIP_CHANGES, TRUE)
+ .equals(TRUE);
+ }
+
+ public ReviewerState autoassignedReviewerState(Project.NameKey projectKey)
+ throws NoSuchProjectException {
+ return settings
+ .projectSpecificConfig(projectKey)
+ .getEnum(PROJECT_CONFIG_AUTOASSIGN_FIELD, ReviewerState.REVIEWER);
+ }
+
+ public boolean isBranchDisabled(String branch) {
+ return settings.isBranchDisabled(branch);
+ }
+
+ public boolean expandGroups() {
+ return settings.expandGroups();
+ }
+}
diff --git a/owners-autoassign/src/main/java/com/googlesource/gerrit/owners/common/AutoassignConfig.java b/owners-autoassign/src/main/java/com/googlesource/gerrit/owners/common/AutoassignConfig.java
deleted file mode 100644
index 1082212..0000000
--- a/owners-autoassign/src/main/java/com/googlesource/gerrit/owners/common/AutoassignConfig.java
+++ /dev/null
@@ -1,58 +0,0 @@
-// Copyright (C) 2021 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.owners.common;
-
-import static com.google.gerrit.extensions.client.InheritableBoolean.TRUE;
-import static com.googlesource.gerrit.owners.common.AutoassignConfigModule.PROJECT_CONFIG_AUTOASSIGN_FIELD;
-import static com.googlesource.gerrit.owners.common.AutoassignConfigModule.PROJECT_CONFIG_AUTOASSIGN_WIP_CHANGES;
-
-import com.google.gerrit.entities.Project;
-import com.google.gerrit.extensions.client.ReviewerState;
-import com.google.gerrit.server.project.NoSuchProjectException;
-import com.google.inject.Inject;
-import com.google.inject.Singleton;
-
-@Singleton
-public class AutoassignConfig {
- private final PluginSettings config;
-
- @Inject
- AutoassignConfig(PluginSettings config) {
- this.config = config;
- }
-
- public boolean autoAssignWip(Project.NameKey projectKey) throws NoSuchProjectException {
- return config
- .projectSpecificConfig(projectKey)
- .getEnum(PROJECT_CONFIG_AUTOASSIGN_WIP_CHANGES, TRUE)
- .equals(TRUE);
- }
-
- public ReviewerState autoassignedReviewerState(Project.NameKey projectKey)
- throws NoSuchProjectException {
- return config
- .projectSpecificConfig(projectKey)
- .getEnum(PROJECT_CONFIG_AUTOASSIGN_FIELD, ReviewerState.REVIEWER);
- }
-
- public boolean isBranchDisabled(String branch) {
- return config.isBranchDisabled(branch);
- }
-
- public boolean expandGroups() {
- return config.expandGroups();
- }
-}
diff --git a/owners-autoassign/src/main/java/com/googlesource/gerrit/owners/common/AutoassignModule.java b/owners-autoassign/src/main/java/com/googlesource/gerrit/owners/common/AutoassignModule.java
index 931c05a..80a03e0 100644
--- a/owners-autoassign/src/main/java/com/googlesource/gerrit/owners/common/AutoassignModule.java
+++ b/owners-autoassign/src/main/java/com/googlesource/gerrit/owners/common/AutoassignModule.java
@@ -28,19 +28,29 @@
public class AutoassignModule extends AbstractModule {
private final Class<? extends OwnersAttentionSet> ownersAttentionSetImpl;
+ private final AutoAssignConfig config;
+
+ @VisibleForTesting
+ AutoassignModule() {
+ this(DefaultAddAllOwnersToAttentionSet.class, new AutoAssignConfig());
+ }
@Inject
- public AutoassignModule() {
- this(DefaultAddAllOwnersToAttentionSet.class);
+ AutoassignModule(AutoAssignConfig config) {
+ this(DefaultAddAllOwnersToAttentionSet.class, config);
}
@VisibleForTesting
- public AutoassignModule(Class<? extends OwnersAttentionSet> ownersAttentionSetImpl) {
+ public AutoassignModule(
+ Class<? extends OwnersAttentionSet> ownersAttentionSetImpl, AutoAssignConfig config) {
this.ownersAttentionSetImpl = ownersAttentionSetImpl;
+ this.config = config;
}
@Override
protected void configure() {
+ bind(ReviewerManager.class)
+ .to(config.isAsyncReviewers() ? AsyncReviewerManager.class : SyncReviewerManager.class);
DynamicSet.bind(binder(), GitReferenceUpdatedListener.class).to(GitRefListener.class);
DynamicItem.bind(binder(), OwnersAttentionSet.class)
.to(ownersAttentionSetImpl)
diff --git a/owners-autoassign/src/main/java/com/googlesource/gerrit/owners/common/GitRefListener.java b/owners-autoassign/src/main/java/com/googlesource/gerrit/owners/common/GitRefListener.java
index 5be3aa0..faf5e42 100644
--- a/owners-autoassign/src/main/java/com/googlesource/gerrit/owners/common/GitRefListener.java
+++ b/owners-autoassign/src/main/java/com/googlesource/gerrit/owners/common/GitRefListener.java
@@ -87,7 +87,7 @@
private ChangeNotes.Factory notesFactory;
- private final AutoassignConfig cfg;
+ private final AutoAssignConfig cfg;
@Inject
public GitRefListener(
@@ -100,7 +100,7 @@
OneOffRequestContext oneOffReqCtx,
Provider<CurrentUser> currentUserProvider,
ChangeNotes.Factory notesFactory,
- AutoassignConfig cfg) {
+ AutoAssignConfig cfg) {
this.api = api;
this.patchListCache = patchListCache;
this.projectCache = projectCache;
diff --git a/owners-autoassign/src/main/java/com/googlesource/gerrit/owners/common/ReviewerManager.java b/owners-autoassign/src/main/java/com/googlesource/gerrit/owners/common/ReviewerManager.java
index 34d6e49..bf56e26 100644
--- a/owners-autoassign/src/main/java/com/googlesource/gerrit/owners/common/ReviewerManager.java
+++ b/owners-autoassign/src/main/java/com/googlesource/gerrit/owners/common/ReviewerManager.java
@@ -1,5 +1,4 @@
-// Copyright (c) 2013 VMware, Inc. All Rights Reserved.
-// Copyright (C) 2017 The Android Open Source Project
+// Copyright (C) 2021 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.
@@ -17,142 +16,14 @@
package com.googlesource.gerrit.owners.common;
import com.google.gerrit.entities.Account;
-import com.google.gerrit.entities.Change;
-import com.google.gerrit.entities.Project;
import com.google.gerrit.entities.Project.NameKey;
-import com.google.gerrit.extensions.api.GerritApi;
-import com.google.gerrit.extensions.api.changes.AttentionSetInput;
import com.google.gerrit.extensions.api.changes.ChangeApi;
-import com.google.gerrit.extensions.api.changes.ReviewInput;
-import com.google.gerrit.extensions.api.changes.ReviewerInput;
-import com.google.gerrit.extensions.client.ReviewerState;
-import com.google.gerrit.extensions.common.ChangeInfo;
-import com.google.gerrit.extensions.registration.DynamicItem;
-import com.google.gerrit.extensions.restapi.RestApiException;
-import com.google.gerrit.server.IdentifiedUser;
-import com.google.gerrit.server.permissions.ChangePermission;
-import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.gerrit.server.project.NoSuchProjectException;
-import com.google.gerrit.server.query.change.ChangeData;
-import com.google.gerrit.server.util.ManualRequestContext;
-import com.google.gerrit.server.util.OneOffRequestContext;
-import com.google.inject.Inject;
-import com.google.inject.Singleton;
-import com.googlesource.gerrit.owners.api.OwnersAttentionSet;
-import java.util.ArrayList;
import java.util.Collection;
-import java.util.Collections;
-import java.util.Objects;
-import java.util.Optional;
-import java.util.Set;
-import java.util.stream.Collectors;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-@Singleton
-public class ReviewerManager {
- private static final Logger log = LoggerFactory.getLogger(ReviewerManager.class);
-
- private final OneOffRequestContext requestContext;
- private final GerritApi gApi;
- private final IdentifiedUser.GenericFactory userFactory;
- private final ChangeData.Factory changeDataFactory;
- private final PermissionBackend permissionBackend;
- private DynamicItem<OwnersAttentionSet> ownersForAttentionSet;
-
- private final AutoassignConfig cfg;
-
- @Inject
- public ReviewerManager(
- OneOffRequestContext requestContext,
- GerritApi gApi,
- IdentifiedUser.GenericFactory userFactory,
- PermissionBackend permissionBackend,
- ChangeData.Factory changeDataFactory,
- DynamicItem<OwnersAttentionSet> ownersForAttentionSet,
- AutoassignConfig cfg) {
- this.requestContext = requestContext;
- this.gApi = gApi;
- this.userFactory = userFactory;
- this.changeDataFactory = changeDataFactory;
- this.permissionBackend = permissionBackend;
- this.ownersForAttentionSet = ownersForAttentionSet;
- this.cfg = cfg;
- }
+public interface ReviewerManager {
public void addReviewers(
NameKey projectNameKey, ChangeApi cApi, Collection<Account.Id> accountsIds)
- throws ReviewerManagerException, NoSuchProjectException {
- try {
- ChangeInfo changeInfo = cApi.get();
- Set<Integer> currentReviewers =
- changeInfo.reviewers.values().stream()
- .flatMap(Collection::stream)
- .map(ri -> ri._accountId)
- .collect(Collectors.toSet());
- ReviewerState reviewerState = cfg.autoassignedReviewerState(projectNameKey);
- try (ManualRequestContext ctx =
- requestContext.openAs(Account.id(changeInfo.owner._accountId))) {
- // 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<>(accountsIds.size());
- Collection<Account.Id> validOwnersForAttentionSet = new ArrayList<>(accountsIds.size());
- for (Account.Id account : accountsIds) {
- if (!currentReviewers.contains(account.get()) && isVisibleTo(changeInfo, account)) {
- ReviewerInput addReviewerInput = new ReviewerInput();
- addReviewerInput.reviewer = account.toString();
- addReviewerInput.state = reviewerState;
- in.reviewers.add(addReviewerInput);
-
- if (reviewerState == ReviewerState.REVIEWER) {
- validOwnersForAttentionSet.add(account);
- }
- } else {
- log.warn(
- "Not adding account {} as reviewer to change {} because the associated ref is not"
- + " visible",
- account,
- changeInfo._number);
- }
- }
-
- Collection<Account.Id> reviewersAccounts;
- if (validOwnersForAttentionSet.isEmpty()) {
- reviewersAccounts = Collections.emptyList();
- } else {
- reviewersAccounts =
- Optional.ofNullable(ownersForAttentionSet)
- .map(DynamicItem::get)
- .filter(Objects::nonNull)
- .map(owners -> owners.addToAttentionSet(changeInfo, validOwnersForAttentionSet))
- .orElse(validOwnersForAttentionSet);
- }
-
- in.ignoreAutomaticAttentionSetRules = true;
- in.addToAttentionSet =
- ownersForAttentionSet.get().addToAttentionSet(changeInfo, reviewersAccounts).stream()
- .map(
- (reviewer) ->
- new AttentionSetInput(
- reviewer.toString(), "Selected as member of the OWNERS file"))
- .collect(Collectors.toList());
-
- gApi.changes().id(changeInfo.id).current().review(in);
- }
- } catch (RestApiException e) {
- log.error("Couldn't add reviewers to the change", e);
- throw new ReviewerManagerException(e);
- }
- }
-
- private boolean isVisibleTo(ChangeInfo changeInfo, Account.Id account) {
- ChangeData changeData =
- changeDataFactory.create(
- Project.nameKey(changeInfo.project), Change.id(changeInfo._number));
- return permissionBackend
- .user(userFactory.create(account))
- .change(changeData)
- .testOrFalse(ChangePermission.READ);
- }
+ throws ReviewerManagerException, NoSuchProjectException;
}
diff --git a/owners-autoassign/src/main/java/com/googlesource/gerrit/owners/common/SyncReviewerManager.java b/owners-autoassign/src/main/java/com/googlesource/gerrit/owners/common/SyncReviewerManager.java
new file mode 100644
index 0000000..83ba531
--- /dev/null
+++ b/owners-autoassign/src/main/java/com/googlesource/gerrit/owners/common/SyncReviewerManager.java
@@ -0,0 +1,165 @@
+// Copyright (c) 2013 VMware, Inc. All Rights Reserved.
+// Copyright (C) 2017 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.owners.common;
+
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.entities.Project.NameKey;
+import com.google.gerrit.extensions.api.GerritApi;
+import com.google.gerrit.extensions.api.changes.AttentionSetInput;
+import com.google.gerrit.extensions.api.changes.ChangeApi;
+import com.google.gerrit.extensions.api.changes.ReviewInput;
+import com.google.gerrit.extensions.api.changes.ReviewerInput;
+import com.google.gerrit.extensions.client.ReviewerState;
+import com.google.gerrit.extensions.common.ChangeInfo;
+import com.google.gerrit.extensions.registration.DynamicItem;
+import com.google.gerrit.extensions.restapi.RestApiException;
+import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.permissions.ChangePermission;
+import com.google.gerrit.server.permissions.PermissionBackend;
+import com.google.gerrit.server.project.NoSuchProjectException;
+import com.google.gerrit.server.query.change.ChangeData;
+import com.google.gerrit.server.util.ManualRequestContext;
+import com.google.gerrit.server.util.OneOffRequestContext;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import com.googlesource.gerrit.owners.api.OwnersAttentionSet;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@Singleton
+class SyncReviewerManager implements ReviewerManager {
+ private static final Logger log = LoggerFactory.getLogger(SyncReviewerManager.class);
+
+ private final OneOffRequestContext requestContext;
+ private final GerritApi gApi;
+ private final IdentifiedUser.GenericFactory userFactory;
+ private final ChangeData.Factory changeDataFactory;
+ private final PermissionBackend permissionBackend;
+
+ /**
+ * TODO: The optional injection here is needed for keeping backward compatibility with existing
+ * setups that do not have the owners-api.jar configured as Gerrit libModule.
+ *
+ * <p>Once merged to master, the optional injection can go and this can be moved as extra argument
+ * in the constructor.
+ */
+ @Inject(optional = true)
+ private DynamicItem<OwnersAttentionSet> ownersForAttentionSet;
+
+ private final AutoAssignConfig cfg;
+
+ @Inject
+ public SyncReviewerManager(
+ OneOffRequestContext requestContext,
+ GerritApi gApi,
+ IdentifiedUser.GenericFactory userFactory,
+ ChangeData.Factory changeDataFactory,
+ PermissionBackend permissionBackend,
+ AutoAssignConfig cfg) {
+ this.requestContext = requestContext;
+ this.gApi = gApi;
+ this.userFactory = userFactory;
+ this.changeDataFactory = changeDataFactory;
+ this.permissionBackend = permissionBackend;
+ this.cfg = cfg;
+ }
+
+ public void addReviewers(
+ NameKey projectNameKey, ChangeApi cApi, Collection<Account.Id> accountsIds)
+ throws ReviewerManagerException, NoSuchProjectException {
+ try {
+ ChangeInfo changeInfo = cApi.get();
+ Set<Integer> currentReviewers =
+ changeInfo.reviewers.values().stream()
+ .flatMap(Collection::stream)
+ .map(ri -> ri._accountId)
+ .collect(Collectors.toSet());
+ ReviewerState reviewerState = cfg.autoassignedReviewerState(projectNameKey);
+ try (ManualRequestContext ctx =
+ requestContext.openAs(Account.id(changeInfo.owner._accountId))) {
+ // 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<>(accountsIds.size());
+ Collection<Account.Id> validOwnersForAttentionSet = new ArrayList<>(accountsIds.size());
+ for (Account.Id account : accountsIds) {
+ if (!currentReviewers.contains(account.get()) && isVisibleTo(changeInfo, account)) {
+ ReviewerInput addReviewerInput = new ReviewerInput();
+ addReviewerInput.reviewer = account.toString();
+ addReviewerInput.state = reviewerState;
+ in.reviewers.add(addReviewerInput);
+
+ if (reviewerState == ReviewerState.REVIEWER) {
+ validOwnersForAttentionSet.add(account);
+ }
+ } else {
+ log.warn(
+ "Not adding account {} as reviewer to change {} because the associated ref is not"
+ + " visible",
+ account,
+ changeInfo._number);
+ }
+ }
+
+ Collection<Account.Id> reviewersAccounts;
+ if (validOwnersForAttentionSet.isEmpty()) {
+ reviewersAccounts = Collections.emptyList();
+ } else {
+ reviewersAccounts =
+ Optional.ofNullable(ownersForAttentionSet)
+ .map(DynamicItem::get)
+ .filter(Objects::nonNull)
+ .map(owners -> owners.addToAttentionSet(changeInfo, validOwnersForAttentionSet))
+ .orElse(validOwnersForAttentionSet);
+ }
+
+ in.ignoreAutomaticAttentionSetRules = true;
+ in.addToAttentionSet =
+ ownersForAttentionSet.get().addToAttentionSet(changeInfo, reviewersAccounts).stream()
+ .map(
+ (reviewer) ->
+ new AttentionSetInput(
+ reviewer.toString(), "Selected as member of the OWNERS file"))
+ .collect(Collectors.toList());
+
+ gApi.changes().id(changeInfo.id).current().review(in);
+ }
+ } catch (RestApiException e) {
+ log.error("Couldn't add reviewers to the change", e);
+ throw new ReviewerManagerException(e);
+ }
+ }
+
+ private boolean isVisibleTo(ChangeInfo changeInfo, Account.Id account) {
+ ChangeData changeData =
+ changeDataFactory.create(
+ Project.nameKey(changeInfo.project), Change.id(changeInfo._number));
+ return permissionBackend
+ .user(userFactory.create(account))
+ .change(changeData)
+ .testOrFalse(ChangePermission.READ);
+ }
+}
diff --git a/owners-autoassign/src/main/resources/Documentation/auto-assign.md b/owners-autoassign/src/main/resources/Documentation/auto-assign.md
new file mode 100644
index 0000000..506a176
--- /dev/null
+++ b/owners-autoassign/src/main/resources/Documentation/auto-assign.md
@@ -0,0 +1,49 @@
+## Reviewers auto-assign configuration
+
+The OWNERS file is processed by the @PLUGIN@ for automatically
+assigning all relevant owners to a change for every new patch-set
+uploaded.
+
+The way that the reviewers are added is controlled by the
+$GERRIT_SITE/etc/@PLUGIN@.config file.
+
+By default, all reviewers are added synchronously when a patch-set
+is uploaded. However, you may want to delay the assignment of additional
+reviewers to a later stage for lowering the pressure on the Git
+repository associated with concurrent updates.
+
+For example, the following configuration would delay the assignment of
+reviewers by 5 seconds:
+
+```
+[reviewers]
+ async = true
+ delay = 5 sec
+```
+
+See below the full list of configuration settings available:
+
+- `reviewers.async`: assign reviewers asynchronously. When set to `false`, all
+ the other settings in @PLUGIN@.config are ignored. By default, set to `false`.
+
+- `reviewers.delay`: delay of the assignment of reviewers since the upload
+ of a new patch-set, expressed in <number> <unit>. By default, set to `0`.
+
+ Values should use common unit suffixes to express their setting:
+
+ - ms, milliseconds
+
+ - s, sec, second, seconds
+
+ - m, min, minute, minutes
+
+ - h, hr, hour, hours
+
+- `reviewers.retryCount`: number of retries for attempting to assign reviewers
+ to a change. By default, set to `2`.
+
+- `reviewers.retryInterval`: delay between retries. Expressed in the same format
+ of the `reviewers.delay`. By default, set to the same value of `reviewers.delay`.
+
+- `reviewers.threads`: maximum concurrency of threads for assigning reviewers to
+ changes. By default, set to 1.
\ No newline at end of file
diff --git a/owners-autoassign/src/test/java/com/vmware/gerrit/owners/common/GitRefListenerIT.java b/owners-autoassign/src/test/java/com/googlesource/gerrit/owners/common/GitRefListenerIT.java
similarity index 97%
rename from owners-autoassign/src/test/java/com/vmware/gerrit/owners/common/GitRefListenerIT.java
rename to owners-autoassign/src/test/java/com/googlesource/gerrit/owners/common/GitRefListenerIT.java
index 884c986..e5be7b5 100644
--- a/owners-autoassign/src/test/java/com/vmware/gerrit/owners/common/GitRefListenerIT.java
+++ b/owners-autoassign/src/test/java/com/googlesource/gerrit/owners/common/GitRefListenerIT.java
@@ -13,7 +13,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.vmware.gerrit.owners.common;
+package com.googlesource.gerrit.owners.common;
import static com.googlesource.gerrit.owners.common.AutoassignConfigModule.PROJECT_CONFIG_AUTOASSIGN_WIP_CHANGES;
import static org.junit.Assert.assertEquals;
@@ -41,12 +41,14 @@
import java.util.HashMap;
import java.util.Map;
import java.util.stream.StreamSupport;
+import com.googlesource.gerrit.owners.common.ReviewerManager;
+
import org.eclipse.jgit.transport.ReceiveCommand.Type;
import org.junit.Test;
@TestPlugin(
name = "owners-autoassign",
- sysModule = "com.vmware.gerrit.owners.common.GitRefListenerIT$TestModule")
+ sysModule = "com.googlesource.gerrit.owners.common.GitRefListenerIT$TestModule")
public class GitRefListenerIT extends LightweightPluginDaemonTest {
private static final String PLUGIN_NAME = "owners-autoassign";
diff --git a/owners-autoassign/src/test/java/com/vmware/gerrit/owners/common/GitRefListenerTest.java b/owners-autoassign/src/test/java/com/googlesource/gerrit/owners/common/GitRefListenerTest.java
similarity index 92%
rename from owners-autoassign/src/test/java/com/vmware/gerrit/owners/common/GitRefListenerTest.java
rename to owners-autoassign/src/test/java/com/googlesource/gerrit/owners/common/GitRefListenerTest.java
index bc093be..9cc6e96 100644
--- a/owners-autoassign/src/test/java/com/vmware/gerrit/owners/common/GitRefListenerTest.java
+++ b/owners-autoassign/src/test/java/com/googlesource/gerrit/owners/common/GitRefListenerTest.java
@@ -13,7 +13,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.vmware.gerrit.owners.common;
+package com.googlesource.gerrit.owners.common;
import com.google.gerrit.entities.Change;
import com.google.gerrit.entities.Project;
@@ -28,7 +28,7 @@
import com.google.inject.Provider;
import com.google.inject.Singleton;
import com.googlesource.gerrit.owners.common.Accounts;
-import com.googlesource.gerrit.owners.common.AutoassignConfig;
+import com.googlesource.gerrit.owners.common.AutoAssignConfig;
import com.googlesource.gerrit.owners.common.GitRefListener;
import com.googlesource.gerrit.owners.common.ReviewerManager;
import org.eclipse.jgit.lib.Repository;
@@ -46,11 +46,11 @@
ProjectCache projectCache,
GitRepositoryManager repositoryManager,
Accounts accounts,
- ReviewerManager reviewerManager,
+ SyncReviewerManager reviewerManager,
OneOffRequestContext oneOffReqCtx,
Provider<CurrentUser> currentUserProvider,
ChangeNotes.Factory notesFactory,
- AutoassignConfig cfg) {
+ AutoAssignConfig cfg) {
super(
api,
patchListCache,
diff --git a/owners-autoassign/src/test/java/com/googlesource/gerrit/owners/common/OwnersAutoassignWithAttentionSetIT.java b/owners-autoassign/src/test/java/com/googlesource/gerrit/owners/common/OwnersAutoassignWithAttentionSetIT.java
index 56cf33a..1cfcf04 100644
--- a/owners-autoassign/src/test/java/com/googlesource/gerrit/owners/common/OwnersAutoassignWithAttentionSetIT.java
+++ b/owners-autoassign/src/test/java/com/googlesource/gerrit/owners/common/OwnersAutoassignWithAttentionSetIT.java
@@ -56,7 +56,7 @@
public static class TestModule extends AbstractModule {
@Override
protected void configure() {
- install(new AutoassignModule(SelectFirstOwnerForAttentionSet.class));
+ install(new AutoassignModule(SelectFirstOwnerForAttentionSet.class, new AutoAssignConfig()));
}
}
diff --git a/owners-autoassign/src/test/java/com/vmware/gerrit/owners/common/ReferenceUpdatedEventTest.java b/owners-autoassign/src/test/java/com/googlesource/gerrit/owners/common/ReferenceUpdatedEventTest.java
similarity index 97%
rename from owners-autoassign/src/test/java/com/vmware/gerrit/owners/common/ReferenceUpdatedEventTest.java
rename to owners-autoassign/src/test/java/com/googlesource/gerrit/owners/common/ReferenceUpdatedEventTest.java
index 7024543..61a485f 100644
--- a/owners-autoassign/src/test/java/com/vmware/gerrit/owners/common/ReferenceUpdatedEventTest.java
+++ b/owners-autoassign/src/test/java/com/googlesource/gerrit/owners/common/ReferenceUpdatedEventTest.java
@@ -13,7 +13,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.vmware.gerrit.owners.common;
+package com.googlesource.gerrit.owners.common;
import com.google.gerrit.entities.Account;
import com.google.gerrit.entities.Project;