Merge "Collapsing related changes"
diff --git a/.bazelversion b/.bazelversion
index 7c69a55d..fcdb2e1 100644
--- a/.bazelversion
+++ b/.bazelversion
@@ -1 +1 @@
-3.7.0
+4.0.0
diff --git a/Documentation/licenses.txt b/Documentation/licenses.txt
index cd794b8..b7cdf8a 100644
--- a/Documentation/licenses.txt
+++ b/Documentation/licenses.txt
@@ -925,6 +925,18 @@
 ----
 
 
+[[PublicDomain]]
+PublicDomain
+
+* guice:aopalliance
+
+[[PublicDomain_license]]
+----
+This software has been placed in the public domain by its author(s).
+
+----
+
+
 [[antlr]]
 antlr
 
diff --git a/WORKSPACE b/WORKSPACE
index c35c190..6cb56ed 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -140,6 +140,12 @@
 )
 
 maven_jar(
+    name = "aopalliance",
+    artifact = "aopalliance:aopalliance:1.0",
+    sha1 = "0235ba8b489512805ac13a8f9ea77a1ca5ebe3e8",
+)
+
+maven_jar(
     name = "javax_inject",
     artifact = "javax.inject:javax.inject:1",
     sha1 = "6975da39a7040257bd51d21a231b76c915872d38",
diff --git a/java/com/google/gerrit/entities/RefNames.java b/java/com/google/gerrit/entities/RefNames.java
index 522c60a..b6efcbf 100644
--- a/java/com/google/gerrit/entities/RefNames.java
+++ b/java/com/google/gerrit/entities/RefNames.java
@@ -139,6 +139,11 @@
     return ref;
   }
 
+  /**
+   * Warning: Change refs have to manually be advertised in {@link
+   * com.google.gerrit.server.permissions.DefaultRefFilter}; this should be done when adding new
+   * change refs.
+   */
   public static String changeMetaRef(Change.Id id) {
     StringBuilder r = newStringBuilder().append(REFS_CHANGES);
     return shard(id.get(), r).append(META_SUFFIX).toString();
diff --git a/java/com/google/gerrit/server/notedb/ChangeNotes.java b/java/com/google/gerrit/server/notedb/ChangeNotes.java
index e8f1fe1..cf09ff3 100644
--- a/java/com/google/gerrit/server/notedb/ChangeNotes.java
+++ b/java/com/google/gerrit/server/notedb/ChangeNotes.java
@@ -512,6 +512,7 @@
   }
 
   public RobotCommentNotes getRobotCommentNotes() {
+    loadRobotComments();
     return robotCommentNotes;
   }
 
diff --git a/java/com/google/gerrit/server/patch/filediff/FileDiffCacheImpl.java b/java/com/google/gerrit/server/patch/filediff/FileDiffCacheImpl.java
index b2a628b..ad61753 100644
--- a/java/com/google/gerrit/server/patch/filediff/FileDiffCacheImpl.java
+++ b/java/com/google/gerrit/server/patch/filediff/FileDiffCacheImpl.java
@@ -246,7 +246,7 @@
         RawTextComparator rawTextComparator,
         GitFileDiffCacheImpl.DiffAlgorithm diffAlgorithm)
         throws IOException {
-      Text aText = newCommit != null ? Text.forCommit(reader, newCommit) : Text.EMPTY;
+      Text aText = oldCommit != null ? Text.forCommit(reader, oldCommit) : Text.EMPTY;
       Text bText = Text.forCommit(reader, newCommit);
       return createMagicFileDiffOutput(
           rawTextComparator, oldCommit, aText, bText, Patch.COMMIT_MSG, diffAlgorithm);
diff --git a/java/com/google/gerrit/server/permissions/DefaultRefFilter.java b/java/com/google/gerrit/server/permissions/DefaultRefFilter.java
index 6272cda..e88a840 100644
--- a/java/com/google/gerrit/server/permissions/DefaultRefFilter.java
+++ b/java/com/google/gerrit/server/permissions/DefaultRefFilter.java
@@ -211,6 +211,14 @@
                 refs.add(
                     new ObjectIdRef.PeeledNonTag(
                         Storage.PACKED, RefNames.patchSetRef(p.id()), p.commitId())));
