Add submit_rule examples from Gerrit User Summit 2012.

Shawn had a different approach on writing Prolog submit rules
than what I initially wrote in the Prolog cookbook. Include
most of the Shawn's examples into the cookbook and relate them
to the already existing examples to give readers more choice
when writing own submit rules.

Some existing examples where reformatted to match the formatting
style of the new examples.

Change-Id: I1cda434cca3cd5806748974df053b94ed3bc1389
Signed-off-by: Sasa Zivkov <sasa.zivkov@sap.com>
This commit is contained in:
Sasa Zivkov 2013-01-16 17:00:54 +01:00
parent 92b7410572
commit 79b08cc06b

View File

@ -347,7 +347,8 @@ regardless of the votes it has:
.rules.pl .rules.pl
[caption=""] [caption=""]
==== ====
submit_rule(submit(label('Any-Label-Name', ok(_)))). submit_rule(submit(W)) :-
W = label('Any-Label-Name', ok(_)).
==== ====
In this case we make no use of facts about the change. We don't need it as we are simply In this case we make no use of facts about the change. We don't need it as we are simply
@ -365,7 +366,9 @@ every change submittable we want to enable voting in the standard
.rules.pl .rules.pl
[caption=""] [caption=""]
==== ====
submit_rule(submit(label('Code-Review', ok(_)), label('Verified', ok(_)))). submit_rule(submit(CR, V)) :-
CR = label('Code-Review', ok(_)),
V = label('Verified', ok(_)).
==== ====
Since for every change all label statuses are `'ok'` every change will be submittable. Since for every change all label statuses are `'ok'` every change will be submittable.
@ -380,7 +383,8 @@ votes they have.
.rules.pl .rules.pl
[caption=""] [caption=""]
==== ====
submit_rule(submit(label('Any-Label-Name', reject(_)))). submit_rule(submit(R)) :-
R = label('Any-Label-Name', reject(_)).
==== ====
Since for any change we return only one label with status `reject`, no change Since for any change we return only one label with status `reject`, no change
@ -396,13 +400,17 @@ In this example no change is submittable but here we show how to present 'Need
[caption=""] [caption=""]
==== ====
% In the UI this will show: Need Any-Label-Name % In the UI this will show: Need Any-Label-Name
submit_rule(submit(label('Any-Label-Name', need(_)))). submit_rule(submit(N)) :-
N = label('Any-Label-Name', need(_)).
% We could define more "need" labels by adding more rules % We could define more "need" labels by adding more rules
submit_rule(submit(label('Another-Label-Name', need(_)))). submit_rule(submit(N)) :-
N = label('Another-Label-Name', need(_)).
% or by providing more than one need label in the same rule % or by providing more than one need label in the same rule
submit_rule(submit(label('X-Label-Name', need(_)), label('Y-Label-Name', need(_)))). submit_rule(submit(NX, NY)) :-
NX = label('X-Label-Name', need(_)),
NY = label('Y-Label-Name', need(_)).
==== ====
In the UI this will show: In the UI this will show:
@ -429,8 +437,11 @@ any of the previous `submit_rule(X)` solutions.
.rules.pl .rules.pl
[caption=""] [caption=""]
==== ====
submit_rule(label('Some-Condition', need(_))). submit_rule(submit(N)) :-
submit_rule(label('Another-Condition', ok(_))). N = label('Some-Condition', need(_)).
submit_rule(submit(OK)) :-
OK = label('Another-Condition', ok(_)).
==== ====
The 'Need Some-Condition' will not be show in the UI because of the result of The 'Need Some-Condition' will not be show in the UI because of the result of
@ -441,8 +452,11 @@ The same is valid if the two rules are swapped:
.rules.pl .rules.pl
[caption=""] [caption=""]
==== ====
submit_rule(label('Another-Condition', ok(_))). submit_rule(submit(OK)) :-
submit_rule(label('Some-Condition', need(_))). OK = label('Another-Condition', ok(_)).
submit_rule(submit(N)) :-
N = label('Some-Condition', need(_)).
==== ====
The result of the first rule will stop search for any further solutions. The result of the first rule will stop search for any further solutions.
@ -459,7 +473,8 @@ submittable:
.rules.pl .rules.pl
[caption=""] [caption=""]
==== ====
submit_rule(submit(label('Author-is-John-Doe', need(_)))). submit_rule(submit(Author)) :-
Author = label('Author-is-John-Doe', need(_)).
==== ====
This will show: This will show:
@ -472,9 +487,12 @@ in the UI but no change will be submittable yet. Let's add another rule:
.rules.pl .rules.pl
[caption=""] [caption=""]
==== ====
submit_rule(submit(label('Author-is-John-Doe', need(_)))). submit_rule(submit(Author)) :-
submit_rule(submit(label('Author-is-John-Doe', ok(_)))) Author = label('Author-is-John-Doe', need(_)).
:- gerrit:commit_author(_, 'John Doe', _).
submit_rule(submit(Author)) :-
gerrit:commit_author(_, 'John Doe', _),
Author = label('Author-is-John-Doe', ok(_)).
==== ====
In the second rule we return `ok` status for the `'Author-is-John-Doe'` label In the second rule we return `ok` status for the `'Author-is-John-Doe'` label
@ -490,9 +508,12 @@ Instead of checking by full name we could also check by the email address:
.rules.pl .rules.pl
[caption=""] [caption=""]
==== ====
submit_rule(submit(label('Author-is-John-Doe', need(_)))). submit_rule(submit(Author)) :-
submit_rule(submit(label('Author-is-John-Doe', ok(_)))) Author = label('Author-is-John-Doe', need(_)).
:- gerrit:commit_author(_, _, 'john.doe@example.com').
submit_rule(submit(Author)) :-
gerrit:commit_author(_, _, 'john.doe@example.com'),
Author = label('Author-is-John-Doe', ok(_)).
==== ====
or by user id (assuming it is 1000000): or by user id (assuming it is 1000000):
@ -500,9 +521,12 @@ or by user id (assuming it is 1000000):
.rules.pl .rules.pl
[caption=""] [caption=""]
==== ====
submit_rule(submit(label('Author-is-John-Doe', need(_)))). submit_rule(submit(Author)) :-
submit_rule(submit(label('Author-is-John-Doe', ok(_)))) Author = label('Author-is-John-Doe', need(_)).
:- gerrit:commit_author(user(1000000), _, _).
submit_rule(submit(Author)) :-
gerrit:commit_author(user(1000000), _, _),
Author = label('Author-is-John-Doe', ok(_)).
==== ====
or by a combination of these 3 attributes: or by a combination of these 3 attributes:
@ -510,13 +534,16 @@ or by a combination of these 3 attributes:
.rules.pl .rules.pl
[caption=""] [caption=""]
==== ====
submit_rule(submit(label('Author-is-John-Doe', need(_)))). submit_rule(submit(Author)) :-
submit_rule(submit(label('Author-is-John-Doe', ok(_)))) Author = label('Author-is-John-Doe', need(_)).
:- gerrit:commit_author(_, 'John Doe', 'john.doe@example.com').
submit_rule(submit(Author)) :-
gerrit:commit_author(_, 'John Doe', 'john.doe@example.com'),
Author = label('Author-is-John-Doe', ok(_)).
==== ====
Example 7: Make change submittable if commit message starts with "Trivial fix" Example 7: Make change submittable if commit message starts with "Fix "
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Besides showing how to make use of the commit message text the purpose of this Besides showing how to make use of the commit message text the purpose of this
example is also to show how to match only a part of a string symbol. Similarly example is also to show how to match only a part of a string symbol. Similarly
like commit author the commit message is provided as a string symbol which is like commit author the commit message is provided as a string symbol which is
@ -534,9 +561,12 @@ Let's implement both options:
.rules.pl .rules.pl
[caption=""] [caption=""]
==== ====
submit_rule(submit(label('Commit-Message-starts-with-Trivial-Fix', need(_)))). submit_rule(submit(Fix)) :-
submit_rule(submit(label('Commit-Message-starts-with-Trivial-Fix', ok(_)))) Fix = label('Commit-Message-starts-with-Fix', need(_)).
:- gerrit:commit_message(M), name(M, L), starts_with(L, "Trivial Fix").
submit_rule(submit(Fix)) :-
gerrit:commit_message(M), name(M, L), starts_with(L, "Fix "),
Fix = label('Commit-Message-starts-with-Fix', ok(_)).
starts_with(L, []). starts_with(L, []).
starts_with([H|T1], [H|T2]) :- starts_with(T1, T2). starts_with([H|T1], [H|T2]) :- starts_with(T1, T2).
@ -555,30 +585,74 @@ Using the `gerrit:commit_message_matches` predicate is probably more efficient:
.rules.pl .rules.pl
[caption=""] [caption=""]
==== ====
submit_rule(submit(label('Commit-Message-starts-with-Trivial-Fix', need(_)))). submit_rule(submit(Fix)) :-
submit_rule(submit(label('Commit-Message-starts-with-Trivial-Fix', ok(_)))) Fix = label('Commit-Message-starts-with-Fix', need(_)).
:- gerrit:commit_message_matches('^Trivial Fix').
submit_rule(submit(Fix)) :-
gerrit:commit_message_matches('^Fix '),
Fix = label('Commit-Message-starts-with-Fix', ok(_)).
==== ====
Reusing the default submit policy The previous example could also be written so that it first checks if the commit
--------------------------------- message starts with 'Fix '. If true then it sets OK for that category and stops
further backtracking by using the cut `!` operator:
.rules.pl
[caption=""]
====
submit_rule(submit(Fix)) :-
gerrit:commit_message_matches('^Fix '),
Fix = label('Commit-Message-starts-with-Fix', ok(_)),
!.
% Message does not start with 'Fix ' so Fix is needed to submit
submit_rule(submit(Fix)) :-
Fix = label('Commit-Message-starts-with-Fix', need(_)).
====
The default submit policy
-------------------------
All examples until now concentrate on one particular aspect of change data. All examples until now concentrate on one particular aspect of change data.
However, in real-life scenarios we would rather want to reuse Gerrit's default However, in real-life scenarios we would rather want to reuse Gerrit's default
submit policy and extend/change it for our specific purpose. In other words, we submit policy and extend/change it for our specific purpose. This could be
would like to keep all the default policies (like the `Verified` category, done in one of the following ways:
vetoing change, etc...) and only extend/change an aspect of it. For example, we
may want to disable the ability for change authors to approve their own changes
but keep all other policies the same.
* understand how the default submit policy is implemented and use that as a
template for implementing custom submit rules,
* invoke the default submit rule implementation and then perform further
actions on its return result.
Default submit rule implementation
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The default submit rule with the two default categories, `Code-Review` and
`Verified`, can be implemented as:
.rules.pl
[caption=""]
====
submit_rule(submit(V, CR)) :-
gerrit:max_with_block(-2, 2, 'Code-Review', CR),
gerrit:max_with_block(-1, 1, 'Verified', V).
====
Once this implementation is understood it can be customized to implement
project specific submit rules. Note, that this implementation hardcodes
the two default categories. Introducing a new category in the database would
require introducing the same category here or a `submit_filter` in a parent
project would have to care about including the new category in the result of
this `submit_rule`. On the other side, this example is easy to read and
understand.
Reusing the default submit policy
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
To get results of Gerrits default submit policy we use the To get results of Gerrits default submit policy we use the
`gerrit:default_submit` predicate. This means that if we write a submit rule like: `gerrit:default_submit` predicate. The `gerrit:default_submit(X)` includes all
categories from the database. This means that if we write a submit rule like:
.rules.pl .rules.pl
[caption=""] [caption=""]
==== ====
submit_rule(X) :- gerrit:default_submit(X). submit_rule(X) :- gerrit:default_submit(X).
==== ====
then this is equivalent to not using `rules.pl` at all. We just delegate to then this is equivalent to not using `rules.pl` at all. We just delegate to
default logic. However, once we invoke the `gerrit:default_submit(X)` we can default logic. However, once we invoke the `gerrit:default_submit(X)` we can
perform further actions on the return result `X` and apply our specific perform further actions on the return result `X` and apply our specific
@ -592,7 +666,7 @@ logic. The following pattern illustrates this technique:
project_specific_policy(R, S) :- ... project_specific_policy(R, S) :- ...
==== ====
The following examples build on top of the default submit policy. In the following examples both styles will be shown.
Example 8: Make change submittable only if `Code-Review+2` is given by a non author Example 8: Make change submittable only if `Code-Review+2` is given by a non author
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ -600,6 +674,8 @@ In this example we introduce a new label `Non-Author-Code-Review` and make it
satisfied if there is at least one `Code-Review+2` from a non author. All other satisfied if there is at least one `Code-Review+2` from a non author. All other
default policies like the `Verified` category and vetoing changes still apply. default policies like the `Verified` category and vetoing changes still apply.
Reusing the `gerrit:default_submit`
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
First, we invoke `gerrit:default_submit` to compute the result for the default First, we invoke `gerrit:default_submit` to compute the result for the default
submit policy and then add the `Non-Author-Code-Review` label to it. The submit policy and then add the `Non-Author-Code-Review` label to it. The
`Non-Author-Code-Review` label is added with status `ok` if such an approval `Non-Author-Code-Review` label is added with status `ok` if such an approval
@ -615,7 +691,8 @@ exists or with status `need` if it doesn't exist.
S =.. [submit | R]. S =.. [submit | R].
add_non_author_approval(S1, S2) :- add_non_author_approval(S1, S2) :-
gerrit:commit_author(A), gerrit:commit_label(label('Code-Review', 2), R), gerrit:commit_author(A),
gerrit:commit_label(label('Code-Review', 2), R),
R \= A, !, R \= A, !,
S2 = [label('Non-Author-Code-Review', ok(R)) | S1]. S2 = [label('Non-Author-Code-Review', ok(R)) | S1].
add_non_author_approval(S1, [label('Non-Author-Code-Review', need(_)) | S1]). add_non_author_approval(S1, [label('Non-Author-Code-Review', need(_)) | S1]).
@ -638,6 +715,44 @@ is no non author `Code-Review+2`. The second rule will only be reached
if the `cut` in the first rule is not reached and it only happens if a if the `cut` in the first rule is not reached and it only happens if a
predicate before the `cut` fails. predicate before the `cut` fails.
Don't use `gerrit:default_submit`
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Let's implement the same submit rule the other way, without reusing the
`gerrit:default_submit`:
.rules.pl
[caption=""]
====
submit_rule(submit(CR, V)) :-
base(CR, V),
CR = label(_, ok(Reviewer)),
gerrit:commit_author(Author),
Author \= Reviewer,
!.
submit_rule(submit(CR, V, N)) :-
base(CR, V),
N = label('Non-Author-Code-Review', need(_)).
base(CR, V) :-
gerrit:max_with_block(-2, 2, 'Code-Review', CR),
gerrit:max_with_block(-1, 1, 'Verified', V).
====
The latter implementation is probably easier to understand and the code looks
cleaner. Note, however, that the latter implementation will always return the
two standard categories only (`Code-Review` and `Verified`) even if a new
category has beeen inserted into the database. To include the new category
the `rules.pl` would need to be modified or a `submit_filter` in a parent
project would have to care about including the new category in the result
of this `submit_rule`.
The former example, however, would include any newly added category as it
invokes the `gerrit:default_submit` and then modifies its result.
Which of these two behaviors is desired will always depend on how a particular
Gerrit server is managed.
Example 9: Remove the `Verified` category Example 9: Remove the `Verified` category
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
A project has no build and test. It consists of only text files and needs only A project has no build and test. It consists of only text files and needs only
@ -646,6 +761,17 @@ that `Code-Review+2` is the only criteria for a change to become submittable.
We also want the UI to not show the `Verified` category in the table with We also want the UI to not show the `Verified` category in the table with
votes and on the voting screen. votes and on the voting screen.
This is quite simple without reusing the 'gerrit:default_submit`:
.rules.pl
[caption=""]
====
submit_rule(submit(CR)) :-
gerrit:max_with_block(-2, 2, 'Code-Review', CR).
====
Implementing the same rule by reusing `gerrit:default_submit` is a bit more complex:
.rules.pl .rules.pl
[caption=""] [caption=""]
==== ====
@ -679,6 +805,27 @@ principle. This means we want a combination of examples 7 and 8.
The `remove_verified_category` and `add_non_author_approval` predicates are the The `remove_verified_category` and `add_non_author_approval` predicates are the
same as defined in the previous two examples. same as defined in the previous two examples.
Without reusing the `gerrit:default_submit` the same example may be implemented
as:
.rules.pl
[caption=""]
====
submit_rule(submit(CR)) :-
base(CR),
CR = label(_, ok(Reviewer)),
gerrit:commit_author(Author),
Author \= Reviewer,
!.
submit_rule(submit(CR, N)) :-
base(CR),
N = label('Non-Author-Code-Review', need(_)).
base(CR) :-
gerrit:max_with_block(-2, 2, 'Code-Review', CR),
====
Example 11: Remove the `Verified` category from all projects Example 11: Remove the `Verified` category from all projects
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Example 9, implements `submit_rule` that removes the `Verified` category from Example 9, implements `submit_rule` that removes the `Verified` category from
@ -700,7 +847,32 @@ we have to do that in the `rules.pl` of the `All-Projects` project.
remove_verified_category([H|T], [H|R]) :- remove_verified_category(T, R). remove_verified_category([H|T], [H|R]) :- remove_verified_category(T, R).
==== ====
Example 12: 1+1=2 Code-Review Example 12: On release branches require DrNo in addition to project rules
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
A new category 'DrNo' is added to the database and is required for release
branches. To mark a branch as a release branch we use `drno('refs/heads/branch')`.
.rules.pl
[caption=""]
====
drno('refs/heads/master').
drno('refs/heads/stable-2.3').
drno('refs/heads/stable-2.4').
drno('refs/heads/stable-2.5').
drno('refs/heads/stable-2.5').
submit_filter(In, Out) :-
gerrit:change_branch(Branch),
drno(Branch),
!,
In =.. [submit | I],
gerrit:max_with_block(-1, 1, 'DrNo', DrNo),
Out =.. [submit, DrNo | I].
submit_filter(In, Out) :- In = Out.
====
Example 13: 1+1=2 Code-Review
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In this example we introduce accumulative voting to determine if a change is In this example we introduce accumulative voting to determine if a change is
submittable or not. We modify the standard Code-Review to be accumulative, and make the submittable or not. We modify the standard Code-Review to be accumulative, and make the
@ -736,7 +908,34 @@ gerrit:remove_label is a built-in helper that is implemented similarly to the
S =.. [submit | Labels]. S =.. [submit | Labels].
==== ====
Example 13: Master and apprentice Implementing the same example without using `gerrit:default_submit`:
.rules.pl
[caption=""]
====
submit_rule(submit(CR, V)) :-
sum(2, 'Code-Review', CR),
gerrit:max_with_block(-1, 1, 'Verified', V).
% Sum the votes in a category. Uses a helper function score/2
% to select out only the score values the given category.
sum(VotesNeeded, Category, label(Category, ok(_))) :-
findall(Score, score(Category, Score), All),
sum_list(All, Sum),
Sum >= VotesNeeded,
!.
sum(VotesNeeded, Category, label(Category, need(VotesNeeded))).
score(Category, Score) :-
gerrit:commit_label(label(Category, Score), User).
% Simple Prolog routine to sum a list of integers.
sum_list(List, Sum) :- sum_list(List, 0, Sum).
sum_list([X|T], Y, S) :- Z is X + Y, sum_list(T, Z, S).
sum_list([], S, S).
====
Example 14: Master and apprentice
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The master and apprentice example allow you to specify a user (the `master`) The master and apprentice example allow you to specify a user (the `master`)
that must approve all changes done by another user (the `apprentice`). that must approve all changes done by another user (the `apprentice`).
@ -773,7 +972,7 @@ review by the associated `master`.
add_apprentice_master(S, S). add_apprentice_master(S, S).
==== ====
Example 14: Only allow Author to submit change Example 15: Only allow Author to submit change
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This example adds a new needed category `Patchset-Author` for any user that is This example adds a new needed category `Patchset-Author` for any user that is
not the author of the patch. This effectively blocks all users except the author not the author of the patch. This effectively blocks all users except the author