Merge "Provide easy way to access plugin configuration"
diff --git a/.buckconfig b/.buckconfig
index e60c7cd5..171acb5 100644
--- a/.buckconfig
+++ b/.buckconfig
@@ -15,3 +15,6 @@
 
 [java]
   src_roots = java, resources
+
+[project]
+  ignore = .git
diff --git a/Documentation/cmd-create-project.txt b/Documentation/cmd-create-project.txt
index 666fe91..e3ad834 100644
--- a/Documentation/cmd-create-project.txt
+++ b/Documentation/cmd-create-project.txt
@@ -109,6 +109,7 @@
 +
 * FAST_FORWARD_ONLY: produces a strictly linear history.
 * MERGE_IF_NECESSARY: create a merge commit when required.
+* REBASE_IF_NECESSARY: rebase the commit when required.
 * MERGE_ALWAYS: always create a merge commit.
 * CHERRY_PICK: always cherry-pick the commit.
 
diff --git a/Documentation/cmd-set-project.txt b/Documentation/cmd-set-project.txt
index 7ff534b..af20006 100644
--- a/Documentation/cmd-set-project.txt
+++ b/Documentation/cmd-set-project.txt
@@ -57,6 +57,7 @@
 +
 * FAST_FORWARD_ONLY: produces a strictly linear history.
 * MERGE_IF_NECESSARY: create a merge commit when required.
+* REBASE_IF_NECESSARY: rebase the commit when required.
 * MERGE_ALWAYS: always create a merge commit.
 * CHERRY_PICK: always cherry-pick the commit.
 
