Merge changes If84461dd,I602a7abf,I090c5e21,Ia0ae8115

* changes:
  Document ApprovalInfo class
  Document ActionInfo class
  Document AccountExternalIdInfo class
  Document fields in AccountInfo /  AccountDetailInfo
diff --git a/Documentation/intro-user.txt b/Documentation/intro-user.txt
index 6f55398..9dd58b8 100644
--- a/Documentation/intro-user.txt
+++ b/Documentation/intro-user.txt
@@ -453,6 +453,15 @@
 Abandoned changes can be link:user-review-ui.html#restore[restored] if
 later they are needed again.
 
+[[cherrypickof]]
+== Cherry-Pick changes of a Change
+
+When a change is created/updated using the 'cherry-pick' functionalty,
+the original change and patchset details are recorded in the Change's
+cherrypick field. This field cannot be set or updated by the user in
+any way. It is set automatically after the cherry-pick operation completes
+successfully.
+
 [[topics]]
 == Using Topics
 
diff --git a/Documentation/metrics.txt b/Documentation/metrics.txt
index b373129..236792b 100644
--- a/Documentation/metrics.txt
+++ b/Documentation/metrics.txt
@@ -141,10 +141,10 @@
 
 === NoteDb
 
-* `notedb/update_latency`: NoteDb update latency by table.
-* `notedb/stage_update_latency`: Latency for staging updates to NoteDb by table.
-* `notedb/read_latency`: NoteDb read latency by table.
-* `notedb/parse_latency`: NoteDb parse latency by table.
+* `notedb/update_latency`: NoteDb update latency for changes.
+* `notedb/stage_update_latency`: Latency for staging change updates to NoteDb.
+* `notedb/read_latency`: NoteDb read latency for changes.
+* `notedb/parse_latency`: NoteDb parse latency for changes.
 * `notedb/external_id_cache_load_count`: Total number of times the external ID
   cache loader was called.
 * `notedb/external_id_partial_read_latency`: Latency for generating a new external ID
diff --git a/Documentation/rest-api-changes.txt b/Documentation/rest-api-changes.txt
index 6cb111b..73ff085 100644
--- a/Documentation/rest-api-changes.txt
+++ b/Documentation/rest-api-changes.txt
@@ -73,7 +73,9 @@
 Queries changes visible to the caller. The
 link:user-search.html#_search_operators[query string] must be provided
 by the `q` parameter. The `n` parameter can be used to limit the
-returned results.
+returned results. The `no-limit` parameter can be used remove the default
+limit on queries and return all results. This might not be supported by
+all index backends.
 
 As result a list of link:#change-info[ChangeInfo] entries is returned.
 The change output is sorted by the last update time, most recently
@@ -369,11 +371,6 @@
   as link:#tracking-id-info[TrackingIdInfo].
 --
 
-[[no-limit]]
---
-* `NO-LIMIT`: Return all results
---
-
 .Request
 ----
   GET /changes/?q=97&o=CURRENT_REVISION&o=CURRENT_COMMIT&o=CURRENT_FILES&o=DOWNLOAD_COMMANDS HTTP/1.0
diff --git a/Documentation/rest-api-config.txt b/Documentation/rest-api-config.txt
index affe1ea..e9e3b94 100644
--- a/Documentation/rest-api-config.txt
+++ b/Documentation/rest-api-config.txt
@@ -1477,8 +1477,8 @@
 |`git_basic_auth_policy`      |optional|
 The link:config-gerrit.html#auth.gitBasicAuthPolicy[policy] to authenticate
 Git over HTTP and REST API requests when
-link:config-gerrit.html#auth.type[authentication type] is `LDAP`.
-Can be `HTTP`, `LDAP` or `HTTP_LDAP`.
+link:config-gerrit.html#auth.type[authentication type] is `LDAP`,
+`LDAP_BIND` or `OAUTH`. Can be `HTTP`, `LDAP`, `HTTP_LDAP` or `OAUTH`.
 |==========================================
 
 [[cache-info]]
diff --git a/Documentation/user-search.txt b/Documentation/user-search.txt
index aa5edf0..359c32a 100644
--- a/Documentation/user-search.txt
+++ b/Documentation/user-search.txt
@@ -229,6 +229,16 @@
 Changes whose link:intro-user.html#hashtags[hashtag] matches 'HASHTAG'.
 The match is case-insensitive.
 
+[[cherrypickof]]
+cherrypickof:'CHANGE[,PATCHSET]'::
++
+Changes which were created using the 'cherry-pick' functionality and
+whose source change number matches 'CHANGE' and source patchset number
+matches 'PATCHSET'. Note that 'PATCHSET' is optional. For example, a
+`cherrypickof:12345` matches all changes which were cherry-picked from
+change 12345 and `cherrypickof:12345,2` matches all changes which were
+cherry-picked from the 2nd patchset of change 12345.
+
 [[ref]]
 ref:'REF'::
 +
diff --git a/e2e-tests/load-tests/Dockerfile b/e2e-tests/load-tests/Dockerfile
new file mode 100644
index 0000000..ceae672
--- /dev/null
+++ b/e2e-tests/load-tests/Dockerfile
@@ -0,0 +1,32 @@
+FROM denvazh/gatling:3.2.1
+
+ARG gatling_git_version=1.0.9
+RUN apk add --no-cache maven
+RUN mvn dependency:get \
+        -DgroupId=com.gerritforge \
+        -DartifactId=gatling-git_2.12 \
+        -Dversion=$gatling_git_version \
+        -Dtype=pom
+RUN mvn dependency:copy-dependencies \
+        -f /root/.m2/repository/com/gerritforge/gatling-git_2.12/$gatling_git_version/gatling-git_2.12-$gatling_git_version.pom \
+        -DoutputDirectory=/opt/gatling/lib/
+RUN mvn dependency:get \
+        -Dartifact=com.gerritforge:gatling-git_2.12:$gatling_git_version:jar \
+        -Ddest=/opt/gatling/lib/gatling-git.jar
+
+ARG gatling_home=/home/gatling
+RUN addgroup -g 1000 -S appgroup && \
+        adduser -u 1000 -S gatling -G appgroup -h $gatling_home
+RUN cp -R /opt/gatling/* $gatling_home && \
+        chown -R gatling:appgroup $gatling_home
+
+WORKDIR $gatling_home
+USER gatling
+
+COPY ./src/test/scala/com/google/gerrit/scenarios $gatling_home/user-files/simulations
+COPY ./src/test/resources/application.conf $gatling_home/conf
+COPY ./src/test/resources/data $gatling_home/user-files/resources/data
+
+ENV GATLING_HOME=$gatling_home
+
+ENTRYPOINT ["/home/gatling/bin/gatling.sh"]
diff --git a/e2e-tests/load-tests/README.md b/e2e-tests/load-tests/README.md
new file mode 100644
index 0000000..534fde5
--- /dev/null
+++ b/e2e-tests/load-tests/README.md
@@ -0,0 +1,11 @@
+# How to build the Docker image
+
+```$shell
+docker build . -t e2e-tests
+```
+
+# How to run a test
+
+```$shell
+docker run -it e2e-tests -s com.google.gerrit.scenarios.ReplayRecordsFromFeederScenario
+```
diff --git a/java/com/google/gerrit/acceptance/UseLocalDisk.java b/java/com/google/gerrit/acceptance/UseLocalDisk.java
index e177bb4..192caa0 100644
--- a/java/com/google/gerrit/acceptance/UseLocalDisk.java
+++ b/java/com/google/gerrit/acceptance/UseLocalDisk.java
@@ -21,6 +21,15 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.Target;
 
+/**
+ * Annotation to mark tests that require a local disk for the execution.
+ *
+ * <p>Tests that do not have this annotation are executed in memory.
+ *
+ * <p>Using this annotation makes the execution of the test more expensive/slower. This is why it
+ * should only be used if the test requires a local disk (e.g. if the test triggers the Git garbage
+ * collection functionality which only works with a local disk).
+ */
 @Target({TYPE, METHOD})
 @Retention(RUNTIME)
 public @interface UseLocalDisk {}
diff --git a/java/com/google/gerrit/acceptance/UseSsh.java b/java/com/google/gerrit/acceptance/UseSsh.java
index 5509140..12a9977 100644
--- a/java/com/google/gerrit/acceptance/UseSsh.java
+++ b/java/com/google/gerrit/acceptance/UseSsh.java
@@ -21,6 +21,14 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.Target;
 
+/**
+ * Annotation to mark SSH tests.
+ *
+ * <p>When running tests the SSH functionality is disabled unless the {@link UseSsh} annotation is
+ * used.
+ *
+ * <p>SSH tests can be skipped when executing tests (see {@link com.google.gerrit.testing.SshMode}).
+ */
 @Target({TYPE, METHOD})
 @Retention(RUNTIME)
 public @interface UseSsh {}
diff --git a/java/com/google/gerrit/common/data/GroupDetail.java b/java/com/google/gerrit/common/data/GroupDetail.java
deleted file mode 100644
index 991d5df..0000000
--- a/java/com/google/gerrit/common/data/GroupDetail.java
+++ /dev/null
@@ -1,37 +0,0 @@
-// Copyright (C) 2008 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.common.data;
-
-import com.google.gerrit.entities.Account;
-import com.google.gerrit.entities.AccountGroup;
-import java.util.Set;
-
-public class GroupDetail {
-  private Set<Account.Id> members;
-  private Set<AccountGroup.UUID> includes;
-
-  public GroupDetail(Set<Account.Id> members, Set<AccountGroup.UUID> includes) {
-    this.members = members;
-    this.includes = includes;
-  }
-
-  public Set<Account.Id> getMembers() {
-    return members;
-  }
-
-  public Set<AccountGroup.UUID> getIncludes() {
-    return includes;
-  }
-}
diff --git a/java/com/google/gerrit/common/data/GroupInfo.java b/java/com/google/gerrit/common/data/GroupInfo.java
deleted file mode 100644
index 5c36104..0000000
--- a/java/com/google/gerrit/common/data/GroupInfo.java
+++ /dev/null
@@ -1,71 +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.common.data;
-
-import com.google.gerrit.entities.AccountGroup;
-
-/** Summary information about an {@link AccountGroup}, for simple tabular displays. */
-public class GroupInfo {
-  protected AccountGroup.UUID uuid;
-  protected String name;
-  protected String description;
-  protected String url;
-
-  protected GroupInfo() {}
-
-  /**
-   * Create an anonymous group info, when only the id is known.
-   *
-   * <p>This constructor should only be a last-ditch effort, when the usual group lookup has failed
-   * and a stale group id has been discovered in the data store.
-   */
-  public GroupInfo(AccountGroup.UUID uuid) {
-    this.uuid = uuid;
-  }
-
-  /**
-   * Create a group description from a real data store record.
-   *
-   * @param a the data store record holding the specific group details.
-   */
-  public GroupInfo(GroupDescription.Basic a) {
-    uuid = a.getGroupUUID();
-    name = a.getName();
-    url = a.getUrl();
-
-    if (a instanceof GroupDescription.Internal) {
-      description = ((GroupDescription.Internal) a).getDescription();
-    }
-  }
-
-  /** @return the unique local id of the group */
-  public AccountGroup.UUID getId() {
-    return uuid;
-  }
-
-  /** @return the name of the group; null if not supplied */
-  public String getName() {
-    return name;
-  }
-
-  /** @return the description of the group; null if not supplied */
-  public String getDescription() {
-    return description;
-  }
-
-  public String getUrl() {
-    return url;
-  }
-}
diff --git a/java/com/google/gerrit/entities/Change.java b/java/com/google/gerrit/entities/Change.java
index 04e97dc..c768094 100644
--- a/java/com/google/gerrit/entities/Change.java
+++ b/java/com/google/gerrit/entities/Change.java
@@ -531,6 +531,9 @@
   /** References a change that this change reverts. */
   @Nullable protected Id revertOf;
 
+  /** References the source change and patchset that this change was cherry-picked from. */
+  @Nullable protected PatchSet.Id cherryPickOf;
+
   protected Change() {}
 
   public Change(
@@ -567,6 +570,7 @@
     workInProgress = other.workInProgress;
     reviewStarted = other.reviewStarted;
     revertOf = other.revertOf;
+    cherryPickOf = other.cherryPickOf;
   }
 
   /** Legacy 32 bit integer identity for a change. */
@@ -760,6 +764,14 @@
     return this.revertOf;
   }
 
+  public PatchSet.Id getCherryPickOf() {
+    return cherryPickOf;
+  }
+
+  public void setCherryPickOf(@Nullable PatchSet.Id cherryPickOf) {
+    this.cherryPickOf = cherryPickOf;
+  }
+
   @Override
   public String toString() {
     return new StringBuilder(getClass().getSimpleName())
diff --git a/java/com/google/gerrit/entities/PatchSet.java b/java/com/google/gerrit/entities/PatchSet.java
index 8b93dbc..4a33bd7 100644
--- a/java/com/google/gerrit/entities/PatchSet.java
+++ b/java/com/google/gerrit/entities/PatchSet.java
@@ -124,13 +124,17 @@
       return id();
     }
 
+    public String getCommaSeparatedChangeAndPatchSetId() {
+      return changeId().toString() + ',' + id();
+    }
+
     public String toRefName() {
       return changeId().refPrefixBuilder().append(id()).toString();
     }
 
     @Override
     public final String toString() {
-      return changeId().toString() + ',' + id();
+      return getCommaSeparatedChangeAndPatchSetId();
     }
   }
 
diff --git a/java/com/google/gerrit/entities/converter/ChangeProtoConverter.java b/java/com/google/gerrit/entities/converter/ChangeProtoConverter.java
index 5b066ea..25e68f9 100644
--- a/java/com/google/gerrit/entities/converter/ChangeProtoConverter.java
+++ b/java/com/google/gerrit/entities/converter/ChangeProtoConverter.java
@@ -29,6 +29,8 @@
 
   private final ProtoConverter<Entities.Change_Id, Change.Id> changeIdConverter =
       ChangeIdProtoConverter.INSTANCE;
+  private final ProtoConverter<Entities.PatchSet_Id, PatchSet.Id> patchSetIdConverter =
+      PatchSetIdProtoConverter.INSTANCE;
   private final ProtoConverter<Entities.Change_Key, Change.Key> changeKeyConverter =
       ChangeKeyProtoConverter.INSTANCE;
   private final ProtoConverter<Entities.Account_Id, Account.Id> accountIdConverter =
@@ -78,6 +80,10 @@
     if (revertOf != null) {
       builder.setRevertOf(changeIdConverter.toProto(revertOf));
     }
+    PatchSet.Id cherryPickOf = change.getCherryPickOf();
+    if (cherryPickOf != null) {
+      builder.setCherryPickOf(patchSetIdConverter.toProto(cherryPickOf));
+    }
     return builder.build();
   }
 
@@ -118,6 +124,9 @@
     if (proto.hasRevertOf()) {
       change.setRevertOf(changeIdConverter.fromProto(proto.getRevertOf()));
     }
+    if (proto.hasCherryPickOf()) {
+      change.setCherryPickOf(patchSetIdConverter.fromProto(proto.getCherryPickOf()));
+    }
     return change;
   }
 
diff --git a/java/com/google/gerrit/extensions/common/AuthInfo.java b/java/com/google/gerrit/extensions/common/AuthInfo.java
index 79c2250..e2977ec 100644
--- a/java/com/google/gerrit/extensions/common/AuthInfo.java
+++ b/java/com/google/gerrit/extensions/common/AuthInfo.java
@@ -19,6 +19,7 @@
 import com.google.gerrit.extensions.client.GitBasicAuthPolicy;
 import java.util.List;
 
