Merge "Extend project resetter to groups"
diff --git a/java/com/google/gerrit/acceptance/AbstractDaemonTest.java b/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
index 3488588..fbf3b84 100644
--- a/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
+++ b/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
@@ -334,14 +334,14 @@
         // Don't reset all refs so that refs/sequences/changes is not touched and change IDs are
         // not reused.
         .reset(allProjects, RefNames.REFS_CONFIG)
-        // Don't reset group branches since this would make the groups inconsistent between
-        // ReviewDb and NoteDb.
         // Don't reset refs/sequences/accounts so that account IDs are not reused.
         .reset(
             allUsers,
             RefNames.REFS_CONFIG,
             RefNames.REFS_USERS + "*",
             RefNames.REFS_EXTERNAL_IDS,
+            RefNames.REFS_GROUPNAMES,
+            RefNames.REFS_GROUPS + "*",
             RefNames.REFS_STARRED_CHANGES + "*",
             RefNames.REFS_DRAFT_COMMENTS + "*");
   }
diff --git a/java/com/google/gerrit/acceptance/ProjectResetter.java b/java/com/google/gerrit/acceptance/ProjectResetter.java
index 86718be..7d17285 100644
--- a/java/com/google/gerrit/acceptance/ProjectResetter.java
+++ b/java/com/google/gerrit/acceptance/ProjectResetter.java
@@ -24,13 +24,17 @@
 import com.google.common.collect.Sets;
 import com.google.gerrit.common.Nullable;
 import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.reviewdb.client.RefNames;
 import com.google.gerrit.server.account.AccountCache;
+import com.google.gerrit.server.account.GroupCache;
+import com.google.gerrit.server.account.GroupIncludeCache;
 import com.google.gerrit.server.config.AllUsersName;
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.index.RefState;
 import com.google.gerrit.server.index.account.AccountIndexer;
+import com.google.gerrit.server.index.group.GroupIndexer;
 import com.google.gerrit.server.project.ProjectCache;
 import com.google.gerrit.server.project.RefPatternMatcher;
 import com.google.inject.Inject;
@@ -88,6 +92,9 @@
     @Nullable private final AccountCreator accountCreator;
     @Nullable private final AccountCache accountCache;
     @Nullable private final AccountIndexer accountIndexer;
+    @Nullable private final GroupCache groupCache;
+    @Nullable private final GroupIncludeCache groupIncludeCache;
+    @Nullable private final GroupIndexer groupIndexer;
     @Nullable private final ProjectCache projectCache;
 
     @Inject
@@ -97,12 +104,18 @@
         @Nullable AccountCreator accountCreator,
         @Nullable AccountCache accountCache,
         @Nullable AccountIndexer accountIndexer,
+        @Nullable GroupCache groupCache,
+        @Nullable GroupIncludeCache groupIncludeCache,
+        @Nullable GroupIndexer groupIndexer,
         @Nullable ProjectCache projectCache) {
       this.repoManager = repoManager;
       this.allUsersName = allUsersName;
       this.accountCreator = accountCreator;
       this.accountCache = accountCache;
       this.accountIndexer = accountIndexer;
+      this.groupCache = groupCache;
+      this.groupIncludeCache = groupIncludeCache;
+      this.groupIndexer = groupIndexer;
       this.projectCache = projectCache;
     }
 
@@ -113,6 +126,9 @@
           accountCreator,
           accountCache,
           accountIndexer,
+          groupCache,
+          groupIncludeCache,
+          groupIndexer,
           projectCache,
           input.refsByProject);
     }
@@ -139,12 +155,18 @@
   @Inject private AllUsersName allUsersName;
   @Inject @Nullable private AccountCreator accountCreator;
   @Inject @Nullable private AccountCache accountCache;
+  @Inject @Nullable private GroupCache groupCache;
+  @Inject @Nullable private GroupIncludeCache groupIncludeCache;
+  @Inject @Nullable private GroupIndexer groupIndexer;
   @Inject @Nullable private AccountIndexer accountIndexer;
   @Inject @Nullable private ProjectCache projectCache;
 
   private final Multimap<Project.NameKey, String> refsPatternByProject;
+
+  // State to which to reset to.
   private final Multimap<Project.NameKey, RefState> savedRefStatesByProject;
 
+  // Results of the resetting
   private Multimap<Project.NameKey, String> keptRefsByProject;
   private Multimap<Project.NameKey, String> restoredRefsByProject;
   private Multimap<Project.NameKey, String> deletedRefsByProject;
@@ -155,6 +177,9 @@
       @Nullable AccountCreator accountCreator,
       @Nullable AccountCache accountCache,
       @Nullable AccountIndexer accountIndexer,
