Merge "Make sorting of releases consistent so that newest release is on top"
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.java
index 74a24ce..a9ad519 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.java
@@ -108,9 +108,11 @@
 
   String plugins();
   String pluginTabInstalled();
+  String pluginDisabled();
 
   String columnPluginName();
   String columnPluginVersion();
+  String columnPluginStatus();
 
   String noGroupSelected();
   String errorNoMatchingGroups();
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.properties b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.properties
index 30375a2..e20d544 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.properties
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.properties
@@ -88,8 +88,10 @@
 
 plugins = Plugins
 pluginTabInstalled = Installed
+pluginDisabled = Disabled
 columnPluginName = Plugin Name
 columnPluginVersion = Version
+columnPluginStatus = Status
 
 noGroupSelected = (No group selected)
 errorNoMatchingGroups = No Matching Groups
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PluginListScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PluginListScreen.java
index 814ae51..3948b35 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PluginListScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PluginListScreen.java
@@ -60,10 +60,12 @@
     PluginTable() {
       table.setText(0, 1, Util.C.columnPluginName());
       table.setText(0, 2, Util.C.columnPluginVersion());
+      table.setText(0, 3, Util.C.columnPluginStatus());
 
       final FlexCellFormatter fmt = table.getFlexCellFormatter();
       fmt.addStyleName(0, 1, Gerrit.RESOURCES.css().dataHeader());
       fmt.addStyleName(0, 2, Gerrit.RESOURCES.css().dataHeader());
+      fmt.addStyleName(0, 3, Gerrit.RESOURCES.css().dataHeader());
     }
 
     void display(final PluginMap plugins) {
@@ -80,16 +82,24 @@
     }
 
     void populate(final int row, final PluginInfo plugin) {
-      table.setWidget(
-          row,
-          1,
-          new Anchor(plugin.name(), Gerrit.selfRedirect("/plugins/"
-              + plugin.name() + "/")));
+      if (plugin.isDisabled()) {
+        table.setText(row, 1, plugin.name());
+      } else {
+        table.setWidget(
+            row,
+            1,
+            new Anchor(plugin.name(), Gerrit.selfRedirect("/plugins/"
+                + plugin.name() + "/")));
+      }
       table.setText(row, 2, plugin.version());
+      if (plugin.isDisabled()) {
+        table.setText(row, 3, Util.C.pluginDisabled());
+      }
 
       final FlexCellFormatter fmt = table.getFlexCellFormatter();
       fmt.addStyleName(row, 1, Gerrit.RESOURCES.css().dataCell());
       fmt.addStyleName(row, 2, Gerrit.RESOURCES.css().dataCell());
+      fmt.addStyleName(row, 3, Gerrit.RESOURCES.css().dataCell());
 
       setRowItem(row, plugin);
     }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchScreen.java
index 05d471b..0976a9d 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchScreen.java
@@ -153,8 +153,6 @@
     idSideB = id.getParentKey();
     this.patchIndex = patchIndex;
 
-    createReviewedPanel();
-
     prefs = fileList != null ? fileList.getPreferences() :
                                new ListenableAccountDiffPreference();
     if (Gerrit.isSignedIn()) {
@@ -168,11 +166,12 @@
           }
         });
 
+    reviewedPanel = new FlowPanel();
     settingsPanel = new PatchScriptSettingsPanel(prefs);
   }
 
