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.ChangeMessage;
|
||||
import com.google.gerrit.reviewdb.client.PatchSet;
|
||||
import com.google.gerrit.reviewdb.client.Project;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
@@ -40,6 +41,8 @@ public class ChangeDetail {
|
||||
protected List<PatchSet> patchSets;
|
||||
protected List<ApprovalDetail> approvals;
|
||||
protected List<SubmitRecord> submitRecords;
|
||||
protected Project.SubmitType submitType;
|
||||
protected SubmitTypeRecord submitTypeRecord;
|
||||
protected boolean canSubmit;
|
||||
protected List<ChangeMessage> messages;
|
||||
protected PatchSet.Id currentPatchSetId;
|
||||
@@ -187,6 +190,14 @@ public class ChangeDetail {
|
||||
return submitRecords;
|
||||
}
|
||||
|
||||
public void setSubmitTypeRecord(SubmitTypeRecord submitTypeRecord) {
|
||||
this.submitTypeRecord = submitTypeRecord;
|
||||
}
|
||||
|
||||
public SubmitTypeRecord getSubmitTypeRecord() {
|
||||
return submitTypeRecord;
|
||||
}
|
||||
|
||||
public boolean isCurrentPatchSet(final PatchSetDetail detail) {
|
||||
return currentPatchSetId != null
|
||||
&& 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.PatchSetApproval;
|
||||
import com.google.gerrit.reviewdb.client.PatchSetInfo;
|
||||
import com.google.gerrit.reviewdb.client.Project;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
@@ -33,6 +34,7 @@ public class PatchSetPublishDetail {
|
||||
protected List<PermissionRange> labels;
|
||||
protected List<ApprovalDetail> approvals;
|
||||
protected List<SubmitRecord> submitRecords;
|
||||
protected SubmitTypeRecord submitTypeRecord;
|
||||
protected List<PatchSetApproval> given;
|
||||
protected boolean canSubmit;
|
||||
|
||||
@@ -61,6 +63,14 @@ public class PatchSetPublishDetail {
|
||||
return submitRecords;
|
||||
}
|
||||
|
||||
public void setSubmitTypeRecord(SubmitTypeRecord submitTypeRecord) {
|
||||
this.submitTypeRecord = submitTypeRecord;
|
||||
}
|
||||
|
||||
public SubmitTypeRecord getSubmitTypeRecord() {
|
||||
return submitTypeRecord;
|
||||
}
|
||||
|
||||
public List<PatchSetApproval> getGiven() {
|
||||
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 changeInfoBlockUpdated();
|
||||
String changeInfoBlockStatus();
|
||||
String changeInfoBlockSubmitType();
|
||||
String changePermalink();
|
||||
String changeInfoBlockCanMerge();
|
||||
String changeInfoBlockCanMergeYes();
|
||||
|
||||
@@ -72,6 +72,7 @@ changeInfoBlockTopic = Topic
|
||||
changeInfoBlockUploaded = Uploaded
|
||||
changeInfoBlockUpdated = Updated
|
||||
changeInfoBlockStatus = Status
|
||||
changeInfoBlockSubmitType = Submit Type
|
||||
changePermalink = Permalink
|
||||
changeInfoBlockCanMerge = Can Merge
|
||||
changeInfoBlockCanMergeYes = Yes
|
||||
|
||||
@@ -15,8 +15,10 @@
|
||||
package com.google.gerrit.client.changes;
|
||||
|
||||
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.PatchSetInfo;
|
||||
import com.google.gerrit.reviewdb.client.Project;
|
||||
import com.google.gwt.user.client.ui.Composite;
|
||||
import com.google.gwt.user.client.ui.HorizontalPanel;
|
||||
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,
|
||||
final AccountInfoCache acc) {
|
||||
infoBlock.display(chg, acc);
|
||||
final AccountInfoCache acc, SubmitTypeRecord submitTypeRecord) {
|
||||
infoBlock.display(chg, acc, submitTypeRecord);
|
||||
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.ProjectLink;
|
||||
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.Change;
|
||||
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_UPLOADED = 5;
|
||||
private static final int R_UPDATED = 6;
|
||||
private static final int R_STATUS = 7;
|
||||
private static final int R_MERGE_TEST = 8;
|
||||
private static final int R_CNT = 9;
|
||||
private static final int R_SUBMIT_TYPE = 7;
|
||||
private static final int R_STATUS = 8;
|
||||
private static final int R_MERGE_TEST = 9;
|
||||
private static final int R_CNT = 10;
|
||||
|
||||
private final Grid table;
|
||||
|
||||
@@ -59,6 +61,7 @@ public class ChangeInfoBlock extends Composite {
|
||||
initRow(R_UPLOADED, Util.C.changeInfoBlockUploaded());
|
||||
initRow(R_UPDATED, Util.C.changeInfoBlockUpdated());
|
||||
initRow(R_STATUS, Util.C.changeInfoBlockStatus());
|
||||
initRow(R_SUBMIT_TYPE, Util.C.changeInfoBlockSubmitType());
|
||||
if (Gerrit.getConfig().testChangeMerge()) {
|
||||
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());
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
CopyableLabel changeIdLabel =
|
||||
@@ -94,6 +98,14 @@ public class ChangeInfoBlock extends Composite {
|
||||
table.setText(R_UPLOADED, 1, mediumFormat(chg.getCreatedOn()));
|
||||
table.setText(R_UPDATED, 1, mediumFormat(chg.getLastUpdatedOn()));
|
||||
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();
|
||||
if (Gerrit.getConfig().testChangeMerge()) {
|
||||
if (status.equals(Change.Status.NEW) || status.equals(Change.Status.DRAFT)) {
|
||||
|
||||
@@ -282,7 +282,7 @@ public class ChangeScreen extends Screen
|
||||
descriptionBlock.display(detail.getChange(),
|
||||
detail.isStarred(),
|
||||
detail.getCurrentPatchSetDetail().getInfo(),
|
||||
detail.getAccounts());
|
||||
detail.getAccounts(), detail.getSubmitTypeRecord());
|
||||
dependsOn.display(detail.getDependsOn());
|
||||
neededBy.display(detail.getNeededBy());
|
||||
approvals.display(detail);
|
||||
|
||||
@@ -274,7 +274,8 @@ public class PublishCommentScreen extends AccountScreen implements
|
||||
private void display(final PatchSetPublishDetail r) {
|
||||
setPageTitle(Util.M.publishComments(r.getChange().getKey().abbreviate(),
|
||||
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()) {
|
||||
initApprovals(r, approvalPanel);
|
||||
|
||||
@@ -168,6 +168,8 @@ public class ChangeDetailFactory extends Handler<ChangeDetail> {
|
||||
}
|
||||
detail.setSubmitRecords(submitRecords);
|
||||
|
||||
detail.setSubmitTypeRecord(control.getSubmitTypeRecord(db, patch));
|
||||
|
||||
patchsetsById = new HashMap<PatchSet.Id, PatchSet>();
|
||||
loadPatchSets();
|
||||
loadMessages();
|
||||
|
||||
@@ -186,6 +186,8 @@ final class PatchSetPublishDetailFactory extends Handler<PatchSetPublishDetail>
|
||||
detail.setSubmitRecords(submitRecords);
|
||||
}
|
||||
|
||||
detail.setSubmitTypeRecord(control.getSubmitTypeRecord(db, patchSet));
|
||||
|
||||
detail.setLabels(allowed);
|
||||
detail.setGiven(given);
|
||||
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(""),
|
||||
|
||||
/** */
|
||||
NO_SUBMIT_TYPE(""),
|
||||
|
||||
/** */
|
||||
CRISS_CROSS_MERGE("Your change requires a recursive merge to resolve.\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 {
|
||||
private final RevWalk rw;
|
||||
private final RevFlag CAN_MERGE;
|
||||
private final RevFlag canMergeFlag;
|
||||
private final Set<RevCommit> accepted;
|
||||
|
||||
MergeSorter(final RevWalk walk, final Set<RevCommit> alreadyAccepted,
|
||||
final RevFlag flagCAN_MERGE) {
|
||||
rw = walk;
|
||||
CAN_MERGE = flagCAN_MERGE;
|
||||
accepted = alreadyAccepted;
|
||||
MergeSorter(final RevWalk rw, final Set<RevCommit> alreadyAccepted,
|
||||
final RevFlag canMergeFlag) {
|
||||
this.rw = rw;
|
||||
this.canMergeFlag = canMergeFlag;
|
||||
this.accepted = alreadyAccepted;
|
||||
}
|
||||
|
||||
Collection<CodeReviewCommit> sort(final Collection<CodeReviewCommit> incoming)
|
||||
@@ -45,7 +45,7 @@ class MergeSorter {
|
||||
while (!sort.isEmpty()) {
|
||||
final CodeReviewCommit n = removeOne(sort);
|
||||
|
||||
rw.resetRetain(CAN_MERGE);
|
||||
rw.resetRetain(canMergeFlag);
|
||||
rw.markStart(n);
|
||||
for (RevCommit c : accepted) {
|
||||
rw.markUninteresting(c);
|
||||
@@ -54,7 +54,7 @@ class MergeSorter {
|
||||
RevCommit c;
|
||||
final RevCommitList<RevCommit> contents = new RevCommitList<RevCommit>();
|
||||
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
|
||||
// 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.SubmitRecord;
|
||||
import com.google.gerrit.common.data.SubmitTypeRecord;
|
||||
import com.google.gerrit.reviewdb.client.Account;
|
||||
import com.google.gerrit.reviewdb.client.Change;
|
||||
import com.google.gerrit.reviewdb.client.PatchSet;
|
||||
import com.google.gerrit.reviewdb.client.PatchSetApproval;
|
||||
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.CurrentUser;
|
||||
import com.google.gerrit.server.IdentifiedUser;
|
||||
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.util.Providers;
|
||||
|
||||
import com.googlecode.prolog_cafe.compiler.CompileException;
|
||||
import com.googlecode.prolog_cafe.lang.IntegerTerm;
|
||||
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.StructureTerm;
|
||||
import com.googlecode.prolog_cafe.lang.Term;
|
||||
import com.googlecode.prolog_cafe.lang.VariableTerm;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
@@ -303,6 +297,7 @@ public class ChangeControl {
|
||||
return canSubmit(db, patchSet, null, false, false);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public List<SubmitRecord> canSubmit(ReviewDb db, PatchSet patchSet,
|
||||
@Nullable ChangeData cd, boolean fastEvalLabels, boolean allowClosed) {
|
||||
if (!allowClosed && change.getStatus().isClosed()) {
|
||||
@@ -334,103 +329,18 @@ public class ChangeControl {
|
||||
return logRuleError("Cannot read patch set " + patchSet.getId(), err);
|
||||
}
|
||||
|
||||
List<Term> results = new ArrayList<Term>();
|
||||
Term submitRule;
|
||||
ProjectState projectState = getProjectControl().getProjectState();
|
||||
PrologEnvironment env;
|
||||
|
||||
List<Term> results;
|
||||
SubmitRuleEvaluator evaluator;
|
||||
try {
|
||||
env = projectState.newPrologEnvironment();
|
||||
} catch (CompileException err) {
|
||||
return logRuleError("Cannot consult rules.pl for "
|
||||
+ getProject().getName(), 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, 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();
|
||||
evaluator = new SubmitRuleEvaluator(db, patchSet,
|
||||
getProjectControl(),
|
||||
this, change, cd,
|
||||
fastEvalLabels,
|
||||
"locate_submit_rule", "can_submit",
|
||||
"locate_submit_filter", "filter_submit_results");
|
||||
results = evaluator.evaluate().toJava();
|
||||
} catch (RuleEvalException e) {
|
||||
return logRuleError(e.getMessage(), e);
|
||||
}
|
||||
|
||||
if (results.isEmpty()) {
|
||||
@@ -438,12 +348,13 @@ public class ChangeControl {
|
||||
// at least one result informing the caller of the labels that are
|
||||
// required for this change to be submittable. Each label will indicate
|
||||
// whether or not that is actually possible given the permissions.
|
||||
log.error("Submit rule " + submitRule + " for change " + change.getId()
|
||||
+ " of " + getProject().getName() + " has no solution.");
|
||||
log.error("Submit rule '" + evaluator.getSubmitRule() + "' for change "
|
||||
+ change.getId() + " of " + getProject().getName()
|
||||
+ " 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;
|
||||
}
|
||||
|
||||
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) {
|
||||
return logRuleError("Submit rule " + rule + " for change " + change.getId()
|
||||
+ " of " + getProject().getName() + " output invalid result: " + record);
|
||||
@@ -550,6 +520,29 @@ public class ChangeControl {
|
||||
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) {
|
||||
if (status.isStructure() && status.arity() == 1) {
|
||||
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.
|
||||
%%
|
||||
can_submit(SubmitRule, S) :-
|
||||
call_submit_rule(SubmitRule, Tmp),
|
||||
call_rule(SubmitRule, Tmp),
|
||||
Tmp =.. [submit | Ls],
|
||||
( is_all_ok(Ls) -> S = ok(Tmp), ! ; S = not_ready(Tmp) ).
|
||||
|
||||
call_submit_rule(P:X, Arg) :- !, F =.. [X, Arg], P:F.
|
||||
call_submit_rule(X, Arg) :- !, F =.. [X, Arg], F.
|
||||
call_rule(P:X, Arg) :- !, F =.. [X, Arg], P:F.
|
||||
call_rule(X, Arg) :- !, F =.. [X, Arg], F.
|
||||
|
||||
is_all_ok([]).
|
||||
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.
|
||||
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
%%
|
||||
%% 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:
|
||||
@@ -164,17 +179,32 @@ is_all_ok(_) :- fail.
|
||||
%%
|
||||
|
||||
locate_submit_rule(RuleName) :-
|
||||
'$compiled_predicate'(user, submit_rule, 1),
|
||||
!,
|
||||
RuleName = user:submit_rule
|
||||
.
|
||||
locate_submit_rule(RuleName) :-
|
||||
clause(user:submit_rule(_), _),
|
||||
!,
|
||||
RuleName = user:submit_rule
|
||||
.
|
||||
locate_submit_rule(RuleName) :-
|
||||
RuleName = gerrit:default_submit.
|
||||
locate_helper(submit_rule, default_submit, 1, RuleName).
|
||||
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
%%
|
||||
%% get_submit_type/2:
|
||||
%%
|
||||
%% Executes the SubmitTypeRule and return the first solution
|
||||
%%
|
||||
:- public get_submit_type/2.
|
||||
%%
|
||||
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(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.
|
||||
%%
|
||||
locate_submit_filter(FilterName) :-
|
||||
'$compiled_predicate'(user, submit_filter, 2),
|
||||
!,
|
||||
FilterName = user:submit_filter
|
||||
.
|
||||
locate_submit_filter(FilterName) :-
|
||||
clause(user:submit_filter(_,_), _),
|
||||
FilterName = user:submit_filter
|
||||
.
|
||||
locate_helper(submit_filter, noop_filter, 2, FilterName).
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
%%
|
||||
%% 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