Merge changes I9f3acf5b,Ia85d8e87

* changes:
  [project.config] Replace Guava collections with native Java ones
  [project.config] Add tests for reading comment links
diff --git a/Documentation/rest-api-changes.txt b/Documentation/rest-api-changes.txt
index 5e67b38..0c30a4b 100644
--- a/Documentation/rest-api-changes.txt
+++ b/Documentation/rest-api-changes.txt
@@ -301,6 +301,12 @@
     link:user-search.html#reviewedby[reviewedby:self].
 --
 
+[[skip_mergeable]]
+--
+* `SKIP_MERGEABLE`: skip the `mergeable` field in
+link:#change-info[ChangeInfo]. For fast moving projects, this field must
+be recomputed often, which is slow for projects with big trees.
+
 [[submittable]]
 --
 * `SUBMITTABLE`: include the `submittable` field in link:#change-info[ChangeInfo],
@@ -5658,7 +5664,8 @@
 Not set for merged changes.
 |`mergeable`          |optional|
 Whether the change is mergeable. +
-Not set for merged changes, or if the change has not yet been tested.
+Not set for merged changes, if the change has not yet been tested, or
+if the link:#skip_mergeable[skip_mergeable] option is set.
 |`submittable`        |optional|
 Whether the change has been approved by the project submit rules. +
 Only set if link:#submittable[requested].
diff --git a/java/com/google/gerrit/extensions/client/ListChangesOption.java b/java/com/google/gerrit/extensions/client/ListChangesOption.java
index ee7d039..ffc5029 100644
--- a/java/com/google/gerrit/extensions/client/ListChangesOption.java
+++ b/java/com/google/gerrit/extensions/client/ListChangesOption.java
@@ -75,7 +75,10 @@
   SUBMITTABLE(20),
 
   /** If tracking Ids are included, include detailed tracking Ids info. */
-  TRACKING_IDS(21);
+  TRACKING_IDS(21),
+
+  /** Skip mergeability data */
+  SKIP_MERGEABLE(22);
 
   private final int value;
 
diff --git a/java/com/google/gerrit/server/change/ChangeJson.java b/java/com/google/gerrit/server/change/ChangeJson.java
index 81558e3..8ba755f 100644
--- a/java/com/google/gerrit/server/change/ChangeJson.java
+++ b/java/com/google/gerrit/server/change/ChangeJson.java
@@ -33,6 +33,7 @@
 import static com.google.gerrit.extensions.client.ListChangesOption.PUSH_CERTIFICATES;
 import static com.google.gerrit.extensions.client.ListChangesOption.REVIEWED;
 import static com.google.gerrit.extensions.client.ListChangesOption.REVIEWER_UPDATES;
+import static com.google.gerrit.extensions.client.ListChangesOption.SKIP_MERGEABLE;
 import static com.google.gerrit.extensions.client.ListChangesOption.SUBMITTABLE;
 import static com.google.gerrit.extensions.client.ListChangesOption.TRACKING_IDS;
 import static com.google.gerrit.extensions.client.ListChangesOption.WEB_LINKS;
@@ -521,7 +522,9 @@
       if (str.isOk()) {
         out.submitType = str.type;
       }
-      out.mergeable = cd.isMergeable();
+      if (!has(SKIP_MERGEABLE)) {
+        out.mergeable = cd.isMergeable();
+      }
       if (has(SUBMITTABLE)) {
         out.submittable = submittable(cd);
       }
diff --git a/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java b/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java
index fa683cf..b770064 100644
--- a/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java
@@ -91,6 +91,7 @@
 import com.google.gerrit.extensions.client.ChangeStatus;
 import com.google.gerrit.extensions.client.Comment.Range;
 import com.google.gerrit.extensions.client.InheritableBoolean;
+import com.google.gerrit.extensions.client.ListChangesOption;
 import com.google.gerrit.extensions.client.ReviewerState;
 import com.google.gerrit.extensions.client.Side;
 import com.google.gerrit.extensions.client.SubmitType;
@@ -237,6 +238,15 @@
   }
 
   @Test
+  public void skipMergeable() throws Exception {
+    PushOneCommit.Result r = createChange();
+    String triplet = project.get() + "~master~" + r.getChangeId();
+    ChangeInfo c =
+        gApi.changes().id(triplet).get(ImmutableList.of(ListChangesOption.SKIP_MERGEABLE));
+    assertThat(c.mergeable).isNull();
+  }
+
+  @Test
   public void setPrivateByOwner() throws Exception {
     TestRepository<InMemoryRepository> userRepo = cloneProject(project, user);
     PushOneCommit.Result result =
diff --git a/polygerrit-ui/app/BUILD b/polygerrit-ui/app/BUILD
index ec7efb6..991ce2e 100644
--- a/polygerrit-ui/app/BUILD
+++ b/polygerrit-ui/app/BUILD
@@ -163,7 +163,7 @@
         ],
     ),
     outs = ["polygerrit_embed_ui.zip"],
