Make the --other-branches configurable

Introduce a branchOrder section in the project.config in order
to define a branch order and automate the back/forward porting
of changes.

For now assume only one branchOrder section. For example:

  [branchOrder]
    branch = master
    branch = stable-2.9
    branch = stable-2.8
    branch = stable-2.7

This format makes the ordering obvious (top-down) and complete: for any
two branches from the list the order is defined.

The --other-branches will rely on the branchOrder section in order to
find out a set of branches for which to check mergeability.  With the
above example, If a change is pushed to the master branch then the
--other-branches option will test mergeability into the stable-2.9,
stable-2.8 and stable-2.7 branches. If the changed would be pushed to
the stable-2.8 then the --other-branches option would test mergeability
only into the stable-2.7 branch.

Change-Id: Ib806d7e4b5702fa2d8effd197829b729cad95432
diff --git a/Documentation/config-project-config.txt b/Documentation/config-project-config.txt
index 474893d..43ede06 100644
--- a/Documentation/config-project-config.txt
+++ b/Documentation/config-project-config.txt
@@ -198,6 +198,41 @@
 documentation for a full list of available capabilities.
 
 
+[[branchOrder-section]]
+=== branchOrder section
+
+Defines a branch ordering which is used for backporting of changes.
+Backporting will be offered for a change (in the Gerrit UI) for all
+more stable branches where the change can merge cleanly.
+
+[[branchOrder.branch]]branchOrder.branch::
++
+A branch name, typically multiple values will be defined. The order of branch
+names in this section defines the branch order. The topmost is considered to be
+the least stable branch (typically the master branch) and the last one the
+most stable (typically the last maintained release branch).
+
+Example:
+
+----
+[branchOrder]
+  branch = master
+  branch = stable-2.9
+  branch = stable-2.8
+  branch = stable-2.7
+----
+
+The `branchOrder` section is inheritable. This is useful when multiple or all
+projects follow the same branch rules. A `branchOrder` section in a child
+project completely overrides any `branchOrder` section from a parent i.e. there
+is no merging of `branchOrder` sections. A present but empty `branchOrder`
+section removes all inherited branch order.
+
+Branches not listed in this section will not be included in the mergeability
+check. If the `branchOrder` section is not defined then the mergeability of a
+change into other branches will not be done.
+
+
 [[file-groups]]
 == The file +groups+
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Mergeable.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Mergeable.java
index e785736..f2c4adc 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Mergeable.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Mergeable.java
@@ -24,12 +24,14 @@
 import com.google.gerrit.reviewdb.client.PatchSet;
 import com.google.gerrit.reviewdb.client.RevId;
 import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.git.BranchOrderSection;
 import com.google.gerrit.server.git.CodeReviewCommit;
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.git.MergeException;
 import com.google.gerrit.server.git.strategy.SubmitStrategyFactory;
 import com.google.gerrit.server.index.ChangeIndexer;
 import com.google.gerrit.server.project.NoSuchProjectException;
+import com.google.gerrit.server.project.ProjectCache;
 import com.google.gwtorm.server.AtomicUpdate;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
@@ -73,6 +75,7 @@
 
   private final TestSubmitType.Get submitType;
   private final GitRepositoryManager gitManager;
+  private final ProjectCache projectCache;
   private final SubmitStrategyFactory submitStrategyFactory;
   private final Provider<ReviewDb> db;
   private final ChangeIndexer indexer;
