Support different project level merge policies

Replace the hidden "gerrit.fastforwardonly" config file option with
a project level property stored in the database containing one of
three different values:

 * FAST_FORWARD_ONLY:  Never create a merge commit
 * MERGE_IF_NECESSARY: Standard/default behavior
 * MERGE_ALWAYS:       Always create a merge commit

Signed-off-by: Shawn O. Pearce <sop@google.com>
This commit is contained in:
Shawn O. Pearce
2009-03-27 16:53:32 -07:00
parent a1cfa14357
commit 97cd0caa39
12 changed files with 306 additions and 12 deletions

View File

@@ -43,11 +43,13 @@ for a project named `project`.
INSERT INTO projects
(project_id
,use_contributor_agreements
,submit_type
,owner_group_id
,name)
VALUES
(nextval('project_id')
,'Y'
,'M'
,(SELECT admin_group_id FROM system_config)
,'new/project');
@@ -61,6 +63,40 @@ for a project named `project`.
,'new/project');
====
Change Submit Action
--------------------
The method Gerrit uses to submit a change to a project can be
modified by any project owner through the project console, `Admin` >
`Projects`. The following methods are supported:
* Fast Forward Only
+
This method produces a strictly linear history. All merges must
be handled on the client, prior to uploading to Gerrit for review.
+
To submit a change, the change must be a strict superset of the
destination branch. That is, the change must already contain the
tip of the destination branch at submit time.
* Merge If Necessary
+
This is the default for a new project (and why `\'M'` is suggested
above in the insert statement).
+
If the change being submitted is a strict superset of the destination
branch, then the branch is fast-forwarded to the change. If not,
then a merge commit is automatically created. This is identical
to the classical `git merge` behavior, or `git merge \--ff`.
* Always Merge
+
Always produce a merge commit, even if the change is a strict
superset of the destination branch. This is identical to the
behavior of `git merge \--no-ff`, and may be useful if the
project needs to follow submits with `git log \--first-parent`.
Registering Additional Branches
-------------------------------

View File