-  private void createReviewedPanel(){
-    reviewedPanel = new FlowPanel();
+  private void populateReviewedPanel(){
+    reviewedPanel.clear();
 
     reviewedCheckBox = new CheckBox(PatchUtil.C.reviewedAnd() + " ");
     reviewedCheckBox.addValueChangeHandler(new ValueChangeHandler<Boolean>() {
@@ -440,6 +439,7 @@
     final int rpcseq = ++rpcSequence;
     lastScript = null;
     settingsPanel.setEnabled(false);
+    populateReviewedPanel();
     if (isFirst && fileList != null) {
       fileList.movePointerTo(patchKey);
     }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/plugins/PluginInfo.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/plugins/PluginInfo.java
index 454c97b..ace4a49 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/plugins/PluginInfo.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/plugins/PluginInfo.java
@@ -20,6 +20,8 @@
 
   public final native String name() /*-{ return this.name; }-*/;
   public final native String version() /*-{ return this.version; }-*/;
+  public final native boolean isDisabled()
+      /*-{ return this.disabled ? true : false; }-*/;
 
   protected PluginInfo() {
   }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/plugins/PluginMap.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/plugins/PluginMap.java
index 0f2ab4c..6eca206 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/plugins/PluginMap.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/plugins/PluginMap.java
@@ -21,7 +21,7 @@
 /** Plugins available from {@code /plugins/}. */
 public class PluginMap extends NativeMap<PluginInfo> {
   public static void all(AsyncCallback<PluginMap> callback) {
-    new RestApi("/plugins/")
+    new RestApi("/plugins/").addParameterTrue("all")
         .send(NativeMap.copyKeysIntoChildren(callback));
   }
 
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GitOverHttpServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GitOverHttpServlet.java
index 1c9c521..aea42f4 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GitOverHttpServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GitOverHttpServlet.java
@@ -44,7 +44,6 @@
 import org.eclipse.jgit.http.server.resolver.AsIsFileService;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.storage.pack.PackConfig;
 import org.eclipse.jgit.transport.ReceivePack;
 import org.eclipse.jgit.transport.UploadPack;
 import org.eclipse.jgit.transport.resolver.ReceivePackFactory;
@@ -177,19 +176,20 @@
   }
 
   static class UploadFactory implements UploadPackFactory<HttpServletRequest> {
-    private final PackConfig packConfig;
+    private final TransferConfig config;
     private final Provider<WebSession> session;
 
     @Inject
     UploadFactory(TransferConfig tc, Provider<WebSession> session) {
-      this.packConfig = tc.getPackConfig();
+      this.config = tc;
       this.session = session;
     }
 
     @Override
     public UploadPack create(HttpServletRequest req, Repository repo) {
       UploadPack up = new UploadPack(repo);
-      up.setPackConfig(packConfig);
+      up.setPackConfig(config.getPackConfig());
+      up.setTimeout(config.getTimeout());
       session.get().setAccessPath(AccessPath.GIT);
       return up;
     }
@@ -239,12 +239,14 @@
   static class ReceiveFactory implements ReceivePackFactory<HttpServletRequest> {
     private final AsyncReceiveCommits.Factory factory;
     private final Provider<WebSession> session;
+    private final TransferConfig config;
 
     @Inject
     ReceiveFactory(AsyncReceiveCommits.Factory factory,
-        Provider<WebSession> session) {
+        Provider<WebSession> session, TransferConfig config) {
       this.factory = factory;
       this.session = session;
+      this.config = config;
     }
 
     @Override
@@ -259,10 +261,13 @@
 
       final IdentifiedUser user = (IdentifiedUser) pc.getCurrentUser();
       final ReceiveCommits rc = factory.create(pc, db).getReceiveCommits();
-      rc.getReceivePack().setRefLogIdent(user.newRefLogIdent());
+      ReceivePack rp = rc.getReceivePack();
+      rp.setRefLogIdent(user.newRefLogIdent());
+      rp.setTimeout(config.getTimeout());
+      rp.setMaxObjectSizeLimit(config.getMaxObjectSizeLimit());
       req.setAttribute(ATT_RC, rc);
       session.get().setAccessPath(AccessPath.GIT);
-      return rc.getReceivePack();
+      return rp;
     }
   }
 
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/RenameGroup.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/RenameGroup.java
index 09dc582..24faf9e 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/RenameGroup.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/RenameGroup.java
@@ -15,6 +15,7 @@
 package com.google.gerrit.httpd.rpc.account;
 
 import com.google.gerrit.common.data.GroupDetail;
+import com.google.gerrit.common.errors.InvalidNameException;
 import com.google.gerrit.common.errors.NameAlreadyUsedException;
 import com.google.gerrit.common.errors.NoSuchGroupException;
 import com.google.gerrit.httpd.rpc.Handler;
@@ -44,7 +45,7 @@
 
   @Override
   public GroupDetail call() throws OrmException, NameAlreadyUsedException,
-      NoSuchGroupException {
+      NoSuchGroupException, InvalidNameException {
     return performRenameGroupFactory.create().renameGroup(groupId, newName);
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/PerformRenameGroup.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/PerformRenameGroup.java
index 6db232a..4c72d49 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/PerformRenameGroup.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/PerformRenameGroup.java
@@ -15,6 +15,7 @@
 package com.google.gerrit.server.account;
 
 import com.google.gerrit.common.data.GroupDetail;
+import com.google.gerrit.common.errors.InvalidNameException;
 import com.google.gerrit.common.errors.NameAlreadyUsedException;
 import com.google.gerrit.common.errors.NoSuchGroupException;
 import com.google.gerrit.reviewdb.client.AccountGroup;
@@ -60,7 +61,7 @@
 
   public GroupDetail renameGroup(final String groupName,
       final String newGroupName) throws OrmException, NameAlreadyUsedException,
-      NoSuchGroupException {
+      NoSuchGroupException, InvalidNameException {
     final AccountGroup.NameKey groupNameKey =
         new AccountGroup.NameKey(groupName);
     final AccountGroup group = groupCache.get(groupNameKey);
@@ -72,12 +73,15 @@
 
   public GroupDetail renameGroup(final AccountGroup.Id groupId,
       final String newName) throws OrmException, NameAlreadyUsedException,
-      NoSuchGroupException {
+      NoSuchGroupException, InvalidNameException {
     final GroupControl ctl = groupControlFactory.validateFor(groupId);
     final AccountGroup group = db.accountGroups().get(groupId);
     if (group == null || !ctl.isOwner()) {
       throw new NoSuchGroupException(groupId);
     }
+    if (newName.trim().isEmpty()) {
+      throw new InvalidNameException();
+    }
 
     final AccountGroup.NameKey old = group.getNameKey();
     final AccountGroup.NameKey key = new AccountGroup.NameKey(newName);
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 dca47e0..1acc6f6 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
@@ -40,6 +40,9 @@
   @Option(name = "--format", metaVar = "FMT", usage = "Output display format")
   private OutputFormat format = OutputFormat.TEXT;
 
+  @Option(name = "--all", aliases = {"-a"}, usage = "List all plugins, including disabled plugins")
+  private boolean all;
+
   @Inject
   protected ListPlugins(PluginLoader pluginLoader) {
     this.pluginLoader = pluginLoader;
@@ -67,7 +70,7 @@
 
     Map<String, PluginInfo> output = Maps.newTreeMap();
 
-    List<Plugin> plugins = Lists.newArrayList(pluginLoader.getPlugins());
+    List<Plugin> plugins = Lists.newArrayList(pluginLoader.getPlugins(all));
     Collections.sort(plugins, new Comparator<Plugin>() {
       @Override
       public int compare(Plugin a, Plugin b) {
@@ -76,20 +79,22 @@
     });
 
     if (!format.isJson()) {
-      stdout.format("%-30s %-10s\n", "Name", "Version");
+      stdout.format("%-30s %-10s %-8s\n", "Name", "Version", "Status");
       stdout
-          .print("----------------------------------------------------------------------\n");
+          .print("-------------------------------------------------------------------------------\n");
     }
 
     for (Plugin p : plugins) {
       PluginInfo info = new PluginInfo();
       info.version = p.getVersion();
+      info.disabled = p.isDisabled() ? true : null;
 
       if (format.isJson()) {
         output.put(p.getName(), info);
       } else {
-        stdout.format("%-30s %-10s\n", p.getName(),
-            Strings.nullToEmpty(info.version));
+        stdout.format("%-30s %-10s %-8s\n", p.getName(),
+            Strings.nullToEmpty(info.version),
+            p.isDisabled() ? "DISABLED" : "");
       }
     }
 
@@ -103,5 +108,9 @@
 
   private static class PluginInfo {
     String version;
+    // disabled is only read via reflection when building the json output.  We
+    // do not want to show a compiler error that it isn't used.
+    @SuppressWarnings("unused")
+    Boolean disabled;
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/Plugin.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/Plugin.java
index a3c363b..91ffbba 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/Plugin.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/Plugin.java
@@ -89,6 +89,7 @@
   private final File dataDir;
   private final ApiType apiType;
   private final ClassLoader classLoader;
+  private final boolean disabled;
   private Class<? extends Module> sysModule;
   private Class<? extends Module> sshModule;
   private Class<? extends Module> httpModule;
@@ -119,6 +120,7 @@
     this.dataDir = dataDir;
     this.apiType = apiType;
     this.classLoader = classLoader;
+    this.disabled = srcJar.getName().endsWith(".disabled");
     this.sysModule = sysModule;
     this.sshModule = sshModule;
     this.httpModule = httpModule;
@@ -165,6 +167,10 @@
     return snapshot.lastModified() != jar.lastModified();
   }
 
+  public boolean isDisabled() {
+    return disabled;
+  }
+
   public void start(PluginGuiceEnvironment env) throws Exception {
     Injector root = newRootInjector(env);
     manager = new LifecycleManager();
@@ -294,13 +300,15 @@
   }
 
   public void add(RegistrationHandle handle) {
-    if (handle instanceof ReloadableRegistrationHandle) {
-      if (reloadableHandles == null) {
-        reloadableHandles = Lists.newArrayList();
+    if (manager != null) {
+      if (handle instanceof ReloadableRegistrationHandle) {
+        if (reloadableHandles == null) {
+          reloadableHandles = Lists.newArrayList();
+        }
+        reloadableHandles.add((ReloadableRegistrationHandle<?>) handle);
       }
-      reloadableHandles.add((ReloadableRegistrationHandle<?>) handle);
+      manager.add(handle);
     }
-    manager.add(handle);
   }
 
   List<ReloadableRegistrationHandle<?>> getReloadableHandles() {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginLoader.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginLoader.java
index 67d715f..071660a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginLoader.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginLoader.java
@@ -45,6 +45,7 @@
 import java.net.URL;
 import java.net.URLClassLoader;
 import java.text.SimpleDateFormat;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.Date;
@@ -67,6 +68,7 @@
   private final PluginGuiceEnvironment env;
   private final ServerInformationImpl srvInfoImpl;
   private final ConcurrentMap<String, Plugin> running;
+  private final ConcurrentMap<String, Plugin> disabled;
   private final Map<String, FileSnapshot> broken;
   private final ReferenceQueue<ClassLoader> cleanupQueue;
   private final ConcurrentMap<CleanupHandle, Boolean> cleanupHandles;
@@ -85,6 +87,7 @@
     env = pe;
     srvInfoImpl = sii;
     running = Maps.newConcurrentMap();
+    disabled = Maps.newConcurrentMap();
     broken = Maps.newHashMap();
     cleanupQueue = new ReferenceQueue<ClassLoader>();
     cleanupHandles = Maps.newConcurrentMap();
@@ -100,8 +103,14 @@
     }
   }
 
-  public Iterable<Plugin> getPlugins() {
-    return running.values();
+  public Iterable<Plugin> getPlugins(boolean all) {
+    if (!all) {
+      return running.values();
+    } else {
+      ArrayList<Plugin> plugins = new ArrayList<Plugin>(running.values());
+      plugins.addAll(disabled.values());
+      return plugins;
+    }
   }
 
   public void installPluginFromStream(String name, InputStream in)
@@ -178,6 +187,15 @@
 
         active.stop();
         running.remove(name);
+        try {
+          FileSnapshot snapshot = FileSnapshot.save(off);
+          Plugin offPlugin = loadPlugin(name, off, snapshot);
+          disabled.put(name, offPlugin);
+        } catch (Throwable e) {
+          // This shouldn't happen, as the plugin was loaded earlier.
+          log.warn(String.format("Cannot load disabled plugin %s", name),
+              e.getCause());
+        }
       }
       cleanInBackground();
     }
@@ -205,6 +223,7 @@
         p.stop();
       }
       running.clear();
+      disabled.clear();
       broken.clear();
       if (cleanupHandles.size() > running.size()) {
         System.gc();
@@ -250,6 +269,7 @@
   public synchronized void rescan() {
     List<File> jars = scanJarsInPluginsDirectory();
     stopRemovedPlugins(jars);
+    dropRemovedDisabledPlugins(jars);
 
     for (File jar : jars) {
       String name = nameOf(jar);
@@ -292,14 +312,20 @@
         oldPlugin.stop();
         running.remove(name);
       }
-      newPlugin.start(env);
+      if (!newPlugin.isDisabled()) {
+        newPlugin.start(env);
+      }
       if (reload) {
         env.onReloadPlugin(oldPlugin, newPlugin);
         oldPlugin.stop();
-      } else {
+      } else if (!newPlugin.isDisabled()) {
         env.onStartPlugin(newPlugin);
       }
-      running.put(name, newPlugin);
+      if (!newPlugin.isDisabled()) {
+        running.put(name, newPlugin);
+      } else {
+        disabled.put(name, newPlugin);
+      }
       broken.remove(name);
     } catch (Throwable err) {
       broken.put(name, snapshot);
@@ -318,6 +344,16 @@
     }
   }
 
+  private void dropRemovedDisabledPlugins(List<File> jars) {
+    Set<String> unload = Sets.newHashSet(disabled.keySet());
+    for (File jar : jars) {
+      unload.remove(nameOf(jar));
+    }
+    for (String name : unload) {
+      disabled.remove(name);
+    }
+  }
+
   synchronized int processPendingCleanups() {
     CleanupHandle h;
     while ((h = (CleanupHandle) cleanupQueue.poll()) != null) {
@@ -336,6 +372,9 @@
 
   private static String nameOf(File jar) {
     String name = jar.getName();
+    if (name.endsWith(".disabled")) {
+      name = name.substring(0, name.lastIndexOf('.'));
+    }
     int ext = name.lastIndexOf('.');
     return 0 < ext ? name.substring(0, ext) : name;
   }
@@ -430,7 +469,9 @@
     File[] matches = pluginsDir.listFiles(new FileFilter() {
       @Override
       public boolean accept(File pathname) {
-        return pathname.getName().endsWith(".jar") && pathname.isFile();
+        String n = pathname.getName();
+        return (n.endsWith(".jar") || n.endsWith(".jar.disabled"))
+            && pathname.isFile();
       }
     });
     if (matches == null) {
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/RenameGroupCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/RenameGroupCommand.java
index ad9733d..b9abc92 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/RenameGroupCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/RenameGroupCommand.java
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.sshd.commands;
 
+import com.google.gerrit.common.errors.InvalidNameException;
 import com.google.gerrit.common.errors.NameAlreadyUsedException;
 import com.google.gerrit.common.errors.NoSuchGroupException;
 import com.google.gerrit.server.account.PerformRenameGroup;
@@ -39,6 +40,8 @@
       performRenameGroupFactory.create().renameGroup(groupName, newGroupName);
     } catch (OrmException e) {
       throw die(e);
+    } catch (InvalidNameException e) {
+      throw die(e);
     } catch (NameAlreadyUsedException e) {
       throw die(e);
     } catch (NoSuchGroupException e) {