1781adb856
Change-Id: I3f52692757811858c03b85820f8a718e0815ffce Signed-off-by: Edwin Kempin <edwin.kempin@sap.com>
1035 lines
38 KiB
Plaintext
1035 lines
38 KiB
Plaintext
= Gerrit Code Review - Prolog Submit Rules Cookbook
|
|
|
|
[[SubmitRule]]
|
|
== 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.
|
|
|
|
[[SubmitType]]
|
|
== 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.
|
|
|
|
[[RulesFile]]
|
|
== 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 implementer 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 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
|
|
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'`
|
|
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.
|
|
|
|
[[HowToWriteSubmitType]]
|
|
== 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 Gerrit's 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.
|
|
|
|
[[NonAuthorCodeReview]]
|
|
=== 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 been 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).
|
|
====
|
|
|
|
|
|
[[SubmitTypePerBranch]]
|
|
=== 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]
|
|
|
|
SEARCHBOX
|
|
---------
|