Add pure_revert Prolog fact
This commit adds a pure_revert prolog fact, tests and documentation. Change-Id: I5eaf094ef84e704a9737361c7e72133ca4b3b5af
This commit is contained in:
parent
8e4c17c0d0
commit
42db934ffa
@ -58,6 +58,10 @@ of them we must use a qualified name like `gerrit:change_branch(X)`.
|
||||
|`current_user(user(peer_daemon)).`
|
||||
|`current_user(user(replication)).`
|
||||
|
||||
|`pure_revert/1` |`pure_revert(1).`
|
||||
|link:rest-api-changes.html#get-pure-revert[Pure revert] as integer atom (1 if
|
||||
the change is a pure revert, 0 otherwise)
|
||||
|
||||
|`uploader/1` |`uploader(user(1000000)).`
|
||||
|Uploader as `user(ID)` term. ID is the numeric account ID
|
||||
|
||||
|
@ -1048,6 +1048,56 @@ It's only used to show `'Needs All-Comments-Resolved'` in the UI to clearly
|
||||
indicate to the user that all the comments have to be resolved for the
|
||||
change to become submittable.
|
||||
|
||||
=== Example 17: Make change submittable if it is a pure revert
|
||||
In this example we will use the `pure_revert` fact about a
|
||||
change. Our goal is to block the submission of any change that is not a
|
||||
pure revert. Basically, it can be achieved by the following rules:
|
||||
|
||||
`rules.pl`
|
||||
[source,prolog]
|
||||
----
|
||||
submit_rule(submit(R)) :-
|
||||
gerrit:pure_revert(1),
|
||||
!,
|
||||
gerrit:commit_author(A),
|
||||
R = label('Is-Pure-Revert', ok(A)).
|
||||
|
||||
submit_rule(submit(R)) :-
|
||||
gerrit:pure_revert(U),
|
||||
U /= 1,
|
||||
R = label('Is-Pure-Revert', need(_)).
|
||||
----
|
||||
|
||||
Suppose currently a change is submittable if it gets `+2` for `Code-Review`
|
||||
and `+1` for `Verified`. It can be extended to support the above rules as
|
||||
follows:
|
||||
|
||||
`rules.pl`
|
||||
[source,prolog]
|
||||
----
|
||||
submit_rule(submit(CR, V, R)) :-
|
||||
base(CR, V),
|
||||
gerrit:pure_revert(1),
|
||||
!,
|
||||
gerrit:commit_author(A),
|
||||
R = label('Is-Pure-Revert', ok(A)).
|
||||
|
||||
submit_rule(submit(CR, V, R)) :-
|
||||
base(CR, V),
|
||||
gerrit:pure_revert(U),
|
||||
U /= 1,
|
||||
R = label('Is-Pure-Revert', need(_)).
|
||||
|
||||
base(CR, V) :-
|
||||
gerrit:max_with_block(-2, 2, 'Code-Review', CR),
|
||||
gerrit:max_with_block(-1, 1, 'Verified', V).
|
||||
----
|
||||
|
||||
Note that a new label as `Is-Pure-Revert` should not be configured.
|
||||
It's only used to show `'Needs Is-Pure-Revert'` in the UI to clearly
|
||||
indicate to the user that all the comments have to be resolved for the
|
||||
change to become submittable.
|
||||
|
||||
== Examples - Submit Type
|
||||
The following examples show how to implement own submit type rules.
|
||||
|
||||
|
@ -63,7 +63,6 @@ import com.google.gerrit.acceptance.GerritConfig;
|
||||
import com.google.gerrit.acceptance.GitUtil;
|
||||
import com.google.gerrit.acceptance.NoHttpd;
|
||||
import com.google.gerrit.acceptance.PushOneCommit;
|
||||
import com.google.gerrit.acceptance.Sandboxed;
|
||||
import com.google.gerrit.acceptance.TestAccount;
|
||||
import com.google.gerrit.acceptance.TestProjectInput;
|
||||
import com.google.gerrit.common.FooterConstants;
|
||||
@ -2963,33 +2962,20 @@ public class ChangeIT extends AbstractDaemonTest {
|
||||
assertThat(approval.permittedVotingRange).isNull();
|
||||
}
|
||||
|
||||
@Sandboxed
|
||||
@Test
|
||||
public void unresolvedCommentsBlocked() throws Exception {
|
||||
RevCommit oldHead = getRemoteHead();
|
||||
GitUtil.fetch(testRepo, RefNames.REFS_CONFIG + ":config");
|
||||
testRepo.reset("config");
|
||||
PushOneCommit push =
|
||||
pushFactory.create(
|
||||
db,
|
||||
admin.getIdent(),
|
||||
testRepo,
|
||||
"Configure",
|
||||
"rules.pl",
|
||||
"submit_rule(submit(R)) :- \n"
|
||||
+ "gerrit:unresolved_comments_count(0), \n"
|
||||
+ "!,"
|
||||
+ "gerrit:commit_author(A), \n"
|
||||
+ "R = label('All-Comments-Resolved', ok(A)).\n"
|
||||
+ "submit_rule(submit(R)) :- \n"
|
||||
+ "gerrit:unresolved_comments_count(U), \n"
|
||||
+ "U > 0,"
|
||||
+ "R = label('All-Comments-Resolved', need(_)). \n\n");
|
||||
modifySubmitRules(
|
||||
"submit_rule(submit(R)) :- \n"
|
||||
+ "gerrit:unresolved_comments_count(0), \n"
|
||||
+ "!,"
|
||||
+ "gerrit:commit_author(A), \n"
|
||||
+ "R = label('All-Comments-Resolved', ok(A)).\n"
|
||||
+ "submit_rule(submit(R)) :- \n"
|
||||
+ "gerrit:unresolved_comments_count(U), \n"
|
||||
+ "U > 0,"
|
||||
+ "R = label('All-Comments-Resolved', need(_)). \n\n");
|
||||
|
||||
push.to(RefNames.REFS_CONFIG);
|
||||
testRepo.reset(oldHead);
|
||||
|
||||
oldHead = getRemoteHead();
|
||||
String oldHead = getRemoteHead().name();
|
||||
PushOneCommit.Result result1 =
|
||||
pushFactory.create(db, user.getIdent(), testRepo).to("refs/for/master");
|
||||
testRepo.reset(oldHead);
|
||||
@ -3002,12 +2988,60 @@ public class ChangeIT extends AbstractDaemonTest {
|
||||
gApi.changes().id(result1.getChangeId()).current().submit();
|
||||
|
||||
exception.expect(ResourceConflictException.class);
|
||||
exception.expectMessage(
|
||||
"Failed to submit 1 change due to the following problems:\n"
|
||||
+ "Change 2: needs All-Comments-Resolved");
|
||||
exception.expectMessage("Failed to submit 1 change due to the following problems");
|
||||
exception.expectMessage("needs All-Comments-Resolved");
|
||||
gApi.changes().id(result2.getChangeId()).current().submit();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void pureRevertFactBlocksSubmissionOfNonReverts() throws Exception {
|
||||
addPureRevertSubmitRule();
|
||||
|
||||
// Create a change that is not a revert of another change
|
||||
PushOneCommit.Result r1 =
|
||||
pushFactory.create(db, user.getIdent(), testRepo).to("refs/for/master");
|
||||
approve(r1.getChangeId());
|
||||
|
||||
exception.expect(ResourceConflictException.class);
|
||||
exception.expectMessage("Failed to submit 1 change due to the following problems");
|
||||
exception.expectMessage("needs Is-Pure-Revert");
|
||||
gApi.changes().id(r1.getChangeId()).current().submit();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void pureRevertFactBlocksSubmissionOfNonPureReverts() throws Exception {
|
||||
PushOneCommit.Result r1 =
|
||||
pushFactory.create(db, user.getIdent(), testRepo).to("refs/for/master");
|
||||
merge(r1);
|
||||
|
||||
addPureRevertSubmitRule();
|
||||
|
||||
// Create a revert and push a content change
|
||||
String revertId = gApi.changes().id(r1.getChangeId()).revert().get().changeId;
|
||||
amendChange(revertId);
|
||||
approve(revertId);
|
||||
|
||||
exception.expect(ResourceConflictException.class);
|
||||
exception.expectMessage("Failed to submit 1 change due to the following problems");
|
||||
exception.expectMessage("needs Is-Pure-Revert");
|
||||
gApi.changes().id(revertId).current().submit();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void pureRevertFactAllowsSubmissionOfPureReverts() throws Exception {
|
||||
// Create a change that we can later revert
|
||||
PushOneCommit.Result r1 =
|
||||
pushFactory.create(db, user.getIdent(), testRepo).to("refs/for/master");
|
||||
merge(r1);
|
||||
|
||||
addPureRevertSubmitRule();
|
||||
|
||||
// Create a revert and submit it
|
||||
String revertId = gApi.changes().id(r1.getChangeId()).revert().get().changeId;
|
||||
approve(revertId);
|
||||
gApi.changes().id(revertId).current().submit();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void changeCommitMessage() throws Exception {
|
||||
// Tests mutating the commit message as both the owner of the change and a regular user with
|
||||
@ -3330,6 +3364,33 @@ public class ChangeIT extends AbstractDaemonTest {
|
||||
}
|
||||
}
|
||||
|
||||
private void addPureRevertSubmitRule() throws Exception {
|
||||
modifySubmitRules(
|
||||
"submit_rule(submit(R)) :- \n"
|
||||
+ "gerrit:pure_revert(1), \n"
|
||||
+ "!,"
|
||||
+ "gerrit:commit_author(A), \n"
|
||||
+ "R = label('Is-Pure-Revert', ok(A)).\n"
|
||||
+ "submit_rule(submit(R)) :- \n"
|
||||
+ "gerrit:pure_revert(U), \n"
|
||||
+ "U \\= 1,"
|
||||
+ "R = label('Is-Pure-Revert', need(_)). \n\n");
|
||||
}
|
||||
|
||||
private void modifySubmitRules(String newContent) throws Exception {
|
||||
try (Repository repo = repoManager.openRepository(project)) {
|
||||
TestRepository testRepo = new TestRepository<>((InMemoryRepository) repo);
|
||||
testRepo
|
||||
.branch(RefNames.REFS_CONFIG)
|
||||
.commit()
|
||||
.author(admin.getIdent())
|
||||
.committer(admin.getIdent())
|
||||
.add("rules.pl", newContent)
|
||||
.message("Modify rules.pl")
|
||||
.create();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@GerritConfig(name = "trackingid.jira-bug.footer", value = "Bug:")
|
||||
@GerritConfig(name = "trackingid.jira-bug.match", value = "JRA\\d{2,8}")
|
||||
|
@ -93,20 +93,30 @@ public class GetPureRevert implements RestReadView<ChangeResource> {
|
||||
.isPatchVisible(currentPatchSet, dbProvider.get())) {
|
||||
throw new AuthException("current revision not accessible");
|
||||
}
|
||||
return getPureRevert(rsrc.getNotes());
|
||||
}
|
||||
|
||||
public PureRevertInfo getPureRevert(ChangeNotes notes)
|
||||
throws OrmException, IOException, BadRequestException, AuthException,
|
||||
ResourceConflictException {
|
||||
PatchSet currentPatchSet = psUtil.current(dbProvider.get(), notes);
|
||||
if (currentPatchSet == null) {
|
||||
throw new ResourceConflictException("current revision is missing");
|
||||
}
|
||||
|
||||
if (claimedOriginal == null) {
|
||||
if (rsrc.getChange().getRevertOf() == null) {
|
||||
if (notes.getChange().getRevertOf() == null) {
|
||||
throw new BadRequestException("no ID was provided and change isn't a revert");
|
||||
}
|
||||
PatchSet ps =
|
||||
psUtil.current(
|
||||
dbProvider.get(),
|
||||
notesFactory.createChecked(
|
||||
dbProvider.get(), rsrc.getProject(), rsrc.getChange().getRevertOf()));
|
||||
dbProvider.get(), notes.getProjectName(), notes.getChange().getRevertOf()));
|
||||
claimedOriginal = ps.getRevision().get();
|
||||
}
|
||||
|
||||
try (Repository repo = repoManager.openRepository(rsrc.getProject());
|
||||
try (Repository repo = repoManager.openRepository(notes.getProjectName());
|
||||
ObjectInserter oi = repo.newObjectInserter();
|
||||
RevWalk rw = new RevWalk(repo)) {
|
||||
RevCommit claimedOriginalCommit;
|
||||
@ -126,7 +136,7 @@ public class GetPureRevert implements RestReadView<ChangeResource> {
|
||||
// Rebase claimed revert onto claimed original
|
||||
ThreeWayMerger merger =
|
||||
mergeUtilFactory
|
||||
.create(projectCache.checkedGet(rsrc.getProject()))
|
||||
.create(projectCache.checkedGet(notes.getProjectName()))
|
||||
.newThreeWayMerger(oi, repo.getConfig());
|
||||
merger.setBase(claimedRevertCommit.getParent(0));
|
||||
merger.merge(claimedRevertCommit, claimedOriginalCommit);
|
||||
|
@ -35,6 +35,9 @@ import com.google.gerrit.common.Nullable;
|
||||
import com.google.gerrit.common.data.LabelTypes;
|
||||
import com.google.gerrit.common.data.SubmitRecord;
|
||||
import com.google.gerrit.common.data.SubmitTypeRecord;
|
||||
import com.google.gerrit.extensions.restapi.AuthException;
|
||||
import com.google.gerrit.extensions.restapi.BadRequestException;
|
||||
import com.google.gerrit.extensions.restapi.ResourceConflictException;
|
||||
import com.google.gerrit.reviewdb.client.Account;
|
||||
import com.google.gerrit.reviewdb.client.Change;
|
||||
import com.google.gerrit.reviewdb.client.ChangeMessage;
|
||||
@ -61,6 +64,7 @@ import com.google.gerrit.server.account.AccountCache;
|
||||
import com.google.gerrit.server.account.Accounts;
|
||||
import com.google.gerrit.server.account.Emails;
|
||||
import com.google.gerrit.server.change.ChangeResource;
|
||||
import com.google.gerrit.server.change.GetPureRevert;
|
||||
import com.google.gerrit.server.change.MergeabilityCache;
|
||||
import com.google.gerrit.server.config.AllUsersName;
|
||||
import com.google.gerrit.server.config.TrackingFooters;
|
||||
@ -349,7 +353,7 @@ public class ChangeData {
|
||||
ChangeData cd =
|
||||
new ChangeData(
|
||||
null, null, null, null, null, null, null, null, null, null, null, null, null, null,
|
||||
null, null, null, null, null, null, project, id, null, null, null);
|
||||
null, null, null, null, null, null, null, project, id, null, null, null);
|
||||
cd.currentPatchSet = new PatchSet(new PatchSet.Id(id, currentPatchSetId));
|
||||
return cd;
|
||||
}
|
||||
@ -374,6 +378,7 @@ public class ChangeData {
|
||||
private final PatchSetUtil psUtil;
|
||||
private final ProjectCache projectCache;
|
||||
private final TrackingFooters trackingFooters;
|
||||
private final GetPureRevert pureRevert;
|
||||
|
||||
// Required assisted injected fields.
|
||||
private final ReviewDb db;
|
||||
@ -445,6 +450,7 @@ public class ChangeData {
|
||||
PatchSetUtil psUtil,
|
||||
ProjectCache projectCache,
|
||||
TrackingFooters trackingFooters,
|
||||
GetPureRevert pureRevert,
|
||||
@Assisted ReviewDb db,
|
||||
@Assisted Project.NameKey project,
|
||||
@Assisted Change.Id id,
|
||||
@ -470,6 +476,7 @@ public class ChangeData {
|
||||
this.projectCache = projectCache;
|
||||
this.starredChangesUtil = starredChangesUtil;
|
||||
this.trackingFooters = trackingFooters;
|
||||
this.pureRevert = pureRevert;
|
||||
|
||||
// May be null in tests when created via createForTest above, in which case lazy-loading will
|
||||
// intentionally fail with NPE. Still not marked @Nullable in the constructor, to force callers
|
||||
@ -1270,6 +1277,22 @@ public class ChangeData {
|
||||
return starsOf.stars();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {@code null} if {@code revertOf} is {@code null}; true if the change is a pure revert;
|
||||
* false otherwise.
|
||||
*/
|
||||
@Nullable
|
||||
public Boolean isPureRevert() throws OrmException {
|
||||
if (change().getRevertOf() == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return pureRevert.getPureRevert(notes()).isPureRevert;
|
||||
} catch (IOException | BadRequestException | AuthException | ResourceConflictException e) {
|
||||
throw new OrmException("could not compute pure revert", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
MoreObjects.ToStringHelper h = MoreObjects.toStringHelper(this);
|
||||
|
50
gerrit-server/src/main/java/gerrit/PRED_pure_revert_1.java
Normal file
50
gerrit-server/src/main/java/gerrit/PRED_pure_revert_1.java
Normal file
@ -0,0 +1,50 @@
|
||||
// Copyright (C) 2017 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 gerrit;
|
||||
|
||||
import com.google.gerrit.rules.StoredValues;
|
||||
import com.google.gwtorm.server.OrmException;
|
||||
import com.googlecode.prolog_cafe.exceptions.JavaException;
|
||||
import com.googlecode.prolog_cafe.exceptions.PrologException;
|
||||
import com.googlecode.prolog_cafe.lang.IntegerTerm;
|
||||
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.Term;
|
||||
|
||||
/** Checks if change is a pure revert of the change it references in 'revertOf'. */
|
||||
public class PRED_pure_revert_1 extends Predicate.P1 {
|
||||
public PRED_pure_revert_1(Term a1, Operation n) {
|
||||
arg1 = a1;
|
||||
cont = n;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Operation exec(Prolog engine) throws PrologException {
|
||||
engine.setB0();
|
||||
Term a1 = arg1.dereference();
|
||||
|
||||
Boolean isPureRevert;
|
||||
try {
|
||||
isPureRevert = StoredValues.CHANGE_DATA.get(engine).isPureRevert();
|
||||
} catch (OrmException e) {
|
||||
throw new JavaException(this, 1, e);
|
||||
}
|
||||
if (!a1.unify(new IntegerTerm(Boolean.TRUE.equals(isPureRevert) ? 1 : 0), engine.trail)) {
|
||||
return engine.fail();
|
||||
}
|
||||
return cont;
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user