Skip to content

Commit

Permalink
Merge pull request #1 from dkruger/master
Browse files Browse the repository at this point in the history
Proposed fixes for JENKINS-9582 and JENKINS-9577
  • Loading branch information
abayer committed Jan 16, 2012
2 parents 8c64c38 + dbe9d64 commit 46807bc
Show file tree
Hide file tree
Showing 6 changed files with 130 additions and 20 deletions.
Expand Up @@ -33,10 +33,12 @@
import hudson.model.AbstractProject;
import hudson.model.BuildListener;
import hudson.model.Result;
import hudson.model.TaskListener;
import hudson.tasks.BuildStepMonitor;
import hudson.tasks.BuildStepDescriptor;
import hudson.tasks.Publisher;
import hudson.tasks.Recorder;
import hudson.util.DirScanner;
import hudson.util.FormValidation;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.DataBoundConstructor;
Expand All @@ -45,8 +47,11 @@

import net.sf.json.JSONObject;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;

import java.util.logging.Level;
import java.util.logging.Logger;
Expand All @@ -64,17 +69,30 @@ public class CloneWorkspacePublisher extends Recorder {
*/
private final String workspaceGlob;

/**
* The glob we'll exclude from the archive.
*/
private final String workspaceExcludeGlob;

/**
* The criteria which determines whether we'll archive a given build's workspace.
* Can be "Any" (meaning most recent completed build), "Not Failed" (meaning most recent unstable/stable build),
* or "Successful" (meaning most recent stable build).
*/
private final String criteria;

/**
* The method by which the SCM will be archived.
* Can by "TAR" or "ZIP".
*/
private final String archiveMethod;

@DataBoundConstructor
public CloneWorkspacePublisher(String workspaceGlob, String criteria) {
public CloneWorkspacePublisher(String workspaceGlob, String workspaceExcludeGlob, String criteria, String archiveMethod) {
this.workspaceGlob = workspaceGlob.trim();
this.workspaceExcludeGlob = Util.fixEmptyAndTrim(workspaceExcludeGlob);
this.criteria = criteria;
this.archiveMethod = archiveMethod;
}

public BuildStepMonitor getRequiredMonitorService() {
Expand All @@ -90,28 +108,46 @@ public String getWorkspaceGlob() {
return workspaceGlob;
}

public String getWorkspaceExcludeGlob() {
return workspaceExcludeGlob;
}

public String getCriteria() {
return criteria;
}

public String getArchiveMethod() {
return archiveMethod;
}


@Override
public boolean perform(AbstractBuild<?,?> build, Launcher launcher, BuildListener listener) throws InterruptedException {
Result criteriaResult = CloneWorkspaceUtil.getResultForCriteria(criteria);

String realGlob;
String realIncludeGlob;
// Default to **/* if no glob is specified.
if (workspaceGlob.length()==0) {
realGlob = "**/*";
realIncludeGlob = "**/*";
}
else {
try {
realGlob = build.getEnvironment(listener).expand(workspaceGlob);
realIncludeGlob = build.getEnvironment(listener).expand(workspaceGlob);
} catch (IOException e) {
// We couldn't get an environment for some reason, so we'll just use the original.
realGlob = workspaceGlob;
realIncludeGlob = workspaceGlob;
}
}

String realExcludeGlob = null;
// Default to empty if no glob is specified.
if (Util.fixNull(workspaceExcludeGlob).length()!=0) {
try {
realExcludeGlob = build.getEnvironment(listener).expand(workspaceExcludeGlob);
} catch (IOException e) {
// We couldn't get an environment for some reason, so we'll just use the original.
realExcludeGlob = workspaceExcludeGlob;
}

}

if (build.getResult().isBetterOrEqualTo(criteriaResult)) {
Expand All @@ -123,18 +159,23 @@ public boolean perform(AbstractBuild<?,?> build, Launcher launcher, BuildListene

try {

String msg = ws.validateAntFileMask(realGlob);
String includeMsg = ws.validateAntFileMask(realIncludeGlob);
String excludeMsg = null;
if (realExcludeGlob != null) {
ws.validateAntFileMask(realExcludeGlob);
}
// This means we found something.
if(msg==null) {
build.addAction(FileSystemProvisioner.DEFAULT.snapshot(build, ws, realGlob, listener));
if((includeMsg==null) && (excludeMsg==null)) {
DirScanner globScanner = new DirScanner.Glob(realIncludeGlob, realExcludeGlob);
build.addAction(snapshot(build, ws, globScanner, listener, archiveMethod));

// Find the next most recent build meeting this criteria with an archived snapshot.
AbstractBuild<?,?> previousArchivedBuild = CloneWorkspaceUtil.getMostRecentBuildForCriteriaWithSnapshot(build.getPreviousBuild(), criteria);

if (previousArchivedBuild!=null) {
listener.getLogger().println(Messages.CloneWorkspacePublisher_DeletingOld(previousArchivedBuild.getDisplayName()));
try {
File oldWss = new File(previousArchivedBuild.getRootDir(), "workspace.zip");
File oldWss = new File(previousArchivedBuild.getRootDir(), CloneWorkspaceUtil.getFileNameForMethod(archiveMethod));
Util.deleteFile(oldWss);
} catch (IOException e) {
e.printStackTrace(listener.error(e.getMessage()));
Expand All @@ -144,17 +185,17 @@ public boolean perform(AbstractBuild<?,?> build, Launcher launcher, BuildListene
return true;
}
else {
listener.getLogger().println(Messages.CloneWorkspacePublisher_NoMatchFound(realGlob,msg));
listener.getLogger().println(Messages.CloneWorkspacePublisher_NoMatchFound(realIncludeGlob,includeMsg));
return true;
}
} catch (IOException e) {
Util.displayIOException(e,listener);
e.printStackTrace(listener.error(
Messages.CloneWorkspacePublisher_FailedToArchive(realGlob)));
Messages.CloneWorkspacePublisher_FailedToArchive(realIncludeGlob)));
return true;
} catch (InterruptedException e) {
e.printStackTrace(listener.error(
Messages.CloneWorkspacePublisher_FailedToArchive(realGlob)));
Messages.CloneWorkspacePublisher_FailedToArchive(realIncludeGlob)));
return true;
}

Expand All @@ -165,6 +206,43 @@ public boolean perform(AbstractBuild<?,?> build, Launcher launcher, BuildListene
}
}

public WorkspaceSnapshot snapshot(AbstractBuild<?,?> build, FilePath ws, DirScanner scanner, TaskListener listener, String archiveMethod) throws IOException, InterruptedException {
File wss = new File(build.getRootDir(), CloneWorkspaceUtil.getFileNameForMethod(archiveMethod));
if (archiveMethod == "ZIP") {
OutputStream os = new BufferedOutputStream(new FileOutputStream(wss));
try {
ws.zip(os, scanner);
} finally {
os.close();
}

return new WorkspaceSnapshotZip();
} else {
OutputStream os = new BufferedOutputStream(FilePath.TarCompression.GZIP.compress(new FileOutputStream(wss)));
try {
ws.tar(os, scanner);
} finally {
os.close();
}

return new WorkspaceSnapshotTar();
}
}

public static final class WorkspaceSnapshotTar extends WorkspaceSnapshot {
public void restoreTo(AbstractBuild<?,?> owner, FilePath dst, TaskListener listener) throws IOException, InterruptedException {
File wss = new File(owner.getRootDir(), CloneWorkspaceUtil.getFileNameForMethod("TAR"));
new FilePath(wss).untar(dst, FilePath.TarCompression.GZIP);
}
}

public static final class WorkspaceSnapshotZip extends WorkspaceSnapshot {
public void restoreTo(AbstractBuild<?,?> owner, FilePath dst, TaskListener listener) throws IOException, InterruptedException {
File wss = new File(owner.getRootDir(), CloneWorkspaceUtil.getFileNameForMethod("ZIP"));
new FilePath(wss).unzip(dst);
}
}

@Extension
public static class DescriptorImpl extends BuildStepDescriptor<Publisher> {
public DescriptorImpl() {
Expand Down Expand Up @@ -195,4 +273,4 @@ public boolean isApplicable(Class<? extends AbstractProject> jobType) {

private static final Logger LOGGER = Logger.getLogger(CloneWorkspacePublisher.class.getName());

}
}
Expand Up @@ -123,16 +123,20 @@ public Snapshot resolve() throws ResolvedFailedException {
public boolean checkout(AbstractBuild build, Launcher launcher, FilePath workspace, BuildListener listener, File changelogFile) throws IOException, InterruptedException {
try {
workspace.deleteContents();
resolve().restoreTo(workspace,listener);

Snapshot snapshot = resolve();
listener.getLogger().println("Restoring workspace from build #" + snapshot.getParent().getNumber() + " of project " + parentJobName);
snapshot.restoreTo(workspace,listener);

// write out the parent build number file
PrintWriter w = new PrintWriter(new FileOutputStream(getParentBuildFile(build)));
try {
w.println(resolve().getParent().getNumber());
w.println(snapshot.getParent().getNumber());
} finally {
w.close();
}

return calcChangeLog(resolve().getParent(), changelogFile, listener);
return calcChangeLog(snapshot.getParent(), changelogFile, listener);
} catch (ResolvedFailedException e) {
listener.error(e.getMessage()); // stack trace is meaningless
build.setResult(Result.FAILURE);
Expand Down Expand Up @@ -351,4 +355,4 @@ AbstractBuild<?,?> getParent() {

private static final Logger LOGGER = Logger.getLogger(CloneWorkspaceSCM.class.getName());

}
}
Expand Up @@ -92,4 +92,13 @@ public static AbstractBuild<?,?> getMostRecentBuildForCriteriaWithSnapshot(Abstr
return null;
}
}
}

public static String getFileNameForMethod(String method)
{
if (method == "ZIP") {
return "workspace.zip";
} else {
return "workspace.tar.gz";
}
}
}
Expand Up @@ -26,11 +26,20 @@ THE SOFTWARE.
<f:entry title="${%Files to include in cloned workspace}" field="workspaceGlob" help="/plugin/clone-workspace-scm/workspaceGlob.html">
<f:textbox />
</f:entry>
<f:entry title="${%Files to exclude from cloned workspace}" field="workspaceExcludeGlob" help="/plugin/clone-workspace-scm/workspaceExcludeGlob.html">
<f:textbox />
</f:entry>
<f:entry title="${%Criteria for build to be archived}" help="/plugin/clone-workspace-scm/parentCriteria.html">
<select name="criteria">
<f:option value="Any" selected='${instance.criteria=="Any"}'>${%Most Recent Completed Build}</f:option>
<f:option value="Not Failed" selected='${instance.criteria=="Not Failed"}'>${%Most Recent Not Failed Build}</f:option>
<f:option value="Successful" selected='${instance.criteria=="Successful"}'>${%Most Recent Successful Build}</f:option>
</select>
</f:entry>
</j:jelly>
<f:entry title="${%Archive method}" help="/plugin/clone-workspace-scm/archiveMethod.html">
<select name="archiveMethod">
<f:option value="TAR" selected='${instance.criteria=="TAR"}'>${%Gzipped tar}</f:option>
<f:option value="ZIP" selected='${instance.criteria=="ZIP"}'>${%Zipped}</f:option>
</select>
</f:entry>
</j:jelly>
3 changes: 3 additions & 0 deletions src/main/webapp/archiveMethod.html
@@ -0,0 +1,3 @@
<div>
<p>Set the archive method to specify what method the workspace is archived with. Zipping is the standard method performed by Jenkins, however prevents Unix permissions from being included. Ussing a gzipped tar file will properly perserve permissions.</p>
</div>
7 changes: 7 additions & 0 deletions src/main/webapp/workspaceExcludeGlob.html
@@ -0,0 +1,7 @@
<div>
<p>Specify the files to exclude from the archive of the build's workspace,
using wildcards and separators like '**/.svn/**/*, .git/**/*'.
See <a href='http://ant.apache.org/manual/CoreTypes/fileset.html'>
the @includes of Ant fileset</a> for the exact format.
The base directory is <a href='ws/'>the workspace</a>.</p>
</div>

0 comments on commit 46807bc

Please sign in to comment.