Add an 'unresolved_comments_count' prolog fact for each change

This change introduces a new prolog fact for the number of unresolved
comments. This fact can be further used by some prolog rules to block
the submission of changes with some unresolved comments.

A new example is also added to the prolog cookbook to show how to
use this new fact.

Change-Id: Iadc48f0758137b0071484265c22ebcb9bc2d2b26
This commit is contained in:
Changcheng Xiao 2017-02-15 13:11:20 +01:00
parent 4ed2216797
commit a5d01788a9
4 changed files with 167 additions and 0 deletions

View File

@ -60,6 +60,9 @@ of them we must use a qualified name like `gerrit:change_branch(X)`.
|`uploader/1` |`uploader(user(1000000)).`
|Uploader as `user(ID)` term. ID is the numeric account ID
|`unresolved_comments_count/1` |`unresolved_comments_count(0).`
|The number of unresolved comments as an integer atom
|=============================================================================
In addition Gerrit provides a set of built-in helper predicates that can be used

View File

@ -998,6 +998,56 @@ only_allow_author_to_submit(S, S) :-
only_allow_author_to_submit(S1, [label('Only-Author-Can-Submit', need(_)) | S1]).
----
=== Example 16: Make change submittable if all comments have been resolved
In this example we will use the `unresolved_comments_count` fact about a
change. Our goal is to block the submission of any change with some
unresolved comments. Basically, it can be achieved by the following rules:
`rules.pl`
[source,prolog]
----
submit_rule(submit(R)) :-
gerrit:unresolved_comments_count(0),
!,
gerrit:commit_author(A),
R = label('All-Comments-Resolved', ok(A)).
submit_rule(submit(R)) :-
gerrit:unresolved_comments_count(U),
U > 0,
R = label('All-Comments-Resolved', need(_)).
----
Suppose currently a change is submittable if it gets `+2` for `Code-Review`
and `+1` for `Verified`. It can be extended to support the above rules as
follows:
`rules.pl`
[source,prolog]
----
submit_rule(submit(CR, V, R)) :-
base(CR, V),
gerrit:unresolved_comments_count(0),
!,
gerrit:commit_author(A),
R = label('All-Comments-Resolved', ok(A)).
submit_rule(submit(CR, V, R)) :-
base(CR, V),
gerrit:unresolved_comments_count(U),
U > 0,
R = label('All-Comments-Resolved', need(_)).
base(CR, V) :-
gerrit:max_with_block(-2, 2, 'Code-Review', CR),
gerrit:max_with_block(-1, 1, 'Verified', V).
----
Note that a new label as `All-Comments-Resolved` should not be configured.
It's only used to show `'Needs All-Comments-Resolved'` in the UI to clearly
indicate to the user that all the comments have to be resolved for the
change to become submittable.
== Examples - Submit Type
The following examples show how to implement own submit type rules.

View File

@ -46,6 +46,7 @@ import com.google.gerrit.acceptance.GerritConfig;
import com.google.gerrit.acceptance.GitUtil;
import com.google.gerrit.acceptance.NoHttpd;
import com.google.gerrit.acceptance.PushOneCommit;
import com.google.gerrit.acceptance.Sandboxed;
import com.google.gerrit.acceptance.TestAccount;
import com.google.gerrit.acceptance.TestProjectInput;
import com.google.gerrit.common.FooterConstants;
@ -2408,6 +2409,71 @@ public class ChangeIT extends AbstractDaemonTest {
assertThat(approval.permittedVotingRange).isNull();
}
@Sandboxed
@Test
public void unresolvedCommentsBlocked() throws Exception {
RevCommit oldHead = getRemoteHead();
GitUtil.fetch(testRepo, RefNames.REFS_CONFIG + ":config");
testRepo.reset("config");
PushOneCommit push =
pushFactory.create(
db,
admin.getIdent(),
testRepo,
"Configure",
"rules.pl",
"submit_rule(submit(R)) :- \n"
+ "gerrit:unresolved_comments_count(0), \n"
+ "!,"
+ "gerrit:commit_author(A), \n"
+ "R = label('All-Comments-Resolved', ok(A)).\n"
+ "submit_rule(submit(R)) :- \n"
+ "gerrit:unresolved_comments_count(U), \n"
+ "U > 0,"
+ "R = label('All-Comments-Resolved', need(_)). \n\n");
push.to(RefNames.REFS_CONFIG);
testRepo.reset(oldHead);
oldHead = getRemoteHead();
PushOneCommit.Result result1 =
pushFactory.create(db, user.getIdent(), testRepo).to("refs/for/master");
testRepo.reset(oldHead);
PushOneCommit.Result result2 =
pushFactory.create(db, user.getIdent(), testRepo).to("refs/for/master");
addComment(result1, "comment 1", true, false, null);
addComment(result2, "comment 2", true, true, null);
gApi.changes().id(result1.getChangeId()).current().submit();
exception.expect(ResourceConflictException.class);
exception.expectMessage(
"Failed to submit 1 change due to the following problems:\n"
+ "Change 2: needs All-Comments-Resolved");
gApi.changes().id(result2.getChangeId()).current().submit();
}
private void addComment(
PushOneCommit.Result r,
String message,
boolean omitDuplicateComments,
Boolean unresolved,
String inReplyTo)
throws Exception {
ReviewInput.CommentInput c = new ReviewInput.CommentInput();
c.line = 1;
c.message = message;
c.path = FILE_NAME;
c.unresolved = unresolved;
c.inReplyTo = inReplyTo;
ReviewInput in = new ReviewInput();
in.comments = new HashMap<>();
in.comments.put(c.path, Lists.newArrayList(c));
in.omitDuplicateComments = omitDuplicateComments;
gApi.changes().id(r.getChangeId()).revision(r.getCommit().name()).review(in);
}
private static Iterable<Account.Id> getReviewers(Collection<AccountInfo> r) {
return Iterables.transform(r, a -> new Account.Id(a._accountId));
}

View File

@ -0,0 +1,48 @@
// Copyright (C) 2017 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 gerrit;
import com.google.gerrit.rules.StoredValues;
import com.google.gwtorm.server.OrmException;
import com.googlecode.prolog_cafe.exceptions.JavaException;
import com.googlecode.prolog_cafe.exceptions.PrologException;
import com.googlecode.prolog_cafe.lang.IntegerTerm;
import com.googlecode.prolog_cafe.lang.Operation;
import com.googlecode.prolog_cafe.lang.Predicate;
import com.googlecode.prolog_cafe.lang.Prolog;
import com.googlecode.prolog_cafe.lang.Term;
public class PRED_unresolved_comments_count_1 extends Predicate.P1 {
public PRED_unresolved_comments_count_1(Term a1, Operation n) {
arg1 = a1;
cont = n;
}
@Override
public Operation exec(Prolog engine) throws PrologException {
engine.setB0();
Term a1 = arg1.dereference();
try {
Integer count = StoredValues.CHANGE_DATA.get(engine).unresolvedCommentCount();
if (!a1.unify(new IntegerTerm(count != null ? count : 0), engine.trail)) {
return engine.fail();
}
} catch (OrmException err) {
throw new JavaException(this, 1, err);
}
return cont;
}
}