Documentation: prolog-cookbook, Update style

Update style and formatting
Wrap lines at 80 chars

Change-Id: Ib3cae9f26d484842fa9cf092ff8c2af2458a0fc1
This commit is contained in:
David Myllykangas
2015-01-23 11:39:01 +01:00
parent 587c7a235d
commit c5f591da43

View File

@@ -2,11 +2,11 @@
[[SubmitRule]] [[SubmitRule]]
== Submit Rule == Submit Rule
A 'Submit Rule' in Gerrit is logic that defines when a change is submittable. A _Submit Rule_ in Gerrit is logic that defines when a change is submittable.
By default, a change is submittable when it gets at least one By default, a change is submittable when it gets at least one
highest vote in each voting category and has no lowest vote (aka veto vote) in highest vote in each voting category and has no lowest vote (aka veto vote) in
any category. Typically, this means that a change needs 'Code-Review+2', any category. Typically, this means that a change needs `Code-Review+2`,
'Verified+1' and has neither 'Code-Review-2' nor 'Verified-1' to become `Verified+1` and has neither `Code-Review-2` nor `Verified-1` to become
submittable. submittable.
While this rule is a good default, there are projects which need more While this rule is a good default, there are projects which need more
@@ -29,7 +29,7 @@ link:http://gerrit-documentation.googlecode.com/svn/ReleaseNotes/ReleaseNotes-2.
[[SubmitType]] [[SubmitType]]
== Submit Type == Submit Type
A 'Submit Type' is a strategy that is used on submit to integrate the A _Submit Type_ is a strategy that is used on submit to integrate the
change into the destination branch. Supported submit types are: change into the destination branch. Supported submit types are:
* `Fast Forward Only` * `Fast Forward Only`
@@ -38,7 +38,7 @@ change into the destination branch. Supported submit types are:
* `Cherry Pick` * `Cherry Pick`
* `Rebase If Necessary` * `Rebase If Necessary`
'Submit Type' is a project global setting. This means that the same submit type _Submit Type_ is a project global setting. This means that the same submit type
is used for all changes of one project. is used for all changes of one project.
Projects which need more flexibility in choosing, or enforcing, a submit type Projects which need more flexibility in choosing, or enforcing, a submit type
@@ -51,14 +51,15 @@ submit type is shown on the change screen for each change.
== Prolog Language == Prolog Language
This document is not a complete Prolog tutorial. This document is not a complete Prolog tutorial.
link:http://en.wikipedia.org/wiki/Prolog[This Wikipedia page on Prolog] is a link:http://en.wikipedia.org/wiki/Prolog[This Wikipedia page on Prolog] is a
good starting point for learning the Prolog language. This document will only explain good starting point for learning the Prolog language. This document will only
some elements of Prolog that are necessary to understand the provided examples. explain some elements of Prolog that are necessary to understand the provided
examples.
== Prolog in Gerrit == Prolog in Gerrit
Gerrit uses its own link:https://code.google.com/p/prolog-cafe/[fork] of the Gerrit uses its own link:https://code.google.com/p/prolog-cafe/[fork] of the
original link:http://kaminari.istc.kobe-u.ac.jp/PrologCafe/[prolog-cafe] original link:http://kaminari.istc.kobe-u.ac.jp/PrologCafe/[prolog-cafe]
project. Gerrit embeds the prolog-cafe library and can interpret Prolog programs at project. Gerrit embeds the prolog-cafe library and can interpret Prolog programs
runtime. at runtime.
== Interactive Prolog Cafe Shell == Interactive Prolog Cafe Shell
For interactive testing and playing with Prolog, Gerrit provides the For interactive testing and playing with Prolog, Gerrit provides the
@@ -66,7 +67,8 @@ link:pgm-prolog-shell.html[prolog-shell] program which opens an interactive
Prolog interpreter shell. Prolog interpreter shell.
NOTE: The interactive shell is just a prolog shell, it does not load NOTE: The interactive shell is just a prolog shell, it does not load
a gerrit server environment and thus is not intended for xref:TestingSubmitRules[testing submit rules]. a gerrit server environment and thus is not intended for
xref:TestingSubmitRules[testing submit rules].
== SWI-Prolog == SWI-Prolog
Instead of using the link:pgm-prolog-shell.html[prolog-shell] program one can Instead of using the link:pgm-prolog-shell.html[prolog-shell] program one can
@@ -94,8 +96,8 @@ file:
[[HowToWriteSubmitRules]] [[HowToWriteSubmitRules]]
== How to write submit rules == How to write submit rules
Whenever Gerrit needs to evaluate submit rules for a change `C` from project `P` it Whenever Gerrit needs to evaluate submit rules for a change `C` from project `P`
will first initialize the embedded Prolog interpreter by: it will first initialize the embedded Prolog interpreter by:
* consulting a set of facts about the change `C` * consulting a set of facts about the change `C`
* consulting the `rules.pl` from the project `P` * consulting the `rules.pl` from the project `P`
@@ -126,9 +128,9 @@ link:prolog-change-facts.html[Prolog Facts for Gerrit Change].
By default, Gerrit will search for a `submit_rule/1` predicate in the `rules.pl` By default, Gerrit will search for a `submit_rule/1` predicate in the `rules.pl`
file, evaluate the `submit_rule(X)` and then inspect the value of `X` in order file, evaluate the `submit_rule(X)` and then inspect the value of `X` in order
to decide whether the change is submittable or not and also to find the set of to decide whether the change is submittable or not and also to find the set of
needed criteria for the change to become submittable. This means that Gerrit has an needed criteria for the change to become submittable. This means that Gerrit has
expectation on the format and value of the result of the `submit_rule` predicate an expectation on the format and value of the result of the `submit_rule`
which is expected to be a `submit` term of the following format: predicate which is expected to be a `submit` term of the following format:
==== ====
submit(label(label-name, status) [, label(label-name, status)]*) submit(label(label-name, status) [, label(label-name, status)]*)
@@ -141,13 +143,13 @@ be any other string (see examples below). The `status` is one of:
used to tell that this label/category has been met. used to tell that this label/category has been met.
* `need(_)` is used to tell that this label/category is needed for change to * `need(_)` is used to tell that this label/category is needed for change to
become submittable become submittable
* `reject(user(ID))` or just `reject(_)`. This status is used to tell that label/category * `reject(user(ID))` or just `reject(_)`. This status is used to tell that
is blocking change submission label/category is blocking change submission
* `impossible(_)` is used when the logic knows that the change cannot be submitted as-is. * `impossible(_)` is used when the logic knows that the change cannot be
Administrative intervention is probably required. This is meant for cases submitted as-is. Administrative intervention is probably required. This is
where the logic requires members of "FooEng" to score "Code-Review +2" on a meant for cases where the logic requires members of "FooEng" to score
change, but nobody is in group "FooEng". It is to hint at permissions `Code-Review +2` on a change, but nobody is in group "FooEng". It is to hint
misconfigurations. at permissions misconfigurations.
* `may(_)` allows expression of approval categories that are optional, i.e. * `may(_)` allows expression of approval categories that are optional, i.e.
could either be set or unset without ever influencing whether the change could either be set or unset without ever influencing whether the change
could be submitted. could be submitted.
@@ -176,10 +178,11 @@ Here some examples of possible return values from the `submit_rule` predicate:
<2> label `'Verified'` is rejected. Change is not submittable. <2> label `'Verified'` is rejected. Change is not submittable.
<3> label `'Author-is-John-Doe'` is needed for the change to become submittable. <3> label `'Author-is-John-Doe'` is needed for the change to become submittable.
Note that this tells nothing about how this criteria will be met. It is up Note that this tells nothing about how this criteria will be met. It is up
to the implementer of the `submit_rule` to return `label('Author-is-John-Doe', to the implementer of the `submit_rule` to return
ok(_))` when this criteria is met. Most likely, it will have to match `label('Author-is-John-Doe', ok(_))` when this criteria is met. Most
against `gerrit:commit_author` in order to check if this criteria is met. likely, it will have to match against `gerrit:commit_author` in order to
This will become clear through the examples below. check if this criteria is met. This will become clear through the examples
below.
Of course, when implementing the `submit_rule` we will use the facts about the Of course, when implementing the `submit_rule` we will use the facts about the
change that are already provided by Gerrit. change that are already provided by Gerrit.
@@ -190,9 +193,9 @@ screen for voting. If the return result contains label `'ABC'` and if the label
`'ABC'` is link:config-labels.html[defined for the project] then voting for the `'ABC'` is link:config-labels.html[defined for the project] then voting for the
label `'ABC'` will be displayed. Otherwise, it is not displayed. Note that the label `'ABC'` will be displayed. Otherwise, it is not displayed. Note that the
project doesn't need a defined label for each label contained in the result of project doesn't need a defined label for each label contained in the result of
`submit_rule` predicate. For example, the decision whether `'Author-is-John-Doe'` `submit_rule` predicate. For example, the decision whether
label is met will probably not be made by explicit voting but, instead, by `'Author-is-John-Doe'` label is met will probably not be made by explicit voting
inspecting the facts about the change. but, instead, by inspecting the facts about the change.
[[SubmitFilter]] [[SubmitFilter]]
== Submit Filter == Submit Filter
@@ -202,7 +205,7 @@ in the `rules.pl` file of the current project, the `submit_filter` will be
searched for in the `rules.pl` of all parent projects of the current project, searched for in the `rules.pl` of all parent projects of the current project,
but not in the `rules.pl` of the current project. The search will start from the but not in the `rules.pl` of the current project. The search will start from the
immediate parent of the current project, then in the parent project of that immediate parent of the current project, then in the parent project of that
project and so on until, and including, the 'All-Projects' project. project and so on until, and including, the `'All-Projects'` project.
The purpose of the submit filter is, as its name says, to filter the results The purpose of the submit filter is, as its name says, to filter the results
of the `submit_rule`. Therefore, the `submit_filter` predicate has two of the `submit_rule`. Therefore, the `submit_filter` predicate has two
@@ -290,14 +293,15 @@ the result.
[[TestingSubmitRules]] [[TestingSubmitRules]]
== Testing submit rules == Testing submit rules
The prolog environment running the `submit_rule` is loaded with state describing the The prolog environment running the `submit_rule` is loaded with state describing
change that is being evaluated. The easiest way to load this state is to test your the change that is being evaluated. The easiest way to load this state is to
`submit_rule` against a real change on a running gerrit instance. The command test your `submit_rule` against a real change on a running gerrit instance. The
link:cmd-test-submit-rule.html[test-submit rule] loads a specific change and executes command link:cmd-test-submit-rule.html[test-submit rule] loads a specific change
the `submit_rule`. It optionally reads the rule from from `stdin` to facilitate easy testing. and executes the `submit_rule`. It optionally reads the rule from from `stdin`
to facilitate easy testing.
==== ====
cat rules.pl | ssh gerrit_srv gerrit test-submit rule I45e080b105a50a625cc8e1fb5b357c0bfabe6d68 -s $ cat rules.pl | ssh gerrit_srv gerrit test-submit rule I45e080b105a50a625cc8e1fb5b357c0bfabe6d68 -s
==== ====
== Prolog vs Gerrit plugin for project specific submit rules == Prolog vs Gerrit plugin for project specific submit rules
@@ -318,9 +322,9 @@ From version 2.6 Gerrit plugins can contribute Prolog predicates. This way, we
can make use of the plugin provided predicates when writing Prolog based rules. can make use of the plugin provided predicates when writing Prolog based rules.
== Examples - Submit Rule == Examples - Submit Rule
The following examples should serve as a cookbook for developing own submit rules. The following examples should serve as a cookbook for developing own submit
Some of them are too trivial to be used in production and their only purpose is rules. Some of them are too trivial to be used in production and their only
to provide step by step introduction and understanding. purpose is to provide step by step introduction and understanding.
Some of the examples will implement the `submit_rule` and some will implement Some of the examples will implement the `submit_rule` and some will implement
the `submit_filter` just to show both possibilities. Remember that the `submit_filter` just to show both possibilities. Remember that
@@ -329,49 +333,49 @@ invoked from all parent projects. This is the most important fact in deciding
whether to implement `submit_rule` or `submit_filter`. whether to implement `submit_rule` or `submit_filter`.
=== Example 1: Make every change submittable === Example 1: Make every change submittable
Let's start with a most trivial example where we would make every change submittable Let's start with a most trivial example where we would make every change
regardless of the votes it has: submittable regardless of the votes it has:
.rules.pl `rules.pl`
[caption=""] [source,prolog]
==== ----
submit_rule(submit(W)) :- submit_rule(submit(W)) :-
W = label('Any-Label-Name', ok(_)). 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
making every change submittable. Note that, in this case, the Gerrit UI will not show are simply making every change submittable. Note that, in this case, the Gerrit
the UI for voting for the standard `'Code-Review'` and `'Verified'` categories as labels UI will not show the UI for voting for the standard `'Code-Review'` and
with these names are not part of the return result. The `'Any-Label-Name'` could really `'Verified'` categories as labels with these names are not part of the return
be any string. result. The `'Any-Label-Name'` could really be any string.
=== Example 2: Every change submittable and voting in the standard categories possible === Example 2: Every change submittable and voting in the standard categories possible
This is continuation of the previous example where, in addition, to making This is continuation of the previous example where, in addition, to making
every change submittable we want to enable voting in the standard every change submittable we want to enable voting in the standard
`'Code-Review'` and `'Verified'` categories. `'Code-Review'` and `'Verified'` categories.
.rules.pl `rules.pl`
[caption=""] [source,prolog]
==== ----
submit_rule(submit(CR, V)) :- submit_rule(submit(CR, V)) :-
CR = label('Code-Review', ok(_)), CR = label('Code-Review', ok(_)),
V = label('Verified', 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
Voting in the standard labels will be shown in the UI as the standard label names are submittable. Voting in the standard labels will be shown in the UI as the
included in the return result. standard label names are included in the return result.
=== Example 3: Nothing is submittable === Example 3: Nothing is submittable
This example shows how to make all changes non-submittable regardless of the This example shows how to make all changes non-submittable regardless of the
votes they have. votes they have.
.rules.pl `rules.pl`
[caption=""] [source,prolog]
==== ----
submit_rule(submit(R)) :- submit_rule(submit(R)) :-
R = label('Any-Label-Name', reject(_)). 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
will be submittable. The UI will, however, not indicate what is needed for a will be submittable. The UI will, however, not indicate what is needed for a
@@ -381,9 +385,9 @@ change to become submittable as we return no labels with status `need`.
In this example no change is submittable but here we show how to present 'Need In this example no change is submittable but here we show how to present 'Need
<label>' information to the user in the UI. <label>' information to the user in the UI.
.rules.pl `rules.pl`
[caption=""] [source,prolog]
==== ----
% In the UI this will show: Need Any-Label-Name % In the UI this will show: Need Any-Label-Name
submit_rule(submit(N)) :- submit_rule(submit(N)) :-
N = label('Any-Label-Name', need(_)). N = label('Any-Label-Name', need(_)).
@@ -396,52 +400,51 @@ In this example no change is submittable but here we show how to present 'Need
submit_rule(submit(NX, NY)) :- submit_rule(submit(NX, NY)) :-
NX = label('X-Label-Name', need(_)), NX = label('X-Label-Name', need(_)),
NY = label('Y-Label-Name', need(_)). NY = label('Y-Label-Name', need(_)).
==== ----
In the UI this will show: In the UI this will show:
****
* Need Any-Label-Name * `Need Any-Label-Name`
* Need Another-Label-Name * `Need Another-Label-Name`
* Need X-Label-Name * `Need X-Label-Name`
* Need Y-Label-Name * `Need Y-Label-Name`
****
From the example above we can see a few more things: From the example above we can see a few more things:
* comment in Prolog starts with the `%` character * comment in Prolog starts with the `%` character
* there could be multiple `submit_rule` predicates. Since Prolog, by default, tries to find * there could be multiple `submit_rule` predicates. Since Prolog, by default,
all solutions for a query, the result will be union of all solutions. tries to find all solutions for a query, the result will be union of all
Therefore, we see all 4 `need` labels in the UI. solutions. Therefore, we see all 4 `need` labels in the UI.
=== Example 5: The 'Need ...' labels not shown when change is submittable === Example 5: The 'Need ...' labels not shown when change is submittable
This example shows that, when there is a solution for `submit_rule(X)` where all labels This example shows that, when there is a solution for `submit_rule(X)` where all
have status `ok` then Gerrit will not show any labels with the `need` status from labels have status `ok` then Gerrit will not show any labels with the `need`
any of the previous `submit_rule(X)` solutions. status from any of the previous `submit_rule(X)` solutions.
.rules.pl `rules.pl`
[caption=""] [source,prolog]
==== ----
submit_rule(submit(N)) :- submit_rule(submit(N)) :-
N = label('Some-Condition', need(_)). N = label('Some-Condition', need(_)).
submit_rule(submit(OK)) :- submit_rule(submit(OK)) :-
OK = label('Another-Condition', ok(_)). OK = label('Another-Condition', ok(_)).
==== ----
The 'Need Some-Condition' will not be shown in the UI because of the result of The `'Need Some-Condition'` will not be shown in the UI because of the result of
the second rule. the second rule.
The same is valid if the two rules are swapped: The same is valid if the two rules are swapped:
.rules.pl `rules.pl`
[caption=""] [source,prolog]
==== ----
submit_rule(submit(OK)) :- submit_rule(submit(OK)) :-
OK = label('Another-Condition', ok(_)). OK = label('Another-Condition', ok(_)).
submit_rule(submit(N)) :- submit_rule(submit(N)) :-
N = label('Some-Condition', need(_)). 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.
@@ -449,81 +452,80 @@ The result of the first rule will stop search for any further solutions.
This is the first example where we will use the Prolog facts about a change that This is the first example where we will use the Prolog facts about a change that
are automatically exposed by Gerrit. Our goal is to make any change submittable are automatically exposed by Gerrit. Our goal is to make any change submittable
when the commit author is named `'John Doe'`. In the very first when the commit author is named `'John Doe'`. In the very first
step let's make sure Gerrit UI shows 'Need Author-is-John-Doe' in step let's make sure Gerrit UI shows `'Need Author-is-John-Doe'` in
the UI to clearly indicate to the user what is needed for a change to become the UI to clearly indicate to the user what is needed for a change to become
submittable: submittable:
.rules.pl `rules.pl`
[caption=""] [source,prolog]
==== ----
submit_rule(submit(Author)) :- submit_rule(submit(Author)) :-
Author = label('Author-is-John-Doe', need(_)). Author = label('Author-is-John-Doe', need(_)).
==== ----
This will show: This will show:
****
* Need Author-is-John-Doe * `Need Author-is-John-Doe`
****
in the UI but no change will be submittable yet. Let's add another rule: in the UI but no change will be submittable yet. Let's add another rule:
.rules.pl `rules.pl`
[caption=""] [source,prolog]
==== ----
submit_rule(submit(Author)) :- submit_rule(submit(Author)) :-
Author = label('Author-is-John-Doe', need(_)). Author = label('Author-is-John-Doe', need(_)).
submit_rule(submit(Author)) :- submit_rule(submit(Author)) :-
gerrit:commit_author(_, 'John Doe', _), gerrit:commit_author(_, 'John Doe', _),
Author = label('Author-is-John-Doe', ok(_)). 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
if there is a `commit_author` fact where the full name is `'John Doe'`. If if there is a `commit_author` fact where the full name is `'John Doe'`. If
author of a change is `'John Doe'` then the second rule will return a solution author of a change is `'John Doe'` then the second rule will return a solution
where all labels have `ok` status and the change will become submittable. If where all labels have `ok` status and the change will become submittable. If
author of a change is not `'John Doe'` then only the first rule will produce a author of a change is not `'John Doe'` then only the first rule will produce a
solution. The UI will show 'Need Author-is-John-Doe' but, as expected, the solution. The UI will show `'Need Author-is-John-Doe'` but, as expected, the
change will not be submittable. change will not be submittable.
Instead of checking by full name we could also check by the email address: Instead of checking by full name we could also check by the email address:
.rules.pl `rules.pl`
[caption=""] [source,prolog]
==== ----
submit_rule(submit(Author)) :- submit_rule(submit(Author)) :-
Author = label('Author-is-John-Doe', need(_)). Author = label('Author-is-John-Doe', need(_)).
submit_rule(submit(Author)) :- submit_rule(submit(Author)) :-
gerrit:commit_author(_, _, 'john.doe@example.com'), gerrit:commit_author(_, _, 'john.doe@example.com'),
Author = label('Author-is-John-Doe', ok(_)). Author = label('Author-is-John-Doe', ok(_)).
==== ----
or by user id (assuming it is 1000000): or by user id (assuming it is `1000000`):
.rules.pl `rules.pl`
[caption=""] [source,prolog]
==== ----
submit_rule(submit(Author)) :- submit_rule(submit(Author)) :-
Author = label('Author-is-John-Doe', need(_)). Author = label('Author-is-John-Doe', need(_)).
submit_rule(submit(Author)) :- submit_rule(submit(Author)) :-
gerrit:commit_author(user(1000000), _, _), gerrit:commit_author(user(1000000), _, _),
Author = label('Author-is-John-Doe', ok(_)). Author = label('Author-is-John-Doe', ok(_)).
==== ----
or by a combination of these 3 attributes: or by a combination of these 3 attributes:
.rules.pl `rules.pl`
[caption=""] [source,prolog]
==== ----
submit_rule(submit(Author)) :- submit_rule(submit(Author)) :-
Author = label('Author-is-John-Doe', need(_)). Author = label('Author-is-John-Doe', need(_)).
submit_rule(submit(Author)) :- submit_rule(submit(Author)) :-
gerrit:commit_author(_, 'John Doe', 'john.doe@example.com'), gerrit:commit_author(_, 'John Doe', 'john.doe@example.com'),
Author = label('Author-is-John-Doe', ok(_)). Author = label('Author-is-John-Doe', ok(_)).
==== ----
=== Example 7: Make change submittable if commit message starts with "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
@@ -540,9 +542,9 @@ options:
Let's implement both options: Let's implement both options:
.rules.pl `rules.pl`
[caption=""] [source,prolog]
==== ----
submit_rule(submit(Fix)) :- submit_rule(submit(Fix)) :-
Fix = label('Commit-Message-starts-with-Fix', need(_)). Fix = label('Commit-Message-starts-with-Fix', need(_)).
@@ -552,7 +554,7 @@ Let's implement both options:
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).
==== ----
NOTE: The `name/2` embedded predicate is used to convert a string symbol into a NOTE: The `name/2` embedded predicate is used to convert a string symbol into a
list of characters. A string `abc` is converted into a list of characters `[97, list of characters. A string `abc` is converted into a list of characters `[97,
@@ -564,23 +566,24 @@ The `starts_with` predicate is self explaining.
Using the `gerrit:commit_message_matches` predicate is probably more efficient: Using the `gerrit:commit_message_matches` predicate is probably more efficient:
.rules.pl `rules.pl`
[caption=""] [source,prolog]
==== ----
submit_rule(submit(Fix)) :- submit_rule(submit(Fix)) :-
Fix = label('Commit-Message-starts-with-Fix', need(_)). Fix = label('Commit-Message-starts-with-Fix', need(_)).
submit_rule(submit(Fix)) :- submit_rule(submit(Fix)) :-
gerrit:commit_message_matches('^Fix '), gerrit:commit_message_matches('^Fix '),
Fix = label('Commit-Message-starts-with-Fix', ok(_)). Fix = label('Commit-Message-starts-with-Fix', ok(_)).
==== ----
The previous example could also be written so that it first checks if the commit 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 message starts with 'Fix '. If true then it sets OK for that category and stops
further backtracking by using the cut `!` operator: further backtracking by using the cut `!` operator:
.rules.pl
[caption=""] `rules.pl`
==== [source,prolog]
----
submit_rule(submit(Fix)) :- submit_rule(submit(Fix)) :-
gerrit:commit_message_matches('^Fix '), gerrit:commit_message_matches('^Fix '),
Fix = label('Commit-Message-starts-with-Fix', ok(_)), Fix = label('Commit-Message-starts-with-Fix', ok(_)),
@@ -589,7 +592,7 @@ further backtracking by using the cut `!` operator:
% Message does not start with 'Fix ' so Fix is needed to submit % Message does not start with 'Fix ' so Fix is needed to submit
submit_rule(submit(Fix)) :- submit_rule(submit(Fix)) :-
Fix = label('Commit-Message-starts-with-Fix', need(_)). Fix = label('Commit-Message-starts-with-Fix', need(_)).
==== ----
== The default submit policy == 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.
@@ -606,13 +609,13 @@ done in one of the following ways:
The default submit rule with the two default categories, `Code-Review` and The default submit rule with the two default categories, `Code-Review` and
`Verified`, can be implemented as: `Verified`, can be implemented as:
.rules.pl `rules.pl`
[caption=""] [source,prolog]
==== ----
submit_rule(submit(V, CR)) :- submit_rule(submit(V, CR)) :-
gerrit:max_with_block(-2, 2, 'Code-Review', CR), gerrit:max_with_block(-2, 2, 'Code-Review', CR),
gerrit:max_with_block(-1, 1, 'Verified', V). gerrit:max_with_block(-1, 1, 'Verified', V).
==== ----
Once this implementation is understood it can be customized to implement Once this implementation is understood it can be customized to implement
project specific submit rules. Note, that this implementation hardcodes project specific submit rules. Note, that this implementation hardcodes
@@ -625,25 +628,27 @@ understand.
=== Reusing the default submit policy === Reusing the default submit policy
To get results of Gerrit's default submit policy we use the To get results of Gerrit's default submit policy we use the
`gerrit:default_submit` predicate. The `gerrit:default_submit(X)` includes all `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: categories from the database. This means that if we write a submit rule like
this:
.rules.pl `rules.pl`
[caption=""] [source,prolog]
==== ----
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
it 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
logic. The following pattern illustrates this technique: logic. The following pattern illustrates this technique:
.rules.pl `rules.pl`
[caption=""] [source,prolog]
==== ----
submit_rule(S) :- gerrit:default_submit(R), project_specific_policy(R, S). submit_rule(S) :- gerrit:default_submit(R), project_specific_policy(R, S).
project_specific_policy(R, S) :- ... project_specific_policy(R, S) :- ...
==== ----
In the following examples both styles will be shown. In the following examples both styles will be shown.
@@ -659,9 +664,9 @@ 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
exists or with status `need` if it doesn't exist. exists or with status `need` if it doesn't exist.
.rules.pl `rules.pl`
[caption=""] [source,prolog]
==== ----
submit_rule(S) :- submit_rule(S) :-
gerrit:default_submit(X), gerrit:default_submit(X),
X =.. [submit | Ls], X =.. [submit | Ls],
@@ -674,11 +679,11 @@ exists or with status `need` if it doesn't exist.
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]).
==== ----
This example uses the `univ` operator `=..` to "unpack" the result of the This example uses the `univ` operator `=..` to "unpack" the result of the
default_submit, which is a structure of the form `submit(label('Code-Review', default_submit, which is a structure of the form `submit(label('Code-Review',
ok(_)), label('Verified', need(_)) ...)` into a list like `[submit, ok(_)), label('Verified', need(_)), ...)` into a list like `[submit,
label('Code-Review', ok(_)), label('Verified', need(_)), ...]`. Then we label('Code-Review', ok(_)), label('Verified', need(_)), ...]`. Then we
process the tail of the list (the list of labels) as a Prolog list, which is process the tail of the list (the list of labels) as a Prolog list, which is
much easier than processing a structure. In the end we use the same `univ` much easier than processing a structure. In the end we use the same `univ`
@@ -697,9 +702,9 @@ predicate before the `cut` fails.
Let's implement the same submit rule the other way, without reusing the Let's implement the same submit rule the other way, without reusing the
`gerrit:default_submit`: `gerrit:default_submit`:
.rules.pl `rules.pl`
[caption=""] [source,prolog]
==== ----
submit_rule(submit(CR, V)) :- submit_rule(submit(CR, V)) :-
base(CR, V), base(CR, V),
CR = label(_, ok(Reviewer)), CR = label(_, ok(Reviewer)),
@@ -714,7 +719,7 @@ Let's implement the same submit rule the other way, without reusing the
base(CR, V) :- base(CR, V) :-
gerrit:max_with_block(-2, 2, 'Code-Review', CR), gerrit:max_with_block(-2, 2, 'Code-Review', CR),
gerrit:max_with_block(-1, 1, 'Verified', V). gerrit:max_with_block(-1, 1, 'Verified', V).
==== ----
The latter implementation is probably easier to understand and the code looks The latter implementation is probably easier to understand and the code looks
cleaner. Note, however, that the latter implementation will always return the cleaner. Note, however, that the latter implementation will always return the
@@ -730,28 +735,27 @@ invokes the `gerrit:default_submit` and then modifies its result.
Which of these two behaviors is desired will always depend on how a particular Which of these two behaviors is desired will always depend on how a particular
Gerrit server is managed. 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
code review. We want to remove the `Verified` category from this project so code review. We want to remove the `Verified` category from this project so
that `Code-Review+2` is the only criteria for a change to become submittable. 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`: This is quite simple without reusing the `gerrit:default_submit`:
.rules.pl `rules.pl`
[caption=""] [source,prolog]
==== ----
submit_rule(submit(CR)) :- submit_rule(submit(CR)) :-
gerrit:max_with_block(-2, 2, 'Code-Review', CR). gerrit:max_with_block(-2, 2, 'Code-Review', CR).
==== ----
Implementing the same rule by reusing `gerrit:default_submit` is a bit more complex: Implementing the same rule by reusing `gerrit:default_submit` is a bit more complex:
.rules.pl `rules.pl`
[caption=""] [source,prolog]
==== ----
submit_rule(S) :- submit_rule(S) :-
gerrit:default_submit(X), gerrit:default_submit(X),
X =.. [submit | Ls], X =.. [submit | Ls],
@@ -761,22 +765,22 @@ Implementing the same rule by reusing `gerrit:default_submit` is a bit more comp
remove_verified_category([], []). remove_verified_category([], []).
remove_verified_category([label('Verified', _) | T], R) :- remove_verified_category(T, R), !. remove_verified_category([label('Verified', _) | T], R) :- remove_verified_category(T, R), !.
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 10: Combine examples 8 and 9 === Example 10: Combine examples 8 and 9
In this example we want to both remove the verified and have the four eyes In this example we want to both remove the verified and have the four eyes
principle. This means we want a combination of examples 7 and 8. principle. This means we want a combination of examples 7 and 8.
.rules.pl `rules.pl`
[caption=""] [source,prolog]
==== ----
submit_rule(S) :- submit_rule(S) :-
gerrit:default_submit(X), gerrit:default_submit(X),
X =.. [submit | Ls], X =.. [submit | Ls],
remove_verified_category(Ls, R1), remove_verified_category(Ls, R1),
add_non_author_approval(R1, R), add_non_author_approval(R1, R),
S =.. [submit | R]. S =.. [submit | R].
==== ----
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.
@@ -784,9 +788,9 @@ same as defined in the previous two examples.
Without reusing the `gerrit:default_submit` the same example may be implemented Without reusing the `gerrit:default_submit` the same example may be implemented
as: as:
.rules.pl `rules.pl`
[caption=""] [source,prolog]
==== ----
submit_rule(submit(CR)) :- submit_rule(submit(CR)) :-
base(CR), base(CR),
CR = label(_, ok(Reviewer)), CR = label(_, ok(Reviewer)),
@@ -800,7 +804,7 @@ as:
base(CR) :- base(CR) :-
gerrit:max_with_block(-2, 2, 'Code-Review', 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
@@ -808,27 +812,27 @@ one project. In this example we do the same but we want to remove the `Verified`
category from all projects. This means we have to implement `submit_filter` and category from all projects. This means we have to implement `submit_filter` and
we have to do that in the `rules.pl` of the `All-Projects` project. we have to do that in the `rules.pl` of the `All-Projects` project.
.rules.pl `rules.pl`
[caption=""] [source,prolog]
==== ----
submit_filter(In, Out) :- submit_filter(In, Out) :-
In =.. [submit | Ls], In =.. [submit | Ls],
remove_verified_category(Ls, R), remove_verified_category(Ls, R),
Out =.. [submit | R]. Out =.. [submit | R].
remove_verified_category([], []). remove_verified_category([], []).
remove_verified_category([label('Verified', _) | T], R) :- remove_verified_category([label('Verified', _) | T], R) :- remove_verified_category(T, R), !.
remove_verified_category(T, R), !.
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: On release branches require DrNo in addition to project rules === 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 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')`. branches. To mark a branch as a release branch we use
`drno('refs/heads/branch')`.
.rules.pl `rules.pl`
[caption=""] [source,prolog]
==== ----
drno('refs/heads/master'). drno('refs/heads/master').
drno('refs/heads/stable-2.3'). drno('refs/heads/stable-2.3').
drno('refs/heads/stable-2.4'). drno('refs/heads/stable-2.4').
@@ -844,23 +848,25 @@ branches. To mark a branch as a release branch we use `drno('refs/heads/branch')
Out =.. [submit, DrNo | I]. Out =.. [submit, DrNo | I].
submit_filter(In, Out) :- In = Out. submit_filter(In, Out) :- In = Out.
==== ----
=== Example 13: 1+1=2 Code-Review === 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
change submittable if the total score is 2 or higher. make the change submittable if the total score is `2` or higher.
The code in this example is very similar to Example 8, with the addition of findall/3 The code in this example is very similar to Example 8, with the addition of
and gerrit:remove_label. `findall/3` and `gerrit:remove_label`.
The findall/3 embedded predicate is used to form a list of all objects that satisfy a
specified Goal. In this example it is used to get a list of all the 'Code-Review' scores.
gerrit:remove_label is a built-in helper that is implemented similarly to the
'remove_verified_category' as seen in the previous example.
.rules.pl The `findall/3` embedded predicate is used to form a list of all objects that
[caption=""] satisfy a specified Goal. In this example it is used to get a list of all the
==== `Code-Review` scores. `gerrit:remove_label` is a built-in helper that is
implemented similarly to the `remove_verified_category` as seen in the previous
example.
`rules.pl`
[source,prolog]
----
sum_list([], 0). sum_list([], 0).
sum_list([H | Rest], Sum) :- sum_list(Rest,Tmp), Sum is H + Tmp. sum_list([H | Rest], Sum) :- sum_list(Rest,Tmp), Sum is H + Tmp.
@@ -879,13 +885,13 @@ gerrit:remove_label is a built-in helper that is implemented similarly to the
gerrit:remove_label(Ls,label('Code-Review',_),NoCR), gerrit:remove_label(Ls,label('Code-Review',_),NoCR),
add_category_min_score(NoCR,'Code-Review', 2, Labels), add_category_min_score(NoCR,'Code-Review', 2, Labels),
S =.. [submit | Labels]. S =.. [submit | Labels].
==== ----
Implementing the same example without using `gerrit:default_submit`: Implementing the same example without using `gerrit:default_submit`:
.rules.pl `rules.pl`
[caption=""] [source,prolog]
==== ----
submit_rule(submit(CR, V)) :- submit_rule(submit(CR, V)) :-
sum(2, 'Code-Review', CR), sum(2, 'Code-Review', CR),
gerrit:max_with_block(-1, 1, 'Verified', V). gerrit:max_with_block(-1, 1, 'Verified', V).
@@ -906,21 +912,21 @@ Implementing the same example without using `gerrit:default_submit`:
sum_list(List, Sum) :- sum_list(List, 0, Sum). 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([X|T], Y, S) :- Z is X + Y, sum_list(T, Z, S).
sum_list([], S, S). sum_list([], S, S).
==== ----
=== Example 14: Master and apprentice === 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`).
The code first checks if the commit author is in the apprentice database. The code first checks if the commit author is in the apprentice database.
If the commit is done by an apprentice, it will check if there is a +2 If the commit is done by an `apprentice`, it will check if there is a `+2`
review by the associated `master`. review by the associated `master`.
.rules.pl `rules.pl`
[caption=""] [source,prolog]
==== ----
% master_apprentice(Master, Apprentice). % master_apprentice(Master, Apprentice).
% Extend this with appropriate user-id's for your master/apprentice setup. % Extend this with appropriate user-id for your master/apprentice setup.
master_apprentice(user(1000064), user(1000000)). master_apprentice(user(1000064), user(1000000)).
submit_rule(S) :- submit_rule(S) :-
@@ -942,17 +948,17 @@ review by the associated `master`.
check_master_approval(S1, S2, Master). check_master_approval(S1, S2, Master).
add_apprentice_master(S, S). add_apprentice_master(S, S).
==== ----
=== Example 15: 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 `Only-Author-Can-Submit` for any user
not the author of the patch. This effectively blocks all users except the author that is not the author of the patch. This effectively blocks all users except
from submitting the change. This could result in an impossible situation if the the author from submitting the change. This could result in an impossible
author does not have permissions for submitting the change. situation if the author does not have permissions for submitting the change.
.rules.pl `rules.pl`
[caption=""] [source,prolog]
==== ----
submit_rule(S) :- submit_rule(S) :-
gerrit:default_submit(In), gerrit:default_submit(In),
In =.. [submit | Ls], In =.. [submit | Ls],
@@ -964,8 +970,8 @@ author does not have permissions for submitting the change.
gerrit:current_user(Id), gerrit:current_user(Id),
!. !.
only_allow_author_to_submit(S1, [label('Patchset-Author', need(_)) | S1]). only_allow_author_to_submit(S1, [label('Only-Author-Can-Submit', need(_)) | S1]).
==== ----
== Examples - Submit Type == Examples - Submit Type
The following examples show how to implement own submit type rules. The following examples show how to implement own submit type rules.
@@ -975,48 +981,47 @@ This example sets the `Cherry Pick` submit type for all changes. It overrides
whatever is set as project default submit type. whatever is set as project default submit type.
rules.pl rules.pl
[caption=""] [source,prolog]
==== ----
submit_type(cherry_pick). submit_type(cherry_pick).
==== ----
[[SubmitTypePerBranch]] [[SubmitTypePerBranch]]
=== Example 2: `Fast Forward Only` for all `refs/heads/stable*` branches === Example 2: `Fast Forward Only` for all `refs/heads/stable*` branches
For all `refs/heads/stable.*` branches we would like to enforce the `Fast For all `refs/heads/stable*` branches we would like to enforce the `Fast
Forward Only` submit type. A reason for this decision may be a need to never Forward Only` submit type. A reason for this decision may be a need to never
break the build in the stable branches. For all other branches, those not break the build in the stable branches. For all other branches, those not
matching the `refs/heads/stable.*` pattern, we would like to use the project's matching the `refs/heads/stable*` pattern, we would like to use the project's
default submit type as defined on the project settings page. default submit type as defined on the project settings page.
.rules.pl `rules.pl`
[caption=""] [source,prolog]
==== ----
submit_type(fast_forward_only) :- submit_type(fast_forward_only) :-
gerrit:change_branch(B), regex_matches('refs/heads/stable.*', B), gerrit:change_branch(B), regex_matches('refs/heads/stable.*', B),
!. !.
submit_type(T) :- gerrit:project_default_submit_type(T) submit_type(T) :- gerrit:project_default_submit_type(T)
==== ----
The first `submit_type` predicate defines the `Fast Forward Only` submit type The first `submit_type` predicate defines the `Fast Forward Only` submit type
for `refs/heads/stable.*` branches. The second `submit_type` predicate returns for `refs/heads/stable.*` branches. The second `submit_type` predicate returns
the project's default submit type. the project's default submit type.
=== Example 3: Don't require `Fast Forward Only` if only documentation was changed === Example 3: Don't require `Fast Forward Only` if only documentation was changed
Like in the previous example we want the `Fast Forward Only` submit type for Like in the previous example we want the `Fast Forward Only` submit type for the
the `refs/heads/stable*` branches. However, if only documentation was changed `refs/heads/stable*` branches. However, if only documentation was changed
(only `*.txt` files), then we allow project's default submit type for such (only `*.txt` files), then we allow project's default submit type for such
changes. changes.
.rules.pl `rules.pl`
[caption=""] [source,prolog]
==== ----
submit_type(fast_forward_only) :- submit_type(fast_forward_only) :-
gerrit:commit_delta('(?<!\.txt)$'), gerrit:commit_delta('(?<!\.txt)$'),
gerrit:change_branch(B), regex_matches('refs/heads/stable.*', B), gerrit:change_branch(B), regex_matches('refs/heads/stable.*', B),
!. !.
submit_type(T) :- gerrit:project_default_submit_type(T) submit_type(T) :- gerrit:project_default_submit_type(T)
==== ----
The `gerrit:commit_delta('(?<!\.txt)$')` succeeds if the change contains a file The `gerrit:commit_delta('(?<!\.txt)$')` succeeds if the change contains a file
whose name doesn't end with `.txt` The rest of this rule is same like in the whose name doesn't end with `.txt` The rest of this rule is same like in the