2019-01-02 00:55:51 -05:00
// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.
2018-06-04 09:25:26 -04:00
using System ;
using System.Collections.Concurrent ;
using System.Collections.Generic ;
using System.Diagnostics ;
using System.IO ;
using System.Linq ;
using System.Reflection ;
using System.Text ;
using System.Threading.Tasks ;
namespace Tools.DotNETCommon.Perforce
{
/// <summary>
/// Stores settings for communicating with a Perforce server.
/// </summary>
public class PerforceConnection
{
2018-08-02 11:17:11 -04:00
/// <summary>
/// Constant for the default changelist, where valid.
/// </summary>
public const int DefaultChange = - 2 ;
2018-06-04 09:25:26 -04:00
#region Plumbing
/// <summary>
/// Stores cached information about a field with a P4Tag attribute
/// </summary>
class CachedTagInfo
{
/// <summary>
/// Name of the tag. Specified in the attribute or inferred from the field name.
/// </summary>
public string Name ;
/// <summary>
/// Whether this tag is optional or not.
/// </summary>
public bool Optional ;
/// <summary>
/// The field containing the value of this data.
/// </summary>
public FieldInfo Field ;
/// <summary>
/// Index into the bitmask of required types
/// </summary>
public ulong RequiredTagBitMask ;
}
/// <summary>
/// Stores cached information about a record
/// </summary>
class CachedRecordInfo
{
/// <summary>
/// Type of the record
/// </summary>
public Type Type ;
/// <summary>
/// Map of tag names to their cached reflection information
/// </summary>
public Dictionary < string , CachedTagInfo > TagNameToInfo = new Dictionary < string , CachedTagInfo > ( ) ;
/// <summary>
/// Bitmask of all the required tags. Formed by bitwise-or'ing the RequiredTagBitMask fields for each required CachedTagInfo.
/// </summary>
public ulong RequiredTagsBitMask ;
/// <summary>
/// The type of records to create for subelements
/// </summary>
public Type SubElementType ;
/// <summary>
/// The cached record info for the subelement type
/// </summary>
public CachedRecordInfo SubElementRecordInfo ;
/// <summary>
/// Field containing subelements
/// </summary>
public FieldInfo SubElementField ;
}
/// <summary>
/// Unix epoch; used for converting times back into C# datetime objects
/// </summary>
static readonly DateTime UnixEpoch = new DateTime ( 1970 , 1 , 1 , 0 , 0 , 0 , DateTimeKind . Utc ) ;
/// <summary>
/// Cached map of enum types to a lookup mapping from p4 strings to enum values.
/// </summary>
static ConcurrentDictionary < Type , Dictionary < string , int > > EnumTypeToFlags = new ConcurrentDictionary < Type , Dictionary < string , int > > ( ) ;
/// <summary>
/// Cached set of record
/// </summary>
static ConcurrentDictionary < Type , CachedRecordInfo > RecordTypeToInfo = new ConcurrentDictionary < Type , CachedRecordInfo > ( ) ;
/// <summary>
/// Default type for info
/// </summary>
static CachedRecordInfo InfoRecordInfo = GetCachedRecordInfo ( typeof ( PerforceInfo ) ) ;
/// <summary>
/// Default type for errors
/// </summary>
static CachedRecordInfo ErrorRecordInfo = GetCachedRecordInfo ( typeof ( PerforceError ) ) ;
/// <summary>
/// Global options for each command
/// </summary>
public readonly string GlobalOptions ;
/// <summary>
/// Constructor
/// </summary>
/// <param name="GlobalOptions">Global options to pass to every Perforce command</param>
public PerforceConnection ( string GlobalOptions )
{
this . GlobalOptions = GlobalOptions ;
}
/// <summary>
/// Execute a Perforce command and parse the output as marshalled Python objects. This is more robustly defined than the text-based tagged output
/// format, because it avoids ambiguity when returned fields can have newlines.
/// </summary>
/// <param name="CommandLine">Command line to execute Perforce with</param>
/// <param name="InputData">Input data to pass to the Perforce server. May be null.</param>
/// <param name="RecordHandler">Handler for each received record.</param>
public void Command ( string CommandLine , byte [ ] InputData , Action < List < KeyValuePair < string , object > > > RecordHandler )
{
2018-06-18 12:57:28 -04:00
using ( PerforceChildProcess Process = new PerforceChildProcess ( InputData , "{0} {1}" , GlobalOptions , CommandLine ) )
2018-06-04 09:25:26 -04:00
{
List < KeyValuePair < string , object > > Record = new List < KeyValuePair < string , object > > ( ) ;
while ( Process . TryReadRecord ( Record ) )
{
RecordHandler ( Record ) ;
}
}
}
/// <summary>
/// Serializes a list of key/value pairs into binary format.
/// </summary>
/// <param name="KeyValuePairs">List of key value pairs</param>
/// <returns>Serialized record data</returns>
static byte [ ] SerializeRecord ( List < KeyValuePair < string , object > > KeyValuePairs )
{
MemoryStream Stream = new MemoryStream ( ) ;
using ( BinaryWriter Writer = new BinaryWriter ( Stream ) )
{
Writer . Write ( ( byte ) '{' ) ;
foreach ( KeyValuePair < string , object > KeyValuePair in KeyValuePairs )
{
Writer . Write ( 's' ) ;
byte [ ] KeyBytes = Encoding . UTF8 . GetBytes ( KeyValuePair . Key ) ;
Writer . Write ( ( int ) KeyBytes . Length ) ;
Writer . Write ( KeyBytes ) ;
if ( KeyValuePair . Value is string )
{
Writer . Write ( 's' ) ;
byte [ ] ValueBytes = Encoding . UTF8 . GetBytes ( ( string ) KeyValuePair . Value ) ;
Writer . Write ( ( int ) ValueBytes . Length ) ;
Writer . Write ( ValueBytes ) ;
}
else
{
throw new PerforceException ( "Unsupported formatting type for {0}" , KeyValuePair . Key ) ;
}
}
Writer . Write ( ( byte ) '0' ) ;
}
return Stream . ToArray ( ) ;
}
/// <summary>
/// Execute a command and parse the response
/// </summary>
/// <param name="Arguments">Arguments for the command</param>
/// <param name="InputData">Input data to pass to Perforce</param>
/// <param name="StatRecordType">The type of records to return for "stat" responses</param>
/// <returns>List of objects returned by the server</returns>
public List < PerforceResponse > Command ( string Arguments , byte [ ] InputData , Type StatRecordType )
{
CachedRecordInfo StatRecordInfo = ( StatRecordType = = null ) ? null : GetCachedRecordInfo ( StatRecordType ) ;
List < PerforceResponse > Responses = new List < PerforceResponse > ( ) ;
Action < List < KeyValuePair < string , object > > > Handler = ( KeyValuePairs ) = >
{
if ( KeyValuePairs . Count = = 0 )
{
throw new PerforceException ( "Unexpected empty record returned by Perforce." ) ;
}
if ( KeyValuePairs [ 0 ] . Key ! = "code" )
{
throw new PerforceException ( "Expected first returned field to be 'code'" ) ;
}
string Code = KeyValuePairs [ 0 ] . Value as string ;
int Idx = 1 ;
if ( Code = = "stat" & & StatRecordType ! = null )
{
Responses . Add ( ParseResponse ( KeyValuePairs , ref Idx , "" , StatRecordInfo ) ) ;
}
else if ( Code = = "info" )
{
Responses . Add ( ParseResponse ( KeyValuePairs , ref Idx , "" , InfoRecordInfo ) ) ;
}
else if ( Code = = "error" )
{
Responses . Add ( ParseResponse ( KeyValuePairs , ref Idx , "" , ErrorRecordInfo ) ) ;
}
else
{
throw new PerforceException ( "Unknown return code for record: {0}" , KeyValuePairs [ 0 ] . Value ) ;
}
} ;
Command ( Arguments , InputData , Handler ) ;
return Responses ;
}
/// <summary>
/// Execute a command and parse the response
/// </summary>
/// <param name="Arguments">Arguments for the command</param>
/// <param name="InputData">Input data to pass to Perforce</param>
/// <returns>List of objects returned by the server</returns>
public PerforceResponseList < T > Command < T > ( string Arguments , byte [ ] InputData ) where T : class
{
List < PerforceResponse > Responses = Command ( Arguments , InputData , typeof ( T ) ) ;
PerforceResponseList < T > TypedResponses = new PerforceResponseList < T > ( ) ;
foreach ( PerforceResponse Response in Responses )
{
TypedResponses . Add ( new PerforceResponse < T > ( Response ) ) ;
}
return TypedResponses ;
}
/// <summary>
/// Attempts to execute the given command, returning the results from the server or the first PerforceResponse object.
/// </summary>
/// <param name="Arguments">Arguments for the command.</param>
/// <param name="InputData">Input data for the command.</param>
/// <param name="StatRecordType">Type of element to return in the response</param>
/// <returns>Response from the server; either an object of type T or error.</returns>
public PerforceResponse SingleResponseCommand ( string Arguments , byte [ ] InputData , Type StatRecordType )
{
List < PerforceResponse > Responses = Command ( Arguments , InputData , StatRecordType ) ;
if ( Responses . Count ! = 1 )
{
throw new PerforceException ( "Expected one result from 'p4 {0}', got {1}" , Arguments , Responses . Count ) ;
}
return Responses [ 0 ] ;
}
/// <summary>
/// Attempts to execute the given command, returning the results from the server or the first PerforceResponse object.
/// </summary>
/// <typeparam name="T">Type of record to parse</typeparam>
/// <param name="Arguments">Arguments for the command.</param>
/// <param name="InputData">Input data for the command.</param>
/// <returns>Response from the server; either an object of type T or error.</returns>
public PerforceResponse < T > SingleResponseCommand < T > ( string Arguments , byte [ ] InputData ) where T : class
{
return new PerforceResponse < T > ( SingleResponseCommand ( Arguments , InputData , typeof ( T ) ) ) ;
}
/// <summary>
/// Parse an individual record from the server.
/// </summary>
/// <param name="KeyValuePairs">List of tagged values returned by the server.</param>
/// <param name="Idx">Index of the first tagged value to parse.</param>
/// <param name="RequiredSuffix">The required suffix for any subobject arrays.</param>
/// <param name="RecordInfo">Reflection information for the type being serialized into.</param>
/// <returns>The parsed object.</returns>
PerforceResponse ParseResponse ( List < KeyValuePair < string , object > > KeyValuePairs , ref int Idx , string RequiredSuffix , CachedRecordInfo RecordInfo )
{
// Create a bitmask for all the required tags
ulong RequiredTagsBitMask = 0 ;
// Get the record info, and parse it into the object
object NewRecord = Activator . CreateInstance ( RecordInfo . Type ) ;
while ( Idx < KeyValuePairs . Count )
{
// Split out the tag and value
string Tag = KeyValuePairs [ Idx ] . Key ;
string Value = KeyValuePairs [ Idx ] . Value . ToString ( ) ;
// Parse the suffix from the current key
int SuffixIdx = Tag . Length ;
while ( SuffixIdx > 0 & & ( Tag [ SuffixIdx - 1 ] = = ',' | | ( Tag [ SuffixIdx - 1 ] > = '0' & & Tag [ SuffixIdx - 1 ] < = '9' ) ) )
{
SuffixIdx - - ;
}
// Split out the suffix
string Suffix = Tag . Substring ( SuffixIdx ) ;
Tag = Tag . Substring ( 0 , SuffixIdx ) ;
// Check whether it's a subobject or part of the current object.
if ( Suffix = = RequiredSuffix )
{
// Part of the current object
CachedTagInfo TagInfo ;
if ( RecordInfo . TagNameToInfo . TryGetValue ( Tag , out TagInfo ) )
{
FieldInfo FieldInfo = TagInfo . Field ;
if ( FieldInfo . FieldType = = typeof ( DateTime ) )
{
DateTime Time ;
if ( ! DateTime . TryParse ( Value , out Time ) )
{
Time = UnixEpoch + TimeSpan . FromSeconds ( long . Parse ( Value ) ) ;
}
FieldInfo . SetValue ( NewRecord , Time ) ;
}
else if ( FieldInfo . FieldType = = typeof ( bool ) )
{
FieldInfo . SetValue ( NewRecord , Value . Length = = 0 | | Value = = "true" ) ;
}
else if ( FieldInfo . FieldType = = typeof ( Nullable < bool > ) )
{
FieldInfo . SetValue ( NewRecord , Value = = "true" ) ;
}
else if ( FieldInfo . FieldType = = typeof ( int ) )
{
if ( Value = = "new" | | Value = = "none" )
{
FieldInfo . SetValue ( NewRecord , - 1 ) ;
}
else if ( Value . StartsWith ( "#" ) )
{
FieldInfo . SetValue ( NewRecord , ( Value = = "#none" ) ? 0 : int . Parse ( Value . Substring ( 1 ) ) ) ;
}
2018-08-02 11:17:11 -04:00
else if ( Value = = "default" )
{
FieldInfo . SetValue ( NewRecord , DefaultChange ) ;
}
2018-06-04 09:25:26 -04:00
else
{
FieldInfo . SetValue ( NewRecord , int . Parse ( Value ) ) ;
}
}
else if ( FieldInfo . FieldType = = typeof ( long ) )
{
FieldInfo . SetValue ( NewRecord , long . Parse ( Value ) ) ;
}
else if ( FieldInfo . FieldType = = typeof ( string ) )
{
FieldInfo . SetValue ( NewRecord , Value ) ;
}
else if ( FieldInfo . FieldType . IsEnum )
{
FieldInfo . SetValue ( NewRecord , ParseEnum ( FieldInfo . FieldType , Value ) ) ;
}
else
{
throw new PerforceException ( "Unsupported type of {0}.{1} for tag '{0}'" , RecordInfo . Type . Name , FieldInfo . FieldType . Name , Tag ) ;
}
RequiredTagsBitMask | = TagInfo . RequiredTagBitMask ;
}
Idx + + ;
}
else if ( Suffix . StartsWith ( RequiredSuffix ) & & ( RequiredSuffix . Length = = 0 | | Suffix [ RequiredSuffix . Length ] = = ',' ) )
{
// Part of a subobject. If this record doesn't have any listed subobject type, skip the field and continue.
if ( RecordInfo . SubElementField = = null )
{
CachedTagInfo TagInfo ;
if ( RecordInfo . TagNameToInfo . TryGetValue ( Tag , out TagInfo ) )
{
FieldInfo FieldInfo = TagInfo . Field ;
if ( FieldInfo . FieldType = = typeof ( List < string > ) )
{
( ( List < string > ) FieldInfo . GetValue ( NewRecord ) ) . Add ( Value ) ;
}
else
{
throw new PerforceException ( "Unsupported type of {0}.{1} for tag '{0}'" , RecordInfo . Type . Name , FieldInfo . FieldType . Name , Tag ) ;
}
RequiredTagsBitMask | = TagInfo . RequiredTagBitMask ;
}
Idx + + ;
}
else
{
// Get the expected suffix for the next item based on the number of elements already in the list
System . Collections . IList List = ( System . Collections . IList ) RecordInfo . SubElementField . GetValue ( NewRecord ) ;
string RequiredChildSuffix = ( RequiredSuffix . Length = = 0 ) ? String . Format ( "{0}" , List . Count ) : String . Format ( "{0},{1}" , RequiredSuffix , List . Count ) ;
if ( Suffix ! = RequiredChildSuffix )
{
throw new PerforceException ( "Subobject element received out of order; expected {0}{1}, got {0}{2}" , Tag , RequiredChildSuffix , Suffix ) ;
}
// Parse the subobject and add it to the list
2018-07-26 10:04:07 -04:00
PerforceResponse Response = ParseResponse ( KeyValuePairs , ref Idx , RequiredChildSuffix , RecordInfo . SubElementRecordInfo ) ;
List . Add ( Response . Data ) ;
2018-06-04 09:25:26 -04:00
}
}
else
{
break ;
}
}
// Make sure we've got all the required tags we need
if ( RequiredTagsBitMask ! = RecordInfo . RequiredTagsBitMask )
{
string MissingTagNames = String . Join ( ", " , RecordInfo . TagNameToInfo . Where ( x = > ( RequiredTagsBitMask | x . Value . RequiredTagBitMask ) ! = RequiredTagsBitMask ) . Select ( x = > x . Key ) ) ;
throw new PerforceException ( "Missing '{0}' tag when parsing '{1}'" , MissingTagNames , RecordInfo . Type . Name ) ;
}
return new PerforceResponse ( NewRecord ) ;
}
/// <summary>
/// Gets a mapping of flags to enum values for the given type
/// </summary>
/// <param name="EnumType">The enum type to retrieve flags for</param>
/// <returns>Map of name to enum value</returns>
static Dictionary < string , int > GetCachedEnumFlags ( Type EnumType )
{
Dictionary < string , int > NameToValue ;
if ( ! EnumTypeToFlags . TryGetValue ( EnumType , out NameToValue ) )
{
NameToValue = new Dictionary < string , int > ( ) ;
FieldInfo [ ] Fields = EnumType . GetFields ( BindingFlags . Public | BindingFlags . Static ) ;
foreach ( FieldInfo Field in Fields )
{
PerforceEnumAttribute Attribute = Field . GetCustomAttribute < PerforceEnumAttribute > ( ) ;
if ( Attribute ! = null )
{
NameToValue . Add ( Attribute . Name , ( int ) Field . GetValue ( null ) ) ;
}
}
if ( ! EnumTypeToFlags . TryAdd ( EnumType , NameToValue ) )
{
NameToValue = EnumTypeToFlags [ EnumType ] ;
}
}
return NameToValue ;
}
/// <summary>
/// Parses an enum value, using PerforceEnumAttribute markup for names.
/// </summary>
/// <param name="EnumType">Type of the enum to parse.</param>
/// <param name="Value">Value of the enum.</param>
/// <returns>Text for the enum.</returns>
string GetEnumText ( Type EnumType , object Value )
{
int IntegerValue = ( int ) Value ;
Dictionary < string , int > NameToValue = GetCachedEnumFlags ( EnumType ) ;
if ( EnumType . GetCustomAttribute < FlagsAttribute > ( ) ! = null )
{
List < string > Names = new List < string > ( ) ;
foreach ( KeyValuePair < string , int > Pair in NameToValue )
{
if ( ( IntegerValue & Pair . Value ) ! = 0 )
{
Names . Add ( Pair . Key ) ;
}
}
return String . Join ( " " , Names ) ;
}
else
{
string Name = null ;
foreach ( KeyValuePair < string , int > Pair in NameToValue )
{
if ( IntegerValue = = Pair . Value )
{
Name = Pair . Key ;
break ;
}
}
return Name ;
}
}
/// <summary>
/// Parses an enum value, using PerforceEnumAttribute markup for names.
/// </summary>
/// <param name="EnumType">Type of the enum to parse.</param>
/// <param name="Text">Text to parse.</param>
/// <returns>The parsed enum value. Unknown values will be ignored.</returns>
object ParseEnum ( Type EnumType , string Text )
{
Dictionary < string , int > NameToValue = GetCachedEnumFlags ( EnumType ) ;
if ( EnumType . GetCustomAttribute < FlagsAttribute > ( ) ! = null )
{
int Result = 0 ;
foreach ( string Item in Text . Split ( ' ' ) )
{
int ItemValue ;
if ( NameToValue . TryGetValue ( Item , out ItemValue ) )
{
Result | = ItemValue ;
}
}
return Enum . ToObject ( EnumType , Result ) ;
}
else
{
int Result ;
NameToValue . TryGetValue ( Text , out Result ) ;
return Enum . ToObject ( EnumType , Result ) ;
}
}
/// <summary>
/// Gets reflection data for the given record type
/// </summary>
/// <param name="RecordType">The type to retrieve record info for</param>
/// <returns>The cached reflection information for the given type</returns>
static CachedRecordInfo GetCachedRecordInfo ( Type RecordType )
{
CachedRecordInfo Record ;
if ( ! RecordTypeToInfo . TryGetValue ( RecordType , out Record ) )
{
Record = new CachedRecordInfo ( ) ;
Record . Type = RecordType ;
// Get all the fields for this type
FieldInfo [ ] Fields = RecordType . GetFields ( BindingFlags . Public | BindingFlags . NonPublic | BindingFlags . Instance ) ;
// Build the map of all tags for this record
foreach ( FieldInfo Field in Fields )
{
PerforceTagAttribute TagAttribute = Field . GetCustomAttribute < PerforceTagAttribute > ( ) ;
if ( TagAttribute ! = null )
{
CachedTagInfo Tag = new CachedTagInfo ( ) ;
Tag . Name = TagAttribute . Name ? ? Field . Name ;
Tag . Optional = TagAttribute . Optional ;
Tag . Field = Field ;
if ( ! Tag . Optional )
{
Tag . RequiredTagBitMask = Record . RequiredTagsBitMask + 1 ;
if ( Tag . RequiredTagBitMask = = 0 )
{
throw new PerforceException ( "Too many required tags in {0}; max is {1}" , RecordType . Name , sizeof ( ulong ) * 8 ) ;
}
Record . RequiredTagsBitMask | = Tag . RequiredTagBitMask ;
}
Record . TagNameToInfo . Add ( Tag . Name , Tag ) ;
}
PerforceRecordListAttribute SubElementAttribute = Field . GetCustomAttribute < PerforceRecordListAttribute > ( ) ;
if ( SubElementAttribute ! = null )
{
Record . SubElementField = Field ;
Record . SubElementType = Field . FieldType . GenericTypeArguments [ 0 ] ;
Record . SubElementRecordInfo = GetCachedRecordInfo ( Record . SubElementType ) ;
}
}
// Try to save the record info, or get the version that's already in the cache
if ( ! RecordTypeToInfo . TryAdd ( RecordType , Record ) )
{
Record = RecordTypeToInfo [ RecordType ] ;
}
}
return Record ;
}
#endregion
#region p4 add
/// <summary>
/// Adds files to a pending changelist.
/// </summary>
/// <param name="ChangeNumber">Changelist to add files to</param>
/// <param name="FileNames">Files to be added</param>
/// <returns>Response from the server</returns>
public PerforceResponseList < AddRecord > Add ( int ChangeNumber , params string [ ] FileNames )
{
return Add ( ChangeNumber , null , AddOptions . None , FileNames ) ;
}
/// <summary>
/// Adds files to a pending changelist.
/// </summary>
/// <param name="ChangeNumber">Changelist to add files to</param>
/// <param name="FileType">Type for new files</param>
/// <param name="Options">Options for the command</param>
/// <param name="FileNames">Files to be added</param>
/// <returns>Response from the server</returns>
public PerforceResponseList < AddRecord > Add ( int ChangeNumber , string FileType , AddOptions Options , params string [ ] FileNames )
{
StringBuilder Arguments = new StringBuilder ( "add" ) ;
if ( ChangeNumber ! = - 1 )
{
Arguments . AppendFormat ( " -c {0}" , ChangeNumber ) ;
}
if ( ( Options & AddOptions . DowngradeToAdd ) ! = 0 )
{
Arguments . Append ( " -d" ) ;
}
if ( ( Options & AddOptions . IncludeWildcards ) ! = 0 )
{
Arguments . Append ( " -f" ) ;
}
if ( ( Options & AddOptions . NoIgnore ) ! = 0 )
{
Arguments . Append ( " -I" ) ;
}
if ( ( Options & AddOptions . PreviewOnly ) ! = 0 )
{
Arguments . Append ( " -n" ) ;
}
if ( FileType ! = null )
{
Arguments . AppendFormat ( " -t \"{0}\"" , FileType ) ;
}
foreach ( string FileName in FileNames )
{
Arguments . AppendFormat ( " \"{0}\"" , FileName ) ;
}
return Command < AddRecord > ( Arguments . ToString ( ) , null ) ;
}
#endregion
#region p4 change
/// <summary>
/// Creates a changelist with the p4 change command.
/// </summary>
/// <param name="Record">Information for the change to create. The number field should be left set to -1.</param>
/// <returns>The changelist number, or an error.</returns>
public PerforceResponse < int > CreateChange ( ChangeRecord Record )
{
if ( Record . Number ! = - 1 )
{
throw new PerforceException ( "'Number' field should be set to -1 to create a new changelist." ) ;
}
PerforceResponse Response = SingleResponseCommand ( "change -i" , SerializeRecord ( Record ) , null ) ;
if ( Response . Failed )
{
return new PerforceResponse < int > ( Response . Error ) ;
}
string [ ] Tokens = Response . Info . Data . Split ( ' ' ) ;
if ( Tokens . Length ! = 3 )
{
throw new PerforceException ( "Unexpected info response from change command: {0}" , Response ) ;
}
return new PerforceResponse < int > ( int . Parse ( Tokens [ 1 ] ) ) ;
}
/// <summary>
/// Updates an existing changelist.
/// </summary>
/// <param name="Options">Options for this command</param>
/// <param name="Record">Information for the change to create. The number field should be left set to zero.</param>
/// <returns>The changelist number, or an error.</returns>
public PerforceResponse UpdateChange ( UpdateChangeOptions Options , ChangeRecord Record )
{
if ( Record . Number = = - 1 )
{
throw new PerforceException ( "'Number' field must be set to update a changelist." ) ;
}
StringBuilder Arguments = new StringBuilder ( "change -i" ) ;
if ( ( Options & UpdateChangeOptions . Force ) ! = 0 )
{
Arguments . Append ( " -f" ) ;
}
if ( ( Options & UpdateChangeOptions . Submitted ) ! = 0 )
{
Arguments . Append ( " -u" ) ;
}
return SingleResponseCommand ( Arguments . ToString ( ) , SerializeRecord ( Record ) , null ) ;
}
/// <summary>
/// Deletes a changelist (p4 change -d)
/// </summary>
/// <param name="Options">Options for the command</param>
/// <param name="ChangeNumber">Changelist number to delete</param>
/// <returns>Response from the server</returns>
public PerforceResponse DeleteChange ( DeleteChangeOptions Options , int ChangeNumber )
{
StringBuilder Arguments = new StringBuilder ( "change -d" ) ;
if ( ( Options & DeleteChangeOptions . Submitted ) ! = 0 )
{
Arguments . Append ( " -f" ) ;
}
if ( ( Options & DeleteChangeOptions . BeforeRenumber ) ! = 0 )
{
Arguments . Append ( " -O" ) ;
}
Arguments . AppendFormat ( " {0}" , ChangeNumber ) ;
return SingleResponseCommand ( Arguments . ToString ( ) , null , null ) ;
}
/// <summary>
/// Gets a changelist
/// </summary>
/// <param name="Options">Options for the command</param>
/// <param name="ChangeNumber">Changelist number to retrieve. -1 is the default changelist for this workspace.</param>
/// <returns>Response from the server</returns>
public PerforceResponse < ChangeRecord > GetChange ( GetChangeOptions Options , int ChangeNumber )
{
StringBuilder Arguments = new StringBuilder ( "change -o" ) ;
if ( ( Options & GetChangeOptions . BeforeRenumber ) ! = 0 )
{
Arguments . Append ( " -O" ) ;
}
Arguments . AppendFormat ( " {0}" , ChangeNumber ) ;
return SingleResponseCommand < ChangeRecord > ( Arguments . ToString ( ) , null ) ;
}
byte [ ] SerializeRecord ( ChangeRecord Input )
{
List < KeyValuePair < string , object > > NameToValue = new List < KeyValuePair < string , object > > ( ) ;
if ( Input . Number = = - 1 )
{
NameToValue . Add ( new KeyValuePair < string , object > ( "Change" , "new" ) ) ;
}
else
{
NameToValue . Add ( new KeyValuePair < string , object > ( "Change" , Input . Number . ToString ( ) ) ) ;
}
if ( Input . Type ! = ChangeType . Unspecified )
{
NameToValue . Add ( new KeyValuePair < string , object > ( "Type" , Input . Type . ToString ( ) ) ) ;
}
if ( Input . User ! = null )
{
NameToValue . Add ( new KeyValuePair < string , object > ( "User" , Input . User ) ) ;
}
if ( Input . Client ! = null )
{
NameToValue . Add ( new KeyValuePair < string , object > ( "Client" , Input . Client ) ) ;
}
if ( Input . Description ! = null )
{
NameToValue . Add ( new KeyValuePair < string , object > ( "Description" , Input . Description ) ) ;
}
return SerializeRecord ( NameToValue ) ;
}
#endregion
#region p4 changes
/// <summary>
/// Enumerates changes on the server
/// </summary>
/// <param name="Options">Options for the command</param>
/// <param name="MaxChanges">List only the highest numbered changes</param>
/// <param name="Status">Limit the list to the changelists with the given status (pending, submitted or shelved)</param>
/// <param name="FileSpecs">Paths to query changes for</param>
/// <returns>List of responses from the server.</returns>
public PerforceResponseList < ChangesRecord > Changes ( ChangesOptions Options , int MaxChanges , ChangeStatus Status , params string [ ] FileSpecs )
{
return Changes ( Options , null , MaxChanges , Status , null , FileSpecs ) ;
}
/// <summary>
/// Enumerates changes on the server
/// </summary>
/// <param name="Options">Options for the command</param>
/// <param name="ClientName">List only changes made from the named client workspace.</param>
/// <param name="MaxChanges">List only the highest numbered changes</param>
/// <param name="Status">Limit the list to the changelists with the given status (pending, submitted or shelved)</param>
/// <param name="UserName">List only changes made by the named user</param>
/// <param name="FileSpecs">Paths to query changes for</param>
/// <returns>List of responses from the server.</returns>
public PerforceResponseList < ChangesRecord > Changes ( ChangesOptions Options , string ClientName , int MaxChanges , ChangeStatus Status , string UserName , params string [ ] FileSpecs )
{
StringBuilder Arguments = new StringBuilder ( "changes" ) ;
if ( ( Options & ChangesOptions . IncludeIntegrations ) ! = 0 )
{
Arguments . Append ( " -i" ) ;
}
if ( ( Options & ChangesOptions . IncludeTimes ) ! = 0 )
{
Arguments . Append ( " -t" ) ;
}
if ( ( Options & ChangesOptions . LongOutput ) ! = 0 )
{
Arguments . Append ( " -l" ) ;
}
if ( ( Options & ChangesOptions . TruncatedLongOutput ) ! = 0 )
{
Arguments . Append ( " -L" ) ;
}
if ( ( Options & ChangesOptions . IncludeRestricted ) ! = 0 )
{
Arguments . Append ( " -f" ) ;
}
if ( ClientName ! = null )
{
Arguments . AppendFormat ( " -c \"{0}\"" , ClientName ) ;
}
if ( MaxChanges ! = - 1 )
{
Arguments . AppendFormat ( " -m {0}" , MaxChanges ) ;
}
if ( Status ! = ChangeStatus . All )
{
Arguments . AppendFormat ( " -s {0}" , GetEnumText ( typeof ( ChangeStatus ) , Status ) ) ;
}
2018-08-02 11:17:11 -04:00
if ( UserName ! = null )
2018-06-04 09:25:26 -04:00
{
Arguments . AppendFormat ( " -u {0}" , UserName ) ;
}
foreach ( string FileSpec in FileSpecs )
{
Arguments . AppendFormat ( " \"{0}\"" , FileSpec ) ;
}
return Command < ChangesRecord > ( Arguments . ToString ( ) , null ) ;
}
#endregion
#region p4 client
/// <summary>
/// Creates a client
/// </summary>
/// <param name="Record">The client record</param>
/// <returns>Response from the server</returns>
public PerforceResponse CreateClient ( ClientRecord Record )
{
return UpdateClient ( Record ) ;
}
/// <summary>
/// Creates a client
/// </summary>
/// <param name="Record">The client record</param>
/// <returns>Response from the server</returns>
public PerforceResponse UpdateClient ( ClientRecord Record )
{
return SingleResponseCommand ( "client -i" , SerializeRecord ( Record ) , null ) ;
}
/// <summary>
/// Deletes a client
/// </summary>
/// <param name="Options">Options for this command</param>
/// <param name="ClientName">Name of the client</param>
/// <returns>Response from the server</returns>
public PerforceResponse DeleteClient ( DeleteClientOptions Options , string ClientName )
{
StringBuilder Arguments = new StringBuilder ( "client -d" ) ;
if ( ( Options & DeleteClientOptions . Force ) ! = 0 )
{
Arguments . Append ( " -f" ) ;
}
if ( ( Options & DeleteClientOptions . DeleteShelved ) ! = 0 )
{
Arguments . Append ( " -Fs" ) ;
}
Arguments . AppendFormat ( " -d \"{0}\"" , ClientName ) ;
return SingleResponseCommand < ClientRecord > ( Arguments . ToString ( ) , null ) ;
}
/// <summary>
/// Changes the stream associated with a client
/// </summary>
/// <param name="ClientName">The client name</param>
/// <param name="StreamName">The new stream to be associated with the client</param>
/// <param name="Options">Options for this command</param>
/// <returns>Response from the server</returns>
public PerforceResponse SwitchClientToStream ( string ClientName , string StreamName , SwitchClientOptions Options )
{
StringBuilder Arguments = new StringBuilder ( "client -s" ) ;
if ( ( Options & SwitchClientOptions . IgnoreOpenFiles ) ! = 0 )
{
Arguments . Append ( " -f" ) ;
}
Arguments . AppendFormat ( " -S \"{0}\"" , StreamName ) ;
return SingleResponseCommand ( Arguments . ToString ( ) , null , null ) ;
}
/// <summary>
/// Changes a client to mirror a template
/// </summary>
/// <param name="ClientName">The client name</param>
/// <param name="TemplateName">The new stream to be associated with the client</param>
/// <returns>Response from the server</returns>
public PerforceResponse SwitchClientToTemplate ( string ClientName , string TemplateName )
{
string Arguments = String . Format ( "client -s -t \"{0}\"" , TemplateName ) ;
return SingleResponseCommand ( Arguments , null , null ) ;
}
/// <summary>
/// Queries the current client definition
/// </summary>
/// <param name="ClientName">Name of the client. Specify null for the current client.</param>
/// <returns>Response from the server; either a client record or error code</returns>
public PerforceResponse < ClientRecord > GetClient ( string ClientName )
{
StringBuilder Arguments = new StringBuilder ( "client -o" ) ;
if ( ClientName ! = null )
{
Arguments . AppendFormat ( " \"{0}\"" , ClientName ) ;
}
return SingleResponseCommand < ClientRecord > ( Arguments . ToString ( ) , null ) ;
}
/// <summary>
/// Queries the view for a stream
/// </summary>
/// <param name="StreamName">Name of the stream.</param>
/// <param name="ChangeNumber">Changelist at which to query the stream view</param>
/// <returns>Response from the server; either a client record or error code</returns>
public PerforceResponse < ClientRecord > GetStreamView ( string StreamName , int ChangeNumber )
{
StringBuilder Arguments = new StringBuilder ( "client -o" ) ;
Arguments . AppendFormat ( " -S \"{0}\"" , StreamName ) ;
if ( ChangeNumber ! = - 1 )
{
Arguments . AppendFormat ( " -c {0}" , ChangeNumber ) ;
}
return SingleResponseCommand < ClientRecord > ( Arguments . ToString ( ) , null ) ;
}
/// <summary>
/// Serializes a client record to a byte array
/// </summary>
/// <param name="Input">The input record</param>
/// <returns>Serialized record data</returns>
byte [ ] SerializeRecord ( ClientRecord Input )
{
List < KeyValuePair < string , object > > NameToValue = new List < KeyValuePair < string , object > > ( ) ;
if ( Input . Name ! = null )
{
NameToValue . Add ( new KeyValuePair < string , object > ( "Client" , Input . Name ) ) ;
}
if ( Input . Owner ! = null )
{
NameToValue . Add ( new KeyValuePair < string , object > ( "Owner" , Input . Owner ) ) ;
}
if ( Input . Host ! = null )
{
NameToValue . Add ( new KeyValuePair < string , object > ( "Host" , Input . Host ) ) ;
}
if ( Input . Description ! = null )
{
NameToValue . Add ( new KeyValuePair < string , object > ( "Description" , Input . Description ) ) ;
}
if ( Input . Root ! = null )
{
NameToValue . Add ( new KeyValuePair < string , object > ( "Root" , Input . Root ) ) ;
}
if ( Input . Options ! = ClientOptions . None )
{
NameToValue . Add ( new KeyValuePair < string , object > ( "Options" , GetEnumText ( typeof ( ClientOptions ) , Input . Options ) ) ) ;
}
if ( Input . SubmitOptions ! = ClientSubmitOptions . Unspecified )
{
NameToValue . Add ( new KeyValuePair < string , object > ( "SubmitOptions" , GetEnumText ( typeof ( ClientSubmitOptions ) , Input . SubmitOptions ) ) ) ;
}
if ( Input . LineEnd ! = ClientLineEndings . Unspecified )
{
NameToValue . Add ( new KeyValuePair < string , object > ( "LineEnd" , GetEnumText ( typeof ( ClientLineEndings ) , Input . LineEnd ) ) ) ;
}
2018-06-25 07:45:37 -04:00
if ( Input . Type ! = null )
{
NameToValue . Add ( new KeyValuePair < string , object > ( "Type" , Input . Type ) ) ;
}
2018-06-04 09:25:26 -04:00
if ( Input . Stream ! = null )
{
NameToValue . Add ( new KeyValuePair < string , object > ( "Stream" , Input . Stream ) ) ;
}
for ( int Idx = 0 ; Idx < Input . View . Count ; Idx + + )
{
NameToValue . Add ( new KeyValuePair < string , object > ( String . Format ( "View{0}" , Idx ) , Input . View [ Idx ] ) ) ;
}
return SerializeRecord ( NameToValue ) ;
}
#endregion
#region p4 clients
/// <summary>
/// Queries the current client definition
/// </summary>
/// <param name="Options">Options for this command</param>
/// <param name="UserName">List only client workspaces owned by this user.</param>
/// <returns>Response from the server; either a client record or error code</returns>
public PerforceResponseList < ClientsRecord > Clients ( ClientsOptions Options , string UserName )
{
return Clients ( Options , null , - 1 , null , UserName ) ;
}
/// <summary>
/// Queries the current client definition
/// </summary>
/// <param name="Options">Options for this command</param>
/// <param name="Filter">List only client workspaces matching filter. Treated as case sensitive if <ref>ClientsOptions.CaseSensitiveFilter</ref> is set.</param>
/// <param name="MaxResults">Limit the number of results to return. -1 for all.</param>
/// <param name="Stream">List client workspaces associated with the specified stream.</param>
/// <param name="UserName">List only client workspaces owned by this user.</param>
/// <returns>Response from the server; either a client record or error code</returns>
public PerforceResponseList < ClientsRecord > Clients ( ClientsOptions Options , string Filter , int MaxResults , string Stream , string UserName )
{
StringBuilder Arguments = new StringBuilder ( "clients" ) ;
if ( ( Options & ClientsOptions . All ) ! = 0 )
{
Arguments . Append ( " -a" ) ;
}
if ( Filter ! = null )
{
if ( ( Options & ClientsOptions . CaseSensitiveFilter ) ! = 0 )
{
Arguments . AppendFormat ( " -e \"{0}\"" , Filter ) ;
}
else
{
Arguments . AppendFormat ( " -E \"{0}\"" , Filter ) ;
}
}
if ( MaxResults ! = - 1 )
{
Arguments . AppendFormat ( " -m {0}" , MaxResults ) ;
}
if ( Stream ! = null )
{
Arguments . AppendFormat ( " -S \"{0}\"" , Stream ) ;
}
if ( ( Options & ClientsOptions . WithTimes ) ! = 0 )
{
Arguments . Append ( " -t" ) ;
}
if ( UserName ! = null )
{
Arguments . AppendFormat ( " -u \"{0}\"" , UserName ) ;
}
if ( ( Options & ClientsOptions . Unloaded ) ! = 0 )
{
Arguments . Append ( " -U" ) ;
}
return Command < ClientsRecord > ( Arguments . ToString ( ) , null ) ;
}
#endregion
2018-07-26 10:04:07 -04:00
#region p4 delete
/// <summary>
/// Execute the 'delete' command
/// </summary>
/// <param name="Options">Options for the command</param>
/// <param name="FileSpecs">List of file specifications to query</param>
/// <returns>List of responses from the server</returns>
public PerforceResponseList < DeleteRecord > Delete ( int ChangeNumber , DeleteOptions Options , params string [ ] FileSpecs )
{
StringBuilder Arguments = new StringBuilder ( "delete" ) ;
if ( ChangeNumber ! = - 1 )
{
Arguments . AppendFormat ( " -c {0}" , ChangeNumber ) ;
}
if ( ( Options & DeleteOptions . PreviewOnly ) ! = 0 )
{
Arguments . Append ( " -n" ) ;
}
if ( ( Options & DeleteOptions . KeepWorkspaceFiles ) ! = 0 )
{
Arguments . Append ( " -k" ) ;
}
if ( ( Options & DeleteOptions . WithoutSyncing ) ! = 0 )
{
Arguments . Append ( " -v" ) ;
}
foreach ( string FileSpec in FileSpecs )
{
Arguments . AppendFormat ( " \"{0}\"" , FileSpec ) ;
}
return Command < DeleteRecord > ( Arguments . ToString ( ) , null ) ;
}
#endregion
2018-06-04 09:25:26 -04:00
#region p4 describe
/// <summary>
/// Describes a single changelist
/// </summary>
/// <param name="ChangeNumber">The changelist number to retrieve description for</param>
/// <returns>Response from the server; either a describe record or error code</returns>
public PerforceResponse < DescribeRecord > Describe ( int ChangeNumber )
{
PerforceResponseList < DescribeRecord > Records = Describe ( new int [ ] { ChangeNumber } ) ;
if ( Records . Count ! = 1 )
{
throw new PerforceException ( "Expected only one record returned from p4 describe command, got {0}" , Records . Count ) ;
}
return Records [ 0 ] ;
}
/// <summary>
/// Describes a set of changelists
/// </summary>
/// <param name="ChangeNumbers">The changelist numbers to retrieve descriptions for</param>
/// <returns>List of responses from the server</returns>
public PerforceResponseList < DescribeRecord > Describe ( params int [ ] ChangeNumbers )
{
StringBuilder Arguments = new StringBuilder ( "describe -s" ) ;
foreach ( int ChangeNumber in ChangeNumbers )
{
Arguments . AppendFormat ( " {0}" , ChangeNumber ) ;
}
return Command < DescribeRecord > ( Arguments . ToString ( ) , null ) ;
}
2018-08-02 11:17:11 -04:00
/// <summary>
/// Describes a set of changelists
/// </summary>
/// <param name="ChangeNumbers">The changelist numbers to retrieve descriptions for</param>
/// <returns>List of responses from the server</returns>
public PerforceResponseList < DescribeRecord > Describe ( DescribeOptions Options , int MaxResults , params int [ ] ChangeNumbers )
{
StringBuilder Arguments = new StringBuilder ( "describe -s" ) ;
if ( ( Options & DescribeOptions . ShowDescriptionForRestrictedChanges ) ! = 0 )
{
Arguments . Append ( " -f" ) ;
}
if ( ( Options & DescribeOptions . Identity ) ! = 0 )
{
Arguments . Append ( " -I" ) ;
}
if ( MaxResults ! = - 1 )
{
Arguments . AppendFormat ( " -m{0}" , MaxResults ) ;
}
if ( ( Options & DescribeOptions . OriginalChangeNumber ) ! = 0 )
{
Arguments . Append ( " -O" ) ;
}
if ( ( Options & DescribeOptions . Shelved ) ! = 0 )
{
Arguments . Append ( " -S" ) ;
}
foreach ( int ChangeNumber in ChangeNumbers )
{
Arguments . AppendFormat ( " {0}" , ChangeNumber ) ;
}
return Command < DescribeRecord > ( Arguments . ToString ( ) , null ) ;
}
2018-06-04 09:25:26 -04:00
#endregion
#region p4 edit
/// <summary>
/// Opens files for edit
/// </summary>
/// <param name="ChangeNumber">Changelist to add files to</param>
/// <param name="FileSpecs">Files to be opened for edit</param>
/// <returns>Response from the server</returns>
public PerforceResponseList < EditRecord > Edit ( int ChangeNumber , params string [ ] FileSpecs )
{
return Edit ( ChangeNumber , null , EditOptions . None , FileSpecs ) ;
}
/// <summary>
/// Opens files for edit
/// </summary>
/// <param name="ChangeNumber">Changelist to add files to</param>
/// <param name="FileType">Type for new files</param>
/// <param name="Options">Options for the command</param>
/// <param name="FileSpecs">Files to be opened for edit</param>
/// <returns>Response from the server</returns>
public PerforceResponseList < EditRecord > Edit ( int ChangeNumber , string FileType , EditOptions Options , params string [ ] FileSpecs )
{
StringBuilder Arguments = new StringBuilder ( "edit" ) ;
if ( ChangeNumber ! = - 1 )
{
Arguments . AppendFormat ( " -c {0}" , ChangeNumber ) ;
}
if ( ( Options & EditOptions . KeepWorkspaceFiles ) ! = 0 )
{
Arguments . Append ( " -k" ) ;
}
if ( ( Options & EditOptions . PreviewOnly ) ! = 0 )
{
Arguments . Append ( " -n" ) ;
}
if ( FileType ! = null )
{
Arguments . AppendFormat ( " -t \"{0}\"" , FileType ) ;
}
foreach ( string FileSpec in FileSpecs )
{
Arguments . AppendFormat ( " \"{0}\"" , FileSpec ) ;
}
return Command < EditRecord > ( Arguments . ToString ( ) , null ) ;
}
#endregion
#region p4 filelog
/// <summary>
/// Execute the 'filelog' command
/// </summary>
/// <param name="Options">Options for the command</param>
/// <param name="FileSpecs">List of file specifications to query</param>
/// <returns>List of responses from the server</returns>
public PerforceResponseList < FileLogRecord > FileLog ( FileLogOptions Options , params string [ ] FileSpecs )
{
return FileLog ( - 1 , - 1 , Options , FileSpecs ) ;
}
/// <summary>
/// Execute the 'filelog' command
/// </summary>
/// <param name="MaxChanges">Number of changelists to show. Ignored if zero or negative.</param>
/// <param name="Options">Options for the command</param>
/// <param name="FileSpecs">List of file specifications to query</param>
/// <returns>List of responses from the server</returns>
public PerforceResponseList < FileLogRecord > FileLog ( int MaxChanges , FileLogOptions Options , params string [ ] FileSpecs )
{
return FileLog ( - 1 , MaxChanges , Options , FileSpecs ) ;
}
/// <summary>
/// Execute the 'filelog' command
/// </summary>
/// <param name="ChangeNumber">Show only files modified by this changelist. Ignored if zero or negative.</param>
/// <param name="MaxChanges">Number of changelists to show. Ignored if zero or negative.</param>
/// <param name="Options">Options for the command</param>
/// <param name="FileSpecs">List of file specifications to query</param>
/// <returns>List of responses from the server</returns>
public PerforceResponseList < FileLogRecord > FileLog ( int ChangeNumber , int MaxChanges , FileLogOptions Options , params string [ ] FileSpecs )
{
// Build the argument list
StringBuilder Arguments = new StringBuilder ( "filelog" ) ;
if ( ChangeNumber > 0 )
{
Arguments . AppendFormat ( " -c {0}" , ChangeNumber ) ;
}
if ( ( Options & FileLogOptions . ContentHistory ) ! = 0 )
{
Arguments . Append ( " -h" ) ;
}
if ( ( Options & FileLogOptions . FollowAcrossBranches ) ! = 0 )
{
Arguments . Append ( " -i" ) ;
}
if ( ( Options & FileLogOptions . FullDescriptions ) ! = 0 )
{
Arguments . Append ( " -l" ) ;
}
if ( ( Options & FileLogOptions . LongDescriptions ) ! = 0 )
{
Arguments . Append ( " -L" ) ;
}
if ( MaxChanges > 0 )
{
Arguments . AppendFormat ( " -m {0}" , MaxChanges ) ;
}
if ( ( Options & FileLogOptions . DoNotFollowPromotedTaskStreams ) ! = 0 )
{
Arguments . Append ( " -p" ) ;
}
if ( ( Options & FileLogOptions . IgnoreNonContributoryIntegrations ) ! = 0 )
{
Arguments . Append ( " -s" ) ;
}
// Always include times to simplify parsing
Arguments . Append ( " -t" ) ;
// Add the file arguments
foreach ( string FileSpec in FileSpecs )
{
Arguments . AppendFormat ( " \"{0}\"" , FileSpec ) ;
}
// Execute the command
return Command < FileLogRecord > ( Arguments . ToString ( ) , null ) ;
}
#endregion
#region p4 fstat
/// <summary>
/// Execute the 'fstat' command
/// </summary>
/// <param name="Options">Options for the command</param>
/// <param name="FileSpecs">List of file specifications to query</param>
/// <returns>List of responses from the server</returns>
public PerforceResponseList < FStatRecord > FStat ( FStatOptions Options , params string [ ] FileSpecs )
{
return FStat ( - 1 , Options , FileSpecs ) ;
}
/// <summary>
/// Execute the 'fstat' command
/// </summary>
/// <param name="MaxFiles">Produce fstat output for only the first max files.</param>
/// <param name="Options">Options for the command</param>
/// <param name="FileSpecs">List of file specifications to query</param>
/// <returns>List of responses from the server</returns>
public PerforceResponseList < FStatRecord > FStat ( int MaxFiles , FStatOptions Options , params string [ ] FileSpecs )
{
return FStat ( - 1 , - 1 , null , MaxFiles , Options , FileSpecs ) ;
}
/// <summary>
/// Execute the 'fstat' command
/// </summary>
/// <param name="AfterChangeNumber">Return only files affected after the given changelist number.</param>
/// <param name="OnlyChangeNumber">Return only files affected by the given changelist number.</param>
/// <param name="Filter">List only those files that match the criteria specified.</param>
/// <param name="MaxFiles">Produce fstat output for only the first max files.</param>
/// <param name="Options">Options for the command</param>
/// <param name="FileSpecs">List of file specifications to query</param>
/// <returns>List of responses from the server</returns>
public PerforceResponseList < FStatRecord > FStat ( int AfterChangeNumber , int OnlyChangeNumber , string Filter , int MaxFiles , FStatOptions Options , params string [ ] FileSpecs )
{
// Build the argument list
StringBuilder Arguments = new StringBuilder ( "fstat" ) ;
if ( AfterChangeNumber ! = - 1 )
{
Arguments . AppendFormat ( " -c {0}" , AfterChangeNumber ) ;
}
if ( OnlyChangeNumber ! = - 1 )
{
Arguments . AppendFormat ( " -e {0}" , OnlyChangeNumber ) ;
}
if ( Filter ! = null )
{
Arguments . AppendFormat ( " -F \"{0}\"" , Filter ) ;
}
if ( ( Options & FStatOptions . ReportDepotSyntax ) ! = 0 )
{
Arguments . Append ( " -L" ) ;
}
if ( ( Options & FStatOptions . AllRevisions ) ! = 0 )
{
Arguments . Append ( " -Of" ) ;
}
if ( ( Options & FStatOptions . IncludeFileSizes ) ! = 0 )
{
Arguments . Append ( " -Ol" ) ;
}
if ( ( Options & FStatOptions . ClientFileInPerforceSyntax ) ! = 0 )
{
Arguments . Append ( " -Op" ) ;
}
if ( ( Options & FStatOptions . ShowPendingIntegrations ) ! = 0 )
{
Arguments . Append ( " -Or" ) ;
}
if ( ( Options & FStatOptions . ShortenOutput ) ! = 0 )
{
Arguments . Append ( " -Os" ) ;
}
if ( ( Options & FStatOptions . ReverseOrder ) ! = 0 )
{
Arguments . Append ( " -r" ) ;
}
if ( ( Options & FStatOptions . OnlyMapped ) ! = 0 )
{
Arguments . Append ( " -Rc" ) ;
}
if ( ( Options & FStatOptions . OnlyHave ) ! = 0 )
{
Arguments . Append ( " -Rh" ) ;
}
if ( ( Options & FStatOptions . OnlyOpenedBeforeHead ) ! = 0 )
{
Arguments . Append ( " -Rn" ) ;
}
if ( ( Options & FStatOptions . OnlyOpenInWorkspace ) ! = 0 )
{
Arguments . Append ( " -Ro" ) ;
}
if ( ( Options & FStatOptions . OnlyOpenAndResolved ) ! = 0 )
{
Arguments . Append ( " -Rr" ) ;
}
if ( ( Options & FStatOptions . OnlyShelved ) ! = 0 )
{
Arguments . Append ( " -Rs" ) ;
}
if ( ( Options & FStatOptions . OnlyUnresolved ) ! = 0 )
{
Arguments . Append ( " -Ru" ) ;
}
if ( ( Options & FStatOptions . SortByDate ) ! = 0 )
{
Arguments . Append ( " -Sd" ) ;
}
if ( ( Options & FStatOptions . SortByHaveRevision ) ! = 0 )
{
Arguments . Append ( " -Sh" ) ;
}
if ( ( Options & FStatOptions . SortByHeadRevision ) ! = 0 )
{
Arguments . Append ( " -Sr" ) ;
}
if ( ( Options & FStatOptions . SortByFileSize ) ! = 0 )
{
Arguments . Append ( " -Ss" ) ;
}
if ( ( Options & FStatOptions . SortByFileType ) ! = 0 )
{
Arguments . Append ( " -St" ) ;
}
if ( ( Options & FStatOptions . IncludeFilesInUnloadDepot ) ! = 0 )
{
Arguments . Append ( " -U" ) ;
}
// Add the file arguments
foreach ( string FileSpec in FileSpecs )
{
Arguments . AppendFormat ( " \"{0}\"" , FileSpec ) ;
}
// Execute the command
return Command < FStatRecord > ( Arguments . ToString ( ) , null ) ;
}
#endregion
#region p4 info
/// <summary>
/// Execute the 'info' command
/// </summary>
/// <param name="Options">Options for the command</param>
/// <returns>Response from the server; an InfoRecord or error code</returns>
public PerforceResponse < InfoRecord > Info ( InfoOptions Options )
{
// Build the argument list
StringBuilder Arguments = new StringBuilder ( "info" ) ;
if ( ( Options & InfoOptions . ShortOutput ) ! = 0 )
{
Arguments . Append ( " -s" ) ;
}
return SingleResponseCommand < InfoRecord > ( Arguments . ToString ( ) , null ) ;
}
#endregion
2018-08-02 11:17:11 -04:00
#region p4 opened
/// <summary>
/// Execute the 'opened' command
/// </summary>
/// <param name="Options">Options for the command</param>
/// <param name="ChangeNumber">List the files in pending changelist change. To list files in the default changelist, use DefaultChange.</param>
/// <param name="ClientName">List only files that are open in the given client</param>
/// <param name="UserName">List only files that are opened by the given user</param>
/// <param name="MaxResults">Maximum number of results to return</param>
/// <param name="FileSpecs">Specification for the files to list</param>
/// <returns>Response from the server</returns>
public PerforceResponseList < FStatRecord > Opened ( OpenedOptions Options , int ChangeNumber , string ClientName , string UserName , int MaxResults , params string [ ] FileSpecs )
{
// Build the argument list
StringBuilder Arguments = new StringBuilder ( "opened" ) ;
if ( ( Options & OpenedOptions . AllWorkspaces ) ! = 0 )
{
Arguments . AppendFormat ( " -a" ) ;
}
if ( ChangeNumber ! = - 1 )
{
Arguments . AppendFormat ( " -c {0}" , ChangeNumber ) ;
}
if ( ClientName ! = null )
{
Arguments . AppendFormat ( " -C \"{0}\"" , ClientName ) ;
}
if ( UserName ! = null )
{
Arguments . AppendFormat ( " -u \"{0}\"" , UserName ) ;
}
if ( MaxResults = = DefaultChange )
{
Arguments . AppendFormat ( " -m default" ) ;
}
else if ( MaxResults ! = - 1 )
{
Arguments . AppendFormat ( " -m {0}" , MaxResults ) ;
}
if ( ( Options & OpenedOptions . ShortOutput ) ! = 0 )
{
Arguments . AppendFormat ( " -s" ) ;
}
foreach ( string FileSpec in FileSpecs )
{
Arguments . AppendFormat ( " \"{0}\"" , FileSpec ) ;
}
return Command < FStatRecord > ( Arguments . ToString ( ) , null ) ;
}
#endregion
2018-06-04 09:25:26 -04:00
#region p4 print
/// <summary>
/// Execute the 'print' command
/// </summary>
/// <param name="OutputFile">Output file to redirect output to</param>
/// <param name="FileSpec">Specification for the files to print</param>
/// <returns>Response from the server</returns>
public PerforceResponse < PrintRecord > Print ( string OutputFile , string FileSpec )
{
// Build the argument list
StringBuilder Arguments = new StringBuilder ( "print" ) ;
Arguments . AppendFormat ( " -o \"{0}\"" , OutputFile ) ;
Arguments . AppendFormat ( " \"{0}\"" , FileSpec ) ;
return SingleResponseCommand < PrintRecord > ( Arguments . ToString ( ) , null ) ;
}
class PrintHandler : IDisposable
{
Dictionary < string , string > DepotFileToLocalFile ;
FileStream OutputStream ;
public PrintHandler ( Dictionary < string , string > DepotFileToLocalFile )
{
this . DepotFileToLocalFile = DepotFileToLocalFile ;
}
public void Dispose ( )
{
CloseStream ( ) ;
}
private void OpenStream ( string FileName )
{
CloseStream ( ) ;
Directory . CreateDirectory ( Path . GetDirectoryName ( FileName ) ) ;
OutputStream = File . Open ( FileName , FileMode . Create , FileAccess . Write , FileShare . None ) ;
}
private void CloseStream ( )
{
if ( OutputStream ! = null )
{
OutputStream . Dispose ( ) ;
OutputStream = null ;
}
}
public void HandleRecord ( List < KeyValuePair < string , object > > Fields )
{
if ( Fields [ 0 ] . Key ! = "code" )
{
throw new Exception ( "Missing code field" ) ;
}
string Value = ( string ) Fields [ 0 ] . Value ;
if ( Value = = "stat" )
{
string DepotFile = Fields . First ( x = > x . Key = = "depotFile" ) . Value . ToString ( ) ;
string LocalFile ;
if ( ! DepotFileToLocalFile . TryGetValue ( DepotFile , out LocalFile ) )
{
throw new PerforceException ( "Depot file '{0}' not found in input dictionary" , DepotFile ) ;
}
OpenStream ( LocalFile ) ;
}
else if ( Value = = "binary" | | Value = = "text" )
{
byte [ ] Data = ( byte [ ] ) Fields . First ( x = > x . Key = = "data" ) . Value ;
OutputStream . Write ( Data , 0 , Data . Length ) ;
}
else
{
throw new Exception ( "Unexpected record type" ) ;
}
}
}
/// <summary>
/// Execute the 'print' command
/// </summary>
/// <param name="OutputFile">Output file to redirect output to</param>
/// <param name="FileSpec">Specification for the files to print</param>
/// <returns>Response from the server</returns>
public void Print ( Dictionary < string , string > DepotFileToLocalFile )
{
string ListFileName = Path . GetTempFileName ( ) ;
try
{
// Write the list of depot files
File . WriteAllLines ( ListFileName , DepotFileToLocalFile . Keys ) ;
// Execute Perforce, consuming the binary output into a memory stream
using ( PrintHandler Handler = new PrintHandler ( DepotFileToLocalFile ) )
{
Command ( String . Format ( "-x \"{0}\" print" , ListFileName ) , null , Handler . HandleRecord ) ;
}
}
finally
{
File . Delete ( ListFileName ) ;
}
}
#endregion
#region p4 reconcile
/// <summary>
/// Open files for add, delete, and/or edit in order to reconcile a workspace with changes made outside of Perforce.
/// </summary>
/// <param name="ChangeNumber">Changelist to open files to</param>
/// <param name="Options">Options for the command</param>
/// <param name="FileSpecs">Files to be reverted</param>
/// <returns>Response from the server</returns>
public PerforceResponseList < ReconcileRecord > Reconcile ( int ChangeNumber , ReconcileOptions Options , params string [ ] FileSpecs )
{
StringBuilder Arguments = new StringBuilder ( "reconcile" ) ;
if ( ChangeNumber ! = - 1 )
{
Arguments . AppendFormat ( " -c {0}" , ChangeNumber ) ;
}
if ( ( Options & ReconcileOptions . Edit ) ! = 0 )
{
Arguments . Append ( " -e" ) ;
}
if ( ( Options & ReconcileOptions . Add ) ! = 0 )
{
Arguments . Append ( " -a" ) ;
}
if ( ( Options & ReconcileOptions . Delete ) ! = 0 )
{
Arguments . Append ( " -d" ) ;
}
if ( ( Options & ReconcileOptions . PreviewOnly ) ! = 0 )
{
Arguments . Append ( " -n" ) ;
}
if ( ( Options & ReconcileOptions . AllowWildcards ) ! = 0 )
{
Arguments . Append ( " -f" ) ;
}
if ( ( Options & ReconcileOptions . NoIgnore ) ! = 0 )
{
Arguments . Append ( " -I" ) ;
}
if ( ( Options & ReconcileOptions . LocalFileSyntax ) ! = 0 )
{
Arguments . Append ( " -l" ) ;
}
foreach ( string FileSpec in FileSpecs )
{
Arguments . AppendFormat ( " \"{0}\"" , FileSpec ) ;
}
return Command < ReconcileRecord > ( Arguments . ToString ( ) , null ) ;
}
#endregion
#region p4 resolve
/// <summary>
/// Resolve conflicts between file revisions.
/// </summary>
/// <param name="ChangeNumber">Changelist to open files to</param>
/// <param name="Options">Options for the command</param>
/// <param name="FileSpecs">Files to be reverted</param>
/// <returns>Response from the server</returns>
public PerforceResponseList < ResolveRecord > Resolve ( int ChangeNumber , ResolveOptions Options , params string [ ] FileSpecs )
{
StringBuilder Arguments = new StringBuilder ( "resolve" ) ;
if ( ( Options & ResolveOptions . Automatic ) ! = 0 )
{
Arguments . Append ( " -am" ) ;
}
if ( ( Options & ResolveOptions . AcceptYours ) ! = 0 )
{
Arguments . Append ( " -ay" ) ;
}
if ( ( Options & ResolveOptions . AcceptTheirs ) ! = 0 )
{
Arguments . Append ( " -at" ) ;
}
if ( ( Options & ResolveOptions . SafeAccept ) ! = 0 )
{
Arguments . Append ( " -as" ) ;
}
if ( ( Options & ResolveOptions . ForceAccept ) ! = 0 )
{
Arguments . Append ( " -af" ) ;
}
if ( ( Options & ResolveOptions . IgnoreWhitespaceOnly ) ! = 0 )
{
Arguments . Append ( " -db" ) ;
}
if ( ( Options & ResolveOptions . IgnoreWhitespace ) ! = 0 )
{
Arguments . Append ( " -dw" ) ;
}
if ( ( Options & ResolveOptions . IgnoreLineEndings ) ! = 0 )
{
Arguments . Append ( " -dl" ) ;
}
if ( ( Options & ResolveOptions . ResolveAgain ) ! = 0 )
{
Arguments . Append ( " -f" ) ;
}
if ( ( Options & ResolveOptions . PreviewOnly ) ! = 0 )
{
Arguments . Append ( " -n" ) ;
}
foreach ( string FileSpec in FileSpecs )
{
Arguments . AppendFormat ( " \"{0}\"" , FileSpec ) ;
}
return Command < ResolveRecord > ( Arguments . ToString ( ) , null ) ;
}
#endregion
#region p4 revert
/// <summary>
/// Reverts files that have been added to a pending changelist.
/// </summary>
/// <param name="ChangeNumber">Changelist to add files to</param>
/// <param name="ClientName">Revert another user’ s open files. </param>
/// <param name="Options">Options for the command</param>
/// <param name="FileSpecs">Files to be reverted</param>
/// <returns>Response from the server</returns>
public PerforceResponseList < RevertRecord > Revert ( int ChangeNumber , string ClientName , RevertOptions Options , params string [ ] FileSpecs )
{
StringBuilder Arguments = new StringBuilder ( "revert" ) ;
if ( ( Options & RevertOptions . Unchanged ) ! = 0 )
{
Arguments . Append ( " -a" ) ;
}
if ( ( Options & RevertOptions . PreviewOnly ) ! = 0 )
{
Arguments . Append ( " -n" ) ;
}
if ( ( Options & RevertOptions . KeepWorkspaceFiles ) ! = 0 )
{
Arguments . Append ( " -k" ) ;
}
if ( ( Options & RevertOptions . DeleteAddedFiles ) ! = 0 )
{
Arguments . Append ( " -w" ) ;
}
if ( ChangeNumber ! = - 1 )
{
Arguments . AppendFormat ( " -c {0}" , ChangeNumber ) ;
}
if ( ClientName ! = null )
{
Arguments . AppendFormat ( " -C \"{0}\"" , ClientName ) ;
}
foreach ( string FileSpec in FileSpecs )
{
Arguments . AppendFormat ( " \"{0}\"" , FileSpec ) ;
}
return Command < RevertRecord > ( Arguments . ToString ( ) , null ) ;
}
#endregion
#region p4 shelve
/// <summary>
/// Shelves a set of files
/// </summary>
/// <param name="ChangeNumber">The change number to receive the shelved files</param>
/// <param name="Options">Options for the command</param>
/// <param name="FileSpecs">Files to sync</param>
/// <returns>Response from the server</returns>
public PerforceResponseList < ShelveRecord > Shelve ( int ChangeNumber , ShelveOptions Options , params string [ ] FileSpecs )
{
StringBuilder Arguments = new StringBuilder ( "shelve" ) ;
Arguments . AppendFormat ( " -c {0}" , ChangeNumber ) ;
if ( ( Options & ShelveOptions . OnlyChanged ) ! = 0 )
{
Arguments . Append ( " -a leaveunchanged" ) ;
}
if ( ( Options & ShelveOptions . Overwrite ) ! = 0 )
{
Arguments . Append ( " -f" ) ;
}
foreach ( string FileSpec in FileSpecs )
{
Arguments . AppendFormat ( " \"{0}\"" , FileSpec ) ;
}
return Command < ShelveRecord > ( Arguments . ToString ( ) , null ) ;
}
/// <summary>
/// Deletes files from a shelved changelist
/// </summary>
/// <param name="ChangeNumber">Changelist containing shelved files to be deleted</param>
/// <param name="FileSpecs">Files to delete</param>
/// <returns>Response from the server</returns>
public PerforceResponse DeleteShelvedFiles ( int ChangeNumber , params string [ ] FileSpecs )
{
StringBuilder Arguments = new StringBuilder ( "shelve -d" ) ;
if ( ChangeNumber ! = - 1 )
{
Arguments . AppendFormat ( " -c {0}" , ChangeNumber ) ;
}
foreach ( string FileSpec in FileSpecs )
{
Arguments . AppendFormat ( " \"{0}\"" , FileSpec ) ;
}
return SingleResponseCommand ( Arguments . ToString ( ) , null , null ) ;
}
#endregion
#region p4 streams
/// <summary>
/// Enumerates all streams in a depot
/// </summary>
/// <param name="StreamPath">The path for streams to enumerate (eg. "//UE4/...")</param>
/// <returns>List of streams matching the given criteria</returns>
public PerforceResponseList < StreamRecord > Streams ( string StreamPath )
{
return Streams ( StreamPath , - 1 , null , false ) ;
}
/// <summary>
/// Enumerates all streams in a depot
/// </summary>
/// <param name="StreamPath">The path for streams to enumerate (eg. "//UE4/...")</param>
/// <param name="MaxResults">Maximum number of results to return</param>
/// <param name="Filter">Additional filter to be applied to the results</param>
/// <param name="bUnloaded">Whether to enumerate unloaded workspaces</param>
/// <returns>List of streams matching the given criteria</returns>
public PerforceResponseList < StreamRecord > Streams ( string StreamPath , int MaxResults , string Filter , bool bUnloaded )
{
// Build the command line
StringBuilder Arguments = new StringBuilder ( "streams" ) ;
if ( bUnloaded )
{
Arguments . Append ( " -U" ) ;
}
if ( Filter ! = null )
{
Arguments . AppendFormat ( "-F \"{0}\"" , Filter ) ;
}
if ( MaxResults > 0 )
{
Arguments . AppendFormat ( "-m {0}" , MaxResults ) ;
}
Arguments . AppendFormat ( " \"{0}\"" , StreamPath ) ;
// Execute the command
return Command < StreamRecord > ( Arguments . ToString ( ) , null ) ;
}
#endregion
#region p4 submit
/// <summary>
/// Submits a pending changelist
/// </summary>
/// <param name="ChangeNumber">The changelist to submit</param>
/// <param name="Options">Options for the command</param>
/// <returns>Response from the server</returns>
public PerforceResponse < SubmitRecord > Submit ( int ChangeNumber , SubmitOptions Options )
{
StringBuilder Arguments = new StringBuilder ( "submit" ) ;
if ( ( Options & SubmitOptions . ReopenAsEdit ) ! = 0 )
{
Arguments . Append ( " -r" ) ;
}
Arguments . AppendFormat ( " -c {0}" , ChangeNumber ) ;
return Command < SubmitRecord > ( Arguments . ToString ( ) , null ) [ 0 ] ;
}
#endregion
#region p4 sync
/// <summary>
/// Syncs files from the server
/// </summary>
/// <param name="FileSpecs">Files to sync</param>
/// <returns>Response from the server</returns>
public PerforceResponseList < SyncRecord > Sync ( params string [ ] FileSpecs )
{
return Sync ( SyncOptions . None , - 1 , FileSpecs ) ;
}
/// <summary>
/// Syncs files from the server
/// </summary>
/// <param name="Options">Options for the command</param>
/// <param name="MaxFiles">Syncs only the first number of files specified.</param>
/// <param name="FileSpecs">Files to sync</param>
/// <returns>Response from the server</returns>
public PerforceResponseList < SyncRecord > Sync ( SyncOptions Options , int MaxFiles , params string [ ] FileSpecs )
{
return Sync ( Options , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , FileSpecs ) ;
}
/// <summary>
/// Syncs files from the server
/// </summary>
/// <param name="Options">Options for the command</param>
/// <param name="MaxFiles">Syncs only the first number of files specified</param>
/// <param name="NumThreads">Sync in parallel using the given number of threads</param>
/// <param name="Batch">The number of files in a batch</param>
/// <param name="BatchSize">The number of bytes in a batch</param>
/// <param name="Min">Minimum number of files in a parallel sync</param>
/// <param name="MinSize">Minimum number of bytes in a parallel sync</param>
/// <param name="FileSpecs">Files to sync</param>
/// <returns>Response from the server</returns>
public PerforceResponseList < SyncRecord > Sync ( SyncOptions Options , int MaxFiles , int NumThreads , int Batch , int BatchSize , int Min , int MinSize , params string [ ] FileSpecs )
{
string Arguments = GetSyncArguments ( Options , MaxFiles , NumThreads , Batch , BatchSize , Min , MinSize , FileSpecs , false ) ;
return Command < SyncRecord > ( Arguments . ToString ( ) , null ) ;
}
/// <summary>
/// Syncs files from the server
/// </summary>
/// <param name="Options">Options for the command</param>
/// <param name="MaxFiles">Syncs only the first number of files specified.</param>
/// <param name="FileSpecs">Files to sync</param>
/// <returns>Response from the server</returns>
public PerforceResponseList < SyncSummaryRecord > SyncQuiet ( SyncOptions Options , int MaxFiles , params string [ ] FileSpecs )
{
return SyncQuiet ( Options , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , FileSpecs ) ;
}
/// <summary>
/// Syncs files from the server without returning detailed file info
/// </summary>
/// <param name="Options">Options for the command</param>
/// <param name="MaxFiles">Syncs only the first number of files specified</param>
/// <param name="NumThreads">Sync in parallel using the given number of threads</param>
/// <param name="Batch">The number of files in a batch</param>
/// <param name="BatchSize">The number of bytes in a batch</param>
/// <param name="Min">Minimum number of files in a parallel sync</param>
/// <param name="MinSize">Minimum number of bytes in a parallel sync</param>
/// <param name="FileSpecs">Files to sync</param>
/// <returns>Response from the server</returns>
public PerforceResponseList < SyncSummaryRecord > SyncQuiet ( SyncOptions Options , int MaxFiles , int NumThreads , int Batch , int BatchSize , int Min , int MinSize , params string [ ] FileSpecs )
{
string Arguments = GetSyncArguments ( Options , MaxFiles , NumThreads , Batch , BatchSize , Min , MinSize , FileSpecs , true ) ;
return Command < SyncSummaryRecord > ( Arguments . ToString ( ) , null ) ;
}
/// <summary>
/// Gets arguments for a sync command
/// </summary>
/// <param name="Options">Options for the command</param>
/// <param name="MaxFiles">Syncs only the first number of files specified</param>
/// <param name="NumThreads">Sync in parallel using the given number of threads</param>
/// <param name="Batch">The number of files in a batch</param>
/// <param name="BatchSize">The number of bytes in a batch</param>
/// <param name="Min">Minimum number of files in a parallel sync</param>
/// <param name="MinSize">Minimum number of bytes in a parallel sync</param>
/// <param name="FileSpecs">Files to sync</param>
/// <param name="bQuiet">Whether to use quiet output</param>
/// <returns>Arguments for the command</returns>
static string GetSyncArguments ( SyncOptions Options , int MaxFiles , int NumThreads , int Batch , int BatchSize , int Min , int MinSize , string [ ] FileSpecs , bool bQuiet )
{
StringBuilder Arguments = new StringBuilder ( "sync" ) ;
if ( ( Options & SyncOptions . Force ) ! = 0 )
{
Arguments . Append ( " -f" ) ;
}
if ( ( Options & SyncOptions . KeepWorkspaceFiles ) ! = 0 )
{
Arguments . Append ( " -k" ) ;
}
if ( ( Options & SyncOptions . FullDepotSyntax ) ! = 0 )
{
Arguments . Append ( " -L" ) ;
}
if ( ( Options & SyncOptions . PreviewOnly ) ! = 0 )
{
Arguments . Append ( " -n" ) ;
}
if ( ( Options & SyncOptions . NetworkPreviewOnly ) ! = 0 )
{
Arguments . Append ( " -N" ) ;
}
if ( ( Options & SyncOptions . DoNotUpdateHaveList ) ! = 0 )
{
Arguments . Append ( " -p" ) ;
}
if ( bQuiet )
{
Arguments . Append ( " -q" ) ;
}
if ( ( Options & SyncOptions . ReopenMovedFiles ) ! = 0 )
{
Arguments . Append ( " -r" ) ;
}
if ( ( Options & SyncOptions . Safe ) ! = 0 )
{
Arguments . Append ( " -s" ) ;
}
if ( MaxFiles ! = - 1 )
{
Arguments . AppendFormat ( " -m {0}" , MaxFiles ) ;
}
if ( NumThreads ! = - 1 )
{
Arguments . AppendFormat ( " --parallel-threads={0}" , NumThreads ) ;
if ( Batch ! = - 1 )
{
Arguments . AppendFormat ( ",batch={0}" , Batch ) ;
}
if ( BatchSize ! = - 1 )
{
Arguments . AppendFormat ( ",batchsize={0}" , BatchSize ) ;
}
if ( Min ! = - 1 )
{
Arguments . AppendFormat ( ",min={0}" , Min ) ;
}
if ( MinSize ! = - 1 )
{
Arguments . AppendFormat ( ",minsize={0}" , MinSize ) ;
}
}
foreach ( string FileSpec in FileSpecs )
{
Arguments . AppendFormat ( " \"{0}\"" , FileSpec ) ;
}
return Arguments . ToString ( ) ;
}
#endregion
#region p4 unshelve
/// <summary>
/// Restore shelved files from a pending change into a workspace
/// </summary>
/// <param name="ChangeNumber">The changelist containing shelved files</param>
/// <param name="IntoChangeNumber">The changelist to receive the unshelved files</param>
/// <param name="UsingBranchSpec">The branchspec to use when unshelving files</param>
/// <param name="UsingStream">Specifies the use of a stream-derived branch view to map the shelved files between the specified stream and its parent stream.</param>
/// <param name="ForceParentStream">Unshelve to the specified parent stream. Overrides the parent defined in the source stream specification.</param>
/// <param name="Options">Options for the command</param>
/// <param name="FileSpecs">Files to unshelve</param>
/// <returns>Response from the server</returns>
public PerforceResponseList < UnshelveRecord > Unshelve ( int ChangeNumber , int IntoChangeNumber , string UsingBranchSpec , string UsingStream , string ForceParentStream , UnshelveOptions Options , params string [ ] FileSpecs )
{
StringBuilder Arguments = new StringBuilder ( "unshelve" ) ;
Arguments . AppendFormat ( " -s {0}" , ChangeNumber ) ;
if ( ( Options & UnshelveOptions . ForceOverwrite ) ! = 0 )
{
Arguments . Append ( " -f" ) ;
}
if ( ( Options & UnshelveOptions . PreviewOnly ) ! = 0 )
{
Arguments . Append ( " -n" ) ;
}
if ( IntoChangeNumber ! = - 1 )
{
Arguments . AppendFormat ( " -c {0}" , IntoChangeNumber ) ;
}
if ( UsingBranchSpec ! = null )
{
Arguments . AppendFormat ( " -b \"{0}\"" , UsingBranchSpec ) ;
}
if ( UsingStream ! = null )
{
Arguments . AppendFormat ( " -S \"{0}\"" , UsingStream ) ;
}
if ( ForceParentStream ! = null )
{
Arguments . AppendFormat ( " -P \"{0}\"" , ForceParentStream ) ;
}
foreach ( string FileSpec in FileSpecs )
{
Arguments . AppendFormat ( " \"{0}\"" , FileSpec ) ;
}
return Command < UnshelveRecord > ( Arguments . ToString ( ) , null ) ;
}
#endregion
#region p4 user
/// <summary>
/// Enumerates all streams in a depot
/// </summary>
/// <param name="UserName">Name of the user to fetch information for</param>
/// <returns>Response from the server</returns>
public PerforceResponse < UserRecord > User ( string UserName )
{
StringBuilder Arguments = new StringBuilder ( "user" ) ;
Arguments . AppendFormat ( " -o \"{0}\"" , UserName ) ;
return SingleResponseCommand < UserRecord > ( Arguments . ToString ( ) , null ) ;
}
#endregion
#region p4 where
/// <summary>
/// Retrieves the location of a file of set of files in the workspace
/// </summary>
/// <param name="FileSpecs">Patterns for the files to query</param>
/// <returns>List of responses from the server</returns>
public PerforceResponseList < WhereRecord > Where ( params string [ ] FileSpecs )
{
StringBuilder Arguments = new StringBuilder ( "where" ) ;
foreach ( string FileSpec in FileSpecs )
{
Arguments . AppendFormat ( " \"{0}\"" , FileSpec ) ;
}
return Command < WhereRecord > ( Arguments . ToString ( ) , null ) ;
}
#endregion
}
}