diff --git a/Documentation/config-auto-site-initialization.txt b/Documentation/config-auto-site-initialization.txt
new file mode 100644
index 0000000..4c204fd
--- /dev/null
+++ b/Documentation/config-auto-site-initialization.txt
@@ -0,0 +1,82 @@
+Gerrit Code Review - Automatic Site Initialization on Startup
+=============================================================
+
+Description
+-----------
+
+Gerrit supports automatic site initialization on server startup
+when Gerrit runs in a servlet container. Both creation of a new site
+and upgrade of an existing site are supported. Installation of
+plugins during the site creation/initialization is not yet supported.
+
+This feature may be useful for such setups where Gerrit administrators
+don't have direct access to the database and the file system of the
+server where Gerrit should be deployed and, therefore, cannot perform
+the init from their local machine prior to deploying Gerrit on such a
+server. It may also make deployment and testing in a local servlet
+container faster to setup as the init step could be skipped.
+
+Gerrit Configuration
+--------------------
+
+The site initialization will be performed only if the `gerrit.init`
+system property exists (the value of the property is not used, only the
+existence of the property matters).
+
+If the `gerrit.site_path` system property is defined then the init is
+run for that site. The database connectivity, in that case, is defined
+in the `etc/gerrit.config`.
+
+If `gerrit.site_path` is not defined then Gerrit will try to find an
+existing site by looking into the `system_config` table in the database
+defined via the `jdbc/ReviewDb` JNDI property. If the `system_config`
+table exists then the `site_path` from that table is used for the
+initialization. The database connectivity is defined by the
+`jdbc/ReviewDb` JNDI property.
+
+Finally, if neither the `gerrit.site_path` property nor the
+`system_config` table exists, the `gerrit.init_path` system property,
+if defined, will be used to determine the site path. The database
+connectivity, also for this case, is defined by the `jdbc/ReviewDb`
+JNDI property.
+
+Example 1
+~~~~~~~~~
+
+Prepare Tomcat so that a site is initialized at a given path using
+the H2 database (if the site doesn't exist yet) or using whatever
+database is defined in `etc/gerrit.config` of that site:
+
+----
+  $ export CATALINA_OPTS='-Dgerrit.init -Dgerrit.site_path=/path/to/site'
+  $ catalina.sh start
+----
+
+Example 2
+~~~~~~~~~
+
+Prepare Tomcat so that an existing site with the path defined in the
+`system_config` table is initialized (upgraded) on Gerrit startup. The
+assumption is that the `jdbc/ReviewDb` JNDI property is defined in
+Tomcat:
+
+----
+  $ export CATALINA_OPTS='-Dgerrit.init'
+  $ catalina.sh start
+----
+
+Example 3
+~~~~~~~~~
+
+Assuming the database schema doesn't exist in the database defined
+via the `jdbc/ReviewDb` JNDI property, initialize a new site using that
+database and a given path:
+
+----
+  $ export CATALINA_OPTS='-Dgerrit.init -Dgerrit.init_path=/path/to/site'
+  $ catalina.sh start
+----
+
+GERRIT
+------
+Part of link:index.html[Gerrit Code Review]
diff --git a/Documentation/config-labels.txt b/Documentation/config-labels.txt
index a1cf9ee..c3e09c4 100644
--- a/Documentation/config-labels.txt
+++ b/Documentation/config-labels.txt
@@ -244,6 +244,30 @@
 configuration for this label in child projects will be ignored. Defaults
 to true.
 
+[[label_branch]]
+`label.Label-Name.branch`
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+By default a given project's label applicable scope is all changes
+on all branches of this project and its child projects.
+
+Label's applicable scope can be branch specific via configuration.
+E.g. create a label `Video-Qualify` on parent project and configure
+the `branch` as:
+
+====
+  [label "Video-Qualify"]
+      branch = refs/heads/video-1.0/*
+      branch = refs/heads/video-1.1/Kino
+====
+
+Then *only* changes in above branch scope of parent project and child
+projects will be affected by `Video-Qualify`.
+
+NOTE: The `branch` is independent from the branch scope defined in `access`
+parts in `project.config` file. That means from the UI a user can always
+assign permissions for that label on a branch, but this permission is then
+ignored if the label doesn't apply for that branch.
 
 [[label_example]]
 Example
diff --git a/Documentation/dev-buck.txt b/Documentation/dev-buck.txt
index 7671767..74f56b3 100644
--- a/Documentation/dev-buck.txt
+++ b/Documentation/dev-buck.txt
@@ -37,6 +37,13 @@
   which buck
 ----
 
+If you plan to use the link:#buck-daemon[Buck daemon] add a symbolic
+link in `~/bin` to the buckd executable:
+
+----
+  ln -s `pwd`/bin/buckd ~/bin/
+----
+
 
 [[eclipse]]
 Eclipse Integration
@@ -289,7 +296,6 @@
  )
 ----
 
-
 Caching Build Results
 ~~~~~~~~~~~~~~~~~~~~~
 
@@ -306,6 +312,29 @@
   EOF
 ----
 
+[[buck-daemon]]
+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 c109294..87ecc16 100644
--- a/Documentation/dev-plugins.txt
+++ b/Documentation/dev-plugins.txt
@@ -255,6 +255,26 @@
 to package additional dependencies. Relocating (or renaming) classes
 should not be necessary due to the ClassLoader isolation.
 
+[[events]]
+Listening to Events
+-------------------
+
+Certain operations in Gerrit trigger events. Plugins may receive
+notifications of these events by implementing the corresponding
+listeners.
+
+* `com.google.gerrit.extensions.events.LifecycleListener`:
++
+Gerrit server startup and shutdown
+
+* `com.google.gerrit.extensions.events.NewProjectCreatedListener`:
++
+Project creation
+
+* `com.google.gerrit.extensions.events.ProjectDeletedListener`:
++
+Project deletion
+
 [[ssh]]
 SSH Commands
 ------------
diff --git a/Documentation/index.txt b/Documentation/index.txt
index 28e0184..ee76855 100644
--- a/Documentation/index.txt
+++ b/Documentation/index.txt
@@ -62,6 +62,7 @@
 .. link:dev-design.html[System Design]
 .. link:config-contact.html[User Contact Information]
 .. link:config-reverseproxy.html[Reverse Proxy]
+.. link:config-auto-site-initialization.html[Automatic Site Initialization on Startup]
 .. link:pgm-index.html[Server Side Administrative Tools]
 . Developer
 .. link:dev-readme.html[Developer Setup]
diff --git a/Documentation/install-j2ee.txt b/Documentation/install-j2ee.txt
index f6853c8..5ba8cb1 100644
--- a/Documentation/install-j2ee.txt
+++ b/Documentation/install-j2ee.txt
@@ -46,6 +46,9 @@
 from `'$site_path'/lib` into your servlet container's extensions
 directory so it's available to Gerrit Code Review.
 
+* ('Optional') link:config-auto-site-initialization.html[
+Configure Automatic Site Initialization on Startup]
+
 
 Jetty 7.x
 ---------
diff --git a/Documentation/install.txt b/Documentation/install.txt
index 8137f53..d80b197 100644
--- a/Documentation/install.txt
+++ b/Documentation/install.txt
@@ -146,6 +146,7 @@
 * link:config-themes.html[Themes]
 * link:config-gitweb.html[Gitweb Integration]
 * link:config-gerrit.html[Other System Settings]
+* link:config-auto-site-initialization.html[Automatic Site Initialization on Startup]
 
 
 [[anonymous_access]]
diff --git a/Documentation/prolog-cookbook.txt b/Documentation/prolog-cookbook.txt
index 80f92a3..b1710a2 100644
--- a/Documentation/prolog-cookbook.txt
+++ b/Documentation/prolog-cookbook.txt
@@ -303,11 +303,11 @@
 The prolog environment running the `submit_rule` is loaded with state describing the
 change that is being evaluated. The easiest way to load this state is to test your
 `submit_rule` against a real change on a running gerrit instance. The command
-link:cmd-test-submit-rule.html[test-submit-rule] loads a specific change and executes
+link:cmd-test-submit-rule.html[test-submit rule] loads a specific change and executes
 the `submit_rule`. It optionally reads the rule from from `stdin` to facilitate easy testing.
 
 ====
-  cat rules.pl | ssh gerrit_srv gerrit test-submit-rule I45e080b105a50a625cc8e1fb5b357c0bfabe6d68 -s
+  cat rules.pl | ssh gerrit_srv gerrit test-submit rule I45e080b105a50a625cc8e1fb5b357c0bfabe6d68 -s
 ====
 
 Prolog vs Gerrit plugin for project specific submit rules
diff --git a/Documentation/rest-api-changes.txt b/Documentation/rest-api-changes.txt
index 97ce2a1..65dd3a4 100644
--- a/Documentation/rest-api-changes.txt
+++ b/Documentation/rest-api-changes.txt
@@ -932,6 +932,44 @@
   blocked by Verified
 ----
 
+[[publish-draft-change]]
+Publish Draft Change
+~~~~~~~~~~~~~~~~~~~~
+[verse]
+'POST /changes/link:#change-id[\{change-id\}]/publish'
+
+Publishes a draft change.
+
+.Request
+----
+  POST /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/publish HTTP/1.0
+  Content-Type: application/json;charset=UTF-8
+----
+
+.Response
+----
+  HTTP/1.1 204 No Content
+----
+
+[[delete-draft-change]]
+Delete Draft Change
+~~~~~~~~~~~~~~~~~~~
+[verse]
+'DELETE /changes/link:#change-id[\{change-id\}]'
+
+Deletes a draft change.
+
+.Request
+----
+  DELETE /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940 HTTP/1.0
+  Content-Type: application/json;charset=UTF-8
+----
+
+.Response
+----
+  HTTP/1.1 204 No Content
+----
+
 [[reviewer-endpoints]]
 Reviewer Endpoints
 ------------------
@@ -1504,6 +1542,44 @@
   "revision 674ac754f91e64a0efb8087e59a176484bd534d1 is not current revision"
 ----
 
+[[publish-draft-revision]]
+Publish Draft Revision
+~~~~~~~~~~~~~~~~~~~~~~
+[verse]
+'POST /changes/link:#change-id[\{change-id\}]/revisions/link:#revision-id[\{revision-id\}]/publish'
+
+Publishes a draft revision.
+
+.Request
+----
+  POST /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/revisions/current/publish HTTP/1.0
+  Content-Type: application/json;charset=UTF-8
+----
+
+.Response
+----
+  HTTP/1.1 204 No Content
+----
+
+[[delete-draft-revision]]
+Delete Draft Revision
+~~~~~~~~~~~~~~~~~~~~~
+[verse]
+'DELETE /changes/link:#change-id[\{change-id\}]/revisions/link:#revision-id[\{revision-id\}]'
+
+Deletes a draft revision.
+
+.Request
+----
+  DELETE /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/revisions/674ac754f91e64a0efb8087e59a176484bd534d1 HTTP/1.0
+  Content-Type: application/json;charset=UTF-8
+----
+
+.Response
+----
+  HTTP/1.1 204 No Content
+----
+
 [[get-patch]]
 Get Patch
 ~~~~~~~~~
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/ReleaseNotes/ReleaseNotes-2.6.2.txt b/ReleaseNotes/ReleaseNotes-2.6.2.txt
index 2a7a7b3..af00a71 100644
--- a/ReleaseNotes/ReleaseNotes-2.6.2.txt
+++ b/ReleaseNotes/ReleaseNotes-2.6.2.txt
@@ -21,10 +21,6 @@
 If the title is not specified, the path of the dashboard config file
 is used as title.
 
-* link:https://code.google.com/p/gerrit/issues/detail?id=2010[Issue 2010]:
-Fix null-pointer exception when searching for changes with the query
-`owner:self`.
-
 * Properly handle double-click on external group in GroupTable.
 +
 Double-clicking on an external group opens the group's URL (if it
@@ -41,11 +37,15 @@
 * link:https://code.google.com/p/gerrit/issues/detail?id=1966[Issue 1966]:
 Fix Gerrit plugins under Tomcat by avoiding Guice static filter.
 
-* link:https://code.google.com/p/gerrit/issues/detail?id=2054[Issue 2054]:
-Expand capabilities of `ldap.groupMemberPattern`.
+* link:https://code.google.com/p/gerrit/issues/detail?id=2010[Issue 2010]:
+Fix null-pointer exception when searching for changes with the query
+`owner:self`.
 
 * link:https://code.google.com/p/gerrit/issues/detail?id=2039[Issue 2039]:
-Fix browser NPE when ChangeCache is incomplete.
+Fix browser null-pointer exception when ChangeCache is incomplete.
+
+* link:https://code.google.com/p/gerrit/issues/detail?id=2054[Issue 2054]:
+Expand capabilities of `ldap.groupMemberPattern`.
 
 * link:https://code.google.com/p/gerrit/issues/detail?id=2056[Issue 2056]:
 Display custom NoOp label score for open changes.
@@ -56,5 +56,12 @@
 * link:https://code.google.com/p/gerrit/issues/detail?id=2098[Issue 2098]:
 Fix re-enabling of disabled plugins.
 
+* link:https://code.google.com/p/gerrit/issues/detail?id=2127[Issue 2127]:
+Remove hard-coded documentation links from the admin page.
+
+* link:https://code.google.com/p/gerrit/issues/detail?id=2128[Issue 2128]:
+Fix null-pointer exception when deleting draft patch set when previous
+draft was already deleted.
+
 No other changes since 2.6.1.
 
diff --git a/ReleaseNotes/ReleaseNotes-2.7.txt b/ReleaseNotes/ReleaseNotes-2.7.txt
index eb95146..348b812 100644
--- a/ReleaseNotes/ReleaseNotes-2.7.txt
+++ b/ReleaseNotes/ReleaseNotes-2.7.txt
@@ -100,7 +100,7 @@
 +
 Teams that want to use Gerrit's submit strategies to handle contention on busy
 branches can use `%submit` to create a change and have it
-link:link:http://gerrit-documentation.googlecode.com/svn/Documentation/2.7/user-upload.html#auto_merge[
+link:http://gerrit-documentation.googlecode.com/svn/Documentation/2.7/user-upload.html#auto_merge[
 immediately submitted], if the caller has Submit permission on `refs/for/<ref>`.
 
 * Allow administrators to see all groups.
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/AbstractDaemonTest.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
index 3a95fc3..adb346f 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
@@ -14,20 +14,51 @@
 
 package com.google.gerrit.acceptance;
 
-import org.junit.After;
-import org.junit.Before;
+import org.eclipse.jgit.lib.Config;
+import org.junit.Rule;
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
 
 public abstract class AbstractDaemonTest {
   protected GerritServer server;
 
-  @Before
-  public final void beforeTest() throws Exception {
-    server = GerritServer.start();
+  @Rule
+  public TestRule testRunner = new TestRule() {
+    @Override
+    public Statement apply(final Statement base, final Description description) {
+      return new Statement() {
+        @Override
+        public void evaluate() throws Throwable {
+          beforeTest(config(description));
+          base.evaluate();
+          afterTest();
+        }
+      };
+    }
+  };
+
+  private static Config config(Description description) {
+    GerritConfigs cfgs = description.getAnnotation(GerritConfigs.class);
+    GerritConfig cfg = description.getAnnotation(GerritConfig.class);
+    if (cfgs != null && cfg != null) {
+      throw new IllegalStateException("Use either @GerritConfigs or @GerritConfig not both");
+    }
+    if (cfgs != null) {
+      return ConfigAnnotationParser.parse(cfgs);
+    } else if (cfg != null) {
+      return ConfigAnnotationParser.parse(cfg);
+    } else {
+      return null;
+    }
+  }
+
+  private void beforeTest(Config cfg) throws Exception {
+    server = GerritServer.start(cfg);
     server.getTestInjector().injectMembers(this);
   }
 
-  @After
-  public final void afterTest() throws Exception {
+  private void afterTest() throws Exception {
     server.stop();
   }
 }
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/ConfigAnnotationParser.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/ConfigAnnotationParser.java
new file mode 100644
index 0000000..cf60fb4
--- /dev/null
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/ConfigAnnotationParser.java
@@ -0,0 +1,58 @@
+// 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.acceptance;
+
+import com.google.common.base.Splitter;
+import com.google.common.collect.Lists;
+
+import org.eclipse.jgit.lib.Config;
+
+import java.util.ArrayList;
+
+class ConfigAnnotationParser {
+
+  private static Splitter splitter = Splitter.on(".").trimResults();
+
+  static Config parse(GerritConfigs annotation) {
+    if (annotation == null) {
+      return null;
+    }
+
+    Config cfg = new Config();
+    for (GerritConfig c : annotation.value()) {
+      parse(cfg, c);
+    }
+    return cfg;
+  }
+
+  static Config parse(GerritConfig annotation) {
+    Config cfg = new Config();
+    parse(cfg, annotation);
+    return cfg;
+  }
+
+  static private void parse(Config cfg, GerritConfig c) {
+    ArrayList<String> l = Lists.newArrayList(splitter.split(c.name()));
+    if (l.size() == 2) {
+      cfg.setString(l.get(0), null, l.get(1), c.value());
+    } else if (l.size() == 3) {
+      cfg.setString(l.get(0), l.get(1), l.get(2), c.value());
+    } else {
+      throw new IllegalArgumentException(
+          "GerritConfig.name must be of the format"
+              + " section.subsection.name or section.name");
+    }
+  }
+}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/GerritConfig.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/GerritConfig.java
new file mode 100644
index 0000000..5cb1229
--- /dev/null
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/GerritConfig.java
@@ -0,0 +1,28 @@
+// 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.acceptance;
+
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+@Target({METHOD})
+@Retention(RUNTIME)
+public @interface GerritConfig {
+  String name();
+  String value();
+}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/GerritConfigs.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/GerritConfigs.java
new file mode 100644
index 0000000..58bb9f2
--- /dev/null
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/GerritConfigs.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.acceptance;
+
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+@Target({METHOD})
+@Retention(RUNTIME)
+public @interface GerritConfigs {
+  public GerritConfig[] value();
+}
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..f97d2b9 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
@@ -23,6 +23,7 @@
 import com.google.inject.Module;
 
 import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.lib.Config;
 import org.eclipse.jgit.lib.RepositoryCache;
 import org.eclipse.jgit.storage.file.FileBasedConfig;
 import org.eclipse.jgit.util.FS;
@@ -42,11 +43,11 @@
 import java.util.concurrent.Executors;
 import java.util.concurrent.TimeUnit;
 
-class GerritServer {
+public class GerritServer {
 
   /** Returns fully started Gerrit server */
-  static GerritServer start() throws Exception {
-    final File site = initSite();
+  static GerritServer start(Config base) throws Exception {
+    final File site = initSite(base);
     final CyclicBarrier serverStarted = new CyclicBarrier(2);
     final Daemon daemon = new Daemon(new Runnable() {
       public void run() {
@@ -80,11 +81,12 @@
     return new GerritServer(site, i, daemon, daemonService);
   }
 
-  private static File initSite() throws Exception {
+  private static File initSite(Config base) throws Exception {
     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");
     }
@@ -92,10 +94,11 @@
     InetSocketAddress http = newPort();
     InetSocketAddress sshd = newPort();
     String url = "http://" + format(http) + "/";
-    FileBasedConfig cfg = new FileBasedConfig(
+    MergeableFileBasedConfig cfg = new MergeableFileBasedConfig(
         new File(new File(tmp, "etc"), "gerrit.config"),
         FS.DETECTED);
     cfg.load();
+    cfg.merge(base);
     cfg.setString("gerrit", null, "canonicalWebUrl", url);
     cfg.setString("httpd", null, "listenUrl", url);
     cfg.setString("sshd", null, "listenAddress", format(sshd));
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/MergeableFileBasedConfig.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/MergeableFileBasedConfig.java
new file mode 100644
index 0000000..f1baa9d
--- /dev/null
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/MergeableFileBasedConfig.java
@@ -0,0 +1,60 @@
+// 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.acceptance;
+
+import com.google.common.collect.Lists;
+
+import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.storage.file.FileBasedConfig;
+import org.eclipse.jgit.util.FS;
+
+import java.io.File;
+
+/**
+ * A file based Config that can merge another Config instance.
+ */
+public class MergeableFileBasedConfig extends FileBasedConfig {
+  public MergeableFileBasedConfig(File cfgLocation, FS fs) {
+    super(cfgLocation, fs);
+  }
+
+  /**
+   * Merge another Config into this Config.
+   *
+   * In case a configuration parameter exists both in this instance and in the
+   * merged instance then the value in this instance will simply replaced by
+   * the value from the merged instance.
+   *
+   * @param s Config to merge into this instance
+   */
+  public void merge(Config s) {
+    if (s == null) {
+      return;
+    }
+    for (String section : s.getSections()) {
+      for (String subsection : s.getSubsections(section)) {
+        for (String name : s.getNames(section, subsection)) {
+          setStringList(section, subsection, name, Lists.newArrayList(s
+              .getStringList(section, subsection, name)));
+        }
+      }
+
+      for (String name : s.getNames(section)) {
+        setStringList(section, null, name,
+            Lists.newArrayList(s.getStringList(section, null, name)));
+      }
+    }
+  }
+}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/UseGerritConfigAnnotationTest.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/UseGerritConfigAnnotationTest.java
new file mode 100644
index 0000000..0931e12
--- /dev/null
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/UseGerritConfigAnnotationTest.java
@@ -0,0 +1,46 @@
+// 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.acceptance;
+
+import static org.junit.Assert.assertEquals;
+
+import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.inject.Inject;
+
+import org.eclipse.jgit.lib.Config;
+import org.junit.Test;
+
+public class UseGerritConfigAnnotationTest extends AbstractDaemonTest {
+
+  @Inject
+  @GerritServerConfig
+  Config serverConfig;
+
+  @Test
+  @GerritConfig(name="x.y", value="z")
+  public void testOne() {
+    assertEquals("z", serverConfig.getString("x", null, "y"));
+  }
+
+  @Test
+  @GerritConfigs({
+      @GerritConfig(name="x.y", value="z"),
+      @GerritConfig(name="a.b", value="c"),
+  })
+  public void testMultiple() {
+    assertEquals("z", serverConfig.getString("x", null, "y"));
+    assertEquals("c", serverConfig.getString("a", null, "b"));
+  }
+}
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/LabelType.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/LabelType.java
index db8bde9..caf914d 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/LabelType.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/LabelType.java
@@ -103,6 +103,7 @@
   protected short maxPositive;
 
   private transient boolean canOverride;
+  private transient List<String> refPatterns;
   private transient List<Integer> intList;
   private transient Map<Short, LabelValue> byValue;
 
@@ -157,10 +158,18 @@
     return canOverride;
   }
 
+  public List<String> getRefPatterns() {
+    return refPatterns;
+  }
+
   public void setCanOverride(boolean canOverride) {
     this.canOverride = canOverride;
   }
 
+  public void setRefPatterns(List<String> refPatterns) {
+    this.refPatterns = refPatterns;
+  }
+
   public List<LabelValue> getValues() {
     return values;
   }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.properties b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.properties
index c767f84..4459bac 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.properties
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.properties
@@ -22,8 +22,8 @@
 projectRepoBrowser = Repository Browser
 useContentMerge = Automatically resolve conflicts
 useContributorAgreements = Require a valid contributor agreement to upload
-useSignedOffBy = Require <a href="http://gerrit-documentation.googlecode.com/svn/Documentation/2.5/user-signedoffby.html#Signed-off-by" target="_blank"><code>Signed-off-by</code></a> in commit message
-requireChangeID = Require <a href="http://gerrit-documentation.googlecode.com/svn/Documentation/2.5/user-changeid.html" target="_blank"><code>Change-Id</code></a> in commit message
+useSignedOffBy = Require <code>Signed-off-by</code> in commit message
+requireChangeID = Require <code>Change-Id</code> in commit message
 headingMaxObjectSizeLimit = Maximum Git object size limit
 headingGroupOptions = Group Options
 isVisibleToAll = Make group visible to all registered users.
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/restapi/RestApiServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/restapi/RestApiServlet.java
index d8a2648..6e8c6f6 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/restapi/RestApiServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/restapi/RestApiServlet.java
@@ -788,8 +788,12 @@
 
     List<String> p = splitProjection(projection);
     if (p.size() == 2) {
+      String viewname = p.get(1);
+      if (Strings.isNullOrEmpty(viewname)) {
+        viewname = "/";
+      }
       RestView<RestResource> view =
-          views.get(p.get(0), method + "." + p.get(1));
+          views.get(p.get(0), method + "." + viewname);
       if (view != null) {
         return new ViewData(p.get(0), view);
       }
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/IncludedInDetailFactory.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/IncludedInDetailFactory.java
index c07ee51..0c7df3e 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/IncludedInDetailFactory.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/IncludedInDetailFactory.java
@@ -20,6 +20,7 @@
 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.change.IncludedInResolver;
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.project.ChangeControl;
 import com.google.gerrit.server.project.NoSuchChangeException;
@@ -29,23 +30,15 @@
 
 import org.eclipse.jgit.errors.IncorrectObjectTypeException;
 import org.eclipse.jgit.errors.MissingObjectException;
-import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.Ref;
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.revwalk.RevCommit;
 import org.eclipse.jgit.revwalk.RevWalk;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
 
 /** Creates a {@link IncludedInDetail} of a {@link Change}. */
 class IncludedInDetailFactory extends Handler<IncludedInDetail> {
-  private static final Logger log =
-      LoggerFactory.getLogger(IncludedInDetailFactory.class);
 
   interface Factory {
     IncludedInDetailFactory create(Change.Id id);
@@ -56,7 +49,6 @@
   private final GitRepositoryManager repoManager;
   private final Change.Id changeId;
 
-  private IncludedInDetail detail;
   private ChangeControl control;
 
   @Inject
@@ -92,11 +84,7 @@
           throw new InvalidRevisionException();
         }
 
-        detail = new IncludedInDetail();
-        detail.setBranches(includedIn(repo, rw, rev, Constants.R_HEADS));
-        detail.setTags(includedIn(repo, rw, rev, Constants.R_TAGS));
-
-        return detail;
+        return IncludedInResolver.resolve(repo, rw, rev);
       } finally {
         rw.release();
       }
@@ -104,32 +92,4 @@
       repo.close();
     }
   }
-
-  private List<String> includedIn(final Repository repo, final RevWalk rw,
-      final RevCommit rev, final String namespace) throws IOException,
-      MissingObjectException, IncorrectObjectTypeException {
-    final List<String> result = new ArrayList<String>();
-    for (final Ref ref : repo.getRefDatabase().getRefs(namespace).values()) {
-      final RevCommit tip;
-      try {
-        tip = rw.parseCommit(ref.getObjectId());
-      } catch (IncorrectObjectTypeException notCommit) {
-        // Its OK for a tag reference to point to a blob or a tree, this
-        // is common in the Linux kernel or git.git repository.
-        //
-        continue;
-      } catch (MissingObjectException notHere) {
-        // Log the problem with this branch, but keep processing.
-        //
-        log.warn("Reference " + ref.getName() + " in " + repo.getDirectory()
-            + " points to dangling object " + ref.getObjectId());
-        continue;
-      }
-
-      if (rw.isMergedInto(rev, tip)) {
-        result.add(ref.getName().substring(namespace.length()));
-      }
-    }
-    return result;
-  }
-}
+}
\ No newline at end of file
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 286565b..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
@@ -48,6 +48,7 @@
 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;
@@ -112,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",
diff --git a/gerrit-pgm/BUCK b/gerrit-pgm/BUCK
index 2a88694..38480bc 100644
--- a/gerrit-pgm/BUCK
+++ b/gerrit-pgm/BUCK
@@ -1,4 +1,6 @@
-INIT_SRCS = ['src/main/java/com/google/gerrit/pgm/' + n for n in [
+SRCS = 'src/main/java/com/google/gerrit/pgm/'
+
+INIT_API_SRCS = [SRCS + n for n in [
   'init/InitFlags.java',
   'init/InitStep.java',
   'init/InitStep.java',
@@ -10,7 +12,7 @@
 
 java_library(
   name = 'init-api',
-  srcs = INIT_SRCS,
+  srcs = INIT_API_SRCS,
   deps = [
     '//gerrit-server:server',
     '//lib:jsr305',
@@ -23,16 +25,65 @@
 
 java_sources(
   name = 'init-api-src',
-  srcs = INIT_SRCS,
+  srcs = INIT_API_SRCS,
   visibility = ['PUBLIC'],
 )
 
+INIT_BASE_SRCS = [SRCS + 'BaseInit.java'] + glob(
+    [SRCS + n for n in [
+      'init/**/*.java',
+      'util/**/*.java',
+    ]],
+    excludes = INIT_API_SRCS +
+      [SRCS + n for n in [
+        'init/Browser.java',
+        'util/ErrorLogFile.java',
+        'util/GarbageCollectionLogFile.java',
+        'util/LogFileCompressor.java',
+        'util/RuntimeShutdown.java',
+      ]]
+  )
+
+INIT_BASE_RSRCS = ['src/main/resources/com/google/gerrit/pgm/libraries.config']
+
+java_library2(
+  name = 'init-base',
+  srcs = INIT_BASE_SRCS,
+  resources = INIT_BASE_RSRCS,
+  deps = [
+    ':init-api',
+    '//gerrit-common:server',
+    '//gerrit-extension-api:api',
+    '//gerrit-reviewdb:server',
+    '//gerrit-server:server',
+    '//gerrit-util-cli:cli',
+    '//lib/commons:dbcp',
+    '//lib/guice:guice',
+    '//lib/guice:guice-assistedinject',
+    '//lib/jgit:jgit',
+    '//lib/mina:sshd',
+    '//lib:args4j',
+    '//lib:guava',
+    '//lib:gwtjsonrpc',
+    '//lib:gwtorm',
+    '//lib:jsr305',
+  ],
+  compile_deps = ['//gerrit-launcher:launcher'],
+  visibility = ['//gerrit-war:'],
+)
+
 java_library2(
   name = 'pgm',
-  srcs = glob(['src/main/java/**/*.java'], excludes = INIT_SRCS),
-  resources = glob(['src/main/resources/**/*']),
+  srcs = glob(
+    ['src/main/java/**/*.java'],
+    excludes = INIT_API_SRCS + INIT_BASE_SRCS
+  ),
+  resources = glob(
+    ['src/main/resources/**/*'],
+    excludes = INIT_BASE_RSRCS),
   deps = [
     ':init-api',
+    ':init-base',
     '//gerrit-cache-h2:cache-h2',
     '//gerrit-common:server',
     '//gerrit-extension-api:api',
@@ -49,13 +100,9 @@
     '//lib:args4j',
     '//lib:guava',
     '//lib:gwtorm',
-    '//lib:gwtjsonrpc',
     '//lib:h2',
-    '//lib:jsr305',
     '//lib:servlet-api-3_0',
-    '//lib/commons:dbcp',
     '//lib/guice:guice',
-    '//lib/guice:guice-assistedinject',
     '//lib/guice:guice-servlet',
     '//lib/jetty:server',
     '//lib/jetty:servlet',
@@ -63,7 +110,6 @@
     '//lib/log:api',
     '//lib/log:log4j',
     '//lib/lucene:core',
-    '//lib/mina:sshd',
     '//lib/prolog:prolog-cafe',
   ],
   compile_deps = ['//gerrit-launcher:launcher'],
@@ -80,6 +126,7 @@
   srcs = glob(['src/test/java/**/*.java']),
   deps = [
     ':init-api',
+    ':init-base',
     ':pgm',
     '//gerrit-server:server',
     '//lib:junit',
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/BaseInit.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/BaseInit.java
new file mode 100644
index 0000000..b830959
--- /dev/null
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/BaseInit.java
@@ -0,0 +1,291 @@
+// 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.pgm;
+
+import static com.google.gerrit.server.schema.DataSourceProvider.Context.SINGLE_USER;
+import static com.google.inject.Stage.PRODUCTION;
+
+import com.google.common.base.Objects;
+import com.google.common.collect.Lists;
+import com.google.gerrit.pgm.init.InitFlags;
+import com.google.gerrit.pgm.init.InitModule;
+import com.google.gerrit.pgm.init.InstallPlugins;
+import com.google.gerrit.pgm.init.SitePathInitializer;
+import com.google.gerrit.pgm.util.ConsoleUI;
+import com.google.gerrit.pgm.util.Die;
+import com.google.gerrit.pgm.util.SiteProgram;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.config.SitePath;
+import com.google.gerrit.server.config.SitePaths;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.schema.SchemaUpdater;
+import com.google.gerrit.server.schema.UpdateUI;
+import com.google.gwtorm.jdbc.JdbcExecutor;
+import com.google.gwtorm.jdbc.JdbcSchema;
+import com.google.gwtorm.server.OrmException;
+import com.google.gwtorm.server.SchemaFactory;
+import com.google.gwtorm.server.StatementExecutor;
+import com.google.inject.AbstractModule;
+import com.google.inject.CreationException;
+import com.google.inject.Guice;
+import com.google.inject.Inject;
+import com.google.inject.Injector;
+import com.google.inject.Module;
+import com.google.inject.Provider;
+import com.google.inject.TypeLiteral;
+import com.google.inject.spi.Message;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.sql.DataSource;
+
+/** Initialize a new Gerrit installation. */
+public class BaseInit extends SiteProgram {
+
+  private final boolean standalone;
+
+  public BaseInit() {
+    this.standalone = true;
+  }
+
+  public BaseInit(File sitePath, boolean standalone) {
+    this(sitePath, null, standalone);
+  }
+
+  public BaseInit(File sitePath, final Provider<DataSource> dsProvider,
+      boolean standalone) {
+    super(sitePath, dsProvider);
+    this.standalone = standalone;
+  }
+
+  @Override
+  public int run() throws Exception {
+    final SiteInit init = createSiteInit();
+    beforeInit(init);
+
+    init.flags.autoStart = getAutoStart() && init.site.isNew;;
+
+    final SiteRun run;
+    try {
+      init.initializer.run();
+      init.flags.deleteOnFailure = false;
+
+      run = createSiteRun(init);
+      run.upgradeSchema();
+    } catch (Exception failure) {
+      if (init.flags.deleteOnFailure) {
+        recursiveDelete(getSitePath());
+      }
+      throw failure;
+    } catch (Error failure) {
+      if (init.flags.deleteOnFailure) {
+        recursiveDelete(getSitePath());
+      }
+      throw failure;
+    }
+
+    System.err.println("Initialized " + getSitePath().getCanonicalPath());
+    afterInit(run);
+    return 0;
+  }
+
+  protected void beforeInit(SiteInit init) throws Exception {
+  }
+
+  protected void afterInit(SiteRun run) throws Exception {
+  }
+
+  protected List<String> getInstallPlugins() {
+    return null;
+  }
+
+  protected boolean getAutoStart() {
+    return false;
+  }
+
+  static class SiteInit {
+    final SitePaths site;
+    final InitFlags flags;
+    final ConsoleUI ui;
+    final SitePathInitializer initializer;
+
+    @Inject
+    SiteInit(final SitePaths site, final InitFlags flags, final ConsoleUI ui,
+        final SitePathInitializer initializer) {
+      this.site = site;
+      this.flags = flags;
+      this.ui = ui;
+      this.initializer = initializer;
+    }
+  }
+
+  private SiteInit createSiteInit() {
+    final ConsoleUI ui = getConsoleUI();
+    final File sitePath = getSitePath();
+    final List<Module> m = new ArrayList<Module>();
+
+    m.add(new InitModule(standalone));
+    m.add(new AbstractModule() {
+      @Override
+      protected void configure() {
+        bind(ConsoleUI.class).toInstance(ui);
+        bind(File.class).annotatedWith(SitePath.class).toInstance(sitePath);
+        List<String> plugins =
+            Objects.firstNonNull(getInstallPlugins(), Lists.<String> newArrayList());
+        bind(new TypeLiteral<List<String>>() {}).annotatedWith(
+            InstallPlugins.class).toInstance(plugins);
+      }
+    });
+
+    try {
+      return Guice.createInjector(PRODUCTION, m).getInstance(SiteInit.class);
+    } catch (CreationException ce) {
+      final Message first = ce.getErrorMessages().iterator().next();
+      Throwable why = first.getCause();
+
+      if (why instanceof Die) {
+        throw (Die) why;
+      }
+
+      final StringBuilder buf = new StringBuilder(ce.getMessage());
+      while (why != null) {
+        buf.append("\n");
+        buf.append(why.getMessage());
+        why = why.getCause();
+        if (why != null) {
+          buf.append("\n  caused by ");
+        }
+      }
+      throw die(buf.toString(), new RuntimeException("InitInjector failed", ce));
+    }
+  }
+
+  protected ConsoleUI getConsoleUI() {
+    return ConsoleUI.getInstance(false);
+  }
+
+  static class SiteRun {
+    final ConsoleUI ui;
+    final SitePaths site;
+    final InitFlags flags;
+    final SchemaUpdater schemaUpdater;
+    final SchemaFactory<ReviewDb> schema;
+    final GitRepositoryManager repositoryManager;
+
+    @Inject
+    SiteRun(final ConsoleUI ui, final SitePaths site, final InitFlags flags,
+        final SchemaUpdater schemaUpdater,
+        final SchemaFactory<ReviewDb> schema,
+        final GitRepositoryManager repositoryManager) {
+      this.ui = ui;
+      this.site = site;
+      this.flags = flags;
+      this.schemaUpdater = schemaUpdater;
+      this.schema = schema;
+      this.repositoryManager = repositoryManager;
+    }
+
+    void upgradeSchema() throws OrmException {
+      final List<String> pruneList = new ArrayList<String>();
+      schemaUpdater.update(new UpdateUI() {
+        @Override
+        public void message(String msg) {
+          System.err.println(msg);
+          System.err.flush();
+        }
+
+        @Override
+        public boolean yesno(boolean def, String msg) {
+          return ui.yesno(def, msg);
+        }
+
+        @Override
+        public boolean isBatch() {
+          return ui.isBatch();
+        }
+
+        @Override
+        public void pruneSchema(StatementExecutor e, List<String> prune) {
+          for (String p : prune) {
+            if (!pruneList.contains(p)) {
+              pruneList.add(p);
+            }
+          }
+        }
+      });
+
+      if (!pruneList.isEmpty()) {
+        StringBuilder msg = new StringBuilder();
+        msg.append("Execute the following SQL to drop unused objects:\n");
+        msg.append("\n");
+        for (String sql : pruneList) {
+          msg.append("  ");
+          msg.append(sql);
+          msg.append(";\n");
+        }
+
+        if (ui.isBatch()) {
+          System.err.print(msg);
+          System.err.flush();
+
+        } else if (ui.yesno(true, "%s\nExecute now", msg)) {
+          final JdbcSchema db = (JdbcSchema) schema.open();
+          try {
+            final JdbcExecutor e = new JdbcExecutor(db);
+            try {
+              for (String sql : pruneList) {
+                e.execute(sql);
+              }
+            } finally {
+              e.close();
+            }
+          } finally {
+            db.close();
+          }
+        }
+      }
+    }
+  }
+
+  private SiteRun createSiteRun(final SiteInit init) {
+    return createSysInjector(init).getInstance(SiteRun.class);
+  }
+
+  private Injector createSysInjector(final SiteInit init) {
+    final List<Module> modules = new ArrayList<Module>();
+    modules.add(new AbstractModule() {
+      @Override
+      protected void configure() {
+        bind(ConsoleUI.class).toInstance(init.ui);
+        bind(InitFlags.class).toInstance(init.flags);
+      }
+    });
+    return createDbInjector(SINGLE_USER).createChildInjector(modules);
+  }
+
+  private static void recursiveDelete(File path) {
+    File[] entries = path.listFiles();
+    if (entries != null) {
+      for (File e : entries) {
+        recursiveDelete(e);
+      }
+    }
+    if (!path.delete() && path.exists()) {
+      System.err.println("warn: Cannot remove " + path);
+    }
+  }
+}
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..1cab515 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
@@ -14,338 +14,161 @@
 
 package com.google.gerrit.pgm;
 
-import static com.google.gerrit.server.schema.DataSourceProvider.Context.SINGLE_USER;
-import static com.google.inject.Stage.PRODUCTION;
-
 import com.google.common.base.Function;
 import com.google.common.base.Joiner;
-import com.google.common.base.Objects;
 import com.google.common.collect.Lists;
 import com.google.gerrit.common.PageLinks;
 import com.google.gerrit.pgm.init.Browser;
-import com.google.gerrit.pgm.init.InitFlags;
-import com.google.gerrit.pgm.init.InitModule;
 import com.google.gerrit.pgm.init.InitPlugins;
 import com.google.gerrit.pgm.init.InitPlugins.PluginData;
-import com.google.gerrit.pgm.init.InstallPlugins;
-import com.google.gerrit.pgm.init.SitePathInitializer;
 import com.google.gerrit.pgm.util.ConsoleUI;
-import com.google.gerrit.pgm.util.Die;
 import com.google.gerrit.pgm.util.ErrorLogFile;
 import com.google.gerrit.pgm.util.IoUtil;
-import com.google.gerrit.pgm.util.SiteProgram;
-import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.config.GerritServerConfigModule;
 import com.google.gerrit.server.config.SitePath;
-import com.google.gerrit.server.config.SitePaths;
-import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gerrit.server.schema.SchemaUpdater;
-import com.google.gerrit.server.schema.UpdateUI;
 import com.google.gerrit.server.util.HostPlatform;
-import com.google.gwtorm.jdbc.JdbcExecutor;
-import com.google.gwtorm.jdbc.JdbcSchema;
-import com.google.gwtorm.server.OrmException;
-import com.google.gwtorm.server.SchemaFactory;
-import com.google.gwtorm.server.StatementExecutor;
 import com.google.inject.AbstractModule;
-import com.google.inject.CreationException;
 import com.google.inject.Guice;
 import com.google.inject.Inject;
-import com.google.inject.Injector;
 import com.google.inject.Module;
-import com.google.inject.TypeLiteral;
-import com.google.inject.spi.Message;
+import com.google.inject.Provider;
 
 import org.kohsuke.args4j.Option;
 
 import java.io.File;
 import java.io.IOException;
-import java.util.ArrayList;
 import java.util.List;
+import java.util.ArrayList;
+
+import javax.sql.DataSource;
 
 /** Initialize a new Gerrit installation. */
-public class Init extends SiteProgram {
+public class Init extends BaseInit {
   @Option(name = "--batch", usage = "Batch mode; skip interactive prompting")
   private boolean batchMode;
 
   @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;
 
   @Option(name = "--install-plugin", usage = "Install given plugin without asking", multiValued = true)
   private List<String> installPlugins;
 
+  @Inject
+  Browser browser;
+
   public Init() {
   }
 
   public Init(File sitePath) {
-    super(sitePath);
+    this(sitePath, null);
+  }
+
+  public Init(File sitePath, final Provider<DataSource> dsProvider) {
+    super(sitePath, dsProvider, true);
     batchMode = true;
     noAutoStart = true;
   }
 
   @Override
-  public int run() throws Exception {
+  protected void beforeInit(SiteInit init) throws Exception {
     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);
-        }
-      } else {
-        ui.message("No plugins found.\n");
-      }
-      return 0;
-    }
-
-    init.flags.autoStart = !noAutoStart && init.site.isNew;
-
-    final SiteRun run;
-    try {
-      init.initializer.run();
-      init.flags.deleteOnFailure = false;
-
-      run = createSiteRun(init);
-      run.upgradeSchema();
-    } catch (Exception failure) {
-      if (init.flags.deleteOnFailure) {
-        recursiveDelete(getSitePath());
-      }
-      throw failure;
-    } catch (Error failure) {
-      if (init.flags.deleteOnFailure) {
-        recursiveDelete(getSitePath());
-      }
-      throw failure;
-    }
-
-    System.err.println("Initialized " + getSitePath().getCanonicalPath());
-    run.start();
-    return 0;
-  }
-
-  static class SiteInit {
-    final SitePaths site;
-    final InitFlags flags;
-    final ConsoleUI ui;
-    final SitePathInitializer initializer;
-
-    @Inject
-    SiteInit(final SitePaths site, final InitFlags flags, final ConsoleUI ui,
-        final SitePathInitializer initializer) {
-      this.site = site;
-      this.flags = flags;
-      this.ui = ui;
-      this.initializer = initializer;
-    }
-  }
-
-  private SiteInit createSiteInit() {
-    final ConsoleUI ui = ConsoleUI.getInstance(batchMode);
-    final File sitePath = getSitePath();
-    final List<Module> m = new ArrayList<Module>();
-
-    m.add(new InitModule());
-    m.add(new AbstractModule() {
-      @Override
-      protected void configure() {
-        bind(ConsoleUI.class).toInstance(ui);
-        bind(File.class).annotatedWith(SitePath.class).toInstance(sitePath);
-        List<String> plugins =
-            Objects.firstNonNull(installPlugins, Lists.<String> newArrayList());
-        bind(new TypeLiteral<List<String>>() {}).annotatedWith(
-            InstallPlugins.class).toInstance(plugins);
-      }
-    });
-
-    try {
-      return Guice.createInjector(PRODUCTION, m).getInstance(SiteInit.class);
-    } catch (CreationException ce) {
-      final Message first = ce.getErrorMessages().iterator().next();
-      Throwable why = first.getCause();
-
-      if (why instanceof Die) {
-        throw (Die) why;
-      }
-
-      final StringBuilder buf = new StringBuilder(ce.getMessage());
-      while (why != null) {
-        buf.append("\n");
-        buf.append(why.getMessage());
-        why = why.getCause();
-        if (why != null) {
-          buf.append("\n  caused by ");
-        }
-      }
-      throw die(buf.toString(), new RuntimeException("InitInjector failed", ce));
-    }
-  }
-
-  static class SiteRun {
-    final ConsoleUI ui;
-    final SitePaths site;
-    final InitFlags flags;
-    final SchemaUpdater schemaUpdater;
-    final SchemaFactory<ReviewDb> schema;
-    final GitRepositoryManager repositoryManager;
-    final Browser browser;
-
-    @Inject
-    SiteRun(final ConsoleUI ui, final SitePaths site, final InitFlags flags,
-        final SchemaUpdater schemaUpdater,
-        final SchemaFactory<ReviewDb> schema,
-        final GitRepositoryManager repositoryManager,
-        final Browser browser) {
-      this.ui = ui;
-      this.site = site;
-      this.flags = flags;
-      this.schemaUpdater = schemaUpdater;
-      this.schema = schema;
-      this.repositoryManager = repositoryManager;
-      this.browser = browser;
-    }
-
-    void upgradeSchema() throws OrmException {
-      final List<String> pruneList = new ArrayList<String>();
-      schemaUpdater.update(new UpdateUI() {
-        @Override
-        public void message(String msg) {
-          System.err.println(msg);
-          System.err.flush();
-        }
-
-        @Override
-        public boolean yesno(boolean def, String msg) {
-          return ui.yesno(def, msg);
-        }
-
-        @Override
-        public boolean isBatch() {
-          return ui.isBatch();
-        }
-
-        @Override
-        public void pruneSchema(StatementExecutor e, List<String> prune) {
-          for (String p : prune) {
-            if (!pruneList.contains(p)) {
-              pruneList.add(p);
-            }
+    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);
           }
-        }
-      });
-
-      if (!pruneList.isEmpty()) {
-        StringBuilder msg = new StringBuilder();
-        msg.append("Execute the following SQL to drop unused objects:\n");
-        msg.append("\n");
-        for (String sql : pruneList) {
-          msg.append("  ");
-          msg.append(sql);
-          msg.append(";\n");
-        }
-
-        if (ui.isBatch()) {
-          System.err.print(msg);
-          System.err.flush();
-
-        } else if (ui.yesno(true, "%s\nExecute now", msg)) {
-          final JdbcSchema db = (JdbcSchema) schema.open();
-          try {
-            final JdbcExecutor e = new JdbcExecutor(db);
-            try {
-              for (String sql : pruneList) {
-                e.execute(sql);
-              }
-            } finally {
-              e.close();
-            }
-          } finally {
-            db.close();
-          }
-        }
-      }
-    }
-
-    void start() throws Exception {
-      if (flags.autoStart) {
-        if (HostPlatform.isWin32()) {
-          System.err.println("Automatic startup not supported on Win32.");
-
         } else {
-          startDaemon();
-          if (!ui.isBatch()) {
-            browser.open(PageLinks.ADMIN_PROJECTS);
-          }
+          ui.message("No plugins found.\n");
         }
       }
     }
-
-    void startDaemon() {
-      final String[] argv = {site.gerrit_sh.getAbsolutePath(), "start"};
-      final Process proc;
-      try {
-        System.err.println("Executing " + argv[0] + " " + argv[1]);
-        proc = Runtime.getRuntime().exec(argv);
-      } catch (IOException e) {
-        System.err.println("error: cannot start Gerrit: " + e.getMessage());
-        return;
-      }
-
-      try {
-        proc.getOutputStream().close();
-      } catch (IOException e) {
-      }
-
-      IoUtil.copyWithThread(proc.getInputStream(), System.err);
-      IoUtil.copyWithThread(proc.getErrorStream(), System.err);
-
-      for (;;) {
-        try {
-          final int rc = proc.waitFor();
-          if (rc != 0) {
-            System.err.println("error: cannot start Gerrit: exit status " + rc);
-          }
-          break;
-        } catch (InterruptedException e) {
-          // retry
-        }
-      }
-    }
-
   }
 
-  private SiteRun createSiteRun(final SiteInit init) {
-    return createSysInjector(init).getInstance(SiteRun.class);
-  }
-
-  private Injector createSysInjector(final SiteInit init) {
-    final List<Module> modules = new ArrayList<Module>();
+  @Override
+  protected void afterInit(SiteRun run) throws Exception {
+    List<Module> modules = Lists.newArrayList();
     modules.add(new AbstractModule() {
       @Override
       protected void configure() {
-        bind(ConsoleUI.class).toInstance(init.ui);
-        bind(InitFlags.class).toInstance(init.flags);
+        bind(File.class).annotatedWith(SitePath.class).toInstance(getSitePath());
+        bind(Browser.class);
       }
     });
-    return createDbInjector(SINGLE_USER).createChildInjector(modules);
+    modules.add(new GerritServerConfigModule());
+    Guice.createInjector(modules).injectMembers(this);
+    start(run);
   }
 
-  private static void recursiveDelete(File path) {
-    File[] entries = path.listFiles();
-    if (entries != null) {
-      for (File e : entries) {
-        recursiveDelete(e);
+  @Override
+  protected List<String> getInstallPlugins() {
+    return installPlugins;
+  }
+
+  @Override
+  protected ConsoleUI getConsoleUI() {
+    return ConsoleUI.getInstance(batchMode);
+  }
+
+  @Override
+  protected boolean getAutoStart() {
+    return !noAutoStart;
+  }
+
+  void start(SiteRun run) throws Exception {
+    if (run.flags.autoStart) {
+      if (HostPlatform.isWin32()) {
+        System.err.println("Automatic startup not supported on Win32.");
+
+      } else {
+        startDaemon(run);
+        if (!run.ui.isBatch()) {
+          browser.open(PageLinks.ADMIN_PROJECTS);
+        }
       }
     }
-    if (!path.delete() && path.exists()) {
-      System.err.println("warn: Cannot remove " + path);
+  }
+
+  void startDaemon(SiteRun run) {
+    final String[] argv = {run.site.gerrit_sh.getAbsolutePath(), "start"};
+    final Process proc;
+    try {
+      System.err.println("Executing " + argv[0] + " " + argv[1]);
+      proc = Runtime.getRuntime().exec(argv);
+    } catch (IOException e) {
+      System.err.println("error: cannot start Gerrit: " + e.getMessage());
+      return;
+    }
+
+    try {
+      proc.getOutputStream().close();
+    } catch (IOException e) {
+    }
+
+    IoUtil.copyWithThread(proc.getInputStream(), System.err);
+    IoUtil.copyWithThread(proc.getErrorStream(), System.err);
+
+    for (;;) {
+      try {
+        final int rc = proc.waitFor();
+        if (rc != 0) {
+          System.err.println("error: cannot start Gerrit: exit status " + rc);
+        }
+        break;
+      } catch (InterruptedException e) {
+        // retry
+      }
     }
   }
 
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/InitModule.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitModule.java
index 8b3d87e..94185eb 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitModule.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitModule.java
@@ -23,6 +23,13 @@
 
 /** Injection configuration for the site initialization process. */
 public class InitModule extends FactoryModule {
+
+  private final boolean standalone;
+
+  public InitModule(boolean standalone) {
+    this.standalone = standalone;
+  }
+
   @Override
   protected void configure() {
     bind(SitePaths.class);
@@ -36,14 +43,20 @@
     step().to(UpgradeFrom2_0_x.class);
 
     step().to(InitGitManager.class);
-    step().to(InitDatabase.class);
+    if (standalone) {
+      step().to(InitDatabase.class);
+    }
     step().to(InitAuth.class);
     step().to(InitSendEmail.class);
-    step().to(InitContainer.class);
+    if (standalone) {
+      step().to(InitContainer.class);
+    }
     step().to(InitSshd.class);
     step().to(InitHttpd.class);
     step().to(InitCache.class);
-    step().to(InitPlugins.class);
+    if (standalone) {
+      step().to(InitPlugins.class);
+    }
   }
 
   protected LinkedBindingBuilder<InitStep> step() {
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..71a4d86 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
@@ -22,7 +22,7 @@
 import static com.google.gerrit.pgm.init.InitUtil.saveSecure;
 import static com.google.gerrit.pgm.init.InitUtil.version;
 
-import com.google.gerrit.pgm.Init;
+import com.google.gerrit.pgm.BaseInit;
 import com.google.gerrit.pgm.util.ConsoleUI;
 import com.google.gerrit.server.config.SitePaths;
 import com.google.gerrit.server.mail.OutgoingEmail;
@@ -75,13 +75,17 @@
     mkdir(site.data_dir);
 
     for (InitStep step : steps) {
+      if (step instanceof InitPlugins
+          && flags.skipPlugins) {
+        continue;
+      }
       step.run();
     }
 
     savePublic(flags.cfg);
     saveSecure(flags.sec);
 
-    extract(site.gerrit_sh, Init.class, "gerrit.sh");
+    extract(site.gerrit_sh, BaseInit.class, "gerrit.sh");
     chmod(0755, site.gerrit_sh);
     chmod(0700, site.tmp_dir);
 
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/SiteProgram.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/SiteProgram.java
index f4f0bd2..11968db 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/SiteProgram.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/SiteProgram.java
@@ -17,6 +17,8 @@
 import static com.google.inject.Scopes.SINGLETON;
 import static com.google.inject.Stage.PRODUCTION;
 
+import com.google.common.collect.Lists;
+import com.google.gerrit.extensions.events.LifecycleListener;
 import com.google.gerrit.lifecycle.LifecycleModule;
 import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gerrit.server.config.GerritServerConfigModule;
@@ -29,11 +31,15 @@
 import com.google.gerrit.server.schema.SchemaModule;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.AbstractModule;
+import com.google.inject.Binding;
 import com.google.inject.CreationException;
 import com.google.inject.Guice;
 import com.google.inject.Injector;
 import com.google.inject.Key;
 import com.google.inject.Module;
+import com.google.inject.Provider;
+import com.google.inject.TypeLiteral;
+import com.google.inject.name.Named;
 import com.google.inject.name.Names;
 import com.google.inject.spi.Message;
 
@@ -41,6 +47,8 @@
 import org.kohsuke.args4j.Option;
 
 import java.io.File;
+import java.lang.annotation.Annotation;
+import java.sql.Connection;
 import java.sql.SQLException;
 import java.util.ArrayList;
 import java.util.List;
@@ -51,11 +59,14 @@
   @Option(name = "--site-path", aliases = {"-d"}, usage = "Local directory containing site data")
   private File sitePath = new File(".");
 
+  protected Provider<DataSource> dsProvider;
+
   protected SiteProgram() {
   }
 
-  protected SiteProgram(File sitePath) {
+  protected SiteProgram(File sitePath, final Provider<DataSource> dsProvider) {
     this.sitePath = sitePath;
+    this.dsProvider = dsProvider;
   }
 
   /** @return the site path specified on the command line. */
@@ -92,17 +103,31 @@
       @Override
       protected void configure() {
         bind(DataSourceProvider.Context.class).toInstance(context);
-        bind(Key.get(DataSource.class, Names.named("ReviewDb")))
-          .toProvider(SiteLibraryBasedDataSourceProvider.class)
-          .in(SINGLETON);
-        listener().to(SiteLibraryBasedDataSourceProvider.class);
+        if (dsProvider != null) {
+          bind(Key.get(DataSource.class, Names.named("ReviewDb")))
+            .toProvider(dsProvider)
+            .in(SINGLETON);
+          if (LifecycleListener.class.isAssignableFrom(dsProvider.getClass())) {
+            listener().toInstance((LifecycleListener) dsProvider);
+          }
+        } else {
+          bind(Key.get(DataSource.class, Names.named("ReviewDb")))
+            .toProvider(SiteLibraryBasedDataSourceProvider.class)
+            .in(SINGLETON);
+          listener().to(SiteLibraryBasedDataSourceProvider.class);
+        }
       }
     });
     Module configModule = new GerritServerConfigModule();
     modules.add(configModule);
     Injector cfgInjector = Guice.createInjector(sitePathModule, configModule);
     Config cfg = cfgInjector.getInstance(Key.get(Config.class, GerritServerConfig.class));
-    String dbType = cfg.getString("database", null, "type");
+    String dbType;
+    if (dsProvider != null) {
+      dbType = getDbType(dsProvider);
+    } else {
+      dbType = cfg.getString("database", null, "type");
+    }
 
     final DataSourceType dst = Guice.createInjector(new DataSourceModule(), configModule,
             sitePathModule).getInstance(
@@ -151,6 +176,44 @@
     }
   }
 
+  private String getDbType(Provider<DataSource> dsProvider) {
+    String dbProductName;
+    try {
+      Connection conn = dsProvider.get().getConnection();
+      try {
+        dbProductName = conn.getMetaData().getDatabaseProductName().toLowerCase();
+      } finally {
+        conn.close();
+      }
+    } catch (SQLException e) {
+      throw new RuntimeException(e);
+    }
+
+    List<Module> modules = Lists.newArrayList();
+    modules.add(new AbstractModule() {
+      @Override
+      protected void configure() {
+        bind(File.class).annotatedWith(SitePath.class).toInstance(sitePath);
+      }
+    });
+    modules.add(new GerritServerConfigModule());
+    modules.add(new DataSourceModule());
+    Injector i = Guice.createInjector(modules);
+    List<Binding<DataSourceType>> dsTypeBindings =
+        i.findBindingsByType(new TypeLiteral<DataSourceType>() {});
+    for (Binding<DataSourceType> binding : dsTypeBindings) {
+      Annotation annotation = binding.getKey().getAnnotation();
+      if (annotation instanceof Named) {
+        if (((Named) annotation).value().toLowerCase().contains(dbProductName)) {
+          return ((Named) annotation).value();
+        }
+      }
+    }
+    throw new IllegalStateException(String.format(
+        "Cannot guess database type from the database product name '%s'",
+        dbProductName));
+  }
+
   @SuppressWarnings("deprecation")
   private static boolean isCannotCreatePoolException(Throwable why) {
     return why instanceof org.apache.commons.dbcp.SQLNestedException
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/ChangeUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/ChangeUtil.java
index bdba433..f79e05f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/ChangeUtil.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/ChangeUtil.java
@@ -397,6 +397,13 @@
       final GitReferenceUpdated gitRefUpdated, final ReviewDb db)
       throws NoSuchChangeException, OrmException, IOException {
     final Change.Id changeId = patchSetId.getParentKey();
+    deleteDraftChange(changeId, gitManager, gitRefUpdated, db);
+  }
+
+  public static void deleteDraftChange(final Change.Id changeId,
+      GitRepositoryManager gitManager,
+      final GitReferenceUpdated gitRefUpdated, final ReviewDb db)
+      throws NoSuchChangeException, OrmException, IOException {
     final Change change = db.changes().get(changeId);
     if (change == null || change.getStatus() != Change.Status.DRAFT) {
       throw new NoSuchChangeException(changeId);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteDraftChange.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteDraftChange.java
new file mode 100644
index 0000000..7d3a012
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteDraftChange.java
@@ -0,0 +1,90 @@
+// 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.change;
+
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.Response;
+import com.google.gerrit.extensions.restapi.RestModifyView;
+import com.google.gerrit.extensions.webui.UiAction;
+import com.google.gerrit.reviewdb.client.Change.Status;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.ChangeUtil;
+import com.google.gerrit.server.change.DeleteDraftChange.Input;
+import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.patch.PatchSetInfoFactory;
+import com.google.gerrit.server.project.NoSuchChangeException;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+import java.io.IOException;
+
+public class DeleteDraftChange implements
+    RestModifyView<ChangeResource, Input>, UiAction<ChangeResource> {
+  public static class Input {
+  }
+
+  protected final Provider<ReviewDb> dbProvider;
+  private final GitRepositoryManager gitManager;
+  private final GitReferenceUpdated gitRefUpdated;
+
+  @Inject
+  public DeleteDraftChange(Provider<ReviewDb> dbProvider,
+      GitRepositoryManager gitManager,
+      GitReferenceUpdated gitRefUpdated,
+      PatchSetInfoFactory patchSetInfoFactory) {
+    this.dbProvider = dbProvider;
+    this.gitManager = gitManager;
+    this.gitRefUpdated = gitRefUpdated;
+  }
+
+  @Override
+  public Object apply(ChangeResource rsrc, Input input)
+      throws ResourceConflictException, AuthException,
+      ResourceNotFoundException, OrmException, IOException {
+    if (rsrc.getChange().getStatus() != Status.DRAFT) {
+      throw new ResourceConflictException("Change is not a draft");
+    }
+
+    if (!rsrc.getControl().canDeleteDraft(dbProvider.get())) {
+      throw new AuthException("Not permitted to delete this draft change");
+    }
+
+    try {
+      ChangeUtil.deleteDraftChange(rsrc.getChange().getId(),
+          gitManager, gitRefUpdated, dbProvider.get());
+    } catch (NoSuchChangeException e) {
+      throw new ResourceNotFoundException(e.getMessage());
+    }
+
+    return Response.none();
+  }
+
+  @Override
+  public UiAction.Description getDescription(ChangeResource rsrc) {
+    try {
+      return new UiAction.Description()
+        .setTitle(String.format("Delete Draft Change %d",
+            rsrc.getChange().getChangeId()))
+        .setVisible(rsrc.getChange().getStatus() == Status.DRAFT
+            && rsrc.getControl().canDeleteDraft(dbProvider.get()));
+    } catch (OrmException e) {
+      throw new IllegalStateException(e);
+    }
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteDraftPatchSet.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteDraftPatchSet.java
new file mode 100644
index 0000000..d4e17d6
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteDraftPatchSet.java
@@ -0,0 +1,161 @@
+// 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.change;
+
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.Response;
+import com.google.gerrit.extensions.restapi.RestModifyView;
+import com.google.gerrit.extensions.webui.UiAction;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gerrit.reviewdb.client.PatchSetInfo;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.ChangeUtil;
+import com.google.gerrit.server.change.DeleteDraftPatchSet.Input;
+import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.patch.PatchSetInfoFactory;
+import com.google.gerrit.server.patch.PatchSetInfoNotAvailableException;
+import com.google.gerrit.server.project.NoSuchChangeException;
+import com.google.gwtorm.server.AtomicUpdate;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+import java.io.IOException;
+
+public class DeleteDraftPatchSet implements RestModifyView<RevisionResource, Input>,
+    UiAction<RevisionResource> {
+  public static class Input {
+  }
+
+  protected final Provider<ReviewDb> dbProvider;
+  private final GitRepositoryManager gitManager;
+  private final GitReferenceUpdated gitRefUpdated;
+  private final PatchSetInfoFactory patchSetInfoFactory;
+
+  @Inject
+  public DeleteDraftPatchSet(Provider<ReviewDb> dbProvider,
+      GitRepositoryManager gitManager,
+      GitReferenceUpdated gitRefUpdated,
+      PatchSetInfoFactory patchSetInfoFactory) {
+    this.dbProvider = dbProvider;
+    this.gitManager = gitManager;
+    this.gitRefUpdated = gitRefUpdated;
+    this.patchSetInfoFactory = patchSetInfoFactory;
+  }
+
+  @Override
+  public Object apply(RevisionResource rsrc, Input input)
+      throws ResourceNotFoundException, AuthException, OrmException,
+      IOException, ResourceConflictException {
+    PatchSet patchSet = rsrc.getPatchSet();
+    PatchSet.Id patchSetId = patchSet.getId();
+    Change change = rsrc.getChange();
+
+    if (!patchSet.isDraft()) {
+      throw new ResourceConflictException("Patch set is not a draft.");
+    }
+
+    if (!rsrc.getControl().canDeleteDraft(dbProvider.get())) {
+      throw new AuthException("Not permitted to delete this draft patch set");
+    }
+
+    deleteDraftPatchSet(patchSet, change);
+    deleteOrUpdateDraftChange(patchSetId, change);
+
+    return Response.none();
+  }
+
+  @Override
+  public UiAction.Description getDescription(RevisionResource rsrc) {
+    PatchSet.Id current = rsrc.getChange().currentPatchSetId();
+    try {
+      int psCount = dbProvider.get().patchSets()
+          .byChange(rsrc.getChange().getId()).toList().size();
+      return new UiAction.Description()
+        .setTitle(String.format("Delete Draft Revision %d",
+            rsrc.getPatchSet().getPatchSetId()))
+        .setVisible(rsrc.getPatchSet().isDraft()
+            && rsrc.getPatchSet().getId().equals(current)
+            && rsrc.getControl().canDeleteDraft(dbProvider.get())
+            && psCount > 1);
+    } catch (OrmException e) {
+      throw new IllegalStateException(e);
+    }
+  }
+
+  private void deleteDraftPatchSet(PatchSet patchSet, Change change)
+      throws ResourceNotFoundException, OrmException, IOException {
+    try {
+      ChangeUtil.deleteOnlyDraftPatchSet(patchSet,
+          change, gitManager, gitRefUpdated, dbProvider.get());
+    } catch (NoSuchChangeException e) {
+      throw new ResourceNotFoundException(e.getMessage());
+    }
+  }
+
+  private void deleteOrUpdateDraftChange(PatchSet.Id patchSetId,
+      Change change) throws OrmException, ResourceNotFoundException,
+      IOException {
+    if (dbProvider.get()
+            .patchSets()
+            .byChange(change.getId())
+            .toList().size() == 0) {
+      deleteDraftChange(patchSetId);
+    } else {
+      if (change.currentPatchSetId().equals(patchSetId)) {
+        updateCurrentPatchSet(dbProvider.get(), change,
+            previousPatchSetInfo(patchSetId));
+      }
+    }
+  }
+
+  private void deleteDraftChange(PatchSet.Id patchSetId)
+      throws OrmException, IOException, ResourceNotFoundException {
+    try {
+      ChangeUtil.deleteDraftChange(patchSetId,
+          gitManager, gitRefUpdated, dbProvider.get());
+    } catch (NoSuchChangeException e) {
+      throw new ResourceNotFoundException(e.getMessage());
+    }
+  }
+
+  private PatchSetInfo previousPatchSetInfo(PatchSet.Id patchSetId)
+      throws ResourceNotFoundException {
+    try {
+      return patchSetInfoFactory.get(dbProvider.get(),
+          new PatchSet.Id(patchSetId.getParentKey(),
+              patchSetId.get() - 1));
+    } catch (PatchSetInfoNotAvailableException e) {
+        throw new ResourceNotFoundException(e.getMessage());
+    }
+  }
+
+  private static void updateCurrentPatchSet(final ReviewDb db,
+      final Change change, final PatchSetInfo psInfo)
+      throws OrmException {
+    db.changes().atomicUpdate(change.getId(), new AtomicUpdate<Change>() {
+      @Override
+      public Change update(Change c) {
+        c.setCurrentPatchSet(psInfo);
+        ChangeUtil.updated(c);
+        return c;
+      }
+    });
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteReviewer.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteReviewer.java
index ac09374..3925f15 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteReviewer.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteReviewer.java
@@ -27,6 +27,7 @@
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.ChangeUtil;
 import com.google.gerrit.server.change.DeleteReviewer.Input;
+import com.google.gerrit.server.index.ChangeIndexer;
 import com.google.gerrit.server.project.ChangeControl;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
@@ -39,10 +40,12 @@
   }
 
   private final Provider<ReviewDb> dbProvider;
+  private final ChangeIndexer indexer;
 
   @Inject
-  DeleteReviewer(Provider<ReviewDb> dbProvider) {
+  DeleteReviewer(Provider<ReviewDb> dbProvider, ChangeIndexer indexer) {
     this.dbProvider = dbProvider;
+    this.indexer = indexer;
   }
 
   @Override
@@ -70,6 +73,7 @@
     } finally {
       db.rollback();
     }
+    indexer.index(rsrc.getChange());
     return Response.none();
   }
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/IncludedInResolver.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/IncludedInResolver.java
new file mode 100644
index 0000000..b02f6f0
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/IncludedInResolver.java
@@ -0,0 +1,159 @@
+// 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.change;
+
+import com.google.gerrit.common.data.IncludedInDetail;
+
+import org.eclipse.jgit.errors.IncorrectObjectTypeException;
+import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevFlag;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Resolve in which tags and branches a commit is included.
+ */
+public class IncludedInResolver {
+
+  private static final Logger log = LoggerFactory
+      .getLogger(IncludedInResolver.class);
+
+  public static IncludedInDetail resolve(final Repository repo,
+      final RevWalk rw, final RevCommit commit) throws IOException {
+
+    Set<Ref> tags =
+        new HashSet<Ref>(repo.getRefDatabase().getRefs(Constants.R_TAGS)
+            .values());
+    Set<Ref> branches =
+        new HashSet<Ref>(repo.getRefDatabase().getRefs(Constants.R_HEADS)
+            .values());
+    Set<Ref> allTagsAndBranches = new HashSet<Ref>();
+    allTagsAndBranches.addAll(tags);
+    allTagsAndBranches.addAll(branches);
+    Set<Ref> allMatchingTagsAndBranches =
+        includedIn(repo, rw, commit, allTagsAndBranches);
+
+    IncludedInDetail detail = new IncludedInDetail();
+    detail
+        .setBranches(getMatchingRefNames(allMatchingTagsAndBranches, branches));
+    detail.setTags(getMatchingRefNames(allMatchingTagsAndBranches, tags));
+
+    return detail;
+  }
+
+  /**
+   * Resolves which tip refs include the target commit.
+   */
+  private static Set<Ref> includedIn(final Repository repo, final RevWalk rw,
+      final RevCommit target, final Set<Ref> tipRefs) throws IOException,
+      MissingObjectException, IncorrectObjectTypeException {
+
+    Set<Ref> result = new HashSet<Ref>();
+
+    Map<RevCommit, Set<Ref>> tipsAndCommits = parseCommits(repo, rw, tipRefs);
+
+    List<RevCommit> tips = new ArrayList<RevCommit>(tipsAndCommits.keySet());
+    Collections.sort(tips, new Comparator<RevCommit>() {
+      @Override
+      public int compare(RevCommit c1, RevCommit c2) {
+        return c1.getCommitTime() - c2.getCommitTime();
+      }
+    });
+
+    Set<RevCommit> targetReachableFrom = new HashSet<RevCommit>();
+    targetReachableFrom.add(target);
+
+    for (RevCommit tip : tips) {
+      boolean commitFound = false;
+      rw.resetRetain(RevFlag.UNINTERESTING);
+      rw.markStart(tip);
+      for (RevCommit commit : rw) {
+        if (targetReachableFrom.contains(commit)) {
+          commitFound = true;
+          targetReachableFrom.add(tip);
+          result.addAll(tipsAndCommits.get(tip));
+          break;
+        }
+      }
+      if (!commitFound) {
+        rw.markUninteresting(tip);
+      }
+    }
+
+    return result;
+  }
+
+  /**
+   * Returns the short names of refs which are as well in the matchingRefs list
+   * as well as in the allRef list.
+   */
+  private static List<String> getMatchingRefNames(Set<Ref> matchingRefs,
+      Set<Ref> allRefs) {
+    List<String> refNames = new ArrayList<String>();
+    for (Ref matchingRef : matchingRefs) {
+      if (allRefs.contains(matchingRef)) {
+        refNames.add(Repository.shortenRefName(matchingRef.getName()));
+      }
+    }
+    return refNames;
+  }
+
+  /**
+   * Parse commit of ref and store the relation between ref and commit.
+   */
+  private static Map<RevCommit, Set<Ref>> parseCommits(final Repository repo,
+      final RevWalk rw, final Set<Ref> refs) throws IOException {
+    Map<RevCommit, Set<Ref>> result = new HashMap<RevCommit, Set<Ref>>();
+    for (Ref ref : refs) {
+      final RevCommit commit;
+      try {
+        commit = rw.parseCommit(ref.getObjectId());
+      } catch (IncorrectObjectTypeException notCommit) {
+        // Its OK for a tag reference to point to a blob or a tree, this
+        // is common in the Linux kernel or git.git repository.
+        //
+        continue;
+      } catch (MissingObjectException notHere) {
+        // Log the problem with this branch, but keep processing.
+        //
+        log.warn("Reference " + ref.getName() + " in " + repo.getDirectory()
+            + " points to dangling object " + ref.getObjectId());
+        continue;
+      }
+      Set<Ref> relatedRefs = result.get(commit);
+      if (relatedRefs == null) {
+        relatedRefs = new HashSet<Ref>();
+        result.put(commit, relatedRefs);
+      }
+      relatedRefs.add(ref);
+    }
+    return result;
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Module.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Module.java
index b96e296..25bd80f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Module.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Module.java
@@ -49,7 +49,9 @@
     get(CHANGE_KIND, "topic").to(GetTopic.class);
     put(CHANGE_KIND, "topic").to(PutTopic.class);
     delete(CHANGE_KIND, "topic").to(PutTopic.class);
+    delete(CHANGE_KIND).to(DeleteDraftChange.class);
     post(CHANGE_KIND, "abandon").to(Abandon.class);
+    post(CHANGE_KIND, "publish").to(Publish.CurrentRevision.class);
     post(CHANGE_KIND, "restore").to(Restore.class);
     post(CHANGE_KIND, "revert").to(Revert.class);
     post(CHANGE_KIND, "submit").to(Submit.CurrentRevision.class);
@@ -64,7 +66,9 @@
     child(CHANGE_KIND, "revisions").to(Revisions.class);
     post(REVISION_KIND, "cherrypick").to(CherryPick.class);
     get(REVISION_KIND, "commit").to(GetCommit.class);
+    delete(REVISION_KIND).to(DeleteDraftPatchSet.class);
     get(REVISION_KIND, "mergeable").to(Mergeable.class);
+    post(REVISION_KIND, "publish").to(Publish.class);
     get(REVISION_KIND, "related").to(GetRelated.class);
     get(REVISION_KIND, "review").to(GetReview.class);
     post(REVISION_KIND, "review").to(PostReview.class);
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/change/Publish.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Publish.java
new file mode 100644
index 0000000..7355ef3
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Publish.java
@@ -0,0 +1,161 @@
+// 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.change;
+
+import com.google.gerrit.common.ChangeHooks;
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.Response;
+import com.google.gerrit.extensions.restapi.RestModifyView;
+import com.google.gerrit.extensions.webui.UiAction;
+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.ChangeUtil;
+import com.google.gerrit.server.mail.PatchSetNotificationSender;
+import com.google.gerrit.server.change.Publish.Input;
+import com.google.gerrit.server.index.ChangeIndexer;
+import com.google.gerrit.server.patch.PatchSetInfoNotAvailableException;
+import com.google.gwtorm.server.AtomicUpdate;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+import java.io.IOException;
+
+public class Publish implements RestModifyView<RevisionResource, Input>,
+    UiAction<RevisionResource> {
+  public static class Input {
+  }
+
+  private final Provider<ReviewDb> dbProvider;
+  private final PatchSetNotificationSender sender;
+  private final ChangeHooks hooks;
+  private final ChangeIndexer indexer;
+
+  @Inject
+  public Publish(Provider<ReviewDb> dbProvider,
+      PatchSetNotificationSender sender,
+      ChangeHooks hooks,
+      ChangeIndexer indexer) {
+    this.dbProvider = dbProvider;
+    this.sender = sender;
+    this.hooks = hooks;
+    this.indexer = indexer;
+  }
+
+  @Override
+  public Object apply(RevisionResource rsrc, Input input) throws IOException,
+      ResourceNotFoundException, ResourceConflictException,
+      OrmException, AuthException {
+    if (!rsrc.getPatchSet().isDraft()) {
+      throw new ResourceConflictException("Patch set is not a draft");
+    }
+
+    if (!rsrc.getControl().canPublish(dbProvider.get())) {
+      throw new AuthException("Cannot publish this draft patch set");
+    }
+
+    PatchSet updatedPatchSet = updateDraftPatchSet(rsrc);
+    Change updatedChange = updateDraftChange(rsrc);
+
+    try {
+      if (!updatedPatchSet.isDraft()
+          || updatedChange.getStatus() == Change.Status.NEW) {
+        indexer.index(updatedChange);
+        hooks.doDraftPublishedHook(updatedChange, updatedPatchSet, dbProvider.get());
+        sender.send(rsrc.getChange().getStatus() == Change.Status.DRAFT,
+            rsrc.getUser(), updatedChange, updatedPatchSet,
+            rsrc.getControl().getLabelTypes());
+      }
+    } catch (PatchSetInfoNotAvailableException e) {
+      throw new ResourceNotFoundException(e.getMessage());
+    }
+
+    return Response.none();
+  }
+
+  private Change updateDraftChange(RevisionResource rsrc) throws OrmException {
+    Change updatedChange = dbProvider.get().changes()
+        .atomicUpdate(rsrc.getChange().getId(),
+        new AtomicUpdate<Change>() {
+      @Override
+      public Change update(Change change) {
+        if (change.getStatus() == Change.Status.DRAFT) {
+          change.setStatus(Change.Status.NEW);
+          ChangeUtil.updated(change);
+        }
+        return change;
+      }
+    });
+    return updatedChange;
+  }
+
+  private PatchSet updateDraftPatchSet(RevisionResource rsrc) throws OrmException {
+    final PatchSet updatedPatchSet = dbProvider.get().patchSets()
+        .atomicUpdate(rsrc.getPatchSet().getId(),
+        new AtomicUpdate<PatchSet>() {
+      @Override
+      public PatchSet update(PatchSet patchset) {
+        patchset.setDraft(false);
+        return patchset;
+      }
+    });
+    return updatedPatchSet;
+  }
+
+  @Override
+  public UiAction.Description getDescription(RevisionResource rsrc) {
+    PatchSet.Id current = rsrc.getChange().currentPatchSetId();
+    try {
+      return new UiAction.Description()
+        .setTitle(String.format("Publish Revision %d",
+            rsrc.getPatchSet().getPatchSetId()))
+        .setVisible(rsrc.getPatchSet().isDraft()
+            && rsrc.getPatchSet().getId().equals(current)
+            && rsrc.getControl().canPublish(dbProvider.get()));
+    } catch (OrmException e) {
+      throw new IllegalStateException(e);
+    }
+  }
+
+  public static class CurrentRevision implements
+      RestModifyView<ChangeResource, Input> {
+    private final Provider<ReviewDb> dbProvider;
+    private final Publish publish;
+
+    @Inject
+    CurrentRevision(Provider<ReviewDb> dbProvider,
+        Publish publish) {
+      this.dbProvider = dbProvider;
+      this.publish = publish;
+    }
+
+    @Override
+    public Object apply(ChangeResource rsrc, Input input) throws AuthException,
+        ResourceConflictException, ResourceConflictException, IOException,
+        OrmException, ResourceNotFoundException, AuthException {
+      PatchSet ps = dbProvider.get().patchSets()
+        .get(rsrc.getChange().currentPatchSetId());
+      if (ps == null) {
+        throw new ResourceConflictException("current revision is missing");
+      } else if (!rsrc.getControl().isPatchVisible(ps, dbProvider.get())) {
+        throw new AuthException("current revision not accessible");
+      }
+      return publish.apply(new RevisionResource(rsrc, ps), input);
+    }
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/changedetail/DeleteDraftPatchSet.java b/gerrit-server/src/main/java/com/google/gerrit/server/changedetail/DeleteDraftPatchSet.java
index adec292..d20b0f6 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/changedetail/DeleteDraftPatchSet.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/changedetail/DeleteDraftPatchSet.java
@@ -112,9 +112,7 @@
       }
       if (change.currentPatchSetId().equals(patchSetId)) {
         try {
-          PatchSet.Id id =
-              new PatchSet.Id(patchSetId.getParentKey(), patchSetId.get() - 1);
-          change.setCurrentPatchSet(patchSetInfoFactory.get(db, id));
+          change.setCurrentPatchSet(patchSetInfoFactory.get(db, highestId));
         } catch (PatchSetInfoNotAvailableException e) {
           throw new NoSuchChangeException(changeId);
         }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/changedetail/PublishDraft.java b/gerrit-server/src/main/java/com/google/gerrit/server/changedetail/PublishDraft.java
index 2fbc79f..fd0596e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/changedetail/PublishDraft.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/changedetail/PublishDraft.java
@@ -15,18 +15,11 @@
 
 package com.google.gerrit.server.changedetail;
 
-import static com.google.gerrit.server.mail.MailUtil.getRecipientsFromApprovals;
-import static com.google.gerrit.server.mail.MailUtil.getRecipientsFromFooters;
-
 import com.google.gerrit.common.ChangeHooks;
 import com.google.gerrit.common.data.LabelTypes;
 import com.google.gerrit.common.data.ReviewResult;
-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.PatchSet;
-import com.google.gerrit.reviewdb.client.PatchSetApproval;
-import com.google.gerrit.reviewdb.client.PatchSetInfo;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.ApprovalsUtil;
 import com.google.gerrit.server.ChangeUtil;
@@ -35,7 +28,7 @@
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.index.ChangeIndexer;
 import com.google.gerrit.server.mail.CreateChangeSender;
-import com.google.gerrit.server.mail.MailUtil.MailRecipients;
+import com.google.gerrit.server.mail.PatchSetNotificationSender;
 import com.google.gerrit.server.mail.ReplacePatchSetSender;
 import com.google.gerrit.server.patch.PatchSetInfoFactory;
 import com.google.gerrit.server.patch.PatchSetInfoNotAvailableException;
@@ -46,23 +39,10 @@
 import com.google.inject.Inject;
 import com.google.inject.assistedinject.Assisted;
 
-import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.revwalk.FooterLine;
-import org.eclipse.jgit.revwalk.RevCommit;
-import org.eclipse.jgit.revwalk.RevWalk;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
 import java.io.IOException;
-import java.util.Collections;
-import java.util.List;
 import java.util.concurrent.Callable;
 
 public class PublishDraft implements Callable<ReviewResult> {
-  private static final Logger log =
-      LoggerFactory.getLogger(PublishDraft.class);
-
   public interface Factory {
     PublishDraft create(PatchSet.Id patchSetId);
   }
@@ -70,13 +50,8 @@
   private final ChangeControl.Factory changeControlFactory;
   private final ReviewDb db;
   private final ChangeHooks hooks;
-  private final GitRepositoryManager repoManager;
-  private final PatchSetInfoFactory patchSetInfoFactory;
-  private final ApprovalsUtil approvalsUtil;
-  private final AccountResolver accountResolver;
-  private final CreateChangeSender.Factory createChangeSenderFactory;
-  private final ReplacePatchSetSender.Factory replacePatchSetFactory;
   private final ChangeIndexer indexer;
+  private final PatchSetNotificationSender sender;
 
   private final PatchSet.Id patchSetId;
 
@@ -90,17 +65,13 @@
       final CreateChangeSender.Factory createChangeSenderFactory,
       final ReplacePatchSetSender.Factory replacePatchSetFactory,
       final ChangeIndexer indexer,
+      final PatchSetNotificationSender sender,
       @Assisted final PatchSet.Id patchSetId) {
     this.changeControlFactory = changeControlFactory;
     this.db = db;
     this.hooks = hooks;
-    this.repoManager = repoManager;
-    this.patchSetInfoFactory = patchSetInfoFactory;
-    this.approvalsUtil = approvalsUtil;
-    this.accountResolver = accountResolver;
-    this.createChangeSenderFactory = createChangeSenderFactory;
-    this.replacePatchSetFactory = replacePatchSetFactory;
     this.indexer = indexer;
+    this.sender = sender;
 
     this.patchSetId = patchSetId;
   }
@@ -153,7 +124,7 @@
         indexer.index(updatedChange);
         hooks.doDraftPublishedHook(updatedChange, updatedPatchSet, db);
 
-        sendNotifications(control.getChange().getStatus() == Change.Status.DRAFT,
+        sender.send(control.getChange().getStatus() == Change.Status.DRAFT,
             (IdentifiedUser) control.getCurrentUser(), updatedChange, updatedPatchSet,
             labelTypes);
       }
@@ -161,66 +132,4 @@
 
     return result;
   }
-
-  private void sendNotifications(final boolean newChange,
-      final IdentifiedUser currentUser, final Change updatedChange,
-      final PatchSet updatedPatchSet, final LabelTypes labelTypes)
-      throws OrmException, IOException, PatchSetInfoNotAvailableException {
-    final Repository git = repoManager.openRepository(updatedChange.getProject());
-    try {
-      final RevWalk revWalk = new RevWalk(git);
-      final RevCommit commit;
-      try {
-        commit = revWalk.parseCommit(ObjectId.fromString(updatedPatchSet.getRevision().get()));
-      } finally {
-        revWalk.release();
-      }
-      final PatchSetInfo info = patchSetInfoFactory.get(commit, updatedPatchSet.getId());
-      final List<FooterLine> footerLines = commit.getFooterLines();
-      final Account.Id me = currentUser.getAccountId();
-      final MailRecipients recipients =
-          getRecipientsFromFooters(accountResolver, updatedPatchSet, footerLines);
-      recipients.remove(me);
-
-      if (newChange) {
-        approvalsUtil.addReviewers(db, labelTypes, updatedChange, updatedPatchSet, info,
-            recipients.getReviewers(), Collections.<Account.Id> emptySet());
-        try {
-          CreateChangeSender cm = createChangeSenderFactory.create(updatedChange);
-          cm.setFrom(me);
-          cm.setPatchSet(updatedPatchSet, info);
-          cm.addReviewers(recipients.getReviewers());
-          cm.addExtraCC(recipients.getCcOnly());
-          cm.send();
-        } catch (Exception e) {
-          log.error("Cannot send email for new change " + updatedChange.getId(), e);
-        }
-      } else {
-        final List<PatchSetApproval> patchSetApprovals =
-            db.patchSetApprovals().byChange(updatedChange.getId()).toList();
-        final MailRecipients oldRecipients =
-            getRecipientsFromApprovals(patchSetApprovals);
-        approvalsUtil.addReviewers(db, labelTypes, updatedChange, updatedPatchSet, info,
-            recipients.getReviewers(), oldRecipients.getAll());
-        final ChangeMessage msg =
-            new ChangeMessage(new ChangeMessage.Key(updatedChange.getId(),
-                ChangeUtil.messageUUID(db)), me,
-                updatedPatchSet.getCreatedOn(), updatedPatchSet.getId());
-        msg.setMessage("Uploaded patch set " + updatedPatchSet.getPatchSetId() + ".");
-        try {
-          ReplacePatchSetSender cm = replacePatchSetFactory.create(updatedChange);
-          cm.setFrom(me);
-          cm.setPatchSet(updatedPatchSet, info);
-          cm.setChangeMessage(msg);
-          cm.addReviewers(recipients.getReviewers());
-          cm.addExtraCC(recipients.getCcOnly());
-          cm.send();
-        } catch (Exception e) {
-          log.error("Cannot send email for new patch set " + updatedPatchSet.getId(), e);
-        }
-      }
-    } finally {
-      git.close();
-    }
-  }
 }
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 f09015b..c4d06d2 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
@@ -509,7 +509,7 @@
           if (rw.isMergedInto(commit, branchTip)) {
             commit.statusCode = CommitMergeStatus.ALREADY_MERGED;
             try {
-              setMergedPatchSet(chg.getId(), ps.getId());
+              setMerged(chg, null);
             } catch (OrmException e) {
               log.error("Cannot mark change " + chg.getId() + " merged", e);
             }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectConfig.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectConfig.java
index a803ddd..3df559d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectConfig.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectConfig.java
@@ -59,6 +59,7 @@
 import java.io.IOException;
 import java.io.StringReader;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.EnumSet;
@@ -127,6 +128,7 @@
   private static final String KEY_COPY_MAX_SCORE = "copyMaxScore";
   private static final String KEY_VALUE = "value";
   private static final String KEY_CAN_OVERRIDE = "canOverride";
+  private static final String KEY_Branch = "branch";
   private static final Set<String> LABEL_FUNCTIONS = ImmutableSet.of(
       "MaxWithBlock", "AnyWithBlock", "MaxNoBlock", "NoBlock", "NoOp");
 
@@ -651,10 +653,17 @@
           rc.getBoolean(LABEL, name, KEY_COPY_MAX_SCORE, false));
       label.setCanOverride(
           rc.getBoolean(LABEL, name, KEY_CAN_OVERRIDE, true));
+      label.setRefPatterns(getStringListOrNull(rc, LABEL, name, KEY_Branch));
       labelSections.put(name, label);
     }
   }
 
+  private List<String> getStringListOrNull(Config rc, String section,
+      String subSection, String name) {
+    String[] ac = rc.getStringList(section, subSection, name);
+    return ac.length == 0 ? null : Arrays.asList(ac);
+  }
+
   private void loadCommentLinkSections(Config rc) {
     Set<String> subsections = rc.getSubsections(COMMENTLINK);
     commentLinkSections = Lists.newArrayListWithCapacity(subsections.size());
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/CommentSender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/CommentSender.java
index 5b3f59a..cd6409e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/CommentSender.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/CommentSender.java
@@ -170,7 +170,6 @@
     CommentRange range = comment.getRange();
     if (range != null) {
       String prefix = String.format("Line %d: ", range.getStartLine());
-      out.append(prefix);
       for (int n = range.getStartLine(); n <= range.getEndLine(); n++) {
         out.append(n == range.getStartLine()
             ? prefix
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/PatchSetNotificationSender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/PatchSetNotificationSender.java
new file mode 100644
index 0000000..3484dd8
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/PatchSetNotificationSender.java
@@ -0,0 +1,149 @@
+// 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.mail;
+
+import static com.google.gerrit.server.mail.MailUtil.getRecipientsFromApprovals;
+import static com.google.gerrit.server.mail.MailUtil.getRecipientsFromFooters;
+
+import com.google.gerrit.common.ChangeHooks;
+import com.google.gerrit.common.data.LabelTypes;
+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.PatchSet;
+import com.google.gerrit.reviewdb.client.PatchSetApproval;
+import com.google.gerrit.reviewdb.client.PatchSetInfo;
+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.AccountResolver;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.index.ChangeIndexer;
+import com.google.gerrit.server.mail.MailUtil.MailRecipients;
+import com.google.gerrit.server.patch.PatchSetInfoFactory;
+import com.google.gerrit.server.patch.PatchSetInfoNotAvailableException;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.FooterLine;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.List;
+
+public class PatchSetNotificationSender {
+  private static final Logger log =
+      LoggerFactory.getLogger(PatchSetNotificationSender.class);
+
+  private final ReviewDb db;
+  private final GitRepositoryManager repoManager;
+  private final PatchSetInfoFactory patchSetInfoFactory;
+  private final ApprovalsUtil approvalsUtil;
+  private final AccountResolver accountResolver;
+  private final CreateChangeSender.Factory createChangeSenderFactory;
+  private final ReplacePatchSetSender.Factory replacePatchSetFactory;
+
+  @Inject
+  public PatchSetNotificationSender(ReviewDb db,
+      ChangeHooks hooks,
+      GitRepositoryManager repoManager,
+      PatchSetInfoFactory patchSetInfoFactory,
+      ApprovalsUtil approvalsUtil,
+      AccountResolver accountResolver,
+      CreateChangeSender.Factory createChangeSenderFactory,
+      ReplacePatchSetSender.Factory replacePatchSetFactory,
+      ChangeIndexer indexer) {
+    this.db = db;
+    this.repoManager = repoManager;
+    this.patchSetInfoFactory = patchSetInfoFactory;
+    this.approvalsUtil = approvalsUtil;
+    this.accountResolver = accountResolver;
+    this.createChangeSenderFactory = createChangeSenderFactory;
+    this.replacePatchSetFactory = replacePatchSetFactory;
+  }
+
+  public void send(final boolean newChange,
+      final IdentifiedUser currentUser, final Change updatedChange,
+      final PatchSet updatedPatchSet, final LabelTypes labelTypes)
+      throws OrmException, IOException, PatchSetInfoNotAvailableException {
+    final Repository git = repoManager.openRepository(updatedChange.getProject());
+    try {
+      final RevWalk revWalk = new RevWalk(git);
+      final RevCommit commit;
+      try {
+        commit = revWalk.parseCommit(ObjectId.fromString(
+            updatedPatchSet.getRevision().get()));
+      } finally {
+        revWalk.release();
+      }
+      final PatchSetInfo info = patchSetInfoFactory.get(commit, updatedPatchSet.getId());
+      final List<FooterLine> footerLines = commit.getFooterLines();
+      final Account.Id me = currentUser.getAccountId();
+      final MailRecipients recipients =
+          getRecipientsFromFooters(accountResolver, updatedPatchSet, footerLines);
+      recipients.remove(me);
+
+      if (newChange) {
+        approvalsUtil.addReviewers(db, labelTypes,
+            updatedChange, updatedPatchSet, info,
+            recipients.getReviewers(), Collections.<Account.Id> emptySet());
+        try {
+          CreateChangeSender cm = createChangeSenderFactory.create(updatedChange);
+          cm.setFrom(me);
+          cm.setPatchSet(updatedPatchSet, info);
+          cm.addReviewers(recipients.getReviewers());
+          cm.addExtraCC(recipients.getCcOnly());
+          cm.send();
+        } catch (Exception e) {
+          log.error("Cannot send email for new change " + updatedChange.getId(), e);
+        }
+      } else {
+        final List<PatchSetApproval> patchSetApprovals =
+            db.patchSetApprovals().byChange(
+                updatedChange.getId()).toList();
+        final MailRecipients oldRecipients =
+            getRecipientsFromApprovals(patchSetApprovals);
+        approvalsUtil.addReviewers(db, labelTypes, updatedChange,
+            updatedPatchSet, info, recipients.getReviewers(),
+            oldRecipients.getAll());
+        final ChangeMessage msg =
+            new ChangeMessage(new ChangeMessage.Key(updatedChange.getId(),
+                ChangeUtil.messageUUID(db)), me,
+                updatedPatchSet.getCreatedOn(), updatedPatchSet.getId());
+        msg.setMessage("Uploaded patch set " + updatedPatchSet.getPatchSetId() + ".");
+        try {
+          ReplacePatchSetSender cm = replacePatchSetFactory.create(updatedChange);
+          cm.setFrom(me);
+          cm.setPatchSet(updatedPatchSet, info);
+          cm.setChangeMessage(msg);
+          cm.addReviewers(recipients.getReviewers());
+          cm.addExtraCC(recipients.getCcOnly());
+          cm.send();
+        } catch (Exception e) {
+          log.error("Cannot send email for new patch set " + updatedPatchSet.getId(), e);
+        }
+      }
+    } finally {
+      git.close();
+    }
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ChangeControl.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ChangeControl.java
index 667a510..decf90a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ChangeControl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ChangeControl.java
@@ -14,8 +14,11 @@
 
 package com.google.gerrit.server.project;
 
+import com.google.common.collect.Lists;
+import com.google.gerrit.common.data.LabelType;
 import com.google.gerrit.common.data.LabelTypes;
 import com.google.gerrit.common.data.PermissionRange;
+import com.google.gerrit.common.data.RefConfigSection;
 import com.google.gerrit.common.data.SubmitRecord;
 import com.google.gerrit.common.data.SubmitTypeRecord;
 import com.google.gerrit.reviewdb.client.Account;
@@ -242,9 +245,28 @@
         && getRefControl().canUpload(); // as long as you can upload too
   }
 
-  /** All available label types for this project. */
+  /** All available label types for this change. */
   public LabelTypes getLabelTypes() {
-    return getProjectControl().getLabelTypes();
+    String destBranch = getChange().getDest().get();
+    List<LabelType> all = getProjectControl().getLabelTypes().getLabelTypes();
+
+    List<LabelType> r = Lists.newArrayListWithCapacity(all.size());
+    for (LabelType l : all) {
+      List<String> refs = l.getRefPatterns();
+      if (refs == null) {
+        r.add(l);
+      } else {
+        for (String refPattern : refs) {
+          if (RefConfigSection.isValid(refPattern)
+              && match(destBranch, refPattern)) {
+            r.add(l);
+            break;
+          }
+        }
+      }
+    }
+
+    return new LabelTypes(r);
   }
 
   /** All value ranges of any allowed label permission. */
@@ -403,6 +425,11 @@
     return resultsToSubmitRecord(evaluator.getSubmitRule(), results);
   }
 
+  private boolean match(String destBranch, String refPattern) {
+    return RefPatternMatcher.getMatcher(refPattern).match(destBranch,
+        this.getRefControl().getCurrentUser().getUserName());
+  }
+
   private List<SubmitRecord> cannotSubmitDraft(ReviewDb db, PatchSet patchSet,
       ChangeData cd) {
     try {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/DeleteBranch.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/DeleteBranch.java
index 2978c89..a41c197 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/DeleteBranch.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/DeleteBranch.java
@@ -36,7 +36,7 @@
 import java.io.IOException;
 
 public class DeleteBranch implements RestModifyView<BranchResource, Input>{
-  private static final Logger log = LoggerFactory.getLogger(CreateBranch.class);
+  private static final Logger log = LoggerFactory.getLogger(DeleteBranch.class);
 
   static class Input {
   }
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/QueryChanges.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryChanges.java
index 76d873a..24186b8 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
@@ -160,6 +160,7 @@
         } else {
           changes = changes.subList(0, imp.getLimit());
         }
+        data.set(n, changes);
         more.set(n, true);
       }
     }
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 72c7138..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
@@ -33,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;
 
@@ -229,7 +230,7 @@
     final Predicate<ChangeData> visibleToMe = queryBuilder.is_visible();
     int cnt = queries.size();
 
-    // Begin all queries, possibly asynchronously.
+    // Parse and rewrite all queries.
     List<Integer> limits = Lists.newArrayListWithCapacity(cnt);
     List<ChangeDataSource> sources = Lists.newArrayListWithCapacity(cnt);
     for (int i = 0; i < cnt; i++) {
@@ -251,9 +252,16 @@
       sources.add(a);
     }
 
+    // Run each query asynchronously, if supported.
+    List<ResultSet<ChangeData>> matches = Lists.newArrayListWithCapacity(cnt);
+    for (ChangeDataSource s : sources) {
+      matches.add(s.read());
+    }
+    sources = null;
+
     List<List<ChangeData>> out = Lists.newArrayListWithCapacity(cnt);
     for (int i = 0; i < cnt; i++) {
-      List<ChangeData> results = Lists.newArrayList(sources.get(i).read());
+      List<ChangeData> results = matches.get(i).toList();
       Collections.sort(results, sortkeyAfter != null ? cmpAfter : cmpBefore);
       if (results.size() > maxLimit) {
         moreResults = true;
diff --git a/gerrit-server/src/main/java/gerrit/PRED_get_legacy_label_types_1.java b/gerrit-server/src/main/java/gerrit/PRED_get_legacy_label_types_1.java
index 4b89544..698c11c 100644
--- a/gerrit-server/src/main/java/gerrit/PRED_get_legacy_label_types_1.java
+++ b/gerrit-server/src/main/java/gerrit/PRED_get_legacy_label_types_1.java
@@ -16,9 +16,7 @@
 
 import com.google.gerrit.common.data.LabelType;
 import com.google.gerrit.common.data.LabelValue;
-import com.google.gerrit.rules.PrologEnvironment;
 import com.google.gerrit.rules.StoredValues;
-import com.google.gerrit.server.project.ProjectState;
 
 import com.googlecode.prolog_cafe.lang.IntegerTerm;
 import com.googlecode.prolog_cafe.lang.ListTerm;
@@ -55,14 +53,8 @@
   public Operation exec(Prolog engine) throws PrologException {
     engine.setB0();
     Term a1 = arg1.dereference();
-
-    PrologEnvironment env = (PrologEnvironment) engine.control;
-    ProjectState state = env.getArgs().getProjectCache()
-        .get(StoredValues.CHANGE.get(engine).getDest().getParentKey());
-    if (state == null) {
-      return engine.fail();
-    }
-    List<LabelType> list = state.getLabelTypes().getLabelTypes();
+    List<LabelType> list =
+        StoredValues.CHANGE_CONTROL.get(engine).getLabelTypes().getLabelTypes();
     Term head = Prolog.Nil;
     for (int idx = list.size() - 1; 0 <= idx; idx--) {
       head = new ListTerm(export(list.get(idx)), head);
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/change/IncludedInResolverTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/change/IncludedInResolverTest.java
new file mode 100644
index 0000000..ae58819
--- /dev/null
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/change/IncludedInResolverTest.java
@@ -0,0 +1,205 @@
+// 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.change;
+
+import com.google.gerrit.common.data.IncludedInDetail;
+
+import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.api.MergeCommand.FastForwardMode;
+import org.eclipse.jgit.junit.RepositoryTestCase;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevTag;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public class IncludedInResolverTest extends RepositoryTestCase {
+
+  // Branch names
+  private static final String BRANCH_MASTER = "master";
+  private static final String BRANCH_1_0 = "rel-1.0";
+  private static final String BRANCH_1_3 = "rel-1.3";
+  private static final String BRANCH_2_0 = "rel-2.0";
+  private static final String BRANCH_2_5 = "rel-2.5";
+
+  // Tag names
+  private static final String TAG_1_0 = "1.0";
+  private static final String TAG_1_0_1 = "1.0.1";
+  private static final String TAG_1_3 = "1.3";
+  private static final String TAG_2_0_1 = "2.0.1";
+  private static final String TAG_2_0 = "2.0";
+  private static final String TAG_2_5 = "2.5";
+  private static final String TAG_2_5_ANNOTATED = "2.5-annotated";
+  private static final String TAG_2_5_ANNOTATED_TWICE = "2.5-annotated_twice";
+
+  // Commits
+  private RevCommit commit_initial;
+  private RevCommit commit_v1_3;
+  private RevCommit commit_v2_5;
+
+  private List<String> expTags = new ArrayList<String>();
+  private List<String> expBranches = new ArrayList<String>();
+
+  private RevWalk revWalk;
+
+  @Before
+  public void setUp() throws Exception {
+    super.setUp();
+
+    /*- The following graph will be created.
+
+      o   tag 2.5, 2.5_annotated, 2.5_annotated_twice
+      |\
+      | o tag 2.0.1
+      | o tag 2.0
+      o | tag 1.3
+      |/
+      o   c3
+
+      | o tag 1.0.1
+      |/
+      o   tag 1.0
+      o   c2
+      o   c1
+
+     */
+
+    Git git = new Git(db);
+    revWalk = new RevWalk(db);
+    // Version 1.0
+    commit_initial = git.commit().setMessage("c1").call();
+    git.commit().setMessage("c2").call();
+    RevCommit commit_v1_0 = git.commit().setMessage("version 1.0").call();
+    git.tag().setName(TAG_1_0).setObjectId(commit_v1_0).call();
+    RevCommit c3 = git.commit().setMessage("c3").call();
+    // Version 1.01
+    createAndCheckoutBranch(commit_v1_0, BRANCH_1_0);
+    RevCommit commit_v1_0_1 =
+        git.commit().setMessage("verREFS_HEADS_RELsion 1.0.1").call();
+    git.tag().setName(TAG_1_0_1).setObjectId(commit_v1_0_1).call();
+    // Version 1.3
+    createAndCheckoutBranch(c3, BRANCH_1_3);
+    commit_v1_3 = git.commit().setMessage("version 1.3").call();
+    git.tag().setName(TAG_1_3).setObjectId(commit_v1_3).call();
+    // Version 2.0
+    createAndCheckoutBranch(c3, BRANCH_2_0);
+    RevCommit commit_v2_0 = git.commit().setMessage("version 2.0").call();
+    git.tag().setName(TAG_2_0).setObjectId(commit_v2_0).call();
+    RevCommit commit_v2_0_1 = git.commit().setMessage("version 2.0.1").call();
+    git.tag().setName(TAG_2_0_1).setObjectId(commit_v2_0_1).call();
+
+    // Version 2.5
+    createAndCheckoutBranch(commit_v1_3, BRANCH_2_5);
+    git.merge().include(commit_v2_0_1).setCommit(false)
+        .setFastForward(FastForwardMode.NO_FF).call();
+    commit_v2_5 = git.commit().setMessage("version 2.5").call();
+    git.tag().setName(TAG_2_5).setObjectId(commit_v2_5).setAnnotated(false)
+        .call();
+    Ref ref_tag_2_5_annotated =
+        git.tag().setName(TAG_2_5_ANNOTATED).setObjectId(commit_v2_5)
+            .setAnnotated(true).call();
+    RevTag tag_2_5_annotated =
+        revWalk.parseTag(ref_tag_2_5_annotated.getObjectId());
+    git.tag().setName(TAG_2_5_ANNOTATED_TWICE).setObjectId(tag_2_5_annotated)
+        .setAnnotated(true).call();
+  }
+
+  @After
+  public void tearDown() throws Exception {
+    revWalk.release();
+    super.tearDown();
+  }
+
+  @Test
+  public void resolveLatestCommit() throws Exception {
+    // Check tip commit
+    IncludedInDetail detail = resolve(commit_v2_5);
+
+    // Check that only tags and branches which refer the tip are returned
+    expTags.add(TAG_2_5);
+    expTags.add(TAG_2_5_ANNOTATED);
+    expTags.add(TAG_2_5_ANNOTATED_TWICE);
+    assertEquals(expTags, detail.getTags());
+    expBranches.add(BRANCH_2_5);
+    assertEquals(expBranches, detail.getBranches());
+  }
+
+  @Test
+  public void resolveFirstCommit() throws Exception {
+    // Check first commit
+    IncludedInDetail detail = resolve(commit_initial);
+
+    // Check whether all tags and branches are returned
+    expTags.add(TAG_1_0);
+    expTags.add(TAG_1_0_1);
+    expTags.add(TAG_1_3);
+    expTags.add(TAG_2_0);
+    expTags.add(TAG_2_0_1);
+    expTags.add(TAG_2_5);
+    expTags.add(TAG_2_5_ANNOTATED);
+    expTags.add(TAG_2_5_ANNOTATED_TWICE);
+    assertEquals(expTags, detail.getTags());
+
+    expBranches.add(BRANCH_MASTER);
+    expBranches.add(BRANCH_1_0);
+    expBranches.add(BRANCH_1_3);
+    expBranches.add(BRANCH_2_0);
+    expBranches.add(BRANCH_2_5);
+    assertEquals(expBranches, detail.getBranches());
+  }
+
+  @Test
+  public void resolveBetwixtCommit() throws Exception {
+    // Check a commit somewhere in the middle
+    IncludedInDetail detail = resolve(commit_v1_3);
+
+    // Check whether all succeeding tags and branches are returned
+    expTags.add(TAG_1_3);
+    expTags.add(TAG_2_5);
+    expTags.add(TAG_2_5_ANNOTATED);
+    expTags.add(TAG_2_5_ANNOTATED_TWICE);
+    assertEquals(expTags, detail.getTags());
+
+    expBranches.add(BRANCH_1_3);
+    expBranches.add(BRANCH_2_5);
+    assertEquals(expBranches, detail.getBranches());
+  }
+
+  private IncludedInDetail resolve(RevCommit commit) throws Exception {
+    return IncludedInResolver.resolve(db, revWalk, commit);
+  }
+
+  private void assertEquals(List<String> list1, List<String> list2) {
+    Collections.sort(list1);
+    Collections.sort(list2);
+    Assert.assertEquals(list1, list2);
+  }
+
+  private void createAndCheckoutBranch(ObjectId objectId, String branchName)
+      throws IOException {
+    String fullBranchName = "refs/heads/" + branchName;
+    super.createBranch(objectId, fullBranchName);
+    super.checkoutBranch(fullBranchName);
+  }
+}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/DispatchCommandProvider.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/DispatchCommandProvider.java
index c7594bc..ce1da95 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/DispatchCommandProvider.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/DispatchCommandProvider.java
@@ -60,10 +60,7 @@
     return new RegistrationHandle() {
       @Override
       public void remove() {
-        if (!m.remove(name.value(), commandProvider)) {
-          throw new IllegalStateException(String.format(
-              "can not unregister command: %s", name.value()));
-        }
+        m.remove(name.value(), commandProvider);
       }
     };
   }
@@ -71,11 +68,12 @@
   public RegistrationHandle replace(final CommandName name,
       final Provider<Command> cmd) {
     final ConcurrentMap<String, CommandProvider> m = getMap();
-    m.put(name.value(), new CommandProvider(cmd, null));
+    final CommandProvider commandProvider = new CommandProvider(cmd, null);
+    m.put(name.value(), commandProvider);
     return new RegistrationHandle() {
       @Override
       public void remove() {
-        m.remove(name.value(), cmd);
+        m.remove(name.value(), commandProvider);
       }
     };
   }
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ReviewCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ReviewCommand.java
index 714a6ad..7af882e 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ReviewCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ReviewCommand.java
@@ -31,11 +31,11 @@
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.change.Abandon;
 import com.google.gerrit.server.change.ChangeResource;
+import com.google.gerrit.server.change.DeleteDraftPatchSet;
 import com.google.gerrit.server.change.PostReview;
 import com.google.gerrit.server.change.Restore;
 import com.google.gerrit.server.change.RevisionResource;
 import com.google.gerrit.server.change.Submit;
-import com.google.gerrit.server.changedetail.DeleteDraftPatchSet;
 import com.google.gerrit.server.changedetail.PublishDraft;
 import com.google.gerrit.server.config.AllProjectsName;
 import com.google.gerrit.server.project.ChangeControl;
@@ -131,7 +131,7 @@
   private ReviewDb db;
 
   @Inject
-  private DeleteDraftPatchSet.Factory deleteDraftPatchSetFactory;
+  private DeleteDraftPatchSet deleteDraftPatchSetImpl;
 
   @Inject
   private ProjectControl.Factory projectControlFactory;
@@ -285,6 +285,16 @@
             new ChangeResource(ctl), patchSet),
           input);
       }
+
+      if (publishPatchSet) {
+        final ReviewResult result =
+            publishDraftFactory.create(patchSet.getId()).call();
+        handleReviewResultErrors(result);
+      } else if (deleteDraftPatchSet) {
+        deleteDraftPatchSetImpl.apply(new RevisionResource(
+            new ChangeResource(ctl), patchSet),
+            new DeleteDraftPatchSet.Input());
+      }
     } catch (InvalidChangeOperationException e) {
       throw error(e.getMessage());
     } catch (IllegalStateException e) {
@@ -296,16 +306,6 @@
     } catch (ResourceConflictException e) {
       throw error(e.getMessage());
     }
-
-    if (publishPatchSet) {
-      final ReviewResult result =
-          publishDraftFactory.create(patchSet.getId()).call();
-      handleReviewResultErrors(result);
-    } else if (deleteDraftPatchSet) {
-      final ReviewResult result =
-          deleteDraftPatchSetFactory.create(patchSet.getId()).call();
-      handleReviewResultErrors(result);
-    }
   }
 
   private void handleReviewResultErrors(final ReviewResult result) {
diff --git a/gerrit-war/BUCK b/gerrit-war/BUCK
index 03461b8..fa9a88f 100644
--- a/gerrit-war/BUCK
+++ b/gerrit-war/BUCK
@@ -9,11 +9,13 @@
     '//gerrit-httpd:httpd',
     '//gerrit-lucene:lucene',
     '//gerrit-openid:openid',
+    '//gerrit-pgm:init-base',
     '//gerrit-reviewdb:server',
     '//gerrit-server:server',
     '//gerrit-server/src/main/prolog:common',
     '//gerrit-solr:solr',
     '//gerrit-sshd:sshd',
+    '//lib:guava',
     '//lib:gwtorm',
     '//lib/guice:guice',
     '//lib/guice:guice-servlet',
diff --git a/gerrit-war/src/main/java/com/google/gerrit/httpd/SiteInitializer.java b/gerrit-war/src/main/java/com/google/gerrit/httpd/SiteInitializer.java
new file mode 100644
index 0000000..0948c46
--- /dev/null
+++ b/gerrit-war/src/main/java/com/google/gerrit/httpd/SiteInitializer.java
@@ -0,0 +1,87 @@
+// 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.httpd;
+
+import com.google.gerrit.pgm.BaseInit;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+public final class SiteInitializer {
+  private static final Logger log = LoggerFactory
+      .getLogger(SiteInitializer.class);
+
+  final String sitePath;
+  final String initPath;
+
+  SiteInitializer(String sitePath, String initPath) {
+    this.sitePath = sitePath;
+    this.initPath = initPath;
+  }
+
+  public void init() {
+    try {
+
+      if (sitePath != null) {
+        File site = new File(sitePath);
+        log.info(String.format("Initializing site at %s",
+            site.getAbsolutePath()));
+        new BaseInit(site, false).run();
+        return;
+      }
+
+      Connection conn = connectToDb();
+      try {
+        File site = getSiteFromReviewDb(conn);
+        if (site == null && initPath != null) {
+          site = new File(initPath);
+        }
+
+        if (site != null) {
+          log.info(String.format("Initializing site at %s",
+              site.getAbsolutePath()));
+          new BaseInit(site, new ReviewDbDataSourceProvider(), false).run();
+        }
+      } finally {
+        conn.close();
+      }
+    } catch (Exception e) {
+      log.error("Site init failed", e);
+      throw new RuntimeException(e);
+    }
+  }
+
+  private Connection connectToDb() throws SQLException {
+    return new ReviewDbDataSourceProvider().get().getConnection();
+  }
+
+  private File getSiteFromReviewDb(Connection conn) {
+    try {
+      ResultSet rs = conn.createStatement().executeQuery(
+          "select site_path from system_config");
+      if (rs.next()) {
+        return new File(rs.getString(1));
+      }
+      return null;
+    } catch (SQLException e) {
+      return null;
+    }
+  }
+}
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 3c70cf6..bccd0de 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
@@ -115,6 +115,10 @@
         sitePath = new File(path);
       }
 
+      if (System.getProperty("gerrit.init") != null) {
+        new SiteInitializer(path, System.getProperty("gerrit.init_path")).init();
+      }
+
       try {
         dbInjector = createDbInjector();
       } catch (CreationException ce) {
diff --git a/plugins/commit-message-length-validator b/plugins/commit-message-length-validator
index 9f988c6..45f347d 160000
--- a/plugins/commit-message-length-validator
+++ b/plugins/commit-message-length-validator
@@ -1 +1 @@
-Subproject commit 9f988c6e5b1e07f66479f0d4d08537281fa4694f
+Subproject commit 45f347d0e258ef7b871b046bfa96b9f902063b10
diff --git a/plugins/replication b/plugins/replication
index 3fffd16..0c5fef2 160000
--- a/plugins/replication
+++ b/plugins/replication
@@ -1 +1 @@
-Subproject commit 3fffd16403c1d3a4d6f0b13e9f33f4f78df1f6b4
+Subproject commit 0c5fef2a8eda0e76677e5487d2fb8391e1cb126e
diff --git a/plugins/reviewnotes b/plugins/reviewnotes
index 1fac69b..d470050 160000
--- a/plugins/reviewnotes
+++ b/plugins/reviewnotes
@@ -1 +1 @@
-Subproject commit 1fac69b085cf67706a3a124df366d55e1976cd21
+Subproject commit d47005095fcf3bc918a1dda62d21988cc6e470e6
diff --git a/tools/util/query_tester.sh b/tools/util/query_tester.sh
new file mode 100755
index 0000000..25646c2
--- /dev/null
+++ b/tools/util/query_tester.sh
@@ -0,0 +1,81 @@
+#!/bin/bash
+
+usage() {
+    cat <<EOF
+  query_suite run -h <host> -i <suite_dir> -o <output_dir> [-s <suite>]
+
+  Run query test suites
+
+  A query test suite must be a subdirectory of suite_dir and contain
+  either files with queries in them, or if the suite name looks like
+  "values_operator", the files must contain values for the operator
+  instead of full queries.
+
+  The query outputs for each suite will be stored in files named
+  after their suites and input file names under the output_dir.
+
+EOF
+    exit
+}
+
+e() { [ -n "$VERBOSE" ] && echo "$@" >&2 ; "$@" ; }
+
+query()  { # host query
+    local host=$1 query="$2"
+    e ssh -p 29418 "$host" gerrit query --format json "$query"
+}
+
+run_suite() { # host idir odir suite operator
+    local host=$1 idir=$2 odir=$3 suite=$4 operator=$5 qfile files
+
+    mkdir -p "$odir/$suite"
+
+    echo "$suite" | grep -q "^values_" && operator=$(echo "$suite" | sed '-es/^values_//')
+
+    if [ -z "$suite" ] ; then
+        files=($(cd "$idir" ; echo *))
+    else
+        files=($(cd "$idir" ; echo $suite/*))
+    fi
+
+    for qfile in "${files[@]}" ; do
+        if [ -d "$idir/$qfile" ] ; then
+            run_suite "$host" "$idir" "$odir" "$qfile" "$operator"
+        else
+            if [ -n "$operator" ] ; then
+                query "$host" "$operator:{$(< "$idir/$qfile")}" > "$odir/$qfile"
+            else
+                query "$host" "$(< "$idir/$qfile")" > "$odir/$qfile"
+            fi
+        fi
+    done
+}
+
+cmd=''
+while [ $# -gt 0 ] ; do
+    case "$1" in
+        -v) VERBOSE=$1 ;;
+
+        -h) shift ; HOST=$1 ;;
+        -s) shift ; SUITE=$1 ;;
+        -i) shift ; IDIR=$1 ;;
+        -o) shift ; ODIR=$1 ;;
+
+        run) cmd=$1 ;;
+
+        --exec) shift ; "$@" ; exit ;;
+
+        *) usage ;;
+    esac
+    shift
+done
+
+case "$cmd" in
+    run)
+        [ -z "$HOST" ] && usage
+        [ -z "$IDIR" ] && usage
+        [ -z "$ODIR" ] && usage
+        [ -z "$SUITE" ] && SUITE=""
+        run_suite "$HOST" "$IDIR" "$ODIR" "$SUITE" ; exit
+    ;;
+esac