package hudson.plugins.templateproject;
import hudson.Extension;
import hudson.FilePath;
import hudson.Launcher;
import hudson.console.HyperlinkNote;
import hudson.Util;
import hudson.model.BuildListener;
import hudson.model.Item;
import hudson.model.TaskListener;
import hudson.model.AbstractBuild;
import hudson.model.AbstractProject;
import hudson.model.Hudson;
import hudson.model.Node;
import hudson.model.Run;
import hudson.scm.ChangeLogParser;
import hudson.scm.NullSCM;
import hudson.scm.PollingResult;
import hudson.scm.RepositoryBrowser;
import hudson.scm.SCMDescriptor;
import hudson.scm.SCMRevisionState;
import hudson.scm.SCM;
import hudson.security.AccessControlled;
import hudson.tasks.Messages;
import hudson.util.FormValidation;
import java.io.File;
import java.io.IOException;
import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
import org.apache.commons.lang.StringUtils;
import org.kohsuke.stapler.AncestorInPath;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.QueryParameter;
import java.util.List;
import org.jenkinsci.plugins.multiplescms.MultiSCM;
import org.jenkinsci.plugins.multiplescms.MultiSCMRevisionState;
public class ProxySCM extends SCM {
private final String projectName;
@DataBoundConstructor
public ProxySCM(String projectName) {
this.projectName = projectName;
}
public String getProjectName() {
return projectName;
}
public String getExpandedProjectName(AbstractBuild<?, ?> build) {
return TemplateUtils.getExpandedProjectName(projectName, build);
}
// Primarily used for polling, not building.
public AbstractProject<?, ?> getProject() {
return TemplateUtils.getProject(projectName, null);
}
public SCM getProjectScm(AbstractBuild<?, ?> build) {
try {
return TemplateUtils.getProject(projectName, build).getScm();
} catch (Exception e) {
return new NullSCM();
}
}
public SCM getProjectScm() {
return getProjectScm(null);
}
public void checkout(@Nonnull Run<?,?> build, @Nonnull Launcher launcher, @Nonnull FilePath workspace,
@Nonnull TaskListener listener, @CheckForNull File changelogFile, @CheckForNull SCMRevisionState baseline)
throws IOException, InterruptedException {
// Unique situation where MultiSCM has $None for SCMRevisionState
// Potentially due to SCM polling and references lost, or fixed with:
// https://github.com/jenkinsci/multiple-scms-plugin/pull/6
// https://issues.jenkins-ci.org/browse/JENKINS-27638
// Since MultiSCM is optional, might not be installed, hacky check string name.
if (getProjectScm((AbstractBuild) build).toString().contains("multiplescms")) {
if ((baseline == SCMRevisionState.NONE) || (baseline == null)) {
baseline = new MultiSCMRevisionState();
}
}
AbstractProject p = TemplateUtils.getProject(getProjectName(), (AbstractBuild) build);
listener.getLogger().println("[TemplateProject] Using SCM from: " + HyperlinkNote.encodeTo('/'+ p.getUrl(), p.getFullDisplayName()));
getProjectScm((AbstractBuild) build).checkout(build, launcher, workspace, listener, changelogFile, baseline);
}
@Override
public ChangeLogParser createChangeLogParser() {
return getProjectScm().createChangeLogParser();
}
@Override
@Deprecated
public boolean pollChanges(AbstractProject project, Launcher launcher,
FilePath workspace, TaskListener listener) throws IOException,
InterruptedException {
return getProjectScm().pollChanges(project, launcher, workspace, listener);
}
@Extension
public static class DescriptorImpl extends SCMDescriptor {
public DescriptorImpl() {
super(null);
}
@Override
public String getDisplayName() {
return "Use SCM from another project";
}
/**
* Form validation method.
*/
public FormValidation doCheckProjectName(@AncestorInPath AccessControlled anc, @QueryParameter String value) {
// Require CONFIGURE permission on this project
if (!anc.hasPermission(Item.CONFIGURE)) return FormValidation.ok();
//this check is important because otherwise plugin will check for similar project which impacts performance
//the check will be performed even if this plugin is not used as SCM for the current project
if(StringUtils.isEmpty(value)) {
return FormValidation.error("Project cannot be empty");
}
Item item = Hudson.getInstance().getItemByFullName(value, Item.class);
if (item == null) {
return FormValidation.error(Messages.BuildTrigger_NoSuchProject(value,
AbstractProject.findNearest(value).getName()));
}
if (!(item instanceof AbstractProject)) {
return FormValidation.error(Messages.BuildTrigger_NotBuildable(value));
}
return FormValidation.ok();
}
}
// If a Parameter is used for projectName, some of these won't return anythign useful.
// Because of it's nature `expand()`-ing the parameter is only useful at run time.
@Override
public RepositoryBrowser getBrowser() {
return getProjectScm().getBrowser();
}
@Override
public FilePath getModuleRoot(FilePath workspace) {
return getProjectScm().getModuleRoot(workspace);
}
@Override
public FilePath[] getModuleRoots(FilePath workspace) {
return getProjectScm().getModuleRoots(workspace);
}
@Override
public boolean processWorkspaceBeforeDeletion(
AbstractProject<?, ?> project, FilePath workspace, Node node)
throws IOException, InterruptedException {
return getProjectScm().processWorkspaceBeforeDeletion(project, workspace, node);
}
@Override
public boolean requiresWorkspaceForPolling() {
return getProjectScm().requiresWorkspaceForPolling();
}
@Override
public boolean supportsPolling() {
// @TODO: worth adding check if expandedProjectName even exists?
// If still $PROJECT, won't expand so nothing to poll.
return getProjectScm().supportsPolling();
}
@Override
public void buildEnvVars(AbstractBuild<?, ?> build, java.util.Map<String, String> env) {
getProjectScm(build).buildEnvVars(build, env);
}
@Override
public SCMRevisionState calcRevisionsFromBuild(AbstractBuild<?, ?> paramAbstractBuild, Launcher paramLauncher,
TaskListener paramTaskListener) throws IOException, InterruptedException {
return getProjectScm(paramAbstractBuild).calcRevisionsFromBuild(paramAbstractBuild, paramLauncher, paramTaskListener);
}
@Override
protected PollingResult compareRemoteRevisionWith(AbstractProject<?, ?> project, Launcher launcher,
FilePath workspace, TaskListener listener, SCMRevisionState baseline)
throws IOException, InterruptedException {
return getProjectScm().poll(project, launcher, workspace, listener, baseline);
}
}