Merge "Replace bootstrapping mode with PROJECT_OWNERS option for fallback code owners"
diff --git a/java/com/google/gerrit/plugins/codeowners/acceptance/AbstractCodeOwnersTest.java b/java/com/google/gerrit/plugins/codeowners/acceptance/AbstractCodeOwnersTest.java
index 3d7c628..a06525d 100644
--- a/java/com/google/gerrit/plugins/codeowners/acceptance/AbstractCodeOwnersTest.java
+++ b/java/com/google/gerrit/plugins/codeowners/acceptance/AbstractCodeOwnersTest.java
@@ -210,27 +210,6 @@
   }
 
   /**
-   * Creates an arbitrary code owner config file.
-   *
-   * <p>Can be used to create an arbitrary code owner config in order to avoid entering the
-   * bootstrapping code path in {@link
-   * com.google.gerrit.plugins.codeowners.backend.CodeOwnerApprovalCheck}.
-   */
-  protected void createArbitraryCodeOwnerConfigFile() throws Exception {
-    TestAccount arbitraryUser =
-        accountCreator.create(
-            "arbitrary-user", "arbitrary-user@example.com", "Arbitrary User", null);
-
-    codeOwnerConfigOperations
-        .newCodeOwnerConfig()
-        .project(project)
-        .branch("master")
-        .folderPath("/arbitrary/path/")
-        .addCodeOwnerEmail(arbitraryUser.email())
-        .create();
-  }
-
-  /**
    * Creates a non-parseable code owner config file at the given path.
    *
    * @param path path of the code owner config file
diff --git a/java/com/google/gerrit/plugins/codeowners/api/CodeOwnerBranchConfigInfo.java b/java/com/google/gerrit/plugins/codeowners/api/CodeOwnerBranchConfigInfo.java
index 6fb3fc7..8cf28cc 100644
--- a/java/com/google/gerrit/plugins/codeowners/api/CodeOwnerBranchConfigInfo.java
+++ b/java/com/google/gerrit/plugins/codeowners/api/CodeOwnerBranchConfigInfo.java
@@ -63,16 +63,4 @@
    * <p>Not set if {@link #disabled} is {@code true}.
    */
   public List<RequiredApprovalInfo> overrideApproval;
-
-  /**
-   * Whether the branch doesn't contain any code owner config file yet.
-   *
-   * <p>If a branch doesn't contain any code owner config file yet, the projects owners are
-   * considered as code owners. Once a first code owner config file is added to the branch, the
-   * project owners are no longer code owners (unless code ownership is granted to them via the code
-   * owner config file).
-   *
-   * <p>Not set if {@code false} or if {@link #disabled} is {@code true}.
-   */
-  public Boolean noCodeOwnersDefined;
 }
diff --git a/java/com/google/gerrit/plugins/codeowners/backend/CodeOwnerApprovalCheck.java b/java/com/google/gerrit/plugins/codeowners/backend/CodeOwnerApprovalCheck.java
index 33fd2e4..4443910 100644
--- a/java/com/google/gerrit/plugins/codeowners/backend/CodeOwnerApprovalCheck.java
+++ b/java/com/google/gerrit/plugins/codeowners/backend/CodeOwnerApprovalCheck.java
@@ -87,7 +87,6 @@
   private final GitRepositoryManager repoManager;
   private final CodeOwnersPluginConfiguration codeOwnersPluginConfiguration;
   private final ChangedFiles changedFiles;
-  private final CodeOwnerConfigScanner.Factory codeOwnerConfigScannerFactory;
   private final CodeOwnerConfigHierarchy codeOwnerConfigHierarchy;
   private final Provider<CodeOwnerResolver> codeOwnerResolver;
   private final ApprovalsUtil approvalsUtil;
@@ -99,7 +98,6 @@
       GitRepositoryManager repoManager,
       CodeOwnersPluginConfiguration codeOwnersPluginConfiguration,
       ChangedFiles changedFiles,
-      CodeOwnerConfigScanner.Factory codeOwnerConfigScannerFactory,
       CodeOwnerConfigHierarchy codeOwnerConfigHierarchy,
       Provider<CodeOwnerResolver> codeOwnerResolver,
       ApprovalsUtil approvalsUtil,
@@ -108,7 +106,6 @@
     this.repoManager = repoManager;
     this.codeOwnersPluginConfiguration = codeOwnersPluginConfiguration;
     this.changedFiles = changedFiles;
-    this.codeOwnerConfigScannerFactory = codeOwnerConfigScannerFactory;
     this.codeOwnerConfigHierarchy = codeOwnerConfigHierarchy;
     this.codeOwnerResolver = codeOwnerResolver;
     this.approvalsUtil = approvalsUtil;
@@ -244,13 +241,6 @@
               .resolveGlobalCodeOwners(changeNotes.getProjectName());
       logger.atFine().log("global code owners = %s", globalCodeOwners);
 
-      // If the branch doesn't contain any code owner config file yet, we apply special logic
-      // (project owners count as code owners) to allow bootstrapping the code owner configuration
-      // in the branch.
-      boolean isBootstrapping =
-          !codeOwnerConfigScannerFactory.create().containsAnyCodeOwnerConfigFile(branch);
-      logger.atFine().log("isBootstrapping = %s", isBootstrapping);
-
       ImmutableSet<Account.Id> reviewerAccountIds =
           getReviewerAccountIds(requiredApproval, changeNotes, patchSetUploader);
       ImmutableSet<Account.Id> approverAccountIds =
@@ -271,7 +261,6 @@
                       reviewerAccountIds,
                       approverAccountIds,
                       hasOverride,
-                      isBootstrapping,
                       changedFile));
     }
   }
@@ -312,15 +301,12 @@
       ObjectId revision = getDestBranchRevision(changeNotes.getChange());
       logger.atFine().log("dest branch %s has revision %s", branch.branch(), revision.name());
 
-      // If the branch doesn't contain any code owner config file yet, we apply special logic
-      // (project owners count as code owners) to allow bootstrapping the code owner configuration
-      // in the branch.
-      boolean isBootstrapping =
-          !codeOwnerConfigScannerFactory.create().containsAnyCodeOwnerConfigFile(branch);
       boolean isProjectOwner = isProjectOwner(changeNotes.getProjectName(), accountId);
+      FallbackCodeOwners fallbackCodeOwners =
+          codeOwnersPluginConfiguration.getFallbackCodeOwners(branch.project());
       logger.atFine().log(
-          "isBootstrapping = %s (isProjectOwner = %s)", isBootstrapping, isProjectOwner);
-      if (isBootstrapping && isProjectOwner) {
+          "fallbackCodeOwner = %s, isProjectOwner = %s", fallbackCodeOwners, isProjectOwner);
+      if (fallbackCodeOwners.equals(FallbackCodeOwners.PROJECT_OWNERS) && isProjectOwner) {
         // Return all paths as approved.
         return changedFiles.compute(changeNotes.getProjectName(), patchSet.commitId()).stream()
             .map(
@@ -357,7 +343,6 @@
                       // Assume an explicit approval of the given account.
                       /* approverAccountIds= */ ImmutableSet.of(accountId),
                       /* hasOverride= */ false,
-                      /* isBootstrapping= */ false,
                       changedFile));
     }
   }
@@ -371,7 +356,6 @@
       ImmutableSet<Account.Id> reviewerAccountIds,
       ImmutableSet<Account.Id> approverAccountIds,
       boolean hasOverride,
-      boolean isBootstrapping,
       ChangedFile changedFile) {
     logger.atFine().log("computing file status for %s", changedFile);
 
@@ -390,7 +374,6 @@
                         reviewerAccountIds,
                         approverAccountIds,
                         hasOverride,
-                        isBootstrapping,
                         newPath));
 
     // Compute the code owner status for the old path, if the file was deleted or renamed.
@@ -411,7 +394,6 @@
                   reviewerAccountIds,
                   approverAccountIds,
                   hasOverride,
-                  isBootstrapping,
                   changedFile.oldPath().get()));
     }
 
@@ -430,7 +412,6 @@
       ImmutableSet<Account.Id> reviewerAccountIds,
       ImmutableSet<Account.Id> approverAccountIds,
       boolean hasOverride,
-      boolean isBootstrapping,
       Path absolutePath) {
     logger.atFine().log("computing path status for %s", absolutePath);
 
@@ -441,179 +422,6 @@
       return PathCodeOwnerStatus.create(absolutePath, CodeOwnerStatus.APPROVED);
     }
 
