Merge changes from topic "motd" into stable-2.16

* changes:
  Document MessageOfTheDay extension
  Add UI element to display messages of the day
  Add MessageOfTheDay-entries to ServerInfo
diff --git a/Documentation/metrics.txt b/Documentation/metrics.txt
index 37d6d01..ce9f74e 100644
--- a/Documentation/metrics.txt
+++ b/Documentation/metrics.txt
@@ -56,6 +56,18 @@
 
 === HTTP
 
+==== Jetty
+
+* `http/server/jetty/threadpool/active_threads`: Active threads
+* `http/server/jetty/threadpool/idle_threads`: Idle threads
+* `http/server/jetty/threadpool/reserved_threads`: Reserved threads
+* `http/server/jetty/threadpool/max_pool_size`: Maximum thread pool size
+* `http/server/jetty/threadpool/min_pool_size`: Minimum thread pool size
+* `http/server/jetty/threadpool/pool_size`: Current thread pool size
+* `http/server/jetty/threadpool/queue_size`: Queued requests waiting for a thread
+
+==== REST API
+
 * `http/server/error_count`: Rate of REST API error responses.
 * `http/server/success_count`: Rate of REST API success responses.
 * `http/server/rest_api/count`: Rate of REST API calls by view.
diff --git a/java/com/google/gerrit/pgm/http/jetty/JettyMetrics.java b/java/com/google/gerrit/pgm/http/jetty/JettyMetrics.java
index 92edf40..b6a2d38 100644
--- a/java/com/google/gerrit/pgm/http/jetty/JettyMetrics.java
+++ b/java/com/google/gerrit/pgm/http/jetty/JettyMetrics.java
@@ -28,42 +28,42 @@
   JettyMetrics(JettyServer jetty, MetricMaker metrics) {
     CallbackMetric0<Integer> minPoolSize =
         metrics.newCallbackMetric(
-            "httpd/jetty/threadpool/min_pool_size",
+            "http/server/jetty/threadpool/min_pool_size",
             Integer.class,
             new Description("Minimum thread pool size").setGauge());
     CallbackMetric0<Integer> maxPoolSize =
         metrics.newCallbackMetric(
-            "httpd/jetty/threadpool/max_pool_size",
+            "http/server/jetty/threadpool/max_pool_size",
             Integer.class,
             new Description("Maximum thread pool size").setGauge());
     CallbackMetric0<Integer> poolSize =
         metrics.newCallbackMetric(
-            "httpd/jetty/threadpool/pool_size",
+            "http/server/jetty/threadpool/pool_size",
             Integer.class,
             new Description("Current thread pool size").setGauge());
     CallbackMetric0<Integer> idleThreads =
         metrics.newCallbackMetric(
-            "httpd/jetty/threadpool/idle_threads",
+            "http/server/jetty/threadpool/idle_threads",
             Integer.class,
-            new Description("Idle httpd threads").setGauge().setUnit("threads"));
+            new Description("Idle threads").setGauge().setUnit("threads"));
     CallbackMetric0<Integer> busyThreads =
         metrics.newCallbackMetric(
-            "httpd/jetty/threadpool/active_threads",
+            "http/server/jetty/threadpool/active_threads",
             Integer.class,
-            new Description("Active httpd threads").setGauge().setUnit("threads"));
+            new Description("Active threads").setGauge().setUnit("threads"));
     CallbackMetric0<Integer> reservedThreads =
         metrics.newCallbackMetric(
-            "httpd/jetty/threadpool/reserved_threads",
+            "http/server/jetty/threadpool/reserved_threads",
             Integer.class,
-            new Description("Reserved httpd threads").setGauge().setUnit("threads"));
+            new Description("Reserved threads").setGauge().setUnit("threads"));
     CallbackMetric0<Integer> queueSize =
         metrics.newCallbackMetric(
-            "httpd/jetty/threadpool/queue_size",
+            "http/server/jetty/threadpool/queue_size",
             Integer.class,
-            new Description("Thread pool queue size").setGauge().setUnit("requests"));
+            new Description("Queued requests waiting for a thread").setGauge().setUnit("requests"));
     CallbackMetric0<Boolean> lowOnThreads =
         metrics.newCallbackMetric(
-            "httpd/jetty/threadpool/is_low_on_threads",
+            "http/server/jetty/threadpool/is_low_on_threads",
             Boolean.class,
             new Description("Whether thread pool is low on threads").setGauge());
     JettyServer.Metrics jettyMetrics = jetty.getMetrics();
