Block inheritance by default on per-branch permissions.

This change permits a single RefRight to block an ApprovalCategory for a given
ref. In other words, if there happens to be a change going to the project
tools/gerrit and that change is for the ref /refs/heads/master, given
the following rights:

in -- All Projects --:

Administrators  Code Review +2 refs/heads/*

in tools/gerrit:

gerrit-devs Code Review +2 refs/heads/*
Dr. No      Code Review +2 refs/heads/master

Then the change can only be Code Review +2 by members of the Dr. No group.

Change-Id: Ib72ac3cc7131c40d4209e438c70ae3e2b08e5830
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/PatchSetPublishDetailFactory.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/PatchSetPublishDetailFactory.java
index 53fe746..179a571 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/PatchSetPublishDetailFactory.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/PatchSetPublishDetailFactory.java
@@ -42,7 +42,9 @@
 import com.google.inject.Inject;
 import com.google.inject.assistedinject.Assisted;
 
+import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
@@ -126,19 +128,31 @@
   private void computeAllowed() {
     final Set<AccountGroup.Id> am = user.getEffectiveGroups();
     final ProjectState pe = projectCache.get(change.getProject());
-    computeAllowed(am, pe.getLocalRights());
-    computeAllowed(am, pe.getInheritedRights());
+    List<RefRight> allRights = new ArrayList<RefRight>();
+    allRights.addAll(filterMatching(pe.getLocalRights()));
+    allRights.addAll(filterMatching(pe.getInheritedRights()));
+    Collections.sort(allRights, RefRight.REF_PATTERN_ORDER);
+    allRights = RefControl.filterMostSpecific(allRights);
+    computeAllowed(am, allRights);
+  }
+
+  private List<RefRight> filterMatching(Collection<RefRight> rights) {
+    List<RefRight> result = new ArrayList<RefRight>();
+    for (RefRight right : rights) {
+      if (RefControl.matches(change.getDest().get(), right.getRefPattern())) {
+        result.add(right);
+      }
+    }
+    return result;
   }
 
   private void computeAllowed(final Set<AccountGroup.Id> am,
-      final Collection<RefRight> list) {
+      final List<RefRight> list) {
+
     for (final RefRight r : list) {
       if (!am.contains(r.getAccountGroupId())) {
         continue;
       }
-      if (!RefControl.matches(change.getDest().get(), r.getRefPattern())) {
-        continue;
-      }
 
       Set<ApprovalCategoryValue.Id> s = allowed.get(r.getApprovalCategoryId());
       if (s == null) {
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/RefRight.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/RefRight.java
index 6994a60..301da87 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/RefRight.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/RefRight.java
@@ -18,6 +18,8 @@
 import com.google.gwtorm.client.CompoundKey;
 import com.google.gwtorm.client.StringKey;
 
+import java.util.Comparator;
+
 /** Grant to use an {@link ApprovalCategory} in the scope of a git ref. */
 public final class RefRight {
   public static class RefPattern extends
@@ -146,4 +148,20 @@
   public void setMaxValue(final short m) {
     maxValue = m;
   }
+
+  private static class RefPatternOrder implements Comparator<RefRight> {
+
+    @Override
+    public int compare(RefRight a, RefRight b) {
+      int aLength = a.getRefPattern().length();
+      int bLength = b.getRefPattern().length();
+      if ((bLength - aLength) == 0) {
+        return a.getApprovalCategoryId().get()
+            .compareTo(b.getApprovalCategoryId().get());
+      }
+      return bLength - aLength;
+    }
+  }
+
+  public static final RefPatternOrder REF_PATTERN_ORDER = new RefPatternOrder();
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java
index 32df41e..74dc7a2 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java
@@ -40,6 +40,7 @@
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.List;
 import java.util.Set;
 
@@ -183,9 +184,19 @@
   private boolean canPerform(ApprovalCategory.Id actionId, short level) {
     final Set<AccountGroup.Id> groups = getCurrentUser().getEffectiveGroups();
     int val = Integer.MIN_VALUE;
-    for (final RefRight right : getLocalRights()) {
-      if (right.getApprovalCategoryId().equals(actionId)
-          && groups.contains(right.getAccountGroupId())) {
+
+    List<RefRight> allRights = new ArrayList<RefRight>();
+    allRights.addAll(getLocalRights(actionId));
+
+    if (actionId.canInheritFromWildProject()) {
+      allRights.addAll(getInheritedRights(actionId));
+    }
+
+    // Sort in descending refPattern length
+    Collections.sort(allRights, RefRight.REF_PATTERN_ORDER);
+
+    for (RefRight right : filterMostSpecific(allRights)) {
+      if (groups.contains(right.getAccountGroupId())) {
         if (val < 0 && right.getMaxValue() > 0) {
           // If one of the user's groups had denied them access, but
           // this group grants them access, prefer the grant over
@@ -200,31 +211,50 @@
         }
       }
     }
-
-    if (val == Integer.MIN_VALUE && actionId.canInheritFromWildProject()) {
-      for (final RefRight pr : getInheritedRights()) {
-        if (actionId.equals(pr.getApprovalCategoryId())
-            && groups.contains(pr.getAccountGroupId())) {
-          val = Math.max(pr.getMaxValue(), val);
-        }
-      }
-    }
-
     return val >= level;
   }
 
-  private Collection<RefRight> getLocalRights() {
-    return filter(projectControl.getProjectState().getLocalRights());
+  public static List<RefRight> filterMostSpecific(List<RefRight> actionRights) {
+    // Grab the first set of RefRight which have the same refPattern
+    // those are the most specific RefRights we have, and are the
+    // we will consider to verify if this action can be performed.
+    // We do this so that one can override the ref rights for a specific
+    // project on a specific branch
+    boolean sameRefPattern = true;
+    List<RefRight> mostSpecific = new ArrayList<RefRight>();
+    String currentRefPattern = null;
+    int i = 0;
+    while (sameRefPattern && i < actionRights.size()) {
+      if (currentRefPattern == null) {
+        currentRefPattern = actionRights.get(i).getRefPattern();
+        mostSpecific.add(actionRights.get(i));
+        i++;
+      } else {
+        if (currentRefPattern.equals(actionRights.get(i).getRefPattern())) {
+          mostSpecific.add(actionRights.get(i));
+          i++;
+        } else {
+          sameRefPattern = false;
+        }
+      }
+    }
+    return mostSpecific;
   }
 
-  private Collection<RefRight> getInheritedRights() {
-    return filter(projectControl.getProjectState().getInheritedRights());
+  private List<RefRight> getLocalRights(ApprovalCategory.Id actionId) {
+    return filter(projectControl.getProjectState().getLocalRights(), actionId);
   }
 
-  private Collection<RefRight> filter(Collection<RefRight> all) {
+  private List<RefRight> getInheritedRights(ApprovalCategory.Id actionId) {
+    return filter(projectControl.getProjectState().getInheritedRights(), actionId);
+  }
+
+  private List<RefRight> filter(Collection<RefRight> all,
+      ApprovalCategory.Id actionId) {
     List<RefRight> mine = new ArrayList<RefRight>(all.size());
     for (RefRight right : all) {
-      if (matches(getRefName(), right.getRefPattern())) {
+      if (matches(getRefName(), right.getRefPattern()) &&
+          right.getApprovalCategoryId().equals(actionId)) {
         mine.add(right);
       }
     }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/workflow/FunctionState.java b/gerrit-server/src/main/java/com/google/gerrit/server/workflow/FunctionState.java
index 0b85db1..63b3883 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/workflow/FunctionState.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/workflow/FunctionState.java
@@ -58,10 +58,10 @@
       new HashMap<ApprovalCategory.Id, Boolean>();
   private final Change change;
   private final ProjectState project;
-  private final Map<ApprovalCategory.Id, Collection<RefRight>> allRights =
-      new HashMap<ApprovalCategory.Id, Collection<RefRight>>();
-  private Map<ApprovalCategory.Id, Collection<RefRight>> RefRights;
-  private Map<ApprovalCategory.Id, Collection<RefRight>> inheritedRights;
+  private final Map<ApprovalCategory.Id, List<RefRight>> allRights =
+      new HashMap<ApprovalCategory.Id, List<RefRight>>();
+  private Map<ApprovalCategory.Id, List<RefRight>> refRights;
+  private Map<ApprovalCategory.Id, List<RefRight>> inheritedRights;
   private Set<PatchSetApproval> modified;
 
   @Inject
@@ -136,53 +136,46 @@
     return Collections.emptySet();
   }
 
-  public Collection<RefRight> getRefRights(final ApprovalType at) {
-    return getRefRights(id(at));
-  }
-
-  public Collection<RefRight> getRefRights(final ApprovalCategory.Id id) {
-    if (RefRights == null) {
-      RefRights = index(project.getLocalRights());
+  private List<RefRight> getRefRights(final ApprovalCategory.Id id) {
+    if (refRights == null) {
+      refRights = index(project.getLocalRights());
     }
-    final Collection<RefRight> l = RefRights.get(id);
-    return l != null ? l : Collections.<RefRight> emptySet();
+    final List<RefRight> l = refRights.get(id);
+    return l != null ? l : Collections.<RefRight> emptyList();
   }
 
-  public Collection<RefRight> getWildcardRights(final ApprovalType at) {
-    return getWildcardRights(id(at));
-  }
-
-  public Collection<RefRight> getWildcardRights(final ApprovalCategory.Id id) {
+  private List<RefRight> getWildcardRights(final ApprovalCategory.Id id) {
     if (inheritedRights == null) {
       inheritedRights = index(project.getInheritedRights());
     }
-    final Collection<RefRight> l = inheritedRights.get(id);
-    return l != null ? l : Collections.<RefRight> emptySet();
+    final List<RefRight> l = inheritedRights.get(id);
+    return l != null ? l : Collections.<RefRight> emptyList();
   }
 
   public Collection<RefRight> getAllRights(final ApprovalType at) {
     return getAllRights(id(at));
   }
 
-  public Collection<RefRight> getAllRights(final ApprovalCategory.Id id) {
-    Collection<RefRight> l = allRights.get(id);
+  public List<RefRight> getAllRights(final ApprovalCategory.Id id) {
+    List<RefRight> l = allRights.get(id);
     if (l == null) {
       l = new ArrayList<RefRight>();
       l.addAll(getRefRights(id));
       l.addAll(getWildcardRights(id));
-      l = Collections.unmodifiableCollection(l);
+      Collections.sort(l, RefRight.REF_PATTERN_ORDER);
+      l = Collections.unmodifiableList(RefControl.filterMostSpecific(l));
       allRights.put(id, l);
     }
     return l;
   }
 
-  private Map<Id, Collection<RefRight>> index(final Collection<RefRight> rights) {
-    final HashMap<ApprovalCategory.Id, Collection<RefRight>> r;
+  private Map<Id, List<RefRight>> index(final Collection<RefRight> rights) {
+    final HashMap<ApprovalCategory.Id, List<RefRight>> r;
 
-    r = new HashMap<ApprovalCategory.Id, Collection<RefRight>>();
+    r = new HashMap<ApprovalCategory.Id, List<RefRight>>();
     for (final RefRight pr : rights) {
       if (RefControl.matches(change.getDest().get(), pr.getRefPattern())) {
-        Collection<RefRight> l = r.get(pr.getApprovalCategoryId());
+        List<RefRight> l = r.get(pr.getApprovalCategoryId());
         if (l == null) {
           l = new ArrayList<RefRight>();
           r.put(pr.getApprovalCategoryId(), l);