Use Prolog Cafe for ChangeControl.canSubmit()
Re-implement the canSubmit() logic in Prolog, removing one of the last uses of FunctionState. This is a simple re-implementation that does not provide any new functionality. PatchSetApproval objects are exposed into Prolog on demand by loading them from the database if the submit predicate needs them. These get indexed in a temporary HashMap by their label name (aka ApprovalCategory), making access faster during evaluation of the rule. ApprovalTypes are loaded into Prolog on demand from the Guice Injector. This will eventually go away when we get rid of the global declaration of ApprovalCategories in the web UI. CurrentUser instances are loaded and cached within the Prolog environment as needed to consider PatchSetApproval objects and if the user has the necessary permission to make that assertion. Bug: 971 Change-Id: I7e261948db08b7c3180e590e81f492ff3e6f237e Signed-off-by: Shawn O. Pearce <sop@google.com>
This commit is contained in:
@@ -14,7 +14,6 @@
|
|||||||
|
|
||||||
package com.google.gerrit.httpd.rpc.changedetail;
|
package com.google.gerrit.httpd.rpc.changedetail;
|
||||||
|
|
||||||
import com.google.gerrit.common.data.ApprovalTypes;
|
|
||||||
import com.google.gerrit.common.data.ChangeDetail;
|
import com.google.gerrit.common.data.ChangeDetail;
|
||||||
import com.google.gerrit.common.errors.NoSuchEntityException;
|
import com.google.gerrit.common.errors.NoSuchEntityException;
|
||||||
import com.google.gerrit.httpd.rpc.Handler;
|
import com.google.gerrit.httpd.rpc.Handler;
|
||||||
@@ -29,7 +28,6 @@ import com.google.gerrit.server.patch.PatchSetInfoNotAvailableException;
|
|||||||
import com.google.gerrit.server.project.CanSubmitResult;
|
import com.google.gerrit.server.project.CanSubmitResult;
|
||||||
import com.google.gerrit.server.project.ChangeControl;
|
import com.google.gerrit.server.project.ChangeControl;
|
||||||
import com.google.gerrit.server.project.NoSuchChangeException;
|
import com.google.gerrit.server.project.NoSuchChangeException;
|
||||||
import com.google.gerrit.server.workflow.FunctionState;
|
|
||||||
import com.google.gwtorm.client.OrmException;
|
import com.google.gwtorm.client.OrmException;
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
import com.google.inject.assistedinject.Assisted;
|
import com.google.inject.assistedinject.Assisted;
|
||||||
@@ -41,8 +39,6 @@ class SubmitAction extends Handler<ChangeDetail> {
|
|||||||
|
|
||||||
private final ReviewDb db;
|
private final ReviewDb db;
|
||||||
private final MergeQueue merger;
|
private final MergeQueue merger;
|
||||||
private final ApprovalTypes approvalTypes;
|
|
||||||
private final FunctionState.Factory functionState;
|
|
||||||
private final IdentifiedUser user;
|
private final IdentifiedUser user;
|
||||||
private final ChangeDetailFactory.Factory changeDetailFactory;
|
private final ChangeDetailFactory.Factory changeDetailFactory;
|
||||||
private final ChangeControl.Factory changeControlFactory;
|
private final ChangeControl.Factory changeControlFactory;
|
||||||
@@ -51,16 +47,14 @@ class SubmitAction extends Handler<ChangeDetail> {
|
|||||||
private final PatchSet.Id patchSetId;
|
private final PatchSet.Id patchSetId;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
SubmitAction(final ReviewDb db, final MergeQueue mq, final ApprovalTypes at,
|
SubmitAction(final ReviewDb db, final MergeQueue mq,
|
||||||
final FunctionState.Factory fs, final IdentifiedUser user,
|
final IdentifiedUser user,
|
||||||
final ChangeDetailFactory.Factory changeDetailFactory,
|
final ChangeDetailFactory.Factory changeDetailFactory,
|
||||||
final ChangeControl.Factory changeControlFactory,
|
final ChangeControl.Factory changeControlFactory,
|
||||||
final MergeOp.Factory opFactory,
|
final MergeOp.Factory opFactory,
|
||||||
@Assisted final PatchSet.Id patchSetId) {
|
@Assisted final PatchSet.Id patchSetId) {
|
||||||
this.db = db;
|
this.db = db;
|
||||||
this.merger = mq;
|
this.merger = mq;
|
||||||
this.approvalTypes = at;
|
|
||||||
this.functionState = fs;
|
|
||||||
this.user = user;
|
this.user = user;
|
||||||
this.changeControlFactory = changeControlFactory;
|
this.changeControlFactory = changeControlFactory;
|
||||||
this.changeDetailFactory = changeDetailFactory;
|
this.changeDetailFactory = changeDetailFactory;
|
||||||
@@ -78,8 +72,7 @@ class SubmitAction extends Handler<ChangeDetail> {
|
|||||||
final ChangeControl changeControl =
|
final ChangeControl changeControl =
|
||||||
changeControlFactory.validateFor(changeId);
|
changeControlFactory.validateFor(changeId);
|
||||||
|
|
||||||
CanSubmitResult err =
|
CanSubmitResult err = changeControl.canSubmit(db, patchSetId);
|
||||||
changeControl.canSubmit(patchSetId, db, approvalTypes, functionState);
|
|
||||||
if (err == CanSubmitResult.OK) {
|
if (err == CanSubmitResult.OK) {
|
||||||
ChangeUtil.submit(patchSetId, user, db, opFactory, merger);
|
ChangeUtil.submit(patchSetId, user, db, opFactory, merger);
|
||||||
return changeDetailFactory.create(changeId).call();
|
return changeDetailFactory.create(changeId).call();
|
||||||
|
|||||||
@@ -164,4 +164,58 @@ limitations under the License.
|
|||||||
<artifactId>PrologCafe</artifactId>
|
<artifactId>PrologCafe</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-antrun-plugin</artifactId>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>prolog-to-java</id>
|
||||||
|
<phase>generate-sources</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>run</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<target>
|
||||||
|
<property name="gensrc" location="${project.build.directory}/generated-sources"/>
|
||||||
|
<property name="pkg" location="${gensrc}/prolog-java/com/google/gerrit/rules"/>
|
||||||
|
|
||||||
|
<java classname="com.googlecode.prolog_cafe.compiler.Compiler"
|
||||||
|
fork="true"
|
||||||
|
failonerror="true"
|
||||||
|
classpathref="maven.compile.classpath">
|
||||||
|
<arg value="--show-stack-trace"/>
|
||||||
|
<arg value="-O"/>
|
||||||
|
<arg value="-am"/><arg value="${gensrc}/prolog-am"/>
|
||||||
|
<arg value="-s" /><arg value="${pkg}/common"/>
|
||||||
|
<arg value="src/main/prolog/common_rules.pl"/>
|
||||||
|
</java>
|
||||||
|
</target>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.codehaus.mojo</groupId>
|
||||||
|
<artifactId>build-helper-maven-plugin</artifactId>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>add-source</id>
|
||||||
|
<phase>generate-sources</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>add-source</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<sources>
|
||||||
|
<source>${project.build.directory}/generated-sources/prolog-java</source>
|
||||||
|
</sources>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
</project>
|
</project>
|
||||||
|
|||||||
@@ -0,0 +1,103 @@
|
|||||||
|
// Copyright (C) 2011 The Android Open Source Project
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package com.google.gerrit.rules;
|
||||||
|
|
||||||
|
import com.google.inject.Inject;
|
||||||
|
import com.google.inject.Injector;
|
||||||
|
import com.google.inject.assistedinject.Assisted;
|
||||||
|
|
||||||
|
import com.googlecode.prolog_cafe.lang.BufferingPrologControl;
|
||||||
|
import com.googlecode.prolog_cafe.lang.Prolog;
|
||||||
|
import com.googlecode.prolog_cafe.lang.PrologClassLoader;
|
||||||
|
import com.googlecode.prolog_cafe.lang.SystemException;
|
||||||
|
import com.googlecode.prolog_cafe.lang.Term;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Per-thread Prolog interpreter.
|
||||||
|
* <p>
|
||||||
|
* This class is not thread safe.
|
||||||
|
* <p>
|
||||||
|
* A single copy of the Prolog interpreter, for the current thread.
|
||||||
|
*/
|
||||||
|
public class PrologEnvironment extends BufferingPrologControl {
|
||||||
|
private static final String[] PACKAGE_LIST = {
|
||||||
|
Prolog.BUILTIN,
|
||||||
|
"com.google.gerrit.rules.common",
|
||||||
|
};
|
||||||
|
|
||||||
|
public static interface Factory {
|
||||||
|
/**
|
||||||
|
* Construct a new Prolog interpreter.
|
||||||
|
*
|
||||||
|
* @param cl ClassLoader to dynamically load predicates from.
|
||||||
|
* @return the new interpreter.
|
||||||
|
*/
|
||||||
|
PrologEnvironment create(ClassLoader cl);
|
||||||
|
}
|
||||||
|
|
||||||
|
private final Injector injector;
|
||||||
|
private boolean intialized;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
PrologEnvironment(Injector i, @Assisted ClassLoader newCL) {
|
||||||
|
injector = i;
|
||||||
|
setPrologClassLoader(new PrologClassLoader(newCL));
|
||||||
|
setMaxArity(8);
|
||||||
|
setMaxDatabaseSize(64);
|
||||||
|
setEnableReflection(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Get the global Guice Injector that configured the environment. */
|
||||||
|
public Injector getInjector() {
|
||||||
|
return injector;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lookup a stored value in the interpreter's hash manager.
|
||||||
|
*
|
||||||
|
* @param <T> type of stored Java object.
|
||||||
|
* @param sv unique key.
|
||||||
|
* @return the value; null if not stored.
|
||||||
|
*/
|
||||||
|
public <T> T get(StoredValue<T> sv) {
|
||||||
|
return sv.getOrNull(engine);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set a stored value on the interpreter's hash manager.
|
||||||
|
*
|
||||||
|
* @param <T> type of stored Java object.
|
||||||
|
* @param sv unique key.
|
||||||
|
* @param obj the value to store under {@code sv}.
|
||||||
|
*/
|
||||||
|
public <T> void set(StoredValue<T> sv, T obj) {
|
||||||
|
sv.set(engine, obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setPredicate(String pkg, String functor, Term... args) {
|
||||||
|
init();
|
||||||
|
super.setPredicate(pkg, functor, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void init() {
|
||||||
|
if (!intialized) {
|
||||||
|
intialized = true;
|
||||||
|
if (!initialize(PACKAGE_LIST)) {
|
||||||
|
throw new SystemException("Prolog initialization failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
// Copyright (C) 2011 The Android Open Source Project
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package com.google.gerrit.rules;
|
||||||
|
|
||||||
|
import com.google.gerrit.server.config.FactoryModule;
|
||||||
|
|
||||||
|
public class PrologModule extends FactoryModule {
|
||||||
|
@Override
|
||||||
|
protected void configure() {
|
||||||
|
factory(PrologEnvironment.Factory.class);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
// Copyright (C) 2011 The Android Open Source Project
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package com.google.gerrit.rules;
|
||||||
|
|
||||||
|
import com.googlecode.prolog_cafe.lang.JavaObjectTerm;
|
||||||
|
import com.googlecode.prolog_cafe.lang.Prolog;
|
||||||
|
import com.googlecode.prolog_cafe.lang.SystemException;
|
||||||
|
import com.googlecode.prolog_cafe.lang.Term;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defines a value cached in a {@link PrologEnvironment}.
|
||||||
|
*
|
||||||
|
* @see StoredValues
|
||||||
|
*/
|
||||||
|
public class StoredValue<T> {
|
||||||
|
/** Construct a new unique key that does not match any other key. */
|
||||||
|
public static <T> StoredValue<T> create() {
|
||||||
|
return new StoredValue<T>(new JavaObjectTerm(new Object()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Construct a key based on a Java Class object, useful for singletons. */
|
||||||
|
public static <T> StoredValue<T> create(Class<T> clazz) {
|
||||||
|
return new StoredValue<T>(new JavaObjectTerm(clazz));
|
||||||
|
}
|
||||||
|
|
||||||
|
private final Term key;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize a stored value key using a Prolog term.
|
||||||
|
*
|
||||||
|
* @param key unique identity of the stored value. This will be the hash key
|
||||||
|
* in the interpreter's hash manager.
|
||||||
|
*/
|
||||||
|
public StoredValue(Term key) {
|
||||||
|
this.key = key;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Look up the value in the engine, or return null. */
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public T getOrNull(Prolog engine) {
|
||||||
|
Term r = engine.getHashManager().get(key);
|
||||||
|
return r != null && r.isJavaObject() ? (T) r.toJava() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Get the value from the engine, or throw SystemException. */
|
||||||
|
public T get(Prolog engine) {
|
||||||
|
T r = getOrNull(engine);
|
||||||
|
if (r == null) {
|
||||||
|
String msg;
|
||||||
|
if (key.isJavaObject() && key.toJava() instanceof Class<?>) {
|
||||||
|
msg = "No " + ((Class<?>) key.toJava()).getName() + " avaliable";
|
||||||
|
} else {
|
||||||
|
msg = key.toString();
|
||||||
|
}
|
||||||
|
throw new SystemException(msg);
|
||||||
|
}
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void set(Prolog engine, T obj) {
|
||||||
|
engine.getHashManager().put(key, new JavaObjectTerm(obj));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Perform {@link #getOrNull(Prolog)} on the environment's interpreter. */
|
||||||
|
public T get(PrologEnvironment env) {
|
||||||
|
return env.get(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Set the value into the environment's interpreter. */
|
||||||
|
public void set(PrologEnvironment env, T obj) {
|
||||||
|
env.set(this, obj);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
// Copyright (C) 2011 The Android Open Source Project
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package com.google.gerrit.rules;
|
||||||
|
|
||||||
|
import static com.google.gerrit.rules.StoredValue.create;
|
||||||
|
|
||||||
|
import com.google.gerrit.reviewdb.Change;
|
||||||
|
import com.google.gerrit.reviewdb.PatchSet;
|
||||||
|
import com.google.gerrit.reviewdb.ReviewDb;
|
||||||
|
import com.google.gerrit.server.project.ChangeControl;
|
||||||
|
|
||||||
|
public final class StoredValues {
|
||||||
|
public static final StoredValue<ReviewDb> REVIEW_DB = create(ReviewDb.class);
|
||||||
|
public static final StoredValue<Change> CHANGE = create(Change.class);
|
||||||
|
public static final StoredValue<PatchSet.Id> PATCH_SET_ID = create(PatchSet.Id.class);
|
||||||
|
public static final StoredValue<ChangeControl> CHANGE_CONTROL = create(ChangeControl.class);
|
||||||
|
|
||||||
|
private StoredValues() {
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
// Copyright (C) 2011 The Android Open Source Project
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package com.google.gerrit.rules.common;
|
||||||
|
|
||||||
|
import com.google.gerrit.reviewdb.Account;
|
||||||
|
import com.google.gerrit.reviewdb.ReviewDb;
|
||||||
|
import com.google.gerrit.rules.PrologEnvironment;
|
||||||
|
import com.google.gerrit.rules.StoredValues;
|
||||||
|
import com.google.gerrit.server.CurrentUser;
|
||||||
|
import com.google.gerrit.server.IdentifiedUser;
|
||||||
|
import com.google.inject.Provider;
|
||||||
|
|
||||||
|
import com.googlecode.prolog_cafe.lang.IllegalTypeException;
|
||||||
|
import com.googlecode.prolog_cafe.lang.IntegerTerm;
|
||||||
|
import com.googlecode.prolog_cafe.lang.JavaObjectTerm;
|
||||||
|
import com.googlecode.prolog_cafe.lang.Operation;
|
||||||
|
import com.googlecode.prolog_cafe.lang.PInstantiationException;
|
||||||
|
import com.googlecode.prolog_cafe.lang.Predicate;
|
||||||
|
import com.googlecode.prolog_cafe.lang.Prolog;
|
||||||
|
import com.googlecode.prolog_cafe.lang.PrologException;
|
||||||
|
import com.googlecode.prolog_cafe.lang.Term;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads a CurrentUser object for a user identity.
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* '$current_user'(+AccountId, -CurrentUser).
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
class PRED_$current_user_2 extends Predicate.P2 {
|
||||||
|
PRED_$current_user_2(Term a1, Term a2, Operation n) {
|
||||||
|
arg1 = a1;
|
||||||
|
arg2 = a2;
|
||||||
|
cont = n;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Operation exec(Prolog engine) throws PrologException {
|
||||||
|
engine.setB0();
|
||||||
|
Term a1 = arg1.dereference();
|
||||||
|
Term a2 = arg2.dereference();
|
||||||
|
|
||||||
|
if (a1.isVariable()) {
|
||||||
|
throw new PInstantiationException(this, 1);
|
||||||
|
}
|
||||||
|
if (!a1.isInteger()) {
|
||||||
|
throw new IllegalTypeException(this, 1, "int", a1);
|
||||||
|
}
|
||||||
|
Account.Id accountId = new Account.Id(((IntegerTerm) a1).intValue());
|
||||||
|
|
||||||
|
final ReviewDb db = StoredValues.REVIEW_DB.getOrNull(engine);
|
||||||
|
IdentifiedUser.GenericFactory userFactory = userFactory(engine);
|
||||||
|
CurrentUser user;
|
||||||
|
if (db != null) {
|
||||||
|
user = userFactory.create(new Provider<ReviewDb>() {
|
||||||
|
public ReviewDb get() {
|
||||||
|
return db;
|
||||||
|
}
|
||||||
|
}, accountId);
|
||||||
|
} else {
|
||||||
|
user = userFactory.create(accountId);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!a2.unify(new JavaObjectTerm(user), engine.trail)) {
|
||||||
|
return engine.fail();
|
||||||
|
}
|
||||||
|
|
||||||
|
return cont;
|
||||||
|
}
|
||||||
|
|
||||||
|
private IdentifiedUser.GenericFactory userFactory(Prolog engine) {
|
||||||
|
PrologEnvironment env = (PrologEnvironment) engine.control;
|
||||||
|
return env.getInjector().getInstance(IdentifiedUser.GenericFactory.class);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
// Copyright 2011 Google Inc. All Rights Reserved.
|
||||||
|
|
||||||
|
package com.google.gerrit.rules.common;
|
||||||
|
|
||||||
|
import com.google.gerrit.common.data.ApprovalType;
|
||||||
|
import com.google.gerrit.common.data.ApprovalTypes;
|
||||||
|
import com.google.gerrit.reviewdb.PatchSet;
|
||||||
|
import com.google.gerrit.reviewdb.PatchSetApproval;
|
||||||
|
import com.google.gerrit.reviewdb.ReviewDb;
|
||||||
|
import com.google.gerrit.rules.PrologEnvironment;
|
||||||
|
import com.google.gerrit.rules.StoredValues;
|
||||||
|
import com.google.gwtorm.client.OrmException;
|
||||||
|
|
||||||
|
import com.googlecode.prolog_cafe.lang.IntegerTerm;
|
||||||
|
import com.googlecode.prolog_cafe.lang.JavaException;
|
||||||
|
import com.googlecode.prolog_cafe.lang.ListTerm;
|
||||||
|
import com.googlecode.prolog_cafe.lang.Operation;
|
||||||
|
import com.googlecode.prolog_cafe.lang.Predicate;
|
||||||
|
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;
|
||||||
|
|
||||||
|
/** Exports list of {@code commit_label( label('Code-Review', 2), user(12345789) )}. */
|
||||||
|
class PRED_$load_commit_labels_1 extends Predicate.P1 {
|
||||||
|
private static final SymbolTerm sym_commit_label = SymbolTerm.makeSymbol("commit_label", 2);
|
||||||
|
private static final SymbolTerm sym_label = SymbolTerm.makeSymbol("label", 2);
|
||||||
|
private static final SymbolTerm sym_user = SymbolTerm.makeSymbol("user", 1);
|
||||||
|
|
||||||
|
PRED_$load_commit_labels_1(Term a1, Operation n) {
|
||||||
|
arg1 = a1;
|
||||||
|
cont = n;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Operation exec(Prolog engine) throws PrologException {
|
||||||
|
engine.setB0();
|
||||||
|
Term a1 = arg1.dereference();
|
||||||
|
|
||||||
|
Term listHead = Prolog.Nil;
|
||||||
|
try {
|
||||||
|
PrologEnvironment env = (PrologEnvironment) engine.control;
|
||||||
|
ReviewDb db = StoredValues.REVIEW_DB.get(engine);
|
||||||
|
PatchSet.Id patchSetId = StoredValues.PATCH_SET_ID.get(engine);
|
||||||
|
ApprovalTypes types = env.getInjector().getInstance(ApprovalTypes.class);
|
||||||
|
|
||||||
|
for (PatchSetApproval a : db.patchSetApprovals().byPatchSet(patchSetId)) {
|
||||||
|
if (a.getValue() == 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
ApprovalType t = types.byId(a.getCategoryId());
|
||||||
|
if (t == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
StructureTerm labelTerm = new StructureTerm(
|
||||||
|
sym_label,
|
||||||
|
SymbolTerm.makeSymbol(t.getCategory().getLabelName()),
|
||||||
|
new IntegerTerm(a.getValue()));
|
||||||
|
|
||||||
|
StructureTerm userTerm = new StructureTerm(
|
||||||
|
sym_user,
|
||||||
|
new IntegerTerm(a.getAccountId().get()));
|
||||||
|
|
||||||
|
listHead = new ListTerm(
|
||||||
|
new StructureTerm(sym_commit_label, labelTerm, userTerm),
|
||||||
|
listHead);
|
||||||
|
}
|
||||||
|
} catch (OrmException err) {
|
||||||
|
throw new JavaException(this, 1, err);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!a1.unify(listHead, engine.trail)) {
|
||||||
|
return engine.fail();
|
||||||
|
}
|
||||||
|
return cont;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
// Copyright (C) 2011 The Android Open Source Project
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package com.google.gerrit.rules.common;
|
||||||
|
|
||||||
|
import com.google.gerrit.common.data.Permission;
|
||||||
|
import com.google.gerrit.common.data.PermissionRange;
|
||||||
|
import com.google.gerrit.rules.StoredValues;
|
||||||
|
import com.google.gerrit.server.CurrentUser;
|
||||||
|
import com.google.gerrit.server.project.ChangeControl;
|
||||||
|
|
||||||
|
import com.googlecode.prolog_cafe.lang.IllegalTypeException;
|
||||||
|
import com.googlecode.prolog_cafe.lang.IntegerTerm;
|
||||||
|
import com.googlecode.prolog_cafe.lang.JavaObjectTerm;
|
||||||
|
import com.googlecode.prolog_cafe.lang.Operation;
|
||||||
|
import com.googlecode.prolog_cafe.lang.PInstantiationException;
|
||||||
|
import com.googlecode.prolog_cafe.lang.Predicate;
|
||||||
|
import com.googlecode.prolog_cafe.lang.Prolog;
|
||||||
|
import com.googlecode.prolog_cafe.lang.PrologException;
|
||||||
|
import com.googlecode.prolog_cafe.lang.Term;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolves the valid range for a label on a CurrentUser.
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* '$user_label_range'(+Label, +CurrentUser, -Min, -Max)
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
class PRED_$user_label_range_4 extends Predicate.P4 {
|
||||||
|
PRED_$user_label_range_4(Term a1, Term a2, Term a3, Term a4, Operation n) {
|
||||||
|
arg1 = a1;
|
||||||
|
arg2 = a2;
|
||||||
|
arg3 = a3;
|
||||||
|
arg4 = a4;
|
||||||
|
cont = n;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Operation exec(Prolog engine) throws PrologException {
|
||||||
|
engine.setB0();
|
||||||
|
Term a1 = arg1.dereference();
|
||||||
|
Term a2 = arg2.dereference();
|
||||||
|
Term a3 = arg3.dereference();
|
||||||
|
Term a4 = arg4.dereference();
|
||||||
|
|
||||||
|
if (a1.isVariable()) {
|
||||||
|
throw new PInstantiationException(this, 1);
|
||||||
|
}
|
||||||
|
if (!a1.isSymbol()) {
|
||||||
|
throw new IllegalTypeException(this, 1, "atom", a1);
|
||||||
|
}
|
||||||
|
String label = a1.name();
|
||||||
|
|
||||||
|
if (a2.isVariable()) {
|
||||||
|
throw new PInstantiationException(this, 2);
|
||||||
|
}
|
||||||
|
if (!a2.isJavaObject() || !a2.convertible(CurrentUser.class)) {
|
||||||
|
throw new IllegalTypeException(this, 2, "CurrentUser)", a2);
|
||||||
|
}
|
||||||
|
CurrentUser user = (CurrentUser) ((JavaObjectTerm) a2).object();
|
||||||
|
|
||||||
|
ChangeControl ctl = StoredValues.CHANGE_CONTROL.get(engine).forUser(user);
|
||||||
|
PermissionRange range = ctl.getRange(Permission.LABEL + label);
|
||||||
|
if (range == null) {
|
||||||
|
return engine.fail();
|
||||||
|
}
|
||||||
|
|
||||||
|
IntegerTerm min = new IntegerTerm(range.getMin());
|
||||||
|
IntegerTerm max = new IntegerTerm(range.getMax());
|
||||||
|
|
||||||
|
if (!a3.unify(min, engine.trail)) {
|
||||||
|
return engine.fail();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!a4.unify(max, engine.trail)) {
|
||||||
|
return engine.fail();
|
||||||
|
}
|
||||||
|
|
||||||
|
return cont;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
// Copyright (C) 2011 The Android Open Source Project
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package com.google.gerrit.rules.common;
|
||||||
|
|
||||||
|
import com.google.gerrit.common.data.ApprovalType;
|
||||||
|
import com.google.gerrit.common.data.ApprovalTypes;
|
||||||
|
import com.google.gerrit.rules.PrologEnvironment;
|
||||||
|
|
||||||
|
import com.googlecode.prolog_cafe.lang.IntegerTerm;
|
||||||
|
import com.googlecode.prolog_cafe.lang.ListTerm;
|
||||||
|
import com.googlecode.prolog_cafe.lang.Operation;
|
||||||
|
import com.googlecode.prolog_cafe.lang.Predicate;
|
||||||
|
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 java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtain a list of approval types from the server configuration.
|
||||||
|
* <p>
|
||||||
|
* Unifies to a Prolog list of: {@code approval_type(Label, Id, Fun, Min, Max)}
|
||||||
|
* where:
|
||||||
|
* <ul>
|
||||||
|
* <li>{@code Label} - the newer style label name</li>
|
||||||
|
* <li>{@code Id} - the legacy ApprovalCategory.Id from the database</li>
|
||||||
|
* <li>{@code Fun} - legacy function name</li>
|
||||||
|
* <li>{@code Min, Max} - the smallest and largest configured values.</li>
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
|
class PRED_get_legacy_approval_types_1 extends Predicate.P1 {
|
||||||
|
PRED_get_legacy_approval_types_1(Term a1, Operation n) {
|
||||||
|
arg1 = a1;
|
||||||
|
cont = n;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Operation exec(Prolog engine) throws PrologException {
|
||||||
|
engine.setB0();
|
||||||
|
Term a1 = arg1.dereference();
|
||||||
|
|
||||||
|
PrologEnvironment env = (PrologEnvironment) engine.control;
|
||||||
|
ApprovalTypes types = env.getInjector().getInstance(ApprovalTypes.class);
|
||||||
|
|
||||||
|
List<ApprovalType> list = types.getApprovalTypes();
|
||||||
|
Term head = Prolog.Nil;
|
||||||
|
for (int idx = list.size() - 1; 0 <= idx; idx--) {
|
||||||
|
head = new ListTerm(export(list.get(idx)), head);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!a1.unify(head, engine.trail)) {
|
||||||
|
return engine.fail();
|
||||||
|
}
|
||||||
|
return cont;
|
||||||
|
}
|
||||||
|
|
||||||
|
static final SymbolTerm symApprovalType = SymbolTerm.makeSymbol(
|
||||||
|
"approval_type", 5);
|
||||||
|
|
||||||
|
static Term export(ApprovalType type) {
|
||||||
|
return new StructureTerm(symApprovalType,
|
||||||
|
SymbolTerm.makeSymbol(type.getCategory().getLabelName()),
|
||||||
|
SymbolTerm.makeSymbol(type.getCategory().getId().get()),
|
||||||
|
SymbolTerm.makeSymbol(type.getCategory().getFunctionName()),
|
||||||
|
new IntegerTerm(type.getMin().getValue()),
|
||||||
|
new IntegerTerm(type.getMax().getValue()));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -21,6 +21,7 @@ import com.google.gerrit.lifecycle.LifecycleListener;
|
|||||||
import com.google.gerrit.lifecycle.LifecycleModule;
|
import com.google.gerrit.lifecycle.LifecycleModule;
|
||||||
import com.google.gerrit.reviewdb.AuthType;
|
import com.google.gerrit.reviewdb.AuthType;
|
||||||
import com.google.gerrit.reviewdb.Project;
|
import com.google.gerrit.reviewdb.Project;
|
||||||
|
import com.google.gerrit.rules.PrologModule;
|
||||||
import com.google.gerrit.server.AnonymousUser;
|
import com.google.gerrit.server.AnonymousUser;
|
||||||
import com.google.gerrit.server.FileTypeRegistry;
|
import com.google.gerrit.server.FileTypeRegistry;
|
||||||
import com.google.gerrit.server.IdentifiedUser;
|
import com.google.gerrit.server.IdentifiedUser;
|
||||||
@@ -149,6 +150,7 @@ public class GerritGlobalModule extends FactoryModule {
|
|||||||
install(ProjectCacheImpl.module());
|
install(ProjectCacheImpl.module());
|
||||||
install(new AccessControlModule());
|
install(new AccessControlModule());
|
||||||
install(new GitModule());
|
install(new GitModule());
|
||||||
|
install(new PrologModule());
|
||||||
|
|
||||||
factory(AccountInfoCacheFactory.Factory.class);
|
factory(AccountInfoCacheFactory.Factory.class);
|
||||||
factory(GroupInfoCacheFactory.Factory.class);
|
factory(GroupInfoCacheFactory.Factory.class);
|
||||||
|
|||||||
@@ -14,27 +14,39 @@
|
|||||||
|
|
||||||
package com.google.gerrit.server.project;
|
package com.google.gerrit.server.project;
|
||||||
|
|
||||||
import com.google.gerrit.common.data.ApprovalType;
|
|
||||||
import com.google.gerrit.common.data.ApprovalTypes;
|
|
||||||
import com.google.gerrit.common.data.PermissionRange;
|
import com.google.gerrit.common.data.PermissionRange;
|
||||||
import com.google.gerrit.reviewdb.Change;
|
import com.google.gerrit.reviewdb.Change;
|
||||||
import com.google.gerrit.reviewdb.PatchSet;
|
import com.google.gerrit.reviewdb.PatchSet;
|
||||||
import com.google.gerrit.reviewdb.PatchSetApproval;
|
import com.google.gerrit.reviewdb.PatchSetApproval;
|
||||||
import com.google.gerrit.reviewdb.Project;
|
import com.google.gerrit.reviewdb.Project;
|
||||||
import com.google.gerrit.reviewdb.ReviewDb;
|
import com.google.gerrit.reviewdb.ReviewDb;
|
||||||
|
import com.google.gerrit.rules.PrologEnvironment;
|
||||||
|
import com.google.gerrit.rules.StoredValues;
|
||||||
import com.google.gerrit.server.CurrentUser;
|
import com.google.gerrit.server.CurrentUser;
|
||||||
import com.google.gerrit.server.IdentifiedUser;
|
import com.google.gerrit.server.IdentifiedUser;
|
||||||
import com.google.gerrit.server.workflow.CategoryFunction;
|
|
||||||
import com.google.gerrit.server.workflow.FunctionState;
|
|
||||||
import com.google.gwtorm.client.OrmException;
|
import com.google.gwtorm.client.OrmException;
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
import com.google.inject.Provider;
|
import com.google.inject.Provider;
|
||||||
|
|
||||||
|
import com.googlecode.prolog_cafe.lang.IntegerTerm;
|
||||||
|
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;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
|
||||||
/** Access control management for a user accessing a single change. */
|
/** Access control management for a user accessing a single change. */
|
||||||
public class ChangeControl {
|
public class ChangeControl {
|
||||||
|
private static final Logger log = LoggerFactory
|
||||||
|
.getLogger(ChangeControl.class);
|
||||||
|
|
||||||
public static class GenericFactory {
|
public static class GenericFactory {
|
||||||
private final ProjectControl.GenericFactory projectControl;
|
private final ProjectControl.GenericFactory projectControl;
|
||||||
|
|
||||||
@@ -234,29 +246,95 @@ public class ChangeControl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** @return {@link CanSubmitResult#OK}, or a result with an error message. */
|
/** @return {@link CanSubmitResult#OK}, or a result with an error message. */
|
||||||
public CanSubmitResult canSubmit(final PatchSet.Id patchSetId, final ReviewDb db,
|
public CanSubmitResult canSubmit(ReviewDb db, PatchSet.Id patchSetId) {
|
||||||
final ApprovalTypes approvalTypes,
|
|
||||||
FunctionState.Factory functionStateFactory)
|
|
||||||
throws OrmException {
|
|
||||||
|
|
||||||
CanSubmitResult result = canSubmit(patchSetId);
|
CanSubmitResult result = canSubmit(patchSetId);
|
||||||
if (result != CanSubmitResult.OK) {
|
if (result != CanSubmitResult.OK) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
final List<PatchSetApproval> all =
|
PrologEnvironment env = getProjectControl().getProjectState().newPrologEnvironment();
|
||||||
db.patchSetApprovals().byPatchSet(patchSetId).toList();
|
env.set(StoredValues.REVIEW_DB, db);
|
||||||
|
env.set(StoredValues.CHANGE, change);
|
||||||
|
env.set(StoredValues.PATCH_SET_ID, patchSetId);
|
||||||
|
env.set(StoredValues.CHANGE_CONTROL, this);
|
||||||
|
|
||||||
final FunctionState fs =
|
StructureTerm submitRule = SymbolTerm.makeSymbol(
|
||||||
functionStateFactory.create(change, patchSetId, all);
|
"com.google.gerrit.rules.common", "default_submit", 1);
|
||||||
|
|
||||||
for (ApprovalType c : approvalTypes.getApprovalTypes()) {
|
List<Term> results = new ArrayList<Term>();
|
||||||
CategoryFunction.forCategory(c.getCategory()).run(c, fs);
|
try {
|
||||||
|
for (Term[] template : env.all(
|
||||||
|
"com.google.gerrit.rules.common", "can_submit",
|
||||||
|
submitRule,
|
||||||
|
new VariableTerm())) {
|
||||||
|
results.add(template[1]);
|
||||||
|
}
|
||||||
|
} catch (PrologException err) {
|
||||||
|
log.error("PrologException calling "+submitRule, err);
|
||||||
|
return new CanSubmitResult("Error in submit rule");
|
||||||
}
|
}
|
||||||
|
|
||||||
for (ApprovalType type : approvalTypes.getApprovalTypes()) {
|
if (results.isEmpty()) {
|
||||||
if (!fs.isValid(type)) {
|
// This should never occur. A well written submit rule will always produce
|
||||||
return new CanSubmitResult("Requires " + type.getCategory().getName());
|
// at least one result informing the caller of the labels that are
|
||||||
|
// required for this change to be submittable. Each label will indicate
|
||||||
|
// whether or not that is actually possible given the permissions.
|
||||||
|
log.error("Submit rule has no solution: " + submitRule);
|
||||||
|
return new CanSubmitResult("Error in submit rule (no solution possible)");
|
||||||
|
}
|
||||||
|
|
||||||
|
// The last result produced will be an "ok(P)" format if submit is possible.
|
||||||
|
// This is always true because can_submit (called above) will cut away all
|
||||||
|
// choice points once a solution is found.
|
||||||
|
Term last = results.get(results.size() - 1);
|
||||||
|
if (last.isStructure() && 1 == last.arity() && "ok".equals(last.name())) {
|
||||||
|
Term solution = last.arg(0);
|
||||||
|
return CanSubmitResult.OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For now only process the first result. Later we can examine all of the
|
||||||
|
// results and proposes different alternative paths to a submit solution.
|
||||||
|
Term first = results.get(0);
|
||||||
|
if (!first.isStructure() || 1 != first.arity() || !"not_ready".equals(first.name())) {
|
||||||
|
log.error("Unexpected result from can_submit: " + first);
|
||||||
|
return new CanSubmitResult("Error in submit rule");
|
||||||
|
}
|
||||||
|
|
||||||
|
Term submitRecord = first.arg(0);
|
||||||
|
if (!submitRecord.isStructure()) {
|
||||||
|
log.error("Invalid result from submit rule " + submitRule + ": " + submitRecord);
|
||||||
|
return new CanSubmitResult("Error in submit rule");
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Term state : ((StructureTerm) submitRecord).args()) {
|
||||||
|
if (!state.isStructure() || 2 != state.arity() || !"label".equals(state.name())) {
|
||||||
|
log.error("Invalid result from submit rule " + submitRule + ": " + submitRecord);
|
||||||
|
return new CanSubmitResult("Invalid submit rule result");
|
||||||
|
}
|
||||||
|
|
||||||
|
String label = state.arg(0).name();
|
||||||
|
Term status = state.arg(1);
|
||||||
|
|
||||||
|
if ("ok".equals(status.name())) {
|
||||||
|
continue;
|
||||||
|
|
||||||
|
} else if ("reject".equals(status.name())) {
|
||||||
|
return new CanSubmitResult("Submit blocked by "+ label);
|
||||||
|
|
||||||
|
} else if ("need".equals(status.name())) {
|
||||||
|
if (status.isStructure() && status.arg(0).isInteger()) {
|
||||||
|
IntegerTerm val = (IntegerTerm) status.arg(0);
|
||||||
|
if (1 < val.intValue()) {
|
||||||
|
label += "+" + val.intValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new CanSubmitResult("Requires " + label);
|
||||||
|
|
||||||
|
} else if ("impossble".equals(status.name())) {
|
||||||
|
return new CanSubmitResult("Requires " + label + " (check permissions)");
|
||||||
|
|
||||||
|
} else {
|
||||||
|
return new CanSubmitResult("Invalid submit rule result");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import com.google.gerrit.common.data.Permission;
|
|||||||
import com.google.gerrit.common.data.PermissionRule;
|
import com.google.gerrit.common.data.PermissionRule;
|
||||||
import com.google.gerrit.reviewdb.AccountGroup;
|
import com.google.gerrit.reviewdb.AccountGroup;
|
||||||
import com.google.gerrit.reviewdb.Project;
|
import com.google.gerrit.reviewdb.Project;
|
||||||
|
import com.google.gerrit.rules.PrologEnvironment;
|
||||||
import com.google.gerrit.server.AnonymousUser;
|
import com.google.gerrit.server.AnonymousUser;
|
||||||
import com.google.gerrit.server.CurrentUser;
|
import com.google.gerrit.server.CurrentUser;
|
||||||
import com.google.gerrit.server.config.WildProjectName;
|
import com.google.gerrit.server.config.WildProjectName;
|
||||||
@@ -49,6 +50,7 @@ public class ProjectState {
|
|||||||
private final Project.NameKey wildProject;
|
private final Project.NameKey wildProject;
|
||||||
private final ProjectCache projectCache;
|
private final ProjectCache projectCache;
|
||||||
private final ProjectControl.AssistedFactory projectControlFactory;
|
private final ProjectControl.AssistedFactory projectControlFactory;
|
||||||
|
private final PrologEnvironment.Factory envFactory;
|
||||||
private final GitRepositoryManager gitMgr;
|
private final GitRepositoryManager gitMgr;
|
||||||
|
|
||||||
private final ProjectConfig config;
|
private final ProjectConfig config;
|
||||||
@@ -62,12 +64,14 @@ public class ProjectState {
|
|||||||
final ProjectCache projectCache,
|
final ProjectCache projectCache,
|
||||||
@WildProjectName final Project.NameKey wildProject,
|
@WildProjectName final Project.NameKey wildProject,
|
||||||
final ProjectControl.AssistedFactory projectControlFactory,
|
final ProjectControl.AssistedFactory projectControlFactory,
|
||||||
|
final PrologEnvironment.Factory envFactory,
|
||||||
final GitRepositoryManager gitMgr,
|
final GitRepositoryManager gitMgr,
|
||||||
@Assisted final ProjectConfig config) {
|
@Assisted final ProjectConfig config) {
|
||||||
this.anonymousUser = anonymousUser;
|
this.anonymousUser = anonymousUser;
|
||||||
this.projectCache = projectCache;
|
this.projectCache = projectCache;
|
||||||
this.wildProject = wildProject;
|
this.wildProject = wildProject;
|
||||||
this.projectControlFactory = projectControlFactory;
|
this.projectControlFactory = projectControlFactory;
|
||||||
|
this.envFactory = envFactory;
|
||||||
this.gitMgr = gitMgr;
|
this.gitMgr = gitMgr;
|
||||||
this.config = config;
|
this.config = config;
|
||||||
this.lastCheckTime = System.currentTimeMillis();
|
this.lastCheckTime = System.currentTimeMillis();
|
||||||
@@ -116,6 +120,12 @@ public class ProjectState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @return Construct a new PrologEnvironment for the calling thread. */
|
||||||
|
public PrologEnvironment newPrologEnvironment() {
|
||||||
|
// TODO Replace this with a per-project ClassLoader to isolate rules.
|
||||||
|
return envFactory.create(getClass().getClassLoader());
|
||||||
|
}
|
||||||
|
|
||||||
public Project getProject() {
|
public Project getProject() {
|
||||||
return getConfig().getProject();
|
return getConfig().getProject();
|
||||||
}
|
}
|
||||||
|
|||||||
261
gerrit-server/src/main/prolog/common_rules.pl
Normal file
261
gerrit-server/src/main/prolog/common_rules.pl
Normal file
@@ -0,0 +1,261 @@
|
|||||||
|
%% Copyright (C) 2011 The Android Open Source Project
|
||||||
|
%%
|
||||||
|
%% Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
%% you may not use this file except in compliance with the License.
|
||||||
|
%% You may obtain a copy of the License at
|
||||||
|
%%
|
||||||
|
%% http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
%%
|
||||||
|
%% Unless required by applicable law or agreed to in writing, software
|
||||||
|
%% distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
%% See the License for the specific language governing permissions and
|
||||||
|
%% limitations under the License.
|
||||||
|
|
||||||
|
:- op(1150, fx, (package)).
|
||||||
|
:- op(1150, fx, (public)).
|
||||||
|
|
||||||
|
:- package 'com.google.gerrit.rules.common'.
|
||||||
|
'$init' :- init.
|
||||||
|
|
||||||
|
|
||||||
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||||
|
%%
|
||||||
|
%% init:
|
||||||
|
%%
|
||||||
|
%% Initialize the module's private state. These typically take the form of global
|
||||||
|
%% aliased hashes carrying "constant" data about the current change for any
|
||||||
|
%% predicate that needs to obtain it.
|
||||||
|
%%
|
||||||
|
init :-
|
||||||
|
define_hash(commit_labels),
|
||||||
|
define_hash(current_user).
|
||||||
|
|
||||||
|
define_hash(A) :- hash_exists(A), !, hash_clear(A).
|
||||||
|
define_hash(A) :- atom(A), !, new_hash(_, [alias(A)]).
|
||||||
|
|
||||||
|
|
||||||
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||||
|
%%
|
||||||
|
%% commit_label/2:
|
||||||
|
%%
|
||||||
|
%% During rule evaluation of a change, this predicate is defined to
|
||||||
|
%% be a table of labels that pertain to the commit of interest.
|
||||||
|
%%
|
||||||
|
%% commit_label( label('Code-Review', 2), user(12345789) ).
|
||||||
|
%% commit_label( label('Verified', -1), user(8181) ).
|
||||||
|
%%
|
||||||
|
:- public commit_label/2.
|
||||||
|
%%
|
||||||
|
commit_label(L, User) :- L = label(H, _),
|
||||||
|
atom(H),
|
||||||
|
!,
|
||||||
|
hash_get(commit_labels, H, Cached),
|
||||||
|
( [] == Cached ->
|
||||||
|
get_commit_labels(_),
|
||||||
|
hash_get(commit_labels, H, Rs), !
|
||||||
|
;
|
||||||
|
Rs = Cached
|
||||||
|
),
|
||||||
|
scan_commit_labels(Rs, L, User)
|
||||||
|
.
|
||||||
|
commit_label(Label, User) :-
|
||||||
|
get_commit_labels(Rs),
|
||||||
|
scan_commit_labels(Rs, Label, User).
|
||||||
|
|
||||||
|
scan_commit_labels([R | Rs], L, U) :- R = commit_label(L, U).
|
||||||
|
scan_commit_labels([_ | Rs], L, U) :- scan_commit_labels(Rs, L, U).
|
||||||
|
scan_commit_labels([], _, _) :- fail.
|
||||||
|
|
||||||
|
get_commit_labels(Rs) :-
|
||||||
|
hash_contains_key(commit_labels, '$all'),
|
||||||
|
!,
|
||||||
|
hash_get(commit_labels, '$all', Rs)
|
||||||
|
.
|
||||||
|
get_commit_labels(Rs) :-
|
||||||
|
'$load_commit_labels'(Rs),
|
||||||
|
set_commit_labels(Rs).
|
||||||
|
|
||||||
|
set_commit_labels(Rs) :-
|
||||||
|
define_hash(commit_labels),
|
||||||
|
hash_put(commit_labels, '$all', Rs),
|
||||||
|
index_commit_labels(Rs).
|
||||||
|
|
||||||
|
index_commit_labels([]).
|
||||||
|
index_commit_labels([R | Rs]) :-
|
||||||
|
R = commit_label(label(H, _), _),
|
||||||
|
atom(H),
|
||||||
|
!,
|
||||||
|
hash_get(commit_labels, H, Tmp),
|
||||||
|
hash_put(commit_labels, H, [R | Tmp]),
|
||||||
|
index_commit_labels(Rs)
|
||||||
|
.
|
||||||
|
index_commit_labels([_ | Rs]) :-
|
||||||
|
index_commit_labels(Rs).
|
||||||
|
|
||||||
|
|
||||||
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||||
|
%%
|
||||||
|
%% current_user/2:
|
||||||
|
%%
|
||||||
|
%% Locate the CurrentUser object caching group memberships, account data.
|
||||||
|
%%
|
||||||
|
current_user(user(AccountId), User) :-
|
||||||
|
hash_get(current_user, AccountId, User),
|
||||||
|
User \== [],
|
||||||
|
!
|
||||||
|
.
|
||||||
|
current_user(user(AccountId), User) :-
|
||||||
|
integer(AccountId),
|
||||||
|
'$current_user'(AccountId, User),
|
||||||
|
hash_put(current_user, AccountId, User).
|
||||||
|
|
||||||
|
|
||||||
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||||
|
%%
|
||||||
|
%% user_label_range/4:
|
||||||
|
%%
|
||||||
|
%% Lookup the range allowed to be used.
|
||||||
|
%%
|
||||||
|
user_label_range(Label, test_user(Name), Min, Max) :-
|
||||||
|
% TODO Replace this hack clause when RefControl is rewritten.
|
||||||
|
!,
|
||||||
|
clause(user:test_grant(Label, test_user(Name), range(Min, Max)), _)
|
||||||
|
.
|
||||||
|
user_label_range(Label, Who, Min, Max) :-
|
||||||
|
atom(Label),
|
||||||
|
current_user(Who, User),
|
||||||
|
'$user_label_range'(Label, User, Min, Max).
|
||||||
|
|
||||||
|
|
||||||
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||||
|
%%
|
||||||
|
%% not_same/2:
|
||||||
|
%%
|
||||||
|
:- public not_same/2.
|
||||||
|
%%
|
||||||
|
not_same(ok(A), ok(B)) :- !, A \= B.
|
||||||
|
not_same(label(_, ok(A)), label(_, ok(B))) :- !, A \= B.
|
||||||
|
not_same(_, _).
|
||||||
|
|
||||||
|
|
||||||
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||||
|
%%
|
||||||
|
%% can_submit/2:
|
||||||
|
%%
|
||||||
|
%% Execute the SubmitRule for each solution until one where all of the
|
||||||
|
%% states has the format label(_, ok(_)) is found, then cut away any
|
||||||
|
%% remaining choice points leaving this as the last solution.
|
||||||
|
%%
|
||||||
|
:- public can_submit/2.
|
||||||
|
%%
|
||||||
|
can_submit(SubmitRule, S) :-
|
||||||
|
call_submit_rule(SubmitRule, Tmp),
|
||||||
|
Tmp =.. [submit | Ls],
|
||||||
|
( is_all_ok(Ls) -> S = ok(Tmp), ! ; S = not_ready(Tmp) ).
|
||||||
|
|
||||||
|
call_submit_rule(P:X, Arg) :- !, F =.. [X, Arg], P:F.
|
||||||
|
call_submit_rule(X, Arg) :- !, F =.. [X, Arg], F.
|
||||||
|
|
||||||
|
is_all_ok([]).
|
||||||
|
is_all_ok([label(_, ok(__)) | Ls]) :- is_all_ok(Ls).
|
||||||
|
is_all_ok(_) :- fail.
|
||||||
|
|
||||||
|
|
||||||
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||||
|
%%
|
||||||
|
%% default_submit/1:
|
||||||
|
%%
|
||||||
|
:- public default_submit/1.
|
||||||
|
%%
|
||||||
|
default_submit(P) :-
|
||||||
|
get_legacy_approval_types(ApprovalTypes),
|
||||||
|
default_submit(ApprovalTypes, P).
|
||||||
|
|
||||||
|
% Apply the old "all approval categories must be satisfied"
|
||||||
|
% loop by scanning over all of the approval types to build
|
||||||
|
% up the submit record.
|
||||||
|
%
|
||||||
|
default_submit(ApprovalTypes, P) :-
|
||||||
|
default_submit(ApprovalTypes, [], Tmp),
|
||||||
|
reverse(Tmp, Ls),
|
||||||
|
P =.. [ submit | Ls].
|
||||||
|
|
||||||
|
default_submit([], Out, Out).
|
||||||
|
default_submit([Type | Types], Tmp, Out) :-
|
||||||
|
approval_type(Label, Id, Fun, Min, Max) = Type,
|
||||||
|
legacy_submit_rule(Fun, Label, Id, Min, Max, Status),
|
||||||
|
R = label(Label, Status),
|
||||||
|
default_submit(Types, [R | Tmp], Out).
|
||||||
|
|
||||||
|
|
||||||
|
%% legacy_submit_rule:
|
||||||
|
%%
|
||||||
|
%% Apply the old -2..+2 style logic.
|
||||||
|
%%
|
||||||
|
legacy_submit_rule('MaxWithBlock', Label, Id, Min, Max, T) :- !, max_with_block(Label, Min, Max, T).
|
||||||
|
legacy_submit_rule('MaxNoBlock', Label, Id, Min, Max, T) :- !, max_no_block(Label, Max, T).
|
||||||
|
legacy_submit_rule('NoBlock', Label, Id, Min, Max, T) :- true.
|
||||||
|
legacy_submit_rule('NoOp', Label, Id, Min, Max, T) :- true.
|
||||||
|
legacy_submit_rule(Fun, Label, Id, Min, Max, T) :- T = impossible(unsupported(Fun)).
|
||||||
|
|
||||||
|
|
||||||
|
%% max_with_block:
|
||||||
|
%%
|
||||||
|
%% - The minimum is never used.
|
||||||
|
%% - At least one maximum is used.
|
||||||
|
%%
|
||||||
|
max_with_block(Label, Min, Max, reject(Who)) :-
|
||||||
|
check_label_range_permission(Label, Min, ok(Who)),
|
||||||
|
!
|
||||||
|
.
|
||||||
|
max_with_block(Label, Min, Max, ok(Who)) :-
|
||||||
|
\+ check_label_range_permission(Label, Min, ok(_)),
|
||||||
|
check_label_range_permission(Label, Max, ok(Who)),
|
||||||
|
!
|
||||||
|
.
|
||||||
|
max_with_block(Label, Min, Max, need(Max)) :-
|
||||||
|
true
|
||||||
|
.
|
||||||
|
%TODO Uncomment this clause when group suggesting is possible.
|
||||||
|
%max_with_block(Label, Min, Max, need(Max, Group)) :-
|
||||||
|
% \+ check_label_range_permission(Label, Max, ok(_)),
|
||||||
|
% check_label_range_permission(Label, Max, ask(Group))
|
||||||
|
% .
|
||||||
|
%max_with_block(Label, Min, Max, impossible(no_access)) :-
|
||||||
|
% \+ check_label_range_permission(Label, Max, ask(Group))
|
||||||
|
% .
|
||||||
|
|
||||||
|
|
||||||
|
%% max_no_block:
|
||||||
|
%%
|
||||||
|
%% - At least one maximum is used.
|
||||||
|
%%
|
||||||
|
max_no_block(Label, Max, ok(Who)) :-
|
||||||
|
check_label_range_permission(Label, Max, ok(Who)),
|
||||||
|
!
|
||||||
|
.
|
||||||
|
max_no_block(Label, Max, need(Max)) :-
|
||||||
|
true
|
||||||
|
.
|
||||||
|
%TODO Uncomment this clause when group suggesting is possible.
|
||||||
|
%max_no_block(Label, Max, need(Max, Group)) :-
|
||||||
|
% check_label_range_permission(Label, Max, ask(Group))
|
||||||
|
% .
|
||||||
|
%max_no_block(Label, Max, impossible(no_access)) :-
|
||||||
|
% \+ check_label_range_permission(Label, Max, ask(Group))
|
||||||
|
% .
|
||||||
|
|
||||||
|
|
||||||
|
%% check_label_range_permission:
|
||||||
|
%%
|
||||||
|
check_label_range_permission(Label, ExpValue, ok(Who)) :-
|
||||||
|
commit_label(label(Label, ExpValue), Who),
|
||||||
|
user_label_range(Label, Who, Min, Max),
|
||||||
|
Min @=< ExpValue, ExpValue @=< Max
|
||||||
|
.
|
||||||
|
%TODO Uncomment this clause when group suggesting is possible.
|
||||||
|
%check_label_range_permission(Label, ExpValue, ask(Group)) :-
|
||||||
|
% grant_range(Label, Group, Min, Max),
|
||||||
|
% Min @=< ExpValue, ExpValue @=< Max
|
||||||
|
% .
|
||||||
@@ -0,0 +1,161 @@
|
|||||||
|
// Copyright (C) 2011 The Android Open Source Project
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package com.google.gerrit.rules;
|
||||||
|
|
||||||
|
import com.google.inject.Guice;
|
||||||
|
import com.google.inject.Module;
|
||||||
|
|
||||||
|
import com.googlecode.prolog_cafe.compiler.CompileException;
|
||||||
|
import com.googlecode.prolog_cafe.lang.Prolog;
|
||||||
|
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;
|
||||||
|
|
||||||
|
import junit.framework.TestCase;
|
||||||
|
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
|
||||||
|
/** Base class for any tests written in Prolog. */
|
||||||
|
public abstract class PrologTestCase extends TestCase {
|
||||||
|
private static final SymbolTerm test_1 = SymbolTerm.makeSymbol("test", 1);
|
||||||
|
|
||||||
|
private String pkg;
|
||||||
|
private boolean hasSetup;
|
||||||
|
private boolean hasTeardown;
|
||||||
|
private List<Term> tests;
|
||||||
|
protected PrologEnvironment env;
|
||||||
|
|
||||||
|
protected void load(String prologResource, Module... modules)
|
||||||
|
throws FileNotFoundException, CompileException {
|
||||||
|
ArrayList<Module> moduleList = new ArrayList<Module>();
|
||||||
|
moduleList.add(new PrologModule());
|
||||||
|
moduleList.addAll(Arrays.asList(modules));
|
||||||
|
|
||||||
|
PrologEnvironment.Factory factory =
|
||||||
|
Guice.createInjector(moduleList).getInstance(
|
||||||
|
PrologEnvironment.Factory.class);
|
||||||
|
env = factory.create(getClass().getClassLoader());
|
||||||
|
env.setMaxDatabaseSize(16 * 1024);
|
||||||
|
|
||||||
|
consult(getClass(), prologResource);
|
||||||
|
|
||||||
|
pkg = myPackage();
|
||||||
|
hasSetup = has("setup");
|
||||||
|
hasTeardown = has("teardown");
|
||||||
|
|
||||||
|
StructureTerm head = new StructureTerm(":",
|
||||||
|
SymbolTerm.makeSymbol(pkg),
|
||||||
|
new StructureTerm(test_1, new VariableTerm()));
|
||||||
|
|
||||||
|
tests = new ArrayList<Term>();
|
||||||
|
for (Term[] pair : env.all(Prolog.BUILTIN, "clause", head, new VariableTerm())) {
|
||||||
|
tests.add(pair[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void consult(Class<?> clazz, String prologResource)
|
||||||
|
throws FileNotFoundException, CompileException {
|
||||||
|
URL url = clazz.getResource(prologResource);
|
||||||
|
if (url == null) {
|
||||||
|
throw new FileNotFoundException(prologResource);
|
||||||
|
}
|
||||||
|
|
||||||
|
String path = url.getPath();
|
||||||
|
if (!env.execute(Prolog.BUILTIN, "consult", SymbolTerm.makeSymbol(path))) {
|
||||||
|
throw new CompileException("Cannot consult" + path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String myPackage() {
|
||||||
|
String pkg = getClass().getName();
|
||||||
|
return pkg.substring(0, pkg.lastIndexOf('.'));
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean has(String name) {
|
||||||
|
StructureTerm head = SymbolTerm.makeSymbol(pkg, name, 0);
|
||||||
|
return env.execute(Prolog.BUILTIN, "clause", head, new VariableTerm());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testRunPrologTestCases() {
|
||||||
|
int errors = 0;
|
||||||
|
long start = System.currentTimeMillis();
|
||||||
|
|
||||||
|
for (Term test : tests) {
|
||||||
|
System.out.format("Prolog %-60s ...", removePackage(test));
|
||||||
|
System.out.flush();
|
||||||
|
|
||||||
|
if (hasSetup) {
|
||||||
|
call("setup");
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Term> all = env.all(Prolog.BUILTIN, "call", test);
|
||||||
|
|
||||||
|
if (hasTeardown) {
|
||||||
|
call("teardown");
|
||||||
|
}
|
||||||
|
|
||||||
|
System.out.println(all.size() == 1 ? "OK" : "FAIL");
|
||||||
|
|
||||||
|
if (all.size() > 0 && !test.equals(all.get(0))) {
|
||||||
|
for (Term t : all) {
|
||||||
|
Term head = ((StructureTerm) removePackage(t)).args()[0];
|
||||||
|
Term[] args = ((StructureTerm) head).args();
|
||||||
|
System.out.print(" Result: ");
|
||||||
|
for (int i = 0; i < args.length; i++) {
|
||||||
|
if (0 < i) {
|
||||||
|
System.out.print(", ");
|
||||||
|
}
|
||||||
|
System.out.print(args[i]);
|
||||||
|
}
|
||||||
|
System.out.println();
|
||||||
|
}
|
||||||
|
System.out.println();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (all.size() != 1) {
|
||||||
|
errors++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
long end = System.currentTimeMillis();
|
||||||
|
System.out.println("-------------------------------");
|
||||||
|
System.out.format("Prolog tests: %d, Failures: %d, Time elapsed %.3f sec",
|
||||||
|
tests.size(), errors, (end - start) / 1000.0);
|
||||||
|
System.out.println();
|
||||||
|
|
||||||
|
assertEquals("No Errors", 0, errors);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void call(String name) {
|
||||||
|
StructureTerm head = SymbolTerm.makeSymbol(pkg, name, 0);
|
||||||
|
if (!env.execute(Prolog.BUILTIN, "call", head)) {
|
||||||
|
fail("Cannot invoke " + pkg + ":" + name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Term removePackage(Term test) {
|
||||||
|
Term name = test;
|
||||||
|
if (name.isStructure() && ":".equals(((StructureTerm) name).name())) {
|
||||||
|
name = ((StructureTerm) name).args()[1];
|
||||||
|
}
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
// Copyright (C) 2011 The Android Open Source Project
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package com.google.gerrit.rules.common;
|
||||||
|
|
||||||
|
import com.google.gerrit.common.data.ApprovalType;
|
||||||
|
import com.google.gerrit.common.data.ApprovalTypes;
|
||||||
|
import com.google.gerrit.reviewdb.ApprovalCategory;
|
||||||
|
import com.google.gerrit.reviewdb.ApprovalCategoryValue;
|
||||||
|
import com.google.gerrit.rules.PrologTestCase;
|
||||||
|
import com.google.inject.AbstractModule;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class CommonRulesTest extends PrologTestCase {
|
||||||
|
@Override
|
||||||
|
public void setUp() throws Exception {
|
||||||
|
super.setUp();
|
||||||
|
|
||||||
|
final ApprovalTypes types = new ApprovalTypes(Arrays.asList(
|
||||||
|
codeReviewCategory(),
|
||||||
|
verifiedCategory()
|
||||||
|
));
|
||||||
|
|
||||||
|
load("common_rules_test.pl", new AbstractModule() {
|
||||||
|
@Override
|
||||||
|
protected void configure() {
|
||||||
|
bind(ApprovalTypes.class).toInstance(types);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ApprovalType codeReviewCategory() {
|
||||||
|
ApprovalCategory cat = category(0, "CRVW", "Code Review");
|
||||||
|
List<ApprovalCategoryValue> vals = newList();
|
||||||
|
vals.add(value(cat, 2, "Looks good to me, approved"));
|
||||||
|
vals.add(value(cat, 1, "Looks good to me, but someone else must approve"));
|
||||||
|
vals.add(value(cat, 0, "No score"));
|
||||||
|
vals.add(value(cat, -1, "I would prefer that you didn't submit this"));
|
||||||
|
vals.add(value(cat, -2, "Do not submit"));
|
||||||
|
return new ApprovalType(cat, vals);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ApprovalType verifiedCategory() {
|
||||||
|
ApprovalCategory cat = category(1, "VRIF", "Verified");
|
||||||
|
List<ApprovalCategoryValue> vals = newList();
|
||||||
|
vals.add(value(cat, 1, "Verified"));
|
||||||
|
vals.add(value(cat, 0, "No score"));
|
||||||
|
vals.add(value(cat, -1, "Fails"));
|
||||||
|
return new ApprovalType(cat, vals);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ApprovalCategory category(int pos, String id, String name) {
|
||||||
|
ApprovalCategory cat;
|
||||||
|
cat = new ApprovalCategory(new ApprovalCategory.Id(id), name);
|
||||||
|
cat.setPosition((short) pos);
|
||||||
|
return cat;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ArrayList<ApprovalCategoryValue> newList() {
|
||||||
|
return new ArrayList<ApprovalCategoryValue>();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ApprovalCategoryValue value(ApprovalCategory c, int v, String n) {
|
||||||
|
return new ApprovalCategoryValue(
|
||||||
|
new ApprovalCategoryValue.Id(c.getId(), (short) v),
|
||||||
|
n);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -26,6 +26,7 @@ import com.google.gerrit.reviewdb.AccountProjectWatch;
|
|||||||
import com.google.gerrit.reviewdb.Change;
|
import com.google.gerrit.reviewdb.Change;
|
||||||
import com.google.gerrit.reviewdb.Project;
|
import com.google.gerrit.reviewdb.Project;
|
||||||
import com.google.gerrit.reviewdb.SystemConfig;
|
import com.google.gerrit.reviewdb.SystemConfig;
|
||||||
|
import com.google.gerrit.rules.PrologEnvironment;
|
||||||
import com.google.gerrit.server.AccessPath;
|
import com.google.gerrit.server.AccessPath;
|
||||||
import com.google.gerrit.server.AnonymousUser;
|
import com.google.gerrit.server.AnonymousUser;
|
||||||
import com.google.gerrit.server.CurrentUser;
|
import com.google.gerrit.server.CurrentUser;
|
||||||
@@ -317,13 +318,16 @@ public class RefControlTest extends TestCase {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
PrologEnvironment.Factory envFactory = null;
|
||||||
GitRepositoryManager mgr = null;
|
GitRepositoryManager mgr = null;
|
||||||
Project.NameKey wildProject = new Project.NameKey("All-Projects");
|
Project.NameKey wildProject = new Project.NameKey("All-Projects");
|
||||||
ProjectControl.AssistedFactory projectControlFactory = null;
|
ProjectControl.AssistedFactory projectControlFactory = null;
|
||||||
all.put(local.getProject().getNameKey(), new ProjectState(anonymousUser,
|
all.put(local.getProject().getNameKey(), new ProjectState(anonymousUser,
|
||||||
projectCache, wildProject, projectControlFactory, mgr, local));
|
projectCache, wildProject, projectControlFactory,
|
||||||
|
envFactory, mgr, local));
|
||||||
all.put(parent.getProject().getNameKey(), new ProjectState(anonymousUser,
|
all.put(parent.getProject().getNameKey(), new ProjectState(anonymousUser,
|
||||||
projectCache, wildProject, projectControlFactory, mgr, parent));
|
projectCache, wildProject, projectControlFactory,
|
||||||
|
envFactory, mgr, parent));
|
||||||
return all.get(local.getProject().getNameKey());
|
return all.get(local.getProject().getNameKey());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,114 @@
|
|||||||
|
%% Copyright (C) 2011 The Android Open Source Project
|
||||||
|
%%
|
||||||
|
%% Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
%% you may not use this file except in compliance with the License.
|
||||||
|
%% You may obtain a copy of the License at
|
||||||
|
%%
|
||||||
|
%% http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
%%
|
||||||
|
%% Unless required by applicable law or agreed to in writing, software
|
||||||
|
%% distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
%% See the License for the specific language governing permissions and
|
||||||
|
%% limitations under the License.
|
||||||
|
|
||||||
|
:- package 'com.google.gerrit.rules.common'.
|
||||||
|
|
||||||
|
|
||||||
|
%% not_same
|
||||||
|
%%
|
||||||
|
test(not_same_success) :-
|
||||||
|
not_same(ok(a), ok(b)),
|
||||||
|
not_same(label(e, ok(a)), label(e, ok(b))).
|
||||||
|
|
||||||
|
|
||||||
|
%% get_legacy_approval_types
|
||||||
|
%%
|
||||||
|
test(get_legacy_approval_types) :-
|
||||||
|
get_legacy_approval_types(T),
|
||||||
|
T = [C, V],
|
||||||
|
C = approval_type('Code-Review', 'CRVW', 'MaxWithBlock', -2, 2),
|
||||||
|
V = approval_type('Verified', 'VRIF', 'MaxWithBlock', -1, 1).
|
||||||
|
|
||||||
|
|
||||||
|
%% commit_label
|
||||||
|
%%
|
||||||
|
test(commit_label_all) :-
|
||||||
|
findall(commit_label(L, U), commit_label(L, U), Out),
|
||||||
|
all_commit_labels(Ls),
|
||||||
|
Ls = Out.
|
||||||
|
|
||||||
|
test(commit_label_CodeReview) :-
|
||||||
|
L = label('Code-Review', _),
|
||||||
|
findall(p(L, U), commit_label(L, U), Out),
|
||||||
|
[ p(label('Code-Review', 2), test_user(bob)),
|
||||||
|
p(label('Code-Review', 2), test_user(alice)) ] == Out.
|
||||||
|
|
||||||
|
|
||||||
|
%% max_with_block
|
||||||
|
%%
|
||||||
|
test(max_with_block_success_accept_max_score) :-
|
||||||
|
max_with_block('Code-Review', -2, 2, ok(test_user(alice))).
|
||||||
|
|
||||||
|
test(max_with_block_success_reject_min_score) :-
|
||||||
|
max_with_block('You-Fail', -1, 1, reject(test_user(failer))).
|
||||||
|
|
||||||
|
test(max_with_block_success_need_suggest) :-
|
||||||
|
max_with_block('Verified', -1, 1, need(1)).
|
||||||
|
|
||||||
|
skip_test(max_with_block_success_impossible) :-
|
||||||
|
max_with_block('Code-Style', 0, 1, impossible(no_access)).
|
||||||
|
|
||||||
|
|
||||||
|
%% default_submit
|
||||||
|
%%
|
||||||
|
test(default_submit_fails) :-
|
||||||
|
findall(P, default_submit(P), All),
|
||||||
|
All = [submit(C, V)],
|
||||||
|
C = label('Code-Review', ok(test_user(alice))),
|
||||||
|
V = label('Verified', need(1)).
|
||||||
|
|
||||||
|
|
||||||
|
%% can_submit
|
||||||
|
%%
|
||||||
|
test(can_submit_ok) :-
|
||||||
|
set_commit_labels([
|
||||||
|
commit_label( label('Code-Review', 2), test_user(alice) ),
|
||||||
|
commit_label( label('Verified', 1), test_user(builder) )
|
||||||
|
]),
|
||||||
|
can_submit('com.google.gerrit.rules.common':default_submit, S),
|
||||||
|
S = ok(submit(C, V)),
|
||||||
|
C = label('Code-Review', ok(test_user(alice))),
|
||||||
|
V = label('Verified', ok(test_user(builder))).
|
||||||
|
|
||||||
|
test(can_submit_not_ready) :-
|
||||||
|
can_submit('com.google.gerrit.rules.common':default_submit, S),
|
||||||
|
S = not_ready(submit(C, V)),
|
||||||
|
C = label('Code-Review', ok(test_user(alice))),
|
||||||
|
V = label('Verified', need(1)).
|
||||||
|
|
||||||
|
|
||||||
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||||
|
%%
|
||||||
|
%% Supporting Data
|
||||||
|
%%
|
||||||
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||||
|
|
||||||
|
setup :-
|
||||||
|
init,
|
||||||
|
all_commit_labels(Ls),
|
||||||
|
set_commit_labels(Ls).
|
||||||
|
|
||||||
|
all_commit_labels(Ls) :-
|
||||||
|
Ls = [
|
||||||
|
commit_label( label('Code-Review', 2), test_user(alice) ),
|
||||||
|
commit_label( label('Code-Review', 2), test_user(bob) ),
|
||||||
|
commit_label( label('You-Fail', -1), test_user(failer) ),
|
||||||
|
commit_label( label('You-Fail', -1), test_user(alice) )
|
||||||
|
].
|
||||||
|
|
||||||
|
:- package user.
|
||||||
|
test_grant('Code-Review', test_user(alice), range(-2, 2)).
|
||||||
|
test_grant('Verified', test_user(builder), range(-1, 1)).
|
||||||
|
test_grant('You-Fail', test_user(alice), range(-1, 1)).
|
||||||
|
test_grant('You-Fail', test_user(failer), range(-1, 1)).
|
||||||
@@ -250,9 +250,7 @@ public class ReviewCommand extends BaseCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (submitChange) {
|
if (submitChange) {
|
||||||
CanSubmitResult result =
|
CanSubmitResult result = changeControl.canSubmit(db, patchSetId);
|
||||||
changeControl.canSubmit(patchSetId, db, approvalTypes,
|
|
||||||
functionStateFactory);
|
|
||||||
if (result == CanSubmitResult.OK) {
|
if (result == CanSubmitResult.OK) {
|
||||||
toSubmit.add(patchSetId);
|
toSubmit.add(patchSetId);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
2
pom.xml
2
pom.xml
@@ -367,7 +367,7 @@ limitations under the License.
|
|||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.codehaus.mojo</groupId>
|
<groupId>org.codehaus.mojo</groupId>
|
||||||
<artifactId>build-helper-maven-plugin</artifactId>
|
<artifactId>build-helper-maven-plugin</artifactId>
|
||||||
<version>1.6</version>
|
<version>1.5</version>
|
||||||
</plugin>
|
</plugin>
|
||||||
</plugins>
|
</plugins>
|
||||||
</pluginManagement>
|
</pluginManagement>
|
||||||
|
|||||||
Reference in New Issue
Block a user