diff --git a/java/com/google/gerrit/server/change/ChangeInserter.java b/java/com/google/gerrit/server/change/ChangeInserter.java
index 80d05df..4e69fbe 100644
--- a/java/com/google/gerrit/server/change/ChangeInserter.java
+++ b/java/com/google/gerrit/server/change/ChangeInserter.java
@@ -71,6 +71,7 @@
 import com.google.gerrit.server.update.Context;
 import com.google.gerrit.server.update.InsertChangeOp;
 import com.google.gerrit.server.update.RepoContext;
+import com.google.gerrit.server.util.CommitMessageUtil;
 import com.google.gerrit.server.util.RequestScopePropagator;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
@@ -88,7 +89,6 @@
 import org.eclipse.jgit.revwalk.RevCommit;
 import org.eclipse.jgit.revwalk.RevWalk;
 import org.eclipse.jgit.transport.ReceiveCommand;
-import org.eclipse.jgit.util.ChangeIdUtil;
 
 public class ChangeInserter implements InsertChangeOp {
   private static final FluentLogger logger = FluentLogger.forEnclosingClass();
@@ -210,16 +210,9 @@
     if (!idList.isEmpty()) {
       return new Change.Key(idList.get(idList.size() - 1).trim());
     }
-    ObjectId changeId =
-        ChangeIdUtil.computeChangeId(
-            commit.getTree(),
-            commit,
-            commit.getAuthorIdent(),
-            commit.getCommitterIdent(),
-            commit.getShortMessage());
-    StringBuilder changeIdStr = new StringBuilder();
-    changeIdStr.append("I").append(ObjectId.toString(changeId));
-    return new Change.Key(changeIdStr.toString());
+    // A Change-Id is generated for the review, but not appended to the commit message.
+    // This can happen if requireChangeId is false.
+    return CommitMessageUtil.generateKey();
   }
 
   public PatchSet.Id getPatchSetId() {
diff --git a/java/com/google/gerrit/server/git/meta/VersionedMetaData.java b/java/com/google/gerrit/server/git/meta/VersionedMetaData.java
index f8cfce5..a228b89 100644
--- a/java/com/google/gerrit/server/git/meta/VersionedMetaData.java
+++ b/java/com/google/gerrit/server/git/meta/VersionedMetaData.java
@@ -22,6 +22,7 @@
 import com.google.gerrit.server.git.LockFailureException;
 import com.google.gerrit.server.logging.TraceContext;
 import com.google.gerrit.server.logging.TraceContext.TraceTimer;
+import com.google.gerrit.server.util.CommitMessageUtil;
 import java.io.BufferedReader;
 import java.io.File;
 import java.io.IOException;
@@ -326,14 +327,8 @@
         }
 
         if (update.insertChangeId()) {
-          ObjectId id =
-              ChangeIdUtil.computeChangeId(
-                  res,
-                  getRevision(),
-                  commit.getAuthor(),
-                  commit.getCommitter(),
-                  commit.getMessage());
-          commit.setMessage(ChangeIdUtil.insertId(commit.getMessage(), id));
+          commit.setMessage(
+              ChangeIdUtil.insertId(commit.getMessage(), CommitMessageUtil.generateChangeId()));
         }
 
         src = rw.parseCommit(inserter.insert(commit));
diff --git a/java/com/google/gerrit/server/notedb/rebuild/NoteDbMigrator.java b/java/com/google/gerrit/server/notedb/rebuild/NoteDbMigrator.java
index ebec9c5..d6daae1 100644
--- a/java/com/google/gerrit/server/notedb/rebuild/NoteDbMigrator.java
+++ b/java/com/google/gerrit/server/notedb/rebuild/NoteDbMigrator.java
@@ -72,6 +72,7 @@
 import com.google.gerrit.server.notedb.RepoSequence;
 import com.google.gerrit.server.notedb.rebuild.ChangeRebuilder.NoPatchSetsException;
 import com.google.gerrit.server.project.NoSuchChangeException;
+import com.google.gerrit.server.project.ProjectCache;
 import com.google.gerrit.server.update.ChainedReceiveCommands;
 import com.google.gerrit.server.update.RefUpdateUtil;
 import com.google.gerrit.server.util.ManualRequestContext;