-    app = "embed/change-diff-views.html",
+    app = "embed/embed.html",
 )
 
 filegroup(
diff --git a/polygerrit-ui/app/elements/admin/gr-permission/gr-permission.html b/polygerrit-ui/app/elements/admin/gr-permission/gr-permission.html
index 4013b37..2294621 100644
--- a/polygerrit-ui/app/elements/admin/gr-permission/gr-permission.html
+++ b/polygerrit-ui/app/elements/admin/gr-permission/gr-permission.html
@@ -89,11 +89,13 @@
         <div class="header">
           <span class="title">[[name]]</span>
           <div class="right">
-            <paper-toggle-button
-                id="exclusiveToggle"
-                checked="{{permission.value.exclusive}}"
-                on-change="_handleValueChange"
-                disabled$="[[!editing]]"></paper-toggle-button>Exclusive
+            <template is=dom-if if="[[!_permissionIsOwner(permission.id)]]">
+              <paper-toggle-button
+                  id="exclusiveToggle"
+                  checked="{{permission.value.exclusive}}"
+                  on-change="_handleValueChange"
+                  disabled$="[[!editing]]"></paper-toggle-button>Exclusive
+            </template>
             <gr-button
                 link
                 id="removeBtn"
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 f9c04e60..cfb0ad5 100644
--- a/polygerrit-ui/app/elements/admin/gr-permission/gr-permission.js
+++ b/polygerrit-ui/app/elements/admin/gr-permission/gr-permission.js
@@ -89,6 +89,10 @@
       this._setupValues();
     },
 
+    _permissionIsOwner(permissionId) {
+      return permissionId === 'owner';
+    },
+
     _handleEditingChanged(editing, editingOld) {
       // Ignore when editing gets set initially.
       if (!editingOld) { return; }
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 b67d705..ec93a5c 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
@@ -335,7 +335,7 @@
 
         assert.isFalse(element._originalExclusiveValue);
         assert.isNotOk(element.permission.value.modified);
-        MockInteractions.tap(element.$.exclusiveToggle);
+        MockInteractions.tap(element.$$('#exclusiveToggle'));
         flushAsynchronousOperations();
         assert.isTrue(element.permission.value.exclusive);
         assert.isTrue(element.permission.value.modified);
@@ -353,6 +353,15 @@
         assert.isTrue(element.permission.value.modified);
         assert.isTrue(modifiedHandler.called);
       });
+
+      test('Exclusive hidden for owner permission', () => {
+        assert.equal(getComputedStyle(element.$$('#exclusiveToggle')).display,
+            'flex');
+        element.set(['permission', 'id'], 'owner');
+        flushAsynchronousOperations();
+        assert.equal(getComputedStyle(element.$$('#exclusiveToggle')).display,
+            'none');
+      });
     });
   });
 </script>
diff --git a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.html b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.html
index 481cd76..f3286f2 100644
--- a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.html
+++ b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.html
@@ -132,6 +132,7 @@
     <gr-overlay id="overlay" with-backdrop>
       <gr-confirm-rebase-dialog id="confirmRebase"
           class="confirmDialog"
