Merge changes Ibcc71ffc,Ia09cc5f4,I0701b269,I1dbfbc00,I9d4c4fda, ...

* changes:
  Do not show "Suggest owners" if code owners is disabled for a branch
  Let users know if no code owners are defined yet
  Handle restAPI errors
  Use CodeOwnersModel in suggest-owners
  Introduce CodeOwnersModel and ModelLoader and use them in UI elements
  Extract code-owners fetcher into a separate class
  Add cache for api calls
  Use async/await instead of promises
  Remove unused methods
  Fix eslint problems
diff --git a/java/com/google/gerrit/plugins/codeowners/api/BranchCodeOwners.java b/java/com/google/gerrit/plugins/codeowners/api/BranchCodeOwners.java
index 1836ca9..2b81afb 100644
--- a/java/com/google/gerrit/plugins/codeowners/api/BranchCodeOwners.java
+++ b/java/com/google/gerrit/plugins/codeowners/api/BranchCodeOwners.java
@@ -32,8 +32,20 @@
   CodeOwnerConfigFilesRequest codeOwnerConfigFiles() throws RestApiException;
 
   abstract class CodeOwnerConfigFilesRequest {
+    private boolean includeNonParsableFiles;
     private String email;
 
+    /** Includes non-parsable code owner config files into the result. */
+    public CodeOwnerConfigFilesRequest includeNonParsableFiles(boolean includeNonParsableFiles) {
+      this.includeNonParsableFiles = includeNonParsableFiles;
+      return this;
+    }
+
+    /** Whether non-parsable code owner config files should be included into the result. */
+    public boolean getIncludeNonParsableFiles() {
+      return includeNonParsableFiles;
+    }
+
     /**
      * Limits the returned code owner config files to those that contain the given email.
      *
diff --git a/java/com/google/gerrit/plugins/codeowners/api/BranchCodeOwnersImpl.java b/java/com/google/gerrit/plugins/codeowners/api/BranchCodeOwnersImpl.java
index 6ed89a9..aacc3e2 100644
--- a/java/com/google/gerrit/plugins/codeowners/api/BranchCodeOwnersImpl.java
+++ b/java/com/google/gerrit/plugins/codeowners/api/BranchCodeOwnersImpl.java
@@ -60,6 +60,7 @@
       @Override
       public List<String> paths() throws RestApiException {
         GetCodeOwnerConfigFiles getCodeOwnerConfigFiles = getCodeOwnerConfigFilesProvider.get();
+        getCodeOwnerConfigFiles.setIncludeNonParsableFiles(getIncludeNonParsableFiles());
         getCodeOwnerConfigFiles.setEmail(getEmail());
         return getCodeOwnerConfigFiles.apply(branchResource).value();
       }
diff --git a/java/com/google/gerrit/plugins/codeowners/restapi/AbstractGetCodeOwnersForPath.java b/java/com/google/gerrit/plugins/codeowners/restapi/AbstractGetCodeOwnersForPath.java
index 854ffd7..a5f7b5d 100644
--- a/java/com/google/gerrit/plugins/codeowners/restapi/AbstractGetCodeOwnersForPath.java
+++ b/java/com/google/gerrit/plugins/codeowners/restapi/AbstractGetCodeOwnersForPath.java
@@ -31,6 +31,7 @@
 import com.google.gerrit.extensions.restapi.AuthException;
 import com.google.gerrit.extensions.restapi.BadRequestException;
 import com.google.gerrit.extensions.restapi.Response;
+import com.google.gerrit.extensions.restapi.RestReadView;
 import com.google.gerrit.plugins.codeowners.api.CodeOwnerInfo;
 import com.google.gerrit.plugins.codeowners.backend.CodeOwner;
 import com.google.gerrit.plugins.codeowners.backend.CodeOwnerConfigHierarchy;
@@ -43,7 +44,6 @@
 import com.google.gerrit.server.account.AccountDirectory.FillOptions;
 import com.google.gerrit.server.account.AccountLoader;
 import com.google.gerrit.server.account.Accounts;
-import com.google.gerrit.server.account.ServiceUserClassifier;
 import com.google.gerrit.server.permissions.GlobalPermission;
 import com.google.gerrit.server.permissions.PermissionBackend;
 import com.google.gerrit.server.permissions.PermissionBackendException;
@@ -63,7 +63,8 @@
  * Abstract base class for REST endpoints that get the code owners for an arbitrary path in a branch
  * or a revision of a change.
  */
-public abstract class AbstractGetCodeOwnersForPath {
+public abstract class AbstractGetCodeOwnersForPath<R extends AbstractPathResource>
+    implements RestReadView<R> {
   private static final FluentLogger logger = FluentLogger.forEnclosingClass();
 
   @VisibleForTesting public static final int DEFAULT_LIMIT = 10;
@@ -75,7 +76,6 @@
   private final CodeOwnersPluginConfiguration codeOwnersPluginConfiguration;
   private final CodeOwnerConfigHierarchy codeOwnerConfigHierarchy;
   private final Provider<CodeOwnerResolver> codeOwnerResolver;
-  private final ServiceUserClassifier serviceUserClassifier;
   private final CodeOwnerJson.Factory codeOwnerJsonFactory;
   private final EnumSet<ListAccountsOption> options;
   private final Set<String> hexOptions;
@@ -114,7 +114,6 @@
       CodeOwnersPluginConfiguration codeOwnersPluginConfiguration,
       CodeOwnerConfigHierarchy codeOwnerConfigHierarchy,
       Provider<CodeOwnerResolver> codeOwnerResolver,
-      ServiceUserClassifier serviceUserClassifier,
       CodeOwnerJson.Factory codeOwnerJsonFactory) {
     this.accountVisibility = accountVisibility;
     this.accounts = accounts;
@@ -123,13 +122,12 @@
     this.codeOwnersPluginConfiguration = codeOwnersPluginConfiguration;
     this.codeOwnerConfigHierarchy = codeOwnerConfigHierarchy;
     this.codeOwnerResolver = codeOwnerResolver;
-    this.serviceUserClassifier = serviceUserClassifier;
     this.codeOwnerJsonFactory = codeOwnerJsonFactory;
     this.options = EnumSet.noneOf(ListAccountsOption.class);
     this.hexOptions = new HashSet<>();
   }
 
-  protected Response<List<CodeOwnerInfo>> applyImpl(AbstractPathResource rsrc)
+  protected Response<List<CodeOwnerInfo>> applyImpl(R rsrc)
       throws AuthException, BadRequestException, PermissionBackendException {
     parseHexOptions();
     validateLimit();
@@ -227,9 +225,23 @@
    *       normally doesn't make sense since they will not react to review requests.
    * </ul>
    */
-  private ImmutableSet<CodeOwner> filterCodeOwners(
-      AbstractPathResource rsrc, ImmutableSet<CodeOwner> codeOwners) {
-    return codeOwners.stream()
+  private ImmutableSet<CodeOwner> filterCodeOwners(R rsrc, ImmutableSet<CodeOwner> codeOwners) {
+    return filterCodeOwners(rsrc, getVisibleCodeOwners(rsrc, codeOwners)).collect(toImmutableSet());
+  }
+
+  /**
+   * To be overridden by subclasses to filter out additional code owners.
+   *
+   * @param rsrc resource on which the request is being performed
+   * @param codeOwners stream of code owners that should be filtered
+   * @return the filtered stream of code owners
+   */
+  protected Stream<CodeOwner> filterCodeOwners(R rsrc, Stream<CodeOwner> codeOwners) {
+    return codeOwners;
+  }
+
+  private Stream<CodeOwner> getVisibleCodeOwners(R rsrc, ImmutableSet<CodeOwner> allCodeOwners) {
+    return allCodeOwners.stream()
         .filter(
             codeOwner -> {
               if (isVisibleTo(rsrc, codeOwner)) {
@@ -239,21 +251,11 @@
                   "Filtering out %s because this code owner cannot see the branch %s",
                   codeOwner, rsrc.getBranch().branch());
               return false;
-            })
-        .filter(
-            codeOwner -> {
-              if (!isServiceUser(codeOwner)) {
-                return true;
-              }
-              logger.atFine().log(
-                  "Filtering out %s because this code owner is a service user", codeOwner);
-              return false;
-            })
-        .collect(toImmutableSet());
+            });
   }
 
   /** Whether the given resource is visible to the given code owner. */
-  private boolean isVisibleTo(AbstractPathResource rsrc, CodeOwner codeOwner) {
+  private boolean isVisibleTo(R rsrc, CodeOwner codeOwner) {
     // We always check for the visibility of the branch.
     // This is also correct for the GetCodeOwnersForPathInChange subclass where branch is the
     // destination branch of the change. For changes the intention of the visibility check is to
@@ -271,11 +273,6 @@
         .testOrFalse(RefPermission.READ);
   }
 
-  /** Whether the given code owner is a service user. */
-  private boolean isServiceUser(CodeOwner codeOwner) {
-    return serviceUserClassifier.isServiceUser(codeOwner.accountId());
-  }
-
   private void parseHexOptions() throws BadRequestException {
     for (String hexOption : hexOptions) {
       try {
@@ -349,8 +346,7 @@
    * <p>Must be only used to complete the suggestion list when it is found that the path is owned by
    * all user.
    */
-  private void fillUpWithRandomUsers(
-      AbstractPathResource rsrc, Set<CodeOwner> codeOwners, int limit) {
+  private void fillUpWithRandomUsers(R rsrc, Set<CodeOwner> codeOwners, int limit) {
     if (codeOwners.size() >= limit) {
       // limit is already reach, we don't need to add further suggestions
       return;
diff --git a/java/com/google/gerrit/plugins/codeowners/restapi/CodeOwnersInChangeCollection.java b/java/com/google/gerrit/plugins/codeowners/restapi/CodeOwnersInChangeCollection.java
index e4a9455..ad0b1ad 100644
--- a/java/com/google/gerrit/plugins/codeowners/restapi/CodeOwnersInChangeCollection.java
+++ b/java/com/google/gerrit/plugins/codeowners/restapi/CodeOwnersInChangeCollection.java
@@ -142,8 +142,15 @@
       return new PathResource(revisionResource, branchRevision, parsePath(pathId));
     }
 
+    private final RevisionResource revisionResource;
+
     private PathResource(RevisionResource revisionResource, ObjectId branchRevision, Path path) {
       super(revisionResource, branchRevision, path);
+      this.revisionResource = revisionResource;
+    }
+
+    public RevisionResource getRevisionResource() {
+      return revisionResource;
     }
   }
 }
diff --git a/java/com/google/gerrit/plugins/codeowners/restapi/GetCodeOwnerConfigFiles.java b/java/com/google/gerrit/plugins/codeowners/restapi/GetCodeOwnerConfigFiles.java
index 0ebf942..8190e7c 100644
--- a/java/com/google/gerrit/plugins/codeowners/restapi/GetCodeOwnerConfigFiles.java
+++ b/java/com/google/gerrit/plugins/codeowners/restapi/GetCodeOwnerConfigFiles.java
@@ -20,6 +20,7 @@
 import com.google.common.collect.ImmutableList;
 import com.google.common.flogger.FluentLogger;
 import com.google.gerrit.common.Nullable;
+import com.google.gerrit.extensions.restapi.BadRequestException;
 import com.google.gerrit.extensions.restapi.Response;
 import com.google.gerrit.extensions.restapi.RestReadView;
 import com.google.gerrit.plugins.codeowners.backend.CodeOwnerBackend;
@@ -48,9 +49,17 @@
   private final CodeOwnersPluginConfiguration codeOwnersPluginConfiguration;
   private final CodeOwnerConfigScanner.Factory codeOwnerConfigScannerFactory;
 
+  private boolean includeNonParsableFiles;
   private String email;
 
   @Option(
+      name = "--include-non-parsable-files",
+      usage = "includes non-parseable code owner config files in the response")
+  public void setIncludeNonParsableFiles(boolean includeNonParsableFiles) {
+    this.includeNonParsableFiles = includeNonParsableFiles;
+  }
+
+  @Option(
       name = "--email",
       metaVar = "EMAIL",
       usage = "limits the returned code owner config files to those that contain this email")
@@ -67,7 +76,9 @@
   }
 
   @Override
-  public Response<List<String>> apply(BranchResource resource) {
+  public Response<List<String>> apply(BranchResource resource) throws BadRequestException {
+    validateOptions();
+
     CodeOwnerBackend codeOwnerBackend =
         codeOwnersPluginConfiguration.getBackend(resource.getBranchKey());
     ImmutableList.Builder<Path> codeOwnerConfigs = ImmutableList.builder();
@@ -93,7 +104,11 @@
               }
               return true;
             },
-            CodeOwnerConfigScanner.ignoreInvalidCodeOwnerConfigFiles());
+            includeNonParsableFiles
+                ? (codeOwnerConfigFilePath, configInvalidException) -> {
+                  codeOwnerConfigs.add(codeOwnerConfigFilePath);
+                }
+                : CodeOwnerConfigScanner.ignoreInvalidCodeOwnerConfigFiles());
     return Response.ok(
         codeOwnerConfigs.build().stream().map(Path::toString).collect(toImmutableList()));
   }
@@ -120,4 +135,11 @@
     }
     return containsEmail;
   }