+    if (changeNotes.getRobotCommentNotes() != null
+        && changeNotes.getRobotCommentNotes().getMetaId() != null) {
+      refs.add(
+          new ObjectIdRef.PeeledNonTag(
+              Storage.PACKED,
+              RefNames.robotCommentsRef(changeNotes.getChangeId()),
+              changeNotes.getRobotCommentNotes().getMetaId()));
+    }
   }
 
   /**
diff --git a/java/com/google/gerrit/util/cli/CmdLineParser.java b/java/com/google/gerrit/util/cli/CmdLineParser.java
index 162f324..c374691 100644
--- a/java/com/google/gerrit/util/cli/CmdLineParser.java
+++ b/java/com/google/gerrit/util/cli/CmdLineParser.java
@@ -562,20 +562,22 @@
      *
      * @param name name
      * @return the {@code OptionHandler} or {@code null}
-     *     <p>Note: this is cut & pasted from the parent class in arg4j, it was private and it
-     *     needed to be exposed.
+     *     <p>Note: this was originally cut & pasted from the parent class in arg4j, it was private
+     *     and it needed to be exposed.
      */
     @SuppressWarnings("rawtypes")
     public OptionHandler findOptionByName(String name) {
       for (OptionHandler h : optionsList) {
-        NamedOptionDef option = (NamedOptionDef) h.option;
-        if (name.equals(option.name())) {
-          return h;
-        }
-        for (String alias : option.aliases()) {
-          if (name.equals(alias)) {
+        if (h.option instanceof NamedOptionDef) {
+          NamedOptionDef option = (NamedOptionDef) h.option;
+          if (name.equals(option.name())) {
             return h;
           }
+          for (String alias : option.aliases()) {
+            if (name.equals(alias)) {
+              return h;
+            }
+          }
         }
       }
       return null;
diff --git a/javatests/com/google/gerrit/acceptance/git/RefAdvertisementIT.java b/javatests/com/google/gerrit/acceptance/git/RefAdvertisementIT.java
index 9976fbc..5699a04 100644
--- a/javatests/com/google/gerrit/acceptance/git/RefAdvertisementIT.java
+++ b/javatests/com/google/gerrit/acceptance/git/RefAdvertisementIT.java
@@ -45,6 +45,7 @@
 import com.google.gerrit.entities.Project;
 import com.google.gerrit.entities.RefNames;
 import com.google.gerrit.extensions.api.changes.DraftInput;
+import com.google.gerrit.extensions.api.changes.ReviewInput.RobotCommentInput;
 import com.google.gerrit.extensions.api.groups.GroupInput;
 import com.google.gerrit.extensions.api.projects.BranchInput;
 import com.google.gerrit.extensions.restapi.RestApiException;
@@ -58,6 +59,7 @@
 import com.google.gerrit.server.permissions.PermissionBackend.RefFilterOptions;
 import com.google.gerrit.server.query.change.ChangeData;
 import com.google.gerrit.testing.ConfigSuite;
+import com.google.gerrit.testing.TestCommentHelper;
 import com.google.inject.Inject;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -86,6 +88,7 @@
   @Inject private PermissionBackend permissionBackend;
   @Inject private ProjectOperations projectOperations;
   @Inject private RequestScopeOperations requestScopeOperations;
+  @Inject private TestCommentHelper testCommentHelper;
 
   private AccountGroup.UUID admins;
   private AccountGroup.UUID nonInteractiveUsers;
@@ -1597,6 +1600,84 @@
   }
 
   @Test
+  public void advertiseMostRecentRefChangesWithRobotCommentRef() throws Exception {
+    projectOperations
+        .project(project)
+        .forUpdate()
+        .add(allow(Permission.READ).ref("refs/heads/master").group(REGISTERED_USERS))
+        .update();
+
+    // user doesn't have refs/* permission.
+    requestScopeOperations.setApiUser(user.id());
+    RobotCommentInput input = TestCommentHelper.createRobotCommentInput(Patch.COMMIT_MSG);
+    testCommentHelper.addRobotComment(cd1.getId(), input);
+
+    try (Repository repo = repoManager.openRepository(project)) {
+      PermissionBackend.ForProject forProject = newFilter(project, admin);
+      assertThat(
+              names(
+                  forProject.filter(
+                      repo.getRefDatabase().getRefs(),
+                      repo,
+                      RefFilterOptions.builder().setReturnMostRecentRefChanges(false).build())))
+          .containsExactlyElementsIn(
+              ImmutableList.of(
+                  "HEAD",
+                  RefNames.changeRefPrefix(cd1.getId()) + "robot-comments",
+                  psRef1,
+                  metaRef1,
+                  psRef2,
+                  metaRef2,
+                  psRef3,
+                  metaRef3,
+                  psRef4,
+                  metaRef4,
+                  "refs/heads/branch",
+                  "refs/heads/master",
+                  "refs/meta/config",
+                  "refs/tags/branch-tag",
+                  "refs/tags/master-tag",
+                  "refs/tags/tree-tag"));
+    }
+  }
+
+  @Test
+  public void advertiseMostRecentRefChangesWithRobotCommentRefWithReturnMostRecentRefChanges()
+      throws Exception {
+    projectOperations
+        .project(project)
+        .forUpdate()
+        .add(allow(Permission.READ).ref("refs/heads/master").group(REGISTERED_USERS))
+        .update();
+
+    // user doesn't have refs/* permission.
+    requestScopeOperations.setApiUser(user.id());
+    RobotCommentInput input = TestCommentHelper.createRobotCommentInput(Patch.COMMIT_MSG);
+    testCommentHelper.addRobotComment(cd1.getId(), input);
+
+    try (Repository repo = repoManager.openRepository(project)) {
+      PermissionBackend.ForProject forProject = newFilter(project, admin);
+      assertThat(
+              names(
+                  forProject.filter(
+                      ImmutableList.of(),
+                      repo,
+                      RefFilterOptions.builder().setReturnMostRecentRefChanges(true).build())))
+          .containsExactlyElementsIn(
+              ImmutableList.of(
+                  RefNames.changeRefPrefix(cd1.getId()) + "robot-comments",
+                  psRef1,
+                  metaRef1,
+                  psRef2,
+                  metaRef2,
+                  psRef3,
+                  metaRef3,
+                  psRef4,
+                  metaRef4));
+    }
+  }
+
+  @Test
   public void fetchSingleChangeWithoutIndexAccess() throws Exception {
     PushOneCommit.Result change = createChange();
     String patchSetRef = change.getPatchSetId().toRefName();
diff --git a/javatests/com/google/gerrit/server/git/MultiBaseLocalDiskRepositoryManagerTest.java b/javatests/com/google/gerrit/server/git/MultiBaseLocalDiskRepositoryManagerTest.java
index 29f520b..4902830 100644
--- a/javatests/com/google/gerrit/server/git/MultiBaseLocalDiskRepositoryManagerTest.java
+++ b/javatests/com/google/gerrit/server/git/MultiBaseLocalDiskRepositoryManagerTest.java
@@ -93,12 +93,14 @@
     Repository repo = repoManager.createRepository(someProjectKey);
     assertThat(repo.getDirectory()).isNotNull();
     assertThat(repo.getDirectory().exists()).isTrue();
-    assertThat(repo.getDirectory().getParent()).isEqualTo(alternateBasePath.toString());
+    assertThat(repo.getDirectory().getParent())
+        .isEqualTo(alternateBasePath.toRealPath().toString());
 
     repo = repoManager.openRepository(someProjectKey);
     assertThat(repo.getDirectory()).isNotNull();
     assertThat(repo.getDirectory().exists()).isTrue();
-    assertThat(repo.getDirectory().getParent()).isEqualTo(alternateBasePath.toString());
+    assertThat(repo.getDirectory().getParent())
+        .isEqualTo(alternateBasePath.toRealPath().toString());
 
     assertThat(repoManager.getBasePath(someProjectKey).toAbsolutePath().toString())
         .isEqualTo(alternateBasePath.toString());
diff --git a/lib/LICENSE-PublicDomain b/lib/LICENSE-PublicDomain
new file mode 100644
index 0000000..8a71ce0
--- /dev/null
+++ b/lib/LICENSE-PublicDomain
@@ -0,0 +1 @@
+This software has been placed in the public domain by its author(s).
diff --git a/lib/guice/BUILD b/lib/guice/BUILD
index 14179d6..f73984b 100644
--- a/lib/guice/BUILD
+++ b/lib/guice/BUILD
@@ -1,9 +1,4 @@
-load("@rules_java//java:defs.bzl", "java_import", "java_library")
-
-java_import(
-    name = "guice-library-no-aop",
-    jars = ["@guice-library-no-aop//file"],
-)
+load("@rules_java//java:defs.bzl", "java_library")
 
 java_library(
     name = "guice",
@@ -19,7 +14,8 @@
     name = "guice-library",
     data = ["//lib:LICENSE-Apache2.0"],
     visibility = ["//visibility:public"],
-    exports = [":guice-library-no-aop"],
+    exports = ["@guice-library//jar"],
+    runtime_deps = ["aopalliance"],
 )
 
 java_library(
@@ -39,6 +35,12 @@
 )
 
 java_library(
+    name = "aopalliance",
+    data = ["//lib:LICENSE-PublicDomain"],
+    exports = ["@aopalliance//jar"],
+)
+
+java_library(
     name = "javax_inject",
     data = ["//lib:LICENSE-Apache2.0"],
     visibility = ["//visibility:public"],
diff --git a/lib/nongoogle_test.sh b/lib/nongoogle_test.sh
index f94486c..f596164 100755
--- a/lib/nongoogle_test.sh
+++ b/lib/nongoogle_test.sh
@@ -23,7 +23,7 @@
 flogger-system-backend
 guava
 guice-assistedinject
-guice-library-no-aop
+guice-library
 guice-servlet
 httpasyncclient
 httpcore-nio
diff --git a/polygerrit-ui/app/api/diff.ts b/polygerrit-ui/app/api/diff.ts
index b5624c6..7e619c2 100644
--- a/polygerrit-ui/app/api/diff.ts
+++ b/polygerrit-ui/app/api/diff.ts
@@ -225,7 +225,7 @@
   code_range: LineRange;
 }
 
-export declare type LineNumber = number | 'FILE';
+export declare type LineNumber = number | 'FILE' | 'LOST';
 
 /** The detail of the 'create-comment' event dispatched by gr-diff. */
 export declare interface CreateCommentEventDetail {
diff --git a/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.ts b/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.ts
index 7008b48..841ee6e 100644
--- a/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.ts
+++ b/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.ts
@@ -57,7 +57,6 @@
 import {DashboardViewState} from '../../../types/types';
 import {firePageError, fireTitleChange} from '../../../utils/event-util';
 import {GerritView} from '../../../services/router/router-model';
-import {KnownExperimentId} from '../../../services/flags/flags';
 
 const PROJECT_PLACEHOLDER_PATTERN = /\$\{project\}/g;
 const RELOAD_DASHBOARD_INTERVAL_MS = 10 * 1000;
@@ -124,8 +123,6 @@
 
   private restApiService = appContext.restApiService;
 
-  private flagService = appContext.flagsService;
-
   private lastVisibleTimestampMs = 0;
 
   constructor() {
@@ -140,19 +137,17 @@
       e.stopPropagation();
       this._reload(this.params);
     });
-    if (this.flagService.isEnabled(KnownExperimentId.AUTO_RELOAD_DASHBOARD)) {
-      document.addEventListener('visibilitychange', () => {
-        if (document.visibilityState === 'visible') {
-          if (
-            Date.now() - this.lastVisibleTimestampMs >
-            RELOAD_DASHBOARD_INTERVAL_MS
-          )
-            this._reload(this.params);
-        } else {
-          this.lastVisibleTimestampMs = Date.now();
-        }
-      });
-    }
+    document.addEventListener('visibilitychange', () => {
+      if (document.visibilityState === 'visible') {
+        if (
+          Date.now() - this.lastVisibleTimestampMs >
+          RELOAD_DASHBOARD_INTERVAL_MS
+        )
+          this._reload(this.params);
+      } else {
+        this.lastVisibleTimestampMs = Date.now();
+      }
+    });
   }
 
   _loadPreferences() {
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.ts b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.ts
index 0ee9ad3..4a92869 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.ts
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.ts
@@ -372,13 +372,6 @@
   })
   _hideEditCommitMessage?: boolean;
 
-  @property({
-    type: Boolean,
-    computed:
-      '_computeHideShowAllContainer(_hideEditCommitMessage, _commitCollapsible)',
-  })
-  _hideShowAllContainer = false;
-
   @property({type: String})
   _diffAgainst?: string;
 
@@ -932,13 +925,6 @@
     return changeStatuses(change, options);
   }
 
