Skip to content
Browse files

[FIXED JENKINS-21958] FilePath.copyRecursiveTo given a scanner includ…

…ing 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.)
  • Loading branch information...
jglick committed Feb 26, 2014
1 parent 7b42017 commit 7c568178f3810ab590d33ba23f1cf7fea418edac
@@ -55,7 +55,9 @@
<!-- Record your changes in the trunk here. -->
<div id="trunk" style="display:none"><!--=TRUNK-BEGIN=-->
<ul class=image>
<li class=>
<li class=bug>
Archiving of symlinks as artifacts did not work in some cases.
(<a href="">issue 21958</a>)

@@ -1933,7 +1933,7 @@ public Integer invoke(File base, VirtualChannel channel) throws IOException {
@Override public void visit(File f, String relativePath) throws IOException {
if (f.isFile()) {
File target = new File(dest, relativePath);
Util.copyFile(f, target);
@@ -1943,6 +1943,7 @@ public Integer invoke(File base, VirtualChannel channel) throws IOException {
@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);
@@ -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 static;
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 void testAllowEmptyArchive() throws Exception {
assertEquals("(no artifacts)", Result.SUCCESS, build(project));

@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.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();
assumeTrue("May not be testable on Windows:\n" + JenkinsRule.getLog(b), ws.child("dir/lodge").exists());
List<FreeStyleBuild.Artifact> 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();

0 comments on commit 7c56817

Please sign in to comment.
You can’t perform that action at this time.