Merge "Cleanup implementation of REST views"
diff --git a/Documentation/cmd-gc.txt b/Documentation/cmd-gc.txt
index 50710e2..3066ca0 100644
--- a/Documentation/cmd-gc.txt
+++ b/Documentation/cmd-gc.txt
@@ -10,6 +10,7 @@
 [verse]
 'ssh' -p <port> <host> 'gerrit gc'
   [--all]
+  [--show-progress]
   <NAME> ...
 
 DESCRIPTION
@@ -46,6 +47,9 @@
 	If specified the Git garbage collection is run for all projects
 	sequentially.
 
+--show-progress::
+	If specified progress information is shown.
+
 EXAMPLES
 --------
 
diff --git a/Documentation/dev-plugins.txt b/Documentation/dev-plugins.txt
index 1745e67..d1c79ac 100644
--- a/Documentation/dev-plugins.txt
+++ b/Documentation/dev-plugins.txt
@@ -497,9 +497,9 @@
 $ ssh -p 29418 review.example.com sh ps
 ----
 
-[[configuration]]
-Configuration
--------------
+[[simple-configuration]]
+Simple Configuration in `gerrit.config`
+---------------------------------------
 
 In Gerrit, global configuration is stored in the `gerrit.config` file.
 If a plugin needs global configuration, this configuration should be
@@ -509,12 +509,12 @@
 plugins that have a simple configuration that only consists of
 key-value pairs. With this approach it is not possible to have
 subsections in the plugin configuration. Plugins that require a complex
-configuration need to store their configuration in their own
-configuration file where they can make use of subsections. On the other
-hand storing the plugin configuration in a 'plugin' subsection in the
-`gerrit.config` file has the advantage that administrators have all
-configuration parameters in one file, instead of having one
-configuration file per plugin.
+configuration need to store their configuration in their
+link:#configuration[own configuration file] where they can make use of
+subsections. On the other hand storing the plugin configuration in a
+'plugin' subsection in the `gerrit.config` file has the advantage that
+administrators have all configuration parameters in one file, instead
+of having one configuration file per plugin.
 
 To avoid conflicts with other plugins, it is recommended that plugins
 only use the `plugin` subsection with their own name. For example the
@@ -541,9 +541,52 @@
                      .getString("language", "English");
 ----
 
-[[project-specific-configuration]]
-Project Specific Configuration
-------------------------------
+[[configuration]]
+Configuration in own config file
+--------------------------------
+
+Plugins can store their configuration in an own configuration file.
+This makes sense if the plugin configuration is rather complex and
+requires the usage of subsections. Plugins that have a simple
+key-value pair configuration can store their configuration in a
+link:#simple-configuration[`plugin` subsection of the `gerrit.config`
+file].
+
+The plugin configuration file must be named after the plugin and must
+be located in the `etc` folder of the review site. For example a
+configuration file for a `default-reviewer` plugin could look like
+this:
+
+.$site_path/etc/default-reviewer.config
+----
+[branch "refs/heads/master"]
+  reviewer = Project Owners
+  reviewer = john.doe@example.com
+[match "file:^.*\.txt"]
+  reviewer = My Info Developers
+----
+
+Via the `com.google.gerrit.server.config.PluginConfigFactory` class a
+plugin can easily access its configuration:
+
+[source,java]
+----
+@Inject
+private com.google.gerrit.server.config.PluginConfigFactory cfg;
+
+[...]
+
+String[] reviewers = cfg.getGlobalPluginConfig("default-reviewer")
+                        .getStringList("branch", "refs/heads/master", "reviewer");
+----
+
+The plugin configuration is loaded only once and is then cached.
+Similar to changes in 'gerrit.config', changes to the plugin
+configuration file will only become effective after a Gerrit restart.
+
+[[simple-project-specific-configuration]]
+Simple Project Specific Configuration in `project.config`
+---------------------------------------------------------
 
 In Gerrit, project specific configuration is stored in the project's
 `project.config` file on the `refs/meta/config` branch.  If a plugin
@@ -555,12 +598,12 @@
 plugins that have a simple configuration that only consists of
 key-value pairs. With this approach it is not possible to have
 subsections in the plugin configuration. Plugins that require a complex
-configuration need to store their configuration in their own
-configuration file where they can make use of subsections. On the other
-hand storing the plugin configuration in a 'plugin' subsection in the
-`project.config` file has the advantage that project owners have all
-configuration parameters in one file, instead of having one
-configuration file per plugin.
+configuration need to store their configuration in their
+link:#project-specific-configuration[own configuration file] where they
+can make use of subsections. On the other hand storing the plugin
+configuration in a 'plugin' subsection in the `project.config` file has
+the advantage that project owners have all configuration parameters in
+one file, instead of having one configuration file per plugin.
 
 To avoid conflicts with other plugins, it is recommended that plugins
 only use the `plugin` subsection with their own name. For example the
@@ -605,6 +648,63 @@
 `refs/meta/config` branch, editing the `project.config` file and
 pushing the commit back.
 
+[[project-specific-configuration]]
+Project Specific Configuration in own config file
+-------------------------------------------------
+
+Plugins can store their project specific configuration in an own
+configuration file in the projects `refs/meta/config` branch.
+This makes sense if the plugins project specific configuration is
+rather complex and requires the usage of subsections. Plugins that
+have a simple key-value pair configuration can store their project
+specific configuration in a link:#simple-project-specific-configuration[
+`plugin` subsection of the `project.config` file].
+
+The plugin configuration file in the `refs/meta/config` branch must be
+named after the plugin. For example a configuration file for a
+`default-reviewer` plugin could look like this:
+
+.default-reviewer.config
+----
+[branch "refs/heads/master"]
+  reviewer = Project Owners
+  reviewer = john.doe@example.com
+[match "file:^.*\.txt"]
+  reviewer = My Info Developers
+----
+
+Via the `com.google.gerrit.server.config.PluginConfigFactory` class a
+plugin can easily access its project specific configuration:
+
+[source,java]
+----
+@Inject
+private com.google.gerrit.server.config.PluginConfigFactory cfg;
+
+[...]
+
+String[] reviewers = cfg.getProjectPluginConfig(project, "default-reviewer")
+                        .getStringList("branch", "refs/heads/master", "reviewer");
+----
+
+It is also possible to get missing configuration parameters inherited
+from the parent projects:
+
+[source,java]
+----
+@Inject
+private com.google.gerrit.server.config.PluginConfigFactory cfg;
+
+[...]
+
+String[] reviewers = cfg.getFromPluginConfigWithInheritance(project, "default-reviewer")
+                        .getStringList("branch", "refs/heads/master", "reviewer");
+----
+
+Project owners can edit the project configuration by fetching the
+`refs/meta/config` branch, editing the `<plugin-name>.config` file and
+pushing the commit back.
+
 [[capabilities]]
 Plugin Owned Capabilities
 -------------------------
@@ -1037,6 +1137,51 @@
 Gerrit-Module: com.googlesource.gerrit.plugins.helloworld.HelloWorldModule
 ----
 
+It is also possible to show some menu entries only if the user has a
+certain capability:
+
+[source,java]
+----
+public class MyTopMenuExtension implements TopMenu {
+  private final String pluginName;
+  private final Provider<CurrentUser> userProvider;
+  private final List<MenuEntry> menuEntries;
+
+  @Inject
+  public MyTopMenuExtension(@PluginName String pluginName,
+      Provider<CurrentUser> userProvider) {
+    this.pluginName = pluginName;
+    this.userProvider = userProvider;
+    menuEntries = new ArrayList<TopMenu.MenuEntry>();
+
+    // add menu entry that is only visible to users with a certain capability
+    if (canSeeMenuEntry()) {
+      menuEntries.add(new MenuEntry("Top Menu Entry", Collections
+          .singletonList(new MenuItem("Gerrit", "http://gerrit.googlecode.com/"))));
+    }
+
+    // add menu entry that is visible to all users (even anonymous users)
+    menuEntries.add(new MenuEntry("Top Menu Entry", Collections
+          .singletonList(new MenuItem("Documentation", "/plugins/myplugin/"))));
+  }
+
+  private boolean canSeeMenuEntry() {
+    if (userProvider.get().isIdentifiedUser()) {
+      CapabilityControl ctl = userProvider.get().getCapabilities();
+      return ctl.canPerform(pluginName + "-" + MyCapability.ID)
+          || ctl.canAdministrateServer();
+    } else {
+      return false;
+    }
+  }
+
+  @Override
+  public List<MenuEntry> getEntries() {
+    return menuEntries;
+  }
+}
+----
+
 [[gwt_ui_extension]]
 GWT UI Extension
 ----------------
@@ -1049,6 +1194,246 @@
 The generated GWT plugin has a link:#top-menu-extensions[top menu] that
 opens a GWT dialog box when the user clicks on it.
 
+In addition to the Gerrit-Plugin API a GWT plugin depends on
+`gerrit-plugin-gwtui`. This dependency must be specified in the
+`pom.xml`:
+
+[source,xml]
+----
+<dependency>
+  <groupId>com.google.gerrit</groupId>
+  <artifactId>gerrit-plugin-gwtui</artifactId>
+  <version>${Gerrit-ApiVersion}</version>
+</dependency>
+----
+
+A GWT plugin must contain a GWT module file, e.g. `HelloPlugin.gwt.xml`,
+that bundles together all the configuration settings of the GWT plugin:
+
+[source,xml]
+----
+<?xml version="1.0" encoding="UTF-8"?>
+<module rename-to="hello_gwt_plugin">
+  <!-- Inherit the core Web Toolkit stuff. -->
+  <inherits name="com.google.gwt.user.User"/>
+  <!-- Other module inherits -->
+  <inherits name="com.google.gerrit.Plugin"/>
+  <inherits name="com.google.gwt.http.HTTP"/>
+  <!-- Using GWT built-in themes adds a number of static -->
+  <!-- resources to the plugin. No theme inherits lines were -->
+  <!-- added in order to make this plugin as simple as possible -->
+  <!-- Specify the app entry point class. -->
+  <entry-point class="${package}.client.HelloPlugin"/>
+  <stylesheet src="hello.css"/>
+</module>
+----
+
+The GWT module must inherit `com.google.gerrit.Plugin` and
+`com.google.gwt.http.HTTP`.
+
+To register the GWT module a `GwtPlugin` needs to be bound.
+
+If no Guice modules are declared in the manifest, the GWT plugin may
+use auto-registration by using the `@Listen` annotation:
+
+[source,java]
+----
+@Listen
+public class MyExtension extends GwtPlugin {
+  public MyExtension() {
+    super("hello_gwt_plugin");
+  }
+}
+----
+
+Otherwise the binding must be done in an `HttpModule`:
+
+[source,java]
+----
+public class HttpModule extends HttpPluginModule {
+
+  @Override
+  protected void configureServlets() {
+    DynamicSet.bind(binder(), WebUiPlugin.class)
+        .toInstance(new GwtPlugin("hello_gwt_plugin"));
+  }
+}
+----
+
+The HTTP module above must be declared in the `pom.xml` for Maven
+driven plugins:
+
+[source,xml]
+----
+<manifestEntries>
+  <Gerrit-HttpModule>com.googlesource.gerrit.plugins.myplugin.HttpModule</Gerrit-HttpModule>
+</manifestEntries>
+----
+
+It is important that the module name that is provided to the
+`GwtPlugin` matches the GWT module contained in the plugin. The name
+of the GWT module can be explicitly set in the GWT module file by
+specifying the `rename-to` attribute on the module.
+
+[source,xml]
+----
+<module rename-to="hello_gwt_plugin">
+----
+
+The actual GWT code must be implemented in a class that extends
+`com.google.gerrit.plugin.client.Plugin`:
+
+[source,java]
+----
+public class HelloPlugin extends Plugin {
+
+  @Override
+  public void onModuleLoad() {
+    // Create the dialog box
+    final DialogBox dialogBox = new DialogBox();
+
+    // The content of the dialog comes from a User specified Preference
+    dialogBox.setText("Hello from GWT Gerrit UI plugin");
+    dialogBox.setAnimationEnabled(true);
+    Button closeButton = new Button("Close");
+    VerticalPanel dialogVPanel = new VerticalPanel();
+    dialogVPanel.setWidth("100%");
+    dialogVPanel.setHorizontalAlignment(VerticalPanel.ALIGN_CENTER);
+    dialogVPanel.add(closeButton);
+
+    closeButton.addClickHandler(new ClickHandler() {
+      public void onClick(ClickEvent event) {
+        dialogBox.hide();
+      }
+    });
+
+    // Set the contents of the Widget
+    dialogBox.setWidget(dialogVPanel);
+
+    RootPanel rootPanel = RootPanel.get(HelloMenu.MENU_ID);
+    rootPanel.getElement().removeAttribute("href");
+    rootPanel.addDomHandler(new ClickHandler() {
+        @Override
+        public void onClick(ClickEvent event) {
+          dialogBox.center();
+          dialogBox.show();
+        }
+    }, ClickEvent.getType());
+  }
+}
+----
+
+This class must be set as entry point in the GWT module:
+
+[source,xml]
+----
+<entry-point class="${package}.client.HelloPlugin"/>
+----
+
+In addition this class must be defined as module in the `pom.xml` for the
+`gwt-maven-plugin` and the `webappDirectory` option of `gwt-maven-plugin`
+must be set to `${project.build.directory}/classes/static`:
+
+[source,xml]
+----
+<plugin>
+  <groupId>org.codehaus.mojo</groupId>
+  <artifactId>gwt-maven-plugin</artifactId>
+  <version>2.5.1</version>
+  <configuration>
+    <module>com.googlesource.gerrit.plugins.myplugin.HelloPlugin</module>
+    <disableClassMetadata>true</disableClassMetadata>
+    <disableCastChecking>true</disableCastChecking>
+    <webappDirectory>${project.build.directory}/classes/static</webappDirectory>
+  </configuration>
+  <executions>
+    <execution>
+      <goals>
+        <goal>compile</goal>
+      </goals>
+    </execution>
+  </executions>
+</plugin>
+----
+
+To attach a GWT widget defined by the plugin to the Gerrit core UI
+`com.google.gwt.user.client.ui.RootPanel` can be used to manipulate the
+Gerrit core widgets:
+
+[source,java]
+----
+RootPanel rootPanel = RootPanel.get(HelloMenu.MENU_ID);
+rootPanel.getElement().removeAttribute("href");
+rootPanel.addDomHandler(new ClickHandler() {
+  @Override
+  public void onClick(ClickEvent event) {
+    dialogBox.center();
+    dialogBox.show();
+  }
+}, ClickEvent.getType());
+----
+
+GWT plugins can come with their own css file. This css file must have a
+unique name and must be registered in the GWT module:
+
+[source,xml]
+----
+<stylesheet src="hello.css"/>
+----
+
+If a GWT plugin wants to invoke the Gerrit REST API it can use
+`com.google.gerrit.plugin.client.rpc.RestApi` to contruct the URL
+path and to trigger the REST calls.
+
+Example for invoking a Gerrit core REST endpoint:
+
+[source,java]
+----
+new RestApi("projects").id(projectName).view("description")
+    .put("new description", new AsyncCallback<JavaScriptObject>() {
+
+  @Override
+  public void onSuccess(JavaScriptObject result) {
+    // TODO
+  }
+
+  @Override
+  public void onFailure(Throwable caught) {
+    // never invoked
+  }
+});
+----
+
+Example for invoking a REST endpoint defined by a plugin:
+
+[source,java]
+----
+new RestApi("projects").id(projectName).view("myplugin", "myview")
+    .get(new AsyncCallback<JavaScriptObject>() {
+
+  @Override
+  public void onSuccess(JavaScriptObject result) {
+    // TODO
+  }
+
+  @Override
+  public void onFailure(Throwable caught) {
+    // never invoked
+  }
+});
+----
+
+The `onFailure(Throwable)` of the provided callback is never invoked.
+If an error occurs, it is shown in an error dialog.
+
+In order to be able to do REST calls the GWT module must inherit
+`com.google.gwt.json.JSON`:
+
+[source,xml]
+----
+<inherits name="com.google.gwt.json.JSON"/>
+----
+
 [[http]]
 HTTP Servlets
 -------------
diff --git a/Documentation/js-api.txt b/Documentation/js-api.txt
index 0e470e7..7411026 100644
--- a/Documentation/js-api.txt
+++ b/Documentation/js-api.txt
@@ -425,6 +425,13 @@
   comes with an onkeypress handler installed to play nicely with
   Gerrit's keyboard binding system.
 
+* `select(a,i)`: a new `<select>` element containing one `<option>`
+  element for each entry in the provided array `a`.  The option with
+  the index `i` will be pre-selected in the drop-down-list.
+
+* `selected(s)`: returns the text of the `<option>` element that is
+  currently selected in the provided `<select>` element `s`.
+
 * `span(...)`: a new `<span>` wrapping the (optional) arguments.
 
 * `msg(label)`: a new label.
diff --git a/Documentation/rest-api-accounts.txt b/Documentation/rest-api-accounts.txt
index 52c6ac9..f72c154 100644
--- a/Documentation/rest-api-accounts.txt
+++ b/Documentation/rest-api-accounts.txt
@@ -924,6 +924,7 @@
   }
 ----
 
+[[get-starred-changes]]
 Get Starred Changes
 ~~~~~~~~~~~~~~~~~~~
 [verse]
@@ -967,6 +968,7 @@
   ]
 ----
 
+[[star-change]]
 Star Change
 ~~~~~~~~~~~
 [verse]
@@ -986,6 +988,7 @@
   HTTP/1.1 204 No Content
 ----
 
+[[unstar-change]]
 Unstar Change
 ~~~~~~~~~~~~~
 [verse]
diff --git a/Documentation/rest-api-projects.txt b/Documentation/rest-api-projects.txt
index 1ac232a..c2e937a 100644
--- a/Documentation/rest-api-projects.txt
+++ b/Documentation/rest-api-projects.txt
@@ -552,9 +552,17 @@
 
 Run the Git garbage collection for the repository of a project.
 
+Options for the Git garbage collection can be specified in the
+request body as a link:#gc-input[GCInput] entity.
+
 .Request
 ----
   POST /projects/plugins%2Freplication/gc HTTP/1.0
+  Content-Type: application/json;charset=UTF-8
+
+  {
+    "show_progress": true
+  }
 ----
 
 The response is the streamed output of the garbage collection.
@@ -1317,6 +1325,19 @@
 Tokens such as `${project}` are not resolved.
 |===========================
 
+[[gc-input]]
+GCInput
+~~~~~~~
+The `GCInput` entity contains information to run the Git garbage
+collection.
+
+[options="header",width="50%",cols="1,^2,4"]
+|=============================
+|Field Name      ||Description
+|`show_progress` |`false` if not set|
+Whether progress information should be shown.
+|=============================
+
 [[head-input]]
 HeadInput
 ~~~~~~~~~
diff --git a/Documentation/user-search.txt b/Documentation/user-search.txt
index 87ea1cd..cb3a3b6 100644
--- a/Documentation/user-search.txt
+++ b/Documentation/user-search.txt
@@ -118,6 +118,12 @@
 link:http://www.brics.dk/automaton/[dk.brics.automaton
 library] is used for evaluation of such patterns.
 
+[[parentproject]]
+parentproject:'PROJECT'::
++
+Changes occurring in 'PROJECT' or in one of the child projects of
+'PROJECT'.
+
 [[branch]]
 branch:'BRANCH'::
 +
diff --git a/ReleaseNotes/ReleaseNotes-2.8.txt b/ReleaseNotes/ReleaseNotes-2.8.txt
index 27ed297..d183cca 100644
--- a/ReleaseNotes/ReleaseNotes-2.8.txt
+++ b/ReleaseNotes/ReleaseNotes-2.8.txt
@@ -278,6 +278,15 @@
 * link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.8/rest-api-accounts.html#get-username[
 Get account username]
 
+* link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.8/rest-api-accounts.html#get-starred-changes[
+Get starred changes]
+
+* link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.8/rest-api-accounts.html#star-change[
+Star change]
+
+* link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.8/rest-api-accounts.html#unstar-change[
+Unstar change]
+
 Changes
 ^^^^^^^
 
@@ -741,5 +750,7 @@
 * Update guava to 15.0
 * Update H2 to 1.3.173
 * Update bouncycastle to 1.44
+* Update Apache Mina to 2.0.7
+* Update Apache SSHD to 0.9.0.201311081
 * asciidoctor 0.1.4 is now required to build the documentation
 * jsr305 library was removed
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/InMemoryTestingDatabaseModule.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/InMemoryTestingDatabaseModule.java
index 5576c4f..8548b5c 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/InMemoryTestingDatabaseModule.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/InMemoryTestingDatabaseModule.java
@@ -34,7 +34,6 @@
 import com.google.gwtorm.server.OrmException;
 import com.google.gwtorm.server.OrmRuntimeException;
 import com.google.gwtorm.server.SchemaFactory;
-import com.google.inject.AbstractModule;
 import com.google.inject.Inject;
 import com.google.inject.Provides;
 import com.google.inject.Singleton;
@@ -46,7 +45,7 @@
 
 import java.io.File;
 
-class InMemoryTestingDatabaseModule extends AbstractModule {
+class InMemoryTestingDatabaseModule extends LifecycleModule {
   private final Config cfg;
 
   InMemoryTestingDatabaseModule(Config cfg) {
@@ -71,12 +70,7 @@
     bind(new TypeLiteral<SchemaFactory<ReviewDb>>() {})
       .to(InMemoryDatabase.class);
 
-    install(new LifecycleModule() {
-      @Override
-      protected void configure() {
-        listener().to(CreateDatabase.class);
-      }
-    });
+    listener().to(CreateDatabase.class);
 
     bind(SitePaths.class);
     bind(TrackingFooters.class)
@@ -126,4 +120,4 @@
       mem.drop();
     }
   }
-}
\ No newline at end of file
+}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/GitUtil.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/GitUtil.java
index c17598f..3d1b009 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/GitUtil.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/GitUtil.java
@@ -28,6 +28,7 @@
 import org.eclipse.jgit.api.CheckoutCommand;
 import org.eclipse.jgit.api.CloneCommand;
 import org.eclipse.jgit.api.CommitCommand;
+import org.eclipse.jgit.api.FetchCommand;
 import org.eclipse.jgit.api.Git;
 import org.eclipse.jgit.api.PushCommand;
 import org.eclipse.jgit.api.errors.GitAPIException;
@@ -105,10 +106,15 @@
   }
 
   public static Git cloneProject(String url) throws GitAPIException, IOException {
+    return cloneProject(url, true);
+  }
+
+  public static Git cloneProject(String url, boolean checkout) throws GitAPIException, IOException {
     final File gitDir = TempFileUtil.createTempDirectory();
     final CloneCommand cloneCmd = Git.cloneRepository();
     cloneCmd.setURI(url);
     cloneCmd.setDirectory(gitDir);
+    cloneCmd.setNoCheckout(!checkout);
     return cloneCmd.call();
   }
 
@@ -178,6 +184,12 @@
     }
   }
 
+  public static void fetch(Git git, String spec) throws GitAPIException {
+    FetchCommand fetch = git.fetch();
+    fetch.setRefSpecs(new RefSpec(spec));
+    fetch.call();
+  }
+
   public static void checkout(Git git, String name) throws GitAPIException {
     CheckoutCommand checkout = git.checkout();
     checkout.setName(name);
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ProjectLevelConfigIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ProjectLevelConfigIT.java
new file mode 100644
index 0000000..9fd7568
--- /dev/null
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ProjectLevelConfigIT.java
@@ -0,0 +1,147 @@
+// 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.rest.project;
+
+import static com.google.gerrit.acceptance.git.GitUtil.checkout;
+import static com.google.gerrit.acceptance.git.GitUtil.cloneProject;
+import static com.google.gerrit.acceptance.git.GitUtil.createProject;
+import static com.google.gerrit.acceptance.git.GitUtil.fetch;
+import static com.google.gerrit.acceptance.git.GitUtil.initSsh;
+import static org.junit.Assert.assertEquals;
+
+import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.AccountCreator;
+import com.google.gerrit.acceptance.SshSession;
+import com.google.gerrit.acceptance.TestAccount;
+import com.google.gerrit.acceptance.git.PushOneCommit;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.config.AllProjectsNameProvider;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.project.ProjectCache;
+import com.google.gerrit.server.project.ProjectState;
+import com.google.gwtorm.server.SchemaFactory;
+import com.google.inject.Inject;
+
+import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.api.errors.GitAPIException;
+import org.eclipse.jgit.lib.Config;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.IOException;
+
+public class ProjectLevelConfigIT extends AbstractDaemonTest {
+
+  @Inject
+  private AccountCreator accounts;
+
+  @Inject
+  private SchemaFactory<ReviewDb> reviewDbProvider;
+
+  @Inject
+  private ProjectCache projectCache;
+
+  @Inject
+  private AllProjectsNameProvider allProjects;
+
+  private ReviewDb db;
+  private TestAccount admin;
+  private SshSession sshSession;
+  private String project;
+  private Git git;
+
+  @Before
+  public void setUp() throws Exception {
+    admin = accounts.admin();
+    initSsh(admin);
+    sshSession = new SshSession(server, admin);
+
+    project = "p";
+    createProject(sshSession, project, null, true);
+    git = cloneProject(sshSession.getUrl() + "/" + project);
+    fetch(git, GitRepositoryManager.REF_CONFIG + ":refs/heads/config");
+    checkout(git, "refs/heads/config");
+
+    db = reviewDbProvider.open();
+  }
+
+  @After
+  public void cleanup() {
+    db.close();
+  }
+
+  @Test
+  public void accessProjectSpecificConfig() throws GitAPIException, IOException {
+    String configName = "test.config";
+    Config cfg = new Config();
+    cfg.setString("s1", null, "k1", "v1");
+    cfg.setString("s2", "ss", "k2", "v2");
+    PushOneCommit push =
+        new PushOneCommit(db, admin.getIdent(), "Create Project Level Config",
+            configName, cfg.toText());
+    push.to(git, GitRepositoryManager.REF_CONFIG);
+
+    ProjectState state = projectCache.get(new Project.NameKey(project));
+    assertEquals(cfg.toText(), state.getConfig(configName).get().toText());
+  }
+
+  @Test
+  public void nonExistingConfig() {
+    ProjectState state = projectCache.get(new Project.NameKey(project));
+    assertEquals("", state.getConfig("test.config").get().toText());
+  }
+
+  @Test
+  public void withInheritance() throws GitAPIException, IOException {
+    String configName = "test.config";
+
+    Config parentCfg = new Config();
+    parentCfg.setString("s1", null, "k1", "parentValue1");
+    parentCfg.setString("s1", null, "k2", "parentValue2");
+    parentCfg.setString("s2", "ss", "k3", "parentValue3");
+    parentCfg.setString("s2", "ss", "k4", "parentValue4");
+
+    Git parentGit =
+        cloneProject(sshSession.getUrl() + "/" + allProjects.get().get(), false);
+    fetch(parentGit, GitRepositoryManager.REF_CONFIG + ":refs/heads/config");
+    checkout(parentGit, "refs/heads/config");
+    PushOneCommit push =
+        new PushOneCommit(db, admin.getIdent(), "Create Project Level Config",
+            configName, parentCfg.toText());
+    push.to(parentGit, GitRepositoryManager.REF_CONFIG);
+
+    Config cfg = new Config();
+    cfg.setString("s1", null, "k1", "childValue1");
+    cfg.setString("s2", "ss", "k3", "childValue2");
+    push = new PushOneCommit(db, admin.getIdent(), "Create Project Level Config",
+        configName, cfg.toText());
+    push.to(git, GitRepositoryManager.REF_CONFIG);
+
+    ProjectState state = projectCache.get(new Project.NameKey(project));
+
+    Config expectedCfg = new Config();
+    expectedCfg.setString("s1", null, "k1", "childValue1");
+    expectedCfg.setString("s1", null, "k2", "parentValue2");
+    expectedCfg.setString("s2", "ss", "k3", "childValue2");
+    expectedCfg.setString("s2", "ss", "k4", "parentValue4");
+
+    assertEquals(expectedCfg.toText(), state.getConfig(configName)
+        .getWithInheritance().toText());
+
+    assertEquals(cfg.toText(), state.getConfig(configName).get().toText());
+  }
+}
diff --git a/gerrit-cache-h2/src/main/java/com/google/gerrit/server/cache/h2/DefaultCacheFactory.java b/gerrit-cache-h2/src/main/java/com/google/gerrit/server/cache/h2/DefaultCacheFactory.java
index 1c850d1..4478cc9 100644
--- a/gerrit-cache-h2/src/main/java/com/google/gerrit/server/cache/h2/DefaultCacheFactory.java
+++ b/gerrit-cache-h2/src/main/java/com/google/gerrit/server/cache/h2/DefaultCacheFactory.java
@@ -27,7 +27,6 @@
 import com.google.gerrit.server.cache.PersistentCacheFactory;
 import com.google.gerrit.server.cache.h2.H2CacheImpl.ValueHolder;
 import com.google.gerrit.server.config.ConfigUtil;
-import com.google.gerrit.server.config.FactoryModule;
 import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.inject.Inject;
 
@@ -39,13 +38,7 @@
   public static class Module extends LifecycleModule {
     @Override
     protected void configure() {
-      install(new FactoryModule() {
-        @Override
-        protected void configure() {
-          factory(ForwardingRemovalListener.Factory.class);
-        }
-      });
-
+      factory(ForwardingRemovalListener.Factory.class);
       bind(DefaultCacheFactory.class);
       bind(MemoryCacheFactory.class).to(DefaultCacheFactory.class);
       bind(PersistentCacheFactory.class).to(H2CacheFactory.class);
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/ProjectAccess.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/ProjectAccess.java
index ee6cc95..dd6c22b 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/ProjectAccess.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/ProjectAccess.java
@@ -28,6 +28,7 @@
   protected Set<String> ownerOf;
   protected boolean isConfigVisible;
   protected boolean canUpload;
+  protected boolean canChangeParent;
   protected LabelTypes labelTypes;
   protected Map<String, String> capabilities;
 
@@ -107,6 +108,14 @@
     this.canUpload = canUpload;
   }
 
+  public boolean canChangeParent() {
+    return canChangeParent;
+  }
+
+  public void setCanChangeParent(boolean canChangeParent) {
+    this.canChangeParent = canChangeParent;
+  }
+
   public LabelTypes getLabelTypes() {
     return labelTypes;
   }
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/ProjectAdminService.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/ProjectAdminService.java
index 44f60861..652acac 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/ProjectAdminService.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/ProjectAdminService.java
@@ -33,11 +33,11 @@
   @Audit
   @SignInRequired
   void changeProjectAccess(Project.NameKey projectName, String baseRevision,
-      String message, List<AccessSection> sections,
+      String message, List<AccessSection> sections, Project.NameKey parentProjectName,
       AsyncCallback<ProjectAccess> callback);
 
   @SignInRequired
   void reviewProjectAccess(Project.NameKey projectName, String baseRevision,
-      String message, List<AccessSection> sections,
+      String message, List<AccessSection> sections, Project.NameKey parentProjectName,
       AsyncCallback<Change.Id> callback);
 }
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/ReviewerInfo.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/ReviewerInfo.java
index 063f13b..28a8340 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/ReviewerInfo.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/ReviewerInfo.java
@@ -51,7 +51,7 @@
       if (accountInfo.getPreferredEmail() != null) {
         return accountInfo.getPreferredEmail();
       }
-      return accountInfo.getFullName().toString();
+      return accountInfo.getFullName();
     }
     return groupReference.getName();
   }
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/errors/UpdateParentFailedException.java b/gerrit-common/src/main/java/com/google/gerrit/common/errors/UpdateParentFailedException.java
new file mode 100644
index 0000000..320d055
--- /dev/null
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/errors/UpdateParentFailedException.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.common.errors;
+
+/** Error indicating that updating a parent project failed. */
+public class UpdateParentFailedException extends Exception {
+  private static final long serialVersionUID = 1L;
+
+  public static final String MESSAGE = "Update Parent Project Failed: ";
+
+  public UpdateParentFailedException(final String message,
+      final Throwable why) {
+    super(MESSAGE + ": " + message, why);
+  }
+}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/ReviewInput.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/ReviewInput.java
index 969c23d..5f106fc 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/ReviewInput.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/ReviewInput.java
@@ -60,15 +60,15 @@
   public String onBehalfOf;
 
   public static enum DraftHandling {
-    DELETE, PUBLISH, KEEP;
+    DELETE, PUBLISH, KEEP
   }
 
   public static enum NotifyHandling {
-    NONE, OWNER, OWNER_REVIEWERS, ALL;
+    NONE, OWNER, OWNER_REVIEWERS, ALL
   }
 
   public static enum Side {
-    PARENT, REVISION;
+    PARENT, REVISION
   }
 
   public static class Comment {
diff --git a/gerrit-gwtdebug/src/main/java/com/google/gerrit/gwtdebug/GerritDebugLauncher.java b/gerrit-gwtdebug/src/main/java/com/google/gerrit/gwtdebug/GerritDebugLauncher.java
index a2a770e..5770f58 100644
--- a/gerrit-gwtdebug/src/main/java/com/google/gerrit/gwtdebug/GerritDebugLauncher.java
+++ b/gerrit-gwtdebug/src/main/java/com/google/gerrit/gwtdebug/GerritDebugLauncher.java
@@ -368,7 +368,7 @@
 
     AbstractConnector connector = getConnector();
     if (bindAddress != null) {
-      connector.setHost(bindAddress.toString());
+      connector.setHost(bindAddress);
     }
     connector.setPort(port);
 
diff --git a/gerrit-gwtui/BUCK b/gerrit-gwtui/BUCK
index 1ae823e..0c873ee 100644
--- a/gerrit-gwtui/BUCK
+++ b/gerrit-gwtui/BUCK
@@ -1,4 +1,5 @@
 include_defs('//gerrit-gwtui/gwt.defs')
+include_defs('//tools/gwt-constants.defs')
 
 genrule(
   name = 'ui_optdbg',
@@ -25,13 +26,7 @@
 gwt_application(
   name = 'ui_opt',
   module_target = MODULE,
-  compiler_opts = [
-    '-strict',
-    '-style', 'OBF',
-    '-optimize', '9',
-    '-XdisableClassMetadata',
-    '-XdisableCastChecking',
-  ],
+  compiler_opts = GWT_COMPILER_OPTS,
   deps = APP_DEPS,
 )
 
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritMessages.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritMessages.java
index cbb5513..21da8ce 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritMessages.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritMessages.java
@@ -34,4 +34,6 @@
 
   String pluginFailed(String scriptPath);
   String cannotDownloadPlugin(String scriptPath);
+
+  String parentUpdateFailed(String message);
 }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritMessages.properties b/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritMessages.properties
index 5ab8b3b..8196e98 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritMessages.properties
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritMessages.properties
@@ -15,3 +15,5 @@
 
 pluginFailed = Plugin JavaScript {0} failed to load
 cannotDownloadPlugin = Cannot download JavaScript plugin from: {0}.
+
+parentUpdateFailed = Setting parent project failed: {0}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/SearchSuggestOracle.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/SearchSuggestOracle.java
index a526c59..4c69300 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/SearchSuggestOracle.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/SearchSuggestOracle.java
@@ -22,13 +22,13 @@
 
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.Collections;
 import java.util.List;
 import java.util.TreeSet;
 
 public class SearchSuggestOracle extends HighlightSuggestOracle {
   private static final List<ParamSuggester> paramSuggester = Arrays.asList(
-      new ParamSuggester("project:", new ProjectNameSuggestOracle()),
+      new ParamSuggester(Arrays.asList("project:", "parentproject:"),
+          new ProjectNameSuggestOracle()),
       new ParamSuggester(Arrays.asList("owner:", "reviewer:"),
           new AccountSuggestOracle() {
             @Override
@@ -82,6 +82,7 @@
     suggestions.add("comment:");
     suggestions.add("conflicts:");
     suggestions.add("project:");
+    suggestions.add("parentproject:");
     suggestions.add("branch:");
     suggestions.add("topic:");
     suggestions.add("ref:");
@@ -207,11 +208,6 @@
     private final List<String> operators;
     private final SuggestOracle parameterSuggestionOracle;
 
-    ParamSuggester(final String operator,
-        final SuggestOracle parameterSuggestionOracle) {
-      this(Collections.singletonList(operator), parameterSuggestionOracle);
-    }
-
     ParamSuggester(final List<String> operators,
         final SuggestOracle parameterSuggestionOracle) {
       this.operators = operators;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectAccessEditor.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectAccessEditor.java
index 49a9aa4..567f14c 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectAccessEditor.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectAccessEditor.java
@@ -18,6 +18,7 @@
 import com.google.gerrit.client.Gerrit;
 import com.google.gerrit.client.GitwebLink;
 import com.google.gerrit.client.ui.Hyperlink;
+import com.google.gerrit.client.ui.ParentProjectBox;
 import com.google.gerrit.common.data.AccessSection;
 import com.google.gerrit.common.data.ProjectAccess;
 import com.google.gerrit.reviewdb.client.Branch;
@@ -56,6 +57,10 @@
   Hyperlink parentProject;
 
   @UiField
+  @Editor.Ignore
+  ParentProjectBox parentProjectBox;
+
+  @UiField
   DivElement history;
 
   @UiField
@@ -106,6 +111,11 @@
       parentProject.setText(parent.get());
       parentProject.setTargetHistoryToken( //
           Dispatcher.toProjectAdmin(parent, ProjectScreen.ACCESS));
+
+      parentProjectBox.setVisible(editing && value.canChangeParent());
+      parentProjectBox.setProject(value.getProjectName());
+      parentProjectBox.setParentProject(value.getInheritsFrom());
+      parentProject.setVisible(!parentProjectBox.isVisible());
     } else {
       inheritsFrom.getStyle().setDisplay(Display.NONE);
     }
@@ -135,6 +145,7 @@
       }
     }
     value.setLocal(keep);
+    value.setInheritsFrom(parentProjectBox.getParentProjectName());
   }
 
   @Override
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectAccessEditor.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectAccessEditor.ui.xml
index 4942b83..120824b 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectAccessEditor.ui.xml
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectAccessEditor.ui.xml
@@ -56,6 +56,9 @@
   <div ui:field='inheritsFrom' class='{style.inheritsFrom}'>
     <span class='{style.parentTitle}'><ui:msg>Rights Inherit From:</ui:msg></span>
     <q:Hyperlink ui:field='parentProject' styleName='{style.parentLink}'/>
+    <q:ParentProjectBox
+      ui:field='parentProjectBox'
+      visible='false'/>
   </div>
   <div ui:field='history' class='{style.history}'>
     <span class='{style.historyTitle}'><ui:msg>History:</ui:msg></span>
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectAccessScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectAccessScreen.java
index ef7f560..c76bba9 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectAccessScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectAccessScreen.java
@@ -17,6 +17,7 @@
 import static com.google.gerrit.common.ProjectAccessUtil.mergeSections;
 import static com.google.gerrit.common.ProjectAccessUtil.removeEmptyPermissionsAndSections;
 
+import com.google.gerrit.client.ErrorDialog;
 import com.google.gerrit.client.Gerrit;
 import com.google.gerrit.client.config.CapabilityInfo;
 import com.google.gerrit.client.config.ConfigServerApi;
@@ -28,6 +29,7 @@
 import com.google.gerrit.common.PageLinks;
 import com.google.gerrit.common.data.AccessSection;
 import com.google.gerrit.common.data.ProjectAccess;
+import com.google.gerrit.common.errors.UpdateParentFailedException;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gwt.core.client.GWT;
@@ -45,6 +47,7 @@
 import com.google.gwt.user.client.ui.UIObject;
 import com.google.gwt.user.client.ui.VerticalPanel;
 import com.google.gwtexpui.globalkey.client.NpTextArea;
+import com.google.gwtjsonrpc.client.RemoteJsonException;
 
 import java.util.Collections;
 import java.util.HashMap;
@@ -205,6 +208,7 @@
         access.getRevision(), //
         message, //
         access.getLocal(), //
+        access.getInheritsFrom(), //
         new GerritCallback<ProjectAccess>() {
           @Override
           public void onSuccess(ProjectAccess newAccess) {
@@ -250,7 +254,15 @@
           public void onFailure(Throwable caught) {
             error.clear();
             enable(true);
-            super.onFailure(caught);
+            if (caught instanceof RemoteJsonException
+                && caught.getMessage().startsWith(
+                    UpdateParentFailedException.MESSAGE)) {
+              new ErrorDialog(Gerrit.M.parentUpdateFailed(caught.getMessage()
+                  .substring(UpdateParentFailedException.MESSAGE.length() + 1)))
+                  .center();
+            } else {
+              super.onFailure(caught);
+            }
           }
         });
   }
@@ -275,6 +287,7 @@
         access.getRevision(), //
         message, //
         access.getLocal(), //
+        access.getInheritsFrom(), //
         new GerritCallback<Change.Id>() {
           @Override
           public void onSuccess(Change.Id changeId) {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/ActionContext.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/ActionContext.java
index 7b00821..f22fa3f 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/ActionContext.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/ActionContext.java
@@ -93,6 +93,21 @@
         e.onkeypress = stopPropagation;
         return e;
       },
+      select: function(a,s) {
+        var e = doc.createElement('select');
+        for (var i = 0; i < a.length; i++) {
+          var o = doc.createElement('option');
+          if (i==s) {
+            o.setAttributeNode(doc.createAttribute("selected"));
+          }
+          o.appendChild(doc.createTextNode(a[i]));
+          e.appendChild(o);
+        }
+        return e;
+      },
+      selected: function(e) {
+        return e.options[e.selectedIndex].text;
+      },
 
       popup: function(e){this._p=@com.google.gerrit.client.api.PopupHelper::popup(Lcom/google/gerrit/client/api/ActionContext;Lcom/google/gwt/dom/client/Element;)(this,e)},
       hide: function() {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/DefaultActions.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/DefaultActions.java
index fcd6056..53a3784 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/DefaultActions.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/DefaultActions.java
@@ -22,6 +22,7 @@
 import com.google.gerrit.client.rpc.RestApi;
 import com.google.gerrit.common.PageLinks;
 import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.reviewdb.client.Project;
 import com.google.gwt.core.client.JavaScriptObject;
 import com.google.gwt.user.client.Window;
 import com.google.gwt.user.client.rpc.AsyncCallback;
@@ -44,7 +45,8 @@
     invoke(action, api, cb);
   }
 
-  static void invokeProjectAction(ActionInfo action, RestApi api) {
+  static void invokeProjectAction(final Project.NameKey project,
+      ActionInfo action, RestApi api) {
     AsyncCallback<JavaScriptObject> cb = new GerritCallback<JavaScriptObject>() {
       @Override
       public void onSuccess(JavaScriptObject msg) {
@@ -54,7 +56,7 @@
             Window.alert(str.asString());
           }
         }
-        Gerrit.display(PageLinks.ADMIN_PROJECTS);
+        Gerrit.display(PageLinks.toProject(project));
       }
     };
     invoke(action, api, cb);
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/ProjectGlue.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/ProjectGlue.java
index b95f4e0..bce691c 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/ProjectGlue.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/ProjectGlue.java
@@ -35,7 +35,7 @@
       c.button(button);
       ApiGlue.invoke(f, c);
     } else {
-      DefaultActions.invokeProjectAction(action, api);
+      DefaultActions.invokeProjectAction(project, action, api);
     }
   }
 
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SidePanel.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SidePanel.java
index b0431e9..52bb003 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SidePanel.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SidePanel.java
@@ -114,7 +114,7 @@
     Label gutter = wrapper.gutter;
     final double height = cm.heightAtLine(line, "local");
     final double scrollbarHeight = cmB.getScrollbarV().getClientHeight();
-    double top = height / (double) cmB.getSizer().getClientHeight() *
+    double top = height / cmB.getSizer().getClientHeight() *
         scrollbarHeight +
         cmB.getScrollbarV().getAbsoluteTop();
     if (top == 0) {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ProjectApi.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ProjectApi.java
index 62dbf1c7..ebae86a 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ProjectApi.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ProjectApi.java
@@ -110,6 +110,15 @@
         });
   }
 
+  public static void getChildren(Project.NameKey name, boolean recursive,
+      AsyncCallback<JsArray<ProjectInfo>> cb) {
+    RestApi view = project(name).view("children");
+    if (recursive) {
+      view.addParameterTrue("recursive");
+    }
+    view.get(cb);
+  }
+
   public static void getDescription(Project.NameKey name,
       AsyncCallback<NativeString> cb) {
     project(name).view("description").get(cb);
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/rpc/RestApi.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/rpc/RestApi.java
index eeb45d4..5ae3091 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/rpc/RestApi.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/rpc/RestApi.java
@@ -124,29 +124,31 @@
         }
 
       } else if (200 <= status && status < 300) {
-        if (!isJsonBody(res)) {
+        T data;
+        if (isTextBody(res)) {
+          data = NativeString.wrap(res.getText()).cast();
+        } else if (isJsonBody(res)) {
+          try {
+            // javac generics bug
+            data = RestApi.<T>cast(parseJson(res));
+          } catch (JSONException e) {
+            if (!background) {
+              RpcStatus.INSTANCE.onRpcComplete();
+            }
+            cb.onFailure(new StatusCodeException(SC_BAD_RESPONSE,
+                "Invalid JSON: " + e.getMessage()));
+            return;
+          }
+        } else {
           if (!background) {
             RpcStatus.INSTANCE.onRpcComplete();
           }
           cb.onFailure(new StatusCodeException(SC_BAD_RESPONSE, "Expected "
-              + JSON_TYPE + "; received Content-Type: "
+              + JSON_TYPE + " or " + TEXT_TYPE + "; received Content-Type: "
               + res.getHeader("Content-Type")));
           return;
         }
 
-        T data;
-        try {
-          // javac generics bug
-          data = RestApi.<T>cast(parseJson(res));
-        } catch (JSONException e) {
-          if (!background) {
-            RpcStatus.INSTANCE.onRpcComplete();
-          }
-          cb.onFailure(new StatusCodeException(SC_BAD_RESPONSE,
-              "Invalid JSON: " + e.getMessage()));
-          return;
-        }
-
         cb.onSuccess(data);
         if (!background) {
           RpcStatus.INSTANCE.onRpcComplete();
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ParentProjectBox.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ParentProjectBox.java
new file mode 100644
index 0000000..a03cb34
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ParentProjectBox.java
@@ -0,0 +1,101 @@
+// 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.client.ui;
+
+import com.google.gerrit.client.projects.ProjectApi;
+import com.google.gerrit.client.projects.ProjectInfo;
+import com.google.gerrit.client.rpc.Natives;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gwt.core.client.JsArray;
+import com.google.gwt.user.client.rpc.AsyncCallback;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.SuggestBox;
+import com.google.gwtexpui.globalkey.client.NpTextBox;
+
+import java.util.HashSet;
+import java.util.Set;
+
+public class ParentProjectBox extends Composite {
+  private final NpTextBox textBox;
+  private final SuggestBox suggestBox;
+  private final ParentProjectNameSuggestOracle suggestOracle;
+
+  public ParentProjectBox() {
+    textBox = new NpTextBox();
+    suggestOracle = new ParentProjectNameSuggestOracle();
+    suggestBox = new SuggestBox(suggestOracle, textBox);
+    initWidget(suggestBox);
+  }
+
+  public void setVisibleLength(int len) {
+    textBox.setVisibleLength(len);
+  }
+
+  public void setProject(final Project.NameKey project) {
+    suggestOracle.setProject(project);
+  }
+
+  public void setParentProject(final Project.NameKey parent) {
+    suggestBox.setText(parent != null ? parent.get() : "");
+  }
+
+  public Project.NameKey getParentProjectName() {
+    final String projectName = suggestBox.getText().trim();
+    if (projectName.isEmpty()) {
+      return null;
+    }
+    return new Project.NameKey(projectName);
+  }
+
+  private static class ParentProjectNameSuggestOracle extends ProjectNameSuggestOracle {
+    private Set<String> exclude = new HashSet<String>();
+
+    public void setProject(Project.NameKey project) {
+      exclude.clear();
+      exclude.add(project.get());
+      ProjectApi.getChildren(project, true, new AsyncCallback<JsArray<ProjectInfo>>() {
+        @Override
+        public void onSuccess(JsArray<ProjectInfo> result) {
+          for (ProjectInfo p : Natives.asList(result)) {
+            exclude.add(p.name());
+          }
+        }
+
+        @Override
+        public void onFailure(Throwable caught) {
+        }
+      });
+    }
+
+    @Override
+    public void _onRequestSuggestions(Request req, final Callback callback) {
+      super._onRequestSuggestions(req, new Callback() {
+        public void onSuggestionsReady(Request request, Response response) {
+          if (exclude.size() > 0) {
+            Set<Suggestion> filteredSuggestions =
+                new HashSet<Suggestion>(response.getSuggestions());
+            for (Suggestion s : response.getSuggestions()) {
+              if (exclude.contains(s.getReplacementString())) {
+                filteredSuggestions.remove(s);
+              }
+            }
+            response.setSuggestions(filteredSuggestions);
+          }
+          callback.onSuggestionsReady(request, response);
+        }
+      });
+    }
+  }
+}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/WebModule.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/WebModule.java
index 5593baf..3443968 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/WebModule.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/WebModule.java
@@ -31,7 +31,6 @@
 import com.google.gerrit.server.RemotePeer;
 import com.google.gerrit.server.config.AuthConfig;
 import com.google.gerrit.server.config.CanonicalWebUrl;
-import com.google.gerrit.server.config.FactoryModule;
 import com.google.gerrit.server.config.GerritRequestModule;
 import com.google.gerrit.server.contact.ContactStore;
 import com.google.gerrit.server.contact.ContactStoreProvider;
@@ -46,7 +45,7 @@
 
 import java.net.SocketAddress;
 
-public class WebModule extends FactoryModule {
+public class WebModule extends LifecycleModule {
   private final AuthConfig authConfig;
   private final UrlModule.UrlConfig urlConfig;
   private final boolean wantSSL;
@@ -132,11 +131,6 @@
     bind(SocketAddress.class).annotatedWith(RemotePeer.class).toProvider(
         HttpRemotePeerProvider.class).in(RequestScoped.class);
 
-    install(new LifecycleModule() {
-      @Override
-      protected void configure() {
-        listener().toInstance(registerInParentInjectors());
-      }
-    });
+    listener().toInstance(registerInParentInjectors());
   }
 }
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ChangeProjectAccess.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ChangeProjectAccess.java
index 0dd3d16..57be31e 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ChangeProjectAccess.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ChangeProjectAccess.java
@@ -19,12 +19,15 @@
 import com.google.gerrit.common.data.ProjectAccess;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.server.account.GroupBackend;
+import com.google.gerrit.server.config.AllProjectsNameProvider;
 import com.google.gerrit.server.git.MetaDataUpdate;
 import com.google.gerrit.server.git.ProjectConfig;
 import com.google.gerrit.server.project.NoSuchProjectException;
 import com.google.gerrit.server.project.ProjectCache;
 import com.google.gerrit.server.project.ProjectControl;
+import com.google.gerrit.server.project.SetParent;
 import com.google.inject.Inject;
+import com.google.inject.Provider;
 import com.google.inject.assistedinject.Assisted;
 
 import org.eclipse.jgit.errors.ConfigInvalidException;
@@ -35,9 +38,11 @@
 
 class ChangeProjectAccess extends ProjectAccessHandler<ProjectAccess> {
   interface Factory {
-    ChangeProjectAccess create(@Assisted Project.NameKey projectName,
+    ChangeProjectAccess create(
+        @Assisted("projectName") Project.NameKey projectName,
         @Nullable @Assisted ObjectId base,
         @Assisted List<AccessSection> sectionList,
+        @Nullable @Assisted("parentProjectName") Project.NameKey parentProjectName,
         @Nullable @Assisted String message);
   }
 
@@ -45,17 +50,21 @@
   private final ProjectCache projectCache;
 
   @Inject
-  ChangeProjectAccess(final ProjectAccessFactory.Factory projectAccessFactory,
-      final ProjectControl.Factory projectControlFactory,
-      final ProjectCache projectCache, final GroupBackend groupBackend,
-      final MetaDataUpdate.User metaDataUpdateFactory,
+  ChangeProjectAccess(ProjectAccessFactory.Factory projectAccessFactory,
+      ProjectControl.Factory projectControlFactory,
+      ProjectCache projectCache, GroupBackend groupBackend,
+      MetaDataUpdate.User metaDataUpdateFactory,
+      AllProjectsNameProvider allProjects,
+      Provider<SetParent> setParent,
 
-      @Assisted final Project.NameKey projectName,
-      @Nullable @Assisted final ObjectId base,
+      @Assisted("projectName") Project.NameKey projectName,
+      @Nullable @Assisted ObjectId base,
       @Assisted List<AccessSection> sectionList,
+      @Nullable @Assisted("parentProjectName") Project.NameKey parentProjectName,
       @Nullable @Assisted String message) {
     super(projectControlFactory, groupBackend, metaDataUpdateFactory,
-        projectName, base, sectionList, message, true);
+        allProjects, setParent, projectName, base, sectionList,
+        parentProjectName, message, true);
     this.projectAccessFactory = projectAccessFactory;
     this.projectCache = projectCache;
   }
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectAccessFactory.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectAccessFactory.java
index 2c5681b..6e7150d 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectAccessFactory.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectAccessFactory.java
@@ -203,6 +203,8 @@
     detail.setOwnerOf(ownerOf);
     detail.setCanUpload(pc.isOwner()
         || (metaConfigControl.isVisible() && metaConfigControl.canUpload()));
+    detail.setCanChangeParent(pc.getCurrentUser().getCapabilities()
+        .canAdministrateServer());
     detail.setConfigVisible(pc.isOwner() || metaConfigControl.isVisible());
     detail.setLabelTypes(pc.getLabelTypes());
     return detail;
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectAccessHandler.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectAccessHandler.java
index 0a50a38..971c4b3 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectAccessHandler.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectAccessHandler.java
@@ -22,16 +22,23 @@
 import com.google.gerrit.common.data.PermissionRule;
 import com.google.gerrit.common.errors.InvalidNameException;
 import com.google.gerrit.common.errors.NoSuchGroupException;
+import com.google.gerrit.common.errors.UpdateParentFailedException;
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
 import com.google.gerrit.httpd.rpc.Handler;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.server.account.GroupBackend;
 import com.google.gerrit.server.account.GroupBackends;
+import com.google.gerrit.server.config.AllProjectsNameProvider;
 import com.google.gerrit.server.git.MetaDataUpdate;
 import com.google.gerrit.server.git.ProjectConfig;
 import com.google.gerrit.server.project.NoSuchProjectException;
 import com.google.gerrit.server.project.ProjectControl;
 import com.google.gerrit.server.project.RefControl;
+import com.google.gerrit.server.project.SetParent;
 import com.google.gwtorm.server.OrmException;
+import com.google.inject.Provider;
 
 import org.eclipse.jgit.errors.ConfigInvalidException;
 import org.eclipse.jgit.errors.RepositoryNotFoundException;
@@ -47,27 +54,32 @@
   private final ProjectControl.Factory projectControlFactory;
   protected final GroupBackend groupBackend;
   private final MetaDataUpdate.User metaDataUpdateFactory;
+  private final AllProjectsNameProvider allProjects;
+  private final Provider<SetParent> setParent;
 
   protected final Project.NameKey projectName;
   protected final ObjectId base;
   private List<AccessSection> sectionList;
+  private final Project.NameKey parentProjectName;
   protected String message;
   private boolean checkIfOwner;
 
-  protected ProjectAccessHandler(
-      final ProjectControl.Factory projectControlFactory,
-      final GroupBackend groupBackend,
-      final MetaDataUpdate.User metaDataUpdateFactory,
-      final Project.NameKey projectName, final ObjectId base,
-      final List<AccessSection> sectionList, final String message,
-      final boolean checkIfOwner) {
+  protected ProjectAccessHandler(ProjectControl.Factory projectControlFactory,
+      GroupBackend groupBackend, MetaDataUpdate.User metaDataUpdateFactory,
+      AllProjectsNameProvider allProjects, Provider<SetParent> setParent,
+      Project.NameKey projectName, ObjectId base,
+      List<AccessSection> sectionList, Project.NameKey parentProjectName,
+      String message, boolean checkIfOwner) {
     this.projectControlFactory = projectControlFactory;
     this.groupBackend = groupBackend;
     this.metaDataUpdateFactory = metaDataUpdateFactory;
+    this.allProjects = allProjects;
+    this.setParent = setParent;
 
     this.projectName = projectName;
     this.base = base;
     this.sectionList = sectionList;
+    this.parentProjectName = parentProjectName;
     this.message = message;
     this.checkIfOwner = checkIfOwner;
   }
@@ -75,7 +87,7 @@
   @Override
   public final T call() throws NoSuchProjectException, IOException,
       ConfigInvalidException, InvalidNameException, NoSuchGroupException,
-      OrmException {
+      OrmException, UpdateParentFailedException {
     final ProjectControl projectControl =
         projectControlFactory.controlFor(projectName);
 
@@ -120,6 +132,20 @@
         }
       }
 
+      if (!config.getProject().getNameKey().equals(allProjects.get()) &&
+          !config.getProject().getParent(allProjects.get()).equals(parentProjectName)) {
+        try {
+          setParent.get().validateParentUpdate(projectControl, parentProjectName.get());
+        } catch (AuthException e) {
+          throw new UpdateParentFailedException(e.getMessage(), e);
+        } catch (ResourceConflictException e) {
+          throw new UpdateParentFailedException(e.getMessage(), e);
+        } catch (UnprocessableEntityException e) {
+          throw new UpdateParentFailedException(e.getMessage(), e);
+        }
+        config.getProject().setParentName(parentProjectName);
+      }
+
       if (message != null && !message.isEmpty()) {
         if (!message.endsWith("\n")) {
           message += "\n";
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectAdminServiceImpl.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectAdminServiceImpl.java
index 66ba75c..c378701 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectAdminServiceImpl.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectAdminServiceImpl.java
@@ -56,14 +56,16 @@
   @Override
   public void changeProjectAccess(Project.NameKey projectName,
       String baseRevision, String msg, List<AccessSection> sections,
-      AsyncCallback<ProjectAccess> cb) {
-    changeProjectAccessFactory.create(projectName, getBase(baseRevision), sections, msg).to(cb);
+      Project.NameKey parentProjectName, AsyncCallback<ProjectAccess> cb) {
+    changeProjectAccessFactory.create(projectName, getBase(baseRevision),
+        sections, parentProjectName, msg).to(cb);
   }
 
   @Override
   public void reviewProjectAccess(Project.NameKey projectName,
       String baseRevision, String msg, List<AccessSection> sections,
-      AsyncCallback<Change.Id> cb) {
-    reviewProjectAccessFactory.create(projectName, getBase(baseRevision), sections, msg).to(cb);
+      Project.NameKey parentProjectName, AsyncCallback<Change.Id> cb) {
+    reviewProjectAccessFactory.create(projectName, getBase(baseRevision),
+        sections, parentProjectName, msg).to(cb);
   }
 }
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ReviewProjectAccess.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ReviewProjectAccess.java
index 27a02d9..3f4030d 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ReviewProjectAccess.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ReviewProjectAccess.java
@@ -31,6 +31,7 @@
 import com.google.gerrit.server.account.GroupBackend;
 import com.google.gerrit.server.change.ChangeResource;
 import com.google.gerrit.server.change.PostReviewers;
+import com.google.gerrit.server.config.AllProjectsNameProvider;
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.git.MetaDataUpdate;
 import com.google.gerrit.server.git.ProjectConfig;
@@ -39,6 +40,7 @@
 import com.google.gerrit.server.patch.PatchSetInfoFactory;
 import com.google.gerrit.server.project.ChangeControl;
 import com.google.gerrit.server.project.ProjectControl;
+import com.google.gerrit.server.project.SetParent;
 import com.google.gerrit.server.util.TimeUtil;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
@@ -60,9 +62,11 @@
       LoggerFactory.getLogger(ReviewProjectAccess.class);
 
   interface Factory {
-    ReviewProjectAccess create(@Assisted Project.NameKey projectName,
+    ReviewProjectAccess create(
+        @Assisted("projectName") Project.NameKey projectName,
         @Nullable @Assisted ObjectId base,
         @Assisted List<AccessSection> sectionList,
+        @Nullable @Assisted("parentProjectName") Project.NameKey parentProjectName,
         @Nullable @Assisted String message);
   }
 
@@ -84,13 +88,17 @@
       ChangeControl.GenericFactory changeFactory,
       ChangeIndexer indexer, ChangeHooks hooks,
       CreateChangeSender.Factory createChangeSenderFactory,
+      AllProjectsNameProvider allProjects,
+      Provider<SetParent> setParent,
 
-      @Assisted Project.NameKey projectName,
+      @Assisted("projectName") Project.NameKey projectName,
       @Nullable @Assisted ObjectId base,
       @Assisted List<AccessSection> sectionList,
+      @Nullable @Assisted("parentProjectName") Project.NameKey parentProjectName,
       @Nullable @Assisted String message) {
     super(projectControlFactory, groupBackend, metaDataUpdateFactory,
-        projectName, base, sectionList, message, false);
+        allProjects, setParent, projectName, base, sectionList,
+        parentProjectName, message, false);
     this.db = db;
     this.user = user;
     this.patchSetInfoFactory = patchSetInfoFactory;
diff --git a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneIndexModule.java b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneIndexModule.java
index ef96374..583e54f 100644
--- a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneIndexModule.java
+++ b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneIndexModule.java
@@ -16,7 +16,6 @@
 
 import com.google.gerrit.extensions.events.LifecycleListener;
 import com.google.gerrit.lifecycle.LifecycleModule;
-import com.google.gerrit.server.config.FactoryModule;
 import com.google.gerrit.server.config.SitePaths;
 import com.google.gerrit.server.index.ChangeSchemas;
 import com.google.gerrit.server.index.IndexCollection;
@@ -45,12 +44,7 @@
 
   @Override
   protected void configure() {
-    install(new FactoryModule() {
-      @Override
-      public void configure() {
-        factory(LuceneChangeIndex.Factory.class);
-      }
-    });
+    factory(LuceneChangeIndex.Factory.class);
     install(new IndexModule(threads));
     if (singleVersion == null && base == null) {
       install(new MultiVersionModule());
@@ -62,12 +56,7 @@
   private class MultiVersionModule extends LifecycleModule {
     @Override
     public void configure() {
-      install(new FactoryModule() {
-        @Override
-        public void configure() {
-          factory(OnlineReindexer.Factory.class);
-        }
-      });
+      factory(OnlineReindexer.Factory.class);
       listener().to(LuceneVersionManager.class);
     }
   }
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 68e0f6a..3cfd1bb 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
@@ -56,7 +56,7 @@
   @Option(name = "--list-plugins", usage = "List available plugins")
   private boolean listPlugins;
 
-  @Option(name = "--install-plugin", usage = "Install given plugin without asking", multiValued = true)
+  @Option(name = "--install-plugin", usage = "Install given plugin without asking")
   private List<String> installPlugins;
 
   @Inject
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/PrologShell.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/PrologShell.java
index 803b702..4c66f0b 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/PrologShell.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/PrologShell.java
@@ -32,7 +32,7 @@
 import java.util.List;
 
 public class PrologShell extends AbstractProgram {
-  @Option(name = "-s", multiValued = true, metaVar = "FILE.pl", usage = "file to load")
+  @Option(name = "-s", metaVar = "FILE.pl", usage = "file to load")
   private List<String> fileName = new ArrayList<String>();
 
   @Override
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/HiddenErrorHandler.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/HiddenErrorHandler.java
index 9fd8929..5ca53df 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/HiddenErrorHandler.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/HiddenErrorHandler.java
@@ -18,9 +18,9 @@
 import com.google.common.base.Strings;
 import com.google.gwtexpui.server.CacheHeaders;
 
-import org.eclipse.jetty.http.HttpHeaders;
+import org.eclipse.jetty.http.HttpHeader;
 import org.eclipse.jetty.http.HttpStatus;
-import org.eclipse.jetty.server.AbstractHttpConnection;
+import org.eclipse.jetty.server.HttpConnection;
 import org.eclipse.jetty.server.Request;
 import org.eclipse.jetty.server.handler.ErrorHandler;
 import org.slf4j.Logger;
@@ -37,8 +37,8 @@
 
   public void handle(String target, Request baseRequest,
       HttpServletRequest req, HttpServletResponse res) throws IOException {
-    AbstractHttpConnection conn = AbstractHttpConnection.getCurrentConnection();
-    conn.getRequest().setHandled(true);
+    HttpConnection conn = HttpConnection.getCurrentConnection();
+    baseRequest.setHandled(true);
     try {
       log(req);
     } finally {
@@ -46,10 +46,11 @@
     }
   }
 
-  private void reply(AbstractHttpConnection conn, HttpServletResponse res)
+  private void reply(HttpConnection conn, HttpServletResponse res)
       throws IOException {
     byte[] msg = message(conn);
-    res.setHeader(HttpHeaders.CONTENT_TYPE, "text/plain; charset=ISO-8859-1");
+    res.setHeader(HttpHeader.CONTENT_TYPE.asString(),
+        "text/plain; charset=ISO-8859-1");
     res.setContentLength(msg.length);
     try {
       CacheHeaders.setNotCacheable(res);
@@ -63,10 +64,11 @@
     }
   }
 
-  private static byte[] message(AbstractHttpConnection conn) {
-    String msg = conn.getResponse().getReason();
+  private static byte[] message(HttpConnection conn) {
+    String msg = conn.getHttpChannel().getResponse().getReason();
     if (msg == null) {
-      msg = HttpStatus.getMessage(conn.getResponse().getStatus());
+      msg = HttpStatus.getMessage(conn.getHttpChannel()
+          .getResponse().getStatus());
     }
     return msg.getBytes(Charsets.ISO_8859_1);
   }
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/JettyModule.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/JettyModule.java
index 1ae9355..b563349 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/JettyModule.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/JettyModule.java
@@ -15,9 +15,8 @@
 package com.google.gerrit.pgm.http.jetty;
 
 import com.google.gerrit.lifecycle.LifecycleModule;
-import com.google.inject.AbstractModule;
 
-public class JettyModule extends AbstractModule {
+public class JettyModule extends LifecycleModule {
   private final JettyEnv env;
 
   public JettyModule(final JettyEnv env) {
@@ -28,11 +27,6 @@
   protected void configure() {
     bind(JettyEnv.class).toInstance(env);
     bind(JettyServer.class);
-    install(new LifecycleModule() {
-      @Override
-      protected void configure() {
-        listener().to(JettyServer.Lifecycle.class);
-      }
-    });
+    listener().to(JettyServer.Lifecycle.class);
   }
 }
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/JettyServer.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/JettyServer.java
index e1d1281b..615e11e 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/JettyServer.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/JettyServer.java
@@ -32,21 +32,24 @@
 import com.google.inject.servlet.GuiceFilter;
 import com.google.inject.servlet.GuiceServletContextListener;
 
-import org.eclipse.jetty.io.EndPoint;
 import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.ForwardedRequestCustomizer;
 import org.eclipse.jetty.server.Handler;
-import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.HttpConfiguration;
+import org.eclipse.jetty.server.HttpConnectionFactory;
+import org.eclipse.jetty.server.SecureRequestCustomizer;
 import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.server.SslConnectionFactory;
 import org.eclipse.jetty.server.handler.ContextHandler;
 import org.eclipse.jetty.server.handler.ContextHandlerCollection;
 import org.eclipse.jetty.server.handler.RequestLogHandler;
-import org.eclipse.jetty.server.nio.SelectChannelConnector;
 import org.eclipse.jetty.server.session.SessionHandler;
-import org.eclipse.jetty.server.ssl.SslSelectChannelConnector;
 import org.eclipse.jetty.servlet.DefaultServlet;
 import org.eclipse.jetty.servlet.FilterHolder;
 import org.eclipse.jetty.servlet.ServletContextHandler;
 import org.eclipse.jetty.servlet.ServletHolder;
+import org.eclipse.jetty.util.BlockingArrayQueue;
 import org.eclipse.jetty.util.resource.Resource;
 import org.eclipse.jetty.util.ssl.SslContextFactory;
 import org.eclipse.jetty.util.thread.QueuedThreadPool;
@@ -131,9 +134,8 @@
       throws MalformedURLException, IOException {
     this.site = site;
 
-    httpd = new Server();
-    httpd.setConnectors(listen(cfg));
-    httpd.setThreadPool(threadPool(cfg));
+    httpd = new Server(threadPool(cfg));
+    httpd.setConnectors(listen(httpd, cfg));
 
     Handler app = makeContext(env, cfg);
     if (cfg.getBoolean("httpd", "requestLog", !reverseProxy)) {
@@ -142,15 +144,12 @@
       handler.setHandler(app);
       app = handler;
     }
-    httpd.setHandler(app);
 
+    httpd.setHandler(app);
     httpd.setStopAtShutdown(false);
-    httpd.setSendDateHeader(true);
-    httpd.setSendServerVersion(false);
-    httpd.setGracefulShutdown((int) MILLISECONDS.convert(1, SECONDS));
   }
 
-  private Connector[] listen(final Config cfg) {
+  private Connector[] listen(Server server, Config cfg) {
     // OpenID and certain web-based single-sign-on products can cause
     // some very long headers, especially in the Referer header. We
     // need to use a larger default header size to ensure we have
@@ -168,7 +167,8 @@
     for (int idx = 0; idx < listenUrls.length; idx++) {
       final URI u = listenUrls[idx];
       final int defaultPort;
-      final SelectChannelConnector c;
+      final ServerConnector c;
+      HttpConfiguration config = defaultConfig(requestHeaderSize);
 
       if (AuthType.CLIENT_SSL_CERT_LDAP.equals(authType) && ! "https".equals(u.getScheme())) {
         throw new IllegalArgumentException("Protocol '" + u.getScheme()
@@ -179,7 +179,9 @@
 
       if ("http".equals(u.getScheme())) {
         defaultPort = 80;
-        c = new SelectChannelConnector();
+        c = new ServerConnector(server, null, null, null, 0, acceptors,
+            new HttpConnectionFactory(config));
+
       } else if ("https".equals(u.getScheme())) {
         SslContextFactory ssl = new SslContextFactory();
         final File keystore = getFile(cfg, "sslkeystore", "etc/keystore");
@@ -188,7 +190,7 @@
           password = "gerrit";
         }
         ssl.setKeyStorePath(keystore.getAbsolutePath());
-        ssl.setTrustStore(keystore.getAbsolutePath());
+        ssl.setTrustStorePath(keystore.getAbsolutePath());
         ssl.setKeyStorePassword(password);
         ssl.setTrustStorePassword(password);
 
@@ -203,24 +205,27 @@
         }
 
         defaultPort = 443;
-        c = new SslSelectChannelConnector(ssl);
+
+        config.addCustomizer(new SecureRequestCustomizer());
+        c = new ServerConnector(server,
+            null, null, null, 0, acceptors,
+            new SslConnectionFactory(ssl, "http/1.1"),
+            new HttpConnectionFactory(config));
 
       } else if ("proxy-http".equals(u.getScheme())) {
         defaultPort = 8080;
-        c = new SelectChannelConnector();
-        c.setForwarded(true);
+        config.addCustomizer(new ForwardedRequestCustomizer());
+        c = new ServerConnector(server,
+            null, null, null, 0, acceptors,
+            new HttpConnectionFactory(config));
 
       } else if ("proxy-https".equals(u.getScheme())) {
         defaultPort = 8080;
-        c = new SelectChannelConnector() {
-          @Override
-          public void customize(EndPoint endpoint, Request request)
-              throws IOException {
-            request.setScheme("https");
-            super.customize(endpoint, request);
-          }
-        };
-        c.setForwarded(true);
+        config.addCustomizer(new ForwardedRequestCustomizer());
+        config.addCustomizer(new SecureRequestCustomizer());
+        c = new ServerConnector(server,
+            null, null, null, 0, acceptors,
+            new HttpConnectionFactory(config));
 
       } else {
         throw new IllegalArgumentException("Protocol '" + u.getScheme() + "' "
@@ -249,16 +254,20 @@
         throw new IllegalArgumentException("Invalid httpd.listenurl " + u, e);
       }
 
-      c.setRequestHeaderSize(requestHeaderSize);
-      c.setAcceptors(acceptors);
       c.setReuseAddress(reuseAddress);
-      c.setStatsOn(false);
-
       connectors[idx] = c;
     }
     return connectors;
   }
 
+  private HttpConfiguration defaultConfig(int requestHeaderSize) {
+    HttpConfiguration config = new HttpConfiguration();
+    config.setRequestHeaderSize(requestHeaderSize);
+    config.setSendServerVersion(false);
+    config.setSendDateHeader(true);
+    return config;
+  }
+
   static boolean isReverseProxied(final URI[] listenUrls) {
     for (URI u : listenUrls) {
       if ("http".equals(u.getScheme()) || "https".equals(u.getScheme())) {
@@ -295,11 +304,20 @@
   }
 
   private ThreadPool threadPool(Config cfg) {
-    final QueuedThreadPool pool = new QueuedThreadPool();
+    int maxThreads = cfg.getInt("httpd", null, "maxthreads", 25);
+    int minThreads = cfg.getInt("httpd", null, "minthreads", 5);
+    int maxCapacity = cfg.getInt("httpd", null, "maxqueued", 50);
+    int idleTimeout = (int)MILLISECONDS.convert(60, SECONDS);
+    QueuedThreadPool pool = new QueuedThreadPool(
+        maxThreads,
+        minThreads,
+        idleTimeout,
+        new BlockingArrayQueue<Runnable>(
+            minThreads, // capacity,
+            minThreads, // growBy,
+            maxCapacity // maxCapacity
+    ));
     pool.setName("HTTP");
-    pool.setMinThreads(cfg.getInt("httpd", null, "minthreads", 5));
-    pool.setMaxThreads(cfg.getInt("httpd", null, "maxthreads", 25));
-    pool.setMaxQueued(cfg.getInt("httpd", null, "maxqueued", 50));
     return pool;
   }
 
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/ConsoleUI.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/ConsoleUI.java
index e8cf0ab..9af95ab 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/ConsoleUI.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/ConsoleUI.java
@@ -179,7 +179,7 @@
         }
         console.printf("       Supported options are:\n");
         for (final String v : allowedValues) {
-          console.printf("         %s\n", v.toString().toLowerCase());
+          console.printf("         %s\n", v.toLowerCase());
         }
       }
     }
diff --git a/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/src/main/java/public/gwt-hello-gadgets-igoogle-thumb.png b/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/src/main/java/public/gwt-hello-gadgets-igoogle-thumb.png
deleted file mode 100644
index d45c63f..0000000
--- a/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/src/main/java/public/gwt-hello-gadgets-igoogle-thumb.png
+++ /dev/null
Binary files differ
diff --git a/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/src/main/java/public/gwt-hello-gadgets-igoogle.png b/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/src/main/java/public/gwt-hello-gadgets-igoogle.png
deleted file mode 100644
index 5c9009a..0000000
--- a/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/src/main/java/public/gwt-hello-gadgets-igoogle.png
+++ /dev/null
Binary files differ
diff --git a/gerrit-server/src/main/java/com/google/gerrit/lifecycle/LifecycleModule.java b/gerrit-server/src/main/java/com/google/gerrit/lifecycle/LifecycleModule.java
index 04682f5..a22e6da 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/lifecycle/LifecycleModule.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/lifecycle/LifecycleModule.java
@@ -1,7 +1,7 @@
 package com.google.gerrit.lifecycle;
 
 import com.google.gerrit.extensions.events.LifecycleListener;
-import com.google.inject.AbstractModule;
+import com.google.gerrit.server.config.FactoryModule;
 import com.google.inject.Singleton;
 import com.google.inject.binder.LinkedBindingBuilder;
 import com.google.inject.internal.UniqueAnnotations;
@@ -9,7 +9,7 @@
 import java.lang.annotation.Annotation;
 
 /** Module to support registering a unique LifecyleListener. */
-public abstract class LifecycleModule extends AbstractModule {
+public abstract class LifecycleModule extends FactoryModule {
   /**
    * Create a unique listener binding.
    * <p>
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/GetCapabilities.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/GetCapabilities.java
index 8634045..03ba94f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/GetCapabilities.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/GetCapabilities.java
@@ -58,7 +58,7 @@
   @Option(name = "--format", usage = "(deprecated) output format")
   private OutputFormat format;
 
-  @Option(name = "-q", metaVar = "CAP", multiValued = true, usage = "Capability to inspect")
+  @Option(name = "-q", metaVar = "CAP", usage = "Capability to inspect")
   void addQuery(String name) {
     if (query == null) {
       query = Sets.newHashSet();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetChange.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetChange.java
index 4f6ceac..8b794f5 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetChange.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetChange.java
@@ -29,7 +29,7 @@
 public class GetChange implements RestReadView<ChangeResource> {
   private final ChangeJson json;
 
-  @Option(name = "-o", multiValued = true, usage = "Output options")
+  @Option(name = "-o", usage = "Output options")
   void addOption(ListChangesOption o) {
     json.addOption(o);
   }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetDetail.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetDetail.java
index 21c4c33..520a09f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetDetail.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetDetail.java
@@ -26,7 +26,7 @@
 public class GetDetail implements RestReadView<ChangeResource> {
   private final GetChange delegate;
 
-  @Option(name = "-o", multiValued = true, usage = "Output options")
+  @Option(name = "-o", usage = "Output options")
   void addOption(ListChangesOption o) {
     delegate.addOption(o);
   }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/PluginConfigFactory.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/PluginConfigFactory.java
index 294d8a5..8df7073 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/PluginConfigFactory.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/PluginConfigFactory.java
@@ -14,27 +14,45 @@
 
 package com.google.gerrit.server.config;
 
+import com.google.common.collect.Maps;
 import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.server.git.ProjectLevelConfig;
 import com.google.gerrit.server.project.NoSuchProjectException;
 import com.google.gerrit.server.project.ProjectCache;
 import com.google.gerrit.server.project.ProjectState;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
 
+import org.eclipse.jgit.errors.ConfigInvalidException;
 import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.storage.file.FileBasedConfig;
+import org.eclipse.jgit.util.FS;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Map;
 
 @Singleton
 public class PluginConfigFactory {
+  private static final Logger log =
+      LoggerFactory.getLogger(PluginConfigFactory.class);
+
+  private final SitePaths site;
   private final Config cfg;
   private final ProjectCache projectCache;
   private final ProjectState.Factory projectStateFactory;
+  private final Map<String, Config> pluginConfigs;
 
   @Inject
-  PluginConfigFactory(@GerritServerConfig Config cfg,
+  PluginConfigFactory(SitePaths site, @GerritServerConfig Config cfg,
       ProjectCache projectCache, ProjectState.Factory projectStateFactory) {
+    this.site = site;
     this.cfg = cfg;
     this.projectCache = projectCache;
     this.projectStateFactory = projectStateFactory;
+    this.pluginConfigs = Maps.newHashMap();
   }
 
   /**
@@ -128,4 +146,96 @@
     return getFromProjectConfig(projectName, pluginName).withInheritance(
         projectStateFactory);
   }
+
+  /**
+   * Returns the configuration for the specified plugin that is stored in the
+   * plugin configuration file 'etc/<plugin-name>.config'.
+   *
+   * The plugin configuration is only loaded once and is then cached.
+   *
+   * @param pluginName the name of the plugin for which the configuration should
+   *        be returned
+   * @return the plugin configuration from the 'etc/<plugin-name>.config' file
+   */
+  public Config getGlobalPluginConfig(String pluginName) {
+    if (pluginConfigs.containsKey(pluginName)) {
+      return pluginConfigs.get(pluginName);
+    }
+
+    File pluginConfigFile = new File(site.etc_dir, pluginName + ".config");
+    FileBasedConfig cfg = new FileBasedConfig(pluginConfigFile, FS.DETECTED);
+    pluginConfigs.put(pluginName, cfg);
+    if (!cfg.getFile().exists()) {
+      log.info("No " + pluginConfigFile.getAbsolutePath() + "; assuming defaults");
+      return cfg;
+    }
+
+    try {
+      cfg.load();
+    } catch (IOException e) {
+      log.warn("Failed to load " + pluginConfigFile.getAbsolutePath(), e);
+    } catch (ConfigInvalidException e) {
+      log.warn("Failed to load " + pluginConfigFile.getAbsolutePath(), e);
+    }
+
+    return cfg;
+  }
+
+  /**
+   * Returns the configuration for the specified plugin that is stored in the
+   * '<plugin-name>.config' file in the 'refs/meta/config' branch of the
+   * specified project.
+   *
+   * @param projectName the name of the project for which the plugin
+   *        configuration should be returned
+   * @param pluginName the name of the plugin for which the configuration should
+   *        be returned
+   * @return the plugin configuration from the '<plugin-name>.config' file of
+   *         the specified project
+   * @throws NoSuchProjectException thrown if the specified project does not
+   *         exist
+   */
+  public Config getProjectPluginConfig(Project.NameKey projectName,
+      String pluginName) throws NoSuchProjectException {
+    return getPluginConfig(projectName, pluginName).get();
+  }
+
+  /**
+   * Returns the configuration for the specified plugin that is stored in the
+   * '<plugin-name>.config' file in the 'refs/meta/config' branch of the
+   * specified project. Parameters which are not set in the
+   * '<plugin-name>.config' of this project are inherited from the parent
+   * project's '<plugin-name>.config' files.
+   *
+   * E.g.: child project: [mySection "mySubsection"] myKey = childValue
+   *
+   * parent project: [mySection "mySubsection"] myKey = parentValue anotherKey =
+   * someValue
+   *
+   * return: [mySection "mySubsection"] myKey = childValue anotherKey =
+   * someValue
+   *
+   * @param projectName the name of the project for which the plugin
+   *        configuration should be returned
+   * @param pluginName the name of the plugin for which the configuration should
+   *        be returned
+   * @return the plugin configuration from the '<plugin-name>.config' file of
+   *         the specified project with inheriting non-set parameters from the
+   *         parent projects
+   * @throws NoSuchProjectException thrown if the specified project does not
+   *         exist
+   */
+  public Config getProjectPluginConfigWithInheritance(Project.NameKey projectName,
+      String pluginName) throws NoSuchProjectException {
+    return getPluginConfig(projectName, pluginName).getWithInheritance();
+  }
+
+  private ProjectLevelConfig getPluginConfig(Project.NameKey projectName,
+      String pluginName) throws NoSuchProjectException {
+    ProjectState projectState = projectCache.get(projectName);
+    if (projectState == null) {
+      throw new NoSuchProjectException(projectName);
+    }
+    return projectState.getConfig(pluginName);
+  }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/LocalDiskRepositoryManager.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/LocalDiskRepositoryManager.java
index c51a6ec..43e0156 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/LocalDiskRepositoryManager.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/LocalDiskRepositoryManager.java
@@ -19,7 +19,6 @@
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gerrit.server.config.SitePaths;
-import com.google.inject.AbstractModule;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
 
@@ -62,17 +61,11 @@
   private static final String UNNAMED =
       "Unnamed repository; edit this file to name it for gitweb.";
 
-  public static class Module extends AbstractModule {
+  public static class Module extends LifecycleModule {
     @Override
     protected void configure() {
       bind(GitRepositoryManager.class).to(LocalDiskRepositoryManager.class);
-
-      install(new LifecycleModule() {
-        @Override
-        protected void configure() {
-          listener().to(LocalDiskRepositoryManager.Lifecycle.class);
-        }
-      });
+      listener().to(LocalDiskRepositoryManager.Lifecycle.class);
     }
   }
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectLevelConfig.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectLevelConfig.java
new file mode 100644
index 0000000..fc95608
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectLevelConfig.java
@@ -0,0 +1,97 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.git;
+
+import com.google.common.collect.Iterables;
+import com.google.gerrit.server.project.ProjectState;
+
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.lib.CommitBuilder;
+import org.eclipse.jgit.lib.Config;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Set;
+
+/** Configuration file in the projects refs/meta/config branch. */
+public class ProjectLevelConfig extends VersionedMetaData {
+  private final String fileName;
+  private final ProjectState project;
+  private Config cfg;
+
+  public ProjectLevelConfig(String fileName, ProjectState project) {
+    this.fileName = fileName;
+    this.project = project;
+  }
+
+  @Override
+  protected String getRefName() {
+    return GitRepositoryManager.REF_CONFIG;
+  }
+
+  @Override
+  protected void onLoad() throws IOException, ConfigInvalidException {
+    cfg = readConfig(fileName);
+  }
+
+  public Config get() {
+    if (cfg == null) {
+      cfg = new Config();
+    }
+    return cfg;
+  }
+
+  public Config getWithInheritance() {
+    Config cfgWithInheritance = new Config();
+    try {
+      cfgWithInheritance.fromText(get().toText());
+    } catch (ConfigInvalidException e) {
+      // cannot happen
+    }
+    ProjectState parent = Iterables.getFirst(project.parents(), null);
+    if (parent != null) {
+      Config parentCfg = parent.getConfig(fileName).getWithInheritance();
+      for (String section : parentCfg.getSections()) {
+        Set<String> allNames = get().getNames(section);
+        for (String name : parentCfg.getNames(section)) {
+          if (!allNames.contains(name)) {
+            cfgWithInheritance.setStringList(section, null, name,
+                Arrays.asList(parentCfg.getStringList(section, null, name)));
+          }
+        }
+
+        for (String subsection : parentCfg.getSubsections(section)) {
+          allNames = get().getNames(section, subsection);
+          for (String name : parentCfg.getNames(section, subsection)) {
+            if (!allNames.contains(name)) {
+              cfgWithInheritance.setStringList(section, subsection, name,
+                  Arrays.asList(parentCfg.getStringList(section, subsection, name)));
+            }
+          }
+        }
+      }
+    }
+    return cfgWithInheritance;
+  }
+
+  @Override
+  protected void onSave(CommitBuilder commit) throws IOException,
+      ConfigInvalidException {
+    if (commit.getMessage() == null || "".equals(commit.getMessage())) {
+      commit.setMessage("Updated configuration\n");
+    }
+    saveConfig(fileName, cfg);
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/RebaseSorter.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/RebaseSorter.java
index ac1929b..09c970e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/RebaseSorter.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/RebaseSorter.java
@@ -64,7 +64,7 @@
             n.statusCode = CommitMergeStatus.MISSING_DEPENDENCY;
             n.missing = new ArrayList<CodeReviewCommit>();
           }
-          n.missing.add((CodeReviewCommit) c);
+          n.missing.add(c);
         } else {
           contents.add(c);
         }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/group/ListGroups.java b/gerrit-server/src/main/java/com/google/gerrit/server/group/ListGroups.java
index 509c5a2..76e54b6 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/group/ListGroups.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/group/ListGroups.java
@@ -93,7 +93,7 @@
   @Option(name = "-m", metaVar = "MATCH", usage = "match group substring")
   private String matchSubstring;
 
-  @Option(name = "-o", multiValued = true, usage = "Output options per group")
+  @Option(name = "-o", usage = "Output options per group")
   public void addOption(ListGroupsOption o) {
     options.add(o);
   }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/patch/IntraLineLoader.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/IntraLineLoader.java
index b95994f..52cba09 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/patch/IntraLineLoader.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/patch/IntraLineLoader.java
@@ -248,12 +248,14 @@
           //
           if (ab < ae //
               && (ab == 0 || a.charAt(ab - 1) == '\n') //
-              && ae < a.size() && a.charAt(ae) == '\n') {
+              && ae < a.size() && a.charAt(ae - 1) != '\n'
+              && a.charAt(ae) == '\n') {
             ae++;
           }
           if (bb < be //
               && (bb == 0 || b.charAt(bb - 1) == '\n') //
-              && be < b.size() && b.charAt(be) == '\n') {
+              && be < b.size() && b.charAt(be - 1) != '\n'
+              && b.charAt(be) == '\n') {
             be++;
           }
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginModule.java
index bd1c3c8..055baf1 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginModule.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginModule.java
@@ -16,9 +16,8 @@
 
 import com.google.gerrit.extensions.systemstatus.ServerInformation;
 import com.google.gerrit.lifecycle.LifecycleModule;
-import com.google.inject.AbstractModule;
 
-public class PluginModule extends AbstractModule {
+public class PluginModule extends LifecycleModule {
   @Override
   protected void configure() {
     bind(ServerInformationImpl.class);
@@ -28,11 +27,6 @@
     bind(PluginGuiceEnvironment.class);
     bind(PluginLoader.class);
     bind(CopyConfigModule.class);
-    install(new LifecycleModule() {
-      @Override
-      protected void configure() {
-        listener().to(PluginLoader.class);
-      }
-    });
+    listener().to(PluginLoader.class);
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/GarbageCollect.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/GarbageCollect.java
index ca7f6f0..b98cb8b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/GarbageCollect.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/GarbageCollect.java
@@ -20,6 +20,7 @@
 import com.google.gerrit.extensions.annotations.RequiresCapability;
 import com.google.gerrit.extensions.restapi.BinaryResult;
 import com.google.gerrit.extensions.restapi.RestModifyView;
+import com.google.gerrit.extensions.webui.UiAction;
 import com.google.gerrit.server.git.GarbageCollection;
 import com.google.gerrit.server.project.GarbageCollect.Input;
 import com.google.inject.Inject;
@@ -31,8 +32,10 @@
 import java.util.Collections;
 
 @RequiresCapability(GlobalCapability.RUN_GC)
-public class GarbageCollect implements RestModifyView<ProjectResource, Input> {
+public class GarbageCollect implements RestModifyView<ProjectResource, Input>,
+    UiAction<ProjectResource> {
   public static class Input {
+    public boolean showProgress;
   }
 
   private GarbageCollection.Factory garbageCollectionFactory;
@@ -43,7 +46,7 @@
   }
 
   @Override
-  public BinaryResult apply(final ProjectResource rsrc, Input input) {
+  public BinaryResult apply(final ProjectResource rsrc, final Input input) {
     return new BinaryResult() {
       @Override
       public void writeTo(OutputStream out) throws IOException {
@@ -56,10 +59,10 @@
         };
         try {
           GarbageCollectionResult result = garbageCollectionFactory.create().run(
-              Collections.singletonList(rsrc.getNameKey()), writer);
+              Collections.singletonList(rsrc.getNameKey()), input.showProgress ? writer : null);
+          String msg = "garbage collection was successfully done";
           if (result.hasErrors()) {
             for (GarbageCollectionResult.Error e : result.getErrors()) {
-              String msg;
               switch (e.getType()) {
                 case REPOSITORY_NOT_FOUND:
                   msg = "error: project \"" + e.getProjectName() + "\" not found";
@@ -76,9 +79,9 @@
                   msg = "error: garbage collection for project \"" + e.getProjectName()
                       + "\" failed: " + e.getType();
               }
-              writer.println(msg);
             }
           }
+          writer.println(msg);
         } finally {
           writer.flush();
         }
@@ -87,4 +90,11 @@
      .setCharacterEncoding(Charsets.UTF_8.name())
      .disableGzip();
   }
+
+  @Override
+  public UiAction.Description getDescription(ProjectResource rsrc) {
+    return new UiAction.Description()
+        .setLabel("Run GC")
+        .setTitle("Triggers the Git Garbage Collection for this project.");
+  }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ListChildProjects.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ListChildProjects.java
index d4b84dd..58abe40 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ListChildProjects.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ListChildProjects.java
@@ -47,6 +47,10 @@
     this.projectNodeFactory = projectNodeFactory;
   }
 
+  public void setRecursive(boolean recursive) {
+    this.recursive = recursive;
+  }
+
   @Override
   public List<ProjectInfo> apply(ProjectResource rsrc) {
     if (recursive) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ListProjects.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ListProjects.java
index f777039..e79912e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ListProjects.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ListProjects.java
@@ -109,7 +109,7 @@
   @Option(name = "--format", usage = "(deprecated) output format")
   private OutputFormat format = OutputFormat.TEXT;
 
-  @Option(name = "--show-branch", aliases = {"-b"}, multiValued = true,
+  @Option(name = "--show-branch", aliases = {"-b"},
       usage = "displays the sha of each project in the specified branch")
   public void addShowBranch(String branch) {
     showBranch.add(branch);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectControl.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectControl.java
index f98822f..8ab3311 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectControl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectControl.java
@@ -496,14 +496,18 @@
       try {
         Map<String, Ref> allRefs = repo.getRefDatabase().getRefs(ALL);
         for (Entry<String, Ref> entry : allRefs.entrySet()) {
+          String refName = entry.getKey();
+          if (!refName.startsWith("refs/heads") && !refName.startsWith("refs/tags")) {
+            continue;
+          }
           RevCommit tip;
           try {
             tip = rw.parseCommit(entry.getValue().getObjectId());
           } catch (IncorrectObjectTypeException e) {
             continue;
           }
-          if (rw.isMergedInto(commit, tip)
-              && controlForRef(entry.getKey()).canPerform(Permission.READ)) {
+          if (controlForRef(entry.getKey()).canPerform(Permission.READ)
+              && rw.isMergedInto(commit, tip)) {
             return true;
           }
         }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectState.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectState.java
index 800fa6e..2e79e2f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectState.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectState.java
@@ -40,12 +40,14 @@
 import com.google.gerrit.server.config.SitePaths;
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.git.ProjectConfig;
+import com.google.gerrit.server.git.ProjectLevelConfig;
 import com.google.inject.Inject;
 import com.google.inject.assistedinject.Assisted;
 
 import com.googlecode.prolog_cafe.compiler.CompileException;
 import com.googlecode.prolog_cafe.lang.PrologMachineCopy;
 
+import org.eclipse.jgit.errors.ConfigInvalidException;
 import org.eclipse.jgit.lib.Ref;
 import org.eclipse.jgit.lib.Repository;
 import org.slf4j.Logger;
@@ -83,6 +85,7 @@
   private final List<CommentLinkInfo> commentLinks;
 
   private final ProjectConfig config;
+  private final Map<String, ProjectLevelConfig> configs;
   private final Set<AccountGroup.UUID> localOwners;
 
   /** Prolog rule state. */
@@ -121,6 +124,7 @@
     this.rulesCache = rulesCache;
     this.commentLinks = commentLinks;
     this.config = config;
+    this.configs = Maps.newHashMap();
     this.capabilities = isAllProjects
       ? new CapabilityCollection(config.getAccessSection(AccessSection.GLOBAL_CAPABILITIES))
       : null;
@@ -216,6 +220,29 @@
     return config;
   }
 
+  public ProjectLevelConfig getConfig(String fileName) {
+    if (configs.containsKey(fileName)) {
+      return configs.get(fileName);
+    }
+
+    ProjectLevelConfig cfg = new ProjectLevelConfig(fileName, this);
+    try {
+      Repository git = gitMgr.openRepository(getProject().getNameKey());
+      try {
+        cfg.load(git);
+      } finally {
+        git.close();
+      }
+    } catch (IOException e) {
+      log.warn("Failed to load " + fileName + " for " + getProject().getName(), e);
+    } catch (ConfigInvalidException e) {
+      log.warn("Failed to load " + fileName + " for " + getProject().getName(), e);
+    }
+
+    configs.put(fileName, cfg);
+    return cfg;
+  }
+
   public long getMaxObjectSizeLimit() {
     return config.getMaxObjectSizeLimit();
   }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/SetParent.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/SetParent.java
index 1dd8282..337c6f4 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/SetParent.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/SetParent.java
@@ -37,7 +37,7 @@
 
 import java.io.IOException;
 
-class SetParent implements RestModifyView<ProjectResource, Input> {
+public class SetParent implements RestModifyView<ProjectResource, Input> {
   static class Input {
     @DefaultInput
     String parent;
@@ -62,41 +62,14 @@
       throws AuthException, ResourceConflictException,
       ResourceNotFoundException, UnprocessableEntityException, IOException {
     ProjectControl ctl = rsrc.getControl();
+    validateParentUpdate(ctl, input.parent);
     IdentifiedUser user = (IdentifiedUser) ctl.getCurrentUser();
-    if (!user.getCapabilities().canAdministrateServer()) {
-      throw new AuthException("not administrator");
-    }
-
-    if (rsrc.getNameKey().equals(allProjects)) {
-      throw new ResourceConflictException("cannot set parent of "
-          + allProjects.get());
-    }
-
-    input.parent = Strings.emptyToNull(input.parent);
-    if (input.parent != null) {
-      ProjectState parent = cache.get(new Project.NameKey(input.parent));
-      if (parent == null) {
-        throw new UnprocessableEntityException("parent project " + input.parent
-            + " not found");
-      }
-
-      if (Iterables.tryFind(parent.tree(), new Predicate<ProjectState>() {
-        @Override
-        public boolean apply(ProjectState input) {
-          return input.getProject().getNameKey().equals(rsrc.getNameKey());
-        }
-      }).isPresent()) {
-        throw new ResourceConflictException("cycle exists between "
-            + rsrc.getName() + " and " + parent.getProject().getName());
-      }
-    }
-
     try {
       MetaDataUpdate md = updateFactory.create(rsrc.getNameKey());
       try {
         ProjectConfig config = ProjectConfig.read(md);
         Project project = config.getProject();
-        project.setParentName(input.parent);
+        project.setParentName(Strings.emptyToNull(input.parent));
 
         String msg = Strings.emptyToNull(input.commitMessage);
         if (msg == null) {
@@ -123,4 +96,39 @@
           "invalid project.config: %s", e.getMessage()));
     }
   }
+
+  public void validateParentUpdate(final ProjectControl ctl, String newParent)
+      throws AuthException, ResourceConflictException,
+      UnprocessableEntityException {
+    IdentifiedUser user = (IdentifiedUser) ctl.getCurrentUser();
+    if (!user.getCapabilities().canAdministrateServer()) {
+      throw new AuthException("not administrator");
+    }
+
+    if (ctl.getProject().getNameKey().equals(allProjects)) {
+      throw new ResourceConflictException("cannot set parent of "
+          + allProjects.get());
+    }
+
+    newParent = Strings.emptyToNull(newParent);
+    if (newParent != null) {
+      ProjectState parent = cache.get(new Project.NameKey(newParent));
+      if (parent == null) {
+        throw new UnprocessableEntityException("parent project " + newParent
+            + " not found");
+      }
+
+      if (Iterables.tryFind(parent.tree(), new Predicate<ProjectState>() {
+        @Override
+        public boolean apply(ProjectState input) {
+          return input.getProject().getNameKey()
+              .equals(ctl.getProject().getNameKey());
+        }
+      }).isPresent()) {
+        throw new ResourceConflictException("cycle exists between "
+            + ctl.getProject().getName() + " and "
+            + parent.getProject().getName());
+      }
+    }
+  }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/BasicChangeRewrites.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/BasicChangeRewrites.java
index f81b0ca..dd96d50 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/BasicChangeRewrites.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/BasicChangeRewrites.java
@@ -33,8 +33,8 @@
       new ChangeQueryBuilder.Arguments( //
           new InvalidProvider<ReviewDb>(), //
           new InvalidProvider<ChangeQueryRewriter>(), //
-          null, null, null, null, null, //
-          null, null, null, null, null), null);
+          null, null, null, null, null, null, //
+          null, null, null, null, null, null), null);
 
   static Schema<ChangeData> schema(@Nullable IndexCollection indexes) {
     ChangeIndex index = indexes != null ? indexes.getSearchIndex() : null;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
index 85459b7..37890a5 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
@@ -37,6 +37,7 @@
 import com.google.gerrit.server.index.Schema;
 import com.google.gerrit.server.patch.PatchListCache;
 import com.google.gerrit.server.project.ChangeControl;
+import com.google.gerrit.server.project.ListChildProjects;
 import com.google.gerrit.server.project.ProjectCache;
 import com.google.gerrit.server.query.IntPredicate;
 import com.google.gerrit.server.query.Predicate;
@@ -94,6 +95,7 @@
   public static final String FIELD_MESSAGE = "message";
   public static final String FIELD_OWNER = "owner";
   public static final String FIELD_OWNERIN = "ownerin";
+  public static final String FIELD_PARENTPROJECT = "parentproject";
   public static final String FIELD_PROJECT = "project";
   public static final String FIELD_REF = "ref";
   public static final String FIELD_REVIEWER = "reviewer";
@@ -140,6 +142,7 @@
     final Provider<ReviewDb> dbProvider;
     final Provider<ChangeQueryRewriter> rewriter;
     final IdentifiedUser.GenericFactory userFactory;
+    final Provider<CurrentUser> self;
     final CapabilityControl.Factory capabilityControlFactory;
     final ChangeControl.GenericFactory changeControlGenericFactory;
     final AccountResolver accountResolver;
@@ -148,6 +151,7 @@
     final PatchListCache patchListCache;
     final GitRepositoryManager repoManager;
     final ProjectCache projectCache;
+    final Provider<ListChildProjects> listChildProjects;
     final IndexCollection indexes;
 
     @Inject
@@ -155,6 +159,7 @@
     public Arguments(Provider<ReviewDb> dbProvider,
         Provider<ChangeQueryRewriter> rewriter,
         IdentifiedUser.GenericFactory userFactory,
+        Provider<CurrentUser> self,
         CapabilityControl.Factory capabilityControlFactory,
         ChangeControl.GenericFactory changeControlGenericFactory,
         AccountResolver accountResolver,
@@ -163,10 +168,12 @@
         PatchListCache patchListCache,
         GitRepositoryManager repoManager,
         ProjectCache projectCache,
+        Provider<ListChildProjects> listChildProjects,
         IndexCollection indexes) {
       this.dbProvider = dbProvider;
       this.rewriter = rewriter;
       this.userFactory = userFactory;
+      this.self = self;
       this.capabilityControlFactory = capabilityControlFactory;
       this.changeControlGenericFactory = changeControlGenericFactory;
       this.accountResolver = accountResolver;
@@ -175,6 +182,7 @@
       this.patchListCache = patchListCache;
       this.repoManager = repoManager;
       this.projectCache = projectCache;
+      this.listChildProjects = listChildProjects;
       this.indexes = indexes;
     }
   }
@@ -321,6 +329,12 @@
   }
 
   @Operator
+  public Predicate<ChangeData> parentproject(String name) {
+    return new ParentProjectPredicate(args.dbProvider, args.projectCache,
+        args.listChildProjects, args.self, name);
+  }
+
+  @Operator
   public Predicate<ChangeData> branch(String name) {
     if (name.startsWith("^"))
       return ref("^" + branchToRef(name.substring(1)));
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsVisibleToPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsVisibleToPredicate.java
index 8992318..27b4994 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsVisibleToPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsVisibleToPredicate.java
@@ -30,7 +30,7 @@
       return ((IdentifiedUser) user).getAccountId().toString();
     }
     if (user instanceof SingleGroupUser) {
-      return "group:" + ((SingleGroupUser) user).getEffectiveGroups().getKnownGroups() //
+      return "group:" + user.getEffectiveGroups().getKnownGroups() //
           .iterator().next().toString();
     }
     return user.toString();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ParentProjectPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ParentProjectPredicate.java
new file mode 100644
index 0000000..2f63f5e
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ParentProjectPredicate.java
@@ -0,0 +1,67 @@
+// 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.query.change;
+
+import com.google.common.collect.Lists;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.project.ListChildProjects;
+import com.google.gerrit.server.project.ProjectCache;
+import com.google.gerrit.server.project.ProjectJson.ProjectInfo;
+import com.google.gerrit.server.project.ProjectResource;
+import com.google.gerrit.server.project.ProjectState;
+import com.google.gerrit.server.query.OrPredicate;
+import com.google.gerrit.server.query.Predicate;
+import com.google.inject.Provider;
+
+import java.util.Collections;
+import java.util.List;
+
+class ParentProjectPredicate extends OrPredicate<ChangeData> {
+  private final String value;
+
+  ParentProjectPredicate(Provider<ReviewDb> dbProvider,
+      ProjectCache projectCache, Provider<ListChildProjects> listChildProjects,
+      Provider<CurrentUser> self, String value) {
+    super(predicates(dbProvider, projectCache, listChildProjects, self, value));
+    this.value = value;
+  }
+
+  private static List<Predicate<ChangeData>> predicates(
+      Provider<ReviewDb> dbProvider, ProjectCache projectCache,
+      Provider<ListChildProjects> listChildProjects,
+      Provider<CurrentUser> self, String value) {
+    ProjectState projectState = projectCache.get(new Project.NameKey(value));
+    if (projectState == null) {
+      return Collections.emptyList();
+    }
+
+    List<Predicate<ChangeData>> r = Lists.newArrayList();
+    r.add(new ProjectPredicate(dbProvider, projectState.getProject().getName()));
+    ListChildProjects children = listChildProjects.get();
+    children.setRecursive(true);
+    for (ProjectInfo p : children.apply(new ProjectResource(
+        projectState.controlFor(self.get())))) {
+      r.add(new ProjectPredicate(dbProvider, p.name));
+    }
+    return r;
+  }
+
+  @Override
+  public String toString() {
+    return ChangeQueryBuilder.FIELD_PARENTPROJECT + ":" + value;
+  }
+}
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 4b6a5a6..6ba9e6d 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
@@ -45,7 +45,7 @@
   private boolean reverse;
   private EnumSet<ListChangesOption> options;
 
-  @Option(name = "--query", aliases = {"-q"}, metaVar = "QUERY", multiValued = true, usage = "Query string")
+  @Option(name = "--query", aliases = {"-q"}, metaVar = "QUERY", usage = "Query string")
   private List<String> queries;
 
   @Option(name = "--limit", aliases = {"-n"}, metaVar = "CNT", usage = "Maximum number of results to return")
@@ -53,7 +53,7 @@
     imp.setLimit(limit);
   }
 
-  @Option(name = "-o", multiValued = true, usage = "Output options per change")
+  @Option(name = "-o", usage = "Output options per change")
   public void addOption(ListChangesOption o) {
     options.add(o);
   }
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/index/FakeQueryBuilder.java b/gerrit-server/src/test/java/com/google/gerrit/server/index/FakeQueryBuilder.java
index 63e62a0..77633c8 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/index/FakeQueryBuilder.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/index/FakeQueryBuilder.java
@@ -26,7 +26,7 @@
         new FakeQueryBuilder.Definition<ChangeData, FakeQueryBuilder>(
           FakeQueryBuilder.class),
         new ChangeQueryBuilder.Arguments(null, null, null, null, null, null,
-          null, null, null, null, null, indexes),
+          null, null, null, null, null, null, null, indexes),
         null);
   }
 
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/CommandModule.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/CommandModule.java
index c64f9d8..12f0db0 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/CommandModule.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/CommandModule.java
@@ -16,13 +16,13 @@
 
 import com.google.common.base.Objects;
 import com.google.common.base.Strings;
-import com.google.inject.AbstractModule;
+import com.google.gerrit.lifecycle.LifecycleModule;
 import com.google.inject.binder.LinkedBindingBuilder;
 
 import org.apache.sshd.server.Command;
 
 /** Module to register commands in the SSH daemon. */
-public abstract class CommandModule extends AbstractModule {
+public abstract class CommandModule extends LifecycleModule {
   /**
    * Configure a command to be invoked by name.
    *
@@ -72,7 +72,7 @@
    */
   protected void command(final CommandName parent,
       final Class<? extends BaseCommand> clazz) {
-    CommandMetaData meta = (CommandMetaData)clazz.getAnnotation(CommandMetaData.class);
+    CommandMetaData meta = clazz.getAnnotation(CommandMetaData.class);
     if (meta == null) {
       throw new IllegalStateException("no CommandMetaData annotation found");
     }
@@ -91,7 +91,7 @@
    */
   protected void alias(final CommandName parent, final String name,
       final Class<? extends BaseCommand> clazz) {
-    CommandMetaData meta = (CommandMetaData)clazz.getAnnotation(CommandMetaData.class);
+    CommandMetaData meta = clazz.getAnnotation(CommandMetaData.class);
     if (meta == null) {
       throw new IllegalStateException("no CommandMetaData annotation found");
     }
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/NoShell.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/NoShell.java
index cde7ae8..78f006b 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/NoShell.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/NoShell.java
@@ -106,7 +106,7 @@
       } finally {
         sshScope.set(old);
       }
-      err.write(Constants.encode(message.toString()));
+      err.write(Constants.encode(message));
       err.flush();
 
       in.close();
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshModule.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshModule.java
index 39b7f16..2322a3b 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshModule.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshModule.java
@@ -21,7 +21,6 @@
 import com.google.gerrit.lifecycle.LifecycleModule;
 import com.google.gerrit.server.PeerDaemonUser;
 import com.google.gerrit.server.RemotePeer;
-import com.google.gerrit.server.config.FactoryModule;
 import com.google.gerrit.server.config.GerritRequestModule;
 import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gerrit.server.git.AsyncReceiveCommits;
@@ -47,7 +46,7 @@
 import java.util.Map;
 
 /** Configures standard dependencies for {@link SshDaemon}. */
-public class SshModule extends FactoryModule {
+public class SshModule extends LifecycleModule {
   private final Map<String, String> aliases;
 
   @Inject
@@ -87,25 +86,20 @@
 
     install(new DefaultCommandModule());
 
-    install(new LifecycleModule() {
-      @Override
-      protected void configure() {
-        bind(ModuleGenerator.class).to(SshAutoRegisterModuleGenerator.class);
-        bind(SshPluginStarterCallback.class);
-        bind(StartPluginListener.class)
-          .annotatedWith(UniqueAnnotations.create())
-          .to(SshPluginStarterCallback.class);
+    bind(ModuleGenerator.class).to(SshAutoRegisterModuleGenerator.class);
+    bind(SshPluginStarterCallback.class);
+    bind(StartPluginListener.class)
+      .annotatedWith(UniqueAnnotations.create())
+      .to(SshPluginStarterCallback.class);
 
-        bind(ReloadPluginListener.class)
-          .annotatedWith(UniqueAnnotations.create())
-          .to(SshPluginStarterCallback.class);
+    bind(ReloadPluginListener.class)
+      .annotatedWith(UniqueAnnotations.create())
+      .to(SshPluginStarterCallback.class);
 
-        listener().toInstance(registerInParentInjectors());
-        listener().to(SshLog.class);
-        listener().to(SshDaemon.class);
-        listener().to(CommandFactoryProvider.class);
-      }
-    });
+    listener().toInstance(registerInParentInjectors());
+    listener().to(SshLog.class);
+    listener().to(SshDaemon.class);
+    listener().to(CommandFactoryProvider.class);
   }
 
   private void configureAliases() {
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ApproveOption.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ApproveOption.java
index 29250d3..fea16cd 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ApproveOption.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ApproveOption.java
@@ -24,8 +24,10 @@
 import org.kohsuke.args4j.spi.OneArgumentOptionHandler;
 import org.kohsuke.args4j.spi.OptionHandler;
 import org.kohsuke.args4j.spi.Setter;
+import org.kohsuke.args4j.spi.FieldSetter;
 
 import java.lang.annotation.Annotation;
+import java.lang.reflect.AnnotatedElement;
 
 final class ApproveOption implements Option, Setter<Short> {
   private final String name;
@@ -46,6 +48,16 @@
   }
 
   @Override
+  public String[] depends() {
+    return new String[] {};
+  }
+
+  @Override
+  public boolean hidden() {
+    return false;
+  }
+
+  @Override
   public Class<? extends OptionHandler<Short>> handler() {
     return Handler.class;
   }
@@ -56,11 +68,6 @@
   }
 
   @Override
-  public boolean multiValued() {
-    return false;
-  }
-
-  @Override
   public String name() {
     return name;
   }
@@ -85,6 +92,16 @@
   }
 
   @Override
+  public FieldSetter asFieldSetter() {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public AnnotatedElement asAnnotatedElement() {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
   public void addValue(final Short val) {
     this.value = val;
   }
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/DefaultCommandModule.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/DefaultCommandModule.java
index 429caf6..75743b0 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/DefaultCommandModule.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/DefaultCommandModule.java
@@ -14,7 +14,6 @@
 
 package com.google.gerrit.sshd.commands;
 
-import com.google.gerrit.lifecycle.LifecycleModule;
 import com.google.gerrit.sshd.CommandModule;
 import com.google.gerrit.sshd.CommandName;
 import com.google.gerrit.sshd.Commands;
@@ -77,11 +76,6 @@
 
     command("suexec").to(SuExec.class);
 
-    install(new LifecycleModule() {
-      @Override
-      protected void configure() {
-        listener().to(ShowCaches.StartupListener.class);
-      }
-    });
+    listener().to(ShowCaches.StartupListener.class);
   }
 }
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/GarbageCollectionCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/GarbageCollectionCommand.java
index 346bea7..81af0d8 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/GarbageCollectionCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/GarbageCollectionCommand.java
@@ -43,6 +43,9 @@
   @Option(name = "--all", usage = "runs the Git garbage collection for all projects")
   private boolean all;
 
+  @Option(name = "--show-progress", usage = "progress information is shown")
+  private boolean showProgress;
+
   @Argument(index = 0, required = false, multiValued = true, metaVar = "NAME",
       usage = "projects for which the Git garbage collection should be run")
   private List<ProjectControl> projects = new ArrayList<ProjectControl>();
@@ -95,7 +98,7 @@
     }
 
     GarbageCollectionResult result =
-        garbageCollectionFactory.create().run(projectNames, stdout);
+        garbageCollectionFactory.create().run(projectNames, showProgress ? stdout : null);
     if (result.hasErrors()) {
       for (GarbageCollectionResult.Error e : result.getErrors()) {
         String msg;
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Receive.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Receive.java
index 31f9301..2a8650a 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Receive.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Receive.java
@@ -62,12 +62,12 @@
   private final Set<Account.Id> reviewerId = new HashSet<Account.Id>();
   private final Set<Account.Id> ccId = new HashSet<Account.Id>();
 
-  @Option(name = "--reviewer", aliases = {"--re"}, multiValued = true, metaVar = "EMAIL", usage = "request reviewer for change(s)")
+  @Option(name = "--reviewer", aliases = {"--re"}, metaVar = "EMAIL", usage = "request reviewer for change(s)")
   void addReviewer(final Account.Id id) {
     reviewerId.add(id);
   }
 
-  @Option(name = "--cc", aliases = {}, multiValued = true, metaVar = "EMAIL", usage = "CC user on change(s)")
+  @Option(name = "--cc", aliases = {}, metaVar = "EMAIL", usage = "CC user on change(s)")
   void addCC(final Account.Id id) {
     ccId.add(id);
   }
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetAccountCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetAccountCommand.java
index 5736bb6..f634147 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetAccountCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetAccountCommand.java
@@ -70,16 +70,16 @@
   @Option(name = "--inactive", usage = "set account's state to inactive")
   private boolean inactive;
 
-  @Option(name = "--add-email", multiValued = true, metaVar = "EMAIL", usage = "email addresses to add to the account")
+  @Option(name = "--add-email", metaVar = "EMAIL", usage = "email addresses to add to the account")
   private List<String> addEmails = new ArrayList<String>();
 
-  @Option(name = "--delete-email", multiValued = true, metaVar = "EMAIL", usage = "email addresses to delete from the account")
+  @Option(name = "--delete-email", metaVar = "EMAIL", usage = "email addresses to delete from the account")
   private List<String> deleteEmails = new ArrayList<String>();
 
-  @Option(name = "--add-ssh-key", multiValued = true, metaVar = "-|KEY", usage = "public keys to add to the account")
+  @Option(name = "--add-ssh-key", metaVar = "-|KEY", usage = "public keys to add to the account")
   private List<String> addSshKeys = new ArrayList<String>();
 
-  @Option(name = "--delete-ssh-key", multiValued = true, metaVar = "-|KEY", usage = "public keys to delete from the account")
+  @Option(name = "--delete-ssh-key", metaVar = "-|KEY", usage = "public keys to delete from the account")
   private List<String> deleteSshKeys = new ArrayList<String>();
 
   @Option(name = "--http-password", metaVar = "PASSWORD", usage = "password for HTTP authentication for the account")
diff --git a/gerrit-util-cli/src/main/java/com/google/gerrit/util/cli/CmdLineParser.java b/gerrit-util-cli/src/main/java/com/google/gerrit/util/cli/CmdLineParser.java
index b75635f..7fc6d6f 100644
--- a/gerrit-util-cli/src/main/java/com/google/gerrit/util/cli/CmdLineParser.java
+++ b/gerrit-util-cli/src/main/java/com/google/gerrit/util/cli/CmdLineParser.java
@@ -52,10 +52,13 @@
 import org.kohsuke.args4j.spi.EnumOptionHandler;
 import org.kohsuke.args4j.spi.OptionHandler;
 import org.kohsuke.args4j.spi.Setter;
+import org.kohsuke.args4j.spi.FieldSetter;
+
 
 import java.io.StringWriter;
 import java.io.Writer;
 import java.lang.annotation.Annotation;
+import java.lang.reflect.AnnotatedElement;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
@@ -381,6 +384,16 @@
     }
 
     @Override
+    public String[] depends() {
+      return new String[] {};
+    }
+
+    @Override
+    public boolean hidden() {
+      return false;
+    }
+
+    @Override
     public String usage() {
       return "display this help text";
     }
@@ -401,11 +414,6 @@
     }
 
     @Override
-    public boolean multiValued() {
-      return false;
-    }
-
-    @Override
     public boolean required() {
       return false;
     }
@@ -416,13 +424,23 @@
     }
 
     @Override
+    public FieldSetter asFieldSetter() {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public AnnotatedElement asAnnotatedElement() {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
     public Class<Boolean> getType() {
       return Boolean.class;
     }
 
     @Override
     public boolean isMultiValued() {
-      return multiValued();
+      return false;
     }
   }
 }
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 5936911..36595c0 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
@@ -196,16 +196,10 @@
       final DataSourceType dst = Guice.createInjector(new DataSourceModule(),
           configModule, sitePathModule).getInstance(
             Key.get(DataSourceType.class, Names.named(dbType.toLowerCase())));
-      modules.add(new AbstractModule() {
-        @Override
-        protected void configure() {
-          bind(DataSourceType.class).toInstance(dst);
-        }
-      });
-
       modules.add(new LifecycleModule() {
         @Override
         protected void configure() {
+          bind(DataSourceType.class).toInstance(dst);
           bind(DataSourceProvider.Context.class).toInstance(
               DataSourceProvider.Context.MULTI_USER);
           bind(Key.get(DataSource.class, Names.named("ReviewDb"))).toProvider(
diff --git a/lib/BUCK b/lib/BUCK
index 5ee1bab..2469692 100644
--- a/lib/BUCK
+++ b/lib/BUCK
@@ -121,8 +121,8 @@
 
 maven_jar(
   name = 'args4j',
-  id = 'args4j:args4j:2.0.16',
-  sha1 = '9f00fb12820743b9e05c686eba543d64dd43f2b1',
+  id = 'args4j:args4j:2.0.26',
+  sha1 = '01ebb18ebb3b379a74207d5af4ea7c8338ebd78b',
   license = 'args4j',
 )
 
diff --git a/lib/jetty/BUCK b/lib/jetty/BUCK
index 6eac1a9..6314f99 100644
--- a/lib/jetty/BUCK
+++ b/lib/jetty/BUCK
@@ -1,12 +1,12 @@
 include_defs('//lib/maven.defs')
 
-VERSION = '8.1.7.v20120910'
+VERSION = '9.0.6.v20130930'
 EXCLUDE = ['about.html']
 
 maven_jar(
   name = 'servlet',
   id = 'org.eclipse.jetty:jetty-servlet:' + VERSION,
-  sha1 = '93da01e3ea26e70449e9a1a0affa5c31436be5a0',
+  sha1 = 'b6953af6f857e78fe3c46935fa96f9c80a9cbefd',
   license = 'Apache2.0',
   deps = [
     ':security',
@@ -18,7 +18,7 @@
 maven_jar(
   name = 'security',
   id = 'org.eclipse.jetty:jetty-security:' + VERSION,
-  sha1 = '8d78beb7a07f4cccee05a3f16a264f1025946258',
+  sha1 = 'b16cd29ad9d3d3cca8176a654adf4b2ddca0eb3e',
   license = 'Apache2.0',
   deps = [':server'],
   exclude = EXCLUDE,
@@ -28,7 +28,7 @@
 maven_jar(
   name = 'server',
   id = 'org.eclipse.jetty:jetty-server:' + VERSION,
-  sha1 = '6c81f733f28713919e99c2f8952e6ca5178033cd',
+  sha1 = 'd89016c2bcd380f548530fa049295e62601dc564',
   license = 'Apache2.0',
   deps = [
     ':continuation',
@@ -41,7 +41,7 @@
 maven_jar(
   name = 'continuation',
   id = 'org.eclipse.jetty:jetty-continuation:' + VERSION,
-  sha1 = 'f60cfe6267038000b459508529c88737601081e4',
+  sha1 = '4f942a52a3e996634ff302ab98f46d224b0db9d2',
   license = 'Apache2.0',
   exclude = EXCLUDE,
 )
@@ -49,7 +49,7 @@
 maven_jar(
   name = 'http',
   id = 'org.eclipse.jetty:jetty-http:' + VERSION,
-  sha1 = '10126433876cd74534695f7f99c4362596555493',
+  sha1 = 'b3a2302717ac1889b4a17ed03e2555f8291121b9',
   license = 'Apache2.0',
   deps = [':io'],
   exclude = EXCLUDE,
@@ -58,7 +58,7 @@
 maven_jar(
   name = 'io',
   id = 'org.eclipse.jetty:jetty-io:' + VERSION,
-  sha1 = 'a81f746ae1b10c37e1bb0a01d1374c202c0bd549',
+  sha1 = 'f3a66e0507d963c51e280243f0472a5b2eadc8b1',
   license = 'Apache2.0',
   deps = [':util'],
   exclude = EXCLUDE,
@@ -68,7 +68,7 @@
 maven_jar(
   name = 'util',
   id = 'org.eclipse.jetty:jetty-util:' + VERSION,
-  sha1 = '7eb2004ab2c22fd3b00095bd9ba0f32a9e88f6a5',
+  sha1 = 'f36c9e61559d1154be9b52803ef4f586e401dac6',
   license = 'Apache2.0',
   exclude = EXCLUDE,
   visibility = [],
diff --git a/plugins/replication b/plugins/replication
index c126e78..5ee79cc 160000
--- a/plugins/replication
+++ b/plugins/replication
@@ -1 +1 @@
-Subproject commit c126e78886cd985edddee66ad1aba0130a4a5819
+Subproject commit 5ee79cc5355e24bee7d38f697aa834e1bd6e0e7d
diff --git a/tools/default.defs b/tools/default.defs
index 4ed5635..b7ef6b4 100644
--- a/tools/default.defs
+++ b/tools/default.defs
@@ -14,6 +14,8 @@
 
 # Rule definitions loaded by default into every BUCK file.
 
+include_defs('//tools/gwt-constants.defs')
+
 def genantlr(
     name,
     srcs,
@@ -128,6 +130,7 @@
     deps = [],
     srcs = [],
     resources = [],
+    gwt_module = None,
     manifest_file = None,
     manifest_entries = [],
     type = 'plugin',
@@ -149,20 +152,47 @@
     srcs = mf_src,
     out = 'MANIFEST.MF',
   )
+  gwt_deps = []
+  static_jars = []
+  if gwt_module:
+    gwt_deps = GWT_PLUGIN_DEPS
+    static_jars = [':%s-static-jar' % name]
   java_library2(
     name = name + '__plugin',
     srcs = srcs,
     resources = resources,
     deps = deps,
-    compile_deps = ['//:%s-lib' % type],
+    compile_deps = ['//:%s-lib' % type] + gwt_deps,
   )
+  if gwt_module:
+    prebuilt_jar(
+      name = '%s-static-jar' % name,
+      binary_jar = genfile('%s-static.zip' % name),
+      deps = [':%s-static' % name],
+    )
+    genrule(
+      name = '%s-static' % name,
+      cmd = 'mkdir -p $TMP/static' +
+        ';unzip -qd $TMP/static $(location %s)' %
+        ':%s__gwt_application' % name +
+        ';cd $TMP' +
+        ';zip -qr $OUT .',
+      out = '%s-static.zip' % name,
+      deps = [':%s__gwt_application' % name]
+    )
+    gwt_application(
+      name = name + '__gwt_application',
+      module_target = gwt_module,
+      compiler_opts = GWT_COMPILER_OPTS,
+      deps = [':%s__plugin' % name] + gwt_deps,
+    )
   java_binary(
     name = name,
     manifest_file = genfile('MANIFEST.MF'),
     deps = [
       ':%s__plugin' % name,
       ':%s__manifest' % name,
-    ],
+    ] + static_jars,
     visibility = visibility,
   )
 
diff --git a/tools/gwt-constants.defs b/tools/gwt-constants.defs
new file mode 100644
index 0000000..56978e7
--- /dev/null
+++ b/tools/gwt-constants.defs
@@ -0,0 +1,13 @@
+GWT_COMPILER_OPTS = [
+  '-strict',
+  '-style', 'OBF',
+  '-optimize', '9',
+  '-XdisableClassMetadata',
+  '-XdisableCastChecking',
+  '-XenableClosureCompiler',
+]
+
+GWT_PLUGIN_DEPS = [
+  '//gerrit-plugin-gwtui:client',
+  '//lib/gwt:user',
+]