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