+/** API response containing values from the {@code auth} section of {@code gerrit.config}. */
 public class AuthInfo {
   public AuthType authType;
   public Boolean useContributorAgreements;
diff --git a/java/com/google/gerrit/extensions/common/ChangeConfigInfo.java b/java/com/google/gerrit/extensions/common/ChangeConfigInfo.java
index e8aeb40..67d0b0d9 100644
--- a/java/com/google/gerrit/extensions/common/ChangeConfigInfo.java
+++ b/java/com/google/gerrit/extensions/common/ChangeConfigInfo.java
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.extensions.common;
 
+/** API response containing values from the {@code change} section of {@code gerrit.config}. */
 public class ChangeConfigInfo {
   public Boolean allowBlame;
   public Boolean showAssigneeInChangesTable;
diff --git a/java/com/google/gerrit/extensions/common/ChangeIndexConfigInfo.java b/java/com/google/gerrit/extensions/common/ChangeIndexConfigInfo.java
index 7bca79e..0cce106 100644
--- a/java/com/google/gerrit/extensions/common/ChangeIndexConfigInfo.java
+++ b/java/com/google/gerrit/extensions/common/ChangeIndexConfigInfo.java
@@ -14,6 +14,9 @@
 
 package com.google.gerrit.extensions.common;
 
+/**
+ * API response containing values from the {@code index.change} section of {@code gerrit.config}.
+ */
 public class ChangeIndexConfigInfo {
   public Boolean indexMergeable;
 }
diff --git a/java/com/google/gerrit/extensions/common/ChangeInfo.java b/java/com/google/gerrit/extensions/common/ChangeInfo.java
index 0d7c5c7..3b3f2ad 100644
--- a/java/com/google/gerrit/extensions/common/ChangeInfo.java
+++ b/java/com/google/gerrit/extensions/common/ChangeInfo.java
@@ -53,6 +53,8 @@
   public Boolean hasReviewStarted;
   public Integer revertOf;
   public String submissionId;
+  public Integer cherryPickOfChange;
+  public Integer cherryPickOfPatchSet;
 
   public int _number;
 
diff --git a/java/com/google/gerrit/extensions/common/DownloadInfo.java b/java/com/google/gerrit/extensions/common/DownloadInfo.java
index 5ea5992..ea94e4d 100644
--- a/java/com/google/gerrit/extensions/common/DownloadInfo.java
+++ b/java/com/google/gerrit/extensions/common/DownloadInfo.java
@@ -17,6 +17,7 @@
 import java.util.List;
 import java.util.Map;
 
+/** API response containing values from the {@code download} section of {@code gerrit.config}. */
 public class DownloadInfo {
   public Map<String, DownloadSchemeInfo> schemes;
   public List<String> archives;
diff --git a/java/com/google/gerrit/extensions/common/GerritInfo.java b/java/com/google/gerrit/extensions/common/GerritInfo.java
index 5c462d9..2ae6703 100644
--- a/java/com/google/gerrit/extensions/common/GerritInfo.java
+++ b/java/com/google/gerrit/extensions/common/GerritInfo.java
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.extensions.common;
 
+/** API response containing values from the {@code gerrit} section of {@code gerrit.config}. */
 public class GerritInfo {
   public String allProjects;
   public String allUsers;
diff --git a/java/com/google/gerrit/extensions/common/IndexConfigInfo.java b/java/com/google/gerrit/extensions/common/IndexConfigInfo.java
index 084c53a..f3f510b 100644
--- a/java/com/google/gerrit/extensions/common/IndexConfigInfo.java
+++ b/java/com/google/gerrit/extensions/common/IndexConfigInfo.java
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.extensions.common;
 
+/** API response containing values from the {@code index} section of {@code gerrit.config}. */
 public class IndexConfigInfo {
   public ChangeIndexConfigInfo change;
 }
diff --git a/java/com/google/gerrit/extensions/common/ReceiveInfo.java b/java/com/google/gerrit/extensions/common/ReceiveInfo.java
index 9fcd92b..23e45ae 100644
--- a/java/com/google/gerrit/extensions/common/ReceiveInfo.java
+++ b/java/com/google/gerrit/extensions/common/ReceiveInfo.java
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.extensions.common;
 
+/** API response containing values from the {@code receive} section of {@code gerrit.config}. */
 public class ReceiveInfo {
   public Boolean enableSignedPush;
 }
diff --git a/java/com/google/gerrit/extensions/common/ServerInfo.java b/java/com/google/gerrit/extensions/common/ServerInfo.java
index 9cf1ec1..555c363 100644
--- a/java/com/google/gerrit/extensions/common/ServerInfo.java
+++ b/java/com/google/gerrit/extensions/common/ServerInfo.java
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.extensions.common;
 
+/** API response containing values from {@code gerrit.config} as nested objects. */
 public class ServerInfo {
   public AccountsInfo accounts;
   public AuthInfo auth;
diff --git a/java/com/google/gerrit/extensions/common/SshdInfo.java b/java/com/google/gerrit/extensions/common/SshdInfo.java
index fb9cb16..7242f98 100644
--- a/java/com/google/gerrit/extensions/common/SshdInfo.java
+++ b/java/com/google/gerrit/extensions/common/SshdInfo.java
@@ -14,4 +14,10 @@
 
 package com.google.gerrit.extensions.common;
 
+/**
+ * API response containing values from the {@code sshd} section of {@code gerrit.config}.
+ *
+ * <p>This is currently empty, but is a class (rather than boolean) for consistency. If we ever
+ * export SSH configuration, more fields will be added here.
+ */
 public class SshdInfo {}
diff --git a/java/com/google/gerrit/extensions/common/SuggestInfo.java b/java/com/google/gerrit/extensions/common/SuggestInfo.java
index 91ca547..8c8c42f 100644
--- a/java/com/google/gerrit/extensions/common/SuggestInfo.java
+++ b/java/com/google/gerrit/extensions/common/SuggestInfo.java
@@ -14,6 +14,9 @@
 
 package com.google.gerrit.extensions.common;
 
+/** API response containing values from the {@code suggest} section of {@code gerrit.config}. */
 public class SuggestInfo {
+
+  /** Number of characters after which we provide suggestions for group member completion. */
   public int from;
 }
diff --git a/java/com/google/gerrit/extensions/common/UserConfigInfo.java b/java/com/google/gerrit/extensions/common/UserConfigInfo.java
index ec03dd0..3d8e851 100644
--- a/java/com/google/gerrit/extensions/common/UserConfigInfo.java
+++ b/java/com/google/gerrit/extensions/common/UserConfigInfo.java
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.extensions.common;
 
+/** API response containing values from the {@code user} section of {@code gerrit.config}. */
 public class UserConfigInfo {
   public String anonymousCowardName;
 }
diff --git a/java/com/google/gerrit/httpd/restapi/RestApiServlet.java b/java/com/google/gerrit/httpd/restapi/RestApiServlet.java
index de39472..51a6191 100644
--- a/java/com/google/gerrit/httpd/restapi/RestApiServlet.java
+++ b/java/com/google/gerrit/httpd/restapi/RestApiServlet.java
@@ -98,7 +98,6 @@
 import com.google.gerrit.extensions.restapi.RestView;
 import com.google.gerrit.extensions.restapi.TopLevelResource;
 import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
-import com.google.gerrit.git.LockFailureException;
 import com.google.gerrit.httpd.WebSession;
 import com.google.gerrit.httpd.restapi.ParameterParser.QueryParams;
 import com.google.gerrit.json.OutputFormat;
@@ -106,6 +105,7 @@
 import com.google.gerrit.server.AnonymousUser;
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.ExceptionHook;
+import com.google.gerrit.server.ExceptionHookImpl;
 import com.google.gerrit.server.OptionUtil;
 import com.google.gerrit.server.RequestInfo;
 import com.google.gerrit.server.RequestListener;
@@ -132,7 +132,6 @@
 import com.google.gerrit.server.update.RetryableAction;
 import com.google.gerrit.server.update.RetryableAction.Action;
 import com.google.gerrit.server.update.RetryableAction.ActionType;
-import com.google.gerrit.server.update.UpdateException;
 import com.google.gerrit.server.util.time.TimeUtil;
 import com.google.gerrit.util.http.CacheHeaders;
 import com.google.gerrit.util.http.RequestUtil;
@@ -662,16 +661,6 @@
         logger.atSevere().withCause(e).log("Error in %s %s", req.getMethod(), uriForLogging(req));
         responseBytes =
             replyError(req, res, status = SC_NOT_IMPLEMENTED, messageOr(e, "Not Implemented"), e);
-      } catch (UpdateException e) {
-        cause = Optional.of(e);
-        Throwable t = e.getCause();
-        if (t instanceof LockFailureException) {
-          logger.atSevere().withCause(t).log("Error in %s %s", req.getMethod(), uriForLogging(req));
-          responseBytes = replyError(req, res, status = SC_SERVICE_UNAVAILABLE, "Lock failure", e);
-        } else {
-          status = SC_INTERNAL_SERVER_ERROR;
-          responseBytes = handleException(traceContext, e, req, res);
-        }
       } catch (QuotaException e) {
         cause = Optional.of(e);
         responseBytes =
@@ -684,6 +673,13 @@
                 e);
       } catch (Exception e) {
         cause = Optional.of(e);
+
+        if (ExceptionHookImpl.isLockFailure(e)) {
+          logger.atSevere().withCause(e.getCause()).log(
+              "Error in %s %s", req.getMethod(), uriForLogging(req));
+          responseBytes = replyError(req, res, status = SC_SERVICE_UNAVAILABLE, "Lock failure", e);
+        }
+
         status = SC_INTERNAL_SERVER_ERROR;
         responseBytes = handleException(traceContext, e, req, res);
       } finally {
diff --git a/java/com/google/gerrit/index/project/IndexedProjectQuery.java b/java/com/google/gerrit/index/project/IndexedProjectQuery.java
index cdfeabd..5fc74ca 100644
--- a/java/com/google/gerrit/index/project/IndexedProjectQuery.java
+++ b/java/com/google/gerrit/index/project/IndexedProjectQuery.java
@@ -22,6 +22,10 @@
 import com.google.gerrit.index.query.Predicate;
 import com.google.gerrit.index.query.QueryParseException;
 
+/**
+ * Wrapper around {@link Predicate}s that are returned by the {@link
+ * com.google.gerrit.index.IndexRewriter}. See {@link IndexedQuery}.
+ */
 public class IndexedProjectQuery extends IndexedQuery<Project.NameKey, ProjectData>
     implements DataSource<ProjectData> {
 
diff --git a/java/com/google/gerrit/index/project/ProjectData.java b/java/com/google/gerrit/index/project/ProjectData.java
index 2bf9a4b5..c70b823 100644
--- a/java/com/google/gerrit/index/project/ProjectData.java
+++ b/java/com/google/gerrit/index/project/ProjectData.java
@@ -23,6 +23,11 @@
 import java.util.List;
 import java.util.Optional;
 
+/**
+ * Representation of a Gerrit project in the project index.
+ *
+ * <p>Includes information about all parent projects.
+ */
 public class ProjectData {
   private final Project project;
   private final Optional<ProjectData> parent;
diff --git a/java/com/google/gerrit/index/project/ProjectIndex.java b/java/com/google/gerrit/index/project/ProjectIndex.java
index 3e99d55..b2ddaff 100644
--- a/java/com/google/gerrit/index/project/ProjectIndex.java
+++ b/java/com/google/gerrit/index/project/ProjectIndex.java
@@ -19,6 +19,10 @@
 import com.google.gerrit.index.IndexDefinition;
 import com.google.gerrit.index.query.Predicate;
 
+/**
+ * Index for Gerrit projects (repositories). This class is mainly used for typing the generic parent
+ * class that contains actual implementations.
+ */
 public interface ProjectIndex extends Index<Project.NameKey, ProjectData> {
 
   public interface Factory
diff --git a/java/com/google/gerrit/index/project/ProjectIndexCollection.java b/java/com/google/gerrit/index/project/ProjectIndexCollection.java
index 30227a3..7ff23c5 100644
--- a/java/com/google/gerrit/index/project/ProjectIndexCollection.java
+++ b/java/com/google/gerrit/index/project/ProjectIndexCollection.java
@@ -19,6 +19,7 @@
 import com.google.gerrit.index.IndexCollection;
 import com.google.inject.Singleton;
 
+/** Collection of active project indices. See {@link IndexCollection} for details on collections. */
 @Singleton
 public class ProjectIndexCollection
     extends IndexCollection<Project.NameKey, ProjectData, ProjectIndex> {
diff --git a/java/com/google/gerrit/index/project/ProjectIndexRewriter.java b/java/com/google/gerrit/index/project/ProjectIndexRewriter.java
index 9e2bbdc..230161b 100644
--- a/java/com/google/gerrit/index/project/ProjectIndexRewriter.java
+++ b/java/com/google/gerrit/index/project/ProjectIndexRewriter.java
@@ -23,6 +23,7 @@
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
 
+/** Rewriter for the project index. See {@link IndexRewriter} for details. */
 @Singleton
 public class ProjectIndexRewriter implements IndexRewriter<ProjectData> {
   private final ProjectIndexCollection indexes;
diff --git a/java/com/google/gerrit/index/project/ProjectIndexer.java b/java/com/google/gerrit/index/project/ProjectIndexer.java
index bd5efeb2..34e8075 100644
--- a/java/com/google/gerrit/index/project/ProjectIndexer.java
+++ b/java/com/google/gerrit/index/project/ProjectIndexer.java
@@ -16,6 +16,7 @@
 
 import com.google.gerrit.entities.Project;
 
+/** Interface for indexing a Gerrit project. */
 public interface ProjectIndexer {
 
   /**
diff --git a/java/com/google/gerrit/index/project/ProjectPredicate.java b/java/com/google/gerrit/index/project/ProjectPredicate.java
index 4926eef..11875ef 100644
--- a/java/com/google/gerrit/index/project/ProjectPredicate.java
+++ b/java/com/google/gerrit/index/project/ProjectPredicate.java
@@ -17,6 +17,7 @@
 import com.google.gerrit.index.FieldDef;
 import com.google.gerrit.index.query.IndexPredicate;
 
+/** Predicate that is mapped to a field in the project index. */
 public class ProjectPredicate extends IndexPredicate<ProjectData> {
   public ProjectPredicate(FieldDef<ProjectData, ?> def, String value) {
     super(def, value);
diff --git a/java/com/google/gerrit/index/query/IndexPredicate.java b/java/com/google/gerrit/index/query/IndexPredicate.java
index 7811a32..aac6682 100644
--- a/java/com/google/gerrit/index/query/IndexPredicate.java
+++ b/java/com/google/gerrit/index/query/IndexPredicate.java
@@ -17,7 +17,7 @@
 import com.google.gerrit.index.FieldDef;
 import com.google.gerrit.index.FieldType;
 
-/** Index-aware predicate that includes a field type annotation. */
+/** Predicate that is mapped to a field in the index. */
 public abstract class IndexPredicate<I> extends OperatorPredicate<I> {
   private final FieldDef<I, ?> def;
 
diff --git a/java/com/google/gerrit/server/ExceptionHookImpl.java b/java/com/google/gerrit/server/ExceptionHookImpl.java
index eabb914..0cc2545 100644
--- a/java/com/google/gerrit/server/ExceptionHookImpl.java
+++ b/java/com/google/gerrit/server/ExceptionHookImpl.java
@@ -51,7 +51,7 @@
     return Optional.empty();
   }
 
-  private static boolean isLockFailure(Throwable throwable) {
+  public static boolean isLockFailure(Throwable throwable) {
     return isMatching(throwable, t -> t instanceof LockFailureException);
   }
 
diff --git a/java/com/google/gerrit/server/account/GroupControl.java b/java/com/google/gerrit/server/account/GroupControl.java
index 36e68c4..64fd7c6 100644
--- a/java/com/google/gerrit/server/account/GroupControl.java
+++ b/java/com/google/gerrit/server/account/GroupControl.java
@@ -160,7 +160,7 @@
       return isOwner;
     }
 
-    // Keep this logic in sync with VisibleRefFilter#isOwner(...).
+    // Keep this logic in sync with DefaultRefFilter#isGroupOwner(...).
     if (group instanceof GroupDescription.Internal) {
       AccountGroup.UUID ownerUUID = ((GroupDescription.Internal) group).getOwnerGroupUUID();
       if (getUser().getEffectiveGroups().contains(ownerUUID)) {
diff --git a/java/com/google/gerrit/server/change/ActionJson.java b/java/com/google/gerrit/server/change/ActionJson.java
index 031c1f2..e87cf70 100644
--- a/java/com/google/gerrit/server/change/ActionJson.java
+++ b/java/com/google/gerrit/server/change/ActionJson.java
@@ -144,6 +144,8 @@
     copy.unresolvedCommentCount = changeInfo.unresolvedCommentCount;
     copy.workInProgress = changeInfo.workInProgress;
     copy.id = changeInfo.id;
+    copy.cherryPickOfChange = changeInfo.cherryPickOfChange;
+    copy.cherryPickOfPatchSet = changeInfo.cherryPickOfPatchSet;
     return copy;
   }
 
diff --git a/java/com/google/gerrit/server/change/ChangeInserter.java b/java/com/google/gerrit/server/change/ChangeInserter.java
index d1c27d5..770f36c 100644
--- a/java/com/google/gerrit/server/change/ChangeInserter.java
+++ b/java/com/google/gerrit/server/change/ChangeInserter.java
@@ -111,6 +111,7 @@
   private final String refName;
 
   // Fields exposed as setters.
+  private PatchSet.Id cherryPickOf;
   private Change.Status status;
   private String topic;
   private String message;
@@ -189,6 +190,7 @@
             ctx.getWhen());
     change.setStatus(MoreObjects.firstNonNull(status, Change.Status.NEW));
     change.setTopic(topic);
+    change.setCherryPickOf(cherryPickOf);
     change.setPrivate(isPrivate);
     change.setWorkInProgress(workInProgress);
     change.setReviewStarted(!workInProgress);
@@ -227,6 +229,11 @@
     return this;
   }
 
+  public ChangeInserter setCherryPickOf(PatchSet.Id cherryPickOf) {
+    this.cherryPickOf = cherryPickOf;
+    return this;
+  }
+
   public ChangeInserter setMessage(String message) {
     this.message = message;
     return this;
@@ -378,6 +385,9 @@
     if (revertOf != null) {
       update.setRevertOf(revertOf.get());
     }
+    if (cherryPickOf != null) {
+      update.setCherryPickOf(cherryPickOf.getCommaSeparatedChangeAndPatchSetId());
+    }
 
     List<String> newGroups = groups;
     if (newGroups.isEmpty()) {
@@ -556,10 +566,16 @@
             reviewerInputs.stream(),
             Streams.stream(
                 newAddReviewerInputFromCommitIdentity(
-                    change, patchSetInfo.getAuthor().getAccount(), NotifyHandling.NONE)),
+                    change,
+                    patchSetInfo.getCommitId(),
+                    patchSetInfo.getAuthor().getAccount(),
+                    NotifyHandling.NONE)),
             Streams.stream(
                 newAddReviewerInputFromCommitIdentity(
-                    change, patchSetInfo.getCommitter().getAccount(), NotifyHandling.NONE)))
+                    change,
+                    patchSetInfo.getCommitId(),
+                    patchSetInfo.getCommitter().getAccount(),
+                    NotifyHandling.NONE)))
         .collect(toImmutableList());
   }
 }
diff --git a/java/com/google/gerrit/server/change/ChangeJson.java b/java/com/google/gerrit/server/change/ChangeJson.java
index 21ee28a..974cb47 100644
--- a/java/com/google/gerrit/server/change/ChangeJson.java
+++ b/java/com/google/gerrit/server/change/ChangeJson.java
@@ -588,6 +588,12 @@
     }
     out.revertOf = cd.change().getRevertOf() != null ? cd.change().getRevertOf().get() : null;
     out.submissionId = cd.change().getSubmissionId();
+    out.cherryPickOfChange =
+        cd.change().getCherryPickOf() != null
+            ? cd.change().getCherryPickOf().changeId().get()
+            : null;
+    out.cherryPickOfPatchSet =
+        cd.change().getCherryPickOf() != null ? cd.change().getCherryPickOf().get() : null;
 
     if (has(REVIEWER_UPDATES)) {
       out.reviewerUpdates = reviewerUpdates(cd);
diff --git a/java/com/google/gerrit/server/change/ReviewerAdder.java b/java/com/google/gerrit/server/change/ReviewerAdder.java
index ba6ba21..4f66492 100644
--- a/java/com/google/gerrit/server/change/ReviewerAdder.java
+++ b/java/com/google/gerrit/server/change/ReviewerAdder.java
@@ -81,6 +81,7 @@
 import java.util.function.Function;
 import org.eclipse.jgit.errors.ConfigInvalidException;
 import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.ObjectId;
 
 public class ReviewerAdder {
   private static final FluentLogger logger = FluentLogger.forEnclosingClass();
@@ -125,12 +126,16 @@
   }
 
   public static Optional<InternalAddReviewerInput> newAddReviewerInputFromCommitIdentity(
-      Change change, @Nullable Account.Id accountId, NotifyHandling notify) {
+      Change change, ObjectId commitId, @Nullable Account.Id accountId, NotifyHandling notify) {
     if (accountId == null || accountId.equals(change.getOwner())) {
       // If git ident couldn't be resolved to a user, or if it's not forged, do nothing.
       return Optional.empty();
     }
 
+    logger.atFine().log(
+        "Adding account %d from author/committer identity of commit %s as reviewer to change %d",
+        accountId.get(), commitId.name(), change.getChangeId());
+
     InternalAddReviewerInput in = new InternalAddReviewerInput();
     in.reviewer = accountId.toString();
     in.state = REVIEWER;
diff --git a/java/com/google/gerrit/server/change/SetCherryPickOp.java b/java/com/google/gerrit/server/change/SetCherryPickOp.java
new file mode 100644
index 0000000..d271923
--- /dev/null
+++ b/java/com/google/gerrit/server/change/SetCherryPickOp.java
@@ -0,0 +1,49 @@
+// Copyright (C) 2019 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.change;
+
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.PatchSet;
+import com.google.gerrit.extensions.restapi.RestApiException;
+import com.google.gerrit.server.notedb.ChangeUpdate;
+import com.google.gerrit.server.update.BatchUpdateOp;
+import com.google.gerrit.server.update.ChangeContext;
+import com.google.inject.Inject;
+import com.google.inject.assistedinject.Assisted;
+
+public class SetCherryPickOp implements BatchUpdateOp {
+  public interface Factory {
+    SetCherryPickOp create(PatchSet.Id cherryPickOf);
+  }
+
+  private final PatchSet.Id newCherryPickOf;
+
+  @Inject
+  SetCherryPickOp(@Assisted PatchSet.Id newCherryPickOf) {
+    this.newCherryPickOf = newCherryPickOf;
+  }
+
+  @Override
+  public boolean updateChange(ChangeContext ctx) throws RestApiException {
+    Change change = ctx.getChange();
+    if (newCherryPickOf.equals(change.getCherryPickOf())) {
+      return false;
+    }
+
+    ChangeUpdate update = ctx.getUpdate(change.currentPatchSetId());
+    update.setCherryPickOf(newCherryPickOf.getCommaSeparatedChangeAndPatchSetId());
+    return true;
+  }
+}
diff --git a/java/com/google/gerrit/server/data/ChangeAttribute.java b/java/com/google/gerrit/server/data/ChangeAttribute.java
index a6da2b9..bc51e2a 100644
--- a/java/com/google/gerrit/server/data/ChangeAttribute.java
+++ b/java/com/google/gerrit/server/data/ChangeAttribute.java
@@ -31,6 +31,8 @@
   public String url;
   public String commitMessage;
   public List<String> hashtags;
+  public Integer cherryPickOfChange;
+  public Integer cherryPickOfPatchSet;
 
   public Long createdOn;
   public Long lastUpdated;
diff --git a/java/com/google/gerrit/server/events/EventFactory.java b/java/com/google/gerrit/server/events/EventFactory.java
index 3f22d7f..9422c18 100644
--- a/java/com/google/gerrit/server/events/EventFactory.java
+++ b/java/com/google/gerrit/server/events/EventFactory.java
@@ -144,6 +144,10 @@
     a.createdOn = change.getCreatedOn().getTime() / 1000L;
     a.wip = change.isWorkInProgress() ? true : null;
     a.isPrivate = change.isPrivate() ? true : null;
+    a.cherryPickOfChange =
+        change.getCherryPickOf() != null ? change.getCherryPickOf().changeId().get() : null;
+    a.cherryPickOfPatchSet =
+        change.getCherryPickOf() != null ? change.getCherryPickOf().get() : null;
     return a;
   }
 
diff --git a/java/com/google/gerrit/server/git/receive/ReplaceOp.java b/java/com/google/gerrit/server/git/receive/ReplaceOp.java
index 24154d60..62114b0 100644
--- a/java/com/google/gerrit/server/git/receive/ReplaceOp.java
+++ b/java/com/google/gerrit/server/git/receive/ReplaceOp.java
@@ -368,10 +368,16 @@
         Streams.concat(
             Streams.stream(
                 newAddReviewerInputFromCommitIdentity(
-                    change, psInfo.getAuthor().getAccount(), NotifyHandling.NONE)),
+                    change,
+                    psInfo.getCommitId(),
+                    psInfo.getAuthor().getAccount(),
+                    NotifyHandling.NONE)),
             Streams.stream(
                 newAddReviewerInputFromCommitIdentity(
-                    change, psInfo.getCommitter().getAccount(), NotifyHandling.NONE)));
+                    change,
+                    psInfo.getCommitId(),
+                    psInfo.getCommitter().getAccount(),
+                    NotifyHandling.NONE)));
     if (magicBranch != null) {
       inputs =
           Streams.concat(
diff --git a/java/com/google/gerrit/server/git/validators/CommitValidators.java b/java/com/google/gerrit/server/git/validators/CommitValidators.java
index e8f6096..90d6b66 100644
--- a/java/com/google/gerrit/server/git/validators/CommitValidators.java
+++ b/java/com/google/gerrit/server/git/validators/CommitValidators.java
@@ -44,6 +44,11 @@
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.git.ValidationError;
 import com.google.gerrit.server.git.validators.ValidationMessage.Type;
+import com.google.gerrit.server.patch.DiffSummary;
+import com.google.gerrit.server.patch.DiffSummaryKey;
+import com.google.gerrit.server.patch.PatchListCache;
+import com.google.gerrit.server.patch.PatchListKey;
+import com.google.gerrit.server.patch.PatchListNotAvailableException;
 import com.google.gerrit.server.permissions.PermissionBackend;
 import com.google.gerrit.server.permissions.PermissionBackendException;
 import com.google.gerrit.server.permissions.RefPermission;
@@ -76,7 +81,8 @@
 import org.eclipse.jgit.util.SystemReader;
 
 /**
- * Represents a list of CommitValidationListeners to run for a push to one branch of one project.
+ * Represents a list of {@link CommitValidationListener}s to run for a push to one branch of one
+ * project.
  */
 public class CommitValidators {
   private static final FluentLogger logger = FluentLogger.forEnclosingClass();
@@ -94,15 +100,16 @@
     private final AllProjectsName allProjects;
     private final ExternalIdsConsistencyChecker externalIdsConsistencyChecker;
     private final AccountValidator accountValidator;
-    private final String installCommitMsgHookCommand;
     private final ProjectCache projectCache;
     private final ProjectConfig.Factory projectConfigFactory;
+    private final PatchListCache patchListCache;
+    private final Config config;
 
     @Inject
     Factory(
         @GerritPersonIdent PersonIdent gerritIdent,
         DynamicItem<UrlFormatter> urlFormatter,
-        @GerritServerConfig Config cfg,
+        @GerritServerConfig Config config,
         PluginSetContext<CommitValidationListener> pluginValidators,
         GitRepositoryManager repoManager,
         AllUsersName allUsers,
@@ -110,19 +117,20 @@
         ExternalIdsConsistencyChecker externalIdsConsistencyChecker,
         AccountValidator accountValidator,
         ProjectCache projectCache,
-        ProjectConfig.Factory projectConfigFactory) {
+        ProjectConfig.Factory projectConfigFactory,
+        PatchListCache patchListCache) {
       this.gerritIdent = gerritIdent;
       this.urlFormatter = urlFormatter;
+      this.config = config;
       this.pluginValidators = pluginValidators;
       this.repoManager = repoManager;
       this.allUsers = allUsers;
       this.allProjects = allProjects;
       this.externalIdsConsistencyChecker = externalIdsConsistencyChecker;
       this.accountValidator = accountValidator;
-      this.installCommitMsgHookCommand =
-          cfg != null ? cfg.getString("gerrit", null, "installCommitMsgHookCommand") : null;
       this.projectCache = projectCache;
       this.projectConfigFactory = projectConfigFactory;
+      this.patchListCache = patchListCache;
     }
 
     public CommitValidators forReceiveCommits(
@@ -146,12 +154,7 @@
               new CommitterUploaderValidator(user, perm, urlFormatter.get()),
               new SignedOffByValidator(user, perm, projectState),
               new ChangeIdValidator(
-                  projectState,
-                  user,
-                  urlFormatter.get(),
-                  installCommitMsgHookCommand,
-                  sshInfo,
-                  change),
+                  projectState, user, urlFormatter.get(), config, sshInfo, change),
               new ConfigValidator(projectConfigFactory, branch, user, rw, allUsers, allProjects),
               new BannedCommitsValidator(rejectCommits),
               new PluginCommitValidationListener(pluginValidators, skipValidation),
@@ -176,14 +179,10 @@
               new ProjectStateValidationListener(projectState),
               new AmendedGerritMergeCommitValidationListener(perm, gerritIdent),
               new AuthorUploaderValidator(user, perm, urlFormatter.get()),
+              new FileCountValidator(patchListCache, config),
               new SignedOffByValidator(user, perm, projectCache.checkedGet(branch.project())),
               new ChangeIdValidator(
-                  projectState,
-                  user,
-                  urlFormatter.get(),
-                  installCommitMsgHookCommand,
-                  sshInfo,
-                  change),
+                  projectState, user, urlFormatter.get(), config, sshInfo, change),
               new ConfigValidator(projectConfigFactory, branch, user, rw, allUsers, allProjects),
               new PluginCommitValidationListener(pluginValidators),
               new ExternalIdUpdateListener(allUsers, externalIdsConsistencyChecker),
@@ -268,14 +267,14 @@
         ProjectState projectState,
         IdentifiedUser user,
         UrlFormatter urlFormatter,
-        String installCommitMsgHookCommand,
+        Config config,
         SshInfo sshInfo,
         Change change) {
       this.projectState = projectState;
-      this.urlFormatter = urlFormatter;
-      this.installCommitMsgHookCommand = installCommitMsgHookCommand;
-      this.sshInfo = sshInfo;
       this.user = user;
+      this.urlFormatter = urlFormatter;
+      installCommitMsgHookCommand = config.getString("gerrit", null, "installCommitMsgHookCommand");
+      this.sshInfo = sshInfo;
       this.change = change;
     }
 
@@ -387,6 +386,40 @@
     }
   }
 
