mirror of
https://github.com/theonedev/onedev.git
synced 2025-12-08 18:26:30 +00:00
Reports gatekeeper message and potential voters.
This commit is contained in:
parent
1f1f3709b9
commit
63fc7c35d2
@ -64,6 +64,11 @@ public class EntityPatternSet extends PatternSet implements Trimmable {
|
|||||||
return input;
|
return input;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return new PatternSet(asInput()).toString();
|
||||||
|
}
|
||||||
|
|
||||||
public static EntityPatternSet fromInput(List<ExclusiveAwarePattern> input, EntityLoader entityLoader) {
|
public static EntityPatternSet fromInput(List<ExclusiveAwarePattern> input, EntityLoader entityLoader) {
|
||||||
List<ExclusiveAwarePattern> stored = new ArrayList<ExclusiveAwarePattern>();
|
List<ExclusiveAwarePattern> stored = new ArrayList<ExclusiveAwarePattern>();
|
||||||
for (ExclusiveAwarePattern eachInput: input) {
|
for (ExclusiveAwarePattern eachInput: input) {
|
||||||
|
|||||||
@ -15,6 +15,7 @@ import com.pmease.commons.jetty.extensionpoints.ServletContextConfigurator;
|
|||||||
import com.pmease.commons.loader.AbstractPlugin;
|
import com.pmease.commons.loader.AbstractPlugin;
|
||||||
import com.pmease.commons.persistence.AbstractEntity;
|
import com.pmease.commons.persistence.AbstractEntity;
|
||||||
import com.pmease.commons.persistence.extensionpoints.ModelContribution;
|
import com.pmease.commons.persistence.extensionpoints.ModelContribution;
|
||||||
|
import com.pmease.commons.util.ClassUtils;
|
||||||
import com.pmease.gitop.core.model.User;
|
import com.pmease.gitop.core.model.User;
|
||||||
import com.pmease.gitop.core.web.asset.AssetLocator;
|
import com.pmease.gitop.core.web.asset.AssetLocator;
|
||||||
|
|
||||||
@ -42,7 +43,7 @@ public class CorePlugin extends AbstractPlugin {
|
|||||||
public Collection<Class<? extends AbstractEntity>> getModelClasses() {
|
public Collection<Class<? extends AbstractEntity>> getModelClasses() {
|
||||||
Collection<Class<? extends AbstractEntity>> modelClasses =
|
Collection<Class<? extends AbstractEntity>> modelClasses =
|
||||||
new HashSet<Class<? extends AbstractEntity>>();
|
new HashSet<Class<? extends AbstractEntity>>();
|
||||||
modelClasses.add(User.class);
|
modelClasses.addAll(ClassUtils.findSubClasses(AbstractEntity.class, User.class));
|
||||||
return modelClasses;
|
return modelClasses;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
package com.pmease.gitop.core.gatekeeper;
|
package com.pmease.gitop.core.gatekeeper;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
public abstract class AbstractGateKeeper implements GateKeeper {
|
public abstract class AbstractGateKeeper implements GateKeeper {
|
||||||
@ -9,4 +11,36 @@ public abstract class AbstractGateKeeper implements GateKeeper {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected CheckResult accept(String reason) {
|
||||||
|
return new CheckResult.Accept(reason);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected CheckResult reject(String reason) {
|
||||||
|
return new CheckResult.Reject(reason);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected CheckResult pending(String reason) {
|
||||||
|
return new CheckResult.Pending(reason);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected CheckResult block(String reason) {
|
||||||
|
return new CheckResult.Block(reason);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected CheckResult accept(List<String> reasons) {
|
||||||
|
return new CheckResult.Accept(reasons);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected CheckResult reject(List<String> reasons) {
|
||||||
|
return new CheckResult.Reject(reasons);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected CheckResult pending(List<String> reasons) {
|
||||||
|
return new CheckResult.Pending(reasons);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected CheckResult block(List<String> reasons) {
|
||||||
|
return new CheckResult.Block(reasons);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,7 +6,7 @@ public class AlwaysAccept extends AbstractGateKeeper {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CheckResult check(MergeRequest request) {
|
public CheckResult check(MergeRequest request) {
|
||||||
return CheckResult.ACCEPT;
|
return accept("always");
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import com.pmease.commons.util.trimmable.AndOrConstruct;
|
|||||||
import com.pmease.commons.util.trimmable.TrimUtils;
|
import com.pmease.commons.util.trimmable.TrimUtils;
|
||||||
import com.pmease.gitop.core.model.MergeRequest;
|
import com.pmease.gitop.core.model.MergeRequest;
|
||||||
|
|
||||||
public class AndGateKeeper implements GateKeeper {
|
public class AndGateKeeper extends AbstractGateKeeper {
|
||||||
|
|
||||||
private List<GateKeeper> gateKeepers = new ArrayList<GateKeeper>();
|
private List<GateKeeper> gateKeepers = new ArrayList<GateKeeper>();
|
||||||
|
|
||||||
@ -21,20 +21,27 @@ public class AndGateKeeper implements GateKeeper {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CheckResult check(MergeRequest request) {
|
public CheckResult check(MergeRequest request) {
|
||||||
boolean pending = false;
|
List<String> pendingReasons = new ArrayList<String>();
|
||||||
|
List<String> acceptReasons = new ArrayList<String>();
|
||||||
|
|
||||||
for (GateKeeper each: getGateKeepers()) {
|
for (GateKeeper each: getGateKeepers()) {
|
||||||
CheckResult result = each.check(request);
|
CheckResult result = each.check(request);
|
||||||
if (result == CheckResult.REJECT || result == CheckResult.PENDING_BLOCK)
|
if (result.isAccept()) {
|
||||||
|
acceptReasons.addAll(result.getReasons());
|
||||||
|
} else if (result.isReject()) {
|
||||||
return result;
|
return result;
|
||||||
else if (result == CheckResult.PENDING)
|
} else if (result.isBlock()) {
|
||||||
pending = true;
|
result.getReasons().addAll(pendingReasons);
|
||||||
|
return result;
|
||||||
|
} else {
|
||||||
|
pendingReasons.addAll(result.getReasons());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pending)
|
if (!pendingReasons.isEmpty())
|
||||||
return CheckResult.PENDING;
|
return pending(pendingReasons);
|
||||||
else
|
else
|
||||||
return CheckResult.ACCEPT;
|
return accept(acceptReasons);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@ -20,8 +20,14 @@ public class ApprovedByAuthorizedUsers extends AbstractGateKeeper {
|
|||||||
|
|
||||||
CheckResult result = or.check(request);
|
CheckResult result = or.check(request);
|
||||||
|
|
||||||
if (result.isPending()) {
|
if (result.isAccept()) {
|
||||||
request.requestVote(authorizedUsers);
|
result = accept("Approved by user with push permission.");
|
||||||
|
} else if (result.isReject()) {
|
||||||
|
result = reject("Not approved by any users with push permission.");
|
||||||
|
} else if (result.isPending()) {
|
||||||
|
result = pending("To be approved by someone with push permission.");
|
||||||
|
} else {
|
||||||
|
result = block("To be approved by someone with push permission.");
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,7 +11,14 @@ public class ApprovedByMajoritiesOfSpecifiedTeam extends TeamAwareGateKeeper {
|
|||||||
gateKeeper.setRequireVoteOfAllMembers(true);
|
gateKeeper.setRequireVoteOfAllMembers(true);
|
||||||
gateKeeper.setTeamId(getTeamId());
|
gateKeeper.setTeamId(getTeamId());
|
||||||
|
|
||||||
return gateKeeper.check(request);
|
CheckResult result = gateKeeper.check(request);
|
||||||
|
|
||||||
|
if (result.isAccept())
|
||||||
|
result = accept("Approved by majorities of team '" + getTeam().getName() + "'.");
|
||||||
|
else if (result.isReject())
|
||||||
|
result = reject("Not approved by majorities of team '" + getTeam().getName() + "'.");
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,6 +3,7 @@ package com.pmease.gitop.core.gatekeeper;
|
|||||||
import com.pmease.commons.util.EasySet;
|
import com.pmease.commons.util.EasySet;
|
||||||
import com.pmease.gitop.core.model.MergeRequest;
|
import com.pmease.gitop.core.model.MergeRequest;
|
||||||
import com.pmease.gitop.core.model.User;
|
import com.pmease.gitop.core.model.User;
|
||||||
|
import com.pmease.gitop.core.model.Vote;
|
||||||
|
|
||||||
public class ApprovedByRepositoryOwner extends AbstractGateKeeper {
|
public class ApprovedByRepositoryOwner extends AbstractGateKeeper {
|
||||||
|
|
||||||
@ -10,10 +11,16 @@ public class ApprovedByRepositoryOwner extends AbstractGateKeeper {
|
|||||||
public CheckResult check(MergeRequest request) {
|
public CheckResult check(MergeRequest request) {
|
||||||
User repositoryOwner = request.getDestination().getRepository().getOwner();
|
User repositoryOwner = request.getDestination().getRepository().getOwner();
|
||||||
|
|
||||||
CheckResult result = repositoryOwner.checkApprovalSince(request.getBaseUpdate());
|
Vote.Result result = repositoryOwner.checkVoteSince(request.getBaseUpdate());
|
||||||
if (result.isPending())
|
|
||||||
request.requestVote(EasySet.of(repositoryOwner));
|
if (result == null) {
|
||||||
return result;
|
request.inviteToVote(EasySet.of(repositoryOwner), 1);
|
||||||
|
return pending("To be approved by user '" + repositoryOwner.getName() + "'.");
|
||||||
|
} else if (result.isAccept()) {
|
||||||
|
return accept("Approved by user '" + repositoryOwner.getName() + "'.");
|
||||||
|
} else {
|
||||||
|
return reject("Rejected by user '" + repositoryOwner.getName() + "'.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,12 +5,10 @@ import java.util.HashSet;
|
|||||||
|
|
||||||
import javax.validation.constraints.Min;
|
import javax.validation.constraints.Min;
|
||||||
|
|
||||||
import com.pmease.commons.loader.AppLoader;
|
|
||||||
import com.pmease.gitop.core.manager.TeamManager;
|
|
||||||
import com.pmease.gitop.core.model.MergeRequest;
|
import com.pmease.gitop.core.model.MergeRequest;
|
||||||
import com.pmease.gitop.core.model.Team;
|
|
||||||
import com.pmease.gitop.core.model.TeamMembership;
|
import com.pmease.gitop.core.model.TeamMembership;
|
||||||
import com.pmease.gitop.core.model.User;
|
import com.pmease.gitop.core.model.User;
|
||||||
|
import com.pmease.gitop.core.model.Vote;
|
||||||
|
|
||||||
public class ApprovedBySpecifiedTeam extends TeamAwareGateKeeper {
|
public class ApprovedBySpecifiedTeam extends TeamAwareGateKeeper {
|
||||||
|
|
||||||
@ -27,41 +25,31 @@ public class ApprovedBySpecifiedTeam extends TeamAwareGateKeeper {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CheckResult check(MergeRequest request) {
|
public CheckResult check(MergeRequest request) {
|
||||||
TeamManager teamManager = AppLoader.getInstance(TeamManager.class);
|
|
||||||
Team team = teamManager.load(getTeamId());
|
|
||||||
|
|
||||||
Collection<User> members = new HashSet<User>();
|
Collection<User> members = new HashSet<User>();
|
||||||
for (TeamMembership membership: team.getMemberships())
|
for (TeamMembership membership: getTeam().getMemberships())
|
||||||
members.add(membership.getUser());
|
members.add(membership.getUser());
|
||||||
|
|
||||||
boolean pendingBlock = false;
|
|
||||||
|
|
||||||
int approvals = 0;
|
int approvals = 0;
|
||||||
int pendings = 0;
|
int pendings = 0;
|
||||||
for (User member: members) {
|
for (User member: members) {
|
||||||
CheckResult result = member.checkApprovalSince(request.getBaseUpdate());
|
Vote.Result result = member.checkVoteSince(request.getBaseUpdate());
|
||||||
if (result == CheckResult.ACCEPT) {
|
if (result == null) {
|
||||||
|
pendings++;
|
||||||
|
} else if (result.isAccept()) {
|
||||||
approvals++;
|
approvals++;
|
||||||
} else if (result == CheckResult.PENDING_BLOCK) {
|
|
||||||
pendingBlock = true;
|
|
||||||
pendings++;
|
|
||||||
} else if (result == CheckResult.PENDING) {
|
|
||||||
pendings++;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (approvals >= getLeastApprovals()) {
|
if (approvals >= getLeastApprovals()) {
|
||||||
return CheckResult.ACCEPT;
|
return accept("Get at least " + getLeastApprovals() + " approvals from team '" + getTeam().getName() + "'.");
|
||||||
} else if (getLeastApprovals() - approvals > pendings) {
|
} else if (getLeastApprovals() - approvals > pendings) {
|
||||||
return CheckResult.REJECT;
|
return reject("Can not get at least " + getLeastApprovals() + " approvals from team '" + getTeam().getName() + "'.");
|
||||||
} else {
|
} else {
|
||||||
for (int i=0; i<getLeastApprovals()-approvals; i++)
|
int lackApprovals = getLeastApprovals() - approvals;
|
||||||
request.requestVote(members);
|
|
||||||
|
|
||||||
if (pendingBlock)
|
request.inviteToVote(members, lackApprovals);
|
||||||
return CheckResult.PENDING_BLOCK;
|
|
||||||
else
|
return pending("To be approved by " + lackApprovals + " user(s) from team '" + getTeam().getName() + "'.");
|
||||||
return CheckResult.PENDING;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -5,8 +5,9 @@ import com.pmease.commons.util.EasySet;
|
|||||||
import com.pmease.gitop.core.manager.UserManager;
|
import com.pmease.gitop.core.manager.UserManager;
|
||||||
import com.pmease.gitop.core.model.MergeRequest;
|
import com.pmease.gitop.core.model.MergeRequest;
|
||||||
import com.pmease.gitop.core.model.User;
|
import com.pmease.gitop.core.model.User;
|
||||||
|
import com.pmease.gitop.core.model.Vote;
|
||||||
|
|
||||||
public class ApprovedBySpecifiedUser implements GateKeeper {
|
public class ApprovedBySpecifiedUser extends AbstractGateKeeper {
|
||||||
|
|
||||||
private Long userId;
|
private Long userId;
|
||||||
|
|
||||||
@ -23,11 +24,15 @@ public class ApprovedBySpecifiedUser implements GateKeeper {
|
|||||||
UserManager userManager = AppLoader.getInstance(UserManager.class);
|
UserManager userManager = AppLoader.getInstance(UserManager.class);
|
||||||
User user = userManager.load(getUserId());
|
User user = userManager.load(getUserId());
|
||||||
|
|
||||||
CheckResult result = user.checkApprovalSince(request.getBaseUpdate());
|
Vote.Result result = user.checkVoteSince(request.getBaseUpdate());
|
||||||
if (result.isPending())
|
if (result == null) {
|
||||||
request.requestVote(EasySet.of(user));
|
request.inviteToVote(EasySet.of(user), 1);
|
||||||
return result;
|
return pending("To be approved by user '" + user.getName() + "'.");
|
||||||
|
} else if (result.isAccept()) {
|
||||||
|
return accept("Approved by user '" + user.getName() + "'.");
|
||||||
|
} else {
|
||||||
|
return reject("Rejected by user '" + user.getName() + "'.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@ -0,0 +1,164 @@
|
|||||||
|
package com.pmease.gitop.core.gatekeeper;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import com.pmease.commons.util.EasyList;
|
||||||
|
|
||||||
|
public abstract class CheckResult {
|
||||||
|
|
||||||
|
private final List<String> reasons;
|
||||||
|
|
||||||
|
public CheckResult(List<String> reasons) {
|
||||||
|
this.reasons = reasons;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CheckResult(String reason) {
|
||||||
|
this.reasons = EasyList.of(reason);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getReasons() {
|
||||||
|
return reasons;
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract boolean isAccept();
|
||||||
|
|
||||||
|
public abstract boolean isReject();
|
||||||
|
|
||||||
|
public abstract boolean isPending();
|
||||||
|
|
||||||
|
public abstract boolean isBlock();
|
||||||
|
|
||||||
|
/* accept the merge request. */
|
||||||
|
public static class Accept extends CheckResult {
|
||||||
|
|
||||||
|
public Accept(List<String> reasons) {
|
||||||
|
super(reasons);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Accept(String reason) {
|
||||||
|
super(reason);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isPending() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isAccept() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isReject() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isBlock() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/* reject the merge request. */
|
||||||
|
public static class Reject extends CheckResult {
|
||||||
|
|
||||||
|
public Reject(List<String> reasons) {
|
||||||
|
super(reasons);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Reject(String reason) {
|
||||||
|
super(reason);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isAccept() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isReject() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isPending() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isBlock() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/* merge request acceptance check is pending and result is unknown yet */
|
||||||
|
public static class Pending extends CheckResult {
|
||||||
|
|
||||||
|
public Pending(List<String> reasons) {
|
||||||
|
super(reasons);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Pending(String reason) {
|
||||||
|
super(reason);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isAccept() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isReject() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isPending() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isBlock() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* same as Pending, but followed gate keeper should not be checked unless result
|
||||||
|
* of this gate keeper has been determined.
|
||||||
|
*/
|
||||||
|
public static class Block extends CheckResult {
|
||||||
|
|
||||||
|
public Block(List<String> reasons) {
|
||||||
|
super(reasons);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Block(String reason) {
|
||||||
|
super(reason);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isPending() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isAccept() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isReject() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isBlock() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
@ -5,45 +5,5 @@ import com.pmease.gitop.core.model.MergeRequest;
|
|||||||
|
|
||||||
public interface GateKeeper extends Trimmable {
|
public interface GateKeeper extends Trimmable {
|
||||||
|
|
||||||
public enum CheckResult {
|
|
||||||
/* accept the merge request. */
|
|
||||||
ACCEPT {
|
|
||||||
@Override
|
|
||||||
public boolean isPending() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/* reject the merge request. */
|
|
||||||
REJECT {
|
|
||||||
@Override
|
|
||||||
public boolean isPending() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/* merge request acceptance check is pending and result is unknown yet */
|
|
||||||
PENDING {
|
|
||||||
@Override
|
|
||||||
public boolean isPending() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/*
|
|
||||||
* same as PENDING, but followed gate keeper should not be checked unless result
|
|
||||||
* of this gate keeper has been determined.
|
|
||||||
*/
|
|
||||||
PENDING_BLOCK {
|
|
||||||
@Override
|
|
||||||
public boolean isPending() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
public abstract boolean isPending();
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
CheckResult check(MergeRequest request);
|
CheckResult check(MergeRequest request);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,12 +5,10 @@ import java.util.HashSet;
|
|||||||
|
|
||||||
import javax.validation.constraints.Min;
|
import javax.validation.constraints.Min;
|
||||||
|
|
||||||
import com.pmease.gitop.core.Gitop;
|
|
||||||
import com.pmease.gitop.core.manager.TeamManager;
|
|
||||||
import com.pmease.gitop.core.model.MergeRequest;
|
import com.pmease.gitop.core.model.MergeRequest;
|
||||||
import com.pmease.gitop.core.model.Team;
|
|
||||||
import com.pmease.gitop.core.model.TeamMembership;
|
import com.pmease.gitop.core.model.TeamMembership;
|
||||||
import com.pmease.gitop.core.model.User;
|
import com.pmease.gitop.core.model.User;
|
||||||
|
import com.pmease.gitop.core.model.Vote;
|
||||||
|
|
||||||
public class GetMinScoreFromSpecifiedTeam extends TeamAwareGateKeeper {
|
public class GetMinScoreFromSpecifiedTeam extends TeamAwareGateKeeper {
|
||||||
|
|
||||||
@ -37,56 +35,46 @@ public class GetMinScoreFromSpecifiedTeam extends TeamAwareGateKeeper {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CheckResult check(MergeRequest request) {
|
public CheckResult check(MergeRequest request) {
|
||||||
TeamManager teamManager = Gitop.getInstance(TeamManager.class);
|
|
||||||
Team team = teamManager.load(getTeamId());
|
|
||||||
Collection<User> members = new HashSet<User>();
|
Collection<User> members = new HashSet<User>();
|
||||||
for (TeamMembership membership: team.getMemberships())
|
for (TeamMembership membership: getTeam().getMemberships())
|
||||||
members.add(membership.getUser());
|
members.add(membership.getUser());
|
||||||
|
|
||||||
boolean pendingBlock = false;
|
|
||||||
|
|
||||||
int score = 0;
|
int score = 0;
|
||||||
int pending = 0;
|
int pendings = 0;
|
||||||
|
|
||||||
for (User member: members) {
|
for (User member: members) {
|
||||||
CheckResult result = member.checkApprovalSince(request.getBaseUpdate());
|
Vote.Result result = member.checkVoteSince(request.getBaseUpdate());
|
||||||
if (result == CheckResult.ACCEPT) {
|
if (result == null) {
|
||||||
|
pendings++;
|
||||||
|
} else if (result.isAccept()) {
|
||||||
score++;
|
score++;
|
||||||
} else if (result == CheckResult.REJECT) {
|
|
||||||
score--;
|
|
||||||
} else if (result == CheckResult.PENDING_BLOCK) {
|
|
||||||
pendingBlock = true;
|
|
||||||
pending++;
|
|
||||||
} else {
|
} else {
|
||||||
pending++;
|
score--;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (score + pending < getMinScore()) {
|
if (score + pendings < getMinScore()) {
|
||||||
return CheckResult.REJECT;
|
return reject("Can not get min score " + getMinScore() + " from team '" + getTeam().getName() + "'.");
|
||||||
} else {
|
} else {
|
||||||
int lackApprovals;
|
int lackApprovals;
|
||||||
if (isRequireVoteOfAllMembers()) {
|
if (isRequireVoteOfAllMembers()) {
|
||||||
if (score - pending >= getMinScore())
|
if (score - pendings >= getMinScore())
|
||||||
return CheckResult.ACCEPT;
|
return accept("Get min score " + getMinScore() + " from team '" + getTeam().getName() + "'.");
|
||||||
int temp = getMinScore() + pending - score;
|
int temp = getMinScore() + pendings - score;
|
||||||
lackApprovals = temp / 2;
|
lackApprovals = temp / 2;
|
||||||
if (temp % 2 != 0)
|
if (temp % 2 != 0)
|
||||||
lackApprovals++;
|
lackApprovals++;
|
||||||
if (lackApprovals > pending)
|
if (lackApprovals > pendings)
|
||||||
lackApprovals = pending;
|
lackApprovals = pendings;
|
||||||
} else {
|
} else {
|
||||||
if (score >= getMinScore())
|
if (score >= getMinScore())
|
||||||
return CheckResult.ACCEPT;
|
return accept("Get min score " + getMinScore() + " from team '" + getTeam().getName() + "'.");
|
||||||
lackApprovals = getMinScore() - score;
|
lackApprovals = getMinScore() - score;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i=0; i<lackApprovals; i++)
|
request.inviteToVote(members, lackApprovals);
|
||||||
request.requestVote(members);
|
|
||||||
|
|
||||||
if (pendingBlock)
|
return pending("To be approved by " + lackApprovals + " users from team '" + getTeam().getName() + ".");
|
||||||
return CheckResult.PENDING_BLOCK;
|
|
||||||
else
|
|
||||||
return CheckResult.PENDING;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -7,9 +7,9 @@ public class HasSourceBranch extends AbstractGateKeeper {
|
|||||||
@Override
|
@Override
|
||||||
public CheckResult check(MergeRequest request) {
|
public CheckResult check(MergeRequest request) {
|
||||||
if (request.getSource() != null)
|
if (request.getSource() != null)
|
||||||
return CheckResult.ACCEPT;
|
return accept("Associated with source branch.");
|
||||||
else
|
else
|
||||||
return CheckResult.REJECT;
|
return reject("Not associated with source branch.");
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -31,18 +31,18 @@ public class IfThenGateKeeper extends AbstractGateKeeper {
|
|||||||
@Override
|
@Override
|
||||||
public CheckResult check(MergeRequest request) {
|
public CheckResult check(MergeRequest request) {
|
||||||
CheckResult ifResult = getIfGate().check(request);
|
CheckResult ifResult = getIfGate().check(request);
|
||||||
if (ifResult == CheckResult.ACCEPT) {
|
if (ifResult.isAccept()) {
|
||||||
return getThenGate().check(request);
|
return getThenGate().check(request);
|
||||||
} else if (ifResult == CheckResult.REJECT) {
|
} else if (ifResult.isReject()) {
|
||||||
return CheckResult.ACCEPT;
|
return accept(ifResult.getReasons());
|
||||||
} else if (ifResult == CheckResult.PENDING_BLOCK) {
|
} else if (ifResult.isBlock()) {
|
||||||
return CheckResult.PENDING_BLOCK;
|
return ifResult;
|
||||||
} else {
|
} else {
|
||||||
CheckResult thenResult = getThenGate().check(request);
|
CheckResult thenResult = getThenGate().check(request);
|
||||||
if (thenResult == CheckResult.ACCEPT)
|
if (thenResult.isAccept())
|
||||||
return CheckResult.ACCEPT;
|
return thenResult;
|
||||||
else
|
else
|
||||||
return CheckResult.PENDING;
|
return ifResult;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -7,9 +7,9 @@ public class IsFastForward extends AbstractGateKeeper {
|
|||||||
@Override
|
@Override
|
||||||
public CheckResult check(MergeRequest request) {
|
public CheckResult check(MergeRequest request) {
|
||||||
if (request.isFastForward())
|
if (request.isFastForward())
|
||||||
return CheckResult.ACCEPT;
|
return accept("Is fast-forward.");
|
||||||
else
|
else
|
||||||
return CheckResult.REJECT;
|
return reject("None fast-forward.");
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,7 +6,7 @@ public class NeverAccept extends AbstractGateKeeper {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CheckResult check(MergeRequest request) {
|
public CheckResult check(MergeRequest request) {
|
||||||
return CheckResult.REJECT;
|
return reject("never");
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,26 +1,22 @@
|
|||||||
package com.pmease.gitop.core.gatekeeper;
|
package com.pmease.gitop.core.gatekeeper;
|
||||||
|
|
||||||
import com.pmease.commons.loader.AppLoader;
|
|
||||||
import com.pmease.gitop.core.manager.TeamManager;
|
|
||||||
import com.pmease.gitop.core.model.MergeRequest;
|
import com.pmease.gitop.core.model.MergeRequest;
|
||||||
import com.pmease.gitop.core.model.Team;
|
|
||||||
import com.pmease.gitop.core.model.TeamMembership;
|
import com.pmease.gitop.core.model.TeamMembership;
|
||||||
|
import com.pmease.gitop.core.model.Vote;
|
||||||
|
|
||||||
public class NoRejectionBySpecifiedTeam extends TeamAwareGateKeeper {
|
public class NoRejectionBySpecifiedTeam extends TeamAwareGateKeeper {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CheckResult check(MergeRequest request) {
|
public CheckResult check(MergeRequest request) {
|
||||||
TeamManager teamManager = AppLoader.getInstance(TeamManager.class);
|
|
||||||
Team team = teamManager.load(getTeamId());
|
|
||||||
|
|
||||||
for (TeamMembership membership: team.getMemberships()) {
|
for (TeamMembership membership: getTeam().getMemberships()) {
|
||||||
CheckResult result = membership.getUser().checkApprovalSince(request.getBaseUpdate());
|
Vote.Result result = membership.getUser().checkVoteSince(request.getBaseUpdate());
|
||||||
if (result == CheckResult.REJECT) {
|
if (result.isReject()) {
|
||||||
return CheckResult.REJECT;
|
return reject("Rejected by user '" + membership.getUser().getName() + "'.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return CheckResult.ACCEPT;
|
return accept("Not rejected by anyone from team '" + getTeam().getName() + ".");
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,7 +2,7 @@ package com.pmease.gitop.core.gatekeeper;
|
|||||||
|
|
||||||
import com.pmease.gitop.core.model.MergeRequest;
|
import com.pmease.gitop.core.model.MergeRequest;
|
||||||
|
|
||||||
public class NotGateKeeper implements GateKeeper {
|
public class NotGateKeeper extends AbstractGateKeeper {
|
||||||
|
|
||||||
private GateKeeper gateKeeper;
|
private GateKeeper gateKeeper;
|
||||||
|
|
||||||
@ -18,12 +18,12 @@ public class NotGateKeeper implements GateKeeper {
|
|||||||
public CheckResult check(MergeRequest request) {
|
public CheckResult check(MergeRequest request) {
|
||||||
CheckResult result = getGateKeeper().check(request);
|
CheckResult result = getGateKeeper().check(request);
|
||||||
|
|
||||||
if (result == CheckResult.PENDING)
|
if (result.isAccept())
|
||||||
return result;
|
return reject(result.getReasons());
|
||||||
else if (result == CheckResult.ACCEPT)
|
else if (result.isReject())
|
||||||
return CheckResult.REJECT;
|
return accept(result.getReasons());
|
||||||
else
|
else
|
||||||
return CheckResult.ACCEPT;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@ -8,7 +8,7 @@ import com.pmease.commons.util.trimmable.TrimUtils;
|
|||||||
import com.pmease.commons.util.trimmable.Trimmable;
|
import com.pmease.commons.util.trimmable.Trimmable;
|
||||||
import com.pmease.gitop.core.model.MergeRequest;
|
import com.pmease.gitop.core.model.MergeRequest;
|
||||||
|
|
||||||
public class OrGateKeeper implements GateKeeper {
|
public class OrGateKeeper extends AbstractGateKeeper {
|
||||||
|
|
||||||
private List<GateKeeper> gateKeepers = new ArrayList<GateKeeper>();
|
private List<GateKeeper> gateKeepers = new ArrayList<GateKeeper>();
|
||||||
|
|
||||||
@ -22,20 +22,27 @@ public class OrGateKeeper implements GateKeeper {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CheckResult check(MergeRequest request) {
|
public CheckResult check(MergeRequest request) {
|
||||||
boolean pending = false;
|
List<String> pendingReasons = new ArrayList<String>();
|
||||||
|
List<String> rejectReasons = new ArrayList<String>();
|
||||||
|
|
||||||
for (GateKeeper each: getGateKeepers()) {
|
for (GateKeeper each: getGateKeepers()) {
|
||||||
CheckResult result = each.check(request);
|
CheckResult result = each.check(request);
|
||||||
if (result == CheckResult.ACCEPT || result == CheckResult.PENDING_BLOCK)
|
if (result.isReject()) {
|
||||||
|
rejectReasons.addAll(result.getReasons());
|
||||||
|
} else if (result.isAccept()) {
|
||||||
return result;
|
return result;
|
||||||
else if (result == CheckResult.PENDING)
|
} else if (result.isBlock()) {
|
||||||
pending = true;
|
result.getReasons().addAll(pendingReasons);
|
||||||
|
return result;
|
||||||
|
} else {
|
||||||
|
pendingReasons.addAll(result.getReasons());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pending)
|
if (!pendingReasons.isEmpty())
|
||||||
return CheckResult.PENDING;
|
return pending(pendingReasons);
|
||||||
else
|
else
|
||||||
return CheckResult.REJECT;
|
return reject(rejectReasons);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@ -11,7 +11,7 @@ import com.pmease.gitop.core.manager.BranchManager;
|
|||||||
import com.pmease.gitop.core.model.MergeRequest;
|
import com.pmease.gitop.core.model.MergeRequest;
|
||||||
import com.pmease.gitop.core.model.Repository;
|
import com.pmease.gitop.core.model.Repository;
|
||||||
|
|
||||||
public class SubmittedToSpecifiedBranch implements GateKeeper {
|
public class SubmittedToSpecifiedBranch extends AbstractGateKeeper {
|
||||||
|
|
||||||
private String branchPatterns;
|
private String branchPatterns;
|
||||||
|
|
||||||
@ -31,10 +31,12 @@ public class SubmittedToSpecifiedBranch implements GateKeeper {
|
|||||||
EntityMatcher entityMatcher = new EntityMatcher(entityLoader, new WildcardPathMatcher());
|
EntityMatcher entityMatcher = new EntityMatcher(entityLoader, new WildcardPathMatcher());
|
||||||
PatternSetMatcher patternSetMatcher = new PatternSetMatcher(entityMatcher);
|
PatternSetMatcher patternSetMatcher = new PatternSetMatcher(entityMatcher);
|
||||||
|
|
||||||
|
EntityPatternSet patternSet = EntityPatternSet.fromStored(getBranchPatterns(), entityLoader);
|
||||||
|
|
||||||
if (patternSetMatcher.matches(getBranchPatterns(), request.getDestination().getName()))
|
if (patternSetMatcher.matches(getBranchPatterns(), request.getDestination().getName()))
|
||||||
return CheckResult.ACCEPT;
|
return accept("Target branch matches pattern '" + patternSet + "'.");
|
||||||
else
|
else
|
||||||
return CheckResult.REJECT;
|
return reject("Target branch does not match pattern '" + patternSet + "'.");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@ -7,9 +7,9 @@ public class SubmittedViaPush extends AbstractGateKeeper {
|
|||||||
@Override
|
@Override
|
||||||
public CheckResult check(MergeRequest request) {
|
public CheckResult check(MergeRequest request) {
|
||||||
if (request.isAutoCreated())
|
if (request.isAutoCreated())
|
||||||
return CheckResult.ACCEPT;
|
return accept("Submitted via push.");
|
||||||
else
|
else
|
||||||
return CheckResult.REJECT;
|
return reject("Not submitted via push.");
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,6 +2,7 @@ package com.pmease.gitop.core.gatekeeper;
|
|||||||
|
|
||||||
import com.pmease.gitop.core.Gitop;
|
import com.pmease.gitop.core.Gitop;
|
||||||
import com.pmease.gitop.core.manager.TeamManager;
|
import com.pmease.gitop.core.manager.TeamManager;
|
||||||
|
import com.pmease.gitop.core.model.Team;
|
||||||
|
|
||||||
public abstract class TeamAwareGateKeeper extends AbstractGateKeeper {
|
public abstract class TeamAwareGateKeeper extends AbstractGateKeeper {
|
||||||
|
|
||||||
@ -15,6 +16,10 @@ public abstract class TeamAwareGateKeeper extends AbstractGateKeeper {
|
|||||||
this.teamId = teamId;
|
this.teamId = teamId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Team getTeam() {
|
||||||
|
return Gitop.getInstance(TeamManager.class).load(getTeamId());
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Object trim(Object context) {
|
public Object trim(Object context) {
|
||||||
TeamManager teamManager = Gitop.getInstance(TeamManager.class);
|
TeamManager teamManager = Gitop.getInstance(TeamManager.class);
|
||||||
|
|||||||
@ -46,12 +46,12 @@ public class TouchSpecifiedFiles extends AbstractGateKeeper {
|
|||||||
for (String file: touchedFiles) {
|
for (String file: touchedFiles) {
|
||||||
if (WildcardUtils.matchPath(getFilePaths(), file)) {
|
if (WildcardUtils.matchPath(getFilePaths(), file)) {
|
||||||
request.setBaseUpdate(update);
|
request.setBaseUpdate(update);
|
||||||
return CheckResult.ACCEPT;
|
return accept("Touched files match pattern '" + getFilePaths() + "'.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return CheckResult.REJECT;
|
return reject("No touched files match pattern '" + getFilePaths() + "'.");
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,15 +0,0 @@
|
|||||||
package com.pmease.gitop.core.manager;
|
|
||||||
|
|
||||||
import com.google.inject.ImplementedBy;
|
|
||||||
import com.pmease.commons.persistence.dao.GenericDao;
|
|
||||||
import com.pmease.gitop.core.manager.impl.DefaultPendingVoteManager;
|
|
||||||
import com.pmease.gitop.core.model.MergeRequest;
|
|
||||||
import com.pmease.gitop.core.model.PendingVote;
|
|
||||||
import com.pmease.gitop.core.model.User;
|
|
||||||
|
|
||||||
@ImplementedBy(DefaultPendingVoteManager.class)
|
|
||||||
public interface PendingVoteManager extends GenericDao<PendingVote> {
|
|
||||||
|
|
||||||
PendingVote find(User reviewer, MergeRequest request);
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -0,0 +1,15 @@
|
|||||||
|
package com.pmease.gitop.core.manager;
|
||||||
|
|
||||||
|
import com.google.inject.ImplementedBy;
|
||||||
|
import com.pmease.commons.persistence.dao.GenericDao;
|
||||||
|
import com.pmease.gitop.core.manager.impl.DefaultVoteInvitationManager;
|
||||||
|
import com.pmease.gitop.core.model.MergeRequest;
|
||||||
|
import com.pmease.gitop.core.model.VoteInvitation;
|
||||||
|
import com.pmease.gitop.core.model.User;
|
||||||
|
|
||||||
|
@ImplementedBy(DefaultVoteInvitationManager.class)
|
||||||
|
public interface VoteInvitationManager extends GenericDao<VoteInvitation> {
|
||||||
|
|
||||||
|
VoteInvitation find(User reviewer, MergeRequest request);
|
||||||
|
|
||||||
|
}
|
||||||
@ -1,41 +0,0 @@
|
|||||||
package com.pmease.gitop.core.manager.impl;
|
|
||||||
|
|
||||||
import javax.inject.Provider;
|
|
||||||
import javax.inject.Singleton;
|
|
||||||
|
|
||||||
import org.hibernate.Session;
|
|
||||||
import org.hibernate.criterion.Criterion;
|
|
||||||
import org.hibernate.criterion.Restrictions;
|
|
||||||
|
|
||||||
import com.pmease.commons.persistence.Transactional;
|
|
||||||
import com.pmease.commons.persistence.dao.DefaultGenericDao;
|
|
||||||
import com.pmease.commons.persistence.dao.GeneralDao;
|
|
||||||
import com.pmease.gitop.core.manager.PendingVoteManager;
|
|
||||||
import com.pmease.gitop.core.model.MergeRequest;
|
|
||||||
import com.pmease.gitop.core.model.PendingVote;
|
|
||||||
import com.pmease.gitop.core.model.User;
|
|
||||||
|
|
||||||
@Singleton
|
|
||||||
public class DefaultPendingVoteManager extends DefaultGenericDao<PendingVote> implements PendingVoteManager {
|
|
||||||
|
|
||||||
public DefaultPendingVoteManager(GeneralDao generalDao, Provider<Session> sessionProvider) {
|
|
||||||
super(generalDao, sessionProvider);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Transactional
|
|
||||||
@Override
|
|
||||||
public PendingVote find(User reviewer, MergeRequest request) {
|
|
||||||
return find(new Criterion[]{Restrictions.eq("reviewer", reviewer), Restrictions.eq("request", request)});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Transactional
|
|
||||||
@Override
|
|
||||||
public void save(PendingVote pendingVote) {
|
|
||||||
if (pendingVote.getId() == null) {
|
|
||||||
pendingVote.getRequest().getPendingVotes().add(pendingVote);
|
|
||||||
pendingVote.getReviewer().getPendingVotes().add(pendingVote);
|
|
||||||
}
|
|
||||||
super.save(pendingVote);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -0,0 +1,49 @@
|
|||||||
|
package com.pmease.gitop.core.manager.impl;
|
||||||
|
|
||||||
|
import javax.inject.Provider;
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
|
||||||
|
import org.hibernate.Session;
|
||||||
|
import org.hibernate.criterion.Criterion;
|
||||||
|
import org.hibernate.criterion.Restrictions;
|
||||||
|
|
||||||
|
import com.pmease.commons.persistence.Transactional;
|
||||||
|
import com.pmease.commons.persistence.dao.DefaultGenericDao;
|
||||||
|
import com.pmease.commons.persistence.dao.GeneralDao;
|
||||||
|
import com.pmease.gitop.core.manager.VoteInvitationManager;
|
||||||
|
import com.pmease.gitop.core.model.MergeRequest;
|
||||||
|
import com.pmease.gitop.core.model.VoteInvitation;
|
||||||
|
import com.pmease.gitop.core.model.User;
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
public class DefaultVoteInvitationManager extends DefaultGenericDao<VoteInvitation> implements VoteInvitationManager {
|
||||||
|
|
||||||
|
public DefaultVoteInvitationManager(GeneralDao generalDao, Provider<Session> sessionProvider) {
|
||||||
|
super(generalDao, sessionProvider);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
@Override
|
||||||
|
public VoteInvitation find(User reviewer, MergeRequest request) {
|
||||||
|
return find(new Criterion[]{Restrictions.eq("reviewer", reviewer), Restrictions.eq("request", request)});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
@Override
|
||||||
|
public void save(VoteInvitation voteInvitation) {
|
||||||
|
if (voteInvitation.getId() == null) {
|
||||||
|
voteInvitation.getRequest().getVoteInvitations().add(voteInvitation);
|
||||||
|
voteInvitation.getReviewer().getVoteInvitations().add(voteInvitation);
|
||||||
|
}
|
||||||
|
super.save(voteInvitation);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
@Override
|
||||||
|
public void delete(VoteInvitation voteInvitation) {
|
||||||
|
voteInvitation.getRequest().getVoteInvitations().remove(voteInvitation);
|
||||||
|
voteInvitation.getReviewer().getVoteInvitations().remove(voteInvitation);
|
||||||
|
super.delete(voteInvitation);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -19,6 +19,7 @@ import javax.persistence.OneToMany;
|
|||||||
|
|
||||||
import org.hibernate.annotations.FetchMode;
|
import org.hibernate.annotations.FetchMode;
|
||||||
|
|
||||||
|
import com.google.common.base.Optional;
|
||||||
import com.google.common.base.Preconditions;
|
import com.google.common.base.Preconditions;
|
||||||
import com.pmease.commons.git.CalcMergeBaseCommand;
|
import com.pmease.commons.git.CalcMergeBaseCommand;
|
||||||
import com.pmease.commons.git.CheckAncestorCommand;
|
import com.pmease.commons.git.CheckAncestorCommand;
|
||||||
@ -26,7 +27,9 @@ import com.pmease.commons.git.FindChangedFilesCommand;
|
|||||||
import com.pmease.commons.git.Git;
|
import com.pmease.commons.git.Git;
|
||||||
import com.pmease.commons.persistence.AbstractEntity;
|
import com.pmease.commons.persistence.AbstractEntity;
|
||||||
import com.pmease.gitop.core.Gitop;
|
import com.pmease.gitop.core.Gitop;
|
||||||
import com.pmease.gitop.core.manager.PendingVoteManager;
|
import com.pmease.gitop.core.gatekeeper.CheckResult;
|
||||||
|
import com.pmease.gitop.core.gatekeeper.GateKeeper;
|
||||||
|
import com.pmease.gitop.core.manager.VoteInvitationManager;
|
||||||
import com.pmease.gitop.core.manager.RepositoryManager;
|
import com.pmease.gitop.core.manager.RepositoryManager;
|
||||||
|
|
||||||
@SuppressWarnings("serial")
|
@SuppressWarnings("serial")
|
||||||
@ -55,6 +58,8 @@ public class MergeRequest extends AbstractEntity {
|
|||||||
@Column(nullable=false)
|
@Column(nullable=false)
|
||||||
private Status status = Status.OPEN;
|
private Status status = Status.OPEN;
|
||||||
|
|
||||||
|
private transient Optional<CheckResult> checkResult;
|
||||||
|
|
||||||
private transient Boolean merged;
|
private transient Boolean merged;
|
||||||
|
|
||||||
private transient String mergeBase;
|
private transient String mergeBase;
|
||||||
@ -65,11 +70,13 @@ public class MergeRequest extends AbstractEntity {
|
|||||||
|
|
||||||
private transient MergeRequestUpdate baseUpdate;
|
private transient MergeRequestUpdate baseUpdate;
|
||||||
|
|
||||||
|
private transient Collection<User> potentialVoters;
|
||||||
|
|
||||||
@OneToMany(mappedBy="request")
|
@OneToMany(mappedBy="request")
|
||||||
private Collection<MergeRequestUpdate> updates = new ArrayList<MergeRequestUpdate>();
|
private Collection<MergeRequestUpdate> updates = new ArrayList<MergeRequestUpdate>();
|
||||||
|
|
||||||
@OneToMany(mappedBy="request")
|
@OneToMany(mappedBy="request")
|
||||||
private Collection<PendingVote> pendingVotes = new ArrayList<PendingVote>();
|
private Collection<VoteInvitation> voteInvitations = new ArrayList<VoteInvitation>();
|
||||||
|
|
||||||
public String getTitle() {
|
public String getTitle() {
|
||||||
return title;
|
return title;
|
||||||
@ -115,12 +122,12 @@ public class MergeRequest extends AbstractEntity {
|
|||||||
this.updates = updates;
|
this.updates = updates;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Collection<PendingVote> getPendingVotes() {
|
public Collection<VoteInvitation> getVoteInvitations() {
|
||||||
return pendingVotes;
|
return voteInvitations;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setPendingVotes(Collection<PendingVote> pendingVotes) {
|
public void setVoteInvitations(Collection<VoteInvitation> voteInvitations) {
|
||||||
this.pendingVotes = pendingVotes;
|
this.voteInvitations = voteInvitations;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Status getStatus() {
|
public Status getStatus() {
|
||||||
@ -239,14 +246,60 @@ public class MergeRequest extends AbstractEntity {
|
|||||||
return mergeBase;
|
return mergeBase;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void requestVote(Collection<User> candidates) {
|
/**
|
||||||
Set<User> pendingUsers = new HashSet<User>();
|
* Find potential voters for this request. Potential voters will be
|
||||||
for (PendingVote each: getPendingVotes())
|
* presented with the vote/reject button when they review the request.
|
||||||
pendingUsers.add(each.getReviewer());
|
* However they may not receive vote invitation.
|
||||||
|
* <p>
|
||||||
|
* @return
|
||||||
|
* a collection of potential voters
|
||||||
|
*/
|
||||||
|
public Collection<User> findPotentialVoters() {
|
||||||
|
check(false);
|
||||||
|
return getPotentialVoters();
|
||||||
|
}
|
||||||
|
|
||||||
pendingUsers.retainAll(candidates);
|
private Collection<User> getPotentialVoters() {
|
||||||
|
if (potentialVoters == null)
|
||||||
|
potentialVoters = new HashSet<User>();
|
||||||
|
return potentialVoters;
|
||||||
|
}
|
||||||
|
|
||||||
if (pendingUsers.isEmpty()) {
|
/**
|
||||||
|
* Invite specified number of users to vote for this request.
|
||||||
|
* <p>
|
||||||
|
* @param users
|
||||||
|
* a collection of users to invite users from
|
||||||
|
* @param count
|
||||||
|
* number of users to invite
|
||||||
|
*/
|
||||||
|
public void inviteToVote(final Collection<User> users, int count) {
|
||||||
|
Collection<User> candidates = new HashSet<User>(users);
|
||||||
|
|
||||||
|
// submitter is not allowed to vote for this request
|
||||||
|
candidates.remove(getSubmitter());
|
||||||
|
|
||||||
|
// users already voted for latest update should be excluded
|
||||||
|
for (Vote vote: getLatestUpdate().getVotes())
|
||||||
|
candidates.remove(vote.getReviewer());
|
||||||
|
|
||||||
|
getPotentialVoters().addAll(candidates);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* users already voted since base update should be excluded from
|
||||||
|
* invitation list as their votes are still valid
|
||||||
|
*/
|
||||||
|
for (Vote vote: getBaseUpdate().listVotesOnwards()) {
|
||||||
|
candidates.remove(vote.getReviewer());
|
||||||
|
}
|
||||||
|
|
||||||
|
Set<User> invited = new HashSet<User>();
|
||||||
|
for (VoteInvitation each: getVoteInvitations())
|
||||||
|
invited.add(each.getReviewer());
|
||||||
|
|
||||||
|
invited.retainAll(candidates);
|
||||||
|
|
||||||
|
for (int i=0; i<count-invited.size(); i++) {
|
||||||
User selected = Collections.min(candidates, new Comparator<User>() {
|
User selected = Collections.min(candidates, new Comparator<User>() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -256,13 +309,48 @@ public class MergeRequest extends AbstractEntity {
|
|||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
PendingVote pendingVote = new PendingVote();
|
candidates.remove(selected);
|
||||||
pendingVote.setRequest(this);
|
|
||||||
pendingVote.setReviewer(selected);
|
|
||||||
|
|
||||||
Gitop.getInstance(PendingVoteManager.class).save(pendingVote);
|
VoteInvitation invitation = new VoteInvitation();
|
||||||
|
invitation.setRequest(this);
|
||||||
|
invitation.setReviewer(selected);
|
||||||
|
|
||||||
|
Gitop.getInstance(VoteInvitationManager.class).save(invitation);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check this request with gate keeper of target repository.
|
||||||
|
* <p>
|
||||||
|
* @param force
|
||||||
|
* whether or not to force the check. Since the check might be time-consuming, Gitop
|
||||||
|
* caches the check result, and returns the cached result for subsequent checks if
|
||||||
|
* force flag is not set.
|
||||||
|
* @return
|
||||||
|
* check result of gate keeper, or Optional.absent() if no gate keeper is defined
|
||||||
|
*/
|
||||||
|
public Optional<CheckResult> check(boolean force) {
|
||||||
|
if (checkResult == null || force) {
|
||||||
|
GateKeeper gateKeeper = getDestination().getRepository().getGateKeeper();
|
||||||
|
if (gateKeeper != null) {
|
||||||
|
checkResult = Optional.of(gateKeeper.check(this));
|
||||||
|
|
||||||
|
Collection<VoteInvitation> withdraws = new HashSet<VoteInvitation>();
|
||||||
|
for (VoteInvitation invitation: getVoteInvitations()) {
|
||||||
|
if (!getPotentialVoters().contains(invitation.getReviewer())) {
|
||||||
|
withdraws.add(invitation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (VoteInvitation invitation: withdraws) {
|
||||||
|
Gitop.getInstance(VoteInvitationManager.class).delete(invitation);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
checkResult = Optional.absent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return checkResult;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -72,6 +72,8 @@ public class Repository extends AbstractEntity implements UserBelonging {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public GateKeeper getGateKeeper() {
|
public GateKeeper getGateKeeper() {
|
||||||
|
if (gateKeeper != null)
|
||||||
|
gateKeeper = (GateKeeper) gateKeeper.trim(this);
|
||||||
return gateKeeper;
|
return gateKeeper;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -7,7 +7,6 @@ import javax.persistence.Entity;
|
|||||||
import javax.persistence.OneToMany;
|
import javax.persistence.OneToMany;
|
||||||
|
|
||||||
import com.pmease.commons.security.AbstractUser;
|
import com.pmease.commons.security.AbstractUser;
|
||||||
import com.pmease.gitop.core.gatekeeper.GateKeeper.CheckResult;
|
|
||||||
import com.pmease.gitop.core.permission.object.ProtectedObject;
|
import com.pmease.gitop.core.permission.object.ProtectedObject;
|
||||||
import com.pmease.gitop.core.permission.object.UserBelonging;
|
import com.pmease.gitop.core.permission.object.UserBelonging;
|
||||||
|
|
||||||
@ -48,7 +47,7 @@ public class User extends AbstractUser implements ProtectedObject {
|
|||||||
private Collection<Vote> votes = new ArrayList<Vote>();
|
private Collection<Vote> votes = new ArrayList<Vote>();
|
||||||
|
|
||||||
@OneToMany(mappedBy="reviewer")
|
@OneToMany(mappedBy="reviewer")
|
||||||
private Collection<PendingVote> pendingVotes = new ArrayList<PendingVote>();
|
private Collection<VoteInvitation> voteVitations = new ArrayList<VoteInvitation>();
|
||||||
|
|
||||||
public String getDescription() {
|
public String getDescription() {
|
||||||
return description;
|
return description;
|
||||||
@ -106,41 +105,38 @@ public class User extends AbstractUser implements ProtectedObject {
|
|||||||
this.votes = votes;
|
this.votes = votes;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Collection<PendingVote> getPendingVotes() {
|
public Collection<VoteInvitation> getVoteInvitations() {
|
||||||
return pendingVotes;
|
return voteVitations;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setPendingVotes(Collection<PendingVote> pendingVotes) {
|
public void setVoteInvitations(Collection<VoteInvitation> voteInvitations) {
|
||||||
this.pendingVotes = pendingVotes;
|
this.voteVitations = voteInvitations;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean has(ProtectedObject object) {
|
public boolean has(ProtectedObject object) {
|
||||||
if (object instanceof User) {
|
if (object instanceof User) {
|
||||||
User user = (User) object;
|
User user = (User) object;
|
||||||
return user.getId().equals(getId());
|
return user.equals(this);
|
||||||
} else if (object instanceof UserBelonging) {
|
} else if (object instanceof UserBelonging) {
|
||||||
UserBelonging userBelonging = (UserBelonging) object;
|
UserBelonging userBelonging = (UserBelonging) object;
|
||||||
return userBelonging.getUser().getId().equals(getId());
|
return userBelonging.getUser().equals(this);
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public CheckResult checkApprovalSince(MergeRequestUpdate update) {
|
public Vote.Result checkVoteSince(MergeRequestUpdate update) {
|
||||||
if (update.getRequest().getSubmitter().getId().equals(getId()))
|
if (update.getRequest().getSubmitter().equals(this))
|
||||||
return CheckResult.ACCEPT;
|
return Vote.Result.ACCEPT;
|
||||||
|
|
||||||
for (Vote vote: update.listVotesOnwards()) {
|
for (Vote vote: update.listVotesOnwards()) {
|
||||||
if (vote.getReviewer().equals(this)) {
|
if (vote.getReviewer().equals(this)) {
|
||||||
if (vote.getResult() == Vote.Result.ACCEPT)
|
return vote.getResult();
|
||||||
return CheckResult.ACCEPT;
|
|
||||||
else
|
|
||||||
return CheckResult.REJECT;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return CheckResult.PENDING;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,7 +19,34 @@ import com.pmease.commons.persistence.AbstractEntity;
|
|||||||
})
|
})
|
||||||
public class Vote extends AbstractEntity {
|
public class Vote extends AbstractEntity {
|
||||||
|
|
||||||
public static enum Result {ACCEPT, REJECT};
|
public static enum Result {
|
||||||
|
ACCEPT {
|
||||||
|
@Override
|
||||||
|
public boolean isAccept() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isReject() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
REJECT {
|
||||||
|
@Override
|
||||||
|
public boolean isAccept() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isReject() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public abstract boolean isAccept();
|
||||||
|
|
||||||
|
public abstract boolean isReject();
|
||||||
|
};
|
||||||
|
|
||||||
@ManyToOne(fetch=FetchType.EAGER)
|
@ManyToOne(fetch=FetchType.EAGER)
|
||||||
@org.hibernate.annotations.Fetch(FetchMode.SELECT)
|
@org.hibernate.annotations.Fetch(FetchMode.SELECT)
|
||||||
|
|||||||
@ -16,7 +16,7 @@ import com.pmease.commons.persistence.AbstractEntity;
|
|||||||
@Table(uniqueConstraints={
|
@Table(uniqueConstraints={
|
||||||
@UniqueConstraint(columnNames={"reviewer", "request"})
|
@UniqueConstraint(columnNames={"reviewer", "request"})
|
||||||
})
|
})
|
||||||
public class PendingVote extends AbstractEntity {
|
public class VoteInvitation extends AbstractEntity {
|
||||||
|
|
||||||
@ManyToOne(fetch=FetchType.EAGER)
|
@ManyToOne(fetch=FetchType.EAGER)
|
||||||
@org.hibernate.annotations.Fetch(FetchMode.SELECT)
|
@org.hibernate.annotations.Fetch(FetchMode.SELECT)
|
||||||
Loading…
x
Reference in New Issue
Block a user