using System;
using System.Collections.Specialized;
using System.IO;
using System.Text.RegularExpressions;
using Exortech.NetReflector;
using ThoughtWorks.CruiseControl.Core.Sourcecontrol;
using ThoughtWorks.CruiseControl.Core.Util;
namespace ThoughtWorks.CruiseControl.Core.label
{
///
/// Labeller for (AccuRev Inc.'s eponymous source code control product.
///
///
/// This code is based on code\sourcecontrol\AccuRev.cs and re-uses code\sourcecontrol\AccuRevHistoryParser.cs.
///
[ReflectorType("accurevLabeller")]
public class AccuRevLabeller : ILabeller
{
#region Properties
///
/// Name of the AccuRev CLI command.
///
///
/// Optional, defaults to "accurev.exe".
///
[ReflectorProperty("executable", Required = false)]
public string Executable = "accurev.exe";
///
/// The location of the AccuRev home directory, either absolute or relative to the project artifact
/// directory. If not specified, AccuRev will follow its rules for determining the location. The
/// home directory itself is always called ".accurev", and AccuRev will create it if there isn't
/// already one present in the home directory.
///
///
/// Optional, default is to let AccuRev decide where the home directory is.
///
[ReflectorProperty("homeDir", Required = false)]
public string AccuRevHomeDir = null;
///
/// If true, log in to AccuRev using the specified principal and password.
///
///
/// Optional, default is not to log in. If set to true, "principal" and "password" must also be set.
///
[ReflectorProperty("login", Required = false)]
public bool LogIn = false;
///
/// The password for the AccuRev "principal" (userid).
///
///
/// Optional, default is no password. Only necessary if "login" is set to "true".
///
[ReflectorProperty("password", Required = false)]
public string AccuRevPassword = null;
///
/// The AccuRev "principal" (userid) to run under. If not specified, AccuRev will follow its rules
/// for determining the principal.
///
///
/// Optional, default is to let AccuRev decide who the principal is. Must be specified if "login" is
/// set to "true".
///
[ReflectorProperty("principal", Required = false)]
public string AccuRevPrincipal = null;
///
/// Pathname of the root of the AccuRev workspace to update and/or check, either absolute or relative
/// to the project working directory.
///
///
/// Optional, defaults to the project working directory.
///
[ReflectorProperty("workspace", Required = false)]
public string Workspace = string.Empty;
#endregion
private readonly ProcessExecutor _executor;
private readonly IHistoryParser _historyParser;
internal static readonly string HISTORY_COMMAND_FORMAT_LAST_TRAN = @"hist -a -k promote -s ""{0}"" -t HIGHEST";
public AccuRevLabeller() : this(new AccuRevHistoryParser(), new ProcessExecutor()) {}
public AccuRevLabeller(IHistoryParser historyParser, ProcessExecutor executor)
{
_historyParser = historyParser;
_executor = executor;
if (LogIn & ((AccuRevPrincipal == null) || (AccuRevPassword == null)))
{
Log.Error("login=true requires principal= and password= to be specified.");
}
}
public string Generate(IIntegrationResult integrationResult)
{
PossiblyLogIn(integrationResult);
string args = string.Format(HISTORY_COMMAND_FORMAT_LAST_TRAN, GetBasisStreamName(integrationResult));
ProcessInfo histCommand = PrepCommand(args, integrationResult);
ProcessResult pResult = Execute(histCommand);
Modification[] mods = _historyParser.Parse(new StringReader(pResult.StandardOutput), DateTime.MinValue, DateTime.MaxValue);
if (mods.Length == 0)
return makeDefaultVersion();
else
return mods[0].ChangeNumber.ToString();
}
public void Run(IIntegrationResult result)
{
result.Label = Generate(result);
}
private static string makeDefaultVersion()
{
return "0";
}
///
/// Determine the AccuRev basis stream name for the specified workspace directory.
///
/// the IIntegrationResult object contaiing the directory name
/// of the workspace
/// the name of the basis stream
private string GetBasisStreamName(IIntegrationResult result)
{
string line;
Regex findBasisRegex = new Regex(@"^\s*Basis:\s+(.+)$");
PossiblyLogIn(result);
ProcessResult cmdResults = RunCommand("info", result);
StringReader infoStdOut = new StringReader(cmdResults.StandardOutput);
while ((line = infoStdOut.ReadLine()) != null)
{
Match parsed = findBasisRegex.Match(line);
if (parsed.Success)
return parsed.Groups[1].ToString().Trim();
// Format is: "Basis: __stream_name__"
}
Log.Error(string.Format("No \"Basis:\" line found in output from AccuRev \"accurev info\": {0}", cmdResults.StandardOutput));
return "";
}
///
/// Log in to AccuRev if we're supposed to do so.
///
/// IntegrationResult for which the command will be run
private void PossiblyLogIn(IIntegrationResult result)
{
if (!LogIn)
return; // Nothing to do.
if ((AccuRevPrincipal == null) || (AccuRevPassword == null))
{
Log.Error("login=true requires principal= and password= to be specified.");
return;
}
RunCommand(string.Format("login {0} \"{1}\"", AccuRevPrincipal, AccuRevPassword), result);
}
///
/// Prepare an AccuRev command for execution.
///
/// arguments for the "accurev" command
/// IntegrationResult for which the command will be run
/// a ProcessInfo object primed to execute the specified command
private ProcessInfo PrepCommand(string args, IIntegrationResult result)
{
Log.Debug(string.Format("Preparing to run AccuRev command: {0} {1}", Executable, args));
ProcessInfo command = new ProcessInfo(Executable, args, result.BaseFromWorkingDirectory(Workspace));
SetEnvironmentVariables(command.EnvironmentVariables, result);
return command;
}
///
/// Execute an AccuRev command and check the results.
///
/// arguments for the "accurev" command
/// IntegrationResult for which the command is being run
/// a ProcessResult object with the results from the command
private ProcessResult RunCommand(string args, IIntegrationResult result)
{
ProcessInfo command = PrepCommand(args, result);
ProcessResult cmdResults = Execute(command);
if (cmdResults.Failed)
{
Log.Error(string.Format("AccuRev command \"{0} {1}\" failed with RC={2}",
Executable, args, cmdResults.ExitCode));
if ((cmdResults.StandardError != null) && (cmdResults.StandardError != ""))
Log.Error(string.Format("\tError output: {0}", cmdResults.StandardError));
}
return cmdResults;
}
///
/// Make sure this IIntegrationResult object has our environment variables set in it.
///
/// The collection of environment variables to be updated.
/// IntegrationResult for the command whose variables we are updating.
private void SetEnvironmentVariables(StringDictionary environmentVariables, IIntegrationResult result)
{
if (!StringUtil.IsBlank(AccuRevHomeDir))
environmentVariables["ACCUREV_HOME"] = result.BaseFromArtifactsDirectory(AccuRevHomeDir);
if (!StringUtil.IsBlank(AccuRevPrincipal))
environmentVariables["ACCUREV_PRINCIPAL"] = AccuRevPrincipal;
}
private ProcessResult Execute(ProcessInfo processInfo)
{
processInfo.TimeOut = 30000;
ProcessResult result = _executor.Execute(processInfo);
if (result.TimedOut)
{
throw new CruiseControlException("Source control operation has timed out.");
}
else if (result.Failed)
{
throw new CruiseControlException(string.Format("Source control operation failed: {0}. Process command: {1} {2}",
result.StandardError, processInfo.FileName, processInfo.Arguments));
}
else if (result.HasErrorOutput)
{
Log.Warning(string.Format("Source control wrote output to stderr: {0}", result.StandardError));
}
return result;
}
}
}