Merge "Block revert submission if message is not edited"
diff --git a/Documentation/config-gerrit.txt b/Documentation/config-gerrit.txt
index 6968e83..fc4b00a 100644
--- a/Documentation/config-gerrit.txt
+++ b/Documentation/config-gerrit.txt
@@ -1223,12 +1223,13 @@
 high CPU usage, memory pressure, persistent cache bloat, and other problems.
 +
 The following operations are allowed even when a change is at the limit:
+
 * Abandon
 * Submit
 * Submit by push with `%submit`
 * Auto-close by pushing directly to the branch
 * Fix with link:rest-api-changes.html#fix-input[`expect_merged_as`]
-+
+
 By default 1000.
 
 [[change.move]]change.move::
diff --git a/java/com/google/gerrit/server/edit/ChangeEditUtil.java b/java/com/google/gerrit/server/edit/ChangeEditUtil.java
index 926e0d5..710916e 100644
--- a/java/com/google/gerrit/server/edit/ChangeEditUtil.java
+++ b/java/com/google/gerrit/server/edit/ChangeEditUtil.java
@@ -169,7 +169,10 @@
 
       RevCommit squashed = squashEdit(rw, oi, edit.getEditCommit(), basePatchSet);
       PatchSet.Id psId = ChangeUtil.nextPatchSetId(repo, change.currentPatchSetId());
-      PatchSetInserter inserter = patchSetInserterFactory.create(notes, psId, squashed);
+      PatchSetInserter inserter =
+          patchSetInserterFactory
+              .create(notes, psId, squashed)
+              .setSendEmail(!change.isWorkInProgress());
 
       StringBuilder message =
           new StringBuilder("Patch Set ").append(inserter.getPatchSetId().get()).append(": ");
diff --git a/javatests/com/google/gerrit/acceptance/edit/ChangeEditIT.java b/javatests/com/google/gerrit/acceptance/edit/ChangeEditIT.java
index 2883d8c..e72bf06 100644
--- a/javatests/com/google/gerrit/acceptance/edit/ChangeEditIT.java
+++ b/javatests/com/google/gerrit/acceptance/edit/ChangeEditIT.java
@@ -142,8 +142,11 @@
   public void publishEdit() throws Exception {
     createArbitraryEditFor(changeId);
 
+    AddReviewerInput in = new AddReviewerInput();
+    in.reviewer = user.email();
+    gApi.changes().id(changeId).addReviewer(in);
+
     PublishChangeEditInput publishInput = new PublishChangeEditInput();
-    publishInput.notify = NotifyHandling.NONE;
     gApi.changes().id(changeId).edit().publish(publishInput);
 
     assertThat(getEdit(changeId)).isAbsent();
@@ -160,8 +163,10 @@
     assertThat(info.messages).isNotEmpty();
     assertThat(Iterables.getLast(info.messages).tag)
         .isEqualTo(ChangeMessagesUtil.TAG_UPLOADED_PATCH_SET);
+    assertThat(sender.getMessages()).isNotEmpty();
 
     // Move the change to WIP, repeat, and verify.
+    sender.clear();
     gApi.changes().id(changeId).setWorkInProgress();
     createEmptyEditFor(changeId);
     gApi.changes().id(changeId).edit().modifyFile(FILE_NAME, RawInputUtil.create(CONTENT_NEW2));
@@ -170,6 +175,7 @@
     assertThat(info.messages).isNotEmpty();
     assertThat(Iterables.getLast(info.messages).tag)
         .isEqualTo(ChangeMessagesUtil.TAG_UPLOADED_WIP_PATCH_SET);
+    assertThat(sender.getMessages()).isEmpty();
   }
 
   @Test
diff --git a/javatests/com/google/gerrit/elasticsearch/ElasticContainer.java b/javatests/com/google/gerrit/elasticsearch/ElasticContainer.java
index c67a842..53acdeb 100644
--- a/javatests/com/google/gerrit/elasticsearch/ElasticContainer.java
+++ b/javatests/com/google/gerrit/elasticsearch/ElasticContainer.java
@@ -63,7 +63,7 @@
       case V7_4:
         return "blacktop/elasticsearch:7.4.2";
       case V7_5:
