Merge changes I0f3c810b,I39d468dc

* changes:
  bazel: use 'flaky' attribute for flaky tests.
  bazel: add polymer licenses to the documentation.
diff --git a/.bazelrc b/.bazelrc
deleted file mode 100644
index a991c76..0000000
--- a/.bazelrc
+++ /dev/null
@@ -1 +0,0 @@
-build --workspace_status_command=./tools/workspace-status.sh --strategy=Javac=worker
diff --git a/Documentation/access-control.txt b/Documentation/access-control.txt
index 41c9dc2..6575018 100644
--- a/Documentation/access-control.txt
+++ b/Documentation/access-control.txt
@@ -442,6 +442,11 @@
 to projects in Gerrit. It can give permission to abandon a specific
 change to a given ref.
 
+The uploader of a change, anyone granted the <<category_owner,`Owner`>>
+permission at the ref or project level, and anyone granted the
+<<capability_administrateServer,`Administrate Server`>>
+permission can also Abandon changes.
+
 This also grants the permission to restore a change if the user also
 has link:#category_push[push permission] on the change's destination
 ref.
diff --git a/gerrit-acceptance-framework/BUILD b/gerrit-acceptance-framework/BUILD
index d01534a..7f06cd3 100644
--- a/gerrit-acceptance-framework/BUILD
+++ b/gerrit-acceptance-framework/BUILD
@@ -23,6 +23,7 @@
   main_class = 'Dummy',
   runtime_deps = [':lib'],
   visibility = ['//visibility:public'],
+  testonly = 1,
 )
 
 java_library2(
@@ -57,6 +58,7 @@
     '//lib/mail:mail',
   ],
   visibility = ['//visibility:public'],
+  testonly = 1,
 )
 
 load('//tools/bzl:javadoc.bzl', 'java_doc')
@@ -67,4 +69,5 @@
   libs = [':lib'],
   pkgs = ['com.google.gerrit.acceptance'],
   visibility = ['//visibility:public'],
+  testonly = 1,
 )
diff --git a/gerrit-acceptance-tests/BUILD b/gerrit-acceptance-tests/BUILD
index 2ec7a05..71b1b45 100644
--- a/gerrit-acceptance-tests/BUILD
+++ b/gerrit-acceptance-tests/BUILD
@@ -39,4 +39,5 @@
     '//lib/mina:sshd',
   ],
   visibility = ['//visibility:public'],
+  testonly = 1,
 )
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/AbstractPushForReview.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/AbstractPushForReview.java
index 1e2fed5..ff34b5e 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/AbstractPushForReview.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/AbstractPushForReview.java
@@ -47,6 +47,7 @@
 import com.google.gerrit.extensions.common.ChangeMessageInfo;
 import com.google.gerrit.extensions.common.EditInfo;
 import com.google.gerrit.extensions.common.LabelInfo;
+import com.google.gerrit.extensions.common.RevisionInfo;
 import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.PatchSet;
@@ -353,6 +354,45 @@
       assertThat(cm.message).isEqualTo(
           "Uploaded patch set 1.\nmy test message");
     }
+    Collection<RevisionInfo> revisions = ci.revisions.values();
+    assertThat(revisions).hasSize(1);
+    for (RevisionInfo ri : revisions) {
+      assertThat(ri.description).isEqualTo("my test message");
+    }
+  }
+
+  @Test
+  public void pushForMasterWithMessageTwiceWithDifferentMessages()
+      throws Exception {
+    ProjectConfig config = projectCache.checkedGet(project).getConfig();
+    config.getProject()
+        .setCreateNewChangeForAllNotInTarget(InheritableBoolean.TRUE);
+    saveProjectConfig(project, config);
+
+    PushOneCommit push =
+        pushFactory
+            .create(db, admin.getIdent(), testRepo, PushOneCommit.SUBJECT,
+                "a.txt", "content");
+    PushOneCommit.Result r = push.to("refs/for/master/%m=my_test_message");
+    r.assertOkStatus();
+
+    push =
+        pushFactory
+          .create(db, admin.getIdent(), testRepo, PushOneCommit.SUBJECT,
+              "b.txt", "anotherContent", r.getChangeId());
+    r = push.to("refs/for/master/%m=new_test_message");
+    r.assertOkStatus();
+
+    ChangeInfo ci = get(r.getChangeId());
+    Collection<RevisionInfo> revisions = ci.revisions.values();
+    assertThat(revisions).hasSize(2);
+    for (RevisionInfo ri: revisions) {
+      if (ri.isCurrent) {
+        assertThat(ri.description).isEqualTo("new test message");
+      } else {
+        assertThat(ri.description).isEqualTo("my test message");
+      }
+    }
   }
 
   @Test
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/BUILD b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/BUILD
index db0d8e9..3b7d2f2 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/BUILD
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/BUILD
@@ -17,10 +17,12 @@
     '//gerrit-acceptance-tests:lib',
     '//lib/joda:joda-time',
   ],
+  testonly = 1,
 )
 
 java_library(
   name = 'submodule_util',
-  srcs = ['AbstractSubmoduleSubscription.java',],
-  deps = ['//gerrit-acceptance-tests:lib',]
+  srcs = ['AbstractSubmoduleSubscription.java'],
+  deps = ['//gerrit-acceptance-tests:lib'],
+  testonly = 1,
 )
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/BUILD b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/BUILD
index 558d0a9..d0339df 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/BUILD
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/BUILD
@@ -20,4 +20,5 @@
     '//lib:junit',
   ],
   visibility = ['//visibility:public'],
+  testonly = 1,
 )
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/BUILD b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/BUILD
index 6fbf9c5..d4d54b6 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/BUILD
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/BUILD
@@ -29,4 +29,5 @@
   deps = [
     '//gerrit-acceptance-tests:lib',
   ],
+  testonly = 1,
 )
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/notedb/ChangeRebuilderIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/notedb/ChangeRebuilderIT.java
index e1771ce..a2b93cc 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/notedb/ChangeRebuilderIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/notedb/ChangeRebuilderIT.java
@@ -26,7 +26,6 @@
 
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.Iterables;
 import com.google.common.collect.Ordering;
 import com.google.gerrit.acceptance.AbstractDaemonTest;
 import com.google.gerrit.acceptance.AcceptanceTestRequestScope;