+  /** Limits the number of files per change. */
+  private static class FileCountValidator implements CommitValidationListener {
+
+    private final PatchListCache patchListCache;
+    private final int maxFileCount;
+
+    FileCountValidator(PatchListCache patchListCache, Config config) {
+      this.patchListCache = patchListCache;
+      maxFileCount = config.getInt("change", null, "maxFiles", 50_000);
+    }
+
+    @Override
+    public List<CommitValidationMessage> onCommitReceived(CommitReceivedEvent receiveEvent)
+        throws CommitValidationException {
+      PatchListKey patchListKey =
+          PatchListKey.againstBase(
+              receiveEvent.commit.getId(), receiveEvent.commit.getParentCount());
+      DiffSummaryKey diffSummaryKey = DiffSummaryKey.fromPatchListKey(patchListKey);
+      try {
+        DiffSummary diffSummary =
+            patchListCache.getDiffSummary(diffSummaryKey, receiveEvent.project.getNameKey());
+        if (diffSummary.getPaths().size() > maxFileCount) {
+          throw new CommitValidationException(
+              String.format(
+                  "Exceeding maximum number of files per change (%d > %d)",
+                  diffSummary.getPaths().size(), maxFileCount));
+        }
+      } catch (PatchListNotAvailableException e) {
+        logger.atWarning().withCause(e).log("Failed to validate file count");
+      }
+      return Collections.emptyList();
+    }
+  }
+
   /** If this is the special project configuration branch, validate the config. */
   public static class ConfigValidator implements CommitValidationListener {
     private final ProjectConfig.Factory projectConfigFactory;
diff --git a/java/com/google/gerrit/server/index/account/IndexedAccountQuery.java b/java/com/google/gerrit/server/index/account/IndexedAccountQuery.java
index 9e6db44..d8ecbd9 100644
--- a/java/com/google/gerrit/server/index/account/IndexedAccountQuery.java
+++ b/java/com/google/gerrit/server/index/account/IndexedAccountQuery.java
@@ -27,7 +27,7 @@
 import com.google.gerrit.server.account.AccountState;
 
 /**
- * Wrapper around {@link Predicate}s that is returned by the {@link
+ * Wrapper around {@link Predicate}s that are returned by the {@link
  * com.google.gerrit.index.IndexRewriter}. See {@link IndexedQuery}.
  */
 public class IndexedAccountQuery extends IndexedQuery<Account.Id, AccountState>
diff --git a/java/com/google/gerrit/server/index/change/ChangeField.java b/java/com/google/gerrit/server/index/change/ChangeField.java
index 07e9b9e4..d3a0065 100644
--- a/java/com/google/gerrit/server/index/change/ChangeField.java
+++ b/java/com/google/gerrit/server/index/change/ChangeField.java
@@ -269,6 +269,24 @@
   public static final FieldDef<ChangeData, Integer> OWNER =
       integer(ChangeQueryBuilder.FIELD_OWNER).build(changeGetter(c -> c.getOwner().get()));
 
+  /** References the source change number that this change was cherry-picked from. */
+  public static final FieldDef<ChangeData, Integer> CHERRY_PICK_OF_CHANGE =
+      integer(ChangeQueryBuilder.FIELD_CHERRY_PICK_OF_CHANGE)
+          .build(
+              cd ->
+                  cd.change().getCherryPickOf() != null
+                      ? cd.change().getCherryPickOf().changeId().get()
+                      : null);
+
+  /** References the source change patch-set that this change was cherry-picked from. */
+  public static final FieldDef<ChangeData, Integer> CHERRY_PICK_OF_PATCHSET =
+      integer(ChangeQueryBuilder.FIELD_CHERRY_PICK_OF_PATCHSET)
+          .build(
+              cd ->
+                  cd.change().getCherryPickOf() != null
+                      ? cd.change().getCherryPickOf().get()
+                      : null);
+
   /** The user assigned to the change. */
   public static final FieldDef<ChangeData, Integer> ASSIGNEE =
       integer(ChangeQueryBuilder.FIELD_ASSIGNEE)
diff --git a/java/com/google/gerrit/server/index/change/ChangeSchemaDefinitions.java b/java/com/google/gerrit/server/index/change/ChangeSchemaDefinitions.java
index 55fe11d..6d6d4b8 100644
--- a/java/com/google/gerrit/server/index/change/ChangeSchemaDefinitions.java
+++ b/java/com/google/gerrit/server/index/change/ChangeSchemaDefinitions.java
@@ -91,6 +91,7 @@
   // New numeric types: use dimensional points using the k-d tree geo-spatial data structure
   // to offer fast single- and multi-dimensional numeric range. As the consequense, integer
   // document id type is replaced with string document id type.
+  @Deprecated
   static final Schema<ChangeData> V57 =
       new Schema.Builder<ChangeData>()
           .add(V56)
@@ -99,6 +100,14 @@
           .legacyNumericFields(false)
           .build();
 
+  // Add new field CHERRY_PICK_OF
+  static final Schema<ChangeData> V58 =
+      new Schema.Builder<ChangeData>()
+          .add(V57)
+          .add(ChangeField.CHERRY_PICK_OF_CHANGE)
+          .add(ChangeField.CHERRY_PICK_OF_PATCHSET)
+          .build();
+
   /**
    * Name of the change index to be used when contacting index backends or loading configurations.
    */
diff --git a/java/com/google/gerrit/server/index/group/IndexedGroupQuery.java b/java/com/google/gerrit/server/index/group/IndexedGroupQuery.java
index 680db12..319b834 100644
--- a/java/com/google/gerrit/server/index/group/IndexedGroupQuery.java
+++ b/java/com/google/gerrit/server/index/group/IndexedGroupQuery.java
@@ -27,7 +27,7 @@
 import java.util.Set;
 
 /**
- * Wrapper around {@link Predicate}s that is returned by the {@link
+ * Wrapper around {@link Predicate}s that are returned by the {@link
  * com.google.gerrit.index.IndexRewriter}. See {@link IndexedQuery}.
  */
 public class IndexedGroupQuery extends IndexedQuery<AccountGroup.UUID, InternalGroup>
diff --git a/java/com/google/gerrit/server/logging/Metadata.java b/java/com/google/gerrit/server/logging/Metadata.java
index d312530..3bb4770 100644
--- a/java/com/google/gerrit/server/logging/Metadata.java
+++ b/java/com/google/gerrit/server/logging/Metadata.java
@@ -116,9 +116,6 @@
   // Type of a sequence in NoteDb (ACCOUNTS, CHANGES, GROUPS).
   public abstract Optional<String> noteDbSequenceType();
 
-  // Name of a "table" in NoteDb (if set, always CHANGES).
-  public abstract Optional<String> noteDbTable();
-
   // The ID of a patch set.
   public abstract Optional<Integer> patchSetId();
 
@@ -164,10 +161,10 @@
    * httpStatus=Optional.empty, indexName=Optional.empty, indexVersion=Optional[0],
    * methodName=Optional.empty, multiple=Optional.empty, operationName=Optional.empty,
    * partial=Optional.empty, noteDbFilePath=Optional.empty, noteDbRefName=Optional.empty,
-   * noteDbSequenceType=Optional.empty, noteDbTable=Optional.empty, patchSetId=Optional.empty,
-   * pluginMetadata=[], pluginName=Optional.empty, projectName=Optional.empty,
-   * pushType=Optional.empty, resourceCount=Optional.empty, restViewName=Optional.empty,
-   * revision=Optional.empty, username=Optional.empty}
+   * noteDbSequenceType=Optional.empty, patchSetId=Optional.empty, pluginMetadata=[],
+   * pluginName=Optional.empty, projectName=Optional.empty, pushType=Optional.empty,
+   * resourceCount=Optional.empty, restViewName=Optional.empty, revision=Optional.empty,
+   * username=Optional.empty}
    * </pre>
    *
    * <p>That's hard to read in logs. This is why this method
