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 | |
Sasa Zivkov | b91296d | 2012-11-08 14:19:12 +0100 | [diff] [blame] | 31 | Submit Type |
| 32 | ----------- |
| 33 | A 'Submit Type' is a strategy that is used on submit to integrate the |
| 34 | change into the destination branch. Supported submit types are: |
| 35 | |
| 36 | * `Fast Forward Only` |
| 37 | * `Merge If Necessary` |
| 38 | * `Merge Always` |
| 39 | * `Cherry Pick` |
| 40 | * `Rebase If Necessary` |
| 41 | |
| 42 | 'Submit Type' is a project global setting. This means that the same submit type |
| 43 | is used for all changes of one project. |
| 44 | |
| 45 | Projects which need more flexibility in choosing, or enforcing, a submit type |
| 46 | can use Prolog based submit type which replaces the project's default submit |
| 47 | type. |
| 48 | |
| 49 | Prolog based submit type computes a submit type for each change. The computed |
| 50 | submit type is shown on the change screen for each change. |
| 51 | |
Sasa Zivkov | ae50f71 | 2012-07-24 14:51:44 +0200 | [diff] [blame] | 52 | Prolog Language |
| 53 | --------------- |
| 54 | This document is not a complete Prolog tutorial. |
| 55 | link:http://en.wikipedia.org/wiki/Prolog[This Wikipedia page on Prolog] is a |
| 56 | good starting point for learning the Prolog language. This document will only explain |
| 57 | some elements of Prolog that are necessary to understand the provided examples. |
| 58 | |
| 59 | Prolog in Gerrit |
| 60 | ---------------- |
| 61 | Gerrit uses its own link:https://code.google.com/p/prolog-cafe/[fork] of the |
| 62 | original link:http://kaminari.istc.kobe-u.ac.jp/PrologCafe/[prolog-cafe] |
| 63 | project. Gerrit embeds the prolog-cafe library and can interpret Prolog programs at |
| 64 | runtime. |
| 65 | |
| 66 | Interactive Prolog Cafe Shell |
| 67 | ----------------------------- |
| 68 | For interactive testing and playing with Prolog, Gerrit provides the |
| 69 | link:pgm-prolog-shell.html[prolog-shell] program which opens an interactive |
| 70 | Prolog interpreter shell. |
| 71 | |
Johan Björk | 2119f05 | 2012-10-24 12:10:45 -0400 | [diff] [blame] | 72 | NOTE: The interactive shell is just a prolog shell, it does not load |
| 73 | a gerrit server environment and thus is not intended for xref:TestingSubmitRules[testing submit rules]. |
Sasa Zivkov | ae50f71 | 2012-07-24 14:51:44 +0200 | [diff] [blame] | 74 | |
| 75 | SWI-Prolog |
| 76 | ---------- |
| 77 | Instead of using the link:pgm-prolog-shell.html[prolog-shell] program one can |
| 78 | also use the link:http://www.swi-prolog.org/[SWI-Prolog] environment. It |
| 79 | provides a better shell interface and a graphical source-level debugger. |
| 80 | |
| 81 | The rules.pl file |
| 82 | ----------------- |
| 83 | This section explains how to create and edit project specific submit rules. How |
| 84 | to actually write the submit rules is explained in the next section. |
| 85 | |
| 86 | Project specific submit rules are stored in the `rules.pl` file in the |
| 87 | `refs/meta/config` branch of that project. Therefore, we need to fetch and |
| 88 | checkout the `refs/meta/config` branch in order to create or edit the `rules.pl` |
| 89 | file: |
| 90 | |
| 91 | ==== |
| 92 | $ git fetch origin refs/meta/config:config |
| 93 | $ git checkout config |
| 94 | ... edit or create the rules.pl file |
| 95 | $ git add rules.pl |
| 96 | $ git commit -m "My submit rules" |
| 97 | $ git push origin HEAD:refs/meta/config |
| 98 | ==== |
| 99 | |
Sasa Zivkov | b91296d | 2012-11-08 14:19:12 +0100 | [diff] [blame] | 100 | [[HowToWriteSubmitRules]] |
Sasa Zivkov | ae50f71 | 2012-07-24 14:51:44 +0200 | [diff] [blame] | 101 | How to write submit rules |
| 102 | ------------------------- |
| 103 | Whenever Gerrit needs to evaluate submit rules for a change `C` from project `P` it |
| 104 | will first initialize the embedded Prolog interpreter by: |
| 105 | |
| 106 | * consulting a set of facts about the change `C` |
| 107 | * consulting the `rules.pl` from the project `P` |
| 108 | |
| 109 | Conceptually we can imagine that Gerrit adds a set of facts about the change |
| 110 | `C` on top of the `rules.pl` file and then consults it. The set of facts about |
| 111 | the change `C` will look like: |
| 112 | |
| 113 | ==== |
| 114 | :- package gerrit. <1> |
| 115 | |
| 116 | commit_author(user(1000000), 'John Doe', 'john.doe@example.com'). <2> |
| 117 | commit_committer(user(1000000), 'John Doe', 'john.doe@example.com'). <3> |
| 118 | commit_message('Add plugin support to Gerrit'). <4> |
| 119 | ... |
| 120 | ==== |
| 121 | |
| 122 | <1> Gerrit will provide its facts in a package named `gerrit`. This means we |
| 123 | have to use qualified names when writing our code and referencing these facts. |
| 124 | For example: `gerrit:commit_author(ID, N, M)` |
| 125 | <2> user ID, full name and email address of the commit author |
| 126 | <3> user ID, full name and email address of the commit committer |
| 127 | <4> commit message |
| 128 | |
| 129 | A complete set of facts which Gerrit provides about the change is listed in the |
| 130 | link:prolog-change-facts.html[Prolog Facts for Gerrit Change]. |
| 131 | |
| 132 | By default, Gerrit will search for a `submit_rule/1` predicate in the `rules.pl` |
| 133 | file, evaluate the `submit_rule(X)` and then inspect the value of `X` in order |
| 134 | to decide whether the change is submittable or not and also to find the set of |
| 135 | needed criteria for the change to become submittable. This means that Gerrit has an |
| 136 | expectation on the format and value of the result of the `submit_rule` predicate |
| 137 | which is expected to be a `submit` term of the following format: |
| 138 | |
| 139 | ==== |
| 140 | submit(label(label-name, status) [, label(label-name, status)]*) |
| 141 | ==== |
| 142 | |
| 143 | where `label-name` is usually `'Code-Review'` or `'Verified'` but could also |
| 144 | be any other string (see examples below). The `status` is one of: |
| 145 | |
| 146 | * `ok(user(ID))` or just `ok(_)` if user info is not important. This status is |
| 147 | used to tell that this label/category has been met. |
| 148 | * `need(_)` is used to tell that this label/category is needed for change to |
| 149 | become submittable |
| 150 | * `reject(user(ID))` or just `reject(_)`. This status is used to tell that label/category |
| 151 | is blocking change submission |
| 152 | * `impossible(_)` is used when the logic knows that the change cannot be submitted as-is. |
| 153 | Administrative intervention is probably required. This is meant for cases |
| 154 | where the logic requires members of "FooEng" to score "Code-Review +2" on a |
| 155 | change, but nobody is in group "FooEng". It is to hint at permissions |
| 156 | misconfigurations. |
| 157 | * `may(_)` allows expression of approval categories that are optional, i.e. |
| 158 | could either be set or unset without ever influencing whether the change |
| 159 | could be submitted. |
| 160 | |
| 161 | NOTE: For a change to be submittable all `label` terms contained in the returned |
| 162 | `submit` term must have either `ok` or `may` status. |
| 163 | |
| 164 | IMPORTANT: Gerrit will let the Prolog engine continue searching for solutions of |
| 165 | the `submit_rule(X)` query until it finds the first one where all labels in the |
| 166 | return result have either status `ok` or `may` or there are no more solutions. |
| 167 | If a solution where all labels have status `ok` is found then all previously |
| 168 | found solutions are ignored. Otherwise, all labels names with status `need` |
| 169 | from all solutions will be displayed in the UI indicating the set of conditions |
| 170 | needed for the change to become submittable. |
| 171 | |
| 172 | Here some examples of possible return values from the `submit_rule` predicate: |
| 173 | |
| 174 | ==== |
| 175 | submit(label('Code-Review', ok(_))) <1> |
| 176 | submit(label('Code-Review', ok(_)), label('Verified', reject(_))) <2> |
| 177 | submit(label('Author-is-John-Doe', need(_)) <3> |
| 178 | ==== |
| 179 | |
| 180 | <1> label `'Code-Review'` is met. As there are no other labels in the |
| 181 | return result, the change is submittable. |
| 182 | <2> label `'Verified'` is rejected. Change is not submittable. |
| 183 | <3> label `'Author-is-John-Doe'` is needed for the change to become submittable. |
| 184 | Note that this tells nothing about how this criteria will be met. It is up |
David Pursehouse | 9246356 | 2013-06-24 10:16:28 +0900 | [diff] [blame] | 185 | to the implementer of the `submit_rule` to return `label('Author-is-John-Doe', |
Sasa Zivkov | ae50f71 | 2012-07-24 14:51:44 +0200 | [diff] [blame] | 186 | ok(_))` when this criteria is met. Most likely, it will have to match |
| 187 | against `gerrit:commit_author` in order to check if this criteria is met. |
| 188 | This will become clear through the examples below. |
| 189 | |
| 190 | Of course, when implementing the `submit_rule` we will use the facts about the |
| 191 | change that are already provided by Gerrit. |
| 192 | |
| 193 | Another aspect of the return result from the `submit_rule` predicate is that |
| 194 | Gerrit uses it to decide which set of labels to display on the change review |
| 195 | screen for voting. If the return result contains label `'ABC'` and if the label |
Dave Borowitz | 01c1b1f | 2013-02-27 13:49:04 -0800 | [diff] [blame] | 196 | `'ABC'` is link:config-labels.html[defined for the project] then voting for the |
| 197 | label `'ABC'` will be displayed. Otherwise, it is not displayed. Note that the |
| 198 | project doesn't need a defined label for each label contained in the result of |
Sasa Zivkov | ae50f71 | 2012-07-24 14:51:44 +0200 | [diff] [blame] | 199 | `submit_rule` predicate. For example, the decision whether `'Author-is-John-Doe'` |
| 200 | label is met will probably not be made by explicit voting but, instead, by |
| 201 | inspecting the facts about the change. |
| 202 | |
Sasa Zivkov | b91296d | 2012-11-08 14:19:12 +0100 | [diff] [blame] | 203 | [[SubmitFilter]] |
Sasa Zivkov | 3d4f0aa | 2012-08-07 15:11:32 +0200 | [diff] [blame] | 204 | Submit Filter |
| 205 | ------------- |
| 206 | Another mechanism of changing the default submit rules is to implement the |
| 207 | `submit_filter/2` predicate. While Gerrit will search for the `submit_rule` only |
| 208 | in the `rules.pl` file of the current project, the `submit_filter` will be |
| 209 | searched for in the `rules.pl` of all parent projects of the current project, |
| 210 | but not in the `rules.pl` of the current project. The search will start from the |
| 211 | immediate parent of the current project, then in the parent project of that |
| 212 | project and so on until, and including, the 'All-Projects' project. |
| 213 | |
| 214 | The purpose of the submit filter is, as its name says, to filter the results |
| 215 | of the `submit_rule`. Therefore, the `submit_filter` predicate has two |
| 216 | parameters: |
| 217 | |
| 218 | ==== |
| 219 | submit_filter(In, Out) :- ... |
| 220 | ==== |
| 221 | |
| 222 | Gerrit will invoke `submit_filter` with the `In` parameter containing a `submit` |
| 223 | structure produced by the `submit_rule` and will take the value of the `Out` |
| 224 | parameter as the result. |
| 225 | |
| 226 | The `Out` value of a `submit_filter` will become the `In` value for the |
| 227 | next `submit_filter` in the parent line. The value of the `Out` parameter |
| 228 | of the top-most `submit_filter` is the final result of the submit rule that |
| 229 | is used to decide whether a change is submittable or not. |
| 230 | |
| 231 | IMPORTANT: `submit_filter` is a mechanism for Gerrit administrators to implement |
| 232 | and enforce submit rules that would apply to all projects while `submit_rule` is |
| 233 | a mechanism for project owners to implement project specific submit rules. |
| 234 | However, project owners who own several projects could also make use of |
| 235 | `submit_filter` by using a common parent project for all their projects and |
| 236 | implementing the `submit_filter` in this common parent project. This way they |
| 237 | can avoid implementing the same `submit_rule` in all their projects. |
| 238 | |
| 239 | The following "drawing" illustrates the order of the invocation and the chaining |
| 240 | of the results of the `submit_rule` and `submit_filter` predicates. |
Sasa Zivkov | 3d4f0aa | 2012-08-07 15:11:32 +0200 | [diff] [blame] | 241 | ==== |
| 242 | All-Projects |
| 243 | ^ submit_filter(B, S) :- ... <4> |
| 244 | | |
| 245 | Parent-3 |
| 246 | ^ <no submit filter here> |
| 247 | | |
| 248 | Parent-2 |
| 249 | ^ submit_filter(A, B) :- ... <3> |
| 250 | | |
| 251 | Parent-1 |
| 252 | ^ submit_filter(X, A) :- ... <2> |
| 253 | | |
| 254 | MyProject |
| 255 | submit_rule(X) :- ... <1> |
| 256 | ==== |
| 257 | |
| 258 | <1> The `submit_rule` of `MyProject` is invoked first. |
| 259 | <2> The result `X` is filtered through the `submit_filter` from the `Parent-1` |
| 260 | project. |
| 261 | <3> The result of `submit_filter` from `Parent-1` project is filtered by the |
| 262 | `submit_filter` in the `Parent-2` project. Since `Parent-3` project doesn't have |
| 263 | a `submit_filter` it is skipped. |
| 264 | <4> The result of `submit_filter` from `Parent-2` project is filtered by the |
| 265 | `submit_filter` in the `All-Projects` project. The value in `S` is the final |
| 266 | value of the submit rule evaluation. |
| 267 | |
| 268 | NOTE: If `MyProject` doesn't define its own `submit_rule` Gerrit will invoke the |
| 269 | default implementation of submit rule that is named `gerrit:default_submit` and |
| 270 | its result will be filtered as described above. |
| 271 | |
Edwin Kempin | ca24f5c | 2013-03-26 13:23:20 +0100 | [diff] [blame] | 272 | [[HowToWriteSubmitType]] |
Sasa Zivkov | b91296d | 2012-11-08 14:19:12 +0100 | [diff] [blame] | 273 | How to write submit type |
| 274 | ------------------------ |
| 275 | Writing custom submit type logic in Prolog is the similar top |
| 276 | xref:HowToWriteSubmitRules[writing submit rules]. The only difference is that |
| 277 | one has to implement a `submit_type` predicate (instead of the `submit_rule`) |
| 278 | and that the return result of the `submit_type` has to be an atom that |
| 279 | represents one of the supported submit types: |
| 280 | |
| 281 | * `fast_forward_only` |
| 282 | * `merge_if_necessary` |
| 283 | * `merge_always` |
| 284 | * `cherry_pick` |
| 285 | * `rebase_if_necessary` |
| 286 | |
| 287 | Submit Type Filter |
| 288 | ------------------ |
| 289 | Submit type filter works the same way as the xref:SubmitFilter[Submit Filter] |
| 290 | where the name of the filter predicate is `submit_type_filter`. |
| 291 | |
| 292 | ==== |
| 293 | submit_type_filter(In, Out). |
| 294 | ==== |
| 295 | |
| 296 | Gerrit will invoke `submit_type_filter` with the `In` parameter containing a |
| 297 | result of the `submit_type` and will take the value of the `Out` parameter as |
| 298 | the result. |
| 299 | |
Johan Björk | 2119f05 | 2012-10-24 12:10:45 -0400 | [diff] [blame] | 300 | [[TestingSubmitRules]] |
| 301 | Testing submit rules |
| 302 | -------------------- |
| 303 | The prolog environment running the `submit_rule` is loaded with state describing the |
| 304 | change that is being evaluated. The easiest way to load this state is to test your |
| 305 | `submit_rule` against a real change on a running gerrit instance. The command |
Edwin Kempin | f9f46bd | 2013-09-02 15:46:23 +0200 | [diff] [blame] | 306 | link:cmd-test-submit-rule.html[test-submit rule] loads a specific change and executes |
Johan Björk | 2119f05 | 2012-10-24 12:10:45 -0400 | [diff] [blame] | 307 | the `submit_rule`. It optionally reads the rule from from `stdin` to facilitate easy testing. |
| 308 | |
| 309 | ==== |
Edwin Kempin | f9f46bd | 2013-09-02 15:46:23 +0200 | [diff] [blame] | 310 | cat rules.pl | ssh gerrit_srv gerrit test-submit rule I45e080b105a50a625cc8e1fb5b357c0bfabe6d68 -s |
Johan Björk | 2119f05 | 2012-10-24 12:10:45 -0400 | [diff] [blame] | 311 | ==== |
Sasa Zivkov | 3d4f0aa | 2012-08-07 15:11:32 +0200 | [diff] [blame] | 312 | |
Sasa Zivkov | ae50f71 | 2012-07-24 14:51:44 +0200 | [diff] [blame] | 313 | Prolog vs Gerrit plugin for project specific submit rules |
| 314 | --------------------------------------------------------- |
| 315 | Since version 2.5 Gerrit supports plugins and extension points. A plugin or an |
| 316 | extension point could also be used as another means to provide custom submit |
| 317 | rules. One could ask for a guideline when to use Prolog based submit rules and |
| 318 | when to go for writing a new plugin. Writing a Prolog program is usually much |
| 319 | faster than writing a Gerrit plugin. Prolog based submit rules can be pushed |
| 320 | to a project by project owners while Gerrit plugins could only be installed by |
| 321 | Gerrit administrators. In addition, Prolog based submit rules can be pushed |
| 322 | for review by pushing to `refs/for/refs/meta/config` branch. |
| 323 | |
| 324 | On the other hand, Prolog based submit rules get a limited amount of facts about |
| 325 | the change exposed to them. Gerrit plugins get full access to Gerrit internals |
| 326 | and can potentially check more things than Prolog based rules. |
| 327 | |
Sasa Zivkov | d0e5526 | 2013-01-16 14:26:06 +0100 | [diff] [blame] | 328 | From version 2.6 Gerrit plugins can contribute Prolog predicates. This way, we |
| 329 | can make use of the plugin provided predicates when writing Prolog based rules. |
| 330 | |
Sasa Zivkov | b91296d | 2012-11-08 14:19:12 +0100 | [diff] [blame] | 331 | Examples - Submit Rule |
| 332 | ---------------------- |
Sasa Zivkov | ae50f71 | 2012-07-24 14:51:44 +0200 | [diff] [blame] | 333 | The following examples should serve as a cookbook for developing own submit rules. |
| 334 | Some of them are too trivial to be used in production and their only purpose is |
| 335 | to provide step by step introduction and understanding. |
| 336 | |
Sasa Zivkov | 3d4f0aa | 2012-08-07 15:11:32 +0200 | [diff] [blame] | 337 | Some of the examples will implement the `submit_rule` and some will implement |
| 338 | the `submit_filter` just to show both possibilities. Remember that |
| 339 | `submit_rule` is only invoked from the current project and `submit_filter` is |
| 340 | invoked from all parent projects. This is the most important fact in deciding |
| 341 | whether to implement `submit_rule` or `submit_filter`. |
| 342 | |
Sasa Zivkov | ae50f71 | 2012-07-24 14:51:44 +0200 | [diff] [blame] | 343 | Example 1: Make every change submittable |
| 344 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| 345 | Let's start with a most trivial example where we would make every change submittable |
| 346 | regardless of the votes it has: |
| 347 | |
| 348 | .rules.pl |
| 349 | [caption=""] |
| 350 | ==== |
Sasa Zivkov | 79b08cc | 2013-01-16 17:00:54 +0100 | [diff] [blame] | 351 | submit_rule(submit(W)) :- |
| 352 | W = label('Any-Label-Name', ok(_)). |
Sasa Zivkov | ae50f71 | 2012-07-24 14:51:44 +0200 | [diff] [blame] | 353 | ==== |
| 354 | |
| 355 | In this case we make no use of facts about the change. We don't need it as we are simply |
| 356 | making every change submittable. Note that, in this case, the Gerrit UI will not show |
| 357 | the UI for voting for the standard `'Code-Review'` and `'Verified'` categories as labels |
| 358 | with these names are not part of the return result. The `'Any-Label-Name'` could really |
| 359 | be any string. |
| 360 | |
| 361 | Example 2: Every change submittable and voting in the standard categories possible |
| 362 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| 363 | This is continuation of the previous example where, in addition, to making |
| 364 | every change submittable we want to enable voting in the standard |
| 365 | `'Code-Review'` and `'Verified'` categories. |
| 366 | |
| 367 | .rules.pl |
| 368 | [caption=""] |
| 369 | ==== |
Sasa Zivkov | 79b08cc | 2013-01-16 17:00:54 +0100 | [diff] [blame] | 370 | submit_rule(submit(CR, V)) :- |
| 371 | CR = label('Code-Review', ok(_)), |
| 372 | V = label('Verified', ok(_)). |
Sasa Zivkov | ae50f71 | 2012-07-24 14:51:44 +0200 | [diff] [blame] | 373 | ==== |
| 374 | |
| 375 | Since for every change all label statuses are `'ok'` every change will be submittable. |
| 376 | Voting in the standard labels will be shown in the UI as the standard label names are |
| 377 | included in the return result. |
| 378 | |
| 379 | Example 3: Nothing is submittable |
| 380 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| 381 | This example shows how to make all changes non-submittable regardless of the |
| 382 | votes they have. |
| 383 | |
| 384 | .rules.pl |
| 385 | [caption=""] |
| 386 | ==== |
Sasa Zivkov | 79b08cc | 2013-01-16 17:00:54 +0100 | [diff] [blame] | 387 | submit_rule(submit(R)) :- |
| 388 | R = label('Any-Label-Name', reject(_)). |
Sasa Zivkov | ae50f71 | 2012-07-24 14:51:44 +0200 | [diff] [blame] | 389 | ==== |
| 390 | |
| 391 | Since for any change we return only one label with status `reject`, no change |
| 392 | will be submittable. The UI will, however, not indicate what is needed for a |
| 393 | change to become submittable as we return no labels with status `need`. |
| 394 | |
| 395 | Example 4: Nothing is submittable but UI shows several 'Need ...' criteria |
| 396 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| 397 | In this example no change is submittable but here we show how to present 'Need |
| 398 | <label>' information to the user in the UI. |
| 399 | |
| 400 | .rules.pl |
| 401 | [caption=""] |
| 402 | ==== |
| 403 | % In the UI this will show: Need Any-Label-Name |
Sasa Zivkov | 79b08cc | 2013-01-16 17:00:54 +0100 | [diff] [blame] | 404 | submit_rule(submit(N)) :- |
| 405 | N = label('Any-Label-Name', need(_)). |
Sasa Zivkov | ae50f71 | 2012-07-24 14:51:44 +0200 | [diff] [blame] | 406 | |
| 407 | % We could define more "need" labels by adding more rules |
Sasa Zivkov | 79b08cc | 2013-01-16 17:00:54 +0100 | [diff] [blame] | 408 | submit_rule(submit(N)) :- |
| 409 | N = label('Another-Label-Name', need(_)). |
Sasa Zivkov | ae50f71 | 2012-07-24 14:51:44 +0200 | [diff] [blame] | 410 | |
| 411 | % or by providing more than one need label in the same rule |
Sasa Zivkov | 79b08cc | 2013-01-16 17:00:54 +0100 | [diff] [blame] | 412 | submit_rule(submit(NX, NY)) :- |
| 413 | NX = label('X-Label-Name', need(_)), |
| 414 | NY = label('Y-Label-Name', need(_)). |
Sasa Zivkov | ae50f71 | 2012-07-24 14:51:44 +0200 | [diff] [blame] | 415 | ==== |
| 416 | |
| 417 | In the UI this will show: |
| 418 | **** |
| 419 | * Need Any-Label-Name |
| 420 | * Need Another-Label-Name |
| 421 | * Need X-Label-Name |
| 422 | * Need Y-Label-Name |
| 423 | **** |
| 424 | |
| 425 | From the example above we can see a few more things: |
| 426 | |
| 427 | * comment in Prolog starts with the `%` character |
| 428 | * there could be multiple `submit_rule` predicates. Since Prolog, by default, tries to find |
| 429 | all solutions for a query, the result will be union of all solutions. |
| 430 | Therefore, we see all 4 `need` labels in the UI. |
| 431 | |
| 432 | Example 5: The 'Need ...' labels not shown when change is submittable |
| 433 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| 434 | This example shows that, when there is a solution for `submit_rule(X)` where all labels |
| 435 | have status `ok` then Gerrit will not show any labels with the `need` status from |
| 436 | any of the previous `submit_rule(X)` solutions. |
| 437 | |
| 438 | .rules.pl |
| 439 | [caption=""] |
| 440 | ==== |
Sasa Zivkov | 79b08cc | 2013-01-16 17:00:54 +0100 | [diff] [blame] | 441 | submit_rule(submit(N)) :- |
| 442 | N = label('Some-Condition', need(_)). |
| 443 | |
| 444 | submit_rule(submit(OK)) :- |
| 445 | OK = label('Another-Condition', ok(_)). |
Sasa Zivkov | ae50f71 | 2012-07-24 14:51:44 +0200 | [diff] [blame] | 446 | ==== |
| 447 | |
| 448 | The 'Need Some-Condition' will not be show in the UI because of the result of |
| 449 | the second rule. |
| 450 | |
| 451 | The same is valid if the two rules are swapped: |
| 452 | |
| 453 | .rules.pl |
| 454 | [caption=""] |
| 455 | ==== |
Sasa Zivkov | 79b08cc | 2013-01-16 17:00:54 +0100 | [diff] [blame] | 456 | submit_rule(submit(OK)) :- |
| 457 | OK = label('Another-Condition', ok(_)). |
| 458 | |
| 459 | submit_rule(submit(N)) :- |
| 460 | N = label('Some-Condition', need(_)). |
Sasa Zivkov | ae50f71 | 2012-07-24 14:51:44 +0200 | [diff] [blame] | 461 | ==== |
| 462 | |
| 463 | The result of the first rule will stop search for any further solutions. |
| 464 | |
| 465 | Example 6: Make change submittable if commit author is "John Doe" |
| 466 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| 467 | This is the first example where we will use the Prolog facts about a change that |
| 468 | are automatically exposed by Gerrit. Our goal is to make any change submittable |
| 469 | when the commit author is named `'John Doe'`. In the very first |
| 470 | step let's make sure Gerrit UI shows 'Need Author-is-John-Doe' in |
| 471 | the UI to clearly indicate to the user what is needed for a change to become |
| 472 | submittable: |
| 473 | |
| 474 | .rules.pl |
| 475 | [caption=""] |
| 476 | ==== |
Sasa Zivkov | 79b08cc | 2013-01-16 17:00:54 +0100 | [diff] [blame] | 477 | submit_rule(submit(Author)) :- |
| 478 | Author = label('Author-is-John-Doe', need(_)). |
Sasa Zivkov | ae50f71 | 2012-07-24 14:51:44 +0200 | [diff] [blame] | 479 | ==== |
| 480 | |
| 481 | This will show: |
| 482 | **** |
| 483 | * Need Author-is-John-Doe |
| 484 | **** |
| 485 | |
| 486 | in the UI but no change will be submittable yet. Let's add another rule: |
| 487 | |
| 488 | .rules.pl |
| 489 | [caption=""] |
| 490 | ==== |
Sasa Zivkov | 79b08cc | 2013-01-16 17:00:54 +0100 | [diff] [blame] | 491 | submit_rule(submit(Author)) :- |
| 492 | Author = label('Author-is-John-Doe', need(_)). |
| 493 | |
| 494 | submit_rule(submit(Author)) :- |
| 495 | gerrit:commit_author(_, 'John Doe', _), |
| 496 | Author = label('Author-is-John-Doe', ok(_)). |
Sasa Zivkov | ae50f71 | 2012-07-24 14:51:44 +0200 | [diff] [blame] | 497 | ==== |
| 498 | |
| 499 | In the second rule we return `ok` status for the `'Author-is-John-Doe'` label |
| 500 | if there is a `commit_author` fact where the full name is `'John Doe'`. If |
| 501 | author of a change is `'John Doe'` then the second rule will return a solution |
| 502 | where all labels have `ok` status and the change will become submittable. If |
| 503 | author of a change is not `'John Doe'` then only the first rule will produce a |
| 504 | solution. The UI will show 'Need Author-is-John-Doe' but, as expected, the |
| 505 | change will not be submittable. |
| 506 | |
| 507 | Instead of checking by full name we could also check by the email address: |
| 508 | |
| 509 | .rules.pl |
| 510 | [caption=""] |
| 511 | ==== |
Sasa Zivkov | 79b08cc | 2013-01-16 17:00:54 +0100 | [diff] [blame] | 512 | submit_rule(submit(Author)) :- |
| 513 | Author = label('Author-is-John-Doe', need(_)). |
| 514 | |
| 515 | submit_rule(submit(Author)) :- |
| 516 | gerrit:commit_author(_, _, 'john.doe@example.com'), |
| 517 | Author = label('Author-is-John-Doe', ok(_)). |
Sasa Zivkov | ae50f71 | 2012-07-24 14:51:44 +0200 | [diff] [blame] | 518 | ==== |
| 519 | |
| 520 | or by user id (assuming it is 1000000): |
| 521 | |
| 522 | .rules.pl |
| 523 | [caption=""] |
| 524 | ==== |
Sasa Zivkov | 79b08cc | 2013-01-16 17:00:54 +0100 | [diff] [blame] | 525 | submit_rule(submit(Author)) :- |
| 526 | Author = label('Author-is-John-Doe', need(_)). |
| 527 | |
| 528 | submit_rule(submit(Author)) :- |
| 529 | gerrit:commit_author(user(1000000), _, _), |
| 530 | Author = label('Author-is-John-Doe', ok(_)). |
Sasa Zivkov | ae50f71 | 2012-07-24 14:51:44 +0200 | [diff] [blame] | 531 | ==== |
| 532 | |
| 533 | or by a combination of these 3 attributes: |
| 534 | |
| 535 | .rules.pl |
| 536 | [caption=""] |
| 537 | ==== |
Sasa Zivkov | 79b08cc | 2013-01-16 17:00:54 +0100 | [diff] [blame] | 538 | submit_rule(submit(Author)) :- |
| 539 | Author = label('Author-is-John-Doe', need(_)). |
| 540 | |
| 541 | submit_rule(submit(Author)) :- |
| 542 | gerrit:commit_author(_, 'John Doe', 'john.doe@example.com'), |
| 543 | Author = label('Author-is-John-Doe', ok(_)). |
Sasa Zivkov | ae50f71 | 2012-07-24 14:51:44 +0200 | [diff] [blame] | 544 | ==== |
| 545 | |
Sasa Zivkov | 79b08cc | 2013-01-16 17:00:54 +0100 | [diff] [blame] | 546 | Example 7: Make change submittable if commit message starts with "Fix " |
| 547 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
Sasa Zivkov | ae50f71 | 2012-07-24 14:51:44 +0200 | [diff] [blame] | 548 | Besides showing how to make use of the commit message text the purpose of this |
| 549 | example is also to show how to match only a part of a string symbol. Similarly |
| 550 | like commit author the commit message is provided as a string symbol which is |
| 551 | an atom in Prolog terms. When working with an atom we could only match against |
| 552 | the whole value. To match only part of a string symbol we have, at least, two |
| 553 | options: |
| 554 | |
| 555 | * convert the string symbol into a list of characters and then perform |
| 556 | the "classical" list matching |
| 557 | * use the `regex_matches/2` or, even more convenient, the |
| 558 | `gerrit:commit_message_matches/1` predicate |
| 559 | |
| 560 | Let's implement both options: |
| 561 | |
| 562 | .rules.pl |
| 563 | [caption=""] |
| 564 | ==== |
Sasa Zivkov | 79b08cc | 2013-01-16 17:00:54 +0100 | [diff] [blame] | 565 | submit_rule(submit(Fix)) :- |
| 566 | Fix = label('Commit-Message-starts-with-Fix', need(_)). |
| 567 | |
| 568 | submit_rule(submit(Fix)) :- |
| 569 | gerrit:commit_message(M), name(M, L), starts_with(L, "Fix "), |
| 570 | Fix = label('Commit-Message-starts-with-Fix', ok(_)). |
Sasa Zivkov | ae50f71 | 2012-07-24 14:51:44 +0200 | [diff] [blame] | 571 | |
| 572 | starts_with(L, []). |
| 573 | starts_with([H|T1], [H|T2]) :- starts_with(T1, T2). |
| 574 | ==== |
| 575 | |
| 576 | NOTE: The `name/2` embedded predicate is used to convert a string symbol into a |
| 577 | list of characters. A string `abc` is converted into a list of characters `[97, |
| 578 | 98, 99]`. A double quoted string in Prolog is just a shortcut for creating a |
| 579 | list of characters. `"abc"` is a shortcut for `[97, 98, 99]`. This is why we use |
| 580 | double quotes for the `"Trivial Fix"` in the example above. |
| 581 | |
| 582 | The `starts_with` predicate is self explaining. |
| 583 | |
| 584 | Using the `gerrit:commit_message_matches` predicate is probably more efficient: |
| 585 | |
| 586 | .rules.pl |
| 587 | [caption=""] |
| 588 | ==== |
Sasa Zivkov | 79b08cc | 2013-01-16 17:00:54 +0100 | [diff] [blame] | 589 | submit_rule(submit(Fix)) :- |
| 590 | Fix = label('Commit-Message-starts-with-Fix', need(_)). |
| 591 | |
| 592 | submit_rule(submit(Fix)) :- |
| 593 | gerrit:commit_message_matches('^Fix '), |
| 594 | Fix = label('Commit-Message-starts-with-Fix', ok(_)). |
Sasa Zivkov | ae50f71 | 2012-07-24 14:51:44 +0200 | [diff] [blame] | 595 | ==== |
| 596 | |
Sasa Zivkov | 79b08cc | 2013-01-16 17:00:54 +0100 | [diff] [blame] | 597 | The previous example could also be written so that it first checks if the commit |
| 598 | message starts with 'Fix '. If true then it sets OK for that category and stops |
| 599 | further backtracking by using the cut `!` operator: |
| 600 | .rules.pl |
| 601 | [caption=""] |
| 602 | ==== |
| 603 | submit_rule(submit(Fix)) :- |
| 604 | gerrit:commit_message_matches('^Fix '), |
| 605 | Fix = label('Commit-Message-starts-with-Fix', ok(_)), |
| 606 | !. |
| 607 | |
| 608 | % Message does not start with 'Fix ' so Fix is needed to submit |
| 609 | submit_rule(submit(Fix)) :- |
| 610 | Fix = label('Commit-Message-starts-with-Fix', need(_)). |
| 611 | ==== |
| 612 | |
| 613 | The default submit policy |
| 614 | ------------------------- |
Sasa Zivkov | ae50f71 | 2012-07-24 14:51:44 +0200 | [diff] [blame] | 615 | All examples until now concentrate on one particular aspect of change data. |
| 616 | However, in real-life scenarios we would rather want to reuse Gerrit's default |
Sasa Zivkov | 79b08cc | 2013-01-16 17:00:54 +0100 | [diff] [blame] | 617 | submit policy and extend/change it for our specific purpose. This could be |
| 618 | done in one of the following ways: |
Sasa Zivkov | ae50f71 | 2012-07-24 14:51:44 +0200 | [diff] [blame] | 619 | |
Sasa Zivkov | 79b08cc | 2013-01-16 17:00:54 +0100 | [diff] [blame] | 620 | * understand how the default submit policy is implemented and use that as a |
| 621 | template for implementing custom submit rules, |
| 622 | * invoke the default submit rule implementation and then perform further |
| 623 | actions on its return result. |
| 624 | |
| 625 | Default submit rule implementation |
| 626 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| 627 | The default submit rule with the two default categories, `Code-Review` and |
| 628 | `Verified`, can be implemented as: |
| 629 | |
| 630 | .rules.pl |
| 631 | [caption=""] |
| 632 | ==== |
| 633 | submit_rule(submit(V, CR)) :- |
| 634 | gerrit:max_with_block(-2, 2, 'Code-Review', CR), |
| 635 | gerrit:max_with_block(-1, 1, 'Verified', V). |
| 636 | ==== |
| 637 | |
| 638 | Once this implementation is understood it can be customized to implement |
| 639 | project specific submit rules. Note, that this implementation hardcodes |
| 640 | the two default categories. Introducing a new category in the database would |
| 641 | require introducing the same category here or a `submit_filter` in a parent |
| 642 | project would have to care about including the new category in the result of |
| 643 | this `submit_rule`. On the other side, this example is easy to read and |
| 644 | understand. |
| 645 | |
| 646 | Reusing the default submit policy |
| 647 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
David Pursehouse | 9246356 | 2013-06-24 10:16:28 +0900 | [diff] [blame] | 648 | To get results of Gerrit's default submit policy we use the |
Sasa Zivkov | 79b08cc | 2013-01-16 17:00:54 +0100 | [diff] [blame] | 649 | `gerrit:default_submit` predicate. The `gerrit:default_submit(X)` includes all |
| 650 | categories from the database. This means that if we write a submit rule like: |
Sasa Zivkov | ae50f71 | 2012-07-24 14:51:44 +0200 | [diff] [blame] | 651 | |
| 652 | .rules.pl |
| 653 | [caption=""] |
| 654 | ==== |
| 655 | submit_rule(X) :- gerrit:default_submit(X). |
| 656 | ==== |
Sasa Zivkov | ae50f71 | 2012-07-24 14:51:44 +0200 | [diff] [blame] | 657 | then this is equivalent to not using `rules.pl` at all. We just delegate to |
| 658 | default logic. However, once we invoke the `gerrit:default_submit(X)` we can |
| 659 | perform further actions on the return result `X` and apply our specific |
| 660 | logic. The following pattern illustrates this technique: |
| 661 | |
| 662 | .rules.pl |
| 663 | [caption=""] |
| 664 | ==== |
| 665 | submit_rule(S) :- gerrit:default_submit(R), project_specific_policy(R, S). |
| 666 | |
| 667 | project_specific_policy(R, S) :- ... |
| 668 | ==== |
| 669 | |
Sasa Zivkov | 79b08cc | 2013-01-16 17:00:54 +0100 | [diff] [blame] | 670 | In the following examples both styles will be shown. |
Sasa Zivkov | ae50f71 | 2012-07-24 14:51:44 +0200 | [diff] [blame] | 671 | |
| 672 | Example 8: Make change submittable only if `Code-Review+2` is given by a non author |
| 673 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| 674 | In this example we introduce a new label `Non-Author-Code-Review` and make it |
| 675 | satisfied if there is at least one `Code-Review+2` from a non author. All other |
| 676 | default policies like the `Verified` category and vetoing changes still apply. |
| 677 | |
Sasa Zivkov | 79b08cc | 2013-01-16 17:00:54 +0100 | [diff] [blame] | 678 | Reusing the `gerrit:default_submit` |
| 679 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ |
Sasa Zivkov | ae50f71 | 2012-07-24 14:51:44 +0200 | [diff] [blame] | 680 | First, we invoke `gerrit:default_submit` to compute the result for the default |
| 681 | submit policy and then add the `Non-Author-Code-Review` label to it. The |
| 682 | `Non-Author-Code-Review` label is added with status `ok` if such an approval |
| 683 | exists or with status `need` if it doesn't exist. |
| 684 | |
| 685 | .rules.pl |
| 686 | [caption=""] |
| 687 | ==== |
| 688 | submit_rule(S) :- |
| 689 | gerrit:default_submit(X), |
| 690 | X =.. [submit | Ls], |
| 691 | add_non_author_approval(Ls, R), |
| 692 | S =.. [submit | R]. |
| 693 | |
| 694 | add_non_author_approval(S1, S2) :- |
Sasa Zivkov | 79b08cc | 2013-01-16 17:00:54 +0100 | [diff] [blame] | 695 | gerrit:commit_author(A), |
| 696 | gerrit:commit_label(label('Code-Review', 2), R), |
Sasa Zivkov | ae50f71 | 2012-07-24 14:51:44 +0200 | [diff] [blame] | 697 | R \= A, !, |
| 698 | S2 = [label('Non-Author-Code-Review', ok(R)) | S1]. |
| 699 | add_non_author_approval(S1, [label('Non-Author-Code-Review', need(_)) | S1]). |
| 700 | ==== |
| 701 | |
| 702 | This example uses the `univ` operator `=..` to "unpack" the result of the |
| 703 | default_submit, which is a structure of the form `submit(label('Code-Review', |
| 704 | ok(_)), label('Verified', need(_)) ...)` into a list like `[submit, |
| 705 | label('Code-Review', ok(_)), label('Verified', need(_)), ...]`. Then we |
| 706 | process the tail of the list (the list of labels) as a Prolog list, which is |
| 707 | much easier than processing a structure. In the end we use the same `univ` |
| 708 | operator to convert the resulting list of labels back into a `submit` structure |
| 709 | which is expected as a return result. The `univ` operator works both ways. |
| 710 | |
| 711 | In `add_non_author_approval` we use the `cut` operator `!` to prevent Prolog |
| 712 | from searching for more solutions once the `cut` point is reached. This is |
| 713 | important because in the second `add_non_author_approval` rule we just add the |
| 714 | `label('Non-Author-Code-Review', need(_))` without first checking that there |
| 715 | is no non author `Code-Review+2`. The second rule will only be reached |
| 716 | if the `cut` in the first rule is not reached and it only happens if a |
| 717 | predicate before the `cut` fails. |
| 718 | |
Sasa Zivkov | 79b08cc | 2013-01-16 17:00:54 +0100 | [diff] [blame] | 719 | Don't use `gerrit:default_submit` |
| 720 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ |
| 721 | Let's implement the same submit rule the other way, without reusing the |
| 722 | `gerrit:default_submit`: |
| 723 | |
| 724 | .rules.pl |
| 725 | [caption=""] |
| 726 | ==== |
| 727 | submit_rule(submit(CR, V)) :- |
| 728 | base(CR, V), |
| 729 | CR = label(_, ok(Reviewer)), |
| 730 | gerrit:commit_author(Author), |
| 731 | Author \= Reviewer, |
| 732 | !. |
| 733 | |
| 734 | submit_rule(submit(CR, V, N)) :- |
| 735 | base(CR, V), |
| 736 | N = label('Non-Author-Code-Review', need(_)). |
| 737 | |
| 738 | base(CR, V) :- |
| 739 | gerrit:max_with_block(-2, 2, 'Code-Review', CR), |
| 740 | gerrit:max_with_block(-1, 1, 'Verified', V). |
| 741 | ==== |
| 742 | |
| 743 | The latter implementation is probably easier to understand and the code looks |
| 744 | cleaner. Note, however, that the latter implementation will always return the |
| 745 | two standard categories only (`Code-Review` and `Verified`) even if a new |
David Pursehouse | 9246356 | 2013-06-24 10:16:28 +0900 | [diff] [blame] | 746 | category has been inserted into the database. To include the new category |
Sasa Zivkov | 79b08cc | 2013-01-16 17:00:54 +0100 | [diff] [blame] | 747 | the `rules.pl` would need to be modified or a `submit_filter` in a parent |
| 748 | project would have to care about including the new category in the result |
| 749 | of this `submit_rule`. |
| 750 | |
| 751 | The former example, however, would include any newly added category as it |
| 752 | invokes the `gerrit:default_submit` and then modifies its result. |
| 753 | |
| 754 | Which of these two behaviors is desired will always depend on how a particular |
| 755 | Gerrit server is managed. |
| 756 | |
Sasa Zivkov | ae50f71 | 2012-07-24 14:51:44 +0200 | [diff] [blame] | 757 | Example 9: Remove the `Verified` category |
| 758 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| 759 | A project has no build and test. It consists of only text files and needs only |
| 760 | code review. We want to remove the `Verified` category from this project so |
| 761 | that `Code-Review+2` is the only criteria for a change to become submittable. |
| 762 | We also want the UI to not show the `Verified` category in the table with |
| 763 | votes and on the voting screen. |
| 764 | |
Sasa Zivkov | 79b08cc | 2013-01-16 17:00:54 +0100 | [diff] [blame] | 765 | This is quite simple without reusing the 'gerrit:default_submit`: |
| 766 | |
| 767 | .rules.pl |
| 768 | [caption=""] |
| 769 | ==== |
| 770 | submit_rule(submit(CR)) :- |
| 771 | gerrit:max_with_block(-2, 2, 'Code-Review', CR). |
| 772 | ==== |
| 773 | |
| 774 | Implementing the same rule by reusing `gerrit:default_submit` is a bit more complex: |
| 775 | |
Sasa Zivkov | ae50f71 | 2012-07-24 14:51:44 +0200 | [diff] [blame] | 776 | .rules.pl |
| 777 | [caption=""] |
| 778 | ==== |
| 779 | submit_rule(S) :- |
| 780 | gerrit:default_submit(X), |
| 781 | X =.. [submit | Ls], |
| 782 | remove_verified_category(Ls, R), |
| 783 | S =.. [submit | R]. |
| 784 | |
| 785 | remove_verified_category([], []). |
| 786 | remove_verified_category([label('Verified', _) | T], R) :- remove_verified_category(T, R), !. |
| 787 | remove_verified_category([H|T], [H|R]) :- remove_verified_category(T, R). |
| 788 | ==== |
| 789 | |
| 790 | Example 10: Combine examples 8 and 9 |
| 791 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| 792 | In this example we want to both remove the verified and have the four eyes |
| 793 | principle. This means we want a combination of examples 7 and 8. |
| 794 | |
| 795 | .rules.pl |
| 796 | [caption=""] |
| 797 | ==== |
| 798 | submit_rule(S) :- |
| 799 | gerrit:default_submit(X), |
| 800 | X =.. [submit | Ls], |
| 801 | remove_verified_category(Ls, R1), |
| 802 | add_non_author_approval(R1, R), |
| 803 | S =.. [submit | R]. |
| 804 | ==== |
| 805 | |
| 806 | The `remove_verified_category` and `add_non_author_approval` predicates are the |
| 807 | same as defined in the previous two examples. |
| 808 | |
Sasa Zivkov | 79b08cc | 2013-01-16 17:00:54 +0100 | [diff] [blame] | 809 | Without reusing the `gerrit:default_submit` the same example may be implemented |
| 810 | as: |
| 811 | |
| 812 | .rules.pl |
| 813 | [caption=""] |
| 814 | ==== |
| 815 | submit_rule(submit(CR)) :- |
| 816 | base(CR), |
| 817 | CR = label(_, ok(Reviewer)), |
| 818 | gerrit:commit_author(Author), |
| 819 | Author \= Reviewer, |
| 820 | !. |
| 821 | |
| 822 | submit_rule(submit(CR, N)) :- |
| 823 | base(CR), |
| 824 | N = label('Non-Author-Code-Review', need(_)). |
| 825 | |
| 826 | base(CR) :- |
| 827 | gerrit:max_with_block(-2, 2, 'Code-Review', CR), |
| 828 | ==== |
| 829 | |
Sasa Zivkov | 3d4f0aa | 2012-08-07 15:11:32 +0200 | [diff] [blame] | 830 | Example 11: Remove the `Verified` category from all projects |
| 831 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| 832 | Example 9, implements `submit_rule` that removes the `Verified` category from |
| 833 | one project. In this example we do the same but we want to remove the `Verified` |
| 834 | category from all projects. This means we have to implement `submit_filter` and |
| 835 | we have to do that in the `rules.pl` of the `All-Projects` project. |
| 836 | |
| 837 | .rules.pl |
| 838 | [caption=""] |
| 839 | ==== |
| 840 | submit_filter(In, Out) :- |
| 841 | In =.. [submit | Ls], |
| 842 | remove_verified_category(Ls, R), |
| 843 | Out =.. [submit | R]. |
| 844 | |
| 845 | remove_verified_category([], []). |
| 846 | remove_verified_category([label('Verified', _) | T], R) :- |
| 847 | remove_verified_category(T, R), !. |
| 848 | remove_verified_category([H|T], [H|R]) :- remove_verified_category(T, R). |
| 849 | ==== |
| 850 | |
Sasa Zivkov | 79b08cc | 2013-01-16 17:00:54 +0100 | [diff] [blame] | 851 | Example 12: On release branches require DrNo in addition to project rules |
| 852 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| 853 | A new category 'DrNo' is added to the database and is required for release |
| 854 | branches. To mark a branch as a release branch we use `drno('refs/heads/branch')`. |
| 855 | |
| 856 | .rules.pl |
| 857 | [caption=""] |
| 858 | ==== |
| 859 | drno('refs/heads/master'). |
| 860 | drno('refs/heads/stable-2.3'). |
| 861 | drno('refs/heads/stable-2.4'). |
| 862 | drno('refs/heads/stable-2.5'). |
| 863 | drno('refs/heads/stable-2.5'). |
| 864 | |
| 865 | submit_filter(In, Out) :- |
| 866 | gerrit:change_branch(Branch), |
| 867 | drno(Branch), |
| 868 | !, |
| 869 | In =.. [submit | I], |
| 870 | gerrit:max_with_block(-1, 1, 'DrNo', DrNo), |
| 871 | Out =.. [submit, DrNo | I]. |
| 872 | |
| 873 | submit_filter(In, Out) :- In = Out. |
| 874 | ==== |
| 875 | |
| 876 | Example 13: 1+1=2 Code-Review |
Johan Björk | ef02854 | 2012-10-24 12:06:33 -0400 | [diff] [blame] | 877 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| 878 | In this example we introduce accumulative voting to determine if a change is |
| 879 | submittable or not. We modify the standard Code-Review to be accumulative, and make the |
| 880 | change submittable if the total score is 2 or higher. |
| 881 | |
| 882 | The code in this example is very similar to Example 8, with the addition of findall/3 |
| 883 | and gerrit:remove_label. |
| 884 | The findall/3 embedded predicate is used to form a list of all objects that satisfy a |
| 885 | specified Goal. In this example it is used to get a list of all the 'Code-Review' scores. |
| 886 | gerrit:remove_label is a built-in helper that is implemented similarly to the |
| 887 | 'remove_verified_category' as seen in the previous example. |
| 888 | |
| 889 | .rules.pl |
| 890 | [caption=""] |
| 891 | ==== |
| 892 | sum_list([], 0). |
| 893 | sum_list([H | Rest], Sum) :- sum_list(Rest,Tmp), Sum is H + Tmp. |
| 894 | |
| 895 | add_category_min_score(In, Category, Min, P) :- |
| 896 | findall(X, gerrit:commit_label(label(Category,X),R),Z), |
| 897 | sum_list(Z, Sum), |
| 898 | Sum >= Min, !, |
| 899 | P = [label(Category,ok(R)) | In]. |
| 900 | |
| 901 | add_category_min_score(In, Category,Min,P) :- |
| 902 | P = [label(Category,need(Min)) | In]. |
| 903 | |
| 904 | submit_rule(S) :- |
| 905 | gerrit:default_submit(X), |
| 906 | X =.. [submit | Ls], |
| 907 | gerrit:remove_label(Ls,label('Code-Review',_),NoCR), |
| 908 | add_category_min_score(NoCR,'Code-Review', 2, Labels), |
| 909 | S =.. [submit | Labels]. |
| 910 | ==== |
| 911 | |
Sasa Zivkov | 79b08cc | 2013-01-16 17:00:54 +0100 | [diff] [blame] | 912 | Implementing the same example without using `gerrit:default_submit`: |
| 913 | |
| 914 | .rules.pl |
| 915 | [caption=""] |
| 916 | ==== |
| 917 | submit_rule(submit(CR, V)) :- |
| 918 | sum(2, 'Code-Review', CR), |
| 919 | gerrit:max_with_block(-1, 1, 'Verified', V). |
| 920 | |
| 921 | % Sum the votes in a category. Uses a helper function score/2 |
| 922 | % to select out only the score values the given category. |
| 923 | sum(VotesNeeded, Category, label(Category, ok(_))) :- |
| 924 | findall(Score, score(Category, Score), All), |
| 925 | sum_list(All, Sum), |
| 926 | Sum >= VotesNeeded, |
| 927 | !. |
| 928 | sum(VotesNeeded, Category, label(Category, need(VotesNeeded))). |
| 929 | |
| 930 | score(Category, Score) :- |
| 931 | gerrit:commit_label(label(Category, Score), User). |
| 932 | |
| 933 | % Simple Prolog routine to sum a list of integers. |
| 934 | sum_list(List, Sum) :- sum_list(List, 0, Sum). |
| 935 | sum_list([X|T], Y, S) :- Z is X + Y, sum_list(T, Z, S). |
| 936 | sum_list([], S, S). |
| 937 | ==== |
| 938 | |
| 939 | Example 14: Master and apprentice |
Johan Björk | 87927a9 | 2012-10-25 15:00:36 -0400 | [diff] [blame] | 940 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| 941 | The master and apprentice example allow you to specify a user (the `master`) |
| 942 | that must approve all changes done by another user (the `apprentice`). |
| 943 | |
| 944 | The code first checks if the commit author is in the apprentice database. |
| 945 | If the commit is done by an apprentice, it will check if there is a +2 |
| 946 | review by the associated `master`. |
| 947 | |
| 948 | .rules.pl |
| 949 | [caption=""] |
| 950 | ==== |
| 951 | % master_apprentice(Master, Apprentice). |
| 952 | % Extend this with appropriate user-id's for your master/apprentice setup. |
| 953 | master_apprentice(user(1000064), user(1000000)). |
| 954 | |
| 955 | submit_rule(S) :- |
| 956 | gerrit:default_submit(In), |
| 957 | In =.. [submit | Ls], |
| 958 | add_apprentice_master(Ls, R), |
| 959 | S =.. [submit | R]. |
| 960 | |
| 961 | check_master_approval(S1, S2, Master) :- |
| 962 | gerrit:commit_label(label('Code-Review', 2), R), |
| 963 | R = Master, !, |
| 964 | S2 = [label('Master-Approval', ok(R)) | S1]. |
| 965 | check_master_approval(S1, [label('Master-Approval', need(_)) | S1], _). |
| 966 | |
| 967 | add_apprentice_master(S1, S2) :- |
| 968 | gerrit:commit_author(Id), |
| 969 | master_apprentice(Master, Id), |
| 970 | !, |
| 971 | check_master_approval(S1, S2, Master). |
| 972 | |
| 973 | add_apprentice_master(S, S). |
| 974 | ==== |
| 975 | |
Sasa Zivkov | 79b08cc | 2013-01-16 17:00:54 +0100 | [diff] [blame] | 976 | Example 15: Only allow Author to submit change |
Johan Björk | 0f13254 | 2012-10-26 10:16:07 -0400 | [diff] [blame] | 977 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| 978 | This example adds a new needed category `Patchset-Author` for any user that is |
| 979 | not the author of the patch. This effectively blocks all users except the author |
| 980 | from submitting the change. This could result in an impossible situation if the |
| 981 | author does not have permissions for submitting the change. |
| 982 | |
| 983 | .rules.pl |
| 984 | [caption=""] |
| 985 | ==== |
| 986 | submit_rule(S) :- |
| 987 | gerrit:default_submit(In), |
| 988 | In =.. [submit | Ls], |
| 989 | only_allow_author_to_submit(Ls, R), |
| 990 | S =.. [submit | R]. |
| 991 | |
| 992 | only_allow_author_to_submit(S, S) :- |
| 993 | gerrit:commit_author(Id), |
| 994 | gerrit:current_user(Id), |
| 995 | !. |
| 996 | |
| 997 | only_allow_author_to_submit(S1, [label('Patchset-Author', need(_)) | S1]). |
| 998 | ==== |
| 999 | |
Sasa Zivkov | b91296d | 2012-11-08 14:19:12 +0100 | [diff] [blame] | 1000 | Examples - Submit Type |
| 1001 | ---------------------- |
| 1002 | The following examples show how to implement own submit type rules. |
| 1003 | |
| 1004 | Example 1: Set a `Cherry Pick` submit type for all changes |
| 1005 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| 1006 | This example sets the `Cherry Pick` submit type for all changes. It overrides |
| 1007 | whatever is set as project default submit type. |
| 1008 | |
| 1009 | rules.pl |
| 1010 | [caption=""] |
| 1011 | ==== |
| 1012 | submit_type(cherry_pick). |
| 1013 | ==== |
| 1014 | |
| 1015 | |
| 1016 | Example 2: `Fast Forward Only` for all `refs/heads/stable*` branches |
| 1017 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| 1018 | For all `refs/heads/stable.*` branches we would like to enforce the `Fast |
| 1019 | Forward Only` submit type. A reason for this decision may be a need to never |
| 1020 | break the build in the stable branches. For all other branches, those not |
| 1021 | matching the `refs/heads/stable.*` pattern, we would like to use the project's |
| 1022 | default submit type as defined on the project settings page. |
| 1023 | |
| 1024 | .rules.pl |
| 1025 | [caption=""] |
| 1026 | ==== |
| 1027 | submit_type(fast_forward_only) :- |
| 1028 | gerrit:change_branch(B), regex_matches('refs/heads/stable.*', B), |
| 1029 | !. |
| 1030 | submit_type(T) :- gerrit:project_default_submit_type(T) |
| 1031 | ==== |
| 1032 | |
| 1033 | The first `submit_type` predicate defines the `Fast Forward Only` submit type |
| 1034 | for `refs/heads/stable.*` branches. The second `submit_type` predicate returns |
| 1035 | the project's default submit type. |
| 1036 | |
| 1037 | Example 3: Don't require `Fast Forward Only` if only documentation was changed |
| 1038 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| 1039 | Like in the previous example we want the `Fast Forward Only` submit type for |
| 1040 | the `refs/heads/stable*` branches. However, if only documentation was changed |
| 1041 | (only `*.txt` files), then we allow project's default submit type for such |
| 1042 | changes. |
| 1043 | |
| 1044 | .rules.pl |
| 1045 | [caption=""] |
| 1046 | ==== |
| 1047 | submit_type(fast_forward_only) :- |
| 1048 | gerrit:commit_delta('(?<!\.txt)$'), |
| 1049 | gerrit:change_branch(B), regex_matches('refs/heads/stable.*', B), |
| 1050 | !. |
| 1051 | submit_type(T) :- gerrit:project_default_submit_type(T) |
| 1052 | ==== |
| 1053 | |
| 1054 | The `gerrit:commit_delta('(?<!\.txt)$')` succeeds if the change contains a file |
| 1055 | whose name doesn't end with `.txt` The rest of this rule is same like in the |
| 1056 | previous example. |
| 1057 | |
| 1058 | If all file names in the change end with `.txt`, then the |
| 1059 | `gerrit:commit_delta('(?<!\.txt)$')` will fail as no file name will match this |
| 1060 | regular expression. |
| 1061 | |
Sasa Zivkov | ae50f71 | 2012-07-24 14:51:44 +0200 | [diff] [blame] | 1062 | GERRIT |
| 1063 | ------ |
| 1064 | Part of link:index.html[Gerrit Code Review] |