Limit the number of comments per change (to 5000 by default)
Also refactor the robot comment size check to be in line with the size check for human comments. Change-Id: If24adc10f5f16cd8894bf246078f0e6c992cdafa
This commit is contained in:
@@ -1195,6 +1195,15 @@ performance improvements by reducing the number of refs in the repo.
|
|||||||
+
|
+
|
||||||
Default is true.
|
Default is true.
|
||||||
|
|
||||||
|
[[change.commentSizeLimit]]change.commentSizeLimit::
|
||||||
|
+
|
||||||
|
Maximum allowed size in characters of a regular (non-robot) comment. Comments
|
||||||
|
which exceed this size will be rejected. Size computation is approximate and may
|
||||||
|
be off by roughly 1%. Common unit suffixes of 'k', 'm', or 'g' are supported.
|
||||||
|
The value must be positive.
|
||||||
|
+
|
||||||
|
The default limit is 16kB.
|
||||||
|
|
||||||
[[change.disablePrivateChanges]]change.disablePrivateChanges::
|
[[change.disablePrivateChanges]]change.disablePrivateChanges::
|
||||||
+
|
+
|
||||||
If set to true, users are not allowed to create private changes.
|
If set to true, users are not allowed to create private changes.
|
||||||
@@ -1212,6 +1221,13 @@ in change tables and user dashboards.
|
|||||||
+
|
+
|
||||||
By default 500.
|
By default 500.
|
||||||
|
|
||||||
|
[[change.maxComments]]change.maxComments::
|
||||||
|
+
|
||||||
|
Maximum number of comments (regular plus robot) allowed per change. Additional
|
||||||
|
comments are rejected.
|
||||||
|
+
|
||||||
|
By default 5,000.
|
||||||
|
|
||||||
[[change.maxUpdates]]change.maxUpdates::
|
[[change.maxUpdates]]change.maxUpdates::
|
||||||
+
|
+
|
||||||
Maximum number of updates to a change. Counts only updates to the main NoteDb
|
Maximum number of updates to a change. Counts only updates to the main NoteDb
|
||||||
@@ -1266,11 +1282,10 @@ and score (Shortcut: a)".
|
|||||||
|
|
||||||
[[change.robotCommentSizeLimit]]change.robotCommentSizeLimit::
|
[[change.robotCommentSizeLimit]]change.robotCommentSizeLimit::
|
||||||
+
|
+
|
||||||
Maximum allowed size of a robot comment that will be accepted. Robot comments
|
Maximum allowed size in characters of a robot comment. Robot comments which
|
||||||
which exceed the indicated size will be rejected on addition. The specified
|
exceed this size will be rejected on addition. Size computation is approximate
|
||||||
value is interpreted as the maximum size in bytes of the JSON representation of
|
and may be off by roughly 1%. Common unit suffixes of 'k', 'm', or 'g' are
|
||||||
the robot comment. Common unit suffixes of 'k', 'm', or 'g' are supported.
|
supported. Zero or negative values allow robot comments of unlimited size.
|
||||||
Zero or negative values allow robot comments of unlimited size.
|
|
||||||
+
|
+
|
||||||
The default limit is 1024kB.
|
The default limit is 1024kB.
|
||||||
|
|
||||||
|
@@ -29,6 +29,8 @@ import org.eclipse.jgit.lib.ObjectId;
|
|||||||
*
|
*
|
||||||
* <p>Changing fields in this class changes the storage format of inline comments in NoteDb and may
|
* <p>Changing fields in this class changes the storage format of inline comments in NoteDb and may
|
||||||
* require a corresponding data migration (adding new optional fields is generally okay).
|
* require a corresponding data migration (adding new optional fields is generally okay).
|
||||||
|
*
|
||||||
|
* <p>Consider updating {@link #getApproximateSize()} when adding/changing fields.
|
||||||
*/
|
*/
|
||||||
public class Comment {
|
public class Comment {
|
||||||
public enum Status {
|
public enum Status {
|
||||||
@@ -56,7 +58,7 @@ public class Comment {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class Key {
|
public static final class Key {
|
||||||
public String uuid;
|
public String uuid;
|
||||||
public String filename;
|
public String filename;
|
||||||
public int patchSetId;
|
public int patchSetId;
|
||||||
@@ -97,7 +99,7 @@ public class Comment {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class Identity {
|
public static final class Identity {
|
||||||
int id;
|
int id;
|
||||||
|
|
||||||
public Identity(Account.Id id) {
|
public Identity(Account.Id id) {
|
||||||
@@ -147,7 +149,7 @@ public class Comment {
|
|||||||
* </ul>
|
* </ul>
|
||||||
* </ul>
|
* </ul>
|
||||||
*/
|
*/
|
||||||
public static class Range implements Comparable<Range> {
|
public static final class Range implements Comparable<Range> {
|
||||||
private static final Comparator<Range> RANGE_COMPARATOR =
|
private static final Comparator<Range> RANGE_COMPARATOR =
|
||||||
Comparator.<Range>comparingInt(range -> range.startLine)
|
Comparator.<Range>comparingInt(range -> range.startLine)
|
||||||
.thenComparingInt(range -> range.startChar)
|
.thenComparingInt(range -> range.startChar)
|
||||||
@@ -220,10 +222,12 @@ public class Comment {
|
|||||||
public Range range;
|
public Range range;
|
||||||
public String tag;
|
public String tag;
|
||||||
|
|
||||||
// Hex commit SHA1 of the commit of the patchset to which this comment applies. Other classes call
|
/**
|
||||||
// this "commitId", but this class uses the old ReviewDb term "revId", and this field name is
|
* Hex commit SHA1 of the commit of the patchset to which this comment applies. Other classes call
|
||||||
// serialized into JSON in NoteDb, so it can't easily be changed. Callers do not access this field
|
* this "commitId", but this class uses the old ReviewDb term "revId", and this field name is
|
||||||
// directly, and instead use the public getter/setter that wraps an ObjectId.
|
* serialized into JSON in NoteDb, so it can't easily be changed. Callers do not access this field
|
||||||
|
* directly, and instead use the public getter/setter that wraps an ObjectId.
|
||||||
|
*/
|
||||||
private String revId;
|
private String revId;
|
||||||
|
|
||||||
public String serverId;
|
public String serverId;
|
||||||
@@ -293,6 +297,23 @@ public class Comment {
|
|||||||
return realAuthor != null ? realAuthor : author;
|
return realAuthor != null ? realAuthor : author;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the comment's approximate size. This is used to enforce size limits and should
|
||||||
|
* therefore include all unbounded fields (e.g. String-s).
|
||||||
|
*/
|
||||||
|
public int getApproximateSize() {
|
||||||
|
return nullableLength(message, parentUuid, tag, revId, serverId)
|
||||||
|
+ (key != null ? nullableLength(key.filename, key.uuid) : 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int nullableLength(String... strings) {
|
||||||
|
int length = 0;
|
||||||
|
for (String s : strings) {
|
||||||
|
length += s == null ? 0 : s.length();
|
||||||
|
}
|
||||||
|
return length;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object o) {
|
public boolean equals(Object o) {
|
||||||
if (!(o instanceof Comment)) {
|
if (!(o instanceof Comment)) {
|
||||||
|
@@ -14,10 +14,10 @@
|
|||||||
|
|
||||||
package com.google.gerrit.entities;
|
package com.google.gerrit.entities;
|
||||||
|
|
||||||
public class FixReplacement {
|
public final class FixReplacement {
|
||||||
public String path;
|
public final String path;
|
||||||
public Comment.Range range;
|
public final Comment.Range range;
|
||||||
public String replacement;
|
public final String replacement;
|
||||||
|
|
||||||
public FixReplacement(String path, Comment.Range range, String replacement) {
|
public FixReplacement(String path, Comment.Range range, String replacement) {
|
||||||
this.path = path;
|
this.path = path;
|
||||||
@@ -38,4 +38,9 @@ public class FixReplacement {
|
|||||||
+ '\''
|
+ '\''
|
||||||
+ '}';
|
+ '}';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Returns this instance's approximate size in bytes for the purpose of applying size limits. */
|
||||||
|
int getApproximateSize() {
|
||||||
|
return path.length() + replacement.length();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -16,10 +16,10 @@ package com.google.gerrit.entities;
|
|||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class FixSuggestion {
|
public final class FixSuggestion {
|
||||||
public String fixId;
|
public final String fixId;
|
||||||
public String description;
|
public final String description;
|
||||||
public List<FixReplacement> replacements;
|
public final List<FixReplacement> replacements;
|
||||||
|
|
||||||
public FixSuggestion(String fixId, String description, List<FixReplacement> replacements) {
|
public FixSuggestion(String fixId, String description, List<FixReplacement> replacements) {
|
||||||
this.fixId = fixId;
|
this.fixId = fixId;
|
||||||
@@ -40,4 +40,11 @@ public class FixSuggestion {
|
|||||||
+ replacements
|
+ replacements
|
||||||
+ '}';
|
+ '}';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Returns this instance's approximate size in bytes for the purpose of applying size limits. */
|
||||||
|
int getApproximateSize() {
|
||||||
|
return fixId.length()
|
||||||
|
+ description.length()
|
||||||
|
+ replacements.stream().map(FixReplacement::getApproximateSize).reduce(0, Integer::sum);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -19,7 +19,7 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
public class RobotComment extends Comment {
|
public final class RobotComment extends Comment {
|
||||||
public String robotId;
|
public String robotId;
|
||||||
public String robotRunId;
|
public String robotRunId;
|
||||||
public String url;
|
public String url;
|
||||||
@@ -40,6 +40,22 @@ public class RobotComment extends Comment {
|
|||||||
this.robotRunId = robotRunId;
|
this.robotRunId = robotRunId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getApproximateSize() {
|
||||||
|
int approximateSize = super.getApproximateSize() + nullableLength(robotId, robotRunId, url);
|
||||||
|
approximateSize +=
|
||||||
|
properties != null
|
||||||
|
? properties.entrySet().stream()
|
||||||
|
.map(entry -> nullableLength(entry.getKey(), entry.getValue()))
|
||||||
|
.reduce(0, Integer::sum)
|
||||||
|
: 0;
|
||||||
|
approximateSize +=
|
||||||
|
fixSuggestions != null
|
||||||
|
? fixSuggestions.stream().map(FixSuggestion::getApproximateSize).reduce(0, Integer::sum)
|
||||||
|
: 0;
|
||||||
|
return approximateSize;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return toStringHelper()
|
return toStringHelper()
|
||||||
|
@@ -17,13 +17,21 @@ package com.google.gerrit.extensions.validators;
|
|||||||
import com.google.auto.value.AutoValue;
|
import com.google.auto.value.AutoValue;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Holds a comment's text and {@link CommentType} in order to pass it to a validation plugin.
|
* Holds a comment's text and some metadata in order to pass it to a validation plugin.
|
||||||
*
|
*
|
||||||
* @see CommentValidator
|
* @see CommentValidator
|
||||||
*/
|
*/
|
||||||
@AutoValue
|
@AutoValue
|
||||||
public abstract class CommentForValidation {
|
public abstract class CommentForValidation {
|
||||||
|
|
||||||
|
/** The creator of the comment. */
|
||||||
|
public enum CommentSource {
|
||||||
|
/** A regular user comment. */
|
||||||
|
HUMAN,
|
||||||
|
/** A robot comment. */
|
||||||
|
ROBOT
|
||||||
|
}
|
||||||
|
|
||||||
/** The type of comment. */
|
/** The type of comment. */
|
||||||
public enum CommentType {
|
public enum CommentType {
|
||||||
/** A regular (inline) comment. */
|
/** A regular (inline) comment. */
|
||||||
@@ -34,14 +42,27 @@ public abstract class CommentForValidation {
|
|||||||
CHANGE_MESSAGE
|
CHANGE_MESSAGE
|
||||||
}
|
}
|
||||||
|
|
||||||
public static CommentForValidation create(CommentType type, String text) {
|
public static CommentForValidation create(
|
||||||
return new AutoValue_CommentForValidation(type, text);
|
CommentSource source, CommentType type, String text, long size) {
|
||||||
|
return new AutoValue_CommentForValidation(source, type, text, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public abstract CommentSource getSource();
|
||||||
|
|
||||||
public abstract CommentType getType();
|
public abstract CommentType getType();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the comment text. Note that especially for robot comments the total size may be
|
||||||
|
* significantly larger and should be determined by using {@link #getApproximateSize()}.
|
||||||
|
*/
|
||||||
public abstract String getText();
|
public abstract String getText();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns this instance's approximate size in bytes for the purpose of applying size limits. For
|
||||||
|
* robot comments this may be significantly larger than the size of the comment text.
|
||||||
|
*/
|
||||||
|
public abstract long getApproximateSize();
|
||||||
|
|
||||||
public CommentValidationFailure failValidation(String message) {
|
public CommentValidationFailure failValidation(String message) {
|
||||||
return CommentValidationFailure.create(this, message);
|
return CommentValidationFailure.create(this, message);
|
||||||
}
|
}
|
||||||
|
@@ -34,16 +34,7 @@ public abstract class CommentValidationContext {
|
|||||||
/** Returns the project the comment is being added to. */
|
/** Returns the project the comment is being added to. */
|
||||||
public abstract String getProject();
|
public abstract String getProject();
|
||||||
|
|
||||||
public static Builder builder() {
|
public static CommentValidationContext create(int changeId, String project) {
|
||||||
return new AutoValue_CommentValidationContext.Builder();
|
return new AutoValue_CommentValidationContext(changeId, project);
|
||||||
}
|
|
||||||
|
|
||||||
@AutoValue.Builder
|
|
||||||
public abstract static class Builder {
|
|
||||||
public abstract Builder changeId(int value);
|
|
||||||
|
|
||||||
public abstract Builder project(String value);
|
|
||||||
|
|
||||||
public abstract CommentValidationContext build();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -25,7 +25,10 @@ import com.google.gerrit.extensions.annotations.ExtensionPoint;
|
|||||||
public interface CommentValidator {
|
public interface CommentValidator {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate the specified comments.
|
* Validate the specified comments. This method will be called with all new comments that need to
|
||||||
|
* be validated. This allows validators to statelessly count the new comments. Note that the
|
||||||
|
* method may be called separately for texts that are not comments, but similar in nature and also
|
||||||
|
* subject to size limits, such as a change message.
|
||||||
*
|
*
|
||||||
* @return An empty list if all comments are valid, or else a list of validation failures.
|
* @return An empty list if all comments are valid, or else a list of validation failures.
|
||||||
*/
|
*/
|
||||||
|
@@ -128,7 +128,8 @@ import com.google.gerrit.server.git.ReceivePackInitializer;
|
|||||||
import com.google.gerrit.server.git.TagCache;
|
import com.google.gerrit.server.git.TagCache;
|
||||||
import com.google.gerrit.server.git.TransferConfig;
|
import com.google.gerrit.server.git.TransferConfig;
|
||||||
import com.google.gerrit.server.git.receive.ReceiveCommitsModule;
|
import com.google.gerrit.server.git.receive.ReceiveCommitsModule;
|
||||||
import com.google.gerrit.server.git.validators.CommentLimitsValidator;
|
import com.google.gerrit.server.git.validators.CommentCountValidator;
|
||||||
|
import com.google.gerrit.server.git.validators.CommentSizeValidator;
|
||||||
import com.google.gerrit.server.git.validators.CommitValidationListener;
|
import com.google.gerrit.server.git.validators.CommitValidationListener;
|
||||||
import com.google.gerrit.server.git.validators.MergeValidationListener;
|
import com.google.gerrit.server.git.validators.MergeValidationListener;
|
||||||
import com.google.gerrit.server.git.validators.MergeValidators;
|
import com.google.gerrit.server.git.validators.MergeValidators;
|
||||||
@@ -410,8 +411,11 @@ public class GerritGlobalModule extends FactoryModule {
|
|||||||
DynamicSet.setOf(binder(), UploadValidationListener.class);
|
DynamicSet.setOf(binder(), UploadValidationListener.class);
|
||||||
|
|
||||||
bind(CommentValidator.class)
|
bind(CommentValidator.class)
|
||||||
.annotatedWith(Exports.named(CommentLimitsValidator.class.getSimpleName()))
|
.annotatedWith(Exports.named(CommentCountValidator.class.getSimpleName()))
|
||||||
.to(CommentLimitsValidator.class);
|
.to(CommentCountValidator.class);
|
||||||
|
bind(CommentValidator.class)
|
||||||
|
.annotatedWith(Exports.named(CommentSizeValidator.class.getSimpleName()))
|
||||||
|
.to(CommentSizeValidator.class);
|
||||||
|
|
||||||
DynamicMap.mapOf(binder(), DynamicOptions.DynamicBean.class);
|
DynamicMap.mapOf(binder(), DynamicOptions.DynamicBean.class);
|
||||||
DynamicMap.mapOf(binder(), ChangeQueryBuilder.ChangeOperatorFactory.class);
|
DynamicMap.mapOf(binder(), ChangeQueryBuilder.ChangeOperatorFactory.class);
|
||||||
|
@@ -92,6 +92,7 @@ import com.google.gerrit.extensions.restapi.ResourceConflictException;
|
|||||||
import com.google.gerrit.extensions.restapi.RestApiException;
|
import com.google.gerrit.extensions.restapi.RestApiException;
|
||||||
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
|
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
|
||||||
import com.google.gerrit.extensions.validators.CommentForValidation;
|
import com.google.gerrit.extensions.validators.CommentForValidation;
|
||||||
|
import com.google.gerrit.extensions.validators.CommentForValidation.CommentSource;
|
||||||
import com.google.gerrit.extensions.validators.CommentForValidation.CommentType;
|
import com.google.gerrit.extensions.validators.CommentForValidation.CommentType;
|
||||||
import com.google.gerrit.extensions.validators.CommentValidationContext;
|
import com.google.gerrit.extensions.validators.CommentValidationContext;
|
||||||
import com.google.gerrit.extensions.validators.CommentValidationFailure;
|
import com.google.gerrit.extensions.validators.CommentValidationFailure;
|
||||||
@@ -126,6 +127,8 @@ import com.google.gerrit.server.git.MultiProgressMonitor.Task;
|
|||||||
import com.google.gerrit.server.git.ReceivePackInitializer;
|
import com.google.gerrit.server.git.ReceivePackInitializer;
|
||||||
import com.google.gerrit.server.git.TagCache;
|
import com.google.gerrit.server.git.TagCache;
|
||||||
import com.google.gerrit.server.git.ValidationError;
|
import com.google.gerrit.server.git.ValidationError;
|
||||||
|
import com.google.gerrit.server.git.validators.CommentCountValidator;
|
||||||
|
import com.google.gerrit.server.git.validators.CommentSizeValidator;
|
||||||
import com.google.gerrit.server.git.validators.CommitValidationMessage;
|
import com.google.gerrit.server.git.validators.CommitValidationMessage;
|
||||||
import com.google.gerrit.server.git.validators.RefOperationValidationException;
|
import com.google.gerrit.server.git.validators.RefOperationValidationException;
|
||||||
import com.google.gerrit.server.git.validators.RefOperationValidators;
|
import com.google.gerrit.server.git.validators.RefOperationValidators;
|
||||||
@@ -1441,11 +1444,18 @@ class ReceiveCommits {
|
|||||||
final ReceiveCommand cmd;
|
final ReceiveCommand cmd;
|
||||||
final LabelTypes labelTypes;
|
final LabelTypes labelTypes;
|
||||||
/**
|
/**
|
||||||
* Result of running {@link CommentValidator}-s on drafts that are published with the commit
|
* Draft comments are published with the commit iff {@code --publish-comments} is set. All
|
||||||
* (which happens iff {@code --publish-comments} is set). Remains {@code true} if none are
|
* drafts are withheld (overriding the option) if at least one of the following conditions are
|
||||||
* installed.
|
* met:
|
||||||
|
*
|
||||||
|
* <ul>
|
||||||
|
* <li>Installed {@link CommentValidator} plugins reject one or more draft comments.
|
||||||
|
* <li>One or more comments exceed the maximum comment size (see {@link
|
||||||
|
* CommentSizeValidator}).
|
||||||
|
* <li>The maximum number of comments would be exceeded (see {@link CommentCountValidator}).
|
||||||
|
* </ul>
|
||||||
*/
|
*/
|
||||||
private boolean commentsValid = true;
|
private boolean withholdComments = false;
|
||||||
|
|
||||||
BranchNameKey dest;
|
BranchNameKey dest;
|
||||||
PermissionBackend.ForRef perm;
|
PermissionBackend.ForRef perm;
|
||||||
@@ -1642,18 +1652,19 @@ class ReceiveCommits {
|
|||||||
.collect(toImmutableSet());
|
.collect(toImmutableSet());
|
||||||
}
|
}
|
||||||
|
|
||||||
void setCommentsValid(boolean commentsValid) {
|
void setWithholdComments(boolean withholdComments) {
|
||||||
this.commentsValid = commentsValid;
|
this.withholdComments = withholdComments;
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean shouldPublishComments() {
|
boolean shouldPublishComments() {
|
||||||
if (!commentsValid) {
|
if (withholdComments) {
|
||||||
// Validation messages of type WARNING have already been added, now withhold the comments.
|
// Validation messages of type WARNING have already been added, now withhold the comments.
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (publishComments) {
|
if (publishComments) {
|
||||||
return true;
|
return true;
|
||||||
} else if (noPublishComments) {
|
}
|
||||||
|
if (noPublishComments) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return defaultPublishComments;
|
return defaultPublishComments;
|
||||||
@@ -2010,19 +2021,18 @@ class ReceiveCommits {
|
|||||||
.map(
|
.map(
|
||||||
comment ->
|
comment ->
|
||||||
CommentForValidation.create(
|
CommentForValidation.create(
|
||||||
|
CommentSource.HUMAN,
|
||||||
comment.lineNbr > 0
|
comment.lineNbr > 0
|
||||||
? CommentType.INLINE_COMMENT
|
? CommentType.INLINE_COMMENT
|
||||||
: CommentType.FILE_COMMENT,
|
: CommentType.FILE_COMMENT,
|
||||||
comment.message))
|
comment.message,
|
||||||
|
comment.message.length()))
|
||||||
.collect(toImmutableList());
|
.collect(toImmutableList());
|
||||||
CommentValidationContext ctx =
|
CommentValidationContext ctx =
|
||||||
CommentValidationContext.builder()
|
CommentValidationContext.create(change.getChangeId(), change.getProject().get());
|
||||||
.changeId(change.getChangeId())
|
|
||||||
.project(change.getProject().get())
|
|
||||||
.build();
|
|
||||||
ImmutableList<CommentValidationFailure> commentValidationFailures =
|
ImmutableList<CommentValidationFailure> commentValidationFailures =
|
||||||
PublishCommentUtil.findInvalidComments(ctx, commentValidators, draftsForValidation);
|
PublishCommentUtil.findInvalidComments(ctx, commentValidators, draftsForValidation);
|
||||||
magicBranch.setCommentsValid(commentValidationFailures.isEmpty());
|
magicBranch.setWithholdComments(!commentValidationFailures.isEmpty());
|
||||||
commentValidationFailures.forEach(
|
commentValidationFailures.forEach(
|
||||||
failure ->
|
failure ->
|
||||||
addMessage(
|
addMessage(
|
||||||
|
@@ -0,0 +1,61 @@
|
|||||||
|
// Copyright (C) 2020 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.validators;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import com.google.common.collect.Iterables;
|
||||||
|
import com.google.gerrit.entities.Change;
|
||||||
|
import com.google.gerrit.entities.Project;
|
||||||
|
import com.google.gerrit.extensions.validators.CommentForValidation;
|
||||||
|
import com.google.gerrit.extensions.validators.CommentValidationContext;
|
||||||
|
import com.google.gerrit.extensions.validators.CommentValidationFailure;
|
||||||
|
import com.google.gerrit.extensions.validators.CommentValidator;
|
||||||
|
import com.google.gerrit.server.config.GerritServerConfig;
|
||||||
|
import com.google.gerrit.server.notedb.ChangeNotes;
|
||||||
|
import com.google.inject.Inject;
|
||||||
|
import org.eclipse.jgit.lib.Config;
|
||||||
|
|
||||||
|
/** Limits number of comments to prevent space/time complexity issues. */
|
||||||
|
public class CommentCountValidator implements CommentValidator {
|
||||||
|
private final int maxComments;
|
||||||
|
private final ChangeNotes.Factory notesFactory;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
CommentCountValidator(@GerritServerConfig Config serverConfig, ChangeNotes.Factory notesFactory) {
|
||||||
|
this.notesFactory = notesFactory;
|
||||||
|
maxComments = serverConfig.getInt("change", "maxComments", 5_000);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ImmutableList<CommentValidationFailure> validateComments(
|
||||||
|
CommentValidationContext ctx, ImmutableList<CommentForValidation> comments) {
|
||||||
|
ImmutableList.Builder<CommentValidationFailure> failures = ImmutableList.builder();
|
||||||
|
ChangeNotes notes =
|
||||||
|
notesFactory.createChecked(Project.nameKey(ctx.getProject()), Change.id(ctx.getChangeId()));
|
||||||
|
int numExistingComments = notes.getComments().size() + notes.getRobotComments().size();
|
||||||
|
if (!comments.isEmpty() && numExistingComments + comments.size() > maxComments) {
|
||||||
|
// This warning really applies to the set of all comments, but we need to pick one to attach
|
||||||
|
// the message to.
|
||||||
|
CommentForValidation commentForFailureMessage = Iterables.getLast(comments);
|
||||||
|
|
||||||
|
failures.add(
|
||||||
|
commentForFailureMessage.failValidation(
|
||||||
|
String.format(
|
||||||
|
"Exceeding maximum number of comments: %d (existing) + %d (new) > %d",
|
||||||
|
numExistingComments, comments.size(), maxComments)));
|
||||||
|
}
|
||||||
|
return failures.build();
|
||||||
|
}
|
||||||
|
}
|
@@ -1,47 +0,0 @@
|
|||||||
// Copyright (C) 2020 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.validators;
|
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableList;
|
|
||||||
import com.google.gerrit.extensions.validators.CommentForValidation;
|
|
||||||
import com.google.gerrit.extensions.validators.CommentValidationContext;
|
|
||||||
import com.google.gerrit.extensions.validators.CommentValidationFailure;
|
|
||||||
import com.google.gerrit.extensions.validators.CommentValidator;
|
|
||||||
import com.google.gerrit.server.config.GerritServerConfig;
|
|
||||||
import com.google.inject.Inject;
|
|
||||||
import org.eclipse.jgit.lib.Config;
|
|
||||||
|
|
||||||
/** Limits the size of comments to prevent large comments from causing issues. */
|
|
||||||
public class CommentLimitsValidator implements CommentValidator {
|
|
||||||
private final int maxCommentLength;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
CommentLimitsValidator(@GerritServerConfig Config serverConfig) {
|
|
||||||
maxCommentLength = serverConfig.getInt("change", null, "maxCommentLength", 16 << 10);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ImmutableList<CommentValidationFailure> validateComments(
|
|
||||||
CommentValidationContext ctx, ImmutableList<CommentForValidation> comments) {
|
|
||||||
return comments.stream()
|
|
||||||
.filter(c -> c.getText().length() > maxCommentLength)
|
|
||||||
.map(
|
|
||||||
c ->
|
|
||||||
c.failValidation(
|
|
||||||
String.format(
|
|
||||||
"Comment too large (%d > %d)", c.getText().length(), maxCommentLength)))
|
|
||||||
.collect(ImmutableList.toImmutableList());
|
|
||||||
}
|
|
||||||
}
|
|
@@ -0,0 +1,71 @@
|
|||||||
|
// Copyright (C) 2020 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.validators;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import com.google.gerrit.extensions.validators.CommentForValidation;
|
||||||
|
import com.google.gerrit.extensions.validators.CommentValidationContext;
|
||||||
|
import com.google.gerrit.extensions.validators.CommentValidationFailure;
|
||||||
|
import com.google.gerrit.extensions.validators.CommentValidator;
|
||||||
|
import com.google.gerrit.server.config.GerritServerConfig;
|
||||||
|
import com.google.inject.Inject;
|
||||||
|
import org.eclipse.jgit.lib.Config;
|
||||||
|
|
||||||
|
/** Limits the size of comments to prevent space/time complexity issues. */
|
||||||
|
public class CommentSizeValidator implements CommentValidator {
|
||||||
|
private final int commentSizeLimit;
|
||||||
|
private final int robotCommentSizeLimit;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
CommentSizeValidator(@GerritServerConfig Config serverConfig) {
|
||||||
|
commentSizeLimit = serverConfig.getInt("change", "commentSizeLimit", 16 << 10);
|
||||||
|
robotCommentSizeLimit = serverConfig.getInt("change", "robotCommentSizeLimit", 1 << 20);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ImmutableList<CommentValidationFailure> validateComments(
|
||||||
|
CommentValidationContext ctx, ImmutableList<CommentForValidation> comments) {
|
||||||
|
return comments.stream()
|
||||||
|
.filter(this::exceedsSizeLimit)
|
||||||
|
.map(c -> c.failValidation(buildErrorMessage(c)))
|
||||||
|
.collect(ImmutableList.toImmutableList());
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean exceedsSizeLimit(CommentForValidation comment) {
|
||||||
|
switch (comment.getSource()) {
|
||||||
|
case HUMAN:
|
||||||
|
return comment.getApproximateSize() > commentSizeLimit;
|
||||||
|
case ROBOT:
|
||||||
|
return robotCommentSizeLimit > 0 && comment.getApproximateSize() > robotCommentSizeLimit;
|
||||||
|
}
|
||||||
|
throw new RuntimeException(
|
||||||
|
"Unknown comment source (should not have compiled): " + comment.getSource());
|
||||||
|
}
|
||||||
|
|
||||||
|
private String buildErrorMessage(CommentForValidation comment) {
|
||||||
|
switch (comment.getSource()) {
|
||||||
|
case HUMAN:
|
||||||
|
return String.format(
|
||||||
|
"Comment size exceeds limit (%d > %d)", comment.getApproximateSize(), commentSizeLimit);
|
||||||
|
|
||||||
|
case ROBOT:
|
||||||
|
return String.format(
|
||||||
|
"Size %d (bytes) of robot comment is greater than limit %d (bytes)",
|
||||||
|
comment.getApproximateSize(), robotCommentSizeLimit);
|
||||||
|
}
|
||||||
|
throw new RuntimeException(
|
||||||
|
"Unknown comment source (should not have compiled): " + comment.getSource());
|
||||||
|
}
|
||||||
|
}
|
@@ -19,6 +19,7 @@ import static java.util.stream.Collectors.toList;
|
|||||||
import com.google.common.base.Strings;
|
import com.google.common.base.Strings;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.google.common.collect.ImmutableMap;
|
import com.google.common.collect.ImmutableMap;
|
||||||
|
import com.google.common.collect.Iterables;
|
||||||
import com.google.common.flogger.FluentLogger;
|
import com.google.common.flogger.FluentLogger;
|
||||||
import com.google.gerrit.entities.Account;
|
import com.google.gerrit.entities.Account;
|
||||||
import com.google.gerrit.entities.Change;
|
import com.google.gerrit.entities.Change;
|
||||||
@@ -243,7 +244,7 @@ public class MailProcessor {
|
|||||||
sendRejectionEmail(message, InboundEmailRejectionSender.Error.INTERNAL_EXCEPTION);
|
sendRejectionEmail(message, InboundEmailRejectionSender.Error.INTERNAL_EXCEPTION);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ChangeData cd = changeDataList.get(0);
|
ChangeData cd = Iterables.getOnlyElement(changeDataList);
|
||||||
if (existingMessageIds(cd).contains(message.id())) {
|
if (existingMessageIds(cd).contains(message.id())) {
|
||||||
logger.atInfo().log("Message %s was already processed. Will delete message.", message.id());
|
logger.atInfo().log("Message %s was already processed. Will delete message.", message.id());
|
||||||
return;
|
return;
|
||||||
@@ -285,14 +286,14 @@ public class MailProcessor {
|
|||||||
.map(
|
.map(
|
||||||
comment ->
|
comment ->
|
||||||
CommentForValidation.create(
|
CommentForValidation.create(
|
||||||
|
CommentForValidation.CommentSource.HUMAN,
|
||||||
MAIL_COMMENT_TYPE_TO_VALIDATION_TYPE.get(comment.getType()),
|
MAIL_COMMENT_TYPE_TO_VALIDATION_TYPE.get(comment.getType()),
|
||||||
comment.getMessage()))
|
comment.getMessage(),
|
||||||
|
comment.getMessage().length()))
|
||||||
.collect(ImmutableList.toImmutableList());
|
.collect(ImmutableList.toImmutableList());
|
||||||
CommentValidationContext commentValidationCtx =
|
CommentValidationContext commentValidationCtx =
|
||||||
CommentValidationContext.builder()
|
CommentValidationContext.create(
|
||||||
.changeId(cd.change().getChangeId())
|
cd.change().getChangeId(), cd.change().getProject().get());
|
||||||
.project(cd.change().getProject().get())
|
|
||||||
.build();
|
|
||||||
ImmutableList<CommentValidationFailure> commentValidationFailures =
|
ImmutableList<CommentValidationFailure> commentValidationFailures =
|
||||||
PublishCommentUtil.findInvalidComments(
|
PublishCommentUtil.findInvalidComments(
|
||||||
commentValidationCtx, commentValidators, parsedCommentsForValidation);
|
commentValidationCtx, commentValidators, parsedCommentsForValidation);
|
||||||
|
@@ -23,12 +23,13 @@ import com.google.gerrit.exceptions.StorageException;
|
|||||||
* <ul>
|
* <ul>
|
||||||
* <li>The number of NoteDb updates per change.
|
* <li>The number of NoteDb updates per change.
|
||||||
* <li>The number of patch sets per change.
|
* <li>The number of patch sets per change.
|
||||||
|
* <li>The number of files per change.
|
||||||
* </ul>
|
* </ul>
|
||||||
*/
|
*/
|
||||||
public class LimitExceededException extends StorageException {
|
public class LimitExceededException extends StorageException {
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
LimitExceededException(String message) {
|
public LimitExceededException(String message) {
|
||||||
super(message);
|
super(message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -80,7 +80,6 @@ import com.google.gerrit.extensions.validators.CommentForValidation;
|
|||||||
import com.google.gerrit.extensions.validators.CommentValidationContext;
|
import com.google.gerrit.extensions.validators.CommentValidationContext;
|
||||||
import com.google.gerrit.extensions.validators.CommentValidationFailure;
|
import com.google.gerrit.extensions.validators.CommentValidationFailure;
|
||||||
import com.google.gerrit.extensions.validators.CommentValidator;
|
import com.google.gerrit.extensions.validators.CommentValidator;
|
||||||
import com.google.gerrit.json.OutputFormat;
|
|
||||||
import com.google.gerrit.mail.Address;
|
import com.google.gerrit.mail.Address;
|
||||||
import com.google.gerrit.server.ApprovalsUtil;
|
import com.google.gerrit.server.ApprovalsUtil;
|
||||||
import com.google.gerrit.server.ChangeMessagesUtil;
|
import com.google.gerrit.server.ChangeMessagesUtil;
|
||||||
@@ -125,11 +124,9 @@ import com.google.gerrit.server.update.Context;
|
|||||||
import com.google.gerrit.server.update.UpdateException;
|
import com.google.gerrit.server.update.UpdateException;
|
||||||
import com.google.gerrit.server.util.LabelVote;
|
import com.google.gerrit.server.util.LabelVote;
|
||||||
import com.google.gerrit.server.util.time.TimeUtil;
|
import com.google.gerrit.server.util.time.TimeUtil;
|
||||||
import com.google.gson.Gson;
|
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
import com.google.inject.Singleton;
|
import com.google.inject.Singleton;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.sql.Timestamp;
|
import java.sql.Timestamp;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
@@ -141,7 +138,6 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.OptionalInt;
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
@@ -159,9 +155,6 @@ public class PostReview implements RestModifyView<RevisionResource, ReviewInput>
|
|||||||
|
|
||||||
public static final String START_REVIEW_MESSAGE = "This change is ready for review.";
|
public static final String START_REVIEW_MESSAGE = "This change is ready for review.";
|
||||||
|
|
||||||
private static final Gson GSON = OutputFormat.JSON_COMPACT.newGson();
|
|
||||||
private static final int DEFAULT_ROBOT_COMMENT_SIZE_LIMIT_IN_BYTES = 1024 * 1024;
|
|
||||||
|
|
||||||
private final BatchUpdate.Factory updateFactory;
|
private final BatchUpdate.Factory updateFactory;
|
||||||
private final ChangeResource.Factory changeResourceFactory;
|
private final ChangeResource.Factory changeResourceFactory;
|
||||||
private final ChangeData.Factory changeDataFactory;
|
private final ChangeData.Factory changeDataFactory;
|
||||||
@@ -177,7 +170,6 @@ public class PostReview implements RestModifyView<RevisionResource, ReviewInput>
|
|||||||
private final ReviewerAdder reviewerAdder;
|
private final ReviewerAdder reviewerAdder;
|
||||||
private final AddReviewersEmail addReviewersEmail;
|
private final AddReviewersEmail addReviewersEmail;
|
||||||
private final NotifyResolver notifyResolver;
|
private final NotifyResolver notifyResolver;
|
||||||
private final Config gerritConfig;
|
|
||||||
private final WorkInProgressOp.Factory workInProgressOpFactory;
|
private final WorkInProgressOp.Factory workInProgressOpFactory;
|
||||||
private final ProjectCache projectCache;
|
private final ProjectCache projectCache;
|
||||||
private final PermissionBackend permissionBackend;
|
private final PermissionBackend permissionBackend;
|
||||||
@@ -221,7 +213,6 @@ public class PostReview implements RestModifyView<RevisionResource, ReviewInput>
|
|||||||
this.reviewerAdder = reviewerAdder;
|
this.reviewerAdder = reviewerAdder;
|
||||||
this.addReviewersEmail = addReviewersEmail;
|
this.addReviewersEmail = addReviewersEmail;
|
||||||
this.notifyResolver = notifyResolver;
|
this.notifyResolver = notifyResolver;
|
||||||
this.gerritConfig = gerritConfig;
|
|
||||||
this.workInProgressOpFactory = workInProgressOpFactory;
|
this.workInProgressOpFactory = workInProgressOpFactory;
|
||||||
this.projectCache = projectCache;
|
this.projectCache = projectCache;
|
||||||
this.permissionBackend = permissionBackend;
|
this.permissionBackend = permissionBackend;
|
||||||
@@ -671,41 +662,15 @@ public class PostReview implements RestModifyView<RevisionResource, ReviewInput>
|
|||||||
for (Map.Entry<String, List<RobotCommentInput>> e : in.entrySet()) {
|
for (Map.Entry<String, List<RobotCommentInput>> e : in.entrySet()) {
|
||||||
String commentPath = e.getKey();
|
String commentPath = e.getKey();
|
||||||
for (RobotCommentInput c : e.getValue()) {
|
for (RobotCommentInput c : e.getValue()) {
|
||||||
ensureSizeOfJsonInputIsWithinBounds(c);
|
|
||||||
ensureRobotIdIsSet(c.robotId, commentPath);
|
ensureRobotIdIsSet(c.robotId, commentPath);
|
||||||
ensureRobotRunIdIsSet(c.robotRunId, commentPath);
|
ensureRobotRunIdIsSet(c.robotRunId, commentPath);
|
||||||
ensureFixSuggestionsAreAddable(c.fixSuggestions, commentPath);
|
ensureFixSuggestionsAreAddable(c.fixSuggestions, commentPath);
|
||||||
|
// Size is validated later, in CommentLimitsValidator.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
checkComments(revision, in);
|
checkComments(revision, in);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ensureSizeOfJsonInputIsWithinBounds(RobotCommentInput robotCommentInput)
|
|
||||||
throws BadRequestException {
|
|
||||||
OptionalInt robotCommentSizeLimit = getRobotCommentSizeLimit();
|
|
||||||
if (robotCommentSizeLimit.isPresent()) {
|
|
||||||
int sizeLimit = robotCommentSizeLimit.getAsInt();
|
|
||||||
byte[] robotCommentBytes = GSON.toJson(robotCommentInput).getBytes(StandardCharsets.UTF_8);
|
|
||||||
int robotCommentSize = robotCommentBytes.length;
|
|
||||||
if (robotCommentSize > sizeLimit) {
|
|
||||||
throw new BadRequestException(
|
|
||||||
String.format(
|
|
||||||
"Size %d (bytes) of robot comment is greater than limit %d (bytes)",
|
|
||||||
robotCommentSize, sizeLimit));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private OptionalInt getRobotCommentSizeLimit() {
|
|
||||||
int robotCommentSizeLimit =
|
|
||||||
gerritConfig.getInt(
|
|
||||||
"change", "robotCommentSizeLimit", DEFAULT_ROBOT_COMMENT_SIZE_LIMIT_IN_BYTES);
|
|
||||||
if (robotCommentSizeLimit <= 0) {
|
|
||||||
return OptionalInt.empty();
|
|
||||||
}
|
|
||||||
return OptionalInt.of(robotCommentSizeLimit);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void ensureRobotIdIsSet(String robotId, String commentPath)
|
private static void ensureRobotIdIsSet(String robotId, String commentPath)
|
||||||
throws BadRequestException {
|
throws BadRequestException {
|
||||||
if (robotId == null) {
|
if (robotId == null) {
|
||||||
@@ -909,8 +874,10 @@ public class PostReview implements RestModifyView<RevisionResource, ReviewInput>
|
|||||||
user = ctx.getIdentifiedUser();
|
user = ctx.getIdentifiedUser();
|
||||||
notes = ctx.getNotes();
|
notes = ctx.getNotes();
|
||||||
ps = psUtil.get(ctx.getNotes(), psId);
|
ps = psUtil.get(ctx.getNotes(), psId);
|
||||||
boolean dirty = insertComments(ctx);
|
List<RobotComment> newRobotComments =
|
||||||
dirty |= insertRobotComments(ctx);
|
in.robotComments == null ? ImmutableList.of() : getNewRobotComments(ctx);
|
||||||
|
boolean dirty = insertComments(ctx, newRobotComments);
|
||||||
|
dirty |= insertRobotComments(ctx, newRobotComments);
|
||||||
dirty |= updateLabels(projectState, ctx);
|
dirty |= updateLabels(projectState, ctx);
|
||||||
dirty |= insertMessage(ctx);
|
dirty |= insertMessage(ctx);
|
||||||
return dirty;
|
return dirty;
|
||||||
@@ -937,7 +904,7 @@ public class PostReview implements RestModifyView<RevisionResource, ReviewInput>
|
|||||||
ctx.getWhen());
|
ctx.getWhen());
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean insertComments(ChangeContext ctx)
|
private boolean insertComments(ChangeContext ctx, List<RobotComment> newRobotComments)
|
||||||
throws UnprocessableEntityException, PatchListNotAvailableException,
|
throws UnprocessableEntityException, PatchListNotAvailableException,
|
||||||
CommentsRejectedException {
|
CommentsRejectedException {
|
||||||
Map<String, List<CommentInput>> inputComments = in.comments;
|
Map<String, List<CommentInput>> inputComments = in.comments;
|
||||||
@@ -1001,21 +968,21 @@ public class PostReview implements RestModifyView<RevisionResource, ReviewInput>
|
|||||||
}
|
}
|
||||||
|
|
||||||
CommentValidationContext commentValidationCtx =
|
CommentValidationContext commentValidationCtx =
|
||||||
CommentValidationContext.builder()
|
CommentValidationContext.create(
|
||||||
.changeId(ctx.getChange().getChangeId())
|
ctx.getChange().getChangeId(), ctx.getChange().getProject().get());
|
||||||
.project(ctx.getChange().getProject().get())
|
|
||||||
.build();
|
|
||||||
switch (in.drafts) {
|
switch (in.drafts) {
|
||||||
case PUBLISH:
|
case PUBLISH:
|
||||||
case PUBLISH_ALL_REVISIONS:
|
case PUBLISH_ALL_REVISIONS:
|
||||||
validateComments(
|
validateComments(
|
||||||
commentValidationCtx, Streams.concat(drafts.values().stream(), toPublish.stream()));
|
commentValidationCtx,
|
||||||
|
Streams.concat(
|
||||||
|
drafts.values().stream(), toPublish.stream(), newRobotComments.stream()));
|
||||||
publishCommentUtil.publish(ctx, ctx.getUpdate(psId), drafts.values(), in.tag);
|
publishCommentUtil.publish(ctx, ctx.getUpdate(psId), drafts.values(), in.tag);
|
||||||
comments.addAll(drafts.values());
|
comments.addAll(drafts.values());
|
||||||
break;
|
break;
|
||||||
case KEEP:
|
case KEEP:
|
||||||
default:
|
validateComments(
|
||||||
validateComments(commentValidationCtx, toPublish.stream());
|
commentValidationCtx, Stream.concat(toPublish.stream(), newRobotComments.stream()));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
ChangeUpdate changeUpdate = ctx.getUpdate(psId);
|
ChangeUpdate changeUpdate = ctx.getUpdate(psId);
|
||||||
@@ -1031,10 +998,14 @@ public class PostReview implements RestModifyView<RevisionResource, ReviewInput>
|
|||||||
.map(
|
.map(
|
||||||
comment ->
|
comment ->
|
||||||
CommentForValidation.create(
|
CommentForValidation.create(
|
||||||
|
comment instanceof RobotComment
|
||||||
|
? CommentForValidation.CommentSource.ROBOT
|
||||||
|
: CommentForValidation.CommentSource.HUMAN,
|
||||||
comment.lineNbr > 0
|
comment.lineNbr > 0
|
||||||
? CommentForValidation.CommentType.INLINE_COMMENT
|
? CommentForValidation.CommentType.INLINE_COMMENT
|
||||||
: CommentForValidation.CommentType.FILE_COMMENT,
|
: CommentForValidation.CommentType.FILE_COMMENT,
|
||||||
comment.message))
|
comment.message,
|
||||||
|
comment.getApproximateSize()))
|
||||||
.collect(toImmutableList());
|
.collect(toImmutableList());
|
||||||
ImmutableList<CommentValidationFailure> draftValidationFailures =
|
ImmutableList<CommentValidationFailure> draftValidationFailures =
|
||||||
PublishCommentUtil.findInvalidComments(ctx, commentValidators, draftsForValidation);
|
PublishCommentUtil.findInvalidComments(ctx, commentValidators, draftsForValidation);
|
||||||
@@ -1043,12 +1014,10 @@ public class PostReview implements RestModifyView<RevisionResource, ReviewInput>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean insertRobotComments(ChangeContext ctx) throws PatchListNotAvailableException {
|
private boolean insertRobotComments(ChangeContext ctx, List<RobotComment> newRobotComments) {
|
||||||
if (in.robotComments == null) {
|
if (in.robotComments == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
List<RobotComment> newRobotComments = getNewRobotComments(ctx);
|
|
||||||
commentsUtil.putRobotComments(ctx.getUpdate(psId), newRobotComments);
|
commentsUtil.putRobotComments(ctx.getUpdate(psId), newRobotComments);
|
||||||
comments.addAll(newRobotComments);
|
comments.addAll(newRobotComments);
|
||||||
return !newRobotComments.isEmpty();
|
return !newRobotComments.isEmpty();
|
||||||
@@ -1424,17 +1393,18 @@ public class PostReview implements RestModifyView<RevisionResource, ReviewInput>
|
|||||||
}
|
}
|
||||||
if (!msg.isEmpty()) {
|
if (!msg.isEmpty()) {
|
||||||
CommentValidationContext commentValidationCtx =
|
CommentValidationContext commentValidationCtx =
|
||||||
CommentValidationContext.builder()
|
CommentValidationContext.create(
|
||||||
.changeId(ctx.getChange().getChangeId())
|
ctx.getChange().getChangeId(), ctx.getChange().getProject().get());
|
||||||
.project(ctx.getChange().getProject().get())
|
|
||||||
.build();
|
|
||||||
ImmutableList<CommentValidationFailure> messageValidationFailure =
|
ImmutableList<CommentValidationFailure> messageValidationFailure =
|
||||||
PublishCommentUtil.findInvalidComments(
|
PublishCommentUtil.findInvalidComments(
|
||||||
commentValidationCtx,
|
commentValidationCtx,
|
||||||
commentValidators,
|
commentValidators,
|
||||||
ImmutableList.of(
|
ImmutableList.of(
|
||||||
CommentForValidation.create(
|
CommentForValidation.create(
|
||||||
CommentForValidation.CommentType.CHANGE_MESSAGE, msg)));
|
CommentForValidation.CommentSource.HUMAN,
|
||||||
|
CommentForValidation.CommentType.CHANGE_MESSAGE,
|
||||||
|
msg,
|
||||||
|
msg.length())));
|
||||||
if (!messageValidationFailure.isEmpty()) {
|
if (!messageValidationFailure.isEmpty()) {
|
||||||
throw new CommentsRejectedException(messageValidationFailure);
|
throw new CommentsRejectedException(messageValidationFailure);
|
||||||
}
|
}
|
||||||
|
@@ -16,7 +16,10 @@ package com.google.gerrit.acceptance.api.change;
|
|||||||
|
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
import static com.google.gerrit.testing.GerritJUnit.assertThrows;
|
import static com.google.gerrit.testing.GerritJUnit.assertThrows;
|
||||||
|
import static java.util.stream.Collectors.toList;
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.ArgumentMatchers.argThat;
|
||||||
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
import static org.mockito.Mockito.clearInvocations;
|
import static org.mockito.Mockito.clearInvocations;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
@@ -25,19 +28,23 @@ import static org.mockito.MockitoAnnotations.initMocks;
|
|||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.google.common.collect.ImmutableMap;
|
import com.google.common.collect.ImmutableMap;
|
||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
|
import com.google.common.truth.Correspondence;
|
||||||
import com.google.gerrit.acceptance.AbstractDaemonTest;
|
import com.google.gerrit.acceptance.AbstractDaemonTest;
|
||||||
import com.google.gerrit.acceptance.PushOneCommit;
|
import com.google.gerrit.acceptance.PushOneCommit;
|
||||||
|
import com.google.gerrit.acceptance.config.GerritConfig;
|
||||||
import com.google.gerrit.extensions.annotations.Exports;
|
import com.google.gerrit.extensions.annotations.Exports;
|
||||||
import com.google.gerrit.extensions.api.changes.DraftInput;
|
import com.google.gerrit.extensions.api.changes.DraftInput;
|
||||||
import com.google.gerrit.extensions.api.changes.ReviewInput;
|
import com.google.gerrit.extensions.api.changes.ReviewInput;
|
||||||
import com.google.gerrit.extensions.api.changes.ReviewInput.CommentInput;
|
import com.google.gerrit.extensions.api.changes.ReviewInput.CommentInput;
|
||||||
import com.google.gerrit.extensions.api.changes.ReviewInput.DraftHandling;
|
import com.google.gerrit.extensions.api.changes.ReviewInput.DraftHandling;
|
||||||
|
import com.google.gerrit.extensions.api.changes.ReviewInput.RobotCommentInput;
|
||||||
import com.google.gerrit.extensions.client.Side;
|
import com.google.gerrit.extensions.client.Side;
|
||||||
import com.google.gerrit.extensions.common.ChangeMessageInfo;
|
import com.google.gerrit.extensions.common.ChangeMessageInfo;
|
||||||
|
import com.google.gerrit.extensions.common.RobotCommentInfo;
|
||||||
import com.google.gerrit.extensions.config.FactoryModule;
|
import com.google.gerrit.extensions.config.FactoryModule;
|
||||||
import com.google.gerrit.extensions.restapi.BadRequestException;
|
import com.google.gerrit.extensions.restapi.BadRequestException;
|
||||||
|
import com.google.gerrit.extensions.restapi.RestApiException;
|
||||||
import com.google.gerrit.extensions.validators.CommentForValidation;
|
import com.google.gerrit.extensions.validators.CommentForValidation;
|
||||||
import com.google.gerrit.extensions.validators.CommentForValidation.CommentType;
|
|
||||||
import com.google.gerrit.extensions.validators.CommentValidationContext;
|
import com.google.gerrit.extensions.validators.CommentValidationContext;
|
||||||
import com.google.gerrit.extensions.validators.CommentValidator;
|
import com.google.gerrit.extensions.validators.CommentValidator;
|
||||||
import com.google.gerrit.server.restapi.change.PostReview;
|
import com.google.gerrit.server.restapi.change.PostReview;
|
||||||
@@ -46,9 +53,12 @@ import com.google.gerrit.testing.TestCommentHelper;
|
|||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
import com.google.inject.Module;
|
import com.google.inject.Module;
|
||||||
import java.sql.Timestamp;
|
import java.sql.Timestamp;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.mockito.ArgumentCaptor;
|
import org.mockito.ArgumentCaptor;
|
||||||
|
import org.mockito.ArgumentMatcher;
|
||||||
import org.mockito.Captor;
|
import org.mockito.Captor;
|
||||||
|
|
||||||
/** Tests for comment validation in {@link PostReview}. */
|
/** Tests for comment validation in {@link PostReview}. */
|
||||||
@@ -57,8 +67,43 @@ public class PostReviewIT extends AbstractDaemonTest {
|
|||||||
@Inject private TestCommentHelper testCommentHelper;
|
@Inject private TestCommentHelper testCommentHelper;
|
||||||
|
|
||||||
private static final String COMMENT_TEXT = "The comment text";
|
private static final String COMMENT_TEXT = "The comment text";
|
||||||
|
private static final CommentForValidation FILE_COMMENT_FOR_VALIDATION =
|
||||||
|
CommentForValidation.create(
|
||||||
|
CommentForValidation.CommentSource.HUMAN,
|
||||||
|
CommentForValidation.CommentType.FILE_COMMENT,
|
||||||
|
COMMENT_TEXT,
|
||||||
|
COMMENT_TEXT.length());
|
||||||
|
private static final CommentForValidation INLINE_COMMENT_FOR_VALIDATION =
|
||||||
|
CommentForValidation.create(
|
||||||
|
CommentForValidation.CommentSource.HUMAN,
|
||||||
|
CommentForValidation.CommentType.INLINE_COMMENT,
|
||||||
|
COMMENT_TEXT,
|
||||||
|
COMMENT_TEXT.length());
|
||||||
|
private static final CommentForValidation CHANGE_MESSAGE_FOR_VALIDATION =
|
||||||
|
CommentForValidation.create(
|
||||||
|
CommentForValidation.CommentSource.HUMAN,
|
||||||
|
CommentForValidation.CommentType.CHANGE_MESSAGE,
|
||||||
|
COMMENT_TEXT,
|
||||||
|
COMMENT_TEXT.length());
|
||||||
|
|
||||||
@Captor private ArgumentCaptor<ImmutableList<CommentForValidation>> capture;
|
@Captor private ArgumentCaptor<ImmutableList<CommentForValidation>> captor;
|
||||||
|
|
||||||
|
private static class SingleCommentMatcher
|
||||||
|
implements ArgumentMatcher<ImmutableList<CommentForValidation>> {
|
||||||
|
final CommentForValidation left;
|
||||||
|
|
||||||
|
SingleCommentMatcher(CommentForValidation left) {
|
||||||
|
this.left = left;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean matches(ImmutableList<CommentForValidation> rightList) {
|
||||||
|
CommentForValidation right = Iterables.getOnlyElement(rightList);
|
||||||
|
return left.getSource() == right.getSource()
|
||||||
|
&& left.getType() == right.getType()
|
||||||
|
&& left.getText().equals(right.getText());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Module createModule() {
|
public Module createModule() {
|
||||||
@@ -84,13 +129,7 @@ public class PostReviewIT extends AbstractDaemonTest {
|
|||||||
public void validateCommentsInInput_commentOK() throws Exception {
|
public void validateCommentsInInput_commentOK() throws Exception {
|
||||||
PushOneCommit.Result r = createChange();
|
PushOneCommit.Result r = createChange();
|
||||||
when(mockCommentValidator.validateComments(
|
when(mockCommentValidator.validateComments(
|
||||||
CommentValidationContext.builder()
|
eq(contextFor(r)), argThat(new SingleCommentMatcher(FILE_COMMENT_FOR_VALIDATION))))
|
||||||
.changeId(r.getChange().getId().get())
|
|
||||||
.project(r.getChange().project().get())
|
|
||||||
.build(),
|
|
||||||
ImmutableList.of(
|
|
||||||
CommentForValidation.create(
|
|
||||||
CommentForValidation.CommentType.FILE_COMMENT, COMMENT_TEXT))))
|
|
||||||
.thenReturn(ImmutableList.of());
|
.thenReturn(ImmutableList.of());
|
||||||
|
|
||||||
ReviewInput input = new ReviewInput();
|
ReviewInput input = new ReviewInput();
|
||||||
@@ -107,15 +146,9 @@ public class PostReviewIT extends AbstractDaemonTest {
|
|||||||
@Test
|
@Test
|
||||||
public void validateCommentsInInput_commentRejected() throws Exception {
|
public void validateCommentsInInput_commentRejected() throws Exception {
|
||||||
PushOneCommit.Result r = createChange();
|
PushOneCommit.Result r = createChange();
|
||||||
CommentForValidation commentForValidation =
|
|
||||||
CommentForValidation.create(CommentType.FILE_COMMENT, COMMENT_TEXT);
|
|
||||||
when(mockCommentValidator.validateComments(
|
when(mockCommentValidator.validateComments(
|
||||||
CommentValidationContext.builder()
|
eq(contextFor(r)), argThat(new SingleCommentMatcher(FILE_COMMENT_FOR_VALIDATION))))
|
||||||
.changeId(r.getChange().getId().get())
|
.thenReturn(ImmutableList.of(FILE_COMMENT_FOR_VALIDATION.failValidation("Oh no!")));
|
||||||
.project(r.getChange().project().get())
|
|
||||||
.build(),
|
|
||||||
ImmutableList.of(CommentForValidation.create(CommentType.FILE_COMMENT, COMMENT_TEXT))))
|
|
||||||
.thenReturn(ImmutableList.of(commentForValidation.failValidation("Oh no!")));
|
|
||||||
|
|
||||||
ReviewInput input = new ReviewInput();
|
ReviewInput input = new ReviewInput();
|
||||||
CommentInput comment = newComment(r.getChange().currentFilePaths().get(0));
|
CommentInput comment = newComment(r.getChange().currentFilePaths().get(0));
|
||||||
@@ -161,19 +194,13 @@ public class PostReviewIT extends AbstractDaemonTest {
|
|||||||
public void validateDrafts_draftOK() throws Exception {
|
public void validateDrafts_draftOK() throws Exception {
|
||||||
PushOneCommit.Result r = createChange();
|
PushOneCommit.Result r = createChange();
|
||||||
when(mockCommentValidator.validateComments(
|
when(mockCommentValidator.validateComments(
|
||||||
CommentValidationContext.builder()
|
eq(contextFor(r)), argThat(new SingleCommentMatcher((INLINE_COMMENT_FOR_VALIDATION)))))
|
||||||
.changeId(r.getChange().getId().get())
|
|
||||||
.project(r.getChange().project().get())
|
|
||||||
.build(),
|
|
||||||
ImmutableList.of(
|
|
||||||
CommentForValidation.create(
|
|
||||||
CommentForValidation.CommentType.INLINE_COMMENT, COMMENT_TEXT))))
|
|
||||||
.thenReturn(ImmutableList.of());
|
.thenReturn(ImmutableList.of());
|
||||||
|
|
||||||
DraftInput draft =
|
DraftInput draft =
|
||||||
testCommentHelper.newDraft(
|
testCommentHelper.newDraft(
|
||||||
r.getChange().currentFilePaths().get(0), Side.REVISION, 1, COMMENT_TEXT);
|
r.getChange().currentFilePaths().get(0), Side.REVISION, 1, COMMENT_TEXT);
|
||||||
gApi.changes().id(r.getChangeId()).revision(r.getCommit().getName()).createDraft(draft).get();
|
gApi.changes().id(r.getChangeId()).revision(r.getCommit().getName()).createDraft(draft);
|
||||||
assertThat(testCommentHelper.getPublishedComments(r.getChangeId())).isEmpty();
|
assertThat(testCommentHelper.getPublishedComments(r.getChangeId())).isEmpty();
|
||||||
|
|
||||||
ReviewInput input = new ReviewInput();
|
ReviewInput input = new ReviewInput();
|
||||||
@@ -186,17 +213,9 @@ public class PostReviewIT extends AbstractDaemonTest {
|
|||||||
@Test
|
@Test
|
||||||
public void validateDrafts_draftRejected() throws Exception {
|
public void validateDrafts_draftRejected() throws Exception {
|
||||||
PushOneCommit.Result r = createChange();
|
PushOneCommit.Result r = createChange();
|
||||||
CommentForValidation commentForValidation =
|
|
||||||
CommentForValidation.create(CommentType.INLINE_COMMENT, COMMENT_TEXT);
|
|
||||||
when(mockCommentValidator.validateComments(
|
when(mockCommentValidator.validateComments(
|
||||||
CommentValidationContext.builder()
|
eq(contextFor(r)), argThat(new SingleCommentMatcher(INLINE_COMMENT_FOR_VALIDATION))))
|
||||||
.changeId(r.getChange().getId().get())
|
.thenReturn(ImmutableList.of(INLINE_COMMENT_FOR_VALIDATION.failValidation("Oh no!")));
|
||||||
.project(r.getChange().project().get())
|
|
||||||
.build(),
|
|
||||||
ImmutableList.of(
|
|
||||||
CommentForValidation.create(
|
|
||||||
CommentForValidation.CommentType.INLINE_COMMENT, COMMENT_TEXT))))
|
|
||||||
.thenReturn(ImmutableList.of(commentForValidation.failValidation("Oh no!")));
|
|
||||||
|
|
||||||
DraftInput draft =
|
DraftInput draft =
|
||||||
testCommentHelper.newDraft(
|
testCommentHelper.newDraft(
|
||||||
@@ -233,7 +252,7 @@ public class PostReviewIT extends AbstractDaemonTest {
|
|||||||
testCommentHelper.addDraft(r.getChangeId(), r.getCommit().getName(), draftFile);
|
testCommentHelper.addDraft(r.getChangeId(), r.getCommit().getName(), draftFile);
|
||||||
assertThat(testCommentHelper.getPublishedComments(r.getChangeId())).isEmpty();
|
assertThat(testCommentHelper.getPublishedComments(r.getChangeId())).isEmpty();
|
||||||
|
|
||||||
when(mockCommentValidator.validateComments(any(), capture.capture()))
|
when(mockCommentValidator.validateComments(any(), captor.capture()))
|
||||||
.thenReturn(ImmutableList.of());
|
.thenReturn(ImmutableList.of());
|
||||||
|
|
||||||
ReviewInput input = new ReviewInput();
|
ReviewInput input = new ReviewInput();
|
||||||
@@ -241,25 +260,33 @@ public class PostReviewIT extends AbstractDaemonTest {
|
|||||||
gApi.changes().id(r.getChangeId()).current().review(input);
|
gApi.changes().id(r.getChangeId()).current().review(input);
|
||||||
assertThat(testCommentHelper.getPublishedComments(r.getChangeId())).hasSize(2);
|
assertThat(testCommentHelper.getPublishedComments(r.getChangeId())).hasSize(2);
|
||||||
|
|
||||||
assertThat(capture.getAllValues()).hasSize(1);
|
assertThat(captor.getAllValues()).hasSize(1);
|
||||||
assertThat(capture.getValue())
|
assertThat(captor.getValue())
|
||||||
|
.comparingElementsUsing(
|
||||||
|
Correspondence.<CommentForValidation, CommentForValidation>from(
|
||||||
|
(a, b) ->
|
||||||
|
a.getSource() == b.getSource()
|
||||||
|
&& a.getType() == b.getType()
|
||||||
|
&& a.getText().equals(b.getText()),
|
||||||
|
"matches (ignoring size approximation)"))
|
||||||
.containsExactly(
|
.containsExactly(
|
||||||
CommentForValidation.create(
|
CommentForValidation.create(
|
||||||
CommentForValidation.CommentType.INLINE_COMMENT, draftInline.message),
|
CommentForValidation.CommentSource.HUMAN,
|
||||||
|
CommentForValidation.CommentType.INLINE_COMMENT,
|
||||||
|
draftInline.message,
|
||||||
|
draftInline.message.length()),
|
||||||
CommentForValidation.create(
|
CommentForValidation.create(
|
||||||
CommentForValidation.CommentType.FILE_COMMENT, draftFile.message));
|
CommentForValidation.CommentSource.HUMAN,
|
||||||
|
CommentForValidation.CommentType.FILE_COMMENT,
|
||||||
|
draftFile.message,
|
||||||
|
draftFile.message.length()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void validateCommentsInChangeMessage_messageOK() throws Exception {
|
public void validateCommentsInChangeMessage_messageOK() throws Exception {
|
||||||
PushOneCommit.Result r = createChange();
|
PushOneCommit.Result r = createChange();
|
||||||
when(mockCommentValidator.validateComments(
|
when(mockCommentValidator.validateComments(
|
||||||
CommentValidationContext.builder()
|
contextFor(r), ImmutableList.of(CHANGE_MESSAGE_FOR_VALIDATION)))
|
||||||
.changeId(r.getChange().getId().get())
|
|
||||||
.project(r.getChange().project().get())
|
|
||||||
.build(),
|
|
||||||
ImmutableList.of(
|
|
||||||
CommentForValidation.create(CommentType.CHANGE_MESSAGE, COMMENT_TEXT))))
|
|
||||||
.thenReturn(ImmutableList.of());
|
.thenReturn(ImmutableList.of());
|
||||||
|
|
||||||
ReviewInput input = new ReviewInput().message(COMMENT_TEXT);
|
ReviewInput input = new ReviewInput().message(COMMENT_TEXT);
|
||||||
@@ -274,16 +301,9 @@ public class PostReviewIT extends AbstractDaemonTest {
|
|||||||
@Test
|
@Test
|
||||||
public void validateCommentsInChangeMessage_messageRejected() throws Exception {
|
public void validateCommentsInChangeMessage_messageRejected() throws Exception {
|
||||||
PushOneCommit.Result r = createChange();
|
PushOneCommit.Result r = createChange();
|
||||||
CommentForValidation commentForValidation =
|
|
||||||
CommentForValidation.create(CommentType.CHANGE_MESSAGE, COMMENT_TEXT);
|
|
||||||
when(mockCommentValidator.validateComments(
|
when(mockCommentValidator.validateComments(
|
||||||
CommentValidationContext.builder()
|
contextFor(r), ImmutableList.of(CHANGE_MESSAGE_FOR_VALIDATION)))
|
||||||
.changeId(r.getChange().getId().get())
|
.thenReturn(ImmutableList.of(CHANGE_MESSAGE_FOR_VALIDATION.failValidation("Oh no!")));
|
||||||
.project(r.getChange().project().get())
|
|
||||||
.build(),
|
|
||||||
ImmutableList.of(
|
|
||||||
CommentForValidation.create(CommentType.CHANGE_MESSAGE, COMMENT_TEXT))))
|
|
||||||
.thenReturn(ImmutableList.of(commentForValidation.failValidation("Oh no!")));
|
|
||||||
|
|
||||||
ReviewInput input = new ReviewInput().message(COMMENT_TEXT);
|
ReviewInput input = new ReviewInput().message(COMMENT_TEXT);
|
||||||
assertThat(gApi.changes().id(r.getChangeId()).get().messages)
|
assertThat(gApi.changes().id(r.getChangeId()).get().messages)
|
||||||
@@ -308,7 +328,56 @@ public class PostReviewIT extends AbstractDaemonTest {
|
|||||||
assertThat(message.message).doesNotContain(COMMENT_TEXT);
|
assertThat(message.message).doesNotContain(COMMENT_TEXT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@GerritConfig(name = "change.maxComments", value = "4")
|
||||||
|
public void restrictNumberOfComments() throws Exception {
|
||||||
|
when(mockCommentValidator.validateComments(any(), any())).thenReturn(ImmutableList.of());
|
||||||
|
|
||||||
|
PushOneCommit.Result r = createChange();
|
||||||
|
String filePath = r.getChange().currentFilePaths().get(0);
|
||||||
|
CommentInput commentInput = new CommentInput();
|
||||||
|
commentInput.line = 1;
|
||||||
|
commentInput.message = "foo";
|
||||||
|
commentInput.path = filePath;
|
||||||
|
RobotCommentInput robotCommentInput =
|
||||||
|
TestCommentHelper.createRobotCommentInputWithMandatoryFields(filePath);
|
||||||
|
ReviewInput reviewInput = new ReviewInput();
|
||||||
|
reviewInput.comments = ImmutableMap.of(filePath, ImmutableList.of(commentInput));
|
||||||
|
reviewInput.robotComments = ImmutableMap.of(filePath, ImmutableList.of(robotCommentInput));
|
||||||
|
gApi.changes().id(r.getChangeId()).current().review(reviewInput);
|
||||||
|
|
||||||
|
// reviewInput still has both a user and a robot comment (and deduplication is false). We also
|
||||||
|
// create a draft so that in total there would be 5 comments. The limit is set to 4, so this
|
||||||
|
// verifies that all three channels are considered.
|
||||||
|
DraftInput draftInline = testCommentHelper.newDraft(filePath, Side.REVISION, 1, "a draft");
|
||||||
|
testCommentHelper.addDraft(r.getChangeId(), r.getPatchSetId().getId(), draftInline);
|
||||||
|
reviewInput.drafts = DraftHandling.PUBLISH;
|
||||||
|
reviewInput.omitDuplicateComments = false;
|
||||||
|
|
||||||
|
BadRequestException exception =
|
||||||
|
assertThrows(
|
||||||
|
BadRequestException.class,
|
||||||
|
() -> gApi.changes().id(r.getChangeId()).current().review(reviewInput));
|
||||||
|
assertThat(exception)
|
||||||
|
.hasMessageThat()
|
||||||
|
.contains("Exceeding maximum number of comments: 2 (existing) + 3 (new) > 4");
|
||||||
|
|
||||||
|
assertThat(testCommentHelper.getPublishedComments(r.getChangeId())).hasSize(1);
|
||||||
|
assertThat(getRobotComments(r.getChangeId())).hasSize(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<RobotCommentInfo> getRobotComments(String changeId) throws RestApiException {
|
||||||
|
return gApi.changes().id(changeId).robotComments().values().stream()
|
||||||
|
.flatMap(Collection::stream)
|
||||||
|
.collect(toList());
|
||||||
|
}
|
||||||
|
|
||||||
private static CommentInput newComment(String path) {
|
private static CommentInput newComment(String path) {
|
||||||
return TestCommentHelper.populate(new CommentInput(), path, PostReviewIT.COMMENT_TEXT);
|
return TestCommentHelper.populate(new CommentInput(), path, PostReviewIT.COMMENT_TEXT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static CommentValidationContext contextFor(PushOneCommit.Result result) {
|
||||||
|
return CommentValidationContext.create(
|
||||||
|
result.getChange().getId().get(), result.getChange().project().get());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -173,9 +173,8 @@ public class RobotCommentsIT extends AbstractDaemonTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void hugeRobotCommentIsRejected() {
|
public void hugeRobotCommentIsRejected() {
|
||||||
int defaultSizeLimit = 1024 * 1024;
|
int defaultSizeLimit = 1 << 20;
|
||||||
int sizeOfRest = 451;
|
fixReplacementInfo.replacement = getStringFor(defaultSizeLimit + 1);
|
||||||
fixReplacementInfo.replacement = getStringFor(defaultSizeLimit - sizeOfRest + 1);
|
|
||||||
|
|
||||||
BadRequestException thrown =
|
BadRequestException thrown =
|
||||||
assertThrows(
|
assertThrows(
|
||||||
@@ -186,9 +185,9 @@ public class RobotCommentsIT extends AbstractDaemonTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void reasonablyLargeRobotCommentIsAccepted() throws Exception {
|
public void reasonablyLargeRobotCommentIsAccepted() throws Exception {
|
||||||
int defaultSizeLimit = 1024 * 1024;
|
int defaultSizeLimit = 1 << 20;
|
||||||
int sizeOfRest = 451;
|
// Allow for a few hundred bytes in other fields.
|
||||||
fixReplacementInfo.replacement = getStringFor(defaultSizeLimit - sizeOfRest);
|
fixReplacementInfo.replacement = getStringFor(defaultSizeLimit - 666);
|
||||||
|
|
||||||
testCommentHelper.addRobotComment(changeId, withFixRobotCommentInput);
|
testCommentHelper.addRobotComment(changeId, withFixRobotCommentInput);
|
||||||
|
|
||||||
@@ -199,7 +198,7 @@ public class RobotCommentsIT extends AbstractDaemonTest {
|
|||||||
@Test
|
@Test
|
||||||
@GerritConfig(name = "change.robotCommentSizeLimit", value = "10k")
|
@GerritConfig(name = "change.robotCommentSizeLimit", value = "10k")
|
||||||
public void maximumAllowedSizeOfRobotCommentCanBeAdjusted() {
|
public void maximumAllowedSizeOfRobotCommentCanBeAdjusted() {
|
||||||
int sizeLimit = 10 * 1024;
|
int sizeLimit = 10 << 20;
|
||||||
fixReplacementInfo.replacement = getStringFor(sizeLimit);
|
fixReplacementInfo.replacement = getStringFor(sizeLimit);
|
||||||
|
|
||||||
BadRequestException thrown =
|
BadRequestException thrown =
|
||||||
@@ -212,8 +211,8 @@ public class RobotCommentsIT extends AbstractDaemonTest {
|
|||||||
@Test
|
@Test
|
||||||
@GerritConfig(name = "change.robotCommentSizeLimit", value = "0")
|
@GerritConfig(name = "change.robotCommentSizeLimit", value = "0")
|
||||||
public void zeroForMaximumAllowedSizeOfRobotCommentRemovesRestriction() throws Exception {
|
public void zeroForMaximumAllowedSizeOfRobotCommentRemovesRestriction() throws Exception {
|
||||||
int defaultSizeLimit = 1024 * 1024;
|
int defaultSizeLimit = 1 << 20;
|
||||||
fixReplacementInfo.replacement = getStringFor(defaultSizeLimit);
|
fixReplacementInfo.replacement = getStringFor(2 * defaultSizeLimit);
|
||||||
|
|
||||||
testCommentHelper.addRobotComment(changeId, withFixRobotCommentInput);
|
testCommentHelper.addRobotComment(changeId, withFixRobotCommentInput);
|
||||||
|
|
||||||
@@ -225,8 +224,8 @@ public class RobotCommentsIT extends AbstractDaemonTest {
|
|||||||
@GerritConfig(name = "change.robotCommentSizeLimit", value = "-1")
|
@GerritConfig(name = "change.robotCommentSizeLimit", value = "-1")
|
||||||
public void negativeValueForMaximumAllowedSizeOfRobotCommentRemovesRestriction()
|
public void negativeValueForMaximumAllowedSizeOfRobotCommentRemovesRestriction()
|
||||||
throws Exception {
|
throws Exception {
|
||||||
int defaultSizeLimit = 1024 * 1024;
|
int defaultSizeLimit = 1 << 20;
|
||||||
fixReplacementInfo.replacement = getStringFor(defaultSizeLimit);
|
fixReplacementInfo.replacement = getStringFor(2 * defaultSizeLimit);
|
||||||
|
|
||||||
testCommentHelper.addRobotComment(changeId, withFixRobotCommentInput);
|
testCommentHelper.addRobotComment(changeId, withFixRobotCommentInput);
|
||||||
|
|
||||||
|
@@ -31,7 +31,6 @@ import com.google.gerrit.extensions.api.changes.DraftInput;
|
|||||||
import com.google.gerrit.extensions.client.Side;
|
import com.google.gerrit.extensions.client.Side;
|
||||||
import com.google.gerrit.extensions.config.FactoryModule;
|
import com.google.gerrit.extensions.config.FactoryModule;
|
||||||
import com.google.gerrit.extensions.validators.CommentForValidation;
|
import com.google.gerrit.extensions.validators.CommentForValidation;
|
||||||
import com.google.gerrit.extensions.validators.CommentForValidation.CommentType;
|
|
||||||
import com.google.gerrit.extensions.validators.CommentValidationContext;
|
import com.google.gerrit.extensions.validators.CommentValidationContext;
|
||||||
import com.google.gerrit.extensions.validators.CommentValidator;
|
import com.google.gerrit.extensions.validators.CommentValidator;
|
||||||
import com.google.gerrit.testing.TestCommentHelper;
|
import com.google.gerrit.testing.TestCommentHelper;
|
||||||
@@ -49,9 +48,15 @@ public class ReceiveCommitsCommentValidationIT extends AbstractDaemonTest {
|
|||||||
@Inject private CommentValidator mockCommentValidator;
|
@Inject private CommentValidator mockCommentValidator;
|
||||||
@Inject private TestCommentHelper testCommentHelper;
|
@Inject private TestCommentHelper testCommentHelper;
|
||||||
|
|
||||||
private static final int MAX_COMMENT_LENGTH = 666;
|
private static final int COMMENT_SIZE_LIMIT = 666;
|
||||||
|
|
||||||
private static final String COMMENT_TEXT = "The comment text";
|
private static final String COMMENT_TEXT = "The comment text";
|
||||||
|
private static final CommentForValidation COMMENT_FOR_VALIDATION =
|
||||||
|
CommentForValidation.create(
|
||||||
|
CommentForValidation.CommentSource.HUMAN,
|
||||||
|
CommentForValidation.CommentType.FILE_COMMENT,
|
||||||
|
COMMENT_TEXT,
|
||||||
|
COMMENT_TEXT.length());
|
||||||
|
|
||||||
@Captor private ArgumentCaptor<ImmutableList<CommentForValidation>> capture;
|
@Captor private ArgumentCaptor<ImmutableList<CommentForValidation>> capture;
|
||||||
@Captor private ArgumentCaptor<CommentValidationContext> captureCtx;
|
@Captor private ArgumentCaptor<CommentValidationContext> captureCtx;
|
||||||
@@ -82,13 +87,9 @@ public class ReceiveCommitsCommentValidationIT extends AbstractDaemonTest {
|
|||||||
String changeId = result.getChangeId();
|
String changeId = result.getChangeId();
|
||||||
String revId = result.getCommit().getName();
|
String revId = result.getCommit().getName();
|
||||||
when(mockCommentValidator.validateComments(
|
when(mockCommentValidator.validateComments(
|
||||||
CommentValidationContext.builder()
|
CommentValidationContext.create(
|
||||||
.changeId(result.getChange().getId().get())
|
result.getChange().getId().get(), result.getChange().project().get()),
|
||||||
.project(result.getChange().project().get())
|
ImmutableList.of(COMMENT_FOR_VALIDATION)))
|
||||||
.build(),
|
|
||||||
ImmutableList.of(
|
|
||||||
CommentForValidation.create(
|
|
||||||
CommentForValidation.CommentType.FILE_COMMENT, COMMENT_TEXT))))
|
|
||||||
.thenReturn(ImmutableList.of());
|
.thenReturn(ImmutableList.of());
|
||||||
DraftInput comment = testCommentHelper.newDraft(COMMENT_TEXT);
|
DraftInput comment = testCommentHelper.newDraft(COMMENT_TEXT);
|
||||||
testCommentHelper.addDraft(changeId, revId, comment);
|
testCommentHelper.addDraft(changeId, revId, comment);
|
||||||
@@ -101,20 +102,14 @@ public class ReceiveCommitsCommentValidationIT extends AbstractDaemonTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void validateComments_commentRejected() throws Exception {
|
public void validateComments_commentRejected() throws Exception {
|
||||||
CommentForValidation commentForValidation =
|
|
||||||
CommentForValidation.create(CommentType.FILE_COMMENT, COMMENT_TEXT);
|
|
||||||
PushOneCommit.Result result = createChange();
|
PushOneCommit.Result result = createChange();
|
||||||
String changeId = result.getChangeId();
|
String changeId = result.getChangeId();
|
||||||
String revId = result.getCommit().getName();
|
String revId = result.getCommit().getName();
|
||||||
when(mockCommentValidator.validateComments(
|
when(mockCommentValidator.validateComments(
|
||||||
CommentValidationContext.builder()
|
CommentValidationContext.create(
|
||||||
.changeId(result.getChange().getId().get())
|
result.getChange().getId().get(), result.getChange().project().get()),
|
||||||
.project(result.getChange().project().get())
|
ImmutableList.of(COMMENT_FOR_VALIDATION)))
|
||||||
.build(),
|
.thenReturn(ImmutableList.of(COMMENT_FOR_VALIDATION.failValidation("Oh no!")));
|
||||||
ImmutableList.of(
|
|
||||||
CommentForValidation.create(
|
|
||||||
CommentForValidation.CommentType.FILE_COMMENT, COMMENT_TEXT))))
|
|
||||||
.thenReturn(ImmutableList.of(commentForValidation.failValidation("Oh no!")));
|
|
||||||
DraftInput comment = testCommentHelper.newDraft(COMMENT_TEXT);
|
DraftInput comment = testCommentHelper.newDraft(COMMENT_TEXT);
|
||||||
testCommentHelper.addDraft(changeId, revId, comment);
|
testCommentHelper.addDraft(changeId, revId, comment);
|
||||||
assertThat(testCommentHelper.getPublishedComments(result.getChangeId())).isEmpty();
|
assertThat(testCommentHelper.getPublishedComments(result.getChangeId())).isEmpty();
|
||||||
@@ -149,18 +144,24 @@ public class ReceiveCommitsCommentValidationIT extends AbstractDaemonTest {
|
|||||||
assertThat(capture.getAllValues().get(0))
|
assertThat(capture.getAllValues().get(0))
|
||||||
.containsExactly(
|
.containsExactly(
|
||||||
CommentForValidation.create(
|
CommentForValidation.create(
|
||||||
CommentForValidation.CommentType.INLINE_COMMENT, draftInline.message),
|
CommentForValidation.CommentSource.HUMAN,
|
||||||
|
CommentForValidation.CommentType.INLINE_COMMENT,
|
||||||
|
draftInline.message,
|
||||||
|
draftInline.message.length()),
|
||||||
CommentForValidation.create(
|
CommentForValidation.create(
|
||||||
CommentForValidation.CommentType.FILE_COMMENT, draftFile.message));
|
CommentForValidation.CommentSource.HUMAN,
|
||||||
|
CommentForValidation.CommentType.FILE_COMMENT,
|
||||||
|
draftFile.message,
|
||||||
|
draftFile.message.length()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@GerritConfig(name = "change.maxCommentLength", value = "" + MAX_COMMENT_LENGTH)
|
@GerritConfig(name = "change.commentSizeLimit", value = "" + COMMENT_SIZE_LIMIT)
|
||||||
public void validateComments_enforceLimits_commentTooLarge() throws Exception {
|
public void validateComments_enforceLimits_commentTooLarge() throws Exception {
|
||||||
when(mockCommentValidator.validateComments(any(), any())).thenReturn(ImmutableList.of());
|
when(mockCommentValidator.validateComments(any(), any())).thenReturn(ImmutableList.of());
|
||||||
PushOneCommit.Result result = createChange();
|
PushOneCommit.Result result = createChange();
|
||||||
String changeId = result.getChangeId();
|
String changeId = result.getChangeId();
|
||||||
int commentLength = MAX_COMMENT_LENGTH + 1;
|
int commentLength = COMMENT_SIZE_LIMIT + 1;
|
||||||
DraftInput comment =
|
DraftInput comment =
|
||||||
testCommentHelper.newDraft(new String(new char[commentLength]).replace("\0", "x"));
|
testCommentHelper.newDraft(new String(new char[commentLength]).replace("\0", "x"));
|
||||||
testCommentHelper.addDraft(changeId, result.getCommit().getName(), comment);
|
testCommentHelper.addDraft(changeId, result.getCommit().getName(), comment);
|
||||||
@@ -169,7 +170,33 @@ public class ReceiveCommitsCommentValidationIT extends AbstractDaemonTest {
|
|||||||
Result amendResult = amendChange(changeId, "refs/for/master%publish-comments", admin, testRepo);
|
Result amendResult = amendChange(changeId, "refs/for/master%publish-comments", admin, testRepo);
|
||||||
amendResult.assertOkStatus();
|
amendResult.assertOkStatus();
|
||||||
amendResult.assertMessage(
|
amendResult.assertMessage(
|
||||||
String.format("Comment too large (%d > %d)", commentLength, MAX_COMMENT_LENGTH));
|
String.format("Comment size exceeds limit (%d > %d)", commentLength, COMMENT_SIZE_LIMIT));
|
||||||
assertThat(testCommentHelper.getPublishedComments(result.getChangeId())).isEmpty();
|
assertThat(testCommentHelper.getPublishedComments(result.getChangeId())).isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@GerritConfig(name = "change.maxComments", value = "3")
|
||||||
|
public void countComments_limitNumberOfComments() throws Exception {
|
||||||
|
when(mockCommentValidator.validateComments(any(), any())).thenReturn(ImmutableList.of());
|
||||||
|
PushOneCommit.Result result = createChange();
|
||||||
|
String changeId = result.getChangeId();
|
||||||
|
String revId = result.getCommit().getName();
|
||||||
|
String filePath = result.getChange().currentFilePaths().get(0);
|
||||||
|
DraftInput draftInline = testCommentHelper.newDraft(filePath, Side.REVISION, 1, COMMENT_TEXT);
|
||||||
|
testCommentHelper.addDraft(changeId, revId, draftInline);
|
||||||
|
amendChange(changeId, "refs/for/master%publish-comments", admin, testRepo);
|
||||||
|
assertThat(testCommentHelper.getPublishedComments(result.getChangeId())).hasSize(1);
|
||||||
|
|
||||||
|
for (int i = 1; i < 3; ++i) {
|
||||||
|
testCommentHelper.addRobotComment(
|
||||||
|
changeId,
|
||||||
|
TestCommentHelper.createRobotCommentInput(result.getChange().currentFilePaths().get(0)));
|
||||||
|
}
|
||||||
|
|
||||||
|
draftInline = testCommentHelper.newDraft(filePath, Side.REVISION, 1, COMMENT_TEXT);
|
||||||
|
testCommentHelper.addDraft(changeId, revId, draftInline);
|
||||||
|
Result amendResult = amendChange(changeId, "refs/for/master%publish-comments", admin, testRepo);
|
||||||
|
assertThat(testCommentHelper.getPublishedComments(result.getChangeId())).hasSize(1);
|
||||||
|
amendResult.assertMessage("exceeding maximum number of comments");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -33,7 +33,9 @@ import org.junit.Ignore;
|
|||||||
public class AbstractMailIT extends AbstractDaemonTest {
|
public class AbstractMailIT extends AbstractDaemonTest {
|
||||||
@Inject private RequestScopeOperations requestScopeOperations;
|
@Inject private RequestScopeOperations requestScopeOperations;
|
||||||
|
|
||||||
protected MailMessage.Builder messageBuilderWithDefaultFields() {
|
static final String FILE_NAME = "gerrit-server/test.txt";
|
||||||
|
|
||||||
|
MailMessage.Builder messageBuilderWithDefaultFields() {
|
||||||
MailMessage.Builder b = MailMessage.builder();
|
MailMessage.Builder b = MailMessage.builder();
|
||||||
b.id("some id");
|
b.id("some id");
|
||||||
b.from(user.getEmailAddress());
|
b.from(user.getEmailAddress());
|
||||||
@@ -43,16 +45,15 @@ public class AbstractMailIT extends AbstractDaemonTest {
|
|||||||
return b;
|
return b;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected String createChangeWithReview() throws Exception {
|
String createChangeWithReview() throws Exception {
|
||||||
return createChangeWithReview(admin);
|
return createChangeWithReview(admin);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected String createChangeWithReview(TestAccount reviewer) throws Exception {
|
String createChangeWithReview(TestAccount reviewer) throws Exception {
|
||||||
// Create change
|
// Create change
|
||||||
String file = "gerrit-server/test.txt";
|
|
||||||
String contents = "contents \nlorem \nipsum \nlorem";
|
String contents = "contents \nlorem \nipsum \nlorem";
|
||||||
PushOneCommit push =
|
PushOneCommit push =
|
||||||
pushFactory.create(admin.newIdent(), testRepo, "first subject", file, contents);
|
pushFactory.create(admin.newIdent(), testRepo, "first subject", FILE_NAME, contents);
|
||||||
PushOneCommit.Result r = push.to("refs/for/master");
|
PushOneCommit.Result r = push.to("refs/for/master");
|
||||||
String changeId = r.getChangeId();
|
String changeId = r.getChangeId();
|
||||||
|
|
||||||
@@ -61,8 +62,8 @@ public class AbstractMailIT extends AbstractDaemonTest {
|
|||||||
ReviewInput input = new ReviewInput();
|
ReviewInput input = new ReviewInput();
|
||||||
input.message = "I have two comments";
|
input.message = "I have two comments";
|
||||||
input.comments = new HashMap<>();
|
input.comments = new HashMap<>();
|
||||||
CommentInput c1 = newComment(file, Side.REVISION, 0, "comment on file");
|
CommentInput c1 = newComment(FILE_NAME, Side.REVISION, 0, "comment on file");
|
||||||
CommentInput c2 = newComment(file, Side.REVISION, 2, "inline comment");
|
CommentInput c2 = newComment(FILE_NAME, Side.REVISION, 2, "inline comment");
|
||||||
input.comments.put(c1.path, ImmutableList.of(c1, c2));
|
input.comments.put(c1.path, ImmutableList.of(c1, c2));
|
||||||
revision(r).review(input);
|
revision(r).review(input);
|
||||||
return changeId;
|
return changeId;
|
||||||
@@ -94,7 +95,7 @@ public class AbstractMailIT extends AbstractDaemonTest {
|
|||||||
* @param fc1 Comment in reply to a comment of file 1.
|
* @param fc1 Comment in reply to a comment of file 1.
|
||||||
* @return A string with all inline comments and the original quoted email.
|
* @return A string with all inline comments and the original quoted email.
|
||||||
*/
|
*/
|
||||||
protected static String newPlaintextBody(
|
static String newPlaintextBody(
|
||||||
String changeURL, String changeMessage, String c1, String f1, String fc1) {
|
String changeURL, String changeMessage, String c1, String f1, String fc1) {
|
||||||
return (changeMessage == null ? "" : changeMessage + "\n")
|
return (changeMessage == null ? "" : changeMessage + "\n")
|
||||||
+ "> Foo Bar has posted comments on this change. ( \n"
|
+ "> Foo Bar has posted comments on this change. ( \n"
|
||||||
@@ -137,7 +138,7 @@ public class AbstractMailIT extends AbstractDaemonTest {
|
|||||||
+ "> \n";
|
+ "> \n";
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static String textFooterForChange(int changeNumber, String timestamp) {
|
static String textFooterForChange(int changeNumber, String timestamp) {
|
||||||
return "Gerrit-Change-Number: "
|
return "Gerrit-Change-Number: "
|
||||||
+ changeNumber
|
+ changeNumber
|
||||||
+ "\n"
|
+ "\n"
|
||||||
|
@@ -21,9 +21,14 @@ import static org.mockito.Mockito.mock;
|
|||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import com.google.common.collect.ImmutableMap;
|
||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
|
import com.google.gerrit.acceptance.config.GerritConfig;
|
||||||
import com.google.gerrit.acceptance.testsuite.account.AccountOperations;
|
import com.google.gerrit.acceptance.testsuite.account.AccountOperations;
|
||||||
import com.google.gerrit.extensions.annotations.Exports;
|
import com.google.gerrit.extensions.annotations.Exports;
|
||||||
|
import com.google.gerrit.extensions.api.changes.ReviewInput;
|
||||||
|
import com.google.gerrit.extensions.api.changes.ReviewInput.CommentInput;
|
||||||
|
import com.google.gerrit.extensions.api.changes.ReviewInput.RobotCommentInput;
|
||||||
import com.google.gerrit.extensions.common.ChangeInfo;
|
import com.google.gerrit.extensions.common.ChangeInfo;
|
||||||
import com.google.gerrit.extensions.common.ChangeMessageInfo;
|
import com.google.gerrit.extensions.common.ChangeMessageInfo;
|
||||||
import com.google.gerrit.extensions.common.CommentInfo;
|
import com.google.gerrit.extensions.common.CommentInfo;
|
||||||
@@ -341,17 +346,53 @@ public class MailProcessorIT extends AbstractMailIT {
|
|||||||
assertThat(message.body()).contains("rejected one or more comments");
|
assertThat(message.body()).contains("rejected one or more comments");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@GerritConfig(name = "change.maxComments", value = "4")
|
||||||
|
public void limitNumberOfComments() throws Exception {
|
||||||
|
String changeId = createChangeWithReview();
|
||||||
|
CommentInput commentInput = new CommentInput();
|
||||||
|
commentInput.line = 1;
|
||||||
|
commentInput.message = "foo";
|
||||||
|
commentInput.path = FILE_NAME;
|
||||||
|
RobotCommentInput robotCommentInput =
|
||||||
|
TestCommentHelper.createRobotCommentInputWithMandatoryFields(FILE_NAME);
|
||||||
|
ReviewInput reviewInput = new ReviewInput();
|
||||||
|
reviewInput.comments = ImmutableMap.of(FILE_NAME, ImmutableList.of(commentInput));
|
||||||
|
reviewInput.robotComments = ImmutableMap.of(FILE_NAME, ImmutableList.of(robotCommentInput));
|
||||||
|
gApi.changes().id(changeId).current().review(reviewInput);
|
||||||
|
|
||||||
|
ChangeInfo changeInfo = gApi.changes().id(changeId).get();
|
||||||
|
List<CommentInfo> comments = gApi.changes().id(changeId).current().commentsAsList();
|
||||||
|
String ts =
|
||||||
|
MailProcessingUtil.rfcDateformatter.format(
|
||||||
|
ZonedDateTime.ofInstant(comments.get(0).updated.toInstant(), ZoneId.of("UTC")));
|
||||||
|
|
||||||
|
MailMessage.Builder b = messageBuilderWithDefaultFields();
|
||||||
|
String txt = newPlaintextBody(getChangeUrl(changeInfo) + "/1", null, null, COMMENT_TEXT, null);
|
||||||
|
b.textContent(txt + textFooterForChange(changeInfo._number, ts));
|
||||||
|
|
||||||
|
Collection<CommentInfo> commentsBefore = testCommentHelper.getPublishedComments(changeId);
|
||||||
|
mailProcessor.process(b.build());
|
||||||
|
assertThat(testCommentHelper.getPublishedComments(changeId)).isEqualTo(commentsBefore);
|
||||||
|
|
||||||
|
assertNotifyTo(user);
|
||||||
|
Message message = sender.nextMessage();
|
||||||
|
assertThat(message.body()).contains("rejected one or more comments");
|
||||||
|
}
|
||||||
|
|
||||||
private String getChangeUrl(ChangeInfo changeInfo) {
|
private String getChangeUrl(ChangeInfo changeInfo) {
|
||||||
return canonicalWebUrl.get() + "c/" + changeInfo.project + "/+/" + changeInfo._number;
|
return canonicalWebUrl.get() + "c/" + changeInfo.project + "/+/" + changeInfo._number;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setupFailValidation(
|
private void setupFailValidation(
|
||||||
CommentForValidation.CommentType type, String failProject, int failChange) {
|
CommentForValidation.CommentType type, String failProject, int failChange) {
|
||||||
CommentForValidation commentForValidation = CommentForValidation.create(type, COMMENT_TEXT);
|
CommentForValidation commentForValidation =
|
||||||
|
CommentForValidation.create(
|
||||||
|
CommentForValidation.CommentSource.HUMAN, type, COMMENT_TEXT, COMMENT_TEXT.length());
|
||||||
|
|
||||||
when(mockCommentValidator.validateComments(
|
when(mockCommentValidator.validateComments(
|
||||||
CommentValidationContext.builder().changeId(failChange).project(failProject).build(),
|
CommentValidationContext.create(failChange, failProject),
|
||||||
ImmutableList.of(CommentForValidation.create(type, COMMENT_TEXT))))
|
ImmutableList.of(commentForValidation)))
|
||||||
.thenReturn(ImmutableList.of(commentForValidation.failValidation("Oh no!")));
|
.thenReturn(ImmutableList.of(commentForValidation.failValidation("Oh no!")));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -64,6 +64,7 @@
|
|||||||
{/template}
|
{/template}
|
||||||
|
|
||||||
{template .InboundEmailRejection_COMMENT_REJECTED kind="text"}
|
{template .InboundEmailRejection_COMMENT_REJECTED kind="text"}
|
||||||
Gerrit Code Review rejected one or more comments because they did not pass validation.
|
Gerrit Code Review rejected one or more comments because they did not pass validation, or
|
||||||
|
because the maximum number of comments per change would be exceeded.
|
||||||
{call .InboundEmailRejectionFooter /}
|
{call .InboundEmailRejectionFooter /}
|
||||||
{/template}
|
{/template}
|
||||||
|
@@ -81,7 +81,8 @@
|
|||||||
|
|
||||||
{template .InboundEmailRejectionHtml_COMMENT_REJECTED}
|
{template .InboundEmailRejectionHtml_COMMENT_REJECTED}
|
||||||
<p>
|
<p>
|
||||||
Gerrit Code Review rejected one or more comments because they did not pass validation.
|
Gerrit Code Review rejected one or more comments because they did not pass validation, or
|
||||||
|
because the maximum number of comments per change would be exceeded.
|
||||||
</p>
|
</p>
|
||||||
{call .InboundEmailRejectionFooterHtml /}
|
{call .InboundEmailRejectionFooterHtml /}
|
||||||
{/template}
|
{/template}
|
||||||
|
Reference in New Issue
Block a user