Merge "Show user status in autocomplete suggestions and account-label"
diff --git a/WORKSPACE b/WORKSPACE
index 6c7663e..5775df8 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -585,10 +585,10 @@
 
 maven_jar(
     name = "blame_cache",
-    artifact = "com/google/gitiles:blame-cache:0.1-9",
+    artifact = "com/google/gitiles:blame-cache:0.2",
     attach_source = False,
     repository = GERRIT,
-    sha1 = "51d35e6f8bbc2412265066cea9653dd758c95826",
+    sha1 = "519fc548df920123bce986056d2f644663665ae4",
 )
 
 # Keep this version of Soy synchronized with the version used in Gitiles.
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/GroupDetail.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/GroupDetail.java
index c890812..2ba0937 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/GroupDetail.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/GroupDetail.java
@@ -21,20 +21,13 @@
 import java.util.List;
 
 public class GroupDetail {
-  public AccountInfoCache accounts;
   public AccountGroup group;
   public List<AccountGroupMember> members;
   public List<AccountGroupById> includes;
-  public GroupReference ownerGroup;
-  public boolean canModify;
 
   public GroupDetail() {
   }
 
-  public void setAccounts(AccountInfoCache c) {
-    accounts = c;
-  }
-
   public void setGroup(AccountGroup g) {
     group = g;
   }
@@ -46,12 +39,4 @@
   public void setIncludes(List<AccountGroupById> i) {
     includes = i;
   }
-
-  public void setOwnerGroup(GroupReference g) {
-    ownerGroup = g;
-  }
-
-  public void setCanModify(final boolean canModify) {
-    this.canModify = canModify;
-  }
 }
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/ProxyUtil.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/ProxyUtil.java
index c2a52ec..7a12d38 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/ProxyUtil.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/ProxyUtil.java
@@ -37,6 +37,8 @@
 
 package com.google.gerrit.pgm.util;
 
+import com.google.common.base.Strings;
+
 import org.eclipse.jgit.util.CachedAuthenticator;
 
 import java.net.MalformedURLException;
@@ -57,7 +59,7 @@
    */
   static void configureHttpProxy() throws MalformedURLException {
     final String s = System.getenv("http_proxy");
-    if (s == null || s.equals("")) {
+    if (Strings.isNullOrEmpty(s)) {
       return;
     }
 
diff --git a/gerrit-plugin-api/BUILD b/gerrit-plugin-api/BUILD
index a9e303c..ebc5619 100644
--- a/gerrit-plugin-api/BUILD
+++ b/gerrit-plugin-api/BUILD
@@ -20,6 +20,7 @@
     "//gerrit-gwtexpui:server",
     "//gerrit-reviewdb:server",
     "//lib/commons:lang",
+    "//lib/commons:lang3",
     "//lib/dropwizard:dropwizard-core",
     "//lib/guice:guice",
     "//lib/guice:guice-assistedinject",
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupDetailFactory.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupDetailFactory.java
index 94feb7d..5f1840f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupDetailFactory.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupDetailFactory.java
@@ -14,23 +14,17 @@
 
 package com.google.gerrit.server.account;
 
-import com.google.gerrit.common.data.GroupDescription;
 import com.google.gerrit.common.data.GroupDetail;
-import com.google.gerrit.common.data.GroupReference;
 import com.google.gerrit.common.errors.NoSuchGroupException;
-import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.reviewdb.client.AccountGroupById;
 import com.google.gerrit.reviewdb.client.AccountGroupMember;
 import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.group.GroupInfoCache;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.assistedinject.Assisted;
 
 import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
 import java.util.List;
 import java.util.concurrent.Callable;
 
@@ -42,9 +36,6 @@
   private final ReviewDb db;
   private final GroupControl.Factory groupControl;
   private final GroupCache groupCache;
-  private final GroupBackend groupBackend;
-  private final AccountInfoCacheFactory aic;
-  private final GroupInfoCache gic;
 
   private final AccountGroup.Id groupId;
   private GroupControl control;
@@ -53,16 +44,10 @@
   GroupDetailFactory(ReviewDb db,
       GroupControl.Factory groupControl,
       GroupCache groupCache,
-      GroupBackend groupBackend,
-      AccountInfoCacheFactory.Factory accountInfoCacheFactory,
-      GroupInfoCache.Factory groupInfoCacheFactory,
       @Assisted AccountGroup.Id groupId) {
     this.db = db;
     this.groupControl = groupControl;
     this.groupCache = groupCache;
-    this.groupBackend = groupBackend;
-    this.aic = accountInfoCacheFactory.create();
-    this.gic = groupInfoCacheFactory.create();
 
     this.groupId = groupId;
   }
@@ -73,14 +58,8 @@
     AccountGroup group = groupCache.get(groupId);
     GroupDetail detail = new GroupDetail();
     detail.setGroup(group);
-    GroupDescription.Basic ownerGroup = groupBackend.get(group.getOwnerGroupUUID());
-    if (ownerGroup != null) {
-      detail.setOwnerGroup(GroupReference.forGroup(ownerGroup));
-    }
     detail.setMembers(loadMembers());
     detail.setIncludes(loadIncludes());
-    detail.setAccounts(aic.create());
-    detail.setCanModify(control.isOwner());
     return detail;
   }
 
@@ -88,33 +67,9 @@
     List<AccountGroupMember> members = new ArrayList<>();
     for (AccountGroupMember m : db.accountGroupMembers().byGroup(groupId)) {
       if (control.canSeeMember(m.getAccountId())) {
-        aic.want(m.getAccountId());
         members.add(m);
       }
     }
-
-    Collections.sort(members, new Comparator<AccountGroupMember>() {
-      @Override
-      public int compare(AccountGroupMember o1, AccountGroupMember o2) {
-        Account a = aic.get(o1.getAccountId());
-        Account b = aic.get(o2.getAccountId());
-        return n(a).compareTo(n(b));
-      }
-
-      private String n(final Account a) {
-        String n = a.getFullName();
-        if (n != null && n.length() > 0) {
-          return n;
-        }
-
-        n = a.getPreferredEmail();
-        if (n != null && n.length() > 0) {
-          return n;
-        }
-
-        return a.getId().toString();
-      }
-    });
     return members;
   }
 