@@ -82,11 +85,13 @@
   @Inject
   Mergeable(TestSubmitType.Get submitType,
       GitRepositoryManager gitManager,
+      ProjectCache projectCache,
       SubmitStrategyFactory submitStrategyFactory,
       Provider<ReviewDb> db,
       ChangeIndexer indexer) {
     this.submitType = submitType;
     this.gitManager = gitManager;
+    this.projectCache = projectCache;
     this.submitStrategyFactory = submitStrategyFactory;
     this.db = db;
     this.indexer = indexer;
@@ -124,11 +129,17 @@
 
       if (otherBranches) {
         result.mergeableInto = new ArrayList<>();
-        for (Ref r : refs.values()) {
-          if (r.getName().startsWith(Constants.R_HEADS)
-              && !r.getName().equals(ref.getName())) {
-            if (isMergeable(change, ps, SubmitType.CHERRY_PICK, git, refs, r)) {
-              result.mergeableInto.add(r.getName());
+        BranchOrderSection branchOrder =
+            projectCache.get(change.getProject()).getBranchOrderSection();
+        if (branchOrder != null) {
+          int prefixLen = Constants.R_HEADS.length();
+          for (String n : branchOrder.getMoreStable(ref.getName())) {
+            Ref other = refs.get(n);
+            if (other == null) {
+              continue;
+            }
+            if (isMergeable(change, ps, SubmitType.CHERRY_PICK, git, refs, other)) {
+              result.mergeableInto.add(other.getName().substring(prefixLen));
             }
           }
         }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/BranchOrderSection.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/BranchOrderSection.java
new file mode 100644
index 0000000..c447d31
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/BranchOrderSection.java
@@ -0,0 +1,60 @@
+// Copyright (C) 2014 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.git;
+
+import com.google.common.collect.ImmutableList;
+
+import org.eclipse.jgit.lib.Constants;
+
+import java.util.List;
+
+public class BranchOrderSection {
+
+  /**
+   * Branch names ordered from least to the most stable.
+   *
+   * Typically the order will be like: master, stable-M.N, stable-M.N-1, ...
+   */
+  private final ImmutableList<String> order;
+
+  public BranchOrderSection(String[] order) {
+    if (order.length == 0) {
+      this.order = ImmutableList.of();
+    } else {
+      ImmutableList.Builder<String> builder = ImmutableList.builder();
+      for (String b : order) {
+        builder.add(fullName(b));
+      }
+      this.order = builder.build();
+    }
+  }
+
+  private static String fullName(String branch) {
+    if (branch.startsWith(Constants.R_HEADS)) {
+      return branch;
+    } else {
+      return Constants.R_HEADS + branch;
+    }
+  }
+
+  public List<String> getMoreStable(String branch) {
+    int i = order.indexOf(fullName(branch));
+    if (0 <= i) {
+      return order.subList(i + 1, order.size());
+    } else {
+      return ImmutableList.of();
+    }
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectConfig.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectConfig.java
index d5dae8e..c4c1b7f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectConfig.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectConfig.java
@@ -95,6 +95,9 @@
   private static final String ACCOUNTS = "accounts";
   private static final String KEY_SAME_GROUP_VISIBILITY = "sameGroupVisibility";
 
+  private static final String BRANCH_ORDER = "branchOrder";
+  private static final String BRANCH = "branch";
+
   private static final String CONTRIBUTOR_AGREEMENT = "contributor-agreement";
   private static final String KEY_ACCEPTED = "accepted";
   private static final String KEY_REQUIRE_CONTACT_INFORMATION = "requireContactInformation";
@@ -152,6 +155,7 @@
   private AccountsSection accountsSection;
   private Map<AccountGroup.UUID, GroupReference> groupsByUUID;
   private Map<String, AccessSection> accessSections;
+  private BranchOrderSection branchOrderSection;
   private Map<String, ContributorAgreement> contributorAgreements;
   private Map<String, NotifyConfig> notifySections;
   private Map<String, LabelType> labelSections;
@@ -242,6 +246,10 @@
     return sort(accessSections.values());
   }
 
+  public BranchOrderSection getBranchOrderSection() {
+    return branchOrderSection;
+  }
+
   public void remove(AccessSection section) {
     if (section != null) {
       accessSections.remove(section.getName());
@@ -420,6 +428,7 @@
     loadAccountsSection(rc, groupsByName);
     loadContributorAgreements(rc, groupsByName);
     loadAccessSections(rc, groupsByName);
+    loadBranchOrderSection(rc);
     loadNotifySections(rc, groupsByName);
     loadLabelSections(rc);
     loadCommentLinkSections(rc);
@@ -570,6 +579,13 @@
     }
   }
 
+  private void loadBranchOrderSection(Config rc) {
+    if (rc.getSections().contains(BRANCH_ORDER)) {
+      branchOrderSection = new BranchOrderSection(
+          rc.getStringList(BRANCH_ORDER, null, BRANCH));
+    }
+  }
+
   private List<PermissionRule> loadPermissionRules(Config rc, String section,
       String subsection, String varName,
       Map<String, GroupReference> groupsByName,
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectState.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectState.java
index f6b96d7..715419d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectState.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectState.java
@@ -40,6 +40,7 @@
 import com.google.gerrit.server.account.GroupMembership;
 import com.google.gerrit.server.config.AllProjectsName;
 import com.google.gerrit.server.config.SitePaths;
+import com.google.gerrit.server.git.BranchOrderSection;
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.git.ProjectConfig;
 import com.google.gerrit.server.git.ProjectLevelConfig;
@@ -445,6 +446,16 @@
     return ImmutableList.copyOf(cls.values());
   }
 
+  public BranchOrderSection getBranchOrderSection() {
+    for (ProjectState s : tree()) {
+      BranchOrderSection section = s.getConfig().getBranchOrderSection();
+      if (section != null) {
+        return section;
+      }
+    }
+    return null;
+  }
+
   public ThemeInfo getTheme() {
     ThemeInfo theme = this.theme;
     if (theme == null) {