Merge feature 'Control submit type from Prolog'
* Control submit type from Prolog: Support controlling the submit type for changes from Prolog Remove unnecessary Prolog-to-Java conversion from submit filters Use locate_helper to replace locate_* predicates ChangeControl: Extract Prolog submit rule evaluation to its own method Display submit type in the change info block Support changes with different submit types in MergeOp Make submit strategies re-executeable Refactor MergeOp: reduce number of member variables, make names consistent Refactor MergeOp: implement each submit strategy in an own class Refactor MergeOp: extract utility methods into MergeUtil class Change-Id: I71d4f5757ab96b5eb8456972398a7bb9fa2a7d4c Signed-off-by: Edwin Kempin <edwin.kempin@sap.com>
This commit is contained in:
@@ -17,6 +17,7 @@ package com.google.gerrit.common.data;
|
|||||||
import com.google.gerrit.reviewdb.client.Change;
|
import com.google.gerrit.reviewdb.client.Change;
|
||||||
import com.google.gerrit.reviewdb.client.ChangeMessage;
|
import com.google.gerrit.reviewdb.client.ChangeMessage;
|
||||||
import com.google.gerrit.reviewdb.client.PatchSet;
|
import com.google.gerrit.reviewdb.client.PatchSet;
|
||||||
|
import com.google.gerrit.reviewdb.client.Project;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
@@ -40,6 +41,8 @@ public class ChangeDetail {
|
|||||||
protected List<PatchSet> patchSets;
|
protected List<PatchSet> patchSets;
|
||||||
protected List<ApprovalDetail> approvals;
|
protected List<ApprovalDetail> approvals;
|
||||||
protected List<SubmitRecord> submitRecords;
|
protected List<SubmitRecord> submitRecords;
|
||||||
|
protected Project.SubmitType submitType;
|
||||||
|
protected SubmitTypeRecord submitTypeRecord;
|
||||||
protected boolean canSubmit;
|
protected boolean canSubmit;
|
||||||
protected List<ChangeMessage> messages;
|
protected List<ChangeMessage> messages;
|
||||||
protected PatchSet.Id currentPatchSetId;
|
protected PatchSet.Id currentPatchSetId;
|
||||||
@@ -187,6 +190,14 @@ public class ChangeDetail {
|
|||||||
return submitRecords;
|
return submitRecords;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setSubmitTypeRecord(SubmitTypeRecord submitTypeRecord) {
|
||||||
|
this.submitTypeRecord = submitTypeRecord;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SubmitTypeRecord getSubmitTypeRecord() {
|
||||||
|
return submitTypeRecord;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isCurrentPatchSet(final PatchSetDetail detail) {
|
public boolean isCurrentPatchSet(final PatchSetDetail detail) {
|
||||||
return currentPatchSetId != null
|
return currentPatchSetId != null
|
||||||
&& detail.getPatchSet().getId().equals(currentPatchSetId);
|
&& detail.getPatchSet().getId().equals(currentPatchSetId);
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import com.google.gerrit.reviewdb.client.Change;
|
|||||||
import com.google.gerrit.reviewdb.client.PatchLineComment;
|
import com.google.gerrit.reviewdb.client.PatchLineComment;
|
||||||
import com.google.gerrit.reviewdb.client.PatchSetApproval;
|
import com.google.gerrit.reviewdb.client.PatchSetApproval;
|
||||||
import com.google.gerrit.reviewdb.client.PatchSetInfo;
|
import com.google.gerrit.reviewdb.client.PatchSetInfo;
|
||||||
|
import com.google.gerrit.reviewdb.client.Project;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
@@ -33,6 +34,7 @@ public class PatchSetPublishDetail {
|
|||||||
protected List<PermissionRange> labels;
|
protected List<PermissionRange> labels;
|
||||||
protected List<ApprovalDetail> approvals;
|
protected List<ApprovalDetail> approvals;
|
||||||
protected List<SubmitRecord> submitRecords;
|
protected List<SubmitRecord> submitRecords;
|
||||||
|
protected SubmitTypeRecord submitTypeRecord;
|
||||||
protected List<PatchSetApproval> given;
|
protected List<PatchSetApproval> given;
|
||||||
protected boolean canSubmit;
|
protected boolean canSubmit;
|
||||||
|
|
||||||
@@ -61,6 +63,14 @@ public class PatchSetPublishDetail {
|
|||||||
return submitRecords;
|
return submitRecords;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setSubmitTypeRecord(SubmitTypeRecord submitTypeRecord) {
|
||||||
|
this.submitTypeRecord = submitTypeRecord;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SubmitTypeRecord getSubmitTypeRecord() {
|
||||||
|
return submitTypeRecord;
|
||||||
|
}
|
||||||
|
|
||||||
public List<PatchSetApproval> getGiven() {
|
public List<PatchSetApproval> getGiven() {
|
||||||
return given;
|
return given;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,58 @@
|
|||||||
|
// Copyright (C) 2012 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.common.data;
|
||||||
|
|
||||||
|
import com.google.gerrit.reviewdb.client.Project;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Describes the submit type for a change.
|
||||||
|
*/
|
||||||
|
public class SubmitTypeRecord {
|
||||||
|
public static enum Status {
|
||||||
|
/** The type was computed successfully */
|
||||||
|
OK,
|
||||||
|
|
||||||
|
/** An internal server error occurred preventing computation.
|
||||||
|
* <p>
|
||||||
|
* Additional detail may be available in {@link SubmitTypeRecord#errorMessage}
|
||||||
|
*/
|
||||||
|
RULE_ERROR
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SubmitTypeRecord OK(Project.SubmitType type) {
|
||||||
|
SubmitTypeRecord r = new SubmitTypeRecord();
|
||||||
|
r.status = Status.OK;
|
||||||
|
r.type = type;
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Status status;
|
||||||
|
public Project.SubmitType type;
|
||||||
|
public String errorMessage;
|
||||||
|
|
||||||
|
public String toString() {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append(status);
|
||||||
|
if (status == Status.RULE_ERROR && errorMessage != null) {
|
||||||
|
sb.append('(').append(errorMessage).append(")");
|
||||||
|
}
|
||||||
|
if (type != null) {
|
||||||
|
sb.append('[');
|
||||||
|
sb.append(type.name());
|
||||||
|
sb.append(']');
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -95,6 +95,7 @@ public interface ChangeConstants extends Constants {
|
|||||||
String changeInfoBlockUploaded();
|
String changeInfoBlockUploaded();
|
||||||
String changeInfoBlockUpdated();
|
String changeInfoBlockUpdated();
|
||||||
String changeInfoBlockStatus();
|
String changeInfoBlockStatus();
|
||||||
|
String changeInfoBlockSubmitType();
|
||||||
String changePermalink();
|
String changePermalink();
|
||||||
String changeInfoBlockCanMerge();
|
String changeInfoBlockCanMerge();
|
||||||
String changeInfoBlockCanMergeYes();
|
String changeInfoBlockCanMergeYes();
|
||||||
|
|||||||
@@ -72,6 +72,7 @@ changeInfoBlockTopic = Topic
|
|||||||
changeInfoBlockUploaded = Uploaded
|
changeInfoBlockUploaded = Uploaded
|
||||||
changeInfoBlockUpdated = Updated
|
changeInfoBlockUpdated = Updated
|
||||||
changeInfoBlockStatus = Status
|
changeInfoBlockStatus = Status
|
||||||
|
changeInfoBlockSubmitType = Submit Type
|
||||||
changePermalink = Permalink
|
changePermalink = Permalink
|
||||||
changeInfoBlockCanMerge = Can Merge
|
changeInfoBlockCanMerge = Can Merge
|
||||||
changeInfoBlockCanMergeYes = Yes
|
changeInfoBlockCanMergeYes = Yes
|
||||||
|
|||||||
@@ -15,8 +15,10 @@
|
|||||||
package com.google.gerrit.client.changes;
|
package com.google.gerrit.client.changes;
|
||||||
|
|
||||||
import com.google.gerrit.common.data.AccountInfoCache;
|
import com.google.gerrit.common.data.AccountInfoCache;
|
||||||
|
import com.google.gerrit.common.data.SubmitTypeRecord;
|
||||||
import com.google.gerrit.reviewdb.client.Change;
|
import com.google.gerrit.reviewdb.client.Change;
|
||||||
import com.google.gerrit.reviewdb.client.PatchSetInfo;
|
import com.google.gerrit.reviewdb.client.PatchSetInfo;
|
||||||
|
import com.google.gerrit.reviewdb.client.Project;
|
||||||
import com.google.gwt.user.client.ui.Composite;
|
import com.google.gwt.user.client.ui.Composite;
|
||||||
import com.google.gwt.user.client.ui.HorizontalPanel;
|
import com.google.gwt.user.client.ui.HorizontalPanel;
|
||||||
import com.google.gwtexpui.globalkey.client.KeyCommandSet;
|
import com.google.gwtexpui.globalkey.client.KeyCommandSet;
|
||||||
@@ -36,8 +38,8 @@ public class ChangeDescriptionBlock extends Composite {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void display(Change chg, Boolean starred, PatchSetInfo info,
|
public void display(Change chg, Boolean starred, PatchSetInfo info,
|
||||||
final AccountInfoCache acc) {
|
final AccountInfoCache acc, SubmitTypeRecord submitTypeRecord) {
|
||||||
infoBlock.display(chg, acc);
|
infoBlock.display(chg, acc, submitTypeRecord);
|
||||||
messageBlock.display(chg.getId(), starred, info.getMessage());
|
messageBlock.display(chg.getId(), starred, info.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import com.google.gerrit.client.ui.AccountLink;
|
|||||||
import com.google.gerrit.client.ui.BranchLink;
|
import com.google.gerrit.client.ui.BranchLink;
|
||||||
import com.google.gerrit.client.ui.ProjectLink;
|
import com.google.gerrit.client.ui.ProjectLink;
|
||||||
import com.google.gerrit.common.data.AccountInfoCache;
|
import com.google.gerrit.common.data.AccountInfoCache;
|
||||||
|
import com.google.gerrit.common.data.SubmitTypeRecord;
|
||||||
import com.google.gerrit.reviewdb.client.Branch;
|
import com.google.gerrit.reviewdb.client.Branch;
|
||||||
import com.google.gerrit.reviewdb.client.Change;
|
import com.google.gerrit.reviewdb.client.Change;
|
||||||
import com.google.gwt.user.client.ui.Composite;
|
import com.google.gwt.user.client.ui.Composite;
|
||||||
@@ -36,9 +37,10 @@ public class ChangeInfoBlock extends Composite {
|
|||||||
private static final int R_TOPIC = 4;
|
private static final int R_TOPIC = 4;
|
||||||
private static final int R_UPLOADED = 5;
|
private static final int R_UPLOADED = 5;
|
||||||
private static final int R_UPDATED = 6;
|
private static final int R_UPDATED = 6;
|
||||||
private static final int R_STATUS = 7;
|
private static final int R_SUBMIT_TYPE = 7;
|
||||||
private static final int R_MERGE_TEST = 8;
|
private static final int R_STATUS = 8;
|
||||||
private static final int R_CNT = 9;
|
private static final int R_MERGE_TEST = 9;
|
||||||
|
private static final int R_CNT = 10;
|
||||||
|
|
||||||
private final Grid table;
|
private final Grid table;
|
||||||
|
|
||||||
@@ -59,6 +61,7 @@ public class ChangeInfoBlock extends Composite {
|
|||||||
initRow(R_UPLOADED, Util.C.changeInfoBlockUploaded());
|
initRow(R_UPLOADED, Util.C.changeInfoBlockUploaded());
|
||||||
initRow(R_UPDATED, Util.C.changeInfoBlockUpdated());
|
initRow(R_UPDATED, Util.C.changeInfoBlockUpdated());
|
||||||
initRow(R_STATUS, Util.C.changeInfoBlockStatus());
|
initRow(R_STATUS, Util.C.changeInfoBlockStatus());
|
||||||
|
initRow(R_SUBMIT_TYPE, Util.C.changeInfoBlockSubmitType());
|
||||||
if (Gerrit.getConfig().testChangeMerge()) {
|
if (Gerrit.getConfig().testChangeMerge()) {
|
||||||
initRow(R_MERGE_TEST, Util.C.changeInfoBlockCanMerge());
|
initRow(R_MERGE_TEST, Util.C.changeInfoBlockCanMerge());
|
||||||
}
|
}
|
||||||
@@ -77,7 +80,8 @@ public class ChangeInfoBlock extends Composite {
|
|||||||
table.getCellFormatter().addStyleName(row, 0, Gerrit.RESOURCES.css().header());
|
table.getCellFormatter().addStyleName(row, 0, Gerrit.RESOURCES.css().header());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void display(final Change chg, final AccountInfoCache acc) {
|
public void display(final Change chg, final AccountInfoCache acc,
|
||||||
|
SubmitTypeRecord submitTypeRecord) {
|
||||||
final Branch.NameKey dst = chg.getDest();
|
final Branch.NameKey dst = chg.getDest();
|
||||||
|
|
||||||
CopyableLabel changeIdLabel =
|
CopyableLabel changeIdLabel =
|
||||||
@@ -94,6 +98,14 @@ public class ChangeInfoBlock extends Composite {
|
|||||||
table.setText(R_UPLOADED, 1, mediumFormat(chg.getCreatedOn()));
|
table.setText(R_UPLOADED, 1, mediumFormat(chg.getCreatedOn()));
|
||||||
table.setText(R_UPDATED, 1, mediumFormat(chg.getLastUpdatedOn()));
|
table.setText(R_UPDATED, 1, mediumFormat(chg.getLastUpdatedOn()));
|
||||||
table.setText(R_STATUS, 1, Util.toLongString(chg.getStatus()));
|
table.setText(R_STATUS, 1, Util.toLongString(chg.getStatus()));
|
||||||
|
String submitType;
|
||||||
|
if (submitTypeRecord.status == SubmitTypeRecord.Status.OK) {
|
||||||
|
submitType = com.google.gerrit.client.admin.Util
|
||||||
|
.toLongString(submitTypeRecord.type);
|
||||||
|
} else {
|
||||||
|
submitType = submitTypeRecord.status.name();
|
||||||
|
}
|
||||||
|
table.setText(R_SUBMIT_TYPE, 1, submitType);
|
||||||
final Change.Status status = chg.getStatus();
|
final Change.Status status = chg.getStatus();
|
||||||
if (Gerrit.getConfig().testChangeMerge()) {
|
if (Gerrit.getConfig().testChangeMerge()) {
|
||||||
if (status.equals(Change.Status.NEW) || status.equals(Change.Status.DRAFT)) {
|
if (status.equals(Change.Status.NEW) || status.equals(Change.Status.DRAFT)) {
|
||||||
|
|||||||
@@ -282,7 +282,7 @@ public class ChangeScreen extends Screen
|
|||||||
descriptionBlock.display(detail.getChange(),
|
descriptionBlock.display(detail.getChange(),
|
||||||
detail.isStarred(),
|
detail.isStarred(),
|
||||||
detail.getCurrentPatchSetDetail().getInfo(),
|
detail.getCurrentPatchSetDetail().getInfo(),
|
||||||
detail.getAccounts());
|
detail.getAccounts(), detail.getSubmitTypeRecord());
|
||||||
dependsOn.display(detail.getDependsOn());
|
dependsOn.display(detail.getDependsOn());
|
||||||
neededBy.display(detail.getNeededBy());
|
neededBy.display(detail.getNeededBy());
|
||||||
approvals.display(detail);
|
approvals.display(detail);
|
||||||
|
|||||||
@@ -274,7 +274,8 @@ public class PublishCommentScreen extends AccountScreen implements
|
|||||||
private void display(final PatchSetPublishDetail r) {
|
private void display(final PatchSetPublishDetail r) {
|
||||||
setPageTitle(Util.M.publishComments(r.getChange().getKey().abbreviate(),
|
setPageTitle(Util.M.publishComments(r.getChange().getKey().abbreviate(),
|
||||||
patchSetId.get()));
|
patchSetId.get()));
|
||||||
descBlock.display(r.getChange(), null, r.getPatchSetInfo(), r.getAccounts());
|
descBlock.display(r.getChange(), null, r.getPatchSetInfo(), r.getAccounts(),
|
||||||
|
r.getSubmitTypeRecord());
|
||||||
|
|
||||||
if (r.getChange().getStatus().isOpen()) {
|
if (r.getChange().getStatus().isOpen()) {
|
||||||
initApprovals(r, approvalPanel);
|
initApprovals(r, approvalPanel);
|
||||||
|
|||||||
@@ -168,6 +168,8 @@ public class ChangeDetailFactory extends Handler<ChangeDetail> {
|
|||||||
}
|
}
|
||||||
detail.setSubmitRecords(submitRecords);
|
detail.setSubmitRecords(submitRecords);
|
||||||
|
|
||||||
|
detail.setSubmitTypeRecord(control.getSubmitTypeRecord(db, patch));
|
||||||
|
|
||||||
patchsetsById = new HashMap<PatchSet.Id, PatchSet>();
|
patchsetsById = new HashMap<PatchSet.Id, PatchSet>();
|
||||||
loadPatchSets();
|
loadPatchSets();
|
||||||
loadMessages();
|
loadMessages();
|
||||||
|
|||||||
@@ -186,6 +186,8 @@ final class PatchSetPublishDetailFactory extends Handler<PatchSetPublishDetail>
|
|||||||
detail.setSubmitRecords(submitRecords);
|
detail.setSubmitRecords(submitRecords);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
detail.setSubmitTypeRecord(control.getSubmitTypeRecord(db, patchSet));
|
||||||
|
|
||||||
detail.setLabels(allowed);
|
detail.setLabels(allowed);
|
||||||
detail.setGiven(given);
|
detail.setGiven(given);
|
||||||
loadApprovals(detail, control);
|
loadApprovals(detail, control);
|
||||||
|
|||||||
@@ -0,0 +1,375 @@
|
|||||||
|
// Copyright (C) 2012 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 static com.google.gerrit.server.git.MergeUtil.commit;
|
||||||
|
import static com.google.gerrit.server.git.MergeUtil.hasMissingDependencies;
|
||||||
|
import static com.google.gerrit.server.git.MergeUtil.markCleanMerges;
|
||||||
|
import static com.google.gerrit.server.git.MergeUtil.mergeOneCommit;
|
||||||
|
import static com.google.gerrit.server.git.MergeUtil.newThreeWayMerger;
|
||||||
|
|
||||||
|
import com.google.gerrit.common.data.ApprovalType;
|
||||||
|
import com.google.gerrit.common.data.ApprovalTypes;
|
||||||
|
import com.google.gerrit.reviewdb.client.Account;
|
||||||
|
import com.google.gerrit.reviewdb.client.ApprovalCategory;
|
||||||
|
import com.google.gerrit.reviewdb.client.Change;
|
||||||
|
import com.google.gerrit.reviewdb.client.PatchSet;
|
||||||
|
import com.google.gerrit.reviewdb.client.PatchSetAncestor;
|
||||||
|
import com.google.gerrit.reviewdb.client.PatchSetApproval;
|
||||||
|
import com.google.gerrit.reviewdb.client.RevId;
|
||||||
|
import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
|
||||||
|
import com.google.gerrit.server.patch.PatchSetInfoFactory;
|
||||||
|
import com.google.gwtorm.server.OrmException;
|
||||||
|
import com.google.inject.Provider;
|
||||||
|
|
||||||
|
import org.eclipse.jgit.lib.CommitBuilder;
|
||||||
|
import org.eclipse.jgit.lib.ObjectId;
|
||||||
|
import org.eclipse.jgit.lib.PersonIdent;
|
||||||
|
import org.eclipse.jgit.lib.RefUpdate;
|
||||||
|
import org.eclipse.jgit.merge.Merger;
|
||||||
|
import org.eclipse.jgit.merge.ThreeWayMerger;
|
||||||
|
import org.eclipse.jgit.revwalk.FooterKey;
|
||||||
|
import org.eclipse.jgit.revwalk.FooterLine;
|
||||||
|
import org.eclipse.jgit.revwalk.RevCommit;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.sql.Timestamp;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class CherryPick extends SubmitStrategy {
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(CherryPick.class);
|
||||||
|
|
||||||
|
private static final ApprovalCategory.Id CRVW = //
|
||||||
|
new ApprovalCategory.Id("CRVW");
|
||||||
|
private static final ApprovalCategory.Id VRIF = //
|
||||||
|
new ApprovalCategory.Id("VRIF");
|
||||||
|
private static final FooterKey REVIEWED_ON = new FooterKey("Reviewed-on");
|
||||||
|
private static final FooterKey CHANGE_ID = new FooterKey("Change-Id");
|
||||||
|
|
||||||
|
private final PatchSetInfoFactory patchSetInfoFactory;
|
||||||
|
private final Provider<String> urlProvider;
|
||||||
|
private final ApprovalTypes approvalTypes;
|
||||||
|
private final GitReferenceUpdated replication;
|
||||||
|
private final Map<Change.Id, CodeReviewCommit> newCommits;
|
||||||
|
|
||||||
|
CherryPick(final SubmitStrategy.Arguments args,
|
||||||
|
final PatchSetInfoFactory patchSetInfoFactory,
|
||||||
|
final Provider<String> urlProvider, final ApprovalTypes approvalTypes,
|
||||||
|
final GitReferenceUpdated replication) {
|
||||||
|
super(args);
|
||||||
|
|
||||||
|
this.patchSetInfoFactory = patchSetInfoFactory;
|
||||||
|
this.urlProvider = urlProvider;
|
||||||
|
this.approvalTypes = approvalTypes;
|
||||||
|
this.replication = replication;
|
||||||
|
this.newCommits = new HashMap<Change.Id, CodeReviewCommit>();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected CodeReviewCommit _run(final CodeReviewCommit mergeTip,
|
||||||
|
final List<CodeReviewCommit> toMerge) throws MergeException {
|
||||||
|
CodeReviewCommit newMergeTip = mergeTip;
|
||||||
|
while (!toMerge.isEmpty()) {
|
||||||
|
final CodeReviewCommit n = toMerge.remove(0);
|
||||||
|
final ThreeWayMerger m =
|
||||||
|
newThreeWayMerger(args.repo, args.inserter, args.useContentMerge);
|
||||||
|
try {
|
||||||
|
if (newMergeTip == null) {
|
||||||
|
// The branch is unborn. Take a fast-forward resolution to
|
||||||
|
// create the branch.
|
||||||
|
//
|
||||||
|
newMergeTip = n;
|
||||||
|
n.statusCode = CommitMergeStatus.CLEAN_MERGE;
|
||||||
|
|
||||||
|
} else if (n.getParentCount() == 0) {
|
||||||
|
// Refuse to merge a root commit into an existing branch,
|
||||||
|
// we cannot obtain a delta for the cherry-pick to apply.
|
||||||
|
//
|
||||||
|
n.statusCode = CommitMergeStatus.CANNOT_CHERRY_PICK_ROOT;
|
||||||
|
|
||||||
|
} else if (n.getParentCount() == 1) {
|
||||||
|
// If there is only one parent, a cherry-pick can be done by
|
||||||
|
// taking the delta relative to that one parent and redoing
|
||||||
|
// that on the current merge tip.
|
||||||
|
//
|
||||||
|
m.setBase(n.getParent(0));
|
||||||
|
if (m.merge(newMergeTip, n)) {
|
||||||
|
newMergeTip = writeCherryPickCommit(m, newMergeTip, n);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
n.statusCode = CommitMergeStatus.PATH_CONFLICT;
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// There are multiple parents, so this is a merge commit. We
|
||||||
|
// don't want to cherry-pick the merge as clients can't easily
|
||||||
|
// rebase their history with that merge present and replaced
|
||||||
|
// by an equivalent merge with a different first parent. So
|
||||||
|
// instead behave as though MERGE_IF_NECESSARY was configured.
|
||||||
|
//
|
||||||
|
if (!hasMissingDependencies(args.mergeSorter, n)) {
|
||||||
|
if (args.rw.isMergedInto(newMergeTip, n)) {
|
||||||
|
newMergeTip = n;
|
||||||
|
} else {
|
||||||
|
newMergeTip =
|
||||||
|
mergeOneCommit(args.db, args.identifiedUserFactory,
|
||||||
|
args.myIdent, args.repo, args.rw, args.inserter,
|
||||||
|
args.canMergeFlag, args.useContentMerge, args.destBranch,
|
||||||
|
newMergeTip, n);
|
||||||
|
}
|
||||||
|
final PatchSetApproval submitApproval =
|
||||||
|
markCleanMerges(args.db, args.rw, args.canMergeFlag,
|
||||||
|
newMergeTip, args.alreadyAccepted);
|
||||||
|
setRefLogIdent(submitApproval);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// One or more dependencies were not met. The status was
|
||||||
|
// already marked on the commit so we have nothing further
|
||||||
|
// to perform at this time.
|
||||||
|
//
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new MergeException("Cannot merge " + n.name(), e);
|
||||||
|
} catch (OrmException e) {
|
||||||
|
throw new MergeException("Cannot merge " + n.name(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return newMergeTip;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<Change.Id, CodeReviewCommit> getNewCommits() {
|
||||||
|
return newCommits;
|
||||||
|
}
|
||||||
|
|
||||||
|
private CodeReviewCommit writeCherryPickCommit(final Merger m,
|
||||||
|
final CodeReviewCommit mergeTip, final CodeReviewCommit n)
|
||||||
|
throws IOException, OrmException {
|
||||||
|
args.rw.parseBody(n);
|
||||||
|
|
||||||
|
final List<FooterLine> footers = n.getFooterLines();
|
||||||
|
final StringBuilder msgbuf = new StringBuilder();
|
||||||
|
msgbuf.append(n.getFullMessage());
|
||||||
|
|
||||||
|
if (msgbuf.length() == 0) {
|
||||||
|
// WTF, an empty commit message?
|
||||||
|
msgbuf.append("<no commit message provided>");
|
||||||
|
}
|
||||||
|
if (msgbuf.charAt(msgbuf.length() - 1) != '\n') {
|
||||||
|
// Missing a trailing LF? Correct it (perhaps the editor was broken).
|
||||||
|
msgbuf.append('\n');
|
||||||
|
}
|
||||||
|
if (footers.isEmpty()) {
|
||||||
|
// Doesn't end in a "Signed-off-by: ..." style line? Add another line
|
||||||
|
// break to start a new paragraph for the reviewed-by tag lines.
|
||||||
|
//
|
||||||
|
msgbuf.append('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!contains(footers, CHANGE_ID, n.change.getKey().get())) {
|
||||||
|
msgbuf.append(CHANGE_ID.getName());
|
||||||
|
msgbuf.append(": ");
|
||||||
|
msgbuf.append(n.change.getKey().get());
|
||||||
|
msgbuf.append('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
final String siteUrl = urlProvider.get();
|
||||||
|
if (siteUrl != null) {
|
||||||
|
final String url = siteUrl + n.patchsetId.getParentKey().get();
|
||||||
|
if (!contains(footers, REVIEWED_ON, url)) {
|
||||||
|
msgbuf.append(REVIEWED_ON.getName());
|
||||||
|
msgbuf.append(": ");
|
||||||
|
msgbuf.append(url);
|
||||||
|
msgbuf.append('\n');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PatchSetApproval submitAudit = null;
|
||||||
|
List<PatchSetApproval> approvalList = null;
|
||||||
|
try {
|
||||||
|
approvalList =
|
||||||
|
args.db.patchSetApprovals().byPatchSet(n.patchsetId).toList();
|
||||||
|
Collections.sort(approvalList, new Comparator<PatchSetApproval>() {
|
||||||
|
@Override
|
||||||
|
public int compare(final PatchSetApproval a, final PatchSetApproval b) {
|
||||||
|
return a.getGranted().compareTo(b.getGranted());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
for (final PatchSetApproval a : approvalList) {
|
||||||
|
if (a.getValue() <= 0) {
|
||||||
|
// Negative votes aren't counted.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ApprovalCategory.SUBMIT.equals(a.getCategoryId())) {
|
||||||
|
// Submit is treated specially, below (becomes committer)
|
||||||
|
//
|
||||||
|
if (submitAudit == null
|
||||||
|
|| a.getGranted().compareTo(submitAudit.getGranted()) > 0) {
|
||||||
|
submitAudit = a;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
final Account acc =
|
||||||
|
args.identifiedUserFactory.create(a.getAccountId()).getAccount();
|
||||||
|
final StringBuilder identbuf = new StringBuilder();
|
||||||
|
if (acc.getFullName() != null && acc.getFullName().length() > 0) {
|
||||||
|
if (identbuf.length() > 0) {
|
||||||
|
identbuf.append(' ');
|
||||||
|
}
|
||||||
|
identbuf.append(acc.getFullName());
|
||||||
|
}
|
||||||
|
if (acc.getPreferredEmail() != null
|
||||||
|
&& acc.getPreferredEmail().length() > 0) {
|
||||||
|
if (isSignedOffBy(footers, acc.getPreferredEmail())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (identbuf.length() > 0) {
|
||||||
|
identbuf.append(' ');
|
||||||
|
}
|
||||||
|
identbuf.append('<');
|
||||||
|
identbuf.append(acc.getPreferredEmail());
|
||||||
|
identbuf.append('>');
|
||||||
|
}
|
||||||
|
if (identbuf.length() == 0) {
|
||||||
|
// Nothing reasonable to describe them by? Ignore them.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
final String tag;
|
||||||
|
if (CRVW.equals(a.getCategoryId())) {
|
||||||
|
tag = "Reviewed-by";
|
||||||
|
} else if (VRIF.equals(a.getCategoryId())) {
|
||||||
|
tag = "Tested-by";
|
||||||
|
} else {
|
||||||
|
final ApprovalType at = approvalTypes.byId(a.getCategoryId());
|
||||||
|
if (at == null) {
|
||||||
|
// A deprecated/deleted approval type, ignore it.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
tag = at.getCategory().getName().replace(' ', '-');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!contains(footers, new FooterKey(tag), identbuf.toString())) {
|
||||||
|
msgbuf.append(tag);
|
||||||
|
msgbuf.append(": ");
|
||||||
|
msgbuf.append(identbuf);
|
||||||
|
msgbuf.append('\n');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (OrmException e) {
|
||||||
|
log.error("Can't read approval records for " + n.patchsetId, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
final CommitBuilder mergeCommit = new CommitBuilder();
|
||||||
|
mergeCommit.setTreeId(m.getResultTreeId());
|
||||||
|
mergeCommit.setParentId(mergeTip);
|
||||||
|
mergeCommit.setAuthor(n.getAuthorIdent());
|
||||||
|
mergeCommit.setCommitter(toCommitterIdent(submitAudit));
|
||||||
|
mergeCommit.setMessage(msgbuf.toString());
|
||||||
|
|
||||||
|
final ObjectId id = commit(args.inserter, mergeCommit);
|
||||||
|
final CodeReviewCommit newCommit =
|
||||||
|
(CodeReviewCommit) args.rw.parseCommit(id);
|
||||||
|
|
||||||
|
n.change.nextPatchSetId();
|
||||||
|
|
||||||
|
final PatchSet ps = new PatchSet(n.change.currPatchSetId());
|
||||||
|
ps.setCreatedOn(new Timestamp(System.currentTimeMillis()));
|
||||||
|
ps.setUploader(submitAudit.getAccountId());
|
||||||
|
ps.setRevision(new RevId(id.getName()));
|
||||||
|
insertAncestors(ps.getId(), newCommit);
|
||||||
|
args.db.patchSets().insert(Collections.singleton(ps));
|
||||||
|
|
||||||
|
n.change.setCurrentPatchSet(patchSetInfoFactory.get(newCommit, ps.getId()));
|
||||||
|
args.db.changes().update(Collections.singletonList(n.change));
|
||||||
|
|
||||||
|
if (approvalList != null) {
|
||||||
|
for (PatchSetApproval a : approvalList) {
|
||||||
|
args.db.patchSetApprovals().insert(
|
||||||
|
Collections.singleton(new PatchSetApproval(ps.getId(), a)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final RefUpdate ru = args.repo.updateRef(ps.getRefName());
|
||||||
|
ru.setExpectedOldObjectId(ObjectId.zeroId());
|
||||||
|
ru.setNewObjectId(newCommit);
|
||||||
|
ru.disableRefLog();
|
||||||
|
if (ru.update(args.rw) != RefUpdate.Result.NEW) {
|
||||||
|
throw new IOException(String.format("Failed to create ref %s in %s: %s",
|
||||||
|
ps.getRefName(), n.change.getDest().getParentKey().get(),
|
||||||
|
ru.getResult()));
|
||||||
|
}
|
||||||
|
replication.fire(n.change.getProject(), ru.getName());
|
||||||
|
|
||||||
|
newCommit.copyFrom(n);
|
||||||
|
newCommit.statusCode = CommitMergeStatus.CLEAN_PICK;
|
||||||
|
newCommits.put(newCommit.patchsetId.getParentKey(), newCommit);
|
||||||
|
setRefLogIdent(submitAudit);
|
||||||
|
return newCommit;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void insertAncestors(PatchSet.Id id, RevCommit src)
|
||||||
|
throws OrmException {
|
||||||
|
final int cnt = src.getParentCount();
|
||||||
|
List<PatchSetAncestor> toInsert = new ArrayList<PatchSetAncestor>(cnt);
|
||||||
|
for (int p = 0; p < cnt; p++) {
|
||||||
|
PatchSetAncestor a;
|
||||||
|
|
||||||
|
a = new PatchSetAncestor(new PatchSetAncestor.Id(id, p + 1));
|
||||||
|
a.setAncestorRevision(new RevId(src.getParent(p).getId().name()));
|
||||||
|
toInsert.add(a);
|
||||||
|
}
|
||||||
|
args.db.patchSetAncestors().insert(toInsert);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean contains(List<FooterLine> footers, FooterKey key, String val) {
|
||||||
|
for (final FooterLine line : footers) {
|
||||||
|
if (line.matches(key) && val.equals(line.getValue())) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isSignedOffBy(List<FooterLine> footers, String email) {
|
||||||
|
for (final FooterLine line : footers) {
|
||||||
|
if (line.matches(FooterKey.SIGNED_OFF_BY)
|
||||||
|
&& email.equals(line.getEmailAddress())) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private PersonIdent toCommitterIdent(final PatchSetApproval audit) {
|
||||||
|
if (audit != null) {
|
||||||
|
return args.identifiedUserFactory.create(audit.getAccountId())
|
||||||
|
.newCommitterIdent(audit.getGranted(), args.myIdent.getTimeZone());
|
||||||
|
}
|
||||||
|
return args.myIdent;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -38,6 +38,9 @@ enum CommitMergeStatus {
|
|||||||
/** */
|
/** */
|
||||||
REVISION_GONE(""),
|
REVISION_GONE(""),
|
||||||
|
|
||||||
|
/** */
|
||||||
|
NO_SUBMIT_TYPE(""),
|
||||||
|
|
||||||
/** */
|
/** */
|
||||||
CRISS_CROSS_MERGE("Your change requires a recursive merge to resolve.\n"
|
CRISS_CROSS_MERGE("Your change requires a recursive merge to resolve.\n"
|
||||||
+ "\n"
|
+ "\n"
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
// Copyright (C) 2012 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 static com.google.gerrit.server.git.MergeUtil.getFirstFastForward;
|
||||||
|
import static com.google.gerrit.server.git.MergeUtil.markCleanMerges;
|
||||||
|
import static com.google.gerrit.server.git.MergeUtil.reduceToMinimalMerge;
|
||||||
|
|
||||||
|
import com.google.gerrit.reviewdb.client.PatchSetApproval;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class FastForwardOnly extends SubmitStrategy {
|
||||||
|
|
||||||
|
FastForwardOnly(final SubmitStrategy.Arguments args) {
|
||||||
|
super(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected CodeReviewCommit _run(final CodeReviewCommit mergeTip,
|
||||||
|
final List<CodeReviewCommit> toMerge) throws MergeException {
|
||||||
|
reduceToMinimalMerge(args.mergeSorter, toMerge);
|
||||||
|
final CodeReviewCommit newMergeTip =
|
||||||
|
getFirstFastForward(mergeTip, args.rw, toMerge);
|
||||||
|
|
||||||
|
while (!toMerge.isEmpty()) {
|
||||||
|
final CodeReviewCommit n = toMerge.remove(0);
|
||||||
|
n.statusCode = CommitMergeStatus.NOT_FAST_FORWARD;
|
||||||
|
}
|
||||||
|
|
||||||
|
final PatchSetApproval submitApproval =
|
||||||
|
markCleanMerges(args.db, args.rw, args.canMergeFlag, newMergeTip,
|
||||||
|
args.alreadyAccepted);
|
||||||
|
setRefLogIdent(submitApproval);
|
||||||
|
|
||||||
|
return newMergeTip;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean retryOnLockFailure() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
// Copyright (C) 2012 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 static com.google.gerrit.server.git.MergeUtil.markCleanMerges;
|
||||||
|
import static com.google.gerrit.server.git.MergeUtil.mergeOneCommit;
|
||||||
|
import static com.google.gerrit.server.git.MergeUtil.reduceToMinimalMerge;
|
||||||
|
|
||||||
|
import com.google.gerrit.reviewdb.client.PatchSetApproval;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class MergeAlways extends SubmitStrategy {
|
||||||
|
|
||||||
|
MergeAlways(final SubmitStrategy.Arguments args) {
|
||||||
|
super(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected CodeReviewCommit _run(final CodeReviewCommit mergeTip,
|
||||||
|
final List<CodeReviewCommit> toMerge) throws MergeException {
|
||||||
|
reduceToMinimalMerge(args.mergeSorter, toMerge);
|
||||||
|
|
||||||
|
CodeReviewCommit newMergeTip = mergeTip;
|
||||||
|
while (!toMerge.isEmpty()) {
|
||||||
|
newMergeTip =
|
||||||
|
mergeOneCommit(args.db, args.identifiedUserFactory, args.myIdent,
|
||||||
|
args.repo, args.rw, args.inserter, args.canMergeFlag,
|
||||||
|
args.useContentMerge, args.destBranch, mergeTip,
|
||||||
|
toMerge.remove(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
final PatchSetApproval submitApproval =
|
||||||
|
markCleanMerges(args.db, args.rw, args.canMergeFlag, newMergeTip,
|
||||||
|
args.alreadyAccepted);
|
||||||
|
setRefLogIdent(submitApproval);
|
||||||
|
|
||||||
|
return newMergeTip;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
// Copyright (C) 2012 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 static com.google.gerrit.server.git.MergeUtil.getFirstFastForward;
|
||||||
|
import static com.google.gerrit.server.git.MergeUtil.markCleanMerges;
|
||||||
|
import static com.google.gerrit.server.git.MergeUtil.mergeOneCommit;
|
||||||
|
import static com.google.gerrit.server.git.MergeUtil.reduceToMinimalMerge;
|
||||||
|
|
||||||
|
import com.google.gerrit.reviewdb.client.PatchSetApproval;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class MergeIfNecessary extends SubmitStrategy {
|
||||||
|
|
||||||
|
MergeIfNecessary(final SubmitStrategy.Arguments args) {
|
||||||
|
super(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected CodeReviewCommit _run(final CodeReviewCommit mergeTip,
|
||||||
|
final List<CodeReviewCommit> toMerge) throws MergeException {
|
||||||
|
reduceToMinimalMerge(args.mergeSorter, toMerge);
|
||||||
|
CodeReviewCommit newMergeTip =
|
||||||
|
getFirstFastForward(mergeTip, args.rw, toMerge);
|
||||||
|
|
||||||
|
// For every other commit do a pair-wise merge.
|
||||||
|
while (!toMerge.isEmpty()) {
|
||||||
|
newMergeTip =
|
||||||
|
mergeOneCommit(args.db, args.identifiedUserFactory, args.myIdent,
|
||||||
|
args.repo, args.rw, args.inserter, args.canMergeFlag,
|
||||||
|
args.useContentMerge, args.destBranch, mergeTip,
|
||||||
|
toMerge.remove(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
final PatchSetApproval submitApproval =
|
||||||
|
markCleanMerges(args.db, args.rw, args.canMergeFlag, newMergeTip,
|
||||||
|
args.alreadyAccepted);
|
||||||
|
setRefLogIdent(submitApproval);
|
||||||
|
|
||||||
|
return newMergeTip;
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -28,14 +28,14 @@ import java.util.Set;
|
|||||||
|
|
||||||
class MergeSorter {
|
class MergeSorter {
|
||||||
private final RevWalk rw;
|
private final RevWalk rw;
|
||||||
private final RevFlag CAN_MERGE;
|
private final RevFlag canMergeFlag;
|
||||||
private final Set<RevCommit> accepted;
|
private final Set<RevCommit> accepted;
|
||||||
|
|
||||||
MergeSorter(final RevWalk walk, final Set<RevCommit> alreadyAccepted,
|
MergeSorter(final RevWalk rw, final Set<RevCommit> alreadyAccepted,
|
||||||
final RevFlag flagCAN_MERGE) {
|
final RevFlag canMergeFlag) {
|
||||||
rw = walk;
|
this.rw = rw;
|
||||||
CAN_MERGE = flagCAN_MERGE;
|
this.canMergeFlag = canMergeFlag;
|
||||||
accepted = alreadyAccepted;
|
this.accepted = alreadyAccepted;
|
||||||
}
|
}
|
||||||
|
|
||||||
Collection<CodeReviewCommit> sort(final Collection<CodeReviewCommit> incoming)
|
Collection<CodeReviewCommit> sort(final Collection<CodeReviewCommit> incoming)
|
||||||
@@ -45,7 +45,7 @@ class MergeSorter {
|
|||||||
while (!sort.isEmpty()) {
|
while (!sort.isEmpty()) {
|
||||||
final CodeReviewCommit n = removeOne(sort);
|
final CodeReviewCommit n = removeOne(sort);
|
||||||
|
|
||||||
rw.resetRetain(CAN_MERGE);
|
rw.resetRetain(canMergeFlag);
|
||||||
rw.markStart(n);
|
rw.markStart(n);
|
||||||
for (RevCommit c : accepted) {
|
for (RevCommit c : accepted) {
|
||||||
rw.markUninteresting(c);
|
rw.markUninteresting(c);
|
||||||
@@ -54,7 +54,7 @@ class MergeSorter {
|
|||||||
RevCommit c;
|
RevCommit c;
|
||||||
final RevCommitList<RevCommit> contents = new RevCommitList<RevCommit>();
|
final RevCommitList<RevCommit> contents = new RevCommitList<RevCommit>();
|
||||||
while ((c = rw.next()) != null) {
|
while ((c = rw.next()) != null) {
|
||||||
if (!c.has(CAN_MERGE)) {
|
if (!c.has(canMergeFlag) || !incoming.contains(c)) {
|
||||||
// We cannot merge n as it would bring something we
|
// We cannot merge n as it would bring something we
|
||||||
// aren't permitted to merge at this time. Drop n.
|
// aren't permitted to merge at this time. Drop n.
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -0,0 +1,364 @@
|
|||||||
|
// Copyright (C) 2012 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.gerrit.reviewdb.client.ApprovalCategory;
|
||||||
|
import com.google.gerrit.reviewdb.client.Branch;
|
||||||
|
import com.google.gerrit.reviewdb.client.PatchSet;
|
||||||
|
import com.google.gerrit.reviewdb.client.PatchSetApproval;
|
||||||
|
import com.google.gerrit.reviewdb.server.ReviewDb;
|
||||||
|
import com.google.gerrit.server.IdentifiedUser;
|
||||||
|
import com.google.gwtorm.server.OrmException;
|
||||||
|
|
||||||
|
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
|
||||||
|
import org.eclipse.jgit.errors.MissingObjectException;
|
||||||
|
import org.eclipse.jgit.lib.AnyObjectId;
|
||||||
|
import org.eclipse.jgit.lib.CommitBuilder;
|
||||||
|
import org.eclipse.jgit.lib.Constants;
|
||||||
|
import org.eclipse.jgit.lib.ObjectId;
|
||||||
|
import org.eclipse.jgit.lib.ObjectInserter;
|
||||||
|
import org.eclipse.jgit.lib.PersonIdent;
|
||||||
|
import org.eclipse.jgit.lib.Repository;
|
||||||
|
import org.eclipse.jgit.merge.MergeStrategy;
|
||||||
|
import org.eclipse.jgit.merge.ThreeWayMerger;
|
||||||
|
import org.eclipse.jgit.revwalk.RevCommit;
|
||||||
|
import org.eclipse.jgit.revwalk.RevFlag;
|
||||||
|
import org.eclipse.jgit.revwalk.RevSort;
|
||||||
|
import org.eclipse.jgit.revwalk.RevWalk;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.UnsupportedEncodingException;
|
||||||
|
import java.sql.Timestamp;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.TimeZone;
|
||||||
|
|
||||||
|
public class MergeUtil {
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(MergeUtil.class);
|
||||||
|
|
||||||
|
private static final String R_HEADS_MASTER =
|
||||||
|
Constants.R_HEADS + Constants.MASTER;
|
||||||
|
|
||||||
|
public static CodeReviewCommit getFirstFastForward(
|
||||||
|
final CodeReviewCommit mergeTip, final RevWalk rw,
|
||||||
|
final List<CodeReviewCommit> toMerge) throws MergeException {
|
||||||
|
for (final Iterator<CodeReviewCommit> i = toMerge.iterator(); i.hasNext();) {
|
||||||
|
try {
|
||||||
|
final CodeReviewCommit n = i.next();
|
||||||
|
if (mergeTip == null || rw.isMergedInto(mergeTip, n)) {
|
||||||
|
i.remove();
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new MergeException("Cannot fast-forward test during merge", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return mergeTip;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void reduceToMinimalMerge(final MergeSorter mergeSorter,
|
||||||
|
final List<CodeReviewCommit> toSort) throws MergeException {
|
||||||
|
final Collection<CodeReviewCommit> heads;
|
||||||
|
try {
|
||||||
|
heads = mergeSorter.sort(toSort);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new MergeException("Branch head sorting failed", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
toSort.clear();
|
||||||
|
toSort.addAll(heads);
|
||||||
|
Collections.sort(toSort, new Comparator<CodeReviewCommit>() {
|
||||||
|
@Override
|
||||||
|
public int compare(final CodeReviewCommit a, final CodeReviewCommit b) {
|
||||||
|
return a.originalOrder - b.originalOrder;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static PatchSetApproval getSubmitter(final ReviewDb reviewDb,
|
||||||
|
final PatchSet.Id c) {
|
||||||
|
if (c == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
PatchSetApproval submitter = null;
|
||||||
|
try {
|
||||||
|
final List<PatchSetApproval> approvals =
|
||||||
|
reviewDb.patchSetApprovals().byPatchSet(c).toList();
|
||||||
|
for (PatchSetApproval a : approvals) {
|
||||||
|
if (a.getValue() > 0
|
||||||
|
&& ApprovalCategory.SUBMIT.equals(a.getCategoryId())) {
|
||||||
|
if (submitter == null
|
||||||
|
|| a.getGranted().compareTo(submitter.getGranted()) > 0) {
|
||||||
|
submitter = a;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (OrmException e) {
|
||||||
|
}
|
||||||
|
return submitter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static PersonIdent computeMergeCommitAuthor(final ReviewDb reviewDb,
|
||||||
|
final IdentifiedUser.GenericFactory identifiedUserFactory,
|
||||||
|
final PersonIdent myIdent, final RevWalk rw,
|
||||||
|
final List<CodeReviewCommit> codeReviewCommits) {
|
||||||
|
PatchSetApproval submitter = null;
|
||||||
|
for (final CodeReviewCommit c : codeReviewCommits) {
|
||||||
|
PatchSetApproval s = getSubmitter(reviewDb, c.patchsetId);
|
||||||
|
if (submitter == null
|
||||||
|
|| (s != null && s.getGranted().compareTo(submitter.getGranted()) > 0)) {
|
||||||
|
submitter = s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to use the submitter's identity for the merge commit author.
|
||||||
|
// If all of the commits being merged are created by the submitter,
|
||||||
|
// prefer the identity line they used in the commits rather than the
|
||||||
|
// preferred identity stored in the user account. This way the Git
|
||||||
|
// commit records are more consistent internally.
|
||||||
|
//
|
||||||
|
PersonIdent authorIdent;
|
||||||
|
if (submitter != null) {
|
||||||
|
IdentifiedUser who =
|
||||||
|
identifiedUserFactory.create(submitter.getAccountId());
|
||||||
|
Set<String> emails = new HashSet<String>();
|
||||||
|
for (RevCommit c : codeReviewCommits) {
|
||||||
|
try {
|
||||||
|
rw.parseBody(c);
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.warn("Cannot parse commit " + c.name(), e);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
emails.add(c.getAuthorIdent().getEmailAddress());
|
||||||
|
}
|
||||||
|
|
||||||
|
final Timestamp dt = submitter.getGranted();
|
||||||
|
final TimeZone tz = myIdent.getTimeZone();
|
||||||
|
if (emails.size() == 1
|
||||||
|
&& who.getEmailAddresses().contains(emails.iterator().next())) {
|
||||||
|
authorIdent =
|
||||||
|
new PersonIdent(codeReviewCommits.get(0).getAuthorIdent(), dt, tz);
|
||||||
|
} else {
|
||||||
|
authorIdent = who.newCommitterIdent(dt, tz);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
authorIdent = myIdent;
|
||||||
|
}
|
||||||
|
return authorIdent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean hasMissingDependencies(final MergeSorter mergeSorter,
|
||||||
|
final CodeReviewCommit toMerge) throws MergeException {
|
||||||
|
try {
|
||||||
|
return !mergeSorter.sort(Collections.singleton(toMerge)).contains(toMerge);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new MergeException("Branch head sorting failed", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static CodeReviewCommit mergeOneCommit(final ReviewDb reviewDb,
|
||||||
|
final IdentifiedUser.GenericFactory identifiedUserFactory,
|
||||||
|
final PersonIdent myIdent, final Repository repo, final RevWalk rw,
|
||||||
|
final ObjectInserter inserter, final RevFlag canMergeFlag,
|
||||||
|
final boolean useContentMerge, final Branch.NameKey destBranch,
|
||||||
|
final CodeReviewCommit mergeTip, final CodeReviewCommit n)
|
||||||
|
throws MergeException {
|
||||||
|
final ThreeWayMerger m = newThreeWayMerger(repo, inserter, useContentMerge);
|
||||||
|
try {
|
||||||
|
if (m.merge(new AnyObjectId[] {mergeTip, n})) {
|
||||||
|
return writeMergeCommit(reviewDb, identifiedUserFactory, myIdent, rw,
|
||||||
|
inserter, canMergeFlag, destBranch, mergeTip, m.getResultTreeId(), n);
|
||||||
|
} else {
|
||||||
|
failed(rw, canMergeFlag, mergeTip, n, CommitMergeStatus.PATH_CONFLICT);
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
if (e.getMessage().startsWith("Multiple merge bases for")) {
|
||||||
|
try {
|
||||||
|
failed(rw, canMergeFlag, mergeTip, n,
|
||||||
|
CommitMergeStatus.CRISS_CROSS_MERGE);
|
||||||
|
} catch (IOException e2) {
|
||||||
|
throw new MergeException("Cannot merge " + n.name(), e);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new MergeException("Cannot merge " + n.name(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return mergeTip;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static CodeReviewCommit failed(final RevWalk rw,
|
||||||
|
final RevFlag canMergeFlag, final CodeReviewCommit mergeTip,
|
||||||
|
final CodeReviewCommit n, final CommitMergeStatus failure)
|
||||||
|
throws MissingObjectException, IncorrectObjectTypeException, IOException {
|
||||||
|
rw.resetRetain(canMergeFlag);
|
||||||
|
rw.markStart(n);
|
||||||
|
rw.markUninteresting(mergeTip);
|
||||||
|
CodeReviewCommit failed;
|
||||||
|
while ((failed = (CodeReviewCommit) rw.next()) != null) {
|
||||||
|
failed.statusCode = failure;
|
||||||
|
}
|
||||||
|
return failed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static CodeReviewCommit writeMergeCommit(final ReviewDb reviewDb,
|
||||||
|
final IdentifiedUser.GenericFactory identifiedUserFactory,
|
||||||
|
final PersonIdent myIdent, final RevWalk rw,
|
||||||
|
final ObjectInserter inserter, final RevFlag canMergeFlag,
|
||||||
|
final Branch.NameKey destBranch, final CodeReviewCommit mergeTip,
|
||||||
|
final ObjectId treeId, final CodeReviewCommit n) throws IOException,
|
||||||
|
MissingObjectException, IncorrectObjectTypeException {
|
||||||
|
final List<CodeReviewCommit> merged = new ArrayList<CodeReviewCommit>();
|
||||||
|
rw.resetRetain(canMergeFlag);
|
||||||
|
rw.markStart(n);
|
||||||
|
rw.markUninteresting(mergeTip);
|
||||||
|
for (final RevCommit c : rw) {
|
||||||
|
final CodeReviewCommit crc = (CodeReviewCommit) c;
|
||||||
|
if (crc.patchsetId != null) {
|
||||||
|
merged.add(crc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final StringBuilder msgbuf = new StringBuilder();
|
||||||
|
if (merged.size() == 1) {
|
||||||
|
final CodeReviewCommit c = merged.get(0);
|
||||||
|
rw.parseBody(c);
|
||||||
|
msgbuf.append("Merge \"");
|
||||||
|
msgbuf.append(c.getShortMessage());
|
||||||
|
msgbuf.append("\"");
|
||||||
|
|
||||||
|
} else {
|
||||||
|
msgbuf.append("Merge changes ");
|
||||||
|
for (final Iterator<CodeReviewCommit> i = merged.iterator(); i.hasNext();) {
|
||||||
|
msgbuf.append(i.next().change.getKey().abbreviate());
|
||||||
|
if (i.hasNext()) {
|
||||||
|
msgbuf.append(',');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!R_HEADS_MASTER.equals(destBranch.get())) {
|
||||||
|
msgbuf.append(" into ");
|
||||||
|
msgbuf.append(destBranch.getShortName());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (merged.size() > 1) {
|
||||||
|
msgbuf.append("\n\n* changes:\n");
|
||||||
|
for (final CodeReviewCommit c : merged) {
|
||||||
|
rw.parseBody(c);
|
||||||
|
msgbuf.append(" ");
|
||||||
|
msgbuf.append(c.getShortMessage());
|
||||||
|
msgbuf.append("\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PersonIdent authorIdent =
|
||||||
|
computeMergeCommitAuthor(reviewDb, identifiedUserFactory, myIdent, rw,
|
||||||
|
merged);
|
||||||
|
|
||||||
|
final CommitBuilder mergeCommit = new CommitBuilder();
|
||||||
|
mergeCommit.setTreeId(treeId);
|
||||||
|
mergeCommit.setParentIds(mergeTip, n);
|
||||||
|
mergeCommit.setAuthor(authorIdent);
|
||||||
|
mergeCommit.setCommitter(myIdent);
|
||||||
|
mergeCommit.setMessage(msgbuf.toString());
|
||||||
|
|
||||||
|
return (CodeReviewCommit) rw.parseCommit(commit(inserter, mergeCommit));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ThreeWayMerger newThreeWayMerger(final Repository repo,
|
||||||
|
final ObjectInserter inserter, final boolean useContentMerge) {
|
||||||
|
ThreeWayMerger m;
|
||||||
|
if (useContentMerge) {
|
||||||
|
// Settings for this project allow us to try and
|
||||||
|
// automatically resolve conflicts within files if needed.
|
||||||
|
// Use ResolveMerge and instruct to operate in core.
|
||||||
|
m = MergeStrategy.RESOLVE.newMerger(repo, true);
|
||||||
|
} else {
|
||||||
|
// No auto conflict resolving allowed. If any of the
|
||||||
|
// affected files was modified, merge will fail.
|
||||||
|
m = MergeStrategy.SIMPLE_TWO_WAY_IN_CORE.newMerger(repo);
|
||||||
|
}
|
||||||
|
m.setObjectInserter(new ObjectInserter.Filter() {
|
||||||
|
@Override
|
||||||
|
protected ObjectInserter delegate() {
|
||||||
|
return inserter;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void flush() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void release() {
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ObjectId commit(final ObjectInserter inserter,
|
||||||
|
final CommitBuilder mergeCommit) throws IOException,
|
||||||
|
UnsupportedEncodingException {
|
||||||
|
ObjectId id = inserter.insert(mergeCommit);
|
||||||
|
inserter.flush();
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static PatchSetApproval markCleanMerges(final ReviewDb reviewDb,
|
||||||
|
final RevWalk rw, final RevFlag canMergeFlag,
|
||||||
|
final CodeReviewCommit mergeTip, final Set<RevCommit> alreadyAccepted)
|
||||||
|
throws MergeException {
|
||||||
|
if (mergeTip == null) {
|
||||||
|
// If mergeTip is null here, branchTip was null, indicating a new branch
|
||||||
|
// at the start of the merge process. We also elected to merge nothing,
|
||||||
|
// probably due to missing dependencies. Nothing was cleanly merged.
|
||||||
|
//
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
PatchSetApproval submitApproval = null;
|
||||||
|
|
||||||
|
rw.resetRetain(canMergeFlag);
|
||||||
|
rw.sort(RevSort.TOPO);
|
||||||
|
rw.sort(RevSort.REVERSE, true);
|
||||||
|
rw.markStart(mergeTip);
|
||||||
|
for (RevCommit c : alreadyAccepted) {
|
||||||
|
rw.markUninteresting(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
CodeReviewCommit c;
|
||||||
|
while ((c = (CodeReviewCommit) rw.next()) != null) {
|
||||||
|
if (c.patchsetId != null) {
|
||||||
|
c.statusCode = CommitMergeStatus.CLEAN_MERGE;
|
||||||
|
if (submitApproval == null) {
|
||||||
|
submitApproval = getSubmitter(reviewDb, c.patchsetId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return submitApproval;
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new MergeException("Cannot mark clean merges", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,171 @@
|
|||||||
|
// Copyright (C) 2012 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.gerrit.reviewdb.client.Branch;
|
||||||
|
import com.google.gerrit.reviewdb.client.Change;
|
||||||
|
import com.google.gerrit.reviewdb.client.PatchSetApproval;
|
||||||
|
import com.google.gerrit.reviewdb.client.Project.SubmitType;
|
||||||
|
import com.google.gerrit.reviewdb.server.ReviewDb;
|
||||||
|
import com.google.gerrit.server.IdentifiedUser;
|
||||||
|
|
||||||
|
import org.eclipse.jgit.lib.ObjectInserter;
|
||||||
|
import org.eclipse.jgit.lib.PersonIdent;
|
||||||
|
import org.eclipse.jgit.lib.RefUpdate;
|
||||||
|
import org.eclipse.jgit.lib.Repository;
|
||||||
|
import org.eclipse.jgit.revwalk.RevCommit;
|
||||||
|
import org.eclipse.jgit.revwalk.RevFlag;
|
||||||
|
import org.eclipse.jgit.revwalk.RevWalk;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base class that submit strategies must extend. A submit strategy for a
|
||||||
|
* certain {@link SubmitType} defines how the submitted commits should be
|
||||||
|
* merged.
|
||||||
|
*/
|
||||||
|
public abstract class SubmitStrategy {
|
||||||
|
|
||||||
|
private PersonIdent refLogIdent;
|
||||||
|
|
||||||
|
static class Arguments {
|
||||||
|
protected final IdentifiedUser.GenericFactory identifiedUserFactory;
|
||||||
|
protected final PersonIdent myIdent;
|
||||||
|
protected final ReviewDb db;
|
||||||
|
|
||||||
|
protected final Repository repo;
|
||||||
|
protected final RevWalk rw;
|
||||||
|
protected final ObjectInserter inserter;
|
||||||
|
protected final RevFlag canMergeFlag;
|
||||||
|
protected final Set<RevCommit> alreadyAccepted;
|
||||||
|
protected final Branch.NameKey destBranch;
|
||||||
|
protected final boolean useContentMerge;
|
||||||
|
protected final MergeSorter mergeSorter;
|
||||||
|
|
||||||
|
Arguments(final IdentifiedUser.GenericFactory identifiedUserFactory,
|
||||||
|
final PersonIdent myIdent, final ReviewDb db, final Repository repo,
|
||||||
|
final RevWalk rw, final ObjectInserter inserter,
|
||||||
|
final RevFlag canMergeFlag, final Set<RevCommit> alreadyAccepted,
|
||||||
|
final Branch.NameKey destBranch, final boolean useContentMerge) {
|
||||||
|
this.identifiedUserFactory = identifiedUserFactory;
|
||||||
|
this.myIdent = myIdent;
|
||||||
|
this.db = db;
|
||||||
|
|
||||||
|
this.repo = repo;
|
||||||
|
this.rw = rw;
|
||||||
|
this.inserter = inserter;
|
||||||
|
this.canMergeFlag = canMergeFlag;
|
||||||
|
this.alreadyAccepted = alreadyAccepted;
|
||||||
|
this.destBranch = destBranch;
|
||||||
|
this.useContentMerge = useContentMerge;
|
||||||
|
this.mergeSorter = new MergeSorter(rw, alreadyAccepted, canMergeFlag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected final Arguments args;
|
||||||
|
|
||||||
|
SubmitStrategy(final Arguments args) {
|
||||||
|
this.args = args;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs this submit strategy. If possible the provided commits will be merged
|
||||||
|
* with this submit strategy.
|
||||||
|
*
|
||||||
|
* @param mergeTip the mergeTip
|
||||||
|
* @param toMerge the list of submitted commits that should be merged using
|
||||||
|
* this submit strategy
|
||||||
|
* @return the new mergeTip
|
||||||
|
* @throws MergeException
|
||||||
|
*/
|
||||||
|
public final CodeReviewCommit run(final CodeReviewCommit mergeTip,
|
||||||
|
final List<CodeReviewCommit> toMerge) throws MergeException {
|
||||||
|
refLogIdent = null;
|
||||||
|
return _run(mergeTip, toMerge);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs this submit strategy. If possible the provided commits will be merged
|
||||||
|
* with this submit strategy.
|
||||||
|
*
|
||||||
|
* @param mergeTip the mergeTip
|
||||||
|
* @param toMerge the list of submitted commits that should be merged using
|
||||||
|
* this submit strategy
|
||||||
|
* @return the new mergeTip
|
||||||
|
* @throws MergeException
|
||||||
|
*/
|
||||||
|
protected abstract CodeReviewCommit _run(CodeReviewCommit mergeTip,
|
||||||
|
List<CodeReviewCommit> toMerge) throws MergeException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the PersonIdent that should be used for the ref log entries when
|
||||||
|
* updating the destination branch. The ref log identity may be set after the
|
||||||
|
* {@link #run(CodeReviewCommit, List)} method finished.
|
||||||
|
*
|
||||||
|
* Do only call this method after the {@link #run(CodeReviewCommit, List)}
|
||||||
|
* method has been invoked.
|
||||||
|
*
|
||||||
|
* @return the ref log identity, may be <code>null</code>
|
||||||
|
*/
|
||||||
|
public final PersonIdent getRefLogIdent() {
|
||||||
|
return refLogIdent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns all commits that have been newly created for the changes that are
|
||||||
|
* getting merged.
|
||||||
|
*
|
||||||
|
* By default this method is returning an empty map, but subclasses may
|
||||||
|
* overwrite this method to provide newly created commits.
|
||||||
|
*
|
||||||
|
* Do only call this method after the {@link #run(CodeReviewCommit, List)}
|
||||||
|
* method has been invoked.
|
||||||
|
*
|
||||||
|
* @return new commits created for changes that are getting merged
|
||||||
|
*/
|
||||||
|
public Map<Change.Id, CodeReviewCommit> getNewCommits() {
|
||||||
|
return Collections.emptyMap();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether a merge that failed with
|
||||||
|
* {@link RefUpdate.Result#LOCK_FAILURE} should be retried.
|
||||||
|
*
|
||||||
|
* May be overwritten by subclasses.
|
||||||
|
*
|
||||||
|
* @return <code>true</code> if a merge that failed with
|
||||||
|
* {@link RefUpdate.Result#LOCK_FAILURE} should be retried, otherwise
|
||||||
|
* <code>false</code>
|
||||||
|
*/
|
||||||
|
public boolean retryOnLockFailure() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the ref log identity if it wasn't set yet.
|
||||||
|
*
|
||||||
|
* @param submitApproval the approval that submitted the patch set
|
||||||
|
*/
|
||||||
|
protected final void setRefLogIdent(final PatchSetApproval submitApproval) {
|
||||||
|
if (refLogIdent == null && submitApproval != null) {
|
||||||
|
refLogIdent =
|
||||||
|
args.identifiedUserFactory.create(submitApproval.getAccountId())
|
||||||
|
.newRefLogIdent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
// Copyright (C) 2012 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.gerrit.common.data.ApprovalTypes;
|
||||||
|
import com.google.gerrit.reviewdb.client.Branch;
|
||||||
|
import com.google.gerrit.reviewdb.client.Project.SubmitType;
|
||||||
|
import com.google.gerrit.reviewdb.server.ReviewDb;
|
||||||
|
import com.google.gerrit.server.GerritPersonIdent;
|
||||||
|
import com.google.gerrit.server.IdentifiedUser;
|
||||||
|
import com.google.gerrit.server.config.CanonicalWebUrl;
|
||||||
|
import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
|
||||||
|
import com.google.gerrit.server.patch.PatchSetInfoFactory;
|
||||||
|
import com.google.inject.Inject;
|
||||||
|
import com.google.inject.Provider;
|
||||||
|
|
||||||
|
import org.eclipse.jgit.lib.ObjectInserter;
|
||||||
|
import org.eclipse.jgit.lib.PersonIdent;
|
||||||
|
import org.eclipse.jgit.lib.Repository;
|
||||||
|
import org.eclipse.jgit.revwalk.RevCommit;
|
||||||
|
import org.eclipse.jgit.revwalk.RevFlag;
|
||||||
|
import org.eclipse.jgit.revwalk.RevWalk;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
/** Factory to create a {@link SubmitStrategy} for a {@link SubmitType}. */
|
||||||
|
public class SubmitStrategyFactory {
|
||||||
|
private static final Logger log = LoggerFactory
|
||||||
|
.getLogger(SubmitStrategyFactory.class);
|
||||||
|
|
||||||
|
private final IdentifiedUser.GenericFactory identifiedUserFactory;
|
||||||
|
private final PersonIdent myIdent;
|
||||||
|
private final PatchSetInfoFactory patchSetInfoFactory;
|
||||||
|
private final Provider<String> urlProvider;
|
||||||
|
private final ApprovalTypes approvalTypes;
|
||||||
|
private final GitReferenceUpdated replication;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
SubmitStrategyFactory(
|
||||||
|
final IdentifiedUser.GenericFactory identifiedUserFactory,
|
||||||
|
@GerritPersonIdent final PersonIdent myIdent,
|
||||||
|
final PatchSetInfoFactory patchSetInfoFactory,
|
||||||
|
@CanonicalWebUrl @Nullable final Provider<String> urlProvider,
|
||||||
|
final ApprovalTypes approvalTypes, final GitReferenceUpdated replication) {
|
||||||
|
this.identifiedUserFactory = identifiedUserFactory;
|
||||||
|
this.myIdent = myIdent;
|
||||||
|
this.patchSetInfoFactory = patchSetInfoFactory;
|
||||||
|
this.urlProvider = urlProvider;
|
||||||
|
this.approvalTypes = approvalTypes;
|
||||||
|
this.replication = replication;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SubmitStrategy create(final SubmitType submitType, final ReviewDb db,
|
||||||
|
final Repository repo, final RevWalk rw, final ObjectInserter inserter,
|
||||||
|
final RevFlag canMergeFlag, final Set<RevCommit> alreadyAccepted,
|
||||||
|
final Branch.NameKey destBranch, final boolean useContentMerge)
|
||||||
|
throws MergeException {
|
||||||
|
final SubmitStrategy.Arguments args =
|
||||||
|
new SubmitStrategy.Arguments(identifiedUserFactory, myIdent, db, repo,
|
||||||
|
rw, inserter, canMergeFlag, alreadyAccepted, destBranch,
|
||||||
|
useContentMerge);
|
||||||
|
switch (submitType) {
|
||||||
|
case CHERRY_PICK:
|
||||||
|
return new CherryPick(args, patchSetInfoFactory, urlProvider,
|
||||||
|
approvalTypes, replication);
|
||||||
|
case FAST_FORWARD_ONLY:
|
||||||
|
return new FastForwardOnly(args);
|
||||||
|
case MERGE_ALWAYS:
|
||||||
|
return new MergeAlways(args);
|
||||||
|
case MERGE_IF_NECESSARY:
|
||||||
|
return new MergeIfNecessary(args);
|
||||||
|
default:
|
||||||
|
final String errorMsg = "No submit strategy for: " + submitType;
|
||||||
|
log.error(errorMsg);
|
||||||
|
throw new MergeException(errorMsg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,14 +16,13 @@ package com.google.gerrit.server.project;
|
|||||||
|
|
||||||
import com.google.gerrit.common.data.PermissionRange;
|
import com.google.gerrit.common.data.PermissionRange;
|
||||||
import com.google.gerrit.common.data.SubmitRecord;
|
import com.google.gerrit.common.data.SubmitRecord;
|
||||||
|
import com.google.gerrit.common.data.SubmitTypeRecord;
|
||||||
import com.google.gerrit.reviewdb.client.Account;
|
import com.google.gerrit.reviewdb.client.Account;
|
||||||
import com.google.gerrit.reviewdb.client.Change;
|
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.PatchSetApproval;
|
import com.google.gerrit.reviewdb.client.PatchSetApproval;
|
||||||
import com.google.gerrit.reviewdb.client.Project;
|
import com.google.gerrit.reviewdb.client.Project;
|
||||||
import com.google.gerrit.reviewdb.server.ReviewDb;
|
import com.google.gerrit.reviewdb.server.ReviewDb;
|
||||||
import com.google.gerrit.rules.PrologEnvironment;
|
|
||||||
import com.google.gerrit.rules.StoredValues;
|
|
||||||
import com.google.gerrit.server.CurrentUser;
|
import com.google.gerrit.server.CurrentUser;
|
||||||
import com.google.gerrit.server.IdentifiedUser;
|
import com.google.gerrit.server.IdentifiedUser;
|
||||||
import com.google.gerrit.server.query.change.ChangeData;
|
import com.google.gerrit.server.query.change.ChangeData;
|
||||||
@@ -32,23 +31,18 @@ import com.google.inject.Inject;
|
|||||||
import com.google.inject.Provider;
|
import com.google.inject.Provider;
|
||||||
import com.google.inject.util.Providers;
|
import com.google.inject.util.Providers;
|
||||||
|
|
||||||
import com.googlecode.prolog_cafe.compiler.CompileException;
|
|
||||||
import com.googlecode.prolog_cafe.lang.IntegerTerm;
|
import com.googlecode.prolog_cafe.lang.IntegerTerm;
|
||||||
import com.googlecode.prolog_cafe.lang.ListTerm;
|
import com.googlecode.prolog_cafe.lang.ListTerm;
|
||||||
import com.googlecode.prolog_cafe.lang.Prolog;
|
import com.googlecode.prolog_cafe.lang.Prolog;
|
||||||
import com.googlecode.prolog_cafe.lang.PrologException;
|
|
||||||
import com.googlecode.prolog_cafe.lang.StructureTerm;
|
import com.googlecode.prolog_cafe.lang.StructureTerm;
|
||||||
import com.googlecode.prolog_cafe.lang.Term;
|
import com.googlecode.prolog_cafe.lang.Term;
|
||||||
import com.googlecode.prolog_cafe.lang.VariableTerm;
|
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
@@ -303,6 +297,7 @@ public class ChangeControl {
|
|||||||
return canSubmit(db, patchSet, null, false, false);
|
return canSubmit(db, patchSet, null, false, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
public List<SubmitRecord> canSubmit(ReviewDb db, PatchSet patchSet,
|
public List<SubmitRecord> canSubmit(ReviewDb db, PatchSet patchSet,
|
||||||
@Nullable ChangeData cd, boolean fastEvalLabels, boolean allowClosed) {
|
@Nullable ChangeData cd, boolean fastEvalLabels, boolean allowClosed) {
|
||||||
if (!allowClosed && change.getStatus().isClosed()) {
|
if (!allowClosed && change.getStatus().isClosed()) {
|
||||||
@@ -334,103 +329,18 @@ public class ChangeControl {
|
|||||||
return logRuleError("Cannot read patch set " + patchSet.getId(), err);
|
return logRuleError("Cannot read patch set " + patchSet.getId(), err);
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Term> results = new ArrayList<Term>();
|
List<Term> results;
|
||||||
Term submitRule;
|
SubmitRuleEvaluator evaluator;
|
||||||
ProjectState projectState = getProjectControl().getProjectState();
|
|
||||||
PrologEnvironment env;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
env = projectState.newPrologEnvironment();
|
evaluator = new SubmitRuleEvaluator(db, patchSet,
|
||||||
} catch (CompileException err) {
|
getProjectControl(),
|
||||||
return logRuleError("Cannot consult rules.pl for "
|
this, change, cd,
|
||||||
+ getProject().getName(), err);
|
fastEvalLabels,
|
||||||
}
|
"locate_submit_rule", "can_submit",
|
||||||
|
"locate_submit_filter", "filter_submit_results");
|
||||||
try {
|
results = evaluator.evaluate().toJava();
|
||||||
env.set(StoredValues.REVIEW_DB, db);
|
} catch (RuleEvalException e) {
|
||||||
env.set(StoredValues.CHANGE, change);
|
return logRuleError(e.getMessage(), e);
|
||||||
env.set(StoredValues.CHANGE_DATA, cd);
|
|
||||||
env.set(StoredValues.PATCH_SET, patchSet);
|
|
||||||
env.set(StoredValues.CHANGE_CONTROL, this);
|
|
||||||
|
|
||||||
submitRule = env.once(
|
|
||||||
"gerrit", "locate_submit_rule",
|
|
||||||
new VariableTerm());
|
|
||||||
if (submitRule == null) {
|
|
||||||
return logRuleError("No user:submit_rule found for "
|
|
||||||
+ getProject().getName());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fastEvalLabels) {
|
|
||||||
env.once("gerrit", "assume_range_from_label");
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
for (Term[] template : env.all(
|
|
||||||
"gerrit", "can_submit",
|
|
||||||
submitRule,
|
|
||||||
new VariableTerm())) {
|
|
||||||
results.add(template[1]);
|
|
||||||
}
|
|
||||||
} catch (PrologException err) {
|
|
||||||
return logRuleError("Exception calling " + submitRule + " on change "
|
|
||||||
+ change.getId() + " of " + getProject().getName(), err);
|
|
||||||
} catch (RuntimeException err) {
|
|
||||||
return logRuleError("Exception calling " + submitRule + " on change "
|
|
||||||
+ change.getId() + " of " + getProject().getName(), err);
|
|
||||||
}
|
|
||||||
|
|
||||||
ProjectState parentState = projectState.getParentState();
|
|
||||||
PrologEnvironment childEnv = env;
|
|
||||||
Set<Project.NameKey> projectsSeen = new HashSet<Project.NameKey>();
|
|
||||||
projectsSeen.add(getProject().getNameKey());
|
|
||||||
|
|
||||||
while (parentState != null) {
|
|
||||||
if (!projectsSeen.add(parentState.getProject().getNameKey())) {
|
|
||||||
//parent has been seen before, stop walk up inheritance tree
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
PrologEnvironment parentEnv;
|
|
||||||
try {
|
|
||||||
parentEnv = parentState.newPrologEnvironment();
|
|
||||||
} catch (CompileException err) {
|
|
||||||
return logRuleError("Cannot consult rules.pl for "
|
|
||||||
+ parentState.getProject().getName(), err);
|
|
||||||
}
|
|
||||||
|
|
||||||
parentEnv.copyStoredValues(childEnv);
|
|
||||||
Term filterRule =
|
|
||||||
parentEnv.once("gerrit", "locate_submit_filter", new VariableTerm());
|
|
||||||
if (filterRule != null) {
|
|
||||||
try {
|
|
||||||
if (fastEvalLabels) {
|
|
||||||
env.once("gerrit", "assume_range_from_label");
|
|
||||||
}
|
|
||||||
|
|
||||||
Term resultsTerm = toListTerm(results);
|
|
||||||
results.clear();
|
|
||||||
Term[] template = parentEnv.once(
|
|
||||||
"gerrit", "filter_submit_results",
|
|
||||||
filterRule,
|
|
||||||
resultsTerm,
|
|
||||||
new VariableTerm());
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
final List<? extends Term> termList = ((ListTerm) template[2]).toJava();
|
|
||||||
results.addAll(termList);
|
|
||||||
} catch (PrologException err) {
|
|
||||||
return logRuleError("Exception calling " + filterRule + " on change "
|
|
||||||
+ change.getId() + " of " + parentState.getProject().getName(), err);
|
|
||||||
} catch (RuntimeException err) {
|
|
||||||
return logRuleError("Exception calling " + filterRule + " on change "
|
|
||||||
+ change.getId() + " of " + parentState.getProject().getName(), err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
parentState = parentState.getParentState();
|
|
||||||
childEnv = parentEnv;
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
env.close();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (results.isEmpty()) {
|
if (results.isEmpty()) {
|
||||||
@@ -438,12 +348,13 @@ public class ChangeControl {
|
|||||||
// at least one result informing the caller of the labels that are
|
// at least one result informing the caller of the labels that are
|
||||||
// required for this change to be submittable. Each label will indicate
|
// required for this change to be submittable. Each label will indicate
|
||||||
// whether or not that is actually possible given the permissions.
|
// whether or not that is actually possible given the permissions.
|
||||||
log.error("Submit rule " + submitRule + " for change " + change.getId()
|
log.error("Submit rule '" + evaluator.getSubmitRule() + "' for change "
|
||||||
+ " of " + getProject().getName() + " has no solution.");
|
+ change.getId() + " of " + getProject().getName()
|
||||||
|
+ " has no solution.");
|
||||||
return ruleError("Project submit rule has no solution");
|
return ruleError("Project submit rule has no solution");
|
||||||
}
|
}
|
||||||
|
|
||||||
return resultsToSubmitRecord(submitRule, results);
|
return resultsToSubmitRecord(evaluator.getSubmitRule(), results);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -528,6 +439,65 @@ public class ChangeControl {
|
|||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public SubmitTypeRecord getSubmitTypeRecord(ReviewDb db, PatchSet patchSet) {
|
||||||
|
return getSubmitTypeRecord(db, patchSet, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public SubmitTypeRecord getSubmitTypeRecord(ReviewDb db, PatchSet patchSet,
|
||||||
|
@Nullable ChangeData cd) {
|
||||||
|
if (!patchSet.getId().equals(change.currentPatchSetId())) {
|
||||||
|
return typeRuleError("Patch set " + patchSet.getPatchSetId()
|
||||||
|
+ " is not current");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (change.getStatus() == Change.Status.DRAFT && !isDraftVisible(db, cd)) {
|
||||||
|
return typeRuleError("Patch set " + patchSet.getPatchSetId()
|
||||||
|
+ " not found");
|
||||||
|
}
|
||||||
|
if (patchSet.isDraft() && !isDraftVisible(db, cd)) {
|
||||||
|
return typeRuleError("Patch set " + patchSet.getPatchSetId()
|
||||||
|
+ " not found");
|
||||||
|
}
|
||||||
|
} catch (OrmException err) {
|
||||||
|
return logTypeRuleError("Cannot read patch set " + patchSet.getId(),
|
||||||
|
err);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> results;
|
||||||
|
SubmitRuleEvaluator evaluator;
|
||||||
|
try {
|
||||||
|
evaluator = new SubmitRuleEvaluator(db, patchSet,
|
||||||
|
getProjectControl(), this, change, cd,
|
||||||
|
false,
|
||||||
|
"locate_submit_type", "get_submit_type",
|
||||||
|
"locate_submit_type_filter", "filter_submit_type_results");
|
||||||
|
results = evaluator.evaluate().toJava();
|
||||||
|
} catch (RuleEvalException e) {
|
||||||
|
return logTypeRuleError(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (results.isEmpty()) {
|
||||||
|
// Should never occur for a well written rule
|
||||||
|
log.error("Submit rule '" + evaluator.getSubmitRule() + "' for change "
|
||||||
|
+ change.getId() + " of " + getProject().getName()
|
||||||
|
+ " has no solution.");
|
||||||
|
return typeRuleError("Project submit rule has no solution");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Take only the first result and convert it to SubmitTypeRecord
|
||||||
|
// This logic will need to change once we support multiple submit types
|
||||||
|
// in the UI
|
||||||
|
String typeName = results.get(0);
|
||||||
|
try {
|
||||||
|
return SubmitTypeRecord.OK(
|
||||||
|
Project.SubmitType.valueOf(typeName.toUpperCase()));
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
return logInvalidType(evaluator.getSubmitRule(), typeName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private List<SubmitRecord> logInvalidResult(Term rule, Term record) {
|
private List<SubmitRecord> logInvalidResult(Term rule, Term record) {
|
||||||
return logRuleError("Submit rule " + rule + " for change " + change.getId()
|
return logRuleError("Submit rule " + rule + " for change " + change.getId()
|
||||||
+ " of " + getProject().getName() + " output invalid result: " + record);
|
+ " of " + getProject().getName() + " output invalid result: " + record);
|
||||||
@@ -550,6 +520,29 @@ public class ChangeControl {
|
|||||||
return Collections.singletonList(rec);
|
return Collections.singletonList(rec);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private SubmitTypeRecord logInvalidType(Term rule, String record) {
|
||||||
|
return logTypeRuleError("Submit type rule " + rule + " for change "
|
||||||
|
+ change.getId() + " of " + getProject().getName()
|
||||||
|
+ " output invalid result: " + record);
|
||||||
|
}
|
||||||
|
|
||||||
|
private SubmitTypeRecord logTypeRuleError(String err, Exception e) {
|
||||||
|
log.error(err, e);
|
||||||
|
return typeRuleError("Error evaluating project type rules, check server log");
|
||||||
|
}
|
||||||
|
|
||||||
|
private SubmitTypeRecord logTypeRuleError(String err) {
|
||||||
|
log.error(err);
|
||||||
|
return typeRuleError("Error evaluating project type rules, check server log");
|
||||||
|
}
|
||||||
|
|
||||||
|
private SubmitTypeRecord typeRuleError(String err) {
|
||||||
|
SubmitTypeRecord rec = new SubmitTypeRecord();
|
||||||
|
rec.status = SubmitTypeRecord.Status.RULE_ERROR;
|
||||||
|
rec.errorMessage = err;
|
||||||
|
return rec;
|
||||||
|
}
|
||||||
|
|
||||||
private void appliedBy(SubmitRecord.Label label, Term status) {
|
private void appliedBy(SubmitRecord.Label label, Term status) {
|
||||||
if (status.isStructure() && status.arity() == 1) {
|
if (status.isStructure() && status.arity() == 1) {
|
||||||
Term who = status.arg(0);
|
Term who = status.arg(0);
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
// Copyright (C) 2012 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.project;
|
||||||
|
|
||||||
|
@SuppressWarnings("serial")
|
||||||
|
public class RuleEvalException extends Exception {
|
||||||
|
public RuleEvalException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
RuleEvalException(String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,207 @@
|
|||||||
|
// Copyright (C) 2012 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.project;
|
||||||
|
|
||||||
|
import com.google.gerrit.reviewdb.client.Change;
|
||||||
|
import com.google.gerrit.reviewdb.client.PatchSet;
|
||||||
|
import com.google.gerrit.reviewdb.client.Project;
|
||||||
|
import com.google.gerrit.reviewdb.server.ReviewDb;
|
||||||
|
import com.google.gerrit.rules.PrologEnvironment;
|
||||||
|
import com.google.gerrit.rules.StoredValues;
|
||||||
|
import com.google.gerrit.server.query.change.ChangeData;
|
||||||
|
|
||||||
|
import com.googlecode.prolog_cafe.compiler.CompileException;
|
||||||
|
import com.googlecode.prolog_cafe.lang.ListTerm;
|
||||||
|
import com.googlecode.prolog_cafe.lang.Prolog;
|
||||||
|
import com.googlecode.prolog_cafe.lang.PrologException;
|
||||||
|
import com.googlecode.prolog_cafe.lang.Term;
|
||||||
|
import com.googlecode.prolog_cafe.lang.VariableTerm;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Evaluates a submit-like Prolog rule found in the rules.pl file of the current
|
||||||
|
* project and filters the results through rules found in the parent projects,
|
||||||
|
* all the way up to All-Projects.
|
||||||
|
*/
|
||||||
|
public class SubmitRuleEvaluator {
|
||||||
|
private final ReviewDb db;
|
||||||
|
private final PatchSet patchSet;
|
||||||
|
private final ProjectControl projectControl;
|
||||||
|
private final ChangeControl changeControl;
|
||||||
|
private final Change change;
|
||||||
|
private final ChangeData cd;
|
||||||
|
private final boolean fastEvalLabels;
|
||||||
|
private final String userRuleLocatorName;
|
||||||
|
private final String userRuleWrapperName;
|
||||||
|
private final String filterRuleLocatorName;
|
||||||
|
private final String filterRuleWrapperName;
|
||||||
|
|
||||||
|
private Term submitRule;
|
||||||
|
private String projectName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param userRuleLocatorName The name of the rule used to locate the
|
||||||
|
* user-supplied rule.
|
||||||
|
* @param userRuleWrapperName The name of the wrapper rule used to evaluate
|
||||||
|
* the user-supplied rule.
|
||||||
|
* @param filterRuleLocatorName The name of the rule used to locate the filter
|
||||||
|
* rule.
|
||||||
|
* @param filterRuleWrapperName The name of the rule used to evaluate the
|
||||||
|
* filter rule.
|
||||||
|
*/
|
||||||
|
SubmitRuleEvaluator(ReviewDb db, PatchSet patchSet,
|
||||||
|
ProjectControl projectControl,
|
||||||
|
ChangeControl changeControl, Change change, @Nullable ChangeData cd,
|
||||||
|
boolean fastEvalLabels,
|
||||||
|
String userRuleLocatorName, String userRuleWrapperName,
|
||||||
|
String filterRuleLocatorName, String filterRuleWrapperName) {
|
||||||
|
this.db = db;
|
||||||
|
this.patchSet = patchSet;
|
||||||
|
this.projectControl = projectControl;
|
||||||
|
this.changeControl = changeControl;
|
||||||
|
this.change = change;
|
||||||
|
this.cd = cd;
|
||||||
|
this.fastEvalLabels = fastEvalLabels;
|
||||||
|
this.userRuleLocatorName = userRuleLocatorName;
|
||||||
|
this.userRuleWrapperName = userRuleWrapperName;
|
||||||
|
this.filterRuleLocatorName = filterRuleLocatorName;
|
||||||
|
this.filterRuleWrapperName = filterRuleWrapperName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Evaluates the given rule and filters.
|
||||||
|
*
|
||||||
|
* Sets the {@link #submitRule} to the Term found by the
|
||||||
|
* {@link #userRuleLocatorName}. This can be used when reporting error(s) on
|
||||||
|
* unexpected return value of this method.
|
||||||
|
*
|
||||||
|
* @return List of {@link Term} objects returned from the evaluated rules.
|
||||||
|
* @throws RuleEvalException
|
||||||
|
*/
|
||||||
|
ListTerm evaluate() throws RuleEvalException {
|
||||||
|
List<Term> results = new ArrayList<Term>();
|
||||||
|
ProjectState projectState = projectControl.getProjectState();
|
||||||
|
PrologEnvironment env;
|
||||||
|
|
||||||
|
try {
|
||||||
|
env = projectState.newPrologEnvironment();
|
||||||
|
} catch (CompileException err) {
|
||||||
|
throw new RuleEvalException("Cannot consult rules.pl for "
|
||||||
|
+ getProjectName(), err);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
env.set(StoredValues.REVIEW_DB, db);
|
||||||
|
env.set(StoredValues.CHANGE, change);
|
||||||
|
env.set(StoredValues.CHANGE_DATA, cd);
|
||||||
|
env.set(StoredValues.PATCH_SET, patchSet);
|
||||||
|
env.set(StoredValues.CHANGE_CONTROL, changeControl);
|
||||||
|
|
||||||
|
submitRule = env.once("gerrit", userRuleLocatorName, new VariableTerm());
|
||||||
|
if (fastEvalLabels) {
|
||||||
|
env.once("gerrit", "assume_range_from_label");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
for (Term[] template : env.all("gerrit", userRuleWrapperName,
|
||||||
|
submitRule, new VariableTerm())) {
|
||||||
|
results.add(template[1]);
|
||||||
|
}
|
||||||
|
} catch (PrologException err) {
|
||||||
|
throw new RuleEvalException("Exception calling " + submitRule
|
||||||
|
+ " on change " + change.getId() + " of " + getProjectName(),
|
||||||
|
err);
|
||||||
|
} catch (RuntimeException err) {
|
||||||
|
throw new RuleEvalException("Exception calling " + submitRule
|
||||||
|
+ " on change " + change.getId() + " of " + getProjectName(),
|
||||||
|
err);
|
||||||
|
}
|
||||||
|
|
||||||
|
ProjectState parentState = projectState.getParentState();
|
||||||
|
PrologEnvironment childEnv = env;
|
||||||
|
Set<Project.NameKey> projectsSeen = new HashSet<Project.NameKey>();
|
||||||
|
projectsSeen.add(projectState.getProject().getNameKey());
|
||||||
|
|
||||||
|
Term resultsTerm = toListTerm(results);
|
||||||
|
while (parentState != null) {
|
||||||
|
if (!projectsSeen.add(parentState.getProject().getNameKey())) {
|
||||||
|
// parent has been seen before, stop walk up inheritance tree
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
PrologEnvironment parentEnv;
|
||||||
|
try {
|
||||||
|
parentEnv = parentState.newPrologEnvironment();
|
||||||
|
} catch (CompileException err) {
|
||||||
|
throw new RuleEvalException("Cannot consult rules.pl for "
|
||||||
|
+ parentState.getProject().getName(), err);
|
||||||
|
}
|
||||||
|
|
||||||
|
parentEnv.copyStoredValues(childEnv);
|
||||||
|
Term filterRule =
|
||||||
|
parentEnv.once("gerrit", filterRuleLocatorName, new VariableTerm());
|
||||||
|
try {
|
||||||
|
if (fastEvalLabels) {
|
||||||
|
env.once("gerrit", "assume_range_from_label");
|
||||||
|
}
|
||||||
|
|
||||||
|
Term[] template =
|
||||||
|
parentEnv.once("gerrit", filterRuleWrapperName, filterRule,
|
||||||
|
resultsTerm, new VariableTerm());
|
||||||
|
resultsTerm = template[2];
|
||||||
|
} catch (PrologException err) {
|
||||||
|
throw new RuleEvalException("Exception calling " + filterRule
|
||||||
|
+ " on change " + change.getId() + " of "
|
||||||
|
+ parentState.getProject().getName(), err);
|
||||||
|
} catch (RuntimeException err) {
|
||||||
|
throw new RuleEvalException("Exception calling " + filterRule
|
||||||
|
+ " on change " + change.getId() + " of "
|
||||||
|
+ parentState.getProject().getName(), err);
|
||||||
|
}
|
||||||
|
|
||||||
|
parentState = parentState.getParentState();
|
||||||
|
childEnv = parentEnv;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (ListTerm) resultsTerm;
|
||||||
|
} finally {
|
||||||
|
env.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Term toListTerm(List<Term> terms) {
|
||||||
|
Term list = Prolog.Nil;
|
||||||
|
for (int i = terms.size() - 1; i >= 0; i--) {
|
||||||
|
list = new ListTerm(terms.get(i), list);
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
Term getSubmitRule() {
|
||||||
|
return submitRule;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getProjectName() {
|
||||||
|
if (projectName == null) {
|
||||||
|
projectName = projectControl.getProjectState().getProject().getName();
|
||||||
|
}
|
||||||
|
return projectName;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
// Copyright (C) 2012 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 gerrit;
|
||||||
|
|
||||||
|
import com.google.gerrit.reviewdb.client.Project.SubmitType;
|
||||||
|
import com.google.gerrit.rules.StoredValues;
|
||||||
|
import com.google.gerrit.server.project.ChangeControl;
|
||||||
|
|
||||||
|
import com.googlecode.prolog_cafe.lang.Operation;
|
||||||
|
import com.googlecode.prolog_cafe.lang.Predicate;
|
||||||
|
import com.googlecode.prolog_cafe.lang.Prolog;
|
||||||
|
import com.googlecode.prolog_cafe.lang.PrologException;
|
||||||
|
import com.googlecode.prolog_cafe.lang.SymbolTerm;
|
||||||
|
import com.googlecode.prolog_cafe.lang.Term;
|
||||||
|
|
||||||
|
public class PRED_project_default_submit_type_1 extends Predicate.P1 {
|
||||||
|
|
||||||
|
private static final SymbolTerm[] term;
|
||||||
|
|
||||||
|
static {
|
||||||
|
SubmitType[] val = SubmitType.values();
|
||||||
|
term = new SymbolTerm[val.length];
|
||||||
|
for (int i = 0; i < val.length; i++) {
|
||||||
|
term[i] = SymbolTerm.create(val[i].name());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public PRED_project_default_submit_type_1(Term a1, Operation n) {
|
||||||
|
arg1 = a1;
|
||||||
|
cont = n;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Operation exec(Prolog engine) throws PrologException {
|
||||||
|
engine.setB0();
|
||||||
|
Term a1 = arg1.dereference();
|
||||||
|
|
||||||
|
ChangeControl control = StoredValues.CHANGE_CONTROL.get(engine);
|
||||||
|
SubmitType submitType = control.getProject().getSubmitType();
|
||||||
|
|
||||||
|
if (!a1.unify(term[submitType.ordinal()], engine.trail)) {
|
||||||
|
return engine.fail();
|
||||||
|
}
|
||||||
|
return cont;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -140,12 +140,12 @@ not_same(_, _).
|
|||||||
:- public can_submit/2.
|
:- public can_submit/2.
|
||||||
%%
|
%%
|
||||||
can_submit(SubmitRule, S) :-
|
can_submit(SubmitRule, S) :-
|
||||||
call_submit_rule(SubmitRule, Tmp),
|
call_rule(SubmitRule, Tmp),
|
||||||
Tmp =.. [submit | Ls],
|
Tmp =.. [submit | Ls],
|
||||||
( is_all_ok(Ls) -> S = ok(Tmp), ! ; S = not_ready(Tmp) ).
|
( is_all_ok(Ls) -> S = ok(Tmp), ! ; S = not_ready(Tmp) ).
|
||||||
|
|
||||||
call_submit_rule(P:X, Arg) :- !, F =.. [X, Arg], P:F.
|
call_rule(P:X, Arg) :- !, F =.. [X, Arg], P:F.
|
||||||
call_submit_rule(X, Arg) :- !, F =.. [X, Arg], F.
|
call_rule(X, Arg) :- !, F =.. [X, Arg], F.
|
||||||
|
|
||||||
is_all_ok([]).
|
is_all_ok([]).
|
||||||
is_all_ok([label(_, ok(__)) | Ls]) :- is_all_ok(Ls).
|
is_all_ok([label(_, ok(__)) | Ls]) :- is_all_ok(Ls).
|
||||||
@@ -153,6 +153,21 @@ is_all_ok([label(_, may(__)) | Ls]) :- is_all_ok(Ls).
|
|||||||
is_all_ok(_) :- fail.
|
is_all_ok(_) :- fail.
|
||||||
|
|
||||||
|
|
||||||
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||||
|
%%
|
||||||
|
%% locate_helper
|
||||||
|
%%
|
||||||
|
%% Returns user:Func if it exists otherwise returns gerrit:Default
|
||||||
|
|
||||||
|
locate_helper(Func, Default, Arity, user:Func) :-
|
||||||
|
'$compiled_predicate'(user, Func, Arity), !.
|
||||||
|
locate_helper(Func, Default, Arity, user:Func) :-
|
||||||
|
listN(Arity, P), C =.. [Func | P], clause(user:C, _), !.
|
||||||
|
locate_helper(Func, Default, _, gerrit:Default).
|
||||||
|
|
||||||
|
listN(0, []).
|
||||||
|
listN(N, [_|T]) :- N > 0, N1 is N - 1, listN(N1, T).
|
||||||
|
|
||||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||||
%%
|
%%
|
||||||
%% locate_submit_rule/1:
|
%% locate_submit_rule/1:
|
||||||
@@ -164,17 +179,32 @@ is_all_ok(_) :- fail.
|
|||||||
%%
|
%%
|
||||||
|
|
||||||
locate_submit_rule(RuleName) :-
|
locate_submit_rule(RuleName) :-
|
||||||
'$compiled_predicate'(user, submit_rule, 1),
|
locate_helper(submit_rule, default_submit, 1, RuleName).
|
||||||
!,
|
|
||||||
RuleName = user:submit_rule
|
|
||||||
.
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||||
locate_submit_rule(RuleName) :-
|
%%
|
||||||
clause(user:submit_rule(_), _),
|
%% get_submit_type/2:
|
||||||
!,
|
%%
|
||||||
RuleName = user:submit_rule
|
%% Executes the SubmitTypeRule and return the first solution
|
||||||
.
|
%%
|
||||||
locate_submit_rule(RuleName) :-
|
:- public get_submit_type/2.
|
||||||
RuleName = gerrit:default_submit.
|
%%
|
||||||
|
get_submit_type(SubmitTypeRule, A) :-
|
||||||
|
call_rule(SubmitTypeRule, A), !.
|
||||||
|
|
||||||
|
|
||||||
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||||
|
%%
|
||||||
|
%% locate_submit_type/1:
|
||||||
|
%%
|
||||||
|
%% Finds a submit_type_rule depending on what rules are available.
|
||||||
|
%% If none are available, use project_default_submit_type/1.
|
||||||
|
%%
|
||||||
|
:- public locate_submit_type/1.
|
||||||
|
%%
|
||||||
|
locate_submit_type(RuleName) :-
|
||||||
|
locate_helper(submit_type, project_default_submit_type, 1, RuleName).
|
||||||
|
|
||||||
|
|
||||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||||
@@ -306,6 +336,17 @@ filter_submit_results(Filter, [], Out, Out).
|
|||||||
call_submit_filter(P:X, R, S) :- !, F =.. [X, R, S], P:F.
|
call_submit_filter(P:X, R, S) :- !, F =.. [X, R, S], P:F.
|
||||||
call_submit_filter(X, R, S) :- F =.. [X, R, S], F.
|
call_submit_filter(X, R, S) :- F =.. [X, R, S], F.
|
||||||
|
|
||||||
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||||
|
%%
|
||||||
|
%% filter_submit_type_results/3:
|
||||||
|
%%
|
||||||
|
%% Executes the submit_type_filter against the result,
|
||||||
|
%% returns the filtered result.
|
||||||
|
%%
|
||||||
|
:- public filter_submit_type_results/3.
|
||||||
|
%%
|
||||||
|
filter_submit_type_results(Filter, In, Out) :- call_submit_filter(Filter, In, Out).
|
||||||
|
|
||||||
|
|
||||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||||
%%
|
%%
|
||||||
@@ -316,15 +357,26 @@ call_submit_filter(X, R, S) :- F =.. [X, R, S], F.
|
|||||||
:- public locate_submit_filter/1.
|
:- public locate_submit_filter/1.
|
||||||
%%
|
%%
|
||||||
locate_submit_filter(FilterName) :-
|
locate_submit_filter(FilterName) :-
|
||||||
'$compiled_predicate'(user, submit_filter, 2),
|
locate_helper(submit_filter, noop_filter, 2, FilterName).
|
||||||
!,
|
|
||||||
FilterName = user:submit_filter
|
|
||||||
.
|
|
||||||
locate_submit_filter(FilterName) :-
|
|
||||||
clause(user:submit_filter(_,_), _),
|
|
||||||
FilterName = user:submit_filter
|
|
||||||
.
|
|
||||||
|
|
||||||
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||||
|
%%
|
||||||
|
%% noop_filter/2:
|
||||||
|
%%
|
||||||
|
:- public noop_filter/2.
|
||||||
|
%%
|
||||||
|
noop_filter(In, In).
|
||||||
|
|
||||||
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||||
|
%%
|
||||||
|
%% locate_submit_type_filter/1:
|
||||||
|
%%
|
||||||
|
%% Finds a submit_type_filter if available.
|
||||||
|
%%
|
||||||
|
:- public locate_submit_type_filter/1.
|
||||||
|
%%
|
||||||
|
locate_submit_type_filter(FilterName) :-
|
||||||
|
locate_helper(submit_type_filter, noop_filter, 2, FilterName).
|
||||||
|
|
||||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||||
%%
|
%%
|
||||||
|
|||||||
Reference in New Issue
Block a user