All Downloads are FREE. Search and download functionalities are using the official Maven repository.

hudson.plugins.cloneworkspace.CloneWorkspaceSCM Maven / Gradle / Ivy

/*
 * The MIT License
 * 
 * Copyright (c) 2004-2010, Sun Microsystems, Inc., Kohsuke Kawaguchi, Andrew Bayer
 * 
 * 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.plugins.cloneworkspace;

import hudson.scm.PollingResult;
import hudson.scm.SCM;
import hudson.scm.ChangeLogParser;
import hudson.scm.SCMDescriptor;
import hudson.scm.SCMRevisionState;
import static hudson.scm.PollingResult.BUILD_NOW;
import static hudson.scm.PollingResult.NO_CHANGES;
import hudson.model.AbstractBuild;
import hudson.model.AbstractProject;
import hudson.model.Descriptor;
import hudson.model.TaskListener;
import hudson.model.ParametersAction;
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 hudson.Extension;
import static hudson.Util.fixEmptyAndTrim;

import java.io.IOException;
import java.io.File;
import java.io.Serializable;
import java.io.FileReader;
import java.io.BufferedReader;
import java.io.FileOutputStream;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

import net.sf.json.JSONObject;

import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.DataBoundConstructor;

/**
 * {@link SCM} that inherits the workspace from another build through {@link WorkspaceSnapshot}
 * Derived from {@link WorkspaceSnapshotSCM}.
 *
 * @author Kohsuke Kawaguchi
 * @author Andrew Bayer
 */
public class CloneWorkspaceSCM extends SCM {
    /**
     * The job name from which we inherit the workspace.
     */
    public String parentJobName;
    
    /**
     * The criteria by which to choose the build to inherit from.
     * Can be "Any" (meaning most recent completed build), "Not Failed" (meaning most recent unstable/stable build),
     * or "Successful" (meaning most recent stable build).
     */
    public String criteria;

    @DataBoundConstructor
    public CloneWorkspaceSCM(String parentJobName, String criteria) {
        this.parentJobName = parentJobName;
        this.criteria = criteria;
    }

    /**
     * Get the parent job name. Process it for parameters if needed.
     *
     * @return Parent job name.
     */
    public String getParamParentJobName(AbstractBuild build) {
        String original = parentJobName;
        if (build != null) {
            ParametersAction parameters = build.getAction(ParametersAction.class);
            if (parameters != null) {
                original = parameters.substitute(build, original);
            }
        }

        return original;
    }
        
    
    /**
     * Obtains the {@link WorkspaceSnapshot} object that this {@link SCM} points to,
     * or throws {@link ResolvedFailedException} upon failing.
     *
     * @param parentJob Processed parent job name.
     * @return never null.
     */
    public Snapshot resolve(String parentJob) throws ResolvedFailedException {
        Hudson h = Hudson.getInstance();
        AbstractProject job = h.getItemByFullName(parentJob, AbstractProject.class);
        if(job==null) {
            if(h.getItemByFullName(parentJob)==null) {
                AbstractProject nearest = AbstractProject.findNearest(parentJob);
                throw new ResolvedFailedException(Messages.CloneWorkspaceSCM_NoSuchJob(parentJob,nearest.getFullName()));
            } else
                throw new ResolvedFailedException(Messages.CloneWorkspaceSCM_IncorrectJobType(parentJob));
        }

        
        AbstractBuild b = CloneWorkspaceUtil.getMostRecentBuildForCriteria(job,criteria);
        
        if(b==null)
            throw new ResolvedFailedException(Messages.CloneWorkspaceSCM_NoBuild(criteria,parentJob));

        WorkspaceSnapshot snapshot = b.getAction(WorkspaceSnapshot.class);
        if(snapshot==null)
            throw new ResolvedFailedException(Messages.CloneWorkspaceSCM_NoWorkspace(parentJob,criteria));

        return new Snapshot(snapshot,b);
    }

