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 ;
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-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 ; }
public UserErrorException ( LogEvent Event )
: base ( Event . ToString ( ) )
{
this . Event = Event ;
this . Code = 1 ;
}
public UserErrorException ( string Message , params object [ ] Args )
: this ( LogEvent . Create ( LogLevel . Error , Message , Args ) )
{
}
}
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 ; }
public CommandInfo ( string Name , Type Type , string Usage , string Brief )
{
this . Name = Name ;
this . Type = Type ;
this . Usage = Usage ;
this . Brief = Brief ;
}
}
static CommandInfo [ ] Commands =
{
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-01-11 15:39:30 -05:00
"ugs sync [change|'latest'] [-build] [-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."
)
} ;
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-01-10 10:58:21 -05:00
public CommandContext ( CommandLineArguments Arguments , ILogger Logger , ILoggerFactory LoggerFactory , GlobalSettingsFile UserSettings )
2022-01-07 16:21:08 -05:00
{
this . Arguments = Arguments ;
this . Logger = Logger ;
this . LoggerFactory = LoggerFactory ;
this . UserSettings = UserSettings ;
}
}
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-01-07 16:21:08 -05:00
public void ApplyTo ( UserWorkspaceSettings Settings )
{
if ( ServerAndPort ! = null )
{
Settings . ServerAndPort = ( ServerAndPort . Length = = 0 ) ? null : ServerAndPort ;
}
if ( UserName ! = null )
{
Settings . UserName = ( UserName . Length = = 0 ) ? null : UserName ;
}
}
}
class ProjectInitOptions : ProjectConfigOptions
{
[CommandLine("-Client=")]
public string? ClientName { get ; set ; }
[CommandLine("-Branch=")]
public string? BranchPath { get ; set ; }
[CommandLine("-Project=")]
public string? ProjectName { get ; set ; }
}
public static async Task < int > Main ( string [ ] RawArgs )
{
2022-01-09 19:54:09 -05:00
DirectoryReference GlobalConfigFolder ;
2022-01-10 10:58:21 -05:00
if ( RuntimeInformation . IsOSPlatform ( OSPlatform . Windows ) )
2022-01-09 19:54:09 -05:00
{
GlobalConfigFolder = DirectoryReference . Combine ( DirectoryReference . GetSpecialFolder ( Environment . SpecialFolder . LocalApplicationData ) ! , "UnrealGameSync" ) ;
}
else
{
GlobalConfigFolder = DirectoryReference . Combine ( DirectoryReference . GetSpecialFolder ( Environment . SpecialFolder . UserProfile ) ! , ".config" , "UnrealGameSync" ) ;
}
DirectoryReference . CreateDirectory ( GlobalConfigFolder ) ;
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
{
LogFolder = DirectoryReference . Combine ( DirectoryReference . GetSpecialFolder ( Environment . SpecialFolder . UserProfile ) ! , "Library" , "Logs" , "Unreal Engine" , "UnrealGameSync" ) ;
LogName = "UnrealGameSync-.log" ;
}
2022-01-10 10:58:21 -05:00
else if ( RuntimeInformation . IsOSPlatform ( OSPlatform . Linux ) )
2022-01-09 19:54:09 -05:00
{
LogFolder = DirectoryReference . GetSpecialFolder ( Environment . SpecialFolder . UserProfile ) ! ;
LogName = ".ugs-.log" ;
}
else
{
LogFolder = GlobalConfigFolder ;
LogName = "UnrealGameSyncCmd-.log" ;
}
2022-01-07 16:21:08 -05:00
Serilog . ILogger SerilogLogger = new LoggerConfiguration ( )
. Enrich . FromLogContext ( )
. WriteTo . Console ( Serilog . Events . LogEventLevel . Information , outputTemplate : "{Message:lj}{NewLine}" )
2022-01-09 19:54:09 -05: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 ( ) ;
using ILoggerFactory LoggerFactory = new Serilog . Extensions . Logging . SerilogLoggerFactory ( SerilogLogger , true ) ;
ILogger Logger = LoggerFactory . CreateLogger ( "Main" ) ;
try
{
2022-01-10 10:58:21 -05:00
GlobalSettingsFile Settings ;
2022-01-07 16:21:08 -05:00
if ( RuntimeInformation . IsOSPlatform ( OSPlatform . Windows ) )
{
2022-05-20 16:37:48 -04:00
Settings = UserSettings . Create ( GlobalConfigFolder , Logger ) ;
2022-01-10 10:58:21 -05:00
}
else
{
Settings = GlobalSettingsFile . Create ( FileReference . Combine ( GlobalConfigFolder , "Global.json" ) ) ;
2022-01-07 16:21:08 -05:00
}
CommandLineArguments Args = new CommandLineArguments ( RawArgs ) ;
string? CommandName ;
if ( ! Args . TryGetPositionalArgument ( out CommandName ) )
{
PrintHelp ( ) ;
return 0 ;
}
CommandInfo ? Command = Commands . FirstOrDefault ( x = > x . Name . Equals ( CommandName , StringComparison . OrdinalIgnoreCase ) ) ;
if ( Command = = null )
{
Logger . LogError ( $"unknown command '{CommandName}'" ) ;
Console . WriteLine ( ) ;
PrintHelp ( ) ;
return 1 ;
}
Command Instance = ( Command ) Activator . CreateInstance ( Command . Type ) ! ;
2022-01-10 10:58:21 -05:00
await Instance . ExecuteAsync ( new CommandContext ( Args , Logger , LoggerFactory , Settings ) ) ;
2022-01-07 16:21:08 -05:00
return 0 ;
}
catch ( UserErrorException Ex )
{
Logger . Log ( Ex . Event . Level , "{Message}" , Ex . Event . ToString ( ) ) ;
return Ex . Code ;
}
catch ( PerforceException Ex )
{
Logger . LogError ( Ex , "{Message}" , Ex . Message ) ;
return 1 ;
}
catch ( Exception Ex )
{
Logger . LogError ( Ex , "Unhandled exception.\n{Str}" , Ex ) ;
return 1 ;
}
}
static void PrintHelp ( )
{
Console . WriteLine ( "Usage:" ) ;
foreach ( CommandInfo Command in Commands )
{
Console . WriteLine ( ) ;
ConsoleUtils . WriteLineWithWordWrap ( Command . Usage , 2 , 8 ) ;
ConsoleUtils . WriteLineWithWordWrap ( Command . Brief , 4 , 4 ) ;
}
}
public static UserWorkspaceSettings ? ReadOptionalUserWorkspaceSettings ( )
{
DirectoryReference ? Dir = DirectoryReference . GetCurrentDirectory ( ) ;
for ( ; Dir ! = null ; Dir = Dir . ParentDirectory )
{
2022-01-14 10:51:47 -05:00
try
2022-01-07 16:21:08 -05:00
{
2022-01-14 10:51:47 -05:00
UserWorkspaceSettings ? Settings ;
if ( UserWorkspaceSettings . TryLoad ( Dir , out Settings ) )
{
return Settings ;
}
}
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 ( )
{
UserWorkspaceSettings ? Settings = ReadOptionalUserWorkspaceSettings ( ) ;
if ( Settings = = null )
{
throw new UserErrorException ( "Unable to find UGS workspace in current directory." ) ;
}
return Settings ;
}
2022-01-10 10:58:21 -05:00
public static async Task < UserWorkspaceState > ReadWorkspaceState ( IPerforceConnection PerforceClient , UserWorkspaceSettings Settings , GlobalSettingsFile UserSettings , ILogger Logger )
2022-01-07 16:21:08 -05:00
{
UserWorkspaceState State = UserSettings . FindOrAddWorkspaceState ( Settings ) ;
if ( State . SettingsTimeUtc ! = Settings . LastModifiedTimeUtc )
{
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 ( ) ;
}
return State ;
}
public static Task < IPerforceConnection > ConnectAsync ( string? ServerAndPort , string? UserName , string? ClientName , ILoggerFactory LoggerFactory )
{
PerforceSettings Settings = new PerforceSettings ( PerforceSettings . Default ) ;
Settings . ClientName = ClientName ;
Settings . PreferNativeClient = true ;
if ( ! String . IsNullOrEmpty ( ServerAndPort ) )
{
Settings . ServerAndPort = ServerAndPort ;
}
if ( ! String . IsNullOrEmpty ( UserName ) )
{
Settings . UserName = UserName ;
}
return PerforceConnection . CreateAsync ( Settings , LoggerFactory . CreateLogger ( "Perforce" ) ) ;
}
public static Task < IPerforceConnection > ConnectAsync ( UserWorkspaceSettings Settings , ILoggerFactory LoggerFactory )
{
return ConnectAsync ( Settings . ServerAndPort , Settings . UserName , Settings . ClientName , LoggerFactory ) ;
}
2022-01-10 10:58:21 -05:00
static string [ ] ReadSyncFilter ( UserWorkspaceSettings WorkspaceSettings , GlobalSettingsFile UserSettings , ConfigFile ProjectConfig )
2022-01-07 16:21:08 -05:00
{
Dictionary < Guid , WorkspaceSyncCategory > SyncCategories = ConfigUtils . GetSyncCategories ( ProjectConfig ) ;
2022-01-10 13:44:32 -05:00
string [ ] CombinedSyncFilter = GlobalSettingsFile . GetCombinedSyncFilter ( SyncCategories , UserSettings . Global . Filter , WorkspaceSettings . Filter ) ;
2022-01-07 16:21:08 -05:00
ConfigSection PerforceSection = ProjectConfig . FindSection ( "Perforce" ) ;
if ( PerforceSection ! = null )
{
IEnumerable < string > AdditionalPaths = PerforceSection . GetValues ( "AdditionalPathsToSync" , new string [ 0 ] ) ;
CombinedSyncFilter = AdditionalPaths . Union ( CombinedSyncFilter ) . ToArray ( ) ;
}
return CombinedSyncFilter ;
}
static async Task < string > FindProjectPathAsync ( IPerforceConnection Perforce , string ClientName , string BranchPath , string? ProjectName )
{
using IPerforceConnection PerforceClient = await PerforceConnection . CreateAsync ( new PerforceSettings ( Perforce . Settings ) { ClientName = ClientName } , Perforce . Logger ) ;
// Find or validate the selected project
string SearchPath ;
if ( ProjectName = = null )
{
SearchPath = $"//{ClientName}{BranchPath}/*.uprojectdirs" ;
}
else if ( ProjectName . Contains ( '.' ) )
{
SearchPath = $"//{ClientName}{BranchPath}/{ProjectName.TrimStart('/')}" ;
}
else
{
SearchPath = $"//{ClientName}{BranchPath}/.../{ProjectName}.uproject" ;
}
List < FStatRecord > ProjectFileRecords = await PerforceClient . FStatAsync ( FStatOptions . ClientFileInPerforceSyntax , SearchPath ) . ToListAsync ( ) ;
ProjectFileRecords . RemoveAll ( x = > ! x . IsMapped ) ;
List < string > Paths = ProjectFileRecords . Select ( x = > PerforceUtils . GetClientRelativePath ( x . ClientFile ! ) ) . Distinct ( StringComparer . Ordinal ) . ToList ( ) ;
if ( Paths . Count = = 0 )
{
throw new UserErrorException ( "No project file found matching {SearchPath}" , SearchPath ) ;
}
if ( Paths . Count > 1 )
{
throw new UserErrorException ( "Multiple projects found matching {SearchPath}: {Paths}" , SearchPath , String . Join ( ", " , Paths ) ) ;
}
return "/" + Paths [ 0 ] ;
}
abstract class Command
{
public abstract Task ExecuteAsync ( CommandContext Context ) ;
}
class InitCommand : Command
{
public override async Task ExecuteAsync ( CommandContext Context )
{
ILogger Logger = Context . Logger ;
// Get the positional argument indicating the file to look for
string? InitName ;
Context . Arguments . TryGetPositionalArgument ( out InitName ) ;
// Get the config settings from the command line
ProjectInitOptions Options = new ProjectInitOptions ( ) ;
Context . Arguments . ApplyTo ( Options ) ;
Context . Arguments . CheckAllArgumentsUsed ( ) ;
// Get the host name
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 ( ) ;
// Create the perforce connection
if ( InitName ! = null )
{
await InitNewClientAsync ( Perforce , Context , InitName , HostName , Options , Logger ) ;
}
else
{
await InitExistingClientAsync ( Perforce , Context , HostName , Options , Logger ) ;
}
}
async Task InitNewClientAsync ( IPerforceConnection Perforce , CommandContext Context , string StreamName , string HostName , ProjectInitOptions Options , ILogger Logger )
{
Logger . LogInformation ( "Checking stream..." ) ;
// Get the given stream
PerforceResponse < StreamRecord > StreamResponse = await Perforce . TryGetStreamAsync ( StreamName , true ) ;
if ( ! StreamResponse . Succeeded )
{
throw new UserErrorException ( $"Unable to find stream '{StreamName}'" ) ;
}
StreamRecord Stream = StreamResponse . Data ;
// Get the new directory for the client
DirectoryReference ClientDir = DirectoryReference . Combine ( DirectoryReference . GetCurrentDirectory ( ) , Stream . Stream . Replace ( '/' , '+' ) ) ;
DirectoryReference . CreateDirectory ( ClientDir ) ;
// Make up a new client name
string ClientName = Options . ClientName ? ? Regex . Replace ( $"{Perforce.Settings.UserName}_{HostName}_{Stream.Stream.Trim('/')}" , "[^0-9a-zA-Z_.-]" , "+" ) ;
// Check there are no existing clients under the current path
List < ClientsRecord > Clients = await FindExistingClients ( Perforce , HostName , ClientDir ) ;
if ( Clients . Count > 0 )
{
if ( Clients . Count = = 1 & & ClientName . Equals ( Clients [ 0 ] . Name , StringComparison . OrdinalIgnoreCase ) & & ClientDir = = TryParseRoot ( Clients [ 0 ] . Root ) )
{
Logger . LogInformation ( "Reusing existing client for {ClientDir} ({ClientName})" , ClientDir , Options . ClientName ) ;
}
else
{
throw new UserErrorException ( "Current directory is already within a Perforce workspace ({ClientName})" , Clients [ 0 ] . Name ) ;
}
}
// Create the new client
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 ) ;
// Branch root is currently hard-coded at the root
string BranchPath = Options . BranchPath ? ? String . Empty ;
string ProjectPath = await FindProjectPathAsync ( Perforce , ClientName , BranchPath , Options . ProjectName ) ;
// Create the settings object
UserWorkspaceSettings Settings = new UserWorkspaceSettings ( ) ;
Settings . RootDir = ClientDir ;
Settings . Init ( Perforce . Settings . ServerAndPort , Perforce . Settings . UserName , ClientName , BranchPath , ProjectPath ) ;
Options . ApplyTo ( Settings ) ;
Settings . Save ( ) ;
Logger . LogInformation ( "Initialized {ClientName} with root at {RootDir}" , ClientName , ClientDir ) ;
}
static DirectoryReference ? TryParseRoot ( string Root )
{
try
{
return new DirectoryReference ( Root ) ;
}
catch
{
return null ;
}
}
async Task InitExistingClientAsync ( IPerforceConnection Perforce , CommandContext Context , string HostName , ProjectInitOptions Options , ILogger Logger )
{
DirectoryReference CurrentDir = DirectoryReference . GetCurrentDirectory ( ) ;
// Make sure the client name is set
string? ClientName = Options . ClientName ;
if ( ClientName = = null )
{
List < ClientsRecord > Clients = await FindExistingClients ( Perforce , HostName , CurrentDir ) ;
if ( Clients . Count = = 0 )
{
throw new UserErrorException ( "Unable to find client for {HostName} under {ClientDir}" , HostName , CurrentDir ) ;
}
if ( Clients . Count > 1 )
{
throw new UserErrorException ( "Multiple clients found for {HostName} under {ClientDir}: {ClientList}" , HostName , CurrentDir , String . Join ( ", " , Clients . Select ( x = > x . Name ) ) ) ;
}
ClientName = Clients [ 0 ] . Name ;
Logger . LogInformation ( "Found client {ClientName}" , ClientName ) ;
}
// Get the client info
2022-01-08 15:45:12 -05:00
ClientRecord Client = await Perforce . GetClientAsync ( ClientName ) ;
2022-01-07 16:21:08 -05:00
DirectoryReference ClientDir = new DirectoryReference ( Client . Root ) ;
// If a project path was specified in local syntax, try to convert it to client-relative syntax
string? ProjectName = Options . ProjectName ;
if ( Options . ProjectName ! = null & & Options . ProjectName . Contains ( '.' ) )
{
Options . ProjectName = FileReference . Combine ( CurrentDir , Options . ProjectName ) . MakeRelativeTo ( ClientDir ) . Replace ( '\\' , '/' ) ;
}
// Branch root is currently hard-coded at the root
string BranchPath = Options . BranchPath ? ? String . Empty ;
string ProjectPath = await FindProjectPathAsync ( Perforce , ClientName , BranchPath , ProjectName ) ;
// Create the settings object
UserWorkspaceSettings Settings = new UserWorkspaceSettings ( ) ;
Settings . RootDir = ClientDir ;
Settings . Init ( Perforce . Settings . ServerAndPort , Perforce . Settings . UserName , ClientName , BranchPath , ProjectPath ) ;
Options . ApplyTo ( Settings ) ;
Settings . Save ( ) ;
Logger . LogInformation ( "Initialized workspace at {RootDir} for {ClientProject}" , ClientDir , Settings . ClientProjectPath ) ;
}
static async Task < List < ClientsRecord > > FindExistingClients ( IPerforceConnection Perforce , string HostName , DirectoryReference ClientDir )
{
List < ClientsRecord > MatchingClients = new List < ClientsRecord > ( ) ;
List < ClientsRecord > Clients = await Perforce . GetClientsAsync ( ClientsOptions . None , Perforce . Settings . UserName ) ;
foreach ( ClientsRecord Client in Clients )
{
if ( ! String . IsNullOrEmpty ( Client . Root ) & & ! String . IsNullOrEmpty ( Client . Host ) & & String . Compare ( HostName , Client . Host , StringComparison . OrdinalIgnoreCase ) = = 0 )
{
DirectoryReference ? RootDir ;
try
{
RootDir = new DirectoryReference ( Client . Root ) ;
}
catch
{
RootDir = null ;
}
if ( RootDir ! = null & & ClientDir . IsUnderDirectory ( RootDir ) )
{
MatchingClients . Add ( Client ) ;
}
}
}
return MatchingClients ;
}
}
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
[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
}
public override async Task ExecuteAsync ( CommandContext Context )
{
ILogger Logger = Context . Logger ;
Context . Arguments . TryGetPositionalArgument ( out string? ChangeString ) ;
SyncOptions SyncOptions = new SyncOptions ( ) ;
Context . Arguments . ApplyTo ( SyncOptions ) ;
Context . Arguments . CheckAllArgumentsUsed ( ) ;
UserWorkspaceSettings Settings = ReadRequiredUserWorkspaceSettings ( ) ;
using IPerforceConnection PerforceClient = await ConnectAsync ( Settings , Context . LoggerFactory ) ;
UserWorkspaceState State = await ReadWorkspaceState ( PerforceClient , Settings , Context . UserSettings , Logger ) ;
ChangeString ? ? = "latest" ;
int Change ;
if ( ! int . TryParse ( ChangeString , out Change ) )
{
if ( String . Equals ( ChangeString , "latest" , StringComparison . OrdinalIgnoreCase ) )
{
List < ChangesRecord > Changes = await PerforceClient . GetChangesAsync ( ChangesOptions . None , 1 , ChangeStatus . Submitted , $"//{Settings.ClientName}/..." ) ;
Change = Changes [ 0 ] . Number ;
}
else
{
throw new UserErrorException ( "Unknown change type for sync '{Change}'" , ChangeString ) ;
}
}
WorkspaceUpdateOptions Options = SyncOptions . SingleChange ? WorkspaceUpdateOptions . SyncSingleChange : WorkspaceUpdateOptions . Sync ;
if ( SyncOptions . Build )
{
Options | = WorkspaceUpdateOptions . Build ;
}
2022-01-11 12:54:38 -05:00
if ( SyncOptions . ProjectFiles )
{
Options | = WorkspaceUpdateOptions . GenerateProjectFiles ;
}
2022-01-14 10:51:47 -05:00
if ( SyncOptions . Clobber )
{
Options | = WorkspaceUpdateOptions . Clobber ;
}
2022-01-14 11:00:17 -05:00
if ( SyncOptions . Refilter )
{
Options | = WorkspaceUpdateOptions . Refilter ;
}
2022-01-11 15:39:30 -05:00
Options | = WorkspaceUpdateOptions . RemoveFilteredFiles ;
2022-01-07 16:21:08 -05:00
ProjectInfo ProjectInfo = State . CreateProjectInfo ( ) ;
2022-02-28 21:15:22 -05:00
UserProjectSettings ProjectSettings = Context . UserSettings . FindOrAddProjectSettings ( ProjectInfo , Settings ) ;
2022-01-07 16:21:08 -05:00
ConfigFile ProjectConfig = await ConfigUtils . ReadProjectConfigFileAsync ( PerforceClient , ProjectInfo , Logger , CancellationToken . None ) ;
string [ ] SyncFilter = ReadSyncFilter ( Settings , Context . UserSettings , ProjectConfig ) ;
WorkspaceUpdateContext UpdateContext = new WorkspaceUpdateContext ( Change , Options , BuildConfig . Development , SyncFilter , ProjectSettings . BuildSteps , null ) ;
WorkspaceUpdate Update = new WorkspaceUpdate ( UpdateContext ) ;
2022-01-11 12:00:10 -05:00
( WorkspaceUpdateResult Result , string Message ) = await Update . ExecuteAsync ( PerforceClient . Settings , ProjectInfo , State , Context . Logger , CancellationToken . None ) ;
2022-01-14 10:51:47 -05:00
if ( Result = = WorkspaceUpdateResult . FilesToClobber )
{
Logger . LogWarning ( "The following files are modified in your workspace:" ) ;
foreach ( string File in UpdateContext . ClobberFiles . Keys . OrderBy ( x = > x ) )
{
Logger . LogWarning ( " {File}" , File ) ;
}
Logger . LogWarning ( "Use -Clobber to overwrite" ) ;
}
else if ( Result ! = WorkspaceUpdateResult . Success )
2022-01-11 12:00:10 -05:00
{
2022-01-11 15:39:30 -05:00
Logger . LogError ( "{Message} (Result: {Result})" , Message , Result ) ;
}
}
}
class ClientsCommand : Command
{
public class ClientsOptions : ServerOptions
{
}
public override async Task ExecuteAsync ( CommandContext Context )
{
ILogger Logger = Context . Logger ;
ClientsOptions Options = Context . Arguments . ApplyTo < ClientsOptions > ( Logger ) ;
Context . Arguments . CheckAllArgumentsUsed ( ) ;
using IPerforceConnection PerforceClient = await ConnectAsync ( Options . ServerAndPort , Options . UserName , null , Context . LoggerFactory ) ;
InfoRecord Info = await PerforceClient . GetInfoAsync ( InfoOptions . ShortOutput ) ;
List < ClientsRecord > Clients = await PerforceClient . GetClientsAsync ( EpicGames . Perforce . ClientsOptions . None , PerforceClient . Settings . UserName ) ;
foreach ( ClientsRecord Client in Clients )
{
if ( String . Equals ( Info . ClientHost , Client . Host , StringComparison . OrdinalIgnoreCase ) )
{
Logger . LogInformation ( "{Client,-50} {Root}" , Client . Name , Client . Root ) ;
}
}
}
}
class RunCommand : Command
{
public override async Task ExecuteAsync ( CommandContext Context )
{
ILogger Logger = Context . Logger ;
UserWorkspaceSettings Settings = ReadRequiredUserWorkspaceSettings ( ) ;
using IPerforceConnection PerforceClient = await ConnectAsync ( Settings , Context . LoggerFactory ) ;
UserWorkspaceState State = await ReadWorkspaceState ( PerforceClient , Settings , Context . UserSettings , Logger ) ;
ProjectInfo ProjectInfo = State . CreateProjectInfo ( ) ;
ConfigFile ProjectConfig = await ConfigUtils . ReadProjectConfigFileAsync ( PerforceClient , ProjectInfo , Logger , CancellationToken . None ) ;
FileReference ReceiptFile = ConfigUtils . GetEditorReceiptFile ( ProjectInfo , ProjectConfig , EditorConfig ) ;
Logger . LogDebug ( "Receipt file: {Receipt}" , ReceiptFile ) ;
if ( ! ConfigUtils . TryReadEditorReceipt ( ProjectInfo , ReceiptFile , out TargetReceipt ? Receipt ) | | String . IsNullOrEmpty ( Receipt . Launch ) )
{
throw new UserErrorException ( "The editor needs to be built before you can run it. (Missing {ReceiptFile})." , ReceiptFile ) ;
}
if ( ! File . Exists ( Receipt . Launch ) )
{
throw new UserErrorException ( "The editor needs to be built before you can run it. (Missing {LaunchFile})." , Receipt . Launch ) ;
}
List < string > LaunchArguments = new List < string > ( ) ;
if ( Settings . LocalProjectPath . HasExtension ( ".uproject" ) )
{
LaunchArguments . Add ( $"\" { Settings . LocalProjectPath } \ "" ) ;
}
if ( EditorConfig = = BuildConfig . Debug | | EditorConfig = = BuildConfig . DebugGame )
{
LaunchArguments . Append ( " -debug" ) ;
}
for ( int Idx = 0 ; Idx < Context . Arguments . Count ; Idx + + )
{
if ( ! Context . Arguments . HasBeenUsed ( Idx ) )
{
LaunchArguments . Add ( Context . Arguments [ Idx ] ) ;
}
}
string CommandLine = CommandLineArguments . Join ( LaunchArguments ) ;
Logger . LogInformation ( "Spawning: {LaunchFile} {CommandLine}" , CommandLineArguments . Quote ( Receipt . Launch ) , CommandLine ) ;
if ( ! Utility . SpawnProcess ( Receipt . Launch , CommandLine ) )
{
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
{
public override async Task ExecuteAsync ( CommandContext Context )
{
ILogger Logger = Context . Logger ;
int Count = Context . Arguments . GetIntegerOrDefault ( "-Count=" , 10 ) ;
int LineCount = Context . Arguments . GetIntegerOrDefault ( "-Lines=" , 3 ) ;
Context . Arguments . CheckAllArgumentsUsed ( Context . Logger ) ;
UserWorkspaceSettings Settings = ReadRequiredUserWorkspaceSettings ( ) ;
using IPerforceConnection PerforceClient = await ConnectAsync ( Settings , Context . LoggerFactory ) ;
List < ChangesRecord > Changes = await PerforceClient . GetChangesAsync ( ChangesOptions . None , Count , ChangeStatus . Submitted , $"//{Settings.ClientName}/..." ) ;
foreach ( IEnumerable < ChangesRecord > ChangesBatch in Changes . Batch ( 10 ) )
{
List < DescribeRecord > DescribeRecords = await PerforceClient . DescribeAsync ( ChangesBatch . Select ( x = > x . Number ) . ToArray ( ) ) ;
Logger . LogInformation ( " Change Type Author Description" ) ;
foreach ( DescribeRecord DescribeRecord in DescribeRecords )
{
PerforceChangeDetails Details = new PerforceChangeDetails ( DescribeRecord ) ;
string Type ;
if ( Details . bContainsCode )
{
if ( Details . bContainsContent )
{
Type = "Both" ;
}
else
{
Type = "Code" ;
}
}
else
{
if ( Details . bContainsContent )
{
Type = "Content" ;
}
else
{
Type = "None" ;
}
}
string Author = StringUtils . Truncate ( DescribeRecord . User , 15 ) ;
List < string > Lines = StringUtils . WordWrap ( Details . Description , Math . Max ( ConsoleUtils . WindowWidth - 40 , 10 ) ) . ToList ( ) ;
if ( Lines . Count = = 0 )
{
Lines . Add ( String . Empty ) ;
}
Logger . LogInformation ( " {Change,-9} {Type,-8} {Author,-15} {Description}" , DescribeRecord . Number , Type , Author , Lines [ 0 ] ) ;
for ( int LineIndex = 1 ; LineIndex < LineCount ; LineIndex + + )
{
Logger . LogInformation ( " {Description}" , Lines [ LineIndex ] ) ;
}
}
}
}
}
2022-01-07 16:21:08 -05:00
class ConfigCommand : Command
{
2022-01-08 15:04:19 -05:00
public override Task ExecuteAsync ( CommandContext Context )
2022-01-07 16:21:08 -05:00
{
ILogger Logger = Context . Logger ;
UserWorkspaceSettings Settings = ReadRequiredUserWorkspaceSettings ( ) ;
if ( ! Context . Arguments . GetUnusedArguments ( ) . Any ( ) )
{
ProcessStartInfo StartInfo = new ProcessStartInfo ( ) ;
StartInfo . FileName = Settings . ConfigFile . FullName ;
StartInfo . UseShellExecute = true ;
using ( Process ? Editor = Process . Start ( StartInfo ) )
{
if ( Editor ! = null )
{
2022-01-08 15:04:19 -05:00
Editor . WaitForExit ( ) ;
2022-01-07 16:21:08 -05:00
}
}
}
else
{
ProjectConfigOptions Options = new ProjectConfigOptions ( ) ;
Context . Arguments . ApplyTo ( Options ) ;
Context . Arguments . CheckAllArgumentsUsed ( Context . Logger ) ;
Options . ApplyTo ( Settings ) ;
Settings . Save ( ) ;
Logger . LogInformation ( "Updated {ConfigFile}" , Settings . ConfigFile ) ;
}
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 ; }
}
public override async Task ExecuteAsync ( CommandContext Context )
{
ILogger Logger = Context . Logger ;
UserWorkspaceSettings WorkspaceSettings = ReadRequiredUserWorkspaceSettings ( ) ;
using IPerforceConnection PerforceClient = await ConnectAsync ( WorkspaceSettings , Context . LoggerFactory ) ;
UserWorkspaceState WorkspaceState = await ReadWorkspaceState ( PerforceClient , WorkspaceSettings , Context . UserSettings , Logger ) ;
ProjectInfo ProjectInfo = WorkspaceState . CreateProjectInfo ( ) ;
ConfigFile ProjectConfig = await ConfigUtils . ReadProjectConfigFileAsync ( PerforceClient , ProjectInfo , Logger , CancellationToken . None ) ;
Dictionary < Guid , WorkspaceSyncCategory > SyncCategories = ConfigUtils . GetSyncCategories ( ProjectConfig ) ;
FilterSettings GlobalFilter = Context . UserSettings . Global . Filter ;
FilterSettings WorkspaceFilter = WorkspaceSettings . Filter ;
FilterCommandOptions Options = Context . Arguments . ApplyTo < FilterCommandOptions > ( Logger ) ;
Context . Arguments . CheckAllArgumentsUsed ( Context . Logger ) ;
if ( Options . Global )
{
ApplyCommandOptions ( Context . UserSettings . Global . Filter , Options , SyncCategories . Values , Logger ) ;
Context . UserSettings . Save ( ) ;
}
else
{
ApplyCommandOptions ( WorkspaceSettings . Filter , Options , SyncCategories . Values , Logger ) ;
WorkspaceSettings . Save ( ) ;
}
Dictionary < Guid , bool > GlobalCategories = GlobalFilter . GetCategories ( ) ;
Dictionary < Guid , bool > WorkspaceCategories = WorkspaceFilter . GetCategories ( ) ;
Logger . LogInformation ( "Categories:" ) ;
foreach ( WorkspaceSyncCategory SyncCategory in SyncCategories . Values )
{
bool bEnabled ;
string Scope = "(Default)" ;
if ( GlobalCategories . TryGetValue ( SyncCategory . UniqueId , out bEnabled ) )
{
Scope = "(Global)" ;
}
else if ( WorkspaceCategories . TryGetValue ( SyncCategory . UniqueId , out bEnabled ) )
{
Scope = "(Workspace)" ;
}
else
{
bEnabled = SyncCategory . bEnable ;
}
Logger . LogInformation ( " {Id,30} {Enabled,3} {Scope,-9} {Name}" , SyncCategory . UniqueId , bEnabled ? "Yes" : "No" , Scope , SyncCategory . Name ) ;
}
if ( GlobalFilter . View . Count > 0 )
{
Logger . LogInformation ( "" ) ;
Logger . LogInformation ( "Global View:" ) ;
foreach ( string Line in GlobalFilter . View )
{
Logger . LogInformation ( " {Line}" , Line ) ;
}
}
if ( WorkspaceFilter . View . Count > 0 )
{
Logger . LogInformation ( "" ) ;
Logger . LogInformation ( "Workspace View:" ) ;
foreach ( string Line in WorkspaceFilter . View )
{
Logger . LogInformation ( " {Line}" , Line ) ;
}
}
string [ ] Filter = ReadSyncFilter ( WorkspaceSettings , Context . UserSettings , ProjectConfig ) ;
Logger . LogInformation ( "" ) ;
Logger . LogInformation ( "Combined view:" ) ;
foreach ( string FilterLine in Filter )
{
Logger . LogInformation ( " {FilterLine}" , FilterLine ) ;
}
}
static void ApplyCommandOptions ( FilterSettings Settings , FilterCommandOptions CommandOptions , IEnumerable < WorkspaceSyncCategory > SyncCategories , ILogger Logger )
{
if ( CommandOptions . Reset )
{
Logger . LogInformation ( "Resetting settings..." ) ;
Settings . Reset ( ) ;
}
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 ) ) ) ;
Guid Id = IncludeCategories . FirstOrDefault ( x = > ExcludeCategories . Contains ( x ) ) ;
if ( Id ! = Guid . Empty )
{
throw new UserErrorException ( "Category {Id} cannot be both included and excluded" , Id ) ;
}
IncludeCategories . ExceptWith ( Settings . IncludeCategories ) ;
Settings . IncludeCategories . AddRange ( IncludeCategories ) ;
ExcludeCategories . ExceptWith ( Settings . ExcludeCategories ) ;
Settings . ExcludeCategories . AddRange ( ExcludeCategories ) ;
if ( CommandOptions . View ! = null )
{
Settings . View = CommandOptions . View ;
}
if ( CommandOptions . RemoveView . Count > 0 )
{
HashSet < string > ViewRemove = new HashSet < string > ( CommandOptions . RemoveView , StringComparer . OrdinalIgnoreCase ) ;
Settings . View . RemoveAll ( x = > ViewRemove . Contains ( x ) ) ;
}
if ( CommandOptions . AddView . Count > 0 )
{
HashSet < string > ViewLines = new HashSet < string > ( Settings . View , StringComparer . OrdinalIgnoreCase ) ;
Settings . View . AddRange ( CommandOptions . AddView . Where ( x = > ! ViewLines . Contains ( x ) ) ) ;
}
Settings . AllProjects = CommandOptions . AllProjects ? ? Settings . AllProjects ;
Settings . AllProjectsInSln = CommandOptions . AllProjectsInSln ? ? Settings . AllProjectsInSln ;
}
static Guid GetCategoryId ( string Text , IEnumerable < WorkspaceSyncCategory > SyncCategories )
{
Guid Id ;
if ( Guid . TryParse ( Text , out Id ) )
{
return Id ;
}
WorkspaceSyncCategory ? Category = SyncCategories . FirstOrDefault ( x = > x . Name . Equals ( Text , StringComparison . OrdinalIgnoreCase ) ) ;
if ( Category ! = null )
{
return Category . UniqueId ;
}
throw new UserErrorException ( "Unable to find category '{Category}'" , Text ) ;
}
}
2022-01-07 16:21:08 -05:00
class BuildCommand : Command
{
public override async Task ExecuteAsync ( CommandContext Context )
{
ILogger Logger = Context . Logger ;
Context . Arguments . TryGetPositionalArgument ( out string? Target ) ;
bool ListOnly = Context . Arguments . HasOption ( "-List" ) ;
Context . Arguments . CheckAllArgumentsUsed ( ) ;
UserWorkspaceSettings Settings = ReadRequiredUserWorkspaceSettings ( ) ;
using IPerforceConnection PerforceClient = await ConnectAsync ( Settings , Context . LoggerFactory ) ;
UserWorkspaceState State = await ReadWorkspaceState ( PerforceClient , Settings , Context . UserSettings , Logger ) ;
ProjectInfo ProjectInfo = State . CreateProjectInfo ( ) ;
if ( ListOnly )
{
ConfigFile ProjectConfig = await ConfigUtils . ReadProjectConfigFileAsync ( PerforceClient , ProjectInfo , Logger , CancellationToken . None ) ;
FileReference EditorTarget = ConfigUtils . GetEditorTargetFile ( ProjectInfo , ProjectConfig ) ;
2022-01-11 15:39:30 -05:00
Dictionary < Guid , ConfigObject > BuildStepObjects = ConfigUtils . GetDefaultBuildStepObjects ( ProjectInfo , EditorTarget . GetFileNameWithoutAnyExtensions ( ) , EditorConfig , ProjectConfig , false ) ;
2022-01-07 16:21:08 -05: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 ) )
{
Logger . LogInformation ( " {Id,-36} | {Name,-40} | {Type,-10} | {Enabled,-8}" , BuildStep . UniqueId , BuildStep . Description , BuildStep . Type , BuildStep . bNormalSync ) ;
}
return ;
}
HashSet < Guid > ? Steps = null ;
if ( Target ! = null )
{
Guid Id ;
if ( ! Guid . TryParse ( Target , out Id ) )
{
Logger . LogError ( "Unable to parse '{Target}' as a GUID. Pass -List to show all available build steps and their identifiers." , Target ) ;
}
Steps = new HashSet < Guid > { Id } ;
}
WorkspaceUpdateContext UpdateContext = new WorkspaceUpdateContext ( State . CurrentChangeNumber , WorkspaceUpdateOptions . Build , BuildConfig . Development , null , new List < ConfigObject > ( ) , Steps ) ;
WorkspaceUpdate Update = new WorkspaceUpdate ( UpdateContext ) ;
await Update . ExecuteAsync ( PerforceClient . Settings , ProjectInfo , State , Context . Logger , CancellationToken . None ) ;
}
}
class StatusCommand : Command
{
public override async Task ExecuteAsync ( CommandContext Context )
{
ILogger Logger = Context . Logger ;
bool bUpdate = Context . Arguments . HasOption ( "-Update" ) ;
Context . Arguments . CheckAllArgumentsUsed ( ) ;
UserWorkspaceSettings Settings = ReadRequiredUserWorkspaceSettings ( ) ;
Logger . LogInformation ( "User: {UserName}" , Settings . UserName ) ;
Logger . LogInformation ( "Server: {ServerAndPort}" , Settings . ServerAndPort ) ;
Logger . LogInformation ( "Project: {ClientProjectPath}" , Settings . ClientProjectPath ) ;
using IPerforceConnection PerforceClient = await ConnectAsync ( Settings , Context . LoggerFactory ) ;
UserWorkspaceState State = await ReadWorkspaceState ( PerforceClient , Settings , Context . UserSettings , Logger ) ;
if ( bUpdate )
{
ProjectInfo NewProjectInfo = await ProjectInfo . CreateAsync ( PerforceClient , Settings , CancellationToken . None ) ;
State . UpdateCachedProjectInfo ( NewProjectInfo , Settings . LastModifiedTimeUtc ) ;
}
string StreamOrBranchName = State . StreamName ? ? Settings . BranchPath . TrimStart ( '/' ) ;
if ( State . LastSyncResultMessage = = null )
{
Logger . LogInformation ( "Not currently synced to {Stream}" , StreamOrBranchName ) ;
}
else if ( State . LastSyncResult = = WorkspaceUpdateResult . Success )
{
Logger . LogInformation ( "Synced to {Stream} CL {Change}" , StreamOrBranchName , State . LastSyncChangeNumber ) ;
}
else
{
Logger . LogWarning ( "Last sync to {Stream} CL {Change} failed: {Result}" , StreamOrBranchName , State . LastSyncChangeNumber , State . LastSyncResultMessage ) ;
}
}
}
class SwitchCommand : Command
{
public override async Task ExecuteAsync ( CommandContext Context )
{
// Get the positional argument indicating the file to look for
string? TargetName ;
if ( ! Context . Arguments . TryGetPositionalArgument ( out TargetName ) )
{
throw new UserErrorException ( "Missing stream or project name to switch to." ) ;
}
bool Force = TargetName . StartsWith ( "//" , StringComparison . Ordinal ) & & Context . Arguments . HasOption ( "-Force" ) ;
// Finish argument parsing
Context . Arguments . CheckAllArgumentsUsed ( ) ;
// Get a connection to the client for this workspace
UserWorkspaceSettings Settings = ReadRequiredUserWorkspaceSettings ( ) ;
using IPerforceConnection PerforceClient = await ConnectAsync ( Settings , Context . LoggerFactory ) ;
// Check whether we're switching stream or project
if ( TargetName . StartsWith ( "//" , StringComparison . Ordinal ) )
{
await SwitchStreamAsync ( PerforceClient , TargetName , Force , Context . Logger ) ;
}
else
{
await SwitchProjectAsync ( PerforceClient , Settings , TargetName , Context . Logger ) ;
}
}
public async Task SwitchStreamAsync ( IPerforceConnection PerforceClient , string StreamName , bool Force , ILogger Logger )
{
if ( ! Force & & await PerforceClient . OpenedAsync ( OpenedOptions . None , FileSpecList . Any ) . AnyAsync ( ) )
{
throw new UserErrorException ( "Client {ClientName} has files opened. Use -Force to switch anyway." , PerforceClient . Settings . ClientName ! ) ;
}
2022-04-14 19:51:54 -04:00
await PerforceClient . SwitchClientToStreamAsync ( StreamName , SwitchClientOptions . IgnoreOpenFiles ) ;
2022-01-07 16:21:08 -05:00
Logger . LogInformation ( "Switched to stream {StreamName}" , StreamName ) ;
}
public async Task SwitchProjectAsync ( IPerforceConnection PerforceClient , UserWorkspaceSettings Settings , string ProjectName , ILogger Logger )
{
Settings . ProjectPath = await FindProjectPathAsync ( PerforceClient , Settings . ClientName , Settings . BranchPath , ProjectName ) ;
Settings . Save ( ) ;
Logger . LogInformation ( "Switched to project {ProjectPath}" , Settings . ClientProjectPath ) ;
}
}
}
}