+          change-number="[[change._number]]"
           on-confirm="_handleRebaseConfirm"
           on-cancel="_handleConfirmDialogCancel"
           branch="[[change.branch]]"
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 b2a6f0d..8e00c06 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
@@ -173,7 +173,7 @@
      */
 
     properties: {
-      /** @type {{ branch: string, project: string }} */
+      /** @type {{ _number: number, branch: string, project: string }} */
       change: Object,
       actions: {
         type: Object,
@@ -501,7 +501,7 @@
                 this.notifyPath('actions.rebaseEdit');
               }
             } else {
-              if (!changeActions.rebasEdit) {
+              if (!changeActions.rebaseEdit) {
                 this.set('actions.rebaseEdit', REBASE_EDIT);
               }
               if (changeActions.publishEdit) {
@@ -802,6 +802,7 @@
       switch (key) {
         case RevisionActions.REBASE:
           this._showActionDialog(this.$.confirmRebase);
+          this.$.confirmRebase.fetchRecentChanges();
           break;
         case RevisionActions.CHERRYPICK:
           this._handleCherrypickTap();
diff --git a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.html b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.html
index 62b4626..df2fbcb 100644
--- a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.html
+++ b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.html
@@ -306,6 +306,9 @@
 
     test('rebase change', done => {
       const fireActionStub = sandbox.stub(element, '_fireAction');
+      const fetchChangesStub = sandbox.stub(element.$.confirmRebase,
+          'fetchRecentChanges').returns(Promise.resolve([]));
+      element._hasKnownChainState = true;
       flush(() => {
         const rebaseButton = element.$$('gr-button[data-action-key="rebase"]');
         MockInteractions.tap(rebaseButton);
@@ -318,6 +321,7 @@
           method: 'POST',
           title: 'Rebase onto tip of branch or parent change',
         };
+        assert.isTrue(fetchChangesStub.called);
         // rebase on other
         element.$.confirmRebase.base = '1234';
         element._handleRebaseConfirm();
@@ -340,6 +344,22 @@
       });
     });
 
+    test(`rebase dialog gets recent changes each time it's opened`, done => {
+      const fetchChangesStub = sandbox.stub(element.$.confirmRebase,
+          'fetchRecentChanges').returns(Promise.resolve([]));
+      element._hasKnownChainState = true;
+      const rebaseButton = element.$$('gr-button[data-action-key="rebase"]');
+      MockInteractions.tap(rebaseButton);
+      assert.isTrue(fetchChangesStub.calledOnce);
+
+      flush(() => {
+        element.$.confirmRebase.fire('cancel');
+        MockInteractions.tap(rebaseButton);
+        assert.isTrue(fetchChangesStub.calledTwice);
+        done();
+      });
+    });
+
     test('two dialogs are not shown at the same time', done => {
       element._hasKnownChainState = true;
       flush(() => {
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 7e661c7..c2f2f6f 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
@@ -76,7 +76,10 @@
       }
       gr-change-status {
         display: initial;
-        margin: .1em .5em .1em 0;
+        margin: .1em .1em .1em .4em;
+      }
+      gr-change-status:first-child {
+        margin-left: 0;
       }
       .header-title {
         align-items: center;
@@ -144,7 +147,9 @@
         }
       }
       .changeStatuses,
-      .commitActions {
+      .changeText,
+      .commitActions,
+      .statusText {
         align-items: center;
         display: flex;
       }
@@ -225,6 +230,7 @@
       }
       gr-commit-info {
         display: inline-block;
+        margin-right: -5px;
       }
       @media screen and (min-width: 80em) {
         .commitMessage {
@@ -327,19 +333,22 @@
                   status="[[status]]"></gr-change-status>
             </template>
           </div>
+          <div class="statusText">
+            <template
+                is="dom-if"
+                if="[[_computeShowCommitInfo(_changeStatus, _change.current_revision)]]">
+              <span class="text"> as </span>
+              <gr-commit-info
+                  change="[[_change]]"
+                  commit-info="[[_computeMergedCommitInfo(_change.current_revision, _change.revisions)]]"
+                  server-config="[[_serverConfig]]"></gr-commit-info>
+            </template>
+          </div>
+          <span class="separator"></span>
           <div class="changeText">
             <a aria-label$="[[_computeChangePermalinkAriaLabel(_change._number)]]"
-                href$="[[_computeChangeUrl(_change)]]">[[_change._number]]</a><!--
-         --><template
-                is="dom-if"
-                if="[[_computeShowCommitInfo(_changeStatus, _change.current_revision)]]"><!--
-           --><span class="text"> ([[_changeStatus]] as </span><!--
-             --><gr-commit-info
-                    change="[[_change]]"
-                    commit-info="[[_computeMergedCommitInfo(_change.current_revision, _change.revisions)]]"
-                    server-config="[[_serverConfig]]"></gr-commit-info>)<!--
-         --></template><!--
-         --><span class="text">: </span><span class="headerSubject">[[_change.subject]]</span>
+                href$="[[_computeChangeUrl(_change)]]">[[_change._number]]</a>
+            <span class="headerSubject">: [[_change.subject]]</span>
           </div>
         </div><!-- end header-title -->
         <div class="commitActions" hidden$="[[!_loggedIn]]">
@@ -370,7 +379,7 @@
               server-config="[[_serverConfig]]"
               missing-labels="[[_missingLabels]]"
               mutable="[[_loggedIn]]"
-              parent-is-current="[[!_rebaseOriginallyEnabled]]"
+              parent-is-current="[[_parentIsCurrent]]"
               on-show-reply-dialog="_handleShowReplyDialog">
           </gr-change-metadata>
           <!-- Plugins insert content into following container.
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 fd9a490..1800175 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
@@ -151,17 +151,7 @@
       _patchRange: {
         type: Object,
       },
-      // These are kept as separate properties from the patchRange so that the
-      // observer can be aware of the previous value. In order to view sub
-      // property changes for _patchRange, a complex observer must be used, and
-      // that only displays the new value.
-      //
-      // If a previous value did not exist, the change is not reloaded with the
-      // new patches. This is just the initial setting from the change view vs.
-      // an update coming from the two way data binding.
-      _patchNum: String,
       _filesExpanded: String,
-      _basePatchNum: String,
       _currentRevision: Object,
       _currentRevisionActions: Object,
       _allPatchSets: {
@@ -224,7 +214,7 @@
         value: false,
         observer: '_updateToggleContainerClass',
       },
-      _rebaseOriginallyEnabled: Boolean,
+      _parentIsCurrent: Boolean,
     },
 
     behaviors: [
@@ -938,8 +928,10 @@
       if (revisionActions && revisionActions.rebase) {
         revisionActions.rebase.rebaseOnCurrent =
             !!revisionActions.rebase.enabled;
-        this._rebaseOriginallyEnabled = !!revisionActions.rebase.enabled;
+        this._parentIsCurrent = !revisionActions.rebase.enabled;
         revisionActions.rebase.enabled = true;
+      } else {
+        this._parentIsCurrent = true;
       }
       return revisionActions;
     },
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 d8367610..7d4a54b 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
@@ -399,6 +399,7 @@
           title: 'Rebase onto tip of branch or parent change',
         },
       };
+      element._parentIsCurrent = undefined;
 
       // Rebase enabled should always end up true.
       // When rebase is enabled initially, rebaseOnCurrent should be set to
@@ -408,6 +409,7 @@
 
       assert.isTrue(currentRevisionActions.rebase.enabled);
       assert.isTrue(currentRevisionActions.rebase.rebaseOnCurrent);
+      assert.isFalse(element._parentIsCurrent);
 
       delete currentRevisionActions.rebase.enabled;
 
@@ -418,6 +420,21 @@
 
       assert.isTrue(currentRevisionActions.rebase.enabled);
       assert.isFalse(currentRevisionActions.rebase.rebaseOnCurrent);
+      assert.isTrue(element._parentIsCurrent);
+    });
+
+    test('_updateRebaseAction sets _parentIsCurrent on no rebase', () => {
+      const currentRevisionActions = {
+        cherrypick: {
+          enabled: true,
+          label: 'Cherry Pick',
+          method: 'POST',
+          title: 'cherrypick',
+        },
+      };
+      element._parentIsCurrent = undefined;
+      element._updateRebaseAction(currentRevisionActions);
+      assert.isTrue(element._parentIsCurrent);
     });
 
     test('_reload is called when an approved label is removed', () => {
diff --git a/polygerrit-ui/app/elements/change/gr-commit-info/gr-commit-info.html b/polygerrit-ui/app/elements/change/gr-commit-info/gr-commit-info.html
index 67b54d6..3eaeab25 100644
--- a/polygerrit-ui/app/elements/change/gr-commit-info/gr-commit-info.html
+++ b/polygerrit-ui/app/elements/change/gr-commit-info/gr-commit-info.html
@@ -25,9 +25,6 @@
         align-items: center;
         display: flex;
       }
-      gr-copy-clipboard {
-        padding-left: .5em;
-      }
     </style>
     <div class="container">
       <template is="dom-if" if="[[_showWebLink]]">
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog.html b/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog.html
index 582a03c..4370d7e 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog.html
+++ b/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog.html
@@ -15,7 +15,9 @@
 -->
 
 <link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="../../shared/gr-autocomplete/gr-autocomplete.html">
 <link rel="import" href="../../shared/gr-confirm-dialog/gr-confirm-dialog.html">
+<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
 <link rel="import" href="../../../styles/shared-styles.html">
 
 <dom-module id="gr-confirm-rebase-dialog">
@@ -49,6 +51,7 @@
       }
     </style>
     <gr-confirm-dialog
+        id="confirmDialog"
         confirm-label="Rebase"
         on-confirm="_handleConfirmTap"
         on-cancel="_handleCancelTap">
@@ -98,15 +101,18 @@
           </label>
         </div>
         <div class="parentRevisionContainer">
-          <input is="iron-input"
-              type="text"
+          <gr-autocomplete
               id="parentInput"
-              bind-value="{{base}}"
+              query="[[_query]]"
+              text="{{_inputText}}"
               on-tap="_handleEnterChangeNumberTap"
+              on-commit="_handleBaseSelected"
               placeholder="Change number">
+          </gr-autocomplete>
         </div>
       </div>
     </gr-confirm-dialog>
+    <gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
   </template>
   <script src="gr-confirm-rebase-dialog.js"></script>
 </dom-module>
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog.js b/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog.js
index eb0fa17..e4f6e97 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog.js
+++ b/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog.js
@@ -36,14 +36,62 @@
        * @type {?string} */
       base: String,
       branch: String,
+      changeNumber: Number,
       hasParent: Boolean,
       rebaseOnCurrent: Boolean,
+      _inputText: String,
+      _query: {
+        type: Function,
+        value() {
+          return this._getChangeSuggestions.bind(this);
+        },
+      },
+      _recentChanges: Array,
     },
 
     observers: [
       '_updateSelectedOption(rebaseOnCurrent, hasParent)',
     ],
 
+    // This is called by gr-change-actions every time the rebase dialog is
+    // re-opened. Unlike other autocompletes that make a request with each
+    // updated input, this one gets all recent changes once and then filters
+    // them by the input. The query is re-run each time the dialog is opened
+    // in case there are new/updated changes in the generic query since the
+    // last time it was run.
+    fetchRecentChanges() {
+      return this.$.restAPI.getChanges(null, `is:open -age:90d`)
+          .then(response => {
+            const changes = [];
+            for (const key in response) {
+              if (!response.hasOwnProperty(key)) { continue; }
+              changes.push({
+                name: `${response[key]._number}: ${response[key].subject}`,
+                value: response[key]._number,
+              });
+            }
+            this._recentChanges = changes;
+            return this._recentChanges;
+          });
+    },
+
+    _getRecentChanges() {
+      if (this._recentChanges) {
+        return Promise.resolve(this._recentChanges);
+      }
+      return this.fetchRecentChanges();
+    },
+
+    _getChangeSuggestions(input) {
+      return this._getRecentChanges().then(changes =>
+          this._filterChanges(input, changes));
+    },
+
+    _filterChanges(input, changes) {
+      return changes.filter(change => change.name.includes(input) &&
+          change.value !== this.changeNumber);
+    },
+
     _displayParentOption(rebaseOnCurrent, hasParent) {
       return hasParent && rebaseOnCurrent;
     },
@@ -58,11 +106,13 @@
 
     _handleConfirmTap(e) {
       e.preventDefault();
+      this._inputText = '';
       this.fire('confirm', null, {bubbles: false});
     },
 
     _handleCancelTap(e) {
       e.preventDefault();
+      this._inputText = '';
       this.fire('cancel', null, {bubbles: false});
     },
 
@@ -85,6 +135,10 @@
       this.base = null;
     },
 