@@ -123,32 +78,10 @@
 
     for (AccountGroupById m : db.accountGroupById().byGroup(groupId)) {
       if (control.canSeeGroup()) {
-        gic.want(m.getIncludeUUID());
         groups.add(m);
       }
     }
 
-    Collections.sort(groups, new Comparator<AccountGroupById>() {
-      @Override
-      public int compare(AccountGroupById o1, AccountGroupById o2) {
-        GroupDescription.Basic a = gic.get(o1.getIncludeUUID());
-        GroupDescription.Basic b = gic.get(o2.getIncludeUUID());
-        return n(a).compareTo(n(b));
-      }
-
-      private String n (GroupDescription.Basic a) {
-        if (a == null) {
-          return "";
-        }
-
-        String n = a.getName();
-        if (n != null && n.length() > 0) {
-          return n;
-        }
-        return a.getGroupUUID().get();
-      }
-    });
-
     return groups;
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java
index 4922058..5c8a251 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java
@@ -131,7 +131,6 @@
 import com.google.gerrit.server.git.validators.RefOperationValidators;
 import com.google.gerrit.server.git.validators.UploadValidationListener;
 import com.google.gerrit.server.git.validators.UploadValidators;
-import com.google.gerrit.server.group.GroupInfoCache;
 import com.google.gerrit.server.group.GroupModule;
 import com.google.gerrit.server.index.change.ReindexAfterUpdate;
 import com.google.gerrit.server.mail.EmailModule;
@@ -253,7 +252,6 @@
     factory(ChangeJson.Factory.class);
     factory(CreateChangeSender.Factory.class);
     factory(GroupDetailFactory.Factory.class);
-    factory(GroupInfoCache.Factory.class);
     factory(GroupMembers.Factory.class);
     factory(EmailMerge.Factory.class);
     factory(MergedSender.Factory.class);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/group/GroupInfoCache.java b/gerrit-server/src/main/java/com/google/gerrit/server/group/GroupInfoCache.java