+      @Nullable GroupCache groupCache,
+      @Nullable GroupIncludeCache groupIncludeCache,
+      @Nullable GroupIndexer groupIndexer,
       @Nullable ProjectCache projectCache,
       Multimap<Project.NameKey, String> refPatternByProject)
       throws IOException {
@@ -163,6 +188,9 @@
     this.accountCreator = accountCreator;
     this.accountCache = accountCache;
     this.accountIndexer = accountIndexer;
+    this.groupCache = groupCache;
+    this.groupIndexer = groupIndexer;
+    this.groupIncludeCache = groupIncludeCache;
     this.projectCache = projectCache;
     this.refsPatternByProject = refPatternByProject;
     this.savedRefStatesByProject = readRefStates();
@@ -265,8 +293,8 @@
   private void evictCachesAndReindex() throws IOException {
     evictAndReindexProjects();
     evictAndReindexAccounts();
+    evictAndReindexGroups();
 
-    // TODO(ekempin): Evict groups from cache if group refs were modified.
     // TODO(ekempin): Reindex changes if starred-changes refs in All-Users were modified.
   }
 
@@ -329,16 +357,47 @@
     }
   }
 
+  /** Evict groups that were modified. */
+  private void evictAndReindexGroups() throws IOException {
+    if (groupCache != null || groupIndexer != null) {
+      Set<AccountGroup.UUID> modifiedGroups =
+          new HashSet<>(groupUUIDs(restoredRefsByProject.get(allUsersName)));
+      Set<AccountGroup.UUID> deletedGroups =
+          new HashSet<>(groupUUIDs(deletedRefsByProject.get(allUsersName)));
+
+      // Evict and reindex all modified and deleted groups.
+      for (AccountGroup.UUID uuid : Sets.union(modifiedGroups, deletedGroups)) {
+        evictAndReindexGroup(uuid);
+      }
+    }
+  }
+
   private void evictAndReindexAccount(Account.Id accountId) throws IOException {
     if (accountCache != null) {
       accountCache.evict(accountId);
     }
-
+    if (groupIncludeCache != null) {
+      groupIncludeCache.evictGroupsWithMember(accountId);
+    }
     if (accountIndexer != null) {
       accountIndexer.index(accountId);
     }
   }
 
+  private void evictAndReindexGroup(AccountGroup.UUID uuid) throws IOException {
+    if (groupCache != null) {
+      groupCache.evict(uuid);
+    }
+
+    if (groupIncludeCache != null) {
+      groupIncludeCache.evictParentGroupsOf(uuid);
+    }
+
+    if (groupIndexer != null) {
+      groupIndexer.index(uuid);
+    }
+  }
+
   private Set<Account.Id> accountIds(Collection<String> refs) {
     return refs.stream()
         .filter(r -> r.startsWith(REFS_USERS))
@@ -346,4 +405,12 @@
         .filter(Objects::nonNull)
         .collect(toSet());
   }
+
+  private Set<AccountGroup.UUID> groupUUIDs(Collection<String> refs) {
+    return refs.stream()
+        .filter(RefNames::isRefsGroups)
+        .map(r -> AccountGroup.UUID.fromRef(r))
+        .filter(Objects::nonNull)
+        .collect(toSet());
+  }
 }
diff --git a/javatests/com/google/gerrit/acceptance/ProjectResetterTest.java b/javatests/com/google/gerrit/acceptance/ProjectResetterTest.java
index fce28de..bed2504 100644
--- a/javatests/com/google/gerrit/acceptance/ProjectResetterTest.java
+++ b/javatests/com/google/gerrit/acceptance/ProjectResetterTest.java
@@ -20,12 +20,16 @@
 import com.google.gerrit.common.Nullable;
 import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.reviewdb.client.RefNames;
 import com.google.gerrit.server.account.AccountCache;
+import com.google.gerrit.server.account.GroupCache;
+import com.google.gerrit.server.account.GroupIncludeCache;
 import com.google.gerrit.server.config.AllUsersName;
 import com.google.gerrit.server.config.AllUsersNameProvider;
 import com.google.gerrit.server.index.account.AccountIndexer;
+import com.google.gerrit.server.index.group.GroupIndexer;
 import com.google.gerrit.server.project.ProjectCache;
 import com.google.gerrit.testing.GerritBaseTests;
 import com.google.gerrit.testing.InMemoryRepositoryManager;