+    _handleBaseSelected(e) {
+      this.base = e.detail.value;
+    },
+
     _handleEnterChangeNumberTap() {
       this.$.rebaseOnOtherInput.checked = true;
     },
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog_test.html b/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog_test.html
index 0ea7c49..ccfc368 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog_test.html
+++ b/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog_test.html
@@ -34,9 +34,15 @@
 <script>
   suite('gr-confirm-rebase-dialog tests', () => {
     let element;
+    let sandbox;
 
     setup(() => {
       element = fixture('basic');
+      sandbox = sinon.sandbox.create();
+    });
+
+    teardown(() => {
+      sandbox.restore();
     });
 
     test('controls with parent and rebase on current available', () => {
@@ -82,5 +88,88 @@
       assert.isTrue(element.$.rebaseOnTip.hasAttribute('hidden'));
       assert.isFalse(element.$.tipUpToDateMsg.hasAttribute('hidden'));
     });
+
+    test('input cleared on cancel or submit', () => {
+      element._inputText = '123';
+      element.$.confirmDialog.fire('confirm');
+      assert.equal(element._inputText, '');
+
+      element._inputText = '123';
+      element.$.confirmDialog.fire('cancel');
+      assert.equal(element._inputText, '');
+    });
+
+    suite('parent suggestions', () => {
+      let recentChanges;
+      setup(() => {
+        recentChanges = [
+          {
+            name: '123: my first awesome change',
+            value: 123,
+          },
+          {
+            name: '124: my second awesome change',
+            value: 124,
+          },
+          {
+            name: '245: my third awesome change',
+            value: 245,
+          },
+        ];
+
+        sandbox.stub(element.$.restAPI, 'getChanges').returns(Promise.resolve(
+            [
+              {
+                _number: 123,
+                subject: 'my first awesome change',
+              },
+              {
+                _number: 124,
+                subject: 'my second awesome change',
+              },
+              {
+                _number: 245,
+                subject: 'my third awesome change',
+              },
+            ]
+        ));
+      });
+
+      test('_getRecentChanges', () => {
+        sandbox.spy(element, '_getRecentChanges');
+        return element._getRecentChanges().then(() => {
+          assert.deepEqual(element._recentChanges, recentChanges);
+          assert.equal(element.$.restAPI.getChanges.callCount, 1);
+          // When called a second time, should not re-request recent changes.
+          element._getRecentChanges();
+        }).then(() => {
+          assert.equal(element._getRecentChanges.callCount, 2);
+          assert.equal(element.$.restAPI.getChanges.callCount, 1);
+        });
+      });
+
+      test('_filterChanges', () => {
+        assert.equal(element._filterChanges('123', recentChanges).length, 1);
+        assert.equal(element._filterChanges('12', recentChanges).length, 2);
+        assert.equal(element._filterChanges('awesome', recentChanges).length,
+            3);
+        assert.equal(element._filterChanges('third', recentChanges).length,
+            1);
+
+        element.changeNumber = 123;
+        assert.equal(element._filterChanges('123', recentChanges).length, 0);
+        assert.equal(element._filterChanges('124', recentChanges).length, 1);
+        assert.equal(element._filterChanges('awesome', recentChanges).length,
+            2);
+      });
+
+      test('input text change triggers function', () => {
+        sandbox.spy(element, '_getRecentChanges');
+        element._inputText = '1';
+        assert.isTrue(element._getRecentChanges.calledOnce);
+        element._inputText = '12';
+        assert.isTrue(element._getRecentChanges.calledTwice);
+      });
+    });
   });
 </script>