@@ -314,8 +311,6 @@
 
     public abstract Builder noteDbSequenceType(@Nullable String noteDbSequenceType);
 
-    public abstract Builder noteDbTable(@Nullable String noteDbTable);
-
     public abstract Builder patchSetId(int patchSetId);
 
     abstract ImmutableList.Builder<PluginMetadata> pluginMetadataBuilder();
diff --git a/java/com/google/gerrit/server/notedb/AbstractChangeNotes.java b/java/com/google/gerrit/server/notedb/AbstractChangeNotes.java
index 9a1ba35..9b5b4d4 100644
--- a/java/com/google/gerrit/server/notedb/AbstractChangeNotes.java
+++ b/java/com/google/gerrit/server/notedb/AbstractChangeNotes.java
@@ -14,7 +14,6 @@
 
 package com.google.gerrit.server.notedb;
 
-import static com.google.gerrit.server.notedb.NoteDbTable.CHANGES;
 import static java.util.Objects.requireNonNull;
 
 import com.google.common.annotations.VisibleForTesting;
@@ -23,7 +22,7 @@
 import com.google.gerrit.entities.Change;
 import com.google.gerrit.entities.Project;
 import com.google.gerrit.exceptions.StorageException;
-import com.google.gerrit.metrics.Timer1;
+import com.google.gerrit.metrics.Timer0;
 import com.google.gerrit.server.config.AllUsersName;
 import com.google.gerrit.server.config.GerritServerId;
 import com.google.gerrit.server.git.GitRepositoryManager;
@@ -141,7 +140,7 @@
     if (args.failOnLoadForTest.get()) {
       throw new StorageException("Reading from NoteDb is disabled");
     }
-    try (Timer1.Context<NoteDbTable> timer = args.metrics.readLatency.start(CHANGES);
+    try (Timer0.Context timer = args.metrics.readLatency.start();
         Repository repo = args.repoManager.openRepository(getProjectName());
         // Call openHandle even if reading is disabled, to trigger
         // auto-rebuilding before this object may get passed to a ChangeUpdate.
diff --git a/java/com/google/gerrit/server/notedb/ChangeNoteUtil.java b/java/com/google/gerrit/server/notedb/ChangeNoteUtil.java
index b221ef5..cebb67d 100644
--- a/java/com/google/gerrit/server/notedb/ChangeNoteUtil.java
+++ b/java/com/google/gerrit/server/notedb/ChangeNoteUtil.java
@@ -48,6 +48,7 @@
   public static final FooterKey FOOTER_TAG = new FooterKey("Tag");
   public static final FooterKey FOOTER_WORK_IN_PROGRESS = new FooterKey("Work-in-progress");
   public static final FooterKey FOOTER_REVERT_OF = new FooterKey("Revert-of");
+  public static final FooterKey FOOTER_CHERRY_PICK_OF = new FooterKey("Cherry-pick-of");
 
   static final String AUTHOR = "Author";
   static final String BASE_PATCH_SET = "Base-for-patch-set";
diff --git a/java/com/google/gerrit/server/notedb/ChangeNotesParser.java b/java/com/google/gerrit/server/notedb/ChangeNotesParser.java
index 3322b68..5468e23 100644
--- a/java/com/google/gerrit/server/notedb/ChangeNotesParser.java
+++ b/java/com/google/gerrit/server/notedb/ChangeNotesParser.java
@@ -18,6 +18,7 @@
 import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_ASSIGNEE;
 import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_BRANCH;
 import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_CHANGE_ID;
+import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_CHERRY_PICK_OF;
 import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_COMMIT;
 import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_CURRENT;
 import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_GROUPS;
@@ -36,7 +37,6 @@
 import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_TOPIC;
 import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_WORK_IN_PROGRESS;
 import static com.google.gerrit.server.notedb.ChangeNoteUtil.parseCommitMessageRange;
-import static com.google.gerrit.server.notedb.NoteDbTable.CHANGES;
 import static java.util.Comparator.comparing;
 import static java.util.stream.Collectors.joining;
 
@@ -65,7 +65,7 @@
 import com.google.gerrit.entities.PatchSetApproval;
 import com.google.gerrit.entities.RefNames;
 import com.google.gerrit.mail.Address;
-import com.google.gerrit.metrics.Timer1;
+import com.google.gerrit.metrics.Timer0;
 import com.google.gerrit.server.AssigneeStatusUpdate;
 import com.google.gerrit.server.ReviewerByEmailSet;
 import com.google.gerrit.server.ReviewerSet;
@@ -148,6 +148,7 @@
   private ReviewerByEmailSet pendingReviewersByEmail;
   private Change.Id revertOf;
   private int updateCount;
+  private PatchSet.Id cherryPickOf;
 
   ChangeNotesParser(
       Change.Id changeId,
@@ -185,7 +186,7 @@
     walk.reset();
     walk.markStart(walk.parseCommit(tip));
 
-    try (Timer1.Context<NoteDbTable> timer = metrics.parseLatency.start(CHANGES)) {
+    try (Timer0.Context timer = metrics.parseLatency.start()) {
       ChangeNotesCommit commit;
       while ((commit = walk.next()) != null) {
         parse(commit);
@@ -246,6 +247,7 @@
         firstNonNull(workInProgress, false),
         firstNonNull(hasReviewStarted, true),
         revertOf,
+        cherryPickOf,
         updateCount);
   }
 
@@ -417,6 +419,10 @@
       revertOf = parseRevertOf(commit);
     }
 
+    if (cherryPickOf == null) {
+      cherryPickOf = parseCherryPickOf(commit);
+    }
+
     previousWorkInProgressFooter = null;
     parseWorkInProgress(commit);
   }
@@ -966,6 +972,18 @@
     return Change.id(revertOf);
   }
 
+  private PatchSet.Id parseCherryPickOf(ChangeNotesCommit commit) throws ConfigInvalidException {
+    String cherryPickOf = parseOneFooter(commit, FOOTER_CHERRY_PICK_OF);
+    if (cherryPickOf == null) {
+      return null;
+    }
+    try {
+      return PatchSet.Id.parse(cherryPickOf);
+    } catch (IllegalArgumentException e) {
+      throw new ConfigInvalidException("\"" + cherryPickOf + "\" is not a valid patchset", e);
+    }
+  }
+
   private void pruneReviewers() {
     Iterator<Table.Cell<Account.Id, ReviewerStateInternal, Timestamp>> rit =
         reviewers.cellSet().iterator();
diff --git a/java/com/google/gerrit/server/notedb/ChangeNotesState.java b/java/com/google/gerrit/server/notedb/ChangeNotesState.java
index 896cca3..064e43b 100644
--- a/java/com/google/gerrit/server/notedb/ChangeNotesState.java
+++ b/java/com/google/gerrit/server/notedb/ChangeNotesState.java
@@ -123,6 +123,7 @@
       boolean workInProgress,
       boolean reviewStarted,
       @Nullable Change.Id revertOf,
+      @Nullable PatchSet.Id cherryPickOf,
       int updateCount) {
     requireNonNull(
         metaId,
@@ -152,6 +153,7 @@
                 .workInProgress(workInProgress)
                 .reviewStarted(reviewStarted)
                 .revertOf(revertOf)
+                .cherryPickOf(cherryPickOf)
                 .build())
         .hashtags(hashtags)
         .serverId(serverId)
@@ -220,6 +222,9 @@
     @Nullable
     abstract Change.Id revertOf();
 
+    @Nullable
+    abstract PatchSet.Id cherryPickOf();
+
     abstract Builder toBuilder();
 
     @AutoValue.Builder
@@ -254,6 +259,8 @@
 
       abstract Builder revertOf(@Nullable Change.Id revertOf);
 
+      abstract Builder cherryPickOf(@Nullable PatchSet.Id cherryPickOf);
+
       abstract ChangeColumns build();
     }
   }
@@ -341,6 +348,7 @@
     change.setWorkInProgress(c.workInProgress());
     change.setReviewStarted(c.reviewStarted());
     change.setRevertOf(c.revertOf());
+    change.setCherryPickOf(c.cherryPickOf());
 
     if (!patchSets().isEmpty()) {
       change.setCurrentPatchSet(c.currentPatchSetId(), c.subject(), c.originalSubject());
@@ -514,6 +522,10 @@
       if (cols.revertOf() != null) {
         b.setRevertOf(cols.revertOf().get()).setHasRevertOf(true);
       }
+      if (cols.cherryPickOf() != null) {
+        b.setCherryPickOf(cols.cherryPickOf().getCommaSeparatedChangeAndPatchSetId())
+            .setHasCherryPickOf(true);
+      }
       return b.build();
     }
 
@@ -637,6 +649,9 @@
       if (proto.getHasRevertOf()) {
         b.revertOf(Change.id(proto.getRevertOf()));
       }
+      if (proto.getHasCherryPickOf()) {
+        b.cherryPickOf(PatchSet.Id.parse(proto.getCherryPickOf()));
+      }
       return b.build();
     }
 
diff --git a/java/com/google/gerrit/server/notedb/ChangeUpdate.java b/java/com/google/gerrit/server/notedb/ChangeUpdate.java
index 6a900c0..2822f61 100644
--- a/java/com/google/gerrit/server/notedb/ChangeUpdate.java
+++ b/java/com/google/gerrit/server/notedb/ChangeUpdate.java
@@ -21,6 +21,7 @@
 import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_ASSIGNEE;
 import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_BRANCH;
 import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_CHANGE_ID;
+import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_CHERRY_PICK_OF;
 import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_COMMIT;
 import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_CURRENT;
 import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_GROUPS;
@@ -137,6 +138,7 @@
   private Boolean isPrivate;
   private Boolean workInProgress;
   private Integer revertOf;
+  private String cherryPickOf;
 
   private ChangeDraftUpdate draftUpdate;
   private RobotCommentUpdate robotCommentUpdate;
@@ -417,6 +419,10 @@
     rootOnly = true;
   }
 
+  public void setCherryPickOf(String cherryPickOf) {
+    this.cherryPickOf = cherryPickOf;
+  }
+
   /** @return the tree id for the updated tree */
   private ObjectId storeRevisionNotes(RevWalk rw, ObjectInserter inserter, ObjectId curr)
       throws ConfigInvalidException, IOException {
@@ -665,6 +671,10 @@
       addFooter(msg, FOOTER_REVERT_OF, revertOf);
     }
 
+    if (cherryPickOf != null) {
+      addFooter(msg, FOOTER_CHERRY_PICK_OF, cherryPickOf);
+    }
+
     cb.setMessage(msg.toString());
     try {
       ObjectId treeId = storeRevisionNotes(rw, ins, curr);
@@ -714,7 +724,8 @@
         && !currentPatchSet
         && isPrivate == null
         && workInProgress == null
-        && revertOf == null;
+        && revertOf == null
+        && cherryPickOf == null;
   }
 
   ChangeDraftUpdate getDraftUpdate() {
diff --git a/java/com/google/gerrit/server/notedb/NoteDbMetrics.java b/java/com/google/gerrit/server/notedb/NoteDbMetrics.java
index 18ffd17..246e7e1 100644
--- a/java/com/google/gerrit/server/notedb/NoteDbMetrics.java
+++ b/java/com/google/gerrit/server/notedb/NoteDbMetrics.java
@@ -16,69 +16,61 @@
 
 import com.google.gerrit.metrics.Description;
 import com.google.gerrit.metrics.Description.Units;
-import com.google.gerrit.metrics.Field;
 import com.google.gerrit.metrics.MetricMaker;
-import com.google.gerrit.metrics.Timer1;
-import com.google.gerrit.server.logging.Metadata;
+import com.google.gerrit.metrics.Timer0;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
 
+/** Metrics for accessing and updating changes in NoteDb. */
 @Singleton
 class NoteDbMetrics {
   /** End-to-end latency for writing a collection of updates. */
-  final Timer1<NoteDbTable> updateLatency;
+  final Timer0 updateLatency;
 
   /**
    * The portion of {@link #updateLatency} due to preparing the sequence of updates.
    *
    * <p>May include some I/O (e.g. reading old refs), but excludes writes.
    */
-  final Timer1<NoteDbTable> stageUpdateLatency;
+  final Timer0 stageUpdateLatency;
 
   /** End-to-end latency for reading changes from NoteDb, including reading ref(s) and parsing. */
-  final Timer1<NoteDbTable> readLatency;
+  final Timer0 readLatency;
 
   /**
    * The portion of {@link #readLatency} due to parsing commits, but excluding I/O (to a best
    * effort).
    */
-  final Timer1<NoteDbTable> parseLatency;
+  final Timer0 parseLatency;
 
   @Inject
   NoteDbMetrics(MetricMaker metrics) {
-    Field<NoteDbTable> tableField =
-        Field.ofEnum(NoteDbTable.class, "table", Metadata.Builder::noteDbTable).build();
-
     updateLatency =
         metrics.newTimer(
             "notedb/update_latency",
-            new Description("NoteDb update latency by table")
+            new Description("NoteDb update latency for changes")
                 .setCumulative()
-                .setUnit(Units.MILLISECONDS),
-            tableField);
+                .setUnit(Units.MILLISECONDS));
 
     stageUpdateLatency =
         metrics.newTimer(
             "notedb/stage_update_latency",
-            new Description("Latency for staging updates to NoteDb by table")
+            new Description("Latency for staging change updates to NoteDb")
                 .setCumulative()
-                .setUnit(Units.MICROSECONDS),
-            tableField);
+                .setUnit(Units.MICROSECONDS));
 
     readLatency =
         metrics.newTimer(
             "notedb/read_latency",
-            new Description("NoteDb read latency by table")
+            new Description("NoteDb read latency for changes")
                 .setCumulative()
-                .setUnit(Units.MILLISECONDS),
-            tableField);
+                .setUnit(Units.MILLISECONDS));
 
     parseLatency =
         metrics.newTimer(
             "notedb/parse_latency",
-            new Description("NoteDb parse latency by table")
+            new Description("NoteDb parse latency for changes")
                 .setCumulative()
-                .setUnit(Units.MICROSECONDS),
-            tableField);
+                .setUnit(Units.MICROSECONDS));
   }
 }
diff --git a/java/com/google/gerrit/server/notedb/NoteDbTable.java b/java/com/google/gerrit/server/notedb/NoteDbTable.java
deleted file mode 100644
index e299fdf..0000000
--- a/java/com/google/gerrit/server/notedb/NoteDbTable.java
+++ /dev/null
@@ -1,30 +0,0 @@
-// 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.notedb;
-
-public enum NoteDbTable {
-  ACCOUNTS,
-  GROUPS,
-  CHANGES;
-
-  public String key() {
-    return name().toLowerCase();
-  }
-
-  @Override
-  public String toString() {
-    return key();
-  }
-}
diff --git a/java/com/google/gerrit/server/notedb/NoteDbUpdateManager.java b/java/com/google/gerrit/server/notedb/NoteDbUpdateManager.java
index 1b92c0e..2d1a04a 100644
--- a/java/com/google/gerrit/server/notedb/NoteDbUpdateManager.java
+++ b/java/com/google/gerrit/server/notedb/NoteDbUpdateManager.java
@@ -17,7 +17,6 @@
 import static com.google.common.base.MoreObjects.firstNonNull;
 import static com.google.common.base.Preconditions.checkArgument;
 import static com.google.common.base.Preconditions.checkState;
-import static com.google.gerrit.server.notedb.NoteDbTable.CHANGES;
 
 import com.google.common.collect.ListMultimap;
 import com.google.common.collect.MultimapBuilder;
@@ -27,7 +26,7 @@
 import com.google.gerrit.entities.RefNames;
 import com.google.gerrit.exceptions.StorageException;
 import com.google.gerrit.git.RefUpdateUtil;
-import com.google.gerrit.metrics.Timer1;
+import com.google.gerrit.metrics.Timer0;
 import com.google.gerrit.server.GerritPersonIdent;
 import com.google.gerrit.server.config.AllUsersName;
 import com.google.gerrit.server.config.GerritServerConfig;
