Merge "PolyGerrit: replace event.path with event.composedPath" into stable-3.3
diff --git a/.bazelrc b/.bazelrc
index 604a64e..71feca1 100644
--- a/.bazelrc
+++ b/.bazelrc
@@ -1,4 +1,4 @@
-build --workspace_status_command="python ./tools/workspace_status.py"
+build --workspace_status_command="python3 ./tools/workspace_status.py"
 build --repository_cache=~/.gerritcodereview/bazel-cache/repository
 build --action_env=PATH
 build --disk_cache=~/.gerritcodereview/bazel-cache/cas
diff --git a/.bazelversion b/.bazelversion
index fcdb2e1..6aba2b2 100644
--- a/.bazelversion
+++ b/.bazelversion
@@ -1 +1 @@
-4.0.0
+4.2.0
diff --git a/Documentation/dev-bazel.txt b/Documentation/dev-bazel.txt
index e6b7950..d5ed4ed 100644
--- a/Documentation/dev-bazel.txt
+++ b/Documentation/dev-bazel.txt
@@ -19,7 +19,7 @@
 
 * A Linux or macOS system (Windows is not supported at this time)
 * A JDK for Java 8|11|...
-* Python 2 or 3
+* Python 3
 * link:https://github.com/nodesource/distributions/blob/master/README.md[Node.js (including npm),role=external,window=_blank]
 * Bower (`npm install -g bower`)
 * link:https://docs.bazel.build/versions/master/install.html[Bazel,role=external,window=_blank] -launched with
diff --git a/Documentation/dev-plugins.txt b/Documentation/dev-plugins.txt
index a68c38b..f6e63d7 100644
--- a/Documentation/dev-plugins.txt
+++ b/Documentation/dev-plugins.txt
@@ -74,9 +74,9 @@
 manifest fields:
 
 ----
-  Implementation-Title: Example plugin showing examples
-  Implementation-Version: 1.0
-  Implementation-Vendor: Example, Inc.
+Implementation-Title: Example plugin showing examples
+Implementation-Version: 1.0
+Implementation-Vendor: Example, Inc.
 ----
 
 === ApiType
@@ -88,7 +88,7 @@
 loading a plugin that needs the plugin API.
 
 ----
-  Gerrit-ApiType: plugin
+Gerrit-ApiType: plugin
 ----
 
 === Explicit Registration
@@ -103,9 +103,9 @@
 `@Listen` and `@Export("")` annotations.
 
 ----
-  Gerrit-Module:     tld.example.project.CoreModuleClassName
-  Gerrit-SshModule:  tld.example.project.SshModuleClassName
-  Gerrit-HttpModule: tld.example.project.HttpModuleClassName
+Gerrit-Module:     tld.example.project.CoreModuleClassName
+Gerrit-SshModule:  tld.example.project.SshModuleClassName
+Gerrit-HttpModule: tld.example.project.HttpModuleClassName
 ----
 
 === Batch runtime
@@ -120,7 +120,7 @@
 offline reindexing task.
 
 ----
-  Gerrit-BatchModule: tld.example.project.CoreModuleClassName
+Gerrit-BatchModule: tld.example.project.CoreModuleClassName
 ----
 
 In this runtime, only the module designated by `Gerrit-BatchModule` is
@@ -132,7 +132,7 @@
 A plugin can optionally provide its own plugin name.
 
 ----
-  Gerrit-PluginName: replication
+Gerrit-PluginName: replication
 ----
 
 This is useful for plugins that contribute plugin-owned capabilities that
@@ -218,7 +218,7 @@
 with no down time.
 
 ----
-  Gerrit-ReloadMode: restart
+Gerrit-ReloadMode: restart
 ----
 
 In either mode ('restart' or 'reload') any plugin or extension can
@@ -261,7 +261,7 @@
 credentials and possibly verify connectivity to validate them.
 
 ----
-  Gerrit-InitStep: tld.example.project.MyInitStep
+Gerrit-InitStep: tld.example.project.MyInitStep
 ----
 
 MyInitStep needs to follow the standard Gerrit InitStep syntax
@@ -278,37 +278,37 @@
 
 [source,java]
 ----