deleted file mode 100644
index d660db0..0000000
--- a/gerrit-server/src/main/java/com/google/gerrit/server/group/GroupInfoCache.java
+++ /dev/null
@@ -1,55 +0,0 @@
-// Copyright (C) 2011 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.
-
-package com.google.gerrit.server.group;
-
-import com.google.gerrit.common.data.GroupDescription;
-import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.server.account.GroupBackend;
-import com.google.inject.Inject;
-
-import java.util.HashMap;
-import java.util.Map;
-
-/** Efficiently builds a {@link GroupInfoCache}. */
-public class GroupInfoCache {
-  public interface Factory {
-    GroupInfoCache create();
-  }
-
-  private final GroupBackend groupBackend;
-  private final Map<AccountGroup.UUID, GroupDescription.Basic> out;
-
-  @Inject
-  GroupInfoCache(GroupBackend groupBackend) {
-    this.groupBackend = groupBackend;
-    this.out = new HashMap<>();
-  }
-
-  /**
-   * Indicate a group will be needed later on.
-   *
-   * @param uuid identity that will be needed in the future; may be null.
-   */
-  public void want(final AccountGroup.UUID uuid) {
-    if (uuid != null && !out.containsKey(uuid)) {
-      out.put(uuid, groupBackend.get(uuid));
-    }
-  }
-
-  public GroupDescription.Basic get(final AccountGroup.UUID uuid) {
-    want(uuid);
-    return out.get(uuid);
-  }
-}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/receive/MailMessage.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/receive/MailMessage.java
index 04ca6c8..27d3052 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/receive/MailMessage.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/receive/MailMessage.java
@@ -38,7 +38,6 @@
   // Envelop Information
   public abstract Address from();
   public abstract ImmutableList<Address> to();
-  @Nullable
   public abstract ImmutableList<Address> cc();
   // Metadata
   public abstract DateTime dateReceived();
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 14a7260..08e2e21 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
@@ -62,7 +62,9 @@
                File comment:
              </span>
           </a>
-          <gr-formatted-text class="message"
+          <gr-formatted-text
+              class="message"
+              no-trailing-margin
               content="[[comment.message]]"
               config="[[projectConfig.commentlinks]]"></gr-formatted-text>
         </div>
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 bb9cf15..1ab8a77 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
@@ -189,15 +189,15 @@
       <div>Files</div>
       <div class="rightControls">
         <template is="dom-if"
-            if="[[_fileListActionsVisible(_numFilesShown, _maxFilesForBulkActions)]]">
+            if="[[_fileListActionsVisible(_shownFiles.*, _maxFilesForBulkActions)]]">
           <gr-button link on-tap="_expandAllDiffs">Show diffs</gr-button>
           <span class="separator">/</span>
           <gr-button link on-tap="_collapseAllDiffs">Hide diffs</gr-button>
         </template>
         <template is="dom-if"
-            if="[[!_fileListActionsVisible(_numFilesShown, _maxFilesForBulkActions)]]">
+            if="[[!_fileListActionsVisible(_shownFiles.*, _maxFilesForBulkActions)]]">
           <div class="warning">
-            Bulk file list actions disabled for large amounts of files
+            Bulk actions disabled because there are too many files.
           </div>
         </template>
         <span class="separator">/</span>
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 ec463b6..ca1ffe9 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
@@ -102,7 +102,7 @@
       _maxFilesForBulkActions: {
         type: Number,
         readOnly: true,
-        value: 225,
+        value: 50,
       },
     },
 
@@ -649,8 +649,9 @@
       return 'SIDE_BY_SIDE';
     },
 
