Merge "Refactor sending notifications from PublishDraft"
diff --git a/Documentation/cmd-stream-events.txt b/Documentation/cmd-stream-events.txt
index 512c801..10689b6 100644
--- a/Documentation/cmd-stream-events.txt
+++ b/Documentation/cmd-stream-events.txt
@@ -70,7 +70,7 @@
 
 change:: link:json.html#change[change attribute]
 
-patchset:: link:json.html#patchSet[patchset attribute]
+patchSet:: link:json.html#patchSet[patchSet attribute]
 
 uploader:: link:json.html#account[account attribute]
 
@@ -148,7 +148,7 @@
 
 change:: link:json.html#change[change attribute]
 
-patchset:: link:json.html#patchSet[patchset attribute]
+patchSet:: link:json.html#patchSet[patchSet attribute]
 
 reviewer:: link:json.html#account[account attribute]
 
diff --git a/Documentation/dev-buck.txt b/Documentation/dev-buck.txt
index 7671767..8fecd64 100644
--- a/Documentation/dev-buck.txt
+++ b/Documentation/dev-buck.txt
@@ -289,7 +289,6 @@
  )
 ----
 
-
 Caching Build Results
 ~~~~~~~~~~~~~~~~~~~~~
 
@@ -306,6 +305,28 @@
   EOF
 ----
 
+Using Buck daemon
+~~~~~~~~~~~~~~~~~
+
+Buck ships with daemon command `buckd`, which uses
+link:https://github.com/martylamb/nailgun[Nailgun] protocol for running
+Java programs from the command line without incurring the JVM startup
+overhead. Using a Buck daemon can save significant amounts of time as it
+avoids the overhead of starting a Java virtual machine, loading the buck class
+files and parsing the build files for each command. It is safe to run several
+buck daemons started from different project directories and they will not
+interfere with each other. Buck's documentation covers daemon in
+http://facebook.github.io/buck/command/buckd.html[buckd]. The trivial case is
+to run `buckd` from the project's root directory and use `buck` as usually:
+
+----
+$>buckd
+$>buck build gerrit
+Using buckd.
+[-] PARSING BUILD FILES...FINISHED 0.6s
+[-] BUILDING...FINISHED 0.2s
+----
+
 GERRIT
 ------
 Part of link:index.html[Gerrit Code Review]
diff --git a/Documentation/dev-plugins.txt b/Documentation/dev-plugins.txt
index 5439869..0ba51c4 100644
--- a/Documentation/dev-plugins.txt
+++ b/Documentation/dev-plugins.txt
@@ -215,28 +215,29 @@
 Plugins InitStep cannot refer to Gerrit DB Schema or any other Gerrit runtime
 objects injected at startup.
 
