Merge "ProjectConfig sections refactoring"
This commit is contained in:
@@ -23,40 +23,18 @@ import java.util.List;
|
|||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
/** Portion of a {@link Project} describing access rules. */
|
/** Portion of a {@link Project} describing access rules. */
|
||||||
public class AccessSection implements Comparable<AccessSection> {
|
public class AccessSection extends RefConfigSection implements
|
||||||
|
Comparable<AccessSection> {
|
||||||
/** Special name given to the global capabilities; not a valid reference. */
|
/** Special name given to the global capabilities; not a valid reference. */
|
||||||
public static final String GLOBAL_CAPABILITIES = "GLOBAL_CAPABILITIES";
|
public static final String GLOBAL_CAPABILITIES = "GLOBAL_CAPABILITIES";
|
||||||
|
|
||||||
/** Pattern that matches all references in a project. */
|
|
||||||
public static final String ALL = "refs/*";
|
|
||||||
|
|
||||||
/** Pattern that matches all branches in a project. */
|
|
||||||
public static final String HEADS = "refs/heads/*";
|
|
||||||
|
|
||||||
/** Prefix that triggers a regular expression pattern. */
|
|
||||||
public static final String REGEX_PREFIX = "^";
|
|
||||||
|
|
||||||
/** @return true if the name is likely to be a valid access section name. */
|
|
||||||
public static boolean isAccessSection(String name) {
|
|
||||||
return name.startsWith("refs/") || name.startsWith("^refs/");
|
|
||||||
}
|
|
||||||
|
|
||||||
protected String name;
|
|
||||||
protected List<Permission> permissions;
|
protected List<Permission> permissions;
|
||||||
|
|
||||||
protected AccessSection() {
|
protected AccessSection() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public AccessSection(String refPattern) {
|
public AccessSection(String refPattern) {
|
||||||
setName(refPattern);
|
super(refPattern);
|
||||||
}
|
|
||||||
|
|
||||||
public String getName() {
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setName(String name) {
|
|
||||||
this.name = name;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Permission> getPermissions() {
|
public List<Permission> getPermissions() {
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
// Copyright (C) 2011 The Android Open Source Project
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package com.google.gerrit.common.data;
|
||||||
|
|
||||||
|
public abstract class RefConfigSection {
|
||||||
|
/** Pattern that matches all references in a project. */
|
||||||
|
public static final String ALL = "refs/*";
|
||||||
|
|
||||||
|
/** Pattern that matches all branches in a project. */
|
||||||
|
public static final String HEADS = "refs/heads/*";
|
||||||
|
|
||||||
|
/** Prefix that triggers a regular expression pattern. */
|
||||||
|
public static final String REGEX_PREFIX = "^";
|
||||||
|
|
||||||
|
/** @return true if the name is likely to be a valid reference section name. */
|
||||||
|
public static boolean isValid(String name) {
|
||||||
|
return name.startsWith("refs/") || name.startsWith("^refs/");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected String name;
|
||||||
|
|
||||||
|
public RefConfigSection() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public RefConfigSection(String name) {
|
||||||
|
setName(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -23,4 +23,8 @@ public class InvalidNameException extends Exception {
|
|||||||
public InvalidNameException() {
|
public InvalidNameException() {
|
||||||
super(MESSAGE);
|
super(MESSAGE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public InvalidNameException(String invalidName) {
|
||||||
|
super(MESSAGE + ": " + invalidName);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import com.google.gerrit.common.data.AccessSection;
|
|||||||
import com.google.gerrit.common.data.ApprovalType;
|
import com.google.gerrit.common.data.ApprovalType;
|
||||||
import com.google.gerrit.common.data.Permission;
|
import com.google.gerrit.common.data.Permission;
|
||||||
import com.google.gerrit.common.data.ProjectAccess;
|
import com.google.gerrit.common.data.ProjectAccess;
|
||||||
|
import com.google.gerrit.common.data.RefConfigSection;
|
||||||
import com.google.gwt.core.client.GWT;
|
import com.google.gwt.core.client.GWT;
|
||||||
import com.google.gwt.core.client.Scheduler;
|
import com.google.gwt.core.client.Scheduler;
|
||||||
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
|
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
|
||||||
@@ -122,7 +123,8 @@ public class AccessSectionEditor extends Composite implements
|
|||||||
void onDeleteSection(ClickEvent event) {
|
void onDeleteSection(ClickEvent event) {
|
||||||
isDeleted = true;
|
isDeleted = true;
|
||||||
|
|
||||||
if (name.isVisible() && AccessSection.isAccessSection(name.getValue())){
|
if (name.isVisible()
|
||||||
|
&& RefConfigSection.isValid(name.getValue())) {
|
||||||
deletedName.setInnerText(Util.M.deletedReference(name.getValue()));
|
deletedName.setInnerText(Util.M.deletedReference(name.getValue()));
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
@@ -185,7 +187,7 @@ public class AccessSectionEditor extends Composite implements
|
|||||||
name.setEnabled(!readOnly);
|
name.setEnabled(!readOnly);
|
||||||
deleteSection.setVisible(!readOnly);
|
deleteSection.setVisible(!readOnly);
|
||||||
|
|
||||||
if (AccessSection.isAccessSection(value.getName())) {
|
if (RefConfigSection.isValid(value.getName())) {
|
||||||
name.setVisible(true);
|
name.setVisible(true);
|
||||||
name.setIgnoreEditorValue(false);
|
name.setIgnoreEditorValue(false);
|
||||||
sectionType.setInnerText(Util.C.sectionTypeReference());
|
sectionType.setInnerText(Util.C.sectionTypeReference());
|
||||||
@@ -225,8 +227,9 @@ public class AccessSectionEditor extends Composite implements
|
|||||||
perms.add(varName);
|
perms.add(varName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (AccessSection.isAccessSection(value.getName())) {
|
} else if (RefConfigSection.isValid(value.getName())) {
|
||||||
for (ApprovalType t : Gerrit.getConfig().getApprovalTypes().getApprovalTypes()) {
|
for (ApprovalType t : Gerrit.getConfig().getApprovalTypes()
|
||||||
|
.getApprovalTypes()) {
|
||||||
String varName = Permission.LABEL + t.getCategory().getLabelName();
|
String varName = Permission.LABEL + t.getCategory().getLabelName();
|
||||||
if (value.getPermission(varName) == null) {
|
if (value.getPermission(varName) == null) {
|
||||||
perms.add(varName);
|
perms.add(varName);
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import com.google.gerrit.common.data.GroupReference;
|
|||||||
import com.google.gerrit.common.data.Permission;
|
import com.google.gerrit.common.data.Permission;
|
||||||
import com.google.gerrit.common.data.PermissionRange;
|
import com.google.gerrit.common.data.PermissionRange;
|
||||||
import com.google.gerrit.common.data.PermissionRule;
|
import com.google.gerrit.common.data.PermissionRule;
|
||||||
|
import com.google.gerrit.common.data.RefConfigSection;
|
||||||
import com.google.gwt.core.client.GWT;
|
import com.google.gwt.core.client.GWT;
|
||||||
import com.google.gwt.core.client.Scheduler;
|
import com.google.gwt.core.client.Scheduler;
|
||||||
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
|
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
|
||||||
@@ -114,7 +115,8 @@ public class PermissionEditor extends Composite implements Editor<Permission>,
|
|||||||
rules = ListEditor.of(new RuleEditorSource());
|
rules = ListEditor.of(new RuleEditorSource());
|
||||||
|
|
||||||
exclusiveGroup.setEnabled(!readOnly);
|
exclusiveGroup.setEnabled(!readOnly);
|
||||||
exclusiveGroup.setVisible(AccessSection.isAccessSection(section.getName()));
|
exclusiveGroup.setVisible(RefConfigSection
|
||||||
|
.isValid(section.getName()));
|
||||||
|
|
||||||
if (readOnly) {
|
if (readOnly) {
|
||||||
addContainer.removeFromParent();
|
addContainer.removeFromParent();
|
||||||
|
|||||||
@@ -38,7 +38,6 @@ import com.google.inject.assistedinject.Assisted;
|
|||||||
import org.eclipse.jgit.errors.ConfigInvalidException;
|
import org.eclipse.jgit.errors.ConfigInvalidException;
|
||||||
import org.eclipse.jgit.errors.RepositoryNotFoundException;
|
import org.eclipse.jgit.errors.RepositoryNotFoundException;
|
||||||
import org.eclipse.jgit.lib.ObjectId;
|
import org.eclipse.jgit.lib.ObjectId;
|
||||||
import org.eclipse.jgit.lib.Repository;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@@ -117,28 +116,12 @@ class ChangeProjectAccess extends Handler<ProjectAccess> {
|
|||||||
}
|
}
|
||||||
replace(config, toDelete, section);
|
replace(config, toDelete, section);
|
||||||
|
|
||||||
} else if (AccessSection.isAccessSection(name)) {
|
} else if (AccessSection.isValid(name)) {
|
||||||
if (!projectControl.controlForRef(name).isOwner()) {
|
if (!projectControl.controlForRef(name).isOwner()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (name.startsWith(AccessSection.REGEX_PREFIX)) {
|
RefControl.validateRefPattern(name);
|
||||||
if (!Repository.isValidRefName(RefControl.shortestExample(name))) {
|
|
||||||
throw new InvalidNameException();
|
|
||||||
}
|
|
||||||
|
|
||||||
} else if (name.equals(AccessSection.ALL)) {
|
|
||||||
// This is a special case we have to allow, it fails below.
|
|
||||||
|
|
||||||
} else if (name.endsWith("/*")) {
|
|
||||||
String prefix = name.substring(0, name.length() - 2);
|
|
||||||
if (!Repository.isValidRefName(prefix)) {
|
|
||||||
throw new InvalidNameException();
|
|
||||||
}
|
|
||||||
|
|
||||||
} else if (!Repository.isValidRefName(name)) {
|
|
||||||
throw new InvalidNameException();
|
|
||||||
}
|
|
||||||
|
|
||||||
replace(config, toDelete, section);
|
replace(config, toDelete, section);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import com.google.gerrit.common.data.AccessSection;
|
|||||||
import com.google.gerrit.common.data.Permission;
|
import com.google.gerrit.common.data.Permission;
|
||||||
import com.google.gerrit.common.data.PermissionRule;
|
import com.google.gerrit.common.data.PermissionRule;
|
||||||
import com.google.gerrit.common.data.ProjectAccess;
|
import com.google.gerrit.common.data.ProjectAccess;
|
||||||
|
import com.google.gerrit.common.data.RefConfigSection;
|
||||||
import com.google.gerrit.common.errors.NoSuchGroupException;
|
import com.google.gerrit.common.errors.NoSuchGroupException;
|
||||||
import com.google.gerrit.httpd.rpc.Handler;
|
import com.google.gerrit.httpd.rpc.Handler;
|
||||||
import com.google.gerrit.reviewdb.client.AccountGroup;
|
import com.google.gerrit.reviewdb.client.AccountGroup;
|
||||||
@@ -122,7 +123,7 @@ class ProjectAccessFactory extends Handler<ProjectAccess> {
|
|||||||
ownerOf.add(name);
|
ownerOf.add(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
} else if (AccessSection.isAccessSection(name)) {
|
} else if (RefConfigSection.isValid(name)) {
|
||||||
RefControl rc = pc.controlForRef(name);
|
RefControl rc = pc.controlForRef(name);
|
||||||
if (rc.isOwner()) {
|
if (rc.isOwner()) {
|
||||||
local.add(section);
|
local.add(section);
|
||||||
|
|||||||
@@ -14,7 +14,6 @@
|
|||||||
|
|
||||||
package com.google.gerrit.server.git;
|
package com.google.gerrit.server.git;
|
||||||
|
|
||||||
import static com.google.gerrit.common.data.AccessSection.isAccessSection;
|
|
||||||
import static com.google.gerrit.common.data.Permission.isPermission;
|
import static com.google.gerrit.common.data.Permission.isPermission;
|
||||||
|
|
||||||
import com.google.gerrit.common.data.AccessSection;
|
import com.google.gerrit.common.data.AccessSection;
|
||||||
@@ -27,6 +26,7 @@ import com.google.gerrit.reviewdb.client.Project;
|
|||||||
import com.google.gerrit.reviewdb.client.Project.State;
|
import com.google.gerrit.reviewdb.client.Project.State;
|
||||||
import com.google.gerrit.reviewdb.client.Project.SubmitType;
|
import com.google.gerrit.reviewdb.client.Project.SubmitType;
|
||||||
import com.google.gerrit.server.account.GroupCache;
|
import com.google.gerrit.server.account.GroupCache;
|
||||||
|
import com.google.gerrit.common.data.RefConfigSection;
|
||||||
|
|
||||||
import org.eclipse.jgit.errors.ConfigInvalidException;
|
import org.eclipse.jgit.errors.ConfigInvalidException;
|
||||||
import org.eclipse.jgit.lib.CommitBuilder;
|
import org.eclipse.jgit.lib.CommitBuilder;
|
||||||
@@ -225,7 +225,7 @@ public class ProjectConfig extends VersionedMetaData {
|
|||||||
|
|
||||||
accessSections = new HashMap<String, AccessSection>();
|
accessSections = new HashMap<String, AccessSection>();
|
||||||
for (String refName : rc.getSubsections(ACCESS)) {
|
for (String refName : rc.getSubsections(ACCESS)) {
|
||||||
if (isAccessSection(refName)) {
|
if (RefConfigSection.isValid(refName)) {
|
||||||
AccessSection as = getAccessSection(refName, true);
|
AccessSection as = getAccessSection(refName, true);
|
||||||
|
|
||||||
for (String varName : rc.getStringList(ACCESS, refName, KEY_GROUP_PERMISSIONS)) {
|
for (String varName : rc.getStringList(ACCESS, refName, KEY_GROUP_PERMISSIONS)) {
|
||||||
@@ -421,7 +421,7 @@ public class ProjectConfig extends VersionedMetaData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (String name : rc.getSubsections(ACCESS)) {
|
for (String name : rc.getSubsections(ACCESS)) {
|
||||||
if (isAccessSection(name) && !accessSections.containsKey(name)) {
|
if (RefConfigSection.isValid(name) && !accessSections.containsKey(name)) {
|
||||||
rc.unsetSection(ACCESS, name);
|
rc.unsetSection(ACCESS, name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ import com.google.gerrit.common.data.AccessSection;
|
|||||||
import com.google.gerrit.common.data.Permission;
|
import com.google.gerrit.common.data.Permission;
|
||||||
import com.google.gerrit.common.data.PermissionRange;
|
import com.google.gerrit.common.data.PermissionRange;
|
||||||
import com.google.gerrit.common.data.PermissionRule;
|
import com.google.gerrit.common.data.PermissionRule;
|
||||||
|
import com.google.gerrit.common.data.RefConfigSection;
|
||||||
|
import com.google.gerrit.common.errors.InvalidNameException;
|
||||||
import com.google.gerrit.reviewdb.client.Project;
|
import com.google.gerrit.reviewdb.client.Project;
|
||||||
import com.google.gerrit.server.CurrentUser;
|
import com.google.gerrit.server.CurrentUser;
|
||||||
import com.google.gerrit.server.IdentifiedUser;
|
import com.google.gerrit.server.IdentifiedUser;
|
||||||
@@ -26,6 +28,7 @@ import com.google.gerrit.server.git.GitRepositoryManager;
|
|||||||
import dk.brics.automaton.RegExp;
|
import dk.brics.automaton.RegExp;
|
||||||
|
|
||||||
import org.eclipse.jgit.lib.PersonIdent;
|
import org.eclipse.jgit.lib.PersonIdent;
|
||||||
|
import org.eclipse.jgit.lib.Repository;
|
||||||
import org.eclipse.jgit.revwalk.RevCommit;
|
import org.eclipse.jgit.revwalk.RevCommit;
|
||||||
import org.eclipse.jgit.revwalk.RevObject;
|
import org.eclipse.jgit.revwalk.RevObject;
|
||||||
import org.eclipse.jgit.revwalk.RevTag;
|
import org.eclipse.jgit.revwalk.RevTag;
|
||||||
@@ -400,7 +403,7 @@ public class RefControl {
|
|||||||
return mine;
|
return mine;
|
||||||
}
|
}
|
||||||
|
|
||||||
static boolean isRE(String refPattern) {
|
public static boolean isRE(String refPattern) {
|
||||||
return refPattern.startsWith(AccessSection.REGEX_PREFIX);
|
return refPattern.startsWith(AccessSection.REGEX_PREFIX);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -414,10 +417,28 @@ public class RefControl {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static RegExp toRegExp(String refPattern) {
|
public static RegExp toRegExp(String refPattern) {
|
||||||
if (isRE(refPattern)) {
|
if (isRE(refPattern)) {
|
||||||
refPattern = refPattern.substring(1);
|
refPattern = refPattern.substring(1);
|
||||||
}
|
}
|
||||||
return new RegExp(refPattern, RegExp.NONE);
|
return new RegExp(refPattern, RegExp.NONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void validateRefPattern(String refPattern)
|
||||||
|
throws InvalidNameException {
|
||||||
|
if (refPattern.startsWith(RefConfigSection.REGEX_PREFIX)) {
|
||||||
|
if (!Repository.isValidRefName(RefControl.shortestExample(refPattern))) {
|
||||||
|
throw new InvalidNameException(refPattern);
|
||||||
|
}
|
||||||
|
} else if (refPattern.equals(RefConfigSection.ALL)) {
|
||||||
|
// This is a special case we have to allow, it fails below.
|
||||||
|
} else if (refPattern.endsWith("/*")) {
|
||||||
|
String prefix = refPattern.substring(0, refPattern.length() - 2);
|
||||||
|
if (!Repository.isValidRefName(prefix)) {
|
||||||
|
throw new InvalidNameException(refPattern);
|
||||||
|
}
|
||||||
|
} else if (!Repository.isValidRefName(refPattern)) {
|
||||||
|
throw new InvalidNameException(refPattern);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ import java.util.regex.Pattern;
|
|||||||
abstract class SectionMatcher {
|
abstract class SectionMatcher {
|
||||||
static SectionMatcher wrap(AccessSection section) {
|
static SectionMatcher wrap(AccessSection section) {
|
||||||
String ref = section.getName();
|
String ref = section.getName();
|
||||||
if (AccessSection.isAccessSection(ref)) {
|
if (AccessSection.isValid(ref)) {
|
||||||
return wrap(ref, section);
|
return wrap(ref, section);
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -14,24 +14,18 @@
|
|||||||
|
|
||||||
package com.google.gerrit.server.project;
|
package com.google.gerrit.server.project;
|
||||||
|
|
||||||
import static com.google.gerrit.server.project.RefControl.isRE;
|
|
||||||
import static com.google.gerrit.server.project.RefControl.shortestExample;
|
|
||||||
import static com.google.gerrit.server.project.RefControl.toRegExp;
|
|
||||||
|
|
||||||
import com.google.gerrit.common.data.AccessSection;
|
import com.google.gerrit.common.data.AccessSection;
|
||||||
import com.google.gerrit.server.cache.Cache;
|
import com.google.gerrit.server.cache.Cache;
|
||||||
import com.google.gerrit.server.cache.CacheModule;
|
import com.google.gerrit.server.cache.CacheModule;
|
||||||
|
import com.google.gerrit.server.util.MostSpecificComparator;
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
import com.google.inject.Module;
|
import com.google.inject.Module;
|
||||||
import com.google.inject.Singleton;
|
import com.google.inject.Singleton;
|
||||||
import com.google.inject.TypeLiteral;
|
import com.google.inject.TypeLiteral;
|
||||||
import com.google.inject.name.Named;
|
import com.google.inject.name.Named;
|
||||||
|
|
||||||
import org.apache.commons.lang.StringUtils;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.IdentityHashMap;
|
import java.util.IdentityHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -161,103 +155,5 @@ public class SectionSortCache {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 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 static final class MostSpecificComparator implements
|
|
||||||
Comparator<AccessSection> {
|
|
||||||
private final String refName;
|
|
||||||
|
|
||||||
MostSpecificComparator(String refName) {
|
|
||||||
this.refName = refName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int compare(AccessSection a, AccessSection b) {
|
|
||||||
return compare(a.getName(), b.getName());
|
|
||||||
}
|
|
||||||
|
|
||||||
private int compare(final String pattern1, final String pattern2) {
|
|
||||||
int cmp = distance(pattern1) - distance(pattern2);
|
|
||||||
if (cmp == 0) {
|
|
||||||
boolean p1_finite = finite(pattern1);
|
|
||||||
boolean p2_finite = finite(pattern2);
|
|
||||||
|
|
||||||
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 (isRE(pattern)) {
|
|
||||||
example = shortestExample(pattern);
|
|
||||||
|
|
||||||
} else if (pattern.endsWith("/*")) {
|
|
||||||
example = pattern.substring(0, pattern.length() - 1) + '1';
|
|
||||||
|
|
||||||
} else if (pattern.equals(refName)) {
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
} else {
|
|
||||||
return Math.max(pattern.length(), refName.length());
|
|
||||||
}
|
|
||||||
return StringUtils.getLevenshteinDistance(example, refName);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean finite(String pattern) {
|
|
||||||
if (isRE(pattern)) {
|
|
||||||
return toRegExp(pattern).toAutomaton().isFinite();
|
|
||||||
|
|
||||||
} else if (pattern.endsWith("/*")) {
|
|
||||||
return false;
|
|
||||||
|
|
||||||
} else {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private int transitions(String pattern) {
|
|
||||||
if (isRE(pattern)) {
|
|
||||||
return toRegExp(pattern).toAutomaton().getNumberOfTransitions();
|
|
||||||
|
|
||||||
} else if (pattern.endsWith("/*")) {
|
|
||||||
return pattern.length();
|
|
||||||
|
|
||||||
} else {
|
|
||||||
return pattern.length();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,124 @@
|
|||||||
|
// Copyright (C) 2011 The Android Open Source Project
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package com.google.gerrit.server.util;
|
||||||
|
|
||||||
|
import com.google.gerrit.common.data.RefConfigSection;
|
||||||
|
import com.google.gerrit.server.project.RefControl;
|
||||||
|
|
||||||
|
import org.apache.commons.lang.StringUtils;
|
||||||
|
|
||||||
|
import java.util.Comparator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
public final class MostSpecificComparator implements
|
||||||
|
Comparator<RefConfigSection> {
|
||||||
|
private final String refName;
|
||||||
|
|
||||||
|
public MostSpecificComparator(String refName) {
|
||||||
|
this.refName = refName;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compare(RefConfigSection a, RefConfigSection b) {
|
||||||
|
return compare(a.getName(), b.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
public int compare(final String pattern1, final String pattern2) {
|
||||||
|
int cmp = distance(pattern1) - distance(pattern2);
|
||||||
|
if (cmp == 0) {
|
||||||
|
boolean p1_finite = finite(pattern1);
|
||||||
|
boolean p2_finite = finite(pattern2);
|
||||||
|
|
||||||
|
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 (RefControl.isRE(pattern)) {
|
||||||
|
example = RefControl.shortestExample(pattern);
|
||||||
|
|
||||||
|
} else if (pattern.endsWith("/*")) {
|
||||||
|
example = pattern.substring(0, pattern.length() - 1) + '1';
|
||||||
|
|
||||||
|
} else if (pattern.equals(refName)) {
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
return Math.max(pattern.length(), refName.length());
|
||||||
|
}
|
||||||
|
return StringUtils.getLevenshteinDistance(example, refName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean finite(String pattern) {
|
||||||
|
if (RefControl.isRE(pattern)) {
|
||||||
|
return RefControl.toRegExp(pattern).toAutomaton().isFinite();
|
||||||
|
|
||||||
|
} else if (pattern.endsWith("/*")) {
|
||||||
|
return false;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int transitions(String pattern) {
|
||||||
|
if (RefControl.isRE(pattern)) {
|
||||||
|
return RefControl.toRegExp(pattern).toAutomaton()
|
||||||
|
.getNumberOfTransitions();
|
||||||
|
|
||||||
|
} else if (pattern.endsWith("/*")) {
|
||||||
|
return pattern.length();
|
||||||
|
|
||||||
|
} else {
|
||||||
|
return pattern.length();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user