-    _fileListActionsVisible: function(numFilesShown, maxFilesForBulkActions) {
-      return numFilesShown <= maxFilesForBulkActions;
+    _fileListActionsVisible: function(shownFilesRecord,
+        maxFilesForBulkActions) {
+      return shownFilesRecord.base.length <= maxFilesForBulkActions;
     },
 
     _computePatchSetDescription: function(revisions, patchNum) {
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 28a784d..4c8b4f1 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
@@ -616,15 +616,14 @@
     });
 
     test('show/hide diffs disabled for large amounts of files', function(done) {
+      var computeSpy = sandbox.spy(element, '_fileListActionsVisible');
       element._files = [];
       element.changeNum = '42';
       element.patchRange = {
         basePatchNum: 'PARENT',
         patchNum: '2',
       };
-      var computeSpy = sandbox.spy(element, '_fileListActionsVisible');
       element.$.fileCursor.setCursorAtIndex(0);
-      element._numFilesShown = 1;
       flush(function() {
         assert.isTrue(computeSpy.lastCall.returnValue);
         var arr = [];
diff --git a/polygerrit-ui/app/elements/settings/gr-http-password/gr-http-password.html b/polygerrit-ui/app/elements/settings/gr-http-password/gr-http-password.html
index e58f1f2..d96237e 100644
--- a/polygerrit-ui/app/elements/settings/gr-http-password/gr-http-password.html
+++ b/polygerrit-ui/app/elements/settings/gr-http-password/gr-http-password.html
@@ -15,7 +15,9 @@
 -->
 
 <link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="../../../styles/gr-settings-styles.html">
 <link rel="import" href="../../shared/gr-button/gr-button.html">
+<link rel="import" href="../../shared/gr-overlay/gr-overlay.html">
 <link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
 
 <dom-module id="gr-http-password">
@@ -24,9 +26,24 @@
       .password {
         font-family: var(--monospace-font-family);
       }
-      .noPassword {
-        color: #777;
+      #generatedPasswordOverlay {
+        padding: 2em;
+        width: 50em;
+      }
+      #generatedPasswordDisplay {
+        margin: 1em 0;
+      }
+      #generatedPasswordDisplay .value {
+        font-family: var(--monospace-font-family);
+      }
+      #passwordWarning {
         font-style: italic;
+        text-align: center;
+      }
+      .closeButton {
+        bottom: 2em;
+        position: absolute;
+        right: 2em;
       }
     </style>
     <style include="gr-settings-styles"></style>
@@ -35,28 +52,28 @@
         <span class="title">Username</span>
         <span class="value">[[_username]]</span>
       </section>
-      <section>
-        <span class="title">Password</span>
-        <span hidden$="[[!_hasPassword]]">
-          <span class="value" hidden$="[[_passwordVisible]]">
-            <gr-button
-                link
-                on-tap="_handleViewPasswordTap">Click to view</gr-button>
-          </span>
-          <span
-              class="value password"
-              hidden$="[[!_passwordVisible]]">[[_password]]</span>
-        </span>
-        <span class="value noPassword" hidden$="[[_hasPassword]]">(None)</span>
-      </section>
       <gr-button
           id="generateButton"
           on-tap="_handleGenerateTap">Generate New Password</gr-button>
-      <gr-button
-          id="clearButton"
-          on-tap="_handleClearTap"
-          disabled="[[!_hasPassword]]">Clear Password</gr-button>
     </div>
+    <gr-overlay
+        id="generatedPasswordOverlay"
+        on-iron-overlay-closed="_generatedPasswordOverlayClosed"
+        with-backdrop>
+      <div class="gr-settings-styles">
+        <section id="generatedPasswordDisplay">
+          <span class="title">New Password:</span>
+          <span class="value">[[_generatedPassword]]</span>
+        </section>
+        <section id="passwordWarning">
+          This password will not be displayed again.<br>
+          If you lose it, you will need to generate a new one.
+        </section>
+        <gr-button
+            class="closeButton"
+            on-tap="_closeOverlay">Close</gr-button>
+      </div>
+    </gr-overlay>
     <gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
   </template>
   <script src="gr-http-password.js"></script>
diff --git a/polygerrit-ui/app/elements/settings/gr-http-password/gr-http-password.js b/polygerrit-ui/app/elements/settings/gr-http-password/gr-http-password.js
index 9248632..bde36aa 100644
--- a/polygerrit-ui/app/elements/settings/gr-http-password/gr-http-password.js
+++ b/polygerrit-ui/app/elements/settings/gr-http-password/gr-http-password.js
@@ -17,65 +17,31 @@
   Polymer({
     is: 'gr-http-password',
 
-    /**
-     * Fired when getting the password fails with non-404.
-     *
-     * @event network-error
-     */
-
     properties: {
-      _serverConfig: Object,
       _username: String,
-      _password: String,
-      _passwordVisible: {
-        type: Boolean,
-        value: false,
-      },
-      _hasPassword: Boolean,
+      _generatedPassword: String,
     },
 
     loadData: function() {
-      var promises = [];
-
-      promises.push(this.$.restAPI.getAccount().then(function(account) {
+      return this.$.restAPI.getAccount().then(function(account) {
         this._username = account.username;
-      }.bind(this)));
-
-      promises.push(this.$.restAPI
-          .getAccountHttpPassword(this._handleGetPasswordError.bind(this))
-          .then(function(pass) {
-            this._password = pass;
-            this._hasPassword = !!pass;
-          }.bind(this)));
-
-      return Promise.all(promises);
-    },
-
-    _handleGetPasswordError: function(response) {
-      if (response.status === 404) {
-        this._hasPassword = false;
-      } else {
-        this.fire('network-error', {response: response});
-      }
-    },
-
-    _handleViewPasswordTap: function() {
-      this._passwordVisible = true;
+      }.bind(this));
     },
 
     _handleGenerateTap: function() {
+      this._generatedPassword = 'Generating...';
+      this.$.generatedPasswordOverlay.open();
       this.$.restAPI.generateAccountHttpPassword().then(function(newPassword) {
-        this._hasPassword = true;
-        this._passwordVisible = true;
-        this._password = newPassword;
+        this._generatedPassword = newPassword;
       }.bind(this));
     },
 
-    _handleClearTap: function() {
-      this.$.restAPI.deleteAccountHttpPassword().then(function() {
-        this._password = '';
-        this._hasPassword = false;
-      }.bind(this));
+    _closeOverlay: function() {
+      this.$.generatedPasswordOverlay.close();
+    },
+
+    _generatedPasswordOverlayClosed: function() {
+      this._generatedPassword = null;
     },
   });
 })();