@@ -224,7 +228,7 @@
     Ref nonMetaConfig = createRef("refs/heads/master");
 
     try (ProjectResetter resetProject =
-        builder(null, null, null, projectCache)
+        builder(null, null, null, null, null, null, projectCache)
             .build(new ProjectResetter.Config().reset(project).reset(project2))) {
       updateRef(nonMetaConfig);
       updateRef(repo2, metaConfig);
@@ -244,7 +248,7 @@
     EasyMock.replay(projectCache);
 
     try (ProjectResetter resetProject =
-        builder(null, null, null, projectCache)
+        builder(null, null, null, null, null, null, projectCache)
             .build(new ProjectResetter.Config().reset(project).reset(project2))) {
       createRef("refs/heads/master");
       createRef(repo2, RefNames.REFS_CONFIG);
@@ -274,7 +278,7 @@
     Ref nonUserBranch = createRef(RefNames.refsUsers(new Account.Id(2)));
 
     try (ProjectResetter resetProject =
-        builder(null, accountCache, accountIndexer, null)
+        builder(null, accountCache, accountIndexer, null, null, null, null)
             .build(new ProjectResetter.Config().reset(project).reset(allUsers))) {
       updateRef(nonUserBranch);
       updateRef(allUsersRepo, userBranch);
@@ -300,7 +304,7 @@
     EasyMock.replay(accountIndexer);
 
     try (ProjectResetter resetProject =
-        builder(null, accountCache, accountIndexer, null)
+        builder(null, accountCache, accountIndexer, null, null, null, null)
             .build(new ProjectResetter.Config().reset(project).reset(allUsers))) {
       // Non-user branch because it's not in All-Users.
       createRef(RefNames.refsUsers(new Account.Id(2)));
@@ -339,7 +343,7 @@
     Ref nonUserBranch = createRef(RefNames.refsUsers(new Account.Id(3)));
 
     try (ProjectResetter resetProject =
-        builder(null, accountCache, accountIndexer, null)
+        builder(null, accountCache, accountIndexer, null, null, null, null)
             .build(new ProjectResetter.Config().reset(project).reset(allUsers))) {
       updateRef(nonUserBranch);
       updateRef(allUsersRepo, externalIds);
@@ -376,7 +380,7 @@
     Ref nonUserBranch = createRef(RefNames.refsUsers(new Account.Id(3)));
 
     try (ProjectResetter resetProject =
-        builder(null, accountCache, accountIndexer, null)
+        builder(null, accountCache, accountIndexer, null, null, null, null)
             .build(new ProjectResetter.Config().reset(project).reset(allUsers))) {
       updateRef(nonUserBranch);
       createRef(allUsersRepo, RefNames.REFS_EXTERNAL_IDS);
@@ -398,7 +402,7 @@
     EasyMock.replay(accountCreator);
 
     try (ProjectResetter resetProject =
-        builder(accountCreator, null, null, null)
+        builder(accountCreator, null, null, null, null, null, null)
             .build(new ProjectResetter.Config().reset(project).reset(allUsers))) {
       createRef(allUsersRepo, RefNames.refsUsers(accountId));
     }
@@ -406,6 +410,39 @@
     EasyMock.verify(accountCreator);
   }
 
+  @Test
+  public void groupEviction() throws Exception {
+    AccountGroup.UUID uuid1 = new AccountGroup.UUID("abcd1");
+    AccountGroup.UUID uuid2 = new AccountGroup.UUID("abcd2");
+    AccountGroup.UUID uuid3 = new AccountGroup.UUID("abcd3");
+    Project.NameKey allUsers = new Project.NameKey(AllUsersNameProvider.DEFAULT);
+    Repository allUsersRepo = repoManager.createRepository(allUsers);
+
+    GroupCache cache = EasyMock.createNiceMock(GroupCache.class);
+    GroupIndexer indexer = EasyMock.createNiceMock(GroupIndexer.class);
+    GroupIncludeCache includeCache = EasyMock.createNiceMock(GroupIncludeCache.class);
+    cache.evict(uuid2);
+    indexer.index(uuid2);
+    includeCache.evictParentGroupsOf(uuid2);
+    cache.evict(uuid3);
+    indexer.index(uuid3);
+    includeCache.evictParentGroupsOf(uuid3);
+    EasyMock.expectLastCall();
+
+    EasyMock.replay(cache, indexer);
+
+    Ref ref1 = createRef(allUsersRepo, RefNames.refsGroups(uuid1));
+    Ref ref2 = createRef(allUsersRepo, RefNames.refsGroups(uuid2));
+    try (ProjectResetter resetProject =
+        builder(null, null, null, cache, includeCache, indexer, null)
+            .build(new ProjectResetter.Config().reset(project).reset(allUsers))) {
+      updateRef(allUsersRepo, ref2);
+      createRef(allUsersRepo, RefNames.refsGroups(uuid3));
+    }
+
+    EasyMock.verify(cache, indexer);
+  }
+
   private Ref createRef(String ref) throws IOException {
     return createRef(repo, ref);
   }
@@ -474,13 +511,16 @@
   }
 
   private ProjectResetter.Builder builder() {
-    return builder(null, null, null, null);
+    return builder(null, null, null, null, null, null, null);
   }
 
   private ProjectResetter.Builder builder(
       @Nullable AccountCreator accountCreator,
       @Nullable AccountCache accountCache,
       @Nullable AccountIndexer accountIndexer,
+      @Nullable GroupCache groupCache,
+      @Nullable GroupIncludeCache groupIncludeCache,
+      @Nullable GroupIndexer groupIndexer,
       @Nullable ProjectCache projectCache) {
     return new ProjectResetter.Builder(
         repoManager,
@@ -488,6 +528,9 @@
         accountCreator,
         accountCache,
         accountIndexer,
+        groupCache,
+        groupIncludeCache,
+        groupIndexer,
         projectCache);
   }
 }