diff --git a/core/src/main/java/hudson/FileSystemProvisioner.java b/core/src/main/java/hudson/FileSystemProvisioner.java
index 8991a20a41..014892e896 100644
--- a/core/src/main/java/hudson/FileSystemProvisioner.java
+++ b/core/src/main/java/hudson/FileSystemProvisioner.java
@@ -9,6 +9,7 @@ import hudson.model.Job;
import hudson.model.TaskListener;
import hudson.model.listeners.RunListener;
import hudson.util.DescriptorList;
+import hudson.scm.SCM;
import java.io.BufferedOutputStream;
import java.io.File;
@@ -67,8 +68,22 @@ import java.io.OutputStream;
* the workspace snapshot of the upstream? Yes, by using {@link RunListener}.
* So this becomes like a special SCM type.
*
- * To support promoted builds, we need an abstraction for permalinks.
- * This is also needed for other UI.
+ *
Design take 2
+ *
+ * The first piece of this is the custom {@link SCM}, which inherits the
+ * workspace of another job. When this executes, it picks up
+ * {@link WorkspaceSnapshot} from the other job and use it to obtain the workspace.
+ *
+ *
+ * Then there's {@link RunListener}, which creates a snapshot if
+ * someone else is interested in using a snapshot later.
+ *
+ *
TODOs
+ *
+ * -
+ * Garbage collection of workspace snapshots.
+ *
+ *
*
* @author Kohsuke Kawaguchi
* @since 1.235
diff --git a/core/src/main/java/hudson/PermalinkList.java b/core/src/main/java/hudson/PermalinkList.java
new file mode 100644
index 0000000000..88c62f8812
--- /dev/null
+++ b/core/src/main/java/hudson/PermalinkList.java
@@ -0,0 +1,47 @@
+package hudson;
+
+import hudson.model.PermalinkProjectAction.Permalink;
+import hudson.util.EditDistance;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * {@link List} of {@link Permalink}s with some convenience methods.
+ *
+ * @author Kohsuke Kawaguchi
+ * @since 1.255
+ */
+public final class PermalinkList extends ArrayList {
+ public PermalinkList(Collection extends Permalink> c) {
+ super(c);
+ }
+
+ public PermalinkList() {
+ }
+
+ /**
+ * Gets the {@link Permalink} by its {@link Permalink#getId() id}.
+ *
+ * @return null if not found
+ */
+ public Permalink get(String id) {
+ for (Permalink p : this)
+ if(p.getId().equals(id))
+ return p;
+ return null;
+ }
+
+ /**
+ * Finds the closest name match by its ID.
+ */
+ public Permalink findNearest(String id) {
+ List ids = new ArrayList();
+ for (Permalink p : this)
+ ids.add(p.getId());
+ String nearest = EditDistance.findNearest(id, ids);
+ if(nearest==null) return null;
+ return get(nearest);
+ }
+}
diff --git a/core/src/main/java/hudson/fsp/WorkspaceSnapshotSCM.java b/core/src/main/java/hudson/fsp/WorkspaceSnapshotSCM.java
new file mode 100644
index 0000000000..6fea95f77e
--- /dev/null
+++ b/core/src/main/java/hudson/fsp/WorkspaceSnapshotSCM.java
@@ -0,0 +1,127 @@
+package hudson.fsp;
+
+import hudson.scm.SCM;
+import hudson.scm.ChangeLogParser;
+import hudson.scm.SCMDescriptor;
+import hudson.model.AbstractProject;
+import hudson.model.TaskListener;
+import hudson.model.AbstractBuild;
+import hudson.model.BuildListener;
+import hudson.model.Hudson;
+import hudson.model.Result;
+import hudson.model.PermalinkProjectAction.Permalink;
+import hudson.Launcher;
+import hudson.FilePath;
+import hudson.WorkspaceSnapshot;
+import hudson.PermalinkList;
+
+import java.io.IOException;
+import java.io.File;
+
+import org.kohsuke.stapler.DataBoundConstructor;
+
+/**
+ * {@link SCM} that inherits the workspace from another build through {@link WorkspaceSnapshot}
+ *
+ * @author Kohsuke Kawaguchi
+ */
+public class WorkspaceSnapshotSCM extends SCM {
+ /**
+ * The job name from which we inherit the workspace.
+ */
+ public String jobName;
+ /**
+ * The permalink name indicating the build from which to inherit the workspace.
+ */
+ public String permalink;
+
+ @DataBoundConstructor
+ public WorkspaceSnapshotSCM(String jobName, String permalink) {
+ this.jobName = jobName;
+ this.permalink = permalink;
+ }
+
+ /**
+ * {@link Exception} indicating that the resolution of the job/permalink failed.
+ */
+ private final class ResolvedFailedException extends Exception {
+ private ResolvedFailedException(String message) {
+ super(message);
+ }
+ }
+
+ private static class Snapshot {
+ final WorkspaceSnapshot snapshot;
+ final AbstractBuild,?> owner;
+ private Snapshot(WorkspaceSnapshot snapshot, AbstractBuild,?> owner) {
+ this.snapshot = snapshot;
+ this.owner = owner;
+ }
+
+ void restoreTo(FilePath dst,TaskListener listener) throws IOException, InterruptedException {
+ snapshot.restoreTo(owner,dst,listener);
+ }
+ }
+ /**
+ * Obtains the {@link WorkspaceSnapshot} object that this {@link SCM} points to,
+ * or throws {@link ResolvedFailedException} upon failing.
+ *
+ * @return never null.
+ */
+ public Snapshot resolve() throws ResolvedFailedException {
+ Hudson h = Hudson.getInstance();
+ AbstractProject,?> job = h.getItemByFullName(jobName, AbstractProject.class);
+ if(job==null) {
+ if(h.getItemByFullName(jobName)==null) {
+ AbstractProject nearest = AbstractProject.findNearest(jobName);
+ throw new ResolvedFailedException(Messages.WorkspaceSnapshotSCM_NoSuchJob(jobName,nearest.getFullName()));
+ } else
+ throw new ResolvedFailedException(Messages.WorkspaceSnapshotSCM_IncorrectJobType(jobName));
+ }
+
+ PermalinkList permalinks = job.getPermalinks();
+ Permalink p = permalinks.get(permalink);
+ if(p==null)
+ throw new ResolvedFailedException(Messages.WorkspaceSnapshotSCM_NoSuchPermalink(permalink,jobName));
+
+ AbstractBuild,?> b = (AbstractBuild,?>)p.resolve(job);
+ if(b==null)
+ throw new ResolvedFailedException(Messages.WorkspaceSnapshotSCM_NoBuild(permalink,jobName));
+
+ WorkspaceSnapshot snapshot = b.getAction(WorkspaceSnapshot.class);
+ if(snapshot==null)
+ throw new ResolvedFailedException(Messages.WorkspaceSnapshotSCM_NoWorkspace(jobName,permalink));
+
+ return new Snapshot(snapshot,b);
+ }
+
+ public boolean pollChanges(AbstractProject project, Launcher launcher, FilePath workspace, TaskListener listener) throws IOException, InterruptedException {
+ AbstractBuild lastBuild = (AbstractBuild) project.getLastBuild();
+ if (lastBuild == null) {
+ listener.getLogger().println("No existing build. Starting a new one");
+ return true;
+ }
+
+
+ return false;
+ }
+
+ public boolean checkout(AbstractBuild build, Launcher launcher, FilePath workspace, BuildListener listener, File changelogFile) throws IOException, InterruptedException {
+ try {
+ resolve().restoreTo(workspace,listener);
+ return true;
+ } catch (ResolvedFailedException e) {
+ listener.error(e.getMessage()); // stack trace is meaningless
+ build.setResult(Result.FAILURE);
+ return false;
+ }
+ }
+
+ public ChangeLogParser createChangeLogParser() {
+ return null;
+ }
+
+ public SCMDescriptor> getDescriptor() {
+ return null;
+ }
+}
diff --git a/core/src/main/java/hudson/model/Job.java b/core/src/main/java/hudson/model/Job.java
index 63c5e46699..1040ce9c24 100644
--- a/core/src/main/java/hudson/model/Job.java
+++ b/core/src/main/java/hudson/model/Job.java
@@ -4,6 +4,7 @@ import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
import hudson.ExtensionPoint;
import hudson.Util;
import hudson.XmlFile;
+import hudson.PermalinkList;
import hudson.model.Descriptor.FormException;
import hudson.model.listeners.ItemListener;
import hudson.model.PermalinkProjectAction.Permalink;
@@ -557,7 +558,7 @@ public abstract class Job, RunT extends Run, RunT extends Run getPermalinks() {
+ public PermalinkList getPermalinks() {
// TODO: shall we cache this?
- List permalinks = new ArrayList(Permalink.BUILTIN);
+ PermalinkList permalinks = new PermalinkList(Permalink.BUILTIN);
for (Action a : getActions()) {
if (a instanceof PermalinkProjectAction) {
PermalinkProjectAction ppa = (PermalinkProjectAction) a;
diff --git a/core/src/main/java/hudson/model/PermalinkProjectAction.java b/core/src/main/java/hudson/model/PermalinkProjectAction.java
index fa8ddc8199..907dd86e20 100644
--- a/core/src/main/java/hudson/model/PermalinkProjectAction.java
+++ b/core/src/main/java/hudson/model/PermalinkProjectAction.java
@@ -34,10 +34,17 @@ public interface PermalinkProjectAction extends Action {
public abstract String getDisplayName();
/**
- * The URL token to represent the permalink, such as "lastSuccessfulBuild".
+ * ID that uniquely identifies this permalink.
+ *
+ *
+ * The is also used as an URL token to represent the permalink.
* This becomes the part of the permanent URL.
+ *
+ *
+ * The expected format is the camel case,
+ * such as "lastSuccessfulBuild".
*/
- public abstract String getUrl();
+ public abstract String getId();
/**
* Resolves the permalink to a build.
@@ -58,7 +65,7 @@ public interface PermalinkProjectAction extends Action {
return Messages.Permalink_LastBuild();
}
- public String getUrl() {
+ public String getId() {
return "lastBuild";
}
@@ -72,7 +79,7 @@ public interface PermalinkProjectAction extends Action {
return Messages.Permalink_LastStableBuild();
}
- public String getUrl() {
+ public String getId() {
return "lastStableBuild";
}
@@ -86,7 +93,7 @@ public interface PermalinkProjectAction extends Action {
return Messages.Permalink_LastSuccessfulBuild();
}
- public String getUrl() {
+ public String getId() {
return "lastSuccessfulBuild";
}
@@ -100,7 +107,7 @@ public interface PermalinkProjectAction extends Action {
return Messages.Permalink_LastFailedBuild();
}
- public String getUrl() {
+ public String getId() {
return "lastFailedBuild";
}
diff --git a/core/src/main/resources/hudson/fsp/Messages.properties b/core/src/main/resources/hudson/fsp/Messages.properties
new file mode 100644
index 0000000000..6f3490388d
--- /dev/null
+++ b/core/src/main/resources/hudson/fsp/Messages.properties
@@ -0,0 +1,8 @@
+WorkspaceSnapshotSCM.NoSuchJob=No such job ''{0}'' exists. Perhaps you meant ''{1}''?
+WorkspaceSnapshotSCM.IncorrectJobType={0} isn''t a job that has a workspace.
+WorkspaceSnapshotSCM.NoBuild=There''s no qualifying build for the {0} permalink in {1}
+WorkspaceSnapshotSCM.NoSuchPermalink=No such permalink ''{0}'' exists for {1}
+WorkspaceSnapshotSCM.NoWorkspace=\
+ {0} {1} doesn''t have a workspace snapshot attached,n\
+ probably because when the build was done, no other jobs needed its workspace snapshot.\n\
+ Please run another build in {0} to get the workspace snapshot taken.
\ No newline at end of file
diff --git a/core/src/main/resources/hudson/model/PermalinkProjectAction/Permalink/link.jelly b/core/src/main/resources/hudson/model/PermalinkProjectAction/Permalink/link.jelly
index 61df88e9ff..87b7b28168 100644
--- a/core/src/main/resources/hudson/model/PermalinkProjectAction/Permalink/link.jelly
+++ b/core/src/main/resources/hudson/model/PermalinkProjectAction/Permalink/link.jelly
@@ -7,7 +7,7 @@
- ${%format(it.displayName,b.number,b.timestampString)}
+ ${%format(it.displayName,b.number,b.timestampString)}
\ No newline at end of file