    @Override
    public boolean checkout(AbstractBuild build, Launcher launcher, FilePath workspace, BuildListener listener, File changelogFile) throws IOException, InterruptedException {
        try {
            workspace.deleteContents();
            String parentJob = getParamParentJobName(build);
            Snapshot snapshot = resolve(parentJob);
            listener.getLogger().println("Restoring workspace from build #" + snapshot.getParent().getNumber() + " of project " + parentJob);
            snapshot.restoreTo(workspace,listener);

            // write out the parent build number file
            PrintWriter w = new PrintWriter(new FileOutputStream(getParentBuildFile(build)));
            try {
                w.println(snapshot.getParent().getNumber());
            } finally {
                w.close();
            }
            
            return calcChangeLog(snapshot.getParent(), changelogFile, listener);
        } catch (ResolvedFailedException e) {
            listener.error(e.getMessage()); // stack trace is meaningless
            build.setResult(Result.FAILURE);
            return false;
        }
    }

    /**
     * Called after checkout has finished to copy the changelog from the parent build.
     */
    private boolean calcChangeLog(AbstractBuild parentBuild, File changelogFile, BuildListener listener) throws IOException, InterruptedException {
        FilePath parentChangeLog = new FilePath(new File(parentBuild.getRootDir(), "changelog.xml"));
        if (parentChangeLog.exists()) {
            FilePath childChangeLog = new FilePath(changelogFile);
            parentChangeLog.copyTo(childChangeLog);
        }
        else {
            createEmptyChangeLog(changelogFile, listener, "log");
        }
        return true;
    }

    @Override
    public ChangeLogParser createChangeLogParser() {
        AbstractProject p = getContainingProject();
        final AbstractBuild lastBuild = p.getLastBuild();
        
        try {
            return resolve(getParamParentJobName(lastBuild)).getParent().getProject().getScm().createChangeLogParser();
        } catch (ResolvedFailedException e) {
            return null;
        } 
    }

    private AbstractProject getContainingProject() {
        for( AbstractProject p : Hudson.getInstance().getAllItems(AbstractProject.class) ) {
            SCM scm = p.getScm();
            if (scm != null && scm.getClass() == this.getClass() && this.equals(scm)) {
                return p;
            }
        }
        return null;
    }
                
    @Override
    public DescriptorImpl getDescriptor() {
        return (DescriptorImpl)super.getDescriptor();
    }

    public static File getParentBuildFile(AbstractBuild b) {
        return new File(b.getRootDir(),"cloneWorkspaceParent.txt");
    }

    
    /**
     * Reads the parent build file of the specified build (or the closest, if the flag is so specified.)
     *
     * @param findClosest
     *      If true, this method will go back the build history until it finds a parent build file.
     *      A build may not have a parent build file for any number of reasons (such as failure, interruption, etc.)
     * @return
     *      Number of parent build
     */
    private int parseParentBuildFile(AbstractBuild build, boolean findClosest) throws IOException {
        int parentBuildNumber = 0; // Default to 0, so that if we don't actually find a build,
                                   // polling et al will return true.

        // If the build itself is null, just return the default.
        if (build==null)
            return parentBuildNumber;
            
        if (findClosest) {
            for (AbstractBuild b=build; b!=null; b=b.getPreviousBuild()) {
                if(getParentBuildFile(b).exists()) {
                    build = b;
                    break;
                }
            }
        }
        
        {// read the parent build file of the build
            File file = getParentBuildFile(build);
            if(!file.exists())
                // nothing to compare against
                return parentBuildNumber;

            BufferedReader br = new BufferedReader(new FileReader(file));
            try {
                String line;
                while((line=br.readLine())!=null) {
                    try {
                        parentBuildNumber = Integer.parseInt(fixEmptyAndTrim(line));
                    } catch (NumberFormatException e) {
                        // perhaps a corrupted line. ignore
                    }
                }
            } finally {
                br.close();
            }
        }

        return parentBuildNumber;
    }

    @Override
    public SCMRevisionState calcRevisionsFromBuild(AbstractBuild build, Launcher launcher, TaskListener listener) throws IOException, InterruptedException {
        // exclude locations that are svn:external-ed with a fixed revision.
        int parentBuildNumber = parseParentBuildFile(build,true);

        return new CloneWorkspaceSCMRevisionState(parentBuildNumber);
    }

