Merge "Skip empty lines in emails when using TextParser"
diff --git a/Documentation/user-review-ui.txt b/Documentation/user-review-ui.txt
index 4803d83..5bd8d46 100644
--- a/Documentation/user-review-ui.txt
+++ b/Documentation/user-review-ui.txt
@@ -271,12 +271,14 @@
 
 ** [[delete]]`Delete Change` / `Delete Revision`:
 +
-Deletes the draft change / the currently viewed draft patch set.
+Deletes the change / the currently viewed draft patch set.
 +
-The `Delete Change` / `Delete Revision` buttons are only available if a
-draft patch set is viewed and the user is the change owner or has the
-link:access-control.html#category_delete_drafts[Delete Drafts] access
-right assigned.
+For normal changes, the `Delete Change` button will only be available if the
+user is an administrator and the change hasn't been merged. For draft changes,
+the `Delete Change` / `Delete Revision` buttons will be available if the user
+is the change owner or has the
+link:access-control.html#category_delete_drafts[Delete Drafts] access right
+assigned.
 
 ** [[plugin-actions]]Further actions may be available if plugins are installed.
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/receive/HtmlParser.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/receive/HtmlParser.java
index 9e04f89..163d414 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/receive/HtmlParser.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/receive/HtmlParser.java
@@ -96,7 +96,7 @@
           if (lastEncounteredComment == null && lastEncounteredFileName == null) {
             // Remove quotation line, email signature and
             // "Sent from my xyz device"
-            content = ParserUtil.trimQuotationLine(content);
+            content = ParserUtil.trimQuotation(content);
             // TODO(hiesel) Add more sanitizer
             if (!Strings.isNullOrEmpty(content)) {
               parsedComments.add(
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/receive/ParserUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/receive/ParserUtil.java
index 72eb18a..bfead94 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/receive/ParserUtil.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/receive/ParserUtil.java
@@ -15,6 +15,7 @@
 package com.google.gerrit.server.mail.receive;
 
 import com.google.gerrit.reviewdb.client.Comment;
+import java.util.StringJoiner;
 import java.util.regex.Pattern;
 
 public class ParserUtil {
@@ -24,39 +25,44 @@
               + "(\\.[A-Za-z0-9]+)*(\\.[A-Za-z]{2,})");
 
   /**
-   * Trims the quotation line that email clients add Example: On Sun, Nov 20, 2016 at 10:33 PM,
-   * <gerrit@hiesel.it> wrote:
+   * Trims the quotation that email clients add Example: On Sun, Nov 20, 2016 at 10:33 PM,
+   * <gerrit@gerritcodereview.com> wrote:
    *
    * @param comment Comment parsed from an email.
    * @return Trimmed comment.
    */
-  public static String trimQuotationLine(String comment) {
-    // Identifying the quotation line is hard, as it can be in any language.
-    // We identify this line by it's characteristics: It usually contains a
-    // valid email address, some digits for the date in groups of 1-4 in a row
-    // as well as some characters.
-    StringBuilder b = new StringBuilder();
-    for (String line : comment.split("\n")) {
-      // Count occurrences of digit groups
-      int numConsecutiveDigits = 0;
-      int maxConsecutiveDigits = 0;
-      int numDigitGroups = 0;
-      for (char c : line.toCharArray()) {
-        if (c >= '0' && c <= '9') {
-          numConsecutiveDigits++;
-        } else if (numConsecutiveDigits > 0) {
-          maxConsecutiveDigits = Integer.max(maxConsecutiveDigits, numConsecutiveDigits);
-          numConsecutiveDigits = 0;
-          numDigitGroups++;
-        }
+  public static String trimQuotation(String comment) {
+    StringJoiner j = new StringJoiner("\n");
+    String[] lines = comment.split("\n");
+    for (int i = 0; i < lines.length - 2; i++) {
+      j.add(lines[i]);
+    }
+
+    // Check if the last line contains the full quotation pattern (date + email)
+    String lastLine = lines[lines.length - 1];
+    if (containsQuotationPattern(lastLine)) {
+      if (lines.length > 1) {
+        j.add(lines[lines.length - 2]);
       }
-      if (numDigitGroups < 4
-          || maxConsecutiveDigits > 4
-          || !SIMPLE_EMAIL_PATTERN.matcher(line).find()) {
-        b.append(line);
+      return j.toString().trim();
+    }
+
+    // Check if the second last line + the last line contain the full quotation pattern. This is
+    // necessary, as the quotation line can be split across the last two lines if it gets too long.
+    if (lines.length > 1) {
+      String lastLines = lines[lines.length - 2] + lastLine;
+      if (containsQuotationPattern(lastLines)) {
+        return j.toString().trim();
       }
     }
-    return b.toString().trim();
+
+    // Add the last two lines
+    if (lines.length > 1) {
+      j.add(lines[lines.length - 2]);
+    }
+    j.add(lines[lines.length - 1]);
+
+    return j.toString().trim();
   }
 
   /** Check if string is an inline comment url on a patch set or the base */
@@ -69,4 +75,31 @@
   public static String filePath(String changeUrl, Comment comment) {
     return changeUrl + "/" + comment.key.patchSetId + "/" + comment.key.filename;
   }
+
+  private static boolean containsQuotationPattern(String s) {
+    // Identifying the quotation line is hard, as it can be in any language.
+    // We identify this line by it's characteristics: It usually contains a
+    // valid email address, some digits for the date in groups of 1-4 in a row
+    // as well as some characters.
+
+    // Count occurrences of digit groups
+    int numConsecutiveDigits = 0;
+    int maxConsecutiveDigits = 0;
+    int numDigitGroups = 0;
+    for (char c : s.toCharArray()) {
+      if (c >= '0' && c <= '9') {
+        numConsecutiveDigits++;
+      } else if (numConsecutiveDigits > 0) {
+        maxConsecutiveDigits = Integer.max(maxConsecutiveDigits, numConsecutiveDigits);
+        numConsecutiveDigits = 0;
+        numDigitGroups++;
+      }
+    }
+    if (numDigitGroups < 4 || maxConsecutiveDigits > 4) {
+      return false;
+    }
+
+    // Check if the string contains an email address
+    return SIMPLE_EMAIL_PATTERN.matcher(s).find();
+  }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/receive/TextParser.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/receive/TextParser.java
index 3164645..fa33cc6 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/receive/TextParser.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/receive/TextParser.java
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.server.mail.receive;
 
+import com.google.common.base.Strings;
 import com.google.common.collect.Iterators;
 import com.google.common.collect.PeekingIterator;
 import com.google.gerrit.reviewdb.client.Comment;
@@ -73,7 +74,12 @@
         // This is not a comment, try to advance the file/comment pointers and
         // add previous comment to list if applicable
         if (currentComment != null) {
-          parsedComments.add(currentComment);
+          if (currentComment.type == MailComment.CommentType.CHANGE_MESSAGE) {
+            currentComment.message = ParserUtil.trimQuotation(currentComment.message);
+          }
+          if (!Strings.isNullOrEmpty(currentComment.message)) {
+            parsedComments.add(currentComment);
+          }
           currentComment = null;
         }
 
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/mail/receive/ParserUtilTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/mail/receive/ParserUtilTest.java
new file mode 100644
index 0000000..dfa492c
--- /dev/null
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/mail/receive/ParserUtilTest.java
@@ -0,0 +1,58 @@
+// Copyright (C) 2016 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.mail.receive;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import org.junit.Test;
+
+public class ParserUtilTest {
+  @Test
+  public void trimQuotationLineOnMessageWithoutQuoatationLine() throws Exception {
+    assertThat(ParserUtil.trimQuotation("One line")).isEqualTo("One line");
+    assertThat(ParserUtil.trimQuotation("Two\nlines")).isEqualTo("Two\nlines");
+    assertThat(ParserUtil.trimQuotation("Thr\nee\nlines")).isEqualTo("Thr\nee\nlines");
+  }
+
+  @Test
+  public void trimQuotationLineOnMixedMessages() throws Exception {
+    assertThat(
+            ParserUtil.trimQuotation(
+                "One line\n"
+                    + "On Thu, Feb 9, 2017 at 8:21 AM, ekempin (Gerrit)\n"
+                    + "<noreply-gerritcodereview-qUgXfQecoDLHwp0MldAzig@google.com> wrote:"))
+        .isEqualTo("One line");
+    assertThat(
+            ParserUtil.trimQuotation(
+                "One line\n"
+                    + "On Thu, Feb 9, 2017 at 8:21 AM, ekempin (Gerrit) "
+                    + "<noreply-gerritcodereview-qUgXfQecoDLHwp0MldAzig@google.com> wrote:"))
+        .isEqualTo("One line");
+  }
+
+  @Test
+  public void trimQuotationLineOnMessagesContainingQuoationLine() throws Exception {
+    assertThat(
+            ParserUtil.trimQuotation(
+                "On Thu, Feb 9, 2017 at 8:21 AM, ekempin (Gerrit)\n"
+                    + "<noreply-gerritcodereview-qUgXfQecoDLHwp0MldAzig@google.com> wrote:"))
+        .isEqualTo("");
+    assertThat(
+            ParserUtil.trimQuotation(
+                "On Thu, Feb 9, 2017 at 8:21 AM, ekempin (Gerrit) "
+                    + "<noreply-gerritcodereview-qUgXfQecoDLHwp0MldAzig@google.com> wrote:"))
+        .isEqualTo("");
+  }
+}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/mail/receive/TextParserTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/mail/receive/TextParserTest.java
index 4033897..a98835b 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/mail/receive/TextParserTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/mail/receive/TextParserTest.java
@@ -180,6 +180,8 @@
   private static String newPlaintextBody(
       String changeMessage, String c1, String c2, String c3, String f1, String f2, String fc1) {
     return (changeMessage == null ? "" : changeMessage + "\n")
+        + "On Thu, Feb 9, 2017 at 8:21 AM, ekempin (Gerrit)\n"
+        + "<noreply-gerritcodereview-qUgXfQecoDLHwp0MldAzig@google.com> wrote: \n"
         + "> Foo Bar has posted comments on this change. (  \n"
         + "> "
         + changeURL