Files
UnrealEngine/Engine/Source/Developer/SourceControl/Private/SourceControlHelpers.cpp
2025-05-18 13:04:45 +08:00

2208 lines
69 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "SourceControlHelpers.h"
#include "Algo/Transform.h"
#include "AssetRegistry/AssetData.h"
#include "ISourceControlState.h"
#include "HAL/FileManager.h"
#include "Misc/Paths.h"
#include "Misc/ConfigContext.h"
#include "ISourceControlOperation.h"
#include "SourceControlOperations.h"
#include "ISourceControlProvider.h"
#include "ISourceControlModule.h"
#include "ISourceControlLabel.h"
#include "UObject/Linker.h"
#include "UObject/Package.h"
#include "UObject/UObjectIterator.h"
#include "Misc/PackageName.h"
#include "Logging/MessageLog.h"
#include "AssetRegistry/AssetRegistryModule.h"
#include "Internationalization/PackageLocalizationUtil.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(SourceControlHelpers)
#if WITH_EDITOR
#include "Editor.h"
#include "PackageTools.h"
#include "ObjectTools.h"
#include "FileHelpers.h"
#endif
#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", "Revision control is not enabled."), bSilent);
return nullptr;
}
ISourceControlProvider* Provider = &SCModule.GetProvider();
if (!Provider->IsAvailable())
{
LogError(LOCTEXT("SourceControlServerUnavailable", "Revision 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, &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/UE/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;
}
void RunQueryFileStates(TConstArrayView<FString> InFiles, EConcurrency::Type InConcurrencyType, bool bSilent, TFunction<void(FSourceControlState)> FileStateCallback)
{
// Ensure source control system is up and running
ISourceControlProvider* Provider = SourceControlHelpersInternal::VerifySourceControl(bSilent);
if (!Provider)
{
return;
}
TArray<FString> SCFilesToUpdate;
for (const FString& File : InFiles)
{
// Determine file type and ensure it is in form source control wants
FString SCFile = SourceControlHelpersInternal::ConvertFileToQualifiedPath(File, bSilent);
if (SCFile.IsEmpty())
{
// Improper or invalid SCC state
FFormatNamedArguments Arguments;
Arguments.Add(TEXT("InFile"), FText::FromString(File));
SourceControlHelpersInternal::LogError(FText::Format(LOCTEXT("CouldNotResolveFilepath", "Could not resolve file path for '{InFile}'."), Arguments), bSilent);
continue;
}
SCFilesToUpdate.Emplace(MoveTemp(SCFile));
}
// 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);
ISourceControlModule::Get().GetProvider().Execute(
UpdateStatusOperation,
SCFilesToUpdate,
InConcurrencyType,
FSourceControlOperationComplete::CreateLambda([FileStateCallback, Provider, SCFilesToUpdate, bSilent](const FSourceControlOperationRef& InOperation, ECommandResult::Type InResult)
{
TArray<FSourceControlStateRef> SCStates;
Provider->GetState(SCFilesToUpdate, SCStates, EStateCacheUsage::Use);
for (const FSourceControlStatePtr SCState : SCStates)
{
FSourceControlState State;
State.SetFromStatus(SCState);
FileStateCallback(State);
}
}));
}
} // 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)
{
// If we have nothing to process, exit immediately
if (InFiles.IsEmpty())
{
return true;
}
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);
}
void LogCheckoutFailure(const FString& InFile, const FString& SCFile, FSourceControlStatePtr SCState, bool bCheckoutFailed, bool bSilent)
{
FString SimultaneousCheckoutUser;
FFormatNamedArguments Arguments;
Arguments.Add(TEXT("InFile"), FText::FromString(InFile));
Arguments.Add(TEXT("SCFile"), FText::FromString(SCFile));
if (bCheckoutFailed)
{
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 revision 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 revision control state of file '{InFile}' ({SCFile})."), Arguments), bSilent);
}
}
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())
{
LogCheckoutFailure(InFile, SCFile, SCState, false, bSilent);
return false;
}
if (SCState->IsCheckedOut() || SCState->IsAdded())
{
// Already checked out or opened for add
return true;
}
bool bCheckOutFailed = false;
if (SCState->CanCheckout())
{
if (Provider->Execute(ISourceControlOperation::Create<FCheckOut>(), SCFile) == ECommandResult::Succeeded)
{
return true;
}
bCheckOutFailed = true;
}
LogCheckoutFailure(InFile, SCFile, SCState, bCheckOutFailed, bSilent);
return false;
}
bool USourceControlHelpers::CheckOutFiles(const TArray<FString>& InFiles, bool bSilent)
{
// If we have nothing to process, exit immediately
if (InFiles.IsEmpty())
{
return true;
}
// 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;
LogCheckoutFailure(InFiles[Index], SCFile, SCState, false, bSilent);
}
}
}
bool bSuccess = !bFilesSkipped && !bCannotCheckoutAtLeastOneFile;
if (bSuccess && 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 revision 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 revision 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 revision control state of file '{InFile}' ({SCFile})."), Arguments), bSilent);
}
return false;
}
bool USourceControlHelpers::CheckOutOrAddFiles(const TArray<FString>& InFiles, bool bSilent)
{
// If we have nothing to process, exit immediately
if (InFiles.IsEmpty())
{
return true;
}
// 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() && !SCState->IsAdded())
{
if (!SCState->IsSourceControlled())
{
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 revision 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 revision control ({SCFile})."), Arguments), bSilent);
return false;
}
}
return true;
}
bool USourceControlHelpers::MarkFilesForAdd(const TArray<FString>& InFiles, bool bSilent)
{
// If we have nothing to process, exit immediately
if (InFiles.IsEmpty())
{
return true;
}
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 revision control state of file '{InFile}' ({SCFile})."), Arguments), bSilent);
return 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 revision 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 revision 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);
}
else
{
return true;
}
}
bool USourceControlHelpers::MarkFilesForDelete(const TArray<FString>& InFiles, bool bSilent)
{
// If we have nothing to process, exit immediately
if (InFiles.IsEmpty())
{
return true;
}
// 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;
}
#if WITH_EDITOR
bool USourceControlHelpers::ApplyOperationAndReloadPackages(const TArray<FString>& InFilenames, const TFunctionRef<bool(const TArray<FString>&)>& InOperation, bool bReloadWorld, bool bInteractive)
{
ISourceControlProvider& SourceControlProvider = ISourceControlModule::Get().GetProvider();
TArray<FString> PackageNames;
TArray<FString> PackageFilenames;
TArray<FString> FilteredPackages;
TArray<FString> NotFoundPackages;
TArray<UPackage*> UnlinkPackages;
/**
* This is necessary to cover an edge case where the user reverts a deleted Level file.
* In that case PackageFilename_Internal() will not be able to resolve to the correct extension
* as the file is deleted and the package cannot be inspected
*/
TMap<FString, FString> MapPackageNamesToFilenames;
bool bSuccess = false;
// Normalize packagenames and filenames
for (const FString& Filename : InFilenames)
{
FString Result;
if (FPackageName::TryConvertFilenameToLongPackageName(Filename, Result))
{
FString Extension = FPaths::GetExtension(Filename);
if (Extension == TEXT("umap"))
{
MapPackageNamesToFilenames.Add(Result, Filename);
}
PackageNames.Add(MoveTemp(Result));
}
else
{
PackageNames.Add(Filename);
}
}
// If bReloadWorld=false, remove packages if they are loaded external packages or world
// If bReloadWorld=true, include world packages to reload
TSet<UPackage*> UniqueLoadedPackages;
PackageNames.RemoveAll([&FilteredPackages, &UniqueLoadedPackages, &NotFoundPackages, &UnlinkPackages, bReloadWorld](const FString& PackageName) -> bool
{
UPackage* Package = FindPackage(NULL, *PackageName);
if (Package != nullptr)
{
if (UWorld* World = UWorld::FindWorldInPackage(Package))
{
if (!bReloadWorld)
{
FilteredPackages.Emplace(PackageName);
return true; // remove the package
}
}
else if (UObject* Asset = Package->FindAssetInPackage())
{
if (Asset->IsPackageExternal())
{
const bool bIsAssetPartOfWorld = Asset->GetWorld() && Asset->GetWorld()->GetPackage();
// In case this is an explicit world object/actor reload or just an external object unload/detach it
if (bReloadWorld || !bIsAssetPartOfWorld)
{
// detach linker on the object
UnlinkPackages.Emplace(Package);
// track its world for reloading - not the object package itself
if (bIsAssetPartOfWorld)
{
UniqueLoadedPackages.Add(Asset->GetWorld()->GetPackage());
}
return false;
}
else
{
FilteredPackages.Emplace(PackageName);
return true; // remove the package
}
}
}
UniqueLoadedPackages.Add(Package);
}
else
{
NotFoundPackages.Add(PackageName);
}
return false; // do not remove the package
});
if (!FilteredPackages.IsEmpty())
{
TStringBuilder<2048> Builder;
Builder.Join(FilteredPackages, TEXT(", "));
const FString Packages = Builder.ToString();
UE_LOG(LogSourceControl, Warning, TEXT("This operation could not complete on the following map or external packages, please unload them before retrying : %s"), *Packages);
return false;
}
// Reverting may reintroduce some packages, so we need to ensure they're picked up...
if (!NotFoundPackages.IsEmpty() && bReloadWorld)
{
const FString& ExternalActorsFolderName = FPackagePath::GetExternalActorsFolderName();
const FString& ExternalObjectsFolderName = FPackagePath::GetExternalObjectsFolderName();
TArray<FString> ExternalFolderNames;
ExternalFolderNames.Add(ExternalActorsFolderName);
ExternalFolderNames.Add(ExternalObjectsFolderName);
// Gather the ExternalPaths in which we're reintroducing packages...
TSet<FString> NotFoundExternalPaths;
for (const FString& ExternalFolderName : ExternalFolderNames)
{
for (const FString& PackageName : NotFoundPackages)
{
int32 Index = PackageName.Find(ExternalFolderName);
if (Index != INDEX_NONE)
{
// Format: /<MountPoint>/__External<xxxxx>__/<Level>/0/AB/CDEFGHIJKLMNOPQRSTUVWX
// Result: /<MountPoint>/__External<xxxxx>__/<Level>
Index += ExternalFolderName.Len();
Index += 1;
Index = PackageName.Find("/", ESearchCase::IgnoreCase, ESearchDir::FromStart, Index);
if (Index != INDEX_NONE)
{
NotFoundExternalPaths.Add(PackageName.Left(Index));
}
}
}
}
// See if any of the loaded levels stores their external actors in any of those paths...
for (TObjectIterator<ULevel> LevelIt; LevelIt; ++LevelIt)
{
ULevel* Level = (*LevelIt);
if (Level->IsUsingExternalActors())
{
const FString& ExternalActorPath = ULevel::GetExternalActorsPath(Level->GetPackage());
if (NotFoundExternalPaths.Contains(ExternalActorPath))
{
UniqueLoadedPackages.Add(Level->GetWorld()->GetPackage());
}
}
if (Level->IsUsingExternalObjects())
{
const TArray<FString> ExternalObjectPaths = ULevel::GetExternalObjectsPaths(Level->GetPackage()->GetName());
for (const FString& ExternalObjectPath : ExternalObjectPaths)
{
if (NotFoundExternalPaths.Contains(ExternalObjectPath))
{
UniqueLoadedPackages.Add(Level->GetWorld()->GetPackage());
}
}
}
}
}
// Prepare the packages to be reverted...
TArray<UPackage*> LoadedPackages = UniqueLoadedPackages.Array();
// Make sure they are unlinked...
UnlinkPackages.Append(LoadedPackages);
if (UnlinkPackages.Num() > 0)
{
TArray<UObject*> LoadedObjects;
TArray<int32> PendingPackageRequestIds;
LoadedObjects.Reserve(UnlinkPackages.Num());
PendingPackageRequestIds.Reserve(UnlinkPackages.Num());
for (UPackage* Package : UnlinkPackages)
{
LoadedObjects.Emplace(Package);
if (!Package->IsFullyLoaded())
{
PendingPackageRequestIds.Emplace(LoadPackageAsync(Package->GetName()));
}
}
FlushAsyncLoading(PendingPackageRequestIds);
ResetLoaders(LoadedObjects);
}
for (int32 PackageIndex = 0; PackageIndex < PackageNames.Num(); PackageIndex++)
{
if (MapPackageNamesToFilenames.Contains(PackageNames[PackageIndex]))
{
PackageFilenames.Add(MapPackageNamesToFilenames[PackageNames[PackageIndex]]);
}
else
{
PackageFilenames.Add(PackageFilename(PackageNames[PackageIndex]));
}
}
// Apply Operation
bSuccess = InOperation(PackageFilenames);
// Reverting may have deleted some packages, so we need to delete those and unload them rather than re-load them...
FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(TEXT("AssetRegistry"));
TArray<TWeakObjectPtr<UObject>> ObjectsMissingOnDisk;
LoadedPackages.RemoveAll([&](UPackage* InPackage) -> bool
{
const FString PackageExtension = InPackage->ContainsMap() ? FPackageName::GetMapPackageExtension() : FPackageName::GetAssetPackageExtension();
const FString PackageFilename = FPackageName::LongPackageNameToFilename(InPackage->GetName(), PackageExtension);
if (!FPaths::FileExists(PackageFilename))
{
TArray<FAssetData> Assets;
AssetRegistryModule.Get().GetAssetsByPackageName(*InPackage->GetName(), Assets);
for (const FAssetData& Asset : Assets)
{
if (UObject* ObjectToDelete = Asset.FastGetAsset())
{
ObjectsMissingOnDisk.Add(ObjectToDelete);
}
}
return true; // remove package
}
return false; // keep package
});
// Hot-reload the new packages...
if (LoadedPackages.Num() > 0)
{
// Split into world and non-world packages and reload them separately.
TArray<UPackage*> LoadedWorldPackages;
TArray<UPackage*> LoadedNonWorldPackages;
LoadedWorldPackages.Reserve(LoadedPackages.Num());
LoadedNonWorldPackages.Reserve(LoadedPackages.Num());
for (UPackage* Package : LoadedPackages)
{
if (UWorld::FindWorldInPackage(Package))
{
LoadedWorldPackages.Add(Package);
}
else
{
LoadedNonWorldPackages.Add(Package);
}
}
// Reload non world package(s).
if (LoadedNonWorldPackages.Num() > 0)
{
FText OutReloadErrorMsg;
UPackageTools::ReloadPackages(LoadedNonWorldPackages, OutReloadErrorMsg, bInteractive ? EReloadPackagesInteractionMode::Interactive : EReloadPackagesInteractionMode::AssumePositive);
if (!OutReloadErrorMsg.IsEmpty())
{
UE_LOG(LogSourceControl, Warning, TEXT("%s"), *OutReloadErrorMsg.ToString());
}
}
// Reload world package(s).
if (LoadedWorldPackages.Num() > 0)
{
FText OutReloadErrorMsg;
UPackageTools::ReloadPackages(LoadedWorldPackages, OutReloadErrorMsg, bInteractive ? EReloadPackagesInteractionMode::Interactive : EReloadPackagesInteractionMode::AssumePositive);
if (!OutReloadErrorMsg.IsEmpty())
{
UE_LOG(LogSourceControl, Warning, TEXT("%s"), *OutReloadErrorMsg.ToString());
}
}
}
// A world reload might have already deleted some objects, so check which missing ones are still valid.
TArray<UObject*> ObjectsToDelete;
if (ObjectsMissingOnDisk.Num() > 0)
{
for (TWeakObjectPtr<UObject> Object : ObjectsMissingOnDisk)
{
if (UObject* ObjectToDelete = Object.Get())
{
ObjectsToDelete.Add(ObjectToDelete);
}
}
}
// Delete and Unload assets...
if (ObjectsToDelete.Num() > 0)
{
if (ObjectTools::DeleteObjectsUnchecked(ObjectsToDelete) != ObjectsToDelete.Num())
{
UE_LOG(LogSourceControl, Warning, TEXT("Failed to unload some assets."));
}
}
// Re-cache the SCC state...
SourceControlProvider.Execute(ISourceControlOperation::Create<FUpdateStatus>(), PackageFilenames, EConcurrency::Asynchronous);
return bSuccess;
}
TArray<FString> USourceControlHelpers::GetSourceControlLocations(const bool bContentOnly)
{
TArray<FString> SourceControlLocations;
TArray<FSourceControlProjectInfo> CustomProjects = ISourceControlModule::Get().GetCustomProjects();
if (CustomProjects.IsEmpty())
{
TArray<FString> RootPaths;
FPackageName::QueryRootContentPaths(RootPaths);
for (const FString& RootPath : RootPaths)
{
const FString RootPathOnDisk = FPackageName::LongPackageNameToFilename(RootPath);
SourceControlLocations.Add(FPaths::ConvertRelativePathToFull(RootPathOnDisk));
}
if (!bContentOnly)
{
SourceControlLocations.Add(FPaths::ConvertRelativePathToFull(FPaths::ProjectConfigDir()));
SourceControlLocations.Add(FPaths::ConvertRelativePathToFull(FPaths::GetProjectFilePath()));
}
}
else
{
for (FSourceControlProjectInfo& ProjectInfo : CustomProjects)
{
for (FString& ContentDir : ProjectInfo.ContentDirectories)
{
SourceControlLocations.Add(FPaths::ConvertRelativePathToFull(MoveTemp(ContentDir)));
}
if (!bContentOnly)
{
SourceControlLocations.Add(FPaths::ConvertRelativePathToFull(MoveTemp(ProjectInfo.ProjectDirectory)));
}
}
}
return SourceControlLocations;
}
bool USourceControlHelpers::ListRevertablePackages(TArray<FString>& OutRevertablePackageNames)
{
if (!ISourceControlModule::Get().IsEnabled() || !ISourceControlModule::Get().GetProvider().IsAvailable())
{
return false;
}
// update status for all packages
TArray<FString> Filenames = GetSourceControlLocations();
ISourceControlProvider& SourceControlProvider = ISourceControlModule::Get().GetProvider();
FSourceControlOperationRef Operation = ISourceControlOperation::Create<FUpdateStatus>();
if (SourceControlProvider.Execute(Operation, Filenames) != ECommandResult::Succeeded)
{
return false;
}
// Get a list of all the revertable packages
TMap<FString, FSourceControlStatePtr> PackageStates;
FEditorFileUtils::FindAllSubmittablePackageFiles(PackageStates, true);
TArray<FString> PackageNames;
PackageStates.GetKeys(PackageNames);
TMap<FString, FString> FileNamesToPackageNames;
// Get a list of files pending delete
TArray<FSourceControlStateRef> PendingDeleteItems = SourceControlProvider.GetCachedStateByPredicate(
[&PackageNames, &FileNamesToPackageNames](const FSourceControlStateRef& State)
{
const FString& Filename = State->GetFilename();
if (FPackageName::IsPackageFilename(Filename))
{
FString PackageName;
FString FailureReason;
if (!FPackageName::TryConvertFilenameToLongPackageName(Filename, PackageName, &FailureReason))
{
UE_LOG(LogSourceControl, Warning, TEXT("%s"), *FailureReason);
return false;
}
if (State->IsDeleted() && !PackageNames.Contains(PackageName))
{
FileNamesToPackageNames.Add(Filename, PackageName);
return true;
}
}
return false;
}
);
// And append them to the list
for (FSourceControlStateRef& Item : PendingDeleteItems)
{
const FString* PackageName = FileNamesToPackageNames.Find(Item->GetFilename());
if (PackageName)
{
PackageStates.Add(*PackageName, Item);
}
}
for (auto& PackageState : PackageStates)
{
const FString PackageName = PackageState.Key;
OutRevertablePackageNames.Add(PackageName);
}
return true;
}
bool USourceControlHelpers::RevertAllChangesAndReloadWorld()
{
TArray<FString> PackagesToReload;
ListRevertablePackages(PackagesToReload);
return RevertAndReloadPackages(PackagesToReload, /*bRevertAll=*/true, /*bReloadWorld=*/true);
}
bool USourceControlHelpers::RevertAndReloadPackages(const TArray<FString>& InPackagesToRevert, bool bRevertAll, bool bReloadWorld)
{
auto RevertOperation = [bRevertAll](const TArray<FString>& InPackagesToRevert) -> bool
{
auto OperationCompleteCallback = FSourceControlOperationComplete::CreateLambda([InPackagesToRevert](const FSourceControlOperationRef& Operation, ECommandResult::Type InResult)
{
if (Operation->GetName() == TEXT("Revert"))
{
TSharedRef<FRevert> RevertOperation = StaticCastSharedRef<FRevert>(Operation);
ISourceControlModule::Get().GetOnFilesDeleted().Broadcast(RevertOperation->GetDeletedFiles());
// The revert may have caused added files to return to an uncontrolled state when not deleting newly added files
// Handle that here, and return any newly uncontrolled files to the default uncontrolled changelist so they're not left in limbo
if (GEditor && !RevertOperation->ShouldDeleteNewFiles())
{
TArray<FSourceControlStateRef> SCStates;
if (ISourceControlModule::Get().GetProvider().GetState(InPackagesToRevert, SCStates, EStateCacheUsage::ForceUpdate) == ECommandResult::Succeeded)
{
TArray<FString> NewlyUncontrolledFiles;
for (const FSourceControlStateRef& SCState : SCStates)
{
if (!SCState->IsSourceControlled())
{
NewlyUncontrolledFiles.Add(SCState->GetFilename());
}
}
if (NewlyUncontrolledFiles.Num() > 0)
{
FScopedDisableSourceControl DisableSourceControl;
GEditor->AddDeferredMarkForAddFiles(NewlyUncontrolledFiles);
GEditor->RunDeferredMarkForAddFiles();
}
}
}
}
});
auto RevertOperation = ISourceControlOperation::Create<FRevert>();
if (bRevertAll)
{
RevertOperation->SetRevertAll(true);
}
return ISourceControlModule::Get().GetProvider().Execute(RevertOperation, InPackagesToRevert, EConcurrency::Synchronous, OperationCompleteCallback) == ECommandResult::Succeeded;
};
return ApplyOperationAndReloadPackages(InPackagesToRevert,RevertOperation, bReloadWorld);
}
#endif //!WITH_EDITOR
bool USourceControlHelpers::RevertFiles(const TArray<FString>& InFiles, bool bSilent)
{
// If we have nothing to process, exit immediately
if (InFiles.IsEmpty())
{
return true;
}
// 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->CanRevert())
{
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)
{
// If we have nothing to process, exit immediately
if (InFiles.IsEmpty())
{
return true;
}
// 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, bool bKeepCheckedOut)
{
// 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));
CheckInOp->SetKeepCheckedOut(bKeepCheckedOut);
ECommandResult::Type Result = Provider->Execute(CheckInOp, SCFile);
return Result == ECommandResult::Succeeded;
}
bool USourceControlHelpers::CheckInFiles(const TArray<FString>& InFiles, const FString& InDescription, bool bSilent, bool bKeepCheckedOut)
{
// If we have nothing to process, exit immediately
if (InFiles.IsEmpty())
{
return true;
}
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));
CheckInOp->SetKeepCheckedOut(bKeepCheckedOut);
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;
}
TArray<FSourceControlState> USourceControlHelpers::QueryFileStates(const TArray<FString> InFiles, bool bSilent)
{
TArray<FSourceControlState> States;
SourceControlHelpersInternal::RunQueryFileStates(InFiles, EConcurrency::Synchronous, bSilent,
[&States](FSourceControlState FileStateOut)
{
States.Emplace(MoveTemp(FileStateOut));
});
return States;
}
FSourceControlState USourceControlHelpers::QueryFileState(const FString& InFile, bool bSilent)
{
FSourceControlState State;
State.Filename = InFile;
SourceControlHelpersInternal::RunQueryFileStates(TArray<FString> {InFile}, EConcurrency::Synchronous, bSilent,
[&State](FSourceControlState FileStateOut)
{
State = MoveTemp(FileStateOut);
});
return State;
}
void USourceControlHelpers::AsyncQueryFileStates(FQueryFileStateDelegate FileStateCallback, TArray<FString> InFiles, bool bSilent)
{
SourceControlHelpersInternal::RunQueryFileStates(InFiles, EConcurrency::Asynchronous, bSilent,
[FileStateCallback](FSourceControlState InState)
{
FileStateCallback.ExecuteIfBound(InState);
});
}
void USourceControlHelpers::AsyncQueryFileState(FQueryFileStateDelegate FileStateCallback, const FString& InFile, bool bSilent)
{
AsyncQueryFileStates(FileStateCallback, TArray<FString> {InFile}, bSilent);
}
bool USourceControlHelpers::GetFilesInDepotAtPath(const FString& Path, TArray<FString>& OutFilesList, bool bIncludeDeleted, bool bSilent, bool bIsFileRegexSearch)
{
TArray<FString> Paths;
Paths.Add(Path);
return GetFilesInDepotAtPaths(Paths, OutFilesList, bIncludeDeleted, bSilent, bIsFileRegexSearch);
}
bool USourceControlHelpers::GetFilesInDepotAtPaths(const TArray<FString>& Paths, TArray<FString>& OutFilesList, bool bIncludeDeleted, bool bSilent, bool bIsFileRegexSearch)
{
TRACE_CPUPROFILER_EVENT_SCOPE(USourceControlHelpers::GetFilesInDepotAtPaths);
bool bSuccess = false;
if (ISourceControlModule::Get().IsEnabled())
{
ISourceControlProvider& SourceControlProvider = ISourceControlModule::Get().GetProvider();
if (SourceControlProvider.IsAvailable())
{
TArray<FString> PathsToQuery;
for (const FString& Path : Paths)
{
FString RelativePath;
FPackageName::TryConvertLongPackageNameToFilename(Path, RelativePath);
FPaths::RemoveDuplicateSlashes(RelativePath);
PathsToQuery.Add(RelativePath);
}
TSharedRef<FGetFileList, ESPMode::ThreadSafe> Operation = ISourceControlOperation::Create<FGetFileList>();
Operation->SetIncludeDeleted(bIncludeDeleted);
Operation->SetQuiet(bSilent);
Operation->SetSearchPattern(PathsToQuery);
if (bIsFileRegexSearch)
{
Operation->SetMethodUsed(FGetFileList::EGetFileListMethod::FileRegexSearch);
}
ECommandResult::Type Result = SourceControlProvider.Execute(Operation, PathsToQuery, EConcurrency::Synchronous);
bSuccess = (Result == ECommandResult::Succeeded);
if (!bSuccess)
{
for (const FString& Path : Paths)
{
FFormatNamedArguments Arguments;
Arguments.Add(TEXT("Path"), FText::FromString(Path));
SourceControlHelpersInternal::LogError(FText::Format(LOCTEXT("CouldNotGetFileList", "Could not get file list at path: {Path}."), Arguments), bSilent);
}
}
else
{
OutFilesList = Operation->GetFilesList();
}
}
}
return bSuccess;
}
static FString PackageFilename_Internal( const FString& InPackageName )
{
FString Filename = InPackageName;
// Get the filename by finding it on disk first
if ( !FPackageName::IsMemoryPackage(InPackageName) && !FPackageName::DoesPackageExist(InPackageName, &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)
{
// Prefer using package loaded path to resolve file name as it properly resolves memory packages
FString PackageLoadedPath = InPackage->GetLoadedPath().GetPackageName();
if (!InPackage->GetLoadedPath().IsEmpty() && FPackageName::IsMemoryPackage(PackageLoadedPath))
{
const FString PackageExtension = InPackage->ContainsMap() ? FPackageName::GetMapPackageExtension() : FPackageName::GetAssetPackageExtension();
Filename = FPaths::ConvertRelativePathToFull(FPackageName::LongPackageNameToFilename(PackageLoadedPath, PackageExtension));
}
else
{
Filename = FPaths::ConvertRelativePathToFull(PackageFilename_Internal(InPackage->GetName()));
}
}
return Filename;
}
TArray<FString> USourceControlHelpers::PackageFilenames( const TArray<UPackage*>& InPackages )
{
TArray<FString> OutNames;
OutNames.Reserve(InPackages.Num());
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;
OutNames.Reserve(InPackageNames.Num());
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;
AbsoluteFiles.Reserve(InFileNames.Num());
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);
}
namespace
{
bool CopyPackage_Internal(UPackage* DestPackage, UPackage* SourcePackage, FCopy::ECopyMethod CopyMethod, EStateCacheUsage::Type StateCacheUsage)
{
if (ISourceControlModule::Get().IsEnabled())
{
ISourceControlProvider& SourceControlProvider = ISourceControlModule::Get().GetProvider();
const FString SourceFilename = USourceControlHelpers::PackageFilename(SourcePackage);
FSourceControlStatePtr SourceControlState = SourceControlProvider.GetState(SourceFilename, StateCacheUsage);
if (SourceControlState.IsValid() && SourceControlState->IsSourceControlled())
{
FString DestinationPath = USourceControlHelpers::PackageFilename(DestPackage);
FSourceControlStatePtr DestinationState = SourceControlProvider.GetState(DestinationPath, StateCacheUsage);
TSharedRef<FCopy, ESPMode::ThreadSafe> CopyOperation = ISourceControlOperation::Create<FCopy>();
CopyOperation->SetDestination(DestinationPath);
CopyOperation->CopyMethod = CopyMethod;
CopyOperation->ResolveMethod = (DestinationState.IsValid() && !DestinationState->CanRevert() && !IFileManager::Get().FileExists(*DestinationPath)) ? FCopy::EResolveMethod::AcceptSource : FCopy::EResolveMethod::AcceptTarget;
return (SourceControlProvider.Execute(CopyOperation, SourceFilename) == ECommandResult::Succeeded);
}
}
return false;
}
}
bool USourceControlHelpers::BranchPackage(UPackage* DestPackage, UPackage* SourcePackage, EStateCacheUsage::Type StateCacheUsage)
{
return CopyPackage_Internal(DestPackage, SourcePackage, FCopy::ECopyMethod::Branch, StateCacheUsage);
}
bool USourceControlHelpers::CopyPackage(UPackage* DestPackage, UPackage* SourcePackage, EStateCacheUsage::Type StateCacheUsage)
{
return CopyPackage_Internal(DestPackage, SourcePackage, FCopy::ECopyMethod::Add, StateCacheUsage);
}
const FString& USourceControlHelpers::GetSettingsIni()
{
if (ISourceControlModule::Get().GetUseGlobalSettings())
{
return GetGlobalSettingsIni();
}
else
{
static FString SourceControlSettingsIni;
if (SourceControlSettingsIni.Len() == 0)
{
FConfigContext Context = FConfigContext::ReadIntoGConfig();
Context.Load(TEXT("SourceControlSettings"), SourceControlSettingsIni);
}
return SourceControlSettingsIni;
}
}
const FString& USourceControlHelpers::GetGlobalSettingsIni()
{
static FString SourceControlGlobalSettingsIni;
if (SourceControlGlobalSettingsIni.Len() == 0)
{
FConfigContext Context = FConfigContext::ReadIntoGConfig();
Context.GeneratedConfigDir = FPaths::EngineSavedDir() + TEXT("Config/");
Context.ProjectConfigDir = (""); // don't load anything from project configs
Context.Load(TEXT("SourceControlSettings"), SourceControlGlobalSettingsIni);
}
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, true);
if (OutAssets.Num() > 0)
{
// Assets are already in the cache, we can query dependencies directly
if (bGetDependencies)
{
AssetRegistryModule.Get().GetDependencies(*InPackageName, *OutDependencies, UE::AssetRegistry::EDependencyCategory::Package, UE::AssetRegistry::EDependencyQuery::Hard);
}
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()
{
bInitSourceControl = !ISourceControlModule::Get().GetProvider().IsAvailable();
if (bInitSourceControl)
{
ISourceControlModule::Get().GetProvider().Init();
}
}
FScopedSourceControl::~FScopedSourceControl()
{
if (bInitSourceControl)
{
ISourceControlModule::Get().GetProvider().Close();
}
}
ISourceControlProvider& FScopedSourceControl::GetProvider()
{
return ISourceControlModule::Get().GetProvider();
}
#undef LOCTEXT_NAMESPACE