+
+  private void validateOptions() throws BadRequestException {
+    if (email != null && includeNonParsableFiles) {
+      throw new BadRequestException(
+          "the options 'email' and 'include-non-parsable-files' are mutually exclusive");
+    }
+  }
 }
diff --git a/java/com/google/gerrit/plugins/codeowners/restapi/GetCodeOwnersForPathInBranch.java b/java/com/google/gerrit/plugins/codeowners/restapi/GetCodeOwnersForPathInBranch.java
index c2c22df..3fab537 100644
--- a/java/com/google/gerrit/plugins/codeowners/restapi/GetCodeOwnersForPathInBranch.java
+++ b/java/com/google/gerrit/plugins/codeowners/restapi/GetCodeOwnersForPathInBranch.java
@@ -22,14 +22,12 @@
 import com.google.gerrit.extensions.restapi.BadRequestException;
 import com.google.gerrit.extensions.restapi.Response;
 import com.google.gerrit.extensions.restapi.RestApiException;
-import com.google.gerrit.extensions.restapi.RestReadView;
 import com.google.gerrit.plugins.codeowners.api.CodeOwnerInfo;
 import com.google.gerrit.plugins.codeowners.backend.CodeOwnerConfigHierarchy;
 import com.google.gerrit.plugins.codeowners.backend.CodeOwnerResolver;
 import com.google.gerrit.plugins.codeowners.config.CodeOwnersPluginConfiguration;
 import com.google.gerrit.server.account.AccountControl;
 import com.google.gerrit.server.account.Accounts;
