Sasa Zivkov | ae50f71 | 2012-07-24 14:51:44 +0200 | [diff] [blame] | 1 | Gerrit Code Review - Prolog Submit Rules Cookbook |
| 2 | ================================================= |
| 3 | |
| 4 | Submit Rule |
| 5 | ----------- |
| 6 | A 'Submit Rule' in Gerrit is logic that defines when a change is submittable. |
| 7 | By default, a change is submittable when it gets at least one |
| 8 | highest vote in each voting category and has no lowest vote (aka veto vote) in |
| 9 | any category. Typically, this means that a change needs 'Code-Review+2', |
| 10 | 'Verified+1' and has neither 'Code-Review-2' nor 'Verified-1' to become |
| 11 | submittable. |
| 12 | |
| 13 | While this rule is a good default, there are projects which need more |
| 14 | flexibility for defining when a change is submittable. In Gerrit, it is |
| 15 | possible to use Prolog based rules to provide project specific submit rules and |
| 16 | replace the default submit rules. Using Prolog based rules, project owners can |
| 17 | define a set of criteria which must be fulfilled for a change to become |
| 18 | submittable. For a change that is not submittable, the set of needed criteria |
| 19 | is displayed in the Gerrit UI. |
| 20 | |
| 21 | NOTE: Loading and executing Prolog submit rules may be disabled by setting |
| 22 | `rules.enabled=false` in the Gerrit config file (see |
| 23 | link:config-gerrit.html#_a_id_rules_a_section_rules[rules section]) |
| 24 | |
| 25 | link:https://groups.google.com/d/topic/repo-discuss/wJxTGhlHZMM/discussion[This |
| 26 | discussion thread] explains why Prolog was chosen for the purpose of writing |
| 27 | project specific submit rules. |
| 28 | link:http://gerrit-documentation.googlecode.com/svn/ReleaseNotes/ReleaseNotes-2.2.2.html[Gerrit |
| 29 | 2.2.2 ReleaseNotes] introduces Prolog support in Gerrit. |
| 30 | |
| 31 | Prolog Language |
| 32 | --------------- |
| 33 | This document is not a complete Prolog tutorial. |
| 34 | link:http://en.wikipedia.org/wiki/Prolog[This Wikipedia page on Prolog] is a |
| 35 | good starting point for learning the Prolog language. This document will only explain |
| 36 | some elements of Prolog that are necessary to understand the provided examples. |
| 37 | |
| 38 | Prolog in Gerrit |
| 39 | ---------------- |
| 40 | Gerrit uses its own link:https://code.google.com/p/prolog-cafe/[fork] of the |
| 41 | original link:http://kaminari.istc.kobe-u.ac.jp/PrologCafe/[prolog-cafe] |
| 42 | project. Gerrit embeds the prolog-cafe library and can interpret Prolog programs at |
| 43 | runtime. |
| 44 | |
| 45 | Interactive Prolog Cafe Shell |
| 46 | ----------------------------- |
| 47 | For interactive testing and playing with Prolog, Gerrit provides the |
| 48 | link:pgm-prolog-shell.html[prolog-shell] program which opens an interactive |
| 49 | Prolog interpreter shell. |
| 50 | |
| 51 | NOTE: It is currently *not possible* to test a Prolog program which implements |
| 52 | Gerrit submit rules using the link:pgm-prolog-shell.html[prolog-shell] program. |
| 53 | The reason is that the Prolog environment that exposes facts about a change |
| 54 | requires a lot of Gerrit server environment to be loaded and running. |
| 55 | |
| 56 | SWI-Prolog |
| 57 | ---------- |
| 58 | Instead of using the link:pgm-prolog-shell.html[prolog-shell] program one can |
| 59 | also use the link:http://www.swi-prolog.org/[SWI-Prolog] environment. It |
| 60 | provides a better shell interface and a graphical source-level debugger. |
| 61 | |
| 62 | The rules.pl file |
| 63 | ----------------- |
| 64 | This section explains how to create and edit project specific submit rules. How |
| 65 | to actually write the submit rules is explained in the next section. |
| 66 | |
| 67 | Project specific submit rules are stored in the `rules.pl` file in the |
| 68 | `refs/meta/config` branch of that project. Therefore, we need to fetch and |
| 69 | checkout the `refs/meta/config` branch in order to create or edit the `rules.pl` |
| 70 | file: |
| 71 | |
| 72 | ==== |
| 73 | $ git fetch origin refs/meta/config:config |
| 74 | $ git checkout config |
| 75 | ... edit or create the rules.pl file |
| 76 | $ git add rules.pl |
| 77 | $ git commit -m "My submit rules" |
| 78 | $ git push origin HEAD:refs/meta/config |
| 79 | ==== |
| 80 | |
| 81 | How to write submit rules |
| 82 | ------------------------- |
| 83 | Whenever Gerrit needs to evaluate submit rules for a change `C` from project `P` it |
| 84 | will first initialize the embedded Prolog interpreter by: |
| 85 | |
| 86 | * consulting a set of facts about the change `C` |
| 87 | * consulting the `rules.pl` from the project `P` |
| 88 | |
| 89 | Conceptually we can imagine that Gerrit adds a set of facts about the change |
| 90 | `C` on top of the `rules.pl` file and then consults it. The set of facts about |
| 91 | the change `C` will look like: |
| 92 | |
| 93 | ==== |
| 94 | :- package gerrit. <1> |
| 95 | |
| 96 | commit_author(user(1000000), 'John Doe', 'john.doe@example.com'). <2> |
| 97 | commit_committer(user(1000000), 'John Doe', 'john.doe@example.com'). <3> |
| 98 | commit_message('Add plugin support to Gerrit'). <4> |
| 99 | ... |
| 100 | ==== |
| 101 | |
| 102 | <1> Gerrit will provide its facts in a package named `gerrit`. This means we |
| 103 | have to use qualified names when writing our code and referencing these facts. |
| 104 | For example: `gerrit:commit_author(ID, N, M)` |
| 105 | <2> user ID, full name and email address of the commit author |
| 106 | <3> user ID, full name and email address of the commit committer |
| 107 | <4> commit message |
| 108 | |
| 109 | A complete set of facts which Gerrit provides about the change is listed in the |
| 110 | link:prolog-change-facts.html[Prolog Facts for Gerrit Change]. |
| 111 | |
| 112 | By default, Gerrit will search for a `submit_rule/1` predicate in the `rules.pl` |
| 113 | file, evaluate the `submit_rule(X)` and then inspect the value of `X` in order |
| 114 | to decide whether the change is submittable or not and also to find the set of |
| 115 | needed criteria for the change to become submittable. This means that Gerrit has an |
| 116 | expectation on the format and value of the result of the `submit_rule` predicate |
| 117 | which is expected to be a `submit` term of the following format: |
| 118 | |
| 119 | ==== |
| 120 | submit(label(label-name, status) [, label(label-name, status)]*) |
| 121 | ==== |
| 122 | |
| 123 | where `label-name` is usually `'Code-Review'` or `'Verified'` but could also |
| 124 | be any other string (see examples below). The `status` is one of: |
| 125 | |
| 126 | * `ok(user(ID))` or just `ok(_)` if user info is not important. This status is |
| 127 | used to tell that this label/category has been met. |
| 128 | * `need(_)` is used to tell that this label/category is needed for change to |
| 129 | become submittable |
| 130 | * `reject(user(ID))` or just `reject(_)`. This status is used to tell that label/category |
| 131 | is blocking change submission |
| 132 | * `impossible(_)` is used when the logic knows that the change cannot be submitted as-is. |
| 133 | Administrative intervention is probably required. This is meant for cases |
| 134 | where the logic requires members of "FooEng" to score "Code-Review +2" on a |
| 135 | change, but nobody is in group "FooEng". It is to hint at permissions |
| 136 | misconfigurations. |
| 137 | * `may(_)` allows expression of approval categories that are optional, i.e. |
| 138 | could either be set or unset without ever influencing whether the change |
| 139 | could be submitted. |
| 140 | |
| 141 | NOTE: For a change to be submittable all `label` terms contained in the returned |
| 142 | `submit` term must have either `ok` or `may` status. |
| 143 | |
| 144 | IMPORTANT: Gerrit will let the Prolog engine continue searching for solutions of |
| 145 | the `submit_rule(X)` query until it finds the first one where all labels in the |
| 146 | return result have either status `ok` or `may` or there are no more solutions. |
| 147 | If a solution where all labels have status `ok` is found then all previously |
| 148 | found solutions are ignored. Otherwise, all labels names with status `need` |
| 149 | from all solutions will be displayed in the UI indicating the set of conditions |
| 150 | needed for the change to become submittable. |
| 151 | |
| 152 | Here some examples of possible return values from the `submit_rule` predicate: |
| 153 | |
| 154 | ==== |
| 155 | submit(label('Code-Review', ok(_))) <1> |
| 156 | submit(label('Code-Review', ok(_)), label('Verified', reject(_))) <2> |
| 157 | submit(label('Author-is-John-Doe', need(_)) <3> |
| 158 | ==== |
| 159 | |
| 160 | <1> label `'Code-Review'` is met. As there are no other labels in the |
| 161 | return result, the change is submittable. |
| 162 | <2> label `'Verified'` is rejected. Change is not submittable. |
| 163 | <3> label `'Author-is-John-Doe'` is needed for the change to become submittable. |
| 164 | Note that this tells nothing about how this criteria will be met. It is up |
| 165 | to the implementor of the `submit_rule` to return `label('Author-is-John-Doe', |
| 166 | ok(_))` when this criteria is met. Most likely, it will have to match |
| 167 | against `gerrit:commit_author` in order to check if this criteria is met. |
| 168 | This will become clear through the examples below. |
| 169 | |
| 170 | Of course, when implementing the `submit_rule` we will use the facts about the |
| 171 | change that are already provided by Gerrit. |
| 172 | |
| 173 | Another aspect of the return result from the `submit_rule` predicate is that |
| 174 | Gerrit uses it to decide which set of labels to display on the change review |
| 175 | screen for voting. If the return result contains label `'ABC'` and if the label |
| 176 | `'ABC'` is one of the (global) voting categories then voting for the label |
| 177 | `'ABC'` will be displayed. Otherwise, it is not displayed. Note that we don't |
| 178 | need a (global) voting category for each label contained in the result of |
| 179 | `submit_rule` predicate. For example, the decision whether `'Author-is-John-Doe'` |
| 180 | label is met will probably not be made by explicit voting but, instead, by |
| 181 | inspecting the facts about the change. |
| 182 | |
Sasa Zivkov | 3d4f0aa | 2012-08-07 15:11:32 +0200 | [diff] [blame] | 183 | Submit Filter |
| 184 | ------------- |
| 185 | Another mechanism of changing the default submit rules is to implement the |
| 186 | `submit_filter/2` predicate. While Gerrit will search for the `submit_rule` only |
| 187 | in the `rules.pl` file of the current project, the `submit_filter` will be |
| 188 | searched for in the `rules.pl` of all parent projects of the current project, |
| 189 | but not in the `rules.pl` of the current project. The search will start from the |
| 190 | immediate parent of the current project, then in the parent project of that |
| 191 | project and so on until, and including, the 'All-Projects' project. |
| 192 | |
| 193 | The purpose of the submit filter is, as its name says, to filter the results |
| 194 | of the `submit_rule`. Therefore, the `submit_filter` predicate has two |
| 195 | parameters: |
| 196 | |
| 197 | ==== |
| 198 | submit_filter(In, Out) :- ... |
| 199 | ==== |
| 200 | |
| 201 | Gerrit will invoke `submit_filter` with the `In` parameter containing a `submit` |
| 202 | structure produced by the `submit_rule` and will take the value of the `Out` |
| 203 | parameter as the result. |
| 204 | |
| 205 | The `Out` value of a `submit_filter` will become the `In` value for the |
| 206 | next `submit_filter` in the parent line. The value of the `Out` parameter |
| 207 | of the top-most `submit_filter` is the final result of the submit rule that |
| 208 | is used to decide whether a change is submittable or not. |
| 209 | |
| 210 | IMPORTANT: `submit_filter` is a mechanism for Gerrit administrators to implement |
| 211 | and enforce submit rules that would apply to all projects while `submit_rule` is |
| 212 | a mechanism for project owners to implement project specific submit rules. |
| 213 | However, project owners who own several projects could also make use of |
| 214 | `submit_filter` by using a common parent project for all their projects and |
| 215 | implementing the `submit_filter` in this common parent project. This way they |
| 216 | can avoid implementing the same `submit_rule` in all their projects. |
| 217 | |
| 218 | The following "drawing" illustrates the order of the invocation and the chaining |
| 219 | of the results of the `submit_rule` and `submit_filter` predicates. |
| 220 | |
| 221 | ==== |
| 222 | All-Projects |
| 223 | ^ submit_filter(B, S) :- ... <4> |
| 224 | | |
| 225 | Parent-3 |
| 226 | ^ <no submit filter here> |
| 227 | | |
| 228 | Parent-2 |
| 229 | ^ submit_filter(A, B) :- ... <3> |
| 230 | | |
| 231 | Parent-1 |
| 232 | ^ submit_filter(X, A) :- ... <2> |
| 233 | | |
| 234 | MyProject |
| 235 | submit_rule(X) :- ... <1> |
| 236 | ==== |
| 237 | |
| 238 | <1> The `submit_rule` of `MyProject` is invoked first. |
| 239 | <2> The result `X` is filtered through the `submit_filter` from the `Parent-1` |
| 240 | project. |
| 241 | <3> The result of `submit_filter` from `Parent-1` project is filtered by the |
| 242 | `submit_filter` in the `Parent-2` project. Since `Parent-3` project doesn't have |
| 243 | a `submit_filter` it is skipped. |
| 244 | <4> The result of `submit_filter` from `Parent-2` project is filtered by the |
| 245 | `submit_filter` in the `All-Projects` project. The value in `S` is the final |
| 246 | value of the submit rule evaluation. |
| 247 | |
| 248 | NOTE: If `MyProject` doesn't define its own `submit_rule` Gerrit will invoke the |
| 249 | default implementation of submit rule that is named `gerrit:default_submit` and |
| 250 | its result will be filtered as described above. |
| 251 | |
| 252 | |
Sasa Zivkov | ae50f71 | 2012-07-24 14:51:44 +0200 | [diff] [blame] | 253 | Prolog vs Gerrit plugin for project specific submit rules |
| 254 | --------------------------------------------------------- |
| 255 | Since version 2.5 Gerrit supports plugins and extension points. A plugin or an |
| 256 | extension point could also be used as another means to provide custom submit |
| 257 | rules. One could ask for a guideline when to use Prolog based submit rules and |
| 258 | when to go for writing a new plugin. Writing a Prolog program is usually much |
| 259 | faster than writing a Gerrit plugin. Prolog based submit rules can be pushed |
| 260 | to a project by project owners while Gerrit plugins could only be installed by |
| 261 | Gerrit administrators. In addition, Prolog based submit rules can be pushed |
| 262 | for review by pushing to `refs/for/refs/meta/config` branch. |
| 263 | |
| 264 | On the other hand, Prolog based submit rules get a limited amount of facts about |
| 265 | the change exposed to them. Gerrit plugins get full access to Gerrit internals |
| 266 | and can potentially check more things than Prolog based rules. |
| 267 | |
| 268 | Examples |
| 269 | -------- |
| 270 | The following examples should serve as a cookbook for developing own submit rules. |
| 271 | Some of them are too trivial to be used in production and their only purpose is |
| 272 | to provide step by step introduction and understanding. |
| 273 | |
Sasa Zivkov | 3d4f0aa | 2012-08-07 15:11:32 +0200 | [diff] [blame] | 274 | Some of the examples will implement the `submit_rule` and some will implement |
| 275 | the `submit_filter` just to show both possibilities. Remember that |
| 276 | `submit_rule` is only invoked from the current project and `submit_filter` is |
| 277 | invoked from all parent projects. This is the most important fact in deciding |
| 278 | whether to implement `submit_rule` or `submit_filter`. |
| 279 | |
Sasa Zivkov | ae50f71 | 2012-07-24 14:51:44 +0200 | [diff] [blame] | 280 | Example 1: Make every change submittable |
| 281 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| 282 | Let's start with a most trivial example where we would make every change submittable |
| 283 | regardless of the votes it has: |
| 284 | |
| 285 | .rules.pl |
| 286 | [caption=""] |
| 287 | ==== |
| 288 | submit_rule(submit(label('Any-Label-Name', ok(_)))). |
| 289 | ==== |
| 290 | |
| 291 | In this case we make no use of facts about the change. We don't need it as we are simply |
| 292 | making every change submittable. Note that, in this case, the Gerrit UI will not show |
| 293 | the UI for voting for the standard `'Code-Review'` and `'Verified'` categories as labels |
| 294 | with these names are not part of the return result. The `'Any-Label-Name'` could really |
| 295 | be any string. |
| 296 | |
| 297 | Example 2: Every change submittable and voting in the standard categories possible |
| 298 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| 299 | This is continuation of the previous example where, in addition, to making |
| 300 | every change submittable we want to enable voting in the standard |
| 301 | `'Code-Review'` and `'Verified'` categories. |
| 302 | |
| 303 | .rules.pl |
| 304 | [caption=""] |
| 305 | ==== |
| 306 | submit_rule(submit(label('Code-Review', ok(_)), label('Verified', ok(_)))). |
| 307 | ==== |
| 308 | |
| 309 | Since for every change all label statuses are `'ok'` every change will be submittable. |
| 310 | Voting in the standard labels will be shown in the UI as the standard label names are |
| 311 | included in the return result. |
| 312 | |
| 313 | Example 3: Nothing is submittable |
| 314 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| 315 | This example shows how to make all changes non-submittable regardless of the |
| 316 | votes they have. |
| 317 | |
| 318 | .rules.pl |
| 319 | [caption=""] |
| 320 | ==== |
| 321 | submit_rule(submit(label('Any-Label-Name', reject(_)))). |
| 322 | ==== |
| 323 | |
| 324 | Since for any change we return only one label with status `reject`, no change |
| 325 | will be submittable. The UI will, however, not indicate what is needed for a |
| 326 | change to become submittable as we return no labels with status `need`. |
| 327 | |
| 328 | Example 4: Nothing is submittable but UI shows several 'Need ...' criteria |
| 329 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| 330 | In this example no change is submittable but here we show how to present 'Need |
| 331 | <label>' information to the user in the UI. |
| 332 | |
| 333 | .rules.pl |
| 334 | [caption=""] |
| 335 | ==== |
| 336 | % In the UI this will show: Need Any-Label-Name |
| 337 | submit_rule(submit(label('Any-Label-Name', need(_)))). |
| 338 | |
| 339 | % We could define more "need" labels by adding more rules |
| 340 | submit_rule(submit(label('Another-Label-Name', need(_)))). |
| 341 | |
| 342 | % or by providing more than one need label in the same rule |
| 343 | submit_rule(submit(label('X-Label-Name', need(_)), label('Y-Label-Name', need(_)))). |
| 344 | ==== |
| 345 | |
| 346 | In the UI this will show: |
| 347 | **** |
| 348 | * Need Any-Label-Name |
| 349 | * Need Another-Label-Name |
| 350 | * Need X-Label-Name |
| 351 | * Need Y-Label-Name |
| 352 | **** |
| 353 | |
| 354 | From the example above we can see a few more things: |
| 355 | |
| 356 | * comment in Prolog starts with the `%` character |
| 357 | * there could be multiple `submit_rule` predicates. Since Prolog, by default, tries to find |
| 358 | all solutions for a query, the result will be union of all solutions. |
| 359 | Therefore, we see all 4 `need` labels in the UI. |
| 360 | |
| 361 | Example 5: The 'Need ...' labels not shown when change is submittable |
| 362 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| 363 | This example shows that, when there is a solution for `submit_rule(X)` where all labels |
| 364 | have status `ok` then Gerrit will not show any labels with the `need` status from |
| 365 | any of the previous `submit_rule(X)` solutions. |
| 366 | |
| 367 | .rules.pl |
| 368 | [caption=""] |
| 369 | ==== |
| 370 | submit_rule(label('Some-Condition', need(_))). |
| 371 | submit_rule(label('Another-Condition', ok(_))). |
| 372 | ==== |
| 373 | |
| 374 | The 'Need Some-Condition' will not be show in the UI because of the result of |
| 375 | the second rule. |
| 376 | |
| 377 | The same is valid if the two rules are swapped: |
| 378 | |
| 379 | .rules.pl |
| 380 | [caption=""] |
| 381 | ==== |
| 382 | submit_rule(label('Another-Condition', ok(_))). |
| 383 | submit_rule(label('Some-Condition', need(_))). |
| 384 | ==== |
| 385 | |
| 386 | The result of the first rule will stop search for any further solutions. |
| 387 | |
| 388 | Example 6: Make change submittable if commit author is "John Doe" |
| 389 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| 390 | This is the first example where we will use the Prolog facts about a change that |
| 391 | are automatically exposed by Gerrit. Our goal is to make any change submittable |
| 392 | when the commit author is named `'John Doe'`. In the very first |
| 393 | step let's make sure Gerrit UI shows 'Need Author-is-John-Doe' in |
| 394 | the UI to clearly indicate to the user what is needed for a change to become |
| 395 | submittable: |
| 396 | |
| 397 | .rules.pl |
| 398 | [caption=""] |
| 399 | ==== |
| 400 | submit_rule(submit(label('Author-is-John-Doe', need(_)))). |
| 401 | ==== |
| 402 | |
| 403 | This will show: |
| 404 | **** |
| 405 | * Need Author-is-John-Doe |
| 406 | **** |
| 407 | |
| 408 | in the UI but no change will be submittable yet. Let's add another rule: |
| 409 | |
| 410 | .rules.pl |
| 411 | [caption=""] |
| 412 | ==== |
| 413 | submit_rule(submit(label('Author-is-John-Doe', need(_)))). |
| 414 | submit_rule(submit(label('Author-is-John-Doe', ok(_)))) |
| 415 | :- gerrit:commit_author(_, 'John Doe', _). |
| 416 | ==== |
| 417 | |
| 418 | In the second rule we return `ok` status for the `'Author-is-John-Doe'` label |
| 419 | if there is a `commit_author` fact where the full name is `'John Doe'`. If |
| 420 | author of a change is `'John Doe'` then the second rule will return a solution |
| 421 | where all labels have `ok` status and the change will become submittable. If |
| 422 | author of a change is not `'John Doe'` then only the first rule will produce a |
| 423 | solution. The UI will show 'Need Author-is-John-Doe' but, as expected, the |
| 424 | change will not be submittable. |
| 425 | |
| 426 | Instead of checking by full name we could also check by the email address: |
| 427 | |
| 428 | .rules.pl |
| 429 | [caption=""] |
| 430 | ==== |
| 431 | submit_rule(submit(label('Author-is-John-Doe', need(_)))). |
| 432 | submit_rule(submit(label('Author-is-John-Doe', ok(_)))) |
| 433 | :- gerrit:commit_author(_, _, 'john.doe@example.com'). |
| 434 | ==== |
| 435 | |
| 436 | or by user id (assuming it is 1000000): |
| 437 | |
| 438 | .rules.pl |
| 439 | [caption=""] |
| 440 | ==== |
| 441 | submit_rule(submit(label('Author-is-John-Doe', need(_)))). |
| 442 | submit_rule(submit(label('Author-is-John-Doe', ok(_)))) |
| 443 | :- gerrit:commit_author(user(1000000), _, _). |
| 444 | ==== |
| 445 | |
| 446 | or by a combination of these 3 attributes: |
| 447 | |
| 448 | .rules.pl |
| 449 | [caption=""] |
| 450 | ==== |
| 451 | submit_rule(submit(label('Author-is-John-Doe', need(_)))). |
| 452 | submit_rule(submit(label('Author-is-John-Doe', ok(_)))) |
| 453 | :- gerrit:commit_author(_, 'John Doe', 'john.doe@example.com'). |
| 454 | ==== |
| 455 | |
| 456 | Example 7: Make change submittable if commit message starts with "Trivial fix" |
| 457 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| 458 | Besides showing how to make use of the commit message text the purpose of this |
| 459 | example is also to show how to match only a part of a string symbol. Similarly |
| 460 | like commit author the commit message is provided as a string symbol which is |
| 461 | an atom in Prolog terms. When working with an atom we could only match against |
| 462 | the whole value. To match only part of a string symbol we have, at least, two |
| 463 | options: |
| 464 | |
| 465 | * convert the string symbol into a list of characters and then perform |
| 466 | the "classical" list matching |
| 467 | * use the `regex_matches/2` or, even more convenient, the |
| 468 | `gerrit:commit_message_matches/1` predicate |
| 469 | |
| 470 | Let's implement both options: |
| 471 | |
| 472 | .rules.pl |
| 473 | [caption=""] |
| 474 | ==== |
| 475 | submit_rule(submit(label('Commit-Message-starts-with-Trivial-Fix', need(_)))). |
| 476 | submit_rule(submit(label('Commit-Message-starts-with-Trivial-Fix', ok(_)))) |
| 477 | :- gerrit:commit_message(M), name(M, L), starts_with(L, "Trivial Fix"). |
| 478 | |
| 479 | starts_with(L, []). |
| 480 | starts_with([H|T1], [H|T2]) :- starts_with(T1, T2). |
| 481 | ==== |
| 482 | |
| 483 | NOTE: The `name/2` embedded predicate is used to convert a string symbol into a |
| 484 | list of characters. A string `abc` is converted into a list of characters `[97, |
| 485 | 98, 99]`. A double quoted string in Prolog is just a shortcut for creating a |
| 486 | list of characters. `"abc"` is a shortcut for `[97, 98, 99]`. This is why we use |
| 487 | double quotes for the `"Trivial Fix"` in the example above. |
| 488 | |
| 489 | The `starts_with` predicate is self explaining. |
| 490 | |
| 491 | Using the `gerrit:commit_message_matches` predicate is probably more efficient: |
| 492 | |
| 493 | .rules.pl |
| 494 | [caption=""] |
| 495 | ==== |
| 496 | submit_rule(submit(label('Commit-Message-starts-with-Trivial-Fix', need(_)))). |
| 497 | submit_rule(submit(label('Commit-Message-starts-with-Trivial-Fix', ok(_)))) |
| 498 | :- gerrit:commit_message_matches('^Trivial Fix'). |
| 499 | ==== |
| 500 | |
| 501 | Reusing the default submit policy |
| 502 | --------------------------------- |
| 503 | All examples until now concentrate on one particular aspect of change data. |
| 504 | However, in real-life scenarios we would rather want to reuse Gerrit's default |
| 505 | submit policy and extend/change it for our specific purpose. In other words, we |
| 506 | would like to keep all the default policies (like the `Verified` category, |
| 507 | vetoing change, etc...) and only extend/change an aspect of it. For example, we |
| 508 | may want to disable the ability for change authors to approve their own changes |
| 509 | but keep all other policies the same. |
| 510 | |
| 511 | To get results of Gerrits default submit policy we use the |
| 512 | `gerrit:default_submit` predicate. This means that if we write a submit rule like: |
| 513 | |
| 514 | .rules.pl |
| 515 | [caption=""] |
| 516 | ==== |
| 517 | submit_rule(X) :- gerrit:default_submit(X). |
| 518 | ==== |
| 519 | |
| 520 | then this is equivalent to not using `rules.pl` at all. We just delegate to |
| 521 | default logic. However, once we invoke the `gerrit:default_submit(X)` we can |
| 522 | perform further actions on the return result `X` and apply our specific |
| 523 | logic. The following pattern illustrates this technique: |
| 524 | |
| 525 | .rules.pl |
| 526 | [caption=""] |
| 527 | ==== |
| 528 | submit_rule(S) :- gerrit:default_submit(R), project_specific_policy(R, S). |
| 529 | |
| 530 | project_specific_policy(R, S) :- ... |
| 531 | ==== |
| 532 | |
| 533 | The following examples build on top of the default submit policy. |
| 534 | |
| 535 | Example 8: Make change submittable only if `Code-Review+2` is given by a non author |
| 536 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| 537 | In this example we introduce a new label `Non-Author-Code-Review` and make it |
| 538 | satisfied if there is at least one `Code-Review+2` from a non author. All other |
| 539 | default policies like the `Verified` category and vetoing changes still apply. |
| 540 | |
| 541 | First, we invoke `gerrit:default_submit` to compute the result for the default |
| 542 | submit policy and then add the `Non-Author-Code-Review` label to it. The |
| 543 | `Non-Author-Code-Review` label is added with status `ok` if such an approval |
| 544 | exists or with status `need` if it doesn't exist. |
| 545 | |
| 546 | .rules.pl |
| 547 | [caption=""] |
| 548 | ==== |
| 549 | submit_rule(S) :- |
| 550 | gerrit:default_submit(X), |
| 551 | X =.. [submit | Ls], |
| 552 | add_non_author_approval(Ls, R), |
| 553 | S =.. [submit | R]. |
| 554 | |
| 555 | add_non_author_approval(S1, S2) :- |
| 556 | gerrit:commit_author(A), gerrit:commit_label(label('Code-Review', 2), R), |
| 557 | R \= A, !, |
| 558 | S2 = [label('Non-Author-Code-Review', ok(R)) | S1]. |
| 559 | add_non_author_approval(S1, [label('Non-Author-Code-Review', need(_)) | S1]). |
| 560 | ==== |
| 561 | |
| 562 | This example uses the `univ` operator `=..` to "unpack" the result of the |
| 563 | default_submit, which is a structure of the form `submit(label('Code-Review', |
| 564 | ok(_)), label('Verified', need(_)) ...)` into a list like `[submit, |
| 565 | label('Code-Review', ok(_)), label('Verified', need(_)), ...]`. Then we |
| 566 | process the tail of the list (the list of labels) as a Prolog list, which is |
| 567 | much easier than processing a structure. In the end we use the same `univ` |
| 568 | operator to convert the resulting list of labels back into a `submit` structure |
| 569 | which is expected as a return result. The `univ` operator works both ways. |
| 570 | |
| 571 | In `add_non_author_approval` we use the `cut` operator `!` to prevent Prolog |
| 572 | from searching for more solutions once the `cut` point is reached. This is |
| 573 | important because in the second `add_non_author_approval` rule we just add the |
| 574 | `label('Non-Author-Code-Review', need(_))` without first checking that there |
| 575 | is no non author `Code-Review+2`. The second rule will only be reached |
| 576 | if the `cut` in the first rule is not reached and it only happens if a |
| 577 | predicate before the `cut` fails. |
| 578 | |
| 579 | Example 9: Remove the `Verified` category |
| 580 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| 581 | A project has no build and test. It consists of only text files and needs only |
| 582 | code review. We want to remove the `Verified` category from this project so |
| 583 | that `Code-Review+2` is the only criteria for a change to become submittable. |
| 584 | We also want the UI to not show the `Verified` category in the table with |
| 585 | votes and on the voting screen. |
| 586 | |
| 587 | .rules.pl |
| 588 | [caption=""] |
| 589 | ==== |
| 590 | submit_rule(S) :- |
| 591 | gerrit:default_submit(X), |
| 592 | X =.. [submit | Ls], |
| 593 | remove_verified_category(Ls, R), |
| 594 | S =.. [submit | R]. |
| 595 | |
| 596 | remove_verified_category([], []). |
| 597 | remove_verified_category([label('Verified', _) | T], R) :- remove_verified_category(T, R), !. |
| 598 | remove_verified_category([H|T], [H|R]) :- remove_verified_category(T, R). |
| 599 | ==== |
| 600 | |
| 601 | Example 10: Combine examples 8 and 9 |
| 602 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| 603 | In this example we want to both remove the verified and have the four eyes |
| 604 | principle. This means we want a combination of examples 7 and 8. |
| 605 | |
| 606 | .rules.pl |
| 607 | [caption=""] |
| 608 | ==== |
| 609 | submit_rule(S) :- |
| 610 | gerrit:default_submit(X), |
| 611 | X =.. [submit | Ls], |
| 612 | remove_verified_category(Ls, R1), |
| 613 | add_non_author_approval(R1, R), |
| 614 | S =.. [submit | R]. |
| 615 | ==== |
| 616 | |
| 617 | The `remove_verified_category` and `add_non_author_approval` predicates are the |
| 618 | same as defined in the previous two examples. |
| 619 | |
Sasa Zivkov | 3d4f0aa | 2012-08-07 15:11:32 +0200 | [diff] [blame] | 620 | Example 11: Remove the `Verified` category from all projects |
| 621 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| 622 | Example 9, implements `submit_rule` that removes the `Verified` category from |
| 623 | one project. In this example we do the same but we want to remove the `Verified` |
| 624 | category from all projects. This means we have to implement `submit_filter` and |
| 625 | we have to do that in the `rules.pl` of the `All-Projects` project. |
| 626 | |
| 627 | .rules.pl |
| 628 | [caption=""] |
| 629 | ==== |
| 630 | submit_filter(In, Out) :- |
| 631 | In =.. [submit | Ls], |
| 632 | remove_verified_category(Ls, R), |
| 633 | Out =.. [submit | R]. |
| 634 | |
| 635 | remove_verified_category([], []). |
| 636 | remove_verified_category([label('Verified', _) | T], R) :- |
| 637 | remove_verified_category(T, R), !. |
| 638 | remove_verified_category([H|T], [H|R]) :- remove_verified_category(T, R). |
| 639 | ==== |
| 640 | |
Johan Björk | ef02854 | 2012-10-24 12:06:33 -0400 | [diff] [blame^] | 641 | Example 12: 1+1=2 Code-Review |
| 642 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| 643 | In this example we introduce accumulative voting to determine if a change is |
| 644 | submittable or not. We modify the standard Code-Review to be accumulative, and make the |
| 645 | change submittable if the total score is 2 or higher. |
| 646 | |
| 647 | The code in this example is very similar to Example 8, with the addition of findall/3 |
| 648 | and gerrit:remove_label. |
| 649 | The findall/3 embedded predicate is used to form a list of all objects that satisfy a |
| 650 | specified Goal. In this example it is used to get a list of all the 'Code-Review' scores. |
| 651 | gerrit:remove_label is a built-in helper that is implemented similarly to the |
| 652 | 'remove_verified_category' as seen in the previous example. |
| 653 | |
| 654 | .rules.pl |
| 655 | [caption=""] |
| 656 | ==== |
| 657 | sum_list([], 0). |
| 658 | sum_list([H | Rest], Sum) :- sum_list(Rest,Tmp), Sum is H + Tmp. |
| 659 | |
| 660 | add_category_min_score(In, Category, Min, P) :- |
| 661 | findall(X, gerrit:commit_label(label(Category,X),R),Z), |
| 662 | sum_list(Z, Sum), |
| 663 | Sum >= Min, !, |
| 664 | P = [label(Category,ok(R)) | In]. |
| 665 | |
| 666 | add_category_min_score(In, Category,Min,P) :- |
| 667 | P = [label(Category,need(Min)) | In]. |
| 668 | |
| 669 | submit_rule(S) :- |
| 670 | gerrit:default_submit(X), |
| 671 | X =.. [submit | Ls], |
| 672 | gerrit:remove_label(Ls,label('Code-Review',_),NoCR), |
| 673 | add_category_min_score(NoCR,'Code-Review', 2, Labels), |
| 674 | S =.. [submit | Labels]. |
| 675 | ==== |
| 676 | |
Sasa Zivkov | ae50f71 | 2012-07-24 14:51:44 +0200 | [diff] [blame] | 677 | GERRIT |
| 678 | ------ |
| 679 | Part of link:index.html[Gerrit Code Review] |