Merge "Validate OWNERS files on branch creation"
diff --git a/java/com/google/gerrit/plugins/codeowners/api/CodeOwnerProjectConfigInput.java b/java/com/google/gerrit/plugins/codeowners/api/CodeOwnerProjectConfigInput.java
index 8bc182e..bd9d0f5 100644
--- a/java/com/google/gerrit/plugins/codeowners/api/CodeOwnerProjectConfigInput.java
+++ b/java/com/google/gerrit/plugins/codeowners/api/CodeOwnerProjectConfigInput.java
@@ -98,6 +98,9 @@
   /** Whether pure revert changes are exempted from needing code owner approvals for submit. */
   public Boolean exemptPureReverts;
 
+  /** Policy for validating code owner config files when a branch is created. */
+  public CodeOwnerConfigValidationPolicy enableValidationOnBranchCreation;
+
   /** Policy for validating code owner config files when a commit is received. */
   public CodeOwnerConfigValidationPolicy enableValidationOnCommitReceived;
 
diff --git a/java/com/google/gerrit/plugins/codeowners/backend/config/CodeOwnersPluginProjectConfigSnapshot.java b/java/com/google/gerrit/plugins/codeowners/backend/config/CodeOwnersPluginProjectConfigSnapshot.java
index f3b6418..5b2ab1c 100644
--- a/java/com/google/gerrit/plugins/codeowners/backend/config/CodeOwnersPluginProjectConfigSnapshot.java
+++ b/java/com/google/gerrit/plugins/codeowners/backend/config/CodeOwnersPluginProjectConfigSnapshot.java
@@ -74,6 +74,9 @@
   @Nullable private Boolean rejectNonResolvableImports;
 
   @Nullable
+  private CodeOwnerConfigValidationPolicy codeOwnerConfigValidationPolicyForBranchCreation;
+
+  @Nullable
   private CodeOwnerConfigValidationPolicy codeOwnerConfigValidationPolicyForCommitReceived;
 
   @Nullable private CodeOwnerConfigValidationPolicy codeOwnerConfigValidationPolicyForSubmit;
@@ -204,6 +207,36 @@
   }
 
   /**
+   * Whether code owner configs should be validated when a branch is created.
+   *
+   * @param branchName the branch for which it should be checked whether code owner configs should
+   *     be validated on branch creation
+   */
+  public CodeOwnerConfigValidationPolicy getCodeOwnerConfigValidationPolicyForBranchCreation(
+      String branchName) {
+    if (codeOwnerConfigValidationPolicyForBranchCreation == null) {
+      codeOwnerConfigValidationPolicyForBranchCreation =
+          readCodeOwnerConfigValidationPolicyForBranchCreation(branchName);
+    }
+    return codeOwnerConfigValidationPolicyForBranchCreation;
+  }
+
+  private CodeOwnerConfigValidationPolicy readCodeOwnerConfigValidationPolicyForBranchCreation(
+      String branchName) {
+    requireNonNull(branchName, "branchName");
+
+    Optional<CodeOwnerConfigValidationPolicy> branchSpecificPolicy =
+        generalConfig.getCodeOwnerConfigValidationPolicyForBranchCreationForBranch(
+            BranchNameKey.create(projectName, branchName), pluginConfig);
+    if (branchSpecificPolicy.isPresent()) {
+      return branchSpecificPolicy.get();
+    }
+
+    return generalConfig.getCodeOwnerConfigValidationPolicyForBranchCreation(
+        projectName, pluginConfig);
+  }
+
+  /**
    * Whether code owner configs should be validated when a commit is received.
    *
    * @param branchName the branch for which it should be checked whether code owner configs should
diff --git a/java/com/google/gerrit/plugins/codeowners/backend/config/GeneralConfig.java b/java/com/google/gerrit/plugins/codeowners/backend/config/GeneralConfig.java
index 5540f0a..00574bc 100644
--- a/java/com/google/gerrit/plugins/codeowners/backend/config/GeneralConfig.java
+++ b/java/com/google/gerrit/plugins/codeowners/backend/config/GeneralConfig.java
@@ -70,6 +70,8 @@
   public static final String KEY_READ_ONLY = "readOnly";
   public static final String KEY_EXEMPT_PURE_REVERTS = "exemptPureReverts";
   public static final String KEY_FALLBACK_CODE_OWNERS = "fallbackCodeOwners";
+  public static final String KEY_ENABLE_VALIDATION_ON_BRANCH_CREATION =
+      "enableValidationOnBranchCreation";
   public static final String KEY_ENABLE_VALIDATION_ON_COMMIT_RECEIVED =
       "enableValidationOnCommitReceived";
   public static final String KEY_ENABLE_VALIDATION_ON_SUBMIT = "enableValidationOnSubmit";
@@ -473,6 +475,52 @@
   }
 
   /**
+   * Gets the enable validation on branch creation configuration from the given plugin config for
+   * the specified project with fallback to {@code gerrit.config} and default to {@code true}.
+   *
+   * <p>The enable validation on branch creation controls whether code owner config files should be
+   * validated when a branch is created.
+   *
+   * @param project the project for which the enable validation on branch creation configuration
+   *     should be read
+   * @param pluginConfig the plugin config from which the enable validation on branch creation
+   *     configuration should be read
+   * @return whether code owner config files should be validated when a branch is created
+   */
+  CodeOwnerConfigValidationPolicy getCodeOwnerConfigValidationPolicyForBranchCreation(
+      Project.NameKey project, Config pluginConfig) {
+    return getCodeOwnerConfigValidationPolicy(
+        KEY_ENABLE_VALIDATION_ON_BRANCH_CREATION,
+        project,
+        pluginConfig,
+        CodeOwnerConfigValidationPolicy.FALSE);
+  }
+
+  /**
+   * Gets the enable validation on branch creation configuration from the given plugin config for
+   * the specified branch.
+   *
+   * <p>If multiple branch-specific configurations match the specified branch, it is undefined which
+   * of the matching branch configurations takes precedence.
+   *
+   * <p>The enable validation on branch creation controls whether code owner config files should be
+   * validated when a branch is created.
+   *
+   * @param branchNameKey the branch and project for which the enable validation on branch creation
+   *     configuration should be read
+   * @param pluginConfig the plugin config from which the enable validation on branch creation
+   *     configuration should be read
+   * @return the enable validation on branch creation configuration that is configured for the
+   *     branch, {@link Optional#empty()} if no branch specific configuration exists
+   */
+  Optional<CodeOwnerConfigValidationPolicy>
+      getCodeOwnerConfigValidationPolicyForBranchCreationForBranch(
+          BranchNameKey branchNameKey, Config pluginConfig) {
+    return getCodeOwnerConfigValidationPolicyForBranch(
+        KEY_ENABLE_VALIDATION_ON_BRANCH_CREATION, branchNameKey, pluginConfig);
+  }
+
+  /**
    * Gets the enable validation on commit received configuration from the given plugin config for
    * the specified project with fallback to {@code gerrit.config} and default to {@code true}.
    *
diff --git a/java/com/google/gerrit/plugins/codeowners/metrics/ValidationTrigger.java b/java/com/google/gerrit/plugins/codeowners/metrics/ValidationTrigger.java
index 4d28b70..8fc24d0 100644
--- a/java/com/google/gerrit/plugins/codeowners/metrics/ValidationTrigger.java
+++ b/java/com/google/gerrit/plugins/codeowners/metrics/ValidationTrigger.java
@@ -16,6 +16,9 @@
 
 /** Enum to express which event triggered the validation. */
 public enum ValidationTrigger {
+  /** A new branch is created for which the initial commit should be validated. */
+  BRANCH_CREATION,
+
   /** A new commit was received that should be validated. */
   COMMIT_RECEIVED,
 
diff --git a/java/com/google/gerrit/plugins/codeowners/restapi/PutCodeOwnerProjectConfig.java b/java/com/google/gerrit/plugins/codeowners/restapi/PutCodeOwnerProjectConfig.java
index 4fe0574..21462a2 100644
--- a/java/com/google/gerrit/plugins/codeowners/restapi/PutCodeOwnerProjectConfig.java
+++ b/java/com/google/gerrit/plugins/codeowners/restapi/PutCodeOwnerProjectConfig.java
@@ -16,6 +16,7 @@
 
 import static com.google.gerrit.plugins.codeowners.backend.config.CodeOwnersPluginConfiguration.SECTION_CODE_OWNERS;
 import static com.google.gerrit.plugins.codeowners.backend.config.GeneralConfig.KEY_ENABLE_IMPLICIT_APPROVALS;
+import static com.google.gerrit.plugins.codeowners.backend.config.GeneralConfig.KEY_ENABLE_VALIDATION_ON_BRANCH_CREATION;
 import static com.google.gerrit.plugins.codeowners.backend.config.GeneralConfig.KEY_ENABLE_VALIDATION_ON_COMMIT_RECEIVED;
 import static com.google.gerrit.plugins.codeowners.backend.config.GeneralConfig.KEY_ENABLE_VALIDATION_ON_SUBMIT;
 import static com.google.gerrit.plugins.codeowners.backend.config.GeneralConfig.KEY_EXEMPTED_USER;
@@ -221,6 +222,14 @@
             input.exemptPureReverts);
       }
 