diff --git a/polygerrit-ui/app/elements/settings/gr-http-password/gr-http-password_test.html b/polygerrit-ui/app/elements/settings/gr-http-password/gr-http-password_test.html
index 36c9abf..e675de2 100644
--- a/polygerrit-ui/app/elements/settings/gr-http-password/gr-http-password_test.html
+++ b/polygerrit-ui/app/elements/settings/gr-http-password/gr-http-password_test.html
@@ -31,7 +31,7 @@
 </test-fixture>
 
 <script>
-  suite('gr-http-password tests (already has password)', function() {
+  suite('gr-http-password tests', function() {
     var element;
     var account;
     var password;
@@ -51,106 +51,30 @@
       element.loadData().then(function() { flush(done); });
     });
 
-    test('loads data', function() {
-      assert.equal(element._username, 'user name');
-      assert.equal(element._password, 'the password');
-      assert.isFalse(element._passwordVisible);
-      assert.isTrue(element._hasPassword);
-    });
-
-    test('view password', function() {
-      var button = element.$$('.value gr-button');
-      assert.isFalse(element._passwordVisible);
-      MockInteractions.tap(button);
-      assert.isTrue(element._passwordVisible);
-    });
-
     test('generate password', function() {
       var button = element.$.generateButton;
       var nextPassword = 'the new password';
+      var generateResolve;
       var generateStub = sinon.stub(element.$.restAPI,
           'generateAccountHttpPassword', function() {
-            return Promise.resolve(nextPassword);
+            return new Promise(function(resolve) {
+              generateResolve = resolve;
+            });
           });
 
-      assert.isTrue(element._hasPassword);
-      assert.isFalse(element._passwordVisible);
-      assert.equal(element._password, 'the password');
+      assert.isNotOk(element._generatedPassword);
 
       MockInteractions.tap(button);
 
       assert.isTrue(generateStub.called);
+      assert.equal(element._generatedPassword, 'Generating...');
+
+      generateResolve(nextPassword);
+
       generateStub.lastCall.returnValue.then(function() {
-        assert.isTrue(element._passwordVisible);
-        assert.isTrue(element._hasPassword);
-        assert.equal(element._password, 'the new password');
-      });
-    });
-
-    test('clear password', function() {
-      var button = element.$.clearButton;
-      var clearStub = sinon.stub(element.$.restAPI, 'deleteAccountHttpPassword',
-          function() { return Promise.resolve(); });
-
-      assert.isTrue(element._hasPassword);
-      assert.equal(element._password, 'the password');
-
-      MockInteractions.tap(button);
-
-      assert.isTrue(clearStub.called);
-      clearStub.lastCall.returnValue.then(function() {
-        assert.isFalse(element._hasPassword);
-        assert.equal(element._password, '');
+        assert.equal(element._generatedPassword, nextPassword);
       });
     });
   });
 
