Merge changes I2a63f725,I582c12ea,I476c6371,I886e4fcf * changes: Store submit requirements for merged changes in NoteDb Enable parsing submit requirements results from change meta commits Add Gson type adapter to submit requirement entities Submit requirements: refactor SubmitRequirementResult
diff --git a/java/com/google/gerrit/entities/SubmitRequirement.java b/java/com/google/gerrit/entities/SubmitRequirement.java index df03fd5..13e0b53 100644 --- a/java/com/google/gerrit/entities/SubmitRequirement.java +++ b/java/com/google/gerrit/entities/SubmitRequirement.java
@@ -15,6 +15,8 @@ package com.google.gerrit.entities; import com.google.auto.value.AutoValue; +import com.google.gson.Gson; +import com.google.gson.TypeAdapter; import java.util.Optional; /** Entity describing a requirement that should be met for a change to become submittable. */ @@ -62,6 +64,10 @@ return new AutoValue_SubmitRequirement.Builder(); } + public static TypeAdapter<SubmitRequirement> typeAdapter(Gson gson) { + return new AutoValue_SubmitRequirement.GsonTypeAdapter(gson); + } + @AutoValue.Builder public abstract static class Builder {
diff --git a/java/com/google/gerrit/entities/SubmitRequirementExpression.java b/java/com/google/gerrit/entities/SubmitRequirementExpression.java index c978347..2af1379 100644 --- a/java/com/google/gerrit/entities/SubmitRequirementExpression.java +++ b/java/com/google/gerrit/entities/SubmitRequirementExpression.java
@@ -17,6 +17,8 @@ import com.google.auto.value.AutoValue; import com.google.common.base.Strings; import com.google.gerrit.common.Nullable; +import com.google.gson.Gson; +import com.google.gson.TypeAdapter; import java.util.Optional; /** Describe a applicability, blocking or override expression of a {@link SubmitRequirement}. */ @@ -41,4 +43,8 @@ /** Returns the underlying String representing this {@link SubmitRequirementExpression}. */ public abstract String expressionString(); + + public static TypeAdapter<SubmitRequirementExpression> typeAdapter(Gson gson) { + return new AutoValue_SubmitRequirementExpression.GsonTypeAdapter(gson); + } }
diff --git a/java/com/google/gerrit/entities/SubmitRequirementExpressionResult.java b/java/com/google/gerrit/entities/SubmitRequirementExpressionResult.java index f7a883e..58eb4ac 100644 --- a/java/com/google/gerrit/entities/SubmitRequirementExpressionResult.java +++ b/java/com/google/gerrit/entities/SubmitRequirementExpressionResult.java
@@ -16,57 +16,78 @@ import com.google.auto.value.AutoValue; import com.google.common.collect.ImmutableList; +import com.google.gson.Gson; +import com.google.gson.TypeAdapter; import java.util.Optional; /** Result of evaluating a submit requirement expression on a given Change. */ @AutoValue public abstract class SubmitRequirementExpressionResult { - /** - * Entity detailing the result of evaluating a Submit requirement expression. Contains an empty - * {@link Optional} if {@link #status()} is equal to {@link Status#ERROR}. - */ - public abstract Optional<PredicateResult> predicateResult(); + /** Submit requirement expression for which this result is evaluated. */ + public abstract SubmitRequirementExpression expression(); + /** Status of evaluation. */ + public abstract Status status(); + + /** + * Optional error message. Populated if the evaluator fails to evaluate the expression for a + * certain change. + */ public abstract Optional<String> errorMessage(); - public Status status() { - if (predicateResult().isPresent()) { - return predicateResult().get().status() ? Status.PASS : Status.FAIL; - } - return Status.ERROR; - } - - public static SubmitRequirementExpressionResult create(PredicateResult predicateResult) { - return new AutoValue_SubmitRequirementExpressionResult( - Optional.of(predicateResult), Optional.empty()); - } - - public static SubmitRequirementExpressionResult error(String errorMessage) { - return new AutoValue_SubmitRequirementExpressionResult( - Optional.empty(), Optional.of(errorMessage)); - } + /** + * List leaf predicates that are fulfilled, for example the expression + * + * <p><i>label:code-review=+2 and branch:refs/heads/master</i> + * + * <p>has two leaf predicates: + * + * <ul> + * <li>label:code-review=+2 + * <li>branch:refs/heads/master + * </ul> + * + * This method will return the leaf predicates that were fulfilled, for example if only the first + * predicate was fulfilled, the returned list will be equal to ["label:code-review=+2"]. + */ + public abstract ImmutableList<String> passingAtoms(); /** - * Returns a list of leaf predicate results whose {@link PredicateResult#status()} is true. If - * {@link #status()} is equal to {@link Status#ERROR}, an empty list is returned. + * List of leaf predicates that are not fulfilled. See {@link #passingAtoms()} for more details. */ - public ImmutableList<PredicateResult> getPassingAtoms() { - if (predicateResult().isPresent()) { - return predicateResult().get().getAtoms(/* status= */ true); - } - return ImmutableList.of(); + public abstract ImmutableList<String> failingAtoms(); + + public static SubmitRequirementExpressionResult create( + SubmitRequirementExpression expression, PredicateResult predicateResult) { + return create( + expression, + predicateResult.status() ? Status.PASS : Status.FAIL, + predicateResult.getPassingAtoms(), + predicateResult.getFailingAtoms()); } - /** - * Returns a list of leaf predicate results whose {@link PredicateResult#status()} is false. If - * {@link #status()} is equal to {@link Status#ERROR}, an empty list is returned. - */ - public ImmutableList<PredicateResult> getFailingAtoms() { - if (predicateResult().isPresent()) { - return predicateResult().get().getAtoms(/* status= */ false); - } - return ImmutableList.of(); + public static SubmitRequirementExpressionResult create( + SubmitRequirementExpression expression, + Status status, + ImmutableList<String> passingAtoms, + ImmutableList<String> failingAtoms) { + return new AutoValue_SubmitRequirementExpressionResult( + expression, status, Optional.empty(), passingAtoms, failingAtoms); + } + + public static SubmitRequirementExpressionResult error( + SubmitRequirementExpression expression, String errorMessage) { + return new AutoValue_SubmitRequirementExpressionResult( + expression, + Status.ERROR, + Optional.of(errorMessage), + ImmutableList.of(), + ImmutableList.of()); + } + + public static TypeAdapter<SubmitRequirementExpressionResult> typeAdapter(Gson gson) { + return new AutoValue_SubmitRequirementExpressionResult.GsonTypeAdapter(gson); } public enum Status { @@ -103,11 +124,25 @@ /** true if the predicate is passing for a given change. */ abstract boolean status(); + /** Returns a list of leaf predicate results whose {@link PredicateResult#status()} is true. */ + ImmutableList<String> getPassingAtoms() { + return getAtoms(/* status= */ true).stream() + .map(PredicateResult::predicateString) + .collect(ImmutableList.toImmutableList()); + } + + /** Returns a list of leaf predicate results whose {@link PredicateResult#status()} is false. */ + ImmutableList<String> getFailingAtoms() { + return getAtoms(/* status= */ false).stream() + .map(PredicateResult::predicateString) + .collect(ImmutableList.toImmutableList()); + } + /** * Returns the list of leaf {@link PredicateResult} whose {@link #status()} is equal to the * {@code status} parameter. */ - ImmutableList<PredicateResult> getAtoms(boolean status) { + private ImmutableList<PredicateResult> getAtoms(boolean status) { ImmutableList.Builder<PredicateResult> atomsList = ImmutableList.builder(); getAtomsRecursively(atomsList, status); return atomsList.build();
diff --git a/java/com/google/gerrit/entities/SubmitRequirementResult.java b/java/com/google/gerrit/entities/SubmitRequirementResult.java index e28c86f..e1d5f39 100644 --- a/java/com/google/gerrit/entities/SubmitRequirementResult.java +++ b/java/com/google/gerrit/entities/SubmitRequirementResult.java
@@ -16,11 +16,17 @@ import com.google.auto.value.AutoValue; import com.google.auto.value.extension.memoized.Memoized; +import com.google.gson.Gson; +import com.google.gson.TypeAdapter; import java.util.Optional; +import org.eclipse.jgit.lib.ObjectId; /** Result of evaluating a {@link SubmitRequirement} on a given Change. */ @AutoValue public abstract class SubmitRequirementResult { + /** Submit requirement for which this result is evaluated. */ + public abstract SubmitRequirement submitRequirement(); + /** Result of evaluating a {@link SubmitRequirement#applicabilityExpression()} on a change. */ public abstract Optional<SubmitRequirementExpressionResult> applicabilityExpressionResult(); @@ -32,6 +38,9 @@ /** Result of evaluating a {@link SubmitRequirement#overrideExpression()} ()} on a change. */ public abstract Optional<SubmitRequirementExpressionResult> overrideExpressionResult(); + /** SHA-1 of the patchset commit ID for which the submit requirement was evaluated. */ + public abstract ObjectId patchSetCommitId(); + @Memoized public Status status() { if (assertError(submittabilityExpressionResult()) @@ -53,6 +62,10 @@ return new AutoValue_SubmitRequirementResult.Builder(); } + public static TypeAdapter<SubmitRequirementResult> typeAdapter(Gson gson) { + return new AutoValue_SubmitRequirementResult.GsonTypeAdapter(gson); + } + public enum Status { /** Submit requirement is fulfilled. */ SATISFIED, @@ -84,6 +97,7 @@ @AutoValue.Builder public abstract static class Builder { + public abstract Builder submitRequirement(SubmitRequirement submitRequirement); public abstract Builder applicabilityExpressionResult( Optional<SubmitRequirementExpressionResult> value); @@ -93,6 +107,8 @@ public abstract Builder overrideExpressionResult( Optional<SubmitRequirementExpressionResult> value); + public abstract Builder patchSetCommitId(ObjectId value); + public abstract SubmitRequirementResult build(); }
diff --git a/java/com/google/gerrit/server/cache/serialize/entities/SubmitRequirementExpressionResultSerializer.java b/java/com/google/gerrit/server/cache/serialize/entities/SubmitRequirementExpressionResultSerializer.java new file mode 100644 index 0000000..4e997b4 --- /dev/null +++ b/java/com/google/gerrit/server/cache/serialize/entities/SubmitRequirementExpressionResultSerializer.java
@@ -0,0 +1,45 @@ +// Copyright (C) 2021 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.cache.serialize.entities; + +import com.google.common.collect.ImmutableList; +import com.google.gerrit.entities.SubmitRequirementExpression; +import com.google.gerrit.entities.SubmitRequirementExpressionResult; +import com.google.gerrit.server.cache.proto.Cache.SubmitRequirementExpressionResultProto; + +/** + * Serializer of a {@link SubmitRequirementExpressionResult} to {@link + * SubmitRequirementExpressionResultProto}. + */ +public class SubmitRequirementExpressionResultSerializer { + public static SubmitRequirementExpressionResult deserialize( + SubmitRequirementExpressionResultProto proto) { + return SubmitRequirementExpressionResult.create( + SubmitRequirementExpression.create(proto.getExpression()), + SubmitRequirementExpressionResult.Status.valueOf(proto.getStatus()), + proto.getPassingAtomsList().stream().collect(ImmutableList.toImmutableList()), + proto.getFailingAtomsList().stream().collect(ImmutableList.toImmutableList())); + } + + public static SubmitRequirementExpressionResultProto serialize( + SubmitRequirementExpressionResult r) { + return SubmitRequirementExpressionResultProto.newBuilder() + .setExpression(r.expression().expressionString()) + .setStatus(r.status().name()) + .addAllPassingAtoms(r.passingAtoms()) + .addAllFailingAtoms(r.failingAtoms()) + .build(); + } +}
diff --git a/java/com/google/gerrit/server/change/ChangeJson.java b/java/com/google/gerrit/server/change/ChangeJson.java index e9c9946..ff8d8cb 100644 --- a/java/com/google/gerrit/server/change/ChangeJson.java +++ b/java/com/google/gerrit/server/change/ChangeJson.java
@@ -63,7 +63,6 @@ import com.google.gerrit.entities.SubmitRequirement; import com.google.gerrit.entities.SubmitRequirementExpression; import com.google.gerrit.entities.SubmitRequirementExpressionResult; -import com.google.gerrit.entities.SubmitRequirementExpressionResult.PredicateResult; import com.google.gerrit.entities.SubmitRequirementResult; import com.google.gerrit.entities.SubmitTypeRecord; import com.google.gerrit.exceptions.StorageException; @@ -411,10 +410,8 @@ SubmitRequirementExpressionInfo info = new SubmitRequirementExpressionInfo(); info.expression = expression.expressionString(); info.fulfilled = result.status().equals(SubmitRequirementExpressionResult.Status.PASS); - info.passingAtoms = - result.getPassingAtoms().stream().map(PredicateResult::predicateString).collect(toList()); - info.failingAtoms = - result.getFailingAtoms().stream().map(PredicateResult::predicateString).collect(toList()); + info.passingAtoms = result.passingAtoms(); + info.failingAtoms = result.failingAtoms(); return info; }
diff --git a/java/com/google/gerrit/server/notedb/ChangeNoteJson.java b/java/com/google/gerrit/server/notedb/ChangeNoteJson.java index 483b2e9..4c41a12 100644 --- a/java/com/google/gerrit/server/notedb/ChangeNoteJson.java +++ b/java/com/google/gerrit/server/notedb/ChangeNoteJson.java
@@ -14,9 +14,17 @@ package com.google.gerrit.server.notedb; +import com.google.common.collect.ImmutableList; +import com.google.gerrit.entities.EntitiesAdapterFactory; +import com.google.gerrit.json.EnumTypeAdapterFactory; import com.google.gson.Gson; import com.google.gson.GsonBuilder; +import com.google.gson.TypeAdapter; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; import com.google.inject.Singleton; +import com.google.inject.TypeLiteral; +import java.io.IOException; import java.sql.Timestamp; @Singleton @@ -26,6 +34,11 @@ static Gson newGson() { return new GsonBuilder() .registerTypeAdapter(Timestamp.class, new CommentTimestampAdapter().nullSafe()) + .registerTypeAdapterFactory(new EnumTypeAdapterFactory()) + .registerTypeAdapterFactory(EntitiesAdapterFactory.create()) + .registerTypeAdapter( + new TypeLiteral<ImmutableList<String>>() {}.getType(), + new ImmutableListAdapter().nullSafe()) .setPrettyPrinting() .create(); } @@ -33,4 +46,27 @@ public Gson getGson() { return gson; } + + static class ImmutableListAdapter extends TypeAdapter<ImmutableList<String>> { + + @Override + public void write(JsonWriter out, ImmutableList<String> value) throws IOException { + out.beginArray(); + for (String v : value) { + out.value(v); + } + out.endArray(); + } + + @Override + public ImmutableList<String> read(JsonReader in) throws IOException { + ImmutableList.Builder<String> builder = ImmutableList.builder(); + in.beginArray(); + while (in.hasNext()) { + builder.add(in.nextString()); + } + in.endArray(); + return builder.build(); + } + } }
diff --git a/java/com/google/gerrit/server/notedb/ChangeNotes.java b/java/com/google/gerrit/server/notedb/ChangeNotes.java index 5daf28c..6684493 100644 --- a/java/com/google/gerrit/server/notedb/ChangeNotes.java +++ b/java/com/google/gerrit/server/notedb/ChangeNotes.java
@@ -50,6 +50,7 @@ import com.google.gerrit.entities.RefNames; import com.google.gerrit.entities.RobotComment; import com.google.gerrit.entities.SubmitRecord; +import com.google.gerrit.entities.SubmitRequirementResult; import com.google.gerrit.server.AssigneeStatusUpdate; import com.google.gerrit.server.ReviewerByEmailSet; import com.google.gerrit.server.ReviewerSet; @@ -413,6 +414,16 @@ } /** + * Returns the evaluated submit requirements for the change. We only intend to store submit + * requirements in NoteDb for closed changes, hence the result will be an empty list for active + * changes, or a list of submit requirements results otherwise. For closed changes, the results + * represent the state of evaluating submit requirements for this change when it was merged. + */ + public ImmutableList<SubmitRequirementResult> getSubmitRequirementsResult() { + return state.submitRequirementsResult(); + } + + /** * @return an ImmutableSet of Account.Ids of all users that have been assigned to this change. The * order of the set is the order in which they were assigned. */
diff --git a/java/com/google/gerrit/server/notedb/ChangeNotesParser.java b/java/com/google/gerrit/server/notedb/ChangeNotesParser.java index 8be0d82..2a53c29 100644 --- a/java/com/google/gerrit/server/notedb/ChangeNotesParser.java +++ b/java/com/google/gerrit/server/notedb/ChangeNotesParser.java
@@ -68,6 +68,7 @@ import com.google.gerrit.entities.PatchSetApproval; import com.google.gerrit.entities.RefNames; import com.google.gerrit.entities.SubmitRecord; +import com.google.gerrit.entities.SubmitRequirementResult; import com.google.gerrit.metrics.Timer0; import com.google.gerrit.server.AssigneeStatusUpdate; import com.google.gerrit.server.ReviewerByEmailSet; @@ -125,6 +126,7 @@ private final List<AssigneeStatusUpdate> assigneeUpdates; private final List<SubmitRecord> submitRecords; private final ListMultimap<ObjectId, HumanComment> humanComments; + private final List<SubmitRequirementResult> submitRequirementResults; private final Map<PatchSet.Id, PatchSet.Builder> patchSets; private final Set<PatchSet.Id> deletedPatchSets; private final Map<PatchSet.Id, PatchSetState> patchSetStates; @@ -187,6 +189,7 @@ submitRecords = Lists.newArrayListWithExpectedSize(1); allChangeMessages = new ArrayList<>(); humanComments = MultimapBuilder.hashKeys().arrayListValues().build(); + submitRequirementResults = new ArrayList<>(); patchSets = new HashMap<>(); deletedPatchSets = new HashSet<>(); patchSetStates = new HashMap<>(); @@ -259,6 +262,7 @@ submitRecords, buildAllMessages(), humanComments, + submitRequirementResults, firstNonNull(isPrivate, false), firstNonNull(workInProgress, false), firstNonNull(hasReviewStarted, true), @@ -774,6 +778,9 @@ for (HumanComment c : e.getValue().getEntities()) { humanComments.put(e.getKey(), c); } + for (SubmitRequirementResult sr : e.getValue().getSubmitRequirementsResult()) { + submitRequirementResults.add(sr); + } } for (PatchSet.Builder b : patchSets.values()) {
diff --git a/java/com/google/gerrit/server/notedb/ChangeNotesState.java b/java/com/google/gerrit/server/notedb/ChangeNotesState.java index 33bc039..e7da025 100644 --- a/java/com/google/gerrit/server/notedb/ChangeNotesState.java +++ b/java/com/google/gerrit/server/notedb/ChangeNotesState.java
@@ -44,6 +44,7 @@ import com.google.gerrit.entities.PatchSetApproval; import com.google.gerrit.entities.Project; import com.google.gerrit.entities.SubmitRecord; +import com.google.gerrit.entities.SubmitRequirementResult; import com.google.gerrit.entities.converter.ChangeMessageProtoConverter; import com.google.gerrit.entities.converter.PatchSetApprovalProtoConverter; import com.google.gerrit.entities.converter.PatchSetProtoConverter; @@ -60,10 +61,14 @@ import com.google.gerrit.server.cache.proto.Cache.ChangeNotesStateProto.ReviewerByEmailSetEntryProto; import com.google.gerrit.server.cache.proto.Cache.ChangeNotesStateProto.ReviewerSetEntryProto; import com.google.gerrit.server.cache.proto.Cache.ChangeNotesStateProto.ReviewerStatusUpdateProto; +import com.google.gerrit.server.cache.proto.Cache.SubmitRequirementResultProto; import com.google.gerrit.server.cache.serialize.CacheSerializer; import com.google.gerrit.server.cache.serialize.ObjectIdConverter; +import com.google.gerrit.server.cache.serialize.entities.SubmitRequirementExpressionResultSerializer; +import com.google.gerrit.server.cache.serialize.entities.SubmitRequirementSerializer; import com.google.gerrit.server.index.change.ChangeField.StoredSubmitRecord; import com.google.gson.Gson; +import com.google.protobuf.Descriptors.FieldDescriptor; import java.sql.Timestamp; import java.time.Instant; import java.util.List; @@ -128,6 +133,7 @@ List<SubmitRecord> submitRecords, List<ChangeMessage> changeMessages, ListMultimap<ObjectId, HumanComment> publishedComments, + List<SubmitRequirementResult> submitRequirementResults, boolean isPrivate, boolean workInProgress, boolean reviewStarted, @@ -181,6 +187,7 @@ .submitRecords(submitRecords) .changeMessages(changeMessages) .publishedComments(publishedComments) + .submitRequirementsResult(submitRequirementResults) .updateCount(updateCount) .mergedOn(mergedOn) .build(); @@ -326,6 +333,8 @@ abstract ImmutableListMultimap<ObjectId, HumanComment> publishedComments(); + abstract ImmutableList<SubmitRequirementResult> submitRequirementsResult(); + abstract int updateCount(); @Nullable @@ -404,6 +413,7 @@ .submitRecords(ImmutableList.of()) .changeMessages(ImmutableList.of()) .publishedComments(ImmutableListMultimap.of()) + .submitRequirementsResult(ImmutableList.of()) .updateCount(0); } @@ -445,6 +455,9 @@ abstract Builder publishedComments(ListMultimap<ObjectId, HumanComment> publishedComments); + abstract Builder submitRequirementsResult( + List<SubmitRequirementResult> submitRequirementsResult); + abstract Builder updateCount(int updateCount); abstract Builder mergedOn(Timestamp mergedOn); @@ -465,6 +478,11 @@ private static final Converter<String, ReviewerStateInternal> REVIEWER_STATE_CONVERTER = Enums.stringConverter(ReviewerStateInternal.class); + private static final FieldDescriptor SR_APPLICABILITY_EXPR_RESULT_FIELD = + SubmitRequirementResultProto.getDescriptor().findFieldByNumber(2); + private static final FieldDescriptor SR_OVERRIDE_EXPR_RESULT_FIELD = + SubmitRequirementResultProto.getDescriptor().findFieldByNumber(4); + @Override public byte[] serialize(ChangeNotesState object) { checkArgument(object.metaId() != null, "meta ID is required in: %s", object); @@ -519,6 +537,9 @@ .changeMessages() .forEach(m -> b.addChangeMessage(ChangeMessageProtoConverter.INSTANCE.toProto(m))); object.publishedComments().values().forEach(c -> b.addPublishedComment(GSON.toJson(c))); + object + .submitRequirementsResult() + .forEach(sr -> b.addSubmitRequirementResult(toSubmitRequirementResultProto(sr))); b.setUpdateCount(object.updateCount()); if (object.mergedOn() != null) { b.setMergedOnMillis(object.mergedOn().getTime()); @@ -613,6 +634,53 @@ return builder.build(); } + private static SubmitRequirementResultProto toSubmitRequirementResultProto( + SubmitRequirementResult r) { + SubmitRequirementResultProto.Builder builder = SubmitRequirementResultProto.newBuilder(); + builder + .setSubmitRequirement(SubmitRequirementSerializer.serialize(r.submitRequirement())) + .setCommit(ObjectIdConverter.create().toByteString(r.patchSetCommitId())); + if (r.applicabilityExpressionResult().isPresent()) { + builder.setApplicabilityExpressionResult( + SubmitRequirementExpressionResultSerializer.serialize( + r.applicabilityExpressionResult().get())); + } + builder.setSubmittabilityExpressionResult( + SubmitRequirementExpressionResultSerializer.serialize( + r.submittabilityExpressionResult())); + if (r.overrideExpressionResult().isPresent()) { + builder.setOverrideExpressionResult( + SubmitRequirementExpressionResultSerializer.serialize( + r.overrideExpressionResult().get())); + } + return builder.build(); + } + + private static SubmitRequirementResult toSubmitRequirementResult( + SubmitRequirementResultProto proto) { + SubmitRequirementResult.Builder builder = + SubmitRequirementResult.builder() + .patchSetCommitId(ObjectIdConverter.create().fromByteString(proto.getCommit())) + .submitRequirement( + SubmitRequirementSerializer.deserialize(proto.getSubmitRequirement())); + if (proto.hasField(SR_APPLICABILITY_EXPR_RESULT_FIELD)) { + builder.applicabilityExpressionResult( + Optional.of( + SubmitRequirementExpressionResultSerializer.deserialize( + proto.getApplicabilityExpressionResult()))); + } + builder.submittabilityExpressionResult( + SubmitRequirementExpressionResultSerializer.deserialize( + proto.getSubmittabilityExpressionResult())); + if (proto.hasField(SR_OVERRIDE_EXPR_RESULT_FIELD)) { + builder.overrideExpressionResult( + Optional.of( + SubmitRequirementExpressionResultSerializer.deserialize( + proto.getOverrideExpressionResult()))); + } + return builder.build(); + } + @Override public ChangeNotesState deserialize(byte[] in) { ChangeNotesStateProto proto = Protos.parseUnchecked(ChangeNotesStateProto.parser(), in); @@ -658,6 +726,10 @@ proto.getPublishedCommentList().stream() .map(r -> GSON.fromJson(r, HumanComment.class)) .collect(toImmutableListMultimap(HumanComment::getCommitId, c -> c))) + .submitRequirementsResult( + proto.getSubmitRequirementResultList().stream() + .map(sr -> toSubmitRequirementResult(sr)) + .collect(toImmutableList())) .updateCount(proto.getUpdateCount()) .mergedOn(proto.getHasMergedOn() ? new Timestamp(proto.getMergedOnMillis()) : null); return b.build();
diff --git a/java/com/google/gerrit/server/notedb/ChangeRevisionNote.java b/java/com/google/gerrit/server/notedb/ChangeRevisionNote.java index bf2cf07..44475db 100644 --- a/java/com/google/gerrit/server/notedb/ChangeRevisionNote.java +++ b/java/com/google/gerrit/server/notedb/ChangeRevisionNote.java
@@ -16,7 +16,9 @@ import static java.nio.charset.StandardCharsets.UTF_8; +import com.google.common.collect.ImmutableList; import com.google.gerrit.entities.HumanComment; +import com.google.gerrit.entities.SubmitRequirementResult; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; @@ -34,6 +36,8 @@ private final HumanComment.Status status; private String pushCert; + private ImmutableList<SubmitRequirementResult> submitRequirementsResult; + ChangeRevisionNote( ChangeNoteJson noteJson, ObjectReader reader, ObjectId noteId, HumanComment.Status status) { super(reader, noteId); @@ -41,6 +45,11 @@ this.status = status; } + public ImmutableList<SubmitRequirementResult> getSubmitRequirementsResult() { + checkParsed(); + return submitRequirementsResult; + } + public String getPushCert() { checkParsed(); return pushCert; @@ -52,20 +61,24 @@ MutableInteger p = new MutableInteger(); p.value = offset; - HumanCommentsRevisionNoteData data = parseJson(noteJson, raw, p.value); + ChangeRevisionNoteData data = parseJson(noteJson, raw, p.value); if (status == HumanComment.Status.PUBLISHED) { pushCert = data.pushCert; } else { pushCert = null; } + this.submitRequirementsResult = + data.submitRequirementResults == null + ? ImmutableList.of() + : ImmutableList.copyOf(data.submitRequirementResults); return data.comments; } - private HumanCommentsRevisionNoteData parseJson(ChangeNoteJson noteUtil, byte[] raw, int offset) + private ChangeRevisionNoteData parseJson(ChangeNoteJson noteUtil, byte[] raw, int offset) throws IOException { try (InputStream is = new ByteArrayInputStream(raw, offset, raw.length - offset); Reader r = new InputStreamReader(is, UTF_8)) { - return noteUtil.getGson().fromJson(r, HumanCommentsRevisionNoteData.class); + return noteUtil.getGson().fromJson(r, ChangeRevisionNoteData.class); } } }
diff --git a/java/com/google/gerrit/server/notedb/HumanCommentsRevisionNoteData.java b/java/com/google/gerrit/server/notedb/ChangeRevisionNoteData.java similarity index 78% rename from java/com/google/gerrit/server/notedb/HumanCommentsRevisionNoteData.java rename to java/com/google/gerrit/server/notedb/ChangeRevisionNoteData.java index e570412..8e33023 100644 --- a/java/com/google/gerrit/server/notedb/HumanCommentsRevisionNoteData.java +++ b/java/com/google/gerrit/server/notedb/ChangeRevisionNoteData.java
@@ -15,14 +15,17 @@ package com.google.gerrit.server.notedb; import com.google.gerrit.entities.HumanComment; +import com.google.gerrit.entities.SubmitRequirementResult; import java.util.List; /** * Holds the raw data of a RevisionNote. * - * <p>It is intended for deserialization from JSON only. It is used for human comments only. + * <p>It is intended for deserialization from JSON only. It is used for human comments. Submit + * requirements are also stored but only for closed changes. */ -class HumanCommentsRevisionNoteData { +class ChangeRevisionNoteData { String pushCert; List<HumanComment> comments; + List<SubmitRequirementResult> submitRequirementResults; }
diff --git a/java/com/google/gerrit/server/notedb/ChangeUpdate.java b/java/com/google/gerrit/server/notedb/ChangeUpdate.java index 8b9d2856..971e0a8 100644 --- a/java/com/google/gerrit/server/notedb/ChangeUpdate.java +++ b/java/com/google/gerrit/server/notedb/ChangeUpdate.java
@@ -65,6 +65,7 @@ import com.google.gerrit.entities.RobotComment; import com.google.gerrit.entities.SubmissionId; import com.google.gerrit.entities.SubmitRecord; +import com.google.gerrit.entities.SubmitRequirementResult; import com.google.gerrit.exceptions.StorageException; import com.google.gerrit.extensions.client.ReviewerState; import com.google.gerrit.server.CurrentUser; @@ -78,6 +79,7 @@ import com.google.inject.assistedinject.AssistedInject; import java.io.IOException; import java.util.ArrayList; +import java.util.Collection; import java.util.Comparator; import java.util.Date; import java.util.HashMap; @@ -129,6 +131,7 @@ private final Map<Account.Id, ReviewerStateInternal> reviewers = new LinkedHashMap<>(); private final Map<Address, ReviewerStateInternal> reviewersByEmail = new LinkedHashMap<>(); private final List<HumanComment> comments = new ArrayList<>(); + private final List<SubmitRequirementResult> submitRequirementResults = new ArrayList<>(); private String commitSubject; private String subject; @@ -302,6 +305,10 @@ this.psDescription = psDescription; } + public void putSubmitRequirementResults(Collection<SubmitRequirementResult> rs) { + submitRequirementResults.addAll(rs); + } + public void putComment(HumanComment.Status status, HumanComment c) { verifyComment(c); createDraftUpdateIfNull(); @@ -488,7 +495,7 @@ /** @return the tree id for the updated tree */ private ObjectId storeRevisionNotes(RevWalk rw, ObjectInserter inserter, ObjectId curr) throws ConfigInvalidException, IOException { - if (comments.isEmpty() && pushCert == null) { + if (submitRequirementResults.isEmpty() && comments.isEmpty() && pushCert == null) { return null; } RevisionNoteMap<ChangeRevisionNote> rnm = getRevisionNoteMap(rw, curr); @@ -498,6 +505,9 @@ c.tag = tag; cache.get(c.getCommitId()).putComment(c); } + for (SubmitRequirementResult sr : submitRequirementResults) { + cache.get(sr.patchSetCommitId()).putSubmitRequirementResult(sr); + } if (pushCert != null) { checkState(commit != null); cache.get(ObjectId.fromString(commit)).setPushCertificate(pushCert);
diff --git a/java/com/google/gerrit/server/notedb/RevisionNoteBuilder.java b/java/com/google/gerrit/server/notedb/RevisionNoteBuilder.java index 3c1d359..7998476 100644 --- a/java/com/google/gerrit/server/notedb/RevisionNoteBuilder.java +++ b/java/com/google/gerrit/server/notedb/RevisionNoteBuilder.java
@@ -22,16 +22,20 @@ import com.google.common.collect.Maps; import com.google.common.collect.MultimapBuilder; import com.google.gerrit.entities.Comment; +import com.google.gerrit.entities.SubmitRequirementResult; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStream; import java.io.OutputStreamWriter; +import java.util.ArrayList; import java.util.Collections; +import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.stream.Collectors; import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.ObjectId; @@ -60,11 +64,16 @@ } } + /** Submit requirements are sorted w.r.t. their names before storing in NoteDb. */ + private final Comparator<SubmitRequirementResult> SUBMIT_REQUIREMENT_RESULT_COMPARATOR = + Comparator.comparing(sr -> sr.submitRequirement().name()); + final byte[] baseRaw; private final List<? extends Comment> baseComments; final Map<Comment.Key, Comment> put; private final Set<Comment.Key> delete; + private List<SubmitRequirementResult> submitRequirementResults; private String pushCert; private RevisionNoteBuilder(RevisionNote<? extends Comment> base) { @@ -81,6 +90,7 @@ put = new HashMap<>(); pushCert = null; } + submitRequirementResults = new ArrayList<>(); delete = new HashSet<>(); } @@ -99,6 +109,10 @@ put.put(comment.key, comment); } + void putSubmitRequirementResult(SubmitRequirementResult result) { + submitRequirementResults.add(result); + } + void deleteComment(Comment.Key key) { checkArgument(!put.containsKey(key), "cannot both delete and put %s", key); delete.add(key); @@ -126,13 +140,19 @@ private void buildNoteJson(ChangeNoteJson noteUtil, OutputStream out) throws IOException { ListMultimap<Integer, Comment> comments = buildCommentMap(); - if (comments.isEmpty() && pushCert == null) { + if (submitRequirementResults.isEmpty() && comments.isEmpty() && pushCert == null) { return; } RevisionNoteData data = new RevisionNoteData(); data.comments = COMMENT_ORDER.sortedCopy(comments.values()); data.pushCert = pushCert; + if (!submitRequirementResults.isEmpty()) { + data.submitRequirementResults = + submitRequirementResults.stream() + .sorted(SUBMIT_REQUIREMENT_RESULT_COMPARATOR) + .collect(Collectors.toList()); + } try (OutputStreamWriter osw = new OutputStreamWriter(out, UTF_8)) { noteUtil.getGson().toJson(data, osw);
diff --git a/java/com/google/gerrit/server/notedb/RevisionNoteData.java b/java/com/google/gerrit/server/notedb/RevisionNoteData.java index da15b34..c8770f1 100644 --- a/java/com/google/gerrit/server/notedb/RevisionNoteData.java +++ b/java/com/google/gerrit/server/notedb/RevisionNoteData.java
@@ -15,15 +15,17 @@ package com.google.gerrit.server.notedb; import com.google.gerrit.entities.Comment; +import com.google.gerrit.entities.SubmitRequirementResult; import java.util.List; /** * Holds the raw data of a RevisionNote. * * <p>It is intended for serialization to JSON only. It is used for human comments and robot - * comments. + * comments, as well as for storing submit requirements. */ class RevisionNoteData { String pushCert; List<Comment> comments; + List<SubmitRequirementResult> submitRequirementResults; }
diff --git a/java/com/google/gerrit/server/notedb/StoreSubmitRequirementsOp.java b/java/com/google/gerrit/server/notedb/StoreSubmitRequirementsOp.java new file mode 100644 index 0000000..47948d7 --- /dev/null +++ b/java/com/google/gerrit/server/notedb/StoreSubmitRequirementsOp.java
@@ -0,0 +1,38 @@ +// Copyright (C) 2021 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.notedb; + +import com.google.gerrit.entities.Change; +import com.google.gerrit.server.query.change.ChangeData; +import com.google.gerrit.server.update.BatchUpdateOp; +import com.google.gerrit.server.update.ChangeContext; + +/** A {@link BatchUpdateOp} that stores the evaluated submit requirements of a change in NoteDb. */ +public class StoreSubmitRequirementsOp implements BatchUpdateOp { + private final ChangeData.Factory changeDataFactory; + + public StoreSubmitRequirementsOp(ChangeData.Factory changeDataFactory) { + this.changeDataFactory = changeDataFactory; + } + + @Override + public boolean updateChange(ChangeContext ctx) throws Exception { + Change change = ctx.getChange(); + ChangeData changeData = changeDataFactory.create(change); + ChangeUpdate update = ctx.getUpdate(change.currentPatchSetId()); + update.putSubmitRequirementResults(changeData.submitRequirements().values()); + return !changeData.submitRequirements().isEmpty(); + } +}
diff --git a/java/com/google/gerrit/server/project/SubmitRequirementsEvaluator.java b/java/com/google/gerrit/server/project/SubmitRequirementsEvaluator.java index 1d154dd..65d9d9e 100644 --- a/java/com/google/gerrit/server/project/SubmitRequirementsEvaluator.java +++ b/java/com/google/gerrit/server/project/SubmitRequirementsEvaluator.java
@@ -66,6 +66,8 @@ : Optional.empty(); return SubmitRequirementResult.builder() + .submitRequirement(sr) + .patchSetCommitId(cd.currentPatchSet().commitId()) .submittabilityExpressionResult(blockingResult) .applicabilityExpressionResult(applicabilityResult) .overrideExpressionResult(overrideResult) @@ -79,9 +81,9 @@ Predicate<ChangeData> predicate = changeQueryBuilderProvider.get().parse(expression.expressionString()); PredicateResult predicateResult = evaluatePredicateTree(predicate, changeData); - return SubmitRequirementExpressionResult.create(predicateResult); + return SubmitRequirementExpressionResult.create(expression, predicateResult); } catch (QueryParseException e) { - return SubmitRequirementExpressionResult.error(e.getMessage()); + return SubmitRequirementExpressionResult.error(expression, e.getMessage()); } }
diff --git a/java/com/google/gerrit/server/submit/MergeOp.java b/java/com/google/gerrit/server/submit/MergeOp.java index 2b4fb3b..363cdca 100644 --- a/java/com/google/gerrit/server/submit/MergeOp.java +++ b/java/com/google/gerrit/server/submit/MergeOp.java
@@ -67,6 +67,7 @@ import com.google.gerrit.server.logging.RequestId; import com.google.gerrit.server.logging.TraceContext; import com.google.gerrit.server.notedb.ChangeNotes; +import com.google.gerrit.server.notedb.StoreSubmitRequirementsOp; import com.google.gerrit.server.permissions.PermissionBackendException; import com.google.gerrit.server.project.NoSuchProjectException; import com.google.gerrit.server.project.SubmitRuleOptions; @@ -96,6 +97,8 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collectors; import org.eclipse.jgit.errors.ConfigInvalidException; import org.eclipse.jgit.errors.IncorrectObjectTypeException; import org.eclipse.jgit.lib.Constants; @@ -654,6 +657,16 @@ toSubmit, updateOrderCalculator, submoduleCommits, subscriptionGraph, dryrun); this.allProjects = updateOrderCalculator.getProjectsInOrder(); List<BatchUpdate> batchUpdates = orm.batchUpdates(allProjects); + // Group batch updates by project + Map<Project.NameKey, BatchUpdate> batchUpdatesByProject = + batchUpdates.stream().collect(Collectors.toMap(b -> b.getProject(), Function.identity())); + for (Map.Entry<Change.Id, ChangeData> entry : cs.changesById().entrySet()) { + Project.NameKey project = entry.getValue().project(); + Change.Id changeId = entry.getKey(); + batchUpdatesByProject + .get(project) + .addOp(changeId, new StoreSubmitRequirementsOp(changeDataFactory)); + } try { submissionExecutor.setAdditionalBatchUpdateListeners( ImmutableList.of(new SubmitStrategyListener(submitInput, strategies, commitStatus)));
diff --git a/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java b/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java index 976e828..c08aa7f 100644 --- a/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java +++ b/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java
@@ -69,6 +69,7 @@ import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; +import com.google.common.collect.MoreCollectors; import com.google.common.truth.ThrowableSubject; import com.google.gerrit.acceptance.AbstractDaemonTest; import com.google.gerrit.acceptance.ChangeIndexedCounter; @@ -104,6 +105,8 @@ import com.google.gerrit.entities.RefNames; import com.google.gerrit.entities.SubmitRequirement; import com.google.gerrit.entities.SubmitRequirementExpression; +import com.google.gerrit.entities.SubmitRequirementExpressionResult; +import com.google.gerrit.entities.SubmitRequirementResult; import com.google.gerrit.exceptions.StorageException; import com.google.gerrit.extensions.annotations.Exports; import com.google.gerrit.extensions.api.accounts.DeleteDraftCommentsInput; @@ -172,6 +175,7 @@ import com.google.gerrit.server.index.change.ChangeIndex; import com.google.gerrit.server.index.change.ChangeIndexCollection; import com.google.gerrit.server.index.change.IndexedChangeQuery; +import com.google.gerrit.server.notedb.ChangeNotes; import com.google.gerrit.server.patch.DiffSummary; import com.google.gerrit.server.patch.DiffSummaryKey; import com.google.gerrit.server.patch.IntraLineDiff; @@ -4287,6 +4291,40 @@ } @Test + public void submitRequirement_storedForClosedChanges() throws Exception { + configSubmitRequirement( + project, + SubmitRequirement.builder() + .setName("code-review") + .setSubmittabilityExpression(SubmitRequirementExpression.create("label:code-review=+2")) + .setAllowOverrideInChildProjects(false) + .build()); + + PushOneCommit.Result r = createChange("Add a file", "foo", "content"); + String changeId = r.getChangeId(); + + voteLabel(changeId, "code-review", 2); + + ChangeInfo change = gApi.changes().id(changeId).get(); + assertThat(change.submitRequirements).hasSize(1); + assertSubmitRequirementStatus(change.submitRequirements, "code-review", Status.SATISFIED); + + RevisionApi revision = gApi.changes().id(r.getChangeId()).current(); + revision.review(ReviewInput.approve()); + revision.submit(); + + ChangeNotes notes = notesFactory.create(project, r.getChange().getId()); + + SubmitRequirementResult result = + notes.getSubmitRequirementsResult().stream().collect(MoreCollectors.onlyElement()); + assertThat(result.status()).isEqualTo(SubmitRequirementResult.Status.SATISFIED); + assertThat(result.submittabilityExpressionResult().status()) + .isEqualTo(SubmitRequirementExpressionResult.Status.PASS); + assertThat(result.submittabilityExpressionResult().expression().expressionString()) + .isEqualTo("label:code-review=+2"); + } + + @Test public void fourByteEmoji() throws Exception { // U+1F601 GRINNING FACE WITH SMILING EYES String smile = new String(Character.toChars(0x1f601));
diff --git a/javatests/com/google/gerrit/acceptance/server/project/SubmitRequirementsEvaluatorIT.java b/javatests/com/google/gerrit/acceptance/server/project/SubmitRequirementsEvaluatorIT.java index 23047a4..e848cef 100644 --- a/javatests/com/google/gerrit/acceptance/server/project/SubmitRequirementsEvaluatorIT.java +++ b/javatests/com/google/gerrit/acceptance/server/project/SubmitRequirementsEvaluatorIT.java
@@ -29,7 +29,6 @@ import com.google.gerrit.entities.SubmitRequirement; import com.google.gerrit.entities.SubmitRequirementExpression; import com.google.gerrit.entities.SubmitRequirementExpressionResult; -import com.google.gerrit.entities.SubmitRequirementExpressionResult.PredicateResult; import com.google.gerrit.entities.SubmitRequirementExpressionResult.Status; import com.google.gerrit.entities.SubmitRequirementResult; import com.google.gerrit.extensions.api.changes.ReviewInput; @@ -102,25 +101,14 @@ assertThat(result.status()).isEqualTo(Status.PASS); - assertThat(result.getPassingAtoms()) - .containsExactly( - PredicateResult.builder() - .predicateString(String.format("project:%s", project.get())) - .status(true) - .build(), - PredicateResult.builder() - .predicateString("message:\"Fix a bug\"") - .status(true) - .build()); + assertThat(result.passingAtoms()) + .containsExactly(String.format("project:%s", project.get()), "message:\"Fix a bug\""); - assertThat(result.getFailingAtoms()) + assertThat(result.failingAtoms()) .containsExactly( - PredicateResult.builder() - // TODO(ghareeb): querying "branch:" creates a RefPredicate. Fix names so that they - // match - .predicateString(String.format("ref:refs/heads/foo")) - .status(false) - .build()); + // TODO(ghareeb): querying "branch:" creates a RefPredicate. Fix names so that they + // match + String.format("ref:refs/heads/foo")); } @Test
diff --git a/javatests/com/google/gerrit/server/notedb/ChangeNotesStateTest.java b/javatests/com/google/gerrit/server/notedb/ChangeNotesStateTest.java index feff89c..ecdb03d 100644 --- a/javatests/com/google/gerrit/server/notedb/ChangeNotesStateTest.java +++ b/javatests/com/google/gerrit/server/notedb/ChangeNotesStateTest.java
@@ -36,6 +36,10 @@ import com.google.gerrit.entities.PatchSet; import com.google.gerrit.entities.PatchSetApproval; import com.google.gerrit.entities.SubmitRecord; +import com.google.gerrit.entities.SubmitRequirement; +import com.google.gerrit.entities.SubmitRequirementExpression; +import com.google.gerrit.entities.SubmitRequirementExpressionResult; +import com.google.gerrit.entities.SubmitRequirementResult; import com.google.gerrit.entities.converter.ChangeMessageProtoConverter; import com.google.gerrit.entities.converter.PatchSetApprovalProtoConverter; import com.google.gerrit.entities.converter.PatchSetProtoConverter; @@ -52,6 +56,9 @@ import com.google.gerrit.server.cache.proto.Cache.ChangeNotesStateProto.ReviewerByEmailSetEntryProto; import com.google.gerrit.server.cache.proto.Cache.ChangeNotesStateProto.ReviewerSetEntryProto; import com.google.gerrit.server.cache.proto.Cache.ChangeNotesStateProto.ReviewerStatusUpdateProto; +import com.google.gerrit.server.cache.proto.Cache.SubmitRequirementExpressionResultProto; +import com.google.gerrit.server.cache.proto.Cache.SubmitRequirementProto; +import com.google.gerrit.server.cache.proto.Cache.SubmitRequirementResultProto; import com.google.gerrit.server.cache.serialize.ObjectIdConverter; import com.google.gerrit.server.notedb.ChangeNotesState.ChangeColumns; import com.google.gerrit.server.notedb.ChangeNotesState.Serializer; @@ -671,6 +678,69 @@ } @Test + public void serializeSubmitRequirementsResult() throws Exception { + assertRoundTrip( + newBuilder() + .submitRequirementsResult( + ImmutableList.of( + SubmitRequirementResult.builder() + .patchSetCommitId( + ObjectId.fromString("26e50c7d315a33a13e5cc00902781fa876bc36cd")) + .submitRequirement( + SubmitRequirement.builder() + .setName("Code-Review") + .setApplicabilityExpression( + SubmitRequirementExpression.of("project:foo")) + .setSubmittabilityExpression( + SubmitRequirementExpression.create("label:code-review=+2")) + .setAllowOverrideInChildProjects(false) + .build()) + .applicabilityExpressionResult( + Optional.of( + SubmitRequirementExpressionResult.create( + SubmitRequirementExpression.create("project:foo"), + SubmitRequirementExpressionResult.Status.PASS, + ImmutableList.of("project:foo"), + ImmutableList.of()))) + .submittabilityExpressionResult( + SubmitRequirementExpressionResult.create( + SubmitRequirementExpression.create("label:code-review=+2"), + SubmitRequirementExpressionResult.Status.FAIL, + ImmutableList.of(), + ImmutableList.of("label:code-review=+2"))) + .build())) + .build(), + newProtoBuilder() + .addSubmitRequirementResult( + SubmitRequirementResultProto.newBuilder() + .setCommit( + ObjectIdConverter.create() + .toByteString( + ObjectId.fromString("26e50c7d315a33a13e5cc00902781fa876bc36cd"))) + .setSubmitRequirement( + SubmitRequirementProto.newBuilder() + .setName("Code-Review") + .setApplicabilityExpression("project:foo") + .setSubmittabilityExpression("label:code-review=+2") + .setAllowOverrideInChildProjects(false) + .build()) + .setApplicabilityExpressionResult( + SubmitRequirementExpressionResultProto.newBuilder() + .setExpression("project:foo") + .setStatus("PASS") + .addPassingAtoms("project:foo") + .build()) + .setSubmittabilityExpressionResult( + SubmitRequirementExpressionResultProto.newBuilder() + .setExpression("label:code-review=+2") + .setStatus("FAIL") + .addFailingAtoms("label:code-review=+2") + .build()) + .build()) + .build()); + } + + @Test public void serializeAssigneeUpdates() throws Exception { assertRoundTrip( newBuilder() @@ -842,6 +912,9 @@ .put( "publishedComments", new TypeLiteral<ImmutableListMultimap<ObjectId, HumanComment>>() {}.getType()) + .put( + "submitRequirementsResult", + new TypeLiteral<ImmutableList<SubmitRequirementResult>>() {}.getType()) .put("updateCount", int.class) .put("mergedOn", Timestamp.class) .build());
diff --git a/proto/cache.proto b/proto/cache.proto index 781538a..aa04555 100644 --- a/proto/cache.proto +++ b/proto/cache.proto
@@ -78,7 +78,7 @@ // Instead, we just take the tedious yet simple approach of having a "has_foo" // field for each nullable field "foo", indicating whether or not foo is null. // -// Next ID: 27 +// Next ID: 28 message ChangeNotesStateProto { // Effectively required, even though the corresponding ChangeNotesState field // is optional, since the field is only absent when NoteDb is disabled, in @@ -226,6 +226,8 @@ // Epoch millis. int64 merged_on_millis = 25; bool has_merged_on = 26; + + repeated SubmitRequirementResultProto submit_requirement_result = 27; } // Serialized form of com.google.gerrit.server.query.change.ConflictKey @@ -480,6 +482,28 @@ bool allow_override_in_child_projects = 6; } +// Serialized form of com.google.gerrit.entities.SubmitRequirementResult. +// Next ID: 6 +message SubmitRequirementResultProto { + SubmitRequirementProto submit_requirement = 1; + SubmitRequirementExpressionResultProto applicability_expression_result = 2; + SubmitRequirementExpressionResultProto submittability_expression_result = 3; + SubmitRequirementExpressionResultProto override_expression_result = 4; + + // Patchset commit ID at which the submit requirements are evaluated. + bytes commit = 5; +} + +// Serialized form of com.google.gerrit.entities.SubmitRequirementExpressionResult. +// Next ID: 6 +message SubmitRequirementExpressionResultProto { + string expression = 1; + string status = 2; // enum as string + string error_message = 3; + repeated string passing_atoms = 4; + repeated string failing_atoms = 5; +} + // Serialized form of com.google.gerrit.server.project.ConfiguredMimeTypes. // Next ID: 4 message ConfiguredMimeTypeProto {