-        return "blacktop/elasticsearch:7.5.1";
+        return "blacktop/elasticsearch:7.5.2";
     }
     throw new IllegalStateException("No tests for version: " + version.name());
   }
diff --git a/polygerrit-ui/app/elements/admin/gr-permission/gr-permission.js b/polygerrit-ui/app/elements/admin/gr-permission/gr-permission.js
index e719154..508c3a2 100644
--- a/polygerrit-ui/app/elements/admin/gr-permission/gr-permission.js
+++ b/polygerrit-ui/app/elements/admin/gr-permission/gr-permission.js
@@ -271,7 +271,12 @@
       // API to work as expected.
       const groupId = decodeURIComponent(e.detail.value.id)
           .replace(/\+/g, ' ');
-      this.set(['permission', 'value', 'rules', groupId], {});
+      // We cannot use "this.set(...)" here, because groupId may contain dots,
+      // and dots in property path names are totally unsupported by Polymer.
+      // Apparently Polymer picks up this change anyway, otherwise we should
+      // have looked at using MutableData:
+      // https://polymer-library.polymer-project.org/2.0/docs/devguide/data-system#mutable-data
+      this.permission.value.rules[groupId] = {};
 
       // Purposely don't recompute sorted array so that the newly added rule
       // is the last item of the array.
@@ -292,7 +297,8 @@
       Polymer.dom.flush();
       const value = this._rules[this._rules.length - 1].value;
       value.added = true;
-      this.set(['permission', 'value', 'rules', groupId], value);
+      // See comment above for why we cannot use "this.set(...)" here.
+      this.permission.value.rules[groupId] = value;
       this.dispatchEvent(
           new CustomEvent('access-modified', {bubbles: true, composed: true}));
     }
diff --git a/polygerrit-ui/app/elements/admin/gr-permission/gr-permission_test.html b/polygerrit-ui/app/elements/admin/gr-permission/gr-permission_test.html
index 7919b28..9928b9e 100644
--- a/polygerrit-ui/app/elements/admin/gr-permission/gr-permission_test.html
+++ b/polygerrit-ui/app/elements/admin/gr-permission/gr-permission_test.html
@@ -312,11 +312,11 @@
         element.name = 'Priority';
         element.section = 'refs/*';
         element.groups = {};
-        element.$.groupAutocomplete.text = 'ldap/tests tests';
+        element.$.groupAutocomplete.text = 'ldap/tests te.st';
         const e = {
           detail: {
             value: {
-              id: 'ldap:CN=test+test',
+              id: 'ldap:CN=test+te.st',
             },
           },
         };
@@ -325,11 +325,11 @@
         assert.equal(Object.keys(element._groupsWithRules).length, 2);
         element._handleAddRuleItem(e);
         flushAsynchronousOperations();
-        assert.deepEqual(element.groups, {'ldap:CN=test test': {
-          name: 'ldap/tests tests'}});
+        assert.deepEqual(element.groups, {'ldap:CN=test te.st': {
+          name: 'ldap/tests te.st'}});
         assert.equal(element._rules.length, 3);
         assert.equal(Object.keys(element._groupsWithRules).length, 3);
-        assert.deepEqual(element.permission.value.rules['ldap:CN=test test'],
+        assert.deepEqual(element.permission.value.rules['ldap:CN=test te.st'],
             {action: 'ALLOW', min: -2, max: 2, added: true});
         // New rule should be removed if cancel from editing.
         element.editing = false;
diff --git a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.js b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.js
index a4ed899..c0a603e 100644
--- a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.js
+++ b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.js
@@ -1166,6 +1166,14 @@
       }
     }
 
