Merge "Add ignore-whitespace control"
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list.html b/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list.html
index 4ebe027..372e6be 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list.html
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list.html
@@ -67,13 +67,18 @@
             </td>
           </tr>
         </template>
-        <template is="dom-if" if="[[!changeSection.results.length]]">
+        <template is="dom-if" if="[[_isEmpty(changeSection)]]">
           <tr class="noChanges">
             <td class="leftPadding"></td>
             <td class="star" hidden$="[[!showStar]]" hidden></td>
             <td class="cell"
                 colspan$="[[_computeColspan(changeTableColumns, labelNames)]]">
-              No changes
+              <template is="dom-if" if="[[_isOutgoing(changeSection)]]">
+                <slot name="empty-outgoing"></slot>
+              </template>
+              <template is="dom-if" if="[[!_isOutgoing(changeSection)]]">
+                No changes
+              </template>
             </td>
           </tr>
         </template>
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list.js b/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list.js
index eb916c1..ba768d9 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list.js
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list.js
@@ -347,5 +347,13 @@
       this.$.cursor.stops = this._getListItems();
       this.$.cursor.moveToStart();
     },
+
+    _isOutgoing(section) {
+      return !!section.isOutgoing;
+    },
+
+    _isEmpty(section) {
+      return !section.results.length;
+    },
   });
 })();
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list_test.html b/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list_test.html
index 3cf5f9a..bb904b5 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list_test.html
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list_test.html
@@ -270,6 +270,32 @@
       assert.equal(noChangesMsg.length, 2);
     });
 
