Merge "Enforce size limits for post submit diffs"
diff --git a/java/com/google/gerrit/server/git/validators/CommentCumulativeSizeValidator.java b/java/com/google/gerrit/server/git/validators/CommentCumulativeSizeValidator.java
index d507531..6e640f3 100644
--- a/java/com/google/gerrit/server/git/validators/CommentCumulativeSizeValidator.java
+++ b/java/com/google/gerrit/server/git/validators/CommentCumulativeSizeValidator.java
@@ -23,6 +23,7 @@
 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.ChangeMessagesUtil;
 import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gerrit.server.notedb.ChangeNotes;
 import com.google.inject.Inject;
@@ -34,6 +35,8 @@
  * issues. Note that autogenerated change messages are not subject to validation.
  */
 public class CommentCumulativeSizeValidator implements CommentValidator {
+  public static final int DEFAULT_CUMULATIVE_COMMENT_SIZE_LIMIT = 3 << 20;
+
   private final int maxCumulativeSize;
   private final ChangeNotes.Factory notesFactory;
 
@@ -41,7 +44,9 @@
   CommentCumulativeSizeValidator(
       @GerritServerConfig Config serverConfig, ChangeNotes.Factory notesFactory) {
     this.notesFactory = notesFactory;
-    maxCumulativeSize = serverConfig.getInt("change", "cumulativeCommentSizeLimit", 3 << 20);
+    maxCumulativeSize =
+        serverConfig.getInt(
+            "change", "cumulativeCommentSizeLimit", DEFAULT_CUMULATIVE_COMMENT_SIZE_LIMIT);
   }
 
   @Override
@@ -55,7 +60,13 @@
                     notes.getRobotComments().values().stream())
                 .mapToInt(Comment::getApproximateSize)
                 .sum()
-            + notes.getChangeMessages().stream().mapToInt(cm -> cm.getMessage().length()).sum();
+            + notes.getChangeMessages().stream()
+                // Auto-generated change messages are not counted for the limit. This method is not
+                // called when those change messages are created, but we should also skip them when
+                // counting the size for unrelated messages.
+                .filter(cm -> !ChangeMessagesUtil.isAutogenerated(cm.getTag()))
+                .mapToInt(cm -> cm.getMessage().length())
+                .sum();
     int newCumulativeSize =
         comments.stream().mapToInt(CommentForValidation::getApproximateSize).sum();
     ImmutableList.Builder<CommentValidationFailure> failures = ImmutableList.builder();
diff --git a/java/com/google/gerrit/server/patch/SubmitWithStickyApprovalDiff.java b/java/com/google/gerrit/server/patch/SubmitWithStickyApprovalDiff.java
index 63e0b7a..18d532b 100644
--- a/java/com/google/gerrit/server/patch/SubmitWithStickyApprovalDiff.java
+++ b/java/com/google/gerrit/server/patch/SubmitWithStickyApprovalDiff.java
@@ -30,7 +30,9 @@
 import com.google.gerrit.prettify.common.SparseFileContent;
 import com.google.gerrit.prettify.common.SparseFileContent.Accessor;
 import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gerrit.server.git.LargeObjectException;
+import com.google.gerrit.server.git.validators.CommentCumulativeSizeValidator;
 import com.google.gerrit.server.notedb.ChangeNotes;
 import com.google.gerrit.server.permissions.PermissionBackendException;
 import com.google.gerrit.server.project.InvalidChangeOperationException;
@@ -41,6 +43,7 @@
 import java.util.List;
 import java.util.stream.Collectors;
 import org.eclipse.jgit.diff.Edit;