-    return isBootstrapping
-        ? getPathCodeOwnerStatusBootstrappingMode(
-            branch,
-            globalCodeOwners,
-            enableImplicitApprovalFromUploader,
-            patchSetUploader,
-            reviewerAccountIds,
-            approverAccountIds,
-            absolutePath)
-        : getPathCodeOwnerStatusRegularMode(
-            branch,
-            globalCodeOwners,
-            enableImplicitApprovalFromUploader,
-            patchSetUploader,
-            revision,
-            reviewerAccountIds,
-            approverAccountIds,
-            absolutePath);
-  }
-
-  /**
-   * Gets the code owner status for the given path when the branch doesn't contain any code owner
-   * config file yet (bootstrapping mode).
-   *
-   * <p>If we are in bootstrapping mode we consider project owners as code owners. This allows
-   * bootstrapping the code owner configuration in the branch.
-   */
-  private PathCodeOwnerStatus getPathCodeOwnerStatusBootstrappingMode(
-      BranchNameKey branch,
-      CodeOwnerResolverResult globalCodeOwners,
-      boolean enableImplicitApprovalFromUploader,
-      @Nullable Account.Id patchSetUploader,
-      ImmutableSet<Account.Id> reviewerAccountIds,
-      ImmutableSet<Account.Id> approverAccountIds,
-      Path absolutePath) {
-    logger.atFine().log("computing path status for %s (bootstrapping mode)", absolutePath);
-
-    CodeOwnerStatus codeOwnerStatus = CodeOwnerStatus.INSUFFICIENT_REVIEWERS;
-    if (isApprovedBootstrappingMode(
-        branch.project(),
-        absolutePath,
-        globalCodeOwners,
-        approverAccountIds,
-        enableImplicitApprovalFromUploader,
-        patchSetUploader)) {
-      codeOwnerStatus = CodeOwnerStatus.APPROVED;
-    } else if (isPendingBootstrappingMode(
-        branch.project(), absolutePath, globalCodeOwners, reviewerAccountIds)) {
-      codeOwnerStatus = CodeOwnerStatus.PENDING;
-    }
-
-    // Since there are no code owner config files in bootstrapping mode, fallback code owners also
-    // apply if they are configured. We can skip checking them if we already found that the file was
-    // approved.
-    if (codeOwnerStatus != CodeOwnerStatus.APPROVED) {
-      codeOwnerStatus =
-          getCodeOwnerStatusForFallbackCodeOwners(
-              codeOwnerStatus,
-              branch.project(),
-              enableImplicitApprovalFromUploader,
-              reviewerAccountIds,
-              approverAccountIds,
-              absolutePath);
-    }
-
-    PathCodeOwnerStatus pathCodeOwnerStatus =
-        PathCodeOwnerStatus.create(absolutePath, codeOwnerStatus);
-    logger.atFine().log("pathCodeOwnerStatus = %s", pathCodeOwnerStatus);
-    return pathCodeOwnerStatus;
-  }
-
-  private boolean isApprovedBootstrappingMode(
-      Project.NameKey projectName,
-      Path absolutePath,
-      CodeOwnerResolverResult globalCodeOwners,
-      ImmutableSet<Account.Id> approverAccountIds,
-      boolean enableImplicitApprovalFromUploader,
-      @Nullable Account.Id patchSetUploader) {
-    return (enableImplicitApprovalFromUploader
-            && isImplicitlyApprovedBootstrappingMode(
-                projectName, absolutePath, globalCodeOwners, patchSetUploader))
-        || isExplicitlyApprovedBootstrappingMode(
-            projectName, absolutePath, globalCodeOwners, approverAccountIds);
-  }
-
-  private boolean isImplicitlyApprovedBootstrappingMode(
-      Project.NameKey projectName,
-      Path absolutePath,
-      CodeOwnerResolverResult globalCodeOwners,
-      Account.Id patchSetUploader) {
-    requireNonNull(
-        patchSetUploader, "patchSetUploader must be set if implicit approvals are enabled");
-    if (isProjectOwner(projectName, patchSetUploader)) {
-      // The uploader of the patch set is a project owner and thus a code owner. This means there
-      // is an implicit code owner approval from the patch set uploader so that the path is
-      // automatically approved.
-      logger.atFine().log(
-          "%s was implicitly approved by the patch set uploader who is a project owner",
-          absolutePath);
-      return true;
-    }
-
-    if (globalCodeOwners.ownedByAllUsers()
-        || globalCodeOwners.codeOwnersAccountIds().contains(patchSetUploader)) {
-      // If the uploader of the patch set is a global code owner, there is an implicit code owner
-      // approval from the patch set uploader so that the path is automatically approved.
-      logger.atFine().log(
-          "%s was implicitly approved by the patch set uploader who is a global owner",
-          absolutePath);
-      return true;
-    }
-
-    return false;
-  }
-
-  private boolean isExplicitlyApprovedBootstrappingMode(
-      Project.NameKey projectName,
-      Path absolutePath,
-      CodeOwnerResolverResult globalCodeOwners,
-      ImmutableSet<Account.Id> approverAccountIds) {
-    if (!Collections.disjoint(approverAccountIds, globalCodeOwners.codeOwnersAccountIds())
-        || (globalCodeOwners.ownedByAllUsers() && !approverAccountIds.isEmpty())) {
-      // At least one of the global code owners approved the change.
-      logger.atFine().log("%s was approved by a global code owner", absolutePath);
-      return true;
-    }
-
-    if (approverAccountIds.stream()
-        .anyMatch(approverAccountId -> isProjectOwner(projectName, approverAccountId))) {
-      // At least one of the approvers is a project owner and thus a code owner.
-      logger.atFine().log("%s was approved by a project owner", absolutePath);
-      return true;
-    }
-
-    return false;
-  }
-
-  private boolean isPendingBootstrappingMode(
-      Project.NameKey projectName,
-      Path absolutePath,
-      CodeOwnerResolverResult globalCodeOwners,
-      ImmutableSet<Account.Id> reviewerAccountIds) {
-    if (reviewerAccountIds.stream()
-        .anyMatch(reviewerAccountId -> isProjectOwner(projectName, reviewerAccountId))) {
-      // At least one of the reviewers is a project owner and thus a code owner.
-      logger.atFine().log("%s is owned by a reviewer who is project owner", absolutePath);
-      return true;
-    }
-
-    if (isPending(absolutePath, globalCodeOwners, reviewerAccountIds)) {
-      // At least one of the reviewers is a global code owner.
-      logger.atFine().log("%s is owned by a reviewer who is a global owner", absolutePath);
-      return true;
-    }
-
-    return false;
-  }
-
-  /**
-   * Gets the code owner status for the given path when the branch contains at least one code owner
-   * config file (regular mode).
-   */
-  private PathCodeOwnerStatus getPathCodeOwnerStatusRegularMode(
-      BranchNameKey branch,
-      CodeOwnerResolverResult globalCodeOwners,
-      boolean enableImplicitApprovalFromUploader,
-      @Nullable Account.Id patchSetUploader,
-      ObjectId revision,
-      ImmutableSet<Account.Id> reviewerAccountIds,
-      ImmutableSet<Account.Id> approverAccountIds,
-      Path absolutePath) {
-    logger.atFine().log("computing path status for %s (regular mode)", absolutePath);
-
     AtomicReference<CodeOwnerStatus> codeOwnerStatus =
         new AtomicReference<>(CodeOwnerStatus.INSUFFICIENT_REVIEWERS);
 
@@ -687,8 +495,10 @@
         codeOwnerStatus.set(
             getCodeOwnerStatusForFallbackCodeOwners(
                 codeOwnerStatus.get(),
-                branch.project(),
+                branch,
+                globalCodeOwners,
                 enableImplicitApprovalFromUploader,
+                patchSetUploader,
                 reviewerAccountIds,
                 approverAccountIds,
                 absolutePath));
@@ -702,17 +512,140 @@
   }
 
   /**
+   * Gets the code owner status for the given path when project owners are configured as fallback
+   * code owners.
+   */
+  private CodeOwnerStatus getCodeOwnerStatusForProjectOwnersAsFallbackCodeOwners(
+      BranchNameKey branch,
+      CodeOwnerResolverResult globalCodeOwners,
+      boolean enableImplicitApprovalFromUploader,
+      @Nullable Account.Id patchSetUploader,
+      ImmutableSet<Account.Id> reviewerAccountIds,
+      ImmutableSet<Account.Id> approverAccountIds,
+      Path absolutePath) {
+    logger.atFine().log(
+        "computing code owner status for %s with project owners as fallback code owners",
+        absolutePath);
+
+    CodeOwnerStatus codeOwnerStatus = CodeOwnerStatus.INSUFFICIENT_REVIEWERS;
+    if (isApprovedByProjectOwnerOrGlobalOwner(
+        branch.project(),
+        absolutePath,
+        globalCodeOwners,
+        approverAccountIds,
+        enableImplicitApprovalFromUploader,
+        patchSetUploader)) {
+      codeOwnerStatus = CodeOwnerStatus.APPROVED;
+    } else if (isPendingByProjectOwnerOrGlobalOwner(
+        branch.project(), absolutePath, globalCodeOwners, reviewerAccountIds)) {
+      codeOwnerStatus = CodeOwnerStatus.PENDING;
+    }
+
+    logger.atFine().log("codeOwnerStatus = %s", codeOwnerStatus);
+    return codeOwnerStatus;
+  }
+
+  private boolean isApprovedByProjectOwnerOrGlobalOwner(
+      Project.NameKey projectName,
+      Path absolutePath,
+      CodeOwnerResolverResult globalCodeOwners,
+      ImmutableSet<Account.Id> approverAccountIds,
+      boolean enableImplicitApprovalFromUploader,
+      @Nullable Account.Id patchSetUploader) {
+    return (enableImplicitApprovalFromUploader
+            && isImplicitlyApprovedByProjectOwnerOrGlobalOwner(
+                projectName, absolutePath, globalCodeOwners, patchSetUploader))
+        || isExplicitlyApprovedByProjectOwnerOrGlobalOwner(
+            projectName, absolutePath, globalCodeOwners, approverAccountIds);
+  }
+
+  private boolean isImplicitlyApprovedByProjectOwnerOrGlobalOwner(
+      Project.NameKey projectName,
+      Path absolutePath,
+      CodeOwnerResolverResult globalCodeOwners,
+      Account.Id patchSetUploader) {
+    requireNonNull(
+        patchSetUploader, "patchSetUploader must be set if implicit approvals are enabled");
+    if (isProjectOwner(projectName, patchSetUploader)) {
+      // The uploader of the patch set is a project owner and thus a code owner. This means there
+      // is an implicit code owner approval from the patch set uploader so that the path is
+      // automatically approved.
+      logger.atFine().log(
+          "%s was implicitly approved by the patch set uploader who is a project owner",
+          absolutePath);
+      return true;
+    }
+
+    if (globalCodeOwners.ownedByAllUsers()
+        || globalCodeOwners.codeOwnersAccountIds().contains(patchSetUploader)) {
+      // If the uploader of the patch set is a global code owner, there is an implicit code owner
+      // approval from the patch set uploader so that the path is automatically approved.
+      logger.atFine().log(
+          "%s was implicitly approved by the patch set uploader who is a global owner",
+          absolutePath);
+      return true;
+    }
+
+    return false;
+  }
+
+  private boolean isExplicitlyApprovedByProjectOwnerOrGlobalOwner(
+      Project.NameKey projectName,
+      Path absolutePath,
+      CodeOwnerResolverResult globalCodeOwners,
+      ImmutableSet<Account.Id> approverAccountIds) {
+    if (!Collections.disjoint(approverAccountIds, globalCodeOwners.codeOwnersAccountIds())
+        || (globalCodeOwners.ownedByAllUsers() && !approverAccountIds.isEmpty())) {
+      // At least one of the global code owners approved the change.
+      logger.atFine().log("%s was approved by a global code owner", absolutePath);
+      return true;
+    }
+
+    if (approverAccountIds.stream()
+        .anyMatch(approverAccountId -> isProjectOwner(projectName, approverAccountId))) {
+      // At least one of the approvers is a project owner and thus a code owner.
+      logger.atFine().log("%s was approved by a project owner", absolutePath);
+      return true;
+    }
+
+    return false;
+  }
+
+  private boolean isPendingByProjectOwnerOrGlobalOwner(
+      Project.NameKey projectName,
+      Path absolutePath,
+      CodeOwnerResolverResult globalCodeOwners,
+      ImmutableSet<Account.Id> reviewerAccountIds) {
+    if (reviewerAccountIds.stream()
+        .anyMatch(reviewerAccountId -> isProjectOwner(projectName, reviewerAccountId))) {
+      // At least one of the reviewers is a project owner and thus a code owner.
+      logger.atFine().log("%s is owned by a reviewer who is project owner", absolutePath);
+      return true;
+    }
+
+    if (isPending(absolutePath, globalCodeOwners, reviewerAccountIds)) {
+      // At least one of the reviewers is a global code owner.
+      logger.atFine().log("%s is owned by a reviewer who is a global owner", absolutePath);
+      return true;
+    }
+
+    return false;
+  }
+
+  /**
    * Computes the code owner status for the given path based on the configured fallback code owners.
    */
   private CodeOwnerStatus getCodeOwnerStatusForFallbackCodeOwners(
       CodeOwnerStatus codeOwnerStatus,
-      Project.NameKey project,
+      BranchNameKey branch,
+      CodeOwnerResolverResult globalCodeOwners,
       boolean enableImplicitApprovalFromUploader,
+      @Nullable Account.Id patchSetUploader,
       ImmutableSet<Account.Id> reviewerAccountIds,
       ImmutableSet<Account.Id> approverAccountIds,
       Path absolutePath) {
     FallbackCodeOwners fallbackCodeOwners =
-        codeOwnersPluginConfiguration.getFallbackCodeOwners(project);
+        codeOwnersPluginConfiguration.getFallbackCodeOwners(branch.project());
     logger.atFine().log(
         "getting code owner status for fallback code owners (fallback code owners = %s)",
         fallbackCodeOwners);
@@ -720,6 +653,15 @@
       case NONE:
         logger.atFine().log("no fallback code owners");
         return codeOwnerStatus;
+      case PROJECT_OWNERS:
+        return getCodeOwnerStatusForProjectOwnersAsFallbackCodeOwners(
+            branch,
+            globalCodeOwners,
+            enableImplicitApprovalFromUploader,
+            patchSetUploader,
+            reviewerAccountIds,
+            approverAccountIds,
+            absolutePath);
       case ALL_USERS:
         return getCodeOwnerStatusIfAllUsersAreCodeOwners(
             enableImplicitApprovalFromUploader,
diff --git a/java/com/google/gerrit/plugins/codeowners/backend/CodeOwnerConfigScanner.java b/java/com/google/gerrit/plugins/codeowners/backend/CodeOwnerConfigScanner.java
index 1aa38e6..95a2885 100644
--- a/java/com/google/gerrit/plugins/codeowners/backend/CodeOwnerConfigScanner.java
+++ b/java/com/google/gerrit/plugins/codeowners/backend/CodeOwnerConfigScanner.java
@@ -27,7 +27,6 @@
 import com.google.inject.Inject;
 import java.io.IOException;
 import java.util.Optional;
-import java.util.concurrent.atomic.AtomicBoolean;
 import org.eclipse.jgit.errors.ConfigInvalidException;
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.revwalk.RevWalk;
@@ -70,26 +69,6 @@
   }
 
   /**
-   * Whether there is at least one code owner config file in the given project and branch.
-   *
-   * @param branchNameKey the project and branch for which if should be checked if it contains any
-   *     code owner config file
-   * @return {@code true} if there is at least one code owner config file in the given project and
-   *     branch, otherwise {@code false}
-   */
-  public boolean containsAnyCodeOwnerConfigFile(BranchNameKey branchNameKey) {
-    AtomicBoolean found = new AtomicBoolean(false);
-    visit(
-        branchNameKey,
-        codeOwnerConfig -> {
-          found.set(true);
-          return false;
-        },
-        (codeOwnerConfigFilePath, configInvalidException) -> found.set(true));
-    return found.get();
-  }
-
-  /**
    * Visits all code owner config files in the given project and branch.
    *
    * @param branchNameKey the project and branch for which the code owner config files should be
diff --git a/java/com/google/gerrit/plugins/codeowners/backend/FallbackCodeOwners.java b/java/com/google/gerrit/plugins/codeowners/backend/FallbackCodeOwners.java
index 5ad4079..872473b 100644
--- a/java/com/google/gerrit/plugins/codeowners/backend/FallbackCodeOwners.java
+++ b/java/com/google/gerrit/plugins/codeowners/backend/FallbackCodeOwners.java
@@ -32,5 +32,11 @@
    * with configuring code owners can easily happen. This is why this option is intended to be only
    * used if requiring code owner approvals should not be enforced.
    */
-  ALL_USERS;
+  ALL_USERS,
+
+  /**
+   * Paths for which no code owners are defined are owned by the project owners. This means changes
+   * to these paths can be approved by the project owners.
+   */
+  PROJECT_OWNERS;
 }
diff --git a/java/com/google/gerrit/plugins/codeowners/restapi/CodeOwnerProjectConfigJson.java b/java/com/google/gerrit/plugins/codeowners/restapi/CodeOwnerProjectConfigJson.java
index d9f9a8b..2f649f9 100644
--- a/java/com/google/gerrit/plugins/codeowners/restapi/CodeOwnerProjectConfigJson.java
+++ b/java/com/google/gerrit/plugins/codeowners/restapi/CodeOwnerProjectConfigJson.java
@@ -33,7 +33,6 @@
 import com.google.gerrit.plugins.codeowners.api.GeneralInfo;
 import com.google.gerrit.plugins.codeowners.api.RequiredApprovalInfo;
 import com.google.gerrit.plugins.codeowners.backend.CodeOwnerBackendId;
-import com.google.gerrit.plugins.codeowners.backend.CodeOwnerConfigScanner;
 import com.google.gerrit.plugins.codeowners.backend.config.CodeOwnersPluginConfiguration;
 import com.google.gerrit.plugins.codeowners.backend.config.RequiredApproval;
 import com.google.gerrit.server.permissions.PermissionBackendException;
@@ -51,16 +50,13 @@
 @Singleton
 public class CodeOwnerProjectConfigJson {
   private final CodeOwnersPluginConfiguration codeOwnersPluginConfiguration;
-  private final CodeOwnerConfigScanner.Factory codeOwnerConfigScannerFactory;
   private final Provider<ListBranches> listBranches;
 
   @Inject
   CodeOwnerProjectConfigJson(
       CodeOwnersPluginConfiguration codeOwnersPluginConfiguration,
-      CodeOwnerConfigScanner.Factory codeOwnerConfigScannerFactory,
       Provider<ListBranches> listBranches) {
     this.codeOwnersPluginConfiguration = codeOwnersPluginConfiguration;
-    this.codeOwnerConfigScannerFactory = codeOwnerConfigScannerFactory;
     this.listBranches = listBranches;
   }
 
@@ -98,12 +94,6 @@
     info.requiredApproval = formatRequiredApprovalInfo(branchResource.getNameKey());
     info.overrideApproval = formatOverrideApprovalInfo(branchResource.getNameKey());
 
-    boolean noCodeOwnersDefined =
-        !codeOwnerConfigScannerFactory
-            .create()
-            .containsAnyCodeOwnerConfigFile(branchResource.getBranchKey());
-    info.noCodeOwnersDefined = noCodeOwnersDefined ? noCodeOwnersDefined : null;
-
     return info;
   }
 