@@ -49,6 +48,7 @@
 import com.google.gerrit.reviewdb.client.PatchSet;
 import com.google.gerrit.reviewdb.client.PatchSetApproval;
 import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.reviewdb.client.RefNames;
 import com.google.gerrit.reviewdb.client.RevId;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.reviewdb.server.ReviewDbUtil;
@@ -86,6 +86,7 @@
 
 import org.apache.http.Header;
 import org.apache.http.message.BasicHeader;
+import org.eclipse.jgit.junit.TestRepository;
 import org.eclipse.jgit.lib.Config;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.ObjectInserter;
@@ -419,10 +420,16 @@
     final Change.Id id = r.getPatchSetId().getParentKey();
     assertChangeUpToDate(true, id);
 
-    // Make a ReviewDb change behind NoteDb's back and ensure it's detected.
-    setNotesMigration(false, false);
-    gApi.changes().id(id.get()).topic(name("a-topic"));
-    setInvalidNoteDbState(id);
+    // Update ReviewDb and NoteDb, then revert the corresponding NoteDb change
+    // to simulate it failing.
+    NoteDbChangeState oldState =
+        NoteDbChangeState.parse(getUnwrappedDb().changes().get(id));
+    String topic = name("a-topic");
+    gApi.changes().id(id.get()).topic(topic);
+    try (Repository repo = repoManager.openRepository(project)) {
+      new TestRepository<>(repo)
+          .update(RefNames.changeMetaRef(id), oldState.getChangeMetaId());
+    }
     assertChangeUpToDate(false, id);
 
     // Next NoteDb read comes inside the transaction started by BatchUpdate. In
@@ -430,7 +437,6 @@
     // the change is parsed by ChangesCollection and when the BatchUpdate
     // executes. We simulate it here by using BatchUpdate directly and not going
     // through an API handler.
-    setNotesMigration(true, true);
     final String msg = "message from BatchUpdate";
     try (BatchUpdate bu = batchUpdateFactory.create(db, project,
           identifiedUserFactory.create(user.getId()), TimeUtil.nowTs())) {
@@ -447,24 +453,33 @@
           return true;
         }
       });
-      bu.execute();
+      try {
+        bu.execute();
+        fail("expected update to fail");
+      } catch (UpdateException e) {
+        assertThat(e.getMessage()).contains("cannot copy ChangeNotesState");
+      }
     }
-    // As an implementation detail, change wasn't actually rebuilt inside the
-    // BatchUpdate transaction, but it was rebuilt during read for the
-    // subsequent reindex. Thus it's impossible to actually observe an
-    // out-of-date state in the caller.
-    assertChangeUpToDate(true, id);
 
-    // Check that the bundles are equal.
-    ChangeNotes notes = notesFactory.create(dbProvider.get(), project, id);
-    ChangeBundle actual = ChangeBundle.fromNotes(commentsUtil, notes);
-    ChangeBundle expected = bundleReader.fromReviewDb(getUnwrappedDb(), id);
-    assertThat(actual.differencesFrom(expected)).isEmpty();
-    assertThat(
-            Iterables.transform(
-                notes.getChangeMessages(),
-                ChangeMessage::getMessage))
-        .contains(msg);
+    // TODO(dborowitz): Re-enable these assertions once we fix auto-rebuilding
+    // in the BatchUpdate path.
+    //// As an implementation detail, change wasn't actually rebuilt inside the
+    //// BatchUpdate transaction, but it was rebuilt during read for the
+    //// subsequent reindex. Thus it's impossible to actually observe an
+    //// out-of-date state in the caller.
+    //assertChangeUpToDate(true, id);
+
+    //// Check that the bundles are equal.
+    //ChangeNotes notes = notesFactory.create(dbProvider.get(), project, id);
+    //ChangeBundle actual = ChangeBundle.fromNotes(commentsUtil, notes);
+    //ChangeBundle expected = bundleReader.fromReviewDb(getUnwrappedDb(), id);
+    //assertThat(actual.differencesFrom(expected)).isEmpty();
+    //assertThat(
+    //        Iterables.transform(
+    //            notes.getChangeMessages(),
+    //            ChangeMessage::getMessage))
+    //    .contains(msg);
+    //assertThat(actual.getChange().getTopic()).isEqualTo(topic);
   }
 
   @Test
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/RevisionInfo.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/RevisionInfo.java
index 34a1e63..5242c7e 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/RevisionInfo.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/RevisionInfo.java
@@ -33,4 +33,5 @@
   public Map<String, ActionInfo> actions;
   public String commitWithFooters;
   public PushCertificateInfo pushCertificate;
+  public String description;
 }
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/ResourceServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/ResourceServlet.java
index c35738b..e4d3339 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/ResourceServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/ResourceServlet.java
@@ -87,6 +87,8 @@
         .put("tif", "image/tiff")
         .put("tiff", "image/tiff")
         .put("txt", "text/plain")
+        .put("woff", "font/woff")
+        .put("woff2", "font/woff2")
         .build();
 
   protected static String contentType(String name) {
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/PatchSet.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/PatchSet.java
index 2210319..cf5c5ad 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/PatchSet.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/PatchSet.java
@@ -194,6 +194,16 @@
   @Column(id = 8, notNull = false, length = Integer.MAX_VALUE)
   protected String pushCertificate;
 
+  /**
+   * Optional user-supplied description for this patch set.
+   * <p>
+   * When this field is null, the description was never set on the patch set.
+   * When this field is an empty string, the description was set and later
+   * cleared.
+   */
+  @Column(id = 9, notNull = false, length = Integer.MAX_VALUE)
+  protected String description;
+
   protected PatchSet() {
   }
 
@@ -209,6 +219,7 @@
     this.draft = src.draft;
     this.groups = src.groups;
     this.pushCertificate = src.pushCertificate;
+    this.description = src.description;
   }
 
   public PatchSet.Id getId() {
@@ -277,6 +288,14 @@
     pushCertificate = cert;
   }
 