    @Override
    protected PollingResult compareRemoteRevisionWith(AbstractProject project, Launcher launcher, FilePath workspace, final TaskListener listener, SCMRevisionState _baseline) throws IOException, InterruptedException {
        final AbstractBuild lastBuild = project.getLastBuild();
        String parentJob = getParamParentJobName(lastBuild);
        Hudson h = Hudson.getInstance();
        AbstractProject parentProject = h.getItemByFullName(parentJob, AbstractProject.class);
        if (parentProject==null) {
            // Disable this project if the parent project no longer exists or doesn't exist in the first place.
            listener.getLogger().println("The CloneWorkspace parent project for " + project + " does not exist, project will be disabled."); 
            project.makeDisabled(true);
            return new PollingResult(_baseline, _baseline, PollingResult.Change.NONE);
        }

        final CloneWorkspaceSCMRevisionState baseline = (CloneWorkspaceSCMRevisionState)_baseline;

        Snapshot s = null;
        try {
            s = resolve(parentJob);
        } catch (ResolvedFailedException e) {
            listener.getLogger().println(e.getMessage());
            return new PollingResult(baseline, baseline, PollingResult.Change.NONE);
            //            return NO_CHANGES;
        }
        if (s==null) {
            listener.getLogger().println("Snapshot failed to resolve for unknown reasons.");
            return new PollingResult(baseline, baseline, PollingResult.Change.NONE);
            //            return NO_CHANGES;
        }
        else {
            if (s.getParent().getNumber() > baseline.parentBuildNumber) {
                listener.getLogger().println("Build #" + s.getParent().getNumber() + " of project " + parentJob
                                             + " is newer than build #" + baseline.parentBuildNumber + ", so a new build of "
                                             + project + " will be run.");
                return new PollingResult(baseline, new CloneWorkspaceSCMRevisionState(s.getParent().getNumber()), PollingResult.Change.SIGNIFICANT);
            //                return BUILD_NOW;
            }
            else {
                listener.getLogger().println("Build #" + s.getParent().getNumber() + " of project " + parentJob
                                             + " is NOT newer than build #" + baseline.parentBuildNumber + ", so no new build of "
                                             + project + " will be run.");
                return new PollingResult(baseline, baseline, PollingResult.Change.NONE);
            //                return NO_CHANGES;
            }
        }
    }

        

    @Extension
    public static class DescriptorImpl extends SCMDescriptor {
        public DescriptorImpl() {
            super(CloneWorkspaceSCM.class, null);
            load();
        }

        @Override
        public String getDisplayName() {
            return Messages.CloneWorkspaceSCM_DisplayName();
        }

        
        @Override
        public SCM newInstance(StaplerRequest req, JSONObject formData) throws FormException {
            return req.bindJSON(CloneWorkspaceSCM.class, formData);
        }

        public List getEligibleParents() {
            List parentNames = new ArrayList();
            
            for (AbstractProject p : Hudson.getInstance().getItems(AbstractProject.class)) {
                if (p.getPublishersList().get(CloneWorkspacePublisher.class) != null) {
                    parentNames.add(p.getDisplayName());
                }
            }
            
            return parentNames;
        }

    }

    public final class CloneWorkspaceSCMRevisionState extends SCMRevisionState implements Serializable {

        final int parentBuildNumber;

        CloneWorkspaceSCMRevisionState(int parentBuildNumber) {
            this.parentBuildNumber = parentBuildNumber;
        }

        private static final long serialVersionUID = 1L;
    }

 
    /**
     * {@link Exception} indicating that the resolution of the job/build failed.
     */
    private final class ResolvedFailedException extends Exception {
        private ResolvedFailedException(String message) {
            super(message);
        }
    }

    private static class Snapshot {
        final WorkspaceSnapshot snapshot;
        final AbstractBuild parent;

        private Snapshot(WorkspaceSnapshot snapshot, AbstractBuild parent) {
            this.snapshot = snapshot;
            this.parent = parent;
        }

        void restoreTo(FilePath dst,TaskListener listener) throws IOException, InterruptedException {
            snapshot.restoreTo(parent,dst,listener);
        }

        AbstractBuild getParent() {
            return parent;
        }
    }

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

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy