Merge "Update Reviewers type in common.ts"
diff --git a/Documentation/rest-api-changes.txt b/Documentation/rest-api-changes.txt
index 7e1cb56..8bd02a8 100644
--- a/Documentation/rest-api-changes.txt
+++ b/Documentation/rest-api-changes.txt
@@ -5585,9 +5585,6 @@
 differences are reported in the result.  Valid values are `IGNORE_NONE`,
 `IGNORE_TRAILING`, `IGNORE_LEADING_AND_TRAILING` or `IGNORE_ALL`.
 
-The `context` parameter can be specified to control the number of lines of surrounding context
-in the diff.  Valid values are `ALL` or number of lines.
-
 [[preview-fix]]
 === Preview fix
 --
@@ -7810,7 +7807,8 @@
 The `RobotCommentInfo` entity contains information about a robot inline
 comment.
 
-`RobotCommentInfo` has the same fields as <<comment-info,CommentInfo>>.
+`RobotCommentInfo` has the same fields as <<comment-info,CommentInfo>>
+except for the `unresolved` field which doesn't exist for robot comments.
 In addition `RobotCommentInfo` has the following fields:
 
 [options="header",cols="1,^1,5"]
@@ -7830,8 +7828,35 @@
 The `RobotCommentInput` entity contains information for creating an inline
 robot comment.
 
-`RobotCommentInput` has the same fields as
-<<robot-comment-info,RobotCommentInfo>>.
+[options="header",cols="1,^1,5"]
+|===========================
+|Field Name    ||Description
+|`path`        ||
+link:#file-id[The file path] for which the inline comment should be added.
+|`side`        |optional|
+The side on which the comment should be added. +
+Allowed values are `REVISION` and `PARENT`. +
+If not set, the default is `REVISION`.
+|`line`        |optional|
+The number of the line for which the comment should be added. +
+`0` if it is a file comment. +
+If neither line nor range is set, a file comment is added. +
+If range is set, this value is ignored in favor of the `end_line` of the range.
+|`range`       |optional|
+The range of the comment as a link:#comment-range[CommentRange]
+entity.
+|`in_reply_to` |optional|
+The URL encoded UUID of the comment to which this comment is a reply.
+|`message`     |optional|
+The comment message.
+|`robot_id`       ||The ID of the robot that generated this comment.
+|`robot_run_id`   ||An ID of the run of the robot.
+|`url`            |optional|URL to more information.
+|`properties`     |optional|Robot specific properties as map that maps arbitrary
+keys to values.
+|`fix_suggestions`|optional|Suggested fixes for this robot comment as a list of
+<<fix-suggestion-info,FixSuggestionInfo>> entities.
+|===========================
 
 [[rule-input]]
 === RuleInput
diff --git a/java/com/google/gerrit/acceptance/testsuite/change/PerPatchsetOperationsImpl.java b/java/com/google/gerrit/acceptance/testsuite/change/PerPatchsetOperationsImpl.java
index b8c841c..eda6c7e 100644
--- a/java/com/google/gerrit/acceptance/testsuite/change/PerPatchsetOperationsImpl.java
+++ b/java/com/google/gerrit/acceptance/testsuite/change/PerPatchsetOperationsImpl.java
@@ -259,9 +259,6 @@
           .ifPresent(range -> newRobotComment.setLineNbrAndRange(null, range));
 
       robotCommentCreation
-          .unresolved()
-          .ifPresent(unresolved -> newRobotComment.unresolved = unresolved);
-      robotCommentCreation
           .parentUuid()
           .ifPresent(parentUuid -> newRobotComment.parentUuid = parentUuid);
       robotCommentCreation.url().ifPresent(url -> newRobotComment.url = url);
diff --git a/java/com/google/gerrit/acceptance/testsuite/change/TestRobotCommentCreation.java b/java/com/google/gerrit/acceptance/testsuite/change/TestRobotCommentCreation.java
index 809190d..558af3f 100644
--- a/java/com/google/gerrit/acceptance/testsuite/change/TestRobotCommentCreation.java
+++ b/java/com/google/gerrit/acceptance/testsuite/change/TestRobotCommentCreation.java
@@ -41,8 +41,6 @@
 
   public abstract Optional<CommentSide> side();
 
-  public abstract Optional<Boolean> unresolved();
-
   public abstract Optional<String> parentUuid();
 
   public abstract Optional<String> tag();
@@ -152,18 +150,6 @@
 
     abstract Builder side(CommentSide side);
 
