Files
UnrealEngineUWP/Engine/Plugins/Developer/PlasticSourceControl/Source/PlasticSourceControl/Private/PlasticSourceControlProvider.cpp
SRombauts 65550e8a34 Update PlasticSCM Plugin from 1.5.0 to 1.5.1
#jira UE-153257
#9195 (Contributed by SRombauts)
#rb Patrick.Laflamme
#preflight 62977d16a660a44a23be54ae

[CL 20453148 by SRombauts in ue5-main branch]
2022-06-01 11:26:54 -04:00

509 lines
17 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "PlasticSourceControlProvider.h"
#include "PlasticSourceControlCommand.h"
#include "PlasticSourceControlModule.h"
#include "PlasticSourceControlOperations.h"
#include "PlasticSourceControlSettings.h"
#include "PlasticSourceControlState.h"
#include "PlasticSourceControlUtils.h"
#include "SPlasticSourceControlSettings.h"
#include "ISourceControlModule.h"
#include "Logging/MessageLog.h"
#include "ScopedSourceControlProgress.h"
#include "SourceControlHelpers.h"
#include "SourceControlOperations.h"
#include "Interfaces/IPluginManager.h"
#include "Misc/Paths.h"
#include "HAL/PlatformProcess.h"
#include "Misc/QueuedThreadPool.h"
#define LOCTEXT_NAMESPACE "PlasticSourceControl"
static FName ProviderName("Plastic SCM");
void FPlasticSourceControlProvider::Init(bool bForceConnection)
{
// Init() is called multiple times at startup: do not check Plastic SCM each time
if (!bPlasticAvailable)
{
const TSharedPtr<IPlugin> Plugin = IPluginManager::Get().FindPlugin(TEXT("PlasticSourceControl"));
if (Plugin.IsValid())
{
PluginVersion = Plugin->GetDescriptor().VersionName;
UE_LOG(LogSourceControl, Log, TEXT("Plastic SCM plugin '%s'"), *PluginVersion);
}
CheckPlasticAvailability();
// Override the source control logs verbosity level if needed based on settings
FPlasticSourceControlModule& PlasticSourceControl = FModuleManager::LoadModuleChecked<FPlasticSourceControlModule>("PlasticSourceControl");
if (PlasticSourceControl.AccessSettings().GetEnableVerboseLogs())
{
PlasticSourceControlUtils::SwitchVerboseLogs(true);
}
}
if (bForceConnection && !bServerAvailable)
{
// Execute a 'checkconnection' command to set bServerAvailable based on the connectivity of the server
TArray<FString> InfoMessages, ErrorMessages;
const bool bCommandSuccessful = PlasticSourceControlUtils::RunCommand(TEXT("checkconnection"), TArray<FString>(), TArray<FString>(), EConcurrency::Synchronous, InfoMessages, ErrorMessages);
bServerAvailable = bCommandSuccessful;
if (!bCommandSuccessful)
{
FMessageLog SourceControlLog("SourceControl");
for (const FString& ErrorMessage : ErrorMessages)
{
SourceControlLog.Error(FText::FromString(ErrorMessage));
}
}
}
}
void FPlasticSourceControlProvider::CheckPlasticAvailability()
{
FPlasticSourceControlModule& PlasticSourceControl = FModuleManager::LoadModuleChecked<FPlasticSourceControlModule>("PlasticSourceControl");
FString PathToPlasticBinary = PlasticSourceControl.AccessSettings().GetBinaryPath();
if (PathToPlasticBinary.IsEmpty())
{
bPlasticAvailable = false;
// Try to find Plastic binary, and update settings accordingly
PathToPlasticBinary = PlasticSourceControlUtils::FindPlasticBinaryPath();
if (!PathToPlasticBinary.IsEmpty())
{
PlasticSourceControl.AccessSettings().SetBinaryPath(PathToPlasticBinary);
}
}
if (!PathToPlasticBinary.IsEmpty())
{
// Find the path to the root Plastic directory (if any, else uses the ProjectDir)
const FString PathToProjectDir = FPaths::ConvertRelativePathToFull(FPaths::ProjectDir());
bWorkspaceFound = PlasticSourceControlUtils::FindRootDirectory(PathToProjectDir, PathToWorkspaceRoot);
// Launch the Plastic SCM cli shell on the background to issue all commands during this session
bPlasticAvailable = PlasticSourceControlUtils::LaunchBackgroundPlasticShell(PathToPlasticBinary, PathToWorkspaceRoot);
if (bPlasticAvailable)
{
PlasticSourceControlUtils::GetPlasticScmVersion(PlasticScmVersion);
// Get user name (from the global Plastic SCM client config)
PlasticSourceControlUtils::GetUserName(UserName);
// Register Console Commands
PlasticSourceControlConsole.Register();
if (!bWorkspaceFound)
{
UE_LOG(LogSourceControl, Warning, TEXT("'%s' is not part of a Plastic workspace"), *FPaths::ProjectDir());
}
}
}
}
void FPlasticSourceControlProvider::Close()
{
// clear the cache
StateCache.Empty();
// terminate the background 'cm shell' process and associated pipes
PlasticSourceControlUtils::Terminate();
// Remove all extensions to the "Source Control" menu in the Editor Toolbar
PlasticSourceControlMenu.Unregister();
// Unregister Console Commands
PlasticSourceControlConsole.Unregister();
bServerAvailable = false;
bPlasticAvailable = false;
bWorkspaceFound = false;
UserName.Empty();
}
TSharedRef<FPlasticSourceControlState, ESPMode::ThreadSafe> FPlasticSourceControlProvider::GetStateInternal(const FString& InFilename) const
{
TSharedRef<FPlasticSourceControlState, ESPMode::ThreadSafe>* State = StateCache.Find(InFilename);
if (State != NULL)
{
// found cached item
return (*State);
}
else
{
// cache an unknown state for this item
TSharedRef<FPlasticSourceControlState, ESPMode::ThreadSafe> NewState = MakeShareable(new FPlasticSourceControlState(FString(InFilename)));
StateCache.Add(InFilename, NewState);
return NewState;
}
}
FText FPlasticSourceControlProvider::GetStatusText() const
{
FFormatNamedArguments Args;
Args.Add( TEXT("PlasticScmVersion"), FText::FromString(PlasticScmVersion) );
Args.Add( TEXT("PluginVersion"), FText::FromString(PluginVersion) );
Args.Add( TEXT("WorkspacePath"), FText::FromString(PathToWorkspaceRoot) );
Args.Add( TEXT("WorkspaceName"), FText::FromString(WorkspaceName) );
Args.Add( TEXT("BranchName"), FText::FromString(BranchName) );
// Detect special case for a partial checkout (CS:-1 in Gluon mode)!
if (-1 != ChangesetNumber)
{
Args.Add( TEXT("ChangesetNumber"), FText::FromString(FString::Printf(TEXT("%d (standard mode)"), ChangesetNumber)) );
}
else
{
Args.Add( TEXT("ChangesetNumber"), FText::FromString(FString::Printf(TEXT("N/A (Gluon/partial mode)"))) );
}
Args.Add( TEXT("UserName"), FText::FromString(UserName) );
return FText::Format( NSLOCTEXT("Status", "Provider: Plastic\nEnabledLabel", "Plastic SCM {PlasticScmVersion} (plugin v{PluginVersion})\nWorkspace: {WorkspaceName} ({WorkspacePath})\n{BranchName}\nChangeset: {ChangesetNumber}\nUser: {UserName}"), Args );
}
/** Quick check if source control is enabled. Specifically, it returns true if a source control provider is set (regardless of whether the provider is available) and false if no provider is set. So all providers except the stub DefaultSourceProvider will return true. */
bool FPlasticSourceControlProvider::IsEnabled() const
{
return true;
}
/** Quick check if source control is available for use (return whether the server is available or not) */
bool FPlasticSourceControlProvider::IsAvailable() const
{
return bServerAvailable;
}
const FName& FPlasticSourceControlProvider::GetName(void) const
{
return ProviderName;
}
ECommandResult::Type FPlasticSourceControlProvider::GetState( const TArray<FString>& InFiles, TArray< TSharedRef<ISourceControlState, ESPMode::ThreadSafe> >& OutState, EStateCacheUsage::Type InStateCacheUsage )
{
if (!IsEnabled())
{
return ECommandResult::Failed;
}
const TArray<FString> AbsoluteFiles = SourceControlHelpers::AbsoluteFilenames(InFiles);
if (InStateCacheUsage == EStateCacheUsage::ForceUpdate)
{
UE_LOG(LogSourceControl, Log, TEXT("GetState: ForceUpdate"));
Execute(ISourceControlOperation::Create<FUpdateStatus>(), AbsoluteFiles);
}
for (const FString& AbsoluteFile : AbsoluteFiles)
{
OutState.Add(GetStateInternal(AbsoluteFile));
}
return ECommandResult::Succeeded;
}
ECommandResult::Type FPlasticSourceControlProvider::GetState(const TArray<FSourceControlChangelistRef>& InChangelists, TArray<FSourceControlChangelistStateRef>& OutState, EStateCacheUsage::Type InStateCacheUsage)
{
return ECommandResult::Failed;
}
TArray<FSourceControlStateRef> FPlasticSourceControlProvider::GetCachedStateByPredicate(TFunctionRef<bool(const FSourceControlStateRef&)> Predicate) const
{
TArray<FSourceControlStateRef> Result;
for (const auto& CacheItem : StateCache)
{
FSourceControlStateRef State = CacheItem.Value;
if (Predicate(State))
{
Result.Add(State);
}
}
return Result;
}
bool FPlasticSourceControlProvider::RemoveFileFromCache(const FString& Filename)
{
return StateCache.Remove(Filename) > 0;
}
FDelegateHandle FPlasticSourceControlProvider::RegisterSourceControlStateChanged_Handle( const FSourceControlStateChanged::FDelegate& SourceControlStateChanged )
{
return OnSourceControlStateChanged.Add( SourceControlStateChanged );
}
void FPlasticSourceControlProvider::UnregisterSourceControlStateChanged_Handle( FDelegateHandle Handle )
{
OnSourceControlStateChanged.Remove( Handle );
}
ECommandResult::Type FPlasticSourceControlProvider::Execute( const FSourceControlOperationRef& InOperation, FSourceControlChangelistPtr InChangelist, const TArray<FString>& InFiles, EConcurrency::Type InConcurrency, const FSourceControlOperationComplete& InOperationCompleteDelegate )
{
if (!bWorkspaceFound && !(InOperation->GetName() == "Connect") && !(InOperation->GetName() == "MakeWorkspace"))
{
UE_LOG(LogSourceControl, Warning, TEXT("'%s': only Connect operation allowed whithout a workspace"), *InOperation->GetName().ToString());
return ECommandResult::Failed;
}
// Query to see if we allow this operation
TSharedPtr<IPlasticSourceControlWorker, ESPMode::ThreadSafe> Worker = CreateWorker(InOperation->GetName());
if (!Worker.IsValid())
{
// this operation is unsupported by this source control provider
FFormatNamedArguments Arguments;
Arguments.Add( TEXT("OperationName"), FText::FromName(InOperation->GetName()) );
Arguments.Add( TEXT("ProviderName"), FText::FromName(GetName()) );
FMessageLog("SourceControl").Error(FText::Format(LOCTEXT("UnsupportedOperation", "Operation '{OperationName}' not supported by source control provider '{ProviderName}'"), Arguments));
return ECommandResult::Failed;
}
FPlasticSourceControlCommand* Command = new FPlasticSourceControlCommand(InOperation, Worker.ToSharedRef());
Command->Files = SourceControlHelpers::AbsoluteFilenames(InFiles);
Command->OperationCompleteDelegate = InOperationCompleteDelegate;
// fire off operation
if (InConcurrency == EConcurrency::Synchronous)
{
Command->bAutoDelete = false;
UE_LOG(LogSourceControl, Log, TEXT("ExecuteSynchronousCommand: %s"), *InOperation->GetName().ToString());
return ExecuteSynchronousCommand(*Command, InOperation->GetInProgressString());
}
else
{
Command->bAutoDelete = true;
UE_LOG(LogSourceControl, Log, TEXT("IssueAsynchronousCommand: %s"), *InOperation->GetName().ToString());
return IssueCommand(*Command);
}
}
bool FPlasticSourceControlProvider::CanCancelOperation( const FSourceControlOperationRef& InOperation ) const
{
return false;
}
void FPlasticSourceControlProvider::CancelOperation( const FSourceControlOperationRef& InOperation )
{
}
bool FPlasticSourceControlProvider::UsesLocalReadOnlyState() const
{
return false; // TODO: use configuration
}
bool FPlasticSourceControlProvider::UsesChangelists() const
{
return false; // We don't want to show ChangeList column anymore (Plastic SCM term would be ChangeSet)
}
bool FPlasticSourceControlProvider::UsesCheckout() const
{
return true;
}
TSharedPtr<IPlasticSourceControlWorker, ESPMode::ThreadSafe> FPlasticSourceControlProvider::CreateWorker(const FName& InOperationName) const
{
const FGetPlasticSourceControlWorker* Operation = WorkersMap.Find(InOperationName);
if (Operation != nullptr)
{
return Operation->Execute();
}
return nullptr;
}
void FPlasticSourceControlProvider::RegisterWorker( const FName& InName, const FGetPlasticSourceControlWorker& InDelegate )
{
WorkersMap.Add( InName, InDelegate );
}
void FPlasticSourceControlProvider::OutputCommandMessages(const FPlasticSourceControlCommand& InCommand) const
{
FMessageLog SourceControlLog("SourceControl");
for (const FString& ErrorMessage : InCommand.ErrorMessages)
{
SourceControlLog.Error(FText::FromString(ErrorMessage));
}
for (const FString& InfoMessage : InCommand.InfoMessages)
{
SourceControlLog.Info(FText::FromString(InfoMessage));
}
}
void FPlasticSourceControlProvider::UpdateWorkspaceStatus(const class FPlasticSourceControlCommand& InCommand)
{
if (InCommand.Operation->GetName() == "Connect")
{
// Is connection successful?
bServerAvailable = InCommand.bCommandSuccessful;
bWorkspaceFound = !InCommand.WorkspaceName.IsEmpty();
WorkspaceName = InCommand.WorkspaceName;
RepositoryName = InCommand.RepositoryName;
ServerUrl = InCommand.ServerUrl;
if (bWorkspaceFound)
{
// Extend the "Source Control" menu in the Editor Toolbar on each successful connection
PlasticSourceControlMenu.Unregister(); // cleanup for any previous connection
PlasticSourceControlMenu.Register();
}
}
else if (InCommand.bConnectionDropped)
{
// checkconnection failed on UpdateStatus
bServerAvailable = false;
}
// And for all operations running UpdateStatus, get Changeset and Branch informations:
if (InCommand.ChangesetNumber != 0)
{
ChangesetNumber = InCommand.ChangesetNumber;
}
if (!InCommand.BranchName.IsEmpty())
{
BranchName = InCommand.BranchName;
}
}
void FPlasticSourceControlProvider::Tick()
{
bool bStatesUpdated = false;
for (int32 CommandIndex = 0; CommandIndex < CommandQueue.Num(); ++CommandIndex)
{
FPlasticSourceControlCommand& Command = *CommandQueue[CommandIndex];
if (Command.bExecuteProcessed)
{
// Remove command from the queue
CommandQueue.RemoveAt(CommandIndex);
// Update workspace status and connection state on Connect and UpdateStatus operations
UpdateWorkspaceStatus(Command);
// let command update the states of any files
bStatesUpdated |= Command.Worker->UpdateStates();
// dump any messages to output log
OutputCommandMessages(Command);
if (Command.Files.Num())
{
UE_LOG(LogSourceControl, Log, TEXT("%s of %d files processed in %.3lfs"), *Command.Operation->GetName().ToString(), Command.Files.Num(), (FPlatformTime::Seconds() - Command.StartTimestamp));
}
else
{
UE_LOG(LogSourceControl, Log, TEXT("%s processed in %.3lfs"), *Command.Operation->GetName().ToString(), (FPlatformTime::Seconds() - Command.StartTimestamp));
}
// run the completion delegate callback if we have one bound
ECommandResult::Type Result = Command.bCommandSuccessful ? ECommandResult::Succeeded : ECommandResult::Failed;
Command.OperationCompleteDelegate.ExecuteIfBound(Command.Operation, Result);
// commands that are left in the array during a tick need to be deleted
if (Command.bAutoDelete)
{
// Only delete commands that are not running 'synchronously'
delete &Command;
}
// only do one command per tick loop, as we dont want concurrent modification
// of the command queue (which can happen in the completion delegate)
break;
}
}
if (bStatesUpdated)
{
OnSourceControlStateChanged.Broadcast();
}
}
TArray< TSharedRef<ISourceControlLabel> > FPlasticSourceControlProvider::GetLabels( const FString& InMatchingSpec ) const
{
TArray< TSharedRef<ISourceControlLabel> > Tags;
return Tags;
}
TArray<FSourceControlChangelistRef> FPlasticSourceControlProvider::GetChangelists( EStateCacheUsage::Type InStateCacheUsage )
{
return TArray<FSourceControlChangelistRef>();
}
#if SOURCE_CONTROL_WITH_SLATE
TSharedRef<class SWidget> FPlasticSourceControlProvider::MakeSettingsWidget() const
{
return SNew(SPlasticSourceControlSettings);
}
#endif
ECommandResult::Type FPlasticSourceControlProvider::ExecuteSynchronousCommand(FPlasticSourceControlCommand& InCommand, const FText& Task)
{
ECommandResult::Type Result = ECommandResult::Failed;
// Display the progress dialog if a string was provided
{
FScopedSourceControlProgress Progress(Task);
// Issue the command asynchronously...
IssueCommand( InCommand );
// ... then wait for its completion (thus making it synchronous)
while (!InCommand.bExecuteProcessed)
{
// Tick the command queue and update progress.
Tick();
Progress.Tick();
// Sleep for a bit so we don't busy-wait so much.
FPlatformProcess::Sleep(0.01f);
}
// always do one more Tick() to make sure the command queue is cleaned up.
Tick();
if (InCommand.bCommandSuccessful)
{
Result = ECommandResult::Succeeded;
}
else
{
// TODO If the command failed, inform the user that they need to try again (see Perforce)
//FMessageDialog::Open( EAppMsgType::Ok, LOCTEXT("Plastic_ServerUnresponsive", "Plastic server is unresponsive. Please check your connection and try again.") );
UE_LOG(LogSourceControl, Error, TEXT("Command '%s' Failed!"), *InCommand.Operation->GetName().ToString());
}
}
// Delete the command now (asynchronous commands are deleted in the Tick() method)
check(!InCommand.bAutoDelete);
// ensure commands that are not auto deleted do not end up in the command queue
if ( CommandQueue.Contains( &InCommand ) )
{
CommandQueue.Remove( &InCommand );
}
delete &InCommand;
return Result;
}
ECommandResult::Type FPlasticSourceControlProvider::IssueCommand(FPlasticSourceControlCommand& InCommand)
{
if (GThreadPool != nullptr)
{
// Queue this to our worker thread(s) for resolving
GThreadPool->AddQueuedWork(&InCommand);
CommandQueue.Add(&InCommand);
return ECommandResult::Succeeded;
}
else
{
return ECommandResult::Failed;
}
}
#undef LOCTEXT_NAMESPACE