diff options
author | Shawn O. Pearce <sop@google.com> | 2009-03-27 16:53:32 -0700 |
---|---|---|
committer | Shawn O. Pearce <sop@google.com> | 2009-03-27 16:53:32 -0700 |
commit | 97cd0caa391d09fb785d27a9cc09c4547e15aa3c (patch) | |
tree | 119cf31e44b80949b22a03b24d9b7c71906c0453 | |
parent | a1cfa14357641aae91e05c23e259b71eac7edfd6 (diff) |
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>
12 files changed, 306 insertions, 12 deletions
diff --git a/Documentation/project-setup.txt b/Documentation/project-setup.txt index 3e7f5fa4c8..c71507e152 100644 --- a/Documentation/project-setup.txt +++ b/Documentation/project-setup.txt @@ -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 ------------------------------- diff --git a/src/main/java/com/google/gerrit/client/admin/AdminConstants.java b/src/main/java/com/google/gerrit/client/admin/AdminConstants.java index d3b93df9f5..1feb05353e 100644 --- a/src/main/java/com/google/gerrit/client/admin/AdminConstants.java +++ b/src/main/java/com/google/gerrit/client/admin/AdminConstants.java @@ -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(); diff --git a/src/main/java/com/google/gerrit/client/admin/AdminConstants.properties b/src/main/java/com/google/gerrit/client/admin/AdminConstants.properties index 94bff9eb3c..16c57d7a2d 100644 --- a/src/main/java/com/google/gerrit/client/admin/AdminConstants.properties +++ b/src/main/java/com/google/gerrit/client/admin/AdminConstants.properties @@ -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 diff --git a/src/main/java/com/google/gerrit/client/admin/ProjectAdminService.java b/src/main/java/com/google/gerrit/client/admin/ProjectAdminService.java index d92e066d22..f9a4898558 100644 --- a/src/main/java/com/google/gerrit/client/admin/ProjectAdminService.java +++ b/src/main/java/com/google/gerrit/client/admin/ProjectAdminService.java @@ -42,6 +42,10 @@ public interface ProjectAdminService extends RemoteJsonService { 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); @SignInRequired diff --git a/src/main/java/com/google/gerrit/client/admin/ProjectInfoPanel.java b/src/main/java/com/google/gerrit/client/admin/ProjectInfoPanel.java index e0c9f25283..35350d5028 100644 --- a/src/main/java/com/google/gerrit/client/admin/ProjectInfoPanel.java +++ b/src/main/java/com/google/gerrit/client/admin/ProjectInfoPanel.java @@ -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()); } } diff --git a/src/main/java/com/google/gerrit/client/admin/Util.java b/src/main/java/com/google/gerrit/client/admin/Util.java index 9b87cf19df..40603d3f74 100644 --- a/src/main/java/com/google/gerrit/client/admin/Util.java +++ b/src/main/java/com/google/gerrit/client/admin/Util.java @@ -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(); + } + } } diff --git a/src/main/java/com/google/gerrit/client/reviewdb/Project.java b/src/main/java/com/google/gerrit/client/reviewdb/Project.java index 4a9f0f2ea7..df97cfe0ed 100644 --- a/src/main/java/com/google/gerrit/client/reviewdb/Project.java +++ b/src/main/java/com/google/gerrit/client/reviewdb/Project.java @@ -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(); + } } diff --git a/src/main/java/com/google/gerrit/client/reviewdb/ReviewDb.java b/src/main/java/com/google/gerrit/client/reviewdb/ReviewDb.java index 1f2d9599c2..a9f853a2f0 100644 --- a/src/main/java/com/google/gerrit/client/reviewdb/ReviewDb.java +++ b/src/main/java/com/google/gerrit/client/reviewdb/ReviewDb.java @@ -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(); diff --git a/src/main/java/com/google/gerrit/git/MergeOp.java b/src/main/java/com/google/gerrit/git/MergeOp.java index ac5da117e8..e77e817d3b 100644 --- a/src/main/java/com/google/gerrit/git/MergeOp.java +++ b/src/main/java/com/google/gerrit/git/MergeOp.java @@ -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,23 +284,24 @@ public class MergeOp { // Take the first fast-forward available, if any is available in the set. // - for (final Iterator<CodeReviewCommit> i = toMerge.iterator(); i.hasNext();) { - try { - final CodeReviewCommit n = i.next(); - if (mergeTip == null || rw.isMergedInto(mergeTip, n)) { - mergeTip = n; - i.remove(); - break; + if (destProject.getSubmitType() != Project.SubmitType.MERGE_ALWAYS) { + for (final Iterator<CodeReviewCommit> i = toMerge.iterator(); i.hasNext();) { + try { + final CodeReviewCommit n = i.next(); + if (mergeTip == null || rw.isMergedInto(mergeTip, n)) { + mergeTip = n; + i.remove(); + break; + } + } catch (IOException e) { + throw new MergeException("Cannot fast-forward test during merge", e); } - } catch (IOException e) { - 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; diff --git a/src/main/java/com/google/gerrit/pgm/ImportProjectSubmitTypes.java b/src/main/java/com/google/gerrit/pgm/ImportProjectSubmitTypes.java new file mode 100644 index 0000000000..24371253ca --- /dev/null +++ b/src/main/java/com/google/gerrit/pgm/ImportProjectSubmitTypes.java @@ -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(); + } + } +} diff --git a/src/main/java/com/google/gerrit/server/ProjectAdminServiceImpl.java b/src/main/java/com/google/gerrit/server/ProjectAdminServiceImpl.java index 1cfc31a6dc..62e91c98d5 100644 --- a/src/main/java/com/google/gerrit/server/ProjectAdminServiceImpl.java +++ b/src/main/java/com/google/gerrit/server/ProjectAdminServiceImpl.java @@ -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>() { diff --git a/src/main/webapp/WEB-INF/sql/upgrade008_009.sql b/src/main/webapp/WEB-INF/sql/upgrade008_009.sql new file mode 100644 index 0000000000..562cf0a5f6 --- /dev/null +++ b/src/main/webapp/WEB-INF/sql/upgrade008_009.sql @@ -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; |