Remove unused GroupRebuilder

GroupRebuilder was rebuilding an entire group's NoteDb refs from group
data read from ReviewDb. Since ReviewDb is gone, we no longer can
rebuild group NoteDb refs.

Change-Id: I00bc26b44e3842d42faedd39d745c9a75bc6d70a
Signed-off-by: Edwin Kempin <ekempin@google.com>
diff --git a/java/com/google/gerrit/server/schema/GroupBundle.java b/java/com/google/gerrit/server/schema/GroupBundle.java
deleted file mode 100644
index 26cd96a..0000000
--- a/java/com/google/gerrit/server/schema/GroupBundle.java
+++ /dev/null
@@ -1,778 +0,0 @@
-// 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.google.gerrit.server.schema;
-
-import static com.google.common.base.Preconditions.checkArgument;
-import static com.google.common.collect.ImmutableList.toImmutableList;
-import static com.google.common.collect.ImmutableSet.toImmutableSet;
-import static com.google.gerrit.reviewdb.server.ReviewDbUtil.checkColumns;
-import static java.util.Comparator.naturalOrder;
-import static java.util.Comparator.nullsLast;
-import static java.util.stream.Collectors.toList;
-
-import com.google.auto.value.AutoValue;
-import com.google.common.base.Strings;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.ListMultimap;
-import com.google.common.collect.MultimapBuilder;
-import com.google.common.collect.Multimaps;
-import com.google.common.collect.Streams;
-import com.google.common.flogger.FluentLogger;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.reviewdb.client.AccountGroupById;
-import com.google.gerrit.reviewdb.client.AccountGroupByIdAud;
-import com.google.gerrit.reviewdb.client.AccountGroupMember;
-import com.google.gerrit.reviewdb.client.AccountGroupMemberAudit;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.reviewdb.server.ReviewDbWrapper;
-import com.google.gerrit.server.group.InternalGroup;
-import com.google.gerrit.server.group.db.AuditLogReader;
-import com.google.gerrit.server.group.db.GroupConfig;
-import com.google.gerrit.server.util.time.TimeUtil;
-import com.google.gwtorm.jdbc.JdbcSchema;
-import com.google.gwtorm.server.OrmException;
-import com.google.inject.Inject;
-import com.google.inject.Singleton;
-import java.io.IOException;
-import java.sql.ResultSet;
-import java.sql.SQLException;
-import java.sql.Statement;
-import java.sql.Timestamp;
-import java.util.ArrayList;
-import java.util.Comparator;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-import java.util.function.Function;
-import java.util.stream.Stream;
-import org.eclipse.jgit.errors.ConfigInvalidException;
-import org.eclipse.jgit.lib.Repository;
-
-/**
- * A bundle of all entities rooted at a single {@link AccountGroup} entity.
- *
- * <p>Used primarily during the migration process. Most callers should prefer {@link InternalGroup}
- * instead.
- */
-@AutoValue
-abstract class GroupBundle {
-  private static final FluentLogger logger = FluentLogger.forEnclosingClass();
-
-  static {
-    // Initialization-time checks that the column set hasn't changed since the
-    // last time this file was updated.
-    checkColumns(AccountGroup.NameKey.class, 1);
-    checkColumns(AccountGroup.UUID.class, 1);
-    checkColumns(AccountGroup.Id.class, 1);
-    checkColumns(AccountGroup.class, 1, 2, 4, 7, 9, 10, 11);
-
-    checkColumns(AccountGroupById.Key.class, 1, 2);
-    checkColumns(AccountGroupById.class, 1);
-
-    checkColumns(AccountGroupByIdAud.Key.class, 1, 2, 3);
-    checkColumns(AccountGroupByIdAud.class, 1, 2, 3, 4);
-
-    checkColumns(AccountGroupMember.Key.class, 1, 2);
-    checkColumns(AccountGroupMember.class, 1);
-
-    checkColumns(AccountGroupMemberAudit.Key.class, 1, 2, 3);
-    checkColumns(AccountGroupMemberAudit.class, 1, 2, 3, 4);
-  }
-
-  public enum Source {
-    REVIEW_DB("ReviewDb"),
-    NOTE_DB("NoteDb");
-
-    private final String name;
-
-    private Source(String name) {
-      this.name = name;
-    }
-
-    @Override
-    public String toString() {
-      return name;
-    }
-  }
-
-  @Singleton
-  public static class Factory {
-    private final AuditLogReader auditLogReader;
-
-    @Inject
-    Factory(AuditLogReader auditLogReader) {
-      this.auditLogReader = auditLogReader;
-    }
-
-    public GroupBundle fromNoteDb(
-        Project.NameKey projectName, Repository repo, AccountGroup.UUID uuid)
-        throws ConfigInvalidException, IOException {
-      GroupConfig groupConfig = GroupConfig.loadForGroup(projectName, repo, uuid);
-      InternalGroup internalGroup = groupConfig.getLoadedGroup().get();
-      AccountGroup.Id groupId = internalGroup.getId();
-
-      AccountGroup accountGroup =
-          new AccountGroup(
-              internalGroup.getNameKey(),
-              internalGroup.getId(),
-              internalGroup.getGroupUUID(),
-              internalGroup.getCreatedOn());
-      accountGroup.setDescription(internalGroup.getDescription());
-      accountGroup.setOwnerGroupUUID(internalGroup.getOwnerGroupUUID());
-      accountGroup.setVisibleToAll(internalGroup.isVisibleToAll());
-
-      return create(
-          Source.NOTE_DB,
-          accountGroup,
-          internalGroup
-              .getMembers()
-              .stream()
-              .map(
-                  accountId ->
-                      new AccountGroupMember(new AccountGroupMember.Key(accountId, groupId)))
-              .collect(toImmutableSet()),
-          auditLogReader.getMembersAudit(repo, uuid),
-          internalGroup
-              .getSubgroups()
-              .stream()
-              .map(
-                  subgroupUuid ->
-                      new AccountGroupById(new AccountGroupById.Key(groupId, subgroupUuid)))
-              .collect(toImmutableSet()),
-          auditLogReader.getSubgroupsAudit(repo, uuid));
-    }
-
-    public static GroupBundle fromReviewDb(ReviewDb db, AccountGroup.UUID groupUuid)
-        throws OrmException {
-      JdbcSchema jdbcSchema = ReviewDbWrapper.unwrapJbdcSchema(db);
-      AccountGroup group = readAccountGroupFromReviewDb(jdbcSchema, groupUuid);
-      AccountGroup.Id groupId = group.getId();
-
-      return create(
-          Source.REVIEW_DB,
-          group,
-          readAccountGroupMembersFromReviewDb(jdbcSchema, groupId),
-          readAccountGroupMemberAuditsFromReviewDb(jdbcSchema, groupId),
-          readAccountGroupSubgroupsFromReviewDb(jdbcSchema, groupId),
-          readAccountGroupSubgroupAuditsFromReviewDb(jdbcSchema, groupId));
-    }
-
-    private static AccountGroup readAccountGroupFromReviewDb(
-        JdbcSchema jdbcSchema, AccountGroup.UUID groupUuid) throws OrmException {
-      try (Statement stmt = jdbcSchema.getConnection().createStatement();
-          ResultSet rs =
-              stmt.executeQuery(
-                  "SELECT group_id,"
-                      + " name,"
-                      + " created_on,"
-                      + " description,"
-                      + " owner_group_uuid,"
-                      + " visible_to_all"
-                      + " FROM account_groups"
-                      + " WHERE group_uuid = '"
-                      + groupUuid.get()
-                      + "'")) {
-        if (!rs.next()) {
-          throw new OrmException(String.format("Group %s not found", groupUuid));
-        }
-
-        AccountGroup.Id groupId = new AccountGroup.Id(rs.getInt(1));
-        AccountGroup.NameKey groupName = new AccountGroup.NameKey(rs.getString(2));
-        Timestamp createdOn = rs.getTimestamp(3);
-        String description = rs.getString(4);
-        AccountGroup.UUID ownerGroupUuid = new AccountGroup.UUID(rs.getString(5));
-        boolean visibleToAll = "Y".equals(rs.getString(6));
-
-        AccountGroup group = new AccountGroup(groupName, groupId, groupUuid, createdOn);
-        group.setDescription(description);
-        group.setOwnerGroupUUID(ownerGroupUuid);
-        group.setVisibleToAll(visibleToAll);
-
-        if (rs.next()) {
-          throw new OrmException(String.format("Group UUID %s is ambiguous", groupUuid));
-        }
-
-        return group;
-      } catch (SQLException e) {
-        throw new OrmException(
-            String.format("Failed to read account group %s from ReviewDb", groupUuid.get()), e);
-      }
-    }
-
-    private static List<AccountGroupMember> readAccountGroupMembersFromReviewDb(
-        JdbcSchema jdbcSchema, AccountGroup.Id groupId) throws OrmException {
-      try (Statement stmt = jdbcSchema.getConnection().createStatement();
-          ResultSet rs =
-              stmt.executeQuery(
-                  "SELECT account_id"
-                      + " FROM account_group_members"
-                      + " WHERE group_id = '"
-                      + groupId.get()
-                      + "'")) {
-        List<AccountGroupMember> members = new ArrayList<>();
-        while (rs.next()) {
-          Account.Id accountId = new Account.Id(rs.getInt(1));
-          members.add(new AccountGroupMember(new AccountGroupMember.Key(accountId, groupId)));
-        }
-        return members;
-      } catch (SQLException e) {
-        throw new OrmException(
-            String.format(
-                "Failed to read members of account group %s from ReviewDb", groupId.get()),
-            e);
-      }
-    }
-
-    private static List<AccountGroupMemberAudit> readAccountGroupMemberAuditsFromReviewDb(
-        JdbcSchema jdbcSchema, AccountGroup.Id groupId) throws OrmException {
-      try (Statement stmt = jdbcSchema.getConnection().createStatement();
-          ResultSet rs =
-              stmt.executeQuery(
-                  "SELECT account_id, added_by, added_on, removed_by, removed_on"
-                      + " FROM account_group_members_audit"
-                      + " WHERE group_id = '"
-                      + groupId.get()
-                      + "'")) {
-        List<AccountGroupMemberAudit> audits = new ArrayList<>();
-        while (rs.next()) {
-          Account.Id accountId = new Account.Id(rs.getInt(1));
-
-          Account.Id addedBy = new Account.Id(rs.getInt(2));
-          Timestamp addedOn = rs.getTimestamp(3);
-
-          Timestamp removedOn = rs.getTimestamp(5);
-          Account.Id removedBy = removedOn != null ? new Account.Id(rs.getInt(4)) : null;
-
-          AccountGroupMemberAudit.Key key =
-              new AccountGroupMemberAudit.Key(accountId, groupId, addedOn);
-          AccountGroupMemberAudit audit = new AccountGroupMemberAudit(key, addedBy);
-          audit.removed(removedBy, removedOn);
-          audits.add(audit);
-        }
-        return audits;
-      } catch (SQLException e) {
-        throw new OrmException(
-            String.format(
-                "Failed to read member audits of account group %s from ReviewDb", groupId.get()),
-            e);
-      }
-    }
-
-    private static List<AccountGroupById> readAccountGroupSubgroupsFromReviewDb(
-        JdbcSchema jdbcSchema, AccountGroup.Id groupId) throws OrmException {
-      try (Statement stmt = jdbcSchema.getConnection().createStatement();
-          ResultSet rs =
-              stmt.executeQuery(
-                  "SELECT include_uuid"
-                      + " FROM account_group_by_id"
-                      + " WHERE group_id = '"
-                      + groupId.get()
-                      + "'")) {
-        List<AccountGroupById> subgroups = new ArrayList<>();
-        while (rs.next()) {
-          AccountGroup.UUID includedGroupUuid = new AccountGroup.UUID(rs.getString(1));
-          subgroups.add(new AccountGroupById(new AccountGroupById.Key(groupId, includedGroupUuid)));
-        }
-        return subgroups;
-      } catch (SQLException e) {
-        throw new OrmException(
-            String.format(
-                "Failed to read subgroups of account group %s from ReviewDb", groupId.get()),
-            e);
-      }
-    }
-
-    private static List<AccountGroupByIdAud> readAccountGroupSubgroupAuditsFromReviewDb(
-        JdbcSchema jdbcSchema, AccountGroup.Id groupId) throws OrmException {
-      try (Statement stmt = jdbcSchema.getConnection().createStatement();
-          ResultSet rs =
-              stmt.executeQuery(
-                  "SELECT include_uuid, added_by, added_on, removed_by, removed_on"
-                      + " FROM account_group_by_id_aud"
-                      + " WHERE group_id = '"
-                      + groupId.get()
-                      + "'")) {
-        List<AccountGroupByIdAud> audits = new ArrayList<>();
-        while (rs.next()) {
-          AccountGroup.UUID includedGroupUuid = new AccountGroup.UUID(rs.getString(1));
-
-          Account.Id addedBy = new Account.Id(rs.getInt(2));
-          Timestamp addedOn = rs.getTimestamp(3);
-
-          Timestamp removedOn = rs.getTimestamp(5);
-          Account.Id removedBy = removedOn != null ? new Account.Id(rs.getInt(4)) : null;
-
-          AccountGroupByIdAud.Key key =
-              new AccountGroupByIdAud.Key(groupId, includedGroupUuid, addedOn);
-          AccountGroupByIdAud audit = new AccountGroupByIdAud(key, addedBy);
-          audit.removed(removedBy, removedOn);
-          audits.add(audit);
-        }
-        return audits;
-      } catch (SQLException e) {
-        throw new OrmException(
-            String.format(
-                "Failed to read subgroup audits of account group %s from ReviewDb", groupId.get()),
-            e);
-      }
-    }
-  }
-
-  private static final Comparator<AccountGroupMember> ACCOUNT_GROUP_MEMBER_COMPARATOR =
-      Comparator.comparingInt((AccountGroupMember m) -> m.getAccountGroupId().get())
-          .thenComparingInt(m -> m.getAccountId().get());
-
-  private static final Comparator<AccountGroupMemberAudit> ACCOUNT_GROUP_MEMBER_AUDIT_COMPARATOR =
-      Comparator.comparingInt((AccountGroupMemberAudit a) -> a.getGroupId().get())
-          .thenComparing(AccountGroupMemberAudit::getAddedOn)
-          .thenComparingInt(a -> a.getAddedBy().get())
-          .thenComparingInt(a -> a.getMemberId().get())
-          .thenComparing(
-              a -> a.getRemovedBy() != null ? a.getRemovedBy().get() : null,
-              nullsLast(naturalOrder()))
-          .thenComparing(AccountGroupMemberAudit::getRemovedOn, nullsLast(naturalOrder()));
-
-  private static final Comparator<AccountGroupById> ACCOUNT_GROUP_BY_ID_COMPARATOR =
-      Comparator.comparingInt((AccountGroupById m) -> m.getGroupId().get())
-          .thenComparing(AccountGroupById::getIncludeUUID);
-
-  private static final Comparator<AccountGroupByIdAud> ACCOUNT_GROUP_BY_ID_AUD_COMPARATOR =
-      Comparator.comparingInt((AccountGroupByIdAud a) -> a.getGroupId().get())
-          .thenComparing(AccountGroupByIdAud::getAddedOn)
-          .thenComparingInt(a -> a.getAddedBy().get())
-          .thenComparing(AccountGroupByIdAud::getIncludeUUID)
-          .thenComparing(
-              a -> a.getRemovedBy() != null ? a.getRemovedBy().get() : null,
-              nullsLast(naturalOrder()))
-          .thenComparing(AccountGroupByIdAud::getRemovedOn, nullsLast(naturalOrder()));
-
-  private static final Comparator<AuditEntry> AUDIT_ENTRY_COMPARATOR =
-      Comparator.comparing(AuditEntry::getTimestamp)
-          .thenComparing(AuditEntry::getAction, Comparator.comparingInt(Action::getOrder));
-
-  public static GroupBundle create(
-      Source source,
-      AccountGroup group,
-      Iterable<AccountGroupMember> members,
-      Iterable<AccountGroupMemberAudit> memberAudit,
-      Iterable<AccountGroupById> byId,
-      Iterable<AccountGroupByIdAud> byIdAudit) {
-    AccountGroup.UUID uuid = group.getGroupUUID();
-    return new AutoValue_GroupBundle.Builder()
-        .source(source)
-        .group(group)
-        .members(
-            logIfNotUnique(
-                source, uuid, members, ACCOUNT_GROUP_MEMBER_COMPARATOR, AccountGroupMember.class))
-        .memberAudit(
-            logIfNotUnique(
-                source,
-                uuid,
-                memberAudit,
-                ACCOUNT_GROUP_MEMBER_AUDIT_COMPARATOR,
-                AccountGroupMemberAudit.class))
-        .byId(
-            logIfNotUnique(
-                source, uuid, byId, ACCOUNT_GROUP_BY_ID_COMPARATOR, AccountGroupById.class))
-        .byIdAudit(
-            logIfNotUnique(
-                source,
-                uuid,
-                byIdAudit,
-                ACCOUNT_GROUP_BY_ID_AUD_COMPARATOR,
-                AccountGroupByIdAud.class))
-        .build();
-  }
-
-  private static <T> ImmutableSet<T> logIfNotUnique(
-      Source source,
-      AccountGroup.UUID uuid,
-      Iterable<T> iterable,
-      Comparator<T> comparator,
-      Class<T> clazz) {
-    List<T> list = Streams.stream(iterable).sorted(comparator).collect(toList());
-    ImmutableSet<T> set = ImmutableSet.copyOf(list);
-    if (set.size() != list.size()) {
-      // One way this can happen is that distinct audit entities can compare equal, because
-      // AccountGroup{MemberAudit,ByIdAud}.Key does not include the addedOn timestamp in its
-      // members() list. However, this particular issue only applies to pure adds, since removedOn
-      // *is* included in equality. As a result, if this happens, it means the audit log is already
-      // corrupt, and it's not clear if we can programmatically repair it. For migrating to NoteDb,
-      // we'll try our best to recreate it, but no guarantees it will match the real sequence of
-      // attempted operations, which is in any case lost in the mists of time.
-      logger.atWarning().log(
-          "group %s in %s has duplicate %s entities: %s",
-          uuid, source, clazz.getSimpleName(), iterable);
-    }
-    return set;
-  }
-
-  static Builder builder() {
-    return new AutoValue_GroupBundle.Builder().members().memberAudit().byId().byIdAudit();
-  }
-
-  public static ImmutableList<String> compareWithAudits(
-      GroupBundle reviewDbBundle, GroupBundle noteDbBundle) {
-    return compare(reviewDbBundle, noteDbBundle, true);
-  }
-
-  public static ImmutableList<String> compareWithoutAudits(
-      GroupBundle reviewDbBundle, GroupBundle noteDbBundle) {
-    return compare(reviewDbBundle, noteDbBundle, false);
-  }
-
-  private static ImmutableList<String> compare(
-      GroupBundle reviewDbBundle, GroupBundle noteDbBundle, boolean compareAudits) {
-    // Normalize the ReviewDb bundle to what we expect in NoteDb. This means that values in error
-    // messages will not reflect the actual data in ReviewDb, but it will make it easier for humans
-    // to see the difference.
-    reviewDbBundle = reviewDbBundle.truncateToSecond();
-    AccountGroup reviewDbGroup = new AccountGroup(reviewDbBundle.group());
-    reviewDbGroup.setDescription(Strings.emptyToNull(reviewDbGroup.getDescription()));
-    reviewDbBundle = reviewDbBundle.toBuilder().group(reviewDbGroup).build();
-
-    checkArgument(
-        reviewDbBundle.source() == Source.REVIEW_DB,
-        "first bundle's source must be %s: %s",
-        Source.REVIEW_DB,
-        reviewDbBundle);
-    checkArgument(
-        noteDbBundle.source() == Source.NOTE_DB,
-        "second bundle's source must be %s: %s",
-        Source.NOTE_DB,
-        noteDbBundle);
-
-    ImmutableList.Builder<String> result = ImmutableList.builder();
-    if (!reviewDbBundle.group().equals(noteDbBundle.group())) {
-      result.add(
-          "AccountGroups differ\n"
-              + ("ReviewDb: " + reviewDbBundle.group() + "\n")
-              + ("NoteDb  : " + noteDbBundle.group()));
-    }
-    if (!reviewDbBundle.members().equals(noteDbBundle.members())) {
-      result.add(
-          "AccountGroupMembers differ\n"
-              + ("ReviewDb: " + reviewDbBundle.members() + "\n")
-              + ("NoteDb  : " + noteDbBundle.members()));
-    }
-    if (compareAudits
-        && !areMemberAuditsConsideredEqual(
-            reviewDbBundle.memberAudit(), noteDbBundle.memberAudit())) {
-      result.add(
-          "AccountGroupMemberAudits differ\n"
-              + ("ReviewDb: " + reviewDbBundle.memberAudit() + "\n")
-              + ("NoteDb  : " + noteDbBundle.memberAudit()));
-    }
-    if (!reviewDbBundle.byId().equals(noteDbBundle.byId())) {
-      result.add(
-          "AccountGroupByIds differ\n"
-              + ("ReviewDb: " + reviewDbBundle.byId() + "\n")
-              + ("NoteDb  : " + noteDbBundle.byId()));
-    }
-    if (compareAudits
-        && !areByIdAuditsConsideredEqual(reviewDbBundle.byIdAudit(), noteDbBundle.byIdAudit())) {
-      result.add(
-          "AccountGroupByIdAudits differ\n"
-              + ("ReviewDb: " + reviewDbBundle.byIdAudit() + "\n")
-              + ("NoteDb  : " + noteDbBundle.byIdAudit()));
-    }
-    return result.build();
-  }
-
-  private static boolean areMemberAuditsConsideredEqual(
-      ImmutableSet<AccountGroupMemberAudit> reviewDbMemberAudits,
-      ImmutableSet<AccountGroupMemberAudit> noteDbMemberAudits) {
-    ListMultimap<String, AuditEntry> reviewDbMemberAuditsByMemberId =
-        toMemberAuditEntriesByMemberId(reviewDbMemberAudits);
-    ListMultimap<String, AuditEntry> noteDbMemberAuditsByMemberId =
-        toMemberAuditEntriesByMemberId(noteDbMemberAudits);
-
-    return areConsideredEqual(reviewDbMemberAuditsByMemberId, noteDbMemberAuditsByMemberId);
-  }
-
-  private static boolean areByIdAuditsConsideredEqual(
-      ImmutableSet<AccountGroupByIdAud> reviewDbByIdAudits,
-      ImmutableSet<AccountGroupByIdAud> noteDbByIdAudits) {
-    ListMultimap<String, AuditEntry> reviewDbByIdAuditsById =
-        toByIdAuditEntriesById(reviewDbByIdAudits);
-    ListMultimap<String, AuditEntry> noteDbByIdAuditsById =
-        toByIdAuditEntriesById(noteDbByIdAudits);
-
-    return areConsideredEqual(reviewDbByIdAuditsById, noteDbByIdAuditsById);
-  }
-
-  private static ListMultimap<String, AuditEntry> toMemberAuditEntriesByMemberId(
-      ImmutableSet<AccountGroupMemberAudit> memberAudits) {
-    return memberAudits
-        .stream()
-        .flatMap(GroupBundle::toAuditEntries)
-        .collect(
-            Multimaps.toMultimap(
-                AuditEntry::getTarget,
-                Function.identity(),
-                MultimapBuilder.hashKeys().arrayListValues()::build));
-  }
-
-  private static Stream<AuditEntry> toAuditEntries(AccountGroupMemberAudit memberAudit) {
-    AuditEntry additionAuditEntry =
-        AuditEntry.create(
-            Action.ADD,
-            memberAudit.getAddedBy(),
-            memberAudit.getMemberId(),
-            memberAudit.getAddedOn());
-    if (memberAudit.isActive()) {
-      return Stream.of(additionAuditEntry);
-    }
-
-    AuditEntry removalAuditEntry =
-        AuditEntry.create(
-            Action.REMOVE,
-            memberAudit.getRemovedBy(),
-            memberAudit.getMemberId(),
-            memberAudit.getRemovedOn());
-    return Stream.of(additionAuditEntry, removalAuditEntry);
-  }
-
-  private static ListMultimap<String, AuditEntry> toByIdAuditEntriesById(
-      ImmutableSet<AccountGroupByIdAud> byIdAudits) {
-    return byIdAudits
-        .stream()
-        .flatMap(GroupBundle::toAuditEntries)
-        .collect(
-            Multimaps.toMultimap(
-                AuditEntry::getTarget,
-                Function.identity(),
-                MultimapBuilder.hashKeys().arrayListValues()::build));
-  }
-
-  private static Stream<AuditEntry> toAuditEntries(AccountGroupByIdAud byIdAudit) {
-    AuditEntry additionAuditEntry =
-        AuditEntry.create(
-            Action.ADD, byIdAudit.getAddedBy(), byIdAudit.getIncludeUUID(), byIdAudit.getAddedOn());
-    if (byIdAudit.isActive()) {
-      return Stream.of(additionAuditEntry);
-    }
-
-    AuditEntry removalAuditEntry =
-        AuditEntry.create(
-            Action.REMOVE,
-            byIdAudit.getRemovedBy(),
-            byIdAudit.getIncludeUUID(),
-            byIdAudit.getRemovedOn());
-    return Stream.of(additionAuditEntry, removalAuditEntry);
-  }
-
-  /**
-   * Determines whether the audit log entries are equal except for redundant entries. Entries of the
-   * same type (addition/removal) which follow directly on each other according to their timestamp
-   * are considered redundant.
-   */
-  private static boolean areConsideredEqual(
-      ListMultimap<String, AuditEntry> reviewDbMemberAuditsByTarget,
-      ListMultimap<String, AuditEntry> noteDbMemberAuditsByTarget) {
-    for (String target : reviewDbMemberAuditsByTarget.keySet()) {
-      ImmutableList<AuditEntry> reviewDbAuditEntries =
-          reviewDbMemberAuditsByTarget
-              .get(target)
-              .stream()
-              .sorted(AUDIT_ENTRY_COMPARATOR)
-              .collect(toImmutableList());
-      ImmutableSet<AuditEntry> noteDbAuditEntries =
-          noteDbMemberAuditsByTarget
-              .get(target)
-              .stream()
-              .sorted(AUDIT_ENTRY_COMPARATOR)
-              .collect(toImmutableSet());
-
-      int reviewDbIndex = 0;
-      for (AuditEntry noteDbAuditEntry : noteDbAuditEntries) {
-        Set<AuditEntry> redundantReviewDbAuditEntries = new HashSet<>();
-        while (reviewDbIndex < reviewDbAuditEntries.size()) {
-          AuditEntry reviewDbAuditEntry = reviewDbAuditEntries.get(reviewDbIndex);
-          if (!reviewDbAuditEntry.getAction().equals(noteDbAuditEntry.getAction())) {
-            break;
-          }
-          redundantReviewDbAuditEntries.add(reviewDbAuditEntry);
-          reviewDbIndex++;
-        }
-
-        // The order of the entries is not perfect as ReviewDb included milliseconds for timestamps
-        // and we cut off everything below seconds due to NoteDb/git. Consequently, we don't have a
-        // way to know in this method in which exact order additions/removals within the same second
-        // happened. The best we can do is to group all additions within the same second as
-        // redundant entries and the removals afterward. To compensate that we possibly group
-        // non-redundant additions/removals, we also accept NoteDb audit entries which just occur
-        // anywhere as ReviewDb audit entries.
-        if (!redundantReviewDbAuditEntries.contains(noteDbAuditEntry)
-            && !reviewDbAuditEntries.contains(noteDbAuditEntry)) {
-          return false;
-        }
-      }
-
-      if (reviewDbIndex < reviewDbAuditEntries.size()) {
-        // Some of the ReviewDb audit log entries aren't matched by NoteDb audit log entries.
-        return false;
-      }
-    }
-    return true;
-  }
-
-  public AccountGroup.Id id() {
-    return group().getId();
-  }
-
-  public AccountGroup.UUID uuid() {
-    return group().getGroupUUID();
-  }
-
-  public abstract Source source();
-
-  public abstract AccountGroup group();
-
-  public abstract ImmutableSet<AccountGroupMember> members();
-
-  public abstract ImmutableSet<AccountGroupMemberAudit> memberAudit();
-
-  public abstract ImmutableSet<AccountGroupById> byId();
-
-  public abstract ImmutableSet<AccountGroupByIdAud> byIdAudit();
-
-  public abstract Builder toBuilder();
-
-  public GroupBundle truncateToSecond() {
-    AccountGroup newGroup = new AccountGroup(group());
-    if (newGroup.getCreatedOn() != null) {
-      newGroup.setCreatedOn(TimeUtil.truncateToSecond(newGroup.getCreatedOn()));
-    }
-    return toBuilder()
-        .group(newGroup)
-        .memberAudit(
-            memberAudit().stream().map(GroupBundle::truncateToSecond).collect(toImmutableSet()))
-        .byIdAudit(
-            byIdAudit().stream().map(GroupBundle::truncateToSecond).collect(toImmutableSet()))
-        .build();
-  }
-
-  private static AccountGroupMemberAudit truncateToSecond(AccountGroupMemberAudit a) {
-    AccountGroupMemberAudit result =
-        new AccountGroupMemberAudit(
-            new AccountGroupMemberAudit.Key(
-                a.getKey().getParentKey(),
-                a.getKey().getGroupId(),
-                TimeUtil.truncateToSecond(a.getKey().getAddedOn())),
-            a.getAddedBy());
-    if (a.getRemovedOn() != null) {
-      result.removed(a.getRemovedBy(), TimeUtil.truncateToSecond(a.getRemovedOn()));
-    }
-    return result;
-  }
-
-  private static AccountGroupByIdAud truncateToSecond(AccountGroupByIdAud a) {
-    AccountGroupByIdAud result =
-        new AccountGroupByIdAud(
-            new AccountGroupByIdAud.Key(
-                a.getKey().getParentKey(),
-                a.getKey().getIncludeUUID(),
-                TimeUtil.truncateToSecond(a.getKey().getAddedOn())),
-            a.getAddedBy());
-    if (a.getRemovedOn() != null) {
-      result.removed(a.getRemovedBy(), TimeUtil.truncateToSecond(a.getRemovedOn()));
-    }
-    return result;
-  }
-
-  public InternalGroup toInternalGroup() {
-    return InternalGroup.create(
-        group(),
-        members().stream().map(AccountGroupMember::getAccountId).collect(toImmutableSet()),
-        byId().stream().map(AccountGroupById::getIncludeUUID).collect(toImmutableSet()));
-  }
-
-  @Override
-  public int hashCode() {
-    throw new UnsupportedOperationException(
-        "hashCode is not supported because equals is not supported");
-  }
-
-  @Override
-  public boolean equals(Object o) {
-    throw new UnsupportedOperationException("Use GroupBundle.compare(a, b) instead of equals");
-  }
-
-  @AutoValue
-  abstract static class AuditEntry {
-    private static AuditEntry create(
-        Action action, Account.Id userId, Account.Id memberId, Timestamp timestamp) {
-      return new AutoValue_GroupBundle_AuditEntry(
-          action, userId, String.valueOf(memberId.get()), timestamp);
-    }
-
-    private static AuditEntry create(
-        Action action, Account.Id userId, AccountGroup.UUID subgroupId, Timestamp timestamp) {
-      return new AutoValue_GroupBundle_AuditEntry(action, userId, subgroupId.get(), timestamp);
-    }
-
-    abstract Action getAction();
-
-    abstract Account.Id getUserId();
-
-    abstract String getTarget();
-
-    abstract Timestamp getTimestamp();
-  }
-
-  enum Action {
-    ADD(1),
-    REMOVE(2);
-
-    private final int order;
-
-    Action(int order) {
-      this.order = order;
-    }
-
-    public int getOrder() {
-      return order;
-    }
-  }
-
-  @AutoValue.Builder
-  abstract static class Builder {
-    abstract Builder source(Source source);
-
-    abstract Builder group(AccountGroup group);
-
-    abstract Builder members(AccountGroupMember... member);
-
-    abstract Builder members(Iterable<AccountGroupMember> member);
-
-    abstract Builder memberAudit(AccountGroupMemberAudit... audit);
-
-    abstract Builder memberAudit(Iterable<AccountGroupMemberAudit> audit);
-
-    abstract Builder byId(AccountGroupById... byId);
-
-    abstract Builder byId(Iterable<AccountGroupById> byId);
-
-    abstract Builder byIdAudit(AccountGroupByIdAud... audit);
-
-    abstract Builder byIdAudit(Iterable<AccountGroupByIdAud> audit);
-
-    abstract GroupBundle build();
-  }
-}
diff --git a/java/com/google/gerrit/server/schema/GroupRebuilder.java b/java/com/google/gerrit/server/schema/GroupRebuilder.java
deleted file mode 100644
index 0157025a..0000000
--- a/java/com/google/gerrit/server/schema/GroupRebuilder.java
+++ /dev/null
@@ -1,303 +0,0 @@
-// 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.google.gerrit.server.schema;
-
-import static com.google.common.base.Preconditions.checkArgument;
-import static com.google.common.base.Preconditions.checkState;
-import static com.google.common.collect.ImmutableSet.toImmutableSet;
-
-import com.google.auto.value.AutoValue;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Iterables;
-import com.google.common.collect.ListMultimap;
-import com.google.common.collect.MultimapBuilder;
-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.AccountGroupById;
-import com.google.gerrit.reviewdb.client.AccountGroupByIdAud;
-import com.google.gerrit.reviewdb.client.AccountGroupMember;
-import com.google.gerrit.reviewdb.client.AccountGroupMemberAudit;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.server.config.AllUsersName;
-import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
-import com.google.gerrit.server.git.meta.MetaDataUpdate;
-import com.google.gerrit.server.git.meta.VersionedMetaData.BatchMetaDataUpdate;
-import com.google.gerrit.server.group.db.AuditLogFormatter;
-import com.google.gerrit.server.group.db.GroupConfig;
-import com.google.gerrit.server.group.db.InternalGroupCreation;
-import com.google.gerrit.server.group.db.InternalGroupUpdate;
-import com.google.gerrit.server.group.db.InternalGroupUpdate.MemberModification;
-import com.google.gerrit.server.group.db.InternalGroupUpdate.SubgroupModification;
-import com.google.gerrit.server.util.time.TimeUtil;
-import com.google.gwtorm.server.OrmDuplicateKeyException;
-import java.io.IOException;
-import java.sql.Timestamp;
-import java.util.Collection;
-import java.util.Comparator;
-import java.util.Map;
-import java.util.NavigableSet;
-import java.util.Optional;
-import java.util.function.Consumer;
-import java.util.stream.Stream;
-import org.eclipse.jgit.errors.ConfigInvalidException;
-import org.eclipse.jgit.lib.BatchRefUpdate;
-import org.eclipse.jgit.lib.CommitBuilder;
-import org.eclipse.jgit.lib.PersonIdent;
-import org.eclipse.jgit.lib.Repository;
-
-/** Helper for rebuilding an entire group's NoteDb refs. */
-class GroupRebuilder {
-  private final PersonIdent serverIdent;
-  private final AllUsersName allUsers;
-  private final AuditLogFormatter auditLogFormatter;
-
-  public GroupRebuilder(
-      PersonIdent serverIdent, AllUsersName allUsers, AuditLogFormatter auditLogFormatter) {
-    this.serverIdent = serverIdent;
-    this.allUsers = allUsers;
-    this.auditLogFormatter = auditLogFormatter;
-  }
-
-  public void rebuild(Repository allUsersRepo, GroupBundle bundle, @Nullable BatchRefUpdate bru)
-      throws IOException, ConfigInvalidException, OrmDuplicateKeyException {
-    AccountGroup group = bundle.group();
-    InternalGroupCreation groupCreation =
-        InternalGroupCreation.builder()
-            .setId(bundle.id())
-            .setNameKey(group.getNameKey())
-            .setGroupUUID(group.getGroupUUID())
-            .build();
-    GroupConfig groupConfig = GroupConfig.createForNewGroup(allUsers, allUsersRepo, groupCreation);
-    groupConfig.setAllowSaveEmptyName();
-
-    InternalGroupUpdate.Builder updateBuilder =
-        InternalGroupUpdate.builder()
-            .setOwnerGroupUUID(group.getOwnerGroupUUID())
-            .setVisibleToAll(group.isVisibleToAll())
-            .setUpdatedOn(group.getCreatedOn());
-    if (bundle.group().getDescription() != null) {
-      updateBuilder.setDescription(group.getDescription());
-    }
-    groupConfig.setGroupUpdate(updateBuilder.build(), auditLogFormatter);
-
-    Map<Key, Collection<Event>> events = toEvents(bundle).asMap();
-    PersonIdent nowServerIdent = getServerIdent(events);
-
-    MetaDataUpdate md = createMetaDataUpdate(allUsers, allUsersRepo, bru);
-
-    // Creation is done by the server (unlike later audit events).
-    PersonIdent created = new PersonIdent(nowServerIdent, group.getCreatedOn());
-    md.getCommitBuilder().setAuthor(created);
-    md.getCommitBuilder().setCommitter(created);
-
-    // Rebuild group ref.
-    try (BatchMetaDataUpdate batch = groupConfig.openUpdate(md)) {
-      batch.write(groupConfig, md.getCommitBuilder());
-
-      for (Map.Entry<Key, Collection<Event>> e : events.entrySet()) {
-        InternalGroupUpdate.Builder ub = InternalGroupUpdate.builder();
-        e.getValue().forEach(event -> event.update().accept(ub));
-        ub.setUpdatedOn(e.getKey().when());
-        groupConfig.setGroupUpdate(ub.build(), auditLogFormatter);
-
-        PersonIdent currServerIdent = new PersonIdent(nowServerIdent, e.getKey().when());
-        CommitBuilder cb = new CommitBuilder();
-        cb.setAuthor(
-            e.getKey()
-                .accountId()
-                .map(id -> auditLogFormatter.getParsableAuthorIdent(id, currServerIdent))
-                .orElse(currServerIdent));
-        cb.setCommitter(currServerIdent);
-        batch.write(groupConfig, cb);
-      }
-
-      batch.createRef(groupConfig.getRefName());
-    }
-  }
-
-  private ListMultimap<Key, Event> toEvents(GroupBundle bundle) {
-    ListMultimap<Key, Event> result =
-        MultimapBuilder.treeKeys(Key.COMPARATOR).arrayListValues(1).build();
-    Event e;
-
-    for (AccountGroupMemberAudit a : bundle.memberAudit()) {
-      checkArgument(
-          a.getKey().getGroupId().equals(bundle.id()),
-          "key %s does not match group %s",
-          a.getKey(),
-          bundle.id());
-      Account.Id accountId = a.getKey().getParentKey();
-      e = event(Type.ADD_MEMBER, a.getAddedBy(), a.getKey().getAddedOn(), addMember(accountId));
-      result.put(e.key(), e);
-      if (!a.isActive()) {
-        e = event(Type.REMOVE_MEMBER, a.getRemovedBy(), a.getRemovedOn(), removeMember(accountId));
-        result.put(e.key(), e);
-      }
-    }
-
-    for (AccountGroupByIdAud a : bundle.byIdAudit()) {
-      checkArgument(
-          a.getKey().getParentKey().equals(bundle.id()),
-          "key %s does not match group %s",
-          a.getKey(),
-          bundle.id());
-      AccountGroup.UUID uuid = a.getKey().getIncludeUUID();
-      e = event(Type.ADD_GROUP, a.getAddedBy(), a.getKey().getAddedOn(), addGroup(uuid));
-      result.put(e.key(), e);
-      if (!a.isActive()) {
-        e = event(Type.REMOVE_GROUP, a.getRemovedBy(), a.getRemovedOn(), removeGroup(uuid));
-        result.put(e.key(), e);
-      }
-    }
-
-    // Due to clock skew, audit events may be in the future relative to this machine. Ensure the
-    // fixup event happens after any other events, both for the purposes of sorting Keys correctly
-    // and to avoid non-monotonic timestamps in the commit history.
-    Timestamp maxTs =
-        Stream.concat(result.keySet().stream().map(Key::when), Stream.of(TimeUtil.nowTs()))
-            .max(Comparator.naturalOrder())
-            .get();
-    Timestamp fixupTs = new Timestamp(maxTs.getTime() + 1);
-    e = serverEvent(Type.FIXUP, fixupTs, setCurrentMembership(bundle));
-    result.put(e.key(), e);
-
-    return result;
-  }
-
-  private PersonIdent getServerIdent(Map<Key, Collection<Event>> events) {
-    // Created with MultimapBuilder.treeKeys, so the keySet is navigable.
-    Key lastKey = ((NavigableSet<Key>) events.keySet()).last();
-    checkState(lastKey.type() == Type.FIXUP);
-    return new PersonIdent(
-        serverIdent.getName(),
-        serverIdent.getEmailAddress(),
-        Iterables.getOnlyElement(events.get(lastKey)).when(),
-        serverIdent.getTimeZone());
-  }
-
-  private static MetaDataUpdate createMetaDataUpdate(
-      Project.NameKey projectName, Repository repository, @Nullable BatchRefUpdate batchRefUpdate) {
-    return new MetaDataUpdate(
-        GitReferenceUpdated.DISABLED, projectName, repository, batchRefUpdate);
-  }
-
-  private static Consumer<InternalGroupUpdate.Builder> addMember(Account.Id toAdd) {
-    return b -> {
-      MemberModification prev = b.getMemberModification();
-      b.setMemberModification(in -> Sets.union(prev.apply(in), ImmutableSet.of(toAdd)));
-    };
-  }
-
-  private static Consumer<InternalGroupUpdate.Builder> removeMember(Account.Id toRemove) {
-    return b -> {
-      MemberModification prev = b.getMemberModification();
-      b.setMemberModification(in -> Sets.difference(prev.apply(in), ImmutableSet.of(toRemove)));
-    };
-  }
-
-  private static Consumer<InternalGroupUpdate.Builder> addGroup(AccountGroup.UUID toAdd) {
-    return b -> {
-      SubgroupModification prev = b.getSubgroupModification();
-      b.setSubgroupModification(in -> Sets.union(prev.apply(in), ImmutableSet.of(toAdd)));
-    };
-  }
-
-  private static Consumer<InternalGroupUpdate.Builder> removeGroup(AccountGroup.UUID toRemove) {
-    return b -> {
-      SubgroupModification prev = b.getSubgroupModification();
-      b.setSubgroupModification(in -> Sets.difference(prev.apply(in), ImmutableSet.of(toRemove)));
-    };
-  }
-
-  private static Consumer<InternalGroupUpdate.Builder> setCurrentMembership(GroupBundle bundle) {
-    // Overwrite members and subgroups with the current values. The storage layer will do the
-    // set differences to compute the appropriate delta, if any.
-    return b ->
-        b.setMemberModification(
-                in ->
-                    bundle
-                        .members()
-                        .stream()
-                        .map(AccountGroupMember::getAccountId)
-                        .collect(toImmutableSet()))
-            .setSubgroupModification(
-                in ->
-                    bundle
-                        .byId()
-                        .stream()
-                        .map(AccountGroupById::getIncludeUUID)
-                        .collect(toImmutableSet()));
-  }
-
-  private static Event event(
-      Type type,
-      Account.Id accountId,
-      Timestamp when,
-      Consumer<InternalGroupUpdate.Builder> update) {
-    return new AutoValue_GroupRebuilder_Event(type, Optional.of(accountId), when, update);
-  }
-
-  private static Event serverEvent(
-      Type type, Timestamp when, Consumer<InternalGroupUpdate.Builder> update) {
-    return new AutoValue_GroupRebuilder_Event(type, Optional.empty(), when, update);
-  }
-
-  @AutoValue
-  abstract static class Event {
-    abstract Type type();
-
-    abstract Optional<Account.Id> accountId();
-
-    abstract Timestamp when();
-
-    abstract Consumer<InternalGroupUpdate.Builder> update();
-
-    Key key() {
-      return new AutoValue_GroupRebuilder_Key(accountId(), when(), type());
-    }
-  }
-
-  /**
-   * Distinct event types.
-   *
-   * <p>Events at the same time by the same user are batched together by type. The types should
-   * correspond to the possible batch operations supported by AuditService.
-   */
-  enum Type {
-    ADD_MEMBER,
-    REMOVE_MEMBER,
-    ADD_GROUP,
-    REMOVE_GROUP,
-    FIXUP;
-  }
-
-  @AutoValue
-  abstract static class Key {
-    static final Comparator<Key> COMPARATOR =
-        Comparator.comparing(Key::when)
-            .thenComparing(
-                k -> k.accountId().map(Account.Id::get).orElse(null),
-                Comparator.nullsFirst(Comparator.naturalOrder()))
-            .thenComparing(Key::type);
-
-    abstract Optional<Account.Id> accountId();
-
-    abstract Timestamp when();
-
-    abstract Type type();
-  }
-}
diff --git a/javatests/com/google/gerrit/server/schema/GroupBundleTest.java b/javatests/com/google/gerrit/server/schema/GroupBundleTest.java
deleted file mode 100644
index c1de3a3..0000000
--- a/javatests/com/google/gerrit/server/schema/GroupBundleTest.java
+++ /dev/null
@@ -1,146 +0,0 @@
-// 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.google.gerrit.server.schema;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.reviewdb.client.AccountGroupById;
-import com.google.gerrit.reviewdb.client.AccountGroupByIdAud;
-import com.google.gerrit.reviewdb.client.AccountGroupMember;
-import com.google.gerrit.reviewdb.client.AccountGroupMemberAudit;
-import com.google.gerrit.server.schema.GroupBundle.Source;
-import com.google.gerrit.server.util.time.TimeUtil;
-import com.google.gerrit.testing.GerritBaseTests;
-import com.google.gerrit.testing.TestTimeUtil;
-import java.sql.Timestamp;
-import java.util.TimeZone;
-import java.util.concurrent.TimeUnit;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-
-public class GroupBundleTest extends GerritBaseTests {
-  // This class just contains sanity checks that GroupBundle#compare correctly compares all parts of
-  // the bundle. Most other test coverage should come via the slightly more realistic
-  // GroupRebuilderTest.
-
-  private static final String TIMEZONE_ID = "US/Eastern";
-
-  private String systemTimeZoneProperty;
-  private TimeZone systemTimeZone;
-  private Timestamp ts;
-
-  @Before
-  public void setUp() {
-    systemTimeZoneProperty = System.setProperty("user.timezone", TIMEZONE_ID);
-    systemTimeZone = TimeZone.getDefault();
-    TimeZone.setDefault(TimeZone.getTimeZone(TIMEZONE_ID));
-    TestTimeUtil.resetWithClockStep(1, TimeUnit.SECONDS);
-    ts = TimeUtil.nowTs();
-  }
-
-  @After
-  public void tearDown() {
-    TestTimeUtil.useSystemTime();
-    System.setProperty("user.timezone", systemTimeZoneProperty);
-    TimeZone.setDefault(systemTimeZone);
-  }
-
-  @Test
-  public void compareNonEqual() throws Exception {
-    GroupBundle reviewDbBundle = newBundle().source(Source.REVIEW_DB).build();
-    AccountGroup g2 = new AccountGroup(reviewDbBundle.group());
-    g2.setDescription("Hello!");
-    GroupBundle noteDbBundle = GroupBundle.builder().source(Source.NOTE_DB).group(g2).build();
-    assertThat(GroupBundle.compareWithAudits(reviewDbBundle, noteDbBundle))
-        .containsExactly(
-            "AccountGroups differ\n"
-                + ("ReviewDb: AccountGroup{name=group, groupId=1, description=null,"
-                    + " visibleToAll=false, groupUUID=group-1, ownerGroupUUID=group-1,"
-                    + " createdOn=2009-09-30 17:00:00.0}\n")
-                + ("NoteDb  : AccountGroup{name=group, groupId=1, description=Hello!,"
-                    + " visibleToAll=false, groupUUID=group-1, ownerGroupUUID=group-1,"
-                    + " createdOn=2009-09-30 17:00:00.0}"),
-            "AccountGroupMembers differ\n"
-                + "ReviewDb: [AccountGroupMember{key=1000,1}]\n"
-                + "NoteDb  : []",
-            "AccountGroupMemberAudits differ\n"
-                + ("ReviewDb: [AccountGroupMemberAudit{key=Key{groupId=1, accountId=1000,"
-                    + " addedOn=2009-09-30 17:00:00.0}, addedBy=2000, removedBy=null,"
-                    + " removedOn=null}]\n")
-                + "NoteDb  : []",
-            "AccountGroupByIds differ\n"
-                + "ReviewDb: [AccountGroupById{key=1,subgroup}]\n"
-                + "NoteDb  : []",
-            "AccountGroupByIdAudits differ\n"
-                + ("ReviewDb: [AccountGroupByIdAud{key=Key{groupId=1, includeUUID=subgroup,"
-                    + " addedOn=2009-09-30 17:00:00.0}, addedBy=3000, removedBy=null,"
-                    + " removedOn=null}]\n")
-                + "NoteDb  : []");
-  }
-
-  @Test
-  public void compareIgnoreAudits() throws Exception {
-    GroupBundle reviewDbBundle = newBundle().source(Source.REVIEW_DB).build();
-    AccountGroup group = new AccountGroup(reviewDbBundle.group());
-
-    AccountGroupMember member =
-        new AccountGroupMember(new AccountGroupMember.Key(new Account.Id(1), group.getId()));
-    AccountGroupMemberAudit memberAudit =
-        new AccountGroupMemberAudit(member, new Account.Id(2), ts);
-    AccountGroupById byId =
-        new AccountGroupById(
-            new AccountGroupById.Key(group.getId(), new AccountGroup.UUID("subgroup-2")));
-    AccountGroupByIdAud byIdAudit = new AccountGroupByIdAud(byId, new Account.Id(3), ts);
-
-    GroupBundle noteDbBundle =
-        newBundle().source(Source.NOTE_DB).memberAudit(memberAudit).byIdAudit(byIdAudit).build();
-
-    assertThat(GroupBundle.compareWithAudits(reviewDbBundle, noteDbBundle)).isNotEmpty();
-    assertThat(GroupBundle.compareWithoutAudits(reviewDbBundle, noteDbBundle)).isEmpty();
-  }
-
-  @Test
-  public void compareEqual() throws Exception {
-    GroupBundle reviewDbBundle = newBundle().source(Source.REVIEW_DB).build();
-    GroupBundle noteDbBundle = newBundle().source(Source.NOTE_DB).build();
-    assertThat(GroupBundle.compareWithAudits(reviewDbBundle, noteDbBundle)).isEmpty();
-  }
-
-  private GroupBundle.Builder newBundle() {
-    AccountGroup group =
-        new AccountGroup(
-            new AccountGroup.NameKey("group"),
-            new AccountGroup.Id(1),
-            new AccountGroup.UUID("group-1"),
-            ts);
-    AccountGroupMember member =
-        new AccountGroupMember(new AccountGroupMember.Key(new Account.Id(1000), group.getId()));
-    AccountGroupMemberAudit memberAudit =
-        new AccountGroupMemberAudit(member, new Account.Id(2000), ts);
-    AccountGroupById byId =
-        new AccountGroupById(
-            new AccountGroupById.Key(group.getId(), new AccountGroup.UUID("subgroup")));
-    AccountGroupByIdAud byIdAudit = new AccountGroupByIdAud(byId, new Account.Id(3000), ts);
-    return GroupBundle.builder()
-        .group(group)
-        .members(member)
-        .memberAudit(memberAudit)
-        .byId(byId)
-        .byIdAudit(byIdAudit);
-  }
-}
diff --git a/javatests/com/google/gerrit/server/schema/GroupRebuilderTest.java b/javatests/com/google/gerrit/server/schema/GroupRebuilderTest.java
deleted file mode 100644
index 6a8a55a..0000000
--- a/javatests/com/google/gerrit/server/schema/GroupRebuilderTest.java
+++ /dev/null
@@ -1,747 +0,0 @@
-// 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.google.gerrit.server.schema;
-
-import static com.google.common.collect.ImmutableList.toImmutableList;
-import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assert_;
-import static com.google.gerrit.extensions.common.testing.CommitInfoSubject.assertThat;
-import static com.google.gerrit.reviewdb.client.RefNames.REFS_GROUPNAMES;
-
-import com.google.common.collect.ImmutableList;
-import com.google.gerrit.common.Nullable;
-import com.google.gerrit.common.data.GroupDescription;
-import com.google.gerrit.common.data.GroupReference;
-import com.google.gerrit.extensions.common.CommitInfo;
-import com.google.gerrit.git.RefUpdateUtil;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.reviewdb.client.AccountGroupById;
-import com.google.gerrit.reviewdb.client.AccountGroupByIdAud;
-import com.google.gerrit.reviewdb.client.AccountGroupMember;
-import com.google.gerrit.reviewdb.client.AccountGroupMemberAudit;
-import com.google.gerrit.reviewdb.client.RefNames;
-import com.google.gerrit.server.config.AllUsersName;
-import com.google.gerrit.server.config.AllUsersNameProvider;
-import com.google.gerrit.server.group.db.AuditLogFormatter;
-import com.google.gerrit.server.group.db.AuditLogReader;
-import com.google.gerrit.server.group.db.GroupNameNotes;
-import com.google.gerrit.server.util.time.TimeUtil;
-import com.google.gerrit.testing.GerritBaseTests;
-import com.google.gerrit.testing.GitTestUtil;
-import com.google.gerrit.testing.InMemoryRepositoryManager;
-import com.google.gerrit.testing.TestTimeUtil;
-import com.google.gwtorm.server.OrmDuplicateKeyException;
-import java.sql.Timestamp;
-import java.util.Optional;
-import java.util.TimeZone;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.stream.IntStream;
-import org.eclipse.jgit.lib.BatchRefUpdate;
-import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.ObjectInserter;
-import org.eclipse.jgit.lib.PersonIdent;
-import org.eclipse.jgit.lib.Repository;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-
-public class GroupRebuilderTest extends GerritBaseTests {
-  private static final TimeZone TZ = TimeZone.getTimeZone("America/Los_Angeles");
-  private static final String SERVER_ID = "server-id";
-  private static final String SERVER_NAME = "Gerrit Server";
-  private static final String SERVER_EMAIL = "noreply@gerritcodereview.com";
-
-  private AtomicInteger idCounter;
-  private AllUsersName allUsersName;
-  private Repository repo;
-  private GroupRebuilder rebuilder;
-  private GroupBundle.Factory bundleFactory;
-
-  @Before
-  public void setUp() throws Exception {
-    TestTimeUtil.resetWithClockStep(1, TimeUnit.SECONDS);
-    idCounter = new AtomicInteger();
-    allUsersName = new AllUsersName(AllUsersNameProvider.DEFAULT);
-    repo = new InMemoryRepositoryManager().createRepository(allUsersName);
-    rebuilder =
-        new GroupRebuilder(
-            GroupRebuilderTest.newPersonIdent(),
-            allUsersName,
-            // Note that the expected name/email values in tests are not necessarily realistic,
-            // since they use these trivial name/email functions.
-            getAuditLogFormatter());
-    bundleFactory = new GroupBundle.Factory(new AuditLogReader(SERVER_ID, allUsersName));
-  }
-
-  @After
-  public void tearDown() {
-    TestTimeUtil.useSystemTime();
-  }
-
-  @Test
-  public void minimalGroupFields() throws Exception {
-    AccountGroup g = newGroup("a");
-    GroupBundle b = builder().group(g).build();
-
-    rebuilder.rebuild(repo, b, null);
-
-    assertMigratedCleanly(reload(g), b);
-    ImmutableList<CommitInfo> log = log(g);
-    assertThat(log).hasSize(1);
-    assertCommit(log.get(0), "Create group", SERVER_NAME, SERVER_EMAIL);
-    assertThat(logGroupNames()).isEmpty();
-  }
-
-  @Test
-  public void allGroupFields() throws Exception {
-    AccountGroup g = newGroup("a");
-    g.setDescription("Description");
-    g.setOwnerGroupUUID(new AccountGroup.UUID("owner"));
-    g.setVisibleToAll(true);
-    GroupBundle b = builder().group(g).build();
-
-    rebuilder.rebuild(repo, b, null);
-
-    assertMigratedCleanly(reload(g), b);
-    ImmutableList<CommitInfo> log = log(g);
-    assertThat(log).hasSize(1);
-    assertServerCommit(log.get(0), "Create group");
-  }
-
-  @Test
-  public void emptyGroupName() throws Exception {
-    AccountGroup g = newGroup("");
-    GroupBundle b = builder().group(g).build();
-
-    rebuilder.rebuild(repo, b, null);
-
-    GroupBundle noteDbBundle = reload(g);
-    assertMigratedCleanly(noteDbBundle, b);
-    assertThat(noteDbBundle.group().getName()).isEmpty();
-  }
-
-  @Test
-  public void nullGroupDescription() throws Exception {
-    AccountGroup g = newGroup("a");
-    g.setDescription(null);
-    assertThat(g.getDescription()).isNull();
-    GroupBundle b = builder().group(g).build();
-
-    rebuilder.rebuild(repo, b, null);
-
-    GroupBundle noteDbBundle = reload(g);
-    assertMigratedCleanly(noteDbBundle, b);
-    assertThat(noteDbBundle.group().getDescription()).isNull();
-  }
-
-  @Test
-  public void emptyGroupDescription() throws Exception {
-    AccountGroup g = newGroup("a");
-    g.setDescription("");
-    assertThat(g.getDescription()).isEmpty();
-    GroupBundle b = builder().group(g).build();
-
-    rebuilder.rebuild(repo, b, null);
-
-    GroupBundle noteDbBundle = reload(g);
-    assertMigratedCleanly(noteDbBundle, b);
-    assertThat(noteDbBundle.group().getDescription()).isNull();
-  }
-
-  @Test
-  public void membersAndSubgroups() throws Exception {
-    AccountGroup g = newGroup("a");
-    GroupBundle b =
-        builder()
-            .group(g)
-            .members(member(g, 1), member(g, 2))
-            .byId(byId(g, "x"), byId(g, "y"))
-            .build();
-
-    rebuilder.rebuild(repo, b, null);
-
-    assertMigratedCleanly(reload(g), b);
-    ImmutableList<CommitInfo> log = log(g);
-    assertThat(log).hasSize(2);
-    assertServerCommit(log.get(0), "Create group");
-    assertServerCommit(
-        log.get(1),
-        "Update group\n"
-            + "\n"
-            + "Add-group: Group x <x>\n"
-            + "Add-group: Group y <y>\n"
-            + "Add: Account 1 <1@server-id>\n"
-            + "Add: Account 2 <2@server-id>");
-  }
-
-  @Test
-  public void memberAudit() throws Exception {
-    AccountGroup g = newGroup("a");
-    Timestamp t1 = TimeUtil.nowTs();
-    Timestamp t2 = TimeUtil.nowTs();
-    Timestamp t3 = TimeUtil.nowTs();
-    GroupBundle b =
-        builder()
-            .group(g)
-            .members(member(g, 1))
-            .memberAudit(addMember(g, 1, 8, t2), addAndRemoveMember(g, 2, 8, t1, 9, t3))
-            .build();
-
-    rebuilder.rebuild(repo, b, null);
-
-    assertMigratedCleanly(reload(g), b);
-    ImmutableList<CommitInfo> log = log(g);
-    assertThat(log).hasSize(4);
-    assertServerCommit(log.get(0), "Create group");
-    assertCommit(
-        log.get(1), "Update group\n\nAdd: Account 2 <2@server-id>", "Account 8", "8@server-id");
-    assertCommit(
-        log.get(2), "Update group\n\nAdd: Account 1 <1@server-id>", "Account 8", "8@server-id");
-    assertCommit(
-        log.get(3), "Update group\n\nRemove: Account 2 <2@server-id>", "Account 9", "9@server-id");
-  }
-
-  @Test
-  public void memberAuditLegacyRemoved() throws Exception {
-    AccountGroup g = newGroup("a");
-    GroupBundle b =
-        builder()
-            .group(g)
-            .members(member(g, 2))
-            .memberAudit(
-                addAndLegacyRemoveMember(g, 1, 8, TimeUtil.nowTs()),
-                addMember(g, 2, 8, TimeUtil.nowTs()))
-            .build();
-
-    rebuilder.rebuild(repo, b, null);
-
-    assertMigratedCleanly(reload(g), b);
-    ImmutableList<CommitInfo> log = log(g);
-    assertThat(log).hasSize(4);
-    assertServerCommit(log.get(0), "Create group");
-    assertCommit(
-        log.get(1), "Update group\n\nAdd: Account 1 <1@server-id>", "Account 8", "8@server-id");
-    assertCommit(
-        log.get(2), "Update group\n\nRemove: Account 1 <1@server-id>", "Account 8", "8@server-id");
-    assertCommit(
-        log.get(3), "Update group\n\nAdd: Account 2 <2@server-id>", "Account 8", "8@server-id");
-  }
-
-  @Test
-  public void unauditedMembershipsAddedAtEnd() throws Exception {
-    AccountGroup g = newGroup("a");
-    GroupBundle b =
-        builder()
-            .group(g)
-            .members(member(g, 1), member(g, 2), member(g, 3))
-            .memberAudit(addMember(g, 1, 8, TimeUtil.nowTs()))
-            .build();
-
-    rebuilder.rebuild(repo, b, null);
-
-    assertMigratedCleanly(reload(g), b);
-    ImmutableList<CommitInfo> log = log(g);
-    assertThat(log).hasSize(3);
-    assertServerCommit(log.get(0), "Create group");
-    assertCommit(
-        log.get(1), "Update group\n\nAdd: Account 1 <1@server-id>", "Account 8", "8@server-id");
-    assertServerCommit(
-        log.get(2), "Update group\n\nAdd: Account 2 <2@server-id>\nAdd: Account 3 <3@server-id>");
-  }
-
-  @Test
-  public void byIdAudit() throws Exception {
-    AccountGroup g = newGroup("a");
-    Timestamp t1 = TimeUtil.nowTs();
-    Timestamp t2 = TimeUtil.nowTs();
-    Timestamp t3 = TimeUtil.nowTs();
-    GroupBundle b =
-        builder()
-            .group(g)
-            .byId(byId(g, "x"))
-            .byIdAudit(addById(g, "x", 8, t2), addAndRemoveById(g, "y", 8, t1, 9, t3))
-            .build();
-
-    rebuilder.rebuild(repo, b, null);
-
-    assertMigratedCleanly(reload(g), b);
-    ImmutableList<CommitInfo> log = log(g);
-    assertThat(log).hasSize(4);
-    assertServerCommit(log.get(0), "Create group");
-    assertCommit(log.get(1), "Update group\n\nAdd-group: Group y <y>", "Account 8", "8@server-id");
-    assertCommit(log.get(2), "Update group\n\nAdd-group: Group x <x>", "Account 8", "8@server-id");
-    assertCommit(
-        log.get(3), "Update group\n\nRemove-group: Group y <y>", "Account 9", "9@server-id");
-  }
-
-  @Test
-  public void unauditedByIdAddedAtEnd() throws Exception {
-    AccountGroup g = newGroup("a");
-    GroupBundle b =
-        builder()
-            .group(g)
-            .byId(byId(g, "x"), byId(g, "y"), byId(g, "z"))
-            .byIdAudit(addById(g, "x", 8, TimeUtil.nowTs()))
-            .build();
-
-    rebuilder.rebuild(repo, b, null);
-
-    assertMigratedCleanly(reload(g), b);
-    ImmutableList<CommitInfo> log = log(g);
-    assertThat(log).hasSize(3);
-    assertServerCommit(log.get(0), "Create group");
-    assertCommit(log.get(1), "Update group\n\nAdd-group: Group x <x>", "Account 8", "8@server-id");
-    assertServerCommit(
-        log.get(2), "Update group\n\nAdd-group: Group y <y>\nAdd-group: Group z <z>");
-  }
-
-  @Test
-  public void auditsAtSameTimestampBrokenDownByType() throws Exception {
-    AccountGroup g = newGroup("a");
-    Timestamp ts = TimeUtil.nowTs();
-    int user = 8;
-    GroupBundle b =
-        builder()
-            .group(g)
-            .members(member(g, 1), member(g, 2))
-            .memberAudit(
-                addMember(g, 1, user, ts),
-                addMember(g, 2, user, ts),
-                addAndRemoveMember(g, 3, user, ts, user, ts))
-            .byId(byId(g, "x"), byId(g, "y"))
-            .byIdAudit(
-                addById(g, "x", user, ts),
-                addById(g, "y", user, ts),
-                addAndRemoveById(g, "z", user, ts, user, ts))
-            .build();
-
-    rebuilder.rebuild(repo, b, null);
-
-    assertMigratedCleanly(reload(g), b);
-    ImmutableList<CommitInfo> log = log(g);
-    assertThat(log).hasSize(5);
-    assertServerCommit(log.get(0), "Create group");
-    assertCommit(
-        log.get(1),
-        "Update group\n"
-            + "\n"
-            + "Add: Account 1 <1@server-id>\n"
-            + "Add: Account 2 <2@server-id>\n"
-            + "Add: Account 3 <3@server-id>",
-        "Account 8",
-        "8@server-id");
-    assertCommit(
-        log.get(2), "Update group\n\nRemove: Account 3 <3@server-id>", "Account 8", "8@server-id");
-    assertCommit(
-        log.get(3),
-        "Update group\n"
-            + "\n"
-            + "Add-group: Group x <x>\n"
-            + "Add-group: Group y <y>\n"
-            + "Add-group: Group z <z>",
-        "Account 8",
-        "8@server-id");
-    assertCommit(
-        log.get(4), "Update group\n\nRemove-group: Group z <z>", "Account 8", "8@server-id");
-  }
-
-  @Test
-  public void auditsAtSameTimestampBrokenDownByUserAndType() throws Exception {
-    AccountGroup g = newGroup("a");
-    Timestamp ts = TimeUtil.nowTs();
-    int user1 = 8;
-    int user2 = 9;
-
-    GroupBundle b =
-        builder()
-            .group(g)
-            .members(member(g, 1), member(g, 2), member(g, 3))
-            .memberAudit(
-                addMember(g, 1, user1, ts), addMember(g, 2, user2, ts), addMember(g, 3, user1, ts))
-            .byId(byId(g, "x"), byId(g, "y"), byId(g, "z"))
-            .byIdAudit(
-                addById(g, "x", user1, ts), addById(g, "y", user2, ts), addById(g, "z", user1, ts))
-            .build();
-
-    rebuilder.rebuild(repo, b, null);
-
-    assertMigratedCleanly(reload(g), b);
-    ImmutableList<CommitInfo> log = log(g);
-    assertThat(log).hasSize(5);
-    assertServerCommit(log.get(0), "Create group");
-    assertCommit(
-        log.get(1),
-        "Update group\n" + "\n" + "Add: Account 1 <1@server-id>\n" + "Add: Account 3 <3@server-id>",
-        "Account 8",
-        "8@server-id");
-    assertCommit(
-        log.get(2),
-        "Update group\n\nAdd-group: Group x <x>\nAdd-group: Group z <z>",
-        "Account 8",
-        "8@server-id");
-    assertCommit(
-        log.get(3), "Update group\n\nAdd: Account 2 <2@server-id>", "Account 9", "9@server-id");
-    assertCommit(log.get(4), "Update group\n\nAdd-group: Group y <y>", "Account 9", "9@server-id");
-  }
-
-  @Test
-  public void fixupCommitPostDatesAllAuditEventsEvenIfAuditEventsAreInTheFuture() throws Exception {
-    AccountGroup g = newGroup("a");
-    IntStream.range(0, 20).forEach(i -> TimeUtil.nowTs());
-    Timestamp future = TimeUtil.nowTs();
-    TestTimeUtil.resetWithClockStep(1, TimeUnit.SECONDS);
-
-    GroupBundle b =
-        builder()
-            .group(g)
-            .byId(byId(g, "x"), byId(g, "y"), byId(g, "z"))
-            .byIdAudit(addById(g, "x", 8, future))
-            .build();
-
-    rebuilder.rebuild(repo, b, null);
-
-    assertMigratedCleanly(reload(g), b);
-    ImmutableList<CommitInfo> log = log(g);
-    assertThat(log).hasSize(3);
-    assertServerCommit(log.get(0), "Create group");
-    assertCommit(log.get(1), "Update group\n\nAdd-group: Group x <x>", "Account 8", "8@server-id");
-    assertServerCommit(
-        log.get(2), "Update group\n\nAdd-group: Group y <y>\nAdd-group: Group z <z>");
-
-    assertThat(log.stream().map(c -> c.committer.date).collect(toImmutableList()))
-        .named("%s", log)
-        .isOrdered();
-    assertThat(TimeUtil.nowTs()).isLessThan(future);
-  }
-
-  @Test
-  public void redundantMemberAuditsAreIgnored() throws Exception {
-    AccountGroup g = newGroup("a");
-    Timestamp t1 = TimeUtil.nowTs();
-    Timestamp t2 = TimeUtil.nowTs();
-    Timestamp t3 = TimeUtil.nowTs();
-    Timestamp t4 = TimeUtil.nowTs();
-    Timestamp t5 = TimeUtil.nowTs();
-    GroupBundle b =
-        builder()
-            .group(g)
-            .members(member(g, 2))
-            .memberAudit(
-                addMember(g, 1, 8, t1),
-                addMember(g, 1, 8, t1),
-                addMember(g, 1, 8, t3),
-                addMember(g, 1, 9, t4),
-                addAndRemoveMember(g, 1, 8, t2, 9, t5),
-                addAndLegacyRemoveMember(g, 2, 9, t3),
-                addMember(g, 2, 8, t1),
-                addMember(g, 2, 9, t4),
-                addMember(g, 1, 8, t5))
-            .build();
-
-    rebuilder.rebuild(repo, b, null);
-
-    assertMigratedCleanly(reload(g), b);
-    ImmutableList<CommitInfo> log = log(g);
-    assertThat(log).hasSize(5);
-    assertServerCommit(log.get(0), "Create group");
-    assertCommit(
-        log.get(1),
-        "Update group\n\nAdd: Account 1 <1@server-id>\nAdd: Account 2 <2@server-id>",
-        "Account 8",
-        "8@server-id");
-    assertCommit(
-        log.get(2), "Update group\n\nRemove: Account 2 <2@server-id>", "Account 9", "9@server-id");
-    assertCommit(
-        log.get(3), "Update group\n\nAdd: Account 2 <2@server-id>", "Account 9", "9@server-id");
-    assertCommit(
-        log.get(4), "Update group\n\nRemove: Account 1 <1@server-id>", "Account 9", "9@server-id");
-  }
-
-  @Test
-  public void additionsAndRemovalsWithinSameSecondCanBeMigrated() throws Exception {
-    TestTimeUtil.resetWithClockStep(1, TimeUnit.MILLISECONDS);
-    AccountGroup g = newGroup("a");
-    Timestamp t1 = TimeUtil.nowTs();
-    Timestamp t2 = TimeUtil.nowTs();
-    Timestamp t3 = TimeUtil.nowTs();
-    Timestamp t4 = TimeUtil.nowTs();
-    Timestamp t5 = TimeUtil.nowTs();
-    GroupBundle b =
-        builder()
-            .group(g)
-            .members(member(g, 1))
-            .memberAudit(
-                addAndLegacyRemoveMember(g, 1, 8, t1),
-                addMember(g, 1, 10, t2),
-                addAndRemoveMember(g, 1, 8, t3, 9, t4),
-                addMember(g, 1, 8, t5))
-            .build();
-
-    rebuilder.rebuild(repo, b, null);
-
-    assertMigratedCleanly(reload(g), b);
-    ImmutableList<CommitInfo> log = log(g);
-    assertThat(log).hasSize(6);
-    assertServerCommit(log.get(0), "Create group");
-    assertCommit(
-        log.get(1), "Update group\n\nAdd: Account 1 <1@server-id>", "Account 8", "8@server-id");
-    assertCommit(
-        log.get(2), "Update group\n\nRemove: Account 1 <1@server-id>", "Account 8", "8@server-id");
-    assertCommit(
-        log.get(3), "Update group\n\nAdd: Account 1 <1@server-id>", "Account 10", "10@server-id");
-    assertCommit(
-        log.get(4), "Update group\n\nRemove: Account 1 <1@server-id>", "Account 9", "9@server-id");
-    assertCommit(
-        log.get(5), "Update group\n\nAdd: Account 1 <1@server-id>", "Account 8", "8@server-id");
-  }
-
-  @Test
-  public void redundantByIdAuditsAreIgnored() throws Exception {
-    AccountGroup g = newGroup("a");
-    Timestamp t1 = TimeUtil.nowTs();
-    Timestamp t2 = TimeUtil.nowTs();
-    Timestamp t3 = TimeUtil.nowTs();
-    Timestamp t4 = TimeUtil.nowTs();
-    Timestamp t5 = TimeUtil.nowTs();
-    GroupBundle b =
-        builder()
-            .group(g)
-            .byId()
-            .byIdAudit(
-                addById(g, "x", 8, t1),
-                addById(g, "x", 8, t3),
-                addById(g, "x", 9, t4),
-                addAndRemoveById(g, "x", 8, t2, 9, t5))
-            .build();
-
-    rebuilder.rebuild(repo, b, null);
-
-    assertMigratedCleanly(reload(g), b);
-    ImmutableList<CommitInfo> log = log(g);
-    assertThat(log).hasSize(3);
-    assertServerCommit(log.get(0), "Create group");
-    assertCommit(log.get(1), "Update group\n\nAdd-group: Group x <x>", "Account 8", "8@server-id");
-    assertCommit(
-        log.get(2), "Update group\n\nRemove-group: Group x <x>", "Account 9", "9@server-id");
-  }
-
-  @Test
-  public void combineWithBatchGroupNameNotes() throws Exception {
-    AccountGroup g1 = newGroup("a");
-    AccountGroup g2 = newGroup("b");
-    GroupReference gr1 = new GroupReference(g1.getGroupUUID(), g1.getName());
-    GroupReference gr2 = new GroupReference(g2.getGroupUUID(), g2.getName());
-
-    GroupBundle b1 = builder().group(g1).build();
-    GroupBundle b2 = builder().group(g2).build();
-
-    BatchRefUpdate bru = repo.getRefDatabase().newBatchUpdate();
-
-    rebuilder.rebuild(repo, b1, bru);
-    rebuilder.rebuild(repo, b2, bru);
-    try (ObjectInserter inserter = repo.newObjectInserter()) {
-      ImmutableList<GroupReference> refs = ImmutableList.of(gr1, gr2);
-      GroupNameNotes.updateAllGroups(repo, inserter, bru, refs, newPersonIdent());
-      inserter.flush();
-    }
-
-    assertThat(log(g1)).isEmpty();
-    assertThat(log(g2)).isEmpty();
-    assertThat(logGroupNames()).isEmpty();
-
-    RefUpdateUtil.executeChecked(bru, repo);
-
-    assertThat(log(g1)).hasSize(1);
-    assertThat(log(g2)).hasSize(1);
-    assertThat(logGroupNames()).hasSize(1);
-    assertMigratedCleanly(reload(g1), b1);
-    assertMigratedCleanly(reload(g2), b2);
-
-    assertThat(GroupNameNotes.loadAllGroups(repo)).containsExactly(gr1, gr2);
-  }
-
-  @Test
-  public void groupNamesWithLeadingAndTrailingWhitespace() throws Exception {
-    for (String leading : ImmutableList.of("", " ", "  ")) {
-      for (String trailing : ImmutableList.of("", " ", "  ")) {
-        AccountGroup g = newGroup(leading + "a" + trailing);
-        GroupBundle b = builder().group(g).build();
-        rebuilder.rebuild(repo, b, null);
-        assertMigratedCleanly(reload(g), b);
-      }
-    }
-  }
-
-  @Test
-  public void disallowExisting() throws Exception {
-    AccountGroup g = newGroup("a");
-    GroupBundle b = builder().group(g).build();
-
-    rebuilder.rebuild(repo, b, null);
-    assertMigratedCleanly(reload(g), b);
-    String refName = RefNames.refsGroups(g.getGroupUUID());
-    ObjectId oldId = repo.exactRef(refName).getObjectId();
-
-    try {
-      rebuilder.rebuild(repo, b, null);
-      assert_().fail("expected OrmDuplicateKeyException");
-    } catch (OrmDuplicateKeyException e) {
-      // Expected.
-    }
-
-    assertThat(repo.exactRef(refName).getObjectId()).isEqualTo(oldId);
-  }
-
-  private GroupBundle reload(AccountGroup g) throws Exception {
-    return bundleFactory.fromNoteDb(allUsersName, repo, g.getGroupUUID());
-  }
-
-  private void assertMigratedCleanly(GroupBundle noteDbBundle, GroupBundle expectedReviewDbBundle) {
-    assertThat(GroupBundle.compareWithAudits(expectedReviewDbBundle, noteDbBundle)).isEmpty();
-  }
-
-  private AccountGroup newGroup(String name) {
-    int id = idCounter.incrementAndGet();
-    return new AccountGroup(
-        new AccountGroup.NameKey(name),
-        new AccountGroup.Id(id),
-        new AccountGroup.UUID(name.trim() + "-" + id),
-        TimeUtil.nowTs());
-  }
-
-  private AccountGroupMember member(AccountGroup g, int accountId) {
-    return new AccountGroupMember(new AccountGroupMember.Key(new Account.Id(accountId), g.getId()));
-  }
-
-  private AccountGroupMemberAudit addMember(
-      AccountGroup g, int accountId, int adder, Timestamp addedOn) {
-    return new AccountGroupMemberAudit(member(g, accountId), new Account.Id(adder), addedOn);
-  }
-
-  private AccountGroupMemberAudit addAndLegacyRemoveMember(
-      AccountGroup g, int accountId, int adder, Timestamp addedOn) {
-    AccountGroupMemberAudit a = addMember(g, accountId, adder, addedOn);
-    a.removedLegacy();
-    return a;
-  }
-
-  private AccountGroupMemberAudit addAndRemoveMember(
-      AccountGroup g,
-      int accountId,
-      int adder,
-      Timestamp addedOn,
-      int removedBy,
-      Timestamp removedOn) {
-    AccountGroupMemberAudit a = addMember(g, accountId, adder, addedOn);
-    a.removed(new Account.Id(removedBy), removedOn);
-    return a;
-  }
-
-  private AccountGroupByIdAud addById(
-      AccountGroup g, String subgroupUuid, int adder, Timestamp addedOn) {
-    return new AccountGroupByIdAud(byId(g, subgroupUuid), new Account.Id(adder), addedOn);
-  }
-
-  private AccountGroupByIdAud addAndRemoveById(
-      AccountGroup g,
-      String subgroupUuid,
-      int adder,
-      Timestamp addedOn,
-      int removedBy,
-      Timestamp removedOn) {
-    AccountGroupByIdAud a = addById(g, subgroupUuid, adder, addedOn);
-    a.removed(new Account.Id(removedBy), removedOn);
-    return a;
-  }
-
-  private AccountGroupById byId(AccountGroup g, String subgroupUuid) {
-    return new AccountGroupById(
-        new AccountGroupById.Key(g.getId(), new AccountGroup.UUID(subgroupUuid)));
-  }
-
-  private ImmutableList<CommitInfo> log(AccountGroup g) throws Exception {
-    return GitTestUtil.log(repo, RefNames.refsGroups(g.getGroupUUID()));
-  }
-
-  private ImmutableList<CommitInfo> logGroupNames() throws Exception {
-    return GitTestUtil.log(repo, REFS_GROUPNAMES);
-  }
-
-  private static GroupBundle.Builder builder() {
-    return GroupBundle.builder().source(GroupBundle.Source.REVIEW_DB);
-  }
-
-  private static PersonIdent newPersonIdent() {
-    return new PersonIdent(SERVER_NAME, SERVER_EMAIL, TimeUtil.nowTs(), TZ);
-  }
-
-  private static void assertServerCommit(CommitInfo commitInfo, String expectedMessage) {
-    assertCommit(commitInfo, expectedMessage, SERVER_NAME, SERVER_EMAIL);
-  }
-
-  private static void assertCommit(
-      CommitInfo commitInfo, String expectedMessage, String expectedName, String expectedEmail) {
-    assertThat(commitInfo).message().isEqualTo(expectedMessage);
-    assertThat(commitInfo).author().name().isEqualTo(expectedName);
-    assertThat(commitInfo).author().email().isEqualTo(expectedEmail);
-
-    // Committer should always be the server, regardless of author.
-    assertThat(commitInfo).committer().name().isEqualTo(SERVER_NAME);
-    assertThat(commitInfo).committer().email().isEqualTo(SERVER_EMAIL);
-    assertThat(commitInfo).committer().date().isEqualTo(commitInfo.author.date);
-    assertThat(commitInfo).committer().tz().isEqualTo(commitInfo.author.tz);
-  }
-
-  private static AuditLogFormatter getAuditLogFormatter() {
-    return AuditLogFormatter.create(
-        GroupRebuilderTest::getAccount, GroupRebuilderTest::getGroup, SERVER_ID);
-  }
-
-  private static Optional<Account> getAccount(Account.Id id) {
-    Account account = new Account(id, TimeUtil.nowTs());
-    account.setFullName("Account " + id);
-    return Optional.of(account);
-  }
-
-  private static Optional<GroupDescription.Basic> getGroup(AccountGroup.UUID uuid) {
-    GroupDescription.Basic group =
-        new GroupDescription.Basic() {
-          @Override
-          public AccountGroup.UUID getGroupUUID() {
-            return uuid;
-          }
-
-          @Override
-          public String getName() {
-            return "Group " + uuid;
-          }
-
-          @Nullable
-          @Override
-          public String getEmailAddress() {
-            return null;
-          }
-
-          @Nullable
-          @Override
-          public String getUrl() {
-            return null;
-          }
-        };
-    return Optional.of(group);
-  }
-}