Add merge validation interface
Add a new MergeValidationListener interface which can be used to
provide additional validation of commits before they are merged to
the git repository.
Add a MergeValidators class to invoke the merge validator listeners,
and call it from MergeOp before the commit is merged.
Add a listener to invoke validation provided by plugins.
Change-Id: I325d923f5cc0245b60e86a035329b640c1682d48
diff --git a/Documentation/config-validation.txt b/Documentation/config-validation.txt
index fd8c0bd..1b09d19 100644
--- a/Documentation/config-validation.txt
+++ b/Documentation/config-validation.txt
@@ -4,6 +4,11 @@
Gerrit supports link:dev-plugins.html[plugin-based] validation of
commits.
+[[new-commit-validation]]
+New commit validation
+---------------------
+
+
Plugins implementing the `CommitValidationListener` interface can
perform additional validation checks against new commits.
@@ -18,6 +23,18 @@
Out of the box, Gerrit includes a plugin that checks the length of the
subject and body lines of commit messages on uploaded commits.
+[[pre-merge-validation]]
+Pre-merge validation
+--------------------
+
+
+Plugins implementing the `MergeValidationListener` interface can
+perform additional validation checks against commits before they
+are merged to the git repository.
+
+If the commit fails the validation, the plugin can throw an exception
+which will cause the merge to fail.
+
GERRIT
------
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java
index 7ccb33f..8aeaa3d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java
@@ -80,6 +80,8 @@
import com.google.gerrit.server.git.TransferConfig;
import com.google.gerrit.server.git.validators.CommitValidationListener;
import com.google.gerrit.server.git.validators.CommitValidators;
+import com.google.gerrit.server.git.validators.MergeValidationListener;
+import com.google.gerrit.server.git.validators.MergeValidators;
import com.google.gerrit.server.mail.AddReviewerSender;
import com.google.gerrit.server.mail.CommitMessageEditedSender;
import com.google.gerrit.server.mail.CreateChangeSender;
@@ -251,11 +253,13 @@
DynamicSet.bind(binder(), GitReferenceUpdatedListener.class).to(ChangeCache.class);
DynamicSet.setOf(binder(), ChangeListener.class);
DynamicSet.setOf(binder(), CommitValidationListener.class);
+ DynamicSet.setOf(binder(), MergeValidationListener.class);
DynamicItem.itemOf(binder(), AvatarProvider.class);
bind(AnonymousUser.class);
factory(CommitValidators.Factory.class);
+ factory(MergeValidators.Factory.class);
factory(NotesBranchUtil.Factory.class);
bind(AccountManager.class);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeOp.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeOp.java
index ff3fe33..d8ae6ca 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeOp.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeOp.java
@@ -43,6 +43,8 @@
import com.google.gerrit.server.account.AccountCache;
import com.google.gerrit.server.config.AllProjectsName;
import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
+import com.google.gerrit.server.git.validators.MergeValidationException;
+import com.google.gerrit.server.git.validators.MergeValidators;
import com.google.gerrit.server.index.ChangeIndexer;
import com.google.gerrit.server.mail.MergeFailSender;
import com.google.gerrit.server.mail.MergedSender;
@@ -130,6 +132,7 @@
private final IdentifiedUser.GenericFactory identifiedUserFactory;
private final ChangeControl.GenericFactory changeControlFactory;
private final MergeQueue mergeQueue;
+ private final MergeValidators.Factory mergeValidatorsFactory;
private final Branch.NameKey destBranch;
private ProjectState destProject;
@@ -170,7 +173,8 @@
final WorkQueue workQueue,
final RequestScopePropagator requestScopePropagator,
final AllProjectsName allProjectsName,
- final ChangeIndexer indexer) {
+ final ChangeIndexer indexer,
+ final MergeValidators.Factory mergeValidatorsFactory) {
repoManager = grm;
schemaFactory = sf;
labelNormalizer = fs;
@@ -191,6 +195,7 @@
this.requestScopePropagator = requestScopePropagator;
this.allProjectsName = allProjectsName;
this.indexer = indexer;
+ this.mergeValidatorsFactory = mergeValidatorsFactory;
destBranch = branch;
toMerge = ArrayListMultimap.create();
potentiallyStillSubmittable = new ArrayList<CodeReviewCommit>();
@@ -487,6 +492,14 @@
continue;
}
+ MergeValidators mergeValidators = mergeValidatorsFactory.create();
+ try {
+ mergeValidators.validatePreMerge(repo, commit, destProject, destBranch, ps.getId());
+ } catch (MergeValidationException mve) {
+ commits.put(changeId, CodeReviewCommit.error(mve.getStatus()));
+ continue;
+ }
+
if (GitRepositoryManager.REF_CONFIG.equals(destBranch.get())) {
final Project.NameKey newParent;
try {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/validators/MergeValidationException.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/validators/MergeValidationException.java
new file mode 100644
index 0000000..78819a8
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/validators/MergeValidationException.java
@@ -0,0 +1,31 @@
+// Copyright (C) 2013 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.validators;
+
+import com.google.gerrit.server.git.CommitMergeStatus;
+
+public class MergeValidationException extends Exception {
+ private static final long serialVersionUID = 1L;
+ private final CommitMergeStatus status;
+
+ public MergeValidationException(CommitMergeStatus status) {
+ super(status.toString());
+ this.status = status;
+ }
+
+ public CommitMergeStatus getStatus() {
+ return status;
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/validators/MergeValidationListener.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/validators/MergeValidationListener.java
new file mode 100644
index 0000000..0a8d245
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/validators/MergeValidationListener.java
@@ -0,0 +1,48 @@
+// Copyright (C) 2013 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.validators;
+
+import com.google.gerrit.extensions.annotations.ExtensionPoint;
+import com.google.gerrit.reviewdb.client.Branch;
+import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gerrit.server.git.CodeReviewCommit;
+import com.google.gerrit.server.project.ProjectState;
+
+import org.eclipse.jgit.lib.Repository;
+
+/**
+ * Listener to provide validation of commits before merging.
+ *
+ * Invoked by Gerrit before a commit is merged.
+ */
+@ExtensionPoint
+public interface MergeValidationListener {
+ /**
+ * Validate a commit before it is merged.
+ *
+ * @param repo the repository
+ * @param commit commit details
+ * @param destProject the destination project
+ * @param destBranch the destination branch
+ * @param patchSetId the patch set ID
+ * @throws MergeValidationException if the commit fails to validate
+ */
+ public void onPreMerge(Repository repo,
+ CodeReviewCommit commit,
+ ProjectState destProject,
+ Branch.NameKey destBranch,
+ PatchSet.Id patchSetId)
+ throws MergeValidationException;
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/validators/MergeValidators.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/validators/MergeValidators.java
new file mode 100644
index 0000000..6fb75d8
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/validators/MergeValidators.java
@@ -0,0 +1,78 @@
+// Copyright (C) 2013 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.validators;
+
+import com.google.common.collect.Lists;
+import com.google.gerrit.extensions.registration.DynamicSet;
+import com.google.gerrit.reviewdb.client.Branch;
+import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gerrit.server.git.CodeReviewCommit;
+import com.google.gerrit.server.project.ProjectState;
+import com.google.inject.Inject;
+
+import org.eclipse.jgit.lib.Repository;
+
+import java.util.List;
+
+public class MergeValidators {
+ private final DynamicSet<MergeValidationListener> mergeValidationListeners;
+
+ public interface Factory {
+ MergeValidators create();
+ }
+
+ @Inject
+ MergeValidators(DynamicSet<MergeValidationListener> mergeValidationListeners) {
+ this.mergeValidationListeners = mergeValidationListeners;
+ }
+
+ public void validatePreMerge(Repository repo,
+ CodeReviewCommit commit,
+ ProjectState destProject,
+ Branch.NameKey destBranch,
+ PatchSet.Id patchSetId)
+ throws MergeValidationException {
+ List<MergeValidationListener> validators = Lists.newLinkedList();
+
+ validators.add(new PluginMergeValidationListener(mergeValidationListeners));
+
+ for (MergeValidationListener validator : validators) {
+ validator.onPreMerge(repo, commit, destProject, destBranch, patchSetId);
+ }
+ }
+
+ /** Execute merge validation plug-ins */
+ public static class PluginMergeValidationListener implements
+ MergeValidationListener {
+ private final DynamicSet<MergeValidationListener> mergeValidationListeners;
+
+ public PluginMergeValidationListener(
+ DynamicSet<MergeValidationListener> mergeValidationListeners) {
+ this.mergeValidationListeners = mergeValidationListeners;
+ }
+
+ @Override
+ public void onPreMerge(Repository repo,
+ CodeReviewCommit commit,
+ ProjectState destProject,
+ Branch.NameKey destBranch,
+ PatchSet.Id patchSetId)
+ throws MergeValidationException {
+ for (MergeValidationListener validator : mergeValidationListeners) {
+ validator.onPreMerge(repo, commit, destProject, destBranch, patchSetId);
+ }
+ }
+ }
+}