Support branch specific labels

By default a given project's label applicable scope is all changes
on all branches of this project and its child projects.

Label's applicable scope can be branch specific via configuration.
E.g. create a label 'Video-Qualify' on parent project and configure
the 'branch' as:

[label "Video-Qualify"]
    branch = refs/heads/video-1.0/*
    branch = refs/heads/video-1.1/Kino

Then ONLY changes in above branch scope of parent project and child
projects will be affected by 'Video-Qualify'.

Please note the 'branch' is independent from the branch scope related
to access control defined in 'access' parts in 'project.config' file.
That means from the UI a user can always assign permissions for that
label on a branch, but this permission is then ignored if the label
doesn't apply for that branch.

Change-Id: I414e62503b0da4af022abbc9988593ba9ac87916
This commit is contained in:
Bruce Zu
2013-09-06 01:23:03 +08:00
parent 9aaf3d15e3
commit 9b22c3ab2d
5 changed files with 73 additions and 12 deletions

View File

@@ -244,6 +244,30 @@ If false, the label cannot be overridden by child projects. Any
configuration for this label in child projects will be ignored. Defaults configuration for this label in child projects will be ignored. Defaults
to true. to true.
[[label_branch]]
`label.Label-Name.branch`
~~~~~~~~~~~~~~~~~~~~~~~~~
By default a given project's label applicable scope is all changes
on all branches of this project and its child projects.
Label's applicable scope can be branch specific via configuration.
E.g. create a label `Video-Qualify` on parent project and configure
the `branch` as:
====
[label "Video-Qualify"]
branch = refs/heads/video-1.0/*
branch = refs/heads/video-1.1/Kino
====
Then *only* changes in above branch scope of parent project and child
projects will be affected by `Video-Qualify`.
NOTE: The `branch` is independent from the branch scope defined in `access`
parts in `project.config` file. That means from the UI a user can always
assign permissions for that label on a branch, but this permission is then
ignored if the label doesn't apply for that branch.
[[label_example]] [[label_example]]
Example Example

View File

@@ -103,6 +103,7 @@ public class LabelType {
protected short maxPositive; protected short maxPositive;
private transient boolean canOverride; private transient boolean canOverride;
private transient List<String> refPatterns;
private transient List<Integer> intList; private transient List<Integer> intList;
private transient Map<Short, LabelValue> byValue; private transient Map<Short, LabelValue> byValue;
@@ -157,10 +158,18 @@ public class LabelType {
return canOverride; return canOverride;
} }
public List<String> getRefPatterns() {
return refPatterns;
}
public void setCanOverride(boolean canOverride) { public void setCanOverride(boolean canOverride) {
this.canOverride = canOverride; this.canOverride = canOverride;
} }
public void setRefPatterns(List<String> refPatterns) {
this.refPatterns = refPatterns;
}
public List<LabelValue> getValues() { public List<LabelValue> getValues() {
return values; return values;
} }

View File

@@ -59,6 +59,7 @@ import java.io.BufferedReader;
import java.io.IOException; import java.io.IOException;
import java.io.StringReader; import java.io.StringReader;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.EnumSet; import java.util.EnumSet;
@@ -127,6 +128,7 @@ public class ProjectConfig extends VersionedMetaData {
private static final String KEY_COPY_MAX_SCORE = "copyMaxScore"; private static final String KEY_COPY_MAX_SCORE = "copyMaxScore";
private static final String KEY_VALUE = "value"; private static final String KEY_VALUE = "value";
private static final String KEY_CAN_OVERRIDE = "canOverride"; private static final String KEY_CAN_OVERRIDE = "canOverride";
private static final String KEY_Branch = "branch";
private static final Set<String> LABEL_FUNCTIONS = ImmutableSet.of( private static final Set<String> LABEL_FUNCTIONS = ImmutableSet.of(
"MaxWithBlock", "AnyWithBlock", "MaxNoBlock", "NoBlock", "NoOp"); "MaxWithBlock", "AnyWithBlock", "MaxNoBlock", "NoBlock", "NoOp");
@@ -651,10 +653,17 @@ public class ProjectConfig extends VersionedMetaData {
rc.getBoolean(LABEL, name, KEY_COPY_MAX_SCORE, false)); rc.getBoolean(LABEL, name, KEY_COPY_MAX_SCORE, false));
label.setCanOverride( label.setCanOverride(
rc.getBoolean(LABEL, name, KEY_CAN_OVERRIDE, true)); rc.getBoolean(LABEL, name, KEY_CAN_OVERRIDE, true));
label.setRefPatterns(getStringListOrNull(rc, LABEL, name, KEY_Branch));
labelSections.put(name, label); labelSections.put(name, label);
} }
} }
private List<String> getStringListOrNull(Config rc, String section,
String subSection, String name) {
String[] ac = rc.getStringList(section, subSection, name);
return ac.length == 0 ? null : Arrays.asList(ac);
}
private void loadCommentLinkSections(Config rc) { private void loadCommentLinkSections(Config rc) {
Set<String> subsections = rc.getSubsections(COMMENTLINK); Set<String> subsections = rc.getSubsections(COMMENTLINK);
commentLinkSections = Lists.newArrayListWithCapacity(subsections.size()); commentLinkSections = Lists.newArrayListWithCapacity(subsections.size());

View File

@@ -14,8 +14,11 @@
package com.google.gerrit.server.project; package com.google.gerrit.server.project;
import com.google.common.collect.Lists;
import com.google.gerrit.common.data.LabelType;
import com.google.gerrit.common.data.LabelTypes; import com.google.gerrit.common.data.LabelTypes;
import com.google.gerrit.common.data.PermissionRange; import com.google.gerrit.common.data.PermissionRange;
import com.google.gerrit.common.data.RefConfigSection;
import com.google.gerrit.common.data.SubmitRecord; import com.google.gerrit.common.data.SubmitRecord;
import com.google.gerrit.common.data.SubmitTypeRecord; import com.google.gerrit.common.data.SubmitTypeRecord;
import com.google.gerrit.reviewdb.client.Account; import com.google.gerrit.reviewdb.client.Account;
@@ -242,9 +245,28 @@ public class ChangeControl {
&& getRefControl().canUpload(); // as long as you can upload too && getRefControl().canUpload(); // as long as you can upload too
} }
/** All available label types for this project. */ /** All available label types for this change. */
public LabelTypes getLabelTypes() { public LabelTypes getLabelTypes() {
return getProjectControl().getLabelTypes(); String destBranch = getChange().getDest().get();
List<LabelType> all = getProjectControl().getLabelTypes().getLabelTypes();
List<LabelType> r = Lists.newArrayListWithCapacity(all.size());
for (LabelType l : all) {
List<String> refs = l.getRefPatterns();
if (refs == null) {
r.add(l);
} else {
for (String refPattern : refs) {
if (RefConfigSection.isValid(refPattern)
&& match(destBranch, refPattern)) {
r.add(l);
break;
}
}
}
}
return new LabelTypes(r);
} }
/** All value ranges of any allowed label permission. */ /** All value ranges of any allowed label permission. */
@@ -403,6 +425,11 @@ public class ChangeControl {
return resultsToSubmitRecord(evaluator.getSubmitRule(), results); return resultsToSubmitRecord(evaluator.getSubmitRule(), results);
} }
private boolean match(String destBranch, String refPattern) {
return RefPatternMatcher.getMatcher(refPattern).match(destBranch,
this.getRefControl().getCurrentUser().getUserName());
}
private List<SubmitRecord> cannotSubmitDraft(ReviewDb db, PatchSet patchSet, private List<SubmitRecord> cannotSubmitDraft(ReviewDb db, PatchSet patchSet,
ChangeData cd) { ChangeData cd) {
try { try {

View File

@@ -16,9 +16,7 @@ package gerrit;
import com.google.gerrit.common.data.LabelType; import com.google.gerrit.common.data.LabelType;
import com.google.gerrit.common.data.LabelValue; import com.google.gerrit.common.data.LabelValue;
import com.google.gerrit.rules.PrologEnvironment;
import com.google.gerrit.rules.StoredValues; import com.google.gerrit.rules.StoredValues;
import com.google.gerrit.server.project.ProjectState;
import com.googlecode.prolog_cafe.lang.IntegerTerm; import com.googlecode.prolog_cafe.lang.IntegerTerm;
import com.googlecode.prolog_cafe.lang.ListTerm; import com.googlecode.prolog_cafe.lang.ListTerm;
@@ -55,14 +53,8 @@ class PRED_get_legacy_label_types_1 extends Predicate.P1 {
public Operation exec(Prolog engine) throws PrologException { public Operation exec(Prolog engine) throws PrologException {
engine.setB0(); engine.setB0();
Term a1 = arg1.dereference(); Term a1 = arg1.dereference();
List<LabelType> list =
PrologEnvironment env = (PrologEnvironment) engine.control; StoredValues.CHANGE_CONTROL.get(engine).getLabelTypes().getLabelTypes();
ProjectState state = env.getArgs().getProjectCache()
.get(StoredValues.CHANGE.get(engine).getDest().getParentKey());
if (state == null) {
return engine.fail();
}
List<LabelType> list = state.getLabelTypes().getLabelTypes();
Term head = Prolog.Nil; Term head = Prolog.Nil;
for (int idx = list.size() - 1; 0 <= idx; idx--) { for (int idx = list.size() - 1; 0 <= idx; idx--) {
head = new ListTerm(export(list.get(idx)), head); head = new ListTerm(export(list.get(idx)), head);