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
This commit is contained in:
Saša Živkov 2014-05-08 14:02:15 +02:00
parent 063f658042
commit 944b838d88
5 changed files with 138 additions and 5 deletions

View File

@ -198,6 +198,41 @@ link:access-control.html#global_capabilities[Global Capabilities]
documentation for a full list of available capabilities. 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]] [[file-groups]]
== The file +groups+ == The file +groups+

View File

@ -24,12 +24,14 @@ import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.PatchSet; import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.client.RevId; import com.google.gerrit.reviewdb.client.RevId;
import com.google.gerrit.reviewdb.server.ReviewDb; 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.CodeReviewCommit;
import com.google.gerrit.server.git.GitRepositoryManager; import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.git.MergeException; import com.google.gerrit.server.git.MergeException;
import com.google.gerrit.server.git.strategy.SubmitStrategyFactory; import com.google.gerrit.server.git.strategy.SubmitStrategyFactory;
import com.google.gerrit.server.index.ChangeIndexer; import com.google.gerrit.server.index.ChangeIndexer;
import com.google.gerrit.server.project.NoSuchProjectException; 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.AtomicUpdate;
import com.google.gwtorm.server.OrmException; import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject; import com.google.inject.Inject;
@ -73,6 +75,7 @@ public class Mergeable implements RestReadView<RevisionResource> {
private final TestSubmitType.Get submitType; private final TestSubmitType.Get submitType;
private final GitRepositoryManager gitManager; private final GitRepositoryManager gitManager;
private final ProjectCache projectCache;
private final SubmitStrategyFactory submitStrategyFactory; private final SubmitStrategyFactory submitStrategyFactory;
private final Provider<ReviewDb> db; private final Provider<ReviewDb> db;
private final ChangeIndexer indexer; private final ChangeIndexer indexer;
@ -82,11 +85,13 @@ public class Mergeable implements RestReadView<RevisionResource> {
@Inject @Inject
Mergeable(TestSubmitType.Get submitType, Mergeable(TestSubmitType.Get submitType,
GitRepositoryManager gitManager, GitRepositoryManager gitManager,
ProjectCache projectCache,
SubmitStrategyFactory submitStrategyFactory, SubmitStrategyFactory submitStrategyFactory,
Provider<ReviewDb> db, Provider<ReviewDb> db,
ChangeIndexer indexer) { ChangeIndexer indexer) {
this.submitType = submitType; this.submitType = submitType;
this.gitManager = gitManager; this.gitManager = gitManager;
this.projectCache = projectCache;
this.submitStrategyFactory = submitStrategyFactory; this.submitStrategyFactory = submitStrategyFactory;
this.db = db; this.db = db;
this.indexer = indexer; this.indexer = indexer;
@ -124,11 +129,17 @@ public class Mergeable implements RestReadView<RevisionResource> {
if (otherBranches) { if (otherBranches) {
result.mergeableInto = new ArrayList<>(); result.mergeableInto = new ArrayList<>();
for (Ref r : refs.values()) { BranchOrderSection branchOrder =
if (r.getName().startsWith(Constants.R_HEADS) projectCache.get(change.getProject()).getBranchOrderSection();
&& !r.getName().equals(ref.getName())) { if (branchOrder != null) {
if (isMergeable(change, ps, SubmitType.CHERRY_PICK, git, refs, r)) { int prefixLen = Constants.R_HEADS.length();
result.mergeableInto.add(r.getName()); 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));
} }
} }
} }

View File

@ -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();
}
}
}

View File

@ -95,6 +95,9 @@ public class ProjectConfig extends VersionedMetaData {
private static final String ACCOUNTS = "accounts"; private static final String ACCOUNTS = "accounts";
private static final String KEY_SAME_GROUP_VISIBILITY = "sameGroupVisibility"; 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 CONTRIBUTOR_AGREEMENT = "contributor-agreement";
private static final String KEY_ACCEPTED = "accepted"; private static final String KEY_ACCEPTED = "accepted";
private static final String KEY_REQUIRE_CONTACT_INFORMATION = "requireContactInformation"; private static final String KEY_REQUIRE_CONTACT_INFORMATION = "requireContactInformation";
@ -152,6 +155,7 @@ public class ProjectConfig extends VersionedMetaData {
private AccountsSection accountsSection; private AccountsSection accountsSection;
private Map<AccountGroup.UUID, GroupReference> groupsByUUID; private Map<AccountGroup.UUID, GroupReference> groupsByUUID;
private Map<String, AccessSection> accessSections; private Map<String, AccessSection> accessSections;
private BranchOrderSection branchOrderSection;
private Map<String, ContributorAgreement> contributorAgreements; private Map<String, ContributorAgreement> contributorAgreements;
private Map<String, NotifyConfig> notifySections; private Map<String, NotifyConfig> notifySections;
private Map<String, LabelType> labelSections; private Map<String, LabelType> labelSections;
@ -242,6 +246,10 @@ public class ProjectConfig extends VersionedMetaData {
return sort(accessSections.values()); return sort(accessSections.values());
} }
public BranchOrderSection getBranchOrderSection() {
return branchOrderSection;
}
public void remove(AccessSection section) { public void remove(AccessSection section) {
if (section != null) { if (section != null) {
accessSections.remove(section.getName()); accessSections.remove(section.getName());
@ -420,6 +428,7 @@ public class ProjectConfig extends VersionedMetaData {
loadAccountsSection(rc, groupsByName); loadAccountsSection(rc, groupsByName);
loadContributorAgreements(rc, groupsByName); loadContributorAgreements(rc, groupsByName);
loadAccessSections(rc, groupsByName); loadAccessSections(rc, groupsByName);
loadBranchOrderSection(rc);
loadNotifySections(rc, groupsByName); loadNotifySections(rc, groupsByName);
loadLabelSections(rc); loadLabelSections(rc);
loadCommentLinkSections(rc); loadCommentLinkSections(rc);
@ -570,6 +579,13 @@ public class ProjectConfig extends VersionedMetaData {
} }
} }
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, private List<PermissionRule> loadPermissionRules(Config rc, String section,
String subsection, String varName, String subsection, String varName,
Map<String, GroupReference> groupsByName, Map<String, GroupReference> groupsByName,

View File

@ -40,6 +40,7 @@ import com.google.gerrit.server.account.CapabilityCollection;
import com.google.gerrit.server.account.GroupMembership; import com.google.gerrit.server.account.GroupMembership;
import com.google.gerrit.server.config.AllProjectsName; import com.google.gerrit.server.config.AllProjectsName;
import com.google.gerrit.server.config.SitePaths; 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.GitRepositoryManager;
import com.google.gerrit.server.git.ProjectConfig; import com.google.gerrit.server.git.ProjectConfig;
import com.google.gerrit.server.git.ProjectLevelConfig; import com.google.gerrit.server.git.ProjectLevelConfig;
@ -445,6 +446,16 @@ public class ProjectState {
return ImmutableList.copyOf(cls.values()); 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() { public ThemeInfo getTheme() {
ThemeInfo theme = this.theme; ThemeInfo theme = this.theme;
if (theme == null) { if (theme == null) {