Don't change manually altered reviewer state

If users manually changes their reviewer-state, that state should not
be altered by automatically triggered reviewer-additions.

Change-Id: I0e6abc0bbde7b8e0b21ab79a5a81c1c71340f1d1
diff --git a/src/main/java/com/googlesource/gerrit/plugins/reviewers/AddReviewers.java b/src/main/java/com/googlesource/gerrit/plugins/reviewers/AddReviewers.java
index 90f5841..43db627 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/reviewers/AddReviewers.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/reviewers/AddReviewers.java
@@ -28,6 +28,7 @@
 import com.google.inject.assistedinject.Assisted;
 import java.util.ArrayList;
 import java.util.Set;
+import java.util.stream.Collectors;
 
 /** Adds reviewers to a change. */
 class AddReviewers implements Runnable {
@@ -72,14 +73,30 @@
     try {
       // TODO(davido): Switch back to using changes API again,
       // when it supports batch mode for adding reviewers
+      Set<Account.Id> existingReviewers =
+          gApi.changes().id(changeInfo._number).reviewers().stream()
+              .map(r -> Account.id(r._accountId))
+              .collect(Collectors.toSet());
+      /* Don't add, or change state of, already existing reviewers. */
+      Set<Account.Id> reviewersToAdd =
+          reviewers.stream()
+              .filter(r -> !existingReviewers.contains(r))
+              .collect(Collectors.toSet());
+      /* If account is already configured to be added as reviewer, don't attempt to add as cc. */
+      Set<Account.Id> ccsToAdd =
+          ccs.stream()
+              .filter(c -> !existingReviewers.contains(c))
+              .filter(r -> !reviewersToAdd.contains(r))
+              .collect(Collectors.toSet());
+
       ReviewInput in = new ReviewInput();
       in.reviewers = new ArrayList<>(reviewers.size() + ccs.size());
-      for (Account.Id account : reviewers) {
+      for (Account.Id account : reviewersToAdd) {
         AddReviewerInput addReviewerInput = new AddReviewerInput();
         addReviewerInput.reviewer = account.toString();
         in.reviewers.add(addReviewerInput);
       }
-      for (Account.Id account : ccs) {
+      for (Account.Id account : ccsToAdd) {
         AddReviewerInput input = new AddReviewerInput();
         input.state = ReviewerState.CC;
         input.reviewer = account.toString();
diff --git a/src/main/java/com/googlesource/gerrit/plugins/reviewers/Reviewers.java b/src/main/java/com/googlesource/gerrit/plugins/reviewers/Reviewers.java
index 42548a7..348e856 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/reviewers/Reviewers.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/reviewers/Reviewers.java
@@ -156,10 +156,12 @@
       List<ReviewerFilter> matching = findMatchingFilters(changeNumber, filters);
       Set<String> reviewers = getReviewersFrom(matching);
       Set<String> ccs = getCcsFrom(matching);
-      ccs.removeAll(reviewers);
       if (reviewers.isEmpty() && ccs.isEmpty()) {
         return;
       }
+      /* Remove all reviewer identifiers (account-ids, group-ids) from ccs that are present in reviewers.
+       * Further filtering of individual accounts is done in AddReviewers after the ids have been resolved into Accounts. */
+      ccs.removeAll(reviewers);
       final AddReviewers addReviewers =
           addReviewersFactory.create(
               c,
diff --git a/src/test/java/com/googlesource/gerrit/plugins/reviewers/ReviewersIT.java b/src/test/java/com/googlesource/gerrit/plugins/reviewers/ReviewersIT.java
index 1cb511e..02f4902 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/reviewers/ReviewersIT.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/reviewers/ReviewersIT.java
@@ -26,6 +26,7 @@
 import com.google.gerrit.acceptance.TestPlugin;
 import com.google.gerrit.entities.Account;
 import com.google.gerrit.entities.BranchNameKey;
+import com.google.gerrit.extensions.api.changes.AddReviewerInput;
 import com.google.gerrit.extensions.api.changes.ReviewInput;
 import com.google.gerrit.extensions.client.ChangeStatus;
 import com.google.gerrit.extensions.client.ReviewerState;
@@ -125,6 +126,43 @@
     assertThat(reviewersFor(changeId)).containsExactlyElementsIn(ImmutableSet.of(user.id()));
   }
 
+  @Test
+  public void dontChangeExistingStateOfReviewer() throws Exception {
+    Set<Account.Id> userSet = ImmutableSet.of(user.id());
+    TestAccount user2 = accountCreator.user2();
+    Set<Account.Id> user2Set = ImmutableSet.of(user2.id());
+
+    createFilters(filter("*").reviewer(user2).cc(user));
+    String changeId = createChange("refs/for/master").getChangeId();
+    assertThat(ccsFor(changeId)).containsExactlyElementsIn(userSet);
+    assertThat(reviewersFor(changeId)).containsExactlyElementsIn(user2Set);
+
+    addReviewer(changeId, user, ReviewerState.REVIEWER);
+    addReviewer(changeId, user2, ReviewerState.CC);
+    assertThat(reviewersFor(changeId)).containsExactlyElementsIn(userSet);
+    assertThat(ccsFor(changeId)).containsExactlyElementsIn(user2Set);
+
+    amendChange(changeId);
+    assertThat(reviewersFor(changeId)).containsExactlyElementsIn(userSet);
+    assertThat(ccsFor(changeId)).containsExactlyElementsIn(user2Set);
+  }
+
+  @Test
+  public void addAsReviewerIfConfiguredAsReviewerAndCc() throws Exception {
+    createFilters(filter("*").cc(user), filter("branch:master").reviewer(user));
+    String changeId = createChange("refs/for/master").getChangeId();
+    assertThat(reviewersFor(changeId)).containsExactlyElementsIn(ImmutableSet.of(user.id()));
+    assertThat(gApi.changes().id(changeId).get().reviewers.get(CC)).isNull();
+  }
+
+  private void addReviewer(String changeId, TestAccount user, ReviewerState state)
+      throws Exception {
+    AddReviewerInput input = new AddReviewerInput();
+    input.reviewer = user.id().toString();
+    input.state = state;
+    gApi.changes().id(changeId).addReviewer(input);
+  }
+
   private Set<Account.Id> ccsFor(String changeId) throws Exception {
     return reviewersFor(changeId, CC);
   }