+    _handleRevertSubmissionDialogConfirm() {
+      const el = this.$.confirmRevertSubmissionDialog;
+      this.$.overlay.close();
+      el.hidden = true;
+      this._fireAction('/revert_submission', this.actions.revert_submission,
+          false, {message: el.message});
+    }
+
     _handleAbandonDialogConfirm() {
       const el = this.$.confirmAbandonDialog;
       this.$.overlay.close();
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.html b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.html
index 471560d..02b017b 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.html
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.html
@@ -135,15 +135,15 @@
         line-height: var(--line-height-mono);
         margin-right: var(--spacing-l);
         margin-bottom: var(--spacing-l);
-        /* Account for border and padding */
-        max-width: calc(72ch + 2px + 2*var(--spacing-m));
+        /* Account for border and padding and rounding errors. */
+        max-width: calc(72ch + 2px + 2*var(--spacing-m) + 0.4px);
       }
       .commitMessage gr-linked-text {
         word-break: break-word;
       }
       #commitMessageEditor {
-        /* Account for border and padding */
-        min-width: calc(72ch + 2px + 2*var(--spacing-m));
+        /* Account for border and padding and rounding errors. */
+        min-width: calc(72ch + 2px + 2*var(--spacing-m) + 0.4px);
       }
       .editCommitMessage {
         margin-top: var(--spacing-l);
@@ -190,10 +190,6 @@
         height: 0;
         margin-bottom: var(--spacing-l);
       }
-      #commitMessage.collapsed {
-        max-height: 36em;
-        overflow: hidden;
-      }
       #relatedChanges.collapsed {
         margin-bottom: var(--spacing-l);
         max-height: var(--relation-chain-max-height, 2em);
@@ -449,12 +445,13 @@
               </div>
               <div
                   id="commitMessage"
-                  class$="commitMessage [[_computeCommitClass(_commitCollapsed, _latestCommitMessage)]]">
+                  class="commitMessage">
                 <gr-editable-content id="commitMessageEditor"
                     editing="[[_editingCommitMessage]]"
                     content="{{_latestCommitMessage}}"
                     storage-key="[[_computeCommitMessageKey(_change._number, _change.current_revision)]]"
-                    remove-zero-width-space>
+                    remove-zero-width-space
+                    collapsed$="[[_computeCommitMessageCollapsed(_commitCollapsed, _commitCollapsible)]]">
                   <gr-linked-text pre
                       content="[[_latestCommitMessage]]"
                       config="[[_projectConfig.commentlinks]]"
@@ -477,7 +474,7 @@
               <div
                   id="commitCollapseToggle"
                   class="collapseToggleContainer"
-                  hidden$="[[_computeCommitToggleHidden(_latestCommitMessage)]]">
+                  hidden$="[[!_commitCollapsible]]">
                 <gr-button
                     link
                     id="commitCollapseToggleButton"
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js
index 10f575b..7536c2c 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js
@@ -184,7 +184,8 @@
         _hideEditCommitMessage: {
           type: Boolean,
           computed: '_computeHideEditCommitMessage(_loggedIn, ' +
-            '_editingCommitMessage, _change, _editMode)',
+              '_editingCommitMessage, _change, _editMode, _commitCollapsed, ' +
+              '_commitCollapsible)',
         },
         _diffAgainst: String,
         /** @type {?string} */
@@ -243,10 +244,16 @@
           computed:
           '_computeChangeStatusChips(_change, _mergeable, _submitEnabled)',
         },
+        /** If false, then the "Show more" button was used to expand. */
         _commitCollapsed: {
           type: Boolean,
           value: true,
         },
+        /** Is the "Show more/less" button visible? */
+        _commitCollapsible: {
+          type: Boolean,
+          computed: '_computeCommitCollapsible(_latestCommitMessage)',
+        },
         _relatedChangesCollapsed: {
           type: Boolean,
           value: true,
@@ -543,10 +550,12 @@
       return this.changeStatuses(change, options);
     }
 