diff --git a/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/CodeOwnerConfigValidatorIT.java b/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/CodeOwnerConfigValidatorIT.java
index 8d3c3cf..3465717 100644
--- a/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/CodeOwnerConfigValidatorIT.java
+++ b/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/CodeOwnerConfigValidatorIT.java
@@ -163,6 +163,8 @@
 
   @Test
   public void canSubmitConfigWithoutIssues() throws Exception {
+    setAsDefaultCodeOwners(admin);
+
     CodeOwnerConfig.Key codeOwnerConfigKey = createCodeOwnerConfigKey("/");
 
     // Create a code owner config without issues.
@@ -467,6 +469,8 @@
   @Test
   @GerritConfig(name = "plugin.code-owners.enableValidationOnCommitReceived", value = "false")
   public void onReceiveCommitValidationDisabled() throws Exception {
+    setAsDefaultCodeOwners(admin);
+
     // upload a change with a code owner config that has issues (non-resolvable code owners)
     String unknownEmail = "non-existing-email@example.com";
     CodeOwnerConfig.Key codeOwnerConfigKey = createCodeOwnerConfigKey("/");
@@ -895,6 +899,8 @@
 
   @Test
   public void cannotSubmitConfigWithNewIssues() throws Exception {
+    setAsDefaultCodeOwners(admin);
+
     CodeOwnerConfig.Key codeOwnerConfigKey = createCodeOwnerConfigKey("/");
 
     // disable the code owners functionality so that we can upload a a change with a code owner
@@ -1003,6 +1009,8 @@
   @GerritConfig(name = "accounts.visibility", value = "SAME_GROUP")
   public void cannotSubmitConfigWithCodeOwnersThatAreNotVisibleToThePatchSetUploader()
       throws Exception {
+    setAsDefaultCodeOwners(admin);
+
     // Create a new user that is not a member of any group. This means 'user' and 'admin' are not
     // visible to this user since they do not share any group.
     TestAccount user2 = accountCreator.user2();
@@ -1055,6 +1063,8 @@
   @GerritConfig(name = "accounts.visibility", value = "SAME_GROUP")
   public void canSubmitConfigWithCodeOwnersThatAreNotVisibleToTheSubmitterButVisibleToTheUploader()
       throws Exception {
+    setAsDefaultCodeOwners(admin);
+
     // Create a new user that is not a member of any group. This means 'user' and 'admin' are not
     // visible to this user since they do not share any group.
     TestAccount user2 = accountCreator.user2();
@@ -1702,6 +1712,8 @@
   }
 
   private void testCanSubmitNonParseableConfig() throws Exception {
+    setAsDefaultCodeOwners(admin);
+
     CodeOwnerConfig.Key codeOwnerConfigKey = createCodeOwnerConfigKey("/");
 
     // disable the code owners functionality so that we can upload a non-parseable code owner config
@@ -1737,6 +1749,8 @@
   }
 
   private void testCanSubmitConfigWithIssues() throws Exception {
+    setAsDefaultCodeOwners(admin);
+
     CodeOwnerConfig.Key codeOwnerConfigKey = createCodeOwnerConfigKey("/");
 
     // disable the code owners functionality so that we can upload a code owner config with issues
@@ -1767,6 +1781,8 @@
   @Test
   @GerritConfig(name = "plugin.code-owners.rejectNonResolvableCodeOwners", value = "false")
   public void canUploadAndSubmitConfigWithUnresolvableCodeOwners() throws Exception {
+    setAsDefaultCodeOwners(admin);
+
     CodeOwnerConfig.Key codeOwnerConfigKey = createCodeOwnerConfigKey("/");
 
     // upload a code owner config that has issues (non-resolvable code owners)
@@ -1797,6 +1813,8 @@
   @Test
   @GerritConfig(name = "plugin.code-owners.rejectNonResolvableImports", value = "false")
   public void canUploadAndSubmitConfigWithUnresolvableImports() throws Exception {
+    setAsDefaultCodeOwners(admin);
+
     skipTestIfImportsNotSupportedByCodeOwnersBackend();
 
     CodeOwnerConfig.Key keyOfImportingCodeOwnerConfig = createCodeOwnerConfigKey("/");
@@ -1854,6 +1872,8 @@
   public void rejectConfigOptionsAreIgnoredIfValidationIsDisabled() throws Exception {
     skipTestIfImportsNotSupportedByCodeOwnersBackend();
 
+    setAsDefaultCodeOwners(admin);
+
     CodeOwnerConfig.Key keyOfImportingCodeOwnerConfig = createCodeOwnerConfigKey("/");
 
     // upload a code owner config that has issues (non-resolvable code owners and non-resolvable
@@ -1916,6 +1936,7 @@
 
   @Test
   @GerritConfig(name = "plugin.code-owners.backend", value = FailingCodeOwnerBackend.ID)
+  @GerritConfig(name = "plugin.code-owners.fallbackCodeOwners", value = "PROJECT_OWNERS")
   public void submitFailsOnInternalError() throws Exception {
     try (AutoCloseable registration = registerTestBackend(new FailingCodeOwnerBackend())) {
       disableCodeOwnersForProject(project);
@@ -1934,6 +1955,7 @@
   @Test
   @GerritConfig(name = "plugin.code-owners.backend", value = FailingCodeOwnerBackend.ID)
   @GerritConfig(name = "plugin.code-owners.enableValidationOnSubmit", value = "DRY_RUN")
+  @GerritConfig(name = "plugin.code-owners.fallbackCodeOwners", value = "PROJECT_OWNERS")
   public void submitSucceedsOnInternalErrorIfValidationIsDoneAsDryRun() throws Exception {
     try (AutoCloseable registration = registerTestBackend(new FailingCodeOwnerBackend())) {
       disableCodeOwnersForProject(project);
diff --git a/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/CodeOwnerSubmitRuleIT.java b/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/CodeOwnerSubmitRuleIT.java
index 1e74951..ecb1a9e 100644
--- a/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/CodeOwnerSubmitRuleIT.java
+++ b/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/CodeOwnerSubmitRuleIT.java
@@ -82,10 +82,6 @@
 
   @Test
   public void changeWithInsufficentReviewersIsNotSubmittable() throws Exception {
-    // create arbitrary code owner config to avoid entering the bootstrapping code path in
-    // CodeOwnerApprovalCheck
-    createArbitraryCodeOwnerConfigFile();
-
     String changeId = createChange("Test Change", "foo/bar.baz", "file content").getChangeId();
 
     // Approve by a non-code-owner.
@@ -226,10 +222,6 @@
   public void changeWithOverrideApprovalIsSubmittable() throws Exception {
     createOwnersOverrideLabel();
 
-    // create arbitrary code owner config to avoid entering the bootstrapping code path in
-    // CodeOwnerApprovalCheck
-    createArbitraryCodeOwnerConfigFile();
-
     String changeId = createChange("Test Change", "foo/bar.baz", "file content").getChangeId();
 
     // Check that the change is not submittable.
diff --git a/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/CodeOwnersPluginConfigValidatorIT.java b/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/CodeOwnersPluginConfigValidatorIT.java
index fbaea56..be0f13b 100644
--- a/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/CodeOwnersPluginConfigValidatorIT.java
+++ b/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/CodeOwnersPluginConfigValidatorIT.java
@@ -26,6 +26,7 @@
 import com.google.common.collect.ImmutableSet;
 import com.google.gerrit.acceptance.GitUtil;
 import com.google.gerrit.acceptance.PushOneCommit;
+import com.google.gerrit.acceptance.config.GerritConfig;
 import com.google.gerrit.entities.BranchNameKey;
 import com.google.gerrit.entities.RefNames;
 import com.google.gerrit.extensions.api.changes.RebaseInput;
@@ -546,6 +547,7 @@
   }
 
   @Test
+  @GerritConfig(name = "plugin.code-owners.disabledBranch", value = "refs/meta/config")
   public void validationDoesntFailOnRebaseChange_unrelatedChange() throws Exception {
     // Create two changes for refs/meta/config both with the same parent.
     GitUtil.fetch(testRepo, RefNames.REFS_CONFIG + ":config");
@@ -576,6 +578,7 @@
   }
 
   @Test
+  @GerritConfig(name = "plugin.code-owners.disabledBranch", value = "refs/meta/config")
   public void validationDoesntFailOnRebaseChange_changeThatUpdatesTheCodeOwnersConfig()
       throws Exception {
     // Create two changes for refs/meta/config both with the same parent.
@@ -615,6 +618,7 @@
   }
 
   @Test
+  @GerritConfig(name = "plugin.code-owners.disabledBranch", value = "refs/meta/config")
   public void validationFailsOnRebaseChange_changeThatCreatesInvalidCodeOwnerConfig()
       throws Exception {
     // Create two changes for refs/meta/config both with the same parent.
diff --git a/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/GetCodeOwnerBranchConfigIT.java b/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/GetCodeOwnerBranchConfigIT.java
index d022d13..3777242 100644
--- a/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/GetCodeOwnerBranchConfigIT.java
+++ b/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/GetCodeOwnerBranchConfigIT.java
@@ -95,7 +95,6 @@
     assertThat(codeOwnerBranchConfigInfo.requiredApproval.value)
         .isEqualTo(RequiredApprovalConfig.DEFAULT_VALUE);
     assertThat(codeOwnerBranchConfigInfo.overrideApproval).isNull();
-    assertThat(codeOwnerBranchConfigInfo.noCodeOwnersDefined).isNull();
   }
 
   @Test
@@ -246,14 +245,6 @@
     assertThat(codeOwnerBranchConfigInfo.general.implicitApprovals).isTrue();
   }
 
-  @Test
-  public void getConfig_bootstrappingMode() throws Exception {
-    configureImplicitApprovals(project);
-    CodeOwnerBranchConfigInfo codeOwnerBranchConfigInfo =
-        projectCodeOwnersApiFactory.project(project).branch("master").getConfig();
-    assertThat(codeOwnerBranchConfigInfo.noCodeOwnersDefined).isTrue();
-  }
-
   private void configureFileExtension(Project.NameKey project, String fileExtension)
       throws Exception {
     setCodeOwnersConfig(
diff --git a/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/GetCodeOwnerStatusIT.java b/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/GetCodeOwnerStatusIT.java
index b0da402..64b24f8 100644
--- a/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/GetCodeOwnerStatusIT.java
+++ b/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/GetCodeOwnerStatusIT.java
@@ -79,10 +79,6 @@
 
   @Test
   public void getCodeOwnerStatusIfCodeOwnersFunctionalityIsDisabled() throws Exception {
-    // create arbitrary code owner config to avoid entering the bootstrapping code path in
-    // CodeOwnerApprovalCheck
-    createArbitraryCodeOwnerConfigFile();
-
     disableCodeOwnersForProject(project);
     String path = "foo/bar.baz";
     String changeId = createChange("Change Adding A File", path, "file content").getChangeId();
diff --git a/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/OnCodeOwnerApprovalIT.java b/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/OnCodeOwnerApprovalIT.java
index 4ceb12a..79b3924 100644
--- a/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/OnCodeOwnerApprovalIT.java
+++ b/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/OnCodeOwnerApprovalIT.java
@@ -948,6 +948,8 @@
   @Test
   public void changeMessageExtendedIfCodeOwnerApprovalIsIgnoredDueToSelfApproval()
       throws Exception {
+    setAsRootCodeOwners(admin);
+
     LabelDefinitionInput input = new LabelDefinitionInput();
     input.ignoreSelfApproval = true;
     gApi.projects().name(allProjects.get()).label("Code-Review").update(input);
@@ -967,6 +969,8 @@
   @Test
   public void changeMessageExtendedIfUpgradedCodeOwnerApprovalIsIgnoredDueToSelfApproval()
       throws Exception {
+    setAsRootCodeOwners(admin);
+
     LabelDefinitionInput input = new LabelDefinitionInput();
     input.ignoreSelfApproval = true;
     gApi.projects().name(allProjects.get()).label("Code-Review").update(input);
@@ -989,6 +993,8 @@
   @Test
   public void changeMessageExtendedIfDowngradedCodeOwnerApprovalIsIgnoredDueToSelfApproval()
       throws Exception {
+    setAsRootCodeOwners(admin);
+
     LabelDefinitionInput input = new LabelDefinitionInput();
     input.ignoreSelfApproval = true;
     gApi.projects().name(allProjects.get()).label("Code-Review").update(input);
diff --git a/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/OnCodeOwnerOverrrideIT.java b/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/OnCodeOwnerOverrrideIT.java
index 0c55059..5f17972 100644
--- a/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/OnCodeOwnerOverrrideIT.java
+++ b/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/OnCodeOwnerOverrrideIT.java
@@ -393,6 +393,8 @@
   @GerritConfig(name = "plugin.code-owners.overrideApproval", value = "Owners-Override+1")
   public void changeMessageExtendedIfCodeOwnersOverrideAndCodeOwnerApprovalAreAppliedTogether()
       throws Exception {
+    setAsRootCodeOwners(admin);
+
     createOwnersOverrideLabel();
 
     String path = "foo/bar.baz";
diff --git a/javatests/com/google/gerrit/plugins/codeowners/acceptance/batch/CodeOwnerSubmitRuleBatchIT.java b/javatests/com/google/gerrit/plugins/codeowners/acceptance/batch/CodeOwnerSubmitRuleBatchIT.java
index f7e58d3..9a06ccb 100644
--- a/javatests/com/google/gerrit/plugins/codeowners/acceptance/batch/CodeOwnerSubmitRuleBatchIT.java
+++ b/javatests/com/google/gerrit/plugins/codeowners/acceptance/batch/CodeOwnerSubmitRuleBatchIT.java
@@ -8,6 +8,7 @@
 import com.google.gerrit.acceptance.LightweightPluginDaemonTest;
 import com.google.gerrit.acceptance.PushOneCommit;
 import com.google.gerrit.acceptance.TestPlugin;
+import com.google.gerrit.acceptance.config.GerritConfig;
 import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
 import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations;
 import com.google.gerrit.extensions.client.ListChangesOption;
@@ -31,6 +32,7 @@
   @Inject private RequestScopeOperations requestScopeOperations;
 
   @Test
+  @GerritConfig(name = "plugin.code-owners.fallbackCodeOwners", value = "PROJECT_OWNERS")
   public void invokeCodeOwnerSubmitRule() throws Exception {
     // Upload a change as a non-code owner.
     TestRepository<InMemoryRepository> testRepo = cloneProject(project, user);
@@ -58,8 +60,8 @@
     submitRequirementInfoSubject.hasFallbackTextThat().isEqualTo("Code Owners");
     submitRequirementInfoSubject.hasTypeThat().isEqualTo("code-owners");
 
-    // Approve by a project owner who is code owner since there is no code owner config file yet,
-    // and hence we are in bootstrapping mode.
+    // Approve by a project owner who is code owner since project owners are configured as fallback
+    // code owners.
     requestScopeOperations.setApiUser(admin.id());
     approve(changeId);
 
diff --git a/javatests/com/google/gerrit/plugins/codeowners/backend/ChangedFilesTest.java b/javatests/com/google/gerrit/plugins/codeowners/backend/ChangedFilesTest.java
index 2acf24b..3cf1aaf 100644
--- a/javatests/com/google/gerrit/plugins/codeowners/backend/ChangedFilesTest.java
+++ b/javatests/com/google/gerrit/plugins/codeowners/backend/ChangedFilesTest.java
@@ -176,6 +176,8 @@
   }
 
   private void testComputeForMergeChange(MergeCommitStrategy mergeCommitStrategy) throws Exception {
+    setAsRootCodeOwners(admin);
+
     String file1 = "foo/a.txt";
     String file2 = "bar/b.txt";
 
@@ -264,6 +266,8 @@
 
   private void testComputeForMergeChangeThatContainsADeletedFileAsConflictResolution()
       throws Exception {
+    setAsRootCodeOwners(admin);
+
     String file = "foo/a.txt";
 
     // Create a base change.
diff --git a/javatests/com/google/gerrit/plugins/codeowners/backend/CodeOwnerApprovalCheckForAccountTest.java b/javatests/com/google/gerrit/plugins/codeowners/backend/CodeOwnerApprovalCheckForAccountTest.java
index f957516..5693c57 100644
--- a/javatests/com/google/gerrit/plugins/codeowners/backend/CodeOwnerApprovalCheckForAccountTest.java
+++ b/javatests/com/google/gerrit/plugins/codeowners/backend/CodeOwnerApprovalCheckForAccountTest.java
@@ -58,10 +58,6 @@
 
   @Test
   public void notApprovedByUser() throws Exception {
-    // create arbitrary code owner config to avoid entering the bootstrapping code path in
-    // CodeOwnerApprovalCheck
-    createArbitraryCodeOwnerConfigFile();
-
     Path path = Paths.get("/foo/bar.baz");
     String changeId =
         createChange("Change Adding A File", JgitPath.of(path).get(), "file content").getChangeId();
@@ -217,10 +213,8 @@
   }
 
   @Test
-  public void notApprovedByUser_bootstrapping() throws Exception {
-    // since no code owner config exists we are entering the bootstrapping code path in
-    // CodeOwnerApprovalCheck
-
+  @GerritConfig(name = "plugin.code-owners.fallbackCodeOwners", value = "PROJECT_OWNERS")
+  public void notApprovedByUser_projectOwnersAreFallbackCodeOwner() throws Exception {
     Path path = Paths.get("/foo/bar.baz");
     String changeId =
         createChange("Change Adding A File", JgitPath.of(path).get(), "file content").getChangeId();
@@ -241,10 +235,8 @@
   }
 
   @Test
-  public void approvedByProjectOwner_bootstrapping() throws Exception {
-    // since no code owner config exists we are entering the bootstrapping code path in
-    // CodeOwnerApprovalCheck
-
+  @GerritConfig(name = "plugin.code-owners.fallbackCodeOwners", value = "PROJECT_OWNERS")
+  public void approvedByProjectOwner_projectOwnersAreFallbackCodeOwner() throws Exception {
     Path path = Paths.get("/foo/bar.baz");
     String changeId =
         createChange("Change Adding A File", JgitPath.of(path).get(), "file content").getChangeId();
@@ -268,10 +260,6 @@
   @GerritConfig(name = "plugin.code-owners.fallbackCodeOwners", value = "ALL_USERS")
   @Test
   public void approvedByFallbackCodeOwner() throws Exception {
-    // create arbitrary code owner config to avoid entering the bootstrapping code path in
-    // CodeOwnerApprovalCheck
-    createArbitraryCodeOwnerConfigFile();
-
     Path path = Paths.get("/foo/bar.baz");
     String changeId =
         createChange("Change Adding A File", JgitPath.of(path).get(), "file content").getChangeId();
@@ -326,31 +314,6 @@
         .isEqualTo(CodeOwnerStatus.INSUFFICIENT_REVIEWERS);
   }
 
-  @GerritConfig(name = "plugin.code-owners.fallbackCodeOwners", value = "ALL_USERS")
-  @Test
-  public void approvedByFallbackCodeOwner_bootstrappingMode() throws Exception {
-    // since no code owner config exists we are entering the bootstrapping code path in
-    // CodeOwnerApprovalCheck
-
-    Path path = Paths.get("/foo/bar.baz");
-    String changeId =
-        createChange("Change Adding A File", JgitPath.of(path).get(), "file content").getChangeId();
-    ChangeNotes changeNotes = getChangeNotes(changeId);
-
-    // Verify that the file would be approved by the user since the user is a fallback code owner.
-    Stream<FileCodeOwnerStatus> fileCodeOwnerStatuses =
-        codeOwnerApprovalCheck.getFileStatusesForAccount(
-            changeNotes, changeNotes.getCurrentPatchSet(), user.id());
-    FileCodeOwnerStatusSubject fileCodeOwnerStatusSubject =
-        assertThatStream(fileCodeOwnerStatuses).onlyElement();
-    fileCodeOwnerStatusSubject.hasNewPathStatus().value().hasPathThat().isEqualTo(path);
-    fileCodeOwnerStatusSubject
-        .hasNewPathStatus()
-        .value()
-        .hasStatusThat()
-        .isEqualTo(CodeOwnerStatus.APPROVED);
-  }
-
   private ChangeNotes getChangeNotes(String changeId) throws Exception {
     return changeNotesFactory.create(project, Change.id(gApi.changes().id(changeId).get()._number));
   }
diff --git a/javatests/com/google/gerrit/plugins/codeowners/backend/CodeOwnerApprovalCheckTest.java b/javatests/com/google/gerrit/plugins/codeowners/backend/CodeOwnerApprovalCheckTest.java
index 10727fb..ce8b10a 100644
--- a/javatests/com/google/gerrit/plugins/codeowners/backend/CodeOwnerApprovalCheckTest.java
+++ b/javatests/com/google/gerrit/plugins/codeowners/backend/CodeOwnerApprovalCheckTest.java
@@ -45,8 +45,6 @@
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.stream.Stream;
-import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
-import org.eclipse.jgit.junit.TestRepository;
 import org.junit.Before;
 import org.junit.Test;
 
@@ -85,10 +83,6 @@
 
   @Test
   public void getStatusForFileAddition_insufficientReviewers() throws Exception {
-    // create arbitrary code owner config to avoid entering the bootstrapping code path in
-    // CodeOwnerApprovalCheck
-    createArbitraryCodeOwnerConfigFile();
-
     TestAccount user2 = accountCreator.user2();
 
     Path path = Paths.get("/foo/bar.baz");
@@ -119,10 +113,6 @@
 
   @Test
   public void getStatusForFileModification_insufficientReviewers() throws Exception {
-    // create arbitrary code owner config to avoid entering the bootstrapping code path in
-    // CodeOwnerApprovalCheck
-    createArbitraryCodeOwnerConfigFile();
-
     TestAccount user2 = accountCreator.user2();
 
     Path path = Paths.get("/foo/bar.baz");
@@ -155,10 +145,6 @@
 
   @Test
   public void getStatusForFileDeletion_insufficientReviewers() throws Exception {
-    // create arbitrary code owner config to avoid entering the bootstrapping code path in
-    // CodeOwnerApprovalCheck
-    createArbitraryCodeOwnerConfigFile();
-
     TestAccount user2 = accountCreator.user2();
 
     Path path = Paths.get("/foo/bar.baz");
@@ -188,10 +174,6 @@
 
   @Test
   public void getStatusForFileRename_insufficientReviewers() throws Exception {
-    // create arbitrary code owner config to avoid entering the bootstrapping code path in
-    // CodeOwnerApprovalCheck
-    createArbitraryCodeOwnerConfigFile();
-
     TestAccount user2 = accountCreator.user2();
 
     Path oldPath = Paths.get("/foo/old.bar");
@@ -947,25 +929,10 @@
   @Test
   @GerritConfig(name = "plugin.code-owners.globalCodeOwner", value = "bot@example.com")
   public void approvedByGlobalCodeOwner() throws Exception {
-    testApprovedByGlobalCodeOwner(/* bootstrappingMode= */ false);
-  }
-
-  @Test
-  @GerritConfig(name = "plugin.code-owners.globalCodeOwner", value = "bot@example.com")
-  public void approvedByGlobalCodeOwner_bootstrappingMode() throws Exception {
-    testApprovedByGlobalCodeOwner(/* bootstrappingMode= */ true);
-  }
-
-  private void testApprovedByGlobalCodeOwner(boolean bootstrappingMode) throws Exception {
     // Create a bot user that is a global code owner.
     TestAccount bot =
         accountCreator.create("bot", "bot@example.com", "Bot", /* displayName= */ null);
 
-    if (!bootstrappingMode) {
-      // Create a code owner config file so that we are not in the bootstrapping mode.
-      createArbitraryCodeOwnerConfigFile();
-    }
-
     // Create a change as a user that is not a code owner.
     Path path = Paths.get("/foo/bar.baz");
     String changeId =
@@ -1008,43 +975,21 @@
   @Test
   @GerritConfig(name = "plugin.code-owners.globalCodeOwner", value = "bot@example.com")
   public void globalCodeOwner_noImplicitApproval() throws Exception {
-    testImplicitlyApprovedByGlobalCodeOwner(
-        /* implicitApprovalsEnabled= */ false, /* bootstrappingMode= */ false);
+    testImplicitlyApprovedByGlobalCodeOwner(/* implicitApprovalsEnabled= */ false);
   }
 
   @Test
   @GerritConfig(name = "plugin.code-owners.globalCodeOwner", value = "bot@example.com")
   @GerritConfig(name = "plugin.code-owners.enableImplicitApprovals", value = "true")
   public void globalCodeOwner_withImplicitApproval() throws Exception {
-    testImplicitlyApprovedByGlobalCodeOwner(
-        /* implicitApprovalsEnabled= */ true, /* bootstrappingMode= */ false);
+    testImplicitlyApprovedByGlobalCodeOwner(/* implicitApprovalsEnabled= */ true);
   }
 
-  @Test
-  @GerritConfig(name = "plugin.code-owners.globalCodeOwner", value = "bot@example.com")
-  public void globalCodeOwner_noImplicitApproval_bootstrappingMode() throws Exception {
-    testImplicitlyApprovedByGlobalCodeOwner(
-        /* implicitApprovalsEnabled= */ false, /* bootstrappingMode= */ true);
-  }
-
-  @Test
-  @GerritConfig(name = "plugin.code-owners.globalCodeOwner", value = "bot@example.com")
-  @GerritConfig(name = "plugin.code-owners.enableImplicitApprovals", value = "true")
-  public void globalCodeOwner_withImplicitApproval_bootstrappingMode() throws Exception {
-    testImplicitlyApprovedByGlobalCodeOwner(
-        /* implicitApprovalsEnabled= */ true, /* bootstrappingMode= */ true);
-  }
-
-  private void testImplicitlyApprovedByGlobalCodeOwner(
-      boolean implicitApprovalsEnabled, boolean bootstrappingMode) throws Exception {
+  private void testImplicitlyApprovedByGlobalCodeOwner(boolean implicitApprovalsEnabled)
+      throws Exception {
     TestAccount bot =
         accountCreator.create("bot", "bot@example.com", "Bot", /* displayName= */ null);
 
-    if (!bootstrappingMode) {
-      // Create a code owner config file so that we are not in the bootstrapping mode.
-      createArbitraryCodeOwnerConfigFile();
-    }
-
     Path path = Paths.get("/foo/bar.baz");
     String changeId =
         createChange(bot, "Change Adding A File", JgitPath.of(path).get(), "file content")
@@ -1068,25 +1013,10 @@
   @Test
   @GerritConfig(name = "plugin.code-owners.globalCodeOwner", value = "bot@example.com")
   public void globalCodeOwnerAsReviewer() throws Exception {
-    testGlobalCodeOwnerAsReviewer(/* bootstrappingMode= */ false);
-  }
-
-  @Test
-  @GerritConfig(name = "plugin.code-owners.globalCodeOwner", value = "bot@example.com")
-  public void globalCodeOwnerAsReviewer_bootstrappingMode() throws Exception {
-    testGlobalCodeOwnerAsReviewer(/* bootstrappingMode= */ true);
-  }
-
-  private void testGlobalCodeOwnerAsReviewer(boolean bootstrappingMode) throws Exception {
     // Create a bot user that is a global code owner.
     TestAccount bot =
         accountCreator.create("bot", "bot@example.com", "Bot", /* displayName= */ null);
 
-    if (!bootstrappingMode) {
-      // Create a code owner config file so that we are not in the bootstrapping mode.
-      createArbitraryCodeOwnerConfigFile();
-    }
-
     // Create a change as a user that is not a code owner.
     Path path = Paths.get("/foo/bar.baz");
     String changeId =
@@ -1143,22 +1073,6 @@
   @Test
   @GerritConfig(name = "plugin.code-owners.globalCodeOwner", value = "*")
   public void approvedByAnyoneWhenEveryoneIsGlobalCodeOwner() throws Exception {
-    testApprovedByAnyoneWhenEveryoneIsGlobalCodeOwner(/* bootstrappingMode= */ false);
-  }
-
-  @Test
-  @GerritConfig(name = "plugin.code-owners.globalCodeOwner", value = "*")
-  public void approvedByAnyoneWhenEveryoneIsGlobalCodeOwner_bootstrappingMode() throws Exception {
-    testApprovedByAnyoneWhenEveryoneIsGlobalCodeOwner(/* bootstrappingMode= */ true);
-  }
-
-  private void testApprovedByAnyoneWhenEveryoneIsGlobalCodeOwner(boolean bootstrappingMode)
-      throws Exception {
-    if (!bootstrappingMode) {
-      // Create a code owner config file so that we are not in the bootstrapping mode.
-      createArbitraryCodeOwnerConfigFile();
-    }
-
     // Create a change.
     Path path = Paths.get("/foo/bar.baz");
     String changeId =
@@ -1197,7 +1111,7 @@
   @GerritConfig(name = "plugin.code-owners.globalCodeOwner", value = "*")
   public void everyoneIsGlobalCodeOwner_noImplicitApproval() throws Exception {
     testImplicitlyApprovedByGlobalCodeOwnerWhenEveryoneIsGlobalCodeOwner(
-        /* implicitApprovalsEnabled= */ false, /* bootstrappingMode= */ false);
+        /* implicitApprovalsEnabled= */ false);
   }
 
   @Test
@@ -1205,31 +1119,11 @@
   @GerritConfig(name = "plugin.code-owners.enableImplicitApprovals", value = "true")
   public void everyoneIsGlobalCodeOwner_withImplicitApproval() throws Exception {
     testImplicitlyApprovedByGlobalCodeOwnerWhenEveryoneIsGlobalCodeOwner(
-        /* implicitApprovalsEnabled= */ true, /* bootstrappingMode= */ false);
-  }
-
-  @Test
-  @GerritConfig(name = "plugin.code-owners.globalCodeOwner", value = "*")
-  public void everyoneIsGlobalCodeOwner_noImplicitApproval_bootstrappingMode() throws Exception {
-    testImplicitlyApprovedByGlobalCodeOwnerWhenEveryoneIsGlobalCodeOwner(
-        /* implicitApprovalsEnabled= */ false, /* bootstrappingMode= */ true);
-  }
-
-  @Test
-  @GerritConfig(name = "plugin.code-owners.globalCodeOwner", value = "*")
-  @GerritConfig(name = "plugin.code-owners.enableImplicitApprovals", value = "true")
-  public void everyoneIsGlobalCodeOwner_withImplicitApproval_bootstrappingMode() throws Exception {
-    testImplicitlyApprovedByGlobalCodeOwnerWhenEveryoneIsGlobalCodeOwner(
-        /* implicitApprovalsEnabled= */ true, /* bootstrappingMode= */ true);
+        /* implicitApprovalsEnabled= */ true);
   }
 
   private void testImplicitlyApprovedByGlobalCodeOwnerWhenEveryoneIsGlobalCodeOwner(
-      boolean implicitApprovalsEnabled, boolean bootstrappingMode) throws Exception {
-    if (!bootstrappingMode) {
-      // Create a code owner config file so that we are not in the bootstrapping mode.
-      createArbitraryCodeOwnerConfigFile();
-    }
-
+      boolean implicitApprovalsEnabled) throws Exception {
     // Create a change as a user that is a code owner only through the global code ownership.
     Path path = Paths.get("/foo/bar.baz");
     String changeId =
@@ -1253,22 +1147,6 @@
   @Test
   @GerritConfig(name = "plugin.code-owners.globalCodeOwner", value = "*")
   public void anyReviewerWhenEveryoneIsGlobalCodeOwner() throws Exception {
-    testAnyReviewerWhenEveryoneIsGlobalCodeOwner(/* bootstrappingMode= */ false);
-  }
-
-  @Test
-  @GerritConfig(name = "plugin.code-owners.globalCodeOwner", value = "*")
-  public void anyReviewerWhenEveryoneIsGlobalCodeOwner_bootstrappingMode() throws Exception {
-    testAnyReviewerWhenEveryoneIsGlobalCodeOwner(/* bootstrappingMode= */ true);
-  }
-
-  private void testAnyReviewerWhenEveryoneIsGlobalCodeOwner(boolean bootstrappingMode)
-      throws Exception {
-    if (!bootstrappingMode) {
-      // Create a code owner config file so that we are not in the bootstrapping mode.
-      createArbitraryCodeOwnerConfigFile();
-    }
-
     // Create a change as a user that is a code owner only through the global code ownership.
     Path path = Paths.get("/foo/bar.baz");
     String changeId =
@@ -1340,10 +1218,6 @@
   @Test
   @GerritConfig(name = "plugin.code-owners.overrideApproval", value = "Owners-Override+1")
   public void getStatus_overrideApprovesAllFiles() throws Exception {
-    // create arbitrary code owner config to avoid entering the bootstrapping code path in
-    // CodeOwnerApprovalCheck
-    createArbitraryCodeOwnerConfigFile();
-
     createOwnersOverrideLabel();
 
     // Create a change.
@@ -1392,10 +1266,6 @@
       name = "plugin.code-owners.overrideApproval",
       values = {"Owners-Override+1", "Another-Override+1"})
   public void getStatus_anyOverrideApprovesAllFiles() throws Exception {
-    // create arbitrary code owner config to avoid entering the bootstrapping code path in
-    // CodeOwnerApprovalCheck
-    createArbitraryCodeOwnerConfigFile();
-
     createOwnersOverrideLabel();
     createOwnersOverrideLabel("Another-Override");
 
@@ -1516,10 +1386,6 @@
   @Test
   @GerritConfig(name = "plugin.code-owners.overrideApproval", value = "Owners-Override+1")
   public void isSubmittableIfOverrideIsPresent() throws Exception {
-    // create arbitrary code owner config to avoid entering the bootstrapping code path in
-    // CodeOwnerApprovalCheck
-    createArbitraryCodeOwnerConfigFile();
-
     createOwnersOverrideLabel();
 
     // Create a change.
@@ -1550,10 +1416,6 @@
       name = "plugin.code-owners.overrideApproval",
       values = {"Owners-Override+1", "Another-Override+1"})
   public void isSubmittableIfAnyOverrideIsPresent() throws Exception {
-    // create arbitrary code owner config to avoid entering the bootstrapping code path in
-    // CodeOwnerApprovalCheck
-    createArbitraryCodeOwnerConfigFile();
-
     createOwnersOverrideLabel();
     createOwnersOverrideLabel("Another-Override");
 
@@ -1593,316 +1455,6 @@
   }
 
   @Test
-  public void bootstrappingGetStatus_insufficientReviewers() throws Exception {
-    // since no code owner config exists we are entering the bootstrapping code path in
-    // CodeOwnerApprovalCheck
-
-    TestAccount user2 = accountCreator.user2();
-    TestAccount user3 =
-        accountCreator.create("user3", "user3@example.com", "User3", /* displayName= */ null);
-
-    // Create change with a user that is not a project owner.
-    Path path = Paths.get("/foo/bar.baz");
-    String changeId =
-        createChange(user, "Change Adding A File", JgitPath.of(path).get(), "file content")
-            .getChangeId();
-
-    // Add a reviewer that is not a project owner.
-    gApi.changes().id(changeId).addReviewer(user2.email());
-
-    // Add a Code-Review+1 (= code owner approval) from a user that is not a project owner.
-    requestScopeOperations.setApiUser(user3.id());
-    recommend(changeId);
-
-    Stream<FileCodeOwnerStatus> fileCodeOwnerStatuses =
-        codeOwnerApprovalCheck.getFileStatuses(getChangeNotes(changeId));
-    FileCodeOwnerStatusSubject fileCodeOwnerStatusSubject =
-        assertThatStream(fileCodeOwnerStatuses).onlyElement();
-    fileCodeOwnerStatusSubject.hasNewPathStatus().value().hasPathThat().isEqualTo(path);
-    fileCodeOwnerStatusSubject
-        .hasNewPathStatus()
-        .value()
-        .hasStatusThat()
-        .isEqualTo(CodeOwnerStatus.INSUFFICIENT_REVIEWERS);
-    fileCodeOwnerStatusSubject.hasOldPathStatus().isEmpty();
-    fileCodeOwnerStatusSubject.hasChangedFile().isNoRename();
-    fileCodeOwnerStatusSubject.hasChangedFile().isNoDeletion();
-  }
-
-  @Test
-  public void bootstrappingGetStatus_pending() throws Exception {
-    // since no code owner config exists we are entering the bootstrapping code path in
-    // CodeOwnerApprovalCheck
-
-    TestAccount user2 = accountCreator.user2();
-
-    // Create change with a user that is not a project owner.
-    Path path = Paths.get("/foo/bar.baz");
-    String changeId =
-        createChange(user, "Change Adding A File", JgitPath.of(path).get(), "file content")
-            .getChangeId();
-
-    // Add a reviewer that is a project owner.
-    gApi.changes().id(changeId).addReviewer(admin.email());
-
-    // Add a Code-Review+1 (= code owner approval) from a user that is not a project owner.
-    requestScopeOperations.setApiUser(user2.id());
-    recommend(changeId);
-
-    Stream<FileCodeOwnerStatus> fileCodeOwnerStatuses =
-        codeOwnerApprovalCheck.getFileStatuses(getChangeNotes(changeId));
-    FileCodeOwnerStatusSubject fileCodeOwnerStatusSubject =
-        assertThatStream(fileCodeOwnerStatuses).onlyElement();
-    fileCodeOwnerStatusSubject.hasNewPathStatus().value().hasPathThat().isEqualTo(path);
-    fileCodeOwnerStatusSubject
-        .hasNewPathStatus()
-        .value()
-        .hasStatusThat()
-        .isEqualTo(CodeOwnerStatus.PENDING);
-    fileCodeOwnerStatusSubject.hasOldPathStatus().isEmpty();
-    fileCodeOwnerStatusSubject.hasChangedFile().isNoRename();
-    fileCodeOwnerStatusSubject.hasChangedFile().isNoDeletion();
-  }
-
-  @Test
-  public void bootstrappingGetStatus_approved() throws Exception {
-    // since no code owner config exists we are entering the bootstrapping code path in
-    // CodeOwnerApprovalCheck
-
-    // Create change with a user that is not a project owner.
-    Path path = Paths.get("/foo/bar.baz");
-    String changeId =
-        createChange(user, "Change Adding A File", JgitPath.of(path).get(), "file content")
-            .getChangeId();
-
-    // Add a Code-Review+1 from a project owner (by default this counts as code owner approval).
-    requestScopeOperations.setApiUser(admin.id());
-    recommend(changeId);
-
-    Stream<FileCodeOwnerStatus> fileCodeOwnerStatuses =
-        codeOwnerApprovalCheck.getFileStatuses(getChangeNotes(changeId));
-    FileCodeOwnerStatusSubject fileCodeOwnerStatusSubject =
-        assertThatStream(fileCodeOwnerStatuses).onlyElement();
-    fileCodeOwnerStatusSubject.hasNewPathStatus().value().hasPathThat().isEqualTo(path);
-    fileCodeOwnerStatusSubject
-        .hasNewPathStatus()
-        .value()
-        .hasStatusThat()
-        .isEqualTo(CodeOwnerStatus.APPROVED);
-    fileCodeOwnerStatusSubject.hasOldPathStatus().isEmpty();
-    fileCodeOwnerStatusSubject.hasChangedFile().isNoRename();
-    fileCodeOwnerStatusSubject.hasChangedFile().isNoDeletion();
-  }
-
-  @Test
-  public void bootstrappingGetStatus_noImplicitApprovalByPatchSetUploader() throws Exception {
-    testImplicitApprovalByPatchSetUploaderOnBootstrappingGetStatus(
-        /* implicitApprovalsEnabled= */ false);
-  }
-
-  @Test
-  @GerritConfig(name = "plugin.code-owners.enableImplicitApprovals", value = "true")
-  public void bootstrappingGetStatus_withImplicitApprovalByPatchSetUploader() throws Exception {
-    testImplicitApprovalByPatchSetUploaderOnBootstrappingGetStatus(
-        /* implicitApprovalsEnabled= */ true);
-  }
-
-  private void testImplicitApprovalByPatchSetUploaderOnBootstrappingGetStatus(
-      boolean implicitApprovalsEnabled) throws Exception {
-    // since no code owner config exists we are entering the bootstrapping code path in
-    // CodeOwnerApprovalCheck
-
-    // Create change with a user that is not a project owner.
-    Path path = Paths.get("/foo/bar.baz");
-    String changeId =
-        createChange(user, "Change Adding A File", JgitPath.of(path).get(), "file content")
-            .getChangeId();
-
-    // Amend change with a user that is a project owner.
-    amendChange(admin, changeId);
-
-    Stream<FileCodeOwnerStatus> fileCodeOwnerStatuses =
-        codeOwnerApprovalCheck.getFileStatuses(getChangeNotes(changeId));
-    FileCodeOwnerStatusSubject fileCodeOwnerStatusSubject =
-        assertThatStream(fileCodeOwnerStatuses).onlyElement();
-    fileCodeOwnerStatusSubject.hasNewPathStatus().value().hasPathThat().isEqualTo(path);
-    fileCodeOwnerStatusSubject
-        .hasNewPathStatus()
-        .value()
-        .hasStatusThat()
-        .isEqualTo(
-            implicitApprovalsEnabled
-                ? CodeOwnerStatus.APPROVED
-                : CodeOwnerStatus.INSUFFICIENT_REVIEWERS);
-    fileCodeOwnerStatusSubject.hasOldPathStatus().isEmpty();
-    fileCodeOwnerStatusSubject.hasChangedFile().isNoRename();
-    fileCodeOwnerStatusSubject.hasChangedFile().isNoDeletion();
-  }
-
-  @Test
-  @GerritConfig(name = "plugin.code-owners.enableImplicitApprovals", value = "true")
-  public void bootstrappingGetStatus_noImplicitlyApprovalByChangeOwner() throws Exception {
-    // since no code owner config exists we are entering the bootstrapping code path in
-    // CodeOwnerApprovalCheck
-
-    // Create change with a user that is a project owner.
-    Path path = Paths.get("/foo/bar.baz");
-    String changeId =
-        createChange("Change Adding A File", JgitPath.of(path).get(), "file content").getChangeId();
-
-    // Amend change with a user that is not a project owner.
-    amendChange(user, changeId);
-
-    Stream<FileCodeOwnerStatus> fileCodeOwnerStatuses =
-        codeOwnerApprovalCheck.getFileStatuses(getChangeNotes(changeId));
-    FileCodeOwnerStatusSubject fileCodeOwnerStatusSubject =
-        assertThatStream(fileCodeOwnerStatuses).onlyElement();
-    fileCodeOwnerStatusSubject.hasNewPathStatus().value().hasPathThat().isEqualTo(path);
-    fileCodeOwnerStatusSubject
-        .hasNewPathStatus()
-        .value()
-        .hasStatusThat()
-        .isEqualTo(CodeOwnerStatus.INSUFFICIENT_REVIEWERS);
-    fileCodeOwnerStatusSubject.hasOldPathStatus().isEmpty();
-    fileCodeOwnerStatusSubject.hasChangedFile().isNoRename();
-    fileCodeOwnerStatusSubject.hasChangedFile().isNoDeletion();
-  }
-
-  @Test
-  @GerritConfig(name = "plugin.code-owners.overrideApproval", value = "Owners-Override+1")
-  public void bootstrappingGetStatus_overrideApprovesAllFiles() throws Exception {
-    // since no code owner config exists we are entering the bootstrapping code path in
-    // CodeOwnerApprovalCheck
-
-    createOwnersOverrideLabel();
-
-    // Create a change with a user that is not a project owner.
-    TestRepository<InMemoryRepository> testRepo = cloneProject(project, user);
-    String changeId =
-        pushFactory
-            .create(
-                user.newIdent(),
-                testRepo,
-                "Test Change",
-                ImmutableMap.of(
-                    "foo/baz.config", "content",
-                    "bar/baz.config", "other content"))
-            .to("refs/for/master")
-            .getChangeId();
-
-    // Without Owners-Override approval the expected status is INSUFFICIENT_REVIEWERS.
-    for (FileCodeOwnerStatus fileCodeOwnerStatus :
-        codeOwnerApprovalCheck
-            .getFileStatuses(getChangeNotes(changeId))
-            .collect(toImmutableList())) {
-      assertThat(fileCodeOwnerStatus)
-          .hasNewPathStatus()
-          .value()
-          .hasStatusThat()
-          .isEqualTo(CodeOwnerStatus.INSUFFICIENT_REVIEWERS);
-    }
-
-    // Add an override approval.
-    gApi.changes().id(changeId).current().review(new ReviewInput().label("Owners-Override", 1));
-
-    // With Owners-Override approval the expected status is APPROVED.
-    for (FileCodeOwnerStatus fileCodeOwnerStatus :
-        codeOwnerApprovalCheck
-            .getFileStatuses(getChangeNotes(changeId))
-            .collect(toImmutableList())) {
-      assertThat(fileCodeOwnerStatus)
-          .hasNewPathStatus()
-          .value()
-          .hasStatusThat()
-          .isEqualTo(CodeOwnerStatus.APPROVED);
-    }
-  }
-
-  @Test
-  @GerritConfig(
-      name = "plugin.code-owners.overrideApproval",
-      values = {"Owners-Override+1", "Another-Override+1"})
-  public void bootstrappingGetStatus_anyOverrideApprovesAllFiles() throws Exception {
-    // since no code owner config exists we are entering the bootstrapping code path in
-    // CodeOwnerApprovalCheck
-
-    createOwnersOverrideLabel();
-    createOwnersOverrideLabel("Another-Override");
-
-    // Create a change with a user that is not a project owner.
-    TestRepository<InMemoryRepository> testRepo = cloneProject(project, user);
-    String changeId =
-        pushFactory
-            .create(
-                user.newIdent(),
-                testRepo,
-                "Test Change",
-                ImmutableMap.of(
-                    "foo/baz.config", "content",
-                    "bar/baz.config", "other content"))
-            .to("refs/for/master")
-            .getChangeId();
-
-    // Without override approval the expected status is INSUFFICIENT_REVIEWERS.
-    for (FileCodeOwnerStatus fileCodeOwnerStatus :
-        codeOwnerApprovalCheck
-            .getFileStatuses(getChangeNotes(changeId))
-            .collect(toImmutableList())) {
-      assertThat(fileCodeOwnerStatus)
-          .hasNewPathStatus()
-          .value()
-          .hasStatusThat()
-          .isEqualTo(CodeOwnerStatus.INSUFFICIENT_REVIEWERS);
-    }
-
-    // Add an override approval (by a user that is not a project owners, and hence no code owner).
-    requestScopeOperations.setApiUser(user.id());
-    gApi.changes().id(changeId).current().review(new ReviewInput().label("Owners-Override", 1));
-
-    // With override approval the expected status is APPROVED.
-    for (FileCodeOwnerStatus fileCodeOwnerStatus :
-        codeOwnerApprovalCheck
-            .getFileStatuses(getChangeNotes(changeId))
-            .collect(toImmutableList())) {
-      assertThat(fileCodeOwnerStatus)
-          .hasNewPathStatus()
-          .value()
-          .hasStatusThat()
-          .isEqualTo(CodeOwnerStatus.APPROVED);
-    }
-
-    // Delete the override approval.
-    gApi.changes().id(changeId).current().review(new ReviewInput().label("Owners-Override", 0));
-
-    // Without override approval the expected status is INSUFFICIENT_REVIEWERS.
-    for (FileCodeOwnerStatus fileCodeOwnerStatus :
-        codeOwnerApprovalCheck
-            .getFileStatuses(getChangeNotes(changeId))
-            .collect(toImmutableList())) {
-      assertThat(fileCodeOwnerStatus)
-          .hasNewPathStatus()
-          .value()
-          .hasStatusThat()
-          .isEqualTo(CodeOwnerStatus.INSUFFICIENT_REVIEWERS);
-    }
-
-    // Add another override approval.
-    gApi.changes().id(changeId).current().review(new ReviewInput().label("Another-Override", 1));
-
-    // With override approval the expected status is APPROVED.
-    for (FileCodeOwnerStatus fileCodeOwnerStatus :
-        codeOwnerApprovalCheck
-            .getFileStatuses(getChangeNotes(changeId))
-            .collect(toImmutableList())) {
-      assertThat(fileCodeOwnerStatus)
-          .hasNewPathStatus()
-          .value()
-          .hasStatusThat()
-          .isEqualTo(CodeOwnerStatus.APPROVED);
-    }
-  }
-
-  @Test
   public void getStatus_branchDeleted() throws Exception {
     String branchName = "tempBranch";
     createBranch(BranchNameKey.create(project, branchName));
@@ -2001,10 +1553,6 @@
     input.copyAnyScore = true;
     gApi.projects().name(project.get()).label("Owners-Override").update(input);
 
-    // create arbitrary code owner config to avoid entering the bootstrapping code path in
-    // CodeOwnerApprovalCheck
-    createArbitraryCodeOwnerConfigFile();
-
     // Create a change as a user that is not a code owner.
     Path path = Paths.get("/foo/bar.baz");
     String changeId =
@@ -2166,7 +1714,7 @@
   }
 
   @Test
-  public void noBootstrappingIfDefaultCodeOwnerConfigExists() throws Exception {
+  public void projectOwnersAreNoCodeOwnersIfDefaultCodeOwnerConfigExists() throws Exception {
     TestAccount user2 = accountCreator.user2();
 
     setAsDefaultCodeOwners(user);
@@ -2193,8 +1741,7 @@
     requestScopeOperations.setApiUser(admin.id());
     approve(changeId);
 
-    // Verify that the file is still approved yet (since we are not in bootstrapping mode, the
-    // project owner doesn't count as code owner).
+    // Verify that the file is not approved yet
     fileCodeOwnerStatuses = codeOwnerApprovalCheck.getFileStatuses(getChangeNotes(changeId));
     fileCodeOwnerStatusSubject = assertThatStream(fileCodeOwnerStatuses).onlyElement();
     fileCodeOwnerStatusSubject.hasNewPathStatus().value().hasPathThat().isEqualTo(path);
diff --git a/javatests/com/google/gerrit/plugins/codeowners/backend/CodeOwnerApprovalCheckWithAllUsersAsFallbackCodeOwnersTest.java b/javatests/com/google/gerrit/plugins/codeowners/backend/CodeOwnerApprovalCheckWithAllUsersAsFallbackCodeOwnersTest.java
index 4cf078c..2807eb4 100644
--- a/javatests/com/google/gerrit/plugins/codeowners/backend/CodeOwnerApprovalCheckWithAllUsersAsFallbackCodeOwnersTest.java
+++ b/javatests/com/google/gerrit/plugins/codeowners/backend/CodeOwnerApprovalCheckWithAllUsersAsFallbackCodeOwnersTest.java
@@ -36,7 +36,7 @@
 import org.junit.Before;
 import org.junit.Test;
 
-/** Tests for {@link CodeOwnerApprovalCheck} with fallback code owners. */
+/** Tests for {@link CodeOwnerApprovalCheck} with ALL_USERS as fallback code owners. */
 public class CodeOwnerApprovalCheckWithAllUsersAsFallbackCodeOwnersTest
     extends AbstractCodeOwnersTest {
   @Inject private ChangeNotes.Factory changeNotesFactory;
@@ -229,10 +229,6 @@
 
   @Test
   public void approvedByFallbackCodeOwner() throws Exception {
-    // create arbitrary code owner config to avoid entering the bootstrapping code path in
-    // CodeOwnerApprovalCheck
-    createArbitraryCodeOwnerConfigFile();
-
     Path path = Paths.get("/foo/bar.baz");
     String changeId =
         createChange("Change Adding A File", JgitPath.of(path).get(), "file content").getChangeId();
@@ -281,10 +277,6 @@
   @Test
   @GerritConfig(name = "plugin.code-owners.enableImplicitApprovals", value = "true")
   public void implicitlyApprovedByFallbackCodeOwner() throws Exception {
-    // create arbitrary code owner config to avoid entering the bootstrapping code path in
-    // CodeOwnerApprovalCheck
-    createArbitraryCodeOwnerConfigFile();
-
     Path path = Paths.get("/foo/bar.baz");
     String changeId =
         createChange("Change Adding A File", JgitPath.of(path).get(), "file content").getChangeId();
@@ -304,84 +296,6 @@
   }
 
   @Test
-  public void approvedByFallbackCodeOwner_bootstrappingMode() throws Exception {
-    // since no code owner config exists we are entering the bootstrapping code path in
-    // CodeOwnerApprovalCheck
-
-    TestAccount user2 = accountCreator.user2();
-
-    Path path = Paths.get("/foo/bar.baz");
-    String changeId =
-        createChange(user, "Change Adding A File", JgitPath.of(path).get(), "file content")
-            .getChangeId();
-
-    // Verify that the file is not approved yet (the change owner is a code owner, but
-    // implicit approvals are disabled).
-    Stream<FileCodeOwnerStatus> fileCodeOwnerStatuses =
-        codeOwnerApprovalCheck.getFileStatuses(getChangeNotes(changeId));
-    FileCodeOwnerStatusSubject fileCodeOwnerStatusSubject =
-        assertThatStream(fileCodeOwnerStatuses).onlyElement();
-    fileCodeOwnerStatusSubject.hasNewPathStatus().value().hasPathThat().isEqualTo(path);
-    fileCodeOwnerStatusSubject
-        .hasNewPathStatus()
-        .value()
-        .hasStatusThat()
-        .isEqualTo(CodeOwnerStatus.INSUFFICIENT_REVIEWERS);
-
-    // Add a user a fallback code owner as reviewer.
-    gApi.changes().id(changeId).addReviewer(user2.email());
-
-    // Verify that the status is pending now .
-    fileCodeOwnerStatuses = codeOwnerApprovalCheck.getFileStatuses(getChangeNotes(changeId));
-    fileCodeOwnerStatusSubject = assertThatStream(fileCodeOwnerStatuses).onlyElement();
-    fileCodeOwnerStatusSubject.hasNewPathStatus().value().hasPathThat().isEqualTo(path);
-    fileCodeOwnerStatusSubject
-        .hasNewPathStatus()
-        .value()
-        .hasStatusThat()
-        .isEqualTo(CodeOwnerStatus.PENDING);
-
-    // Add a Code-Review+1 (= code owner approval) from a fallback code owner.
-    requestScopeOperations.setApiUser(user2.id());
-    recommend(changeId);
-
-    // Verify that the status is approved now
-    fileCodeOwnerStatuses = codeOwnerApprovalCheck.getFileStatuses(getChangeNotes(changeId));
-    fileCodeOwnerStatusSubject = assertThatStream(fileCodeOwnerStatuses).onlyElement();
-    fileCodeOwnerStatusSubject.hasNewPathStatus().value().hasPathThat().isEqualTo(path);
-    fileCodeOwnerStatusSubject
-        .hasNewPathStatus()
-        .value()
-        .hasStatusThat()
-        .isEqualTo(CodeOwnerStatus.APPROVED);
-  }
-
-  @Test
-  @GerritConfig(name = "plugin.code-owners.enableImplicitApprovals", value = "true")
-  public void implicitlyApprovedByFallbackCodeOwner_bootstrappingMode() throws Exception {
-    // since no code owner config exists we are entering the bootstrapping code path in
-    // CodeOwnerApprovalCheck
-
-    Path path = Paths.get("/foo/bar.baz");
-    String changeId =
-        createChange(user, "Change Adding A File", JgitPath.of(path).get(), "file content")
-            .getChangeId();
-
-    // Verify that the file is approved (the change owner is a code owner and implicit approvals are
-    // enabled).
-    Stream<FileCodeOwnerStatus> fileCodeOwnerStatuses =
-        codeOwnerApprovalCheck.getFileStatuses(getChangeNotes(changeId));
-    FileCodeOwnerStatusSubject fileCodeOwnerStatusSubject =
-        assertThatStream(fileCodeOwnerStatuses).onlyElement();
-    fileCodeOwnerStatusSubject.hasNewPathStatus().value().hasPathThat().isEqualTo(path);
-    fileCodeOwnerStatusSubject
-        .hasNewPathStatus()
-        .value()
-        .hasStatusThat()
-        .isEqualTo(CodeOwnerStatus.APPROVED);
-  }
-
-  @Test
   public void notApprovedByFallbackCodeOwnerIfParentCodeOwnersIgnored() throws Exception {
     codeOwnerConfigOperations
         .newCodeOwnerConfig()
diff --git a/javatests/com/google/gerrit/plugins/codeowners/backend/CodeOwnerApprovalCheckWithProjectOwnersAsFallbackCodeOwnersTest.java b/javatests/com/google/gerrit/plugins/codeowners/backend/CodeOwnerApprovalCheckWithProjectOwnersAsFallbackCodeOwnersTest.java
new file mode 100644
index 0000000..15d0c51
--- /dev/null
+++ b/javatests/com/google/gerrit/plugins/codeowners/backend/CodeOwnerApprovalCheckWithProjectOwnersAsFallbackCodeOwnersTest.java
@@ -0,0 +1,616 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.plugins.codeowners.backend;
+
+import static com.google.common.collect.ImmutableList.toImmutableList;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allowLabel;
+import static com.google.gerrit.plugins.codeowners.testing.FileCodeOwnerStatusSubject.assertThat;
+import static com.google.gerrit.plugins.codeowners.testing.FileCodeOwnerStatusSubject.assertThatStream;
+import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.gerrit.acceptance.TestAccount;
+import com.google.gerrit.acceptance.config.GerritConfig;
+import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
+import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.extensions.api.changes.ReviewInput;
+import com.google.gerrit.plugins.codeowners.acceptance.AbstractCodeOwnersTest;
+import com.google.gerrit.plugins.codeowners.backend.config.GeneralConfig;
+import com.google.gerrit.plugins.codeowners.common.CodeOwnerStatus;
+import com.google.gerrit.plugins.codeowners.testing.FileCodeOwnerStatusSubject;
+import com.google.gerrit.plugins.codeowners.util.JgitPath;
+import com.google.gerrit.server.notedb.ChangeNotes;
+import com.google.gerrit.testing.ConfigSuite;
+import com.google.inject.Inject;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.stream.Stream;
+import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
+import org.eclipse.jgit.junit.TestRepository;
+import org.eclipse.jgit.lib.Config;
+import org.junit.Before;
+import org.junit.Test;
+
+/** Tests for {@link CodeOwnerApprovalCheck} with PROJECT_OWNERS as fallback code owners. */
+public class CodeOwnerApprovalCheckWithProjectOwnersAsFallbackCodeOwnersTest
+    extends AbstractCodeOwnersTest {
+  @Inject private ChangeNotes.Factory changeNotesFactory;
+  @Inject private RequestScopeOperations requestScopeOperations;
+  @Inject private ProjectOperations projectOperations;
+
+  private CodeOwnerApprovalCheck codeOwnerApprovalCheck;
+
+  /** Returns a {@code gerrit.config} that configures all users as fallback code owners. */
+  @ConfigSuite.Default
+  public static Config defaultConfig() {
+    Config cfg = new Config();
+    cfg.setEnum(
+        "plugin",
+        "code-owners",
+        GeneralConfig.KEY_FALLBACK_CODE_OWNERS,
+        FallbackCodeOwners.PROJECT_OWNERS);
+    return cfg;
+  }
+
+  @Before
+  public void setUpCodeOwnersPlugin() throws Exception {
+    codeOwnerApprovalCheck = plugin.getSysInjector().getInstance(CodeOwnerApprovalCheck.class);
+  }
+
+  @Test
+  @GerritConfig(name = "plugin.code-owners.globalCodeOwner", value = "bot@example.com")
+  public void approvedByGlobalCodeOwner() throws Exception {
+    // Create a bot user that is a global code owner.
+    TestAccount bot =
+        accountCreator.create("bot", "bot@example.com", "Bot", /* displayName= */ null);
+
+    // Create a change as a user that is not a code owner.
+    Path path = Paths.get("/foo/bar.baz");
+    String changeId =
+        createChange(user, "Change Adding A File", JgitPath.of(path).get(), "file content")
+            .getChangeId();
+
+    // Verify that the file is not approved yet.
+    Stream<FileCodeOwnerStatus> fileCodeOwnerStatuses =
+        codeOwnerApprovalCheck.getFileStatuses(getChangeNotes(changeId));
+    FileCodeOwnerStatusSubject fileCodeOwnerStatusSubject =
+        assertThatStream(fileCodeOwnerStatuses).onlyElement();
+    fileCodeOwnerStatusSubject.hasNewPathStatus().value().hasPathThat().isEqualTo(path);
+    fileCodeOwnerStatusSubject
+        .hasNewPathStatus()
+        .value()
+        .hasStatusThat()
+        .isEqualTo(CodeOwnerStatus.INSUFFICIENT_REVIEWERS);
+
+    // Let the bot approve the change.
+    projectOperations
+        .project(project)
+        .forUpdate()
+        .add(allowLabel("Code-Review").ref("refs/heads/*").group(REGISTERED_USERS).range(-2, +2))
+        .update();
+    requestScopeOperations.setApiUser(bot.id());
+    approve(changeId);
+
+    // Check that the file is approved now.
+    requestScopeOperations.setApiUser(admin.id());
+    fileCodeOwnerStatuses = codeOwnerApprovalCheck.getFileStatuses(getChangeNotes(changeId));
+    fileCodeOwnerStatusSubject = assertThatStream(fileCodeOwnerStatuses).onlyElement();
+    fileCodeOwnerStatusSubject.hasNewPathStatus().value().hasPathThat().isEqualTo(path);
+    fileCodeOwnerStatusSubject
+        .hasNewPathStatus()
+        .value()
+        .hasStatusThat()
+        .isEqualTo(CodeOwnerStatus.APPROVED);
+  }
+
+  @Test
+  @GerritConfig(name = "plugin.code-owners.globalCodeOwner", value = "bot@example.com")
+  public void globalCodeOwner_noImplicitApproval() throws Exception {
+    testImplicitlyApprovedByGlobalCodeOwner(/* implicitApprovalsEnabled= */ false);
+  }
+
+  @Test
+  @GerritConfig(name = "plugin.code-owners.globalCodeOwner", value = "bot@example.com")
+  @GerritConfig(name = "plugin.code-owners.enableImplicitApprovals", value = "true")
+  public void globalCodeOwner_withImplicitApproval() throws Exception {
+    testImplicitlyApprovedByGlobalCodeOwner(/* implicitApprovalsEnabled= */ true);
+  }
+
+  private void testImplicitlyApprovedByGlobalCodeOwner(boolean implicitApprovalsEnabled)
+      throws Exception {
+    TestAccount bot =
+        accountCreator.create("bot", "bot@example.com", "Bot", /* displayName= */ null);
+
+    Path path = Paths.get("/foo/bar.baz");
+    String changeId =
+        createChange(bot, "Change Adding A File", JgitPath.of(path).get(), "file content")
+            .getChangeId();
+
+    Stream<FileCodeOwnerStatus> fileCodeOwnerStatuses =
+        codeOwnerApprovalCheck.getFileStatuses(getChangeNotes(changeId));
+    FileCodeOwnerStatusSubject fileCodeOwnerStatusSubject =
+        assertThatStream(fileCodeOwnerStatuses).onlyElement();
+    fileCodeOwnerStatusSubject.hasNewPathStatus().value().hasPathThat().isEqualTo(path);
+    fileCodeOwnerStatusSubject
+        .hasNewPathStatus()
+        .value()
+        .hasStatusThat()
+        .isEqualTo(
+            implicitApprovalsEnabled
+                ? CodeOwnerStatus.APPROVED
+                : CodeOwnerStatus.INSUFFICIENT_REVIEWERS);
+  }
+
+  @Test
+  @GerritConfig(name = "plugin.code-owners.globalCodeOwner", value = "bot@example.com")
+  public void globalCodeOwnerAsReviewer() throws Exception {
+    // Create a bot user that is a global code owner.
+    TestAccount bot =
+        accountCreator.create("bot", "bot@example.com", "Bot", /* displayName= */ null);
+
+    // Create a change as a user that is not a code owner.
+    Path path = Paths.get("/foo/bar.baz");
+    String changeId =
+        createChange(user, "Change Adding A File", JgitPath.of(path).get(), "file content")
+            .getChangeId();
+
+    // Verify that the status of the file is INSUFFICIENT_REVIEWERS.
+    Stream<FileCodeOwnerStatus> fileCodeOwnerStatuses =
+        codeOwnerApprovalCheck.getFileStatuses(getChangeNotes(changeId));
+    FileCodeOwnerStatusSubject fileCodeOwnerStatusSubject =
+        assertThatStream(fileCodeOwnerStatuses).onlyElement();
+    fileCodeOwnerStatusSubject.hasNewPathStatus().value().hasPathThat().isEqualTo(path);
+    fileCodeOwnerStatusSubject
+        .hasNewPathStatus()
+        .value()
+        .hasStatusThat()
+        .isEqualTo(CodeOwnerStatus.INSUFFICIENT_REVIEWERS);
+
+    // Add the bot approve as reviewer.
+    gApi.changes().id(changeId).addReviewer(bot.email());
+
+    // Check that the status of the file is PENDING now.
+    requestScopeOperations.setApiUser(admin.id());
+    fileCodeOwnerStatuses = codeOwnerApprovalCheck.getFileStatuses(getChangeNotes(changeId));
+    fileCodeOwnerStatusSubject = assertThatStream(fileCodeOwnerStatuses).onlyElement();
+    fileCodeOwnerStatusSubject.hasNewPathStatus().value().hasPathThat().isEqualTo(path);
+    fileCodeOwnerStatusSubject
+        .hasNewPathStatus()
+        .value()
+        .hasStatusThat()
+        .isEqualTo(CodeOwnerStatus.PENDING);
+
+    // Let the bot approve the change.
+    projectOperations
+        .project(project)
+        .forUpdate()
+        .add(allowLabel("Code-Review").ref("refs/heads/*").group(REGISTERED_USERS).range(-2, +2))
+        .update();
+    requestScopeOperations.setApiUser(bot.id());
+    approve(changeId);
+
+    // Check that the file is approved now.
+    requestScopeOperations.setApiUser(admin.id());
+    fileCodeOwnerStatuses = codeOwnerApprovalCheck.getFileStatuses(getChangeNotes(changeId));
+    fileCodeOwnerStatusSubject = assertThatStream(fileCodeOwnerStatuses).onlyElement();
+    fileCodeOwnerStatusSubject.hasNewPathStatus().value().hasPathThat().isEqualTo(path);
+    fileCodeOwnerStatusSubject
+        .hasNewPathStatus()
+        .value()
+        .hasStatusThat()
+        .isEqualTo(CodeOwnerStatus.APPROVED);
+  }
+
+  @Test
+  @GerritConfig(name = "plugin.code-owners.globalCodeOwner", value = "*")
+  public void approvedByAnyoneWhenEveryoneIsGlobalCodeOwner() throws Exception {
+    // Create a change.
+    Path path = Paths.get("/foo/bar.baz");
+    String changeId =
+        createChange(user, "Change Adding A File", JgitPath.of(path).get(), "file content")
+            .getChangeId();
+
+    // Verify that the file is not approved yet (the change owner is a global code owner, but
+    // implicit approvals are disabled).
+    Stream<FileCodeOwnerStatus> fileCodeOwnerStatuses =
+        codeOwnerApprovalCheck.getFileStatuses(getChangeNotes(changeId));
+    FileCodeOwnerStatusSubject fileCodeOwnerStatusSubject =
+        assertThatStream(fileCodeOwnerStatuses).onlyElement();
+    fileCodeOwnerStatusSubject.hasNewPathStatus().value().hasPathThat().isEqualTo(path);
+    fileCodeOwnerStatusSubject
+        .hasNewPathStatus()
+        .value()
+        .hasStatusThat()
+        .isEqualTo(CodeOwnerStatus.INSUFFICIENT_REVIEWERS);
+
+    // Add an approval by a user that is a code owner only through the global code ownership.
+    approve(changeId);
+
+    // Check that the file is approved now.
+    requestScopeOperations.setApiUser(admin.id());
+    fileCodeOwnerStatuses = codeOwnerApprovalCheck.getFileStatuses(getChangeNotes(changeId));
+    fileCodeOwnerStatusSubject = assertThatStream(fileCodeOwnerStatuses).onlyElement();
+    fileCodeOwnerStatusSubject.hasNewPathStatus().value().hasPathThat().isEqualTo(path);
+    fileCodeOwnerStatusSubject
+        .hasNewPathStatus()
+        .value()
+        .hasStatusThat()
+        .isEqualTo(CodeOwnerStatus.APPROVED);
+  }
+
+  @Test
+  @GerritConfig(name = "plugin.code-owners.globalCodeOwner", value = "*")
+  public void everyoneIsGlobalCodeOwner_noImplicitApproval() throws Exception {
+    testImplicitlyApprovedByGlobalCodeOwnerWhenEveryoneIsGlobalCodeOwner(
+        /* implicitApprovalsEnabled= */ false);
+  }
+
+  @Test
+  @GerritConfig(name = "plugin.code-owners.globalCodeOwner", value = "*")
+  @GerritConfig(name = "plugin.code-owners.enableImplicitApprovals", value = "true")
+  public void everyoneIsGlobalCodeOwner_withImplicitApproval() throws Exception {
+    testImplicitlyApprovedByGlobalCodeOwnerWhenEveryoneIsGlobalCodeOwner(
+        /* implicitApprovalsEnabled= */ true);
+  }
+
+  private void testImplicitlyApprovedByGlobalCodeOwnerWhenEveryoneIsGlobalCodeOwner(
+      boolean implicitApprovalsEnabled) throws Exception {
+    // Create a change as a user that is a code owner only through the global code ownership.
+    Path path = Paths.get("/foo/bar.baz");
+    String changeId =
+        createChange("Change Adding A File", JgitPath.of(path).get(), "file content").getChangeId();
+
+    Stream<FileCodeOwnerStatus> fileCodeOwnerStatuses =
+        codeOwnerApprovalCheck.getFileStatuses(getChangeNotes(changeId));
+    FileCodeOwnerStatusSubject fileCodeOwnerStatusSubject =
+        assertThatStream(fileCodeOwnerStatuses).onlyElement();
+    fileCodeOwnerStatusSubject.hasNewPathStatus().value().hasPathThat().isEqualTo(path);
+    fileCodeOwnerStatusSubject
+        .hasNewPathStatus()
+        .value()
+        .hasStatusThat()
+        .isEqualTo(
+            implicitApprovalsEnabled
+                ? CodeOwnerStatus.APPROVED
+                : CodeOwnerStatus.INSUFFICIENT_REVIEWERS);
+  }
+
+  @Test
+  @GerritConfig(name = "plugin.code-owners.globalCodeOwner", value = "*")
+  public void anyReviewerWhenEveryoneIsGlobalCodeOwner() throws Exception {
+    // Create a change as a user that is a code owner only through the global code ownership.
+    Path path = Paths.get("/foo/bar.baz");
+    String changeId =
+        createChange("Change Adding A File", JgitPath.of(path).get(), "file content").getChangeId();
+
+    // Verify that the status of the file is INSUFFICIENT_REVIEWERS (since there is no implicit
+    // approval by default).
+    Stream<FileCodeOwnerStatus> fileCodeOwnerStatuses =
+        codeOwnerApprovalCheck.getFileStatuses(getChangeNotes(changeId));
+    FileCodeOwnerStatusSubject fileCodeOwnerStatusSubject =
+        assertThatStream(fileCodeOwnerStatuses).onlyElement();
+    fileCodeOwnerStatusSubject.hasNewPathStatus().value().hasPathThat().isEqualTo(path);
+    fileCodeOwnerStatusSubject
+        .hasNewPathStatus()
+        .value()
+        .hasStatusThat()
+        .isEqualTo(CodeOwnerStatus.INSUFFICIENT_REVIEWERS);
+
+    // Add a user as reviewer that is a code owner only through the global code ownership.
+    gApi.changes().id(changeId).addReviewer(user.email());
+
+    // Check that the status of the file is PENDING now.
+    fileCodeOwnerStatuses = codeOwnerApprovalCheck.getFileStatuses(getChangeNotes(changeId));
+    fileCodeOwnerStatusSubject = assertThatStream(fileCodeOwnerStatuses).onlyElement();
+    fileCodeOwnerStatusSubject.hasNewPathStatus().value().hasPathThat().isEqualTo(path);
+    fileCodeOwnerStatusSubject
+        .hasNewPathStatus()
+        .value()
+        .hasStatusThat()
+        .isEqualTo(CodeOwnerStatus.PENDING);
+  }
+
+  @Test
+  public void getStatus_insufficientReviewers() throws Exception {
+    TestAccount user2 = accountCreator.user2();
+    TestAccount user3 =
+        accountCreator.create("user3", "user3@example.com", "User3", /* displayName= */ null);
+
+    // Create change with a user that is not a project owner.
+    Path path = Paths.get("/foo/bar.baz");
+    String changeId =
+        createChange(user, "Change Adding A File", JgitPath.of(path).get(), "file content")
+            .getChangeId();
+
+    // Add a reviewer that is not a project owner.
+    gApi.changes().id(changeId).addReviewer(user2.email());
+
+    // Add a Code-Review+1 (= code owner approval) from a user that is not a project owner.
+    requestScopeOperations.setApiUser(user3.id());
+    recommend(changeId);
+
+    Stream<FileCodeOwnerStatus> fileCodeOwnerStatuses =
+        codeOwnerApprovalCheck.getFileStatuses(getChangeNotes(changeId));
+    FileCodeOwnerStatusSubject fileCodeOwnerStatusSubject =
+        assertThatStream(fileCodeOwnerStatuses).onlyElement();
+    fileCodeOwnerStatusSubject.hasNewPathStatus().value().hasPathThat().isEqualTo(path);
+    fileCodeOwnerStatusSubject
+        .hasNewPathStatus()
+        .value()
+        .hasStatusThat()
+        .isEqualTo(CodeOwnerStatus.INSUFFICIENT_REVIEWERS);
+    fileCodeOwnerStatusSubject.hasOldPathStatus().isEmpty();
+    fileCodeOwnerStatusSubject.hasChangedFile().isNoRename();
+    fileCodeOwnerStatusSubject.hasChangedFile().isNoDeletion();
+  }
+
+  @Test
+  public void getStatus_pending() throws Exception {
+    TestAccount user2 = accountCreator.user2();
+
+    // Create change with a user that is not a project owner.
+    Path path = Paths.get("/foo/bar.baz");
+    String changeId =
+        createChange(user, "Change Adding A File", JgitPath.of(path).get(), "file content")
+            .getChangeId();
+
+    // Add a reviewer that is a project owner.
+    gApi.changes().id(changeId).addReviewer(admin.email());
+
+    // Add a Code-Review+1 (= code owner approval) from a user that is not a project owner.
+    requestScopeOperations.setApiUser(user2.id());
+    recommend(changeId);
+
+    Stream<FileCodeOwnerStatus> fileCodeOwnerStatuses =
+        codeOwnerApprovalCheck.getFileStatuses(getChangeNotes(changeId));
+    FileCodeOwnerStatusSubject fileCodeOwnerStatusSubject =
+        assertThatStream(fileCodeOwnerStatuses).onlyElement();
+    fileCodeOwnerStatusSubject.hasNewPathStatus().value().hasPathThat().isEqualTo(path);
+    fileCodeOwnerStatusSubject
+        .hasNewPathStatus()
+        .value()
+        .hasStatusThat()
+        .isEqualTo(CodeOwnerStatus.PENDING);
+    fileCodeOwnerStatusSubject.hasOldPathStatus().isEmpty();
+    fileCodeOwnerStatusSubject.hasChangedFile().isNoRename();
+    fileCodeOwnerStatusSubject.hasChangedFile().isNoDeletion();
+  }
+
+  @Test
+  public void getStatus_approved() throws Exception {
+    // Create change with a user that is not a project owner.
+    Path path = Paths.get("/foo/bar.baz");
+    String changeId =
+        createChange(user, "Change Adding A File", JgitPath.of(path).get(), "file content")
+            .getChangeId();
+
+    // Add a Code-Review+1 from a project owner (by default this counts as code owner approval).
+    requestScopeOperations.setApiUser(admin.id());
+    recommend(changeId);
+
+    Stream<FileCodeOwnerStatus> fileCodeOwnerStatuses =
+        codeOwnerApprovalCheck.getFileStatuses(getChangeNotes(changeId));
+    FileCodeOwnerStatusSubject fileCodeOwnerStatusSubject =
+        assertThatStream(fileCodeOwnerStatuses).onlyElement();
+    fileCodeOwnerStatusSubject.hasNewPathStatus().value().hasPathThat().isEqualTo(path);
+    fileCodeOwnerStatusSubject
+        .hasNewPathStatus()
+        .value()
+        .hasStatusThat()
+        .isEqualTo(CodeOwnerStatus.APPROVED);
+    fileCodeOwnerStatusSubject.hasOldPathStatus().isEmpty();
+    fileCodeOwnerStatusSubject.hasChangedFile().isNoRename();
+    fileCodeOwnerStatusSubject.hasChangedFile().isNoDeletion();
+  }
+
+  @Test
+  public void getStatus_noImplicitApprovalByPatchSetUploader() throws Exception {
+    testImplicitApprovalByPatchSetUploaderOnGetStatus(/* implicitApprovalsEnabled= */ false);
+  }
+
+  @Test
+  @GerritConfig(name = "plugin.code-owners.enableImplicitApprovals", value = "true")
+  public void getStatus_withImplicitApprovalByPatchSetUploader() throws Exception {
+    testImplicitApprovalByPatchSetUploaderOnGetStatus(/* implicitApprovalsEnabled= */ true);
+  }
+
+  private void testImplicitApprovalByPatchSetUploaderOnGetStatus(boolean implicitApprovalsEnabled)
+      throws Exception {
+    // Create change with a user that is not a project owner.
+    Path path = Paths.get("/foo/bar.baz");
+    String changeId =
+        createChange(user, "Change Adding A File", JgitPath.of(path).get(), "file content")
+            .getChangeId();
+
+    // Amend change with a user that is a project owner.
+    amendChange(admin, changeId);
+
+    Stream<FileCodeOwnerStatus> fileCodeOwnerStatuses =
+        codeOwnerApprovalCheck.getFileStatuses(getChangeNotes(changeId));
+    FileCodeOwnerStatusSubject fileCodeOwnerStatusSubject =
+        assertThatStream(fileCodeOwnerStatuses).onlyElement();
+    fileCodeOwnerStatusSubject.hasNewPathStatus().value().hasPathThat().isEqualTo(path);
+    fileCodeOwnerStatusSubject
+        .hasNewPathStatus()
+        .value()
+        .hasStatusThat()
+        .isEqualTo(
+            implicitApprovalsEnabled
+                ? CodeOwnerStatus.APPROVED
+                : CodeOwnerStatus.INSUFFICIENT_REVIEWERS);
+    fileCodeOwnerStatusSubject.hasOldPathStatus().isEmpty();
+    fileCodeOwnerStatusSubject.hasChangedFile().isNoRename();
+    fileCodeOwnerStatusSubject.hasChangedFile().isNoDeletion();
+  }
+
+  @Test
+  @GerritConfig(name = "plugin.code-owners.enableImplicitApprovals", value = "true")
+  public void getStatus_noImplicitlyApprovalByChangeOwner() throws Exception {
+    // Create change with a user that is a project owner.
+    Path path = Paths.get("/foo/bar.baz");
+    String changeId =
+        createChange("Change Adding A File", JgitPath.of(path).get(), "file content").getChangeId();
+
+    // Amend change with a user that is not a project owner.
+    amendChange(user, changeId);
+
+    Stream<FileCodeOwnerStatus> fileCodeOwnerStatuses =
+        codeOwnerApprovalCheck.getFileStatuses(getChangeNotes(changeId));
+    FileCodeOwnerStatusSubject fileCodeOwnerStatusSubject =
+        assertThatStream(fileCodeOwnerStatuses).onlyElement();
+    fileCodeOwnerStatusSubject.hasNewPathStatus().value().hasPathThat().isEqualTo(path);
+    fileCodeOwnerStatusSubject
+        .hasNewPathStatus()
+        .value()
+        .hasStatusThat()
+        .isEqualTo(CodeOwnerStatus.INSUFFICIENT_REVIEWERS);
+    fileCodeOwnerStatusSubject.hasOldPathStatus().isEmpty();
+    fileCodeOwnerStatusSubject.hasChangedFile().isNoRename();
+    fileCodeOwnerStatusSubject.hasChangedFile().isNoDeletion();
+  }
+
+  @Test
+  @GerritConfig(name = "plugin.code-owners.overrideApproval", value = "Owners-Override+1")
+  public void getStatus_overrideApprovesAllFiles() throws Exception {
+    createOwnersOverrideLabel();
+
+    // Create a change with a user that is not a project owner.
+    TestRepository<InMemoryRepository> testRepo = cloneProject(project, user);
+    String changeId =
+        pushFactory
+            .create(
+                user.newIdent(),
+                testRepo,
+                "Test Change",
+                ImmutableMap.of(
+                    "foo/baz.config", "content",
+                    "bar/baz.config", "other content"))
+            .to("refs/for/master")
+            .getChangeId();
+
+    // Without Owners-Override approval the expected status is INSUFFICIENT_REVIEWERS.
+    for (FileCodeOwnerStatus fileCodeOwnerStatus :
+        codeOwnerApprovalCheck
+            .getFileStatuses(getChangeNotes(changeId))
+            .collect(toImmutableList())) {
+      assertThat(fileCodeOwnerStatus)
+          .hasNewPathStatus()
+          .value()
+          .hasStatusThat()
+          .isEqualTo(CodeOwnerStatus.INSUFFICIENT_REVIEWERS);
+    }
+
+    // Add an override approval.
+    gApi.changes().id(changeId).current().review(new ReviewInput().label("Owners-Override", 1));
+
+    // With Owners-Override approval the expected status is APPROVED.
+    for (FileCodeOwnerStatus fileCodeOwnerStatus :
+        codeOwnerApprovalCheck
+            .getFileStatuses(getChangeNotes(changeId))
+            .collect(toImmutableList())) {
+      assertThat(fileCodeOwnerStatus)
+          .hasNewPathStatus()
+          .value()
+          .hasStatusThat()
+          .isEqualTo(CodeOwnerStatus.APPROVED);
+    }
+  }
+
+  @Test
+  @GerritConfig(
+      name = "plugin.code-owners.overrideApproval",
+      values = {"Owners-Override+1", "Another-Override+1"})
+  public void getStatus_anyOverrideApprovesAllFiles() throws Exception {
+    createOwnersOverrideLabel();
+    createOwnersOverrideLabel("Another-Override");
+
+    // Create a change with a user that is not a project owner.
+    TestRepository<InMemoryRepository> testRepo = cloneProject(project, user);
+    String changeId =
+        pushFactory
+            .create(
+                user.newIdent(),
+                testRepo,
+                "Test Change",
+                ImmutableMap.of(
+                    "foo/baz.config", "content",
+                    "bar/baz.config", "other content"))
+            .to("refs/for/master")
+            .getChangeId();
+
+    // Without override approval the expected status is INSUFFICIENT_REVIEWERS.
+    for (FileCodeOwnerStatus fileCodeOwnerStatus :
+        codeOwnerApprovalCheck
+            .getFileStatuses(getChangeNotes(changeId))
+            .collect(toImmutableList())) {
+      assertThat(fileCodeOwnerStatus)
+          .hasNewPathStatus()
+          .value()
+          .hasStatusThat()
+          .isEqualTo(CodeOwnerStatus.INSUFFICIENT_REVIEWERS);
+    }
+
+    // Add an override approval (by a user that is not a project owners, and hence no code owner).
+    requestScopeOperations.setApiUser(user.id());
+    gApi.changes().id(changeId).current().review(new ReviewInput().label("Owners-Override", 1));
+
+    // With override approval the expected status is APPROVED.
+    for (FileCodeOwnerStatus fileCodeOwnerStatus :
+        codeOwnerApprovalCheck
+            .getFileStatuses(getChangeNotes(changeId))
+            .collect(toImmutableList())) {
+      assertThat(fileCodeOwnerStatus)
+          .hasNewPathStatus()
+          .value()
+          .hasStatusThat()
+          .isEqualTo(CodeOwnerStatus.APPROVED);
+    }
+
+    // Delete the override approval.
+    gApi.changes().id(changeId).current().review(new ReviewInput().label("Owners-Override", 0));
+
+    // Without override approval the expected status is INSUFFICIENT_REVIEWERS.
+    for (FileCodeOwnerStatus fileCodeOwnerStatus :
+        codeOwnerApprovalCheck
+            .getFileStatuses(getChangeNotes(changeId))
+            .collect(toImmutableList())) {
+      assertThat(fileCodeOwnerStatus)
+          .hasNewPathStatus()
+          .value()
+          .hasStatusThat()
+          .isEqualTo(CodeOwnerStatus.INSUFFICIENT_REVIEWERS);
+    }
+
+    // Add another override approval.
+    gApi.changes().id(changeId).current().review(new ReviewInput().label("Another-Override", 1));
+
+    // With override approval the expected status is APPROVED.
+    for (FileCodeOwnerStatus fileCodeOwnerStatus :
+        codeOwnerApprovalCheck
+            .getFileStatuses(getChangeNotes(changeId))
+            .collect(toImmutableList())) {
+      assertThat(fileCodeOwnerStatus)
+          .hasNewPathStatus()
+          .value()
+          .hasStatusThat()
+          .isEqualTo(CodeOwnerStatus.APPROVED);
+    }
+  }
+
+  private ChangeNotes getChangeNotes(String changeId) throws Exception {
+    return changeNotesFactory.create(project, Change.id(gApi.changes().id(changeId).get()._number));
+  }
+}
diff --git a/javatests/com/google/gerrit/plugins/codeowners/backend/CodeOwnerApprovalCheckWithSelfApprovalsIgnoredTest.java b/javatests/com/google/gerrit/plugins/codeowners/backend/CodeOwnerApprovalCheckWithSelfApprovalsIgnoredTest.java
index e6bb57f..3195389 100644
--- a/javatests/com/google/gerrit/plugins/codeowners/backend/CodeOwnerApprovalCheckWithSelfApprovalsIgnoredTest.java
+++ b/javatests/com/google/gerrit/plugins/codeowners/backend/CodeOwnerApprovalCheckWithSelfApprovalsIgnoredTest.java
@@ -274,10 +274,6 @@
 
   @Test
   public void notOverriddenByUploaderWhoIsChangeOwner() throws Exception {
-    // create arbitrary code owner config to avoid entering the bootstrapping code path in
-    // CodeOwnerApprovalCheck
-    createArbitraryCodeOwnerConfigFile();
-
     TestAccount changeOwner =
         accountCreator.create(
             "changeOwner", "changeOwner@example.com", "ChangeOwner", /* displayName= */ null);
@@ -317,10 +313,6 @@
 
   @Test
   public void overridenByChangeOwnerThatIsNotUploader() throws Exception {
-    // create arbitrary code owner config to avoid entering the bootstrapping code path in
-    // CodeOwnerApprovalCheck
-    createArbitraryCodeOwnerConfigFile();
-
     TestAccount changeOwner =
         accountCreator.create(
             "changeOwner", "changeOwner@example.com", "ChangeOwner", /* displayName= */ null);
@@ -363,10 +355,6 @@
 
   @Test
   public void notOverridenByUploader() throws Exception {
-    // create arbitrary code owner config to avoid entering the bootstrapping code path in
-    // CodeOwnerApprovalCheck
-    createArbitraryCodeOwnerConfigFile();
-
     TestAccount changeOwner =
         accountCreator.create(
             "changeOwner", "changeOwner@example.com", "ChangeOwner", /* displayName= */ null);
diff --git a/javatests/com/google/gerrit/plugins/codeowners/backend/CodeOwnerConfigScannerTest.java b/javatests/com/google/gerrit/plugins/codeowners/backend/CodeOwnerConfigScannerTest.java
index df2e602..9bc1bab 100644
--- a/javatests/com/google/gerrit/plugins/codeowners/backend/CodeOwnerConfigScannerTest.java
+++ b/javatests/com/google/gerrit/plugins/codeowners/backend/CodeOwnerConfigScannerTest.java
@@ -490,99 +490,6 @@
   }
 
   @Test
-  public void containsNoCodeOwnerConfigFile() throws Exception {
-    assertThat(
-            codeOwnerConfigScannerFactory
-                .create()
-                .containsAnyCodeOwnerConfigFile(BranchNameKey.create(project, "master")))
-        .isFalse();
-  }
-
-  @Test
-  public void containsACodeOwnerConfigFile() throws Exception {
-    codeOwnerConfigOperations
-        .newCodeOwnerConfig()
-        .project(project)
-        .branch("master")
-        .folderPath("/foo/bar/")
-        .fileName("OWNERS")
-        .addCodeOwnerEmail(admin.email())
-        .create();
-
-    assertThat(
-            codeOwnerConfigScannerFactory
-                .create()
-                .containsAnyCodeOwnerConfigFile(BranchNameKey.create(project, "master")))
-        .isTrue();
-  }
-
-  @Test
-  public void containsACodeOwnerConfigFile_defaultCodeOwnerConfigExists() throws Exception {
-    codeOwnerConfigOperations
-        .newCodeOwnerConfig()
-        .project(project)
-        .branch(RefNames.REFS_CONFIG)
-        .folderPath("/")
-        .addCodeOwnerEmail(admin.email())
-        .create();
-
-    assertThat(
-            codeOwnerConfigScannerFactory
-                .create()
-                .containsAnyCodeOwnerConfigFile(BranchNameKey.create(project, "master")))
-        .isTrue();
-  }
-
-  @Test
-  public void containsACodeOwnerConfigFile_defaultCodeOwnerConfigIsSkipped() throws Exception {
-    codeOwnerConfigOperations
-        .newCodeOwnerConfig()
-        .project(project)
-        .branch(RefNames.REFS_CONFIG)
-        .folderPath("/")
-        .addCodeOwnerEmail(admin.email())
-        .create();
-
-    assertThat(
-            codeOwnerConfigScannerFactory
-                .create()
-                .includeDefaultCodeOwnerConfig(false)
-                .containsAnyCodeOwnerConfigFile(BranchNameKey.create(project, "master")))
-        .isFalse();
-  }
-
-  @Test
-  public void containsACodeOwnerConfigFile_invalidCodeOwnerConfigFileExists() throws Exception {
-    createNonParseableCodeOwnerConfig("/OWNERS");
-
-    codeOwnerConfigOperations
-        .newCodeOwnerConfig()
-        .project(project)
-        .branch("master")
-        .folderPath("/foo/bar/")
-        .fileName("OWNERS")
-        .addCodeOwnerEmail(admin.email())
-        .create();
-
-    assertThat(
-            codeOwnerConfigScannerFactory
-                .create()
-                .containsAnyCodeOwnerConfigFile(BranchNameKey.create(project, "master")))
-        .isTrue();
-  }
-
-  @Test
-  public void containsOnlyInvalidCodeOwnerConfigFiles() throws Exception {
-    createNonParseableCodeOwnerConfig("/OWNERS");
-
-    assertThat(
-            codeOwnerConfigScannerFactory
-                .create()
-                .containsAnyCodeOwnerConfigFile(BranchNameKey.create(project, "master")))
-        .isTrue();
-  }
-
-  @Test
   public void visitorIsOnlyInvokedOnceForDefaultCodeOnwerConfigFileIfRefsMetaConfigIsScanned()
       throws Exception {
     CodeOwnerConfig.Key codeOwnerConfigKey =
diff --git a/javatests/com/google/gerrit/plugins/codeowners/backend/CodeOwnerSubmitRuleTest.java b/javatests/com/google/gerrit/plugins/codeowners/backend/CodeOwnerSubmitRuleTest.java
index 8e03935..b495bd4 100644
--- a/javatests/com/google/gerrit/plugins/codeowners/backend/CodeOwnerSubmitRuleTest.java
+++ b/javatests/com/google/gerrit/plugins/codeowners/backend/CodeOwnerSubmitRuleTest.java
@@ -51,10 +51,6 @@
 
   @Test
   public void notReady() throws Exception {
-    // create arbitrary code owner config to avoid entering the bootstrapping code path in
-    // CodeOwnerApprovalCheck
-    createArbitraryCodeOwnerConfigFile();
-
     ChangeData changeData = createChange().getChange();
     SubmitRecordSubject submitRecordSubject =
         assertThatOptional(codeOwnerSubmitRule.evaluate(changeData)).value();
diff --git a/javatests/com/google/gerrit/plugins/codeowners/restapi/CodeOwnerProjectConfigJsonTest.java b/javatests/com/google/gerrit/plugins/codeowners/restapi/CodeOwnerProjectConfigJsonTest.java
index 5861aa4..13a326d 100644
--- a/javatests/com/google/gerrit/plugins/codeowners/restapi/CodeOwnerProjectConfigJsonTest.java
+++ b/javatests/com/google/gerrit/plugins/codeowners/restapi/CodeOwnerProjectConfigJsonTest.java
@@ -33,7 +33,6 @@
 import com.google.gerrit.plugins.codeowners.api.CodeOwnersStatusInfo;
 import com.google.gerrit.plugins.codeowners.api.RequiredApprovalInfo;
 import com.google.gerrit.plugins.codeowners.backend.CodeOwnerBackendId;
-import com.google.gerrit.plugins.codeowners.backend.CodeOwnerConfigScanner;
 import com.google.gerrit.plugins.codeowners.backend.FallbackCodeOwners;
 import com.google.gerrit.plugins.codeowners.backend.config.CodeOwnersPluginConfiguration;
 import com.google.gerrit.plugins.codeowners.backend.config.RequiredApproval;
@@ -79,7 +78,6 @@
     codeOwnerProjectConfigJson =
         new CodeOwnerProjectConfigJson(
             codeOwnersPluginConfiguration,
-            plugin.getSysInjector().getInstance(CodeOwnerConfigScanner.Factory.class),
             plugin.getSysInjector().getInstance(new Key<Provider<ListBranches>>() {}));
     findOwnersBackend = plugin.getSysInjector().getInstance(FindOwnersBackend.class);
     protoBackend = plugin.getSysInjector().getInstance(ProtoBackend.class);
@@ -304,7 +302,6 @@
     assertThat(codeOwnerBranchConfigInfo.overrideApproval.get(0).label)
         .isEqualTo("Owners-Override");
     assertThat(codeOwnerBranchConfigInfo.overrideApproval.get(0).value).isEqualTo(1);
-    assertThat(codeOwnerBranchConfigInfo.noCodeOwnersDefined).isNull();
   }
 
   @Test
@@ -318,35 +315,6 @@
     assertThat(codeOwnerBranchConfigInfo.backendId).isNull();
     assertThat(codeOwnerBranchConfigInfo.requiredApproval).isNull();
     assertThat(codeOwnerBranchConfigInfo.overrideApproval).isNull();
-    assertThat(codeOwnerBranchConfigInfo.noCodeOwnersDefined).isNull();
-  }
-
-  @Test
-  public void formatCodeOwnerBranchConfig_bootstrappingMode() throws Exception {
-    createOwnersOverrideLabel();
-
-    when(codeOwnersPluginConfiguration.isDisabled(any(BranchNameKey.class))).thenReturn(false);
-    when(codeOwnersPluginConfiguration.getBackend(BranchNameKey.create(project, "master")))
-        .thenReturn(findOwnersBackend);
-    when(codeOwnersPluginConfiguration.getFileExtension(project)).thenReturn(Optional.of("foo"));
-    when(codeOwnersPluginConfiguration.getOverrideInfoUrl(project))
-        .thenReturn(Optional.of("http://foo.example.com"));
-    when(codeOwnersPluginConfiguration.getMergeCommitStrategy(project))
-        .thenReturn(MergeCommitStrategy.FILES_WITH_CONFLICT_RESOLUTION);
-    when(codeOwnersPluginConfiguration.getFallbackCodeOwners(project))
-        .thenReturn(FallbackCodeOwners.ALL_USERS);
-    when(codeOwnersPluginConfiguration.areImplicitApprovalsEnabled(project)).thenReturn(true);
-    when(codeOwnersPluginConfiguration.getRequiredApproval(project))
-        .thenReturn(RequiredApproval.create(getDefaultCodeReviewLabel(), (short) 2));
-    when(codeOwnersPluginConfiguration.getOverrideApproval(project))
-        .thenReturn(
-            ImmutableSet.of(
-                RequiredApproval.create(
-                    LabelType.withDefaultValues("Owners-Override"), (short) 1)));
-
-    CodeOwnerBranchConfigInfo codeOwnerBranchConfigInfo =
-        codeOwnerProjectConfigJson.format(createBranchResource("refs/heads/master"));
-    assertThat(codeOwnerBranchConfigInfo.noCodeOwnersDefined).isTrue();
   }
 
   private ProjectResource createProjectResource() {
diff --git a/resources/Documentation/config.md b/resources/Documentation/config.md
index 9c99988..8c036fb 100644
--- a/resources/Documentation/config.md
+++ b/resources/Documentation/config.md
@@ -301,13 +301,18 @@
         owners hasn't been explicity disabled in a relevant code owner config
         file and if there are no unresolved imports.\
         \
-        Can be `NONE` or `ALL_USERS`.\
+        Can be `NONE`, `PROJECT_OWNERS` or `ALL_USERS`.\
         \
         `NONE`:\
         Paths for which no code owners are defined are owned by no one. This
         means changes that touch these files can only be submitted with a code
         owner override.\
         \
+        `PROJECT_OWNERS`:\
+        Paths for which no code owners are defined are owned by the project
+        owners. This means changes to these paths can be approved by the project
+        owners.\
+        \
         `ALL_USERS`:\
         Paths for which no code owners are defined are owned by all users. This
         means changes to these paths can be approved by anyone. If [implicit
@@ -319,6 +324,9 @@
         this option is intended to be only used if requiring code owner
         approvals should not be enforced.\
         \
+        Please note that fallback code owners are not suggested as code owners
+        in the UI.
+        \
         Can be overridden per project by setting
         [codeOwners.fallbackCodeOwners](#codeOwnersFallbackCodeOwners) in
         `@PLUGIN@.config`.\
@@ -637,7 +645,7 @@
         defined. This policy only applies if the inheritance of parent code
         owners hasn't been explicity disabled in a relevant code owner config
         file and if there are no unresolved imports.\
-        Can be `NONE` or `ALL_USERS` (see
+        Can be `NONE`, `PROJECT_OWNERS` or `ALL_USERS` (see
         [plugin.@PLUGIN@.fallbackCodeOwners](#pluginCodeOwnersFallbackCodeOwners)
         for an explanation of these values).\
         Overrides the global setting
diff --git a/resources/Documentation/rest-api.md b/resources/Documentation/rest-api.md
index 00a2ac1..ffebc28 100644
--- a/resources/Documentation/rest-api.md
+++ b/resources/Documentation/rest-api.md
@@ -845,7 +845,6 @@
 | `backend_id`| optional | ID of the code owner backend that is configured for the branch. Not set if `disabled` is `true`.
 | `required_approval` | optional | The approval that is required from code owners to approve the files in a change as [RequiredApprovalInfo](#required-approval-info) entity. The required approval defines which approval counts as code owner approval. Any approval on this label with a value >= the given value is considered as code owner approval. Not set if `disabled` is `true`.
 | `override_approval` | optional | Approvals that count as override for the code owners submit check as a list of [RequiredApprovalInfo](#required-approval-info) entities (sorted alphabetically). If multiple approvals are returned, any of them is sufficient to override the code owners submit check. All returned override approvals are guarenteed to have distinct label names. Any approval on these labels with a value >= the given values is considered as code owner override. If unset, overriding the code owners submit check is disabled. Not set if `disabled` is `true`.
-| `no_code_owners_defined` | optional | Whether the branch doesn't contain any code owner config file yet. If a branch doesn't contain any code owner config file yet, the projects owners are considered as code owners. Once a first code owner config file is added to the branch, the project owners are no longer code owners (unless code ownership is granted to them via the code owner config file). Not set if `false` or if `disabled` is `true`.
 
 ---
 
diff --git a/resources/Documentation/user-guide.md b/resources/Documentation/user-guide.md
index d4308a1..a718d8e 100644
--- a/resources/Documentation/user-guide.md
+++ b/resources/Documentation/user-guide.md
@@ -169,16 +169,6 @@
 a [code owner override](#codeOwnerOverride). Please note that fallback code
 owners are not included in the [code owner suggestion](#codeOwnerSuggestion).
 
-If the destination branch doesn't contain any [code owner config
-file](#codeOwnerConfigFiles) at all yet and the project also doesn't have a
-[default code owner config file](backend-find-owners.html#defaultCodeOwnerConfiguration),
-the project owners are considered as code owners and can grant [code owner
-approvals](#codeOwnerApproval) for all files. This is to allow bootstrapping
-code owners and should be only a temporary state until the first [code owner
-config file](#codeOwnerConfigFiles) is added.  Please note that the [code owner
-suggestion](#codeOwnerSuggestion) isn't working if no code owners are defined
-yet (project owners will not be suggested in this case).
-
 ## <a id="renames">Renames
 
 A rename is treated as a deletion at the old path and a creation at the new