-  _computeHideShowAllContainer(
-    _hideEditCommitMessage?: boolean,
-    _commitCollapsible?: boolean
-  ) {
-    return !_commitCollapsible && _hideEditCommitMessage;
-  }
-
   _computeHideEditCommitMessage(
     loggedIn: boolean,
     editing: boolean,
@@ -2380,6 +2366,9 @@
   }
 
   _computeCommitMessageCollapsed(collapsed?: boolean, collapsible?: boolean) {
+    if (this._isNewChangeSummaryUiEnabled) {
+      return false;
+    }
     return collapsible && collapsed;
   }
 
@@ -2388,9 +2377,6 @@
   }
 
   _computeCollapseText(collapsed: boolean) {
-    if (this._isNewChangeSummaryUiEnabled) {
-      return collapsed ? 'Show all' : 'Show less';
-    }
     // Symbols are up and down triangles.
     return collapsed ? '\u25bc Show more' : '\u25b2 Show less';
   }
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_html.ts b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_html.ts
index ed33916..3c37ec7 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_html.ts
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_html.ts
@@ -109,25 +109,6 @@
       /* Account for border and padding and rounding errors. */
       max-width: calc(72ch + 2px + 2 * var(--spacing-m) + 0.4px);
     }
-    .show-all-container {
-      background-color: var(--view-background-color);
-      display: flex;
-      justify-content: flex-end;
-      margin-bottom: 8px;
-      border-top-width: 1px;
-      border-top-style: solid;
-      border-radius: 0 0 4px 4px;
-      border-color: var(--border-color);
-      box-shadow: var(--elevation-level-1);
-    }
-    .show-all-container .show-all-button {
-      margin-right: auto;
-    }
-    .show-all-container iron-icon {
-      color: inherit;
-      --iron-icon-height: 18px;
-      --iron-icon-width: 18px;
-    }
     .commitMessage gr-linked-text {
       word-break: break-word;
     }
@@ -139,9 +120,6 @@
     .new-change-summary-true #commitMessageEditor {
       --collapsed-max-height: 300px;
     }
