79b08cc06b
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>
1064 lines
39 KiB
Plaintext
1064 lines
39 KiB
Plaintext
Gerrit Code Review - Prolog Submit Rules Cookbook
|
|
=================================================
|
|
|
|
Submit Rule
|
|
-----------
|
|
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
|
|
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',
|
|
'Verified+1' and has neither 'Code-Review-2' nor 'Verified-1' to become
|
|
submittable.
|
|
|
|
While this rule is a good default, there are projects which need more
|
|
flexibility for defining when a change is submittable. In Gerrit, it is
|
|
possible to use Prolog based rules to provide project specific submit rules and
|
|
replace the default submit rules. Using Prolog based rules, project owners can
|
|
define a set of criteria which must be fulfilled for a change to become
|
|
submittable. For a change that is not submittable, the set of needed criteria
|
|
is displayed in the Gerrit UI.
|
|
|
|
NOTE: Loading and executing Prolog submit rules may be disabled by setting
|
|
`rules.enabled=false` in the Gerrit config file (see
|
|
link:config-gerrit.html#_a_id_rules_a_section_rules[rules section])
|
|
|
|
link:https://groups.google.com/d/topic/repo-discuss/wJxTGhlHZMM/discussion[This
|
|
discussion thread] explains why Prolog was chosen for the purpose of writing
|
|
project specific submit rules.
|
|
link:http://gerrit-documentation.googlecode.com/svn/ReleaseNotes/ReleaseNotes-2.2.2.html[Gerrit
|
|
2.2.2 ReleaseNotes] introduces Prolog support in Gerrit.
|
|
|
|
Submit Type
|
|
-----------
|
|
A 'Submit Type' is a strategy that is used on submit to integrate the
|
|
change into the destination branch. Supported submit types are:
|
|
|
|
* `Fast Forward Only`
|
|
* `Merge If Necessary`
|
|
* `Merge Always`
|
|
* `Cherry Pick`
|
|
* `Rebase If Necessary`
|
|
|
|
'Submit Type' is a project global setting. This means that the same submit type
|
|
is used for all changes of one project.
|
|
|
|
Projects which need more flexibility in choosing, or enforcing, a submit type
|
|
can use Prolog based submit type which replaces the project's default submit
|
|
type.
|
|
|
|
Prolog based submit type computes a submit type for each change. The computed
|
|
submit type is shown on the change screen for each change.
|
|
|
|
Prolog Language
|
|
---------------
|
|
This document is not a complete Prolog tutorial.
|
|
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
|
|
some elements of Prolog that are necessary to understand the provided examples.
|
|
|
|
Prolog in Gerrit
|
|
----------------
|
|
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]
|
|
project. Gerrit embeds the prolog-cafe library and can interpret Prolog programs at
|
|
runtime.
|
|
|
|
Interactive Prolog Cafe Shell
|
|
-----------------------------
|
|
For interactive testing and playing with Prolog, Gerrit provides the
|
|
link:pgm-prolog-shell.html[prolog-shell] program which opens an interactive
|
|
Prolog interpreter shell.
|
|
|
|
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].
|
|
|
|
SWI-Prolog
|
|
----------
|
|
Instead of using the link:pgm-prolog-shell.html[prolog-shell] program one can
|
|
also use the link:http://www.swi-prolog.org/[SWI-Prolog] environment. It
|
|
provides a better shell interface and a graphical source-level debugger.
|
|
|
|
The rules.pl file
|
|
-----------------
|
|
This section explains how to create and edit project specific submit rules. How
|
|
to actually write the submit rules is explained in the next section.
|
|
|
|
Project specific submit rules are stored in the `rules.pl` file in the
|
|
`refs/meta/config` branch of that project. Therefore, we need to fetch and
|
|
checkout the `refs/meta/config` branch in order to create or edit the `rules.pl`
|
|
file:
|
|
|
|
====
|
|
$ git fetch origin refs/meta/config:config
|
|
$ git checkout config
|
|
... edit or create the rules.pl file
|
|
$ git add rules.pl
|
|
$ git commit -m "My submit rules"
|
|
$ git push origin HEAD:refs/meta/config
|
|
====
|
|
|
|
[[HowToWriteSubmitRules]]
|
|
How to write submit rules
|
|
-------------------------
|
|
Whenever Gerrit needs to evaluate submit rules for a change `C` from project `P` it
|
|
will first initialize the embedded Prolog interpreter by:
|
|
|
|
* consulting a set of facts about the change `C`
|
|
* consulting the `rules.pl` from the project `P`
|
|
|
|
Conceptually we can imagine that Gerrit adds a set of facts about the change
|
|
`C` on top of the `rules.pl` file and then consults it. The set of facts about
|
|
the change `C` will look like:
|
|
|
|
====
|
|
:- package gerrit. <1>
|
|
|
|
commit_author(user(1000000), 'John Doe', 'john.doe@example.com'). <2>
|
|
commit_committer(user(1000000), 'John Doe', 'john.doe@example.com'). <3>
|
|
commit_message('Add plugin support to Gerrit'). <4>
|
|
...
|
|
====
|
|
|
|
<1> Gerrit will provide its facts in a package named `gerrit`. This means we
|
|
have to use qualified names when writing our code and referencing these facts.
|
|
For example: `gerrit:commit_author(ID, N, M)`
|
|
<2> user ID, full name and email address of the commit author
|
|
<3> user ID, full name and email address of the commit committer
|
|
<4> commit message
|
|
|
|
A complete set of facts which Gerrit provides about the change is listed in the
|
|
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`
|
|
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
|
|
needed criteria for the change to become submittable. This means that Gerrit has an
|
|
expectation on the format and value of the result of the `submit_rule` predicate
|
|
which is expected to be a `submit` term of the following format:
|
|
|
|
====
|
|
submit(label(label-name, status) [, label(label-name, status)]*)
|
|
====
|
|
|
|
where `label-name` is usually `'Code-Review'` or `'Verified'` but could also
|
|
be any other string (see examples below). The `status` is one of:
|
|
|
|
* `ok(user(ID))` or just `ok(_)` if user info is not important. This status is
|
|
used to tell that this label/category has been met.
|
|
* `need(_)` is used to tell that this label/category is needed for change to
|
|
become submittable
|
|
* `reject(user(ID))` or just `reject(_)`. This status is used to tell that label/category
|
|
is blocking change submission
|
|
* `impossible(_)` is used when the logic knows that the change cannot be submitted as-is.
|
|
Administrative intervention is probably required. This is meant for cases
|
|
where the logic requires members of "FooEng" to score "Code-Review +2" on a
|
|
change, but nobody is in group "FooEng". It is to hint at permissions
|
|
misconfigurations.
|
|
* `may(_)` allows expression of approval categories that are optional, i.e.
|
|
could either be set or unset without ever influencing whether the change
|
|
could be submitted.
|
|
|
|
NOTE: For a change to be submittable all `label` terms contained in the returned
|
|
`submit` term must have either `ok` or `may` status.
|
|
|
|
IMPORTANT: Gerrit will let the Prolog engine continue searching for solutions of
|
|
the `submit_rule(X)` query until it finds the first one where all labels in the
|
|
return result have either status `ok` or `may` or there are no more solutions.
|
|
If a solution where all labels have status `ok` is found then all previously
|
|
found solutions are ignored. Otherwise, all labels names with status `need`
|
|
from all solutions will be displayed in the UI indicating the set of conditions
|
|
needed for the change to become submittable.
|
|
|
|
Here some examples of possible return values from the `submit_rule` predicate:
|
|
|
|
====
|
|
submit(label('Code-Review', ok(_))) <1>
|
|
submit(label('Code-Review', ok(_)), label('Verified', reject(_))) <2>
|
|
submit(label('Author-is-John-Doe', need(_)) <3>
|
|
====
|
|
|
|
<1> label `'Code-Review'` is met. As there are no other labels in the
|
|
return result, the change is submittable.
|
|
<2> label `'Verified'` is rejected. Change is not 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
|
|
to the implementor of the `submit_rule` to return `label('Author-is-John-Doe',
|
|
ok(_))` when this criteria is met. Most likely, it will have to match
|
|
against `gerrit:commit_author` in order to 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
|
|
change that are already provided by Gerrit.
|
|
|
|
Another aspect of the return result from the `submit_rule` predicate is that
|
|
Gerrit uses it to decide which set of labels to display on the change review
|
|
screen for voting. If the return result contains label `'ABC'` and if the label
|
|
`'ABC'` is one of the (global) voting categories then voting for the label
|
|
`'ABC'` will be displayed. Otherwise, it is not displayed. Note that we don't
|
|
need a (global) voting category for each label contained in the result of
|
|
`submit_rule` predicate. For example, the decision whether `'Author-is-John-Doe'`
|
|
label is met will probably not be made by explicit voting but, instead, by
|
|
inspecting the facts about the change.
|
|
|
|
[[SubmitFilter]]
|
|
Submit Filter
|
|
-------------
|
|
Another mechanism of changing the default submit rules is to implement the
|
|
`submit_filter/2` predicate. While Gerrit will search for the `submit_rule` only
|
|
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,
|
|
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
|
|
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
|
|
of the `submit_rule`. Therefore, the `submit_filter` predicate has two
|
|
parameters:
|
|
|
|
====
|
|
submit_filter(In, Out) :- ...
|
|
====
|
|
|
|
Gerrit will invoke `submit_filter` with the `In` parameter containing a `submit`
|
|
structure produced by the `submit_rule` and will take the value of the `Out`
|
|
parameter as the result.
|
|
|
|
The `Out` value of a `submit_filter` will become the `In` value for the
|
|
next `submit_filter` in the parent line. The value of the `Out` parameter
|
|
of the top-most `submit_filter` is the final result of the submit rule that
|
|
is used to decide whether a change is submittable or not.
|
|
|
|
IMPORTANT: `submit_filter` is a mechanism for Gerrit administrators to implement
|
|
and enforce submit rules that would apply to all projects while `submit_rule` is
|
|
a mechanism for project owners to implement project specific submit rules.
|
|
However, project owners who own several projects could also make use of
|
|
`submit_filter` by using a common parent project for all their projects and
|
|
implementing the `submit_filter` in this common parent project. This way they
|
|
can avoid implementing the same `submit_rule` in all their projects.
|
|
|
|
The following "drawing" illustrates the order of the invocation and the chaining
|
|
of the results of the `submit_rule` and `submit_filter` predicates.
|
|
====
|
|
All-Projects
|
|
^ submit_filter(B, S) :- ... <4>
|
|
|
|
|
Parent-3
|
|
^ <no submit filter here>
|
|
|
|
|
Parent-2
|
|
^ submit_filter(A, B) :- ... <3>
|
|
|
|
|
Parent-1
|
|
^ submit_filter(X, A) :- ... <2>
|
|
|
|
|
MyProject
|
|
submit_rule(X) :- ... <1>
|
|
====
|
|
|
|
<1> The `submit_rule` of `MyProject` is invoked first.
|
|
<2> The result `X` is filtered through the `submit_filter` from the `Parent-1`
|
|
project.
|
|
<3> The result of `submit_filter` from `Parent-1` project is filtered by the
|
|
`submit_filter` in the `Parent-2` project. Since `Parent-3` project doesn't have
|
|
a `submit_filter` it is skipped.
|
|
<4> The result of `submit_filter` from `Parent-2` project is filtered by the
|
|
`submit_filter` in the `All-Projects` project. The value in `S` is the final
|
|
value of the submit rule evaluation.
|
|
|
|
NOTE: If `MyProject` doesn't define its own `submit_rule` Gerrit will invoke the
|
|
default implementation of submit rule that is named `gerrit:default_submit` and
|
|
its result will be filtered as described above.
|
|
|
|
How to write submit type
|
|
------------------------
|
|
Writing custom submit type logic in Prolog is the similar top
|
|
xref:HowToWriteSubmitRules[writing submit rules]. The only difference is that
|
|
one has to implement a `submit_type` predicate (instead of the `submit_rule`)
|
|
and that the return result of the `submit_type` has to be an atom that
|
|
represents one of the supported submit types:
|
|
|
|
* `fast_forward_only`
|
|
* `merge_if_necessary`
|
|
* `merge_always`
|
|
* `cherry_pick`
|
|
* `rebase_if_necessary`
|
|
|
|
Submit Type Filter
|
|
------------------
|
|
Submit type filter works the same way as the xref:SubmitFilter[Submit Filter]
|
|
where the name of the filter predicate is `submit_type_filter`.
|
|
|
|
====
|
|
submit_type_filter(In, Out).
|
|
====
|
|
|
|
Gerrit will invoke `submit_type_filter` with the `In` parameter containing a
|
|
result of the `submit_type` and will take the value of the `Out` parameter as
|
|
the result.
|
|
|
|
[[TestingSubmitRules]]
|
|
Testing submit rules
|
|
--------------------
|
|
The prolog environment running the `submit_rule` is loaded with state describing the
|
|
change that is being evaluated. The easiest way to load this state is to test your
|
|
`submit_rule` against a real change on a running gerrit instance. The command
|
|
link:cmd-test-submit-rule.html[test-submit-rule] loads a specific change 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
|
|
====
|
|
|
|
Prolog vs Gerrit plugin for project specific submit rules
|
|
---------------------------------------------------------
|
|
Since version 2.5 Gerrit supports plugins and extension points. A plugin or an
|
|
extension point could also be used as another means to provide custom submit
|
|
rules. One could ask for a guideline when to use Prolog based submit rules and
|
|
when to go for writing a new plugin. Writing a Prolog program is usually much
|
|
faster than writing a Gerrit plugin. Prolog based submit rules can be pushed
|
|
to a project by project owners while Gerrit plugins could only be installed by
|
|
Gerrit administrators. In addition, Prolog based submit rules can be pushed
|
|
for review by pushing to `refs/for/refs/meta/config` branch.
|
|
|
|
On the other hand, Prolog based submit rules get a limited amount of facts about
|
|
the change exposed to them. Gerrit plugins get full access to Gerrit internals
|
|
and can potentially check more things than Prolog based rules.
|
|
|
|
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.
|
|
|
|
Examples - Submit Rule
|
|
----------------------
|
|
The following examples should serve as a cookbook for developing own submit rules.
|
|
Some of them are too trivial to be used in production and their only purpose is
|
|
to provide step by step introduction and understanding.
|
|
|
|
Some of the examples will implement the `submit_rule` and some will implement
|
|
the `submit_filter` just to show both possibilities. Remember that
|
|
`submit_rule` is only invoked from the current project and `submit_filter` is
|
|
invoked from all parent projects. This is the most important fact in deciding
|
|
whether to implement `submit_rule` or `submit_filter`.
|
|
|
|
Example 1: Make every change submittable
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
Let's start with a most trivial example where we would make every change submittable
|
|
regardless of the votes it has:
|
|
|
|
.rules.pl
|
|
[caption=""]
|
|
====
|
|
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
|
|
making every change submittable. Note that, in this case, the Gerrit UI will not show
|
|
the UI for voting for the standard `'Code-Review'` and `'Verified'` categories as labels
|
|
with these names are not part of the return result. The `'Any-Label-Name'` could really
|
|
be any string.
|
|
|
|
Example 2: Every change submittable and voting in the standard categories possible
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
This is continuation of the previous example where, in addition, to making
|
|
every change submittable we want to enable voting in the standard
|
|
`'Code-Review'` and `'Verified'` categories.
|
|
|
|
.rules.pl
|
|
[caption=""]
|
|
====
|
|
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.
|
|
Voting in the standard labels will be shown in the UI as the standard label names are
|
|
included in the return result.
|
|
|
|
Example 3: Nothing is submittable
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
This example shows how to make all changes non-submittable regardless of the
|
|
votes they have.
|
|
|
|
.rules.pl
|
|
[caption=""]
|
|
====
|
|
submit_rule(submit(R)) :-
|
|
R = label('Any-Label-Name', reject(_)).
|
|
====
|
|
|
|
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
|
|
change to become submittable as we return no labels with status `need`.
|
|
|
|
Example 4: Nothing is submittable but UI shows several 'Need ...' criteria
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
In this example no change is submittable but here we show how to present 'Need
|
|
<label>' information to the user in the UI.
|
|
|
|
.rules.pl
|
|
[caption=""]
|
|
====
|
|
% In the UI this will show: Need Any-Label-Name
|
|
submit_rule(submit(N)) :-
|
|
N = label('Any-Label-Name', need(_)).
|
|
|
|
% We could define more "need" labels by adding more rules
|
|
submit_rule(submit(N)) :-
|
|
N = label('Another-Label-Name', need(_)).
|
|
|
|
% or by providing more than one need label in the same rule
|
|
submit_rule(submit(NX, NY)) :-
|
|
NX = label('X-Label-Name', need(_)),
|
|
NY = label('Y-Label-Name', need(_)).
|
|
====
|
|
|
|
In the UI this will show:
|
|
****
|
|
* Need Any-Label-Name
|
|
* Need Another-Label-Name
|
|
* Need X-Label-Name
|
|
* Need Y-Label-Name
|
|
****
|
|
|
|
From the example above we can see a few more things:
|
|
|
|
* comment in Prolog starts with the `%` character
|
|
* there could be multiple `submit_rule` predicates. Since Prolog, by default, tries to find
|
|
all solutions for a query, the result will be union of all solutions.
|
|
Therefore, we see all 4 `need` labels in the UI.
|
|
|
|
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
|
|
have status `ok` then Gerrit will not show any labels with the `need` status from
|
|
any of the previous `submit_rule(X)` solutions.
|
|
|
|
.rules.pl
|
|
[caption=""]
|
|
====
|
|
submit_rule(submit(N)) :-
|
|
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 second rule.
|
|
|
|
The same is valid if the two rules are swapped:
|
|
|
|
.rules.pl
|
|
[caption=""]
|
|
====
|
|
submit_rule(submit(OK)) :-
|
|
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.
|
|
|
|
Example 6: Make change submittable if commit author is "John Doe"
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
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
|
|
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
|
|
the UI to clearly indicate to the user what is needed for a change to become
|
|
submittable:
|
|
|
|
.rules.pl
|
|
[caption=""]
|
|
====
|
|
submit_rule(submit(Author)) :-
|
|
Author = label('Author-is-John-Doe', need(_)).
|
|
====
|
|
|
|
This will show:
|
|
****
|
|
* Need Author-is-John-Doe
|
|
****
|
|
|
|
in the UI but no change will be submittable yet. Let's add another rule:
|
|
|
|
.rules.pl
|
|
[caption=""]
|
|
====
|
|
submit_rule(submit(Author)) :-
|
|
Author = label('Author-is-John-Doe', need(_)).
|
|
|
|
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
|
|
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
|
|
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
|
|
solution. The UI will show 'Need Author-is-John-Doe' but, as expected, the
|
|
change will not be submittable.
|
|
|
|
Instead of checking by full name we could also check by the email address:
|
|
|
|
.rules.pl
|
|
[caption=""]
|
|
====
|
|
submit_rule(submit(Author)) :-
|
|
Author = label('Author-is-John-Doe', need(_)).
|
|
|
|
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):
|
|
|
|
.rules.pl
|
|
[caption=""]
|
|
====
|
|
submit_rule(submit(Author)) :-
|
|
Author = label('Author-is-John-Doe', need(_)).
|
|
|
|
submit_rule(submit(Author)) :-
|
|
gerrit:commit_author(user(1000000), _, _),
|
|
Author = label('Author-is-John-Doe', ok(_)).
|
|
====
|
|
|
|
or by a combination of these 3 attributes:
|
|
|
|
.rules.pl
|
|
[caption=""]
|
|
====
|
|
submit_rule(submit(Author)) :-
|
|
Author = label('Author-is-John-Doe', need(_)).
|
|
|
|
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 "Fix "
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
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
|
|
like commit author the commit message is provided as a string symbol which is
|
|
an atom in Prolog terms. When working with an atom we could only match against
|
|
the whole value. To match only part of a string symbol we have, at least, two
|
|
options:
|
|
|
|
* convert the string symbol into a list of characters and then perform
|
|
the "classical" list matching
|
|
* use the `regex_matches/2` or, even more convenient, the
|
|
`gerrit:commit_message_matches/1` predicate
|
|
|
|
Let's implement both options:
|
|
|
|
.rules.pl
|
|
[caption=""]
|
|
====
|
|
submit_rule(submit(Fix)) :-
|
|
Fix = label('Commit-Message-starts-with-Fix', need(_)).
|
|
|
|
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([H|T1], [H|T2]) :- starts_with(T1, T2).
|
|
====
|
|
|
|
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,
|
|
98, 99]`. A double quoted string in Prolog is just a shortcut for creating a
|
|
list of characters. `"abc"` is a shortcut for `[97, 98, 99]`. This is why we use
|
|
double quotes for the `"Trivial Fix"` in the example above.
|
|
|
|
The `starts_with` predicate is self explaining.
|
|
|
|
Using the `gerrit:commit_message_matches` predicate is probably more efficient:
|
|
|
|
.rules.pl
|
|
[caption=""]
|
|
====
|
|
submit_rule(submit(Fix)) :-
|
|
Fix = label('Commit-Message-starts-with-Fix', need(_)).
|
|
|
|
submit_rule(submit(Fix)) :-
|
|
gerrit:commit_message_matches('^Fix '),
|
|
Fix = label('Commit-Message-starts-with-Fix', ok(_)).
|
|
====
|
|
|
|
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.
|
|
However, in real-life scenarios we would rather want to reuse Gerrit's default
|
|
submit policy and extend/change it for our specific purpose. This could be
|
|
done in one of the following ways:
|
|
|
|
* 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
|
|
`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
|
|
[caption=""]
|
|
====
|
|
submit_rule(X) :- gerrit:default_submit(X).
|
|
====
|
|
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
|
|
perform further actions on the return result `X` and apply our specific
|
|
logic. The following pattern illustrates this technique:
|
|
|
|
.rules.pl
|
|
[caption=""]
|
|
====
|
|
submit_rule(S) :- gerrit:default_submit(R), project_specific_policy(R, S).
|
|
|
|
project_specific_policy(R, S) :- ...
|
|
====
|
|
|
|
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
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
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
|
|
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
|
|
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
|
|
exists or with status `need` if it doesn't exist.
|
|
|
|
.rules.pl
|
|
[caption=""]
|
|
====
|
|
submit_rule(S) :-
|
|
gerrit:default_submit(X),
|
|
X =.. [submit | Ls],
|
|
add_non_author_approval(Ls, R),
|
|
S =.. [submit | R].
|
|
|
|
add_non_author_approval(S1, S2) :-
|
|
gerrit:commit_author(A),
|
|
gerrit:commit_label(label('Code-Review', 2), R),
|
|
R \= A, !,
|
|
S2 = [label('Non-Author-Code-Review', ok(R)) | 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
|
|
default_submit, which is a structure of the form `submit(label('Code-Review',
|
|
ok(_)), label('Verified', need(_)) ...)` into a list like `[submit,
|
|
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
|
|
much easier than processing a structure. In the end we use the same `univ`
|
|
operator to convert the resulting list of labels back into a `submit` structure
|
|
which is expected as a return result. The `univ` operator works both ways.
|
|
|
|
In `add_non_author_approval` we use the `cut` operator `!` to prevent Prolog
|
|
from searching for more solutions once the `cut` point is reached. This is
|
|
important because in the second `add_non_author_approval` rule we just add the
|
|
`label('Non-Author-Code-Review', need(_))` without first checking that there
|
|
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
|
|
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
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
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
|
|
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
|
|
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
|
|
[caption=""]
|
|
====
|
|
submit_rule(S) :-
|
|
gerrit:default_submit(X),
|
|
X =.. [submit | Ls],
|
|
remove_verified_category(Ls, R),
|
|
S =.. [submit | R].
|
|
|
|
remove_verified_category([], []).
|
|
remove_verified_category([label('Verified', _) | T], 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
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
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.
|
|
|
|
.rules.pl
|
|
[caption=""]
|
|
====
|
|
submit_rule(S) :-
|
|
gerrit:default_submit(X),
|
|
X =.. [submit | Ls],
|
|
remove_verified_category(Ls, R1),
|
|
add_non_author_approval(R1, R),
|
|
S =.. [submit | R].
|
|
====
|
|
|
|
The `remove_verified_category` and `add_non_author_approval` predicates are the
|
|
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 9, implements `submit_rule` that removes the `Verified` category from
|
|
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
|
|
we have to do that in the `rules.pl` of the `All-Projects` project.
|
|
|
|
.rules.pl
|
|
[caption=""]
|
|
====
|
|
submit_filter(In, Out) :-
|
|
In =.. [submit | Ls],
|
|
remove_verified_category(Ls, R),
|
|
Out =.. [submit | R].
|
|
|
|
remove_verified_category([], []).
|
|
remove_verified_category([label('Verified', _) | T], 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
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
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
|
|
submittable or not. We modify the standard Code-Review to be accumulative, and 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
|
|
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
|
|
[caption=""]
|
|
====
|
|
sum_list([], 0).
|
|
sum_list([H | Rest], Sum) :- sum_list(Rest,Tmp), Sum is H + Tmp.
|
|
|
|
add_category_min_score(In, Category, Min, P) :-
|
|
findall(X, gerrit:commit_label(label(Category,X),R),Z),
|
|
sum_list(Z, Sum),
|
|
Sum >= Min, !,
|
|
P = [label(Category,ok(R)) | In].
|
|
|
|
add_category_min_score(In, Category,Min,P) :-
|
|
P = [label(Category,need(Min)) | In].
|
|
|
|
submit_rule(S) :-
|
|
gerrit:default_submit(X),
|
|
X =.. [submit | Ls],
|
|
gerrit:remove_label(Ls,label('Code-Review',_),NoCR),
|
|
add_category_min_score(NoCR,'Code-Review', 2, Labels),
|
|
S =.. [submit | Labels].
|
|
====
|
|
|
|
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`)
|
|
that must approve all changes done by another user (the `apprentice`).
|
|
|
|
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
|
|
review by the associated `master`.
|
|
|
|
.rules.pl
|
|
[caption=""]
|
|
====
|
|
% master_apprentice(Master, Apprentice).
|
|
% Extend this with appropriate user-id's for your master/apprentice setup.
|
|
master_apprentice(user(1000064), user(1000000)).
|
|
|
|
submit_rule(S) :-
|
|
gerrit:default_submit(In),
|
|
In =.. [submit | Ls],
|
|
add_apprentice_master(Ls, R),
|
|
S =.. [submit | R].
|
|
|
|
check_master_approval(S1, S2, Master) :-
|
|
gerrit:commit_label(label('Code-Review', 2), R),
|
|
R = Master, !,
|
|
S2 = [label('Master-Approval', ok(R)) | S1].
|
|
check_master_approval(S1, [label('Master-Approval', need(_)) | S1], _).
|
|
|
|
add_apprentice_master(S1, S2) :-
|
|
gerrit:commit_author(Id),
|
|
master_apprentice(Master, Id),
|
|
!,
|
|
check_master_approval(S1, S2, Master).
|
|
|
|
add_apprentice_master(S, S).
|
|
====
|
|
|
|
Example 15: Only allow Author to submit change
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
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
|
|
from submitting the change. This could result in an impossible situation if the
|
|
author does not have permissions for submitting the change.
|
|
|
|
.rules.pl
|
|
[caption=""]
|
|
====
|
|
submit_rule(S) :-
|
|
gerrit:default_submit(In),
|
|
In =.. [submit | Ls],
|
|
only_allow_author_to_submit(Ls, R),
|
|
S =.. [submit | R].
|
|
|
|
only_allow_author_to_submit(S, S) :-
|
|
gerrit:commit_author(Id),
|
|
gerrit:current_user(Id),
|
|
!.
|
|
|
|
only_allow_author_to_submit(S1, [label('Patchset-Author', need(_)) | S1]).
|
|
====
|
|
|
|
Examples - Submit Type
|
|
----------------------
|
|
The following examples show how to implement own submit type rules.
|
|
|
|
Example 1: Set a `Cherry Pick` submit type for all changes
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
This example sets the `Cherry Pick` submit type for all changes. It overrides
|
|
whatever is set as project default submit type.
|
|
|
|
rules.pl
|
|
[caption=""]
|
|
====
|
|
submit_type(cherry_pick).
|
|
====
|
|
|
|
|
|
Example 2: `Fast Forward Only` for all `refs/heads/stable*` branches
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
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
|
|
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
|
|
default submit type as defined on the project settings page.
|
|
|
|
.rules.pl
|
|
[caption=""]
|
|
====
|
|
submit_type(fast_forward_only) :-
|
|
gerrit:change_branch(B), regex_matches('refs/heads/stable.*', B),
|
|
!.
|
|
submit_type(T) :- gerrit:project_default_submit_type(T)
|
|
====
|
|
|
|
The first `submit_type` predicate defines the `Fast Forward Only` submit type
|
|
for `refs/heads/stable.*` branches. The second `submit_type` predicate returns
|
|
the project's default submit type.
|
|
|
|
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
|
|
the `refs/heads/stable*` branches. However, if only documentation was changed
|
|
(only `*.txt` files), then we allow project's default submit type for such
|
|
changes.
|
|
|
|
.rules.pl
|
|
[caption=""]
|
|
====
|
|
submit_type(fast_forward_only) :-
|
|
gerrit:commit_delta('(?<!\.txt)$'),
|
|
gerrit:change_branch(B), regex_matches('refs/heads/stable.*', B),
|
|
!.
|
|
submit_type(T) :- gerrit:project_default_submit_type(T)
|
|
====
|
|
|
|
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
|
|
previous example.
|
|
|
|
If all file names in the change end with `.txt`, then the
|
|
`gerrit:commit_delta('(?<!\.txt)$')` will fail as no file name will match this
|
|
regular expression.
|
|
|
|
GERRIT
|
|
------
|
|
Part of link:index.html[Gerrit Code Review]
|