2022-01-07 16:21:08 -05:00
// Copyright Epic Games, Inc. All Rights Reserved.
using EpicGames.Core ;
using EpicGames.Perforce ;
using Microsoft.Extensions.Logging ;
using Serilog ;
using System ;
using System.Collections.Generic ;
using System.Diagnostics ;
using System.Globalization ;
2022-01-11 15:39:30 -05:00
using System.IO ;
2022-01-07 16:21:08 -05:00
using System.Linq ;
using System.Net ;
2022-06-01 10:50:02 -04:00
using System.Reflection ;
2022-01-07 16:21:08 -05:00
using System.Runtime.InteropServices ;
2022-01-11 15:39:30 -05:00
using System.Text ;
2022-01-07 16:21:08 -05:00
using System.Text.RegularExpressions ;
2022-01-08 15:04:19 -05:00
using System.Threading ;
using System.Threading.Tasks ;
2022-07-01 04:39:56 -04:00
using EpicGames.OIDC ;
using Microsoft.Extensions.Configuration ;
2022-01-07 16:21:08 -05:00
using UnrealGameSync ;
namespace UnrealGameSyncCmd
{
using ILogger = Microsoft . Extensions . Logging . ILogger ;
sealed class UserErrorException : Exception
{
public LogEvent Event { get ; }
public int Code { get ; }
2022-06-20 14:31:50 -04:00
public UserErrorException ( LogEvent evt )
: base ( evt . ToString ( ) )
2022-01-07 16:21:08 -05:00
{
2022-06-20 14:31:50 -04:00
this . Event = evt ;
2022-01-07 16:21:08 -05:00
this . Code = 1 ;
}
2022-06-20 14:31:50 -04:00
public UserErrorException ( string message , params object [ ] args )
: this ( LogEvent . Create ( LogLevel . Error , message , args ) )
2022-01-07 16:21:08 -05:00
{
}
}
public class Program
{
2022-01-11 15:39:30 -05:00
static BuildConfig EditorConfig = > BuildConfig . Development ;
2022-01-07 16:21:08 -05:00
class CommandInfo
{
public string Name { get ; }
public Type Type { get ; }
public string Usage { get ; }
public string Brief { get ; }
2022-06-20 14:31:50 -04:00
public CommandInfo ( string name , Type type , string usage , string brief )
2022-01-07 16:21:08 -05:00
{
2022-06-20 14:31:50 -04:00
this . Name = name ;
this . Type = type ;
this . Usage = usage ;
this . Brief = brief ;
2022-01-07 16:21:08 -05:00
}
}
2022-06-20 14:31:50 -04:00
static CommandInfo [ ] _commands =
2022-01-07 16:21:08 -05:00
{
new CommandInfo ( "init" , typeof ( InitCommand ) ,
"ugs init [stream-path] [-client=..] [-server=..] [-user=..] [-branch=..] [-project=..]" ,
"Create a client for the given stream, or initializes an existing client for use by UGS."
) ,
new CommandInfo ( "switch" , typeof ( SwitchCommand ) ,
"ugs switch [project name|project path|stream]" ,
"Changes the active project to the one in the workspace with the given name, or switches to a new stream."
) ,
2022-01-10 09:33:30 -05:00
new CommandInfo ( "changes" , typeof ( ChangesCommand ) ,
"ugs changes" ,
"List recently submitted changes to the current branch."
) ,
2022-01-07 16:21:08 -05:00
new CommandInfo ( "config" , typeof ( ConfigCommand ) ,
"ugs config" ,
"Updates the configuration for the current workspace."
) ,
2022-01-10 13:44:32 -05:00
new CommandInfo ( "filter" , typeof ( FilterCommand ) ,
"ugs filter [-reset] [-include=..] [-exclude=..] [-view=..] [-addview=..] [-removeview=..] [-global]" ,
"Displays or updates the workspace or global sync filter"
) ,
2022-01-07 16:21:08 -05:00
new CommandInfo ( "sync" , typeof ( SyncCommand ) ,
2022-08-25 12:00:10 -04:00
"ugs sync [change|'latest'] [-build] [-binaries] [-remove] [-only]" ,
2022-01-07 16:21:08 -05:00
"Syncs the current workspace to the given changelist, optionally removing all local state."
) ,
2022-01-11 15:39:30 -05:00
new CommandInfo ( "clients" , typeof ( ClientsCommand ) ,
"ugs clients" ,
"Lists all clients suitable for use on the current machine."
) ,
new CommandInfo ( "run" , typeof ( RunCommand ) ,
"ugs run" ,
"Runs the editor for the current branch."
) ,
2022-01-07 16:21:08 -05:00
new CommandInfo ( "build" , typeof ( BuildCommand ) ,
"ugs build [id] [-list]" ,
"Runs the default build steps for the current project, or a particular step referenced by id."
) ,
new CommandInfo ( "status" , typeof ( StatusCommand ) ,
"ugs status [-update]" ,
"Shows the status of the currently synced branch."
2022-06-01 10:50:02 -04:00
) ,
2022-07-01 04:39:56 -04:00
new CommandInfo ( "login" , typeof ( LoginCommand ) ,
"ugs login" ,
"Starts a interactive login flow against the configured Identity Provider"
) ,
2022-06-01 10:50:02 -04:00
new CommandInfo ( "version" , typeof ( VersionCommand ) ,
"ugs version" ,
"Prints the current application version"
) ,
2022-01-07 16:21:08 -05:00
} ;
class CommandContext
{
public CommandLineArguments Arguments { get ; }
public ILogger Logger { get ; }
public ILoggerFactory LoggerFactory { get ; }
2022-01-10 10:58:21 -05:00
public GlobalSettingsFile UserSettings { get ; }
2022-01-07 16:21:08 -05:00
2022-06-20 14:31:50 -04:00
public CommandContext ( CommandLineArguments arguments , ILogger logger , ILoggerFactory loggerFactory , GlobalSettingsFile userSettings )
2022-01-07 16:21:08 -05:00
{
2022-06-20 14:31:50 -04:00
this . Arguments = arguments ;
this . Logger = logger ;
this . LoggerFactory = loggerFactory ;
this . UserSettings = userSettings ;
2022-01-07 16:21:08 -05:00
}
}
2022-01-11 15:39:30 -05:00
class ServerOptions
2022-01-07 16:21:08 -05:00
{
[CommandLine("-Server=")]
public string? ServerAndPort { get ; set ; }
[CommandLine("-User=")]
public string? UserName { get ; set ; }
2022-01-11 15:39:30 -05:00
}
2022-01-07 16:21:08 -05:00
2022-01-11 15:39:30 -05:00
class ProjectConfigOptions : ServerOptions
{
2022-06-20 14:31:50 -04:00
public void ApplyTo ( UserWorkspaceSettings settings )
2022-01-07 16:21:08 -05:00
{
if ( ServerAndPort ! = null )
{
2022-06-20 14:31:50 -04:00
settings . ServerAndPort = ( ServerAndPort . Length = = 0 ) ? null : ServerAndPort ;
2022-01-07 16:21:08 -05:00
}
if ( UserName ! = null )
{
2022-06-20 14:31:50 -04:00
settings . UserName = ( UserName . Length = = 0 ) ? null : UserName ;
2022-01-07 16:21:08 -05:00
}
}
}
class ProjectInitOptions : ProjectConfigOptions
{
[CommandLine("-Client=")]
public string? ClientName { get ; set ; }
[CommandLine("-Branch=")]
public string? BranchPath { get ; set ; }
[CommandLine("-Project=")]
public string? ProjectName { get ; set ; }
}
2022-06-20 14:31:50 -04:00
public static async Task < int > Main ( string [ ] rawArgs )
2022-01-07 16:21:08 -05:00
{
2022-06-20 14:31:50 -04:00
DirectoryReference globalConfigFolder ;
2022-01-10 10:58:21 -05:00
if ( RuntimeInformation . IsOSPlatform ( OSPlatform . Windows ) )
2022-01-09 19:54:09 -05:00
{
2022-06-20 14:31:50 -04:00
globalConfigFolder = DirectoryReference . Combine ( DirectoryReference . GetSpecialFolder ( Environment . SpecialFolder . LocalApplicationData ) ! , "UnrealGameSync" ) ;
2022-01-09 19:54:09 -05:00
}
else
{
2022-06-20 14:31:50 -04:00
globalConfigFolder = DirectoryReference . Combine ( DirectoryReference . GetSpecialFolder ( Environment . SpecialFolder . UserProfile ) ! , ".config" , "UnrealGameSync" ) ;
2022-01-09 19:54:09 -05:00
}
2022-06-20 14:31:50 -04:00
DirectoryReference . CreateDirectory ( globalConfigFolder ) ;
2022-01-09 19:54:09 -05:00
2022-06-20 14:31:50 -04:00
string logName ;
DirectoryReference logFolder ;
2022-01-10 10:58:21 -05:00
if ( RuntimeInformation . IsOSPlatform ( OSPlatform . OSX ) )
2022-01-09 19:54:09 -05:00
{
2022-06-20 14:31:50 -04:00
logFolder = DirectoryReference . Combine ( DirectoryReference . GetSpecialFolder ( Environment . SpecialFolder . UserProfile ) ! , "Library" , "Logs" , "Unreal Engine" , "UnrealGameSync" ) ;
logName = "UnrealGameSync-.log" ;
2022-01-09 19:54:09 -05:00
}
2022-01-10 10:58:21 -05:00
else if ( RuntimeInformation . IsOSPlatform ( OSPlatform . Linux ) )
2022-01-09 19:54:09 -05:00
{
2022-06-20 14:31:50 -04:00
logFolder = DirectoryReference . GetSpecialFolder ( Environment . SpecialFolder . UserProfile ) ! ;
logName = ".ugs-.log" ;
2022-01-09 19:54:09 -05:00
}
else
{
2022-06-20 14:31:50 -04:00
logFolder = globalConfigFolder ;
logName = "UnrealGameSyncCmd-.log" ;
2022-01-09 19:54:09 -05:00
}
2022-01-07 16:21:08 -05:00
2022-06-20 14:31:50 -04:00
Serilog . ILogger serilogLogger = new LoggerConfiguration ( )
2022-01-07 16:21:08 -05:00
. Enrich . FromLogContext ( )
. WriteTo . Console ( Serilog . Events . LogEventLevel . Information , outputTemplate : "{Message:lj}{NewLine}" )
2022-06-20 14:31:50 -04:00
. WriteTo . File ( FileReference . Combine ( logFolder , logName ) . FullName , Serilog . Events . LogEventLevel . Debug , rollingInterval : RollingInterval . Day , rollOnFileSizeLimit : true , fileSizeLimitBytes : 20 * 1024 * 1024 , retainedFileCountLimit : 10 )
2022-01-07 16:21:08 -05:00
. CreateLogger ( ) ;
2022-06-20 14:31:50 -04:00
using ILoggerFactory loggerFactory = new Serilog . Extensions . Logging . SerilogLoggerFactory ( serilogLogger , true ) ;
ILogger logger = loggerFactory . CreateLogger ( "Main" ) ;
2022-01-07 16:21:08 -05:00
try
{
2022-06-20 14:31:50 -04:00
GlobalSettingsFile settings ;
2022-01-07 16:21:08 -05:00
if ( RuntimeInformation . IsOSPlatform ( OSPlatform . Windows ) )
{
2022-06-20 14:31:50 -04:00
settings = UserSettings . Create ( globalConfigFolder , logger ) ;
2022-01-10 10:58:21 -05:00
}
else
{
2022-06-20 14:31:50 -04:00
settings = GlobalSettingsFile . Create ( FileReference . Combine ( globalConfigFolder , "Global.json" ) ) ;
2022-01-07 16:21:08 -05:00
}
2022-06-20 14:31:50 -04:00
CommandLineArguments args = new CommandLineArguments ( rawArgs ) ;
2022-01-07 16:21:08 -05:00
2022-06-20 14:31:50 -04:00
string? commandName ;
if ( ! args . TryGetPositionalArgument ( out commandName ) )
2022-01-07 16:21:08 -05:00
{
PrintHelp ( ) ;
return 0 ;
}
2022-06-20 14:31:50 -04:00
CommandInfo ? command = _commands . FirstOrDefault ( x = > x . Name . Equals ( commandName , StringComparison . OrdinalIgnoreCase ) ) ;
if ( command = = null )
2022-01-07 16:21:08 -05:00
{
2022-06-20 14:31:50 -04:00
logger . LogError ( $"unknown command '{commandName}'" ) ;
2022-01-07 16:21:08 -05:00
Console . WriteLine ( ) ;
PrintHelp ( ) ;
return 1 ;
}
2022-06-20 14:31:50 -04:00
Command instance = ( Command ) Activator . CreateInstance ( command . Type ) ! ;
await instance . ExecuteAsync ( new CommandContext ( args , logger , loggerFactory , settings ) ) ;
2022-01-07 16:21:08 -05:00
return 0 ;
}
2022-06-20 14:31:50 -04:00
catch ( UserErrorException ex )
2022-01-07 16:21:08 -05:00
{
2022-06-20 14:31:50 -04:00
logger . Log ( ex . Event . Level , "{Message}" , ex . Event . ToString ( ) ) ;
return ex . Code ;
2022-01-07 16:21:08 -05:00
}
2022-06-20 14:31:50 -04:00
catch ( PerforceException ex )
2022-01-07 16:21:08 -05:00
{
2022-06-20 14:31:50 -04:00
logger . LogError ( ex , "{Message}" , ex . Message ) ;
2022-01-07 16:21:08 -05:00
return 1 ;
}
2022-06-20 14:31:50 -04:00
catch ( Exception ex )
2022-01-07 16:21:08 -05:00
{
2022-06-20 14:31:50 -04:00
logger . LogError ( ex , "Unhandled exception.\n{Str}" , ex ) ;
2022-01-07 16:21:08 -05:00
return 1 ;
}
}
static void PrintHelp ( )
{
Console . WriteLine ( "Usage:" ) ;
2022-06-20 14:31:50 -04:00
foreach ( CommandInfo command in _commands )
2022-01-07 16:21:08 -05:00
{
Console . WriteLine ( ) ;
2022-06-20 14:31:50 -04:00
ConsoleUtils . WriteLineWithWordWrap ( command . Usage , 2 , 8 ) ;
ConsoleUtils . WriteLineWithWordWrap ( command . Brief , 4 , 4 ) ;
2022-01-07 16:21:08 -05:00
}
}
public static UserWorkspaceSettings ? ReadOptionalUserWorkspaceSettings ( )
{
2022-06-20 14:31:50 -04:00
DirectoryReference ? dir = DirectoryReference . GetCurrentDirectory ( ) ;
for ( ; dir ! = null ; dir = dir . ParentDirectory )
2022-01-07 16:21:08 -05:00
{
2022-01-14 10:51:47 -05:00
try
2022-01-07 16:21:08 -05:00
{
2022-06-20 14:31:50 -04:00
UserWorkspaceSettings ? settings ;
if ( UserWorkspaceSettings . TryLoad ( dir , out settings ) )
2022-01-14 10:51:47 -05:00
{
2022-06-20 14:31:50 -04:00
return settings ;
2022-01-14 10:51:47 -05:00
}
}
catch
{
// Guard against directories we can't access, eg. /Users/.ugs
2022-01-07 16:21:08 -05:00
}
}
return null ;
}
public static UserWorkspaceSettings ReadRequiredUserWorkspaceSettings ( )
{
2022-06-20 14:31:50 -04:00
UserWorkspaceSettings ? settings = ReadOptionalUserWorkspaceSettings ( ) ;
if ( settings = = null )
2022-01-07 16:21:08 -05:00
{
throw new UserErrorException ( "Unable to find UGS workspace in current directory." ) ;
}
2022-06-20 14:31:50 -04:00
return settings ;
2022-01-07 16:21:08 -05:00
}
2022-06-20 14:31:50 -04:00
public static async Task < UserWorkspaceState > ReadWorkspaceState ( IPerforceConnection perforceClient , UserWorkspaceSettings settings , GlobalSettingsFile userSettings , ILogger logger )
2022-01-07 16:21:08 -05:00
{
2022-06-20 14:31:50 -04:00
UserWorkspaceState state = userSettings . FindOrAddWorkspaceState ( settings , logger ) ;
if ( state . SettingsTimeUtc ! = settings . LastModifiedTimeUtc )
2022-01-07 16:21:08 -05:00
{
2022-06-20 14:31:50 -04:00
logger . LogDebug ( "Updating state due to modified settings timestamp" ) ;
ProjectInfo info = await ProjectInfo . CreateAsync ( perforceClient , settings , CancellationToken . None ) ;
state . UpdateCachedProjectInfo ( info , settings . LastModifiedTimeUtc ) ;
state . Save ( logger ) ;
2022-01-07 16:21:08 -05:00
}
2022-06-20 14:31:50 -04:00
return state ;
2022-01-07 16:21:08 -05:00
}
2022-06-20 14:31:50 -04:00
public static Task < IPerforceConnection > ConnectAsync ( string? serverAndPort , string? userName , string? clientName , ILoggerFactory loggerFactory )
2022-01-07 16:21:08 -05:00
{
2022-06-20 14:31:50 -04:00
PerforceSettings settings = new PerforceSettings ( PerforceSettings . Default ) ;
settings . ClientName = clientName ;
settings . PreferNativeClient = true ;
if ( ! String . IsNullOrEmpty ( serverAndPort ) )
2022-01-07 16:21:08 -05:00
{
2022-06-20 14:31:50 -04:00
settings . ServerAndPort = serverAndPort ;
2022-01-07 16:21:08 -05:00
}
2022-06-20 14:31:50 -04:00
if ( ! String . IsNullOrEmpty ( userName ) )
2022-01-07 16:21:08 -05:00
{
2022-06-20 14:31:50 -04:00
settings . UserName = userName ;
2022-01-07 16:21:08 -05:00
}
2022-06-20 14:31:50 -04:00
return PerforceConnection . CreateAsync ( settings , loggerFactory . CreateLogger ( "Perforce" ) ) ;
2022-01-07 16:21:08 -05:00
}
2022-06-20 14:31:50 -04:00
public static Task < IPerforceConnection > ConnectAsync ( UserWorkspaceSettings settings , ILoggerFactory loggerFactory )
2022-01-07 16:21:08 -05:00
{
2022-06-20 14:31:50 -04:00
return ConnectAsync ( settings . ServerAndPort , settings . UserName , settings . ClientName , loggerFactory ) ;
2022-01-07 16:21:08 -05:00
}
2022-06-20 14:31:50 -04:00
static string [ ] ReadSyncFilter ( UserWorkspaceSettings workspaceSettings , GlobalSettingsFile userSettings , ConfigFile projectConfig )
2022-01-07 16:21:08 -05:00
{
2022-06-20 14:31:50 -04:00
Dictionary < Guid , WorkspaceSyncCategory > syncCategories = ConfigUtils . GetSyncCategories ( projectConfig ) ;
string [ ] combinedSyncFilter = GlobalSettingsFile . GetCombinedSyncFilter ( syncCategories , userSettings . Global . Filter , workspaceSettings . Filter ) ;
2022-01-07 16:21:08 -05:00
2022-06-20 14:31:50 -04:00
ConfigSection perforceSection = projectConfig . FindSection ( "Perforce" ) ;
if ( perforceSection ! = null )
2022-01-07 16:21:08 -05:00
{
2022-06-20 14:31:50 -04:00
IEnumerable < string > additionalPaths = perforceSection . GetValues ( "AdditionalPathsToSync" , new string [ 0 ] ) ;
combinedSyncFilter = additionalPaths . Union ( combinedSyncFilter ) . ToArray ( ) ;
2022-01-07 16:21:08 -05:00
}
2022-06-20 14:31:50 -04:00
return combinedSyncFilter ;
2022-01-07 16:21:08 -05:00
}
2022-06-20 14:31:50 -04:00
static async Task < string > FindProjectPathAsync ( IPerforceConnection perforce , string clientName , string branchPath , string? projectName )
2022-01-07 16:21:08 -05:00
{
2022-06-20 14:31:50 -04:00
using IPerforceConnection perforceClient = await PerforceConnection . CreateAsync ( new PerforceSettings ( perforce . Settings ) { ClientName = clientName } , perforce . Logger ) ;
2022-01-07 16:21:08 -05:00
// Find or validate the selected project
2022-06-20 14:31:50 -04:00
string searchPath ;
if ( projectName = = null )
2022-01-07 16:21:08 -05:00
{
2022-06-20 14:31:50 -04:00
searchPath = $"//{clientName}{branchPath}/*.uprojectdirs" ;
2022-01-07 16:21:08 -05:00
}
2022-06-20 14:31:50 -04:00
else if ( projectName . Contains ( '.' ) )
2022-01-07 16:21:08 -05:00
{
2022-06-20 14:31:50 -04:00
searchPath = $"//{clientName}{branchPath}/{projectName.TrimStart('/')}" ;
2022-01-07 16:21:08 -05:00
}
else
{
2022-06-20 14:31:50 -04:00
searchPath = $"//{clientName}{branchPath}/.../{projectName}.uproject" ;
2022-01-07 16:21:08 -05:00
}
2022-06-20 14:31:50 -04:00
List < FStatRecord > projectFileRecords = await perforceClient . FStatAsync ( FStatOptions . ClientFileInPerforceSyntax , searchPath ) . ToListAsync ( ) ;
projectFileRecords . RemoveAll ( x = > x . HeadAction = = FileAction . Delete | | x . HeadAction = = FileAction . MoveDelete ) ;
projectFileRecords . RemoveAll ( x = > ! x . IsMapped ) ;
2022-01-07 16:21:08 -05:00
2022-06-20 14:31:50 -04:00
List < string > paths = projectFileRecords . Select ( x = > PerforceUtils . GetClientRelativePath ( x . ClientFile ! ) ) . Distinct ( StringComparer . Ordinal ) . ToList ( ) ;
if ( paths . Count = = 0 )
2022-01-07 16:21:08 -05:00
{
2022-06-20 14:31:50 -04:00
throw new UserErrorException ( "No project file found matching {SearchPath}" , searchPath ) ;
2022-01-07 16:21:08 -05:00
}
2022-06-20 14:31:50 -04:00
if ( paths . Count > 1 )
2022-01-07 16:21:08 -05:00
{
2022-06-20 14:31:50 -04:00
throw new UserErrorException ( "Multiple projects found matching {SearchPath}: {Paths}" , searchPath , String . Join ( ", " , paths ) ) ;
2022-01-07 16:21:08 -05:00
}
2022-06-20 14:31:50 -04:00
return "/" + paths [ 0 ] ;
2022-01-07 16:21:08 -05:00
}
abstract class Command
{
2022-06-20 14:31:50 -04:00
public abstract Task ExecuteAsync ( CommandContext context ) ;
2022-01-07 16:21:08 -05:00
}
class InitCommand : Command
{
2022-06-20 14:31:50 -04:00
public override async Task ExecuteAsync ( CommandContext context )
2022-01-07 16:21:08 -05:00
{
2022-06-20 14:31:50 -04:00
ILogger logger = context . Logger ;
2022-01-07 16:21:08 -05:00
// Get the positional argument indicating the file to look for
2022-06-20 14:31:50 -04:00
string? initName ;
context . Arguments . TryGetPositionalArgument ( out initName ) ;
2022-01-07 16:21:08 -05:00
// Get the config settings from the command line
2022-06-20 14:31:50 -04:00
ProjectInitOptions options = new ProjectInitOptions ( ) ;
context . Arguments . ApplyTo ( options ) ;
context . Arguments . CheckAllArgumentsUsed ( ) ;
2022-01-07 16:21:08 -05:00
// Get the host name
2022-06-20 14:31:50 -04:00
using IPerforceConnection perforce = await ConnectAsync ( options . ServerAndPort , options . UserName , null , context . LoggerFactory ) ;
InfoRecord perforceInfo = await perforce . GetInfoAsync ( InfoOptions . ShortOutput ) ;
string hostName = perforceInfo . ClientHost ? ? Dns . GetHostName ( ) ;
2022-01-07 16:21:08 -05:00
// Create the perforce connection
2022-06-20 14:31:50 -04:00
if ( initName ! = null )
2022-01-07 16:21:08 -05:00
{
2022-06-20 14:31:50 -04:00
await InitNewClientAsync ( perforce , context , initName , hostName , options , logger ) ;
2022-01-07 16:21:08 -05:00
}
else
{
2022-06-20 14:31:50 -04:00
await InitExistingClientAsync ( perforce , context , hostName , options , logger ) ;
2022-01-07 16:21:08 -05:00
}
}
2022-06-20 14:31:50 -04:00
async Task InitNewClientAsync ( IPerforceConnection perforce , CommandContext context , string streamName , string hostName , ProjectInitOptions options , ILogger logger )
2022-01-07 16:21:08 -05:00
{
2022-06-20 14:31:50 -04:00
logger . LogInformation ( "Checking stream..." ) ;
2022-01-07 16:21:08 -05:00
// Get the given stream
2022-06-20 14:31:50 -04:00
PerforceResponse < StreamRecord > streamResponse = await perforce . TryGetStreamAsync ( streamName , true ) ;
if ( ! streamResponse . Succeeded )
2022-01-07 16:21:08 -05:00
{
2022-06-20 14:31:50 -04:00
throw new UserErrorException ( $"Unable to find stream '{streamName}'" ) ;
2022-01-07 16:21:08 -05:00
}
2022-06-20 14:31:50 -04:00
StreamRecord stream = streamResponse . Data ;
2022-01-07 16:21:08 -05:00
// Get the new directory for the client
2022-06-20 14:31:50 -04:00
DirectoryReference clientDir = DirectoryReference . Combine ( DirectoryReference . GetCurrentDirectory ( ) , stream . Stream . Replace ( '/' , '+' ) ) ;
DirectoryReference . CreateDirectory ( clientDir ) ;
2022-01-07 16:21:08 -05:00
// Make up a new client name
2022-06-20 14:31:50 -04:00
string clientName = options . ClientName ? ? Regex . Replace ( $"{perforce.Settings.UserName}_{hostName}_{stream.Stream.Trim('/')}" , "[^0-9a-zA-Z_.-]" , "+" ) ;
2022-01-07 16:21:08 -05:00
// Check there are no existing clients under the current path
2022-06-20 14:31:50 -04:00
List < ClientsRecord > clients = await FindExistingClients ( perforce , hostName , clientDir ) ;
if ( clients . Count > 0 )
2022-01-07 16:21:08 -05:00
{
2022-06-20 14:31:50 -04:00
if ( clients . Count = = 1 & & clientName . Equals ( clients [ 0 ] . Name , StringComparison . OrdinalIgnoreCase ) & & clientDir = = TryParseRoot ( clients [ 0 ] . Root ) )
2022-01-07 16:21:08 -05:00
{
2022-06-20 14:31:50 -04:00
logger . LogInformation ( "Reusing existing client for {ClientDir} ({ClientName})" , clientDir , options . ClientName ) ;
2022-01-07 16:21:08 -05:00
}
else
{
2022-06-20 14:31:50 -04:00
throw new UserErrorException ( "Current directory is already within a Perforce workspace ({ClientName})" , clients [ 0 ] . Name ) ;
2022-01-07 16:21:08 -05:00
}
}
// Create the new client
2022-06-20 14:31:50 -04:00
ClientRecord client = new ClientRecord ( clientName , perforce . Settings . UserName , clientDir . FullName ) ;
client . Host = hostName ;
client . Stream = stream . Stream ;
client . Options = ClientOptions . Rmdir ;
await perforce . CreateClientAsync ( client ) ;
2022-01-07 16:21:08 -05:00
// Branch root is currently hard-coded at the root
2022-06-20 14:31:50 -04:00
string branchPath = options . BranchPath ? ? String . Empty ;
string projectPath = await FindProjectPathAsync ( perforce , clientName , branchPath , options . ProjectName ) ;
2022-01-07 16:21:08 -05:00
// Create the settings object
2022-06-20 14:31:50 -04:00
UserWorkspaceSettings settings = new UserWorkspaceSettings ( ) ;
settings . RootDir = clientDir ;
settings . Init ( perforce . Settings . ServerAndPort , perforce . Settings . UserName , clientName , branchPath , projectPath ) ;
options . ApplyTo ( settings ) ;
settings . Save ( logger ) ;
2022-01-07 16:21:08 -05:00
2022-06-20 14:31:50 -04:00
logger . LogInformation ( "Initialized {ClientName} with root at {RootDir}" , clientName , clientDir ) ;
2022-01-07 16:21:08 -05:00
}
2022-06-20 14:31:50 -04:00
static DirectoryReference ? TryParseRoot ( string root )
2022-01-07 16:21:08 -05:00
{
try
{
2022-06-20 14:31:50 -04:00
return new DirectoryReference ( root ) ;
2022-01-07 16:21:08 -05:00
}
catch
{
return null ;
}
}
2022-06-20 14:31:50 -04:00
async Task InitExistingClientAsync ( IPerforceConnection perforce , CommandContext context , string hostName , ProjectInitOptions options , ILogger logger )
2022-01-07 16:21:08 -05:00
{
2022-06-20 14:31:50 -04:00
DirectoryReference currentDir = DirectoryReference . GetCurrentDirectory ( ) ;
2022-01-07 16:21:08 -05:00
// Make sure the client name is set
2022-06-20 14:31:50 -04:00
string? clientName = options . ClientName ;
if ( clientName = = null )
2022-01-07 16:21:08 -05:00
{
2022-06-20 14:31:50 -04:00
List < ClientsRecord > clients = await FindExistingClients ( perforce , hostName , currentDir ) ;
if ( clients . Count = = 0 )
2022-01-07 16:21:08 -05:00
{
2022-06-20 14:31:50 -04:00
throw new UserErrorException ( "Unable to find client for {HostName} under {ClientDir}" , hostName , currentDir ) ;
2022-01-07 16:21:08 -05:00
}
2022-06-20 14:31:50 -04:00
if ( clients . Count > 1 )
2022-01-07 16:21:08 -05:00
{
2022-06-20 14:31:50 -04:00
throw new UserErrorException ( "Multiple clients found for {HostName} under {ClientDir}: {ClientList}" , hostName , currentDir , String . Join ( ", " , clients . Select ( x = > x . Name ) ) ) ;
2022-01-07 16:21:08 -05:00
}
2022-06-20 14:31:50 -04:00
clientName = clients [ 0 ] . Name ;
logger . LogInformation ( "Found client {ClientName}" , clientName ) ;
2022-01-07 16:21:08 -05:00
}
// Get the client info
2022-06-20 14:31:50 -04:00
ClientRecord client = await perforce . GetClientAsync ( clientName ) ;
DirectoryReference clientDir = new DirectoryReference ( client . Root ) ;
2022-01-07 16:21:08 -05:00
// If a project path was specified in local syntax, try to convert it to client-relative syntax
2022-06-20 14:31:50 -04:00
string? projectName = options . ProjectName ;
if ( options . ProjectName ! = null & & options . ProjectName . Contains ( '.' ) )
2022-01-07 16:21:08 -05:00
{
2022-06-20 14:31:50 -04:00
options . ProjectName = FileReference . Combine ( currentDir , options . ProjectName ) . MakeRelativeTo ( clientDir ) . Replace ( '\\' , '/' ) ;
2022-01-07 16:21:08 -05:00
}
// Branch root is currently hard-coded at the root
2022-06-20 14:31:50 -04:00
string branchPath = options . BranchPath ? ? String . Empty ;
string projectPath = await FindProjectPathAsync ( perforce , clientName , branchPath , projectName ) ;
2022-01-07 16:21:08 -05:00
// Create the settings object
2022-06-20 14:31:50 -04:00
UserWorkspaceSettings settings = new UserWorkspaceSettings ( ) ;
settings . RootDir = clientDir ;
settings . Init ( perforce . Settings . ServerAndPort , perforce . Settings . UserName , clientName , branchPath , projectPath ) ;
options . ApplyTo ( settings ) ;
settings . Save ( logger ) ;
2022-01-07 16:21:08 -05:00
2022-06-20 14:31:50 -04:00
logger . LogInformation ( "Initialized workspace at {RootDir} for {ClientProject}" , clientDir , settings . ClientProjectPath ) ;
2022-01-07 16:21:08 -05:00
}
2022-06-20 14:31:50 -04:00
static async Task < List < ClientsRecord > > FindExistingClients ( IPerforceConnection perforce , string hostName , DirectoryReference clientDir )
2022-01-07 16:21:08 -05:00
{
2022-06-20 14:31:50 -04:00
List < ClientsRecord > matchingClients = new List < ClientsRecord > ( ) ;
2022-01-07 16:21:08 -05:00
2022-06-20 14:31:50 -04:00
List < ClientsRecord > clients = await perforce . GetClientsAsync ( ClientsOptions . None , perforce . Settings . UserName ) ;
foreach ( ClientsRecord client in clients )
2022-01-07 16:21:08 -05:00
{
2022-06-20 14:31:50 -04:00
if ( ! String . IsNullOrEmpty ( client . Root ) & & ! String . IsNullOrEmpty ( client . Host ) & & String . Compare ( hostName , client . Host , StringComparison . OrdinalIgnoreCase ) = = 0 )
2022-01-07 16:21:08 -05:00
{
2022-06-20 14:31:50 -04:00
DirectoryReference ? rootDir ;
2022-01-07 16:21:08 -05:00
try
{
2022-06-20 14:31:50 -04:00
rootDir = new DirectoryReference ( client . Root ) ;
2022-01-07 16:21:08 -05:00
}
catch
{
2022-06-20 14:31:50 -04:00
rootDir = null ;
2022-01-07 16:21:08 -05:00
}
2022-06-20 14:31:50 -04:00
if ( rootDir ! = null & & clientDir . IsUnderDirectory ( rootDir ) )
2022-01-07 16:21:08 -05:00
{
2022-06-20 14:31:50 -04:00
matchingClients . Add ( client ) ;
2022-01-07 16:21:08 -05:00
}
}
}
2022-06-20 14:31:50 -04:00
return matchingClients ;
2022-01-07 16:21:08 -05:00
}
}
class SyncCommand : Command
{
class SyncOptions
{
[CommandLine("-Only")]
public bool SingleChange { get ; set ; }
[CommandLine("-Build")]
public bool Build { get ; set ; }
2022-01-11 12:54:38 -05:00
2022-08-25 12:00:10 -04:00
[CommandLine("-Binaries")]
public bool Binaries { get ; set ; }
2022-01-11 12:54:38 -05:00
[CommandLine("-NoGPF", Value = "false")]
[CommandLine("-NoProjectFiles", Value = "false")]
public bool ProjectFiles { get ; set ; } = true ;
2022-01-14 10:51:47 -05:00
[CommandLine("-Clobber")]
public bool Clobber { get ; set ; }
2022-01-14 11:00:17 -05:00
[CommandLine("-Refilter")]
public bool Refilter { get ; set ; }
2022-01-07 16:21:08 -05:00
}
2022-08-25 12:00:10 -04:00
async Task < bool > IsCodeChangeAsync ( IPerforceConnection perforce , int change )
{
DescribeRecord describeRecord = await perforce . DescribeAsync ( change ) ;
return IsCodeChange ( describeRecord ) ;
}
bool IsCodeChange ( DescribeRecord describeRecord )
{
foreach ( DescribeFileRecord file in describeRecord . Files )
{
if ( PerforceUtils . CodeExtensions . Any ( extension = > file . DepotFile . EndsWith ( extension , StringComparison . OrdinalIgnoreCase ) ) )
{
return true ;
}
}
return false ;
}
2022-06-20 14:31:50 -04:00
public override async Task ExecuteAsync ( CommandContext context )
2022-01-07 16:21:08 -05:00
{
2022-06-20 14:31:50 -04:00
ILogger logger = context . Logger ;
context . Arguments . TryGetPositionalArgument ( out string? changeString ) ;
2022-01-07 16:21:08 -05:00
2022-06-20 14:31:50 -04:00
SyncOptions syncOptions = new SyncOptions ( ) ;
context . Arguments . ApplyTo ( syncOptions ) ;
2022-01-07 16:21:08 -05:00
2022-06-20 14:31:50 -04:00
context . Arguments . CheckAllArgumentsUsed ( ) ;
2022-01-07 16:21:08 -05:00
2022-06-20 14:31:50 -04:00
UserWorkspaceSettings settings = ReadRequiredUserWorkspaceSettings ( ) ;
using IPerforceConnection perforceClient = await ConnectAsync ( settings , context . LoggerFactory ) ;
UserWorkspaceState state = await ReadWorkspaceState ( perforceClient , settings , context . UserSettings , logger ) ;
2022-01-07 16:21:08 -05:00
2022-06-20 14:31:50 -04:00
changeString ? ? = "latest" ;
2022-01-07 16:21:08 -05:00
2022-08-25 12:00:10 -04:00
ProjectInfo projectInfo = state . CreateProjectInfo ( ) ;
UserProjectSettings projectSettings = context . UserSettings . FindOrAddProjectSettings ( projectInfo , settings , logger ) ;
ConfigFile projectConfig = await ConfigUtils . ReadProjectConfigFileAsync ( perforceClient , projectInfo , logger , CancellationToken . None ) ;
bool syncLatest = String . Equals ( changeString , "latest" , StringComparison . OrdinalIgnoreCase ) ;
2022-06-20 14:31:50 -04:00
int change ;
if ( ! int . TryParse ( changeString , out change ) )
2022-01-07 16:21:08 -05:00
{
2022-08-25 12:00:10 -04:00
if ( syncLatest )
2022-01-07 16:21:08 -05:00
{
2022-06-20 14:31:50 -04:00
List < ChangesRecord > changes = await perforceClient . GetChangesAsync ( ChangesOptions . None , 1 , ChangeStatus . Submitted , $"//{settings.ClientName}/..." ) ;
change = changes [ 0 ] . Number ;
2022-01-07 16:21:08 -05:00
}
else
{
2022-06-20 14:31:50 -04:00
throw new UserErrorException ( "Unknown change type for sync '{Change}'" , changeString ) ;
2022-01-07 16:21:08 -05:00
}
}
2022-06-20 14:31:50 -04:00
WorkspaceUpdateOptions options = syncOptions . SingleChange ? WorkspaceUpdateOptions . SyncSingleChange : WorkspaceUpdateOptions . Sync ;
if ( syncOptions . Build )
2022-01-07 16:21:08 -05:00
{
2022-06-20 14:31:50 -04:00
options | = WorkspaceUpdateOptions . Build ;
2022-01-07 16:21:08 -05:00
}
2022-06-20 14:31:50 -04:00
if ( syncOptions . ProjectFiles )
2022-01-11 12:54:38 -05:00
{
2022-06-20 14:31:50 -04:00
options | = WorkspaceUpdateOptions . GenerateProjectFiles ;
2022-01-11 12:54:38 -05:00
}
2022-06-20 14:31:50 -04:00
if ( syncOptions . Clobber )
2022-01-14 10:51:47 -05:00
{
2022-06-20 14:31:50 -04:00
options | = WorkspaceUpdateOptions . Clobber ;
2022-01-14 10:51:47 -05:00
}
2022-06-20 14:31:50 -04:00
if ( syncOptions . Refilter )
2022-01-14 11:00:17 -05:00
{
2022-06-20 14:31:50 -04:00
options | = WorkspaceUpdateOptions . Refilter ;
2022-01-14 11:00:17 -05:00
}
2022-06-20 14:31:50 -04:00
options | = WorkspaceUpdateOptions . RemoveFilteredFiles ;
2022-01-07 16:21:08 -05:00
2022-06-20 14:31:50 -04:00
string [ ] syncFilter = ReadSyncFilter ( settings , context . UserSettings , projectConfig ) ;
2022-01-07 16:21:08 -05:00
2022-06-20 14:31:50 -04:00
WorkspaceUpdateContext updateContext = new WorkspaceUpdateContext ( change , options , BuildConfig . Development , syncFilter , projectSettings . BuildSteps , null ) ;
2022-08-25 12:00:10 -04:00
if ( syncOptions . Binaries )
{
List < PerforceArchiveInfo > archives = await PerforceArchive . EnumerateAsync ( perforceClient , projectConfig , state . ProjectIdentifier , CancellationToken . None ) ;
PerforceArchiveInfo ? editorArchiveInfo = archives . FirstOrDefault ( x = > x . Name = = IArchiveInfo . EditorArchiveType ) ;
if ( editorArchiveInfo = = null )
{
throw new UserErrorException ( "No editor archives found for project" ) ;
}
KeyValuePair < int , string > revision = editorArchiveInfo . ChangeNumberToFileRevision . LastOrDefault ( x = > x . Key < = change ) ;
if ( revision . Key = = 0 )
{
throw new UserErrorException ( $"No editor archives found for CL {change}" ) ;
}
if ( revision . Key < change )
{
int lastChange = revision . Key ;
List < ChangesRecord > changeRecords = await perforceClient . GetChangesAsync ( ChangesOptions . None , 1 , ChangeStatus . Submitted , $"//{settings.ClientName}/...@{revision.Key + 1},{change}" ) ;
foreach ( ChangesRecord changeRecord in changeRecords . OrderBy ( x = > x . Number ) )
{
if ( await IsCodeChangeAsync ( perforceClient , changeRecord . Number ) )
{
if ( syncLatest )
{
updateContext . ChangeNumber = lastChange ;
}
else
{
throw new UserErrorException ( $"No editor binaries found for CL {change} (last archive at CL {revision.Key}, but CL {changeRecord.Number} is a code change)" ) ;
}
break ;
}
change = changeRecord . Number ;
}
}
updateContext . Options | = WorkspaceUpdateOptions . SyncArchives ;
updateContext . ArchiveTypeToArchive [ IArchiveInfo . EditorArchiveType ] = Tuple . Create < IArchiveInfo , string > ( editorArchiveInfo , revision . Value ) ;
}
2022-01-07 16:21:08 -05:00
2022-06-20 14:31:50 -04:00
WorkspaceUpdate update = new WorkspaceUpdate ( updateContext ) ;
( WorkspaceUpdateResult result , string message ) = await update . ExecuteAsync ( perforceClient . Settings , projectInfo , state , context . Logger , CancellationToken . None ) ;
if ( result = = WorkspaceUpdateResult . FilesToClobber )
2022-01-14 10:51:47 -05:00
{
2022-06-20 14:31:50 -04:00
logger . LogWarning ( "The following files are modified in your workspace:" ) ;
foreach ( string file in updateContext . ClobberFiles . Keys . OrderBy ( x = > x ) )
2022-01-14 10:51:47 -05:00
{
2022-06-20 14:31:50 -04:00
logger . LogWarning ( " {File}" , file ) ;
2022-01-14 10:51:47 -05:00
}
2022-06-20 14:31:50 -04:00
logger . LogWarning ( "Use -Clobber to overwrite" ) ;
2022-01-14 10:51:47 -05:00
}
2022-06-20 14:31:50 -04:00
else if ( result ! = WorkspaceUpdateResult . Success )
2022-01-11 12:00:10 -05:00
{
2022-06-20 14:31:50 -04:00
logger . LogError ( "{Message} (Result: {Result})" , message , result ) ;
2022-01-11 15:39:30 -05:00
}
2022-06-01 18:29:16 -04:00
2022-06-20 14:31:50 -04:00
state . SetLastSyncState ( result , updateContext , message ) ;
state . Save ( logger ) ;
2022-01-11 15:39:30 -05:00
}
}
class ClientsCommand : Command
{
public class ClientsOptions : ServerOptions
{
}
2022-06-20 14:31:50 -04:00
public override async Task ExecuteAsync ( CommandContext context )
2022-01-11 15:39:30 -05:00
{
2022-06-20 14:31:50 -04:00
ILogger logger = context . Logger ;
2022-01-11 15:39:30 -05:00
2022-06-20 14:31:50 -04:00
ClientsOptions options = context . Arguments . ApplyTo < ClientsOptions > ( logger ) ;
context . Arguments . CheckAllArgumentsUsed ( ) ;
2022-01-11 15:39:30 -05:00
2022-06-20 14:31:50 -04:00
using IPerforceConnection perforceClient = await ConnectAsync ( options . ServerAndPort , options . UserName , null , context . LoggerFactory ) ;
InfoRecord info = await perforceClient . GetInfoAsync ( InfoOptions . ShortOutput ) ;
2022-01-11 15:39:30 -05:00
2022-06-20 14:31:50 -04:00
List < ClientsRecord > clients = await perforceClient . GetClientsAsync ( EpicGames . Perforce . ClientsOptions . None , perforceClient . Settings . UserName ) ;
foreach ( ClientsRecord client in clients )
2022-01-11 15:39:30 -05:00
{
2022-06-20 14:31:50 -04:00
if ( String . Equals ( info . ClientHost , client . Host , StringComparison . OrdinalIgnoreCase ) )
2022-01-11 15:39:30 -05:00
{
2022-06-20 14:31:50 -04:00
logger . LogInformation ( "{Client,-50} {Root}" , client . Name , client . Root ) ;
2022-01-11 15:39:30 -05:00
}
}
}
}
class RunCommand : Command
{
2022-06-20 14:31:50 -04:00
public override async Task ExecuteAsync ( CommandContext context )
2022-01-11 15:39:30 -05:00
{
2022-06-20 14:31:50 -04:00
ILogger logger = context . Logger ;
2022-01-11 15:39:30 -05:00
2022-06-20 14:31:50 -04:00
UserWorkspaceSettings settings = ReadRequiredUserWorkspaceSettings ( ) ;
using IPerforceConnection perforceClient = await ConnectAsync ( settings , context . LoggerFactory ) ;
UserWorkspaceState state = await ReadWorkspaceState ( perforceClient , settings , context . UserSettings , logger ) ;
2022-01-11 15:39:30 -05:00
2022-06-20 14:31:50 -04:00
ProjectInfo projectInfo = state . CreateProjectInfo ( ) ;
ConfigFile projectConfig = await ConfigUtils . ReadProjectConfigFileAsync ( perforceClient , projectInfo , logger , CancellationToken . None ) ;
2022-01-11 15:39:30 -05:00
2022-06-20 14:31:50 -04:00
FileReference receiptFile = ConfigUtils . GetEditorReceiptFile ( projectInfo , projectConfig , EditorConfig ) ;
logger . LogDebug ( "Receipt file: {Receipt}" , receiptFile ) ;
2022-01-11 15:39:30 -05:00
2022-06-20 14:31:50 -04:00
if ( ! ConfigUtils . TryReadEditorReceipt ( projectInfo , receiptFile , out TargetReceipt ? receipt ) | | String . IsNullOrEmpty ( receipt . Launch ) )
2022-01-11 15:39:30 -05:00
{
2022-06-20 14:31:50 -04:00
throw new UserErrorException ( "The editor needs to be built before you can run it. (Missing {ReceiptFile})." , receiptFile ) ;
2022-01-11 15:39:30 -05:00
}
2022-06-20 14:31:50 -04:00
if ( ! File . Exists ( receipt . Launch ) )
2022-01-11 15:39:30 -05:00
{
2022-06-20 14:31:50 -04:00
throw new UserErrorException ( "The editor needs to be built before you can run it. (Missing {LaunchFile})." , receipt . Launch ) ;
2022-01-11 15:39:30 -05:00
}
2022-06-20 14:31:50 -04:00
List < string > launchArguments = new List < string > ( ) ;
if ( settings . LocalProjectPath . HasExtension ( ".uproject" ) )
2022-01-11 15:39:30 -05:00
{
2022-06-20 14:31:50 -04:00
launchArguments . Add ( $"\" { settings . LocalProjectPath } \ "" ) ;
2022-01-11 15:39:30 -05:00
}
if ( EditorConfig = = BuildConfig . Debug | | EditorConfig = = BuildConfig . DebugGame )
{
2022-06-20 14:31:50 -04:00
launchArguments . Append ( " -debug" ) ;
2022-01-11 15:39:30 -05:00
}
2022-06-20 14:31:50 -04:00
for ( int idx = 0 ; idx < context . Arguments . Count ; idx + + )
2022-01-11 15:39:30 -05:00
{
2022-06-20 14:31:50 -04:00
if ( ! context . Arguments . HasBeenUsed ( idx ) )
2022-01-11 15:39:30 -05:00
{
2022-06-20 14:31:50 -04:00
launchArguments . Add ( context . Arguments [ idx ] ) ;
2022-01-11 15:39:30 -05:00
}
}
2022-06-20 14:31:50 -04:00
string commandLine = CommandLineArguments . Join ( launchArguments ) ;
logger . LogInformation ( "Spawning: {LaunchFile} {CommandLine}" , CommandLineArguments . Quote ( receipt . Launch ) , commandLine ) ;
2022-01-11 15:39:30 -05:00
2022-06-20 14:31:50 -04:00
if ( ! Utility . SpawnProcess ( receipt . Launch , commandLine ) )
2022-01-11 15:39:30 -05:00
{
2022-06-20 14:31:50 -04:00
logger . LogError ( "Unable to spawn {0} {1}" , receipt . Launch , launchArguments . ToString ( ) ) ;
2022-01-11 12:00:10 -05:00
}
2022-01-07 16:21:08 -05:00
}
}
2022-01-10 09:33:30 -05:00
class ChangesCommand : Command
{
2022-06-20 14:31:50 -04:00
public override async Task ExecuteAsync ( CommandContext context )
2022-01-10 09:33:30 -05:00
{
2022-06-20 14:31:50 -04:00
ILogger logger = context . Logger ;
2022-01-10 09:33:30 -05:00
2022-06-20 14:31:50 -04:00
int count = context . Arguments . GetIntegerOrDefault ( "-Count=" , 10 ) ;
int lineCount = context . Arguments . GetIntegerOrDefault ( "-Lines=" , 3 ) ;
context . Arguments . CheckAllArgumentsUsed ( context . Logger ) ;
2022-01-10 09:33:30 -05:00
2022-06-20 14:31:50 -04:00
UserWorkspaceSettings settings = ReadRequiredUserWorkspaceSettings ( ) ;
using IPerforceConnection perforceClient = await ConnectAsync ( settings , context . LoggerFactory ) ;
2022-01-10 09:33:30 -05:00
2022-06-20 14:31:50 -04:00
List < ChangesRecord > changes = await perforceClient . GetChangesAsync ( ChangesOptions . None , count , ChangeStatus . Submitted , $"//{settings.ClientName}/..." ) ;
foreach ( IEnumerable < ChangesRecord > changesBatch in changes . Batch ( 10 ) )
2022-01-10 09:33:30 -05:00
{
2022-06-20 14:31:50 -04:00
List < DescribeRecord > describeRecords = await perforceClient . DescribeAsync ( changesBatch . Select ( x = > x . Number ) . ToArray ( ) ) ;
2022-01-10 09:33:30 -05:00
2022-06-20 14:31:50 -04:00
logger . LogInformation ( " Change Type Author Description" ) ;
foreach ( DescribeRecord describeRecord in describeRecords )
2022-01-10 09:33:30 -05:00
{
2022-06-20 14:31:50 -04:00
PerforceChangeDetails details = new PerforceChangeDetails ( describeRecord ) ;
2022-01-10 09:33:30 -05:00
2022-06-20 14:31:50 -04:00
string type ;
if ( details . ContainsCode )
2022-01-10 09:33:30 -05:00
{
2022-06-20 14:31:50 -04:00
if ( details . ContainsContent )
2022-01-10 09:33:30 -05:00
{
2022-06-20 14:31:50 -04:00
type = "Both" ;
2022-01-10 09:33:30 -05:00
}
else
{
2022-06-20 14:31:50 -04:00
type = "Code" ;
2022-01-10 09:33:30 -05:00
}
}
else
{
2022-06-20 14:31:50 -04:00
if ( details . ContainsContent )
2022-01-10 09:33:30 -05:00
{
2022-06-20 14:31:50 -04:00
type = "Content" ;
2022-01-10 09:33:30 -05:00
}
else
{
2022-06-20 14:31:50 -04:00
type = "None" ;
2022-01-10 09:33:30 -05:00
}
}
2022-06-20 14:31:50 -04:00
string author = StringUtils . Truncate ( describeRecord . User , 15 ) ;
2022-01-10 09:33:30 -05:00
2022-06-20 14:31:50 -04:00
List < string > lines = StringUtils . WordWrap ( details . Description , Math . Max ( ConsoleUtils . WindowWidth - 40 , 10 ) ) . ToList ( ) ;
if ( lines . Count = = 0 )
2022-01-10 09:33:30 -05:00
{
2022-06-20 14:31:50 -04:00
lines . Add ( String . Empty ) ;
2022-01-10 09:33:30 -05:00
}
2022-09-01 18:34:46 -04:00
lineCount = Math . Min ( lineCount , lines . Count ) ;
2022-06-20 14:31:50 -04:00
logger . LogInformation ( " {Change,-9} {Type,-8} {Author,-15} {Description}" , describeRecord . Number , type , author , lines [ 0 ] ) ;
for ( int lineIndex = 1 ; lineIndex < lineCount ; lineIndex + + )
2022-01-10 09:33:30 -05:00
{
2022-06-20 14:31:50 -04:00
logger . LogInformation ( " {Description}" , lines [ lineIndex ] ) ;
2022-01-10 09:33:30 -05:00
}
}
}
}
}
2022-01-07 16:21:08 -05:00
class ConfigCommand : Command
{
2022-06-20 14:31:50 -04:00
public override Task ExecuteAsync ( CommandContext context )
2022-01-07 16:21:08 -05:00
{
2022-06-20 14:31:50 -04:00
ILogger logger = context . Logger ;
2022-01-07 16:21:08 -05:00
2022-06-20 14:31:50 -04:00
UserWorkspaceSettings settings = ReadRequiredUserWorkspaceSettings ( ) ;
if ( ! context . Arguments . GetUnusedArguments ( ) . Any ( ) )
2022-01-07 16:21:08 -05:00
{
2022-06-20 14:31:50 -04:00
ProcessStartInfo startInfo = new ProcessStartInfo ( ) ;
startInfo . FileName = settings . ConfigFile . FullName ;
startInfo . UseShellExecute = true ;
using ( Process ? editor = Process . Start ( startInfo ) )
2022-01-07 16:21:08 -05:00
{
2022-06-20 14:31:50 -04:00
if ( editor ! = null )
2022-01-07 16:21:08 -05:00
{
2022-06-20 14:31:50 -04:00
editor . WaitForExit ( ) ;
2022-01-07 16:21:08 -05:00
}
}
}
else
{
2022-06-20 14:31:50 -04:00
ProjectConfigOptions options = new ProjectConfigOptions ( ) ;
context . Arguments . ApplyTo ( options ) ;
context . Arguments . CheckAllArgumentsUsed ( context . Logger ) ;
2022-01-07 16:21:08 -05:00
2022-06-20 14:31:50 -04:00
options . ApplyTo ( settings ) ;
settings . Save ( logger ) ;
2022-01-07 16:21:08 -05:00
2022-06-20 14:31:50 -04:00
logger . LogInformation ( "Updated {ConfigFile}" , settings . ConfigFile ) ;
2022-01-07 16:21:08 -05:00
}
2022-01-08 15:04:19 -05:00
return Task . CompletedTask ;
2022-01-07 16:21:08 -05:00
}
}
2022-01-10 13:44:32 -05:00
class FilterCommand : Command
{
class FilterCommandOptions
{
[CommandLine("-Reset")]
public bool Reset = false ;
[CommandLine("-Include=")]
public List < string > Include { get ; set ; } = new List < string > ( ) ;
[CommandLine("-Exclude=")]
public List < string > Exclude { get ; set ; } = new List < string > ( ) ;
[CommandLine("-View=", ListSeparator = ';')]
public List < string > ? View { get ; set ; }
[CommandLine("-AddView=", ListSeparator = ';')]
public List < string > AddView { get ; set ; } = new List < string > ( ) ;
[CommandLine("-RemoveView=", ListSeparator = ';')]
public List < string > RemoveView { get ; set ; } = new List < string > ( ) ;
[CommandLine("-AllProjects", Value = "true")]
[CommandLine("-OnlyCurrent", Value = "false")]
public bool? AllProjects = null ;
[CommandLine("-GpfAllProjects", Value ="true")]
[CommandLine("-GpfOnlyCurrent", Value = "false")]
public bool? AllProjectsInSln = null ;
[CommandLine("-Global")]
public bool Global { get ; set ; }
}
2022-06-20 14:31:50 -04:00
public override async Task ExecuteAsync ( CommandContext context )
2022-01-10 13:44:32 -05:00
{
2022-06-20 14:31:50 -04:00
ILogger logger = context . Logger ;
2022-01-10 13:44:32 -05:00
2022-06-20 14:31:50 -04:00
UserWorkspaceSettings workspaceSettings = ReadRequiredUserWorkspaceSettings ( ) ;
using IPerforceConnection perforceClient = await ConnectAsync ( workspaceSettings , context . LoggerFactory ) ;
UserWorkspaceState workspaceState = await ReadWorkspaceState ( perforceClient , workspaceSettings , context . UserSettings , logger ) ;
ProjectInfo projectInfo = workspaceState . CreateProjectInfo ( ) ;
2022-01-10 13:44:32 -05:00
2022-06-20 14:31:50 -04:00
ConfigFile projectConfig = await ConfigUtils . ReadProjectConfigFileAsync ( perforceClient , projectInfo , logger , CancellationToken . None ) ;
Dictionary < Guid , WorkspaceSyncCategory > syncCategories = ConfigUtils . GetSyncCategories ( projectConfig ) ;
2022-01-10 13:44:32 -05:00
2022-06-20 14:31:50 -04:00
FilterSettings globalFilter = context . UserSettings . Global . Filter ;
FilterSettings workspaceFilter = workspaceSettings . Filter ;
2022-01-10 13:44:32 -05:00
2022-06-20 14:31:50 -04:00
FilterCommandOptions options = context . Arguments . ApplyTo < FilterCommandOptions > ( logger ) ;
context . Arguments . CheckAllArgumentsUsed ( context . Logger ) ;
2022-01-10 13:44:32 -05:00
2022-06-20 14:31:50 -04:00
if ( options . Global )
2022-01-10 13:44:32 -05:00
{
2022-06-20 14:31:50 -04:00
ApplyCommandOptions ( context . UserSettings . Global . Filter , options , syncCategories . Values , logger ) ;
context . UserSettings . Save ( logger ) ;
2022-01-10 13:44:32 -05:00
}
else
{
2022-06-20 14:31:50 -04:00
ApplyCommandOptions ( workspaceSettings . Filter , options , syncCategories . Values , logger ) ;
workspaceSettings . Save ( logger ) ;
2022-01-10 13:44:32 -05:00
}
2022-06-20 14:31:50 -04:00
Dictionary < Guid , bool > globalCategories = globalFilter . GetCategories ( ) ;
Dictionary < Guid , bool > workspaceCategories = workspaceFilter . GetCategories ( ) ;
2022-01-10 13:44:32 -05:00
2022-06-20 14:31:50 -04:00
logger . LogInformation ( "Categories:" ) ;
foreach ( WorkspaceSyncCategory syncCategory in syncCategories . Values )
2022-01-10 13:44:32 -05:00
{
2022-06-20 14:31:50 -04:00
bool enabled ;
2022-01-10 13:44:32 -05:00
2022-06-20 14:31:50 -04:00
string scope = "(Default)" ;
if ( globalCategories . TryGetValue ( syncCategory . UniqueId , out enabled ) )
2022-01-10 13:44:32 -05:00
{
2022-06-20 14:31:50 -04:00
scope = "(Global)" ;
2022-01-10 13:44:32 -05:00
}
2022-06-20 14:31:50 -04:00
else if ( workspaceCategories . TryGetValue ( syncCategory . UniqueId , out enabled ) )
2022-01-10 13:44:32 -05:00
{
2022-06-20 14:31:50 -04:00
scope = "(Workspace)" ;
2022-01-10 13:44:32 -05:00
}
else
{
2022-06-20 14:31:50 -04:00
enabled = syncCategory . Enable ;
2022-01-10 13:44:32 -05:00
}
2022-06-20 14:31:50 -04:00
logger . LogInformation ( " {Id,30} {Enabled,3} {Scope,-9} {Name}" , syncCategory . UniqueId , enabled ? "Yes" : "No" , scope , syncCategory . Name ) ;
2022-01-10 13:44:32 -05:00
}
2022-06-20 14:31:50 -04:00
if ( globalFilter . View . Count > 0 )
2022-01-10 13:44:32 -05:00
{
2022-06-20 14:31:50 -04:00
logger . LogInformation ( "" ) ;
logger . LogInformation ( "Global View:" ) ;
foreach ( string line in globalFilter . View )
2022-01-10 13:44:32 -05:00
{
2022-06-20 14:31:50 -04:00
logger . LogInformation ( " {Line}" , line ) ;
2022-01-10 13:44:32 -05:00
}
}
2022-06-20 14:31:50 -04:00
if ( workspaceFilter . View . Count > 0 )
2022-01-10 13:44:32 -05:00
{
2022-06-20 14:31:50 -04:00
logger . LogInformation ( "" ) ;
logger . LogInformation ( "Workspace View:" ) ;
foreach ( string line in workspaceFilter . View )
2022-01-10 13:44:32 -05:00
{
2022-06-20 14:31:50 -04:00
logger . LogInformation ( " {Line}" , line ) ;
2022-01-10 13:44:32 -05:00
}
}
2022-06-20 14:31:50 -04:00
string [ ] filter = ReadSyncFilter ( workspaceSettings , context . UserSettings , projectConfig ) ;
2022-01-10 13:44:32 -05:00
2022-06-20 14:31:50 -04:00
logger . LogInformation ( "" ) ;
logger . LogInformation ( "Combined view:" ) ;
foreach ( string filterLine in filter )
2022-01-10 13:44:32 -05:00
{
2022-06-20 14:31:50 -04:00
logger . LogInformation ( " {FilterLine}" , filterLine ) ;
2022-01-10 13:44:32 -05:00
}
}
2022-06-20 14:31:50 -04:00
static void ApplyCommandOptions ( FilterSettings settings , FilterCommandOptions commandOptions , IEnumerable < WorkspaceSyncCategory > syncCategories , ILogger logger )
2022-01-10 13:44:32 -05:00
{
2022-06-20 14:31:50 -04:00
if ( commandOptions . Reset )
2022-01-10 13:44:32 -05:00
{
2022-06-20 14:31:50 -04:00
logger . LogInformation ( "Resetting settings..." ) ;
settings . Reset ( ) ;
2022-01-10 13:44:32 -05:00
}
2022-06-20 14:31:50 -04:00
HashSet < Guid > includeCategories = new HashSet < Guid > ( commandOptions . Include . Select ( x = > GetCategoryId ( x , syncCategories ) ) ) ;
HashSet < Guid > excludeCategories = new HashSet < Guid > ( commandOptions . Exclude . Select ( x = > GetCategoryId ( x , syncCategories ) ) ) ;
2022-01-10 13:44:32 -05:00
2022-06-20 14:31:50 -04:00
Guid id = includeCategories . FirstOrDefault ( x = > excludeCategories . Contains ( x ) ) ;
if ( id ! = Guid . Empty )
2022-01-10 13:44:32 -05:00
{
2022-06-20 14:31:50 -04:00
throw new UserErrorException ( "Category {Id} cannot be both included and excluded" , id ) ;
2022-01-10 13:44:32 -05:00
}
2022-06-20 14:31:50 -04:00
includeCategories . ExceptWith ( settings . IncludeCategories ) ;
settings . IncludeCategories . AddRange ( includeCategories ) ;
2022-01-10 13:44:32 -05:00
2022-06-20 14:31:50 -04:00
excludeCategories . ExceptWith ( settings . ExcludeCategories ) ;
settings . ExcludeCategories . AddRange ( excludeCategories ) ;
2022-01-10 13:44:32 -05:00
2022-06-20 14:31:50 -04:00
if ( commandOptions . View ! = null )
2022-01-10 13:44:32 -05:00
{
2022-06-20 14:31:50 -04:00
settings . View = commandOptions . View ;
2022-01-10 13:44:32 -05:00
}
2022-06-20 14:31:50 -04:00
if ( commandOptions . RemoveView . Count > 0 )
2022-01-10 13:44:32 -05:00
{
2022-06-20 14:31:50 -04:00
HashSet < string > viewRemove = new HashSet < string > ( commandOptions . RemoveView , StringComparer . OrdinalIgnoreCase ) ;
settings . View . RemoveAll ( x = > viewRemove . Contains ( x ) ) ;
2022-01-10 13:44:32 -05:00
}
2022-06-20 14:31:50 -04:00
if ( commandOptions . AddView . Count > 0 )
2022-01-10 13:44:32 -05:00
{
2022-06-20 14:31:50 -04:00
HashSet < string > viewLines = new HashSet < string > ( settings . View , StringComparer . OrdinalIgnoreCase ) ;
settings . View . AddRange ( commandOptions . AddView . Where ( x = > ! viewLines . Contains ( x ) ) ) ;
2022-01-10 13:44:32 -05:00
}
2022-06-20 14:31:50 -04:00
settings . AllProjects = commandOptions . AllProjects ? ? settings . AllProjects ;
settings . AllProjectsInSln = commandOptions . AllProjectsInSln ? ? settings . AllProjectsInSln ;
2022-01-10 13:44:32 -05:00
}
2022-06-20 14:31:50 -04:00
static Guid GetCategoryId ( string text , IEnumerable < WorkspaceSyncCategory > syncCategories )
2022-01-10 13:44:32 -05:00
{
2022-06-20 14:31:50 -04:00
Guid id ;
if ( Guid . TryParse ( text , out id ) )
2022-01-10 13:44:32 -05:00
{
2022-06-20 14:31:50 -04:00
return id ;
2022-01-10 13:44:32 -05:00
}
2022-06-20 14:31:50 -04:00
WorkspaceSyncCategory ? category = syncCategories . FirstOrDefault ( x = > x . Name . Equals ( text , StringComparison . OrdinalIgnoreCase ) ) ;
if ( category ! = null )
2022-01-10 13:44:32 -05:00
{
2022-06-20 14:31:50 -04:00
return category . UniqueId ;
2022-01-10 13:44:32 -05:00
}
2022-06-20 14:31:50 -04:00
throw new UserErrorException ( "Unable to find category '{Category}'" , text ) ;
2022-01-10 13:44:32 -05:00
}
}
2022-01-07 16:21:08 -05:00
class BuildCommand : Command
{
2022-06-20 14:31:50 -04:00
public override async Task ExecuteAsync ( CommandContext context )
2022-01-07 16:21:08 -05:00
{
2022-06-20 14:31:50 -04:00
ILogger logger = context . Logger ;
context . Arguments . TryGetPositionalArgument ( out string? target ) ;
bool listOnly = context . Arguments . HasOption ( "-List" ) ;
context . Arguments . CheckAllArgumentsUsed ( ) ;
2022-01-07 16:21:08 -05:00
2022-06-20 14:31:50 -04:00
UserWorkspaceSettings settings = ReadRequiredUserWorkspaceSettings ( ) ;
using IPerforceConnection perforceClient = await ConnectAsync ( settings , context . LoggerFactory ) ;
UserWorkspaceState state = await ReadWorkspaceState ( perforceClient , settings , context . UserSettings , logger ) ;
2022-01-07 16:21:08 -05:00
2022-06-20 14:31:50 -04:00
ProjectInfo projectInfo = state . CreateProjectInfo ( ) ;
2022-01-07 16:21:08 -05:00
2022-06-20 14:31:50 -04:00
if ( listOnly )
2022-01-07 16:21:08 -05:00
{
2022-06-20 14:31:50 -04:00
ConfigFile projectConfig = await ConfigUtils . ReadProjectConfigFileAsync ( perforceClient , projectInfo , logger , CancellationToken . None ) ;
2022-01-07 16:21:08 -05:00
2022-06-20 14:31:50 -04:00
FileReference editorTarget = ConfigUtils . GetEditorTargetFile ( projectInfo , projectConfig ) ;
2022-01-07 16:21:08 -05:00
2022-06-20 14:31:50 -04:00
Dictionary < Guid , ConfigObject > buildStepObjects = ConfigUtils . GetDefaultBuildStepObjects ( projectInfo , editorTarget . GetFileNameWithoutAnyExtensions ( ) , EditorConfig , projectConfig , false ) ;
2022-01-07 16:21:08 -05:00
2022-06-20 14:31:50 -04:00
logger . LogInformation ( "Available build steps:" ) ;
logger . LogInformation ( "" ) ;
logger . LogInformation ( " Id | Description | Type | Enabled" ) ;
logger . LogInformation ( " -------------------------------------|------------------------------------------|------------|-----------------" ) ;
foreach ( BuildStep buildStep in buildStepObjects . Values . Select ( x = > new BuildStep ( x ) ) . OrderBy ( x = > x . OrderIndex ) )
2022-01-07 16:21:08 -05:00
{
2022-06-20 14:31:50 -04:00
logger . LogInformation ( " {Id,-36} | {Name,-40} | {Type,-10} | {Enabled,-8}" , buildStep . UniqueId , buildStep . Description , buildStep . Type , buildStep . NormalSync ) ;
2022-01-07 16:21:08 -05:00
}
return ;
}
2022-06-20 14:31:50 -04:00
HashSet < Guid > ? steps = null ;
if ( target ! = null )
2022-01-07 16:21:08 -05:00
{
2022-06-20 14:31:50 -04:00
Guid id ;
if ( ! Guid . TryParse ( target , out id ) )
2022-01-07 16:21:08 -05:00
{
2022-06-20 14:31:50 -04:00
logger . LogError ( "Unable to parse '{Target}' as a GUID. Pass -List to show all available build steps and their identifiers." , target ) ;
2022-01-07 16:21:08 -05:00
}
2022-06-20 14:31:50 -04:00
steps = new HashSet < Guid > { id } ;
2022-01-07 16:21:08 -05:00
}
2022-06-20 14:31:50 -04:00
WorkspaceUpdateContext updateContext = new WorkspaceUpdateContext ( state . CurrentChangeNumber , WorkspaceUpdateOptions . Build , BuildConfig . Development , null , new List < ConfigObject > ( ) , steps ) ;
2022-01-07 16:21:08 -05:00
2022-06-20 14:31:50 -04:00
WorkspaceUpdate update = new WorkspaceUpdate ( updateContext ) ;
await update . ExecuteAsync ( perforceClient . Settings , projectInfo , state , context . Logger , CancellationToken . None ) ;
2022-01-07 16:21:08 -05:00
}
}
class StatusCommand : Command
{
2022-06-20 14:31:50 -04:00
public override async Task ExecuteAsync ( CommandContext context )
2022-01-07 16:21:08 -05:00
{
2022-06-20 14:31:50 -04:00
ILogger logger = context . Logger ;
bool update = context . Arguments . HasOption ( "-Update" ) ;
context . Arguments . CheckAllArgumentsUsed ( ) ;
2022-01-07 16:21:08 -05:00
2022-06-20 14:31:50 -04:00
UserWorkspaceSettings settings = ReadRequiredUserWorkspaceSettings ( ) ;
logger . LogInformation ( "User: {UserName}" , settings . UserName ) ;
logger . LogInformation ( "Server: {ServerAndPort}" , settings . ServerAndPort ) ;
logger . LogInformation ( "Project: {ClientProjectPath}" , settings . ClientProjectPath ) ;
2022-01-07 16:21:08 -05:00
2022-06-20 14:31:50 -04:00
using IPerforceConnection perforceClient = await ConnectAsync ( settings , context . LoggerFactory ) ;
2022-01-07 16:21:08 -05:00
2022-06-20 14:31:50 -04:00
UserWorkspaceState state = await ReadWorkspaceState ( perforceClient , settings , context . UserSettings , logger ) ;
if ( update )
2022-01-07 16:21:08 -05:00
{
2022-06-20 14:31:50 -04:00
ProjectInfo newProjectInfo = await ProjectInfo . CreateAsync ( perforceClient , settings , CancellationToken . None ) ;
state . UpdateCachedProjectInfo ( newProjectInfo , settings . LastModifiedTimeUtc ) ;
2022-01-07 16:21:08 -05:00
}
2022-06-20 14:31:50 -04:00
string streamOrBranchName = state . StreamName ? ? settings . BranchPath . TrimStart ( '/' ) ;
if ( state . LastSyncResultMessage = = null )
2022-01-07 16:21:08 -05:00
{
2022-06-20 14:31:50 -04:00
logger . LogInformation ( "Not currently synced to {Stream}" , streamOrBranchName ) ;
2022-01-07 16:21:08 -05:00
}
2022-06-20 14:31:50 -04:00
else if ( state . LastSyncResult = = WorkspaceUpdateResult . Success )
2022-01-07 16:21:08 -05:00
{
2022-06-20 14:31:50 -04:00
logger . LogInformation ( "Synced to {Stream} CL {Change}" , streamOrBranchName , state . LastSyncChangeNumber ) ;
2022-01-07 16:21:08 -05:00
}
else
{
2022-06-20 14:31:50 -04:00
logger . LogWarning ( "Last sync to {Stream} CL {Change} failed: {Result}" , streamOrBranchName , state . LastSyncChangeNumber , state . LastSyncResultMessage ) ;
2022-01-07 16:21:08 -05:00
}
}
}
2022-07-01 04:39:56 -04:00
class LoginCommand : Command
{
public override async Task ExecuteAsync ( CommandContext context )
{
ILogger logger = context . Logger ;
// Get the positional argument indicating the file to look for
if ( ! context . Arguments . TryGetPositionalArgument ( out string? providerIdentifier ) )
{
throw new UserErrorException ( "Missing provider identifier to login to." ) ;
}
context . Arguments . CheckAllArgumentsUsed ( ) ;
UserWorkspaceSettings settings = ReadRequiredUserWorkspaceSettings ( ) ;
// Find the valid config file paths
DirectoryInfo engineDir = DirectoryReference . Combine ( settings . RootDir , "Engine" ) . ToDirectoryInfo ( ) ;
DirectoryInfo gameDir = new DirectoryInfo ( settings . ProjectPath ) ;
using ITokenStore tokenStore = TokenStoreFactory . CreateTokenStore ( ) ;
IConfiguration providerConfiguration = ProviderConfigurationFactory . ReadConfiguration ( engineDir , gameDir ) ;
OidcTokenManager oidcTokenManager = OidcTokenManager . CreateTokenManager ( providerConfiguration , tokenStore , new List < string > ( ) { providerIdentifier } ) ;
OidcTokenInfo result = await oidcTokenManager . Login ( providerIdentifier ) ;
logger . LogInformation ( "Logged in to provider {ProviderIdentifier}" , providerIdentifier ) ;
}
}
2022-01-07 16:21:08 -05:00
class SwitchCommand : Command
{
2022-06-20 14:31:50 -04:00
public override async Task ExecuteAsync ( CommandContext context )
2022-01-07 16:21:08 -05:00
{
// Get the positional argument indicating the file to look for
2022-06-20 14:31:50 -04:00
string? targetName ;
if ( ! context . Arguments . TryGetPositionalArgument ( out targetName ) )
2022-01-07 16:21:08 -05:00
{
throw new UserErrorException ( "Missing stream or project name to switch to." ) ;
}
2022-06-20 14:31:50 -04:00
bool force = targetName . StartsWith ( "//" , StringComparison . Ordinal ) & & context . Arguments . HasOption ( "-Force" ) ;
2022-01-07 16:21:08 -05:00
// Finish argument parsing
2022-06-20 14:31:50 -04:00
context . Arguments . CheckAllArgumentsUsed ( ) ;
2022-01-07 16:21:08 -05:00
// Get a connection to the client for this workspace
2022-06-20 14:31:50 -04:00
UserWorkspaceSettings settings = ReadRequiredUserWorkspaceSettings ( ) ;
using IPerforceConnection perforceClient = await ConnectAsync ( settings , context . LoggerFactory ) ;
2022-01-07 16:21:08 -05:00
// Check whether we're switching stream or project
2022-06-20 14:31:50 -04:00
if ( targetName . StartsWith ( "//" , StringComparison . Ordinal ) )
2022-01-07 16:21:08 -05:00
{
2022-06-20 14:31:50 -04:00
await SwitchStreamAsync ( perforceClient , targetName , force , context . Logger ) ;
2022-01-07 16:21:08 -05:00
}
else
{
2022-06-20 14:31:50 -04:00
await SwitchProjectAsync ( perforceClient , settings , targetName , context . Logger ) ;
2022-01-07 16:21:08 -05:00
}
}
2022-06-20 14:31:50 -04:00
public async Task SwitchStreamAsync ( IPerforceConnection perforceClient , string streamName , bool force , ILogger logger )
2022-01-07 16:21:08 -05:00
{
2022-06-20 14:31:50 -04:00
if ( ! force & & await perforceClient . OpenedAsync ( OpenedOptions . None , FileSpecList . Any ) . AnyAsync ( ) )
2022-01-07 16:21:08 -05:00
{
2022-06-20 14:31:50 -04:00
throw new UserErrorException ( "Client {ClientName} has files opened. Use -Force to switch anyway." , perforceClient . Settings . ClientName ! ) ;
2022-01-07 16:21:08 -05:00
}
2022-06-20 14:31:50 -04:00
await perforceClient . SwitchClientToStreamAsync ( streamName , SwitchClientOptions . IgnoreOpenFiles ) ;
2022-01-07 16:21:08 -05:00
2022-06-20 14:31:50 -04:00
logger . LogInformation ( "Switched to stream {StreamName}" , streamName ) ;
2022-01-07 16:21:08 -05:00
}
2022-06-20 14:31:50 -04:00
public async Task SwitchProjectAsync ( IPerforceConnection perforceClient , UserWorkspaceSettings settings , string projectName , ILogger logger )
2022-01-07 16:21:08 -05:00
{
2022-06-20 14:31:50 -04:00
settings . ProjectPath = await FindProjectPathAsync ( perforceClient , settings . ClientName , settings . BranchPath , projectName ) ;
settings . Save ( logger ) ;
logger . LogInformation ( "Switched to project {ProjectPath}" , settings . ClientProjectPath ) ;
2022-01-07 16:21:08 -05:00
}
}
2022-06-01 10:50:02 -04:00
class VersionCommand : Command
{
2022-06-20 14:31:50 -04:00
public override Task ExecuteAsync ( CommandContext context )
2022-06-01 10:50:02 -04:00
{
2022-06-20 14:31:50 -04:00
ILogger logger = context . Logger ;
2022-06-02 08:55:57 -04:00
2022-06-20 14:31:50 -04:00
AssemblyInformationalVersionAttribute ? version = Assembly . GetExecutingAssembly ( ) . GetCustomAttribute < AssemblyInformationalVersionAttribute > ( ) ;
logger . LogInformation ( "UnrealGameSync {Version}" , version ? . InformationalVersion ? ? "Unknown" ) ;
2022-06-02 08:55:57 -04:00
2022-06-01 10:50:02 -04:00
return Task . CompletedTask ;
}
}
2022-01-07 16:21:08 -05:00
}
}