Merge branch 'stable-2.16'

* stable-2.16:
  GroupsBaseInfo: Add toString method
  Redirect /Documentation(/)? to /Documentation/index.html
  Redirect /Documentation(/)? to /Documentation/index.html
  Use regex for query route pattern
  Set "never" referrer policy
  Add project to changeBaseURL calls
  BugFix: Add the BeanReceiver plumbing to the top-level query commands
  Use project lookup on change starring requests
  Release 2.16-rc2

Change-Id: I1bb7a73b0f78756187a53551515df3465ee6aafb
diff --git a/java/com/google/gerrit/extensions/common/GroupBaseInfo.java b/java/com/google/gerrit/extensions/common/GroupBaseInfo.java
index 288adb6..4d35b36 100644
--- a/java/com/google/gerrit/extensions/common/GroupBaseInfo.java
+++ b/java/com/google/gerrit/extensions/common/GroupBaseInfo.java
@@ -14,7 +14,14 @@
 
 package com.google.gerrit.extensions.common;
 
+import com.google.common.base.MoreObjects;
+
 public class GroupBaseInfo {
   public String id;
   public String name;
+
+  @Override
+  public String toString() {
+    return MoreObjects.toStringHelper(this).add("name", name).add("id", id).toString();
+  }
 }
diff --git a/java/com/google/gerrit/server/query/change/OutputStreamQuery.java b/java/com/google/gerrit/server/query/change/OutputStreamQuery.java
index 1287970..17c23b6 100644
--- a/java/com/google/gerrit/server/query/change/OutputStreamQuery.java
+++ b/java/com/google/gerrit/server/query/change/OutputStreamQuery.java
@@ -24,6 +24,7 @@
 import com.google.gerrit.reviewdb.client.PatchSet;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.DynamicOptions;
 import com.google.gerrit.server.config.TrackingFooters;
 import com.google.gerrit.server.data.ChangeAttribute;
 import com.google.gerrit.server.data.PatchSetAttribute;
@@ -180,6 +181,10 @@
     this.outputFormat = fmt;
   }
 
+  public void setDynamicBean(String plugin, DynamicOptions.DynamicBean dynamicBean) {
+    queryProcessor.setDynamicBean(plugin, dynamicBean);
+  }
+
   public void query(String queryString) throws IOException {
     out =
         new PrintWriter( //
diff --git a/java/com/google/gerrit/server/restapi/change/QueryChanges.java b/java/com/google/gerrit/server/restapi/change/QueryChanges.java
index 2284b00..4a145bc 100644
--- a/java/com/google/gerrit/server/restapi/change/QueryChanges.java
+++ b/java/com/google/gerrit/server/restapi/change/QueryChanges.java
@@ -25,6 +25,7 @@
 import com.google.gerrit.index.query.QueryParseException;
 import com.google.gerrit.index.query.QueryRequiresAuthException;
 import com.google.gerrit.index.query.QueryResult;
+import com.google.gerrit.server.DynamicOptions;
 import com.google.gerrit.server.change.ChangeJson;
 import com.google.gerrit.server.permissions.PermissionBackendException;
 import com.google.gerrit.server.query.change.ChangeData;
@@ -38,7 +39,7 @@
 import java.util.List;
 import org.kohsuke.args4j.Option;
 
-public class QueryChanges implements RestReadView<TopLevelResource> {
+public class QueryChanges implements RestReadView<TopLevelResource>, DynamicOptions.BeanReceiver {
   private static final FluentLogger logger = FluentLogger.forEnclosingClass();
 
   private final ChangeJson.Factory json;
@@ -81,6 +82,10 @@
     imp.setStart(start);
   }
 
+  public void setDynamicBean(String plugin, DynamicOptions.DynamicBean dynamicBean) {
+    imp.setDynamicBean(plugin, dynamicBean);
+  }
+
   @Inject
   QueryChanges(ChangeJson.Factory json, ChangeQueryBuilder qb, ChangeQueryProcessor qp) {
     this.json = json;
diff --git a/java/com/google/gerrit/sshd/commands/Query.java b/java/com/google/gerrit/sshd/commands/Query.java
index 3fe0396..c4a21d1 100644
--- a/java/com/google/gerrit/sshd/commands/Query.java
+++ b/java/com/google/gerrit/sshd/commands/Query.java
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.sshd.commands;
 
+import com.google.gerrit.server.DynamicOptions;
 import com.google.gerrit.server.query.change.OutputStreamQuery;
 import com.google.gerrit.server.query.change.OutputStreamQuery.OutputFormat;
 import com.google.gerrit.sshd.CommandMetaData;
@@ -24,7 +25,7 @@
 import org.kohsuke.args4j.Option;
 
 @CommandMetaData(name = "query", description = "Query the change database")
-public class Query extends SshCommand {
+public class Query extends SshCommand implements DynamicOptions.BeanReceiver {
   @Inject private OutputStreamQuery processor;
 
   @Option(name = "--format", metaVar = "FMT", usage = "Output display format")
@@ -103,6 +104,10 @@
     processor.query(join(query, " "));
   }
 
+  public void setDynamicBean(String plugin, DynamicOptions.DynamicBean dynamicBean) {
+    processor.setDynamicBean(plugin, dynamicBean);
+  }
+
   @Override
   protected void parseCommandLine() throws UnloggedFailure {
     processor.setOutput(out, OutputFormat.TEXT);
diff --git a/polygerrit-ui/app/elements/core/gr-router/gr-router_test.html b/polygerrit-ui/app/elements/core/gr-router/gr-router_test.html
index 270faf0..7d4f9ba 100644
--- a/polygerrit-ui/app/elements/core/gr-router/gr-router_test.html
+++ b/polygerrit-ui/app/elements/core/gr-router/gr-router_test.html
@@ -676,6 +676,22 @@
         assert.equal(redirectStub.lastCall.args[0], '/q/foo+bar');
       });
 
+      test('_handleQueryRoute', () => {
+        const data = {params: ['project:foo/bar/baz']};
+        assertDataToParams(data, '_handleQueryRoute', {
+          view: Gerrit.Nav.View.SEARCH,
+          query: 'project:foo/bar/baz',
+          offset: undefined,
+        });
+
+        data.params.push(',123', '123');
+        assertDataToParams(data, '_handleQueryRoute', {
+          view: Gerrit.Nav.View.SEARCH,
+          query: 'project:foo/bar/baz',
+          offset: '123',
+        });
+      });
+
       suite('_handleRegisterRoute', () => {
         test('happy path', () => {
           const ctx = {params: ['/foo/bar']};