+    suite('empty outgoing', () => {
+      test('not shown on empty non-outgoing sections', () => {
+        const section = {results: []};
+        assert.isTrue(element._isEmpty(section));
+        assert.isFalse(element._isOutgoing(section));
+      });
+
+      test('shown on empty outgoing sections', () => {
+        const section = {results: [], isOutgoing: true};
+        assert.isTrue(element._isEmpty(section));
+        assert.isTrue(element._isOutgoing(section));
+      });
+
+      test('not shown on non-empty outgoing sections', () => {
+        const section = {isOutgoing: true, results: [
+          {_number: 0, labels: {Verified: {approved: {}}}}]};
+        assert.isFalse(element._isEmpty(section));
+        assert.isTrue(element._isOutgoing(section));
+      });
+    });
+
+    test('_isOutgoing', () => {
+      assert.isTrue(element._isOutgoing({results: [], isOutgoing: true}));
+      assert.isFalse(element._isOutgoing({results: []}));
+    });
+
     suite('empty column preference', () => {
       let element;
 
diff --git a/polygerrit-ui/app/elements/change-list/gr-create-commands-dialog/gr-create-commands-dialog.html b/polygerrit-ui/app/elements/change-list/gr-create-commands-dialog/gr-create-commands-dialog.html
new file mode 100644
index 0000000..bf17b911
--- /dev/null
+++ b/polygerrit-ui/app/elements/change-list/gr-create-commands-dialog/gr-create-commands-dialog.html
@@ -0,0 +1,87 @@
+<!--
+@license
+Copyright (C) 2018 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+
+<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="../../shared/gr-dialog/gr-dialog.html">
+<link rel="import" href="../../shared/gr-overlay/gr-overlay.html">
+<link rel="import" href="../../shared/gr-shell-command/gr-shell-command.html">
+
+<dom-module id="gr-create-commands-dialog">
+  <template>
+    <style include="shared-styles">
+      ol {
+        list-style: decimal;
+        margin-left: 1em;
+      }
+      p {
+        margin-bottom: .75em;
+      }
+      #commandsDialog {
+        max-width: 40em;
+      }
+    </style>
+    <gr-overlay id="commandsOverlay" with-backdrop>
+      <gr-dialog
+          id="commandsDialog"
+          confirm-label="Done"
+          cancel-label=""
+          confirm-on-enter
+          on-confirm="_handleClose">
+        <div class="header" slot="header">
+          Create change commands
+        </div>
+        <div class="main" slot="main">
+          <ol>
+            <li>
+              <p>
+                Make the changes to the files on your machine
+              </p>
+            </li>
+            <li>
+              <p>
+                If you are making a new commit use
+              </p>
+              <gr-shell-command command="[[_createNewCommitCommand]]"></gr-shell-command>
+              <p>
+                Or to amend an existing commit use
+              </p>
+              <gr-shell-command command="[[_amendExistingCommitCommand]]"></gr-shell-command>
+              <p>
+                Please make sure you add a commit message as it becomes the
+                description for your change.
+              </p>
+            </li>
+            <li>
+              <p>
+                Push the change for code review
+              </p>
+              <gr-shell-command command="[[_pushCommand]]"></gr-shell-command>
+            </li>
+            <li>
+              <p>
+                Close this dialog and you should be able to see your recently
+                created change in ``Outgoing changes'' section on
+                ``Your changes'' page.
+              </p>
+            </li>
+          </ol>
+        </div>
+      </gr-dialog>
+    </gr-overlay>
+  </template>
+  <script src="gr-create-commands-dialog.js"></script>
+</dom-module>
diff --git a/polygerrit-ui/app/elements/change-list/gr-create-commands-dialog/gr-create-commands-dialog.js b/polygerrit-ui/app/elements/change-list/gr-create-commands-dialog/gr-create-commands-dialog.js
new file mode 100644
index 0000000..0e71f1c
--- /dev/null
+++ b/polygerrit-ui/app/elements/change-list/gr-create-commands-dialog/gr-create-commands-dialog.js
@@ -0,0 +1,58 @@
+/**
+ * @license
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+(function() {
+  'use strict';
+
+  const Commands = {
+    CREATE: 'git commit',
+    AMEND: 'git commit --amend',
+    PUSH_PREFIX: 'git push origin HEAD:refs/for/',
+  };
+
+  Polymer({
+    is: 'gr-create-commands-dialog',
+    properties: {
+      branch: String,
+      _createNewCommitCommand: {
+        type: String,
+        readonly: true,
+        value: Commands.CREATE,
+      },
+      _amendExistingCommitCommand: {
+        type: String,
+        readonly: true,
+        value: Commands.AMEND,
+      },
+      _pushCommand: {
+        type: String,
+        computed: '_computePushCommand(branch)',
+      },
+    },
+
+    open() {
+      this.$.commandsOverlay.open();
+    },
+
+    _handleClose() {
+      this.$.commandsOverlay.close();
+    },
+
+    _computePushCommand(branch) {
+      return Commands.PUSH_PREFIX + branch;
+    },
+  });
+})();
diff --git a/polygerrit-ui/app/elements/change-list/gr-create-commands-dialog/gr-create-commands-dialog_test.html b/polygerrit-ui/app/elements/change-list/gr-create-commands-dialog/gr-create-commands-dialog_test.html
new file mode 100644
index 0000000..e00037d
--- /dev/null
+++ b/polygerrit-ui/app/elements/change-list/gr-create-commands-dialog/gr-create-commands-dialog_test.html
@@ -0,0 +1,53 @@
+<!DOCTYPE html>
+<!--
+@license
+Copyright (C) 2018 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+
+<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
+<title>gr-create-commands-dialog</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="../../../test/common-test-setup.html"/>
+<link rel="import" href="gr-create-commands-dialog.html">
+
+<script>void(0);</script>
+
+<test-fixture id="basic">
+  <template>
+    <gr-create-commands-dialog></gr-create-commands-dialog>
+  </template>
+</test-fixture>
+
+<script>
+  suite('gr-create-commands-dialog tests', () => {
+    let element;
+
+    setup(() => {
+      element = fixture('basic');
+    });
+
+    test('_computePushCommand', () => {
+      element.branch = 'master';
+      assert.equal(element._pushCommand,
+          'git push origin HEAD:refs/for/master');
+
+      element.branch = 'stable-2.15';
+      assert.equal(element._pushCommand,
+          'git push origin HEAD:refs/for/stable-2.15');
+    });
+  });
+</script>
diff --git a/polygerrit-ui/app/elements/change-list/gr-create-destination-dialog/gr-create-destination-dialog.html b/polygerrit-ui/app/elements/change-list/gr-create-destination-dialog/gr-create-destination-dialog.html
new file mode 100644
index 0000000..d12d84b
--- /dev/null
+++ b/polygerrit-ui/app/elements/change-list/gr-create-destination-dialog/gr-create-destination-dialog.html
@@ -0,0 +1,48 @@
+<!--
+@license
+Copyright (C) 2018 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+
+<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="../../shared/gr-dialog/gr-dialog.html">
+<link rel="import" href="../../shared/gr-overlay/gr-overlay.html">
+<link rel="import" href="../../shared/gr-repo-branch-picker/gr-repo-branch-picker.html">
+
+<dom-module id="gr-create-destination-dialog">
+  <template>
+    <style include="shared-styles">
+    </style>
+    <gr-overlay id="createOverlay" with-backdrop>
+      <gr-dialog
+          confirm-label="View commands"
+          on-confirm="_pickerConfirm"
+          on-cancel="_handleClose"
+          disabled="[[!_repoAndBranchSelected]]">
+        <div class="header" slot="header">
+          Create change
+        </div>
+        <div class="main" slot="main">
+          <gr-repo-branch-picker
+              repo="{{_repo}}"
+              branch="{{_branch}}"></gr-repo-branch-picker>
+          <p>
+            If you haven't done so, you will need to clone the repository.
+          </p>
+        </div>
+      </gr-dialog>
+    </gr-overlay>
+  </template>
+  <script src="gr-create-destination-dialog.js"></script>
+</dom-module>
diff --git a/polygerrit-ui/app/elements/change-list/gr-create-destination-dialog/gr-create-destination-dialog.js b/polygerrit-ui/app/elements/change-list/gr-create-destination-dialog/gr-create-destination-dialog.js
new file mode 100644
index 0000000..4d2802e
--- /dev/null
+++ b/polygerrit-ui/app/elements/change-list/gr-create-destination-dialog/gr-create-destination-dialog.js
@@ -0,0 +1,58 @@
+/**
+ * @license
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+(function() {
+  'use strict';
+
+  /**
+   * Fired when a destination has been picked. Event details contain the repo
+   * name and the branch name.
+   *
+   * @event confirm
+   */
+
+  Polymer({
+    is: 'gr-create-destination-dialog',
+    properties: {
+      _repo: String,
+      _branch: String,
+      _repoAndBranchSelected: {
+        type: Boolean,
+        value: false,
+        computed: '_computeRepoAndBranchSelected(_repo, _branch)',
+      },
+    },
+    open() {
+      this._repo = '';
+      this._branch = '';
+      this.$.createOverlay.open();
+    },
+
+    _handleClose() {
+      this.$.createOverlay.close();
+    },
+
+    _pickerConfirm() {
+      this.$.createOverlay.close();
+      const detail = {repo: this._repo, branch: this._branch};
+      this.dispatchEvent(new CustomEvent('confirm', {detail, bubbles: false}));
+    },
+
+    _computeRepoAndBranchSelected(repo, branch) {
+      return !!(repo && branch);
+    },
+  });
+})();
diff --git a/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.html b/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.html
index b18e8cd..1edb8ea 100644
--- a/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.html
+++ b/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.html
@@ -21,6 +21,8 @@
 <link rel="import" href="../../change-list/gr-change-list/gr-change-list.html">
 <link rel="import" href="../../core/gr-reporting/gr-reporting.html">
 <link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
+<link rel="import" href="../gr-create-commands-dialog/gr-create-commands-dialog.html">
+<link rel="import" href="../gr-create-destination-dialog/gr-create-destination-dialog.html">
 <link rel="import" href="../gr-user-header/gr-user-header.html">
 
 <dom-module id="gr-dashboard-view">
@@ -43,10 +45,42 @@
       gr-user-header {
         border-bottom: 1px solid var(--border-color);
       }
+      #emptyOutgoing {
+        display: block;
+      }
+      #emptyOutgoing #graphic,
+      #emptyOutgoing #help {
+        display: inline-block;
+        margin: .5em;
+      }
+      #emptyOutgoing #graphic {
+        fill: var(--deemphasized-text-color);
+        max-width: 12em;
+      }
+      #emptyOutgoing #graphic svg {
+        display: block;
+        margin: 0 auto;
+        max-width: 100px;
+      }
+      #emptyOutgoing #graphic p {
+        text-align: center;
+      }
+      #emptyOutgoing #help {
+        vertical-align: top;
+      }
+      #emptyOutgoing #help h1 {
+        font-size: var(--font-size-large);
+      }
+      #emptyOutgoing #help p {
+        max-width: 35em;
+      }
       @media only screen and (max-width: 50em) {
         .loading {
           padding: 0 var(--default-horizontal-margin);
         }
+        #emptyOutgoing #graphic {
+          display: none;
+        }
       }
     </style>
     <div class="loading" hidden$="[[!_loading]]">Loading...</div>
