Backend: Support cc Support for cc in: * Config * REST * Adding ccs on patchset upload Maintain backwards compatibility by defaulting the newly introduced ConfigReviewersInput#type to "REVIEWERS". TODO: Frontend support for cc. Feature: Issue 11676 Change-Id: I2c2aba662f1a45ed4f2540b7eb32ee56cc8faaba
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 30568dc..90f5841 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/reviewers/AddReviewers.java +++ b/src/main/java/com/googlesource/gerrit/plugins/reviewers/AddReviewers.java
@@ -19,6 +19,7 @@ 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.client.ReviewerState; import com.google.gerrit.extensions.common.ChangeInfo; import com.google.gerrit.extensions.restapi.RestApiException; import com.google.gerrit.server.util.ManualRequestContext; @@ -36,9 +37,13 @@ private final OneOffRequestContext requestContext; private final ChangeInfo changeInfo; private final Set<Account.Id> reviewers; + private final Set<Account.Id> ccs; interface Factory { - AddReviewers create(ChangeInfo changeInfo, Set<Account.Id> reviewers); + AddReviewers create( + ChangeInfo changeInfo, + @Assisted("reviewers") Set<Account.Id> reviewers, + @Assisted("ccs") Set<Account.Id> ccs); } @Inject @@ -46,11 +51,13 @@ GerritApi gApi, OneOffRequestContext requestContext, @Assisted ChangeInfo changeInfo, - @Assisted Set<Account.Id> reviewers) { + @Assisted("reviewers") Set<Account.Id> reviewers, + @Assisted("ccs") Set<Account.Id> ccs) { this.gApi = gApi; this.requestContext = requestContext; this.changeInfo = changeInfo; this.reviewers = reviewers; + this.ccs = ccs; } @Override @@ -66,12 +73,18 @@ // 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()); + in.reviewers = new ArrayList<>(reviewers.size() + ccs.size()); for (Account.Id account : reviewers) { AddReviewerInput addReviewerInput = new AddReviewerInput(); addReviewerInput.reviewer = account.toString(); in.reviewers.add(addReviewerInput); } + for (Account.Id account : ccs) { + AddReviewerInput input = new AddReviewerInput(); + input.state = ReviewerState.CC; + input.reviewer = account.toString(); + in.reviewers.add(input); + } gApi.changes().id(changeInfo._number).current().review(in); } catch (RestApiException e) { logger.atSevere().withCause(e).log("Couldn't add reviewers to the change");
diff --git a/src/main/java/com/googlesource/gerrit/plugins/reviewers/PutReviewers.java b/src/main/java/com/googlesource/gerrit/plugins/reviewers/PutReviewers.java index 9efc8bd..d815633 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/reviewers/PutReviewers.java +++ b/src/main/java/com/googlesource/gerrit/plugins/reviewers/PutReviewers.java
@@ -54,6 +54,7 @@ protected static class Input { public Action action; + public ReviewerType type; public String filter; public String reviewer; } @@ -105,23 +106,29 @@ if (input.action == Action.ADD) { validateReviewer(input.reviewer); } + if (input.type == null) { + input.type = ReviewerType.REVIEWER; + } try { StringBuilder message = new StringBuilder(pluginName).append(" plugin: "); forProject.load(md); if (input.action == Action.ADD) { message - .append("Add reviewer ") + .append("Add ") + .append(input.type.name) + .append(" ") .append(input.reviewer) .append(" to filter ") .append(input.filter); - forProject.addReviewer(input.filter, input.reviewer); + forProject.addReviewer(input.filter, input.reviewer, input.type); } else { message - .append("Remove reviewer ") - .append(input.reviewer) + .append("Remove ") + .append(input.type.name) + .append(" ") .append(" from filter ") .append(input.filter); - forProject.removeReviewer(input.filter, input.reviewer); + forProject.removeReviewer(input.filter, input.reviewer, input.type); } message.append("\n"); md.setMessage(message.toString());
diff --git a/src/main/java/com/googlesource/gerrit/plugins/reviewers/ReviewerFilter.java b/src/main/java/com/googlesource/gerrit/plugins/reviewers/ReviewerFilter.java index 99a0c59..b991760 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/reviewers/ReviewerFilter.java +++ b/src/main/java/com/googlesource/gerrit/plugins/reviewers/ReviewerFilter.java
@@ -29,6 +29,7 @@ public abstract class ReviewerFilter { protected String filter; protected Set<String> reviewers; + protected Set<String> ccs; String getFilter() { return filter; @@ -38,17 +39,23 @@ return reviewers; } + Set<String> getCcs() { + return ccs; + } + @Override public boolean equals(Object o) { if (o instanceof ReviewerFilter) { ReviewerFilter other = ((ReviewerFilter) o); - return Objects.equals(filter, other.filter) && Objects.equals(reviewers, other.reviewers); + return Objects.equals(filter, other.filter) + && Objects.equals(reviewers, other.reviewers) + && Objects.equals(ccs, other.ccs); } return false; } @Override public int hashCode() { - return Objects.hash(filter, reviewers); + return Objects.hash(filter, reviewers, ccs); } }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/reviewers/ReviewerType.java b/src/main/java/com/googlesource/gerrit/plugins/reviewers/ReviewerType.java new file mode 100644 index 0000000..2d8f943 --- /dev/null +++ b/src/main/java/com/googlesource/gerrit/plugins/reviewers/ReviewerType.java
@@ -0,0 +1,25 @@ +// Copyright (C) 2020 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; + +public enum ReviewerType { + REVIEWER("reviewer"), + CC("cc"); + + String name; + + ReviewerType(String name) { + this.name = name; + } +}
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 75a976d..42548a7 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/reviewers/Reviewers.java +++ b/src/main/java/com/googlesource/gerrit/plugins/reviewers/Reviewers.java
@@ -20,6 +20,7 @@ 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.common.flogger.FluentLogger; import com.google.gerrit.common.Nullable; import com.google.gerrit.entities.Account; @@ -152,13 +153,18 @@ AccountInfo uploader = event.getWho(); int changeNumber = c._number; try { - Set<String> reviewers = findReviewers(changeNumber, filters); - if (reviewers.isEmpty()) { + 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; } final AddReviewers addReviewers = addReviewersFactory.create( - c, resolver.resolve(reviewers, projectName, changeNumber, uploader)); + c, + resolver.resolve(reviewers, projectName, changeNumber, uploader), + resolver.resolve(ccs, projectName, changeNumber, uploader)); workQueue.submit(addReviewers); } catch (QueryParseException e) { logger.atWarning().log( @@ -171,15 +177,26 @@ private Set<String> findReviewers(int change, List<ReviewerFilter> filters) throws StorageException, QueryParseException { + return getReviewersFrom(findMatchingFilters(change, filters)); + } + + private Set<String> getReviewersFrom(List<ReviewerFilter> filters) throws StorageException { ImmutableSet.Builder<String> reviewers = ImmutableSet.builder(); - List<ReviewerFilter> found = findReviewerFilters(change, filters); - for (ReviewerFilter s : found) { - reviewers.addAll(s.getReviewers()); + for (ReviewerFilter f : filters) { + reviewers.addAll(f.getReviewers()); } return reviewers.build(); } - private List<ReviewerFilter> findReviewerFilters(int change, List<ReviewerFilter> filters) + private Set<String> getCcsFrom(List<ReviewerFilter> filters) throws StorageException { + Set<String> ccs = Sets.newHashSet(); + for (ReviewerFilter f : filters) { + ccs.addAll(f.getCcs()); + } + return ccs; + } + + private List<ReviewerFilter> findMatchingFilters(int change, List<ReviewerFilter> filters) throws StorageException, QueryParseException { ImmutableList.Builder<ReviewerFilter> found = ImmutableList.builder(); for (ReviewerFilter s : filters) {
diff --git a/src/main/java/com/googlesource/gerrit/plugins/reviewers/config/ReviewerFilterCollection.java b/src/main/java/com/googlesource/gerrit/plugins/reviewers/config/ReviewerFilterCollection.java index dab0ca0..cd41373 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/reviewers/config/ReviewerFilterCollection.java +++ b/src/main/java/com/googlesource/gerrit/plugins/reviewers/config/ReviewerFilterCollection.java
@@ -14,6 +14,7 @@ package com.googlesource.gerrit.plugins.reviewers.config; +import static com.googlesource.gerrit.plugins.reviewers.config.ReviewersConfig.KEY_CC; import static com.googlesource.gerrit.plugins.reviewers.config.ReviewersConfig.KEY_REVIEWER; import static com.googlesource.gerrit.plugins.reviewers.config.ReviewersConfig.SECTION_FILTER; @@ -50,6 +51,7 @@ private ReviewerFilterSection(String filter) { this.filter = filter; this.reviewers = Sets.newHashSet(cfg.getStringList(SECTION_FILTER, filter, KEY_REVIEWER)); + this.ccs = Sets.newHashSet(cfg.getStringList(SECTION_FILTER, filter, KEY_CC)); } public void removeReviewer(String reviewer) { @@ -62,11 +64,22 @@ save(); } + public void removeCc(String cc) { + ccs.remove(cc); + save(); + } + + public void addCc(String cc) { + ccs.add(cc); + save(); + } + private void save() { - if (this.reviewers.isEmpty()) { + if (this.reviewers.isEmpty() && this.ccs.isEmpty()) { cfg.unsetSection(SECTION_FILTER, filter); } else { cfg.setStringList(SECTION_FILTER, filter, KEY_REVIEWER, Lists.newArrayList(this.reviewers)); + cfg.setStringList(SECTION_FILTER, filter, KEY_CC, Lists.newArrayList(this.ccs)); } } }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/reviewers/config/ReviewersConfig.java b/src/main/java/com/googlesource/gerrit/plugins/reviewers/config/ReviewersConfig.java index e440dcd..c81cbd5 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/reviewers/config/ReviewersConfig.java +++ b/src/main/java/com/googlesource/gerrit/plugins/reviewers/config/ReviewersConfig.java
@@ -26,6 +26,7 @@ import com.google.inject.Inject; import com.google.inject.Singleton; import com.googlesource.gerrit.plugins.reviewers.ReviewerFilter; +import com.googlesource.gerrit.plugins.reviewers.ReviewerType; import java.io.IOException; import java.util.List; import org.eclipse.jgit.errors.ConfigInvalidException; @@ -39,6 +40,7 @@ @VisibleForTesting public static final String FILENAME = "reviewers.config"; @VisibleForTesting public static final String SECTION_FILTER = "filter"; + @VisibleForTesting public static final String KEY_CC = "cc"; @VisibleForTesting public static final String KEY_REVIEWER = "reviewer"; private static final String KEY_ENABLE_REST = "enableREST"; private static final String KEY_SUGGEST_ONLY = "suggestOnly"; @@ -88,12 +90,24 @@ private Config cfg; private ReviewerFilterCollection filters; - public void addReviewer(String filter, String reviewer) { - filters.get(filter).addReviewer(reviewer); + public void addReviewer(String filter, String reviewer, ReviewerType type) { + switch (type) { + case REVIEWER: + filters.get(filter).addReviewer(reviewer); + break; + case CC: + filters.get(filter).addCc(reviewer); + } } - public void removeReviewer(String filter, String reviewer) { - filters.get(filter).removeReviewer(reviewer); + public void removeReviewer(String filter, String reviewer, ReviewerType type) { + switch (type) { + case REVIEWER: + filters.get(filter).removeReviewer(reviewer); + break; + case CC: + filters.get(filter).removeCc(reviewer); + } } @Override
diff --git a/src/main/resources/Documentation/config.md b/src/main/resources/Documentation/config.md index 1088535..1b51181 100644 --- a/src/main/resources/Documentation/config.md +++ b/src/main/resources/Documentation/config.md
@@ -38,6 +38,7 @@ ``` [filter "*"] reviewer = john.doe@example.com + cc = DevGroup [filter "branch:main file:^lib/.*"] reviewer = jane.doe@example.com @@ -52,9 +53,16 @@ account's email address or username, or the group name. Multiple `reviewer` occurrences are allowed. +filter.\<filter\>.cc +: An account or a group name. Must be an exact match (case sensitive) with the + account's email address or username, or the group name. Multiple `cc` + occurrences are allowed. + ##Multiple filter matches The plugin supports multiple filter matches. +If a reviewer, according to filter matches, should be added as both `reviewer` and `cc`, +`reviewer` takes precedence. ###Example
diff --git a/src/main/resources/Documentation/rest-api.md b/src/main/resources/Documentation/rest-api.md index 8d13cad..957318f 100644 --- a/src/main/resources/Documentation/rest-api.md +++ b/src/main/resources/Documentation/rest-api.md
@@ -36,6 +36,9 @@ "reviewers": [ "UserA", "UserB" + ], + "cc": [ + "DevGroup" ] }, { @@ -66,8 +69,9 @@ Content-Type: application/json;charset=UTF-8 { "action": "ADD", - "filter": "branch:master" - "reviewer": "UserA" + "type": "REVIEWER" + "filter": "branch:master", + "reviewer": "UserA", } ``` @@ -88,6 +92,9 @@ "UserA", "UserB" ] + "ccs": [ + "DevGroup" + ] }, { "filter": "file:^lib/*", @@ -111,6 +118,8 @@ * _filter_: A filter that is used to assign default reviewers. * _reviewers_: List of usernames which are assigned as default reviewers under the filter. +* _ccs_: List of usernames which are assigned as default ccs + under the filter. ### <a id="config-reviewers-input"></a>ConfigReviewersInput @@ -118,6 +127,7 @@ reviewers. * _action_: Indicates whether to add or remove the input reviewer +* _type_: Which type to add/remove the user as, (REVIEWER|CC), defaults to REVIEWER. * _filter_: The filter associated with the input reviewer. * _reviewer_: The user to add or remove from the default reviewers.
diff --git a/src/test/java/com/googlesource/gerrit/plugins/reviewers/AbstractReviewersPluginTest.java b/src/test/java/com/googlesource/gerrit/plugins/reviewers/AbstractReviewersPluginTest.java index 731602d..1407b48 100644 --- a/src/test/java/com/googlesource/gerrit/plugins/reviewers/AbstractReviewersPluginTest.java +++ b/src/test/java/com/googlesource/gerrit/plugins/reviewers/AbstractReviewersPluginTest.java
@@ -16,6 +16,7 @@ import static com.google.gerrit.acceptance.GitUtil.fetch; import static com.googlesource.gerrit.plugins.reviewers.config.ReviewersConfig.FILENAME; +import static com.googlesource.gerrit.plugins.reviewers.config.ReviewersConfig.KEY_CC; import static com.googlesource.gerrit.plugins.reviewers.config.ReviewersConfig.KEY_REVIEWER; import static com.googlesource.gerrit.plugins.reviewers.config.ReviewersConfig.SECTION_FILTER; @@ -47,9 +48,11 @@ Config cfg = new Config(); Arrays.stream(filters) .forEach( - f -> - cfg.setStringList( - SECTION_FILTER, f.filter, KEY_REVIEWER, Lists.newArrayList(f.reviewers))); + f -> { + cfg.setStringList( + SECTION_FILTER, f.filter, KEY_REVIEWER, Lists.newArrayList(f.reviewers)); + cfg.setStringList(SECTION_FILTER, f.filter, KEY_CC, Lists.newArrayList(f.ccs)); + }); pushFactory .create(admin.newIdent(), repo, "Add reviewers", FILENAME, cfg.toText()) .to(RefNames.REFS_CONFIG) @@ -63,8 +66,8 @@ return repo; } - protected TestFilter filter(String filter, Set<String> reviewers) { - return new TestFilter(filter, reviewers); + private TestFilter filter(String filter, Set<String> reviewers, Set<String> ccs) { + return new TestFilter(filter, reviewers, ccs); } protected TestFilter filter(String filter) { @@ -73,13 +76,14 @@ protected static class TestFilter extends ReviewerFilter { - public TestFilter(String filter, Set<String> reviewers) { + public TestFilter(String filter, Set<String> reviewers, Set<String> ccs) { this.filter = filter; this.reviewers = reviewers; + this.ccs = ccs; } public TestFilter(String filter) { - this(filter, Sets.newHashSet()); + this(filter, Sets.newHashSet(), Sets.newHashSet()); } public TestFilter reviewer(String reviewerId) { @@ -90,5 +94,14 @@ public TestFilter reviewer(TestAccount reviewer) { return reviewer(reviewer.email()); } + + public TestFilter cc(String ccId) { + ccs.add(ccId); + return this; + } + + public TestFilter cc(TestAccount cc) { + return cc(cc.email()); + } } }
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 a2a7035..1cb511e 100644 --- a/src/test/java/com/googlesource/gerrit/plugins/reviewers/ReviewersIT.java +++ b/src/test/java/com/googlesource/gerrit/plugins/reviewers/ReviewersIT.java
@@ -15,6 +15,7 @@ package com.googlesource.gerrit.plugins.reviewers; import static com.google.common.truth.Truth.assertThat; +import static com.google.gerrit.extensions.client.ReviewerState.CC; import static com.google.gerrit.extensions.client.ReviewerState.REVIEWER; import static java.util.stream.Collectors.toSet; @@ -27,6 +28,7 @@ import com.google.gerrit.entities.BranchNameKey; import com.google.gerrit.extensions.api.changes.ReviewInput; import com.google.gerrit.extensions.client.ChangeStatus; +import com.google.gerrit.extensions.client.ReviewerState; import com.google.gerrit.extensions.common.ChangeInfo; import java.util.Set; import org.junit.Test; @@ -38,10 +40,19 @@ @Test public void addReviewers() throws Exception { TestAccount user2 = accountCreator.user2(); - createFilters(filter("*").reviewer(user).reviewer(user2)); + createFilters(filter("*").reviewer(user).cc(user2)); String changeId = createChange().getChangeId(); - assertThat(reviewersFor(changeId)) - .containsExactlyElementsIn(ImmutableSet.of(user.id(), user2.id())); + assertThat(reviewersFor(changeId)).containsExactlyElementsIn(ImmutableSet.of(user.id())); + assertThat(ccsFor(changeId)).containsExactlyElementsIn(ImmutableSet.of(user2.id())); + } + + @Test + public void addReviewerMatchingReviewerAndCc() throws Exception { + TestAccount user2 = accountCreator.user2(); + createFilters(filter("*").cc(user).cc(user2), filter("^a.txt").reviewer(user2)); + String changeId = createChange().getChangeId(); + assertThat(reviewersFor(changeId)).containsExactlyElementsIn(ImmutableSet.of(user2.id())); + assertThat(ccsFor(changeId)).containsExactlyElementsIn(ImmutableSet.of(user.id())); } @Test @@ -54,8 +65,9 @@ } @Test - public void doNotAddReviewersFromNonMatchingFilters() throws Exception { - createFilters(filter("branch:master").reviewer(user)); + public void doNotAddReviewersOrCcFromNonMatchingFilters() throws Exception { + TestAccount user2 = accountCreator.user2(); + createFilters(filter("branch:master").reviewer(user).cc(user2)); createBranch(BranchNameKey.create(project, "other-branch")); // Create a change that matches the filter section. createChange("refs/for/master"); @@ -113,13 +125,23 @@ assertThat(reviewersFor(changeId)).containsExactlyElementsIn(ImmutableSet.of(user.id())); } + private Set<Account.Id> ccsFor(String changeId) throws Exception { + return reviewersFor(changeId, CC); + } + private Set<Account.Id> reviewersFor(String changeId) throws Exception { - return gApi.changes().id(changeId).get().reviewers.get(REVIEWER).stream() + return reviewersFor(changeId, REVIEWER); + } + + private Set<Account.Id> reviewersFor(String changeId, ReviewerState reviewerState) + throws Exception { + return gApi.changes().id(changeId).get().reviewers.get(reviewerState).stream() .map(a -> Account.id(a._accountId)) .collect(toSet()); } private void assertNoReviewersAddedFor(String changeId) throws Exception { assertThat(gApi.changes().id(changeId).get().reviewers.get(REVIEWER)).isNull(); + assertThat(gApi.changes().id(changeId).get().reviewers.get(CC)).isNull(); } }
diff --git a/src/test/java/com/googlesource/gerrit/plugins/reviewers/config/ReviewersConfigIT.java b/src/test/java/com/googlesource/gerrit/plugins/reviewers/config/ReviewersConfigIT.java index 9e4c06f..cd593c8 100644 --- a/src/test/java/com/googlesource/gerrit/plugins/reviewers/config/ReviewersConfigIT.java +++ b/src/test/java/com/googlesource/gerrit/plugins/reviewers/config/ReviewersConfigIT.java
@@ -47,27 +47,39 @@ } @Test - public void reviewersConfigWithMergedInheritance() throws Exception { + public void reviewersConfigSingleWithCc() throws Exception { + createFilters(filter(NO_FILTER).reviewer(JOHN_DOE).cc(JANE_DOE)); + + assertProjectHasFilters(project, filter(NO_FILTER).reviewer(JOHN_DOE).cc(JANE_DOE)); + } + + @Test + public void reviewersConfigSingleWithCcSeparateFilters() throws Exception { + createFilters(filter(NO_FILTER).reviewer(JOHN_DOE), filter(BRANCH_MAIN).cc(JANE_DOE)); + + assertProjectHasFilters( + project, filter(NO_FILTER).reviewer(JOHN_DOE), filter(BRANCH_MAIN).cc(JANE_DOE)); + } + + @Test + public void reviewersConfigWithMergedInheritanceWithCc() throws Exception { Project.NameKey childProject = projectOperations.newProject().parent(project).create(); TestRepository<?> childTestRepo = checkoutRefsMetaConfig(cloneProject(childProject)); createFiltersFor( - childTestRepo, - filter(NO_FILTER).reviewer(JANE_DOE), - filter(BRANCH_MAIN).reviewer(JANE_DOE)); + childTestRepo, filter(NO_FILTER).reviewer(JANE_DOE), filter(BRANCH_MAIN).cc(JANE_DOE)); - createFilters(filter(NO_FILTER).reviewer(JOHN_DOE), filter(BRANCH_MAIN).reviewer(JOHN_DOE)); + createFilters(filter(NO_FILTER).cc(JOHN_DOE), filter(BRANCH_MAIN).reviewer(JOHN_DOE)); assertProjectHasFilters( childProject, - filter(NO_FILTER).reviewer(JOHN_DOE).reviewer(JANE_DOE), - filter(BRANCH_MAIN).reviewer(JOHN_DOE).reviewer(JANE_DOE)); + filter(NO_FILTER).cc(JOHN_DOE).reviewer(JANE_DOE), + filter(BRANCH_MAIN).reviewer(JOHN_DOE).cc(JANE_DOE)); } private void assertProjectHasFilters(Project.NameKey project, TestFilter... filters) { assertThat(reviewersConfig().filtersWithInheritance(project)) - .containsExactlyElementsIn(ImmutableList.copyOf(filters)) - .inOrder(); + .containsExactlyElementsIn(ImmutableList.copyOf(filters)); } private ReviewersConfig reviewersConfig() {