added UEGitPlugin

This commit is contained in:
2026-06-15 14:46:13 +09:00
parent cfd64ebba4
commit f525d25a90
46 changed files with 9791 additions and 0 deletions
@@ -0,0 +1,83 @@
#pragma once
#include "Runtime/Launch/Resources/Version.h"
#if ENGINE_MAJOR_VERSION == 5
#include "ISourceControlChangelist.h"
class FGitSourceControlChangelist : public ISourceControlChangelist
{
public:
FGitSourceControlChangelist() = default;
explicit FGitSourceControlChangelist(FString&& InChangelistName, const bool bInInitialized = false)
: ChangelistName(MoveTemp(InChangelistName))
, bInitialized(bInInitialized)
{
}
virtual bool CanDelete() const override
{
return false;
}
bool operator==(const FGitSourceControlChangelist& InOther) const
{
return ChangelistName == InOther.ChangelistName;
}
bool operator!=(const FGitSourceControlChangelist& InOther) const
{
return ChangelistName != InOther.ChangelistName;
}
#if ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION >= 3
virtual bool IsDefault() const override
{
return ChangelistName == WorkingChangelist.ChangelistName;
}
#endif
void SetInitialized()
{
bInitialized = true;
}
bool IsInitialized() const
{
return bInitialized;
}
void Reset()
{
ChangelistName.Reset();
bInitialized = false;
}
friend FORCEINLINE uint32 GetTypeHash(const FGitSourceControlChangelist& InGitChangelist)
{
return GetTypeHash(InGitChangelist.ChangelistName);
}
FString GetName() const
{
return ChangelistName;
}
#if ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION >= 3
virtual FString GetIdentifier() const override
{
return ChangelistName;
}
#endif
public:
static FGitSourceControlChangelist WorkingChangelist;
static FGitSourceControlChangelist StagedChangelist;
private:
FString ChangelistName;
bool bInitialized = false;
};
typedef TSharedRef<class FGitSourceControlChangelist, ESPMode::ThreadSafe> FGitSourceControlChangelistRef;
#endif
@@ -0,0 +1,131 @@
// 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)
#pragma once
#include "GitSourceControlChangelist.h"
#include "ISourceControlProvider.h"
#include "Misc/IQueuedWork.h"
#include "Runtime/Launch/Resources/Version.h"
/** Accumulated error and info messages for a revision control operation. */
struct FGitSourceControlResultInfo
{
/** Append any messages from another FSourceControlResultInfo, ensuring to keep any already accumulated info. */
void Append(const FGitSourceControlResultInfo& InResultInfo)
{
InfoMessages.Append(InResultInfo.InfoMessages);
ErrorMessages.Append(InResultInfo.ErrorMessages);
}
/** Info and/or warning message storage */
TArray<FString> InfoMessages;
/** Potential error message storage */
TArray<FString> ErrorMessages;
};
/**
* Used to execute Git commands multi-threaded.
*/
class FGitSourceControlCommand : public IQueuedWork
{
public:
FGitSourceControlCommand(const TSharedRef<class ISourceControlOperation, ESPMode::ThreadSafe>& InOperation, const TSharedRef<class IGitSourceControlWorker, ESPMode::ThreadSafe>& InWorker, const FSourceControlOperationComplete& InOperationCompleteDelegate = FSourceControlOperationComplete());
/**
* Modify the repo root if all selected files are in a plugin subfolder, and the plugin subfolder is a git repo
* This supports the case where each plugin is a sub module
*/
void UpdateRepositoryRootIfSubmodule(TArray<FString>& AbsoluteFilePaths);
/**
* This is where the real thread work is done. All work that is done for
* this queued object should be done from within the call to this function.
*/
bool DoWork();
/**
* Tells the queued work that it is being abandoned so that it can do
* per object clean up as needed. This will only be called if it is being
* abandoned before completion. NOTE: This requires the object to delete
* itself using whatever heap it was allocated in.
*/
virtual void Abandon() override;
/**
* This method is also used to tell the object to cleanup but not before
* the object has finished it's work.
*/
virtual void DoThreadedWork() override;
/** Attempt to cancel the operation */
void Cancel();
/** Is the operation canceled? */
bool IsCanceled() const;
/** Save any results and call any registered callbacks. */
ECommandResult::Type ReturnResults();
public:
/** Path to the Git binary */
FString PathToGitBinary;
/** Path to the root of the Unreal revision control repository: usually the ProjectDir */
FString PathToRepositoryRoot;
/** Path to the root of the Git repository: can be the ProjectDir itself, or any parent directory (found by the "Connect" operation) */
FString PathToGitRoot;
/** Tell if using the Git LFS file Locking workflow */
bool bUsingGitLfsLocking;
/** Operation we want to perform - contains outward-facing parameters & results */
TSharedRef<class ISourceControlOperation, ESPMode::ThreadSafe> Operation;
/** The object that will actually do the work */
TSharedRef<class IGitSourceControlWorker, ESPMode::ThreadSafe> Worker;
/** Delegate to notify when this operation completes */
FSourceControlOperationComplete OperationCompleteDelegate;
/**If true, this command has been processed by the revision control thread*/
volatile int32 bExecuteProcessed;
/**If true, this command has been cancelled*/
volatile int32 bCancelled;
/**If true, the revision control command succeeded*/
bool bCommandSuccessful;
/** Current Commit full SHA1 */
FString CommitId;
/** Current Commit description's Summary */
FString CommitSummary;
/** If true, this command will be automatically cleaned up in Tick() */
bool bAutoDelete;
/** Whether we are running multi-treaded or not*/
EConcurrency::Type Concurrency;
/** Files to perform this operation on */
TArray<FString> Files;
#if ENGINE_MAJOR_VERSION == 5
/** Changelist to perform this operation on */
FGitSourceControlChangelist Changelist;
#endif
/** Potential error, warning and info message storage */
FGitSourceControlResultInfo ResultInfo;
/** Branch names for status queries */
TArray< FString > StatusBranchNames;
};
@@ -0,0 +1,70 @@
// 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 "ISourceControlProvider.h"
#include "Runtime/Launch/Resources/Version.h"
struct FToolMenuSection;
class FMenuBuilder;
/** Git extension of the Revision Control toolbar menu */
class FGitSourceControlMenu
{
public:
void Register();
void Unregister();
/** This functions will be bound to appropriate Command. */
void CommitClicked();
void PushClicked();
void SyncClicked();
void RevertClicked();
void RefreshClicked();
protected:
static void RevertAllCallback(const FSourceControlOperationRef& InOperation, ECommandResult::Type InResult);
static void RevertAllCancelled(FSourceControlOperationRef InOperation);
private:
bool HaveRemoteUrl() const;
bool CanCommit() const;
bool SaveDirtyPackages();
bool StashAwayAnyModifications();
void ReApplyStashedModifications();
#if ENGINE_MAJOR_VERSION >= 5
void AddMenuExtension(FToolMenuSection& Builder);
#else
void AddMenuExtension(FMenuBuilder& Builder);
TSharedRef<class FExtender> OnExtendLevelEditorViewMenu(const TSharedRef<class FUICommandList> CommandList);
#endif
static void DisplayInProgressNotification(const FText& InOperationInProgressString);
static void RemoveInProgressNotification();
static void DisplaySucessNotification(const FName& InOperationName);
static void DisplayFailureNotification(const FName& InOperationName);
private:
#if ENGINE_MAJOR_VERSION < 5
FDelegateHandle ViewMenuExtenderHandle;
#endif
/** Was there a need to stash away modifications before Sync? */
bool bStashMadeBeforeSync;
/** Loaded packages to reload after a Sync or Revert operation */
TArray<UPackage*> PackagesToReload;
/** Current revision control operation from extended menu if any */
static TWeakPtr<class SNotificationItem> OperationInProgressNotification;
/** Delegate called when a revision control operation has completed */
void OnSourceControlOperationComplete(const FSourceControlOperationRef& InOperation, ECommandResult::Type InResult);
};
@@ -0,0 +1,160 @@
// 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 "Modules/ModuleInterface.h"
#include "Modules/ModuleManager.h"
#include "GitSourceControlSettings.h"
#include "GitSourceControlProvider.h"
struct FAssetData;
class FExtender;
/**
UEGitPlugin is a simple Git Revision Control Plugin for Unreal Engine
Written and contributed by Sebastien Rombauts (sebastien.rombauts@gmail.com)
### Supported features
- initialize a new Git local repository ('git init') to manage your UE Game Project
- can also create an appropriate .gitignore file as part of initialization
- can also create a .gitattributes file to enable Git LFS (Large File System) as part of initialization
- can also make the initial commit, with custom multi-line message
- can also configure the default remote origin URL
- display status icons to show modified/added/deleted/untracked files
- show history of a file
- visual diff of a blueprint against depot or between previous versions of a file
- revert modifications of a file
- add, delete, rename a file
- checkin/commit a file (cannot handle atomically more than 50 files)
- migrate an asset between two projects if both are using Git
- solve a merge conflict on a blueprint
- show current branch name in status text
- Sync to Pull (rebase) the current branch
- Git LFS (Github, Gitlab, Bitbucket) is working with Git 2.10+ under Windows
- Git LFS 2 File Locking is working with Git 2.10+ and Git LFS 2.0.0
- Windows, Mac and Linux
### TODO
1. configure the name of the remote instead of default "origin"
### TODO LFS 2.x File Locking
Known issues:
0. False error logs after a successful push:
Use "TODO LFS" in the code to track things left to do/improve/refactor:
2. Implement FGitSourceControlProvider::bWorkingOffline like the SubversionSourceControl plugin
3. Trying to deactivate Git LFS 2 file locking afterward on the "Login to Revision Control" (Connect/Configure) screen
is not working after Git LFS 2 has switched "read-only" flag on files (which needs the Checkout operation to be editable)!
- temporarily deactivating locks may be required if we want to be able to work while not connected (do we really need this ???)
- does Git LFS have a command to do this deactivation ?
- perhaps should we rely on detection of such flags to detect LFS 2 usage (ie. the need to do a checkout)
- see SubversionSourceControl plugin that deals with such flags
- this would need a rework of the way the "bIsUsingFileLocking" is propagated, since this would no more be a configuration (or not only) but a file state
- else we should at least revert those read-only flags when going out of "Lock mode"
### What *cannot* be done presently
- Branch/Merge are not in the current Editor workflow
- Amend a commit is not in the current Editor workflow
- Configure user name & email ('git config user.name' & git config user.email')
### Known issues
- the Editor does not show deleted files (only when deleted externally?)
- the Editor does not show missing files
- missing localization for git specific messages
- renaming a Blueprint in Editor leaves a redirector file, AND modify too much the asset to enable git to track its history through renaming
- standard Editor commit dialog asks if user wants to "Keep Files Checked Out" => no use for Git or Mercurial CanCheckOut()==false
*/
class FGitSourceControlModule : public IModuleInterface
{
public:
/** IModuleInterface implementation */
virtual void StartupModule() override;
virtual void ShutdownModule() override;
/** Access the Git revision control settings */
FGitSourceControlSettings& AccessSettings()
{
return GitSourceControlSettings;
}
const FGitSourceControlSettings& AccessSettings() const
{
return GitSourceControlSettings;
}
/** Save the Git revision control settings */
void SaveSettings();
/** Access the Git revision control provider */
FGitSourceControlProvider& GetProvider()
{
return GitSourceControlProvider;
}
const FGitSourceControlProvider& GetProvider() const
{
return GitSourceControlProvider;
}
GITSOURCECONTROL_API static const TArray< FString > & GetEmptyStringArray()
{
return EmptyStringArray;
}
/**
* Singleton-like access to this module's interface. This is just for convenience!
* Beware of calling this during the shutdown phase, though. Your module might have been unloaded already.
*
* @return Returns singleton instance, loading the module on demand if needed
*/
static inline FGitSourceControlModule& Get()
{
return FModuleManager::Get().LoadModuleChecked< FGitSourceControlModule >("GitSourceControl");
}
static inline FGitSourceControlModule* GetThreadSafe()
{
IModuleInterface* ModulePtr = FModuleManager::Get().GetModule("GitSourceControl");
if (!ModulePtr)
{
// Main thread should never have this unloaded.
check(!IsInGameThread());
return nullptr;
}
return static_cast<FGitSourceControlModule*>(ModulePtr);
}
/** Set list of error messages that occurred after last git command */
static void SetLastErrors(const TArray<FText>& InErrors);
private:
TSharedRef<FExtender> OnExtendContentBrowserAssetSelectionMenu(const TArray<FAssetData>& SelectedAssets);
void CreateGitContentBrowserAssetMenu(FMenuBuilder& MenuBuilder, const TArray<FAssetData> SelectedAssets);
void DiffAssetAgainstGitOriginBranch(const TArray<FAssetData> SelectedAssets, FString BranchName) const;
void DiffAgainstOriginBranch(UObject* InObject, const FString& InPackagePath, const FString& InPackageName, const FString& BranchName) const;
/** The one and only Git revision control provider */
FGitSourceControlProvider GitSourceControlProvider;
/** The settings for Git revision control */
FGitSourceControlSettings GitSourceControlSettings;
static TArray<FString> EmptyStringArray;
#if ENGINE_MAJOR_VERSION >= 5
// ContentBrowserDelegate Handles
FDelegateHandle CbdHandle_OnFilterChanged;
FDelegateHandle CbdHandle_OnSearchBoxChanged;
FDelegateHandle CbdHandle_OnAssetSelectionChanged;
FDelegateHandle CbdHandle_OnSourcesViewChanged;
FDelegateHandle CbdHandle_OnAssetPathChanged;
#endif
FDelegateHandle CbdHandle_OnExtendAssetSelectionMenu;
};
@@ -0,0 +1,323 @@
// 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 "GitSourceControlChangelist.h"
#include "ISourceControlProvider.h"
#include "IGitSourceControlWorker.h"
#include "GitSourceControlMenu.h"
#include "Runtime/Launch/Resources/Version.h"
class FGitSourceControlChangelistState;
class FGitSourceControlState;
class FGitSourceControlCommand;
DECLARE_DELEGATE_RetVal(FGitSourceControlWorkerRef, FGetGitSourceControlWorker)
/// Git version and capabilites extracted from the string "git version 2.11.0.windows.3"
struct FGitVersion
{
// Git version extracted from the string "git version 2.11.0.windows.3" (Windows), "git version 2.11.0" (Linux/Mac/Cygwin/WSL) or "git version 2.31.1.vfs.0.3" (Microsoft)
int Major; // 2 Major version number
int Minor; // 31 Minor version number
int Patch; // 1 Patch/bugfix number
bool bIsFork;
FString Fork; // "vfs"
int ForkMajor; // 0 Fork specific revision number
int ForkMinor; // 3
int ForkPatch; // ?
FGitVersion()
: Major(0)
, Minor(0)
, Patch(0)
, bIsFork(false)
, ForkMajor(0)
, ForkMinor(0)
, ForkPatch(0)
{
}
};
class GITSOURCECONTROL_API FGitSourceControlProvider final : public ISourceControlProvider
{
public:
/* ISourceControlProvider implementation */
virtual void Init(bool bForceConnection = true) override;
virtual void Close() override;
virtual FText GetStatusText() const override;
virtual bool IsEnabled() const override;
virtual bool IsAvailable() const override;
virtual const FName& GetName(void) const override;
virtual bool QueryStateBranchConfig(const FString& ConfigSrc, const FString& ConfigDest) override;
virtual void RegisterStateBranches(const TArray<FString>& BranchNames, const FString& ContentRootIn) override;
virtual int32 GetStateBranchIndex(const FString& BranchName) const override;
virtual ECommandResult::Type GetState( const TArray<FString>& InFiles, TArray<FSourceControlStateRef>& OutState, EStateCacheUsage::Type InStateCacheUsage ) override;
#if ENGINE_MAJOR_VERSION >= 5
virtual ECommandResult::Type GetState(const TArray<FSourceControlChangelistRef>& InChangelists, TArray<FSourceControlChangelistStateRef>& OutState, EStateCacheUsage::Type InStateCacheUsage) override;
#endif
virtual TArray<FSourceControlStateRef> GetCachedStateByPredicate(TFunctionRef<bool(const FSourceControlStateRef&)> Predicate) const override;
virtual FDelegateHandle RegisterSourceControlStateChanged_Handle(const FSourceControlStateChanged::FDelegate& SourceControlStateChanged) override;
virtual void UnregisterSourceControlStateChanged_Handle(FDelegateHandle Handle) override;
#if ENGINE_MAJOR_VERSION < 5
virtual ECommandResult::Type Execute( const FSourceControlOperationRef& InOperation, const TArray<FString>& InFiles, EConcurrency::Type InConcurrency = EConcurrency::Synchronous, const FSourceControlOperationComplete& InOperationCompleteDelegate = FSourceControlOperationComplete()) override;
virtual bool CanCancelOperation( const FSourceControlOperationRef& InOperation ) const override;
virtual void CancelOperation( const FSourceControlOperationRef& InOperation ) override;
#else
virtual ECommandResult::Type Execute(const FSourceControlOperationRef& InOperation, FSourceControlChangelistPtr InChangelist, const TArray<FString>& InFiles, EConcurrency::Type InConcurrency = EConcurrency::Synchronous, const FSourceControlOperationComplete& InOperationCompleteDelegate = FSourceControlOperationComplete()) override;
virtual bool CanCancelOperation( const FSourceControlOperationRef& InOperation ) const override;
virtual void CancelOperation( const FSourceControlOperationRef& InOperation ) override;
#endif
virtual bool UsesLocalReadOnlyState() const override;
virtual bool UsesChangelists() const override;
virtual bool UsesCheckout() const override;
#if ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION >= 1
virtual bool UsesFileRevisions() const override;
#if ENGINE_MINOR_VERSION >= 8
virtual TOptional<bool> HasChangesToSync() const override;
virtual TOptional<bool> HasChangesToCheckIn() const override;
#else
virtual TOptional<bool> IsAtLatestRevision() const override;
virtual TOptional<int> GetNumLocalChanges() const override;
#endif
#endif
#if ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION >= 2
virtual bool AllowsDiffAgainstDepot() const override;
virtual bool UsesUncontrolledChangelists() const override;
virtual bool UsesSnapshots() const override;
#endif
#if ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION >= 8
virtual bool UsesSoftRevertOnDelete() const override;
#endif
#if ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION >= 3
virtual bool CanExecuteOperation( const FSourceControlOperationRef& InOperation ) const override;
virtual TMap<EStatus, FString> GetStatus() const override;
#endif
virtual void Tick() override;
virtual TArray< TSharedRef<class ISourceControlLabel> > GetLabels( const FString& InMatchingSpec ) const override;
#if ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION >= 7
virtual bool GetStateBranchAtIndex(int32 BranchIndex, FString& OutBranchName) const override;
#endif
#if ENGINE_MAJOR_VERSION >= 5
virtual TArray<FSourceControlChangelistRef> GetChangelists( EStateCacheUsage::Type InStateCacheUsage ) override;
#endif
#if SOURCE_CONTROL_WITH_SLATE
virtual TSharedRef<class SWidget> MakeSettingsWidget() const override;
#endif
using ISourceControlProvider::Execute;
/**
* Check configuration, else standard paths, and run a Git "version" command to check the availability of the binary.
*/
void CheckGitAvailability();
/** Refresh Git settings from revision control settings */
void UpdateSettings();
/**
* Find the .git/ repository and check its status.
*/
void CheckRepositoryStatus();
/** Is git binary found and working. */
inline bool IsGitAvailable() const
{
return bGitAvailable;
}
/** Git version for feature checking */
inline const FGitVersion& GetGitVersion() const
{
return GitVersion;
}
/** Path to the root of the Unreal revision control repository: usually the ProjectDir */
inline const FString& GetPathToRepositoryRoot() const
{
return PathToRepositoryRoot;
}
/** Path to the root of the Git repository: can be the ProjectDir itself, or any parent directory (found by the "Connect" operation) */
inline const FString& GetPathToGitRoot() const
{
return PathToGitRoot;
}
/** Gets the path to the Git binary */
inline const FString& GetGitBinaryPath() const
{
return PathToGitBinary;
}
/** Git config user.name */
inline const FString& GetUserName() const
{
return UserName;
}
/** Git config user.email */
inline const FString& GetUserEmail() const
{
return UserEmail;
}
/** Git remote origin url */
inline const FString& GetRemoteUrl() const
{
return RemoteUrl;
}
inline const FString& GetLockUser() const
{
return LockUser;
}
/** Helper function used to update state cache */
TSharedRef<FGitSourceControlState, ESPMode::ThreadSafe> GetStateInternal(const FString& Filename);
#if ENGINE_MAJOR_VERSION == 5
/** Helper function used to update changelists state cache */
TSharedRef<FGitSourceControlChangelistState, ESPMode::ThreadSafe> GetStateInternal(const FGitSourceControlChangelist& InChangelist);
#endif
/**
* Register a worker with the provider.
* This is used internally so the provider can maintain a map of all available operations.
*/
void RegisterWorker( const FName& InName, const FGetGitSourceControlWorker& InDelegate );
/** Set list of error messages that occurred after last perforce command */
void SetLastErrors(const TArray<FText>& InErrors);
/** Get list of error messages that occurred after last perforce command */
TArray<FText> GetLastErrors() const;
/** Get number of error messages seen after running last perforce command */
int32 GetNumLastErrors() const;
/** Remove a named file from the state cache */
bool RemoveFileFromCache(const FString& Filename);
/** Get files in cache */
TArray<FString> GetFilesInCache();
bool AddFileToIgnoreForceCache(const FString& Filename);
bool RemoveFileFromIgnoreForceCache(const FString& Filename);
const FString& GetBranchName() const
{
return BranchName;
}
const FString& GetRemoteBranchName() const { return RemoteBranchName; }
TArray<FString> GetStatusBranchNames() const;
/** Indicates editor binaries are to be updated upon next sync */
bool bPendingRestart;
#if ENGINE_MAJOR_VERSION >= 5
uint32 TicksUntilNextForcedUpdate = 0;
#endif
private:
/** Is git binary found and working. */
bool bGitAvailable = false;
/** Is git repository found. */
bool bGitRepositoryFound = false;
/** Is LFS locking enabled? */
bool bUsingGitLfsLocking = false;
FString PathToGitBinary;
FString LockUser;
/** Critical section for thread safety of error messages that occurred after last perforce command */
mutable FCriticalSection LastErrorsCriticalSection;
/** List of error messages that occurred after last perforce command */
TArray<FText> LastErrors;
/** Helper function for Execute() */
TSharedPtr<class IGitSourceControlWorker, ESPMode::ThreadSafe> CreateWorker(const FName& InOperationName) const;
/** Helper function for running command synchronously. */
ECommandResult::Type ExecuteSynchronousCommand(class FGitSourceControlCommand& InCommand, const FText& Task, bool bSuppressResponseMsg);
/** Issue a command asynchronously if possible. */
ECommandResult::Type IssueCommand(class FGitSourceControlCommand& InCommand, const bool bSynchronous = false );
/** Output any messages this command holds */
void OutputCommandMessages(const class FGitSourceControlCommand& InCommand) const;
/** Update repository status on Connect and UpdateStatus operations */
void UpdateRepositoryStatus(const class FGitSourceControlCommand& InCommand);
/** Path to the root of the Unreal revision control repository: usually the ProjectDir */
FString PathToRepositoryRoot;
/** Path to the root of the Git repository: can be the ProjectDir itself, or any parent directory (found by the "Connect" operation) */
FString PathToGitRoot;
/** Git config user.name (from local repository, else globally) */
FString UserName;
/** Git config user.email (from local repository, else globally) */
FString UserEmail;
/** Name of the current branch */
FString BranchName;
/** Name of the current remote branch */
FString RemoteBranchName;
/** URL of the "origin" default remote server */
FString RemoteUrl;
/** Current Commit full SHA1 */
FString CommitId;
/** Current Commit description's Summary */
FString CommitSummary;
/** State cache */
TMap<FString, TSharedRef<class FGitSourceControlState, ESPMode::ThreadSafe> > StateCache;
#if ENGINE_MAJOR_VERSION == 5
TMap<FGitSourceControlChangelist, TSharedRef<class FGitSourceControlChangelistState, ESPMode::ThreadSafe> > ChangelistsStateCache;
#endif
/** The currently registered revision control operations */
TMap<FName, FGetGitSourceControlWorker> WorkersMap;
/** Queue for commands given by the main thread */
TArray < FGitSourceControlCommand* > CommandQueue;
/** For notifying when the revision control states in the cache have changed */
FSourceControlStateChanged OnSourceControlStateChanged;
/** Git version for feature checking */
FGitVersion GitVersion;
/** Revision Control Menu Extension */
FGitSourceControlMenu GitSourceControlMenu;
/**
Ignore these files when forcing status updates. We add to this list when we've just updated the status already.
UE's SourceControl has a habit of performing a double status update, immediately after an operation.
*/
TArray<FString> IgnoreForceCache;
/** Array of branch name patterns for status queries */
TArray<FString> StatusBranchNamePatternsInternal;
class FGitSourceControlRunner* Runner = nullptr;
};
@@ -0,0 +1,80 @@
// 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)
#pragma once
#include "ISourceControlRevision.h"
#include "Runtime/Launch/Resources/Version.h"
#include "Misc/DateTime.h"
/** Revision of a file, linked to a specific commit */
class FGitSourceControlRevision : public ISourceControlRevision
{
public:
/** ISourceControlRevision interface */
#if ENGINE_MAJOR_VERSION >= 5
virtual bool Get( FString& InOutFilename, EConcurrency::Type InConcurrency = EConcurrency::Synchronous ) const override;
#else
virtual bool Get( FString& InOutFilename ) const override;
#endif
virtual bool GetAnnotated( TArray<FAnnotationLine>& OutLines ) const override;
virtual bool GetAnnotated( FString& InOutFilename ) const override;
virtual const FString& GetFilename() const override;
virtual int32 GetRevisionNumber() const override;
virtual const FString& GetRevision() const override;
virtual const FString& GetDescription() const override;
virtual const FString& GetUserName() const override;
virtual const FString& GetClientSpec() const override;
virtual const FString& GetAction() const override;
virtual TSharedPtr<class ISourceControlRevision, ESPMode::ThreadSafe> GetBranchSource() const override;
virtual const FDateTime& GetDate() const override;
virtual int32 GetCheckInIdentifier() const override;
virtual int32 GetFileSize() const override;
public:
/** The filename this revision refers to */
FString Filename;
/** The full hexadecimal SHA1 id of the commit this revision refers to */
FString CommitId;
/** The short hexadecimal SHA1 id (8 first hex char out of 40) of the commit: the string to display */
FString ShortCommitId;
/** The numeric value of the short SHA1 (8 first hex char out of 40) */
int32 CommitIdNumber = 0;
/** The index of the revision in the history (SBlueprintRevisionMenu assumes order for the "Depot" label) */
int32 RevisionNumber = 0;
/** The SHA1 identifier of the file at this revision */
FString FileHash;
/** The description of this revision */
FString Description;
/** The user that made the change */
FString UserName;
/** The action (add, edit, branch etc.) performed at this revision */
FString Action;
/** Source of move ("branch" in Perforce term) if any */
TSharedPtr<FGitSourceControlRevision, ESPMode::ThreadSafe> BranchSource;
/** The date this revision was made */
FDateTime Date;
/** The size of the file at this revision */
int32 FileSize;
/** Dynamic repository root **/
FString PathToRepoRoot;
};
/** History composed of the last 100 revisions of the file */
typedef TArray< TSharedRef<FGitSourceControlRevision, ESPMode::ThreadSafe> > TGitSourceControlHistory;
@@ -0,0 +1,50 @@
// 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 "Containers/UnrealString.h"
#include "HAL/CriticalSection.h"
class GITSOURCECONTROL_API FGitSourceControlSettings
{
public:
/** Get the Git Binary Path */
const FString & GetBinaryPath() const;
/** Set the Git Binary Path */
bool SetBinaryPath(const FString& InString);
/** Tell if using the Git LFS file Locking workflow */
bool IsUsingGitLfsLocking() const;
/** Configure the usage of Git LFS file Locking workflow */
bool SetUsingGitLfsLocking(const bool InUsingGitLfsLocking);
/** Get the username used by the Git LFS 2 File Locks server */
const FString GetLfsUserName() const;
/** Set the username used by the Git LFS 2 File Locks server */
bool SetLfsUserName(const FString& InString);
/** Load settings from ini file */
void LoadSettings();
/** Save settings to ini file */
void SaveSettings() const;
private:
/** A critical section for settings access */
mutable FCriticalSection CriticalSection;
/** Git binary path */
FString BinaryPath;
/** Tells if using the Git LFS file Locking workflow */
bool bUsingGitLfsLocking = true;
/** Username used by the Git LFS 2 File Locks server */
FString LfsUserName;
};
@@ -0,0 +1,223 @@
// 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 "GitSourceControlChangelist.h"
#include "GitSourceControlRevision.h"
#include "Runtime/Launch/Resources/Version.h"
/** A consolidation of state priorities. */
namespace EGitState
{
enum Type
{
Unset,
NotAtHead,
#if 0
AddedAtHead,
DeletedAtHead,
#endif
LockedOther,
NotLatest,
/** Unmerged state (modified, but conflicts) */
Unmerged,
Added,
Deleted,
Modified,
/** Not modified, but locked explicitly. */
CheckedOut,
Untracked,
Lockable,
Unmodified,
Ignored,
/** Whatever else. */
None,
};
}
/** Corresponds to diff file states. */
namespace EFileState
{
enum Type
{
Unset,
Unknown,
Added,
Copied,
Deleted,
Modified,
Renamed,
Missing,
Unmerged,
};
}
/** Where in the world is this file? */
namespace ETreeState
{
enum Type
{
Unset,
/** This file is synced to commit */
Unmodified,
/** This file is modified, but not in staging tree */
Working,
/** This file is in staging tree (git add) */
Staged,
/** This file is not tracked in the repo yet */
Untracked,
/** This file is ignored by the repo */
Ignored,
/** This file is outside the repo folder */
NotInRepo,
};
}
/** LFS locks status of this file */
namespace ELockState
{
enum Type
{
Unset,
Unknown,
Unlockable,
NotLocked,
Locked,
LockedOther,
};
}
/** What is this file doing at HEAD? */
namespace ERemoteState
{
enum Type
{
Unset,
/** Up to date */
UpToDate,
/** Local version is behind remote */
NotAtHead,
#if 0
// TODO: handle these
/** Remote file does not exist on local */
AddedAtHead,
/** Local was deleted on remote */
DeletedAtHead,
#endif
/** Not at the latest revision amongst the tracked branches */
NotLatest,
};
}
/** Combined state, for updating cache in a map. */
struct FGitState
{
EFileState::Type FileState = EFileState::Unknown;
ETreeState::Type TreeState = ETreeState::NotInRepo;
ELockState::Type LockState = ELockState::Unknown;
/** Name of user who has locked the file */
FString LockUser;
ERemoteState::Type RemoteState = ERemoteState::UpToDate;
/** The branch with the latest commit for this file */
FString HeadBranch;
};
class GITSOURCECONTROL_API FGitSourceControlState : public ISourceControlState
{
public:
explicit FGitSourceControlState(const FString &InLocalFilename) :
LocalFilename( InLocalFilename ),
TimeStamp( 0 ),
HeadAction( TEXT( "Changed" ) ),
HeadModTime( 0 ),
HeadCommit( TEXT( "Unknown" ) )
{
}
/** ISourceControlState interface */
virtual int32 GetHistorySize() const override;
virtual TSharedPtr<class ISourceControlRevision, ESPMode::ThreadSafe> GetHistoryItem(int32 HistoryIndex) const override;
virtual TSharedPtr<class ISourceControlRevision, ESPMode::ThreadSafe> FindHistoryRevision(int32 RevisionNumber) const override;
virtual TSharedPtr<class ISourceControlRevision, ESPMode::ThreadSafe> FindHistoryRevision(const FString& InRevision) const override;
#if ENGINE_MAJOR_VERSION < 5 || ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION < 3
virtual TSharedPtr<class ISourceControlRevision, ESPMode::ThreadSafe> GetBaseRevForMerge() const override;
#else
virtual FResolveInfo GetResolveInfo() const override;
#endif
#if ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION >= 2
virtual TSharedPtr<class ISourceControlRevision, ESPMode::ThreadSafe> GetCurrentRevision() const override;
#endif
#if ENGINE_MAJOR_VERSION >= 5
virtual FSlateIcon GetIcon() const override;
#else
virtual FName GetIconName() const override;
virtual FName GetSmallIconName() const override;
#endif
virtual FText GetDisplayName() const override;
virtual FText GetDisplayTooltip() const override;
virtual const FString& GetFilename() const override;
virtual const FDateTime& GetTimeStamp() const override;
virtual bool CanCheckIn() const override;
virtual bool CanCheckout() const override;
virtual bool IsCheckedOut() const override;
virtual bool IsCheckedOutOther(FString* Who = NULL) const override;
virtual bool IsCheckedOutInOtherBranch(const FString& CurrentBranch = FString()) const override;
virtual bool IsModifiedInOtherBranch(const FString& CurrentBranch = FString()) const override;
virtual bool IsCheckedOutOrModifiedInOtherBranch(const FString& CurrentBranch = FString()) const override { return IsModifiedInOtherBranch(CurrentBranch); }
virtual TArray<FString> GetCheckedOutBranches() const override { return TArray<FString>(); }
virtual FString GetOtherUserBranchCheckedOuts() const override { return FString(); }
virtual bool GetOtherBranchHeadModification(FString& HeadBranchOut, FString& ActionOut, int32& HeadChangeListOut) const override;
virtual bool IsCurrent() const override;
virtual bool IsSourceControlled() const override;
virtual bool IsAdded() const override;
virtual bool IsDeleted() const override;
virtual bool IsIgnored() const override;
virtual bool CanEdit() const override;
virtual bool IsUnknown() const override;
virtual bool IsModified() const override;
virtual bool CanAdd() const override;
virtual bool CanDelete() const override;
virtual bool IsConflicted() const override;
virtual bool CanRevert() const override;
private:
EGitState::Type GetGitState() const;
public:
/** History of the item, if any */
TGitSourceControlHistory History;
/** Filename on disk */
FString LocalFilename;
#if ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION >= 3
/** Pending rev info with which a file must be resolved, invalid if no resolve pending */
FResolveInfo PendingResolveInfo;
UE_DEPRECATED(5.3, "Use PendingResolveInfo.BaseRevision instead")
#endif
/** File Id with which our local revision diverged from the remote revision */
FString PendingMergeBaseFileHash;
/** Status of the file */
FGitState State;
#if ENGINE_MAJOR_VERSION == 5
FGitSourceControlChangelist Changelist;
#endif
/** The timestamp of the last update */
FDateTime TimeStamp;
/** The action within the head branch TODO */
FString HeadAction;
/** The last file modification time in the head branch TODO */
int64 HeadModTime;
/** The change list the last modification TODO */
FString HeadCommit;
};
@@ -0,0 +1,393 @@
// 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 "GitSourceControlRevision.h"
#include "GitSourceControlState.h"
#include "Runtime/Launch/Resources/Version.h"
#if ENGINE_MAJOR_VERSION == 5
#include "UObject/ObjectSaveContext.h"
#endif
class FGitSourceControlState;
class FGitSourceControlCommand;
/**
* Helper struct for maintaining temporary files for passing to commands
*/
class FGitScopedTempFile
{
public:
/** Constructor - open & write string to temp file */
FGitScopedTempFile(const FText& InText);
/** Destructor - delete temp file */
~FGitScopedTempFile();
/** Get the filename of this temp file - empty if it failed to be created */
const FString& GetFilename() const;
private:
/** The filename we are writing to */
FString Filename;
};
struct FGitVersion;
class FGitLockedFilesCache
{
public:
static FDateTime LastUpdated;
static const TMap<FString, FString>& GetLockedFiles() { return LockedFiles; }
static void SetLockedFiles(const TMap<FString, FString>& newLocks);
static void AddLockedFile(const FString& filePath, const FString& lockUser);
static void RemoveLockedFile(const FString& filePath);
private:
static void OnFileLockChanged(const FString& filePath, const FString& lockUser, bool locked);
// update local read/write state when our own lock statuses change
static TMap<FString, FString> LockedFiles;
};
namespace GitSourceControlUtils
{
/**
* Returns an updated repo root if all selected files are in a plugin subfolder, and the plugin subfolder is a git repo
* This supports the case where each plugin is a sub module
*
* @param AbsoluteFilePaths The list of files in the SC operation
* @param PathToRepositoryRoot The original path to the repository root (used by default)
*/
FString ChangeRepositoryRootIfSubmodule(TArray<FString>& AbsoluteFilePaths, const FString& PathToRepositoryRoot);
/**
* Returns an updated repo root if all selected file is in a plugin subfolder, and the plugin subfolder is a git repo
* This supports the case where each plugin is a sub module
*
* @param AbsoluteFilePath The file in the SC operation
* @param PathToRepositoryRoot The original path to the repository root (used by default)
*/
FString ChangeRepositoryRootIfSubmodule(FString & AbsoluteFilePath, const FString& PathToRepositoryRoot);
/**
* Find the path to the Git binary, looking into a few places (standalone Git install, and other common tools embedding Git)
* @returns the path to the Git binary if found, or an empty string.
*/
FString FindGitBinaryPath();
/**
* Run a Git "version" command to check the availability of the binary.
* @param InPathToGitBinary The path to the Git binary
* @param OutGitVersion If provided, populate with the git version parsed from "version" command
* @returns true if the command succeeded and returned no errors
*/
bool CheckGitAvailability(const FString& InPathToGitBinary, FGitVersion* OutVersion = nullptr);
/**
* Parse the output from the "version" command into GitMajorVersion and GitMinorVersion.
* @param InVersionString The version string returned by `git --version`
* @param OutVersion The FGitVersion to populate
*/
void ParseGitVersion(const FString& InVersionString, FGitVersion* OutVersion);
/**
* Check git for various optional capabilities by various means.
* @param InPathToGitBinary The path to the Git binary
* @param OutGitVersion If provided, populate with the git version parsed from "version" command
*/
void FindGitCapabilities(const FString& InPathToGitBinary, FGitVersion* OutVersion);
/**
* Run a Git "lfs" command to check the availability of the "Large File System" extension.
* @param InPathToGitBinary The path to the Git binary
* @param OutGitVersion If provided, populate with the git version parsed from "version" command
*/
void FindGitLfsCapabilities(const FString& InPathToGitBinary, FGitVersion* OutVersion);
/**
* Find the root of the Git repository, looking from the provided path and upward in its parent directories
* @param InPath The path to the Game Directory (or any path or file in any git repository)
* @param OutRepositoryRoot The path to the root directory of the Git repository if found, else the path to the ProjectDir
* @returns true if the command succeeded and returned no errors
*/
bool FindRootDirectory(const FString& InPath, FString& OutRepositoryRoot);
/**
* Get Git config user.name & user.email
* @param InPathToGitBinary The path to the Git binary
* @param InRepositoryRoot The Git repository from where to run the command - usually the Game directory (can be empty)
* @param OutUserName Name of the Git user configured for this repository (or globaly)
* @param OutEmailName E-mail of the Git user configured for this repository (or globaly)
*/
void GetUserConfig(const FString& InPathToGitBinary, const FString& InRepositoryRoot, FString& OutUserName, FString& OutUserEmail);
/**
* Get Git current checked-out branch
* @param InPathToGitBinary The path to the Git binary
* @param InRepositoryRoot The Git repository from where to run the command - usually the Game directory
* @param OutBranchName Name of the current checked-out branch (if any, ie. not in detached HEAD)
* @returns true if the command succeeded and returned no errors
*/
bool GetBranchName(const FString& InPathToGitBinary, const FString& InRepositoryRoot, FString& OutBranchName);
/**
* Get Git remote tracking branch
* @returns false if the branch is not tracking a remote
*/
bool GetRemoteBranchName(const FString& InPathToGitBinary, const FString& InRepositoryRoot, FString& OutBranchName);
/**
* Get Git remote tracking branches that match wildcard
* @returns false if no matching branches
*/
bool GetRemoteBranchesWildcard(const FString& InPathToGitBinary, const FString& InRepositoryRoot, const FString& PatternMatch, TArray<FString>& OutBranchNames);
/**
* Get Git current commit details
* @param InPathToGitBinary The path to the Git binary
* @param InRepositoryRoot The Git repository from where to run the command - usually the Game directory
* @param OutCommitId Current Commit full SHA1
* @param OutCommitSummary Current Commit description's Summary
* @returns true if the command succeeded and returned no errors
*/
bool GetCommitInfo(const FString& InPathToGitBinary, const FString& InRepositoryRoot, FString& OutCommitId, FString& OutCommitSummary);
/**
* Get the URL of the "origin" defaut remote server
* @param InPathToGitBinary The path to the Git binary
* @param InRepositoryRoot The Git repository from where to run the command - usually the Game directory
* @param OutRemoteUrl URL of "origin" defaut remote server
* @returns true if the command succeeded and returned no errors
*/
bool GetRemoteUrl(const FString& InPathToGitBinary, const FString& InRepositoryRoot, FString& OutRemoteUrl);
// A list of asset paths that we will always check locks, status, etc on startup and periodically
TArray<FString> GetSourceControlledAssetPaths();
/**
* Run a Git command - output is a string TArray.
*
* @param InCommand The Git command - e.g. commit
* @param InPathToGitBinary The path to the Git binary
* @param InRepositoryRoot The Git repository from where to run the command - usually the Game directory (can be empty)
* @param InParameters The parameters to the Git command
* @param InFiles The files to be operated on
* @param OutResults The results (from StdOut) as an array per-line
* @param OutErrorMessages Any errors (from StdErr) as an array per-line
* @returns true if the command succeeded and returned no errors
*/
GITSOURCECONTROL_API bool RunCommand( const FString & InCommand, const FString & InPathToGitBinary, const FString & InRepositoryRoot, const TArray< FString > & InParameters, const TArray< FString > & InFiles, TArray< FString > & OutResults, TArray< FString > & OutErrorMessages );
bool RunCommandInternalRaw(const FString& InCommand, const FString& InPathToGitBinary, const FString& InRepositoryRoot, const TArray<FString>& InParameters, const TArray<FString>& InFiles, FString& OutResults, FString& OutErrors, const int32 ExpectedReturnCode = 0);
/**
* Unloads packages of specified named files
*/
TArray<class UPackage*> UnlinkPackages(const TArray<FString>& InPackageNames);
/**
* Reloads packages for these packages
*/
void ReloadPackages(TArray<UPackage*>& InPackagesToReload);
/**
* Gets all Git tracked files, including within directories, recursively
*/
bool ListFilesInDirectoryRecurse(const FString& InPathToGitBinary, const FString& InRepositoryRoot, const FString& InDirectory, TArray<FString>& OutFiles);
/**
* Run a Git "commit" command by batches.
*
* @param InPathToGitBinary The path to the Git binary
* @param InRepositoryRoot The Git repository from where to run the command - usually the Game directory
* @param InParameter The parameters to the Git commit command
* @param InFiles The files to be operated on
* @param OutErrorMessages Any errors (from StdErr) as an array per-line
* @returns true if the command succeeded and returned no errors
*/
bool RunCommit(const FString& InPathToGitBinary, const FString& InRepositoryRoot, const TArray<FString>& InParameters, const TArray<FString>& InFiles, TArray<FString>& OutResults, TArray<FString>& OutErrorMessages);
/**
* @brief Detects how to parse the result of a "status" command to get workspace file states
*
* It is either a command for a whole directory (ie. "Content/", in case of "Submit to Revision Control" menu),
* or for one or more files all on a same directory (by design, since we group files by directory in RunUpdateStatus())
*
* @param[in] InPathToGitBinary The path to the Git binary
* @param[in] InRepositoryRoot The Git repository from where to run the command - usually the Game directory (can be empty)
* @param[in] InUsingLfsLocking Tells if using the Git LFS file Locking workflow
* @param[in] InFiles List of files in a directory, or the path to the directory itself (never empty).
* @param[out] InResults Results from the "status" command
* @param[out] OutStates States of files for witch the status has been gathered (distinct than InFiles in case of a "directory status")
*/
GITSOURCECONTROL_API void ParseStatusResults( const FString & InPathToGitBinary, const FString & InRepositoryRoot, const bool InUsingLfsLocking, const TArray< FString > & InFiles, const TMap< FString, FString > & InResults, TMap< FString, FGitSourceControlState > & OutStates );
/**
* Checks remote branches to see file differences.
*
* @param CurrentBranchName The current branch we are on.
* @param InPathToGitBinary The path to the Git binary
* @param InRepositoryRoot The Git repository from where to run the command - usually the Game directory
* @param OnePath The file to be checked
* @param OutErrorMessages Any errors (from StdErr) as an array per-line
*/
void CheckRemote(const FString& InPathToGitBinary, const FString& InRepositoryRoot, const TArray<FString>& Files,
TArray<FString>& OutErrorMessages, TMap<FString, FGitSourceControlState>& OutStates);
/**
* Run a Git "status" command and parse it.
*
* @param InPathToGitBinary The path to the Git binary
* @param InRepositoryRoot The Git repository from where to run the command - usually the Game directory (can be empty)
* @param InUsingLfsLocking Tells if using the Git LFS file Locking workflow
* @param InFiles The files to be operated on
* @param OutErrorMessages Any errors (from StdErr) as an array per-line
* @param OutStates The resultant states
* @returns true if the command succeeded and returned no errors
*/
bool RunUpdateStatus(const FString& InPathToGitBinary, const FString& InRepositoryRoot, const bool InUsingLfsLocking, const TArray<FString>& InFiles,
TArray<FString>& OutErrorMessages, TMap<FString, FGitSourceControlState>& OutStates);
#if ENGINE_MAJOR_VERSION == 5
/**
* Keep Consistency of being file staged
*
* @param Filename Saved filename
* @param Pkg Package (for adapting delegate)
* @param ObjectSaveContext Context for save (for adapting delegate)
*/
void UpdateFileStagingOnSaved(const FString& Filename, UPackage* Pkg, FObjectPostSaveContext ObjectSaveContext);
#endif
/**
* Keep Consistency of being file staged with simple argument
*
* @param Filename Saved filename
*/
bool UpdateFileStagingOnSavedInternal(const FString& Filename);
/**
*
*
* @param Filename Saved filename
* @param Pkg Package (for adapting delegate)
* @param ObjectSaveContext Context for save (for adapting delegate)
*/
void UpdateStateOnAssetRename(const FAssetData& InAssetData, const FString& InOldName);
#if ENGINE_MAJOR_VERSION == 5
/**
*
*
* @param Filename Saved filename
* @param Pkg Package (for adapting delegate)
* @param ObjectSaveContext Context for save (for adapting delegate)
*/
bool UpdateChangelistStateByCommand();
#endif
/**
* Run a Git "cat-file" command to dump the binary content of a revision into a file.
*
* @param InPathToGitBinary The path to the Git binary
* @param InRepositoryRoot The Git repository from where to run the command - usually the Game directory
* @param InParameter The parameters to the Git show command (rev:path)
* @param InDumpFileName The temporary file to dump the revision
* @returns true if the command succeeded and returned no errors
*/
bool RunDumpToFile(const FString& InPathToGitBinary, const FString& InRepositoryRoot, const FString& InParameter, const FString& InDumpFileName);
/**
* Run a Git "log" command and parse it.
*
* @param InPathToGitBinary The path to the Git binary
* @param InRepositoryRoot The Git repository from where to run the command - usually the Game directory
* @param InFile The file to be operated on
* @param bMergeConflict In case of a merge conflict, we also need to get the tip of the "remote branch" (MERGE_HEAD) before the log of the "current branch" (HEAD)
* @param OutErrorMessages Any errors (from StdErr) as an array per-line
* @param OutHistory The history of the file
*/
bool RunGetHistory(const FString& InPathToGitBinary, const FString& InRepositoryRoot, const FString& InFile, bool bMergeConflict, TArray<FString>& OutErrorMessages, TGitSourceControlHistory& OutHistory);
/**
* Helper function to convert a filename array to relative paths.
* @param InFileNames The filename array
* @param InRelativeTo Path to the WorkspaceRoot
* @return an array of filenames, transformed into relative paths
*/
TArray<FString> RelativeFilenames(const TArray<FString>& InFileNames, const FString& InRelativeTo);
/**
* Helper function to convert a filename array to absolute paths.
* @param InFileNames The filename array (relative paths)
* @param InRelativeTo Path to the WorkspaceRoot
* @return an array of filenames, transformed into absolute paths
*/
TArray<FString> AbsoluteFilenames(const TArray<FString>& InFileNames, const FString& InRelativeTo);
/**
* Remove redundant errors (that contain a particular string) and also
* update the commands success status if all errors were removed.
*/
void RemoveRedundantErrors(FGitSourceControlCommand& InCommand, const FString& InFilter);
bool RunLFSCommand(const FString& InCommand, const FString& InRepositoryRoot, const FString& GitBinaryFallback, const TArray<FString>& InParameters, const TArray<FString>& InFiles, TArray<FString>& OutResults, TArray<FString>& OutErrorMessages);
/**
* Helper function for various commands to update cached states.
* @returns true if any states were updated
*/
GITSOURCECONTROL_API bool UpdateCachedStates( const TMap< const FString, FGitState > & InResults );
/**
* Helper function for various commands to collect new states.
* @returns true if any states were updated
*/
GITSOURCECONTROL_API bool CollectNewStates( const TMap< FString, FGitSourceControlState > & InStates, TMap< const FString, FGitState > & OutResults );
/**
* Helper function for various commands to collect new states.
* @returns true if any states were updated
*/
bool CollectNewStates(const TArray<FString>& InFiles, TMap<const FString, FGitState>& OutResults, EFileState::Type FileState, ETreeState::Type TreeState = ETreeState::Unset, ELockState::Type LockState = ELockState::Unset, ERemoteState::Type RemoteState = ERemoteState::Unset);
/**
* Run 'git lfs locks" to extract all lock information for all files in the repository
*
* @param InRepositoryRoot The Git repository from where to run the command - usually the Game directory
* @param GitBinaryFallBack The Git binary fallback path
* @param OutErrorMessages Any errors (from StdErr) as an array per-line
* @param OutLocks The lock results (file, username)
* @returns true if the command succeeded and returned no errors
*/
bool GetAllLocks(const FString& InRepositoryRoot, const FString& GitBinaryFallBack, TArray<FString>& OutErrorMessages, TMap<FString, FString>& OutLocks, bool bInvalidateCache = false);
/**
* Gets locks from state cache
*/
void GetLockedFiles(const TArray<FString>& InFiles, TArray<FString>& OutFiles);
/**
* Checks cache for if this file type is lockable
*/
bool IsFileLFSLockable(const FString& InFile);
/**
* Gets Git attribute to see if these extensions are lockable
*/
bool CheckLFSLockable(const FString& InPathToGitBinary, const FString& InRepositoryRoot, const TArray<FString>& InFiles, TArray<FString>& OutErrorMessages);
GITSOURCECONTROL_API bool FetchRemote( const FString & InPathToGitBinary, const FString & InPathToRepositoryRoot, bool InUsingGitLfsLocking, TArray< FString > & OutResults, TArray< FString > & OutErrorMessages );
bool PullOrigin(const FString& InPathToGitBinary, const FString& InPathToRepositoryRoot, const TArray<FString>& InFiles, TArray<FString>& OutFiles,
TArray<FString>& OutResults, TArray<FString>& OutErrorMessages);
GITSOURCECONTROL_API TSharedPtr< class ISourceControlRevision, ESPMode::ThreadSafe > GetOriginRevisionOnBranch( const FString & InPathToGitBinary, const FString & InRepositoryRoot, const FString & InRelativeFileName, TArray< FString > & OutErrorMessages, const FString & BranchName );
}
@@ -0,0 +1,31 @@
// 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 "Templates/SharedPointer.h"
class IGitSourceControlWorker
{
public:
/**
* Name describing the work that this worker does. Used for factory method hookup.
*/
virtual FName GetName() const = 0;
/**
* Function that actually does the work. Can be executed on another thread.
*/
virtual bool Execute( class FGitSourceControlCommand& InCommand ) = 0;
/**
* Updates the state of any items after completion (if necessary). This is always executed on the main thread.
* @returns true if states were updated
*/
virtual bool UpdateStates() const = 0;
};
typedef TSharedRef<IGitSourceControlWorker, ESPMode::ThreadSafe> FGitSourceControlWorkerRef;