+import org.eclipse.jgit.lib.Config;
 
 /**
  * This class is used on submit to compute the diff between the latest approved patch-set, and the
@@ -58,15 +61,22 @@
   private final ProjectCache projectCache;
   private final PatchScriptFactory.Factory patchScriptFactoryFactory;
   private final PatchListCache patchListCache;
+  private final int maxCumulativeSize;
 
   @Inject
   SubmitWithStickyApprovalDiff(
       ProjectCache projectCache,
       PatchScriptFactory.Factory patchScriptFactoryFactory,
-      PatchListCache patchListCache) {
+      PatchListCache patchListCache,
+      @GerritServerConfig Config serverConfig) {
     this.projectCache = projectCache;
     this.patchScriptFactoryFactory = patchScriptFactoryFactory;
     this.patchListCache = patchListCache;
+    maxCumulativeSize =
+        serverConfig.getInt(
+            "change",
+            "cumulativeCommentSizeLimit",
+            CommentCumulativeSizeValidator.DEFAULT_CUMULATIVE_COMMENT_SIZE_LIMIT);
   }
 
   public String apply(ChangeNotes notes, CurrentUser currentUser)
@@ -117,6 +127,16 @@
           getDiffForFile(
               notes, currentPatchset.id(), latestApprovedPatchsetId, patchListEntry, currentUser));
     }
+    if (diff.length() > maxCumulativeSize) {
+      // The diff length is not counted as part of the limit (for technical reasons, since we'd
+      // have to call CommentCumulativeSizeValidator), but it's best not to post an extra large
+      // change message here.
+      return String.format(
+          "\n\n%d is the latest approved patch-set.\nThe change was submitted "
+              + "with many unreviewed changes (the diff is too large to show). Please review the "
+              + "diff.",
+          latestApprovedPatchsetId.get());
+    }
     return diff.toString();
   }
 
diff --git a/javatests/com/google/gerrit/acceptance/api/change/SubmitWithStickyApprovalDiffIT.java b/javatests/com/google/gerrit/acceptance/api/change/SubmitWithStickyApprovalDiffIT.java
index eba2634..5ca7310 100644
--- a/javatests/com/google/gerrit/acceptance/api/change/SubmitWithStickyApprovalDiffIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/change/SubmitWithStickyApprovalDiffIT.java
@@ -21,8 +21,10 @@
 import static com.google.gerrit.server.project.testing.TestLabels.value;
 
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Iterables;
 import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.config.GerritConfig;
 import com.google.gerrit.acceptance.testsuite.change.ChangeOperations;
 import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
 import com.google.gerrit.entities.Change;
@@ -30,6 +32,7 @@
 import com.google.gerrit.entities.LabelType;
 import com.google.gerrit.entities.RefNames;
 import com.google.gerrit.extensions.api.changes.ReviewInput;
+import com.google.gerrit.extensions.api.changes.ReviewInput.CommentInput;
 import com.google.gerrit.server.patch.filediff.Edit;
 import com.google.gerrit.server.project.testing.TestLabels;
 import com.google.inject.Inject;
@@ -144,6 +147,56 @@
   }
 
   @Test
+  @GerritConfig(name = "change.cumulativeCommentSizeLimit", value = "1k")
+  public void autoGeneratedPostSubmitDiffIsNotPartOfTheCommentSizeLimit() throws Exception {
+    Change.Id changeId =
+        changeOperations.newChange().project(project).file("file").content("content").create();
+    gApi.changes().id(changeId.get()).current().review(ReviewInput.approve());
+    String content = new String(new char[800]).replace("\0", "a");
+    changeOperations.change(changeId).newPatchset().file("file").content(content).create();
+
+    // Post a submit diff that is almost the cumulativeCommentSizeLimit
+    gApi.changes().id(changeId.get()).current().submit();
+    assertThat(Iterables.getLast(gApi.changes().id(changeId.get()).messages()).message)
+        .doesNotContain("many unreviewed changes");
+
+    // unrelated comment and change message posting works fine, since the post submit diff is not
+    // counted towards the cumulativeCommentSizeLimit for unrelated follow-up comments.
+    // 800 + 400 + 400 > 1k, but 400 + 400 < 1k, hence these comments are accepted (the original
+    // 800 is not counted).
+    String message = new String(new char[400]).replace("\0", "a");
+    ReviewInput reviewInput = new ReviewInput().message(message);
+    CommentInput commentInput = new CommentInput();
+    commentInput.line = 1;
+    commentInput.message = message;
+    commentInput.path = "file";
+    reviewInput.comments = ImmutableMap.of("file", ImmutableList.of(commentInput));
+
+    gApi.changes().id(changeId.get()).current().review(reviewInput);
+  }
+
+  @Test
+  @GerritConfig(name = "change.cumulativeCommentSizeLimit", value = "1k")
+  public void postSubmitDiffCannotBeTooBig() throws Exception {
+    Change.Id changeId =
+        changeOperations.newChange().project(project).file("file").content("content").create();
+    gApi.changes().id(changeId.get()).current().review(ReviewInput.approve());
+
+    String content = new String(new char[1100]).replace("\0", "a");
+
+    changeOperations.change(changeId).newPatchset().file("file").content(content).create();
+
+    // Post submit diff is over the cumulativeCommentSizeLimit, so we shorten the message.
+    gApi.changes().id(changeId.get()).current().submit();
+    assertThat(Iterables.getLast(gApi.changes().id(changeId.get()).messages()).message)
+        .isEqualTo(
+            "Change has been successfully merged\n\n1 is the latest approved patch-set.\nThe "
+                + "change was submitted "
+                + "with many unreviewed changes (the diff is too large to show). Please review the "
+                + "diff.");
+  }
+
+  @Test
   public void diffChangeMessageOnSubmitWithStickyVote_addedFile() throws Exception {
     Change.Id changeId = changeOperations.newChange().project(project).create();
     gApi.changes().id(changeId.get()).current().review(ReviewInput.approve());