diff --git a/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header.html b/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header.html
index c92919d..4c6b95a 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header.html
+++ b/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header.html
@@ -135,6 +135,9 @@
         font-family: var(--font-family-bold);
         margin-right: 24px;
       }
+      gr-commit-info {
+        margin-right: -5px;
+      }
       @media screen and (max-width: 50em) {
         .patchInfo-header .desktop {
           display: none;
diff --git a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_test.html b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_test.html
index 8d91ef8..6a7966b 100644
--- a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_test.html
+++ b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_test.html
@@ -55,7 +55,7 @@
       stub('gr-rest-api-interface', {
         getConfig() { return Promise.resolve({}); },
         getAccount() { return Promise.resolve({}); },
-        getChange() { return Promise.resolve([{}]); },
+        getChange() { return Promise.resolve({}); },
         getChangeSuggestedReviewers() { return Promise.resolve([]); },
       });
 
diff --git a/polygerrit-ui/app/elements/core/gr-navigation/gr-navigation.html b/polygerrit-ui/app/elements/core/gr-navigation/gr-navigation.html
index 7b17f22..006b9d9 100644
--- a/polygerrit-ui/app/elements/core/gr-navigation/gr-navigation.html
+++ b/polygerrit-ui/app/elements/core/gr-navigation/gr-navigation.html
@@ -319,10 +319,12 @@
       /**
        * @param {{ _number: number, project: string }} change The change object.
        * @param {string} path The file path.
+       * @param {number=} opt_patchNum
        * @return {string}
        */
-      getEditUrlForDiff(change, path) {
-        return this.getEditUrlForDiffById(change._number, change.project, path);
+      getEditUrlForDiff(change, path, opt_patchNum) {
+        return this.getEditUrlForDiffById(change._number, change.project, path,
+            opt_patchNum);
       },
 
       /**
@@ -331,13 +333,13 @@
        * @param {string} path The file path.
        * @return {string}
        */
-      getEditUrlForDiffById(changeNum, project, path) {
+      getEditUrlForDiffById(changeNum, project, path, opt_patchNum) {
         return this._getUrlFor({
           view: Gerrit.Nav.View.EDIT,
           changeNum,
           project,
           path,
-          patchNum: EDIT_PATCHNUM,
+          patchNum: opt_patchNum || EDIT_PATCHNUM,
         });
       },
 
diff --git a/polygerrit-ui/app/elements/core/gr-router/gr-router.js b/polygerrit-ui/app/elements/core/gr-router/gr-router.js
index dc264f2..cc59be8 100644
--- a/polygerrit-ui/app/elements/core/gr-router/gr-router.js
+++ b/polygerrit-ui/app/elements/core/gr-router/gr-router.js
@@ -114,9 +114,8 @@
     // eslint-disable-next-line max-len
     CHANGE_OR_DIFF: /^\/c\/(.+)\/\+\/(\d+)(\/?((-?\d+|edit)(\.\.(\d+|edit))?(\/(.+))?))?\/?$/,
 
-    // Matches /c/<project>/+/<changeNum>/edit/<path>,edit
-    // eslint-disable-next-line max-len
-    DIFF_EDIT: /^\/c\/(.+)\/\+\/(\d+)\/edit\/(.+),edit$/,
+    // Matches /c/<project>/+/<changeNum>/[<patchNum|edit>]/<path>,edit
+    DIFF_EDIT: /^\/c\/(.+)\/\+\/(\d+)\/(\d+|edit)\/(.+),edit$/,
 
     // Matches non-project-relative
     // /c/<changeNum>/[<basePatchNum>..]<patchNum>/<path>.
@@ -498,9 +497,12 @@
 
       return this.$.restAPI.getFromProjectLookup(params.changeNum)
           .then(project => {
-            // Do nothing if the lookup request failed. This avoids an infinite
-            // loop of project lookups.
-            if (!project) { return; }
+            // Show a 404 and terminate if the lookup request failed. Attempting
+            // to redirect after failing to get the project loops infinitely.
+            if (!project) {
+              this._show404();
+              return;
+            }
 
             params.project = project;
             this._normalizePatchRangeParams(params);
@@ -1250,7 +1252,8 @@
       this._redirectOrNavigate({
         project: ctx.params[0],
         changeNum: ctx.params[1],
-        path: ctx.params[2],
+        patchNum: ctx.params[2],
+        path: ctx.params[3],
         view: Gerrit.Nav.View.EDIT,
       });
     },
@@ -1333,6 +1336,10 @@
      * Catchall route for when no other route is matched.
      */
     _handleDefaultRoute() {
+      this._show404();
+    },
+
+    _show404() {
       // Note: the app's 404 display is tightly-coupled with catching 404
       // network responses, so we simulate a 404 response status to display it.
       // TODO: Decouple the gr-app error view from network responses.
diff --git a/polygerrit-ui/app/elements/core/gr-router/gr-router_test.html b/polygerrit-ui/app/elements/core/gr-router/gr-router_test.html
index 7011c65..f5a7dd9 100644
--- a/polygerrit-ui/app/elements/core/gr-router/gr-router_test.html
+++ b/polygerrit-ui/app/elements/core/gr-router/gr-router_test.html
@@ -430,11 +430,13 @@
       suite('_normalizeLegacyRouteParams', () => {
         let rangeStub;
         let redirectStub;
+        let show404Stub;
 
         setup(() => {
           rangeStub = sandbox.stub(element, '_normalizePatchRangeParams')
               .returns(Promise.resolve());
           redirectStub = sandbox.stub(element, '_redirect');
+          show404Stub = sandbox.stub(element, '_show404');
         });
 
         test('w/o changeNum', () => {
@@ -445,6 +447,7 @@
             assert.isFalse(rangeStub.called);
             assert.isNotOk(params.project);
             assert.isFalse(redirectStub.called);
+            assert.isFalse(show404Stub.called);
           });
         });
 
@@ -456,18 +459,19 @@
             assert.isTrue(rangeStub.called);
             assert.equal(params.project, 'foo/bar');
             assert.isTrue(redirectStub.calledOnce);
+            assert.isFalse(show404Stub.called);
           });
         });
 
         test('halts on project lookup failure', () => {
           projectLookupStub.returns(Promise.resolve(undefined));
-
           const params = {changeNum: 1234};
           return element._normalizeLegacyRouteParams(params).then(() => {
             assert.isTrue(projectLookupStub.called);
             assert.isFalse(rangeStub.called);
             assert.isUndefined(params.project);
             assert.isFalse(redirectStub.called);
+            assert.isTrue(show404Stub.calledOnce);
           });
         });
       });