+  public String getDescription() {
+    return description;
+  }
+
+  public void setDescription(String description) {
+    this.description = description;
+  }
+
   @Override
   public String toString() {
     return "[PatchSet " + getId().toString() + "]";
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/PatchSetInfo.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/PatchSetInfo.java
index 1a00fae..40cb9fc 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/PatchSetInfo.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/PatchSetInfo.java
@@ -54,6 +54,9 @@
   /** SHA-1 of commit */
   protected String revId;
 
+  /** Optional user-supplied description for the patch set. */
+  protected String description;
+
   protected PatchSetInfo() {
   }
 
@@ -116,4 +119,12 @@
   public String getRevId() {
     return revId;
   }
+
+  public void setDescription(String description) {
+    this.description = description;
+  }
+
+  public String getDescription() {
+    return description;
+  }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/PatchSetUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/PatchSetUtil.java
index 3ca11d5..ee40cd4 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/PatchSetUtil.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/PatchSetUtil.java
@@ -91,7 +91,7 @@
 
   public PatchSet insert(ReviewDb db, RevWalk rw, ChangeUpdate update,
       PatchSet.Id psId, ObjectId commit, boolean draft,
-      List<String> groups, String pushCertificate)
+      List<String> groups, String pushCertificate, String description)
       throws OrmException, IOException {
     checkNotNull(groups, "groups may not be null");
     ensurePatchSetMatches(psId, update);
@@ -103,9 +103,11 @@
     ps.setDraft(draft);
     ps.setGroups(groups);
     ps.setPushCertificate(pushCertificate);
+    ps.setDescription(description);
     db.patchSets().insert(Collections.singleton(ps));
 
     update.setCommit(rw, commit, pushCertificate);
+    update.setPsDescription(description);
     update.setGroups(groups);
     if (draft) {
       update.setPatchSetState(DRAFT);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeInserter.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeInserter.java
index 975f459..a2f5df4 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeInserter.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeInserter.java
@@ -107,6 +107,7 @@
   private Change.Status status;
   private String topic;
   private String message;
+  private String patchSetDescription;
   private List<String> groups = Collections.emptyList();
   private CommitValidators.Policy validatePolicy =
       CommitValidators.Policy.GERRIT;
@@ -219,6 +220,11 @@
     return this;
   }
 
+  public ChangeInserter setPatchSetDescription(String patchSetDescription) {
+    this.patchSetDescription = patchSetDescription;
+    return this;
+  }
+
   public ChangeInserter setValidatePolicy(CommitValidators.Policy validate) {
     this.validatePolicy = checkNotNull(validate);
     return this;
@@ -336,6 +342,7 @@
     update.setSubjectForCommit("Create change");
     update.setBranch(change.getDest().get());
     update.setTopic(change.getTopic());
+    update.setPsDescription(patchSetDescription);
 
     boolean draft = status == Change.Status.DRAFT;
     List<String> newGroups = groups;
@@ -343,7 +350,7 @@
       newGroups = GroupCollector.getDefaultGroups(commit);
     }
     patchSet = psUtil.insert(ctx.getDb(), ctx.getRevWalk(), update, psId,
-        commit, draft, newGroups, pushCert);
+        commit, draft, newGroups, pushCert, patchSetDescription);
 
     /* TODO: fixStatus is used here because the tests
      * (byStatusClosed() in AbstractQueryChangesTest)
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeJson.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeJson.java
index 1684e76..e418364 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeJson.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeJson.java
@@ -1065,6 +1065,7 @@
     out.draft = in.isDraft() ? true : null;
     out.fetch = makeFetchMap(ctl, in);
     out.kind = changeKindCache.getChangeKind(repo, cd, in);
+    out.description = in.getDescription();
 
     boolean setCommit = has(ALL_COMMITS)
         || (out.isCurrent && has(CURRENT_COMMIT));
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/PatchSetInserter.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/PatchSetInserter.java
index 2b31c71..e47013e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/PatchSetInserter.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/PatchSetInserter.java
@@ -217,7 +217,7 @@
       }
     }
     patchSet = psUtil.insert(db, ctx.getRevWalk(), ctx.getUpdate(psId),
-        psId, commit, draft, newGroups, null);
+        psId, commit, draft, newGroups, null, null);
 
     if (notify != NotifyHandling.NONE) {
       oldReviewers = approvalsUtil.getReviewers(db, ctl.getNotes());
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReview.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReview.java
index 2decd12..e711181 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReview.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReview.java
@@ -109,6 +109,7 @@
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 
 @Singleton
@@ -248,7 +249,7 @@
         bu.addOp(revision.getChange().getId(), reviewerResult.op);
         if (!ccOrReviewer && reviewerResult.result.reviewers != null) {
           for (ReviewerInfo reviewerInfo : reviewerResult.result.reviewers) {
-            if (id.equals(reviewerInfo._accountId)) {
+            if (Objects.equals(id.get(), reviewerInfo._accountId)) {
               ccOrReviewer = true;
               break;
             }
@@ -256,7 +257,7 @@
         }
         if (!ccOrReviewer && reviewerResult.result.ccs != null) {
           for (AccountInfo accountInfo : reviewerResult.result.ccs) {
-            if (id.equals(accountInfo._accountId)) {
+            if (Objects.equals(id.get(), accountInfo._accountId)) {
               ccOrReviewer = true;
               break;
             }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java
index 74362b8..4f86927 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java
@@ -2082,7 +2082,8 @@
             .setNotify(magicBranch.notify)
             .setRequestScopePropagator(requestScopePropagator)
             .setSendMail(true)
-            .setUpdateRef(false));
+            .setUpdateRef(false)
+            .setPatchSetDescription(magicBranch.message));
         if (!magicBranch.hashtags.isEmpty()) {
           bu.addOp(
               changeId,
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/ReplaceOp.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/ReplaceOp.java
index a60b86f..e4a79cb 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/ReplaceOp.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/ReplaceOp.java
@@ -229,9 +229,11 @@
     update.setSubjectForCommit("Create patch set " + patchSetId.get());
 
     String reviewMessage = null;
+    String psDescription = null;
     if (magicBranch != null) {
       recipients.add(magicBranch.getMailRecipients());
       reviewMessage = magicBranch.message;
+      psDescription = magicBranch.message;
       approvals.putAll(magicBranch.labels);
       Set<String> hashtags = magicBranch.hashtags;
       if (hashtags != null && !hashtags.isEmpty()) {
@@ -252,8 +254,9 @@
         ctx.getDb(), ctx.getRevWalk(), update, patchSetId, commit, draft, groups,
         pushCertificate != null
           ? pushCertificate.toTextWithSignature()
-          : null);
+          : null, psDescription);
 
+    update.setPsDescription(psDescription);
     recipients.add(getRecipientsFromFooters(
         ctx.getDb(), accountResolver, draft, commit.getFooterLines()));
     recipients.remove(ctx.getAccountId());
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/CherryPick.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/CherryPick.java
index c0d96c9..a4656ae 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/CherryPick.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/CherryPick.java
@@ -146,7 +146,7 @@
       PatchSet newPs = args.psUtil.insert(ctx.getDb(), ctx.getRevWalk(),
           ctx.getUpdate(psId), psId, newCommit, false,
           prevPs != null ? prevPs.getGroups() : ImmutableList.<String> of(),
-          null);
+          null, null);
       ctx.getChange().setCurrentPatchSet(patchSetInfo);
 
       // Don't copy approvals, as this is already taken care of by
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/RebaseSubmitStrategy.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/RebaseSubmitStrategy.java
index 925d515..c93cf6d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/RebaseSubmitStrategy.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/RebaseSubmitStrategy.java
@@ -210,7 +210,7 @@
         newPs = args.psUtil.insert(ctx.getDb(), ctx.getRevWalk(),
             ctx.getUpdate(newPatchSetId), newPatchSetId, newCommit, false,
             prevPs != null ? prevPs.getGroups() : ImmutableList.<String> of(),
-            null);
+            null, null);
       }
       ctx.getChange().setCurrentPatchSet(args.patchSetInfoFactory
           .get(ctx.getRevWalk(), newCommit, newPatchSetId));
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategyOp.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategyOp.java
index 8a303a7..af1052f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategyOp.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategyOp.java
@@ -296,7 +296,7 @@
         ? prevPs.getGroups()
         : GroupCollector.getDefaultGroups(alreadyMerged);
     return args.psUtil.insert(ctx.getDb(), ctx.getRevWalk(),
-        ctx.getUpdate(psId), psId, alreadyMerged, false, groups, null);
+        ctx.getUpdate(psId), psId, alreadyMerged, false, groups, null, null);
   }
 
   private void setApproval(ChangeContext ctx, IdentifiedUser user)
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/receive/ImapMailReceiver.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/receive/ImapMailReceiver.java
index 32a26b1..00f3091 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/receive/ImapMailReceiver.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/receive/ImapMailReceiver.java
@@ -32,7 +32,7 @@
 public class ImapMailReceiver extends MailReceiver {
   private static final Logger log =
       LoggerFactory.getLogger(ImapMailReceiver.class);
-  private static final String inboxFolder = "INBOX";
+  private static final String INBOX_FOLDER = "INBOX";
 
   @Inject
   public ImapMailReceiver(EmailSettings mailSettings) {
@@ -64,8 +64,8 @@
           return;
         }
         try {
-          if (!imap.select(inboxFolder)){
-            log.error("Could not select IMAP folder " + inboxFolder);
+          if (!imap.select(INBOX_FOLDER)){
+            log.error("Could not select IMAP folder " + INBOX_FOLDER);
             return;
           }
           // Fetch just the internal dates first to know how many messages we
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/receive/MailReceiver.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/receive/MailReceiver.java
index 4a8f7fb..7e75b2e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/receive/MailReceiver.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/receive/MailReceiver.java
@@ -59,7 +59,6 @@
     }
   }
 
-  @Inject
   public MailReceiver(EmailSettings mailSettings) {
     this.mailSettings = mailSettings;
     pendingDeletion = Collections.synchronizedSet(new HashSet<>());
@@ -77,7 +76,7 @@
       public void run() {
         MailReceiver.this.handleEmails();
       }
-    }, 0l, mailSettings.fetchInterval);
+    }, 0L, mailSettings.fetchInterval);
   }
 
   @Override
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/receive/RawMailParser.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/receive/RawMailParser.java
index ed35c9b..7315e44 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/receive/RawMailParser.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/receive/RawMailParser.java
@@ -14,6 +14,8 @@
 
 package com.google.gerrit.server.mail.receive;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
+
 import com.google.common.base.Strings;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.io.CharStreams;
@@ -54,7 +56,7 @@
     try {
       MessageBuilder builder = new DefaultMessageBuilder();
       mimeMessage =
-          builder.parseMessage(new ByteArrayInputStream(raw.getBytes()));
+          builder.parseMessage(new ByteArrayInputStream(raw.getBytes(UTF_8)));
     } catch (IOException | MimeException e) {
       throw new MailParsingException("Can't parse email", e);
     }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/send/ChangeEmail.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/send/ChangeEmail.java
index 73d77c0..7053e84 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/send/ChangeEmail.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/send/ChangeEmail.java
@@ -446,6 +446,7 @@
     changeData.put("subject", change.getSubject());
     changeData.put("originalSubject", change.getOriginalSubject());
     changeData.put("ownerEmail", getNameEmailFor(change.getOwner()));
+    changeData.put("changeNumber", Integer.toString(change.getChangeId()));
     soyContext.put("change", changeData);
 
     String subject = change.getSubject();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeBundle.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeBundle.java
index 3c7277a..d1e3e64 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeBundle.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeBundle.java
@@ -228,7 +228,7 @@
     checkColumns(ChangeMessage.Key.class, 1, 2);
     checkColumns(ChangeMessage.class, 1, 2, 3, 4, 5, 6, 7);
     checkColumns(PatchSet.Id.class, 1, 2);
-    checkColumns(PatchSet.class, 1, 2, 3, 4, 5, 6, 8);
+    checkColumns(PatchSet.class, 1, 2, 3, 4, 5, 6, 8, 9);
     checkColumns(PatchSetApproval.Key.class, 1, 2, 3);
     checkColumns(PatchSetApproval.class, 1, 2, 3, 6, 7, 8);
     checkColumns(PatchLineComment.Key.class, 1, 2);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNoteUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNoteUtil.java
index 239b54e..8618606 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNoteUtil.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNoteUtil.java
@@ -71,6 +71,8 @@
   public static final FooterKey FOOTER_HASHTAGS = new FooterKey("Hashtags");
   public static final FooterKey FOOTER_LABEL = new FooterKey("Label");
   public static final FooterKey FOOTER_PATCH_SET = new FooterKey("Patch-set");
+  public static final FooterKey FOOTER_PATCH_SET_DESCRIPTION =
+      new FooterKey("Patch-set-description");
   public static final FooterKey FOOTER_REAL_USER = new FooterKey("Real-user");
   public static final FooterKey FOOTER_STATUS = new FooterKey("Status");
   public static final FooterKey FOOTER_SUBJECT = new FooterKey("Subject");
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotesCache.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotesCache.java
index 73b7aec..92ad17d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotesCache.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotesCache.java
@@ -86,7 +86,8 @@
       // Numbers are largely hand-wavy based on
       // http://stackoverflow.com/questions/258120/what-is-the-memory-consumption-of-an-object-in-java
       return
-          K // changeId
+          P + O + 20 // metaId
+          + K // changeId
           + str(40) // changeKey
           + T // createdOn
           + T // lastUpdatedOn
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotesParser.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotesParser.java
index 37fc9f9..9689486 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotesParser.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotesParser.java
@@ -22,6 +22,7 @@
 import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_HASHTAGS;
 import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_LABEL;
 import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_PATCH_SET;
+import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_PATCH_SET_DESCRIPTION;
 import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_REAL_USER;
 import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_STATUS;
 import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_SUBJECT;
@@ -210,6 +211,7 @@
 
   private ChangeNotesState buildState() {
     return ChangeNotesState.create(
+        tip.copy(),
         id,
         new Change.Key(changeId),
         createdOn,
@@ -325,7 +327,6 @@
     }
 
     parseHashtags(commit);
-
     parseAssignee(commit);
 
     if (submissionId == null) {
@@ -365,6 +366,8 @@
     if (lastUpdatedOn == null || ts.after(lastUpdatedOn)) {
       lastUpdatedOn = ts;
     }
+
+    parseDescription(psId, commit);
   }
 
   private String parseSubmissionId(ChangeNotesCommit commit)
@@ -593,6 +596,28 @@
     throw invalidFooter(FOOTER_PATCH_SET, psIdLine);
   }
 
+  private void parseDescription(PatchSet.Id psId, ChangeNotesCommit commit)
+      throws ConfigInvalidException {
+    List<String> descLines =
+        commit.getFooterLineValues(FOOTER_PATCH_SET_DESCRIPTION);
+    if (descLines.isEmpty()) {
+      return;
+    } else if (descLines.size() == 1) {
+      String desc = descLines.get(0).trim();
+      PatchSet ps = patchSets.get(psId);
+      if (ps == null) {
+        ps = new PatchSet(psId);
+        ps.setRevision(PARTIAL_PATCH_SET);
+        patchSets.put(psId, ps);
+      }
+      if (ps.getDescription() == null) {
+        ps.setDescription(desc);
+      }
+    } else {
+      throw expectedOneFooter(FOOTER_PATCH_SET_DESCRIPTION, descLines);
+    }
+  }
+
   private void parseChangeMessage(PatchSet.Id psId,
       Account.Id accountId, Account.Id realAccountId,
       ChangeNotesCommit commit, Timestamp ts) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotesState.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotesState.java
index d5abdee..337bf93 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotesState.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotesState.java
@@ -15,6 +15,7 @@
 package com.google.gerrit.server.notedb;
 
 import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
 
 import com.google.auto.value.AutoValue;
 import com.google.common.base.Strings;
@@ -35,7 +36,11 @@
 import com.google.gerrit.reviewdb.client.RevId;
 import com.google.gerrit.server.ReviewerSet;
 import com.google.gerrit.server.ReviewerStatusUpdate;
+import com.google.gerrit.server.notedb.NoteDbChangeState.PrimaryStorage;
 
+import org.eclipse.jgit.lib.ObjectId;
+
+import java.io.IOException;
 import java.sql.Timestamp;
 import java.util.List;
 import java.util.Map;
@@ -56,6 +61,7 @@
 public abstract class ChangeNotesState {
   static ChangeNotesState empty(Change change) {
     return new AutoValue_ChangeNotesState(
+        null,
         change.getId(),
         null,
         ImmutableSet.of(),
@@ -72,6 +78,7 @@
   }
 
   static ChangeNotesState create(
+      @Nullable ObjectId metaId,
       Change.Id changeId,
       Change.Key changeKey,
       Timestamp createdOn,
@@ -100,6 +107,7 @@
       hashtags = ImmutableSet.of();
     }
     return new AutoValue_ChangeNotesState(
+        metaId,
         changeId,
         new AutoValue_ChangeNotesState_ChangeColumns(
             changeKey,
@@ -156,8 +164,12 @@
     @Nullable abstract Change.Status status();
   }
 
+  // Only null if NoteDb is disabled.
+  @Nullable abstract ObjectId metaId();
+
   abstract Change.Id changeId();
 
+  // Only null if NoteDb is disabled.
   @Nullable abstract ChangeColumns columns();
 
   // Other related to this Change.
@@ -189,8 +201,12 @@
     return change;
   }
 
-  void copyColumnsTo(Change change) {
-    ChangeColumns c = checkNotNull(columns(), "columns are required");
+  void copyColumnsTo(Change change) throws IOException {
+    ChangeColumns c = columns();
+    checkState(c != null && metaId() != null,
+        "missing columns or metaId in ChangeNotesState; is NoteDb enabled? %s",
+        this);
+    checkMetaId(change);
     change.setKey(c.changeKey());
     change.setOwner(c.owner());
     change.setDest(new Branch.NameKey(change.getProject(), c.branch()));
@@ -198,6 +214,23 @@
     copyNonConstructorColumnsTo(change);
   }
 
+  private void checkMetaId(Change change) throws IOException {
+    NoteDbChangeState state = NoteDbChangeState.parse(change);
+    if (state == null) {
+      return; // Can happen during small NoteDb tests.
+    } else if (state.getPrimaryStorage() == PrimaryStorage.NOTE_DB) {
+      return;
+    }
+    checkState(state.getRefState().isPresent(), "expected RefState: %s", state);
+    ObjectId idFromState = state.getRefState().get().changeMetaId();
+    if (!idFromState.equals(metaId())) {
+      throw new IOException(
+          "cannot copy ChangeNotesState into Change " + changeId()
+          + "; this ChangeNotesState was created from " + metaId()
+          + ", but change requires state " + idFromState);
+    }
+  }
+
   private void copyNonConstructorColumnsTo(Change change) {
     ChangeColumns c = checkNotNull(columns(), "columns are required");
     if (c.status() != null) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeUpdate.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeUpdate.java
index 1a417e1..c9a55c6 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeUpdate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeUpdate.java
@@ -27,6 +27,7 @@
 import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_HASHTAGS;
 import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_LABEL;
 import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_PATCH_SET;
+import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_PATCH_SET_DESCRIPTION;
 import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_REAL_USER;
 import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_STATUS;
 import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_SUBJECT;
@@ -142,6 +143,7 @@
   private Iterable<String> groups;
   private String pushCert;
   private boolean isAllowWriteToNewtRef;
+  private String psDescription;
 
   private ChangeDraftUpdate draftUpdate;
   private RobotCommentUpdate robotCommentUpdate;
@@ -322,6 +324,10 @@
     this.tag = tag;
   }
 
+  public void setPsDescription(String psDescription) {
+    this.psDescription = psDescription;
+  }
+
   public void putComment(PatchLineComment.Status status, Comment c) {
     verifyComment(c);
     createDraftUpdateIfNull();
@@ -561,6 +567,10 @@
 
     addPatchSetFooter(msg, ps);
 
+    if (psDescription != null) {
+      addFooter(msg, FOOTER_PATCH_SET_DESCRIPTION, psDescription);
+    }
+
     if (changeId != null) {
       addFooter(msg, FOOTER_CHANGE_ID, changeId);
     }
@@ -703,7 +713,8 @@
         && commit == null
         && psState == null
         && groups == null
-        && tag == null;
+        && tag == null
+        && psDescription == null;
   }
 
   ChangeDraftUpdate getDraftUpdate() {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/PatchSetEvent.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/PatchSetEvent.java
index abac1b0..eeeec55 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/PatchSetEvent.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/PatchSetEvent.java
@@ -57,6 +57,7 @@
       update.setSubjectForCommit("Create patch set " + ps.getPatchSetId());
     }
     setRevision(update, ps);
+    update.setPsDescription(ps.getDescription());
     List<String> groups = ps.getGroups();
     if (!groups.isEmpty()) {
       update.setGroups(ps.getGroups());
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaVersion.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaVersion.java
index 893b2d4..87b9eff 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaVersion.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaVersion.java
@@ -36,7 +36,7 @@
 /** A version of the database schema. */
 public abstract class SchemaVersion {
   /** The current schema version. */
-  public static final Class<Schema_135> C = Schema_135.class;
+  public static final Class<Schema_136> C = Schema_136.class;
 
   public static int getBinaryVersion() {
     return guessVersion(C);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_136.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_136.java
new file mode 100644
index 0000000..a4b1c82
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_136.java
@@ -0,0 +1,25 @@
+// Copyright (C) 2016 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.schema;
+
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+public class Schema_136 extends SchemaVersion {
+  @Inject
+  Schema_136(Provider<Schema_135> prior) {
+    super(prior);
+  }
+}
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/ChangeFooter.soy b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/ChangeFooter.soy
index 644008b..4958bde 100644
--- a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/ChangeFooter.soy
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/ChangeFooter.soy
@@ -47,6 +47,7 @@
 
   Gerrit-MessageType: {$messageClass}{\n}
   Gerrit-Change-Id: {$changeId}{\n}
+  Gerrit-Change-Number: {$change.changeNumber}{\n}
   Gerrit-PatchSet: {$patchSet.patchSetId}{\n}
   Gerrit-Project: {$projectName}{\n}
   Gerrit-Branch: {$branch.shortName}{\n}
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/ChangeFooterHtml.soy b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/ChangeFooterHtml.soy
index 41ea1b6..28b2c28 100644
--- a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/ChangeFooterHtml.soy
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/ChangeFooterHtml.soy
@@ -47,6 +47,7 @@
   <p style="{$footerStyle}">
     Gerrit-MessageType: {$messageClass}<br/>
     Gerrit-Change-Id: {$changeId}<br/>
+    Gerrit-Change-Number: {$change.changeNumber}<br/>
     Gerrit-PatchSet: {$patchSet.patchSetId}<br/>
     Gerrit-Project: {$projectName}<br/>
     Gerrit-Branch: {$branch.shortName}<br/>
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/notedb/ChangeNotesTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/notedb/ChangeNotesTest.java
index 29c11ff..a648276 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/notedb/ChangeNotesTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/notedb/ChangeNotesTest.java
@@ -98,6 +98,28 @@
   }
 
   @Test
+  public void patchSetDescription() throws Exception {
+    String description = "descriptive";
+    Change c = newChange();
+    ChangeUpdate update = newUpdate(c, changeOwner);
+    update.setPsDescription(description);
+    update.commit();
+
+    ChangeNotes notes = newNotes(c);
+    assertThat(notes.getCurrentPatchSet().getDescription())
+        .isEqualTo(description);
+
+    description = "new, now more descriptive!";
+    update = newUpdate(c, changeOwner);
+    update.setPsDescription(description);
+    update.commit();
+
+    notes = newNotes(c);
+    assertThat(notes.getCurrentPatchSet().getDescription())
+      .isEqualTo(description);
+  }
+
+  @Test
   public void tagInlineCommenrts() throws Exception {
     String tag = "jenkins";
     Change c = newChange();
diff --git a/lib/fonts/BUCK b/lib/fonts/BUCK
index c5b78eb..7b64cf2 100644
--- a/lib/fonts/BUCK
+++ b/lib/fonts/BUCK
@@ -11,20 +11,3 @@
   license = 'OFL1.1',
   visibility = ['PUBLIC'],
 )
-
-# Open Sans at Revision 53a5266 and converted using a Google woff file
-# converter (same one that Google Fonts uses).
-# https://github.com/google/fonts/tree/master/apache/opensans
-genrule(
-  name = 'opensans',
-  cmd = 'zip -rq $OUT .',
-  srcs = [
-    'OpenSans-Bold.woff',
-    'OpenSans-Bold.woff2',
-    'OpenSans-Regular.woff',
-    'OpenSans-Regular.woff2'
-  ],
-  out = 'opensans.zip',
-  license = 'Apache2.0',
-  visibility = ['PUBLIC'],
-)
diff --git a/lib/fonts/BUILD b/lib/fonts/BUILD
index 88db107..a764524 100644
--- a/lib/fonts/BUILD
+++ b/lib/fonts/BUILD
@@ -11,21 +11,3 @@
   data = [ "//lib:LICENSE-OFL1.1" ],
   visibility = ['//visibility:public'],
 )