@@ -164,6 +165,7 @@
     private final MutableNotesMigration globalNotesMigration;
     private final PrimaryStorageMigrator primaryStorageMigrator;
     private final DynamicSet<NotesMigrationStateListener> listeners;
+    private final ProjectCache projectCache;
 
     private int threads;
     private ImmutableList<Project.NameKey> projects = ImmutableList.of();
@@ -193,7 +195,8 @@
         WorkQueue workQueue,
         MutableNotesMigration globalNotesMigration,
         PrimaryStorageMigrator primaryStorageMigrator,
-        DynamicSet<NotesMigrationStateListener> listeners) {
+        DynamicSet<NotesMigrationStateListener> listeners,
+        ProjectCache projectCache) {
       // Reload gerrit.config/notedb.config on each migrator invocation, in case a previous
       // migration in the same process modified the on-disk contents. This ensures the defaults for
       // trial/autoMigrate get set correctly below.
@@ -213,6 +216,7 @@
       this.globalNotesMigration = globalNotesMigration;
       this.primaryStorageMigrator = primaryStorageMigrator;
       this.listeners = listeners;
+      this.projectCache = projectCache;
       this.trial = getTrialMode(cfg);
       this.autoMigrate = getAutoMigrate(cfg);
     }
@@ -400,6 +404,7 @@
           changes,
           progressOut,
           stopAtState,
+          projectCache,
           trial,
           forceRebuild,
           sequenceGap >= 0 ? sequenceGap : Sequences.getChangeSequenceGap(cfg),
@@ -429,6 +434,7 @@
   private final ImmutableList<Change.Id> changes;
   private final OutputStream progressOut;
   private final NotesMigrationState stopAtState;
+  private final ProjectCache projectCache;
   private final boolean trial;
   private final boolean forceRebuild;
   private final int sequenceGap;
@@ -455,6 +461,7 @@
       ImmutableList<Change.Id> changes,
       OutputStream progressOut,
       NotesMigrationState stopAtState,
+      ProjectCache projectCache,
       boolean trial,
       boolean forceRebuild,
       int sequenceGap,
@@ -489,6 +496,7 @@
     this.changes = changes;
     this.progressOut = progressOut;
     this.stopAtState = stopAtState;
+    this.projectCache = projectCache;
     this.trial = trial;
     this.forceRebuild = forceRebuild;
     this.sequenceGap = sequenceGap;
@@ -702,11 +710,13 @@
    * of the NoteDb migration code, which is too risky to attempt in the stable branch where this bug
    * had to be fixed.
    *
-   * <p>As of this writing, the only case where this happens is when a change has no patch sets.
+   * <p>As of this writing, there are only two cases where this happens: when a change has no patch
+   * sets, or the project doesn't exist.
    */
-  private static boolean canSkipPrimaryStorageMigration(ReviewDb db, Change.Id id) {
+  private boolean canSkipPrimaryStorageMigration(ReviewDb db, Change.Id id) {
     try {
-      return Iterables.isEmpty(unwrapDb(db).patchSets().byChange(id));
+      return Iterables.isEmpty(unwrapDb(db).patchSets().byChange(id))
+          || projectCache.get(unwrapDb(db).changes().get(id).getProject()) == null;
     } catch (Exception e) {
       logger.atSevere().withCause(e).log(
           "Error checking if change %s can be skipped, assuming no", id);
diff --git a/java/com/google/gerrit/server/restapi/change/CherryPickChange.java b/java/com/google/gerrit/server/restapi/change/CherryPickChange.java
index 1104761..5def390 100644
--- a/java/com/google/gerrit/server/restapi/change/CherryPickChange.java
+++ b/java/com/google/gerrit/server/restapi/change/CherryPickChange.java
@@ -57,6 +57,7 @@
 import com.google.gerrit.server.submit.MergeIdenticalTreeException;
 import com.google.gerrit.server.update.BatchUpdate;
 import com.google.gerrit.server.update.UpdateException;
+import com.google.gerrit.server.util.CommitMessageUtil;
 import com.google.gerrit.server.util.time.TimeUtil;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
@@ -193,14 +194,8 @@
       Timestamp now = TimeUtil.nowTs();
       PersonIdent committerIdent = identifiedUser.newCommitterIdent(now, serverTimeZone);
 
-      final ObjectId computedChangeId =
-          ChangeIdUtil.computeChangeId(
-              commitToCherryPick.getTree(),
-              baseCommit,
-              commitToCherryPick.getAuthorIdent(),
-              committerIdent,
-              input.message);
-      String commitMessage = ChangeIdUtil.insertId(input.message, computedChangeId).trim() + '\n';
+      final ObjectId generatedChangeId = CommitMessageUtil.generateChangeId();
+      String commitMessage = ChangeIdUtil.insertId(input.message, generatedChangeId).trim() + '\n';
 
       CodeReviewCommit cherryPickCommit;
       ProjectState projectState = projectCache.checkedGet(dest.getParentKey());
@@ -235,7 +230,7 @@
           final String idStr = idList.get(idList.size() - 1).trim();
           changeKey = new Change.Key(idStr);
         } else {
-          changeKey = new Change.Key("I" + computedChangeId.name());
+          changeKey = new Change.Key("I" + generatedChangeId.name());
         }
 
         Branch.NameKey newDest = new Branch.NameKey(project, destRef.getName());
diff --git a/java/com/google/gerrit/server/restapi/change/CreateChange.java b/java/com/google/gerrit/server/restapi/change/CreateChange.java
index 565777e..f906f7c 100644
--- a/java/com/google/gerrit/server/restapi/change/CreateChange.java
+++ b/java/com/google/gerrit/server/restapi/change/CreateChange.java
@@ -70,6 +70,7 @@
 import com.google.gerrit.server.update.RetryHelper;
 import com.google.gerrit.server.update.RetryingRestCollectionModifyView;
 import com.google.gerrit.server.update.UpdateException;
+import com.google.gerrit.server.util.CommitMessageUtil;
 import com.google.gerrit.server.util.time.TimeUtil;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
@@ -275,8 +276,7 @@
       // Add a Change-Id line if there isn't already one
       String commitMessage = subject;
       if (ChangeIdUtil.indexOfChangeId(commitMessage, "\n") == -1) {
-        ObjectId treeId = mergeTip == null ? emptyTreeId(oi) : mergeTip.getTree();
-        ObjectId id = ChangeIdUtil.computeChangeId(treeId, mergeTip, author, author, commitMessage);
+        ObjectId id = CommitMessageUtil.generateChangeId();
         commitMessage = ChangeIdUtil.insertId(commitMessage, id);
       }
 
diff --git a/java/com/google/gerrit/server/restapi/change/Revert.java b/java/com/google/gerrit/server/restapi/change/Revert.java
index 7309fde..1040f43 100644
--- a/java/com/google/gerrit/server/restapi/change/Revert.java
+++ b/java/com/google/gerrit/server/restapi/change/Revert.java
@@ -65,6 +65,7 @@
 import com.google.gerrit.server.update.RetryHelper;
 import com.google.gerrit.server.update.RetryingRestModifyView;
 import com.google.gerrit.server.update.UpdateException;
+import com.google.gerrit.server.util.CommitMessageUtil;
 import com.google.gerrit.server.util.time.TimeUtil;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
@@ -207,14 +208,8 @@
                 patch.getRevision().get());
       }
 
-      ObjectId computedChangeId =
-          ChangeIdUtil.computeChangeId(
-              parentToCommitToRevert.getTree(),
-              commitToRevert,
-              authorIdent,
-              committerIdent,
-              message);
-      revertCommitBuilder.setMessage(ChangeIdUtil.insertId(message, computedChangeId, true));
+      ObjectId generatedChangeId = CommitMessageUtil.generateChangeId();
+      revertCommitBuilder.setMessage(ChangeIdUtil.insertId(message, generatedChangeId, true));
 
       Change.Id changeId = new Change.Id(seq.nextChangeId());
       ObjectId id = oi.insert(revertCommitBuilder);
@@ -246,7 +241,7 @@
         bu.setRepository(git, revWalk, oi);
         bu.insertChange(ins);
         bu.addOp(changeId, new NotifyOp(changeToRevert, ins, input.notify, accountsToNotify));
-        bu.addOp(changeToRevert.getId(), new PostRevertedMessageOp(computedChangeId));
+        bu.addOp(changeToRevert.getId(), new PostRevertedMessageOp(generatedChangeId));
         bu.execute();
       }
       return changeId;
diff --git a/java/com/google/gerrit/server/util/CommitMessageUtil.java b/java/com/google/gerrit/server/util/CommitMessageUtil.java
index fa55597..ef54715 100644
--- a/java/com/google/gerrit/server/util/CommitMessageUtil.java
+++ b/java/com/google/gerrit/server/util/CommitMessageUtil.java
@@ -14,11 +14,28 @@
 
 package com.google.gerrit.server.util;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