@@ -1313,7 +1317,8 @@
             params: [
               'foo/bar', // 0 Project
               1234, // 1 Change number
-              'foo/bar/baz', // 2 File path
+              3, // 2 Patch num
+              'foo/bar/baz', // 3 File path
             ],
           };
           const appParams = {
@@ -1321,6 +1326,7 @@
             changeNum: 1234,
             view: Gerrit.Nav.View.EDIT,
             path: 'foo/bar/baz',
+            patchNum: 3,
           };
 
           element._handleDiffEditRoute(ctx);
diff --git a/polygerrit-ui/app/elements/edit/gr-edit-file-controls/gr-edit-file-controls.html b/polygerrit-ui/app/elements/edit/gr-edit-file-controls/gr-edit-file-controls.html
index f0d7f6f..96ba196b 100644
--- a/polygerrit-ui/app/elements/edit/gr-edit-file-controls/gr-edit-file-controls.html
+++ b/polygerrit-ui/app/elements/edit/gr-edit-file-controls/gr-edit-file-controls.html
@@ -37,6 +37,12 @@
       #more {
         margin-right: 1em;
       }
+      gr-button,
+      gr-dropdown {
+        --gr-button: {
+          height: 1.8em;
+        }
+      }
       gr-dropdown {
         --gr-dropdown-item: {
           background-color: transparent;
diff --git a/polygerrit-ui/app/elements/gr-app.html b/polygerrit-ui/app/elements/gr-app.html
index ef93794..ced27dd 100644
--- a/polygerrit-ui/app/elements/gr-app.html
+++ b/polygerrit-ui/app/elements/gr-app.html
@@ -55,6 +55,7 @@
 <link rel="import" href="./settings/gr-registration-dialog/gr-registration-dialog.html">
 <link rel="import" href="./settings/gr-settings-view/gr-settings-view.html">
 <link rel="import" href="./shared/gr-fixed-panel/gr-fixed-panel.html">
+<link rel="import" href="./shared/gr-rest-api-interface/gr-rest-api-interface.html">
 
 <script src="../scripts/util.js"></script>
 
diff --git a/polygerrit-ui/app/elements/shared/gr-dropdown/gr-dropdown.html b/polygerrit-ui/app/elements/shared/gr-dropdown/gr-dropdown.html
index 6d7df94..62da3bb 100644
--- a/polygerrit-ui/app/elements/shared/gr-dropdown/gr-dropdown.html
+++ b/polygerrit-ui/app/elements/shared/gr-dropdown/gr-dropdown.html
@@ -37,11 +37,8 @@
         background-color: #fff;
         box-shadow: 0 1px 5px rgba(0, 0, 0, .3);
       }
-      button {
-        background: none;
-        border: none;
-        font: inherit;
-        padding: .3em 0;
+      gr-button {
+        @apply --gr-button;
       }
       gr-avatar {
         height: 2em;
diff --git a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js
index b5bb35a..3f19f7e 100644
--- a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js
+++ b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js
@@ -1993,7 +1993,11 @@
      */
     getChange(changeNum, opt_errFn) {
       // Cannot use _changeBaseURL, as this function is used by _projectLookup.
-      return this.fetchJSON(`/changes/?q=${changeNum}`, opt_errFn);
+      return this.fetchJSON(`/changes/?q=change:${changeNum}`, opt_errFn)
+          .then(res => {
+            if (!res || !res.length) { return null; }
+            return res[0];
+          });
     },
 
     /**
@@ -2026,8 +2030,7 @@
         this.fire('page-error', {response});
       };
 
-      return this.getChange(changeNum, onError).then(res => {
-        const change = res[0];
+      return this.getChange(changeNum, onError).then(change => {
         if (!change || !change.project) { return; }
         this.setInProjectLookup(changeNum, change.project);
         return change.project;
diff --git a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface_test.html b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface_test.html
index a8c09d1..33eb181 100644
--- a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface_test.html
@@ -1036,7 +1036,7 @@
     suite('getFromProjectLookup', () => {
       test('getChange fails', () => {
         sandbox.stub(element, 'getChange')
-            .returns(Promise.resolve([]));
+            .returns(Promise.resolve(null));
         return element.getFromProjectLookup().then(val => {
           assert.strictEqual(val, undefined);
           assert.deepEqual(element._projectLookup, {});
@@ -1044,7 +1044,7 @@
       });
 
       test('getChange succeeds, no project', () => {
-        sandbox.stub(element, 'getChange').returns(Promise.resolve([]));
+        sandbox.stub(element, 'getChange').returns(Promise.resolve(null));
         return element.getFromProjectLookup().then(val => {
           assert.strictEqual(val, undefined);
           assert.deepEqual(element._projectLookup, {});
@@ -1053,7 +1053,7 @@
 
       test('getChange succeeds with project', () => {
         sandbox.stub(element, 'getChange')
-            .returns(Promise.resolve([{project: 'project'}]));
+            .returns(Promise.resolve({project: 'project'}));
         return element.getFromProjectLookup('test').then(val => {
           assert.equal(val, 'project');
           assert.deepEqual(element._projectLookup, {test: 'project'});
diff --git a/polygerrit-ui/app/embed/change-diff-views.html b/polygerrit-ui/app/embed/embed.html
similarity index 81%
rename from polygerrit-ui/app/embed/change-diff-views.html
rename to polygerrit-ui/app/embed/embed.html
index 8426585..14b6b66 100644
--- a/polygerrit-ui/app/embed/change-diff-views.html
+++ b/polygerrit-ui/app/embed/embed.html
@@ -16,4 +16,6 @@
 <link rel="import" href="../bower_components/polymer/polymer.html">
 <link rel="import" href="../elements/change/gr-change-view/gr-change-view.html">
 <link rel="import" href="../elements/diff/gr-diff-view/gr-diff-view.html">
+<link rel="import" href="../elements/change-list/gr-dashboard-view/gr-dashboard-view.html">
+<link rel="import" href="../elements/change-list/gr-change-list-view/gr-change-list-view.html">
 <link rel="import" href="../styles/app-theme.html">
diff --git a/polygerrit-ui/app/embed/embed_test.html b/polygerrit-ui/app/embed/embed_test.html
index 26ea895..80f7e5d 100644
--- a/polygerrit-ui/app/embed/embed_test.html
+++ b/polygerrit-ui/app/embed/embed_test.html
@@ -16,11 +16,11 @@
 -->
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
-<title>change-diff-views-embed_test</title>
+<title>embed_test</title>
 
 <script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
 <script src="../../../bower_components/web-component-tester/browser.js"></script>
-<link rel="import" href="../polygerrit_ui/elements/change-diff-views.html"/>
+<link rel="import" href="../polygerrit_ui/elements/embed.html"/>
 
 <script>void(0);</script>
 
@@ -36,6 +36,18 @@
   </template>
 </test-fixture>
 
+<test-fixture id="dashboard-view">
+  <template>
+    <gr-dashboard-view></gr-dashboard-view>
+  </template>
+</test-fixture>
+
+<test-fixture id="change-list-view">
+  <template>
+    <gr-change-list-view></gr-change-list-view>
+  </template>
+</test-fixture>
+
 <script>
   suite('embed test', () => {
     test('gr-change-view is embedded', () => {
@@ -47,5 +59,15 @@
       const element = fixture('diff-view');
       assert.equal(element.is, 'gr-diff-view');
     });
+
+    test('dashboard-view is embedded', () => {
+      const element = fixture('dashboard-view');
+      assert.equal(element.is, 'gr-dashboard-view');
+    });
+
+    test('change-list-view is embedded', () => {
+      const element = fixture('change-list-view');
+      assert.equal(element.is, 'gr-change-list-view');
+    });
   });
 </script>