Merge branch 'stable-2.12'

* stable-2.12:
  Cookbook: Remove duplicate bind

Change-Id: If2d9659745a3188a7623fe8879dbd5aeda21c978
diff --git a/BUCK b/BUCK
index f47a479..0160c15 100644
--- a/BUCK
+++ b/BUCK
@@ -14,7 +14,7 @@
     'Gerrit-SshModule: com.googlesource.gerrit.plugins.cookbook.SshModule',
     'Implementation-Title: Cookbook plugin',
     'Implementation-URL: https://gerrit-review.googlesource.com/#/admin/projects/plugins/cookbook-plugin',
-  ]
+  ],
 )
 
 java_test(
diff --git a/lib/gerrit/BUCK b/lib/gerrit/BUCK
index 968cdb1..1d742dc 100644
--- a/lib/gerrit/BUCK
+++ b/lib/gerrit/BUCK
@@ -1,6 +1,6 @@
 include_defs('//bucklets/maven_jar.bucklet')
 
-VER = '2.12-SNAPSHOT'
+VER = '2.13-SNAPSHOT'
 REPO = MAVEN_LOCAL
 
 maven_jar(
diff --git a/src/main/java/com/googlesource/gerrit/plugins/cookbook/ConsoleMetricReporter.java b/src/main/java/com/googlesource/gerrit/plugins/cookbook/ConsoleMetricReporter.java
new file mode 100644
index 0000000..f2a3eb6
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/cookbook/ConsoleMetricReporter.java
@@ -0,0 +1,80 @@
+// Copyright (C) 2015 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.googlesource.gerrit.plugins.cookbook;
+
+import com.google.gerrit.extensions.annotations.Listen;
+import com.google.gerrit.extensions.annotations.PluginName;
+import com.google.gerrit.extensions.events.LifecycleListener;
+import com.google.gerrit.server.config.PluginConfigFactory;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+import com.codahale.metrics.ConsoleReporter;
+import com.codahale.metrics.MetricRegistry;
+
+import org.eclipse.jgit.lib.Config;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Demonstration of how to add a new Dropwizard  Metrics Reporter using
+ * Gerrit's plug-in API.
+ *
+ * @see <a href="https://dropwizard.github.io/metrics/3.1.0/getting-started/#reporting-via-jmx">here</a>
+ * @see <a href="https://dropwizard.github.io/metrics/3.1.0/getting-started/#reporting-via-http">here</a>
+ * @see <a href="https://dropwizard.github.io/metrics/3.1.0/getting-started/#other-reporting">here</a>
+ *
+ * Also shows how to fetch plug-in specific configuration values.
+ * <@see com.google.gerrit.server.config.PluginConfigFactory>
+ *
+ * Enable by adding the file etc/cookbook.config:
+ *
+ *  [console-metrics]
+ *      enabled = true
+ *
+ */
+@Listen
+@Singleton
+public class ConsoleMetricReporter implements LifecycleListener {
+  private ConsoleReporter consoleReporter;
+  private boolean enabled;
+
+  @Inject
+  public ConsoleMetricReporter(MetricRegistry registry,
+      PluginConfigFactory configFactory,
+      @PluginName String pluginName) {
+    Config config = configFactory.getGlobalPluginConfig(pluginName);
+    enabled = config.getBoolean("console-metrics", "enabled", false);
+    if (!enabled) {
+      return;
+    }
+    this.consoleReporter =
+        ConsoleReporter.forRegistry(registry).convertRatesTo(TimeUnit.SECONDS)
+            .convertDurationsTo(TimeUnit.MILLISECONDS).build();
+  }
+
+  @Override
+  public void start() {
+    if (enabled) {
+      consoleReporter.start(1, TimeUnit.MINUTES);
+    }
+  }
+
+  @Override
+  public void stop() {
+    if (enabled) {
+      consoleReporter.stop();
+    }
+  }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/cookbook/HelloTopMenu.java b/src/main/java/com/googlesource/gerrit/plugins/cookbook/HelloTopMenu.java
index 96e22d8..8734b7c 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/cookbook/HelloTopMenu.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/cookbook/HelloTopMenu.java
@@ -16,6 +16,7 @@
 
 import com.google.common.collect.Lists;
 import com.google.gerrit.extensions.annotations.PluginName;
+import com.google.gerrit.extensions.client.MenuItem;
 import com.google.gerrit.extensions.webui.TopMenu;
 import com.google.inject.Inject;
 
diff --git a/src/main/java/com/googlesource/gerrit/plugins/cookbook/MergeUserValidator.java b/src/main/java/com/googlesource/gerrit/plugins/cookbook/MergeUserValidator.java
index 0147839..ca48312 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/cookbook/MergeUserValidator.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/cookbook/MergeUserValidator.java
@@ -18,7 +18,6 @@
 import com.google.gerrit.reviewdb.client.PatchSet;
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.git.CodeReviewCommit;
-import com.google.gerrit.server.git.CommitMergeStatus;
 import com.google.gerrit.server.git.validators.MergeValidationException;
 import com.google.gerrit.server.git.validators.MergeValidationListener;
 import com.google.gerrit.server.project.ProjectState;
@@ -43,7 +42,8 @@
       PatchSet.Id patchSetId, IdentifiedUser caller)
           throws MergeValidationException {
     if (!caller.getCapabilities().canAdministrateServer()) {
-      throw new MergeValidationException(CommitMergeStatus.MISSING_DEPENDENCY);
+      throw new MergeValidationException("Submitter " + caller.getNameEmail()
+          + " is not a site administrator");
     }
   }
 }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/cookbook/Module.java b/src/main/java/com/googlesource/gerrit/plugins/cookbook/Module.java
index 7edaa03..0db7b90 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/cookbook/Module.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/cookbook/Module.java
@@ -20,6 +20,7 @@
 import com.google.common.collect.ImmutableList;
 import com.google.gerrit.extensions.annotations.Exports;
 import com.google.gerrit.extensions.client.InheritableBoolean;
+import com.google.gerrit.extensions.events.LifecycleListener;
 import com.google.gerrit.extensions.events.NewProjectCreatedListener;
 import com.google.gerrit.extensions.events.UsageDataPublishedListener;
 import com.google.gerrit.extensions.registration.DynamicSet;
@@ -34,6 +35,7 @@
 import com.google.gerrit.server.git.validators.MergeValidationListener;
 import com.google.gerrit.server.git.validators.UploadValidationListener;
 import com.google.gerrit.server.plugins.ServerPluginProvider;
+import com.google.gerrit.server.query.change.ChangeQueryBuilder.ChangeOperatorFactory;
 import com.google.gerrit.server.validators.HashtagValidationListener;
 import com.google.inject.AbstractModule;
 
@@ -52,7 +54,7 @@
     DynamicSet.bind(binder(), ServerPluginProvider.class).to(
         HelloSshPluginProvider.class);
     DynamicSet.bind(binder(), UsageDataPublishedListener.class).to(UsageDataLogger.class);
-
+    DynamicSet.bind(binder(), LifecycleListener.class).to(ConsoleMetricReporter.class);
     install(new RestApiModule() {
       @Override
       protected void configure() {
@@ -72,6 +74,10 @@
     DynamicSet.bind(binder(), NewProjectCreatedListener.class)
         .to(ProjectCreatedListener.class);
     configurePluginParameters();
+
+    bind(ChangeOperatorFactory.class)
+        .annotatedWith(Exports.named("sample"))
+        .to(SampleOperator.class);
   }
 
   private void configurePluginParameters() {
diff --git a/src/main/java/com/googlesource/gerrit/plugins/cookbook/SampleOperator.java b/src/main/java/com/googlesource/gerrit/plugins/cookbook/SampleOperator.java
new file mode 100644
index 0000000..908a934
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/cookbook/SampleOperator.java
@@ -0,0 +1,52 @@
+// Copyright (C) 2015 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.googlesource.gerrit.plugins.cookbook;
+
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.server.query.OperatorPredicate;
+import com.google.gerrit.server.query.Predicate;
+import com.google.gerrit.server.query.QueryParseException;
+import com.google.gerrit.server.query.change.ChangeData;
+import com.google.gerrit.server.query.change.ChangeQueryBuilder;
+import com.google.inject.Singleton;
+
+@Singleton
+public class SampleOperator
+    implements ChangeQueryBuilder.ChangeOperatorFactory {
+  public static class MyPredicate extends OperatorPredicate<ChangeData> {
+    private final Change.Id id;
+
+    MyPredicate(Change.Id id) {
+      super("sample", id.toString());
+      this.id = id;
+    }
+
+    @Override
+    public boolean match(ChangeData object) {
+      return id.equals(object.getId());
+    }
+
+    @Override
+    public int getCost() {
+      return 1;
+    }
+  }
+
+  @Override
+  public Predicate<ChangeData> create(ChangeQueryBuilder builder, String value)
+      throws QueryParseException {
+    return new MyPredicate(Change.Id.parse(value));
+  }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/cookbook/client/ChangeScreenPreferencePanel.java b/src/main/java/com/googlesource/gerrit/plugins/cookbook/client/ChangeScreenPreferencePanel.java
index b610f87..47d05a9 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/cookbook/client/ChangeScreenPreferencePanel.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/cookbook/client/ChangeScreenPreferencePanel.java
@@ -14,7 +14,7 @@
 
 package com.googlesource.gerrit.plugins.cookbook.client;
 
-import com.google.gerrit.client.info.AccountPreferencesInfo;
+import com.google.gerrit.client.info.GeneralPreferences;
 import com.google.gerrit.plugin.client.Plugin;
 import com.google.gerrit.plugin.client.extension.Panel;
 import com.google.gerrit.plugin.client.rpc.RestApi;
@@ -50,9 +50,9 @@
 
   ChangeScreenPreferencePanel() {
     new RestApi("accounts").id("self").view("preferences")
-        .get(new AsyncCallback<AccountPreferencesInfo>() {
+        .get(new AsyncCallback<GeneralPreferences>() {
           @Override
-          public void onSuccess(AccountPreferencesInfo result) {
+          public void onSuccess(GeneralPreferences result) {
             display(result);
           }
 
@@ -63,7 +63,7 @@
         });
   }
 
-  private void display(final AccountPreferencesInfo info) {
+  private void display(final GeneralPreferences info) {
     Label heading = new Label(Plugin.get().getName() + " plugin");
     heading.setStyleName("smallHeading");
     add(heading);
@@ -120,9 +120,9 @@
         info.setUrlAliases(urlAliases);
 
         new RestApi("accounts").id("self").view("preferences")
-            .put(info, new AsyncCallback<AccountPreferencesInfo>() {
+            .put(info, new AsyncCallback<GeneralPreferences>() {
               @Override
-              public void onSuccess(AccountPreferencesInfo result) {
+              public void onSuccess(GeneralPreferences result) {
                 Plugin.get().refreshUserPreferences();
                 showSavedStatus();
               }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/cookbook/client/CookBookChangeScreenExtension.java b/src/main/java/com/googlesource/gerrit/plugins/cookbook/client/CookBookChangeScreenExtension.java
index d5d6e8a..d2a2151 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/cookbook/client/CookBookChangeScreenExtension.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/cookbook/client/CookBookChangeScreenExtension.java
@@ -16,6 +16,7 @@
 
 import com.google.gerrit.client.GerritUiExtensionPoint;
 import com.google.gerrit.client.info.ChangeInfo;
+import com.google.gerrit.client.info.ChangeInfo.RevisionInfo;
 import com.google.gerrit.plugin.client.extension.Panel;
 import com.google.gwt.user.client.ui.Grid;
 import com.google.gwt.user.client.ui.HTMLTable.CellFormatter;
@@ -37,18 +38,25 @@
   CookBookChangeScreenExtension(Panel panel) {
     ChangeInfo change =
         panel.getObject(GerritUiExtensionPoint.Key.CHANGE_INFO).cast();
+    RevisionInfo rev =
+        panel.getObject(GerritUiExtensionPoint.Key.REVISION_INFO).cast();
 
-    Grid g = new Grid(1, 2);
+    Grid g = new Grid(2, 2);
     g.addStyleName("infoBlock");
     CellFormatter fmt = g.getCellFormatter();
 
     g.setText(0, 0, "Numeric Change ID");
     fmt.addStyleName(0, 0, "header");
+    fmt.addStyleName(0, 0, "topmost");
+    fmt.addStyleName(0, 1, "topmost");
     g.setWidget(0, 1, new CopyableLabel(Integer.toString(change._number())));
     add(g);
 
-    fmt.addStyleName(0, 0, "topmost");
-    fmt.addStyleName(0, 1, "topmost");
-    fmt.addStyleName(0, 0, "bottomheader");
+    g.setText(1, 0, "Patch Set ID");
+    fmt.addStyleName(1, 0, "header");
+    fmt.addStyleName(1, 0, "bottomheader");
+    fmt.addStyleName(1, 1, "bottomheader");
+    g.setWidget(1, 1, new CopyableLabel(String.valueOf(rev._number())));
+    add(g);
   }
 }
diff --git a/src/main/resources/Documentation/change-search-operators.md b/src/main/resources/Documentation/change-search-operators.md
new file mode 100644
index 0000000..78d7bf3
--- /dev/null
+++ b/src/main/resources/Documentation/change-search-operators.md
@@ -0,0 +1,12 @@
+Search Operators
+================
+
+Sample Operator
+---------------
+
+The @PLUGIN@ plugin provides a new `sample_@PLUGIN@` change search operator
+which can search for one specific change by change number.
+
+*sample_@PLUGIN@:number*
+
+Returns changes which match the change number `number`.
diff --git a/src/test/java/com/googlesource/gerrit/plugins/cookbook/CookbookIT.java b/src/test/java/com/googlesource/gerrit/plugins/cookbook/CookbookIT.java
index 4c5bf0e..0d985c5 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/cookbook/CookbookIT.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/cookbook/CookbookIT.java
@@ -16,12 +16,11 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import com.google.gerrit.acceptance.NoHttpd;
 import com.google.gerrit.acceptance.PluginDaemonTest;
+import com.google.gerrit.acceptance.RestResponse;
 
 import org.junit.Test;
 
-@NoHttpd
 public class CookbookIT extends PluginDaemonTest {
 
   @Test
@@ -29,4 +28,13 @@
     assertThat(sshSession.exec("cookbook print")).isEqualTo("Hello world!\n");
     assertThat(sshSession.hasError()).isFalse();
   }
+
+  @Test
+  public void revisionTest() throws Exception {
+    createChange();
+    RestResponse response =
+        adminSession.post("/changes/1/revisions/1/cookbook~hello-revision");
+    assertThat(response.getEntityContent())
+        .contains("Hello admin from change 1, patch set 1!");
+  }
 }