-    .new-change-summary-true gr-linked-text {
-      min-height: 160px;
-    }
     .editCommitMessage {
       margin-top: var(--spacing-l);
 
@@ -493,9 +471,11 @@
               >
                 <gr-editable-content
                   id="commitMessageEditor"
-                  editing="[[_editingCommitMessage]]"
+                  editing="{{_editingCommitMessage}}"
                   content="{{_latestCommitMessage}}"
                   storage-key="[[_computeCommitMessageKey(_change._number, _change.current_revision)]]"
+                  hide-edit-commit-message="[[_hideEditCommitMessage]]"
+                  commit-collapsible="[[_commitCollapsible]]"
                   remove-zero-width-space=""
                   collapsed$="[[_computeCommitMessageCollapsed(_commitCollapsed, _commitCollapsible)]]"
                 >
@@ -506,37 +486,6 @@
                     remove-zero-width-space=""
                   ></gr-linked-text>
                 </gr-editable-content>
-                <template is="dom-if" if="[[_isNewChangeSummaryUiEnabled]]">
-                  <div
-                    class="show-all-container"
-                    hidden$="[[_hideShowAllContainer]]"
-                  >
-                    <gr-button
-                      link=""
-                      class="show-all-button"
-                      on-click="_toggleCommitCollapsed"
-                      hidden$="[[!_commitCollapsible]]"
-                      ><iron-icon
-                        icon="gr-icons:expand-more"
-                        hidden$="[[!_commitCollapsed]]"
-                      ></iron-icon
-                      ><iron-icon
-                        icon="gr-icons:expand-less"
-                        hidden$="[[_commitCollapsed]]"
-                      ></iron-icon>
-                      [[_computeCollapseText(_commitCollapsed)]]
-                    </gr-button>
-                    <gr-button
-                      link=""
-                      class="edit-commit-message"
-                      title="Edit commit message"
-                      on-click="_handleEditCommitMessage"
-                      hidden$="[[_hideEditCommitMessage]]"
-                      ><iron-icon icon="gr-icons:edit"></iron-icon>
-                      Edit</gr-button
-                    >
-                  </div>
-                </template>
                 <template is="dom-if" if="[[!_isNewChangeSummaryUiEnabled]]">
                   <gr-button
                     link=""
diff --git a/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog.ts b/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog.ts
index 5949513..7fbe4b5 100644
--- a/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog.ts
+++ b/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog.ts
@@ -20,7 +20,7 @@
 import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
 import {PolymerElement} from '@polymer/polymer/polymer-element';
 import {htmlTemplate} from './gr-download-dialog_html';
-import {changeBaseURL} from '../../../utils/change-util';
+import {changeBaseURL, getRevisionKey} from '../../../utils/change-util';
 import {customElement, property, computed, observe} from '@polymer/decorators';
 import {ChangeInfo, ServerInfo, PatchSetNum} from '../../../types/common';
 import {RevisionInfo} from '../../shared/revision-info/revision-info';
@@ -168,13 +168,9 @@
       return '';
     }
 
-    let shortRev = '';
-    for (const rev in change.revisions) {
-      if (change.revisions[rev]._number === patchNum) {
-        shortRev = rev.substr(0, 7);
-        break;
-      }
-    }
+    const rev = getRevisionKey(change, patchNum) ?? '';
+    const shortRev = rev.substr(0, 7);
+
     return shortRev + '.diff.' + (zip ? 'zip' : 'base64');
   }
 
diff --git a/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header_html.ts b/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header_html.ts
index 1e72ae9..af72b67 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header_html.ts
+++ b/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header_html.ts
@@ -47,6 +47,9 @@
     .patchInfoOldPatchSet .container.latestPatchContainer {
       display: initial;
     }
+    .editMode.patchInfoOldPatchSet .container.latestPatchContainer {
+      display: none;
+    }
     .latestPatchContainer a {
       text-decoration: none;
     }
