Support regular expressions for ref access rules

This change considers the rights of the most specific ref pattern.
If the ref pattern starts with ^ it is considered to be a regular
expression, otherwise the older glob style or exact match rules
are used.

Change-Id: Ie060d3758e5184a7cedd38883253f60817a04e1b
Portions-by: carloseduardo.baldacin <carloseduardo.baldacin@sonyericsson.com>
Signed-off-by: Shawn O. Pearce <sop@google.com>
This commit is contained in:
Shawn O. Pearce
2010-06-23 14:39:19 -03:00
parent cf60707301
commit 993ddfb847
9 changed files with 241 additions and 52 deletions

View File

@@ -34,6 +34,9 @@ import com.google.gerrit.reviewdb.RefRight;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
import dk.brics.automaton.RegExp;
import org.apache.commons.lang.StringUtils;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevObject;
@@ -49,6 +52,7 @@ import java.util.List;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.regex.Pattern;
/** Manages access control for Git references (aka branches, tags). */
@@ -59,9 +63,17 @@ public class RefControl {
private Boolean canForgeAuthor;
private Boolean canForgeCommitter;
RefControl(final ProjectControl projectControl, final String refName) {
RefControl(final ProjectControl projectControl, String ref) {
if (ref.startsWith(RefRight.REGEX_PREFIX)) {
ref = shortestExample(ref);
} else if (ref.endsWith("/*")) {
ref = ref.substring(0, ref.length() - 1);
}
this.projectControl = projectControl;
this.refName = refName;
this.refName = ref;
}
public String getRefName() {
@@ -94,7 +106,9 @@ public class RefControl {
// calls us to find out if there is ownership of all references in
// order to determine project level ownership.
//
if (!RefRight.ALL.equals(getRefName()) && getProjectControl().isOwner()) {
if (getRefName().equals(
RefRight.ALL.substring(0, RefRight.ALL.length() - 1))
&& getProjectControl().isOwner()) {
return true;
}
@@ -302,19 +316,95 @@ public class RefControl {
return val >= level;
}
public static final Comparator<String> DESCENDING_SORT =
/**
* Order the Ref Pattern by the most specific. This sort is done by:
* <ul>
* <li>1 - The minor value of Levenshtein string distance between the branch
* name and the regex string shortest example. A shorter distance is a more
* specific match.
* <li>2 - Finites first, infinities after.
* <li>3 - Number of transitions.
* <li>4 - Length of the expression text.
* </ul>
*
* Levenshtein distance is a measure of the similarity between two strings.
* The distance is the number of deletions, insertions, or substitutions
* required to transform one string into another.
*
* For example, if given refs/heads/m* and refs/heads/*, the distances are 5
* and 6. It means that refs/heads/m* is more specific because it's closer to
* refs/heads/master than refs/heads/*.
*
* Another example could be refs/heads/* and refs/heads/[a-zA-Z]*, the
* distances are both 6. Both are infinite, but refs/heads/[a-zA-Z]* has more
* transitions, which after all turns it more specific.
*/
private final Comparator<String> BY_MOST_SPECIFIC_SORT =
new Comparator<String>() {
public int compare(final String pattern1, final String pattern2) {
int cmp = distance(pattern2) - distance(pattern1);
if (cmp == 0) {
boolean p1_finite = finite(pattern1);
boolean p2_finite = finite(pattern2);
@Override
public int compare(String a, String b) {
int aLength = a.length();
int bLength = b.length();
if (bLength == aLength) {
return a.compareTo(b);
}
return bLength - aLength;
}
};
if (p1_finite && !p2_finite) {
cmp = -1;
} else if (!p1_finite && p2_finite) {
cmp = 1;
} else /* if (f1 == f2) */{
cmp = 0;
}
}
if (cmp == 0) {
cmp = transitions(pattern1) - transitions(pattern2);
}
if (cmp == 0) {
cmp = pattern2.length() - pattern1.length();
}
return cmp;
}
private int distance(String pattern) {
String example;
if (pattern.startsWith(RefRight.REGEX_PREFIX)) {
example = shortestExample(pattern);
} else if (pattern.endsWith("/*")) {
example = pattern.substring(0, pattern.length() - 1) + '1';
} else if (pattern.equals(getRefName())) {
return 0;
} else {
return Math.max(pattern.length(), getRefName().length());
}
return StringUtils.getLevenshteinDistance(example, getRefName());
}
private boolean finite(String pattern) {
if (pattern.startsWith(RefRight.REGEX_PREFIX)) {
return toRegExp(pattern).toAutomaton().isFinite();
} else if (pattern.endsWith("/*")) {
return false;
} else {
return true;
}
}
private int transitions(String pattern) {
if (pattern.startsWith(RefRight.REGEX_PREFIX)) {
return toRegExp(pattern).toAutomaton().getNumberOfTransitions();
} else if (pattern.endsWith("/*")) {
return pattern.length();
} else {
return pattern.length();
}
}
};
/**
* Sorts all given rights into a map, ordered by descending length of
@@ -338,10 +428,10 @@ public class RefControl {
* @param actionRights
* @return A sorted map keyed off the ref pattern of all rights.
*/
private static SortedMap<String, RefRightsForPattern> sortedRightsByPattern(
private SortedMap<String, RefRightsForPattern> sortedRightsByPattern(
List<RefRight> actionRights) {
SortedMap<String, RefRightsForPattern> rights =
new TreeMap<String, RefRightsForPattern>(DESCENDING_SORT);
new TreeMap<String, RefRightsForPattern>(BY_MOST_SPECIFIC_SORT);
for (RefRight actionRight : actionRights) {
RefRightsForPattern patternRights =
rights.get(actionRight.getRefPattern());
@@ -403,6 +493,10 @@ public class RefControl {
}
public static boolean matches(String refName, String refPattern) {
if (refPattern.startsWith(RefRight.REGEX_PREFIX)) {
return Pattern.matches(refPattern, refName);
}
if (refPattern.endsWith("/*")) {
String prefix = refPattern.substring(0, refPattern.length() - 1);
return refName.startsWith(prefix);
@@ -411,4 +505,21 @@ public class RefControl {
return refName.equals(refPattern);
}
}
public static String shortestExample(String pattern) {
if (pattern.startsWith(RefRight.REGEX_PREFIX)) {
return toRegExp(pattern).toAutomaton().getShortestExample(true);
} else if (pattern.endsWith("/*")) {
return pattern.substring(0, pattern.length() - 1) + '1';
} else {
return pattern;
}
}
private static RegExp toRegExp(String refPattern) {
if (refPattern.startsWith(RefRight.REGEX_PREFIX)) {
refPattern = refPattern.substring(1);
}
return new RegExp(refPattern);
}
}

View File

@@ -19,19 +19,33 @@ import com.google.gerrit.reviewdb.Project;
import com.google.gerrit.reviewdb.RefRight;
import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.reviewdb.RefRight.RefPattern;
import com.google.gerrit.server.project.RefControl;
import com.google.gerrit.server.project.RefControl.RefRightsForPattern;
import com.google.gwtorm.client.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
public class Schema_34 extends SchemaVersion {
private static final Comparator<String> DESCENDING_SORT =
new Comparator<String>() {
@Override
public int compare(String a, String b) {
int aLength = a.length();
int bLength = b.length();
if (bLength == aLength) {
return a.compareTo(b);
}
return bLength - aLength;
}
};
@Inject
Schema_34(Provider<Schema_33> prior) {
super(prior);
@@ -54,7 +68,7 @@ public class Schema_34 extends SchemaVersion {
ApprovalCategory.Id cat = right.getApprovalCategoryId();
if (r.get(cat) == null) {
Map<String, RefRightsForPattern> m =
new TreeMap<String, RefRightsForPattern>(RefControl.DESCENDING_SORT);
new TreeMap<String, RefRightsForPattern>(DESCENDING_SORT);
r.put(cat, m);
}
if (r.get(cat).get(right.getRefPattern()) == null) {