@@ -273,7 +272,7 @@
    * @throws IOException if a storage layer error occurs.
    */
   private void stage() throws IOException {
-    try (Timer1.Context<NoteDbTable> timer = metrics.stageUpdateLatency.start(CHANGES)) {
+    try (Timer0.Context timer = metrics.stageUpdateLatency.start()) {
       if (isEmpty()) {
         return;
       }
@@ -298,7 +297,7 @@
       executed = true;
       return null;
     }
-    try (Timer1.Context<NoteDbTable> timer = metrics.updateLatency.start(CHANGES)) {
+    try (Timer0.Context timer = metrics.updateLatency.start()) {
       stage();
       // ChangeUpdates must execute before ChangeDraftUpdates.
       //
diff --git a/java/com/google/gerrit/server/patch/PatchListCacheImpl.java b/java/com/google/gerrit/server/patch/PatchListCacheImpl.java
index 15fa0f4..b663b9d 100644
--- a/java/com/google/gerrit/server/patch/PatchListCacheImpl.java
+++ b/java/com/google/gerrit/server/patch/PatchListCacheImpl.java
@@ -138,11 +138,10 @@
       throws PatchListNotAvailableException {
     Project.NameKey project = change.getProject();
     ObjectId b = patchSet.commitId();
-    Whitespace ws = Whitespace.IGNORE_NONE;
     if (parentNum != null) {
-      return get(PatchListKey.againstParentNum(parentNum, b, ws), project);
+      return get(PatchListKey.againstParentNum(parentNum, b, Whitespace.IGNORE_NONE), project);
     }
-    return get(PatchListKey.againstDefaultBase(b, ws), project);
+    return get(PatchListKey.againstDefaultBase(b, Whitespace.IGNORE_NONE), project);
   }
 
   @Override
diff --git a/java/com/google/gerrit/server/patch/PatchListKey.java b/java/com/google/gerrit/server/patch/PatchListKey.java
index bf38029..386c50d 100644
--- a/java/com/google/gerrit/server/patch/PatchListKey.java
+++ b/java/com/google/gerrit/server/patch/PatchListKey.java
@@ -59,6 +59,12 @@
     return new PatchListKey(otherCommitId, newId, whitespace);
   }
 
+  public static PatchListKey againstBase(ObjectId id, int parentCount) {
+    return parentCount > 1
+        ? PatchListKey.againstParentNum(1, id, Whitespace.IGNORE_NONE)
+        : PatchListKey.againstDefaultBase(id, Whitespace.IGNORE_NONE);
+  }
+
   /**
    * Old patch-set ID
    *
diff --git a/java/com/google/gerrit/server/query/account/AccountPredicates.java b/java/com/google/gerrit/server/query/account/AccountPredicates.java
index 1eed7ea..e4da946 100644
--- a/java/com/google/gerrit/server/query/account/AccountPredicates.java
+++ b/java/com/google/gerrit/server/query/account/AccountPredicates.java
@@ -29,6 +29,7 @@
 import com.google.gerrit.server.notedb.ChangeNotes;
 import java.util.List;
 
+/** Utility class to create predicates for account index queries. */
 public class AccountPredicates {
   public static boolean hasActive(Predicate<AccountState> p) {
     return QueryBuilder.find(p, AccountPredicate.class, AccountField.ACTIVE.getName()) != null;
@@ -130,6 +131,7 @@
     return new CanSeeChangePredicate(args.permissionBackend, changeNotes);
   }
 
+  /** Predicate that is mapped to a field in the account index. */
   static class AccountPredicate extends IndexPredicate<AccountState>
       implements Matchable<AccountState> {
     AccountPredicate(FieldDef<AccountState, ?> def, String value) {
diff --git a/java/com/google/gerrit/server/query/change/ChangeData.java b/java/com/google/gerrit/server/query/change/ChangeData.java
index 78ca0fc..8879388 100644
--- a/java/com/google/gerrit/server/query/change/ChangeData.java
+++ b/java/com/google/gerrit/server/query/change/ChangeData.java
@@ -45,7 +45,6 @@
 import com.google.gerrit.entities.RefNames;
 import com.google.gerrit.entities.RobotComment;
 import com.google.gerrit.exceptions.StorageException;
-import com.google.gerrit.extensions.client.DiffPreferencesInfo.Whitespace;
 import com.google.gerrit.extensions.restapi.BadRequestException;
 import com.google.gerrit.extensions.restapi.ResourceConflictException;
 import com.google.gerrit.server.ApprovalsUtil;
@@ -301,6 +300,7 @@
   private Integer unresolvedCommentCount;
   private Integer totalCommentCount;
   private LabelTypes labelTypes;
+  private Optional<PatchSet.Id> cherryPickOf;
 
   private ImmutableList<byte[]> refStates;
   private ImmutableList<byte[]> refStatePatterns;
@@ -396,12 +396,7 @@
         return Optional.empty();
       }
 
-      ObjectId id = ps.commitId();
-      Whitespace ws = Whitespace.IGNORE_NONE;
-      PatchListKey pk =
-          parentCount > 1
-              ? PatchListKey.againstParentNum(1, id, ws)
-              : PatchListKey.againstDefaultBase(id, ws);
+      PatchListKey pk = PatchListKey.againstBase(ps.commitId(), parentCount);
       DiffSummaryKey key = DiffSummaryKey.fromPatchListKey(pk);
       try {
         diffSummary = Optional.of(patchListCache.getDiffSummary(key, c.getProject()));
diff --git a/java/com/google/gerrit/server/query/change/ChangeIndexPredicate.java b/java/com/google/gerrit/server/query/change/ChangeIndexPredicate.java
index 7428e3a..a176a58 100644
--- a/java/com/google/gerrit/server/query/change/ChangeIndexPredicate.java
+++ b/java/com/google/gerrit/server/query/change/ChangeIndexPredicate.java
@@ -19,6 +19,7 @@
 import com.google.gerrit.index.query.Matchable;
 import com.google.gerrit.index.query.Predicate;
 
+/** Predicate that is mapped to a field in the change index. */
 public abstract class ChangeIndexPredicate extends IndexPredicate<ChangeData>
     implements Matchable<ChangeData> {
   /**
diff --git a/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java b/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
index 5f076b1..7747998 100644
--- a/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
+++ b/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
@@ -34,6 +34,7 @@
 import com.google.gerrit.entities.AccountGroup;
 import com.google.gerrit.entities.BranchNameKey;
 import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.PatchSet;
 import com.google.gerrit.entities.RefNames;
 import com.google.gerrit.exceptions.NotSignedInException;
 import com.google.gerrit.exceptions.StorageException;
@@ -189,6 +190,9 @@
   public static final String FIELD_WATCHEDBY = "watchedby";
   public static final String FIELD_WIP = "wip";
   public static final String FIELD_REVERTOF = "revertof";
+  public static final String FIELD_CHERRY_PICK_OF = "cherrypickof";
+  public static final String FIELD_CHERRY_PICK_OF_CHANGE = "cherrypickofchange";
+  public static final String FIELD_CHERRY_PICK_OF_PATCHSET = "cherrypickofpatchset";
 
   public static final String ARG_ID_USER = "user";
   public static final String ARG_ID_GROUP = "group";
@@ -1268,6 +1272,29 @@
         "'submissionid' operator is not supported by change index version");
   }
 
+  @Operator
+  public Predicate<ChangeData> cherryPickOf(String value) throws QueryParseException {
+    if (args.getSchema().hasField(ChangeField.CHERRY_PICK_OF_CHANGE)
+        && args.getSchema().hasField(ChangeField.CHERRY_PICK_OF_PATCHSET)) {
+      if (Ints.tryParse(value) != null) {
+        return new CherryPickOfChangePredicate(value);
+      }
+      try {
+        PatchSet.Id patchSetId = PatchSet.Id.parse(value);
+        return Predicate.and(
+            new CherryPickOfChangePredicate(patchSetId.changeId().toString()),
+            new CherryPickOfPatchSetPredicate(patchSetId.getId()));
+      } catch (IllegalArgumentException e) {
+        throw new QueryParseException(
+            "'"
+                + value
+                + "' is not a valid input. It must be in the 'ChangeNumber[,PatchsetNumber]' format.");
+      }
+    }
+    throw new QueryParseException(
+        "'cherrypickof' operator is not supported by change index version");
+  }
+
   @Override
   protected Predicate<ChangeData> defaultField(String query) throws QueryParseException {
     if (query.startsWith("refs/")) {
diff --git a/java/com/google/gerrit/server/query/change/CherryPickOfChangePredicate.java b/java/com/google/gerrit/server/query/change/CherryPickOfChangePredicate.java
new file mode 100644
index 0000000..d452017
--- /dev/null
+++ b/java/com/google/gerrit/server/query/change/CherryPickOfChangePredicate.java
@@ -0,0 +1,36 @@
+// Copyright (C) 2019 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.query.change;
+
+import com.google.gerrit.server.index.change.ChangeField;
+
+public class CherryPickOfChangePredicate extends ChangeIndexPredicate {
+  public CherryPickOfChangePredicate(String cherryPickOfChange) {
+    super(ChangeField.CHERRY_PICK_OF_CHANGE, cherryPickOfChange);
+  }
+
+  @Override
+  public boolean match(ChangeData cd) {
+    if (cd.change().getCherryPickOf() == null) {
+      return false;
+    }
+    return Integer.toString(cd.change().getCherryPickOf().changeId().get()).equals(value);
+  }
+
+  @Override
+  public int getCost() {
+    return 1;
+  }
+}
diff --git a/java/com/google/gerrit/server/query/change/CherryPickOfPatchSetPredicate.java b/java/com/google/gerrit/server/query/change/CherryPickOfPatchSetPredicate.java
new file mode 100644
index 0000000..888f45d
--- /dev/null
+++ b/java/com/google/gerrit/server/query/change/CherryPickOfPatchSetPredicate.java
@@ -0,0 +1,36 @@
+// Copyright (C) 2019 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.query.change;
+
+import com.google.gerrit.server.index.change.ChangeField;
+
+public class CherryPickOfPatchSetPredicate extends ChangeIndexPredicate {
+  public CherryPickOfPatchSetPredicate(String cherryPickOfPatchSet) {
+    super(ChangeField.CHERRY_PICK_OF_PATCHSET, cherryPickOfPatchSet);
+  }
+
+  @Override
+  public boolean match(ChangeData cd) {
+    if (cd.change().getCherryPickOf() == null) {
+      return false;
+    }
+    return cd.change().getCherryPickOf().getId().equals(value);
+  }
+
+  @Override
+  public int getCost() {
+    return 1;
+  }
+}
diff --git a/java/com/google/gerrit/server/query/group/GroupPredicates.java b/java/com/google/gerrit/server/query/group/GroupPredicates.java
index 17a7000..5231c5a 100644
--- a/java/com/google/gerrit/server/query/group/GroupPredicates.java
+++ b/java/com/google/gerrit/server/query/group/GroupPredicates.java
@@ -23,6 +23,7 @@
 import com.google.gerrit.server.index.group.GroupField;
 import java.util.Locale;
 
+/** Utility class to create predicates for group index queries. */
 public class GroupPredicates {
   public static Predicate<InternalGroup> id(AccountGroup.Id groupId) {
     return new GroupPredicate(GroupField.ID, groupId.toString());
@@ -63,6 +64,7 @@
     return new GroupPredicate(GroupField.SUBGROUP, subgroupUuid.get());
   }
 
+  /** Predicate that is mapped to a field in the group index. */
   static class GroupPredicate extends IndexPredicate<InternalGroup> {
     GroupPredicate(FieldDef<InternalGroup, ?> def, String value) {
       super(def, value);
diff --git a/java/com/google/gerrit/server/query/project/ProjectPredicates.java b/java/com/google/gerrit/server/query/project/ProjectPredicates.java
index 4e56fac..8b4048f 100644
--- a/java/com/google/gerrit/server/query/project/ProjectPredicates.java
+++ b/java/com/google/gerrit/server/query/project/ProjectPredicates.java
@@ -22,6 +22,7 @@
 import com.google.gerrit.index.query.Predicate;
 import java.util.Locale;
 
+/** Utility class to create predicates for project index queries. */
 public class ProjectPredicates {
   public static Predicate<ProjectData> name(Project.NameKey nameKey) {
     return new ProjectPredicate(ProjectField.NAME, nameKey.get());
diff --git a/java/com/google/gerrit/server/restapi/access/AccessCollection.java b/java/com/google/gerrit/server/restapi/access/AccessCollection.java
index 8ae2ce7..d8832a2 100644
--- a/java/com/google/gerrit/server/restapi/access/AccessCollection.java
+++ b/java/com/google/gerrit/server/restapi/access/AccessCollection.java
@@ -24,6 +24,7 @@
 import com.google.inject.Provider;
 import com.google.inject.Singleton;
 
+/** REST collection that serves requests to {@code /access/}. */
 @Singleton
 public class AccessCollection implements RestCollection<TopLevelResource, AccessResource> {
   private final Provider<ListAccess> list;
diff --git a/java/com/google/gerrit/server/restapi/access/AccessResource.java b/java/com/google/gerrit/server/restapi/access/AccessResource.java
index 915165b..4847da4 100644
--- a/java/com/google/gerrit/server/restapi/access/AccessResource.java
+++ b/java/com/google/gerrit/server/restapi/access/AccessResource.java
@@ -18,6 +18,13 @@
 import com.google.gerrit.extensions.restapi.RestView;
 import com.google.inject.TypeLiteral;
 
+/**
+ * REST resource that represents members in {@link AccessCollection}.
+ *
+ * <p>{@link AccessCollection} doesn't support accessing single members, hence this class only
+ * defines the {@link TypeLiteral} for the resource kind that is needed for binding the REST
+ * collection.
+ */
 public class AccessResource implements RestResource {
   public static final TypeLiteral<RestView<AccessResource>> ACCESS_KIND =
       new TypeLiteral<RestView<AccessResource>>() {};
diff --git a/java/com/google/gerrit/server/restapi/access/ListAccess.java b/java/com/google/gerrit/server/restapi/access/ListAccess.java
index 2520821..1e1bade 100644
--- a/java/com/google/gerrit/server/restapi/access/ListAccess.java
+++ b/java/com/google/gerrit/server/restapi/access/ListAccess.java
@@ -27,6 +27,11 @@
 import java.util.TreeMap;
 import org.kohsuke.args4j.Option;
 
+/**
+ * REST endpoint to list members of the {@link AccessCollection}.
+ *
+ * <p>This REST endpoint handles {@code GET /access/} requests.
+ */
 public class ListAccess implements RestReadView<TopLevelResource> {
 
   @Option(
diff --git a/java/com/google/gerrit/server/restapi/access/Module.java b/java/com/google/gerrit/server/restapi/access/Module.java
index 7da2e26b..3a4955d 100644
--- a/java/com/google/gerrit/server/restapi/access/Module.java
+++ b/java/com/google/gerrit/server/restapi/access/Module.java
@@ -19,6 +19,7 @@
 import com.google.gerrit.extensions.registration.DynamicMap;
 import com.google.gerrit.extensions.restapi.RestApiModule;
 
+/** Guice module that binds all REST endpoints for {@code /access/}. */
 public class Module extends RestApiModule {
   @Override
   protected void configure() {
diff --git a/java/com/google/gerrit/server/restapi/change/ChangeEdits.java b/java/com/google/gerrit/server/restapi/change/ChangeEdits.java
index e220f35..aa36b73 100644
--- a/java/com/google/gerrit/server/restapi/change/ChangeEdits.java
+++ b/java/com/google/gerrit/server/restapi/change/ChangeEdits.java
@@ -30,7 +30,6 @@
 import com.google.gerrit.extensions.restapi.ChildCollection;
 import com.google.gerrit.extensions.restapi.DefaultInput;
 import com.google.gerrit.extensions.restapi.IdString;
-import com.google.gerrit.extensions.restapi.RawInput;
 import com.google.gerrit.extensions.restapi.ResourceConflictException;
 import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
 import com.google.gerrit.extensions.restapi.Response;
@@ -120,7 +119,8 @@
 
     @Override
     public Response<?> apply(ChangeResource resource, IdString id, FileContentInput input)
-        throws AuthException, ResourceConflictException, IOException, PermissionBackendException {
+        throws AuthException, BadRequestException, ResourceConflictException, IOException,
+            PermissionBackendException {
       putEdit.apply(resource, id.get(), input);
       return Response.none();
     }
@@ -280,19 +280,24 @@
 
     @Override
     public Response<?> apply(ChangeEditResource rsrc, FileContentInput input)
-        throws AuthException, ResourceConflictException, IOException, PermissionBackendException {
+        throws AuthException, BadRequestException, ResourceConflictException, IOException,
+            PermissionBackendException {
       return apply(rsrc.getChangeResource(), rsrc.getPath(), input);
     }
 
     public Response<?> apply(ChangeResource rsrc, String path, FileContentInput input)
-        throws ResourceConflictException, AuthException, IOException, PermissionBackendException {
+        throws AuthException, BadRequestException, ResourceConflictException, IOException,
+            PermissionBackendException {
       if (Strings.isNullOrEmpty(path) || path.charAt(0) == '/') {
         throw new ResourceConflictException("Invalid path: " + path);
       }
 
-      RawInput newContent = input.content;
+      if (input.content == null) {
+        throw new BadRequestException("new content required");
+      }
+
       try (Repository repository = repositoryManager.openRepository(rsrc.getProject())) {
-        editModifier.modifyFile(repository, rsrc.getNotes(), path, newContent);
+        editModifier.modifyFile(repository, rsrc.getNotes(), path, input.content);
       } catch (InvalidChangeOperationException e) {
         throw new ResourceConflictException(e.getMessage());
       }
diff --git a/java/com/google/gerrit/server/restapi/change/CherryPickChange.java b/java/com/google/gerrit/server/restapi/change/CherryPickChange.java
index 6b671c9..3496491 100644
--- a/java/com/google/gerrit/server/restapi/change/CherryPickChange.java
+++ b/java/com/google/gerrit/server/restapi/change/CherryPickChange.java
@@ -42,6 +42,7 @@
 import com.google.gerrit.server.change.ChangeInserter;
 import com.google.gerrit.server.change.NotifyResolver;
 import com.google.gerrit.server.change.PatchSetInserter;
+import com.google.gerrit.server.change.SetCherryPickOp;
 import com.google.gerrit.server.git.CodeReviewCommit;
 import com.google.gerrit.server.git.CodeReviewCommit.CodeReviewRevWalk;
 import com.google.gerrit.server.git.GitRepositoryManager;
@@ -102,6 +103,7 @@
   private final Provider<IdentifiedUser> user;
   private final ChangeInserter.Factory changeInserterFactory;
   private final PatchSetInserter.Factory patchSetInserterFactory;
+  private final SetCherryPickOp.Factory setCherryPickOfFactory;
   private final MergeUtil.Factory mergeUtilFactory;
   private final ChangeNotes.Factory changeNotesFactory;
   private final ProjectCache projectCache;
@@ -118,6 +120,7 @@
       Provider<IdentifiedUser> user,
       ChangeInserter.Factory changeInserterFactory,
       PatchSetInserter.Factory patchSetInserterFactory,
+      SetCherryPickOp.Factory setCherryPickOfFactory,
       MergeUtil.Factory mergeUtilFactory,
       ChangeNotes.Factory changeNotesFactory,
       ProjectCache projectCache,
@@ -131,6 +134,7 @@
     this.user = user;
     this.changeInserterFactory = changeInserterFactory;
     this.patchSetInserterFactory = patchSetInserterFactory;
+    this.setCherryPickOfFactory = setCherryPickOfFactory;
     this.mergeUtilFactory = mergeUtilFactory;
     this.changeNotesFactory = changeNotesFactory;
     this.projectCache = projectCache;
@@ -368,7 +372,13 @@
                       dest.project(),
                       destChanges.get(0).getId().get()));
             }
-            changeId = insertPatchSet(bu, git, destChanges.get(0).notes(), cherryPickCommit);
+            changeId =
+                insertPatchSet(
+                    bu,
+                    git,
+                    destChanges.get(0).notes(),
+                    cherryPickCommit,
+                    sourceChange.currentPatchSetId());
           } else {
             // Change key not found on destination branch. We can create a new
             // change.
@@ -451,13 +461,22 @@
   }
 
   private Change.Id insertPatchSet(
-      BatchUpdate bu, Repository git, ChangeNotes destNotes, CodeReviewCommit cherryPickCommit)
+      BatchUpdate bu,
+      Repository git,
+      ChangeNotes destNotes,
+      CodeReviewCommit cherryPickCommit,
+      PatchSet.Id sourcePatchSetId)
       throws IOException {
     Change destChange = destNotes.getChange();
     PatchSet.Id psId = ChangeUtil.nextPatchSetId(git, destChange.currentPatchSetId());
     PatchSetInserter inserter = patchSetInserterFactory.create(destNotes, psId, cherryPickCommit);
     inserter.setMessage("Uploaded patch set " + inserter.getPatchSetId().get() + ".");
     bu.addOp(destChange.getId(), inserter);
+    if (destChange.getCherryPickOf() == null
+        || !destChange.getCherryPickOf().equals(sourcePatchSetId)) {
+      SetCherryPickOp cherryPickOfUpdater = setCherryPickOfFactory.create(sourcePatchSetId);
+      bu.addOp(destChange.getId(), cherryPickOfUpdater);
+    }
     return destChange.getId();
   }
 
@@ -477,6 +496,7 @@
     ChangeInserter ins = changeInserterFactory.create(changeId, cherryPickCommit, refName);
     ins.setRevertOf(revertOf);
     BranchNameKey sourceBranch = sourceChange == null ? null : sourceChange.getDest();
+    PatchSet.Id sourcePatchSetId = sourceChange == null ? null : sourceChange.currentPatchSetId();
     ins.setMessage(
             revertOf == null
                 ? messageForDestinationChange(
@@ -484,6 +504,7 @@
                 : "Uploaded patch set 1.") // For revert commits, the message should not include
         // cherry-pick information.
         .setTopic(topic)
+        .setCherryPickOf(sourcePatchSetId)
         .setWorkInProgress(
             (sourceChange != null && sourceChange.isWorkInProgress())
                 || !cherryPickCommit.getFilesWithGitConflicts().isEmpty());
diff --git a/java/com/google/gerrit/server/restapi/change/CreateChange.java b/java/com/google/gerrit/server/restapi/change/CreateChange.java
index 310f71b..c0b28d2 100644
--- a/java/com/google/gerrit/server/restapi/change/CreateChange.java
+++ b/java/com/google/gerrit/server/restapi/change/CreateChange.java
@@ -20,6 +20,7 @@
 import com.google.common.base.Joiner;
 import com.google.common.base.Strings;
 import com.google.common.collect.Iterables;
+import com.google.common.flogger.FluentLogger;
 import com.google.gerrit.common.Nullable;
 import com.google.gerrit.entities.BooleanProjectConfig;
 import com.google.gerrit.entities.BranchNameKey;
@@ -86,7 +87,6 @@
 import org.eclipse.jgit.errors.InvalidObjectIdException;
 import org.eclipse.jgit.errors.MissingObjectException;
 import org.eclipse.jgit.errors.NoMergeBaseException;
-import org.eclipse.jgit.errors.NoMergeBaseException.MergeBaseFailureReason;
 import org.eclipse.jgit.lib.CommitBuilder;
 import org.eclipse.jgit.lib.Config;
 import org.eclipse.jgit.lib.Constants;
@@ -104,6 +104,8 @@
 @Singleton
 public class CreateChange
     implements RestCollectionModifyView<TopLevelResource, ChangeResource, ChangeInput> {
+  private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+
   private final BatchUpdate.Factory updateFactory;
   private final String anonymousCowardName;
   private final GitRepositoryManager gitManager;
@@ -294,20 +296,30 @@
       BatchUpdate.Factory updateFactory)
       throws RestApiException, PermissionBackendException, IOException, ConfigInvalidException,
           UpdateException {
+    logger.atFine().log(
+        "Creating new change for target branch %s in project %s"
+            + " (new branch = %s, base change = %s, base commit = %s)",
+        input.branch, projectState.getName(), input.newBranch, input.baseChange, input.baseCommit);
+
     try (Repository git = gitManager.openRepository(projectState.getNameKey());
         ObjectInserter oi = git.newObjectInserter();
         ObjectReader reader = oi.newReader();
         RevWalk rw = new RevWalk(reader)) {
       PatchSet basePatchSet = null;
       List<String> groups = Collections.emptyList();
+
       if (input.baseChange != null) {
         ChangeNotes baseChange = getBaseChange(input.baseChange);
         basePatchSet = psUtil.current(baseChange);
         groups = basePatchSet.groups();
+        logger.atFine().log("base patch set = %s (groups = %s)", basePatchSet.id(), groups);
       }
+
       ObjectId parentCommit =
           getParentCommit(
               git, rw, input.branch, input.newBranch, basePatchSet, input.baseCommit, input.merge);
+      logger.atFine().log(
+          "parent commit = %s", parentCommit != null ? parentCommit.name() : "NULL");
 
       RevCommit mergeTip = parentCommit == null ? null : rw.parseCommit(parentCommit);
 
@@ -464,6 +476,7 @@
       RevCommit mergeTip,
       String commitMessage)
       throws IOException {
+    logger.atFine().log("Creating empty commit");
     CommitBuilder commit = new CommitBuilder();
     if (mergeTip == null) {
       commit.setTreeId(emptyTreeId(oi));
@@ -487,6 +500,9 @@
       PersonIdent authorIdent,
       String commitMessage)
       throws RestApiException, IOException {
+    logger.atFine().log(
+        "Creating merge commit: source = %s, strategy = %s", merge.source, merge.strategy);
+
     if (Strings.isNullOrEmpty(merge.source)) {
       throw new BadRequestException("merge.source must be non-empty");
     }
@@ -500,6 +516,7 @@
     // default merge strategy from project settings
     String mergeStrategy =
         firstNonNull(Strings.emptyToNull(merge.strategy), mergeUtil.mergeStrategyName());
+    logger.atFine().log("merge strategy = %s", mergeStrategy);
 
     try {
       return MergeUtil.createMergeCommit(
@@ -512,12 +529,8 @@
           commitMessage,
           rw);
     } catch (NoMergeBaseException e) {
-      if (MergeBaseFailureReason.TOO_MANY_MERGE_BASES == e.getReason()
-          || MergeBaseFailureReason.CONFLICTS_DURING_MERGE_BASE_CALCULATION == e.getReason()) {
-        throw new ResourceConflictException(
-            String.format("Cannot create merge commit: %s", e.getMessage()), e);
-      }
-      throw e;
+      throw new ResourceConflictException(
+          String.format("Cannot create merge commit: %s", e.getMessage()), e);
     }
   }
 
diff --git a/java/com/google/gerrit/server/restapi/change/GetDiff.java b/java/com/google/gerrit/server/restapi/change/GetDiff.java
index 82bdc34..c1d286a 100644
--- a/java/com/google/gerrit/server/restapi/change/GetDiff.java
+++ b/java/com/google/gerrit/server/restapi/change/GetDiff.java
@@ -29,6 +29,7 @@
 import com.google.gerrit.extensions.common.DiffWebLinkInfo;
 import com.google.gerrit.extensions.common.WebLinkInfo;
 import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.BadRequestException;
 import com.google.gerrit.extensions.restapi.CacheControl;
 import com.google.gerrit.extensions.restapi.IdString;
 import com.google.gerrit.extensions.restapi.ResourceConflictException;
@@ -102,8 +103,8 @@
 
   @Override
   public Response<DiffInfo> apply(FileResource resource)
-      throws ResourceConflictException, ResourceNotFoundException, AuthException,
-          InvalidChangeOperationException, IOException, PermissionBackendException {
+      throws BadRequestException, ResourceConflictException, ResourceNotFoundException,
+          AuthException, InvalidChangeOperationException, IOException, PermissionBackendException {
     DiffPreferencesInfo prefs = new DiffPreferencesInfo();
     if (whitespace != null) {
       prefs.ignoreWhitespace = whitespace;
@@ -130,6 +131,9 @@
       RevisionResource baseResource =
           revisions.parse(resource.getRevision().getChangeResource(), IdString.fromDecoded(base));
       basePatchSet = baseResource.getPatchSet();
+      if (basePatchSet.id().get() == 0) {
+        throw new BadRequestException("edit not allowed as base");
+      }
       psf = patchScriptFactoryFactory.create(notes, fileName, basePatchSet.id(), pId, prefs);
     } else if (parentNum > 0) {
       psf = patchScriptFactoryFactory.create(notes, fileName, parentNum - 1, pId, prefs);
diff --git a/java/com/google/gerrit/server/restapi/change/Module.java b/java/com/google/gerrit/server/restapi/change/Module.java
index 4409c6a..e339c67 100644
--- a/java/com/google/gerrit/server/restapi/change/Module.java
+++ b/java/com/google/gerrit/server/restapi/change/Module.java
@@ -40,6 +40,7 @@
 import com.google.gerrit.server.change.RebaseChangeOp;
 import com.google.gerrit.server.change.ReviewerResource;
 import com.google.gerrit.server.change.SetAssigneeOp;
+import com.google.gerrit.server.change.SetCherryPickOp;
 import com.google.gerrit.server.change.SetHashtagsOp;
 import com.google.gerrit.server.change.SetPrivateOp;
 import com.google.gerrit.server.change.WorkInProgressOp;
@@ -201,6 +202,7 @@
     factory(RebaseChangeOp.Factory.class);
     factory(ReviewerResource.Factory.class);
     factory(SetAssigneeOp.Factory.class);
+    factory(SetCherryPickOp.Factory.class);
     factory(SetHashtagsOp.Factory.class);
     factory(SetPrivateOp.Factory.class);
     factory(WorkInProgressOp.Factory.class);
diff --git a/java/com/google/gerrit/server/schema/Schema_180.java b/java/com/google/gerrit/server/schema/Schema_180.java
index 6912b3e..7e13d0d 100644
--- a/java/com/google/gerrit/server/schema/Schema_180.java
+++ b/java/com/google/gerrit/server/schema/Schema_180.java
@@ -14,6 +14,15 @@
 
 package com.google.gerrit.server.schema;
 
+/**
+ * Schema 180 for Gerrit metadata.
+ *
+ * <p>180 is the first schema version that is supported by NoteDb. All previous schema versions were
+ * for ReviewDb. Since ReviewDb no longer exists those schema versions have been deleted.
+ *
+ * <p>Upgrading to this schema version creates the {@code refs/meta/version} ref in NoteDb that
+ * stores the number of the current schema version.
+ */
 public class Schema_180 implements NoteDbSchemaVersion {
   @Override
   public void upgrade(Arguments args, UpdateUI ui) {
diff --git a/java/com/google/gerrit/server/schema/Schema_181.java b/java/com/google/gerrit/server/schema/Schema_181.java
index 3054ad3..d357ae7 100644
--- a/java/com/google/gerrit/server/schema/Schema_181.java
+++ b/java/com/google/gerrit/server/schema/Schema_181.java
@@ -17,10 +17,16 @@
 import com.google.gerrit.gpg.PublicKeyStore;
 import org.eclipse.jgit.lib.Repository;
 
+/**
+ * Schema 181 for Gerrit metadata.
+ *
+ * <p>Upgrading to this schema version populates the GPG subkey to master key map (see {@link
+ * PublicKeyStore}.
+ */
 public class Schema_181 implements NoteDbSchemaVersion {
   @Override
   public void upgrade(Arguments args, UpdateUI ui) throws Exception {
-    ui.message("Rebuild GPGP note map to build subkey to master key map");
+    ui.message("Rebuild GPG note map to build subkey to master key map");
     try (Repository repo = args.repoManager.openRepository(args.allUsers);
         PublicKeyStore store = new PublicKeyStore(repo)) {
       store.rebuildSubkeyMasterKeyMap();
diff --git a/java/com/google/gerrit/server/submit/MergeSuperSet.java b/java/com/google/gerrit/server/submit/MergeSuperSet.java
index b36eb7d..f8540fb 100644
--- a/java/com/google/gerrit/server/submit/MergeSuperSet.java
+++ b/java/com/google/gerrit/server/submit/MergeSuperSet.java
@@ -18,7 +18,6 @@
 import static java.util.Objects.requireNonNull;
 
 import com.google.common.base.Strings;
-import com.google.common.collect.Iterables;
 import com.google.common.flogger.FluentLogger;
 import com.google.gerrit.entities.Change;
 import com.google.gerrit.extensions.registration.DynamicItem;
@@ -58,6 +57,7 @@
 public class MergeSuperSet {
   private static final FluentLogger logger = FluentLogger.forEnclosingClass();
 
+  private final ChangeData.Factory changeDataFactory;
   private final Provider<InternalChangeQuery> queryProvider;
   private final Provider<MergeOpRepoManager> repoManagerProvider;
   private final DynamicItem<MergeSuperSetComputation> mergeSuperSetComputation;
@@ -72,6 +72,7 @@
   @Inject
   MergeSuperSet(
       @GerritServerConfig Config cfg,
+      ChangeData.Factory changeDataFactory,
       Provider<InternalChangeQuery> queryProvider,
       Provider<MergeOpRepoManager> repoManagerProvider,
       DynamicItem<MergeSuperSetComputation> mergeSuperSetComputation,
@@ -79,6 +80,7 @@
       ProjectCache projectCache,
       ChangeNotes.Factory notesFactory) {
     this.cfg = cfg;
+    this.changeDataFactory = changeDataFactory;
     this.queryProvider = queryProvider;
     this.repoManagerProvider = repoManagerProvider;
     this.mergeSuperSetComputation = mergeSuperSetComputation;
@@ -105,14 +107,7 @@
         orm = repoManagerProvider.get();
         closeOrm = true;
       }
-      List<ChangeData> cds = queryProvider.get().byLegacyChangeId(change.getId());
-      checkState(
-          cds.size() == 1,
-          "Expected exactly one ChangeData for change ID %s, got %s",
-          change.getId(),
-          cds.size());
-      ChangeData cd = Iterables.getFirst(cds, null);
-
+      ChangeData cd = changeDataFactory.create(change.getProject(), change.getId());
       boolean visible = false;
       if (cd != null) {
         ProjectState projectState = projectCache.checkedGet(cd.project());
diff --git a/java/gerrit/AbstractCommitUserIdentityPredicate.java b/java/gerrit/AbstractCommitUserIdentityPredicate.java
index bd8cf1a..51c4a3b 100644
--- a/java/gerrit/AbstractCommitUserIdentityPredicate.java
+++ b/java/gerrit/AbstractCommitUserIdentityPredicate.java
@@ -30,6 +30,20 @@
 import java.io.IOException;
 import org.eclipse.jgit.lib.PersonIdent;
 
+/**
+ * Abstract Prolog predicate for a Git person identity of a change.
+ *
+ * <p>Checks that the terms that are provided as input to this Prolog predicate match a Git person
+ * identity of the change (either author or committer).
+ *
+ * <p>The terms that are provided as input to this Prolog predicate are:
+ *
+ * <ul>
+ *   <li>a user ID term that matches the account ID of the Git person identity
+ *   <li>a string atom that matches the full name of the Git person identity
+ *   <li>a string atom that matches the email of the Git person identity
+ * </ul>
+ */
 abstract class AbstractCommitUserIdentityPredicate extends Predicate.P3 {
   private static final SymbolTerm user = SymbolTerm.intern("user", 1);
   private static final SymbolTerm anonymous = SymbolTerm.intern("anonymous");
diff --git a/java/gerrit/PRED_change_branch_1.java b/java/gerrit/PRED_change_branch_1.java
index 4501169..62744f7 100644
--- a/java/gerrit/PRED_change_branch_1.java
+++ b/java/gerrit/PRED_change_branch_1.java
@@ -23,6 +23,16 @@
 import com.googlecode.prolog_cafe.lang.SymbolTerm;
 import com.googlecode.prolog_cafe.lang.Term;
 
+/**
+ * Prolog predicate for the destination branch of a change.
+ *
+ * <p>Checks that the term that is provided as input to this Prolog predicate is a string atom that
+ * matches the destination branch of the change.
+ *
+ * <pre>
+ *   'change_branch'(-Branch)
+ * </pre>
+ */
 public class PRED_change_branch_1 extends Predicate.P1 {
   public PRED_change_branch_1(Term a1, Operation n) {
     arg1 = a1;
diff --git a/java/gerrit/PRED_change_owner_1.java b/java/gerrit/PRED_change_owner_1.java
index d42c0e1..f6fbb80 100644
--- a/java/gerrit/PRED_change_owner_1.java
+++ b/java/gerrit/PRED_change_owner_1.java
@@ -25,6 +25,16 @@
 import com.googlecode.prolog_cafe.lang.SymbolTerm;
 import com.googlecode.prolog_cafe.lang.Term;
 
+/**
+ * Prolog predicate for the owner of a change.
+ *
+ * <p>Checks that the term that is provided as input to this Prolog predicate is a user ID term that
+ * matches the account ID of the change owner.
+ *
+ * <pre>
+ *   'change_owner'(user(-ID))
+ * </pre>
+ */
 public class PRED_change_owner_1 extends Predicate.P1 {
   private static final SymbolTerm user = SymbolTerm.intern("user", 1);
 
diff --git a/java/gerrit/PRED_change_project_1.java b/java/gerrit/PRED_change_project_1.java
index a973e1c..b2ef109 100644
--- a/java/gerrit/PRED_change_project_1.java
+++ b/java/gerrit/PRED_change_project_1.java
@@ -23,6 +23,16 @@
 import com.googlecode.prolog_cafe.lang.SymbolTerm;
 import com.googlecode.prolog_cafe.lang.Term;
 
+/**
+ * Prolog predicate for the project of a change.
+ *
+ * <p>Checks that the term that is provided as input to this Prolog predicate is a string atom that
+ * matches the project of the change.
+ *
+ * <pre>
+ *   'change_project'(-Project)
+ * </pre>
+ */
 public class PRED_change_project_1 extends Predicate.P1 {
   public PRED_change_project_1(Term a1, Operation n) {
     arg1 = a1;
diff --git a/java/gerrit/PRED_change_topic_1.java b/java/gerrit/PRED_change_topic_1.java
index 11d737a..f0175ef 100644
--- a/java/gerrit/PRED_change_topic_1.java
+++ b/java/gerrit/PRED_change_topic_1.java
@@ -23,6 +23,16 @@
 import com.googlecode.prolog_cafe.lang.SymbolTerm;
 import com.googlecode.prolog_cafe.lang.Term;
 
+/**
+ * Prolog predicate for the topic of a change.
+ *
+ * <p>Checks that the term that is provided as input to this Prolog predicate is a string atom that
+ * matches the topic of the change.
+ *
+ * <pre>
+ *   'change_topic'(-Topic)
+ * </pre>
+ */
 public class PRED_change_topic_1 extends Predicate.P1 {
   public PRED_change_topic_1(Term a1, Operation n) {
     arg1 = a1;
diff --git a/java/gerrit/PRED_commit_author_3.java b/java/gerrit/PRED_commit_author_3.java
index 998b30e..3381344 100644
--- a/java/gerrit/PRED_commit_author_3.java
+++ b/java/gerrit/PRED_commit_author_3.java
@@ -21,6 +21,27 @@
 import com.googlecode.prolog_cafe.lang.Term;
 import org.eclipse.jgit.revwalk.RevCommit;
 
+/**
+ * Prolog predicate for the Git author of the current patch set of a change.
+ *
+ * <p>Checks that the terms that are provided as input to this Prolog predicate match the Git author
+ * of the current patch set of the change.
+ *
+ * <p>The terms that are provided as input to this Prolog predicate are:
+ *
+ * <ul>
+ *   <li>a user ID term that matches the account ID of the Git author of the current patch set of
+ *       the change
+ *   <li>a string atom that matches the full name of the Git author of the current patch set of the
+ *       change
+ *   <li>a string atom that matches the email of the Git author of the current patch set of the
+ *       change
+ * </ul>
+ *
+ * <pre>
+ *   'commit_author'(user(-ID), -FullName, -Email)
+ * </pre>
+ */
 public class PRED_commit_author_3 extends AbstractCommitUserIdentityPredicate {
   public PRED_commit_author_3(Term a1, Term a2, Term a3, Operation n) {
     super(a1, a2, a3, n);
diff --git a/java/gerrit/PRED_commit_committer_3.java b/java/gerrit/PRED_commit_committer_3.java
index 293d8ce..1757336 100644
--- a/java/gerrit/PRED_commit_committer_3.java
+++ b/java/gerrit/PRED_commit_committer_3.java
@@ -21,6 +21,27 @@
 import com.googlecode.prolog_cafe.lang.Term;
 import org.eclipse.jgit.revwalk.RevCommit;
 
+/**
+ * Prolog predicate for the Git committer of the current patch set of a change.
+ *
+ * <p>Checks that the terms that are provided as input to this Prolog predicate match the Git
+ * committer of the current patch set of the change.
+ *
+ * <p>The terms that are provided as input to this Prolog predicate are:
+ *
+ * <ul>
+ *   <li>a user ID term that matches the account ID of the Git committer of the current patch set of
+ *       the change
+ *   <li>a string atom that matches the full name of the Git committer of the current patch set of
+ *       the change
+ *   <li>a string atom that matches the email of the Git committer of the current patch set of the
+ *       change
+ * </ul>
+ *
+ * <pre>
+ *   'commit_committer'(user(-ID), -FullName, -Email)
+ * </pre>
+ */
 public class PRED_commit_committer_3 extends AbstractCommitUserIdentityPredicate {
   public PRED_commit_committer_3(Term a1, Term a2, Term a3, Operation n) {
     super(a1, a2, a3, n);
diff --git a/java/gerrit/PRED_commit_message_1.java b/java/gerrit/PRED_commit_message_1.java
index eb996d6..3485af6 100644
--- a/java/gerrit/PRED_commit_message_1.java
+++ b/java/gerrit/PRED_commit_message_1.java
@@ -24,7 +24,10 @@
 import org.eclipse.jgit.revwalk.RevCommit;
 
 /**
- * Returns the commit message as a symbol
+ * Prolog predicate for the commit message of a change.
+ *
+ * <p>Checks that the term that is provided as input to this Prolog predicate is a string atom that
+ * matches the commit message of the change.
  *
  * <pre>
  *   'commit_message'(-Msg)
diff --git a/java/gerrit/PRED_project_default_submit_type_1.java b/java/gerrit/PRED_project_default_submit_type_1.java
index d70a9e4..77a0261 100644
--- a/java/gerrit/PRED_project_default_submit_type_1.java
+++ b/java/gerrit/PRED_project_default_submit_type_1.java
@@ -24,6 +24,16 @@
 import com.googlecode.prolog_cafe.lang.SymbolTerm;
 import com.googlecode.prolog_cafe.lang.Term;
 
+/**
+ * Prolog predicate for the default submit type of the project of a change.
+ *
+ * <p>Checks that the term that is provided as input to this Prolog predicate is a string atom that
+ * matches the default submit type of the change's project.
+ *
+ * <pre>
+ *   'project_default_submit_type'(-SubmitType)
+ * </pre>
+ */
 public class PRED_project_default_submit_type_1 extends Predicate.P1 {
 
   private static final SymbolTerm[] term;
diff --git a/java/gerrit/PRED_pure_revert_1.java b/java/gerrit/PRED_pure_revert_1.java
index 6300a668..19e7b68 100644
--- a/java/gerrit/PRED_pure_revert_1.java
+++ b/java/gerrit/PRED_pure_revert_1.java
@@ -22,7 +22,17 @@
 import com.googlecode.prolog_cafe.lang.Prolog;
 import com.googlecode.prolog_cafe.lang.Term;
 
-/** Checks if change is a pure revert of the change it references in 'revertOf'. */
+/**
+ * Prolog Predicate that checks if the change is a pure revert of the change it references in
+ * 'revertOf'.
+ *
+ * <p>The input is an integer atom where '1' represents a pure revert and '0' represents a non-pure
+ * revert.
+ *
+ * <pre>
+ *   'pure_revert'(-PureRevert)
+ * </pre>
+ */
 public class PRED_pure_revert_1 extends Predicate.P1 {
   public PRED_pure_revert_1(Term a1, Operation n) {
     arg1 = a1;
diff --git a/java/gerrit/PRED_unresolved_comments_count_1.java b/java/gerrit/PRED_unresolved_comments_count_1.java
index d4abcc54..9a1fcca 100644
--- a/java/gerrit/PRED_unresolved_comments_count_1.java
+++ b/java/gerrit/PRED_unresolved_comments_count_1.java
@@ -22,6 +22,16 @@
 import com.googlecode.prolog_cafe.lang.Prolog;
 import com.googlecode.prolog_cafe.lang.Term;
 
+/**
+ * Prolog predicate for the number of unresolved comments of a change.
+ *
+ * <p>Checks that the term that is provided as input to this Prolog predicate is an integer atom
+ * that matches the number of unresolved comments of the change.
+ *
+ * <pre>
+ *   'unresolved_comments_count'(-NumberOfUnresolvedComments)
+ * </pre>
+ */
 public class PRED_unresolved_comments_count_1 extends Predicate.P1 {
   public PRED_unresolved_comments_count_1(Term a1, Operation n) {
     arg1 = a1;
diff --git a/java/gerrit/PRED_uploader_1.java b/java/gerrit/PRED_uploader_1.java
index 681d86c..89e367e 100644
--- a/java/gerrit/PRED_uploader_1.java
+++ b/java/gerrit/PRED_uploader_1.java
@@ -27,6 +27,16 @@
 import com.googlecode.prolog_cafe.lang.SymbolTerm;
 import com.googlecode.prolog_cafe.lang.Term;
 
+/**
+ * Prolog predicate for the uploader of the current patch set of a change.
+ *
+ * <p>Checks that the term that is provided as input to this Prolog predicate is a user ID term that
+ * matches the account ID of the uploader of the current patch set.
+ *
+ * <pre>
+ *   'uploader'(user(-ID))
+ * </pre>
+ */
 public class PRED_uploader_1 extends Predicate.P1 {
   private static final FluentLogger logger = FluentLogger.forEnclosingClass();
 
diff --git a/javatests/com/google/gerrit/acceptance/api/revision/RevisionDiffIT.java b/javatests/com/google/gerrit/acceptance/api/revision/RevisionDiffIT.java
index d4a4c45..717d3cc 100644
--- a/javatests/com/google/gerrit/acceptance/api/revision/RevisionDiffIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/revision/RevisionDiffIT.java
@@ -21,6 +21,7 @@
 import static com.google.gerrit.extensions.common.testing.DiffInfoSubject.assertThat;
 import static com.google.gerrit.extensions.common.testing.FileInfoSubject.assertThat;
 import static com.google.gerrit.git.ObjectIds.abbreviateName;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
 import static java.util.stream.Collectors.joining;
 import static java.util.stream.Collectors.toMap;
 
@@ -41,6 +42,7 @@
 import com.google.gerrit.extensions.common.ChangeType;
 import com.google.gerrit.extensions.common.DiffInfo;
 import com.google.gerrit.extensions.common.FileInfo;
+import com.google.gerrit.extensions.restapi.BadRequestException;
 import com.google.gerrit.extensions.restapi.BinaryResult;
 import com.google.gerrit.extensions.restapi.RestApiException;
 import com.google.gerrit.testing.ConfigSuite;
@@ -2718,6 +2720,23 @@
     assertThat(diffInfo).content().element(0).numberOfSkippedLines().isGreaterThan(0);
   }
 
+  @Test
+  public void editNotAllowedAsBase() throws Exception {
+    gApi.changes().id(changeId).edit().create();
+
+    BadRequestException e =
+        assertThrows(
+            BadRequestException.class,
+            () -> getDiffRequest(changeId, CURRENT, FILE_NAME).withBase("edit").get());
+    assertThat(e).hasMessageThat().isEqualTo("edit not allowed as base");
+
+    e =
+        assertThrows(
+            BadRequestException.class,
+            () -> getDiffRequest(changeId, CURRENT, FILE_NAME).withBase("0").get());
+    assertThat(e).hasMessageThat().isEqualTo("edit not allowed as base");
+  }
+
   private static CommentInput createCommentInput(
       int startLine, int startCharacter, int endLine, int endCharacter, String message) {
     CommentInput comment = new CommentInput();
diff --git a/javatests/com/google/gerrit/acceptance/api/revision/RevisionIT.java b/javatests/com/google/gerrit/acceptance/api/revision/RevisionIT.java
index 61d0fd5..c4dec31 100644
--- a/javatests/com/google/gerrit/acceptance/api/revision/RevisionIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/revision/RevisionIT.java
@@ -337,6 +337,9 @@
 
     assertThat(cherry.get().subject).contains(in.message);
     assertThat(cherry.get().topic).isEqualTo("someTopic-foo");
+    assertThat(cherry.get().cherryPickOfChange).isEqualTo(orig.get()._number);
+    assertThat(cherry.get().cherryPickOfPatchSet).isEqualTo(1);
+
     cherry.current().review(ReviewInput.approve());
     cherry.current().submit();
   }
@@ -416,23 +419,23 @@
     ChangeApi orig = gApi.changes().id(project.get() + "~master~" + r.getChangeId());
 
     ChangeApi cherry = orig.revision(r.getCommit().name()).cherryPick(in);
+    assertThat(cherry.get().cherryPickOfChange).isEqualTo(orig.get()._number);
+    assertThat(cherry.get().cherryPickOfPatchSet).isEqualTo(1);
     assertThat(cherry.get().workInProgress).isTrue();
   }
 
   @Test
   public void cherryPickToSameBranch() throws Exception {
     PushOneCommit.Result r = createChange();
+    ChangeApi change = gApi.changes().id(project.get() + "~master~" + r.getChangeId());
     CherryPickInput in = new CherryPickInput();
     in.destination = "master";
     in.message = "it generates a new patch set\n\nChange-Id: " + r.getChangeId();
-    ChangeInfo cherryInfo =
-        gApi.changes()
-            .id(project.get() + "~master~" + r.getChangeId())
-            .revision(r.getCommit().name())
-            .cherryPick(in)
-            .get();
+    ChangeInfo cherryInfo = change.revision(r.getCommit().name()).cherryPick(in).get();
     assertThat(cherryInfo.messages).hasSize(2);
     Iterator<ChangeMessageInfo> cherryIt = cherryInfo.messages.iterator();
+    assertThat(cherryInfo.cherryPickOfChange).isEqualTo(change.get()._number);
+    assertThat(cherryInfo.cherryPickOfPatchSet).isEqualTo(1);
     assertThat(cherryIt.next().message).isEqualTo("Uploaded patch set 1.");
     assertThat(cherryIt.next().message).isEqualTo("Uploaded patch set 2.");
   }
@@ -625,6 +628,42 @@
   }
 
   @Test
+  public void cherryPickToExistingChangeUpdatesCherryPickOf() throws Exception {
+    PushOneCommit.Result r1 =
+        pushFactory
+            .create(admin.newIdent(), testRepo, SUBJECT, FILE_NAME, "a")
+            .to("refs/for/master");
+    String t1 = project.get() + "~master~" + r1.getChangeId();
+    ChangeApi orig = gApi.changes().id(project.get() + "~master~" + r1.getChangeId());
+
+    BranchInput bin = new BranchInput();
+    bin.revision = r1.getCommit().getParent(0).name();
+    gApi.projects().name(project.get()).branch("foo").create(bin);
+
+    PushOneCommit.Result r2 =
+        pushFactory
+            .create(admin.newIdent(), testRepo, SUBJECT, FILE_NAME, "b", r1.getChangeId())
+            .to("refs/for/foo");
+    String t2 = project.get() + "~foo~" + r2.getChangeId();
+
+    CherryPickInput in = new CherryPickInput();
+    in.destination = "foo";
+    in.message = r1.getCommit().getFullMessage();
+    ChangeApi cherry = gApi.changes().id(t1).current().cherryPick(in);
+    assertThat(get(t2, ALL_REVISIONS).revisions).hasSize(2);
+    assertThat(cherry.get().cherryPickOfChange).isEqualTo(orig.get()._number);
+    assertThat(cherry.get().cherryPickOfPatchSet).isEqualTo(1);
+
+    PushOneCommit.Result r3 = amendChange(r1.getChangeId(), SUBJECT, "b.txt", "b");
+    in = new CherryPickInput();
+    in.destination = "foo";
+    in.message = r3.getCommit().getFullMessage();
+    cherry = gApi.changes().id(t1).current().cherryPick(in);
+    assertThat(cherry.get().cherryPickOfChange).isEqualTo(orig.get()._number);
+    assertThat(cherry.get().cherryPickOfPatchSet).isEqualTo(2);
+  }
+
+  @Test
   public void cherryPickToExistingChange() throws Exception {
     PushOneCommit.Result r1 =
         pushFactory
diff --git a/javatests/com/google/gerrit/acceptance/edit/ChangeEditIT.java b/javatests/com/google/gerrit/acceptance/edit/ChangeEditIT.java
index bf93d58..b0f183e 100644
--- a/javatests/com/google/gerrit/acceptance/edit/ChangeEditIT.java
+++ b/javatests/com/google/gerrit/acceptance/edit/ChangeEditIT.java
@@ -520,6 +520,14 @@
   }
 
   @Test
+  public void changeEditNoContentProvidedRest() throws Exception {
+    createEmptyEditFor(changeId);
+    adminRestSession
+        .put(urlEditFile(changeId, FILE_NAME), new FileContentInput())
+        .assertBadRequest();
+  }
+
+  @Test
   public void emptyPutRequest() throws Exception {
     createEmptyEditFor(changeId);
     adminRestSession.put(urlEditFile(changeId, FILE_NAME)).assertNoContent();
diff --git a/javatests/com/google/gerrit/entities/converter/ChangeProtoConverterTest.java b/javatests/com/google/gerrit/entities/converter/ChangeProtoConverterTest.java
index 72e4a7a..bc669cc 100644
--- a/javatests/com/google/gerrit/entities/converter/ChangeProtoConverterTest.java
+++ b/javatests/com/google/gerrit/entities/converter/ChangeProtoConverterTest.java
@@ -335,6 +335,7 @@
                 .put("workInProgress", boolean.class)
                 .put("reviewStarted", boolean.class)
                 .put("revertOf", Change.Id.class)
+                .put("cherryPickOf", PatchSet.Id.class)
                 .build());
   }
 
diff --git a/javatests/com/google/gerrit/server/notedb/ChangeNotesStateTest.java b/javatests/com/google/gerrit/server/notedb/ChangeNotesStateTest.java
index 6ece894..16981dc 100644
--- a/javatests/com/google/gerrit/server/notedb/ChangeNotesStateTest.java
+++ b/javatests/com/google/gerrit/server/notedb/ChangeNotesStateTest.java
@@ -776,6 +776,7 @@
                 .put("workInProgress", boolean.class)
                 .put("reviewStarted", boolean.class)
                 .put("revertOf", Change.Id.class)
+                .put("cherryPickOf", PatchSet.Id.class)
                 .put("toBuilder", ChangeNotesState.ChangeColumns.Builder.class)
                 .build());
   }
diff --git a/javatests/com/google/gerrit/server/update/BatchUpdateTest.java b/javatests/com/google/gerrit/server/update/BatchUpdateTest.java
index c7ca887..083493d 100644
--- a/javatests/com/google/gerrit/server/update/BatchUpdateTest.java
+++ b/javatests/com/google/gerrit/server/update/BatchUpdateTest.java
@@ -19,6 +19,7 @@
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.gerrit.testing.GerritJUnit.assertThrows;
 
+import com.google.common.cache.Cache;
 import com.google.common.collect.ImmutableList;
 import com.google.gerrit.common.Nullable;
 import com.google.gerrit.common.data.SubmitRecord;
@@ -35,10 +36,13 @@
 import com.google.gerrit.server.notedb.ChangeNotes;
 import com.google.gerrit.server.notedb.ChangeUpdate;
 import com.google.gerrit.server.notedb.Sequences;
+import com.google.gerrit.server.patch.DiffSummary;
+import com.google.gerrit.server.patch.DiffSummaryKey;
 import com.google.gerrit.server.util.time.TimeUtil;
 import com.google.gerrit.testing.InMemoryTestEnvironment;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
+import com.google.inject.name.Named;
 import org.eclipse.jgit.junit.TestRepository;
 import org.eclipse.jgit.lib.Config;
 import org.eclipse.jgit.lib.ObjectId;
@@ -57,8 +61,9 @@
       new InMemoryTestEnvironment(
           () -> {
             Config cfg = new Config();
-            cfg.setInt("change", null, "maxUpdates", MAX_UPDATES);
+            cfg.setInt("change", null, "maxFiles", 2);
             cfg.setInt("change", null, "maxPatchSets", MAX_PATCH_SETS);
+            cfg.setInt("change", null, "maxUpdates", MAX_UPDATES);
             return cfg;
           });
 
@@ -70,6 +75,9 @@
   @Inject private Provider<CurrentUser> user;
   @Inject private Sequences sequences;
 
+  @Inject
+  private @Named("diff_summary") Cache<DiffSummaryKey, DiffSummary> diffSummaryCache;
+
   private Project.NameKey project;
   private TestRepository<Repository> repo;
 
@@ -243,6 +251,59 @@
     assertThat(getMetaId(changeId)).isEqualTo(oldMetaId);
   }
 
+  @Test
+  public void limitFileCount_exceed() throws Exception {
+    Change.Id changeId = createChangeWithUpdates(1);
+    ChangeNotes notes = changeNotesFactory.create(project, changeId);
+
+    try (BatchUpdate bu = batchUpdateFactory.create(project, user.get(), TimeUtil.nowTs())) {
+      ObjectId commitId =
+          repo.amend(notes.getCurrentPatchSet().commitId())
+              .add("bar.txt", "bar")
+              .add("baz.txt", "baz")
+              .add("boom.txt", "boom")
+              .message("blah")
+              .create();
+      bu.addOp(
+          changeId,
+          patchSetInserterFactory
+              .create(notes, PatchSet.id(changeId, 2), commitId)
+              .setMessage("blah"));
+      ResourceConflictException thrown = assertThrows(ResourceConflictException.class, bu::execute);
+      assertThat(thrown)
+          .hasMessageThat()
+          .contains("Exceeding maximum number of files per change (3 > 2)");
+    }
+  }
+
+  @Test
+  public void limitFileCount_cacheKeyMatches() throws Exception {
+    Change.Id changeId = createChangeWithUpdates(1);
+    ChangeNotes notes = changeNotesFactory.create(project, changeId);
+
+    int cacheSizeBefore = diffSummaryCache.asMap().size();
+
+    // We don't want to depend on the test helper used above so we perform an explicit commit here.
+    try (BatchUpdate bu = batchUpdateFactory.create(project, user.get(), TimeUtil.nowTs())) {
+      ObjectId commitId =
+          repo.amend(notes.getCurrentPatchSet().commitId())
+              .add("bar.txt", "bar")
+              .add("baz.txt", "baz")
+              .message("blah")
+              .create();
+      bu.addOp(
+          changeId,
+          patchSetInserterFactory
+              .create(notes, PatchSet.id(changeId, 3), commitId)
+              .setMessage("blah"));
+      bu.execute();
+    }
+
+    // Assert that we only performed the diff computation once. This would e.g. catch
+    // bugs/deviations in the computation of the cache key.
+    assertThat(diffSummaryCache.asMap()).hasSize(cacheSizeBefore + 1);
+  }
+
   private Change.Id createChangeWithUpdates(int totalUpdates) throws Exception {
     checkArgument(totalUpdates > 0);
     checkArgument(totalUpdates <= MAX_UPDATES);
diff --git a/plugins/replication b/plugins/replication
index b3ed3c8..90ca234 160000
--- a/plugins/replication
+++ b/plugins/replication
@@ -1 +1 @@
-Subproject commit b3ed3c8f9a8bab51beed794de5be8fd6da44231a
+Subproject commit 90ca234c456767b79608ad2254f3e80f02f308ca
diff --git a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.html b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.html
index 539b1b6..9073342 100644
--- a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.html
+++ b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.html
@@ -270,6 +270,19 @@
           </template>
         </span>
       </section>
+      <template is="dom-if" if="[[_showCherryPickOf(change.*)]]">
+        <section>
+          <span class="title">Cherry pick of</span>
+          <span class="value">
+            <a href$="[[_computeCherryPickOfURL(change.cherry_pick_of_change, change.cherry_pick_of_patch_set, change.project)]]">
+              <gr-limited-text
+                  text="[[change.cherry_pick_of_change]],[[change.cherry_pick_of_patch_set]]"
+                  limit="40">
+              </gr-limited-text>
+            </a>
+          </span>
+        </section>
+      </template>
       <section class="strategy" hidden$="[[_computeHideStrategy(change)]]" hidden>
         <span class="title">Strategy</span>
         <span class="value">[[_computeStrategy(change)]]</span>
diff --git a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.js b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.js
index 94519b0..2eab284 100644
--- a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.js
+++ b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.js
@@ -227,6 +227,13 @@
       return hasTopic && !settingTopic;
     }
 
+    _showCherryPickOf(changeRecord) {
+      const hasCherryPickOf = !!changeRecord &&
+          !!changeRecord.base && !!changeRecord.base.cherry_pick_of_change &&
+          !!changeRecord.base.cherry_pick_of_patch_set;
+      return hasCherryPickOf;
+    }
+
     _handleHashtagChanged(e) {
       const lastHashtag = this.change.hashtag;
       if (!this._newHashtag.length) { return; }
@@ -353,6 +360,10 @@
             this.change.status.toLowerCase());
     }
 
+    _computeCherryPickOfURL(change, patchset, project) {
+      return Gerrit.Nav.getUrlForChangeById(change, project, patchset);
+    }
+
     _computeTopicURL(topic) {
       return Gerrit.Nav.getUrlForTopic(topic);
     }
diff --git a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata_test.html b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata_test.html
index 148d917..09cb4cc 100644
--- a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata_test.html
+++ b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata_test.html
@@ -464,6 +464,22 @@
       assert.isTrue(element._showTopicChip({base: {topic: 'foo'}}, false));
     });
 
+    test('_showCherryPickOf', () => {
+      assert.isFalse(element._showCherryPickOf(null));
+      assert.isFalse(element._showCherryPickOf({
+        base: {
+          cherry_pick_of_change: null,
+          cherry_pick_of_patch_set: null,
+        },
+      }));
+      assert.isTrue(element._showCherryPickOf({
+        base: {
+          cherry_pick_of_change: 123,
+          cherry_pick_of_patch_set: 1,
+        },
+      }));
+    });
+
     suite('Topic removal', () => {
       let change;
       setup(() => {
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-abandon-dialog/gr-confirm-abandon-dialog.html b/polygerrit-ui/app/elements/change/gr-confirm-abandon-dialog/gr-confirm-abandon-dialog.html
index 05a2bb2..9e7857c4 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-abandon-dialog/gr-confirm-abandon-dialog.html
+++ b/polygerrit-ui/app/elements/change/gr-confirm-abandon-dialog/gr-confirm-abandon-dialog.html
@@ -45,14 +45,7 @@
         font-family: var(--monospace-font-family);
         font-size: var(--font-size-mono);
         line-height: var(--line-height-mono);
-        padding: 0;
         width: 73ch; /* Add a char to account for the border. */
-
-        --iron-autogrow-textarea {
-          border: 1px solid var(--border-color);
-          box-sizing: border-box;
-          font-family: var(--monospace-font-family);
-        }
       }
     </style>
     <gr-dialog
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog.html b/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog.html
index 8ddcd83..cab9fd6 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog.html
+++ b/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog.html
@@ -47,18 +47,11 @@
         display: block;
         width: 100%;
       }
-      .main .message {
-        width: 100%;
-      }
       iron-autogrow-textarea {
         font-family: var(--monospace-font-family);
         font-size: var(--font-size-mono);
         line-height: var(--line-height-mono);
-        padding: 0;
-        --iron-autogrow-textarea: {
-          font-family: var(--monospace-font-family);
-          width: 72ch;
-        };
+        width: 73ch; /* Add a char to account for the border. */
       }
     </style>
     <gr-dialog
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-move-dialog/gr-confirm-move-dialog.html b/polygerrit-ui/app/elements/change/gr-confirm-move-dialog/gr-confirm-move-dialog.html
index 24c1132..f65ec03 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-move-dialog/gr-confirm-move-dialog.html
+++ b/polygerrit-ui/app/elements/change/gr-confirm-move-dialog/gr-confirm-move-dialog.html
@@ -37,9 +37,6 @@
       label {
         cursor: pointer;
       }
-      iron-autogrow-textarea {
-        padding: 0;
-      }
       .main {
         display: flex;
         flex-direction: column;
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-revert-dialog/gr-confirm-revert-dialog.html b/polygerrit-ui/app/elements/change/gr-confirm-revert-dialog/gr-confirm-revert-dialog.html
index 2e1e6ae..fc3a8c1 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-revert-dialog/gr-confirm-revert-dialog.html
+++ b/polygerrit-ui/app/elements/change/gr-confirm-revert-dialog/gr-confirm-revert-dialog.html
@@ -41,14 +41,7 @@
         font-family: var(--monospace-font-family);
         font-size: var(--font-size-mono);
         line-height: var(--line-height-mono);
-        padding: 0;
         width: 73ch; /* Add a char to account for the border. */
-
-        --iron-autogrow-textarea {
-          border: 1px solid var(--border-color);
-          box-sizing: border-box;
-          font-family: var(--monospace-font-family);
-        }
       }
     </style>
     <gr-dialog
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-revert-submission-dialog/gr-confirm-revert-submission-dialog.html b/polygerrit-ui/app/elements/change/gr-confirm-revert-submission-dialog/gr-confirm-revert-submission-dialog.html
index 0107c33..f2cfef8 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-revert-submission-dialog/gr-confirm-revert-submission-dialog.html
+++ b/polygerrit-ui/app/elements/change/gr-confirm-revert-submission-dialog/gr-confirm-revert-submission-dialog.html
@@ -40,14 +40,9 @@
       }
       iron-autogrow-textarea {
         font-family: var(--monospace-font-family);
-        padding: 0;
+        font-size: var(--font-size-mono);
+        line-height: var(--line-height-mono);
         width: 73ch; /* Add a char to account for the border. */
-
-        --iron-autogrow-textarea {
-          border: 1px solid var(--border-color);
-          box-sizing: border-box;
-          font-family: var(--monospace-font-family);
-        }
       }
     </style>
     <gr-dialog
diff --git a/polygerrit-ui/app/elements/core/gr-reporting/gr-reporting.js b/polygerrit-ui/app/elements/core/gr-reporting/gr-reporting.js
index 5c98281..889f1f3 100644
--- a/polygerrit-ui/app/elements/core/gr-reporting/gr-reporting.js
+++ b/polygerrit-ui/app/elements/core/gr-reporting/gr-reporting.js
@@ -436,8 +436,13 @@
 
       // Finalize the interval. Either from a registered start mark or
       // the navigation start time (if baseTime is 0).
-      const startMark = baseTime === 0 ? undefined : `${name}-start`;
-      window.performance.measure(name, startMark);
+      if (baseTime !== 0) {
+        window.performance.measure(name, `${name}-start`);
+      } else {
+        // Microsft Edge does not handle the 2nd param correctly
+        // (if undefined).
+        window.performance.measure(name);
+      }
     },
 
     /**
diff --git a/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.js b/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.js
index 48e8662..ee44e06 100644
--- a/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.js
+++ b/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.js
@@ -29,6 +29,7 @@
     'cc:',
     'cc:self',
     'change:',
+    'cherrypickof:',
     'comment:',
     'commentby:',
     'commit:',
diff --git a/polygerrit-ui/app/elements/shared/gr-comment/gr-comment.html b/polygerrit-ui/app/elements/shared/gr-comment/gr-comment.html
index a96cf44..afd0e59 100644
--- a/polygerrit-ui/app/elements/shared/gr-comment/gr-comment.html
+++ b/polygerrit-ui/app/elements/shared/gr-comment/gr-comment.html
@@ -43,10 +43,6 @@
         display: block;
         font-family: var(--font-family);
         padding: var(--spacing-m);
-        --iron-autogrow-textarea: {
-          box-sizing: border-box;
-          padding: 2px;
-        };
       }
       :host([disabled]) {
         pointer-events: none;
diff --git a/polygerrit-ui/app/elements/shared/gr-confirm-delete-comment-dialog/gr-confirm-delete-comment-dialog.html b/polygerrit-ui/app/elements/shared/gr-confirm-delete-comment-dialog/gr-confirm-delete-comment-dialog.html
index 62ab307..e986ad0 100644
--- a/polygerrit-ui/app/elements/shared/gr-confirm-delete-comment-dialog/gr-confirm-delete-comment-dialog.html
+++ b/polygerrit-ui/app/elements/shared/gr-confirm-delete-comment-dialog/gr-confirm-delete-comment-dialog.html
@@ -45,14 +45,7 @@
         font-family: var(--monospace-font-family);
         font-size: var(--font-size-mono);
         line-height: var(--line-height-mono);
-        padding: 0;
         width: 73ch; /* Add a char to account for the border. */
-
-        --iron-autogrow-textarea {
-          border: 1px solid var(--border-color);
-          box-sizing: border-box;
-          font-family: var(--monospace-font-family);
-        }
       }
     </style>
     <gr-dialog
diff --git a/polygerrit-ui/app/elements/shared/gr-editable-content/gr-editable-content.html b/polygerrit-ui/app/elements/shared/gr-editable-content/gr-editable-content.html
index 45ddfc8..950be2a 100644
--- a/polygerrit-ui/app/elements/shared/gr-editable-content/gr-editable-content.html
+++ b/polygerrit-ui/app/elements/shared/gr-editable-content/gr-editable-content.html
@@ -41,9 +41,11 @@
         background-color: var(--view-background-color);
         width: 100%;
 
+        /* You have to also repeat everything from shared-styles here, because
+           you can only *replace* --iron-autogrow-textarea vars as a whole. */
         --iron-autogrow-textarea: {
-          padding: var(--spacing-m);
           box-sizing: border-box;
+          padding: var(--spacing-m);
           overflow-y: hidden;
           white-space: pre;
         };
diff --git a/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea.html b/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea.html
index 6803eb9..42a4f3b 100644
--- a/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea.html
+++ b/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea.html
@@ -59,11 +59,7 @@
         visibility: visible;
         white-space: normal;
       }
-      /*This is needed to not add a scroll bar on the side of gr-textarea
-      since there is 2px of padding in iron-autogrow-textarea for the
-      native textarea*/
       iron-autogrow-textarea {
-        padding: 2px;
         position: relative;
 
         /** This is needed for firefox */
diff --git a/polygerrit-ui/app/styles/gr-form-styles.html b/polygerrit-ui/app/styles/gr-form-styles.html
index 7c9ae0d..5133051 100644
--- a/polygerrit-ui/app/styles/gr-form-styles.html
+++ b/polygerrit-ui/app/styles/gr-form-styles.html
@@ -93,15 +93,8 @@
         width: 100%;
       }
       .gr-form-styles iron-autogrow-textarea {
-        border: none;
         height: auto;
         min-height: 4em;
-        --iron-autogrow-textarea: {
-          border: 1px solid var(--border-color);
-          border-radius: var(--border-radius);
-          box-sizing: border-box;
-          padding: var(--spacing-s);
-        }
       }
       .gr-form-styles gr-autocomplete {
         width: 14em;
diff --git a/polygerrit-ui/app/styles/shared-styles.html b/polygerrit-ui/app/styles/shared-styles.html
index 51b92e1..ce6bead 100644
--- a/polygerrit-ui/app/styles/shared-styles.html
+++ b/polygerrit-ui/app/styles/shared-styles.html
@@ -53,13 +53,15 @@
         color: var(--primary-text-color);
         border: 1px solid var(--border-color);
         border-radius: var(--border-radius);
+        padding: 0;
         box-sizing: border-box;
         /* iron-autogrow-textarea has a "-webkit-appearance: textarea" :host
            css rule, which prevents overriding the border color. Clear that. */
         -webkit-appearance: none;
 
         --iron-autogrow-textarea: {
-          padding: 4px;
+          box-sizing: border-box;
+          padding: var(--spacing-s);
         };
       }
       a {
diff --git a/polygerrit-ui/app/styles/themes/dark-theme.html b/polygerrit-ui/app/styles/themes/dark-theme.html
index cfc1f62..80243dc 100644
--- a/polygerrit-ui/app/styles/themes/dark-theme.html
+++ b/polygerrit-ui/app/styles/themes/dark-theme.html
@@ -98,8 +98,8 @@
       --light-rebased-add-highlight-color: #487165;
       --light-remove-add-highlight-color: #2f3f2f;
       --light-remove-highlight-color: #320404;
-      --coverage-covered: #e0f2f1;
-      --coverage-not-covered: #ffd1a4;
+      --coverage-covered: #112826;
+      --coverage-not-covered: #6b3600;
 
       /* syntax colors */
       --syntax-attr-color: #80cbbf;
diff --git a/proto/cache.proto b/proto/cache.proto
index 10e0216..8f73388 100644
--- a/proto/cache.proto
+++ b/proto/cache.proto
@@ -84,7 +84,7 @@
 
   int32 change_id = 2;
 
-  // Next ID: 24
+  // Next ID: 26
   message ChangeColumnsProto {
     string change_key = 1;
 
@@ -124,6 +124,9 @@
 
     int32 revert_of = 22;
     bool has_revert_of = 23;
+
+    string cherry_pick_of = 24;
+    bool has_cherry_pick_of = 25;
   }
   // Effectively required, even though the corresponding ChangeNotesState field
   // is optional, since the field is only absent when NoteDb is disabled, in
diff --git a/proto/entities.proto b/proto/entities.proto
index 374b47c..84c7fbd 100644
--- a/proto/entities.proto
+++ b/proto/entities.proto
@@ -31,7 +31,7 @@
 }
 
 // Serialized form of com.google.gerrit.entities.Change.
-// Next ID: 24
+// Next ID: 25
 message Change {
   required Change_Id change_id = 1;
   optional Change_Key change_key = 2;
@@ -51,6 +51,7 @@
   optional bool work_in_progress = 21;
   optional bool review_started = 22;
   optional Change_Id revert_of = 23;
+  optional PatchSet_Id cherry_pick_of = 24;
 
   // Deleted fields, should not be reused:
   reserved 6;    // sortkey