Merge changes Ibebb6b55,Iafb1d32f,I2486c293,I2695d16d,If579300d, ...

* changes:
  List code owners: Add option to only get code owners with the highest score
  AbstractGetCodeOwnersForPath: Make code owner scorings accessible
  AbstractGetCodeOwnersForPath: Cleanup how subclasses add further scorings
  AbstractGetCodeOwnersForPath: Add a comment
  Add distance scores for code owners that are added for resolving all users
  Fix sorting in code owner suggestion if file is owned by all users
  GetOwnedPath: Limit the number of returned paths
  ChangeCodeOwners: Add request object for computing the code owner status
  Adapt projectOwnersAreNoCodeOwnersIfDefaultCodeOwnerConfigExists test
diff --git a/java/com/google/gerrit/plugins/codeowners/backend/CodeOwnerSubmitRule.java b/java/com/google/gerrit/plugins/codeowners/backend/CodeOwnerSubmitRule.java
index c35d1b0..011e8cf 100644
--- a/java/com/google/gerrit/plugins/codeowners/backend/CodeOwnerSubmitRule.java
+++ b/java/com/google/gerrit/plugins/codeowners/backend/CodeOwnerSubmitRule.java
@@ -100,6 +100,11 @@
               changeData.currentPatchSet().id().get(), changeData.change().getId().get()));
       return Optional.of(notReady());
     } catch (Throwable t) {
+      // Whether the exception should be treated as RULE_ERROR.
+      // RULE_ERROR must only be returned if the exception is caused by user misconfiguration (e.g.
+      // an invalid OWNERS file), but not for internal server errors.
+      boolean isRuleError = false;
+
       String cause = t.getClass().getSimpleName();
       String errorMessage = "Failed to evaluate code owner statuses";
       if (changeData != null) {
@@ -113,9 +118,11 @@
       Optional<InvalidCodeOwnerConfigException> invalidCodeOwnerConfigException =
           CodeOwners.getInvalidCodeOwnerConfigCause(t);
       if (invalidPathException.isPresent()) {
+        isRuleError = true;
         cause = "invalid_path";
         errorMessage += String.format(" (cause: %s)", invalidPathException.get().getMessage());
       } else if (invalidCodeOwnerConfigException.isPresent()) {
+        isRuleError = true;
         codeOwnerMetrics.countInvalidCodeOwnerConfigFiles.increment(
             invalidCodeOwnerConfigException.get().getProjectName().get(),
             invalidCodeOwnerConfigException.get().getRef(),
@@ -137,7 +144,11 @@
       errorMessage += ".";
       logger.atSevere().withCause(t).log(errorMessage);
       codeOwnerMetrics.countCodeOwnerSubmitRuleErrors.increment(cause);
-      return Optional.of(ruleError(errorMessage));
+
+      if (isRuleError) {
+        return Optional.of(ruleError(errorMessage));
+      }
+      throw new CodeOwnersInternalServerErrorException(errorMessage, t);
     }
   }
 
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 9a3cc98..cf6e515 100644
--- a/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/CodeOwnerSubmitRuleIT.java
+++ b/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/CodeOwnerSubmitRuleIT.java
@@ -23,6 +23,7 @@
 import com.google.common.collect.ImmutableMap;
 import com.google.gerrit.acceptance.PushOneCommit;
 import com.google.gerrit.acceptance.TestAccount;
+import com.google.gerrit.acceptance.TestMetricMaker;
 import com.google.gerrit.acceptance.config.GerritConfig;
 import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
 import com.google.gerrit.common.Nullable;
@@ -50,6 +51,7 @@
 /** Acceptance test for {@code com.google.gerrit.plugins.codeowners.backend.CodeOwnerSubmitRule}. */
 public class CodeOwnerSubmitRuleIT extends AbstractCodeOwnersIT {
   @Inject private ProjectOperations projectOperations;
+  @Inject private TestMetricMaker testMetricMaker;
 
   @Test
   public void changeIsSubmittableIfCodeOwnersFuctionalityIsDisabled() throws Exception {
@@ -496,6 +498,37 @@
     assertThat(gApi.changes().id(changeId).get().status).isEqualTo(ChangeStatus.MERGED);
   }
 
+  @Test
+  public void submitRuleIsInvokedOnlyOnceWhenGettingChangeDetails() throws Exception {
+    PushOneCommit.Result r = createChange("Some Change", "foo.txt", "some content");
+    String changeId = r.getChangeId();
+
+    testMetricMaker.reset();
+    gApi.changes()
+        .id(changeId)
+        .get(ListChangesOption.ALL_REVISIONS, ListChangesOption.CURRENT_ACTIONS);
+
+    // Submit rules are computed freshly, but only once.
+    assertThat(testMetricMaker.getCount("plugins/code-owners/count_code_owner_submit_rule_runs"))
+        .isEqualTo(1);
+  }
+
+  @Test
+  public void submitRuleIsNotInvokedWhenQueryingChange() throws Exception {
+    PushOneCommit.Result r = createChange("Some Change", "foo.txt", "some content");
+    String changeId = r.getChangeId();
+
+    testMetricMaker.reset();
+    gApi.changes()
+        .query(changeId)
+        .withOptions(ListChangesOption.ALL_REVISIONS, ListChangesOption.CURRENT_ACTIONS)
+        .get();
+
+    // Submit rule evaluation results from the change index are reused
+    assertThat(testMetricMaker.getCount("plugins/code-owners/count_code_owner_submit_rule_runs"))
+        .isEqualTo(0);
+  }
+
   private void deleteAutoMergeBranch(ObjectId mergeCommit) throws Exception {
     try (Repository repo = repoManager.openRepository(project)) {
       RefUpdate ru = repo.updateRef(RefNames.refsCacheAutomerge(mergeCommit.name()));
diff --git a/javatests/com/google/gerrit/plugins/codeowners/backend/CodeOwnerSubmitRuleTest.java b/javatests/com/google/gerrit/plugins/codeowners/backend/CodeOwnerSubmitRuleTest.java
index dc709aa..e493e40 100644
--- a/javatests/com/google/gerrit/plugins/codeowners/backend/CodeOwnerSubmitRuleTest.java
+++ b/javatests/com/google/gerrit/plugins/codeowners/backend/CodeOwnerSubmitRuleTest.java
@@ -14,8 +14,10 @@
 
 package com.google.gerrit.plugins.codeowners.backend;
 
+import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth8.assertThat;
 import static com.google.gerrit.plugins.codeowners.testing.SubmitRecordSubject.assertThatOptional;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
@@ -120,7 +122,7 @@
   }
 
   @Test
-  public void ruleError() throws Exception {
+  public void internalServerError() throws Exception {
     ChangeData changeData = createChange().getChange();
 
     // Create a ChangeData without change notes to trigger an error.
@@ -129,11 +131,12 @@
     when(changeDataWithoutChangeNotes.change()).thenReturn(changeData.change());
     when(changeDataWithoutChangeNotes.currentPatchSet()).thenReturn(changeData.currentPatchSet());
 
-    SubmitRecordSubject submitRecordSubject =
-        assertThatOptional(codeOwnerSubmitRule.evaluate(changeDataWithoutChangeNotes)).value();
-    submitRecordSubject.hasStatusThat().isRuleError();
-    submitRecordSubject
-        .hasErrorMessageThat()
+    CodeOwnersInternalServerErrorException exception =
+        assertThrows(
+            CodeOwnersInternalServerErrorException.class,
+            () -> codeOwnerSubmitRule.evaluate(changeDataWithoutChangeNotes));
+    assertThat(exception)
+        .hasMessageThat()
         .isEqualTo(
             String.format(
                 "Failed to evaluate code owner statuses for patch set %d of change %d.",
@@ -141,11 +144,12 @@
   }
 
   @Test
-  public void ruleError_changeDataIsNull() throws Exception {
-    SubmitRecordSubject submitRecordSubject =
-        assertThatOptional(codeOwnerSubmitRule.evaluate(/* changeData= */ null)).value();
-    submitRecordSubject.hasStatusThat().isRuleError();
-    submitRecordSubject.hasErrorMessageThat().isEqualTo("Failed to evaluate code owner statuses.");
+  public void internalServerError_changeDataIsNull() throws Exception {
+    CodeOwnersInternalServerErrorException exception =
+        assertThrows(
+            CodeOwnersInternalServerErrorException.class,
+            () -> codeOwnerSubmitRule.evaluate(/* changeData= */ null));
+    assertThat(exception).hasMessageThat().isEqualTo("Failed to evaluate code owner statuses.");
   }
 
   @Test