-    _computeHideEditCommitMessage(loggedIn, editing, change, editMode) {
+    _computeHideEditCommitMessage(
+        loggedIn, editing, change, editMode, collapsed, collapsible) {
       if (!loggedIn || editing ||
           (change && change.status === this.ChangeStatus.MERGED) ||
-          editMode) {
+          editMode ||
+          (collapsed && collapsible)) {
         return true;
       }
 
@@ -1589,9 +1598,8 @@
       return 'Change ' + changeNum;
     }
 
-    _computeCommitClass(collapsed, commitMessage) {
-      if (this._computeCommitToggleHidden(commitMessage)) { return ''; }
-      return collapsed ? 'collapsed' : '';
+    _computeCommitMessageCollapsed(collapsed, collapsible) {
+      return collapsible && collapsed;
     }
 
     _computeRelatedChangesClass(collapsed) {
@@ -1628,9 +1636,9 @@
       }
     }
 
-    _computeCommitToggleHidden(commitMessage) {
-      if (!commitMessage) { return true; }
-      return commitMessage.split('\n').length < MIN_LINES_FOR_COMMIT_COLLAPSE;
+    _computeCommitCollapsible(commitMessage) {
+      if (!commitMessage) { return false; }
+      return commitMessage.split('\n').length >= MIN_LINES_FOR_COMMIT_COLLAPSE;
     }
 
     _getOffsetHeight(element) {
@@ -1658,8 +1666,6 @@
       // bottom margin.
       const EXTRA_HEIGHT = 30;
       let newHeight;
-      const hasCommitToggle =
-          !this._computeCommitToggleHidden(this._latestCommitMessage);
 
       if (window.matchMedia(`(max-width: ${BREAKPOINT_RELATED_SMALL})`)
           .matches) {
@@ -1678,7 +1684,7 @@
             MINIMUM_RELATED_MAX_HEIGHT);
         newHeight = medRelatedHeight;
       } else {
-        if (hasCommitToggle) {
+        if (this._commitCollapsible) {
           // Make sure the content is lined up if both areas have buttons. If
           // the commit message is not collapsed, instead use the change info
           // height.
@@ -1701,7 +1707,7 @@
       stylesToUpdate['--relation-chain-max-height'] = newHeight + 'px';
 
       // Update the max-height of the relation chain to this new height.
-      if (hasCommitToggle) {
+      if (this._commitCollapsible) {
         stylesToUpdate['--related-change-btn-top-padding'] = remainder + 'px';
       }
 
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.html b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.html
index 9c23d0e..1ef79cf 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.html
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.html
@@ -1238,14 +1238,16 @@
       });
 
       test('commitCollapseToggle functions', () => {
-        element._latestCommitMessage = _.times(31, String).join('\n');
+        element._latestCommitMessage = _.times(35, String).join('\n');
         assert.isTrue(element._commitCollapsed);
+        assert.isTrue(element._commitCollapsible);
         assert.isTrue(
-            element.$.commitMessage.classList.contains('collapsed'));
+            element.$.commitMessageEditor.hasAttribute('collapsed'));
         MockInteractions.tap(element.$.commitCollapseToggleButton);
         assert.isFalse(element._commitCollapsed);
+        assert.isTrue(element._commitCollapsible);
         assert.isFalse(
-            element.$.commitMessage.classList.contains('collapsed'));
+            element.$.commitMessageEditor.hasAttribute('collapsed'));
       });
     });
 
diff --git a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.html b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.html
index fe64a1b..dea9789 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.html
@@ -37,11 +37,6 @@
       :host(.no-left) .sideBySide .right:not([data-value]) + td {
         display: none;
       }
