Add inheritance of prolog rules
Projects now inherit the prolog rules defined in their parent project. Submit results from the child project are filtered by the parent project using the filter predicate defined the parent's rules.pl. The results of the filtering are then passed up to the parent's parent and filtered. This process repeats up to the top level All-Projects. If a parent does not have a filter rule defined, the results are not filtered. Change-Id: I961d10149f62996d1c3c74c80935b2f72ea0acc7
This commit is contained in:
@@ -32,8 +32,11 @@ import com.google.inject.Provider;
|
||||
|
||||
import com.googlecode.prolog_cafe.compiler.CompileException;
|
||||
import com.googlecode.prolog_cafe.lang.IntegerTerm;
|
||||
import com.googlecode.prolog_cafe.lang.ListTerm;
|
||||
import com.googlecode.prolog_cafe.lang.Prolog;
|
||||
import com.googlecode.prolog_cafe.lang.PrologException;
|
||||
import com.googlecode.prolog_cafe.lang.StructureTerm;
|
||||
import com.googlecode.prolog_cafe.lang.SymbolTerm;
|
||||
import com.googlecode.prolog_cafe.lang.Term;
|
||||
import com.googlecode.prolog_cafe.lang.VariableTerm;
|
||||
|
||||
@@ -42,7 +45,9 @@ import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
|
||||
/** Access control management for a user accessing a single change. */
|
||||
@@ -238,9 +243,10 @@ public class ChangeControl {
|
||||
return ruleError("Patch set " + patchSetId + " is not current");
|
||||
}
|
||||
|
||||
ProjectState projectState = getProjectControl().getProjectState();
|
||||
PrologEnvironment env;
|
||||
try {
|
||||
env = getProjectControl().getProjectState().newPrologEnvironment();
|
||||
env = projectState.newPrologEnvironment();
|
||||
} catch (CompileException err) {
|
||||
return logRuleError("Cannot consult rules.pl for "
|
||||
+ getProject().getName(), err);
|
||||
@@ -275,6 +281,49 @@ public class ChangeControl {
|
||||
+ change.getId() + " of " + getProject().getName(), err);
|
||||
}
|
||||
|
||||
ProjectState parentState = projectState.getParentState();
|
||||
PrologEnvironment childEnv = env;
|
||||
Set<Project.NameKey> projectsSeen = new HashSet<Project.NameKey>();
|
||||
projectsSeen.add(getProject().getNameKey());
|
||||
|
||||
while (parentState != null) {
|
||||
if (!projectsSeen.add(parentState.getProject().getNameKey())) {
|
||||
//parent has been seen before, stop walk up inheritance tree
|
||||
break;
|
||||
}
|
||||
PrologEnvironment parentEnv;
|
||||
try {
|
||||
parentEnv = parentState.newPrologEnvironment();
|
||||
} catch (CompileException err) {
|
||||
return logRuleError("Cannot consult rules.pl for "
|
||||
+ parentState.getProject().getName(), err);
|
||||
}
|
||||
parentEnv.copyStoredValues(childEnv);
|
||||
Term filterRule =
|
||||
parentEnv.once("gerrit", "locate_submit_filter", new VariableTerm());
|
||||
if (filterRule != null) {
|
||||
try {
|
||||
Term resultsTerm = toListTerm(results);
|
||||
results.clear();
|
||||
Term[] template = parentEnv.once(
|
||||
"gerrit", "filter_submit_results",
|
||||
filterRule,
|
||||
resultsTerm,
|
||||
new VariableTerm());
|
||||
results.addAll(((ListTerm) template[2]).toJava());
|
||||
} catch (PrologException err) {
|
||||
return logRuleError("Exception calling " + filterRule + " on change "
|
||||
+ change.getId() + " of " + parentState.getProject().getName(), err);
|
||||
} catch (RuntimeException err) {
|
||||
return logRuleError("Exception calling " + filterRule + " on change "
|
||||
+ change.getId() + " of " + parentState.getProject().getName(), err);
|
||||
}
|
||||
}
|
||||
|
||||
parentState = parentState.getParentState();
|
||||
childEnv = parentEnv;
|
||||
}
|
||||
|
||||
if (results.isEmpty()) {
|
||||
// This should never occur. A well written submit rule will always produce
|
||||
// at least one result informing the caller of the labels that are
|
||||
@@ -398,4 +447,12 @@ public class ChangeControl {
|
||||
&& who.name().equals("user")
|
||||
&& who.arg(0).isInteger();
|
||||
}
|
||||
|
||||
private static Term toListTerm(List<Term> terms) {
|
||||
Term list = Prolog.Nil;
|
||||
for (int i = terms.size() - 1; i >= 0; i--) {
|
||||
list = new ListTerm(terms.get(i), list);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
}
|
@@ -52,6 +52,7 @@ public class ProjectState {
|
||||
}
|
||||
|
||||
private final boolean isAllProjects;
|
||||
private final AllProjectsName allProjectsName;
|
||||
private final ProjectCache projectCache;
|
||||
private final ProjectControl.AssistedFactory projectControlFactory;
|
||||
private final PrologEnvironment.Factory envFactory;
|
||||
@@ -84,6 +85,7 @@ public class ProjectState {
|
||||
@Assisted final ProjectConfig config) {
|
||||
this.projectCache = projectCache;
|
||||
this.isAllProjects = config.getProject().getNameKey().equals(allProjectsName);
|
||||
this.allProjectsName = allProjectsName;
|
||||
this.projectControlFactory = projectControlFactory;
|
||||
this.envFactory = envFactory;
|
||||
this.gitMgr = gitMgr;
|
||||
@@ -257,4 +259,20 @@ public class ProjectState {
|
||||
public ProjectControl controlFor(final CurrentUser user) {
|
||||
return projectControlFactory.create(user, this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ProjectState of project's parent. If the project does not have a
|
||||
* parent, return state of the top level project, All-Projects. If
|
||||
* this project is All-Projects, return null.
|
||||
*/
|
||||
public ProjectState getParentState() {
|
||||
if (isAllProjects) {
|
||||
return null;
|
||||
}
|
||||
Project.NameKey parentName = getProject().getParent();
|
||||
if (parentName == null) {
|
||||
parentName = allProjectsName;
|
||||
}
|
||||
return projectCache.get(parentName);
|
||||
}
|
||||
}
|
@@ -266,6 +266,98 @@ check_label_range_permission(Label, ExpValue, ok(Who)) :-
|
||||
% .
|
||||
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
%%
|
||||
%% filter_submit_results/3:
|
||||
%%
|
||||
%% Executes the submit_filter against the given list of results,
|
||||
%% returns a list of filtered results.
|
||||
%%
|
||||
:- public filter_submit_results/3.
|
||||
%%
|
||||
filter_submit_results(Filter, In, Out) :-
|
||||
filter_submit_results(Filter, In, [], Tmp),
|
||||
reverse(Tmp, Out).
|
||||
filter_submit_results(Filter, [I | In], Tmp, Out) :-
|
||||
arg(1, I, R),
|
||||
call_submit_filter(Filter, R, S),
|
||||
!,
|
||||
S =.. [submit | Ls],
|
||||
( is_all_ok(Ls) -> T = ok(S) ; T = not_ready(S) ),
|
||||
filter_submit_results(Filter, In, [T | Tmp], Out).
|
||||
filter_submit_results(Filter, [_ | In], Tmp, Out) :-
|
||||
filter_submit_results(Filter, In, Tmp, Out),
|
||||
!
|
||||
.
|
||||
filter_submit_results(Filter, [], Out, Out).
|
||||
|
||||
call_submit_filter(P:X, R, S) :- !, F =.. [X, R, S], P:F.
|
||||
call_submit_filter(X, R, S) :- F =.. [X, R, S], F.
|
||||
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
%%
|
||||
%% locate_submit_filter/1:
|
||||
%%
|
||||
%% Finds a submit_filter if available.
|
||||
%%
|
||||
:- public locate_submit_filter/1.
|
||||
%%
|
||||
locate_submit_filter(FilterName) :-
|
||||
'$compiled_predicate'(user, submit_filter, 2),
|
||||
!,
|
||||
FilterName = user:submit_filter
|
||||
.
|
||||
locate_submit_filter(FilterName) :-
|
||||
clause(user:submit_filter(_,_), _),
|
||||
FilterName = user:submit_filter
|
||||
.
|
||||
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
%%
|
||||
%% find_label/3:
|
||||
%%
|
||||
%% Finds labels successively and fails when there are no more results.
|
||||
%%
|
||||
:- public find_label/3.
|
||||
%%
|
||||
find_label([], _, _) :- !, fail.
|
||||
find_label(List, Name, Label) :-
|
||||
List = [_ | _],
|
||||
!,
|
||||
find_label2(List, Name, Label).
|
||||
find_label(S, Name, Label) :-
|
||||
S =.. [submit | Ls],
|
||||
find_label2(Ls, Name, Label).
|
||||
|
||||
find_label2([L | _ ], Name, L) :- L = label(Name, _).
|
||||
find_label2([_ | Ls], Name, L) :- find_label2(Ls, Name, L).
|
||||
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
%%
|
||||
%% remove_label/3:
|
||||
%%
|
||||
%% Removes all occurances of label(Name, Status).
|
||||
%%
|
||||
:- public remove_label/3.
|
||||
%%
|
||||
remove_label([], _, []) :- !.
|
||||
remove_label(List, Label, Out) :-
|
||||
List = [_ | _],
|
||||
!,
|
||||
subtract1(List, Label, Out).
|
||||
remove_label(S, Label, Out) :-
|
||||
S =.. [submit | Ls],
|
||||
subtract1(Ls, Label, Tmp),
|
||||
Out =.. [submit | Tmp].
|
||||
|
||||
subtract1([], _, []) :- !.
|
||||
subtract1([E | L], E, R) :- !, subtract1(L, E, R).
|
||||
subtract1([H | L], E, [H | R]) :- subtract1(L, E, R).
|
||||
|
||||
|
||||
%% commit_author/1:
|
||||
%%
|
||||
:- public commit_author/1.
|
||||
|
@@ -87,6 +87,57 @@ test(can_submit_not_ready) :-
|
||||
C = label('Code-Review', ok(test_user(alice))),
|
||||
V = label('Verified', need(1)).
|
||||
|
||||
test(can_submit_only_verified_not_ready) :-
|
||||
can_submit(submit_only_verified, S),
|
||||
S = not_ready(submit(V)),
|
||||
V = label('Verified', need(1)).
|
||||
|
||||
|
||||
%% filter_submit_results
|
||||
%%
|
||||
test(filter_submit_remove_verified) :-
|
||||
can_submit(gerrit:default_submit, R),
|
||||
filter_submit_results(filter_out_v, [R], S),
|
||||
S = [ok(submit(C))],
|
||||
C = label('Code-Review', ok(test_user(alice))).
|
||||
|
||||
test(filter_submit_add_code_review) :-
|
||||
set_commit_labels([
|
||||
commit_label( label('Code-Review', 2), test_user(alice) ),
|
||||
commit_label( label('Verified', 1), test_user(builder) )
|
||||
]),
|
||||
can_submit(submit_only_verified, R),
|
||||
filter_submit_results(filter_in_cr, [R], S),
|
||||
S = [ok(submit(C, V))],
|
||||
C = label('Code-Review', ok(test_user(alice))),
|
||||
V = label('Verified', ok(test_user(builder))).
|
||||
|
||||
|
||||
%% find_label
|
||||
%%
|
||||
test(find_default_code_review) :-
|
||||
can_submit(gerrit:default_submit, R),
|
||||
arg(1, R, S),
|
||||
find_label(S, 'Code-Review', L),
|
||||
L = label('Code-Review', ok(test_user(alice))).
|
||||
|
||||
test(find_default_verified) :-
|
||||
can_submit(gerrit:default_submit, R),
|
||||
arg(1, R, S),
|
||||
find_label(S, 'Verified', L),
|
||||
L = label('Verified', need(1)).
|
||||
|
||||
|
||||
%% remove_label
|
||||
%%
|
||||
test(remove_default_code_review) :-
|
||||
can_submit(gerrit:default_submit, R),
|
||||
arg(1, R, S),
|
||||
C = label('Code-Review', ok(test_user(alice))),
|
||||
remove_label(S, C, Out),
|
||||
Out = submit(V),
|
||||
V = label('Verified', need(1)).
|
||||
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
%%
|
||||
@@ -107,6 +158,21 @@ all_commit_labels(Ls) :-
|
||||
commit_label( label('You-Fail', -1), test_user(alice) )
|
||||
].
|
||||
|
||||
submit_only_verified(P) :-
|
||||
max_with_block('Verified', -1, 1, Status),
|
||||
P = submit(label('Verified', Status)).
|
||||
|
||||
filter_out_v(R, S) :-
|
||||
find_label(R, 'Verified', Verified), !,
|
||||
remove_label(R, Verified, S).
|
||||
filter_out_v(R, S).
|
||||
|
||||
filter_in_cr(R, S) :-
|
||||
R =.. [submit | Labels],
|
||||
max_with_block('Code-Review', -2, 2, Status),
|
||||
CR = label('Code-Review', Status),
|
||||
S =.. [submit , CR | Labels].
|
||||
|
||||
:- package user.
|
||||
test_grant('Code-Review', test_user(alice), range(-2, 2)).
|
||||
test_grant('Verified', test_user(builder), range(-1, 1)).
|
||||
|
Reference in New Issue
Block a user