| :linkattrs: |
| = 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.enable=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,role=external,window=_blank] explains why Prolog was chosen for the purpose of writing |
| project specific submit rules. |
| link:https://gerrit-documentation.storage.googleapis.com/ReleaseNotes/ReleaseNotes-2.2.2.html#_prolog[Gerrit |
| 2.2.2 ReleaseNotes,role=external,window=_blank] 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. |
| |
| When submitting changes in a batch using "Submit including ancestors" or "Submit |
| whole topic", submit type rules may not be used to mix submit types on a single |
| branch, and trying to submit such a batch will fail. This avoids potentially |
| confusing behavior and spurious submit failures. It is recommended to only use |
| submit type rules to change submit types for an entire branch, which avoids this |
| situation. |
| |
| == Prolog Language |
| This document is not a complete Prolog tutorial. |
| link:http://en.wikipedia.org/wiki/Prolog[This Wikipedia page on Prolog,role=external,window=_blank] 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://gerrit.googlesource.com/prolog-cafe/[fork,role=external,window=_blank] of the |
| original link:http://kaminari.istc.kobe-u.ac.jp/PrologCafe/[prolog-cafe,role=external,window=_blank] |
| 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. |
| |
| For batch or unit tests, see the examples in Gerrit source directory |
| link:https://gerrit.googlesource.com/gerrit/+/refs/heads/master/prologtests/examples/[prologtests/examples,role=external,window=_blank]. |
| |
| [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,role=external,window=_blank] 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))`. 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 the change to |
| become submittable. |
| * `reject(user(ID))`. This status is used to tell that this label/category is |
| blocking submission of the change. |
| * `impossible(_)` is used when the logic knows that the change cannot be submitted |
| as-is. This is meant for cases where the logic requires members of a specific |
| group to apply a specific label on a change, but no users are in that group. |
| This is usually caused by misconfiguration of permissions. |
| * `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(user(ID)))) <1> |
| submit(label('Code-Review', ok(user(ID))), |
| label('Verified', reject(user(ID)))) <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(user(ID)))` 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 similar to |
| 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` |
| [source,prolog] |
| ---- |
| submit_rule(submit(W)) :- |
| W = label('Any-Label-Name', ok(user(1000000))). |
| ---- |
| |
| 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. |
| |
| The `user(1000000)` represents the user whose account ID is `1000000`. |
| |
| [NOTE] |
| Instead of the account ID `1000000` we could have used any other account ID. |
| The following examples will use `user(ID)` instead of `user(1000000)` because |
| it is easier to read and doesn't suggest that there is anything special with |
| the account ID `1000000`. |
| |
| === 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` |
| [source,prolog] |
| ---- |
| submit_rule(submit(CR, V)) :- |
| CR = label('Code-Review', ok(user(ID))), |
| V = label('Verified', ok(user(ID))). |
| ---- |
| |
| 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` |
| [source,prolog] |
| ---- |
| submit_rule(submit(R)) :- |
| R = label('Any-Label-Name', reject(user(ID))). |
| ---- |
| |
| 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` |
| [source,prolog] |
| ---- |
| % 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` |
| [source,prolog] |
| ---- |
| submit_rule(submit(N)) :- |
| N = label('Some-Condition', need(_)). |
| |
| submit_rule(submit(OK)) :- |
| OK = label('Another-Condition', ok(user(ID))). |
| ---- |
| |
| The `'Need Some-Condition'` will not be shown in the UI because of the result of |
| the second rule. |
| |
| The same is valid if the two rules are swapped: |
| |
| `rules.pl` |
| [source,prolog] |
| ---- |
| submit_rule(submit(OK)) :- |
| OK = label('Another-Condition', ok(user(ID))). |
| |
| 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` |
| [source,prolog] |
| ---- |
| 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` |
| [source,prolog] |
| ---- |
| submit_rule(submit(Author)) :- |
| Author = label('Author-is-John-Doe', need(_)). |
| |
| submit_rule(submit(Author)) :- |
| gerrit:commit_author(A, 'John Doe', _), |
| Author = label('Author-is-John-Doe', ok(A)). |
| ---- |
| |
| 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` |
| [source,prolog] |
| ---- |
| submit_rule(submit(Author)) :- |
| Author = label('Author-is-John-Doe', need(_)). |
| |
| submit_rule(submit(Author)) :- |
| gerrit:commit_author(_, _, 'john.doe@example.com'), |
| gerrit:uploader(U), |
| Author = label('Author-is-John-Doe', ok(U)). |
| ---- |
| |
| or by user id (assuming it is `1000000`): |
| |
| `rules.pl` |
| [source,prolog] |
| ---- |
| submit_rule(submit(Author)) :- |
| Author = label('Author-is-John-Doe', need(_)). |
| |
| submit_rule(submit(Author)) :- |
| U = user(1000000), |
| gerrit:commit_author(U, _, _), |
| Author = label('Author-is-John-Doe', ok(U)). |
| ---- |
| |
| or by a combination of these 3 attributes: |
| |
| `rules.pl` |
| [source,prolog] |
| ---- |
| submit_rule(submit(Author)) :- |
| Author = label('Author-is-John-Doe', need(_)). |
| |
| submit_rule(submit(Author)) :- |
| gerrit:commit_author(_, 'John Doe', 'john.doe@example.com'), |
| gerrit:uploader(U), |
| Author = label('Author-is-John-Doe', ok(U)). |
| ---- |
| |
| === 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` |
| [source,prolog] |
| ---- |
| 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 "), |
| gerrit:uploader(U), |
| Fix = label('Commit-Message-starts-with-Fix', ok(U)). |
| |
| 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` |
| [source,prolog] |
| ---- |
| submit_rule(submit(Fix)) :- |
| Fix = label('Commit-Message-starts-with-Fix', need(_)). |
| |
| submit_rule(submit(Fix)) :- |
| gerrit:commit_message_matches('^Fix '), |
| gerrit:uploader(U), |
| Fix = label('Commit-Message-starts-with-Fix', ok(U)). |
| ---- |
| |
| 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` |
| [source,prolog] |
| ---- |
| submit_rule(submit(Fix)) :- |
| gerrit:commit_message_matches('^Fix '), |
| gerrit:uploader(U), |
| Fix = label('Commit-Message-starts-with-Fix', ok(U)), |
| !. |
| |
| % 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` |
| [source,prolog] |
| ---- |
| 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 |
| this: |
| |
| `rules.pl` |
| [source,prolog] |
| ---- |
| submit_rule(X) :- gerrit:default_submit(X). |
| ---- |
| |
| it is equivalent to not using `rules.pl` at all. We just delegate to |
| default logic. However, once we invoke the `gerrit:default_submit(X)` we can |
| perform further actions on the return result `X` and apply our specific |
| logic. The following pattern illustrates this technique: |
| |
| `rules.pl` |
| [source,prolog] |
| ---- |
| 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` |
| [source,prolog] |
| ---- |
| 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(user(ID))), label('Verified', need(_)), ...)` into a list like `[submit, |
| label('Code-Review', ok(user(ID))), 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. |
| |
| This fact can be bypassed by users who have |
| link:access-control.html#category_forge_author[Forge Author] permission. |
| |
| ==== Don't use `gerrit:default_submit` |
| Let's implement the same submit rule the other way, without reusing the |
| `gerrit:default_submit`: |
| |
| `rules.pl` |
| [source,prolog] |
| ---- |
| 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` |
| [source,prolog] |
| ---- |
| 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` |
| [source,prolog] |
| ---- |
| 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 8 and 9. |
| |
| `rules.pl` |
| [source,prolog] |
| ---- |
| 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` |
| [source,prolog] |
| ---- |
| 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` |
| [source,prolog] |
| ---- |
| 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` |
| [source,prolog] |
| ---- |
| 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` |
| [source,prolog] |
| ---- |
| 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, !, |
| gerrit:commit_label(label(Category, V), U), |
| V >= 1, |
| !, |
| P = [label(Category,ok(U)) | 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` |
| [source,prolog] |
| ---- |
| 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: Mentor and Mentee |
| The mentor and mentee example allow you to specify a user (the `mentor`) |
| that must approve all changes done by another user (the `mentee`). |
| |
| The code first checks if the commit author is in the apprentice database. |
| If the commit is done by a `mentee`, it will check if there is a `+2` |
| review by the associated `mentor`. |
| |
| `rules.pl` |
| [source,prolog] |
| ---- |
| % mentor_mentee(Mentor, Mentee). |
| % Extend this with appropriate user-id for your mentor/mentee setup. |
| mentor_mentee(user(1000064), user(1000000)). |
| |
| submit_rule(S) :- |
| gerrit:default_submit(In), |
| In =.. [submit | Ls], |
| add_mentee_mentor(Ls, R), |
| S =.. [submit | R]. |
| |
| check_mentor_approval(S1, S2, Mentor) :- |
| gerrit:commit_label(label('Code-Review', 2), R), |
| R = Mentor, !, |
| S2 = [label('Mentor-Approval', ok(R)) | S1]. |
| check_mentor_approval(S1, [label('Mentor-Approval', need(_)) | S1], _). |
| |
| add_mentee_mentor(S1, S2) :- |
| gerrit:commit_author(Id), |
| mentor_mentee(Mentor, Id), |
| !, |
| check_mentor_approval(S1, S2, Mentor). |
| |
| add_mentee_mentor(S, S). |
| ---- |
| |
| === Example 15: Make change submittable if all comments have been resolved |
| In this example we will use the `unresolved_comments_count` fact about a |
| change. Our goal is to block the submission of any change with some |
| unresolved comments. Basically, it can be achieved by the following rules: |
| |
| `rules.pl` |
| [source,prolog] |
| ---- |
| submit_rule(submit(R)) :- |
| gerrit:unresolved_comments_count(0), |
| !, |
| gerrit:uploader(U), |
| R = label('All-Comments-Resolved', ok(U)). |
| |
| submit_rule(submit(R)) :- |
| gerrit:unresolved_comments_count(U), |
| U > 0, |
| R = label('All-Comments-Resolved', need(_)). |
| ---- |
| |
| Suppose currently a change is submittable if it gets `+2` for `Code-Review` |
| and `+1` for `Verified`. It can be extended to support the above rules as |
| follows: |
| |
| `rules.pl` |
| [source,prolog] |
| ---- |
| submit_rule(submit(CR, V, R)) :- |
| base(CR, V), |
| gerrit:unresolved_comments_count(0), |
| !, |
| gerrit:uploader(U), |
| R = label('All-Comments-Resolved', ok(U)). |
| |
| submit_rule(submit(CR, V, R)) :- |
| base(CR, V), |
| gerrit:unresolved_comments_count(U), |
| U > 0, |
| R = label('All-Comments-Resolved', need(_)). |
| |
| base(CR, V) :- |
| gerrit:max_with_block(-2, 2, 'Code-Review', CR), |
| gerrit:max_with_block(-1, 1, 'Verified', V). |
| ---- |
| |
| Note that a new label as `All-Comments-Resolved` should not be configured. |
| It's only used to show `'Needs All-Comments-Resolved'` in the UI to clearly |
| indicate to the user that all the comments have to be resolved for the |
| change to become submittable. |
| |
| === Example 16: Make change submittable if it is a pure revert |
| In this example we will use the `pure_revert` fact about a |
| change. Our goal is to block the submission of any change that is not a |
| pure revert. Basically, it can be achieved by the following rules: |
| |
| `rules.pl` |
| [source,prolog] |
| ---- |
| submit_rule(submit(R)) :- |
| gerrit:pure_revert(1), |
| !, |
| gerrit:uploader(U), |
| R = label('Is-Pure-Revert', ok(U)). |
| |
| submit_rule(submit(label('Is-Pure-Revert', need(_)))). |
| ---- |
| |
| Suppose currently a change is submittable if it gets `+2` for `Code-Review` |
| and `+1` for `Verified`. It can be extended to support the above rules as |
| follows: |
| |
| `rules.pl` |
| [source,prolog] |
| ---- |
| submit_rule(submit(CR, V, R)) :- |
| base(CR, V), |
| set_pure_revert_label(R). |
| |
| base(CR, V) :- |
| gerrit:max_with_block(-2, 2, 'Code-Review', CR), |
| gerrit:max_with_block(-1, 1, 'Verified', V). |
| |
| set_pure_revert_label(R) :- |
| gerrit:pure_revert(1), |
| !, |
| gerrit:uploader(U), |
| R = label('Is-Pure-Revert', ok(U)). |
| |
| set_pure_revert_label(label('Is-Pure-Revert', need(_))). |
| ---- |
| |
| Note that a new label as `Is-Pure-Revert` should not be configured. |
| It's only used to show `'Needs Is-Pure-Revert'` in the UI to clearly |
| indicate to the user that the change has to be a pure revert in order |
| to become submittable. |
| |
| === Example 17: Make a change submittable if it doesn't include specific files |
| |
| We can block any change which contains a submodule file change: |
| |
| `rules.pl` |
| [source,prolog] |
| ---- |
| submit_rule(submit(R)) :- |
| gerrit:includes_file(file(_,_,'SUBMODULE')), |
| !, |
| R = label('All-Submodules-Resolved', need(_)). |
| submit_rule(submit(label('All-Submodules-Resolved', ok(A)))) :- |
| gerrit:commit_author(A). |
| ---- |
| |
| We can also block specific files, modification type, or file type, |
| by changing include_files/1 to a different parameter. E.g, |
| include_files('a.txt',_,_) includes any update to "a.txt", and |
| ('a.txt','D',_) includes any deletion to "a.txt". Also, (_,_,_) includes |
| any file (other than magic file). |
| |
| An inclusive list of possible arguments using the code above with variations |
| of include_file: |
| The first parameter is the file name. |
| The second is the modification type ('A' for 'added', 'M' for 'modified', |
| 'D' for 'deleted', 'R' for 'renamed', 'C' for 'COPIED' and 'W' for 'rewrite'). |
| The third argument is the type of file, with the options being a submodule |
| file 'SUBMODULE' and a non-submodule file being 'REGULAR'. |
| |
| == 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 |
| [source,prolog] |
| ---- |
| 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` |
| [source,prolog] |
| ---- |
| 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. |
| |
| GERRIT |
| ------ |
| Part of link:index.html[Gerrit Code Review] |
| |
| SEARCHBOX |
| --------- |