Merge "Log on fine level when entities are indexed"
diff --git a/java/com/google/gerrit/server/git/receive/AsyncReceiveCommits.java b/java/com/google/gerrit/server/git/receive/AsyncReceiveCommits.java
index 7fe0c04..832b8cd 100644
--- a/java/com/google/gerrit/server/git/receive/AsyncReceiveCommits.java
+++ b/java/com/google/gerrit/server/git/receive/AsyncReceiveCommits.java
@@ -94,6 +94,7 @@
       // Don't expose the binding for ReceiveCommits.Factory. All callers should
       // be using AsyncReceiveCommits.Factory instead.
       install(new FactoryModuleBuilder().build(ReceiveCommits.Factory.class));
+      install(new FactoryModuleBuilder().build(BranchCommitValidator.Factory.class));
     }
 
     @Provides
diff --git a/java/com/google/gerrit/server/git/receive/BranchCommitValidator.java b/java/com/google/gerrit/server/git/receive/BranchCommitValidator.java
new file mode 100644
index 0000000..24b6ab1
--- /dev/null
+++ b/java/com/google/gerrit/server/git/receive/BranchCommitValidator.java
@@ -0,0 +1,132 @@
+// Copyright (C) 2018 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.git.receive;
+
+import static org.eclipse.jgit.transport.ReceiveCommand.Result.REJECTED_OTHER_REASON;
+
+import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.common.Nullable;
+import com.google.gerrit.reviewdb.client.Branch;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.reviewdb.client.RevId;
+import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.events.CommitReceivedEvent;
+import com.google.gerrit.server.git.validators.CommitValidationException;
+import com.google.gerrit.server.git.validators.CommitValidationMessage;
+import com.google.gerrit.server.git.validators.CommitValidators;
+import com.google.gerrit.server.git.validators.ValidationMessage;
+import com.google.gerrit.server.permissions.PermissionBackend;
+import com.google.gerrit.server.project.ProjectState;
+import com.google.gerrit.server.ssh.SshInfo;
+import com.google.inject.Inject;
+import com.google.inject.assistedinject.Assisted;
+import java.io.IOException;
+import java.util.List;
+import org.eclipse.jgit.lib.ObjectReader;
+import org.eclipse.jgit.notes.NoteMap;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.transport.ReceiveCommand;
+
+/** Validates single commits for a branch. */
+public class BranchCommitValidator {
+  private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+
+  private final CommitValidators.Factory commitValidatorsFactory;
+  private final IdentifiedUser user;
+  private final PermissionBackend.ForProject permissions;
+  private final Project project;
+  private final Branch.NameKey branch;
+  private final SshInfo sshInfo;
+
+  interface Factory {
+    BranchCommitValidator create(
+        ProjectState projectState, Branch.NameKey branch, IdentifiedUser user);
+  }
+
+  @Inject
+  BranchCommitValidator(
+      CommitValidators.Factory commitValidatorsFactory,
+      PermissionBackend permissionBackend,
+      SshInfo sshInfo,
+      @Assisted ProjectState projectState,
+      @Assisted Branch.NameKey branch,
+      @Assisted IdentifiedUser user) {
+    this.sshInfo = sshInfo;
+    this.user = user;
+    this.branch = branch;
+    this.commitValidatorsFactory = commitValidatorsFactory;
+    project = projectState.getProject();
+    permissions = permissionBackend.user(user).project(project.getNameKey());
+  }
+
+  /**
+   * Validates a single commit. If the commit does not validate, the command is rejected.
+   *
+   * @param objectReader the object reader to use.
+   * @param cmd the ReceiveCommand executing the push.
+   * @param commit the commit being validated.
+   * @param isMerged whether this is a merge commit created by magicBranch --merge option
+   * @param change the change for which this is a new patchset.
+   */
+  public boolean validCommit(
+      ObjectReader objectReader,
+      ReceiveCommand cmd,
+      RevCommit commit,
+      boolean isMerged,
+      List<ValidationMessage> messages,
+      NoteMap rejectCommits,
+      @Nullable Change change)
+      throws IOException {
+    try (CommitReceivedEvent receiveEvent =
+        new CommitReceivedEvent(cmd, project, branch.get(), objectReader, commit, user)) {
+      CommitValidators validators;
+      if (isMerged) {
+        validators =
+            commitValidatorsFactory.forMergedCommits(permissions, branch, user.asIdentifiedUser());
+      } else {
+        validators =
+            commitValidatorsFactory.forReceiveCommits(
+                permissions,
+                branch,
+                user.asIdentifiedUser(),
+                sshInfo,
+                rejectCommits,
+                receiveEvent.revWalk,
+                change);
+      }
+
+      for (CommitValidationMessage m : validators.validate(receiveEvent)) {
+        messages.add(
+            new CommitValidationMessage(messageForCommit(commit, m.getMessage()), m.isError()));
+      }
+    } catch (CommitValidationException e) {
+      logger.atFine().log("Commit validation failed on %s", commit.name());
+      for (CommitValidationMessage m : e.getMessages()) {
+        // The non-error messages may contain background explanation for the
+        // fatal error, so have to preserve all messages.
+        messages.add(
+            new CommitValidationMessage(messageForCommit(commit, m.getMessage()), m.isError()));
+      }
+      cmd.setResult(REJECTED_OTHER_REASON, messageForCommit(commit, e.getMessage()));
+      return false;
+    }
+    return true;
+  }
+
+  private String messageForCommit(RevCommit c, String msg) {
+    return String.format("commit %s: %s", c.abbreviate(RevId.ABBREV_LEN).name(), msg);
+  }
+}
diff --git a/java/com/google/gerrit/server/git/receive/ReceiveCommits.java b/java/com/google/gerrit/server/git/receive/ReceiveCommits.java
index 438438c..2541d28 100644
--- a/java/com/google/gerrit/server/git/receive/ReceiveCommits.java
+++ b/java/com/google/gerrit/server/git/receive/ReceiveCommits.java
@@ -39,7 +39,6 @@
 import static org.eclipse.jgit.transport.ReceiveCommand.Result.REJECTED_MISSING_OBJECT;
 import static org.eclipse.jgit.transport.ReceiveCommand.Result.REJECTED_OTHER_REASON;
 
