Files
2025-05-18 13:04:45 +08:00

322 lines
9.9 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "CommandBase.h"
#include "HAL/FileManager.h"
#include "ISourceControlModule.h"
#include "ISourceControlProvider.h"
#include "Misc/PackageName.h"
#include "Misc/Paths.h"
#include "Misc/ScopeExit.h"
#include "SourceControlInitSettings.h"
#include "SourceControlOperations.h"
#include "UnrealVirtualizationTool.h"
namespace UE::Virtualization
{
/** Utility for testing if a file path resolves to a valid package file or not */
bool IsPackageFile(const FString FilePath)
{
// ::IsPackageExtension requires a TCHAR so we cannot use FPathViews here
const FString Extension = FPaths::GetExtension(FilePath);
// Currently we don't virtualize text based assets so no call to FPackageName::IsTextPackageExtension
return FPackageName::IsPackageExtension(*Extension);
}
FCommand::FCommand(FStringView InCommandName)
: CommandName(InCommandName)
{
}
FCommand::~FCommand()
{
}
void FCommand::ParseCommandLine(const TCHAR* CmdLine, TArray<FString>& Tokens, TArray<FString>& Switches)
{
// TODO: Taken from UCommandlet, maybe consider moving this code to a general purpose utility. We
// could also make a version that returns TArray<FStringView>
FString NextToken;
while (FParse::Token(CmdLine, NextToken, false))
{
if (**NextToken == TCHAR('-'))
{
new(Switches) FString(NextToken.Mid(1));
}
else
{
new(Tokens) FString(NextToken);
}
}
}
FCommand::EPathResult FCommand::ParseSwitchForPaths(const FString& Switch, TArray<FString>& OutPackages)
{
FString Path;
if (FParse::Value(*Switch, TEXT("Package="), Path))
{
FPaths::NormalizeFilename(Path);
if (!FPackageName::IsPackageFilename(Path))
{
UE_LOG(LogVirtualizationTool, Error, TEXT("Requested package file '%s' is not a valid package filename"), *Path);
return EPathResult::Error;
}
if (!IFileManager::Get().FileExists(*Path))
{
UE_LOG(LogVirtualizationTool, Error, TEXT("Could not find the requested package file '%s'"), *Path);
return EPathResult::Error;
}
OutPackages.Add(Path);
return EPathResult::Success;
}
else if (FParse::Value(*Switch, TEXT("PackageDir="), Path) || FParse::Value(*Switch, TEXT("PackageFolder="), Path))
{
// Note that 'PackageFolder' is the switch used by the resave commandlet, so allowing it here for compatibility purposes
FPaths::NormalizeFilename(Path);
if (IFileManager::Get().DirectoryExists(*Path))
{
IFileManager::Get().IterateDirectoryRecursively(*Path, [&OutPackages](const TCHAR* Path, bool bIsDirectory)
{
if (!bIsDirectory && FPackageName::IsPackageFilename(Path))
{
FString FilePath(Path);
FPaths::NormalizeFilename(FilePath);
OutPackages.Add(MoveTemp(FilePath));
}
return true; // Continue
});
return EPathResult::Success;
}
else
{
UE_LOG(LogVirtualizationTool, Error, TEXT("Could not find the requested directory '%s'"), *Path);
return EPathResult::Error;
}
}
return EPathResult::NotFound;
}
bool FCommand::TryConnectToSourceControl(FStringView ClientSpecName)
{
if (SCCProvider != nullptr)
{
// Already connected so just return
return true;
}
UE_LOG(LogVirtualizationTool, Log, TEXT("Trying to connect to source control..."));
if (FPaths::IsProjectFilePathSet())
{
// If the project has been set then we can use the default source control provider
if (!ISourceControlModule::Get().GetProvider().IsEnabled())
{
ISourceControlModule::Get().SetProvider(FName("Perforce"));
}
SCCProvider = &ISourceControlModule::Get().GetProvider();
SCCProvider->Init(true);
}
else
{
// If we do not have a project set we need to create our own provider. We make the assumption that
// we should use perforce since the only reason we should need a provider without a project is if
// we are parsing a perforce changelist for files to operate on.
FSourceControlInitSettings SCCSettings(FSourceControlInitSettings::EBehavior::OverrideAll);
SCCSettings.SetConfigBehavior(FSourceControlInitSettings::EConfigBehavior::ReadOnly);
SCCSettings.SetCmdLineFlags(FSourceControlInitSettings::ECmdLineFlags::ReadAll);
SCCSettings.AddSetting(TEXT("P4Client"), ClientSpecName);
OwnedSCCProvider = ISourceControlModule::Get().CreateProvider(FName("Perforce"), TEXT("UnrealVirtualizationTool"), SCCSettings);
if (OwnedSCCProvider.IsValid())
{
SCCProvider = OwnedSCCProvider.Get();
SCCProvider->Init(true);
}
else
{
UE_LOG(LogVirtualizationTool, Error, TEXT("\tFailed to instantiate a perforce revision control connection"));
return false;
}
}
if (SCCProvider->IsAvailable())
{
TMap<ISourceControlProvider::EStatus, FString> StatusMap = SCCProvider->GetStatus();
if (FString* Server = StatusMap.Find(ISourceControlProvider::EStatus::Port))
{
UE_LOG(LogVirtualizationTool, Display, TEXT("\tSuccessfully connected to server '%s'"), **Server);
}
return true;
}
else
{
UE_LOG(LogVirtualizationTool, Error, TEXT("\tFailed to establish a perforce connection"));
return false;
}
}
bool FCommand::TryParseChangelist(FStringView ClientSpecName, FStringView ChangelistNumber, TArray<FString>& OutPackages, FSourceControlChangelistPtr* OutChangelist)
{
TRACE_CPUPROFILER_EVENT_SCOPE(TryParseChangelist);
UE_LOG(LogVirtualizationTool, Display, TEXT("\tAttempting to parse changelist '%.*s' in workspace '%.*s'"),
ChangelistNumber.Len(), ChangelistNumber.GetData(),
ClientSpecName.Len(), ClientSpecName.GetData());
if (!TryConnectToSourceControl(ClientSpecName))
{
return false;
}
if (SCCProvider == nullptr)
{
UE_LOG(LogVirtualizationTool, Error, TEXT("No valid source control connection found!"));
return false;
}
if (!SCCProvider->UsesChangelists())
{
UE_LOG(LogVirtualizationTool, Error, TEXT("The source control provider does not support the use of changelists"));
return false;
}
TArray<FSourceControlChangelistRef> Changelists = SCCProvider->GetChangelists(EStateCacheUsage::ForceUpdate);
if (Changelists.IsEmpty())
{
UE_LOG(LogVirtualizationTool, Error, TEXT("Failed to find any changelists"));
return false;
}
// TODO: At the moment we have to poll for all changelists under the current workspace and then iterate over
// them to find the one we want.
// We need to add better support in the API to go from a changelist number to a FSourceControlChangelistRef
// directly.
TArray<FSourceControlChangelistStateRef> ChangelistsStates;
if (SCCProvider->GetState(Changelists, ChangelistsStates, EStateCacheUsage::Use) != ECommandResult::Succeeded)
{
UE_LOG(LogVirtualizationTool, Error, TEXT("Failed to find changelist data"));
return false;
}
for (FSourceControlChangelistStateRef& ChangelistState : ChangelistsStates)
{
const FText DisplayText = ChangelistState->GetDisplayText();
if (ChangelistNumber == DisplayText.ToString())
{
TSharedRef<FUpdatePendingChangelistsStatus, ESPMode::ThreadSafe> Operation = ISourceControlOperation::Create<FUpdatePendingChangelistsStatus>();
FSourceControlChangelistRef Changelist = ChangelistState->GetChangelist();
Operation->SetChangelistsToUpdate(MakeArrayView(&Changelist, 1));
Operation->SetUpdateFilesStates(true);
if (SCCProvider->Execute(Operation, EConcurrency::Synchronous) != ECommandResult::Succeeded)
{
UE_LOG(LogVirtualizationTool, Error, TEXT("Failed to find the files in changelist '%.*s'"), ChangelistNumber.Len(), ChangelistNumber.GetData());
return false;
}
const TArray<FSourceControlStateRef>& FilesinChangelist = ChangelistState->GetFilesStates();
UE_LOG(LogVirtualizationTool, Log, TEXT("\tFound %d files in the changelist"), FilesinChangelist.Num());
for (const FSourceControlStateRef& FileState : FilesinChangelist)
{
if (!IsPackageFile(FileState->GetFilename()))
{
UE_LOG(LogVirtualizationTool, Log, TEXT("\tIgnoring non-package file '%s'"), *FileState->GetFilename());
continue;
}
if (FileState->IsDeleted())
{
UE_LOG(LogVirtualizationTool, Verbose, TEXT("\tIgnoring package marked for delete '%s'"), *FileState->GetFilename());
continue;
}
if (FileState->IsIgnored())
{
UE_LOG(LogVirtualizationTool, Verbose, TEXT("\tIgnoring package marked for ignore '%s'"), *FileState->GetFilename());
continue;
}
OutPackages.Add(FileState->GetFilename());
}
if (OutChangelist != nullptr)
{
*OutChangelist = Changelist;
}
return true;
}
}
UE_LOG(LogVirtualizationTool, Error, TEXT("Failed to find the changelist '%.*s'"), ChangelistNumber.Len(), ChangelistNumber.GetData());
return false;
}
FString FCommand::FindClientSpecForChangelist(FStringView ChangelistNumber)
{
TRACE_CPUPROFILER_EVENT_SCOPE(FindClientSpecForChangelist);
UE_LOG(LogVirtualizationTool, Display, TEXT("\tAttempting to find the workspace for '%.*s'"),
ChangelistNumber.Len(), ChangelistNumber.GetData());
if (!TryConnectToSourceControl())
{
return FString();
}
if (SCCProvider == nullptr)
{
UE_LOG(LogVirtualizationTool, Error, TEXT("No valid source control connection found!"));
return FString();
}
if (!SCCProvider->UsesChangelists())
{
UE_LOG(LogVirtualizationTool, Error, TEXT("The source control provider does not support the use of changelists"));
return FString();
}
TSharedRef<FGetChangelistDetails> Operation = ISourceControlOperation::Create<FGetChangelistDetails>(ChangelistNumber);
if (SCCProvider->Execute(Operation, EConcurrency::Synchronous) == ECommandResult::Succeeded && !Operation->GetChangelistDetails().IsEmpty())
{
const FString* ClientSpec = Operation->GetChangelistDetails()[0].Find(TEXT("Client"));
if (ClientSpec != nullptr)
{
return *ClientSpec;
}
else
{
UE_LOG(LogVirtualizationTool, Error, TEXT("Unable to find the 'client' field for the changelist '%.*s'"), ChangelistNumber.Len(), ChangelistNumber.GetData());
return FString();
}
}
else
{
UE_LOG(LogVirtualizationTool, Error, TEXT("Failed to find details for the changelist '%.*s'"), ChangelistNumber.Len(), ChangelistNumber.GetData());
return FString();
}
}
} //namespace UE::Virtualization