-
-# Open Sans at Revision 53a5266 and converted using a Google woff file
-# converter (same one that Google Fonts uses).
-# https://github.com/google/fonts/tree/master/apache/opensans
-genrule2(
-  name = 'opensans',
-  cmd = 'zip -rq $@ $(SRCS)',
-  srcs = [
-    'OpenSans-Bold.woff',
-    'OpenSans-Bold.woff2',
-    'OpenSans-Regular.woff',
-    'OpenSans-Regular.woff2'
-  ],
-  outs = [ 'opensans.zip' ],
-# TODO(hanwen): license.
-#  license = 'Apache2.0',
-  visibility = ['//visibility:public'],
-)
diff --git a/lib/fonts/OpenSans-Bold.woff b/lib/fonts/OpenSans-Bold.woff
deleted file mode 100644
index 74c4086..0000000
--- a/lib/fonts/OpenSans-Bold.woff
+++ /dev/null
Binary files differ
diff --git a/lib/fonts/OpenSans-Bold.woff2 b/lib/fonts/OpenSans-Bold.woff2
deleted file mode 100644
index 44d6c26..0000000
--- a/lib/fonts/OpenSans-Bold.woff2
+++ /dev/null
Binary files differ
diff --git a/lib/fonts/OpenSans-Regular.woff b/lib/fonts/OpenSans-Regular.woff
deleted file mode 100644
index 882f7c9..0000000
--- a/lib/fonts/OpenSans-Regular.woff
+++ /dev/null
Binary files differ
diff --git a/lib/fonts/OpenSans-Regular.woff2 b/lib/fonts/OpenSans-Regular.woff2
deleted file mode 100644
index 52217ee..0000000
--- a/lib/fonts/OpenSans-Regular.woff2
+++ /dev/null
Binary files differ
diff --git a/lib/guava.bzl b/lib/guava.bzl
index a7f65c1..310d66b 100644
--- a/lib/guava.bzl
+++ b/lib/guava.bzl
@@ -1,3 +1,3 @@
-GUAVA_VERSION = '20.0-rc1'
-GUAVA_BIN_SHA1 = '4c2a4581b69b16a57968da32fcadb8e362b639b2'
+GUAVA_VERSION = '20.0'
+GUAVA_BIN_SHA1 = '89507701249388e1ed5ddcf8c41f4ce1be7831ef'
 GUAVA_DOC_URL = 'https://google.github.io/guava/releases/' + GUAVA_VERSION + '/api/docs/'