-import com.google.auto.value.AutoValue;
 import com.google.common.base.Function;
 import com.google.common.base.Joiner;
 import com.google.common.base.Splitter;
@@ -96,7 +95,6 @@
 import com.google.gerrit.server.Sequences;
 import com.google.gerrit.server.ServerInitiated;
 import com.google.gerrit.server.account.AccountResolver;
-import com.google.gerrit.server.account.AccountState;
 import com.google.gerrit.server.account.AccountsUpdate;
 import com.google.gerrit.server.change.ChangeInserter;
 import com.google.gerrit.server.change.SetHashtagsOp;
@@ -106,7 +104,6 @@
 import com.google.gerrit.server.config.ProjectConfigEntry;
 import com.google.gerrit.server.edit.ChangeEdit;
 import com.google.gerrit.server.edit.ChangeEditUtil;
-import com.google.gerrit.server.events.CommitReceivedEvent;
 import com.google.gerrit.server.git.BanCommit;
 import com.google.gerrit.server.git.ChangeReportFormatter;
 import com.google.gerrit.server.git.GroupCollector;
@@ -116,9 +113,7 @@
 import com.google.gerrit.server.git.ReceivePackInitializer;
 import com.google.gerrit.server.git.TagCache;
 import com.google.gerrit.server.git.ValidationError;
-import com.google.gerrit.server.git.validators.CommitValidationException;
 import com.google.gerrit.server.git.validators.CommitValidationMessage;
-import com.google.gerrit.server.git.validators.CommitValidators;
 import com.google.gerrit.server.git.validators.RefOperationValidationException;
 import com.google.gerrit.server.git.validators.RefOperationValidators;
 import com.google.gerrit.server.git.validators.ValidationMessage;
@@ -145,7 +140,6 @@
 import com.google.gerrit.server.project.ProjectState;
 import com.google.gerrit.server.query.change.ChangeData;
 import com.google.gerrit.server.query.change.InternalChangeQuery;
-import com.google.gerrit.server.ssh.SshInfo;
 import com.google.gerrit.server.submit.MergeOp;
 import com.google.gerrit.server.submit.MergeOpRepoManager;
 import com.google.gerrit.server.submit.SubmoduleException;
@@ -218,38 +212,16 @@
  *
  * <p>Conceptually, most use of Gerrit is a push of some commits to refs/for/BRANCH. However, the
  * receive-pack protocol that this is based on allows multiple ref updates to be processed at once.