+      if (input.enableValidationOnBranchCreation != null) {
+        codeOwnersConfig.setEnum(
+            SECTION_CODE_OWNERS,
+            /* subsection= */ null,
+            KEY_ENABLE_VALIDATION_ON_BRANCH_CREATION,
+            input.enableValidationOnBranchCreation);
+      }
+
       if (input.enableValidationOnCommitReceived != null) {
         codeOwnersConfig.setEnum(
             SECTION_CODE_OWNERS,
diff --git a/java/com/google/gerrit/plugins/codeowners/validation/CodeOwnerConfigValidator.java b/java/com/google/gerrit/plugins/codeowners/validation/CodeOwnerConfigValidator.java
index c4fd816..f763a2c 100644
--- a/java/com/google/gerrit/plugins/codeowners/validation/CodeOwnerConfigValidator.java
+++ b/java/com/google/gerrit/plugins/codeowners/validation/CodeOwnerConfigValidator.java
@@ -54,6 +54,7 @@
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.PatchSetUtil;
 import com.google.gerrit.server.events.CommitReceivedEvent;
+import com.google.gerrit.server.events.RefReceivedEvent;
 import com.google.gerrit.server.git.CodeReviewCommit;
 import com.google.gerrit.server.git.CodeReviewCommit.CodeReviewRevWalk;
 import com.google.gerrit.server.git.GitRepositoryManager;
@@ -62,6 +63,7 @@
 import com.google.gerrit.server.git.validators.CommitValidationMessage;
 import com.google.gerrit.server.git.validators.MergeValidationException;
 import com.google.gerrit.server.git.validators.MergeValidationListener;
+import com.google.gerrit.server.git.validators.RefOperationValidationListener;
 import com.google.gerrit.server.git.validators.ValidationMessage;
 import com.google.gerrit.server.logging.Metadata;
 import com.google.gerrit.server.logging.TraceContext;
@@ -74,6 +76,7 @@
 import com.google.gerrit.server.permissions.RefPermission;
 import com.google.gerrit.server.project.ProjectCache;
 import com.google.gerrit.server.project.ProjectState;
+import com.google.gerrit.server.validators.ValidationException;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
 import com.google.inject.Singleton;
@@ -85,10 +88,13 @@
 import java.util.stream.Stream;
 import org.eclipse.jgit.errors.ConfigInvalidException;
 import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectReader;
 import org.eclipse.jgit.lib.Ref;
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.revwalk.RevCommit;
 import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.transport.ReceiveCommand;
+import org.eclipse.jgit.treewalk.TreeWalk;
 
 /**
  * Validates modifications to the code owner config files.
@@ -129,7 +135,8 @@
  * </ul>
  */
 @Singleton
-public class CodeOwnerConfigValidator implements CommitValidationListener, MergeValidationListener {
+public class CodeOwnerConfigValidator
+    implements CommitValidationListener, MergeValidationListener, RefOperationValidationListener {
   private static final FluentLogger logger = FluentLogger.forEnclosingClass();
 
   private final String pluginName;
@@ -208,6 +215,7 @@
                   receiveEvent.commit,
                   receiveEvent.user,
                   codeOwnerConfigValidationPolicy.isForced(),
+                  /* isBranchCreation= */ false,
                   receiveEvent.pushOptions);
         } catch (RuntimeException e) {
           codeOwnerMetrics.countCodeOwnerConfigValidations.increment(
@@ -296,6 +304,7 @@
                   commit,
                   patchSetUploader,
                   codeOwnerConfigValidationPolicy.isForced(),
+                  /* isBranchCreation= */ false,
                   /* pushOptions= */ ImmutableListMultimap.of());
         } catch (RuntimeException e) {
           codeOwnerMetrics.countCodeOwnerConfigValidations.increment(
@@ -329,6 +338,101 @@
     }
   }
 
+  @Override
+  public List<ValidationMessage> onRefOperation(RefReceivedEvent refReceivedEvent)
+      throws ValidationException {
+    if (!ReceiveCommand.Type.CREATE.equals(refReceivedEvent.command.getType())) {
+      // We are only interested in branch creations. Return early if this is not a branch creation.
+      return ImmutableList.of();
+    }
+
+    try (TraceTimer traceTimer =
+        TraceContext.newTimer(
+            "Validate code owner config files on branch creation",
+            Metadata.builder()
+                .projectName(refReceivedEvent.getProjectNameKey().get())
+                .commit(refReceivedEvent.command.getNewId().name())
+                .branchName(refReceivedEvent.getRefName())
+                .username(refReceivedEvent.user.getLoggableName())
+                .build())) {
+      CodeOwnerConfigValidationPolicy codeOwnerConfigValidationPolicy =
+          codeOwnersPluginConfiguration
+              .getProjectConfig(refReceivedEvent.getProjectNameKey())
+              .getCodeOwnerConfigValidationPolicyForBranchCreation(refReceivedEvent.getRefName());
+      logger.atFine().log("codeOwnerConfigValidationPolicy = %s", codeOwnerConfigValidationPolicy);
+      boolean metricRecordingDone = false;
+      Optional<ValidationResult> validationResult;
+      if (!codeOwnerConfigValidationPolicy.runValidation()) {
+        validationResult =
+            Optional.of(
+                ValidationResult.create(
+                    pluginName,
+                    "skipping validation of code owner config files",
+                    new CommitValidationMessage(
+                        "code owners config validation is disabled", ValidationMessage.Type.HINT)));
+      } else {
+        try {
+          try (Repository repo = repoManager.openRepository(refReceivedEvent.getProjectNameKey());
+              RevWalk revWalk = new RevWalk(repo)) {
+            validationResult =
+                validateCodeOwnerConfig(
+                    refReceivedEvent.getBranchNameKey(),
+                    revWalk.parseCommit(refReceivedEvent.command.getNewId()),
+                    refReceivedEvent.user,
+                    codeOwnerConfigValidationPolicy.isForced(),
+                    /* isBranchCreation= */ true,
+                    refReceivedEvent.pushOptions);
+          } catch (IOException e) {
+            throw newInternalServerError(
+                String.format(
+                    "failed to validate code owner config files in revision %s"
+                        + " (project = %s, branch = %s)",
+                    refReceivedEvent.command.getNewId().name(),
+                    refReceivedEvent.getProjectNameKey(),
+                    refReceivedEvent.getRefName()),
+                e);
+          }
+        } catch (RuntimeException e) {
+          codeOwnerMetrics.countCodeOwnerConfigValidations.increment(
+              ValidationTrigger.BRANCH_CREATION,
+              com.google.gerrit.plugins.codeowners.metrics.ValidationResult.FAILED,
+              codeOwnerConfigValidationPolicy.isDryRun());
+          metricRecordingDone = true;
+
+          if (!codeOwnerConfigValidationPolicy.isDryRun()) {
+            throw e;
+          }
+
+          // The validation was executed as dry-run and failures during the validation should not
+          // cause an error. Hence we swallow the exception here.
+          logger.atWarning().withCause(e).log(
+              "ignoring failure during validation of code owner config files in revision %s"
+                  + " (project = %s, branch = %s) because the validation was performed as dry-run",
+              refReceivedEvent.command.getNewId().getName(),
+              refReceivedEvent.getProjectNameKey(),
+              refReceivedEvent.getBranchNameKey().branch());
+          validationResult = Optional.empty();
+        }
+      }
+      if (!validationResult.isPresent()) {
+        return ImmutableList.of();
+      }
+
+      logger.atFine().log("validation result = %s", validationResult.get());
+      if (!metricRecordingDone) {
+        codeOwnerMetrics.countCodeOwnerConfigValidations.increment(
+            ValidationTrigger.BRANCH_CREATION,
+            validationResult.get().hasError()
+                ? com.google.gerrit.plugins.codeowners.metrics.ValidationResult.REJECTED
+                : com.google.gerrit.plugins.codeowners.metrics.ValidationResult.PASSED,
+            codeOwnerConfigValidationPolicy.isDryRun());
+      }
+      return validationResult
+          .get()
+          .processForOnRefOperation(codeOwnerConfigValidationPolicy.isDryRun());
+    }
+  }
+
   /**
    * Validates the code owner config files which are newly added or modified in the given commit.
    *
@@ -339,6 +443,7 @@
    * @param user user for which the code owner visibility checks should be performed
    * @param force whether the validation should be done even if the code owners functionality is
    *     disabled for the branch
+   * @param isBranchCreation whether a new branch is being created
    * @return the validation result, {@link Optional#empty()} if no validation is performed because
    *     the given commit doesn't contain newly added or modified code owner configs
    */
@@ -347,6 +452,7 @@
       RevCommit revCommit,
       IdentifiedUser user,
       boolean force,
+      boolean isBranchCreation,
       ImmutableListMultimap<String, String> pushOptions) {
     CodeOwnersPluginProjectConfigSnapshot codeOwnersConfig =
         codeOwnersPluginConfiguration.getProjectConfig(branchNameKey.project());
@@ -402,31 +508,13 @@
     try {
       CodeOwnerBackend codeOwnerBackend = codeOwnersConfig.getBackend(branchNameKey.branch());
 
-      // For merge commits, always do the comparison against the destination branch
-      // (MergeCommitStrategy.ALL_CHANGED_FILES). Doing the comparison against the auto-merge
-      // (MergeCommitStrategy.FILES_WITH_CONFLICT_RESOLUTION) is not possible because loading the
-      // auto-merge cannot reuse the rev walk that can see newly created merge commits and hence
-      // trying to get the auto merge would fail with a missing object exception. This is why we
-      // use MergeCommitStrategy.ALL_CHANGED_FILES here even if
-      // MergeCommitStrategy.FILES_WITH_CONFLICT_RESOLUTION is configured.
-      ImmutableList<ChangedFile> modifiedCodeOwnerConfigFiles =
-          changedFiles
-              .getFromDiffCache(
-                  branchNameKey.project(), revCommit, MergeCommitStrategy.ALL_CHANGED_FILES)
-              .stream()
-              // filter out deletions (files without new path)
-              .filter(changedFile -> changedFile.newPath().isPresent())
-              // filter out non code owner config files
-              .filter(
-                  changedFile ->
-                      codeOwnerBackend.isCodeOwnerConfigFile(
-                          branchNameKey.project(),
-                          Paths.get(changedFile.newPath().get().toString())
-                              .getFileName()
-                              .toString()))
-              .collect(toImmutableList());
+      ImmutableList<ChangedFile> codeOwnerConfigFilesToValidate =
+          isBranchCreation
+              ? getAllCodeOwnerConfigFiles(codeOwnerBackend, branchNameKey.project(), revCommit)
+              : getModifiedCodeOwnerConfigFiles(
+                  codeOwnerBackend, branchNameKey.project(), revCommit);
 
-      if (modifiedCodeOwnerConfigFiles.isEmpty()) {
+      if (codeOwnerConfigFilesToValidate.isEmpty()) {
         return Optional.empty();
       }
 
@@ -434,7 +522,7 @@
       return Optional.of(
           ValidationResult.create(
               pluginName,
-              modifiedCodeOwnerConfigFiles.stream()
+              codeOwnerConfigFilesToValidate.stream()
                   .flatMap(
                       changedFile ->
                           validateCodeOwnerConfig(
@@ -467,6 +555,48 @@
     }
   }
 
+  public ImmutableList<ChangedFile> getModifiedCodeOwnerConfigFiles(
+      CodeOwnerBackend codeOwnerBackend, Project.NameKey project, ObjectId revCommit)
+      throws IOException, DiffNotAvailableException {
+    // For merge commits, always do the comparison against the destination branch
+    // (MergeCommitStrategy.ALL_CHANGED_FILES). Doing the comparison against the auto-merge
+    // (MergeCommitStrategy.FILES_WITH_CONFLICT_RESOLUTION) is not possible because loading the
+    // auto-merge cannot reuse the rev walk that can see newly created merge commits and hence
+    // trying to get the auto merge would fail with a missing object exception. This is why we
+    // use MergeCommitStrategy.ALL_CHANGED_FILES here even if
+    // MergeCommitStrategy.FILES_WITH_CONFLICT_RESOLUTION is configured.
+    return changedFiles.getFromDiffCache(project, revCommit, MergeCommitStrategy.ALL_CHANGED_FILES)
+        .stream()
+        // filter out deletions (files without new path)
+        .filter(changedFile -> changedFile.newPath().isPresent())
+        // filter out non code owner config files
+        .filter(
+            changedFile ->
+                codeOwnerBackend.isCodeOwnerConfigFile(
+                    project,
+                    Paths.get(changedFile.newPath().get().toString()).getFileName().toString()))
+        .collect(toImmutableList());
+  }
+
+  public ImmutableList<ChangedFile> getAllCodeOwnerConfigFiles(
+      CodeOwnerBackend codeOwnerBackend, Project.NameKey project, RevCommit revCommit)
+      throws IOException {
+    try (Repository git = repoManager.openRepository(project);
+        ObjectReader or = git.newObjectReader();
+        TreeWalk tw = new TreeWalk(or)) {
+      tw.addTree(revCommit.getTree());
+      tw.setRecursive(true);
+      ImmutableList.Builder<ChangedFile> paths = ImmutableList.builder();
+      while (tw.next()) {
+        Path path = Path.of(tw.getPathString());
+        if (codeOwnerBackend.isCodeOwnerConfigFile(project, path.getFileName().toString())) {
+          paths.add(ChangedFile.addition(path));
+        }
+      }
+      return paths.build();
+    }
+  }
+
   /**
    * Validates the specified code owner config and returns a stream of validation messages.
    *
@@ -1221,7 +1351,7 @@
             withPluginName(summaryMessage()), withPluginName(validationMessages()));
       }
 
-      return validationMessagesWithIncludedSummaryMessage();
+      return commitValidationMessagesWithIncludedSummaryMessage();
     }
 
     /**
@@ -1240,12 +1370,29 @@
       if (!validationMessages().isEmpty()) {
         logger.atFine().log(
             "submitting changes to code owner config files with the following messages: %s",
-            validationMessagesWithIncludedSummaryMessage());
+            commitValidationMessagesWithIncludedSummaryMessage());
       } else {
         logger.atFine().log("submitting changes to code owner config files, no issues found");
       }
     }
 
+    /**
+     * Processes the validation messages for a validation that is done when a ref operation is done
+     * (e.g. on branch creation).
+     *
+     * <p>Throws a {@link ValidationException} if there are errors to make the ref operation fail.
+     *
+     * <p>If there are no errors the validation messages are returned so that they can be sent to
+     * the client without causing the ref operation to fail.
+     */
+    List<ValidationMessage> processForOnRefOperation(boolean dryRun) throws ValidationException {
+      if (!dryRun && hasError()) {
+        throw new ValidationException(getMessage(validationMessages()));
+      }
+
+      return validationMessagesWithIncludedSummaryMessage();
+    }
+
     /** Checks whether any of the validation messages is an error. */
     public boolean hasError() {
       return validationMessages().stream()
@@ -1255,7 +1402,8 @@
                       || ValidationMessage.Type.ERROR.equals(validationMessage.getType()));
     }
 
-    private ImmutableList<CommitValidationMessage> validationMessagesWithIncludedSummaryMessage() {
+    private ImmutableList<CommitValidationMessage>
+        commitValidationMessagesWithIncludedSummaryMessage() {
       return ImmutableList.<CommitValidationMessage>builder()
           .add(
               new CommitValidationMessage(
@@ -1264,6 +1412,15 @@
           .build();
     }
 
+    private ImmutableList<ValidationMessage> validationMessagesWithIncludedSummaryMessage() {
+      return ImmutableList.<ValidationMessage>builder()
+          .add(
+              new ValidationMessage(
+                  withPluginName(summaryMessage()), getValidationMessageTypeForSummaryMessage()))
+          .addAll(withPluginName(validationMessages()))
+          .build();
+    }
+
     /**
      * Gets the validation message type that should be used for the summary message.
      *
diff --git a/java/com/google/gerrit/plugins/codeowners/validation/ValidationModule.java b/java/com/google/gerrit/plugins/codeowners/validation/ValidationModule.java
index d28925c..6f5ee08 100644
--- a/java/com/google/gerrit/plugins/codeowners/validation/ValidationModule.java
+++ b/java/com/google/gerrit/plugins/codeowners/validation/ValidationModule.java
@@ -20,6 +20,7 @@
 import com.google.gerrit.server.git.receive.PluginPushOption;
 import com.google.gerrit.server.git.validators.CommitValidationListener;
 import com.google.gerrit.server.git.validators.MergeValidationListener;
+import com.google.gerrit.server.git.validators.RefOperationValidationListener;
 import com.google.inject.AbstractModule;
 
 /** Guice module that registers validation extensions of the code-owners plugin. */
@@ -28,6 +29,8 @@
   protected void configure() {
     DynamicSet.bind(binder(), CommitValidationListener.class).to(CodeOwnerConfigValidator.class);
     DynamicSet.bind(binder(), MergeValidationListener.class).to(CodeOwnerConfigValidator.class);
+    DynamicSet.bind(binder(), RefOperationValidationListener.class)
+        .to(CodeOwnerConfigValidator.class);
 
     bind(CapabilityDefinition.class)
         .annotatedWith(Exports.named(SkipCodeOwnerConfigValidationCapability.ID))
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 ab76995..5b7ab3d 100644
--- a/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/CodeOwnerConfigValidatorIT.java
+++ b/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/CodeOwnerConfigValidatorIT.java
@@ -16,6 +16,9 @@
 
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.TruthJUnit.assume;
+import static com.google.gerrit.acceptance.GitUtil.assertPushOk;
+import static com.google.gerrit.acceptance.GitUtil.assertPushRejected;
+import static com.google.gerrit.acceptance.GitUtil.pushHead;
 import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allow;
 import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allowCapability;
 import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.block;
@@ -72,6 +75,7 @@
 import org.eclipse.jgit.lib.AnyObjectId;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.transport.PushResult;
 import org.junit.Before;
 import org.junit.Test;
 
@@ -2461,6 +2465,347 @@
   }
 
   @Test
+  @GerritConfig(name = "plugin.code-owners.enableValidationOnBranchCreation", value = "true")
+  public void cannotCreateBranchWithInvalidCodeOwnerConfigFileViaRestApi() throws Exception {
+    // Add a non code owner config file to verify that it is not validated as code owner config file
+    PushOneCommit.Result r = pushFactory.create(admin.newIdent(), testRepo).to("refs/heads/master");
+    r.assertOkStatus();
+
+    // Create code owner configs with a non-existing user as code owner.
+    // We create 2 code owner configs with different commits so that it's tested that the validator
+    // checks all code owner config files and not only those added in the last commit.
+    String unknownEmail = "non-existing@example.com";
+    CodeOwnerConfig.Key codeOwnerConfigKey1 =
+        codeOwnerConfigOperations
+            .newCodeOwnerConfig()
+            .project(project)
+            .branch("master")
+            .folderPath("/")
+            .addCodeOwnerEmail(unknownEmail)
+            .create();
+    CodeOwnerConfig.Key codeOwnerConfigKey2 =
+        codeOwnerConfigOperations
+            .newCodeOwnerConfig()
+            .project(project)
+            .branch("master")
+            .folderPath("/foo/")
+            .addCodeOwnerEmail(unknownEmail)
+            .create();
+
+    BranchInput input = new BranchInput();
+    input.ref = "new";
+    input.revision = projectOperations.project(project).getHead("master").name();
+
+    ResourceConflictException exception =
+        assertThrows(
+            ResourceConflictException.class,
+            () -> gApi.projects().name(project.get()).branch(input.ref).create(input));
+    assertThat(exception)
+        .hasMessageThat()
+        .isEqualTo(
+            String.format(
+                "Validation for creation of ref 'refs/heads/new' in project %s failed:\n"
+                    + "[code-owners] invalid code owner config files:\n"
+                    + "  ERROR: code owner email '%s' in '%s' cannot be resolved for %s\n"
+                    + "  ERROR: code owner email '%s' in '%s' cannot be resolved for %s",
+                project,
+                unknownEmail,
+                codeOwnerConfigOperations.codeOwnerConfig(codeOwnerConfigKey1).getFilePath(),
+                identifiedUserFactory.create(admin.id()).getLoggableName(),
+                unknownEmail,
+                codeOwnerConfigOperations.codeOwnerConfig(codeOwnerConfigKey2).getFilePath(),
+                identifiedUserFactory.create(admin.id()).getLoggableName()));
+  }
+
+  @Test
+  @GerritConfig(name = "plugin.code-owners.enableValidationOnBranchCreation", value = "true")
+  public void skipValidationForBranchCreationViaRestApi() throws Exception {
+    // Create code owner config with a non-existing user as code owner.
+    String unknownEmail = "non-existing@example.com";
+    codeOwnerConfigOperations
+        .newCodeOwnerConfig()
+        .project(project)
+        .branch("master")
+        .folderPath("/")
+        .addCodeOwnerEmail(unknownEmail)
+        .create();
+
+    BranchInput input = new BranchInput();
+    input.ref = "new";
+    input.revision = projectOperations.project(project).getHead("master").name();
+
+    ResourceConflictException exception =
+        assertThrows(
+            ResourceConflictException.class,
+            () -> gApi.projects().name(project.get()).branch(input.ref).create(input));
+    assertThat(exception)
+        .hasMessageThat()
+        .contains("[code-owners] invalid code owner config files:");
+
+    input.validationOptions =
+        ImmutableMap.of(
+            String.format("code-owners~%s", SkipCodeOwnerConfigValidationPushOption.NAME), "true");
+    gApi.projects().name(project.get()).branch(input.ref).create(input);
+  }
+
+  @Test
+  @GerritConfig(name = "plugin.code-owners.enableValidationOnBranchCreation", value = "true")
+  public void userWithoutCapabilitySkipValidationCannotSkipValidationForBranchCreationViaRestApi()
+      throws Exception {
+    // Create code owner config with a non-existing user as code owner.
+    String unknownEmail = "non-existing@example.com";
+    codeOwnerConfigOperations
+        .newCodeOwnerConfig()
+        .project(project)
+        .branch("master")
+        .folderPath("/")
+        .addCodeOwnerEmail(unknownEmail)
+        .create();
+
+    requestScopeOperations.setApiUser(user.id());
+
+    BranchInput input = new BranchInput();
+    input.ref = "new";
+    input.revision = projectOperations.project(project).getHead("master").name();
+    input.validationOptions =
+        ImmutableMap.of(
+            String.format("code-owners~%s", SkipCodeOwnerConfigValidationPushOption.NAME), "true");
+
+    projectOperations
+        .project(project)
+        .forUpdate()
+        .add(allow(Permission.CREATE).ref(RefNames.REFS_HEADS + "*").group(REGISTERED_USERS))
+        .update();
+
+    ResourceConflictException resourceConflictException =
+        assertThrows(
+            ResourceConflictException.class,
+            () -> gApi.projects().name(project.get()).branch(input.ref).create(input));
+    assertThat(resourceConflictException)
+        .hasMessageThat()
+        .contains(
+            String.format(
+                "Validation for creation of ref 'refs/heads/new' in project %s failed:\n"
+                    + "[code-owners] skipping code owner config validation not allowed:\n"
+                    + "  ERROR: %s for plugin code-owners not permitted",
+                project, SkipCodeOwnerConfigValidationCapability.ID));
+  }
+
+  @Test
+  @GerritConfig(name = "plugin.code-owners.enableValidationOnBranchCreation", value = "true")
+  public void cannotCreateBranchWithInvalidCodeOwnerConfigFileViaPush() throws Exception {
+    // Add a non code owner config file to verify that it is not validated as code owner config file
+    PushOneCommit.Result r = pushFactory.create(admin.newIdent(), testRepo).to("refs/heads/master");
+    r.assertOkStatus();
+
+    // Create code owner configs with a non-existing user as code owner.
+    // We create 2 code owner configs with different commits so that it's tested that the validator
+    // checks all code owner config files and not only those added in the last commit.
+    String unknownEmail = "non-existing@example.com";
+    CodeOwnerConfig.Key codeOwnerConfigKey1 =
+        codeOwnerConfigOperations
+            .newCodeOwnerConfig()
+            .project(project)
+            .branch("master")
+            .folderPath("/")
+            .addCodeOwnerEmail(unknownEmail)
+            .create();
+    CodeOwnerConfig.Key codeOwnerConfigKey2 =
+        codeOwnerConfigOperations
+            .newCodeOwnerConfig()
+            .project(project)
+            .branch("master")
+            .folderPath("/foo/")
+            .addCodeOwnerEmail(unknownEmail)
+            .create();
+
+    RevCommit head = projectOperations.project(project).getHead("master");
+    testRepo.git().fetch().call();
+    testRepo.reset(head.name());
+
+    PushResult r2 =
+        pushHead(
+            testRepo,
+            "refs/heads/new",
+            /* pushTags= */ false,
+            /* force= */ false,
+            /* pushOptions= */ ImmutableList.of());
+    assertPushRejected(
+        r2,
+        "refs/heads/new",
+        String.format(
+            "Validation for creation of ref 'refs/heads/new' in project %s failed:\n"
+                + "[code-owners] invalid code owner config files:\n"
+                + "  ERROR: code owner email '%s' in '%s' cannot be resolved for %s\n"
+                + "  ERROR: code owner email '%s' in '%s' cannot be resolved for %s",
+            project,
+            unknownEmail,
+            codeOwnerConfigOperations.codeOwnerConfig(codeOwnerConfigKey1).getFilePath(),
+            identifiedUserFactory.create(admin.id()).getLoggableName(),
+            unknownEmail,
+            codeOwnerConfigOperations.codeOwnerConfig(codeOwnerConfigKey2).getFilePath(),
+            identifiedUserFactory.create(admin.id()).getLoggableName()));
+  }
+
+  @Test
+  @GerritConfig(name = "plugin.code-owners.enableValidationOnBranchCreation", value = "true")
+  public void skipValidationForBranchCreationViaPush() throws Exception {
+    // Create code owner config with a non-existing user as code owner.
+    String unknownEmail = "non-existing@example.com";
+    CodeOwnerConfig.Key codeOwnerConfigKey =
+        codeOwnerConfigOperations
+            .newCodeOwnerConfig()
+            .project(project)
+            .branch("master")
+            .folderPath("/")
+            .addCodeOwnerEmail(unknownEmail)
+            .create();
+
+    RevCommit head = projectOperations.project(project).getHead("master");
+    testRepo.git().fetch().call();
+    testRepo.reset(head.name());
+
+    PushResult r =
+        pushHead(
+            testRepo,
+            "refs/heads/new",
+            /* pushTags= */ false,
+            /* force= */ false,
+            /* pushOptions= */ ImmutableList.of());
+    assertPushRejected(
+        r,
+        "refs/heads/new",
+        String.format(
+            "Validation for creation of ref 'refs/heads/new' in project %s failed:\n"
+                + "[code-owners] invalid code owner config files:\n"
+                + "  ERROR: code owner email '%s' in '%s' cannot be resolved for %s",
+            project,
+            unknownEmail,
+            codeOwnerConfigOperations.codeOwnerConfig(codeOwnerConfigKey).getFilePath(),
+            identifiedUserFactory.create(admin.id()).getLoggableName()));
+
+    r =
+        pushHead(
+            testRepo,
+            "refs/heads/new",
+            /* pushTags= */ false,
+            /* force= */ false,
+            /* pushOptions= */ ImmutableList.of(
+                String.format(
+                    "code-owners~%s=true", SkipCodeOwnerConfigValidationPushOption.NAME)));
+    assertPushOk(r, "refs/heads/new");
+  }
+
+  @Test
+  @GerritConfig(name = "plugin.code-owners.enableValidationOnBranchCreation", value = "true")
+  public void userWithoutCapabilitySkipValidationCannotSkipValidationForBranchCreationViaPush()
+      throws Exception {
+    // Create code owner config with a non-existing user as code owner.
+    String unknownEmail = "non-existing@example.com";
+    codeOwnerConfigOperations
+        .newCodeOwnerConfig()
+        .project(project)
+        .branch("master")
+        .folderPath("/")
+        .addCodeOwnerEmail(unknownEmail)
+        .create();
+
+    projectOperations
+        .project(project)
+        .forUpdate()
+        .add(allow(Permission.CREATE).ref(RefNames.REFS_HEADS + "*").group(REGISTERED_USERS))
+        .update();
+
+    requestScopeOperations.setApiUser(user.id());
+    TestRepository<InMemoryRepository> userRepo = cloneProject(project, user);
+    PushResult r =
+        pushHead(
+            userRepo,
+            "refs/heads/new",
+            /* pushTags= */ false,
+            /* force= */ false,
+            /* pushOptions= */ ImmutableList.of(
+                String.format(
+                    "code-owners~%s=true", SkipCodeOwnerConfigValidationPushOption.NAME)));
+    assertPushRejected(
+        r,
+        "refs/heads/new",
+        String.format(
+            "Validation for creation of ref 'refs/heads/new' in project %s failed:\n"
+                + "[code-owners] skipping code owner config validation not allowed:\n"
+                + "  ERROR: %s for plugin code-owners not permitted",
+            project, SkipCodeOwnerConfigValidationCapability.ID));
+  }
+
+  @Test
+  @GerritConfig(name = "plugin.code-owners.enableValidationOnBranchCreation", value = "false")
+  public void onBranchCreationValidationDisabled() throws Exception {
+    // Create a code owner config with a non-existing user as code owner.
+    codeOwnerConfigOperations
+        .newCodeOwnerConfig()
+        .project(project)
+        .branch("master")
+        .folderPath("/")
+        .addCodeOwnerEmail("non-existing@example.com")
+        .create();
+
+    RevCommit head = projectOperations.project(project).getHead("master");
+    testRepo.git().fetch().call();
+    testRepo.reset(head.name());
+
+    PushResult r =
+        pushHead(
+            testRepo,
+            "refs/heads/new",
+            /* pushTags= */ false,
+            /* force= */ false,
+            /* pushOptions= */ ImmutableList.of());
+    assertPushOk(r, "refs/heads/new");
+    assertThat(r.getMessages())
+        .contains(
+            "hint: [code-owners] skipping validation of code owner config files\n"
+                + "hint: [code-owners] code owners config validation is disabled");
+  }
+
+  @Test
+  @GerritConfig(name = "plugin.code-owners.enableValidationOnBranchCreation", value = "dry_run")
+  public void canCreateBranchWithInvalidCodeOwnerConfigIfValidationIsDoneAsDryRun()
+      throws Exception {
+    // Create a code owner config with a non-existing user as code owner.
+    String unknownEmail = "non-existing@example.com";
+    CodeOwnerConfig.Key codeOwnerConfigKey =
+        codeOwnerConfigOperations
+            .newCodeOwnerConfig()
+            .project(project)
+            .branch("master")
+            .folderPath("/")
+            .addCodeOwnerEmail(unknownEmail)
+            .create();
+
+    RevCommit head = projectOperations.project(project).getHead("master");
+    testRepo.git().fetch().call();
+    testRepo.reset(head.name());
+
+    PushResult r =
+        pushHead(
+            testRepo,
+            "refs/heads/new",
+            /* pushTags= */ false,
+            /* force= */ false,
+            /* pushOptions= */ ImmutableList.of());
+    assertPushOk(r, "refs/heads/new");
+    assertThat(r.getMessages())
+        .contains(
+            String.format(
+                "ERROR: [code-owners] invalid code owner config files\n"
+                    + "ERROR: [code-owners] code owner email '%s' in '%s' cannot be resolved for"
+                    + " %s",
+                unknownEmail,
+                codeOwnerConfigOperations.codeOwnerConfig(codeOwnerConfigKey).getFilePath(),
+                identifiedUserFactory.create(admin.id()).getLoggableName()));
+  }
+
+  @Test
   @GerritConfig(name = "plugin.code-owners.rejectNonResolvableCodeOwners", value = "false")
   @GerritConfig(name = "plugin.code-owners.enableValidationOnSubmit", value = "true")
   public void canUploadAndSubmitConfigWithUnresolvableCodeOwners() throws Exception {
diff --git a/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/PutCodeOwnerProjectConfigIT.java b/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/PutCodeOwnerProjectConfigIT.java
index 5834499..e632f2a 100644
--- a/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/PutCodeOwnerProjectConfigIT.java
+++ b/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/PutCodeOwnerProjectConfigIT.java
@@ -493,6 +493,32 @@
   }
 
   @Test
+  public void setEnableValidationOnBranchCreation() throws Exception {
+    assertThat(
+            codeOwnersPluginConfiguration
+                .getProjectConfig(project)
+                .getCodeOwnerConfigValidationPolicyForBranchCreation("master"))
+        .isEqualTo(CodeOwnerConfigValidationPolicy.FALSE);
+
+    CodeOwnerProjectConfigInput input = new CodeOwnerProjectConfigInput();
+    input.enableValidationOnBranchCreation = CodeOwnerConfigValidationPolicy.TRUE;
+    projectCodeOwnersApiFactory.project(project).updateConfig(input);
+    assertThat(
+            codeOwnersPluginConfiguration
+                .getProjectConfig(project)
+                .getCodeOwnerConfigValidationPolicyForBranchCreation("master"))
+        .isEqualTo(CodeOwnerConfigValidationPolicy.TRUE);
+
+    input.enableValidationOnBranchCreation = CodeOwnerConfigValidationPolicy.FALSE;
+    projectCodeOwnersApiFactory.project(project).updateConfig(input);
+    assertThat(
+            codeOwnersPluginConfiguration
+                .getProjectConfig(project)
+                .getCodeOwnerConfigValidationPolicyForBranchCreation("master"))
+        .isEqualTo(CodeOwnerConfigValidationPolicy.FALSE);
+  }
+
+  @Test
   public void setEnableValidationOnCommitReceived() throws Exception {
     assertThat(
             codeOwnersPluginConfiguration
diff --git a/javatests/com/google/gerrit/plugins/codeowners/backend/config/CodeOwnersPluginProjectConfigSnapshotTest.java b/javatests/com/google/gerrit/plugins/codeowners/backend/config/CodeOwnersPluginProjectConfigSnapshotTest.java
index 1dbe420..66649f9 100644
--- a/javatests/com/google/gerrit/plugins/codeowners/backend/config/CodeOwnersPluginProjectConfigSnapshotTest.java
+++ b/javatests/com/google/gerrit/plugins/codeowners/backend/config/CodeOwnersPluginProjectConfigSnapshotTest.java
@@ -1607,6 +1607,104 @@
   }
 
   @Test
+  public void cannotGetCodeOwnerConfigValidationPolicyForBranchCreationForNullBranch()
+      throws Exception {
+    NullPointerException npe =
+        assertThrows(
+            NullPointerException.class,
+            () ->
+                cfgSnapshot()
+                    .getCodeOwnerConfigValidationPolicyForBranchCreation(/* branchName= */ null));
+    assertThat(npe).hasMessageThat().isEqualTo("branchName");
+  }
+
+  @Test
+  public void getCodeOwnerConfigValidationPolicyForBranchCreation_notConfigured() throws Exception {
+    assertThat(cfgSnapshot().getCodeOwnerConfigValidationPolicyForBranchCreation("master"))
+        .isEqualTo(CodeOwnerConfigValidationPolicy.FALSE);
+    assertThat(cfgSnapshot().getCodeOwnerConfigValidationPolicyForBranchCreation("non-existing"))
+        .isEqualTo(CodeOwnerConfigValidationPolicy.FALSE);
+  }
+
+  @Test
+  public void getCodeOwnerConfigValidationPolicyForBranchCreation_configuredOnProjectLevel()
+      throws Exception {
+    configureEnableValidationOnBranchCreation(project, CodeOwnerConfigValidationPolicy.FALSE);
+    assertThat(cfgSnapshot().getCodeOwnerConfigValidationPolicyForBranchCreation("master"))
+        .isEqualTo(CodeOwnerConfigValidationPolicy.FALSE);
+    assertThat(cfgSnapshot().getCodeOwnerConfigValidationPolicyForBranchCreation("non-existing"))
+        .isEqualTo(CodeOwnerConfigValidationPolicy.FALSE);
+  }
+
+  @Test
+  public void getCodeOwnerConfigValidationPolicyForBranchCreation_configuredOnBranchLevel()
+      throws Exception {
+    configureEnableValidationOnBranchCreationForBranch(
+        project, "refs/heads/master", CodeOwnerConfigValidationPolicy.TRUE);
+    assertThat(cfgSnapshot().getCodeOwnerConfigValidationPolicyForBranchCreation("master"))
+        .isEqualTo(CodeOwnerConfigValidationPolicy.TRUE);
+    assertThat(
+            cfgSnapshot().getCodeOwnerConfigValidationPolicyForBranchCreation("refs/heads/master"))
+        .isEqualTo(CodeOwnerConfigValidationPolicy.TRUE);
+    assertThat(cfgSnapshot().getCodeOwnerConfigValidationPolicyForBranchCreation("foo"))
+        .isEqualTo(CodeOwnerConfigValidationPolicy.FALSE);
+  }
+
+  @Test
+  public void getCodeOwnerConfigValidationPolicyForBranchCreation_branchLevelConfigTakesPrecedence()
+      throws Exception {
+    updateCodeOwnersConfig(
+        project,
+        codeOwnersConfig -> {
+          codeOwnersConfig.setEnum(
+              CodeOwnersPluginConfiguration.SECTION_CODE_OWNERS,
+              /* subsection= */ null,
+              GeneralConfig.KEY_ENABLE_VALIDATION_ON_BRANCH_CREATION,
+              CodeOwnerConfigValidationPolicy.DRY_RUN);
+          codeOwnersConfig.setEnum(
+              GeneralConfig.SECTION_VALIDATION,
+              "refs/heads/master",
+              GeneralConfig.KEY_ENABLE_VALIDATION_ON_BRANCH_CREATION,
+              CodeOwnerConfigValidationPolicy.FALSE);
+        });
+    assertThat(cfgSnapshot().getCodeOwnerConfigValidationPolicyForBranchCreation("master"))
+        .isEqualTo(CodeOwnerConfigValidationPolicy.FALSE);
+    assertThat(
+            cfgSnapshot().getCodeOwnerConfigValidationPolicyForBranchCreation("refs/heads/master"))
+        .isEqualTo(CodeOwnerConfigValidationPolicy.FALSE);
+    assertThat(cfgSnapshot().getCodeOwnerConfigValidationPolicyForBranchCreation("foo"))
+        .isEqualTo(CodeOwnerConfigValidationPolicy.DRY_RUN);
+  }
+
+  @Test
+  public void
+      getCodeOwnerConfigValidationPolicyForBranchCreation_inheritedBranchLevelConfigTakesPrecedence()
+          throws Exception {
+    configureEnableValidationOnBranchCreationForBranch(
+        allProjects, "refs/heads/master", CodeOwnerConfigValidationPolicy.FALSE);
+    configureEnableValidationOnBranchCreation(project, CodeOwnerConfigValidationPolicy.DRY_RUN);
+    assertThat(cfgSnapshot().getCodeOwnerConfigValidationPolicyForBranchCreation("master"))
+        .isEqualTo(CodeOwnerConfigValidationPolicy.FALSE);
+    assertThat(
+            cfgSnapshot().getCodeOwnerConfigValidationPolicyForBranchCreation("refs/heads/master"))
+        .isEqualTo(CodeOwnerConfigValidationPolicy.FALSE);
+    assertThat(cfgSnapshot().getCodeOwnerConfigValidationPolicyForBranchCreation("foo"))
+        .isEqualTo(CodeOwnerConfigValidationPolicy.DRY_RUN);
+  }
+
+  @Test
+  public void
+      getCodeOwnerConfigValidationPolicyForBranchCreation_inheritedBranchLevelCanBeOverridden()
+          throws Exception {
+    configureEnableValidationOnBranchCreationForBranch(
+        allProjects, "refs/heads/master", CodeOwnerConfigValidationPolicy.FALSE);
+    configureEnableValidationOnBranchCreationForBranch(
+        project, "refs/heads/master", CodeOwnerConfigValidationPolicy.DRY_RUN);
+    assertThat(cfgSnapshot().getCodeOwnerConfigValidationPolicyForBranchCreation("master"))
+        .isEqualTo(CodeOwnerConfigValidationPolicy.DRY_RUN);
+  }
+
+  @Test
   public void cannotGetCodeOwnerConfigValidationPolicyForCommitReceivedForNullBranch()
       throws Exception {
     NullPointerException npe =
@@ -2036,6 +2134,31 @@
         requiredApproval);
   }
 
+  private void configureEnableValidationOnBranchCreation(
+      Project.NameKey project, CodeOwnerConfigValidationPolicy codeOwnerConfigValidationPolicy)
+      throws Exception {
+    setCodeOwnersConfig(
+        project,
+        /* subsection= */ null,
+        GeneralConfig.KEY_ENABLE_VALIDATION_ON_BRANCH_CREATION,
+        codeOwnerConfigValidationPolicy.name());
+  }
+
+  private void configureEnableValidationOnBranchCreationForBranch(
+      Project.NameKey project,
+      String branchSubsection,
+      CodeOwnerConfigValidationPolicy codeOwnerConfigValidationPolicy)
+      throws Exception {
+    updateCodeOwnersConfig(
+        project,
+        codeOwnersConfig ->
+            codeOwnersConfig.setString(
+                GeneralConfig.SECTION_VALIDATION,
+                branchSubsection,
+                GeneralConfig.KEY_ENABLE_VALIDATION_ON_BRANCH_CREATION,
+                codeOwnerConfigValidationPolicy.name()));
+  }
+
   private void configureEnableValidationOnCommitReceived(
       Project.NameKey project, CodeOwnerConfigValidationPolicy codeOwnerConfigValidationPolicy)
       throws Exception {
diff --git a/javatests/com/google/gerrit/plugins/codeowners/backend/config/GeneralConfigTest.java b/javatests/com/google/gerrit/plugins/codeowners/backend/config/GeneralConfigTest.java
index 06de8a5..43d8bfe 100644
--- a/javatests/com/google/gerrit/plugins/codeowners/backend/config/GeneralConfigTest.java
+++ b/javatests/com/google/gerrit/plugins/codeowners/backend/config/GeneralConfigTest.java
@@ -20,6 +20,7 @@
 import static com.google.gerrit.plugins.codeowners.backend.config.GeneralConfig.KEY_ENABLE_ASYNC_MESSAGE_ON_ADD_REVIEWER;
 import static com.google.gerrit.plugins.codeowners.backend.config.GeneralConfig.KEY_ENABLE_CODE_OWNER_CONFIG_FILES_WITH_FILE_EXTENSIONS;
 import static com.google.gerrit.plugins.codeowners.backend.config.GeneralConfig.KEY_ENABLE_IMPLICIT_APPROVALS;
+import static com.google.gerrit.plugins.codeowners.backend.config.GeneralConfig.KEY_ENABLE_VALIDATION_ON_BRANCH_CREATION;
 import static com.google.gerrit.plugins.codeowners.backend.config.GeneralConfig.KEY_ENABLE_VALIDATION_ON_COMMIT_RECEIVED;
 import static com.google.gerrit.plugins.codeowners.backend.config.GeneralConfig.KEY_ENABLE_VALIDATION_ON_SUBMIT;
 import static com.google.gerrit.plugins.codeowners.backend.config.GeneralConfig.KEY_EXEMPTED_USER;
@@ -786,6 +787,234 @@
   }
 
   @Test
+  public void cannotGetEnableValidationOnBranchCreationForNullPluginConfig() throws Exception {
+    NullPointerException npe =
+        assertThrows(
+            NullPointerException.class,
+            () ->
+                generalConfig.getCodeOwnerConfigValidationPolicyForBranchCreation(
+                    project, /* pluginConfig= */ null));
+    assertThat(npe).hasMessageThat().isEqualTo("pluginConfig");
+  }
+
+  @Test
+  public void noEnableValidationOnBranchCreationConfiguration() throws Exception {
+    assertThat(
+            generalConfig.getCodeOwnerConfigValidationPolicyForBranchCreation(
+                project, new Config()))
+        .isEqualTo(CodeOwnerConfigValidationPolicy.FALSE);
+  }
+
+  @Test
+  @GerritConfig(name = "plugin.code-owners.enableValidationOnBranchCreation", value = "true")
+  public void
+      enableValidationOnBranchCreationConfigurationIsRetrievedFromGerritConfigIfNotSpecifiedOnProjectLevel()
+          throws Exception {
+    assertThat(
+            generalConfig.getCodeOwnerConfigValidationPolicyForBranchCreation(
+                project, new Config()))
+        .isEqualTo(CodeOwnerConfigValidationPolicy.TRUE);
+  }
+
+  @Test
+  @GerritConfig(name = "plugin.code-owners.enableValidationOnBranchCreation", value = "false")
+  public void
+      enableValidationOnBranchConfigurationInPluginConfigOverridesEnableValidationOnBranchCreationConfigurationInGerritConfig()
+          throws Exception {
+    Config cfg = new Config();
+    cfg.setString(
+        SECTION_CODE_OWNERS,
+        /* subsection= */ null,
+        KEY_ENABLE_VALIDATION_ON_BRANCH_CREATION,
+        "true");
+    assertThat(generalConfig.getCodeOwnerConfigValidationPolicyForBranchCreation(project, cfg))
+        .isEqualTo(CodeOwnerConfigValidationPolicy.TRUE);
+  }
+
+  @Test
+  @GerritConfig(name = "plugin.code-owners.enableValidationOnBranchCreation", value = "false")
+  public void invalidEnableValidationOnBranchCreationConfigurationInPluginConfigIsIgnored()
+      throws Exception {
+    Config cfg = new Config();
+    cfg.setString(
+        SECTION_CODE_OWNERS,
+        /* subsection= */ null,
+        KEY_ENABLE_VALIDATION_ON_BRANCH_CREATION,
+        "INVALID");
+    assertThat(generalConfig.getCodeOwnerConfigValidationPolicyForBranchCreation(project, cfg))
+        .isEqualTo(CodeOwnerConfigValidationPolicy.FALSE);
+  }
+
+  @Test
+  @GerritConfig(name = "plugin.code-owners.enableValidationOnBranchCreation", value = "INVALID")
+  public void invalidEnableValidationOnBranchCreationConfigurationInGerritConfigIsIgnored()
+      throws Exception {
+    assertThat(
+            generalConfig.getCodeOwnerConfigValidationPolicyForBranchCreation(
+                project, new Config()))
+        .isEqualTo(CodeOwnerConfigValidationPolicy.FALSE);
+  }
+
+  @Test
+  public void cannotGetEnableValidationOnBranchForBranchForNullBranch() throws Exception {
+    NullPointerException npe =
+        assertThrows(
+            NullPointerException.class,
+            () ->
+                generalConfig.getCodeOwnerConfigValidationPolicyForBranchCreationForBranch(
+                    /* branchNameKey= */ null, new Config()));
+    assertThat(npe).hasMessageThat().isEqualTo("branchNameKey");
+  }
+
+  @Test
+  public void cannotGetEnableValidationOnBranchCreationForBranchForNullPluginConfig()
+      throws Exception {
+    NullPointerException npe =
+        assertThrows(
+            NullPointerException.class,
+            () ->
+                generalConfig.getCodeOwnerConfigValidationPolicyForBranchCreationForBranch(
+                    BranchNameKey.create(project, "master"), /* pluginConfig= */ null));
+    assertThat(npe).hasMessageThat().isEqualTo("pluginConfig");
+  }
+
+  @Test
+  public void noBranchSpecificEnableValidationOnBranchCreationConfiguration() throws Exception {
+    assertThat(
+            generalConfig.getCodeOwnerConfigValidationPolicyForBranchCreationForBranch(
+                BranchNameKey.create(project, "master"), new Config()))
+        .isEmpty();
+  }
+
+  @Test
+  public void noMatchingBranchSpecificEnableValidationOnBranchCreationConfiguration_exact()
+      throws Exception {
+    Config cfg = new Config();
+    cfg.setString(
+        SECTION_VALIDATION, "refs/heads/foo", KEY_ENABLE_VALIDATION_ON_BRANCH_CREATION, "false");
+    assertThat(
+            generalConfig.getCodeOwnerConfigValidationPolicyForBranchCreationForBranch(
+                BranchNameKey.create(project, "master"), cfg))
+        .isEmpty();
+  }
+
+  @Test
+  public void noMatchingBranchSpecificEnableValidationOnBranchCreationConfiguration_refPattern()
+      throws Exception {
+    Config cfg = new Config();
+    cfg.setString(
+        SECTION_VALIDATION, "refs/heads/foo/*", KEY_ENABLE_VALIDATION_ON_COMMIT_RECEIVED, "false");
+    assertThat(
+            generalConfig.getCodeOwnerConfigValidationPolicyForBranchCreationForBranch(
+                BranchNameKey.create(project, "master"), cfg))
+        .isEmpty();
+  }
+
+  @Test
+  public void noMatchingBranchSpecificEnableValidationOnBranchCreationConfiguration_regEx()
+      throws Exception {
+    Config cfg = new Config();
+    cfg.setString(
+        SECTION_VALIDATION,
+        "^refs/heads/.*foo.*",
+        KEY_ENABLE_VALIDATION_ON_BRANCH_CREATION,
+        "false");
+    assertThat(
+            generalConfig.getCodeOwnerConfigValidationPolicyForBranchCreationForBranch(
+                BranchNameKey.create(project, "master"), cfg))
+        .isEmpty();
+  }
+
+  @Test
+  public void noMatchingBranchSpecificEnableValidationOnBranchCreationConfiguration_invalidRegEx()
+      throws Exception {
+    Config cfg = new Config();
+    cfg.setString(
+        SECTION_VALIDATION, "^refs/heads/[", KEY_ENABLE_VALIDATION_ON_BRANCH_CREATION, "false");
+    assertThat(
+            generalConfig.getCodeOwnerConfigValidationPolicyForBranchCreationForBranch(
+                BranchNameKey.create(project, "master"), cfg))
+        .isEmpty();
+  }
+
+  @Test
+  public void matchingBranchSpecificEnableValidationOnBranchCreationConfiguration_exact()
+      throws Exception {
+    Config cfg = new Config();
+    cfg.setString(
+        SECTION_VALIDATION, "refs/heads/master", KEY_ENABLE_VALIDATION_ON_BRANCH_CREATION, "false");
+    assertThat(
+            generalConfig.getCodeOwnerConfigValidationPolicyForBranchCreationForBranch(
+                BranchNameKey.create(project, "master"), cfg))
+        .value()
+        .isEqualTo(CodeOwnerConfigValidationPolicy.FALSE);
+  }
+
+  @Test
+  public void matchingBranchSpecificEnableValidationOnBranchCreationConfiguration_refPattern()
+      throws Exception {
+    Config cfg = new Config();
+    cfg.setString(
+        SECTION_VALIDATION, "refs/heads/*", KEY_ENABLE_VALIDATION_ON_BRANCH_CREATION, "false");
+    assertThat(
+            generalConfig.getCodeOwnerConfigValidationPolicyForBranchCreationForBranch(
+                BranchNameKey.create(project, "master"), cfg))
+        .value()
+        .isEqualTo(CodeOwnerConfigValidationPolicy.FALSE);
+  }
+
+  @Test
+  public void matchingBranchSpecificEnableValidationOnBranchCreationConfiguration_regEx()
+      throws Exception {
+    Config cfg = new Config();
+    cfg.setString(
+        SECTION_VALIDATION,
+        "^refs/heads/.*bar.*",
+        KEY_ENABLE_VALIDATION_ON_BRANCH_CREATION,
+        "false");
+    assertThat(
+            generalConfig.getCodeOwnerConfigValidationPolicyForBranchCreationForBranch(
+                BranchNameKey.create(project, "foobarbaz"), cfg))
+        .value()
+        .isEqualTo(CodeOwnerConfigValidationPolicy.FALSE);
+  }
+
+  @Test
+  public void branchSpecificEnableValidationOnBranchCreationConfigurationIsIgnoredIfValueIsInvalid()
+      throws Exception {
+    Config cfg = new Config();
+    cfg.setString(
+        SECTION_VALIDATION,
+        "refs/heads/master",
+        KEY_ENABLE_VALIDATION_ON_BRANCH_CREATION,
+        "INVALID");
+    assertThat(
+            generalConfig.getCodeOwnerConfigValidationPolicyForBranchCreationForBranch(
+                BranchNameKey.create(project, "master"), cfg))
+        .isEmpty();
+  }
+
+  @Test
+  public void multipleMatchingBranchSpecificEnableValidationOnBranchCreationConfiguration()
+      throws Exception {
+    Config cfg = new Config();
+    cfg.setString(
+        SECTION_VALIDATION, "refs/heads/master", KEY_ENABLE_VALIDATION_ON_BRANCH_CREATION, "false");
+    cfg.setString(
+        SECTION_VALIDATION, "refs/heads/*", KEY_ENABLE_VALIDATION_ON_BRANCH_CREATION, "false");
+    cfg.setString(
+        SECTION_VALIDATION, "^refs/heads/.*", KEY_ENABLE_VALIDATION_ON_BRANCH_CREATION, "false");
+
+    // it is non-deterministic which of the branch-specific configurations takes precedence, but
+    // since they all configure the same value it's not important for this assertion
+    assertThat(
+            generalConfig.getCodeOwnerConfigValidationPolicyForBranchCreationForBranch(
+                BranchNameKey.create(project, "master"), cfg))
+        .value()
+        .isEqualTo(CodeOwnerConfigValidationPolicy.FALSE);
+  }
+
+  @Test
   public void cannotGetEnableValidationOnCommitReceivedForNullPluginConfig() throws Exception {
     NullPointerException npe =
         assertThrows(
diff --git a/resources/Documentation/backend-find-owners.md b/resources/Documentation/backend-find-owners.md
index 18fb11e..c2eaeea 100644
--- a/resources/Documentation/backend-find-owners.md
+++ b/resources/Documentation/backend-find-owners.md
@@ -167,6 +167,7 @@
 file](backends.html#codeOwnerConfigFiles). It's not possible to import arbitrary
 files.
 
+##### <a id="referenceCodeOwnerConfigFilesFromOtherProjects">
 It's also possible to reference code owner config files from other projects or
 branches (only within the same host):
 
diff --git a/resources/Documentation/config-faqs.md b/resources/Documentation/config-faqs.md
index ab88bb7..8766ffd 100644
--- a/resources/Documentation/config-faqs.md
+++ b/resources/Documentation/config-faqs.md
@@ -5,6 +5,8 @@
 * [How to avoid issues with code owner config files](#avoidIssuesWithCodeOwnerConfigs)
 * [How to investigate issues with code owner config files](#investigateIssuesWithCodeOwnerConfigs)
 * [How to investigate issues with the code owner suggestion](#investigateIssuesWithCodeOwnerSuggestion)
+* [What should be done when creating a branch fails due to invalid code owner
+  config files?](#branchCreationFailsDueInvalidCodeOwnerConfigFiles)
 * [How to define default code owners](#defineDefaultCodeOwners)
 * [How to setup code owner overrides](#setupOverrides)
 * [What's the best place to keep the global plugin
@@ -136,6 +138,31 @@
 Also see [above](#avoidIssuesWithCodeOwnerConfigs) how to avoid issues with code
 owner config files in the first place.
 
+## <a id="branchCreationFailsDueInvalidCodeOwnerConfigFiles">What should be done when creating a branch fails due to invalid code owner config files?
+
+When creating a new branch, all code owner config files that are contained in
+the initial commit are newly [validated](validation.html#codeOwnerConfigValidationOnBranchCreation), even if the branch is created for a
+commit that already exists in the repository.
+
+If creating a branch fails due to this validation, it is recommended to:
+
+1. Use the [code-owners~skip-validation
+   validation](validation.html#skipCodeOwnerConfigValidationOnDemand) option to
+   skip the validation of code owner config files when creating the branch.
+2. Use the
+   [Check Code Owner Config Files](rest-api.html#check-code-owner-config-files)
+   REST endpoint to validate the code owner files in the new branch (specify
+   the branch in the `branches` field in the
+   [CheckCodeOwnerConfigFilesInput](rest-api.html#check-code-owner-config-files-input))
+   to see which code owner config files have issues.
+3. Fix the reported issues and push them as a change for code review. If
+   needed, get the change submitted with a [code owner
+   override](user-guide.html#codeOwnerOverride).
+4. Repeat step 2. to verify that all issues have been fixed.
+
+It's also possible to switch of the code owner config validation on branch
+creation by [configuration](config.html#pluginCodeOwnersEnableValidationOnBranchCreation).
+
 ## <a id="defineDefaultCodeOwners">How to define default code owners
 
 [Default code owners](backend-find-owners.html#defaultCodeOwnerConfiguration)
diff --git a/resources/Documentation/config.md b/resources/Documentation/config.md
index 4a53e87..b4ea424 100644
--- a/resources/Documentation/config.md
+++ b/resources/Documentation/config.md
@@ -304,6 +304,50 @@
         `@PLUGIN@.config`.\
         By default `false`.
 
+<a id="pluginCodeOwnersEnableValidationOnBranchCreation">plugin.@PLUGIN@.enableValidationOnBranchCreation</a>
+:       Policy for validating code owner config files when a branch is created.
+        If the validation is on all code owner config files that are contained
+        in the commit on which the branch is being created are validated. Doing
+        this is rather expensive and will make branch creations significantly
+        slower (in average a latency increase of 10s to 20s is expected).
+        \
+        Can be `TRUE`, `FALSE`, `DRY_RUN`, `FORCED` or `FORCED_DRY_RUN`.\
+        \
+        `TRUE`:\
+        The code owner config file validation is enabled and the creation of
+        branches that contain invalid code owner config files is rejected.\
+        If the code owners functionality is disabled, no validation is
+        performed.\
+        \
+        `FALSE`:\
+        The code owner config file validation is disabled, the creation of
+        branches that contain invalid code owner config files is not rejected.\
+        \
+        `DRY_RUN`:\
+        Code owner config files are validated on branch creation, but the
+        creation of branches that contain invalid code owner config files is not
+        rejected.\
+        If the code owners functionality is disabled, no dry-run validation is
+        performed.\
+        \
+        `FORCED`:\
+        Code owner config files are validated on branch creation even if the
+        code owners functionality is disabled.\
+        This option is useful when the code owner config validation should be
+        enabled as preparation to enabling the code owners functionality.\
+        \
+        `FORCED_DRY_RUN`:\
+        Code owner config files are validated on branch creation even if the
+        code owners functionality is disabled, but the creation of branches that
+        contain invalid code owner config files is not rejected.\
+        This option is useful when the code owner config validation should be
+        enabled as preparation to enabling the code owners functionality.\
+        \
+        Can be overridden per project by setting
+        [codeOwners.enableValidationOnBranchCreation](#codeOwnersEnableValidationOnBranchCreation)
+        in `@PLUGIN@.config`.\
+        By default `FALSE`.
+
 <a id="pluginCodeOwnersEnableValidationOnCommitReceived">plugin.@PLUGIN@.enableValidationOnCommitReceived</a>
 :       Policy for validating code owner config files when a commit is received.
         \
@@ -872,6 +916,35 @@
         [plugin.@PLUGIN@.exemptPureReverts](#pluginCodeOwnersExemptPureReverts)
         in `gerrit.config` is used.
 
+<a id="codeOwnersEnableValidationOnBranchCreation">codeOwners.enableValidationOnBranchCreation</a>
+:       Policy for validating code owner config files when a branch is created.\
+        Can be `TRUE`, `FALSE`, `DRY_RUN`, `FORCED` or `FORCED_DRY_RUN`. For a
+        description of the values see
+        [plugin.@PLUGIN@.enableValidationOnBranchCreation](#pluginCodeOwnersEnableValidationOnBranchCreation).\
+        Overrides the global setting
+        [plugin.@PLUGIN@.enableValidationOnBranchCreation](#pluginCodeOwnersEnableValidationOnBranchCreation)
+        in `gerrit.config` and the `codeOwners.enableValidationOnBranchCreation`
+        setting from parent projects.\
+        Can be overriden on branch-level by setting
+        [validation.\<branch\>.enableValidationOnBranchCreation](#validationBranchEnableValidationOnBranchCreation).\
+        If not set, the global setting
+        [plugin.@PLUGIN@.enableValidationOnBranchCreation](#pluginCodeOwnersEnableValidationOnBranchCreation)
+        in `gerrit.config` is used.
+
+<a id="validationBranchEnableValidationOnBranchCreation">validation.\<branch\>.enableValidationOnBranchCreation</a>
+:       Branch-level policy for validating code owner config files when a branch
+        is created.\
+        Applies to all branches that are matched by `<branch>`, which can be
+        an exact ref name (e.g. `refs/heads/master`), a ref pattern (e.g.
+        `refs/heads/*`) or a regular expression (e.g. `^refs/heads/stable-.*`).\
+        If a branch matches multiple validation subsections it is undefined
+        which of the subsections takes precedence.\
+        Overrides the project-level configuration for validating code owner
+        config files when a branch is created that is configured by
+        [codeOwners.enableValidationOnBranchCreation](#codeOwnersEnableValidationOnBranchCreation).\
+        For further details see the description of
+        [codeOwners.enableValidationOnBranchCreation](#codeOwnersEnableValidationOnBranchCreation).
+
 <a id="codeOwnersEnableValidationOnCommitReceived">codeOwners.enableValidationOnCommitReceived</a>
 :       Policy for validating code owner config files when a commit is
         received.\
diff --git a/resources/Documentation/rest-api.md b/resources/Documentation/rest-api.md
index c38c20f..581b8ee 100644
--- a/resources/Documentation/rest-api.md
+++ b/resources/Documentation/rest-api.md
@@ -995,6 +995,7 @@
 | `invalid_code_owner_config_info_url` | optional | URL for a page that provides project/host-specific information about how to deal with invalid code owner config files.
 | `read_only` | optional | Whether code owner config files are read-only.
 | `exempt_pure_reverts` | optional | Whether pure revert changes are exempted from needing code owner approvals for submit.
+| `enable_validation_on_branch_creation` | optional | Policy for validating code owner config files when a branch is created. Allowed values are `true` (the code owner config file validation is enabled and the creation of branches that contain invalid code owner config files is rejected), `false` (the code owner config file validation is disabled, the creation of branches that contain invalid code owner config files is not rejected), `dry_run` (code owner config files are validated on branch creation, but the creation of branches that contain invalid code owner config files is not rejected), `forced` (code owner config files are validated on branch creation even if the code owners functionality is disabled) and `forced_dry_run` (code owner config files are validated on branch creation even if the code owners functionality is disabled, but the creation of branches that contain invalid code owner config files is not rejected).
 | `enable_validation_on_commit_received` | optional | Policy for validating code owner config files when a commit is received. Allowed values are `true` (the code owner config file validation is enabled and the upload of invalid code owner config files is rejected), `false` (the code owner config file validation is disabled, invalid code owner config files are not rejected), `dry_run` (code owner config files are validated, but invalid code owner config files are not rejected), `forced` (code owner config files are validated even if the code owners functionality is disabled) and `forced_dry_run` (code owner config files are validated even if the code owners functionality is disabled, but invalid code owner config files are not rejected).
 | `enable_validation_on_submit` | optional | Policy for validating code owner config files when a change is submitted. Allowed values are `true` (the code owner config file validation is enabled and the submission of invalid code owner config files is rejected), `false` (the code owner config file validation is disabled, invalid code owner config files are not rejected), `dry_run` (code owner config files are validated, but invalid code owner config files are not rejected), `forced` (code owner config files are validated even if the code owners functionality is disabled) and `forced_dry_run` (code owner config files are validated even if the code owners functionality is disabled, but invalid code owner config files are not rejected).
 | `reject_non_resolvable_code_owners` | optional | Whether modifications of code owner config files that newly add non-resolvable code owners should be rejected on commit received and submit.
diff --git a/resources/Documentation/validation.md b/resources/Documentation/validation.md
index ae1f60f..60ee4d2 100644
--- a/resources/Documentation/validation.md
+++ b/resources/Documentation/validation.md
@@ -95,6 +95,35 @@
 validation that was done on upload. This means, all visibility checks will be
 done from the perspective of the uploader.
 
+## <a id="codeOwnerConfigValidationOnBranchCreation">Code owner config validation on branch creation
+
+It's possible to [enable validation of code owner config files on branch
+creation](config.html#pluginCodeOwnersEnableValidationOnBranchCreation) (off by
+default).
+
+If the validation is enabled and a new branch is created, all code owner config
+files that are contained in the initial commit are newly validated, even if the
+branch is created for a commit that already exists in the repository.
+
+Validating code owner config files newly when a branch is created makes sense
+because:
+
+* the validation configuration of the new branch may differ from the validation
+  configuration of the branch that already contains the commit
+* [imports from other projects](backend-find-owners.html#referenceCodeOwnerConfigFilesFromOtherProjects)
+  that do not specify a branch may not be resolvable: If a branch is not
+  specified it's assumed that the code owner config file from the other project
+  should be imported from the same branch that contains the importing code owner
+  config. This means when a new branch `foo` is being created a code owner
+  config file that is referenced by such an import is expected to be found in
+  the branch `foo` of the other project, but this branch may not exist there so
+  that the import is unresolvable. By validating the code owner config files on
+  branch creation such unresolvable imports are detected and flagged.
+
+What should be done if the creation of a branch fails due to invalid code owner
+config files is explained in the
+[config FAQs](config-faqs.html#branchCreationFailsDueInvalidCodeOwnerConfigFiles).
+
 ## <a id="skipCodeOwnerConfigValidationOnDemand">Skip code owner config validation on demand
 
 By setting the `code-owners~skip-validation` push option it is possible to skip
@@ -102,6 +131,7 @@
 `git push -o code-owners~skip-validation origin HEAD:refs/for/master`
 
 For the [Create
+Branch](../../../Documentation/rest-api-projects.html#create-branch), [Create
 Change](../../../Documentation/rest-api-changes.html#create-change), the [Cherry
 Pick Revision](../../../Documentation/rest-api-changes.html#cherry-pick) and the
 [Rebase](../../../Documentation/rest-api-changes.html#rebase-change) REST