From 7c568178f3810ab590d33ba23f1cf7fea418edac Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Wed, 26 Feb 2014 10:13:39 -0500 Subject: [PATCH] [FIXED JENKINS-21958] FilePath.copyRecursiveTo given a scanner including a symlink but not any regular file in the same directory would neglect to create the parent directory on the destination and thus not copy the link. (Util.createSymlink is partly to blame for not throwing a descriptive IOException when it fails to create a link, but this is its historical behavior needed for compatibility. copyRecursiveTo also continues to suppress error text from this method because there is nowhere for it to go. Perhaps need a new variant of createSymlink that is guaranteed to either have created the link or throw an exception.) --- changelog.html | 4 +- core/src/main/java/hudson/FilePath.java | 3 +- .../hudson/tasks/ArtifactArchiverTest.java | 43 +++++++++++++++++-- 3 files changed, 44 insertions(+), 6 deletions(-) diff --git a/changelog.html b/changelog.html index 642cd3eaa1..aedb2bc141 100644 --- a/changelog.html +++ b/changelog.html @@ -55,7 +55,9 @@ Upcoming changes diff --git a/core/src/main/java/hudson/FilePath.java b/core/src/main/java/hudson/FilePath.java index 9fd3134740..f0c40534cc 100644 --- a/core/src/main/java/hudson/FilePath.java +++ b/core/src/main/java/hudson/FilePath.java @@ -1933,7 +1933,7 @@ public final class FilePath implements Serializable { @Override public void visit(File f, String relativePath) throws IOException { if (f.isFile()) { File target = new File(dest, relativePath); - target.getParentFile().mkdirs(); + IOUtils.mkdirs(target.getParentFile()); Util.copyFile(f, target); count.incrementAndGet(); } @@ -1943,6 +1943,7 @@ public final class FilePath implements Serializable { } @Override public void visitSymlink(File link, String target, String relativePath) throws IOException { try { + IOUtils.mkdirs(new File(dest, relativePath).getParentFile()); Util.createSymlink(dest, target, relativePath, TaskListener.NULL); } catch (InterruptedException x) { throw (IOException) new IOException(x.toString()).initCause(x); diff --git a/test/src/test/java/hudson/tasks/ArtifactArchiverTest.java b/test/src/test/java/hudson/tasks/ArtifactArchiverTest.java index 76ce824e4d..f5d7f4ac41 100644 --- a/test/src/test/java/hudson/tasks/ArtifactArchiverTest.java +++ b/test/src/test/java/hudson/tasks/ArtifactArchiverTest.java @@ -24,28 +24,32 @@ package hudson.tasks; -import hudson.model.AbstractProject; -import org.junit.Test; import hudson.FilePath; import hudson.Launcher; import hudson.model.AbstractBuild; +import hudson.model.AbstractProject; import hudson.model.BuildListener; +import hudson.model.FreeStyleBuild; import hudson.model.FreeStyleProject; import hudson.model.Result; import hudson.model.StreamBuildListener; import hudson.tasks.LogRotatorTest.TestsFail; -import java.io.File; import static hudson.tasks.LogRotatorTest.build; +import java.io.File; import java.io.IOException; import java.nio.charset.Charset; import java.util.Arrays; import java.util.Collections; +import java.util.List; +import jenkins.util.VirtualFile; +import static org.junit.Assert.*; +import static org.junit.Assume.*; import org.junit.Rule; +import org.junit.Test; import org.jvnet.hudson.test.Bug; import org.jvnet.hudson.test.FailureBuilder; import org.jvnet.hudson.test.JenkinsRule; import org.jvnet.hudson.test.TestBuilder; -import static org.junit.Assert.*; /** * Verifies that artifacts from the last successful and stable builds of a job will be kept if requested. @@ -167,6 +171,37 @@ public class ArtifactArchiverTest { assertEquals("(no artifacts)", Result.SUCCESS, build(project)); assertFalse(project.getBuildByNumber(1).getHasArtifacts()); } + + @Bug(21958) + @Test public void symlinks() throws Exception { + FreeStyleProject p = j.createFreeStyleProject(); + p.getBuildersList().add(new TestBuilder() { + @Override public boolean perform(AbstractBuild build, Launcher launcher, BuildListener listener) throws InterruptedException, IOException { + FilePath ws = build.getWorkspace(); + if (ws == null) { + return false; + } + FilePath dir = ws.child("dir"); + dir.mkdirs(); + dir.child("fizz").write("contents", null); + dir.child("lodge").symlinkTo("fizz", listener); + return true; + } + }); + p.getPublishersList().add(new ArtifactArchiver("dir/lodge", "", false, true)); + FreeStyleBuild b = j.assertBuildStatusSuccess(p.scheduleBuild2(0)); + FilePath ws = b.getWorkspace(); + assertNotNull(ws); + assumeTrue("May not be testable on Windows:\n" + JenkinsRule.getLog(b), ws.child("dir/lodge").exists()); + List artifacts = b.getArtifacts(); + assertEquals(1, artifacts.size()); + FreeStyleBuild.Artifact artifact = artifacts.get(0); + assertEquals("dir/lodge", artifact.relativePath); + VirtualFile[] kids = b.getArtifactManager().root().child("dir").list(); + assertEquals(1, kids.length); + assertEquals("lodge", kids[0].getName()); + // do not check that it .exists() since its target has not been archived + } private void runNewBuildAndStartUnitlIsCreated(AbstractProject project) throws InterruptedException{ int buildNumber = project.getNextBuildNumber(); -- GitLab