-import com.google.gerrit.server.account.ServiceUserClassifier;
 import com.google.gerrit.server.change.IncludedInResolver;
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.permissions.PermissionBackend;
@@ -56,8 +54,8 @@
  *
  * <p>The path may or may not exist in the branch.
  */
-public class GetCodeOwnersForPathInBranch extends AbstractGetCodeOwnersForPath
-    implements RestReadView<CodeOwnersInBranchCollection.PathResource> {
+public class GetCodeOwnersForPathInBranch
+    extends AbstractGetCodeOwnersForPath<CodeOwnersInBranchCollection.PathResource> {
   private final GitRepositoryManager repoManager;
   private String revision;
 
@@ -80,7 +78,6 @@
       CodeOwnersPluginConfiguration codeOwnersPluginConfiguration,
       CodeOwnerConfigHierarchy codeOwnerConfigHierarchy,
       Provider<CodeOwnerResolver> codeOwnerResolver,
-      ServiceUserClassifier serviceUserClassifier,
       CodeOwnerJson.Factory codeOwnerJsonFactory,
       GitRepositoryManager repoManager) {
     super(
@@ -91,7 +88,6 @@
         codeOwnersPluginConfiguration,
         codeOwnerConfigHierarchy,
         codeOwnerResolver,
-        serviceUserClassifier,
         codeOwnerJsonFactory);
     this.repoManager = repoManager;
   }
diff --git a/java/com/google/gerrit/plugins/codeowners/restapi/GetCodeOwnersForPathInChange.java b/java/com/google/gerrit/plugins/codeowners/restapi/GetCodeOwnersForPathInChange.java
index 3483614..c0465f3 100644
--- a/java/com/google/gerrit/plugins/codeowners/restapi/GetCodeOwnersForPathInChange.java
+++ b/java/com/google/gerrit/plugins/codeowners/restapi/GetCodeOwnersForPathInChange.java
@@ -14,11 +14,12 @@
 
 package com.google.gerrit.plugins.codeowners.restapi;
 
+import com.google.common.flogger.FluentLogger;
 import com.google.gerrit.extensions.common.AccountVisibility;
 import com.google.gerrit.extensions.restapi.Response;
 import com.google.gerrit.extensions.restapi.RestApiException;
-import com.google.gerrit.extensions.restapi.RestReadView;
 import com.google.gerrit.plugins.codeowners.api.CodeOwnerInfo;
+import com.google.gerrit.plugins.codeowners.backend.CodeOwner;
 import com.google.gerrit.plugins.codeowners.backend.CodeOwnerConfigHierarchy;
 import com.google.gerrit.plugins.codeowners.backend.CodeOwnerResolver;
 import com.google.gerrit.plugins.codeowners.config.CodeOwnersPluginConfiguration;
@@ -30,6 +31,8 @@
 import com.google.inject.Inject;
 import com.google.inject.Provider;
 import java.util.List;
+import java.util.function.Predicate;
+import java.util.stream.Stream;
 
 /**
  * REST endpoint that gets the code owners for an arbitrary path in a revision of a change.
@@ -39,8 +42,12 @@
  *
  * <p>The path may or may not exist in the revision of the change.
  */
-public class GetCodeOwnersForPathInChange extends AbstractGetCodeOwnersForPath
-    implements RestReadView<CodeOwnersInChangeCollection.PathResource> {
+public class GetCodeOwnersForPathInChange
+    extends AbstractGetCodeOwnersForPath<CodeOwnersInChangeCollection.PathResource> {
+  private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+
+  private final ServiceUserClassifier serviceUserClassifier;
+
   @Inject
   GetCodeOwnersForPathInChange(
       AccountVisibility accountVisibility,
@@ -60,8 +67,8 @@
         codeOwnersPluginConfiguration,
         codeOwnerConfigHierarchy,
         codeOwnerResolver,
-        serviceUserClassifier,
         codeOwnerJsonFactory);
+    this.serviceUserClassifier = serviceUserClassifier;
   }
 
   @Override
@@ -69,4 +76,41 @@
       throws RestApiException, PermissionBackendException {
     return super.applyImpl(rsrc);
   }
+
+  @Override
+  protected Stream<CodeOwner> filterCodeOwners(
+      CodeOwnersInChangeCollection.PathResource rsrc, Stream<CodeOwner> codeOwners) {
+    return codeOwners.filter(filterOutChangeOwner(rsrc)).filter(filterOutServiceUsers());
+  }
+
+  private Predicate<CodeOwner> filterOutChangeOwner(
+      CodeOwnersInChangeCollection.PathResource rsrc) {
+    return codeOwner -> {
+      if (!codeOwner.accountId().equals(rsrc.getRevisionResource().getChange().getOwner())) {
+        // Returning true from the Predicate here means that the code owner should be kept.
+        return true;
+      }
+      logger.atFine().log(
+          "Filtering out %s because this code owner is the change owner", codeOwner);
+      // Returning false from the Predicate here means that the code owner should be filtered out.
+      return false;
+    };
+  }
+
+  private Predicate<CodeOwner> filterOutServiceUsers() {
+    return codeOwner -> {
+      if (!isServiceUser(codeOwner)) {
+        // Returning true from the Predicate here means that the code owner should be kept.
+        return true;
+      }
+      logger.atFine().log("Filtering out %s because this code owner is a service user", codeOwner);
+      // Returning false from the Predicate here means that the code owner should be filtered out.
+      return false;
+    };
+  }
+
+  /** Whether the given code owner is a service user. */
+  private boolean isServiceUser(CodeOwner codeOwner) {
+    return serviceUserClassifier.isServiceUser(codeOwner.accountId());
+  }
 }
diff --git a/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/AbstractGetCodeOwnersForPathIT.java b/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/AbstractGetCodeOwnersForPathIT.java
index b9032cb..e6d8955 100644
--- a/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/AbstractGetCodeOwnersForPathIT.java
+++ b/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/AbstractGetCodeOwnersForPathIT.java
@@ -410,39 +410,6 @@
   }
 
   @Test