diff --git a/polygerrit-ui/app/elements/change/gr-message/gr-message_html.ts b/polygerrit-ui/app/elements/change/gr-message/gr-message_html.ts
index 070aa2d..6515af0 100644
--- a/polygerrit-ui/app/elements/change/gr-message/gr-message_html.ts
+++ b/polygerrit-ui/app/elements/change/gr-message/gr-message_html.ts
@@ -96,7 +96,9 @@
       margin-right: var(--spacing-s);
     }
     .authorLabel {
-      width: 140px;
+      min-width: 130px;
+      --account-max-length: 120px;
+      margin-right: var(--spacing-s);
     }
     .expanded .author {
       cursor: pointer;
diff --git a/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list.ts b/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list.ts
index 3503b9b..4ad99d5 100644
--- a/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list.ts
+++ b/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list.ts
@@ -26,7 +26,7 @@
 import {GerritNav} from '../../core/gr-navigation/gr-navigation';
 import {ChangeStatus} from '../../../constants/constants';
 
-import {changeIsOpen} from '../../../utils/change-util';
+import {changeIsOpen, getRevisionKey} from '../../../utils/change-util';
 import {getPluginEndpoints} from '../../shared/gr-js-api-interface/gr-plugin-endpoints';
 import {customElement, observe, property} from '@polymer/decorators';
 import {
@@ -395,25 +395,12 @@
     patchNum?: PatchSetNum,
     relatedChanges?: RelatedChangeAndCommitInfo[]
   ) {
-    // Polymer 2: check for undefined
-    if (
-      change === undefined ||
-      patchNum === undefined ||
-      relatedChanges === undefined
-    ) {
-      return undefined;
+    if (!patchNum || !relatedChanges || !change) {
+      return [];
     }
 
     const connected: CommitId[] = [];
-    let changeRevision;
-    if (!change) {
-      return [];
-    }
-    for (const rev in change.revisions) {
-      if (change.revisions[rev]._number === patchNum) {
-        changeRevision = rev;
-      }
-    }
+    const changeRevision = getRevisionKey(change, patchNum);
     const commits = relatedChanges.map(c => c.commit);
     let pos = commits.length - 1;
 
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-element_test.js b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-element_test.js
index 229ca76..0ae0e84 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-element_test.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-element_test.js
@@ -866,7 +866,7 @@
     });
 
     test('getSectionsByLineRange one line', () => {
-      const section = outputEl.querySelector('stub:nth-of-type(2)');
+      const section = outputEl.querySelector('stub:nth-of-type(3)');
       const sections = element._builder.getSectionsByLineRange(1, 1, 'left');
       assert.equal(sections.length, 1);
       assert.strictEqual(sections[0], section);
@@ -874,8 +874,8 @@
 
     test('getSectionsByLineRange over diff', () => {
       const section = [
-        outputEl.querySelector('stub:nth-of-type(2)'),
         outputEl.querySelector('stub:nth-of-type(3)'),
+        outputEl.querySelector('stub:nth-of-type(4)'),
       ];
       const sections = element._builder.getSectionsByLineRange(1, 2, 'left');
       assert.equal(sections.length, 2);
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.ts b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.ts
index 1d95057..da1b928 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.ts
@@ -541,7 +541,10 @@
       td.classList.add('lineNum');
       td.dataset['value'] = number.toString();
 
-      if (this._prefs.show_file_comment_button === false && number === 'FILE') {
+      if (
+        (this._prefs.show_file_comment_button === false && number === 'FILE') ||
+        number === 'LOST'
+      ) {
         return td;
       }
 
@@ -589,7 +592,7 @@
     }
     td.classList.add(line.type);
 
-    if (line.beforeNumber !== 'FILE') {
+    if (line.beforeNumber !== 'FILE' && line.beforeNumber !== 'LOST') {
       const lineLimit = !this._prefs.line_wrapping
         ? this._prefs.line_length
         : Infinity;
@@ -614,9 +617,8 @@
       }
 
       td.appendChild(contentText);
-    } else {
-      td.classList.add('file');
-    }
+    } else if (line.beforeNumber === 'FILE') td.classList.add('file');
+    else if (line.beforeNumber === 'LOST') td.classList.add('lost');
 
     return td;
   }
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor_test.js b/polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor_test.js
index 4160d38..60b82da 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor_test.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor_test.js
@@ -606,7 +606,7 @@
 
   test('_findRowByNumberAndFile', () => {
     // Get the first ab row after the first chunk.
-    const row = diffElement.root.querySelectorAll('tr')[8];
+    const row = diffElement.root.querySelectorAll('tr')[9];
 
     // It should be line 8 on the right, but line 5 on the left.
     assert.equal(cursorElement._findRowByNumberAndFile(8, 'right'), row);
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-diff-highlight.ts b/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-diff-highlight.ts
index fd5c19d..d5c7d8e 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-diff-highlight.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-diff-highlight.ts
@@ -357,7 +357,7 @@
     const side = this.diffBuilder.getSideByLineEl(lineEl);
     if (!side) return null;
     const line = this.diffBuilder.getLineNumberByChild(lineEl);
-    if (!line || line === FILE) return null;
+    if (!line || line === FILE || line === 'LOST') return null;
     const contentTd = this.diffBuilder.getContentTdByLineEl(lineEl);
     if (!contentTd) return null;
     const contentText = contentTd.querySelector('.contentText');
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host.ts b/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host.ts
index 89ee170..1b1e71c 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host.ts
@@ -840,7 +840,10 @@
   _createThreadElement(thread: CommentThread) {
     const threadEl = document.createElement('gr-comment-thread');
     threadEl.className = 'comment-thread';
-    threadEl.setAttribute('slot', `${thread.diffSide}-${thread.line}`);
+    threadEl.setAttribute(
+      'slot',
+      `${thread.diffSide}-${thread.line || 'LOST'}`
+    );
     threadEl.comments = thread.comments;
     threadEl.diffSide = thread.diffSide;
     threadEl.isOnParent = thread.commentSide === CommentSide.PARENT;
@@ -861,8 +864,9 @@
     threadEl.patchNum = thread.patchNum;
     threadEl.showPatchset = false;
     threadEl.showPortedComment = !!thread.ported;
+    if (thread.rangeInfoLost) threadEl.lineNum = 'LOST';
     // GrCommentThread does not understand 'FILE', but requires undefined.
-    threadEl.lineNum = thread.line !== 'FILE' ? thread.line : undefined;
+    else threadEl.lineNum = thread.line !== 'FILE' ? thread.line : undefined;
     threadEl.projectName = this.projectName;
     threadEl.range = thread.range;
     const threadDiscardListener = (e: Event) => {
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-processor/gr-diff-processor.ts b/polygerrit-ui/app/elements/diff/gr-diff-processor/gr-diff-processor.ts
index a0584b6..034081d 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-processor/gr-diff-processor.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff-processor/gr-diff-processor.ts
@@ -22,6 +22,7 @@
   GrDiffLineType,
   FILE,
   Highlights,
+  LineNumber,
 } from '../gr-diff/gr-diff-line';
 import {
   GrDiffGroup,
@@ -150,7 +151,8 @@
     this.cancel();
 
     this.groups = [];
-    this.push('groups', this._makeFileComments());
+    this.push('groups', this._makeGroup('LOST'));
+    this.push('groups', this._makeGroup(FILE));
 
     // If it's a binary diff, we won't be rendering hunks of text differences
     // so finish processing.
@@ -450,10 +452,10 @@
     return line;
   }
 
-  _makeFileComments() {
+  _makeGroup(number: LineNumber) {
     const line = new GrDiffLine(GrDiffLineType.BOTH);
-    line.beforeNumber = FILE;
-    line.afterNumber = FILE;
+    line.beforeNumber = number;
+    line.afterNumber = number;
     return new GrDiffGroup(GrDiffGroupType.BOTH, [line]);
   }
 
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-processor/gr-diff-processor_test.js b/polygerrit-ui/app/elements/diff/gr-diff-processor/gr-diff-processor_test.js
index f5cbcc0..b8f7498 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-processor/gr-diff-processor_test.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-processor/gr-diff-processor_test.js
@@ -73,7 +73,7 @@
 
       return element.process(content).then(() => {
         const groups = element.groups;
-
+        groups.shift(); // remove portedThreadsWithoutRangeGroup
         assert.equal(groups.length, 4);
 
         let group = groups[0];
@@ -133,6 +133,7 @@
 
       return element.process(content).then(() => {
         const groups = element.groups;
+        groups.shift(); // remove portedThreadsWithoutRangeGroup
 
         assert.equal(groups[0].type, GrDiffGroupType.BOTH);
         assert.equal(groups[0].lines.length, 1);
@@ -153,6 +154,7 @@
 
         return element.process(content).then(() => {
           const groups = element.groups;
+          groups.shift(); // remove portedThreadsWithoutRangeGroup
 
           // group[0] is the file group
 
@@ -185,6 +187,7 @@
         await element.process(content);
 
         const groups = element.groups;
+        groups.shift(); // remove portedThreadsWithoutRangeGroup
 
         // group[0] is the file group
 
@@ -231,6 +234,7 @@
 
         return element.process(content).then(() => {
           const groups = element.groups;
+          groups.shift(); // remove portedThreadsWithoutRangeGroup
 
           // group[0] is the file group
 
@@ -252,6 +256,7 @@
 
         return element.process(content).then(() => {
           const groups = element.groups;
+          groups.shift(); // remove portedThreadsWithoutRangeGroup
 
           // group[0] is the file group
           // group[1] is the "a" group
@@ -283,6 +288,7 @@
 
         return element.process(content).then(() => {
           const groups = element.groups;
+          groups.shift(); // remove portedThreadsWithoutRangeGroup
 
           // group[0] is the file group
           // group[1] is the "a" group
@@ -324,6 +330,7 @@
 
         return element.process(content).then(() => {
           const groups = element.groups;
+          groups.shift(); // remove portedThreadsWithoutRangeGroup
 
           // group[0] is the file group
           // group[1] is the "a" group
@@ -411,6 +418,7 @@
 
         return element.process(content).then(() => {
           const groups = element.groups;
+          groups.shift(); // remove portedThreadsWithoutRangeGroup
 
           // group[0] is the file group
           // group[1] is the "a" group
@@ -450,6 +458,7 @@
 
         return element.process(content).then(() => {
           const groups = element.groups;
+          groups.shift(); // remove portedThreadsWithoutRangeGroup
 
           // group[0] is the file group
           // group[1] is the "a" group
@@ -479,6 +488,7 @@
       await element.process(content);
 
       const groups = element.groups;
+      groups.shift(); // remove portedThreadsWithoutRangeGroup
 
       // group[0] is the file group
       // group[1] is the chunk with a
@@ -744,12 +754,12 @@
       element._isScrolling = true;
       element.process(content);
       // Just the files group - no more processing during scrolling.
-      assert.equal(element.groups.length, 1);
+      assert.equal(element.groups.length, 2);
 
       element._isScrolling = false;
       element.process(content);
       // More groups have been processed. How many does not matter here.
-      assert.isAtLeast(element.groups.length, 2);
+      assert.isAtLeast(element.groups.length, 3);
     });
 
     test('image diffs', () => {
@@ -762,7 +772,7 @@
       const content = _.times(200, _.constant(contentRow));
       sinon.stub(element, 'async');
       element.process(content, true);
-      assert.equal(element.groups.length, 1);
+      assert.equal(element.groups.length, 2);
 
       // Image diffs don't process content, just the 'FILE' line.
       assert.equal(element.groups[0].lines.length, 1);
diff --git a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff-group.ts b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff-group.ts
index 3fd7775..ba6fe7e 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff-group.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff-group.ts
@@ -324,7 +324,12 @@
   }
 
   _updateRange(line: GrDiffLine) {
-    if (line.beforeNumber === 'FILE' || line.afterNumber === 'FILE') {
+    if (
+      line.beforeNumber === 'FILE' ||
+      line.afterNumber === 'FILE' ||
+      line.beforeNumber === 'LOST' ||
+      line.afterNumber === 'LOST'
+    ) {
       return;
     }
 
diff --git a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff-utils.ts b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff-utils.ts
index 1e5a8d3..5edd353 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff-utils.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff-utils.ts
@@ -48,12 +48,14 @@
   const lineNumberStr = lineEl.getAttribute('data-value');
   if (!lineNumberStr) return null;
   if (lineNumberStr === FILE) return FILE;
+  if (lineNumberStr === 'LOST') return 'LOST';
   const lineNumber = Number(lineNumberStr);
   return Number.isInteger(lineNumber) ? lineNumber : null;
 }
 
 export function getLine(threadEl: HTMLElement): LineNumber {
   const lineAtt = threadEl.getAttribute('line-num');
+  if (lineAtt === 'LOST') return lineAtt;
   if (!lineAtt || lineAtt === 'FILE') return FILE;
   const line = Number(lineAtt);
   if (isNaN(line)) throw new Error(`cannot parse line number: ${lineAtt}`);
diff --git a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.ts b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.ts
index b85d948..9fb5e74 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.ts
@@ -518,8 +518,9 @@
       );
       this.$.diffBuilder.showContext(e.detail.groups, e.detail.section);
     } else if (
-      el.classList.contains('lineNum') ||
-      el.classList.contains('lineNumButton')
+      el.getAttribute('data-value') !== 'LOST' &&
+      (el.classList.contains('lineNum') ||
+        el.classList.contains('lineNumButton'))
     ) {
       this.addDraftAtLine(el);
     } else if (
@@ -817,6 +818,9 @@
         }
         const contentEl = this.$.diffBuilder.getContentTdByLineEl(lineEl);
         if (!contentEl) continue;
+        if (lineNum === 'LOST' && !contentEl.hasChildNodes()) {
+          contentEl.appendChild(this._portedCommentsWithoutRangeMessage());
+        }
         const threadGroupEl = this._getOrCreateThreadGroup(
           contentEl,
           commentSide
@@ -862,6 +866,17 @@
     });
   }
 
+  _portedCommentsWithoutRangeMessage() {
+    const div = document.createElement('div');
+    const icon = document.createElement('iron-icon');
+    icon.setAttribute('icon', 'gr-icons:info');
+    div.appendChild(icon);
+    const span = document.createElement('span');
+    span.innerText = 'Original comment position not found in this patchset';
+    div.appendChild(span);
+    return div;
+  }
+
   _unobserveIncrementalNodes() {
     if (this._incrementalNodeObserver) {
       (dom(this) as PolymerDomWrapper).unobserveNodes(
diff --git a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_html.ts b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_html.ts
index 291f842..b0f48ce 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_html.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_html.ts
@@ -427,6 +427,13 @@
     .target-row td.blame {
       background: var(--diff-selection-background-color);
     }
+    td.lost div {
+      background-color: var(--blue-50);
+      padding: var(--spacing-s);
+    }
+    td.lost iron-icon {
+      margin-right: var(--spacing-s);
+    }
     col.blame {
       display: none;
     }
diff --git a/polygerrit-ui/app/elements/diff/gr-syntax-layer/gr-syntax-layer.ts b/polygerrit-ui/app/elements/diff/gr-syntax-layer/gr-syntax-layer.ts
index 04bb3d2..da29b85 100644
--- a/polygerrit-ui/app/elements/diff/gr-syntax-layer/gr-syntax-layer.ts
+++ b/polygerrit-ui/app/elements/diff/gr-syntax-layer/gr-syntax-layer.ts
@@ -212,9 +212,8 @@
    */
   annotate(el: HTMLElement, _: HTMLElement, line: GrDiffLine) {
     if (!this.enabled) return;
-    if (line.beforeNumber === FILE) return;
-    if (line.afterNumber === FILE) return;
-
+    if (line.beforeNumber === FILE || line.afterNumber === FILE) return;
+    if (line.beforeNumber === 'LOST' || line.afterNumber === 'LOST') return;
     // Determine the side.
     let side;
     if (
diff --git a/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread.ts b/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread.ts
index c8b3be2..cb64001 100644
--- a/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread.ts
+++ b/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread.ts
@@ -277,6 +277,7 @@
       (this.comments.length && this.comments[0].side === 'PARENT') ||
       isDraft(this.comments[0])
     ) {
+      if (this.lineNum === 'LOST') throw new Error('invalid lineNum lost');
       return GerritNav.getUrlForDiffById(
         changeNum,
         projectName,
@@ -503,7 +504,7 @@
       __draftID: Math.random().toString(36),
       __date: new Date(),
     };
-
+    if (lineNum === 'LOST') throw new Error('invalid lineNum lost');
     // For replies, always use same meta info as root.
     if (this.comments && this.comments.length >= 1) {
       const rootComment = this.comments[0];
diff --git a/polygerrit-ui/app/elements/shared/gr-editable-content/gr-editable-content.ts b/polygerrit-ui/app/elements/shared/gr-editable-content/gr-editable-content.ts
index 2537c1a..e825d21 100644
--- a/polygerrit-ui/app/elements/shared/gr-editable-content/gr-editable-content.ts
+++ b/polygerrit-ui/app/elements/shared/gr-editable-content/gr-editable-content.ts
@@ -25,6 +25,8 @@
 import {customElement, property} from '@polymer/decorators';
 import {htmlTemplate} from './gr-editable-content_html';
 import {fireAlert, fireEvent} from '../../../utils/event-util';
+import {appContext} from '../../../services/app-context';
+import {KnownExperimentId} from '../../../services/flags/flags';
 
 const RESTORED_MESSAGE = 'Content restored from a previous edit.';
 const STORAGE_DEBOUNCE_INTERVAL_MS = 400;
@@ -67,7 +69,7 @@
   @property({type: Boolean, reflectToAttribute: true})
   disabled = false;
 
-  @property({type: Boolean, observer: '_editingChanged'})
+  @property({type: Boolean, observer: '_editingChanged', notify: true})
   editing = false;
 
   @property({type: Boolean})
@@ -77,6 +79,29 @@
   @property({type: String})
   storageKey?: string;
 
+  /** If false, then the "Show more" button was used to expand. */
+  @property({type: Boolean})
+  _commitCollapsed = true;
+
+  @property({type: Boolean})
+  commitCollapsible = true;
+
+  @property({
+    type: Boolean,
+    computed:
+      '_computeHideShowAllContainer(hideEditCommitMessage, _hideShowAllButton, editing)',
+  })
+  _hideShowAllContainer = false;
+
+  @property({
+    type: Boolean,
+    computed: '_computeHideShowAllButton(commitCollapsible, editing)',
+  })
+  _hideShowAllButton = false;
+
+  @property({type: Boolean})
+  hideEditCommitMessage?: boolean;
+
   @property({
     type: Boolean,
     computed: '_computeSaveDisabled(disabled, content, _newContent)',
@@ -86,8 +111,21 @@
   @property({type: String, observer: '_newContentChanged'})
   _newContent?: string;
 
+  @property({type: Boolean})
+  _isNewChangeSummaryUiEnabled = false;
+
   private readonly storage = new GrStorage();
 
+  private readonly flagsService = appContext.flagsService;
+
+  /** @override */
+  ready() {
+    super.ready();
+    this._isNewChangeSummaryUiEnabled = this.flagsService.isEnabled(
+      KnownExperimentId.NEW_CHANGE_SUMMARY_UI
+    );
+  }
+
   _contentChanged() {
     /* A changed content means that either a different change has been loaded
      * or new content was saved. Either way, let's reset the component.
@@ -186,4 +224,37 @@
     this.editing = false;
     fireEvent(this, 'editable-content-cancel');
   }
+
+  _computeCollapseText(collapsed: boolean) {
+    return collapsed ? 'Show all' : 'Show less';
+  }
+
+  _toggleCommitCollapsed() {
+    this._commitCollapsed = !this._commitCollapsed;
+    if (this._commitCollapsed) {
+      window.scrollTo(0, 0);
+    }
+  }
+
+  _computeHideShowAllContainer(
+    hideEditCommitMessage?: boolean,
+    _hideShowAllButton?: boolean,
+    editing?: boolean
+  ) {
+    if (editing) return false;
+    return _hideShowAllButton && hideEditCommitMessage;
+  }
+
+  _computeHideShowAllButton(commitCollapsible?: boolean, editing?: boolean) {
+    return !commitCollapsible || editing;
+  }
+
+  _computeCommitMessageCollapsed(collapsed?: boolean, collapsible?: boolean) {
+    return collapsible && collapsed;
+  }
+
+  _handleEditCommitMessage() {
+    this.editing = true;
+    this.focusTextarea();
+  }
 }
diff --git a/polygerrit-ui/app/elements/shared/gr-editable-content/gr-editable-content_html.ts b/polygerrit-ui/app/elements/shared/gr-editable-content/gr-editable-content_html.ts
index fa18761..6605394 100644
--- a/polygerrit-ui/app/elements/shared/gr-editable-content/gr-editable-content_html.ts
+++ b/polygerrit-ui/app/elements/shared/gr-editable-content/gr-editable-content_html.ts
@@ -31,13 +31,19 @@
       box-shadow: var(--elevation-level-1);
       padding: var(--spacing-m);
     }
-    :host([collapsed]) .viewer {
+    :host([collapsed]) .viewer,
+    .viewer[collapsed] {
       max-height: var(--collapsed-max-height, 300px);
       overflow: hidden;
     }
+    .editor.new-change-summary-true iron-autogrow-textarea,
+    .viewer.new-change-summary-true {
+      min-height: 160px;
+    }
     .editor iron-autogrow-textarea {
       background-color: var(--view-background-color);
       width: 100%;
+      display: block;
 
       /* You have to also repeat everything from shared-styles here, because
            you can only *replace* --iron-autogrow-textarea vars as a whole. */
@@ -52,23 +58,103 @@
       display: flex;
       justify-content: space-between;
     }
+    .show-all-container {
+      background-color: var(--view-background-color);
+      display: flex;
+      justify-content: flex-end;
+      margin-bottom: 8px;
+      border-top-width: 1px;
+      border-top-style: solid;
+      border-radius: 0 0 4px 4px;
+      border-color: var(--border-color);
+      box-shadow: var(--elevation-level-1);
+    }
+    .show-all-container .show-all-button {
+      margin-right: auto;
+    }
+    .show-all-container iron-icon {
+      color: inherit;
+      --iron-icon-height: 18px;
+      --iron-icon-width: 18px;
+    }
+    .cancel-button {
+      margin-right: var(--spacing-l);
+    }
+    .save-button {
+      margin-right: var(--spacing-xs);
+    }
   </style>
-  <div class="viewer" hidden$="[[editing]]">
+  <div
+    class$="viewer new-change-summary-[[_isNewChangeSummaryUiEnabled]]"
+    hidden$="[[editing]]"
+    collapsed$="[[_computeCommitMessageCollapsed(_commitCollapsed, commitCollapsible)]]"
+  >
     <slot></slot>
   </div>
-  <div class="editor" hidden$="[[!editing]]">
-    <iron-autogrow-textarea
-      autocomplete="on"
-      bind-value="{{_newContent}}"
-      disabled="[[disabled]]"
-    ></iron-autogrow-textarea>
-    <div class="editButtons">
-      <gr-button primary="" on-click="_handleSave" disabled="[[_saveDisabled]]"
-        >Save</gr-button
-      >
-      <gr-button on-click="_handleCancel" disabled="[[disabled]]"
-        >Cancel</gr-button
-      >
+  <div
+    class$="editor new-change-summary-[[_isNewChangeSummaryUiEnabled]]"
+    hidden$="[[!editing]]"
+  >
+    <div>
+      <iron-autogrow-textarea
+        autocomplete="on"
+        bind-value="{{_newContent}}"
+        disabled="[[disabled]]"
+      ></iron-autogrow-textarea>
+      <div class="editButtons" hidden$="[[_isNewChangeSummaryUiEnabled]]">
+        <gr-button
+          primary=""
+          on-click="_handleSave"
+          disabled="[[_saveDisabled]]"
+          >Save</gr-button
+        >
+        <gr-button on-click="_handleCancel" disabled="[[disabled]]"
+          >Cancel</gr-button
+        >
+      </div>
     </div>
   </div>
+  <template is="dom-if" if="[[_isNewChangeSummaryUiEnabled]]">
+    <div class="show-all-container" hidden$="[[_hideShowAllContainer]]">
+      <gr-button
+        link=""
+        class="show-all-button"
+        on-click="_toggleCommitCollapsed"
+        hidden$="[[_hideShowAllButton]]"
+        ><iron-icon
+          icon="gr-icons:expand-more"
+          hidden$="[[!_commitCollapsed]]"
+        ></iron-icon
+        ><iron-icon
+          icon="gr-icons:expand-less"
+          hidden$="[[_commitCollapsed]]"
+        ></iron-icon>
+        [[_computeCollapseText(_commitCollapsed)]]
+      </gr-button>
+      <gr-button
+        link=""
+        class="edit-commit-message"
+        title="Edit commit message"
+        on-click="_handleEditCommitMessage"
+        hidden$="[[hideEditCommitMessage]]"
+        ><iron-icon icon="gr-icons:edit"></iron-icon> Edit</gr-button
+      >
+      <div class="editButtons" hidden$="[[!editing]]">
+        <gr-button
+          link=""
+          class="cancel-button"
+          on-click="_handleCancel"
+          disabled="[[disabled]]"
+          >Cancel</gr-button
+        >
+        <gr-button
+          class="save-button"
+          primary=""
+          on-click="_handleSave"
+          disabled="[[_saveDisabled]]"
+          >Save</gr-button
+        >
+      </div>
+    </div>
+  </template>
 `;
diff --git a/polygerrit-ui/app/elements/shared/gr-editable-content/gr-editable-content_test.js b/polygerrit-ui/app/elements/shared/gr-editable-content/gr-editable-content_test.js
index a0481ae..b99b119 100644
--- a/polygerrit-ui/app/elements/shared/gr-editable-content/gr-editable-content_test.js
+++ b/polygerrit-ui/app/elements/shared/gr-editable-content/gr-editable-content_test.js
@@ -105,7 +105,7 @@
 
       assert.equal(element._newContent, 'stored content');
       assert.isTrue(dispatchSpy.called);
-      assert.equal(dispatchSpy.lastCall.args[0].type, 'show-alert');
+      assert.equal(dispatchSpy.firstCall.args[0].type, 'show-alert');
     });
 
     test('editing toggled to true, has no stored data', () => {
@@ -114,7 +114,7 @@
       element.editing = true;
 
       assert.equal(element._newContent, 'current content');
-      assert.isFalse(dispatchSpy.called);
+      assert.equal(dispatchSpy.firstCall.args[0].type, 'editing-changed');
     });
 
     test('edits are cached', () => {
diff --git a/polygerrit-ui/app/services/flags/flags.ts b/polygerrit-ui/app/services/flags/flags.ts
index 5edbf87..bf62d0d0 100644
--- a/polygerrit-ui/app/services/flags/flags.ts
+++ b/polygerrit-ui/app/services/flags/flags.ts
@@ -31,5 +31,4 @@
   CI_REBOOT_CHECKS = 'UiFeature__ci_reboot_checks',
   NEW_CHANGE_SUMMARY_UI = 'UiFeature__new_change_summary_ui',
   PORTING_COMMENTS = 'UiFeature__porting_comments',
-  AUTO_RELOAD_DASHBOARD = 'UiFeature__auto_reload_dashboard',
 }
diff --git a/polygerrit-ui/app/utils/change-util.ts b/polygerrit-ui/app/utils/change-util.ts
index 094aa3b..8839be1 100644
--- a/polygerrit-ui/app/utils/change-util.ts
+++ b/polygerrit-ui/app/utils/change-util.ts
@@ -201,6 +201,15 @@
   return change.revisions[change.current_revision];
 }
 
+export function getRevisionKey(
+  change: ChangeInfo | ParsedChangeInfo,
+  patchNum: PatchSetNum
+) {
+  return Object.keys(change.revisions ?? []).find(
+    rev => change?.revisions?.[rev]._number === patchNum
+  );
+}
+
 export function changeStatusString(change: ChangeInfo) {
   return changeStatuses(change).join(', ');
 }
diff --git a/tools/nongoogle.bzl b/tools/nongoogle.bzl
index 3eb1e4b..e06fba3 100644
--- a/tools/nongoogle.bzl
+++ b/tools/nongoogle.bzl
@@ -142,34 +142,24 @@
         sha1 = GUAVA_BIN_SHA1,
     )
 
-    GUICE_VERS = "4.2.3"
+    GUICE_VERS = "5.0.0-BETA-1"
 
-    GUICE_LIBRARY_SHA256 = "5168f5e7383f978c1b4154ac777b78edd8ac214bb9f9afdb92921c8d156483d3"
-
-    http_file(
-        name = "guice-library-no-aop",
-        canonical_id = "guice-library-no-aop-" + GUICE_VERS + ".jar-" + GUICE_LIBRARY_SHA256,
-        downloaded_file_path = "guice-library-no-aop.jar",
-        sha256 = GUICE_LIBRARY_SHA256,
-        urls = [
-            "https://repo1.maven.org/maven2/com/google/inject/guice/" +
-            GUICE_VERS +
-            "/guice-" +
-            GUICE_VERS +
-            "-no_aop.jar",
-        ],
+    maven_jar(
+        name = "guice-library",
+        artifact = "com.google.inject:guice:" + GUICE_VERS,
+        sha1 = "c5572be8a8b75ea50e0fdf54fa1f75a3141ab936",
     )
 
     maven_jar(
         name = "guice-assistedinject",
         artifact = "com.google.inject.extensions:guice-assistedinject:" + GUICE_VERS,
-        sha1 = "acbfddc556ee9496293ed1df250cc378f331d854",
+        sha1 = "4d06eba0e08151b52d9e25a14e4f01eedf998bc3",
     )
 
     maven_jar(
         name = "guice-servlet",
         artifact = "com.google.inject.extensions:guice-servlet:" + GUICE_VERS,
-        sha1 = "8d6e7e35eac4fb5e7df19c55b3bc23fa51b10a11",
+        sha1 = "373b9a4f1b6683d9a991410660d2c9adb9f06737",
     )
 
     # Keep this version of Soy synchronized with the version used in Gitiles.