Improved UI for implicitly locked builds.
(issue 10197)
+
+ Incorrect URL computation broke context menu for computers with spaces in their names.
+ (issue 18236)
Promote the use of 'H' in cron.
(issue 17311)
diff --git a/cli/pom.xml b/cli/pom.xml
index 4a8150f132..37d2203146 100644
--- a/cli/pom.xml
+++ b/cli/pom.xml
@@ -5,7 +5,7 @@
pomorg.jenkins-ci.main
- 1.513
+ 1.520-SNAPSHOTcli
diff --git a/cli/src/main/java/hudson/cli/CLI.java b/cli/src/main/java/hudson/cli/CLI.java
index 03004f30f9..8b348c8145 100644
--- a/cli/src/main/java/hudson/cli/CLI.java
+++ b/cli/src/main/java/hudson/cli/CLI.java
@@ -488,7 +488,7 @@ public class CLI {
LOGGER.log(FINE,e.getMessage(),e);
return -1;
}
- System.err.println("Failed to authenticate with your SSH keys.");
+ System.err.println("[WARN] Failed to authenticate with your SSH keys. Proceeding as anonymous");
LOGGER.log(FINE,"Failed to authenticate with your SSH keys.",e);
}
}
diff --git a/core/pom.xml b/core/pom.xml
index 3504947e3a..a706850ad1 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -29,7 +29,7 @@ THE SOFTWARE.
org.jenkins-ci.mainpom
- 1.513
+ 1.520-SNAPSHOT../pom.xml
@@ -112,11 +112,16 @@ THE SOFTWARE.
-
+ org.jruby.ext.posixjna-posix1.0.3
+
+ com.github.jnr
+ jnr-posix
+ 3.0.0
+ org.kohsuketrilead-putty-extension
@@ -164,7 +169,7 @@ THE SOFTWARE.
org.kohsuke.staplerstapler-adjunct-zeroclipboard
- 1.0.7-2
+ 1.1.7-1org.kohsuke.stapler
@@ -501,14 +506,6 @@ THE SOFTWARE.
jmdns3.4.0-jenkins-3
-
- com.sun.winsw
- winsw
- 1.10
- bin
- exe
- provided
- net.java.dev.jnajna
@@ -746,7 +743,7 @@ THE SOFTWARE.
com.sun.winswwinsw
- 1.10
+ 1.13binexe${project.build.outputDirectory}/windows-service
@@ -757,6 +754,14 @@ THE SOFTWARE.
+
+ maven-surefire-plugin
+
+ 0.5C
+ true
+ -XX:MaxPermSize=128m
+
+ maven-jar-plugin
diff --git a/core/src/main/java/hudson/AboutJenkins.java b/core/src/main/java/hudson/AboutJenkins.java
index e8136e1f5d..42332f9a92 100644
--- a/core/src/main/java/hudson/AboutJenkins.java
+++ b/core/src/main/java/hudson/AboutJenkins.java
@@ -1,6 +1,9 @@
package hudson;
import hudson.model.ManagementLink;
+import java.net.URL;
+import org.kohsuke.accmod.Restricted;
+import org.kohsuke.accmod.restrictions.NoExternalUse;
/**
* Show "About Jenkins" link.
@@ -27,4 +30,10 @@ public class AboutJenkins extends ManagementLink {
public String getDescription() {
return Messages.AboutJenkins_Description();
}
+
+ @Restricted(NoExternalUse.class)
+ public URL getLicensesURL() {
+ return AboutJenkins.class.getResource("/META-INF/licenses.xml");
+ }
+
}
diff --git a/core/src/main/java/hudson/ClassicPluginStrategy.java b/core/src/main/java/hudson/ClassicPluginStrategy.java
index 56b8ee5990..28c1ad78e5 100644
--- a/core/src/main/java/hudson/ClassicPluginStrategy.java
+++ b/core/src/main/java/hudson/ClassicPluginStrategy.java
@@ -23,7 +23,6 @@
*/
package hudson;
-import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import hudson.Plugin.DummyImpl;
import hudson.PluginWrapper.Dependency;
@@ -36,11 +35,21 @@ import hudson.util.MaskingClassLoader;
import hudson.util.VersionNumber;
import jenkins.ClassLoaderReflectionToolkit;
import jenkins.ExtensionFilter;
+import org.apache.commons.io.output.NullOutputStream;
import org.apache.tools.ant.AntClassLoader;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.taskdefs.Expand;
+import org.apache.tools.ant.taskdefs.Zip;
import org.apache.tools.ant.types.FileSet;
+import org.apache.tools.ant.types.PatternSet;
+import org.apache.tools.ant.types.Resource;
+import org.apache.tools.ant.types.ZipFileSet;
+import org.apache.tools.ant.types.resources.MappedResourceCollection;
+import org.apache.tools.ant.util.GlobPatternMapper;
+import org.apache.tools.zip.ZipEntry;
+import org.apache.tools.zip.ZipExtraField;
+import org.apache.tools.zip.ZipOutputStream;
import java.io.Closeable;
import java.io.File;
@@ -57,7 +66,6 @@ import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
-import java.util.Iterator;
import java.util.List;
import java.util.Vector;
import java.util.jar.Attributes;
@@ -413,11 +421,10 @@ public class ClassicPluginStrategy implements PluginStrategy {
* Explodes the plugin into a directory, if necessary.
*/
private static void explode(File archive, File destDir) throws IOException {
- if(!destDir.exists())
- destDir.mkdirs();
+ destDir.mkdirs();
// timestamp check
- File explodeTime = new File(destDir,".timestamp");
+ File explodeTime = new File(destDir,".timestamp2");
if(explodeTime.exists() && explodeTime.lastModified()==archive.lastModified())
return; // no need to expand
@@ -425,12 +432,9 @@ public class ClassicPluginStrategy implements PluginStrategy {
Util.deleteRecursive(destDir);
try {
- Expand e = new Expand();
- e.setProject(new Project());
- e.setTaskType("unzip");
- e.setSrc(archive);
- e.setDest(destDir);
- e.execute();
+ Project prj = new Project();
+ unzipExceptClasses(archive, destDir, prj);
+ createClassJarFromWebInfClasses(archive, destDir, prj);
} catch (BuildException x) {
throw new IOException2("Failed to expand " + archive,x);
}
@@ -442,6 +446,66 @@ public class ClassicPluginStrategy implements PluginStrategy {
}
}
+ /**
+ * Repackage classes directory into a jar file to make it remoting friendly.
+ * The remoting layer can cache jar files but not class files.
+ */
+ private static void createClassJarFromWebInfClasses(File archive, File destDir, Project prj) {
+ File classesJar = new File(destDir, "WEB-INF/lib/classes.jar");
+
+ ZipFileSet zfs = new ZipFileSet();
+ zfs.setProject(prj);
+ zfs.setSrc(archive);
+ zfs.setIncludes("WEB-INF/classes/");
+
+ MappedResourceCollection mapper = new MappedResourceCollection();
+ mapper.add(zfs);
+
+ GlobPatternMapper gm = new GlobPatternMapper();
+ gm.setFrom("WEB-INF/classes/*");
+ gm.setTo("*");
+ mapper.add(gm);
+
+ final long dirTime = archive.lastModified();
+ Zip z = new Zip() {
+ /**
+ * Forces the fixed timestamp for directories to make sure
+ * classes.jar always get a consistent checksum.
+ */
+ protected void zipDir(Resource dir, ZipOutputStream zOut, String vPath,
+ int mode, ZipExtraField[] extra)
+ throws IOException {
+
+ ZipOutputStream wrapped = new ZipOutputStream(new NullOutputStream()) {
+ @Override
+ public void putNextEntry(ZipEntry ze) throws IOException {
+ ze.setTime(dirTime+1999); // roundup
+ super.putNextEntry(ze);
+ }
+ };
+ super.zipDir(dir,wrapped,vPath,mode,extra);
+ }
+ };
+ z.setProject(prj);
+ z.setTaskType("zip");
+ classesJar.getParentFile().mkdirs();
+ z.setDestFile(classesJar);
+ z.add(mapper);
+ z.execute();
+ }
+
+ private static void unzipExceptClasses(File archive, File destDir, Project prj) {
+ Expand e = new Expand();
+ e.setProject(prj);
+ e.setTaskType("unzip");
+ e.setSrc(archive);
+ e.setDest(destDir);
+ PatternSet p = new PatternSet();
+ p.setExcludes("WEB-INF/classes/");
+ e.addPatternset(p);
+ e.execute();
+ }
+
/**
* Used to load classes from dependency plugins.
*/
diff --git a/core/src/main/java/hudson/ExtensionFinder.java b/core/src/main/java/hudson/ExtensionFinder.java
index 72169a1791..c63c948898 100644
--- a/core/src/main/java/hudson/ExtensionFinder.java
+++ b/core/src/main/java/hudson/ExtensionFinder.java
@@ -413,7 +413,14 @@ public abstract class ExtensionFinder implements ExtensionPoint {
* This is necessary as a failure to load one plugin shouldn't fail the startup of the entire Jenkins.
* Instead, we should just drop the failing plugins.
*/
- public static final Scope FAULT_TOLERANT_SCOPE = new Scope() {
+ public static final Scope FAULT_TOLERANT_SCOPE = new FaultTolerantScope(true);
+ private static final Scope QUIET_FAULT_TOLERANT_SCOPE = new FaultTolerantScope(false);
+
+ private static final class FaultTolerantScope implements Scope {
+ private final boolean verbose;
+ FaultTolerantScope(boolean verbose) {
+ this.verbose = verbose;
+ }
public Provider scope(final Key key, final Provider unscoped) {
final Provider base = Scopes.SINGLETON.scope(key,unscoped);
return new Provider() {
@@ -421,13 +428,21 @@ public abstract class ExtensionFinder implements ExtensionPoint {
try {
return base.get();
} catch (Exception e) {
- LOGGER.log(Level.WARNING,"Failed to instantiate. Skipping this component",e);
+ error(key, e);
return null;
} catch (LinkageError e) {
- LOGGER.log(Level.WARNING,"Failed to instantiate. Skipping this component",e);
+ error(key, e);
return null;
}
}
+ void error(Key key, Throwable x) {
+ if (verbose) {
+ LOGGER.log(Level.WARNING, "Failed to instantiate " + key + "; skipping this component", x);
+ } else {
+ LOGGER.log(Level.WARNING, "Failed to instantiate optional component {0}; skipping", key.getTypeLiteral());
+ LOGGER.log(Level.FINE, key.toString(), x);
+ }
+ }
};
}
};
@@ -484,16 +499,18 @@ public abstract class ExtensionFinder implements ExtensionPoint {
for (final IndexItem,Object> item : index) {
id++;
+ boolean optional = isOptional(item.annotation());
try {
AnnotatedElement e = item.element();
Annotation a = item.annotation();
if (!isActive(a,e)) continue;
+ Scope scope = optional ? QUIET_FAULT_TOLERANT_SCOPE : FAULT_TOLERANT_SCOPE;
if (e instanceof Class) {
Key key = Key.get((Class)e);
resolve((Class)e);
annotations.put(key,a);
- bind(key).in(FAULT_TOLERANT_SCOPE);
+ bind(key).in(scope);
} else {
Class extType;
if (e instanceof Field) {
@@ -513,15 +530,15 @@ public abstract class ExtensionFinder implements ExtensionPoint {
public Object get() {
return instantiate(item);
}
- }).in(FAULT_TOLERANT_SCOPE);
+ }).in(scope);
}
} catch (LinkageError e) {
// sometimes the instantiation fails in an indirect classloading failure,
// which results in a LinkageError
- LOGGER.log(isOptional(item.annotation()) ? Level.FINE : Level.WARNING,
+ LOGGER.log(optional ? Level.FINE : Level.WARNING,
"Failed to load "+item.className(), e);
} catch (InstantiationException e) {
- LOGGER.log(isOptional(item.annotation()) ? Level.FINE : Level.WARNING,
+ LOGGER.log(optional ? Level.FINE : Level.WARNING,
"Failed to load "+item.className(), e);
}
}
diff --git a/core/src/main/java/hudson/FilePath.java b/core/src/main/java/hudson/FilePath.java
index 225f42ed34..569d4419ce 100644
--- a/core/src/main/java/hudson/FilePath.java
+++ b/core/src/main/java/hudson/FilePath.java
@@ -97,6 +97,7 @@ import com.jcraft.jzlib.GZIPOutputStream;
import com.sun.jna.Native;
import hudson.os.PosixException;
+import java.io.BufferedInputStream;
import java.util.Enumeration;
import java.util.logging.Logger;
import org.apache.tools.ant.taskdefs.Chmod;
@@ -496,7 +497,7 @@ public final class FilePath implements Serializable {
private static void unzip(File dir, InputStream in) throws IOException {
File tmpFile = File.createTempFile("tmpzip", null); // uses java.io.tmpdir
try {
- // XXX why does this not simply use ZipInputStream?
+ // TODO why does this not simply use ZipInputStream?
IOUtils.copy(in, tmpFile);
unzip(dir,tmpFile);
}
@@ -1663,7 +1664,7 @@ public final class FilePath implements Serializable {
return act(new FileCallable() {
private static final long serialVersionUID = 1L;
public String invoke(File f, VirtualChannel channel) throws IOException {
- return Util.getDigestOf(new FileInputStream(f));
+ return Util.getDigestOf(new BufferedInputStream(new FileInputStream(f)));
}
});
}
diff --git a/core/src/main/java/hudson/Functions.java b/core/src/main/java/hudson/Functions.java
index c73b72f4c5..d922f3310e 100644
--- a/core/src/main/java/hudson/Functions.java
+++ b/core/src/main/java/hudson/Functions.java
@@ -974,6 +974,67 @@ public class Functions {
return Items.getAllItems(root, TopLevelItem.class);
}
+ /**
+ * Gets the relative name or display name to the given item from the specified group.
+ *
+ * @since 1.515
+ * @param p the Item we want the relative display name
+ * @param g the ItemGroup used as point of reference for the item
+ * @param useDisplayName if true, returns a display name, otherwise returns a name
+ * @return
+ * String like "foo » bar"
+ */
+ public static String getRelativeNameFrom(Item p, ItemGroup g, boolean useDisplayName) {
+ if (p == null) return null;
+ if (g == null) return useDisplayName ? p.getFullDisplayName() : p.getFullName();
+ String separationString = useDisplayName ? " » " : "/";
+
+ // first list up all the parents
+ Map parents = new HashMap();
+ int depth=0;
+ while (g!=null) {
+ parents.put(g, depth++);
+ if (g instanceof Item)
+ g = ((Item)g).getParent();
+ else
+ g = null;
+ }
+
+ StringBuilder buf = new StringBuilder();
+ Item i=p;
+ while (true) {
+ if (buf.length()>0) buf.insert(0,separationString);
+ buf.insert(0,useDisplayName ? i.getDisplayName() : i.getName());
+ ItemGroup gr = i.getParent();
+
+ Integer d = parents.get(gr);
+ if (d!=null) {
+ String s="";
+ for (int j=d; j>0; j--)
+ s+=".." + separationString;
+ return s+buf;
+ }
+
+ if (gr instanceof Item)
+ i = (Item)gr;
+ else
+ return null;
+ }
+ }
+
+ /**
+ * Gets the name to the given item relative to given group.
+ *
+ * @since 1.515
+ * @param p the Item we want the relative display name
+ * @param g the ItemGroup used as point of reference for the item
+ * @return
+ * String like "foo » bar"
+ */
+ public static String getRelativeNameFrom(Item p, ItemGroup g) {
+ return getRelativeNameFrom(p, g, false);
+ }
+
/**
* Gets the relative display name to the given item from the specified group.
@@ -985,10 +1046,7 @@ public class Functions {
* String like "foo » bar"
*/
public static String getRelativeDisplayNameFrom(Item p, ItemGroup g) {
- if (p == null) return null;
- String relativeName = p.getRelativeNameFrom(g);
- if (relativeName == null) return null;
- return relativeName.replace("/", " » ");
+ return getRelativeNameFrom(p, g, true);
}
public static Map dumpAllThreads() {
@@ -1658,4 +1716,20 @@ public class Functions {
DecimalFormat format = new DecimalFormat("#0.00");
return format.format(number) + " " + measure;
}
+
+ /**
+ * Get a string that can be safely broken to several lines when necessary.
+ *
+ * This implementation inserts <wbr> tags into string. It allows browsers
+ * to wrap line before any sequence of punctuation characters or anywhere
+ * in the middle of prolonged sequences of word characters.
+ *
+ * @since 1.517
+ */
+ public static String breakableString(final String plain) {
+
+ return plain.replaceAll("(\\p{Punct}+\\w)", "$1")
+ .replaceAll("(\\w{10})(?=\\w{3})", "$1")
+ ;
+ }
}
diff --git a/core/src/main/java/hudson/PluginManager.java b/core/src/main/java/hudson/PluginManager.java
index 9c79924e7b..8bdb17ae89 100644
--- a/core/src/main/java/hudson/PluginManager.java
+++ b/core/src/main/java/hudson/PluginManager.java
@@ -101,6 +101,7 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Future;
+import java.util.jar.JarFile;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.xml.sax.Attributes;
@@ -729,19 +730,25 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas
if(!fileName.endsWith(".jpi") && !fileName.endsWith(".hpi")){
throw new Failure(hudson.model.Messages.Hudson_NotAPlugin(fileName));
}
- final String baseName = FilenameUtils.getBaseName(fileName);
- new File(rootDir, baseName + ".hpi").delete(); // don't keep confusing legacy *.hpi
- fileItem.write(new File(rootDir, baseName + ".jpi")); // rename all new plugins to *.jpi
+
+ // first copy into a temporary file name
+ File t = File.createTempFile("uploaded", ".jpi");
+ t.deleteOnExit();
+ fileItem.write(t);
fileItem.delete();
- PluginWrapper existing = getPlugin(baseName);
- if (existing!=null && existing.isBundled){
- existing.doPin();
- }
+ final String baseName = identifyPluginShortName(t);
pluginUploaded = true;
- return new HttpRedirect(".");
+ // Now create a dummy plugin that we can dynamically load (the InstallationJob will force a restart if one is needed):
+ JSONObject cfg = new JSONObject().
+ element("name", baseName).
+ element("version", "0"). // unused but mandatory
+ element("url", t.toURI().toString()).
+ element("dependencies", new JSONArray());
+ new UpdateSite(UpdateCenter.ID_UPLOAD, null).new Plugin(UpdateCenter.ID_UPLOAD, cfg).deploy(true);
+ return new HttpRedirect("../updateCenter");
} catch (IOException e) {
throw e;
} catch (Exception e) {// grrr. fileItem.write throws this
@@ -749,6 +756,21 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas
}
}
+ protected String identifyPluginShortName(File t) {
+ try {
+ JarFile j = new JarFile(t);
+ try {
+ String name = j.getManifest().getMainAttributes().getValue("Short-Name");
+ if (name!=null) return name;
+ } finally {
+ j.close();
+ }
+ } catch (IOException e) {
+ LOGGER.log(WARNING, "Failed to identify the short name from "+t,e);
+ }
+ return FilenameUtils.getBaseName(t.getName()); // fall back to the base name of what's uploaded
+ }
+
public Descriptor getProxyDescriptor() {
return Jenkins.getInstance().getDescriptor(ProxyConfiguration.class);
}
@@ -775,7 +797,7 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas
Jenkins.getInstance().checkPermission(Jenkins.ADMINISTER);
List> jobs = new ArrayList>();
UpdateCenter uc = Jenkins.getInstance().getUpdateCenter();
- // XXX call uc.updateAllSites() when available? perhaps not, since we should not block on network here
+ // TODO call uc.updateAllSites() when available? perhaps not, since we should not block on network here
for (Map.Entry requestedPlugin : parseRequestedPlugins(configXml).entrySet()) {
PluginWrapper pw = getPlugin(requestedPlugin.getKey());
if (pw == null) { // install new
diff --git a/core/src/main/java/hudson/Util.java b/core/src/main/java/hudson/Util.java
index 1365bca2be..47ad0bc665 100644
--- a/core/src/main/java/hudson/Util.java
+++ b/core/src/main/java/hudson/Util.java
@@ -30,11 +30,9 @@ import edu.umd.cs.findbugs.annotations.SuppressWarnings;
import hudson.Proc.LocalProc;
import hudson.model.TaskListener;
import hudson.os.PosixAPI;
-import hudson.util.IOException2;
import hudson.util.QuotedStringTokenizer;
import hudson.util.VariableResolver;
import hudson.util.jna.WinIOException;
-import jenkins.model.Jenkins;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.time.FastDateFormat;
import org.apache.tools.ant.BuildException;
@@ -42,10 +40,9 @@ import org.apache.tools.ant.Project;
import org.apache.tools.ant.taskdefs.Chmod;
import org.apache.tools.ant.taskdefs.Copy;
import org.apache.tools.ant.types.FileSet;
-import org.jruby.ext.posix.FileStat;
-import org.jruby.ext.posix.POSIX;
+import jnr.posix.FileStat;
+import jnr.posix.POSIX;
import org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement;
-import org.kohsuke.stapler.Stapler;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
@@ -61,7 +58,6 @@ import java.nio.CharBuffer;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.nio.charset.CharsetEncoder;
-import java.security.DigestInputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.NumberFormat;
@@ -76,6 +72,7 @@ import java.util.regex.Pattern;
import hudson.util.jna.Kernel32Utils;
import static hudson.util.jna.GNUCLibrary.LIBC;
+import org.apache.commons.codec.digest.DigestUtils;
/**
* Various utility methods that don't have more proper home.
@@ -284,7 +281,7 @@ public class Util {
}
try {// try libc chmod
- POSIX posix = PosixAPI.get();
+ POSIX posix = PosixAPI.jnr();
String path = f.getAbsolutePath();
FileStat stat = posix.stat(path);
posix.chmod(path, stat.mode()|0200); // u+w
@@ -554,23 +551,10 @@ public class Util {
* The stream will be closed by this method at the end of this method.
* @return
* 32-char wide string
+ * @see DigestUtils#md5(InputStream)
*/
public static String getDigestOf(InputStream source) throws IOException {
- try {
- MessageDigest md5 = MessageDigest.getInstance("MD5");
-
- byte[] buffer = new byte[1024];
- DigestInputStream in =new DigestInputStream(source,md5);
- try {
- while(in.read(buffer)>=0)
- ; // simply discard the input
- } finally {
- in.close();
- }
- return toHexString(md5.digest());
- } catch (NoSuchAlgorithmException e) {
- throw new IOException2("MD5 not installed",e); // impossible
- }
+ return DigestUtils.md5Hex(source);
}
public static String getDigestOf(String text) {
@@ -1089,13 +1073,16 @@ public class Util {
} catch (LinkageError e) {
// if JNA is unavailable, fall back.
// we still prefer to try JNA first as PosixAPI supports even smaller platforms.
- if (PosixAPI.supportsNative()) {
- r = PosixAPI.get().symlink(targetPath,symlinkFile.getAbsolutePath());
+ POSIX posix = PosixAPI.jnr();
+ if (posix.isNative()) {
+ // TODO should we rethrow PosixException as IOException here?
+ r = posix.symlink(targetPath,symlinkFile.getAbsolutePath());
}
}
}
if (r==null) {
// if all else fail, fall back to the most expensive approach of forking a process
+ // TODO is this really necessary? JavaPOSIX should do this automatically
r = new LocalProc(new String[]{
"ln","-s", targetPath, symlinkPath},
new String[0],listener.getLogger(), baseDir).join();
@@ -1200,6 +1187,10 @@ public class Util {
} catch (ClassNotFoundException x3) {
assert false : x3; // should be Java 7+ here
}
+ if (x2.getClass().getName().equals("java.nio.file.FileSystemException")) {
+ // Thrown ("Incorrect function.") on JDK 7u21 in Windows 2012 when called on a non-symlink, rather than NotLinkException, contrary to documentation. Maybe only when not on NTFS?
+ return null;
+ }
if (x2 instanceof IOException) {
throw (IOException) x2;
}
@@ -1233,7 +1224,7 @@ public class Util {
} catch (LinkageError e) {
// if JNA is unavailable, fall back.
// we still prefer to try JNA first as PosixAPI supports even smaller platforms.
- return PosixAPI.get().readlink(filename);
+ return PosixAPI.jnr().readlink(filename);
}
}
diff --git a/core/src/main/java/hudson/WebAppMain.java b/core/src/main/java/hudson/WebAppMain.java
index f73bbe02c6..15ba27cd4b 100644
--- a/core/src/main/java/hudson/WebAppMain.java
+++ b/core/src/main/java/hudson/WebAppMain.java
@@ -341,8 +341,7 @@ public final class WebAppMain implements ServletContextListener {
// Logger is in the system classloader, so if we don't do this
// the whole web app will never be undepoyed.
- Logger.getLogger("hudson").removeHandler(handler);
- Logger.getLogger("jenkins").removeHandler(handler);
+ Logger.getLogger("").removeHandler(handler);
}
private static final Logger LOGGER = Logger.getLogger(WebAppMain.class.getName());
diff --git a/core/src/main/java/hudson/cli/ClientAuthenticationCache.java b/core/src/main/java/hudson/cli/ClientAuthenticationCache.java
index 25bc040d8b..3525599178 100644
--- a/core/src/main/java/hudson/cli/ClientAuthenticationCache.java
+++ b/core/src/main/java/hudson/cli/ClientAuthenticationCache.java
@@ -118,7 +118,7 @@ public class ClientAuthenticationCache implements Serializable {
}
// try to protect this file from other users, if we can.
- PosixAPI.get().chmod(f.getAbsolutePath(),0600);
+ PosixAPI.jnr().chmod(f.getAbsolutePath(),0600);
return null;
}
});
diff --git a/core/src/main/java/hudson/cli/CommandDuringBuild.java b/core/src/main/java/hudson/cli/CommandDuringBuild.java
index 7f1320ec8a..6f9230b3ee 100644
--- a/core/src/main/java/hudson/cli/CommandDuringBuild.java
+++ b/core/src/main/java/hudson/cli/CommandDuringBuild.java
@@ -39,17 +39,27 @@ import java.io.IOException;
*/
public abstract class CommandDuringBuild extends CLICommand {
/**
- * This method makes sense only when called from within the build kicked by Hudson.
- * We use the environment variables that Hudson sets to determine the build that is being run.
+ * This method makes sense only when called from within the build kicked by Jenkins.
+ * We use the environment variables that Jenkins sets to determine the build that is being run.
*/
protected Run getCurrentlyBuilding() throws CmdLineException {
+ Run r = optCurrentlyBuilding();
+ if (r==null)
+ throw new CmdLineException("This CLI command works only when invoked from inside a build");
+ return r;
+ }
+
+ /**
+ * If the command is currently running inside a build, return it. Otherwise null.
+ */
+ protected Run optCurrentlyBuilding() throws CmdLineException {
try {
CLICommand c = CLICommand.getCurrent();
if (c==null) throw new IllegalStateException("Not executing a CLI command");
String[] envs = c.checkChannel().call(new GetCharacteristicEnvironmentVariables());
if (envs[0]==null || envs[1]==null)
- throw new CmdLineException("This CLI command works only when invoked from inside a build");
+ return null;
Job j = Jenkins.getInstance().getItemByFullName(envs[0],Job.class);
if (j==null) throw new CmdLineException("No such job: "+envs[0]);
diff --git a/core/src/main/java/hudson/cli/SetBuildParameterCommand.java b/core/src/main/java/hudson/cli/SetBuildParameterCommand.java
new file mode 100644
index 0000000000..2862e9e8d7
--- /dev/null
+++ b/core/src/main/java/hudson/cli/SetBuildParameterCommand.java
@@ -0,0 +1,49 @@
+package hudson.cli;
+
+import hudson.Extension;
+import hudson.model.ParametersAction;
+import hudson.model.Run;
+import hudson.model.StringParameterValue;
+import org.kohsuke.args4j.Argument;
+
+import java.util.Collections;
+
+/**
+ * Used from the build to update the build variable.
+ *
+ * This allows one build step to affect the environment variables seen by later build steps.
+ *
+ * @author Kohsuke Kawaguchi
+ * @since 1.514
+ */
+@Extension
+public class SetBuildParameterCommand extends CommandDuringBuild {
+ @Argument(index=0, required=true, usage="Name of the build variable")
+ public String name;
+
+ @Argument(index=1,required=true, usage="Value of the build variable")
+ public String value;
+
+ @Override
+ public String getShortDescription() {
+ return Messages.SetBuildParameterCommand_ShortDescription();
+ }
+
+ @Override
+ protected int run() throws Exception {
+ Run r = getCurrentlyBuilding();
+
+ StringParameterValue p = new StringParameterValue(name, value);
+
+ ParametersAction a = r.getAction(ParametersAction.class);
+ if (a!=null) {
+ ParametersAction b = a.createUpdated(Collections.singleton(p));
+ r.addAction(b);
+ r.getActions().remove(a);
+ } else {
+ r.addAction(new ParametersAction(p));
+ }
+
+ return 0;
+ }
+}
diff --git a/core/src/main/java/hudson/init/impl/GroovyInitScript.java b/core/src/main/java/hudson/init/impl/GroovyInitScript.java
index cc365cb274..cf5da23407 100644
--- a/core/src/main/java/hudson/init/impl/GroovyInitScript.java
+++ b/core/src/main/java/hudson/init/impl/GroovyInitScript.java
@@ -31,12 +31,12 @@ import java.io.FileFilter;
import java.io.IOException;
import java.net.URL;
import java.util.Arrays;
-import java.util.Collections;
import java.util.logging.Logger;
import jenkins.model.Jenkins;
import static hudson.init.InitMilestone.JOB_LOADED;
import hudson.init.Initializer;
+import java.util.logging.Level;
/**
* Run the initialization script, if it exists.
@@ -78,9 +78,13 @@ public class GroovyInitScript {
execute(new GroovyCodeSource(initScript));
}
- private static void execute(GroovyCodeSource initScript) throws IOException {
+ private static void execute(GroovyCodeSource initScript) {
GroovyShell shell = new GroovyShell(Jenkins.getInstance().getPluginManager().uberClassLoader);
- shell.evaluate(initScript);
+ try {
+ shell.evaluate(initScript);
+ } catch (RuntimeException x) {
+ LOGGER.log(Level.WARNING, "Failed to run script " + initScript.getName(), x);
+ }
}
private static final Logger LOGGER = Logger.getLogger(GroovyInitScript.class.getName());
diff --git a/core/src/main/java/hudson/logging/LogRecorder.java b/core/src/main/java/hudson/logging/LogRecorder.java
index 1326f93c0f..df76fbbb5e 100644
--- a/core/src/main/java/hudson/logging/LogRecorder.java
+++ b/core/src/main/java/hudson/logging/LogRecorder.java
@@ -25,12 +25,20 @@ package hudson.logging;
import com.thoughtworks.xstream.XStream;
import hudson.BulkChange;
+import hudson.Extension;
+import hudson.FilePath;
import hudson.Util;
import hudson.XmlFile;
import hudson.model.AbstractModelObject;
+import hudson.model.Computer;
import jenkins.model.Jenkins;
import hudson.model.Saveable;
+import hudson.model.TaskListener;
import hudson.model.listeners.SaveableListener;
+import hudson.remoting.Callable;
+import hudson.remoting.Channel;
+import hudson.remoting.VirtualChannel;
+import hudson.slaves.ComputerListener;
import hudson.util.CopyOnWriteList;
import hudson.util.RingBufferLogHandler;
import hudson.util.XStream2;
@@ -43,12 +51,20 @@ import org.kohsuke.stapler.interceptor.RequirePOST;
import javax.servlet.ServletException;
import java.io.File;
import java.io.IOException;
+import java.text.Collator;
+import java.util.ArrayList;
import java.util.List;
import java.util.Arrays;
-import java.util.Locale;
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
+import org.kohsuke.accmod.Restricted;
+import org.kohsuke.accmod.restrictions.NoExternalUse;
/**
* Records a selected set of logs so that the system administrator
@@ -100,7 +116,7 @@ public class LogRecorder extends AbstractModelObject implements Saveable {
@DataBoundConstructor
public Target(String name, String level) {
- this(name,Level.parse(level.toUpperCase(Locale.ENGLISH)));
+ this(name,Level.parse(level));
}
public Level getLevel() {
@@ -110,11 +126,13 @@ public class LogRecorder extends AbstractModelObject implements Saveable {
public boolean includes(LogRecord r) {
if(r.getLevel().intValue() < level)
return false; // below the threshold
+ if (name.length() == 0) {
+ return true; // like root logger, includes everything
+ }
String logName = r.getLoggerName();
if(logName==null || !logName.startsWith(name))
return false; // not within this logger
-
- String rest = r.getLoggerName().substring(name.length());
+ String rest = logName.substring(name.length());
return rest.startsWith(".") || rest.length()==0;
}
@@ -132,6 +150,54 @@ public class LogRecorder extends AbstractModelObject implements Saveable {
Logger l = getLogger();
if(!l.isLoggable(getLevel()))
l.setLevel(getLevel());
+ new SetLevel(name, getLevel()).broadcast();
+ }
+
+ public void disable() {
+ getLogger().setLevel(null);
+ new SetLevel(name, null).broadcast();
+ }
+
+ }
+
+ private static final class SetLevel implements Callable {
+ /** known loggers (kept per slave), to avoid GC */
+ @SuppressWarnings("MismatchedQueryAndUpdateOfCollection") private static final Set loggers = new HashSet();
+ private final String name;
+ private final Level level;
+ SetLevel(String name, Level level) {
+ this.name = name;
+ this.level = level;
+ }
+ @Override public Void call() throws Error {
+ Logger logger = Logger.getLogger(name);
+ loggers.add(logger);
+ logger.setLevel(level);
+ return null;
+ }
+ void broadcast() {
+ for (Computer c : Jenkins.getInstance().getComputers()) {
+ if (c.getName().length() > 0) { // i.e. not master
+ VirtualChannel ch = c.getChannel();
+ if (ch != null) {
+ try {
+ ch.call(this);
+ } catch (Exception x) {
+ Logger.getLogger(LogRecorder.class.getName()).log(Level.WARNING, "could not set up logging on " + c, x);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ @Extension @Restricted(NoExternalUse.class) public static final class ComputerLogInitializer extends ComputerListener {
+ @Override public void preOnline(Computer c, Channel channel, FilePath root, TaskListener listener) throws IOException, InterruptedException {
+ for (LogRecorder recorder : Jenkins.getInstance().getLog().logRecorders.values()) {
+ for (Target t : recorder.targets) {
+ channel.call(new SetLevel(t.name, t.getLevel()));
+ }
+ }
}
}
@@ -215,7 +281,7 @@ public class LogRecorder extends AbstractModelObject implements Saveable {
// Disable logging for all our targets,
// then reenable all other loggers in case any also log the same targets
for (Target t : targets)
- t.getLogger().setLevel(null);
+ t.disable();
for (LogRecorder log : getParent().logRecorders.values())
for (Target t : log.targets)
t.enable();
@@ -243,6 +309,44 @@ public class LogRecorder extends AbstractModelObject implements Saveable {
return handler.getView();
}
+ /**
+ * Gets a view of log records per slave matching this recorder.
+ * @return a map (sorted by display name) from computer to (nonempty) list of log records
+ * @since 1.519
+ */
+ public Map> getSlaveLogRecords() {
+ Map> result = new TreeMap>(new Comparator() {
+ final Collator COLL = Collator.getInstance();
+ public int compare(Computer c1, Computer c2) {
+ return COLL.compare(c1.getDisplayName(), c2.getDisplayName());
+ }
+ });
+ for (Computer c : Jenkins.getInstance().getComputers()) {
+ if (c.getName().length() == 0) {
+ continue; // master
+ }
+ List recs = new ArrayList();
+ try {
+ for (LogRecord rec : c.getLogRecords()) {
+ for (Target t : targets) {
+ if (t.includes(rec)) {
+ recs.add(rec);
+ break;
+ }
+ }
+ }
+ } catch (IOException x) {
+ continue;
+ } catch (InterruptedException x) {
+ continue;
+ }
+ if (!recs.isEmpty()) {
+ result.put(c, recs);
+ }
+ }
+ return result;
+ }
+
/**
* Thread-safe reusable {@link XStream}.
*/
@@ -257,6 +361,5 @@ public class LogRecorder extends AbstractModelObject implements Saveable {
* Log levels that can be configured for {@link Target}.
*/
public static List LEVELS =
- Arrays.asList(Level.SEVERE, Level.WARNING, Level.INFO, Level.CONFIG,
- Level.FINE, Level.FINER, Level.FINEST, Level.ALL);
+ Arrays.asList(Level.ALL, Level.FINEST, Level.FINER, Level.FINE, Level.CONFIG, Level.INFO, Level.WARNING, Level.SEVERE);
}
diff --git a/core/src/main/java/hudson/markup/MyspacePolicy.java b/core/src/main/java/hudson/markup/MyspacePolicy.java
index f0543f9538..46d0c31e22 100644
--- a/core/src/main/java/hudson/markup/MyspacePolicy.java
+++ b/core/src/main/java/hudson/markup/MyspacePolicy.java
@@ -67,7 +67,6 @@ public class MyspacePolicy {
tag("img", "src",ONSITE_OR_OFFSITE_URL,
"hspace","vspace");
tag("iframe", "src");
- tag("link", "type","rel");
tag("ul,ol,li,dd,dl,dt,thead,tbody,tfoot");
tag("table", "noresize");
tag("td,th,tr");
diff --git a/core/src/main/java/hudson/matrix/Combination.java b/core/src/main/java/hudson/matrix/Combination.java
index 67a02584f9..76782a9df3 100644
--- a/core/src/main/java/hudson/matrix/Combination.java
+++ b/core/src/main/java/hudson/matrix/Combination.java
@@ -111,10 +111,18 @@ public final class Combination extends TreeMap implements Compara
* true.
*/
public boolean evalGroovyExpression(AxisList axes, String expression) {
+
+ return evalGroovyExpression(axes, expression, new Binding());
+ }
+
+ /**
+ * @see #evalGroovyExpression(AxisList, String)
+ * @since 1.515
+ */
+ public boolean evalGroovyExpression(AxisList axes, String expression, Binding binding) {
if(Util.fixEmptyAndTrim(expression)==null)
return true;
- Binding binding = new Binding();
for (Map.Entry e : entrySet())
binding.setVariable(e.getKey(),e.getValue());
diff --git a/core/src/main/java/hudson/matrix/DefaultMatrixExecutionStrategyImpl.java b/core/src/main/java/hudson/matrix/DefaultMatrixExecutionStrategyImpl.java
index 2a25fc2bf9..9541d907a5 100644
--- a/core/src/main/java/hudson/matrix/DefaultMatrixExecutionStrategyImpl.java
+++ b/core/src/main/java/hudson/matrix/DefaultMatrixExecutionStrategyImpl.java
@@ -1,5 +1,7 @@
package hudson.matrix;
+import groovy.lang.Binding;
+import groovy.lang.GroovyRuntimeException;
import hudson.AbortException;
import hudson.Extension;
import hudson.Util;
@@ -8,6 +10,7 @@ import hudson.matrix.MatrixBuild.MatrixBuildExecution;
import hudson.matrix.listeners.MatrixBuildListener;
import hudson.model.BuildListener;
import hudson.model.Cause.UpstreamCause;
+import hudson.model.ParameterValue;
import hudson.model.ParametersAction;
import hudson.model.Queue;
import hudson.model.ResourceController;
@@ -113,27 +116,21 @@ public class DefaultMatrixExecutionStrategyImpl extends MatrixExecutionStrategy
@Override
public Result run(MatrixBuildExecution execution) throws InterruptedException, IOException {
- MatrixBuild build = execution.getBuild();
- MatrixProject p = execution.getProject();
- PrintStream logger = execution.getListener().getLogger();
Collection touchStoneConfigurations = new HashSet();
Collection delayedConfigurations = new HashSet();
- for (MatrixConfiguration c: execution.getActiveConfigurations()) {
- if (!MatrixBuildListener.buildConfiguration(build, c))
- continue; // skip rebuild
- if (touchStoneCombinationFilter != null && c.getCombination().evalGroovyExpression(p.getAxes(), getTouchStoneCombinationFilter())) {
- touchStoneConfigurations.add(c);
- } else {
- delayedConfigurations.add(c);
- }
- }
+
+ filterConfigurations(
+ execution,
+ touchStoneConfigurations,
+ delayedConfigurations
+ );
if (notifyStartBuild(execution.getAggregators())) return Result.FAILURE;
if (sorter != null) {
touchStoneConfigurations = createTreeSet(touchStoneConfigurations, sorter);
- delayedConfigurations = createTreeSet(delayedConfigurations,sorter);
+ delayedConfigurations = createTreeSet(delayedConfigurations, sorter);
}
if(!runSequentially)
@@ -148,7 +145,9 @@ public class DefaultMatrixExecutionStrategyImpl extends MatrixExecutionStrategy
notifyEndBuild(run,execution.getAggregators());
r = r.combine(getResult(run));
}
-
+
+ PrintStream logger = execution.getListener().getLogger();
+
if (touchStoneResultCondition != null && r.isWorseThan(touchStoneResultCondition)) {
logger.printf("Touchstone configurations resulted in %s, so aborting...%n", r);
return r;
@@ -170,6 +169,71 @@ public class DefaultMatrixExecutionStrategyImpl extends MatrixExecutionStrategy
return r;
}
+ private void filterConfigurations(
+ final MatrixBuildExecution execution,
+ final Collection touchStoneConfigurations,
+ final Collection delayedConfigurations
+ ) throws AbortException {
+
+ final MatrixBuild build = execution.getBuild();
+
+ final String combinationFilter = execution.getProject().getCombinationFilter();
+ final String touchStoneFilter = getTouchStoneCombinationFilter();
+
+ try {
+
+ for (MatrixConfiguration c: execution.getActiveConfigurations()) {
+
+ if (!MatrixBuildListener.buildConfiguration(build, c)) continue; // skip rebuild
+
+ final Combination combination = c.getCombination();
+
+ if (touchStoneFilter != null && satisfies(execution, combination, touchStoneFilter)) {
+ touchStoneConfigurations.add(c);
+ } else if (satisfies(execution, combination, combinationFilter)) {
+ delayedConfigurations.add(c);
+ }
+ }
+ } catch (GroovyRuntimeException ex) {
+
+ PrintStream logger = execution.getListener().getLogger();
+ logger.println(ex.getMessage());
+ ex.printStackTrace(logger);
+ throw new AbortException("Failed executing combination filter");
+ }
+ }
+
+ private boolean satisfies(
+ final MatrixBuildExecution execution,
+ final Combination combination,
+ final String filter
+ ) {
+
+ return combination.evalGroovyExpression(
+ execution.getProject().getAxes(),
+ filter,
+ getConfiguredBinding(execution)
+ );
+ }
+
+ private Binding getConfiguredBinding(final MatrixBuildExecution execution) {
+
+ final Binding binding = new Binding();
+ final ParametersAction parameters = execution.getBuild().getAction(ParametersAction.class);
+
+ if (parameters == null) return binding;
+
+ for (final ParameterValue pv: parameters) {
+
+ if (pv == null) continue;
+ final String name = pv.getName();
+ final String value = pv.createVariableResolver(null).resolve(name);;
+ binding.setVariable(name, value);
+ }
+
+ return binding;
+ }
+
private Result getResult(@Nullable MatrixRun run) {
// null indicates that the run was cancelled before it even gets going
return run!=null ? run.getResult() : Result.ABORTED;
@@ -250,7 +314,7 @@ public class DefaultMatrixExecutionStrategyImpl extends MatrixExecutionStrategy
if(qi!=null) {
// if the build seems to be stuck in the queue, display why
String why = qi.getWhy();
- if(!why.equals(whyInQueue) && System.currentTimeMillis()-startTime>5000) {
+ if(why != null && !why.equals(whyInQueue) && System.currentTimeMillis()-startTime>5000) {
listener.getLogger().print("Configuration " + ModelHyperlinkNote.encodeTo(c)+" is still in the queue: ");
qi.getCauseOfBlockage().print(listener); //this is still shown on the same line
whyInQueue = why;
diff --git a/core/src/main/java/hudson/matrix/MatrixBuild.java b/core/src/main/java/hudson/matrix/MatrixBuild.java
index bcef17f888..5675842311 100644
--- a/core/src/main/java/hudson/matrix/MatrixBuild.java
+++ b/core/src/main/java/hudson/matrix/MatrixBuild.java
@@ -368,7 +368,8 @@ public class MatrixBuild extends AbstractBuild {
final int n = getNumber();
for (MatrixConfiguration c : activeConfigurations) {
for (Item i : q.getItems(c)) {
- if (i.getAction(ParentBuildAction.class).parent==getBuild()) {
+ ParentBuildAction a = i.getAction(ParentBuildAction.class);
+ if (a!=null && a.parent==getBuild()) {
q.cancel(i);
logger.println(Messages.MatrixBuild_Cancelled(ModelHyperlinkNote.encodeTo(c)));
}
diff --git a/core/src/main/java/hudson/matrix/MatrixConfiguration.java b/core/src/main/java/hudson/matrix/MatrixConfiguration.java
index 66516a0e33..b1c56409be 100644
--- a/core/src/main/java/hudson/matrix/MatrixConfiguration.java
+++ b/core/src/main/java/hudson/matrix/MatrixConfiguration.java
@@ -232,7 +232,10 @@ public class MatrixConfiguration extends Project
MatrixBuild lb = getParent().getLastBuild();
for (Action a : actions) {
if (a instanceof ParentBuildAction) {
- lb = ((ParentBuildAction) a).parent;
+ MatrixBuild _lb = ((ParentBuildAction) a).parent;
+ if (_lb != null) {
+ lb = _lb;
+ }
}
}
diff --git a/core/src/main/java/hudson/matrix/MatrixProject.java b/core/src/main/java/hudson/matrix/MatrixProject.java
index c3918fb784..7dae077835 100644
--- a/core/src/main/java/hudson/matrix/MatrixProject.java
+++ b/core/src/main/java/hudson/matrix/MatrixProject.java
@@ -41,6 +41,7 @@ import hudson.model.Descriptor;
import hudson.model.Descriptor.FormException;
import hudson.model.Node;
import hudson.slaves.WorkspaceList;
+import hudson.util.AlternativeUiTextProvider;
import jenkins.model.Jenkins;
import hudson.model.Item;
import hudson.model.ItemGroup;
@@ -48,6 +49,8 @@ import hudson.model.Items;
import hudson.model.JDK;
import hudson.model.Job;
import hudson.model.Label;
+import hudson.model.ParameterDefinition;
+import hudson.model.ParametersDefinitionProperty;
import hudson.model.Queue.FlyweightTask;
import hudson.model.Result;
import hudson.model.SCMedItem;
@@ -89,6 +92,8 @@ import java.util.Map.Entry;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
/**
* {@link Job} that allows you to run multiple different configurations
@@ -190,6 +195,11 @@ public class MatrixProject extends AbstractProject im
super(parent, name);
}
+ @Override
+ public String getPronoun() {
+ return AlternativeUiTextProvider.get(PRONOUN, this, Messages.MatrixProject_Pronoun());
+ }
+
/**
* Gets the workspace location that {@link MatrixConfiguration} uses.
*
@@ -603,9 +613,11 @@ public class MatrixProject extends AbstractProject im
}
// find all active configurations
- Set active = new LinkedHashSet();
+ final Set active = new LinkedHashSet();
+ final boolean isDynamicFilter = isDynamicFilter(getCombinationFilter());
+
for (Combination c : activeCombinations) {
- if(c.evalGroovyExpression(axes,combinationFilter)) {
+ if(isDynamicFilter || c.evalGroovyExpression(axes,getCombinationFilter())) {
LOGGER.fine("Adding configuration: " + c);
MatrixConfiguration config = configurations.get(c);
if(config==null) {
@@ -622,6 +634,27 @@ public class MatrixProject extends AbstractProject im
return active;
}
+ private boolean isDynamicFilter(final String filter) {
+
+ if (!isParameterized() || filter == null) return false;
+
+ final ParametersDefinitionProperty paramDefProp = getProperty(ParametersDefinitionProperty.class);
+
+ for (final ParameterDefinition definition : paramDefProp.getParameterDefinitions()) {
+
+ final String name = definition.getName();
+
+ final Matcher matcher = Pattern
+ .compile("\\b" + name + "\\b")
+ .matcher(filter)
+ ;
+
+ if (matcher.find()) return true;
+ }
+
+ return false;
+ }
+
private File getConfigurationsDir() {
return new File(getRootDir(),"configurations");
}
diff --git a/core/src/main/java/hudson/model/AbstractBuild.java b/core/src/main/java/hudson/model/AbstractBuild.java
index e4ff4b86c1..b553723bf3 100644
--- a/core/src/main/java/hudson/model/AbstractBuild.java
+++ b/core/src/main/java/hudson/model/AbstractBuild.java
@@ -487,9 +487,9 @@ public abstract class AbstractBuild
,R extends Abs
private void createSymlink(TaskListener listener, String name, Permalink target) throws InterruptedException {
String targetDir;
if (getProject().getBuildDir().equals(new File(getProject().getRootDir(), "builds"))) {
- targetDir = "builds/" + target.getId();
+ targetDir = "builds" + File.separator + target.getId();
} else {
- targetDir = getProject().getBuildDir()+"/"+target.getId();
+ targetDir = getProject().getBuildDir() + File.separator + target.getId();
}
Util.createSymlink(getProject().getRootDir(), targetDir, name, listener);
}
diff --git a/core/src/main/java/hudson/model/AbstractItem.java b/core/src/main/java/hudson/model/AbstractItem.java
index 436e62dfe4..018049a50b 100644
--- a/core/src/main/java/hudson/model/AbstractItem.java
+++ b/core/src/main/java/hudson/model/AbstractItem.java
@@ -52,8 +52,6 @@ import org.kohsuke.stapler.export.ExportedBean;
import java.io.File;
import java.io.IOException;
import java.util.Collection;
-import java.util.HashMap;
-import java.util.Map;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
@@ -327,6 +325,18 @@ public abstract class AbstractItem extends Actionable implements Item, HttpDelet
else return n+" \u00BB "+getDisplayName();
}
+ /**
+ * Gets the display name of the current item relative to the given group.
+ *
+ * @since 1.515
+ * @param p the ItemGroup used as point of reference for the item
+ * @return
+ * String like "foo » bar"
+ */
+ public String getRelativeDisplayNameFrom(ItemGroup p) {
+ return Functions.getRelativeDisplayNameFrom(this, p);
+ }
+
/**
* This method only exists to disambiguate {@link #getRelativeNameFrom(ItemGroup)} and {@link #getRelativeNameFrom(Item)}
* @since 1.512
@@ -344,38 +354,7 @@ public abstract class AbstractItem extends Actionable implements Item, HttpDelet
* Nested ItemGroups are separated by / character.
*/
public String getRelativeNameFrom(ItemGroup p) {
- if (p == null) return getFullName();
- // first list up all the parents
- Map parents = new HashMap();
- int depth=0;
- while (p!=null) {
- parents.put(p, depth++);
- if (p instanceof Item)
- p = ((Item)p).getParent();
- else
- p = null;
- }
-
- StringBuilder buf = new StringBuilder();
- Item i=this;
- while (true) {
- if (buf.length()>0) buf.insert(0,'/');
- buf.insert(0,i.getName());
- ItemGroup g = i.getParent();
-
- Integer d = parents.get(g);
- if (d!=null) {
- String s="";
- for (int j=d; j>0; j--)
- s+="../";
- return s+buf;
- }
-
- if (g instanceof Item)
- i = (Item)g;
- else
- return null;
- }
+ return Functions.getRelativeNameFrom(this, p);
}
public String getRelativeNameFrom(Item item) {
@@ -422,7 +401,9 @@ public abstract class AbstractItem extends Actionable implements Item, HttpDelet
}
public String getShortUrl() {
- return getParent().getUrlChildPrefix()+'/'+Util.rawEncode(getName())+'/';
+ String prefix = getParent().getUrlChildPrefix();
+ String subdir = Util.rawEncode(getName());
+ return prefix.equals(".") ? subdir + '/' : prefix + '/' + subdir + '/';
}
public String getSearchUrl() {
diff --git a/core/src/main/java/hudson/model/AbstractProject.java b/core/src/main/java/hudson/model/AbstractProject.java
index 239d6ffaed..14571c3503 100644
--- a/core/src/main/java/hudson/model/AbstractProject.java
+++ b/core/src/main/java/hudson/model/AbstractProject.java
@@ -1798,8 +1798,11 @@ public abstract class AbstractProject
,R extends A
if (!isBuildable())
throw HttpResponses.error(SC_INTERNAL_SERVER_ERROR,new IOException(getFullName()+" is not buildable"));
- Jenkins.getInstance().getQueue().schedule(this, (int)delay.getTime(), getBuildCause(req));
- rsp.sendRedirect(".");
+ WaitingItem item = Jenkins.getInstance().getQueue().schedule(this, (int) delay.getTime(), getBuildCause(req));
+ if (item!=null) {
+ rsp.sendRedirect(SC_CREATED,req.getContextPath()+'/'+item.getUrl());
+ } else
+ rsp.sendRedirect(".");
}
/**
diff --git a/core/src/main/java/hudson/model/Cause.java b/core/src/main/java/hudson/model/Cause.java
index 4ccab03338..187bed2ce4 100644
--- a/core/src/main/java/hudson/model/Cause.java
+++ b/core/src/main/java/hudson/model/Cause.java
@@ -148,6 +148,39 @@ public abstract class Cause {
this.upstreamCauses = upstreamCauses;
}
+ /**
+ * @since 1.515
+ */
+ @Override
+ public boolean equals(Object rhs) {
+
+ if (this == rhs) return true;
+
+ if (!(rhs instanceof UpstreamCause)) return false;
+
+ final UpstreamCause o = (UpstreamCause) rhs;
+
+ if (upstreamBuild != o.upstreamBuild) return false;
+ if (!upstreamCauses.equals(o.upstreamCauses)) return false;
+ if (upstreamUrl == null ? o.upstreamUrl != null : !upstreamUrl.equals(o.upstreamUrl)) return false;
+ if (upstreamProject == null ? o.upstreamProject != null : !upstreamProject.equals(o.upstreamProject)) return false;
+
+ return true;
+ }
+
+ /**
+ * @since 1.515
+ */
+ @Override
+ public int hashCode() {
+
+ int hashCode = 17;
+ hashCode = hashCode * 31 + upstreamCauses.hashCode();
+ hashCode = hashCode * 31 + upstreamBuild;
+ hashCode = hashCode * 31 + (upstreamUrl == null ? 0 : upstreamUrl.hashCode ());
+ return hashCode * 31 + (upstreamProject == null ? 0 : upstreamProject.hashCode ());
+ }
+
private @Nonnull Cause trim(@Nonnull Cause c, int depth, Set traversed) {
if (!(c instanceof UpstreamCause)) {
return c;
diff --git a/core/src/main/java/hudson/model/CauseAction.java b/core/src/main/java/hudson/model/CauseAction.java
index e29dfeed57..c3a967932b 100644
--- a/core/src/main/java/hudson/model/CauseAction.java
+++ b/core/src/main/java/hudson/model/CauseAction.java
@@ -35,9 +35,10 @@ import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
+import jenkins.model.RunAction2;
@ExportedBean
-public class CauseAction implements FoldableAction, RunAction {
+public class CauseAction implements FoldableAction, RunAction2 {
/**
* @deprecated since 2009-02-28
*/
@@ -105,18 +106,14 @@ public class CauseAction implements FoldableAction, RunAction {
return causes.get(0).getShortDescription();
}
- public void onLoad() {
- // noop
- }
-
- public void onBuildComplete() {
+ @Override public void onLoad(Run,?> r) {
// noop
}
/**
* When hooked up to build, notify {@link Cause}s.
*/
- public void onAttached(Run owner) {
+ @Override public void onAttached(Run,?> owner) {
if (owner instanceof AbstractBuild) {// this should be always true but being defensive here
AbstractBuild b = (AbstractBuild) owner;
for (Cause c : causes) {
diff --git a/core/src/main/java/hudson/model/Computer.java b/core/src/main/java/hudson/model/Computer.java
index cc0edeb367..0778741435 100644
--- a/core/src/main/java/hudson/model/Computer.java
+++ b/core/src/main/java/hudson/model/Computer.java
@@ -51,8 +51,6 @@ import hudson.slaves.RetentionStrategy;
import hudson.slaves.WorkspaceList;
import hudson.slaves.OfflineCause;
import hudson.slaves.OfflineCause.ByCLI;
-import hudson.tasks.BuildWrapper;
-import hudson.tasks.Publisher;
import hudson.util.DaemonThreadFactory;
import hudson.util.EditDistance;
import hudson.util.ExceptionCatchingThreadFactory;
@@ -1157,20 +1155,7 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces
}
protected void _doScript( StaplerRequest req, StaplerResponse rsp, String view) throws IOException, ServletException {
- // ability to run arbitrary script is dangerous
- checkPermission(Jenkins.RUN_SCRIPTS);
-
- String text = req.getParameter("script");
- if(text!=null) {
- try {
- req.setAttribute("output",
- RemotingDiagnostics.executeGroovy(text,getChannel()));
- } catch (InterruptedException e) {
- throw new ServletException(e);
- }
- }
-
- req.getView(this,view).forward(req, rsp);
+ Jenkins._doScript(req, rsp, req.getView(this, view), getChannel(), getACL());
}
/**
diff --git a/core/src/main/java/hudson/model/ComputerSet.java b/core/src/main/java/hudson/model/ComputerSet.java
index 12ad63c941..dc1436d3f3 100644
--- a/core/src/main/java/hudson/model/ComputerSet.java
+++ b/core/src/main/java/hudson/model/ComputerSet.java
@@ -426,8 +426,8 @@ public final class ComputerSet extends AbstractModelObject implements Describabl
r.add(i);
}
monitors.replaceBy(r.toList());
- } catch (IOException e) {
- LOGGER.log(Level.WARNING, "Failed to instanciate NodeMonitors",e);
+ } catch (Throwable x) {
+ LOGGER.log(Level.WARNING, "Failed to instantiate NodeMonitors", x);
}
}
diff --git a/core/src/main/java/hudson/model/Descriptor.java b/core/src/main/java/hudson/model/Descriptor.java
index 414e4124f5..5b9d244b1a 100644
--- a/core/src/main/java/hudson/model/Descriptor.java
+++ b/core/src/main/java/hudson/model/Descriptor.java
@@ -223,7 +223,22 @@ public abstract class Descriptor> implements Saveable {
*
* @see #getHelpFile(String)
*/
- private transient final Map helpRedirect = new HashMap();
+ private transient final Map helpRedirect = new HashMap();
+
+ private static class HelpRedirect {
+ private final Class extends Describable> owner;
+ private final String fieldNameToRedirectTo;
+
+ private HelpRedirect(Class extends Describable> owner, String fieldNameToRedirectTo) {
+ this.owner = owner;
+ this.fieldNameToRedirectTo = fieldNameToRedirectTo;
+ }
+
+ private String resolve() {
+ // the resolution has to be deferred to avoid ordering issue among descriptor registrations.
+ return Jenkins.getInstance().getDescriptor(owner).getHelpFile(fieldNameToRedirectTo);
+ }
+ }
/**
*
@@ -657,8 +672,8 @@ public abstract class Descriptor> implements Saveable {
}
public String getHelpFile(Klass> clazz, String fieldName) {
- String v = helpRedirect.get(fieldName);
- if (v!=null) return v;
+ HelpRedirect r = helpRedirect.get(fieldName);
+ if (r!=null) return r.resolve();
for (Klass> c : clazz.getAncestors()) {
String page = "/descriptor/" + getId() + "/help";
@@ -688,8 +703,7 @@ public abstract class Descriptor> implements Saveable {
* @since 1.425
*/
protected void addHelpFileRedirect(String fieldName, Class extends Describable> owner, String fieldNameToRedirectTo) {
- helpRedirect.put(fieldName,
- Jenkins.getInstance().getDescriptor(owner).getHelpFile(fieldNameToRedirectTo));
+ helpRedirect.put(fieldName, new HelpRedirect(owner,fieldNameToRedirectTo));
}
/**
diff --git a/core/src/main/java/hudson/model/DisplayNameListener.java b/core/src/main/java/hudson/model/DisplayNameListener.java
index 93ddbdea05..ed5cf71495 100644
--- a/core/src/main/java/hudson/model/DisplayNameListener.java
+++ b/core/src/main/java/hudson/model/DisplayNameListener.java
@@ -49,8 +49,8 @@ public class DisplayNameListener extends ItemListener {
* displayed.
*/
public void onCopied(Item src, Item item) {
- // bug 5056825 - Display name field should be cleared when you copy a job.
- if(item instanceof AbstractItem) {
+ // bug 5056825 - Display name field should be cleared when you copy a job within the same folder.
+ if(item instanceof AbstractItem && src.getParent() == item.getParent()) {
AbstractItem dest = (AbstractItem)item;
try {
dest.setDisplayName(null);
diff --git a/core/src/main/java/hudson/model/Executor.java b/core/src/main/java/hudson/model/Executor.java
index ee4eddec36..b99b6fbb02 100644
--- a/core/src/main/java/hudson/model/Executor.java
+++ b/core/src/main/java/hudson/model/Executor.java
@@ -49,6 +49,7 @@ import org.kohsuke.stapler.export.Exported;
import javax.servlet.ServletException;
import java.io.IOException;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
import java.util.Vector;
import java.util.logging.Logger;
@@ -135,6 +136,9 @@ public class Executor extends Thread implements ModelObject {
* Interrupt the execution. Mark the cause and the status accordingly.
*/
public void interrupt(Result result, CauseOfInterruption... causes) {
+ if (LOGGER.isLoggable(FINE))
+ LOGGER.log(FINE, String.format("%s is interrupted(%s): %s", getDisplayName(), result, Util.join(Arrays.asList(causes),",")), new InterruptedException());
+
interruptStatus = result;
synchronized (this.causes) {
for (CauseOfInterruption c : causes) {
@@ -210,6 +214,7 @@ public class Executor extends Thread implements ModelObject {
task = workUnit.work;
startTime = System.currentTimeMillis();
executable = task.createExecutable();
+ workUnit.setExecutable(executable);
}
if (LOGGER.isLoggable(FINE))
LOGGER.log(FINE, getName()+" is going to execute "+executable);
diff --git a/core/src/main/java/hudson/model/FileParameterDefinition.java b/core/src/main/java/hudson/model/FileParameterDefinition.java
index 5cc3f78b3a..761a179d98 100644
--- a/core/src/main/java/hudson/model/FileParameterDefinition.java
+++ b/core/src/main/java/hudson/model/FileParameterDefinition.java
@@ -86,12 +86,21 @@ public class FileParameterDefinition extends ParameterDefinition {
// the requested file parameter wasn't uploaded
return null;
}
- FileParameterValue p = new FileParameterValue(getName(), src);
+ FileParameterValue p = new FileParameterValue(getName(), src, getFileName(src.getName()));
p.setDescription(getDescription());
p.setLocation(getName());
return p;
}
+ /**
+ * Strip off the path portion if the given path contains it.
+ */
+ private String getFileName(String possiblyPathName) {
+ possiblyPathName = possiblyPathName.substring(possiblyPathName.lastIndexOf('/')+1);
+ possiblyPathName = possiblyPathName.substring(possiblyPathName.lastIndexOf('\\')+1);
+ return possiblyPathName;
+ }
+
@Override
public ParameterValue createValue(CLICommand command, String value) throws IOException, InterruptedException {
// capture the file to the server
diff --git a/core/src/main/java/hudson/model/FileParameterValue.java b/core/src/main/java/hudson/model/FileParameterValue.java
index 4abddab70b..e5bfda9881 100644
--- a/core/src/main/java/hudson/model/FileParameterValue.java
+++ b/core/src/main/java/hudson/model/FileParameterValue.java
@@ -66,6 +66,9 @@ public class FileParameterValue extends ParameterValue {
*/
private final String originalFileName;
+ /**
+ * Overrides the location in the build to place this file. Initially set to {@link #getName()}
+ */
private String location;
@DataBoundConstructor
@@ -81,6 +84,7 @@ public class FileParameterValue extends ParameterValue {
super(name);
this.file = file;
this.originalFileName = originalFileName;
+ setLocation(name);
}
// post initialization hook
@@ -129,7 +133,7 @@ public class FileParameterValue extends ParameterValue {
return new BuildWrapper() {
@Override
public Environment setUp(AbstractBuild build, Launcher launcher, BuildListener listener) throws IOException, InterruptedException {
- if (!StringUtils.isEmpty(file.getName())) {
+ if (!StringUtils.isEmpty(location)) {
listener.getLogger().println("Copying file to "+location);
FilePath locationFilePath = build.getWorkspace().child(location);
locationFilePath.getParent().mkdirs();
diff --git a/core/src/main/java/hudson/model/Fingerprint.java b/core/src/main/java/hudson/model/Fingerprint.java
index ab6989ba09..c1b40239aa 100644
--- a/core/src/main/java/hudson/model/Fingerprint.java
+++ b/core/src/main/java/hudson/model/Fingerprint.java
@@ -24,11 +24,11 @@
package hudson.model;
import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableListMultimap;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.converters.Converter;
import com.thoughtworks.xstream.converters.MarshallingContext;
import com.thoughtworks.xstream.converters.UnmarshallingContext;
+import com.thoughtworks.xstream.converters.basic.DateConverter;
import com.thoughtworks.xstream.converters.collections.CollectionConverter;
import com.thoughtworks.xstream.io.HierarchicalStreamReader;
import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
@@ -51,6 +51,7 @@ import org.kohsuke.stapler.export.ExportedBean;
import java.io.File;
import java.io.IOException;
+import java.io.PrintWriter;
import java.util.AbstractCollection;
import java.util.ArrayList;
import java.util.Collection;
@@ -60,7 +61,9 @@ import java.util.Date;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
+import java.util.Map;
import java.util.Map.Entry;
+import java.util.TreeMap;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -180,6 +183,10 @@ public class Fingerprint implements ModelObject, Saveable {
return false;
}
+
+ @Override public String toString() {
+ return name + " #" + number;
+ }
}
/**
@@ -503,7 +510,10 @@ public class Fingerprint implements ModelObject, Saveable {
public void marshal(Object source, HierarchicalStreamWriter writer, MarshallingContext context) {
RangeSet src = (RangeSet) source;
+ writer.setValue(serialize(src));
+ }
+ static String serialize(RangeSet src) {
StringBuilder buf = new StringBuilder(src.ranges.size()*10);
for (Range r : src.ranges) {
if(buf.length()>0) buf.append(',');
@@ -512,7 +522,7 @@ public class Fingerprint implements ModelObject, Saveable {
else
buf.append(r.start).append('-').append(r.end-1);
}
- writer.setValue(buf.toString());
+ return buf.toString();
}
public Object unmarshal(HierarchicalStreamReader reader, final UnmarshallingContext context) {
@@ -555,6 +565,8 @@ public class Fingerprint implements ModelObject, Saveable {
}
}
}
+
+ private static final DateConverter DATE_CONVERTER = new DateConverter();
private final Date timestamp;
@@ -573,7 +585,7 @@ public class Fingerprint implements ModelObject, Saveable {
*/
private final Hashtable usages = new Hashtable();
- private PersistedList facets = new PersistedList(this);
+ PersistedList facets = new PersistedList(this);
/**
* Lazily computed immutable {@link FingerprintFacet}s created from {@link TransientFingerprintFacetFactory}.
@@ -581,11 +593,15 @@ public class Fingerprint implements ModelObject, Saveable {
private transient volatile List transientFacets = null;
public Fingerprint(Run build, String fileName, byte[] md5sum) throws IOException {
- this.original = build==null ? null : new BuildPtr(build);
+ this(build==null ? null : new BuildPtr(build), fileName, md5sum);
+ save();
+ }
+
+ Fingerprint(BuildPtr original, String fileName, byte[] md5sum) {
+ this.original = original;
this.md5sum = md5sum;
this.fileName = fileName;
this.timestamp = new Date();
- save();
}
/**
@@ -703,7 +719,12 @@ public class Fingerprint implements ModelObject, Saveable {
* Records that a build of a job has used this file.
*/
public synchronized void add(String jobFullName, int n) throws IOException {
- synchronized(usages) {
+ addWithoutSaving(jobFullName, n);
+ save();
+ }
+
+ void addWithoutSaving(String jobFullName, int n) {
+ synchronized(usages) { // TODO why not synchronized (this) like some, though not all, other accesses?
RangeSet r = usages.get(jobFullName);
if(r==null) {
r = new RangeSet();
@@ -711,7 +732,6 @@ public class Fingerprint implements ModelObject, Saveable {
}
r.add(n);
}
- save();
}
/**
@@ -838,13 +858,64 @@ public class Fingerprint implements ModelObject, Saveable {
start = System.currentTimeMillis();
File file = getFingerprintFile(md5sum);
- getConfigFile(file).write(this);
+ save(file);
SaveableListener.fireOnChange(this, getConfigFile(file));
if(logger.isLoggable(Level.FINE))
logger.fine("Saving fingerprint "+file+" took "+(System.currentTimeMillis()-start)+"ms");
}
+ void save(File file) throws IOException {
+ if (facets.isEmpty()) {
+ file.getParentFile().mkdirs();
+ // JENKINS-16301: fast path for the common case.
+ PrintWriter w = new PrintWriter(file, "UTF-8");
+ try {
+ w.println("");
+ w.println("");
+ w.print(" ");
+ w.print(DATE_CONVERTER.toString(timestamp));
+ w.println("");
+ if (original != null) {
+ w.println(" ");
+ w.print(" ");
+ w.print(original.name);
+ w.println("");
+ w.print(" ");
+ w.print(original.number);
+ w.println("");
+ w.println(" ");
+ }
+ w.print(" ");
+ w.print(Util.toHexString(md5sum));
+ w.println("");
+ w.print(" ");
+ w.print(fileName);
+ w.println("");
+ w.println(" ");
+ for (Map.Entry e : usages.entrySet()) {
+ w.println(" ");
+ w.print(" ");
+ w.print(e.getKey());
+ w.println("");
+ w.print(" ");
+ w.print(RangeSet.ConverterImpl.serialize(e.getValue()));
+ w.println("");
+ w.println(" ");
+ }
+ w.println(" ");
+ w.println(" ");
+ w.print("");
+ w.flush();
+ } finally {
+ w.close();
+ }
+ } else {
+ // Slower fallback that can persist facets.
+ getConfigFile(file).write(this);
+ }
+ }
+
/**
* Update references to a renamed job in the fingerprint
*/
@@ -932,6 +1003,10 @@ public class Fingerprint implements ModelObject, Saveable {
}
}
+ @Override public String toString() {
+ return "Fingerprint[original=" + original + ",hash=" + getHashString() + ",fileName=" + fileName + ",timestamp=" + DATE_CONVERTER.toString(timestamp) + ",usages=" + new TreeMap(usages) + ",facets=" + facets + "]";
+ }
+
private static final XStream XSTREAM = new XStream2();
static {
XSTREAM.alias("fingerprint",Fingerprint.class);
diff --git a/core/src/main/java/hudson/model/ItemGroup.java b/core/src/main/java/hudson/model/ItemGroup.java
index 9c7fe37d7c..a87814eb9d 100644
--- a/core/src/main/java/hudson/model/ItemGroup.java
+++ b/core/src/main/java/hudson/model/ItemGroup.java
@@ -26,6 +26,7 @@ package hudson.model;
import java.io.IOException;
import java.util.Collection;
import java.io.File;
+import javax.annotation.CheckForNull;
/**
* Represents a grouping inherent to a kind of {@link Item}s.
@@ -64,9 +65,9 @@ public interface ItemGroup extends PersistenceRoot, ModelObject
String getUrlChildPrefix();
/**
- * Gets the {@link Item} inside this group that has a given name.
+ * Gets the {@link Item} inside this group that has a given name, or null if it does not exist.
*/
- T getItem(String name);
+ @CheckForNull T getItem(String name);
/**
* Assigns the {@link Item#getRootDir() root directory} for children.
diff --git a/core/src/main/java/hudson/model/JDK.java b/core/src/main/java/hudson/model/JDK.java
index 73ca0c9db1..dbf9b2dcad 100644
--- a/core/src/main/java/hudson/model/JDK.java
+++ b/core/src/main/java/hudson/model/JDK.java
@@ -149,7 +149,7 @@ public final class JDK extends ToolInstallation implements NodeSpecific, En
public static class DescriptorImpl extends ToolDescriptor {
public String getDisplayName() {
- return "JDK"; // XXX I18N
+ return "JDK"; // TODO I18N
}
public @Override JDK[] getInstallations() {
diff --git a/core/src/main/java/hudson/model/Job.java b/core/src/main/java/hudson/model/Job.java
index 8b5f2906f6..9b351e35f8 100644
--- a/core/src/main/java/hudson/model/Job.java
+++ b/core/src/main/java/hudson/model/Job.java
@@ -26,7 +26,6 @@ package hudson.model;
import com.google.common.base.Function;
import com.google.common.collect.Collections2;
import com.infradna.tool.bridge_method_injector.WithBridgeMethods;
-import hudson.BulkChange;
import hudson.EnvVars;
import hudson.Extension;
import hudson.ExtensionPoint;
@@ -64,7 +63,6 @@ import hudson.widgets.Widget;
import jenkins.model.BuildDiscarder;
import jenkins.model.Jenkins;
import jenkins.model.ProjectNamingStrategy;
-import jenkins.scm.SCMCheckoutStrategy;
import jenkins.security.HexStringConfidentialKey;
import jenkins.util.io.OnMaster;
import net.sf.json.JSONException;
@@ -809,11 +807,7 @@ public abstract class Job, RunT extends Run, RunT extends Run, RunT extends Run, RunT extends Run, RunT extends Run jobNames = new TreeSet(CaseInsensitiveComparator.INSTANCE);
+ /*package*/ /*almost-final*/ SortedSet jobNames = new TreeSet(CaseInsensitiveComparator.INSTANCE);
private DescribableList> jobFilters;
@@ -106,8 +108,17 @@ public class ListView extends View implements Saveable {
}
private Object readResolve() {
- if(includeRegex!=null)
- includePattern = Pattern.compile(includeRegex);
+ if(includeRegex!=null) {
+ try {
+ includePattern = Pattern.compile(includeRegex);
+ } catch (PatternSyntaxException x) {
+ includeRegex = null;
+ OldDataMonitor.report(this, Collections.singleton(x));
+ }
+ }
+ if (jobNames == null) {
+ jobNames = new TreeSet(CaseInsensitiveComparator.INSTANCE);
+ }
initColumns();
initJobFilters();
return this;
@@ -186,15 +197,18 @@ public class ListView extends View implements Saveable {
includeItems(parent, parent, names);
}
- private void includeItems(ItemGroup extends TopLevelItem> root, ItemGroup extends TopLevelItem> parent, SortedSet names) {
+ private void includeItems(ItemGroup extends TopLevelItem> root, ItemGroup> parent, SortedSet names) {
if (includePattern != null) {
for (Item item : parent.getItems()) {
if (recurse && item instanceof ItemGroup) {
- includeItems(root, (ItemGroup extends TopLevelItem>)item, names);
+ ItemGroup> ig = (ItemGroup>) item;
+ includeItems(root, ig, names);
}
- String itemName = item.getRelativeNameFrom(root);
- if (includePattern.matcher(itemName).matches()) {
- names.add(itemName);
+ if (item instanceof TopLevelItem) {
+ String itemName = item.getRelativeNameFrom(root);
+ if (includePattern.matcher(itemName).matches()) {
+ names.add(itemName);
+ }
}
}
}
@@ -226,6 +240,13 @@ public class ListView extends View implements Saveable {
public boolean isRecurse() {
return recurse;
}
+
+ /*
+ * For testing purposes
+ */
+ void setRecurse(boolean recurse) {
+ this.recurse = recurse;
+ }
/**
* Filter by enabled/disabled status of jobs.
diff --git a/core/src/main/java/hudson/model/Node.java b/core/src/main/java/hudson/model/Node.java
index fa34001043..fe0a9dbcc8 100644
--- a/core/src/main/java/hudson/model/Node.java
+++ b/core/src/main/java/hudson/model/Node.java
@@ -62,6 +62,7 @@ import javax.annotation.CheckForNull;
import jenkins.model.Jenkins;
import jenkins.util.io.OnMaster;
import net.sf.json.JSONObject;
+import org.jvnet.localizer.Localizable;
import org.kohsuke.stapler.BindInterceptor;
import org.kohsuke.stapler.Stapler;
import org.kohsuke.stapler.StaplerRequest;
@@ -442,20 +443,20 @@ public abstract class Node extends AbstractModelObject implements Reconfigurable
* Constants that control how Hudson allocates jobs to slaves.
*/
public enum Mode {
- NORMAL(Messages.Node_Mode_NORMAL()),
- EXCLUSIVE(Messages.Node_Mode_EXCLUSIVE());
+ NORMAL(Messages._Node_Mode_NORMAL()),
+ EXCLUSIVE(Messages._Node_Mode_EXCLUSIVE());
- private final String description;
+ private final Localizable description;
public String getDescription() {
- return description;
+ return description.toString();
}
public String getName() {
return name();
}
- Mode(String description) {
+ Mode(Localizable description) {
this.description = description;
}
diff --git a/core/src/main/java/hudson/model/ParametersAction.java b/core/src/main/java/hudson/model/ParametersAction.java
index d52c7ec598..132e1014ea 100644
--- a/core/src/main/java/hudson/model/ParametersAction.java
+++ b/core/src/main/java/hudson/model/ParametersAction.java
@@ -36,6 +36,7 @@ import hudson.util.VariableResolver;
import org.kohsuke.stapler.export.Exported;
import org.kohsuke.stapler.export.ExportedBean;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
@@ -159,6 +160,29 @@ public class ParametersAction implements Action, Iterable, Queue
}
}
+ /**
+ * Creates a new {@link ParametersAction} that contains all the parameters in this action
+ * with the overrides / new values given as parameters.
+ */
+ public ParametersAction createUpdated(Collection extends ParameterValue> newValues) {
+ List r = new ArrayList();
+
+ Set names = new HashSet();
+ for (ParameterValue v : newValues) {
+ names.add(v.name);
+ }
+
+ for (Iterator itr = parameters.iterator(); itr.hasNext(); ) {
+ ParameterValue v = itr.next();
+ if (!names.contains(v.getName()))
+ r.add(v);
+ }
+
+ r.addAll(newValues);
+
+ return new ParametersAction(r);
+ }
+
private Object readResolve() {
if (build != null)
OldDataMonitor.report(build, "1.283");
diff --git a/core/src/main/java/hudson/model/ParametersDefinitionProperty.java b/core/src/main/java/hudson/model/ParametersDefinitionProperty.java
index 0cc266ce43..e25116bb6f 100644
--- a/core/src/main/java/hudson/model/ParametersDefinitionProperty.java
+++ b/core/src/main/java/hudson/model/ParametersDefinitionProperty.java
@@ -34,6 +34,7 @@ import java.util.AbstractList;
import javax.servlet.ServletException;
+import hudson.model.Queue.WaitingItem;
import jenkins.model.Jenkins;
import jenkins.util.TimeDuration;
import net.sf.json.JSONArray;
@@ -48,6 +49,8 @@ import org.kohsuke.stapler.export.ExportedBean;
import hudson.Extension;
import org.kohsuke.stapler.export.Flavor;
+import static javax.servlet.http.HttpServletResponse.SC_CREATED;
+
/**
* Keeps a list of the parameters defined for a project.
*
@@ -132,11 +135,13 @@ public class ParametersDefinitionProperty extends JobProperty
* This class implements the core scheduling logic. {@link Task} represents the executable
* task that are placed in the queue. While in the queue, it's wrapped into {@link Item}
- * so that we can keep track of additional data used for deciding what to exeucte when.
+ * so that we can keep track of additional data used for deciding what to execute when.
*
*
* Items in queue goes through several stages, as depicted below:
@@ -123,7 +128,7 @@ import org.kohsuke.stapler.interceptor.RequirePOST;
* | ^
* | |
* | v
- * +--> buildables ---> pending ---> (executed)
+ * +--> buildables ---> pending ---> left
*
*
*
@@ -165,6 +170,13 @@ public class Queue extends ResourceController implements Saveable {
*/
private final ItemList pendings = new ItemList();
+ /**
+ * Items that left queue would stay here for a while to enable tracking via {@link Item#id}.
+ *
+ * This map is forgetful, since we can't remember everything that executed in the past.
+ */
+ private final Cache leftItems = CacheBuilder.newBuilder().expireAfterWrite(5*60, TimeUnit.SECONDS).build();
+
private final CachedItemList itemsView = new CachedItemList();
/**
@@ -516,7 +528,7 @@ public class Queue extends ResourceController implements Saveable {
}
}
if (duplicatesInQueue.isEmpty()) {
- LOGGER.fine(p.getFullDisplayName() + " added to queue");
+ LOGGER.log(Level.FINE, "{0} added to queue", p);
// put the item in the queue
WaitingItem added = new WaitingItem(due,p,actions);
@@ -525,7 +537,7 @@ public class Queue extends ResourceController implements Saveable {
return added;
}
- LOGGER.fine(p.getFullDisplayName() + " is already in the queue");
+ LOGGER.log(Level.FINE, "{0} is already in the queue", p);
// but let the actions affect the existing stuff.
for(Item item : duplicatesInQueue) {
@@ -593,7 +605,7 @@ public class Queue extends ResourceController implements Saveable {
* false if this was no-op.
*/
public synchronized boolean cancel(Task p) {
- LOGGER.fine("Cancelling " + p.getFullDisplayName());
+ LOGGER.log(Level.FINE, "Cancelling {0}", p);
for (Iterator itr = waitingList.iterator(); itr.hasNext();) {
Item item = itr.next();
if (item.task.equals(p)) {
@@ -607,9 +619,13 @@ public class Queue extends ResourceController implements Saveable {
}
public synchronized boolean cancel(Item item) {
- LOGGER.fine("Cancelling " + item.task.getFullDisplayName() + " item#" + item.id);
+ LOGGER.log(Level.FINE, "Cancelling {0} item#{1}", new Object[] {item.task, item.id});
// use bitwise-OR to make sure that all the branches get evaluated all the time
boolean r = (item instanceof WaitingItem && waitingList.remove(item)) | blockedProjects.remove(item) | buildables.remove(item);
+
+ LeftItem li = new LeftItem(item);
+ leftItems.put(li.id,li);
+
if(r)
item.onCancelled();
return r;
@@ -681,7 +697,8 @@ public class Queue extends ResourceController implements Saveable {
for (Item item: blockedProjects) if (item.id == id) return item;
for (Item item: buildables) if (item.id == id) return item;
for (Item item: pendings) if (item.id == id) return item;
- return null;
+
+ return leftItems.getIfPresent(id);
}
/**
@@ -718,6 +735,24 @@ public class Queue extends ResourceController implements Saveable {
return new ArrayList(pendings.values());
}
+ /**
+ * Returns the snapshot of all {@link LeftItem}s.
+ *
+ * @since 1.519
+ */
+ public Collection getLeftItems() {
+ return Collections.unmodifiableCollection(leftItems.asMap().values());
+ }
+
+ /**
+ * Immediately clear the {@link #getLeftItems} cache.
+ * Useful for tests which need to verify that no links to a build remain.
+ * @since 1.519
+ */
+ public void clearLeftItems() {
+ leftItems.invalidateAll();
+ }
+
/**
* Gets all items that are in the queue but not blocked
*
@@ -850,6 +885,9 @@ public class Queue extends ResourceController implements Saveable {
OneOffExecutor ooe = (OneOffExecutor) exec;
final WorkUnit wu = ooe.getAssignedWorkUnit();
pendings.remove(wu.context.item);
+
+ LeftItem li = new LeftItem(wu.context);
+ leftItems.put(li.id,li);
return wu;
}
@@ -890,11 +928,14 @@ public class Queue extends ResourceController implements Saveable {
// am I woken up because I have a project to build?
if (offer.workUnit != null) {
// if so, just build it
- LOGGER.fine("Pop returning " + offer.workUnit + " for " + exec.getName());
+ LOGGER.log(Level.FINE, "Pop returning {0} for {1}", new Object[] {offer.workUnit, exec.getName()});
// TODO: I think this has to be done by the last executor that leaves the pop(), not by main executor
- if (offer.workUnit.isMainWork())
+ if (offer.workUnit.isMainWork()) {
pendings.remove(offer.workUnit.context.item);
+ LeftItem li = new LeftItem(offer.workUnit.context);
+ leftItems.put(li.id,li);
+ }
return offer.workUnit;
}
@@ -979,8 +1020,7 @@ public class Queue extends ResourceController implements Saveable {
* and it also gets invoked periodically (see {@link MaintainTask}.)
*/
public synchronized void maintain() {
- if (LOGGER.isLoggable(Level.FINE))
- LOGGER.fine("Queue maintenance started " + this);
+ LOGGER.log(Level.FINE, "Queue maintenance started {0}", this);
{// blocked -> buildable
Iterator itr = blockedProjects.values().iterator();
@@ -988,7 +1028,7 @@ public class Queue extends ResourceController implements Saveable {
BlockedItem p = itr.next();
if (!isBuildBlocked(p) && allowNewBuildableTask(p.task)) {
// ready to be executed
- LOGGER.fine(p.task.getFullDisplayName() + " no longer blocked");
+ LOGGER.log(Level.FINE, "{0} no longer blocked", p.task);
itr.remove();
makeBuildable(new BuildableItem(p));
}
@@ -1006,12 +1046,12 @@ public class Queue extends ResourceController implements Saveable {
Task p = top.task;
if (!isBuildBlocked(top) && allowNewBuildableTask(p)) {
// ready to be executed immediately
- LOGGER.fine(p.getFullDisplayName() + " ready to build");
+ LOGGER.log(Level.FINE, "{0} ready to build", p);
makeBuildable(new BuildableItem(top));
} else {
// this can't be built now because another build is in progress
// set this project aside.
- LOGGER.fine(p.getFullDisplayName() + " is blocked");
+ LOGGER.log(Level.FINE, "{0} is blocked", p);
blockedProjects.put(p,new BlockedItem(top));
}
}
@@ -1029,7 +1069,7 @@ public class Queue extends ResourceController implements Saveable {
if (isBuildBlocked(p)) {
itr.remove();
blockedProjects.put(p.task,new BlockedItem(p));
- LOGGER.fine(String.format("Catching that %s is blocked in the last minute", p));
+ LOGGER.log(Level.FINE, "Catching that {0} is blocked in the last minute", p);
continue;
}
@@ -1044,10 +1084,7 @@ public class Queue extends ResourceController implements Saveable {
// if we couldn't find the executor that fits,
// just leave it in the buildables list and
// check if we can execute other projects
- if (LOGGER.isLoggable(Level.FINER)) {
- LOGGER.finer(String.format("Failed to map %s to executors. candidates=%s parked=%s",
- p, candidates, parked.values()));
- }
+ LOGGER.log(Level.FINER, "Failed to map {0} to executors. candidates={1} parked={2}", new Object[] {p, candidates, parked.values()});
continue;
}
@@ -1059,7 +1096,7 @@ public class Queue extends ResourceController implements Saveable {
if (!wuc.getWorkUnits().isEmpty())
makePending(p);
else
- LOGGER.fine(String.format("BuildableItem %s with empty work units!?",p));
+ LOGGER.log(Level.FINE, "BuildableItem {0} with empty work units!?", p);
}
}
@@ -1357,7 +1394,8 @@ public class Queue extends ResourceController implements Saveable {
*
* Just like {@link #id}, the same object tracks various stages of the queue.
*/
- public Future getFuture() { return future; }
+ @WithBridgeMethods(Future.class)
+ public QueueTaskFuture getFuture() { return future; }
/**
* If this task needs to be run on a node with a particular label,
@@ -1408,6 +1446,18 @@ public class Queue extends ResourceController implements Saveable {
this(item.task, item.getActions(), item.id, item.future, item.inQueueSince);
}
+ /**
+ * Returns the URL of this {@link Item} relative to the context path of Jenkins
+ *
+ * @return
+ * URL that ends with '/'.
+ * @since 1.519
+ */
+ @Exported
+ public String getUrl() {
+ return "queue/item/"+id+'/';
+ }
+
/**
* Gets a human-readable status message describing why it's in the queue.
*/
@@ -1469,6 +1519,10 @@ public class Queue extends ResourceController implements Saveable {
future.setAsCancelled();
}
+ public Api getApi() {
+ return new Api(this);
+ }
+
private Object readResolve() {
this.future = new FutureImpl(task);
return this;
@@ -1692,6 +1746,54 @@ public class Queue extends ResourceController implements Saveable {
}
}
+ /**
+ * {@link Item} in the {@link Queue#leftItems} stage. These are items that had left the queue
+ * by either began executing or by getting cancelled.
+ *
+ * @since 1.519
+ */
+ public final static class LeftItem extends Item {
+ public final WorkUnitContext outcome;
+
+ /**
+ * When item has left the queue and begin executing.
+ */
+ public LeftItem(WorkUnitContext wuc) {
+ super(wuc.item);
+ this.outcome = wuc;
+ }
+
+ /**
+ * When item is cancelled.
+ */
+ public LeftItem(Item cancelled) {
+ super(cancelled);
+ this.outcome = null;
+ }
+
+ @Override
+ public CauseOfBlockage getCauseOfBlockage() {
+ return null;
+ }
+
+ /**
+ * If this is representing an item that started executing, this property returns
+ * the primary executable (such as {@link AbstractBuild}) that created out of it.
+ */
+ @Exported
+ public Executable getExecutable() {
+ return outcome!=null ? outcome.getPrimaryWorkUnit().getExecutable() : null;
+ }
+
+ /**
+ * Is this representing a cancelled item?
+ */
+ @Exported
+ public boolean isCancelled() {
+ return outcome==null;
+ }
+ }
+
private static final Logger LOGGER = Logger.getLogger(Queue.class.getName());
/**
diff --git a/core/src/main/java/hudson/model/Run.java b/core/src/main/java/hudson/model/Run.java
index e1d944f92b..fb8a2f0f67 100644
--- a/core/src/main/java/hudson/model/Run.java
+++ b/core/src/main/java/hudson/model/Run.java
@@ -110,6 +110,7 @@ import org.kohsuke.stapler.export.Exported;
import org.kohsuke.stapler.export.ExportedBean;
import com.thoughtworks.xstream.XStream;
+import hudson.model.Run.RunExecution;
import java.io.ByteArrayInputStream;
import org.kohsuke.stapler.interceptor.RequirePOST;
@@ -119,6 +120,7 @@ import java.io.OutputStream;
import static java.util.logging.Level.*;
import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
+import jenkins.model.RunAction2;
/**
* A particular execution of {@link Job}.
@@ -313,10 +315,15 @@ public abstract class Run ,RunT extends Run,RunT extends Run actions = new ArrayList();
for (TransientBuildActionFactory factory: TransientBuildActionFactory.all()) {
actions.addAll(factory.createFor(this));
+ assert !actions.contains(null) : "null action added by " + factory;
}
return Collections.unmodifiableList(actions);
}
+ @SuppressWarnings("deprecation")
@Override
public void addAction(Action a) {
super.addAction(a);
- if (a instanceof RunAction)
+ if (a instanceof RunAction2) {
+ ((RunAction2) a).onAttached(this);
+ } else if (a instanceof RunAction) {
((RunAction) a).onAttached(this);
+ }
}
- /*package*/ static long parseTimestampFromBuildDir(File buildDir) throws IOException {
+ static class InvalidDirectoryNameException extends IOException {
+ InvalidDirectoryNameException(File buildDir) {
+ super("Invalid directory name " + buildDir);
+ }
+ }
+
+ /*package*/ static long parseTimestampFromBuildDir(File buildDir) throws IOException, InvalidDirectoryNameException {
try {
if(Util.isSymlink(buildDir)) {
// "Util.resolveSymlink(file)" resolves NTFS symlinks.
@@ -349,11 +367,11 @@ public abstract class Run ,RunT extends Run,RunT extends Run,RunT extends Run,RunT extends Run,RunT extends Run> extends AbstractLazyLoadRunMap
if (LOGGER.isLoggable(FINE))
LOGGER.log(FINE,"Loaded " + b.getFullDisplayName(),new ThisIsHowItsLoaded());
return b;
+ } catch (Run.InvalidDirectoryNameException x) {
+ Level lvl;
+ try {
+ Integer.parseInt(d.getName());
+ // JENKINS-15587: just an mangled symlink
+ lvl = Level.FINE;
+ } catch (NumberFormatException x2) {
+ // potentially a real build dir, maybe a bug
+ lvl = Level.WARNING;
+ }
+ LOGGER.log(lvl, "skipping non-build directory {0}", d);
} catch (IOException e) {
LOGGER.log(Level.WARNING, "could not load " + d, e);
} catch (InstantiationError e) {
diff --git a/core/src/main/java/hudson/model/RunParameterDefinition.java b/core/src/main/java/hudson/model/RunParameterDefinition.java
index 04d074f01e..bacef92a08 100644
--- a/core/src/main/java/hudson/model/RunParameterDefinition.java
+++ b/core/src/main/java/hudson/model/RunParameterDefinition.java
@@ -1,7 +1,7 @@
/*
* The MIT License
*
- * Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Tom Huybrechts
+ * Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Tom Huybrechts, Geoff Cummings
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -30,31 +30,67 @@ import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.export.Exported;
import hudson.Extension;
+import hudson.util.EnumConverter;
+import hudson.util.RunList;
+import org.kohsuke.stapler.Stapler;
import org.kohsuke.stapler.QueryParameter;
public class RunParameterDefinition extends SimpleParameterDefinition {
+ /**
+ * Constants that control how Run Parameter is filtered.
+ * @since 1.517
+ */
+ public enum RunParameterFilter {
+ ALL,
+ STABLE,
+ SUCCESSFUL,
+ COMPLETED;
+
+ public String getName() {
+ return name();
+ }
+
+ static {
+ Stapler.CONVERT_UTILS.register(new EnumConverter(), RunParameterFilter.class);
+ }
+ }
+
private final String projectName;
private final String runId;
+ private final RunParameterFilter filter;
+ /**
+ * @since 1.517
+ */
@DataBoundConstructor
- public RunParameterDefinition(String name, String projectName, String description) {
+ public RunParameterDefinition(String name, String projectName, String description, RunParameterFilter filter) {
super(name, description);
this.projectName = projectName;
this.runId = null;
+ this.filter = filter;
}
- private RunParameterDefinition(String name, String projectName, String runId, String description) {
+ /**
+ * @deprecated as of 1.517
+ */
+ public RunParameterDefinition(String name, String projectName, String description) {
+ // delegate to updated constructor with additional RunParameterFilter parameter defaulted to ALL.
+ this(name, projectName, description, RunParameterFilter.ALL);
+ }
+
+ private RunParameterDefinition(String name, String projectName, String runId, String description, RunParameterFilter filter) {
super(name, description);
this.projectName = projectName;
this.runId = runId;
+ this.filter = filter;
}
@Override
public ParameterDefinition copyWithDefaultValue(ParameterValue defaultValue) {
if (defaultValue instanceof RunParameterValue) {
RunParameterValue value = (RunParameterValue) defaultValue;
- return new RunParameterDefinition(getName(), value.getRunId(), getDescription());
+ return new RunParameterDefinition(getName(), value.getRunId(), getDescription(), getFilter());
} else {
return this;
}
@@ -69,6 +105,33 @@ public class RunParameterDefinition extends SimpleParameterDefinition {
return Jenkins.getInstance().getItemByFullName(projectName, Job.class);
}
+ /**
+ * @return The current filter value, if filter is null, returns ALL
+ * @since 1.517
+ */
+ public RunParameterFilter getFilter() {
+ // if filter is null, default to RunParameterFilter.ALL
+ return (null == filter) ? RunParameterFilter.ALL : filter;
+ }
+
+ /**
+ * @since 1.517
+ * @return Returns a list of builds, filtered based on the filter value.
+ */
+ public RunList getBuilds() {
+ // use getFilter() method so we dont have to worry about null filter value.
+ switch (getFilter()) {
+ case COMPLETED:
+ return getProject().getBuilds().overThresholdOnly(Result.ABORTED);
+ case SUCCESSFUL:
+ return getProject().getBuilds().overThresholdOnly(Result.UNSTABLE);
+ case STABLE :
+ return getProject().getBuilds().overThresholdOnly(Result.SUCCESS);
+ default:
+ return getProject().getBuilds();
+ }
+ }
+
@Extension
public static class DescriptorImpl extends ParameterDescriptor {
@Override
@@ -98,7 +161,24 @@ public class RunParameterDefinition extends SimpleParameterDefinition {
return createValue(runId);
}
- Run,?> lastBuild = getProject().getLastBuild();
+ Run,?> lastBuild = null;
+
+ // use getFilter() so we dont have to worry about null filter value.
+ switch (getFilter()) {
+ case COMPLETED:
+ lastBuild = getProject().getLastCompletedBuild();
+ break;
+ case SUCCESSFUL:
+ lastBuild = getProject().getLastSuccessfulBuild();
+ break;
+ case STABLE :
+ lastBuild = getProject().getLastStableBuild();
+ break;
+ default:
+ lastBuild = getProject().getLastBuild();
+ break;
+ }
+
if (lastBuild != null) {
return createValue(lastBuild.getExternalizableId());
} else {
diff --git a/core/src/main/java/hudson/model/RunParameterValue.java b/core/src/main/java/hudson/model/RunParameterValue.java
index 3ee6072cad..6368a94277 100644
--- a/core/src/main/java/hudson/model/RunParameterValue.java
+++ b/core/src/main/java/hudson/model/RunParameterValue.java
@@ -1,7 +1,7 @@
/*
* The MIT License
*
- * Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Tom Huybrechts
+ * Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Tom Huybrechts, Geoff Cummings
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -82,6 +82,9 @@ public class RunParameterValue extends ParameterValue {
env.put(name + "_NAME", run.getDisplayName()); // since 1.504
+ String buildResult = (null == run.getResult()) ? "UNKNOWN" : run.getResult().toString();
+ env.put(name + "_RESULT", buildResult); // since 1.517
+
env.put(name.toUpperCase(Locale.ENGLISH),value); // backward compatibility pre 1.345
}
diff --git a/core/src/main/java/hudson/model/StreamBuildListener.java b/core/src/main/java/hudson/model/StreamBuildListener.java
index 43ff3e208d..f00877ec11 100644
--- a/core/src/main/java/hudson/model/StreamBuildListener.java
+++ b/core/src/main/java/hudson/model/StreamBuildListener.java
@@ -70,7 +70,7 @@ public class StreamBuildListener extends StreamTaskListener implements BuildList
if (causes==null || causes.isEmpty())
l.println("Started");
else for (Cause cause : causes) {
- // XXX elide duplicates as per CauseAction.getCauseCounts (used in summary.jelly)
+ // TODO elide duplicates as per CauseAction.getCauseCounts (used in summary.jelly)
cause.print(this);
}
}
diff --git a/core/src/main/java/hudson/model/UpdateCenter.java b/core/src/main/java/hudson/model/UpdateCenter.java
index 7e874ea6de..b8fe591b9f 100644
--- a/core/src/main/java/hudson/model/UpdateCenter.java
+++ b/core/src/main/java/hudson/model/UpdateCenter.java
@@ -89,6 +89,8 @@ import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.CheckForNull;
import org.acegisecurity.context.SecurityContextHolder;
+import org.kohsuke.accmod.Restricted;
+import org.kohsuke.accmod.restrictions.NoExternalUse;
import org.kohsuke.stapler.export.Exported;
import org.kohsuke.stapler.export.ExportedBean;
import org.kohsuke.stapler.interceptor.RequirePOST;
@@ -119,6 +121,9 @@ public class UpdateCenter extends AbstractModelObject implements Saveable, OnMas
* @since 1.483
*/
public static final String ID_DEFAULT = "default";
+
+ @Restricted(NoExternalUse.class)
+ public static final String ID_UPLOAD = "_upload";
/**
* {@link ExecutorService} that performs installation.
@@ -1012,6 +1017,9 @@ public class UpdateCenter extends AbstractModelObject implements Saveable, OnMas
}
public void run() {
+ if (ID_UPLOAD.equals(site.getId())) {
+ return;
+ }
LOGGER.fine("Doing a connectivity check");
try {
String connectionCheckUrl = site.getConnectionCheckUrl();
diff --git a/core/src/main/java/hudson/model/View.java b/core/src/main/java/hudson/model/View.java
index 4f88290400..40a90b97d0 100644
--- a/core/src/main/java/hudson/model/View.java
+++ b/core/src/main/java/hudson/model/View.java
@@ -792,7 +792,7 @@ public abstract class View extends AbstractModelObject implements AccessControll
UserInfo info = users.get(user);
if (info == null) {
UserInfo userInfo = new UserInfo(user, p, build.getTimestamp());
- userInfo.avatar = UserAvatarResolver.resolve(user, iconSize);
+ userInfo.avatar = UserAvatarResolver.resolveOrNull(user, iconSize);
synchronized (this) {
users.put(user, userInfo);
modified.add(user);
@@ -805,7 +805,7 @@ public abstract class View extends AbstractModelObject implements AccessControll
}
}
}
- // XXX consider also adding the user of the UserCause when applicable
+ // TODO consider also adding the user of the UserCause when applicable
buildCount++;
progress((itemCount + 1.0 * buildCount / builds.size()) / (items.size() + 1));
}
@@ -818,13 +818,13 @@ public abstract class View extends AbstractModelObject implements AccessControll
if (canceled()) {
return;
}
- for (User u : User.getAll()) { // XXX nice to have a method to iterate these lazily
+ for (User u : User.getAll()) { // TODO nice to have a method to iterate these lazily
if (u == unknown) {
continue;
}
if (!users.containsKey(u)) {
UserInfo userInfo = new UserInfo(u, null, null);
- userInfo.avatar = UserAvatarResolver.resolve(u, iconSize);
+ userInfo.avatar = UserAvatarResolver.resolveOrNull(u, iconSize);
synchronized (this) {
users.put(u, userInfo);
modified.add(u);
@@ -842,7 +842,7 @@ public abstract class View extends AbstractModelObject implements AccessControll
accumulate("id", u.getId()).
accumulate("fullName", u.getFullName()).
accumulate("url", u.getUrl()).
- accumulate("avatar", i.avatar).
+ accumulate("avatar", i.avatar != null ? i.avatar : Stapler.getCurrentRequest().getContextPath() + Functions.getResourcePath() + "/images/" + iconSize + "/user.png").
accumulate("timeSortKey", i.getTimeSortKey()).
accumulate("lastChangeTimeString", i.getLastChangeTimeString());
AbstractProject,?> p = i.getProject();
diff --git a/core/src/main/java/hudson/model/queue/CauseOfBlockage.java b/core/src/main/java/hudson/model/queue/CauseOfBlockage.java
index 44513142a8..4c56340144 100644
--- a/core/src/main/java/hudson/model/queue/CauseOfBlockage.java
+++ b/core/src/main/java/hudson/model/queue/CauseOfBlockage.java
@@ -43,6 +43,10 @@ public abstract class CauseOfBlockage {
};
}
+ @Override public String toString() {
+ return getShortDescription();
+ }
+
/**
* Marker interface to indicates that we can reasonably expect
* that adding a suitable executor/node will resolve this blockage.
diff --git a/core/src/main/java/hudson/model/queue/WorkUnit.java b/core/src/main/java/hudson/model/queue/WorkUnit.java
index 739cdfe64a..c9ffb3e8d6 100644
--- a/core/src/main/java/hudson/model/queue/WorkUnit.java
+++ b/core/src/main/java/hudson/model/queue/WorkUnit.java
@@ -27,6 +27,8 @@ import hudson.model.Executor;
import hudson.model.Queue;
import hudson.model.Queue.Executable;
import hudson.model.Queue.Task;
+import org.kohsuke.accmod.Restricted;
+import org.kohsuke.accmod.restrictions.NoExternalUse;
import org.kohsuke.stapler.export.ExportedBean;
/**
@@ -48,6 +50,7 @@ public final class WorkUnit {
public final WorkUnitContext context;
private volatile Executor executor;
+ private Executable executable;
WorkUnit(WorkUnitContext context, SubTask work) {
this.context = context;
@@ -69,10 +72,18 @@ public final class WorkUnit {
}
/**
- * If the execution has already started, return the current executable.
+ * If the execution has already started, return the executable that was created.
*/
public Executable getExecutable() {
- return executor!=null ? executor.getCurrentExecutable() : null;
+ return executable;
+ }
+
+ /**
+ * This method is only meant to be called internally by {@link Executor}.
+ */
+ @Restricted(NoExternalUse.class)
+ public void setExecutable(Executable executable) {
+ this.executable = executable;
}
/**
diff --git a/core/src/main/java/hudson/os/PosixAPI.java b/core/src/main/java/hudson/os/PosixAPI.java
index 1e893c1b90..2e69069c1c 100644
--- a/core/src/main/java/hudson/os/PosixAPI.java
+++ b/core/src/main/java/hudson/os/PosixAPI.java
@@ -1,45 +1,74 @@
package hudson.os;
-import org.jruby.ext.posix.JavaPOSIX;
-import org.jruby.ext.posix.POSIX;
-import org.jruby.ext.posix.POSIXFactory;
-import org.jruby.ext.posix.POSIXHandler;
-import org.jruby.ext.posix.POSIX.ERRORS;
-
import java.io.File;
import java.io.InputStream;
import java.io.PrintStream;
import java.util.Map;
import java.util.logging.Logger;
+import jnr.constants.platform.Errno;
+import jnr.posix.POSIX;
+import jnr.posix.POSIXFactory;
+import jnr.posix.util.DefaultPOSIXHandler;
/**
* POSIX API wrapper.
- *
+ * Formerly used the jna-posix library, but this has been superseded by jnr-posix.
* @author Kohsuke Kawaguchi
*/
public class PosixAPI {
- public static POSIX get() {
+
+ private static POSIX posix;
+
+ /**
+ * Load the JNR implementation of the POSIX APIs for the current platform.
+ * Runtime exceptions will be of type {@link PosixException}.
+ * @return some implementation (even on Windows or unsupported Unix)
+ * @since 1.518
+ */
+ public static synchronized POSIX jnr() {
+ if (posix == null) {
+ posix = POSIXFactory.getPOSIX(new DefaultPOSIXHandler() {
+ @Override public void error(Errno error, String extraData) {
+ throw new PosixException("native error " + error.description() + " " + extraData, convert(error));
+ }
+ @Override public void error(Errno error, String methodName, String extraData) {
+ throw new PosixException("native error calling " + methodName + ": " + error.description() + " " + extraData, convert(error));
+ }
+ private org.jruby.ext.posix.POSIX.ERRORS convert(Errno error) {
+ try {
+ return org.jruby.ext.posix.POSIX.ERRORS.valueOf(error.name());
+ } catch (IllegalArgumentException x) {
+ return org.jruby.ext.posix.POSIX.ERRORS.EIO; // PosixException.message has real error anyway
+ }
+ }
+ }, true);
+ }
return posix;
}
/**
- * @deprecated as of 1.448
- * Use {@link #supportsNative()}.
+ * @deprecated use {@link #jnr} and {@link POSIX#isNative}
*/
+ @Deprecated
public boolean isNative() {
return supportsNative();
}
/**
- * Determine if the jna-posix library could not provide native support, and
- * used a fallback java implementation which does not support many operations.
+ * @deprecated use {@link #jnr} and {@link POSIX#isNative}
*/
+ @Deprecated
public static boolean supportsNative() {
- return !(posix instanceof JavaPOSIX);
+ return !(jnaPosix instanceof org.jruby.ext.posix.JavaPOSIX);
}
-
- private static final POSIX posix = POSIXFactory.getPOSIX(new POSIXHandler() {
- public void error(ERRORS errors, String s) {
+
+ private static org.jruby.ext.posix.POSIX jnaPosix;
+ /** @deprecated Use {@link #jnr} instead. */
+ @Deprecated
+ public static synchronized org.jruby.ext.posix.POSIX get() {
+ if (jnaPosix == null) {
+ jnaPosix = org.jruby.ext.posix.POSIXFactory.getPOSIX(new org.jruby.ext.posix.POSIXHandler() {
+ public void error(org.jruby.ext.posix.POSIX.ERRORS errors, String s) {
throw new PosixException(s,errors);
}
@@ -87,6 +116,9 @@ public class PosixAPI {
return System.err;
}
}, true);
+ }
+ return jnaPosix;
+ }
private static final Logger LOGGER = Logger.getLogger(PosixAPI.class.getName());
}
diff --git a/core/src/main/java/hudson/os/PosixException.java b/core/src/main/java/hudson/os/PosixException.java
index 93e96ef5d0..03cdd50e38 100644
--- a/core/src/main/java/hudson/os/PosixException.java
+++ b/core/src/main/java/hudson/os/PosixException.java
@@ -4,7 +4,7 @@ import org.jruby.ext.posix.POSIX.ERRORS;
/**
* Indicates an error during POSIX API call.
- *
+ * @see PosixAPI
* @author Kohsuke Kawaguchi
*/
public class PosixException extends RuntimeException {
@@ -15,6 +15,8 @@ public class PosixException extends RuntimeException {
this.errors = errors;
}
+ /** @deprecated Leaks reference to deprecated jna-posix API. */
+ @Deprecated
public ERRORS getErrorCode() {
return errors;
}
diff --git a/core/src/main/java/hudson/scheduler/CronTab.java b/core/src/main/java/hudson/scheduler/CronTab.java
index ec8c4ffb43..14ad347a00 100644
--- a/core/src/main/java/hudson/scheduler/CronTab.java
+++ b/core/src/main/java/hudson/scheduler/CronTab.java
@@ -442,7 +442,7 @@ public final class CronTab {
* Checks a prospective crontab specification to see if it could benefit from balanced hashes.
* @param spec a (legal) spec
* @return a similar spec that uses a hash, if such a transformation is necessary; null if it is OK as is
- * @since 1.509
+ * @since 1.510
*/
public static @CheckForNull String hashify(String spec) {
if (spec.contains("H")) {
diff --git a/core/src/main/java/hudson/search/Search.java b/core/src/main/java/hudson/search/Search.java
index 0bece56757..c6899a7cdf 100644
--- a/core/src/main/java/hudson/search/Search.java
+++ b/core/src/main/java/hudson/search/Search.java
@@ -332,9 +332,7 @@ public class Search {
List items = new ArrayList(); // items found in 1 step
- if(LOGGER.isLoggable(Level.FINE)) {
- LOGGER.fine("tokens="+tokens.toString());
- }
+ LOGGER.log(Level.FINE, "tokens={0}", tokens);
// first token
int w=1; // width of token
@@ -343,7 +341,7 @@ public class Search {
m.find(index,token,items);
for (SearchItem si : items) {
paths[w].add(new SuggestedItem(si));
- LOGGER.info("found search item:" + si.getSearchName());
+ LOGGER.log(Level.FINE, "found search item: {0}", si.getSearchName());
}
w++;
}
diff --git a/core/src/main/java/hudson/search/SearchItem.java b/core/src/main/java/hudson/search/SearchItem.java
index dda28ebc6c..3da63411ed 100644
--- a/core/src/main/java/hudson/search/SearchItem.java
+++ b/core/src/main/java/hudson/search/SearchItem.java
@@ -48,7 +48,7 @@ public interface SearchItem {
* @return
* URL like "foo" or "foo/bar". The path can end with '/'.
* The path that starts with '/' will be interpreted as the absolute path
- * (within the context path of Hudson.)
+ * (within the context path of Jenkins.)
*/
String getSearchUrl();
diff --git a/core/src/main/java/hudson/security/BasicAuthenticationFilter.java b/core/src/main/java/hudson/security/BasicAuthenticationFilter.java
index 9a81f6022b..8d18afff06 100644
--- a/core/src/main/java/hudson/security/BasicAuthenticationFilter.java
+++ b/core/src/main/java/hudson/security/BasicAuthenticationFilter.java
@@ -93,6 +93,7 @@ public class BasicAuthenticationFilter implements Filter {
servletContext = filterConfig.getServletContext();
}
+ @SuppressWarnings("ACL.impersonate")
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse rsp = (HttpServletResponse) response;
diff --git a/core/src/main/java/hudson/security/FederatedLoginService.java b/core/src/main/java/hudson/security/FederatedLoginService.java
index 0e00f8b9a2..eb071d9bee 100644
--- a/core/src/main/java/hudson/security/FederatedLoginService.java
+++ b/core/src/main/java/hudson/security/FederatedLoginService.java
@@ -174,6 +174,7 @@ public abstract class FederatedLoginService implements ExtensionPoint {
* to the caller of your "doXyz" method, it will either render an error page or initiate
* a user registration session (provided that {@link SecurityRealm} supports that.)
*/
+ @SuppressWarnings("ACL.impersonate")
public User signin() throws UnclaimedIdentityException {
User u = locateUser();
if (u!=null) {
diff --git a/core/src/main/java/hudson/security/HudsonPrivateSecurityRealm.java b/core/src/main/java/hudson/security/HudsonPrivateSecurityRealm.java
index a6568631dd..22348231c3 100644
--- a/core/src/main/java/hudson/security/HudsonPrivateSecurityRealm.java
+++ b/core/src/main/java/hudson/security/HudsonPrivateSecurityRealm.java
@@ -233,6 +233,7 @@ public class HudsonPrivateSecurityRealm extends AbstractPasswordBasedSecurityRea
/**
* Lets the current user silently login as the given user and report back accordingly.
*/
+ @SuppressWarnings("ACL.impersonate")
private void loginAndTakeBack(StaplerRequest req, StaplerResponse rsp, User u) throws ServletException, IOException {
// ... and let him login
Authentication a = new UsernamePasswordAuthenticationToken(u.getId(),req.getParameter("password1"));
diff --git a/core/src/main/java/hudson/security/Permission.java b/core/src/main/java/hudson/security/Permission.java
index 4e12a11a0d..60c53893d8 100644
--- a/core/src/main/java/hudson/security/Permission.java
+++ b/core/src/main/java/hudson/security/Permission.java
@@ -33,6 +33,7 @@ import java.util.Comparator;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
+import javax.annotation.CheckForNull;
import org.jvnet.localizer.Localizable;
@@ -223,7 +224,7 @@ public final class Permission {
* null if the conversion failed.
* @see #getId()
*/
- public static Permission fromId(String id) {
+ public static @CheckForNull Permission fromId(String id) {
int idx = id.lastIndexOf('.');
if(idx<0) return null;
diff --git a/core/src/main/java/hudson/slaves/SlaveComputer.java b/core/src/main/java/hudson/slaves/SlaveComputer.java
index cdffb6d5f7..66e9c14c9b 100644
--- a/core/src/main/java/hudson/slaves/SlaveComputer.java
+++ b/core/src/main/java/hudson/slaves/SlaveComputer.java
@@ -39,6 +39,7 @@ import hudson.FilePath;
import hudson.Util;
import hudson.AbortException;
import hudson.remoting.Launcher;
+import hudson.security.ACL;
import static hudson.slaves.SlaveComputer.LogHolder.SLAVE_LOG_HANDLER;
import hudson.slaves.OfflineCause.ChannelTermination;
import hudson.util.Secret;
@@ -80,6 +81,8 @@ import org.kohsuke.stapler.HttpRedirect;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponseWrapper;
+import org.acegisecurity.context.SecurityContext;
+import org.acegisecurity.context.SecurityContextHolder;
import org.kohsuke.stapler.ResponseImpl;
import org.kohsuke.stapler.WebMethod;
import org.kohsuke.stapler.compression.FilterServletOutputStream;
@@ -348,6 +351,18 @@ public class SlaveComputer extends Computer {
return channel.call(new LoadingCount(false));
}
+ /**
+ * Shows {@link Channel#classLoadingPrefetchCacheCount}.
+ * @return -1 in case that capability is not supported
+ * @since 1.519
+ */
+ public int getClassLoadingPrefetchCacheCount() throws IOException, InterruptedException {
+ if (!channel.remoteCapability.supportsPrefetch()) {
+ return -1;
+ }
+ return channel.call(new LoadingPrefetchCacheCount());
+ }
+
/**
* Shows {@link Channel#resourceLoadingCount}.
* @since 1.495
@@ -383,6 +398,12 @@ public class SlaveComputer extends Computer {
}
}
+ static class LoadingPrefetchCacheCount implements Callable {
+ @Override public Integer call() {
+ return Channel.current().classLoadingPrefetchCacheCount.get();
+ }
+ }
+
static class LoadingTime implements Callable {
private final boolean resource;
LoadingTime(boolean resource) {
@@ -442,8 +463,14 @@ public class SlaveComputer extends Computer {
channel.pinClassLoader(getClass().getClassLoader());
channel.call(new SlaveInitializer());
- for (ComputerListener cl : ComputerListener.all())
- cl.preOnline(this,channel,root,taskListener);
+ SecurityContext old = ACL.impersonate(ACL.SYSTEM);
+ try {
+ for (ComputerListener cl : ComputerListener.all()) {
+ cl.preOnline(this,channel,root,taskListener);
+ }
+ } finally {
+ SecurityContextHolder.setContext(old);
+ }
offlineCause = null;
@@ -468,8 +495,14 @@ public class SlaveComputer extends Computer {
statusChangeLock.notifyAll();
}
}
- for (ComputerListener cl : ComputerListener.all())
- cl.onOnline(this,taskListener);
+ old = ACL.impersonate(ACL.SYSTEM);
+ try {
+ for (ComputerListener cl : ComputerListener.all()) {
+ cl.onOnline(this,taskListener);
+ }
+ } finally {
+ SecurityContextHolder.setContext(old);
+ }
log.println("Slave successfully connected and online");
Jenkins.getInstance().getQueue().scheduleMaintenance();
}
@@ -718,7 +751,7 @@ public class SlaveComputer extends Computer {
return null;
}
private static final long serialVersionUID = 1L;
- private static final Logger LOGGER = Logger.getLogger("hudson");
+ private static final Logger LOGGER = Logger.getLogger("");
}
/**
diff --git a/core/src/main/java/hudson/tasks/Fingerprinter.java b/core/src/main/java/hudson/tasks/Fingerprinter.java
index 2bf37ef36d..d62e500fff 100644
--- a/core/src/main/java/hudson/tasks/Fingerprinter.java
+++ b/core/src/main/java/hudson/tasks/Fingerprinter.java
@@ -44,7 +44,6 @@ import hudson.model.FingerprintMap;
import jenkins.model.Jenkins;
import hudson.model.Result;
import hudson.model.Run;
-import hudson.model.RunAction;
import hudson.model.TaskListener;
import hudson.remoting.VirtualChannel;
import hudson.util.FormValidation;
@@ -75,6 +74,7 @@ import java.util.Set;
import java.util.TreeMap;
import java.util.logging.Level;
import java.util.logging.Logger;
+import jenkins.model.RunAction2;
/**
* Records fingerprints of the specified files.
@@ -292,10 +292,10 @@ public class Fingerprinter extends Recorder implements Serializable, DependencyD
/**
* Action for displaying fingerprints.
*/
- public static final class FingerprintAction implements RunAction {
-
- private AbstractBuild build;
+ public static final class FingerprintAction implements RunAction2 {
+ private transient AbstractBuild build;
+
private static final Random rand = new Random();
/**
@@ -308,7 +308,7 @@ public class Fingerprinter extends Recorder implements Serializable, DependencyD
public FingerprintAction(AbstractBuild build, Map record) {
this.build = build;
this.record = PackedMap.of(record);
- onLoad(); // make compact
+ compact();
}
public void add(Map moreRecords) {
@@ -316,7 +316,7 @@ public class Fingerprinter extends Recorder implements Serializable, DependencyD
r.putAll(moreRecords);
record = PackedMap.of(r);
ref = null;
- onLoad();
+ compact();
}
public String getIconFileName() {
@@ -342,12 +342,16 @@ public class Fingerprinter extends Recorder implements Serializable, DependencyD
return record;
}
- public void onLoad() {
- if (build.getParent() == null) {
- logger.warning("JENKINS-16845: broken FingerprintAction record");
- build = null;
- return;
- }
+ @Override public void onLoad(Run,?> r) {
+ build = (AbstractBuild) r;
+ compact();
+ }
+
+ @Override public void onAttached(Run,?> r) {
+ // for historical reasons this setup is done in the constructor instead
+ }
+
+ private void compact() {
// share data structure with nearby builds, but to keep lazy loading efficient,
// don't go back the history forever.
if (rand.nextInt(2)!=0) {
@@ -360,12 +364,6 @@ public class Fingerprinter extends Recorder implements Serializable, DependencyD
}
}
- public void onAttached(Run r) {
- }
-
- public void onBuildComplete() {
- }
-
/**
* Reuse string instances from another {@link FingerprintAction} to reduce memory footprint.
*/
@@ -435,11 +433,6 @@ public class Fingerprinter extends Recorder implements Serializable, DependencyD
public Map getDependencies(boolean includeMissing) {
Map r = new HashMap();
- if (build == null) {
- // Broken, do not do anything.
- return r;
- }
-
for (Fingerprint fp : getFingerprints().values()) {
BuildPtr bp = fp.getOriginal();
if(bp==null) continue; // outside Hudson
diff --git a/core/src/main/java/hudson/tasks/Maven.java b/core/src/main/java/hudson/tasks/Maven.java
index b7c3a2119b..c2fe89b465 100644
--- a/core/src/main/java/hudson/tasks/Maven.java
+++ b/core/src/main/java/hudson/tasks/Maven.java
@@ -38,8 +38,7 @@ import hudson.model.Computer;
import hudson.model.EnvironmentSpecific;
import hudson.model.Node;
import jenkins.model.Jenkins;
-import jenkins.mvn.DefaultGlobalSettingsProvider;
-import jenkins.mvn.DefaultSettingsProvider;
+import jenkins.mvn.GlobalMavenConfig;
import jenkins.mvn.GlobalSettingsProvider;
import jenkins.mvn.SettingsProvider;
import hudson.model.TaskListener;
@@ -129,13 +128,13 @@ public class Maven extends Builder {
* Provides access to the settings.xml to be used for a build.
* @since 1.491
*/
- private SettingsProvider settings = new DefaultSettingsProvider();
+ private SettingsProvider settings;
/**
* Provides access to the global settings.xml to be used for a build.
* @since 1.491
*/
- private GlobalSettingsProvider globalSettings = new DefaultGlobalSettingsProvider();
+ private GlobalSettingsProvider globalSettings;
private final static String MAVEN_1_INSTALLATION_COMMON_FILE = "bin/maven";
private final static String MAVEN_2_INSTALLATION_COMMON_FILE = "bin/mvn";
@@ -163,8 +162,8 @@ public class Maven extends Builder {
this.properties = Util.fixEmptyAndTrim(properties);
this.jvmOptions = Util.fixEmptyAndTrim(jvmOptions);
this.usePrivateRepository = usePrivateRepository;
- this.settings = settings != null ? settings : new DefaultSettingsProvider();
- this.globalSettings = globalSettings != null ? globalSettings : new DefaultGlobalSettingsProvider();
+ this.settings = settings != null ? settings : GlobalMavenConfig.get().getSettingsProvider();
+ this.globalSettings = globalSettings != null ? globalSettings : GlobalMavenConfig.get().getGlobalSettingsProvider();
}
public String getTargets() {
@@ -175,14 +174,22 @@ public class Maven extends Builder {
* @since 1.491
*/
public SettingsProvider getSettings() {
- return settings != null ? settings : new DefaultSettingsProvider();
+ return settings != null ? settings : GlobalMavenConfig.get().getSettingsProvider();
+ }
+
+ protected void setSettings(SettingsProvider settings) {
+ this.settings = settings;
}
/**
* @since 1.491
*/
public GlobalSettingsProvider getGlobalSettings() {
- return globalSettings != null ? globalSettings : new DefaultGlobalSettingsProvider();
+ return globalSettings != null ? globalSettings : GlobalMavenConfig.get().getGlobalSettingsProvider();
+ }
+
+ protected void setGlobalSettings(GlobalSettingsProvider globalSettings) {
+ this.globalSettings = globalSettings;
}
public void setUsePrivateRepository(boolean usePrivateRepository) {
@@ -398,6 +405,14 @@ public class Maven extends Builder {
public String getDisplayName() {
return Messages.Maven_DisplayName();
}
+
+ public GlobalSettingsProvider getDefaultGlobalSettingsProvider() {
+ return GlobalMavenConfig.get().getGlobalSettingsProvider();
+ }
+
+ public SettingsProvider getDefaultSettingsProvider() {
+ return GlobalMavenConfig.get().getSettingsProvider();
+ }
public MavenInstallation[] getInstallations() {
return installations;
@@ -420,7 +435,10 @@ public class Maven extends Builder {
@Override
public Builder newInstance(StaplerRequest req, JSONObject formData) throws FormException {
- return req.bindJSON(Maven.class,formData);
+ Maven m = req.bindJSON(Maven.class,formData);
+ m.setSettings(GlobalMavenConfig.get().getSettingsProvider());
+ m.setGlobalSettings(GlobalMavenConfig.get().getGlobalSettingsProvider());
+ return m;
}
}
diff --git a/core/src/main/java/hudson/tasks/UserAvatarResolver.java b/core/src/main/java/hudson/tasks/UserAvatarResolver.java
index 244a34a618..1cf8d13302 100644
--- a/core/src/main/java/hudson/tasks/UserAvatarResolver.java
+++ b/core/src/main/java/hudson/tasks/UserAvatarResolver.java
@@ -33,6 +33,7 @@ import hudson.ExtensionList;
import hudson.ExtensionPoint;
import hudson.Functions;
import hudson.model.User;
+import javax.annotation.CheckForNull;
/**
* Infers avatar image URLs for users
@@ -78,13 +79,22 @@ public abstract class UserAvatarResolver implements ExtensionPoint {
public abstract String findAvatarFor(User u, int width, int height);
/**
- * Resolve an avatar image URL string for the user
+ * Resolve an avatar image URL string for the user.
+ * Note that this method must be called from an HTTP request to be reliable; else use {@link #resolveOrNull}.
* @param u user
* @param avatarSize the preferred image size, "[width]x[height]"
* @return a URL string for a user Avatar image.
*/
public static String resolve(User u, String avatarSize) {
+ String avatar = resolveOrNull(u, avatarSize);
+ return avatar != null ? avatar : Jenkins.getInstance().getRootUrl() + Functions.getResourcePath() + "/images/" + avatarSize + "/user.png";
+ }
+ /**
+ * Like {@link #resolve} but returns null rather than a fallback URL in case there is no special avatar.
+ * @since 1.518
+ */
+ public static @CheckForNull String resolveOrNull(User u, String avatarSize) {
Matcher matcher = iconSizeRegex.matcher(avatarSize);
if (matcher.matches() && matcher.groupCount() == 2) {
int width = Integer.parseInt(matcher.group(1));
@@ -97,7 +107,7 @@ public abstract class UserAvatarResolver implements ExtensionPoint {
LOGGER.warning(String.format("Could not split up the avatar size (%s) into a width and height.", avatarSize));
}
- return Jenkins.getInstance().getRootUrl() + Functions.getResourcePath() + "/images/" + avatarSize + "/user.png";
+ return null;
}
/**
diff --git a/core/src/main/java/hudson/tasks/junit/CaseResult.java b/core/src/main/java/hudson/tasks/junit/CaseResult.java
index 899fc36ce1..e11564533b 100644
--- a/core/src/main/java/hudson/tasks/junit/CaseResult.java
+++ b/core/src/main/java/hudson/tasks/junit/CaseResult.java
@@ -214,7 +214,7 @@ public final class CaseResult extends TestResult implements Comparable all() {
+ return Jenkins.getInstance().getExtensionList(TestNameTransformer.class);
+ }
+}
diff --git a/core/src/main/java/hudson/tasks/test/TestResult.java b/core/src/main/java/hudson/tasks/test/TestResult.java
index 98b069eda6..ebf7515946 100644
--- a/core/src/main/java/hudson/tasks/test/TestResult.java
+++ b/core/src/main/java/hudson/tasks/test/TestResult.java
@@ -246,7 +246,7 @@ public abstract class TestResult extends TestObject {
sb.append("Total Count: ").append(this.getTotalCount()).append(", ");
sb.append("Fail: ").append(this.getFailCount()).append(", ");
sb.append("Skipt: ").append(this.getSkipCount()).append(", ");
- sb.append("Pass: ").append(this.getSkipCount()).append(",\n");
+ sb.append("Pass: ").append(this.getPassCount()).append(",\n");
sb.append("Test Result Class: " ).append(this.getClass().getName()).append(" }\n");
return sb.toString();
}
diff --git a/core/src/main/java/hudson/tools/CommandInstaller.java b/core/src/main/java/hudson/tools/CommandInstaller.java
index 25be46dedb..bbcc18d6bb 100644
--- a/core/src/main/java/hudson/tools/CommandInstaller.java
+++ b/core/src/main/java/hudson/tools/CommandInstaller.java
@@ -78,7 +78,7 @@ public class CommandInstaller extends ToolInstaller {
public FilePath performInstallation(ToolInstallation tool, Node node, TaskListener log) throws IOException, InterruptedException {
FilePath dir = preferredLocation(tool, node);
- // XXX support Windows batch scripts, Unix scripts with interpreter line, etc. (see CommandInterpreter subclasses)
+ // TODO support Windows batch scripts, Unix scripts with interpreter line, etc. (see CommandInterpreter subclasses)
FilePath script = dir.createTextTempFile("hudson", ".sh", command);
try {
String[] cmd = {"sh", "-e", script.getRemote()};
diff --git a/core/src/main/java/hudson/tools/ZipExtractionInstaller.java b/core/src/main/java/hudson/tools/ZipExtractionInstaller.java
index 1fa7adb8bd..48ef62b97c 100644
--- a/core/src/main/java/hudson/tools/ZipExtractionInstaller.java
+++ b/core/src/main/java/hudson/tools/ZipExtractionInstaller.java
@@ -137,7 +137,7 @@ public class ZipExtractionInstaller extends ToolInstaller {
} catch (LinkageError e) {
// if JNA is unavailable, fall back.
// we still prefer to try JNA first as PosixAPI supports even smaller platforms.
- PosixAPI.get().chmod(f.getAbsolutePath(),0755);
+ PosixAPI.jnr().chmod(f.getAbsolutePath(),0755);
}
}
} else {
diff --git a/core/src/main/java/hudson/util/CopyOnWriteMap.java b/core/src/main/java/hudson/util/CopyOnWriteMap.java
index cc1b9ae6b7..3624bf550e 100644
--- a/core/src/main/java/hudson/util/CopyOnWriteMap.java
+++ b/core/src/main/java/hudson/util/CopyOnWriteMap.java
@@ -148,6 +148,19 @@ public abstract class CopyOnWriteMap implements Map {
return view.entrySet();
}
+ @Override public int hashCode() {
+ return copy().hashCode();
+ }
+
+ @SuppressWarnings("EqualsWhichDoesntCheckParameterClass")
+ @Override public boolean equals(Object obj) {
+ return copy().equals(obj);
+ }
+
+ @Override public String toString() {
+ return copy().toString();
+ }
+
/**
* {@link CopyOnWriteMap} backed by {@link HashMap}.
*/
diff --git a/core/src/main/java/hudson/util/FormFieldValidator.java b/core/src/main/java/hudson/util/FormFieldValidator.java
index e644e028be..9558090186 100644
--- a/core/src/main/java/hudson/util/FormFieldValidator.java
+++ b/core/src/main/java/hudson/util/FormFieldValidator.java
@@ -254,7 +254,7 @@ public abstract class FormFieldValidator {
protected BufferedReader open(URL url) throws IOException {
// use HTTP content type to find out the charset.
URLConnection con = ProxyConfiguration.open(url);
- if (con == null) { // XXX is this even permitted by URL.openConnection?
+ if (con == null) { // TODO is this even permitted by URL.openConnection?
throw new IOException(url.toExternalForm());
}
return new BufferedReader(
diff --git a/core/src/main/java/hudson/util/FormValidation.java b/core/src/main/java/hudson/util/FormValidation.java
index 2f001ec234..fa67e4af8d 100644
--- a/core/src/main/java/hudson/util/FormValidation.java
+++ b/core/src/main/java/hudson/util/FormValidation.java
@@ -417,7 +417,7 @@ public abstract class FormValidation extends IOException implements HttpResponse
protected BufferedReader open(URL url) throws IOException {
// use HTTP content type to find out the charset.
URLConnection con = ProxyConfiguration.open(url);
- if (con == null) { // XXX is this even permitted by URL.openConnection?
+ if (con == null) { // TODO is this even permitted by URL.openConnection?
throw new IOException(url.toExternalForm());
}
return new BufferedReader(
diff --git a/core/src/main/java/hudson/util/IOUtils.java b/core/src/main/java/hudson/util/IOUtils.java
index 4d046b7541..60faaaf032 100644
--- a/core/src/main/java/hudson/util/IOUtils.java
+++ b/core/src/main/java/hudson/util/IOUtils.java
@@ -122,7 +122,7 @@ public class IOUtils extends org.apache.commons.io.IOUtils {
*/
public static int mode(File f) throws PosixException {
if(Functions.isWindows()) return -1;
- return PosixAPI.get().stat(f.getPath()).mode();
+ return PosixAPI.jnr().stat(f.getPath()).mode();
}
/**
diff --git a/core/src/main/java/hudson/util/PersistedList.java b/core/src/main/java/hudson/util/PersistedList.java
index 52d20a3caf..89325d0c05 100644
--- a/core/src/main/java/hudson/util/PersistedList.java
+++ b/core/src/main/java/hudson/util/PersistedList.java
@@ -193,6 +193,10 @@ public class PersistedList implements Iterable {
return data.contains(item);
}
+ @Override public String toString() {
+ return toList().toString();
+ }
+
/**
* {@link Converter} implementation for XStream.
*
diff --git a/core/src/main/java/hudson/util/RunList.java b/core/src/main/java/hudson/util/RunList.java
index 25f7deadd3..c86b0b0da5 100644
--- a/core/src/main/java/hudson/util/RunList.java
+++ b/core/src/main/java/hudson/util/RunList.java
@@ -1,7 +1,7 @@
/*
* The MIT License
*
- * Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi
+ * Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Geoff Cummings
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -221,6 +221,13 @@ public class RunList extends AbstractList {
return this;
}
+ /**
+ * Return only the most recent builds.
+ * Warning: this method mutates the original list and then returns it.
+ * @param n a count
+ * @return the n most recent builds
+ * @since 1.507
+ */
public RunList limit(final int n) {
return limit(new CountingPredicate() {
public boolean apply(int index, R input) {
@@ -231,6 +238,7 @@ public class RunList extends AbstractList {
/**
* Filter the list to non-successful builds only.
+ * Warning: this method mutates the original list and then returns it.
*/
public RunList failureOnly() {
return filter(new Predicate() {
@@ -240,8 +248,22 @@ public class RunList extends AbstractList {
});
}
+ /**
+ * Filter the list to builds above threshold.
+ * Warning: this method mutates the original list and then returns it.
+ * @since 1.517
+ */
+ public RunList overThresholdOnly(final Result threshold) {
+ return filter(new Predicate() {
+ public boolean apply(R r) {
+ return (r.getResult() != null && r.getResult().isBetterOrEqualTo(threshold));
+ }
+ });
+ }
+
/**
* Filter the list to builds on a single node only
+ * Warning: this method mutates the original list and then returns it.
*/
public RunList node(final Node node) {
return filter(new Predicate() {
@@ -253,6 +275,7 @@ public class RunList extends AbstractList {
/**
* Filter the list to regression builds only.
+ * Warning: this method mutates the original list and then returns it.
*/
public RunList regressionOnly() {
return filter(new Predicate() {
@@ -266,6 +289,7 @@ public class RunList extends AbstractList {
* Filter the list by timestamp.
*
* {@code s<=;e}.
+ * Warning: this method mutates the original list and then returns it.
*/
public RunList byTimestamp(final long start, final long end) {
return
@@ -284,6 +308,7 @@ public class RunList extends AbstractList {
* Reduce the size of the list by only leaving relatively new ones.
* This also removes on-going builds, as RSS cannot be used to publish information
* if it changes.
+ * Warning: this method mutates the original list and then returns it.
*/
public RunList newBuilds() {
GregorianCalendar cal = new GregorianCalendar();
diff --git a/core/src/main/java/hudson/util/XStream2.java b/core/src/main/java/hudson/util/XStream2.java
index c4543979b1..adebbbd5a9 100644
--- a/core/src/main/java/hudson/util/XStream2.java
+++ b/core/src/main/java/hudson/util/XStream2.java
@@ -122,7 +122,7 @@ public class XStream2 extends XStream {
}
static String trimVersion(String version) {
- // XXX seems like there should be some trick with VersionNumber to do this
+ // TODO seems like there should be some trick with VersionNumber to do this
return version.replaceFirst(" .+$", "");
}
diff --git a/core/src/main/java/hudson/util/jna/GNUCLibrary.java b/core/src/main/java/hudson/util/jna/GNUCLibrary.java
index aa397b5b37..8659089f52 100644
--- a/core/src/main/java/hudson/util/jna/GNUCLibrary.java
+++ b/core/src/main/java/hudson/util/jna/GNUCLibrary.java
@@ -30,6 +30,8 @@ import com.sun.jna.Native;
import com.sun.jna.Memory;
import com.sun.jna.NativeLong;
import com.sun.jna.ptr.IntByReference;
+import hudson.os.PosixAPI;
+import jnr.posix.POSIX;
import org.jvnet.libpam.impl.CLibrary.passwd;
/**
@@ -38,7 +40,7 @@ import org.jvnet.libpam.impl.CLibrary.passwd;
*
* Not available on all platforms (such as Linux/PPC, IBM mainframe, etc.), so the caller should recover gracefully
* in case of {@link LinkageError}. See HUDSON-4820.
- *
+ *
Consider deprecating all methods present also in {@link POSIX} (as obtained by {@link PosixAPI#jnr}).
* @author Kohsuke Kawaguchi
*/
public interface GNUCLibrary extends Library {
diff --git a/core/src/main/java/hudson/widgets/Widget.java b/core/src/main/java/hudson/widgets/Widget.java
index 69153ff3e3..2aa4863589 100644
--- a/core/src/main/java/hudson/widgets/Widget.java
+++ b/core/src/main/java/hudson/widgets/Widget.java
@@ -24,6 +24,7 @@
package hudson.widgets;
import hudson.ExtensionPoint;
+import hudson.model.View;
/**
* Box to be rendered in the side panel.
@@ -32,6 +33,14 @@ import hudson.ExtensionPoint;
*
*
index.jelly should display the widget. It should have:
* <l:pane width="2" title="..."> ...body... </l:pane> structure.
+ * In this view, "it" points to the {@link Widget} and "view" points to {@link View}
+ * that's rendering the widget.
+ *
+ *
+ * TODO:
+ * - make Widget describable & provide the UI to let admin configure widgets?
+ * - backward compatibility implications?
+ *
*
* @author Kohsuke Kawaguchi
* @since 1.146
diff --git a/core/src/main/java/jenkins/model/Jenkins.java b/core/src/main/java/jenkins/model/Jenkins.java
index a5b6af1cb2..691e2ac853 100755
--- a/core/src/main/java/jenkins/model/Jenkins.java
+++ b/core/src/main/java/jenkins/model/Jenkins.java
@@ -261,6 +261,7 @@ import java.io.InputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.net.BindException;
+import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.Charset;
import java.security.SecureRandom;
@@ -820,11 +821,13 @@ public class Jenkins extends AbstractCIBase implements ModifiableTopLevelItemGro
} else
tcpSlaveAgentListener = null;
- try {
- udpBroadcastThread = new UDPBroadcastThread(this);
- udpBroadcastThread.start();
- } catch (IOException e) {
- LOGGER.log(Level.WARNING, "Failed to broadcast over UDP",e);
+ if (UDPBroadcastThread.PORT != -1) {
+ try {
+ udpBroadcastThread = new UDPBroadcastThread(this);
+ udpBroadcastThread.start();
+ } catch (IOException e) {
+ LOGGER.log(Level.WARNING, "Failed to broadcast over UDP (use -Dhudson.udp=-1 to disable)", e);
+ }
}
dnsMultiCast = new DNSMultiCast(this);
@@ -1763,9 +1766,9 @@ public class Jenkins extends AbstractCIBase implements ModifiableTopLevelItemGro
if (!d.isDirectory() && (d.getParentFile() == null || !d.getParentFile().canWrite())) {
return FormValidation.error(value + " does not exist and probably cannot be created");
}
- // XXX failure to use either ITEM_* variable might be an error too?
+ // TODO failure to use either ITEM_* variable might be an error too?
}
- return FormValidation.ok(); // XXX assumes it will be OK after substitution, but can we be sure?
+ return FormValidation.ok(); // TODO assumes it will be OK after substitution, but can we be sure?
}
// to route /descriptor/FQCN/xxx to getDescriptor(FQCN).xxx
@@ -2268,7 +2271,7 @@ public class Jenkins extends AbstractCIBase implements ModifiableTopLevelItemGro
if (ctx instanceof ItemGroup) {
ItemGroup g = (ItemGroup) ctx;
Item i = g.getItem(s);
- if (i==null || !i.hasPermission(Item.READ)) { // XXX consider DISCOVER
+ if (i==null || !i.hasPermission(Item.READ)) { // TODO consider DISCOVER
ctx=null; // can't go up further
break;
}
@@ -2336,7 +2339,7 @@ public class Jenkins extends AbstractCIBase implements ModifiableTopLevelItemGro
return null; // this item can't have any children
if (!item.hasPermission(Item.READ))
- return null; // XXX consider DISCOVER
+ return null; // TODO consider DISCOVER
parent = (ItemGroup) item;
}
@@ -2777,6 +2780,8 @@ public class Jenkins extends AbstractCIBase implements ModifiableTopLevelItemGro
bc.commit();
}
+ updateComputerList();
+
rsp.sendRedirect(req.getContextPath()+'/'+toComputer().getUrl()); // back to the computer page
}
@@ -3355,25 +3360,31 @@ public class Jenkins extends AbstractCIBase implements ModifiableTopLevelItemGro
* Run arbitrary Groovy script.
*/
public void doScript(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException {
- doScript(req, rsp, req.getView(this, "_script.jelly"));
+ _doScript(req, rsp, req.getView(this, "_script.jelly"), MasterComputer.localChannel, getACL());
}
/**
* Run arbitrary Groovy script and return result as plain text.
*/
public void doScriptText(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException {
- doScript(req, rsp, req.getView(this, "_scriptText.jelly"));
+ _doScript(req, rsp, req.getView(this, "_scriptText.jelly"), MasterComputer.localChannel, getACL());
}
- private void doScript(StaplerRequest req, StaplerResponse rsp, RequestDispatcher view) throws IOException, ServletException {
+ /**
+ * @since 1.509.1
+ */
+ public static void _doScript(StaplerRequest req, StaplerResponse rsp, RequestDispatcher view, VirtualChannel channel, ACL acl) throws IOException, ServletException {
// ability to run arbitrary script is dangerous
- checkPermission(RUN_SCRIPTS);
+ acl.checkPermission(RUN_SCRIPTS);
String text = req.getParameter("script");
if (text != null) {
+ if (!"POST".equals(req.getMethod())) {
+ throw HttpResponses.error(HttpURLConnection.HTTP_BAD_METHOD, "requires POST");
+ }
try {
req.setAttribute("output",
- RemotingDiagnostics.executeGroovy(text, MasterComputer.localChannel));
+ RemotingDiagnostics.executeGroovy(text, channel));
} catch (InterruptedException e) {
throw new ServletException(e);
}
@@ -3389,7 +3400,7 @@ public class Jenkins extends AbstractCIBase implements ModifiableTopLevelItemGro
*/
@RequirePOST
public void doEval(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException {
- checkPermission(ADMINISTER);
+ checkPermission(RUN_SCRIPTS);
try {
MetaClass mc = WebApp.getCurrent().getMetaClass(getClass());
@@ -3620,7 +3631,7 @@ public class Jenkins extends AbstractCIBase implements ModifiableTopLevelItemGro
|| rest.startsWith("/adjuncts/")
|| rest.startsWith("/signup")
|| rest.startsWith("/tcpSlaveAgentListener")
- // XXX SlaveComputer.doSlaveAgentJnlp; there should be an annotation to request unprotected access
+ // TODO SlaveComputer.doSlaveAgentJnlp; there should be an annotation to request unprotected access
|| rest.matches("/computer/[^/]+/slave-agent[.]jnlp") && "true".equals(Stapler.getCurrentRequest().getParameter("encrypt"))
|| rest.startsWith("/cli")
|| rest.startsWith("/federatedLoginService/")
@@ -3647,8 +3658,8 @@ public class Jenkins extends AbstractCIBase implements ModifiableTopLevelItemGro
*/
public Collection getUnprotectedRootActions() {
Set names = new TreeSet();
- names.add("jnlpJars"); // XXX cleaner to refactor doJnlpJars into a URA
- // XXX consider caching (expiring cache when actions changes)
+ names.add("jnlpJars"); // TODO cleaner to refactor doJnlpJars into a URA
+ // TODO consider caching (expiring cache when actions changes)
for (Action a : getActions()) {
if (a instanceof UnprotectedRootAction) {
names.add(a.getUrlName());
diff --git a/core/src/main/java/jenkins/model/PeepholePermalink.java b/core/src/main/java/jenkins/model/PeepholePermalink.java
index aedd8325b5..397d035743 100644
--- a/core/src/main/java/jenkins/model/PeepholePermalink.java
+++ b/core/src/main/java/jenkins/model/PeepholePermalink.java
@@ -17,6 +17,7 @@ import javax.annotation.Nullable;
import java.io.File;
import java.io.IOException;
import java.io.StringWriter;
+import java.util.Arrays;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -81,12 +82,7 @@ public abstract class PeepholePermalink extends Permalink implements Predicate b=null;
try {
- String target = Util.resolveSymlink(f);
- if (target==null && f.exists()) {
- // if this file isn't a symlink, it must be a regular file
- target = FileUtils.readFileToString(f,"UTF-8").trim();
- }
-
+ String target = readSymlink(f);
if (target!=null) {
int n = Integer.parseInt(Util.getFileName(target));
if (n==RESOLVES_TO_NONE) return null;
@@ -135,19 +131,46 @@ public abstract class PeepholePermalink extends Permalink implements Predicate r);
+
+ /**
+ * Called after a {@linkplain Run#onLoad build is loaded} to which this action was previously {@linkplain #onAttached attached}.
+ */
+ void onLoad(Run,?> r);
+
+}
diff --git a/core/src/main/java/jenkins/model/lazy/AbstractLazyLoadRunMap.java b/core/src/main/java/jenkins/model/lazy/AbstractLazyLoadRunMap.java
index c81c4d4e80..e6d0b86ce6 100644
--- a/core/src/main/java/jenkins/model/lazy/AbstractLazyLoadRunMap.java
+++ b/core/src/main/java/jenkins/model/lazy/AbstractLazyLoadRunMap.java
@@ -215,6 +215,7 @@ public abstract class AbstractLazyLoadRunMap extends AbstractMap i
*
* This is a bit more sophisticated version of forcing GC.
* Primarily for debugging and testing lazy loading behaviour.
+ * @since 1.507
*/
public void purgeCache() {
index = new Index();
@@ -350,7 +351,7 @@ public abstract class AbstractLazyLoadRunMap extends AbstractMap i
* defines what we mean by "nearby" above.
* If EXACT, find #N or return null.
* If ASC, finds the closest #M that satisfies M>=N.
- * If DESC, finds the closest #M that satisfies M<=N.
+ * If DESC, finds the closest #M that satisfies M<=N.
*/
public R search(final int n, final Direction d) {
Entry> c = index.ceilingEntry(n);
@@ -436,10 +437,10 @@ public abstract class AbstractLazyLoadRunMap extends AbstractMap i
// assertion error, but we are so far unable to get to the bottom of this bug.
// but don't let this kill the loading the hard way
String msg = String.format(
- "Assertion error: failing to load #%d %s: lo=%d,hi=%d,size=%d,size2=%d",
- n, d, lo, hi, idOnDisk.size(), initialSize);
- LOGGER.log(Level.WARNING, msg,new Exception());
- throw new ArrayIndexOutOfBoundsException(msg);
+ "JENKINS-15652 Assertion error #1: failing to load %s #%d %s: lo=%d,hi=%d,size=%d,size2=%d",
+ dir, n, d, lo, hi, idOnDisk.size(), initialSize);
+ LOGGER.log(Level.WARNING, msg);
+ return null;
}
while (lo extends AbstractMap i
// assertion error, but we are so far unable to get to the bottom of this bug.
// but don't let this kill the loading the hard way
String msg = String.format(
- "Assertion error: failing to load #%d %s: lo=%d,hi=%d,pivot=%d,size=%d (initial:lo=%d,hi=%d,size=%d)",
- n, d, lo, hi, pivot, idOnDisk.size(), initialLo, initialHi, initialSize);
- LOGGER.log(Level.WARNING, msg,new Exception());
- throw new ArrayIndexOutOfBoundsException(msg);
+ "JENKINS-15652 Assertion error #2: failing to load %s #%d %s: lo=%d,hi=%d,pivot=%d,size=%d (initial:lo=%d,hi=%d,size=%d)",
+ dir, n, d, lo, hi, pivot, idOnDisk.size(), initialLo, initialHi, initialSize);
+ LOGGER.log(Level.WARNING, msg);
+ return null;
}
R r = load(idOnDisk.get(pivot), null);
if (r==null) {
@@ -489,8 +490,8 @@ public abstract class AbstractLazyLoadRunMap extends AbstractMap i
// assertion error, but we are so far unable to get to the bottom of this bug.
// but don't let this kill the loading the hard way
LOGGER.log(Level.WARNING, String.format(
- "Assertion error: failing to load #%d %s: lo=%d,hi=%d,size=%d (initial:lo=%d,hi=%d,size=%d)",
- n,d,lo,hi,idOnDisk.size(), initialLo,initialHi,initialSize),new Exception());
+ "JENKINS-15652 Assertion error #3: failing to load %s #%d %s: lo=%d,hi=%d,size=%d (initial:lo=%d,hi=%d,size=%d)",
+ dir, n,d,lo,hi,idOnDisk.size(), initialLo,initialHi,initialSize));
return null;
}
return getById(idOnDisk.get(lo-1));
diff --git a/core/src/main/java/jenkins/model/lazy/Boundary.java b/core/src/main/java/jenkins/model/lazy/Boundary.java
index 00459523d2..0521ac3afd 100644
--- a/core/src/main/java/jenkins/model/lazy/Boundary.java
+++ b/core/src/main/java/jenkins/model/lazy/Boundary.java
@@ -32,7 +32,7 @@ package jenkins.model.lazy;
* this class provides likes of {@code ceil(X,p)} which is the smallest xi
* that still satisfies xi >= p.
*
- * Simiarly, {@link #HIGHER} is the smallest xi
+ * Similarly, {@link #HIGHER} is the smallest xi
* that still satisfies xi > p.
*
* @author Kohsuke Kawaguchi
diff --git a/core/src/main/java/jenkins/model/lazy/BuildReferenceMapAdapter.java b/core/src/main/java/jenkins/model/lazy/BuildReferenceMapAdapter.java
index f8903e7645..362138a2ac 100644
--- a/core/src/main/java/jenkins/model/lazy/BuildReferenceMapAdapter.java
+++ b/core/src/main/java/jenkins/model/lazy/BuildReferenceMapAdapter.java
@@ -10,6 +10,7 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.Iterator;
+import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -99,7 +100,7 @@ class BuildReferenceMapAdapter implements SortedMap {
}
public boolean containsValue(Object value) {
- return core.containsValue(value);
+ return core.containsValue(value); // TODO should this be core.containsValue(wrap(value))?
}
public R get(Object key) {
@@ -125,7 +126,7 @@ class BuildReferenceMapAdapter implements SortedMap {
@Override
public boolean equals(Object o) {
- return core.equals(o);
+ return core.equals(o); // TODO this is wrong
}
@Override
@@ -133,6 +134,10 @@ class BuildReferenceMapAdapter implements SortedMap {
return core.hashCode();
}
+ @Override public String toString() {
+ return new LinkedHashMap(this).toString();
+ }
+
private class CollectionAdapter implements Collection {
private final Collection> core;
diff --git a/core/src/main/java/jenkins/mvn/GlobalMavenConfig.java b/core/src/main/java/jenkins/mvn/GlobalMavenConfig.java
new file mode 100644
index 0000000000..67ace424e8
--- /dev/null
+++ b/core/src/main/java/jenkins/mvn/GlobalMavenConfig.java
@@ -0,0 +1,47 @@
+package jenkins.mvn;
+
+import hudson.Extension;
+import jenkins.model.GlobalConfiguration;
+import net.sf.json.JSONObject;
+
+import org.kohsuke.stapler.StaplerRequest;
+
+//as close as it gets to the global Maven Project configuration
+@Extension(ordinal = 50)
+public class GlobalMavenConfig extends GlobalConfiguration {
+ private SettingsProvider settingsProvider;
+ private GlobalSettingsProvider globalSettingsProvider;
+
+ public GlobalMavenConfig() {
+ load();
+ }
+
+ @Override
+ public boolean configure(StaplerRequest req, JSONObject json) throws FormException {
+ req.bindJSON(this, json);
+ return true;
+ }
+
+ public void setGlobalSettingsProvider(GlobalSettingsProvider globalSettingsProvider) {
+ this.globalSettingsProvider = globalSettingsProvider;
+ save();
+ }
+
+ public void setSettingsProvider(SettingsProvider settingsProvider) {
+ this.settingsProvider = settingsProvider;
+ save();
+ }
+
+ public GlobalSettingsProvider getGlobalSettingsProvider() {
+ return globalSettingsProvider != null ? globalSettingsProvider : new DefaultGlobalSettingsProvider();
+ }
+
+ public SettingsProvider getSettingsProvider() {
+ return settingsProvider != null ? settingsProvider : new DefaultSettingsProvider();
+ }
+
+ public static GlobalMavenConfig get() {
+ return GlobalConfiguration.all().get(GlobalMavenConfig.class);
+ }
+
+}
diff --git a/core/src/main/java/jenkins/security/ConfidentialStore.java b/core/src/main/java/jenkins/security/ConfidentialStore.java
index de82437653..9b3efdbec6 100644
--- a/core/src/main/java/jenkins/security/ConfidentialStore.java
+++ b/core/src/main/java/jenkins/security/ConfidentialStore.java
@@ -63,7 +63,11 @@ public abstract class ConfidentialStore {
public static @Nonnull ConfidentialStore get() {
if (TEST!=null) return TEST.get();
- Lookup lookup = Jenkins.getInstance().lookup;
+ Jenkins j = Jenkins.getInstance();
+ if (j == null) {
+ throw new IllegalStateException("cannot initialize confidential key store until Jenkins has started");
+ }
+ Lookup lookup = j.lookup;
ConfidentialStore cs = lookup.get(ConfidentialStore.class);
if (cs==null) {
try {
diff --git a/core/src/main/java/jenkins/security/RekeySecretAdminMonitor.java b/core/src/main/java/jenkins/security/RekeySecretAdminMonitor.java
index a5ce72c824..6ccfabd1e3 100644
--- a/core/src/main/java/jenkins/security/RekeySecretAdminMonitor.java
+++ b/core/src/main/java/jenkins/security/RekeySecretAdminMonitor.java
@@ -114,7 +114,7 @@ public class RekeySecretAdminMonitor extends AdministrativeMonitor implements St
if(req.hasParameter("dismiss")) {
disable(true);
} else
- throw HttpResponses.error(400,"Invalid request submission");
+ throw HttpResponses.error(400,"Invalid request submission: " + req.getParameterMap());
return HttpResponses.redirectViaContextPath("/manage");
}
diff --git a/core/src/main/java/jenkins/util/JSONSignatureValidator.java b/core/src/main/java/jenkins/util/JSONSignatureValidator.java
index 684f8ebe6c..77470f39ba 100644
--- a/core/src/main/java/jenkins/util/JSONSignatureValidator.java
+++ b/core/src/main/java/jenkins/util/JSONSignatureValidator.java
@@ -32,6 +32,7 @@ import java.util.logging.Logger;
/**
* @author Kohsuke Kawaguchi
+ * @since 1.482
*/
public class JSONSignatureValidator {
private final String name;
diff --git a/core/src/main/java/jenkins/util/ProgressiveRendering.java b/core/src/main/java/jenkins/util/ProgressiveRendering.java
index 04b2f97956..d659d0b5ed 100644
--- a/core/src/main/java/jenkins/util/ProgressiveRendering.java
+++ b/core/src/main/java/jenkins/util/ProgressiveRendering.java
@@ -44,7 +44,7 @@ import org.kohsuke.stapler.bind.JavaScriptMethod;
* (since it may be canceled if the user simply browses to another page while it is running).
*
*
Write a {@code
diff --git a/core/src/main/resources/hudson/model/View/AsynchPeople/index.properties b/core/src/main/resources/hudson/model/View/AsynchPeople/index.properties
new file mode 100644
index 0000000000..228d60ca66
--- /dev/null
+++ b/core/src/main/resources/hudson/model/View/AsynchPeople/index.properties
@@ -0,0 +1,23 @@
+# The MIT License
+#
+# Copyright 2013 Jesse Glick.
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+
+blurb=Includes all known \u201cusers\u201d, including login identities which the current security realm can enumerate, as well as people mentioned in commit messages in recorded changelogs.
diff --git a/core/src/main/resources/hudson/model/View/sidepanel.jelly b/core/src/main/resources/hudson/model/View/sidepanel.jelly
index 599e8a3dc8..0e6336039a 100644
--- a/core/src/main/resources/hudson/model/View/sidepanel.jelly
+++ b/core/src/main/resources/hudson/model/View/sidepanel.jelly
@@ -70,9 +70,8 @@ THE SOFTWARE.
-
-
+
diff --git a/core/src/main/resources/hudson/model/labels/LabelAtom/configure_fr.properties b/core/src/main/resources/hudson/model/labels/LabelAtom/configure_fr.properties
new file mode 100644
index 0000000000..781cda7c2a
--- /dev/null
+++ b/core/src/main/resources/hudson/model/labels/LabelAtom/configure_fr.properties
@@ -0,0 +1,25 @@
+# The MIT License
+#
+# Copyright (c) 2011, Seiji Sogabe
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+
+Name=Nom
+Save=Sauver
+title={0} Configuration
diff --git a/core/src/main/resources/hudson/slaves/JNLPLauncher/main.jelly b/core/src/main/resources/hudson/slaves/JNLPLauncher/main.jelly
index 7f7c31a13e..6a9d0066ec 100644
--- a/core/src/main/resources/hudson/slaves/JNLPLauncher/main.jelly
+++ b/core/src/main/resources/hudson/slaves/JNLPLauncher/main.jelly
@@ -65,7 +65,7 @@ THE SOFTWARE.
\ No newline at end of file
diff --git a/core/src/main/resources/hudson/tasks/junit/CaseResult/index.jelly b/core/src/main/resources/hudson/tasks/junit/CaseResult/index.jelly
index 036d0211a2..b68fefc07c 100644
--- a/core/src/main/resources/hudson/tasks/junit/CaseResult/index.jelly
+++ b/core/src/main/resources/hudson/tasks/junit/CaseResult/index.jelly
@@ -34,7 +34,7 @@ THE SOFTWARE.
-
\ No newline at end of file
+
diff --git a/core/src/main/resources/jenkins/diagnostics/SecurityIsOffMonitor/message_fr.properties b/core/src/main/resources/jenkins/diagnostics/SecurityIsOffMonitor/message_fr.properties
new file mode 100644
index 0000000000..7225894844
--- /dev/null
+++ b/core/src/main/resources/jenkins/diagnostics/SecurityIsOffMonitor/message_fr.properties
@@ -0,0 +1,28 @@
+#
+# The MIT License
+#
+# Copyright (c) 2013, Kohsuke Kawaguchi
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+#
+
+blurb=La non s\u00e9curisation de Jenkins permet \u00e0 quiconque sur le r\u00e9seau de lancer des processus en votre nom. \
+ Envisager au moins une authentification permettant de d\u00e9courager les abus.
+Setup\ Security=Configuration de la s\u00e9curit\u00e9
+Dismiss=Cacher ce message
diff --git a/core/src/main/resources/jenkins/management/Messages_pl.properties b/core/src/main/resources/jenkins/management/Messages_pl.properties
index 3b7b18b830..174a79fbe6 100644
--- a/core/src/main/resources/jenkins/management/Messages_pl.properties
+++ b/core/src/main/resources/jenkins/management/Messages_pl.properties
@@ -32,4 +32,5 @@ PluginsLink.DisplayName=Zarz\u0105dzaj dodatkami
ShutdownLink.DisplayName_prepare=Przygotuj do wy\u0142\u0105czenia
ReloadLink.DisplayName=Odczytaj ponownie konfiguracj\u0119 z dysku
ShutdownLink.Description=Zatrzyma wykonanie nowych build\u00F3w, tak by system m\u00F3g\u0142by\u0107 bezpiecznie wy\u0142\u0105czony.
+ShutdownLink.DisplayName_cancel=Anuluj wy\u0142\u0105czenie
SystemInfoLink.DisplayName=Informacje o systemie
diff --git a/core/src/main/resources/jenkins/management/PluginsLink/info_pl.properties b/core/src/main/resources/jenkins/management/PluginsLink/info_pl.properties
new file mode 100644
index 0000000000..8baf283cd3
--- /dev/null
+++ b/core/src/main/resources/jenkins/management/PluginsLink/info_pl.properties
@@ -0,0 +1,25 @@
+#
+# The MIT License
+#
+# Copyright (c) 2013, Lukasz Jader
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+#
+
+updates\ available=dost\u0119pne s\u0105 aktualizacje
diff --git a/core/src/main/resources/jenkins/model/CauseOfInterruption/UserInterruption/summary_pl.properties b/core/src/main/resources/jenkins/model/CauseOfInterruption/UserInterruption/summary_pl.properties
new file mode 100644
index 0000000000..0095ece665
--- /dev/null
+++ b/core/src/main/resources/jenkins/model/CauseOfInterruption/UserInterruption/summary_pl.properties
@@ -0,0 +1 @@
+blurb=Przerwane przez u\u017Cytkownika {0}
\ No newline at end of file
diff --git a/core/src/main/resources/jenkins/model/Jenkins/legend_fr.properties b/core/src/main/resources/jenkins/model/Jenkins/legend_fr.properties
index b1d2443b35..322a7d9d53 100644
--- a/core/src/main/resources/jenkins/model/Jenkins/legend_fr.properties
+++ b/core/src/main/resources/jenkins/model/Jenkins/legend_fr.properties
@@ -20,23 +20,17 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
-grey=Le projet n''a jamais été construit avant ou est désactivé.
+grey=Le projet n''a jamais \u00e9t\u00e9 construit avant ou est d\u00e9sactiv\u00e9.
grey_anime=Le premier build du projet est en cours.
-blue=Le dernier build s''est achevé avec succès.
-blue_anime=Le dernier build s''est achevé avec succès. Un nouveau build est en cours.
-yellow=Le dernier build s''est achevé avec succès mais est instable. \
- Cela est utilisé principalement pour représenter des échecs sur les tests.
-yellow_anime=Le dernier build s''est achevé avec succès mais est instable. Un nouveau build est en cours.
+blue=Le dernier build s''est achev\u00e9 avec succ\u00e8s.
+blue_anime=Le dernier build s''est achev\u00e9 avec succ\u00e8s. Un nouveau build est en cours.
+yellow=Le dernier build s''est achev\u00e9 avec succ\u00e8s mais est instable. \
+ Cela est utilis\u00e9 principalement pour repr\u00e9senter des \u00e9checs sur les tests.
+yellow_anime=Le dernier build s''est achev\u00e9 avec succ\u00e8s mais est instable. Un nouveau build est en cours.
+red=Le dernier build est en \u00e9chec.
+red_anime=Le dernier build est en \u00e9chec. Un nouveau build est en cours.
health-81plus=La sant\u00E9 du projet est au dessus de 80%. Vous pouvez passer votre souris au dessus de l''ic\u00F4ne du projet pour plus d''informations.
-red=Le dernier build est en échec.
-red_anime=Le dernier build est en échec. Un nouveau build est en cours.
health-61to80=La sant\u00E9 du projet est entre 60% et 80%. Vous pouvez passer votre souris au dessus de l''ic\u00F4ne du projet pour plus d''informations.
-health-80plus=La stabilité du projet est d''au moins 80%. Placez la souris au-dessus de l''icône du projet pour plus de détails.
health-41to60=La sant\u00E9 du projet est entre 40% et 60%. Vous pouvez passer votre souris au dessus de l''ic\u00F4ne du projet pour plus d''informations.
-health-60to79=La stabilité du projet se situe entre 60% et 80%. Placez la souris au-dessus de l''icône du projet pour plus de détails.
health-21to40=La sant\u00E9 du projet est entre 20% et 40%. Vous pouvez passer votre souris au dessus de l''ic\u00F4ne du projet pour plus d''informations.
-health-40to59=La stabilité du projet se situe entre 40% et 60%. Placez la souris au-dessus de l''icône du projet pour plus de détails.
health-00to20=La sant\u00E9 du projet est en dessous de 20%. Vous pouvez passer votre souris au dessus de l''ic\u00F4ne du projet pour plus d''informations.
-health-20to39=La stabilité du projet se situe entre 20% et 40%. Placez la souris au-dessus de l''icône du projet pour plus de détails.
-health-00to19=La stabilité du projet est de moins de 20%. Placez la souris au-dessus de l''icône du projet pour plus de détails.
-
diff --git a/core/src/main/resources/jenkins/model/Jenkins/manage.jelly b/core/src/main/resources/jenkins/model/Jenkins/manage.jelly
index bb056ce549..5bac7f576c 100644
--- a/core/src/main/resources/jenkins/model/Jenkins/manage.jelly
+++ b/core/src/main/resources/jenkins/model/Jenkins/manage.jelly
@@ -33,7 +33,7 @@ THE SOFTWARE.
${taskTags!=null and attrs.contextMenu!='false' ? taskTags.add(href,iconUrl,title,requiresConfirmation,requiresConfirmation) : null}
-
+
diff --git a/core/src/main/resources/jenkins/mvn/GlobalMavenConfig/config.groovy b/core/src/main/resources/jenkins/mvn/GlobalMavenConfig/config.groovy
new file mode 100644
index 0000000000..79e427df0f
--- /dev/null
+++ b/core/src/main/resources/jenkins/mvn/GlobalMavenConfig/config.groovy
@@ -0,0 +1,8 @@
+package jenkins.mvn.GlobalMavenConfig;
+
+def f = namespace(lib.FormTagLib)
+
+f.section(title:_("Maven Configuration")) {
+ f.dropdownDescriptorSelector(title:_("Default settings provider"), field:"settingsProvider")
+ f.dropdownDescriptorSelector(title:_("Default global settings provider"), field:"globalSettingsProvider")
+}
diff --git a/core/src/main/resources/jenkins/widgets/BuildQueueWidget/index.groovy b/core/src/main/resources/jenkins/widgets/BuildQueueWidget/index.groovy
new file mode 100644
index 0000000000..727d053fa9
--- /dev/null
+++ b/core/src/main/resources/jenkins/widgets/BuildQueueWidget/index.groovy
@@ -0,0 +1,5 @@
+package jenkins.widgets.BuildQueueWidget;
+
+def t = namespace(lib.JenkinsTagLib.class)
+
+t.queue(items:view.approximateQueueItemsQuickly)
\ No newline at end of file
diff --git a/core/src/main/resources/jenkins/widgets/ExecutorsWidget/index.groovy b/core/src/main/resources/jenkins/widgets/ExecutorsWidget/index.groovy
new file mode 100644
index 0000000000..f963db4849
--- /dev/null
+++ b/core/src/main/resources/jenkins/widgets/ExecutorsWidget/index.groovy
@@ -0,0 +1,5 @@
+package jenkins.widgets.ExecutorsWidget
+
+def t = namespace(lib.JenkinsTagLib.class)
+
+t.executors(computers:view.computers)
\ No newline at end of file
diff --git a/core/src/main/resources/lib/form/apply_fr.properties b/core/src/main/resources/lib/form/apply_fr.properties
new file mode 100644
index 0000000000..77842f0cc6
--- /dev/null
+++ b/core/src/main/resources/lib/form/apply_fr.properties
@@ -0,0 +1,23 @@
+# The MIT License
+#
+# Copyright (c) 2012, Kohsuke Kawaguchi, Sun Microsystems, Inc., and a number of other of contributers
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+
+Apply=Appliquer
diff --git a/core/src/main/resources/lib/form/booleanRadio_fr.properties b/core/src/main/resources/lib/form/booleanRadio_fr.properties
new file mode 100644
index 0000000000..d0869be48e
--- /dev/null
+++ b/core/src/main/resources/lib/form/booleanRadio_fr.properties
@@ -0,0 +1,24 @@
+# The MIT License
+#
+# Copyright (c) 2004-2010, Sun Microsystems, Inc.
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+
+Yes=Oui
+No=Non
diff --git a/core/src/main/resources/lib/form/breadcrumb-config-outline_fr.properties b/core/src/main/resources/lib/form/breadcrumb-config-outline_fr.properties
new file mode 100644
index 0000000000..e212b91926
--- /dev/null
+++ b/core/src/main/resources/lib/form/breadcrumb-config-outline_fr.properties
@@ -0,0 +1,23 @@
+# The MIT License
+#
+# Copyright (c) 2012, Seiji Sogabe
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+
+configuration=configuration
diff --git a/core/src/main/resources/lib/form/dropdownDescriptorSelector.jelly b/core/src/main/resources/lib/form/dropdownDescriptorSelector.jelly
index c68760382f..aad0a87e51 100644
--- a/core/src/main/resources/lib/form/dropdownDescriptorSelector.jelly
+++ b/core/src/main/resources/lib/form/dropdownDescriptorSelector.jelly
@@ -39,7 +39,8 @@ THE SOFTWARE.
If unspecified, inferred from the type of the field.
- If specified, this will be chosen as the default value in case the current selection is null.
+ If specified, this will be chosen as the default value in case the current selection is null. The default can be an specific instance or a descriptor e.g.
+ ${descriptor.defaultSettingsProvider} or ${descriptor.defaultSettingsProvider.descriptor}. In the later case, the from input fields will be empty.
@@ -49,6 +50,7 @@ THE SOFTWARE.
+ <input type="number" ... /> to be
used inside <f:entry/>
-
+
Used for databinding. TBD.
-
+
This becomes @name of the <input> tag.
If @field is specified, this value is inferred from it.
diff --git a/core/src/main/resources/lib/form/textarea.jelly b/core/src/main/resources/lib/form/textarea.jelly
index 14bbe71eea..c61fc19cab 100644
--- a/core/src/main/resources/lib/form/textarea.jelly
+++ b/core/src/main/resources/lib/form/textarea.jelly
@@ -93,6 +93,9 @@ THE SOFTWARE.
-
+
diff --git a/core/src/main/resources/lib/layout/layout_fr.properties b/core/src/main/resources/lib/layout/layout_fr.properties
index 39b15e91f2..dbc67c5460 100644
--- a/core/src/main/resources/lib/layout/layout_fr.properties
+++ b/core/src/main/resources/lib/layout/layout_fr.properties
@@ -24,5 +24,3 @@ search=rechercher
searchBox.url=http://wiki.jenkins-ci.org/display/JENKINS/Search+Box
Page\ generated=Page g\u00E9n\u00E9r\u00E9e le
logout=Se d\u00E9connecter
-DISABLE\ AUTO\ REFRESH=ANNULER LE RAFRA\u00CECHISSEMENT AUTOMATIQUE
-ENABLE\ AUTO\ REFRESH=ACTIVER LE RAFRA\u00CECHISSEMENT AUTOMATIQUE
diff --git a/core/src/main/resources/lib/layout/progressiveRendering.jelly b/core/src/main/resources/lib/layout/progressiveRendering.jelly
index f4f3cb3391..954affc0b2 100644
--- a/core/src/main/resources/lib/layout/progressiveRendering.jelly
+++ b/core/src/main/resources/lib/layout/progressiveRendering.jelly
@@ -35,7 +35,7 @@ THE SOFTWARE.
-
+
diff --git a/core/src/main/resources/lib/layout/progressiveRendering/progressiveRendering.js b/core/src/main/resources/lib/layout/progressiveRendering/progressiveRendering.js
index 1aa5914476..e56ab5bb53 100644
--- a/core/src/main/resources/lib/layout/progressiveRendering/progressiveRendering.js
+++ b/core/src/main/resources/lib/layout/progressiveRendering/progressiveRendering.js
@@ -29,7 +29,7 @@ function progressivelyRender(handler, callback) {
callback(r.data);
$('status').style.display = 'none';
} else if (r.status == 'canceled') {
- // XXX ugly; replace with single tr of class=unknown?
+ // TODO ugly; replace with single tr of class=unknown?
$$('#status .progress-bar-done')[0].innerHTML = 'Aborted.';
} else if (r.status == 'error') {
$$('#status .progress-bar-done')[0].style.width = '100%';
diff --git a/core/src/main/resources/lib/layout/progressiveRendering_fr.properties b/core/src/main/resources/lib/layout/progressiveRendering_fr.properties
new file mode 100644
index 0000000000..c4e3e757fb
--- /dev/null
+++ b/core/src/main/resources/lib/layout/progressiveRendering_fr.properties
@@ -0,0 +1,23 @@
+# The MIT License
+#
+# Copyright (c) 2013, Chunghwa Telecom Co., Ltd., Pei-Tang Huang
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+
+progressMessage=Calcul en cours.
diff --git a/core/src/main/resources/lib/layout/task_fr.properties b/core/src/main/resources/lib/layout/task_fr.properties
new file mode 100644
index 0000000000..7fae593429
--- /dev/null
+++ b/core/src/main/resources/lib/layout/task_fr.properties
@@ -0,0 +1,23 @@
+# The MIT License
+#
+# Copyright (c) 2013, Seiji Sogabe
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+
+Done.=Fait.
diff --git a/core/src/main/resources/windows-service/jenkins-slave.xml b/core/src/main/resources/windows-service/jenkins-slave.xml
index 6b28c087c2..b4d3bf58f2 100644
--- a/core/src/main/resources/windows-service/jenkins-slave.xml
+++ b/core/src/main/resources/windows-service/jenkins-slave.xml
@@ -44,4 +44,6 @@ THE SOFTWARE.
-->
rotate
-
\ No newline at end of file
+
+
+
diff --git a/core/src/main/resources/windows-service/jenkins.xml b/core/src/main/resources/windows-service/jenkins.xml
index 15e83bb462..6e151b36dd 100644
--- a/core/src/main/resources/windows-service/jenkins.xml
+++ b/core/src/main/resources/windows-service/jenkins.xml
@@ -45,4 +45,6 @@ THE SOFTWARE.
-->
rotate
-
\ No newline at end of file
+
+
+
diff --git a/core/src/test/java/hudson/FilePathTest.java b/core/src/test/java/hudson/FilePathTest.java
index 8641bcef1c..04dca89a1a 100644
--- a/core/src/test/java/hudson/FilePathTest.java
+++ b/core/src/test/java/hudson/FilePathTest.java
@@ -449,7 +449,7 @@ public class FilePathTest extends ChannelTestCase {
assertValidateAntFileMask(Messages.FilePath_validateAntFileMask_portionMatchButPreviousNotMatchAndSuggest("**/*.js", "**", "**/*.js"), d, "**/*.js");
assertValidateAntFileMask(Messages.FilePath_validateAntFileMask_doesntMatchAnything("index.htm"), d, "index.htm");
assertValidateAntFileMask(Messages.FilePath_validateAntFileMask_doesntMatchAndSuggest("f.html", "d1/d2/d3/f.html"), d, "f.html");
- // XXX lots more to test, e.g. multiple patterns separated by commas; ought to have full code coverage for this method
+ // TODO lots more to test, e.g. multiple patterns separated by commas; ought to have full code coverage for this method
} finally {
Util.deleteRecursive(tmp);
}
diff --git a/core/src/test/java/hudson/FunctionsTest.java b/core/src/test/java/hudson/FunctionsTest.java
index 03b6179e81..84b03bc481 100644
--- a/core/src/test/java/hudson/FunctionsTest.java
+++ b/core/src/test/java/hudson/FunctionsTest.java
@@ -28,6 +28,7 @@ import static org.powermock.api.mockito.PowerMockito.mock;
import static org.powermock.api.mockito.PowerMockito.mockStatic;
import static org.powermock.api.mockito.PowerMockito.when;
import hudson.model.Action;
+import hudson.model.Item;
import hudson.model.ItemGroup;
import hudson.model.TopLevelItem;
import hudson.model.View;
@@ -170,6 +171,47 @@ public class FunctionsTest {
String result = Functions.getRelativeLinkTo(i);
assertEquals("job/i/", result);
}
+
+ @Bug(17713)
+ @PrepareForTest({Stapler.class, Jenkins.class})
+ @Test public void getRelativeLinkTo_MavenModules() throws Exception {
+ Jenkins j = createMockJenkins();
+ StaplerRequest req = createMockRequest("/jenkins");
+ mockStatic(Stapler.class);
+ when(Stapler.getCurrentRequest()).thenReturn(req);
+ TopLevelItemAndItemGroup ms = mock(TopLevelItemAndItemGroup.class);
+ when(ms.getShortUrl()).thenReturn("job/ms/");
+ // TODO "." (in second ancestor) is what Stapler currently fails to do. Could edit test to use ".." but set a different request path?
+ createMockAncestors(req, createAncestor(j, "../.."), createAncestor(ms, "."));
+ Item m = mock(Item.class);
+ when(m.getParent()).thenReturn(ms);
+ when(m.getShortUrl()).thenReturn("grp$art/");
+ assertEquals("grp$art/", Functions.getRelativeLinkTo(m));
+ }
+
+ @Test
+ public void testGetRelativeDisplayName() {
+ Item i = mock(Item.class);
+ when(i.getName()).thenReturn("jobName");
+ when(i.getFullDisplayName()).thenReturn("displayName");
+ assertEquals("displayName",Functions.getRelativeDisplayNameFrom(i, null));
+ }
+
+ @Test
+ public void testGetRelativeDisplayNameInsideItemGroup() {
+ Item i = mock(Item.class);
+ when(i.getName()).thenReturn("jobName");
+ when(i.getDisplayName()).thenReturn("displayName");
+ TopLevelItemAndItemGroup ig = mock(TopLevelItemAndItemGroup.class);
+ Jenkins j = mock(Jenkins.class);
+ when(ig.getName()).thenReturn("parent");
+ when(ig.getDisplayName()).thenReturn("parentDisplay");
+ when(ig.getParent()).thenReturn((ItemGroup) j);
+ when(i.getParent()).thenReturn(ig);
+
+ assertEquals("displayName", Functions.getRelativeDisplayNameFrom(i, ig));
+ assertEquals("parentDisplay » displayName", Functions.getRelativeDisplayNameFrom(i, j));
+ }
private void createMockAncestors(StaplerRequest req, Ancestor... ancestors) {
List ancestorsList = Arrays.asList(ancestors);
@@ -239,4 +281,12 @@ public class FunctionsTest {
}
}
+ @Bug(17030)
+ @Test
+ public void testBreakableString() {
+
+ assertEquals("Hello world!", Functions.breakableString("Hello world!"));
+ assertEquals("H,e.l/l:o-w_o=+|d", Functions.breakableString("H,e.l/l:o-w_o=+|d"));
+ assertEquals("ALongStringThatCanNotBeBrokenByDefault", Functions.breakableString("ALongStringThatCanNotBeBrokenByDefault"));
+ }
}
diff --git a/core/src/test/java/hudson/logging/LogRecorderTest.java b/core/src/test/java/hudson/logging/LogRecorderTest.java
new file mode 100644
index 0000000000..5a9043e6d2
--- /dev/null
+++ b/core/src/test/java/hudson/logging/LogRecorderTest.java
@@ -0,0 +1,51 @@
+/*
+ * The MIT License
+ *
+ * Copyright 2013 Jesse Glick.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package hudson.logging;
+
+import java.util.logging.Level;
+import java.util.logging.LogRecord;
+import org.junit.Test;
+import static org.junit.Assert.*;
+import org.jvnet.hudson.test.Bug;
+
+public class LogRecorderTest {
+
+ @Bug(17983)
+ @Test public void targetIncludes() {
+ assertTrue(includes("hudson", "hudson"));
+ assertFalse(includes("hudson", "hudsone"));
+ assertFalse(includes("hudson", "hudso"));
+ assertTrue(includes("hudson", "hudson.model.Hudson"));
+ assertFalse(includes("hudson", "jenkins.model.Jenkins"));
+ assertTrue(includes("", "hudson.model.Hudson"));
+ }
+
+ private static boolean includes(String target, String logger) {
+ LogRecord r = new LogRecord(Level.INFO, "whatever");
+ r.setLoggerName(logger);
+ return new LogRecorder.Target(target, Level.INFO).includes(r);
+ }
+
+}
diff --git a/core/src/test/java/hudson/markup/MyspacePolicyTest.java b/core/src/test/java/hudson/markup/MyspacePolicyTest.java
index cb1a63e2bb..75ac37b28c 100644
--- a/core/src/test/java/hudson/markup/MyspacePolicyTest.java
+++ b/core/src/test/java/hudson/markup/MyspacePolicyTest.java
@@ -43,7 +43,7 @@ public class MyspacePolicyTest extends Assert {
assertReject("script","");
assertIntact("");
- assertIntact("");
+ assertReject("link", "");
assertIntact("
inline CSS
");
assertIntact(" ");
diff --git a/core/src/test/java/hudson/matrix/CombinationFilterUsingBuildParamsTest.java b/core/src/test/java/hudson/matrix/CombinationFilterUsingBuildParamsTest.java
new file mode 100644
index 0000000000..51caa0e6d2
--- /dev/null
+++ b/core/src/test/java/hudson/matrix/CombinationFilterUsingBuildParamsTest.java
@@ -0,0 +1,284 @@
+/*
+ * The MIT License
+ *
+ * Copyright (c) 2012, RedHat Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package hudson.matrix;
+
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import hudson.ExtensionList;
+import hudson.matrix.MatrixBuild.MatrixBuildExecution;
+import hudson.matrix.listeners.MatrixBuildListener;
+import hudson.model.AbstractItem;
+import hudson.model.BuildListener;
+import hudson.model.Cause;
+import hudson.model.Node;
+import hudson.model.ParametersAction;
+import hudson.model.Result;
+import hudson.model.Run;
+import hudson.model.StringParameterValue;
+
+import java.io.IOException;
+import java.io.PrintStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+
+import jenkins.model.Jenkins;
+
+import org.apache.commons.io.output.ByteArrayOutputStream;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.verification.VerificationMode;
+import org.powermock.api.mockito.PowerMockito;
+import org.powermock.core.classloader.annotations.PrepareForTest;
+import org.powermock.modules.junit4.PowerMockRunner;
+
+/**
+ * Make sure that the combination filter schedules correct builds in correct order
+ *
+ * @author ogondza
+ */
+@RunWith(PowerMockRunner.class)
+@PrepareForTest( {Jenkins.class, MatrixBuildListener.class, MatrixProject.class, AbstractItem.class})
+public class CombinationFilterUsingBuildParamsTest {
+
+ /**
+ * Execute releases: experimental, stable, beta, devel
+ *
+ * x s b d
+ * 0.1
+ * 0.9 * * * *
+ * 1 * * *
+ * 2 * *
+ * 3 *
+ */
+ private static final String filter =
+ String.format(
+ "(%s) || (%s) || (%s)",
+ "RELEASE == 'stable' && VERSION == '1'",
+ "RELEASE == 'beta' && VERSION >= '1' && VERSION <= '2'",
+ "RELEASE == 'devel' && VERSION >= '1' && VERSION <= '3'"
+ );
+
+ private static final String touchstoneFilter = "VERSION == '0.9'";
+
+ private static final List releases = Arrays.asList(
+ "stable", "beta", "devel", "experimental"
+ );
+
+ private final Map confs = new HashMap();
+ private final MatrixExecutionStrategy strategy = new DefaultMatrixExecutionStrategyImpl (
+ true, touchstoneFilter, Result.SUCCESS, new NoopMatrixConfigurationSorter()
+ );
+
+ private MatrixProject project;
+ @Mock private MatrixBuildExecution execution;
+ @Mock private MatrixBuild build;
+ @Mock private MatrixRun run;
+ @Mock private BuildListener listener;
+
+ @Mock private Jenkins jenkins;
+ @Mock private ExtensionList extensions;
+
+ @Before
+ public void setUp() throws Exception {
+
+ MockitoAnnotations.initMocks(this);
+
+ usingDummyJenkins();
+ usingNoListeners();
+ usingDummyProject();
+ usingDummyExecution();
+ withReleaseAxis(releases);
+ }
+
+ @Test
+ public void testCombinationFilterV01() throws InterruptedException, IOException {
+
+ givenTheVersionIs("0.1");
+
+ strategy.run(execution);
+
+ wasNotBuilt(confs.get("devel"));
+ wasNotBuilt(confs.get("beta"));
+ wasNotBuilt(confs.get("stable"));
+ wasNotBuilt(confs.get("experimental"));
+ }
+
+ @Test
+ public void testCombinationFilterV09() throws InterruptedException, IOException {
+
+ givenTheVersionIs("0.9");
+
+ strategy.run(execution);
+
+ wasBuilt(confs.get("devel"));
+ wasBuilt(confs.get("beta"));
+ wasBuilt(confs.get("stable"));
+ wasBuilt(confs.get("experimental"));
+ }
+
+ @Test
+ public void testCombinationFilterV1() throws InterruptedException, IOException {
+
+ givenTheVersionIs("1");
+
+ strategy.run(execution);
+
+ wasBuilt(confs.get("devel"));
+ wasBuilt(confs.get("beta"));
+ wasBuilt(confs.get("stable"));
+ wasNotBuilt(confs.get("experimental"));
+ }
+
+ @Test
+ public void testCombinationFilterV2() throws InterruptedException, IOException {
+
+ givenTheVersionIs("2");
+
+ strategy.run(execution);
+
+ wasBuilt(confs.get("devel"));
+ wasBuilt(confs.get("beta"));
+ wasNotBuilt(confs.get("stable"));
+ wasNotBuilt(confs.get("experimental"));
+ }
+
+ @Test
+ public void testCombinationFilterV3() throws InterruptedException, IOException {
+
+ givenTheVersionIs("3");
+
+ strategy.run(execution);
+
+ wasBuilt(confs.get("devel"));
+ wasNotBuilt(confs.get("beta"));
+ wasNotBuilt(confs.get("stable"));
+ wasNotBuilt(confs.get("experimental"));
+ }
+
+ private void usingDummyProject() {
+
+ project = PowerMockito.mock(MatrixProject.class);
+
+ PowerMockito.when(build.getParent()).thenReturn(project);
+ PowerMockito.when(project.getUrl()).thenReturn("/my/project/");
+
+ when(project.getAxes()).thenReturn(new AxisList(new Axis("RELEASE", releases)));
+ when(project.getCombinationFilter()).thenReturn(filter);
+ }
+
+ private void usingDummyExecution() {
+
+ when(execution.getProject()).thenReturn(project);
+ when(execution.getBuild()).thenReturn(build);
+ when(execution.getListener()).thenReturn(listener);
+
+ // throw away logs
+ when(listener.getLogger()).thenReturn(new PrintStream(
+ new ByteArrayOutputStream()
+ ));
+
+ // Succeed immediately
+ when(run.isBuilding()).thenReturn(false);
+ when(run.getResult()).thenReturn(Result.SUCCESS);
+ }
+
+ private void usingDummyJenkins() {
+
+ PowerMockito.mockStatic(Jenkins.class);
+ when(Jenkins.getInstance()).thenReturn(jenkins);
+ when(jenkins.getNodes()).thenReturn(new ArrayList());
+ }
+
+ private void usingNoListeners() {
+
+ when(extensions.iterator()).thenReturn(new ArrayList().iterator());
+ when(MatrixBuildListener.all()).thenReturn(extensions);
+ }
+
+ private void withReleaseAxis(final List releases) {
+
+ for(final String release: releases) {
+
+ confs.put(release, getConfiguration("RELEASE=" + release));
+ }
+
+ when(execution.getActiveConfigurations()).thenReturn(
+ new HashSet(confs.values())
+ );
+ }
+
+ private MatrixConfiguration getConfiguration (final String axis) {
+
+ final MatrixConfiguration conf = mock(MatrixConfiguration.class);
+ when(conf.getParent()).thenReturn(project);
+ when(conf.getCombination()).thenReturn(Combination.fromString(axis));
+ when(conf.getDisplayName()).thenReturn(axis);
+ when(conf.getUrl()).thenReturn(axis);
+ when(conf.getBuildByNumber(anyInt())).thenReturn(run);
+
+ return conf;
+ }
+
+ private void givenTheVersionIs(final String version) {
+
+ final ParametersAction parametersAction = new ParametersAction(
+ new StringParameterValue("VERSION", version)
+ );
+
+ when(build.getAction(ParametersAction.class))
+ .thenReturn(parametersAction)
+ ;
+ }
+
+ private void wasBuilt(final MatrixConfiguration conf) {
+
+ wasBuildTimes(conf, times(1));
+ }
+
+ private void wasNotBuilt(final MatrixConfiguration conf) {
+
+ wasBuildTimes(conf, never());
+ }
+
+ private void wasBuildTimes(
+ final MatrixConfiguration conf, final VerificationMode mode
+ ) {
+
+ verify(conf, mode).scheduleBuild(
+ new ArrayList(),
+ new Cause.UpstreamCause((Run, ?>) build)
+ );
+ }
+}
diff --git a/core/src/test/java/hudson/model/FingerprintTest.java b/core/src/test/java/hudson/model/FingerprintTest.java
index 2c1cbbdc71..296cb77fdf 100644
--- a/core/src/test/java/hudson/model/FingerprintTest.java
+++ b/core/src/test/java/hudson/model/FingerprintTest.java
@@ -23,14 +23,23 @@
*/
package hudson.model;
-import junit.framework.TestCase;
+import hudson.Util;
import hudson.model.Fingerprint.RangeSet;
+import java.io.File;
+import jenkins.model.FingerprintFacet;
+import static org.junit.Assert.*;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
/**
* @author Kohsuke Kawaguchi
*/
-public class FingerprintTest extends TestCase {
- public void test() {
+public class FingerprintTest {
+
+ @Rule public TemporaryFolder tmp = new TemporaryFolder();
+
+ @Test public void rangeSet() {
RangeSet rs = new RangeSet();
assertFalse(rs.includes(0));
assertFalse(rs.includes(3));
@@ -62,7 +71,7 @@ public class FingerprintTest extends TestCase {
assertEquals("[3,7),[9,11)",rs.toString());
}
- public void testMerge() {
+ @Test public void merge() {
RangeSet x = new RangeSet();
x.add(1);
x.add(2);
@@ -81,7 +90,7 @@ public class FingerprintTest extends TestCase {
assertEquals("[1,7)",x.toString());
}
- public void testMerge2() {
+ @Test public void merge2() {
RangeSet x = new RangeSet();
x.add(1);
x.add(2);
@@ -98,7 +107,7 @@ public class FingerprintTest extends TestCase {
assertEquals("[1,7)",x.toString());
}
- public void testMerge3() {
+ @Test public void merge3() {
RangeSet x = new RangeSet();
x.add(1);
x.add(5);
@@ -113,4 +122,55 @@ public class FingerprintTest extends TestCase {
x.add(y);
assertEquals("[1,2),[3,4),[5,6),[7,8)",x.toString());
}
+
+ @Test public void deserialize() throws Exception {
+ assertEquals("Fingerprint["
+ + "original=stapler/org.kohsuke.stapler:stapler-jelly #123,"
+ + "hash=069484c9e963cc615c51278327da8eab,"
+ + "fileName=org.kohsuke.stapler:stapler-jelly-1.207.jar,"
+ + "timestamp=2013-05-21 19:20:03.534 UTC,"
+ + "usages={stuff=[304,306),[307,324),[328,330), stuff/test:stuff=[2,67),[72,77),[84,223),[228,229),[232,268)},"
+ + "facets=[]]",
+ Fingerprint.load(new File(FingerprintTest.class.getResource("fingerprint.xml").toURI())).toString());
+ }
+
+ @Test public void roundTrip() throws Exception {
+ Fingerprint f = new Fingerprint(new Fingerprint.BuildPtr("foo", 13), "stuff.jar", SOME_MD5);
+ f.addWithoutSaving("some", 1);
+ f.addWithoutSaving("some", 2);
+ f.addWithoutSaving("some", 3);
+ f.addWithoutSaving("some", 10);
+ f.addWithoutSaving("other", 6);
+ File xml = new File(new File(tmp.getRoot(), "dir"), "fp.xml");
+ f.save(xml);
+ Fingerprint f2 = Fingerprint.load(xml);
+ assertEquals(f.toString(), f2.toString());
+ f.facets.setOwner(Saveable.NOOP);
+ f.facets.add(new TestFacet(f, 123, "val"));
+ f.save(xml);
+ //System.out.println(FileUtils.readFileToString(xml));
+ f2 = Fingerprint.load(xml);
+ assertEquals(f.toString(), f2.toString());
+ assertEquals(1, f2.facets.size());
+ TestFacet facet = (TestFacet) f2.facets.get(0);
+ assertEquals(f2, facet.getFingerprint());
+ }
+ private static byte[] toByteArray(String md5sum) {
+ byte[] data = new byte[16];
+ for( int i=0; i {}
+
+ @Test
+ @PrepareForTest({ListViewColumn.class,Items.class})
+ public void listItemRecurseWorksWithNonTopLevelItems() throws IOException{
+ mockStatic(Items.class);
+ mockStatic(ListViewColumn.class);
+ List columns = Collections.emptyList();
+ when(ListViewColumn.createDefaultInitialColumnList()).thenReturn(columns);
+ ViewGroup owner = mock(ViewGroup.class);
+ ItemGroup itemGroupOwner = mock(ItemGroup.class);
+ when(owner.getItemGroup()).thenReturn(itemGroupOwner);
+ ListView lv = new ListView("test", owner);
+ ItemGroupOfNonTopLevelItem ig = Mockito.mock(ItemGroupOfNonTopLevelItem.class);
+ when(Items.getAllItems(eq(itemGroupOwner), eq(TopLevelItem.class))).thenReturn(Arrays.asList((TopLevelItem) ig));
+ when(ig.getRelativeNameFrom(any(ItemGroup.class))).thenReturn("test-item");
+ lv.setRecurse(true);
+ lv.add(ig);
+ assertEquals(1, lv.getItems().size());
+ }
+}
diff --git a/core/src/test/java/hudson/model/StubJob.java b/core/src/test/java/hudson/model/StubJob.java
index cc5224aae6..7f3b1dda7d 100644
--- a/core/src/test/java/hudson/model/StubJob.java
+++ b/core/src/test/java/hudson/model/StubJob.java
@@ -28,8 +28,9 @@ import java.util.SortedMap;
/**
* @author kingfai
- *
+ * @deprecated Does not behave very consistently. Either write a real functional test with {@code JenkinsRule}, or use PowerMock/Mockito.
*/
+@Deprecated
@SuppressWarnings({ "rawtypes", "unchecked" })
class StubJob extends Job {
@@ -69,4 +70,8 @@ class StubJob extends Job {
}
+ @Override public ItemGroup getParent() {
+ return null;
+ }
+
}
diff --git a/core/src/test/java/hudson/tasks/junit/SuiteResultTest.java b/core/src/test/java/hudson/tasks/junit/SuiteResultTest.java
index ffeb8a0c8d..5e9d486459 100644
--- a/core/src/test/java/hudson/tasks/junit/SuiteResultTest.java
+++ b/core/src/test/java/hudson/tasks/junit/SuiteResultTest.java
@@ -82,7 +82,7 @@ public class SuiteResultTest extends TestCase {
List cases = result.getCases();
for (CaseResult caseResult : cases) {
- assertEquals("Test class name is incorrect in " + caseResult.getDisplayName(), "WLI-FI-Tests-Fake", caseResult.getClassName());
+ assertEquals("Test class name is incorrect in " + caseResult.getName(), "WLI-FI-Tests-Fake", caseResult.getClassName());
}
assertEquals("Test name is incorrect", "IF_importTradeConfirmationToDwh", cases.get(0).getName());
assertEquals("Test name is incorrect", "IF_getAmartaDisbursements", cases.get(1).getName());
@@ -119,7 +119,7 @@ public class SuiteResultTest extends TestCase {
List cases = result.getCases();
for (CaseResult caseResult : cases) {
- assertEquals("Test class name is incorrect in " + caseResult.getDisplayName(), "some.package.somewhere.WhooHoo", caseResult.getClassName());
+ assertEquals("Test class name is incorrect in " + caseResult.getName(), "some.package.somewhere.WhooHoo", caseResult.getClassName());
}
assertEquals("this normally has the string like, expected mullet, but got bream", cases.get(0).getErrorDetails());
}
diff --git a/core/src/test/java/hudson/util/CopyOnWriteMapTest.java b/core/src/test/java/hudson/util/CopyOnWriteMapTest.java
index 9e4f0dbba3..33536bff7f 100644
--- a/core/src/test/java/hudson/util/CopyOnWriteMapTest.java
+++ b/core/src/test/java/hudson/util/CopyOnWriteMapTest.java
@@ -25,13 +25,15 @@ package hudson.util;
import java.util.Comparator;
import java.util.HashMap;
+import java.util.Map;
import java.util.TreeMap;
-import junit.framework.TestCase;
+import static org.junit.Assert.*;
+import org.junit.Test;
/**
* @author Mike Dillon, Alan Harder
*/
-public class CopyOnWriteMapTest extends TestCase {
+public class CopyOnWriteMapTest {
public static final class HashData {
CopyOnWriteMap.Hash map1 = new CopyOnWriteMap.Hash();
HashMap map2 = new HashMap();
@@ -40,7 +42,7 @@ public class CopyOnWriteMapTest extends TestCase {
/**
* Verify that serialization form of CopyOnWriteMap.Hash and HashMap are the same.
*/
- public void testHashSerialization() throws Exception {
+ @Test public void hashSerialization() throws Exception {
HashData td = new HashData();
XStream2 xs = new XStream2();
@@ -82,7 +84,7 @@ public class CopyOnWriteMapTest extends TestCase {
* Verify that an empty CopyOnWriteMap.Tree can be serialized,
* and that serialization form is the same as a standard TreeMap.
*/
- public void testTreeSerialization() throws Exception {
+ @Test public void treeSerialization() throws Exception {
TreeData td = new TreeData();
XStream2 xs = new XStream2();
@@ -111,4 +113,18 @@ public class CopyOnWriteMapTest extends TestCase {
assertEquals("bar1", td2.map1.get("foo1"));
assertEquals("bar2", td2.map2.get("foo2"));
}
+
+ @Test public void equalsHashCodeToString() throws Exception {
+ Map m1 = new TreeMap();
+ Map m2 = new CopyOnWriteMap.Tree();
+ m1.put("foo", 5);
+ m1.put("bar", 7);
+ m2.put("foo", 5);
+ m2.put("bar", 7);
+ assertEquals(m1.hashCode(), m2.hashCode());
+ assertTrue(m2.equals(m1));
+ assertTrue(m1.equals(m2));
+ assertEquals(m1.toString(), m2.toString());
+ }
+
}
diff --git a/core/src/test/java/jenkins/model/PeepholePermalinkTest.java b/core/src/test/java/jenkins/model/PeepholePermalinkTest.java
new file mode 100644
index 0000000000..82c1ab4dc3
--- /dev/null
+++ b/core/src/test/java/jenkins/model/PeepholePermalinkTest.java
@@ -0,0 +1,45 @@
+/*
+ * The MIT License
+ *
+ * Copyright 2013 Jesse Glick.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package jenkins.model;
+
+import java.io.File;
+import org.junit.Test;
+import static org.junit.Assert.*;
+import org.junit.Rule;
+import org.junit.rules.TemporaryFolder;
+import org.jvnet.hudson.test.Bug;
+
+public class PeepholePermalinkTest {
+
+ @Rule public TemporaryFolder tmp = new TemporaryFolder();
+
+ @Bug(17681)
+ @Test public void symlinks() throws Exception {
+ File link = new File(tmp.getRoot(), "link");
+ PeepholePermalink.writeSymlink(link, "stuff");
+ assertEquals("stuff", PeepholePermalink.readSymlink(link));
+ }
+
+}
diff --git a/core/src/test/java/jenkins/model/lazy/AbstractLazyLoadRunMapTest.java b/core/src/test/java/jenkins/model/lazy/AbstractLazyLoadRunMapTest.java
index 647b0356a7..16bfcda069 100644
--- a/core/src/test/java/jenkins/model/lazy/AbstractLazyLoadRunMapTest.java
+++ b/core/src/test/java/jenkins/model/lazy/AbstractLazyLoadRunMapTest.java
@@ -30,11 +30,14 @@ import org.junit.Rule;
import org.junit.Test;
import java.io.IOException;
+import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
+import java.util.Set;
import java.util.SortedMap;
import java.util.logging.Level;
import org.junit.BeforeClass;
+import org.junit.Ignore;
import org.jvnet.hudson.test.Bug;
/**
@@ -251,4 +254,66 @@ public class AbstractLazyLoadRunMapTest extends Assert {
Build x = map.search(Integer.MAX_VALUE, Direction.DESC);
assert x.n==201;
}
+
+ @Bug(15652)
+ @Test public void outOfOrder() throws Exception {
+ FakeMap map = localBuilder
+ .add( 4, "2012-A")
+ .add( 5, "2012-B")
+ .add( 6, "2012-C")
+ .add( 7, "2012-D")
+ .add( 8, "2012-E")
+ .add( 9, "2012-F")
+ .add(10, "2012-G")
+ .add(11, "2012-H")
+ .add(12, "2012-I")
+ .add( 1, "2013-A")
+ .add( 7, "2013-B")
+ .add( 9, "2013-C")
+ .add(10, "2013-D")
+ .add(11, "2013-E")
+ .make();
+ map.entrySet(); // forces Index to be populated
+ assertNull(map.search(3, Direction.DESC));
+ }
+
+ @Ignore("just calling entrySet triggers loading of every build!")
+ @Bug(18065)
+ @Test public void all() throws Exception {
+ assertEquals("[]", a.getLoadedBuilds().keySet().toString());
+ Set> entries = a.entrySet();
+ assertEquals("[]", a.getLoadedBuilds().keySet().toString());
+ assertFalse(entries.isEmpty());
+ assertEquals("[]", a.getLoadedBuilds().keySet().toString());
+ assertEquals(5, a.getById("C").n);
+ assertEquals("[5]", a.getLoadedBuilds().keySet().toString());
+ assertEquals("A", a.getByNumber(1).id);
+ assertEquals("[5, 1]", a.getLoadedBuilds().keySet().toString());
+ a.purgeCache();
+ assertEquals("[]", a.getLoadedBuilds().keySet().toString());
+ Iterator> iterator = entries.iterator();
+ assertEquals("[]", a.getLoadedBuilds().keySet().toString());
+ assertTrue(iterator.hasNext());
+ assertEquals("[]", a.getLoadedBuilds().keySet().toString());
+ Map.Entry entry = iterator.next();
+ assertEquals("[]", a.getLoadedBuilds().keySet().toString());
+ assertEquals(5, entry.getKey().intValue());
+ assertEquals("[]", a.getLoadedBuilds().keySet().toString());
+ assertEquals("C", entry.getValue().id);
+ assertEquals("[5]", a.getLoadedBuilds().keySet().toString());
+ assertTrue(iterator.hasNext());
+ entry = iterator.next();
+ assertEquals(3, entry.getKey().intValue());
+ assertEquals("[5]", a.getLoadedBuilds().keySet().toString());
+ assertEquals("B", entry.getValue().id);
+ assertEquals("[5, 3]", a.getLoadedBuilds().keySet().toString());
+ assertTrue(iterator.hasNext());
+ entry = iterator.next();
+ assertEquals(1, entry.getKey().intValue());
+ assertEquals("[5, 3]", a.getLoadedBuilds().keySet().toString());
+ assertEquals("A", entry.getValue().id);
+ assertEquals("[5, 3, 1]", a.getLoadedBuilds().keySet().toString());
+ assertFalse(iterator.hasNext());
+ }
+
}
diff --git a/core/src/test/resources/hudson/model/fingerprint.xml b/core/src/test/resources/hudson/model/fingerprint.xml
new file mode 100644
index 0000000000..dc933bcc3d
--- /dev/null
+++ b/core/src/test/resources/hudson/model/fingerprint.xml
@@ -0,0 +1,21 @@
+
+
+ 2013-05-21 19:20:03.534 UTC
+
+ stapler/org.kohsuke.stapler:stapler-jelly
+ 123
+
+ 069484c9e963cc615c51278327da8eab
+ org.kohsuke.stapler:stapler-jelly-1.207.jar
+
+
+ stuff/test:stuff
+ 2-66,72-76,84-222,228,232-267
+
+
+ stuff
+ 304-305,307-323,328-329
+
+
+
+
\ No newline at end of file
diff --git a/debian/debian/changelog b/debian/debian/changelog
index 5a494da3b2..8573aef225 100644
--- a/debian/debian/changelog
+++ b/debian/debian/changelog
@@ -1,3 +1,39 @@
+jenkins (1.518) unstable; urgency=low
+
+ * See http://jenkins-ci.org/changelog for more details.
+
+ -- Kohsuke Kawaguchi Tue, 11 Jun 2013 02:33:30 -0700
+
+jenkins (1.517) unstable; urgency=low
+
+ * See http://jenkins-ci.org/changelog for more details.
+
+ -- Kohsuke Kawaguchi Sun, 02 Jun 2013 19:14:40 -0700
+
+jenkins (1.516) unstable; urgency=low
+
+ * See http://jenkins-ci.org/changelog for more details.
+
+ -- Kohsuke Kawaguchi Mon, 27 May 2013 22:47:35 -0700
+
+jenkins (1.515) unstable; urgency=low
+
+ * See http://jenkins-ci.org/changelog for more details.
+
+ -- Kohsuke Kawaguchi Sat, 18 May 2013 21:29:37 -0700
+
+jenkins (1.514) unstable; urgency=low
+
+ * See http://jenkins-ci.org/changelog for more details.
+
+ -- Kohsuke Kawaguchi Wed, 01 May 2013 20:15:32 -0700
+
+jenkins (1.513) unstable; urgency=low
+
+ * See http://jenkins-ci.org/changelog for more details.
+
+ -- Kohsuke Kawaguchi Sun, 28 Apr 2013 20:23:23 -0700
+
jenkins (1.512) unstable; urgency=low
* See http://jenkins-ci.org/changelog for more details.
diff --git a/maven-plugin/pom.xml b/maven-plugin/pom.xml
index 63e703b332..2702482ae9 100644
--- a/maven-plugin/pom.xml
+++ b/maven-plugin/pom.xml
@@ -29,7 +29,7 @@ THE SOFTWARE.
org.jenkins-ci.mainpom
- 1.513
+ 1.520-SNAPSHOTmaven-plugin
@@ -41,7 +41,7 @@ THE SOFTWARE.
http://wiki.jenkins-ci.org/display/JENKINS/Maven+2+Project+Plugin
- 1.2
+ 1.33.0.5${mavenVersion}1.13.1
@@ -69,7 +69,7 @@ THE SOFTWARE.
org.jenkins-ci.pluginsmailer
- 1.3
+ 1.5javax.servlet
@@ -359,17 +359,17 @@ THE SOFTWARE.
org.powermockpowermock-module-junit4test
-
-
+
+ org.powermockpowermock-api-mockitotest
-
-
- org.slf4j
- slf4j-jdk14
- test
-
+
+
+ org.slf4j
+ slf4j-jdk14
+ test
+
diff --git a/maven-plugin/src/main/java/hudson/maven/AbstractMavenProcessFactory.java b/maven-plugin/src/main/java/hudson/maven/AbstractMavenProcessFactory.java
index bc02fa5076..8e7e9d9094 100644
--- a/maven-plugin/src/main/java/hudson/maven/AbstractMavenProcessFactory.java
+++ b/maven-plugin/src/main/java/hudson/maven/AbstractMavenProcessFactory.java
@@ -69,6 +69,11 @@ import org.kohsuke.stapler.framework.io.IOException2;
*/
/**
+ * Launches the maven process.
+ *
+ * This class captures the common part, and {@link MavenProcessFactory} and {@link Maven3ProcessFactory}
+ * adds Maven2/Maven3 flavors to it to make it concrete.
+ *
* @author Olivier Lamy
*/
public abstract class AbstractMavenProcessFactory
@@ -234,11 +239,14 @@ public abstract class AbstractMavenProcessFactory
throw e;
}
- return new NewProcess(
- Channels.forProcess("Channel to Maven "+ Arrays.toString(cmds),
+ Channel ch = Channels.forProcess("Channel to Maven " + Arrays.toString(cmds),
Computer.threadPoolForRemoting, new BufferedInputStream(con.in), new BufferedOutputStream(con.out),
- listener.getLogger(), proc),
- proc);
+ listener.getLogger(), proc);
+
+ if (!PlexusModuleContributor.all().isEmpty())
+ applyPlexusModuleContributor(ch);
+
+ return new NewProcess(ch,proc);
} catch (IOException e) {
if(fixNull(e.getMessage()).contains("java: not found")) {
// diagnose issue #659
@@ -250,6 +258,12 @@ public abstract class AbstractMavenProcessFactory
}
}
+ /**
+ * Apply extension plexus modules to the newly launched Maven process.
+ * @since 1.519
+ */
+ protected abstract void applyPlexusModuleContributor(Channel channel) throws InterruptedException, IOException;
+
/**
* Builds the command line argument list to launch the maven process.
*/
diff --git a/maven-plugin/src/main/java/hudson/maven/AggregatingClassLoader.java b/maven-plugin/src/main/java/hudson/maven/AggregatingClassLoader.java
new file mode 100644
index 0000000000..c18672acb3
--- /dev/null
+++ b/maven-plugin/src/main/java/hudson/maven/AggregatingClassLoader.java
@@ -0,0 +1,57 @@
+package hudson.maven;
+
+import java.io.IOException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.List;
+
+/**
+ * ClassLoader that delegates to multiple side classloaders, in addition to the parent.
+ *
+ * The side classloaders generally need to be carefully crafted
+ * to avoid classloader constraint violations.
+ *
+ * @author Kohsuke Kawaguchi
+ */
+class AggregatingClassLoader extends ClassLoader {
+ private final List sides;
+
+ public AggregatingClassLoader(ClassLoader parent, List sides) {
+ super(parent);
+ this.sides = new ArrayList(sides);
+ }
+
+ @Override
+ protected Class> findClass(String name) throws ClassNotFoundException {
+ for (ClassLoader cl : sides) {
+ try {
+ return cl.loadClass(name);
+ } catch (ClassNotFoundException e) {
+ //not found. try next
+ }
+ }
+ // not found in any of the classloader. delegate.
+ throw new ClassNotFoundException(name);
+ }
+
+ @Override
+ protected URL findResource(String name) {
+ for (ClassLoader cl : sides) {
+ URL url = cl.getResource(name);
+ if (url!=null)
+ return url;
+ }
+ return null;
+ }
+
+ @Override
+ protected Enumeration findResources(String name) throws IOException {
+ List resources = new ArrayList();
+ for (ClassLoader cl : sides) {
+ resources.addAll(Collections.list(cl.getResources(name)));
+ }
+ return Collections.enumeration(resources);
+ }
+}
diff --git a/maven-plugin/src/main/java/hudson/maven/Maven3Builder.java b/maven-plugin/src/main/java/hudson/maven/Maven3Builder.java
index 182adb0830..d39e3f603b 100644
--- a/maven-plugin/src/main/java/hudson/maven/Maven3Builder.java
+++ b/maven-plugin/src/main/java/hudson/maven/Maven3Builder.java
@@ -95,8 +95,8 @@ public class Maven3Builder extends AbstractMavenBuilder implements DelegatingCal
registerSystemProperties();
listener.getLogger().println(formatArgs(goals));
-
-
+
+
int r = Maven3Main.launch( goals.toArray(new String[goals.size()]));
// now check the completion status of async ops
@@ -160,6 +160,9 @@ public class Maven3Builder extends AbstractMavenBuilder implements DelegatingCal
throw new IOException2(e);
} catch (Exception e) {
throw new IOException2(e);
+ } finally {
+ if (DUMP_PERFORMANCE_COUNTERS)
+ Channel.current().dumpPerformanceCounters(listener.error("Remoting stats"));
}
}
@@ -571,4 +574,6 @@ public class Maven3Builder extends AbstractMavenBuilder implements DelegatingCal
private static final long serialVersionUID = 1L;
private static final Logger LOGGER = Logger.getLogger(Maven3Builder.class.getName());
+
+ public static boolean DUMP_PERFORMANCE_COUNTERS = Boolean.getBoolean(Maven3Builder.class.getName()+".dumpPerformanceCounters");
}
diff --git a/maven-plugin/src/main/java/hudson/maven/Maven3ProcessFactory.java b/maven-plugin/src/main/java/hudson/maven/Maven3ProcessFactory.java
index b4235d62bb..0d6fc6c7cb 100644
--- a/maven-plugin/src/main/java/hudson/maven/Maven3ProcessFactory.java
+++ b/maven-plugin/src/main/java/hudson/maven/Maven3ProcessFactory.java
@@ -26,16 +26,19 @@ package hudson.maven;
import hudson.EnvVars;
import hudson.FilePath;
import hudson.Launcher;
+import hudson.maven.agent.Main;
import hudson.model.BuildListener;
import hudson.model.Run.RunnerAbortedException;
import hudson.model.TaskListener;
import hudson.remoting.Callable;
+import hudson.remoting.Channel;
import hudson.remoting.Which;
import hudson.tasks.Maven.MavenInstallation;
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
+import java.net.URL;
import org.jvnet.hudson.maven3.agent.Maven3Main;
import org.jvnet.hudson.maven3.launcher.Maven3Launcher;
@@ -78,6 +81,23 @@ public class Maven3ProcessFactory extends AbstractMavenProcessFactory implements
return null;
}
+ @Override
+ protected void applyPlexusModuleContributor(Channel channel) throws InterruptedException, IOException {
+ channel.call(new InstallPlexusModulesTask());
+ }
+
+ private static final class InstallPlexusModulesTask implements Callable {
+ PlexusModuleContributor c = PlexusModuleContributor.aggregate();
+
+ public Void call() throws IOException {
+ Maven3Main.addPlexusComponents(c.getPlexusComponentJars().toArray(new URL[0]));
+ return null;
+ }
+
+ private static final long serialVersionUID = 1L;
+ }
+
+
/**
* Finds classworlds.jar
*/
diff --git a/maven-plugin/src/main/java/hudson/maven/MavenBuildInformation.java b/maven-plugin/src/main/java/hudson/maven/MavenBuildInformation.java
index cd631dd90f..66dbb24f8c 100644
--- a/maven-plugin/src/main/java/hudson/maven/MavenBuildInformation.java
+++ b/maven-plugin/src/main/java/hudson/maven/MavenBuildInformation.java
@@ -21,9 +21,13 @@ package hudson.maven;
*/
import java.io.Serializable;
+import java.util.HashMap;
+import java.util.Map;
import org.apache.commons.lang.StringUtils;
import org.apache.maven.artifact.versioning.ComparableVersion;
+import org.apache.maven.model.Model;
+import org.apache.maven.project.MavenProject;
/**
* @author Olivier Lamy
@@ -33,6 +37,15 @@ public class MavenBuildInformation implements Serializable {
private static final long serialVersionUID = -3719709179508200057L;
private String mavenVersion;
+ /**
+ * Map of model IDs to IDs of the model parents (if defined).
+ * @see MavenProject#getId
+ * @see Model#getId
+ * @see MavenProject#getParent
+ * @see Model#getParent
+ * @since 1.515
+ */
+ public final Map modelParents = new HashMap();
public MavenBuildInformation(String mavenVersion) {
this.mavenVersion = mavenVersion;
diff --git a/maven-plugin/src/main/java/hudson/maven/MavenEmbedderRequest.java b/maven-plugin/src/main/java/hudson/maven/MavenEmbedderRequest.java
index 68b09e4d39..73784b4a3e 100755
--- a/maven-plugin/src/main/java/hudson/maven/MavenEmbedderRequest.java
+++ b/maven-plugin/src/main/java/hudson/maven/MavenEmbedderRequest.java
@@ -24,9 +24,15 @@ package hudson.maven;
import hudson.model.TaskListener;
+import java.io.BufferedReader;
import java.io.File;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.URL;
+import java.util.Enumeration;
import java.util.Properties;
+import org.apache.commons.io.IOUtils;
import org.apache.maven.model.building.ModelBuildingRequest;
import org.sonatype.aether.repository.WorkspaceReader;
import org.sonatype.aether.transfer.TransferListener;
@@ -51,10 +57,14 @@ public class MavenEmbedderRequest
private TransferListener transferListener;
/**
+ * The classloader used to create Maven embedder.
+ *
+ * This needs to be able to see all the plexus components for core Maven stuff.
+ *
* @since 1.393
*/
- private ClassLoader classLoader;
-
+ private ClassLoader classLoader = getDefaultMavenClassLoader();
+
/**
* will processPlugins during project reading
* @since 1.393
@@ -88,7 +98,7 @@ public class MavenEmbedderRequest
* @since 1.461
*/
private boolean updateSnapshots;
-
+
/**
* @param listener
* This is where the log messages from Maven will be recorded.
@@ -163,6 +173,9 @@ public class MavenEmbedderRequest
return alternateSettings;
}
+ /**
+ * Overrides the user settings (by default we look at ~/.m2/settings.xml)
+ */
public MavenEmbedderRequest setAlternateSettings( File alternateSettings ) {
this.alternateSettings = alternateSettings;
return this;
@@ -177,6 +190,14 @@ public class MavenEmbedderRequest
return this;
}
+ /**
+ * Default value of {@link #getClassLoader()}
+ * @since 1.519
+ */
+ public static ClassLoader getDefaultMavenClassLoader() {
+ return new MaskingClassLoader( MavenUtil.class.getClassLoader() );
+ }
+
public ClassLoader getClassLoader() {
return classLoader;
}
@@ -208,6 +229,11 @@ public class MavenEmbedderRequest
return validationLevel;
}
+ /**
+ * Controls the level of error checks done while parsing POM.
+ *
+ * @see ModelBuildingRequest#VALIDATION_LEVEL_MAVEN_3_0
+ */
public MavenEmbedderRequest setValidationLevel( int validationLevel ) {
this.validationLevel = validationLevel;
return this;
@@ -238,4 +264,78 @@ public class MavenEmbedderRequest
public boolean isUpdateSnapshots() {
return updateSnapshots;
}
+
+
+ /**
+ * When we run in Jetty during development, embedded Maven will end up
+ * seeing some of the Maven class visible through Jetty, and this confuses it.
+ *
+ *
+ * Specifically, embedded Maven will find all the component descriptors
+ * visible through Jetty, yet when it comes to loading classes, classworlds
+ * still load classes from local realms created inside embedder.
+ *
+ *
+ * This classloader prevents this issue by hiding the component descriptor
+ * visible through Jetty.
+ */
+ private static final class MaskingClassLoader extends ClassLoader {
+
+ public MaskingClassLoader(ClassLoader parent) {
+ super(parent);
+ }
+
+ public Enumeration getResources(String name) throws IOException {
+ final Enumeration e = super.getResources(name);
+ return new Enumeration() {
+ URL next;
+
+ public boolean hasMoreElements() {
+ fetch();
+ return next!=null;
+ }
+
+ public URL nextElement() {
+ fetch();
+ URL r = next;
+ next = null;
+ return r;
+ }
+
+ private void fetch() {
+ while(next==null && e.hasMoreElements()) {
+ next = e.nextElement();
+ if(shouldBeIgnored(next))
+ next = null;
+ }
+ }
+
+ private boolean shouldBeIgnored(URL url) {
+ String s = url.toExternalForm();
+ if(s.contains("maven-plugin-tools-api"))
+ return true;
+ // because RemoteClassLoader mangles the path, we can't check for plexus/components.xml,
+ // which would have otherwise made the test cheaper.
+ if(s.endsWith("components.xml")) {
+ BufferedReader r=null;
+ try {
+ // is this designated for interception purpose? If so, don't load them in the MavenEmbedder
+ // earlier I tried to use a marker file in the same directory, but that won't work
+ r = new BufferedReader(new InputStreamReader(url.openStream()));
+ for (int i=0; i<2; i++) {
+ String l = r.readLine();
+ if(l!=null && l.contains("MAVEN-INTERCEPTION-TO-BE-MASKED"))
+ return true;
+ }
+ } catch (IOException _) {
+ // let whoever requesting this resource re-discover an error and report it
+ } finally {
+ IOUtils.closeQuietly(r);
+ }
+ }
+ return false;
+ }
+ };
+ }
+ }
}
diff --git a/maven-plugin/src/main/java/hudson/maven/MavenModuleSet.java b/maven-plugin/src/main/java/hudson/maven/MavenModuleSet.java
index 31c594da42..24039aefab 100644
--- a/maven-plugin/src/main/java/hudson/maven/MavenModuleSet.java
+++ b/maven-plugin/src/main/java/hudson/maven/MavenModuleSet.java
@@ -67,6 +67,7 @@ import hudson.tasks.Maven.MavenInstallation;
import hudson.tasks.Publisher;
import hudson.tasks.JavadocArchiver;
import hudson.tasks.junit.JUnitResultArchiver;
+import hudson.util.AlternativeUiTextProvider;
import hudson.util.CopyOnWriteMap;
import hudson.util.DescribableList;
import hudson.util.FormValidation;
@@ -94,6 +95,7 @@ import jenkins.model.ModelObjectWithChildren;
import jenkins.mvn.DefaultGlobalSettingsProvider;
import jenkins.mvn.DefaultSettingsProvider;
import jenkins.mvn.FilePathSettingsProvider;
+import jenkins.mvn.GlobalMavenConfig;
import jenkins.mvn.GlobalSettingsProvider;
import jenkins.mvn.GlobalSettingsProviderDescriptor;
import jenkins.mvn.SettingsProvider;
@@ -246,7 +248,9 @@ public class MavenModuleSet extends AbstractMavenProject list : getModuleBuilds().values())
+ for (MavenBuild build : list)
+ build.deleteArtifacts();
+ }
+
@Override
public Object getDynamic(String token, StaplerRequest req, StaplerResponse rsp) {
// map corresponding module build under this object
@@ -582,7 +596,7 @@ public class MavenModuleSetBuild extends AbstractMavenBuild)MavenModuleSetBuild.this));
@@ -619,7 +633,7 @@ public class MavenModuleSetBuild extends AbstractMavenBuild();
List changedModules = new ArrayList();
@@ -682,6 +696,10 @@ public class MavenModuleSetBuild extends AbstractMavenBuild