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:
Robin Shen 2019-06-02 17:51:21 +08:00
parent 9805047fb1
commit a6df8777c4
14 changed files with 268 additions and 174 deletions

View File

@ -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();

View File

@ -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;
}
}

View File

@ -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";
}
}

View File

@ -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();
}
}
}
}

View File

@ -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);

View File

@ -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

View File

@ -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;
}
}

View File

@ -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) {

View File

@ -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) {

View File

@ -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) {

View File

@ -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,

View File

@ -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);

View File

@ -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;
}

View File

@ -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);