Remove workflow package and replace with simple LabelNormalizer
The per-CategoryFunction logic has been replaced by Prolog rules, and the only remaining functionality of FunctionState was to normalize labels to the correct ranges. Add a LabelNormalizer that explicitly just normalizes labels, and remove the now-unused classes. In addition to squashing ranges, LabelNormalizer now also removes PatchSetApprovals from the input list that correspond to nonexistent labels. Fix MergeOp to handle this behavior, which can happen for example if a label is removed from the project config while in review. Change-Id: I6955594f7ac1c4e080f82d10f4b2579c31057512
This commit is contained in:
@@ -59,6 +59,7 @@ import com.google.gerrit.server.IdentifiedUser;
|
|||||||
import com.google.gerrit.server.account.AccountInfo;
|
import com.google.gerrit.server.account.AccountInfo;
|
||||||
import com.google.gerrit.server.config.CanonicalWebUrl;
|
import com.google.gerrit.server.config.CanonicalWebUrl;
|
||||||
import com.google.gerrit.server.config.GerritServerConfig;
|
import com.google.gerrit.server.config.GerritServerConfig;
|
||||||
|
import com.google.gerrit.server.git.LabelNormalizer;
|
||||||
import com.google.gerrit.server.patch.PatchList;
|
import com.google.gerrit.server.patch.PatchList;
|
||||||
import com.google.gerrit.server.patch.PatchListCache;
|
import com.google.gerrit.server.patch.PatchListCache;
|
||||||
import com.google.gerrit.server.patch.PatchListEntry;
|
import com.google.gerrit.server.patch.PatchListEntry;
|
||||||
@@ -113,6 +114,7 @@ public class ChangeJson {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private final Provider<ReviewDb> db;
|
private final Provider<ReviewDb> db;
|
||||||
|
private final LabelNormalizer labelNormalizer;
|
||||||
private final CurrentUser user;
|
private final CurrentUser user;
|
||||||
private final AnonymousUser anonymous;
|
private final AnonymousUser anonymous;
|
||||||
private final IdentifiedUser.GenericFactory userFactory;
|
private final IdentifiedUser.GenericFactory userFactory;
|
||||||
@@ -131,6 +133,7 @@ public class ChangeJson {
|
|||||||
@Inject
|
@Inject
|
||||||
ChangeJson(
|
ChangeJson(
|
||||||
Provider<ReviewDb> db,
|
Provider<ReviewDb> db,
|
||||||
|
LabelNormalizer ln,
|
||||||
CurrentUser u,
|
CurrentUser u,
|
||||||
AnonymousUser au,
|
AnonymousUser au,
|
||||||
IdentifiedUser.GenericFactory uf,
|
IdentifiedUser.GenericFactory uf,
|
||||||
@@ -141,6 +144,7 @@ public class ChangeJson {
|
|||||||
@CanonicalWebUrl Provider<String> curl,
|
@CanonicalWebUrl Provider<String> curl,
|
||||||
Urls urls) {
|
Urls urls) {
|
||||||
this.db = db;
|
this.db = db;
|
||||||
|
this.labelNormalizer = ln;
|
||||||
this.user = u;
|
this.user = u;
|
||||||
this.anonymous = au;
|
this.anonymous = au;
|
||||||
this.userFactory = uf;
|
this.userFactory = uf;
|
||||||
@@ -423,13 +427,14 @@ public class ChangeJson {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Set<Account.Id> allUsers = Sets.newHashSet();
|
Set<Account.Id> allUsers = Sets.newHashSet();
|
||||||
for (PatchSetApproval psa : cd.allApprovals(db)) {
|
|
||||||
allUsers.add(psa.getAccountId());
|
|
||||||
}
|
|
||||||
|
|
||||||
Multimap<Account.Id, PatchSetApproval> current = HashMultimap.create();
|
Multimap<Account.Id, PatchSetApproval> current = HashMultimap.create();
|
||||||
for (PatchSetApproval a : cd.currentApprovals(db)) {
|
PatchSet.Id psId = baseCtrl.getChange().currentPatchSetId();
|
||||||
current.put(a.getAccountId(), a);
|
for (PatchSetApproval psa : labelNormalizer.normalize(
|
||||||
|
baseCtrl, cd.allApprovals(db))) {
|
||||||
|
allUsers.add(psa.getAccountId());
|
||||||
|
if (psa.getPatchSetId().equals(psId)) {
|
||||||
|
current.put(psa.getAccountId(), psa);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (Account.Id accountId : allUsers) {
|
for (Account.Id accountId : allUsers) {
|
||||||
@@ -453,9 +458,7 @@ public class ChangeJson {
|
|||||||
if (info == null) {
|
if (info == null) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
info.value = psa.getValue();
|
||||||
PermissionRange r = ctl.getRange(Permission.forLabel(lt.getName()));
|
|
||||||
info.value = r != null ? r.squash(psa.getValue()) : 0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -28,10 +28,9 @@ import com.google.gerrit.reviewdb.client.PatchSet;
|
|||||||
import com.google.gerrit.reviewdb.client.PatchSetApproval;
|
import com.google.gerrit.reviewdb.client.PatchSetApproval;
|
||||||
import com.google.gerrit.reviewdb.server.ReviewDb;
|
import com.google.gerrit.reviewdb.server.ReviewDb;
|
||||||
import com.google.gerrit.server.account.AccountInfo;
|
import com.google.gerrit.server.account.AccountInfo;
|
||||||
|
import com.google.gerrit.server.git.LabelNormalizer;
|
||||||
import com.google.gerrit.server.project.ChangeControl;
|
import com.google.gerrit.server.project.ChangeControl;
|
||||||
import com.google.gerrit.server.query.change.ChangeData;
|
import com.google.gerrit.server.query.change.ChangeData;
|
||||||
import com.google.gerrit.server.workflow.CategoryFunction;
|
|
||||||
import com.google.gerrit.server.workflow.FunctionState;
|
|
||||||
import com.google.gwtorm.server.OrmException;
|
import com.google.gwtorm.server.OrmException;
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
import com.google.inject.Provider;
|
import com.google.inject.Provider;
|
||||||
@@ -43,15 +42,15 @@ import java.util.TreeMap;
|
|||||||
|
|
||||||
public class ReviewerJson {
|
public class ReviewerJson {
|
||||||
private final Provider<ReviewDb> db;
|
private final Provider<ReviewDb> db;
|
||||||
private final FunctionState.Factory functionState;
|
private final LabelNormalizer labelNormalizer;
|
||||||
private final AccountInfo.Loader.Factory accountLoaderFactory;
|
private final AccountInfo.Loader.Factory accountLoaderFactory;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
ReviewerJson(Provider<ReviewDb> db,
|
ReviewerJson(Provider<ReviewDb> db,
|
||||||
FunctionState.Factory functionState,
|
LabelNormalizer labelNormalizer,
|
||||||
AccountInfo.Loader.Factory accountLoaderFactory) {
|
AccountInfo.Loader.Factory accountLoaderFactory) {
|
||||||
this.db = db;
|
this.db = db;
|
||||||
this.functionState = functionState;
|
this.labelNormalizer = labelNormalizer;
|
||||||
this.accountLoaderFactory = accountLoaderFactory;
|
this.accountLoaderFactory = accountLoaderFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,12 +79,8 @@ public class ReviewerJson {
|
|||||||
approvals = ChangeData.sortApprovals(db.get().patchSetApprovals()
|
approvals = ChangeData.sortApprovals(db.get().patchSetApprovals()
|
||||||
.byPatchSetUser(psId, out._id));
|
.byPatchSetUser(psId, out._id));
|
||||||
}
|
}
|
||||||
|
approvals = labelNormalizer.normalize(ctl, approvals);
|
||||||
LabelTypes labelTypes = ctl.getLabelTypes();
|
LabelTypes labelTypes = ctl.getLabelTypes();
|
||||||
FunctionState fs = functionState.create(ctl, psId, approvals);
|
|
||||||
for (LabelType at : labelTypes.getLabelTypes()) {
|
|
||||||
CategoryFunction.forType(at).run(at, fs);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Don't use Maps.newTreeMap(Comparator) due to OpenJDK bug 100167.
|
// Don't use Maps.newTreeMap(Comparator) due to OpenJDK bug 100167.
|
||||||
out.approvals = new TreeMap<String,String>(labelTypes.nameComparator());
|
out.approvals = new TreeMap<String,String>(labelTypes.nameComparator());
|
||||||
|
@@ -104,7 +104,6 @@ import com.google.gerrit.server.ssh.SshAddressesModule;
|
|||||||
import com.google.gerrit.server.tools.ToolsCatalog;
|
import com.google.gerrit.server.tools.ToolsCatalog;
|
||||||
import com.google.gerrit.server.util.IdGenerator;
|
import com.google.gerrit.server.util.IdGenerator;
|
||||||
import com.google.gerrit.server.util.ThreadLocalRequestContext;
|
import com.google.gerrit.server.util.ThreadLocalRequestContext;
|
||||||
import com.google.gerrit.server.workflow.FunctionState;
|
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
import com.google.inject.TypeLiteral;
|
import com.google.inject.TypeLiteral;
|
||||||
|
|
||||||
@@ -223,7 +222,6 @@ public class GerritGlobalModule extends FactoryModule {
|
|||||||
bind(ChangeControl.GenericFactory.class);
|
bind(ChangeControl.GenericFactory.class);
|
||||||
bind(ProjectControl.GenericFactory.class);
|
bind(ProjectControl.GenericFactory.class);
|
||||||
bind(AccountControl.Factory.class);
|
bind(AccountControl.Factory.class);
|
||||||
factory(FunctionState.Factory.class);
|
|
||||||
|
|
||||||
install(new AuditModule());
|
install(new AuditModule());
|
||||||
install(new com.google.gerrit.server.account.Module());
|
install(new com.google.gerrit.server.account.Module());
|
||||||
|
@@ -0,0 +1,117 @@
|
|||||||
|
// Copyright (C) 2013 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.server.git;
|
||||||
|
|
||||||
|
import static com.google.common.base.Preconditions.checkArgument;
|
||||||
|
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
|
import com.google.gerrit.common.data.LabelType;
|
||||||
|
import com.google.gerrit.common.data.LabelTypes;
|
||||||
|
import com.google.gerrit.common.data.LabelValue;
|
||||||
|
import com.google.gerrit.common.data.Permission;
|
||||||
|
import com.google.gerrit.common.data.PermissionRange;
|
||||||
|
import com.google.gerrit.reviewdb.client.Change;
|
||||||
|
import com.google.gerrit.reviewdb.client.PatchSetApproval;
|
||||||
|
import com.google.gerrit.server.IdentifiedUser;
|
||||||
|
import com.google.gerrit.server.project.ChangeControl;
|
||||||
|
import com.google.gerrit.server.project.NoSuchChangeException;
|
||||||
|
import com.google.inject.Inject;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class LabelNormalizer {
|
||||||
|
private final ChangeControl.GenericFactory changeFactory;
|
||||||
|
private final IdentifiedUser.GenericFactory userFactory;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
LabelNormalizer(ChangeControl.GenericFactory changeFactory,
|
||||||
|
IdentifiedUser.GenericFactory userFactory) {
|
||||||
|
this.changeFactory = changeFactory;
|
||||||
|
this.userFactory = userFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param change change containing the given approvals.
|
||||||
|
* @param approvals list of approvals.
|
||||||
|
* @return copies of approvals normalized to the defined ranges for the label
|
||||||
|
* type and permissions for the user. Approvals for unknown labels are not
|
||||||
|
* included in the output.
|
||||||
|
* @throws NoSuchChangeException
|
||||||
|
*/
|
||||||
|
public List<PatchSetApproval> normalize(Change change,
|
||||||
|
Collection<PatchSetApproval> approvals) throws NoSuchChangeException {
|
||||||
|
return normalize(
|
||||||
|
changeFactory.controlFor(change, userFactory.create(change.getOwner())),
|
||||||
|
approvals);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param change change control containing the given approvals.
|
||||||
|
* @param approvals list of approvals.
|
||||||
|
* @return copies of approvals normalized to the defined ranges for the label
|
||||||
|
* type and permissions for the user. Approvals for unknown labels are not
|
||||||
|
* included in the output.
|
||||||
|
*/
|
||||||
|
public List<PatchSetApproval> normalize(ChangeControl ctl,
|
||||||
|
Collection<PatchSetApproval> approvals) {
|
||||||
|
List<PatchSetApproval> result =
|
||||||
|
Lists.newArrayListWithCapacity(approvals.size());
|
||||||
|
LabelTypes labelTypes = ctl.getLabelTypes();
|
||||||
|
for (PatchSetApproval psa : approvals) {
|
||||||
|
Change.Id changeId = psa.getKey().getParentKey().getParentKey();
|
||||||
|
checkArgument(changeId.equals(ctl.getChange().getId()),
|
||||||
|
"Approval %s does not match change %s",
|
||||||
|
psa.getKey(), ctl.getChange().getKey());
|
||||||
|
LabelType label = labelTypes.byLabel(psa.getLabelId());
|
||||||
|
boolean isSubmit = psa.isSubmit();
|
||||||
|
if (label != null || isSubmit) {
|
||||||
|
psa = copy(psa, ctl);
|
||||||
|
result.add(psa);
|
||||||
|
if (!isSubmit) {
|
||||||
|
applyTypeFloor(label, psa);
|
||||||
|
applyRightFloor(ctl, label, psa);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private PatchSetApproval copy(PatchSetApproval src, ChangeControl ctl) {
|
||||||
|
PatchSetApproval dest = new PatchSetApproval(src.getPatchSetId(), src);
|
||||||
|
dest.cache(ctl.getChange());
|
||||||
|
dest.setLabel(src.getLabel());
|
||||||
|
return dest;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void applyRightFloor(ChangeControl ctl, LabelType lt,
|
||||||
|
PatchSetApproval a) {
|
||||||
|
String permission = Permission.forLabel(lt.getName());
|
||||||
|
IdentifiedUser user = userFactory.create(a.getAccountId());
|
||||||
|
PermissionRange range = ctl.forUser(user).getRange(permission);
|
||||||
|
a.setValue((short) range.squash(a.getValue()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void applyTypeFloor(LabelType lt, PatchSetApproval a) {
|
||||||
|
LabelValue atMin = lt.getMin();
|
||||||
|
if (atMin != null && a.getValue() < atMin.getValue()) {
|
||||||
|
a.setValue(atMin.getValue());
|
||||||
|
}
|
||||||
|
LabelValue atMax = lt.getMax();
|
||||||
|
if (atMax != null && a.getValue() > atMax.getValue()) {
|
||||||
|
a.setValue(atMax.getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -25,9 +25,9 @@ import com.google.common.base.Objects;
|
|||||||
import com.google.common.collect.ArrayListMultimap;
|
import com.google.common.collect.ArrayListMultimap;
|
||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
import com.google.common.collect.ListMultimap;
|
import com.google.common.collect.ListMultimap;
|
||||||
|
import com.google.common.collect.Sets;
|
||||||
import com.google.gerrit.common.ChangeHooks;
|
import com.google.gerrit.common.ChangeHooks;
|
||||||
import com.google.gerrit.common.data.Capable;
|
import com.google.gerrit.common.data.Capable;
|
||||||
import com.google.gerrit.common.data.LabelType;
|
|
||||||
import com.google.gerrit.common.data.LabelTypes;
|
import com.google.gerrit.common.data.LabelTypes;
|
||||||
import com.google.gerrit.common.data.SubmitTypeRecord;
|
import com.google.gerrit.common.data.SubmitTypeRecord;
|
||||||
import com.google.gerrit.reviewdb.client.Account;
|
import com.google.gerrit.reviewdb.client.Account;
|
||||||
@@ -55,8 +55,6 @@ import com.google.gerrit.server.project.NoSuchProjectException;
|
|||||||
import com.google.gerrit.server.project.ProjectCache;
|
import com.google.gerrit.server.project.ProjectCache;
|
||||||
import com.google.gerrit.server.project.ProjectState;
|
import com.google.gerrit.server.project.ProjectState;
|
||||||
import com.google.gerrit.server.util.RequestScopePropagator;
|
import com.google.gerrit.server.util.RequestScopePropagator;
|
||||||
import com.google.gerrit.server.workflow.CategoryFunction;
|
|
||||||
import com.google.gerrit.server.workflow.FunctionState;
|
|
||||||
import com.google.gwtorm.server.AtomicUpdate;
|
import com.google.gwtorm.server.AtomicUpdate;
|
||||||
import com.google.gwtorm.server.OrmConcurrencyException;
|
import com.google.gwtorm.server.OrmConcurrencyException;
|
||||||
import com.google.gwtorm.server.OrmException;
|
import com.google.gwtorm.server.OrmException;
|
||||||
@@ -126,7 +124,7 @@ public class MergeOp {
|
|||||||
private final GitRepositoryManager repoManager;
|
private final GitRepositoryManager repoManager;
|
||||||
private final SchemaFactory<ReviewDb> schemaFactory;
|
private final SchemaFactory<ReviewDb> schemaFactory;
|
||||||
private final ProjectCache projectCache;
|
private final ProjectCache projectCache;
|
||||||
private final FunctionState.Factory functionState;
|
private final LabelNormalizer labelNormalizer;
|
||||||
private final GitReferenceUpdated gitRefUpdated;
|
private final GitReferenceUpdated gitRefUpdated;
|
||||||
private final MergedSender.Factory mergedSenderFactory;
|
private final MergedSender.Factory mergedSenderFactory;
|
||||||
private final MergeFailSender.Factory mergeFailSenderFactory;
|
private final MergeFailSender.Factory mergeFailSenderFactory;
|
||||||
@@ -160,7 +158,7 @@ public class MergeOp {
|
|||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
MergeOp(final GitRepositoryManager grm, final SchemaFactory<ReviewDb> sf,
|
MergeOp(final GitRepositoryManager grm, final SchemaFactory<ReviewDb> sf,
|
||||||
final ProjectCache pc, final FunctionState.Factory fs,
|
final ProjectCache pc, final LabelNormalizer fs,
|
||||||
final GitReferenceUpdated gru, final MergedSender.Factory msf,
|
final GitReferenceUpdated gru, final MergedSender.Factory msf,
|
||||||
final MergeFailSender.Factory mfsf,
|
final MergeFailSender.Factory mfsf,
|
||||||
final LabelTypes labelTypes, final PatchSetInfoFactory psif,
|
final LabelTypes labelTypes, final PatchSetInfoFactory psif,
|
||||||
@@ -176,7 +174,7 @@ public class MergeOp {
|
|||||||
final AllProjectsName allProjectsName) {
|
final AllProjectsName allProjectsName) {
|
||||||
repoManager = grm;
|
repoManager = grm;
|
||||||
schemaFactory = sf;
|
schemaFactory = sf;
|
||||||
functionState = fs;
|
labelNormalizer = fs;
|
||||||
projectCache = pc;
|
projectCache = pc;
|
||||||
gitRefUpdated = gru;
|
gitRefUpdated = gru;
|
||||||
mergedSenderFactory = msf;
|
mergedSenderFactory = msf;
|
||||||
@@ -927,16 +925,18 @@ public class MergeOp {
|
|||||||
PatchSetApproval submitter = null;
|
PatchSetApproval submitter = null;
|
||||||
try {
|
try {
|
||||||
c.setStatus(Change.Status.MERGED);
|
c.setStatus(Change.Status.MERGED);
|
||||||
final List<PatchSetApproval> approvals =
|
|
||||||
|
List<PatchSetApproval> approvals =
|
||||||
db.patchSetApprovals().byChange(changeId).toList();
|
db.patchSetApprovals().byChange(changeId).toList();
|
||||||
final ChangeControl control = changeControlFactory.controlFor(c,
|
Set<PatchSetApproval.Key> toDelete =
|
||||||
identifiedUserFactory.create(c.getOwner()));
|
Sets.newHashSetWithExpectedSize(approvals.size());
|
||||||
final FunctionState fs = functionState.create(
|
|
||||||
control, merged, approvals);
|
|
||||||
for (LabelType lt : control.getLabelTypes().getLabelTypes()) {
|
|
||||||
CategoryFunction.forType(lt).run(lt, fs);
|
|
||||||
}
|
|
||||||
for (PatchSetApproval a : approvals) {
|
for (PatchSetApproval a : approvals) {
|
||||||
|
toDelete.add(a.getKey());
|
||||||
|
}
|
||||||
|
|
||||||
|
approvals = labelNormalizer.normalize(c, approvals);
|
||||||
|
for (PatchSetApproval a : approvals) {
|
||||||
|
toDelete.remove(a.getKey());
|
||||||
if (a.getValue() > 0 && a.isSubmit()
|
if (a.getValue() > 0 && a.isSubmit()
|
||||||
&& a.getPatchSetId().equals(merged)) {
|
&& a.getPatchSetId().equals(merged)) {
|
||||||
if (submitter == null
|
if (submitter == null
|
||||||
@@ -947,6 +947,7 @@ public class MergeOp {
|
|||||||
a.cache(c);
|
a.cache(c);
|
||||||
}
|
}
|
||||||
db.patchSetApprovals().update(approvals);
|
db.patchSetApprovals().update(approvals);
|
||||||
|
db.patchSetApprovals().deleteKeys(toDelete);
|
||||||
} catch (NoSuchChangeException err) {
|
} catch (NoSuchChangeException err) {
|
||||||
log.warn("Cannot normalize approvals for change " + changeId, err);
|
log.warn("Cannot normalize approvals for change " + changeId, err);
|
||||||
} catch (OrmException err) {
|
} catch (OrmException err) {
|
||||||
|
@@ -17,9 +17,11 @@ package com.google.gerrit.server.git;
|
|||||||
import static com.google.gerrit.common.data.Permission.isPermission;
|
import static com.google.gerrit.common.data.Permission.isPermission;
|
||||||
|
|
||||||
import com.google.common.base.CharMatcher;
|
import com.google.common.base.CharMatcher;
|
||||||
|
import com.google.common.base.Joiner;
|
||||||
import com.google.common.base.Objects;
|
import com.google.common.base.Objects;
|
||||||
import com.google.common.base.Splitter;
|
import com.google.common.base.Splitter;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import com.google.common.collect.ImmutableSet;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
import com.google.common.collect.Maps;
|
import com.google.common.collect.Maps;
|
||||||
import com.google.common.collect.Sets;
|
import com.google.common.collect.Sets;
|
||||||
@@ -43,7 +45,6 @@ import com.google.gerrit.reviewdb.client.Project.SubmitType;
|
|||||||
import com.google.gerrit.server.account.GroupBackend;
|
import com.google.gerrit.server.account.GroupBackend;
|
||||||
import com.google.gerrit.server.config.ConfigUtil;
|
import com.google.gerrit.server.config.ConfigUtil;
|
||||||
import com.google.gerrit.server.mail.Address;
|
import com.google.gerrit.server.mail.Address;
|
||||||
import com.google.gerrit.server.workflow.CategoryFunction;
|
|
||||||
|
|
||||||
import org.eclipse.jgit.errors.ConfigInvalidException;
|
import org.eclipse.jgit.errors.ConfigInvalidException;
|
||||||
import org.eclipse.jgit.lib.CommitBuilder;
|
import org.eclipse.jgit.lib.CommitBuilder;
|
||||||
@@ -114,6 +115,8 @@ public class ProjectConfig extends VersionedMetaData {
|
|||||||
private static final String KEY_COPY_MIN_SCORE = "copyMinScore";
|
private static final String KEY_COPY_MIN_SCORE = "copyMinScore";
|
||||||
private static final String KEY_VALUE = "value";
|
private static final String KEY_VALUE = "value";
|
||||||
private static final String KEY_CAN_OVERRIDE = "canOverride";
|
private static final String KEY_CAN_OVERRIDE = "canOverride";
|
||||||
|
private static final Set<String> LABEL_FUNCTIONS = ImmutableSet.of(
|
||||||
|
"MaxWithBlock", "MaxNoBlock", "NoBlock", "NoOp");
|
||||||
|
|
||||||
private static final SubmitType defaultSubmitAction =
|
private static final SubmitType defaultSubmitAction =
|
||||||
SubmitType.MERGE_IF_NECESSARY;
|
SubmitType.MERGE_IF_NECESSARY;
|
||||||
@@ -570,11 +573,12 @@ public class ProjectConfig extends VersionedMetaData {
|
|||||||
|
|
||||||
String functionName = Objects.firstNonNull(
|
String functionName = Objects.firstNonNull(
|
||||||
rc.getString(LABEL, name, KEY_FUNCTION), "MaxWithBlock");
|
rc.getString(LABEL, name, KEY_FUNCTION), "MaxWithBlock");
|
||||||
if (CategoryFunction.forName(functionName) != null) {
|
if (LABEL_FUNCTIONS.contains(functionName)) {
|
||||||
label.setFunctionName(functionName);
|
label.setFunctionName(functionName);
|
||||||
} else {
|
} else {
|
||||||
error(new ValidationError(PROJECT_CONFIG, String.format(
|
error(new ValidationError(PROJECT_CONFIG, String.format(
|
||||||
"Invalid %s for label \"%s\"", KEY_FUNCTION, name)));
|
"Invalid %s for label \"%s\". Valid names are: %s",
|
||||||
|
KEY_FUNCTION, name, Joiner.on(", ").join(LABEL_FUNCTIONS))));
|
||||||
label.setFunctionName(null);
|
label.setFunctionName(null);
|
||||||
}
|
}
|
||||||
label.setCopyMinScore(
|
label.setCopyMinScore(
|
||||||
|
@@ -1,80 +0,0 @@
|
|||||||
// Copyright (C) 2008 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.server.workflow;
|
|
||||||
|
|
||||||
import com.google.gerrit.common.data.LabelType;
|
|
||||||
import com.google.gerrit.reviewdb.client.PatchSetApproval;
|
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/** Function to control {@link PatchSetApproval}s in an {@link ApprovalCategory}. */
|
|
||||||
public abstract class CategoryFunction {
|
|
||||||
private static Map<String, CategoryFunction> all =
|
|
||||||
new HashMap<String, CategoryFunction>();
|
|
||||||
static {
|
|
||||||
all.put(MaxWithBlock.NAME, new MaxWithBlock());
|
|
||||||
all.put(MaxNoBlock.NAME, new MaxNoBlock());
|
|
||||||
all.put(NoOpFunction.NAME, new NoOpFunction());
|
|
||||||
all.put(NoBlock.NAME, new NoBlock());
|
|
||||||
}
|
|
||||||
|
|
||||||
public static CategoryFunction forName(String name) {
|
|
||||||
return all.get(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Locate a function by type.
|
|
||||||
*
|
|
||||||
* @param type the type the function is for.
|
|
||||||
* @return the function implementation; {@link NoOpFunction} if the function
|
|
||||||
* is not known to Gerrit and thus cannot be executed.
|
|
||||||
*/
|
|
||||||
public static CategoryFunction forType(LabelType type) {
|
|
||||||
CategoryFunction r = all.get(type.getFunctionName());
|
|
||||||
return r != null ? r : new NoOpFunction();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Normalize ChangeApprovals and set the valid flag for this category.
|
|
||||||
* <p>
|
|
||||||
* Implementors should invoke:
|
|
||||||
*
|
|
||||||
* <pre>
|
|
||||||
* state.valid(at, true);
|
|
||||||
* </pre>
|
|
||||||
* <p>
|
|
||||||
* If the set of approvals from <code>state.getApprovals(at)</code> covers the
|
|
||||||
* requirements for the function, indicating the category has been completed.
|
|
||||||
* <p>
|
|
||||||
* An example implementation which requires at least one positive and no
|
|
||||||
* negatives might be:
|
|
||||||
*
|
|
||||||
* <pre>
|
|
||||||
* boolean neg = false, pos = false;
|
|
||||||
* for (final ChangeApproval ca : state.getApprovals(at)) {
|
|
||||||
* state.normalize(ca);
|
|
||||||
* neg |= ca.getValue() < 0;
|
|
||||||
* pos |= ca.getValue() > 0;
|
|
||||||
* }
|
|
||||||
* state.valid(at, !neg && pos);
|
|
||||||
* </pre>
|
|
||||||
*
|
|
||||||
* @param lt the cached category description to process.
|
|
||||||
* @param state state to read approvals and project rights from, and to update
|
|
||||||
* the valid status into.
|
|
||||||
*/
|
|
||||||
public abstract void run(LabelType lt, FunctionState state);
|
|
||||||
}
|
|
@@ -1,150 +0,0 @@
|
|||||||
// Copyright (C) 2008 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.server.workflow;
|
|
||||||
|
|
||||||
import com.google.gerrit.common.data.LabelType;
|
|
||||||
import com.google.gerrit.common.data.LabelValue;
|
|
||||||
import com.google.gerrit.common.data.Permission;
|
|
||||||
import com.google.gerrit.common.data.PermissionRange;
|
|
||||||
import com.google.gerrit.reviewdb.client.Change;
|
|
||||||
import com.google.gerrit.reviewdb.client.PatchSet;
|
|
||||||
import com.google.gerrit.reviewdb.client.PatchSetApproval;
|
|
||||||
import com.google.gerrit.server.CurrentUser;
|
|
||||||
import com.google.gerrit.server.IdentifiedUser;
|
|
||||||
import com.google.gerrit.server.project.ChangeControl;
|
|
||||||
import com.google.inject.Inject;
|
|
||||||
import com.google.inject.assistedinject.Assisted;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/** State passed through to a {@link CategoryFunction}. */
|
|
||||||
public class FunctionState {
|
|
||||||
public interface Factory {
|
|
||||||
FunctionState create(ChangeControl c, PatchSet.Id psId,
|
|
||||||
Collection<PatchSetApproval> all);
|
|
||||||
}
|
|
||||||
|
|
||||||
private final IdentifiedUser.GenericFactory userFactory;
|
|
||||||
|
|
||||||
private final Map<String, Collection<PatchSetApproval>> approvals =
|
|
||||||
new HashMap<String, Collection<PatchSetApproval>>();
|
|
||||||
private final Map<String, Boolean> valid = new HashMap<String, Boolean>();
|
|
||||||
private final ChangeControl callerChangeControl;
|
|
||||||
private final Change change;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
FunctionState(final IdentifiedUser.GenericFactory userFactory,
|
|
||||||
@Assisted final ChangeControl c, @Assisted final PatchSet.Id psId,
|
|
||||||
@Assisted final Collection<PatchSetApproval> all) {
|
|
||||||
this.userFactory = userFactory;
|
|
||||||
|
|
||||||
callerChangeControl = c;
|
|
||||||
change = c.getChange();
|
|
||||||
|
|
||||||
for (final PatchSetApproval ca : all) {
|
|
||||||
if (psId.equals(ca.getPatchSetId())) {
|
|
||||||
LabelType lt = c.getLabelTypes().byLabel(ca.getLabelId());
|
|
||||||
if (lt == null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
Collection<PatchSetApproval> l = approvals.get(lt.getName());
|
|
||||||
if (l == null) {
|
|
||||||
l = new ArrayList<PatchSetApproval>();
|
|
||||||
approvals.put(lt.getName(), l);
|
|
||||||
}
|
|
||||||
l.add(ca);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
List<LabelType> getLabelTypes() {
|
|
||||||
return callerChangeControl.getLabelTypes().getLabelTypes();
|
|
||||||
}
|
|
||||||
|
|
||||||
Change getChange() {
|
|
||||||
return change;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void valid(final LabelType lt, final boolean v) {
|
|
||||||
valid.put(lt.getName(), v);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isValid(final LabelType lt) {
|
|
||||||
return isValid(lt.getName());
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isValid(final String labelName) {
|
|
||||||
final Boolean b = valid.get(labelName);
|
|
||||||
return b != null && b;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Collection<PatchSetApproval> getApprovals(final LabelType lt) {
|
|
||||||
return getApprovals(lt.getName());
|
|
||||||
}
|
|
||||||
|
|
||||||
public Collection<PatchSetApproval> getApprovals(final String labelName) {
|
|
||||||
final Collection<PatchSetApproval> l = approvals.get(labelName);
|
|
||||||
return l != null ? l : Collections.<PatchSetApproval> emptySet();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Normalize the approval record down to the range permitted by the type, in
|
|
||||||
* case the type was modified since the approval was originally granted.
|
|
||||||
* <p>
|
|
||||||
*/
|
|
||||||
private void applyTypeFloor(final LabelType lt, final PatchSetApproval a) {
|
|
||||||
final LabelValue atMin = lt.getMin();
|
|
||||||
|
|
||||||
if (atMin != null && a.getValue() < atMin.getValue()) {
|
|
||||||
a.setValue(atMin.getValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
final LabelValue atMax = lt.getMax();
|
|
||||||
if (atMax != null && a.getValue() > atMax.getValue()) {
|
|
||||||
a.setValue(atMax.getValue());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Normalize the approval record to be inside the maximum range permitted by
|
|
||||||
* the RefRights granted to groups the account is a member of.
|
|
||||||
* <p>
|
|
||||||
* If multiple RefRights are matched (assigned to different groups the account
|
|
||||||
* is a member of) the lowest minValue and the highest maxValue of the union
|
|
||||||
* of them is used.
|
|
||||||
* <p>
|
|
||||||
*/
|
|
||||||
private void applyRightFloor(final LabelType lt, final PatchSetApproval a) {
|
|
||||||
final String permission = Permission.forLabel(lt.getName());
|
|
||||||
final IdentifiedUser user = userFactory.create(a.getAccountId());
|
|
||||||
final PermissionRange range = controlFor(user).getRange(permission);
|
|
||||||
a.setValue((short) range.squash(a.getValue()));
|
|
||||||
}
|
|
||||||
|
|
||||||
private ChangeControl controlFor(CurrentUser user) {
|
|
||||||
return callerChangeControl.forUser(user);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Run <code>applyTypeFloor</code>, <code>applyRightFloor</code>. */
|
|
||||||
public void normalize(final LabelType lt, final PatchSetApproval ca) {
|
|
||||||
applyTypeFloor(lt, ca);
|
|
||||||
applyRightFloor(lt, ca);
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,47 +0,0 @@
|
|||||||
// Copyright (C) 2010 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.server.workflow;
|
|
||||||
|
|
||||||
import com.google.gerrit.common.data.LabelType;
|
|
||||||
import com.google.gerrit.reviewdb.client.PatchSetApproval;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Computes an {@link ApprovalCategory} by looking at maximum values.
|
|
||||||
* <p>
|
|
||||||
* In order to be considered "approved" this function requires that:
|
|
||||||
* <ul>
|
|
||||||
* <li>The maximum positive value is used at least once;</li>
|
|
||||||
* <li>The user approving the maximum positive has been granted that.</li>
|
|
||||||
* </ul>
|
|
||||||
* <p>
|
|
||||||
* This function is primarily useful for advisory review fields.
|
|
||||||
*/
|
|
||||||
public class MaxNoBlock extends CategoryFunction {
|
|
||||||
public static String NAME = "MaxNoBlock";
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run(final LabelType lt, final FunctionState state) {
|
|
||||||
boolean passed = false;
|
|
||||||
for (final PatchSetApproval a : state.getApprovals(lt)) {
|
|
||||||
state.normalize(lt, a);
|
|
||||||
|
|
||||||
passed |= lt.isMaxPositive(a);
|
|
||||||
}
|
|
||||||
|
|
||||||
// The type must have at least one max positive (a full accept).
|
|
||||||
//
|
|
||||||
state.valid(lt, passed);
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,62 +0,0 @@
|
|||||||
// Copyright (C) 2008 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.server.workflow;
|
|
||||||
|
|
||||||
import com.google.gerrit.common.data.LabelType;
|
|
||||||
import com.google.gerrit.reviewdb.client.PatchSetApproval;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Computes an {@link ApprovalCategory} by looking at maximum values.
|
|
||||||
* <p>
|
|
||||||
* In order to be considered "approved" this function requires that:
|
|
||||||
* <ul>
|
|
||||||
* <li>The maximum negative value is never used;</li>
|
|
||||||
* <li>The maximum positive value is used at least once;</li>
|
|
||||||
* <li>The user approving the maximum positive has been granted that.</li>
|
|
||||||
* </ul>
|
|
||||||
* <p>
|
|
||||||
* This function is primarily useful for review fields, with values such as:
|
|
||||||
* <ul>
|
|
||||||
* <li>+2: Approved change.</li>
|
|
||||||
* <li>+1: Looks ok, but get another approval from someone with more depth.</li>
|
|
||||||
* <li>-1: Soft reject, it isn't a great change but its OK if approved.</li>
|
|
||||||
* <li>-2: Rejected, must not be submitted.
|
|
||||||
* </ul>
|
|
||||||
* <p>
|
|
||||||
* Note that projects using this function would typically want to assign out the
|
|
||||||
* middle range (-1 .. +1) to almost everyone, so people can indicate how they
|
|
||||||
* feel about a change, but the extremes of -2 and +2 should be reserved for the
|
|
||||||
* project's long-term maintainers, those who are most familiar with its code.
|
|
||||||
*/
|
|
||||||
public class MaxWithBlock extends CategoryFunction {
|
|
||||||
public static String NAME = "MaxWithBlock";
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run(final LabelType lt, final FunctionState state) {
|
|
||||||
boolean rejected = false;
|
|
||||||
boolean passed = false;
|
|
||||||
for (final PatchSetApproval a : state.getApprovals(lt)) {
|
|
||||||
state.normalize(lt, a);
|
|
||||||
|
|
||||||
rejected |= lt.isMaxNegative(a);
|
|
||||||
passed |= lt.isMaxPositive(a);
|
|
||||||
}
|
|
||||||
|
|
||||||
// The type must not have had its max negative (a forceful reject)
|
|
||||||
// and must have at least one max positive (a full accept).
|
|
||||||
//
|
|
||||||
state.valid(lt, !rejected && passed);
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,27 +0,0 @@
|
|||||||
// Copyright (C) 2010 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.server.workflow;
|
|
||||||
|
|
||||||
import com.google.gerrit.common.data.LabelType;
|
|
||||||
|
|
||||||
/** A function that does nothing. */
|
|
||||||
public class NoBlock extends CategoryFunction {
|
|
||||||
public static String NAME = "NoBlock";
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run(final LabelType lt, final FunctionState state) {
|
|
||||||
state.valid(lt, true);
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,26 +0,0 @@
|
|||||||
// Copyright (C) 2008 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.server.workflow;
|
|
||||||
|
|
||||||
import com.google.gerrit.common.data.LabelType;
|
|
||||||
|
|
||||||
/** A function that does nothing. */
|
|
||||||
public class NoOpFunction extends CategoryFunction {
|
|
||||||
public static String NAME = "NoOp";
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run(final LabelType lt, final FunctionState state) {
|
|
||||||
}
|
|
||||||
}
|
|
Reference in New Issue
Block a user