@@ -62,8 +96,32 @@
           selected-index="{{viewState.selectedChangeIndex}}"
           sections="[[_results]]"
           on-toggle-star="_handleToggleStar"
-          on-toggle-reviewed="_handleToggleReviewed"></gr-change-list>
+          on-toggle-reviewed="_handleToggleReviewed">
+        <div id="emptyOutgoing" slot="empty-outgoing">
+          <div id="graphic">
+            <svg width="150" height="100">
+              <circle cx="50" cy="50" r="50"></circle>
+            </svg>
+            <p>
+              No outgoing changes yet
+            </p>
+          </div>
+          <div id="help">
+            <h1>Push your first changes for code review</h1>
+            <p>
+              Pushing a change for review is easy, but a little different from
+              other git code review tools. Click on the `Create Change' button
+              and follow the step by step instructions.
+            </p>
+            <gr-button on-tap="_createChangeTap">Create Change</gr-button>
+          </div>
+        </div>
+      </gr-change-list>
     </div>
+    <gr-create-destination-dialog
+        id="destinationDialog"
+        on-confirm="_handleDestinationConfirm"></gr-create-destination-dialog>
+    <gr-create-commands-dialog id="commandsDialog"></gr-create-commands-dialog>
     <gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
     <gr-reporting id="reporting"></gr-reporting>
   </template>
diff --git a/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.js b/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.js
index d84dec5..6dec1a6 100644
--- a/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.js
+++ b/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.js
@@ -45,6 +45,7 @@
       // by the viewing user.
       name: 'Outgoing reviews',
       query: 'is:open owner:${user} -is:wip -is:ignored',
+      isOutgoing: true,
     },
     {
       // Non-WIP open changes not owned by the viewed user, that the viewed user
@@ -249,6 +250,7 @@
               sectionName: res.sections[i].name,
               query: res.sections[i].query,
               results,
+              isOutgoing: res.sections[i].isOutgoing,
             })).filter((section, i) => !res.sections[i].hideIfEmpty ||
                 section.results.length);
           });
@@ -267,5 +269,14 @@
       this.$.restAPI.saveChangeReviewed(e.detail.change._number,
           e.detail.reviewed);
     },
+
+    _createChangeTap() {
+      this.$.destinationDialog.open();
+    },
+
+    _handleDestinationConfirm(e) {
+      this.$.commandsDialog.branch = e.detail.branch;
+      this.$.commandsDialog.open();
+    },
   });
 })();
diff --git a/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view_test.html b/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view_test.html
index cac2627..21e917a 100644
--- a/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view_test.html
+++ b/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view_test.html
@@ -257,6 +257,22 @@
       });
     });
 
+    test('preserve isOutgoing sections', () => {
+      const sections = [
+        {name: 'test1', query: 'test1', isOutgoing: true},
+        {name: 'test2', query: 'test2'},
+      ];
+      getChangesStub.restore();
+      sandbox.stub(element.$.restAPI, 'getChanges')
+          .returns(Promise.resolve([[], []]));
+
+      return element._fetchDashboardChanges({sections}).then(() => {
+        assert.equal(element._results.length, 2);
+        assert.isTrue(element._results[0].isOutgoing);
+        assert.isNotOk(element._results[1].isOutgoing);
+      });
+    });
+
     test('_computeUserHeaderClass', () => {
       assert.equal(element._computeUserHeaderClass(undefined), '');
       assert.equal(element._computeUserHeaderClass(''), '');
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 76d7f1e..461bfc4 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
@@ -606,6 +606,7 @@
     </gr-overlay>
     <gr-overlay id="uploadHelpOverlay" with-backdrop>
       <gr-upload-help-dialog
+          target-branch="[[_change.branch]]"
           on-close="_handleCloseUploadHelpDialog"></gr-upload-help-dialog>
     </gr-overlay>
     <gr-overlay id="includedInOverlay" with-backdrop>
diff --git a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.html b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.html
index a710b0b..fa984c9 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.html
+++ b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.html
@@ -287,6 +287,7 @@
               data-path$="[[file.__path]]" tabindex="-1">
               <div class$="[[_computeClass('status', file.__path)]]"
                   tabindex="0"
+                  title$="[[_computeFileStatusLabel(file.status)]]"
                   aria-label$="[[_computeFileStatusLabel(file.status)]]">
               [[_computeFileStatus(file.status)]]
             </div>
diff --git a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.js b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.js
index 95615ea..f54e058 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.js
+++ b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.js
@@ -35,6 +35,7 @@
     A: 'Added',
     C: 'Copied',
     D: 'Deleted',
+    M: 'Modified',
     R: 'Renamed',
     W: 'Rewritten',
     U: 'Unchanged',
@@ -894,6 +895,12 @@
           rev.description.substring(0, PATCH_DESC_MAX_LENGTH) : '';
     },
 
+    /**
+     * Get a descriptive label for use in the status indicator's tooltip and
+     * ARIA label.
+     * @param {string} status
+     * @return {string}
+     */
     _computeFileStatusLabel(status) {
       const statusCode = this._computeFileStatus(status);
       return FileStatus.hasOwnProperty(statusCode) ?
diff --git a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.html b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.html
index 8e32ea3..88b5f66 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.html
+++ b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.html
@@ -797,6 +797,11 @@
       assert.isTrue(tapSpy.lastCall.args[0].defaultPrevented);
     });
 
+    test('_computeFileStatusLabel', () => {
+      assert.equal(element._computeFileStatusLabel('A'), 'Added');
+      assert.equal(element._computeFileStatusLabel('M'), 'Modified');
+    });
+
     test('_handleFileListTap', () => {
       element._filesByPath = {
         '/COMMIT_MSG': {},
diff --git a/polygerrit-ui/app/elements/change/gr-upload-help-dialog/gr-upload-help-dialog.js b/polygerrit-ui/app/elements/change/gr-upload-help-dialog/gr-upload-help-dialog.js
index d796999..548116c 100644
--- a/polygerrit-ui/app/elements/change/gr-upload-help-dialog/gr-upload-help-dialog.js
+++ b/polygerrit-ui/app/elements/change/gr-upload-help-dialog/gr-upload-help-dialog.js
@@ -18,7 +18,7 @@
   'use strict';
 
   const COMMIT_COMMAND = 'git add . && git commit --amend --no-edit';
-  const PUSH_COMMAND = 'git push origin HEAD:refs/for/master';
+  const PUSH_COMMAND_PREFIX = 'git push origin HEAD:refs/for/';
 
   Polymer({
     is: 'gr-upload-help-dialog',
@@ -30,6 +30,7 @@
      */
 
     properties: {
+      targetBranch: String,
       _commitCommand: {
         type: String,
         value: COMMIT_COMMAND,
@@ -37,8 +38,7 @@
       },
       _pushCommand: {
         type: String,
-        value: PUSH_COMMAND,
-        readOnly: true,
+        computed: '_computePushCommand(targetBranch)',
       },
     },
 
@@ -46,5 +46,9 @@
       e.preventDefault();
       this.fire('close', null, {bubbles: false});
     },
+
+    _computePushCommand(targetBranch) {
+      return PUSH_COMMAND_PREFIX + targetBranch;
+    },
   });
 })();
diff --git a/polygerrit-ui/app/elements/change/gr-upload-help-dialog/gr-upload-help-dialog_test.html b/polygerrit-ui/app/elements/change/gr-upload-help-dialog/gr-upload-help-dialog_test.html
new file mode 100644
index 0000000..60fe3e6
--- /dev/null
+++ b/polygerrit-ui/app/elements/change/gr-upload-help-dialog/gr-upload-help-dialog_test.html
@@ -0,0 +1,52 @@
+<!DOCTYPE html>
+<!--
+@license
+Copyright (C) 2018 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+
+<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
+<title>gr-upload-help-dialog</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="../../../test/common-test-setup.html"/>
+<link rel="import" href="gr-upload-help-dialog.html">
+
+<script>void(0);</script>
+
+<test-fixture id="basic">
+  <template>
+    <gr-upload-help-dialog></gr-upload-help-dialog>
+  </template>
+</test-fixture>
+
+<script>
+  suite('gr-upload-help-dialog tests', () => {
+    let element;
+
+    setup(() => {
+      element = fixture('basic');
+    });
+
+    test('constructs push command from branch', () => {
+      element.targetBranch = 'foo';
+      assert.equal(element._pushCommand, 'git push origin HEAD:refs/for/foo');
+
+      element.targetBranch = 'master';
+      assert.equal(element._pushCommand,
+          'git push origin HEAD:refs/for/master');
+    });
+  });
+</script>
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 a307b85..bdd0942 100644
--- a/polygerrit-ui/app/elements/core/gr-router/gr-router.js
+++ b/polygerrit-ui/app/elements/core/gr-router/gr-router.js
@@ -396,7 +396,8 @@
         suffix += ',edit';
       }
       if (params.project) {
-        return `/c/${params.project}/+/${params.changeNum}${suffix}`;
+        const encodedProject = this.encodeURL(params.project, true);
+        return `/c/${encodedProject}/+/${params.changeNum}${suffix}`;
       } else {
         return `/c/${params.changeNum}${suffix}`;
       }
@@ -462,7 +463,8 @@
       }
 
       if (params.project) {
-        return `/c/${params.project}/+/${params.changeNum}${suffix}`;
+        const encodedProject = this.encodeURL(params.project, true);
+        return `/c/${encodedProject}/+/${params.changeNum}${suffix}`;
       } else {
         return `/c/${params.changeNum}${suffix}`;
       }
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 2211039..53a7c07 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
@@ -283,6 +283,16 @@
             '/c/test/+/1234/5..10?revert&foo=bar');
       });
 
+      test('change with repo name encoding', () => {
+        const params = {
+          view: Gerrit.Nav.View.CHANGE,
+          changeNum: '1234',
+          project: 'x+/y+/z+/w',
+        };
+        assert.equal(element._generateUrl(params),
+            '/c/x%252B/y%252B/z%252B/w/+/1234');
+      });
+
       test('diff', () => {
         const params = {
           view: Gerrit.Nav.View.DIFF,
@@ -317,6 +327,18 @@
             '/c/test/+/42/2/file.cpp#b123');
       });
 
+      test('diff with repo name encoding', () => {
+        const params = {
+          view: Gerrit.Nav.View.DIFF,
+          changeNum: '42',
+          path: 'x+y/path.cpp',
+          patchNum: 12,
+          project: 'x+/y',
+        };
+        assert.equal(element._generateUrl(params),
+            '/c/x%252B/y/+/42/12/x%252By/path.cpp');
+      });
+
       test('edit', () => {
         const params = {
           view: Gerrit.Nav.View.EDIT,
diff --git a/polygerrit-ui/app/elements/settings/gr-change-table-editor/gr-change-table-editor.html b/polygerrit-ui/app/elements/settings/gr-change-table-editor/gr-change-table-editor.html
index fa188d7..4f69513 100644
--- a/polygerrit-ui/app/elements/settings/gr-change-table-editor/gr-change-table-editor.html
+++ b/polygerrit-ui/app/elements/settings/gr-change-table-editor/gr-change-table-editor.html
@@ -37,6 +37,9 @@
         cursor: pointer;
         text-align: center;
       }
+      .checkboxContainer input {
+        cursor: pointer;
+      }
       .checkboxContainer:hover {
         outline: 1px solid var(--border-color);
       }
@@ -52,12 +55,12 @@
         <tbody>
           <tr>
             <td>Number</td>
-            <td
-                class="checkboxContainer"
-                on-tap="_handleTargetTap">
+            <td class="checkboxContainer"
+                on-tap="_handleCheckboxContainerTap">
               <input
                   type="checkbox"
                   name="number"
+                  on-tap="_handleNumberCheckboxTap"
                   checked$="[[showNumber]]">
             </td>
           </tr>
@@ -65,10 +68,11 @@
             <tr>
               <td>[[item]]</td>
               <td class="checkboxContainer"
-                  on-tap="_handleTargetTap">
+                  on-tap="_handleCheckboxContainerTap">
                 <input
                     type="checkbox"
                     name="[[item]]"
+                    on-tap="_handleTargetTap"
                     checked$="[[!isColumnHidden(item, displayedColumns)]]">
               </td>
             </tr>
diff --git a/polygerrit-ui/app/elements/settings/gr-change-table-editor/gr-change-table-editor.js b/polygerrit-ui/app/elements/settings/gr-change-table-editor/gr-change-table-editor.js
index 7b74096..7d109633 100644
--- a/polygerrit-ui/app/elements/settings/gr-change-table-editor/gr-change-table-editor.js
+++ b/polygerrit-ui/app/elements/settings/gr-change-table-editor/gr-change-table-editor.js
@@ -35,40 +35,42 @@
       Gerrit.ChangeTableBehavior,
     ],
 
-    _getButtonText(isShown) {
-      return isShown ? 'Hide' : 'Show';
-    },
-
-    _updateDisplayedColumns(displayedColumns, name, checked) {
-      if (!checked) {
-        return displayedColumns.filter(column => {
-          return name.toLowerCase() !== column.toLowerCase();
-        });
-      } else {
-        return displayedColumns.concat([name]);
-      }
+    /**
+     * Get the list of enabled column names from whichever checkboxes are
+     * checked (excluding the number checkbox).
+     * @return {!Array<string>}
+     */
+    _getDisplayedColumns() {
+      return Polymer.dom(this.root)
+          .querySelectorAll('.checkboxContainer input:not([name=number])')
+          .filter(checkbox => checkbox.checked)
+          .map(checkbox => checkbox.name);
     },
 
     /**
-     * Handles tap on either the checkbox itself or the surrounding table cell.
+     * Handle a tap on a checkbox container and relay the tap to the checkbox it
+     * contains.
+     */
+    _handleCheckboxContainerTap(e) {
+      const checkbox = Polymer.dom(e.target).querySelector('input');
+      if (!checkbox) { return; }
+      checkbox.click();
+    },
+
+    /**
+     * Handle a tap on the number checkbox and update the showNumber property
+     * accordingly.
+     */
+    _handleNumberCheckboxTap(e) {
+      this.showNumber = Polymer.dom(e).rootTarget.checked;
+    },
+
+    /**
+     * Handle a tap on a displayed column checkboxes (excluding number) and
+     * update the displayedColumns property accordingly.
      */
     _handleTargetTap(e) {
-      let checkbox = Polymer.dom(e.target).querySelector('input');
-      if (checkbox) {
-        checkbox.click();
-      } else {
-        // The target is the checkbox itself.
-        checkbox = Polymer.dom(e).rootTarget;
-      }
-
-      if (checkbox.name === 'number') {
-        this.showNumber = checkbox.checked;
-        return;
-      }
-
-      this.set('displayedColumns',
-          this._updateDisplayedColumns(
-              this.displayedColumns, checkbox.name, checkbox.checked));
+      this.set('displayedColumns', this._getDisplayedColumns());
     },
   });
 })();
diff --git a/polygerrit-ui/app/elements/settings/gr-change-table-editor/gr-change-table-editor_test.html b/polygerrit-ui/app/elements/settings/gr-change-table-editor/gr-change-table-editor_test.html
index 587cc3b..32fab9d 100644
--- a/polygerrit-ui/app/elements/settings/gr-change-table-editor/gr-change-table-editor_test.html
+++ b/polygerrit-ui/app/elements/settings/gr-change-table-editor/gr-change-table-editor_test.html
@@ -53,6 +53,7 @@
       ];
 
       element.set('displayedColumns', columns);
+      element.showNumber = false;
       flushAsynchronousOperations();
     });
 
@@ -108,66 +109,50 @@
           displayedLength + 1);
     });
 
-    test('_handleTargetTap', () => {
-      const checkbox = element.$$('table tr:nth-child(2) input');
-      let originalDisplayedColumns = element.displayedColumns;
-      const td = element.$$('table tr:nth-child(2) .checkboxContainer');
-      const displayedColumnStub =
-          sandbox.stub(element, '_updateDisplayedColumns');
-
-      MockInteractions.tap(checkbox);
-      assert.isTrue(displayedColumnStub.lastCall.calledWithExactly(
-          originalDisplayedColumns,
-          checkbox.name,
-          checkbox.checked));
-
-      originalDisplayedColumns = element.displayedColumns;
-      MockInteractions.tap(td);
-      assert.isTrue(displayedColumnStub.lastCall.calledWithExactly(
-          originalDisplayedColumns,
-          checkbox.name,
-          checkbox.checked));
+    test('_getDisplayedColumns', () => {
+      assert.deepEqual(element._getDisplayedColumns(), columns);
+      MockInteractions.tap(
+          element.$$('.checkboxContainer input[name=Assignee]'));
+      assert.deepEqual(element._getDisplayedColumns(),
+          columns.filter(c => c !== 'Assignee'));
     });
 
-    test('_handleTargetTap on number', () => {
-      element.showNumber = false;
-      const checkbox = element.$$('table tr:nth-child(1) input');
-      const displayedColumnStub =
-          sandbox.stub(element, '_updateDisplayedColumns');
+    test('_handleCheckboxContainerTap relayes taps to checkboxes', () => {
+      sandbox.stub(element, '_handleNumberCheckboxTap');
+      sandbox.stub(element, '_handleTargetTap');
 
-      MockInteractions.tap(checkbox);
-      assert.isFalse(displayedColumnStub.called);
+      MockInteractions.tap(
+          element.$$('table tr:first-of-type .checkboxContainer'));
+      assert.isTrue(element._handleNumberCheckboxTap.calledOnce);
+      assert.isFalse(element._handleTargetTap.called);
+
+      MockInteractions.tap(
+          element.$$('table tr:last-of-type .checkboxContainer'));
+      assert.isTrue(element._handleNumberCheckboxTap.calledOnce);
+      assert.isTrue(element._handleTargetTap.calledOnce);
+    });
+
+    test('_handleNumberCheckboxTap', () => {
+      sandbox.spy(element, '_handleNumberCheckboxTap');
+
+      MockInteractions
+          .tap(element.$$('.checkboxContainer input[name=number]'));
+      assert.isTrue(element._handleNumberCheckboxTap.calledOnce);
       assert.isTrue(element.showNumber);
 
-      MockInteractions.tap(checkbox);
+      MockInteractions
+          .tap(element.$$('.checkboxContainer input[name=number]'));
+      assert.isTrue(element._handleNumberCheckboxTap.calledTwice);
       assert.isFalse(element.showNumber);
     });
 
-    test('_updateDisplayedColumns', () => {
-      let name = 'Subject';
-      let checked = false;
-      assert.deepEqual(element._updateDisplayedColumns(columns, name, checked),
-          [
-            'Status',
-            'Owner',
-            'Assignee',
-            'Repo',
-            'Branch',
-            'Updated',
-          ]);
-      name = 'Size';
-      checked = true;
-      assert.deepEqual(element._updateDisplayedColumns(columns, name, checked),
-          [
-            'Subject',
-            'Status',
-            'Owner',
-            'Assignee',
-            'Repo',
-            'Branch',
-            'Updated',
-            'Size',
-          ]);
+    test('_handleTargetTap', () => {
+      sandbox.spy(element, '_handleTargetTap');
+      assert.include(element.displayedColumns, 'Assignee');
+      MockInteractions
+          .tap(element.$$('.checkboxContainer input[name=Assignee]'));
+      assert.isTrue(element._handleTargetTap.calledOnce);
+      assert.notInclude(element.displayedColumns, 'Assignee');
     });
   });
 </script>
diff --git a/polygerrit-ui/app/elements/shared/gr-labeled-autocomplete/gr-labeled-autocomplete.html b/polygerrit-ui/app/elements/shared/gr-labeled-autocomplete/gr-labeled-autocomplete.html
new file mode 100644
index 0000000..5825301
--- /dev/null
+++ b/polygerrit-ui/app/elements/shared/gr-labeled-autocomplete/gr-labeled-autocomplete.html
@@ -0,0 +1,72 @@
+<!--
+@license
+Copyright (C) 2018 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="../../shared/gr-autocomplete/gr-autocomplete.html">
+<link rel="import" href="../../../styles/shared-styles.html">
+
+<dom-module id="gr-labeled-autocomplete">
+  <template>
+    <style include="shared-styles">
+      :host {
+        display: block;
+        width: 12em;
+      }
+      #container {
+        background: var(--chip-background-color);
+        border-radius: 1em;
+        padding: .5em;
+      }
+      #header {
+        color: var(--deemphasized-text-color);
+        font-family: var(--font-family-bold);
+        font-size: var(--font-size-small);
+      }
+      #body {
+        display: flex;
+      }
+      gr-autocomplete {
+        height: 1.5em;
+        --gr-autocomplete: {
+          border: none;
+        }
+      }
+      #trigger {
+        border-left: 1px solid var(--deemphasized-text-color);
+        color: var(--deemphasized-text-color);
+        cursor: pointer;
+        padding-left: .4em;
+      }
+      #trigger:hover {
+        color: var(--primary-text-color);
+      }
+    </style>
+    <div id="container">
+      <div id="header">[[label]]</div>
+      <div id="body">
+        <gr-autocomplete
+            id="autocomplete"
+            threshold="[[_autocompleteThreshold]]"
+            query="[[query]]"
+            disabled="[[disabled]]"
+            placeholder="[[placeholder]]"
+            borderless></gr-autocomplete>
+        <div id="trigger" on-tap="_handleTriggerTap">▼</div>
+      </div>
+    </div>
+  </template>
+  <script src="gr-labeled-autocomplete.js"></script>
+</dom-module>
diff --git a/polygerrit-ui/app/elements/shared/gr-labeled-autocomplete/gr-labeled-autocomplete.js b/polygerrit-ui/app/elements/shared/gr-labeled-autocomplete/gr-labeled-autocomplete.js
new file mode 100644
index 0000000..cb3d546
--- /dev/null
+++ b/polygerrit-ui/app/elements/shared/gr-labeled-autocomplete/gr-labeled-autocomplete.js
@@ -0,0 +1,73 @@
+/**
+ * @license
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+(function() {
+  'use strict';
+
+  Polymer({
+    is: 'gr-labeled-autocomplete',
+
+    /**
+     * Fired when a value is chosen.
+     *
+     * @event commit
+     */
+
+    properties: {
+
+      /**
+       * Used just like the query property of gr-autocomplete.
+       *
+       * @type {function(string): Promise<?>}
+       */
+      query: {
+        type: Function,
+        value() {
+          return function() {
+            return Promise.resolve([]);
+          };
+        },
+      },
+
+      text: {
+        type: String,
+        value: '',
+        notify: true,
+      },
+      label: String,
+      placeholder: String,
+      disabled: Boolean,
+
+      _autocompleteThreshold: {
+        type: Number,
+        value: 0,
+        readOnly: true,
+      },
+    },
+
+    _handleTriggerTap() {
+      this.$.autocomplete.focus();
+    },
+
+    setText(text) {
+      this.$.autocomplete.setText(text);
+    },
+
+    clear() {
+      this.setText('');
+    },
+  });
+})();
diff --git a/polygerrit-ui/app/elements/shared/gr-labeled-autocomplete/gr-labeled-autocomplete_test.html b/polygerrit-ui/app/elements/shared/gr-labeled-autocomplete/gr-labeled-autocomplete_test.html
new file mode 100644
index 0000000..f7632d3
--- /dev/null
+++ b/polygerrit-ui/app/elements/shared/gr-labeled-autocomplete/gr-labeled-autocomplete_test.html
@@ -0,0 +1,58 @@
+<!DOCTYPE html>
+<!--
+@license
+Copyright (C) 2018 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
+<title>gr-labeled-autocomplete</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="../../../test/common-test-setup.html"/>
+<link rel="import" href="gr-labeled-autocomplete.html">
+
+<script>void(0);</script>
+
+<test-fixture id="basic">
+  <template>
+    <gr-labeled-autocomplete></gr-labeled-autocomplete>
+  </template>
+</test-fixture>
+
+<script>
+  suite('gr-labeled-autocomplete tests', () => {
+    let element;
+    let sandbox;
+
+    setup(() => {
+      sandbox = sinon.sandbox.create();
+      element = fixture('basic');
+    });
+
+    teardown(() => { sandbox.restore(); });
+
+    test('tapping trigger focuses autocomplete', () => {
+      sandbox.stub(element.$.autocomplete, 'focus');
+      element._handleTriggerTap();
+      assert.isTrue(element.$.autocomplete.focus.calledOnce);
+    });
+
+    test('setText', () => {
+      sandbox.stub(element.$.autocomplete, 'setText');
+      element.setText('foo-bar');
+      assert.isTrue(element.$.autocomplete.setText.calledWith('foo-bar'));
+    });
+  });
+</script>
diff --git a/polygerrit-ui/app/elements/shared/gr-repo-branch-picker/gr-repo-branch-picker.html b/polygerrit-ui/app/elements/shared/gr-repo-branch-picker/gr-repo-branch-picker.html
new file mode 100644
index 0000000..d794dd6
--- /dev/null
+++ b/polygerrit-ui/app/elements/shared/gr-repo-branch-picker/gr-repo-branch-picker.html
@@ -0,0 +1,60 @@
+<!--
+@license
+Copyright (C) 2018 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="../../../bower_components/iron-icon/iron-icon.html">
+<link rel="import" href="../../../styles/shared-styles.html">
+<link rel="import" href="../../../behaviors/gr-url-encoding-behavior/gr-url-encoding-behavior.html">
+<link rel="import" href="../../shared/gr-icons/gr-icons.html">
+<link rel="import" href="../../shared/gr-labeled-autocomplete/gr-labeled-autocomplete.html">
+<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
+
+<dom-module id="gr-repo-branch-picker">
+  <template>
+    <style include="shared-styles">
+      :host {
+        display: block;
+      }
+      gr-labeled-autocomplete,
+      iron-icon {
+        display: inline-block;
+      }
+      iron-icon {
+        margin-bottom: 1.2em;
+      }
+    </style>
+    <div>
+      <gr-labeled-autocomplete
+          id="repoInput"
+          label="Repository"
+          placeholder="Select repo"
+          on-commit="_repoCommitted"
+          query="[[_repoQuery]]">
+      </gr-labeled-autocomplete>
+      <iron-icon icon="gr-icons:chevron-right"></iron-icon>
+      <gr-labeled-autocomplete
+          id="branchInput"
+          label="Branch"
+          placeholder="Select branch"
+          disabled="[[_branchDisabled]]"
+          on-commit="_branchCommitted"
+          query="[[_query]]">
+      </gr-labeled-autocomplete>
+    </div>
+    <gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
+  </template>
+  <script src="gr-repo-branch-picker.js"></script>
+</dom-module>
diff --git a/polygerrit-ui/app/elements/shared/gr-repo-branch-picker/gr-repo-branch-picker.js b/polygerrit-ui/app/elements/shared/gr-repo-branch-picker/gr-repo-branch-picker.js
new file mode 100644
index 0000000..e2298c3
--- /dev/null
+++ b/polygerrit-ui/app/elements/shared/gr-repo-branch-picker/gr-repo-branch-picker.js
@@ -0,0 +1,109 @@
+/**
+ * @license
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+(function() {
+  'use strict';
+
+  const SUGGESTIONS_LIMIT = 15;
+  const REF_PREFIX = 'refs/heads/';
+
+  Polymer({
+    is: 'gr-repo-branch-picker',
+
+    properties: {
+      repo: {
+        type: String,
+        notify: true,
+        observer: '_repoChanged',
+      },
+      branch: {
+        type: String,
+        notify: true,
+      },
+      _branchDisabled: Boolean,
+      _query: {
+        type: Function,
+        value() {
+          return this._getRepoBranchesSuggestions.bind(this);
+        },
+      },
+      _repoQuery: {
+        type: Function,
+        value() {
+          return this._getRepoSuggestions.bind(this);
+        },
+      },
+    },
+
+    behaviors: [
+      Gerrit.URLEncodingBehavior,
+    ],
+
+    attached() {
+      if (this.repo) {
+        this.$.repoInput.setText(this.repo);
+      }
+    },
+
+    ready() {
+      this._branchDisabled = !this.repo;
+    },
+
+    _getRepoBranchesSuggestions(input) {
+      if (!this.repo) { return Promise.resolve([]); }
+      if (input.startsWith(REF_PREFIX)) {
+        input = input.substring(REF_PREFIX.length);
+      }
+      return this.$.restAPI.getRepoBranches(input, this.repo, SUGGESTIONS_LIMIT)
+          .then(this._branchResponseToSuggestions.bind(this));
+    },
+
+    _getRepoSuggestions(input) {
+      return this.$.restAPI.getRepos(input, SUGGESTIONS_LIMIT)
+          .then(this._repoResponseToSuggestions.bind(this));
+    },
+
+    _repoResponseToSuggestions(res) {
+      return res.map(repo => ({
+        name: repo.name,
+        value: this.singleDecodeURL(repo.id),
+      }));
+    },
+
+    _branchResponseToSuggestions(res) {
+      return Object.keys(res).map(key => {
+        let branch = res[key].ref;
+        if (branch.startsWith(REF_PREFIX)) {
+          branch = branch.substring(REF_PREFIX.length);
+        }
+        return {name: branch, value: branch};
+      });
+    },
+
+    _repoCommitted(e) {
+      this.repo = e.detail.value;
+    },
+
+    _branchCommitted(e) {
+      this.branch = e.detail.value;
+    },
+
+    _repoChanged() {
+      this.$.branchInput.clear();
+      this._branchDisabled = !this.repo;
+    },
+  });
+})();
diff --git a/polygerrit-ui/app/elements/shared/gr-repo-branch-picker/gr-repo-branch-picker_test.html b/polygerrit-ui/app/elements/shared/gr-repo-branch-picker/gr-repo-branch-picker_test.html
new file mode 100644
index 0000000..989e838
--- /dev/null
+++ b/polygerrit-ui/app/elements/shared/gr-repo-branch-picker/gr-repo-branch-picker_test.html
@@ -0,0 +1,140 @@
+<!DOCTYPE html>
+<!--
+@license
+Copyright (C) 2018 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
+<title>gr-repo-branch-picker</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="../../../test/common-test-setup.html"/>
+<link rel="import" href="gr-repo-branch-picker.html">
+
+<script>void(0);</script>
+
+<test-fixture id="basic">
+  <template>
+    <gr-repo-branch-picker></gr-repo-branch-picker>
+  </template>
+</test-fixture>
+
+<script>
+  suite('gr-repo-branch-picker tests', () => {
+    let element;
+    let sandbox;
+
+    setup(() => {
+      sandbox = sinon.sandbox.create();
+      element = fixture('basic');
+    });
+
+    teardown(() => { sandbox.restore(); });
+
+    suite('_getRepoSuggestions', () => {
+      setup(() => {
+        sandbox.stub(element.$.restAPI, 'getRepos')
+            .returns(Promise.resolve([
+              {
+                id: 'plugins%2Favatars-external',
+                name: 'plugins/avatars-external',
+              }, {
+                id: 'plugins%2Favatars-gravatar',
+                name: 'plugins/avatars-gravatar',
+              }, {
+                id: 'plugins%2Favatars%2Fexternal',
+                name: 'plugins/avatars/external',
+              }, {
+                id: 'plugins%2Favatars%2Fgravatar',
+                name: 'plugins/avatars/gravatar',
+              },
+            ]));
+      });
+
+      test('converts to suggestion objects', () => {
+        const input = 'plugins/avatars';
+        return element._getRepoSuggestions(input).then(suggestions => {
+          assert.isTrue(element.$.restAPI.getRepos.calledWith(input));
+          const unencodedNames = [
+            'plugins/avatars-external',
+            'plugins/avatars-gravatar',
+            'plugins/avatars/external',
+            'plugins/avatars/gravatar',
+          ];
+          assert.deepEqual(suggestions.map(s => s.name), unencodedNames);
+          assert.deepEqual(suggestions.map(s => s.value), unencodedNames);
+        });
+      });
+    });
+
+    suite('_getRepoBranchesSuggestions', () => {
+      setup(() => {
+        sandbox.stub(element.$.restAPI, 'getRepoBranches')
+            .returns(Promise.resolve([
+              {ref: 'refs/heads/stable-2.10'},
+              {ref: 'refs/heads/stable-2.11'},
+              {ref: 'refs/heads/stable-2.12'},
+              {ref: 'refs/heads/stable-2.13'},
+              {ref: 'refs/heads/stable-2.14'},
+              {ref: 'refs/heads/stable-2.15'},
+            ]));
+      });
+
+      test('converts to suggestion objects', () => {
+        const repo = 'gerrit';
+        const branchInput = 'stable-2.1';
+        element.repo = repo;
+        return element._getRepoBranchesSuggestions(branchInput)
+            .then(suggestions => {
+              assert.isTrue(element.$.restAPI.getRepoBranches.calledWith(
+                  branchInput, repo, 15));
+              const refNames = [
+                'stable-2.10',
+                'stable-2.11',
+                'stable-2.12',
+                'stable-2.13',
+                'stable-2.14',
+                'stable-2.15',
+              ];
+              assert.deepEqual(suggestions.map(s => s.name), refNames);
+              assert.deepEqual(suggestions.map(s => s.value), refNames);
+            });
+      });
+
+      test('filters out ref prefix', () => {
+        const repo = 'gerrit';
+        const branchInput = 'refs/heads/stable-2.1';
+        element.repo = repo;
+        return element._getRepoBranchesSuggestions(branchInput)
+            .then(suggestions => {
+              assert.isTrue(element.$.restAPI.getRepoBranches.calledWith(
+                  'stable-2.1', repo, 15));
+            });
+      });
+
+      test('does not query when repo is unset', () => {
+        return element._getRepoBranchesSuggestions('')
+            .then(() => {
+              assert.isFalse(element.$.restAPI.getRepoBranches.called);
+              element.repo = 'gerrit';
+              return element._getRepoBranchesSuggestions('');
+            })
+            .then(() => {
+              assert.isTrue(element.$.restAPI.getRepoBranches.called);
+            });
+      });
+    });
+  });
+</script>
diff --git a/polygerrit-ui/app/styles/gr-change-list-styles.html b/polygerrit-ui/app/styles/gr-change-list-styles.html
index 37e8bdc..51a3fe9 100644
--- a/polygerrit-ui/app/styles/gr-change-list-styles.html
+++ b/polygerrit-ui/app/styles/gr-change-list-styles.html
@@ -207,6 +207,10 @@
         .size {
           max-width: none;
         }
+        .noChanges .cell {
+          display: block;
+          height: auto;
+        }
       }
       @media only screen and (min-width: 1450px) {
         :host {
diff --git a/polygerrit-ui/app/test/index.html b/polygerrit-ui/app/test/index.html
index 3ec55dd..4cba249 100644
--- a/polygerrit-ui/app/test/index.html
+++ b/polygerrit-ui/app/test/index.html
@@ -56,6 +56,7 @@
     'change-list/gr-change-list-item/gr-change-list-item_test.html',
     'change-list/gr-change-list-view/gr-change-list-view_test.html',
     'change-list/gr-change-list/gr-change-list_test.html',
+    'change-list/gr-create-commands-dialog/gr-create-commands-dialog_test.html',
     'change-list/gr-dashboard-view/gr-dashboard-view_test.html',
     'change-list/gr-user-header/gr-user-header_test.html',
     'change/gr-account-entry/gr-account-entry_test.html',
@@ -86,6 +87,7 @@
     'change/gr-reply-dialog/gr-reply-dialog_test.html',
     'change/gr-reviewer-list/gr-reviewer-list_test.html',
     'change/gr-thread-list/gr-thread-list_test.html',
+    'change/gr-upload-help-dialog/gr-upload-help-dialog_test.html',
     'core/gr-account-dropdown/gr-account-dropdown_test.html',
     'core/gr-error-dialog/gr-error-dialog_test.html',
     'core/gr-error-manager/gr-error-manager_test.html',
@@ -167,12 +169,14 @@
     'shared/gr-js-api-interface/gr-plugin-endpoints_test.html',
     'shared/gr-js-api-interface/gr-plugin-rest-api_test.html',
     'shared/gr-fixed-panel/gr-fixed-panel_test.html',
+    'shared/gr-labeled-autocomplete/gr-labeled-autocomplete_test.html',
     'shared/gr-lib-loader/gr-lib-loader_test.html',
     'shared/gr-limited-text/gr-limited-text_test.html',
     'shared/gr-linked-chip/gr-linked-chip_test.html',
     'shared/gr-linked-text/gr-linked-text_test.html',
     'shared/gr-list-view/gr-list-view_test.html',
     'shared/gr-page-nav/gr-page-nav_test.html',
+    'shared/gr-repo-branch-picker/gr-repo-branch-picker_test.html',
     'shared/gr-rest-api-interface/gr-auth_test.html',
     'shared/gr-rest-api-interface/gr-rest-api-interface_test.html',
     'shared/gr-rest-api-interface/gr-reviewer-updates-parser_test.html',