added UEGitPlugin
This commit is contained in:
@@ -0,0 +1,113 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "ISourceControlModule.h"
|
||||
#include "Internationalization/Text.h"
|
||||
#include "Logging/MessageLog.h"
|
||||
#include "Logging/TokenizedMessage.h"
|
||||
#include "Templates/SharedPointer.h"
|
||||
|
||||
/**
|
||||
* A thread safe replacement for FMessageLog which can be called from background threads.
|
||||
* It only exposes methods from FMessageLog that we would be able to safely delay, such
|
||||
* as messages. We do not provide any functionality to open error dialogs etc.
|
||||
* At the moment if we detect a message is being queued when not on the game thread we log
|
||||
* it instead of sending to the FMessageLog system. In the future we will store the messages
|
||||
* and marshal them to the GameThread so that they can be displayed as originally intended.
|
||||
*/
|
||||
class FTSMessageLog
|
||||
{
|
||||
public:
|
||||
FTSMessageLog() = delete;
|
||||
FTSMessageLog(const FName& InLogName)
|
||||
: Log(InLogName)
|
||||
{}
|
||||
|
||||
FTSMessageLog(FTSMessageLog&&) = default;
|
||||
FTSMessageLog& operator = (FTSMessageLog&&) = default;
|
||||
|
||||
FTSMessageLog(const FTSMessageLog&) = delete;
|
||||
FTSMessageLog& operator = (const FTSMessageLog&) = delete;
|
||||
|
||||
~FTSMessageLog() = default;
|
||||
|
||||
TSharedRef<FTokenizedMessage> Message(EMessageSeverity::Type InSeverity, const FText& InMessage = FText())
|
||||
{
|
||||
if (IsInGameThread())
|
||||
{
|
||||
return Log.Message(InSeverity, InMessage);
|
||||
}
|
||||
else
|
||||
{
|
||||
TSharedRef<FTokenizedMessage> Message = FTokenizedMessage::Create(InSeverity, InMessage);
|
||||
UE_LOG(LogSourceControl, Display, TEXT("%s"), *Message->ToText().ToString());
|
||||
|
||||
return Message;
|
||||
}
|
||||
}
|
||||
|
||||
TSharedRef<FTokenizedMessage> Error(const FText& InMessage = FText())
|
||||
{
|
||||
if (IsInGameThread())
|
||||
{
|
||||
return Log.Error(InMessage);
|
||||
}
|
||||
else
|
||||
{
|
||||
TSharedRef<FTokenizedMessage> Message = FTokenizedMessage::Create(EMessageSeverity::Error, InMessage);
|
||||
UE_LOG(LogSourceControl, Error, TEXT("%s"), *Message->ToText().ToString());
|
||||
|
||||
return Message;
|
||||
}
|
||||
}
|
||||
|
||||
TSharedRef<FTokenizedMessage> PerformanceWarning(const FText& InMessage = FText())
|
||||
{
|
||||
if (IsInGameThread())
|
||||
{
|
||||
return Log.PerformanceWarning(InMessage);
|
||||
}
|
||||
else
|
||||
{
|
||||
TSharedRef<FTokenizedMessage> Message = FTokenizedMessage::Create(EMessageSeverity::PerformanceWarning, InMessage);
|
||||
UE_LOG(LogSourceControl, Warning, TEXT("%s"), *Message->ToText().ToString());
|
||||
|
||||
return Message;
|
||||
}
|
||||
}
|
||||
|
||||
TSharedRef<FTokenizedMessage> Warning(const FText& InMessage = FText())
|
||||
{
|
||||
if (IsInGameThread())
|
||||
{
|
||||
return Log.Warning(InMessage);
|
||||
}
|
||||
else
|
||||
{
|
||||
TSharedRef<FTokenizedMessage> Message = FTokenizedMessage::Create(EMessageSeverity::Warning, InMessage);
|
||||
UE_LOG(LogSourceControl, Warning, TEXT("%s"), *Message->ToText().ToString());
|
||||
|
||||
return Message;
|
||||
}
|
||||
}
|
||||
|
||||
TSharedRef<FTokenizedMessage> Info(const FText& InMessage = FText())
|
||||
{
|
||||
if (IsInGameThread())
|
||||
{
|
||||
return Log.Info(InMessage);
|
||||
}
|
||||
else
|
||||
{
|
||||
TSharedRef<FTokenizedMessage> Message = FTokenizedMessage::Create(EMessageSeverity::Info, InMessage);
|
||||
UE_LOG(LogSourceControl, Display, TEXT("%s"), *Message->ToText().ToString());
|
||||
|
||||
return Message;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
FMessageLog Log;
|
||||
};
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
#include "GitSourceControlChangelist.h"
|
||||
|
||||
#if ENGINE_MAJOR_VERSION == 5
|
||||
FGitSourceControlChangelist FGitSourceControlChangelist::WorkingChangelist(TEXT("Working"), true);
|
||||
FGitSourceControlChangelist FGitSourceControlChangelist::StagedChangelist(TEXT("Staged"), true);
|
||||
#endif
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
#include "GitSourceControlChangelistState.h"
|
||||
|
||||
#if ENGINE_MAJOR_VERSION == 5
|
||||
#define LOCTEXT_NAMESPACE "GitSourceControl.ChangelistState"
|
||||
|
||||
FName FGitSourceControlChangelistState::GetIconName() const
|
||||
{
|
||||
// Mimic P4V colors, returning the red icon if there are active file(s), the blue if the changelist is empty or all the files are shelved.
|
||||
return FName("SourceControl.Changelist");
|
||||
}
|
||||
|
||||
FName FGitSourceControlChangelistState::GetSmallIconName() const
|
||||
{
|
||||
return GetIconName();
|
||||
}
|
||||
|
||||
FText FGitSourceControlChangelistState::GetDisplayText() const
|
||||
{
|
||||
return FText::FromString(Changelist.GetName());
|
||||
}
|
||||
|
||||
FText FGitSourceControlChangelistState::GetDescriptionText() const
|
||||
{
|
||||
return FText::FromString(Description);
|
||||
}
|
||||
|
||||
FText FGitSourceControlChangelistState::GetDisplayTooltip() const
|
||||
{
|
||||
return LOCTEXT("Tooltip", "Tooltip");
|
||||
}
|
||||
|
||||
const FDateTime& FGitSourceControlChangelistState::GetTimeStamp() const
|
||||
{
|
||||
return TimeStamp;
|
||||
}
|
||||
|
||||
#if ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION >= 4
|
||||
const TArray<FSourceControlStateRef> FGitSourceControlChangelistState::GetFilesStates() const
|
||||
#else
|
||||
const TArray<FSourceControlStateRef>& FGitSourceControlChangelistState::GetFilesStates() const
|
||||
#endif
|
||||
{
|
||||
return Files;
|
||||
}
|
||||
|
||||
#if ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION >= 4
|
||||
int32 FGitSourceControlChangelistState::GetFilesStatesNum() const
|
||||
{
|
||||
return Files.Num();
|
||||
}
|
||||
#endif
|
||||
|
||||
#if ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION >= 4
|
||||
const TArray<FSourceControlStateRef> FGitSourceControlChangelistState::GetShelvedFilesStates() const
|
||||
#else
|
||||
const TArray<FSourceControlStateRef>& FGitSourceControlChangelistState::GetShelvedFilesStates() const
|
||||
#endif
|
||||
{
|
||||
return ShelvedFiles;
|
||||
}
|
||||
|
||||
#if ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION >= 4
|
||||
int32 FGitSourceControlChangelistState::GetShelvedFilesStatesNum() const
|
||||
{
|
||||
return ShelvedFiles.Num();
|
||||
}
|
||||
#endif
|
||||
|
||||
FSourceControlChangelistRef FGitSourceControlChangelistState::GetChangelist() const
|
||||
{
|
||||
FGitSourceControlChangelistRef ChangelistCopy = MakeShareable( new FGitSourceControlChangelist(Changelist));
|
||||
return StaticCastSharedRef<ISourceControlChangelist>(ChangelistCopy);
|
||||
}
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
#endif
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
#pragma once
|
||||
#include "Runtime/Launch/Resources/Version.h"
|
||||
#if ENGINE_MAJOR_VERSION == 5
|
||||
#include "GitSourceControlChangelist.h"
|
||||
#include "ISourceControlChangelistState.h"
|
||||
#include "ISourceControlState.h"
|
||||
|
||||
class FGitSourceControlChangelistState : public ISourceControlChangelistState
|
||||
{
|
||||
public:
|
||||
explicit FGitSourceControlChangelistState(const FGitSourceControlChangelist& InChangelist,
|
||||
const FString& InDescription = FString())
|
||||
: Changelist(InChangelist)
|
||||
, Description(InDescription)
|
||||
{
|
||||
}
|
||||
|
||||
explicit FGitSourceControlChangelistState(FGitSourceControlChangelist&& InChangelist,
|
||||
FString&& InDescription)
|
||||
: Changelist(MoveTemp(InChangelist))
|
||||
, Description(MoveTemp(InDescription))
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of the icon graphic we should use to display the state in a UI.
|
||||
* @returns the name of the icon to display
|
||||
*/
|
||||
virtual FName GetIconName() const override;
|
||||
|
||||
/**
|
||||
* Get the name of the small icon graphic we should use to display the state in a UI.
|
||||
* @returns the name of the icon to display
|
||||
*/
|
||||
virtual FName GetSmallIconName() const override;
|
||||
|
||||
/**
|
||||
* Get a text representation of the state
|
||||
* @returns the text to display for this state
|
||||
*/
|
||||
virtual FText GetDisplayText() const override;
|
||||
|
||||
/**
|
||||
* Get a text representation of the state
|
||||
* @returns the text to display for this state
|
||||
*/
|
||||
virtual FText GetDescriptionText() const override;
|
||||
|
||||
/**
|
||||
* Get a tooltip to describe this state
|
||||
* @returns the text to display for this states tooltip
|
||||
*/
|
||||
virtual FText GetDisplayTooltip() const override;
|
||||
|
||||
/**
|
||||
* Get the timestamp of the last update that was made to this state.
|
||||
* @returns the timestamp of the last update
|
||||
*/
|
||||
virtual const FDateTime& GetTimeStamp() const override;
|
||||
|
||||
#if ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION >= 4
|
||||
virtual const TArray<FSourceControlStateRef> GetFilesStates() const override;
|
||||
virtual int32 GetFilesStatesNum() const override;
|
||||
|
||||
virtual const TArray<FSourceControlStateRef> GetShelvedFilesStates() const override;
|
||||
virtual int32 GetShelvedFilesStatesNum() const override;
|
||||
#else
|
||||
virtual const TArray<FSourceControlStateRef>& GetFilesStates() const override;
|
||||
|
||||
virtual const TArray<FSourceControlStateRef>& GetShelvedFilesStates() const override;
|
||||
#endif
|
||||
|
||||
virtual FSourceControlChangelistRef GetChangelist() const override;
|
||||
|
||||
public:
|
||||
FGitSourceControlChangelist Changelist;
|
||||
|
||||
FString Description;
|
||||
|
||||
TArray<FSourceControlStateRef> Files;
|
||||
|
||||
TArray<FSourceControlStateRef> ShelvedFiles;
|
||||
|
||||
/** The timestamp of the last update */
|
||||
FDateTime TimeStamp;
|
||||
};
|
||||
#endif
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
// Copyright (c) 2014-2023 Sebastien Rombauts (sebastien.rombauts@gmail.com)
|
||||
//
|
||||
// Distributed under the MIT License (MIT) (See accompanying file LICENSE.txt
|
||||
// or copy at http://opensource.org/licenses/MIT)
|
||||
|
||||
#include "GitSourceControlCommand.h"
|
||||
|
||||
#include "Modules/ModuleManager.h"
|
||||
#include "GitSourceControlModule.h"
|
||||
#include "GitSourceControlUtils.h"
|
||||
|
||||
FGitSourceControlCommand::FGitSourceControlCommand(const TSharedRef<class ISourceControlOperation, ESPMode::ThreadSafe>& InOperation, const TSharedRef<class IGitSourceControlWorker, ESPMode::ThreadSafe>& InWorker, const FSourceControlOperationComplete& InOperationCompleteDelegate)
|
||||
: Operation(InOperation)
|
||||
, Worker(InWorker)
|
||||
, OperationCompleteDelegate(InOperationCompleteDelegate)
|
||||
, bExecuteProcessed(0)
|
||||
, bCancelled(0)
|
||||
, bCommandSuccessful(false)
|
||||
, bAutoDelete(true)
|
||||
, Concurrency(EConcurrency::Synchronous)
|
||||
{
|
||||
// cache the providers settings here
|
||||
const FGitSourceControlModule& GitSourceControl = FGitSourceControlModule::Get();
|
||||
const FGitSourceControlProvider& Provider = GitSourceControl.GetProvider();
|
||||
PathToGitBinary = Provider.GetGitBinaryPath();
|
||||
bUsingGitLfsLocking = Provider.UsesCheckout();
|
||||
PathToRepositoryRoot = Provider.GetPathToRepositoryRoot();
|
||||
PathToGitRoot = Provider.GetPathToGitRoot();
|
||||
}
|
||||
|
||||
void FGitSourceControlCommand::UpdateRepositoryRootIfSubmodule(TArray<FString>& AbsoluteFilePaths)
|
||||
{
|
||||
PathToRepositoryRoot = GitSourceControlUtils::ChangeRepositoryRootIfSubmodule(AbsoluteFilePaths, PathToRepositoryRoot);
|
||||
}
|
||||
|
||||
bool FGitSourceControlCommand::DoWork()
|
||||
{
|
||||
bCommandSuccessful = Worker->Execute(*this);
|
||||
FPlatformAtomics::InterlockedExchange(&bExecuteProcessed, 1);
|
||||
|
||||
return bCommandSuccessful;
|
||||
}
|
||||
|
||||
void FGitSourceControlCommand::Abandon()
|
||||
{
|
||||
FPlatformAtomics::InterlockedExchange(&bExecuteProcessed, 1);
|
||||
}
|
||||
|
||||
void FGitSourceControlCommand::DoThreadedWork()
|
||||
{
|
||||
Concurrency = EConcurrency::Asynchronous;
|
||||
DoWork();
|
||||
}
|
||||
|
||||
void FGitSourceControlCommand::Cancel()
|
||||
{
|
||||
FPlatformAtomics::InterlockedExchange(&bCancelled, 1);
|
||||
}
|
||||
|
||||
bool FGitSourceControlCommand::IsCanceled() const
|
||||
{
|
||||
return bCancelled != 0;
|
||||
}
|
||||
|
||||
ECommandResult::Type FGitSourceControlCommand::ReturnResults()
|
||||
{
|
||||
// Save any messages that have accumulated
|
||||
for (const auto& String : ResultInfo.InfoMessages)
|
||||
{
|
||||
Operation->AddInfoMessge(FText::FromString(String));
|
||||
}
|
||||
for (const auto& String : ResultInfo.ErrorMessages)
|
||||
{
|
||||
Operation->AddErrorMessge(FText::FromString(String));
|
||||
}
|
||||
|
||||
// run the completion delegate if we have one bound
|
||||
ECommandResult::Type Result = bCancelled ? ECommandResult::Cancelled : (bCommandSuccessful ? ECommandResult::Succeeded : ECommandResult::Failed);
|
||||
OperationCompleteDelegate.ExecuteIfBound(Operation, Result);
|
||||
|
||||
return Result;
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
// Copyright (c) 2014-2022 Sebastien Rombauts (sebastien.rombauts@gmail.com)
|
||||
|
||||
#include "GitSourceControlConsole.h"
|
||||
|
||||
#include "HAL/IConsoleManager.h"
|
||||
#include "ISourceControlModule.h"
|
||||
|
||||
#include "GitSourceControlModule.h"
|
||||
#include "GitSourceControlUtils.h"
|
||||
|
||||
// Auto-registered console commands:
|
||||
// No re-register on hot reload, and unregistered only once on editor shutdown.
|
||||
static FAutoConsoleCommand g_executeGitConsoleCommand(TEXT("git"),
|
||||
TEXT("Git Command Line Interface.\n")
|
||||
TEXT("Run any 'git' command directly from the Unreal Editor Console.\n")
|
||||
TEXT("Type 'git help' to get a command list."),
|
||||
FConsoleCommandWithArgsDelegate::CreateStatic(&GitSourceControlConsole::ExecuteGitConsoleCommand));
|
||||
|
||||
void GitSourceControlConsole::ExecuteGitConsoleCommand(const TArray<FString>& a_args)
|
||||
{
|
||||
FGitSourceControlModule& GitSourceControl = FModuleManager::LoadModuleChecked<FGitSourceControlModule>("GitSourceControl");
|
||||
const FString& PathToGitBinary = GitSourceControl.AccessSettings().GetBinaryPath();
|
||||
const FString& RepositoryRoot = GitSourceControl.GetProvider().GetPathToRepositoryRoot();
|
||||
|
||||
// The first argument is the command to send to git, the following ones are forwarded as parameters for the command
|
||||
TArray<FString> Parameters = a_args;
|
||||
FString Command;
|
||||
if (a_args.Num() > 0)
|
||||
{
|
||||
Command = a_args[0];
|
||||
Parameters.RemoveAt(0);
|
||||
}
|
||||
else
|
||||
{
|
||||
// If no command is provided, use "help" to emulate the behavior of the git CLI
|
||||
Command = TEXT("help");
|
||||
}
|
||||
|
||||
FString Results;
|
||||
FString Errors;
|
||||
GitSourceControlUtils::RunCommandInternalRaw(Command, PathToGitBinary, RepositoryRoot, Parameters, TArray<FString>(), Results, Errors);
|
||||
|
||||
UE_LOG(LogSourceControl, Log, TEXT("Output:\n%s"), *Results);
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
// Copyright (c) 2014-2022 Sebastien Rombauts (sebastien.rombauts@gmail.com)
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
|
||||
/**
|
||||
* Editor only console commands.
|
||||
*
|
||||
* Such commands can be executed from the editor output log window, but also from command line arguments,
|
||||
* from Editor Blueprints utilities, or from C++ Code using. eg. GEngine->Exec("git status Content/");
|
||||
*/
|
||||
class GitSourceControlConsole
|
||||
{
|
||||
public:
|
||||
// Git Command Line Interface: Run 'git' commands directly from the Unreal Editor Console.
|
||||
static void ExecuteGitConsoleCommand(const TArray<FString>& a_args);
|
||||
};
|
||||
+643
@@ -0,0 +1,643 @@
|
||||
// Copyright (c) 2014-2020 Sebastien Rombauts (sebastien.rombauts@gmail.com)
|
||||
//
|
||||
// Distributed under the MIT License (MIT) (See accompanying file LICENSE.txt
|
||||
// or copy at http://opensource.org/licenses/MIT)
|
||||
|
||||
#include "GitSourceControlMenu.h"
|
||||
|
||||
#include "GitSourceControlModule.h"
|
||||
#include "GitSourceControlProvider.h"
|
||||
#include "GitSourceControlOperations.h"
|
||||
#include "GitSourceControlUtils.h"
|
||||
|
||||
#include "ISourceControlModule.h"
|
||||
#include "ISourceControlOperation.h"
|
||||
#include "SourceControlOperations.h"
|
||||
|
||||
#include "LevelEditor.h"
|
||||
#include "Widgets/Notifications/SNotificationList.h"
|
||||
#include "Framework/Notifications/NotificationManager.h"
|
||||
#include "Framework/MultiBox/MultiBoxBuilder.h"
|
||||
#include "Misc/MessageDialog.h"
|
||||
#if ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION >= 1
|
||||
#include "Styling/AppStyle.h"
|
||||
#else
|
||||
#include "EditorStyleSet.h"
|
||||
#endif
|
||||
|
||||
#include "PackageTools.h"
|
||||
#include "FileHelpers.h"
|
||||
|
||||
#include "Logging/MessageLog.h"
|
||||
#include "SourceControlHelpers.h"
|
||||
#include "SourceControlWindows.h"
|
||||
|
||||
#if ENGINE_MAJOR_VERSION == 5
|
||||
#include "ToolMenus.h"
|
||||
#include "ToolMenuContext.h"
|
||||
#include "ToolMenuMisc.h"
|
||||
#endif
|
||||
|
||||
#if ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION >= 6
|
||||
#include "EditorModeManager.h"
|
||||
#endif
|
||||
#include "UObject/Linker.h"
|
||||
|
||||
static const FName GitSourceControlMenuTabName(TEXT("GitSourceControlMenu"));
|
||||
static const FName LevelEditorName(TEXT("LevelEditor"));
|
||||
|
||||
#define LOCTEXT_NAMESPACE "GitSourceControl"
|
||||
|
||||
TWeakPtr<SNotificationItem> FGitSourceControlMenu::OperationInProgressNotification;
|
||||
|
||||
void FGitSourceControlMenu::Register()
|
||||
{
|
||||
#if ENGINE_MAJOR_VERSION >= 5
|
||||
FToolMenuOwnerScoped SourceControlMenuOwner( GitSourceControlMenuTabName );
|
||||
if (UToolMenus* ToolMenus = UToolMenus::Get())
|
||||
{
|
||||
UToolMenu* SourceControlMenu = ToolMenus->ExtendMenu("StatusBar.ToolBar.SourceControl");
|
||||
FToolMenuSection& Section = SourceControlMenu->AddSection("GitSourceControlActions", LOCTEXT("GitSourceControlMenuHeadingActions", "Git"), FToolMenuInsert(NAME_None, EToolMenuInsertType::First));
|
||||
|
||||
AddMenuExtension(Section);
|
||||
}
|
||||
#else
|
||||
// Register the extension with the level editor
|
||||
FLevelEditorModule * LevelEditorModule = FModuleManager::GetModulePtr< FLevelEditorModule >( LevelEditorName );
|
||||
if (LevelEditorModule)
|
||||
{
|
||||
FLevelEditorModule::FLevelEditorMenuExtender ViewMenuExtender = FLevelEditorModule::FLevelEditorMenuExtender::CreateRaw(this, &FGitSourceControlMenu::OnExtendLevelEditorViewMenu);
|
||||
auto& MenuExtenders = LevelEditorModule->GetAllLevelEditorToolbarSourceControlMenuExtenders();
|
||||
MenuExtenders.Add(ViewMenuExtender);
|
||||
ViewMenuExtenderHandle = MenuExtenders.Last().GetHandle();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void FGitSourceControlMenu::Unregister()
|
||||
{
|
||||
#if ENGINE_MAJOR_VERSION >= 5
|
||||
if (UToolMenus* ToolMenus = UToolMenus::Get())
|
||||
{
|
||||
UToolMenus::Get()->UnregisterOwnerByName("GitSourceControlMenu");
|
||||
}
|
||||
#else
|
||||
// Unregister the level editor extensions
|
||||
FLevelEditorModule * LevelEditorModule = FModuleManager::GetModulePtr< FLevelEditorModule >( LevelEditorName );
|
||||
if (LevelEditorModule)
|
||||
{
|
||||
LevelEditorModule->GetAllLevelEditorToolbarSourceControlMenuExtenders().RemoveAll([=](const FLevelEditorModule::FLevelEditorMenuExtender& Extender) { return Extender.GetHandle() == ViewMenuExtenderHandle; });
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
bool FGitSourceControlMenu::HaveRemoteUrl() const
|
||||
{
|
||||
const FGitSourceControlModule& GitSourceControl = FGitSourceControlModule::Get();
|
||||
return !GitSourceControl.GetProvider().GetRemoteUrl().IsEmpty();
|
||||
}
|
||||
|
||||
bool FGitSourceControlMenu::CanCommit() const
|
||||
{
|
||||
// The 'Submit Content' operation could lead to a world reload (in UEFN) that takes the user out of their selected editor mode.
|
||||
// Piggy back on the 'CanAutoSave' functionality to determine if now is a good time to trigger a 'Submit Content' SCC operation.
|
||||
#if ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION >= 6
|
||||
return GLevelEditorModeTools().CanAutoSave() && FSourceControlWindows::CanChoosePackagesToCheckIn();
|
||||
#else
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
/// Prompt to save or discard all packages
|
||||
bool FGitSourceControlMenu::SaveDirtyPackages()
|
||||
{
|
||||
const bool bPromptUserToSave = true;
|
||||
const bool bSaveMapPackages = true;
|
||||
const bool bSaveContentPackages = true;
|
||||
const bool bFastSave = false;
|
||||
const bool bNotifyNoPackagesSaved = false;
|
||||
const bool bCanBeDeclined = true; // If the user clicks "don't save" this will continue and lose their changes
|
||||
bool bHadPackagesToSave = false;
|
||||
|
||||
bool bSaved = FEditorFileUtils::SaveDirtyPackages(bPromptUserToSave, bSaveMapPackages, bSaveContentPackages, bFastSave, bNotifyNoPackagesSaved, bCanBeDeclined, &bHadPackagesToSave);
|
||||
|
||||
// bSaved can be true if the user selects to not save an asset by unchecking it and clicking "save"
|
||||
if (bSaved)
|
||||
{
|
||||
TArray<UPackage*> DirtyPackages;
|
||||
FEditorFileUtils::GetDirtyWorldPackages(DirtyPackages);
|
||||
FEditorFileUtils::GetDirtyContentPackages(DirtyPackages);
|
||||
bSaved = DirtyPackages.Num() == 0;
|
||||
}
|
||||
|
||||
return bSaved;
|
||||
}
|
||||
|
||||
// Ask the user if they want to stash any modification and try to unstash them afterward, which could lead to conflicts
|
||||
bool FGitSourceControlMenu::StashAwayAnyModifications()
|
||||
{
|
||||
bool bStashOk = true;
|
||||
|
||||
FGitSourceControlModule& GitSourceControl = FGitSourceControlModule::Get();
|
||||
const FGitSourceControlProvider& Provider = GitSourceControl.GetProvider();
|
||||
const FString& PathToRespositoryRoot = Provider.GetPathToRepositoryRoot();
|
||||
const FString& PathToGitBinary = Provider.GetGitBinaryPath();
|
||||
const TArray<FString> ParametersStatus{"--porcelain --untracked-files=no"};
|
||||
TArray<FString> InfoMessages;
|
||||
TArray<FString> ErrorMessages;
|
||||
// Check if there is any modification to the working tree
|
||||
const bool bStatusOk = GitSourceControlUtils::RunCommand(TEXT("status"), PathToGitBinary, PathToRespositoryRoot, ParametersStatus, FGitSourceControlModule::GetEmptyStringArray(), InfoMessages, ErrorMessages);
|
||||
if ((bStatusOk) && (InfoMessages.Num() > 0))
|
||||
{
|
||||
// Ask the user before stashing
|
||||
const FText DialogText(LOCTEXT("SourceControlMenu_Stash_Ask", "Stash (save) all modifications of the working tree? Required to Sync/Pull!"));
|
||||
const EAppReturnType::Type Choice = FMessageDialog::Open(EAppMsgType::OkCancel, DialogText);
|
||||
if (Choice == EAppReturnType::Ok)
|
||||
{
|
||||
const TArray<FString> ParametersStash{ "save \"Stashed by Unreal Engine Git Plugin\"" };
|
||||
bStashMadeBeforeSync = GitSourceControlUtils::RunCommand(TEXT("stash"), PathToGitBinary, PathToRespositoryRoot, ParametersStash, FGitSourceControlModule::GetEmptyStringArray(), InfoMessages, ErrorMessages);
|
||||
if (!bStashMadeBeforeSync)
|
||||
{
|
||||
FMessageLog SourceControlLog("SourceControl");
|
||||
SourceControlLog.Warning(LOCTEXT("SourceControlMenu_StashFailed", "Stashing away modifications failed!"));
|
||||
SourceControlLog.Notify();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
bStashOk = false;
|
||||
}
|
||||
}
|
||||
|
||||
return bStashOk;
|
||||
}
|
||||
|
||||
// Unstash any modifications if a stash was made at the beginning of the Sync operation
|
||||
void FGitSourceControlMenu::ReApplyStashedModifications()
|
||||
{
|
||||
if (bStashMadeBeforeSync)
|
||||
{
|
||||
FGitSourceControlModule& GitSourceControl = FGitSourceControlModule::Get();
|
||||
FGitSourceControlProvider& Provider = GitSourceControl.GetProvider();
|
||||
const FString& PathToRespositoryRoot = Provider.GetPathToRepositoryRoot();
|
||||
const FString& PathToGitBinary = Provider.GetGitBinaryPath();
|
||||
const TArray<FString> ParametersStash{ "pop" };
|
||||
TArray<FString> InfoMessages;
|
||||
TArray<FString> ErrorMessages;
|
||||
const bool bUnstashOk = GitSourceControlUtils::RunCommand(TEXT("stash"), PathToGitBinary, PathToRespositoryRoot, ParametersStash, FGitSourceControlModule::GetEmptyStringArray(), InfoMessages, ErrorMessages);
|
||||
if (!bUnstashOk)
|
||||
{
|
||||
FMessageLog SourceControlLog("SourceControl");
|
||||
SourceControlLog.Warning(LOCTEXT("SourceControlMenu_UnstashFailed", "Unstashing previously saved modifications failed!"));
|
||||
SourceControlLog.Notify();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FGitSourceControlMenu::SyncClicked()
|
||||
{
|
||||
if (!OperationInProgressNotification.IsValid())
|
||||
{
|
||||
// Ask the user to save any dirty assets opened in Editor
|
||||
const bool bSaved = SaveDirtyPackages();
|
||||
if (bSaved)
|
||||
{
|
||||
FGitSourceControlModule& GitSourceControl = FGitSourceControlModule::Get();
|
||||
FGitSourceControlProvider& Provider = GitSourceControl.GetProvider();
|
||||
|
||||
// Launch a "Sync" operation
|
||||
TSharedRef<FSync, ESPMode::ThreadSafe> SyncOperation = ISourceControlOperation::Create<FSync>();
|
||||
#if ENGINE_MAJOR_VERSION >= 5
|
||||
const ECommandResult::Type Result = Provider.Execute(SyncOperation, FSourceControlChangelistPtr(), FGitSourceControlModule::GetEmptyStringArray(), EConcurrency::Asynchronous,
|
||||
FSourceControlOperationComplete::CreateRaw(this, &FGitSourceControlMenu::OnSourceControlOperationComplete));
|
||||
#else
|
||||
const ECommandResult::Type Result = Provider.Execute(SyncOperation, FGitSourceControlModule::GetEmptyStringArray(), EConcurrency::Asynchronous,
|
||||
FSourceControlOperationComplete::CreateRaw(this, &FGitSourceControlMenu::OnSourceControlOperationComplete));
|
||||
#endif
|
||||
if (Result == ECommandResult::Succeeded)
|
||||
{
|
||||
// Display an ongoing notification during the whole operation (packages will be reloaded at the completion of the operation)
|
||||
DisplayInProgressNotification(SyncOperation->GetInProgressString());
|
||||
}
|
||||
else
|
||||
{
|
||||
// Report failure with a notification and Reload all packages
|
||||
DisplayFailureNotification(SyncOperation->GetName());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
FMessageLog SourceControlLog("SourceControl");
|
||||
SourceControlLog.Warning(LOCTEXT("SourceControlMenu_Sync_Unsaved", "Save All Assets before attempting to Sync!"));
|
||||
SourceControlLog.Notify();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
FMessageLog SourceControlLog("SourceControl");
|
||||
SourceControlLog.Warning(LOCTEXT("SourceControlMenu_InProgress", "Revision control operation already in progress"));
|
||||
SourceControlLog.Notify();
|
||||
}
|
||||
}
|
||||
|
||||
void FGitSourceControlMenu::CommitClicked()
|
||||
{
|
||||
if (OperationInProgressNotification.IsValid())
|
||||
{
|
||||
FMessageLog SourceControlLog("SourceControl");
|
||||
SourceControlLog.Warning(LOCTEXT("SourceControlMenu_InProgress", "Revision control operation already in progress"));
|
||||
SourceControlLog.Notify();
|
||||
return;
|
||||
}
|
||||
|
||||
FLevelEditorModule & LevelEditorModule = FModuleManager::Get().LoadModuleChecked<FLevelEditorModule>("LevelEditor");
|
||||
FSourceControlWindows::ChoosePackagesToCheckIn(nullptr);
|
||||
}
|
||||
|
||||
void FGitSourceControlMenu::PushClicked()
|
||||
{
|
||||
if (!OperationInProgressNotification.IsValid())
|
||||
{
|
||||
// Launch a "Push" Operation
|
||||
FGitSourceControlModule& GitSourceControl = FGitSourceControlModule::Get();
|
||||
FGitSourceControlProvider& Provider = GitSourceControl.GetProvider();
|
||||
TSharedRef<FCheckIn, ESPMode::ThreadSafe> PushOperation = ISourceControlOperation::Create<FCheckIn>();
|
||||
#if ENGINE_MAJOR_VERSION >= 5
|
||||
const ECommandResult::Type Result = Provider.Execute(PushOperation, FSourceControlChangelistPtr(), FGitSourceControlModule::GetEmptyStringArray(), EConcurrency::Asynchronous, FSourceControlOperationComplete::CreateRaw(this, &FGitSourceControlMenu::OnSourceControlOperationComplete));
|
||||
#else
|
||||
const ECommandResult::Type Result = Provider.Execute(PushOperation, FGitSourceControlModule::GetEmptyStringArray(), EConcurrency::Asynchronous, FSourceControlOperationComplete::CreateRaw(this, &FGitSourceControlMenu::OnSourceControlOperationComplete));
|
||||
#endif
|
||||
if (Result == ECommandResult::Succeeded)
|
||||
{
|
||||
// Display an ongoing notification during the whole operation
|
||||
DisplayInProgressNotification(PushOperation->GetInProgressString());
|
||||
}
|
||||
else
|
||||
{
|
||||
// Report failure with a notification
|
||||
DisplayFailureNotification(PushOperation->GetName());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
FMessageLog SourceControlLog("SourceControl");
|
||||
SourceControlLog.Warning(LOCTEXT("SourceControlMenu_InProgress", "Revision control operation already in progress"));
|
||||
SourceControlLog.Notify();
|
||||
}
|
||||
}
|
||||
|
||||
void FGitSourceControlMenu::RevertClicked()
|
||||
{
|
||||
if (OperationInProgressNotification.IsValid())
|
||||
{
|
||||
FMessageLog SourceControlLog("SourceControl");
|
||||
SourceControlLog.Warning(LOCTEXT("SourceControlMenu_InProgress", "Revision control operation already in progress"));
|
||||
SourceControlLog.Notify();
|
||||
return;
|
||||
}
|
||||
|
||||
// Ask the user before reverting all!
|
||||
const FText DialogText(LOCTEXT("SourceControlMenu_Revert_Ask", "Revert all modifications of the working tree?"));
|
||||
const EAppReturnType::Type Choice = FMessageDialog::Open(EAppMsgType::OkCancel, DialogText);
|
||||
if (Choice != EAppReturnType::Ok)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// make sure we update the SCC status of all packages (this could take a long time, so we will run it as a background task)
|
||||
const TArray<FString> Filenames = GitSourceControlUtils::GetSourceControlledAssetPaths();
|
||||
|
||||
ISourceControlProvider& SourceControlProvider = ISourceControlModule::Get().GetProvider();
|
||||
FSourceControlOperationRef Operation = ISourceControlOperation::Create<FUpdateStatus>();
|
||||
#if ENGINE_MAJOR_VERSION >= 5
|
||||
SourceControlProvider.Execute(Operation, FSourceControlChangelistPtr(), Filenames, EConcurrency::Asynchronous, FSourceControlOperationComplete::CreateStatic(&FGitSourceControlMenu::RevertAllCallback));
|
||||
#else
|
||||
SourceControlProvider.Execute(Operation, Filenames, EConcurrency::Asynchronous, FSourceControlOperationComplete::CreateStatic(&FGitSourceControlMenu::RevertAllCallback));
|
||||
#endif
|
||||
|
||||
FNotificationInfo Info(LOCTEXT("SourceControlMenuRevertAll", "Checking for assets to revert..."));
|
||||
Info.bFireAndForget = false;
|
||||
Info.ExpireDuration = 0.0f;
|
||||
Info.FadeOutDuration = 1.0f;
|
||||
|
||||
if (SourceControlProvider.CanCancelOperation(Operation))
|
||||
{
|
||||
Info.ButtonDetails.Add(FNotificationButtonInfo(
|
||||
LOCTEXT("SourceControlMenuRevertAll_CancelButton", "Cancel"),
|
||||
LOCTEXT("SourceControlMenuRevertAll_CancelButtonTooltip", "Cancel the revert operation."),
|
||||
FSimpleDelegate::CreateStatic(&FGitSourceControlMenu::RevertAllCancelled, Operation)
|
||||
));
|
||||
}
|
||||
|
||||
OperationInProgressNotification = FSlateNotificationManager::Get().AddNotification(Info);
|
||||
if (OperationInProgressNotification.IsValid())
|
||||
{
|
||||
OperationInProgressNotification.Pin()->SetCompletionState(SNotificationItem::CS_Pending);
|
||||
}
|
||||
}
|
||||
|
||||
void FGitSourceControlMenu::RevertAllCallback(const FSourceControlOperationRef& InOperation, ECommandResult::Type InResult)
|
||||
{
|
||||
if (InResult != ECommandResult::Succeeded)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Get a list of all the checked out packages
|
||||
TArray<FString> PackageNames;
|
||||
TArray<UPackage*> LoadedPackages;
|
||||
TMap<FString, FSourceControlStatePtr> PackageStates;
|
||||
FEditorFileUtils::FindAllSubmittablePackageFiles(PackageStates, true);
|
||||
|
||||
for (TMap<FString, FSourceControlStatePtr>::TConstIterator PackageIter(PackageStates); PackageIter; ++PackageIter)
|
||||
{
|
||||
const FString PackageName = *PackageIter.Key();
|
||||
const FSourceControlStatePtr CurPackageSCCState = PackageIter.Value();
|
||||
|
||||
UPackage* Package = FindPackage(nullptr, *PackageName);
|
||||
if (Package != nullptr)
|
||||
{
|
||||
LoadedPackages.Add(Package);
|
||||
|
||||
if (!Package->IsFullyLoaded())
|
||||
{
|
||||
FlushAsyncLoading();
|
||||
Package->FullyLoad();
|
||||
}
|
||||
ResetLoaders(Package);
|
||||
}
|
||||
|
||||
PackageNames.Add(PackageName);
|
||||
}
|
||||
|
||||
const auto FileNames = SourceControlHelpers::PackageFilenames(PackageNames);
|
||||
|
||||
// Launch a "Revert" Operation
|
||||
FGitSourceControlModule& GitSourceControl = FGitSourceControlModule::Get();
|
||||
FGitSourceControlProvider& Provider = GitSourceControl.GetProvider();
|
||||
const TSharedRef<FRevert, ESPMode::ThreadSafe> RevertOperation = ISourceControlOperation::Create<FRevert>();
|
||||
#if ENGINE_MAJOR_VERSION >= 5
|
||||
const auto Result = Provider.Execute(RevertOperation, FSourceControlChangelistPtr(), FileNames);
|
||||
#else
|
||||
const auto Result = Provider.Execute(RevertOperation, FileNames);
|
||||
#endif
|
||||
|
||||
RemoveInProgressNotification();
|
||||
if (Result != ECommandResult::Succeeded)
|
||||
{
|
||||
DisplayFailureNotification(TEXT("Revert"));
|
||||
}
|
||||
else
|
||||
{
|
||||
DisplaySucessNotification(TEXT("Revert"));
|
||||
}
|
||||
|
||||
GitSourceControlUtils::ReloadPackages(LoadedPackages);
|
||||
#if ENGINE_MAJOR_VERSION >= 5
|
||||
Provider.Execute(ISourceControlOperation::Create<FUpdateStatus>(), FSourceControlChangelistPtr(), FGitSourceControlModule::GetEmptyStringArray(), EConcurrency::Asynchronous);
|
||||
#else
|
||||
Provider.Execute(ISourceControlOperation::Create<FUpdateStatus>(), FGitSourceControlModule::GetEmptyStringArray(), EConcurrency::Asynchronous);
|
||||
#endif
|
||||
}
|
||||
|
||||
void FGitSourceControlMenu::RefreshClicked()
|
||||
{
|
||||
if (!OperationInProgressNotification.IsValid())
|
||||
{
|
||||
FGitSourceControlModule& GitSourceControl = FGitSourceControlModule::Get();
|
||||
FGitSourceControlProvider& Provider = GitSourceControl.GetProvider();
|
||||
// Launch an "GitFetch" Operation
|
||||
TSharedRef<FGitFetch, ESPMode::ThreadSafe> RefreshOperation = ISourceControlOperation::Create<FGitFetch>();
|
||||
RefreshOperation->bUpdateStatus = true;
|
||||
#if ENGINE_MAJOR_VERSION >= 5
|
||||
const ECommandResult::Type Result = Provider.Execute(RefreshOperation, FSourceControlChangelistPtr(), FGitSourceControlModule::GetEmptyStringArray(), EConcurrency::Asynchronous,
|
||||
FSourceControlOperationComplete::CreateRaw(this, &FGitSourceControlMenu::OnSourceControlOperationComplete));
|
||||
#else
|
||||
const ECommandResult::Type Result = Provider.Execute(RefreshOperation, FGitSourceControlModule::GetEmptyStringArray(), EConcurrency::Asynchronous,
|
||||
FSourceControlOperationComplete::CreateRaw(this, &FGitSourceControlMenu::OnSourceControlOperationComplete));
|
||||
#endif
|
||||
if (Result == ECommandResult::Succeeded)
|
||||
{
|
||||
// Display an ongoing notification during the whole operation
|
||||
DisplayInProgressNotification(RefreshOperation->GetInProgressString());
|
||||
}
|
||||
else
|
||||
{
|
||||
// Report failure with a notification
|
||||
DisplayFailureNotification(RefreshOperation->GetName());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
FMessageLog SourceControlLog("SourceControl");
|
||||
SourceControlLog.Warning(LOCTEXT("SourceControlMenu_InProgress", "Revision control operation already in progress"));
|
||||
SourceControlLog.Notify();
|
||||
}
|
||||
}
|
||||
|
||||
// Display an ongoing notification during the whole operation
|
||||
void FGitSourceControlMenu::DisplayInProgressNotification(const FText& InOperationInProgressString)
|
||||
{
|
||||
if (!OperationInProgressNotification.IsValid())
|
||||
{
|
||||
FNotificationInfo Info(InOperationInProgressString);
|
||||
Info.bFireAndForget = false;
|
||||
Info.ExpireDuration = 0.0f;
|
||||
Info.FadeOutDuration = 1.0f;
|
||||
OperationInProgressNotification = FSlateNotificationManager::Get().AddNotification(Info);
|
||||
if (OperationInProgressNotification.IsValid())
|
||||
{
|
||||
OperationInProgressNotification.Pin()->SetCompletionState(SNotificationItem::CS_Pending);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FGitSourceControlMenu::RevertAllCancelled(FSourceControlOperationRef InOperation)
|
||||
{
|
||||
ISourceControlProvider& SourceControlProvider = ISourceControlModule::Get().GetProvider();
|
||||
SourceControlProvider.CancelOperation(InOperation);
|
||||
|
||||
if (OperationInProgressNotification.IsValid())
|
||||
{
|
||||
OperationInProgressNotification.Pin()->ExpireAndFadeout();
|
||||
}
|
||||
|
||||
OperationInProgressNotification.Reset();
|
||||
}
|
||||
|
||||
// Remove the ongoing notification at the end of the operation
|
||||
void FGitSourceControlMenu::RemoveInProgressNotification()
|
||||
{
|
||||
if (OperationInProgressNotification.IsValid())
|
||||
{
|
||||
OperationInProgressNotification.Pin()->ExpireAndFadeout();
|
||||
OperationInProgressNotification.Reset();
|
||||
}
|
||||
}
|
||||
|
||||
// Display a temporary success notification at the end of the operation
|
||||
void FGitSourceControlMenu::DisplaySucessNotification(const FName& InOperationName)
|
||||
{
|
||||
const FText NotificationText = FText::Format(
|
||||
LOCTEXT("SourceControlMenu_Success", "{0} operation was successful!"),
|
||||
FText::FromName(InOperationName)
|
||||
);
|
||||
FNotificationInfo Info(NotificationText);
|
||||
Info.bUseSuccessFailIcons = true;
|
||||
#if ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION >= 1
|
||||
Info.Image = FAppStyle::GetBrush(TEXT("NotificationList.SuccessImage"));
|
||||
#else
|
||||
Info.Image = FEditorStyle::GetBrush(TEXT("NotificationList.SuccessImage"));
|
||||
#endif
|
||||
|
||||
FSlateNotificationManager::Get().AddNotification(Info);
|
||||
#if UE_BUILD_DEBUG
|
||||
UE_LOG(LogSourceControl, Log, TEXT("%s"), *NotificationText.ToString());
|
||||
#endif
|
||||
}
|
||||
|
||||
// Display a temporary failure notification at the end of the operation
|
||||
void FGitSourceControlMenu::DisplayFailureNotification(const FName& InOperationName)
|
||||
{
|
||||
const FText NotificationText = FText::Format(
|
||||
LOCTEXT("SourceControlMenu_Failure", "Error: {0} operation failed!"),
|
||||
FText::FromName(InOperationName)
|
||||
);
|
||||
FNotificationInfo Info(NotificationText);
|
||||
Info.ExpireDuration = 8.0f;
|
||||
FSlateNotificationManager::Get().AddNotification(Info);
|
||||
UE_LOG(LogSourceControl, Error, TEXT("%s"), *NotificationText.ToString());
|
||||
}
|
||||
|
||||
void FGitSourceControlMenu::OnSourceControlOperationComplete(const FSourceControlOperationRef& InOperation, ECommandResult::Type InResult)
|
||||
{
|
||||
RemoveInProgressNotification();
|
||||
|
||||
if ((InOperation->GetName() == "Sync") || (InOperation->GetName() == "Revert"))
|
||||
{
|
||||
// Unstash any modifications if a stash was made at the beginning of the Sync operation
|
||||
ReApplyStashedModifications();
|
||||
// Reload packages that where unlinked at the beginning of the Sync/Revert operation
|
||||
GitSourceControlUtils::ReloadPackages(PackagesToReload);
|
||||
}
|
||||
|
||||
// Report result with a notification
|
||||
if (InResult == ECommandResult::Succeeded)
|
||||
{
|
||||
DisplaySucessNotification(InOperation->GetName());
|
||||
}
|
||||
else
|
||||
{
|
||||
DisplayFailureNotification(InOperation->GetName());
|
||||
}
|
||||
}
|
||||
|
||||
#if ENGINE_MAJOR_VERSION >= 5
|
||||
void FGitSourceControlMenu::AddMenuExtension(FToolMenuSection& Builder)
|
||||
#else
|
||||
void FGitSourceControlMenu::AddMenuExtension(FMenuBuilder& Builder)
|
||||
#endif
|
||||
{
|
||||
// UE 5.6 doesn't show the Submit Content button if changelists are enabled
|
||||
#if ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION >= 6
|
||||
const FGitSourceControlProvider& Provider = FGitSourceControlModule::Get().GetProvider();
|
||||
if (Provider.UsesChangelists())
|
||||
{
|
||||
Builder.AddMenuEntry(
|
||||
"CommitAndPush",
|
||||
LOCTEXT("GitCommit", "Submit Content"),
|
||||
LOCTEXT("GitPushTooltip", "Opens a dialog with check in options for content and levels."),
|
||||
FSlateIcon(FAppStyle::GetAppStyleSetName(), "SourceControl.Actions.Submit"),
|
||||
FUIAction(
|
||||
FExecuteAction::CreateRaw(this, &FGitSourceControlMenu::CommitClicked),
|
||||
FCanExecuteAction::CreateRaw(this, &FGitSourceControlMenu::CanCommit)
|
||||
)
|
||||
);
|
||||
}
|
||||
#endif
|
||||
|
||||
Builder.AddMenuEntry(
|
||||
#if ENGINE_MAJOR_VERSION >= 5
|
||||
"GitPush",
|
||||
#endif
|
||||
LOCTEXT("GitPush", "Push pending local commits"),
|
||||
LOCTEXT("GitPushTooltip", "Push all pending local commits to the remote server."),
|
||||
#if ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION >= 1
|
||||
FSlateIcon(FAppStyle::GetAppStyleSetName(), "SourceControl.Actions.Submit"),
|
||||
#else
|
||||
FSlateIcon(FEditorStyle::GetStyleSetName(), "SourceControl.Actions.Submit"),
|
||||
#endif
|
||||
FUIAction(
|
||||
FExecuteAction::CreateRaw(this, &FGitSourceControlMenu::PushClicked),
|
||||
FCanExecuteAction::CreateRaw(this, &FGitSourceControlMenu::HaveRemoteUrl)
|
||||
)
|
||||
);
|
||||
|
||||
Builder.AddMenuEntry(
|
||||
#if ENGINE_MAJOR_VERSION >= 5
|
||||
"GitSync",
|
||||
#endif
|
||||
LOCTEXT("GitSync", "Pull"),
|
||||
LOCTEXT("GitSyncTooltip", "Update all files in the local repository to the latest version of the remote server."),
|
||||
#if ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION >= 1
|
||||
FSlateIcon(FAppStyle::GetAppStyleSetName(), "SourceControl.Actions.Sync"),
|
||||
#else
|
||||
FSlateIcon(FEditorStyle::GetStyleSetName(), "SourceControl.Actions.Sync"),
|
||||
#endif
|
||||
FUIAction(
|
||||
FExecuteAction::CreateRaw(this, &FGitSourceControlMenu::SyncClicked),
|
||||
FCanExecuteAction::CreateRaw(this, &FGitSourceControlMenu::HaveRemoteUrl)
|
||||
)
|
||||
);
|
||||
|
||||
Builder.AddMenuEntry(
|
||||
#if ENGINE_MAJOR_VERSION >= 5
|
||||
"GitRevert",
|
||||
#endif
|
||||
LOCTEXT("GitRevert", "Revert"),
|
||||
LOCTEXT("GitRevertTooltip", "Revert all files in the repository to their unchanged state."),
|
||||
#if ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION >= 1
|
||||
FSlateIcon(FAppStyle::GetAppStyleSetName(), "SourceControl.Actions.Revert"),
|
||||
#else
|
||||
FSlateIcon(FEditorStyle::GetStyleSetName(), "SourceControl.Actions.Revert"),
|
||||
#endif
|
||||
FUIAction(
|
||||
FExecuteAction::CreateRaw(this, &FGitSourceControlMenu::RevertClicked),
|
||||
FCanExecuteAction()
|
||||
)
|
||||
);
|
||||
|
||||
Builder.AddMenuEntry(
|
||||
#if ENGINE_MAJOR_VERSION >= 5
|
||||
"GitRefresh",
|
||||
#endif
|
||||
LOCTEXT("GitRefresh", "Refresh"),
|
||||
LOCTEXT("GitRefreshTooltip", "Update the revision control status of all files in the local repository."),
|
||||
#if ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION >= 1
|
||||
FSlateIcon(FAppStyle::GetAppStyleSetName(), "SourceControl.Actions.Refresh"),
|
||||
#else
|
||||
FSlateIcon(FEditorStyle::GetStyleSetName(), "SourceControl.Actions.Refresh"),
|
||||
#endif
|
||||
FUIAction(
|
||||
FExecuteAction::CreateRaw(this, &FGitSourceControlMenu::RefreshClicked),
|
||||
FCanExecuteAction()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
#if ENGINE_MAJOR_VERSION < 5
|
||||
TSharedRef<FExtender> FGitSourceControlMenu::OnExtendLevelEditorViewMenu(const TSharedRef<FUICommandList> CommandList)
|
||||
{
|
||||
TSharedRef<FExtender> Extender(new FExtender());
|
||||
|
||||
Extender->AddMenuExtension(
|
||||
"SourceControlActions",
|
||||
EExtensionHook::After,
|
||||
nullptr,
|
||||
FMenuExtensionDelegate::CreateRaw(this, &FGitSourceControlMenu::AddMenuExtension));
|
||||
|
||||
return Extender;
|
||||
}
|
||||
#endif
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
+284
@@ -0,0 +1,284 @@
|
||||
// Copyright (c) 2014-2020 Sebastien Rombauts (sebastien.rombauts@gmail.com)
|
||||
//
|
||||
// Distributed under the MIT License (MIT) (See accompanying file LICENSE.txt
|
||||
// or copy at http://opensource.org/licenses/MIT)
|
||||
|
||||
#include "GitSourceControlModule.h"
|
||||
|
||||
#include "AssetToolsModule.h"
|
||||
#if ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION >= 1
|
||||
#include "Styling/AppStyle.h"
|
||||
#else
|
||||
#include "EditorStyleSet.h"
|
||||
#endif
|
||||
#include "Misc/App.h"
|
||||
#include "Modules/ModuleManager.h"
|
||||
#include "Features/IModularFeatures.h"
|
||||
|
||||
#include "ContentBrowserModule.h"
|
||||
#include "ContentBrowserDelegates.h"
|
||||
|
||||
#include "GitSourceControlOperations.h"
|
||||
#include "GitSourceControlUtils.h"
|
||||
#include "ISourceControlModule.h"
|
||||
#include "SourceControlHelpers.h"
|
||||
#include "Framework/Commands/UIAction.h"
|
||||
#include "Framework/MultiBox/MultiBoxExtender.h"
|
||||
#include "Framework/MultiBox/MultiBoxBuilder.h"
|
||||
#include "Misc/ConfigCacheIni.h"
|
||||
#include "Runtime/Launch/Resources/Version.h"
|
||||
|
||||
#define LOCTEXT_NAMESPACE "GitSourceControl"
|
||||
|
||||
TArray<FString> FGitSourceControlModule::EmptyStringArray;
|
||||
|
||||
namespace
|
||||
{
|
||||
static const FName NAME_SourceControl( TEXT( "SourceControl" ) );
|
||||
static const FName NAME_ContentBrowser( TEXT( "ContentBrowser" ) );
|
||||
}
|
||||
|
||||
template<typename Type>
|
||||
static TSharedRef<IGitSourceControlWorker, ESPMode::ThreadSafe> CreateWorker()
|
||||
{
|
||||
return MakeShareable( new Type() );
|
||||
}
|
||||
|
||||
void FGitSourceControlModule::StartupModule()
|
||||
{
|
||||
// Register our operations (implemented in GitSourceControlOperations.cpp by subclassing from Engine\Source\Developer\SourceControl\Public\SourceControlOperations.h)
|
||||
GitSourceControlProvider.RegisterWorker( "Connect", FGetGitSourceControlWorker::CreateStatic( &CreateWorker<FGitConnectWorker> ) );
|
||||
// Note: this provider uses the "CheckOut" command only with Git LFS 2 "lock" command, since Git itself has no lock command (all tracked files in the working copy are always already checked-out).
|
||||
GitSourceControlProvider.RegisterWorker( "CheckOut", FGetGitSourceControlWorker::CreateStatic( &CreateWorker<FGitCheckOutWorker> ) );
|
||||
GitSourceControlProvider.RegisterWorker( "UpdateStatus", FGetGitSourceControlWorker::CreateStatic( &CreateWorker<FGitUpdateStatusWorker> ) );
|
||||
GitSourceControlProvider.RegisterWorker( "MarkForAdd", FGetGitSourceControlWorker::CreateStatic( &CreateWorker<FGitMarkForAddWorker> ) );
|
||||
GitSourceControlProvider.RegisterWorker( "Delete", FGetGitSourceControlWorker::CreateStatic( &CreateWorker<FGitDeleteWorker> ) );
|
||||
GitSourceControlProvider.RegisterWorker( "Revert", FGetGitSourceControlWorker::CreateStatic( &CreateWorker<FGitRevertWorker> ) );
|
||||
GitSourceControlProvider.RegisterWorker( "Sync", FGetGitSourceControlWorker::CreateStatic( &CreateWorker<FGitSyncWorker> ) );
|
||||
GitSourceControlProvider.RegisterWorker( "Fetch", FGetGitSourceControlWorker::CreateStatic( &CreateWorker<FGitFetchWorker> ) );
|
||||
GitSourceControlProvider.RegisterWorker( "CheckIn", FGetGitSourceControlWorker::CreateStatic( &CreateWorker<FGitCheckInWorker> ) );
|
||||
GitSourceControlProvider.RegisterWorker( "Copy", FGetGitSourceControlWorker::CreateStatic( &CreateWorker<FGitCopyWorker> ) );
|
||||
GitSourceControlProvider.RegisterWorker( "Resolve", FGetGitSourceControlWorker::CreateStatic( &CreateWorker<FGitResolveWorker> ) );
|
||||
#if ENGINE_MAJOR_VERSION == 5
|
||||
GitSourceControlProvider.RegisterWorker( "MoveToChangelist", FGetGitSourceControlWorker::CreateStatic( &CreateWorker<FGitMoveToChangelistWorker> ) );
|
||||
GitSourceControlProvider.RegisterWorker( "UpdateChangelistsStatus", FGetGitSourceControlWorker::CreateStatic( &CreateWorker<FGitUpdateStagingWorker> ) );
|
||||
#endif
|
||||
|
||||
// load our settings
|
||||
GitSourceControlSettings.LoadSettings();
|
||||
|
||||
// If configured, do a check if the current user has permissions to access a specified repository. Exit with a fatal error if that is the case.
|
||||
FString RequiredRepositoryAccessURL, RequiredRepositoryAccessBranchName;
|
||||
GConfig->GetString(TEXT("GitSourceControl"), TEXT("RequiredAccessRepositoryURL"), RequiredRepositoryAccessURL, GEditorIni);
|
||||
GConfig->GetString(TEXT("GitSourceControl"), TEXT("RequiredAccessRepositoryBranchName"), RequiredRepositoryAccessBranchName, GEditorIni);
|
||||
if (!RequiredRepositoryAccessURL.IsEmpty())
|
||||
{
|
||||
if (RequiredRepositoryAccessBranchName.IsEmpty())
|
||||
{
|
||||
RequiredRepositoryAccessBranchName = "main";
|
||||
}
|
||||
int32 ReturnCode;
|
||||
FString StdErr;
|
||||
// Will fail (or block forever) over HTTPS if GCM is not set up
|
||||
// If using SSH, will fail if user doesn't have SSH keys set up
|
||||
const bool bLaunchedProcess = FPlatformProcess::ExecProcess(
|
||||
TEXT("git"),
|
||||
*FString::Format(TEXT("ls-remote --exit-code {0} {1}"), {RequiredRepositoryAccessURL, RequiredRepositoryAccessBranchName}),
|
||||
&ReturnCode, nullptr, &StdErr);
|
||||
if (!bLaunchedProcess)
|
||||
{
|
||||
UE_LOG(LogSourceControl, Fatal, TEXT("Could not launch git: %s"), *StdErr);
|
||||
}
|
||||
else if (ReturnCode != 0)
|
||||
{
|
||||
if (StdErr.IsEmpty())
|
||||
{
|
||||
StdErr = TEXT("Branch not found"); // if there is no output and there is a bad exit code, it's very likely the branch name was not found
|
||||
}
|
||||
UE_LOG(LogSourceControl, Fatal, TEXT("Could access branch %s on required repository %s(%d): %s"),
|
||||
*RequiredRepositoryAccessBranchName, *RequiredRepositoryAccessURL, ReturnCode, *StdErr);
|
||||
}
|
||||
}
|
||||
|
||||
// Bind our revision control provider to the editor
|
||||
IModularFeatures::Get().RegisterModularFeature( NAME_SourceControl, &GitSourceControlProvider );
|
||||
|
||||
FContentBrowserModule & ContentBrowserModule = FModuleManager::Get().LoadModuleChecked< FContentBrowserModule >( NAME_ContentBrowser );
|
||||
|
||||
#if ENGINE_MAJOR_VERSION >= 5
|
||||
// Register ContentBrowserDelegate Handles for UE5 EA
|
||||
// At the time of writing this UE5 is in Early Access and has no support for revision control yet. So instead we hook into the content browser..
|
||||
// .. and force a state update on the next tick for revision control. Usually the contentbrowser assets will request this themselves, but that's not working
|
||||
// Values here are 1 or 2 based on whether the change can be done immediately or needs to be delayed as unreal needs to work through its internal delegates first
|
||||
// >> Technically you wouldn't need to use `GetOnAssetSelectionChanged` -- but it's there as a safety mechanism. States aren't forceupdated for the first path that loads
|
||||
// >> Making sure we force an update on selection change that acts like a just in case other measures fail
|
||||
CbdHandle_OnFilterChanged = ContentBrowserModule.GetOnFilterChanged().AddLambda( [this]( const FARFilter&, bool ) { GitSourceControlProvider.TicksUntilNextForcedUpdate = 2; } );
|
||||
CbdHandle_OnSearchBoxChanged = ContentBrowserModule.GetOnSearchBoxChanged().AddLambda( [this]( const FText&, bool ){ GitSourceControlProvider.TicksUntilNextForcedUpdate = 1; } );
|
||||
CbdHandle_OnAssetSelectionChanged = ContentBrowserModule.GetOnAssetSelectionChanged().AddLambda( [this]( const TArray<FAssetData>&, bool ) { GitSourceControlProvider.TicksUntilNextForcedUpdate = 1; } );
|
||||
CbdHandle_OnAssetPathChanged = ContentBrowserModule.GetOnAssetPathChanged().AddLambda( [this]( const FString& ) { GitSourceControlProvider.TicksUntilNextForcedUpdate = 2; } );
|
||||
#endif
|
||||
|
||||
TArray<FContentBrowserMenuExtender_SelectedAssets>& CBAssetMenuExtenderDelegates = ContentBrowserModule.GetAllAssetViewContextMenuExtenders();
|
||||
CBAssetMenuExtenderDelegates.Add(FContentBrowserMenuExtender_SelectedAssets::CreateRaw( this, &FGitSourceControlModule::OnExtendContentBrowserAssetSelectionMenu ));
|
||||
CbdHandle_OnExtendAssetSelectionMenu = CBAssetMenuExtenderDelegates.Last().GetHandle();
|
||||
}
|
||||
|
||||
void FGitSourceControlModule::ShutdownModule()
|
||||
{
|
||||
// shut down the provider, as this module is going away
|
||||
GitSourceControlProvider.Close();
|
||||
|
||||
// unbind provider from editor
|
||||
IModularFeatures::Get().UnregisterModularFeature( NAME_SourceControl, &GitSourceControlProvider );
|
||||
|
||||
|
||||
// Unregister ContentBrowserDelegate Handles
|
||||
FContentBrowserModule & ContentBrowserModule = FModuleManager::Get().GetModuleChecked< FContentBrowserModule >( NAME_ContentBrowser );
|
||||
#if ENGINE_MAJOR_VERSION >= 5
|
||||
ContentBrowserModule.GetOnFilterChanged().Remove( CbdHandle_OnFilterChanged );
|
||||
ContentBrowserModule.GetOnSearchBoxChanged().Remove( CbdHandle_OnSearchBoxChanged );
|
||||
ContentBrowserModule.GetOnAssetSelectionChanged().Remove( CbdHandle_OnAssetSelectionChanged );
|
||||
ContentBrowserModule.GetOnAssetPathChanged().Remove( CbdHandle_OnAssetPathChanged );
|
||||
#endif
|
||||
|
||||
TArray<FContentBrowserMenuExtender_SelectedAssets>& CBAssetMenuExtenderDelegates = ContentBrowserModule.GetAllAssetViewContextMenuExtenders();
|
||||
CBAssetMenuExtenderDelegates.RemoveAll([ &ExtenderDelegateHandle = CbdHandle_OnExtendAssetSelectionMenu ]( const FContentBrowserMenuExtender_SelectedAssets& Delegate ) {
|
||||
return Delegate.GetHandle() == ExtenderDelegateHandle;
|
||||
});
|
||||
}
|
||||
|
||||
void FGitSourceControlModule::SaveSettings()
|
||||
{
|
||||
if (FApp::IsUnattended() || IsRunningCommandlet())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GitSourceControlSettings.SaveSettings();
|
||||
}
|
||||
|
||||
void FGitSourceControlModule::SetLastErrors(const TArray<FText>& InErrors)
|
||||
{
|
||||
FGitSourceControlModule* Module = FModuleManager::GetModulePtr<FGitSourceControlModule>("GitSourceControl");
|
||||
if (Module)
|
||||
{
|
||||
Module->GetProvider().SetLastErrors(InErrors);
|
||||
}
|
||||
}
|
||||
|
||||
TSharedRef<FExtender> FGitSourceControlModule::OnExtendContentBrowserAssetSelectionMenu(const TArray<FAssetData>& SelectedAssets)
|
||||
{
|
||||
TSharedRef<FExtender> Extender(new FExtender());
|
||||
|
||||
Extender->AddMenuExtension(
|
||||
"AssetSourceControlActions",
|
||||
EExtensionHook::After,
|
||||
nullptr,
|
||||
FMenuExtensionDelegate::CreateRaw( this, &FGitSourceControlModule::CreateGitContentBrowserAssetMenu, SelectedAssets )
|
||||
);
|
||||
|
||||
return Extender;
|
||||
}
|
||||
|
||||
void FGitSourceControlModule::CreateGitContentBrowserAssetMenu(FMenuBuilder& MenuBuilder, const TArray<FAssetData> SelectedAssets)
|
||||
{
|
||||
if (!FGitSourceControlModule::Get().GetProvider().GetStatusBranchNames().Num())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const TArray<FString>& StatusBranchNames = FGitSourceControlModule::Get().GetProvider().GetStatusBranchNames();
|
||||
const FString& BranchName = StatusBranchNames[0];
|
||||
MenuBuilder.AddMenuEntry(
|
||||
FText::Format(LOCTEXT("StatusBranchDiff", "Diff against status branch"), FText::FromString(BranchName)),
|
||||
FText::Format(LOCTEXT("StatusBranchDiffDesc", "Compare this asset to the latest status branch version"), FText::FromString(BranchName)),
|
||||
#if ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION >= 1
|
||||
FSlateIcon(FAppStyle::GetAppStyleSetName(), "SourceControl.Actions.Diff"),
|
||||
#else
|
||||
FSlateIcon(FEditorStyle::GetStyleSetName(), "SourceControl.Actions.Diff"),
|
||||
#endif
|
||||
FUIAction(FExecuteAction::CreateRaw( this, &FGitSourceControlModule::DiffAssetAgainstGitOriginBranch, SelectedAssets, BranchName ))
|
||||
);
|
||||
}
|
||||
|
||||
void FGitSourceControlModule::DiffAssetAgainstGitOriginBranch(const TArray<FAssetData> SelectedAssets, FString BranchName) const
|
||||
{
|
||||
for (int32 AssetIdx = 0; AssetIdx < SelectedAssets.Num(); AssetIdx++)
|
||||
{
|
||||
// Get the actual asset (will load it)
|
||||
const FAssetData& AssetData = SelectedAssets[AssetIdx];
|
||||
|
||||
if (UObject* CurrentObject = AssetData.GetAsset())
|
||||
{
|
||||
const FString PackagePath = AssetData.PackageName.ToString();
|
||||
const FString PackageName = AssetData.AssetName.ToString();
|
||||
DiffAgainstOriginBranch(CurrentObject, PackagePath, PackageName, BranchName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FGitSourceControlModule::DiffAgainstOriginBranch( UObject * InObject, const FString & InPackagePath, const FString & InPackageName, const FString & BranchName ) const
|
||||
{
|
||||
check(InObject);
|
||||
|
||||
const FGitSourceControlModule& GitSourceControl = FModuleManager::GetModuleChecked<FGitSourceControlModule>("GitSourceControl");
|
||||
const FString& PathToGitBinary = GitSourceControl.AccessSettings().GetBinaryPath();
|
||||
const FString& PathToRepositoryRoot = GitSourceControl.GetProvider().GetPathToRepositoryRoot();
|
||||
|
||||
ISourceControlProvider& SourceControlProvider = ISourceControlModule::Get().GetProvider();
|
||||
|
||||
const FAssetToolsModule& AssetToolsModule = FModuleManager::GetModuleChecked<FAssetToolsModule>("AssetTools");
|
||||
|
||||
// Get the SCC state
|
||||
const FSourceControlStatePtr SourceControlState = SourceControlProvider.GetState(SourceControlHelpers::PackageFilename(InPackagePath), EStateCacheUsage::Use);
|
||||
|
||||
// If we have an asset and its in SCC..
|
||||
if (SourceControlState.IsValid() && InObject != nullptr && SourceControlState->IsSourceControlled())
|
||||
{
|
||||
// Get the file name of package
|
||||
FString RelativeFileName;
|
||||
#if ENGINE_MAJOR_VERSION >= 5
|
||||
if (FPackageName::DoesPackageExist(InPackagePath, &RelativeFileName))
|
||||
#else
|
||||
if (FPackageName::DoesPackageExist(InPackagePath, nullptr, &RelativeFileName))
|
||||
#endif
|
||||
{
|
||||
// if(SourceControlState->GetHistorySize() > 0)
|
||||
{
|
||||
TArray<FString> Errors;
|
||||
const auto& Revision = GitSourceControlUtils::GetOriginRevisionOnBranch(PathToGitBinary, PathToRepositoryRoot, RelativeFileName, Errors, BranchName);
|
||||
|
||||
check(Revision.IsValid());
|
||||
|
||||
FString TempFileName;
|
||||
if (Revision->Get(TempFileName))
|
||||
{
|
||||
// Try and load that package
|
||||
UPackage* TempPackage = LoadPackage(nullptr, *TempFileName, LOAD_ForDiff | LOAD_DisableCompileOnLoad);
|
||||
if (TempPackage != nullptr)
|
||||
{
|
||||
// Grab the old asset from that old package
|
||||
UObject* OldObject = FindObject<UObject>(TempPackage, *InPackageName);
|
||||
if (OldObject != nullptr)
|
||||
{
|
||||
/* Set the revision information*/
|
||||
FRevisionInfo OldRevision;
|
||||
OldRevision.Changelist = Revision->GetCheckInIdentifier();
|
||||
OldRevision.Date = Revision->GetDate();
|
||||
OldRevision.Revision = Revision->GetRevision();
|
||||
|
||||
FRevisionInfo NewRevision;
|
||||
NewRevision.Revision = TEXT("");
|
||||
|
||||
AssetToolsModule.Get().DiffAssets(OldObject, InObject, OldRevision, NewRevision);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
IMPLEMENT_MODULE( FGitSourceControlModule, GitSourceControl );
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
+911
@@ -0,0 +1,911 @@
|
||||
// Copyright (c) 2014-2020 Sebastien Rombauts (sebastien.rombauts@gmail.com)
|
||||
//
|
||||
// Distributed under the MIT License (MIT) (See accompanying file LICENSE.txt
|
||||
// or copy at http://opensource.org/licenses/MIT)
|
||||
|
||||
#include "GitSourceControlOperations.h"
|
||||
|
||||
#include "Misc/Paths.h"
|
||||
#include "Modules/ModuleManager.h"
|
||||
#include "SourceControlOperations.h"
|
||||
#include "ISourceControlModule.h"
|
||||
#include "GitSourceControlModule.h"
|
||||
#include "GitSourceControlCommand.h"
|
||||
#include "GitSourceControlUtils.h"
|
||||
#include "SourceControlHelpers.h"
|
||||
#include "Logging/MessageLog.h"
|
||||
#include "Misc/MessageDialog.h"
|
||||
#include "HAL/PlatformProcess.h"
|
||||
#include "GenericPlatform/GenericPlatformFile.h"
|
||||
#if ENGINE_MAJOR_VERSION >= 5
|
||||
#include "HAL/PlatformFileManager.h"
|
||||
#else
|
||||
#include "HAL/PlatformFilemanager.h"
|
||||
#endif
|
||||
|
||||
#include <thread>
|
||||
|
||||
#define LOCTEXT_NAMESPACE "GitSourceControl"
|
||||
|
||||
FName FGitConnectWorker::GetName() const
|
||||
{
|
||||
return "Connect";
|
||||
}
|
||||
|
||||
bool FGitConnectWorker::Execute(FGitSourceControlCommand& InCommand)
|
||||
{
|
||||
// The connect worker checks if we are connected to the remote server.
|
||||
check(InCommand.Operation->GetName() == GetName());
|
||||
TSharedRef<FConnect, ESPMode::ThreadSafe> Operation = StaticCastSharedRef<FConnect>(InCommand.Operation);
|
||||
|
||||
// Skip login operations, since Git does not have to login.
|
||||
// It's not a big deal for async commands though, so let those go through.
|
||||
// More information: this is a heuristic for cases where UE is trying to create
|
||||
// a valid Perforce connection as a side effect for the connect worker. For Git,
|
||||
// the connect worker has no side effects. It is simply a query to retrieve information
|
||||
// to be displayed to the user, like in the revision control settings or on init.
|
||||
// Therefore, there is no need for synchronously establishing a connection if not there.
|
||||
if (InCommand.Concurrency == EConcurrency::Synchronous)
|
||||
{
|
||||
InCommand.bCommandSuccessful = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check Git availability
|
||||
// We already know that Git is available if PathToGitBinary is not empty, since it is validated then.
|
||||
if (InCommand.PathToGitBinary.IsEmpty())
|
||||
{
|
||||
const FText& NotFound = LOCTEXT("GitNotFound", "Failed to enable Git revision control. You need to install Git and ensure the plugin has a valid path to the git executable.");
|
||||
InCommand.ResultInfo.ErrorMessages.Add(NotFound.ToString());
|
||||
Operation->SetErrorText(NotFound);
|
||||
InCommand.bCommandSuccessful = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get default branch: git remote show
|
||||
|
||||
TArray<FString> Parameters {
|
||||
TEXT("-h"), // Only limit to branches
|
||||
TEXT("-q") // Skip printing out remote URL, we don't use it
|
||||
};
|
||||
|
||||
// Check if remote matches our refs.
|
||||
// Could be useful in the future, but all we want to know right now is if connection is up.
|
||||
// Parameters.Add("--exit-code");
|
||||
TArray<FString> InfoMessages;
|
||||
TArray<FString> ErrorMessages;
|
||||
InCommand.bCommandSuccessful = GitSourceControlUtils::RunCommand(TEXT("ls-remote"), InCommand.PathToGitBinary, InCommand.PathToRepositoryRoot, FGitSourceControlModule::GetEmptyStringArray(), FGitSourceControlModule::GetEmptyStringArray(), InfoMessages, ErrorMessages);
|
||||
if (!InCommand.bCommandSuccessful)
|
||||
{
|
||||
const FText& NotFound = LOCTEXT("GitRemoteFailed", "Failed Git remote connection. Ensure your repo is initialized, and check your connection to the Git host.");
|
||||
InCommand.ResultInfo.ErrorMessages.Add(NotFound.ToString());
|
||||
Operation->SetErrorText(NotFound);
|
||||
}
|
||||
|
||||
// TODO: always return true, and enter an offline mode if could not connect to remote
|
||||
return InCommand.bCommandSuccessful;
|
||||
}
|
||||
|
||||
bool FGitConnectWorker::UpdateStates() const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
FName FGitCheckOutWorker::GetName() const
|
||||
{
|
||||
return "CheckOut";
|
||||
}
|
||||
|
||||
bool FGitCheckOutWorker::Execute(FGitSourceControlCommand& InCommand)
|
||||
{
|
||||
// If we have nothing to process, exit immediately
|
||||
if (InCommand.Files.Num() == 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
check(InCommand.Operation->GetName() == GetName());
|
||||
|
||||
if (!InCommand.bUsingGitLfsLocking)
|
||||
{
|
||||
InCommand.bCommandSuccessful = false;
|
||||
return InCommand.bCommandSuccessful;
|
||||
}
|
||||
|
||||
// lock files: execute the LFS command on relative filenames
|
||||
const TArray<FString>& RelativeFiles = GitSourceControlUtils::RelativeFilenames(InCommand.Files, InCommand.PathToGitRoot);
|
||||
|
||||
const TArray<FString>& LockableRelativeFiles = RelativeFiles.FilterByPredicate(GitSourceControlUtils::IsFileLFSLockable);
|
||||
|
||||
if (LockableRelativeFiles.Num() < 1)
|
||||
{
|
||||
InCommand.bCommandSuccessful = true;
|
||||
return InCommand.bCommandSuccessful;
|
||||
}
|
||||
|
||||
const bool bSuccess = GitSourceControlUtils::RunLFSCommand(TEXT("lock"), InCommand.PathToGitRoot, InCommand.PathToGitBinary, FGitSourceControlModule::GetEmptyStringArray(), LockableRelativeFiles, InCommand.ResultInfo.InfoMessages, InCommand.ResultInfo.ErrorMessages);
|
||||
InCommand.bCommandSuccessful = bSuccess;
|
||||
const FString& LockUser = FGitSourceControlModule::Get().GetProvider().GetLockUser();
|
||||
if (bSuccess)
|
||||
{
|
||||
TArray<FString> AbsoluteFiles;
|
||||
for (const auto& RelativeFile : RelativeFiles)
|
||||
{
|
||||
FString AbsoluteFile = FPaths::Combine(InCommand.PathToGitRoot, RelativeFile);
|
||||
FGitLockedFilesCache::AddLockedFile(AbsoluteFile, LockUser);
|
||||
FPaths::NormalizeFilename(AbsoluteFile);
|
||||
AbsoluteFiles.Add(AbsoluteFile);
|
||||
}
|
||||
|
||||
GitSourceControlUtils::CollectNewStates(AbsoluteFiles, States, EFileState::Unset, ETreeState::Unset, ELockState::Locked);
|
||||
for (auto& State : States)
|
||||
{
|
||||
State.Value.LockUser = LockUser;
|
||||
}
|
||||
}
|
||||
|
||||
return InCommand.bCommandSuccessful;
|
||||
}
|
||||
|
||||
bool FGitCheckOutWorker::UpdateStates() const
|
||||
{
|
||||
return GitSourceControlUtils::UpdateCachedStates(States);
|
||||
}
|
||||
|
||||
static FText ParseCommitResults(const TArray<FString>& InResults)
|
||||
{
|
||||
if (InResults.Num() >= 1)
|
||||
{
|
||||
const FString& FirstLine = InResults[0];
|
||||
return FText::Format(LOCTEXT("CommitMessage", "Commited {0}."), FText::FromString(FirstLine));
|
||||
}
|
||||
return LOCTEXT("CommitMessageUnknown", "Submitted revision.");
|
||||
}
|
||||
|
||||
FName FGitCheckInWorker::GetName() const
|
||||
{
|
||||
return "CheckIn";
|
||||
}
|
||||
|
||||
const FText EmptyCommitMsg;
|
||||
|
||||
bool FGitCheckInWorker::Execute(FGitSourceControlCommand& InCommand)
|
||||
{
|
||||
check(InCommand.Operation->GetName() == GetName());
|
||||
|
||||
TSharedRef<FCheckIn, ESPMode::ThreadSafe> Operation = StaticCastSharedRef<FCheckIn>(InCommand.Operation);
|
||||
|
||||
// make a temp file to place our commit message in
|
||||
bool bDoCommit = InCommand.Files.Num() > 0;
|
||||
const FText& CommitMsg = bDoCommit ? Operation->GetDescription() : EmptyCommitMsg;
|
||||
FGitScopedTempFile CommitMsgFile(CommitMsg);
|
||||
if (CommitMsgFile.GetFilename().Len() > 0)
|
||||
{
|
||||
FGitSourceControlProvider& Provider = FGitSourceControlModule::Get().GetProvider();
|
||||
|
||||
if (bDoCommit)
|
||||
{
|
||||
FString ParamCommitMsgFilename = TEXT("--file=\"");
|
||||
ParamCommitMsgFilename += FPaths::ConvertRelativePathToFull(CommitMsgFile.GetFilename());
|
||||
ParamCommitMsgFilename += TEXT("\"");
|
||||
TArray<FString> CommitParameters {ParamCommitMsgFilename};
|
||||
const TArray<FString>& FilesToCommit = GitSourceControlUtils::RelativeFilenames(InCommand.Files, InCommand.PathToRepositoryRoot);
|
||||
|
||||
// If no files were committed, this is false, so we treat it as if we never wanted to commit in the first place.
|
||||
bDoCommit = GitSourceControlUtils::RunCommit(InCommand.PathToGitBinary, InCommand.PathToRepositoryRoot, CommitParameters,
|
||||
FilesToCommit, InCommand.ResultInfo.InfoMessages, InCommand.ResultInfo.ErrorMessages);
|
||||
}
|
||||
|
||||
// If we commit, we can push up the deleted state to gone
|
||||
if (bDoCommit)
|
||||
{
|
||||
// Remove any deleted files from status cache
|
||||
TArray<TSharedRef<ISourceControlState, ESPMode::ThreadSafe>> LocalStates;
|
||||
Provider.GetState(InCommand.Files, LocalStates, EStateCacheUsage::Use);
|
||||
for (const auto& State : LocalStates)
|
||||
{
|
||||
if (State->IsDeleted())
|
||||
{
|
||||
Provider.RemoveFileFromCache(State->GetFilename());
|
||||
}
|
||||
}
|
||||
Operation->SetSuccessMessage(ParseCommitResults(InCommand.ResultInfo.InfoMessages));
|
||||
const FString& Message = (InCommand.ResultInfo.InfoMessages.Num() > 0) ? InCommand.ResultInfo.InfoMessages[0] : TEXT("");
|
||||
UE_LOG(LogSourceControl, Log, TEXT("commit successful: %s"), *Message);
|
||||
GitSourceControlUtils::GetCommitInfo(InCommand.PathToGitBinary, InCommand.PathToRepositoryRoot, InCommand.CommitId, InCommand.CommitSummary);
|
||||
}
|
||||
|
||||
// Collect difference between the remote and what we have on top of remote locally. This is to handle unpushed commits other than the one we just did.
|
||||
// Doesn't matter that we're not synced. Because our local branch is always based on the remote.
|
||||
TArray<FString> CommittedFiles;
|
||||
FString BranchName;
|
||||
bool bDiffSuccess;
|
||||
if (GitSourceControlUtils::GetRemoteBranchName(InCommand.PathToGitBinary, InCommand.PathToRepositoryRoot, BranchName))
|
||||
{
|
||||
TArray<FString> Parameters {"--name-only", FString::Printf(TEXT("%s...HEAD"), *BranchName), "--"};
|
||||
bDiffSuccess = GitSourceControlUtils::RunCommand(TEXT("diff"), InCommand.PathToGitBinary, InCommand.PathToRepositoryRoot, Parameters,
|
||||
FGitSourceControlModule::GetEmptyStringArray(), CommittedFiles, InCommand.ResultInfo.ErrorMessages);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Get all non-remote commits and list out their files
|
||||
TArray<FString> Parameters {"--branches", "--not" "--remotes", "--name-only", "--pretty="};
|
||||
bDiffSuccess = GitSourceControlUtils::RunCommand(TEXT("log"), InCommand.PathToGitBinary, InCommand.PathToRepositoryRoot, Parameters, FGitSourceControlModule::GetEmptyStringArray(), CommittedFiles, InCommand.ResultInfo.ErrorMessages);
|
||||
// Dedup files list between commits
|
||||
CommittedFiles = TSet<FString>{CommittedFiles}.Array();
|
||||
}
|
||||
|
||||
bool bUnpushedFiles;
|
||||
TSet<FString> FilesToCheckIn {InCommand.Files};
|
||||
if (bDiffSuccess)
|
||||
{
|
||||
// Only push if we have a difference (any commits at all, not just the one we just did)
|
||||
bUnpushedFiles = CommittedFiles.Num() > 0;
|
||||
CommittedFiles = GitSourceControlUtils::AbsoluteFilenames(CommittedFiles, InCommand.PathToRepositoryRoot);
|
||||
FilesToCheckIn.Append(CommittedFiles.FilterByPredicate(GitSourceControlUtils::IsFileLFSLockable));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Be cautious, try pushing anyway
|
||||
bUnpushedFiles = true;
|
||||
}
|
||||
|
||||
TArray<FString> PulledFiles;
|
||||
|
||||
// If we have unpushed files, push
|
||||
if (bUnpushedFiles)
|
||||
{
|
||||
// TODO: configure remote
|
||||
TArray<FString> PushParameters {TEXT("-u"), TEXT("origin"), TEXT("HEAD")};
|
||||
InCommand.bCommandSuccessful = GitSourceControlUtils::RunCommand(TEXT("push"), InCommand.PathToGitBinary, InCommand.PathToRepositoryRoot,
|
||||
PushParameters, FGitSourceControlModule::GetEmptyStringArray(),
|
||||
InCommand.ResultInfo.InfoMessages, InCommand.ResultInfo.ErrorMessages);
|
||||
|
||||
if (!InCommand.bCommandSuccessful)
|
||||
{
|
||||
// if out of date, pull first, then try again
|
||||
bool bWasOutOfDate = false;
|
||||
for (const auto& PushError : InCommand.ResultInfo.ErrorMessages)
|
||||
{
|
||||
if ((PushError.Contains(TEXT("[rejected]")) && (PushError.Contains(TEXT("non-fast-forward")) || PushError.Contains(TEXT("fetch first")))) ||
|
||||
PushError.Contains(TEXT("cannot lock ref")))
|
||||
{
|
||||
// Don't do it during iteration, want to append pull results to InCommand.ResultInfo.ErrorMessages
|
||||
bWasOutOfDate = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (bWasOutOfDate)
|
||||
{
|
||||
// Get latest
|
||||
const bool bFetched = GitSourceControlUtils::FetchRemote(InCommand.PathToGitBinary, InCommand.PathToRepositoryRoot, false,
|
||||
InCommand.ResultInfo.InfoMessages, InCommand.ResultInfo.ErrorMessages);
|
||||
if (bFetched)
|
||||
{
|
||||
// Update local with latest
|
||||
const bool bPulled = GitSourceControlUtils::PullOrigin(InCommand.PathToGitBinary, InCommand.PathToRepositoryRoot,
|
||||
FGitSourceControlModule::GetEmptyStringArray(), PulledFiles,
|
||||
InCommand.ResultInfo.InfoMessages, InCommand.ResultInfo.ErrorMessages);
|
||||
if (bPulled)
|
||||
{
|
||||
InCommand.bCommandSuccessful = GitSourceControlUtils::RunCommand(
|
||||
TEXT("push"), InCommand.PathToGitBinary, InCommand.PathToRepositoryRoot, PushParameters,
|
||||
FGitSourceControlModule::GetEmptyStringArray(), InCommand.ResultInfo.InfoMessages, InCommand.ResultInfo.ErrorMessages);
|
||||
}
|
||||
}
|
||||
|
||||
// Our push still wasn't successful
|
||||
if (!InCommand.bCommandSuccessful)
|
||||
{
|
||||
if (!Provider.bPendingRestart)
|
||||
{
|
||||
// If it fails, just let the user do it
|
||||
FText PushFailMessage(LOCTEXT("GitPush_OutOfDate_Msg", "Git Push failed because there are changes you need to pull.\n\n"
|
||||
"An attempt was made to pull, but failed, because while the Unreal Editor is "
|
||||
"open, files cannot always be updated.\n\n"
|
||||
"Please exit the editor, and update the project again."));
|
||||
FText PushFailTitle(LOCTEXT("GitPush_OutOfDate_Title", "Git Pull Required"));
|
||||
#if ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION >= 3
|
||||
FMessageDialog::Open(EAppMsgType::Ok, PushFailMessage, PushFailTitle);
|
||||
#else
|
||||
FMessageDialog::Open(EAppMsgType::Ok, PushFailMessage, &PushFailTitle);
|
||||
#endif
|
||||
UE_LOG(LogSourceControl, Log, TEXT("Push failed because we're out of date, prompting user to resolve manually"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
InCommand.bCommandSuccessful = true;
|
||||
}
|
||||
|
||||
// git-lfs: unlock files
|
||||
if (InCommand.bUsingGitLfsLocking)
|
||||
{
|
||||
// If we successfully pushed (or didn't need to push), unlock the files marked for check in
|
||||
if (InCommand.bCommandSuccessful)
|
||||
{
|
||||
// unlock files: execute the LFS command on relative filenames
|
||||
// (unlock only locked files, that is, not Added files)
|
||||
TArray<FString> LockedFiles;
|
||||
GitSourceControlUtils::GetLockedFiles(FilesToCheckIn.Array(), LockedFiles);
|
||||
if (LockedFiles.Num() > 0)
|
||||
{
|
||||
const TArray<FString>& FilesToUnlock = GitSourceControlUtils::RelativeFilenames(LockedFiles, InCommand.PathToGitRoot);
|
||||
|
||||
if (FilesToUnlock.Num() > 0)
|
||||
{
|
||||
// Not strictly necessary to succeed, so don't update command success
|
||||
const bool bUnlockSuccess = GitSourceControlUtils::RunLFSCommand(TEXT("unlock"), InCommand.PathToGitRoot, InCommand.PathToGitBinary,
|
||||
FGitSourceControlModule::GetEmptyStringArray(), FilesToUnlock,
|
||||
InCommand.ResultInfo.InfoMessages, InCommand.ResultInfo.ErrorMessages);
|
||||
if (bUnlockSuccess)
|
||||
{
|
||||
for (const auto& File : LockedFiles)
|
||||
{
|
||||
FGitLockedFilesCache::RemoveLockedFile(File);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#if 0
|
||||
for (const FString& File : FilesToCheckIn.Array())
|
||||
{
|
||||
FPlatformFileManager::Get().GetPlatformFile().SetReadOnly(*File, true);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
// Collect all the files we touched through the pull update
|
||||
if (bUnpushedFiles && PulledFiles.Num())
|
||||
{
|
||||
FilesToCheckIn.Append(PulledFiles);
|
||||
}
|
||||
// Before, we added only lockable files from CommittedFiles. But now, we want to update all files, not just lockables.
|
||||
FilesToCheckIn.Append(CommittedFiles);
|
||||
|
||||
// now update the status of our files
|
||||
TMap<FString, FGitSourceControlState> UpdatedStates;
|
||||
bool bSuccess = GitSourceControlUtils::RunUpdateStatus(InCommand.PathToGitBinary, InCommand.PathToRepositoryRoot, InCommand.bUsingGitLfsLocking,
|
||||
FilesToCheckIn.Array(), InCommand.ResultInfo.ErrorMessages, UpdatedStates);
|
||||
if (bSuccess)
|
||||
{
|
||||
GitSourceControlUtils::CollectNewStates(UpdatedStates, States);
|
||||
}
|
||||
GitSourceControlUtils::RemoveRedundantErrors(InCommand, TEXT("' is outside repository"));
|
||||
return InCommand.bCommandSuccessful;
|
||||
}
|
||||
|
||||
InCommand.bCommandSuccessful = false;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool FGitCheckInWorker::UpdateStates() const
|
||||
{
|
||||
return GitSourceControlUtils::UpdateCachedStates(States);
|
||||
}
|
||||
|
||||
FName FGitMarkForAddWorker::GetName() const
|
||||
{
|
||||
return "MarkForAdd";
|
||||
}
|
||||
|
||||
bool FGitMarkForAddWorker::Execute(FGitSourceControlCommand& InCommand)
|
||||
{
|
||||
// If we have nothing to process, exit immediately
|
||||
if (InCommand.Files.Num() == 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
check(InCommand.Operation->GetName() == GetName());
|
||||
|
||||
InCommand.bCommandSuccessful = GitSourceControlUtils::RunCommand(TEXT("add"), InCommand.PathToGitBinary, InCommand.PathToRepositoryRoot, FGitSourceControlModule::GetEmptyStringArray(), InCommand.Files, InCommand.ResultInfo.InfoMessages, InCommand.ResultInfo.ErrorMessages);
|
||||
|
||||
if (InCommand.bCommandSuccessful)
|
||||
{
|
||||
GitSourceControlUtils::CollectNewStates(InCommand.Files, States, EFileState::Added, ETreeState::Staged);
|
||||
}
|
||||
else
|
||||
{
|
||||
TMap<FString, FGitSourceControlState> UpdatedStates;
|
||||
bool bSuccess = GitSourceControlUtils::RunUpdateStatus(InCommand.PathToGitBinary, InCommand.PathToRepositoryRoot, InCommand.bUsingGitLfsLocking, InCommand.Files, InCommand.ResultInfo.ErrorMessages, UpdatedStates);
|
||||
if (bSuccess)
|
||||
{
|
||||
GitSourceControlUtils::CollectNewStates(UpdatedStates, States);
|
||||
}
|
||||
GitSourceControlUtils::RemoveRedundantErrors(InCommand, TEXT("' is outside repository"));
|
||||
}
|
||||
|
||||
return InCommand.bCommandSuccessful;
|
||||
}
|
||||
|
||||
bool FGitMarkForAddWorker::UpdateStates() const
|
||||
{
|
||||
return GitSourceControlUtils::UpdateCachedStates(States);
|
||||
}
|
||||
|
||||
FName FGitDeleteWorker::GetName() const
|
||||
{
|
||||
return "Delete";
|
||||
}
|
||||
|
||||
bool FGitDeleteWorker::Execute(FGitSourceControlCommand& InCommand)
|
||||
{
|
||||
// If we have nothing to process, exit immediately
|
||||
if (InCommand.Files.Num() == 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
check(InCommand.Operation->GetName() == GetName());
|
||||
|
||||
InCommand.bCommandSuccessful = GitSourceControlUtils::RunCommand(TEXT("rm"), InCommand.PathToGitBinary, InCommand.PathToRepositoryRoot, FGitSourceControlModule::GetEmptyStringArray(), InCommand.Files, InCommand.ResultInfo.InfoMessages, InCommand.ResultInfo.ErrorMessages);
|
||||
|
||||
if (InCommand.bCommandSuccessful)
|
||||
{
|
||||
GitSourceControlUtils::CollectNewStates(InCommand.Files, States, EFileState::Deleted, ETreeState::Staged);
|
||||
}
|
||||
else
|
||||
{
|
||||
TMap<FString, FGitSourceControlState> UpdatedStates;
|
||||
bool bSuccess = GitSourceControlUtils::RunUpdateStatus(InCommand.PathToGitBinary, InCommand.PathToRepositoryRoot, InCommand.bUsingGitLfsLocking, InCommand.Files, InCommand.ResultInfo.ErrorMessages, UpdatedStates);
|
||||
if (bSuccess)
|
||||
{
|
||||
GitSourceControlUtils::CollectNewStates(UpdatedStates, States);
|
||||
}
|
||||
GitSourceControlUtils::RemoveRedundantErrors(InCommand, TEXT("' is outside repository"));
|
||||
}
|
||||
|
||||
return InCommand.bCommandSuccessful;
|
||||
}
|
||||
|
||||
bool FGitDeleteWorker::UpdateStates() const
|
||||
{
|
||||
return GitSourceControlUtils::UpdateCachedStates(States);
|
||||
}
|
||||
|
||||
|
||||
// Get lists of Missing files (ie "deleted"), Modified files, and "other than Added" Existing files
|
||||
void GetMissingVsExistingFiles(const TArray<FString>& InFiles, TArray<FString>& OutMissingFiles, TArray<FString>& OutAllExistingFiles, TArray<FString>& OutOtherThanAddedExistingFiles)
|
||||
{
|
||||
FGitSourceControlModule& GitSourceControl = FGitSourceControlModule::Get();
|
||||
FGitSourceControlProvider& Provider = GitSourceControl.GetProvider();
|
||||
|
||||
const TArray<FString> Files = (InFiles.Num() > 0) ? (InFiles) : (Provider.GetFilesInCache());
|
||||
|
||||
TArray<TSharedRef<ISourceControlState, ESPMode::ThreadSafe>> LocalStates;
|
||||
Provider.GetState(Files, LocalStates, EStateCacheUsage::Use);
|
||||
for (const auto& State : LocalStates)
|
||||
{
|
||||
if (FPaths::FileExists(State->GetFilename()))
|
||||
{
|
||||
if (State->IsAdded())
|
||||
{
|
||||
OutAllExistingFiles.Add(State->GetFilename());
|
||||
}
|
||||
else if (State->IsModified())
|
||||
{
|
||||
OutOtherThanAddedExistingFiles.Add(State->GetFilename());
|
||||
OutAllExistingFiles.Add(State->GetFilename());
|
||||
}
|
||||
else if (State->CanRevert()) // for locked but unmodified files
|
||||
{
|
||||
OutOtherThanAddedExistingFiles.Add(State->GetFilename());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// If already queued for deletion, don't try to delete again
|
||||
if (State->IsSourceControlled() && !State->IsDeleted())
|
||||
{
|
||||
OutMissingFiles.Add(State->GetFilename());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FName FGitRevertWorker::GetName() const
|
||||
{
|
||||
return "Revert";
|
||||
}
|
||||
|
||||
bool FGitRevertWorker::Execute(FGitSourceControlCommand& InCommand)
|
||||
{
|
||||
InCommand.bCommandSuccessful = true;
|
||||
|
||||
// Filter files by status
|
||||
TArray<FString> MissingFiles;
|
||||
TArray<FString> AllExistingFiles;
|
||||
TArray<FString> OtherThanAddedExistingFiles;
|
||||
GetMissingVsExistingFiles(InCommand.Files, MissingFiles, AllExistingFiles, OtherThanAddedExistingFiles);
|
||||
|
||||
const bool bRevertAll = InCommand.Files.Num() < 1;
|
||||
if (bRevertAll)
|
||||
{
|
||||
TArray<FString> Parms;
|
||||
Parms.Add(TEXT("--hard"));
|
||||
InCommand.bCommandSuccessful &= GitSourceControlUtils::RunCommand(TEXT("reset"), InCommand.PathToGitBinary, InCommand.PathToRepositoryRoot, Parms, FGitSourceControlModule::GetEmptyStringArray(), InCommand.ResultInfo.InfoMessages, InCommand.ResultInfo.ErrorMessages);
|
||||
|
||||
Parms.Reset(2);
|
||||
Parms.Add(TEXT("-f")); // force
|
||||
Parms.Add(TEXT("-d")); // remove directories
|
||||
InCommand.bCommandSuccessful &= GitSourceControlUtils::RunCommand(TEXT("clean"), InCommand.PathToGitBinary, InCommand.PathToRepositoryRoot, Parms, FGitSourceControlModule::GetEmptyStringArray(), InCommand.ResultInfo.InfoMessages, InCommand.ResultInfo.ErrorMessages);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (MissingFiles.Num() > 0)
|
||||
{
|
||||
// "Added" files that have been deleted needs to be removed from revision control
|
||||
InCommand.bCommandSuccessful &= GitSourceControlUtils::RunCommand(TEXT("rm"), InCommand.PathToGitBinary, InCommand.PathToRepositoryRoot, FGitSourceControlModule::GetEmptyStringArray(), MissingFiles, InCommand.ResultInfo.InfoMessages, InCommand.ResultInfo.ErrorMessages);
|
||||
}
|
||||
if (AllExistingFiles.Num() > 0)
|
||||
{
|
||||
// reset and revert any changes already added to the index
|
||||
InCommand.bCommandSuccessful &= GitSourceControlUtils::RunCommand(TEXT("reset"), InCommand.PathToGitBinary, InCommand.PathToRepositoryRoot, FGitSourceControlModule::GetEmptyStringArray(), AllExistingFiles, InCommand.ResultInfo.InfoMessages, InCommand.ResultInfo.ErrorMessages);
|
||||
InCommand.bCommandSuccessful &= GitSourceControlUtils::RunCommand(TEXT("checkout"), InCommand.PathToGitBinary, InCommand.PathToRepositoryRoot, FGitSourceControlModule::GetEmptyStringArray(), AllExistingFiles, InCommand.ResultInfo.InfoMessages, InCommand.ResultInfo.ErrorMessages);
|
||||
}
|
||||
if (OtherThanAddedExistingFiles.Num() > 0)
|
||||
{
|
||||
// revert any changes in working copy (this would fails if the asset was in "Added" state, since after "reset" it is now "untracked")
|
||||
// may need to try a few times due to file locks from prior operations
|
||||
bool CheckoutSuccess = false;
|
||||
int32 Attempts = 10;
|
||||
while( Attempts-- > 0 )
|
||||
{
|
||||
CheckoutSuccess = GitSourceControlUtils::RunCommand(TEXT("checkout"), InCommand.PathToGitBinary, InCommand.PathToRepositoryRoot, FGitSourceControlModule::GetEmptyStringArray(), OtherThanAddedExistingFiles, InCommand.ResultInfo.InfoMessages, InCommand.ResultInfo.ErrorMessages);
|
||||
if (CheckoutSuccess)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
FPlatformProcess::Sleep(0.1f);
|
||||
}
|
||||
|
||||
InCommand.bCommandSuccessful &= CheckoutSuccess;
|
||||
}
|
||||
}
|
||||
|
||||
if (InCommand.bUsingGitLfsLocking)
|
||||
{
|
||||
// unlock files: execute the LFS command on relative filenames
|
||||
// (unlock only locked files, that is, not Added files)
|
||||
TArray<FString> LockedFiles;
|
||||
GitSourceControlUtils::GetLockedFiles(OtherThanAddedExistingFiles, LockedFiles);
|
||||
if (LockedFiles.Num() > 0)
|
||||
{
|
||||
const TArray<FString>& RelativeFiles = GitSourceControlUtils::RelativeFilenames(LockedFiles, InCommand.PathToGitRoot);
|
||||
InCommand.bCommandSuccessful &= GitSourceControlUtils::RunLFSCommand(TEXT("unlock"), InCommand.PathToGitRoot, InCommand.PathToGitBinary, FGitSourceControlModule::GetEmptyStringArray(), RelativeFiles,
|
||||
InCommand.ResultInfo.InfoMessages, InCommand.ResultInfo.ErrorMessages);
|
||||
if (InCommand.bCommandSuccessful)
|
||||
{
|
||||
for (const auto& File : LockedFiles)
|
||||
{
|
||||
FGitLockedFilesCache::RemoveLockedFile(File);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If no files were specified (full revert), refresh all relevant files instead of the specified files (which is an empty list in full revert)
|
||||
// This is required so that files that were "Marked for add" have their status updated after a full revert.
|
||||
TArray<FString> FilesToUpdate = InCommand.Files;
|
||||
if (InCommand.Files.Num() <= 0)
|
||||
{
|
||||
for (const auto& File : MissingFiles) FilesToUpdate.Add(File);
|
||||
for (const auto& File : AllExistingFiles) FilesToUpdate.Add(File);
|
||||
for (const auto& File : OtherThanAddedExistingFiles) FilesToUpdate.Add(File);
|
||||
}
|
||||
|
||||
// now update the status of our files
|
||||
TMap<FString, FGitSourceControlState> UpdatedStates;
|
||||
bool bSuccess = GitSourceControlUtils::RunUpdateStatus(InCommand.PathToGitBinary, InCommand.PathToRepositoryRoot, InCommand.bUsingGitLfsLocking, FilesToUpdate, InCommand.ResultInfo.ErrorMessages, UpdatedStates);
|
||||
if (bSuccess)
|
||||
{
|
||||
GitSourceControlUtils::CollectNewStates(UpdatedStates, States);
|
||||
}
|
||||
GitSourceControlUtils::RemoveRedundantErrors(InCommand, TEXT("' is outside repository"));
|
||||
|
||||
return InCommand.bCommandSuccessful;
|
||||
}
|
||||
|
||||
bool FGitRevertWorker::UpdateStates() const
|
||||
{
|
||||
return GitSourceControlUtils::UpdateCachedStates(States);
|
||||
}
|
||||
|
||||
FName FGitSyncWorker::GetName() const
|
||||
{
|
||||
return "Sync";
|
||||
}
|
||||
|
||||
bool FGitSyncWorker::Execute(FGitSourceControlCommand& InCommand)
|
||||
{
|
||||
TArray<FString> Results;
|
||||
const bool bFetched = GitSourceControlUtils::FetchRemote(InCommand.PathToGitBinary, InCommand.PathToRepositoryRoot, false, InCommand.ResultInfo.InfoMessages, InCommand.ResultInfo.ErrorMessages);
|
||||
if (!bFetched)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
InCommand.bCommandSuccessful = GitSourceControlUtils::PullOrigin(InCommand.PathToGitBinary, InCommand.PathToRepositoryRoot, InCommand.Files, InCommand.Files, Results, InCommand.ResultInfo.ErrorMessages);
|
||||
|
||||
// now update the status of our files
|
||||
TMap<FString, FGitSourceControlState> UpdatedStates;
|
||||
const bool bSuccess = GitSourceControlUtils::RunUpdateStatus(InCommand.PathToGitBinary, InCommand.PathToRepositoryRoot, InCommand.bUsingGitLfsLocking,
|
||||
InCommand.Files, InCommand.ResultInfo.ErrorMessages, UpdatedStates);
|
||||
if (bSuccess)
|
||||
{
|
||||
GitSourceControlUtils::CollectNewStates(UpdatedStates, States);
|
||||
}
|
||||
GitSourceControlUtils::RemoveRedundantErrors(InCommand, TEXT("' is outside repository"));
|
||||
GitSourceControlUtils::GetCommitInfo(InCommand.PathToGitBinary, InCommand.PathToRepositoryRoot, InCommand.CommitId, InCommand.CommitSummary);
|
||||
|
||||
return InCommand.bCommandSuccessful;
|
||||
}
|
||||
|
||||
bool FGitSyncWorker::UpdateStates() const
|
||||
{
|
||||
return GitSourceControlUtils::UpdateCachedStates(States);
|
||||
}
|
||||
|
||||
FName FGitFetch::GetName() const
|
||||
{
|
||||
return "Fetch";
|
||||
}
|
||||
|
||||
FText FGitFetch::GetInProgressString() const
|
||||
{
|
||||
// TODO Configure origin
|
||||
return LOCTEXT("SourceControl_Push", "Fetching from remote origin...");
|
||||
}
|
||||
|
||||
FName FGitFetchWorker::GetName() const
|
||||
{
|
||||
return "Fetch";
|
||||
}
|
||||
|
||||
bool FGitFetchWorker::Execute(FGitSourceControlCommand& InCommand)
|
||||
{
|
||||
InCommand.bCommandSuccessful = GitSourceControlUtils::FetchRemote(InCommand.PathToGitBinary, InCommand.PathToRepositoryRoot, InCommand.bUsingGitLfsLocking,
|
||||
InCommand.ResultInfo.InfoMessages, InCommand.ResultInfo.ErrorMessages);
|
||||
if (!InCommand.bCommandSuccessful)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
check(InCommand.Operation->GetName() == GetName());
|
||||
TSharedRef<FGitFetch, ESPMode::ThreadSafe> Operation = StaticCastSharedRef<FGitFetch>(InCommand.Operation);
|
||||
|
||||
if (Operation->bUpdateStatus)
|
||||
{
|
||||
// Now update the status of all our files
|
||||
const TArray<FString> ProjectDirs = GitSourceControlUtils::GetSourceControlledAssetPaths();
|
||||
|
||||
TMap<FString, FGitSourceControlState> UpdatedStates;
|
||||
InCommand.bCommandSuccessful = GitSourceControlUtils::RunUpdateStatus(InCommand.PathToGitBinary, InCommand.PathToRepositoryRoot, InCommand.bUsingGitLfsLocking,
|
||||
ProjectDirs, InCommand.ResultInfo.ErrorMessages, UpdatedStates);
|
||||
GitSourceControlUtils::RemoveRedundantErrors(InCommand, TEXT("' is outside repository"));
|
||||
if (InCommand.bCommandSuccessful)
|
||||
{
|
||||
GitSourceControlUtils::CollectNewStates(UpdatedStates, States);
|
||||
}
|
||||
}
|
||||
|
||||
return InCommand.bCommandSuccessful;
|
||||
}
|
||||
|
||||
bool FGitFetchWorker::UpdateStates() const
|
||||
{
|
||||
return GitSourceControlUtils::UpdateCachedStates(States);
|
||||
}
|
||||
|
||||
FName FGitUpdateStatusWorker::GetName() const
|
||||
{
|
||||
return "UpdateStatus";
|
||||
}
|
||||
|
||||
bool FGitUpdateStatusWorker::Execute(FGitSourceControlCommand& InCommand)
|
||||
{
|
||||
check(InCommand.Operation->GetName() == GetName());
|
||||
|
||||
TSharedRef<FUpdateStatus, ESPMode::ThreadSafe> Operation = StaticCastSharedRef<FUpdateStatus>(InCommand.Operation);
|
||||
|
||||
if(InCommand.Files.Num() > 0)
|
||||
{
|
||||
TMap<FString, FGitSourceControlState> UpdatedStates;
|
||||
InCommand.bCommandSuccessful = GitSourceControlUtils::RunUpdateStatus(InCommand.PathToGitBinary, InCommand.PathToRepositoryRoot, InCommand.bUsingGitLfsLocking, InCommand.Files, InCommand.ResultInfo.ErrorMessages, UpdatedStates);
|
||||
GitSourceControlUtils::RemoveRedundantErrors(InCommand, TEXT("' is outside repository"));
|
||||
if (InCommand.bCommandSuccessful)
|
||||
{
|
||||
GitSourceControlUtils::CollectNewStates(UpdatedStates, States);
|
||||
if (Operation->ShouldUpdateHistory())
|
||||
{
|
||||
for (const auto& State : UpdatedStates)
|
||||
{
|
||||
const FString& File = State.Key;
|
||||
TGitSourceControlHistory History;
|
||||
|
||||
if (State.Value.IsConflicted())
|
||||
{
|
||||
// In case of a merge conflict, we first need to get the tip of the "remote branch" (MERGE_HEAD)
|
||||
GitSourceControlUtils::RunGetHistory(InCommand.PathToGitBinary, InCommand.PathToRepositoryRoot, File, true,
|
||||
InCommand.ResultInfo.ErrorMessages, History);
|
||||
}
|
||||
// Get the history of the file in the current branch
|
||||
InCommand.bCommandSuccessful &= GitSourceControlUtils::RunGetHistory(InCommand.PathToGitBinary, InCommand.PathToRepositoryRoot, File, false,
|
||||
InCommand.ResultInfo.ErrorMessages, History);
|
||||
Histories.Add(*File, History);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// no path provided: only update the status of assets in Content/ directory and also Config files
|
||||
const TArray<FString> ProjectDirs = GitSourceControlUtils::GetSourceControlledAssetPaths();
|
||||
|
||||
TMap<FString, FGitSourceControlState> UpdatedStates;
|
||||
InCommand.bCommandSuccessful = GitSourceControlUtils::RunUpdateStatus(InCommand.PathToGitBinary, InCommand.PathToRepositoryRoot, InCommand.bUsingGitLfsLocking, ProjectDirs, InCommand.ResultInfo.ErrorMessages, UpdatedStates);
|
||||
GitSourceControlUtils::RemoveRedundantErrors(InCommand, TEXT("' is outside repository"));
|
||||
if (InCommand.bCommandSuccessful)
|
||||
{
|
||||
GitSourceControlUtils::CollectNewStates(UpdatedStates, States);
|
||||
}
|
||||
}
|
||||
|
||||
GitSourceControlUtils::GetCommitInfo(InCommand.PathToGitBinary, InCommand.PathToRepositoryRoot, InCommand.CommitId, InCommand.CommitSummary);
|
||||
|
||||
// don't use the ShouldUpdateModifiedState() hint here as it is specific to Perforce: the above normal Git status has already told us this information (like Git and Mercurial)
|
||||
|
||||
return InCommand.bCommandSuccessful;
|
||||
}
|
||||
|
||||
bool FGitUpdateStatusWorker::UpdateStates() const
|
||||
{
|
||||
bool bUpdated = GitSourceControlUtils::UpdateCachedStates(States);
|
||||
|
||||
FGitSourceControlModule& GitSourceControl = FModuleManager::GetModuleChecked<FGitSourceControlModule>( "GitSourceControl" );
|
||||
FGitSourceControlProvider& Provider = GitSourceControl.GetProvider();
|
||||
const bool bUsingGitLfsLocking = Provider.UsesCheckout();
|
||||
|
||||
// TODO without LFS : Workaround a bug with the Source Control Module not updating file state after a simple "Save" with no "Checkout" (when not using File Lock)
|
||||
const FDateTime Now = bUsingGitLfsLocking ? FDateTime::Now() : FDateTime::MinValue();
|
||||
|
||||
// add history, if any
|
||||
for(const auto& History : Histories)
|
||||
{
|
||||
TSharedRef<FGitSourceControlState, ESPMode::ThreadSafe> State = Provider.GetStateInternal(History.Key);
|
||||
State->History = History.Value;
|
||||
State->TimeStamp = Now;
|
||||
bUpdated = true;
|
||||
}
|
||||
|
||||
return bUpdated;
|
||||
}
|
||||
|
||||
FName FGitCopyWorker::GetName() const
|
||||
{
|
||||
return "Copy";
|
||||
}
|
||||
|
||||
bool FGitCopyWorker::Execute(FGitSourceControlCommand& InCommand)
|
||||
{
|
||||
check(InCommand.Operation->GetName() == GetName());
|
||||
|
||||
// Copy or Move operation on a single file : Git does not need an explicit copy nor move,
|
||||
// but after a Move the Editor create a redirector file with the old asset name that points to the new asset.
|
||||
// The redirector needs to be committed with the new asset to perform a real rename.
|
||||
// => the following is to "MarkForAdd" the redirector, but it still need to be committed by selecting the whole directory and "check-in"
|
||||
InCommand.bCommandSuccessful = GitSourceControlUtils::RunCommand(TEXT("add"), InCommand.PathToGitBinary, InCommand.PathToRepositoryRoot, FGitSourceControlModule::GetEmptyStringArray(), InCommand.Files, InCommand.ResultInfo.InfoMessages, InCommand.ResultInfo.ErrorMessages);
|
||||
|
||||
if (InCommand.bCommandSuccessful)
|
||||
{
|
||||
GitSourceControlUtils::CollectNewStates(InCommand.Files, States, EFileState::Added, ETreeState::Staged);
|
||||
}
|
||||
else
|
||||
{
|
||||
TMap<FString, FGitSourceControlState> UpdatedStates;
|
||||
const bool bSuccess = GitSourceControlUtils::RunUpdateStatus(InCommand.PathToGitBinary, InCommand.PathToRepositoryRoot, InCommand.bUsingGitLfsLocking, InCommand.Files, InCommand.ResultInfo.ErrorMessages, UpdatedStates);
|
||||
GitSourceControlUtils::RemoveRedundantErrors(InCommand, TEXT("' is outside repository"));
|
||||
if (bSuccess)
|
||||
{
|
||||
GitSourceControlUtils::CollectNewStates(UpdatedStates, States);
|
||||
}
|
||||
}
|
||||
|
||||
return InCommand.bCommandSuccessful;
|
||||
}
|
||||
|
||||
bool FGitCopyWorker::UpdateStates() const
|
||||
{
|
||||
return GitSourceControlUtils::UpdateCachedStates(States);
|
||||
}
|
||||
|
||||
FName FGitResolveWorker::GetName() const
|
||||
{
|
||||
return "Resolve";
|
||||
}
|
||||
|
||||
bool FGitResolveWorker::Execute( class FGitSourceControlCommand& InCommand )
|
||||
{
|
||||
check(InCommand.Operation->GetName() == GetName());
|
||||
|
||||
// mark the conflicting files as resolved:
|
||||
TArray<FString> Results;
|
||||
InCommand.bCommandSuccessful = GitSourceControlUtils::RunCommand(TEXT("add"), InCommand.PathToGitBinary, InCommand.PathToRepositoryRoot, FGitSourceControlModule::GetEmptyStringArray(), InCommand.Files, Results, InCommand.ResultInfo.ErrorMessages);
|
||||
|
||||
// now update the status of our files
|
||||
TMap<FString, FGitSourceControlState> UpdatedStates;
|
||||
const bool bSuccess = GitSourceControlUtils::RunUpdateStatus(InCommand.PathToGitBinary, InCommand.PathToRepositoryRoot, InCommand.bUsingGitLfsLocking, InCommand.Files, InCommand.ResultInfo.ErrorMessages, UpdatedStates);
|
||||
GitSourceControlUtils::RemoveRedundantErrors(InCommand, TEXT("' is outside repository"));
|
||||
if (bSuccess)
|
||||
{
|
||||
GitSourceControlUtils::CollectNewStates(UpdatedStates, States);
|
||||
}
|
||||
|
||||
return InCommand.bCommandSuccessful;
|
||||
}
|
||||
|
||||
bool FGitResolveWorker::UpdateStates() const
|
||||
{
|
||||
return GitSourceControlUtils::UpdateCachedStates(States);
|
||||
}
|
||||
|
||||
#if ENGINE_MAJOR_VERSION == 5
|
||||
FName FGitMoveToChangelistWorker::GetName() const
|
||||
{
|
||||
return "MoveToChangelist";
|
||||
}
|
||||
|
||||
bool FGitMoveToChangelistWorker::UpdateStates() const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FGitMoveToChangelistWorker::Execute(FGitSourceControlCommand& InCommand)
|
||||
{
|
||||
check(InCommand.Operation->GetName() == GetName());
|
||||
|
||||
FGitSourceControlChangelist DestChangelist = InCommand.Changelist;
|
||||
bool bResult = false;
|
||||
if(DestChangelist.GetName().Equals(TEXT("Staged")))
|
||||
{
|
||||
bResult = GitSourceControlUtils::RunCommand(TEXT("add"), InCommand.PathToGitBinary, InCommand.PathToRepositoryRoot, FGitSourceControlModule::GetEmptyStringArray(), InCommand.Files, InCommand.ResultInfo.InfoMessages, InCommand.ResultInfo.ErrorMessages);
|
||||
}
|
||||
else if(DestChangelist.GetName().Equals(TEXT("Working")))
|
||||
{
|
||||
TArray<FString> Parameter;
|
||||
Parameter.Add(TEXT("--staged"));
|
||||
bResult = GitSourceControlUtils::RunCommand(TEXT("restore"), InCommand.PathToGitBinary, InCommand.PathToRepositoryRoot, Parameter, InCommand.Files, InCommand.ResultInfo.InfoMessages, InCommand.ResultInfo.ErrorMessages);
|
||||
}
|
||||
|
||||
if (bResult)
|
||||
{
|
||||
TMap<FString, FGitSourceControlState> DummyStates;
|
||||
GitSourceControlUtils::RunUpdateStatus(InCommand.PathToGitBinary, InCommand.PathToRepositoryRoot, InCommand.bUsingGitLfsLocking, InCommand.Files, InCommand.ResultInfo.InfoMessages, DummyStates);
|
||||
}
|
||||
return bResult;
|
||||
}
|
||||
|
||||
FName FGitUpdateStagingWorker::GetName() const
|
||||
{
|
||||
return "UpdateChangelistsStatus";
|
||||
}
|
||||
|
||||
bool FGitUpdateStagingWorker::Execute(FGitSourceControlCommand& InCommand)
|
||||
{
|
||||
return GitSourceControlUtils::UpdateChangelistStateByCommand();
|
||||
}
|
||||
|
||||
bool FGitUpdateStagingWorker::UpdateStates() const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
+212
@@ -0,0 +1,212 @@
|
||||
// Copyright (c) 2014-2020 Sebastien Rombauts (sebastien.rombauts@gmail.com)
|
||||
//
|
||||
// Distributed under the MIT License (MIT) (See accompanying file LICENSE.txt
|
||||
// or copy at http://opensource.org/licenses/MIT)
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "IGitSourceControlWorker.h"
|
||||
#include "GitSourceControlState.h"
|
||||
|
||||
#include "ISourceControlOperation.h"
|
||||
|
||||
/**
|
||||
* Internal operation used to fetch from remote
|
||||
*/
|
||||
class FGitFetch : public ISourceControlOperation
|
||||
{
|
||||
public:
|
||||
// ISourceControlOperation interface
|
||||
virtual FName GetName() const override;
|
||||
|
||||
virtual FText GetInProgressString() const override;
|
||||
|
||||
bool bUpdateStatus = false;
|
||||
};
|
||||
|
||||
/** Called when first activated on a project, and then at project load time.
|
||||
* Look for the root directory of the git repository (where the ".git/" subdirectory is located). */
|
||||
class FGitConnectWorker : public IGitSourceControlWorker
|
||||
{
|
||||
public:
|
||||
virtual ~FGitConnectWorker() {}
|
||||
// IGitSourceControlWorker interface
|
||||
virtual FName GetName() const override;
|
||||
virtual bool Execute(class FGitSourceControlCommand& InCommand) override;
|
||||
virtual bool UpdateStates() const override;
|
||||
|
||||
/** Temporary states for results */
|
||||
TMap<const FString, FGitState> States;
|
||||
};
|
||||
|
||||
/** Lock (check-out) a set of files using Git LFS 2. */
|
||||
class FGitCheckOutWorker : public IGitSourceControlWorker
|
||||
{
|
||||
public:
|
||||
virtual ~FGitCheckOutWorker() {}
|
||||
// IGitSourceControlWorker interface
|
||||
virtual FName GetName() const override;
|
||||
virtual bool Execute(class FGitSourceControlCommand& InCommand) override;
|
||||
virtual bool UpdateStates() const override;
|
||||
|
||||
/** Temporary states for results */
|
||||
TMap<const FString, FGitState> States;
|
||||
};
|
||||
|
||||
/** Commit (check-in) a set of files to the local depot. */
|
||||
class FGitCheckInWorker : public IGitSourceControlWorker
|
||||
{
|
||||
public:
|
||||
virtual ~FGitCheckInWorker() {}
|
||||
// IGitSourceControlWorker interface
|
||||
virtual FName GetName() const override;
|
||||
virtual bool Execute(class FGitSourceControlCommand& InCommand) override;
|
||||
virtual bool UpdateStates() const override;
|
||||
|
||||
/** Temporary states for results */
|
||||
TMap<const FString, FGitState> States;
|
||||
};
|
||||
|
||||
/** Add an untracked file to revision control (so only a subset of the git add command). */
|
||||
class FGitMarkForAddWorker : public IGitSourceControlWorker
|
||||
{
|
||||
public:
|
||||
virtual ~FGitMarkForAddWorker() {}
|
||||
// IGitSourceControlWorker interface
|
||||
virtual FName GetName() const override;
|
||||
virtual bool Execute(class FGitSourceControlCommand& InCommand) override;
|
||||
virtual bool UpdateStates() const override;
|
||||
|
||||
/** Temporary states for results */
|
||||
TMap<const FString, FGitState> States;
|
||||
};
|
||||
|
||||
/** Delete a file and remove it from revision control. */
|
||||
class FGitDeleteWorker : public IGitSourceControlWorker
|
||||
{
|
||||
public:
|
||||
virtual ~FGitDeleteWorker() {}
|
||||
// IGitSourceControlWorker interface
|
||||
virtual FName GetName() const override;
|
||||
virtual bool Execute(class FGitSourceControlCommand& InCommand) override;
|
||||
virtual bool UpdateStates() const override;
|
||||
|
||||
/** Temporary states for results */
|
||||
TMap<const FString, FGitState> States;
|
||||
};
|
||||
|
||||
/** Revert any change to a file to its state on the local depot. */
|
||||
class FGitRevertWorker : public IGitSourceControlWorker
|
||||
{
|
||||
public:
|
||||
virtual ~FGitRevertWorker() {}
|
||||
// IGitSourceControlWorker interface
|
||||
virtual FName GetName() const override;
|
||||
virtual bool Execute(class FGitSourceControlCommand& InCommand) override;
|
||||
virtual bool UpdateStates() const override;
|
||||
|
||||
/** Temporary states for results */
|
||||
TMap<const FString, FGitState> States;
|
||||
};
|
||||
|
||||
/** Git pull --rebase to update branch from its configured remote */
|
||||
class FGitSyncWorker : public IGitSourceControlWorker
|
||||
{
|
||||
public:
|
||||
virtual ~FGitSyncWorker() {}
|
||||
// IGitSourceControlWorker interface
|
||||
virtual FName GetName() const override;
|
||||
virtual bool Execute(class FGitSourceControlCommand& InCommand) override;
|
||||
virtual bool UpdateStates() const override;
|
||||
|
||||
/** Temporary states for results */
|
||||
TMap<const FString, FGitState> States;
|
||||
};
|
||||
|
||||
/** Get revision control status of files on local working copy. */
|
||||
class FGitUpdateStatusWorker : public IGitSourceControlWorker
|
||||
{
|
||||
public:
|
||||
virtual ~FGitUpdateStatusWorker() {}
|
||||
// IGitSourceControlWorker interface
|
||||
virtual FName GetName() const override;
|
||||
virtual bool Execute(class FGitSourceControlCommand& InCommand) override;
|
||||
virtual bool UpdateStates() const override;
|
||||
|
||||
public:
|
||||
/** Temporary states for results */
|
||||
TMap<const FString, FGitState> States;
|
||||
|
||||
/** Map of filenames to history */
|
||||
TMap<FString, TGitSourceControlHistory> Histories;
|
||||
};
|
||||
|
||||
/** Copy or Move operation on a single file */
|
||||
class FGitCopyWorker : public IGitSourceControlWorker
|
||||
{
|
||||
public:
|
||||
virtual ~FGitCopyWorker() {}
|
||||
// IGitSourceControlWorker interface
|
||||
virtual FName GetName() const override;
|
||||
virtual bool Execute(class FGitSourceControlCommand& InCommand) override;
|
||||
virtual bool UpdateStates() const override;
|
||||
|
||||
/** Temporary states for results */
|
||||
TMap<const FString, FGitState> States;
|
||||
};
|
||||
|
||||
/** git add to mark a conflict as resolved */
|
||||
class FGitResolveWorker : public IGitSourceControlWorker
|
||||
{
|
||||
public:
|
||||
virtual ~FGitResolveWorker() {}
|
||||
virtual FName GetName() const override;
|
||||
virtual bool Execute(class FGitSourceControlCommand& InCommand) override;
|
||||
virtual bool UpdateStates() const override;
|
||||
|
||||
/** Temporary states for results */
|
||||
TMap<const FString, FGitState> States;
|
||||
};
|
||||
|
||||
/** Git push to publish branch for its configured remote */
|
||||
class FGitFetchWorker : public IGitSourceControlWorker
|
||||
{
|
||||
public:
|
||||
virtual ~FGitFetchWorker() {}
|
||||
// IGitSourceControlWorker interface
|
||||
virtual FName GetName() const override;
|
||||
virtual bool Execute(class FGitSourceControlCommand& InCommand) override;
|
||||
virtual bool UpdateStates() const override;
|
||||
|
||||
/** Temporary states for results */
|
||||
TMap<const FString, FGitState> States;
|
||||
};
|
||||
|
||||
#if ENGINE_MAJOR_VERSION == 5
|
||||
class FGitMoveToChangelistWorker : public IGitSourceControlWorker
|
||||
{
|
||||
public:
|
||||
virtual ~FGitMoveToChangelistWorker() {}
|
||||
// IGitSourceControlWorker interface
|
||||
virtual FName GetName() const override;
|
||||
virtual bool Execute(class FGitSourceControlCommand& InCommand) override;
|
||||
virtual bool UpdateStates() const override;
|
||||
|
||||
/** Temporary states for results */
|
||||
TMap<const FString, FGitState> States;
|
||||
};
|
||||
|
||||
class FGitUpdateStagingWorker: public IGitSourceControlWorker
|
||||
{
|
||||
public:
|
||||
virtual ~FGitUpdateStagingWorker() {}
|
||||
// IGitSourceControlWorker interface
|
||||
virtual FName GetName() const override;
|
||||
virtual bool Execute(class FGitSourceControlCommand& InCommand) override;
|
||||
virtual bool UpdateStates() const override;
|
||||
|
||||
/** Temporary states for results */
|
||||
TMap<const FString, FGitState> States;
|
||||
};
|
||||
#endif
|
||||
+969
@@ -0,0 +1,969 @@
|
||||
// Copyright (c) 2014-2023 Sebastien Rombauts (sebastien.rombauts@gmail.com)
|
||||
//
|
||||
// Distributed under the MIT License (MIT) (See accompanying file LICENSE.txt
|
||||
// or copy at http://opensource.org/licenses/MIT)
|
||||
|
||||
#include "GitSourceControlProvider.h"
|
||||
|
||||
#include "GitMessageLog.h"
|
||||
#include "GitSourceControlState.h"
|
||||
#include "Misc/Paths.h"
|
||||
#include "Misc/QueuedThreadPool.h"
|
||||
#include "GitSourceControlCommand.h"
|
||||
#include "ISourceControlModule.h"
|
||||
#include "GitSourceControlModule.h"
|
||||
#include "GitSourceControlUtils.h"
|
||||
#include "SGitSourceControlSettings.h"
|
||||
#include "GitSourceControlRunner.h"
|
||||
#include "GitSourceControlChangelistState.h"
|
||||
#include "Logging/MessageLog.h"
|
||||
#include "ScopedSourceControlProgress.h"
|
||||
#include "SourceControlHelpers.h"
|
||||
#include "SourceControlOperations.h"
|
||||
#include "AssetRegistry/AssetRegistryModule.h"
|
||||
#include "Async/Async.h"
|
||||
#include "GenericPlatform/GenericPlatformFile.h"
|
||||
#include "HAL/FileManager.h"
|
||||
#include "Interfaces/IPluginManager.h"
|
||||
#include "Misc/App.h"
|
||||
#include "Misc/EngineVersion.h"
|
||||
#include "Misc/MessageDialog.h"
|
||||
|
||||
#include "Runtime/Launch/Resources/Version.h"
|
||||
#if ENGINE_MAJOR_VERSION == 5
|
||||
#include "UObject/ObjectSaveContext.h"
|
||||
#endif
|
||||
|
||||
#include "UObject/Package.h"
|
||||
|
||||
#define LOCTEXT_NAMESPACE "GitSourceControl"
|
||||
|
||||
static FName ProviderName("Git LFS 2");
|
||||
|
||||
void FGitSourceControlProvider::Init(bool bForceConnection)
|
||||
{
|
||||
// Init() is called multiple times at startup: do not check git each time
|
||||
if(!bGitAvailable)
|
||||
{
|
||||
const TSharedPtr<IPlugin> Plugin = IPluginManager::Get().FindPlugin(TEXT("GitSourceControl"));
|
||||
if(Plugin.IsValid())
|
||||
{
|
||||
UE_LOG(LogSourceControl, Log, TEXT("Git plugin '%s'"), *(Plugin->GetDescriptor().VersionName));
|
||||
}
|
||||
|
||||
CheckGitAvailability();
|
||||
}
|
||||
|
||||
#if ENGINE_MAJOR_VERSION == 5
|
||||
UPackage::PackageSavedWithContextEvent.AddStatic(&GitSourceControlUtils::UpdateFileStagingOnSaved);
|
||||
#endif
|
||||
|
||||
FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(TEXT("AssetRegistry"));
|
||||
AssetRegistryModule.Get().OnAssetRenamed().AddStatic(&GitSourceControlUtils::UpdateStateOnAssetRename);
|
||||
|
||||
// bForceConnection: not used anymore
|
||||
}
|
||||
|
||||
void FGitSourceControlProvider::CheckGitAvailability()
|
||||
{
|
||||
FGitSourceControlModule& GitSourceControl = FGitSourceControlModule::Get();
|
||||
PathToGitBinary = GitSourceControl.AccessSettings().GetBinaryPath();
|
||||
if(PathToGitBinary.IsEmpty())
|
||||
{
|
||||
// Try to find Git binary, and update settings accordingly
|
||||
PathToGitBinary = GitSourceControlUtils::FindGitBinaryPath();
|
||||
if(!PathToGitBinary.IsEmpty())
|
||||
{
|
||||
GitSourceControl.AccessSettings().SetBinaryPath(PathToGitBinary);
|
||||
}
|
||||
}
|
||||
|
||||
if(!PathToGitBinary.IsEmpty())
|
||||
{
|
||||
UE_LOG(LogSourceControl, Log, TEXT("Using '%s'"), *PathToGitBinary);
|
||||
bGitAvailable = true;
|
||||
CheckRepositoryStatus();
|
||||
}
|
||||
else
|
||||
{
|
||||
bGitAvailable = false;
|
||||
}
|
||||
}
|
||||
|
||||
void FGitSourceControlProvider::UpdateSettings()
|
||||
{
|
||||
const FGitSourceControlModule& GitSourceControl = FGitSourceControlModule::Get();
|
||||
bUsingGitLfsLocking = GitSourceControl.AccessSettings().IsUsingGitLfsLocking();
|
||||
LockUser = GitSourceControl.AccessSettings().GetLfsUserName();
|
||||
}
|
||||
|
||||
void FGitSourceControlProvider::CheckRepositoryStatus()
|
||||
{
|
||||
GitSourceControlMenu.Register();
|
||||
|
||||
// Make sure our settings our up to date
|
||||
UpdateSettings();
|
||||
|
||||
// Find the path to the root Git directory (if any, else uses the ProjectDir)
|
||||
const FString PathToProjectDir = FPaths::ConvertRelativePathToFull(FPaths::ProjectDir());
|
||||
PathToRepositoryRoot = PathToProjectDir;
|
||||
if (!GitSourceControlUtils::FindRootDirectory(PathToProjectDir, PathToGitRoot))
|
||||
{
|
||||
UE_LOG(LogSourceControl, Error, TEXT("Failed to find valid Git root directory."));
|
||||
bGitRepositoryFound = false;
|
||||
return;
|
||||
}
|
||||
PathToRepositoryRoot = PathToGitRoot;
|
||||
|
||||
if (!GitSourceControlUtils::CheckGitAvailability(PathToGitBinary, &GitVersion))
|
||||
{
|
||||
UE_LOG(LogSourceControl, Error, TEXT("Failed to find valid Git executable."));
|
||||
bGitRepositoryFound = false;
|
||||
return;
|
||||
}
|
||||
|
||||
TUniqueFunction<void()> InitFunc = [this]()
|
||||
{
|
||||
if (!IsInGameThread())
|
||||
{
|
||||
// Wait until the module interface is valid
|
||||
do
|
||||
{
|
||||
if (FModuleManager::Get().IsModuleLoaded("GitSourceControl"))
|
||||
{
|
||||
break;
|
||||
}
|
||||
FPlatformProcess::Sleep(0.01f);
|
||||
} while (true);
|
||||
}
|
||||
|
||||
// Get user name & email (of the repository, else from the global Git config)
|
||||
GitSourceControlUtils::GetUserConfig(PathToGitBinary, PathToRepositoryRoot, UserName, UserEmail);
|
||||
|
||||
TMap<FString, FGitSourceControlState> States;
|
||||
auto ConditionalRepoInit = [this, &States]()
|
||||
{
|
||||
if (!GitSourceControlUtils::GetBranchName(PathToGitBinary, PathToRepositoryRoot, BranchName))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
GitSourceControlUtils::GetRemoteBranchName(PathToGitBinary, PathToRepositoryRoot, RemoteBranchName);
|
||||
GitSourceControlUtils::GetRemoteUrl(PathToGitBinary, PathToRepositoryRoot, RemoteUrl);
|
||||
const TArray<FString> Files{TEXT("*.uasset"), TEXT("*.umap")};
|
||||
TArray<FString> LockableErrorMessages;
|
||||
if (!GitSourceControlUtils::CheckLFSLockable(PathToGitBinary, PathToRepositoryRoot, Files, LockableErrorMessages))
|
||||
{
|
||||
for (const auto &ErrorMessage : LockableErrorMessages)
|
||||
{
|
||||
UE_LOG(LogSourceControl, Error, TEXT("%s"), *ErrorMessage);
|
||||
}
|
||||
}
|
||||
else if (bUsingGitLfsLocking)
|
||||
{
|
||||
if (!GitSourceControlUtils::IsFileLFSLockable(".umap")
|
||||
|| !GitSourceControlUtils::IsFileLFSLockable(".uasset"))
|
||||
{
|
||||
UE_LOG(LogSourceControl, Error, TEXT("Git LFS Locking is disabled. Files .uasset or .umap are not lockable. Make sure your .gitattributes is setting lockable attributes for .uasset or .umap at the root of the git repository."));
|
||||
bUsingGitLfsLocking = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
UE_LOG(LogSourceControl, Log, TEXT("Git LFS Locking is enabled."));
|
||||
}
|
||||
}
|
||||
|
||||
const TArray<FString> ProjectDirs = GitSourceControlUtils::GetSourceControlledAssetPaths();
|
||||
|
||||
TArray<FString> StatusErrorMessages;
|
||||
if (!GitSourceControlUtils::RunUpdateStatus(PathToGitBinary, PathToRepositoryRoot, bUsingGitLfsLocking, ProjectDirs, StatusErrorMessages, States))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
if (ConditionalRepoInit())
|
||||
{
|
||||
TUniqueFunction<void()> SuccessFunc = [States, this]()
|
||||
{
|
||||
TMap<const FString, FGitState> Results;
|
||||
if (GitSourceControlUtils::CollectNewStates(States, Results))
|
||||
{
|
||||
GitSourceControlUtils::UpdateCachedStates(Results);
|
||||
}
|
||||
Runner = new FGitSourceControlRunner();
|
||||
bGitRepositoryFound = true;
|
||||
};
|
||||
if (FApp::IsUnattended() || IsRunningCommandlet())
|
||||
{
|
||||
SuccessFunc();
|
||||
}
|
||||
else
|
||||
{
|
||||
AsyncTask(ENamedThreads::GameThread, MoveTemp(SuccessFunc));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
TUniqueFunction<void()> ErrorFunc = [States, this]()
|
||||
{
|
||||
UE_LOG(LogSourceControl, Error, TEXT("Failed to update repo on initialization."));
|
||||
bGitRepositoryFound = false;
|
||||
};
|
||||
if (FApp::IsUnattended() || IsRunningCommandlet())
|
||||
{
|
||||
ErrorFunc();
|
||||
}
|
||||
else
|
||||
{
|
||||
AsyncTask(ENamedThreads::GameThread, MoveTemp(ErrorFunc));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (FApp::IsUnattended() || IsRunningCommandlet())
|
||||
{
|
||||
InitFunc();
|
||||
}
|
||||
else
|
||||
{
|
||||
AsyncTask(ENamedThreads::AnyHiPriThreadNormalTask, MoveTemp(InitFunc));
|
||||
}
|
||||
}
|
||||
|
||||
void FGitSourceControlProvider::SetLastErrors(const TArray<FText>& InErrors)
|
||||
{
|
||||
|
||||
FScopeLock Lock(&LastErrorsCriticalSection);
|
||||
LastErrors = InErrors;
|
||||
}
|
||||
|
||||
TArray<FText> FGitSourceControlProvider::GetLastErrors() const
|
||||
{
|
||||
FScopeLock Lock(&LastErrorsCriticalSection);
|
||||
TArray<FText> Result = LastErrors;
|
||||
return Result;
|
||||
}
|
||||
|
||||
int32 FGitSourceControlProvider::GetNumLastErrors() const
|
||||
{
|
||||
FScopeLock Lock(&LastErrorsCriticalSection);
|
||||
return LastErrors.Num();
|
||||
}
|
||||
|
||||
void FGitSourceControlProvider::Close()
|
||||
{
|
||||
// clear the cache
|
||||
StateCache.Empty();
|
||||
// Remove all extensions to the "Revision Control" menu in the Editor Toolbar
|
||||
GitSourceControlMenu.Unregister();
|
||||
|
||||
bGitAvailable = false;
|
||||
bGitRepositoryFound = false;
|
||||
UserName.Empty();
|
||||
UserEmail.Empty();
|
||||
if (Runner)
|
||||
{
|
||||
delete Runner;
|
||||
Runner = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
TSharedRef<FGitSourceControlState, ESPMode::ThreadSafe> FGitSourceControlProvider::GetStateInternal(const FString& Filename)
|
||||
{
|
||||
TSharedRef<FGitSourceControlState, ESPMode::ThreadSafe>* State = StateCache.Find(Filename);
|
||||
if (State != NULL)
|
||||
{
|
||||
// found cached item
|
||||
return (*State);
|
||||
}
|
||||
else
|
||||
{
|
||||
// cache an unknown state for this item
|
||||
TSharedRef<FGitSourceControlState, ESPMode::ThreadSafe> NewState = MakeShareable( new FGitSourceControlState(Filename) );
|
||||
StateCache.Add(Filename, NewState);
|
||||
return NewState;
|
||||
}
|
||||
}
|
||||
|
||||
#if ENGINE_MAJOR_VERSION == 5
|
||||
TSharedRef<FGitSourceControlChangelistState, ESPMode::ThreadSafe> FGitSourceControlProvider::GetStateInternal(const FGitSourceControlChangelist& InChangelist)
|
||||
{
|
||||
TSharedRef<FGitSourceControlChangelistState, ESPMode::ThreadSafe>* State = ChangelistsStateCache.Find(InChangelist);
|
||||
if (State != NULL)
|
||||
{
|
||||
// found cached item
|
||||
return (*State);
|
||||
}
|
||||
else
|
||||
{
|
||||
// cache an unknown state for this item
|
||||
TSharedRef<FGitSourceControlChangelistState, ESPMode::ThreadSafe> NewState = MakeShared<FGitSourceControlChangelistState>(InChangelist);
|
||||
ChangelistsStateCache.Add(InChangelist, NewState);
|
||||
return NewState;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
FText FGitSourceControlProvider::GetStatusText() const
|
||||
{
|
||||
FFormatNamedArguments Args;
|
||||
Args.Add(TEXT("IsAvailable"), (IsEnabled() && IsAvailable()) ? LOCTEXT("Yes", "Yes") : LOCTEXT("No", "No"));
|
||||
Args.Add( TEXT("RepositoryName"), FText::FromString(PathToRepositoryRoot) );
|
||||
Args.Add( TEXT("RemoteUrl"), FText::FromString(RemoteUrl) );
|
||||
Args.Add( TEXT("UserName"), FText::FromString(UserName) );
|
||||
Args.Add( TEXT("UserEmail"), FText::FromString(UserEmail) );
|
||||
Args.Add( TEXT("BranchName"), FText::FromString(BranchName) );
|
||||
Args.Add( TEXT("CommitId"), FText::FromString(CommitId.Left(8)) );
|
||||
Args.Add( TEXT("CommitSummary"), FText::FromString(CommitSummary) );
|
||||
|
||||
FText FormattedError;
|
||||
const TArray<FText>& RecentErrors = GetLastErrors();
|
||||
if (RecentErrors.Num() > 0)
|
||||
{
|
||||
FFormatNamedArguments ErrorArgs;
|
||||
ErrorArgs.Add(TEXT("ErrorText"), RecentErrors[0]);
|
||||
|
||||
FormattedError = FText::Format(LOCTEXT("GitErrorStatusText", "Error: {ErrorText}\n\n"), ErrorArgs);
|
||||
}
|
||||
|
||||
Args.Add(TEXT("ErrorText"), FormattedError);
|
||||
|
||||
return FText::Format( NSLOCTEXT("GitStatusText", "{ErrorText}Enabled: {IsAvailable}", "Local repository: {RepositoryName}\nRemote: {RemoteUrl}\nUser: {UserName}\nE-mail: {UserEmail}\n[{BranchName} {CommitId}] {CommitSummary}"), Args );
|
||||
}
|
||||
|
||||
/** Quick check if revision control is enabled */
|
||||
bool FGitSourceControlProvider::IsEnabled() const
|
||||
{
|
||||
return bGitRepositoryFound;
|
||||
}
|
||||
|
||||
/** Quick check if revision control is available for use (useful for server-based providers) */
|
||||
bool FGitSourceControlProvider::IsAvailable() const
|
||||
{
|
||||
return bGitRepositoryFound;
|
||||
}
|
||||
|
||||
const FName& FGitSourceControlProvider::GetName(void) const
|
||||
{
|
||||
return ProviderName;
|
||||
}
|
||||
|
||||
ECommandResult::Type FGitSourceControlProvider::GetState( const TArray<FString>& InFiles, TArray< TSharedRef<ISourceControlState, ESPMode::ThreadSafe> >& OutState, EStateCacheUsage::Type InStateCacheUsage )
|
||||
{
|
||||
if (!IsEnabled())
|
||||
{
|
||||
return ECommandResult::Failed;
|
||||
}
|
||||
|
||||
if (InStateCacheUsage == EStateCacheUsage::ForceUpdate)
|
||||
{
|
||||
TArray<FString> ForceUpdate;
|
||||
for (FString Path : InFiles)
|
||||
{
|
||||
// Remove the path from the cache, so it's not ignored the next time we force check.
|
||||
// If the file isn't in the cache, force update it now.
|
||||
if (!RemoveFileFromIgnoreForceCache(Path))
|
||||
{
|
||||
ForceUpdate.Add(Path);
|
||||
}
|
||||
}
|
||||
if (ForceUpdate.Num() > 0)
|
||||
{
|
||||
Execute(ISourceControlOperation::Create<FUpdateStatus>(), ForceUpdate);
|
||||
}
|
||||
}
|
||||
|
||||
const TArray<FString>& AbsoluteFiles = SourceControlHelpers::AbsoluteFilenames(InFiles);
|
||||
|
||||
for (TArray<FString>::TConstIterator It(AbsoluteFiles); It; It++)
|
||||
{
|
||||
OutState.Add(GetStateInternal(*It));
|
||||
}
|
||||
|
||||
return ECommandResult::Succeeded;
|
||||
}
|
||||
|
||||
#if ENGINE_MAJOR_VERSION >= 5
|
||||
ECommandResult::Type FGitSourceControlProvider::GetState(const TArray<FSourceControlChangelistRef>& InChangelists, TArray<FSourceControlChangelistStateRef>& OutState, EStateCacheUsage::Type InStateCacheUsage)
|
||||
{
|
||||
if (!IsEnabled())
|
||||
{
|
||||
return ECommandResult::Failed;
|
||||
}
|
||||
|
||||
for (FSourceControlChangelistRef Changelist : InChangelists)
|
||||
{
|
||||
FGitSourceControlChangelistRef GitChangelist = StaticCastSharedRef<FGitSourceControlChangelist>(Changelist);
|
||||
OutState.Add(GetStateInternal(GitChangelist.Get()));
|
||||
}
|
||||
return ECommandResult::Succeeded;
|
||||
}
|
||||
#endif
|
||||
|
||||
TArray<FSourceControlStateRef> FGitSourceControlProvider::GetCachedStateByPredicate(TFunctionRef<bool(const FSourceControlStateRef&)> Predicate) const
|
||||
{
|
||||
TArray<FSourceControlStateRef> Result;
|
||||
for (const auto& CacheItem : StateCache)
|
||||
{
|
||||
const FSourceControlStateRef& State = CacheItem.Value;
|
||||
if (Predicate(State))
|
||||
{
|
||||
Result.Add(State);
|
||||
}
|
||||
}
|
||||
return Result;
|
||||
}
|
||||
|
||||
bool FGitSourceControlProvider::RemoveFileFromCache(const FString& Filename)
|
||||
{
|
||||
return StateCache.Remove(Filename) > 0;
|
||||
}
|
||||
|
||||
bool FGitSourceControlProvider::AddFileToIgnoreForceCache(const FString& Filename)
|
||||
{
|
||||
return IgnoreForceCache.Add(Filename) > 0;
|
||||
}
|
||||
|
||||
bool FGitSourceControlProvider::RemoveFileFromIgnoreForceCache(const FString& Filename)
|
||||
{
|
||||
return IgnoreForceCache.Remove(Filename) > 0;
|
||||
}
|
||||
|
||||
/** Get files in cache */
|
||||
TArray<FString> FGitSourceControlProvider::GetFilesInCache()
|
||||
{
|
||||
TArray<FString> Files;
|
||||
for (const auto& State : StateCache)
|
||||
{
|
||||
Files.Add(State.Key);
|
||||
}
|
||||
return Files;
|
||||
}
|
||||
|
||||
FDelegateHandle FGitSourceControlProvider::RegisterSourceControlStateChanged_Handle( const FSourceControlStateChanged::FDelegate& SourceControlStateChanged )
|
||||
{
|
||||
return OnSourceControlStateChanged.Add( SourceControlStateChanged );
|
||||
}
|
||||
|
||||
void FGitSourceControlProvider::UnregisterSourceControlStateChanged_Handle( FDelegateHandle Handle )
|
||||
{
|
||||
OnSourceControlStateChanged.Remove( Handle );
|
||||
}
|
||||
|
||||
#if ENGINE_MAJOR_VERSION < 5
|
||||
ECommandResult::Type FGitSourceControlProvider::Execute( const FSourceControlOperationRef& InOperation, const TArray<FString>& InFiles, EConcurrency::Type InConcurrency, const FSourceControlOperationComplete& InOperationCompleteDelegate )
|
||||
#else
|
||||
ECommandResult::Type FGitSourceControlProvider::Execute( const FSourceControlOperationRef& InOperation, FSourceControlChangelistPtr InChangelist, const TArray<FString>& InFiles, EConcurrency::Type InConcurrency, const FSourceControlOperationComplete& InOperationCompleteDelegate )
|
||||
#endif
|
||||
{
|
||||
if(!IsEnabled() && !(InOperation->GetName() == "Connect")) // Only Connect operation allowed while not Enabled (Repository found)
|
||||
{
|
||||
InOperationCompleteDelegate.ExecuteIfBound(InOperation, ECommandResult::Failed);
|
||||
return ECommandResult::Failed;
|
||||
}
|
||||
|
||||
TArray<FString> AbsoluteFiles = SourceControlHelpers::AbsoluteFilenames(InFiles);
|
||||
|
||||
// Query to see if we allow this operation
|
||||
TSharedPtr<IGitSourceControlWorker, ESPMode::ThreadSafe> Worker = CreateWorker(InOperation->GetName());
|
||||
if(!Worker.IsValid())
|
||||
{
|
||||
// this operation is unsupported by this revision control provider
|
||||
FFormatNamedArguments Arguments;
|
||||
Arguments.Add( TEXT("OperationName"), FText::FromName(InOperation->GetName()) );
|
||||
Arguments.Add( TEXT("ProviderName"), FText::FromName(GetName()) );
|
||||
FText Message(FText::Format(LOCTEXT("UnsupportedOperation", "Operation '{OperationName}' not supported by revision control provider '{ProviderName}'"), Arguments));
|
||||
|
||||
FTSMessageLog("SourceControl").Error(Message);
|
||||
InOperation->AddErrorMessge(Message);
|
||||
|
||||
InOperationCompleteDelegate.ExecuteIfBound(InOperation, ECommandResult::Failed);
|
||||
return ECommandResult::Failed;
|
||||
}
|
||||
|
||||
FGitSourceControlCommand* Command = new FGitSourceControlCommand(InOperation, Worker.ToSharedRef());
|
||||
Command->UpdateRepositoryRootIfSubmodule(AbsoluteFiles);
|
||||
Command->Files = AbsoluteFiles;
|
||||
Command->OperationCompleteDelegate = InOperationCompleteDelegate;
|
||||
|
||||
#if ENGINE_MAJOR_VERSION == 5
|
||||
TSharedPtr<FGitSourceControlChangelist, ESPMode::ThreadSafe> ChangelistPtr = StaticCastSharedPtr<FGitSourceControlChangelist>(InChangelist);
|
||||
Command->Changelist = ChangelistPtr ? ChangelistPtr.ToSharedRef().Get() : FGitSourceControlChangelist();
|
||||
#endif
|
||||
|
||||
// fire off operation
|
||||
if(InConcurrency == EConcurrency::Synchronous)
|
||||
{
|
||||
Command->bAutoDelete = false;
|
||||
|
||||
#if UE_BUILD_DEBUG
|
||||
UE_LOG(LogSourceControl, Log, TEXT("ExecuteSynchronousCommand(%s)"), *InOperation->GetName().ToString());
|
||||
#endif
|
||||
return ExecuteSynchronousCommand(*Command, InOperation->GetInProgressString(), false);
|
||||
}
|
||||
else
|
||||
{
|
||||
Command->bAutoDelete = true;
|
||||
|
||||
#if UE_BUILD_DEBUG
|
||||
UE_LOG(LogSourceControl, Log, TEXT("IssueAsynchronousCommand(%s)"), *InOperation->GetName().ToString());
|
||||
#endif
|
||||
return IssueCommand(*Command);
|
||||
}
|
||||
}
|
||||
|
||||
#if ENGINE_MAJOR_VERSION < 5
|
||||
bool FGitSourceControlProvider::CanCancelOperation( const FSourceControlOperationRef& InOperation ) const
|
||||
#else
|
||||
bool FGitSourceControlProvider::CanCancelOperation( const FSourceControlOperationRef& InOperation ) const
|
||||
#endif
|
||||
{
|
||||
// TODO: maybe support cancellation again?
|
||||
#if 0
|
||||
for (int32 CommandIndex = 0; CommandIndex < CommandQueue.Num(); ++CommandIndex)
|
||||
{
|
||||
const FGitSourceControlCommand& Command = *CommandQueue[CommandIndex];
|
||||
if (Command.Operation == InOperation)
|
||||
{
|
||||
check(Command.bAutoDelete);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// operation was not in progress!
|
||||
return false;
|
||||
}
|
||||
|
||||
#if ENGINE_MAJOR_VERSION < 5
|
||||
void FGitSourceControlProvider::CancelOperation( const FSourceControlOperationRef& InOperation )
|
||||
#else
|
||||
void FGitSourceControlProvider::CancelOperation( const FSourceControlOperationRef& InOperation )
|
||||
#endif
|
||||
{
|
||||
for (int32 CommandIndex = 0; CommandIndex < CommandQueue.Num(); ++CommandIndex)
|
||||
{
|
||||
FGitSourceControlCommand& Command = *CommandQueue[CommandIndex];
|
||||
if (Command.Operation == InOperation)
|
||||
{
|
||||
check(Command.bAutoDelete);
|
||||
Command.Cancel();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool FGitSourceControlProvider::UsesLocalReadOnlyState() const
|
||||
{
|
||||
return bUsingGitLfsLocking; // Git LFS Lock uses read-only state
|
||||
}
|
||||
|
||||
bool FGitSourceControlProvider::UsesChangelists() const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FGitSourceControlProvider::UsesCheckout() const
|
||||
{
|
||||
return bUsingGitLfsLocking; // Git LFS Lock uses read-only state
|
||||
}
|
||||
|
||||
#if ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION >= 1
|
||||
bool FGitSourceControlProvider::UsesFileRevisions() const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
#if ENGINE_MINOR_VERSION >= 8
|
||||
TOptional<bool> FGitSourceControlProvider::HasChangesToSync() const
|
||||
{
|
||||
return TOptional<bool>();
|
||||
}
|
||||
|
||||
TOptional<bool> FGitSourceControlProvider::HasChangesToCheckIn() const
|
||||
{
|
||||
return TOptional<bool>();
|
||||
}
|
||||
#else
|
||||
TOptional<bool> FGitSourceControlProvider::IsAtLatestRevision() const
|
||||
{
|
||||
return TOptional<bool>();
|
||||
}
|
||||
|
||||
TOptional<int> FGitSourceControlProvider::GetNumLocalChanges() const
|
||||
{
|
||||
return TOptional<int>();
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION >= 2
|
||||
bool FGitSourceControlProvider::AllowsDiffAgainstDepot() const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FGitSourceControlProvider::UsesUncontrolledChangelists() const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FGitSourceControlProvider::UsesSnapshots() const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION >= 8
|
||||
bool FGitSourceControlProvider::UsesSoftRevertOnDelete() const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION >= 3
|
||||
bool FGitSourceControlProvider::CanExecuteOperation(const FSourceControlOperationRef& InOperation) const {
|
||||
return WorkersMap.Find(InOperation->GetName()) != nullptr;
|
||||
}
|
||||
|
||||
TMap<ISourceControlProvider::EStatus, FString> FGitSourceControlProvider::GetStatus() const
|
||||
{
|
||||
TMap<EStatus, FString> Result;
|
||||
Result.Add(EStatus::Enabled, IsEnabled() ? TEXT("Yes") : TEXT("No") );
|
||||
Result.Add(EStatus::Connected, (IsEnabled() && IsAvailable()) ? TEXT("Yes") : TEXT("No") );
|
||||
Result.Add(EStatus::User, UserName);
|
||||
Result.Add(EStatus::Repository, PathToRepositoryRoot);
|
||||
Result.Add(EStatus::Remote, RemoteUrl);
|
||||
Result.Add(EStatus::Branch, BranchName);
|
||||
Result.Add(EStatus::Email, UserEmail);
|
||||
return Result;
|
||||
}
|
||||
#endif
|
||||
|
||||
TSharedPtr<IGitSourceControlWorker, ESPMode::ThreadSafe> FGitSourceControlProvider::CreateWorker(const FName& InOperationName) const
|
||||
{
|
||||
const FGetGitSourceControlWorker* Operation = WorkersMap.Find(InOperationName);
|
||||
if(Operation != nullptr)
|
||||
{
|
||||
return Operation->Execute();
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void FGitSourceControlProvider::RegisterWorker( const FName& InName, const FGetGitSourceControlWorker& InDelegate )
|
||||
{
|
||||
WorkersMap.Add( InName, InDelegate );
|
||||
}
|
||||
|
||||
void FGitSourceControlProvider::OutputCommandMessages(const FGitSourceControlCommand& InCommand) const
|
||||
{
|
||||
FTSMessageLog SourceControlLog("SourceControl");
|
||||
|
||||
for (int32 ErrorIndex = 0; ErrorIndex < InCommand.ResultInfo.ErrorMessages.Num(); ++ErrorIndex)
|
||||
{
|
||||
SourceControlLog.Error(FText::FromString(InCommand.ResultInfo.ErrorMessages[ErrorIndex]));
|
||||
}
|
||||
|
||||
for (int32 InfoIndex = 0; InfoIndex < InCommand.ResultInfo.InfoMessages.Num(); ++InfoIndex)
|
||||
{
|
||||
SourceControlLog.Info(FText::FromString(InCommand.ResultInfo.InfoMessages[InfoIndex]));
|
||||
}
|
||||
}
|
||||
|
||||
void FGitSourceControlProvider::UpdateRepositoryStatus(const class FGitSourceControlCommand& InCommand)
|
||||
{
|
||||
// For all operations running UpdateStatus, get Commit information:
|
||||
if (!InCommand.CommitId.IsEmpty())
|
||||
{
|
||||
CommitId = InCommand.CommitId;
|
||||
CommitSummary = InCommand.CommitSummary;
|
||||
}
|
||||
}
|
||||
|
||||
void FGitSourceControlProvider::Tick()
|
||||
{
|
||||
#if ENGINE_MAJOR_VERSION < 5
|
||||
bool bStatesUpdated = false;
|
||||
#else
|
||||
bool bStatesUpdated = TicksUntilNextForcedUpdate == 1;
|
||||
if( TicksUntilNextForcedUpdate > 0 )
|
||||
{
|
||||
--TicksUntilNextForcedUpdate;
|
||||
}
|
||||
#endif
|
||||
|
||||
for (int32 CommandIndex = 0; CommandIndex < CommandQueue.Num(); ++CommandIndex)
|
||||
{
|
||||
FGitSourceControlCommand& Command = *CommandQueue[CommandIndex];
|
||||
|
||||
if (Command.bExecuteProcessed)
|
||||
{
|
||||
// Remove command from the queue
|
||||
CommandQueue.RemoveAt(CommandIndex);
|
||||
|
||||
if (!Command.IsCanceled())
|
||||
{
|
||||
// Update repository status on UpdateStatus operations
|
||||
UpdateRepositoryStatus(Command);
|
||||
}
|
||||
|
||||
// let command update the states of any files
|
||||
bStatesUpdated |= Command.Worker->UpdateStates();
|
||||
|
||||
// dump any messages to output log
|
||||
OutputCommandMessages(Command);
|
||||
|
||||
// run the completion delegate callback if we have one bound
|
||||
if (!Command.IsCanceled())
|
||||
{
|
||||
Command.ReturnResults();
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
else if (Command.bCancelled)
|
||||
{
|
||||
// If this was a synchronous command, set it free so that it will be deleted automatically
|
||||
// when its (still running) thread finally finishes
|
||||
Command.bAutoDelete = true;
|
||||
|
||||
Command.ReturnResults();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (bStatesUpdated)
|
||||
{
|
||||
OnSourceControlStateChanged.Broadcast();
|
||||
}
|
||||
}
|
||||
|
||||
TArray< TSharedRef<ISourceControlLabel> > FGitSourceControlProvider::GetLabels( const FString& InMatchingSpec ) const
|
||||
{
|
||||
TArray< TSharedRef<ISourceControlLabel> > Tags;
|
||||
|
||||
// NOTE list labels. Called by CrashDebugHelper() (to remote debug Engine crash)
|
||||
// and by SourceControlHelpers::AnnotateFile() (to add source file to report)
|
||||
// Reserved for internal use by Epic Games with Perforce only
|
||||
return Tags;
|
||||
}
|
||||
|
||||
#if ENGINE_MAJOR_VERSION >= 5
|
||||
TArray<FSourceControlChangelistRef> FGitSourceControlProvider::GetChangelists( EStateCacheUsage::Type InStateCacheUsage )
|
||||
{
|
||||
if (!IsEnabled())
|
||||
{
|
||||
return TArray<FSourceControlChangelistRef>();
|
||||
}
|
||||
|
||||
TArray<FSourceControlChangelistRef> Changelists;
|
||||
Algo::Transform(ChangelistsStateCache, Changelists, [](const auto& Pair) { return MakeShared<FGitSourceControlChangelist, ESPMode::ThreadSafe>(Pair.Key); });
|
||||
return Changelists;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if SOURCE_CONTROL_WITH_SLATE
|
||||
TSharedRef<class SWidget> FGitSourceControlProvider::MakeSettingsWidget() const
|
||||
{
|
||||
return SNew(SGitSourceControlSettings);
|
||||
}
|
||||
#endif
|
||||
|
||||
ECommandResult::Type FGitSourceControlProvider::ExecuteSynchronousCommand(FGitSourceControlCommand& InCommand, const FText& Task, bool bSuppressResponseMsg)
|
||||
{
|
||||
ECommandResult::Type Result = ECommandResult::Failed;
|
||||
|
||||
struct Local
|
||||
{
|
||||
static void CancelCommand(FGitSourceControlCommand* InControlCommand)
|
||||
{
|
||||
InControlCommand->Cancel();
|
||||
}
|
||||
};
|
||||
|
||||
FText TaskText = Task;
|
||||
// Display the progress dialog
|
||||
if (bSuppressResponseMsg)
|
||||
{
|
||||
TaskText = FText::GetEmpty();
|
||||
}
|
||||
|
||||
int i = 0;
|
||||
|
||||
// Display the progress dialog if a string was provided
|
||||
{
|
||||
// TODO: support cancellation?
|
||||
//FScopedSourceControlProgress Progress(TaskText, FSimpleDelegate::CreateStatic(&Local::CancelCommand, &InCommand));
|
||||
FScopedSourceControlProgress Progress(TaskText);
|
||||
|
||||
// Issue the command asynchronously...
|
||||
IssueCommand( InCommand );
|
||||
|
||||
// ... then wait for its completion (thus making it synchronous)
|
||||
while (!InCommand.IsCanceled() && CommandQueue.Contains(&InCommand))
|
||||
{
|
||||
// Tick the command queue and update progress.
|
||||
Tick();
|
||||
|
||||
if (i >= 20) {
|
||||
Progress.Tick();
|
||||
i = 0;
|
||||
}
|
||||
i++;
|
||||
|
||||
// Sleep for a bit so we don't busy-wait so much.
|
||||
FPlatformProcess::Sleep(0.01f);
|
||||
}
|
||||
|
||||
if (InCommand.bCancelled)
|
||||
{
|
||||
Result = ECommandResult::Cancelled;
|
||||
}
|
||||
if (InCommand.bCommandSuccessful)
|
||||
{
|
||||
Result = ECommandResult::Succeeded;
|
||||
}
|
||||
else if (!bSuppressResponseMsg)
|
||||
{
|
||||
FMessageDialog::Open( EAppMsgType::Ok, LOCTEXT("Git_ServerUnresponsive", "Git command failed. Please check your connection and try again, or check the output log for more information.") );
|
||||
UE_LOG(LogSourceControl, Error, TEXT("Command '%s' Failed!"), *InCommand.Operation->GetName().ToString());
|
||||
}
|
||||
}
|
||||
|
||||
// Delete the command now if not marked as auto-delete
|
||||
if (!InCommand.bAutoDelete)
|
||||
{
|
||||
delete &InCommand;
|
||||
}
|
||||
|
||||
return Result;
|
||||
}
|
||||
|
||||
ECommandResult::Type FGitSourceControlProvider::IssueCommand(FGitSourceControlCommand& InCommand, const bool bSynchronous)
|
||||
{
|
||||
if (!bSynchronous && GThreadPool != nullptr)
|
||||
{
|
||||
// Queue this to our worker thread(s) for resolving.
|
||||
// When asynchronous, any callback gets called from Tick().
|
||||
GThreadPool->AddQueuedWork(&InCommand);
|
||||
CommandQueue.Add(&InCommand);
|
||||
return ECommandResult::Succeeded;
|
||||
}
|
||||
else
|
||||
{
|
||||
UE_LOG(LogSourceControl, Log, TEXT("There are no threads available to process the revision control command '%s'. Running synchronously."), *InCommand.Operation->GetName().ToString());
|
||||
|
||||
InCommand.bCommandSuccessful = InCommand.DoWork();
|
||||
|
||||
InCommand.Worker->UpdateStates();
|
||||
|
||||
OutputCommandMessages(InCommand);
|
||||
|
||||
// Callback now if present. When asynchronous, this callback gets called from Tick().
|
||||
return InCommand.ReturnResults();
|
||||
}
|
||||
}
|
||||
|
||||
bool FGitSourceControlProvider::QueryStateBranchConfig(const FString& ConfigSrc, const FString& ConfigDest)
|
||||
{
|
||||
// Check similar preconditions to Perforce (valid src and dest),
|
||||
if (ConfigSrc.Len() == 0 || ConfigDest.Len() == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!bGitAvailable || !bGitRepositoryFound)
|
||||
{
|
||||
FTSMessageLog("SourceControl").Error(LOCTEXT("StatusBranchConfigNoConnection", "Unable to retrieve status branch configuration from repo, no connection"));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Otherwise, we can assume that whatever our user is doing to config state branches is properly synced, so just copy.
|
||||
// TODO: maybe don't assume, and use git show instead?
|
||||
IFileManager::Get().Copy(*ConfigDest, *ConfigSrc);
|
||||
return true;
|
||||
}
|
||||
|
||||
void FGitSourceControlProvider::RegisterStateBranches(const TArray<FString>& BranchNames, const FString& ContentRootIn)
|
||||
{
|
||||
StatusBranchNamePatternsInternal = BranchNames;
|
||||
}
|
||||
|
||||
#if ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION >= 7
|
||||
bool FGitSourceControlProvider::GetStateBranchAtIndex(int32 BranchIndex, FString& OutBranchName) const
|
||||
{
|
||||
auto StatusBranchNames = GetStatusBranchNames();
|
||||
|
||||
if (StatusBranchNames.IsValidIndex(BranchIndex))
|
||||
{
|
||||
OutBranchName = StatusBranchNames[BranchIndex];
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
int32 FGitSourceControlProvider::GetStateBranchIndex(const FString& StateBranchName) const
|
||||
{
|
||||
// How do state branches indices work?
|
||||
// Order matters. Lower values are lower in the hierarchy, i.e., changes from higher branches get automatically merged down.
|
||||
// The higher branch is, the stabler it is, and has changes manually promoted up.
|
||||
|
||||
// Check if we are checking the index of the current branch
|
||||
// UE uses FEngineVersion for the current branch name because of UEGames setup, but we want to handle otherwise for Git repos.
|
||||
auto StatusBranchNames = GetStatusBranchNames();
|
||||
if (StateBranchName == FEngineVersion::Current().GetBranch())
|
||||
{
|
||||
const int32 CurrentBranchStatusIndex = StatusBranchNames.IndexOfByKey(BranchName);
|
||||
const bool bCurrentBranchInStatusBranches = CurrentBranchStatusIndex != INDEX_NONE;
|
||||
// If the user's current branch is tracked as a status branch, give the proper index
|
||||
if (bCurrentBranchInStatusBranches)
|
||||
{
|
||||
return CurrentBranchStatusIndex;
|
||||
}
|
||||
// If the current branch is not a status branch, make it the highest branch
|
||||
// This is semantically correct, since if a branch is not marked as a status branch
|
||||
// it merges changes in a similar fashion to the highest status branch, i.e. manually promotes them
|
||||
// based on the user merging those changes in. and these changes always get merged from even the highest point
|
||||
// of the stream. i.e, promoted/stable changes are always up for consumption by this branch.
|
||||
return INT32_MAX;
|
||||
}
|
||||
|
||||
// If we're not checking the current branch, then we don't need to do special handling.
|
||||
// If it is not a status branch, there is no message
|
||||
return StatusBranchNames.IndexOfByKey(StateBranchName);
|
||||
}
|
||||
|
||||
TArray<FString> FGitSourceControlProvider::GetStatusBranchNames() const
|
||||
{
|
||||
TArray<FString> StatusBranches;
|
||||
if (PathToGitBinary.IsEmpty() || PathToRepositoryRoot.IsEmpty())
|
||||
return StatusBranches;
|
||||
|
||||
for (int i = 0; i < StatusBranchNamePatternsInternal.Num(); i++)
|
||||
{
|
||||
TArray<FString> Matches;
|
||||
bool bResult = GitSourceControlUtils::GetRemoteBranchesWildcard(PathToGitBinary, PathToRepositoryRoot, StatusBranchNamePatternsInternal[i], Matches);
|
||||
if (bResult && Matches.Num() > 0)
|
||||
{
|
||||
for (int j = 0; j < Matches.Num(); j++)
|
||||
{
|
||||
StatusBranches.Add(Matches[j].TrimStartAndEnd());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return StatusBranches;
|
||||
}
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
+134
@@ -0,0 +1,134 @@
|
||||
// Copyright (c) 2014-2023 Sebastien Rombauts (sebastien.rombauts@gmail.com)
|
||||
//
|
||||
// Distributed under the MIT License (MIT) (See accompanying file LICENSE.txt
|
||||
// or copy at http://opensource.org/licenses/MIT)
|
||||
|
||||
#include "GitSourceControlRevision.h"
|
||||
|
||||
#include "HAL/FileManager.h"
|
||||
#include "Misc/Paths.h"
|
||||
#include "Modules/ModuleManager.h"
|
||||
#include "GitSourceControlModule.h"
|
||||
#include "GitSourceControlUtils.h"
|
||||
#include "ISourceControlModule.h"
|
||||
|
||||
#define LOCTEXT_NAMESPACE "GitSourceControl"
|
||||
|
||||
#if ENGINE_MAJOR_VERSION >= 5
|
||||
bool FGitSourceControlRevision::Get( FString& InOutFilename, EConcurrency::Type InConcurrency ) const
|
||||
{
|
||||
if (InConcurrency != EConcurrency::Synchronous)
|
||||
{
|
||||
UE_LOG(LogSourceControl, Warning, TEXT("Only EConcurrency::Synchronous is tested/supported for this operation."));
|
||||
}
|
||||
#else
|
||||
bool FGitSourceControlRevision::Get( FString& InOutFilename ) const
|
||||
{
|
||||
#endif
|
||||
const FGitSourceControlModule* GitSourceControl = FGitSourceControlModule::GetThreadSafe();
|
||||
if (!GitSourceControl)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
const FGitSourceControlProvider& Provider = GitSourceControl->GetProvider();
|
||||
const FString PathToGitBinary = Provider.GetGitBinaryPath();
|
||||
FString PathToRepositoryRoot = Provider.GetPathToRepositoryRoot();
|
||||
// the repo root can be customised if in a plugin that has it's own repo
|
||||
if (PathToRepoRoot.Len())
|
||||
{
|
||||
PathToRepositoryRoot = PathToRepoRoot;
|
||||
}
|
||||
|
||||
// if a filename for the temp file wasn't supplied generate a unique-ish one
|
||||
if(InOutFilename.Len() == 0)
|
||||
{
|
||||
// create the diff dir if we don't already have it (Git wont)
|
||||
IFileManager::Get().MakeDirectory(*FPaths::DiffDir(), true);
|
||||
// create a unique temp file name based on the unique commit Id
|
||||
const FString TempFileName = FString::Printf(TEXT("%stemp-%s-%s"), *FPaths::DiffDir(), *CommitId, *FPaths::GetCleanFilename(Filename));
|
||||
InOutFilename = FPaths::ConvertRelativePathToFull(TempFileName);
|
||||
}
|
||||
|
||||
// Diff against the revision
|
||||
const FString Parameter = FString::Printf(TEXT("%s:%s"), *CommitId, *Filename);
|
||||
|
||||
bool bCommandSuccessful;
|
||||
if(FPaths::FileExists(InOutFilename))
|
||||
{
|
||||
bCommandSuccessful = true; // if the temp file already exists, reuse it directly
|
||||
}
|
||||
else
|
||||
{
|
||||
bCommandSuccessful = GitSourceControlUtils::RunDumpToFile(PathToGitBinary, PathToRepositoryRoot, Parameter, InOutFilename);
|
||||
}
|
||||
return bCommandSuccessful;
|
||||
}
|
||||
|
||||
bool FGitSourceControlRevision::GetAnnotated( TArray<FAnnotationLine>& OutLines ) const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool FGitSourceControlRevision::GetAnnotated( FString& InOutFilename ) const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const FString& FGitSourceControlRevision::GetFilename() const
|
||||
{
|
||||
return Filename;
|
||||
}
|
||||
|
||||
int32 FGitSourceControlRevision::GetRevisionNumber() const
|
||||
{
|
||||
return RevisionNumber;
|
||||
}
|
||||
|
||||
const FString& FGitSourceControlRevision::GetRevision() const
|
||||
{
|
||||
return ShortCommitId;
|
||||
}
|
||||
|
||||
const FString& FGitSourceControlRevision::GetDescription() const
|
||||
{
|
||||
return Description;
|
||||
}
|
||||
|
||||
const FString& FGitSourceControlRevision::GetUserName() const
|
||||
{
|
||||
return UserName;
|
||||
}
|
||||
|
||||
const FString& FGitSourceControlRevision::GetClientSpec() const
|
||||
{
|
||||
static FString EmptyString(TEXT(""));
|
||||
return EmptyString;
|
||||
}
|
||||
|
||||
const FString& FGitSourceControlRevision::GetAction() const
|
||||
{
|
||||
return Action;
|
||||
}
|
||||
|
||||
TSharedPtr<class ISourceControlRevision, ESPMode::ThreadSafe> FGitSourceControlRevision::GetBranchSource() const
|
||||
{
|
||||
// if this revision was copied/moved from some other revision
|
||||
return BranchSource;
|
||||
}
|
||||
|
||||
const FDateTime& FGitSourceControlRevision::GetDate() const
|
||||
{
|
||||
return Date;
|
||||
}
|
||||
|
||||
int32 FGitSourceControlRevision::GetCheckInIdentifier() const
|
||||
{
|
||||
return CommitIdNumber;
|
||||
}
|
||||
|
||||
int32 FGitSourceControlRevision::GetFileSize() const
|
||||
{
|
||||
return FileSize;
|
||||
}
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
+95
@@ -0,0 +1,95 @@
|
||||
// Copyright Project Borealis
|
||||
|
||||
#include "GitSourceControlRunner.h"
|
||||
|
||||
#include "GitSourceControlModule.h"
|
||||
#include "GitSourceControlProvider.h"
|
||||
#include "GitSourceControlOperations.h"
|
||||
|
||||
#include "Async/Async.h"
|
||||
|
||||
FGitSourceControlRunner::FGitSourceControlRunner()
|
||||
{
|
||||
bRunThread = true;
|
||||
bRefreshSpawned = false;
|
||||
StopEvent = FPlatformProcess::GetSynchEventFromPool(true);
|
||||
Thread = FRunnableThread::Create(this, TEXT("GitSourceControlRunner"));
|
||||
}
|
||||
|
||||
FGitSourceControlRunner::~FGitSourceControlRunner()
|
||||
{
|
||||
if (Thread)
|
||||
{
|
||||
Thread->Kill();
|
||||
delete StopEvent;
|
||||
delete Thread;
|
||||
}
|
||||
}
|
||||
|
||||
bool FGitSourceControlRunner::Init()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
uint32 FGitSourceControlRunner::Run()
|
||||
{
|
||||
while (bRunThread)
|
||||
{
|
||||
StopEvent->Wait(30000);
|
||||
if (!bRunThread)
|
||||
{
|
||||
break;
|
||||
}
|
||||
// If we're not running the task already
|
||||
if (!bRefreshSpawned)
|
||||
{
|
||||
// Flag that we're running the task already
|
||||
bRefreshSpawned = true;
|
||||
const auto ExecuteResult = Async(EAsyncExecution::TaskGraphMainThread, [this] {
|
||||
FGitSourceControlModule* GitSourceControl = FGitSourceControlModule::GetThreadSafe();
|
||||
// Module not loaded, bail. Usually happens when editor is shutting down, and this prevents a crash from bad timing.
|
||||
if (!GitSourceControl)
|
||||
{
|
||||
return ECommandResult::Failed;
|
||||
}
|
||||
FGitSourceControlProvider& Provider = GitSourceControl->GetProvider();
|
||||
TSharedRef<FGitFetch, ESPMode::ThreadSafe> RefreshOperation = ISourceControlOperation::Create<FGitFetch>();
|
||||
RefreshOperation->bUpdateStatus = true;
|
||||
#if ENGINE_MAJOR_VERSION >= 5
|
||||
const ECommandResult::Type Result = Provider.Execute(RefreshOperation, FSourceControlChangelistPtr(), FGitSourceControlModule::GetEmptyStringArray(), EConcurrency::Asynchronous,
|
||||
FSourceControlOperationComplete::CreateRaw(this, &FGitSourceControlRunner::OnSourceControlOperationComplete));
|
||||
#else
|
||||
const ECommandResult::Type Result = Provider.Execute(RefreshOperation, FGitSourceControlModule::GetEmptyStringArray(), EConcurrency::Asynchronous,
|
||||
FSourceControlOperationComplete::CreateRaw(this, &FGitSourceControlRunner::OnSourceControlOperationComplete));
|
||||
#endif
|
||||
return Result;
|
||||
});
|
||||
// Wait for result if not already completed
|
||||
if (bRefreshSpawned && bRunThread)
|
||||
{
|
||||
// Get the result
|
||||
ECommandResult::Type Result = ExecuteResult.Get();
|
||||
// If still not completed,
|
||||
if (bRefreshSpawned)
|
||||
{
|
||||
// mark failures as done, successes have to complete
|
||||
bRefreshSpawned = Result == ECommandResult::Succeeded;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void FGitSourceControlRunner::Stop()
|
||||
{
|
||||
bRunThread = false;
|
||||
StopEvent->Trigger();
|
||||
}
|
||||
|
||||
void FGitSourceControlRunner::OnSourceControlOperationComplete(const FSourceControlOperationRef& InOperation, ECommandResult::Type InResult)
|
||||
{
|
||||
// Mark task as done
|
||||
bRefreshSpawned = false;
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
// Copyright Project Borealis
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
|
||||
#include "HAL/Runnable.h"
|
||||
|
||||
#include "ISourceControlProvider.h"
|
||||
#include "ISourceControlOperation.h"
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
class FGitSourceControlRunner : public FRunnable
|
||||
{
|
||||
public:
|
||||
FGitSourceControlRunner();
|
||||
|
||||
// Destructor
|
||||
virtual ~FGitSourceControlRunner() override;
|
||||
|
||||
bool Init() override;
|
||||
uint32 Run() override;
|
||||
void Stop() override;
|
||||
void OnSourceControlOperationComplete(const FSourceControlOperationRef& InOperation, ECommandResult::Type InResult);
|
||||
|
||||
|
||||
private:
|
||||
FRunnableThread* Thread;
|
||||
FEvent* StopEvent;
|
||||
bool bRunThread;
|
||||
bool bRefreshSpawned;
|
||||
};
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
// Copyright (c) 2014-2020 Sebastien Rombauts (sebastien.rombauts@gmail.com)
|
||||
//
|
||||
// Distributed under the MIT License (MIT) (See accompanying file LICENSE.txt
|
||||
// or copy at http://opensource.org/licenses/MIT)
|
||||
|
||||
#include "GitSourceControlSettings.h"
|
||||
|
||||
#include "Misc/ConfigCacheIni.h"
|
||||
#include "SourceControlHelpers.h"
|
||||
|
||||
namespace GitSettingsConstants
|
||||
{
|
||||
|
||||
/** The section of the ini file we load our settings from */
|
||||
static const FString SettingsSection = TEXT("GitSourceControl.GitSourceControlSettings");
|
||||
|
||||
}
|
||||
|
||||
const FString & FGitSourceControlSettings::GetBinaryPath() const
|
||||
{
|
||||
FScopeLock ScopeLock(&CriticalSection);
|
||||
return BinaryPath; // Return a copy to be thread-safe
|
||||
}
|
||||
|
||||
bool FGitSourceControlSettings::SetBinaryPath(const FString& InString)
|
||||
{
|
||||
FScopeLock ScopeLock(&CriticalSection);
|
||||
const bool bChanged = (BinaryPath != InString);
|
||||
if(bChanged)
|
||||
{
|
||||
BinaryPath = InString;
|
||||
}
|
||||
return bChanged;
|
||||
}
|
||||
|
||||
/** Tell if using the Git LFS file Locking workflow */
|
||||
bool FGitSourceControlSettings::IsUsingGitLfsLocking() const
|
||||
{
|
||||
FScopeLock ScopeLock(&CriticalSection);
|
||||
return bUsingGitLfsLocking;
|
||||
}
|
||||
|
||||
/** Configure the usage of Git LFS file Locking workflow */
|
||||
bool FGitSourceControlSettings::SetUsingGitLfsLocking(const bool InUsingGitLfsLocking)
|
||||
{
|
||||
FScopeLock ScopeLock(&CriticalSection);
|
||||
const bool bChanged = (bUsingGitLfsLocking != InUsingGitLfsLocking);
|
||||
bUsingGitLfsLocking = InUsingGitLfsLocking;
|
||||
return bChanged;
|
||||
}
|
||||
|
||||
const FString FGitSourceControlSettings::GetLfsUserName() const
|
||||
{
|
||||
FScopeLock ScopeLock(&CriticalSection);
|
||||
return LfsUserName; // Return a copy to be thread-safe
|
||||
}
|
||||
|
||||
bool FGitSourceControlSettings::SetLfsUserName(const FString& InString)
|
||||
{
|
||||
FScopeLock ScopeLock(&CriticalSection);
|
||||
const bool bChanged = (LfsUserName != InString);
|
||||
if (bChanged)
|
||||
{
|
||||
LfsUserName = InString;
|
||||
}
|
||||
return bChanged;
|
||||
}
|
||||
|
||||
// This is called at startup nearly before anything else in our module: BinaryPath will then be used by the provider
|
||||
void FGitSourceControlSettings::LoadSettings()
|
||||
{
|
||||
FScopeLock ScopeLock(&CriticalSection);
|
||||
const FString& IniFile = SourceControlHelpers::GetSettingsIni();
|
||||
GConfig->GetString(*GitSettingsConstants::SettingsSection, TEXT("BinaryPath"), BinaryPath, IniFile);
|
||||
GConfig->GetBool(*GitSettingsConstants::SettingsSection, TEXT("UsingGitLfsLocking"), bUsingGitLfsLocking, IniFile);
|
||||
GConfig->GetString(*GitSettingsConstants::SettingsSection, TEXT("LfsUserName"), LfsUserName, IniFile);
|
||||
}
|
||||
|
||||
void FGitSourceControlSettings::SaveSettings() const
|
||||
{
|
||||
FScopeLock ScopeLock(&CriticalSection);
|
||||
const FString& IniFile = SourceControlHelpers::GetSettingsIni();
|
||||
GConfig->SetString(*GitSettingsConstants::SettingsSection, TEXT("BinaryPath"), *BinaryPath, IniFile);
|
||||
GConfig->SetBool(*GitSettingsConstants::SettingsSection, TEXT("UsingGitLfsLocking"), bUsingGitLfsLocking, IniFile);
|
||||
GConfig->SetString(*GitSettingsConstants::SettingsSection, TEXT("LfsUserName"), *LfsUserName, IniFile);
|
||||
GConfig->Flush(false, IniFile);
|
||||
}
|
||||
+461
@@ -0,0 +1,461 @@
|
||||
// Copyright (c) 2014-2020 Sebastien Rombauts (sebastien.rombauts@gmail.com)
|
||||
//
|
||||
// Distributed under the MIT License (MIT) (See accompanying file LICENSE.txt
|
||||
// or copy at http://opensource.org/licenses/MIT)
|
||||
|
||||
#include "GitSourceControlState.h"
|
||||
|
||||
#if ENGINE_MAJOR_VERSION >= 5
|
||||
#include "Textures/SlateIcon.h"
|
||||
#if ENGINE_MINOR_VERSION >= 2
|
||||
#include "RevisionControlStyle/RevisionControlStyle.h"
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#define LOCTEXT_NAMESPACE "GitSourceControl.State"
|
||||
|
||||
int32 FGitSourceControlState::GetHistorySize() const
|
||||
{
|
||||
return History.Num();
|
||||
}
|
||||
|
||||
TSharedPtr<class ISourceControlRevision, ESPMode::ThreadSafe> FGitSourceControlState::GetHistoryItem( int32 HistoryIndex ) const
|
||||
{
|
||||
check(History.IsValidIndex(HistoryIndex));
|
||||
return History[HistoryIndex];
|
||||
}
|
||||
|
||||
TSharedPtr<class ISourceControlRevision, ESPMode::ThreadSafe> FGitSourceControlState::FindHistoryRevision(int32 RevisionNumber) const
|
||||
{
|
||||
for (auto Iter(History.CreateConstIterator()); Iter; Iter++)
|
||||
{
|
||||
if ((*Iter)->GetRevisionNumber() == RevisionNumber)
|
||||
{
|
||||
return *Iter;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
TSharedPtr<class ISourceControlRevision, ESPMode::ThreadSafe> FGitSourceControlState::FindHistoryRevision(const FString& InRevision) const
|
||||
{
|
||||
for (const auto& Revision : History)
|
||||
{
|
||||
if (Revision->GetRevision() == InRevision)
|
||||
{
|
||||
return Revision;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
#if ENGINE_MAJOR_VERSION < 5 || ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION < 3
|
||||
TSharedPtr<class ISourceControlRevision, ESPMode::ThreadSafe> FGitSourceControlState::GetBaseRevForMerge() const
|
||||
{
|
||||
for(const auto& Revision : History)
|
||||
{
|
||||
// look for the the SHA1 id of the file, not the commit id (revision)
|
||||
if (Revision->FileHash == PendingMergeBaseFileHash)
|
||||
{
|
||||
return Revision;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION >= 2
|
||||
TSharedPtr<class ISourceControlRevision, ESPMode::ThreadSafe> FGitSourceControlState::GetCurrentRevision() const
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION >= 3
|
||||
ISourceControlState::FResolveInfo FGitSourceControlState::GetResolveInfo() const
|
||||
{
|
||||
return PendingResolveInfo;
|
||||
}
|
||||
#endif
|
||||
|
||||
// @todo add Slate icons for git specific states (NotAtHead vs Conflicted...)
|
||||
|
||||
#if ENGINE_MAJOR_VERSION < 5
|
||||
#define GET_ICON_RETURN( NAME ) FName( "ContentBrowser.SCC_" #NAME )
|
||||
FName FGitSourceControlState::GetIconName() const
|
||||
{
|
||||
#else
|
||||
#if ENGINE_MINOR_VERSION >= 2
|
||||
#define GET_ICON_RETURN( NAME ) FSlateIcon(FRevisionControlStyleManager::GetStyleSetName(), "RevisionControl." #NAME )
|
||||
#else
|
||||
#define GET_ICON_RETURN( NAME ) FSlateIcon(FAppStyle::GetAppStyleSetName(), "Perforce." #NAME )
|
||||
#endif
|
||||
FSlateIcon FGitSourceControlState::GetIcon() const
|
||||
{
|
||||
#endif
|
||||
switch (GetGitState())
|
||||
{
|
||||
case EGitState::NotAtHead:
|
||||
return GET_ICON_RETURN(NotAtHeadRevision);
|
||||
case EGitState::LockedOther:
|
||||
return GET_ICON_RETURN(CheckedOutByOtherUser);
|
||||
case EGitState::NotLatest:
|
||||
return GET_ICON_RETURN(ModifiedOtherBranch);
|
||||
case EGitState::Unmerged:
|
||||
return GET_ICON_RETURN(Branched);
|
||||
case EGitState::Added:
|
||||
return GET_ICON_RETURN(OpenForAdd);
|
||||
case EGitState::Untracked:
|
||||
return GET_ICON_RETURN(NotInDepot);
|
||||
case EGitState::Deleted:
|
||||
return GET_ICON_RETURN(MarkedForDelete);
|
||||
case EGitState::Modified:
|
||||
case EGitState::CheckedOut:
|
||||
return GET_ICON_RETURN(CheckedOut);
|
||||
case EGitState::Ignored:
|
||||
return GET_ICON_RETURN(NotInDepot);
|
||||
default:
|
||||
#if ENGINE_MAJOR_VERSION < 5
|
||||
return NAME_None;
|
||||
#else
|
||||
return FSlateIcon();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
#if ENGINE_MAJOR_VERSION < 5
|
||||
FName FGitSourceControlState::GetSmallIconName() const
|
||||
{
|
||||
switch (GetGitState()) {
|
||||
case EGitState::NotAtHead:
|
||||
return FName("ContentBrowser.SCC_NotAtHeadRevision_Small");
|
||||
case EGitState::LockedOther:
|
||||
return FName("ContentBrowser.SCC_CheckedOutByOtherUser_Small");
|
||||
case EGitState::NotLatest:
|
||||
return FName("ContentBrowser.SCC_ModifiedOtherBranch_Small");
|
||||
case EGitState::Unmerged:
|
||||
return FName("ContentBrowser.SCC_Branched_Small");
|
||||
case EGitState::Added:
|
||||
return FName("ContentBrowser.SCC_OpenForAdd_Small");
|
||||
case EGitState::Untracked:
|
||||
return FName("ContentBrowser.SCC_NotInDepot_Small");
|
||||
case EGitState::Deleted:
|
||||
return FName("ContentBrowser.SCC_MarkedForDelete_Small");
|
||||
case EGitState::Modified:
|
||||
case EGitState::CheckedOut:
|
||||
return FName("ContentBrowser.SCC_CheckedOut_Small");
|
||||
case EGitState::Ignored:
|
||||
return FName("ContentBrowser.SCC_NotInDepot_Small");
|
||||
default:
|
||||
return NAME_None;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
FText FGitSourceControlState::GetDisplayName() const
|
||||
{
|
||||
switch (GetGitState())
|
||||
{
|
||||
case EGitState::NotAtHead:
|
||||
return LOCTEXT("NotCurrent", "Not current");
|
||||
case EGitState::LockedOther:
|
||||
return FText::Format(LOCTEXT("CheckedOutOther", "Checked out by: {0}"), FText::FromString(State.LockUser));
|
||||
case EGitState::NotLatest:
|
||||
return FText::Format(LOCTEXT("ModifiedOtherBranch", "Modified in branch: {0}"), FText::FromString(State.HeadBranch));
|
||||
case EGitState::Unmerged:
|
||||
return LOCTEXT("Conflicted", "Conflicted");
|
||||
case EGitState::Added:
|
||||
return LOCTEXT("OpenedForAdd", "Opened for add");
|
||||
case EGitState::Untracked:
|
||||
return LOCTEXT("NotControlled", "Not Under Revision Control");
|
||||
case EGitState::Deleted:
|
||||
return LOCTEXT("MarkedForDelete", "Marked for delete");
|
||||
case EGitState::Modified:
|
||||
case EGitState::CheckedOut:
|
||||
return LOCTEXT("CheckedOut", "Checked out");
|
||||
case EGitState::Ignored:
|
||||
return LOCTEXT("Ignore", "Ignore");
|
||||
case EGitState::Lockable:
|
||||
return LOCTEXT("ReadOnly", "Read only");
|
||||
case EGitState::None:
|
||||
return LOCTEXT("Unknown", "Unknown");
|
||||
default:
|
||||
return FText();
|
||||
}
|
||||
}
|
||||
|
||||
FText FGitSourceControlState::GetDisplayTooltip() const
|
||||
{
|
||||
switch (GetGitState())
|
||||
{
|
||||
case EGitState::NotAtHead:
|
||||
return LOCTEXT("NotCurrent_Tooltip", "The file(s) are not at the head revision");
|
||||
case EGitState::LockedOther:
|
||||
return FText::Format(LOCTEXT("CheckedOutOther_Tooltip", "Checked out by: {0}"), FText::FromString(State.LockUser));
|
||||
case EGitState::NotLatest:
|
||||
return FText::Format(LOCTEXT("ModifiedOtherBranch_Tooltip", "Modified in branch: {0} CL:{1} ({2})"), FText::FromString(State.HeadBranch), FText::FromString(HeadCommit), FText::FromString(HeadAction));
|
||||
case EGitState::Unmerged:
|
||||
return LOCTEXT("ContentsConflict_Tooltip", "The contents of the item conflict with updates received from the repository.");
|
||||
case EGitState::Added:
|
||||
return LOCTEXT("OpenedForAdd_Tooltip", "The file(s) are opened for add");
|
||||
case EGitState::Untracked:
|
||||
return LOCTEXT("NotControlled_Tooltip", "Item is not under revision control.");
|
||||
case EGitState::Deleted:
|
||||
return LOCTEXT("MarkedForDelete_Tooltip", "The file(s) are marked for delete");
|
||||
case EGitState::Modified:
|
||||
case EGitState::CheckedOut:
|
||||
return LOCTEXT("CheckedOut_Tooltip", "The file(s) are checked out");
|
||||
case EGitState::Ignored:
|
||||
return LOCTEXT("Ignored_Tooltip", "Item is being ignored.");
|
||||
case EGitState::Lockable:
|
||||
return LOCTEXT("ReadOnly_Tooltip", "The file(s) are marked locally as read-only");
|
||||
case EGitState::None:
|
||||
return LOCTEXT("Unknown_Tooltip", "Unknown revision control state");
|
||||
default:
|
||||
return FText();
|
||||
}
|
||||
}
|
||||
|
||||
const FString& FGitSourceControlState::GetFilename() const
|
||||
{
|
||||
return LocalFilename;
|
||||
}
|
||||
|
||||
const FDateTime& FGitSourceControlState::GetTimeStamp() const
|
||||
{
|
||||
return TimeStamp;
|
||||
}
|
||||
|
||||
// Deleted and Missing assets cannot appear in the Content Browser, but they do in the Submit files to Revision Control window!
|
||||
bool FGitSourceControlState::CanCheckIn() const
|
||||
{
|
||||
// We can check in if this is new content
|
||||
if (IsAdded())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// Cannot check back in if conflicted or not current
|
||||
if (!IsCurrent() || IsConflicted())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// We can check back in if we're locked.
|
||||
if (State.LockState == ELockState::Locked)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// We can check in any file that has been modified, unless someone else locked it.
|
||||
if (State.LockState != ELockState::LockedOther && IsModified() && IsSourceControlled())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool FGitSourceControlState::CanCheckout() const
|
||||
{
|
||||
if (State.LockState == ELockState::Unlockable)
|
||||
{
|
||||
// Everything is already available for check in (checked out).
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
// We don't want to allow checkout if the file is out-of-date, as modifying an out-of-date binary file will most likely result in a merge conflict
|
||||
return State.LockState == ELockState::NotLocked && IsCurrent();
|
||||
}
|
||||
}
|
||||
|
||||
bool FGitSourceControlState::IsCheckedOut() const
|
||||
{
|
||||
if (State.LockState == ELockState::Unlockable)
|
||||
{
|
||||
return IsSourceControlled(); // TODO: try modified instead? might block editing the file with a holding pattern
|
||||
}
|
||||
else
|
||||
{
|
||||
// We check for modified here too, because sometimes you don't lock a file but still want to push it. CanCheckout still true, so that you can lock it later...
|
||||
return State.LockState == ELockState::Locked || (State.FileState == EFileState::Modified && State.LockState != ELockState::LockedOther);
|
||||
}
|
||||
}
|
||||
|
||||
bool FGitSourceControlState::IsCheckedOutOther(FString* Who) const
|
||||
{
|
||||
if (Who != nullptr)
|
||||
{
|
||||
// The packages dialog uses our lock user regardless if it was locked by other or us.
|
||||
// But, if there is no lock user, it shows information about modification in other branches, which is important.
|
||||
// So, only show our own lock user if it hasn't been modified in another branch.
|
||||
// This is a very, very rare state (maybe impossible), but one that should be displayed properly.
|
||||
if (State.LockState == ELockState::LockedOther || (State.LockState == ELockState::Locked && !IsModifiedInOtherBranch()))
|
||||
{
|
||||
*Who = State.LockUser;
|
||||
}
|
||||
}
|
||||
return State.LockState == ELockState::LockedOther;
|
||||
}
|
||||
|
||||
bool FGitSourceControlState::IsCheckedOutInOtherBranch(const FString& CurrentBranch) const
|
||||
{
|
||||
// You can't check out separately per branch
|
||||
return false;
|
||||
}
|
||||
|
||||
bool FGitSourceControlState::IsModifiedInOtherBranch(const FString& CurrentBranch) const
|
||||
{
|
||||
return State.RemoteState == ERemoteState::NotLatest;
|
||||
}
|
||||
|
||||
bool FGitSourceControlState::GetOtherBranchHeadModification(FString& HeadBranchOut, FString& ActionOut, int32& HeadChangeListOut) const
|
||||
{
|
||||
if (!IsModifiedInOtherBranch())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
HeadBranchOut = State.HeadBranch;
|
||||
ActionOut = HeadAction; // TODO: from ERemoteState
|
||||
HeadChangeListOut = 0; // TODO: get head commit
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FGitSourceControlState::IsCurrent() const
|
||||
{
|
||||
return State.RemoteState != ERemoteState::NotAtHead && State.RemoteState != ERemoteState::NotLatest;
|
||||
}
|
||||
|
||||
bool FGitSourceControlState::IsSourceControlled() const
|
||||
{
|
||||
return State.TreeState != ETreeState::Untracked && State.TreeState != ETreeState::Ignored && State.TreeState != ETreeState::NotInRepo;
|
||||
}
|
||||
|
||||
bool FGitSourceControlState::IsAdded() const
|
||||
{
|
||||
// Added is when a file was untracked and is now added.
|
||||
return State.FileState == EFileState::Added;
|
||||
}
|
||||
|
||||
bool FGitSourceControlState::IsDeleted() const
|
||||
{
|
||||
return State.FileState == EFileState::Deleted;
|
||||
}
|
||||
|
||||
bool FGitSourceControlState::IsIgnored() const
|
||||
{
|
||||
return State.TreeState == ETreeState::Ignored;
|
||||
}
|
||||
|
||||
bool FGitSourceControlState::CanEdit() const
|
||||
{
|
||||
// Perforce does not care about it being current
|
||||
return IsCheckedOut() || IsAdded();
|
||||
}
|
||||
|
||||
bool FGitSourceControlState::CanDelete() const
|
||||
{
|
||||
// Perforce enforces that a deleted file must be current.
|
||||
if (!IsCurrent())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
// If someone else hasn't checked it out, we can delete revision controlled files.
|
||||
return !IsCheckedOutOther() && IsSourceControlled();
|
||||
}
|
||||
|
||||
bool FGitSourceControlState::IsUnknown() const
|
||||
{
|
||||
return State.FileState == EFileState::Unknown && State.TreeState == ETreeState::NotInRepo;
|
||||
}
|
||||
|
||||
bool FGitSourceControlState::IsModified() const
|
||||
{
|
||||
return State.TreeState == ETreeState::Working ||
|
||||
State.TreeState == ETreeState::Staged;
|
||||
}
|
||||
|
||||
|
||||
bool FGitSourceControlState::CanAdd() const
|
||||
{
|
||||
return State.TreeState == ETreeState::Untracked;
|
||||
}
|
||||
|
||||
bool FGitSourceControlState::IsConflicted() const
|
||||
{
|
||||
return State.FileState == EFileState::Unmerged;
|
||||
}
|
||||
|
||||
bool FGitSourceControlState::CanRevert() const
|
||||
{
|
||||
// Can revert the file state if we modified, even if it was locked by someone else.
|
||||
// Useful for when someone locked a file, and you just wanna play around with it locallly, and then revert it.
|
||||
return CanCheckIn() || IsModified();
|
||||
}
|
||||
|
||||
EGitState::Type FGitSourceControlState::GetGitState() const
|
||||
{
|
||||
// No matter what, we must pull from remote, even if we have locked or if we have modified.
|
||||
switch (State.RemoteState)
|
||||
{
|
||||
case ERemoteState::NotAtHead:
|
||||
return EGitState::NotAtHead;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
/** Someone else locked this file across branches. */
|
||||
// We cannot push under any circumstance, if someone else has locked.
|
||||
if (State.LockState == ELockState::LockedOther)
|
||||
{
|
||||
return EGitState::LockedOther;
|
||||
}
|
||||
|
||||
// We could theoretically push, but we shouldn't.
|
||||
if (State.RemoteState == ERemoteState::NotLatest)
|
||||
{
|
||||
return EGitState::NotLatest;
|
||||
}
|
||||
|
||||
switch (State.FileState)
|
||||
{
|
||||
case EFileState::Unmerged:
|
||||
return EGitState::Unmerged;
|
||||
case EFileState::Added:
|
||||
return EGitState::Added;
|
||||
case EFileState::Deleted:
|
||||
return EGitState::Deleted;
|
||||
case EFileState::Modified:
|
||||
return EGitState::Modified;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (State.TreeState == ETreeState::Untracked)
|
||||
{
|
||||
return EGitState::Untracked;
|
||||
}
|
||||
|
||||
if (State.LockState == ELockState::Locked)
|
||||
{
|
||||
return EGitState::CheckedOut;
|
||||
}
|
||||
|
||||
if (IsSourceControlled())
|
||||
{
|
||||
if (CanCheckout())
|
||||
{
|
||||
return EGitState::Lockable;
|
||||
}
|
||||
return EGitState::Unmodified;
|
||||
}
|
||||
|
||||
return EGitState::None;
|
||||
}
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
+2565
File diff suppressed because it is too large
Load Diff
+984
@@ -0,0 +1,984 @@
|
||||
// Copyright (c) 2014-2020 Sebastien Rombauts (sebastien.rombauts@gmail.com)
|
||||
//
|
||||
// Distributed under the MIT License (MIT) (See accompanying file LICENSE.txt
|
||||
// or copy at http://opensource.org/licenses/MIT)
|
||||
|
||||
#include "SGitSourceControlSettings.h"
|
||||
|
||||
#include "Runtime/Launch/Resources/Version.h"
|
||||
#include "Fonts/SlateFontInfo.h"
|
||||
#include "Misc/App.h"
|
||||
#include "Misc/FileHelper.h"
|
||||
#include "Misc/Paths.h"
|
||||
#include "Modules/ModuleManager.h"
|
||||
#include "Widgets/SBoxPanel.h"
|
||||
#include "Widgets/Text/STextBlock.h"
|
||||
#include "Widgets/Input/SButton.h"
|
||||
#include "Widgets/Input/SEditableTextBox.h"
|
||||
#include "Widgets/Input/SFilePathPicker.h"
|
||||
#include "Widgets/Input/SMultiLineEditableTextBox.h"
|
||||
#include "Widgets/Layout/SSeparator.h"
|
||||
#include "Widgets/Notifications/SNotificationList.h"
|
||||
#include "Framework/Notifications/NotificationManager.h"
|
||||
#include "EditorDirectories.h"
|
||||
#if ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION >= 1
|
||||
#else
|
||||
#include "EditorStyleSet.h"
|
||||
#endif
|
||||
#include "SourceControlOperations.h"
|
||||
#include "GitSourceControlModule.h"
|
||||
#include "GitSourceControlUtils.h"
|
||||
|
||||
|
||||
#define LOCTEXT_NAMESPACE "SGitSourceControlSettings"
|
||||
|
||||
void SGitSourceControlSettings::Construct(const FArguments& InArgs)
|
||||
{
|
||||
bAutoCreateGitIgnore = true;
|
||||
bAutoCreateReadme = true;
|
||||
bAutoCreateGitAttributes = false;
|
||||
bAutoInitialCommit = true;
|
||||
|
||||
InitialCommitMessage = LOCTEXT("InitialCommitMessage", "Initial commit");
|
||||
ReadmeContent = FText::FromString(FString(TEXT("# ")) + FApp::GetProjectName() + "\n\nDeveloped with Unreal Engine\n");
|
||||
|
||||
ConstructBasedOnEngineVersion( );
|
||||
}
|
||||
|
||||
#if ENGINE_MAJOR_VERSION < 5
|
||||
void SGitSourceControlSettings::ConstructBasedOnEngineVersion( )
|
||||
{
|
||||
const FText FileFilterType = NSLOCTEXT("GitSourceControl", "Executables", "Executables");
|
||||
#if PLATFORM_WINDOWS
|
||||
const FString FileFilterText = FString::Printf(TEXT("%s (*.exe)|*.exe"), *FileFilterType.ToString());
|
||||
#else
|
||||
const FString FileFilterText = FString::Printf(TEXT("%s"), *FileFilterType.ToString());
|
||||
#endif
|
||||
|
||||
const FSlateFontInfo Font = FEditorStyle::GetFontStyle(TEXT("SourceControl.LoginWindow.Font"));
|
||||
ChildSlot
|
||||
[
|
||||
SNew(SBorder)
|
||||
.BorderImage( FEditorStyle::GetBrush("DetailsView.CategoryBottom"))
|
||||
.Padding(FMargin(0.0f, 3.0f, 0.0f, 0.0f))
|
||||
[
|
||||
SNew(SVerticalBox)
|
||||
// Path to the Git command line executable
|
||||
+SVerticalBox::Slot()
|
||||
.AutoHeight()
|
||||
.Padding(2.0f)
|
||||
.VAlign(VAlign_Center)
|
||||
[
|
||||
SNew(SHorizontalBox)
|
||||
.ToolTipText(LOCTEXT("BinaryPathLabel_Tooltip", "Path to Git binary"))
|
||||
+SHorizontalBox::Slot()
|
||||
.FillWidth(1.0f)
|
||||
[
|
||||
SNew(STextBlock)
|
||||
.Text(LOCTEXT("BinaryPathLabel", "Git Path"))
|
||||
.Font(Font)
|
||||
]
|
||||
+SHorizontalBox::Slot()
|
||||
.FillWidth(2.0f)
|
||||
[
|
||||
SNew(SFilePathPicker)
|
||||
.BrowseButtonImage(FEditorStyle::GetBrush("PropertyWindow.Button_Ellipsis"))
|
||||
.BrowseButtonStyle(FEditorStyle::Get(), "HoverHintOnly")
|
||||
.BrowseDirectory(FEditorDirectories::Get().GetLastDirectory(ELastDirectory::GENERIC_OPEN))
|
||||
.BrowseTitle(LOCTEXT("BinaryPathBrowseTitle", "File picker..."))
|
||||
.FilePath(this, &SGitSourceControlSettings::GetBinaryPathString)
|
||||
.FileTypeFilter(FileFilterText)
|
||||
.OnPathPicked(this, &SGitSourceControlSettings::OnBinaryPathPicked)
|
||||
]
|
||||
]
|
||||
// Root of the local repository
|
||||
+SVerticalBox::Slot()
|
||||
.AutoHeight()
|
||||
.Padding(2.0f)
|
||||
.VAlign(VAlign_Center)
|
||||
[
|
||||
SNew(SHorizontalBox)
|
||||
.ToolTipText(LOCTEXT("RepositoryRootLabel_Tooltip", "Path to the root of the Git repository"))
|
||||
+SHorizontalBox::Slot()
|
||||
.FillWidth(1.0f)
|
||||
[
|
||||
SNew(STextBlock)
|
||||
.Text(LOCTEXT("RepositoryRootLabel", "Root of the repository"))
|
||||
.Font(Font)
|
||||
]
|
||||
+SHorizontalBox::Slot()
|
||||
.FillWidth(2.0f)
|
||||
[
|
||||
SNew(STextBlock)
|
||||
.Text(this, &SGitSourceControlSettings::GetPathToRepositoryRoot)
|
||||
.Font(Font)
|
||||
]
|
||||
]
|
||||
// User Name
|
||||
+SVerticalBox::Slot()
|
||||
.AutoHeight()
|
||||
.Padding(2.0f)
|
||||
.VAlign(VAlign_Center)
|
||||
[
|
||||
SNew(SHorizontalBox)
|
||||
.ToolTipText(LOCTEXT("GitUserName_Tooltip", "User name configured for the Git repository"))
|
||||
+SHorizontalBox::Slot()
|
||||
.FillWidth(1.0f)
|
||||
[
|
||||
SNew(STextBlock)
|
||||
.Text(LOCTEXT("GitUserName", "User Name"))
|
||||
.Font(Font)
|
||||
]
|
||||
+SHorizontalBox::Slot()
|
||||
.FillWidth(2.0f)
|
||||
[
|
||||
SNew(STextBlock)
|
||||
.Text(this, &SGitSourceControlSettings::GetUserName)
|
||||
.Font(Font)
|
||||
]
|
||||
]
|
||||
// User e-mail
|
||||
+SVerticalBox::Slot()
|
||||
.FillHeight(1.0f)
|
||||
.Padding(2.0f)
|
||||
.VAlign(VAlign_Center)
|
||||
[
|
||||
SNew(SHorizontalBox)
|
||||
.ToolTipText(LOCTEXT("GitUserEmail_Tooltip", "User e-mail configured for the Git repository"))
|
||||
+SHorizontalBox::Slot()
|
||||
.FillWidth(1.0f)
|
||||
[
|
||||
SNew(STextBlock)
|
||||
.Text(LOCTEXT("GitUserEmail", "E-Mail"))
|
||||
.Font(Font)
|
||||
]
|
||||
+SHorizontalBox::Slot()
|
||||
.FillWidth(2.0f)
|
||||
[
|
||||
SNew(STextBlock)
|
||||
.Text(this, &SGitSourceControlSettings::GetUserEmail)
|
||||
.Font(Font)
|
||||
]
|
||||
]
|
||||
// Separator
|
||||
+SVerticalBox::Slot()
|
||||
.AutoHeight()
|
||||
.Padding(2.0f)
|
||||
.VAlign(VAlign_Center)
|
||||
[
|
||||
SNew(SSeparator)
|
||||
]
|
||||
// Explanation text
|
||||
+SVerticalBox::Slot()
|
||||
.FillHeight(1.0f)
|
||||
.Padding(2.0f)
|
||||
.VAlign(VAlign_Center)
|
||||
[
|
||||
SNew(SHorizontalBox)
|
||||
.Visibility(this, &SGitSourceControlSettings::MustInitializeGitRepository)
|
||||
+SHorizontalBox::Slot()
|
||||
.FillWidth(1.0f)
|
||||
.HAlign(HAlign_Center)
|
||||
[
|
||||
SNew(STextBlock)
|
||||
.Text(LOCTEXT("RepositoryNotFound", "Current Project is not contained in a Git Repository. Fill the form below to initialize a new Repository."))
|
||||
.ToolTipText(LOCTEXT("RepositoryNotFound_Tooltip", "No Repository found at the level or above the current Project"))
|
||||
.Font(Font)
|
||||
]
|
||||
]
|
||||
// Option to configure the URL of the default remote 'origin'
|
||||
// TODO: option to configure the name of the remote instead of the default origin
|
||||
+SVerticalBox::Slot()
|
||||
.AutoHeight()
|
||||
.Padding(2.0f)
|
||||
.VAlign(VAlign_Center)
|
||||
[
|
||||
SNew(SHorizontalBox)
|
||||
.Visibility(this, &SGitSourceControlSettings::MustInitializeGitRepository)
|
||||
.ToolTipText(LOCTEXT("ConfigureOrigin_Tooltip", "Configure the URL of the default remote 'origin'"))
|
||||
+SHorizontalBox::Slot()
|
||||
.FillWidth(1.0f)
|
||||
[
|
||||
SNew(STextBlock)
|
||||
.Text(LOCTEXT("ConfigureOrigin", "URL of the remote server 'origin'"))
|
||||
.Font(Font)
|
||||
]
|
||||
+SHorizontalBox::Slot()
|
||||
.FillWidth(2.0f)
|
||||
.VAlign(VAlign_Center)
|
||||
[
|
||||
SNew(SEditableTextBox)
|
||||
.Text(this, &SGitSourceControlSettings::GetRemoteUrl)
|
||||
.OnTextCommitted(this, &SGitSourceControlSettings::OnRemoteUrlCommited)
|
||||
.Font(Font)
|
||||
]
|
||||
]
|
||||
// Option to add a proper .gitignore file (true by default)
|
||||
+SVerticalBox::Slot()
|
||||
.AutoHeight()
|
||||
.Padding(2.0f)
|
||||
.VAlign(VAlign_Center)
|
||||
[
|
||||
SNew(SHorizontalBox)
|
||||
.Visibility(this, &SGitSourceControlSettings::MustInitializeGitRepository)
|
||||
.ToolTipText(LOCTEXT("CreateGitIgnore_Tooltip", "Create and add a standard '.gitignore' file"))
|
||||
+SHorizontalBox::Slot()
|
||||
.FillWidth(0.1f)
|
||||
[
|
||||
SNew(SCheckBox)
|
||||
.IsChecked(ECheckBoxState::Checked)
|
||||
.OnCheckStateChanged(this, &SGitSourceControlSettings::OnCheckedCreateGitIgnore)
|
||||
]
|
||||
+SHorizontalBox::Slot()
|
||||
.FillWidth(2.9f)
|
||||
.VAlign(VAlign_Center)
|
||||
[
|
||||
SNew(STextBlock)
|
||||
.Text(LOCTEXT("CreateGitIgnore", "Add a .gitignore file"))
|
||||
.Font(Font)
|
||||
]
|
||||
]
|
||||
// Option to add a README.md file with custom content
|
||||
+SVerticalBox::Slot()
|
||||
.AutoHeight()
|
||||
.Padding(2.0f)
|
||||
.VAlign(VAlign_Center)
|
||||
[
|
||||
SNew(SHorizontalBox)
|
||||
.Visibility(this, &SGitSourceControlSettings::MustInitializeGitRepository)
|
||||
.ToolTipText(LOCTEXT("CreateReadme_Tooltip", "Add a README.md file"))
|
||||
+SHorizontalBox::Slot()
|
||||
.FillWidth(0.1f)
|
||||
[
|
||||
SNew(SCheckBox)
|
||||
.IsChecked(ECheckBoxState::Checked)
|
||||
.OnCheckStateChanged(this, &SGitSourceControlSettings::OnCheckedCreateReadme)
|
||||
]
|
||||
+SHorizontalBox::Slot()
|
||||
.FillWidth(0.9f)
|
||||
.VAlign(VAlign_Center)
|
||||
[
|
||||
SNew(STextBlock)
|
||||
.Text(LOCTEXT("CreateReadme", "Add a basic README.md file"))
|
||||
.Font(Font)
|
||||
]
|
||||
+SHorizontalBox::Slot()
|
||||
.FillWidth(2.0f)
|
||||
.Padding(2.0f)
|
||||
[
|
||||
SNew(SMultiLineEditableTextBox)
|
||||
.Text(this, &SGitSourceControlSettings::GetReadmeContent)
|
||||
.OnTextCommitted(this, &SGitSourceControlSettings::OnReadmeContentCommited)
|
||||
.IsEnabled(this, &SGitSourceControlSettings::GetAutoCreateReadme)
|
||||
.SelectAllTextWhenFocused(true)
|
||||
.Font(Font)
|
||||
]
|
||||
]
|
||||
// Option to add a proper .gitattributes file for Git LFS (false by default)
|
||||
+SVerticalBox::Slot()
|
||||
.AutoHeight()
|
||||
.Padding(2.0f)
|
||||
.VAlign(VAlign_Center)
|
||||
[
|
||||
SNew(SHorizontalBox)
|
||||
.Visibility(this, &SGitSourceControlSettings::MustInitializeGitRepository)
|
||||
.ToolTipText(LOCTEXT("CreateGitAttributes_Tooltip", "Create and add a '.gitattributes' file to enable Git LFS for the whole 'Content/' directory (needs Git LFS extensions to be installed)."))
|
||||
+SHorizontalBox::Slot()
|
||||
.FillWidth(0.1f)
|
||||
[
|
||||
SNew(SCheckBox)
|
||||
.IsChecked(ECheckBoxState::Unchecked)
|
||||
.OnCheckStateChanged(this, &SGitSourceControlSettings::OnCheckedCreateGitAttributes)
|
||||
]
|
||||
+SHorizontalBox::Slot()
|
||||
.FillWidth(2.9f)
|
||||
.VAlign(VAlign_Center)
|
||||
[
|
||||
SNew(STextBlock)
|
||||
.Text(LOCTEXT("CreateGitAttributes", "Add a .gitattributes file to enable Git LFS"))
|
||||
.Font(Font)
|
||||
]
|
||||
]
|
||||
// Option to use the Git LFS File Locking workflow (false by default)
|
||||
// Enabled even after init to switch it off in case of no network
|
||||
// TODO LFS turning it off afterwards does not work because all files are readonly !
|
||||
+SVerticalBox::Slot()
|
||||
.AutoHeight()
|
||||
.Padding(2.0f)
|
||||
.VAlign(VAlign_Center)
|
||||
[
|
||||
SNew(SHorizontalBox)
|
||||
.ToolTipText(LOCTEXT("UseGitLfsLocking_Tooltip", "Uses Git LFS 2 File Locking workflow (CheckOut and Commit/Push)."))
|
||||
+ SHorizontalBox::Slot()
|
||||
.FillWidth(0.1f)
|
||||
[
|
||||
SNew(SCheckBox)
|
||||
.IsChecked(SGitSourceControlSettings::IsUsingGitLfsLocking())
|
||||
.OnCheckStateChanged(this, &SGitSourceControlSettings::OnCheckedUseGitLfsLocking)
|
||||
.IsEnabled(this, &SGitSourceControlSettings::CanUseGitLfsLocking)
|
||||
]
|
||||
+ SHorizontalBox::Slot()
|
||||
.FillWidth(0.9f)
|
||||
.VAlign(VAlign_Center)
|
||||
[
|
||||
SNew(STextBlock)
|
||||
.Text(LOCTEXT("UseGitLfsLocking", "Uses Git LFS 2 File Locking workflow"))
|
||||
.Font(Font)
|
||||
]
|
||||
// Username credential used to access the Git LFS 2 File Locks server
|
||||
+ SHorizontalBox::Slot()
|
||||
.FillWidth(2.0f)
|
||||
.VAlign(VAlign_Center)
|
||||
[
|
||||
SNew(SEditableTextBox)
|
||||
.Text(this, &SGitSourceControlSettings::GetLfsUserName)
|
||||
.OnTextCommitted(this, &SGitSourceControlSettings::OnLfsUserNameCommited)
|
||||
.IsEnabled(this, &SGitSourceControlSettings::GetIsUsingGitLfsLocking)
|
||||
.HintText(LOCTEXT("LfsUserName_Hint", "Username to lock files on the LFS server"))
|
||||
.Font(Font)
|
||||
]
|
||||
]
|
||||
// Option to Make the initial Git commit with custom message
|
||||
+ SVerticalBox::Slot()
|
||||
.AutoHeight()
|
||||
.Padding(2.0f)
|
||||
.VAlign(VAlign_Center)
|
||||
[
|
||||
SNew(SHorizontalBox)
|
||||
.Visibility(this, &SGitSourceControlSettings::MustInitializeGitRepository)
|
||||
.ToolTipText(LOCTEXT("InitialGitCommit_Tooltip", "Make the initial Git commit"))
|
||||
+ SHorizontalBox::Slot()
|
||||
.FillWidth(0.1f)
|
||||
[
|
||||
SNew(SCheckBox)
|
||||
.IsChecked(ECheckBoxState::Checked)
|
||||
.OnCheckStateChanged(this, &SGitSourceControlSettings::OnCheckedInitialCommit)
|
||||
]
|
||||
+ SHorizontalBox::Slot()
|
||||
.FillWidth(0.9f)
|
||||
.VAlign(VAlign_Center)
|
||||
[
|
||||
SNew(STextBlock)
|
||||
.Text(LOCTEXT("InitialGitCommit", "Make the initial Git commit"))
|
||||
.Font(Font)
|
||||
]
|
||||
+ SHorizontalBox::Slot()
|
||||
.FillWidth(2.0f)
|
||||
.Padding(2.0f)
|
||||
[
|
||||
SNew(SMultiLineEditableTextBox)
|
||||
.Text(this, &SGitSourceControlSettings::GetInitialCommitMessage)
|
||||
.OnTextCommitted(this, &SGitSourceControlSettings::OnInitialCommitMessageCommited)
|
||||
.IsEnabled(this, &SGitSourceControlSettings::GetAutoInitialCommit)
|
||||
.SelectAllTextWhenFocused(true)
|
||||
.Font(Font)
|
||||
]
|
||||
]
|
||||
// Button to initialize the project with Git, create .gitignore/.gitattributes files, and make the first commit)
|
||||
+ SVerticalBox::Slot()
|
||||
.FillHeight(2.5f)
|
||||
.Padding(4.0f)
|
||||
.VAlign(VAlign_Center)
|
||||
[
|
||||
SNew(SHorizontalBox)
|
||||
.Visibility(this, &SGitSourceControlSettings::MustInitializeGitRepository)
|
||||
+ SHorizontalBox::Slot()
|
||||
.FillWidth(1.0f)
|
||||
[
|
||||
SNew(SButton)
|
||||
.Text(LOCTEXT("GitInitRepository", "Initialize project with Git"))
|
||||
.ToolTipText(LOCTEXT("GitInitRepository_Tooltip", "Initialize current project as a new Git repository"))
|
||||
.OnClicked(this, &SGitSourceControlSettings::OnClickedInitializeGitRepository)
|
||||
.IsEnabled(this, &SGitSourceControlSettings::CanInitializeGitRepository)
|
||||
.HAlign(HAlign_Center)
|
||||
.ContentPadding(6)
|
||||
]
|
||||
]
|
||||
]
|
||||
];
|
||||
}
|
||||
#else
|
||||
void SGitSourceControlSettings::ConstructBasedOnEngineVersion( )
|
||||
{
|
||||
const FText FileFilterType = NSLOCTEXT("GitSourceControl", "Executables", "Executables");
|
||||
#if PLATFORM_WINDOWS
|
||||
const FString FileFilterText = FString::Printf(TEXT("%s (*.exe)|*.exe"), *FileFilterType.ToString());
|
||||
#else
|
||||
const FString FileFilterText = FString::Printf(TEXT("%s"), *FileFilterType.ToString());
|
||||
#endif
|
||||
|
||||
using Self = std::remove_pointer_t<decltype(this)>;
|
||||
|
||||
#define ROW_LEFT( PADDING_HEIGHT ) +SHorizontalBox::Slot() \
|
||||
.VAlign(VAlign_Center) \
|
||||
.HAlign(HAlign_Right) \
|
||||
.FillWidth(1.0f) \
|
||||
.Padding(FMargin(0.0f, 0.0f, 16.0f, PADDING_HEIGHT))
|
||||
|
||||
#define ROW_RIGHT( PADDING_HEIGHT ) +SHorizontalBox::Slot() \
|
||||
.VAlign(VAlign_Center) \
|
||||
.FillWidth(2.0f) \
|
||||
.Padding(FMargin(0.0f, 0.0f, 0.0f, PADDING_HEIGHT))
|
||||
|
||||
#define TT_GitPath LOCTEXT("BinaryPathLabel_Tooltip", "Path to Git binary")
|
||||
#define TT_RepoRoot LOCTEXT("RepositoryRootLabel_Tooltip", "Path to the root of the Git repository")
|
||||
#define TT_UserName LOCTEXT("UserNameLabel_Tooltip", "Git Username fetched from local config")
|
||||
#define TT_Email LOCTEXT("GitUserEmail_Tooltip", "Git E-mail fetched from local config")
|
||||
#define TT_LFS LOCTEXT("UseGitLfsLocking_Tooltip", "Uses Git LFS 2 File Locking workflow (CheckOut and Commit/Push).")
|
||||
|
||||
ChildSlot
|
||||
[
|
||||
SNew(SVerticalBox)
|
||||
// Git Path
|
||||
+SVerticalBox::Slot()
|
||||
.AutoHeight()
|
||||
[
|
||||
SNew(SHorizontalBox)
|
||||
ROW_LEFT( 10.0f )
|
||||
[
|
||||
SNew(STextBlock)
|
||||
.Text(LOCTEXT("BinaryPathLabel", "Git Path"))
|
||||
.ToolTipText( TT_GitPath )
|
||||
]
|
||||
ROW_RIGHT( 10.0f )
|
||||
[
|
||||
SNew(SFilePathPicker)
|
||||
#if ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION >= 1
|
||||
.BrowseButtonImage(FAppStyle::GetBrush("PropertyWindow.Button_Ellipsis"))
|
||||
.BrowseButtonStyle(FAppStyle::Get(), "HoverHintOnly")
|
||||
#else
|
||||
.BrowseButtonImage(FEditorStyle::GetBrush("PropertyWindow.Button_Ellipsis"))
|
||||
.BrowseButtonStyle(FEditorStyle::Get(), "HoverHintOnly")
|
||||
#endif
|
||||
.BrowseDirectory(FEditorDirectories::Get().GetLastDirectory(ELastDirectory::GENERIC_OPEN))
|
||||
.BrowseTitle(LOCTEXT("BinaryPathBrowseTitle", "File picker..."))
|
||||
.FilePath(this, &Self::GetBinaryPathString)
|
||||
.FileTypeFilter(FileFilterText)
|
||||
.OnPathPicked(this, &Self::OnBinaryPathPicked)
|
||||
]
|
||||
]
|
||||
// Repository Root
|
||||
+SVerticalBox::Slot()
|
||||
[
|
||||
SNew(SHorizontalBox)
|
||||
ROW_LEFT( 10.0f )
|
||||
[
|
||||
SNew(STextBlock)
|
||||
.Text(LOCTEXT("RepositoryRootLabel", "Root of the repository"))
|
||||
.ToolTipText( TT_RepoRoot )
|
||||
]
|
||||
ROW_RIGHT( 10.0f )
|
||||
[
|
||||
SNew(STextBlock)
|
||||
.Text(this, &Self::GetPathToRepositoryRoot)
|
||||
.ToolTipText( TT_RepoRoot )
|
||||
]
|
||||
]
|
||||
// User Name
|
||||
+SVerticalBox::Slot()
|
||||
[
|
||||
SNew(SHorizontalBox)
|
||||
ROW_LEFT( 10.0f )
|
||||
[
|
||||
SNew(STextBlock)
|
||||
.Text(LOCTEXT("UserNameLabel", "User Name"))
|
||||
.ToolTipText( TT_UserName )
|
||||
]
|
||||
ROW_RIGHT( 10.0f )
|
||||
[
|
||||
SNew(STextBlock)
|
||||
.Text(this, &Self::GetUserName)
|
||||
.ToolTipText( TT_UserName )
|
||||
]
|
||||
]
|
||||
// Email
|
||||
+SVerticalBox::Slot()
|
||||
[
|
||||
SNew(SHorizontalBox)
|
||||
ROW_LEFT( 10.0f )
|
||||
[
|
||||
SNew(STextBlock)
|
||||
.Text(LOCTEXT("EmailLabel", "E-mail"))
|
||||
.ToolTipText( TT_Email )
|
||||
]
|
||||
ROW_RIGHT( 10.0f )
|
||||
[
|
||||
SNew(STextBlock)
|
||||
.Text(this, &Self::GetUserEmail )
|
||||
.ToolTipText( TT_Email )
|
||||
]
|
||||
]
|
||||
// LFS Config
|
||||
+SVerticalBox::Slot()
|
||||
.AutoHeight()
|
||||
[
|
||||
SNew(SHorizontalBox)
|
||||
ROW_LEFT( 10.0f )
|
||||
[
|
||||
SNew(SCheckBox)
|
||||
.IsChecked(Self::IsUsingGitLfsLocking())
|
||||
.OnCheckStateChanged(this, &Self::OnCheckedUseGitLfsLocking)
|
||||
.IsEnabled(this, &Self::CanUseGitLfsLocking)
|
||||
.Content()
|
||||
[
|
||||
SNew(STextBlock)
|
||||
.Text(LOCTEXT("UseGitLfsLocking", "Uses Git LFS"))
|
||||
.ToolTipText( TT_LFS )
|
||||
]
|
||||
]
|
||||
ROW_RIGHT( 10.0f )
|
||||
[
|
||||
SNew(SEditableTextBox)
|
||||
.Text(this, &Self::GetLfsUserName)
|
||||
.OnTextCommitted(this, &Self::OnLfsUserNameCommited)
|
||||
.IsEnabled(this, &Self::GetIsUsingGitLfsLocking)
|
||||
.HintText(LOCTEXT("LfsUserName_Hint", "Username to lock files on the LFS server"))
|
||||
]
|
||||
]
|
||||
// [Optional] Initial Git Commit
|
||||
+SVerticalBox::Slot()
|
||||
.AutoHeight()
|
||||
.Padding(2.0f)
|
||||
.VAlign(VAlign_Center)
|
||||
[
|
||||
SNew(SHorizontalBox)
|
||||
.ToolTipText(LOCTEXT("InitialGitCommit_Tooltip", "Make the initial Git commit"))
|
||||
.Visibility(this, &SGitSourceControlSettings::MustInitializeGitRepository)
|
||||
+ SHorizontalBox::Slot()
|
||||
.FillWidth(0.1f)
|
||||
[
|
||||
SNew(SCheckBox)
|
||||
.IsChecked(ECheckBoxState::Checked)
|
||||
.OnCheckStateChanged(this, &SGitSourceControlSettings::OnCheckedInitialCommit)
|
||||
]
|
||||
+ SHorizontalBox::Slot()
|
||||
.FillWidth(0.9f)
|
||||
.VAlign(VAlign_Center)
|
||||
[
|
||||
SNew(STextBlock)
|
||||
.Text(LOCTEXT("InitialGitCommit", "Make the initial Git commit"))
|
||||
]
|
||||
+ SHorizontalBox::Slot()
|
||||
.FillWidth(2.0f)
|
||||
.Padding(2.0f)
|
||||
[
|
||||
SNew(SMultiLineEditableTextBox)
|
||||
.Text(this, &SGitSourceControlSettings::GetInitialCommitMessage)
|
||||
.OnTextCommitted(this, &SGitSourceControlSettings::OnInitialCommitMessageCommited)
|
||||
.IsEnabled(this, &SGitSourceControlSettings::GetAutoInitialCommit)
|
||||
.SelectAllTextWhenFocused(true)
|
||||
]
|
||||
]
|
||||
// [Optional] Initialize Project with Git
|
||||
+SVerticalBox::Slot()
|
||||
.FillHeight(2.5f)
|
||||
.Padding(4.0f)
|
||||
.VAlign(VAlign_Center)
|
||||
[
|
||||
SNew(SHorizontalBox)
|
||||
.Visibility(this, &SGitSourceControlSettings::MustInitializeGitRepository)
|
||||
+ SHorizontalBox::Slot()
|
||||
.FillWidth(1.0f)
|
||||
[
|
||||
SNew(SButton)
|
||||
.Text(LOCTEXT("GitInitRepository", "Initialize project with Git"))
|
||||
.ToolTipText(LOCTEXT("GitInitRepository_Tooltip", "Initialize current project as a new Git repository"))
|
||||
.OnClicked(this, &SGitSourceControlSettings::OnClickedInitializeGitRepository)
|
||||
.IsEnabled(this, &SGitSourceControlSettings::CanInitializeGitRepository)
|
||||
.HAlign(HAlign_Center)
|
||||
.ContentPadding(6)
|
||||
]
|
||||
]
|
||||
];
|
||||
|
||||
// TODO [RW] The UE5 GUI for the two optional initial git support functionalities has not been tested
|
||||
}
|
||||
#endif
|
||||
|
||||
SGitSourceControlSettings::~SGitSourceControlSettings()
|
||||
{
|
||||
RemoveInProgressNotification();
|
||||
}
|
||||
|
||||
FString SGitSourceControlSettings::GetBinaryPathString() const
|
||||
{
|
||||
const FGitSourceControlModule& GitSourceControl = FGitSourceControlModule::Get();
|
||||
return GitSourceControl.AccessSettings().GetBinaryPath();
|
||||
}
|
||||
|
||||
void SGitSourceControlSettings::OnBinaryPathPicked( const FString& PickedPath ) const
|
||||
{
|
||||
FGitSourceControlModule& GitSourceControl = FGitSourceControlModule::Get();
|
||||
FString PickedFullPath = FPaths::ConvertRelativePathToFull(PickedPath);
|
||||
const bool bChanged = GitSourceControl.AccessSettings().SetBinaryPath(PickedFullPath);
|
||||
if(bChanged)
|
||||
{
|
||||
// Re-Check provided git binary path for each change
|
||||
GitSourceControl.GetProvider().CheckGitAvailability();
|
||||
if(GitSourceControl.GetProvider().IsGitAvailable())
|
||||
{
|
||||
GitSourceControl.SaveSettings();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FText SGitSourceControlSettings::GetPathToRepositoryRoot() const
|
||||
{
|
||||
const FGitSourceControlModule& GitSourceControl = FGitSourceControlModule::Get();
|
||||
const FString& PathToRepositoryRoot = GitSourceControl.GetProvider().GetPathToRepositoryRoot();
|
||||
return FText::FromString(PathToRepositoryRoot);
|
||||
}
|
||||
|
||||
FText SGitSourceControlSettings::GetUserName() const
|
||||
{
|
||||
const FGitSourceControlModule& GitSourceControl = FGitSourceControlModule::Get();
|
||||
const FString& UserName = GitSourceControl.GetProvider().GetUserName();
|
||||
return FText::FromString(UserName);
|
||||
}
|
||||
|
||||
FText SGitSourceControlSettings::GetUserEmail() const
|
||||
{
|
||||
const FGitSourceControlModule& GitSourceControl = FGitSourceControlModule::Get();
|
||||
const FString& UserEmail = GitSourceControl.GetProvider().GetUserEmail();
|
||||
return FText::FromString(UserEmail);
|
||||
}
|
||||
|
||||
EVisibility SGitSourceControlSettings::MustInitializeGitRepository() const
|
||||
{
|
||||
const FGitSourceControlModule& GitSourceControl = FGitSourceControlModule::Get();
|
||||
const bool bGitAvailable = GitSourceControl.GetProvider().IsGitAvailable();
|
||||
const bool bGitRepositoryFound = GitSourceControl.GetProvider().IsEnabled();
|
||||
#if 0
|
||||
return (bGitAvailable && !bGitRepositoryFound) ? EVisibility::Visible : EVisibility::Collapsed;
|
||||
#else
|
||||
return EVisibility::Collapsed;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool SGitSourceControlSettings::CanInitializeGitRepository() const
|
||||
{
|
||||
const FGitSourceControlModule& GitSourceControl = FGitSourceControlModule::Get();
|
||||
const bool bGitAvailable = GitSourceControl.GetProvider().IsGitAvailable();
|
||||
const bool bGitRepositoryFound = GitSourceControl.GetProvider().IsEnabled();
|
||||
const FString& LfsUserName = GitSourceControl.AccessSettings().GetLfsUserName();
|
||||
const bool bIsUsingGitLfsLocking = GitSourceControl.GetProvider().UsesCheckout();
|
||||
const bool bGitLfsConfigOk = !bIsUsingGitLfsLocking || !LfsUserName.IsEmpty();
|
||||
const bool bInitialCommitConfigOk = !bAutoInitialCommit || !InitialCommitMessage.IsEmpty();
|
||||
#if 0
|
||||
return (bGitAvailable && !bGitRepositoryFound && bGitLfsConfigOk && bInitialCommitConfigOk);
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool SGitSourceControlSettings::CanUseGitLfsLocking() const
|
||||
{
|
||||
const FGitSourceControlModule& GitSourceControl = FGitSourceControlModule::Get();
|
||||
// TODO LFS SRombauts : check if .gitattributes file is present and if Content/ is already tracked!
|
||||
const bool bGitAttributesCreated = true;
|
||||
return (bAutoCreateGitAttributes || bGitAttributesCreated);
|
||||
}
|
||||
|
||||
FReply SGitSourceControlSettings::OnClickedInitializeGitRepository()
|
||||
{
|
||||
FGitSourceControlModule& GitSourceControl = FGitSourceControlModule::Get();
|
||||
const FString& PathToGitBinary = GitSourceControl.AccessSettings().GetBinaryPath();
|
||||
const FString PathToProjectDir = FPaths::ConvertRelativePathToFull(FPaths::ProjectDir());
|
||||
TArray<FString> InfoMessages;
|
||||
TArray<FString> ErrorMessages;
|
||||
|
||||
// 1.a. Synchronous (very quick) "git init" operation: initialize a Git local repository with a .git/ subdirectory
|
||||
GitSourceControlUtils::RunCommand(TEXT("init"), PathToGitBinary, PathToProjectDir, FGitSourceControlModule::GetEmptyStringArray(), FGitSourceControlModule::GetEmptyStringArray(), InfoMessages, ErrorMessages);
|
||||
// 1.b. Synchronous (very quick) "git remote add" operation: configure the URL of the default remote server 'origin' if specified
|
||||
if(!RemoteUrl.IsEmpty())
|
||||
{
|
||||
TArray<FString> Parameters;
|
||||
Parameters.Add(TEXT("add origin"));
|
||||
Parameters.Add(RemoteUrl.ToString());
|
||||
GitSourceControlUtils::RunCommand(TEXT("remote"), PathToGitBinary, PathToProjectDir, Parameters, FGitSourceControlModule::GetEmptyStringArray(), InfoMessages, ErrorMessages);
|
||||
}
|
||||
|
||||
// Check the new repository status to enable connection (branch, user e-mail)
|
||||
GitSourceControl.GetProvider().CheckGitAvailability();
|
||||
if(GitSourceControl.GetProvider().IsAvailable())
|
||||
{
|
||||
// List of files to add to Revision Control (.uproject, Config/, Content/, Plugins/, Source/ files and .gitignore/.gitattributes if any)
|
||||
TArray<FString> ProjectFiles;
|
||||
for (const FString& Path : GitSourceControlUtils::GetSourceControlledAssetPaths())
|
||||
{
|
||||
ProjectFiles.Add(Path);
|
||||
}
|
||||
|
||||
if (FPaths::DirectoryExists(FPaths::GameSourceDir()))
|
||||
{
|
||||
ProjectFiles.Add(FPaths::GameSourceDir());
|
||||
}
|
||||
if(bAutoCreateGitIgnore)
|
||||
{
|
||||
// 2.a. Create a standard ".gitignore" file with common patterns for a typical Blueprint & C++ project
|
||||
const FString GitIgnoreFilename = FPaths::Combine(FPaths::ProjectDir(), TEXT(".gitignore"));
|
||||
const FString GitIgnoreContent = TEXT("Binaries\nDerivedDataCache\nIntermediate\nSaved\n.vscode\n.vs\n*.VC.db\n*.opensdf\n*.opendb\n*.sdf\n*.sln\n*.suo\n*.xcodeproj\n*.xcworkspace\n*.log");
|
||||
if(FFileHelper::SaveStringToFile(GitIgnoreContent, *GitIgnoreFilename, FFileHelper::EEncodingOptions::ForceUTF8WithoutBOM))
|
||||
{
|
||||
ProjectFiles.Add(GitIgnoreFilename);
|
||||
}
|
||||
}
|
||||
if(bAutoCreateReadme)
|
||||
{
|
||||
// 2.b. Create a "README.md" file with a custom description
|
||||
const FString ReadmeFilename = FPaths::Combine(FPaths::ProjectDir(), TEXT("README.md"));
|
||||
if (FFileHelper::SaveStringToFile(ReadmeContent.ToString(), *ReadmeFilename, FFileHelper::EEncodingOptions::ForceUTF8WithoutBOM))
|
||||
{
|
||||
ProjectFiles.Add(ReadmeFilename);
|
||||
}
|
||||
}
|
||||
if(bAutoCreateGitAttributes)
|
||||
{
|
||||
// 2.c. Synchronous (very quick) "lfs install" operation: needs only to be run once by user
|
||||
GitSourceControlUtils::RunCommand(TEXT("install"), PathToGitBinary, PathToProjectDir, FGitSourceControlModule::GetEmptyStringArray(), FGitSourceControlModule::GetEmptyStringArray(), InfoMessages, ErrorMessages);
|
||||
|
||||
// 2.d. Create a ".gitattributes" file to enable Git LFS (Large File System) for the whole "Content/" subdir
|
||||
const FString GitAttributesFilename = FPaths::Combine(FPaths::ProjectDir(), TEXT(".gitattributes"));
|
||||
FString GitAttributesContent;
|
||||
if (GitSourceControl.GetProvider().UsesCheckout())
|
||||
{
|
||||
// Git LFS 2.x File Locking mechanism
|
||||
GitAttributesContent = TEXT("Content/** filter=lfs diff=lfs merge=lfs -text lockable\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
GitAttributesContent = TEXT("Content/** filter=lfs diff=lfs merge=lfs -text\n");
|
||||
}
|
||||
if(FFileHelper::SaveStringToFile(GitAttributesContent, *GitAttributesFilename, FFileHelper::EEncodingOptions::ForceUTF8WithoutBOM))
|
||||
{
|
||||
ProjectFiles.Add(GitAttributesFilename);
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Add files to Revision Control: launch an asynchronous MarkForAdd operation
|
||||
LaunchMarkForAddOperation(ProjectFiles);
|
||||
|
||||
// 4. The CheckIn will follow, at completion of the MarkForAdd operation
|
||||
FGitSourceControlProvider& Provider = FGitSourceControlModule::Get().GetProvider();
|
||||
Provider.CheckRepositoryStatus();
|
||||
}
|
||||
return FReply::Handled();
|
||||
}
|
||||
|
||||
// Launch an asynchronous "MarkForAdd" operation and start an ongoing notification
|
||||
void SGitSourceControlSettings::LaunchMarkForAddOperation(const TArray<FString>& InFiles)
|
||||
{
|
||||
FGitSourceControlModule& GitSourceControl = FGitSourceControlModule::Get();
|
||||
TSharedRef<FMarkForAdd, ESPMode::ThreadSafe> MarkForAddOperation = ISourceControlOperation::Create<FMarkForAdd>();
|
||||
#if ENGINE_MAJOR_VERSION >= 5
|
||||
ECommandResult::Type Result = GitSourceControl.GetProvider().Execute(MarkForAddOperation, FSourceControlChangelistPtr(), InFiles, EConcurrency::Asynchronous, FSourceControlOperationComplete::CreateSP(this, &SGitSourceControlSettings::OnSourceControlOperationComplete));
|
||||
#else
|
||||
ECommandResult::Type Result = GitSourceControl.GetProvider().Execute(MarkForAddOperation, InFiles, EConcurrency::Asynchronous, FSourceControlOperationComplete::CreateSP(this, &SGitSourceControlSettings::OnSourceControlOperationComplete));
|
||||
#endif
|
||||
if (Result == ECommandResult::Succeeded)
|
||||
{
|
||||
DisplayInProgressNotification(MarkForAddOperation);
|
||||
}
|
||||
else
|
||||
{
|
||||
DisplayFailureNotification(MarkForAddOperation);
|
||||
}
|
||||
}
|
||||
|
||||
// Launch an asynchronous "CheckIn" operation and start another ongoing notification
|
||||
void SGitSourceControlSettings::LaunchCheckInOperation()
|
||||
{
|
||||
TSharedRef<FCheckIn, ESPMode::ThreadSafe> CheckInOperation = ISourceControlOperation::Create<FCheckIn>();
|
||||
CheckInOperation->SetDescription(InitialCommitMessage);
|
||||
FGitSourceControlModule& GitSourceControl = FGitSourceControlModule::Get();
|
||||
#if ENGINE_MAJOR_VERSION >= 5
|
||||
ECommandResult::Type Result = GitSourceControl.GetProvider().Execute(CheckInOperation, FSourceControlChangelistPtr(), FGitSourceControlModule::GetEmptyStringArray(), EConcurrency::Asynchronous, FSourceControlOperationComplete::CreateSP(this, &SGitSourceControlSettings::OnSourceControlOperationComplete));
|
||||
#else
|
||||
ECommandResult::Type Result = GitSourceControl.GetProvider().Execute(CheckInOperation, FGitSourceControlModule::GetEmptyStringArray(), EConcurrency::Asynchronous, FSourceControlOperationComplete::CreateSP(this, &SGitSourceControlSettings::OnSourceControlOperationComplete));
|
||||
#endif
|
||||
if (Result == ECommandResult::Succeeded)
|
||||
{
|
||||
DisplayInProgressNotification(CheckInOperation);
|
||||
}
|
||||
else
|
||||
{
|
||||
DisplayFailureNotification(CheckInOperation);
|
||||
}
|
||||
}
|
||||
|
||||
/// Delegate called when a Revision control operation has completed: launch the next one and manage notifications
|
||||
void SGitSourceControlSettings::OnSourceControlOperationComplete(const FSourceControlOperationRef& InOperation, ECommandResult::Type InResult)
|
||||
{
|
||||
RemoveInProgressNotification();
|
||||
|
||||
// Report result with a notification
|
||||
if (InResult == ECommandResult::Succeeded)
|
||||
{
|
||||
DisplaySuccessNotification(InOperation);
|
||||
}
|
||||
else
|
||||
{
|
||||
DisplayFailureNotification(InOperation);
|
||||
}
|
||||
|
||||
if ((InOperation->GetName() == "MarkForAdd") && (InResult == ECommandResult::Succeeded) && bAutoInitialCommit)
|
||||
{
|
||||
// 4. optional initial Asynchronous commit with custom message: launch a "CheckIn" Operation
|
||||
LaunchCheckInOperation();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Display an ongoing notification during the whole operation
|
||||
void SGitSourceControlSettings::DisplayInProgressNotification(const FSourceControlOperationRef& InOperation)
|
||||
{
|
||||
FNotificationInfo Info(InOperation->GetInProgressString());
|
||||
Info.bFireAndForget = false;
|
||||
Info.ExpireDuration = 0.0f;
|
||||
Info.FadeOutDuration = 1.0f;
|
||||
OperationInProgressNotification = FSlateNotificationManager::Get().AddNotification(Info);
|
||||
if (OperationInProgressNotification.IsValid())
|
||||
{
|
||||
OperationInProgressNotification.Pin()->SetCompletionState(SNotificationItem::CS_Pending);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove the ongoing notification at the end of the operation
|
||||
void SGitSourceControlSettings::RemoveInProgressNotification()
|
||||
{
|
||||
if (OperationInProgressNotification.IsValid())
|
||||
{
|
||||
OperationInProgressNotification.Pin()->ExpireAndFadeout();
|
||||
OperationInProgressNotification.Reset();
|
||||
}
|
||||
}
|
||||
|
||||
// Display a temporary success notification at the end of the operation
|
||||
void SGitSourceControlSettings::DisplaySuccessNotification(const FSourceControlOperationRef& InOperation)
|
||||
{
|
||||
const FText NotificationText = FText::Format(LOCTEXT("InitialCommit_Success", "{0} operation was successfull!"), FText::FromName(InOperation->GetName()));
|
||||
FNotificationInfo Info(NotificationText);
|
||||
Info.bUseSuccessFailIcons = true;
|
||||
#if ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION >= 1
|
||||
Info.Image = FAppStyle::GetBrush(TEXT("NotificationList.SuccessImage"));
|
||||
#else
|
||||
Info.Image = FEditorStyle::GetBrush(TEXT("NotificationList.SuccessImage"));
|
||||
#endif
|
||||
FSlateNotificationManager::Get().AddNotification(Info);
|
||||
}
|
||||
|
||||
// Display a temporary failure notification at the end of the operation
|
||||
void SGitSourceControlSettings::DisplayFailureNotification(const FSourceControlOperationRef& InOperation)
|
||||
{
|
||||
const FText NotificationText = FText::Format(LOCTEXT("InitialCommit_Failure", "Error: {0} operation failed!"), FText::FromName(InOperation->GetName()));
|
||||
FNotificationInfo Info(NotificationText);
|
||||
Info.ExpireDuration = 8.0f;
|
||||
FSlateNotificationManager::Get().AddNotification(Info);
|
||||
}
|
||||
|
||||
void SGitSourceControlSettings::OnCheckedCreateGitIgnore(ECheckBoxState NewCheckedState)
|
||||
{
|
||||
bAutoCreateGitIgnore = (NewCheckedState == ECheckBoxState::Checked);
|
||||
}
|
||||
|
||||
void SGitSourceControlSettings::OnCheckedCreateReadme(ECheckBoxState NewCheckedState)
|
||||
{
|
||||
bAutoCreateReadme = (NewCheckedState == ECheckBoxState::Checked);
|
||||
}
|
||||
|
||||
bool SGitSourceControlSettings::GetAutoCreateReadme() const
|
||||
{
|
||||
return bAutoCreateReadme;
|
||||
}
|
||||
|
||||
void SGitSourceControlSettings::OnReadmeContentCommited(const FText& InText, ETextCommit::Type InCommitType)
|
||||
{
|
||||
ReadmeContent = InText;
|
||||
}
|
||||
|
||||
FText SGitSourceControlSettings::GetReadmeContent() const
|
||||
{
|
||||
return ReadmeContent;
|
||||
}
|
||||
|
||||
void SGitSourceControlSettings::OnCheckedCreateGitAttributes(ECheckBoxState NewCheckedState)
|
||||
{
|
||||
bAutoCreateGitAttributes = (NewCheckedState == ECheckBoxState::Checked);
|
||||
}
|
||||
|
||||
void SGitSourceControlSettings::OnCheckedUseGitLfsLocking(ECheckBoxState NewCheckedState)
|
||||
{
|
||||
FGitSourceControlModule& GitSourceControl = FGitSourceControlModule::Get();
|
||||
GitSourceControl.AccessSettings().SetUsingGitLfsLocking(NewCheckedState == ECheckBoxState::Checked);
|
||||
GitSourceControl.AccessSettings().SaveSettings();
|
||||
GitSourceControl.GetProvider().UpdateSettings();
|
||||
}
|
||||
|
||||
bool SGitSourceControlSettings::GetIsUsingGitLfsLocking() const
|
||||
{
|
||||
const FGitSourceControlModule& GitSourceControl = FGitSourceControlModule::Get();
|
||||
return GitSourceControl.AccessSettings().IsUsingGitLfsLocking();
|
||||
}
|
||||
|
||||
ECheckBoxState SGitSourceControlSettings::IsUsingGitLfsLocking() const
|
||||
{
|
||||
return (GetIsUsingGitLfsLocking() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked);
|
||||
}
|
||||
|
||||
void SGitSourceControlSettings::OnLfsUserNameCommited(const FText& InText, ETextCommit::Type InCommitType)
|
||||
{
|
||||
FGitSourceControlModule& GitSourceControl = FGitSourceControlModule::Get();
|
||||
GitSourceControl.AccessSettings().SetLfsUserName(InText.ToString());
|
||||
GitSourceControl.AccessSettings().SaveSettings();
|
||||
GitSourceControl.GetProvider().UpdateSettings();
|
||||
}
|
||||
|
||||
FText SGitSourceControlSettings::GetLfsUserName() const
|
||||
{
|
||||
FGitSourceControlModule& GitSourceControl = FGitSourceControlModule::Get();
|
||||
const FString LFSUserName = GitSourceControl.AccessSettings().GetLfsUserName();
|
||||
if (LFSUserName.IsEmpty())
|
||||
{
|
||||
const FText& UserName = GetUserName();
|
||||
GitSourceControl.AccessSettings().SetLfsUserName(UserName.ToString());
|
||||
GitSourceControl.AccessSettings().SaveSettings();
|
||||
GitSourceControl.GetProvider().UpdateSettings();
|
||||
return UserName;
|
||||
}
|
||||
else
|
||||
{
|
||||
return FText::FromString(LFSUserName);
|
||||
}
|
||||
}
|
||||
|
||||
void SGitSourceControlSettings::OnCheckedInitialCommit(ECheckBoxState NewCheckedState)
|
||||
{
|
||||
bAutoInitialCommit = (NewCheckedState == ECheckBoxState::Checked);
|
||||
}
|
||||
|
||||
bool SGitSourceControlSettings::GetAutoInitialCommit() const
|
||||
{
|
||||
return bAutoInitialCommit;
|
||||
}
|
||||
|
||||
void SGitSourceControlSettings::OnInitialCommitMessageCommited(const FText& InText, ETextCommit::Type InCommitType)
|
||||
{
|
||||
InitialCommitMessage = InText;
|
||||
}
|
||||
|
||||
FText SGitSourceControlSettings::GetInitialCommitMessage() const
|
||||
{
|
||||
return InitialCommitMessage;
|
||||
}
|
||||
|
||||
void SGitSourceControlSettings::OnRemoteUrlCommited(const FText& InText, ETextCommit::Type InCommitType)
|
||||
{
|
||||
RemoteUrl = InText;
|
||||
}
|
||||
|
||||
FText SGitSourceControlSettings::GetRemoteUrl() const
|
||||
{
|
||||
return RemoteUrl;
|
||||
}
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
// Copyright (c) 2014-2020 Sebastien Rombauts (sebastien.rombauts@gmail.com)
|
||||
//
|
||||
// Distributed under the MIT License (MIT) (See accompanying file LICENSE.txt
|
||||
// or copy at http://opensource.org/licenses/MIT)
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Widgets/SCompoundWidget.h"
|
||||
#include "ISourceControlProvider.h"
|
||||
#include "Runtime/Launch/Resources/Version.h"
|
||||
|
||||
class SNotificationItem;
|
||||
#if ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 2
|
||||
namespace ETextCommit { enum Type : int; }
|
||||
#else
|
||||
namespace ETextCommit { enum Type; }
|
||||
#endif
|
||||
|
||||
enum class ECheckBoxState : uint8;
|
||||
|
||||
class SGitSourceControlSettings : public SCompoundWidget
|
||||
{
|
||||
public:
|
||||
|
||||
SLATE_BEGIN_ARGS(SGitSourceControlSettings) {}
|
||||
|
||||
SLATE_END_ARGS()
|
||||
|
||||
public:
|
||||
|
||||
void Construct(const FArguments& InArgs);
|
||||
|
||||
~SGitSourceControlSettings();
|
||||
|
||||
private:
|
||||
void ConstructBasedOnEngineVersion( );
|
||||
|
||||
/** Delegates to get Git binary path from/to settings */
|
||||
FString GetBinaryPathString() const;
|
||||
void OnBinaryPathPicked(const FString & PickedPath) const;
|
||||
|
||||
/** Delegate to get repository root, user name and email from provider */
|
||||
FText GetPathToRepositoryRoot() const;
|
||||
FText GetUserName() const;
|
||||
FText GetUserEmail() const;
|
||||
|
||||
EVisibility MustInitializeGitRepository() const;
|
||||
bool CanInitializeGitRepository() const;
|
||||
bool CanUseGitLfsLocking() const;
|
||||
|
||||
/** Delegate to initialize a new Git repository */
|
||||
FReply OnClickedInitializeGitRepository();
|
||||
|
||||
void OnCheckedCreateGitIgnore(ECheckBoxState NewCheckedState);
|
||||
bool bAutoCreateGitIgnore;
|
||||
|
||||
/** Delegates to create a README.md file */
|
||||
void OnCheckedCreateReadme(ECheckBoxState NewCheckedState);
|
||||
bool GetAutoCreateReadme() const;
|
||||
bool bAutoCreateReadme;
|
||||
void OnReadmeContentCommited(const FText& InText, ETextCommit::Type InCommitType);
|
||||
FText GetReadmeContent() const;
|
||||
FText ReadmeContent;
|
||||
|
||||
void OnCheckedCreateGitAttributes(ECheckBoxState NewCheckedState);
|
||||
bool bAutoCreateGitAttributes;
|
||||
|
||||
void OnCheckedUseGitLfsLocking(ECheckBoxState NewCheckedState);
|
||||
ECheckBoxState IsUsingGitLfsLocking() const;
|
||||
bool GetIsUsingGitLfsLocking() const;
|
||||
|
||||
void OnLfsUserNameCommited(const FText& InText, ETextCommit::Type InCommitType);
|
||||
FText GetLfsUserName() const;
|
||||
|
||||
void OnCheckedInitialCommit(ECheckBoxState NewCheckedState);
|
||||
bool GetAutoInitialCommit() const;
|
||||
bool bAutoInitialCommit;
|
||||
void OnInitialCommitMessageCommited(const FText& InText, ETextCommit::Type InCommitType);
|
||||
FText GetInitialCommitMessage() const;
|
||||
FText InitialCommitMessage;
|
||||
|
||||
void OnRemoteUrlCommited(const FText& InText, ETextCommit::Type InCommitType);
|
||||
FText GetRemoteUrl() const;
|
||||
FText RemoteUrl;
|
||||
|
||||
/** Launch initial asynchronous add and commit operations */
|
||||
void LaunchMarkForAddOperation(const TArray<FString>& InFiles);
|
||||
void LaunchCheckInOperation();
|
||||
|
||||
/** Delegate called when a revision control operation has completed */
|
||||
void OnSourceControlOperationComplete(const FSourceControlOperationRef& InOperation, ECommandResult::Type InResult);
|
||||
|
||||
/** Asynchronous operation progress notifications */
|
||||
TWeakPtr<SNotificationItem> OperationInProgressNotification;
|
||||
|
||||
void DisplayInProgressNotification(const FSourceControlOperationRef& InOperation);
|
||||
void RemoveInProgressNotification();
|
||||
void DisplaySuccessNotification(const FSourceControlOperationRef& InOperation);
|
||||
void DisplayFailureNotification(const FSourceControlOperationRef& InOperation);
|
||||
};
|
||||
Reference in New Issue
Block a user