-      ::slotted(*) .thread-group {
-        display: block;
-        max-width: var(--content-width, 80ch);
-        white-space: normal;
-      }
       :host {
         font-family: var(--monospace-font-family, ''), 'Roboto Mono';
         font-size: var(--font-size, var(--font-size-code, 12px));
@@ -52,6 +47,7 @@
         display: block;
         max-width: var(--content-width, 80ch);
         white-space: normal;
+        background-color: var(--diff-blank-background-color);
       }
       .diffContainer {
         display: flex;
@@ -67,7 +63,7 @@
         table-layout: fixed;
       }
       .lineNum {
-        background-color: var(--table-header-background-color);
+        background-color: var(--diff-blank-background-color);
       }
       .image-diff .gr-diff {
         text-align: center;
@@ -94,13 +90,16 @@
         color: var(--primary-text-color);
       }
       .content {
+        background-color: var(--diff-blank-background-color);
+      }
+      .contentText {
         background-color: var(--view-background-color);
       }
       .blank {
         background-color: var(--diff-blank-background-color);
       }
       .image-diff .content {
-        background-color: var(--table-header-background-color);
+        background-color: var(--diff-blank-background-color);
       }
       .full-width {
         width: 100%;
@@ -135,48 +134,48 @@
         min-width: var(--content-width, 80ch);
         width: var(--content-width, 80ch);
       }
-      .content.add .intraline,
+      .content.add .contentText .intraline,
       /* If there are no intraline info, consider everything changed */
