Merge "Color statuses in change list items"
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item.html b/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item.html
index 33c2601..45109cd 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item.html
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item.html
@@ -93,6 +93,9 @@
       .positionIndicator {
         visibility: hidden;
       }
+      .size {
+        text-align: center;
+      }
       :host([selected]) .positionIndicator {
         visibility: visible;
       }
@@ -201,10 +204,10 @@
           has-tooltip
           date-str="[[change.updated]]"></gr-date-formatter>
     </td>
-    <td class="cell size u-monospace"
+    <td class="cell size"
+        title$="[[_computeSizeTooltip(change)]]"
         hidden$="[[isColumnHidden('Size', visibleChangeTableColumns)]]">
-      <span class="u-green"><span>+</span>[[change.insertions]]</span>,
-      <span class="u-red"><span>-</span>[[change.deletions]]</span>
+      <span>[[_computeChangeSize(change)]]</span>
     </td>
     <template is="dom-repeat" items="[[labelNames]]" as="labelName">
       <td title$="[[_computeLabelTitle(change, labelName)]]"
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item.js b/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item.js
index d594297..5d7121a 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item.js
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item.js
@@ -14,6 +14,13 @@
 (function() {
   'use strict';
 
+  const CHANGE_SIZE = {
+    XS: 10,
+    SMALL: 50,
+    MEDIUM: 250,
+    LARGE: 1000,
+  };
+
   Polymer({
     is: 'gr-change-list-item',
 
@@ -129,5 +136,36 @@
     _computeAccountStatusString(account) {
       return account && account.status ? `(${account.status})` : '';
     },
+
+    _computeSizeTooltip(change) {
+      if (change.insertions + change.deletions === 0 ||
+          isNaN(change.insertions + change.deletions)) {
+        return 'Size unknown';
+      } else {
+        return `+${change.insertions}, -${change.deletions}`;
+      }
+    },
+
+    /**
+     * TShirt sizing is based on the following paper:
+     * http://dirkriehle.com/wp-content/uploads/2008/09/hicss-42-csdistr-final-web.pdf
+     */
+    _computeChangeSize(change) {
+      const delta = change.insertions + change.deletions;
+      if (isNaN(delta) || delta === 0) {
+        return '🤷'; // Unknown
+      }
+      if (delta < CHANGE_SIZE.XS) {
+        return 'XS';
+      } else if (delta < CHANGE_SIZE.SMALL) {
+        return 'S';
+      } else if (delta < CHANGE_SIZE.MEDIUM) {
+        return 'M';
+      } else if (delta < CHANGE_SIZE.LARGE) {
+        return 'L';
+      } else {
+        return 'XL';
+      }
+    },
   });
 })();
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item_test.html b/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item_test.html
index 5e44a45..4b001c3 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item_test.html
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item_test.html
@@ -206,5 +206,47 @@
       assert.equal(element._computeAccountStatusString({status: 'Working'}),
           '(Working)');
     });
+
+    test('TShirt sizing tooltip', () => {
+      assert.equal(element._computeSizeTooltip({
+        insertions: 'foo',
+        deletions: 'bar',
+      }), 'Size unknown');
+      assert.equal(element._computeSizeTooltip({
+        insertions: 0,
+        deletions: 0,
+      }), 'Size unknown');
+      assert.equal(element._computeSizeTooltip({
+        insertions: 1,
+        deletions: 2,
+      }), '+1, -2');
+    });
+
+    test('TShirt sizing', () => {
+      assert.equal(element._computeChangeSize({
+        insertions: 'foo',
+        deletions: 'bar',
+      }), '🤷');
+      assert.equal(element._computeChangeSize({
+        insertions: 1,
+        deletions: 1,
+      }), 'XS');
+      assert.equal(element._computeChangeSize({
+        insertions: 9,
+        deletions: 1,
+      }), 'S');
+      assert.equal(element._computeChangeSize({
+        insertions: 10,
+        deletions: 200,
+      }), 'M');
+      assert.equal(element._computeChangeSize({
+        insertions: 99,
+        deletions: 900,
+      }), 'L');
+      assert.equal(element._computeChangeSize({
+        insertions: 99,
+        deletions: 999,
+      }), 'XL');
+    });
   });
 </script>
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 a954559..adab1c8 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
@@ -211,6 +211,10 @@
         type: Boolean,
         value: false,
       },
+      _hideQuickApproveAction: {
+        type: Boolean,
+        value: false,
+      },
       changeNum: String,
       changeStatus: String,
       commitNum: String,
