summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJohan Björk <jbjoerk@gmail.com>2012-08-02 15:57:13 +0200
committerEdwin Kempin <edwin.kempin@sap.com>2012-10-10 15:34:34 +0200
commit6fb89cfea4e092f01a27a6f9e39adedcb82f25ca (patch)
tree28469552af8690dd719e88e5c9ecad26917c8244
parentcd7b1e4814d262cd4690013fb9c2b8bd24cd118c (diff)
Add ssh command "test-submit-rule"
The command creates a fresh prolog environment and loads a prolog script either from stdin or rules.pl in the project refs/meta/config branch depending on the options used. can_submit/1 is then queried and the results are returned in human readable text or json. Change-Id: I246a89f9f35718320b69ec43ddf11fbe002ed566
-rw-r--r--Documentation/cmd-index.txt3
-rw-r--r--Documentation/cmd-test-submit-rule.txt93
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/project/ChangeControl.java2
-rw-r--r--gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/MasterCommandModule.java1
-rw-r--r--gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/TestSubmitRule.java240
5 files changed, 338 insertions, 1 deletions
diff --git a/Documentation/cmd-index.txt b/Documentation/cmd-index.txt
index 3895b905c0..ccd9ffc191 100644
--- a/Documentation/cmd-index.txt
+++ b/Documentation/cmd-index.txt
@@ -147,6 +147,9 @@ link:cmd-plugin-remove.html[gerrit plugin remove]::
link:cmd-plugin-remove.html[gerrit plugin rm]::
Alias for 'gerrit plugin remove'.
+link:cmd-test-submit-rule.html[gerrit test-submit-rule]::
+ Test prolog submit rules.
+
link:cmd-kill.html[kill]::
Kills a scheduled or running task.
diff --git a/Documentation/cmd-test-submit-rule.txt b/Documentation/cmd-test-submit-rule.txt
new file mode 100644
index 0000000000..5b70bd1e51
--- /dev/null
+++ b/Documentation/cmd-test-submit-rule.txt
@@ -0,0 +1,93 @@
+gerrit test-submit-rule
+=======================
+
+NAME
+----
+gerrit test-submit-rule - Test prolog submit rules with a chosen changeset.
+
+SYNOPSIS
+--------
+[verse]
+'ssh' -p <port> <host> 'gerrit test-submit-rule'
+ [-s]
+ [--no-filters]
+ [--format {TEXT | JSON}]
+ CHANGE
+
+DESCRIPTION
+-----------
+Provides a way to test prolog link:prolog-cookbook.html[submit rules].
+
+OPTIONS
+-------
+-s::
+ Reads a rules.pl file from stdin instead of rules.pl in refs/meta/config.
+
+--no-filters::
+ Don't run the submit_filter/2 from the parent projects of the specified change.
+
+--format::
+ What output format to display the results in.
++
+--
+`text`:: Simple text based format.
+`json`:: A JSON object described in link:json.html#submitRecord[submit record].
+`json_compact`:: Minimized JSON output.
+--
+
+ACCESS
+------
+Can be used by anyone that has permission to read the specified changeset.
+
+EXAMPLES
+--------
+
+
+Test submit_rule from stdin.
+====
+ $ cat non-author-codereview.pl | ssh -p 29418 review.example.com gerrit test-submit-rule -s I78f2c6673db24e4e92ed32f604c960dc952437d9
+ Non-Author-Code-Review: NOT_READY
+ Verified: NOT_READY
+ Code-Review: NOT_READY by Anonymous Coward <test@email.com>
+
+ NOT_READY
+====
+
+Test submit_rule from stdin and return the results as JSON.
+====
+ cat non-author-codereview.pl | ssh -p 29418 review.example.com gerrit test-submit-rule --format=JSON -s I78f2c6673db24e4e92ed32f604c960dc952437d9
+ {
+ "approvals": [
+ {
+ "type": "Verified",
+ "value": "NEED"
+ },
+ {
+ "type": "Code-Review",
+ "value": "OK",
+ "by": {
+ "email": "test@email.com",
+ "username": "test"
+ }
+ }
+ ],
+ "value": "NOT_READY"
+ }
+====
+
+Test the active submit_rule from the refs/meta/config branch, ignoring filters in the project parents.
+====
+ $ ssh -p 29418 review.example.com gerrit test-submit-rule I78f2c6673db24e4e92ed32f604c960dc952437d9 --no-filters
+ Verified: NOT_READY
+ Code-Review: NOT_READY by Anonymous Coward <test@email.com>
+
+ NOT_READY
+====
+
+SCRIPTING
+---------
+Can be used either interactively for testing new prolog submit rules, or from a script to check the submit status of a change.
+
+GERRIT
+------
+Part of link:index.html[Gerrit Code Review]
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ChangeControl.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ChangeControl.java
index 0863623d5d..db4b0217b3 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ChangeControl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ChangeControl.java
@@ -570,7 +570,7 @@ public class ChangeControl {
&& who.arg(0).isInteger();
}
- private static Term toListTerm(List<Term> terms) {
+ public static Term toListTerm(List<Term> terms) {
Term list = Prolog.Nil;
for (int i = terms.size() - 1; i >= 0; i--) {
list = new ListTerm(terms.get(i), list);
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/MasterCommandModule.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/MasterCommandModule.java
index db4e985d01..90bc07e831 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/MasterCommandModule.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/MasterCommandModule.java
@@ -31,6 +31,7 @@ public class MasterCommandModule extends CommandModule {
command(gerrit, "rename-group").to(RenameGroupCommand.class);
command(gerrit, "create-project").to(CreateProjectCommand.class);
command(gerrit, "gsql").to(AdminQueryShell.class);
+ command(gerrit, "test-submit-rule").to(TestSubmitRule.class);
command(gerrit, "set-reviewers").to(SetReviewersCommand.class);
command(gerrit, "receive-pack").to(Receive.class);
command(gerrit, "set-project-parent").to(AdminSetParent.class);
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/TestSubmitRule.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/TestSubmitRule.java
new file mode 100644
index 0000000000..9a81140360
--- /dev/null
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/TestSubmitRule.java
@@ -0,0 +1,240 @@
+// Copyright (C) 2012 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.sshd.commands;
+
+import com.google.gerrit.common.data.AccountInfo;
+import com.google.gerrit.common.data.SubmitRecord;
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.rules.PrologEnvironment;
+import com.google.gerrit.rules.StoredValues;
+import com.google.gerrit.server.OutputFormat;
+import com.google.gerrit.server.account.AccountCache;
+import com.google.gerrit.server.config.AnonymousCowardName;
+import com.google.gerrit.server.events.AccountAttribute;
+import com.google.gerrit.server.events.SubmitLabelAttribute;
+import com.google.gerrit.server.events.SubmitRecordAttribute;
+import com.google.gerrit.server.project.ChangeControl;
+import com.google.gerrit.server.project.ProjectState;
+import com.google.gerrit.sshd.SshCommand;
+import com.google.gson.reflect.TypeToken;
+import com.google.inject.Inject;
+
+import com.googlecode.prolog_cafe.compiler.CompileException;
+import com.googlecode.prolog_cafe.lang.BufferingPrologControl;
+import com.googlecode.prolog_cafe.lang.JavaObjectTerm;
+import com.googlecode.prolog_cafe.lang.ListTerm;
+import com.googlecode.prolog_cafe.lang.Prolog;
+import com.googlecode.prolog_cafe.lang.PrologClassLoader;
+import com.googlecode.prolog_cafe.lang.PrologException;
+import com.googlecode.prolog_cafe.lang.PrologMachineCopy;
+import com.googlecode.prolog_cafe.lang.SymbolTerm;
+import com.googlecode.prolog_cafe.lang.Term;
+import com.googlecode.prolog_cafe.lang.VariableTerm;
+
+import org.kohsuke.args4j.Argument;
+import org.kohsuke.args4j.Option;
+
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+
+/** Command that allows testing of prolog submit-rules in a live instance. */
+final class TestSubmitRule extends SshCommand {
+ @Inject
+ private ReviewDb db;
+
+ @Inject
+ private PrologEnvironment.Factory envFactory;
+
+ @Inject
+ private ChangeControl.Factory ccFactory;
+
+ @Inject
+ private AccountCache accountCache;
+
+ final @AnonymousCowardName String anonymousCowardName;
+
+ @Argument(index = 0, required = true, usage = "ChangeId to load in prolog environment")
+ private String changeId;
+
+ @Option(name = "-s",
+ usage = "Read prolog script from stdin instead of reading rules.pl from the refs/meta/config branch")
+ private boolean useStdin;
+
+ @Option(name = "--format", metaVar = "FMT", usage = "Output display format")
+ private OutputFormat format = OutputFormat.TEXT;
+
+ @Option(name = "--no-filters", aliases = {"-n"},
+ usage = "Don't run the submit_filter/2 from the parent projects")
+ private boolean skipSubmitFilters;
+
+ private static final String[] PACKAGE_LIST = {Prolog.BUILTIN, "gerrit"};
+
+ @Inject
+ public TestSubmitRule(@AnonymousCowardName String anonymous) {
+ anonymousCowardName = anonymous;
+ }
+ private PrologMachineCopy newMachine() {
+ BufferingPrologControl ctl = new BufferingPrologControl();
+ ctl.setMaxDatabaseSize(16 * 1024);
+ ctl.setPrologClassLoader(new PrologClassLoader(getClass().getClassLoader()));
+ return PrologMachineCopy.save(ctl);
+ }
+
+ @Override
+ protected void run() throws UnloggedFailure {
+ InputStreamReader inReader = new InputStreamReader(in);
+
+ try {
+ PrologEnvironment pcl;
+
+ List<Change> changeList =
+ db.changes().byKey(new Change.Key(changeId)).toList();
+ if (changeList.size() != 1)
+ throw new UnloggedFailure(1, "Invalid ChangeId");
+
+ Change c = changeList.get(0);
+ PatchSet ps = db.patchSets().get(c.currentPatchSetId());
+ // Will throw exception if current user can not access this change, and
+ // thus will leak information that a change-id is valid even though the
+ // user are not allowed to see the change.
+ // See http://code.google.com/p/gerrit/issues/detail?id=1586
+ ChangeControl cc = ccFactory.controlFor(c);
+ ProjectState projectState = cc.getProjectControl().getProjectState();
+
+ if (useStdin) {
+ pcl = envFactory.create(newMachine());
+ } else {
+ pcl = projectState.newPrologEnvironment();
+ }
+
+ pcl.set(StoredValues.REVIEW_DB, db);
+ pcl.set(StoredValues.CHANGE, c);
+ pcl.set(StoredValues.PATCH_SET, ps);
+ pcl.set(StoredValues.CHANGE_CONTROL, cc);
+ if (useStdin) {
+ pcl.initialize(PACKAGE_LIST);
+ pcl.execute(Prolog.BUILTIN, "consult_stream",
+ SymbolTerm.intern("stdin"), new JavaObjectTerm(inReader));
+ }
+
+ List<Term> results = new ArrayList<Term>();
+ Term submitRule =
+ pcl.once("gerrit", "locate_submit_rule", new VariableTerm());
+
+ for (Term[] template : pcl.all("gerrit", "can_submit", submitRule,
+ new VariableTerm())) {
+ results.add(template[1]);
+ }
+
+ if (!skipSubmitFilters) {
+ runSubmitFilters(projectState, results, pcl);
+ }
+
+ List<SubmitRecord> res = cc.resultsToSubmitRecord(submitRule, results);
+ for (SubmitRecord r : res) {
+ if (format.isJson()) {
+ SubmitRecordAttribute submitRecord = new SubmitRecordAttribute();
+ submitRecord.status = r.status.name();
+
+ List<SubmitLabelAttribute> submitLabels = new LinkedList<SubmitLabelAttribute>();
+ for(SubmitRecord.Label l : r.labels) {
+ SubmitLabelAttribute label = new SubmitLabelAttribute();
+ label.label = l.label;
+ label.status= l.status.name();
+ if(l.appliedBy != null) {
+ Account a = accountCache.get(l.appliedBy).getAccount();
+ label.by = new AccountAttribute();
+ label.by.email = a.getPreferredEmail();
+ label.by.name = a.getFullName();
+ label.by.username = a.getUserName();
+ }
+ submitLabels.add(label);
+ }
+ submitRecord.labels = submitLabels;
+ format.newGson().toJson(submitRecord, new TypeToken<SubmitRecordAttribute>() {}.getType(), stdout);
+ stdout.print('\n');
+ } else {
+ for(SubmitRecord.Label l : r.labels) {
+ stdout.print(l.label + ": " + l.status);
+ if(l.appliedBy != null) {
+ AccountInfo a = new AccountInfo(accountCache.get(l.appliedBy).getAccount());
+ stdout.print(" by " + a.getNameEmail(anonymousCowardName));
+ }
+ stdout.print('\n');
+ }
+ stdout.print("\n" + r.status.name() + "\n");
+ }
+ }
+ } catch (Exception e) {
+ throw new UnloggedFailure("Processing of prolog script failed: " + e);
+ }
+ }
+
+ private void runSubmitFilters(ProjectState projectState, List<Term> results,
+ PrologEnvironment pcl) throws UnloggedFailure {
+ ProjectState parentState = projectState.getParentState();
+ PrologEnvironment childEnv = pcl;
+ Set<Project.NameKey> projectsSeen = new HashSet<Project.NameKey>();
+ projectsSeen.add(projectState.getProject().getNameKey());
+
+ while (parentState != null) {
+ if (!projectsSeen.add(parentState.getProject().getNameKey())) {
+ // parent has been seen before, stop walk up inheritance tree
+ break;
+ }
+ PrologEnvironment parentEnv;
+ try {
+ parentEnv = parentState.newPrologEnvironment();
+ } catch (CompileException err) {
+ throw new UnloggedFailure("Cannot consult rules.pl for "
+ + parentState.getProject().getName() + err);
+ }
+
+ parentEnv.copyStoredValues(childEnv);
+ Term filterRule =
+ parentEnv.once("gerrit", "locate_submit_filter", new VariableTerm());
+ if (filterRule != null) {
+ try {
+ Term resultsTerm = ChangeControl.toListTerm(results);
+ results.clear();
+ Term[] template =
+ parentEnv.once("gerrit", "filter_submit_results", filterRule,
+ resultsTerm, new VariableTerm());
+ @SuppressWarnings("unchecked")
+ final List<? extends Term> termList =
+ ((ListTerm) template[2]).toJava();
+ results.addAll(termList);
+ } catch (PrologException err) {
+ throw new UnloggedFailure("Exception calling " + filterRule + " of "
+ + parentState.getProject().getName() + err);
+ } catch (RuntimeException err) {
+ throw new UnloggedFailure("Exception calling " + filterRule + " of "
+ + parentState.getProject().getName() + err);
+ }
+ }
+
+ parentState = parentState.getParentState();
+ childEnv = parentEnv;
+ }
+ }
+}