| 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. |
| |
| 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 |
| ==== |
| |
| 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. |
| |
| 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. |
| |
| [[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. |
| |
| Examples |
| -------- |
| 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(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(label('Code-Review', ok(_)), 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(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(label('Any-Label-Name', need(_)))). |
| |
| % We could define more "need" labels by adding more rules |
| submit_rule(submit(label('Another-Label-Name', need(_)))). |
| |
| % or by providing more than one need label in the same rule |
| submit_rule(submit(label('X-Label-Name', need(_)), label('Y-Label-Name', need(_)))). |
| ==== |
| |
| 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(label('Some-Condition', need(_))). |
| submit_rule(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(label('Another-Condition', ok(_))). |
| submit_rule(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(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(label('Author-is-John-Doe', need(_)))). |
| submit_rule(submit(label('Author-is-John-Doe', ok(_)))) |
| :- gerrit:commit_author(_, 'John Doe', _). |
| ==== |
| |
| 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(label('Author-is-John-Doe', need(_)))). |
| submit_rule(submit(label('Author-is-John-Doe', ok(_)))) |
| :- gerrit:commit_author(_, _, 'john.doe@example.com'). |
| ==== |
| |
| or by user id (assuming it is 1000000): |
| |
| .rules.pl |
| [caption=""] |
| ==== |
| submit_rule(submit(label('Author-is-John-Doe', need(_)))). |
| submit_rule(submit(label('Author-is-John-Doe', ok(_)))) |
| :- gerrit:commit_author(user(1000000), _, _). |
| ==== |
| |
| or by a combination of these 3 attributes: |
| |
| .rules.pl |
| [caption=""] |
| ==== |
| submit_rule(submit(label('Author-is-John-Doe', need(_)))). |
| submit_rule(submit(label('Author-is-John-Doe', ok(_)))) |
| :- gerrit:commit_author(_, 'John Doe', 'john.doe@example.com'). |
| ==== |
| |
| Example 7: Make change submittable if commit message starts with "Trivial 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(label('Commit-Message-starts-with-Trivial-Fix', need(_)))). |
| submit_rule(submit(label('Commit-Message-starts-with-Trivial-Fix', ok(_)))) |
| :- gerrit:commit_message(M), name(M, L), starts_with(L, "Trivial Fix"). |
| |
| 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(label('Commit-Message-starts-with-Trivial-Fix', need(_)))). |
| submit_rule(submit(label('Commit-Message-starts-with-Trivial-Fix', ok(_)))) |
| :- gerrit:commit_message_matches('^Trivial Fix'). |
| ==== |
| |
| Reusing 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. In other words, we |
| would like to keep all the default policies (like the `Verified` category, |
| vetoing change, etc...) and only extend/change an aspect of it. For example, we |
| may want to disable the ability for change authors to approve their own changes |
| but keep all other policies the same. |
| |
| To get results of Gerrits default submit policy we use the |
| `gerrit:default_submit` predicate. This means that if we write a submit rule like: |
| |
| .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) :- ... |
| ==== |
| |
| The following examples build on top of the default submit policy. |
| |
| 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. |
| |
| 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. |
| |
| 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. |
| |
| .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. |
| |
| 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: 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]. |
| ==== |
| |
| Example 13: 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 14: 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]). |
| ==== |
| |
| GERRIT |
| ------ |
| Part of link:index.html[Gerrit Code Review] |