rules.pl: add Trust+2 check and disallow self-code-review

For golang/go#40699.

Change-Id: I681f08cb84aa9bf4b227188594ea02c2727b3697
Reviewed-on: https://go-review.googlesource.com/c/All-Projects/+/254799
Reviewed-by: Andrew Bonventre <andybons@google.com>
diff --git a/rules.pl b/rules.pl
index 88efe81..4247e63 100644
--- a/rules.pl
+++ b/rules.pl
@@ -1,22 +1,93 @@
-% We don't send email if the commit message says DO NOT REVIEW, so don't allow submit either.
-% Case insensitive because the mail filter rules are.
+% submit_rule in the actual project (go, scratch, etc.)
+% generates a list of labels and whether they are ok, needed, or can be ignored.
+% Most repos don't define a specific submit_rule; the server uses gerrit:default_submit.
+%
+% submit_filter, defined below, is applied to the output of submit_rule to adjust it.
+% The adjustments are:
+%
+%  * do_not_review_filter: add a “rejected by Do-Not-Review label”
+%    if the commit message says DO NOT REVIEW.
+%
+%  * do_not_submit_filter: add a “rejected by Do-Not-Submit label”
+%    if the commit message says DO NOT SUBMIT.
+%
+%  * trust_filter: make sure that two different people have set
+%    either Code-Review+2 or Trust+1 on the commit.
+%
+%  * self_review_filter: make sure that Code-Review+2
+%    is from someone other than the author of the commit.
+%
+% Note that if you make any mistake in this file that causes
+% a Prolog execution exception or makes submit_filter not succeed,
+% the usual signal you get in Gerrit is that labels disappear from the UI for all CLs.
+% Sometimes you get an internal error popup instead (syntax errors do this, for example).
+
 submit_filter(In, Out) :-
-	In =.. [submit | I],
+	Gobot = user(5976),
+	In =.. [submit | A],
+	do_not_review_filter(Gobot, A, B),
+	do_not_submit_filter(Gobot, B, C),
+	trust_filter(Gobot, C, D),
+	self_review_filter(Gobot, D, E),
+	Out =.. [submit | E].
+
+% We don't send email if the commit message says DO NOT REVIEW,
+% so don't allow submit either.
+% Case insensitive because the mail filter rules are.
+do_not_review_filter(Gobot, In, Out) :-
 	gerrit:commit_message_matches('[Dd][Oo][ \t\r\n]+[Nn][Oo][Tt][ \t\r\n]+[Rr][Ee][Vv][Ii][Ee][Ww]'),
 	!,
 	gerrit:commit_author(Id),
 	Error = label('Do-Not-Review', reject(Id)),
-	Out =.. [submit, Error | I].
+	Out = [Error | In].
+
+do_not_review_filter(Gobot, In, Out) :-
+	Out = In.
 
 % Don't allow submit of DO NOT SUBMIT commit message either.
 % Case insensitive because DO NOT REVIEW is.
-submit_filter(In, Out) :-
-	In =.. [submit | I],
+do_not_submit_filter(Gobot, In, Out) :-
 	gerrit:commit_message_matches('[Dd][Oo][ \t\r\n]+[Nn][Oo][Tt][ \t\r\n]+[Ss][Uu][Bb][Mm][Ii][Tt]'),
 	!,
 	gerrit:commit_author(Id),
 	Error = label('Do-Not-Submit', reject(Id)),
-	Out =.. [submit, Error | I].
+	Out = [Error | In].
 
-% Otherwise, pass submit rule through.
-submit_filter(In, Out) :- In = Out.
+do_not_submit_filter(Gobot, In, Out) :-
+	Out = In.
+
+% Check for two trusted author/reviewers, unless Self-Review-OK label set by project.
+trust_filter(Gobot, In, Out) :- has_label('Self-Review-OK', In), !, Out = In.
+trust_filter(Gobot, In, Out) :- trust2, !, Out = [label('Trust-2', ok(Gobot)) | In].
+trust_filter(Gobot, In, Out) :- Out = [label('Trust-2', reject(Gobot)) | In].
+
+trust2 :- trust(U1), trust(U2), U1 \= U2.
+
+trust(U) :- gerrit:commit_label(label('Code-Review', 2), U).
+trust(U) :- gerrit:commit_label(label('Trust', 1), U).
+
+% Reject self-code-review, unless Self-Review-OK label set by project.
+%
+% NOTE: Projects (= repos) that allow self-code-review and that do not require Trust+2
+% (for example, the blog and proposal projects), can use this in rules.pl to set Self-Review-OK:
+%
+%	submit_rule(S) :-
+%		Gobot = user(5976),
+%		gerrit:default_submit(R),
+%		R =.. [submit | A],
+%		self_review_ok(Gobot, A, B),
+%		S =.. [submit | B].
+%
+%	self_review_ok(Gobot, In, Out) :- Out = [label('Self-Review-OK', ok(Gobot)) | In].
+%
+self_review_filter(Gobot, In, Out) :- has_label('Self-Review-OK', In), !, Out = In.
+self_review_filter(Gobot, In, Out) :- self_review, !, Out = [label('Self-Review', reject(Gobot)) | In].
+self_review_filter(Gobot, In, Out) :- Out = In.
+
+self_review :-
+	gerrit:change_owner(Owner),
+	gerrit:commit_label(label('Code-Review', 2), Owner).
+
+% has_label checks whether the list contains the label L.
+has_label(L, [label(L, _) | T]).
+has_label(L, [_ | T]) :- has_label(L, T).