+
 import com.google.common.base.Strings;
 import com.google.gerrit.extensions.restapi.BadRequestException;
+import com.google.gerrit.reviewdb.client.Change;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectInserter;
 
 /** Utility functions to manipulate commit messages. */
 public class CommitMessageUtil {
+  private static final SecureRandom rng;
+
+  static {
+    try {
+      rng = SecureRandom.getInstance("SHA1PRNG");
+    } catch (NoSuchAlgorithmException e) {
+      throw new RuntimeException("Cannot create RNG for Change-Id generator", e);
+    }
+  }
 
   private CommitMessageUtil() {}
 
@@ -37,4 +54,18 @@
     wellFormedMessage = wellFormedMessage + "\n";
     return wellFormedMessage;
   }
+
+  public static ObjectId generateChangeId() {
+    byte[] rand = new byte[Constants.OBJECT_ID_STRING_LENGTH];
+    rng.nextBytes(rand);
+    String randomString = new String(rand, UTF_8);
+
+    try (ObjectInserter f = new ObjectInserter.Formatter()) {
+      return f.idFor(Constants.OBJ_COMMIT, Constants.encode(randomString));
+    }
+  }
+
+  public static Change.Key generateKey() {
+    return new Change.Key("I" + generateChangeId().name());
+  }
 }