-    /** Indicates a resolved comment. */
-    public Builder resolved() {
-      return unresolved(false);
-    }
-
-    /** Indicates an unresolved comment. */
-    public Builder unresolved() {
-      return unresolved(true);
-    }
-
-    abstract Builder unresolved(boolean unresolved);
-
     /**
      * UUID of another comment to which this comment is a reply. This comment must have similar
      * attributes (e.g. file, line, side) as the parent comment. The parent comment must be a
diff --git a/java/com/google/gerrit/entities/Comment.java b/java/com/google/gerrit/entities/Comment.java
index e7ac4e4..37b8620 100644
--- a/java/com/google/gerrit/entities/Comment.java
+++ b/java/com/google/gerrit/entities/Comment.java
@@ -231,7 +231,6 @@
   private String revId;
 
   public String serverId;
-  public boolean unresolved;
 
   public Comment(Comment c) {
     this(
@@ -240,15 +239,13 @@
         new Timestamp(c.writtenOn.getTime()),
         c.side,
         c.message,
-        c.serverId,
-        c.unresolved);
+        c.serverId);
     this.lineNbr = c.lineNbr;
     this.realAuthor = c.realAuthor;
     this.parentUuid = c.parentUuid;
     this.range = c.range != null ? new Range(c.range) : null;
     this.tag = c.tag;
     this.revId = c.revId;
-    this.unresolved = c.unresolved;
   }
 
   public Comment(
@@ -257,8 +254,7 @@
       Timestamp writtenOn,
       short side,
       String message,
-      String serverId,
-      boolean unresolved) {
+      String serverId) {
     this.key = key;
     this.author = new Comment.Identity(author);
     this.realAuthor = this.author;
@@ -266,7 +262,6 @@
     this.side = side;
     this.message = message;
     this.serverId = serverId;
-    this.unresolved = unresolved;
   }
 
   public void setLineNbrAndRange(
@@ -334,8 +329,7 @@
         && Objects.equals(range, c.range)
         && Objects.equals(tag, c.tag)
         && Objects.equals(revId, c.revId)
-        && Objects.equals(serverId, c.serverId)
-        && unresolved == c.unresolved;
+        && Objects.equals(serverId, c.serverId);
   }
 
   @Override
@@ -352,8 +346,7 @@
         range,
         tag,
         revId,
-        serverId,
-        unresolved);
+        serverId);
   }
 
   @Override
@@ -373,7 +366,6 @@
         .add("parentUuid", Objects.toString(parentUuid, ""))
         .add("range", Objects.toString(range, ""))
         .add("revId", Objects.toString(revId, ""))
-        .add("tag", Objects.toString(tag, ""))
-        .add("unresolved", unresolved);
+        .add("tag", Objects.toString(tag, ""));
   }
 }
diff --git a/java/com/google/gerrit/entities/HumanComment.java b/java/com/google/gerrit/entities/HumanComment.java
index 8b687cc..50bee8d 100644
--- a/java/com/google/gerrit/entities/HumanComment.java
+++ b/java/com/google/gerrit/entities/HumanComment.java
@@ -15,6 +15,7 @@
 package com.google.gerrit.entities;
 
 import java.sql.Timestamp;
+import java.util.Objects;
 
 /**
  * This class represents inline human comments in NoteDb. This means it determines the JSON format
@@ -27,6 +28,8 @@
  */
 public class HumanComment extends Comment {
 
+  public boolean unresolved;
+
   public HumanComment(
       Key key,
       Account.Id author,
@@ -35,7 +38,8 @@
       String message,
       String serverId,
       boolean unresolved) {
-    super(key, author, writtenOn, side, message, serverId, unresolved);
+    super(key, author, writtenOn, side, message, serverId);
+    this.unresolved = unresolved;
   }
 
   public HumanComment(HumanComment comment) {
@@ -49,19 +53,23 @@
 
   @Override
   public String toString() {
-    return toStringHelper().toString();
+    return toStringHelper().add("unresolved", unresolved).toString();
   }
 
   @Override
-  public boolean equals(Object o) {
-    if (!(o instanceof HumanComment)) {
+  public boolean equals(Object otherObject) {
+    if (!(otherObject instanceof HumanComment)) {
       return false;
     }
-    return super.equals(o);
+    if (!super.equals(otherObject)) {
+      return false;
+    }
+    HumanComment otherComment = (HumanComment) otherObject;
+    return unresolved == otherComment.unresolved;
   }
 
   @Override
   public int hashCode() {
-    return super.hashCode();
+    return Objects.hash(super.hashCode(), unresolved);
   }
 }
diff --git a/java/com/google/gerrit/entities/RobotComment.java b/java/com/google/gerrit/entities/RobotComment.java
index 03ddad5..e2e4114 100644
--- a/java/com/google/gerrit/entities/RobotComment.java
+++ b/java/com/google/gerrit/entities/RobotComment.java
@@ -35,7 +35,7 @@
       String serverId,
       String robotId,
       String robotRunId) {
-    super(key, author, writtenOn, side, message, serverId, false);
+    super(key, author, writtenOn, side, message, serverId);
     this.robotId = robotId;
     this.robotRunId = robotRunId;
   }
diff --git a/java/com/google/gerrit/extensions/api/changes/DraftInput.java b/java/com/google/gerrit/extensions/api/changes/DraftInput.java
index b3c2786..74f626f 100644
--- a/java/com/google/gerrit/extensions/api/changes/DraftInput.java
+++ b/java/com/google/gerrit/extensions/api/changes/DraftInput.java
@@ -19,18 +19,19 @@
 
 public class DraftInput extends Comment {
   public String tag;
+  public Boolean unresolved;
 
   @Override
   public boolean equals(Object o) {
     if (super.equals(o)) {
       DraftInput di = (DraftInput) o;
-      return Objects.equals(tag, di.tag);
+      return Objects.equals(tag, di.tag) && Objects.equals(unresolved, di.unresolved);
     }
     return false;
   }
 
   @Override
   public int hashCode() {
-    return Objects.hash(super.hashCode(), tag);
+    return Objects.hash(super.hashCode(), tag, unresolved);
   }
 }
diff --git a/java/com/google/gerrit/extensions/api/changes/FileApi.java b/java/com/google/gerrit/extensions/api/changes/FileApi.java
index 8d9b2d5..26f9452 100644
--- a/java/com/google/gerrit/extensions/api/changes/FileApi.java
+++ b/java/com/google/gerrit/extensions/api/changes/FileApi.java
@@ -52,7 +52,6 @@
 
   abstract class DiffRequest {
     private String base;
-    private Integer context;
     private Boolean intraline;
     private Whitespace whitespace;
     private OptionalInt parent = OptionalInt.empty();
@@ -64,11 +63,6 @@
       return this;
     }
 
-    public DiffRequest withContext(int context) {
-      this.context = context;
-      return this;
-    }
-
     public DiffRequest withIntraline(boolean intraline) {
       this.intraline = intraline;
       return this;
@@ -88,10 +82,6 @@
       return base;
     }
 
-    public Integer getContext() {
-      return context;
-    }
-
     public Boolean getIntraline() {
       return intraline;
     }
diff --git a/java/com/google/gerrit/extensions/api/changes/ReviewInput.java b/java/com/google/gerrit/extensions/api/changes/ReviewInput.java
index a213ed6..7ecc0a6 100644
--- a/java/com/google/gerrit/extensions/api/changes/ReviewInput.java
+++ b/java/com/google/gerrit/extensions/api/changes/ReviewInput.java
@@ -100,9 +100,11 @@
     PUBLISH_ALL_REVISIONS
   }
 
-  public static class CommentInput extends Comment {}
+  public static class CommentInput extends Comment {
+    public Boolean unresolved;
+  }
 
-  public static class RobotCommentInput extends CommentInput {
+  public static class RobotCommentInput extends Comment {
     public String robotId;
     public String robotRunId;
     public String url;
diff --git a/java/com/google/gerrit/extensions/client/Comment.java b/java/com/google/gerrit/extensions/client/Comment.java
index faa9f69..634992e 100644
--- a/java/com/google/gerrit/extensions/client/Comment.java
+++ b/java/com/google/gerrit/extensions/client/Comment.java
@@ -37,7 +37,6 @@
   public String inReplyTo;
   public Timestamp updated;
   public String message;
-  public Boolean unresolved;
 
   /**
    * Hex commit SHA1 (as 40 characters hex string) of the commit of the patchset to which this
@@ -128,7 +127,6 @@
           && Objects.equals(inReplyTo, c.inReplyTo)
           && Objects.equals(updated, c.updated)
           && Objects.equals(message, c.message)
-          && Objects.equals(unresolved, c.unresolved)
           && Objects.equals(commitId, c.commitId);
     }
     return false;
diff --git a/java/com/google/gerrit/extensions/client/DiffPreferencesInfo.java b/java/com/google/gerrit/extensions/client/DiffPreferencesInfo.java
index ed01a4d..6d52a93 100644
--- a/java/com/google/gerrit/extensions/client/DiffPreferencesInfo.java
+++ b/java/com/google/gerrit/extensions/client/DiffPreferencesInfo.java
@@ -28,12 +28,6 @@
   /** Default line length. */
   public static final int DEFAULT_LINE_LENGTH = 100;
 
-  /** Context setting to display the entire file. */
-  public static final short WHOLE_FILE_CONTEXT = -1;
-
-  /** Typical valid choices for the default context setting. */
-  public static final short[] CONTEXT_CHOICES = {3, 10, 25, 50, 75, 100, WHOLE_FILE_CONTEXT};
-
   public enum Whitespace {
     IGNORE_NONE,
     IGNORE_TRAILING,
diff --git a/java/com/google/gerrit/extensions/common/CommentInfo.java b/java/com/google/gerrit/extensions/common/CommentInfo.java
index 19d721b..fcce2b3 100644
--- a/java/com/google/gerrit/extensions/common/CommentInfo.java
+++ b/java/com/google/gerrit/extensions/common/CommentInfo.java
@@ -22,6 +22,7 @@
   public AccountInfo author;
   public String tag;
   public String changeMessageId;
+  public Boolean unresolved;
 
   /**
    * A list of {@link ContextLineInfo}, that is, a list of pairs of {line_num, line_text} of the
@@ -33,13 +34,15 @@
   public boolean equals(Object o) {
     if (super.equals(o)) {
       CommentInfo ci = (CommentInfo) o;
-      return Objects.equals(author, ci.author) && Objects.equals(tag, ci.tag);
+      return Objects.equals(author, ci.author)
+          && Objects.equals(tag, ci.tag)
+          && Objects.equals(unresolved, ci.unresolved);
     }
     return false;
   }
 
   @Override
   public int hashCode() {
-    return Objects.hash(super.hashCode(), author, tag);
+    return Objects.hash(super.hashCode(), author, tag, unresolved);
   }
 }
diff --git a/java/com/google/gerrit/prettify/common/EditHunk.java b/java/com/google/gerrit/prettify/common/EditHunk.java
new file mode 100644
index 0000000..68cb796
--- /dev/null
+++ b/java/com/google/gerrit/prettify/common/EditHunk.java
@@ -0,0 +1,92 @@
+// Copyright (C) 2009 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.prettify.common;
+
+import java.util.List;
+import org.eclipse.jgit.diff.Edit;
+
+/**
+ * This is a legacy class. It was only simplified but not improved regarding readability or code
+ * health. Feel free to completely rewrite it or replace it with some other, better code.
+ */
+public class EditHunk {
+  private final List<Edit> edits;
+
+  private int curIdx;
+  private Edit curEdit;
+
+  private int aCur;
+  private int bCur;
+  private final int aEnd;
+  private final int bEnd;
+
+  public EditHunk(List<Edit> edits, int aSize, int bSize) {
+    this.edits = edits;
+
+    curIdx = 0;
+    curEdit = edits.get(curIdx);
+
+    aCur = 0;
+    bCur = 0;
+    aEnd = aSize;
+    bEnd = bSize;
+  }
+
+  public int getCurA() {
+    return aCur;
+  }
+
+  public int getCurB() {
+    return bCur;
+  }
+
+  public void incA() {
+    aCur++;
+  }
+
+  public void incB() {
+    bCur++;
+  }
+
+  public void incBoth() {
+    incA();
+    incB();
+  }
+
+  public boolean isUnmodifiedLine() {
+    return !isDeletedA() && !isInsertedB();
+  }
+
+  public boolean isDeletedA() {
+    return curEdit.getBeginA() <= aCur && aCur < curEdit.getEndA();
+  }
+
+  public boolean isInsertedB() {
+    return curEdit.getBeginB() <= bCur && bCur < curEdit.getEndB();
+  }
+
+  public boolean next() {
+    if (!in(curEdit)) {
+      if (curIdx < edits.size() - 1) {
+        curEdit = edits.get(++curIdx);
+      }
+    }
+    return aCur < aEnd || bCur < bEnd;
+  }
+
+  private boolean in(Edit edit) {
+    return aCur < edit.getEndA() || bCur < edit.getEndB();
+  }
+}
diff --git a/java/com/google/gerrit/prettify/common/EditList.java b/java/com/google/gerrit/prettify/common/EditList.java
deleted file mode 100644
index 172a346..0000000
--- a/java/com/google/gerrit/prettify/common/EditList.java
+++ /dev/null
@@ -1,174 +0,0 @@
-// Copyright (C) 2009 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.prettify.common;
-
-import java.util.Iterator;
-import java.util.List;
-import org.eclipse.jgit.diff.Edit;
-
-public class EditList {
-  private final List<Edit> edits;
-  private final int context;
-  private final int aSize;
-  private final int bSize;
-
-  public EditList(final List<Edit> edits, int contextLines, int aSize, int bSize) {
-    this.edits = edits;
-    this.context = contextLines;
-    this.aSize = aSize;
-    this.bSize = bSize;
-  }
-
-  public List<Edit> getEdits() {
-    return edits;
-  }
-
-  public Iterable<Hunk> getHunks() {
-    return () ->
-        new Iterator<Hunk>() {
-          private int curIdx;
-
-          @Override
-          public boolean hasNext() {
-            return curIdx < edits.size();
-          }
-
-          @Override
-          public Hunk next() {
-            final int c = curIdx;
-            final int e = findCombinedEnd(c);
-            curIdx = e + 1;
-            return new Hunk(c, e);
-          }
-
-          @Override
-          public void remove() {
-            throw new UnsupportedOperationException();
-          }
-        };
-  }
-
-  private int findCombinedEnd(int i) {
-    int end = i + 1;
-    while (end < edits.size() && (combineA(end) || combineB(end))) {
-      end++;
-    }
-    return end - 1;
-  }
-
-  private boolean combineA(int i) {
-    final Edit s = edits.get(i);
-    final Edit e = edits.get(i - 1);
-    // + 1 to prevent '... skipping 1 common line ...' messages.
-    return s.getBeginA() - e.getEndA() <= 2 * context + 1;
-  }
-
-  private boolean combineB(int i) {
-    final int s = edits.get(i).getBeginB();
-    final int e = edits.get(i - 1).getEndB();
-    // + 1 to prevent '... skipping 1 common line ...' messages.
-    return s - e <= 2 * context + 1;
-  }
-
-  public class Hunk {
-    private int curIdx;
-    private Edit curEdit;
-    private final int endIdx;
-    private final Edit endEdit;
-
-    private int aCur;
-    private int bCur;
-    private final int aEnd;
-    private final int bEnd;
-
-    private Hunk(int ci, int ei) {
-      curIdx = ci;
-      endIdx = ei;
-      curEdit = edits.get(curIdx);
-      endEdit = edits.get(endIdx);
-
-      aCur = Math.max(0, curEdit.getBeginA() - context);
-      bCur = Math.max(0, curEdit.getBeginB() - context);
-      aEnd = Math.min(aSize, endEdit.getEndA() + context);
-      bEnd = Math.min(bSize, endEdit.getEndB() + context);
-    }
-
-    public int getCurA() {
-      return aCur;
-    }
-
-    public int getCurB() {
-      return bCur;
-    }
-
-    public Edit getCurEdit() {
-      return curEdit;
-    }
-
-    public int getEndA() {
-      return aEnd;
-    }
-
-    public int getEndB() {
-      return bEnd;
-    }
-
-    public void incA() {
-      aCur++;
-    }
-
-    public void incB() {
-      bCur++;
-    }
-
-    public void incBoth() {
-      incA();
-      incB();
-    }
-
-    public boolean isStartOfFile() {
-      return aCur == 0 && bCur == 0;
-    }
-
-    public boolean isContextLine() {
-      return !isModifiedLine();
-    }
-
-    public boolean isDeletedA() {
-      return curEdit.getBeginA() <= aCur && aCur < curEdit.getEndA();
-    }
-
-    public boolean isInsertedB() {
-      return curEdit.getBeginB() <= bCur && bCur < curEdit.getEndB();
-    }
-
-    public boolean isModifiedLine() {
-      return isDeletedA() || isInsertedB();
-    }
-
-    public boolean next() {
-      if (!in(curEdit)) {
-        if (curIdx < endIdx) {
-          curEdit = edits.get(++curIdx);
-        }
-      }
-      return aCur < aEnd || bCur < bEnd;
-    }
-
-    private boolean in(Edit edit) {
-      return aCur < edit.getEndA() || bCur < edit.getEndB();
-    }
-  }
-}
diff --git a/java/com/google/gerrit/server/api/changes/FileApiImpl.java b/java/com/google/gerrit/server/api/changes/FileApiImpl.java
index c506b2e..cf9f243 100644
--- a/java/com/google/gerrit/server/api/changes/FileApiImpl.java
+++ b/java/com/google/gerrit/server/api/changes/FileApiImpl.java
@@ -123,9 +123,6 @@
     if (r.getBase() != null) {
       getDiff.setBase(r.getBase());
     }
-    if (r.getContext() != null) {
-      getDiff.setContext(r.getContext());
-    }
     if (r.getIntraline() != null) {
       getDiff.setIntraline(r.getIntraline());
     }
diff --git a/java/com/google/gerrit/server/change/CommentThread.java b/java/com/google/gerrit/server/change/CommentThread.java
index 7b729d2..0265f60 100644
--- a/java/com/google/gerrit/server/change/CommentThread.java
+++ b/java/com/google/gerrit/server/change/CommentThread.java
@@ -17,9 +17,11 @@
 import com.google.auto.value.AutoValue;
 import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Iterables;
+import com.google.common.collect.Streams;
 import com.google.gerrit.entities.Comment;
+import com.google.gerrit.entities.HumanComment;
 import java.util.List;
+import java.util.Optional;
 
 /**
  * Representation of a comment thread.
@@ -37,7 +39,14 @@
 
   /** Whether the whole thread is considered as unresolved. */
   public boolean unresolved() {
-    return Iterables.getLast(comments()).unresolved;
+    Optional<HumanComment> lastHumanComment =
+        Streams.findLast(
+            comments().stream()
+                .filter(HumanComment.class::isInstance)
+                .map(HumanComment.class::cast));
+    // We often use false == null for boolean fields. It's also a safe fall-back if no human comment
+    // is part of the thread.
+    return lastHumanComment.map(comment -> comment.unresolved).orElse(false);
   }
 
   public static <T extends Comment> Builder<T> builder() {
diff --git a/java/com/google/gerrit/server/logging/LoggingContext.java b/java/com/google/gerrit/server/logging/LoggingContext.java
index 36c7e9e..671c224 100644
--- a/java/com/google/gerrit/server/logging/LoggingContext.java
+++ b/java/com/google/gerrit/server/logging/LoggingContext.java
@@ -42,6 +42,15 @@
   private static final ThreadLocal<MutableTags> tags = new ThreadLocal<>();
   private static final ThreadLocal<Boolean> forceLogging = new ThreadLocal<>();
   private static final ThreadLocal<Boolean> performanceLogging = new ThreadLocal<>();
+
+  /**
+   * When copying the logging context to a new thread we need to ensure that the performance log
+   * records that are added in the new thread are added to the same {@link
+   * MutablePerformanceLogRecords} instance (see {@link LoggingContextAwareRunnable} and {@link
+   * LoggingContextAwareCallable}). This is important since performance log records are processed
+   * only at the end of the request and performance log records that are created in another thread
+   * should not get lost.
+   */
   private static final ThreadLocal<MutablePerformanceLogRecords> performanceLogRecords =
       new ThreadLocal<>();
 
@@ -57,11 +66,6 @@
       return runnable;
     }
 
-    // Pass the MutablePerformanceLogRecords instance into the LoggingContextAwareRunnable
-    // constructor so that performance log records that are created in the wrapped runnable are
-    // added to this MutablePerformanceLogRecords instance. This is important since performance
-    // log records are processed only at the end of the request and performance log records that
-    // are created in another thread should not get lost.
     return new LoggingContextAwareRunnable(
         runnable, getInstance().getMutablePerformanceLogRecords());
   }
@@ -71,11 +75,6 @@
       return callable;
     }
 
-    // Pass the MutablePerformanceLogRecords instance into the LoggingContextAwareCallable
-    // constructor so that performance log records that are created in the wrapped runnable are
-    // added to this MutablePerformanceLogRecords instance. This is important since performance
-    // log records are processed only at the end of the request and performance log records that
-    // are created in another thread should not get lost.
     return new LoggingContextAwareCallable<>(
         callable, getInstance().getMutablePerformanceLogRecords());
   }
@@ -233,12 +232,7 @@
    * <p><strong>Attention:</strong> The passed in {@link MutablePerformanceLogRecords} instance is
    * directly stored in the logging context.
    *
-   * <p>This method is intended to be only used when the logging context is copied to a new thread
-   * to ensure that the performance log records that are added in the new thread are added to the
-   * same {@link MutablePerformanceLogRecords} instance (see {@link LoggingContextAwareRunnable} and
-   * {@link LoggingContextAwareCallable}). This is important since performance log records are
-   * processed only at the end of the request and performance log records that are created in
-   * another thread should not get lost.
+   * <p>This method is intended to be only used when the logging context is copied to a new thread.
    *
    * @param mutablePerformanceLogRecords the {@link MutablePerformanceLogRecords} instance in which
    *     performance log records should be stored
diff --git a/java/com/google/gerrit/server/logging/LoggingContextAwareCallable.java b/java/com/google/gerrit/server/logging/LoggingContextAwareCallable.java
index d2701d7..1adee1b 100644
--- a/java/com/google/gerrit/server/logging/LoggingContextAwareCallable.java
+++ b/java/com/google/gerrit/server/logging/LoggingContextAwareCallable.java
@@ -75,13 +75,6 @@
     loggingCtx.setTags(tags);
     loggingCtx.forceLogging(forceLogging);
     loggingCtx.performanceLogging(performanceLogging);
-
-    // For the performance log records use the {@link MutablePerformanceLogRecords} instance from
-    // the logging context of the calling thread in the logging context of the new thread. This way
-    // performance log records that are created from the new thread are available from the logging
-    // context of the calling thread. This is important since performance log records are processed
-    // only at the end of the request and performance log records that are created in another thread
-    // should not get lost.
     loggingCtx.setMutablePerformanceLogRecords(mutablePerformanceLogRecords);
     try {
       return callable.call();
diff --git a/java/com/google/gerrit/server/logging/LoggingContextAwareRunnable.java b/java/com/google/gerrit/server/logging/LoggingContextAwareRunnable.java
index 23162b1..d0559cc 100644
--- a/java/com/google/gerrit/server/logging/LoggingContextAwareRunnable.java
+++ b/java/com/google/gerrit/server/logging/LoggingContextAwareRunnable.java
@@ -98,13 +98,6 @@
     loggingCtx.setTags(tags);
     loggingCtx.forceLogging(forceLogging);
     loggingCtx.performanceLogging(performanceLogging);
-
-    // For the performance log records use the {@link MutablePerformanceLogRecords} instance from
-    // the logging context of the calling thread in the logging context of the new thread. This way
-    // performance log records that are created from the new thread are available from the logging
-    // context of the calling thread. This is important since performance log records are processed
-    // only at the end of the request and performance log records that are created in another thread
-    // should not get lost.
     loggingCtx.setMutablePerformanceLogRecords(mutablePerformanceLogRecords);
     try {
       runnable.run();
diff --git a/java/com/google/gerrit/server/patch/DiffContentCalculator.java b/java/com/google/gerrit/server/patch/DiffContentCalculator.java
index 53f7ca6..a387da6 100644
--- a/java/com/google/gerrit/server/patch/DiffContentCalculator.java
+++ b/java/com/google/gerrit/server/patch/DiffContentCalculator.java
@@ -14,28 +14,19 @@
 
 package com.google.gerrit.server.patch;
 
-import static java.util.Comparator.comparing;
-
 import com.google.common.collect.ImmutableList;
-import com.google.gerrit.common.data.CommentDetail;
-import com.google.gerrit.entities.Comment;
 import com.google.gerrit.extensions.client.DiffPreferencesInfo;
 import com.google.gerrit.extensions.client.DiffPreferencesInfo.Whitespace;
 import com.google.gerrit.jgit.diff.ReplaceEdit;
-import com.google.gerrit.prettify.common.EditList;
+import com.google.gerrit.prettify.common.EditHunk;
 import com.google.gerrit.prettify.common.SparseFileContent;
 import com.google.gerrit.prettify.common.SparseFileContentBuilder;
-import java.util.Comparator;
 import java.util.List;
 import java.util.Optional;
 import org.eclipse.jgit.diff.Edit;
 
 /** Collects all lines and their content to be displayed in diff view. */
 class DiffContentCalculator {
-  private static final int MAX_CONTEXT = 5000000;
-
-  private static final Comparator<Edit> EDIT_SORT = comparing(Edit::getBeginA);
-
   private final DiffPreferencesInfo diffPrefs;
 
   DiffContentCalculator(DiffPreferencesInfo diffPrefs) {
@@ -60,13 +51,11 @@
    * @param srcA Original text content
    * @param srcB New text content
    * @param edits List of edits which was applied to srcA to produce srcB
-   * @param comments Existing comments for srcA and srcB
    * @return an instance of {@link DiffCalculatorResult}.
    */
   DiffCalculatorResult calculateDiffContent(
-      TextSource srcA, TextSource srcB, ImmutableList<Edit> edits, CommentDetail comments) {
-    int context = getContext();
-    if (srcA.src == srcB.src && srcA.size() <= context && edits.isEmpty()) {
+      TextSource srcA, TextSource srcB, ImmutableList<Edit> edits) {
+    if (srcA.src == srcB.src && edits.isEmpty()) {
       // Odd special case; the files are identical (100% rename or copy)
       // and the user has asked for context that is larger than the file.
       // Send them the entire file, with an empty edit after the last line.
@@ -80,43 +69,13 @@
       Edit emptyEdit = new Edit(srcA.size(), srcA.size());
       return new DiffCalculatorResult(diffContent, ImmutableList.of(emptyEdit));
     }
-    ImmutableList.Builder<Edit> builder = ImmutableList.builder();
+    ImmutableList<Edit> sortedEdits = correctForDifferencesInNewlineAtEnd(srcA, srcB, edits);
 
-    builder.addAll(correctForDifferencesInNewlineAtEnd(srcA, srcB, edits));
-
-    boolean nonsortedEdits = false;
-    if (comments != null) {
-      ImmutableList<Edit> commentEdits = ensureCommentsVisible(comments, edits);
-      builder.addAll(commentEdits);
-      nonsortedEdits = !commentEdits.isEmpty();
-    }
-
-    ImmutableList<Edit> sortedEdits = builder.build();
-    if (nonsortedEdits) {
-      sortedEdits = ImmutableList.sortedCopyOf(EDIT_SORT, sortedEdits);
-    }
-
-    // In order to expand the skipped common lines or syntax highlight the
-    // file properly we need to give the client the complete file contents.
-    // So force our context temporarily to the complete file size.
-    //
     DiffContent diffContent =
-        packContent(
-            srcA,
-            srcB,
-            diffPrefs.ignoreWhitespace != Whitespace.IGNORE_NONE,
-            sortedEdits,
-            MAX_CONTEXT);
+        packContent(srcA, srcB, diffPrefs.ignoreWhitespace != Whitespace.IGNORE_NONE, sortedEdits);
     return new DiffCalculatorResult(diffContent, sortedEdits);
   }
 
-  private int getContext() {
-    if (diffPrefs.context == DiffPreferencesInfo.WHOLE_FILE_CONTEXT) {
-      return MAX_CONTEXT;
-    }
-    return Math.min(diffPrefs.context, MAX_CONTEXT);
-  }
-
   private ImmutableList<Edit> correctForDifferencesInNewlineAtEnd(
       TextSource a, TextSource b, ImmutableList<Edit> edits) {
     // a.src.size() is the size ignoring a newline at the end whereas a.size() considers it.
@@ -205,128 +164,14 @@
     return a.src.isMissingNewlineAtEnd() && !b.src.isMissingNewlineAtEnd();
   }
 
-  private ImmutableList<Edit> ensureCommentsVisible(
-      CommentDetail comments, ImmutableList<Edit> edits) {
-    if (comments.getCommentsA().isEmpty() && comments.getCommentsB().isEmpty()) {
-      // No comments, no additional dummy edits are required.
-      //
-      return ImmutableList.of();
-    }
-
-    // Construct empty Edit blocks around each location where a comment is.
-    // This will force the later packContent method to include the regions
-    // containing comments, potentially combining those regions together if
-    // they have overlapping contexts. UI renders will also be able to make
-    // correct hunks from this, but because the Edit is empty they will not
-    // style it specially.
-    //
-    final ImmutableList.Builder<Edit> commmentEdits = ImmutableList.builder();
-    int lastLine;
-
-    lastLine = -1;
-    for (Comment c : comments.getCommentsA()) {
-      final int a = c.lineNbr;
-      if (lastLine != a) {
-        final int b = mapA2B(a - 1, edits);
-        if (0 <= b) {
-          getNewEditForComment(edits, new Edit(a - 1, b)).ifPresent(commmentEdits::add);
-        }
-        lastLine = a;
-      }
-    }
-
-    lastLine = -1;
-    for (Comment c : comments.getCommentsB()) {
-      int b = c.lineNbr;
-      if (lastLine != b) {
-        final int a = mapB2A(b - 1, edits);
-        if (0 <= a) {
-          getNewEditForComment(edits, new Edit(a, b - 1)).ifPresent(commmentEdits::add);
-        }
-        lastLine = b;
-      }
-    }
-    return commmentEdits.build();
-  }
-
-  private Optional<Edit> getNewEditForComment(ImmutableList<Edit> edits, Edit toAdd) {
-    final int a = toAdd.getBeginA();
-    final int b = toAdd.getBeginB();
-    for (Edit e : edits) {
-      if (e.getBeginA() <= a && a <= e.getEndA()) {
-        return Optional.empty();
-      }
-      if (e.getBeginB() <= b && b <= e.getEndB()) {
-        return Optional.empty();
-      }
-    }
-    return Optional.of(toAdd);
-  }
-
-  private int mapA2B(int a, ImmutableList<Edit> edits) {
-    if (edits.isEmpty()) {
-      // Magic special case of an unmodified file.
-      //
-      return a;
-    }
-
-    for (int i = 0; i < edits.size(); i++) {
-      final Edit e = edits.get(i);
-      if (a < e.getBeginA()) {
-        if (i == 0) {
-          // Special case of context at start of file.
-          //
-          return a;
-        }
-        return e.getBeginB() - (e.getBeginA() - a);
-      }
-      if (e.getBeginA() <= a && a <= e.getEndA()) {
-        return -1;
-      }
-    }
-
-    final Edit last = edits.get(edits.size() - 1);
-    return last.getEndB() + (a - last.getEndA());
-  }
-
-  private int mapB2A(int b, ImmutableList<Edit> edits) {
-    if (edits.isEmpty()) {
-      // Magic special case of an unmodified file.
-      //
-      return b;
-    }
-
-    for (int i = 0; i < edits.size(); i++) {
-      final Edit e = edits.get(i);
-      if (b < e.getBeginB()) {
-        if (i == 0) {
-          // Special case of context at start of file.
-          //
-          return b;
-        }
-        return e.getBeginA() - (e.getBeginB() - b);
-      }
-      if (e.getBeginB() <= b && b <= e.getEndB()) {
-        return -1;
-      }
-    }
-
-    final Edit last = edits.get(edits.size() - 1);
-    return last.getEndA() + (b - last.getEndB());
-  }
-
   private DiffContent packContent(
-      TextSource a,
-      TextSource b,
-      boolean ignoredWhitespace,
-      ImmutableList<Edit> edits,
-      int context) {
+      TextSource a, TextSource b, boolean ignoredWhitespace, ImmutableList<Edit> edits) {
     SparseFileContentBuilder diffA = new SparseFileContentBuilder(a.size());
     SparseFileContentBuilder diffB = new SparseFileContentBuilder(b.size());
-    EditList list = new EditList(edits, context, a.size(), b.size());
-    for (EditList.Hunk hunk : list.getHunks()) {
+    if (!edits.isEmpty()) {
+      EditHunk hunk = new EditHunk(edits, a.size(), b.size());
       while (hunk.next()) {
-        if (hunk.isContextLine()) {
+        if (hunk.isUnmodifiedLine()) {
           String lineA = a.getSourceLine(hunk.getCurA());
           diffA.addLine(hunk.getCurA(), lineA);
 
diff --git a/java/com/google/gerrit/server/patch/PatchScriptBuilder.java b/java/com/google/gerrit/server/patch/PatchScriptBuilder.java
index 9b8409d..c6f7acf 100644
--- a/java/com/google/gerrit/server/patch/PatchScriptBuilder.java
+++ b/java/com/google/gerrit/server/patch/PatchScriptBuilder.java
@@ -18,7 +18,6 @@
 
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
-import com.google.gerrit.common.data.CommentDetail;
 import com.google.gerrit.common.data.PatchScript;
 import com.google.gerrit.common.data.PatchScript.DisplayMethod;
 import com.google.gerrit.entities.FixReplacement;
@@ -69,8 +68,7 @@
     intralineDiffCalculator = calculator;
   }
 
-  PatchScript toPatchScript(
-      Repository git, PatchList list, PatchListEntry content, CommentDetail comments)
+  PatchScript toPatchScript(Repository git, PatchList list, PatchListEntry content)
       throws IOException {
 
     PatchFileChange change =
@@ -86,7 +84,7 @@
     ResolvedSides sides =
         resolveSides(
             git, sidesResolver, oldName(change), newName(change), list.getOldId(), list.getNewId());
-    return build(sides.a, sides.b, change, comments);
+    return build(sides.a, sides.b, change);
   }
 
   private ResolvedSides resolveSides(
@@ -136,7 +134,7 @@
             ChangeType.MODIFIED,
             PatchType.UNIFIED);
 
-    return build(a, b, change, null);
+    return build(a, b, change);
   }
 
   private PatchSide resolveSideA(
@@ -147,9 +145,7 @@
     }
   }
 
-  private PatchScript build(
-      PatchSide a, PatchSide b, PatchFileChange content, CommentDetail comments) {
-
+  private PatchScript build(PatchSide a, PatchSide b, PatchFileChange content) {
     ImmutableList<Edit> contentEdits = content.getEdits();
     ImmutableSet<Edit> editsDueToRebase = content.getEditsDueToRebase();
 
@@ -163,8 +159,7 @@
     ImmutableList<Edit> finalEdits = intralineResult.edits.orElse(contentEdits);
     DiffContentCalculator calculator = new DiffContentCalculator(diffPrefs);
     DiffCalculatorResult diffCalculatorResult =
-        calculator.calculateDiffContent(
-            new TextSource(a.src), new TextSource(b.src), finalEdits, comments);
+        calculator.calculateDiffContent(new TextSource(a.src), new TextSource(b.src), finalEdits);
 
     return new PatchScript(
         content.getChangeType(),
diff --git a/java/com/google/gerrit/server/patch/PatchScriptFactory.java b/java/com/google/gerrit/server/patch/PatchScriptFactory.java
index 30930ec..60a0688 100644
--- a/java/com/google/gerrit/server/patch/PatchScriptFactory.java
+++ b/java/com/google/gerrit/server/patch/PatchScriptFactory.java
@@ -20,19 +20,13 @@
 import com.google.common.collect.ImmutableList;
 import com.google.common.flogger.FluentLogger;
 import com.google.gerrit.common.Nullable;
-import com.google.gerrit.common.data.CommentDetail;
 import com.google.gerrit.common.data.PatchScript;
-import com.google.gerrit.entities.Account;
 import com.google.gerrit.entities.Change;
-import com.google.gerrit.entities.HumanComment;
-import com.google.gerrit.entities.Patch.ChangeType;
 import com.google.gerrit.entities.PatchSet;
 import com.google.gerrit.entities.Project;
 import com.google.gerrit.extensions.client.DiffPreferencesInfo;
 import com.google.gerrit.extensions.client.DiffPreferencesInfo.Whitespace;
 import com.google.gerrit.extensions.restapi.AuthException;
-import com.google.gerrit.server.CommentsUtil;
-import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.PatchSetUtil;
 import com.google.gerrit.server.edit.ChangeEdit;
 import com.google.gerrit.server.edit.ChangeEditUtil;
@@ -84,7 +78,6 @@
   private final PatchSetUtil psUtil;
   private final Provider<PatchScriptBuilder> builderFactory;
   private final PatchListCache patchListCache;
-  private final CommentsUtil commentsUtil;
 
   private final String fileName;
   @Nullable private final PatchSet.Id psa;
@@ -92,12 +85,10 @@
   private final PatchSet.Id psb;
   private final DiffPreferencesInfo diffPrefs;
   private final ChangeEditUtil editReader;
-  private final Provider<CurrentUser> userProvider;
   private final PermissionBackend permissionBackend;
   private final ProjectCache projectCache;
 
   private final Change.Id changeId;
-  private boolean loadComments = true;
 
   private ChangeNotes notes;
 
@@ -107,9 +98,7 @@
       PatchSetUtil psUtil,
       Provider<PatchScriptBuilder> builderFactory,
       PatchListCache patchListCache,
-      CommentsUtil commentsUtil,
       ChangeEditUtil editReader,
-      Provider<CurrentUser> userProvider,
       PermissionBackend permissionBackend,
       ProjectCache projectCache,
       @Assisted ChangeNotes notes,
@@ -122,9 +111,7 @@
     this.builderFactory = builderFactory;
     this.patchListCache = patchListCache;
     this.notes = notes;
-    this.commentsUtil = commentsUtil;
     this.editReader = editReader;
-    this.userProvider = userProvider;
     this.permissionBackend = permissionBackend;
     this.projectCache = projectCache;
 
@@ -143,9 +130,7 @@
       PatchSetUtil psUtil,
       Provider<PatchScriptBuilder> builderFactory,
       PatchListCache patchListCache,
-      CommentsUtil commentsUtil,
       ChangeEditUtil editReader,
-      Provider<CurrentUser> userProvider,
       PermissionBackend permissionBackend,
       ProjectCache projectCache,
       @Assisted ChangeNotes notes,
@@ -158,9 +143,7 @@
     this.builderFactory = builderFactory;
     this.patchListCache = patchListCache;
     this.notes = notes;
-    this.commentsUtil = commentsUtil;
     this.editReader = editReader;
-    this.userProvider = userProvider;
     this.permissionBackend = permissionBackend;
     this.projectCache = projectCache;
 
@@ -174,10 +157,6 @@
     checkArgument(parentNum >= 0, "parentNum must be >= 0");
   }
 
-  public void setLoadComments(boolean load) {
-    loadComments = load;
-  }
-
   @Override
   public PatchScript call()
       throws LargeObjectException, AuthException, InvalidChangeOperationException, IOException,
@@ -218,9 +197,7 @@
         final PatchScriptBuilder b = newBuilder();
         final PatchListEntry content = list.get(fileName);
 
-        Optional<CommentDetail> comments = loadComments(content, changeEdit);
-
-        return b.toPatchScript(git, list, content, comments.orElse(null));
+        return b.toPatchScript(git, list, content);
       } catch (PatchListNotAvailableException e) {
         throw new NoSuchChangeException(changeId, e);
       } catch (IOException e) {
@@ -238,14 +215,6 @@
     }
   }
 
-  private Optional<CommentDetail> loadComments(PatchListEntry content, boolean changeEdit) {
-    if (!loadComments) {
-      return Optional.empty();
-    }
-    return new CommentsLoader(psa, psb, userProvider, notes, commentsUtil)
-        .load(changeEdit, content.getChangeType(), content.getOldName(), content.getNewName());
-  }
-
   private Optional<ObjectId> getAId() {
     if (psa == null) {
       return Optional.empty();
@@ -300,99 +269,6 @@
     }
   }
 
-  private static class CommentsLoader {
-    private final PatchSet.Id psa;
-    private final PatchSet.Id psb;
-    private final Provider<CurrentUser> userProvider;
-    private final ChangeNotes notes;
-    private final CommentsUtil commentsUtil;
-    private CommentDetail comments;
-
-    CommentsLoader(
-        PatchSet.Id psa,
-        PatchSet.Id psb,
-        Provider<CurrentUser> userProvider,
-        ChangeNotes notes,
-        CommentsUtil commentsUtil) {
-      this.psa = psa;
-      this.psb = psb;
-      this.userProvider = userProvider;
-      this.notes = notes;
-      this.commentsUtil = commentsUtil;
-    }
-
-    private Optional<CommentDetail> load(
-        boolean changeEdit, ChangeType changeType, String oldName, String newName) {
-      // TODO: Implement this method with CommentDetailBuilder (this class doesn't exists yet).
-      // This is a legacy code which create final object and populate it and then returns it.
-      if (changeEdit) {
-        return Optional.empty();
-      }
-
-      comments = new CommentDetail(psa, psb);
-      switch (changeType) {
-        case ADDED:
-        case MODIFIED:
-          loadPublished(newName);
-          break;
-
-        case DELETED:
-          loadPublished(newName);
-          break;
-
-        case COPIED:
-        case RENAMED:
-          if (psa != null) {
-            loadPublished(oldName);
-          }
-          loadPublished(newName);
-          break;
-
-        case REWRITE:
-          break;
-      }
-
-      CurrentUser user = userProvider.get();
-      if (user.isIdentifiedUser()) {
-        Account.Id me = user.getAccountId();
-        switch (changeType) {
-          case ADDED:
-          case MODIFIED:
-            loadDrafts(me, newName);
-            break;
-
-          case DELETED:
-            loadDrafts(me, newName);
-            break;
-
-          case COPIED:
-          case RENAMED:
-            if (psa != null) {
-              loadDrafts(me, oldName);
-            }
-            loadDrafts(me, newName);
-            break;
-
-          case REWRITE:
-            break;
-        }
-      }
-      return Optional.of(comments);
-    }
-
-    private void loadPublished(String file) {
-      for (HumanComment c : commentsUtil.publishedByChangeFile(notes, file)) {
-        comments.include(notes.getChangeId(), c);
-      }
-    }
-
-    private void loadDrafts(Account.Id me, String file) {
-      for (HumanComment c : commentsUtil.draftByChangeFileAuthor(notes, file, me)) {
-        comments.include(notes.getChangeId(), c);
-      }
-    }
-  }
-
   private static class IntraLineDiffCalculator
       implements PatchScriptBuilder.IntraLineDiffCalculator {
 
diff --git a/java/com/google/gerrit/server/restapi/change/CommentJson.java b/java/com/google/gerrit/server/restapi/change/CommentJson.java
index 67049e8..4de9b63 100644
--- a/java/com/google/gerrit/server/restapi/change/CommentJson.java
+++ b/java/com/google/gerrit/server/restapi/change/CommentJson.java
@@ -166,7 +166,6 @@
       r.updated = c.writtenOn;
       r.range = toRange(c.range);
       r.tag = c.tag;
-      r.unresolved = c.unresolved;
       if (loader != null) {
         r.author = loader.get(c.author.getId());
       }
@@ -194,6 +193,7 @@
     protected CommentInfo toInfo(HumanComment c, AccountLoader loader) {
       CommentInfo ci = new CommentInfo();
       fillCommentInfo(c, ci, loader);
+      ci.unresolved = c.unresolved;
       return ci;
     }
 
diff --git a/java/com/google/gerrit/server/restapi/change/GetDiff.java b/java/com/google/gerrit/server/restapi/change/GetDiff.java
index f4e2ddd..8d51786 100644
--- a/java/com/google/gerrit/server/restapi/change/GetDiff.java
+++ b/java/com/google/gerrit/server/restapi/change/GetDiff.java
@@ -15,7 +15,6 @@
 package com.google.gerrit.server.restapi.change;
 
 import static com.google.gerrit.server.project.ProjectCache.illegalState;
-import static com.google.gerrit.util.cli.Localizable.localizable;
 
 import com.google.common.base.MoreObjects;
 import com.google.common.collect.ImmutableList;
@@ -54,9 +53,7 @@
 import com.google.inject.Inject;
 import java.io.IOException;
 import java.util.concurrent.TimeUnit;
-import org.kohsuke.args4j.CmdLineException;
 import org.kohsuke.args4j.CmdLineParser;
-import org.kohsuke.args4j.NamedOptionDef;
 import org.kohsuke.args4j.Option;
 import org.kohsuke.args4j.OptionDef;
 import org.kohsuke.args4j.spi.OptionHandler;
@@ -84,8 +81,9 @@
   @Option(name = "--whitespace")
   Whitespace whitespace;
 
+  // TODO(hiesel): Remove parameter when not used by callers (e.g. frontend) anymore.
   @Option(name = "--context", handler = ContextOptionHandler.class)
-  int context = DiffPreferencesInfo.DEFAULT_CONTEXT;
+  int context;
 
   @Option(name = "--intraline")
   boolean intraline;
@@ -114,11 +112,10 @@
     } else {
       prefs.ignoreWhitespace = Whitespace.IGNORE_LEADING_AND_TRAILING;
     }
-    prefs.context = context;
     prefs.intralineDifference = intraline;
     logger.atFine().log(
-        "diff preferences: ignoreWhitespace = %s, context = %s, intralineDifference = %s",
-        prefs.ignoreWhitespace, prefs.context, prefs.intralineDifference);
+        "diff preferences: ignoreWhitespace = %s, intralineDifference = %s",
+        prefs.ignoreWhitespace, prefs.intralineDifference);
 
     PatchScriptFactory psf;
     PatchSet basePatchSet = null;
@@ -143,7 +140,6 @@
     }
 
     try {
-      psf.setLoadComments(context != DiffPreferencesInfo.WHOLE_FILE_CONTEXT);
       PatchScript ps = psf.call();
       Project.NameKey projectName = resource.getRevision().getChange().getProject();
       ProjectState state = projectCache.get(projectName).orElseThrow(illegalState(projectName));
@@ -245,11 +241,6 @@
     return this;
   }
 
-  public GetDiff setContext(int context) {
-    this.context = context;
-    return this;
-  }
-
   public GetDiff setIntraline(boolean intraline) {
     this.intraline = intraline;
     return this;
@@ -274,6 +265,7 @@
     }
   }
 
+  // TODO(hiesel): Remove this class once clients don't send the context parameter anymore.
   public static class ContextOptionHandler extends OptionHandler<Short> {
 
     public ContextOptionHandler(CmdLineParser parser, OptionDef option, Setter<Short> setter) {
@@ -281,33 +273,14 @@
     }
 
     @Override
-    public final int parseArguments(Parameters params) throws CmdLineException {
-      final String value = params.getParameter(0);
-      short context;
-      if ("all".equalsIgnoreCase(value)) {
-        context = DiffPreferencesInfo.WHOLE_FILE_CONTEXT;
-      } else {
-        try {
-          context = Short.parseShort(value, 10);
-          if (context < 0) {
-            throw new NumberFormatException();
-          }
-        } catch (NumberFormatException e) {
-          logger.atFine().withCause(e).log("invalid numeric value");
-          throw new CmdLineException(
-              owner,
-              localizable("\"%s\" is not a valid value for \"%s\""),
-              value,
-              ((NamedOptionDef) option).name());
-        }
-      }
-      setter.addValue(context);
+    public final int parseArguments(Parameters params) {
+      // Return 1 to consume the context parameter.
       return 1;
     }
 
     @Override
     public final String getDefaultMetaVariable() {
-      return "ALL|# LINES";
+      return "ignored";
     }
   }
 }
diff --git a/java/com/google/gerrit/server/restapi/change/PostReview.java b/java/com/google/gerrit/server/restapi/change/PostReview.java
index 3367ca6..604c87f 100644
--- a/java/com/google/gerrit/server/restapi/change/PostReview.java
+++ b/java/com/google/gerrit/server/restapi/change/PostReview.java
@@ -568,8 +568,8 @@
     }
   }
 
-  private static <T extends CommentInput> Map<String, List<T>> cleanUpComments(
-      Map<String, List<T>> commentsPerPath) {
+  private static <T extends com.google.gerrit.extensions.client.Comment>
+      Map<String, List<T>> cleanUpComments(Map<String, List<T>> commentsPerPath) {
     Map<String, List<T>> cleanedUpCommentMap = new HashMap<>();
     for (Map.Entry<String, List<T>> e : commentsPerPath.entrySet()) {
       String path = e.getKey();
@@ -587,7 +587,8 @@
     return cleanedUpCommentMap;
   }
 
-  private static <T extends CommentInput> List<T> cleanUpComments(List<T> comments) {
+  private static <T extends com.google.gerrit.extensions.client.Comment> List<T> cleanUpComments(
+      List<T> comments) {
     return comments.stream()
         .filter(Objects::nonNull)
         .filter(comment -> !Strings.nullToEmpty(comment.message).trim().isEmpty())
@@ -598,7 +599,7 @@
     return TraceContext.newTimer(getClass().getSimpleName() + "#" + method, Metadata.empty());
   }
 
-  private <T extends CommentInput> void checkComments(
+  private <T extends com.google.gerrit.extensions.client.Comment> void checkComments(
       RevisionResource revision, Map<String, List<T>> commentsPerPath)
       throws BadRequestException, PatchListNotAvailableException {
     logger.atFine().log("checking comments");
@@ -646,15 +647,16 @@
     }
   }
 
-  private static <T extends CommentInput> void ensureCommentNotOnMagicFilesOfAutoMerge(
-      String path, T comment) throws BadRequestException {
+  private static <T extends com.google.gerrit.extensions.client.Comment>
+      void ensureCommentNotOnMagicFilesOfAutoMerge(String path, T comment)
+          throws BadRequestException {
     if (Patch.isMagic(path) && comment.side == Side.PARENT && comment.parent == null) {
       throw new BadRequestException(String.format("cannot comment on %s on auto-merge", path));
     }
   }
 
-  private static <T extends CommentInput> void ensureValidPatchsetLevelComment(
-      String path, T comment) throws BadRequestException {
+  private static <T extends com.google.gerrit.extensions.client.Comment>
+      void ensureValidPatchsetLevelComment(String path, T comment) throws BadRequestException {
     if (path.equals(PATCHSET_LEVEL)
         && (comment.side != null || comment.range != null || comment.line != null)) {
       throw new BadRequestException("Patchset-level comments can't have side, range, or line");
diff --git a/java/com/google/gerrit/server/restapi/change/ReplyAttentionSetUpdates.java b/java/com/google/gerrit/server/restapi/change/ReplyAttentionSetUpdates.java
index c523036..65c0cda 100644
--- a/java/com/google/gerrit/server/restapi/change/ReplyAttentionSetUpdates.java
+++ b/java/com/google/gerrit/server/restapi/change/ReplyAttentionSetUpdates.java
@@ -125,7 +125,6 @@
     }
     if (serviceUserClassifier.isServiceUser(currentUser.getAccountId())) {
       botsWithNegativeLabelsAddOwnerAndUploader(bu, changeNotes, input);
-      robotCommentAddsOwnerAndUploader(bu, changeNotes, input);
       return;
     }
 
@@ -254,8 +253,8 @@
   }
 
   /**
-   * Bots don't process automatic rules, but they do have special rules; One of them: If voted
-   * negatively on a label, add the owner and uploader.
+   * Bots don't process automatic rules, the only attention set change they do is this rule: Add
+   * owner and uploader when a bot votes negatively.
    */
   private void botsWithNegativeLabelsAddOwnerAndUploader(
       BatchUpdate bu, ChangeNotes changeNotes, ReviewInput input) {
@@ -270,22 +269,6 @@
   }
 
   /**
-   * Bots don't process automatic rules, but they do have special rules; One of them: When adding a
-   * robot comment, add the owner and uploader. This only applies on open changes.
-   */
-  private void robotCommentAddsOwnerAndUploader(
-      BatchUpdate bu, ChangeNotes changeNotes, ReviewInput input) {
-    if (input.robotComments != null && changeNotes.getChange().isNew()) {
-      Account.Id uploader = changeNotes.getCurrentPatchSet().uploader();
-      Account.Id owner = changeNotes.getChange().getOwner();
-      addToAttentionSet(bu, changeNotes, owner, "A robot comment was added", false);
-      if (!owner.equals(uploader)) {
-        addToAttentionSet(bu, changeNotes, uploader, "A robot comment was added", false);
-      }
-    }
-  }
-
-  /**
    * Adds the user to the attention set
    *
    * @param bu BatchUpdate to perform the updates to the attention set
diff --git a/java/com/google/gerrit/testing/TestCommentHelper.java b/java/com/google/gerrit/testing/TestCommentHelper.java
index 76a5521..da8f871 100644
--- a/java/com/google/gerrit/testing/TestCommentHelper.java
+++ b/java/com/google/gerrit/testing/TestCommentHelper.java
@@ -82,7 +82,6 @@
     c.parent = null;
     c.line = line != 0 ? line : null;
     c.message = message;
-    c.unresolved = false;
     c.inReplyTo = inReplyTo;
     if (line != 0) c.range = range;
     return c;
@@ -96,7 +95,6 @@
     c.parent = null;
     c.line = line != 0 ? line : null;
     c.message = message;
-    c.unresolved = false;
     if (line != 0) c.range = range;
     return c;
   }
diff --git a/javatests/com/google/gerrit/acceptance/api/revision/RevisionDiffIT.java b/javatests/com/google/gerrit/acceptance/api/revision/RevisionDiffIT.java
index 5684b1f..82215b6 100644
--- a/javatests/com/google/gerrit/acceptance/api/revision/RevisionDiffIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/revision/RevisionDiffIT.java
@@ -36,16 +36,12 @@
 import com.google.gerrit.common.RawInputUtil;
 import com.google.gerrit.extensions.api.changes.FileApi;
 import com.google.gerrit.extensions.api.changes.RebaseInput;
-import com.google.gerrit.extensions.api.changes.ReviewInput;
-import com.google.gerrit.extensions.api.changes.ReviewInput.CommentInput;
-import com.google.gerrit.extensions.client.Comment;
 import com.google.gerrit.extensions.client.DiffPreferencesInfo;
 import com.google.gerrit.extensions.common.ChangeType;
 import com.google.gerrit.extensions.common.DiffInfo;
 import com.google.gerrit.extensions.common.FileInfo;
 import com.google.gerrit.extensions.restapi.BadRequestException;
 import com.google.gerrit.extensions.restapi.BinaryResult;
-import com.google.gerrit.extensions.restapi.RestApiException;
 import com.google.gerrit.testing.ConfigSuite;
 import java.awt.image.BufferedImage;
 import java.io.ByteArrayOutputStream;
@@ -64,6 +60,7 @@
 import org.eclipse.jgit.lib.PersonIdent;
 import org.eclipse.jgit.revwalk.RevCommit;
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Test;
 
 public class RevisionDiffIT extends AbstractDaemonTest {
@@ -676,12 +673,6 @@
     String baseFileContent = FILE_CONTENT.concat("Line 101");
     ObjectId commit2 = addCommit(commit1, FILE_NAME, baseFileContent);
     rebaseChangeOn(changeId, commit2);
-    // Add a comment so that file contents are not 'skipped'. To be able to add a comment, touch
-    // (= modify) the file in the change.
-    addModifiedPatchSet(
-        changeId, FILE_NAME, fileContent -> fileContent.replace("Line 2\n", "Line two\n"));
-    CommentInput comment = createCommentInput(3, 0, 4, 0, "Comment to not skip file content.");
-    addCommentTo(changeId, CURRENT, FILE_NAME, comment);
     String previousPatchSetId = gApi.changes().id(changeId).get().currentRevision;
     String newBaseFileContent = baseFileContent.concat("\n");
     ObjectId commit3 = addCommit(commit2, FILE_NAME, newBaseFileContent);
@@ -2517,17 +2508,14 @@
   }
 
   @Test
-  public void diffOfUnmodifiedFileWithWholeFileContextReturnsFileContents() throws Exception {
+  public void diffOfUnmodifiedFileReturnsAllFileContents() throws Exception {
     addModifiedPatchSet(changeId, FILE_NAME, content -> content.replace("Line 2\n", "Line two\n"));
     String previousPatchSetId = gApi.changes().id(changeId).get().currentRevision;
     addModifiedPatchSet(
         changeId, FILE_NAME2, content -> content.replace("2nd line\n", "Second line\n"));
 
     DiffInfo diffInfo =
-        getDiffRequest(changeId, CURRENT, FILE_NAME)
-            .withBase(previousPatchSetId)
-            .withContext(DiffPreferencesInfo.WHOLE_FILE_CONTEXT)
-            .get();
+        getDiffRequest(changeId, CURRENT, FILE_NAME).withBase(previousPatchSetId).get();
     // We don't list the full file contents here as that is not the focus of this test.
     assertThat(diffInfo)
         .content()
@@ -2538,40 +2526,16 @@
   }
 
   @Test
-  public void diffOfUnmodifiedFileWithCommentAndWholeFileContextReturnsFileContents()
+  // TODO(ghareeb): Don't exclude diffs which only contain rebase hunks within the diff caches. Only
+  // filter such files in the GetFiles REST endpoint.
+  @Ignore
+  public void diffOfFileWithOnlyRebaseHunksConsideringWhitespaceReturnsFileContents()
       throws Exception {
     addModifiedPatchSet(changeId, FILE_NAME, content -> content.replace("Line 2\n", "Line two\n"));
     String previousPatchSetId = gApi.changes().id(changeId).get().currentRevision;
-    CommentInput comment = createCommentInput(2, 0, 3, 0, "Should be 'Line 2'.");
-    addCommentTo(changeId, previousPatchSetId, FILE_NAME, comment);
-    addModifiedPatchSet(
-        changeId, FILE_NAME2, content -> content.replace("2nd line\n", "Second line\n"));
-
-    DiffInfo diffInfo =
-        getDiffRequest(changeId, CURRENT, FILE_NAME)
-            .withBase(previousPatchSetId)
-            .withContext(DiffPreferencesInfo.WHOLE_FILE_CONTEXT)
-            .get();
-    // We don't list the full file contents here as that is not the focus of this test.
-    assertThat(diffInfo)
-        .content()
-        .element(0)
-        .commonLines()
-        .containsAtLeast("Line 1", "Line two", "Line 3", "Line 4", "Line 5")
-        .inOrder();
-  }
-
-  @Test
-  public void
-      diffOfFileWithOnlyRebaseHunksAndWithCommentAndConsideringWhitespaceReturnsFileContents()
-          throws Exception {
-    addModifiedPatchSet(changeId, FILE_NAME, content -> content.replace("Line 2\n", "Line two\n"));
-    String previousPatchSetId = gApi.changes().id(changeId).get().currentRevision;
     String newBaseFileContent = FILE_CONTENT.replace("Line 70\n", "Line seventy\n");
     ObjectId commit2 = addCommit(commit1, FILE_NAME, newBaseFileContent);
     rebaseChangeOn(changeId, commit2);
-    CommentInput comment = createCommentInput(2, 0, 3, 0, "Should be 'Line 2'.");
-    addCommentTo(changeId, previousPatchSetId, FILE_NAME, comment);
 
     DiffInfo diffInfo =
         getDiffRequest(changeId, CURRENT, FILE_NAME)
@@ -2585,18 +2549,23 @@
         .commonLines()
         .containsAtLeast("Line 1", "Line two", "Line 3", "Line 4", "Line 5")
         .inOrder();
+    // It's crucial that the line changed in the rebase is reported correctly.
+    assertThat(diffInfo).content().element(1).linesOfA().containsExactly("Line 70");
+    assertThat(diffInfo).content().element(1).linesOfB().containsExactly("Line seventy");
+    assertThat(diffInfo).content().element(1).isDueToRebase();
   }
 
   @Test
-  public void diffOfFileWithOnlyRebaseHunksAndWithCommentAndIgnoringWhitespaceReturnsFileContents()
+  // TODO(ghareeb): Don't exclude diffs which only contain rebase hunks within the diff caches. Only
+  // filter such files in the GetFiles REST endpoint.
+  @Ignore
+  public void diffOfFileWithOnlyRebaseHunksAndIgnoringWhitespaceReturnsFileContents()
       throws Exception {
     addModifiedPatchSet(changeId, FILE_NAME, content -> content.replace("Line 2\n", "Line two\n"));
     String previousPatchSetId = gApi.changes().id(changeId).get().currentRevision;
     String newBaseFileContent = FILE_CONTENT.replace("Line 70\n", "Line seventy\n");
     ObjectId commit2 = addCommit(commit1, FILE_NAME, newBaseFileContent);
     rebaseChangeOn(changeId, commit2);
-    CommentInput comment = createCommentInput(2, 0, 3, 0, "Should be 'Line 2'.");
-    addCommentTo(changeId, previousPatchSetId, FILE_NAME, comment);
 
     DiffInfo diffInfo =
         getDiffRequest(changeId, CURRENT, FILE_NAME)
@@ -2610,12 +2579,18 @@
         .commonLines()
         .containsAtLeast("Line 1", "Line two", "Line 3", "Line 4", "Line 5")
         .inOrder();
+    // It's crucial that the line changed in the rebase is reported correctly.
+    assertThat(diffInfo).content().element(1).linesOfA().containsExactly("Line 70");
+    assertThat(diffInfo).content().element(1).linesOfB().containsExactly("Line seventy");
+    assertThat(diffInfo).content().element(1).isDueToRebase();
   }
 
   @Test
-  public void
-      diffOfFileWithMultilineRebaseHunkAddingNewlineAtEndOfFileAndWithCommentReturnsFileContents()
-          throws Exception {
+  // TODO(ghareeb): Don't exclude diffs which only contain rebase hunks within the diff caches. Only
+  // filter such files in the GetFiles REST endpoint.
+  @Ignore
+  public void diffOfFileWithMultilineRebaseHunkAddingNewlineAtEndOfFileReturnsFileContents()
+      throws Exception {
     String baseFileContent = FILE_CONTENT.concat("Line 101");
     ObjectId commit2 = addCommit(commit1, FILE_NAME, baseFileContent);
     rebaseChangeOn(changeId, commit2);
@@ -2624,8 +2599,6 @@
     String newBaseFileContent = baseFileContent.concat("\nLine 102\nLine 103\n");
     ObjectId commit3 = addCommit(commit2, FILE_NAME, newBaseFileContent);
     rebaseChangeOn(changeId, commit3);
-    CommentInput comment = createCommentInput(2, 0, 3, 0, "Should be 'Line 2'.");
-    addCommentTo(changeId, previousPatchSetId, FILE_NAME, comment);
 
     DiffInfo diffInfo =
         getDiffRequest(changeId, CURRENT, FILE_NAME)
@@ -2639,19 +2612,27 @@
         .commonLines()
         .containsAtLeast("Line 1", "Line two", "Line 3", "Line 4", "Line 5")
         .inOrder();
+    // It's crucial that the lines changed in the rebase are reported correctly.
+    assertThat(diffInfo).content().element(1).linesOfA().containsExactly("Line 101");
+    assertThat(diffInfo)
+        .content()
+        .element(1)
+        .linesOfB()
+        .containsExactly("Line 101", "Line 102", "Line 103", "");
+    assertThat(diffInfo).content().element(1).isDueToRebase();
   }
 
   @Test
-  public void
-      diffOfFileWithMultilineRebaseHunkRemovingNewlineAtEndOfFileAndWithCommentReturnsFileContents()
-          throws Exception {
+  // TODO(ghareeb): Don't exclude diffs which only contain rebase hunks within the diff caches. Only
+  // filter such files in the GetFiles REST endpoint.
+  @Ignore
+  public void diffOfFileWithMultilineRebaseHunkRemovingNewlineAtEndOfFileReturnsFileContents()
+      throws Exception {
     addModifiedPatchSet(changeId, FILE_NAME, content -> content.replace("Line 2\n", "Line two\n"));
     String previousPatchSetId = gApi.changes().id(changeId).get().currentRevision;
-    String newBaseFileContent = FILE_CONTENT.concat("Line 101\nLine 103\nLine 104");
+    String newBaseFileContent = FILE_CONTENT.concat("Line 101\nLine 102\nLine 103");
     ObjectId commit2 = addCommit(commit1, FILE_NAME, newBaseFileContent);
     rebaseChangeOn(changeId, commit2);
-    CommentInput comment = createCommentInput(2, 0, 3, 0, "Should be 'Line 2'.");
-    addCommentTo(changeId, previousPatchSetId, FILE_NAME, comment);
 
     DiffInfo diffInfo =
         getDiffRequest(changeId, CURRENT, FILE_NAME)
@@ -2665,6 +2646,14 @@
         .commonLines()
         .containsAtLeast("Line 1", "Line two", "Line 3", "Line 4", "Line 5")
         .inOrder();
+    // It's crucial that the lines changed in the rebase are reported correctly.
+    assertThat(diffInfo).content().element(1).linesOfA().containsExactly("Line 100", "");
+    assertThat(diffInfo)
+        .content()
+        .element(1)
+        .linesOfB()
+        .containsExactly("Line 100", "Line 101", "Line 102", "Line 103");
+    assertThat(diffInfo).content().element(1).isDueToRebase();
   }
 
   @Test
@@ -2674,49 +2663,10 @@
     DiffInfo diffInfo =
         getDiffRequest(changeId, CURRENT, "a_non-existent_file.txt")
             .withBase(initialPatchSetId)
-            .withContext(DiffPreferencesInfo.WHOLE_FILE_CONTEXT)
             .get();
     assertThat(diffInfo).content().isEmpty();
   }
 
-  // This behavior is likely a bug. A fix might not be easy as it might break syntax highlighting.
-  // TODO: Fix this issue or remove the broken parameter (at least in the documentation).
-  @Test
-  public void contextParameterIsIgnored() throws Exception {
-    addModifiedPatchSet(
-        changeId, FILE_NAME, content -> content.replace("Line 20\n", "Line twenty\n"));
-
-    DiffInfo diffInfo =
-        getDiffRequest(changeId, CURRENT, FILE_NAME)
-            .withBase(initialPatchSetId)
-            .withContext(5)
-            .get();
-    assertThat(diffInfo).content().element(0).commonLines().hasSize(19);
-    assertThat(diffInfo).content().element(1).linesOfA().containsExactly("Line 20");
-    assertThat(diffInfo).content().element(1).linesOfB().containsExactly("Line twenty");
-    assertThat(diffInfo).content().element(2).commonLines().hasSize(81);
-  }
-
-  // This behavior is likely a bug. A fix might not be easy as it might break syntax highlighting.
-  // TODO: Fix this issue or remove the broken parameter (at least in the documentation).
-  @Test
-  public void contextParameterIsIgnoredForUnmodifiedFileWithComment() throws Exception {
-    addModifiedPatchSet(
-        changeId, FILE_NAME, content -> content.replace("Line 20\n", "Line twenty\n"));
-    String previousPatchSetId = gApi.changes().id(changeId).get().currentRevision;
-    CommentInput comment = createCommentInput(20, 0, 21, 0, "Should be 'Line 20'.");
-    addCommentTo(changeId, previousPatchSetId, FILE_NAME, comment);
-    addModifiedPatchSet(
-        changeId, FILE_NAME2, content -> content.replace("2nd line\n", "Second line\n"));
-
-    DiffInfo diffInfo =
-        getDiffRequest(changeId, CURRENT, FILE_NAME)
-            .withBase(previousPatchSetId)
-            .withContext(5)
-            .get();
-    assertThat(diffInfo).content().element(0).commonLines().hasSize(101);
-  }
-
   @Test
   public void requestingDiffForOldFileNameOfRenamedFileYieldsReasonableResult() throws Exception {
     addModifiedPatchSet(changeId, FILE_NAME, content -> content.replace("Line 2\n", "Line two\n"));
@@ -2726,40 +2676,13 @@
     gApi.changes().id(changeId).edit().publish();
 
     DiffInfo diffInfo =
-        getDiffRequest(changeId, CURRENT, FILE_NAME)
-            .withBase(previousPatchSetId)
-            .withContext(DiffPreferencesInfo.WHOLE_FILE_CONTEXT)
-            .get();
+        getDiffRequest(changeId, CURRENT, FILE_NAME).withBase(previousPatchSetId).get();
     // This behavior has been present in Gerrit for quite some time. It differs from the results
-    // returned for other cases (e.g. requesting the diff with whole file context for an unmodified
-    // file; requesting the diff with whole file context for a non-existent file). However, it's not
-    // completely clear what should be returned. The closest would be the result of a file deletion
-    // but that might also be misleading for users as actually a file rename occurred. In fact,
-    // requesting the diff result for the old file name of a renamed file is not a reasonable use
-    // case at all. We at least guarantee that we don't run into an internal error.
-    assertThat(diffInfo).content().element(0).commonLines().isNull();
-    assertThat(diffInfo).content().element(0).numberOfSkippedLines().isGreaterThan(0);
-  }
-
-  @Test
-  public void requestingDiffForOldFileNameOfRenamedFileWithCommentOnOldFileYieldsReasonableResult()
-      throws Exception {
-    addModifiedPatchSet(changeId, FILE_NAME, content -> content.replace("Line 2\n", "Line two\n"));
-    String previousPatchSetId = gApi.changes().id(changeId).get().currentRevision;
-    CommentInput comment = createCommentInput(2, 0, 3, 0, "Should be 'Line 2'.");
-    addCommentTo(changeId, previousPatchSetId, FILE_NAME, comment);
-    String newFilePath = "a_new_file.txt";
-    gApi.changes().id(changeId).edit().renameFile(FILE_NAME, newFilePath);
-    gApi.changes().id(changeId).edit().publish();
-
-    DiffInfo diffInfo =
-        getDiffRequest(changeId, CURRENT, FILE_NAME)
-            .withBase(previousPatchSetId)
-            .withContext(DiffPreferencesInfo.WHOLE_FILE_CONTEXT)
-            .get();
-    // See comment for requestingDiffForOldFileNameOfRenamedFileYieldsReasonableResult().
-    // This test should additionally ensure that we also don't run into an internal error when
-    // a comment is present.
+    // returned for other cases (e.g. requesting the diff for an unmodified file; requesting the
+    // diff for a non-existent file). After a rename, the original file doesn't exist anymore.
+    // Hence, the most reasonable thing would be to match the behavior of requesting the diff for a
+    // non-existent file, which returns an empty diff.
+    // This test at least guarantees that we don't run into an internal error.
     assertThat(diffInfo).content().element(0).commonLines().isNull();
     assertThat(diffInfo).content().element(0).numberOfSkippedLines().isGreaterThan(0);
   }
@@ -2781,26 +2704,6 @@
     assertThat(e).hasMessageThat().isEqualTo("edit not allowed as base");
   }
 
-  private static CommentInput createCommentInput(
-      int startLine, int startCharacter, int endLine, int endCharacter, String message) {
-    CommentInput comment = new CommentInput();
-    comment.range = new Comment.Range();
-    comment.range.startLine = startLine;
-    comment.range.startCharacter = startCharacter;
-    comment.range.endLine = endLine;
-    comment.range.endCharacter = endCharacter;
-    comment.message = message;
-    return comment;
-  }
-
-  private void addCommentTo(
-      String changeId, String previousPatchSetId, String fileName, CommentInput comment)
-      throws RestApiException {
-    ReviewInput reviewInput = new ReviewInput();
-    reviewInput.comments = ImmutableMap.of(fileName, ImmutableList.of(comment));
-    gApi.changes().id(changeId).revision(previousPatchSetId).review(reviewInput);
-  }
-
   private void assertDiffForNewFile(
       PushOneCommit.Result pushResult, String path, String expectedContentSideB) throws Exception {
     DiffInfo diff =
diff --git a/javatests/com/google/gerrit/acceptance/edit/ChangeEditIT.java b/javatests/com/google/gerrit/acceptance/edit/ChangeEditIT.java
index 22cecdb..5dbbe96 100644
--- a/javatests/com/google/gerrit/acceptance/edit/ChangeEditIT.java
+++ b/javatests/com/google/gerrit/acceptance/edit/ChangeEditIT.java
@@ -1016,11 +1016,7 @@
   }
 
   private String urlDiff(String changeId, String fileName) {
-    return "/changes/"
-        + changeId
-        + "/revisions/0/files/"
-        + fileName
-        + "/diff?context=ALL&intraline";
+    return "/changes/" + changeId + "/revisions/0/files/" + fileName + "/diff?intraline";
   }
 
   private String urlDiff(String changeId, String revisionId, String fileName) {
@@ -1030,7 +1026,7 @@
         + revisionId
         + "/files/"
         + fileName
-        + "/diff?context=ALL&intraline";
+        + "/diff?intraline";
   }
 
   private <T> T readContentFromJson(RestResponse r, Class<T> clazz) throws Exception {
diff --git a/javatests/com/google/gerrit/acceptance/rest/change/AttentionSetIT.java b/javatests/com/google/gerrit/acceptance/rest/change/AttentionSetIT.java
index cc6b199..976be96 100644
--- a/javatests/com/google/gerrit/acceptance/rest/change/AttentionSetIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/change/AttentionSetIT.java
@@ -1279,25 +1279,6 @@
   }
 
   @Test
-  public void robotCommentAddsOwnerOnNewChanges() throws Exception {
-    TestAccount robot =
-        accountCreator.create("robot2", "robot2@example.com", "Ro Bot", "Ro", "Service Users");
-    PushOneCommit.Result r = createChange();
-    requestScopeOperations.setApiUser(robot.id());
-    ReviewInput reviewInput = new ReviewInput();
-    ReviewInput.RobotCommentInput robotCommentInput =
-        TestCommentHelper.createRobotCommentInputWithMandatoryFields("a.txt");
-    reviewInput.robotComments = ImmutableMap.of("a.txt", ImmutableList.of(robotCommentInput));
-    change(r).current().review(reviewInput);
-
-    AttentionSetUpdate attentionSet =
-        Iterables.getOnlyElement(getAttentionSetUpdatesForUser(r, admin));
-    assertThat(attentionSet).hasAccountIdThat().isEqualTo(admin.id());
-    assertThat(attentionSet).hasOperationThat().isEqualTo(AttentionSetUpdate.Operation.ADD);
-    assertThat(attentionSet).hasReasonThat().isEqualTo("A robot comment was added");
-  }
-
-  @Test
   public void robotCommentDoesNotAddOwnerOnClosedChanges() throws Exception {
     TestAccount robot =
         accountCreator.create("robot2", "robot2@example.com", "Ro Bot", "Ro", "Service Users");
diff --git a/javatests/com/google/gerrit/acceptance/server/change/CommentsIT.java b/javatests/com/google/gerrit/acceptance/server/change/CommentsIT.java
index 903ce1a..b4dd4b3 100644
--- a/javatests/com/google/gerrit/acceptance/server/change/CommentsIT.java
+++ b/javatests/com/google/gerrit/acceptance/server/change/CommentsIT.java
@@ -78,7 +78,6 @@
 import java.util.Map;
 import java.util.Optional;
 import java.util.function.Function;
-import java.util.function.Supplier;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 import java.util.stream.Collectors;
@@ -1883,39 +1882,46 @@
 
   private static CommentInput newCommentWithOnlyMandatoryFields(String path, String message) {
     CommentInput c = new CommentInput();
-    return populate(c, path, null, null, null, null, message, false);
+    c.unresolved = false;
+    return populate(c, path, null, null, null, null, message);
   }
 
   private static CommentInput newComment(
       String path, Side side, int line, String message, Boolean unresolved) {
     CommentInput c = new CommentInput();
-    return populate(c, path, side, null, line, message, unresolved);
+    c.unresolved = unresolved;
+    return populate(c, path, side, null, line, message);
   }
 
   private static CommentInput newCommentOnParent(
       String path, int parent, int line, String message) {
     CommentInput c = new CommentInput();
-    return populate(c, path, Side.PARENT, parent, line, message, false);
+    c.unresolved = false;
+    return populate(c, path, Side.PARENT, parent, line, message);
   }
 
   private DraftInput newDraft(String path, Side side, int line, String message) {
     DraftInput d = new DraftInput();
-    return populate(d, path, side, null, line, message, false);
+    d.unresolved = false;
+    return populate(d, path, side, null, line, message);
   }
 
   private DraftInput newDraft(String path, Side side, Comment.Range range, String message) {
     DraftInput d = new DraftInput();
-    return populate(d, path, side, null, range.startLine, range, message, false);
+    d.unresolved = false;
+    return populate(d, path, side, null, range.startLine, range, message);
   }
 
   private DraftInput newDraftOnParent(String path, int parent, int line, String message) {
     DraftInput d = new DraftInput();
-    return populate(d, path, Side.PARENT, parent, line, message, false);
+    d.unresolved = false;
+    return populate(d, path, Side.PARENT, parent, line, message);
   }
 
   private DraftInput newDraftWithOnlyMandatoryFields(String path, String message) {
     DraftInput d = new DraftInput();
-    return populate(d, path, null, null, null, null, message, false);
+    d.unresolved = false;
+    return populate(d, path, null, null, null, null, message);
   }
 
   private static <C extends Comment> C populate(
@@ -1925,14 +1931,12 @@
       Integer parent,
       Integer line,
       Comment.Range range,
-      String message,
-      Boolean unresolved) {
+      String message) {
     c.path = path;
     c.side = side;
     c.parent = parent;
     c.line = line != null && line != 0 ? line : null;
     c.message = message;
-    c.unresolved = unresolved;
     if (range != null) {
       c.range = range;
     }
@@ -1940,8 +1944,8 @@
   }
 
   private static <C extends Comment> C populate(
-      C c, String path, Side side, Integer parent, int line, String message, Boolean unresolved) {
-    return populate(c, path, side, parent, line, null, message, unresolved);
+      C c, String path, Side side, Integer parent, int line, String message) {
+    return populate(c, path, side, parent, line, null, message);
   }
 
   private static Comment.Range createLineRange(int startChar, int endChar) {
@@ -1954,20 +1958,22 @@
   }
 
   private static Function<CommentInfo, CommentInput> infoToInput(String path) {
-    return infoToInput(path, CommentInput::new);
+    return info -> {
+      CommentInput commentInput = new CommentInput();
+      commentInput.path = path;
+      commentInput.unresolved = info.unresolved;
+      copy(info, commentInput);
+      return commentInput;
+    };
   }
 
   private static Function<CommentInfo, DraftInput> infoToDraft(String path) {
-    return infoToInput(path, DraftInput::new);
-  }
-
-  private static <I extends Comment> Function<CommentInfo, I> infoToInput(
-      String path, Supplier<I> supplier) {
     return info -> {
-      I i = supplier.get();
-      i.path = path;
-      copy(info, i);
-      return i;
+      DraftInput draftInput = new DraftInput();
+      draftInput.path = path;
+      draftInput.unresolved = info.unresolved;
+      copy(info, draftInput);
+      return draftInput;
     };
   }
 
@@ -1977,7 +1983,6 @@
     to.line = from.line;
     to.message = from.message;
     to.range = from.range;
-    to.unresolved = from.unresolved;
     to.inReplyTo = from.inReplyTo;
   }
 }
diff --git a/javatests/com/google/gerrit/acceptance/testsuite/change/PatchsetOperationsImplTest.java b/javatests/com/google/gerrit/acceptance/testsuite/change/PatchsetOperationsImplTest.java
index 9161928..46d9abf 100644
--- a/javatests/com/google/gerrit/acceptance/testsuite/change/PatchsetOperationsImplTest.java
+++ b/javatests/com/google/gerrit/acceptance/testsuite/change/PatchsetOperationsImplTest.java
@@ -1067,28 +1067,6 @@
   }
 
   @Test
-  public void robotCommentCanBeCreatedAsResolved() throws Exception {
-    Change.Id changeId = changeOperations.newChange().create();
-
-    String commentUuid =
-        changeOperations.change(changeId).currentPatchset().newRobotComment().resolved().create();
-
-    CommentInfo comment = getRobotCommentFromServerInCurrentPatchset(changeId, commentUuid);
-    assertThat(comment).unresolved().isFalse();
-  }
-
-  @Test
-  public void robotCommentCanBeCreatedAsUnresolved() throws Exception {
-    Change.Id changeId = changeOperations.newChange().create();
-
-    String commentUuid =
-        changeOperations.change(changeId).currentPatchset().newRobotComment().unresolved().create();
-
-    CommentInfo comment = getRobotCommentFromServerInCurrentPatchset(changeId, commentUuid);
-    assertThat(comment).unresolved().isTrue();
-  }
-
-  @Test
   public void replyToRobotCommentCanBeCreated() throws Exception {
     Change.Id changeId = changeOperations.newChange().create();
     String parentCommentUuid =
diff --git a/plugins/replication b/plugins/replication
index 7ad27c7..293ee35 160000
--- a/plugins/replication
+++ b/plugins/replication
@@ -1 +1 @@
-Subproject commit 7ad27c7ac49731e70dd5ad3691b0b410492792f0
+Subproject commit 293ee3590d9fb57f6882e11126d8c97532cce968
diff --git a/polygerrit-ui/app/elements/change/gr-message/gr-message.ts b/polygerrit-ui/app/elements/change/gr-message/gr-message.ts
index d627071..87d443b 100644
--- a/polygerrit-ui/app/elements/change/gr-message/gr-message.ts
+++ b/polygerrit-ui/app/elements/change/gr-message/gr-message.ts
@@ -44,8 +44,8 @@
 import {CommentThread} from '../../diff/gr-comment-api/gr-comment-api';
 import {hasOwnProperty} from '../../../utils/common-util';
 
-const PATCH_SET_PREFIX_PATTERN = /^Patch Set \d+:\s*(.*)/;
-const LABEL_TITLE_SCORE_PATTERN = /^(-?)([A-Za-z0-9-]+?)([+-]\d+)?$/;
+const PATCH_SET_PREFIX_PATTERN = /^(?:Uploaded\s*)?(?:P|p)atch (?:S|s)et \d+:\s*(.*)/;
+const LABEL_TITLE_SCORE_PATTERN = /^(-?)([A-Za-z0-9-]+?)([+-]\d+)?[.]?$/;
 
 declare global {
   interface HTMLElementTagNameMap {
diff --git a/polygerrit-ui/app/elements/change/gr-message/gr-message_test.js b/polygerrit-ui/app/elements/change/gr-message/gr-message_test.js
index 2e2b278..a705d45 100644
--- a/polygerrit-ui/app/elements/change/gr-message/gr-message_test.js
+++ b/polygerrit-ui/app/elements/change/gr-message/gr-message_test.js
@@ -315,6 +315,32 @@
       assert.isFalse(scoreChips[2].classList.contains('min'));
     });
 
+    test('Uploaded patch set X', () => {
+      element.message = {
+        author: {},
+        expanded: false,
+        message: 'Uploaded patch set 1:' +
+         'Verified+1 Code-Review-2 Trybot-Label3+1 Blub+1',
+      };
+      element.labelExtremes = {
+        'Verified': {max: 1, min: -1},
+        'Code-Review': {max: 2, min: -2},
+        'Trybot-Label3': {max: 3, min: 0},
+      };
+      flush();
+      const scoreChips = element.root.querySelectorAll('.score');
+      assert.equal(scoreChips.length, 3);
+
+      assert.isTrue(scoreChips[0].classList.contains('positive'));
+      assert.isTrue(scoreChips[0].classList.contains('max'));
+
+      assert.isTrue(scoreChips[1].classList.contains('negative'));
+      assert.isTrue(scoreChips[1].classList.contains('min'));
+
+      assert.isTrue(scoreChips[2].classList.contains('positive'));
+      assert.isFalse(scoreChips[2].classList.contains('min'));
+    });
+
     test('removed votes', () => {
       element.message = {
         author: {},
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_html.ts b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_html.ts
index a289be4..ef3e848 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_html.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_html.ts
@@ -225,6 +225,7 @@
           on-change="_handleReviewedChange"
           hidden$="[[!_loggedIn]]"
           hidden=""
+          title="Toggle reviewed status of file"
           aria-label="file reviewed"
         /><!--
        -->
diff --git a/polygerrit-ui/app/elements/shared/gr-hovercard/gr-hovercard-behavior.ts b/polygerrit-ui/app/elements/shared/gr-hovercard/gr-hovercard-behavior.ts
index a3233be..90dac88 100644
--- a/polygerrit-ui/app/elements/shared/gr-hovercard/gr-hovercard-behavior.ts
+++ b/polygerrit-ui/app/elements/shared/gr-hovercard/gr-hovercard-behavior.ts
@@ -193,10 +193,12 @@
        * Hovercard elements are created outside of <gr-app>, so if you want to fire
        * events, then you probably want to do that through the target element.
        */
-      dispatchEventThroughTarget(eventName: string) {
+      dispatchEventThroughTarget(eventName: string, detail?: unknown) {
+        if (!detail) detail = {};
         if (this._target)
           this._target.dispatchEvent(
             new CustomEvent(eventName, {
+              detail,
               bubbles: true,
               composed: true,
             })
@@ -463,7 +465,7 @@
   removeListeners(): void;
   debounceHide(): void;
   cancelHideDebouncer(): void;
-  dispatchEventThroughTarget(eventName: string): void;
+  dispatchEventThroughTarget(eventName: string, detail?: unknown): void;
   hide(e?: MouseEvent): void;
   debounceShow(): void;
   debounceShowBy(delayMs: number): void;
diff --git a/polygerrit-ui/app/services/flags/flags.ts b/polygerrit-ui/app/services/flags/flags.ts
index b45710b..047e9e0 100644
--- a/polygerrit-ui/app/services/flags/flags.ts
+++ b/polygerrit-ui/app/services/flags/flags.ts
@@ -26,4 +26,5 @@
 export enum KnownExperimentId {
   PATCHSET_COMMENTS = 'UiFeature__patchset_comments',
   PATCHSET_CHOICE_FOR_COMMENT_LINKS = 'UiFeature__patchset_choice_for_comment_links',
+  NEW_CONTEXT_CONTROLS = 'UiFeature__new_context_controls',
 }
diff --git a/polygerrit-ui/app/types/common.ts b/polygerrit-ui/app/types/common.ts
index 06e3810..caa5f7f 100644
--- a/polygerrit-ui/app/types/common.ts
+++ b/polygerrit-ui/app/types/common.ts
@@ -1830,7 +1830,7 @@
   labels?: LabelNameToValuesMap;
   comments?: PathToCommentsInputMap;
   robot_comments?: PathToRobotCommentsMap;
-  drafts: DraftsAction;
+  drafts?: DraftsAction;
   notify?: NotifyType;
   notify_details?: RecipientTypeToNotifyInfoMap;
   omit_duplicate_comments?: boolean;
diff --git a/polygerrit-ui/server.go b/polygerrit-ui/server.go
index 12b3516..3844b5b 100644
--- a/polygerrit-ui/server.go
+++ b/polygerrit-ui/server.go
@@ -527,38 +527,56 @@
 )
 
 type typescriptLogWriter struct {
-	logger *log.Logger
+	unfinishedLine string
+	logger         *log.Logger
 	// when WaitGroup counter is 0 the compilation is complete
 	compilationDoneWaiter *sync.WaitGroup
 }
 
 func newTypescriptLogWriter(compilationCompleteWaiter *sync.WaitGroup) *typescriptLogWriter {
 	return &typescriptLogWriter{
+		unfinishedLine:        "",
 		logger:                log.New(log.Writer(), "TSC - ", log.Flags()),
 		compilationDoneWaiter: compilationCompleteWaiter,
 	}
 }
 
 func (lw typescriptLogWriter) Write(p []byte) (n int, err error) {
-	text := strings.TrimSpace(string(p))
-	if strings.HasSuffix(text, tsFileChangeDetectedMsg) ||
-		strings.HasSuffix(text, tsStartingCompilation) {
-		lw.compilationDoneWaiter.Add(1)
+	// The input p can contain several lines and/or the partial line
+	// Code splits the input by EOL marker (\n) and stores the unfinished line
+	// for the next call to Write.
+	partialText := lw.unfinishedLine + string(p)
+	lines := strings.Split(partialText, "\n")
+	fullLines := lines
+	if strings.HasSuffix(partialText, "\n") {
+		lw.unfinishedLine = ""
+	} else {
+		fullLines = lines[:len(lines)-1]
+		lw.unfinishedLine = lines[len(lines)-1]
 	}
-	if tsStartWatchingMsg.MatchString(text) {
-		// A source code can be changed while previous compiler run is in progress.
-		// In this case typescript reruns compilation again almost immediately
-		// after the previous run finishes. To detect this situation, we are
-		// waiting waitForNextChangeInterval before decreasing the counter.
-		// If another compiler run is started in this interval, we will wait
-		// again until it finishes.
-		go func() {
-			time.Sleep(waitForNextChangeInterval)
-			lw.compilationDoneWaiter.Add(-1)
-		}()
-
+	for _, fullLine := range fullLines {
+		text := strings.TrimSpace(fullLine)
+		if text == "" {
+			continue
+		}
+		if strings.HasSuffix(text, tsFileChangeDetectedMsg) ||
+			strings.HasSuffix(text, tsStartingCompilation) {
+			lw.compilationDoneWaiter.Add(1)
+		}
+		if tsStartWatchingMsg.MatchString(text) {
+			// A source code can be changed while previous compiler run is in progress.
+			// In this case typescript reruns compilation again almost immediately
+			// after the previous run finishes. To detect this situation, we are
+			// waiting waitForNextChangeInterval before decreasing the counter.
+			// If another compiler run is started in this interval, we will wait
+			// again until it finishes.
+			go func() {
+				time.Sleep(waitForNextChangeInterval)
+				lw.compilationDoneWaiter.Add(-1)
+			}()
+		}
+		lw.logger.Print(text)
 	}
-	lw.logger.Print(text)
 	return len(p), nil
 }