From b1f730b89458d9d094bc93f3d328ad744c39bbb5 Mon Sep 17 00:00:00 2001 From: Shawn Pearce Date: Mon, 4 Mar 2013 07:54:09 -0800 Subject: [PATCH] Add REST APIs to test submit_rule and submit_filter These are available over SSH, but not on HTTP. Make them available on HTTP with REST API views on the revision resource. Use the REST implementations to back the SSH commands, so the implementation is not duplicated. To match the REST API the SSH test-submit rule command no longer accepts a format parameter. Output is in pretty formatted JSON. Change-Id: I6a57b4561067eaa32d407a426c95ea61a96f1948 --- Documentation/cmd-test-submit-rule.txt | 69 ++---- Documentation/rest-api-changes.txt | 138 ++++++++++++ .../com/google/gerrit/rules/RulesCache.java | 4 + .../google/gerrit/server/change/Module.java | 3 + .../gerrit/server/change/Revisions.java | 2 +- .../gerrit/server/change/TestSubmitRule.java | 198 ++++++++++++++++++ .../gerrit/server/change/TestSubmitType.java | 116 ++++++++++ .../sshd/commands/BaseTestPrologCommand.java | 81 +++++++ .../gerrit/sshd/commands/BaseTestSubmit.java | 108 ---------- .../sshd/commands/MasterCommandModule.java | 5 +- .../gerrit/sshd/commands/TestSubmitRule.java | 95 --------- .../sshd/commands/TestSubmitRuleCommand.java | 35 ++++ .../gerrit/sshd/commands/TestSubmitType.java | 58 ----- .../sshd/commands/TestSubmitTypeCommand.java | 35 ++++ 14 files changed, 635 insertions(+), 312 deletions(-) create mode 100644 gerrit-server/src/main/java/com/google/gerrit/server/change/TestSubmitRule.java create mode 100644 gerrit-server/src/main/java/com/google/gerrit/server/change/TestSubmitType.java create mode 100644 gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/BaseTestPrologCommand.java delete mode 100644 gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/BaseTestSubmit.java delete mode 100644 gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/TestSubmitRule.java create mode 100644 gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/TestSubmitRuleCommand.java delete mode 100644 gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/TestSubmitType.java create mode 100644 gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/TestSubmitTypeCommand.java diff --git a/Documentation/cmd-test-submit-rule.txt b/Documentation/cmd-test-submit-rule.txt index 5b70bd1e51..ae68b802f8 100644 --- a/Documentation/cmd-test-submit-rule.txt +++ b/Documentation/cmd-test-submit-rule.txt @@ -1,17 +1,16 @@ -gerrit test-submit-rule +gerrit test-submit rule ======================= NAME ---- -gerrit test-submit-rule - Test prolog submit rules with a chosen changeset. +gerrit test-submit rule - Test prolog submit rules with a chosen changeset. SYNOPSIS -------- [verse] -'ssh' -p 'gerrit test-submit-rule' +'ssh' -p 'gerrit test-submit rule' [-s] [--no-filters] - [--format {TEXT | JSON}] CHANGE DESCRIPTION @@ -26,15 +25,6 @@ OPTIONS --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. @@ -42,46 +32,31 @@ 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 - - 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" - } + cat rules.pl | ssh -p 29418 review.example.com gerrit test-submit rule -s I78f2c6673db24e4e92ed32f604c960dc952437d9 + [ + { + "status": "NOT_READY", + "reject": { + "Any-Label-Name": {} + } + } + ] ==== 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 - - NOT_READY + $ ssh -p 29418 review.example.com gerrit test-submit rule I78f2c6673db24e4e92ed32f604c960dc952437d9 --no-filters + [ + { + "status": "NOT_READY", + "need": { + "Code-Review": {} + "Verified": {} + } + } + ] ==== SCRIPTING diff --git a/Documentation/rest-api-changes.txt b/Documentation/rest-api-changes.txt index 9762465647..46a51eccb0 100644 --- a/Documentation/rest-api-changes.txt +++ b/Documentation/rest-api-changes.txt @@ -1185,6 +1185,93 @@ message is contained in the response body. "revision 674ac754f91e64a0efb8087e59a176484bd534d1 is not current revision" ---- +[[submit-type]] +GET /changes/\{change-id\}/revisions/\{revision-id\}/submit_type (Get Submit Type) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Gets the method the server will use to submit (merge) the change. + +.Request +---- + GET /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/revisions/current/submit_type HTTP/1.0 +---- + +.Response +---- + HTTP/1.1 200 OK + Content-Disposition: attachment + Content-Type: application/json;charset=UTF-8 + + )]}' + "MERGE_IF_NECESSARY" +---- + +[[test-submit-type]] +POST /changes/\{change-id\}/revisions/\{revision-id\}/test.submit_type (Test Submit Type) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Tests the submit_type Prolog rule in the project, or the one given. + +Request body may be either the Prolog code as `text/plain` or a +link:#rule-input[RuleInput] object. The query parameter `filters` +may be set to `SKIP` to bypass parent project filters while testing +a project-specific rule. + +.Request +---- + POST /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/revisions/current/test.submit_type HTTP/1.0 + Content-Type: text/plain;charset-UTF-8 + + submit_type(cherry_pick). +---- + +.Response +---- + HTTP/1.1 200 OK + Content-Disposition: attachment + Content-Type: application/json;charset=UTF-8 + + )]}' + "cherry_pick" +---- + +[[test-submit-rule]] +POST /changes/\{change-id\}/revisions/\{revision-id\}/test.submit_rule (Test Submit Rule) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Tests the submit_rule Prolog rule in the project, or the one given. + +Request body may be either the Prolog code as `text/plain` or a +link:#rule-input[RuleInput] object. The query parameter `filters` +may be set to `SKIP` to bypass parent project filters while testing +a project-specific rule. + +.Request +---- + POST /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/revisions/current/test.submit_type?filters=SKIP HTTP/1.0 + Content-Type: text/plain;charset-UTF-8 + + submit_rule(submit(R)) :- + R = label('Any-Label-Name', reject(_)). +---- + +The response is a list of link:#submit-record[SubmitRecord] entries +describing the permutations that satisfy the tested submit rule. + +.Response +---- + HTTP/1.1 200 OK + Content-Disposition: attachment + Content-Type: application/json;charset=UTF-8 + + )]}' + [ + { + "status": "NOT_READY", + "reject": { + "Any-Label-Name": {} + } + } + ] +---- + [[list-drafts]] GET /changes/\{change-id\}/revisions/\{revision-id\}/drafts/ (List Drafts) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -1888,6 +1975,24 @@ The files of the patch set as a map that maps the file names to link:#file-info[FileInfo] entities. |=========================== +[[rule-input]] +RuleInput +~~~~~~~~~ +The `RuleInput` entity contains information to test a Prolog rule. + +[options="header",width="50%",cols="1,^1,5"] +|=========================== +|Field Name ||Description +|`rule`|| +Prolog code to execute instead of the code in `refs/meta/config`. +|`filters`|`RUN` if not set| +When `RUN` filter rules in the parent projects are called to +post-process the results of the project specific rule. This +behavior matches how the rule will execute if installed. + +If `SKIP` the parent filters are not called, allowing the test +to return results from the input rule. +|=========================== + [[submit-info]] SubmitInfo ~~~~~~~~~~ @@ -1920,6 +2025,39 @@ added to the merge queue and the caller can't know whether the change could be merged successfully. |=========================== +[[submit-record]] +SubmitRecord +~~~~~~~~~~~~ +The `SubmitRecord` entity describes results from a submit_rule. + +[options="header",width="50%",cols="1,^1,5"] +|=========================== +|Field Name ||Description +|`status`|| +`OK`, the change can be submitted. + +`NOT_READY`, additional labels are required before submit. + +`CLOSED`, closed changes cannot be submitted. + +`RULE_ERROR`, rule code failed with an error. +|`ok`|optional| +Map of labels that are approved; an link:#account-info[AccountInfo] +identifies the voter chosen by the rule. +|`reject`|optional| +Map of labels that are preventing submit; AccountInfo identifies voter. +|`need`|optional| +Map of labels that need to be given to submit. The value is +currently an empty object. +|`may`|optional| +Map of labels that can be used, but do not affect submit. +AccountInfo identifies voter, if the label has been applied. +|`impossible`|optional| +Map of labels that should have been in `need` but cannot be +used by any user because of access restrictions. The value +is currently an empty object. +|`error_message`|optional| +When status is RULE_ERROR this message provides some text describing +the failure of the rule predicate. +|=========================== + [[topic-input]] TopicInput ~~~~~~~~~~ diff --git a/gerrit-server/src/main/java/com/google/gerrit/rules/RulesCache.java b/gerrit-server/src/main/java/com/google/gerrit/rules/RulesCache.java index f625c0b8d7..dad53b9f4c 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/rules/RulesCache.java +++ b/gerrit-server/src/main/java/com/google/gerrit/rules/RulesCache.java @@ -116,6 +116,10 @@ public class RulesCache { defaultMachine = save(newEmptyMachine(systemLoader)); } + public boolean isProjectRulesEnabled() { + return enableProjectRules; + } + /** * Locate a cached Prolog machine state, or create one if not available. * diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Module.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Module.java index c89e518762..1bf254214a 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Module.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Module.java @@ -59,6 +59,9 @@ public class Module extends RestApiModule { get(REVISION_KIND, "review").to(GetReview.class); post(REVISION_KIND, "review").to(PostReview.class); post(REVISION_KIND, "submit").to(Submit.class); + get(REVISION_KIND, "submit_type").to(TestSubmitType.Get.class); + post(REVISION_KIND, "test.submit_rule").to(TestSubmitRule.class); + post(REVISION_KIND, "test.submit_type").to(TestSubmitType.class); child(REVISION_KIND, "drafts").to(Drafts.class); put(REVISION_KIND, "drafts").to(CreateDraft.class); diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Revisions.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Revisions.java index 2970852a19..68ef63cedf 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Revisions.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Revisions.java @@ -31,7 +31,7 @@ import com.google.inject.Provider; import java.util.Collections; import java.util.List; -class Revisions implements ChildCollection { +public class Revisions implements ChildCollection { private final DynamicMap> views; private final Provider dbProvider; diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/TestSubmitRule.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/TestSubmitRule.java new file mode 100644 index 0000000000..fdf63be0bf --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/TestSubmitRule.java @@ -0,0 +1,198 @@ +// 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.change; + +import com.google.common.base.Charsets; +import com.google.common.base.Function; +import com.google.common.base.Joiner; +import com.google.common.base.Objects; +import com.google.common.base.Throwables; +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.google.gerrit.common.data.SubmitRecord; +import com.google.gerrit.extensions.restapi.AuthException; +import com.google.gerrit.extensions.restapi.BadRequestException; +import com.google.gerrit.extensions.restapi.DefaultInput; +import com.google.gerrit.extensions.restapi.RestModifyView; +import com.google.gerrit.reviewdb.server.ReviewDb; +import com.google.gerrit.rules.RulesCache; +import com.google.gerrit.server.change.TestSubmitRule.Input; +import com.google.gerrit.server.project.RuleEvalException; +import com.google.gerrit.server.project.SubmitRuleEvaluator; +import com.google.gerrit.server.query.change.ChangeData; +import com.google.gwtorm.server.OrmException; +import com.google.inject.Inject; + +import com.googlecode.prolog_cafe.lang.Term; + +import org.kohsuke.args4j.Option; + +import java.io.ByteArrayInputStream; +import java.util.List; +import java.util.Map; + +public class TestSubmitRule implements RestModifyView { + public enum Filters { + RUN, SKIP; + } + + public static class Input { + @DefaultInput + public String rule; + public Filters filters; + } + + private final ReviewDb db; + private final RulesCache rules; + private final AccountInfo.Loader.Factory accountInfoFactory; + + @Option(name = "--filters", usage = "impact of filters in parent projects") + private Filters filters = Filters.RUN; + + @Inject + TestSubmitRule(ReviewDb db, RulesCache rules, + AccountInfo.Loader.Factory infoFactory) { + this.db = db; + this.rules = rules; + this.accountInfoFactory = infoFactory; + } + + @Override + public Object apply(RevisionResource rsrc, Input input) throws OrmException, + BadRequestException, AuthException { + if (input == null) { + input = new Input(); + } + if (input.rule != null && !rules.isProjectRulesEnabled()) { + throw new AuthException("project rules are disabled"); + } + input.filters = Objects.firstNonNull(input.filters, filters); + + SubmitRuleEvaluator evaluator = new SubmitRuleEvaluator( + db, + rsrc.getPatchSet(), + rsrc.getControl().getProjectControl(), + rsrc.getControl(), + rsrc.getChange(), + new ChangeData(rsrc.getChange()), + false, + "locate_submit_rule", "can_submit", + "locate_submit_filter", "filter_submit_results", + input.filters == Filters.SKIP, + input.rule != null + ? new ByteArrayInputStream(input.rule.getBytes(Charsets.UTF_8)) + : null); + + List results; + try { + results = eval(evaluator); + } catch (RuleEvalException e) { + String msg = Joiner.on(": ").skipNulls().join(Iterables.transform( + Throwables.getCausalChain(e), + new Function() { + @Override + public String apply(Throwable in) { + return in.getMessage(); + } + })); + throw new BadRequestException("rule failed: " + msg); + } + if (results.isEmpty()) { + throw new BadRequestException(String.format( + "rule %s has no solutions", + evaluator.getSubmitRule().toString())); + } + + List records = rsrc.getControl().resultsToSubmitRecord( + evaluator.getSubmitRule(), + results); + List out = Lists.newArrayListWithCapacity(records.size()); + AccountInfo.Loader accounts = accountInfoFactory.create(true); + for (SubmitRecord r : records) { + out.add(new Record(r, accounts)); + } + accounts.fill(); + return out; + } + + @SuppressWarnings("unchecked") + private static List eval(SubmitRuleEvaluator evaluator) + throws RuleEvalException { + return evaluator.evaluate().toJava(); + } + + static class Record { + SubmitRecord.Status status; + String errorMessage; + Map ok; + Map reject; + Map need; + Map may; + Map impossible; + + Record(SubmitRecord r, AccountInfo.Loader accounts) { + this.status = r.status; + this.errorMessage = r.errorMessage; + + if (r.labels != null) { + for (SubmitRecord.Label n : r.labels) { + AccountInfo who = n.appliedBy != null + ? accounts.get(n.appliedBy) + : new AccountInfo(null); + label(n, who); + } + } + } + + private void label(SubmitRecord.Label n, AccountInfo who) { + switch (n.status) { + case OK: + if (ok == null) { + ok = Maps.newLinkedHashMap(); + } + ok.put(n.label, who); + break; + case REJECT: + if (reject == null) { + reject = Maps.newLinkedHashMap(); + } + reject.put(n.label, who); + break; + case NEED: + if (need == null) { + need = Maps.newLinkedHashMap(); + } + need.put(n.label, new None()); + break; + case MAY: + if (may == null) { + may = Maps.newLinkedHashMap(); + } + may.put(n.label, who); + break; + case IMPOSSIBLE: + if (impossible == null) { + impossible = Maps.newLinkedHashMap(); + } + impossible.put(n.label, new None()); + break; + } + } + } + + static class None { + } +} diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/TestSubmitType.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/TestSubmitType.java new file mode 100644 index 0000000000..f58fe75149 --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/TestSubmitType.java @@ -0,0 +1,116 @@ +// 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.change; + +import com.google.common.base.Charsets; +import com.google.common.base.Objects; +import com.google.gerrit.extensions.restapi.AuthException; +import com.google.gerrit.extensions.restapi.BadRequestException; +import com.google.gerrit.extensions.restapi.RestModifyView; +import com.google.gerrit.extensions.restapi.RestReadView; +import com.google.gerrit.reviewdb.server.ReviewDb; +import com.google.gerrit.rules.RulesCache; +import com.google.gerrit.server.change.TestSubmitRule.Filters; +import com.google.gerrit.server.change.TestSubmitRule.Input; +import com.google.gerrit.server.project.RuleEvalException; +import com.google.gerrit.server.project.SubmitRuleEvaluator; +import com.google.gerrit.server.query.change.ChangeData; +import com.google.gwtorm.server.OrmException; +import com.google.inject.Inject; + +import com.googlecode.prolog_cafe.lang.ListTerm; +import com.googlecode.prolog_cafe.lang.Term; + +import org.kohsuke.args4j.Option; + +import java.io.ByteArrayInputStream; + +public class TestSubmitType implements RestModifyView { + private final ReviewDb db; + private final RulesCache rules; + + @Option(name = "--filters", usage = "impact of filters in parent projects") + private Filters filters = Filters.RUN; + + @Inject + TestSubmitType(ReviewDb db, RulesCache rules) { + this.db = db; + this.rules = rules; + } + + @Override + public String apply(RevisionResource rsrc, Input input) throws OrmException, + BadRequestException, AuthException { + if (input == null) { + input = new Input(); + } + if (input.rule != null && !rules.isProjectRulesEnabled()) { + throw new AuthException("project rules are disabled"); + } + input.filters = Objects.firstNonNull(input.filters, filters); + + SubmitRuleEvaluator evaluator = new SubmitRuleEvaluator( + db, + rsrc.getPatchSet(), + rsrc.getControl().getProjectControl(), + rsrc.getControl(), + rsrc.getChange(), + new ChangeData(rsrc.getChange()), + false, + "locate_submit_type", "get_submit_type", + "locate_submit_type_filter", "filter_submit_type_results", + input.filters == Filters.SKIP, + input.rule != null + ? new ByteArrayInputStream(input.rule.getBytes(Charsets.UTF_8)) + : null); + + ListTerm results; + try { + results = evaluator.evaluate(); + } catch (RuleEvalException e) { + throw new BadRequestException(String.format( + "rule failed with exception: %s", + e.getMessage())); + } + if (results.isNil()) { + throw new BadRequestException(String.format( + "rule %s has no solution", + evaluator.getSubmitRule())); + } + Term type = results.car(); + if (!type.isSymbol()) { + throw new BadRequestException(String.format( + "rule %s produced invalid result: %s", + evaluator.getSubmitRule().toString(), + type)); + } + return type.toString(); + } + + static class Get implements RestReadView { + private final TestSubmitType test; + + @Inject + Get(TestSubmitType test) { + this.test = test; + } + + @Override + public String apply(RevisionResource resource) throws BadRequestException, + OrmException, AuthException { + return test.apply(resource, null); + } + } +} diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/BaseTestPrologCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/BaseTestPrologCommand.java new file mode 100644 index 0000000000..0e13fd6bc0 --- /dev/null +++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/BaseTestPrologCommand.java @@ -0,0 +1,81 @@ +// 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.extensions.restapi.IdString; +import com.google.gerrit.extensions.restapi.RestModifyView; +import com.google.gerrit.extensions.restapi.TopLevelResource; +import com.google.gerrit.server.OutputFormat; +import com.google.gerrit.server.change.ChangesCollection; +import com.google.gerrit.server.change.RevisionResource; +import com.google.gerrit.server.change.Revisions; +import com.google.gerrit.server.change.TestSubmitRule.Filters; +import com.google.gerrit.server.change.TestSubmitRule.Input; +import com.google.gerrit.sshd.SshCommand; +import com.google.inject.Inject; + +import org.eclipse.jgit.util.IO; +import org.eclipse.jgit.util.RawParseUtils; +import org.kohsuke.args4j.Argument; +import org.kohsuke.args4j.Option; + +import java.nio.ByteBuffer; + +abstract class BaseTestPrologCommand extends SshCommand { + private Input input = new Input(); + + @Inject + private ChangesCollection changes; + + @Inject + private Revisions revisions; + + @Argument(index = 0, required = true, usage = "ChangeId to load in prolog environment") + protected String changeId; + + @Option(name = "-s", + usage = "Read prolog script from stdin instead of reading rules.pl from the refs/meta/config branch") + protected boolean useStdin; + + @Option(name = "--no-filters", aliases = {"-n"}, + usage = "Don't run the submit_filter/2 from the parent projects") + void setNoFilters(boolean no) { + input.filters = no ? Filters.SKIP : Filters.RUN; + } + + protected abstract RestModifyView createView(); + + protected final void run() throws UnloggedFailure { + try { + RevisionResource revision = revisions.parse( + changes.parse( + TopLevelResource.INSTANCE, + IdString.fromUrl(changeId)), + IdString.fromUrl("current")); + if (useStdin) { + ByteBuffer buf = IO.readWholeStream(in, 4096); + input.rule = RawParseUtils.decode( + buf.array(), + buf.arrayOffset(), + buf.limit()); + } + Object result = createView().apply(revision, input); + OutputFormat.JSON.newGson().toJson(result, stdout); + stdout.print('\n'); + } catch (Exception e) { + throw new UnloggedFailure("Processing of prolog script failed: " + e); + } + } +} diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/BaseTestSubmit.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/BaseTestSubmit.java deleted file mode 100644 index b8a71159be..0000000000 --- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/BaseTestSubmit.java +++ /dev/null @@ -1,108 +0,0 @@ -// 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.reviewdb.client.Change; -import com.google.gerrit.reviewdb.client.PatchSet; -import com.google.gerrit.reviewdb.server.ReviewDb; -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.project.ChangeControl; -import com.google.gerrit.server.project.NoSuchChangeException; -import com.google.gerrit.server.project.SubmitRuleEvaluator; -import com.google.gerrit.sshd.SshCommand; -import com.google.gwtorm.server.OrmException; -import com.google.inject.Inject; - -import com.googlecode.prolog_cafe.lang.ListTerm; -import com.googlecode.prolog_cafe.lang.Term; - -import org.kohsuke.args4j.Argument; -import org.kohsuke.args4j.Option; - -import java.util.List; - -abstract class BaseTestSubmit extends SshCommand { - @Inject - protected ReviewDb db; - - @Inject - private ChangeControl.Factory ccFactory; - - @Inject - protected AccountCache accountCache; - - @Inject - @AnonymousCowardName - protected String anonymousCowardName; - - @Argument(index = 0, required = true, usage = "ChangeId to load in prolog environment") - protected String changeId; - - @Option(name = "-s", - usage = "Read prolog script from stdin instead of reading rules.pl from the refs/meta/config branch") - protected boolean useStdin; - - @Option(name = "--format", metaVar = "FMT", usage = "Output display format") - protected OutputFormat format = OutputFormat.TEXT; - - @Option(name = "--no-filters", aliases = {"-n"}, - usage = "Don't run the submit_filter/2 from the parent projects") - protected boolean skipSubmitFilters; - - private Change change; - - private ChangeControl changeControl; - - protected abstract SubmitRuleEvaluator createEvaluator(PatchSet ps) - throws Exception; - protected abstract void processResults(ListTerm results, Term submitRule) - throws Exception; - - protected final void run() throws UnloggedFailure { - try { - PatchSet ps = db.patchSets().get(getChange().currentPatchSetId()); - SubmitRuleEvaluator evaluator = createEvaluator(ps); - processResults(evaluator.evaluate(), evaluator.getSubmitRule()); - } catch (Exception e) { - throw new UnloggedFailure("Processing of prolog script failed: " + e); - } - } - - protected final Change getChange() throws OrmException, UnloggedFailure { - if (change == null) { - List changeList = - db.changes().byKey(new Change.Key(changeId)).toList(); - if (changeList.size() != 1) - throw new UnloggedFailure(1, "Invalid ChangeId"); - - change = changeList.get(0); - } - return change; - } - - protected final ChangeControl getChangeControl() throws OrmException, - NoSuchChangeException, UnloggedFailure { - if (changeControl == null) { - // 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 = ccFactory.controlFor(getChange()); - } - return changeControl; - } -} 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 7838b45344..30f3c95b2d 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 @@ -32,7 +32,6 @@ public class MasterCommandModule extends CommandModule { command(gerrit, RenameGroupCommand.class); command(gerrit, CreateProjectCommand.class); command(gerrit, AdminQueryShell.class); - command(gerrit, TestSubmitRule.class); command(gerrit, SetReviewersCommand.class); command(gerrit, Receive.class); command(gerrit, AdminSetParent.class); @@ -43,7 +42,7 @@ public class MasterCommandModule extends CommandModule { command(gerrit, SetProjectCommand.class); command(gerrit, "test-submit").toProvider(new DispatchCommandProvider(testSubmit)); - command(testSubmit, TestSubmitRule.class); - command(testSubmit, TestSubmitType.class); + command(testSubmit, TestSubmitRuleCommand.class); + command(testSubmit, TestSubmitTypeCommand.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 deleted file mode 100644 index b0729d948e..0000000000 --- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/TestSubmitRule.java +++ /dev/null @@ -1,95 +0,0 @@ -// 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.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.SubmitRuleEvaluator; -import com.google.gerrit.sshd.CommandMetaData; -import com.google.gson.reflect.TypeToken; - -import com.googlecode.prolog_cafe.lang.ListTerm; -import com.googlecode.prolog_cafe.lang.Term; - -import java.util.LinkedList; -import java.util.List; - -/** Command that allows testing of prolog submit-rules in a live instance. */ -@CommandMetaData(name = "rule", descr = "Test prolog submit rules") -final class TestSubmitRule extends BaseTestSubmit { - - protected SubmitRuleEvaluator createEvaluator(PatchSet ps) throws Exception { - ChangeControl cc = getChangeControl(); - return new SubmitRuleEvaluator( - db, ps, cc.getProjectControl(), cc, getChange(), null, - false, "locate_submit_rule", "can_submit", - "locate_submit_filter", "filter_submit_results", - skipSubmitFilters, useStdin ? in : null); - } - - protected void processResults(ListTerm results, Term submitRule) throws Exception { - @SuppressWarnings("unchecked") - List res = getChangeControl().resultsToSubmitRecord(submitRule, - results.toJava()); - if (res.isEmpty()) { - // Should never occur for a well written rule - Change c = getChange(); - stderr.print("Submit rule " + submitRule + " for change " + c.getChangeId() - + " of " + c.getProject().get() + " has no solution"); - return; - } - for (SubmitRecord r : res) { - if (format.isJson()) { - SubmitRecordAttribute submitRecord = new SubmitRecordAttribute(); - submitRecord.status = r.status.name(); - - List submitLabels = new LinkedList(); - 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() {}.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"); - } - } - } -} diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/TestSubmitRuleCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/TestSubmitRuleCommand.java new file mode 100644 index 0000000000..6335160ac7 --- /dev/null +++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/TestSubmitRuleCommand.java @@ -0,0 +1,35 @@ +// 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.extensions.restapi.RestModifyView; +import com.google.gerrit.server.change.RevisionResource; +import com.google.gerrit.server.change.TestSubmitRule; +import com.google.gerrit.server.change.TestSubmitRule.Input; +import com.google.gerrit.sshd.CommandMetaData; +import com.google.inject.Inject; +import com.google.inject.Provider; + +/** Command that allows testing of prolog submit-rules in a live instance. */ +@CommandMetaData(name = "rule", descr = "Test prolog submit rules") +final class TestSubmitRuleCommand extends BaseTestPrologCommand { + @Inject + private Provider view; + + @Override + protected RestModifyView createView() { + return view.get(); + } +} diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/TestSubmitType.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/TestSubmitType.java deleted file mode 100644 index fb4a811426..0000000000 --- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/TestSubmitType.java +++ /dev/null @@ -1,58 +0,0 @@ -// 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.reviewdb.client.Change; -import com.google.gerrit.reviewdb.client.PatchSet; -import com.google.gerrit.server.project.ChangeControl; -import com.google.gerrit.server.project.SubmitRuleEvaluator; -import com.google.gerrit.sshd.CommandMetaData; - -import com.googlecode.prolog_cafe.lang.ListTerm; -import com.googlecode.prolog_cafe.lang.Term; - -import java.util.List; - -@CommandMetaData(name = "type", descr = "Test prolog submit type") -final class TestSubmitType extends BaseTestSubmit { - - @Override - protected SubmitRuleEvaluator createEvaluator(PatchSet ps) throws Exception { - ChangeControl cc = getChangeControl(); - return new SubmitRuleEvaluator( - db, ps, cc.getProjectControl(), cc, getChange(), null, - false, "locate_submit_type", "get_submit_type", - "locate_submit_type_filter", "filter_submit_type_results", - skipSubmitFilters, useStdin ? in : null); - } - - @Override - protected void processResults(ListTerm resultsTerm, Term submitRule) - throws Exception { - @SuppressWarnings("unchecked") - List results = resultsTerm.toJava(); - if (results.isEmpty()) { - // Should never occur for a well written rule - Change c = getChange(); - stderr.print("Submit rule " + submitRule + " for change " + c.getChangeId() - + " of " + c.getProject().get() + " has no solution"); - return; - } - String typeName = results.get(0); - stdout.print(typeName); - stdout.print('\n'); - } -} diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/TestSubmitTypeCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/TestSubmitTypeCommand.java new file mode 100644 index 0000000000..326ff46d3b --- /dev/null +++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/TestSubmitTypeCommand.java @@ -0,0 +1,35 @@ +// 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.extensions.restapi.RestModifyView; +import com.google.gerrit.server.change.RevisionResource; +import com.google.gerrit.server.change.TestSubmitRule.Input; +import com.google.gerrit.server.change.TestSubmitType; +import com.google.gerrit.sshd.CommandMetaData; +import com.google.inject.Inject; +import com.google.inject.Provider; + +@CommandMetaData(name = "type", descr = "Test prolog submit type") +final class TestSubmitTypeCommand extends BaseTestPrologCommand { + @Inject + private Provider view; + + @Override + protected RestModifyView createView() { + return view.get(); + } +}