+ * So we have to be prepared to also handle normal pushes (refs/heads/BRANCH), and legacy pushes
+ * (refs/changes/CHANGE). It is hard to split this class up further, because normal pushes can also
+ * result in updates to reviews, through the autoclose mechanism.
  */
 class ReceiveCommits {
   private static final FluentLogger logger = FluentLogger.forEnclosingClass();
 
-  private enum ReceiveError {
-    CONFIG_UPDATE(
-        "You are not allowed to perform this operation.\n"
-            + "Configuration changes can only be pushed by project owners\n"
-            + "who also have 'Push' rights on "
-            + RefNames.REFS_CONFIG),
-    UPDATE(
-        "You are not allowed to perform this operation.\n"
-            + "To push into this reference you need 'Push' rights."),
-    DELETE(
-        "You need 'Delete Reference' rights or 'Push' rights with the \n"
-            + "'Force Push' flag set to delete references."),
-    DELETE_CHANGES("Cannot delete from '" + REFS_CHANGES + "'"),
-    CODE_REVIEW(
-        "You need 'Push' rights to upload code review requests.\n"
-            + "Verify that you are pushing to the right branch.");
-
-    private final String value;
-
-    ReceiveError(String value) {
-      this.value = value;
-    }
-
-    String get() {
-      return value;
-    }
-  }
-
+  private static final String CODE_REVIEW_ERROR =
+      "You need 'Push' rights to upload code review requests.\n"
+          + "Verify that you are pushing to the right branch.";
   private static final String CANNOT_DELETE_CHANGES = "Cannot delete from '" + REFS_CHANGES + "'";
   private static final String CANNOT_DELETE_CONFIG =
       "Cannot delete project configuration from '" + RefNames.REFS_CONFIG + "'";
@@ -319,7 +291,6 @@
 
   // Injected fields.
   private final AccountResolver accountResolver;
-  private final Provider<AccountsUpdate> accountsUpdateProvider;
   private final AllProjectsName allProjectsName;
   private final BatchUpdate.Factory batchUpdateFactory;
   private final ChangeEditUtil editUtil;
@@ -328,7 +299,7 @@
   private final ChangeNotes.Factory notesFactory;
   private final ChangeReportFormatter changeFormatter;
   private final CmdLineParser.Factory optionParserFactory;
-  private final CommitValidators.Factory commitValidatorsFactory;
+  private final BranchCommitValidator.Factory commitValidatorFactory;
   private final CreateGroupPermissionSyncer createGroupPermissionSyncer;
   private final CreateRefControl createRefControl;
   private final DynamicMap<ProjectConfigEntry> pluginConfigEntries;
@@ -350,7 +321,6 @@
   private final ReviewDb db;
   private final Sequences seq;
   private final SetHashtagsOp.Factory hashtagsFactory;
-  private final SshInfo sshInfo;
   private final SubmoduleOp.Factory subOpFactory;
   private final TagCache tagCache;
 
@@ -379,15 +349,6 @@
   private final ListMultimap<String, String> pushOptions;
   private final Map<Change.Id, ReplaceRequest> replaceByChange;
 
-  @AutoValue
-  protected abstract static class ValidCommitKey {
-    abstract ObjectId getObjectId();
-
-    abstract Branch.NameKey getBranch();
-  }
-
-  private final Set<ValidCommitKey> validCommits;
-
   // Collections lazily populated during processing.
   private ListMultimap<Change.Id, Ref> refsByChange;
   private ListMultimap<ObjectId, Ref> refsById;
@@ -395,7 +356,6 @@
   // Other settings populated during processing.
   private MagicBranchInput magicBranch;
   private boolean newChangeForAllNotInTarget;
-  private String setFullNameTo;
   private boolean setChangeAsPrivate;
   private Optional<NoteDbPushOption> noteDbPushOption;
   private Optional<String> tracePushOption;
@@ -415,7 +375,7 @@
       ChangeNotes.Factory notesFactory,
       DynamicItem<ChangeReportFormatter> changeFormatterProvider,
       CmdLineParser.Factory optionParserFactory,
-      CommitValidators.Factory commitValidatorsFactory,
+      BranchCommitValidator.Factory commitValidatorFactory,
       CreateGroupPermissionSyncer createGroupPermissionSyncer,
       CreateRefControl createRefControl,
       DynamicMap<ProjectConfigEntry> pluginConfigEntries,
@@ -437,7 +397,6 @@
       ReviewDb db,
       Sequences seq,
       SetHashtagsOp.Factory hashtagsFactory,
-      SshInfo sshInfo,
       SubmoduleOp.Factory subOpFactory,
       TagCache tagCache,
       @Assisted ProjectState projectState,
@@ -449,12 +408,11 @@
       throws IOException {
     // Injected fields.
     this.accountResolver = accountResolver;
-    this.accountsUpdateProvider = accountsUpdateProvider;
     this.allProjectsName = allProjectsName;
     this.batchUpdateFactory = batchUpdateFactory;
     this.changeFormatter = changeFormatterProvider.get();
     this.changeInserterFactory = changeInserterFactory;
-    this.commitValidatorsFactory = commitValidatorsFactory;
+    this.commitValidatorFactory = commitValidatorFactory;
     this.createRefControl = createRefControl;
     this.createGroupPermissionSyncer = createGroupPermissionSyncer;
     this.db = db;
@@ -480,7 +438,6 @@
     this.retryHelper = retryHelper;
     this.requestScopePropagator = requestScopePropagator;
     this.seq = seq;
-    this.sshInfo = sshInfo;
     this.subOpFactory = subOpFactory;
     this.tagCache = tagCache;
 
@@ -505,7 +462,6 @@
     pushOptions = LinkedListMultimap.create();
     replaceByChange = new LinkedHashMap<>();
     updateGroups = new ArrayList<>();
-    validCommits = new HashSet<>();
 
     this.allowProjectOwnersToChangeParent =
         cfg.getBoolean("receive", "allowProjectOwnersToChangeParent", false);
@@ -567,10 +523,6 @@
 
     commandProgress.end();
     progress.end();
-
-    // Update account info with details discovered during commit walking. The account update happens
-    // in a separate batch update, and failure doesn't cause the push itself to fail.
-    updateAccountInfo();
   }
 
   // Process as many commands as possible, but may leave some commands in state NOT_ATTEMPTED.
@@ -588,44 +540,44 @@
       // into the trace if tracing is enabled.
       logger.atFine().log("push options: %s", receivePack.getPushOptions());
 
-      try {
-        if (!projectState.getProject().getState().permitsWrite()) {
-          for (ReceiveCommand cmd : commands) {
-            reject(cmd, "prohibited by Gerrit: project state does not permit write");
-          }
-          return;
-        }
-
-        logger.atFine().log("Parsing %d commands", commands.size());
-
-        List<ReceiveCommand> magicCommands = new ArrayList<>();
-        List<ReceiveCommand> directPatchSetPushCommands = new ArrayList<>();
-        List<ReceiveCommand> regularCommands = new ArrayList<>();
-
+      if (!projectState.getProject().getState().permitsWrite()) {
         for (ReceiveCommand cmd : commands) {
-          if (MagicBranch.isMagicBranch(cmd.getRefName())) {
-            magicCommands.add(cmd);
-          } else if (isDirectChangesPush(cmd.getRefName())) {
-            directPatchSetPushCommands.add(cmd);
-          } else {
-            regularCommands.add(cmd);
+          reject(cmd, "prohibited by Gerrit: project state does not permit write");
+        }
+        return;
+      }
+
+      logger.atFine().log("Parsing %d commands", commands.size());
+
+      List<ReceiveCommand> magicCommands = new ArrayList<>();
+      List<ReceiveCommand> directPatchSetPushCommands = new ArrayList<>();
+      List<ReceiveCommand> regularCommands = new ArrayList<>();
+
+      for (ReceiveCommand cmd : commands) {
+        if (MagicBranch.isMagicBranch(cmd.getRefName())) {
+          magicCommands.add(cmd);
+        } else if (isDirectChangesPush(cmd.getRefName())) {
+          directPatchSetPushCommands.add(cmd);
+        } else {
+          regularCommands.add(cmd);
+        }
+      }
+
+      int commandTypes =
+          (magicCommands.isEmpty() ? 0 : 1)
+              + (directPatchSetPushCommands.isEmpty() ? 0 : 1)
+              + (regularCommands.isEmpty() ? 0 : 1);
+
+      if (commandTypes > 1) {
+        for (ReceiveCommand cmd : commands) {
+          if (cmd.getResult() == NOT_ATTEMPTED) {
+            cmd.setResult(REJECTED_OTHER_REASON, "cannot combine normal pushes and magic pushes");
           }
         }
+        return;
+      }
 
-        int commandTypes =
-            (magicCommands.isEmpty() ? 0 : 1)
-                + (directPatchSetPushCommands.isEmpty() ? 0 : 1)
-                + (regularCommands.isEmpty() ? 0 : 1);
-
-        if (commandTypes > 1) {
-          for (ReceiveCommand cmd : commands) {
-            if (cmd.getResult() == NOT_ATTEMPTED) {
-              cmd.setResult(REJECTED_OTHER_REASON, "cannot combine normal pushes and magic pushes");
-            }
-          }
-          return;
-        }
-
+      try {
         if (!regularCommands.isEmpty()) {
           handleRegularCommands(regularCommands, progress);
           return;
@@ -1085,131 +1037,139 @@
     }
 
     if (isConfig(cmd)) {
-      logger.atFine().log("Processing %s command", cmd.getRefName());
-      try {
-        permissions.check(ProjectPermission.WRITE_CONFIG);
-      } catch (AuthException e) {
-        reject(
-            cmd,
-            String.format(
-                "must be either project owner or have %s permission",
-                ProjectPermission.WRITE_CONFIG.describeForException()));
-        return;
-      }
+      validateConfigPush(cmd);
+    }
+  }
 
-      switch (cmd.getType()) {
-        case CREATE:
-        case UPDATE:
-        case UPDATE_NONFASTFORWARD:
-          try {
-            ProjectConfig cfg = new ProjectConfig(project.getNameKey());
-            cfg.load(project.getNameKey(), receivePack.getRevWalk(), cmd.getNewId());
-            if (!cfg.getValidationErrors().isEmpty()) {
-              addError("Invalid project configuration:");
-              for (ValidationError err : cfg.getValidationErrors()) {
-                addError("  " + err.getMessage());
-              }
-              reject(cmd, "invalid project configuration");
-              logger.atSevere().log(
-                  "User %s tried to push invalid project configuration %s for %s",
-                  user.getLoggableName(), cmd.getNewId().name(), project.getName());
-              return;
+  /** Validates a push to refs/meta/config, and reject the command if it fails. */
+  private void validateConfigPush(ReceiveCommand cmd) throws PermissionBackendException {
+    logger.atFine().log("Processing %s command", cmd.getRefName());
+    try {
+      permissions.check(ProjectPermission.WRITE_CONFIG);
+    } catch (AuthException e) {
+      reject(
+          cmd,
+          String.format(
+              "must be either project owner or have %s permission",
+              ProjectPermission.WRITE_CONFIG.describeForException()));
+      return;
+    }
+
+    switch (cmd.getType()) {
+      case CREATE:
+      case UPDATE:
+      case UPDATE_NONFASTFORWARD:
+        try {
+          ProjectConfig cfg = new ProjectConfig(project.getNameKey());
+          cfg.load(project.getNameKey(), receivePack.getRevWalk(), cmd.getNewId());
+          if (!cfg.getValidationErrors().isEmpty()) {
+            addError("Invalid project configuration:");
+            for (ValidationError err : cfg.getValidationErrors()) {
+              addError("  " + err.getMessage());
             }
-            Project.NameKey newParent = cfg.getProject().getParent(allProjectsName);
-            Project.NameKey oldParent = project.getParent(allProjectsName);
-            if (oldParent == null) {
-              // update of the 'All-Projects' project
-              if (newParent != null) {
-                reject(cmd, "invalid project configuration: root project cannot have parent");
-                return;
-              }
-            } else {
-              if (!oldParent.equals(newParent)) {
-                if (allowProjectOwnersToChangeParent) {
-                  try {
-                    permissionBackend
-                        .user(user)
-                        .project(project.getNameKey())
-                        .check(ProjectPermission.WRITE_CONFIG);
-                  } catch (AuthException e) {
-                    reject(
-                        cmd, "invalid project configuration: only project owners can set parent");
-                    return;
-                  }
-                } else {
-                  try {
-                    permissionBackend.user(user).check(GlobalPermission.ADMINISTRATE_SERVER);
-                  } catch (AuthException e) {
-                    reject(cmd, "invalid project configuration: only Gerrit admin can set parent");
-                    return;
-                  }
-                }
-              }
-
-              if (projectCache.get(newParent) == null) {
-                reject(cmd, "invalid project configuration: parent does not exist");
-                return;
-              }
-            }
-
-            for (Entry<ProjectConfigEntry> e : pluginConfigEntries) {
-              PluginConfig pluginCfg = cfg.getPluginConfig(e.getPluginName());
-              ProjectConfigEntry configEntry = e.getProvider().get();
-              String value = pluginCfg.getString(e.getExportName());
-              String oldValue =
-                  projectState
-                      .getConfig()
-                      .getPluginConfig(e.getPluginName())
-                      .getString(e.getExportName());
-              if (configEntry.getType() == ProjectConfigEntryType.ARRAY) {
-                oldValue =
-                    Arrays.stream(
-                            projectState
-                                .getConfig()
-                                .getPluginConfig(e.getPluginName())
-                                .getStringList(e.getExportName()))
-                        .collect(joining("\n"));
-              }
-
-              if ((value == null ? oldValue != null : !value.equals(oldValue))
-                  && !configEntry.isEditable(projectState)) {
-                reject(
-                    cmd,
-                    String.format(
-                        "invalid project configuration: Not allowed to set parameter"
-                            + " '%s' of plugin '%s' on project '%s'.",
-                        e.getExportName(), e.getPluginName(), project.getName()));
-                continue;
-              }
-
-              if (ProjectConfigEntryType.LIST.equals(configEntry.getType())
-                  && value != null
-                  && !configEntry.getPermittedValues().contains(value)) {
-                reject(
-                    cmd,
-                    String.format(
-                        "invalid project configuration: The value '%s' is "
-                            + "not permitted for parameter '%s' of plugin '%s'.",
-                        value, e.getExportName(), e.getPluginName()));
-              }
-            }
-          } catch (Exception e) {
             reject(cmd, "invalid project configuration");
-            logger.atSevere().withCause(e).log(
+            logger.atSevere().log(
                 "User %s tried to push invalid project configuration %s for %s",
                 user.getLoggableName(), cmd.getNewId().name(), project.getName());
             return;
           }
-          break;
+          Project.NameKey newParent = cfg.getProject().getParent(allProjectsName);
+          Project.NameKey oldParent = project.getParent(allProjectsName);
+          if (oldParent == null) {
+            // update of the 'All-Projects' project
+            if (newParent != null) {
+              reject(cmd, "invalid project configuration: root project cannot have parent");
+              return;
+            }
+          } else {
+            if (!oldParent.equals(newParent)) {
+              if (allowProjectOwnersToChangeParent) {
+                try {
+                  permissionBackend
+                      .user(user)
+                      .project(project.getNameKey())
+                      .check(ProjectPermission.WRITE_CONFIG);
+                } catch (AuthException e) {
+                  reject(cmd, "invalid project configuration: only project owners can set parent");
+                  return;
+                }
+              } else {
+                try {
+                  permissionBackend.user(user).check(GlobalPermission.ADMINISTRATE_SERVER);
+                } catch (AuthException e) {
+                  reject(cmd, "invalid project configuration: only Gerrit admin can set parent");
+                  return;
+                }
+              }
+            }
 
-        case DELETE:
-          break;
+            if (projectCache.get(newParent) == null) {
+              reject(cmd, "invalid project configuration: parent does not exist");
+              return;
+            }
+          }
+          validatePluginConfig(cmd, cfg);
+        } catch (Exception e) {
+          reject(cmd, "invalid project configuration");
+          logger.atSevere().withCause(e).log(
+              "User %s tried to push invalid project configuration %s for %s",
+              user.getLoggableName(), cmd.getNewId().name(), project.getName());
+          return;
+        }
+        break;
 
-        default:
-          reject(
-              cmd,
-              "prohibited by Gerrit: don't know how to handle config update of type "
-                  + cmd.getType());
+      case DELETE:
+        break;
+
+      default:
+        reject(
+            cmd,
+            "prohibited by Gerrit: don't know how to handle config update of type "
+                + cmd.getType());
+    }
+  }
+
+  /**
+   * validates a push to refs/meta/config for plugin configuration, and rejects the push if it
+   * fails.
+   */
+  private void validatePluginConfig(ReceiveCommand cmd, ProjectConfig cfg) {
+    for (Entry<ProjectConfigEntry> e : pluginConfigEntries) {
+      PluginConfig pluginCfg = cfg.getPluginConfig(e.getPluginName());
+      ProjectConfigEntry configEntry = e.getProvider().get();
+      String value = pluginCfg.getString(e.getExportName());
+      String oldValue =
+          projectState.getConfig().getPluginConfig(e.getPluginName()).getString(e.getExportName());
+      if (configEntry.getType() == ProjectConfigEntryType.ARRAY) {
+        oldValue =
+            Arrays.stream(
+                    projectState
+                        .getConfig()
+                        .getPluginConfig(e.getPluginName())
+                        .getStringList(e.getExportName()))
+                .collect(joining("\n"));
+      }
+
+      if ((value == null ? oldValue != null : !value.equals(oldValue))
+          && !configEntry.isEditable(projectState)) {
+        reject(
+            cmd,
+            String.format(
+                "invalid project configuration: Not allowed to set parameter"
+                    + " '%s' of plugin '%s' on project '%s'.",
+                e.getExportName(), e.getPluginName(), project.getName()));
+        continue;
+      }
+
+      if (ProjectConfigEntryType.LIST.equals(configEntry.getType())
+          && value != null
+          && !configEntry.getPermittedValues().contains(value)) {
+        reject(
+            cmd,
+            String.format(
+                "invalid project configuration: The value '%s' is "
+                    + "not permitted for parameter '%s' of plugin '%s'.",
+                value, e.getExportName(), e.getPluginName()));
       }
     }
   }
@@ -1698,7 +1658,7 @@
     // TODO(davido): Remove legacy support for drafts magic branch option
     // after repo-tool supports private and work-in-progress changes.
     if (magicBranch.draft && !receiveConfig.allowDrafts) {
-      errors.put(ReceiveError.CODE_REVIEW.get(), ref);
+      errors.put(CODE_REVIEW_ERROR, ref);
       reject(cmd, "draft workflow is disabled");
       return;
     }
@@ -1910,16 +1870,17 @@
       return;
     }
 
+    BranchCommitValidator validator =
+        commitValidatorFactory.create(projectState, changeEnt.getDest(), user);
     try {
-      NoteMap rejectCommits = BanCommit.loadRejectCommitsMap(repo, receivePack.getRevWalk());
-      if (validCommit(
+      if (validator.validCommit(
           receivePack.getRevWalk().getObjectReader(),
-          changeEnt.getDest(),
           cmd,
           newCommit,
           false,
-          changeEnt,
-          rejectCommits)) {
+          messages,
+          rejectCommits,
+          changeEnt)) {
         logger.atFine().log("Replacing change %s", changeEnt.getId());
         requestReplace(cmd, true, changeEnt, newCommit);
       }
@@ -1968,10 +1929,6 @@
     }
   }
 
-  private NoteMap loadRejectCommits() throws IOException {
-    return BanCommit.loadRejectCommitsMap(repo, receivePack.getRevWalk());
-  }
-
   private List<CreateRequest> selectNewAndReplacedChangesFromMagicBranch(Task newProgress) {
     logger.atFine().log("Finding new and replaced changes");
     List<CreateRequest> newChanges = new ArrayList<>();
@@ -1980,6 +1937,9 @@
     GroupCollector groupCollector =
         GroupCollector.create(changeRefsById(), db, psUtil, notesFactory, project.getNameKey());
 
+    BranchCommitValidator validator =
+        commitValidatorFactory.create(projectState, magicBranch.dest, user);
+
     try {
       RevCommit start = setUpWalkForSelectingChanges();
       if (start == null) {
@@ -2079,14 +2039,14 @@
           logger.atFine().log("Creating new change for %s even though it is already tracked", name);
         }
 
-        if (!validCommit(
+        if (!validator.validCommit(
             receivePack.getRevWalk().getObjectReader(),
-            magicBranch.dest,
             magicBranch.cmd,
             c,
             magicBranch.merged,
-            null,
-            loadRejectCommits())) {
+            messages,
+            rejectCommits,
+            null)) {
           // Not a change the user can propose? Abort as early as possible.
           logger.atFine().log("Aborting early due to invalid commit");
           return Collections.emptyList();
@@ -3045,13 +3005,11 @@
       return;
     }
 
-    boolean missingFullName = Strings.isNullOrEmpty(user.getAccount().getFullName());
+    BranchCommitValidator validator = commitValidatorFactory.create(projectState, branch, user);
     RevWalk walk = receivePack.getRevWalk();
     walk.reset();
     walk.sort(RevSort.NONE);
     try {
-      NoteMap rejectCommits = loadRejectCommits();
-
       RevObject parsedObject = walk.parseAny(cmd.getNewId());
       if (!(parsedObject instanceof RevCommit)) {
         return;
@@ -3074,15 +3032,10 @@
           continue;
         }
 
-        if (!validCommit(walk.getObjectReader(), branch, cmd, c, false, null, rejectCommits)) {
+        if (!validator.validCommit(
+            walk.getObjectReader(), cmd, c, false, messages, rejectCommits, null)) {
           break;
         }
-
-        if (missingFullName && user.hasEmailAddress(c.getCommitterIdent().getEmailAddress())) {
-          logger.atFine().log("Will update full name of caller");
-          setFullNameTo = c.getCommitterIdent().getName();
-          missingFullName = false;
-        }
       }
       logger.atFine().log("Validated %d new commits", n);
     } catch (IOException err) {
@@ -3091,78 +3044,9 @@
     }
   }
 
-  private String messageForCommit(RevCommit c, String msg) {
-    return String.format("commit %s: %s", c.abbreviate(RevId.ABBREV_LEN).name(), msg);
-  }
-
-  /**
-   * Validates a single commit. If the commit does not validate, the command is rejected.
-   *
-   * @param objectReader the object reader to use.
-   * @param branch the branch to which this commit is pushed
-   * @param cmd the ReceiveCommand executing the push.
-   * @param commit the commit being validated.
-   * @param isMerged whether this is a merge commit created by magicBranch --merge option
-   * @param change the change for which this is a new patchset.
-   */
-  private boolean validCommit(
-      ObjectReader objectReader,
-      Branch.NameKey branch,
-      ReceiveCommand cmd,
-      RevCommit commit,
-      boolean isMerged,
-      @Nullable Change change,
-      NoteMap rejectCommits)
-      throws IOException {
-
-    ValidCommitKey key = new AutoValue_ReceiveCommits_ValidCommitKey(commit.copy(), branch);
-    if (validCommits.contains(key)) {
-      return true;
-    }
-
-    try (CommitReceivedEvent receiveEvent =
-        new CommitReceivedEvent(cmd, project, branch.get(), objectReader, commit, user)) {
-      CommitValidators validators;
-      if (isMerged) {
-        validators =
-            commitValidatorsFactory.forMergedCommits(permissions, branch, user.asIdentifiedUser());
-      } else {
-        validators =
-            commitValidatorsFactory.forReceiveCommits(
-                permissions,
-                branch,
-                user.asIdentifiedUser(),
-                sshInfo,
-                rejectCommits,
-                receiveEvent.revWalk,
-                change);
-      }
-
-      for (CommitValidationMessage m : validators.validate(receiveEvent)) {
-        messages.add(
-            new CommitValidationMessage(messageForCommit(commit, m.getMessage()), m.isError()));
-      }
-    } catch (CommitValidationException e) {
-      logger.atFine().log("Commit validation failed on %s", commit.name());
-      for (CommitValidationMessage m : e.getMessages()) {
-        // TODO(hanwen): drop the non-error messages?
-        messages.add(
-            new CommitValidationMessage(messageForCommit(commit, m.getMessage()), m.isError()));
-      }
-      reject(cmd, messageForCommit(commit, e.getMessage()));
-      return false;
-    }
-    validCommits.add(key);
-    return true;
-  }
-
   private void autoCloseChanges(ReceiveCommand cmd, Task progress) {
     logger.atFine().log("Starting auto-closing of changes");
     String refName = cmd.getRefName();
-    checkState(
-        !MagicBranch.isMagicBranch(refName),
-        "shouldn't be auto-closing changes on magic branch %s",
-        refName);
 
     // TODO(dborowitz): Combine this BatchUpdate with the main one in
     // handleRegularCommands
@@ -3280,31 +3164,6 @@
     }
   }
 
-  private void updateAccountInfo() {
-    if (setFullNameTo == null) {
-      return;
-    }
-    logger.atFine().log("Updating full name of caller");
-    try {
-      Optional<AccountState> accountState =
-          accountsUpdateProvider
-              .get()
-              .update(
-                  "Set Full Name on Receive Commits",
-                  user.getAccountId(),
-                  (a, u) -> {
-                    if (Strings.isNullOrEmpty(a.getAccount().getFullName())) {
-                      u.setFullName(setFullNameTo);
-                    }
-                  });
-      accountState
-          .map(AccountState::getAccount)
-          .ifPresent(a -> user.getAccount().setFullName(a.getFullName()));
-    } catch (OrmException | IOException | ConfigInvalidException e) {
-      logger.atWarning().withCause(e).log("Failed to update full name of caller");
-    }
-  }
-
   private Map<Change.Key, ChangeNotes> openChangesByKeyByBranch(Branch.NameKey branch)
       throws OrmException {
     Map<Change.Key, ChangeNotes> r = new HashMap<>();
diff --git a/java/com/google/gerrit/server/git/validators/CommitValidators.java b/java/com/google/gerrit/server/git/validators/CommitValidators.java
index bcb910a..7930fe8 100644
--- a/java/com/google/gerrit/server/git/validators/CommitValidators.java
+++ b/java/com/google/gerrit/server/git/validators/CommitValidators.java
@@ -75,6 +75,9 @@
 import org.eclipse.jgit.revwalk.RevWalk;
 import org.eclipse.jgit.util.SystemReader;
 
+/**
+ * Represents a list of CommitValidationListeners to run for a push to one branch of one project.
+ */
 public class CommitValidators {
   private static final FluentLogger logger = FluentLogger.forEnclosingClass();
 
diff --git a/lib/jgit/jgit.bzl b/lib/jgit/jgit.bzl
index 6ada5bd..7191901 100644
--- a/lib/jgit/jgit.bzl
+++ b/lib/jgit/jgit.bzl
@@ -1,6 +1,6 @@
 load("//tools/bzl:maven_jar.bzl", "GERRIT", "MAVEN_CENTRAL", "MAVEN_LOCAL", "maven_jar")
 
-_JGIT_VERS = "5.0.2.201807311906-r"
+_JGIT_VERS = "5.0.3.201809091024-r"
 
 _DOC_VERS = _JGIT_VERS  # Set to _JGIT_VERS unless using a snapshot
 
@@ -40,28 +40,28 @@
         name = "jgit-lib",
         artifact = "org.eclipse.jgit:org.eclipse.jgit:" + _JGIT_VERS,
         repository = _JGIT_REPO,
-        sha1 = "a81d7c8d153a8a744b6be1d9c6d698270beec1c0",
-        src_sha1 = "c89f8f38cebaf75d13f9b2f7a1da71206d8c38f7",
+        sha1 = "0afec2df3ff8835bc4d5c279d14fad0daae6dd93",
+        src_sha1 = "e2c978064e2a46b260bbda0d8c393ed741046420",
         unsign = True,
     )
     maven_jar(
         name = "jgit-servlet",
         artifact = "org.eclipse.jgit:org.eclipse.jgit.http.server:" + _JGIT_VERS,
         repository = _JGIT_REPO,
-        sha1 = "ab3d0c85bc2008da513c1127ab4acf3df8ef414e",
+        sha1 = "8fb0f9b6c38ac6fce60f2ead740e03dd79c3c288",
         unsign = True,
     )
     maven_jar(
         name = "jgit-archive",
         artifact = "org.eclipse.jgit:org.eclipse.jgit.archive:" + _JGIT_VERS,
         repository = _JGIT_REPO,
-        sha1 = "ba6e0aaf3f733f2f460e227145526e1737ca160f",
+        sha1 = "72a157ce261f3eb938d9e0ee83d7c9700aa7d736",
     )
     maven_jar(
         name = "jgit-junit",
         artifact = "org.eclipse.jgit:org.eclipse.jgit.junit:" + _JGIT_VERS,
         repository = _JGIT_REPO,
-        sha1 = "fe28963520e19c918eb26747e678ec9772ba800f",
+        sha1 = "eb430358d96dedd923e4075cd54a7db4cab51ca2",
         unsign = True,
     )