diff --git a/polygerrit-ui/BUCK b/polygerrit-ui/BUCK
index 80f9f29..206065d 100644
--- a/polygerrit-ui/BUCK
+++ b/polygerrit-ui/BUCK
@@ -22,8 +22,9 @@
   name = 'fonts',
   cmd = ' && '.join([
     'cd $TMP',
-    'for file in $SRCS; do unzip -q $file; done',
-    'zip -q $OUT *',
+    'mkdir fonts',
+    'for file in $SRCS; do echo `pwd` > /tmp/log.log; unzip -qd fonts/ $file; done',
+    'zip -qr $OUT fonts',
   ]),
   srcs = [
     '//lib/fonts:sourcecodepro',
diff --git a/polygerrit-ui/app/BUCK b/polygerrit-ui/app/BUCK
index 6a2b299..5d4f06d 100644
--- a/polygerrit-ui/app/BUCK
+++ b/polygerrit-ui/app/BUCK
@@ -43,7 +43,7 @@
     'cd $TMP/polygerrit_ui',
     'mkdir -p {fonts,elements}',
     ' && '.join(JS_LIBS_MKDIR_CMDS),
-    'unzip -qd fonts $(location //polygerrit-ui:fonts)',
+    'unzip -qd . $(location //polygerrit-ui:fonts)',
     'unzip -qd elements $(location :gr-app)',
     'cp -rp $SRCDIR/* .',
     ' && '.join(JS_LIBS_UNZIP_CMDS),
diff --git a/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list.js b/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list.js
index cac45c6..c066b17 100644
--- a/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list.js
+++ b/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list.js
@@ -60,14 +60,18 @@
         this._getSubmittedTogether().then(function(response) {
           this._submittedTogether = response;
         }.bind(this)),
-        this._getConflicts().then(function(response) {
-          this._conflicts = response;
-        }.bind(this)),
         this._getCherryPicks().then(function(response) {
           this._cherryPicks = response;
         }.bind(this)),
       ];
 