-  public void codeOwnersThatAreServiceUsersAreFilteredOut() throws Exception {
-    TestAccount serviceUser =
-        accountCreator.create("serviceUser", "service.user@example.com", "Service User", null);
-
-    // Create a code owner config with 2 code owners.
-    codeOwnerConfigOperations
-        .newCodeOwnerConfig()
-        .project(project)
-        .branch("master")
-        .folderPath("/")
-        .addCodeOwnerEmail(admin.email())
-        .addCodeOwnerEmail(serviceUser.email())
-        .create();
-
-    // Check that both code owners are suggested.
-    assertThat(queryCodeOwners("/foo/bar/baz.md"))
-        .comparingElementsUsing(hasAccountId())
-        .containsExactly(admin.id(), serviceUser.id());
-
-    // Make 'serviceUser' a service user.
-    groupOperations
-        .group(groupCache.get(AccountGroup.nameKey("Service Users")).get().getGroupUUID())
-        .forUpdate()
-        .addMember(serviceUser.id())
-        .update();
-
-    // Expect that 'serviceUser' is filtered out now.
-    assertThat(queryCodeOwners("/foo/bar/baz.md"))
-        .comparingElementsUsing(hasAccountId())
-        .containsExactly(admin.id());
-  }
-
-  @Test
   public void codeOwnersThatCannotSeeTheBranchAreFilteredOut() throws Exception {
     // Create a code owner config with 2 code owners.
     codeOwnerConfigOperations
@@ -714,19 +681,6 @@
   }
 
   @Test
-  @GerritConfig(name = "plugin.code-owners.globalCodeOwner", value = "service.user@example.com")
-  public void globalCodeOwnersThatAreServiceUsersAreFilteredOut() throws Exception {
-    TestAccount serviceUser =
-        accountCreator.create("serviceUser", "service.user@example.com", "Service User", null);
-    groupOperations
-        .group(groupCache.get(AccountGroup.nameKey("Service Users")).get().getGroupUUID())
-        .forUpdate()
-        .addMember(serviceUser.id())
-        .update();
-    assertThat(queryCodeOwners("/foo/bar/baz.md")).isEmpty();
-  }
-
-  @Test
   public void getDefaultCodeOwners() throws Exception {
     // Create default code owner config file in refs/meta/config.
     codeOwnerConfigOperations
diff --git a/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/GetCodeOwnerConfigFilesIT.java b/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/GetCodeOwnerConfigFilesIT.java
index 50b795b..284f01b 100644
--- a/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/GetCodeOwnerConfigFilesIT.java
+++ b/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/GetCodeOwnerConfigFilesIT.java
@@ -15,9 +15,11 @@
 package com.google.gerrit.plugins.codeowners.acceptance.api;
 
 import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
 
 import com.google.gerrit.acceptance.TestAccount;
 import com.google.gerrit.entities.RefNames;
+import com.google.gerrit.extensions.restapi.BadRequestException;
 import com.google.gerrit.plugins.codeowners.JgitPath;
 import com.google.gerrit.plugins.codeowners.acceptance.AbstractCodeOwnersIT;
 import com.google.gerrit.plugins.codeowners.backend.CodeOwnerBackend;
@@ -226,6 +228,50 @@
   }
 
   @Test
+  public void includeInvalidCodeOwnerConfigFiles() throws Exception {
+    String nameOfInvalidCodeOwnerConfigFile = getCodeOwnerConfigFileName();
+    createInvalidCodeOwnerConfig(nameOfInvalidCodeOwnerConfigFile);
+
+    CodeOwnerConfig.Key codeOwnerConfigKey =
+        codeOwnerConfigOperations
+            .newCodeOwnerConfig()
+            .project(project)
+            .branch("master")
+            .folderPath("/foo/")
+            .addCodeOwnerEmail(admin.email())
+            .create();
+
+    assertThat(
+            projectCodeOwnersApiFactory
+                .project(project)
+                .branch("master")
+                .codeOwnerConfigFiles()
+                .includeNonParsableFiles(true)
+                .paths())
+        .containsExactly(
+            JgitPath.of(nameOfInvalidCodeOwnerConfigFile).getAsAbsolutePath().toString(),
+            codeOwnerConfigOperations.codeOwnerConfig(codeOwnerConfigKey).getFilePath());
+  }
+
+  @Test
+  public void includeNonParsableFilesAndEmailOptionsAreMutuallyExclusive() throws Exception {
+    BadRequestException exception =
+        assertThrows(
+            BadRequestException.class,
+            () ->
+                projectCodeOwnersApiFactory
+                    .project(project)
+                    .branch("master")
+                    .codeOwnerConfigFiles()
+                    .includeNonParsableFiles(true)
+                    .withEmail(admin.email())
+                    .paths());
+    assertThat(exception)
+        .hasMessageThat()
+        .isEqualTo("the options 'email' and 'include-non-parsable-files' are mutually exclusive");
+  }
+
+  @Test
   public void filterByEmail() throws Exception {
     TestAccount user2 = accountCreator.user2();
     TestAccount user3 = accountCreator.create("user3", "user3@example.com", "User3", null);
diff --git a/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/GetCodeOwnersForPathInBranchIT.java b/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/GetCodeOwnersForPathInBranchIT.java
index db43d51..341beaf 100644
--- a/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/GetCodeOwnersForPathInBranchIT.java
+++ b/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/GetCodeOwnersForPathInBranchIT.java
@@ -18,7 +18,11 @@
 import static com.google.gerrit.plugins.codeowners.testing.CodeOwnerInfoSubject.hasAccountId;
 import static com.google.gerrit.testing.GerritJUnit.assertThrows;
 
+import com.google.gerrit.acceptance.TestAccount;
+import com.google.gerrit.acceptance.config.GerritConfig;
+import com.google.gerrit.acceptance.testsuite.group.GroupOperations;
 import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
+import com.google.gerrit.entities.AccountGroup;
 import com.google.gerrit.entities.RefNames;
 import com.google.gerrit.extensions.restapi.BadRequestException;
 import com.google.gerrit.extensions.restapi.RestApiException;
@@ -42,6 +46,7 @@
  */
 public class GetCodeOwnersForPathInBranchIT extends AbstractGetCodeOwnersForPathIT {
   @Inject private ProjectOperations projectOperations;
+  @Inject private GroupOperations groupOperations;
 
   @Override
   protected CodeOwners getCodeOwnersApi() throws RestApiException {
@@ -121,4 +126,47 @@
                     getCodeOwnersApi().query().forRevision(revision.name()), "/foo/bar/baz.md"));
     assertThat(exception).hasMessageThat().isEqualTo("unknown revision");
   }
+
+  @Test
+  public void codeOwnersThatAreServiceUsersAreIncluded() throws Exception {
+    TestAccount serviceUser =
+        accountCreator.create("serviceUser", "service.user@example.com", "Service User", null);
+
+    // Make 'serviceUser' a service user.
+    groupOperations
+        .group(groupCache.get(AccountGroup.nameKey("Service Users")).get().getGroupUUID())
+        .forUpdate()
+        .addMember(serviceUser.id())
+        .update();
+
+    // Create a code owner config with 2 code owners.
+    codeOwnerConfigOperations
+        .newCodeOwnerConfig()
+        .project(project)
+        .branch("master")
+        .folderPath("/")
+        .addCodeOwnerEmail(admin.email())
+        .addCodeOwnerEmail(serviceUser.email())
+        .create();
+
+    // Expect that 'serviceUser' is included.
+    assertThat(queryCodeOwners("/foo/bar/baz.md"))
+        .comparingElementsUsing(hasAccountId())
+        .containsExactly(admin.id(), serviceUser.id());
+  }
+
+  @Test
+  @GerritConfig(name = "plugin.code-owners.globalCodeOwner", value = "service.user@example.com")
+  public void globalCodeOwnersThatAreServiceUsersAreIncluded() throws Exception {
+    TestAccount serviceUser =
+        accountCreator.create("serviceUser", "service.user@example.com", "Service User", null);
+    groupOperations
+        .group(groupCache.get(AccountGroup.nameKey("Service Users")).get().getGroupUUID())
+        .forUpdate()
+        .addMember(serviceUser.id())
+        .update();
+    assertThat(queryCodeOwners("/foo/bar/baz.md"))
+        .comparingElementsUsing(hasAccountId())
+        .containsExactly(serviceUser.id());
+  }
 }
diff --git a/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/GetCodeOwnersForPathInChangeIT.java b/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/GetCodeOwnersForPathInChangeIT.java
index 428cc38..9b05857 100644
--- a/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/GetCodeOwnersForPathInChangeIT.java
+++ b/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/GetCodeOwnersForPathInChangeIT.java
@@ -21,8 +21,12 @@
 import static java.util.stream.Collectors.toMap;
 
 import com.google.gerrit.acceptance.PushOneCommit;
+import com.google.gerrit.acceptance.TestAccount;
+import com.google.gerrit.acceptance.config.GerritConfig;
+import com.google.gerrit.acceptance.testsuite.group.GroupOperations;
 import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
 import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations;
+import com.google.gerrit.entities.AccountGroup;
 import com.google.gerrit.extensions.restapi.BadRequestException;
 import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
 import com.google.gerrit.extensions.restapi.RestApiException;
@@ -31,6 +35,8 @@
 import com.google.gerrit.plugins.codeowners.api.CodeOwners;
 import com.google.inject.Inject;
 import java.util.List;
+import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
+import org.eclipse.jgit.junit.TestRepository;
 import org.eclipse.jgit.revwalk.RevCommit;
 import org.junit.Before;
 import org.junit.Test;
@@ -47,17 +53,27 @@
 public class GetCodeOwnersForPathInChangeIT extends AbstractGetCodeOwnersForPathIT {
   @Inject private RequestScopeOperations requestScopeOperations;
   @Inject private ProjectOperations projectOperations;
+  @Inject private GroupOperations groupOperations;
 
+  private TestAccount changeOwner;
   private String changeId;
 
   @Before
   public void createTestChange() throws Exception {
+    changeOwner =
+        accountCreator.create(
+            "changeOwner",
+            "changeOwner@example.com",
+            "ChangeOwner",
+            /** displayName = */
+            null);
+    TestRepository<InMemoryRepository> testRepo = cloneProject(project, changeOwner);
     // Create a change that contains files for all paths that are used in the tests. This is
     // necessary since CodeOwnersInChangeCollection rejects requests for paths that are not present
     // in the change.
     PushOneCommit push =
         pushFactory.create(
-            admin.newIdent(),
+            changeOwner.newIdent(),
             testRepo,
             "test change",
             TEST_PATHS.stream()
@@ -91,7 +107,7 @@
         .project(project)
         .branch("master")
         .folderPath("/foo/bar/")
-        .addCodeOwnerEmail(admin.email())
+        .addCodeOwnerEmail(user.email())
         .create();
 
     String path = "/foo/bar/baz.txt";
@@ -99,17 +115,19 @@
 
     List<CodeOwnerInfo> codeOwnerInfos =
         codeOwnersApiFactory.change(changeId, "current").query().get(path);
-    assertThat(codeOwnerInfos).comparingElementsUsing(hasAccountId()).containsExactly(admin.id());
+    assertThat(codeOwnerInfos).comparingElementsUsing(hasAccountId()).containsExactly(user.id());
   }
 
   @Test
   public void getCodeOwnersForRenamedFile() throws Exception {
+    TestAccount user2 = accountCreator.user2();
+
     codeOwnerConfigOperations
         .newCodeOwnerConfig()
         .project(project)
         .branch("master")
         .folderPath("/foo/new/")
-        .addCodeOwnerEmail(admin.email())
+        .addCodeOwnerEmail(user.email())
         .create();
 
     codeOwnerConfigOperations
@@ -117,7 +135,7 @@
         .project(project)
         .branch("master")
         .folderPath("/foo/old/")
-        .addCodeOwnerEmail(user.email())
+        .addCodeOwnerEmail(user2.email())
         .create();
 
     String oldPath = "/foo/old/bar.txt";
@@ -128,13 +146,13 @@
         codeOwnersApiFactory.change(changeId, "current").query().get(newPath);
     assertThat(codeOwnerInfosNewPath)
         .comparingElementsUsing(hasAccountId())
-        .containsExactly(admin.id());
+        .containsExactly(user.id());
 
     List<CodeOwnerInfo> codeOwnerInfosOldPath =
         codeOwnersApiFactory.change(changeId, "current").query().get(oldPath);
     assertThat(codeOwnerInfosOldPath)
         .comparingElementsUsing(hasAccountId())
-        .containsExactly(user.id());
+        .containsExactly(user2.id());
   }
 
   @Test
@@ -169,9 +187,71 @@
 
     // Check that 'user' is anyway suggested as code owner for the file in the private change since
     // by adding 'user' as reviewer the change would get visible to 'user'.
-    requestScopeOperations.setApiUser(admin.id());
+    requestScopeOperations.setApiUser(changeOwner.id());
     List<CodeOwnerInfo> codeOwnerInfos =
         codeOwnersApiFactory.change(changeId, "current").query().get(TEST_PATHS.get(0));
     assertThat(codeOwnerInfos).comparingElementsUsing(hasAccountId()).containsExactly(user.id());
   }
+
+  @Test
+  public void codeOwnersThatAreServiceUsersAreFilteredOut() throws Exception {
+    TestAccount serviceUser =
+        accountCreator.create("serviceUser", "service.user@example.com", "Service User", null);
+
+    // Create a code owner config with 2 code owners.
+    codeOwnerConfigOperations
+        .newCodeOwnerConfig()
+        .project(project)
+        .branch("master")
+        .folderPath("/")
+        .addCodeOwnerEmail(admin.email())
+        .addCodeOwnerEmail(serviceUser.email())
+        .create();
+
+    // Check that both code owners are suggested.
+    assertThat(queryCodeOwners("/foo/bar/baz.md"))
+        .comparingElementsUsing(hasAccountId())
+        .containsExactly(admin.id(), serviceUser.id());
+
+    // Make 'serviceUser' a service user.
+    groupOperations
+        .group(groupCache.get(AccountGroup.nameKey("Service Users")).get().getGroupUUID())
+        .forUpdate()
+        .addMember(serviceUser.id())
+        .update();
+
+    // Expect that 'serviceUser' is filtered out now.
+    assertThat(queryCodeOwners("/foo/bar/baz.md"))
+        .comparingElementsUsing(hasAccountId())
+        .containsExactly(admin.id());
+  }
+
+  @Test
+  @GerritConfig(name = "plugin.code-owners.globalCodeOwner", value = "service.user@example.com")
+  public void globalCodeOwnersThatAreServiceUsersAreFilteredOut() throws Exception {
+    TestAccount serviceUser =
+        accountCreator.create("serviceUser", "service.user@example.com", "Service User", null);
+    groupOperations
+        .group(groupCache.get(AccountGroup.nameKey("Service Users")).get().getGroupUUID())
+        .forUpdate()
+        .addMember(serviceUser.id())
+        .update();
+    assertThat(queryCodeOwners("/foo/bar/baz.md")).isEmpty();
+  }
+
+  @Test
+  public void changeOwnerIsFilteredOut() throws Exception {
+    codeOwnerConfigOperations
+        .newCodeOwnerConfig()
+        .project(project)
+        .branch("master")
+        .folderPath("/")
+        .addCodeOwnerEmail(changeOwner.email())
+        .addCodeOwnerEmail(user.email())
+        .create();
+
+    assertThat(queryCodeOwners("/foo/bar/baz.md"))
+        .comparingElementsUsing(hasAccountId())
+        .containsExactly(user.id());
+  }
 }
