// Copyright Epic Games, Inc. All Rights Reserved.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
namespace EpicGames.Perforce
{
///
/// Stores settings for communicating with a Perforce server.
///
public sealed class PerforceConnection : IPerforceConnection
{
///
/// Create a new Perforce connection
///
///
///
public static Task CreateAsync(ILogger logger)
{
return CreateAsync(PerforceSettings.Default, logger);
}
///
/// Create a new Perforce connection
///
/// The server address and port
/// The user name
/// Interface for logging
///
public static Task CreateAsync(string? serverAndPort, string? userName, ILogger logger)
{
return CreateAsync(CombineSettings(serverAndPort, userName), logger);
}
///
/// Create a new Perforce connection
///
/// The server address and port
/// The user name
/// The client name
/// Interface for logging
///
public static Task CreateAsync(string? serverAndPort, string? userName, string? clientName, ILogger logger)
{
return CreateAsync(CombineSettings(serverAndPort, userName, clientName), logger);
}
///
/// Create a new Perforce connection
///
/// The server address and port
/// The user name
/// The client name
///
///
/// Interface for logging
///
public static Task CreateAsync(string? serverAndPort, string? userName, string? clientName, string? appName, string? appVersion, ILogger logger)
{
return CreateAsync(CombineSettings(serverAndPort, userName, clientName, appName, appVersion), logger);
}
///
/// Create a new Perforce connection
///
/// Settings for the connection
///
///
public static async Task CreateAsync(IPerforceSettings settings, ILogger logger)
{
if (settings.PreferNativeClient && NativePerforceConnection.IsSupported())
{
return await NativePerforceConnection.CreateAsync(settings, logger);
}
else
{
return new PerforceConnection(settings, logger);
}
}
static PerforceSettings CombineSettings(string? serverAndPort, string? userName, string? clientName = null, string? appName = null, string? appVersion = null)
{
PerforceSettings settings = new PerforceSettings(PerforceSettings.Default);
if (serverAndPort != null)
{
settings.ServerAndPort = serverAndPort;
}
if (userName != null)
{
settings.UserName = userName;
}
if (clientName != null)
{
settings.ClientName = clientName;
}
if (appName != null)
{
settings.AppName = appName;
}
if (appVersion != null)
{
settings.AppVersion = appVersion;
}
return settings;
}
#region Legacy implementation
///
public IPerforceSettings Settings
{
get
{
PerforceSettings settings = new PerforceSettings(PerforceEnvironment.Default);
if (ServerAndPort != null)
{
settings.ServerAndPort = ServerAndPort;
}
if (UserName != null)
{
settings.UserName = UserName;
}
if (ClientName != null)
{
settings.ClientName = ClientName;
}
if (AppName != null)
{
settings.AppName = AppName;
}
if (AppVersion != null)
{
settings.AppVersion = AppVersion;
}
return settings;
}
}
///
/// The current server and port
///
public string? ServerAndPort
{
get;
set;
}
///
/// The current user name
///
public string? UserName
{
get;
set;
}
///
/// The current host name
///
public string? HostName
{
get;
set;
}
///
/// The current client name
///
public string? ClientName
{
get;
set;
}
///
/// Name of this application, reported to server through -zprog arguments
///
public string? AppName
{
get;
set;
}
///
/// Version of this application, reported to server through -zversion arguments
///
public string? AppVersion
{
get;
set;
}
///
/// Additional options to append to the command line
///
public List GlobalOptions { get; } = new List();
///
/// The logging interface
///
public ILogger Logger
{
get;
set;
}
///
/// Constructor
///
/// The server address and port
/// The user name
/// Interface for logging
public PerforceConnection(string? serverAndPort, string? userName, ILogger logger)
: this(serverAndPort, userName, null, logger)
{
}
///
/// Constructor
///
/// The server address and port
/// The user name
/// The client name
/// Interface for logging
public PerforceConnection(string? serverAndPort, string? userName, string? clientName, ILogger logger)
: this(serverAndPort, userName, clientName, null, null, logger)
{
AssemblyName entryAssemblyName = Assembly.GetEntryAssembly()!.GetName();
if (entryAssemblyName.Name != null)
{
AppName = entryAssemblyName.Name;
AppVersion = entryAssemblyName.Version?.ToString() ?? String.Empty;
}
}
///
/// Constructor
///
/// The server address and port
/// The user name
/// The client name
///
///
/// Interface for logging
public PerforceConnection(string? serverAndPort, string? userName, string? clientName, string? appName, string? appVersion, ILogger logger)
{
ServerAndPort = serverAndPort;
UserName = userName;
ClientName = clientName;
AppName = appName;
AppVersion = appVersion;
Logger = logger;
}
///
/// Constructor
///
///
///
public PerforceConnection(IPerforceSettings settings, ILogger logger)
: this(settings.ServerAndPort, settings.UserName, settings.ClientName, settings.AppName, settings.AppVersion, logger)
{
HostName = settings.HostName;
}
///
/// Constructor
///
/// Connection to copy settings from
public PerforceConnection(PerforceConnection other)
: this(other.ServerAndPort, other.UserName, other.ClientName, other.AppName, other.AppVersion, other.Logger)
{
GlobalOptions.AddRange(other.GlobalOptions);
}
///
public void Dispose()
{
}
List GetGlobalArguments()
{
List arguments = new List();
if (ServerAndPort != null)
{
arguments.Add($"-p{ServerAndPort}");
}
if (UserName != null)
{
arguments.Add($"-u{UserName}");
}
if (HostName != null)
{
arguments.Add($"-H{HostName}");
}
if (ClientName != null)
{
arguments.Add($"-c{ClientName}");
}
if (AppName != null)
{
arguments.Add($"-zprog={AppName}");
}
if (AppVersion != null)
{
arguments.Add($"-zversion={AppVersion}");
}
arguments.AddRange(GlobalOptions);
return arguments;
}
///
public IPerforceOutput Command(string command, IReadOnlyList arguments, IReadOnlyList? fileArguments, byte[]? inputData, string? promptResponse, bool interceptIo)
{
if (promptResponse != null)
{
inputData = Encoding.UTF8.GetBytes(promptResponse);
}
if (interceptIo)
{
throw new NotSupportedException($"{nameof(interceptIo)} option is not supported through legacy Perforce client");
}
return new PerforceChildProcess(command, arguments, fileArguments, inputData, GetGlobalArguments(), Logger);
}
///
/// Sets an environment variable
///
/// Name of the variable to set
/// Value for the variable
/// Token used to cancel the operation
/// Response from the server
public async Task SetAsync(string name, string value, CancellationToken cancellationToken = default)
{
Tuple result = await TrySetAsync(name, value, cancellationToken);
if (!result.Item1)
{
throw new PerforceException(result.Item2);
}
}
///
/// Sets an environment variable
///
/// Name of the variable to set
/// Value for the variable
/// Token used to cancel the operation
/// Response from the server
public async Task> TrySetAsync(string name, string value, CancellationToken cancellationToken = default)
{
List arguments = new List();
arguments.Add($"{name}={value}");
using (PerforceChildProcess childProcess = new PerforceChildProcess("set", arguments, null, null, GetGlobalArguments(), Logger))
{
return await childProcess.TryReadToEndAsync(cancellationToken);
}
}
///
public PerforceRecord CreateRecord(List> fields) => PerforceRecord.FromFields(fields, true);
#endregion
}
///
/// Extension methods for
///
public static class PerforceConnectionExtensions
{
///
/// Create a new connection with a different client
///
///
///
///
public static Task WithClientAsync(this IPerforceConnection perforce, string? clientName)
{
PerforceSettings settings = new PerforceSettings(perforce.Settings) { ClientName = clientName };
return PerforceConnection.CreateAsync(settings, perforce.Logger);
}
///
/// Create a new connection with a different client
///
///
///
public static Task WithoutClientAsync(this IPerforceConnection perforce)
{
return WithClientAsync(perforce, null);
}
#region Command wrappers
///
/// Execute a command and parse the response
///
/// The Perforce connection
/// Command to execute
/// Arguments for the command
/// Input data to pass to Perforce
/// The type of records to return for "stat" responses
/// Token used to cancel the operation
/// List of objects returned by the server
public static Task> CommandAsync(this IPerforceConnection perforce, string command, IReadOnlyList arguments, byte[]? inputData, Type? statRecordType, CancellationToken cancellationToken = default)
{
return CommandAsync(perforce, command, arguments, null, inputData, statRecordType, cancellationToken);
}
///
/// Execute a command and parse the response
///
/// The Perforce connection
/// Command to execute
/// Arguments for the command
/// File arguments for the command
/// Input data to pass to Perforce
/// The type of records to return for "stat" responses
/// Token used to cancel the operation
/// List of objects returned by the server
public static async Task> CommandAsync(this IPerforceConnection perforce, string command, IReadOnlyList arguments, IReadOnlyList? fileArguments, byte[]? inputData, Type? statRecordType, CancellationToken cancellationToken = default)
{
await using (IPerforceOutput response = perforce.Command(command, arguments, fileArguments, inputData, null, false))
{
return await response.ReadResponsesAsync(statRecordType, cancellationToken);
}
}
///
/// Execute a command and parse the response
///
/// The Perforce connection
/// Command to execute
/// Arguments for the command
/// File arguments for the command
/// Input data to pass to Perforce
/// The type of records to return for "stat" responses
/// Whether to intercept Io operations and return them in the response output
/// Token used to cancel the operation
/// List of objects returned by the server
public static async IAsyncEnumerable StreamCommandAsync(this IPerforceConnection perforce, string command, IReadOnlyList arguments, IReadOnlyList? fileArguments, byte[]? inputData, Type? statRecordType, bool interceptIo, [EnumeratorCancellation] CancellationToken cancellationToken)
{
#pragma warning disable CA1849 // Call async methods when in an async method
await using (IPerforceOutput output = perforce.Command(command, arguments, fileArguments, inputData, null, interceptIo))
{
await foreach (PerforceResponse response in output.ReadStreamingResponsesAsync(statRecordType, cancellationToken))
{
yield return response;
}
}
#pragma warning restore CA1849 // Call async methods when in an async method
}
///
/// Execute a command and parse the response
///
/// The Perforce connection
/// Command to execute
/// Arguments for the command
/// File arguments for the command
/// Input data to pass to Perforce
/// Token used to cancel the operation
/// List of objects returned by the server
public static async IAsyncEnumerable> StreamCommandAsync(this IPerforceConnection perforce, string command, IReadOnlyList arguments, IReadOnlyList? fileArguments = null, byte[]? inputData = null, [EnumeratorCancellation] CancellationToken cancellationToken = default) where T : class
{
#pragma warning disable CA1849 // Call async methods when in an async method
await using (IPerforceOutput output = perforce.Command(command, arguments, fileArguments, inputData, null, false))
{
Type statRecordType = typeof(T);
await foreach (PerforceResponse response in output.ReadStreamingResponsesAsync(statRecordType, cancellationToken))
{
yield return new PerforceResponse(response);
}
}
#pragma warning restore CA1849 // Call async methods when in an async method
}
///
/// Execute a command and parse the response
///
/// The Perforce connection
/// Command to execute
/// Arguments for the command
/// Input data to pass to Perforce
/// Delegate used to handle each record
/// Token used to cancel the operation
/// List of objects returned by the server
public static async Task RecordCommandAsync(this IPerforceConnection perforce, string command, IReadOnlyList arguments, byte[]? inputData, Action handleRecord, CancellationToken cancellationToken = default)
{
#pragma warning disable CA1849 // Call async methods when in an async method
await using (IPerforceOutput response = perforce.Command(command, arguments, null, inputData, null, false))
{
await response.ReadRecordsAsync(handleRecord, cancellationToken);
}
#pragma warning restore CA1849 // Call async methods when in an async method
}
///
/// Execute a command and parse the response
///
/// Connection to the Perforce server
/// The command to execute
/// Arguments for the command
/// Arguments which can be passed into a -x argument
/// Input data to pass to Perforce
/// Token used to cancel the operation
/// List of objects returned by the server
public static async Task> CommandAsync(this IPerforceConnection connection, string command, IReadOnlyList arguments, IReadOnlyList? fileArguments, byte[]? inputData, CancellationToken cancellationToken = default) where T : class
{
List responses = await connection.CommandAsync(command, arguments, fileArguments, inputData, typeof(T), cancellationToken);
PerforceResponseList typedResponses = new PerforceResponseList();
foreach (PerforceResponse response in responses)
{
typedResponses.Add(new PerforceResponse(response));
}
return typedResponses;
}
///
/// Execute a command and parse the response
///
/// Connection to the Perforce server
/// The command to execute
/// Arguments for the command
/// Input data to pass to Perforce
/// Token used to cancel the operation
/// List of objects returned by the server
public static Task> CommandAsync(this IPerforceConnection connection, string command, List arguments, byte[]? inputData, CancellationToken cancellationToken = default) where T : class
{
return CommandAsync(connection, command, arguments, null, inputData, cancellationToken);
}
///
/// Execute a command and parse the response
///
/// Connection to the Perforce server
/// The command to execute
/// Arguments for the command
/// Arguments to pass to the command in batches
/// Token used to cancel the operation
/// List of objects returned by the server
public static async Task> BatchedCommandAsync(IPerforceConnection connection, string command, IReadOnlyList commonArguments, IReadOnlyList batchedArguments, CancellationToken cancellationToken = default) where T : class
{
PerforceResponseList responses = new PerforceResponseList();
for (int fileSpecIdx = 0; fileSpecIdx < batchedArguments.Count;)
{
List arguments = new List();
arguments.AddRange(commonArguments);
const int PerArgumentExtra = 5;
int length = (command.Length + PerArgumentExtra) + arguments.Sum(x => x.Length + PerArgumentExtra);
for (; fileSpecIdx < batchedArguments.Count && length < 4096; fileSpecIdx++)
{
arguments.Add(batchedArguments[fileSpecIdx]);
length += batchedArguments[fileSpecIdx].Length + PerArgumentExtra;
}
responses.AddRange(await CommandAsync(connection, command, arguments, null, cancellationToken));
}
return responses;
}
///
/// Attempts to execute the given command, returning the results from the server or the first PerforceResponse object.
///
/// Connection to the Perforce server
/// The command to execute
/// Arguments for the command.
/// Input data for the command.
/// Type of element to return in the response
/// Token used to cancel the operation
/// Response from the server; either an object of type T or error.
static async Task SingleResponseCommandAsync(IPerforceConnection connection, string command, IReadOnlyList arguments, byte[]? inputData, Type? statRecordType, CancellationToken cancellationToken = default)
{
List responses = await connection.CommandAsync(command, arguments, inputData, statRecordType, cancellationToken);
if (responses.Count != 1)
{
for (int idx = 0; idx < responses.Count; idx++)
{
connection.Logger.LogInformation("Unexpected response {Idx}: {Text}", idx, responses[idx].ToString());
}
throw new PerforceException("Expected one result from 'p4 {0}', got {1}", command, responses.Count);
}
return responses[0];
}
///
/// Attempts to execute the given command, returning the results from the server or the first PerforceResponse object.
///
/// Type of record to parse
/// Connection to the Perforce server
/// The command to execute
/// Arguments for the command.
/// Input data for the command.
/// Token used to cancel the operation
/// Response from the server; either an object of type T or error.
public static async Task> SingleResponseCommandAsync(IPerforceConnection connection, string command, List arguments, byte[]? inputData, CancellationToken cancellationToken = default) where T : class
{
return new PerforceResponse(await SingleResponseCommandAsync(connection, command, arguments, inputData, typeof(T), cancellationToken));
}
#endregion
#region p4 add
///
/// Adds files to a pending changelist.
///
/// Connection to the Perforce server
/// Changelist to add files to
/// Files to be added
/// Token used to cancel the operation
/// Response from the server
public static async Task> AddAsync(this IPerforceConnection connection, int changeNumber, FileSpecList fileSpecList, CancellationToken cancellationToken = default)
{
return (await TryAddAsync(connection, changeNumber, null, AddOptions.None, fileSpecList, cancellationToken)).Data;
}
///
/// Adds files to a pending changelist.
///
/// Connection to the Perforce server
/// Changelist to add files to
/// Type for new files
/// Options for the command
/// Files to be added
/// Token used to cancel the operation
/// Response from the server
public static async Task> AddAsync(this IPerforceConnection connection, int changeNumber, string? fileType, AddOptions options, FileSpecList fileSpecList, CancellationToken cancellationToken = default)
{
return (await TryAddAsync(connection, changeNumber, fileType, options, fileSpecList, cancellationToken)).Data;
}
///
/// Adds files to a pending changelist.
///
/// Connection to the Perforce server
/// Changelist to add files to
/// Files to be added
/// Token used to cancel the operation
/// Response from the server
public static Task> TryAddAsync(this IPerforceConnection connection, int changeNumber, FileSpecList fileSpecList, CancellationToken cancellationToken = default)
{
return TryAddAsync(connection, changeNumber, null, AddOptions.None, fileSpecList, cancellationToken);
}
///
/// Adds files to a pending changelist.
///
/// Connection to the Perforce server
/// Changelist to add files to
/// Type for new files
/// Options for the command
/// Files to be added
/// Token used to cancel the operation
/// Response from the server
public static Task> TryAddAsync(this IPerforceConnection connection, int changeNumber, string? fileType, AddOptions options, FileSpecList fileNames, CancellationToken cancellationToken = default)
{
List arguments = new List();
if (changeNumber != -1)
{
arguments.Add($"-c{changeNumber}");
}
if ((options & AddOptions.DowngradeToAdd) != 0)
{
arguments.Add("-d");
}
if ((options & AddOptions.IncludeWildcards) != 0)
{
arguments.Add("-f");
}
if ((options & AddOptions.NoIgnore) != 0)
{
arguments.Add("-I");
}
if ((options & AddOptions.PreviewOnly) != 0)
{
arguments.Add("-n");
}
if (fileType != null)
{
arguments.Add($"-t{fileType}");
}
return BatchedCommandAsync(connection, "add", arguments, fileNames.List, cancellationToken);
}
#endregion
#region p4 change
///
/// Creates a changelist with the p4 change command.
///
/// Connection to the Perforce server
/// Information for the change to create. The number field should be left set to -1.
/// Token used to cancel the operation
/// The changelist number, or an error.
public static async Task CreateChangeAsync(this IPerforceConnection connection, ChangeRecord record, CancellationToken cancellationToken = default)
{
return (await TryCreateChangeAsync(connection, record, cancellationToken)).Data;
}
///
/// Creates a changelist with the p4 change command.
///
/// Connection to the Perforce server
/// Information for the change to create. The number field should be left set to -1.
/// Token used to cancel the operation
/// The changelist number, or an error.
public static async Task> TryCreateChangeAsync(this IPerforceConnection connection, ChangeRecord record, CancellationToken cancellationToken = default)
{
if (record.Number != -1)
{
throw new PerforceException("'Number' field should be set to -1 to create a new changelist.");
}
PerforceResponse response = await SingleResponseCommandAsync(connection, "change", new List { "-i" }, SerializeRecord(connection, record), null, cancellationToken);
PerforceError? error = response.Error;
if (error != null)
{
return new PerforceResponse(error);
}
PerforceInfo? info = response.Info;
if (info == null)
{
throw new PerforceException("Unexpected info response from change command: {0}", response);
}
Match match = Regex.Match(info.Data, @"^Change (\d+) created");
if (!match.Success)
{
throw new PerforceException("Unexpected info response from change command: {0}", response);
}
record.Number = Int32.Parse(match.Groups[1].Value);
return new PerforceResponse(record);
}
///
/// Updates an existing changelist.
///
/// Connection to the Perforce server
/// Options for this command
/// Information for the change to create. The number field should be left set to zero.
/// Token used to cancel the operation
/// The changelist number, or an error.
public static async Task UpdateChangeAsync(this IPerforceConnection connection, UpdateChangeOptions options, ChangeRecord record, CancellationToken cancellationToken = default)
{
(await TryUpdateChangeAsync(connection, options, record, cancellationToken)).EnsureSuccess();
}
///
/// Updates an existing changelist.
///
/// Connection to the Perforce server
/// Options for this command
/// Information for the change to create. The number field should be left set to zero.
/// Token used to cancel the operation
/// The changelist number, or an error.
public static Task TryUpdateChangeAsync(this IPerforceConnection connection, UpdateChangeOptions options, ChangeRecord record, CancellationToken cancellationToken = default)
{
if (record.Number == -1)
{
throw new PerforceException("'Number' field must be set to update a changelist.");
}
List arguments = new List();
arguments.Add("-i");
if ((options & UpdateChangeOptions.Force) != 0)
{
arguments.Add("-f");
}
if ((options & UpdateChangeOptions.Submitted) != 0)
{
arguments.Add("-u");
}
return SingleResponseCommandAsync(connection, "change", arguments, connection.SerializeRecord(record), null, cancellationToken);
}
///
/// Deletes a changelist (p4 change -d)
///
/// Connection to the Perforce server
/// Options for the command
/// Changelist number to delete
/// Token used to cancel the operation
/// Response from the server
public static async Task DeleteChangeAsync(this IPerforceConnection connection, DeleteChangeOptions options, int changeNumber, CancellationToken cancellationToken = default)
{
(await TryDeleteChangeAsync(connection, options, changeNumber, cancellationToken)).EnsureSuccess();
}
///
/// Deletes a changelist (p4 change -d)
///
/// Connection to the Perforce server
/// Options for the command
/// Changelist number to delete
/// Token used to cancel the operation
/// Response from the server
public static Task TryDeleteChangeAsync(this IPerforceConnection connection, DeleteChangeOptions options, int changeNumber, CancellationToken cancellationToken = default)
{
List arguments = new List { "-d" };
if ((options & DeleteChangeOptions.Submitted) != 0)
{
arguments.Add("-f");
}
if ((options & DeleteChangeOptions.BeforeRenumber) != 0)
{
arguments.Add("-O");
}
arguments.Add($"{changeNumber}");
return SingleResponseCommandAsync(connection, "change", arguments, null, null, cancellationToken);
}
///
/// Gets a changelist
///
/// Connection to the Perforce server
/// Options for the command
/// Changelist number to retrieve. -1 is the default changelist for this workspace.
/// Token used to cancel the operation
/// Response from the server
public static async Task GetChangeAsync(this IPerforceConnection connection, GetChangeOptions options, int changeNumber, CancellationToken cancellationToken = default)
{
return (await TryGetChange(connection, options, changeNumber, cancellationToken)).Data;
}
///
/// Gets a changelist
///
/// Connection to the Perforce server
/// Options for the command
/// Changelist number to retrieve. -1 is the default changelist for this workspace.
/// Token used to cancel the operation
/// Response from the server
public static Task> TryGetChange(this IPerforceConnection connection, GetChangeOptions options, int changeNumber, CancellationToken cancellationToken = default)
{
List arguments = new List { "-o" };
if ((options & GetChangeOptions.BeforeRenumber) != 0)
{
arguments.Add("-O");
}
if (changeNumber != -1)
{
arguments.Add($"{changeNumber}");
}
return SingleResponseCommandAsync(connection, "change", arguments, null, cancellationToken);
}
///
/// Serializes a change record to a byte array
///
/// Connection to the Perforce server
/// The record to serialize
/// Serialized record
static byte[] SerializeRecord(this IPerforceConnection connection, ChangeRecord input)
{
List> nameToValue = new List>();
if (input.Number == -1)
{
nameToValue.Add(new KeyValuePair("Change", "new"));
}
else
{
nameToValue.Add(new KeyValuePair("Change", input.Number.ToString()));
}
if (input.Type != ChangeType.Unspecified)
{
nameToValue.Add(new KeyValuePair("Type", input.Type.ToString()));
}
if (input.User != null)
{
nameToValue.Add(new KeyValuePair("User", input.User));
}
if (input.Client != null)
{
nameToValue.Add(new KeyValuePair("Client", input.Client));
}
if (input.Description != null)
{
nameToValue.Add(new KeyValuePair("Description", input.Description));
}
if (input.Status != ChangeStatus.All)
{
nameToValue.Add(new KeyValuePair("Status", PerforceReflection.GetEnumText(typeof(ChangeStatus), input.Status)));
}
if (input.Files.Count > 0)
{
nameToValue.Add(new KeyValuePair("Files", input.Files));
}
return connection.CreateRecord(nameToValue).Serialize();
}
#endregion
#region p4 changes
///
/// Enumerates changes on the server
///
/// Connection to the Perforce server
/// Options for the command
/// List only the highest numbered changes
/// Limit the list to the changelists with the given status (pending, submitted or shelved)
/// Paths to query changes for
/// Token used to cancel the operation
/// List of responses from the server.
public static async Task> GetChangesAsync(this IPerforceConnection connection, ChangesOptions options, int maxChanges, ChangeStatus status, FileSpecList fileSpecs, CancellationToken cancellationToken = default)
{
return (await TryGetChangesAsync(connection, options, maxChanges, status, fileSpecs, cancellationToken)).Data;
}
///
/// Enumerates changes on the server
///
/// Connection to the Perforce server
/// Options for the command
/// List only changes made from the named client workspace.
/// List only the highest numbered changes
/// Limit the list to the changelists with the given status (pending, submitted or shelved)
/// List only changes made by the named user
/// Paths to query changes for
/// Token used to cancel the operation
/// List of responses from the server.
public static async Task> GetChangesAsync(this IPerforceConnection connection, ChangesOptions options, string? clientName, int maxChanges, ChangeStatus status, string? userName, FileSpecList fileSpecs, CancellationToken cancellationToken = default)
{
return (await TryGetChangesAsync(connection, options, clientName, maxChanges, status, userName, fileSpecs, cancellationToken)).Data;
}
///
/// Enumerates changes on the server
///
/// Connection to the Perforce server
/// Options for the command
/// List only changes made from the named client workspace.
/// The minimum changelist number
/// List only the highest numbered changes
/// Limit the list to the changelists with the given status (pending, submitted or shelved)
/// List only changes made by the named user
/// Paths to query changes for
/// Token used to cancel the operation
/// List of responses from the server.
public static async Task> GetChangesAsync(this IPerforceConnection connection, ChangesOptions options, string? clientName, int minChangeNumber, int maxChanges, ChangeStatus status, string? userName, FileSpecList fileSpecs, CancellationToken cancellationToken = default)
{
return (await TryGetChangesAsync(connection, options, clientName, minChangeNumber, maxChanges, status, userName, fileSpecs, cancellationToken)).Data;
}
///
/// Enumerates changes on the server
///
/// Connection to the Perforce server
/// Options for the command
/// List only the highest numbered changes
/// Limit the list to the changelists with the given status (pending, submitted or shelved)
/// Paths to query changes for
/// Token used to cancel the operation
/// List of responses from the server.
public static Task> TryGetChangesAsync(this IPerforceConnection connection, ChangesOptions options, int maxChanges, ChangeStatus status, FileSpecList fileSpecs, CancellationToken cancellationToken = default)
{
return TryGetChangesAsync(connection, options, null, maxChanges, status, null, fileSpecs, cancellationToken);
}
///
/// Enumerates changes on the server
///
/// Connection to the Perforce server
/// Options for the command
/// List only changes made from the named client workspace.
/// List only the highest numbered changes
/// Limit the list to the changelists with the given status (pending, submitted or shelved)
/// List only changes made by the named user
/// Paths to query changes for
/// Token used to cancel the operation
/// List of responses from the server.
public static Task> TryGetChangesAsync(this IPerforceConnection connection, ChangesOptions options, string? clientName, int maxChanges, ChangeStatus status, string? userName, FileSpecList fileSpecs, CancellationToken cancellationToken = default)
{
return TryGetChangesAsync(connection, options, clientName, -1, maxChanges, status, userName, fileSpecs, cancellationToken);
}
///
/// Enumerates changes on the server
///
/// Connection to the Perforce server
/// Options for the command
/// List only changes made from the named client workspace.
/// The minimum changelist number
/// List only the highest numbered changes
/// Limit the list to the changelists with the given status (pending, submitted or shelved)
/// List only changes made by the named user
/// Paths to query changes for
/// Token used to cancel the operation
/// List of responses from the server.
public static Task> TryGetChangesAsync(this IPerforceConnection connection, ChangesOptions options, string? clientName, int minChangeNumber, int maxChanges, ChangeStatus status, string? userName, FileSpecList fileSpecs, CancellationToken cancellationToken = default)
{
List arguments = new List();
if ((options & ChangesOptions.IncludeIntegrations) != 0)
{
arguments.Add("-i");
}
if ((options & ChangesOptions.IncludeTimes) != 0)
{
arguments.Add("-t");
}
if ((options & ChangesOptions.LongOutput) != 0)
{
arguments.Add("-l");
}
if ((options & ChangesOptions.Reverse) != 0)
{
arguments.Add("-r");
}
if ((options & ChangesOptions.TruncatedLongOutput) != 0)
{
arguments.Add("-L");
}
if ((options & ChangesOptions.IncludeRestricted) != 0)
{
arguments.Add("-f");
}
if (clientName != null)
{
arguments.Add($"-c{clientName}");
}
if (minChangeNumber != -1)
{
arguments.Add($"-e{minChangeNumber}");
}
if (maxChanges != -1)
{
arguments.Add($"-m{maxChanges}");
}
if (status != ChangeStatus.All)
{
arguments.Add($"-s{PerforceReflection.GetEnumText(typeof(ChangeStatus), status)}");
}
if (userName != null)
{
arguments.Add($"-u{userName}");
}
if (fileSpecs != FileSpecList.Any) // Queries are slower with a path specification
{
arguments.AddRange(fileSpecs.List);
}
return CommandAsync(connection, "changes", arguments, null, cancellationToken);
}
#endregion
#region p4 clean
///
/// Cleans the workspace
///
/// Connection to the Perforce server
/// Options for the command
/// Files to sync
/// Token used to cancel the operation
/// Response from the server
public static async Task> CleanAsync(this IPerforceConnection connection, CleanOptions options, FileSpecList fileSpecs, CancellationToken cancellationToken = default)
{
return (await TryCleanAsync(connection, options, fileSpecs, cancellationToken)).Data;
}
///
/// Cleans the workspace
///
/// Connection to the Perforce server
/// Options for the command
/// Files to sync
/// Token used to cancel the operation
/// Response from the server
public static async Task> TryCleanAsync(this IPerforceConnection connection, CleanOptions options, FileSpecList fileSpecs, CancellationToken cancellationToken = default)
{
List arguments = new List();
if ((options & CleanOptions.Edited) != 0)
{
arguments.Add("-e");
}
if ((options & CleanOptions.Added) != 0)
{
arguments.Add("-a");
}
if ((options & CleanOptions.Deleted) != 0)
{
arguments.Add("-d");
}
if ((options & CleanOptions.Preview) != 0)
{
arguments.Add("-n");
}
if ((options & CleanOptions.NoIgnoreChecking) != 0)
{
arguments.Add("-I");
}
if ((options & CleanOptions.LocalSyntax) != 0)
{
arguments.Add("-l");
}
if ((options & CleanOptions.ModifiedTimes) != 0)
{
arguments.Add("-m");
}
arguments.AddRange(fileSpecs.List);
PerforceResponseList records = await CommandAsync(connection, "clean", arguments, null, cancellationToken);
records.RemoveAll(x => x.Error != null && x.Error.Generic == PerforceGenericCode.Empty);
return records;
}
#endregion
#region p4 client
///
/// Creates a client
///
/// Connection to the Perforce server
/// The client record
/// Token used to cancel the operation
/// Response from the server
public static async Task CreateClientAsync(this IPerforceConnection connection, ClientRecord record, CancellationToken cancellationToken = default)
{
(await TryCreateClientAsync(connection, record, cancellationToken)).EnsureSuccess();
}
///
/// Creates a client
///
/// Connection to the Perforce server
/// The client record
/// Token used to cancel the operation
/// Response from the server
public static Task TryCreateClientAsync(this IPerforceConnection connection, ClientRecord record, CancellationToken cancellationToken = default)
{
return TryUpdateClientAsync(connection, record, cancellationToken);
}
///
/// Creates a client
///
/// Connection to the Perforce server
/// The client record
/// Token used to cancel the operation
/// Response from the server
public static async Task UpdateClientAsync(this IPerforceConnection connection, ClientRecord record, CancellationToken cancellationToken = default)
{
(await TryUpdateClientAsync(connection, record, cancellationToken)).EnsureSuccess();
}
///
/// Update a client
///
/// Connection to the Perforce server
/// The client record
/// Token used to cancel the operation
/// Response from the server
public static Task TryUpdateClientAsync(this IPerforceConnection connection, ClientRecord record, CancellationToken cancellationToken = default)
{
return SingleResponseCommandAsync(connection, "client", new List { "-i" }, connection.SerializeRecord(record), null, cancellationToken);
}
///
/// Deletes a client
///
/// Connection to the Perforce server
/// Options for this command
/// Name of the client
/// Token used to cancel the operation
/// Response from the server
public static async Task DeleteClientAsync(this IPerforceConnection connection, DeleteClientOptions options, string clientName, CancellationToken cancellationToken = default)
{
(await TryDeleteClientAsync(connection, options, clientName, cancellationToken)).EnsureSuccess();
}
///
/// Deletes a client
///
/// Connection to the Perforce server
/// Options for this command
/// Name of the client
/// Token used to cancel the operation
/// Response from the server
public static Task TryDeleteClientAsync(this IPerforceConnection connection, DeleteClientOptions options, string clientName, CancellationToken cancellationToken = default)
{
List arguments = new List { "-d" };
if ((options & DeleteClientOptions.Force) != 0)
{
arguments.Add("-f");
}
if ((options & DeleteClientOptions.DeleteShelved) != 0)
{
arguments.Add("-Fs");
}
arguments.Add(clientName);
return SingleResponseCommandAsync(connection, "client", arguments, null, null, cancellationToken);
}
///
/// Changes the stream associated with a client
///
/// Connection to the Perforce server
/// The new stream to be associated with the client
/// Options for this command
/// Token used to cancel the operation
/// Response from the server
public static async Task SwitchClientToStreamAsync(this IPerforceConnection connection, string streamName, SwitchClientOptions options, CancellationToken cancellationToken = default)
{
(await TrySwitchClientToStreamAsync(connection, streamName, options, cancellationToken)).EnsureSuccess();
}
///
/// Changes the stream associated with a client
///
/// Connection to the Perforce server
/// The new stream to be associated with the client
/// Options for this command
/// Token used to cancel the operation
/// Response from the server
public static Task TrySwitchClientToStreamAsync(this IPerforceConnection connection, string streamName, SwitchClientOptions options, CancellationToken cancellationToken = default)
{
List arguments = new List { "-s" };
if ((options & SwitchClientOptions.IgnoreOpenFiles) != 0)
{
arguments.Add("-f");
}
arguments.Add($"-S{streamName}");
return SingleResponseCommandAsync(connection, "client", arguments, null, null, cancellationToken);
}
///
/// Changes a client to mirror a template
///
/// Connection to the Perforce server
/// The new stream to be associated with the client
/// Token used to cancel the operation
/// Response from the server
public static async Task SwitchClientToTemplateAsync(this IPerforceConnection connection, string templateName, CancellationToken cancellationToken = default)
{
(await TrySwitchClientToTemplateAsync(connection, templateName, cancellationToken)).EnsureSuccess();
}
///
/// Changes a client to mirror a template
///
/// Connection to the Perforce server
/// The new stream to be associated with the client
/// Token used to cancel the operation
/// Response from the server
public static Task TrySwitchClientToTemplateAsync(this IPerforceConnection connection, string templateName, CancellationToken cancellationToken = default)
{
List arguments = new List();
arguments.Add("-s");
arguments.Add($"-t{templateName}");
return SingleResponseCommandAsync(connection, "client", arguments, null, null, cancellationToken);
}
///
/// Queries the current client definition
///
/// Connection to the Perforce server
/// Name of the client. Specify null for the current client.
/// Token used to cancel the operation
/// Response from the server; either a client record or error code
public static async Task GetClientAsync(this IPerforceConnection connection, string? clientName, CancellationToken cancellationToken = default)
{
return (await TryGetClientAsync(connection, clientName, cancellationToken)).Data;
}
///
/// Queries the current client definition
///
/// Connection to the Perforce server
/// Name of the client. Specify null for the current client.
/// Token used to cancel the operation
/// Response from the server; either a client record or error code
public static Task> TryGetClientAsync(this IPerforceConnection connection, string? clientName, CancellationToken cancellationToken = default)
{
List arguments = new List { "-o" };
if (clientName != null)
{
arguments.Add(clientName);
}
return SingleResponseCommandAsync(connection, "client", arguments, null, cancellationToken);
}
///
/// Queries the view for a stream
///
/// Connection to the Perforce server
/// Name of the stream.
/// Changelist at which to query the stream view
/// Token used to cancel the operation
/// Response from the server; either a client record or error code
public static async Task GetStreamViewAsync(this IPerforceConnection connection, string streamName, int changeNumber, CancellationToken cancellationToken = default)
{
return (await TryGetStreamViewAsync(connection, streamName, changeNumber, cancellationToken)).Data;
}
///
/// Queries the view for a stream
///
/// Connection to the Perforce server
/// Name of the stream.
/// Changelist at which to query the stream view
/// Token used to cancel the operation
/// Response from the server; either a client record or error code
public static Task> TryGetStreamViewAsync(this IPerforceConnection connection, string streamName, int changeNumber, CancellationToken cancellationToken = default)
{
List arguments = new List { "-o" };
arguments.Add($"-S{streamName}");
if (changeNumber != -1)
{
arguments.Add($"-c{changeNumber}");
}
return SingleResponseCommandAsync(connection, "client", arguments, null, cancellationToken);
}
///
/// Serializes a client record to a byte array
///
/// Connection to the Perforce server
/// The input record
/// Serialized record data
static byte[] SerializeRecord(this IPerforceConnection connection, ClientRecord input)
{
List> nameToValue = new List>();
if (input.Name != null)
{
nameToValue.Add(new KeyValuePair("Client", input.Name));
}
if (input.Owner != null)
{
nameToValue.Add(new KeyValuePair("Owner", input.Owner));
}
if (input.Host != null)
{
nameToValue.Add(new KeyValuePair("Host", input.Host));
}
if (input.Description != null)
{
nameToValue.Add(new KeyValuePair("Description", input.Description));
}
if (input.Root != null)
{
nameToValue.Add(new KeyValuePair("Root", input.Root));
}
if (input.Options != ClientOptions.None)
{
nameToValue.Add(new KeyValuePair("Options", PerforceReflection.GetEnumText(typeof(ClientOptions), input.Options)));
}
if (input.SubmitOptions != ClientSubmitOptions.Unspecified)
{
nameToValue.Add(new KeyValuePair("SubmitOptions", PerforceReflection.GetEnumText(typeof(ClientSubmitOptions), input.SubmitOptions)));
}
if (input.LineEnd != ClientLineEndings.Unspecified)
{
nameToValue.Add(new KeyValuePair("LineEnd", PerforceReflection.GetEnumText(typeof(ClientLineEndings), input.LineEnd)));
}
if (input.Type != null)
{
nameToValue.Add(new KeyValuePair("Type", input.Type));
}
if (input.Stream != null)
{
nameToValue.Add(new KeyValuePair("Stream", input.Stream));
}
if (input.View.Count > 0)
{
nameToValue.Add(new KeyValuePair("View", input.View));
}
return connection.CreateRecord(nameToValue).Serialize();
}
#endregion
#region p4 clients
///
/// Queries the current client definition
///
/// Connection to the Perforce server
/// Options for this command
/// List only client workspaces owned by this user.
/// Token used to cancel the operation
/// Response from the server; either a client record or error code
public static async Task> GetClientsAsync(this IPerforceConnection connection, ClientsOptions options, string? userName, CancellationToken cancellationToken = default)
{
return (await TryGetClientsAsync(connection, options, userName, cancellationToken)).Data;
}
///
/// Queries the current client definition
///
/// Connection to the Perforce server
/// Options for this command
/// List only client workspaces owned by this user.
/// Token used to cancel the operation
/// Response from the server; either a client record or error code
public static Task> TryGetClientsAsync(this IPerforceConnection connection, ClientsOptions options, string? userName, CancellationToken cancellationToken = default)
{
return TryGetClientsAsync(connection, options, null, -1, null, userName, cancellationToken);
}
///
/// Queries the current client definition
///
/// Connection to the Perforce server
/// Options for this command
/// List only client workspaces matching filter. Treated as case sensitive if [ClientsOptions.CaseSensitiveFilter] is set.
/// Limit the number of results to return. -1 for all.
/// List client workspaces associated with the specified stream.
/// List only client workspaces owned by this user.
/// Token used to cancel the operation
/// Response from the server; either a client record or error code
public static async Task> GetClientsAsync(this IPerforceConnection connection, ClientsOptions options, string? filter, int maxResults, string? stream, string? userName, CancellationToken cancellationToken = default)
{
return (await TryGetClientsAsync(connection, options, filter, maxResults, stream, userName, cancellationToken)).Data;
}
///
/// Queries the current client definition
///
/// Connection to the Perforce server
/// Options for this command
/// List only client workspaces matching filter. Treated as case sensitive if [ClientsOptions.CaseSensitiveFilter] is set.
/// Limit the number of results to return. -1 for all.
/// List client workspaces associated with the specified stream.
/// List only client workspaces owned by this user.
/// Token used to cancel the operation
/// Response from the server; either a client record or error code
public static Task> TryGetClientsAsync(this IPerforceConnection connection, ClientsOptions options, string? filter, int maxResults, string? stream, string? userName, CancellationToken cancellationToken = default)
{
List arguments = new List();
if ((options & ClientsOptions.All) != 0)
{
arguments.Add("-a");
}
if (filter != null)
{
if ((options & ClientsOptions.CaseSensitiveFilter) != 0)
{
arguments.Add("-e");
arguments.Add(filter);
}
else
{
arguments.Add("-E");
arguments.Add(filter);
}
}
if (maxResults != -1)
{
arguments.Add($"-m{maxResults}");
}
if (stream != null)
{
arguments.Add($"-S{stream}");
}
if ((options & ClientsOptions.WithTimes) != 0)
{
arguments.Add("-t");
}
if (userName != null)
{
arguments.Add("-u");
arguments.Add(userName);
}
if ((options & ClientsOptions.Unloaded) != 0)
{
arguments.Add("-U");
}
return CommandAsync(connection, "clients", arguments, null, cancellationToken);
}
#endregion
#region p4 delete
///
/// Execute the 'delete' command
///
/// Connection to the Perforce server
/// The change to add deleted files to
/// Options for the command
/// List of file specifications to query
/// Token used to cancel the operation
/// List of responses from the server
public static async Task> DeleteAsync(this IPerforceConnection connection, int changeNumber, DeleteOptions options, FileSpecList fileSpecs, CancellationToken cancellationToken = default)
{
return (await TryDeleteAsync(connection, changeNumber, options, fileSpecs, cancellationToken)).Data;
}
///
/// Execute the 'delete' command
///
/// Connection to the Perforce server
/// The change to add deleted files to
/// Options for the command
/// List of file specifications to query
/// Token used to cancel the operation
/// List of responses from the server
public static async Task> TryDeleteAsync(this IPerforceConnection connection, int changeNumber, DeleteOptions options, FileSpecList fileSpecs, CancellationToken cancellationToken = default)
{
List arguments = new List();
if (changeNumber != -1)
{
arguments.Add($"-c{changeNumber}");
}
if ((options & DeleteOptions.PreviewOnly) != 0)
{
arguments.Add("-n");
}
if ((options & DeleteOptions.KeepWorkspaceFiles) != 0)
{
arguments.Add("-k");
}
if ((options & DeleteOptions.WithoutSyncing) != 0)
{
arguments.Add("-v");
}
PerforceResponseList records = await BatchedCommandAsync(connection, "delete", arguments, fileSpecs.List, cancellationToken);
records.RemoveAll(x => x.Error != null && x.Error.Generic == PerforceGenericCode.Empty);
return records;
}
#endregion
#region p4 depot
///
/// Queries the current depot definition
///
/// Connection to the Perforce server
/// Name of the client. Specify null for the current client.
/// Token used to cancel the operation
/// Response from the server; either a client record or error code
public static Task GetDepotAsync(this IPerforceConnection connection, string depotName, CancellationToken cancellationToken = default)
{
return TryGetDepotAsync(connection, depotName, cancellationToken).UnwrapAsync();
}
///
/// Queries the current depot definition
///
/// Connection to the Perforce server
/// Name of the client. Specify null for the current client.
/// Token used to cancel the operation
/// Response from the server; either a client record or error code
public static Task> TryGetDepotAsync(this IPerforceConnection connection, string depotName, CancellationToken cancellationToken = default)
{
List arguments = new List { "-o", depotName };
return SingleResponseCommandAsync(connection, "depot", arguments, null, cancellationToken);
}
#endregion
#region p4 describe
///
/// Describes a single changelist
///
/// Connection to the Perforce server
/// The changelist number to retrieve description for
/// Token used to cancel the operation
/// Response from the server; either a describe record or error code
public static async Task DescribeAsync(this IPerforceConnection connection, int changeNumber, CancellationToken cancellationToken = default)
{
return (await TryDescribeAsync(connection, changeNumber, cancellationToken)).Data;
}
///
/// Describes a single changelist
///
/// Connection to the Perforce server
/// The changelist number to retrieve description for
/// Token used to cancel the operation
/// Response from the server; either a describe record or error code
public static async Task> TryDescribeAsync(this IPerforceConnection connection, int changeNumber, CancellationToken cancellationToken = default)
{
PerforceResponseList records = await TryDescribeAsync(connection, new int[] { changeNumber }, cancellationToken);
if (records.Count != 1)
{
throw new PerforceException("Expected only one record returned from p4 describe command, got {0}", records.Count);
}
return records[0];
}
///
/// Describes a single changelist
///
/// Connection to the Perforce server
/// Options for the command
/// Maximum number of files to return
/// The changelist number to retrieve description for
/// Token used to cancel the operation
/// Response from the server; either a describe record or error code
public static async Task DescribeAsync(this IPerforceConnection connection, DescribeOptions options, int maxNumFiles, int changeNumber, CancellationToken cancellationToken = default)
{
PerforceResponse response = await TryDescribeAsync(connection, options, maxNumFiles, changeNumber, cancellationToken);
return response.Data;
}
///
/// Describes a single changelist
///
/// Connection to the Perforce server
/// Options for the command
/// Maximum number of files to return
/// The changelist number to retrieve description for
/// Token used to cancel the operation
/// Response from the server; either a describe record or error code
public static async Task> TryDescribeAsync(this IPerforceConnection connection, DescribeOptions options, int maxNumFiles, int changeNumber, CancellationToken cancellationToken = default)
{
PerforceResponseList records = await TryDescribeAsync(connection, options, maxNumFiles, new int[] { changeNumber }, cancellationToken);
if (records.Count != 1)
{
throw new PerforceException("Expected only one record returned from p4 describe command, got {0}", records.Count);
}
return records[0];
}
///
/// Describes a set of changelists
///
/// Connection to the Perforce server
/// The changelist numbers to retrieve descriptions for
/// Token used to cancel the operation
/// List of responses from the server
public static async Task> DescribeAsync(this IPerforceConnection connection, int[] changeNumbers, CancellationToken cancellationToken = default)
{
return (await TryDescribeAsync(connection, changeNumbers, cancellationToken)).Data;
}
///
/// Describes a set of changelists
///
/// Connection to the Perforce server
/// The changelist numbers to retrieve descriptions for
/// Token used to cancel the operation
/// List of responses from the server
public static Task> TryDescribeAsync(this IPerforceConnection connection, int[] changeNumbers, CancellationToken cancellationToken = default)
{
List arguments = new List { "-s" };
foreach (int changeNumber in changeNumbers)
{
arguments.Add($"{changeNumber}");
}
return CommandAsync(connection, "describe", arguments, null, cancellationToken);
}
///
/// Describes a set of changelists
///
/// Connection to the Perforce server
/// Options for the command
/// Maximum number of files to return
/// The changelist numbers to retrieve descriptions for
/// Token used to cancel the operation
/// List of responses from the server
public static async Task> DescribeAsync(this IPerforceConnection connection, DescribeOptions options, int maxNumFiles, int[] changeNumbers, CancellationToken cancellationToken = default)
{
return (await TryDescribeAsync(connection, options, maxNumFiles, changeNumbers, cancellationToken)).Data;
}
///
/// Describes a set of changelists
///
/// Connection to the Perforce server
/// Options for the command
/// Maximum number of files to return
/// The changelist numbers to retrieve descriptions for
/// Token used to cancel the operation
/// List of responses from the server
public static Task> TryDescribeAsync(this IPerforceConnection connection, DescribeOptions options, int maxNumFiles, int[] changeNumbers, CancellationToken cancellationToken = default)
{
List arguments = new List { "-s" };
if ((options & DescribeOptions.ShowDescriptionForRestrictedChanges) != 0)
{
arguments.Add("-f");
}
if ((options & DescribeOptions.Identity) != 0)
{
arguments.Add("-I");
}
if (maxNumFiles != -1)
{
arguments.Add($"-m{maxNumFiles}");
}
if ((options & DescribeOptions.OriginalChangeNumber) != 0)
{
arguments.Add("-O");
}
if ((options & DescribeOptions.Shelved) != 0)
{
arguments.Add("-S");
}
foreach (int changeNumber in changeNumbers)
{
arguments.Add($"{changeNumber}");
}
return CommandAsync(connection, "describe", arguments, null, cancellationToken);
}
#endregion
#region p4 dirs
///
/// List directories under a depot path
///
/// Connection to the Perforce server
/// Options for the command
/// List directories mapped for the specified stream
/// Files to be opened for edit
/// Token used to cancel the operation
/// Response from the server
public static async Task> GetDirsAsync(this IPerforceConnection connection, DirsOptions options, string? stream, FileSpecList fileSpecs, CancellationToken cancellationToken = default)
{
return (await TryGetDirsAsync(connection, options, stream, fileSpecs, cancellationToken)).Data;
}
///
/// List directories under a depot path
///
/// Connection to the Perforce server
/// Options for the command
/// List directories mapped for the specified stream
/// Files to be opened for edit
/// Token used to cancel the operation
/// Response from the server
public static Task> TryGetDirsAsync(this IPerforceConnection connection, DirsOptions options, string? stream, FileSpecList fileSpecs, CancellationToken cancellationToken = default)
{
List arguments = new List();
if ((options & DirsOptions.OnlyMapped) != 0)
{
arguments.Add("-C");
}
if ((options & DirsOptions.IncludeDeleted) != 0)
{
arguments.Add("-D");
}
if ((options & DirsOptions.OnlyHave) != 0)
{
arguments.Add("-H");
}
if (stream != null)
{
arguments.Add("-S");
arguments.Add(stream);
}
if ((options & DirsOptions.IgnoreCase) != 0)
{
arguments.Add("-i");
}
return CommandAsync(connection, "dirs", arguments, fileSpecs.List, null, cancellationToken);
}
#endregion
#region p4 edit
///
/// Opens files for edit
///
/// Connection to the Perforce server
/// Changelist to add files to
/// Files to be opened for edit
/// Token used to cancel the operation
/// Response from the server
public static async Task> EditAsync(this IPerforceConnection connection, int changeNumber, FileSpecList fileSpecs, CancellationToken cancellationToken = default)
{
return (await TryEditAsync(connection, changeNumber, fileSpecs, cancellationToken)).Data;
}
///
/// Opens files for edit
///
/// Connection to the Perforce server
/// Changelist to add files to
/// Files to be opened for edit
/// Token used to cancel the operation
/// Response from the server
public static Task> TryEditAsync(this IPerforceConnection connection, int changeNumber, FileSpecList fileSpecs, CancellationToken cancellationToken = default)
{
return TryEditAsync(connection, changeNumber, null, EditOptions.None, fileSpecs, cancellationToken);
}
///
/// Opens files for edit
///
/// Connection to the Perforce server
/// Changelist to add files to
/// Type for new files
/// Options for the command
/// Files to be opened for edit
/// Token used to cancel the operation
/// Response from the server
public static async Task> EditAsync(this IPerforceConnection connection, int changeNumber, string? fileType, EditOptions options, FileSpecList fileSpecs, CancellationToken cancellationToken = default)
{
return (await TryEditAsync(connection, changeNumber, fileType, options, fileSpecs, cancellationToken)).Data;
}
///
/// Opens files for edit
///
/// Connection to the Perforce server
/// Changelist to add files to
/// Type for new files
/// Options for the command
/// Files to be opened for edit
/// Token used to cancel the operation
/// Response from the server
public static Task> TryEditAsync(this IPerforceConnection connection, int changeNumber, string? fileType, EditOptions options, FileSpecList fileSpecs, CancellationToken cancellationToken = default)
{
List arguments = new List();
if (changeNumber != -1)
{
arguments.Add($"-c{changeNumber}");
}
if ((options & EditOptions.KeepWorkspaceFiles) != 0)
{
arguments.Add("-k");
}
if ((options & EditOptions.PreviewOnly) != 0)
{
arguments.Add("-n");
}
if (fileType != null)
{
arguments.Add($"-t{fileType}");
}
return BatchedCommandAsync(connection, "edit", arguments, fileSpecs.List, cancellationToken);
}
#endregion
#region p4 filelog
///
/// Execute the 'filelog' command
///
/// Connection to the Perforce server
/// Options for the command
/// List of file specifications to query
/// Token used to cancel the operation
/// List of responses from the server
public static async Task> FileLogAsync(this IPerforceConnection connection, FileLogOptions options, FileSpecList fileSpecs, CancellationToken cancellationToken = default)
{
return (await TryFileLogAsync(connection, options, fileSpecs, cancellationToken)).Data;
}
///
/// Execute the 'filelog' command
///
/// Connection to the Perforce server
/// Options for the command
/// List of file specifications to query
/// Token used to cancel the operation
/// List of responses from the server
public static Task> TryFileLogAsync(this IPerforceConnection connection, FileLogOptions options, FileSpecList fileSpecs, CancellationToken cancellationToken = default)
{
return TryFileLogAsync(connection, -1, -1, options, fileSpecs, cancellationToken);
}
///
/// Execute the 'filelog' command
///
/// Connection to the Perforce server
/// Number of changelists to show. Ignored if zero or negative.
/// Options for the command
/// List of file specifications to query
/// Token used to cancel the operation
/// List of responses from the server
public static async Task> FileLogAsync(this IPerforceConnection connection, int maxChanges, FileLogOptions options, FileSpecList fileSpecs, CancellationToken cancellationToken = default)
{
return (await TryFileLogAsync(connection, maxChanges, options, fileSpecs, cancellationToken)).Data;
}
///
/// Execute the 'filelog' command
///
/// Connection to the Perforce server
/// Number of changelists to show. Ignored if zero or negative.
/// Options for the command
/// List of file specifications to query
/// Token used to cancel the operation
/// List of responses from the server
public static Task> TryFileLogAsync(this IPerforceConnection connection, int maxChanges, FileLogOptions options, FileSpecList fileSpecs, CancellationToken cancellationToken = default)
{
return TryFileLogAsync(connection, -1, maxChanges, options, fileSpecs, cancellationToken);
}
///
/// Execute the 'filelog' command
///
/// Connection to the Perforce server
/// Show only files modified by this changelist. Ignored if zero or negative.
/// Number of changelists to show. Ignored if zero or negative.
/// Options for the command
/// List of file specifications to query
/// Token used to cancel the operation
/// List of responses from the server
public static async Task> FileLogAsync(this IPerforceConnection connection, int changeNumber, int maxChanges, FileLogOptions options, FileSpecList fileSpecs, CancellationToken cancellationToken = default)
{
return (await TryFileLogAsync(connection, changeNumber, maxChanges, options, fileSpecs, cancellationToken)).Data;
}
///
/// Execute the 'filelog' command
///
/// Connection to the Perforce server
/// Show only files modified by this changelist. Ignored if zero or negative.
/// Number of changelists to show. Ignored if zero or negative.
/// Options for the command
/// List of file specifications to query
/// Token used to cancel the operation
/// List of responses from the server
public static async Task> TryFileLogAsync(this IPerforceConnection connection, int changeNumber, int maxChanges, FileLogOptions options, FileSpecList fileSpecs, CancellationToken cancellationToken = default)
{
// Build the argument list
List arguments = new List();
if (changeNumber > 0)
{
arguments.Add($"-c{changeNumber}");
}
if ((options & FileLogOptions.ContentHistory) != 0)
{
arguments.Add("-h");
}
if ((options & FileLogOptions.FollowAcrossBranches) != 0)
{
arguments.Add("-i");
}
if ((options & FileLogOptions.FullDescriptions) != 0)
{
arguments.Add("-l");
}
if ((options & FileLogOptions.LongDescriptions) != 0)
{
arguments.Add("-L");
}
if (maxChanges > 0)
{
arguments.Add($"-m{maxChanges}");
}
if ((options & FileLogOptions.DoNotFollowPromotedTaskStreams) != 0)
{
arguments.Add("-p");
}
if ((options & FileLogOptions.IgnoreNonContributoryIntegrations) != 0)
{
arguments.Add("-s");
}
// Always include times to simplify parsing
arguments.Add("-t");
// Append all the arguments
PerforceResponseList records = await BatchedCommandAsync(connection, "filelog", arguments, fileSpecs.List, cancellationToken);
records.RemoveAll(x => x.Error != null && x.Error.Generic == PerforceGenericCode.Empty);
return records;
}
#endregion
#region p4 files
///
/// Execute the 'files' command
///
/// Connection to the Perforce server
/// Options for the command
/// List of file specifications to query
/// Token used to cancel the operation
/// List of response objects
public static async Task> FilesAsync(this IPerforceConnection connection, FilesOptions options, FileSpecList fileSpecs, CancellationToken cancellationToken = default)
{
return (await TryFilesAsync(connection, options, -1, fileSpecs, cancellationToken)).Data;
}
///
/// Execute the 'files' command
///
/// Connection to the Perforce server
/// Options for the command
/// List of file specifications to query
/// Token used to cancel the operation
/// List of response objects
public static Task> TryFilesAsync(this IPerforceConnection connection, FilesOptions options, FileSpecList fileSpecs, CancellationToken cancellationToken = default)
{
return TryFilesAsync(connection, options, -1, fileSpecs, cancellationToken);
}
///
/// Execute the 'files' command
///
/// Connection to the Perforce server
/// Options for the command
/// Maximum number of results to return. Ignored if less than or equal to zero.
/// List of file specifications to query
/// Token used to cancel the operation
/// List of response objects
public static async Task> FilesAsync(this IPerforceConnection connection, FilesOptions options, int maxFiles, FileSpecList fileSpecs, CancellationToken cancellationToken = default)
{
return (await TryFilesAsync(connection, options, maxFiles, fileSpecs, cancellationToken)).Data;
}
///
/// Execute the 'files' command
///
/// Connection to the Perforce server
/// Options for the command
/// Maximum number of results to return. Ignored if less than or equal to zero.
/// List of file specifications to query
/// Token used to cancel the operation
/// List of response objects
public static async Task> TryFilesAsync(this IPerforceConnection connection, FilesOptions options, int maxFiles, FileSpecList fileSpecs, CancellationToken cancellationToken = default)
{
List arguments = new List();
if ((options & FilesOptions.AllRevisions) != 0)
{
arguments.Add("-a");
}
if ((options & FilesOptions.LimitToArchiveDepots) != 0)
{
arguments.Add("-A");
}
if ((options & FilesOptions.ExcludeDeleted) != 0)
{
arguments.Add("-e");
}
if ((options & FilesOptions.IgnoreCase) != 0)
{
arguments.Add("-i");
}
if (maxFiles > 0)
{
arguments.Add($"-m{maxFiles}");
}
PerforceResponseList records = await BatchedCommandAsync(connection, "files", arguments, fileSpecs.List, cancellationToken);
records.RemoveAll(x => x.Error != null && x.Error.Generic == PerforceGenericCode.Empty);
return records;
}
#endregion
#region p4 fstat
///
/// Execute the 'fstat' command
///
/// Connection to the Perforce server
/// List of file specifications to query
/// Token used to cancel the operation
/// List of responses from the server
public static IAsyncEnumerable FStatAsync(this IPerforceConnection connection, FileSpecList fileSpecs, CancellationToken cancellationToken = default)
{
return TryFStatAsync(connection, FStatOptions.None, fileSpecs, cancellationToken).Select(x => x.Data);
}
///
/// Execute the 'fstat' command
///
/// Connection to the Perforce server
/// Options for the command
/// List of file specifications to query
/// Token used to cancel the operation
/// List of responses from the server
public static IAsyncEnumerable FStatAsync(this IPerforceConnection connection, FStatOptions options, FileSpecList fileSpecs, CancellationToken cancellationToken = default)
{
return TryFStatAsync(connection, options, fileSpecs, cancellationToken).Select(x => x.Data);
}
///
/// Execute the 'fstat' command
///
/// Connection to the Perforce server
/// Options for the command
/// List of file specifications to query
/// Token used to cancel the operation
/// List of responses from the server
public static IAsyncEnumerable> TryFStatAsync(this IPerforceConnection connection, FStatOptions options, FileSpecList fileSpecs, CancellationToken cancellationToken = default)
{
return TryFStatAsync(connection, -1, options, fileSpecs, cancellationToken);
}
///
/// Execute the 'fstat' command
///
/// Connection to the Perforce server
/// Produce fstat output for only the first max files.
/// Options for the command
/// List of file specifications to query
/// Token used to cancel the operation
/// List of responses from the server
public static IAsyncEnumerable FStatAsync(this IPerforceConnection connection, int maxFiles, FStatOptions options, FileSpecList fileSpecs, CancellationToken cancellationToken = default)
{
return TryFStatAsync(connection, maxFiles, options, fileSpecs, cancellationToken).Select(x => x.Data);
}
///
/// Execute the 'fstat' command
///
/// Connection to the Perforce server
/// Produce fstat output for only the first max files.
/// Options for the command
/// List of file specifications to query
/// Token used to cancel the operation
/// List of responses from the server
public static IAsyncEnumerable> TryFStatAsync(this IPerforceConnection connection, int maxFiles, FStatOptions options, FileSpecList fileSpecs, CancellationToken cancellationToken = default)
{
return TryFStatAsync(connection, -1, -1, null, null, maxFiles, options, fileSpecs, cancellationToken);
}
///
/// Execute the 'fstat' command
///
/// Connection to the Perforce server
/// Return only files affected after the given changelist number.
/// Return only files affected by the given changelist number.
/// List only those files that match the criteria specified.
/// Fields to return in the output
/// Produce fstat output for only the first max files.
/// Options for the command
/// List of file specifications to query
/// Token used to cancel the operation
/// List of responses from the server
public static IAsyncEnumerable FStatAsync(this IPerforceConnection connection, int afterChangeNumber, int onlyChangeNumber, string? filter, string? fields, int maxFiles, FStatOptions options, FileSpecList fileSpecs, CancellationToken cancellationToken = default)
{
return TryFStatAsync(connection, afterChangeNumber, onlyChangeNumber, filter, fields, maxFiles, options, fileSpecs, cancellationToken).Select(x => x.Data);
}
///
/// Execute the 'fstat' command
///
/// Connection to the Perforce server
/// Return only files affected after the given changelist number.
/// Return only files affected by the given changelist number.
/// List only those files that match the criteria specified.
/// Fields to return in the output
/// Produce fstat output for only the first max files.
/// Options for the command
/// List of file specifications to query
/// Token used to cancel the operation
/// List of responses from the server
public static IAsyncEnumerable> TryFStatAsync(this IPerforceConnection connection, int afterChangeNumber, int onlyChangeNumber, string? filter, string? fields, int maxFiles, FStatOptions options, FileSpecList fileSpecs, CancellationToken cancellationToken = default)
{
// Build the argument list
List arguments = new List();
if (afterChangeNumber != -1)
{
arguments.Add($"-c{afterChangeNumber}");
}
if (onlyChangeNumber != -1)
{
arguments.Add($"-e{onlyChangeNumber}");
}
if (filter != null)
{
arguments.Add("-F");
arguments.Add(filter);
}
if (fields != null)
{
arguments.Add("-T");
arguments.Add(fields);
}
if ((options & FStatOptions.ReportDepotSyntax) != 0)
{
arguments.Add("-L");
}
if ((options & FStatOptions.AllRevisions) != 0)
{
arguments.Add("-Of");
}
if ((options & FStatOptions.IncludeFileSizes) != 0)
{
arguments.Add("-Ol");
}
if ((options & FStatOptions.ClientFileInPerforceSyntax) != 0)
{
arguments.Add("-Op");
}
if ((options & FStatOptions.ShowPendingIntegrations) != 0)
{
arguments.Add("-Or");
}
if ((options & FStatOptions.ShortenOutput) != 0)
{
arguments.Add("-Os");
}
if ((options & FStatOptions.ReverseOrder) != 0)
{
arguments.Add("-r");
}
if ((options & FStatOptions.OnlyMapped) != 0)
{
arguments.Add("-Rc");
}
if ((options & FStatOptions.OnlyHave) != 0)
{
arguments.Add("-Rh");
}
if ((options & FStatOptions.OnlyOpenedBeforeHead) != 0)
{
arguments.Add("-Rn");
}
if ((options & FStatOptions.OnlyOpenInWorkspace) != 0)
{
arguments.Add("-Ro");
}
if ((options & FStatOptions.OnlyOpenAndResolved) != 0)
{
arguments.Add("-Rr");
}
if ((options & FStatOptions.OnlyShelved) != 0)
{
arguments.Add("-Rs");
}
if ((options & FStatOptions.OnlyUnresolved) != 0)
{
arguments.Add("-Ru");
}
if ((options & FStatOptions.SortByDate) != 0)
{
arguments.Add("-Sd");
}
if ((options & FStatOptions.SortByHaveRevision) != 0)
{
arguments.Add("-Sh");
}
if ((options & FStatOptions.SortByHeadRevision) != 0)
{
arguments.Add("-Sr");
}
if ((options & FStatOptions.SortByFileSize) != 0)
{
arguments.Add("-Ss");
}
if ((options & FStatOptions.SortByFileType) != 0)
{
arguments.Add("-St");
}
if ((options & FStatOptions.IncludeFilesInUnloadDepot) != 0)
{
arguments.Add("-U");
}
if (maxFiles > 0)
{
arguments.Add($"-m{maxFiles}");
}
// Execute the command
IAsyncEnumerable> records = StreamCommandAsync(connection, "fstat", arguments, fileSpecs.List, null, cancellationToken);
records = records.Where(x => x.Error == null || x.Error.Generic != PerforceGenericCode.Empty);
if (onlyChangeNumber != -1)
{
records = records.Where(x => !x.Succeeded || x.Data.Description == null);
}
return records;
}
#endregion
#region p4 have
///
/// Determine files currently synced to the client
///
/// Connection to the Perforce server
/// Files to query
/// Token used to cancel the operation
/// List of file records
public static IAsyncEnumerable HaveAsync(this IPerforceConnection connection, FileSpecList fileSpec, CancellationToken cancellationToken = default)
{
return TryHaveAsync(connection, fileSpec, cancellationToken).Select(x => x.Data);
}
///
/// Determine files currently synced to the client
///
/// Connection to the Perforce server
/// Files to query
/// Token used to cancel the operation
/// List of file records
public static IAsyncEnumerable> TryHaveAsync(this IPerforceConnection connection, FileSpecList fileSpec, CancellationToken cancellationToken = default)
{
IAsyncEnumerable> records = StreamCommandAsync(connection, "have", new List(), fileSpec.List, null, cancellationToken);
records = records.Where(x => x.Error == null || x.Error.Generic != PerforceGenericCode.Empty);
return records;
}
#endregion
#region p4 info
///
/// Execute the 'info' command
///
/// Connection to the Perforce server
/// Options for the command
/// Token used to cancel the operation
/// Response from the server; an InfoRecord or error code
public static async Task GetInfoAsync(this IPerforceConnection connection, InfoOptions options, CancellationToken cancellationToken = default)
{
return (await TryGetInfoAsync(connection, options, cancellationToken)).Data;
}
///
/// Execute the 'info' command
///
/// Connection to the Perforce server
/// Options for the command
/// Token used to cancel the operation
/// Response from the server; an InfoRecord or error code
public static Task> TryGetInfoAsync(this IPerforceConnection connection, InfoOptions options, CancellationToken cancellationToken = default)
{
// Build the argument list
List arguments = new List();
if ((options & InfoOptions.ShortOutput) != 0)
{
arguments.Add("-s");
}
return SingleResponseCommandAsync(connection, "info", arguments, null, cancellationToken);
}
#endregion
#region p4 login
///
/// Log in to the server
///
///
///
///
///
public static async Task LoginAsync(this IPerforceConnection connection, string password, CancellationToken cancellationToken = default)
{
return (await TryLoginAsync(connection, password, cancellationToken)).Data;
}
///
/// Attempts to log in to the server
///
/// Connection to use
///
///
///
public static Task> TryLoginAsync(this IPerforceConnection connection, string? password, CancellationToken cancellationToken = default)
{
return TryLoginAsync(connection, LoginOptions.None, null, password, null, cancellationToken);
}
///
/// Attempts to log in to the server
///
/// Connection to use
/// Options for the command
/// User to login as
/// Password for the user
/// Host for the ticket
///
///
public static async Task> TryLoginAsync(this IPerforceConnection connection, LoginOptions options, string? user, string? password, string? host, CancellationToken cancellationToken = default)
{
List arguments = new List();
if ((options & LoginOptions.AllHosts) != 0)
{
arguments.Add("-a");
}
if ((options & LoginOptions.PrintTicket) != 0)
{
arguments.Add("-p");
}
if ((options & LoginOptions.Status) != 0)
{
arguments.Add("-s");
}
if (host != null)
{
arguments.Add($"-h{host}");
}
if (user != null)
{
arguments.Add(user);
}
List parsedResponses;
#pragma warning disable CA1849 // Call async methods when in an async method
await using (IPerforceOutput response = connection.Command("login", arguments, null, null, password, false))
{
for (; ; )
{
if (!await response.ReadAsync(cancellationToken))
{
break;
}
}
DiscardPasswordPrompt(response);
parsedResponses = await response.ReadResponsesAsync(typeof(LoginRecord), cancellationToken);
// end lifetime of `response` here
// this prevents a deadlock in case `connection` is a `NativePerforceConnection` and `response` is a `NativePerforceConnection.Response`
// not DisposeAsync()ing here will cause a deadlock when calling `TryGetLoginStateAsync()` below
}
#pragma warning restore CA1849 // Call async methods when in an async method
PerforceResponse? error = parsedResponses.FirstOrDefault(x => !x.Succeeded);
if (error != null)
{
return new PerforceResponse(error);
}
string? ticket = null;
if ((options & LoginOptions.PrintTicket) != 0)
{
if (parsedResponses.Count != 2)
{
throw new PerforceException("Unable to parse login response; expected two records, one with ticket id");
}
PerforceInfo? info = parsedResponses[0].Info;
if (info == null)
{
throw new PerforceException("Unable to parse login response; expected two records, one with ticket id");
}
ticket = info.Data;
parsedResponses.RemoveAt(0);
}
LoginRecord? loginRecord = parsedResponses.First().Data as LoginRecord;
if (loginRecord == null)
{
// Older versions of P4.EXE do not return a login record for succesful login, instread just returning a string. Call p4 login -s to get the login state instead.
PerforceResponse legacyResponse = await TryGetLoginStateAsync(connection, cancellationToken);
if (!legacyResponse.Succeeded)
{
return legacyResponse;
}
loginRecord = legacyResponse.Data;
}
loginRecord.Ticket = ticket;
return new PerforceResponse(loginRecord);
}
static void DiscardPasswordPrompt(IPerforceOutput output)
{
ReadOnlySpan data = output.Data.Span;
for (int idx = 0; idx < data.Length; idx++)
{
if (data[idx] == '{')
{
output.Discard(idx);
break;
}
}
}
///
/// Gets the state of the current user's login
///
/// Connection to the Perforce server
/// Token used to cancel the operation
///
public static async Task GetLoginStateAsync(this IPerforceConnection connection, CancellationToken cancellationToken = default)
{
return (await TryGetLoginStateAsync(connection, cancellationToken)).Data;
}
///
/// Gets the state of the current user's login
///
/// Connection to the Perforce server
/// Token used to cancel the operation
///
public static async Task> TryGetLoginStateAsync(this IPerforceConnection connection, CancellationToken cancellationToken = default)
{
return await SingleResponseCommandAsync(connection, "login", new List { "-s" }, null, cancellationToken);
}
#endregion
#region p4 merge
///
/// Execute the 'merge' command
///
/// Connection to the Perforce server
/// Options for the merge
///
/// Maximum number of files to merge
/// The source filespec and revision range
/// The target filespec
/// Cancellation token
/// List of records
public static async Task> MergeAsync(this IPerforceConnection connection, MergeOptions options, int change, int maxFiles, string sourceFileSpec, string targetFileSpec, CancellationToken cancellationToken = default)
{
List arguments = new List();
if ((options & MergeOptions.Preview) != 0)
{
arguments.Add($"-n");
}
if (change != -1)
{
arguments.Add($"-c{change}");
}
if (maxFiles != -1)
{
arguments.Add($"-m{maxFiles}");
}
if ((options & MergeOptions.Force) != 0)
{
arguments.Add("-F");
}
if ((options & MergeOptions.ReverseMapping) != 0)
{
arguments.Add("-r");
}
if ((options & MergeOptions.AsStreamSpec) != 0)
{
arguments.Add("-As");
}
if ((options & MergeOptions.AsFiles) != 0)
{
arguments.Add("-Af");
}
if ((options & MergeOptions.Stream) != 0)
{
arguments.Add("-S");
}
arguments.Add(sourceFileSpec);
if ((options & MergeOptions.Source) != 0)
{
arguments.Add("-s");
}
arguments.Add(targetFileSpec);
PerforceResponseList records = await CommandAsync(connection, "merge", arguments, null, cancellationToken);
records.RemoveAll(x => x.Error != null && x.Error.Generic == PerforceGenericCode.Empty);
return records;
}
#endregion
#region p4 move
///
/// Opens files for move
///
/// Connection to the Perforce server
/// Changelist to add files to
/// Type for new files
/// Options for the command
/// The source file(s)
/// The target file(s)
/// Token used to cancel the operation
/// Response from the server
public static async Task> MoveAsync(this IPerforceConnection connection, int changeNumber, string? fileType, MoveOptions options, string sourceFileSpec, string targetFileSpec, CancellationToken cancellationToken = default)
{
return (await TryMoveAsync(connection, changeNumber, fileType, options, sourceFileSpec, targetFileSpec, cancellationToken)).Data;
}
///
/// Opens files for move
///
/// Connection to the Perforce server
/// Changelist to add files to
/// Type for new files
/// Options for the command
/// The source file(s)
/// The target file(s)
/// Token used to cancel the operation
/// Response from the server
public static async Task> TryMoveAsync(this IPerforceConnection connection, int changeNumber, string? fileType, MoveOptions options, string sourceFileSpec, string targetFileSpec, CancellationToken cancellationToken = default)
{
List arguments = new List();
if (changeNumber != -1)
{
arguments.Add($"-c{changeNumber}");
}
if ((options & MoveOptions.KeepWorkspaceFiles) != 0)
{
arguments.Add("-k");
}
if ((options & MoveOptions.RenameOnly) != 0)
{
arguments.Add("-r");
}
if ((options & MoveOptions.PreviewOnly) != 0)
{
arguments.Add("-n");
}
if (fileType != null)
{
arguments.Add($"-t{fileType}");
}
arguments.Add(sourceFileSpec);
arguments.Add(targetFileSpec);
PerforceResponseList