package hudson.plugins.pvcs_scm;
import hudson.Extension;
import hudson.plugins.pvcs_scm.changelog.ChangeLogDocument;
import hudson.plugins.pvcs_scm.changelog.PvcsChangeLogSet;
import hudson.plugins.pvcs_scm.changelog.PvcsChangeLogEntry;
import hudson.Launcher;
import hudson.Proc;
import hudson.Util;
import hudson.FilePath;
import hudson.model.AbstractBuild;
import hudson.model.AbstractProject;
import hudson.model.BuildListener;
import hudson.model.TaskListener;
import hudson.model.Run;
import hudson.scm.SCM;
import hudson.scm.SCMDescriptor;
import hudson.util.ArgumentListBuilder;
import hudson.util.FormValidation;
import java.io.File;
import java.io.ByteArrayOutputStream;
import java.io.PipedOutputStream;
import java.io.PipedInputStream;
import java.io.IOException;
import java.util.Calendar;
import java.text.SimpleDateFormat;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
import javax.servlet.ServletException;
import net.sf.json.JSONObject;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.kohsuke.stapler.QueryParameter;
/**
* Provides integration with PCVS.
*
* @author Brian Lalor <blalor@bravo5.org>
*/
public class PvcsScm extends SCM
{
private final Log logger = LogFactory.getLog(getClass());
/**
* Date format required by commands passed to PVCS
*/
private static final String IN_DATE_FORMAT = "MM/dd/yyyy hh:mm:ss aa";
/**
* Date format returned in the output of PVCS commands.
*/
private static final String OUT_DATE_FORMAT = "MMM dd yyyy HH:mm:ss";
private String projectRoot;
private String archiveRoot;
/**
* Additional prefix to changeset filenames to match with what {@link hudson.maven.FilteredChangeLogSet}
* expects.
*/
private String changeLogPrefixFudge;
private String moduleDir;
private String loginId;
private String pvcsWorkspace;
private String promotionGroup;
private String versionLabel;
private boolean cleanCopy;
//private int MAX;
// {{{ constructor
@DataBoundConstructor
public PvcsScm(final String projectRoot,
final String archiveRoot,
final String changeLogPrefixFudge,
//final int MAX,
final String moduleDir,
final String loginId,
final String pvcsWorkspace,
final String promotionGroup,
final String versionLabel,
final boolean cleanCopy)
{
this.projectRoot = projectRoot;
this.archiveRoot = archiveRoot;
this.changeLogPrefixFudge = changeLogPrefixFudge;
this.moduleDir= moduleDir;
this.loginId = loginId;
this.pvcsWorkspace = pvcsWorkspace;
this.promotionGroup = promotionGroup;
this.versionLabel = versionLabel;
this.cleanCopy = cleanCopy;
logger.debug("created new instance");
}
// }}}
// {{{ getProjectRoot
public String getProjectRoot() {
return projectRoot;
}
// }}}
// {{{ setProjectRoot
public void setProjectRoot(final String projectRoot) {
this.projectRoot = projectRoot;
}
// }}}
// {{{ getArchiveRoot
public String getArchiveRoot() {
return archiveRoot;
}
// }}}
// {{{ setArchiveRoot
public void setArchiveRoot(final String archiveRoot) {
this.archiveRoot = archiveRoot;
}
// }}}
// {{{ isCleanCopy
public boolean isCleanCopy() {
return cleanCopy;
}
// }}}
// {{{ setCleanCopy
public void setCleanCopy(final boolean cleanCopy) {
this.cleanCopy = cleanCopy;
}
// }}}
// {{{ getModuleDir
public String getModuleDir() {
return this.moduleDir;
}
// }}}
// {{{ setModuleDir
public void setModuleDir(final String moduleDir) {
System.out.println("moduleDir");
this.moduleDir=moduleDir;
}
// }}}
// {{{ getLoginId
public String getLoginId() {
return loginId;
}
// }}}
// {{{ setLoginId
public void setLoginId(final String loginId) {
this.loginId = loginId;
}
// }}}
// {{{ getPvcsWorkspace
public String getPvcsWorkspace() {
return pvcsWorkspace;
}
// }}}
// {{{ setPvcsWorkspace
public void setPvcsWorkpace(final String pvcsWorkspace) {
this.pvcsWorkspace = pvcsWorkspace;
}
// }}}
// {{{ getPromotionGroup
public String getPromotionGroup() {
return promotionGroup;
}
// }}}
// {{{ setPromotionGroup
public void setPromotionGroup(final String promotionGroup) {
this.promotionGroup = promotionGroup;
}
// }}}
// {{{ getVersionLabel
public String getVersionLabel() {
return versionLabel;
}
// }}}
// {{{ setVersionLabel
public void setVersionLabel(final String versionLabel) {
this.versionLabel = versionLabel;
}
// }}}
// {{{ getChangeLogPrefixFudge
public String getChangeLogPrefixFudge() {
return changeLogPrefixFudge;
}
// }}}
// {{{ setChangeLogPrefixFudge
public void setChangeLogPrefixFudge(final String changeLogPrefixFudge) {
this.changeLogPrefixFudge = changeLogPrefixFudge;
}
// }}}
// {{{ requiresWorkspaceForPolling
/**
* @todo
*/
@Override
public boolean requiresWorkspaceForPolling() {
return false;
}
// }}}
// {{{ checkout
/**
* {@inheritDoc}
*/
@Override
public boolean checkout(final AbstractBuild build,
final Launcher launcher,
final FilePath workspace,
final BuildListener listener,
final File changelogFile)
throws IOException, InterruptedException
{
logger.trace("in checkout()");
boolean checkoutSucceeded = true;
ChangeLogDocument doc = ChangeLogDocument.Factory.newInstance();
Run previousBuild = build.getPreviousBuild();
if (previousBuild != null) {
doc.setChangeLog(getModifications(launcher,
listener,
previousBuild.getTimestamp()));
doc.getChangeLog().setLastBuildId(previousBuild.getId());
doc.getChangeLog().setLastBuildTime(previousBuild.getTimestamp());
} else {
doc.addNewChangeLog();
}
doc.getChangeLog().setBuildId(build.getId());
doc.save(changelogFile);
if (cleanCopy) {
listener.getLogger().println("clean copy configured; deleting contents of " + workspace);
logger.info("deleting contents of workspace " + workspace);
workspace.deleteContents();
}
ArgumentListBuilder cmd = new ArgumentListBuilder();
cmd.add(getDescriptor().getExecutable());
cmd.add("-nb", "run", "-ns", "-y");
cmd.add("get");
cmd.add("-pr" + projectRoot);
if (loginId != null && !loginId.trim().equals("")) {
cmd.add("-id" + loginId);
}
if (pvcsWorkspace != null && !pvcsWorkspace.trim().equals("")) {
cmd.add("-sp" + pvcsWorkspace);
}
if (versionLabel != null && !versionLabel.trim().equals("")) {
cmd.add("-v" + versionLabel);
}else if (promotionGroup != null && !promotionGroup.trim().equals("")) {
cmd.add("-g" + promotionGroup);
}
cmd.add("-bp/");
cmd.add("-o");
cmd.add("-a.");
cmd.add("-z");
cmd.add(moduleDir);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
logger.debug("launching command " + cmd.toList());
int rc = launcher.launch().cmds(cmd).stdout(baos).pwd(workspace).join();
if (rc != 0) {
// checkoutSucceeded = false;
logger.error("command exited with " + rc);
listener.error("command exited with " + rc);
// listener.error(baos.toString());
listener.error("continuing anyway. @todo: filter results from PVCS");
} /* else */ {
if (logger.isTraceEnabled()) {
logger.trace("pcli output:\n" + new String(baos.toByteArray()));
}
listener.getLogger().println("pcli output:");
listener.getLogger().write(baos.toByteArray(), 0, baos.size());
}
return checkoutSucceeded;
}
// }}}
// {{{ pollChanges
/**
* {@inheritDoc}
*/
@Override
public boolean pollChanges(final AbstractProject project,
final Launcher launcher,
final FilePath workspace,
final TaskListener listener)
throws IOException, InterruptedException
{
logger.debug("polling for changes in " + workspace);
// default to change being detected
boolean changeDetected = true;
if (project.getLastBuild() == null) {
logger.info("no existing build; starting a new one");
listener.getLogger().println("no existing build; starting a new one");
} else {
PvcsChangeLogSet changeSet =
getModifications(launcher,
listener,
project.getLastBuild().getTimestamp());
changeDetected = (changeSet.sizeOfEntryArray() > 0);
if (! changeDetected) {
listener.getLogger().println("no changes detected");
} else {
for (PvcsChangeLogEntry entry : changeSet.getEntryArray()) {
listener.getLogger().print("==> " + entry.getFileName() + " ");
listener.getLogger().print(entry.getRevision() + " ");
listener.getLogger().print(entry.getModifiedTime().getTime() + " ");
listener.getLogger().println(entry.getModifiedTime().getTime());
listener.getLogger().println(entry.getComment());
}
}
}
return changeDetected;
}
// }}}
// {{{ getModifications
/**
* Returns a PvcsChangeLogSet containing all change entries since
* lastBuild.
*
* @param launcher the launcher to use to invoke the PVCS client.
* @param listener task listener for outputting status
* @param lastBuild the last time the job was built.
*/
public PvcsChangeLogSet getModifications(final Launcher launcher,
final TaskListener listener,
final Calendar lastBuild)
throws IOException, InterruptedException
{
Calendar now = Calendar.getInstance();
logger.info("looking for changes between " + lastBuild.getTime() + " and " + now.getTime());
listener.getLogger().println("looking for changes between " + lastBuild.getTime() + " and " + now.getTime());
SimpleDateFormat df = new SimpleDateFormat(IN_DATE_FORMAT);
PipedOutputStream os = new PipedOutputStream();
PvcsLogReader logReader =
new PvcsLogReader(new PipedInputStream(os),
archiveRoot,
changeLogPrefixFudge,
lastBuild.getTime());
ArgumentListBuilder cmd = new ArgumentListBuilder();
cmd.add(getDescriptor().getExecutable());
cmd.add("-nb", "run", "-ns", "-q");
cmd.add("vlog");
cmd.add("-pr" + projectRoot);
if (loginId != null && !loginId.trim().equals("")) {
cmd.add("-id" + loginId);
}
if (pvcsWorkspace != null && !pvcsWorkspace.trim().equals("")) {
cmd.add("-sp" + pvcsWorkspace);
}
if (versionLabel != null && !versionLabel.trim().equals("")) {
cmd.add("-v" + versionLabel);
}
if (promotionGroup != null && !promotionGroup.trim().equals("")) {
cmd.add("-g" + promotionGroup);
}
cmd.add("-i");
cmd.add("-ds" + df.format(lastBuild.getTime()));
cmd.add("-de" + df.format(now.getTime()));
cmd.add("-z");
cmd.add(moduleDir);
logger.debug("launching command " + cmd.toList());
Proc proc = launcher.launch().cmds(cmd).stdout(os).start();
Thread t = new Thread(logReader);
t.start();
int rc = proc.join();
os.close();
t.join();
if (rc != 0) {
logger.error("command failed, returned " + rc);
listener.error("command failed, returned " + rc);
}
return logReader.getChangeLogSet();
}
// }}}
// {{{ createChangeLogParser
/**
* {@inheritDoc}
*/
@Override
public PvcsChangeLogParser createChangeLogParser() {
return new PvcsChangeLogParser();
}
// }}}
// {{{ getDescriptor
/**
*
*/
@Override
public DescriptorImpl getDescriptor() {
return DescriptorImpl.DESCRIPTOR;
}
// }}}
public static final class DescriptorImpl extends SCMDescriptor<PvcsScm>
{
private static final Log LOGGER = LogFactory.getLog(DescriptorImpl.class);
@Extension
public static final DescriptorImpl DESCRIPTOR = new DescriptorImpl();
private String executable = "pcli";
// {{{ constructor
private DescriptorImpl() {
super(PvcsScm.class, null);
load();
}
// }}}
// {{{ getDisplayName
/**
* {@inheritDoc}
*/
@Override
public String getDisplayName() {
return "PVCS";
}
// }}}
// {{{ configure
/**
*
*/
@Override
public boolean configure(final StaplerRequest req, JSONObject formData) throws FormException {
LOGGER.debug("configuring from " + req);
executable = Util.fixEmpty(req.getParameter("pvcs.executable").trim());
save();
return true;
}
// }}}
// {{{ getExecutable
public String getExecutable() {
return executable;
}
// }}}
// {{{ doExecutableCheck
/**
*
*/
public FormValidation doExecutableCheck(@QueryParameter String value) {
return FormValidation.validateExecutable(value);
}
// }}}
}
}