diff --git a/javatests/com/google/gerrit/plugins/codeowners/acceptance/restapi/GetCodeOwnersForPathInChangeRestIT.java b/javatests/com/google/gerrit/plugins/codeowners/acceptance/restapi/GetCodeOwnersForPathInChangeRestIT.java
index 55a1e05..5583fdc 100644
--- a/javatests/com/google/gerrit/plugins/codeowners/acceptance/restapi/GetCodeOwnersForPathInChangeRestIT.java
+++ b/javatests/com/google/gerrit/plugins/codeowners/acceptance/restapi/GetCodeOwnersForPathInChangeRestIT.java
@@ -17,6 +17,7 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import com.google.gerrit.acceptance.RestResponse;
+import com.google.gerrit.acceptance.TestAccount;
 import com.google.gerrit.extensions.restapi.IdString;
 import com.google.gerrit.plugins.codeowners.JgitPath;
 import org.junit.Before;
@@ -37,10 +38,18 @@
 
   @Before
   public void createTestChange() throws Exception {
+    TestAccount changeOwner =
+        accountCreator.create(
+            "changeOwner",
+            "changeOwner@example.com",
+            "ChangeOwner",
+            /** displayName = */
+            null);
     // Create a change that contains the file that is used in the tests. This is necessary since
     // CodeOwnersInChangeCollection rejects requests for paths that are not present in the change.
     changeId =
-        createChange("Test Change", JgitPath.of(TEST_PATH).get(), "some content").getChangeId();
+        createChange(changeOwner, "Test Change", JgitPath.of(TEST_PATH).get(), "some content")
+            .getChangeId();
   }
 
   @Override
diff --git a/resources/Documentation/how-to-use.md b/resources/Documentation/how-to-use.md
index 8a95561..d412c2c 100644
--- a/resources/Documentation/how-to-use.md
+++ b/resources/Documentation/how-to-use.md
@@ -1,12 +1,42 @@
 # Intro
 
-The Code-Owners plugin is currently in development. We are testing code-owners on some hosts on googlesource.com right now. If you build your Gerrit from master, you can enable it by enabling the code-owners plugin and adding OWNERS info to your code base.
+The `code-owners` plugin provides support for code owners in Gerrit and is
+replacing the `find-owners` plugin.
 
-The Code-Owner plugin is an open-source plugin and maintained by the Gerrit team at Google to replace find-owners plugin.
+For projects that used code owners with the `find-owners` plugin before, the
+existing `OWNERS` files continue to work and the only major difference is that
+the `code-owners` plugin comes with a new UI for selecting code owners and
+showing the code owner status.
+
+The `code-owners` plugin is an open-source plugin and is maintained by the
+Gerrit team at Google.
+
+This document focuses on the workflows in the UI. Further information can be
+found in the [backend user guide](user-guide.html).
+
+### Enable the plugin
+
+#### As a user
+
+You don’t need to do anything as the plugin is enabled by the host admin.
+
+#### As an admin
+
+The `code-owners` plugin is only supported for the Gerrit version that is
+currently developed in master. The first Gerrit release that supports the
+`code-owners` plugin is Gerrit 3.3.0.
+
+Before installing/enabling the plugin, or enabling the code owners functionality
+for further projects, it is important that the plugin is correctly configured.
+The required configuration is described in the plugin [setup
+guide](setup-guide.html).
 
 ### Bug report / Feedback
 
