blob: b1710a20f8d593f4c9a66893c97f7abd00109b26 [file] [log] [blame]
Sasa Zivkovae50f712012-07-24 14:51:44 +02001Gerrit Code Review - Prolog Submit Rules Cookbook
2=================================================
3
4Submit Rule
5-----------
6A 'Submit Rule' in Gerrit is logic that defines when a change is submittable.
7By default, a change is submittable when it gets at least one
8highest vote in each voting category and has no lowest vote (aka veto vote) in
9any 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
11submittable.
12
13While this rule is a good default, there are projects which need more
14flexibility for defining when a change is submittable. In Gerrit, it is
15possible to use Prolog based rules to provide project specific submit rules and
16replace the default submit rules. Using Prolog based rules, project owners can
17define a set of criteria which must be fulfilled for a change to become
18submittable. For a change that is not submittable, the set of needed criteria
19is displayed in the Gerrit UI.
20
21NOTE: Loading and executing Prolog submit rules may be disabled by setting
22`rules.enabled=false` in the Gerrit config file (see
23link:config-gerrit.html#_a_id_rules_a_section_rules[rules section])
24
25link:https://groups.google.com/d/topic/repo-discuss/wJxTGhlHZMM/discussion[This
26discussion thread] explains why Prolog was chosen for the purpose of writing
27project specific submit rules.
28link:http://gerrit-documentation.googlecode.com/svn/ReleaseNotes/ReleaseNotes-2.2.2.html[Gerrit
292.2.2 ReleaseNotes] introduces Prolog support in Gerrit.
30
Sasa Zivkovb91296d2012-11-08 14:19:12 +010031Submit Type
32-----------
33A 'Submit Type' is a strategy that is used on submit to integrate the
34change 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
43is used for all changes of one project.
44
45Projects which need more flexibility in choosing, or enforcing, a submit type
46can use Prolog based submit type which replaces the project's default submit
47type.
48
49Prolog based submit type computes a submit type for each change. The computed
50submit type is shown on the change screen for each change.
51
Sasa Zivkovae50f712012-07-24 14:51:44 +020052Prolog Language
53---------------
54This document is not a complete Prolog tutorial.
55link:http://en.wikipedia.org/wiki/Prolog[This Wikipedia page on Prolog] is a
56good starting point for learning the Prolog language. This document will only explain
57some elements of Prolog that are necessary to understand the provided examples.
58
59Prolog in Gerrit
60----------------
61Gerrit uses its own link:https://code.google.com/p/prolog-cafe/[fork] of the
62original link:http://kaminari.istc.kobe-u.ac.jp/PrologCafe/[prolog-cafe]
63project. Gerrit embeds the prolog-cafe library and can interpret Prolog programs at
64runtime.
65
66Interactive Prolog Cafe Shell
67-----------------------------
68For interactive testing and playing with Prolog, Gerrit provides the
69link:pgm-prolog-shell.html[prolog-shell] program which opens an interactive
70Prolog interpreter shell.
71
Johan Björk2119f052012-10-24 12:10:45 -040072NOTE: The interactive shell is just a prolog shell, it does not load
73a gerrit server environment and thus is not intended for xref:TestingSubmitRules[testing submit rules].
Sasa Zivkovae50f712012-07-24 14:51:44 +020074
75SWI-Prolog
76----------
77Instead of using the link:pgm-prolog-shell.html[prolog-shell] program one can
78also use the link:http://www.swi-prolog.org/[SWI-Prolog] environment. It
79provides a better shell interface and a graphical source-level debugger.
80
81The rules.pl file
82-----------------
83This section explains how to create and edit project specific submit rules. How
84to actually write the submit rules is explained in the next section.
85
86Project 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
88checkout the `refs/meta/config` branch in order to create or edit the `rules.pl`
89file:
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 Zivkovb91296d2012-11-08 14:19:12 +0100100[[HowToWriteSubmitRules]]
Sasa Zivkovae50f712012-07-24 14:51:44 +0200101How to write submit rules
102-------------------------
103Whenever Gerrit needs to evaluate submit rules for a change `C` from project `P` it
104will 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
109Conceptually 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
111the 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
123have to use qualified names when writing our code and referencing these facts.
124For 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
129A complete set of facts which Gerrit provides about the change is listed in the
130link:prolog-change-facts.html[Prolog Facts for Gerrit Change].
131
132By default, Gerrit will search for a `submit_rule/1` predicate in the `rules.pl`
133file, evaluate the `submit_rule(X)` and then inspect the value of `X` in order
134to decide whether the change is submittable or not and also to find the set of
135needed criteria for the change to become submittable. This means that Gerrit has an
136expectation on the format and value of the result of the `submit_rule` predicate
137which 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
143where `label-name` is usually `'Code-Review'` or `'Verified'` but could also
144be 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
161NOTE: For a change to be submittable all `label` terms contained in the returned
162`submit` term must have either `ok` or `may` status.
163
164IMPORTANT: Gerrit will let the Prolog engine continue searching for solutions of
165the `submit_rule(X)` query until it finds the first one where all labels in the
166return result have either status `ok` or `may` or there are no more solutions.
167If a solution where all labels have status `ok` is found then all previously
168found solutions are ignored. Otherwise, all labels names with status `need`
169from all solutions will be displayed in the UI indicating the set of conditions
170needed for the change to become submittable.
171
172Here 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 Pursehouse92463562013-06-24 10:16:28 +0900185 to the implementer of the `submit_rule` to return `label('Author-is-John-Doe',
Sasa Zivkovae50f712012-07-24 14:51:44 +0200186 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
190Of course, when implementing the `submit_rule` we will use the facts about the
191change that are already provided by Gerrit.
192
193Another aspect of the return result from the `submit_rule` predicate is that
194Gerrit uses it to decide which set of labels to display on the change review
195screen for voting. If the return result contains label `'ABC'` and if the label
Dave Borowitz01c1b1f2013-02-27 13:49:04 -0800196`'ABC'` is link:config-labels.html[defined for the project] then voting for the
197label `'ABC'` will be displayed. Otherwise, it is not displayed. Note that the
198project doesn't need a defined label for each label contained in the result of
Sasa Zivkovae50f712012-07-24 14:51:44 +0200199`submit_rule` predicate. For example, the decision whether `'Author-is-John-Doe'`
200label is met will probably not be made by explicit voting but, instead, by
201inspecting the facts about the change.
202
Sasa Zivkovb91296d2012-11-08 14:19:12 +0100203[[SubmitFilter]]
Sasa Zivkov3d4f0aa2012-08-07 15:11:32 +0200204Submit Filter
205-------------
206Another 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
208in the `rules.pl` file of the current project, the `submit_filter` will be
209searched for in the `rules.pl` of all parent projects of the current project,
210but not in the `rules.pl` of the current project. The search will start from the
211immediate parent of the current project, then in the parent project of that
212project and so on until, and including, the 'All-Projects' project.
213
214The purpose of the submit filter is, as its name says, to filter the results
215of the `submit_rule`. Therefore, the `submit_filter` predicate has two
216parameters:
217
218====
219 submit_filter(In, Out) :- ...
220====
221
222Gerrit will invoke `submit_filter` with the `In` parameter containing a `submit`
223structure produced by the `submit_rule` and will take the value of the `Out`
224parameter as the result.
225
226The `Out` value of a `submit_filter` will become the `In` value for the
227next `submit_filter` in the parent line. The value of the `Out` parameter
228of the top-most `submit_filter` is the final result of the submit rule that
229is used to decide whether a change is submittable or not.
230
231IMPORTANT: `submit_filter` is a mechanism for Gerrit administrators to implement
232and enforce submit rules that would apply to all projects while `submit_rule` is
233a mechanism for project owners to implement project specific submit rules.
234However, 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
236implementing the `submit_filter` in this common parent project. This way they
237can avoid implementing the same `submit_rule` in all their projects.
238
239The following "drawing" illustrates the order of the invocation and the chaining
240of the results of the `submit_rule` and `submit_filter` predicates.
Sasa Zivkov3d4f0aa2012-08-07 15:11:32 +0200241====
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`
260project.
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
263a `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
266value of the submit rule evaluation.
267
268NOTE: If `MyProject` doesn't define its own `submit_rule` Gerrit will invoke the
269default implementation of submit rule that is named `gerrit:default_submit` and
270its result will be filtered as described above.
271
Edwin Kempinca24f5c2013-03-26 13:23:20 +0100272[[HowToWriteSubmitType]]
Sasa Zivkovb91296d2012-11-08 14:19:12 +0100273How to write submit type
274------------------------
275Writing custom submit type logic in Prolog is the similar top
276xref:HowToWriteSubmitRules[writing submit rules]. The only difference is that
277one has to implement a `submit_type` predicate (instead of the `submit_rule`)
278and that the return result of the `submit_type` has to be an atom that
279represents 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
287Submit Type Filter
288------------------
289Submit type filter works the same way as the xref:SubmitFilter[Submit Filter]
290where the name of the filter predicate is `submit_type_filter`.
291
292====
293 submit_type_filter(In, Out).
294====
295
296Gerrit will invoke `submit_type_filter` with the `In` parameter containing a
297result of the `submit_type` and will take the value of the `Out` parameter as
298the result.
299
Johan Björk2119f052012-10-24 12:10:45 -0400300[[TestingSubmitRules]]
301Testing submit rules
302--------------------
303The prolog environment running the `submit_rule` is loaded with state describing the
304change 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 Kempinf9f46bd2013-09-02 15:46:23 +0200306link:cmd-test-submit-rule.html[test-submit rule] loads a specific change and executes
Johan Björk2119f052012-10-24 12:10:45 -0400307the `submit_rule`. It optionally reads the rule from from `stdin` to facilitate easy testing.
308
309====
Edwin Kempinf9f46bd2013-09-02 15:46:23 +0200310 cat rules.pl | ssh gerrit_srv gerrit test-submit rule I45e080b105a50a625cc8e1fb5b357c0bfabe6d68 -s
Johan Björk2119f052012-10-24 12:10:45 -0400311====
Sasa Zivkov3d4f0aa2012-08-07 15:11:32 +0200312
Sasa Zivkovae50f712012-07-24 14:51:44 +0200313Prolog vs Gerrit plugin for project specific submit rules
314---------------------------------------------------------
315Since version 2.5 Gerrit supports plugins and extension points. A plugin or an
316extension point could also be used as another means to provide custom submit
317rules. One could ask for a guideline when to use Prolog based submit rules and
318when to go for writing a new plugin. Writing a Prolog program is usually much
319faster than writing a Gerrit plugin. Prolog based submit rules can be pushed
320to a project by project owners while Gerrit plugins could only be installed by
321Gerrit administrators. In addition, Prolog based submit rules can be pushed
322for review by pushing to `refs/for/refs/meta/config` branch.
323
324On the other hand, Prolog based submit rules get a limited amount of facts about
325the change exposed to them. Gerrit plugins get full access to Gerrit internals
326and can potentially check more things than Prolog based rules.
327
Sasa Zivkovd0e55262013-01-16 14:26:06 +0100328From version 2.6 Gerrit plugins can contribute Prolog predicates. This way, we
329can make use of the plugin provided predicates when writing Prolog based rules.
330
Sasa Zivkovb91296d2012-11-08 14:19:12 +0100331Examples - Submit Rule
332----------------------
Sasa Zivkovae50f712012-07-24 14:51:44 +0200333The following examples should serve as a cookbook for developing own submit rules.
334Some of them are too trivial to be used in production and their only purpose is
335to provide step by step introduction and understanding.
336
Sasa Zivkov3d4f0aa2012-08-07 15:11:32 +0200337Some of the examples will implement the `submit_rule` and some will implement
338the `submit_filter` just to show both possibilities. Remember that
339`submit_rule` is only invoked from the current project and `submit_filter` is
340invoked from all parent projects. This is the most important fact in deciding
341whether to implement `submit_rule` or `submit_filter`.
342
Sasa Zivkovae50f712012-07-24 14:51:44 +0200343Example 1: Make every change submittable
344~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
345Let's start with a most trivial example where we would make every change submittable
346regardless of the votes it has:
347
348.rules.pl
349[caption=""]
350====
Sasa Zivkov79b08cc2013-01-16 17:00:54 +0100351 submit_rule(submit(W)) :-
352 W = label('Any-Label-Name', ok(_)).
Sasa Zivkovae50f712012-07-24 14:51:44 +0200353====
354
355In this case we make no use of facts about the change. We don't need it as we are simply
356making every change submittable. Note that, in this case, the Gerrit UI will not show
357the UI for voting for the standard `'Code-Review'` and `'Verified'` categories as labels
358with these names are not part of the return result. The `'Any-Label-Name'` could really
359be any string.
360
361Example 2: Every change submittable and voting in the standard categories possible
362~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
363This is continuation of the previous example where, in addition, to making
364every change submittable we want to enable voting in the standard
365`'Code-Review'` and `'Verified'` categories.
366
367.rules.pl
368[caption=""]
369====
Sasa Zivkov79b08cc2013-01-16 17:00:54 +0100370 submit_rule(submit(CR, V)) :-
371 CR = label('Code-Review', ok(_)),
372 V = label('Verified', ok(_)).
Sasa Zivkovae50f712012-07-24 14:51:44 +0200373====
374
375Since for every change all label statuses are `'ok'` every change will be submittable.
376Voting in the standard labels will be shown in the UI as the standard label names are
377included in the return result.
378
379Example 3: Nothing is submittable
380~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
381This example shows how to make all changes non-submittable regardless of the
382votes they have.
383
384.rules.pl
385[caption=""]
386====
Sasa Zivkov79b08cc2013-01-16 17:00:54 +0100387 submit_rule(submit(R)) :-
388 R = label('Any-Label-Name', reject(_)).
Sasa Zivkovae50f712012-07-24 14:51:44 +0200389====
390
391Since for any change we return only one label with status `reject`, no change
392will be submittable. The UI will, however, not indicate what is needed for a
393change to become submittable as we return no labels with status `need`.
394
395Example 4: Nothing is submittable but UI shows several 'Need ...' criteria
396~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
397In 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 Zivkov79b08cc2013-01-16 17:00:54 +0100404 submit_rule(submit(N)) :-
405 N = label('Any-Label-Name', need(_)).
Sasa Zivkovae50f712012-07-24 14:51:44 +0200406
407 % We could define more "need" labels by adding more rules
Sasa Zivkov79b08cc2013-01-16 17:00:54 +0100408 submit_rule(submit(N)) :-
409 N = label('Another-Label-Name', need(_)).
Sasa Zivkovae50f712012-07-24 14:51:44 +0200410
411 % or by providing more than one need label in the same rule
Sasa Zivkov79b08cc2013-01-16 17:00:54 +0100412 submit_rule(submit(NX, NY)) :-
413 NX = label('X-Label-Name', need(_)),
414 NY = label('Y-Label-Name', need(_)).
Sasa Zivkovae50f712012-07-24 14:51:44 +0200415====
416
417In 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
425From 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
432Example 5: The 'Need ...' labels not shown when change is submittable
433~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
434This example shows that, when there is a solution for `submit_rule(X)` where all labels
435have status `ok` then Gerrit will not show any labels with the `need` status from
436any of the previous `submit_rule(X)` solutions.
437
438.rules.pl
439[caption=""]
440====
Sasa Zivkov79b08cc2013-01-16 17:00:54 +0100441 submit_rule(submit(N)) :-
442 N = label('Some-Condition', need(_)).
443
444 submit_rule(submit(OK)) :-
445 OK = label('Another-Condition', ok(_)).
Sasa Zivkovae50f712012-07-24 14:51:44 +0200446====
447
448The 'Need Some-Condition' will not be show in the UI because of the result of
449the second rule.
450
451The same is valid if the two rules are swapped:
452
453.rules.pl
454[caption=""]
455====
Sasa Zivkov79b08cc2013-01-16 17:00:54 +0100456 submit_rule(submit(OK)) :-
457 OK = label('Another-Condition', ok(_)).
458
459 submit_rule(submit(N)) :-
460 N = label('Some-Condition', need(_)).
Sasa Zivkovae50f712012-07-24 14:51:44 +0200461====
462
463The result of the first rule will stop search for any further solutions.
464
465Example 6: Make change submittable if commit author is "John Doe"
466~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
467This is the first example where we will use the Prolog facts about a change that
468are automatically exposed by Gerrit. Our goal is to make any change submittable
469when the commit author is named `'John Doe'`. In the very first
470step let's make sure Gerrit UI shows 'Need Author-is-John-Doe' in
471the UI to clearly indicate to the user what is needed for a change to become
472submittable:
473
474.rules.pl
475[caption=""]
476====
Sasa Zivkov79b08cc2013-01-16 17:00:54 +0100477 submit_rule(submit(Author)) :-
478 Author = label('Author-is-John-Doe', need(_)).
Sasa Zivkovae50f712012-07-24 14:51:44 +0200479====
480
481This will show:
482****
483* Need Author-is-John-Doe
484****
485
486in the UI but no change will be submittable yet. Let's add another rule:
487
488.rules.pl
489[caption=""]
490====
Sasa Zivkov79b08cc2013-01-16 17:00:54 +0100491 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 Zivkovae50f712012-07-24 14:51:44 +0200497====
498
499In the second rule we return `ok` status for the `'Author-is-John-Doe'` label
500if there is a `commit_author` fact where the full name is `'John Doe'`. If
501author of a change is `'John Doe'` then the second rule will return a solution
502where all labels have `ok` status and the change will become submittable. If
503author of a change is not `'John Doe'` then only the first rule will produce a
504solution. The UI will show 'Need Author-is-John-Doe' but, as expected, the
505change will not be submittable.
506
507Instead of checking by full name we could also check by the email address:
508
509.rules.pl
510[caption=""]
511====
Sasa Zivkov79b08cc2013-01-16 17:00:54 +0100512 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 Zivkovae50f712012-07-24 14:51:44 +0200518====
519
520or by user id (assuming it is 1000000):
521
522.rules.pl
523[caption=""]
524====
Sasa Zivkov79b08cc2013-01-16 17:00:54 +0100525 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 Zivkovae50f712012-07-24 14:51:44 +0200531====
532
533or by a combination of these 3 attributes:
534
535.rules.pl
536[caption=""]
537====
Sasa Zivkov79b08cc2013-01-16 17:00:54 +0100538 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 Zivkovae50f712012-07-24 14:51:44 +0200544====
545
Sasa Zivkov79b08cc2013-01-16 17:00:54 +0100546Example 7: Make change submittable if commit message starts with "Fix "
547~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Sasa Zivkovae50f712012-07-24 14:51:44 +0200548Besides showing how to make use of the commit message text the purpose of this
549example is also to show how to match only a part of a string symbol. Similarly
550like commit author the commit message is provided as a string symbol which is
551an atom in Prolog terms. When working with an atom we could only match against
552the whole value. To match only part of a string symbol we have, at least, two
553options:
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
560Let's implement both options:
561
562.rules.pl
563[caption=""]
564====
Sasa Zivkov79b08cc2013-01-16 17:00:54 +0100565 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 Zivkovae50f712012-07-24 14:51:44 +0200571
572 starts_with(L, []).
573 starts_with([H|T1], [H|T2]) :- starts_with(T1, T2).
574====
575
576NOTE: The `name/2` embedded predicate is used to convert a string symbol into a
577list of characters. A string `abc` is converted into a list of characters `[97,
57898, 99]`. A double quoted string in Prolog is just a shortcut for creating a
579list of characters. `"abc"` is a shortcut for `[97, 98, 99]`. This is why we use
580double quotes for the `"Trivial Fix"` in the example above.
581
582The `starts_with` predicate is self explaining.
583
584Using the `gerrit:commit_message_matches` predicate is probably more efficient:
585
586.rules.pl
587[caption=""]
588====
Sasa Zivkov79b08cc2013-01-16 17:00:54 +0100589 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 Zivkovae50f712012-07-24 14:51:44 +0200595====
596
Sasa Zivkov79b08cc2013-01-16 17:00:54 +0100597The previous example could also be written so that it first checks if the commit
598message starts with 'Fix '. If true then it sets OK for that category and stops
599further 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
613The default submit policy
614-------------------------
Sasa Zivkovae50f712012-07-24 14:51:44 +0200615All examples until now concentrate on one particular aspect of change data.
616However, in real-life scenarios we would rather want to reuse Gerrit's default
Sasa Zivkov79b08cc2013-01-16 17:00:54 +0100617submit policy and extend/change it for our specific purpose. This could be
618done in one of the following ways:
Sasa Zivkovae50f712012-07-24 14:51:44 +0200619
Sasa Zivkov79b08cc2013-01-16 17:00:54 +0100620* 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
625Default submit rule implementation
626~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
627The 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
638Once this implementation is understood it can be customized to implement
639project specific submit rules. Note, that this implementation hardcodes
640the two default categories. Introducing a new category in the database would
641require introducing the same category here or a `submit_filter` in a parent
642project would have to care about including the new category in the result of
643this `submit_rule`. On the other side, this example is easy to read and
644understand.
645
646Reusing the default submit policy
647~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
David Pursehouse92463562013-06-24 10:16:28 +0900648To get results of Gerrit's default submit policy we use the
Sasa Zivkov79b08cc2013-01-16 17:00:54 +0100649`gerrit:default_submit` predicate. The `gerrit:default_submit(X)` includes all
650categories from the database. This means that if we write a submit rule like:
Sasa Zivkovae50f712012-07-24 14:51:44 +0200651
652.rules.pl
653[caption=""]
654====
655 submit_rule(X) :- gerrit:default_submit(X).
656====
Sasa Zivkovae50f712012-07-24 14:51:44 +0200657then this is equivalent to not using `rules.pl` at all. We just delegate to
658default logic. However, once we invoke the `gerrit:default_submit(X)` we can
659perform further actions on the return result `X` and apply our specific
660logic. 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 Zivkov79b08cc2013-01-16 17:00:54 +0100670In the following examples both styles will be shown.
Sasa Zivkovae50f712012-07-24 14:51:44 +0200671
672Example 8: Make change submittable only if `Code-Review+2` is given by a non author
673~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
674In this example we introduce a new label `Non-Author-Code-Review` and make it
675satisfied if there is at least one `Code-Review+2` from a non author. All other
676default policies like the `Verified` category and vetoing changes still apply.
677
Sasa Zivkov79b08cc2013-01-16 17:00:54 +0100678Reusing the `gerrit:default_submit`
679^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Sasa Zivkovae50f712012-07-24 14:51:44 +0200680First, we invoke `gerrit:default_submit` to compute the result for the default
681submit 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
683exists 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 Zivkov79b08cc2013-01-16 17:00:54 +0100695 gerrit:commit_author(A),
696 gerrit:commit_label(label('Code-Review', 2), R),
Sasa Zivkovae50f712012-07-24 14:51:44 +0200697 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
702This example uses the `univ` operator `=..` to "unpack" the result of the
703default_submit, which is a structure of the form `submit(label('Code-Review',
704ok(_)), label('Verified', need(_)) ...)` into a list like `[submit,
705label('Code-Review', ok(_)), label('Verified', need(_)), ...]`. Then we
706process the tail of the list (the list of labels) as a Prolog list, which is
707much easier than processing a structure. In the end we use the same `univ`
708operator to convert the resulting list of labels back into a `submit` structure
709which is expected as a return result. The `univ` operator works both ways.
710
711In `add_non_author_approval` we use the `cut` operator `!` to prevent Prolog
712from searching for more solutions once the `cut` point is reached. This is
713important 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
715is no non author `Code-Review+2`. The second rule will only be reached
716if the `cut` in the first rule is not reached and it only happens if a
717predicate before the `cut` fails.
718
Sasa Zivkov79b08cc2013-01-16 17:00:54 +0100719Don't use `gerrit:default_submit`
720^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
721Let'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
743The latter implementation is probably easier to understand and the code looks
744cleaner. Note, however, that the latter implementation will always return the
745two standard categories only (`Code-Review` and `Verified`) even if a new
David Pursehouse92463562013-06-24 10:16:28 +0900746category has been inserted into the database. To include the new category
Sasa Zivkov79b08cc2013-01-16 17:00:54 +0100747the `rules.pl` would need to be modified or a `submit_filter` in a parent
748project would have to care about including the new category in the result
749of this `submit_rule`.
750
751The former example, however, would include any newly added category as it
752invokes the `gerrit:default_submit` and then modifies its result.
753
754Which of these two behaviors is desired will always depend on how a particular
755Gerrit server is managed.
756
Sasa Zivkovae50f712012-07-24 14:51:44 +0200757Example 9: Remove the `Verified` category
758~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
759A project has no build and test. It consists of only text files and needs only
760code review. We want to remove the `Verified` category from this project so
761that `Code-Review+2` is the only criteria for a change to become submittable.
762We also want the UI to not show the `Verified` category in the table with
763votes and on the voting screen.
764
Sasa Zivkov79b08cc2013-01-16 17:00:54 +0100765This 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
774Implementing the same rule by reusing `gerrit:default_submit` is a bit more complex:
775
Sasa Zivkovae50f712012-07-24 14:51:44 +0200776.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
790Example 10: Combine examples 8 and 9
791~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
792In this example we want to both remove the verified and have the four eyes
793principle. 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
806The `remove_verified_category` and `add_non_author_approval` predicates are the
807same as defined in the previous two examples.
808
Sasa Zivkov79b08cc2013-01-16 17:00:54 +0100809Without reusing the `gerrit:default_submit` the same example may be implemented
810as:
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 Zivkov3d4f0aa2012-08-07 15:11:32 +0200830Example 11: Remove the `Verified` category from all projects
831~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
832Example 9, implements `submit_rule` that removes the `Verified` category from
833one project. In this example we do the same but we want to remove the `Verified`
834category from all projects. This means we have to implement `submit_filter` and
835we 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 Zivkov79b08cc2013-01-16 17:00:54 +0100851Example 12: On release branches require DrNo in addition to project rules
852~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
853A new category 'DrNo' is added to the database and is required for release
854branches. 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
876Example 13: 1+1=2 Code-Review
Johan Björkef028542012-10-24 12:06:33 -0400877~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
878In this example we introduce accumulative voting to determine if a change is
879submittable or not. We modify the standard Code-Review to be accumulative, and make the
880change submittable if the total score is 2 or higher.
881
882The code in this example is very similar to Example 8, with the addition of findall/3
883and gerrit:remove_label.
884The findall/3 embedded predicate is used to form a list of all objects that satisfy a
885specified Goal. In this example it is used to get a list of all the 'Code-Review' scores.
886gerrit: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 Zivkov79b08cc2013-01-16 17:00:54 +0100912Implementing 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
939Example 14: Master and apprentice
Johan Björk87927a92012-10-25 15:00:36 -0400940~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
941The master and apprentice example allow you to specify a user (the `master`)
942that must approve all changes done by another user (the `apprentice`).
943
944The code first checks if the commit author is in the apprentice database.
945If the commit is done by an apprentice, it will check if there is a +2
946review 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 Zivkov79b08cc2013-01-16 17:00:54 +0100976Example 15: Only allow Author to submit change
Johan Björk0f132542012-10-26 10:16:07 -0400977~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
978This example adds a new needed category `Patchset-Author` for any user that is
979not the author of the patch. This effectively blocks all users except the author
980from submitting the change. This could result in an impossible situation if the
981author 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 Zivkovb91296d2012-11-08 14:19:12 +01001000Examples - Submit Type
1001----------------------
1002The following examples show how to implement own submit type rules.
1003
1004Example 1: Set a `Cherry Pick` submit type for all changes
1005~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1006This example sets the `Cherry Pick` submit type for all changes. It overrides
1007whatever is set as project default submit type.
1008
1009rules.pl
1010[caption=""]
1011====
1012 submit_type(cherry_pick).
1013====
1014
1015
1016Example 2: `Fast Forward Only` for all `refs/heads/stable*` branches
1017~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1018For all `refs/heads/stable.*` branches we would like to enforce the `Fast
1019Forward Only` submit type. A reason for this decision may be a need to never
1020break the build in the stable branches. For all other branches, those not
1021matching the `refs/heads/stable.*` pattern, we would like to use the project's
1022default 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
1033The first `submit_type` predicate defines the `Fast Forward Only` submit type
1034for `refs/heads/stable.*` branches. The second `submit_type` predicate returns
1035the project's default submit type.
1036
1037Example 3: Don't require `Fast Forward Only` if only documentation was changed
1038~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1039Like in the previous example we want the `Fast Forward Only` submit type for
1040the `refs/heads/stable*` branches. However, if only documentation was changed
1041(only `*.txt` files), then we allow project's default submit type for such
1042changes.
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
1054The `gerrit:commit_delta('(?<!\.txt)$')` succeeds if the change contains a file
1055whose name doesn't end with `.txt` The rest of this rule is same like in the
1056previous example.
1057
1058If 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
1060regular expression.
1061
Sasa Zivkovae50f712012-07-24 14:51:44 +02001062GERRIT
1063------
1064Part of link:index.html[Gerrit Code Review]