-  public class MyInitStep implements InitStep {
-    private final String pluginName;
-    private final ConsoleUI ui;
-    private final AllProjectsConfig allProjectsConfig;
+public class MyInitStep implements InitStep {
+  private final String pluginName;
+  private final ConsoleUI ui;
+  private final AllProjectsConfig allProjectsConfig;
 
-    @Inject
-    public MyInitStep(@PluginName String pluginName, ConsoleUI ui,
-        AllProjectsConfig allProjectsConfig) {
-      this.pluginName = pluginName;
-      this.ui = ui;
-      this.allProjectsConfig = allProjectsConfig;
-    }
-
-    @Override
-    public void run() throws Exception {
-    }
-
-    @Override
-    public void postRun() throws Exception {
-      ui.message("\n");
-      ui.header(pluginName + " Integration");
-      boolean enabled = ui.yesno(true, "By default enabled for all projects");
-      Config cfg = allProjectsConfig.load().getConfig();
-      if (enabled) {
-        cfg.setBoolean("plugin", pluginName, "enabled", enabled);
-      } else {
-        cfg.unset("plugin", pluginName, "enabled");
-      }
-      allProjectsConfig.save(pluginName, "Initialize " + pluginName + " Integration");
-    }
+  @Inject
+  public MyInitStep(@PluginName String pluginName, ConsoleUI ui,
+      AllProjectsConfig allProjectsConfig) {
+    this.pluginName = pluginName;
+    this.ui = ui;
+    this.allProjectsConfig = allProjectsConfig;
   }
+
+  @Override
+  public void run() throws Exception {
+  }
+
+  @Override
+  public void postRun() throws Exception {
+    ui.message("\n");
+    ui.header(pluginName + " Integration");
+    boolean enabled = ui.yesno(true, "By default enabled for all projects");
+    Config cfg = allProjectsConfig.load().getConfig();
+    if (enabled) {
+      cfg.setBoolean("plugin", pluginName, "enabled", enabled);
+    } else {
+      cfg.unset("plugin", pluginName, "enabled");
+    }
+    allProjectsConfig.save(pluginName, "Initialize " + pluginName + " Integration");
+  }
+}
 ----
 
 Bear in mind that the Plugin's InitStep class will be loaded but
@@ -706,9 +706,12 @@
 in between, leading to the final operator name.  An example
 registration looks like this:
 
-    bind(ChangeOperatorFactory.class)
-       .annotatedWith(Exports.named("sample"))
-       .to(SampleOperator.class);
+[source,java]
+----
+bind(ChangeOperatorFactory.class)
+  .annotatedWith(Exports.named("sample"))
+  .to(SampleOperator.class);
+----
 
 If this is registered in the `myplugin` plugin, then the resulting
 operator will be named `sample_myplugin`.
@@ -736,7 +739,7 @@
 ----
 
 [[search_operands]]
-=== Search Operands ===
+== Search Operands
 
 Plugins can define new search operands to extend change searching.
 Plugin methods implementing search operands (returning a
@@ -748,31 +751,33 @@
 a module's `configure()` method in the plugin.
 
 The new operand, when used in a search would appear as:
-  operatorName:operandName_pluginName
+  `operatorName:operandName_pluginName`
 
 A sample `ChangeHasOperandFactory` class implementing, and registering, a
 new `has:sample_pluginName` operand is shown below:
 
-====
-  public class SampleHasOperand implements ChangeHasOperandFactory {
-    public static class Module extends AbstractModule {
-      @Override
-      protected void configure() {
-        bind(ChangeHasOperandFactory.class)
-            .annotatedWith(Exports.named("sample")
-            .to(SampleHasOperand.class);
-      }
-    }
-
+[source, java]
+----
+public class SampleHasOperand implements ChangeHasOperandFactory {
+  public static class Module extends AbstractModule {
     @Override
-    public Predicate<ChangeData> create(ChangeQueryBuilder builder)
-        throws QueryParseException {
-      return new HasSamplePredicate();
+    protected void configure() {
+      bind(ChangeHasOperandFactory.class)
+          .annotatedWith(Exports.named("sample")
+          .to(SampleHasOperand.class);
     }
-====
+  }
+
+  @Override
+  public Predicate<ChangeData> create(ChangeQueryBuilder builder)
+      throws QueryParseException {
+    return new HasSamplePredicate();
+  }
+}
+----
 
 [[command_options]]
-=== Command Options ===
+== Command Options
 
 Plugins can provide additional options for each of the gerrit ssh and the
 REST API commands by implementing the DynamicBean interface and registering
@@ -801,6 +806,64 @@
       logger.atSevere().log("Say Hello in the Log %s", arg);
     }
   }
+}
+----
+
+Plugins can receive a bean object for each of the gerrit ssh and the REST API
+commands by implementing BeanParseListener interface and registering it to a
+command class name in the plugin module's `configure()` method. The below
+example shows a plugin that always limits the number of projects returned
+by the ls-projects SSH command.
+
+[source, java]
+----
+protected static class PluginModule extends AbstractModule {
+  @Override
+  public void configure() {
+    bind(DynamicOptions.DynamicBean.class)
+        .annotatedWith(Exports.named(ListProjectsCommand.class))
+        .to(ListProjectsCommandBeanListener.class);
+  }
+
+  protected static class ListProjectsCommandBeanListener
+      implements DynamicOptions.BeanParseListener {
+    @Override
+    public void onBeanParseStart(String plugin, Object bean) {
+      ListProjectsCommand command = (ListProjectsCommand) bean;
+      command.impl.setLimit(1);
+    }
+
+    @Override
+    public void onBeanParseEnd(String plugin, Object bean) {}
+  }
+}
+----
+
+The below example shows a plugin that always limits the number of projects
+returned by the /projects/ REST API.
+
+[source, java]
+----
+protected static class PluginModule extends AbstractModule {
+  @Override
+  public void configure() {
+    bind(DynamicOptions.DynamicBean.class)
+        .annotatedWith(Exports.named(ListProjects.class))
+        .to(ListProjectsBeanListener.class);
+  }
+
+  protected static class ListProjectsBeanListener
+      implements DynamicOptions.BeanParseListener {
+    @Override
+    public void onBeanParseStart(String plugin, Object bean) {
+      ListProjects listProjects = (ListProjects) bean;
+      listProjects.setLimit(1);
+    }
+
+    @Override
+    public void onBeanParseEnd(String plugin, Object bean) {}
+  }
+}
 ----
 
 === Calling Command Options ===
@@ -862,9 +925,9 @@
 ----
 
 [[query_attributes]]
-=== Change Attributes ===
+== Change Attributes
 
-==== ChangePluginDefinedInfoFactory
+=== ChangePluginDefinedInfoFactory
 
 Plugins can provide additional attributes to be returned from the Get Change and
 Query Change APIs by implementing the `ChangePluginDefinedInfoFactory` interface
@@ -938,13 +1001,9 @@
 }
 ----
 
-Example
+Example:
 ----
-
-ssh -p 29418 localhost gerrit query --myplugin-name--all "change:1" --format json
-
-Output:
-
+$ ssh -p 29418 localhost gerrit query --myplugin-name--all "change:1" --format json
 {
    "url" : "http://localhost:8080/1",
    "plugins" : [
@@ -957,10 +1016,7 @@
     ...
 }
 
-curl http://localhost:8080/changes/1?myplugin-name--all
-
-Output:
-
+$ curl http://localhost:8080/changes/1?myplugin-name--all
 {
   "_number": 1,
   ...
@@ -978,7 +1034,7 @@
 Runtime exceptions generated by the implementors of ChangePluginDefinedInfoFactory
 are encapsulated in PluginDefinedInfo objects which are part of SSH/REST query output.
 
-==== ChangeAttributeFactory
+=== ChangeAttributeFactory
 
 Alternatively, there is also `ChangeAttributeFactory` which takes in one single
 `ChangeData` at a time. `ChangePluginDefinedInfoFactory` should be preferred
@@ -1112,8 +1168,8 @@
 `plugin.helloworld` subsection:
 
 ----
-  [plugin "helloworld"]
-    enabled = true
+[plugin "helloworld"]
+  enabled = true
 ----
 
 Via the `com.google.gerrit.server.config.PluginConfigFactory` class a
@@ -1316,7 +1372,7 @@
 Here and example of ref-updated JSON event payload with `instanceId`:
 
 [source,json]
----
+----
 {
   "submitter": {
     "name": "Administrator",
@@ -1333,7 +1389,7 @@
   "eventCreatedOn": 1588849085,
   "instanceId": "instance1"
 }
----
+----
 
 [[capabilities]]
 == Plugin Owned Capabilities
@@ -1552,9 +1608,9 @@
 Example config:
 ----
 [extension-panels "CHANGE_SCREEN_BELOW_CHANGE_INFO_BLOCK"]
-        panel = helloworld.change_id
-        panel = myotherplugin
-        panel = myplugin.my_panel_name
+  panel = helloworld.change_id
+  panel = myotherplugin
+  panel = myplugin.my_panel_name
 ----
 
 
@@ -1779,11 +1835,11 @@
 can be accessed from any REST client, i. e.:
 
 ----
-  curl -X POST -H "Content-Type: application/json" \
+$ curl -X POST -H "Content-Type: application/json" \
     -d '{message: "François", french: true}' \
     --user joe:secret \
     http://host:port/a/changes/1/revisions/1/cookbook~say-hello
-  "Bonjour François from change 1, patch set 1!"
+"Bonjour François from change 1, patch set 1!"
 ----
 
 A special case is to bind an endpoint without a view name.  This is
@@ -1880,7 +1936,6 @@
 [source,java]
 ----
 public class MyTopMenuExtension implements TopMenu {
-
   @Override
   public List<MenuEntry> getEntries() {
     return Lists.newArrayList(
@@ -1897,7 +1952,6 @@
 [source,java]
 ----
 public class MyTopMenuExtension implements TopMenu {
-
   @Override
   public List<MenuEntry> getEntries() {
     return Lists.newArrayList(
@@ -1915,17 +1969,17 @@
 specific requests and add an menu item for this:
 
 [source,java]
----
-  new MenuItem("My Screen", "/plugins/myplugin/project/${projectName}");
----
+----
+new MenuItem("My Screen", "/plugins/myplugin/project/${projectName}");
+----
 
 This also enables plugins to provide menu items for project aware
 screens:
 
 [source,java]
----
-  new MenuItem("My Screen", "/x/my-screen/for/${projectName}");
----
+----
+new MenuItem("My Screen", "/x/my-screen/for/${projectName}");
+----
 
 If no Guice modules are declared in the manifest, the top menu extension may use
 auto-registration by providing an `@Listen` annotation:
@@ -2139,7 +2193,6 @@
 bind(AccountExternalIdCreator.class)
   .annotatedWith(UniqueAnnotations.create())
   .to(MyExternalIdCreator.class);
-}
 ----
 
 [[download-commands]]
@@ -2206,7 +2259,6 @@
 
 @Listen
 public class MyWeblinkPlugin implements PatchSetWebLink {
-
   private String name = "MyLink";
   private String placeHolderUrlProjectCommit = "http://my.tool.com/project=%s/commit=%s";
   private String imageUrl = "http://placehold.it/16x16.gif";
@@ -2286,7 +2338,6 @@
 import com.google.inject.servlet.ServletModule;
 
 public class HttpModule extends ServletModule {
-
   @Override
   protected void configureServlets() {
     serveRegex(URL_REGEX).with(LfsApiServlet.class);
@@ -2297,7 +2348,7 @@
 import org.eclipse.jgit.lfs.server.s3.S3Repository;
 
 public class S3LargeFileRepository extends S3Repository {
-...
+  ...
 }
 ----
 
@@ -2347,8 +2398,8 @@
 file. For example:
 
 ----
-  [plugin "my-plugin"]
-    metricsPrefix = my-metrics
+[plugin "my-plugin"]
+  metricsPrefix = my-metrics
 ----
 
 will cause the metrics to be recorded under `my-metrics/${metric-name}`.
@@ -2371,6 +2422,7 @@
 implementation, e.g. one that supports cluster setup with multiple
 primary Gerrit nodes handling write operations.
 
+[source,java]
 ----
 DynamicItem.bind(binder(), AccountPatchReviewStore.class)
     .to(MultiMasterAccountPatchReviewStore.class);
@@ -2592,6 +2644,8 @@
   @Override
   public String intercept(String in) {
     return pluginName + " mycommand";
+  }
+}
 ----
 
 [[ssh-command-execution-interception]]
@@ -2625,7 +2679,8 @@
 And then declare it in your SSH module:
 [source, java]
 ----
-  DynamicSet.bind(binder(), SshExecuteCommandInterceptor.class).to(SshExecuteCommandInterceptorImpl.class);
+DynamicSet.bind(binder(), SshExecuteCommandInterceptor.class)
+  .to(SshExecuteCommandInterceptorImpl.class);
 ----
 
 [[pre-submit-evaluator]]
diff --git a/Documentation/intro-user.txt b/Documentation/intro-user.txt
index eb2025c..9909aac 100644
--- a/Documentation/intro-user.txt
+++ b/Documentation/intro-user.txt
@@ -459,7 +459,7 @@
 push permission] on the destination branch.
 
 The move operation will not update the change's parent and users will have
-to link:#rebase[rebase] the change. Also, merge commits cannot be moved.
+to link:#rebase[rebase] the change.
 
 [[abandon]]
 [[restore]]
diff --git a/Documentation/logs.txt b/Documentation/logs.txt
index 6624366..f072984 100644
--- a/Documentation/logs.txt
+++ b/Documentation/logs.txt
@@ -51,6 +51,11 @@
 * `referer`: the `Referer` HTTP request header. This gives the site that
   the client reports having been referred from.
 * `client agent`: the client agent which sent the request.
+* `command status`: the overall result of the git command over HTTP. Currently
+   populated only for the transfer phase of `git-upload-pack` commands.
+   Possible values:
+** `-1`: The `git-upload-pack` transfer was ultimately not successful
+** `0`: The `git-upload-pack` transfer was ultimately successful
 
 Example:
 ```
diff --git a/Documentation/replace_macros.py b/Documentation/replace_macros.py
index aaa9223..f270231 100755
--- a/Documentation/replace_macros.py
+++ b/Documentation/replace_macros.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 # coding=utf-8
 # Copyright (C) 2013 The Android Open Source Project
 #
diff --git a/Documentation/rest-api-projects.txt b/Documentation/rest-api-projects.txt
index 92759b6..84da169 100644
--- a/Documentation/rest-api-projects.txt
+++ b/Documentation/rest-api-projects.txt
@@ -421,11 +421,11 @@
 ----
 
 The `/projects/` URL also accepts a start integer in the `start`
-parameter. The results will skip `start` groups from project list.
+parameter. The results will skip `start` projects from project list.
 
 Query 25 projects starting from index 50.
 ----
-  GET /groups/?query=<query>&limit=25&start=50 HTTP/1.0
+  GET /projects/?query=<query>&limit=25&start=50 HTTP/1.0
 ----
 
 [[project-query-options]]
@@ -2556,6 +2556,9 @@
 Retrieves the branches and tags in which a change is included. As result
 an link:rest-api-changes.html#included-in-info[IncludedInInfo] entity is returned.
 
+Branches that are not visible to the calling user according to the project's
+read permissions are filtered out from the result.
+
 .Request
 ----
   GET /projects/work%2Fmy-project/commits/a8a477efffbbf3b44169bb9a1d3a334cbbd9aa96/in HTTP/1.0
diff --git a/Documentation/user-search.txt b/Documentation/user-search.txt
index 0c1ec2d..b22788a 100644
--- a/Documentation/user-search.txt
+++ b/Documentation/user-search.txt
@@ -305,8 +305,9 @@
 +
 Slash ('/') is used path separator.
 +
-More examples:
-* `-file:^path/.*` - changes that do not modify files from `path/`,
+*More examples:*
+
+* `-file:^path/.*` - changes that do not modify files from `path/`.
 * `file:{^~(path/.*)}` - changes that modify files not from `path/` (but may
 contain files from `path/`).
 
@@ -555,14 +556,17 @@
 author:'AUTHOR'::
 +
 Changes where 'AUTHOR' is the author of the current patch set. 'AUTHOR' may be
-the author's exact email address, or part of the name or email address.
+the author's exact email address, or part of the name or email address. The
+special case of `author:self` will find changes authored by the caller.
 
 [[committer]]
 committer:'COMMITTER'::
 +
 Changes where 'COMMITTER' is the committer of the current patch set.
 'COMMITTER' may be the committer's exact email address, or part of the name or
-email address.
+email address. The special case of `committer:self` will find changes committed
+by the caller.
+
 
 [[submittable]]
 submittable:'SUBMIT_STATUS'::
diff --git a/WORKSPACE b/WORKSPACE
index b530e4e..6fe7af5 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -1144,8 +1144,8 @@
 bower_archive(
     name = "codemirror-minified",
     package = "Dominator008/codemirror-minified",
-    sha1 = "a1ddf3a6dcc6817597eacc52688cfe5083ded4cd",
-    version = "5.59.1",
+    sha1 = "904bae2a8716087fd21e92324e8a136a0c4a99b7",
+    version = "5.62.2",
 )
 
 # bower test stuff
diff --git a/contrib/check-valid-commit.py b/contrib/check-valid-commit.py
index 763ae3e..bb018f9 100755
--- a/contrib/check-valid-commit.py
+++ b/contrib/check-valid-commit.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 from __future__ import print_function
 
diff --git a/contrib/git-push-review b/contrib/git-push-review
index b995fc2..5a7f664 100755
--- a/contrib/git-push-review
+++ b/contrib/git-push-review
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 # Copyright (C) 2014 The Android Open Source Project
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
diff --git a/contrib/populate-fixture-data.py b/contrib/populate-fixture-data.py
index 4c6769c..e51e29d 100755
--- a/contrib/populate-fixture-data.py
+++ b/contrib/populate-fixture-data.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 # Copyright (C) 2016 The Android Open Source Project
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
diff --git a/java/com/google/gerrit/acceptance/testsuite/project/ProjectOperationsImpl.java b/java/com/google/gerrit/acceptance/testsuite/project/ProjectOperationsImpl.java
index f6e5de3..e7354ab 100644
--- a/java/com/google/gerrit/acceptance/testsuite/project/ProjectOperationsImpl.java
+++ b/java/com/google/gerrit/acceptance/testsuite/project/ProjectOperationsImpl.java
@@ -138,6 +138,9 @@
         throws IOException, ConfigInvalidException {
       try (MetaDataUpdate metaDataUpdate = metaDataUpdateFactory.create(nameKey)) {
         ProjectConfig projectConfig = projectConfigFactory.read(metaDataUpdate);
+        if (projectUpdate.removeAllAccessSections()) {
+          projectConfig.getAccessSections().forEach(as -> projectConfig.remove(as));
+        }
         removePermissions(projectConfig, projectUpdate.removedPermissions());
         addCapabilities(projectConfig, projectUpdate.addedCapabilities());
         addPermissions(projectConfig, projectUpdate.addedPermissions());
diff --git a/java/com/google/gerrit/acceptance/testsuite/project/TestProjectUpdate.java b/java/com/google/gerrit/acceptance/testsuite/project/TestProjectUpdate.java
index ea20931..9a9a21a 100644
--- a/java/com/google/gerrit/acceptance/testsuite/project/TestProjectUpdate.java
+++ b/java/com/google/gerrit/acceptance/testsuite/project/TestProjectUpdate.java
@@ -294,7 +294,8 @@
     return new AutoValue_TestProjectUpdate.Builder()
         .nameKey(nameKey)
         .allProjectsName(allProjectsName)
-        .projectUpdater(projectUpdater);
+        .projectUpdater(projectUpdater)
+        .removeAllAccessSections(false);
   }
 
   /** Builder for {@link TestProjectUpdate}. */
@@ -314,6 +315,16 @@
 
     abstract ImmutableMap.Builder<TestPermissionKey, Boolean> exclusiveGroupPermissionsBuilder();
 
+    abstract Builder removeAllAccessSections(boolean value);
+
+    /**
+     * Removes all access sections. Useful when testing against a specific set of access sections or
+     * permissions.
+     */
+    public Builder removeAllAccessSections() {
+      return removeAllAccessSections(true);
+    }
+
     /** Adds a permission to be included in this update. */
     public Builder add(TestPermission testPermission) {
       addedPermissionsBuilder().add(testPermission);
@@ -418,6 +429,8 @@
 
   abstract ThrowingConsumer<TestProjectUpdate> projectUpdater();
 
+  abstract boolean removeAllAccessSections();
+
   boolean hasCapabilityUpdates() {
     return !addedCapabilities().isEmpty()
         || removedPermissions().stream().anyMatch(k -> k.section().equals(GLOBAL_CAPABILITIES));
diff --git a/java/com/google/gerrit/httpd/GitOverHttpServlet.java b/java/com/google/gerrit/httpd/GitOverHttpServlet.java
index 6e22704..3e2d3ef 100644
--- a/java/com/google/gerrit/httpd/GitOverHttpServlet.java
+++ b/java/com/google/gerrit/httpd/GitOverHttpServlet.java
@@ -14,11 +14,15 @@
 
 package com.google.gerrit.httpd;
 
+import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
+import static org.eclipse.jgit.http.server.GitSmartHttpTools.sendError;
+
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.cache.Cache;
 import com.google.common.collect.ImmutableListMultimap;
 import com.google.common.collect.ListMultimap;
 import com.google.common.collect.Lists;
+import com.google.common.flogger.FluentLogger;
 import com.google.gerrit.common.data.Capable;
 import com.google.gerrit.entities.Project;
 import com.google.gerrit.extensions.registration.DynamicSet;
@@ -54,6 +58,7 @@
 import com.google.inject.TypeLiteral;
 import com.google.inject.name.Named;
 import java.io.IOException;
+import java.text.MessageFormat;
 import java.time.Duration;
 import java.util.Arrays;
 import java.util.Collections;
@@ -70,10 +75,13 @@
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import javax.servlet.http.HttpServletResponseWrapper;
+import org.eclipse.jgit.errors.PackProtocolException;
 import org.eclipse.jgit.errors.RepositoryNotFoundException;
 import org.eclipse.jgit.http.server.GitServlet;
 import org.eclipse.jgit.http.server.GitSmartHttpTools;
+import org.eclipse.jgit.http.server.HttpServerText;
 import org.eclipse.jgit.http.server.ServletUtils;
+import org.eclipse.jgit.http.server.UploadPackErrorHandler;
 import org.eclipse.jgit.http.server.resolver.AsIsFileService;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.Repository;
@@ -100,6 +108,23 @@
   private static final String ID_CACHE = "adv_bases";
 
   public static final String URL_REGEX;
+  public static final String GIT_COMMAND_STATUS_HEADER = "X-git-command-status";
+
+  private enum GIT_COMMAND_STATUS {
+    OK(0),
+    FAIL(-1);
+
+    private final int exitStatus;
+
+    GIT_COMMAND_STATUS(int exitStatus) {
+      this.exitStatus = exitStatus;
+    }
+
+    @Override
+    public String toString() {
+      return Integer.toString(exitStatus);
+    }
+  }
 
   static {
     StringBuilder url = new StringBuilder();
@@ -212,12 +237,14 @@
       Resolver resolver,
       UploadFactory upload,
       UploadFilter uploadFilter,
+      GerritUploadPackErrorHandler uploadPackErrorHandler,
       ReceivePackFactory<HttpServletRequest> receive,
       ReceiveFilter receiveFilter) {
     setRepositoryResolver(resolver);
     setAsIsFileService(AsIsFileService.DISABLED);
 
     setUploadPackFactory(upload);
+    setUploadPackErrorHandler(uploadPackErrorHandler);
     addUploadPackFilter(uploadFilter);
 
     setReceivePackFactory(receive);
@@ -456,6 +483,35 @@
     public void destroy() {}
   }
 
+  static class GerritUploadPackErrorHandler implements UploadPackErrorHandler {
+    private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+
+    @Override
+    public void upload(HttpServletRequest req, HttpServletResponse rsp, UploadPackRunnable r)
+        throws IOException {
+      rsp.setHeader(GIT_COMMAND_STATUS_HEADER, GIT_COMMAND_STATUS.FAIL.toString());
+      try {
+        r.upload();
+        rsp.setHeader(GIT_COMMAND_STATUS_HEADER, GIT_COMMAND_STATUS.OK.toString());
+      } catch (ServiceMayNotContinueException e) {
+        if (!e.isOutput() && !rsp.isCommitted()) {
+          rsp.reset();
+          sendError(req, rsp, e.getStatusCode(), e.getMessage());
+        }
+      } catch (Throwable e) {
+        logger.atSevere().withCause(e).log(
+            MessageFormat.format(
+                HttpServerText.get().internalErrorDuringUploadPack,
+                ServletUtils.getRepository(req)));
+        if (!rsp.isCommitted()) {
+          rsp.reset();
+          String msg = e instanceof PackProtocolException ? e.getMessage() : null;
+          sendError(req, rsp, SC_INTERNAL_SERVER_ERROR, msg);
+        }
+      }
+    }
+  }
+
   static class ReceiveFactory implements ReceivePackFactory<HttpServletRequest> {
     private final AsyncReceiveCommits.Factory factory;
     private final Provider<CurrentUser> userProvider;
diff --git a/java/com/google/gerrit/httpd/plugins/PluginServletContext.java b/java/com/google/gerrit/httpd/plugins/PluginServletContext.java
index 40083e4..5a8fa31 100644
--- a/java/com/google/gerrit/httpd/plugins/PluginServletContext.java
+++ b/java/com/google/gerrit/httpd/plugins/PluginServletContext.java
@@ -199,6 +199,11 @@
       String v = Version.getVersion();
       return "Gerrit Code Review/" + (v != null ? v : "dev");
     }
+
+    @Override
+    public String getVirtualServerName() {
+      return null;
+    }
   }
 
   interface API {
@@ -255,5 +260,7 @@
     int getMinorVersion();
 
     String getServerInfo();
+
+    String getVirtualServerName();
   }
 }
diff --git a/java/com/google/gerrit/pgm/http/jetty/HttpLog.java b/java/com/google/gerrit/pgm/http/jetty/HttpLog.java
index 4e4c93b..27a53c2 100644
--- a/java/com/google/gerrit/pgm/http/jetty/HttpLog.java
+++ b/java/com/google/gerrit/pgm/http/jetty/HttpLog.java
@@ -14,6 +14,8 @@
 
 package com.google.gerrit.pgm.http.jetty;
 
+import static com.google.gerrit.httpd.GitOverHttpServlet.GIT_COMMAND_STATUS_HEADER;
+
 import com.google.common.base.Strings;
 import com.google.gerrit.httpd.GetUserFilter;
 import com.google.gerrit.httpd.restapi.LogRedactUtil;
@@ -51,6 +53,7 @@
   protected static final String P_LATENCY = "Latency";
   protected static final String P_REFERER = "Referer";
   protected static final String P_USER_AGENT = "User-Agent";
+  protected static final String P_COMMAND_STATUS = "Command-Status";
 
   private final AsyncAppender async;
 
@@ -113,6 +116,7 @@
     set(event, P_LATENCY, System.currentTimeMillis() - req.getTimeStamp());
     set(event, P_REFERER, req.getHeader("Referer"));
     set(event, P_USER_AGENT, req.getHeader("User-Agent"));
+    set(event, P_COMMAND_STATUS, rsp.getHeader(GIT_COMMAND_STATUS_HEADER));
 
     async.append(event);
   }
diff --git a/java/com/google/gerrit/pgm/http/jetty/HttpLogJsonLayout.java b/java/com/google/gerrit/pgm/http/jetty/HttpLogJsonLayout.java
index 95a5b07..807b311 100644
--- a/java/com/google/gerrit/pgm/http/jetty/HttpLogJsonLayout.java
+++ b/java/com/google/gerrit/pgm/http/jetty/HttpLogJsonLayout.java
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.pgm.http.jetty;
 
+import static com.google.gerrit.pgm.http.jetty.HttpLog.P_COMMAND_STATUS;
 import static com.google.gerrit.pgm.http.jetty.HttpLog.P_CONTENT_LENGTH;
 import static com.google.gerrit.pgm.http.jetty.HttpLog.P_HOST;
 import static com.google.gerrit.pgm.http.jetty.HttpLog.P_LATENCY;
@@ -50,6 +51,7 @@
     public String latency;
     public String referer;
     public String userAgent;
+    public String commandStatus;
 
     public HttpJsonLogEntry(LoggingEvent event) {
       this.host = getMdcString(event, P_HOST);
@@ -64,6 +66,7 @@
       this.latency = getMdcString(event, P_LATENCY);
       this.referer = getMdcString(event, P_REFERER);
       this.userAgent = getMdcString(event, P_USER_AGENT);
+      this.commandStatus = getMdcString(event, P_COMMAND_STATUS);
     }
   }
 }
diff --git a/java/com/google/gerrit/pgm/http/jetty/HttpLogLayout.java b/java/com/google/gerrit/pgm/http/jetty/HttpLogLayout.java
index 268f59f..b83fcc5 100644
--- a/java/com/google/gerrit/pgm/http/jetty/HttpLogLayout.java
+++ b/java/com/google/gerrit/pgm/http/jetty/HttpLogLayout.java
@@ -71,6 +71,9 @@
     buf.append(' ');
     dq_opt(buf, event, HttpLog.P_USER_AGENT);
 
+    buf.append(' ');
+    dq_opt(buf, event, HttpLog.P_COMMAND_STATUS);
+
     buf.append('\n');
     return buf.toString();
   }
diff --git a/java/com/google/gerrit/server/DynamicOptions.java b/java/com/google/gerrit/server/DynamicOptions.java
index 41dc082..ebf4ec6 100644
--- a/java/com/google/gerrit/server/DynamicOptions.java
+++ b/java/com/google/gerrit/server/DynamicOptions.java
@@ -213,7 +213,7 @@
     Class<?> beanClass =
         (bean instanceof BeanReceiver)
             ? ((BeanReceiver) bean).getExportedBeanReceiver()
-            : getClass();
+            : bean.getClass();
     for (String plugin : dynamicBeans.plugins()) {
       Provider<DynamicBean> provider =
           dynamicBeans.byPlugin(plugin).get(beanClass.getCanonicalName());
diff --git a/java/com/google/gerrit/server/change/IncludedIn.java b/java/com/google/gerrit/server/change/IncludedIn.java
index 3c66c2c..c06ce82 100644
--- a/java/com/google/gerrit/server/change/IncludedIn.java
+++ b/java/com/google/gerrit/server/change/IncludedIn.java
@@ -14,6 +14,12 @@
 
 package com.google.gerrit.server.change;
 
+import static com.google.common.collect.ImmutableList.toImmutableList;
+import static com.google.common.collect.ImmutableSortedSet.toImmutableSortedSet;
+import static java.util.Comparator.naturalOrder;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSortedSet;
 import com.google.common.collect.ListMultimap;
 import com.google.common.collect.MultimapBuilder;
 import com.google.gerrit.entities.Project;
@@ -23,13 +29,18 @@
 import com.google.gerrit.extensions.restapi.ResourceConflictException;
 import com.google.gerrit.extensions.restapi.RestApiException;
 import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.permissions.PermissionBackend;
+import com.google.gerrit.server.permissions.PermissionBackend.RefFilterOptions;
+import com.google.gerrit.server.permissions.PermissionBackendException;
 import com.google.gerrit.server.plugincontext.PluginSetContext;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
 import java.io.IOException;
+import java.util.Collection;
 import org.eclipse.jgit.errors.IncorrectObjectTypeException;
 import org.eclipse.jgit.errors.MissingObjectException;
 import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Ref;
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.revwalk.RevCommit;
 import org.eclipse.jgit.revwalk.RevWalk;
@@ -37,17 +48,21 @@
 @Singleton
 public class IncludedIn {
   private final GitRepositoryManager repoManager;
+  private final PermissionBackend permissionBackend;
   private final PluginSetContext<ExternalIncludedIn> externalIncludedIn;
 
   @Inject
   IncludedIn(
-      GitRepositoryManager repoManager, PluginSetContext<ExternalIncludedIn> externalIncludedIn) {
+      GitRepositoryManager repoManager,
+      PermissionBackend permissionBackend,
+      PluginSetContext<ExternalIncludedIn> externalIncludedIn) {
     this.repoManager = repoManager;
+    this.permissionBackend = permissionBackend;
     this.externalIncludedIn = externalIncludedIn;
   }
 
   public IncludedInInfo apply(Project.NameKey project, String revisionId)
-      throws RestApiException, IOException {
+      throws RestApiException, IOException, PermissionBackendException {
     try (Repository r = repoManager.openRepository(project);
         RevWalk rw = new RevWalk(r)) {
       rw.setRetainBody(false);
@@ -61,18 +76,48 @@
       }
 
       IncludedInResolver.Result d = IncludedInResolver.resolve(r, rw, rev);
+
+      // Filter branches and tags according to their visbility by the user
+      ImmutableSortedSet<String> filteredBranches =
+          sortedShortNames(filterReadableRefs(project, d.branches()));
+      ImmutableSortedSet<String> filteredTags =
+          sortedShortNames(filterReadableRefs(project, d.tags()));
+
       ListMultimap<String, String> external = MultimapBuilder.hashKeys().arrayListValues().build();
       externalIncludedIn.runEach(
           ext -> {
             ListMultimap<String, String> extIncludedIns =
-                ext.getIncludedIn(project.get(), rev.name(), d.tags(), d.branches());
+                ext.getIncludedIn(project.get(), rev.name(), filteredBranches, filteredTags);
             if (extIncludedIns != null) {
               external.putAll(extIncludedIns);
             }
           });
 
       return new IncludedInInfo(
-          d.branches(), d.tags(), (!external.isEmpty() ? external.asMap() : null));
+          filteredBranches, filteredTags, (!external.isEmpty() ? external.asMap() : null));
     }
   }
+
+  /**
+   * Filter readable branches or tags according to the caller's refs visibility.
+   *
+   * @param project specific Gerrit project.
+   * @param inputRefs a list of branches (in short name) as strings
+   */
+  private Collection<String> filterReadableRefs(
+      Project.NameKey project, ImmutableList<Ref> inputRefs)
+      throws IOException, PermissionBackendException {
+    PermissionBackend.ForProject perm = permissionBackend.currentUser().project(project);
+    try (Repository repo = repoManager.openRepository(project)) {
+      return perm.filter(inputRefs, repo, RefFilterOptions.defaults()).stream()
+          .map(Ref::getName)
+          .collect(toImmutableList());
+    }
+  }
+
+  private ImmutableSortedSet<String> sortedShortNames(Collection<String> refs) {
+    return refs.stream()
+        .map(Repository::shortenRefName)
+        .collect(toImmutableSortedSet(naturalOrder()));
+  }
 }
diff --git a/java/com/google/gerrit/server/change/IncludedInResolver.java b/java/com/google/gerrit/server/change/IncludedInResolver.java
index 09ca258..3891700 100644
--- a/java/com/google/gerrit/server/change/IncludedInResolver.java
+++ b/java/com/google/gerrit/server/change/IncludedInResolver.java
@@ -14,13 +14,11 @@
 
 package com.google.gerrit.server.change;
 
-import static com.google.common.collect.ImmutableSortedSet.toImmutableSortedSet;
 import static java.util.Comparator.comparing;
-import static java.util.Comparator.naturalOrder;
 import static java.util.stream.Collectors.toList;
 
 import com.google.auto.value.AutoValue;
-import com.google.common.collect.ImmutableSortedSet;
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.LinkedListMultimap;
 import com.google.common.collect.ListMultimap;
 import com.google.common.collect.Lists;
@@ -171,13 +169,12 @@
    * Returns the short names of refs which are as well in the matchingRefs list as well as in the
    * allRef list.
    */
-  private static ImmutableSortedSet<String> getMatchingRefNames(
+  private static ImmutableList<Ref> getMatchingRefNames(
       Set<String> matchingRefs, Collection<Ref> allRefs) {
     return allRefs.stream()
-        .map(Ref::getName)
-        .filter(matchingRefs::contains)
-        .map(Repository::shortenRefName)
-        .collect(toImmutableSortedSet(naturalOrder()));
+        .filter(r -> matchingRefs.contains(r.getName()))
+        .distinct()
+        .collect(ImmutableList.toImmutableList());
   }
 
   /** Parse commit of ref and store the relation between ref and commit. */
@@ -211,8 +208,8 @@
 
   @AutoValue
   public abstract static class Result {
-    public abstract ImmutableSortedSet<String> branches();
+    public abstract ImmutableList<Ref> branches();
 
-    public abstract ImmutableSortedSet<String> tags();
+    public abstract ImmutableList<Ref> tags();
   }
 }
diff --git a/java/com/google/gerrit/server/events/EventGsonProvider.java b/java/com/google/gerrit/server/events/EventGsonProvider.java
index 688507b..ab51518 100644
--- a/java/com/google/gerrit/server/events/EventGsonProvider.java
+++ b/java/com/google/gerrit/server/events/EventGsonProvider.java
@@ -30,7 +30,7 @@
         .registerTypeAdapter(Supplier.class, new SupplierSerializer())
         .registerTypeAdapter(Supplier.class, new SupplierDeserializer())
         .registerTypeAdapter(Change.Key.class, new ChangeKeyAdapter())
-        .registerTypeAdapter(Project.NameKey.class, new ProjectNameKeyAdapter())
+        .registerTypeHierarchyAdapter(Project.NameKey.class, new ProjectNameKeyAdapter())
         .create();
   }
 }
diff --git a/java/com/google/gerrit/server/git/PermissionAwareReadOnlyRefDatabase.java b/java/com/google/gerrit/server/git/PermissionAwareReadOnlyRefDatabase.java
index b7dc2b3..7950dc6 100644
--- a/java/com/google/gerrit/server/git/PermissionAwareReadOnlyRefDatabase.java
+++ b/java/com/google/gerrit/server/git/PermissionAwareReadOnlyRefDatabase.java
@@ -14,7 +14,6 @@
 
 package com.google.gerrit.server.git;
 
-import static java.util.stream.Collectors.toList;
 import static java.util.stream.Collectors.toMap;
 
 import com.google.common.base.Preconditions;
@@ -95,20 +94,11 @@
     return Iterables.getOnlyElement(result);
   }
 
+  // WARNING: This method is deprecated in JGit's RefDatabase and it will be removed on master.
+  // Do not add any logic here but rather enrich the getRefsByPrefix method below.
   @Override
   public Map<String, Ref> getRefs(String prefix) throws IOException {
-    List<Ref> refs = getDelegate().getRefDatabase().getRefsByPrefix(prefix);
-    if (refs.isEmpty()) {
-      return Collections.emptyMap();
-    }
-
-    Collection<Ref> result;
-    try {
-      result = forProject.filter(refs, getDelegate(), RefFilterOptions.defaults());
-    } catch (PermissionBackendException e) {
-      throw new IOException("", e);
-    }
-    return buildPrefixRefMap(prefix, result);
+    return buildPrefixRefMap(prefix, getRefsByPrefix(prefix));
   }
 
   private Map<String, Ref> buildPrefixRefMap(String prefix, Collection<Ref> refs) {
@@ -125,26 +115,18 @@
 
   @Override
   public List<Ref> getRefsByPrefix(String prefix) throws IOException {
-    Map<String, Ref> coarseRefs;
-    int lastSlash = prefix.lastIndexOf('/');
-    if (lastSlash == -1) {
-      coarseRefs = getRefs(ALL);
-    } else {
-      coarseRefs = getRefs(prefix.substring(0, lastSlash + 1));
+    List<Ref> refs = getDelegate().getRefDatabase().getRefsByPrefix(prefix);
+    if (refs.isEmpty()) {
+      return Collections.emptyList();
     }
 
-    List<Ref> result;
-    if (lastSlash + 1 == prefix.length()) {
-      result = coarseRefs.values().stream().collect(toList());
-    } else {
-      String p = prefix.substring(lastSlash + 1);
-      result =
-          coarseRefs.entrySet().stream()
-              .filter(e -> e.getKey().startsWith(p))
-              .map(e -> e.getValue())
-              .collect(toList());
+    Collection<Ref> result;
+    try {
+      result = forProject.filter(refs, getDelegate(), RefFilterOptions.defaults());
+    } catch (PermissionBackendException e) {
+      throw new IOException("", e);
     }
-    return Collections.unmodifiableList(result);
+    return result.stream().collect(Collectors.toList());
   }
 
   @Override
diff --git a/java/com/google/gerrit/server/git/receive/ReceiveCommits.java b/java/com/google/gerrit/server/git/receive/ReceiveCommits.java
index 8d92347..6d234ac7e 100644
--- a/java/com/google/gerrit/server/git/receive/ReceiveCommits.java
+++ b/java/com/google/gerrit/server/git/receive/ReceiveCommits.java
@@ -2693,7 +2693,7 @@
     try (TraceTimer traceTimer = newTimer("readChangesForReplace")) {
       replaceByChange.values().stream()
           .map(r -> r.ontoChange)
-          .map(id -> notesFactory.create(project.getNameKey(), id))
+          .map(id -> notesFactory.create(repo, project.getNameKey(), id))
           .forEach(notes -> replaceByChange.get(notes.getChangeId()).notes = notes);
     }
   }
diff --git a/java/com/google/gerrit/server/notedb/AbstractChangeNotes.java b/java/com/google/gerrit/server/notedb/AbstractChangeNotes.java
index 9b5b4d4..e81160a 100644
--- a/java/com/google/gerrit/server/notedb/AbstractChangeNotes.java
+++ b/java/com/google/gerrit/server/notedb/AbstractChangeNotes.java
@@ -118,9 +118,10 @@
   private ObjectId revision;
   private boolean loaded;
 
-  protected AbstractChangeNotes(Args args, Change.Id changeId) {
+  protected AbstractChangeNotes(Args args, Change.Id changeId, @Nullable ObjectId metaSha1) {
     this.args = requireNonNull(args);
     this.changeId = requireNonNull(changeId);
+    this.revision = metaSha1;
   }
 
   public Change.Id getChangeId() {
@@ -133,6 +134,15 @@
   }
 
   public T load() {
+    try (Repository repo = args.repoManager.openRepository(getProjectName())) {
+      load(repo);
+      return self();
+    } catch (IOException e) {
+      throw new StorageException(e);
+    }
+  }
+
+  public T load(Repository repo) {
     if (loaded) {
       return self();
     }
@@ -141,10 +151,9 @@
       throw new StorageException("Reading from NoteDb is disabled");
     }
     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.
-        LoadHandle handle = openHandle(repo)) {
+        LoadHandle handle = openHandle(repo, revision)) {
       revision = handle.id();
       onLoad(handle);
       loaded = true;
@@ -166,15 +175,16 @@
    * <p>Implementations may override this method to provide auto-rebuilding behavior.
    *
    * @param repo open repository.
+   * @param id version SHA1 of the change notes to load
    * @return handle for reading the entity.
    * @throws NoSuchChangeException change does not exist.
    * @throws IOException a repo-level error occurred.
    */
-  protected LoadHandle openHandle(Repository repo) throws NoSuchChangeException, IOException {
-    return openHandle(repo, readRef(repo));
-  }
-
-  protected LoadHandle openHandle(Repository repo, ObjectId id) {
+  protected LoadHandle openHandle(Repository repo, @Nullable ObjectId id)
+      throws NoSuchChangeException, IOException {
+    if (id == null) {
+      id = readRef(repo);
+    }
     return new LoadHandle(repo, id);
   }
 
diff --git a/java/com/google/gerrit/server/notedb/ChangeNotes.java b/java/com/google/gerrit/server/notedb/ChangeNotes.java
index c35d815..047aa0c 100644
--- a/java/com/google/gerrit/server/notedb/ChangeNotes.java
+++ b/java/com/google/gerrit/server/notedb/ChangeNotes.java
@@ -66,12 +66,14 @@
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.List;
 import java.util.Objects;
 import java.util.Optional;
 import java.util.function.Predicate;
 import java.util.stream.Stream;
 import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.errors.RepositoryNotFoundException;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.Ref;
 import org.eclipse.jgit.lib.Repository;
@@ -110,9 +112,23 @@
       return createChecked(c.getProject(), c.getId());
     }
 
-    public ChangeNotes createChecked(Project.NameKey project, Change.Id changeId) {
+    public ChangeNotes createChecked(
+        Repository repo,
+        Project.NameKey project,
+        Change.Id changeId,
+        @Nullable ObjectId metaRevId) {
       Change change = newChange(project, changeId);
-      return new ChangeNotes(args, change, true, null).load();
+      return new ChangeNotes(args, change, true, null, metaRevId).load(repo);
+    }
+
+    public ChangeNotes createChecked(
+        Project.NameKey project, Change.Id changeId, @Nullable ObjectId metaRevId) {
+      Change change = newChange(project, changeId);
+      return new ChangeNotes(args, change, true, null, metaRevId).load();
+    }
+
+    public ChangeNotes createChecked(Project.NameKey project, Change.Id changeId) {
+      return createChecked(project, changeId, null);
     }
 
     public static Change newChange(Project.NameKey project, Change.Id changeId) {
@@ -125,6 +141,11 @@
       return new ChangeNotes(args, newChange(project, changeId), true, null).load();
     }
 
+    public ChangeNotes create(Repository repository, Project.NameKey project, Change.Id changeId) {
+      checkArgument(project != null, "project is required");
+      return new ChangeNotes(args, newChange(project, changeId), true, null).load(repository);
+    }
+
     /**
      * Create change notes for a change that was loaded from index. This method should only be used
      * when database access is harmful and potentially stale data from the index is acceptable.
@@ -179,13 +200,14 @@
     }
 
     public List<ChangeNotes> create(
+        Repository repo,
         Project.NameKey project,
         Collection<Change.Id> changeIds,
         Predicate<ChangeNotes> predicate) {
       List<ChangeNotes> notes = new ArrayList<>();
       for (Change.Id cid : changeIds) {
         try {
-          ChangeNotes cn = create(project, cid);
+          ChangeNotes cn = create(repo, project, cid);
           if (cn.getChange() != null && predicate.test(cn)) {
             notes.add(cn);
           }
@@ -198,6 +220,28 @@
       return notes;
     }
 
+    /* TODO: This is now unused in the Gerrit code-base, however it is kept in the code
+    /* because it is a public method in a stable branch.
+     * It can be removed in master branch where we have more flexibility to change the API
+     * interface.
+     */
+    public List<ChangeNotes> create(
+        Project.NameKey project,
+        Collection<Change.Id> changeIds,
+        Predicate<ChangeNotes> predicate) {
+      try (Repository repo = args.repoManager.openRepository(project)) {
+        return create(repo, project, changeIds, predicate);
+      } catch (RepositoryNotFoundException e) {
+        // The repository does not exist, hence it does not contain
+        // any change.
+      } catch (IOException e) {
+        logger.atWarning().withCause(e).log(
+            "Unable to open project=%s when trying to retrieve changeId=%s from NoteDb",
+            project, changeIds);
+      }
+      return Collections.emptyList();
+    }
+
     public ListMultimap<Project.NameKey, ChangeNotes> create(Predicate<ChangeNotes> predicate)
         throws IOException {
       ListMultimap<Project.NameKey, ChangeNotes> m =
@@ -339,14 +383,23 @@
   private ImmutableListMultimap<PatchSet.Id, PatchSetApproval> approvals;
   private ImmutableSet<Comment.Key> commentKeys;
 
-  @VisibleForTesting
-  public ChangeNotes(Args args, Change change, boolean shouldExist, @Nullable RefCache refs) {
-    super(args, change.getId());
+  public ChangeNotes(
+      Args args,
+      Change change,
+      boolean shouldExist,
+      @Nullable RefCache refs,
+      @Nullable ObjectId metaSha1) {
+    super(args, change.getId(), metaSha1);
     this.change = new Change(change);
     this.shouldExist = shouldExist;
     this.refs = refs;
   }
 
+  @VisibleForTesting
+  public ChangeNotes(Args args, Change change, boolean shouldExist, @Nullable RefCache refs) {
+    this(args, change, shouldExist, refs, null);
+  }
+
   public Change getChange() {
     return change;
   }
diff --git a/java/com/google/gerrit/server/notedb/DraftCommentNotes.java b/java/com/google/gerrit/server/notedb/DraftCommentNotes.java
index 9b403e8..4988406 100644
--- a/java/com/google/gerrit/server/notedb/DraftCommentNotes.java
+++ b/java/com/google/gerrit/server/notedb/DraftCommentNotes.java
@@ -59,7 +59,7 @@
   }
 
   DraftCommentNotes(Args args, Change.Id changeId, Account.Id author, @Nullable Ref ref) {
-    super(args, changeId);
+    super(args, changeId, null);
     this.author = requireNonNull(author);
     this.ref = ref;
     if (ref != null) {
diff --git a/java/com/google/gerrit/server/notedb/RobotCommentNotes.java b/java/com/google/gerrit/server/notedb/RobotCommentNotes.java
index fe05643..d53b2ca 100644
--- a/java/com/google/gerrit/server/notedb/RobotCommentNotes.java
+++ b/java/com/google/gerrit/server/notedb/RobotCommentNotes.java
@@ -47,7 +47,7 @@
 
   @Inject
   RobotCommentNotes(Args args, @Assisted Change change) {
-    super(args, change.getId());
+    super(args, change.getId(), null);
     this.change = change;
   }
 
diff --git a/java/com/google/gerrit/server/permissions/DefaultRefFilter.java b/java/com/google/gerrit/server/permissions/DefaultRefFilter.java
index eca30b6..03d3b63 100644
--- a/java/com/google/gerrit/server/permissions/DefaultRefFilter.java
+++ b/java/com/google/gerrit/server/permissions/DefaultRefFilter.java
@@ -142,7 +142,7 @@
         return ImmutableList.of();
       }
       if (RefNames.isRefsChanges(refName)) {
-        boolean isChangeRefVisisble = canSeeSingleChangeRef(refName);
+        boolean isChangeRefVisisble = canSeeSingleChangeRef(repo, refName);
         if (isChangeRefVisisble) {
           logger.atFinest().log("Change ref %s is visible", refName);
           return refs;
@@ -466,7 +466,8 @@
    * with refs-in-wants is used as that enables Gerrit to skip traditional advertisement of all
    * visible refs.
    */
-  private boolean canSeeSingleChangeRef(String refName) throws PermissionBackendException {
+  private boolean canSeeSingleChangeRef(Repository repo, String refName)
+      throws PermissionBackendException {
     // We are treating just a single change ref. We are therefore not going through regular ref
     // filtering, but use NoteDb directly. This makes it so that we can always serve this ref
     // even if the change is not part of the set of most recent changes that
@@ -480,7 +481,7 @@
     }
     ChangeNotes notes;
     try {
-      notes = changeNotesFactory.create(projectState.getNameKey(), cId);
+      notes = changeNotesFactory.create(repo, projectState.getNameKey(), cId);
     } catch (StorageException e) {
       throw new PermissionBackendException("can't construct change notes", e);
     }
diff --git a/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java b/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
index 2021b2b..04e6d49 100644
--- a/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
+++ b/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
@@ -1417,6 +1417,12 @@
   private Predicate<ChangeData> getAuthorOrCommitterFullTextPredicate(
       String who, Function<String, Predicate<ChangeData>> fullPredicateFunc)
       throws QueryParseException {
+    if (isSelf(who)) {
+      IdentifiedUser me = args.getIdentifiedUser();
+      List<Predicate<ChangeData>> predicates =
+          me.getEmailAddresses().stream().map(fullPredicateFunc).collect(toList());
+      return Predicate.or(predicates);
+    }
     Set<String> parts = SchemaUtil.getNameParts(who);
     if (parts.isEmpty()) {
       throw error("invalid value");
diff --git a/java/com/google/gerrit/server/query/change/InternalChangeQuery.java b/java/com/google/gerrit/server/query/change/InternalChangeQuery.java
index 6605c23..1012f4a 100644
--- a/java/com/google/gerrit/server/query/change/InternalChangeQuery.java
+++ b/java/com/google/gerrit/server/query/change/InternalChangeQuery.java
@@ -193,6 +193,7 @@
 
     List<ChangeNotes> notes =
         notesFactory.create(
+            repo,
             branch.project(),
             changeIds,
             cn -> {
diff --git a/java/com/google/gerrit/server/restapi/change/ChangeIncludedIn.java b/java/com/google/gerrit/server/restapi/change/ChangeIncludedIn.java
index 67b5870..517fbdf 100644
--- a/java/com/google/gerrit/server/restapi/change/ChangeIncludedIn.java
+++ b/java/com/google/gerrit/server/restapi/change/ChangeIncludedIn.java
@@ -22,6 +22,7 @@
 import com.google.gerrit.server.PatchSetUtil;
 import com.google.gerrit.server.change.ChangeResource;
 import com.google.gerrit.server.change.IncludedIn;
+import com.google.gerrit.server.permissions.PermissionBackendException;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
 import java.io.IOException;
@@ -38,7 +39,8 @@
   }
 
   @Override
-  public Response<IncludedInInfo> apply(ChangeResource rsrc) throws RestApiException, IOException {
+  public Response<IncludedInInfo> apply(ChangeResource rsrc)
+      throws RestApiException, IOException, PermissionBackendException {
     PatchSet ps = psUtil.current(rsrc.getNotes());
     return Response.ok(includedIn.apply(rsrc.getProject(), ps.commitId().name()));
   }
diff --git a/java/com/google/gerrit/server/restapi/change/Move.java b/java/com/google/gerrit/server/restapi/change/Move.java
index 577174f..ecfb96d 100644
--- a/java/com/google/gerrit/server/restapi/change/Move.java
+++ b/java/com/google/gerrit/server/restapi/change/Move.java
@@ -191,9 +191,6 @@
           RevWalk revWalk = new RevWalk(repo)) {
         RevCommit currPatchsetRevCommit =
             revWalk.parseCommit(psUtil.current(ctx.getNotes()).commitId());
-        if (currPatchsetRevCommit.getParentCount() > 1) {
-          throw new ResourceConflictException("Merge commit cannot be moved");
-        }
 
         ObjectId refId = repo.resolve(input.destinationBranch);
         // Check if destination ref exists in project repo
diff --git a/java/com/google/gerrit/server/restapi/project/CommitIncludedIn.java b/java/com/google/gerrit/server/restapi/project/CommitIncludedIn.java
index a4a82ce..e566858 100644
--- a/java/com/google/gerrit/server/restapi/project/CommitIncludedIn.java
+++ b/java/com/google/gerrit/server/restapi/project/CommitIncludedIn.java
@@ -20,6 +20,7 @@
 import com.google.gerrit.extensions.restapi.RestApiException;
 import com.google.gerrit.extensions.restapi.RestReadView;
 import com.google.gerrit.server.change.IncludedIn;
+import com.google.gerrit.server.permissions.PermissionBackendException;
 import com.google.gerrit.server.project.CommitResource;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
@@ -36,7 +37,8 @@
   }
 
   @Override
-  public Response<IncludedInInfo> apply(CommitResource rsrc) throws RestApiException, IOException {
+  public Response<IncludedInInfo> apply(CommitResource rsrc)
+      throws RestApiException, IOException, PermissionBackendException {
     RevCommit commit = rsrc.getCommit();
     Project.NameKey project = rsrc.getProjectState().getNameKey();
     return Response.ok(includedIn.apply(project, commit.getId().getName()));
diff --git a/java/com/google/gerrit/truth/NullAwareCorrespondence.java b/java/com/google/gerrit/truth/NullAwareCorrespondence.java
index 687ad94..5b107a6 100644
--- a/java/com/google/gerrit/truth/NullAwareCorrespondence.java
+++ b/java/com/google/gerrit/truth/NullAwareCorrespondence.java
@@ -7,15 +7,6 @@
 // http://www.apache.org/licenses/LICENSE-2.0
 //
 // Unless required by applicable law or agreed to in writing, software
-// Copyright (C) 2020 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
diff --git a/javatests/com/google/gerrit/acceptance/api/project/CommitIT.java b/javatests/com/google/gerrit/acceptance/api/project/CommitIT.java
index 80e04c0..2356327 100644
--- a/javatests/com/google/gerrit/acceptance/api/project/CommitIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/project/CommitIT.java
@@ -15,7 +15,10 @@
 package com.google.gerrit.acceptance.api.project;
 
 import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.acceptance.GitUtil.pushHead;
 import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allow;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.block;
+import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
 import static java.util.stream.Collectors.toList;
 import static org.eclipse.jgit.lib.Constants.R_TAGS;
 
@@ -26,6 +29,7 @@
 import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
 import com.google.gerrit.entities.BranchNameKey;
 import com.google.gerrit.entities.Permission;
+import com.google.gerrit.entities.RefNames;
 import com.google.gerrit.extensions.api.changes.CherryPickInput;
 import com.google.gerrit.extensions.api.changes.IncludedInInfo;
 import com.google.gerrit.extensions.api.changes.ReviewInput;
@@ -96,6 +100,53 @@
   }
 
   @Test
+  public void includedInMergedChange_filtersOutNonVisibleBranches() throws Exception {
+    Result baseChange = createAndSubmitChange("refs/for/master");
+
+    createBranch(BranchNameKey.create(project, "test-branch-1"));
+    createBranch(BranchNameKey.create(project, "test-branch-2"));
+    createAndSubmitChange("refs/for/test-branch-1");
+    createAndSubmitChange("refs/for/test-branch-2");
+
+    assertThat(getIncludedIn(baseChange.getCommit().getId()).branches)
+        .containsExactly("master", "test-branch-1", "test-branch-2");
+
+    projectOperations
+        .project(project)
+        .forUpdate()
+        .add(block(Permission.READ).ref("refs/heads/test-branch-1").group(REGISTERED_USERS))
+        .update();
+
+    assertThat(getIncludedIn(baseChange.getCommit().getId()).branches)
+        .containsExactly("master", "test-branch-2");
+  }
+
+  @Test
+  public void includedInMergedChange_filtersOutNonVisibleTags() throws Exception {
+    String tagBase = "tag_base";
+    String tagBranch1 = "tag_1";
+
+    Result baseChange = createAndSubmitChange("refs/for/master");
+    createLightWeightTag(tagBase);
+    assertThat(getIncludedIn(baseChange.getCommit().getId()).tags).containsExactly(tagBase);
+
+    createBranch(BranchNameKey.create(project, "test-branch-1"));
+    createAndSubmitChange("refs/for/test-branch-1");
+    createLightWeightTag(tagBranch1);
+    assertThat(getIncludedIn(baseChange.getCommit().getId()).tags)
+        .containsExactly(tagBase, tagBranch1);
+
+    projectOperations
+        .project(project)
+        .forUpdate()
+        // Tag permissions are controlled by read permissions on branches. Blocking read permission
+        // on test-branch-1 so that tagBranch1 becomes non-visible
+        .add(block(Permission.READ).ref("refs/heads/test-branch-1").group(REGISTERED_USERS))
+        .update();
+    assertThat(getIncludedIn(baseChange.getCommit().getId()).tags).containsExactly(tagBase);
+  }
+
+  @Test
   public void cherryPickWithoutMessage() throws Exception {
     String branch = "foo";
 
@@ -192,4 +243,15 @@
     assertThat(actual.email).isEqualTo(expected.email());
     assertThat(actual.name).isEqualTo(expected.fullName());
   }
+
+  private Result createAndSubmitChange(String branch) throws Exception {
+    Result r = createChange(branch);
+    approve(r.getChangeId());
+    gApi.changes().id(r.getChangeId()).current().submit();
+    return r;
+  }
+
+  private void createLightWeightTag(String tagName) throws Exception {
+    pushHead(testRepo, RefNames.REFS_TAGS + tagName, false, false);
+  }
 }
diff --git a/javatests/com/google/gerrit/acceptance/rest/DynamicOptionsBeanParseListenerIT.java b/javatests/com/google/gerrit/acceptance/rest/DynamicOptionsBeanParseListenerIT.java
new file mode 100644
index 0000000..39f1e8d
--- /dev/null
+++ b/javatests/com/google/gerrit/acceptance/rest/DynamicOptionsBeanParseListenerIT.java
@@ -0,0 +1,67 @@
+// Copyright (C) 2021 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.acceptance.rest;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.RestResponse;
+import com.google.gerrit.extensions.annotations.Exports;
+import com.google.gerrit.json.OutputFormat;
+import com.google.gerrit.server.DynamicOptions;
+import com.google.gerrit.server.restapi.project.ListProjects;
+import com.google.gson.Gson;
+import com.google.gson.reflect.TypeToken;
+import com.google.inject.AbstractModule;
+import java.util.Map;
+import org.junit.Test;
+
+public class DynamicOptionsBeanParseListenerIT extends AbstractDaemonTest {
+  private static final Gson GSON = OutputFormat.JSON.newGson();
+
+  @Test
+  public void testBeanParseListener() throws Exception {
+    createProjectOverAPI("project1", project, true, null);
+    createProjectOverAPI("project2", project, true, null);
+    try (AutoCloseable ignored = installPlugin("my-plugin", PluginModule.class)) {
+      assertThat(getProjects(adminRestSession.get("/projects/"))).hasSize(1);
+    }
+  }
+
+  protected Map<String, Object> getProjects(RestResponse res) throws Exception {
+    res.assertOK();
+    return GSON.fromJson(res.getReader(), new TypeToken<Map<String, Object>>() {}.getType());
+  }
+
+  protected static class ListProjectsBeanListener implements DynamicOptions.BeanParseListener {
+    @Override
+    public void onBeanParseStart(String plugin, Object bean) {
+      ListProjects listProjects = (ListProjects) bean;
+      listProjects.setLimit(1);
+    }
+
+    @Override
+    public void onBeanParseEnd(String plugin, Object bean) {}
+  }
+
+  protected static class PluginModule extends AbstractModule {
+    @Override
+    public void configure() {
+      bind(DynamicOptions.DynamicBean.class)
+          .annotatedWith(Exports.named(ListProjects.class))
+          .to(ListProjectsBeanListener.class);
+    }
+  }
+}
diff --git a/javatests/com/google/gerrit/acceptance/rest/change/MoveChangeIT.java b/javatests/com/google/gerrit/acceptance/rest/change/MoveChangeIT.java
index d5881ea..987d646 100644
--- a/javatests/com/google/gerrit/acceptance/rest/change/MoveChangeIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/change/MoveChangeIT.java
@@ -169,11 +169,9 @@
     BranchNameKey newBranch =
         BranchNameKey.create(r1.getChange().change().getProject(), "moveTest");
     createBranch(newBranch);
-    ResourceConflictException thrown =
-        assertThrows(
-            ResourceConflictException.class,
-            () -> move(GitUtil.getChangeId(testRepo, c).get(), newBranch.branch()));
-    assertThat(thrown).hasMessageThat().contains("Merge commit cannot be moved");
+    String changeId = GitUtil.getChangeId(testRepo, c).get();
+    move(changeId, newBranch.branch());
+    assertThat(gApi.changes().id(changeId).get().branch).isEqualTo("moveTest");
   }
 
   @Test
diff --git a/javatests/com/google/gerrit/acceptance/ssh/DynamicOptionsBeanParseListenerIT.java b/javatests/com/google/gerrit/acceptance/ssh/DynamicOptionsBeanParseListenerIT.java
new file mode 100644
index 0000000..0afdbc6
--- /dev/null
+++ b/javatests/com/google/gerrit/acceptance/ssh/DynamicOptionsBeanParseListenerIT.java
@@ -0,0 +1,69 @@
+// Copyright (C) 2021 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.acceptance.ssh;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.NoHttpd;
+import com.google.gerrit.acceptance.UseSsh;
+import com.google.gerrit.extensions.annotations.Exports;
+import com.google.gerrit.server.DynamicOptions;
+import com.google.gerrit.sshd.commands.ListProjectsCommand;
+import com.google.inject.AbstractModule;
+import java.util.Arrays;
+import java.util.List;
+import org.junit.Test;
+
+@NoHttpd
+@UseSsh
+public class DynamicOptionsBeanParseListenerIT extends AbstractDaemonTest {
+
+  @Test
+  public void testBeanParseListener() throws Exception {
+    createProjectOverAPI("project1", project, true, null);
+    createProjectOverAPI("project2", project, true, null);
+    try (AutoCloseable ignored = installPlugin("my-plugin", PluginModule.class)) {
+      String output = adminSshSession.exec("gerrit ls-projects");
+      adminSshSession.assertSuccess();
+      assertThat(getProjects(output)).hasSize(1);
+    }
+  }
+
+  protected List<String> getProjects(String sshOutput) {
+    return Arrays.asList(sshOutput.split("\n"));
+  }
+
+  protected static class ListProjectsCommandBeanListener
+      implements DynamicOptions.BeanParseListener {
+    @Override
+    public void onBeanParseStart(String plugin, Object bean) {
+      ListProjectsCommand command = (ListProjectsCommand) bean;
+      command.impl.setLimit(1);
+    }
+
+    @Override
+    public void onBeanParseEnd(String plugin, Object bean) {}
+  }
+
+  protected static class PluginModule extends AbstractModule {
+    @Override
+    public void configure() {
+      bind(DynamicOptions.DynamicBean.class)
+          .annotatedWith(Exports.named(ListProjectsCommand.class))
+          .to(ListProjectsCommandBeanListener.class);
+    }
+  }
+}
diff --git a/javatests/com/google/gerrit/acceptance/testsuite/project/ProjectOperationsImplTest.java b/javatests/com/google/gerrit/acceptance/testsuite/project/ProjectOperationsImplTest.java
index 00d01d6..7543ba8 100644
--- a/javatests/com/google/gerrit/acceptance/testsuite/project/ProjectOperationsImplTest.java
+++ b/javatests/com/google/gerrit/acceptance/testsuite/project/ProjectOperationsImplTest.java
@@ -595,6 +595,15 @@
   }
 
   @Test
+  public void removeAllAccessSections() {
+    projectOperations.allProjectsForUpdate().removeAllAccessSections().update();
+
+    assertThat(projectOperations.project(allProjects).getConfig())
+        .sectionValues("access")
+        .isEmpty();
+  }
+
+  @Test
   public void updatingCapabilitiesNotAllowedForNonAllProjects() throws Exception {
     Project.NameKey key = projectOperations.newProject().create();
     assertThrows(
diff --git a/javatests/com/google/gerrit/integration/git/GitProtocolV2IT.java b/javatests/com/google/gerrit/integration/git/GitProtocolV2IT.java
index 76ce956..3068003 100644
--- a/javatests/com/google/gerrit/integration/git/GitProtocolV2IT.java
+++ b/javatests/com/google/gerrit/integration/git/GitProtocolV2IT.java
@@ -98,10 +98,7 @@
                   .group(SystemGroupBackend.REGISTERED_USERS))
           .update();
 
-      // Set protocol.version=2 in target repository
-      execute(
-          ImmutableList.of("git", "config", "protocol.version", "2"),
-          sitePaths.site_path.resolve("git").resolve(project.get() + Constants.DOT_GIT).toFile());
+      setProtocolV2(project);
 
       // Retrieve HTTP url
       String url = config.getString("gerrit", null, "canonicalweburl");
@@ -217,14 +214,7 @@
       Project.NameKey allRefsVisibleProject = Project.nameKey("all-refs-visible");
       gApi.projects().create(allRefsVisibleProject.get());
 
-      // Set protocol.version=2 in target repository
-      execute(
-          ImmutableList.of("git", "config", "protocol.version", "2"),
-          sitePaths
-              .site_path
-              .resolve("git")
-              .resolve(allRefsVisibleProject.get() + Constants.DOT_GIT)
-              .toFile());
+      setProtocolV2(allRefsVisibleProject);
 
       // Set up project permission to allow reading all refs
       projectOperations
@@ -280,14 +270,7 @@
       Project.NameKey privateProject = Project.nameKey("private-project");
       gApi.projects().create(privateProject.get());
 
-      // Set protocol.version=2 in target repository
-      execute(
-          ImmutableList.of("git", "config", "protocol.version", "2"),
-          sitePaths
-              .site_path
-              .resolve("git")
-              .resolve(privateProject.get() + Constants.DOT_GIT)
-              .toFile());
+      setProtocolV2(privateProject);
 
       // Disallow general read permissions for anonymous users
       projectOperations
@@ -356,6 +339,12 @@
                 UTF_8));
   }
 
+  private void setProtocolV2(Project.NameKey projectName) throws Exception {
+    execute(
+        ImmutableList.of("git", "config", "protocol.version", "2"),
+        sitePaths.site_path.resolve("git").resolve(projectName.get() + Constants.DOT_GIT).toFile());
+  }
+
   private static void assertGitProtocolV2Refs(String commit, String out) {
     assertThat(out).contains("git< version 2");
     assertThat(out).contains("refs/changes/01/1/1");
diff --git a/javatests/com/google/gerrit/server/change/IncludedInResolverTest.java b/javatests/com/google/gerrit/server/change/IncludedInResolverTest.java
index 19c479d..b69a894 100644
--- a/javatests/com/google/gerrit/server/change/IncludedInResolverTest.java
+++ b/javatests/com/google/gerrit/server/change/IncludedInResolverTest.java
@@ -17,9 +17,13 @@
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.gerrit.entities.RefNames.REFS_TAGS;
 
+import com.google.common.truth.Correspondence;
+import com.google.gerrit.truth.NullAwareCorrespondence;
 import org.eclipse.jgit.internal.storage.dfs.DfsRepositoryDescription;
 import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
 import org.eclipse.jgit.junit.TestRepository;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.revwalk.RevCommit;
 import org.eclipse.jgit.revwalk.RevObject;
 import org.eclipse.jgit.revwalk.RevTag;
@@ -112,8 +116,12 @@
     IncludedInResolver.Result detail = resolve(commit_v2_5);
 
     // Check that only tags and branches which refer the tip are returned
-    assertThat(detail.tags()).containsExactly(TAG_2_5, TAG_2_5_ANNOTATED, TAG_2_5_ANNOTATED_TWICE);
-    assertThat(detail.branches()).containsExactly(BRANCH_2_5);
+    assertThat(detail.tags())
+        .comparingElementsUsing(hasShortName())
+        .containsExactly(TAG_2_5, TAG_2_5_ANNOTATED, TAG_2_5_ANNOTATED_TWICE);
+    assertThat(detail.branches())
+        .comparingElementsUsing(hasShortName())
+        .containsExactly(BRANCH_2_5);
   }
 
   @Test
@@ -123,6 +131,7 @@
 
     // Check whether all tags and branches are returned
     assertThat(detail.tags())
+        .comparingElementsUsing(hasShortName())
         .containsExactly(
             TAG_1_0,
             TAG_1_0_1,
@@ -133,6 +142,7 @@
             TAG_2_5_ANNOTATED,
             TAG_2_5_ANNOTATED_TWICE);
     assertThat(detail.branches())
+        .comparingElementsUsing(hasShortName())
         .containsExactly(BRANCH_MASTER, BRANCH_1_0, BRANCH_1_3, BRANCH_2_0, BRANCH_2_5);
   }
 
@@ -143,8 +153,11 @@
 
     // Check whether all succeeding tags and branches are returned
     assertThat(detail.tags())
+        .comparingElementsUsing(hasShortName())
         .containsExactly(TAG_1_3, TAG_2_5, TAG_2_5_ANNOTATED, TAG_2_5_ANNOTATED_TWICE);
-    assertThat(detail.branches()).containsExactly(BRANCH_1_3, BRANCH_2_5);
+    assertThat(detail.branches())
+        .comparingElementsUsing(hasShortName())
+        .containsExactly(BRANCH_1_3, BRANCH_2_5);
   }
 
   private IncludedInResolver.Result resolve(RevCommit commit) throws Exception {
@@ -154,4 +167,9 @@
   private RevTag tag(String name, RevObject dest) throws Exception {
     return tr.update(REFS_TAGS + name, tr.tag(name, dest));
   }
+
+  private static Correspondence<Ref, String> hasShortName() {
+    return NullAwareCorrespondence.transforming(
+        ref -> Repository.shortenRefName(ref.getName()), "has short name");
+  }
 }
diff --git a/javatests/com/google/gerrit/server/events/EventDeserializerTest.java b/javatests/com/google/gerrit/server/events/EventDeserializerTest.java
index e0223e4..97f6e4e 100644
--- a/javatests/com/google/gerrit/server/events/EventDeserializerTest.java
+++ b/javatests/com/google/gerrit/server/events/EventDeserializerTest.java
@@ -22,6 +22,8 @@
 import com.google.gerrit.entities.BranchNameKey;
 import com.google.gerrit.entities.Change;
 import com.google.gerrit.entities.Project;
+import com.google.gerrit.server.config.AllProjectsName;
+import com.google.gerrit.server.config.AllUsersName;
 import com.google.gerrit.server.data.AccountAttribute;
 import com.google.gerrit.server.data.ChangeAttribute;
 import com.google.gerrit.server.data.RefUpdateAttribute;
@@ -240,6 +242,31 @@
     assertSameChangeEvent(e, orig);
   }
 
+  @Test
+  public void shouldSerializeAllProjectsToString() {
+    String allProjectsString = "foobar";
+    AllProjectsName allProjectsNameKey = new AllProjectsName(allProjectsString);
+
+    assertThat(gson.toJson(allProjectsNameKey))
+        .isEqualTo(String.format("\"%s\"", allProjectsString));
+  }
+
+  @Test
+  public void shouldSerializeAllUsersToString() {
+    String allUsersString = "foobar";
+    AllUsersName allUsersNameKey = new AllUsersName(allUsersString);
+
+    assertThat(gson.toJson(allUsersNameKey)).isEqualTo(String.format("\"%s\"", allUsersString));
+  }
+
+  @Test
+  public void shouldSerializeProjectNameKeyToString() {
+    String projectString = "foobar";
+    Project.NameKey projectNameKey = Project.nameKey(projectString);
+
+    assertThat(gson.toJson(projectNameKey)).isEqualTo(String.format("\"%s\"", projectString));
+  }
+
   private <T> Supplier<T> createSupplier(T value) {
     return Suppliers.memoize(() -> value);
   }
diff --git a/javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java b/javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
index 02f514a..297977c 100644
--- a/javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
+++ b/javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
@@ -617,9 +617,14 @@
     PersonIdent johnDoe = new PersonIdent("John Doe", "john.doe@example.com");
     PersonIdent john = new PersonIdent("John", "john@example.com");
     PersonIdent doeSmith = new PersonIdent("Doe Smith", "doe_smith@example.com");
+    Account ua = user.asIdentifiedUser().getAccount();
+    PersonIdent myself = new PersonIdent("I Am", ua.preferredEmail());
+    PersonIdent selfName = new PersonIdent("My Self", "my.self@example.com");
+
     Change change1 = createChange(repo, johnDoe);
     Change change2 = createChange(repo, john);
     Change change3 = createChange(repo, doeSmith);
+    Change change4 = createChange(repo, selfName);
 
     // Only email address.
     assertQuery(searchOperator + "john.doe@example.com", change1);
@@ -635,6 +640,18 @@
     assertQuery(searchOperator + "\"John <john.doe@example.com>\"");
     assertQuery(searchOperator + "\"Doe John <john@example.com>\"");
     assertQuery(searchOperator + "\"Doe John <doe_smith@example.com>\"");
+
+    // Partial name
+    assertQuery(searchOperator + "ohn");
+    assertQuery(searchOperator + "smith", change3);
+
+    // The string 'self' in the name should not be matched
+    assertQuery(searchOperator + "self");
+
+    // ':self' matches a change created with the current user's email address
+    Change change5 = createChange(repo, myself);
+    assertQuery(searchOperator + "me", change5);
+    assertQuery(searchOperator + "self", change5);
   }
 
   private void byAuthorOrCommitterFullText(String searchOperator) throws Exception {
diff --git a/modules/jgit b/modules/jgit
index 24d6d60..8470771 160000
--- a/modules/jgit
+++ b/modules/jgit
@@ -1 +1 @@
-Subproject commit 24d6d605388c82201092cf1699b51095299380a2
+Subproject commit 84707715108a65a366ef35f2ae04aabecd0b35f6
diff --git a/plugins/codemirror-editor b/plugins/codemirror-editor
index 7357ab4..c621796 160000
--- a/plugins/codemirror-editor
+++ b/plugins/codemirror-editor
@@ -1 +1 @@
-Subproject commit 7357ab473599d16ae33cc982bbd65472f08c2dd6
+Subproject commit c6217963e42322accc3f0bacb6540f8791f67ab0
diff --git a/plugins/gitiles b/plugins/gitiles
index 584360d..25580c3 160000
--- a/plugins/gitiles
+++ b/plugins/gitiles
@@ -1 +1 @@
-Subproject commit 584360d050f5adf8317ebc078a874da9c4316bd0
+Subproject commit 25580c3e60a58265b74ac5022b1dbde0e9ef008e
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-plugin-config/gr-repo-plugin-config.ts b/polygerrit-ui/app/elements/admin/gr-repo-plugin-config/gr-repo-plugin-config.ts
index e9a6158..880b397 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-plugin-config/gr-repo-plugin-config.ts
+++ b/polygerrit-ui/app/elements/admin/gr-repo-plugin-config/gr-repo-plugin-config.ts
@@ -117,8 +117,8 @@
     );
   }
 
-  _computeDisabled(editable: string) {
-    return editable === 'false';
+  _computeDisabled(editable: boolean) {
+    return !editable;
   }
 
   _computeChecked(value = 'false') {
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-plugin-config/gr-repo-plugin-config_test.js b/polygerrit-ui/app/elements/admin/gr-repo-plugin-config/gr-repo-plugin-config_test.js
index 168984a..28ef2f8 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-plugin-config/gr-repo-plugin-config_test.js
+++ b/polygerrit-ui/app/elements/admin/gr-repo-plugin-config/gr-repo-plugin-config_test.js
@@ -39,8 +39,10 @@
   });
 
   test('_computeDisabled', () => {
-    assert.isFalse(element._computeDisabled('true'));
-    assert.isTrue(element._computeDisabled('false'));
+    assert.isFalse(element._computeDisabled(true));
+    assert.isTrue(element._computeDisabled(undefined));
+    assert.isTrue(element._computeDisabled(null));
+    assert.isTrue(element._computeDisabled(false));
   });
 
   test('_handleChange', () => {
@@ -75,7 +77,7 @@
     test('ARRAY type option', () => {
       element.pluginData = {
         name: 'testName',
-        config: {plugin: {value: 'test', type: 'ARRAY'}},
+        config: {plugin: {value: 'test', type: 'ARRAY', editable: true}},
       };
       flush();
 
@@ -90,7 +92,7 @@
     test('BOOLEAN type option', () => {
       element.pluginData = {
         name: 'testName',
-        config: {plugin: {value: 'true', type: 'BOOLEAN'}},
+        config: {plugin: {value: 'true', type: 'BOOLEAN', editable: true}},
       };
       flush();
 
@@ -109,7 +111,7 @@
     test('INT/LONG/STRING type option', () => {
       element.pluginData = {
         name: 'testName',
-        config: {plugin: {value: 'test', type: 'STRING'}},
+        config: {plugin: {value: 'test', type: 'STRING', editable: true}},
       };
       flush();
 
@@ -130,7 +132,9 @@
       const permitted_values = ['test', 'newTest'];
       element.pluginData = {
         name: 'testName',
-        config: {plugin: {value: 'test', type: 'LIST', permitted_values}},
+        config: {plugin:
+          {value: 'test', type: 'LIST', editable: true, permitted_values},
+        },
       };
       flush();
 
diff --git a/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.ts b/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.ts
index d34dcc9..abbe316 100644
--- a/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.ts
+++ b/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.ts
@@ -314,6 +314,8 @@
         // Fetch projects.
         return this.projectSuggestions(predicate, expression);
 
+      case 'assignee':
+      case 'attention':
       case 'author':
       case 'cc':
       case 'commentby':
diff --git a/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label.ts b/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label.ts
index be23cb3..5852135 100644
--- a/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label.ts
+++ b/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label.ts
@@ -151,9 +151,11 @@
     change: ChangeInfo,
     force: boolean
   ) {
+    const hasAvatars = !!config?.plugin?.has_avatars;
     return (
       !hideAvatar &&
-      !this._hasAttention(config, highlight, account, change, force)
+      !this._hasAttention(config, highlight, account, change, force) &&
+      hasAvatars
     );
   }
 
diff --git a/polygerrit-ui/app/elements/shared/gr-tooltip/gr-tooltip_test.js b/polygerrit-ui/app/elements/shared/gr-tooltip/gr-tooltip_test.js
index b5f068c..65a442b 100644
--- a/polygerrit-ui/app/elements/shared/gr-tooltip/gr-tooltip_test.js
+++ b/polygerrit-ui/app/elements/shared/gr-tooltip/gr-tooltip_test.js
@@ -26,8 +26,9 @@
 
 suite('gr-tooltip tests', () => {
   let element;
-  setup(() => {
+  setup(async () => {
     element = basicFixture.instantiate();
+    await flush();
   });
 
   test('max-width is respected if set', () => {
diff --git a/tools/bzl/js.bzl b/tools/bzl/js.bzl
index 5214cec..dcc4739 100644
--- a/tools/bzl/js.bzl
+++ b/tools/bzl/js.bzl
@@ -28,7 +28,7 @@
     else:
         fail("repository %s not in {%s,%s}" % (repository, GERRIT, NPMJS))
 
-    python = ctx.which("python")
+    python = ctx.which("python3")
     script = ctx.path(ctx.attr._download_script)
 
     args = [python, script, "-o", dest, "-u", url, "-v", sha1]
@@ -51,7 +51,7 @@
 
 # for use in repo rules.
 def _run_npm_binary_str(ctx, tarball, args):
-    python_bin = ctx.which("python")
+    python_bin = ctx.which("python3")
     return " ".join([
         str(python_bin),
         str(ctx.path(ctx.attr._run_npm)),
@@ -65,7 +65,7 @@
     version_name = "%s__version.json" % ctx.name
 
     cmd = [
-        ctx.which("python"),
+        ctx.which("python3"),
         ctx.path(ctx.attr._download_bower),
         "-b",
         "%s" % _run_npm_binary_str(ctx, ctx.attr._bower_archive, []),
@@ -320,7 +320,7 @@
     app_path = app_path[app_path.index(pkg_dir) + len(pkg_dir):]
 
     hermetic_npm_binary = " ".join([
-        "python",
+        "python3",
         "$p/" + ctx.file._run_npm.path,
         "$p/" + ctx.file._bundler_archive.path,
         "--inline-scripts",
@@ -371,7 +371,7 @@
 
     if ctx.attr.split:
         hermetic_npm_command = "export PATH && " + " ".join([
-            "python",
+            "python3",
             ctx.file._run_npm.path,
             ctx.file._crisper_archive.path,
             "--script-in-head=false",
diff --git a/tools/bzl/license-map.py b/tools/bzl/license-map.py
index 221ae2f..3e4fc92 100644
--- a/tools/bzl/license-map.py
+++ b/tools/bzl/license-map.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 # reads bazel query XML files, to join target names with their licenses.
 
@@ -24,7 +24,7 @@
 
 def read_file(filename):
     "Reads file and returns its content"
-    with open(filename) as fd:
+    with open(filename, encoding='utf-8') as fd:
         return fd.read()
 
 # List of files in package to which license is applied.
@@ -56,6 +56,8 @@
 LicenseMapItem = namedtuple("LicenseMapItem",
                             ["name", "safename", "packages", "license_text"])
 
+def print_utf8(str=""):
+  stdout.buffer.write((str + "\n").encode('utf-8'))
 
 def load_xmls(xml_filenames):
     """Load xml files produced by bazel query
@@ -134,7 +136,7 @@
 
     if args.asciidoctor:
         # We don't want any blank line before "= Gerrit Code Review - Licenses"
-        print("""= Gerrit Code Review - Licenses
+        print_utf8("""= Gerrit Code Review - Licenses
 
 // DO NOT EDIT - GENERATED AUTOMATICALLY.
 
@@ -178,10 +180,10 @@
     for data in xml_data + json_map_data:
         name = data.name
         safename = data.safename
-        print()
-        print("[[%s]]" % safename)
-        print(name)
-        print()
+        print_utf8()
+        print_utf8("[[%s]]" % safename)
+        print_utf8(name)
+        print_utf8()
         for p in data.packages:
             package_notice = ""
             if p.licensed_files.kind == "OnlySpecificFiles":
@@ -189,20 +191,20 @@
             elif p.licensed_files.kind == "AllFilesExceptSpecific":
                 package_notice = " - except the following file(s):"
 
-            print("* " + get_package_display_name(p) + package_notice)
+            print_utf8("* " + get_package_display_name(p) + package_notice)
             for file in p.licensed_files.files:
-                print("** " + file)
-        print()
-        print("[[%s_license]]" % safename)
-        print("----")
+                print_utf8("** " + file)
+        print_utf8()
+        print_utf8("[[%s_license]]" % safename)
+        print_utf8("----")
         license_text = data.license_text
-        print(data.license_text.rstrip("\r\n"))
-        print()
-        print("----")
-        print()
+        print_utf8(data.license_text.rstrip("\r\n"))
+        print_utf8()
+        print_utf8("----")
+        print_utf8()
 
     if args.asciidoctor:
-        print("""
+        print_utf8("""
 GERRIT
 ------
 Part of link:index.html[Gerrit Code Review]
@@ -219,7 +221,7 @@
     """
     result = []
     for json_map in json_filenames:
-        with open(json_map, 'r') as f:
+        with open(json_map, 'r', encoding='utf-8') as f:
             licenses_list = json.load(f)
         for license_id, license in licenses_list.items():
             name = license["licenseName"]
diff --git a/tools/bzl/license.bzl b/tools/bzl/license.bzl
index cdb13d0..7b1375a 100644
--- a/tools/bzl/license.bzl
+++ b/tools/bzl/license.bzl
@@ -50,7 +50,7 @@
     # post process the XML into our favorite format.
     native.genrule(
         name = "gen_license_txt_" + name,
-        cmd = "python $(location //tools/bzl:license-map.py) %s %s %s > $@" % (" ".join(opts), " ".join(json_maps_locations), " ".join(xmls)),
+        cmd = "python3 $(location //tools/bzl:license-map.py) %s %s %s > $@" % (" ".join(opts), " ".join(json_maps_locations), " ".join(xmls)),
         outs = [name + ".gen.txt"],
         tools = tools,
         **kwargs
diff --git a/tools/bzl/maven_jar.bzl b/tools/bzl/maven_jar.bzl
index d96ffc2..adea89e 100644
--- a/tools/bzl/maven_jar.bzl
+++ b/tools/bzl/maven_jar.bzl
@@ -141,7 +141,7 @@
     binjar_path = ctx.path("/".join(["jar", binjar]))
     binurl = url + ".jar"
 
-    python = ctx.which("python")
+    python = ctx.which("python3")
     script = ctx.path(ctx.attr._download_script)
 
     args = [python, script, "-o", binjar_path, "-u", binurl]
diff --git a/tools/download_file.py b/tools/download_file.py
index 936bcef..2af2c07 100755
--- a/tools/download_file.py
+++ b/tools/download_file.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 # Copyright (C) 2013 The Android Open Source Project
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
diff --git a/tools/eclipse/project.py b/tools/eclipse/project.py
index acb5346..6cfa858 100755
--- a/tools/eclipse/project.py
+++ b/tools/eclipse/project.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 # Copyright (C) 2016 The Android Open Source Project
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
diff --git a/tools/js/download_bower.py b/tools/js/download_bower.py
index d541b56..2a75fc1 100755
--- a/tools/js/download_bower.py
+++ b/tools/js/download_bower.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 # Copyright (C) 2015 The Android Open Source Project
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
diff --git a/tools/js/npm_pack.py b/tools/js/npm_pack.py
index 57f3166..33b38a0 100755
--- a/tools/js/npm_pack.py
+++ b/tools/js/npm_pack.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 # Copyright (C) 2015 The Android Open Source Project
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
diff --git a/tools/js/run_npm_binary.py b/tools/js/run_npm_binary.py
index bdee5ab..31f8a54 100644
--- a/tools/js/run_npm_binary.py
+++ b/tools/js/run_npm_binary.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 # Copyright (C) 2015 The Android Open Source Project
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
diff --git a/tools/maven/gerrit-acceptance-framework_pom.xml b/tools/maven/gerrit-acceptance-framework_pom.xml
index 4e20948..de2336e 100644
--- a/tools/maven/gerrit-acceptance-framework_pom.xml
+++ b/tools/maven/gerrit-acceptance-framework_pom.xml
@@ -2,7 +2,7 @@
   <modelVersion>4.0.0</modelVersion>
   <groupId>com.google.gerrit</groupId>
   <artifactId>gerrit-acceptance-framework</artifactId>
-  <version>3.3.6-SNAPSHOT</version>
+  <version>3.3.8-SNAPSHOT</version>
   <packaging>jar</packaging>
   <name>Gerrit Code Review - Acceptance Test Framework</name>
   <description>Framework for Gerrit's acceptance tests</description>
diff --git a/tools/maven/gerrit-extension-api_pom.xml b/tools/maven/gerrit-extension-api_pom.xml
index 15cfc33..4a9bcb1 100644
--- a/tools/maven/gerrit-extension-api_pom.xml
+++ b/tools/maven/gerrit-extension-api_pom.xml
@@ -2,7 +2,7 @@
   <modelVersion>4.0.0</modelVersion>
   <groupId>com.google.gerrit</groupId>
   <artifactId>gerrit-extension-api</artifactId>
-  <version>3.3.6-SNAPSHOT</version>
+  <version>3.3.8-SNAPSHOT</version>
   <packaging>jar</packaging>
   <name>Gerrit Code Review - Extension API</name>
   <description>API for Gerrit Extensions</description>
diff --git a/tools/maven/gerrit-plugin-api_pom.xml b/tools/maven/gerrit-plugin-api_pom.xml
index 37857cc..38b4536 100644
--- a/tools/maven/gerrit-plugin-api_pom.xml
+++ b/tools/maven/gerrit-plugin-api_pom.xml
@@ -2,7 +2,7 @@
   <modelVersion>4.0.0</modelVersion>
   <groupId>com.google.gerrit</groupId>
   <artifactId>gerrit-plugin-api</artifactId>
-  <version>3.3.6-SNAPSHOT</version>
+  <version>3.3.8-SNAPSHOT</version>
   <packaging>jar</packaging>
   <name>Gerrit Code Review - Plugin API</name>
   <description>API for Gerrit Plugins</description>
diff --git a/tools/maven/gerrit-war_pom.xml b/tools/maven/gerrit-war_pom.xml
index eb3f657..e848b43 100644
--- a/tools/maven/gerrit-war_pom.xml
+++ b/tools/maven/gerrit-war_pom.xml
@@ -2,7 +2,7 @@
   <modelVersion>4.0.0</modelVersion>
   <groupId>com.google.gerrit</groupId>
   <artifactId>gerrit-war</artifactId>
-  <version>3.3.6-SNAPSHOT</version>
+  <version>3.3.8-SNAPSHOT</version>
   <packaging>war</packaging>
   <name>Gerrit Code Review - WAR</name>
   <description>Gerrit WAR</description>
diff --git a/tools/maven/mvn.py b/tools/maven/mvn.py
index 60e9f15..4ed5bf9 100755
--- a/tools/maven/mvn.py
+++ b/tools/maven/mvn.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 # Copyright (C) 2013 The Android Open Source Project
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
diff --git a/tools/util_test.py b/tools/util_test.py
index 1a389f5..ab1133b2 100644
--- a/tools/util_test.py
+++ b/tools/util_test.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 # Copyright (C) 2013 The Android Open Source Project
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
diff --git a/tools/version.py b/tools/version.py
index 2326757..d02fc26 100755
--- a/tools/version.py
+++ b/tools/version.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 # Copyright (C) 2014 The Android Open Source Project
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
diff --git a/tools/workspace_status.py b/tools/workspace_status.py
index 443c2f0..bedc051 100644
--- a/tools/workspace_status.py
+++ b/tools/workspace_status.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 # This script will be run by bazel when the build process starts to
 # generate key-value information that represents the status of the
diff --git a/tools/workspace_status_release.py b/tools/workspace_status_release.py
index 36535fb..b3e72ff 100644
--- a/tools/workspace_status_release.py
+++ b/tools/workspace_status_release.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 # This is a variant of the `workspace_status.py` script that in addition to
 # plain `git describe` implements a few heuristics to arrive at more to the
@@ -9,7 +9,7 @@
 #
 # To use it, simply add
 #
-#   --workspace_status_command="python ./tools/workspace_status_release.py"
+#   --workspace_status_command="python3 ./tools/workspace_status_release.py"
 #
 # to your bazel command. So for example instead of
 #
@@ -17,11 +17,11 @@
 #
 # use
 #
-#   bazel build --workspace_status_command="python ./tools/workspace_status_release.py" release.war
+#   bazel build --workspace_status_command="python3 ./tools/workspace_status_release.py" release.war
 #
 # Alternatively, you can add
 #
-#   build --workspace_status_command="python ./tools/workspace_status_release.py"
+#   build --workspace_status_command="python3 ./tools/workspace_status_release.py"
 #
 # to `.bazelrc` in your home directory.
 #
@@ -150,7 +150,7 @@
         'tools', 'workspace_status_release.py')
     if os.path.isfile(workspace_status_script):
         # directory has own workspace_status_command, so we use stamps from that
-        for line in run(["python", workspace_status_script]).split('\n'):
+        for line in run(["python3", workspace_status_script]).split('\n'):
             if re.search("^STABLE_[a-zA-Z0-9().:@/_ -]*$", line):
                 print(line)
     else:
diff --git a/version.bzl b/version.bzl
index ef49388..661f6d5 100644
--- a/version.bzl
+++ b/version.bzl
@@ -2,4 +2,4 @@
 # Used by :api_install and :api_deploy targets
 # when talking to the destination repository.
 #
-GERRIT_VERSION = "3.3.6-SNAPSHOT"
+GERRIT_VERSION = "3.3.8-SNAPSHOT"