-Report a bug or send feedback using this [Monorail template](https://bugs.chromium.org/p/gerrit/issues/entry?template=code-owners-plugin). You can also report a bug through the bug icon in the reply dialog next to the Suggest Owners button.
+Report a bug or send feedback using this [Monorail
+template](https://bugs.chromium.org/p/gerrit/issues/entry?template=code-owners-plugin).
+You can also report a bug through the bug icon in the reply dialog next to the
+`HIDE OWNERS` / `SUGGEST OWNERS` button.
 
 ![suggest owners from reply dialog](./suggest-owners-from-reply-dialog.png "Suggest owners")
 
@@ -14,39 +44,83 @@
 
 ### Who are code owners?
 
-A code owner is a user whose approval is required to modify files under a certain path. Who is a code owner of a path is controlled via "OWNERS'' files that are checked into the repository. For submitting a change Gerrit requires that all files that were touched in the change are approved by a code owner. Code owners usually apply their approval by voting with "Code-Review+1" on the change. Their approval is to confirm that “This change is appropriate for our system and belongs in this directory."
+A code owner is a user whose approval is required to modify files under a
+certain path. Who is a code owner of a path is controlled via `OWNERS` files
+that are checked into the repository. For submitting a change Gerrit requires
+that all files that were touched in the change are approved by a code owner.
+Code owners usually apply their approval by voting with "Code-Review+1" on the
+change. Their approval is to confirm that “This change is appropriate for our
+system and belongs in this directory."
 
 ### Why do we leverage Code Owners?
 
-Owners are gatekeepers before a CL is submitted, they enforce standards across the code base, help disseminate knowledge around their specific area of ownership, ensure their is appropriate code review coverage, and provide timely reviews. Code owners is designed as a code quality feature to ensure someone familiar with the code base reviews any changes to the codebase or a subset of the codebase they are the Owner of, by making sure the change is appropriate for the system.
+Owners are gatekeepers before a CL is submitted, they enforce standards across
+the code base, help disseminate knowledge around their specific area of
+ownership, ensure their is appropriate code review coverage, and provide timely
+reviews. Code owners is designed as a code quality feature to ensure someone
+familiar with the code base reviews any changes to the codebase or a subset of
+the codebase they are the Owner of, by making sure the change is appropriate for
+the system.
 
-## What is the code-owners plugin?
+## What is the `code-owners` plugin?
 
 ### What is the benefit?
 
-Code owners in Gerrit will be supported by a new code-owners plugin which is developed as an open-source plugin and maintained by the Gerrit team at Google.
-The code-owners plugin supports:
+Code owners in Gerrit will be supported by a new `code-owners` plugin which is
+developed as an open-source plugin and maintained by the Gerrit team at Google.
 
-- defining code owners
+The `code-owners` plugin supports:
+
+- [defining code owners](#definingCodeOwners)
 - requiring owner approvals for submitting changes
 - displaying owner information in the UI & suggesting code owners as reviewers
 - overriding owner checks
+- a [REST API](rest-api.html) to inspect code owners
 
 ### How does it work?
 
-The plugin provides suggestions of owners for the directory or files that you are modifying in your change based on a score. It also informs you at a glance about the status of code-owners for the change and the status of code-owners per file.
+The plugin provides suggestions of owners for the directory or files that you
+are modifying in your change based on a score. It also informs you at a glance
+about the status of code owners for the change and the status of code owners per
+file.
 
 #### Score
 
-The Code-owners plugin suggests a maximum of 5 closest owners based on their score. The owner score is calculated based on the distance of owners to the files.
+The `code-owners` plugin suggests a maximum of 5 closest code owners based on
+their score. The code owner score is calculated based on the distance of code
+owners to the files.
+
+## <a id="definingCodeOwners">Defining code owners
+
+If you have used code owners via the `find-owners` plugin before, your code
+owners are already defined in `OWNERS` files and you don’t need to do anything
+since the new `code-owners` plugin just reads the existing `OWNERS` files.
+
+If you haven’t used code owners before, you can now define code owners in
+`OWNERS` files which are stored in the source tree. The code owners that are
+defined in an `OWNERS` file apply to the directory that contains the `OWNERS`
+file, and all its subdirectories (except if a subdirectory contains an `OWNERS`
+file that disables the inheritance of code owners from the parent directories).
+
+The syntax of `OWNERS` file is explained in the [backend
+documentation](backend-find-owners.html#syntax) and examples can be found in the
+[cookbook](backend-find-owners-cookbook.html).
+
+The code-owners plugin does not support an editor to create and edit `OWNERS`
+files from the UI. `OWNERS` files must be created and edited manually in the
+local repository and then be pushed to the remote repository, the same way as
+any other source file.
 
 ## <a id="addCodeOwnersAsReviewers">Add owners to your change
 
-1. To add owners of the files in your change, click on Suggest owners next to the Code-Owners submit requirement.
+1. To add owners of the files in your change, click on `SUGGEST OWNERS` next to
+   the `Code-Owners` submit requirement.
 
 ![suggest owners from change page](./suggest-owners-from-change-page.png "Suggest owners from change page")
 
-2. The Reply dialog opens with the Code-Owners section expanded by default with owners suggestions. Code-Owners are suggested by groups of files which share the same file-owners.
+2. The Reply dialog opens with the code owners section expanded by default with
+   owners suggestions. Code owners are suggested by groups of files which share
+   the same code owners.
 
 ![owner suggestions](./owner-suggestions.png "owner suggestions")
 
@@ -54,36 +128,42 @@
 
 ![suggestion file groups](./suggestions-file-groups.png "suggestion file groups")
 
-4. Click user chips to select owners for each file or group of files. The selected owner is automatically added to the Reviewers section and automatically selected on other files the code-owner owns in the change (if applicable).
+4. Click user chips to select code owners for each file or group of files. The
+   selected code owner is automatically added to the reviewers section and
+   automatically selected on other files the code owner owns in the change (if
+   applicable).
 
 ![add or modify reviewers from suggestions](./add-owner-to-reviewer.png "add owner to reviewer")
 
-5. Click Send to notify the owners you selected on your change.
+5. Click `SEND` to notify the code owners you selected on your change.
 
 ## Reply dialog use cases
 
 ### Approved files
 
-Once a file has received a +1 vote by the owner, the file disappears from the file list in the reply dialog. This lets you focus on the files that are not yet assigned to an owner or are pending approval.
+Once a file has received an approval vote by the code owner, the file disappears
+from the file list in the reply dialog. This lets you focus on the files that
+are not yet assigned to a code owner or are pending approval.
 
 ### No code owners found
 
-There are 3 possible reasons for encountering a “Not found” text:
+There are 3 possible reasons for encountering a "Not found" text:
 
 ![no owners found](./no-owners-found.png "no owners found")
 
 - No owners were defined for these files.
-  Reason: This could be due to missing OWNERS defined for these files.
+  Reason: This could be due to missing `OWNERS` defined for these files.
 
 - None of the code owners of these files are visible.
-  Reason: The owners accounts are not visible to you.
+  Reason: The code owners accounts are not visible to you.
 
-- Owners defined for these files are invalid.
+- Code owners defined for these files are invalid.
   Reason: The emails cannot be resolved.
 
 For these 3 cases, we advise you to:
 
-1. Ask someone with override powers (e.g. sheriff) to grant an override vote to unblock the change submission.
+1. Ask someone with override powers (e.g. sheriff) to grant an override vote to
+   unblock the change submission.
 2. Contact the project owner to ask them to fix the code owner definitions.
 
 ### Renamed files
@@ -92,7 +172,9 @@
 
 ![renamed file in code owners](./renamed-file-in-code-owners.png "Renamed files in code owners")
 
-Renamed files (new path) will have a “Renamed” chip attached to them. A renamed file will be considered as approved only if both old path/name and new path/name are approved.
+Renamed files (new path) will have a "Renamed" chip attached to them. A renamed
+file will be considered as approved only if both old path/name and new path/name
+are approved.
 
 ### Failed to fetch file
 
@@ -103,18 +185,24 @@
 
 ### Large change
 
-In case of a large change containing a large number of files (hundreds or even thousands), it will take some time to fetch all suggested owners.
-In the reply dialog, the plugin will show the overall status of the fetching and results as soon as it has results together with the loading indicator. The loading will disappear until all files finished fetching, failed files will be grouped into a single group.
+In case of a large change containing a large number of files (hundreds or even
+thousands), it will take some time to fetch all suggested code owners. In the
+reply dialog, the plugin will show the overall status of the fetching and
+results as soon as it has results together with the loading indicator. The
+loading will disappear until all files finished fetching, failed files will be
+grouped into a single group.
 
-The fetching of suggested owners should not block the reply itself. So you still can select from suggestions even when not all files are finished and sent for reviewing.
+The fetching of suggested code owners should not block the reply itself. So you
+still can select from suggestions even when not all files are finished and sent
+for reviewing.
 
 ## Change page overview
 
-In the change page, you can get an overview of the Code-Owners statuses.
+In the change page, you can get an overview of the code owner statuses.
 
-If applicable, the Code-Owner status is displayed:
+If applicable, the code owner status is displayed:
 
-- Next to the Code-Owners submit requirement
+- Next to the `Code-Owners` submit requirement
 
 ![submit requirement](./submit-requirement.png "Submit requirement")
 
@@ -122,41 +210,48 @@
 
 ![owner status](./owner-status.png "Owner status")
 
-### Code-owner status
+### Code owner status
 
-#### Code-owner label
+#### `Code-Owners` submit requirement
 
-The Code-Owner label is providing an overview about the owners status at a glance.
+The `Code-Owners` submit requirement is providing an overview about the code
+owner status at a glance.
 
-- Missing a reviewer that can grant the code-owner approval
-- Pending code-owner approval
-- Approved by code-owner
+- Missing a reviewer that can grant the code owner approval
+- Pending code owner approval
+- Approved by a code owner
 
 **Missing code owner approval**
 
-The change is missing a reviewer that can grant the code-owner approval.
+The change is missing a reviewer that can grant the code owner approval.
 
-![missing owner](./owner-status-missing.png "Missing owner")
+![missing owner](./owner-status-missing.png "Missing code owner")
 
-**Pending code-owner approval**
+**Pending code owner approval**
 
-- The change is pending a vote from a reviewer that can grant the code-owner approval.  Owners have been added to the change but have not voted yet.
+- The change is pending a vote from a reviewer that can grant the code owner
+  approval. Code owners have been added to the change but have not voted yet.
 
 ![pending owner's approval 1](./owner-status-pending-1.png "Pending owner's approval")
 
-- A code owner has voted -1 on the change. A -1 doesn't block a file from being approved by another code owner. The status is pending because the change needs another round of review.
+- A code owner has voted -1 on the change. A -1 doesn't block a file from being
+  approved by another code owner. The status is pending because the change needs
+  another round of review.
 
 ![pending owner's approval 2](./owner-status-pending-2.png "Pending owner's approval")
 
-**Approved by code-owner**
+**Approved by code owner**
 
-Each file in your change was approved by at least one code owner. It's not required that all code owners approve a change.
+Each file in your change was approved by at least one code owner. It's not
+required that all code owners approve a change.
 
-![owner approved](./owner-status-approved.png "Owner approved")
+![owner approved](./owner-status-approved.png "Code owner approved")
 
 #### File status
 
-Additionally, the code-owners plugin provides a more detailed overview of code-owner status per file in the change with 3 statuses and you can **hover over the icon** to display a tooltip.
+Additionally, the `code-owners` plugin provides a more detailed overview of code
+owner status per file in the change with 3 statuses and you can **hover over the
+icon** to display a tooltip.
 
 **Missing code owner approval**
 
@@ -172,7 +267,9 @@
 
 **Approved by code owner**
 
-A code owner of this file has approved the change. You can also see this icon if you are a code-owner of the file as in this case the file is implicitly approved by you.
+A code owner of this file has approved the change. You can also see this icon if
+you are a code owner of the file as in this case the file is implicitly approved
+by you.
 
 ![approved owner tooltip](./tooltip-approved-owner.png "Tooltip for approved status")
 
@@ -185,24 +282,26 @@
 
 #### No label and no status
 
-When you own all the files in your change, the Code-Owners plugin will:
+When you own all the files in your change, the `code-owners` plugin will:
 
-- Not show the Code-Owner submit requirement
+- Not show the `Code-Owners` submit requirement
 - Not show the file status
 
 ### Owners-Override label
 
 #### In the reply dialog
 
-The Owners-Override label is votable by a user with certain permissions (e.r.sheriff).
-The owner-override label will show in the reply dialog and you can vote on it if you have certain permissions.
+The `Owners-Override` label is votable by a user with certain permissions (e.g.
+sheriff). The `Owner-Override` label will show in the reply dialog and you can
+vote on it if you have certain permissions.
 
 ![code owner override label in reply dialog](./code-owner-override-label-in-reply.png "Vote on owners-override label")
 
-
 #### In the change page
 
-When a user with certain permissions has voted "Owners-Override+1" and the Code-Owners submit requirement returns the status `Approved (Owners-Override)`.
+When a user with certain permissions has voted "Owners-Override+1" and the
+`Code-Owners` submit requirement returns the status `Approved
+(Owners-Override)`.
 
 ![code owner override label in change page](./code-owner-override-label-in-change.png "Owners-override label")
 
diff --git a/resources/Documentation/rest-api.md b/resources/Documentation/rest-api.md
index 7044cd8..1978bd0 100644
--- a/resources/Documentation/rest-api.md
+++ b/resources/Documentation/rest-api.md
@@ -182,8 +182,8 @@
 
 | Field Name  |          | Description |
 | ----------- | -------- | ----------- |
-| `email`     | optional | Code owner email that must appear in the returned
-code owner config files.
+| `include-non-parsable-files` | optional | Includes non-parseable code owner config files in the response. By default `false`. Cannot be used in combination with the `email` option.
+| `email`     | optional | Code owner email that must appear in the returned code owner config files.
 
 #### Request
 
@@ -195,7 +195,8 @@
 result also includes code owner config that use name prefixes
 ('\<prefix\>_OWNERS') or name extensions ('OWNERS_\<extension\>').
 
-Non-parseable code owner config files are omitted from the response.
+Non-parseable code owner config files are omitted from the response, unless the
+`include-non-parsable-files` option was set.
 
 #### Response
 
@@ -277,7 +278,6 @@
 * are referenced by an email with a disallowed domain (see
   [allowedEmailDomain configuration](config.html#pluginCodeOwnersAllowedEmailDomain))
 * do not have read access to the branch
-* are service users (members of the `Service Users` group)
 
 are omitted from the result.
 
@@ -390,16 +390,23 @@
 
 ## <a id="revision-endpoints"> Revision Endpoints
 
-### <a id="list-code-owners-for-path-in-change"> List Code Owners for path in change
+### <a id="list-code-owners-for-path-in-change"> Suggest Code Owners for path in change
 _'GET /changes/[\{change-id}](../../../Documentation/rest-api-changes.html#change-id)/revisions/[\{revison-id\}](../../../Documentation/rest-api-changes.html#revision-id)/code_owners/[\{path\}](#path)'_
 
-Lists the accounts that are code owners of a file in a change revision.
+Suggests accounts that are code owners of a file in a change revision.
 
 The code owners are computed from the owner configuration at the tip of the
 change's destination branch.
 
 This REST endpoint has the exact same request and response format as the
-[REST endpoint to list code owners for a path in a branch](#list-code-owners-for-path-in-branch).
+[REST endpoint to list code owners for a path in a branch](#list-code-owners-for-path-in-branch),
+but filters out code owners that which should be omitted from the code owner
+suggestion.
+
+The following code owners are filtered out additionally:
+
+* service users (members of the `Service Users` group)
+* the change owner (since the change owner cannot be added as reviewer)
 
 ### <a id="check-code-owner-config-files-in-revision">Check Code Owner Config Files In Revision
 _'POST /changes/[\{change-id}](../../../Documentation/rest-api-changes.html#change-id)/revisions/[\{revison-id\}](../../../Documentation/rest-api-changes.html#revision-id)/code_owners.check_config'_