-====
-  public class MyInitStep implements InitStep {
-    private final ConsoleUI ui;
-    private final Section.Factory sections;
-    private final String pluginName;
+[source,java]
+----
+public class MyInitStep implements InitStep {
+  private final ConsoleUI ui;
+  private final Section.Factory sections;
+  private final String pluginName;
 
-    @Inject
-    public GitBlitInitStep(final ConsoleUI ui, Section.Factory sections, @PluginName String pluginName) {
-      this.ui = ui;
-      this.sections = sections;
-      this.pluginName = pluginName;
-    }
-
-    @Override
-    public void run() throws Exception {
-      ui.header("\nMy plugin");
-
-      Section mySection = getSection("myplugin", null);
-      mySection.string("Link name", "linkname", "MyLink");
-    }
+  @Inject
+  public GitBlitInitStep(final ConsoleUI ui, Section.Factory sections, @PluginName String pluginName) {
+    this.ui = ui;
+    this.sections = sections;
+    this.pluginName = pluginName;
   }
-====
+
+  @Override
+  public void run() throws Exception {
+    ui.header("\nMy plugin");
+
+    Section mySection = getSection("myplugin", null);
+    mySection.string("Link name", "linkname", "MyLink");
+  }
+}
+----
 
 [[classpath]]
 Classpath
@@ -263,44 +264,47 @@
 
 Command implementations must extend the base class SshCommand:
 
-====
-  import com.google.gerrit.sshd.SshCommand;
+[source,java]
+----
+import com.google.gerrit.sshd.SshCommand;
 
-  class PrintHello extends SshCommand {
-    protected abstract void run() {
-      stdout.print("Hello\n");
-    }
+class PrintHello extends SshCommand {
+  protected abstract void run() {
+    stdout.print("Hello\n");
   }
-====
+}
+----
 
 If no Guice modules are declared in the manifest, SSH commands may
 use auto-registration by providing an `@Export` annotation:
 
-====
-  import com.google.gerrit.extensions.annotations.Export;
-  import com.google.gerrit.sshd.SshCommand;
+[source,java]
+----
+import com.google.gerrit.extensions.annotations.Export;
+import com.google.gerrit.sshd.SshCommand;
 
-  @Export("print")
-  class PrintHello extends SshCommand {
-    protected abstract void run() {
-      stdout.print("Hello\n");
-    }
+@Export("print")
+class PrintHello extends SshCommand {
+  protected abstract void run() {
+    stdout.print("Hello\n");
   }
-====
+}
+----
 
 If explicit registration is being used, a Guice module must be
 supplied to register the SSH command and declared in the manifest
 with the `Gerrit-SshModule` attribute:
 
-====
-  import com.google.gerrit.sshd.PluginCommandModule;
+[source,java]
+----
+import com.google.gerrit.sshd.PluginCommandModule;
 
-  class MyCommands extends PluginCommandModule {
-    protected void configureCommands() {
-      command("print").to(PrintHello.class);
-    }
+class MyCommands extends PluginCommandModule {
+  protected void configureCommands() {
+    command("print").to(PrintHello.class);
   }
-====
+}
+----
 
 For a plugin installed as name `helloworld`, the command implemented
 by PrintHello class will be available to users as:
@@ -319,56 +323,64 @@
 Plugins define the capabilities by overriding the `CapabilityDefinition`
 abstract class:
 
-====
-  public class PrintHelloCapability extends CapabilityDefinition {
-    @Override
-    public String getDescription() {
-      return "Print Hello";
-    }
+[source,java]
+----
+public class PrintHelloCapability extends CapabilityDefinition {
+  @Override
+  public String getDescription() {
+    return "Print Hello";
   }
-====
+}
+----
 
 If no Guice modules are declared in the manifest, UI actions may
 use auto-registration by providing an `@Export` annotation:
 
-====
-  @Export("printHello")
-  public class PrintHelloCapability extends CapabilityDefinition {
+[source,java]
+----
+@Export("printHello")
+public class PrintHelloCapability extends CapabilityDefinition {
   ...
-====
+}
+----
 
 Otherwise the capability must be bound in a plugin module:
 
-====
-  public class HelloWorldModule extends AbstractModule {
-    @Override
-    protected void configure() {
-      bind(CapabilityDefinition.class)
-        .annotatedWith(Exports.named("printHello"))
-        .to(PrintHelloCapability.class);
-    }
+[source,java]
+----
+public class HelloWorldModule extends AbstractModule {
+  @Override
+  protected void configure() {
+    bind(CapabilityDefinition.class)
+      .annotatedWith(Exports.named("printHello"))
+      .to(PrintHelloCapability.class);
   }
-====
+}
+----
 
 With a plugin-owned capability defined in this way, it is possible to restrict
 usage of an SSH command or `UiAction` to members of the group that were granted
 this capability in the usual way, using the `RequiresCapability` annotation:
 
-====
-  @RequiresCapability("printHello")
-  @CommandMetaData(name="print", description="Print greeting in different languages")
-  public final class PrintHelloWorldCommand extends SshCommand {
+[source,java]
+----
+@RequiresCapability("printHello")
+@CommandMetaData(name="print", description="Print greeting in different languages")
+public final class PrintHelloWorldCommand extends SshCommand {
   ...
-====
+}
+----
 
 Or with `UiAction`:
 
-====
-  @RequiresCapability("printHello")
-  public class SayHelloAction extends UiAction<RevisionResource>
-    implements RestModifyView<RevisionResource, SayHelloAction.Input> {
+[source,java]
+----
+@RequiresCapability("printHello")
+public class SayHelloAction extends UiAction<RevisionResource>
+  implements RestModifyView<RevisionResource, SayHelloAction.Input> {
   ...
-====
+}
+----
 
 Capability scope was introduced to differentiate between plugin-owned
 capabilities and core capabilities. Per default the scope of the
@@ -384,11 +396,12 @@
 If a plugin needs to use a core capability name (e.g. "administrateServer")
 this can be specified by setting `scope = CapabilityScope.CORE`:
 
-====
-  @RequiresCapability(value = "administrateServer", scope =
-      CapabilityScope.CORE)
+[source,java]
+----
+@RequiresCapability(value = "administrateServer", scope =
+    CapabilityScope.CORE)
   ...
-====
+----
 
 [[ui_extension]]
 UI Extension
@@ -410,138 +423,146 @@
 
 Plugins contribute UI actions by implementing the `UiAction` interface:
 
-====
-  @RequiresCapability("printHello")
-  class HelloWorldAction implements UiAction<RevisionResource>,
-      RestModifyView<RevisionResource, HelloWorldAction.Input> {
-    static class Input {
-      boolean french;
-      String message;
-    }
-
-    private Provider<CurrentUser> user;
-
-    @Inject
-    HelloWorldAction(Provider<CurrentUser> user) {
-      this.user = user;
-    }
-
-    @Override
-    public String apply(RevisionResource rev, Input input) {
-      final String greeting = input.french
-          ? "Bonjour"
-          : "Hello";
-      return String.format("%s %s from change %s, patch set %d!",
-          greeting,
-          Strings.isNullOrEmpty(input.message)
-              ? Objects.firstNonNull(user.get().getUserName(), "world")
-              : input.message,
-          rev.getChange().getId().toString(),
-          rev.getPatchSet().getPatchSetId());
-    }
-
-    @Override
-    public Description getDescription(
-        RevisionResource resource) {
-      return new Description()
-          .setLabel("Say hello")
-          .setTitle("Say hello in different languages");
-    }
+[source,java]
+----
+@RequiresCapability("printHello")
+class HelloWorldAction implements UiAction<RevisionResource>,
+    RestModifyView<RevisionResource, HelloWorldAction.Input> {
+  static class Input {
+    boolean french;
+    String message;
   }
-====
+
+  private Provider<CurrentUser> user;
+
+  @Inject
+  HelloWorldAction(Provider<CurrentUser> user) {
+    this.user = user;
+  }
+
+  @Override
+  public String apply(RevisionResource rev, Input input) {
+    final String greeting = input.french
+        ? "Bonjour"
+        : "Hello";
+    return String.format("%s %s from change %s, patch set %d!",
+        greeting,
+        Strings.isNullOrEmpty(input.message)
+            ? Objects.firstNonNull(user.get().getUserName(), "world")
+            : input.message,
+        rev.getChange().getId().toString(),
+        rev.getPatchSet().getPatchSetId());
+  }
+
+  @Override
+  public Description getDescription(
+      RevisionResource resource) {
+    return new Description()
+        .setLabel("Say hello")
+        .setTitle("Say hello in different languages");
+  }
+}
+----
 
 `UiAction` must be bound in a plugin module:
 
-====
-  public class Module extends AbstractModule {
-    @Override
-    protected void configure() {
-      install(new RestApiModule() {
-        @Override
-        protected void configure() {
-          post(REVISION_KIND, "say-hello")
-              .to(HelloWorldAction.class);
-        }
-      });
-    }
+[source,java]
+----
+public class Module extends AbstractModule {
+  @Override
+  protected void configure() {
+    install(new RestApiModule() {
+      @Override
+      protected void configure() {
+        post(REVISION_KIND, "say-hello")
+            .to(HelloWorldAction.class);
+      }
+    });
   }
-====
+}
+----
 
 The module above must be declared in pom.xml for Maven driven plugins:
 
-====
-  <manifestEntries>
-    <Gerrit-Module>com.googlesource.gerrit.plugins.cookbook.Module</Gerrit-Module>
-  </manifestEntries>
-====
+[source,xml]
+----
+<manifestEntries>
+  <Gerrit-Module>com.googlesource.gerrit.plugins.cookbook.Module</Gerrit-Module>
+</manifestEntries>
+----
 
 or in the BUCK configuration file for Buck driven plugins:
 
-====
-  manifest_entries = [
-    'Gerrit-Module: com.googlesource.gerrit.plugins.cookbook.Module',
-  ]
-====
+[source,python]
+----
+manifest_entries = [
+  'Gerrit-Module: com.googlesource.gerrit.plugins.cookbook.Module',
+]
+----
 
 In some use cases more user input must be gathered, for that `UiAction` can be
 combined with the JavaScript API. This would display a small popup near the
 activation button to gather additional input from the user. The JS file is
 typically put in the `static` folder within the plugin's directory:
 
-====
-  Gerrit.install(function(self) {
-    function onSayHello(c) {
-      var f = c.textfield();
-      var t = c.checkbox();
-      var b = c.button('Say hello', {onclick: function(){
-        c.call(
-          {message: f.value, french: t.checked},
-          function(r) {
-            c.hide();
-            window.alert(r);
-            c.refresh();
-          });
-      }});
-      c.popup(c.div(
-        c.prependLabel('Greeting message', f),
-        c.br(),
-        c.label(t, 'french'),
-        c.br(),
-        b));
-      f.focus();
-    }
-    self.onAction('revision', 'say-hello', onSayHello);
-  });
-====
+[source,javascript]
+----
+Gerrit.install(function(self) {
+  function onSayHello(c) {
+    var f = c.textfield();
+    var t = c.checkbox();
+    var b = c.button('Say hello', {onclick: function(){
+      c.call(
+        {message: f.value, french: t.checked},
+        function(r) {
+          c.hide();
+          window.alert(r);
+          c.refresh();
+        });
+    }});
+    c.popup(c.div(
+      c.prependLabel('Greeting message', f),
+      c.br(),
+      c.label(t, 'french'),
+      c.br(),
+      b));
+    f.focus();
+  }
+  self.onAction('revision', 'say-hello', onSayHello);
+});
+----
 
 The JS module must be exposed as a `WebUiPlugin` and bound as
 an HTTP Module:
 
-====
-  public class HttpModule extends HttpPluginModule {
-    @Override
-    protected void configureServlets() {
-      DynamicSet.bind(binder(), WebUiPlugin.class)
-          .toInstance(new JavaScriptPlugin("hello.js"));
-    }
+[source,java]
+----
+public class HttpModule extends HttpPluginModule {
+  @Override
+  protected void configureServlets() {
+    DynamicSet.bind(binder(), WebUiPlugin.class)
+        .toInstance(new JavaScriptPlugin("hello.js"));
   }
-====
+}
+----
 
 The HTTP module above must be declared in pom.xml for Maven driven plugins:
 
-====
-  <manifestEntries>
-    <Gerrit-HttpModule>com.googlesource.gerrit.plugins.cookbook.HttpModule</Gerrit-HttpModule>
-  </manifestEntries>
-====
+[source,xml]
+----
+<manifestEntries>
+  <Gerrit-HttpModule>com.googlesource.gerrit.plugins.cookbook.HttpModule</Gerrit-HttpModule>
+</manifestEntries>
+----
 
 or in the BUCK configuration file for Buck driven plugins
 
-====
-  manifest_entries = [
-    'Gerrit-HttpModule: com.googlesource.gerrit.plugins.cookbook.HttpModule',
-  ]
-====
+[source,python]
+----
+manifest_entries = [
+  'Gerrit-HttpModule: com.googlesource.gerrit.plugins.cookbook.HttpModule',
+]
+----
 
 If `UiAction` is annotated with the `@RequiresCapability` annotation, then the
 capability check is done during the `UiAction` gathering, so the plugin author
@@ -579,38 +600,40 @@
 
 Servlets may use auto-registration to declare the URL they handle:
 
-====
-  import com.google.gerrit.extensions.annotations.Export;
-  import com.google.inject.Singleton;
-  import javax.servlet.http.HttpServlet;
-  import javax.servlet.http.HttpServletRequest;
-  import javax.servlet.http.HttpServletResponse;
+[source,java]
+----
+import com.google.gerrit.extensions.annotations.Export;
+import com.google.inject.Singleton;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
 
-  @Export("/print")
-  @Singleton
-  class HelloServlet extends HttpServlet {
-    protected void doGet(HttpServletRequest req, HttpServletResponse res) throws IOException {
-      res.setContentType("text/plain");
-      res.setCharacterEncoding("UTF-8");
-      res.getWriter().write("Hello");
-    }
+@Export("/print")
+@Singleton
+class HelloServlet extends HttpServlet {
+  protected void doGet(HttpServletRequest req, HttpServletResponse res) throws IOException {
+    res.setContentType("text/plain");
+    res.setCharacterEncoding("UTF-8");
+    res.getWriter().write("Hello");
   }
-====
+}
+----
 
 The auto registration only works for standard servlet mappings like
 `/foo` or `/foo/*`. Regex style bindings must use a Guice ServletModule
 to register the HTTP servlets and declare it explicitly in the manifest
 with the `Gerrit-HttpModule` attribute:
 
-====
-  import com.google.inject.servlet.ServletModule;
+[source,java]
+----
+import com.google.inject.servlet.ServletModule;
 
-  class MyWebUrls extends ServletModule {
-    protected void configureServlets() {
-      serve("/print").with(HelloServlet.class);
-    }
+class MyWebUrls extends ServletModule {
+  protected void configureServlets() {
+    serve("/print").with(HelloServlet.class);
   }
-====
+}
+----
 
 For a plugin installed as name `helloworld`, the servlet implemented
 by HelloServlet class will be available to users as:
@@ -629,12 +652,13 @@
 
 Plugins can use this to store any data they want.
 
-====
-  @Inject
-  MyType(@PluginData java.io.File myDir) {
-    new FileInputStream(new File(myDir, "my.config"));
-  }
-====
+[source,java]
+----
+@Inject
+MyType(@PluginData java.io.File myDir) {
+  new FileInputStream(new File(myDir, "my.config"));
+}
+----
 
 [[documentation]]
 Documentation
diff --git a/Documentation/index.txt b/Documentation/index.txt
index a30d16f..28e0184 100644
--- a/Documentation/index.txt
+++ b/Documentation/index.txt
@@ -65,6 +65,7 @@
 .. link:pgm-index.html[Server Side Administrative Tools]
 . Developer
 .. link:dev-readme.html[Developer Setup]
+.. link:dev-buck.html[Building with Buck]
 .. link:dev-eclipse.html[Eclipse Setup]
 .. link:dev-contributing.html[Contributing to Gerrit]
 .. Documentation formatting guide for contributions
diff --git a/Documentation/js-api.txt b/Documentation/js-api.txt
index 394a7b1..9e0c308 100644
--- a/Documentation/js-api.txt
+++ b/Documentation/js-api.txt
@@ -12,6 +12,7 @@
 within an anonymous function passed to `Gerrit.install()`. The plugin
 will be passed an object describing its registration with Gerrit:
 
+[source,javascript]
 ----
 Gerrit.install(function (self) {
   // ... plugin JavaScript code here ...
@@ -32,6 +33,7 @@
 Issues a DELETE REST API request to the Gerrit server.
 
 .Signature
+[source,javascript]
 ----
 Gerrit.delete(url, callback)
 ----
@@ -49,6 +51,7 @@
 Issues a GET REST API request to the Gerrit server.
 
 .Signature
+[source,javascript]
 ----
 self.get(url, callback)
 ----
@@ -74,6 +77,7 @@
 Issues a POST REST API request to the Gerrit server.
 
 .Signature
+[source,javascript]
 ----
 self.post(url, input, callback)
 ----
@@ -88,6 +92,7 @@
   a string, otherwise the result is a JavaScript object or array,
   as described in the relevant REST API documentation.
 
+[source,javascript]
 ----
 self.post(
   '/my-servlet',
@@ -101,6 +106,7 @@
 Issues a PUT REST API request to the Gerrit server.
 
 .Signature
+[source,javascript]
 ----
 self.put(url, input, callback)
 ----
@@ -115,6 +121,7 @@
   a string, otherwise the result is a JavaScript object or array,
   as described in the relevant REST API documentation.
 
+[source,javascript]
 ----
 self.put(
   '/builds',
@@ -129,6 +136,7 @@
 on a button associated with a server side `UiAction`.
 
 .Signature
+[source,javascript]
 ----
 Gerrit.onAction(type, view_name, callback);
 ----
@@ -150,6 +158,7 @@
 parameter the URL of the plugin is returned. If passed a string
 the argument is appended to the plugin URL.
 
+[source,javascript]
 ----
 self.url();                    // "https://gerrit-review.googlesource.com/plugins/demo/"
 self.url('/static/icon.png');  // "https://gerrit-review.googlesource.com/plugins/demo/static/icon.png"
@@ -167,7 +176,7 @@
 [[context_action]]
 context.action
 ~~~~~~~~~~~~~~
-A link:rest-api-changes.html#action-info[ActionInfo] object instance
+An link:rest-api-changes.html#action-info[ActionInfo] object instance
 supplied by the server describing the UI button the user used to
 invoke the action.
 
@@ -179,6 +188,7 @@
 needing to care.
 
 .Signature
+[source,javascript]
 ----
 context.call(input, callback)
 ----
@@ -191,6 +201,7 @@
   a string, otherwise the result is a JavaScript object or array,
   as described in the relevant REST API documentation.
 
+[source,javascript]
 ----
 context.call(
   {message: "..."},
@@ -213,6 +224,7 @@
 Issues a DELETE REST API call to the URL associated with the action.
 
 .Signature
+[source,javascript]
 ----
 context.delete(callback)
 ----
@@ -221,6 +233,7 @@
   JSON result of the API call. DELETE methods often return
   `204 No Content`, which is passed as null.
 
+[source,javascript]
 ----
 context.delete(function () {});
 ----
@@ -231,6 +244,7 @@
 Issues a GET REST API call to the URL associated with the action.
 
 .Signature
+[source,javascript]
 ----
 context.get(callback)
 ----
@@ -240,6 +254,7 @@
   a string, otherwise the result is a JavaScript object or array,
   as described in the relevant REST API documentation.
 
+[source,javascript]
 ----
 context.get(function (result) {
   // ... use result here ...
@@ -263,6 +278,7 @@
 Issues a POST REST API call to the URL associated with the action.
 
 .Signature
+[source,javascript]
 ----
 context.post(input, callback)
 ----
@@ -274,6 +290,7 @@
   a string, otherwise the result is a JavaScript object or array,
   as described in the relevant REST API documentation.
 
+[source,javascript]
 ----
 context.post(
   {message: "..."},
@@ -295,6 +312,7 @@
 the popup.
 
 .Signature
+[source,javascript]
 ----
 context.popup(element)
 ----
@@ -304,6 +322,8 @@
   element. CSS can be used to style the element beyond the defaults.
 
 A common usage is to gather more input:
+
+[source,javascript]
 ----
 self.onAction('revision', 'start-build', function (c) {
   var l = c.checkbox();
@@ -329,6 +349,7 @@
 Issues a PUT REST API call to the URL associated with the action.
 
 .Signature
+[source,javascript]
 ----
 context.put(input, callback)
 ----
@@ -340,6 +361,7 @@
   a string, otherwise the result is a JavaScript object or array,
   as described in the relevant REST API documentation.
 
+[source,javascript]
 ----
 context.put(
   {message: "..."},
@@ -417,6 +439,7 @@
 private REST API URLs see link:#self_delete[self.delete()].
 
 .Signature
+[source,javascript]
 ----
 Gerrit.delete(url, callback)
 ----
@@ -428,6 +451,7 @@
   JSON result of the API call. DELETE methods often return
   `204 No Content`, which is passed as null.
 
+[source,javascript]
 ----
 Gerrit.delete(
   '/changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/topic',
@@ -441,6 +465,7 @@
 private REST API URLs see link:#self_get[self.get()].
 
 .Signature
+[source,javascript]
 ----
 Gerrit.get(url, callback)
 ----
@@ -453,6 +478,7 @@
   a string, otherwise the result is a JavaScript object or array,
   as described in the relevant REST API documentation.
 
+[source,javascript]
 ----
 Gerrit.get('/changes/?q=status:open', function (open) {
   for (var i = 0; i < open.length; i++) {
@@ -478,6 +504,7 @@
 Updates the web UI to display the view identified by the supplied
 URL token. The URL token is the text after `#` in the browser URL.
 
+[source,javascript]
 ----
 Gerrit.go('/admin/projects/');
 ----
@@ -492,6 +519,7 @@
 Registers a new plugin by invoking the supplied initialization
 function. The function is passed the link:#self[plugin instance].
 
+[source,javascript]
 ----
 Gerrit.install(function (self) {
   // ... plugin JavaScript code here ...
@@ -505,6 +533,7 @@
 private REST API URLs see link:#self_post[self.post()].
 
 .Signature
+[source,javascript]
 ----
 Gerrit.post(url, input, callback)
 ----
@@ -519,6 +548,7 @@
   a string, otherwise the result is a JavaScript object or array,
   as described in the relevant REST API documentation.
 
+[source,javascript]
 ----
 Gerrit.post(
   '/changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/topic',
@@ -533,6 +563,7 @@
 private REST API URLs see link:#self_put[self.put()].
 
 .Signature
+[source,javascript]
 ----
 Gerrit.put(url, input, callback)
 ----
@@ -547,6 +578,7 @@
   a string, otherwise the result is a JavaScript object or array,
   as described in the relevant REST API documentation.
 
+[source,javascript]
 ----
 Gerrit.put(
   '/changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/topic',
@@ -561,6 +593,7 @@
 on a button associated with a server side `UiAction`.
 
 .Signature
+[source,javascript]
 ----
 Gerrit.onAction(type, view_name, callback);
 ----
@@ -587,6 +620,7 @@
 no parameter the URL of the site is returned. If passed a string
 the argument is appended to the site URL.
 
+[source,javascript]
 ----
 Gerrit.url();        // "https://gerrit-review.googlesource.com/"
 Gerrit.url('/123');  // "https://gerrit-review.googlesource.com/123"
diff --git a/Documentation/rest-api-changes.txt b/Documentation/rest-api-changes.txt
index 6835670..97ce2a1 100644
--- a/Documentation/rest-api-changes.txt
+++ b/Documentation/rest-api-changes.txt
@@ -48,7 +48,6 @@
       "status": "NEW",
       "created": "2012-07-17 07:18:30.854000000",
       "updated": "2012-07-17 07:19:27.766000000",
-      "reviewed": true,
       "mergeable": true,
       "_sortkey": "001e7057000006dc",
       "_number": 1756,
@@ -120,7 +119,6 @@
         "status": "NEW",
         "created": "2012-07-17 07:18:30.854000000",
         "updated": "2012-07-17 07:19:27.766000000",
-        "reviewed": true,
         "mergeable": true,
         "_sortkey": "001e7057000006dc",
         "_number": 1756,
@@ -220,6 +218,12 @@
   authenticated to obtain the available actions.
 --
 
+[[reviewed]]
+--
+* `REVIEWED`: include the `reviewed` field if the caller is
+  authenticated and has commented on the current revision.
+--
+
 .Request
 ----
   GET /changes/?q=97&o=CURRENT_REVISION&o=CURRENT_COMMIT&o=CURRENT_FILES HTTP/1.0
@@ -351,7 +355,6 @@
     "status": "NEW",
     "created": "2013-02-01 09:59:32.126000000",
     "updated": "2013-02-21 11:16:36.775000000",
-    "reviewed": true,
     "mergeable": true,
     "_sortkey": "0023412400000f7d",
     "_number": 3965,
@@ -401,7 +404,6 @@
     "status": "NEW",
     "created": "2013-02-01 09:59:32.126000000",
     "updated": "2013-02-21 11:16:36.775000000",
-    "reviewed": true,
     "mergeable": true,
     "_sortkey": "0023412400000f7d",
     "_number": 3965,
@@ -640,7 +642,6 @@
     "status": "ABANDONED",
     "created": "2013-02-01 09:59:32.126000000",
     "updated": "2013-02-21 11:16:36.775000000",
-    "reviewed": true,
     "mergeable": true,
     "_sortkey": "0023412400000f7d",
     "_number": 3965,
@@ -699,7 +700,6 @@
     "status": "NEW",
     "created": "2013-02-01 09:59:32.126000000",
     "updated": "2013-02-21 11:16:36.775000000",
-    "reviewed": true,
     "mergeable": true,
     "_sortkey": "0023412400000f7d",
     "_number": 3965,
@@ -847,7 +847,6 @@
     "status": "NEW",
     "created": "2013-02-01 09:59:32.126000000",
     "updated": "2013-02-21 11:16:36.775000000",
-    "reviewed": true,
     "mergeable": true,
     "_sortkey": "0023412400000f7d",
     "_number": 3965,
@@ -912,7 +911,6 @@
     "status": "MERGED",
     "created": "2013-02-01 09:59:32.126000000",
     "updated": "2013-02-21 11:16:36.775000000",
-    "reviewed": true,
     "_sortkey": "0023412400000f7d",
     "_number": 3965,
     "owner": {
@@ -1220,7 +1218,6 @@
     "status": "NEW",
     "created": "2013-02-01 09:59:32.126000000",
     "updated": "2013-02-21 11:16:36.775000000",
-    "reviewed": true,
     "mergeable": true,
     "_sortkey": "0023412400000f7d",
     "_number": 3965,
@@ -2271,7 +2268,6 @@
     "status": "NEW",
     "created": "2013-02-01 09:59:32.126000000",
     "updated": "2013-02-21 11:16:36.775000000",
-    "reviewed": true,
     "mergeable": true,
     "_sortkey": "0023412400000f7d",
     "_number": 3965,
@@ -2322,7 +2318,6 @@
     "status": "NEW",
     "created": "2013-02-01 09:59:32.126000000",
     "updated": "2013-02-21 11:16:36.775000000",
-    "reviewed": true,
     "mergeable": true,
     "_sortkey": "0023412400000f7d",
     "_number": 3965,
@@ -2503,6 +2498,7 @@
 Whether the calling user has starred this change.
 |`reviewed`           |not set if `false`|
 Whether the change was reviewed by the calling user.
+Only set if link:#reviewed[reviewed] is requested.
 |`mergeable`          |optional|
 Whether the change is mergeable. +
 Not set for merged changes.
@@ -2529,7 +2525,7 @@
 link:rest-api-accounts.html#account-info[AccountInfo] entities. +
 Only set if link:#detailed-labels[detailed labels] are requested.
 |`messages`|optional|
-Messages associated with the change as a list of 
+Messages associated with the change as a list of
 link:#change-message-info[ChangeMessageInfo] entities. +
 Only set if link:#messages[messages] are requested.
 |`current_revision`   |optional|
diff --git a/Documentation/user-search.txt b/Documentation/user-search.txt
index d7e9fd7..f1c13f8 100644
--- a/Documentation/user-search.txt
+++ b/Documentation/user-search.txt
@@ -168,6 +168,11 @@
 +
 Changes that match 'MESSAGE' arbitrary string in the commit message body.
 
+[[comment]]
+comment:'TEXT'::
++
+Changes that match 'TEXT' string in any comment left by a reviewer.
+
 [[file]]
 file:^'REGEX'::
 +
@@ -187,12 +192,6 @@
 ones using a bracket expression). For example, to match all XML
 files named like 'name1.xml', 'name2.xml', and 'name3.xml' use
 `file:"^name[1-3].xml"`.
-+
-Currently this operator is only available on a watched project
-and may not be used in the search bar. The same holds true for web UI
-"My > Watched Changes", i. e. file:regex is used over the is:watched
-expression. It never produces any results, because the error message:
-"operator not permitted here: file:regex" is suppressed.
 
 [[has]]
 has:draft::
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/AccountCreator.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/AccountCreator.java
index f56ef07..cb785b9 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/AccountCreator.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/AccountCreator.java
@@ -114,6 +114,12 @@
     return create(username, null, username, (String[]) null);
   }
 
+  public TestAccount admin()
+      throws UnsupportedEncodingException, OrmException, JSchException {
+    return create("admin", "admin@example.com", "Administrator",
+      "Administrators");
+  }
+
   private AccountExternalId.Key getEmailKey(String email) {
     return new AccountExternalId.Key(AccountExternalId.SCHEME_MAILTO, email);
   }
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/GerritServer.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/GerritServer.java
index a9ce827..de70796 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/GerritServer.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/GerritServer.java
@@ -84,7 +84,8 @@
     File tmp = TempFileUtil.createTempDirectory();
     Init init = new Init();
     int rc = init.main(new String[] {
-        "-d", tmp.getPath(), "--batch", "--no-auto-start"});
+        "-d", tmp.getPath(), "--batch", "--no-auto-start",
+        "--skip-plugins"});
     if (rc != 0) {
       throw new RuntimeException("Couldn't initialize site");
     }
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/changes/ListChangesOption.java b/gerrit-common/src/main/java/com/google/gerrit/common/changes/ListChangesOption.java
index 6db0847..94d8389 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/changes/ListChangesOption.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/changes/ListChangesOption.java
@@ -40,7 +40,10 @@
   MESSAGES(9),
 
   /** Include allowed actions client could perform. */
-  CURRENT_ACTIONS(10);
+  CURRENT_ACTIONS(10),
+
+  /** Set the reviewed boolean for the caller. */
+  REVIEWED(11);
 
   private final int value;
 
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/events/ProjectDeletedListener.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/events/ProjectDeletedListener.java
new file mode 100644
index 0000000..b03f99c
--- /dev/null
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/events/ProjectDeletedListener.java
@@ -0,0 +1,27 @@
+// Copyright (C) 2013 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.extensions.events;
+
+import com.google.gerrit.extensions.annotations.ExtensionPoint;
+
+/** Notified whenever a project is deleted on the master. */
+@ExtensionPoint
+public interface ProjectDeletedListener {
+  public interface Event {
+    String getProjectName();
+  }
+
+  void onProjectDeleted(Event event);
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/DownloadBox.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/DownloadBox.java
index 25208d3..ae29801 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/DownloadBox.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/DownloadBox.java
@@ -181,7 +181,6 @@
           case ANON_HTTP:
             return "http";
           case SSH:
-          case ANON_SSH:
             return "ssh";
           default:
             return null;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/AccountDashboardScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/AccountDashboardScreen.java
index 6273717..5f89bf0 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/AccountDashboardScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/AccountDashboardScreen.java
@@ -19,6 +19,7 @@
 import com.google.gerrit.client.rpc.Natives;
 import com.google.gerrit.client.rpc.ScreenLoadCallback;
 import com.google.gerrit.client.ui.Screen;
+import com.google.gerrit.common.changes.ListChangesOption;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gwt.core.client.JsArray;
 import com.google.gwt.event.dom.client.KeyPressEvent;
@@ -26,6 +27,7 @@
 
 import java.util.Collections;
 import java.util.Comparator;
+import java.util.EnumSet;
 
 public class AccountDashboardScreen extends Screen implements ChangeListScreen {
   private final Account.Id ownerId;
@@ -61,7 +63,7 @@
 
     outgoing.setTitleText(Util.C.outgoingReviews());
     incoming.setTitleText(Util.C.incomingReviews());
-    incoming.setHighlightUnreviewed(true);
+    incoming.setHighlightUnreviewed(mine);
     closed.setTitleText(Util.C.recentlyClosed());
 
     table.addSection(outgoing);
@@ -83,6 +85,9 @@
             display(result);
           }
         },
+        mine
+          ? EnumSet.of(ListChangesOption.REVIEWED)
+          : EnumSet.noneOf(ListChangesOption.class),
         "is:open owner:" + who,
         "is:open reviewer:" + who + " -owner:" + who,
         "is:closed owner:" + who + " -age:4w limit:10");
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeList.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeList.java
index fea117f..773313a 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeList.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeList.java
@@ -28,13 +28,18 @@
 
   /** Run 2 or more queries in a single remote invocation. */
   public static void query(
-      AsyncCallback<JsArray<ChangeList>> callback, String... queries) {
+      AsyncCallback<JsArray<ChangeList>> callback,
+      EnumSet<ListChangesOption> options,
+      String... queries) {
     assert queries.length >= 2; // At least 2 is required for correct result.
     RestApi call = new RestApi(URI);
     for (String q : queries) {
       call.addParameterRaw("q", KeyUtil.encode(q));
     }
-    addOptions(call, EnumSet.of(ListChangesOption.LABELS));
+
+    EnumSet<ListChangesOption> o = EnumSet.of(ListChangesOption.LABELS);
+    o.addAll(options);
+    addOptions(call, o);
     call.get(callback);
   }
 
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/DashboardTable.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/DashboardTable.java
index 7b387ab..3002e48 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/DashboardTable.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/DashboardTable.java
@@ -19,10 +19,12 @@
 import com.google.gerrit.client.rpc.Natives;
 import com.google.gerrit.client.ui.InlineHyperlink;
 import com.google.gerrit.common.PageLinks;
+import com.google.gerrit.common.changes.ListChangesOption;
 import com.google.gwt.core.client.JsArray;
 import com.google.gwt.http.client.URL;
 
 import java.util.ArrayList;
+import java.util.EnumSet;
 import java.util.List;
 import java.util.ListIterator;
 
@@ -103,6 +105,7 @@
               finishDisplay();
             }
           },
+          EnumSet.noneOf(ListChangesOption.class),
           queries.toArray(new String[queries.size()]));
     }
   }
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GitOverHttpServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GitOverHttpServlet.java
index 06cc2f0..4ecd020 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GitOverHttpServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GitOverHttpServlet.java
@@ -309,9 +309,6 @@
       }
 
       if (!rp.isCheckReferencedObjectsAreReachable()) {
-        if (isGet) {
-          rc.advertiseHistory();
-        }
         chain.doFilter(request, response);
         return;
       }
@@ -326,7 +323,6 @@
           projectName);
 
       if (isGet) {
-        rc.advertiseHistory();
         cache.invalidate(cacheKey);
       } else {
         Set<ObjectId> ids = cache.getIfPresent(cacheKey);
diff --git a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneChangeIndex.java b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneChangeIndex.java
index 284f6c1..c98bd5d 100644
--- a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneChangeIndex.java
+++ b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneChangeIndex.java
@@ -25,9 +25,12 @@
 import com.google.common.util.concurrent.ListenableFuture;
 import com.google.common.util.concurrent.ListeningScheduledExecutorService;
 import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.reviewdb.client.PatchSetApproval;
 import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gerrit.server.config.SitePaths;
 import com.google.gerrit.server.index.ChangeField;
+import com.google.gerrit.server.index.ChangeField.ChangeProtoField;
+import com.google.gerrit.server.index.ChangeField.PatchSetApprovalProtoField;
 import com.google.gerrit.server.index.ChangeIndex;
 import com.google.gerrit.server.index.FieldDef;
 import com.google.gerrit.server.index.FieldDef.FillArgs;
@@ -45,11 +48,13 @@
 import com.google.inject.assistedinject.AssistedInject;
 
 import org.apache.lucene.analysis.standard.StandardAnalyzer;
+import org.apache.lucene.analysis.util.CharArraySet;
 import org.apache.lucene.document.Document;
 import org.apache.lucene.document.Field;
 import org.apache.lucene.document.Field.Store;
 import org.apache.lucene.document.IntField;
 import org.apache.lucene.document.LongField;
+import org.apache.lucene.document.StoredField;
 import org.apache.lucene.document.StringField;
 import org.apache.lucene.document.TextField;
 import org.apache.lucene.index.IndexWriter;
@@ -63,6 +68,7 @@
 import org.apache.lucene.search.Sort;
 import org.apache.lucene.search.SortField;
 import org.apache.lucene.search.TopDocs;
+import org.apache.lucene.util.BytesRef;
 import org.apache.lucene.util.Version;
 import org.eclipse.jgit.errors.ConfigInvalidException;
 import org.eclipse.jgit.lib.Config;
@@ -98,6 +104,8 @@
   public static final String CHANGES_OPEN = "open";
   public static final String CHANGES_CLOSED = "closed";
   private static final String ID_FIELD = ChangeField.LEGACY_ID.getName();
+  private static final String CHANGE_FIELD = ChangeField.CHANGE.getName();
+  private static final String APPROVAL_FIELD = ChangeField.APPROVAL.getName();
 
   static interface Factory {
     LuceneChangeIndex create(Schema<ChangeData> schema, String base);
@@ -105,7 +113,7 @@
 
   private static IndexWriterConfig getIndexWriterConfig(Config cfg, String name) {
     IndexWriterConfig writerConfig = new IndexWriterConfig(LUCENE_VERSION,
-        new StandardAnalyzer(LUCENE_VERSION));
+        new StandardAnalyzer(LUCENE_VERSION, CharArraySet.EMPTY_SET));
     writerConfig.setOpenMode(OpenMode.CREATE_OR_APPEND);
     double m = 1 << 20;
     writerConfig.setRAMBufferSizeMB(cfg.getLong("index", name, "ramBufferSize",
@@ -256,7 +264,8 @@
   }
 
   private static class QuerySource implements ChangeDataSource {
-    private static final ImmutableSet<String> FIELDS = ImmutableSet.of(ID_FIELD);
+    private static final ImmutableSet<String> FIELDS =
+        ImmutableSet.of(ID_FIELD, CHANGE_FIELD, APPROVAL_FIELD);
 
     private final List<SubIndex> indexes;
     private final Query query;
@@ -303,8 +312,7 @@
             Lists.newArrayListWithCapacity(docs.scoreDocs.length);
         for (ScoreDoc sd : docs.scoreDocs) {
           Document doc = searchers[sd.shardIndex].doc(sd.doc, FIELDS);
-          Number v = doc.getField(ID_FIELD).numericValue();
-          result.add(new ChangeData(new Change.Id(v.intValue())));
+          result.add(toChangeData(doc));
         }
 
         final List<ChangeData> r = Collections.unmodifiableList(result);
@@ -340,6 +348,30 @@
     }
   }
 
+  private static ChangeData toChangeData(Document doc) {
+    BytesRef cb = doc.getBinaryValue(CHANGE_FIELD);
+    if (cb == null) {
+      int id = doc.getField(ID_FIELD).numericValue().intValue();
+      return new ChangeData(new Change.Id(id));
+    }
+
+    Change change = ChangeProtoField.CODEC.decode(
+        cb.bytes, cb.offset, cb.length);
+    ChangeData cd = new ChangeData(change);
+
+    BytesRef[] approvalsBytes = doc.getBinaryValues(APPROVAL_FIELD);
+    if (approvalsBytes != null) {
+      List<PatchSetApproval> approvals =
+          Lists.newArrayListWithCapacity(approvalsBytes.length);
+      for (BytesRef ab : approvalsBytes) {
+        approvals.add(PatchSetApprovalProtoField.CODEC.decode(
+            ab.bytes, ab.offset, ab.length));
+      }
+      cd.setCurrentApprovals(approvals);
+    }
+    return cd;
+  }
+
   private Document toDocument(ChangeData cd) throws IOException {
     try {
       Document result = new Document();
@@ -386,6 +418,10 @@
       for (Object value : values) {
         doc.add(new TextField(name, (String) value, store));
       }
+    } else if (f.getType() == FieldType.STORED_ONLY) {
+      for (Object value : values) {
+        doc.add(new StoredField(name, (byte[]) value));
+      }
     } else {
       throw QueryBuilder.badFieldType(f.getType());
     }
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java
index 24c33cd..891dadc 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java
@@ -56,7 +56,7 @@
 import com.google.gerrit.server.mail.SmtpEmailSender;
 import com.google.gerrit.server.patch.IntraLineWorkerPool;
 import com.google.gerrit.server.plugins.PluginGuiceEnvironment;
-import com.google.gerrit.server.plugins.PluginModule;
+import com.google.gerrit.server.plugins.PluginRestApiModule;
 import com.google.gerrit.server.schema.SchemaVersionCheck;
 import com.google.gerrit.server.ssh.NoSshKeyCache;
 import com.google.gerrit.server.ssh.NoSshModule;
@@ -255,7 +255,7 @@
     modules.add(new DefaultCacheFactory.Module());
     modules.add(new SmtpEmailSender.Module());
     modules.add(new SignedTokenEmailTokenVerifier.Module());
-    modules.add(new PluginModule());
+    modules.add(new PluginRestApiModule());
     AbstractModule changeIndexModule;
     switch (IndexModule.getIndexType(cfgInjector)) {
       case LUCENE:
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Init.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Init.java
index 0f0b208..ae1ab71 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Init.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Init.java
@@ -70,6 +70,9 @@
   @Option(name = "--no-auto-start", usage = "Don't automatically start daemon after init")
   private boolean noAutoStart;
 
+  @Option(name = "--skip-plugins", usage = "Don't install plugin")
+  private boolean skipPlugins = false;
+
   @Option(name = "--list-plugins", usage = "List available plugins")
   private boolean listPlugins;
 
@@ -90,22 +93,25 @@
     ErrorLogFile.errorOnlyConsole();
 
     final SiteInit init = createSiteInit();
-    final List<PluginData> plugins = InitPlugins.listPlugins(init.site);
-    ConsoleUI ui = ConsoleUI.getInstance(false);
-    verifyInstallPluginList(ui, plugins);
-    if (listPlugins) {
-      if (!plugins.isEmpty()) {
-        ui.message("Available plugins:\n");
-        for (PluginData plugin : plugins) {
-          ui.message(" * %s version %s\n", plugin.name, plugin.version);
+    if (!skipPlugins) {
+      final List<PluginData> plugins = InitPlugins.listPlugins(init.site);
+      ConsoleUI ui = ConsoleUI.getInstance(false);
+      verifyInstallPluginList(ui, plugins);
+      if (listPlugins) {
+        if (!plugins.isEmpty()) {
+          ui.message("Available plugins:\n");
+          for (PluginData plugin : plugins) {
+            ui.message(" * %s version %s\n", plugin.name, plugin.version);
+          }
+        } else {
+          ui.message("No plugins found.\n");
         }
-      } else {
-        ui.message("No plugins found.\n");
+        return 0;
       }
-      return 0;
     }
 
     init.flags.autoStart = !noAutoStart && init.site.isNew;
+    init.flags.skipPlugins = skipPlugins;
 
     final SiteRun run;
     try {
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/HttpLog.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/HttpLog.java
index 9c81cfa..5ee335d 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/HttpLog.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/HttpLog.java
@@ -111,7 +111,7 @@
       uri = uri + "?" + qs;
     }
 
-    if (user.isIdentifiedUser()) {
+    if (user != null && user.isIdentifiedUser()) {
       IdentifiedUser who = (IdentifiedUser) user;
       if (who.getUserName() != null && !who.getUserName().isEmpty()) {
         event.setProperty(P_USER, who.getUserName());
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitFlags.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitFlags.java
index adbb03a..267b41a 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitFlags.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitFlags.java
@@ -34,10 +34,14 @@
   /** Run the daemon (and open the web UI in a browser) after initialization. */
   public boolean autoStart;
 
+  /** Skip plugins */
+  public boolean skipPlugins;
+
   public final FileBasedConfig cfg;
   public final FileBasedConfig sec;
   public final List<String> installPlugins;
 
+
   @Inject
   InitFlags(final SitePaths site,
       final @InstallPlugins List<String> installPlugins) throws IOException,
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/SitePathInitializer.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/SitePathInitializer.java
index e71f3d1..1c2d024 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/SitePathInitializer.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/SitePathInitializer.java
@@ -75,6 +75,10 @@
     mkdir(site.data_dir);
 
     for (InitStep step : steps) {
+      if (step instanceof InitPlugins
+          && flags.skipPlugins) {
+        continue;
+      }
       step.run();
     }
 
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/AccountGeneralPreferences.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/AccountGeneralPreferences.java
index 7244f49..92c479e 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/AccountGeneralPreferences.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/AccountGeneralPreferences.java
@@ -27,7 +27,7 @@
 
   /** Preferred scheme type to download a change. */
   public static enum DownloadScheme {
-    ANON_GIT, ANON_HTTP, ANON_SSH, HTTP, SSH, REPO_DOWNLOAD, DEFAULT_DOWNLOADS;
+    ANON_GIT, ANON_HTTP, HTTP, SSH, REPO_DOWNLOAD, DEFAULT_DOWNLOADS;
   }
 
   /** Preferred method to download a change. */
diff --git a/gerrit-server/BUCK b/gerrit-server/BUCK
index f12fac1..1e20546 100644
--- a/gerrit-server/BUCK
+++ b/gerrit-server/BUCK
@@ -32,6 +32,7 @@
     '//lib:ow2-asm-util',
     '//lib:parboiled-core',
     '//lib:pegdown',
+    '//lib:protobuf',
     '//lib:velocity',
     '//lib/antlr:java_runtime',
     '//lib/commons:codec',
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/IdentifiedUser.java b/gerrit-server/src/main/java/com/google/gerrit/server/IdentifiedUser.java
index e4209f0..7ab2564 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/IdentifiedUser.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/IdentifiedUser.java
@@ -15,6 +15,7 @@
 package com.google.gerrit.server;
 
 import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Sets;
 import com.google.gerrit.common.data.AccountInfo;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.AccountDiffPreference;
@@ -34,6 +35,7 @@
 import com.google.gerrit.server.config.AuthConfig;
 import com.google.gerrit.server.config.CanonicalWebUrl;
 import com.google.gwtorm.server.OrmException;
+import com.google.gwtorm.server.ResultSet;
 import com.google.inject.Inject;
 import com.google.inject.OutOfScopeException;
 import com.google.inject.Provider;
@@ -53,7 +55,6 @@
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Date;
-import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
 import java.util.TimeZone;
@@ -195,6 +196,7 @@
   private Set<String> emailAddresses;
   private GroupMembership effectiveGroups;
   private Set<Change.Id> starredChanges;
+  private ResultSet<StarredChange> starredQuery;
   private Collection<AccountProjectWatch> notificationFilters;
   private CurrentUser realUser;
 
@@ -296,11 +298,18 @@
       if (dbProvider == null) {
         throw new OutOfScopeException("Not in request scoped user");
       }
-      final Set<Change.Id> h = new HashSet<Change.Id>();
+      Set<Change.Id> h = Sets.newHashSet();
       try {
-        for (final StarredChange sc : dbProvider.get().starredChanges()
-            .byAccount(getAccountId())) {
-          h.add(sc.getChangeId());
+        if (starredQuery != null) {
+          for (StarredChange sc : starredQuery) {
+            h.add(sc.getChangeId());
+          }
+          starredQuery = null;
+        } else {
+          for (StarredChange sc : dbProvider.get().starredChanges()
+              .byAccount(getAccountId())) {
+            h.add(sc.getChangeId());
+          }
         }
       } catch (OrmException e) {
         log.warn("Cannot query starred by user changes", e);
@@ -310,6 +319,29 @@
     return starredChanges;
   }
 
+  public void asyncStarredChanges() {
+    if (starredChanges == null && dbProvider != null) {
+      try {
+        starredQuery =
+            dbProvider.get().starredChanges().byAccount(getAccountId());
+      } catch (OrmException e) {
+        log.warn("Cannot query starred by user changes", e);
+        starredQuery = null;
+        starredChanges = Collections.emptySet();
+      }
+    }
+  }
+
+  public void abortStarredChanges() {
+    if (starredQuery != null) {
+      try {
+        starredQuery.close();
+      } finally {
+        starredQuery = null;
+      }
+    }
+  }
+
   @Override
   public Collection<AccountProjectWatch> getNotificationFilters() {
     if (notificationFilters == null) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeJson.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeJson.java
index 90ff799..8f16bb4 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeJson.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeJson.java
@@ -25,6 +25,7 @@
 import static com.google.gerrit.common.changes.ListChangesOption.DETAILED_LABELS;
 import static com.google.gerrit.common.changes.ListChangesOption.LABELS;
 import static com.google.gerrit.common.changes.ListChangesOption.MESSAGES;
+import static com.google.gerrit.common.changes.ListChangesOption.REVIEWED;
 
 import com.google.common.base.Joiner;
 import com.google.common.base.Objects;
@@ -32,6 +33,7 @@
 import com.google.common.collect.HashBasedTable;
 import com.google.common.collect.HashMultimap;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
 import com.google.common.collect.LinkedListMultimap;
 import com.google.common.collect.ListMultimap;
 import com.google.common.collect.Lists;
@@ -77,6 +79,7 @@
 import com.google.gerrit.server.query.change.ChangeData;
 import com.google.gerrit.server.ssh.SshAdvertisedAddresses;
 import com.google.gwtorm.server.OrmException;
+import com.google.gwtorm.server.ResultSet;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
 import com.google.inject.Singleton;
@@ -90,6 +93,7 @@
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.EnumSet;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -97,6 +101,22 @@
 
 public class ChangeJson {
   private static final Logger log = LoggerFactory.getLogger(ChangeJson.class);
+  private static final ResultSet<ChangeMessage> NO_MESSAGES =
+      new ResultSet<ChangeMessage>() {
+        @Override
+        public Iterator<ChangeMessage> iterator() {
+          return toList().iterator();
+        }
+
+        @Override
+        public List<ChangeMessage> toList() {
+          return Collections.emptyList();
+        }
+
+        @Override
+        public void close() {
+        }
+      };
 
   @Singleton
   static class Urls {
@@ -140,6 +160,7 @@
   private EnumSet<ListChangesOption> options;
   private AccountInfo.Loader accountLoader;
   private ChangeControl lastControl;
+  private Set<Change.Id> reviewed;
 
   @Inject
   ChangeJson(
@@ -214,16 +235,22 @@
   public List<List<ChangeInfo>> formatList2(List<List<ChangeData>> in)
       throws OrmException {
     accountLoader = accountLoaderFactory.create(has(DETAILED_ACCOUNTS));
+    Iterable<ChangeData> all = Iterables.concat(in);
+    ChangeData.ensureChangeLoaded(db, all);
+    if (has(ALL_REVISIONS)) {
+      ChangeData.ensureAllPatchSetsLoaded(db, all);
+    } else {
+      ChangeData.ensureCurrentPatchSetLoaded(db, all);
+    }
+    if (has(REVIEWED)) {
+      ensureReviewedLoaded(all);
+    }
+    ChangeData.ensureCurrentApprovalsLoaded(db, all);
+
     List<List<ChangeInfo>> res = Lists.newArrayListWithCapacity(in.size());
+    Map<Change.Id, ChangeInfo> out = Maps.newHashMap();
     for (List<ChangeData> changes : in) {
-      ChangeData.ensureChangeLoaded(db, changes);
-      if (has(ALL_REVISIONS)) {
-        ChangeData.ensureAllPatchSetsLoaded(db, changes);
-      } else {
-        ChangeData.ensureCurrentPatchSetLoaded(db, changes);
-      }
-      ChangeData.ensureCurrentApprovalsLoaded(db, changes);
-      res.add(toChangeInfo(changes));
+      res.add(toChangeInfo(out, changes));
     }
     accountLoader.fill();
     return res;
@@ -233,11 +260,16 @@
     return options.contains(option);
   }
 
-  private List<ChangeInfo> toChangeInfo(List<ChangeData> changes)
-      throws OrmException {
+  private List<ChangeInfo> toChangeInfo(Map<Change.Id, ChangeInfo> out,
+      List<ChangeData> changes) throws OrmException {
     List<ChangeInfo> info = Lists.newArrayListWithCapacity(changes.size());
     for (ChangeData cd : changes) {
-      info.add(toChangeInfo(cd));
+      ChangeInfo i = out.get(cd.getId());
+      if (i == null) {
+        i = toChangeInfo(cd);
+        out.put(cd.getId(), i);
+      }
+      info.add(i);
     }
     return info;
   }
@@ -260,7 +292,9 @@
     out.starred = userProvider.get().getStarredChanges().contains(in.getId())
         ? true
         : null;
-    out.reviewed = in.getStatus().isOpen() && isChangeReviewed(cd) ? true : null;
+    out.reviewed = in.getStatus().isOpen()
+        && has(REVIEWED)
+        && reviewed.contains(cd.getId()) ? true : null;
     out.labels = labelsFor(cd, has(LABELS), has(DETAILED_LABELS));
 
     Collection<PatchSet.Id> limited = cd.getLimitedPatchSets();
@@ -696,39 +730,49 @@
     return result;
   }
 
-  private boolean isChangeReviewed(ChangeData cd) throws OrmException {
-    CurrentUser user = userProvider.get();
-    if (user.isIdentifiedUser()) {
-      PatchSet currentPatchSet = cd.currentPatchSet(db);
-      if (currentPatchSet == null) {
-        return false;
-      }
-
-      List<ChangeMessage> messages =
-          db.get().changeMessages().byPatchSet(currentPatchSet.getId()).toList();
-
-      if (messages.isEmpty()) {
-        return false;
-      }
-
-      // Sort messages to let the most recent ones at the beginning.
-      Collections.sort(messages, new Comparator<ChangeMessage>() {
-        @Override
-        public int compare(ChangeMessage a, ChangeMessage b) {
-          return b.getWrittenOn().compareTo(a.getWrittenOn());
+  private void ensureReviewedLoaded(Iterable<ChangeData> all)
+      throws OrmException {
+    reviewed = Sets.newHashSet();
+    if (userProvider.get().isIdentifiedUser()) {
+      Account.Id self = ((IdentifiedUser) userProvider.get()).getAccountId();
+      for (List<ChangeData> batch : Iterables.partition(all, 50)) {
+        List<ResultSet<ChangeMessage>> m =
+            Lists.newArrayListWithCapacity(batch.size());
+        for (ChangeData cd : batch) {
+          PatchSet.Id ps = cd.change(db).currentPatchSetId();
+          if (ps != null && cd.change(db).getStatus().isOpen()) {
+            m.add(db.get().changeMessages().byPatchSet(ps));
+          } else {
+            m.add(NO_MESSAGES);
+          }
         }
-      });
-
-      Account.Id currentUserId = ((IdentifiedUser) user).getAccountId();
-      Account.Id changeOwnerId = cd.change(db).getOwner();
-      for (ChangeMessage cm : messages) {
-        if (currentUserId.equals(cm.getAuthor())) {
-          return true;
-        } else if (changeOwnerId.equals(cm.getAuthor())) {
-          return false;
+        for (int i = 0; i < m.size(); i++) {
+          if (isChangeReviewed(self, batch.get(i), m.get(i).toList())) {
+            reviewed.add(batch.get(i).getId());
+          }
         }
       }
     }
+  }
+
+  private boolean isChangeReviewed(Account.Id self, ChangeData cd,
+      List<ChangeMessage> msgs) throws OrmException {
+    // Sort messages to keep the most recent ones at the beginning.
+    Collections.sort(msgs, new Comparator<ChangeMessage>() {
+      @Override
+      public int compare(ChangeMessage a, ChangeMessage b) {
+        return b.getWrittenOn().compareTo(a.getWrittenOn());
+      }
+    });
+
+    Account.Id changeOwnerId = cd.change(db).getOwner();
+    for (ChangeMessage cm : msgs) {
+      if (self.equals(cm.getAuthor())) {
+        return true;
+      } else if (changeOwnerId.equals(cm.getAuthor())) {
+        return false;
+      }
+    }
     return false;
   }
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReviewers.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReviewers.java
index 5a6d441..f41d97d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReviewers.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReviewers.java
@@ -48,6 +48,7 @@
 import com.google.gerrit.server.change.ReviewerJson.ReviewerInfo;
 import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gerrit.server.group.GroupsCollection;
+import com.google.gerrit.server.index.ChangeIndexer;
 import com.google.gerrit.server.mail.AddReviewerSender;
 import com.google.gerrit.server.project.ChangeControl;
 import com.google.gerrit.server.project.NoSuchProjectException;
@@ -94,6 +95,7 @@
   private final ChangeHooks hooks;
   private final AccountCache accountCache;
   private final ReviewerJson json;
+  private final ChangeIndexer indexer;
 
   @Inject
   PostReviewers(AccountsCollection accounts,
@@ -108,7 +110,8 @@
       @GerritServerConfig Config cfg,
       ChangeHooks hooks,
       AccountCache accountCache,
-      ReviewerJson json) {
+      ReviewerJson json,
+      ChangeIndexer indexer) {
     this.accounts = accounts;
     this.reviewerFactory = reviewerFactory;
     this.addReviewerSenderFactory = addReviewerSenderFactory;
@@ -122,6 +125,7 @@
     this.hooks = hooks;
     this.accountCache = accountCache;
     this.json = json;
+    this.indexer = indexer;
   }
 
   @Override
@@ -255,6 +259,7 @@
       db.rollback();
     }
 
+    indexer.index(rsrc.getChange());
     accountLoaderFactory.create(true).fill(result.reviewers);
     postAdd(rsrc.getChange(), result);
   }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/AuthModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/AuthModule.java
new file mode 100644
index 0000000..f9e9949
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/AuthModule.java
@@ -0,0 +1,54 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.package com.google.gerrit.server.git;
+
+package com.google.gerrit.server.config;
+
+import com.google.gerrit.extensions.registration.DynamicSet;
+import com.google.gerrit.reviewdb.client.AuthType;
+import com.google.gerrit.server.account.DefaultRealm;
+import com.google.gerrit.server.account.Realm;
+import com.google.gerrit.server.auth.AuthBackend;
+import com.google.gerrit.server.auth.InternalAuthBackend;
+import com.google.gerrit.server.auth.ldap.LdapModule;
+import com.google.inject.AbstractModule;
+import com.google.inject.Inject;
+
+public class AuthModule extends AbstractModule {
+  private final AuthType loginType;
+
+  @Inject
+  AuthModule(AuthConfig authConfig) {
+    loginType = authConfig.getAuthType();
+  }
+
+  @Override
+  protected void configure() {
+    switch (loginType) {
+      case HTTP_LDAP:
+      case LDAP:
+      case LDAP_BIND:
+      case CLIENT_SSL_CERT_LDAP:
+        install(new LdapModule());
+        break;
+
+      case CUSTOM_EXTENSION:
+        break;
+
+      default:
+        bind(Realm.class).to(DefaultRealm.class);
+        DynamicSet.bind(binder(), AuthBackend.class).to(InternalAuthBackend.class);
+        break;
+    }
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java
index 66ee72b..50b8075 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java
@@ -21,11 +21,12 @@
 import com.google.gerrit.common.ChangeListener;
 import com.google.gerrit.extensions.config.CapabilityDefinition;
 import com.google.gerrit.extensions.events.GitReferenceUpdatedListener;
+import com.google.gerrit.extensions.events.LifecycleListener;
 import com.google.gerrit.extensions.events.NewProjectCreatedListener;
+import com.google.gerrit.extensions.events.ProjectDeletedListener;
 import com.google.gerrit.extensions.registration.DynamicItem;
 import com.google.gerrit.extensions.registration.DynamicMap;
 import com.google.gerrit.extensions.registration.DynamicSet;
-import com.google.gerrit.reviewdb.client.AuthType;
 import com.google.gerrit.rules.PrologModule;
 import com.google.gerrit.rules.RulesCache;
 import com.google.gerrit.server.AnonymousUser;
@@ -46,7 +47,6 @@
 import com.google.gerrit.server.account.AccountVisibilityProvider;
 import com.google.gerrit.server.account.CapabilityControl;
 import com.google.gerrit.server.account.ChangeUserName;
-import com.google.gerrit.server.account.DefaultRealm;
 import com.google.gerrit.server.account.EmailExpander;
 import com.google.gerrit.server.account.GroupBackend;
 import com.google.gerrit.server.account.GroupCacheImpl;
@@ -59,12 +59,9 @@
 import com.google.gerrit.server.account.InternalGroupBackend;
 import com.google.gerrit.server.account.PerformCreateGroup;
 import com.google.gerrit.server.account.PerformRenameGroup;
-import com.google.gerrit.server.account.Realm;
 import com.google.gerrit.server.account.UniversalGroupBackend;
 import com.google.gerrit.server.auth.AuthBackend;
-import com.google.gerrit.server.auth.InternalAuthBackend;
 import com.google.gerrit.server.auth.UniversalAuthBackend;
-import com.google.gerrit.server.auth.ldap.LdapModule;
 import com.google.gerrit.server.avatar.AvatarProvider;
 import com.google.gerrit.server.cache.CacheRemovalListener;
 import com.google.gerrit.server.events.EventFactory;
@@ -118,45 +115,27 @@
 import com.google.inject.TypeLiteral;
 
 import org.apache.velocity.runtime.RuntimeInstance;
-import org.eclipse.jgit.lib.Config;
 
 import java.util.List;
 
 
 /** Starts global state with standard dependencies. */
 public class GerritGlobalModule extends FactoryModule {
-  private final AuthType loginType;
+  private final AuthModule authModule;
 
   @Inject
-  GerritGlobalModule(final AuthConfig authConfig,
-      @GerritServerConfig final Config config) {
-    loginType = authConfig.getAuthType();
+  GerritGlobalModule(AuthModule authModule) {
+    this.authModule = authModule;
   }
 
   @Override
   protected void configure() {
-    switch (loginType) {
-      case HTTP_LDAP:
-      case LDAP:
-      case LDAP_BIND:
-      case CLIENT_SSL_CERT_LDAP:
-        install(new LdapModule());
-        break;
-
-      case CUSTOM_EXTENSION:
-        break;
-
-      default:
-        bind(Realm.class).to(DefaultRealm.class);
-        DynamicSet.bind(binder(), AuthBackend.class).to(InternalAuthBackend.class);
-        break;
-    }
-
     bind(EmailExpander.class).toProvider(EmailExpanderProvider.class).in(
         SINGLETON);
 
     bind(IdGenerator.class);
     bind(RulesCache.class);
+    install(authModule);
     install(AccountByEmailCacheImpl.module());
     install(AccountCacheImpl.module());
     install(GroupCacheImpl.module());
@@ -253,11 +232,13 @@
     DynamicMap.mapOf(binder(), CapabilityDefinition.class);
     DynamicSet.setOf(binder(), GitReferenceUpdatedListener.class);
     DynamicSet.setOf(binder(), NewProjectCreatedListener.class);
+    DynamicSet.setOf(binder(), ProjectDeletedListener.class);
     DynamicSet.bind(binder(), GitReferenceUpdatedListener.class).to(ChangeCache.class);
     DynamicSet.setOf(binder(), ChangeListener.class);
     DynamicSet.setOf(binder(), CommitValidationListener.class);
     DynamicSet.setOf(binder(), MergeValidationListener.class);
     DynamicItem.itemOf(binder(), AvatarProvider.class);
+    DynamicSet.setOf(binder(), LifecycleListener.class);
 
     bind(AnonymousUser.class);
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/extensions/events/GitReferenceUpdated.java b/gerrit-server/src/main/java/com/google/gerrit/server/extensions/events/GitReferenceUpdated.java
index 49c90bd..04fccc2 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/extensions/events/GitReferenceUpdated.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/extensions/events/GitReferenceUpdated.java
@@ -22,11 +22,16 @@
 
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.RefUpdate;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import java.util.Collections;
 import java.util.List;
 
 public class GitReferenceUpdated {
+  private static final Logger log = LoggerFactory
+      .getLogger(GitReferenceUpdated.class);
+
   public static final GitReferenceUpdated DISABLED = new GitReferenceUpdated(
       Collections.<GitReferenceUpdatedListener> emptyList());
 
@@ -52,7 +57,11 @@
     ObjectId n = newObjectId != null ? newObjectId : ObjectId.zeroId();
     Event event = new Event(project, ref, o.name(), n.name());
     for (GitReferenceUpdatedListener l : listeners) {
-      l.onGitReferenceUpdated(event);
+      try {
+        l.onGitReferenceUpdated(event);
+      } catch (RuntimeException e) {
+        log.warn("Failure in GitReferenceUpdatedListener", e);
+      }
     }
   }
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeOp.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeOp.java
index e1d8627..f09015b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeOp.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeOp.java
@@ -15,7 +15,7 @@
 package com.google.gerrit.server.git;
 
 import static com.google.gerrit.server.git.MergeUtil.getSubmitter;
-import static java.util.concurrent.TimeUnit.DAYS;
+import static java.util.concurrent.TimeUnit.HOURS;
 import static java.util.concurrent.TimeUnit.MILLISECONDS;
 import static java.util.concurrent.TimeUnit.MINUTES;
 import static java.util.concurrent.TimeUnit.SECONDS;
@@ -38,6 +38,7 @@
 import com.google.gerrit.reviewdb.client.Project.SubmitType;
 import com.google.gerrit.reviewdb.client.RevId;
 import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.ApprovalsUtil;
 import com.google.gerrit.server.ChangeUtil;
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.account.AccountCache;
@@ -56,7 +57,6 @@
 import com.google.gerrit.server.project.ProjectState;
 import com.google.gerrit.server.util.RequestScopePropagator;
 import com.google.gwtorm.server.AtomicUpdate;
-import com.google.gwtorm.server.OrmConcurrencyException;
 import com.google.gwtorm.server.OrmException;
 import com.google.gwtorm.server.SchemaFactory;
 import com.google.inject.Inject;
@@ -89,6 +89,8 @@
 import java.util.Map;
 import java.util.Set;
 
+import javax.annotation.Nullable;
+
 /**
  * Merges changes in submission order into a single branch.
  * <p>
@@ -117,8 +119,8 @@
   private static final long LOCK_FAILURE_RETRY_DELAY =
       MILLISECONDS.convert(15, SECONDS);
 
-  private static final long DUPLICATE_MESSAGE_INTERVAL =
-      MILLISECONDS.convert(1, DAYS);
+  private static final long MAX_SUBMIT_WINDOW =
+      MILLISECONDS.convert(12, HOURS);
 
   private final GitRepositoryManager repoManager;
   private final SchemaFactory<ReviewDb> schemaFactory;
@@ -211,7 +213,7 @@
     }
   }
 
-  public void merge() throws MergeException, NoSuchProjectException {
+  public void merge() throws MergeException {
     setDestProject();
     try {
       openSchema();
@@ -270,6 +272,11 @@
               message(commit.change, capable.getMessage()), false);
         }
       }
+    } catch (NoSuchProjectException noProject) {
+      log.warn(String.format(
+          "Project %s no longer exists, abandoning open changes",
+          destBranch.getParentKey().get()));
+      abandonAllOpenChanges();
     } catch (OrmException e) {
       throw new MergeException("Cannot query the database", e);
     } finally {
@@ -343,13 +350,12 @@
         canMergeFlag, getAlreadyAccepted(branchTip), destBranch);
   }
 
-  private void openRepository() throws MergeException {
+  private void openRepository() throws MergeException, NoSuchProjectException {
     final Project.NameKey name = destBranch.getParentKey();
     try {
       repo = repoManager.openRepository(name);
-    } catch (RepositoryNotFoundException notGit) {
-      final String m = "Repository \"" + name.get() + "\" unknown.";
-      throw new MergeException(m, notGit);
+    } catch (RepositoryNotFoundException notFound) {
+      throw new NoSuchProjectException(name, notFound);
     } catch (IOException err) {
       final String m = "Error opening repository \"" + name.get() + '"';
       throw new MergeException(m, err);
@@ -667,7 +673,6 @@
       } catch (OrmException err) {
         log.warn("Error updating change status for " + c.getId(), err);
       }
-      indexer.index(c);
     }
   }
 
@@ -811,6 +816,7 @@
     } finally {
       db.rollback();
     }
+    indexer.index(c);
   }
 
   private void setMergedPatchSet(Change.Id changeId, final PatchSet.Id merged)
@@ -932,61 +938,41 @@
     sendMergeFail(c, msg, true);
   }
 
-  private boolean isDuplicate(ChangeMessage msg) {
+  private enum RetryStatus {
+    UNSUBMIT, RETRY_NO_MESSAGE, RETRY_ADD_MESSAGE;
+  }
+
+  private RetryStatus getRetryStatus(
+      @Nullable PatchSetApproval submitter,
+      ChangeMessage msg) {
+    if (submitter != null
+        && System.currentTimeMillis() - submitter.getGranted().getTime()
+          > MAX_SUBMIT_WINDOW) {
+      return RetryStatus.UNSUBMIT;
+    }
+
     try {
       ChangeMessage last = Iterables.getLast(db.changeMessages().byChange(
           msg.getPatchSetId().getParentKey()), null);
       if (last != null) {
-        long lastMs = last.getWrittenOn().getTime();
-        long msgMs = msg.getWrittenOn().getTime();
         if (Objects.equal(last.getAuthor(), msg.getAuthor())
-            && Objects.equal(last.getMessage(), msg.getMessage())
-            && msgMs - lastMs < DUPLICATE_MESSAGE_INTERVAL) {
-          return true;
+            && Objects.equal(last.getMessage(), msg.getMessage())) {
+          long lastMs = last.getWrittenOn().getTime();
+          long msgMs = msg.getWrittenOn().getTime();
+          return msgMs - lastMs > MAX_SUBMIT_WINDOW
+              ? RetryStatus.UNSUBMIT
+              : RetryStatus.RETRY_NO_MESSAGE;
         }
       }
+      return RetryStatus.RETRY_ADD_MESSAGE;
     } catch (OrmException err) {
-      log.warn("Cannot check previous merge failure message", err);
+      log.warn("Cannot check previous merge failure, unsubmitting", err);
+      return RetryStatus.UNSUBMIT;
     }
-    return false;
   }
 
   private void sendMergeFail(final Change c, final ChangeMessage msg,
-      final boolean makeNew) {
-    if (isDuplicate(msg)) {
-      return;
-    }
-
-    try {
-      db.changeMessages().insert(Collections.singleton(msg));
-    } catch (OrmException err) {
-      log.warn("Cannot record merge failure message", err);
-    }
-
-    if (makeNew) {
-      try {
-        db.changes().atomicUpdate(c.getId(), new AtomicUpdate<Change>() {
-          @Override
-          public Change update(Change c) {
-            if (c.getStatus().isOpen()) {
-              c.setStatus(Change.Status.NEW);
-              ChangeUtil.updated(c);
-            }
-            return c;
-          }
-        });
-      } catch (OrmConcurrencyException err) {
-      } catch (OrmException err) {
-        log.warn("Cannot update change status", err);
-      }
-    } else {
-      try {
-        ChangeUtil.touch(c, db);
-      } catch (OrmException err) {
-        log.warn("Cannot update change timestamp", err);
-      }
-    }
-
+      boolean makeNew) {
     PatchSetApproval submitter = null;
     try {
       submitter = getSubmitter(db, c.currentPatchSetId());
@@ -994,6 +980,43 @@
       log.error("Cannot get submitter", e);
     }
 
+    if (!makeNew) {
+      RetryStatus retryStatus = getRetryStatus(submitter, msg);
+      if (retryStatus == RetryStatus.RETRY_NO_MESSAGE) {
+        return;
+      } else if (retryStatus == RetryStatus.UNSUBMIT) {
+        makeNew = true;
+      }
+    }
+
+    final boolean setStatusNew = makeNew;
+    try {
+      db.changes().beginTransaction(c.getId());
+      try {
+        Change change = db.changes().atomicUpdate(
+            c.getId(),
+            new AtomicUpdate<Change>() {
+          @Override
+          public Change update(Change c) {
+            if (c.getStatus().isOpen()) {
+              if (setStatusNew) {
+                c.setStatus(Change.Status.NEW);
+              }
+              ChangeUtil.updated(c);
+            }
+            return c;
+          }
+        });
+        db.changeMessages().insert(Collections.singleton(msg));
+        db.commit();
+        indexer.index(change);
+      } finally {
+        db.rollback();
+      }
+    } catch (OrmException err) {
+      log.warn("Cannot record merge failure message", err);
+    }
+
     final PatchSetApproval from = submitter;
     workQueue.getDefaultQueue()
         .submit(requestScopePropagator.wrap(new Runnable() {
@@ -1041,4 +1064,53 @@
       }
     }
   }
+
+  private void abandonAllOpenChanges() {
+    try {
+      openSchema();
+      for (Change c : db.changes().byProjectOpenAll(destBranch.getParentKey())) {
+        abandonOneChange(c);
+      }
+      db.close();
+      db = null;
+    } catch (OrmException e) {
+      log.warn(String.format(
+          "Cannot abandon changes for deleted project %s",
+          destBranch.getParentKey().get()), e);
+    }
+  }
+
+  private void abandonOneChange(Change change) throws OrmException {
+    db.changes().beginTransaction(change.getId());
+    try {
+      change = db.changes().atomicUpdate(
+        change.getId(),
+        new AtomicUpdate<Change>() {
+          @Override
+          public Change update(Change change) {
+            if (change.getStatus().isOpen()) {
+              change.setStatus(Change.Status.ABANDONED);
+              return change;
+            }
+            return null;
+          }
+        });
+      if (change != null) {
+        ChangeMessage msg = new ChangeMessage(
+            new ChangeMessage.Key(
+                change.getId(),
+                ChangeUtil.messageUUID(db)),
+            null,
+            change.getLastUpdatedOn(),
+            change.currentPatchSetId());
+        msg.setMessage("Project was deleted.");
+        db.changeMessages().insert(Collections.singleton(msg));
+        new ApprovalsUtil(db).syncChangeStatus(change);
+        db.commit();
+        indexer.index(change);
+      }
+    } finally {
+      db.rollback();
+    }
+  }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java
index 86b7e0d..5245957 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java
@@ -404,7 +404,8 @@
       }
     });
     advHooks.add(rp.getAdvertiseRefsHook());
-    advHooks.add(new ReceiveCommitsAdvertiseRefsHook());
+    advHooks.add(new ReceiveCommitsAdvertiseRefsHook(
+        db, projectControl.getProject().getNameKey()));
     rp.setAdvertiseRefsHook(AdvertiseRefsHookChain.newChain(advHooks));
   }
 
@@ -439,80 +440,6 @@
     return rp;
   }
 
-  /** Scan part of history and include it in the advertisement. */
-  public void advertiseHistory() {
-    Set<ObjectId> toInclude = new HashSet<ObjectId>();
-
-    // Advertise some recent open changes, in case a commit is based one.
-    try {
-      Set<PatchSet.Id> toGet = new HashSet<PatchSet.Id>();
-      for (Change change : db.changes()
-          .byProjectOpenNext(project.getNameKey(), "z", 32)) {
-        PatchSet.Id id = change.currentPatchSetId();
-        if (id != null) {
-          toGet.add(id);
-        }
-      }
-      for (PatchSet ps : db.patchSets().get(toGet)) {
-        if (ps.getRevision() != null && ps.getRevision().get() != null) {
-          toInclude.add(ObjectId.fromString(ps.getRevision().get()));
-        }
-      }
-    } catch (OrmException err) {
-      log.error("Cannot list open changes of " + project.getNameKey(), err);
-    }
-
-    // Size of an additional ".have" line.
-    final int haveLineLen = 4 + Constants.OBJECT_ID_STRING_LENGTH + 1 + 5 + 1;
-
-    // Maximum number of bytes to "waste" in the advertisement with
-    // a peek at this repository's current reachable history.
-    final int maxExtraSize = 8192;
-
-    // Number of recent commits to advertise immediately, hoping to
-    // show a client a nearby merge base.
-    final int base = 64;
-
-    // Number of commits to skip once base has already been shown.
-    final int step = 16;
-
-    // Total number of commits to extract from the history.
-    final int max = maxExtraSize / haveLineLen;
-
-    // Scan history until the advertisement is full.
-    Set<ObjectId> alreadySending = rp.getAdvertisedObjects();
-    RevWalk rw = rp.getRevWalk();
-    for (ObjectId haveId : alreadySending) {
-      try {
-        rw.markStart(rw.parseCommit(haveId));
-      } catch (IOException badCommit) {
-        continue;
-      }
-    }
-
-    int stepCnt = 0;
-    RevCommit c;
-    try {
-      while ((c = rw.next()) != null && toInclude.size() < max) {
-        if (alreadySending.contains(c)) {
-        } else if (toInclude.contains(c)) {
-        } else if (c.getParentCount() > 1) {
-        } else if (toInclude.size() < base) {
-          toInclude.add(c);
-        } else {
-          stepCnt = ++stepCnt % step;
-          if (stepCnt == 0) {
-            toInclude.add(c);
-          }
-        }
-      }
-    } catch (IOException err) {
-      log.error("Error trying to advertise history on " + project.getNameKey(), err);
-    }
-    rw.reset();
-    rp.getAdvertisedObjects().addAll(toInclude);
-  }
-
   /** Determine if the user can upload commits. */
   public Capable canUpload() {
     Capable result = projectControl.canPushToAtLeastOneRef();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommitsAdvertiseRefsHook.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommitsAdvertiseRefsHook.java
index 0eb9b61..ace4e90 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommitsAdvertiseRefsHook.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommitsAdvertiseRefsHook.java
@@ -15,17 +15,43 @@
 package com.google.gerrit.server.git;
 
 import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import com.google.gerrit.reviewdb.client.Change;
+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.util.MagicBranch;
+import com.google.gwtorm.server.OrmException;
 
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevWalk;
 import org.eclipse.jgit.transport.AdvertiseRefsHook;
 import org.eclipse.jgit.transport.BaseReceivePack;
 import org.eclipse.jgit.transport.UploadPack;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
+import java.io.IOException;
 import java.util.Map;
+import java.util.Set;
 
 /** Exposes only the non refs/changes/ reference names. */
 public class ReceiveCommitsAdvertiseRefsHook implements AdvertiseRefsHook {
+  private static final Logger log = LoggerFactory
+      .getLogger(ReceiveCommitsAdvertiseRefsHook.class);
+
+  private final ReviewDb db;
+  private final Project.NameKey projectName;
+
+  public ReceiveCommitsAdvertiseRefsHook(ReviewDb db,
+      Project.NameKey projectName) {
+    this.db = db;
+    this.projectName = projectName;
+  }
+
   @Override
   public void advertiseRefs(UploadPack us) {
     throw new UnsupportedOperationException(
@@ -45,7 +71,84 @@
         r.put(name, e.getValue());
       }
     }
-    rp.setAdvertisedRefs(r, rp.getAdvertisedObjects());
+    rp.setAdvertisedRefs(r, advertiseHistory(r.values(), rp));
+  }
+
+  private Set<ObjectId> advertiseHistory(
+      Iterable<Ref> sending,
+      BaseReceivePack rp) {
+    Set<ObjectId> toInclude = Sets.newHashSet();
+
+    // Advertise some recent open changes, in case a commit is based one.
+    try {
+      Set<PatchSet.Id> toGet = Sets.newHashSetWithExpectedSize(32);
+      for (Change c : db.changes().byProjectOpenNext(projectName, "z", 32)) {
+        PatchSet.Id id = c.currentPatchSetId();
+        if (id != null) {
+          toGet.add(id);
+        }
+      }
+      for (PatchSet ps : db.patchSets().get(toGet)) {
+        if (ps.getRevision() != null && ps.getRevision().get() != null) {
+          toInclude.add(ObjectId.fromString(ps.getRevision().get()));
+        }
+      }
+    } catch (OrmException err) {
+      log.error("Cannot list open changes of " + projectName, err);
+    }
+
+    // Size of an additional ".have" line.
+    final int haveLineLen = 4 + Constants.OBJECT_ID_STRING_LENGTH + 1 + 5 + 1;
+
+    // Maximum number of bytes to "waste" in the advertisement with
+    // a peek at this repository's current reachable history.
+    final int maxExtraSize = 8192;
+
+    // Number of recent commits to advertise immediately, hoping to
+    // show a client a nearby merge base.
+    final int base = 64;
+
+    // Number of commits to skip once base has already been shown.
+    final int step = 16;
+
+    // Total number of commits to extract from the history.
+    final int max = maxExtraSize / haveLineLen;
+
+    // Scan history until the advertisement is full.
+    Set<ObjectId> alreadySending = Sets.newHashSet();
+    RevWalk rw = rp.getRevWalk();
+    for (Ref ref : sending) {
+      try {
+        if (ref.getObjectId() != null) {
+          alreadySending.add(ref.getObjectId());
+          rw.markStart(rw.parseCommit(ref.getObjectId()));
+        }
+      } catch (IOException badCommit) {
+        continue;
+      }
+    }
+
+    int stepCnt = 0;
+    RevCommit c;
+    try {
+      while ((c = rw.next()) != null && toInclude.size() < max) {
+        if (alreadySending.contains(c)) {
+        } else if (toInclude.contains(c)) {
+        } else if (c.getParentCount() > 1) {
+        } else if (toInclude.size() < base) {
+          toInclude.add(c);
+        } else {
+          stepCnt = ++stepCnt % step;
+          if (stepCnt == 0) {
+            toInclude.add(c);
+          }
+        }
+      }
+    } catch (IOException err) {
+      log.error("Error trying to advertise history on " + projectName, err);
+    }
+    rw.reset();
+    return toInclude;
   }
 
   private static boolean skip(String name) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeField.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeField.java
index d62df95..20b61e7 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeField.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeField.java
@@ -14,8 +14,10 @@
 
 package com.google.gerrit.server.index;
 
+import com.google.common.collect.Lists;
 import com.google.common.collect.Sets;
 import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.ChangeMessage;
 import com.google.gerrit.reviewdb.client.PatchLineComment;
 import com.google.gerrit.reviewdb.client.PatchSet;
@@ -25,10 +27,16 @@
 import com.google.gerrit.server.query.change.ChangeData;
 import com.google.gerrit.server.query.change.ChangeQueryBuilder;
 import com.google.gerrit.server.query.change.ChangeStatusPredicate;
+import com.google.gwtorm.protobuf.CodecFactory;
+import com.google.gwtorm.protobuf.ProtobufCodec;
 import com.google.gwtorm.server.OrmException;
+import com.google.protobuf.CodedOutputStream;
 
+import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.sql.Timestamp;
+import java.util.Collection;
+import java.util.List;
 import java.util.Set;
 
 /**
@@ -233,6 +241,47 @@
         }
       };
 
+  public static class ChangeProtoField extends FieldDef.Single<ChangeData, byte[]> {
+    public static final ProtobufCodec<Change> CODEC =
+        CodecFactory.encoder(Change.class);
+
+    private ChangeProtoField() {
+      super("_change", FieldType.STORED_ONLY, true);
+    }
+
+    @Override
+    public byte[] get(ChangeData input, FieldDef.FillArgs args)
+        throws OrmException {
+      return CODEC.encodeToByteArray(input.change(args.db));
+    }
+  }
+
+  /** Serialized change object, used for pre-populating results. */
+  public static final ChangeProtoField CHANGE = new ChangeProtoField();
+
+  public static class PatchSetApprovalProtoField
+      extends FieldDef.Repeatable<ChangeData, byte[]> {
+    public static final ProtobufCodec<PatchSetApproval> CODEC =
+        CodecFactory.encoder(PatchSetApproval.class);
+
+    private PatchSetApprovalProtoField() {
+      super("_approval", FieldType.STORED_ONLY, true);
+    }
+
+    @Override
+    public Iterable<byte[]> get(ChangeData input, FillArgs args)
+        throws OrmException {
+      return toProtos(CODEC, input.currentApprovals(args.db));
+    }
+  }
+
+  /**
+   * Serialized approvals for the current patch set, used for pre-populating
+   * results.
+   */
+  public static final PatchSetApprovalProtoField APPROVAL =
+      new PatchSetApprovalProtoField();
+
   public static String formatLabel(String label, int value) {
     return formatLabel(label, value, null);
   }
@@ -273,4 +322,22 @@
           return r;
         }
       };
+
+  private static <T> List<byte[]> toProtos(ProtobufCodec<T> codec, Collection<T> objs)
+      throws OrmException {
+    List<byte[]> result = Lists.newArrayListWithCapacity(objs.size());
+    ByteArrayOutputStream out = new ByteArrayOutputStream(256);
+    try {
+      for (T obj : objs) {
+        out.reset();
+        CodedOutputStream cos = CodedOutputStream.newInstance(out);
+        codec.encode(obj, cos);
+        cos.flush();
+        result.add(out.toByteArray());
+      }
+    } catch (IOException e) {
+      throw new OrmException(e);
+    }
+    return result;
+  }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeSchemas.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeSchemas.java
index 5b3cdad..1ed6c47 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeSchemas.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeSchemas.java
@@ -49,6 +49,28 @@
         ChangeField.COMMIT_MESSAGE,
         ChangeField.COMMENT);
 
+  @SuppressWarnings({"unchecked", "deprecation"})
+  static final Schema<ChangeData> V2 = release(
+        ChangeField.LEGACY_ID,
+        ChangeField.ID,
+        ChangeField.STATUS,
+        ChangeField.PROJECT,
+        ChangeField.REF,
+        ChangeField.TOPIC,
+        ChangeField.UPDATED,
+        ChangeField.SORTKEY,
+        ChangeField.FILE,
+        ChangeField.OWNER,
+        ChangeField.REVIEWER,
+        ChangeField.COMMIT,
+        ChangeField.TR,
+        ChangeField.LABEL,
+        ChangeField.REVIEWED,
+        ChangeField.COMMIT_MESSAGE,
+        ChangeField.COMMENT,
+        ChangeField.CHANGE,
+        ChangeField.APPROVAL);
+
   private static Schema<ChangeData> release(FieldDef<ChangeData, ?>... fields) {
     return new Schema<ChangeData>(true, Arrays.asList(fields));
   }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/FieldType.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/FieldType.java
index ded98ea..a3247b9 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/FieldType.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/FieldType.java
@@ -43,6 +43,10 @@
   public static final FieldType<String> FULL_TEXT =
       new FieldType<String>("FULL_TEXT");
 
+  /** A field that is only stored as raw bytes and cannot be queried. */
+  public static final FieldType<byte[]> STORED_ONLY =
+      new FieldType<byte[]>("STORED_ONLY");
+
   private final String name;
 
   private FieldType(String name) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexedChangeQuery.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexedChangeQuery.java
index dd94577..ccd6c89 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexedChangeQuery.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexedChangeQuery.java
@@ -126,7 +126,10 @@
 
   @Override
   public int getCost() {
-    return pred.getCost();
+    // Index queries are assumed to be cheaper than any other type of query, so
+    // so try to make sure they get picked. Note that pred's cost may be higher
+    // because it doesn't know whether it's being used in an index query or not.
+    return 0;
   }
 
   @Override
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/EmailArguments.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/EmailArguments.java
index 3e50283..7798028 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/EmailArguments.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/EmailArguments.java
@@ -30,7 +30,6 @@
 import com.google.gerrit.server.patch.PatchSetInfoFactory;
 import com.google.gerrit.server.project.ProjectCache;
 import com.google.gerrit.server.query.change.ChangeQueryBuilder;
-import com.google.gerrit.server.query.change.ChangeQueryRewriter;
 import com.google.gerrit.server.ssh.SshAdvertisedAddresses;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
@@ -60,7 +59,6 @@
   final List<String> sshAddresses;
 
   final ChangeQueryBuilder.Factory queryBuilder;
-  final Provider<ChangeQueryRewriter> queryRewriter;
   final Provider<ReviewDb> db;
   final RuntimeInstance velocityRuntime;
   final EmailSettings settings;
@@ -78,7 +76,7 @@
       @CanonicalWebUrl @Nullable Provider<String> urlProvider,
       AllProjectsName allProjectsName,
       ChangeQueryBuilder.Factory queryBuilder,
-      Provider<ChangeQueryRewriter> queryRewriter, Provider<ReviewDb> db,
+      Provider<ReviewDb> db,
       RuntimeInstance velocityRuntime,
       EmailSettings settings,
       @SshAdvertisedAddresses List<String> sshAddresses) {
@@ -98,7 +96,6 @@
     this.urlProvider = urlProvider;
     this.allProjectsName = allProjectsName;
     this.queryBuilder = queryBuilder;
-    this.queryRewriter = queryRewriter;
     this.db = db;
     this.velocityRuntime = velocityRuntime;
     this.settings = settings;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/ProjectWatch.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/ProjectWatch.java
index 69cc947..757562c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/ProjectWatch.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/ProjectWatch.java
@@ -88,9 +88,9 @@
             add(matching, nc, state.getProject().getNameKey());
           } catch (QueryParseException e) {
             log.warn(String.format(
-                "Project %s has invalid notify %s filter \"%s\": %s",
+                "Project %s has invalid notify %s filter \"%s\"",
                 state.getProject().getName(), nc.getName(),
-                nc.getFilter(), e.getMessage()));
+                nc.getFilter()), e);
           }
         }
       }
@@ -209,7 +209,6 @@
       } else {
         p = Predicate.and(filterPredicate, p);
       }
-      p = args.queryRewriter.get().rewrite(p);
     }
     return p == null ? true : p.match(changeData);
   }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginModule.java
index 4cdafb3..bd1c3c8 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginModule.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginModule.java
@@ -14,21 +14,17 @@
 
 package com.google.gerrit.server.plugins;
 
-import static com.google.gerrit.server.plugins.PluginResource.PLUGIN_KIND;
-
-import com.google.gerrit.extensions.registration.DynamicMap;
-import com.google.gerrit.extensions.restapi.RestApiModule;
 import com.google.gerrit.extensions.systemstatus.ServerInformation;
 import com.google.gerrit.lifecycle.LifecycleModule;
+import com.google.inject.AbstractModule;
 
-public class PluginModule extends RestApiModule {
+public class PluginModule extends AbstractModule {
   @Override
   protected void configure() {
     bind(ServerInformationImpl.class);
     bind(ServerInformation.class).to(ServerInformationImpl.class);
 
     bind(PluginCleanerTask.class);
-    bind(PluginsCollection.class);
     bind(PluginGuiceEnvironment.class);
     bind(PluginLoader.class);
     bind(CopyConfigModule.class);
@@ -38,13 +34,5 @@
         listener().to(PluginLoader.class);
       }
     });
-
-    DynamicMap.mapOf(binder(), PLUGIN_KIND);
-    put(PLUGIN_KIND).to(InstallPlugin.Overwrite.class);
-    delete(PLUGIN_KIND).to(DisablePlugin.class);
-    get(PLUGIN_KIND, "status").to(GetStatus.class);
-    post(PLUGIN_KIND, "disable").to(DisablePlugin.class);
-    post(PLUGIN_KIND, "enable").to(EnablePlugin.class);
-    post(PLUGIN_KIND, "reload").to(ReloadPlugin.class);
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginRestApiModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginRestApiModule.java
new file mode 100644
index 0000000..6b52a59
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginRestApiModule.java
@@ -0,0 +1,35 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.plugins;
+
+import static com.google.gerrit.server.plugins.PluginResource.PLUGIN_KIND;
+
+import com.google.gerrit.extensions.registration.DynamicMap;
+import com.google.gerrit.extensions.restapi.RestApiModule;
+
+public class PluginRestApiModule extends RestApiModule {
+  @Override
+  protected void configure() {
+    install(new PluginModule());
+    bind(PluginsCollection.class);
+    DynamicMap.mapOf(binder(), PLUGIN_KIND);
+    put(PLUGIN_KIND).to(InstallPlugin.Overwrite.class);
+    delete(PLUGIN_KIND).to(DisablePlugin.class);
+    get(PLUGIN_KIND, "status").to(GetStatus.class);
+    post(PLUGIN_KIND, "disable").to(DisablePlugin.class);
+    post(PLUGIN_KIND, "enable").to(EnablePlugin.class);
+    post(PLUGIN_KIND, "reload").to(ReloadPlugin.class);
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/PerformCreateProject.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/PerformCreateProject.java
index 29317440..86410af 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/PerformCreateProject.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/PerformCreateProject.java
@@ -118,7 +118,11 @@
           }
         };
         for (NewProjectCreatedListener l : createdListener) {
-          l.onNewProjectCreated(event);
+          try {
+            l.onNewProjectCreated(event);
+          } catch (RuntimeException e) {
+            log.warn("Failure in NewProjectCreatedListener", e);
+          }
         }
 
         final RefUpdate u = repo.updateRef(Constants.HEAD);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/AndSource.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/AndSource.java
index 0555fc7..5282b49 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/AndSource.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/AndSource.java
@@ -162,12 +162,15 @@
   }
 
   private ChangeDataSource source() {
+    int minCost = Integer.MAX_VALUE;
+    Predicate<ChangeData> s = null;
     for (Predicate<ChangeData> p : getChildren()) {
-      if (p instanceof ChangeDataSource) {
-        return (ChangeDataSource) p;
+      if (p instanceof ChangeDataSource && p.getCost() < minCost) {
+        s = p;
+        minCost = p.getCost();
       }
     }
-    return null;
+    return (ChangeDataSource) s;
   }
 
   @Override
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeData.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeData.java
index ff44c73..4522fe6 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeData.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeData.java
@@ -71,8 +71,8 @@
     return SORT_APPROVALS.sortedCopy(approvals);
   }
 
-  public static void ensureChangeLoaded(
-      Provider<ReviewDb> db, List<ChangeData> changes) throws OrmException {
+  public static void ensureChangeLoaded(Provider<ReviewDb> db,
+      Iterable<ChangeData> changes) throws OrmException {
     Map<Change.Id, ChangeData> missing = Maps.newHashMap();
     for (ChangeData cd : changes) {
       if (cd.change == null) {
@@ -87,14 +87,14 @@
   }
 
   public static void ensureAllPatchSetsLoaded(Provider<ReviewDb> db,
-      List<ChangeData> changes) throws OrmException {
+      Iterable<ChangeData> changes) throws OrmException {
     for (ChangeData cd : changes) {
       cd.patches(db);
     }
   }
 
-  public static void ensureCurrentPatchSetLoaded(
-      Provider<ReviewDb> db, List<ChangeData> changes) throws OrmException {
+  public static void ensureCurrentPatchSetLoaded(Provider<ReviewDb> db,
+      Iterable<ChangeData> changes) throws OrmException {
     Map<PatchSet.Id, ChangeData> missing = Maps.newHashMap();
     for (ChangeData cd : changes) {
       if (cd.currentPatchSet == null && cd.patches == null) {
@@ -112,8 +112,8 @@
     }
   }
 
-  public static void ensureCurrentApprovalsLoaded(
-      Provider<ReviewDb> db, List<ChangeData> changes) throws OrmException {
+  public static void ensureCurrentApprovalsLoaded(Provider<ReviewDb> db,
+      Iterable<ChangeData> changes) throws OrmException {
     List<ResultSet<PatchSetApproval>> pending = Lists.newArrayList();
     for (ChangeData cd : changes) {
       if (cd.currentApprovals == null && cd.limitedApprovals == null) {
@@ -272,6 +272,10 @@
     return change;
   }
 
+  void setChange(Change c) {
+    change = c;
+  }
+
   public PatchSet currentPatchSet(Provider<ReviewDb> db) throws OrmException {
     if (currentPatchSet == null) {
       Change c = change(db);
@@ -307,6 +311,10 @@
     return currentApprovals;
   }
 
+  public void setCurrentApprovals(List<PatchSetApproval> approvals) {
+    currentApprovals = approvals;
+  }
+
   public String commitMessage(GitRepositoryManager repoManager,
       Provider<ReviewDb> db) throws IOException, OrmException {
     if (commitMessage == null) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryChanges.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryChanges.java
index 5084fff..76d873a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryChanges.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryChanges.java
@@ -20,12 +20,15 @@
 import com.google.gerrit.extensions.restapi.BadRequestException;
 import com.google.gerrit.extensions.restapi.RestReadView;
 import com.google.gerrit.extensions.restapi.TopLevelResource;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.change.ChangeJson;
 import com.google.gerrit.server.change.ChangeJson.ChangeInfo;
 import com.google.gerrit.server.project.ChangeControl;
 import com.google.gerrit.server.query.QueryParseException;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
+import com.google.inject.Provider;
 
 import org.kohsuke.args4j.Option;
 
@@ -39,6 +42,7 @@
 public class QueryChanges implements RestReadView<TopLevelResource> {
   private final ChangeJson json;
   private final QueryProcessor imp;
+  private final Provider<CurrentUser> user;
   private boolean reverse;
   private EnumSet<ListChangesOption> options;
 
@@ -80,9 +84,11 @@
   @Inject
   QueryChanges(ChangeJson json,
       QueryProcessor qp,
-      ChangeControl.Factory cf) {
+      ChangeControl.Factory cf,
+      Provider<CurrentUser> user) {
     this.json = json;
     this.imp = qp;
+    this.user = user;
 
     options = EnumSet.noneOf(ListChangesOption.class);
     json.setChangeControlFactory(cf);
@@ -127,12 +133,27 @@
       throw new QueryParseException("limit of 10 queries");
     }
 
+    IdentifiedUser self = null;
+    try {
+      if (user.get().isIdentifiedUser()) {
+        self = (IdentifiedUser) user.get();
+        self.asyncStarredChanges();
+      }
+      return query0();
+    } finally {
+      if (self != null) {
+        self.abortStarredChanges();
+      }
+    }
+  }
+
+  private List<List<ChangeInfo>> query0() throws OrmException,
+      QueryParseException {
     int cnt = queries.size();
     BitSet more = new BitSet(cnt);
-    List<List<ChangeData>> data = Lists.newArrayListWithCapacity(cnt);
+    List<List<ChangeData>> data = imp.queryChanges(queries);
     for (int n = 0; n < cnt; n++) {
-      String query = queries.get(n);
-      List<ChangeData> changes = imp.queryChanges(query);
+      List<ChangeData> changes = data.get(n);
       if (imp.getLimit() > 0 && changes.size() > imp.getLimit()) {
         if (reverse) {
           changes = changes.subList(1, changes.size());
@@ -141,7 +162,6 @@
         }
         more.set(n, true);
       }
-      data.add(changes);
     }
 
     List<List<ChangeInfo>> res = json.addOptions(options).formatList2(data);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryProcessor.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryProcessor.java
index 18ebdb1..bdce7f4 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryProcessor.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryProcessor.java
@@ -14,10 +14,11 @@
 
 package com.google.gerrit.server.query.change;
 
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
 import com.google.gerrit.common.data.GlobalCapability;
 import com.google.gerrit.common.data.LabelTypes;
 import com.google.gerrit.common.data.SubmitRecord;
-import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.PatchSet;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.CurrentUser;
@@ -32,6 +33,7 @@
 import com.google.gerrit.server.query.QueryParseException;
 import com.google.gson.Gson;
 import com.google.gwtorm.server.OrmException;
+import com.google.gwtorm.server.ResultSet;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
 
@@ -52,7 +54,6 @@
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.Date;
-import java.util.HashSet;
 import java.util.List;
 
 public class QueryProcessor {
@@ -211,47 +212,70 @@
    * there are more than {@code limit} matches and suggest to its own caller
    * that the query could be retried with {@link #setSortkeyBefore(String)}.
    */
-  public List<ChangeData> queryChanges(final String queryString)
+  public List<ChangeData> queryChanges(String queryString)
+      throws OrmException, QueryParseException {
+    return queryChanges(ImmutableList.of(queryString)).get(0);
+  }
+
+  /**
+   * Query for changes that match the query string.
+   * <p>
+   * If a limit was specified using {@link #setLimit(int)} this method may
+   * return up to {@code limit + 1} results, allowing the caller to determine if
+   * there are more than {@code limit} matches and suggest to its own caller
+   * that the query could be retried with {@link #setSortkeyBefore(String)}.
+   */
+  public List<List<ChangeData>> queryChanges(List<String> queries)
       throws OrmException, QueryParseException {
     final Predicate<ChangeData> visibleToMe = queryBuilder.is_visible();
-    Predicate<ChangeData> s = compileQuery(queryString, visibleToMe);
-    List<ChangeData> results = new ArrayList<ChangeData>();
-    HashSet<Change.Id> want = new HashSet<Change.Id>();
-    for (ChangeData d : ((ChangeDataSource) s).read()) {
-      if (d.hasChange()) {
-        // Checking visibleToMe here should be unnecessary, the
-        // query should have already performed it. But we don't
-        // want to trust the query rewriter that much yet.
-        //
-        if (visibleToMe.match(d)) {
-          results.add(d);
-        }
-      } else {
-        want.add(d.getId());
+    int cnt = queries.size();
+
+    // Parse and rewrite all queries.
+    List<Integer> limits = Lists.newArrayListWithCapacity(cnt);
+    List<ChangeDataSource> sources = Lists.newArrayListWithCapacity(cnt);
+    for (int i = 0; i < cnt; i++) {
+      Predicate<ChangeData> q = parseQuery(queries.get(i), visibleToMe);
+      Predicate<ChangeData> s = queryRewriter.rewrite(q);
+      if (!(s instanceof ChangeDataSource)) {
+        @SuppressWarnings("unchecked")
+        Predicate<ChangeData> o = Predicate.and(queryBuilder.status_open(), q);
+        q = o;
+        s = queryRewriter.rewrite(q);
       }
+      if (!(s instanceof ChangeDataSource)) {
+        throw new QueryParseException("invalid query: " + s);
+      }
+
+      // Don't trust QueryRewriter to have left the visible predicate.
+      AndSource a = new AndSource(db, ImmutableList.of(s, visibleToMe));
+      limits.add(limit(q));
+      sources.add(a);
     }
 
-    if (!want.isEmpty()) {
-      for (Change c : db.get().changes().get(want)) {
-        ChangeData d = new ChangeData(c);
-        if (visibleToMe.match(d)) {
-          results.add(d);
-        }
-      }
+    // Run each query asynchronously, if supported.
+    List<ResultSet<ChangeData>> matches = Lists.newArrayListWithCapacity(cnt);
+    for (ChangeDataSource s : sources) {
+      matches.add(s.read());
     }
+    sources = null;
 
-    Collections.sort(results, sortkeyAfter != null ? cmpAfter : cmpBefore);
-    int limit = limit(s);
-    if (results.size() > maxLimit) {
-      moreResults = true;
+    List<List<ChangeData>> out = Lists.newArrayListWithCapacity(cnt);
+    for (int i = 0; i < cnt; i++) {
+      List<ChangeData> results = matches.get(i).toList();
+      Collections.sort(results, sortkeyAfter != null ? cmpAfter : cmpBefore);
+      if (results.size() > maxLimit) {
+        moreResults = true;
+      }
+      int limit = limits.get(i);
+      if (limit < results.size()) {
+        results = results.subList(0, limit);
+      }
+      if (sortkeyAfter != null) {
+        Collections.reverse(results);
+      }
+      out.add(results);
     }
-    if (limit < results.size()) {
-      results = results.subList(0, limit);
-    }
-    if (sortkeyAfter != null) {
-      Collections.reverse(results);
-    }
-    return results;
+    return out;
   }
 
   public void query(String queryString) throws IOException {
@@ -378,9 +402,8 @@
   }
 
   @SuppressWarnings("unchecked")
-  private Predicate<ChangeData> compileQuery(String queryString,
+  private Predicate<ChangeData> parseQuery(String queryString,
       final Predicate<ChangeData> visibleToMe) throws QueryParseException {
-
     Predicate<ChangeData> q = queryBuilder.parse(queryString);
     if (!ChangeQueryBuilder.hasSortKey(q)) {
       if (sortkeyBefore != null) {
@@ -391,20 +414,9 @@
         q = Predicate.and(q, queryBuilder.sortkey_before("z"));
       }
     }
-    q = Predicate.and(q,
+    return Predicate.and(q,
         queryBuilder.limit(limit > 0 ? Math.min(limit, maxLimit) + 1 : maxLimit),
         visibleToMe);
-
-    Predicate<ChangeData> s = queryRewriter.rewrite(q);
-    if (!(s instanceof ChangeDataSource)) {
-      s = queryRewriter.rewrite(Predicate.and(queryBuilder.status_open(), q));
-    }
-
-    if (!(s instanceof ChangeDataSource)) {
-      throw new QueryParseException("invalid query: " + s);
-    }
-
-    return s;
   }
 
   private void show(Object data) {
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshLog.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshLog.java
index 5120bfd..8c17407 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshLog.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshLog.java
@@ -233,7 +233,7 @@
 
     String userName = "-", accountId = "-";
 
-    if (user.isIdentifiedUser()) {
+    if (user != null && user.isIdentifiedUser()) {
       IdentifiedUser u = (IdentifiedUser) user;
       userName = u.getAccount().getUserName();
       accountId = "a/" + u.getAccountId().toString();
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshScope.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshScope.java
index 70a8592..9b19e02 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshScope.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshScope.java
@@ -81,7 +81,7 @@
     @Override
     public CurrentUser getCurrentUser() {
       final CurrentUser user = session.getCurrentUser();
-      if (user.isIdentifiedUser()) {
+      if (user != null && user.isIdentifiedUser()) {
         IdentifiedUser identifiedUser = userFactory.create(((IdentifiedUser) user).getAccountId());
         identifiedUser.setAccessPath(user.getAccessPath());
         return identifiedUser;
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Receive.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Receive.java
index b3aad6f..cd50499 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Receive.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Receive.java
@@ -97,7 +97,6 @@
     rp.setMaxObjectSizeLimit(config.getEffectiveMaxObjectSizeLimit(
         projectControl.getProjectState()));
     try {
-      receive.advertiseHistory();
       rp.receive(in, out, err);
     } catch (UnpackException badStream) {
       // In case this was caused by the user pushing an object whose size
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowConnections.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowConnections.java
index 09ce920..c0e5d6e 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowConnections.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowConnections.java
@@ -146,7 +146,7 @@
     }
 
     final CurrentUser user = sd.getCurrentUser();
-    if (user.isIdentifiedUser()) {
+    if (user != null && user.isIdentifiedUser()) {
       IdentifiedUser u = (IdentifiedUser) user;
 
       if (!numeric) {
diff --git a/gerrit-war/src/main/java/com/google/gerrit/httpd/WebAppInitializer.java b/gerrit-war/src/main/java/com/google/gerrit/httpd/WebAppInitializer.java
index dd41f52..3c70cf6 100644
--- a/gerrit-war/src/main/java/com/google/gerrit/httpd/WebAppInitializer.java
+++ b/gerrit-war/src/main/java/com/google/gerrit/httpd/WebAppInitializer.java
@@ -44,7 +44,7 @@
 import com.google.gerrit.server.mail.SmtpEmailSender;
 import com.google.gerrit.server.patch.IntraLineWorkerPool;
 import com.google.gerrit.server.plugins.PluginGuiceEnvironment;
-import com.google.gerrit.server.plugins.PluginModule;
+import com.google.gerrit.server.plugins.PluginRestApiModule;
 import com.google.gerrit.server.schema.DataSourceModule;
 import com.google.gerrit.server.schema.DataSourceProvider;
 import com.google.gerrit.server.schema.DataSourceType;
@@ -257,7 +257,7 @@
     modules.add(new DefaultCacheFactory.Module());
     modules.add(new SmtpEmailSender.Module());
     modules.add(new SignedTokenEmailTokenVerifier.Module());
-    modules.add(new PluginModule());
+    modules.add(new PluginRestApiModule());
     AbstractModule changeIndexModule;
     switch (IndexModule.getIndexType(cfgInjector)) {
       case LUCENE:
diff --git a/lib/BUCK b/lib/BUCK
index 1a2681a..d9f1ecf 100644
--- a/lib/BUCK
+++ b/lib/BUCK
@@ -19,6 +19,7 @@
 define_license(name = 'ow2')
 define_license(name = 'postgresql')
 define_license(name = 'prologcafe')
+define_license(name = 'protobuf')
 define_license(name = 'slf4j')
 define_license(name = 'DO_NOT_DISTRIBUTE')
 
@@ -28,6 +29,7 @@
   bin_sha1 = 'ee3b316a023f1422dd4b6654a3d51d0e5690809c',
   src_sha1 = 'a145bde4cc87a4ff4cec283880e2a03be32cc868',
   license = 'Apache2.0',
+  deps = [':protobuf'],
   repository = GERRIT,
 )
 
@@ -205,6 +207,15 @@
 )
 
 maven_jar(
+  name = 'protobuf',
+  # Must match version in gwtorm/pom.xml.
+  id = 'com.google.protobuf:protobuf-java:2.4.1',
+  bin_sha1 = '0c589509ec6fd86d5d2fda37e07c08538235d3b9',
+  src_sha1 = 'e406f69360f2a89cb4aa724ed996a1c5599af383',
+  license = 'protobuf',
+)
+
+maven_jar(
   name = 'junit',
   id = 'junit:junit:4.11',
   sha1 = '4e031bb61df09069aeb2bffb4019e7a5034a4ee0',
diff --git a/lib/LICENSE-protobuf b/lib/LICENSE-protobuf
new file mode 100644
index 0000000..705db57
--- /dev/null
+++ b/lib/LICENSE-protobuf
@@ -0,0 +1,33 @@
+Copyright 2008, Google Inc.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+    * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+    * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+Code generated by the Protocol Buffer compiler is owned by the owner
+of the input file used when generating it.  This code is not
+standalone and requires a support library to be linked with it.  This
+support library is itself covered by the above license.
diff --git a/website/releases/index.html b/website/releases/index.html
index 27d4fb9..0ca0cb4 100644
--- a/website/releases/index.html
+++ b/website/releases/index.html
@@ -31,7 +31,7 @@
 
 <h1>Gerrit Code Review - Releases</h1>
 <a href="http://code.google.com/p/gerrit">
-<img id="diffy_logo" src="https://gerrit-review.googlesource.com/static/logo.png">
+  <img id="diffy_logo" src="https://gerrit-review.googlesource.com/static/diffy1.cache.png">
 </a>
 
 <div id='download_container'>