diff --git a/core/src/main/java/hudson/PluginManager.java b/core/src/main/java/hudson/PluginManager.java index 7ffbbf40d9..a7971ca33c 100644 --- a/core/src/main/java/hudson/PluginManager.java +++ b/core/src/main/java/hudson/PluginManager.java @@ -1700,7 +1700,7 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas for (Map.Entry requestedPlugin : parseRequestedPlugins(configXml).entrySet()) { PluginWrapper pw = getPlugin(requestedPlugin.getKey()); if (pw == null) { // install new - UpdateSite.Plugin toInstall = uc.getPlugin(requestedPlugin.getKey()); + UpdateSite.Plugin toInstall = uc.getPlugin(requestedPlugin.getKey(), requestedPlugin.getValue()); if (toInstall == null) { LOGGER.log(WARNING, "No such plugin {0} to install", requestedPlugin.getKey()); continue; @@ -1713,7 +1713,7 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas } jobs.add(toInstall.deploy(true)); } else if (pw.isOlderThan(requestedPlugin.getValue())) { // upgrade - UpdateSite.Plugin toInstall = uc.getPlugin(requestedPlugin.getKey()); + UpdateSite.Plugin toInstall = uc.getPlugin(requestedPlugin.getKey(), requestedPlugin.getValue()); if (toInstall == null) { LOGGER.log(WARNING, "No such plugin {0} to upgrade", requestedPlugin.getKey()); continue; diff --git a/core/src/main/java/hudson/PluginWrapper.java b/core/src/main/java/hudson/PluginWrapper.java index 9ca85b04aa..75e358a811 100644 --- a/core/src/main/java/hudson/PluginWrapper.java +++ b/core/src/main/java/hudson/PluginWrapper.java @@ -641,7 +641,7 @@ public class PluginWrapper implements Comparable, ModelObject { */ public UpdateSite.Plugin getUpdateInfo() { UpdateCenter uc = Jenkins.getInstance().getUpdateCenter(); - UpdateSite.Plugin p = uc.getPlugin(getShortName()); + UpdateSite.Plugin p = uc.getPlugin(getShortName(), getVersionNumber()); if(p!=null && p.isNewerThan(getVersion())) return p; return null; } @@ -651,6 +651,8 @@ public class PluginWrapper implements Comparable, ModelObject { */ public UpdateSite.Plugin getInfo() { UpdateCenter uc = Jenkins.getInstance().getUpdateCenter(); + UpdateSite.Plugin p = uc.getPlugin(getShortName(), getVersionNumber()); + if (p != null) return p; return uc.getPlugin(getShortName()); } diff --git a/core/src/main/java/hudson/cli/InstallPluginCommand.java b/core/src/main/java/hudson/cli/InstallPluginCommand.java index e040328936..5268df333f 100644 --- a/core/src/main/java/hudson/cli/InstallPluginCommand.java +++ b/core/src/main/java/hudson/cli/InstallPluginCommand.java @@ -27,6 +27,7 @@ import hudson.AbortException; import hudson.Extension; import hudson.FilePath; import hudson.PluginManager; +import hudson.util.VersionNumber; import jenkins.model.Jenkins; import hudson.model.UpdateSite; import hudson.model.UpdateSite.Data; @@ -60,7 +61,9 @@ public class InstallPluginCommand extends CLICommand { "If this is an URL, Jenkins downloads the URL and installs that as a plugin. " + "If it is the string ‘=’, the file will be read from standard input of the command, and ‘-name’ must be specified. " + "Otherwise the name is assumed to be the short name of the plugin in the existing update center (like ‘findbugs’), " + - "and the plugin will be installed from the update center.") + "and the plugin will be installed from the update center. If the short name includes a minimum version number " + + "(like ‘findbugs:1.4’), and there are multiple update centers publishing different versions, the update centers " + + "will be searched in order for the first one publishing a version that is at least the specified version.") public List sources = new ArrayList(); @Option(name="-name",usage="If specified, the plugin will be installed as this short name (whereas normally the name is inferred from the source name automatically).") @@ -133,7 +136,18 @@ public class InstallPluginCommand extends CLICommand { } // is this a plugin the update center? - UpdateSite.Plugin p = h.getUpdateCenter().getPlugin(source); + int index = source.lastIndexOf(':'); + UpdateSite.Plugin p; + if (index == -1) { + p = h.getUpdateCenter().getPlugin(source); + } else { + // try to find matching min version number + VersionNumber version = new VersionNumber(source.substring(index + 1)); + p = h.getUpdateCenter().getPlugin(source.substring(0,index), version); + if (p == null) { + p = h.getUpdateCenter().getPlugin(source); + } + } if (p!=null) { stdout.println(Messages.InstallPluginCommand_InstallingFromUpdateCenter(source)); Throwable e = p.deploy(dynamicLoad).get().getError(); diff --git a/core/src/main/java/hudson/model/UpdateCenter.java b/core/src/main/java/hudson/model/UpdateCenter.java index 109efb9d37..e2686cee56 100644 --- a/core/src/main/java/hudson/model/UpdateCenter.java +++ b/core/src/main/java/hudson/model/UpdateCenter.java @@ -31,6 +31,7 @@ import hudson.PluginManager; import hudson.PluginWrapper; import hudson.ProxyConfiguration; import hudson.security.ACLContext; +import hudson.util.VersionNumber; import java.nio.file.Files; import java.nio.file.InvalidPathException; import jenkins.util.SystemProperties; @@ -615,6 +616,19 @@ public class UpdateCenter extends AbstractModelObject implements Saveable, OnMas return null; } + /** + * Gets the plugin with the given name from the first {@link UpdateSite} to contain it. + * @return Discovered {@link Plugin}. {@code null} if it cannot be found + */ + public @CheckForNull Plugin getPlugin(String artifactId, VersionNumber minVersion) { + for (UpdateSite s : sites) { + Plugin p = s.getPlugin(artifactId); + if (minVersion.isNewerThan(new VersionNumber(p.version))) continue; + if (p!=null) return p; + } + return null; + } + /** * Schedules a Jenkins upgrade. */ @@ -2159,8 +2173,37 @@ public class UpdateCenter extends AbstractModelObject implements Saveable, OnMas public int compareTo(PluginEntry o) { int r = category.compareTo(o.category); if (r==0) r = plugin.name.compareToIgnoreCase(o.plugin.name); + if (r==0) r = new VersionNumber(plugin.version).compareTo(new VersionNumber(o.plugin.version)); return r; } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + PluginEntry that = (PluginEntry) o; + + if (!category.equals(that.category)) { + return false; + } + if (!plugin.name.equals(that.plugin.name)) { + return false; + } + return plugin.version.equals(that.plugin.version); + } + + @Override + public int hashCode() { + int result = category.hashCode(); + result = 31 * result + plugin.name.hashCode(); + result = 31 * result + plugin.version.hashCode(); + return result; + } } /** diff --git a/core/src/main/java/hudson/model/UpdateSite.java b/core/src/main/java/hudson/model/UpdateSite.java index 5771bf1e54..c7f7899431 100644 --- a/core/src/main/java/hudson/model/UpdateSite.java +++ b/core/src/main/java/hudson/model/UpdateSite.java @@ -1027,13 +1027,13 @@ public class UpdateSite { List deps = new ArrayList(); for(Map.Entry e : dependencies.entrySet()) { - Plugin depPlugin = Jenkins.getInstance().getUpdateCenter().getPlugin(e.getKey()); + VersionNumber requiredVersion = new VersionNumber(e.getValue()); + Plugin depPlugin = Jenkins.getInstance().getUpdateCenter().getPlugin(e.getKey(), requiredVersion); if (depPlugin == null) { LOGGER.log(Level.WARNING, "Could not find dependency {0} of {1}", new Object[] {e.getKey(), name}); continue; } - VersionNumber requiredVersion = new VersionNumber(e.getValue()); - + // Is the plugin installed already? If not, add it. PluginWrapper current = depPlugin.getInstalled(); @@ -1052,11 +1052,11 @@ public class UpdateSite { } for(Map.Entry e : optionalDependencies.entrySet()) { - Plugin depPlugin = Jenkins.getInstance().getUpdateCenter().getPlugin(e.getKey()); + VersionNumber requiredVersion = new VersionNumber(e.getValue()); + Plugin depPlugin = Jenkins.getInstance().getUpdateCenter().getPlugin(e.getKey(), requiredVersion); if (depPlugin == null) { continue; } - VersionNumber requiredVersion = new VersionNumber(e.getValue()); PluginWrapper current = depPlugin.getInstalled(); diff --git a/core/src/main/resources/hudson/PluginManager/_table.js b/core/src/main/resources/hudson/PluginManager/_table.js index 4176940362..b75704beb1 100644 --- a/core/src/main/resources/hudson/PluginManager/_table.js +++ b/core/src/main/resources/hudson/PluginManager/_table.js @@ -1,15 +1,15 @@ function showhideCategories(hdr,on) { var table = hdr.parentNode.parentNode.parentNode, newDisplay = on ? '' : 'none', - nameList = new Array(), name; + nameList = new Array(), id; for (var i = 1; i < table.rows.length; i++) { if (on || table.rows[i].cells.length == 1) table.rows[i].style.display = newDisplay; else { - // Hide duplicate rows for a plugin when not viewing by-category - name = table.rows[i].cells[1].getAttribute('data'); - if (nameList[name] == 1) table.rows[i].style.display = 'none'; - nameList[name] = 1; + // Hide duplicate rows for a plugin:version when not viewing by-category + id = table.rows[i].cells[1].getAttribute('data-id'); + if (nameList[id] == 1) table.rows[i].style.display = 'none'; + nameList[id] = 1; } } } @@ -28,8 +28,11 @@ Behaviour.specify("#filter-box", '_table', 0, function(e) { var items = document.getElementsBySelector(clz); for (var i=0; i=0); - var name = items[i].getAttribute("name"); + var name = items[i].cells && items[i].cells.length > 1 + ? items[i].cells[1].getAttribute('data-id') + : items[i].getAttribute("name"); if (visible && name != null) { + console.log(name); if (encountered[name]) { visible = false; } @@ -397,4 +400,4 @@ Behaviour.specify("#filter-box", '_table', 0, function(e) { setEnableWidgetStates(); }); -}()); \ No newline at end of file +}()); diff --git a/core/src/main/resources/hudson/PluginManager/table.jelly b/core/src/main/resources/hudson/PluginManager/table.jelly index 52fed76549..5e2ab64709 100644 --- a/core/src/main/resources/hudson/PluginManager/table.jelly +++ b/core/src/main/resources/hudson/PluginManager/table.jelly @@ -92,7 +92,7 @@ THE SOFTWARE. checked="${installedOk ? 'checked' : null}" disabled="${installedOk ? 'disabled' : null}" onClick="flip(this);"/> - +