You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
2157 lines
68 KiB
C#
2157 lines
68 KiB
C#
// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.
|
||
|
||
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
|
||
{
|
||
/// <summary>
|
||
/// Constant for the default changelist, where valid.
|
||
/// </summary>
|
||
public const int DefaultChange = -2;
|
||
|
||
#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)
|
||
{
|
||
using(PerforceChildProcess Process = new PerforceChildProcess(InputData, "{0} {1}", GlobalOptions, CommandLine))
|
||
{
|
||
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)));
|
||
}
|
||
else if(Value == "default")
|
||
{
|
||
FieldInfo.SetValue(NewRecord, DefaultChange);
|
||
}
|
||
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
|
||
PerforceResponse Response = ParseResponse(KeyValuePairs, ref Idx, RequiredChildSuffix, RecordInfo.SubElementRecordInfo);
|
||
List.Add(Response.Data);
|
||
}
|
||
}
|
||
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));
|
||
}
|
||
if(UserName != null)
|
||
{
|
||
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)));
|
||
}
|
||
if(Input.Type != null)
|
||
{
|
||
NameToValue.Add(new KeyValuePair<string, object>("Type", Input.Type));
|
||
}
|
||
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
|
||
|
||
#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
|
||
|
||
#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);
|
||
}
|
||
|
||
/// <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);
|
||
}
|
||
|
||
#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
|
||
|
||
#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
|
||
|
||
#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
|
||
}
|
||
}
|