mirror of
https://github.com/theonedev/onedev.git
synced 2025-12-08 18:26:30 +00:00
Move build requirement of branch protection into CISpec definition
This is necessary to avoid mis-match of job parameter requirement in branch protection setting and job parameter specification
This commit is contained in:
parent
9805047fb1
commit
a6df8777c4
@ -6,7 +6,7 @@ import org.eclipse.jgit.lib.ObjectId;
|
||||
import io.onedev.server.ci.CISpec;
|
||||
import io.onedev.server.ci.job.Job;
|
||||
import io.onedev.server.ci.job.cache.JobCache;
|
||||
import io.onedev.server.ci.job.trigger.BranchPushedTrigger;
|
||||
import io.onedev.server.ci.job.trigger.BranchUpdateTrigger;
|
||||
import io.onedev.server.git.Blob;
|
||||
import io.onedev.server.git.BlobIdent;
|
||||
import io.onedev.server.model.Project;
|
||||
@ -30,7 +30,7 @@ public class MavenDetector implements CISpecDetector {
|
||||
+ "echo\n"
|
||||
+ "mvn clean test");
|
||||
|
||||
BranchPushedTrigger trigger = new BranchPushedTrigger();
|
||||
BranchUpdateTrigger trigger = new BranchUpdateTrigger();
|
||||
job.getTriggers().add(trigger);
|
||||
|
||||
JobCache cache = new JobCache();
|
||||
|
||||
@ -7,19 +7,21 @@ import org.eclipse.jgit.lib.ObjectId;
|
||||
|
||||
import io.onedev.commons.codeassist.InputSuggestion;
|
||||
import io.onedev.commons.utils.PathUtils;
|
||||
import io.onedev.commons.utils.stringmatch.ChildAwareMatcher;
|
||||
import io.onedev.server.ci.job.Job;
|
||||
import io.onedev.server.event.ProjectEvent;
|
||||
import io.onedev.server.event.RefUpdated;
|
||||
import io.onedev.server.git.GitUtils;
|
||||
import io.onedev.server.util.OneContext;
|
||||
import io.onedev.server.util.patternset.PatternSet;
|
||||
import io.onedev.server.web.editable.annotation.BranchPatterns;
|
||||
import io.onedev.server.web.editable.annotation.Editable;
|
||||
import io.onedev.server.web.editable.annotation.NameOfEmptyValue;
|
||||
import io.onedev.server.web.editable.annotation.Patterns;
|
||||
import io.onedev.server.web.util.SuggestionUtils;
|
||||
|
||||
@Editable(order=100, name="When pushes to branches")
|
||||
public class BranchPushedTrigger extends JobTrigger {
|
||||
@Editable(order=100, name="When update branches")
|
||||
public class BranchUpdateTrigger extends JobTrigger {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@ -27,7 +29,9 @@ public class BranchPushedTrigger extends JobTrigger {
|
||||
|
||||
private String paths;
|
||||
|
||||
@Editable(name="Pushed Branches", order=100,
|
||||
private boolean rejectIfNotSuccessful;
|
||||
|
||||
@Editable(name="Branches", order=100,
|
||||
description="Optionally specify space-separated branches to check. Use * or ? for wildcard match. "
|
||||
+ "Leave empty to match all branches")
|
||||
@BranchPatterns
|
||||
@ -52,7 +56,17 @@ public class BranchPushedTrigger extends JobTrigger {
|
||||
public void setPaths(String paths) {
|
||||
this.paths = paths;
|
||||
}
|
||||
|
||||
|
||||
@Editable(order=300, description="If checked, branch updating will be rejected if the triggering is not successful. "
|
||||
+ "It also tells pull requests to require successful triggering of the job before merging")
|
||||
public boolean isRejectIfNotSuccessful() {
|
||||
return rejectIfNotSuccessful;
|
||||
}
|
||||
|
||||
public void setRejectIfNotSuccessful(boolean rejectIfNotSuccessful) {
|
||||
this.rejectIfNotSuccessful = rejectIfNotSuccessful;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static List<InputSuggestion> getPathSuggestions(String matchWith) {
|
||||
return SuggestionUtils.suggestBlobs(OneContext.get().getProject(), matchWith);
|
||||
@ -82,9 +96,9 @@ public class BranchPushedTrigger extends JobTrigger {
|
||||
public boolean matches(ProjectEvent event, Job job) {
|
||||
if (event instanceof RefUpdated) {
|
||||
RefUpdated refUpdated = (RefUpdated) event;
|
||||
String pushedBranch = GitUtils.ref2branch(refUpdated.getRefName());
|
||||
if (pushedBranch != null) {
|
||||
if ((getBranches() == null || PathUtils.matchChildAware(getBranches(), pushedBranch))
|
||||
String branch = GitUtils.ref2branch(refUpdated.getRefName());
|
||||
if (branch != null) {
|
||||
if ((getBranches() == null || PatternSet.fromString(getBranches()).matches(new ChildAwareMatcher(), branch))
|
||||
&& touchedFile(refUpdated)) {
|
||||
return true;
|
||||
}
|
||||
@ -95,14 +109,18 @@ public class BranchPushedTrigger extends JobTrigger {
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
String description;
|
||||
if (getBranches() != null && getPaths() != null)
|
||||
return String.format("When push to branches '%s' and touch files '%s'", getBranches(), getPaths());
|
||||
description = String.format("When update branches '%s' and touch files '%s'", getBranches(), getPaths());
|
||||
else if (getBranches() != null)
|
||||
return String.format("When push to branches '%s'", getBranches());
|
||||
description = String.format("When update branches '%s'", getBranches());
|
||||
else if (getPaths() != null)
|
||||
return String.format("When touch files '%s'", getBranches());
|
||||
description = String.format("When touch files '%s'", getBranches());
|
||||
else
|
||||
return "When push to branches";
|
||||
description = "When update branches";
|
||||
if (rejectIfNotSuccessful)
|
||||
description += " (reject if not successful)";
|
||||
return description;
|
||||
}
|
||||
|
||||
}
|
||||
@ -2,23 +2,24 @@ package io.onedev.server.ci.job.trigger;
|
||||
|
||||
import org.eclipse.jgit.lib.ObjectId;
|
||||
|
||||
import io.onedev.commons.utils.PathUtils;
|
||||
import io.onedev.commons.utils.stringmatch.ChildAwareMatcher;
|
||||
import io.onedev.server.ci.job.Job;
|
||||
import io.onedev.server.event.ProjectEvent;
|
||||
import io.onedev.server.event.RefUpdated;
|
||||
import io.onedev.server.git.GitUtils;
|
||||
import io.onedev.server.util.patternset.PatternSet;
|
||||
import io.onedev.server.web.editable.annotation.Editable;
|
||||
import io.onedev.server.web.editable.annotation.NameOfEmptyValue;
|
||||
import io.onedev.server.web.editable.annotation.TagPatterns;
|
||||
|
||||
@Editable(order=200, name="When tags are created")
|
||||
public class TagCreatedTrigger extends JobTrigger {
|
||||
@Editable(order=200, name="When create tags")
|
||||
public class TagCreateTrigger extends JobTrigger {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private String tags;
|
||||
|
||||
@Editable(name="Created Tags", order=100,
|
||||
@Editable(name="Tags", order=100,
|
||||
description="Optionally specify space-separated tags to check. Use * or ? for wildcard match. "
|
||||
+ "Leave empty to match all tags")
|
||||
@TagPatterns
|
||||
@ -37,7 +38,7 @@ public class TagCreatedTrigger extends JobTrigger {
|
||||
RefUpdated refUpdated = (RefUpdated) event;
|
||||
String pushedTag = GitUtils.ref2tag(refUpdated.getRefName());
|
||||
if (pushedTag != null && !refUpdated.getNewCommitId().equals(ObjectId.zeroId())
|
||||
&& (getTags() == null || PathUtils.matchChildAware(getTags(), pushedTag))) {
|
||||
&& (getTags() == null || PatternSet.fromString(getTags()).matches(new ChildAwareMatcher(), pushedTag))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -47,9 +48,9 @@ public class TagCreatedTrigger extends JobTrigger {
|
||||
@Override
|
||||
public String getDescription() {
|
||||
if (getTags() != null)
|
||||
return String.format("When tags '%s' are created", getTags());
|
||||
return String.format("When create tags '%s'", getTags());
|
||||
else
|
||||
return "When tags are created";
|
||||
return "When create tags";
|
||||
}
|
||||
|
||||
}
|
||||
@ -61,9 +61,11 @@ import io.onedev.server.OneDev;
|
||||
import io.onedev.server.OneException;
|
||||
import io.onedev.server.cache.CommitInfoManager;
|
||||
import io.onedev.server.ci.CISpec;
|
||||
import io.onedev.server.ci.JobDependency;
|
||||
import io.onedev.server.ci.job.Job;
|
||||
import io.onedev.server.ci.job.JobManager;
|
||||
import io.onedev.server.ci.job.param.JobParam;
|
||||
import io.onedev.server.ci.job.trigger.BranchUpdateTrigger;
|
||||
import io.onedev.server.ci.job.trigger.JobTrigger;
|
||||
import io.onedev.server.entitymanager.ProjectManager;
|
||||
import io.onedev.server.entitymanager.PullRequestBuildManager;
|
||||
import io.onedev.server.entitymanager.PullRequestChangeManager;
|
||||
@ -690,8 +692,6 @@ public class DefaultPullRequestManager extends AbstractEntityManager<PullRequest
|
||||
if (branchProtection != null) {
|
||||
checkReviews(ReviewRequirement.fromString(branchProtection.getReviewRequirement()), request.getLatestUpdate());
|
||||
|
||||
checkBuilds(request, branchProtection.getJobDependencies());
|
||||
|
||||
Set<FileProtection> checkedFileProtections = new HashSet<>();
|
||||
for (int i=request.getSortedUpdates().size()-1; i>=0; i--) {
|
||||
if (checkedFileProtections.containsAll(branchProtection.getFileProtections()))
|
||||
@ -707,6 +707,9 @@ public class DefaultPullRequestManager extends AbstractEntityManager<PullRequest
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
checkBuilds(request);
|
||||
|
||||
ProjectFacade project = request.getTargetProject().getFacade();
|
||||
Permission writeCode = new ProjectPermission(project, ProjectPrivilege.CODE_WRITE);
|
||||
if (request.getSubmitter() == null || !request.getSubmitter().asSubject().isPermitted(writeCode)) {
|
||||
@ -717,7 +720,7 @@ public class DefaultPullRequestManager extends AbstractEntityManager<PullRequest
|
||||
}
|
||||
}
|
||||
|
||||
private void checkBuilds(PullRequest request, List<JobDependency> jobDependencies) {
|
||||
private void checkBuilds(PullRequest request) {
|
||||
Collection<PullRequestBuild> prevRequirements = new ArrayList<>(request.getPullRequestBuilds());
|
||||
request.getPullRequestBuilds().clear();
|
||||
MergePreview preview = request.getMergePreview();
|
||||
@ -727,31 +730,40 @@ public class DefaultPullRequestManager extends AbstractEntityManager<PullRequest
|
||||
CISpec ciSpec = project.getCISpec(commitId);
|
||||
if (ciSpec == null)
|
||||
throw new OneException("No CI spec defined in merge preview commit");
|
||||
for (JobDependency dependency: jobDependencies) {
|
||||
if (!ciSpec.getJobMap().containsKey(dependency.getJobName()))
|
||||
throw new OneException("Job '" + dependency.getJobName() + "' is not defined in merge preview commit");
|
||||
new MatrixRunner<List<String>>(JobParam.getParamMatrix(dependency.getJobParams())) {
|
||||
|
||||
@Override
|
||||
public void run(Map<String, List<String>> paramMap) {
|
||||
Build build = jobManager.submit(request.getTargetProject(),
|
||||
commitId, dependency.getJobName(), paramMap);
|
||||
PullRequestBuild pullRequestBuild = null;
|
||||
for (PullRequestBuild prevRequirement: prevRequirements) {
|
||||
if (prevRequirement.getBuild().equals(build)) {
|
||||
pullRequestBuild = prevRequirement;
|
||||
break;
|
||||
for (Job job: ciSpec.getJobs()) {
|
||||
for (JobTrigger trigger: job.getTriggers()) {
|
||||
if (trigger instanceof BranchUpdateTrigger) {
|
||||
BranchUpdateTrigger branchUpdateTrigger = (BranchUpdateTrigger) trigger;
|
||||
if (branchUpdateTrigger.isRejectIfNotSuccessful()) {
|
||||
RefUpdated updated = new RefUpdated(project, GitUtils.branch2ref(request.getTargetBranch()),
|
||||
request.getTarget().getObjectId(), commitId);
|
||||
if (branchUpdateTrigger.matches(updated, job)) {
|
||||
new MatrixRunner<List<String>>(JobParam.getParamMatrix(trigger.getParams())) {
|
||||
|
||||
@Override
|
||||
public void run(Map<String, List<String>> paramMap) {
|
||||
Build build = jobManager.submit(request.getTargetProject(),
|
||||
commitId, job.getName(), paramMap);
|
||||
PullRequestBuild pullRequestBuild = null;
|
||||
for (PullRequestBuild prevRequirement: prevRequirements) {
|
||||
if (prevRequirement.getBuild().equals(build)) {
|
||||
pullRequestBuild = prevRequirement;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (pullRequestBuild == null) {
|
||||
pullRequestBuild = new PullRequestBuild();
|
||||
pullRequestBuild.setRequest(request);
|
||||
pullRequestBuild.setBuild(build);
|
||||
}
|
||||
request.getPullRequestBuilds().add(pullRequestBuild);
|
||||
}
|
||||
|
||||
}.run();
|
||||
}
|
||||
}
|
||||
if (pullRequestBuild == null) {
|
||||
pullRequestBuild = new PullRequestBuild();
|
||||
pullRequestBuild.setRequest(request);
|
||||
pullRequestBuild.setBuild(build);
|
||||
}
|
||||
request.getPullRequestBuilds().add(pullRequestBuild);
|
||||
}
|
||||
|
||||
}.run();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -155,10 +155,16 @@ public class GitPreReceiveCallback extends HttpServlet {
|
||||
} else if (protection.isNoForcedPush()
|
||||
&& !GitUtils.isMergedInto(project.getRepository(), gitEnvs, oldObjectId, newObjectId)) {
|
||||
errorMessages.add("Can not force-push to this branch according to branch protection setting");
|
||||
} else if (!protection.isPushAllowed(user, project, branchName, oldObjectId, newObjectId, gitEnvs)) {
|
||||
errorMessages.add("Your changes need to be reviewed/verified. Please submit pull request instead");
|
||||
} else if (protection.isReviewRequiredForPush(user, project, branchName, oldObjectId, newObjectId, gitEnvs)) {
|
||||
errorMessages.add("Review required for your change. Please submit pull request instead");
|
||||
}
|
||||
}
|
||||
if (errorMessages.isEmpty()
|
||||
&& !oldObjectId.equals(ObjectId.zeroId())
|
||||
&& !newObjectId.equals(ObjectId.zeroId())
|
||||
&& project.isBuildRequiredForPush(branchName, oldObjectId, newObjectId, gitEnvs)) {
|
||||
errorMessages.add("Build required for your change. Please submit pull request instead");
|
||||
}
|
||||
if (errorMessages.isEmpty() && newObjectId.equals(ObjectId.zeroId())) {
|
||||
try {
|
||||
projectManager.onDeleteBranch(project, branchName);
|
||||
|
||||
@ -15,6 +15,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.concurrent.locks.ReadWriteLock;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@ -77,6 +78,10 @@ import io.onedev.server.OneDev;
|
||||
import io.onedev.server.cache.CommitInfoManager;
|
||||
import io.onedev.server.ci.CISpec;
|
||||
import io.onedev.server.ci.detector.CISpecDetector;
|
||||
import io.onedev.server.ci.job.Job;
|
||||
import io.onedev.server.ci.job.param.JobParam;
|
||||
import io.onedev.server.ci.job.trigger.BranchUpdateTrigger;
|
||||
import io.onedev.server.ci.job.trigger.JobTrigger;
|
||||
import io.onedev.server.entitymanager.BuildManager;
|
||||
import io.onedev.server.entitymanager.BuildQuerySettingManager;
|
||||
import io.onedev.server.entitymanager.CodeCommentQuerySettingManager;
|
||||
@ -114,6 +119,7 @@ import io.onedev.server.persistence.TransactionManager;
|
||||
import io.onedev.server.security.SecurityUtils;
|
||||
import io.onedev.server.security.permission.DefaultPrivilege;
|
||||
import io.onedev.server.storage.StorageManager;
|
||||
import io.onedev.server.util.MatrixRunner;
|
||||
import io.onedev.server.util.facade.ProjectFacade;
|
||||
import io.onedev.server.util.jackson.DefaultView;
|
||||
import io.onedev.server.util.patternset.PatternSet;
|
||||
@ -1420,21 +1426,102 @@ public class Project extends AbstractEntity implements Validatable {
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isModificationAllowed(User user, String branch, @Nullable String file) {
|
||||
public boolean isReviewRequiredForModification(User user, String branch, @Nullable String file) {
|
||||
BranchProtection branchProtection = getBranchProtection(branch, user);
|
||||
if (branchProtection != null)
|
||||
return branchProtection.isModificationAllowed(user, this, branch, file);
|
||||
return branchProtection.isReviewRequiredForModification(user, this, branch, file);
|
||||
else
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean isPushAllowed(User user, String branch, ObjectId oldObjectId,
|
||||
public boolean isReviewRequiredForPush(User user, String branch, ObjectId oldObjectId,
|
||||
ObjectId newObjectId, Map<String, String> gitEnvs) {
|
||||
BranchProtection branchProtection = getBranchProtection(branch, user);
|
||||
if (branchProtection != null)
|
||||
return branchProtection.isPushAllowed(user, this, branch, oldObjectId, newObjectId, gitEnvs);
|
||||
else
|
||||
return true;
|
||||
if (branchProtection != null) {
|
||||
return branchProtection.isReviewRequiredForPush(user, this, branch, oldObjectId, newObjectId, gitEnvs);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isBuildRequiredForModification(String branch, @Nullable String file) {
|
||||
// Exclude cispec from build requirement to avoid being locking out
|
||||
if (!CISpec.BLOB_PATH.equals(file)) {
|
||||
try {
|
||||
CISpec ciSpec = getCISpec(getObjectId(branch, true));
|
||||
if (ciSpec != null) {
|
||||
for (Job job: ciSpec.getJobs()) {
|
||||
for (JobTrigger trigger: job.getTriggers()) {
|
||||
if (trigger instanceof BranchUpdateTrigger) {
|
||||
BranchUpdateTrigger branchUpdateTrigger = (BranchUpdateTrigger) trigger;
|
||||
if (branchUpdateTrigger.isRejectIfNotSuccessful()
|
||||
&& (branchUpdateTrigger.getBranches() == null || PatternSet.fromString(branchUpdateTrigger.getBranches()).matches(new ChildAwareMatcher(), branch))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean isBuildRequiredForPush(String branch, ObjectId oldObjectId, ObjectId newObjectId,
|
||||
Map<String, String> gitEnvs) {
|
||||
// Exclude cispec from build requirement to avoid being locking out
|
||||
if (!getChangedFiles(oldObjectId, newObjectId, gitEnvs).contains(CISpec.BLOB_PATH)) {
|
||||
Collection<Build> builds = OneDev.getInstance(BuildManager.class).query(this, newObjectId);
|
||||
try {
|
||||
CISpec ciSpec = getCISpec(oldObjectId);
|
||||
if (ciSpec != null) {
|
||||
for (Job job: ciSpec.getJobs()) {
|
||||
for (JobTrigger trigger: job.getTriggers()) {
|
||||
if (trigger instanceof BranchUpdateTrigger) {
|
||||
BranchUpdateTrigger branchUpdateTrigger = (BranchUpdateTrigger) trigger;
|
||||
if (branchUpdateTrigger.isRejectIfNotSuccessful()
|
||||
&& (branchUpdateTrigger.getBranches() == null || PatternSet.fromString(branchUpdateTrigger.getBranches()).matches(new ChildAwareMatcher(), branch))) {
|
||||
Map<String, List<List<String>>> paramMatrix = new HashMap<>();
|
||||
Set<String> secretParamNames = new HashSet<>();
|
||||
for (JobParam param: trigger.getParams()) {
|
||||
paramMatrix.put(param.getName(), param.getValuesProvider().getValues());
|
||||
if (param.isSecret())
|
||||
secretParamNames.add(param.getName());
|
||||
}
|
||||
|
||||
AtomicReference<Build> buildRef = new AtomicReference<>(null);
|
||||
new MatrixRunner<List<String>>(paramMatrix) {
|
||||
|
||||
@Override
|
||||
public void run(Map<String, List<String>> params) {
|
||||
for (Build build: builds) {
|
||||
Map<String, List<String>> paramsWithoutSecrets = new HashMap<>(params);
|
||||
Map<String, List<String>> buildParamsWithoutSecrets = new HashMap<>(build.getParamMap());
|
||||
paramsWithoutSecrets.keySet().removeAll(secretParamNames);
|
||||
buildParamsWithoutSecrets.keySet().removeAll(secretParamNames);
|
||||
if (build.getJobName().equals(job.getName())
|
||||
&& buildParamsWithoutSecrets.equals(paramsWithoutSecrets)) {
|
||||
buildRef.set(build);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}.run();
|
||||
|
||||
Build build = buildRef.get();
|
||||
if (build == null || build.getStatus() != Build.Status.SUCCESSFUL)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@ -2,13 +2,8 @@ package io.onedev.server.model.support;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.validation.constraints.NotNull;
|
||||
@ -17,14 +12,8 @@ import org.eclipse.jgit.lib.ObjectId;
|
||||
import org.hibernate.validator.constraints.NotEmpty;
|
||||
|
||||
import io.onedev.commons.utils.stringmatch.ChildAwareMatcher;
|
||||
import io.onedev.server.OneDev;
|
||||
import io.onedev.server.ci.JobDependency;
|
||||
import io.onedev.server.ci.job.param.JobParam;
|
||||
import io.onedev.server.entitymanager.BuildManager;
|
||||
import io.onedev.server.model.Build;
|
||||
import io.onedev.server.model.Project;
|
||||
import io.onedev.server.model.User;
|
||||
import io.onedev.server.util.MatrixRunner;
|
||||
import io.onedev.server.util.Usage;
|
||||
import io.onedev.server.util.patternset.PatternSet;
|
||||
import io.onedev.server.util.reviewrequirement.ReviewRequirement;
|
||||
@ -55,8 +44,6 @@ public class BranchProtection implements Serializable {
|
||||
|
||||
private String reviewRequirement;
|
||||
|
||||
private List<JobDependency> jobDependencies = new ArrayList<>();
|
||||
|
||||
private List<FileProtection> fileProtections = new ArrayList<>();
|
||||
|
||||
public boolean isEnabled() {
|
||||
@ -128,16 +115,6 @@ public class BranchProtection implements Serializable {
|
||||
this.reviewRequirement = reviewRequirement;
|
||||
}
|
||||
|
||||
@Editable(order=500, name="Required Builds", description="Optionally specify required builds")
|
||||
@NameOfEmptyValue("No any")
|
||||
public List<JobDependency> getJobDependencies() {
|
||||
return jobDependencies;
|
||||
}
|
||||
|
||||
public void setJobDependencies(List<JobDependency> jobDependencies) {
|
||||
this.jobDependencies = jobDependencies;
|
||||
}
|
||||
|
||||
@Editable(order=700, description="Optionally specify additional users to review "
|
||||
+ "particular paths. For each changed file, the first matched file protection setting will be used")
|
||||
@NotNull(message="may not be empty")
|
||||
@ -222,20 +199,17 @@ public class BranchProtection implements Serializable {
|
||||
* @return
|
||||
* result of the check.
|
||||
*/
|
||||
public boolean isModificationAllowed(User user, Project project, String branch, @Nullable String file) {
|
||||
public boolean isReviewRequiredForModification(User user, Project project, String branch, @Nullable String file) {
|
||||
if (!ReviewRequirement.fromString(getReviewRequirement()).satisfied(user))
|
||||
return false;
|
||||
if (!getJobDependencies().isEmpty())
|
||||
return false;
|
||||
|
||||
return true;
|
||||
if (file != null) {
|
||||
FileProtection fileProtection = getFileProtection(file);
|
||||
if (fileProtection != null
|
||||
&& !ReviewRequirement.fromString(fileProtection.getReviewRequirement()).satisfied(user)) {
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -254,55 +228,19 @@ public class BranchProtection implements Serializable {
|
||||
* @return
|
||||
* result of the check
|
||||
*/
|
||||
public boolean isPushAllowed(User user, Project project, String branch, ObjectId oldObjectId,
|
||||
public boolean isReviewRequiredForPush(User user, Project project, String branch, ObjectId oldObjectId,
|
||||
ObjectId newObjectId, Map<String, String> gitEnvs) {
|
||||
if (!ReviewRequirement.fromString(getReviewRequirement()).satisfied(user))
|
||||
return false;
|
||||
return true;
|
||||
|
||||
Collection<Build> builds = OneDev.getInstance(BuildManager.class).query(project, newObjectId);
|
||||
|
||||
for (JobDependency dependency: getJobDependencies()) {
|
||||
Map<String, List<List<String>>> paramMatrix = new HashMap<>();
|
||||
Set<String> secretParamNames = new HashSet<>();
|
||||
for (JobParam param: dependency.getJobParams()) {
|
||||
paramMatrix.put(param.getName(), param.getValuesProvider().getValues());
|
||||
if (param.isSecret())
|
||||
secretParamNames.add(param.getName());
|
||||
}
|
||||
|
||||
AtomicReference<Build> buildRef = new AtomicReference<>(null);
|
||||
new MatrixRunner<List<String>>(paramMatrix) {
|
||||
|
||||
@Override
|
||||
public void run(Map<String, List<String>> params) {
|
||||
for (Build build: builds) {
|
||||
Map<String, List<String>> paramsWithoutSecrets = new HashMap<>(params);
|
||||
Map<String, List<String>> buildParamsWithoutSecrets = new HashMap<>(build.getParamMap());
|
||||
paramsWithoutSecrets.keySet().removeAll(secretParamNames);
|
||||
buildParamsWithoutSecrets.keySet().removeAll(secretParamNames);
|
||||
if (build.getJobName().equals(dependency.getJobName())
|
||||
&& buildParamsWithoutSecrets.equals(paramsWithoutSecrets)) {
|
||||
buildRef.set(build);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}.run();
|
||||
|
||||
Build build = buildRef.get();
|
||||
if (build == null || build.getStatus() != Build.Status.SUCCESSFUL)
|
||||
return false;
|
||||
}
|
||||
|
||||
for (String changedFile: project.getChangedFiles(oldObjectId, newObjectId, gitEnvs)) {
|
||||
FileProtection fileProtection = getFileProtection(changedFile);
|
||||
if (fileProtection != null
|
||||
&& !ReviewRequirement.fromString(fileProtection.getReviewRequirement()).satisfied(user)) {
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -104,11 +104,15 @@ public class SecurityUtils extends org.apache.shiro.SecurityUtils {
|
||||
}
|
||||
|
||||
public static boolean canModify(Project project, String branch, String file) {
|
||||
return canWriteCode(project.getFacade()) && project.isModificationAllowed(getUser(), branch, file);
|
||||
return canWriteCode(project.getFacade())
|
||||
&& !project.isReviewRequiredForModification(getUser(), branch, file)
|
||||
&& !project.isBuildRequiredForModification(branch, file);
|
||||
}
|
||||
|
||||
public static boolean canPush(Project project, String branch, ObjectId oldObjectId, ObjectId newObjectId) {
|
||||
return canWriteCode(project.getFacade()) && project.isPushAllowed(getUser(), branch, oldObjectId, newObjectId, null);
|
||||
return canWriteCode(project.getFacade())
|
||||
&& !project.isReviewRequiredForPush(getUser(), branch, oldObjectId, newObjectId, null)
|
||||
&& !project.isBuildRequiredForPush(branch, oldObjectId, newObjectId, null);
|
||||
}
|
||||
|
||||
public static boolean canAdministrate(UserFacade user) {
|
||||
|
||||
@ -48,23 +48,19 @@ public class PatternSet implements Serializable {
|
||||
return excludes;
|
||||
}
|
||||
|
||||
public int getMatchIndex(Matcher matcher, String value) {
|
||||
public boolean matches(Matcher matcher, String value) {
|
||||
for (String exclude: excludes) {
|
||||
if (matcher.matches(exclude, value))
|
||||
return -1;
|
||||
return false;
|
||||
}
|
||||
int index = 0;
|
||||
for (String include: includes) {
|
||||
if (matcher.matches(include, value))
|
||||
return index;
|
||||
else
|
||||
index++;
|
||||
return true;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public boolean matches(Matcher matcher, String value) {
|
||||
return getMatchIndex(matcher, value) != -1;
|
||||
if (excludes.isEmpty())
|
||||
return false;
|
||||
else
|
||||
return includes.isEmpty();
|
||||
}
|
||||
|
||||
public Collection<File> listFiles(File dir) {
|
||||
|
||||
@ -11,7 +11,8 @@ onedev.server.editable = {
|
||||
var $container = $("#" + containerId);
|
||||
var $group = $container.children(".group");
|
||||
$group.children("a").click(function() {
|
||||
$(this).parent().toggleClass("expanded");
|
||||
$(this).parent().toggleClass("expanded");
|
||||
$(window).resize();
|
||||
});
|
||||
},
|
||||
onBeanEditorPropertyContainerDomReady: function(containerId) {
|
||||
|
||||
@ -28,9 +28,8 @@ onedev.server.scriptSupport = {
|
||||
var cm = $cm[0].CodeMirror;
|
||||
function setWidth() {
|
||||
var $cm = $("#"+inputId).next();
|
||||
$cm.hide(); // hide temporarily in order to get original width
|
||||
cm.setSize(1, null); // shrink temporarily in order to get original parent width
|
||||
cm.setSize($cm.parent().width(), null);
|
||||
$cm.show();
|
||||
cm.refresh();
|
||||
}
|
||||
setWidth();
|
||||
@ -43,7 +42,7 @@ onedev.server.scriptSupport = {
|
||||
indentUnit: 4,
|
||||
tabSize: 4,
|
||||
theme: "eclipse",
|
||||
lineNumbers: true,
|
||||
lineNumbers: true,
|
||||
styleActiveLine: true,
|
||||
styleSelectedText: true,
|
||||
foldGutter: true,
|
||||
|
||||
@ -334,6 +334,10 @@ public class ProjectBlobPage extends ProjectPage implements BlobRenderContext {
|
||||
WebMarkupContainer blobOperations = new WebMarkupContainer("blobOperations");
|
||||
blobOperations.setOutputMarkupId(true);
|
||||
|
||||
String revision = state.blobIdent.revision;
|
||||
boolean reviewRequired = getProject().isReviewRequiredForModification(getLoginUser(), revision, null);
|
||||
boolean buildRequired = getProject().isBuildRequiredForModification(revision, null);
|
||||
|
||||
blobOperations.add(new MenuLink("add") {
|
||||
|
||||
@Override
|
||||
@ -404,10 +408,18 @@ public class ProjectBlobPage extends ProjectPage implements BlobRenderContext {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void disableLink(ComponentTag tag) {
|
||||
super.disableLink(tag);
|
||||
tag.append("class", "disabled", " ");
|
||||
tag.put("title", "Add files not allowed. Submit pull request for review instead");
|
||||
protected void onComponentTag(ComponentTag tag) {
|
||||
super.onComponentTag(tag);
|
||||
|
||||
if (reviewRequired) {
|
||||
tag.append("class", "disabled", " ");
|
||||
tag.put("title", "Review required for this change. Submit pull request instead");
|
||||
} else if (buildRequired) {
|
||||
tag.append("class", "disabled", " ");
|
||||
tag.put("title", "Build required for this change. Submit pull request instead");
|
||||
} else {
|
||||
tag.put("title", "Add on branch " + state.blobIdent.revision);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -418,7 +430,8 @@ public class ProjectBlobPage extends ProjectPage implements BlobRenderContext {
|
||||
if ((state.mode == Mode.VIEW || state.mode == Mode.VIEW_PLAIN || state.mode == Mode.BLAME)
|
||||
&& isOnBranch() && state.blobIdent.isTree()
|
||||
&& SecurityUtils.canWriteCode(project.getFacade())) {
|
||||
setEnabled(project.isModificationAllowed(getLoginUser(), state.blobIdent.revision, null));
|
||||
setVisible(true);
|
||||
setEnabled(!reviewRequired && !buildRequired);
|
||||
} else {
|
||||
setVisible(false);
|
||||
}
|
||||
@ -1174,10 +1187,10 @@ public class ProjectBlobPage extends ProjectPage implements BlobRenderContext {
|
||||
if (parentPath != null)
|
||||
blobPath = parentPath + "/" + blobPath;
|
||||
|
||||
if (!getProject().isModificationAllowed(SecurityUtils.getUser(), blobIdent.revision, blobPath)) {
|
||||
throw new BlobUploadException("Adding of file '" + blobPath + "' need to be reviewed/verified. "
|
||||
+ "Please submit pull request instead");
|
||||
}
|
||||
if (getProject().isReviewRequiredForModification(SecurityUtils.getUser(), blobIdent.revision, blobPath))
|
||||
throw new BlobUploadException("Review required for this change. Please submit pull request instead");
|
||||
else if (getProject().isBuildRequiredForModification(blobIdent.revision, blobPath))
|
||||
throw new BlobUploadException("Build required for this change. Please submit pull request instead");
|
||||
|
||||
BlobContent blobContent = new BlobContent.Immutable(upload.getBytes(), FileMode.REGULAR_FILE);
|
||||
newBlobs.put(blobPath, blobContent);
|
||||
|
||||
@ -317,10 +317,14 @@ public class CommitOptionPanel extends Panel {
|
||||
|
||||
Map<String, BlobContent> newBlobs = new HashMap<>();
|
||||
if (newContentProvider != null) {
|
||||
if (!context.getProject().isModificationAllowed(SecurityUtils.getUser(),
|
||||
context.getBlobIdent().revision, context.getNewPath())) {
|
||||
CommitOptionPanel.this.error("Adding of file '" + context.getNewPath() + "' need to be reviewed/verified. "
|
||||
+ "Please submit pull request instead");
|
||||
String revision = context.getBlobIdent().revision;
|
||||
String newPath = context.getNewPath();
|
||||
if (context.getProject().isReviewRequiredForModification(SecurityUtils.getUser(), revision, newPath)) {
|
||||
CommitOptionPanel.this.error("Review required for this change. Please submit pull request instead");
|
||||
target.add(feedback);
|
||||
return false;
|
||||
} else if (context.getProject().isBuildRequiredForModification(revision, newPath)) {
|
||||
CommitOptionPanel.this.error("Build required for this change. Please submit pull request instead");
|
||||
target.add(feedback);
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -7,7 +7,6 @@ import org.apache.wicket.ajax.attributes.AjaxRequestAttributes;
|
||||
import org.apache.wicket.ajax.attributes.AjaxRequestAttributes.Method;
|
||||
import org.apache.wicket.ajax.form.OnChangeAjaxBehavior;
|
||||
import org.apache.wicket.ajax.markup.html.AjaxLink;
|
||||
import org.apache.wicket.behavior.AttributeAppender;
|
||||
import org.apache.wicket.core.request.handler.IPartialPageRequestHandler;
|
||||
import org.apache.wicket.markup.ComponentTag;
|
||||
import org.apache.wicket.markup.head.IHeaderResponse;
|
||||
@ -73,8 +72,10 @@ public abstract class BlobViewPanel extends Panel {
|
||||
Project project = context.getProject();
|
||||
if (SecurityUtils.canWriteCode(project.getFacade()) && context.isOnBranch()) {
|
||||
User user = OneDev.getInstance(UserManager.class).getCurrent();
|
||||
boolean modificationAllowed = project.isModificationAllowed(
|
||||
user, context.getBlobIdent().revision, context.getBlobIdent().path);
|
||||
String revision = context.getBlobIdent().revision;
|
||||
String path = context.getBlobIdent().path;
|
||||
boolean reviewRequired = project.isReviewRequiredForModification(user, revision, path);
|
||||
boolean buildRequired = project.isBuildRequiredForModification(revision, path);
|
||||
|
||||
if (isEditSupported()) {
|
||||
AjaxLink<Void> editLink = new ViewStateAwareAjaxLink<Void>("edit", true) {
|
||||
@ -82,8 +83,7 @@ public abstract class BlobViewPanel extends Panel {
|
||||
@Override
|
||||
protected void disableLink(ComponentTag tag) {
|
||||
super.disableLink(tag);
|
||||
tag.append("class", "disabled", " ");
|
||||
tag.put("title", "Direct edit not allowed. Submit pull request for review instead");
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -92,16 +92,27 @@ public abstract class BlobViewPanel extends Panel {
|
||||
attributes.getAjaxCallListeners().add(new ConfirmLeaveListener());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onComponentTag(ComponentTag tag) {
|
||||
super.onComponentTag(tag);
|
||||
if (reviewRequired) {
|
||||
tag.append("class", "disabled", " ");
|
||||
tag.put("title", "Review required for this change. Submit pull request instead");
|
||||
} else if (buildRequired) {
|
||||
tag.append("class", "disabled", " ");
|
||||
tag.put("title", "Build required for this change. Submit pull request instead");
|
||||
} else {
|
||||
tag.put("title", "Edit on branch " + context.getBlobIdent().revision);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(AjaxRequestTarget target) {
|
||||
context.onModeChange(target, Mode.EDIT);
|
||||
}
|
||||
|
||||
};
|
||||
if (modificationAllowed)
|
||||
editLink.add(AttributeAppender.append("title", "Edit on branch " + context.getBlobIdent().revision));
|
||||
else
|
||||
editLink.setEnabled(false);
|
||||
editLink.setEnabled(!reviewRequired && !buildRequired);
|
||||
|
||||
changeActions.add(editLink);
|
||||
} else {
|
||||
@ -111,12 +122,19 @@ public abstract class BlobViewPanel extends Panel {
|
||||
AjaxLink<Void> deleteLink = new ViewStateAwareAjaxLink<Void>("delete") {
|
||||
|
||||
@Override
|
||||
protected void disableLink(ComponentTag tag) {
|
||||
super.disableLink(tag);
|
||||
tag.append("class", "disabled", " ");
|
||||
tag.put("title", "Direct deletion not allowed. Submit pull request for review instead");
|
||||
protected void onComponentTag(ComponentTag tag) {
|
||||
super.onComponentTag(tag);
|
||||
if (reviewRequired) {
|
||||
tag.append("class", "disabled", " ");
|
||||
tag.put("title", "Review required for this change. Submit pull request instead");
|
||||
} else if (buildRequired) {
|
||||
tag.append("class", "disabled", " ");
|
||||
tag.put("title", "Build required for this change. Submit pull request instead");
|
||||
} else {
|
||||
tag.put("title", "Delete from branch " + context.getBlobIdent().revision);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void updateAjaxAttributes(AjaxRequestAttributes attributes) {
|
||||
super.updateAjaxAttributes(attributes);
|
||||
@ -130,10 +148,7 @@ public abstract class BlobViewPanel extends Panel {
|
||||
|
||||
};
|
||||
|
||||
if (modificationAllowed)
|
||||
deleteLink.add(AttributeAppender.append("title", "Delete from branch " + context.getBlobIdent().revision));
|
||||
else
|
||||
deleteLink.setEnabled(false);
|
||||
deleteLink.setEnabled(!reviewRequired && !buildRequired);
|
||||
|
||||
changeActions.add(deleteLink);
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user