-  suite('gr-http-password tests (has no password)', function() {
-    var element;
-    var account;
-
-    setup(function(done) {
-      account = {username: 'user name'};
-
-      stub('gr-rest-api-interface', {
-        getAccount: function() { return Promise.resolve(account); },
-        getAccountHttpPassword: function(errFn) {
-          errFn({status: 404});
-          return Promise.resolve('');
-        },
-      });
-
-      element = fixture('basic');
-      element.loadData().then(function() { flush(done); });
-    });
-
-    test('loads data', function() {
-      assert.equal(element._username, 'user name');
-      assert.isNotOk(element._password);
-      assert.isFalse(element._passwordVisible);
-      assert.isFalse(element._hasPassword);
-    });
-
-    test('generate password', function() {
-      var button = element.$.generateButton;
-      var nextPassword = 'the new password';
-      var generateStub = sinon.stub(element.$.restAPI,
-          'generateAccountHttpPassword', function() {
-            return Promise.resolve(nextPassword);
-          });
-
-      assert.isFalse(element._hasPassword);
-      assert.isFalse(element._passwordVisible);
-      assert.isNotOk(element._password);
-
-      MockInteractions.tap(button);
-
-      assert.isTrue(generateStub.called);
-      generateStub.lastCall.returnValue.then(function() {
-        assert.isTrue(element._passwordVisible);
-        assert.isOk(element._hasPassword);
-        assert.equal(element._password, 'the new password');
-      });
-    });
-  });
 </script>
diff --git a/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text.html b/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text.html
index 11560f1..8855b0a 100644
--- a/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text.html
+++ b/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text.html
@@ -29,6 +29,12 @@
       gr-linked-text.pre {
         margin: 0 0 1.4em 0;
       }
+      :host.noTrailingMargin p:last-child,
+      :host.noTrailingMargin ul:last-child,
+      :host.noTrailingMargin blockquote:last-child,
+      :host.noTrailingMargin gr-linked-text.pre:last-child {
+        margin: 0;
+      }
       blockquote {
         border-left: 1px solid #aaa;
         padding: 0 .7em;
diff --git a/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text.js b/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text.js
index 1b129a7..c17e9e4 100644
--- a/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text.js
+++ b/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text.js
@@ -22,12 +22,22 @@
     properties: {
       content: String,
       config: Object,
+      noTrailingMargin: {
+        type: Boolean,
+        value: false,
+      },
     },
 
     observers: [
       '_contentOrConfigChanged(content, config)',
     ],
 
+    ready: function() {
+      if (this.noTrailingMargin) {
+        this.classList.add('noTrailingMargin');
+      }
+    },
+
     /**
      * Get the plain text as it appears in the generated DOM.
      *
diff --git a/tools/bzl/plugin.bzl b/tools/bzl/plugin.bzl
index 6c6470d..6e5faf0 100644
--- a/tools/bzl/plugin.bzl
+++ b/tools/bzl/plugin.bzl
@@ -22,6 +22,7 @@
     gwt_module = [],
     resources = [],
     manifest_entries = [],
+    target_suffix = "",
     **kwargs):
   native.java_library(
     name = name + '__plugin',
@@ -78,7 +79,7 @@
   # TODO(davido): Remove manual merge of manifest file when this feature
   # request is implemented: https://github.com/bazelbuild/bazel/issues/2009
   genrule2(
-    name = name,
+    name = name + target_suffix,
     stamp = 1,
     srcs = ['%s__non_stamped_deploy.jar' % name],
     cmd = " && ".join([
@@ -87,6 +88,6 @@
       "unzip -q $$ROOT/$<",
       "echo \"Implementation-Version: $$GEN_VERSION\n$$(cat META-INF/MANIFEST.MF)\" > META-INF/MANIFEST.MF",
       "zip -qr $$ROOT/$@ ."]),
-    outs = ['%s.jar' % name],
+    outs = ['%s%s.jar' % (name, target_suffix)],
     visibility = ['//visibility:public'],
   )