Files
UnrealEngineUWP/Engine/Source/Developer/SourceControl/Private/SourceControlHelpers.cpp
julien lheureux a460459eed Added possibility to get asset data from history, with a max fetch size (until we have a better solution)
Changed the way we associate shelved files with their local filename to facilitate mapping (perforce operations)
Implemented what was needed on the SCC side to support diff against shelve
Improved way we display object names for deleted & shelved files

#rb sebastien.lussier

[CL 15356101 by julien lheureux in ue5-main branch]
2021-02-08 09:36:20 -04:00

1569 lines
48 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "SourceControlHelpers.h"
#include "ISourceControlState.h"
#include "HAL/FileManager.h"
#include "Misc/Paths.h"
#include "Misc/ConfigCacheIni.h"
#include "ISourceControlOperation.h"
#include "SourceControlOperations.h"
#include "ISourceControlProvider.h"
#include "ISourceControlModule.h"
#include "ISourceControlLabel.h"
#include "UObject/Package.h"
#include "Misc/PackageName.h"
#include "Logging/MessageLog.h"
#include "AssetRegistry/AssetRegistryModule.h"
#define LOCTEXT_NAMESPACE "SourceControlHelpers"
namespace SourceControlHelpersInternal
{
/*
* Status info set by LogError() and USourceControlHelpers methods if an error occurs
* regardless whether their bSilent is set or not.
* Should be empty if there is was no error.
* @see USourceControlHelpers::LastErrorMsg(), LogError()
*/
FText LastErrorText;
/* Store error and write to Log if bSilent is false. */
inline void LogError(const FText& ErrorText, bool bSilent)
{
LastErrorText = ErrorText;
if (!bSilent)
{
FMessageLog("SourceControl").Error(LastErrorText);
}
}
/* Return provider if ready to go, else return nullptr. */
ISourceControlProvider* VerifySourceControl(bool bSilent)
{
ISourceControlModule& SCModule = ISourceControlModule::Get();
if (!SCModule.IsEnabled())
{
LogError(LOCTEXT("SourceControlDisabled", "Source control is not enabled."), bSilent);
return nullptr;
}
ISourceControlProvider* Provider = &SCModule.GetProvider();
if (!Provider->IsAvailable())
{
LogError(LOCTEXT("SourceControlServerUnavailable", "Source control server is currently not available."), bSilent);
return nullptr;
}
// Clear the last error text if there hasn't been an error (yet).
LastErrorText = FText::GetEmpty();
return Provider;
}
/*
* Converts specified file to fully qualified file path that is compatible with source control.
*
* @param InFile File string - can be either fully qualified path, relative path, long package name, asset path or export text path (often stored on clipboard)
* @param bSilent if false then write out any error info to the Log. Any error text can be retrieved by LastErrorMsg() regardless.
* @return Fully qualified file path to use with source control or "" if conversion unsuccessful.
*/
FString ConvertFileToQualifiedPath(const FString& InFile, bool bSilent, bool bAllowDirectories = false, const TCHAR* AssociatedExtension = nullptr)
{
// Converted to qualified file path
FString SCFile;
if (InFile.IsEmpty())
{
LogError(LOCTEXT("UnspecifiedFile", "File not specified"), bSilent);
return SCFile;
}
// Try to determine if file is one of:
// - fully qualified path
// - relative path
// - long package name
// - asset path
// - export text path (often stored on clipboard)
//
// For example:
// - D:\Epic\Dev-Ent\Projects\Python3rdBP\Content\Mannequin\Animations\ThirdPersonIdle.uasset
// - Content\Mannequin\Animations\ThirdPersonIdle.uasset
// - /Game/Mannequin/Animations/ThirdPersonIdle
// - /Game/Mannequin/Animations/ThirdPersonIdle.ThirdPersonIdle
// - AnimSequence'/Game/Mannequin/Animations/ThirdPersonIdle.ThirdPersonIdle'
SCFile = InFile;
// Is ExportTextPath (often stored in Clipboard) form?
// - i.e. AnimSequence'/Game/Mannequin/Animations/ThirdPersonIdle.ThirdPersonIdle'
if (SCFile[SCFile.Len() - 1] == '\'')
{
SCFile = FPackageName::ExportTextPathToObjectPath(SCFile);
}
// Package paths
if (SCFile[0] == TEXT('/') && FPackageName::IsValidLongPackageName(SCFile, /*bIncludeReadOnlyRoots*/false))
{
// Assume it is a package
bool bPackage = true;
// Try to get filename by finding it on disk
if (!FPackageName::DoesPackageExist(SCFile, nullptr, &SCFile))
{
// First do the conversion without any extension set, as this will allow us to test whether the path represents an existing directory rather than an asset
if (FPackageName::TryConvertLongPackageNameToFilename(SCFile, SCFile))
{
if (bAllowDirectories && FPaths::DirectoryExists(SCFile))
{
// This path mapped to a known directory, so ensure it ends in a slash
SCFile /= FString();
}
else if (AssociatedExtension)
{
// Just use the requested extension
SCFile += AssociatedExtension;
}
else
{
// The package does not exist on disk, see if we can find it in memory and predict the file extension
UPackage* Package = FindPackage(nullptr, *SCFile);
SCFile += (Package && Package->ContainsMap() ? FPackageName::GetMapPackageExtension() : FPackageName::GetAssetPackageExtension());
}
}
else
{
bPackage = false;
}
}
if (bPackage)
{
SCFile = FPaths::ConvertRelativePathToFull(SCFile);
return SCFile;
}
}
// Assume it is a qualified or relative file path
// Could normalize it
//FPaths::NormalizeFilename(SCFile);
if (!FPaths::IsRelative(SCFile))
{
return SCFile;
}
// Qualify based on process base directory.
// Something akin to "C:/Epic/UE4/Engine/Binaries/Win64/" as a current path.
SCFile = FPaths::ConvertRelativePathToFull(InFile);
if (FPaths::FileExists(SCFile) || (bAllowDirectories && FPaths::DirectoryExists(SCFile)))
{
return SCFile;
}
// Qualify based on project directory.
SCFile = FPaths::ConvertRelativePathToFull(FPaths::ConvertRelativePathToFull(FPaths::ProjectDir()), InFile);
if (FPaths::FileExists(SCFile) || (bAllowDirectories && FPaths::DirectoryExists(SCFile)))
{
return SCFile;
}
// Qualify based on Engine directory
SCFile = FPaths::ConvertRelativePathToFull(FPaths::ConvertRelativePathToFull(FPaths::EngineDir()), InFile);
return SCFile;
}
/**
* Converts specified files to fully qualified file paths that are compatible with source control.
*
* @param InFiles File strings - can be either fully qualified path, relative path, long package name, asset name or export text path (often stored on clipboard)
* @param OutFilePaths Fully qualified file paths to use with source control or "" if conversion unsuccessful.
* @param bSilent if false then write out any error info to the Log. Any error text can be retrieved by LastErrorMsg() regardless.
* @return true if all files successfully converted, false if any had errors
*/
bool ConvertFilesToQualifiedPaths(const TArray<FString>& InFiles, TArray<FString>& OutFilePaths, bool bSilent, bool bAllowDirectories = false)
{
uint32 SkipNum = 0u;
for (const FString& File : InFiles)
{
FString SCFile = ConvertFileToQualifiedPath(File, bSilent, bAllowDirectories);
if (SCFile.IsEmpty())
{
SkipNum++;
}
else
{
OutFilePaths.Add(MoveTemp(SCFile));
}
}
if (SkipNum)
{
FFormatNamedArguments Arguments;
Arguments.Add(TEXT("SkipNum"), FText::AsNumber(SkipNum));
LogError(FText::Format(LOCTEXT("FilesSkipped", "During conversion to qualified file paths, {SkipNum} files were skipped!"), Arguments), bSilent);
return false;
}
return true;
}
} // namespace SourceControlHelpersInternal
FString USourceControlHelpers::CurrentProvider()
{
// Note that if there is no provider there is still a dummy default provider object
ISourceControlProvider& Provider = ISourceControlModule::Get().GetProvider();
return Provider.GetName().ToString();
}
bool USourceControlHelpers::IsEnabled()
{
return ISourceControlModule::Get().IsEnabled();
}
bool USourceControlHelpers::IsAvailable()
{
ISourceControlModule& SCModule = ISourceControlModule::Get();
return SCModule.IsEnabled() && SCModule.GetProvider().IsAvailable();
}
FText USourceControlHelpers::LastErrorMsg()
{
return SourceControlHelpersInternal::LastErrorText;
}
bool USourceControlHelpers::SyncFile(const FString& InFile, bool bSilent)
{
// Determine file type and ensure it is in form source control wants
FString SCFile = SourceControlHelpersInternal::ConvertFileToQualifiedPath(InFile, bSilent, /*bAllowDirectories*/true);
if (SCFile.IsEmpty())
{
return false;
}
// Ensure source control system is up and running
ISourceControlProvider* Provider = SourceControlHelpersInternal::VerifySourceControl(bSilent);
if (!Provider)
{
return false;
}
if (Provider->Execute(ISourceControlOperation::Create<FSync>(), SCFile) == ECommandResult::Succeeded)
{
return true;
}
// Only error info after this point
FFormatNamedArguments Arguments;
Arguments.Add(TEXT("InFile"), FText::FromString(InFile));
Arguments.Add(TEXT("SCFile"), FText::FromString(SCFile));
SourceControlHelpersInternal::LogError(FText::Format(LOCTEXT("SyncFailed", "Failed to sync file '{InFile}' ({SCFile})."), Arguments), bSilent);
return false;
}
bool USourceControlHelpers::SyncFiles(const TArray<FString>& InFiles, bool bSilent)
{
ISourceControlProvider* Provider = SourceControlHelpersInternal::VerifySourceControl(bSilent);
if (!Provider)
{
return false;
}
TArray<FString> FilePaths;
// Even if some files were skipped, still apply to the others
bool bFilesSkipped = !SourceControlHelpersInternal::ConvertFilesToQualifiedPaths(InFiles, FilePaths, bSilent, /*bAllowDirectories*/true);
// Less error checking and info is made for multiple files than the single file version.
// This multi-file version could be made similarly more sophisticated.
ECommandResult::Type Result = Provider->Execute(ISourceControlOperation::Create<FSync>(), FilePaths);
return !bFilesSkipped && (Result == ECommandResult::Succeeded);
}
bool USourceControlHelpers::CheckOutFile(const FString& InFile, bool bSilent)
{
// Determine file type and ensure it is in form source control wants
FString SCFile = SourceControlHelpersInternal::ConvertFileToQualifiedPath(InFile, bSilent);
if (SCFile.IsEmpty())
{
return false;
}
// Ensure source control system is up and running
ISourceControlProvider* Provider = SourceControlHelpersInternal::VerifySourceControl(bSilent);
if (!Provider)
{
return false;
}
FSourceControlStatePtr SCState = Provider->GetState(SCFile, EStateCacheUsage::ForceUpdate);
if (!SCState.IsValid())
{
// Improper or invalid SCC state
FFormatNamedArguments Arguments;
Arguments.Add(TEXT("InFile"), FText::FromString(InFile));
Arguments.Add(TEXT("SCFile"), FText::FromString(SCFile));
SourceControlHelpersInternal::LogError(FText::Format(LOCTEXT("CouldNotDetermineState", "Could not determine source control state of file '{InFile}' ({SCFile})."), Arguments), bSilent);
return false;
}
if (SCState->IsCheckedOut() || SCState->IsAdded())
{
// Already checked out or opened for add
return true;
}
bool bCheckOutFail = false;
if (SCState->CanCheckout())
{
if (Provider->Execute(ISourceControlOperation::Create<FCheckOut>(), SCFile) == ECommandResult::Succeeded)
{
return true;
}
bCheckOutFail = true;
}
// Only error info after this point
FString SimultaneousCheckoutUser;
FFormatNamedArguments Arguments;
Arguments.Add(TEXT("InFile"), FText::FromString(InFile));
Arguments.Add(TEXT("SCFile"), FText::FromString(SCFile));
if (bCheckOutFail)
{
SourceControlHelpersInternal::LogError(FText::Format(LOCTEXT("CheckoutFailed", "Failed to check out file '{InFile}' ({SCFile})."), Arguments), bSilent);
}
else if (!SCState->IsSourceControlled())
{
SourceControlHelpersInternal::LogError(FText::Format(LOCTEXT("NotSourceControlled", "Could not check out the file '{InFile}' because it is not under source control ({SCFile})."), Arguments), bSilent);
}
else if (!SCState->IsCurrent())
{
SourceControlHelpersInternal::LogError(FText::Format(LOCTEXT("NotAtHeadRevision", "File '{InFile}' is not at head revision ({SCFile})."), Arguments), bSilent);
}
else if (SCState->IsCheckedOutOther(&(SimultaneousCheckoutUser)))
{
Arguments.Add(TEXT("SimultaneousCheckoutUser"), FText::FromString(SimultaneousCheckoutUser));
SourceControlHelpersInternal::LogError(FText::Format(LOCTEXT("SimultaneousCheckout", "File '{InFile}' is checked out by another ({SimultaneousCheckoutUser}) ({SCFile})."), Arguments), bSilent);
}
else
{
// Improper or invalid SCC state
SourceControlHelpersInternal::LogError(FText::Format(LOCTEXT("CouldNotDetermineState", "Could not determine source control state of file '{InFile}' ({SCFile})."), Arguments), bSilent);
}
return false;
}
bool USourceControlHelpers::CheckOutFiles(const TArray<FString>& InFiles, bool bSilent)
{
// Determine file type and ensure it is in form source control wants
// Even if some files were skipped, still apply to the others
TArray<FString> SCFiles;
bool bFilesSkipped = !SourceControlHelpersInternal::ConvertFilesToQualifiedPaths(InFiles, SCFiles, bSilent);
const int32 NumFiles = SCFiles.Num();
// Ensure source control system is up and running
ISourceControlProvider* Provider = SourceControlHelpersInternal::VerifySourceControl(bSilent);
if (!Provider)
{
// Error or can't communicate with source control
return false;
}
TArray<FSourceControlStateRef> SCStates;
Provider->GetState(SCFiles, SCStates, EStateCacheUsage::ForceUpdate);
TArray<FString> SCFilesToCheckout;
bool bCannotCheckoutAtLeastOneFile = false;
for (int32 Index = 0; Index < NumFiles; ++Index)
{
FString SCFile = SCFiles[Index];
FSourceControlStateRef SCState = SCStates[Index];
// Less error checking and info is made for multiple files than the single file version.
// This multi-file version could be made similarly more sophisticated.
if (!SCState->IsCheckedOut() && !SCState->IsAdded())
{
if (SCState->CanCheckout())
{
SCFilesToCheckout.Add(SCFile);
}
else
{
bCannotCheckoutAtLeastOneFile = true;
}
}
}
bool bSuccess = !bFilesSkipped && !bCannotCheckoutAtLeastOneFile;
if (SCFilesToCheckout.Num())
{
bSuccess &= Provider->Execute(ISourceControlOperation::Create<FCheckOut>(), SCFilesToCheckout) == ECommandResult::Succeeded;
}
return bSuccess;
}
bool USourceControlHelpers::CheckOutOrAddFile(const FString& InFile, bool bSilent)
{
// Determine file type and ensure it is in form source control wants
FString SCFile = SourceControlHelpersInternal::ConvertFileToQualifiedPath(InFile, bSilent);
if (SCFile.IsEmpty())
{
return false;
}
// Ensure source control system is up and running
ISourceControlProvider* Provider = SourceControlHelpersInternal::VerifySourceControl(bSilent);
if (!Provider)
{
return false;
}
FSourceControlStatePtr SCState = Provider->GetState(SCFile, EStateCacheUsage::ForceUpdate);
if (!SCState.IsValid())
{
// Improper or invalid SCC state
FFormatNamedArguments Arguments;
Arguments.Add(TEXT("InFile"), FText::FromString(InFile));
Arguments.Add(TEXT("SCFile"), FText::FromString(SCFile));
SourceControlHelpersInternal::LogError(FText::Format(LOCTEXT("CouldNotDetermineState", "Could not determine source control state of file '{InFile}' ({SCFile})."), Arguments), bSilent);
return false;
}
if (SCState->IsCheckedOut() || SCState->IsAdded())
{
// Already checked out or opened for add
return true;
}
// Stuff single file in array for functions that require array
TArray<FString> FilesToBeCheckedOut;
FilesToBeCheckedOut.Add(SCFile);
if (SCState->CanCheckout())
{
if (Provider->Execute(ISourceControlOperation::Create<FCheckOut>(), FilesToBeCheckedOut) != ECommandResult::Succeeded)
{
FFormatNamedArguments Arguments;
Arguments.Add(TEXT("InFile"), FText::FromString(InFile));
Arguments.Add(TEXT("SCFile"), FText::FromString(SCFile));
SourceControlHelpersInternal::LogError(FText::Format(LOCTEXT("CheckoutFailed", "Failed to check out file '{InFile}' ({SCFile})."), Arguments), bSilent);
return false;
}
return true;
}
bool bAddFail = false;
if (!SCState->IsSourceControlled())
{
if (Provider->Execute(ISourceControlOperation::Create<FMarkForAdd>(), FilesToBeCheckedOut) == ECommandResult::Succeeded)
{
return true;
}
bAddFail = true;;
}
FString SimultaneousCheckoutUser;
FFormatNamedArguments Arguments;
Arguments.Add(TEXT("InFile"), FText::FromString(InFile));
Arguments.Add(TEXT("SCFile"), FText::FromString(SCFile));
if (bAddFail)
{
SourceControlHelpersInternal::LogError(FText::Format(LOCTEXT("AddFailed", "Failed to add file '{InFile}' to source control ({SCFile})."), Arguments), bSilent);
}
else if (!SCState->IsCurrent())
{
SourceControlHelpersInternal::LogError(FText::Format(LOCTEXT("NotAtHeadRevision", "File '{InFile}' is not at head revision ({SCFile})."), Arguments), bSilent);
}
else if (SCState->IsCheckedOutOther(&(SimultaneousCheckoutUser)))
{
Arguments.Add(TEXT("SimultaneousCheckoutUser"), FText::FromString(SimultaneousCheckoutUser));
SourceControlHelpersInternal::LogError(FText::Format(LOCTEXT("SimultaneousCheckout", "File '{InFile}' is checked out by another ({SimultaneousCheckoutUser}) ({SCFile})."), Arguments), bSilent);
}
else
{
// Improper or invalid SCC state
SourceControlHelpersInternal::LogError(FText::Format(LOCTEXT("CouldNotDetermineState", "Could not determine source control state of file '{InFile}' ({SCFile})."), Arguments), bSilent);
}
return false;
}
bool USourceControlHelpers::CheckOutOrAddFiles(const TArray<FString>& InFiles, bool bSilent)
{
// Determine file type and ensure it is in form source control wants
// Even if some files were skipped, still apply to the others
TArray<FString> SCFiles;
bool bFilesSkipped = !SourceControlHelpersInternal::ConvertFilesToQualifiedPaths(InFiles, SCFiles, bSilent);
const int32 NumFiles = SCFiles.Num();
// Ensure source control system is up and running
ISourceControlProvider* Provider = SourceControlHelpersInternal::VerifySourceControl(bSilent);
if (!Provider)
{
// Error or can't communicate with source control
return false;
}
TArray<FSourceControlStateRef> SCStates;
Provider->GetState(SCFiles, SCStates, EStateCacheUsage::ForceUpdate);
TArray<FString> SCFilesToAdd;
TArray<FString> SCFilesToCheckout;
bool bCannotAddAtLeastOneFile = false;
bool bCannotCheckoutAtLeastOneFile = false;
for (int32 Index = 0; Index < NumFiles; ++Index)
{
FString SCFile = SCFiles[Index];
FSourceControlStateRef SCState = SCStates[Index];
// Less error checking and info is made for multiple files than the single file version.
// This multi-file version could be made similarly more sophisticated.
if (!SCState->IsCheckedOut())
{
if (!SCState->IsAdded())
{
if (SCState->CanAdd())
{
SCFilesToAdd.Add(SCFile);
}
else
{
bCannotAddAtLeastOneFile = true;
}
}
else
{
if (SCState->CanCheckout())
{
SCFilesToCheckout.Add(SCFile);
}
else
{
bCannotCheckoutAtLeastOneFile = true;
}
}
}
}
bool bSuccess = !bFilesSkipped && !bCannotCheckoutAtLeastOneFile && !bCannotAddAtLeastOneFile;
if (SCFilesToAdd.Num())
{
bSuccess &= Provider->Execute(ISourceControlOperation::Create<FMarkForAdd>(), SCFilesToAdd) == ECommandResult::Succeeded;
}
if (SCFilesToCheckout.Num())
{
bSuccess &= Provider->Execute(ISourceControlOperation::Create<FCheckOut>(), SCFilesToCheckout) == ECommandResult::Succeeded;
}
return bSuccess;
}
bool USourceControlHelpers::MarkFileForAdd(const FString& InFile, bool bSilent)
{
// Determine file type and ensure it is in form source control wants
FString SCFile = SourceControlHelpersInternal::ConvertFileToQualifiedPath(InFile, bSilent);
if (SCFile.IsEmpty())
{
return false;
}
// Ensure source control system is up and running
ISourceControlProvider* Provider = SourceControlHelpersInternal::VerifySourceControl(bSilent);
if (!Provider)
{
return false;
}
// Mark for add now if needed
FSourceControlStatePtr SCState = Provider->GetState(SCFile, EStateCacheUsage::Use);
if (!SCState.IsValid())
{
// Improper or invalid SCC state
FFormatNamedArguments Arguments;
Arguments.Add(TEXT("InFile"), FText::FromString(InFile));
Arguments.Add(TEXT("SCFile"), FText::FromString(SCFile));
SourceControlHelpersInternal::LogError(FText::Format(LOCTEXT("CouldNotDetermineState", "Could not determine source control state of file '{InFile}' ({SCFile})."), Arguments), bSilent);
return false;
}
// Add if necessary
if (SCState->IsUnknown() || (!SCState->IsSourceControlled() && !SCState->IsAdded()))
{
if (Provider->Execute(ISourceControlOperation::Create<FMarkForAdd>(), SCFile) != ECommandResult::Succeeded)
{
FFormatNamedArguments Arguments;
Arguments.Add(TEXT("InFile"), FText::FromString(InFile));
Arguments.Add(TEXT("SCFile"), FText::FromString(SCFile));
SourceControlHelpersInternal::LogError(FText::Format(LOCTEXT("MarkForAddFailed", "Failed to add file '{InFile}' to source control ({SCFile})."), Arguments), bSilent);
return false;
}
}
return true;
}
bool USourceControlHelpers::MarkFilesForAdd(const TArray<FString>& InFiles, bool bSilent)
{
ISourceControlProvider* Provider = SourceControlHelpersInternal::VerifySourceControl(bSilent);
if (!Provider)
{
return false;
}
TArray<FString> FilePaths;
// Even if some files were skipped, still apply to the others
bool bFilesSkipped = !SourceControlHelpersInternal::ConvertFilesToQualifiedPaths(InFiles, FilePaths, bSilent);
// Less error checking and info is made for multiple files than the single file version.
// This multi-file version could be made similarly more sophisticated.
ECommandResult::Type Result = Provider->Execute(ISourceControlOperation::Create<FMarkForAdd>(), FilePaths);
return !bFilesSkipped && (Result == ECommandResult::Succeeded);
}
bool USourceControlHelpers::MarkFileForDelete(const FString& InFile, bool bSilent)
{
// Determine file type and ensure it is in form source control wants
FString SCFile = SourceControlHelpersInternal::ConvertFileToQualifiedPath(InFile, bSilent);
if (SCFile.IsEmpty())
{
return false;
}
// Ensure source control system is up and running
ISourceControlProvider* Provider = SourceControlHelpersInternal::VerifySourceControl(bSilent);
if (!Provider)
{
// Error or can't communicate with source control
// Could erase it anyway, though keeping it for now.
return false;
}
FSourceControlStatePtr SCState = Provider->GetState(SCFile, EStateCacheUsage::ForceUpdate);
if (!SCState.IsValid())
{
// Improper or invalid SCC state
FFormatNamedArguments Arguments;
Arguments.Add(TEXT("InFile"), FText::FromString(InFile));
Arguments.Add(TEXT("SCFile"), FText::FromString(SCFile));
SourceControlHelpersInternal::LogError(FText::Format(LOCTEXT("CouldNotDetermineState", "Could not determine source control state of file '{InFile}' ({SCFile})."), Arguments), bSilent);
return false;
}
bool bDelete = false;
if (SCState->IsSourceControlled())
{
bool bAdded = SCState->IsAdded();
if (bAdded || SCState->IsCheckedOut())
{
if (Provider->Execute(ISourceControlOperation::Create<FRevert>(), SCFile) != ECommandResult::Succeeded)
{
FFormatNamedArguments Arguments;
Arguments.Add(TEXT("InFile"), FText::FromString(InFile));
Arguments.Add(TEXT("SCFile"), FText::FromString(SCFile));
SourceControlHelpersInternal::LogError(FText::Format(LOCTEXT("CouldNotRevert", "Could not revert source control state of file '{InFile}' ({SCFile})."), Arguments), bSilent);
return false;
}
}
if (!bAdded)
{
// Was previously added to source control so mark it for delete
if (Provider->Execute(ISourceControlOperation::Create<FDelete>(), SCFile) != ECommandResult::Succeeded)
{
FFormatNamedArguments Arguments;
Arguments.Add(TEXT("InFile"), FText::FromString(InFile));
Arguments.Add(TEXT("SCFile"), FText::FromString(SCFile));
SourceControlHelpersInternal::LogError(FText::Format(LOCTEXT("CouldNotDelete", "Could not delete file '{InFile}' from source control ({SCFile})."), Arguments), bSilent);
return false;
}
}
}
// Delete file if it still exists
IFileManager& FileManager = IFileManager::Get();
if (FileManager.FileExists(*SCFile))
{
// Just a regular file not tracked by source control so erase it.
// Don't bother checking if it exists since Delete doesn't care.
return FileManager.Delete(*SCFile, false, true);
}
return false;
}
bool USourceControlHelpers::MarkFilesForDelete(const TArray<FString>& InFiles, bool bSilent)
{
// Determine file type and ensure it is in form source control wants
// Even if some files were skipped, still apply to the others
TArray<FString> SCFiles;
bool bFilesSkipped = !SourceControlHelpersInternal::ConvertFilesToQualifiedPaths(InFiles, SCFiles, bSilent);
const int32 NumFiles = SCFiles.Num();
// Ensure source control system is up and running
ISourceControlProvider* Provider = SourceControlHelpersInternal::VerifySourceControl(bSilent);
if (!Provider)
{
// Error or can't communicate with source control
// Could erase the files anyway, though keeping them for now.
return false;
}
TArray<FSourceControlStateRef> SCStates;
Provider->GetState(SCFiles, SCStates, EStateCacheUsage::ForceUpdate);
TArray<FString> SCFilesToRevert;
TArray<FString> SCFilesToMarkForDelete;
bool bCannotDeleteAtLeastOneFile = false;
for (int32 Index = 0; Index < NumFiles; ++Index)
{
FString SCFile = SCFiles[Index];
FSourceControlStateRef SCState = SCStates[Index];
// Less error checking and info is made for multiple files than the single file version.
// This multi-file version could be made similarly more sophisticated.
if (SCState->IsSourceControlled())
{
bool bAdded = SCState->IsAdded();
if (bAdded || SCState->IsCheckedOut())
{
SCFilesToRevert.Add(SCFile);
}
if (!bAdded)
{
if (SCState->CanDelete())
{
SCFilesToMarkForDelete.Add(SCFile);
}
else
{
bCannotDeleteAtLeastOneFile = true;
}
}
}
}
bool bSuccess = !bFilesSkipped && !bCannotDeleteAtLeastOneFile;
if (SCFilesToRevert.Num())
{
bSuccess &= Provider->Execute(ISourceControlOperation::Create<FRevert>(), SCFilesToRevert) == ECommandResult::Succeeded;
}
if (SCFilesToMarkForDelete.Num())
{
bSuccess &= Provider->Execute(ISourceControlOperation::Create<FDelete>(), SCFilesToMarkForDelete) == ECommandResult::Succeeded;
}
// Delete remaining files if they still exist :
IFileManager& FileManager = IFileManager::Get();
for (FString SCFile : SCFiles)
{
if (FileManager.FileExists(*SCFile))
{
// Just a regular file not tracked by source control so erase it.
// Don't bother checking if it exists since Delete doesn't care.
bSuccess &= FileManager.Delete(*SCFile, false, true);
}
}
return bSuccess;
}
bool USourceControlHelpers::RevertFile(const FString& InFile, bool bSilent)
{
// Determine file type and ensure it is in form source control wants
FString SCFile = SourceControlHelpersInternal::ConvertFileToQualifiedPath(InFile, bSilent);
if (SCFile.IsEmpty())
{
return false;
}
// Ensure source control system is up and running
ISourceControlProvider* Provider = SourceControlHelpersInternal::VerifySourceControl(bSilent);
if (!Provider)
{
return false;
}
// Revert file regardless of whether it has had any changes made
ECommandResult::Type Result = Provider->Execute(ISourceControlOperation::Create<FRevert>(), SCFile);
return Result == ECommandResult::Succeeded;
}
bool USourceControlHelpers::RevertFiles(const TArray<FString>& InFiles, bool bSilent)
{
// Determine file type and ensure they are in form source control wants
// Even if some files were skipped, still apply to the others
TArray<FString> SCFiles;
bool bFilesSkipped = !SourceControlHelpersInternal::ConvertFilesToQualifiedPaths(InFiles, SCFiles, bSilent);
const int32 NumFiles = SCFiles.Num();
// Ensure source control system is up and running
ISourceControlProvider* Provider = SourceControlHelpersInternal::VerifySourceControl(bSilent);
if (!Provider)
{
// Error or can't communicate with source control
return false;
}
TArray<FSourceControlStateRef> SCStates;
Provider->GetState(SCFiles, SCStates, EStateCacheUsage::ForceUpdate);
TArray<FString> SCFilesToRevert;
for (int32 Index = 0; Index < NumFiles; ++Index)
{
FString SCFile = SCFiles[Index];
FSourceControlStateRef SCState = SCStates[Index];
// Less error checking and info is made for multiple files than the single file version.
// This multi-file version could be made similarly more sophisticated.
if (SCState->IsCheckedOut() || SCState->IsAdded())
{
SCFilesToRevert.Add(SCFile);
}
}
bool bSuccess = !bFilesSkipped;
if (SCFilesToRevert.Num())
{
bSuccess &= Provider->Execute(ISourceControlOperation::Create<FRevert>(), SCFilesToRevert) == ECommandResult::Succeeded;
}
return bSuccess;
}
bool USourceControlHelpers::RevertUnchangedFile(const FString& InFile, bool bSilent)
{
// Determine file type and ensure it is in form source control wants
FString SCFile = SourceControlHelpersInternal::ConvertFileToQualifiedPath(InFile, bSilent);
if (SCFile.IsEmpty())
{
return false;
}
// Ensure source control system is up and running
ISourceControlProvider* Provider = SourceControlHelpersInternal::VerifySourceControl(bSilent);
if (!Provider)
{
return false;
}
// Only revert file if they haven't had any changes made
// Stuff single file in array for functions that require array
TArray<FString> InFiles;
InFiles.Add(SCFile);
RevertUnchangedFiles(*Provider, InFiles);
// Assume it succeeded
return true;
}
bool USourceControlHelpers::RevertUnchangedFiles(const TArray<FString>& InFiles, bool bSilent)
{
// Determine file types and ensure they are in form source control wants
TArray<FString> FilePaths;
SourceControlHelpersInternal::ConvertFilesToQualifiedPaths(InFiles, FilePaths, bSilent);
ISourceControlProvider* Provider = SourceControlHelpersInternal::VerifySourceControl(bSilent);
if (!Provider)
{
return false;
}
// Less error checking and info is made for multiple files than the single file version.
// This multi-file version could be made similarly more sophisticated.
// Only revert files if they haven't had any changes made
RevertUnchangedFiles(*Provider, FilePaths);
// Assume it succeeded
return true;
}
bool USourceControlHelpers::CheckInFile(const FString& InFile, const FString& InDescription, bool bSilent)
{
// Determine file type and ensure it is in form source control wants
FString SCFile = SourceControlHelpersInternal::ConvertFileToQualifiedPath(InFile, bSilent);
if (SCFile.IsEmpty())
{
return false;
}
// Ensure source control system is up and running
ISourceControlProvider* Provider = SourceControlHelpersInternal::VerifySourceControl(bSilent);
if (!Provider)
{
return false;
}
TSharedRef<FCheckIn, ESPMode::ThreadSafe> CheckInOp = ISourceControlOperation::Create<FCheckIn>();
CheckInOp->SetDescription(FText::FromString(InDescription));
ECommandResult::Type Result = Provider->Execute(CheckInOp, SCFile);
return Result == ECommandResult::Succeeded;
}
bool USourceControlHelpers::CheckInFiles(const TArray<FString>& InFiles, const FString& InDescription, bool bSilent)
{
ISourceControlProvider* Provider = SourceControlHelpersInternal::VerifySourceControl(bSilent);
if (!Provider)
{
return false;
}
TArray<FString> FilePaths;
// Even if some files were skipped, still apply to the others
bool bFilesSkipped = !SourceControlHelpersInternal::ConvertFilesToQualifiedPaths(InFiles, FilePaths, bSilent);
// Less error checking and info is made for multiple files than the single file version.
// This multi-file version could be made similarly more sophisticated.
TSharedRef<FCheckIn, ESPMode::ThreadSafe> CheckInOp = ISourceControlOperation::Create<FCheckIn>();
CheckInOp->SetDescription(FText::FromString(InDescription));
ECommandResult::Type Result = Provider->Execute(CheckInOp, FilePaths);
return !bFilesSkipped && (Result == ECommandResult::Succeeded);
}
bool USourceControlHelpers::CopyFile(const FString& InSourcePath, const FString& InDestPath, bool bSilent)
{
// Determine file type and ensure it is in form source control wants
FString SCSource = SourceControlHelpersInternal::ConvertFileToQualifiedPath(InSourcePath, bSilent);
if (SCSource.IsEmpty())
{
return false;
}
// Determine file type and ensure it is in form source control wants
FString SCSourcExt(FPaths::GetExtension(SCSource, true));
FString SCDest(SourceControlHelpersInternal::ConvertFileToQualifiedPath(InDestPath, bSilent, /*bAllowDirectories*/false, *SCSourcExt));
if (SCDest.IsEmpty())
{
return false;
}
// Ensure source control system is up and running
ISourceControlProvider* Provider = SourceControlHelpersInternal::VerifySourceControl(bSilent);
if (!Provider)
{
return false;
}
TSharedRef<FCopy, ESPMode::ThreadSafe> CopyOp = ISourceControlOperation::Create<FCopy>();
CopyOp->SetDestination(SCDest);
ECommandResult::Type Result = Provider->Execute(CopyOp, SCSource);
return Result == ECommandResult::Succeeded;
}
FSourceControlState USourceControlHelpers::QueryFileState(const FString& InFile, bool bSilent)
{
FSourceControlState State;
State.bIsValid = false;
// Determine file type and ensure it is in form source control wants
FString SCFile = SourceControlHelpersInternal::ConvertFileToQualifiedPath(InFile, bSilent);
if (SCFile.IsEmpty())
{
State.Filename = InFile;
return State;
}
State.Filename = SCFile;
// Ensure source control system is up and running
ISourceControlProvider* Provider = SourceControlHelpersInternal::VerifySourceControl(bSilent);
if (!Provider)
{
return State;
}
// Make sure we update the modified state of the files (Perforce requires this
// since can be a more expensive test).
TSharedRef<FUpdateStatus, ESPMode::ThreadSafe> UpdateStatusOperation = ISourceControlOperation::Create<FUpdateStatus>();
UpdateStatusOperation->SetUpdateModifiedState(true);
Provider->Execute(UpdateStatusOperation, SCFile);
FSourceControlStatePtr SCState = Provider->GetState(SCFile, EStateCacheUsage::Use);
if (!SCState.IsValid())
{
// Improper or invalid SCC state
FFormatNamedArguments Arguments;
Arguments.Add(TEXT("InFile"), FText::FromString(InFile));
Arguments.Add(TEXT("SCFile"), FText::FromString(SCFile));
SourceControlHelpersInternal::LogError(FText::Format(LOCTEXT("CouldNotDetermineState", "Could not determine source control state of file '{InFile}' ({SCFile})."), Arguments), bSilent);
return State;
}
// Return FSourceControlState rather than a ISourceControlState directly so that
// scripting systems can access it.
State.bIsValid = true;
// Copy over state info
// - make these assignments a method of FSourceControlState if anything else sets a state
State.bIsUnknown = SCState->IsUnknown();
State.bIsSourceControlled = SCState->IsSourceControlled();
State.bCanCheckIn = SCState->CanCheckIn();
State.bCanCheckOut = SCState->CanCheckout();
State.bIsCheckedOut = SCState->IsCheckedOut();
State.bIsCurrent = SCState->IsCurrent();
State.bIsAdded = SCState->IsAdded();
State.bIsDeleted = SCState->IsDeleted();
State.bIsIgnored = SCState->IsIgnored();
State.bCanEdit = SCState->CanEdit();
State.bCanDelete = SCState->CanDelete();
State.bCanAdd = SCState->CanAdd();
State.bIsConflicted = SCState->IsConflicted();
State.bCanRevert = SCState->CanRevert();
State.bIsModified = SCState->IsModified();
State.bIsCheckedOutOther = SCState->IsCheckedOutOther();
if (State.bIsCheckedOutOther)
{
SCState->IsCheckedOutOther(&State.CheckedOutOther);
}
return State;
}
static FString PackageFilename_Internal( const FString& InPackageName )
{
FString Filename = InPackageName;
// Get the filename by finding it on disk first
if ( !FPackageName::DoesPackageExist(InPackageName, nullptr, &Filename) )
{
// The package does not exist on disk, see if we can find it in memory and predict the file extension
// Only do this if the supplied package name is valid
const bool bIncludeReadOnlyRoots = false;
if ( FPackageName::IsValidLongPackageName(InPackageName, bIncludeReadOnlyRoots) )
{
UPackage* Package = FindPackage(nullptr, *InPackageName);
// This is a package in memory that has not yet been saved. Determine the extension and convert to a filename, if we do have the package, just assume normal asset extension
const FString PackageExtension = Package && Package->ContainsMap() ? FPackageName::GetMapPackageExtension() : FPackageName::GetAssetPackageExtension();
Filename = FPackageName::LongPackageNameToFilename(InPackageName, PackageExtension);
}
}
return Filename;
}
FString USourceControlHelpers::PackageFilename( const FString& InPackageName )
{
return FPaths::ConvertRelativePathToFull(PackageFilename_Internal(InPackageName));
}
FString USourceControlHelpers::PackageFilename( const UPackage* InPackage )
{
FString Filename;
if(InPackage != nullptr)
{
Filename = FPaths::ConvertRelativePathToFull(PackageFilename_Internal(InPackage->GetName()));
}
return Filename;
}
TArray<FString> USourceControlHelpers::PackageFilenames( const TArray<UPackage*>& InPackages )
{
TArray<FString> OutNames;
for (int32 PackageIndex = 0; PackageIndex < InPackages.Num(); PackageIndex++)
{
OutNames.Add(FPaths::ConvertRelativePathToFull(PackageFilename(InPackages[PackageIndex])));
}
return OutNames;
}
TArray<FString> USourceControlHelpers::PackageFilenames( const TArray<FString>& InPackageNames )
{
TArray<FString> OutNames;
for (int32 PackageIndex = 0; PackageIndex < InPackageNames.Num(); PackageIndex++)
{
OutNames.Add(FPaths::ConvertRelativePathToFull(PackageFilename_Internal(InPackageNames[PackageIndex])));
}
return OutNames;
}
TArray<FString> USourceControlHelpers::AbsoluteFilenames( const TArray<FString>& InFileNames )
{
TArray<FString> AbsoluteFiles;
for (const FString& FileName : InFileNames)
{
if(!FPaths::IsRelative(FileName))
{
AbsoluteFiles.Add(FileName);
}
else
{
AbsoluteFiles.Add(FPaths::ConvertRelativePathToFull(FileName));
}
FPaths::NormalizeFilename(AbsoluteFiles[AbsoluteFiles.Num() - 1]);
}
return AbsoluteFiles;
}
void USourceControlHelpers::RevertUnchangedFiles( ISourceControlProvider& InProvider, const TArray<FString>& InFiles )
{
// Make sure we update the modified state of the files
TSharedRef<FUpdateStatus, ESPMode::ThreadSafe> UpdateStatusOperation = ISourceControlOperation::Create<FUpdateStatus>();
UpdateStatusOperation->SetUpdateModifiedState(true);
InProvider.Execute(UpdateStatusOperation, InFiles);
TArray<FString> UnchangedFiles;
TArray< TSharedRef<ISourceControlState, ESPMode::ThreadSafe> > OutStates;
InProvider.GetState(InFiles, OutStates, EStateCacheUsage::Use);
for(TArray< TSharedRef<ISourceControlState, ESPMode::ThreadSafe> >::TConstIterator It(OutStates); It; It++)
{
TSharedRef<ISourceControlState, ESPMode::ThreadSafe> SourceControlState = *It;
if(SourceControlState->IsCheckedOut() && !SourceControlState->IsModified())
{
UnchangedFiles.Add(SourceControlState->GetFilename());
}
}
if(UnchangedFiles.Num())
{
InProvider.Execute( ISourceControlOperation::Create<FRevert>(), UnchangedFiles );
}
}
bool USourceControlHelpers::AnnotateFile( ISourceControlProvider& InProvider, const FString& InLabel, const FString& InFile, TArray<FAnnotationLine>& OutLines )
{
TArray< TSharedRef<ISourceControlLabel> > Labels = InProvider.GetLabels( InLabel );
if(Labels.Num() > 0)
{
TSharedRef<ISourceControlLabel> Label = Labels[0];
TArray< TSharedRef<ISourceControlRevision, ESPMode::ThreadSafe> > Revisions;
Label->GetFileRevisions(InFile, Revisions);
if(Revisions.Num() > 0)
{
TSharedRef<ISourceControlRevision, ESPMode::ThreadSafe> Revision = Revisions[0];
if(Revision->GetAnnotated(OutLines))
{
return true;
}
}
}
return false;
}
bool USourceControlHelpers::AnnotateFile( ISourceControlProvider& InProvider, int32 InCheckInIdentifier, const FString& InFile, TArray<FAnnotationLine>& OutLines )
{
TSharedRef<FUpdateStatus, ESPMode::ThreadSafe> UpdateStatusOperation = ISourceControlOperation::Create<FUpdateStatus>();
UpdateStatusOperation->SetUpdateHistory(true);
if(InProvider.Execute(UpdateStatusOperation, InFile) == ECommandResult::Succeeded)
{
FSourceControlStatePtr State = InProvider.GetState(InFile, EStateCacheUsage::Use);
if(State.IsValid())
{
for(int32 HistoryIndex = State->GetHistorySize() - 1; HistoryIndex >= 0; HistoryIndex--)
{
// check that the changelist corresponds to this revision - we assume history is in latest-first order
TSharedPtr<ISourceControlRevision, ESPMode::ThreadSafe> Revision = State->GetHistoryItem(HistoryIndex);
if(Revision.IsValid() && Revision->GetCheckInIdentifier() >= InCheckInIdentifier)
{
if(Revision->GetAnnotated(OutLines))
{
return true;
}
}
}
}
}
return false;
}
bool USourceControlHelpers::CheckoutOrMarkForAdd( const FString& InDestFile, const FText& InFileDescription, const FOnPostCheckOut& OnPostCheckOut, FText& OutFailReason )
{
bool bSucceeded = true;
ISourceControlProvider& Provider = ISourceControlModule::Get().GetProvider();
// first check for source control check out
if (ISourceControlModule::Get().IsEnabled())
{
FSourceControlStatePtr SourceControlState = Provider.GetState(InDestFile, EStateCacheUsage::ForceUpdate);
if (SourceControlState.IsValid())
{
if (SourceControlState->IsSourceControlled() && SourceControlState->CanCheckout())
{
ECommandResult::Type Result = Provider.Execute(ISourceControlOperation::Create<FCheckOut>(), InDestFile);
bSucceeded = (Result == ECommandResult::Succeeded);
if (!bSucceeded)
{
OutFailReason = FText::Format(LOCTEXT("SourceControlCheckoutError", "Could not check out {0} file."), InFileDescription);
}
}
}
}
if (bSucceeded)
{
if(OnPostCheckOut.IsBound())
{
bSucceeded = OnPostCheckOut.Execute(InDestFile, InFileDescription, OutFailReason);
}
}
// mark for add now if needed
if (bSucceeded && ISourceControlModule::Get().IsEnabled())
{
FSourceControlStatePtr SourceControlState = Provider.GetState(InDestFile, EStateCacheUsage::Use);
if (SourceControlState.IsValid())
{
if (!SourceControlState->IsSourceControlled())
{
ECommandResult::Type Result = Provider.Execute(ISourceControlOperation::Create<FMarkForAdd>(), InDestFile);
bSucceeded = (Result == ECommandResult::Succeeded);
if (!bSucceeded)
{
OutFailReason = FText::Format(LOCTEXT("SourceControlMarkForAddError", "Could not mark {0} file for add."), InFileDescription);
}
}
}
}
return bSucceeded;
}
bool USourceControlHelpers::CopyFileUnderSourceControl( const FString& InDestFile, const FString& InSourceFile, const FText& InFileDescription, FText& OutFailReason)
{
struct Local
{
static bool CopyFile(const FString& InDestinationFile, const FText& InFileDesc, FText& OutFailureReason, FString InFileToCopy)
{
const bool bReplace = true;
const bool bEvenIfReadOnly = true;
bool bSucceeded = (IFileManager::Get().Copy(*InDestinationFile, *InFileToCopy, bReplace, bEvenIfReadOnly) == COPY_OK);
if (!bSucceeded)
{
OutFailureReason = FText::Format(LOCTEXT("ExternalImageCopyError", "Could not overwrite {0} file."), InFileDesc);
}
return bSucceeded;
}
};
return CheckoutOrMarkForAdd(InDestFile, InFileDescription, FOnPostCheckOut::CreateStatic(&Local::CopyFile, InSourceFile), OutFailReason);
}
bool USourceControlHelpers::BranchPackage( UPackage* DestPackage, UPackage* SourcePackage, EStateCacheUsage::Type StateCacheUsage )
{
if(ISourceControlModule::Get().IsEnabled())
{
ISourceControlProvider& SourceControlProvider = ISourceControlModule::Get().GetProvider();
const FString SourceFilename = PackageFilename(SourcePackage);
const FString DestFilename = PackageFilename(DestPackage);
FSourceControlStatePtr SourceControlState = SourceControlProvider.GetState(SourceFilename, StateCacheUsage);
if(SourceControlState.IsValid() && SourceControlState->IsSourceControlled())
{
TSharedRef<FCopy, ESPMode::ThreadSafe> CopyOperation = ISourceControlOperation::Create<FCopy>();
CopyOperation->SetDestination(DestFilename);
return (SourceControlProvider.Execute(CopyOperation, SourceFilename) == ECommandResult::Succeeded);
}
}
return false;
}
const FString& USourceControlHelpers::GetSettingsIni()
{
if (ISourceControlModule::Get().GetUseGlobalSettings())
{
return GetGlobalSettingsIni();
}
else
{
static FString SourceControlSettingsIni;
if (SourceControlSettingsIni.Len() == 0)
{
const FString SourceControlSettingsDir = FPaths::GeneratedConfigDir();
FConfigCacheIni::LoadGlobalIniFile(SourceControlSettingsIni, TEXT("SourceControlSettings"), nullptr, false, false, true, true, *SourceControlSettingsDir);
}
return SourceControlSettingsIni;
}
}
const FString& USourceControlHelpers::GetGlobalSettingsIni()
{
static FString SourceControlGlobalSettingsIni;
if (SourceControlGlobalSettingsIni.Len() == 0)
{
const FString SourceControlSettingsDir = FPaths::EngineSavedDir() + TEXT("Config/");
FConfigCacheIni::LoadGlobalIniFile(SourceControlGlobalSettingsIni, TEXT("SourceControlSettings"), nullptr, false, false, true, true, *SourceControlSettingsDir);
}
return SourceControlGlobalSettingsIni;
}
bool USourceControlHelpers::GetAssetData(const FString& InFileName, TArray<FAssetData>& OutAssets, TArray<FName>* OutDependencies)
{
FString PackageName;
if (FPackageName::TryConvertFilenameToLongPackageName(InFileName, PackageName))
{
return GetAssetData(InFileName, PackageName, OutAssets, OutDependencies);
}
else
{
return false;
}
}
bool USourceControlHelpers::GetAssetDataFromPackage(const FString& PackageName, TArray<FAssetData>& OutAssets, TArray<FName>* OutDependencies)
{
return GetAssetData(PackageFilename(PackageName), PackageName, OutAssets, OutDependencies);
}
bool USourceControlHelpers::GetAssetData(const FString & InFileName, const FString& InPackageName, TArray<FAssetData>& OutAssets, TArray<FName>* OutDependencies)
{
const bool bGetDependencies = (OutDependencies != nullptr);
OutAssets.Reset();
if (bGetDependencies)
{
OutDependencies->Reset();
}
// Try the registry first
FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(TEXT("AssetRegistry"));
AssetRegistryModule.Get().GetAssetsByPackageName(*InPackageName, OutAssets);
if (OutAssets.Num() > 0)
{
// Assets are already in the cache, we can query dependencies directly
if (bGetDependencies)
{
AssetRegistryModule.Get().GetDependencies(*InPackageName, *OutDependencies);
}
return true;
}
// Filter on improbable file extensions
EPackageExtension PackageExtension = FPackagePath::ParseExtension(InFileName);
if (PackageExtension == EPackageExtension::Unspecified ||
PackageExtension == EPackageExtension::Custom)
{
return false;
}
// If nothing was done, try to get the data explicitly
IAssetRegistry::FLoadPackageRegistryData LoadedData(bGetDependencies);
AssetRegistryModule.Get().LoadPackageRegistryData(InFileName, LoadedData);
OutAssets = MoveTemp(LoadedData.Data);
if (bGetDependencies)
{
*OutDependencies = MoveTemp(LoadedData.DataDependencies);
}
return OutAssets.Num() > 0;
}
bool USourceControlHelpers::GetAssetDataFromFileHistory(const FString& InFileName, TArray<FAssetData>& OutAssets, TArray<FName>* OutDependencies, int64 MaxFetchSize)
{
OutAssets.Reset();
if (OutDependencies)
{
OutDependencies->Reset();
}
ISourceControlProvider& SourceControlProvider = ISourceControlModule::Get().GetProvider();
// Get the SCC state
FSourceControlStatePtr SourceControlState = SourceControlProvider.GetState(InFileName, EStateCacheUsage::Use);
if (SourceControlState.IsValid())
{
return GetAssetDataFromFileHistory(SourceControlState, OutAssets, OutDependencies, MaxFetchSize);
}
else
{
return false;
}
}
bool USourceControlHelpers::GetAssetDataFromFileHistory(FSourceControlStatePtr InSourceControlState, TArray<FAssetData>& OutAssets, TArray<FName>* OutDependencies /* = nullptr */, int64 MaxFetchSize /* = -1 */)
{
check(InSourceControlState.IsValid());
OutAssets.Reset();
if (OutDependencies)
{
OutDependencies->Reset();
}
// This code is similar to what's done in UAssetToolsImpl::DiffAgainstDepot but we'll force it quiet to prevent recursion issues
if (InSourceControlState->GetHistorySize() == 0)
{
ISourceControlProvider& SourceControlProvider = ISourceControlModule::Get().GetProvider();
TSharedRef<FUpdateStatus, ESPMode::ThreadSafe> UpdateStatusOperation = ISourceControlOperation::Create<FUpdateStatus>();
UpdateStatusOperation->SetUpdateHistory(true);
UpdateStatusOperation->SetQuiet(true);
SourceControlProvider.Execute(UpdateStatusOperation, InSourceControlState->GetFilename());
}
if (InSourceControlState->GetHistorySize() > 0)
{
TSharedPtr<ISourceControlRevision, ESPMode::ThreadSafe> Revision = InSourceControlState->GetHistoryItem(0);
check(Revision.IsValid());
const bool bShouldGetFile = (MaxFetchSize < 0 || MaxFetchSize >(int64)Revision->GetFileSize());
FString TempFileName;
if (bShouldGetFile && Revision->Get(TempFileName))
{
return GetAssetData(TempFileName, OutAssets, OutDependencies);
}
}
return false;
}
FScopedSourceControl::FScopedSourceControl()
{
ISourceControlModule::Get().GetProvider().Init();
}
FScopedSourceControl::~FScopedSourceControl()
{
ISourceControlModule::Get().GetProvider().Close();
}
ISourceControlProvider& FScopedSourceControl::GetProvider()
{
return ISourceControlModule::Get().GetProvider();
}
#undef LOCTEXT_NAMESPACE