+      // Get conflicts if change is open and is mergeable.
+      if (this.changeIsOpen(this.change.status) && this.change.mergeable) {
+        promises.push(this._getConflicts().then(function(response) {
+          this._conflicts = response;
+        }.bind(this)));
+      }
+
       promises.push(this._getServerConfig().then(function(config) {
         if (this.change.topic && !config.change.submit_whole_topic) {
           return this._getChangesWithSameTopic().then(function(response) {
diff --git a/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list_test.html b/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list_test.html
index f7864ce..21903d2 100644
--- a/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list_test.html
+++ b/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list_test.html
@@ -33,9 +33,15 @@
 <script>
   suite('gr-related-changes-list tests', function() {
     var element;
+    var sandbox;
 
     setup(function() {
       element = fixture('basic');
+      sandbox = sinon.sandbox.create();
+    });
+
+    teardown(function() {
+      sandbox.restore();
     });
 
     test('connected revisions', function() {
@@ -223,5 +229,64 @@
       assert.equal(element._computeChangeContainerClass(
           change1, change2).indexOf('thisChange'), -1);
     });
+
+    suite('get conflicts tests', function() {
+      var element;
+      var conflictsStub;
+
+      setup(function() {
+        element = fixture('basic');
+
+        sandbox.stub(element, '_getRelatedChanges',
+            function() { return Promise.resolve(); });
+        sandbox.stub(element, '_getSubmittedTogether',
+            function() { return Promise.resolve(); });
+        sandbox.stub(element, '_getCherryPicks',
+            function() { return Promise.resolve(); });
+        conflictsStub = sandbox.stub(element, '_getConflicts',
+            function() { return Promise.resolve(); });
+      });
+
+      test('request conflicts if open and mergeable', function() {
+        element.patchNum = 7;
+        element.change = {
+          status: 'NEW',
+          mergeable: true,
+        };
+        element.reload();
+        assert.isTrue(conflictsStub.called);
+      });
+
+      test('does not request conflicts if closed and mergeable', function() {
+        element.patchNum = 7;
+        element.change = {
+          status: 'MERGED',
+          mergeable: true,
+        };
+        element.reload();
+        assert.isFalse(conflictsStub.called);
+      });
+
+      test('does not request conflicts if open and not mergeable', function() {
+        element.patchNum = 7;
+        element.change = {
+          status: 'NEW',
+          mergeable: false,
+        };
+        element.reload();
+        assert.isFalse(conflictsStub.called);
+      });
+
+      test('does not request conflicts if closed and not mergeable',
+          function() {
+        element.patchNum = 7;
+        element.change = {
+          status: 'MERGED',
+          mergeable: false,
+        };
+        element.reload();
+        assert.isFalse(conflictsStub.called);
+      });
+    });
   });
 </script>
diff --git a/polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text_test.html b/polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text_test.html
index f530331..7feaa51 100644
--- a/polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text_test.html
@@ -47,6 +47,10 @@
           match: '(I[0-9a-f]{8,40})',
           link: '#/q/$1'
         },
+        changeid2: {
+          match: 'Change-Id: +(I[0-9a-f]{8,40})',
+          link: '#/q/$1'
+        },
         googlesearch: {
           match: 'google:(.+)',
           link: 'https://bing.com/search?q=$1',  // html should supercede link.
@@ -151,7 +155,6 @@
       assert.equal(bugLinkEl.textContent, 'Issue 3650');
     });
 
-
     test('html field in link config', function() {
       element.content = 'google:do a barrel roll';
       var linkEl = element.$.output.childNodes[0];
diff --git a/polygerrit-ui/app/elements/shared/gr-linked-text/link-text-parser.js b/polygerrit-ui/app/elements/shared/gr-linked-text/link-text-parser.js
index 5e1ff62..303a9cc 100644
--- a/polygerrit-ui/app/elements/shared/gr-linked-text/link-text-parser.js
+++ b/polygerrit-ui/app/elements/shared/gr-linked-text/link-text-parser.js
@@ -93,12 +93,31 @@
   if (!text) {
     return;
   }
-  this.addItem(text, href, null, position, length, outputArray);
+  if (!this.hasOverlap(position, length, outputArray)) {
+    this.addItem(text, href, null, position, length, outputArray);
+  }
 };
 
 GrLinkTextParser.prototype.addHTML =
     function(html, position, length, outputArray) {
-  this.addItem(null, null, html, position, length, outputArray);
+  if (!this.hasOverlap(position, length, outputArray)) {
+    this.addItem(null, null, html, position, length, outputArray);
+  }
+};
+
+GrLinkTextParser.prototype.hasOverlap =
+    function(position, length, outputArray) {
+  var endPosition = position + length;
+  for (var i = 0; i < outputArray.length; i++) {
+    var arrayItemStart = outputArray[i].position;
+    var arrayItemEnd = outputArray[i].position + outputArray[i].length;
+    if ((position >= arrayItemStart && position < arrayItemEnd) ||
+      (endPosition > arrayItemStart && endPosition <= arrayItemEnd) ||
+      (position === arrayItemStart && position === arrayItemEnd)) {
+          return true;
+    }
+  }
+  return false;
 };
 
 GrLinkTextParser.prototype.parse = function(text) {
diff --git a/tools/bazel.rc b/tools/bazel.rc
new file mode 100644
index 0000000..2210511
--- /dev/null
+++ b/tools/bazel.rc
@@ -0,0 +1 @@
+build --workspace_status_command=./tools/workspace-status.sh
diff --git a/tools/maven/package.bzl b/tools/maven/package.bzl
index fbd08c6..66ea237 100644
--- a/tools/maven/package.bzl
+++ b/tools/maven/package.bzl
@@ -48,6 +48,7 @@
     srcs = api_targets,
     outs = ['api_install.sh'],
     executable = True,
+    testonly = 1,
   )
 
   if repository and url:
@@ -61,6 +62,7 @@
       srcs = api_targets,
       outs = ['api_deploy.sh'],
       executable = True,
+      testonly = 1,
     )
 
   war_cmd = mvn_cmd[:]