package hudson.plugins.accurev;
import hudson.EnvVars;
import hudson.Extension;
import hudson.FilePath;
import hudson.Launcher;
import hudson.model.BuildListener;
import hudson.model.ModelObject;
import hudson.model.ParameterValue;
import hudson.model.Result;
import hudson.model.TaskListener;
import hudson.model.AbstractBuild;
import hudson.model.AbstractProject;
import hudson.model.Hudson;
import hudson.model.ParameterDefinition;
import hudson.model.ParametersDefinitionProperty;
import hudson.model.Run;
import hudson.model.StringParameterValue;
import hudson.plugins.jetty.security.Password;
import hudson.scm.ChangeLogParser;
import hudson.scm.ChangeLogSet;
import hudson.scm.EditType;
import hudson.scm.SCMDescriptor;
import hudson.scm.SCM;
import hudson.util.ArgumentListBuilder;
import hudson.util.FormValidation;
import java.io.File;
import java.io.IOException;
import java.io.ObjectStreamException;
import java.io.Serializable;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.TreeMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.servlet.ServletException;
import net.sf.json.JSONObject;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.QueryParameter;
import org.kohsuke.stapler.StaplerRequest;
import org.xmlpull.v1.XmlPullParserFactory;
/**
* Created by IntelliJ IDEA.
*
* @author connollys
* @since 09-Oct-2007 16:17:34
*/
public class AccurevSCM extends SCM {
// ------------------------------ FIELDS ------------------------------
public static final SimpleDateFormat ACCUREV_DATETIME_FORMATTER = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
@Extension
public static final AccurevSCMDescriptor DESCRIPTOR = new AccurevSCMDescriptor();
private static final Logger logger = Logger.getLogger(AccurevSCM.class.getName());
private static final Date NO_TRANS_DATE = new Date(0);
private static final String DEFAULT_SNAPSHOT_NAME_FORMAT = "${JOB_NAME}_${BUILD_NUMBER}";
private final String serverName;
private final String depot;
private final String stream;
private final boolean ignoreStreamParent;
private final boolean useWorkspace;
private final boolean usePurgeIfLastFailed;
private final boolean useUpdate;
private final boolean useRevert;
private final boolean useSnapshot;
private final String snapshotNameFormat;
private final boolean synctime;
private final String workspace;
private final String workspaceSubPath;
// --------------------------- CONSTRUCTORS ---------------------------
/**
* Our constructor.
*/
@DataBoundConstructor
public AccurevSCM(String serverName,
String depot,
String stream,
boolean useWorkspace,
String workspace,
String workspaceSubPath,
boolean synctime,
boolean useUpdate,
boolean usePurgeIfLastFailed,
boolean useRevert,
boolean useSnapshot,
String snapshotNameFormat,
boolean ignoreStreamParent) {
super();
this.serverName = serverName;
this.depot = depot;
this.stream = stream;
this.useWorkspace = useWorkspace;
this.workspace = workspace;
this.workspaceSubPath = workspaceSubPath;
this.synctime = synctime;
this.useUpdate = useUpdate;
this.usePurgeIfLastFailed = usePurgeIfLastFailed;
this.useRevert = useRevert;
this.useSnapshot = useSnapshot;
this.snapshotNameFormat = snapshotNameFormat;
this.ignoreStreamParent = ignoreStreamParent;
}
// --------------------- GETTER / SETTER METHODS ---------------------
/**
* Getter for property 'depot'.
*
* @return Value for property 'depot'.
*/
public String getDepot() {
return depot;
}
/**
* Getter for property 'serverName'.
*
* @return Value for property 'serverName'.
*/
public String getServerName() {
return serverName;
}
/**
* Getter for property 'stream'.
*
* @return Value for property 'stream'.
*/
public String getStream() {
return stream;
}
/**
* Getter for property 'workspace'.
*
* @return Value for property 'workspace'.
*/
public String getWorkspace() {
return workspace;
}
/**
* Getter for property 'workspaceSubPath'.
*
* @return Value for property 'workspaceSubPath'.
*/
public String getWorkspaceSubPath() {
return workspaceSubPath;
}
/**
* Getter for property 'snapshotNameFormat'.
*
* @return Value for property 'snapshotNameFormat'.
*/
public String getSnapshotNameFormat() {
return snapshotNameFormat;
}
/**
* Getter for property 'ignoreStreamParent'.
*
* @return Value for property 'ignoreStreamParent'.
*/
public boolean isIgnoreStreamParent() {
return ignoreStreamParent;
}
/**
* Getter for property 'synctime'.
*
* @return Value for property 'synctime'.
*/
public boolean isSynctime() {
return synctime;
}
/**
* Getter for property 'usePurgeIfLastFailed'.
*
* @return Value for property 'usePurgeIfLastFailed'.
*/
public boolean isUsePurgeIfLastFailed() {
return usePurgeIfLastFailed;
}
/**
* Getter for property 'useUpdate'.
*
* @return Value for property 'useUpdate'.
*/
public boolean isUseUpdate() {
return useUpdate;
}
/**
* Getter for property 'useRevert'.
*
* @return Value for property 'useRevert'.
*/
public boolean isUseRevert() {
return useRevert;
}
/**
* Getter for property 'useSnapshot'.
*
* @return Value for property 'useSnapshot'.
*/
public boolean isUseSnapshot() {
return useSnapshot;
}
/**
* Getter for property 'useWorkspace'.
*
* @return Value for property 'useWorkspace'.
*/
public boolean isUseWorkspace() {
return useWorkspace;
}
// ------------------------ INTERFACE METHODS ------------------------
// --------------------- Interface Describable ---------------------
/**
* {@inheritDoc}
*/
@Override
public SCMDescriptor> getDescriptor() {
return DESCRIPTOR;
}
// -------------------------- OTHER METHODS --------------------------
/**
* Exposes AccuRev-specific information to the environment.
* The following variables become available, if not null:
*
* - ACCUREV_DEPOT - The depot name
* - ACCUREV_STREAM - The stream name
* - ACCUREV_SERVER - The server name
* - ACCUREV_WORKSPACE - The workspace name
* - ACCUREV_SUBPATH - The workspace subpath
*
*
* @since 0.6.9
*/
@Override
public void buildEnvVars(AbstractBuild build, Map env) {
// call super even though SCM.buildEnvVars currently does nothing - this could change
super.buildEnvVars(build, env);
// add various accurev-specific variables to the environment
if (depot != null)
env.put("ACCUREV_DEPOT", depot);
if (stream != null)
env.put("ACCUREV_STREAM", stream);
if (serverName != null)
env.put("ACCUREV_SERVER", serverName);
if (workspace != null && useWorkspace)
env.put("ACCUREV_WORKSPACE", workspace);
if (workspaceSubPath != null)
env.put("ACCUREV_SUBPATH", workspaceSubPath);
// grab the last promote transaction from the changelog file
String lastTransaction = null;
// Abstract should have this since checkout should have already run
ChangeLogSet changeSet = build.getChangeSet();
if (!changeSet.isEmptySet()) {
// first EDIT entry should be the last transaction we want
for (Object o : changeSet.getItems()) {
AccurevTransaction t = (AccurevTransaction) o;
if (t.getEditType() == EditType.EDIT) { // this means promote or chstream in AccuRev
lastTransaction = t.getRevision();
break;
}
}
/*
* in case you get a changelog with no changes (e.g. a dispatch
* message or something I don't know about yet), set something
* different than nothing
*/
if (lastTransaction == null) {
lastTransaction = "NO_EDITS";
}
}
if (lastTransaction != null) {
env.put("ACCUREV_LAST_TRANSACTION", lastTransaction);
}
}
/**
* {@inheritDoc}
*/
public boolean checkout(AbstractBuild build, Launcher launcher, FilePath workspace, BuildListener listener,
File changelogFile) throws IOException, InterruptedException {
final AccurevServer server = DESCRIPTOR.getServer(serverName);
final String accurevPath = workspace.act(new FindAccurevClientExe(server));
if (!useWorkspace
|| !useUpdate
||
(
usePurgeIfLastFailed &&
build.getPreviousBuild() != null &&
build.getPreviousBuild().getResult().isWorseThan(Result.UNSTABLE)
)
) {
workspace.act(new PurgeWorkspaceContents(listener));
}
final Map accurevEnv = new HashMap();
if (!ensureLoggedInToAccurev(server, accurevEnv, workspace, listener, accurevPath,
launcher)) {
return false;
}
if (synctime) {
listener.getLogger().println("Synchronizing clock with the server...");
if (!synctime(server, accurevEnv, workspace, listener, accurevPath, launcher)) {
return false;
}
}
if (depot == null || "".equals(depot)) {
listener.fatalError("Must specify a depot");
return false;
}
if (stream == null || "".equals(stream)) {
listener.fatalError("Must specify a stream");
return false;
}
final EnvVars environment = build.getEnvironment(listener);
final String localStream = environment.expand(stream);
listener.getLogger().println("Getting a list of streams...");
final Map streams = getStreams(localStream, server, accurevEnv, workspace, listener, accurevPath,
launcher);
if (streams != null && !streams.containsKey(localStream)) {
listener.fatalError("The specified stream does not appear to exist!");
return false;
}
if (useWorkspace && (this.workspace == null || "".equals(this.workspace))) {
listener.fatalError("Must specify a workspace");
return false;
}
final Date startDateOfPopulate;
if (useWorkspace) {
listener.getLogger().println("Getting a list of workspaces...");
final Map workspaces = getWorkspaces(server, accurevEnv, workspace, listener, accurevPath, launcher);
if (workspaces == null) {
listener.fatalError("Cannot determine workspace configuration information");
return false;
}
if (!workspaces.containsKey(this.workspace)) {
listener.fatalError("The specified workspace does not appear to exist!");
return false;
}
AccurevWorkspace accurevWorkspace = workspaces.get(this.workspace);
if (!depot.equals(accurevWorkspace.getDepot())) {
listener.fatalError("The specified workspace, " + this.workspace + ", is based in the depot " + accurevWorkspace.getDepot() + " not " + depot);
return false;
}
for (AccurevStream accurevStream : streams.values()) {
if (accurevWorkspace.getStreamNumber().equals(accurevStream.getNumber())) {
accurevWorkspace.setStream(accurevStream);
break;
}
}
final RemoteWorkspaceDetails remoteDetails;
try {
remoteDetails = workspace.act(new DetermineRemoteHostname(workspace.getRemote()));
} catch (IOException e) {
listener.fatalError("Unable to validate workspace host.");
e.printStackTrace(listener.getLogger());
return false;
}
// handle workspace relocation
{
boolean needsRelocation = false;
final ArgumentListBuilder chwscmd = new ArgumentListBuilder();
chwscmd.add(accurevPath);
chwscmd.add("chws");
addServer(chwscmd, server);
chwscmd.add("-w");
chwscmd.add(this.workspace);
if (!localStream.equals(accurevWorkspace.getStream().getParent().getName())) {
listener.getLogger().println("Parent stream needs to be updated.");
needsRelocation = true;
chwscmd.add("-b");
chwscmd.add(localStream);
}
if (!accurevWorkspace.getHost().equals(remoteDetails.getHostName())) {
listener.getLogger().println("Host needs to be updated.");
needsRelocation = true;
chwscmd.add("-m");
chwscmd.add(remoteDetails.getHostName());
}
final String oldStorage = accurevWorkspace.getStorage()
.replace("/", remoteDetails.getFileSeparator())
.replace("\\", remoteDetails.getFileSeparator());
if (!oldStorage.equals(remoteDetails.getPath())) {
listener.getLogger().println("Storage needs to be updated.");
needsRelocation = true;
chwscmd.add("-l");
chwscmd.add(workspace.getRemote());
}
if (needsRelocation) {
listener.getLogger().println("Relocating workspace...");
listener.getLogger().println(" Old host: " + accurevWorkspace.getHost());
listener.getLogger().println(" New host: " + remoteDetails.getHostName());
listener.getLogger().println(" Old storage: " + oldStorage);
listener.getLogger().println(" New storage: " + remoteDetails.getPath());
listener.getLogger().println(" Old parent stream: " + accurevWorkspace.getStream().getParent()
.getName());
listener.getLogger().println(" New parent stream: " + localStream);
if (!AccurevLauncher.runCommand("Workspace relocation command", launcher, chwscmd, null,
getOptionalLock(), accurevEnv, workspace, listener, logger, true)) {
return false;
}
listener.getLogger().println("Relocation successfully.");
}
}
if (useRevert) {
listener.getLogger().println("attempting to get overlaps");
final List overlaps = getOverlaps(server, accurevEnv, workspace, listener, accurevPath, launcher);
if (overlaps != null && overlaps.size() > 0) {
workspace.act(new PurgeWorkspaceOverlaps(listener, overlaps));
}
}
// Update workspace to contain latest code
{
listener.getLogger().println("Updating workspace...");
final ArgumentListBuilder updatecmd = new ArgumentListBuilder();
updatecmd.add(accurevPath);
updatecmd.add("update");
addServer(updatecmd, server);
if (!AccurevLauncher.runCommand("Workspace update command", launcher, updatecmd, null,
getOptionalLock(), accurevEnv, workspace, listener, logger, true)) {
return false;
}
listener.getLogger().println("Update completed successfully.");
}
// Now get that into local filesystem
{
listener.getLogger().println("Populating workspace...");
final ArgumentListBuilder popcmd = new ArgumentListBuilder();
popcmd.add(accurevPath);
popcmd.add("pop");
addServer(popcmd, server);
popcmd.add("-R");
if ((workspaceSubPath == null) || (workspaceSubPath.trim().length() == 0)) {
popcmd.add(".");
} else {
final StringTokenizer st = new StringTokenizer(workspaceSubPath, ",");
while (st.hasMoreElements()) {
popcmd.add(st.nextToken().trim());
}
}
startDateOfPopulate = new Date();
if (Boolean.TRUE != AccurevLauncher.runCommand("Populate workspace command", launcher, popcmd, null,
getOptionalLock(), accurevEnv, workspace, listener, logger, new ParsePopulate(),
listener.getLogger())) {
return false;
}
listener.getLogger().println("Populate completed successfully.");
}
} else if ( isUseSnapshot() ) {
final String snapshotName = calculateSnapshotName(build, listener);
listener.getLogger().println("Creating snapshot: " + snapshotName + "...");
build.getEnvironment(listener).put("ACCUREV_SNAPSHOT", snapshotName);
// snapshot command: accurev mksnap -H -s -b -t now
final ArgumentListBuilder mksnapcmd = new ArgumentListBuilder();
mksnapcmd.add(accurevPath);
mksnapcmd.add("mksnap");
addServer(mksnapcmd, server);
mksnapcmd.add("-s");
mksnapcmd.add(snapshotName);
mksnapcmd.add("-b");
mksnapcmd.add(localStream);
mksnapcmd.add("-t");
mksnapcmd.add("now");
if (!AccurevLauncher.runCommand("Create snapshot command", launcher, mksnapcmd, null, getOptionalLock(),
accurevEnv, workspace, listener, logger, true)) {
return false;
}
listener.getLogger().println("Snapshot created successfully.");
listener.getLogger().println("Populating workspace from snapshot...");
final ArgumentListBuilder popcmd = new ArgumentListBuilder();
popcmd.add(accurevPath);
popcmd.add("pop");
addServer(popcmd, server);
popcmd.add("-v");
popcmd.add(snapshotName);
popcmd.add("-L");
popcmd.add(workspace.getRemote());
popcmd.add("-R");
if ((workspaceSubPath == null) || (workspaceSubPath.trim().length() == 0)) {
popcmd.add(".");
} else {
final StringTokenizer st = new StringTokenizer(workspaceSubPath, ",");
while (st.hasMoreElements()) {
popcmd.add(st.nextToken().trim());
}
}
startDateOfPopulate = new Date();
if (Boolean.TRUE != AccurevLauncher.runCommand("Populate from snapshot command", launcher, popcmd, null,
getOptionalLock(), accurevEnv, workspace, listener, logger, new ParsePopulate(),
listener.getLogger())) {
return false;
}
listener.getLogger().println("Populate completed successfully.");
} else {
listener.getLogger().println("Populating workspace...");
final ArgumentListBuilder popcmd = new ArgumentListBuilder();
popcmd.add(accurevPath);
popcmd.add("pop");
addServer(popcmd, server);
popcmd.add("-v");
popcmd.add(localStream);
popcmd.add("-L");
popcmd.add(workspace.getRemote());
popcmd.add("-R");
if ((workspaceSubPath == null) || (workspaceSubPath.trim().length() == 0)) {
popcmd.add(".");
} else {
final StringTokenizer st = new StringTokenizer(workspaceSubPath, ",");
while (st.hasMoreElements()) {
popcmd.add(st.nextToken().trim());
}
}
startDateOfPopulate = new Date();
if (Boolean.TRUE != AccurevLauncher.runCommand("Populate command", launcher, popcmd, null,
getOptionalLock(), accurevEnv, workspace, listener, logger, new ParsePopulate(),
listener.getLogger())) {
return false;
}
listener.getLogger().println("Populate completed successfully.");
}
listener.getLogger().println(
"Calculating changelog" + (ignoreStreamParent ? ", ignoring changes in parent" : "") + "...");
final Calendar startTime;
if (null == build.getPreviousBuild()) {
listener.getLogger().println("Cannot find a previous build to compare against. Computing all changes.");
startTime = null;
} else {
startTime = build.getPreviousBuild().getTimestamp();
}
{
AccurevStream stream = streams == null ? null : streams.get(localStream);
if (stream == null) {
// if there was a problem, fall back to simple stream check
return captureChangelog(server, accurevEnv, workspace, listener, accurevPath, launcher,
startDateOfPopulate, startTime == null ? null : startTime.getTime(),
localStream, changelogFile);
}
// There may be changes in a parent stream that we need to factor in.
// TODO produce a consolidated list of changes from the parent streams
do {
// This is a best effort to get as close to the changes as possible
if (checkStreamForChanges(server, accurevEnv, workspace, listener, accurevPath, launcher,
stream.getName(), startTime == null ? null : startTime.getTime())) {
return captureChangelog(server, accurevEnv, workspace, listener, accurevPath, launcher,
startDateOfPopulate, startTime == null ? null : startTime
.getTime(), stream.getName(), changelogFile);
}
stream = stream.getParent();
} while (stream != null && stream.isReceivingChangesFromParent());
}
return captureChangelog(server, accurevEnv, workspace, listener, accurevPath, launcher,
startDateOfPopulate, startTime == null ? null : startTime.getTime(), localStream,
changelogFile);
}
private String calculateSnapshotName(final AbstractBuild build,
final BuildListener listener) throws IOException, InterruptedException {
final String actualFormat = (snapshotNameFormat == null || snapshotNameFormat
.trim().isEmpty()) ? DEFAULT_SNAPSHOT_NAME_FORMAT : snapshotNameFormat.trim();
final EnvVars environment = build.getEnvironment(listener);
final String snapshotName = environment.expand(actualFormat);
return snapshotName;
}
private Map getWorkspaces(AccurevServer server,
Map accurevEnv,
FilePath workspace,
TaskListener listener,
String accurevPath,
Launcher launcher)
throws IOException, InterruptedException {
final ArgumentListBuilder cmd = new ArgumentListBuilder();
cmd.add(accurevPath);
cmd.add("show");
addServer(cmd, server);
cmd.add("-fx");
cmd.add("-p");
cmd.add(depot);
cmd.add("wspaces");
final Map workspaces = AccurevLauncher.runCommand("Show workspaces command",
launcher, cmd, null, getOptionalLock(), accurevEnv, workspace, listener, logger,
XmlParserFactory.getFactory(), new ParseShowWorkspaces(), null);
return workspaces;
}
private boolean captureChangelog(AccurevServer server,
Map accurevEnv,
FilePath workspace,
BuildListener listener,
String accurevPath,
Launcher launcher,
Date buildDate,
Date startDate,
String stream,
File changelogFile) throws IOException, InterruptedException {
ArgumentListBuilder cmd = new ArgumentListBuilder();
cmd.add(accurevPath);
cmd.add("hist");
addServer(cmd, server);
cmd.add("-fx");
cmd.add("-a");
cmd.add("-s");
cmd.add(stream);
cmd.add("-t");
String dateRange = ACCUREV_DATETIME_FORMATTER.format(buildDate);
if (startDate != null) {
dateRange += "-" + ACCUREV_DATETIME_FORMATTER.format(startDate);
} else {
dateRange += ".100";
}
cmd.add(dateRange); // if this breaks windows there's going to be fun
final String commandDescription = "Changelog command";
final Boolean success = AccurevLauncher.runCommand(commandDescription, launcher, cmd, null, getOptionalLock(),
accurevEnv, workspace, listener, logger, new ParseOutputToFile(), changelogFile);
if (success != Boolean.TRUE) {
return false;
}
listener.getLogger().println("Changelog calculated successfully.");
return true;
}
/**
* {@inheritDoc}
*/
public ChangeLogParser createChangeLogParser() {
return new AccurevChangeLogParser();
}
private static boolean hasStringVariableReference(final String str) {
return str != null && str.indexOf("${") != -1;
}
@Override
public boolean requiresWorkspaceForPolling() {
final boolean needSlaveForPolling = !DESCRIPTOR.isPollOnMaster();
return needSlaveForPolling;
}
/**
* {@inheritDoc}
*/
public boolean pollChanges(AbstractProject project, Launcher launcher, FilePath workspace, TaskListener listener)
throws IOException, InterruptedException {
if( project.isInQueue()) {
listener.getLogger().println("Project build is currently in queue.");
return false;
}
if (workspace == null) {
// If we're claiming not to need a workspace in order to poll, then
// workspace will be null. In that case, we need to run directly
// from the project folder on the master.
final File projectDir = project.getRootDir();
workspace = new FilePath(projectDir);
launcher = Hudson.getInstance().createLauncher(listener);
}
listener.getLogger().println("Running commands from folder \""+workspace+'"');
AccurevServer server = DESCRIPTOR.getServer(serverName);
final String accurevPath = workspace.act(new FindAccurevClientExe(server));
final Map accurevEnv = new HashMap();
if (!ensureLoggedInToAccurev(server, accurevEnv, workspace, listener, accurevPath, launcher)) {
listener.getLogger().println("Authentication failure");
return false;
}
if (synctime) {
listener.getLogger().println("Synchronizing clock with the server...");
if (!synctime(server, accurevEnv, workspace, listener, accurevPath, launcher)) {
listener.getLogger().println("Synchronizing clock failure");
return false;
}
}
final Run lastBuild = project.getLastBuild();
if (lastBuild == null) {
listener.getLogger().println("Project has never been built");
return true;
}
final Date buildDate = lastBuild.getTimestamp().getTime();
listener.getLogger().println("Last build on " + buildDate);
final String localStream;
if (hasStringVariableReference(this.stream)) {
ParametersDefinitionProperty paramDefProp = (ParametersDefinitionProperty) project
.getProperty(ParametersDefinitionProperty.class);
if (paramDefProp == null) {
listener.getLogger().println(
"Polling is not supported when stream name has a variable reference '" + this.stream + "'.");
// as we don't know which stream to check we just state that
// there is no changes
return false;
}
// listener.getLogger().println("logout of parameter definitions ...");
Map keyValues = new TreeMap();
/* Scan for all parameter with an associated default values */
for (ParameterDefinition paramDefinition : paramDefProp.getParameterDefinitions()) {
// listener.getLogger().println("parameter definition for '" +
// paramDefinition.getName() + "':");
ParameterValue defaultValue = paramDefinition.getDefaultParameterValue();
if (defaultValue instanceof StringParameterValue) {
StringParameterValue strdefvalue = (StringParameterValue) defaultValue;
// listener.getLogger().println("parameter default value for '"
// + defaultValue.getName() + " / " +
// defaultValue.getDescription() + "' is '" +
// strdefvalue.value + "'.");
keyValues.put(defaultValue.getName(), strdefvalue.value);
}
}
final EnvVars environment = new EnvVars(keyValues);
localStream = environment.expand(this.stream);
listener.getLogger().println("... expanded '" + this.stream + "' to '" + localStream + "'.");
} else {
localStream = this.stream;
}
if (hasStringVariableReference(localStream)) {
listener.getLogger().println(
"Polling is not supported when stream name has a variable reference '" + this.stream + "'.");
// as we don't know which stream to check we just state that there
// is no changes
return false;
}
final Map streams = this.ignoreStreamParent ? null : getStreams(localStream, server,
accurevEnv, workspace, listener, accurevPath, launcher);
AccurevStream stream = streams == null ? null : streams.get(localStream);
if (stream == null) {
// if there was a problem, fall back to simple stream check
return checkStreamForChanges(server, accurevEnv, workspace, listener, accurevPath, launcher, localStream,
buildDate);
}
// There may be changes in a parent stream that we need to factor in.
do {
if (checkStreamForChanges(server, accurevEnv, workspace, listener, accurevPath, launcher, stream.getName(),
buildDate)) {
return true;
}
stream = stream.getParent();
} while (stream != null && stream.isReceivingChangesFromParent());
return false;
}
private boolean ensureLoggedInToAccurev(
AccurevServer server,
Map accurevEnv,
FilePath pathToRunCommandsIn,
TaskListener listener,
String accurevPath,
Launcher launcher)
throws IOException, InterruptedException {
final String accurevHomeEnvVar = "ACCUREV_HOME";
if (!accurevEnv.containsKey(accurevHomeEnvVar)) {
final String accurevHome = pathToRunCommandsIn.getParent().getRemote();
accurevEnv.put(accurevHomeEnvVar, accurevHome);
listener.getLogger().println("Setting " + accurevHomeEnvVar + " to \"" + accurevHome + '"');
}
if (server == null) {
return true;
}
final String requiredUsername = server.getUsername();
if( requiredUsername==null || requiredUsername.trim().length()==0 ) {
return true;
}
DESCRIPTOR.ACCUREV_LOCK.lock();
try {
final boolean loginRequired;
if (server.isMinimiseLogins()) {
final String currentUsername = getLoggedInUsername(server,
accurevEnv, pathToRunCommandsIn, listener, accurevPath,
launcher);
if (currentUsername==null) {
loginRequired = true;
listener.getLogger().println(
"Not currently authenticated with Accurev server");
} else {
loginRequired = !currentUsername
.equals(requiredUsername);
listener.getLogger().println(
"Currently authenticated with Accurev server as '"
+ currentUsername
+ (loginRequired ? "', login required"
: "', not logging in again."));
}
} else {
loginRequired = true;
}
if (loginRequired) {
return accurevLogin(server, accurevEnv, pathToRunCommandsIn,
listener, accurevPath, launcher);
}
} finally {
DESCRIPTOR.ACCUREV_LOCK.unlock();
}
return true;
}
private boolean accurevLogin(//
final AccurevServer server, //
final Map accurevEnv, //
final FilePath workspace, //
final TaskListener listener, //
final String accurevPath, //
final Launcher launcher) throws IOException, InterruptedException {
listener.getLogger().println("Authenticating with Accurev server...");
final boolean[] masks;
final ArgumentListBuilder cmd = new ArgumentListBuilder();
cmd.add(accurevPath);
cmd.add("login");
addServer(cmd, server);
if (server.isUseNonexpiringLogin()) {
cmd.add("-n");
}
cmd.add(server.getUsername());
if (server.getPassword() == null || "".equals(server.getPassword())) {
cmd.addQuoted("");
masks = new boolean[cmd.toCommandArray().length];
} else {
cmd.add(server.getPassword());
masks = new boolean[cmd.toCommandArray().length];
masks[masks.length - 1] = true;
}
final boolean success = AccurevLauncher.runCommand("login", launcher, cmd, masks, null, accurevEnv, workspace,
listener, logger);
if (success) {
listener.getLogger().println("Authentication completed successfully.");
return true;
} else {
return false;
}
}
private boolean synctime(//
final AccurevServer server, //
final Map accurevEnv, //
final FilePath workspace, //
final TaskListener listener, //
final String accurevPath, //
final Launcher launcher) throws IOException, InterruptedException {
final ArgumentListBuilder cmd = new ArgumentListBuilder();
if(launcher.isUnix()){
cmd.add("sudo");
}
cmd.add(accurevPath);
cmd.add("synctime");
addServer(cmd, server);
final boolean success = AccurevLauncher.runCommand("Synctime command", launcher, cmd, null, getOptionalLock(),
accurevEnv, workspace, listener, logger);
return success;
}
private Map getStreams(//
final String nameOfStreamRequired, //
final AccurevServer server, //
final Map accurevEnv, //
final FilePath workspace, //
final TaskListener listener, //
final String accurevPath, //
final Launcher launcher) throws IOException, InterruptedException {
final Map streams;
if( server.useRestrictedShowStreams)
{
streams = getAllAncestorStreams(nameOfStreamRequired, server, accurevEnv, workspace, listener, accurevPath,
launcher);
} else {
if (this.ignoreStreamParent) {
streams = getOneStream(nameOfStreamRequired, server, accurevEnv, workspace, listener, accurevPath,
launcher);
} else {
streams = getAllStreams(server, accurevEnv, workspace, listener, accurevPath, launcher);
}
}
return streams;
}
private Map getAllStreams(//
final AccurevServer server, //
final Map accurevEnv, //
final FilePath workspace, //
final TaskListener listener, //
final String accurevPath, //
final Launcher launcher) {
final ArgumentListBuilder cmd = new ArgumentListBuilder();
cmd.add(accurevPath);
cmd.add("show");
addServer(cmd, server);
cmd.add("-fx");
cmd.add("-p");
cmd.add(depot);
cmd.add("streams");
final Map streams = AccurevLauncher.runCommand("Show streams command", launcher, cmd,
null, getOptionalLock(), accurevEnv, workspace, listener, logger, XmlParserFactory.getFactory(),
new ParseShowStreams(), depot);
return streams;
}
private Map getAllAncestorStreams(//
final String nameOfStreamRequired, //
final AccurevServer server, //
final Map accurevEnv, //
final FilePath workspace, //
final TaskListener listener, //
final String accurevPath, //
final Launcher launcher) {
final Map streams = new HashMap();
String streamName = nameOfStreamRequired;
while (streamName != null && !streamName.isEmpty()) {
final Map oneStream = getOneStream(streamName, server, accurevEnv, workspace,
listener, accurevPath, launcher);
final AccurevStream theStream = oneStream == null ? null : oneStream.get(streamName);
streamName = null;
if (theStream != null) {
if (theStream.getBasisName() != null) {
streamName = theStream.getBasisName();
} else if (theStream.getBasisNumber() != null) {
streamName = theStream.getBasisNumber().toString();
}
streams.putAll(oneStream);
}
}
return streams;
}
private Map getOneStream(//
final String streamName, //
final AccurevServer server, //
final Map accurevEnv, //
final FilePath workspace, //
final TaskListener listener, //
final String accurevPath, //
final Launcher launcher) {
final ArgumentListBuilder cmd = new ArgumentListBuilder();
cmd.add(accurevPath);
cmd.add("show");
addServer(cmd, server);
cmd.add("-fx");
cmd.add("-p");
cmd.add(depot);
cmd.add("-s");
cmd.add(streamName);
cmd.add("streams");
final Map oneStream = AccurevLauncher.runCommand("Restricted show streams command",
launcher, cmd, null, getOptionalLock(), accurevEnv, workspace, listener, logger,
XmlParserFactory.getFactory(), new ParseShowStreams(), depot);
return oneStream;
}
/**
* get
*
* @param server
* @param accurevEnv
* @param workspace
* @param listener
* @param accurevPath
* @param launcher
* @return
* @throws IOException
* @throws InterruptedException
*/
private List getOverlaps(//
final AccurevServer server, //
final Map accurevEnv, //
final FilePath workspace, //
final TaskListener listener, //
final String accurevPath, //
final Launcher launcher) throws IOException, InterruptedException {
final ArgumentListBuilder cmd = new ArgumentListBuilder();
cmd.add(accurevPath);
cmd.add("stat");
addServer(cmd, server);
cmd.add("-fx");
cmd.add("-o");
final List overlaps = AccurevLauncher.runCommand("Stat overlaps command", launcher, cmd, null,
getOptionalLock(), accurevEnv, workspace, listener, logger, XmlParserFactory.getFactory(),
new ParseStatOverlaps(), null);
if (overlaps != null) {
for (final String filename : overlaps) {
listener.getLogger().println("Adding file to overlap list: " + filename);
}
}
return overlaps;
}
/**
*
* @param server
* @param accurevEnv
* @param workspace
* @param listener
* @param accurevPath
* @param launcher
* @param stream
* @param buildDate
* @return if there are any new transactions in the stream since the last build was done
* @throws IOException
* @throws InterruptedException
*/
private boolean checkStreamForChanges(AccurevServer server,
Map accurevEnv,
FilePath workspace,
TaskListener listener,
String accurevPath,
Launcher launcher,
String stream,
Date buildDate)
throws IOException, InterruptedException {
AccurevTransaction latestCodeChangeTransaction = new AccurevTransaction();
latestCodeChangeTransaction.setDate(NO_TRANS_DATE);
//query AccuRev for the latest transactions of each kind defined in transactionTypes using getTimeOfLatestTransaction
String[] validTransactionTypes;
if (server.getValidTransactionTypes() != null) {
validTransactionTypes = server.getValidTransactionTypes().split(AccurevServer.VTT_DELIM);
// if this is still empty, use default list
if (validTransactionTypes.length == 0) {
validTransactionTypes = AccurevServer.DEFAULT_VALID_TRANSACTION_TYPES.split(AccurevServer.VTT_DELIM);
}
}
else {
validTransactionTypes = AccurevServer.DEFAULT_VALID_TRANSACTION_TYPES.split(AccurevServer.VTT_DELIM);
}
listener.getLogger().println(//
"Checking transactions of type " + Arrays.asList(validTransactionTypes) + //
" in stream [" + stream + "]");
for (final String transactionType : validTransactionTypes) {
try {
final AccurevTransaction tempTransaction = getLatestTransaction(server, accurevEnv, workspace,
listener, accurevPath, launcher, stream, transactionType);
if (tempTransaction != null) {
listener.getLogger().println(
"Last transaction of type [" + transactionType + "] is " + tempTransaction);
if (latestCodeChangeTransaction.getDate().before(tempTransaction.getDate())) {
latestCodeChangeTransaction = tempTransaction;
}
} else {
listener.getLogger().println("No transactions of type [" + transactionType + "]");
}
}
catch (Exception e) {
final String msg = "getLatestTransaction failed when checking the stream " + stream + " for changes with transaction type " + transactionType;
listener.getLogger().println(msg);
e.printStackTrace(listener.getLogger());
logger.log(Level.WARNING, msg, e);
}
}
//log last transaction information if retrieved
if (latestCodeChangeTransaction.getDate().equals(NO_TRANS_DATE)) {
listener.getLogger().println("No last transaction found.");
}
else {
listener.getLogger().println("Last valid trans " + latestCodeChangeTransaction);
}
return buildDate == null || buildDate.before(latestCodeChangeTransaction.getDate());
}
/**
*
*
* @param server
* @param accurevEnv
* @param workspace
* @param listener
* @param accurevPath
* @param launcher
* @param stream
* @param transactionType Specify what type of transaction to search for
* @return the latest transaction of the specified type from the selected stream
* @throws Exception
*/
private AccurevTransaction getLatestTransaction(//
final AccurevServer server, //
final Map accurevEnv, //
final FilePath workspace, //
final TaskListener listener, //
final String accurevPath, //
final Launcher launcher, //
final String stream, //
final String transactionType) throws Exception {
// initialize code that extracts the latest transaction of a certain
// type using -k flag
final ArgumentListBuilder cmd = new ArgumentListBuilder();
cmd.add(accurevPath);
cmd.add("hist");
addServer(cmd, server);
cmd.add("-fx");
cmd.add("-p");
cmd.add(depot);
cmd.add("-s");
cmd.add(stream);
cmd.add("-t");
cmd.add("now.1");
cmd.add("-k");
cmd.add(transactionType);
// execute code that extracts the latest transaction
final List transaction = new ArrayList(1);
final Boolean transactionFound = AccurevLauncher.runCommand("History command", launcher, cmd, null,
getOptionalLock(), accurevEnv, workspace, listener, logger, XmlParserFactory.getFactory(),
new ParseHistory(), transaction);
if (transactionFound == null) {
final String msg = "History command failed when trying to get the latest transaction of type "
+ transactionType;
throw new Exception(msg);
}
if (transactionFound.booleanValue()) {
return transaction.get(0);
} else {
return null;
}
}
/**
* Helper method to retrieve include/exclude rules for a given stream.
*
* @return HashMap key: String path , val: String (enum) incl/excl rule type
*/
private HashMap getIncludeExcludeRules(//
final AccurevServer server, //
final Map accurevEnv, //
final FilePath workspace, //
final TaskListener listener, //
final String accurevPath, //
final Launcher launcher, //
final String stream) throws IOException, InterruptedException {
listener.getLogger().println("Retrieving include/exclude rules for stream: " + stream);
// Build the 'accurev lsrules' command
ArgumentListBuilder cmd = new ArgumentListBuilder();
cmd.add(accurevPath);
cmd.add("lsrules");
addServer(cmd, server);
cmd.add("-fx");
cmd.add("-s");
cmd.add(stream);
// Execute 'accurev lsrules' command and save off output
final XmlPullParserFactory parserFactory = XmlParserFactory.getFactory();
final HashMap locationToKindMap = AccurevLauncher.runCommand("lsrules command", launcher, cmd,
null, getOptionalLock(), accurevEnv, workspace, listener, logger, parserFactory, new ParseLsRules(),
null);
if (locationToKindMap != null) {
for (String location : locationToKindMap.keySet()) {
final String kind = locationToKindMap.get(location);
listener.getLogger().println("Found rule: " + kind + " for: " + location);
}
}
return locationToKindMap;
}
/**
* @return The currently logged in user "Principal" name, which may be
* "(not logged in)" if not logged in.
* Returns null on failure.
*/
private static String getLoggedInUsername(//
final AccurevServer server, //
final Map accurevEnv, //
final FilePath workspace, //
final TaskListener listener, //
final String accurevPath, //
final Launcher launcher) {
final String commandDescription = "info command";
final ArgumentListBuilder cmd = new ArgumentListBuilder();
cmd.add(accurevPath);
cmd.add("info");
addServer(cmd, server);
final String username = AccurevLauncher.runCommand(commandDescription, launcher, cmd, null, null, accurevEnv,
workspace, listener, logger, new ParseInfoToLoginName(), null);
return username;
}
/**
* Adds the server reference to the Arguments list.
*
* @param cmd The accurev command line.
* @param server The Accurev server details.
*/
private static void addServer(ArgumentListBuilder cmd, AccurevServer server) {
if (null != server && null != server.getHost() && !"".equals(server.getHost())) {
cmd.add("-H");
if (server.getPort() != 0) {
cmd.add(server.getHost() + ":" + server.getPort());
} else {
cmd.add(server.getHost());
}
}
}
/**
* Gets the lock to be used on "normal" accurev commands, or
* null
if command synchronization is switched off.
*
* @return See above.
*/
private Lock getOptionalLock() {
final AccurevServer server = DESCRIPTOR.getServer(serverName);
final boolean shouldLock = server.isSyncOperations();
if (shouldLock) {
return getMandatoryLock();
} else {
return null;
}
}
/**
* Gets the lock to be used on accurev commands where synchronization is
* mandatory.
*
* @return See above.
*/
private Lock getMandatoryLock() {
return DESCRIPTOR.ACCUREV_LOCK;
}
// -------------------------- INNER CLASSES --------------------------
public static final class AccurevSCMDescriptor extends SCMDescriptor implements ModelObject {
/**
* The accurev server has been known to crash if more than one copy of the accurev has been run concurrently on
* the local machine.
*
* Also, the accurev client has been known to complain that it's not logged in if another
* client on the same machine logs in again.
*/
transient static final Lock ACCUREV_LOCK = new ReentrantLock();
private List servers;
private boolean pollOnMaster;
/**
* Constructs a new AccurevSCMDescriptor.
*/
protected AccurevSCMDescriptor() {
super(AccurevSCM.class, null);
load();
}
/**
* {@inheritDoc}
*/
@Override
public String getDisplayName() {
return "Accurev";
}
/**
* {@inheritDoc}
*/
@Override
public boolean configure(StaplerRequest req, JSONObject formData) throws FormException {
servers = req.bindJSONToList(AccurevServer.class, formData.get("server"));
pollOnMaster = req.getParameter("descriptor.pollOnMaster") != null;
save();
return true;
}
/**
* {@inheritDoc}
*/
@Override
public SCM newInstance(StaplerRequest req, JSONObject formData) throws FormException {
return new AccurevSCM( //
req.getParameter("accurev.serverName"), //
req.getParameter("accurev.depot"), //
req.getParameter("accurev.stream"), //
req.getParameter("accurev.useWorkspace") != null, //
req.getParameter("accurev.workspace"), //
req.getParameter("accurev.workspaceSubPath"), //
req.getParameter("accurev.synctime") != null, //
req.getParameter("accurev.useUpdate") != null, //
req.getParameter("accurev.usePurgeIfLastFailed") != null, //
req.getParameter("accurev.useRevert") != null, //
req.getParameter("accurev.useSnapshot") != null, //
req.getParameter("accurev.snapshotNameFormat"), //
req.getParameter("accurev.ignoreStreamParent") != null);
}
/**
* Getter for property 'servers'.
*
* @return Value for property 'servers'.
*/
public List getServers() {
if (servers == null) {
servers = new ArrayList();
}
return servers;
}
/**
* Setter for property 'servers'.
*
* @param servers Value to set for property 'servers'.
*/
public void setServers(List servers) {
this.servers = servers;
}
/**
* Getter for property 'pollOnMaster'.
*
* @return Value for property 'pollOnMaster'.
*/
public boolean isPollOnMaster() {
return pollOnMaster;
}
/**
* Setter for property 'pollOnMaster'.
*
* @param servers Value to set for property 'pollOnMaster'.
*/
public void setPollOnMaster(boolean pollOnMaster) {
this.pollOnMaster = pollOnMaster;
}
public AccurevServer getServer(String name) {
if (name == null) {
return null;
}
for (AccurevServer server : servers) {
if (name.equals(server.getName())) {
return server;
}
}
return null;
}
/**
* Getter for property 'serverNames'.
*
* @return Value for property 'serverNames'.
*/
public String[] getServerNames() {
String[] result = new String[servers.size()];
for (int i = 0; i < result.length; i++) {
result[i] = servers.get(i).getName();
}
return result;
}
}
/**
* Bean that contains the definition of an accurev server, as contained by
* the global configuration.
*/
public static final class AccurevServer implements Serializable {
private String name;
private String host;
private int port;
private String username;
private String password;
private transient List winCmdLocations;
private transient List nixCmdLocations;
private String validTransactionTypes;
private boolean syncOperations;
private boolean minimiseLogins;
private boolean useNonexpiringLogin;
private boolean useRestrictedShowStreams;
/**
* The default search paths for Windows clients.
*/
private static final List DEFAULT_WIN_CMD_LOCATIONS = Arrays.asList(//
"C:\\Program Files\\AccuRev\\bin\\accurev.exe", //
"C:\\Program Files (x86)\\AccuRev\\bin\\accurev.exe");
/**
* The default search paths for *nix clients
*/
private static final List DEFAULT_NIX_CMD_LOCATIONS = Arrays.asList(//
"/usr/local/bin/accurev", //
"/usr/bin/accurev", //
"/bin/accurev", //
"/local/bin/accurev");
private static final String VTT_DELIM = ",";
// keep all transaction types in a set for validation
private static final String VTT_LIST = "add,chstream,co,defcomp,defunct,keep,mkstream,move,promote,purge,dispatch";
private static final Set VALID_TRANSACTION_TYPES = new HashSet(Arrays.asList(VTT_LIST
.split(VTT_DELIM)));
private static final String DEFAULT_VALID_TRANSACTION_TYPES = "add,chstream,co,defcomp,defunct,keep,mkstream,move,promote,purge";
/**
* Constructs a new AccurevServer.
*/
public AccurevServer() {
this.winCmdLocations = new ArrayList(DEFAULT_WIN_CMD_LOCATIONS);
this.nixCmdLocations = new ArrayList(DEFAULT_NIX_CMD_LOCATIONS);
}
@DataBoundConstructor
public AccurevServer(//
String name, //
String host, //
int port, //
String username, //
String password, //
String validTransactionTypes, //
boolean syncOperations, //
boolean minimiseLogins, //
boolean useNonexpiringLogin, //
boolean useRestrictedShowStreams) {
this();
this.name = name;
this.host = host;
this.port = port;
this.username = username;
this.password = Password.obfuscate(password);
this.validTransactionTypes = validTransactionTypes;
this.syncOperations = syncOperations;
this.minimiseLogins = minimiseLogins;
this.useNonexpiringLogin = useNonexpiringLogin;
this.useRestrictedShowStreams = useRestrictedShowStreams;
}
/**
* When f:repeatable tags are nestable, we can change the advances page of the server config to
* allow specifying these locations... until then this hack!
* @return This.
* @throws ObjectStreamException
*/
private Object readResolve() throws ObjectStreamException {
if (winCmdLocations == null) {
winCmdLocations = new ArrayList(DEFAULT_WIN_CMD_LOCATIONS);
}
if (nixCmdLocations == null) {
nixCmdLocations = new ArrayList(DEFAULT_NIX_CMD_LOCATIONS);
}
return this;
}
/**
* Getter for property 'name'.
*
* @return Value for property 'name'.
*/
public String getName() {
return name;
}
/**
* Getter for property 'host'.
*
* @return Value for property 'host'.
*/
public String getHost() {
return host;
}
/**
* Getter for property 'port'.
*
* @return Value for property 'port'.
*/
public int getPort() {
return port;
}
/**
* Getter for property 'username'.
*
* @return Value for property 'username'.
*/
public String getUsername() {
return username;
}
/**
* Getter for property 'password'.
*
* @return Value for property 'password'.
*/
public String getPassword() {
return Password.deobfuscate(password);
}
/**
* Getter for property 'nixCmdLocations'.
*
* @return Value for property 'nixCmdLocations'.
*/
public String[] getNixCmdLocations() {
return nixCmdLocations.toArray(new String[nixCmdLocations.size()]);
}
/**
* Getter for property 'winCmdLocations'.
*
* @return Value for property 'winCmdLocations'.
*/
public String[] getWinCmdLocations() {
return winCmdLocations.toArray(new String[winCmdLocations.size()]);
}
/**
*
* @return returns the currently set transaction types that are seen as
* valid for triggering builds and whos authors get notified
* when a build fails
*/
public String getValidTransactionTypes() {
return validTransactionTypes;
}
/**
*
* @param validTransactionTypes
* the currently set transaction types that are seen as valid
* for triggering builds and whos authors get notified when a
* build fails
*/
public void setValidTransactionTypes(String validTransactionTypes) {
this.validTransactionTypes = validTransactionTypes;
}
public boolean isSyncOperations() {
return syncOperations;
}
public void setSyncOperations(boolean syncOperations) {
this.syncOperations = syncOperations;
}
public boolean isMinimiseLogins() {
return minimiseLogins;
}
public void setMinimiseLogins(boolean minimiseLogins) {
this.minimiseLogins = minimiseLogins;
}
public boolean isUseNonexpiringLogin() {
return useNonexpiringLogin;
}
public void setUseNonexpiringLogin(boolean useNonexpiringLogin) {
this.useNonexpiringLogin = useNonexpiringLogin;
}
public boolean isUseRestrictedShowStreams() {
return useRestrictedShowStreams;
}
public void setUseRestrictedShowStreams(boolean useRestrictedShowStreams) {
this.useRestrictedShowStreams = useRestrictedShowStreams;
}
public FormValidation doValidTransactionTypesCheck(@QueryParameter String value)//
throws IOException, ServletException {
final String[] formValidTypes = value.split(VTT_DELIM);
for (final String formValidType : formValidTypes) {
if (!VALID_TRANSACTION_TYPES.contains(formValidType)) {
return FormValidation.error("Invalid transaction type [" + formValidType + "]. Valid types are: "
+ VTT_LIST);
}
}
return FormValidation.ok();
}
}
/**
* Class responsible for parsing change-logs recorded by the builds.
* If this is renamed or moved it'll break data-compatibility with old builds.
*/
private static final class AccurevChangeLogParser extends ParseChangeLog {
}
}