Merge "Fix incorrect reviewedb dependency in solr"
diff --git a/.buckconfig b/.buckconfig
index bcefa2a..bb55e59 100644
--- a/.buckconfig
+++ b/.buckconfig
@@ -1,16 +1,16 @@
 [alias]
   api = //:api
-  api_deploy = //:api_deploy
-  api_install = //:api_install
-  download = //:download
-  download_sources = //:download_sources
+  api_deploy = //tools/maven:deploy
+  api_install = //tools/maven:install
+  download = //tools:download
+  download_sources = //tools:download_sources
   gerrit = //:gerrit
   eclipse = //tools/eclipse:eclipse
   eclipse_project = //tools/eclipse:eclipse_project
   release = //:release
 
 [buildfile]
-  includes = //tools/DEFS
+  includes = //tools/default.defs
 
 [java]
   src_roots = java, resources
diff --git a/BUCK b/BUCK
index c878aba..3674370 100644
--- a/BUCK
+++ b/BUCK
@@ -20,10 +20,12 @@
   out = '__fake.api__',
 )
 
-maven_install(name = 'api_install', deps = API_DEPS)
-maven_deploy(name = 'api_deploy', deps = API_DEPS)
+java_binary(
+  name = 'extension-api',
+  deps = [':extension-lib'],
+  visibility = ['//tools/maven:'],
+)
 
-java_binary(name = 'extension-api', deps = [':extension-lib'])
 java_library(
   name = 'extension-lib',
   deps = [
@@ -35,11 +37,13 @@
   export_deps = True,
   visibility = ['PUBLIC'],
 )
+
 genrule(
   name = 'extension-api-src',
   cmd = 'ln -s $(location //gerrit-extension-api:api-src) $OUT',
   deps = ['//gerrit-extension-api:api-src'],
   out = 'extension-api-src.jar',
+  visibility = ['//tools/maven:'],
 )
 
 PLUGIN_API = [
@@ -48,30 +52,23 @@
   '//gerrit-httpd:httpd',
 ]
 
-java_binary(name = 'plugin-api', deps = [':plugin-lib'])
+java_binary(
+  name = 'plugin-api',
+  deps = [':plugin-lib'],
+  visibility = ['//tools/maven:'],
+)
+
 java_library(
   name = 'plugin-lib',
   deps = PLUGIN_API,
   export_deps = True,
   visibility = ['PUBLIC'],
 )
+
 java_binary(
   name = 'plugin-api-src',
   deps = [
     '//gerrit-extension-api:api-src',
   ] + [d + '-src' for d in PLUGIN_API],
-)
-
-genrule(
-  name = 'download',
-  cmd = '$(exe //tools:download_all)',
-  deps = ['//tools:download_all'],
-  out = '__fake.download__',
-)
-
-genrule(
-  name = 'download_sources',
-  cmd = '$(exe //tools:download_all) --src',
-  deps = ['//tools:download_all'],
-  out = '__fake.download__',
+  visibility = ['//tools/maven:'],
 )
diff --git a/Documentation/config-gerrit.txt b/Documentation/config-gerrit.txt
index 49472df..b6867b4 100644
--- a/Documentation/config-gerrit.txt
+++ b/Documentation/config-gerrit.txt
@@ -255,6 +255,26 @@
 HTTP header to trust the username from, or unset to select HTTP basic
 or digest authentication.  Only used if `auth.type` is set to HTTP.
 
+[[auth.loginUrl]]auth.loginUrl::
++
+URL to redirect a browser to after the end-user has clicked on the
+login link in the upper right corner. Only used if 'auth.type' was set
+to HTTP or HTTP_LDAP.
+Organizations using an enterprise single-sign-on solution may want to
+redirect the browser to the SSO product's sign-in page for completing the
+login process and validate their credentials.
++
+If set, Gerrit allows to access anonymously until the end-user performs the login
+and then provides a trusted identity through the HTTP header.
+If not set, Gerrit requires the HTTP header with a trusted identity
+otherwise returns the error page LoginRedirect.html.
+
+[[auth.loginText]]auth.loginText::
++
+Text displayed in the loginUrl link. Only used if 'auth.loginUrl' was set.
++
+If not set, the 'Sign In' text is used.
+
 [[auth.logoutUrl]]auth.logoutUrl::
 +
 URL to redirect a browser to after the end-user has clicked on the
diff --git a/gerrit-acceptance-tests/BUCK b/gerrit-acceptance-tests/BUCK
index abde86f..fc65f2c 100644
--- a/gerrit-acceptance-tests/BUCK
+++ b/gerrit-acceptance-tests/BUCK
@@ -1,4 +1,4 @@
-include_defs('//gerrit-acceptance-tests/DEFS')
+include_defs('//gerrit-acceptance-tests/tests.defs')
 
 java_library(
   name = 'lib',
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/GerritServer.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/GerritServer.java
index 3624353..6c6cd26 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/GerritServer.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/GerritServer.java
@@ -23,6 +23,7 @@
 import com.google.inject.Module;
 
 import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.lib.RepositoryCache;
 import org.eclipse.jgit.storage.file.FileBasedConfig;
 import org.eclipse.jgit.util.FS;
 
@@ -33,6 +34,7 @@
 import java.net.InetSocketAddress;
 import java.net.ServerSocket;
 import java.net.URI;
+import java.net.UnknownHostException;
 import java.util.concurrent.BrokenBarrierException;
 import java.util.concurrent.Callable;
 import java.util.concurrent.CyclicBarrier;
@@ -97,6 +99,9 @@
     cfg.setString("gerrit", null, "canonicalWebUrl", url);
     cfg.setString("httpd", null, "listenUrl", url);
     cfg.setString("sshd", null, "listenAddress", format(sshd));
+    cfg.setString("cache", null, "directory", null);
+    cfg.setInt("cache", "projects", "checkFrequency", 0);
+    cfg.setInt("plugins", null, "checkFrequency", 0);
     cfg.save();
     return tmp;
   }
@@ -125,7 +130,7 @@
   }
 
   private static final InetSocketAddress newPort() throws IOException {
-    ServerSocket s = new ServerSocket(0, 0, InetAddress.getLocalHost());
+    ServerSocket s = new ServerSocket(0, 0, getLocalHost());
     try {
       return (InetSocketAddress) s.getLocalSocketAddress();
     } finally {
@@ -133,13 +138,25 @@
     }
   }
 
+  private static InetAddress getLocalHost() throws UnknownHostException {
+    try {
+      return InetAddress.getLocalHost();
+    } catch (UnknownHostException e1) {
+      try {
+        return InetAddress.getByName("localhost");
+      } catch (UnknownHostException e2) {
+        return InetAddress.getByName("127.0.0.1");
+      }
+    }
+  }
+
   private File sitePath;
   private Daemon daemon;
   private ExecutorService daemonService;
   private Injector testInjector;
   private String url;
-  private int sshdPort;
-  private int httpPort;
+  private InetSocketAddress sshdAddress;
+  private InetSocketAddress httpAddress;
 
   private GerritServer(File sitePath, Injector testInjector, Daemon daemon,
       ExecutorService daemonService) throws IOException, ConfigInvalidException {
@@ -152,23 +169,26 @@
         new File(new File(sitePath, "etc"), "gerrit.config"),
         FS.DETECTED);
     cfg.load();
+
     url = cfg.getString("gerrit", null, "canonicalWebUrl");
-    sshdPort = SocketUtil.parse(
+    URI uri = URI.create(url);
+
+    sshdAddress = SocketUtil.resolve(
         cfg.getString("sshd", null, "listenAddress"),
-        0).getPort();
-    httpPort = URI.create(url).getPort();
+        0);
+    httpAddress = new InetSocketAddress(uri.getHost(), uri.getPort());
   }
 
   String getUrl() {
     return url;
   }
 
-  int getSshdPort() {
-    return sshdPort;
+  InetSocketAddress getSshdAddress() {
+    return sshdAddress;
   }
 
-  int getHttpPort() {
-    return httpPort;
+  InetSocketAddress getHttpAddress() {
+    return httpAddress;
   }
 
   Injector getTestInjector() {
@@ -182,5 +202,6 @@
     daemonService.shutdownNow();
     daemonService.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS);
     TempFileUtil.recursivelyDelete(sitePath);
+    RepositoryCache.clear();
   }
 }
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/SshSession.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/SshSession.java
index aa6d025..dc648fc 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/SshSession.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/SshSession.java
@@ -16,6 +16,7 @@
 
 import java.io.IOException;
 import java.io.InputStream;
+import java.net.InetSocketAddress;
 import java.util.Scanner;
 
 import com.jcraft.jsch.ChannelExec;
@@ -25,13 +26,13 @@
 
 public class SshSession {
 
-  private final int port;
+  private final InetSocketAddress addr;
   private final TestAccount account;
   private Session session;
   private String error;
 
   public SshSession(GerritServer server, TestAccount account) {
-    this.port = server.getSshdPort();
+    this.addr = server.getSshdAddress();
     this.account = account;
   }
 
@@ -74,7 +75,10 @@
       JSch jsch = new JSch();
       jsch.addIdentity("KeyPair",
           account.privateKey(), account.sshKey.getPublicKeyBlob(), null);
-      session = jsch.getSession(account.username, "localhost", port);
+      session = jsch.getSession(
+          account.username,
+          addr.getAddress().getHostAddress(),
+          addr.getPort());
       session.setConfig("StrictHostKeyChecking", "no");
       session.connect();
     }
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/TestAccount.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/TestAccount.java
index bfd5c2f..b85ffea 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/TestAccount.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/TestAccount.java
@@ -52,12 +52,10 @@
   }
 
   public String getHttpUrl(GerritServer server) {
-    StringBuilder b = new StringBuilder();
-    b.append("http://");
-    b.append(username);
-    b.append(":");
-    b.append(httpPassword);
-    b.append("@localhost:" + server.getHttpPort());
-    return b.toString();
+    return String.format("http://%s:%s@%s:%d",
+        username,
+        httpPassword,
+        server.getHttpAddress().getAddress().getHostAddress(),
+        server.getHttpAddress().getPort());
   }
 }
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/PushForReviewIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/AbstractPushForReview.java
similarity index 67%
rename from gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/PushForReviewIT.java
rename to gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/AbstractPushForReview.java
index 8493434..a6ce132 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/PushForReviewIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/AbstractPushForReview.java
@@ -39,8 +39,8 @@
 
 import java.io.IOException;
 
-public class PushForReviewIT extends AbstractDaemonTest {
-  private enum Protocol {
+public abstract class AbstractPushForReview extends AbstractDaemonTest {
+  protected enum Protocol {
     SSH, HTTP
   }
 
@@ -72,7 +72,7 @@
     db = reviewDbProvider.open();
   }
 
-  private void selectProtocol(Protocol p) throws GitAPIException, IOException {
+  protected void selectProtocol(Protocol p) throws GitAPIException, IOException {
     String url;
     switch (p) {
       case SSH:
@@ -93,40 +93,16 @@
   }
 
   @Test
-  public void testPushForMaster_HTTP() throws GitAPIException, OrmException,
+  public void testPushForMaster() throws GitAPIException, OrmException,
       IOException {
-    testPushForMaster(Protocol.HTTP);
-  }
-
-  @Test
-  public void testPushForMaster_SSH() throws GitAPIException, OrmException,
-      IOException {
-    testPushForMaster(Protocol.SSH);
-  }
-
-  private void testPushForMaster(Protocol p) throws GitAPIException,
-      OrmException, IOException {
-    selectProtocol(p);
     PushOneCommit.Result r = pushTo("refs/for/master");
     r.assertOkStatus();
     r.assertChange(Change.Status.NEW, null);
   }
 
   @Test
-  public void testPushForMasterWithTopic_HTTP()
-      throws GitAPIException, OrmException, IOException {
-    testPushForMasterWithTopic(Protocol.HTTP);
-  }
-
-  @Test
-  public void testPushForMasterWithTopic_SSH()
-      throws GitAPIException, OrmException, IOException {
-    testPushForMasterWithTopic(Protocol.SSH);
-  }
-
-  private void testPushForMasterWithTopic(Protocol p) throws GitAPIException,
+  public void testPushForMasterWithTopic() throws GitAPIException,
       OrmException, IOException {
-    selectProtocol(p);
     // specify topic in ref
     String topic = "my/topic";
     PushOneCommit.Result r = pushTo("refs/for/master/" + topic);
@@ -140,20 +116,8 @@
   }
 
   @Test
-  public void testPushForMasterWithCc_HTTP() throws GitAPIException,
-      OrmException, IOException, JSchException {
-    testPushForMasterWithCc(Protocol.HTTP);
-  }
-
-  @Test
-  public void testPushForMasterWithCc_SSH() throws GitAPIException,
-      OrmException, IOException, JSchException {
-    testPushForMasterWithCc(Protocol.SSH);
-  }
-
-  private void testPushForMasterWithCc(Protocol p) throws GitAPIException,
-      OrmException, IOException, JSchException {
-    selectProtocol(p);
+  public void testPushForMasterWithCc() throws GitAPIException, OrmException,
+      IOException, JSchException {
     // cc one user
     TestAccount user = accounts.create("user", "user@example.com", "User");
     String topic = "my/topic";
@@ -177,20 +141,8 @@
   }
 
   @Test
-  public void testPushForMasterWithReviewer_HTTP() throws GitAPIException,
+  public void testPushForMasterWithReviewer() throws GitAPIException,
       OrmException, IOException, JSchException {
-    testPushForMasterWithReviewer(Protocol.HTTP);
-  }
-
-  @Test
-  public void testPushForMasterWithReviewer_SSH() throws GitAPIException,
-      OrmException, IOException, JSchException {
-    testPushForMasterWithReviewer(Protocol.SSH);
-  }
-
-  private void testPushForMasterWithReviewer(Protocol p)
-      throws GitAPIException, OrmException, IOException, JSchException {
-    selectProtocol(p);
     // add one reviewer
     TestAccount user = accounts.create("user", "user@example.com", "User");
     String topic = "my/topic";
@@ -215,20 +167,8 @@
   }
 
   @Test
-  public void testPushForMasterAsDraft_HTTP() throws GitAPIException,
-      OrmException, IOException {
-    testPushForMasterAsDraft(Protocol.HTTP);
-  }
-
-  @Test
-  public void testPushForMasterAsDraft_SSH() throws GitAPIException,
-      OrmException, IOException {
-    testPushForMasterAsDraft(Protocol.SSH);
-  }
-
-  private void testPushForMasterAsDraft(Protocol p) throws GitAPIException,
-      OrmException, IOException {
-    selectProtocol(p);
+  public void testPushForMasterAsDraft() throws GitAPIException, OrmException,
+      IOException {
     // create draft by pushing to 'refs/drafts/'
     PushOneCommit.Result r = pushTo("refs/drafts/master");
     r.assertOkStatus();
@@ -241,20 +181,8 @@
   }
 
   @Test
-  public void testPushForNonExistingBranch_HTTP() throws GitAPIException,
+  public void testPushForNonExistingBranch() throws GitAPIException,
       OrmException, IOException {
-    testPushForNonExistingBranch(Protocol.HTTP);
-  }
-
-  @Test
-  public void testPushForNonExistingBranch_SSH() throws GitAPIException,
-      OrmException, IOException {
-    testPushForNonExistingBranch(Protocol.SSH);
-  }
-
-  private void testPushForNonExistingBranch(Protocol p) throws GitAPIException,
-      OrmException, IOException {
-    selectProtocol(p);
     String branchName = "non-existing";
     PushOneCommit.Result r = pushTo("refs/for/" + branchName);
     r.assertErrorStatus("branch " + branchName + " not found");
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/BUCK b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/BUCK
index b2dc58a..6014118 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/BUCK
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/BUCK
@@ -1,14 +1,30 @@
-include_defs('//gerrit-acceptance-tests/DEFS')
+include_defs('//gerrit-acceptance-tests/tests.defs')
 
 acceptance_tests(
-  srcs = glob(['*IT.java']),
+  srcs = ['SubmitOnPushIT.java'],
   deps = [':util'],
-  vm_args = ['-Xmx512m'],
+)
+
+acceptance_tests(
+  srcs = ['HttpPushForReviewIT.java', 'SshPushForReviewIT.java'],
+  deps = [':push_for_review'],
+)
+
+java_library(
+  name = 'push_for_review',
+  srcs = ['AbstractPushForReview.java'],
+  deps = [
+    ':util',
+    '//gerrit-acceptance-tests:lib',
+  ],
 )
 
 java_library(
   name = 'util',
-  srcs = ['GitUtil.java', 'PushOneCommit.java'],
+  srcs = [
+    'GitUtil.java',
+    'PushOneCommit.java',
+  ],
   deps = [
     '//gerrit-acceptance-tests:lib',
     '//gerrit-reviewdb:server',
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/HttpPushForReviewIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/HttpPushForReviewIT.java
new file mode 100644
index 0000000..465befd
--- /dev/null
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/HttpPushForReviewIT.java
@@ -0,0 +1,27 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.acceptance.git;
+
+import org.eclipse.jgit.api.errors.GitAPIException;
+import org.junit.Before;
+
+import java.io.IOException;
+
+public class HttpPushForReviewIT extends AbstractPushForReview {
+  @Before
+  public void selectHttpUrl() throws GitAPIException, IOException {
+    selectProtocol(Protocol.HTTP);
+  }
+}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/SshPushForReviewIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/SshPushForReviewIT.java
new file mode 100644
index 0000000..5251d2d
--- /dev/null
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/SshPushForReviewIT.java
@@ -0,0 +1,27 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.acceptance.git;
+
+import org.eclipse.jgit.api.errors.GitAPIException;
+import org.junit.Before;
+
+import java.io.IOException;
+
+public class SshPushForReviewIT extends AbstractPushForReview {
+  @Before
+  public void selectSshUrl() throws GitAPIException, IOException {
+    selectProtocol(Protocol.SSH);
+  }
+}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/BUCK b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/BUCK
index b73148b..d813501 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/BUCK
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/BUCK
@@ -1,4 +1,4 @@
-include_defs('//gerrit-acceptance-tests/DEFS')
+include_defs('//gerrit-acceptance-tests/tests.defs')
 
 acceptance_tests(
   srcs = glob(['*IT.java']),
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/BUCK b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/BUCK
index b50a347..dff94ce 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/BUCK
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/BUCK
@@ -1,4 +1,4 @@
-include_defs('//gerrit-acceptance-tests/DEFS')
+include_defs('//gerrit-acceptance-tests/tests.defs')
 
 acceptance_tests(
   srcs = glob(['*IT.java']),
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/BUCK b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/BUCK
index 43126a5..b6e017d 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/BUCK
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/BUCK
@@ -1,4 +1,4 @@
-include_defs('//gerrit-acceptance-tests/DEFS')
+include_defs('//gerrit-acceptance-tests/tests.defs')
 
 acceptance_tests(
   srcs = glob(['*IT.java']),
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/BUCK b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/BUCK
index 862f3be..bb3bb30 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/BUCK
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/BUCK
@@ -1,4 +1,4 @@
-include_defs('//gerrit-acceptance-tests/DEFS')
+include_defs('//gerrit-acceptance-tests/tests.defs')
 
 acceptance_tests(
   srcs = glob(['*IT.java']),
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/ssh/BUCK b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/ssh/BUCK
index 0cabe55..94e6f6a 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/ssh/BUCK
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/ssh/BUCK
@@ -1,4 +1,4 @@
-include_defs('//gerrit-acceptance-tests/DEFS')
+include_defs('//gerrit-acceptance-tests/tests.defs')
 
 acceptance_tests(
   srcs = glob(['*IT.java']),
diff --git a/gerrit-acceptance-tests/DEFS b/gerrit-acceptance-tests/tests.defs
similarity index 92%
rename from gerrit-acceptance-tests/DEFS
rename to gerrit-acceptance-tests/tests.defs
index 4bd40c1..f125a39 100644
--- a/gerrit-acceptance-tests/DEFS
+++ b/gerrit-acceptance-tests/tests.defs
@@ -1,7 +1,7 @@
 def acceptance_tests(
     srcs,
     deps = [],
-    vm_args = []):
+    vm_args = ['-Xmx128m']):
   for j in srcs:
     java_test(
       name = j[:-len('.java')],
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/GerritConfig.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/GerritConfig.java
index dd90400..38901c7 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/GerritConfig.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/GerritConfig.java
@@ -26,6 +26,8 @@
 public class GerritConfig implements Cloneable {
   protected String registerUrl;
   protected String registerText;
+  protected String loginUrl;
+  protected String loginText;
   protected String httpPasswordUrl;
   protected String reportBugUrl;
 
@@ -48,6 +50,22 @@
   protected int suggestFrom;
   protected int changeUpdateDelay;
 
+  public String getLoginUrl() {
+    return loginUrl;
+  }
+
+  public void setLoginUrl(final String u) {
+    loginUrl = u;
+  }
+
+  public String getLoginText() {
+    return loginText;
+  }
+
+  public void setLoginText(String signinText) {
+    this.loginText = signinText;
+  }
+
   public String getRegisterUrl() {
     return registerUrl;
   }
diff --git a/gerrit-gwtui/BUCK b/gerrit-gwtui/BUCK
index c05f682..5db6e0e 100644
--- a/gerrit-gwtui/BUCK
+++ b/gerrit-gwtui/BUCK
@@ -1,4 +1,4 @@
-include_defs('//gerrit-gwtui/DEFS')
+include_defs('//gerrit-gwtui/gwt.defs')
 
 genrule(
   name = 'ui_optdbg',
diff --git a/gerrit-gwtui/DEFS b/gerrit-gwtui/gwt.defs
similarity index 100%
rename from gerrit-gwtui/DEFS
rename to gerrit-gwtui/gwt.defs
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/Gerrit.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/Gerrit.java
index 864b2c9..d3ba3e8 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/Gerrit.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/Gerrit.java
@@ -729,8 +729,6 @@
       whoAmI(cfg.getAuthType() != AuthType.CLIENT_SSL_CERT_LDAP);
     } else {
       switch (cfg.getAuthType()) {
-        case HTTP:
-        case HTTP_LDAP:
         case CLIENT_SSL_CERT_LDAP:
           break;
 
@@ -759,6 +757,14 @@
           });
           break;
 
+        case HTTP:
+        case HTTP_LDAP:
+          if (cfg.getLoginUrl() != null) {
+            final String signinText = cfg.getLoginText() == null ? C.menuSignIn() : cfg.getLoginText();
+            menuRight.add(anchor(signinText, cfg.getLoginUrl()));
+          }
+          break;
+
         case LDAP:
         case LDAP_BIND:
         case CUSTOM_EXTENSION:
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GerritConfigProvider.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GerritConfigProvider.java
index 3966bc5..9a42866 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GerritConfigProvider.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GerritConfigProvider.java
@@ -94,10 +94,14 @@
         config.setHttpPasswordUrl(cfg.getString("auth", null, "httpPasswordUrl"));
         break;
 
-      case CLIENT_SSL_CERT_LDAP:
-      case DEVELOPMENT_BECOME_ANY_ACCOUNT:
       case HTTP:
       case HTTP_LDAP:
+        config.setLoginUrl(cfg.getString("auth", null, "loginurl"));
+        config.setLoginText(cfg.getString("auth", null, "logintext"));
+        break;
+
+      case CLIENT_SSL_CERT_LDAP:
+      case DEVELOPMENT_BECOME_ANY_ACCOUNT:
       case OPENID:
       case OPENID_SSO:
         break;
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 efd8e24..b46e6d7 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
@@ -88,7 +88,7 @@
     switch (authConfig.getAuthType()) {
       case HTTP:
       case HTTP_LDAP:
-        install(new HttpAuthModule());
+        install(new HttpAuthModule(authConfig));
         break;
 
       case CLIENT_SSL_CERT_LDAP:
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/container/HttpAuthModule.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/container/HttpAuthModule.java
index daaa7e2..638d527 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/container/HttpAuthModule.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/container/HttpAuthModule.java
@@ -14,13 +14,22 @@
 
 package com.google.gerrit.httpd.auth.container;
 
+import com.google.gerrit.server.config.AuthConfig;
 import com.google.inject.servlet.ServletModule;
 
 /** Servlets and support related to HTTP authentication. */
 public class HttpAuthModule extends ServletModule {
+  private final AuthConfig authConfig;
+
+  public HttpAuthModule(final AuthConfig authConfig) {
+    this.authConfig = authConfig;
+  }
+
   @Override
   protected void configureServlets() {
-    filter("/").through(HttpAuthFilter.class);
+    if (authConfig.getLoginUrl() == null) {
+      filter("/").through(HttpAuthFilter.class);
+    }
     serve("/login", "/login/*").with(HttpLoginServlet.class);
   }
 }
diff --git a/gerrit-server/BUCK b/gerrit-server/BUCK
index 95f5b11..cec2af8 100644
--- a/gerrit-server/BUCK
+++ b/gerrit-server/BUCK
@@ -1,4 +1,4 @@
-include_defs('//lib/prolog/DEFS')
+include_defs('//lib/prolog/prolog.defs')
 
 SRCS = glob(['src/main/java/**/*.java'])
 RESOURCES =  glob(['src/main/resources/**/*'])
diff --git a/gerrit-server/src/main/java/com/google/gerrit/common/ChangeHookRunner.java b/gerrit-server/src/main/java/com/google/gerrit/common/ChangeHookRunner.java
index 66a6ae8..50a883a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/common/ChangeHookRunner.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/common/ChangeHookRunner.java
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.common;
 
+import com.google.common.util.concurrent.ThreadFactoryBuilder;
 import com.google.gerrit.common.data.ContributorAgreement;
 import com.google.gerrit.common.data.LabelType;
 import com.google.gerrit.common.data.LabelTypes;
@@ -213,7 +214,7 @@
     private final SitePaths sitePaths;
 
     /** Thread pool used to monitor sync hooks */
-    private final ExecutorService syncHookThreadPool = Executors.newCachedThreadPool();
+    private final ExecutorService syncHookThreadPool;
 
     /** Timeout value for synchronous hooks */
     private final int syncHookTimeout;
@@ -262,6 +263,10 @@
         claSignedHook = sitePath.resolve(new File(hooksPath, getValue(config, "hooks", "claSignedHook", "cla-signed")).getPath());
         refUpdateHook = sitePath.resolve(new File(hooksPath, getValue(config, "hooks", "refUpdateHook", "ref-update")).getPath());
         syncHookTimeout = config.getInt("hooks", "syncHookTimeout", 30);
+        syncHookThreadPool = Executors.newCachedThreadPool(
+            new ThreadFactoryBuilder()
+              .setNameFormat("SyncHook-%d")
+              .build());
     }
 
     public void addChangeListener(ChangeListener listener, IdentifiedUser user) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/PatchSetInserter.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/PatchSetInserter.java
index ebacec5..d929a89 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/PatchSetInserter.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/PatchSetInserter.java
@@ -333,6 +333,8 @@
       case GERRIT:
         cv.validateForGerritCommits(event);
         break;
+      case NONE:
+        break;
       }
     } catch (CommitValidationException e) {
       throw new InvalidChangeOperationException(e.getMessage());
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/AuthConfig.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/AuthConfig.java
index 06d2a71..f1c24cb 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/AuthConfig.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/AuthConfig.java
@@ -40,6 +40,7 @@
   private final boolean enableRunAs;
   private final boolean userNameToLowerCase;
   private final boolean gitBasicAuth;
+  private final String loginUrl;
   private final String logoutUrl;
   private final String openIdSsoUrl;
   private final List<String> openIdDomains;
@@ -57,6 +58,7 @@
       throws XsrfException {
     authType = toType(cfg);
     httpHeader = cfg.getString("auth", null, "httpheader");
+    loginUrl = cfg.getString("auth", null, "loginurl");
     logoutUrl = cfg.getString("auth", null, "logouturl");
     openIdSsoUrl = cfg.getString("auth", null, "openidssourl");
     openIdDomains = Arrays.asList(cfg.getStringList("auth", null, "openIdDomain"));
@@ -124,6 +126,10 @@
     return httpHeader;
   }
 
+  public String getLoginUrl() {
+    return loginUrl;
+  }
+
   public String getLogoutURL() {
     return logoutUrl;
   }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeOp.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeOp.java
index 2b9234a..ff3fe33 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeOp.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeOp.java
@@ -847,7 +847,7 @@
         try {
           hooks.doChangeMergedHook(c,
               accountCache.get(submitter.getAccountId()).getAccount(),
-              db.patchSets().get(c.currentPatchSetId()), db);
+              db.patchSets().get(commit.patchsetId), db);
         } catch (OrmException ex) {
           log.error("Cannot run hook for submitted patch set " + c.getId(), ex);
         }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/JarPlugin.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/JarPlugin.java
index deb532f..bbb2164 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/JarPlugin.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/JarPlugin.java
@@ -57,34 +57,10 @@
     }
   }
 
-  static {
-    // Guice logs warnings about multiple injectors being created.
-    // Silence this in case HTTP plugins are used.
-    java.util.logging.Logger.getLogger("com.google.inject.servlet.GuiceFilter")
-        .setLevel(java.util.logging.Level.OFF);
-  }
-
-  static ApiType getApiType(Manifest manifest) throws InvalidPluginException {
-    Attributes main = manifest.getMainAttributes();
-    String v = main.getValue("Gerrit-ApiType");
-    if (Strings.isNullOrEmpty(v)
-        || ApiType.EXTENSION.name().equalsIgnoreCase(v)) {
-      return ApiType.EXTENSION;
-    } else if (ApiType.PLUGIN.name().equalsIgnoreCase(v)) {
-      return ApiType.PLUGIN;
-    } else if (ApiType.JS.name().equalsIgnoreCase(v)) {
-      return ApiType.JS;
-    } else {
-      throw new InvalidPluginException("Invalid Gerrit-ApiType: " + v);
-    }
-  }
-
-  private final FileSnapshot snapshot;
   private final JarFile jarFile;
   private final Manifest manifest;
   private final File dataDir;
   private final ClassLoader classLoader;
-  private final boolean disabled;
   private Class<? extends Module> sysModule;
   private Class<? extends Module> sshModule;
   private Class<? extends Module> httpModule;
@@ -108,12 +84,10 @@
       @Nullable Class<? extends Module> sshModule,
       @Nullable Class<? extends Module> httpModule) {
     super(name, srcJar, pluginUser, snapshot, apiType);
-    this.snapshot = snapshot;
     this.jarFile = jarFile;
     this.manifest = manifest;
     this.dataDir = dataDir;
     this.classLoader = classLoader;
-    this.disabled = srcJar.getName().endsWith(".disabled");
     this.sysModule = sysModule;
     this.sshModule = sshModule;
     this.httpModule = httpModule;
@@ -144,14 +118,6 @@
     }
   }
 
-  boolean isModified(File jar) {
-    return snapshot.lastModified() != jar.lastModified();
-  }
-
-  public boolean isDisabled() {
-    return disabled;
-  }
-
   void start(PluginGuiceEnvironment env) throws Exception {
     RequestContext oldContext = env.enter(this);
     try {
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/CommandFactoryProvider.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/CommandFactoryProvider.java
index 910f5f3..a2e6542 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/CommandFactoryProvider.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/CommandFactoryProvider.java
@@ -16,6 +16,7 @@
 
 import com.google.common.util.concurrent.Atomics;
 import com.google.common.util.concurrent.ThreadFactoryBuilder;
+import com.google.gerrit.extensions.events.LifecycleListener;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gerrit.server.git.WorkQueue;
@@ -23,6 +24,7 @@
 import com.google.gwtorm.server.SchemaFactory;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
+import com.google.inject.Singleton;
 
 import org.apache.sshd.server.Command;
 import org.apache.sshd.server.CommandFactory;
@@ -39,7 +41,7 @@
 import java.io.OutputStream;
 import java.util.ArrayList;
 import java.util.List;
-import java.util.concurrent.Executor;
+import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 import java.util.concurrent.Future;
 import java.util.concurrent.ScheduledExecutorService;
@@ -49,7 +51,9 @@
 /**
  * Creates a CommandFactory using commands registered by {@link CommandModule}.
  */
-class CommandFactoryProvider implements Provider<CommandFactory> {
+@Singleton
+class CommandFactoryProvider implements Provider<CommandFactory>,
+    LifecycleListener {
   private static final Logger logger = LoggerFactory
       .getLogger(CommandFactoryProvider.class);
 
@@ -57,7 +61,7 @@
   private final SshLog log;
   private final SshScope sshScope;
   private final ScheduledExecutorService startExecutor;
-  private final Executor destroyExecutor;
+  private final ExecutorService destroyExecutor;
   private final SchemaFactory<ReviewDb> schemaFactory;
 
   @Inject
@@ -80,6 +84,15 @@
   }
 
   @Override
+  public void start() {
+  }
+
+  @Override
+  public void stop() {
+    destroyExecutor.shutdownNow();
+  }
+
+  @Override
   public CommandFactory get() {
     return new CommandFactory() {
       public Command createCommand(final String requestCommand) {
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 2e42c23..0115065 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
@@ -107,6 +107,7 @@
         listener().toInstance(registerInParentInjectors());
         listener().to(SshLog.class);
         listener().to(SshDaemon.class);
+        listener().to(CommandFactoryProvider.class);
       }
     });
   }
diff --git a/lib/prolog/DEFS b/lib/prolog/prolog.defs
similarity index 100%
rename from lib/prolog/DEFS
rename to lib/prolog/prolog.defs
diff --git a/plugins/replication b/plugins/replication
index 353417f..fefda22 160000
--- a/plugins/replication
+++ b/plugins/replication
@@ -1 +1 @@
-Subproject commit 353417f94b0dbbb82e343bc114113ea8f4a7cf9d
+Subproject commit fefda225f42c7d5995f791034704eb4989915972
diff --git a/tools/BUCK b/tools/BUCK
index d18b653..4323ff9 100644
--- a/tools/BUCK
+++ b/tools/BUCK
@@ -1,7 +1,20 @@
+genrule(
+  name = 'download',
+  cmd = '$(exe :download_all)',
+  deps = [':download_all'],
+  out = '__fake.download__',
+)
+
+genrule(
+  name = 'download_sources',
+  cmd = '$(exe :download_all) --src',
+  deps = [':download_all'],
+  out = '__fake.download__',
+)
+
 python_binary(
   name = 'download_all',
   main = 'download_all.py',
-  visibility = ['PUBLIC'],
 )
 
 python_binary(
@@ -17,13 +30,6 @@
   visibility = ['PUBLIC'],
 )
 
-python_binary(
-  name = 'maven_deploy',
-  main = 'maven_deploy.py',
-  deps = [':util'],
-  visibility = ['PUBLIC'],
-)
-
 python_library(
   name = 'util',
   srcs = ['util.py'],
diff --git a/tools/build.defs b/tools/build.defs
index 58bc862..5acf9c0 100644
--- a/tools/build.defs
+++ b/tools/build.defs
@@ -11,7 +11,8 @@
 # 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.
-include_defs('//VERSION')
+
+# These definitions support building a runnable version of Gerrit.
 
 DOCS = ['//Documentation:html.zip']
 LIBS = [
diff --git a/tools/DEFS b/tools/default.defs
similarity index 98%
rename from tools/DEFS
rename to tools/default.defs
index e1f193b..e7981dd 100644
--- a/tools/DEFS
+++ b/tools/default.defs
@@ -12,6 +12,8 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+# Rule definitions loaded by default into every BUCK file.
+
 def genantlr(
     name,
     srcs,
diff --git a/tools/deploy_api.sh b/tools/deploy_api.sh
deleted file mode 100755
index 26baa31..0000000
--- a/tools/deploy_api.sh
+++ /dev/null
@@ -1,60 +0,0 @@
-#!/bin/sh
-
-set -e
-
-SRC=$(ls gerrit-plugin-api/target/gerrit-plugin-api-*-sources.jar)
-VER=${SRC#gerrit-plugin-api/target/gerrit-plugin-api-}
-VER=${VER%-sources.jar}
-
-type=release
-case $VER in
-*-SNAPSHOT)
-  echo >&2 "fatal: Cannot deploy $VER"
-  echo >&2 "       Use ./tools/version.sh --release && mvn clean package"
-  exit 1
-  ;;
-*-[0-9]*-g*) type=snapshot ;;
-esac
-URL=gs://gerrit-api/$type
-
-
-echo "Deploying $type gerrit-extension-api $VER"
-mvn deploy:deploy-file \
-  -DgroupId=com.google.gerrit \
-  -DartifactId=gerrit-extension-api \
-  -Dversion=$VER \
-  -Dpackaging=jar \
-  -Dfile=gerrit-extension-api/target/gerrit-extension-api-$VER-all.jar \
-  -DrepositoryId=gerrit-api-repository \
-  -Durl=$URL
-
-mvn deploy:deploy-file \
-  -DgroupId=com.google.gerrit \
-  -DartifactId=gerrit-extension-api \
-  -Dversion=$VER \
-  -Dpackaging=java-source \
-  -Dfile=gerrit-extension-api/target/gerrit-extension-api-$VER-all-sources.jar \
-  -Djava-source=false \
-  -DrepositoryId=gerrit-api-repository \
-  -Durl=$URL
-
-
-echo "Deploying $type gerrit-plugin-api $VER"
-mvn deploy:deploy-file \
-  -DgroupId=com.google.gerrit \
-  -DartifactId=gerrit-plugin-api \
-  -Dversion=$VER \
-  -Dpackaging=jar \
-  -Dfile=gerrit-plugin-api/target/gerrit-plugin-api-$VER.jar \
-  -DrepositoryId=gerrit-api-repository \
-  -Durl=$URL
-
-mvn deploy:deploy-file \
-  -DgroupId=com.google.gerrit \
-  -DartifactId=gerrit-plugin-api \
-  -Dversion=$VER \
-  -Dpackaging=java-source \
-  -Dfile=gerrit-plugin-api/target/gerrit-plugin-api-$VER-sources.jar \
-  -Djava-source=false \
-  -DrepositoryId=gerrit-api-repository \
-  -Durl=$URL
diff --git a/tools/maven/BUCK b/tools/maven/BUCK
new file mode 100644
index 0000000..0a470a4
--- /dev/null
+++ b/tools/maven/BUCK
@@ -0,0 +1,24 @@
+include_defs('//VERSION')
+include_defs('//tools/maven/package.defs')
+
+TYPE = 'snapshot' if GERRIT_VERSION.endswith('-SNAPSHOT') else 'release'
+
+maven_package(
+  repository = 'gerrit-api-repository',
+  url = 's3://gerrit-api@commondatastorage.googleapis.com/%s' % TYPE,
+  version = GERRIT_VERSION,
+  jar = {
+    'gerrit-extension-api': '//:extension-api',
+    'gerrit-plugin-api': '//:plugin-api',
+  },
+  src = {
+    'gerrit-extension-api': '//:extension-api-src',
+    'gerrit-plugin-api': '//:plugin-api-src',
+  },
+)
+
+python_binary(
+  name = 'mvn',
+  main = 'mvn.py',
+  deps = ['//tools:util'],
+)
diff --git a/tools/maven/mvn.py b/tools/maven/mvn.py
new file mode 100644
index 0000000..cea610c
--- /dev/null
+++ b/tools/maven/mvn.py
@@ -0,0 +1,61 @@
+#!/usr/bin/python
+# 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.
+
+from __future__ import print_function
+from optparse import OptionParser
+from sys import stderr
+from util import check_output
+
+opts = OptionParser()
+opts.add_option('--repository', help='maven repository id')
+opts.add_option('--url', help='maven repository url')
+opts.add_option('-a', help='action (valid actions are: install,deploy)')
+opts.add_option('-v', help='gerrit version')
+opts.add_option('-s', action='append', help='triplet of artifactId:type:path')
+
+args, ctx = opts.parse_args()
+if not args.v:
+  print('version is empty', file=stderr)
+  exit(1)
+
+common = [
+  '-DgroupId=com.google.gerrit',
+  '-Dversion=%s' % args.v,
+]
+
+if 'install' == args.a:
+  cmd = ['mvn', 'install:install-file'] + common
+elif 'deploy' == args.a:
+  cmd = [
+    'mvn',
+    'deploy:deploy-file',
+    '-DrepositoryId=%s' % args.repository,
+    '-Durl=%s' % args.url,
+  ] + common
+else:
+  print("unknown action -a %s" % args.a, file=stderr)
+  exit(1)
+
+for spec in args.s:
+  artifact, type, src = spec.split(':')
+  try:
+    check_output(cmd + [
+      '-DartifactId=%s' % artifact,
+      '-Dpackaging=%s' % type,
+      '-Dfile=%s' % src,
+    ])
+  except Exception as e:
+    print('%s command failed: %s' % (action, e), file=stderr)
+    exit(1)
diff --git a/tools/maven/package.defs b/tools/maven/package.defs
new file mode 100644
index 0000000..b19701f
--- /dev/null
+++ b/tools/maven/package.defs
@@ -0,0 +1,45 @@
+# 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.
+
+def maven_package(
+    version,
+    repository = None,
+    url = None,
+    jar = {},
+    src = {}):
+  cmd = ['$(exe //tools/maven:mvn)', '-v', version]
+  dep = []
+
+  for type,d in [('jar', jar), ('java-source', src)]:
+    for a,t in d.iteritems():
+      cmd.append('-s %s:%s:$(location %s)' % (a,type,t))
+      dep.append(t)
+
+  genrule(
+    name = 'install',
+    cmd = ' '.join(cmd + ['-a', 'install']),
+    deps = dep + ['//tools/maven:mvn'],
+    out = '__fake.install__',
+  )
+
+  if repository and url:
+    genrule(
+      name = 'deploy',
+      cmd = ' '.join(cmd + [
+        '-a', 'deploy',
+        '--repository', repository,
+        '--url', url]),
+      deps = dep + ['//tools/maven:mvn'],
+      out = '__fake.deploy__',
+    )
diff --git a/tools/maven_deploy.py b/tools/maven_deploy.py
deleted file mode 100644
index 4779abe..0000000
--- a/tools/maven_deploy.py
+++ /dev/null
@@ -1,92 +0,0 @@
-#!/usr/bin/python
-# 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.
-
-from __future__ import print_function
-from optparse import OptionParser
-from os.path import exists
-from sys import stderr
-from util import check_output
-
-opts = OptionParser()
-opts.add_option('-a', help='action (valid actions are: install,deploy)')
-opts.add_option('-v', help='gerrit version')
-opts.add_option('-d', help='dependencies (jars artifacts)')
-
-args, ctx = opts.parse_args()
-action = args.a
-if action not in ['deploy', 'install']:
-  print("unknown action : %s" % action, file=stderr)
-  exit(1)
-
-deps = args.d.split()
-if not deps:
-  print('dependencies are empty')
-  exit(1)
-
-extension_jar = [x for x in deps if "extension-api.jar" in x][0]
-extension_src = [x for x in deps if "extension-api-src.jar" in x][0]
-plugin_jar = [x for x in deps if "plugin-api.jar" in x][0]
-plugin_src = [x for x in deps if "plugin-api-src.jar" in x][0]
-
-version = args.v
-if not version:
-  print('version is empty')
-  exit(1)
-
-REPO_TYPE = 'snapshot' if version.endswith("SNAPSHOT") else 'release'
-URL = 's3://gerrit-api@commondatastorage.googleapis.com/%s' % REPO_TYPE
-
-plugin = ['-DartifactId=gerrit-plugin-api']
-extension = ['-DartifactId=gerrit-extension-api']
-common = [
-  '-DgroupId=com.google.gerrit',
-  '-Dversion=%s' % version,
-]
-jar = ['-Dpackaging=jar']
-src = ['-Dpackaging=java-source']
-
-cmd = {
-  'deploy': ['mvn',
-             'deploy:deploy-file',
-             '-DrepositoryId=gerrit-api-repository',
-             '-Durl=%s' % URL],
-  'install': ['mvn',
-              'install:install-file'],
-  }
-
-try:
-  check_output(cmd[action] +
-               plugin +
-               common +
-               jar +
-               ['-Dfile=%s' % plugin_jar])
-  check_output(cmd[action] +
-               plugin +
-               common +
-               src +
-               ['-Dfile=%s' % plugin_src])
-  check_output(cmd[action] +
-               extension +
-               common +
-               jar +
-               ['-Dfile=%s' % extension_jar])
-  check_output(cmd[action] +
-               extension +
-               common +
-               src +
-               ['-Dfile=%s' % extension_src])
-except Exception as e:
-  print('%s command failed: %s' % (action, e), file=stderr)
-  exit(1)