-      .content.add.no-intraline-info,
-      .delta.total .content.add {
+      .content.add.no-intraline-info .contentText,
+      .delta.total .content.add .contentText {
         background-color: var(--dark-add-highlight-color);
       }
-      .content.add {
+      .content.add .contentText {
         background-color: var(--light-add-highlight-color);
       }
-      .content.remove .intraline,
+      .content.remove .contentText .intraline,
       /* If there are no intraline info, consider everything changed */
-      .content.remove.no-intraline-info,
-      .delta.total .content.remove {
+      .content.remove.no-intraline-info .contentText,
+      .delta.total .content.remove .contentText  {
         background-color: var(--dark-remove-highlight-color);
       }
-      .content.remove {
+      .content.remove .contentText {
         background-color: var(--light-remove-highlight-color);
       }
 
       /* dueToRebase */
-      .dueToRebase .content.add .intraline,
-      .delta.total.dueToRebase .content.add {
+      .dueToRebase .content.add .contentText .intraline,
+      .delta.total.dueToRebase .content.add .contentText {
         background-color: var(--dark-rebased-add-highlight-color);
       }
-      .dueToRebase .content.add {
+      .dueToRebase .content.add .contentText {
         background-color: var(--light-rebased-add-highlight-color);
       }
-      .dueToRebase .content.remove .intraline,
-      .delta.total.dueToRebase .content.remove {
+      .dueToRebase .content.remove .contentText .intraline,
+      .delta.total.dueToRebase .content.remove .contentText {
         background-color: var(--dark-rebased-remove-highlight-color);
       }
-      .dueToRebase .content.remove {
+      .dueToRebase .content.remove .contentText {
         background-color: var(--light-remove-add-highlight-color);
       }
 
       /* ignoredWhitespaceOnly */
-      .ignoredWhitespaceOnly .content.add .intraline,
-      .delta.total.ignoredWhitespaceOnly .content.add,
-      .ignoredWhitespaceOnly .content.add,
-      .ignoredWhitespaceOnly .content.remove .intraline,
-      .delta.total.ignoredWhitespaceOnly .content.remove,
-      .ignoredWhitespaceOnly .content.remove {
+      .ignoredWhitespaceOnly .content.add .contentText .intraline,
+      .delta.total.ignoredWhitespaceOnly .content.add .contentText,
+      .ignoredWhitespaceOnly .content.add .contentText,
+      .ignoredWhitespaceOnly .content.remove .contentText .intraline,
+      .delta.total.ignoredWhitespaceOnly .content.remove .contentText,
+      .ignoredWhitespaceOnly .content.remove .contentText {
         background: none;
       }
 
@@ -281,8 +280,7 @@
         width: 200px;
       }
       /** Support the line length indicator **/
-      .full-width td.content,
-      .full-width td.blank {
+      .full-width td.content .contentText {
         /* Base 64 encoded 1x1px of #ddd */
         background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mO8+x8AAr8B3gzOjaQAAAAASUVORK5CYII=');
         background-position: var(--line-limit) 0;
diff --git a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.js b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.js
index ef22188..5d32e9b 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.js
@@ -815,10 +815,9 @@
         // for each line from the start.
         let lastEl;
         for (const threadEl of addedThreadEls) {
-          const lineNumString = threadEl.getAttribute('line-num') || 'FILE';
           const commentSide = threadEl.getAttribute('comment-side');
-          const lineEl = this.$.diffBuilder.getLineElByNumber(
-              lineNumString, commentSide);
+          const lineEl = this._getLineElement(threadEl,
+              commentSide);
           const contentText = this.$.diffBuilder.getContentByLineEl(lineEl);
           const contentEl = contentText.parentElement;
           const threadGroupEl = this._getOrCreateThreadGroup(
@@ -844,6 +843,18 @@
       });
     }
 
+    _getLineElement(threadEl, commentSide) {
+      const lineNumString = threadEl.getAttribute('line-num') || 'FILE';
+      const lineEl = this.$.diffBuilder.getLineElByNumber(
+          lineNumString, commentSide);
+      if (lineEl) {
+        return lineEl;
+      }
+      // It is possible to add comment to non-existing line via API
+      threadEl.invalidLineNumber = true;
+      return this.$.diffBuilder.getLineElByNumber('FILE', commentSide);
+    }
+
     _unobserveIncrementalNodes() {
       if (this._incrementalNodeObserver) {
         Polymer.dom(this).unobserveNodes(this._incrementalNodeObserver);
diff --git a/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread.html b/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread.html
index cce1d19..7bc93b5 100644
--- a/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread.html
+++ b/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread.html
@@ -16,6 +16,8 @@
 -->
 
 <link rel="import" href="/bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/iron-icon/iron-icon.html">
+<link rel="import" href="../../shared/gr-icons/gr-icons.html">
 <link rel="import" href="../../../behaviors/fire-behavior/fire-behavior.html">
 <link rel="import" href="../../../behaviors/gr-path-list-behavior/gr-path-list-behavior.html">
 <link rel="import" href="../../../styles/shared-styles.html">
@@ -78,6 +80,18 @@
         margin-left: var(--spacing-m);
         font-style: italic;
       }
+      .invalidLineNumber {
+        padding: var(--spacing-m);
+      }
+      .invalidLineNumberText {
+        color: var(--error-text-color);
+      }
+      .invalidLineNumberIcon {
+        color: var(--error-text-color);
+        vertical-align: top;
+        margin-right: var(--spacing-s);
+      }
+
     </style>
     <template is="dom-if" if="[[showFilePath]]">
       <div class="pathInfo">
@@ -86,6 +100,11 @@
       </div>
     </template>
     <div id="container" class$="[[_computeHostClass(unresolved, isRobotComment)]]">
+      <template is="dom-if" if="[[invalidLineNumber]]">
+        <div class="invalidLineNumber">
+          <span class="invalidLineNumberText"><iron-icon icon="gr-icons:error" class="invalidLineNumberIcon"></iron-icon>This comment thread is attached to non-existing line [[lineNum]].</span>
+        </div>
+      </template>
       <template id="commentList" is="dom-repeat" items="[[_orderedComments]]"
           as="comment">
         <gr-comment
diff --git a/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread.js b/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread.js
index d8a56f8..00ff03a 100644
--- a/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread.js
+++ b/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread.js
@@ -124,6 +124,11 @@
           type: Boolean,
           value: false,
         },
+        /** It is possible to add comment to non-existing line via API */
+        invalidLineNumber: {
+          type: Number,
+          reflectToAttribute: true,
+        },
         /** Necessary only if showFilePath is true or when used with gr-diff */
         lineNum: {
           type: Number,
diff --git a/polygerrit-ui/app/elements/shared/gr-editable-content/gr-editable-content.html b/polygerrit-ui/app/elements/shared/gr-editable-content/gr-editable-content.html
index 69c280b..627f948 100644
--- a/polygerrit-ui/app/elements/shared/gr-editable-content/gr-editable-content.html
+++ b/polygerrit-ui/app/elements/shared/gr-editable-content/gr-editable-content.html
@@ -38,6 +38,10 @@
         box-shadow: var(--elevation-level-1);
         padding: var(--spacing-m);
       }
+      :host([collapsed]) .viewer {
+        max-height: 36em;
+        overflow: hidden;
+      }
       .editor iron-autogrow-textarea {
         background-color: var(--view-background-color);
         width: 100%;
diff --git a/polygerrit-ui/app/elements/shared/gr-icons/gr-icons.html b/polygerrit-ui/app/elements/shared/gr-icons/gr-icons.html
index 743923b..5ecae00 100644
--- a/polygerrit-ui/app/elements/shared/gr-icons/gr-icons.html
+++ b/polygerrit-ui/app/elements/shared/gr-icons/gr-icons.html
@@ -54,6 +54,8 @@
       <g id="info"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z"></path></g>
       <!-- This SVG is a copy from material.io https://material.io/icons/#ic_hourglass_full-->
       <g id="hourglass"><path d="M6 2v6h.01L6 8.01 10 12l-4 4 .01.01H6V22h12v-5.99h-.01L18 16l-4-4 4-3.99-.01-.01H18V2H6z"/><path d="M0 0h24v24H0V0z" fill="none"/></g>
+      <!-- This SVG is a copy from iron-icons https://github.com/PolymerElements/iron-icons/blob/master/iron-icons.js -->
+      <g id="error"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z"></path></g>
       <!-- This is a custom PolyGerrit SVG -->
       <g id="side-by-side"><path d="M17.1578947,10.8888889 L2.84210526,10.8888889 C2.37894737,10.8888889 2,11.2888889 2,11.7777778 L2,17.1111111 C2,17.6 2.37894737,18 2.84210526,18 L17.1578947,18 C17.6210526,18 18,17.6 18,17.1111111 L18,11.7777778 C18,11.2888889 17.6210526,10.8888889 17.1578947,10.8888889 Z M17.1578947,2 L2.84210526,2 C2.37894737,2 2,2.4 2,2.88888889 L2,8.22222222 C2,8.71111111 2.37894737,9.11111111 2.84210526,9.11111111 L17.1578947,9.11111111 C17.6210526,9.11111111 18,8.71111111 18,8.22222222 L18,2.88888889 C18,2.4 17.6210526,2 17.1578947,2 Z M16.1973628,2 L2.78874238,2 C2.35493407,2 2,2.4 2,2.88888889 L2,8.22222222 C2,8.71111111 2.35493407,9.11111111 2.78874238,9.11111111 L16.1973628,9.11111111 C16.6311711,9.11111111 16.9861052,8.71111111 16.9861052,8.22222222 L16.9861052,2.88888889 C16.9861052,2.4 16.6311711,2 16.1973628,2 Z" id="Shape" transform="scale(1.2) translate(10.000000, 10.000000) rotate(-90.000000) translate(-10.000000, -10.000000)"/></g>
       <!-- This is a custom PolyGerrit SVG -->
diff --git a/polygerrit-ui/app/styles/themes/app-theme.html b/polygerrit-ui/app/styles/themes/app-theme.html
index 369af2b..1378f33 100644
--- a/polygerrit-ui/app/styles/themes/app-theme.html
+++ b/polygerrit-ui/app/styles/themes/app-theme.html
@@ -59,15 +59,17 @@
   --view-background-color: var(--background-color-primary);
   /* unique background colors */
   --assignee-highlight-color: #fcfad6;
-  --comment-background-color: #fcfad6;
-  --robot-comment-background-color: #e8f0fe;
   --edit-mode-background-color: #ebf5fb;
   --emphasis-color: #fff9c4;
   --hover-background-color: rgba(161, 194, 250, 0.2);
   --primary-button-background-color: #2a66d9;
   --selection-background-color: rgba(161, 194, 250, 0.1);
   --tooltip-background-color: #333;
-  --unresolved-comment-background-color: #fcfaa6;
+  /* comment background colors */
+  --comment-background-color: #fef7f0;
+  --robot-comment-background-color: #e8f0fe;
+  --unresolved-comment-background-color: #e8eaed;
+  /* vote background colors */
   --vote-color-approved: #9fcc6b;
   --vote-color-disliked: #f7c4cb;
   --vote-color-neutral: #ebf5fb;
@@ -125,7 +127,7 @@
   --dark-rebased-add-highlight-color: #d7d7f9;
   --dark-rebased-remove-highlight-color: #f7e8b7;
   --dark-remove-highlight-color: #ffcdd2;
-  --diff-blank-background-color: white;
+  --diff-blank-background-color: var(--background-color-secondary);
   --diff-context-control-background-color: #fff7d4;
   --diff-context-control-border-color: #f6e6a5;
   --diff-context-control-color: var(--deemphasized-text-color);
diff --git a/polygerrit-ui/app/styles/themes/dark-theme.html b/polygerrit-ui/app/styles/themes/dark-theme.html
index 80243dc..56452cb 100644
--- a/polygerrit-ui/app/styles/themes/dark-theme.html
+++ b/polygerrit-ui/app/styles/themes/dark-theme.html
@@ -49,15 +49,17 @@
       /*   empty, because inheriting from app-theme is just fine
       /* unique background colors */
       --assignee-highlight-color: #3a361c;
-      --comment-background-color: #0b162b;
-      --robot-comment-background-color: rgba(232, 234, 237, 0.08);
       --edit-mode-background-color: #5c0a36;
       --emphasis-color: #383f4a;
       --hover-background-color: rgba(161, 194, 250, 0.2);
       --primary-button-background-color: var(--link-color);
       --selection-background-color: rgba(161, 194, 250, 0.1);
       --tooltip-background-color: #111;
-      --unresolved-comment-background-color: #385a9a;
+      /* comment background colors */
+      --comment-background-color: #303134;
+      --robot-comment-background-color: #4B5058;
+      --unresolved-comment-background-color: #545146;
+      /* vote background colors */
       --vote-color-approved: #7fb66b;
       --vote-color-disliked: #bf6874;
       --vote-color-neutral: #597280;
@@ -85,7 +87,7 @@
       --dark-rebased-add-highlight-color: rgba(11, 255, 155, 0.15);
       --dark-rebased-remove-highlight-color: rgba(255, 139, 6, 0.15);
       --dark-remove-highlight-color: #62110f;
-      --diff-blank-background-color: #212121;
+      --diff-blank-background-color: var(--background-color-secondary);
       --diff-context-control-background-color: #333311;
       --diff-context-control-border-color: var(--border-color);
       --diff-context-control-color: var(--deemphasized-text-color);
diff --git a/tools/bzl/asciidoc.bzl b/tools/bzl/asciidoc.bzl
index f3c4646..1e7ec96 100644
--- a/tools/bzl/asciidoc.bzl
+++ b/tools/bzl/asciidoc.bzl
@@ -1,7 +1,7 @@
 def documentation_attributes():
     return [
         "toc2",
-        'newline="\\n"',
+        "newline=\"\\n\"",
         'asterisk="&#42;"',
         'plus="&#43;"',
         'caret="&#94;"',
diff --git a/tools/nongoogle.bzl b/tools/nongoogle.bzl
index 3af4a28..acf01fa 100644
--- a/tools/nongoogle.bzl
+++ b/tools/nongoogle.bzl
@@ -102,8 +102,8 @@
     # and httpasyncclient as necessary.
     maven_jar(
         name = "elasticsearch-rest-client",
-        artifact = "org.elasticsearch.client:elasticsearch-rest-client:7.5.1",
-        sha1 = "094c155906dc94146fc5adc344ea2c676d487cf2",
+        artifact = "org.elasticsearch.client:elasticsearch-rest-client:7.5.2",
+        sha1 = "e11393f600a425b7f62e6f653e19a9e53556fd79",
     )
 
     maven_jar(