diff --git a/javatests/com/google/gerrit/acceptance/git/AbstractPushForReview.java b/javatests/com/google/gerrit/acceptance/git/AbstractPushForReview.java
index 398ed84..2acc3cd 100644
--- a/javatests/com/google/gerrit/acceptance/git/AbstractPushForReview.java
+++ b/javatests/com/google/gerrit/acceptance/git/AbstractPushForReview.java
@@ -1440,14 +1440,7 @@
 
   @Test
   public void pushSameCommitTwice() throws Exception {
-    try (ProjectConfigUpdate u = updateProject(project)) {
-      u.getConfig()
-          .getProject()
-          .setBooleanConfig(
-              BooleanProjectConfig.CREATE_NEW_CHANGE_FOR_ALL_NOT_IN_TARGET,
-              InheritableBoolean.TRUE);
-      u.save();
-    }
+    enableCreateNewChangeForAllNotInTarget();
 
     PushOneCommit push =
         pushFactory.create(
@@ -1469,14 +1462,7 @@
 
   @Test
   public void pushSameCommitTwiceWhenIndexFailed() throws Exception {
-    try (ProjectConfigUpdate u = updateProject(project)) {
-      u.getConfig()
-          .getProject()
-          .setBooleanConfig(
-              BooleanProjectConfig.CREATE_NEW_CHANGE_FOR_ALL_NOT_IN_TARGET,
-              InheritableBoolean.TRUE);
-      u.save();
-    }
+    enableCreateNewChangeForAllNotInTarget();
 
     PushOneCommit push =
         pushFactory.create(
diff --git a/javatests/com/google/gerrit/acceptance/rest/change/AbstractSubmit.java b/javatests/com/google/gerrit/acceptance/rest/change/AbstractSubmit.java
index 08a76e4..6622bae 100644
--- a/javatests/com/google/gerrit/acceptance/rest/change/AbstractSubmit.java
+++ b/javatests/com/google/gerrit/acceptance/rest/change/AbstractSubmit.java
@@ -59,7 +59,6 @@
 import com.google.gerrit.extensions.restapi.RestApiException;
 import com.google.gerrit.extensions.webui.UiAction;
 import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.BooleanProjectConfig;
 import com.google.gerrit.reviewdb.client.Branch;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.PatchSet;
@@ -574,14 +573,7 @@
     //  |
     // C0 -- Master
     //
-    try (ProjectConfigUpdate u = updateProject(project)) {
-      u.getConfig()
-          .getProject()
-          .setBooleanConfig(
-              BooleanProjectConfig.CREATE_NEW_CHANGE_FOR_ALL_NOT_IN_TARGET,
-              InheritableBoolean.TRUE);
-      u.save();
-    }
+    enableCreateNewChangeForAllNotInTarget();
 
     PushOneCommit push1 =
         pushFactory.create(
diff --git a/javatests/com/google/gerrit/acceptance/rest/change/CreateChangeIT.java b/javatests/com/google/gerrit/acceptance/rest/change/CreateChangeIT.java
index e388d6c..a83bf7b 100644
--- a/javatests/com/google/gerrit/acceptance/rest/change/CreateChangeIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/change/CreateChangeIT.java
@@ -49,6 +49,12 @@
 import com.google.gerrit.testing.TestTimeUtil;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.Callable;
+import java.util.concurrent.CyclicBarrier;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.PersonIdent;
 import org.eclipse.jgit.lib.Repository;
@@ -404,6 +410,42 @@
     assertCreateSucceeds(in);
   }
 
+  @Test
+  public void sha1sOfTwoNewChangesDiffer() throws Exception {
+    ChangeInput changeInput = newChangeInput(ChangeStatus.NEW);
+    ChangeInfo info1 = assertCreateSucceeds(changeInput);
+    ChangeInfo info2 = assertCreateSucceeds(changeInput);
+    assertThat(info1.currentRevision).isNotEqualTo(info2.currentRevision);
+    assertThat(info1.changeId).isNotEqualTo(info2.changeId);
+  }
+
+  @Test
+  public void sha1sOfTwoNewChangesDifferIfCreatedConcurrently() throws Exception {
+    ExecutorService executor = Executors.newFixedThreadPool(2);
+    try {
+      for (int i = 0; i < 10; i++) {
+        ChangeInput changeInput = newChangeInput(ChangeStatus.NEW);
+
+        CyclicBarrier sync = new CyclicBarrier(2);
+        Callable<ChangeInfo> createChange =
+            () -> {
+              setApiUser(admin);
+              sync.await();
+              return assertCreateSucceeds(changeInput);
+            };
+
+        Future<ChangeInfo> changeInfo1 = executor.submit(createChange);
+        Future<ChangeInfo> changeInfo2 = executor.submit(createChange);
+        assertThat(changeInfo1.get().currentRevision)
+            .isNotEqualTo(changeInfo2.get().currentRevision);
+        assertThat(changeInfo1.get().changeId).isNotEqualTo(changeInfo2.get().changeId);
+      }
+    } finally {
+      executor.shutdown();
+      executor.awaitTermination(5, TimeUnit.SECONDS);
+    }
+  }
+
   private ChangeInput newChangeInput(ChangeStatus status) {
     ChangeInput in = new ChangeInput();
     in.project = project.get();
diff --git a/javatests/com/google/gerrit/acceptance/server/notedb/OnlineNoteDbMigrationIT.java b/javatests/com/google/gerrit/acceptance/server/notedb/OnlineNoteDbMigrationIT.java
index c249973..b9ed0f3 100644
--- a/javatests/com/google/gerrit/acceptance/server/notedb/OnlineNoteDbMigrationIT.java
+++ b/javatests/com/google/gerrit/acceptance/server/notedb/OnlineNoteDbMigrationIT.java
@@ -35,6 +35,8 @@
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSortedSet;
+import com.google.common.io.MoreFiles;
+import com.google.common.io.RecursiveDeleteOption;
 import com.google.gerrit.acceptance.AbstractDaemonTest;
 import com.google.gerrit.acceptance.GerritConfig;
 import com.google.gerrit.acceptance.NoHttpd;
@@ -84,6 +86,7 @@
 import org.eclipse.jgit.lib.Ref;
 import org.eclipse.jgit.lib.RefUpdate;
 import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.lib.RepositoryCache.FileKey;
 import org.eclipse.jgit.storage.file.FileBasedConfig;
 import org.eclipse.jgit.util.FS;
 import org.junit.After;
@@ -511,6 +514,42 @@
   }
 
   @Test
+  public void fullMigrationOneChangeWithNoProject() throws Exception {
+    PushOneCommit.Result r1 = createChange();
+    Change.Id id1 = r1.getChange().getId();
+
+    Project.NameKey p2 = createProject("project2");
+    TestRepository<?> tr2 = cloneProject(p2, admin);
+    PushOneCommit.Result r2 = pushFactory.create(db, admin.getIdent(), tr2).to("refs/for/master");
+    Change.Id id2 = r2.getChange().getId();
+
+    // TODO(davido): Find an easier way to wipe out a repository from the file system.
+    MoreFiles.deleteRecursively(
+        FileKey.lenient(
+                sitePaths
+                    .resolve(cfg.getString("gerrit", null, "basePath"))
+                    .resolve(p2.get())
+                    .toFile(),
+                FS.DETECTED)
+            .getFile()
+            .toPath(),
+        RecursiveDeleteOption.ALLOW_INSECURE);
+
+    migrate(b -> b);
+    assertNotesMigrationState(NOTE_DB, false, false);
+
+    try (ReviewDb db = schemaFactory.open();
+        Repository repo = repoManager.openRepository(project)) {
+      assertThat(repo.exactRef(RefNames.changeMetaRef(id1))).isNotNull();
+      assertThat(db.changes().get(id1).getNoteDbState()).isEqualTo(NOTE_DB_PRIMARY_STATE);
+    }
+
+    // A change without project is so corrupt that it is completely skipped by the migration
+    // process.
+    assertThat(db.changes().get(id2).getNoteDbState()).isNull();
+  }
+
+  @Test
   public void fullMigrationMissingPatchSetRefs() throws Exception {
     PushOneCommit.Result r = createChange();
     Change.Id id = r.getChange().getId();
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 6d413b7..19839a8 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
@@ -50,6 +50,10 @@
           match: '([Bb]ug|[Ii]ssue)\\s*#?(\\d+)',
           link: 'https://bugs.chromium.org/p/gerrit/issues/detail?id=$2',
         },
+        prefixsameinlinkandpattern: {
+          match: '([Hh][Tt][Tt][Pp]example)\\s*#?(\\d+)',
+          link: 'https://bugs.chromium.org/p/gerrit/issues/detail?id=$2',
+        },
         changeid: {
           match: '(I[0-9a-f]{8,40})',
           link: '#/q/$1',
@@ -116,6 +120,18 @@
       assert.equal(linkEl.textContent, 'Bug 3650');
     });
 
+    test('Pattern with same prefix as link was correctly parsed', () => {
+      // Pattern starts with the same prefix (`http`) as the url.
+      element.content = 'httpexample 3650';
+
+      assert.equal(element.$.output.childNodes.length, 1);
+      const linkEl = element.$.output.childNodes[0];
+      const url = 'https://bugs.chromium.org/p/gerrit/issues/detail?id=3650';
+      assert.equal(linkEl.target, '_blank');
+      assert.equal(linkEl.href, url);
+      assert.equal(linkEl.textContent, 'httpexample 3650');
+    });
+
     test('Change-Id pattern was parsed and linked', () => {
       // "Change-Id:" pattern.
       const changeID = 'I11d6a37f5e9b5df0486f6c922d8836dfa780e03e';
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 23a71f9..027c632 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
@@ -312,14 +312,15 @@
         let result = match[0].replace(pattern,
             patterns[p].html || patterns[p].link);
 
-        let i;
-        // Skip portion of replacement string that is equal to original.
-        for (i = 0; i < result.length; i++) {
-          if (result[i] !== match[0][i]) { break; }
-        }
-        result = result.slice(i);
-
         if (patterns[p].html) {
+          let i;
+          // Skip portion of replacement string that is equal to original to
+          // allow overlapping patterns.
+          for (i = 0; i < result.length; i++) {
+            if (result[i] !== match[0][i]) { break; }
+          }
+          result = result.slice(i);
+
           this.addHTML(
               result,
               susbtrIndex + match.index + i,
@@ -329,8 +330,8 @@
           this.addLink(
               match[0],
               result,
-              susbtrIndex + match.index + i,
-              match[0].length - i,
+              susbtrIndex + match.index,
+              match[0].length,
               outputArray);
         } else {
           throw Error('linkconfig entry ' + p +
diff --git a/resources/com/google/gerrit/server/mime/mime-types.properties b/resources/com/google/gerrit/server/mime/mime-types.properties
index 7c94f32..84eef77 100644
--- a/resources/com/google/gerrit/server/mime/mime-types.properties
+++ b/resources/com/google/gerrit/server/mime/mime-types.properties
@@ -13,6 +13,7 @@
 bzl = text/x-python
 BUCK = text/x-python
 BUILD = text/x-python
+BUILD.bazel = text/x-python
 c = text/x-csrc
 cfg = text/x-ttcn-cfg
 cl = text/x-common-lisp
@@ -247,6 +248,7 @@
 vtl = text/velocity
 webidl = text/x-webidl
 wsdl = application/xml
+WORKSPACE = text/x-python
 xaml = application/xml
 xhtml = text/html
 xml = application/xml