@@ -32,10 +32,15 @@ public interface AdminConstants extends Constants {
String headingOwner();
String headingDescription();
String headingSubmitType();
String headingMembers();
String headingCreateGroup();
String headingAccessRights();
String projectSubmitType_FAST_FORWARD_ONLY();
String projectSubmitType_MERGE_ALWAYS();
String projectSubmitType_MERGE_IF_NECESSARY();
String columnMember();
String columnEmailAddress();
String columnGroupName();

View File

@@ -13,10 +13,15 @@ buttonAddProjectRight = Add Access Right
headingOwner = Owners
headingDescription = Description
headingSubmitType = Change Submit Action
headingMembers = Members
headingCreateGroup = Create New Group
headingAccessRights = Access Rights
projectSubmitType_FAST_FORWARD_ONLY = Fast Forward Only
projectSubmitType_MERGE_IF_NECESSARY = Merge If Necessary
projectSubmitType_MERGE_ALWAYS = Always Merge
columnMember = Member
columnEmailAddress = Email Address
columnGroupName = Group Name

View File

@@ -41,6 +41,10 @@ public interface ProjectAdminService extends RemoteJsonService {
void changeProjectOwner(Project.Id projectId, String newOwnerName,
AsyncCallback<VoidResult> callback);
@SignInRequired
void changeProjectSubmitType(Project.Id projectId,
Project.SubmitType newSubmitType, AsyncCallback<VoidResult> callback);
@SignInRequired
void deleteRight(Set<ProjectRight.Key> ids, AsyncCallback<VoidResult> callback);

View File

@@ -22,9 +22,11 @@ import com.google.gerrit.client.ui.AccountGroupSuggestOracle;
import com.google.gerrit.client.ui.SmallHeading;
import com.google.gerrit.client.ui.TextSaveButtonListener;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.ChangeListener;
import com.google.gwt.user.client.ui.ClickListener;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.ListBox;
import com.google.gwt.user.client.ui.Panel;
import com.google.gwt.user.client.ui.SuggestBox;
import com.google.gwt.user.client.ui.TextArea;
@@ -41,6 +43,10 @@ public class ProjectInfoPanel extends Composite {
private SuggestBox ownerTxt;
private Button saveOwner;
private Panel submitTypePanel;
private ListBox submitType;
private Project.SubmitType currentSubmitType;
private TextArea descTxt;
private Button saveDesc;
@@ -48,6 +54,7 @@ public class ProjectInfoPanel extends Composite {
final FlowPanel body = new FlowPanel();
initOwner(body);
initDescription(body);
initSubmitType(body);
initWidget(body);
projectId = toShow;
@@ -72,6 +79,7 @@ public class ProjectInfoPanel extends Composite {
}
private void enableForm(final boolean on) {
submitType.setEnabled(on);
ownerTxtBox.setEnabled(on);
descTxt.setEnabled(on);
}
@@ -132,6 +140,56 @@ public class ProjectInfoPanel extends Composite {
new TextSaveButtonListener(descTxt, saveDesc);
}
private void initSubmitType(final Panel body) {
submitTypePanel = new VerticalPanel();
submitTypePanel.add(new SmallHeading(Util.C.headingSubmitType()));
submitType = new ListBox();
for (final Project.SubmitType type : Project.SubmitType.values()) {
submitType.addItem(Util.toLongString(type), type.name());
}
submitType.addChangeListener(new ChangeListener() {
public void onChange(Widget sender) {
final int i = submitType.getSelectedIndex();
if (i < 0) {
return;
}
final Project.SubmitType newSubmitType =
Project.SubmitType.valueOf(submitType.getValue(i));
submitType.setEnabled(false);
Util.PROJECT_SVC.changeProjectSubmitType(projectId, newSubmitType,
new GerritCallback<VoidResult>() {
public void onSuccess(final VoidResult result) {
currentSubmitType = newSubmitType;
submitType.setEnabled(true);
}
@Override
public void onFailure(final Throwable caught) {
submitType.setEnabled(false);
setSubmitType(currentSubmitType);
super.onFailure(caught);
}
});
}
});
submitTypePanel.add(submitType);
body.add(submitTypePanel);
}
private void setSubmitType(final Project.SubmitType newSubmitType) {
currentSubmitType = newSubmitType;
if (submitType != null) {
for (int i = 0; i < submitType.getItemCount(); i++) {
if (newSubmitType.name().equals(submitType.getValue(i))) {
submitType.setSelectedIndex(i);
return;
}
}
submitType.setSelectedIndex(-1);
}
}
void display(final ProjectDetail result) {
final Project project = result.project;
final AccountGroup owner = result.groups.get(project.getOwnerGroupId());
@@ -143,10 +201,13 @@ public class ProjectInfoPanel extends Composite {
if (ProjectRight.WILD_PROJECT.equals(project.getId())) {
ownerPanel.setVisible(false);
submitTypePanel.setVisible(false);
} else {
ownerPanel.setVisible(true);
submitTypePanel.setVisible(true);
}
descTxt.setText(project.getDescription());
setSubmitType(project.getSubmitType());
}
}

View File

@@ -14,6 +14,7 @@
package com.google.gerrit.client.admin;
import com.google.gerrit.client.reviewdb.Project;
import com.google.gwt.core.client.GWT;
import com.google.gwtjsonrpc.client.JsonUtil;
@@ -30,4 +31,20 @@ public class Util {
PROJECT_SVC = GWT.create(ProjectAdminService.class);
JsonUtil.bind(PROJECT_SVC, "rpc/ProjectAdminService");
}
public static String toLongString(final Project.SubmitType type) {
if (type == null) {
return "";
}
switch (type) {
case FAST_FORWARD_ONLY:
return C.projectSubmitType_FAST_FORWARD_ONLY();
case MERGE_IF_NECESSARY:
return C.projectSubmitType_MERGE_IF_NECESSARY();
case MERGE_ALWAYS:
return C.projectSubmitType_MERGE_ALWAYS();
default:
return type.name();
}
}
}

View File

@@ -81,6 +81,33 @@ public final class Project {
}
}
public static enum SubmitType {
FAST_FORWARD_ONLY('F'),
MERGE_IF_NECESSARY('M'),
MERGE_ALWAYS('A');
private final char code;
private SubmitType(final char c) {
code = c;
}
public char getCode() {
return code;
}
public static SubmitType forCode(final char c) {
for (final SubmitType s : SubmitType.values()) {
if (s.code == c) {
return s;
}
}
return null;
}
}
@Column
protected NameKey name;
@@ -96,6 +123,9 @@ public final class Project {
@Column
protected boolean useContributorAgreements;
@Column
protected char submitType;
protected Project() {
}
@@ -103,6 +133,7 @@ public final class Project {
name = newName;
projectId = newId;
useContributorAgreements = true;
setSubmitType(SubmitType.MERGE_IF_NECESSARY);
}
public Project.Id getId() {
@@ -140,4 +171,12 @@ public final class Project {
public void setUseContributorAgreements(final boolean u) {
useContributorAgreements = u;
}
public SubmitType getSubmitType() {
return SubmitType.forCode(submitType);
}
public void setSubmitType(final SubmitType type) {
submitType = type.getCode();
}
}

View File

@@ -21,7 +21,7 @@ import com.google.gwtorm.client.Sequence;
/** The review service database schema. */
public interface ReviewDb extends Schema {
public static final int VERSION = 8;
public static final int VERSION = 9;
@Relation
SchemaVersionAccess schemaVersion();

View File

@@ -15,12 +15,14 @@
package com.google.gerrit.git;
import com.google.gerrit.client.data.ApprovalType;
import com.google.gerrit.client.data.ProjectCache;
import com.google.gerrit.client.reviewdb.ApprovalCategory;
import com.google.gerrit.client.reviewdb.Branch;
import com.google.gerrit.client.reviewdb.Change;
import com.google.gerrit.client.reviewdb.ChangeApproval;
import com.google.gerrit.client.reviewdb.ChangeMessage;
import com.google.gerrit.client.reviewdb.PatchSet;
import com.google.gerrit.client.reviewdb.Project;
import com.google.gerrit.client.reviewdb.ReviewDb;
import com.google.gerrit.client.rpc.Common;
import com.google.gerrit.client.workflow.FunctionState;
@@ -84,6 +86,7 @@ public class MergeOp {
private final GerritServer server;
private final PersonIdent myIdent;
private final Branch.NameKey destBranch;
private Project destProject;
private final List<CodeReviewCommit> toMerge;
private List<Change> submitted;
private final Map<Change.Id, CommitMergeStatus> status;
@@ -103,6 +106,13 @@ public class MergeOp {
}
public void merge() throws MergeException {
final ProjectCache.Entry pe =
Common.getProjectCache().get(destBranch.getParentKey());
if (pe == null) {
throw new MergeException("No such project: " + destBranch.getParentKey());
}
destProject = pe.getProject();
try {
schema = Common.getSchemaFactory().open();
} catch (OrmException e) {
@@ -274,6 +284,7 @@ public class MergeOp {
// Take the first fast-forward available, if any is available in the set.
//
if (destProject.getSubmitType() != Project.SubmitType.MERGE_ALWAYS) {
for (final Iterator<CodeReviewCommit> i = toMerge.iterator(); i.hasNext();) {
try {
final CodeReviewCommit n = i.next();
@@ -286,11 +297,11 @@ public class MergeOp {
throw new MergeException("Cannot fast-forward test during merge", e);
}
}
}
// If this project only permits fast-forwards, abort everything else.
//
if ("true".equals(db.getConfig().getString("gerrit", null,
"fastforwardonly"))) {
if (destProject.getSubmitType() == Project.SubmitType.FAST_FORWARD_ONLY) {
while (!toMerge.isEmpty()) {
final CodeReviewCommit n = toMerge.remove(0);
n.statusCode = CommitMergeStatus.PATH_CONFLICT;

View File

@@ -0,0 +1,89 @@
// Copyright (C) 2009 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.pgm;
import com.google.gerrit.client.reviewdb.Project;
import com.google.gerrit.client.reviewdb.ReviewDb;
import com.google.gerrit.client.rpc.Common;
import com.google.gerrit.git.InvalidRepositoryException;
import com.google.gerrit.git.WorkQueue;
import com.google.gerrit.server.GerritServer;
import com.google.gwtjsonrpc.server.XsrfException;
import com.google.gwtorm.client.OrmException;
import org.spearce.jgit.lib.ProgressMonitor;
import org.spearce.jgit.lib.Repository;
import org.spearce.jgit.lib.TextProgressMonitor;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
/** Update project's submit_type field from their git config files. */
public class ImportProjectSubmitTypes {
private static final String GERRIT = "gerrit";
private static final String FFO = "fastforwardonly";
public static void main(final String[] argv) throws OrmException,
XsrfException {
try {
mainImpl(argv);
} finally {
WorkQueue.terminate();
}
}
private static void mainImpl(final String[] argv) throws OrmException,
XsrfException {
final ProgressMonitor pm = new TextProgressMonitor();
final GerritServer gs = GerritServer.getInstance();
final ReviewDb db = Common.getSchemaFactory().open();
try {
final List<Project> all = db.projects().all().toList();
pm.start(1);
pm.beginTask("Update projects", all.size());
for (final Project p : all) {
if (p.getSubmitType() != null
&& p.getSubmitType() != Project.SubmitType.MERGE_IF_NECESSARY) {
pm.update(1);
continue;
}
final Repository r;
try {
r = gs.getRepositoryCache().get(p.getName());
} catch (InvalidRepositoryException e) {
pm.update(1);
continue;
}
if ("true".equals(r.getConfig().getString(GERRIT, null, FFO))) {
p.setSubmitType(Project.SubmitType.FAST_FORWARD_ONLY);
db.projects().update(Collections.singleton(p));
r.getConfig().unsetString(GERRIT, null, FFO);
try {
r.getConfig().save();
} catch (IOException e) {
// Ignore a save error
}
}
pm.update(1);
}
pm.endTask();
} finally {
db.close();
}
}
}

View File

@@ -180,6 +180,24 @@ public class ProjectAdminServiceImpl extends BaseServiceImplementation
});
}
public void changeProjectSubmitType(final Project.Id projectId,
final Project.SubmitType newSubmitType,
final AsyncCallback<VoidResult> callback) {
run(callback, new Action<VoidResult>() {
public VoidResult run(final ReviewDb db) throws OrmException, Failure {
assertAmProjectOwner(db, projectId);
final Project project = db.projects().get(projectId);
if (project == null) {
throw new Failure(new NoSuchEntityException());
}
project.setSubmitType(newSubmitType);
db.projects().update(Collections.singleton(project));
Common.getProjectCache().invalidate(project);
return VoidResult.INSTANCE;
}
});
}
public void deleteRight(final Set<ProjectRight.Key> keys,
final AsyncCallback<VoidResult> callback) {
run(callback, new Action<VoidResult>() {

View File

@@ -0,0 +1,9 @@
-- Upgrade: schema_version 8 to 9
--
ALTER TABLE projects ADD submit_type CHAR(1);
UPDATE projects SET submit_type = 'M'; -- MERGE_IF_NECESSARY
ALTER TABLE projects ALTER COLUMN submit_type SET DEFAULT ' ';
ALTER TABLE projects ALTER COLUMN submit_type SET NOT NULL;
UPDATE schema_version SET version_nbr = 9;