@@ -653,7 +657,18 @@
       return null;
     },
 
+    hideQuickApproveAction() {
+      this._topLevelSecondaryActions =
+        this._topLevelSecondaryActions.filter(sa => {
+          return sa.key !== QUICK_APPROVE_ACTION.key;
+        });
+      this._hideQuickApproveAction = true;
+    },
+
     _getQuickApproveAction() {
+      if (this._hideQuickApproveAction) {
+        return null;
+      }
       const approval = this._getTopMissingApproval();
       if (!approval) {
         return null;
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 8986816..835d560 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
@@ -1091,6 +1091,21 @@
         assert.isNotNull(approveButton);
       });
 
+      test('hide quick approve', () => {
+        const approveButton =
+            element.$$('gr-button[data-action-key=\'review\']');
+        assert.isNotNull(approveButton);
+        assert.isFalse(element._hideQuickApproveAction);
+
+        // Assert approve button gets removed from list of buttons.
+        element.hideQuickApproveAction();
+        flushAsynchronousOperations();
+        const approveButtonUpdated =
+            element.$$('gr-button[data-action-key=\'review\']');
+        assert.isNull(approveButtonUpdated);
+        assert.isTrue(element._hideQuickApproveAction);
+      });
+
       test('is first in list of secondary actions', () => {
         const approveButton = element.$.secondaryActions
             .querySelector('gr-button');
diff --git a/polygerrit-ui/app/elements/change/gr-comment-list/gr-comment-list.html b/polygerrit-ui/app/elements/change/gr-comment-list/gr-comment-list.html
index 6ce6a1e..464e1bb 100644
--- a/polygerrit-ui/app/elements/change/gr-comment-list/gr-comment-list.html
+++ b/polygerrit-ui/app/elements/change/gr-comment-list/gr-comment-list.html
@@ -42,7 +42,7 @@
       }
       .container {
         display: flex;
-        margin: 5px 0;
+        margin: .5em 0;
       }
       .lineNum {
         margin-right: .5em;
@@ -53,6 +53,16 @@
         flex: 1;
         --gr-formatted-text-prose-max-width: 80ch;
       }
+      @media screen and (max-width: 50em) {
+        .container {
+          flex-direction: column;
+          margin: 0 0 .5em .5em;
+        }
+        .lineNum {
+          min-width: initial;
+          text-align: left;
+        }
+      }
     </style>
     <template is="dom-repeat" items="[[_computeFilesFromComments(comments)]]" as="file">
       <div class="file">[[computeDisplayPath(file)]]:</div>
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 4370d7e..ea70de9 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
@@ -95,7 +95,7 @@
               type="radio"
               on-tap="_handleRebaseOnOther">
           <label id="rebaseOnOtherLabel" for="rebaseOnOtherInput">
-            Rebase on a specific change or ref <span hidden$="[[!hasParent]]">
+            Rebase on a specific change, ref, or commit <span hidden$="[[!hasParent]]">
               (breaks relation chain)
             </span>
           </label>
@@ -107,7 +107,8 @@
               text="{{_inputText}}"
               on-tap="_handleEnterChangeNumberTap"
               on-commit="_handleBaseSelected"
-              placeholder="Change number">
+              allow-non-suggested-values
+              placeholder="Change number, ref, or commit hash">
           </gr-autocomplete>
         </div>
       </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 a326f54..0e7a709 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
@@ -14,8 +14,6 @@
 (function() {
   'use strict';
 
-  const ERR_EDIT_LOADED = 'You cannot change the review status of an edit.';
-
   // Maximum length for patch set descriptions.
   const PATCH_DESC_MAX_LENGTH = 500;
   const WARN_SHOW_ALL_THRESHOLD = 1000;
@@ -397,10 +395,7 @@
     },
 
     _reviewFile(path) {
-      if (this.editMode) {
-        this.fire('show-alert', {message: ERR_EDIT_LOADED});
-        return;
-      }
+      if (this.editMode) { return; }
       const index = this._reviewed.indexOf(path);
       const reviewed = index !== -1;
       if (reviewed) {
@@ -896,7 +891,7 @@
           diffElem.comments = this.changeComments.getCommentsBySideForPath(
               path, this.patchRange, this.projectConfig);
           const promises = [diffElem.reload()];
-          if (this._isLoggedIn) {
+          if (this._loggedIn && !this.diffPrefs.manual_review) {
             promises.push(this._reviewFile(path));
           }
           return Promise.all(promises);
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 38ab31b..72a9629 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
@@ -85,7 +85,7 @@
             .returns({meta: {}, left: [], right: []});
         done();
       });
-
+      element.diffPrefs = {};
       element.numFilesShown = 200;
       saveStub = sandbox.stub(element, '_saveReviewedState',
           () => { return Promise.resolve(); });
@@ -927,7 +927,7 @@
     });
 
     test('_renderInOrder logged in', done => {
-      element._isLoggedIn = true;
+      element._loggedIn = true;
       const reviewStub = sandbox.stub(element, '_reviewFile');
       let callCount = 0;
       const diffs = [{
@@ -959,6 +959,24 @@
           });
     });
 
+    test('_renderInOrder respects diffPrefs.manual_review', () => {
+      element._loggedIn = true;
+      element.diffPrefs = {manual_review: true};
+      const reviewStub = sandbox.stub(element, '_reviewFile');
+      const diffs = [{
+        path: 'p',
+        reload() { return Promise.resolve(); },
+      }];
+
+      return element._renderInOrder(['p'], diffs, 1).then(() => {
+        assert.isFalse(reviewStub.called);
+        delete element.diffPrefs.manual_review;
+        return element._renderInOrder(['p'], diffs, 1).then(() => {
+          assert.isTrue(reviewStub.called);
+        });
+      });
+    });
+
     test('_loadingChanged fired from reload in debouncer', done => {
       element.changeNum = 123;
       element.patchRange = {patchNum: 12};
@@ -1102,6 +1120,8 @@
       commentApiWrapper = fixture('basic');
       element = commentApiWrapper.$.fileList;
       loadCommentSpy = sandbox.spy(commentApiWrapper.$.commentAPI, 'loadAll');
+      element.diffPrefs = {};
+      sandbox.stub(element, '_reviewFile');
 
       // Stub methods on the changeComments object after changeComments has
       // been initalized.
@@ -1323,20 +1343,17 @@
 
     suite('editMode behavior', () => {
       test('reviewed checkbox', () => {
-        const alertStub = sandbox.stub();
+        element._reviewFile.restore();
         const saveReviewStub = sandbox.stub(element, '_saveReviewedState');
 
-        element.addEventListener('show-alert', alertStub);
         element.editMode = false;
         MockInteractions.pressAndReleaseKeyOn(element, 82, null, 'r');
-        assert.isFalse(alertStub.called);
         assert.isTrue(saveReviewStub.calledOnce);
 
         element.editMode = true;
         flushAsynchronousOperations();
 
         MockInteractions.pressAndReleaseKeyOn(element, 82, null, 'r');
-        assert.isTrue(alertStub.called);
         assert.isTrue(saveReviewStub.calledOnce);
       });
 
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-change-actions-js-api.js b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-change-actions-js-api.js
index fe74906..7be007f 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-change-actions-js-api.js
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-change-actions-js-api.js
@@ -62,6 +62,11 @@
     });
   };
 
+  GrChangeActionsInterface.prototype.hideQuickApproveAction = function() {
+    ensureEl(this);
+    this._el.hideQuickApproveAction();
+  };
+
   GrChangeActionsInterface.prototype.setActionOverflow = function(type, key,
       overflow) {
     ensureEl(this);
diff --git a/polygerrit-ui/app/styles/gr-change-list-styles.html b/polygerrit-ui/app/styles/gr-change-list-styles.html
index c109381..7ba4fc6 100644
--- a/polygerrit-ui/app/styles/gr-change-list-styles.html
+++ b/polygerrit-ui/app/styles/gr-change-list-styles.html
@@ -171,7 +171,7 @@
         }
         .owner,
         .size {
-          width: auto;
+          max-width: none;
         }
       }
       @media only screen and (min-width: 1450px) {
diff --git a/tools/release-announcement.py b/tools/release-announcement.py
index 83a78fe..f700185 100755
--- a/tools/release-announcement.py
+++ b/tools/release-announcement.py
@@ -142,7 +142,10 @@
     if not os.path.isdir(gpghome):
         print("Skipping signing due to missing gnupg home folder")
     else:
-        gpg = GPG(homedir=gpghome)
+        try:
+            gpg = GPG(homedir=gpghome)
+        except TypeError:
+            gpg = GPG(gnupghome=gpghome)
         signed = gpg.sign(output)
         filename = filename + ".asc"
         with open(filename, "w") as f: