Merge "Add extension point to register JGit PostReceiveHooks"
diff --git a/Documentation/dev-plugins.txt b/Documentation/dev-plugins.txt
index 7593578..2e9c83c 100644
--- a/Documentation/dev-plugins.txt
+++ b/Documentation/dev-plugins.txt
@@ -1519,7 +1519,7 @@
 ----
 
 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
+`com.google.gerrit.plugin.client.rpc.RestApi` to construct the URL
 path and to trigger the REST calls.
 
 Example for invoking a Gerrit core REST endpoint:
diff --git a/Documentation/rest-api-projects.txt b/Documentation/rest-api-projects.txt
index 95d7f26..1409e96 100644
--- a/Documentation/rest-api-projects.txt
+++ b/Documentation/rest-api-projects.txt
@@ -1299,16 +1299,18 @@
 |`warning`         |optional|
 Warning message for the configuration parameter.
 |`type`            ||
-The type of the configuration parameter, can be `STRING`, `INT`,
-`LONG`, `BOOLEAN` or `LIST`.
+The type of the configuration parameter. Can be `STRING`, `INT`,
+`LONG`, `BOOLEAN`, `LIST` or `ARRAY`.
 |`value`           |optional|
 The value of the configuration parameter as string. If the parameter
 is inheritable this is the effective value which is deduced from
 `configured_value` and `inherited_value`.
+|`values`          |optional|
+The list of values. Only set if the `type` is `ARRAY`.
 `editable`         |`false` if not set|
 Whether the value is editable.
 |`permitted_values`|optional|
-The list of permitted values, only set if the `type` is `LIST`.
+The list of permitted values. Only set if the `type` is `LIST`.
 |`inheritable`     |`false` if not set|
 Whether the configuration parameter can be inherited.
 |`configured_value`|optional|
diff --git a/Documentation/user-search.txt b/Documentation/user-search.txt
index b28cbdb..99d910e 100644
--- a/Documentation/user-search.txt
+++ b/Documentation/user-search.txt
@@ -67,16 +67,16 @@
 [[before_until]]
 before:'TIME'/until:'TIME'
 +
-Changes modified before the given 'TIME', inclusive. With no time,
-assumes 00:00:00 in the local time zone. Supports many formats supported
-by `git log`.
+Changes modified before the given 'TIME', inclusive. Must be in the
+format `2006-01-02[ 15:04:05[.890][ -0700]]`; omitting the time defaults
+to 00:00:00 and omitting the timezone defaults to UTC.
 
 [[after_since]]
 after:'TIME'/since:'TIME'
 +
-Changes modified before the given 'TIME', inclusive. With no time,
-assumes 00:00:00 in the local time zone. Supports many formats supported
-by `git log`.
+Changes modified after the given 'TIME', inclusive. Must be in the
+format `2006-01-02[ 15:04:05[.890][ -0700]]`; omitting the time defaults
+to 00:00:00 and omitting the timezone defaults to UTC.
 
 [[change]]
 change:'ID'::
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/AbstractDaemonTest.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
index 6e6ef37..5c5746b 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
@@ -22,6 +22,7 @@
 import com.google.common.base.Joiner;
 import com.google.common.primitives.Chars;
 import com.google.gerrit.extensions.api.GerritApi;
+import com.google.gerrit.extensions.api.changes.RevisionApi;
 import com.google.gerrit.extensions.common.ChangeInfo;
 import com.google.gerrit.extensions.common.ListChangesOption;
 import com.google.gerrit.extensions.restapi.RestApiException;
@@ -196,4 +197,10 @@
   protected static Gson newGson() {
     return OutputFormat.JSON_COMPACT.newGson();
   }
+
+  protected RevisionApi revision(PushOneCommit.Result r) throws Exception {
+    return gApi.changes()
+        .id(r.getChangeId())
+        .current();
+  }
 }
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/project/CustomLabelIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/project/CustomLabelIT.java
new file mode 100644
index 0000000..83c9f38
--- /dev/null
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/project/CustomLabelIT.java
@@ -0,0 +1,150 @@
+// Copyright (C) 2014 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.server.project;
+
+import static com.google.gerrit.server.group.SystemGroupBackend.ANONYMOUS_USERS;
+import static com.google.gerrit.server.project.Util.category;
+import static com.google.gerrit.server.project.Util.grant;
+import static com.google.gerrit.server.project.Util.value;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.NoHttpd;
+import com.google.gerrit.acceptance.PushOneCommit;
+import com.google.gerrit.common.data.LabelType;
+import com.google.gerrit.common.data.Permission;
+import com.google.gerrit.extensions.api.changes.ReviewInput;
+import com.google.gerrit.extensions.common.ChangeInfo;
+import com.google.gerrit.extensions.common.LabelInfo;
+import com.google.gerrit.reviewdb.client.AccountGroup;
+import com.google.gerrit.server.config.AllProjectsName;
+import com.google.gerrit.server.git.MetaDataUpdate;
+import com.google.gerrit.server.git.ProjectConfig;
+import com.google.gerrit.server.group.SystemGroupBackend;
+import com.google.gerrit.server.project.ProjectCache;
+import com.google.inject.Inject;
+
+import org.junit.Before;
+import org.junit.Test;
+
+@NoHttpd
+public class CustomLabelIT extends AbstractDaemonTest {
+
+  @Inject
+  private ProjectCache projectCache;
+
+  @Inject
+  private AllProjectsName allProjects;
+
+  @Inject
+  private MetaDataUpdate.Server metaDataUpdateFactory;
+
+  private final LabelType Q = category("CustomLabel",
+      value(1, "Positive"),
+      value(0, "No score"),
+      value(-1, "Negative"));
+
+  @Before
+  public void setUp() throws Exception {
+    ProjectConfig cfg = projectCache.checkedGet(allProjects).getConfig();
+    AccountGroup.UUID anonymousUsers =
+        SystemGroupBackend.getGroup(ANONYMOUS_USERS).getUUID();
+    grant(cfg, Permission.forLabel(Q.getName()), -1, 1, anonymousUsers,
+        "refs/heads/*");
+    saveProjectConfig(cfg);
+  }
+
+  @Test
+  public void customLabelNoOp_NegativeVoteNotBlock() throws Exception {
+    Q.setFunctionName("NoOp");
+    saveLabelConfig();
+    PushOneCommit.Result r = createChange();
+    revision(r).review(new ReviewInput().label(Q.getName(), -1));
+    ChangeInfo c = get(r.getChangeId());
+    LabelInfo q = c.labels.get(Q.getName());
+    assertEquals(1, q.all.size());
+    assertNull(q.rejected);
+    assertNotNull(q.disliked);
+  }
+
+  @Test
+  public void customLabelNoBlock_NegativeVoteNotBlock() throws Exception {
+    Q.setFunctionName("NoBlock");
+    saveLabelConfig();
+    PushOneCommit.Result r = createChange();
+    revision(r).review(new ReviewInput().label(Q.getName(), -1));
+    ChangeInfo c = get(r.getChangeId());
+    LabelInfo q = c.labels.get(Q.getName());
+    assertEquals(1, q.all.size());
+    assertNull(q.rejected);
+    assertNotNull(q.disliked);
+  }
+
+  @Test
+  public void customLabelMaxNoBlock_NegativeVoteNotBlock() throws Exception {
+    Q.setFunctionName("MaxNoBlock");
+    saveLabelConfig();
+    PushOneCommit.Result r = createChange();
+    revision(r).review(new ReviewInput().label(Q.getName(), -1));
+    ChangeInfo c = get(r.getChangeId());
+    LabelInfo q = c.labels.get(Q.getName());
+    assertEquals(1, q.all.size());
+    assertNull(q.rejected);
+    assertNotNull(q.disliked);
+  }
+
+  @Test
+  public void customLabelAnyWithBlock_NegativeVoteBlock() throws Exception {
+    Q.setFunctionName("AnyWithBlock");
+    saveLabelConfig();
+    PushOneCommit.Result r = createChange();
+    revision(r).review(new ReviewInput().label(Q.getName(), -1));
+    ChangeInfo c = get(r.getChangeId());
+    LabelInfo q = c.labels.get(Q.getName());
+    assertEquals(1, q.all.size());
+    assertNull(q.disliked);
+    assertNotNull(q.rejected);
+  }
+
+  @Test
+  public void customLabelMaxWithBlock_NegativeVoteBlock() throws Exception {
+    saveLabelConfig();
+    PushOneCommit.Result r = createChange();
+    revision(r).review(new ReviewInput().label(Q.getName(), -1));
+    ChangeInfo c = get(r.getChangeId());
+    LabelInfo q = c.labels.get(Q.getName());
+    assertEquals(1, q.all.size());
+    assertNull(q.disliked);
+    assertNotNull(q.rejected);
+  }
+
+  private void saveLabelConfig() throws Exception {
+    ProjectConfig cfg = projectCache.checkedGet(allProjects).getConfig();
+    cfg.getLabelSections().put(Q.getName(), Q);
+    saveProjectConfig(cfg);
+  }
+
+  private void saveProjectConfig(ProjectConfig cfg) throws Exception {
+    MetaDataUpdate md = metaDataUpdateFactory.create(allProjects);
+    try {
+      cfg.commit(md);
+    } finally {
+      md.close();
+    }
+    projectCache.evict(allProjects);
+  }
+}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/project/LabelTypeIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/project/LabelTypeIT.java
index e94d8f3..a2dd8ec 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/project/LabelTypeIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/project/LabelTypeIT.java
@@ -15,7 +15,6 @@
 package com.google.gerrit.acceptance.server.project;
 
 import static com.google.common.base.Preconditions.checkNotNull;
-import static com.google.gerrit.extensions.common.ListChangesOption.DETAILED_LABELS;
 import static org.junit.Assert.assertEquals;
 
 import com.google.gerrit.acceptance.AbstractDaemonTest;
@@ -23,7 +22,6 @@
 import com.google.gerrit.acceptance.PushOneCommit;
 import com.google.gerrit.common.data.LabelType;
 import com.google.gerrit.extensions.api.changes.ReviewInput;
-import com.google.gerrit.extensions.api.changes.RevisionApi;
 import com.google.gerrit.extensions.common.ChangeInfo;
 import com.google.gerrit.extensions.common.LabelInfo;
 import com.google.gerrit.server.config.AllProjectsName;
@@ -235,12 +233,6 @@
     }
   }
 
-  private RevisionApi revision(PushOneCommit.Result r) throws Exception {
-    return gApi.changes()
-        .id(r.getChangeId())
-        .current();
-  }
-
   private void merge(PushOneCommit.Result r) throws Exception {
     revision(r).review(ReviewInput.approve());
     revision(r).submit();
@@ -261,7 +253,7 @@
       throws Exception {
     // Don't use asserts from PushOneCommit so we can test the round-trip
     // through JSON instead of querying the DB directly.
-    ChangeInfo c = get(r.getChangeId(), DETAILED_LABELS);
+    ChangeInfo c = get(r.getChangeId());
     LabelInfo cr = c.labels.get("Code-Review");
     assertEquals(1, cr.all.size());
     assertEquals("Administrator", cr.all.get(0).name);
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectInfoScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectInfoScreen.java
index 94f8322..b3f0f65 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectInfoScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectInfoScreen.java
@@ -23,6 +23,7 @@
 import com.google.gerrit.client.download.DownloadPanel;
 import com.google.gerrit.client.projects.ConfigInfo;
 import com.google.gerrit.client.projects.ConfigInfo.ConfigParameterInfo;
+import com.google.gerrit.client.projects.ConfigInfo.ConfigParameterValue;
 import com.google.gerrit.client.projects.ConfigInfo.InheritedBooleanInfo;
 import com.google.gerrit.client.projects.ProjectApi;
 import com.google.gerrit.client.rpc.CallbackGroup;
@@ -366,17 +367,23 @@
       pluginConfig.copyKeysIntoChildren("name");
       for (ConfigParameterInfo param : Natives.asList(pluginConfig.values())) {
         FocusWidget w;
-        if ("STRING".equals(param.type())) {
-          w = renderTextBox(g, param, false);
-        } else if ("INT".equals(param.type()) || "LONG".equals(param.type())) {
-          w = renderTextBox(g, param, true);
-        } else if ("BOOLEAN".equals(param.type())) {
-          w = renderCheckBox(g, param);
-        } else if ("LIST".equals(param.type())
-            && param.permittedValues() != null) {
-          w = renderListBox(g, param);
-        } else {
-          continue;
+        switch (param.type()) {
+          case "STRING":
+          case "INT":
+          case "LONG":
+            w = renderTextBox(g, param);
+            break;
+          case "BOOLEAN":
+            w = renderCheckBox(g, param);
+            break;
+          case "LIST":
+            w = renderListBox(g, param);
+            break;
+          case "ARRAY":
+            w = renderTextArea(g, param);
+            break;
+          default:
+            throw new UnsupportedOperationException("unsupported widget type");
         }
         if (param.editable()) {
           widgetMap.put(param.name(), w);
@@ -390,8 +397,10 @@
   }
 
   private TextBox renderTextBox(LabeledWidgetsGrid g,
-      ConfigParameterInfo param, boolean numbersOnly) {
-    NpTextBox textBox = numbersOnly ? new NpIntTextBox() : new NpTextBox();
+      ConfigParameterInfo param) {
+    NpTextBox textBox = param.type().equals("STRING")
+        ? new NpTextBox()
+        : new NpIntTextBox();
     if (param.inheritable()) {
       textBox.setValue(param.configuredValue());
       Label inheritedLabel =
@@ -434,6 +443,9 @@
 
   private ListBox renderListBox(LabeledWidgetsGrid g,
       ConfigParameterInfo param) {
+    if (param.permittedValues() == null) {
+      return null;
+    }
     ListBox listBox = new ListBox();
     if (param.inheritable()) {
       listBox.addItem(
@@ -485,6 +497,26 @@
     return listBox;
   }
 
+  private NpTextArea renderTextArea(LabeledWidgetsGrid g,
+      ConfigParameterInfo param) {
+    NpTextArea txtArea = new NpTextArea();
+    txtArea.setVisibleLines(4);
+    txtArea.setCharacterWidth(40);
+    StringBuilder sb = new StringBuilder();
+    for (int i = 0; i < param.values().length(); i++) {
+      String v = param.values().get(i);
+      sb.append(v).append("\n");
+    }
+    txtArea.setText(sb.toString());
+    if (param.editable()) {
+      saveEnabler.listenTo(txtArea);
+    } else {
+      txtArea.setEnabled(false);
+    }
+    addWidget(g, txtArea, param);
+    return txtArea;
+  }
+
   private void addWidget(LabeledWidgetsGrid g, Widget w, ConfigParameterInfo param) {
     if (param.description() != null || param.warning() != null) {
       HorizontalPanel p = new HorizontalPanel();
@@ -554,26 +586,34 @@
         });
   }
 
-  private Map<String, Map<String, String>> getPluginConfigValues() {
-    Map<String, Map<String, String>> pluginConfigValues =
+  private Map<String, Map<String, ConfigParameterValue>> getPluginConfigValues() {
+    Map<String, Map<String, ConfigParameterValue>> pluginConfigValues =
         new HashMap<>(pluginConfigWidgets.size());
     for (Entry<String, Map<String, FocusWidget>> e : pluginConfigWidgets.entrySet()) {
-      Map<String, String> values =
-          new HashMap<String, String>(e.getValue().size());
+      Map<String, ConfigParameterValue> values = new HashMap<>(e.getValue().size());
       pluginConfigValues.put(e.getKey(), values);
       for (Entry<String, FocusWidget> e2 : e.getValue().entrySet()) {
         FocusWidget widget = e2.getValue();
         if (widget instanceof TextBox) {
-          values.put(e2.getKey(), ((TextBox) widget).getValue().trim());
+          values.put(e2.getKey(), ConfigParameterValue.create()
+              .value(((TextBox) widget).getValue().trim()));
         } else if (widget instanceof CheckBox) {
-          values.put(e2.getKey(), Boolean.toString(((CheckBox) widget).getValue()));
+          values.put(e2.getKey(), ConfigParameterValue.create()
+              .value(Boolean.toString(((CheckBox) widget).getValue())));
         } else if (widget instanceof ListBox) {
           ListBox listBox = (ListBox) widget;
           // the inherited value is at index 0,
           // if it is selected no value should be set on this project
           String value = listBox.getSelectedIndex() > 0
               ? listBox.getValue(listBox.getSelectedIndex()) : null;
-          values.put(e2.getKey(), value);
+          values.put(e2.getKey(), ConfigParameterValue.create()
+              .value(value));
+        } else if (widget instanceof NpTextArea) {
+          String text = ((NpTextArea) widget).getText().trim();
+          values.put(e2.getKey(), ConfigParameterValue.create()
+              .values(text.split("\n")));
+        } else {
+          throw new UnsupportedOperationException("unsupported widget type");
         }
       }
     }
@@ -591,11 +631,21 @@
         if (allowedCommands.contains(DownloadCommand.CHECKOUT)
             || allowedCommands.contains(DownloadCommand.DEFAULT_DOWNLOADS)) {
           commands.add(cmdLinkfactory.new CloneCommandLink());
+          if (Gerrit.getConfig().getSshdAddress() != null && hasUserName()) {
+            commands.add(
+                cmdLinkfactory.new CloneWithCommitMsgHookCommandLink(getProjectKey()));
+          }
         }
       }
     }
   }
 
+  private static boolean hasUserName() {
+    return Gerrit.isSignedIn()
+        && Gerrit.getUserAccount().getUserName() != null
+        && Gerrit.getUserAccount().getUserName().length() > 0;
+  }
+
   private class LabeledWidgetsGrid extends FlexTable {
     private String labelSuffix;
 
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/DownloadBox.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/DownloadBox.java
index 29be66b..6fc678b 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/DownloadBox.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/DownloadBox.java
@@ -40,9 +40,13 @@
 import com.google.gwt.user.client.ui.Widget;
 import com.google.gwtexpui.clippy.client.CopyableLabel;
 
+import java.util.ArrayList;
 import java.util.EnumSet;
+import java.util.Iterator;
+import java.util.List;
 
 class DownloadBox extends VerticalPanel {
+  private final static String ARCHIVE[] = {"tar", "tbz2", "tgz", "txz"};
   private final ChangeInfo change;
   private final String revision;
   private final PatchSet.Id psId;
@@ -109,6 +113,7 @@
       }
     }
     insertPatch();
+    insertArchive();
     insertCommand(null, scheme);
   }
 
@@ -141,6 +146,34 @@
     insertCommand("Patch-File", p);
   }
 
+  private void insertArchive() {
+    List<Anchor> formats = new ArrayList<>(ARCHIVE.length);
+    for (String f : ARCHIVE) {
+      Anchor archive = new Anchor(f);
+      archive.setHref(new RestApi("/changes/")
+          .id(psId.getParentKey().get())
+          .view("revisions")
+          .id(revision)
+          .view("archive")
+          .addParameter("format", f)
+          .url());
+      formats.add(archive);
+    }
+
+    HorizontalPanel p = new HorizontalPanel();
+    Iterator<Anchor> it = formats.iterator();
+    while (it.hasNext()) {
+      Anchor a = it.next();
+      p.add(a);
+      if (it.hasNext()) {
+        InlineLabel spacer = new InlineLabel("|");
+        spacer.setStyleName(Gerrit.RESOURCES.css().downloadBoxSpacer());
+        p.add(spacer);
+      }
+    }
+    insertCommand("Archive", p);
+  }
+
   private void insertCommand(String commandName, Widget w) {
     int row = commandTable.getRowCount();
     commandTable.insertRow(row);
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/PatchSetsBox.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/PatchSetsBox.java
index a9a8a93..2fa721e 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/PatchSetsBox.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/PatchSetsBox.java
@@ -205,8 +205,8 @@
       .append(FormatUtil.shortFormatDayTime(c.committer().date()))
       .closeTd();
 
-    String an = c.author() != null ? c.author().name() : null;
-    String cn = c.committer() != null ? c.committer().name() : null;
+    String an = c.author() != null ? c.author().name() : "";
+    String cn = c.committer() != null ? c.committer().name() : "";
     sb.openTd();
     sb.append(an);
     if (!"".equals(an) && !"".equals(cn) && !an.equals(cn)) {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/download/DownloadCommandLink.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/download/DownloadCommandLink.java
index 7a477fa..0c0f529 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/download/DownloadCommandLink.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/download/DownloadCommandLink.java
@@ -17,9 +17,11 @@
 import com.google.gerrit.client.Gerrit;
 import com.google.gerrit.reviewdb.client.AccountGeneralPreferences;
 import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.DownloadCommand;
+import com.google.gerrit.reviewdb.client.Project;
 import com.google.gwt.aria.client.Roles;
 import com.google.gwt.event.dom.client.ClickEvent;
 import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.user.client.Window;
 import com.google.gwt.user.client.ui.Anchor;
 import com.google.gwt.user.client.ui.Widget;
 import com.google.gwtexpui.clippy.client.CopyableLabel;
@@ -115,6 +117,55 @@
       }
     }
 
+    public class CloneWithCommitMsgHookCommandLink extends DownloadCommandLink {
+      private final Project.NameKey project;
+
+      public CloneWithCommitMsgHookCommandLink(Project.NameKey project) {
+        super(DownloadCommand.CHECKOUT, "clone with commmit-msg hook");
+        this.project = project;
+      }
+
+      @Override
+      protected void setCurrentUrl(DownloadUrlLink link) {
+        widget.setVisible(true);
+
+        String sshPort = "29418";
+        String sshAddr = Gerrit.getConfig().getSshdAddress();
+        int p = sshAddr.lastIndexOf(':');
+        if (p != -1 && !sshAddr.endsWith(":")) {
+          sshPort = sshAddr.substring(p + 1);
+        }
+
+        StringBuilder cmd = new StringBuilder();
+        cmd.append("git clone ");
+        cmd.append(link.getUrlData());
+        cmd.append(" && scp -p -P ");
+        cmd.append(sshPort);
+        cmd.append(" ");
+        cmd.append(Gerrit.getUserAccount().getUserName());
+        cmd.append("@");
+
+        if (sshAddr.startsWith("*:") || p == -1) {
+          cmd.append(Window.Location.getHostName());
+        } else {
+          cmd.append(sshAddr.substring(0, p));
+        }
+
+        cmd.append(":hooks/commit-msg ");
+
+        p = project.get().lastIndexOf('/');
+        if (p != -1) {
+          cmd.append(project.get().substring(p + 1));
+        } else {
+          cmd.append(project.get());
+        }
+
+        cmd.append("/.git/hooks/");
+
+        copyLabel.setText(cmd.toString());
+      }
+    }
+
     public CopyableCommandLinkFactory(CopyableLabel label, Widget widget) {
       copyLabel = label;
       this.widget = widget;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ConfigInfo.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ConfigInfo.java
index 09d2e0b..5c88655 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ConfigInfo.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ConfigInfo.java
@@ -158,8 +158,35 @@
     public final native String configuredValue() /*-{ return this.configured_value; }-*/;
     public final native String inheritedValue() /*-{ return this.inherited_value; }-*/;
     public final native JsArrayString permittedValues()  /*-{ return this.permitted_values; }-*/;
+    public final native JsArrayString values()  /*-{ return this.values; }-*/;
 
     protected ConfigParameterInfo() {
     }
   }
+
+  public static class ConfigParameterValue extends JavaScriptObject {
+    final native void init() /*-{ this.values = []; }-*/;
+    final native void add_value(String v) /*-{ this.values.push(v); }-*/;
+    final native void set_value(String v) /*-{ if(v)this.value = v; }-*/;
+    public static ConfigParameterValue create() {
+      ConfigParameterValue v = createObject().cast();
+      return v;
+    }
+
+    public final ConfigParameterValue values(String[] values) {
+      init();
+      for (String v : values) {
+        add_value(v);
+      }
+      return this;
+    }
+
+    public final ConfigParameterValue value(String v) {
+      set_value(v);
+      return this;
+    }
+
+    protected ConfigParameterValue() {
+    }
+  }
 }
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 c7d3bc2..63f7e15 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
@@ -14,6 +14,7 @@
 package com.google.gerrit.client.projects;
 
 import com.google.gerrit.client.VoidResult;
+import com.google.gerrit.client.projects.ConfigInfo.ConfigParameterValue;
 import com.google.gerrit.client.rpc.CallbackGroup;
 import com.google.gerrit.client.rpc.NativeMap;
 import com.google.gerrit.client.rpc.NativeString;
@@ -85,7 +86,7 @@
       InheritableBoolean useContentMerge, InheritableBoolean useSignedOffBy,
       InheritableBoolean requireChangeId, String maxObjectSizeLimit,
       SubmitType submitType, Project.State state,
-      Map<String, Map<String, String>> pluginConfigValues,
+      Map<String, Map<String, ConfigParameterValue>> pluginConfigValues,
       AsyncCallback<ConfigInfo> cb) {
     ConfigInput in = ConfigInput.create();
     in.setDescription(description);
@@ -222,32 +223,32 @@
     private final native void setStateRaw(String s)
     /*-{ if(s)this.state=s; }-*/;
 
-    final void setPluginConfigValues(Map<String, Map<String, String>> pluginConfigValues) {
+    final void setPluginConfigValues(Map<String, Map<String, ConfigParameterValue>> pluginConfigValues) {
       if (!pluginConfigValues.isEmpty()) {
-        NativeMap<StringMap> configValues = NativeMap.create().cast();
-        for (Entry<String, Map<String, String>> e : pluginConfigValues.entrySet()) {
-          StringMap values = StringMap.create();
+        NativeMap<ConfigParameterValueMap> configValues = NativeMap.create().cast();
+        for (Entry<String, Map<String, ConfigParameterValue>> e : pluginConfigValues.entrySet()) {
+          ConfigParameterValueMap values = ConfigParameterValueMap.create();
           configValues.put(e.getKey(), values);
-          for (Entry<String, String> e2 : e.getValue().entrySet()) {
+          for (Entry<String, ConfigParameterValue> e2 : e.getValue().entrySet()) {
             values.put(e2.getKey(), e2.getValue());
           }
         }
         setPluginConfigValuesRaw(configValues);
       }
     }
-    private final native void setPluginConfigValuesRaw(NativeMap<StringMap> v)
+    private final native void setPluginConfigValuesRaw(NativeMap<ConfigParameterValueMap> v)
     /*-{ this.plugin_config_values=v; }-*/;
   }
 
-  private static class StringMap extends JavaScriptObject {
-    static StringMap create() {
-      return (StringMap) createObject();
+  private static class ConfigParameterValueMap extends JavaScriptObject {
+    static ConfigParameterValueMap create() {
+      return createObject().cast();
     }
 
-    protected StringMap() {
+    protected ConfigParameterValueMap() {
     }
 
-    public final native void put(String n, String v) /*-{ this[n] = v; }-*/;
+    public final native void put(String n, ConfigParameterValue v) /*-{ this[n] = v; }-*/;
   }
 
   private static class BranchInput extends JavaScriptObject {
diff --git a/gerrit-gwtui/src/main/java/net/codemirror/lib/CodeMirror.java b/gerrit-gwtui/src/main/java/net/codemirror/lib/CodeMirror.java
index c0247c6..0e5f9a5 100644
--- a/gerrit-gwtui/src/main/java/net/codemirror/lib/CodeMirror.java
+++ b/gerrit-gwtui/src/main/java/net/codemirror/lib/CodeMirror.java
@@ -180,28 +180,34 @@
     }));
   }-*/;
 
-  /** TODO: Break this line after updating GWT */
   public final native void on(String event, EventHandler handler) /*-{
     this.on(event, $entry(function(cm, e) {
-      handler.@net.codemirror.lib.CodeMirror.EventHandler::handle(Lnet/codemirror/lib/CodeMirror;Lcom/google/gwt/dom/client/NativeEvent;)(cm, e);
+      handler.@net.codemirror.lib.CodeMirror.EventHandler::handle(
+        Lnet/codemirror/lib/CodeMirror;Lcom/google/gwt/dom/client/NativeEvent;)(cm, e);
     }));
   }-*/;
 
   public final native void on(String event, RenderLineHandler handler) /*-{
     this.on(event, $entry(function(cm, h, ele) {
-      handler.@net.codemirror.lib.CodeMirror.RenderLineHandler::handle(Lnet/codemirror/lib/CodeMirror;Lnet/codemirror/lib/CodeMirror$LineHandle;Lcom/google/gwt/dom/client/Element;)(cm, h, ele);
+      handler.@net.codemirror.lib.CodeMirror.RenderLineHandler::handle(
+        Lnet/codemirror/lib/CodeMirror;Lnet/codemirror/lib/CodeMirror$LineHandle;
+        Lcom/google/gwt/dom/client/Element;)(cm, h, ele);
     }));
   }-*/;
 
   public final native void on(String event, GutterClickHandler handler) /*-{
     this.on(event, $entry(function(cm, l, g, e) {
-      handler.@net.codemirror.lib.CodeMirror.GutterClickHandler::handle(Lnet/codemirror/lib/CodeMirror;ILjava/lang/String;Lcom/google/gwt/dom/client/NativeEvent;)(cm, l, g, e);
+      handler.@net.codemirror.lib.CodeMirror.GutterClickHandler::handle(
+        Lnet/codemirror/lib/CodeMirror;ILjava/lang/String;
+        Lcom/google/gwt/dom/client/NativeEvent;)(cm, l, g, e);
     }));
   }-*/;
 
   public final native void on(String event, BeforeSelectionChangeHandler handler) /*-{
     this.on(event, $entry(function(cm, e) {
-      handler.@net.codemirror.lib.CodeMirror.BeforeSelectionChangeHandler::handle(Lnet/codemirror/lib/CodeMirror;Lnet/codemirror/lib/LineCharacter;Lnet/codemirror/lib/LineCharacter;)(cm,e.anchor,e.head);
+      handler.@net.codemirror.lib.CodeMirror.BeforeSelectionChangeHandler::handle(
+        Lnet/codemirror/lib/CodeMirror;Lnet/codemirror/lib/LineCharacter;
+        Lnet/codemirror/lib/LineCharacter;)(cm,e.anchor,e.head);
     }));
   }-*/;
 
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/ProtoGen.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/ProtoGen.java
index 4512078..12e1e99 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/ProtoGen.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/ProtoGen.java
@@ -26,6 +26,7 @@
 import java.io.BufferedWriter;
 import java.io.File;
 import java.io.InputStream;
+import java.io.OutputStream;
 import java.io.OutputStreamWriter;
 import java.io.PrintWriter;
 import java.nio.ByteBuffer;
@@ -42,9 +43,9 @@
     }
     try {
       JavaSchemaModel jsm = new JavaSchemaModel(ReviewDb.class);
-      PrintWriter out = new PrintWriter(new BufferedWriter(
-          new OutputStreamWriter(lock.getOutputStream(), "UTF-8")));
-      try {
+      try (OutputStream o = lock.getOutputStream();
+          PrintWriter out = new PrintWriter(
+              new BufferedWriter(new OutputStreamWriter(o, "UTF-8")))) {
         String header;
         InputStream in = getClass().getResourceAsStream("ProtoGenHeader.txt");
         try {
@@ -60,8 +61,6 @@
         out.write(header.replace("@@VERSION@@", version));
         jsm.generateProto(out);
         out.flush();
-      } finally {
-        out.close();
       }
       if (!lock.commit()) {
         throw die("Could not write to " + file);
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/HttpLogLayout.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/HttpLogLayout.java
index 25c6d09..bab4de7 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/HttpLogLayout.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/HttpLogLayout.java
@@ -26,7 +26,7 @@
   private long lastTimeMillis;
   private String lastTimeString;
 
-  HttpLogLayout() {
+  public HttpLogLayout() {
     final TimeZone tz = TimeZone.getDefault();
     dateFormat = new SimpleDateFormat("dd/MMM/yyyy:HH:mm:ss Z");
     dateFormat.setTimeZone(tz);
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitPluginStepsLoader.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitPluginStepsLoader.java
index 85a8ee3..5584cf3 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitPluginStepsLoader.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitPluginStepsLoader.java
@@ -68,24 +68,25 @@
   @SuppressWarnings("resource")
   private InitStep loadInitStep(File jar) {
     try {
-      ClassLoader pluginLoader =
+      URLClassLoader pluginLoader =
           new URLClassLoader(new URL[] {jar.toURI().toURL()},
-              InitPluginStepsLoader.class.getClassLoader());
-      JarFile jarFile = new JarFile(jar);
-      Attributes jarFileAttributes = jarFile.getManifest().getMainAttributes();
-      String initClassName = jarFileAttributes.getValue("Gerrit-InitStep");
-      if (initClassName == null) {
+             InitPluginStepsLoader.class.getClassLoader());
+      try (JarFile jarFile = new JarFile(jar)) {
+        Attributes jarFileAttributes = jarFile.getManifest().getMainAttributes();
+        String initClassName = jarFileAttributes.getValue("Gerrit-InitStep");
+        if (initClassName == null) {
+          return null;
+        }
+        @SuppressWarnings("unchecked")
+        Class<? extends InitStep> initStepClass =
+            (Class<? extends InitStep>) pluginLoader.loadClass(initClassName);
+        return getPluginInjector(jar).getInstance(initStepClass);
+      } catch (ClassCastException e) {
+        ui.message(
+            "WARN: InitStep from plugin %s does not implement %s (Exception: %s)",
+            jar.getName(), InitStep.class.getName(), e.getMessage());
         return null;
       }
-      @SuppressWarnings("unchecked")
-      Class<? extends InitStep> initStepClass =
-          (Class<? extends InitStep>) pluginLoader.loadClass(initClassName);
-      return getPluginInjector(jar).getInstance(initStepClass);
-    } catch (ClassCastException e) {
-      ui.message(
-          "WARN: InitStep from plugin %s does not implement %s (Exception: %s)",
-          jar.getName(), InitStep.class.getName(), e.getMessage());
-      return null;
     } catch (Exception e) {
       ui.message(
           "WARN: Cannot load and get plugin init step for %s (Exception: %s)",
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitUtil.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitUtil.java
index 0ad7560..dde0b06 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitUtil.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitUtil.java
@@ -162,10 +162,11 @@
 
   static void extract(final File dst, final Class<?> sibling,
       final String name) throws IOException {
-    final InputStream in = open(sibling, name);
-    if (in != null) {
-      ByteBuffer buf = IO.readWholeStream(in, 8192);
-      copy(dst, buf);
+    try (InputStream in = open(sibling, name)) {
+      if (in != null) {
+        ByteBuffer buf = IO.readWholeStream(in, 8192);
+        copy(dst, buf);
+      }
     }
   }
 
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/Libraries.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/Libraries.java
index f4a673c..7209990 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/Libraries.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/Libraries.java
@@ -106,21 +106,19 @@
   }
 
   private static String read(final String p) throws IOException {
-    InputStream in = Libraries.class.getClassLoader().getResourceAsStream(p);
-    if (in == null) {
-      throw new FileNotFoundException("Cannot load resource " + p);
-    }
-    final Reader r = new InputStreamReader(in, "UTF-8");
-    try {
-      final StringBuilder buf = new StringBuilder();
-      final char[] tmp = new char[512];
-      int n;
-      while (0 < (n = r.read(tmp))) {
-        buf.append(tmp, 0, n);
+    try (InputStream in = Libraries.class.getClassLoader().getResourceAsStream(p)) {
+      if (in == null) {
+        throw new FileNotFoundException("Cannot load resource " + p);
       }
-      return buf.toString();
-    } finally {
-      r.close();
+      try (Reader r = new InputStreamReader(in, "UTF-8")) {
+        final StringBuilder buf = new StringBuilder();
+        final char[] tmp = new char[512];
+        int n;
+        while (0 < (n = r.read(tmp))) {
+          buf.append(tmp, 0, n);
+        }
+        return buf.toString();
+      }
     }
   }
 }
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/shell/JythonShell.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/shell/JythonShell.java
index dfe28b7..38f08c1 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/shell/JythonShell.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/shell/JythonShell.java
@@ -20,6 +20,7 @@
 import org.slf4j.LoggerFactory;
 
 import java.io.File;
+import java.io.IOException;
 import java.io.InputStream;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
@@ -165,11 +166,14 @@
   }
 
   protected void execResource(final String p) {
-    InputStream in = JythonShell.class.getClassLoader().getResourceAsStream(p);
-    if (in != null) {
-      execStream(in, "resource " + p);
-    } else {
-      log.error("Cannot load resource " + p);
+    try (InputStream in = JythonShell.class.getClassLoader().getResourceAsStream(p)) {
+      if (in != null) {
+        execStream(in, "resource " + p);
+      } else {
+        log.error("Cannot load resource " + p);
+      }
+    } catch (IOException e) {
+      log.error(e.getMessage(), e);
     }
   }
 
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/IoUtil.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/IoUtil.java
index 1c3d1e3..d9a64fc 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/IoUtil.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/IoUtil.java
@@ -58,16 +58,14 @@
       throw noAddURL("Not loaded by URLClassLoader", null);
     }
 
-    @SuppressWarnings("resource")
+    @SuppressWarnings("resource") // Leave open so classes can be loaded.
     URLClassLoader urlClassLoader = (URLClassLoader) cl;
 
     Method addURL;
     try {
       addURL = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
       addURL.setAccessible(true);
-    } catch (SecurityException e) {
-      throw noAddURL("Method addURL not available", e);
-    } catch (NoSuchMethodException e) {
+    } catch (SecurityException | NoSuchMethodException e) {
       throw noAddURL("Method addURL not available", e);
     }
 
@@ -78,11 +76,8 @@
         if (have.add(url)) {
           addURL.invoke(cl, url);
         }
-      } catch (MalformedURLException e) {
-        throw noAddURL("addURL " + path + " failed", e);
-      } catch (IllegalArgumentException e) {
-        throw noAddURL("addURL " + path + " failed", e);
-      } catch (IllegalAccessException e) {
+      } catch (MalformedURLException | IllegalArgumentException |
+          IllegalAccessException e) {
         throw noAddURL("addURL " + path + " failed", e);
       } catch (InvocationTargetException e) {
         throw noAddURL("addURL " + path + " failed", e.getCause());
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/LogFileCompressor.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/LogFileCompressor.java
index b5af54e..aed1b9a 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/LogFileCompressor.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/LogFileCompressor.java
@@ -118,21 +118,15 @@
     final File dst = new File(dir, src.getName() + ".gz");
     final File tmp = new File(dir, ".tmp." + src.getName());
     try {
-      final InputStream in = new FileInputStream(src);
-      try {
-        OutputStream out = new GZIPOutputStream(new FileOutputStream(tmp));
-        try {
-          final byte[] buf = new byte[2048];
-          int n;
-          while (0 < (n = in.read(buf))) {
-            out.write(buf, 0, n);
-          }
-        } finally {
-          out.close();
+      try (InputStream in = new FileInputStream(src);
+          FileOutputStream fo = new FileOutputStream(tmp);
+          OutputStream out = new GZIPOutputStream(fo)) {
+        final byte[] buf = new byte[2048];
+        int n;
+        while (0 < (n = in.read(buf))) {
+          out.write(buf, 0, n);
         }
         tmp.setReadOnly();
-      } finally {
-        in.close();
       }
       if (!tmp.renameTo(dst)) {
         throw new IOException("Cannot rename " + tmp + " to " + dst);
diff --git a/gerrit-prettify/src/main/java/com/google/gerrit/prettify/client/PrettyFormatter.java b/gerrit-prettify/src/main/java/com/google/gerrit/prettify/client/PrettyFormatter.java
index df3cd0e..d4aef2a 100644
--- a/gerrit-prettify/src/main/java/com/google/gerrit/prettify/client/PrettyFormatter.java
+++ b/gerrit-prettify/src/main/java/com/google/gerrit/prettify/client/PrettyFormatter.java
@@ -408,7 +408,7 @@
 
         String line = src.get(index) + "\n";
         for (int c = 0; c < line.length();) {
-          if (charEdits.size() <= lastIdx) {
+          if (charEdits == null || (charEdits.size() <= lastIdx)) {
             appendShowBareCR(buf, line.substring(c), false);
             break;
           }
diff --git a/gerrit-server/BUCK b/gerrit-server/BUCK
index 103092f..12dd162 100644
--- a/gerrit-server/BUCK
+++ b/gerrit-server/BUCK
@@ -57,6 +57,7 @@
     '//lib/guice:guice-assistedinject',
     '//lib/guice:guice-servlet',
     '//lib/jgit:jgit',
+    '//lib/jgit:jgit-archive',
     '//lib/joda:joda-time',
     '//lib/log:api',
     '//lib/prolog:prolog-cafe',
@@ -141,12 +142,13 @@
   ],
 )
 
+QUERY_TESTS = glob(
+  ['src/test/java/com/google/gerrit/server/query/**/*.java'],
+)
+
 java_test(
-  name = 'server_tests',
-  srcs = glob(
-    ['src/test/java/**/*.java'],
-    excludes = TESTUTIL + PROLOG_TESTS + PROLOG_TEST_CASE
-  ),
+  name = 'query_tests',
+  srcs = QUERY_TESTS,
   deps = [
     ':server',
     ':testutil',
@@ -157,8 +159,6 @@
     '//gerrit-extension-api:api',
     '//gerrit-reviewdb:server',
     '//gerrit-server/src/main/prolog:common',
-    '//lib:args4j',
-    '//lib:easymock',
     '//lib:guava',
     '//lib:gwtorm',
     '//lib:junit',
@@ -167,8 +167,35 @@
     '//lib/jgit:jgit',
     '//lib/jgit:junit',
     '//lib/joda:joda-time',
+  ],
+  source_under_test = [':server'],
+)
+
+java_test(
+  name = 'server_tests',
+  srcs = glob(
+    ['src/test/java/**/*.java'],
+    excludes = TESTUTIL + PROLOG_TESTS + PROLOG_TEST_CASE + QUERY_TESTS
+  ),
+  deps = [
+    ':server',
+    ':testutil',
+    '//gerrit-antlr:query_exception',
+    '//gerrit-common:annotations',
+    '//gerrit-common:server',
+    '//gerrit-extension-api:api',
+    '//gerrit-reviewdb:server',
+    '//gerrit-server/src/main/prolog:common',
+    '//lib:args4j',
+    '//lib:easymock',
+    '//lib:guava',
+    '//lib:gwtorm',
+    '//lib:junit',
+    '//lib/guice:guice',
+    '//lib/jgit:jgit',
+    '//lib/jgit:junit',
+    '//lib/joda:joda-time',
     '//lib/prolog:prolog-cafe',
   ],
   source_under_test = [':server'],
-  vm_args = ['-Duser.timezone=US/Eastern'],
 )
diff --git a/gerrit-server/src/main/java/com/google/gerrit/common/Version.java b/gerrit-server/src/main/java/com/google/gerrit/common/Version.java
index 8ad4d48..e69360a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/common/Version.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/common/Version.java
@@ -14,12 +14,16 @@
 
 package com.google.gerrit.common;
 
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
 import java.io.BufferedReader;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
 
 public class Version {
+  private static final Logger log = LoggerFactory.getLogger(Version.class);
   private static final String version;
 
   public static String getVersion() {
@@ -31,13 +35,11 @@
   }
 
   private static String loadVersion() {
-    InputStream in = Version.class.getResourceAsStream("Version");
-    if (in == null) {
-      return null;
-    }
-    try {
-      BufferedReader r = new BufferedReader(new InputStreamReader(in, "UTF-8"));
-      try {
+    try (InputStream in = Version.class.getResourceAsStream("Version")) {
+      if (in == null) {
+        return null;
+      }
+      try (BufferedReader r = new BufferedReader(new InputStreamReader(in, "UTF-8"))) {
         String vs = r.readLine();
         if (vs != null && vs.startsWith("v")) {
           vs = vs.substring(1);
@@ -46,10 +48,9 @@
           vs = null;
         }
         return vs;
-      } finally {
-        r.close();
       }
     } catch (IOException e) {
+      log.error(e.getMessage(), e);
       return null;
     }
   }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/rules/PrologCompiler.java b/gerrit-server/src/main/java/com/google/gerrit/rules/PrologCompiler.java
index ab57059..8202ac2 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/rules/PrologCompiler.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/rules/PrologCompiler.java
@@ -235,10 +235,9 @@
       mf.getMainAttributes().putValue("Source-Commit", metaConfig.name());
       mf.getMainAttributes().putValue("Source-Blob", rulesId.name());
 
-      FileOutputStream stream = new FileOutputStream(tmpjar);
-      JarOutputStream out = new JarOutputStream(stream, mf);
-      byte buffer[] = new byte[10240];
-      try {
+      try (FileOutputStream stream = new FileOutputStream(tmpjar);
+          JarOutputStream out = new JarOutputStream(stream, mf)) {
+        byte buffer[] = new byte[10240];
         for (String path : toBeJared) {
           JarEntry jarAdd = new JarEntry(path);
           File f = new File(tempDir, path);
@@ -260,8 +259,6 @@
           }
           out.closeEntry();
         }
-      } finally {
-        out.close();
       }
 
       if (!tmpjar.renameTo(archiveFile)) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/DefaultFileExtensionRegistry.java b/gerrit-server/src/main/java/com/google/gerrit/server/DefaultFileExtensionRegistry.java
index bda15d0..0416ba4 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/DefaultFileExtensionRegistry.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/DefaultFileExtensionRegistry.java
@@ -37,6 +37,7 @@
       .put(".gitmodules", INI)
       .put("project.config", INI)
       .put("BUCK", PYTHON)
+      .put("bucklet", newMimeType(PYTHON.toString(), 1))
       .put("defs", newMimeType(PYTHON.toString(), 1))
       .put("py", newMimeType(PYTHON.toString(), 1))
       .put("go", newMimeType("text/x-go", 1))
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ArchiveFormat.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ArchiveFormat.java
new file mode 100644
index 0000000..e7d05df
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ArchiveFormat.java
@@ -0,0 +1,84 @@
+// Copyright 2013 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.change;
+
+import com.google.common.collect.Maps;
+
+import org.eclipse.jgit.api.ArchiveCommand;
+import org.eclipse.jgit.archive.TarFormat;
+import org.eclipse.jgit.archive.Tbz2Format;
+import org.eclipse.jgit.archive.TgzFormat;
+import org.eclipse.jgit.archive.TxzFormat;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Collections;
+import java.util.Map;
+
+enum ArchiveFormat {
+  TGZ("application/x-gzip", new TgzFormat()),
+  TAR("application/x-tar", new TarFormat()),
+  TBZ2("application/x-bzip2", new Tbz2Format()),
+  TXZ("application/x-xz", new TxzFormat());
+  // Zip is not supported because it may be interpreted by a Java plugin as a
+  // valid JAR file, whose code would have access to cookies on the domain.
+
+  static final Logger log = LoggerFactory.getLogger(ArchiveFormat.class);
+
+  private final ArchiveCommand.Format<?> format;
+  private final String mimeType;
+
+  private ArchiveFormat(String mimeType, ArchiveCommand.Format<?> format) {
+    this.format = format;
+    this.mimeType = mimeType;
+    ArchiveCommand.registerFormat(name(), format);
+  }
+
+  String getShortName() {
+    return name().toLowerCase();
+  }
+
+  String getMimeType() {
+    return mimeType;
+  }
+
+  String getDefaultSuffix() {
+    return getSuffixes().iterator().next();
+  }
+
+  Iterable<String> getSuffixes() {
+    return format.suffixes();
+  }
+
+  static Map<String, ArchiveFormat> init() {
+    String[] formats = new String[values().length];
+    for (int i = 0; i < values().length; i++) {
+      formats[i] = values()[i].name();
+    }
+
+    Map<String, ArchiveFormat> exts = Maps.newLinkedHashMap();
+    for (String name : formats) {
+      try {
+        ArchiveFormat format = valueOf(name.toUpperCase());
+        for (String ext : format.getSuffixes()) {
+          exts.put(ext, format);
+        }
+      } catch (IllegalArgumentException e) {
+        log.warn("Invalid archive.format {}", name);
+      }
+    }
+    return Collections.unmodifiableMap(exts);
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeJson.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeJson.java
index 6d2dc58..cb852a8 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeJson.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeJson.java
@@ -482,18 +482,12 @@
       return;
     }
 
-    if (score != 0) {
-      if (score == type.getMin().getValue()) {
-        label.rejected = accountLoader.get(accountId);
-      } else if (score == type.getMax().getValue()) {
-        label.approved = accountLoader.get(accountId);
-      } else if (score < 0) {
-        label.disliked = accountLoader.get(accountId);
-        label.value = score;
-      } else if (score > 0 && label.disliked == null) {
-        label.recommended = accountLoader.get(accountId);
-        label.value = score;
-      }
+    if (score < 0) {
+      label.disliked = accountLoader.get(accountId);
+      label.value = score;
+    } else if (score > 0 && label.disliked == null) {
+      label.recommended = accountLoader.get(accountId);
+      label.value = score;
     }
   }
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetArchive.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetArchive.java
new file mode 100644
index 0000000..9a4ab21
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetArchive.java
@@ -0,0 +1,111 @@
+// Copyright (C) 2014 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.change;
+
+import com.google.common.base.Strings;
+import com.google.gerrit.extensions.restapi.BadRequestException;
+import com.google.gerrit.extensions.restapi.BinaryResult;
+import com.google.gerrit.extensions.restapi.RestReadView;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.inject.Inject;
+
+import org.eclipse.jgit.api.ArchiveCommand;
+import org.eclipse.jgit.api.errors.GitAPIException;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.kohsuke.args4j.Option;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.Map;
+
+class GetArchive implements RestReadView<RevisionResource> {
+  private static final Map<String, ArchiveFormat> formats = ArchiveFormat.init();
+  private final GitRepositoryManager repoManager;
+
+  @Option(name = "--format")
+  private String format;
+
+  @Inject
+  GetArchive(GitRepositoryManager repoManager) {
+    this.repoManager = repoManager;
+  }
+
+  @Override
+  public BinaryResult apply(RevisionResource rsrc)
+      throws BadRequestException, IOException {
+    if (Strings.isNullOrEmpty(format)) {
+      throw new BadRequestException("format is not specified");
+    }
+    final ArchiveFormat f = formats.get("." + format);
+    if (f == null) {
+      throw new BadRequestException("unknown archive format");
+    }
+    boolean close = true;
+    final Repository repo = repoManager
+        .openRepository(rsrc.getControl().getProject().getNameKey());
+    try {
+      final RevWalk rw = new RevWalk(repo);
+      try {
+        final RevCommit commit =
+            rw.parseCommit(ObjectId.fromString(rsrc.getPatchSet()
+                .getRevision().get()));
+        BinaryResult bin = new BinaryResult() {
+          @Override
+          public void writeTo(OutputStream out) throws IOException {
+            try {
+              new ArchiveCommand(repo)
+                  .setFormat(f.name())
+                  .setTree(commit.getTree())
+                  .setOutputStream(out).call();
+            } catch (GitAPIException e) {
+              throw new IOException(e);
+            }
+          }
+
+          @Override
+          public void close() throws IOException {
+            rw.release();
+            repo.close();
+          }
+        };
+
+        bin.disableGzip()
+            .setContentType(f.getMimeType())
+            .setAttachmentName(name(f, rw, commit));
+
+        close = false;
+        return bin;
+      } finally {
+        if (close) {
+          rw.release();
+        }
+      }
+    } finally {
+      if (close) {
+        repo.close();
+      }
+    }
+  }
+
+  private static String name(ArchiveFormat format, RevWalk rw, RevCommit commit)
+      throws IOException {
+    return String.format("%s%s",
+        rw.getObjectReader().abbreviate(commit,7).name(),
+        format.getDefaultSuffix());
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Module.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Module.java
index 9ed2bee..6880ca2 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Module.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Module.java
@@ -82,6 +82,7 @@
     get(REVISION_KIND, "submit_type").to(TestSubmitType.Get.class);
     post(REVISION_KIND, "test.submit_rule").to(TestSubmitRule.class);
     post(REVISION_KIND, "test.submit_type").to(TestSubmitType.class);
+    get(REVISION_KIND, "archive").to(GetArchive.class);
 
     child(REVISION_KIND, "drafts").to(Drafts.class);
     put(REVISION_KIND, "drafts").to(CreateDraft.class);
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 dd5101d..b581c67 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
@@ -104,6 +104,7 @@
   private boolean draft;
   private boolean runHooks;
   private boolean sendMail;
+  private Account.Id uploader;
 
   @Inject
   public PatchSetInserter(ChangeHooks hooks,
@@ -205,6 +206,11 @@
     return this;
   }
 
+  public PatchSetInserter setUploader(Account.Id uploader) {
+    this.uploader = uploader;
+    return this;
+  }
+
   public Change insert() throws InvalidChangeOperationException, OrmException,
       IOException {
     init();
@@ -321,6 +327,9 @@
       patchSet.setRevision(new RevId(commit.name()));
     }
     patchSet.setDraft(draft);
+    if (uploader != null) {
+      patchSet.setUploader(uploader);
+    }
   }
 
   private void validate() throws InvalidChangeOperationException {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/changedetail/RebaseChange.java b/gerrit-server/src/main/java/com/google/gerrit/server/changedetail/RebaseChange.java
index b0fe0b5..8de6904 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/changedetail/RebaseChange.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/changedetail/RebaseChange.java
@@ -299,6 +299,7 @@
         .setCopyLabels(true)
         .setValidatePolicy(validate)
         .setDraft(originalPatchSet.isDraft())
+        .setUploader(uploader.getAccountId())
         .setSendMail(sendMail)
         .setRunHooks(runHooks);
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/ProjectConfigEntry.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/ProjectConfigEntry.java
index 3bb6389..96fba81 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/ProjectConfigEntry.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/ProjectConfigEntry.java
@@ -26,6 +26,7 @@
 import com.google.gerrit.server.git.ProjectConfig;
 import com.google.gerrit.server.project.ProjectState;
 import com.google.inject.Inject;
+import com.google.inject.ProvisionException;
 
 import org.eclipse.jgit.errors.ConfigInvalidException;
 import org.eclipse.jgit.errors.RepositoryNotFoundException;
@@ -40,7 +41,7 @@
 @ExtensionPoint
 public class ProjectConfigEntry {
   public enum Type {
-    STRING, INT, LONG, BOOLEAN, LIST
+    STRING, INT, LONG, BOOLEAN, LIST, ARRAY
   }
 
   private final String displayName;
@@ -145,7 +146,7 @@
         }), inheritable, description);
   }
 
-  private ProjectConfigEntry(String displayName, String defaultValue,
+  public ProjectConfigEntry(String displayName, String defaultValue,
       Type type, List<String> permittedValues, boolean inheritable,
       String description) {
     this.displayName = displayName;
@@ -154,6 +155,10 @@
     this.permittedValues = permittedValues;
     this.inheritable = inheritable;
     this.description = description;
+    if (type == Type.ARRAY && inheritable) {
+      throw new ProvisionException(
+          "ARRAY doesn't support inheritable values");
+    }
   }
 
   public String getDisplayName() {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/contact/EncryptedContactStore.java b/gerrit-server/src/main/java/com/google/gerrit/server/contact/EncryptedContactStore.java
index 40699c8..8c1fdb6 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/contact/EncryptedContactStore.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/contact/EncryptedContactStore.java
@@ -108,16 +108,10 @@
     return true;
   }
 
-  @SuppressWarnings("resource")
   private static PGPPublicKeyRingCollection readPubRing(final File pub) {
-    try {
-      InputStream in = new FileInputStream(pub);
-      try {
-        in = PGPUtil.getDecoderStream(in);
+    try (InputStream fin = new FileInputStream(pub);
+        InputStream in = PGPUtil.getDecoderStream(fin)) {
         return new PGPPublicKeyRingCollection(in);
-      } finally {
-        in.close();
-      }
     } catch (IOException e) {
       throw new ProvisionException("Cannot read " + pub, e);
     } catch (PGPException e) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/contact/HttpContactStoreConnection.java b/gerrit-server/src/main/java/com/google/gerrit/server/contact/HttpContactStoreConnection.java
index 123c20d..471f6a2 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/contact/HttpContactStoreConnection.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/contact/HttpContactStoreConnection.java
@@ -48,9 +48,9 @@
         "application/x-www-form-urlencoded; charset=UTF-8");
     conn.setDoOutput(true);
     conn.setFixedLengthStreamingMode(body.length);
-    final OutputStream out = conn.getOutputStream();
-    out.write(body);
-    out.close();
+    try (OutputStream out = conn.getOutputStream()) {
+      out.write(body);
+    }
     if (conn.getResponseCode() != HttpURLConnection.HTTP_OK) {
       throw new IOException("Connection failed: " + conn.getResponseCode());
     }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java
index ed4f9ab..847eaee 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java
@@ -27,6 +27,7 @@
 import static org.eclipse.jgit.transport.ReceiveCommand.Result.REJECTED_OTHER_REASON;
 
 import com.google.common.base.Function;
+import com.google.common.base.Joiner;
 import com.google.common.base.Predicate;
 import com.google.common.base.Splitter;
 import com.google.common.base.Strings;
@@ -147,6 +148,7 @@
 import java.io.IOException;
 import java.io.StringWriter;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
@@ -891,6 +893,13 @@
                     projectControl.getProjectState().getConfig()
                         .getPluginConfig(e.getPluginName())
                         .getString(e.getExportName());
+                if (configEntry.getType() == ProjectConfigEntry.Type.ARRAY) {
+                  List<String> l =
+                      Arrays.asList(projectControl.getProjectState()
+                          .getConfig().getPluginConfig(e.getPluginName())
+                          .getStringList(e.getExportName()));
+                  oldValue = Joiner.on("\n").join(l);
+                }
 
                 if ((value == null ? oldValue != null : !value.equals(oldValue)) &&
                     !configEntry.isEditable(projectControl.getProjectState())) {
@@ -945,7 +954,7 @@
     }
 
     RefControl ctl = projectControl.controlForRef(cmd.getRefName());
-    if (ctl.canCreate(rp.getRevWalk(), obj)) {
+    if (ctl.canCreate(rp.getRevWalk(), obj, allRefs.values().contains(obj))) {
       validateNewCommits(ctl, cmd);
       batch.addCommand(cmd);
     } else {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/RebaseIfNecessary.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/RebaseIfNecessary.java
index 7a6ce7a..a97b3fd 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/RebaseIfNecessary.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/RebaseIfNecessary.java
@@ -99,8 +99,9 @@
                 args.db, n.notes(), n.getPatchsetId())) {
               approvals.add(new PatchSetApproval(newPatchSet.getId(), a));
             }
-            args.db.patchSetApprovals().insert(approvals);
-
+            // rebaseChange.rebase() may already have copied some approvals,
+            // use upsert, not insert, to avoid constraint violation on database
+            args.db.patchSetApprovals().upsert(approvals);
             newMergeTip =
                 (CodeReviewCommit) args.rw.parseCommit(ObjectId
                     .fromString(newPatchSet.getRevision().get()));
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/TimestampRangePredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/TimestampRangePredicate.java
index 0ba20e2..9277f6b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/TimestampRangePredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/TimestampRangePredicate.java
@@ -20,14 +20,10 @@
 
 import com.google.gerrit.server.query.QueryParseException;
 import com.google.gerrit.server.query.change.ChangeData;
-
-import org.eclipse.jgit.util.GitDateParser;
-import org.joda.time.DateTime;
+import com.google.gwtjsonrpc.common.JavaSqlTimestampHelper;
 
 import java.sql.Timestamp;
-import java.text.ParseException;
 import java.util.Date;
-import java.util.Locale;
 
 public abstract class TimestampRangePredicate<I> extends IndexPredicate<I> {
   @SuppressWarnings({"deprecation", "unchecked"})
@@ -46,11 +42,11 @@
     return (FieldDef<ChangeData, Timestamp>) f;
   }
 
-  protected static Date parse(String value) throws QueryParseException {
+  protected static Timestamp parse(String value) throws QueryParseException {
     try {
-      return GitDateParser.parse(value, DateTime.now().toCalendar(Locale.US));
-    } catch (ParseException e) {
-      // ParseException's message is specific and helpful, so preserve it.
+      return JavaSqlTimestampHelper.parseTimestamp(value);
+    } catch (IllegalArgumentException e) {
+      // parseTimestamp's errors are specific and helpful, so preserve them.
       throw new QueryParseException(e.getMessage(), e);
     }
   }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/ListPlugins.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/ListPlugins.java
index 3264a67..a2348f3 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/ListPlugins.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/ListPlugins.java
@@ -82,6 +82,9 @@
       } catch (UnsupportedEncodingException e) {
         throw new RuntimeException("JVM lacks UTF-8 encoding", e);
       }
+    } else if (!format.isJson()) {
+      throw new IllegalStateException(
+          "Text output requires that a display OutputStream is provided.");
     }
 
     Map<String, PluginInfo> output = Maps.newTreeMap();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ConfigInfo.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ConfigInfo.java
index af67f05..bb620a0 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ConfigInfo.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ConfigInfo.java
@@ -33,6 +33,7 @@
 import com.google.gerrit.server.git.TransferConfig;
 import com.google.inject.util.Providers;
 
+import java.util.Arrays;
 import java.util.List;
 import java.util.Map;
 import java.util.TreeMap;
@@ -153,7 +154,11 @@
         p.configuredValue = configuredValue;
         p.inheritedValue = getInheritedValue(project, cfgFactory, e);
       } else {
-        p.value = configuredValue != null ? configuredValue : configEntry.getDefaultValue();
+        if (configEntry.getType() == ProjectConfigEntry.Type.ARRAY) {
+          p.values = Arrays.asList(cfg.getStringList(e.getExportName()));
+        } else {
+          p.value = configuredValue != null ? configuredValue : configEntry.getDefaultValue();
+        }
       }
       Map<String, ConfigParameterInfo> pc = pluginConfig.get(e.getPluginName());
       if (pc == null) {
@@ -204,5 +209,6 @@
     public String configuredValue;
     public String inheritedValue;
     public List<String> permittedValues;
+    public List<String> values;
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateBranch.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateBranch.java
index 621809a..8e8c844 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateBranch.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateBranch.java
@@ -127,7 +127,7 @@
         }
       }
 
-      if (!refControl.canCreate(rw, object)) {
+      if (!refControl.canCreate(rw, object, true)) {
         throw new AuthException("Cannot create \"" + ref + "\"");
       }
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateProject.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateProject.java
index 6fd611f..5f3d2da 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateProject.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateProject.java
@@ -37,6 +37,7 @@
 import com.google.gerrit.server.group.GroupsCollection;
 import com.google.gerrit.server.project.CreateProject.Input;
 import com.google.gerrit.server.project.ProjectJson.ProjectInfo;
+import com.google.gerrit.server.project.PutConfig.ConfigValue;
 import com.google.gerrit.server.validators.ProjectCreationValidationListener;
 import com.google.gerrit.server.validators.ValidationException;
 import com.google.inject.Inject;
@@ -65,7 +66,7 @@
     public InheritableBoolean useContentMerge;
     public InheritableBoolean requireChangeId;
     public String maxObjectSizeLimit;
-    public Map<String, Map<String, String>> pluginConfigValues;
+    public Map<String, Map<String, ConfigValue>> pluginConfigValues;
   }
 
   public static interface Factory {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/PutConfig.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/PutConfig.java
index 6338b55..f8399c4 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/PutConfig.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/PutConfig.java
@@ -15,6 +15,7 @@
 package com.google.gerrit.server.project;
 
 import com.google.common.base.CharMatcher;
+import com.google.common.base.Joiner;
 import com.google.common.base.Strings;
 import com.google.gerrit.extensions.registration.DynamicMap;
 import com.google.gerrit.extensions.restapi.BadRequestException;
@@ -44,12 +45,17 @@
 import org.slf4j.LoggerFactory;
 
 import java.io.IOException;
+import java.util.Arrays;
+import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
 
 public class PutConfig implements RestModifyView<ProjectResource, Input> {
   private static final Logger log = LoggerFactory.getLogger(PutConfig.class);
-
+  public static class ConfigValue {
+    public String value;
+    public List<String> values;
+  }
   public static class Input {
     public String description;
     public InheritableBoolean useContributorAgreements;
@@ -59,7 +65,7 @@
     public String maxObjectSizeLimit;
     public SubmitType submitType;
     public Project.State state;
-    public Map<String, Map<String, String>> pluginConfigValues;
+    public Map<String, Map<String, ConfigValue>> pluginConfigValues;
   }
 
   private final MetaDataUpdate.User metaDataUpdateFactory;
@@ -180,12 +186,12 @@
   }
 
   private void setPluginConfigValues(ProjectState projectState,
-      ProjectConfig projectConfig, Map<String, Map<String, String>> pluginConfigValues)
+      ProjectConfig projectConfig, Map<String, Map<String, ConfigValue>> pluginConfigValues)
       throws BadRequestException {
-    for (Entry<String, Map<String, String>> e : pluginConfigValues.entrySet()) {
+    for (Entry<String, Map<String, ConfigValue>> e : pluginConfigValues.entrySet()) {
       String pluginName = e.getKey();
       PluginConfig cfg = projectConfig.getPluginConfig(pluginName);
-      for (Entry<String, String> v : e.getValue().entrySet()) {
+      for (Entry<String, ConfigValue> v : e.getValue().entrySet()) {
         ProjectConfigEntry projectConfigEntry =
             pluginConfigEntries.get(pluginName, v.getKey());
         if (projectConfigEntry != null) {
@@ -195,32 +201,41 @@
             continue;
           }
           String oldValue = cfg.getString(v.getKey());
-          if (Strings.emptyToNull(v.getValue()) != null) {
-            if (!v.getValue().equals(oldValue)) {
+          String value = v.getValue().value;
+          if (projectConfigEntry.getType() == ProjectConfigEntry.Type.ARRAY) {
+            List<String> l = Arrays.asList(cfg.getStringList(v.getKey()));
+            oldValue = Joiner.on("\n").join(l);
+            value = Joiner.on("\n").join(v.getValue().values);
+          }
+          if (Strings.emptyToNull(value) != null) {
+            if (!value.equals(oldValue)) {
               validateProjectConfigEntryIsEditable(projectConfigEntry,
                   projectState, e.getKey(), pluginName);
               try {
                 switch (projectConfigEntry.getType()) {
                   case BOOLEAN:
-                    boolean newBooleanValue = Boolean.parseBoolean(v.getValue());
+                    boolean newBooleanValue = Boolean.parseBoolean(value);
                     cfg.setBoolean(v.getKey(), newBooleanValue);
                     break;
                   case INT:
-                    int newIntValue = Integer.parseInt(v.getValue());
+                    int newIntValue = Integer.parseInt(value);
                     cfg.setInt(v.getKey(), newIntValue);
                     break;
                   case LONG:
-                    long newLongValue = Long.parseLong(v.getValue());
+                    long newLongValue = Long.parseLong(value);
                     cfg.setLong(v.getKey(), newLongValue);
                     break;
                   case LIST:
-                    if (!projectConfigEntry.getPermittedValues().contains(v.getValue())) {
+                    if (!projectConfigEntry.getPermittedValues().contains(value)) {
                       throw new BadRequestException(String.format(
                           "The value '%s' is not permitted for parameter '%s' of plugin '"
-                              + pluginName + "'", v.getValue(), v.getKey()));
+                              + pluginName + "'", value, v.getKey()));
                     }
                   case STRING:
-                    cfg.setString(v.getKey(), v.getValue());
+                    cfg.setString(v.getKey(), value);
+                    break;
+                  case ARRAY:
+                    cfg.setStringList(v.getKey(), v.getValue().values);
                     break;
                   default:
                     log.warn(String.format(
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java
index a6ad374..858ff91 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java
@@ -233,9 +233,10 @@
    *
    * @param rw revision pool {@code object} was parsed in.
    * @param object the object the user will start the reference with.
+   * @param existsOnServer the object exists on server or not.
    * @return {@code true} if the user specified can create a new Git ref
    */
-  public boolean canCreate(RevWalk rw, RevObject object) {
+  public boolean canCreate(RevWalk rw, RevObject object, boolean existsOnServer) {
     if (!canWrite()) {
       return false;
     }
@@ -253,8 +254,8 @@
     if (object instanceof RevCommit) {
       return getCurrentUser().getCapabilities().canAdministrateServer()
           || (owner && !isBlocked(Permission.CREATE))
-          || (canPerform(Permission.CREATE) && projectControl.canReadCommit(rw,
-              (RevCommit) object));
+          || (canPerform(Permission.CREATE) && (!existsOnServer && canUpdate() || projectControl
+              .canReadCommit(rw, (RevCommit) object)));
     } else if (object instanceof RevTag) {
       final RevTag tag = (RevTag) object;
       try {
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/tools/root/hooks/commit-msg b/gerrit-server/src/main/resources/com/google/gerrit/server/tools/root/hooks/commit-msg
index 12dcd52..d8f009b 100644
--- a/gerrit-server/src/main/resources/com/google/gerrit/server/tools/root/hooks/commit-msg
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/tools/root/hooks/commit-msg
@@ -26,7 +26,7 @@
 #
 add_ChangeId() {
 	clean_message=`sed -e '
-		/^diff --git a\/.*/{
+		/^diff --git .*/{
 			s///
 			q
 		}
@@ -81,7 +81,7 @@
 	# Skip the line starting with the diff command and everything after it,
 	# up to the end of the file, assuming it is only patch data.
 	# If more than one line before the diff was empty, strip all but one.
-	/^diff --git a/ {
+	/^diff --git / {
 		blankLines = 0
 		while (getline) { }
 		next
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
index 295f841..4342684 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
@@ -736,20 +736,24 @@
   }
 
   @Test
-  public void byBeforeAbsolute() throws Exception {
+  public void byBefore() throws Exception {
     clockStepMs = MILLISECONDS.convert(30, HOURS);
     TestRepository<InMemoryRepository> repo = createProject("repo");
     Change change1 = newChange(repo, null, null, null, null).insert();
     Change change2 = newChange(repo, null, null, null, null).insert();
     clockStepMs = 0;
 
-    // GitDateParser drops unparsed portions of the string, so be very careful
-    // with formats.
     assertTrue(query("before:2009-09-29").isEmpty());
     assertTrue(query("before:2009-09-30").isEmpty());
     assertTrue(query("before:\"2009-09-30 16:59:00 -0400\"").isEmpty());
+    assertTrue(query("before:\"2009-09-30 20:59:00 -0000\"").isEmpty());
+    assertTrue(query("before:\"2009-09-30 20:59:00\"").isEmpty());
     assertResultEquals(change1,
-        queryOne("before:\"2009-09-30 21:02:00 -0400\""));
+        queryOne("before:\"2009-09-30 17:02:00 -0400\""));
+    assertResultEquals(change1,
+        queryOne("before:\"2009-10-01 21:02:00 -0000\""));
+    assertResultEquals(change1,
+        queryOne("before:\"2009-10-01 21:02:00\""));
     assertResultEquals(change1, queryOne("before:2009-10-01"));
 
     List<ChangeInfo> results;
@@ -760,41 +764,18 @@
   }
 
   @Test
-  public void byBeforeRelative() throws Exception {
+  public void byAfter() throws Exception {
     clockStepMs = MILLISECONDS.convert(30, HOURS);
     TestRepository<InMemoryRepository> repo = createProject("repo");
     Change change1 = newChange(repo, null, null, null, null).insert();
     Change change2 = newChange(repo, null, null, null, null).insert();
     clockStepMs = 0;
 
-    assertTrue(query("before:\"3 days ago\"").isEmpty());
-    assertResultEquals(change1, queryOne("before:\"2 days ago\""));
-
-    List<ChangeInfo> results;
-    results = query("before:\"1 day ago\"");
-    assertEquals(2, results.size());
-    assertResultEquals(change2, results.get(0));
-    assertResultEquals(change1, results.get(1));
-
-    results = query("before:\"12 hours ago\"");
-    assertEquals(2, results.size());
-    assertResultEquals(change2, results.get(0));
-    assertResultEquals(change1, results.get(1));
-  }
-
-  @Test
-  public void byAfterAbsolute() throws Exception {
-    clockStepMs = MILLISECONDS.convert(30, HOURS);
-    TestRepository<InMemoryRepository> repo = createProject("repo");
-    Change change1 = newChange(repo, null, null, null, null).insert();
-    Change change2 = newChange(repo, null, null, null, null).insert();
-    clockStepMs = 0;
-
-    // GitDateParser drops unparsed portions of the string, so be very careful
-    // with formats.
-    assertTrue(query("after:2009-10-02").isEmpty());
+    assertTrue(query("after:2009-10-03").isEmpty());
     assertResultEquals(change2,
         queryOne("after:\"2009-10-01 20:59:59 -0400\""));
+    assertResultEquals(change2,
+        queryOne("after:\"2009-10-01 20:59:59 -0000\""));
     assertResultEquals(change2, queryOne("after:2009-10-01"));
 
     List<ChangeInfo> results;
@@ -804,29 +785,6 @@
     assertResultEquals(change1, results.get(1));
   }
 
-  @Test
-  public void byAfterRelative() throws Exception {
-    clockStepMs = MILLISECONDS.convert(30, HOURS);
-    TestRepository<InMemoryRepository> repo = createProject("repo");
-    Change change1 = newChange(repo, null, null, null, null).insert();
-    Change change2 = newChange(repo, null, null, null, null).insert();
-    clockStepMs = 0;
-
-    assertTrue(query("after:\"1 days ago\"").isEmpty());
-    assertResultEquals(change2, queryOne("after:\"2 days ago\""));
-
-    List<ChangeInfo> results;
-    results = query("after:\"3 days ago\"");
-    assertEquals(2, results.size());
-    assertResultEquals(change2, results.get(0));
-    assertResultEquals(change1, results.get(1));
-
-    results = query("after:\"72 hours ago\"");
-    assertEquals(2, results.size());
-    assertResultEquals(change2, results.get(0));
-    assertResultEquals(change1, results.get(1));
-  }
-
   protected ChangeInserter newChange(
       TestRepository<InMemoryRepository> repo,
       @Nullable RevCommit commit, @Nullable String key, @Nullable Integer owner,
diff --git a/gerrit-sshd/BUCK b/gerrit-sshd/BUCK
index 679072b..4728c31 100644
--- a/gerrit-sshd/BUCK
+++ b/gerrit-sshd/BUCK
@@ -38,3 +38,17 @@
   srcs = SRCS,
   visibility = ['PUBLIC'],
 )
+
+java_test(
+  name = 'sshd_tests',
+  srcs = glob(
+    ['src/test/java/**/*.java'],
+  ),
+  deps = [
+    ':sshd',
+    '//gerrit-server:server',
+    '//lib:guava',
+    '//lib:junit',
+  ],
+  source_under_test = [':sshd'],
+)
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CreateProjectCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CreateProjectCommand.java
index f012dac..67d2738 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CreateProjectCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CreateProjectCommand.java
@@ -14,7 +14,9 @@
 
 package com.google.gerrit.sshd.commands;
 
+import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Function;
+import com.google.common.base.Splitter;
 import com.google.common.collect.Lists;
 import com.google.gerrit.common.data.GlobalCapability;
 import com.google.gerrit.common.errors.ProjectCreationFailedException;
@@ -28,6 +30,7 @@
 import com.google.gerrit.server.project.CreateProject;
 import com.google.gerrit.server.project.NoSuchProjectException;
 import com.google.gerrit.server.project.ProjectControl;
+import com.google.gerrit.server.project.PutConfig.ConfigValue;
 import com.google.gerrit.server.project.SuggestParentCandidates;
 import com.google.gerrit.sshd.CommandMetaData;
 import com.google.gerrit.sshd.SshCommand;
@@ -189,21 +192,29 @@
     }
   }
 
-  private Map<String, Map<String, String>> parsePluginConfigValues(
+  @VisibleForTesting
+  Map<String, Map<String, ConfigValue>> parsePluginConfigValues(
       List<String> pluginConfigValues) throws UnloggedFailure {
-    Map<String, Map<String, String>> m = new HashMap<>();
+    Map<String, Map<String, ConfigValue>> m = new HashMap<>();
     for (String pluginConfigValue : pluginConfigValues) {
       String[] s = pluginConfigValue.split("=");
       String[] s2 = s[0].split("\\.");
       if (s.length != 2 || s2.length != 2) {
         throw new UnloggedFailure(1, "Invalid plugin config value '"
             + pluginConfigValue
-            + "', expected format '<plugin-name>.<parameter-name>=<value>'");
+            + "', expected format '<plugin-name>.<parameter-name>=<value>'"
+            + " or '<plugin-name>.<parameter-name>=<value1,value2,...>'");
       }
-      String value = s[1];
+      ConfigValue value = new ConfigValue();
+      String v = s[1];
+      if (v.contains(",")) {
+        value.values = Lists.newArrayList(Splitter.on(",").split(v));
+      } else {
+        value.value = v;
+      }
       String pluginName = s2[0];
       String paramName = s2[1];
-      Map<String, String> l = m.get(pluginName);
+      Map<String, ConfigValue> l = m.get(pluginName);
       if (l == null) {
         l = new HashMap<>();
         m.put(pluginName, l);
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowQueue.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowQueue.java
index 40f7059..77d79b3 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowQueue.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowQueue.java
@@ -16,6 +16,7 @@
 
 import static com.google.gerrit.sshd.CommandMetaData.Mode.MASTER_OR_SLAVE;
 
+import com.google.common.base.Objects;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.git.TaskInfoFactory;
@@ -139,14 +140,17 @@
             id(taskInfo.getTaskId()), start, startTime, "",
             taskInfo.getTaskString(taskNameWidth)));
       } else if (regularUserCanSee) {
-        if (remoteName == null) {
-          remoteName = projectName.get();
-        } else {
-          remoteName = remoteName + "/" + projectName;
+        if (projectName != null) {
+          if (remoteName == null) {
+            remoteName = projectName.get();
+          } else {
+            remoteName = remoteName + "/" + projectName.get();
+          }
         }
 
-        stdout.print(String.format("%8s %-12s %-4s %s\n", //
-            id(taskInfo.getTaskId()), start, startTime, remoteName));
+        stdout.print(String.format("%8s %-12s %-4s %s\n",
+            id(taskInfo.getTaskId()), start, startTime,
+            Objects.firstNonNull(remoteName, "n/a")));
       }
     }
     stdout.print("----------------------------------------------"
diff --git a/gerrit-sshd/src/test/java/com/google/gerrit/sshd/commands/ProjectConfigParamParserTest.java b/gerrit-sshd/src/test/java/com/google/gerrit/sshd/commands/ProjectConfigParamParserTest.java
new file mode 100644
index 0000000..a22bae2
--- /dev/null
+++ b/gerrit-sshd/src/test/java/com/google/gerrit/sshd/commands/ProjectConfigParamParserTest.java
@@ -0,0 +1,56 @@
+// Copyright (C) 2014 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.sshd.commands;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertNull;
+
+import com.google.gerrit.server.project.PutConfig.ConfigValue;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.Collections;
+import java.util.Map;
+
+public class ProjectConfigParamParserTest {
+
+  private CreateProjectCommand cmd;
+  @Before
+  public void setUp() {
+    cmd = new CreateProjectCommand();
+  }
+
+  @Test
+  public void parseSingleValue() throws Exception {
+    String in = "a.b=c";
+    Map<String, Map<String, ConfigValue>> r =
+        cmd.parsePluginConfigValues(Collections.singletonList(in));
+    ConfigValue configValue = r.get("a").get("b");
+    assertEquals("c", configValue.value);
+    assertNull(configValue.values);
+  }
+
+  @Test
+  public void parseMultipleValue() throws Exception {
+    String in = "a.b=c,d,e";
+    Map<String, Map<String, ConfigValue>> r =
+        cmd.parsePluginConfigValues(Collections.singletonList(in));
+    ConfigValue configValue = r.get("a").get("b");
+    assertArrayEquals(new String[] {"c", "d", "e"}, configValue.values.toArray());
+    assertNull(configValue.value);
+  }
+}
diff --git a/lib/BUCK b/lib/BUCK
index 67bf92a..290ed92 100644
--- a/lib/BUCK
+++ b/lib/BUCK
@@ -20,6 +20,7 @@
 define_license(name = 'prologcafe')
 define_license(name = 'protobuf')
 define_license(name = 'slf4j')
+define_license(name = 'xz')
 define_license(name = 'DO_NOT_DISTRIBUTE')
 
 maven_jar(
@@ -34,9 +35,9 @@
 
 maven_jar(
   name = 'gwtjsonrpc',
-  id = 'gwtjsonrpc:gwtjsonrpc:1.4',
-  bin_sha1 = '7500577cbf3afc3395ce15a2f5bccc4b02b0d497',
-  src_sha1 = '589e8eeb4663ee7ba330275ccfac2d6b2c6f28db',
+  id = 'gwtjsonrpc:gwtjsonrpc:1.5',
+  bin_sha1 = '8995287e2c3c866e826d06993904e2c8d7961e4b',
+  src_sha1 = 'c9461f6c0490f26720e3ff15b5607320eab89d96',
   license = 'Apache2.0',
   repository = GERRIT,
 )
@@ -248,3 +249,12 @@
   visibility = ['//lib:easymock'],
   attach_source = False,
 )
+
+maven_jar(
+  name = 'tukaani-xz',
+  id = 'org.tukaani:xz:1.4',
+  sha1 = '18a9a2ce6abf32ea1b5fd31dae5210ad93f4e5e3',
+  license = 'xz',
+  attach_source = False,
+  visibility = ['//lib/jgit:jgit-archive'],
+)
diff --git a/lib/LICENSE-xz b/lib/LICENSE-xz
new file mode 100644
index 0000000..420556e
--- /dev/null
+++ b/lib/LICENSE-xz
@@ -0,0 +1,4 @@
+All the files in this package have been written by Lasse Collin
+and/or Igor Pavlov. All these files have been put into the
+public domain. You can do whatever you want with these files.
+This software is provided "as is", without any warranty.
diff --git a/lib/commons/BUCK b/lib/commons/BUCK
index 6f412e4..aed2c68 100644
--- a/lib/commons/BUCK
+++ b/lib/commons/BUCK
@@ -22,6 +22,15 @@
 )
 
 maven_jar(
+  name = 'compress',
+  id = 'org.apache.commons:commons-compress:1.7',
+  sha1 = 'ab365c96ee9bc88adcc6fa40d185c8e15a31410d',
+  license = 'Apache2.0',
+  exclude = ['META-INF/LICENSE.txt', 'META-INF/NOTICE.txt'],
+  visibility = ['//lib/jgit:jgit-archive'],
+)
+
+maven_jar(
   name = 'dbcp',
   id = 'commons-dbcp:commons-dbcp:1.4',
   sha1 = '30be73c965cc990b153a100aaaaafcf239f82d39',
diff --git a/lib/jgit/BUCK b/lib/jgit/BUCK
index ee5c4d0..026fa40 100644
--- a/lib/jgit/BUCK
+++ b/lib/jgit/BUCK
@@ -34,6 +34,23 @@
 )
 
 maven_jar(
+  name = 'jgit-archive',
+  id = 'org.eclipse.jgit:org.eclipse.jgit.archive:' + VERS,
+  sha1 = 'c645b284344ec9791404f6fd0e04f6dbedb58b7d',
+  license = 'jgit',
+  repository = REPO,
+  deps = [':jgit',
+    '//lib/commons:compress',
+    '//lib:tukaani-xz',
+  ],
+  unsign = True,
+  exclude = [
+    'about.html',
+    'plugin.properties',
+  ],
+)
+
+maven_jar(
   name = 'junit',
   id = 'org.eclipse.jgit:org.eclipse.jgit.junit:' + VERS,
   sha1 = '6ba0c7bf8f9577067c4338976385828e3ad774eb',
diff --git a/plugins/cookbook-plugin b/plugins/cookbook-plugin
index ea8c2ed..cccfb37 160000
--- a/plugins/cookbook-plugin
+++ b/plugins/cookbook-plugin
@@ -1 +1 @@
-Subproject commit ea8c2ed60f0459669349d709883c788d4f4d0486
+Subproject commit cccfb378aedc773bd5a82dcb50b1b3d78ce034d0
diff --git a/plugins/replication b/plugins/replication
index f9dc2f1..a6f1995 160000
--- a/plugins/replication
+++ b/plugins/replication
@@ -1 +1 @@
-Subproject commit f9dc2f125f7bba4f707904cc7fa2675d3736f2ba
+Subproject commit a6f1995cea9cc885f0a2faa174fbafbab5c9f85c
diff --git a/tools/default.defs b/tools/default.defs
index 0df8c1f..ff4b936 100644
--- a/tools/default.defs
+++ b/tools/default.defs
@@ -152,7 +152,8 @@
     mf_src = []
     mf_cmd += 'echo "Manifest-Version: 1.0" >$OUT;'
     mf_cmd += 'echo "Gerrit-ApiType: %s" >>$OUT;' % type
-    mf_cmd += 'echo "Implementation-Version: $v" >>$OUT'
+    mf_cmd += 'echo "Implementation-Version: $v" >>$OUT;'
+    mf_cmd += 'echo "Implementation-Vendor: Gerrit Code Review" >>$OUT'
     for line in manifest_entries:
       mf_cmd += ';echo "%s" >> $OUT' % line
   genrule(