From 79f20ca768f8e47ae4f1d98d843ff7898aa09369 Mon Sep 17 00:00:00 2001 From: Guillaume Escarieux Date: Tue, 17 Jan 2023 15:21:44 +0100 Subject: [PATCH] feat: add support for git submodules in Plugins --- .../Private/GitSourceControlCommand.cpp | 8 +- .../Private/GitSourceControlCommand.h | 8 +- .../Private/GitSourceControlProvider.cpp | 3 +- .../Private/GitSourceControlRevision.cpp | 9 +- .../Private/GitSourceControlRevision.h | 5 +- .../Private/GitSourceControlUtils.cpp | 42 ++ .../Private/GitSourceControlUtils.h | 519 +++++++++--------- 7 files changed, 337 insertions(+), 257 deletions(-) diff --git a/Source/GitSourceControl/Private/GitSourceControlCommand.cpp b/Source/GitSourceControl/Private/GitSourceControlCommand.cpp index 23c5f5c..dbae915 100644 --- a/Source/GitSourceControl/Private/GitSourceControlCommand.cpp +++ b/Source/GitSourceControl/Private/GitSourceControlCommand.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020 Sebastien Rombauts (sebastien.rombauts@gmail.com) +// 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) @@ -7,6 +7,7 @@ #include "Modules/ModuleManager.h" #include "GitSourceControlModule.h" +#include "GitSourceControlUtils.h" FGitSourceControlCommand::FGitSourceControlCommand(const TSharedRef& InOperation, const TSharedRef& InWorker, const FSourceControlOperationComplete& InOperationCompleteDelegate) : Operation(InOperation) @@ -27,6 +28,11 @@ FGitSourceControlCommand::FGitSourceControlCommand(const TSharedRef& AbsoluteFilePaths) +{ + PathToRepositoryRoot = GitSourceControlUtils::ChangeRepositoryRootIfSubmodule(AbsoluteFilePaths, PathToRepositoryRoot); +} + bool FGitSourceControlCommand::DoWork() { bCommandSuccessful = Worker->Execute(*this); diff --git a/Source/GitSourceControl/Private/GitSourceControlCommand.h b/Source/GitSourceControl/Private/GitSourceControlCommand.h index d66d45d..29ab485 100644 --- a/Source/GitSourceControl/Private/GitSourceControlCommand.h +++ b/Source/GitSourceControl/Private/GitSourceControlCommand.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020 Sebastien Rombauts (sebastien.rombauts@gmail.com) +// 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) @@ -36,6 +36,12 @@ class FGitSourceControlCommand : public IQueuedWork FGitSourceControlCommand(const TSharedRef& InOperation, const TSharedRef& 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(const TArray& 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. diff --git a/Source/GitSourceControl/Private/GitSourceControlProvider.cpp b/Source/GitSourceControl/Private/GitSourceControlProvider.cpp index 7cb750a..1906bde 100644 --- a/Source/GitSourceControl/Private/GitSourceControlProvider.cpp +++ b/Source/GitSourceControl/Private/GitSourceControlProvider.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020 Sebastien Rombauts (sebastien.rombauts@gmail.com) +// 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) @@ -422,6 +422,7 @@ ECommandResult::Type FGitSourceControlProvider::Execute( const FSourceControlOpe FGitSourceControlCommand* Command = new FGitSourceControlCommand(InOperation, Worker.ToSharedRef()); Command->Files = AbsoluteFiles; + Command->UpdateRepositoryRootIfSubmodule(AbsoluteFiles); Command->OperationCompleteDelegate = InOperationCompleteDelegate; // fire off operation diff --git a/Source/GitSourceControl/Private/GitSourceControlRevision.cpp b/Source/GitSourceControl/Private/GitSourceControlRevision.cpp index 0efe64e..372933c 100644 --- a/Source/GitSourceControl/Private/GitSourceControlRevision.cpp +++ b/Source/GitSourceControl/Private/GitSourceControlRevision.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020 Sebastien Rombauts (sebastien.rombauts@gmail.com) +// 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) @@ -28,7 +28,12 @@ bool FGitSourceControlRevision::Get( FString& InOutFilename ) const const FGitSourceControlModule& GitSourceControl = FGitSourceControlModule::Get(); const FGitSourceControlProvider& Provider = GitSourceControl.GetProvider(); const FString PathToGitBinary = Provider.GetGitBinaryPath(); - const FString PathToRepositoryRoot = Provider.GetPathToRepositoryRoot(); + 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) diff --git a/Source/GitSourceControl/Private/GitSourceControlRevision.h b/Source/GitSourceControl/Private/GitSourceControlRevision.h index c8e7e3a..10ccbb1 100644 --- a/Source/GitSourceControl/Private/GitSourceControlRevision.h +++ b/Source/GitSourceControl/Private/GitSourceControlRevision.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020 Sebastien Rombauts (sebastien.rombauts@gmail.com) +// 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) @@ -71,6 +71,9 @@ class FGitSourceControlRevision : public ISourceControlRevision /** 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 */ diff --git a/Source/GitSourceControl/Private/GitSourceControlUtils.cpp b/Source/GitSourceControl/Private/GitSourceControlUtils.cpp index c7f20b5..7e2c0e7 100644 --- a/Source/GitSourceControl/Private/GitSourceControlUtils.cpp +++ b/Source/GitSourceControl/Private/GitSourceControlUtils.cpp @@ -83,6 +83,47 @@ TMap FGitLockedFilesCache::LockedFiles = TMap& AbsoluteFilePaths, const FString& PathToRepositoryRoot) + { + FString Ret = PathToRepositoryRoot; + FString PluginsRoot = FPaths::ConvertRelativePathToFull(FPaths::ProjectPluginsDir()); + // note this is not going to support operations where selected files are both in the root repo and the submodule/plugin's repo + int NumPluginFiles = 0; + + for (auto& FilePath : AbsoluteFilePaths) + { + if (FilePath.Contains(PluginsRoot)) + { + NumPluginFiles++; + } + } + // if all plugins? + // modify Source control base path + if ((NumPluginFiles == AbsoluteFilePaths.Num()) && (AbsoluteFilePaths.Num() > 0)) + { + FString FullPath = AbsoluteFilePaths[0]; + + FString PluginPart = FullPath.Replace(*PluginsRoot, *FString("")); + PluginPart = PluginPart.Left(PluginPart.Find("/")); + + + FString CandidateRepoRoot = PluginsRoot + PluginPart; + + FString IsItUsingGitPath = CandidateRepoRoot + "/.git"; + if (FPaths::FileExists(IsItUsingGitPath) || FPaths::DirectoryExists(IsItUsingGitPath)) + { + Ret = CandidateRepoRoot; + } + } + return Ret; + } + + FString ChangeRepositoryRootIfSubmodule(const FString& AbsoluteFilePath, const FString& PathToRepositoryRoot) + { + TArray AbsoluteFilePaths = { AbsoluteFilePath }; + return ChangeRepositoryRootIfSubmodule(AbsoluteFilePaths, PathToRepositoryRoot); + } + // Launch the Git command line process and extract its results & errors bool RunCommandInternalRaw(const FString& InCommand, const FString& InPathToGitBinary, const FString& InRepositoryRoot, const TArray& InParameters, const TArray& InFiles, FString& OutResults, FString& OutErrors, const int32 ExpectedReturnCode /* = 0 */) { @@ -1830,6 +1871,7 @@ bool RunGetHistory(const FString& InPathToGitBinary, const FString& InRepository Revision->FileHash = LsTree.FileHash; Revision->FileSize = LsTree.FileSize; } + Revision->PathToRepoRoot = InRepositoryRoot; } return bResults; diff --git a/Source/GitSourceControl/Private/GitSourceControlUtils.h b/Source/GitSourceControl/Private/GitSourceControlUtils.h index 299b803..c0d14d0 100644 --- a/Source/GitSourceControl/Private/GitSourceControlUtils.h +++ b/Source/GitSourceControl/Private/GitSourceControlUtils.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2020 Sebastien Rombauts (sebastien.rombauts@gmail.com) +// 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) @@ -43,254 +43,271 @@ class FGitLockedFilesCache namespace GitSourceControlUtils { - -/** - * 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 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); - -/** - * 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 - */ -bool RunCommand(const FString& InCommand, const FString& InPathToGitBinary, const FString& InRepositoryRoot, const TArray& InParameters, const TArray& InFiles, TArray& OutResults, TArray& OutErrorMessages); -bool RunCommandInternalRaw(const FString& InCommand, const FString& InPathToGitBinary, const FString& InRepositoryRoot, const TArray& InParameters, const TArray& InFiles, FString& OutResults, FString& OutErrors, const int32 ExpectedReturnCode = 0); - -/** - * Unloads packages of specified named files - */ -TArray UnlinkPackages(const TArray& InPackageNames); - -/** - * Reloads packages for these packages - */ -void ReloadPackages(TArray& InPackagesToReload); - -/** - * Gets all Git tracked files, including within directories, recursively - */ -bool ListFilesInDirectoryRecurse(const FString& InPathToGitBinary, const FString& InRepositoryRoot, const FString& InDirectory, TArray& 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& InParameters, const TArray& InFiles, TArray& OutResults, TArray& OutErrorMessages); - -/** - * 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& Files, - TArray& OutErrorMessages, TMap& 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& InFiles, - TArray& OutErrorMessages, TMap& OutStates); - -/** - * 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& 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 RelativeFilenames(const TArray& 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 AbsoluteFilenames(const TArray& 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& InParameters, const TArray& InFiles, TArray& OutResults, TArray& OutErrorMessages); - -/** - * Helper function for various commands to update cached states. - * @returns true if any states were updated - */ -bool UpdateCachedStates(const TMap& InResults); - -/** -* Helper function for various commands to collect new states. -* @returns true if any states were updated -*/ -bool CollectNewStates(const TMap& InStates, TMap& OutResults); - -/** - * Helper function for various commands to collect new states. - * @returns true if any states were updated - */ -bool CollectNewStates(const TArray& InFiles, TMap& 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 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& OutErrorMessages, TMap& OutLocks, bool bInvalidateCache = false); - -/** - * Gets locks from state cache - */ -void GetLockedFiles(const TArray& InFiles, TArray& 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& InFiles, TArray& OutErrorMessages); - -bool FetchRemote(const FString& InPathToGitBinary, const FString& InPathToRepositoryRoot, bool InUsingGitLfsLocking, TArray& OutResults, TArray& OutErrorMessages); - -bool PullOrigin(const FString& InPathToGitBinary, const FString& InPathToRepositoryRoot, const TArray& InFiles, TArray& OutFiles, - TArray& OutResults, TArray& OutErrorMessages); - + /** + * 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(const TArray& 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(const 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 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); + + /** + * 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 + */ + bool RunCommand(const FString& InCommand, const FString& InPathToGitBinary, const FString& InRepositoryRoot, const TArray& InParameters, const TArray& InFiles, TArray& OutResults, TArray& OutErrorMessages); + bool RunCommandInternalRaw(const FString& InCommand, const FString& InPathToGitBinary, const FString& InRepositoryRoot, const TArray& InParameters, const TArray& InFiles, FString& OutResults, FString& OutErrors, const int32 ExpectedReturnCode = 0); + + /** + * Unloads packages of specified named files + */ + TArray UnlinkPackages(const TArray& InPackageNames); + + /** + * Reloads packages for these packages + */ + void ReloadPackages(TArray& InPackagesToReload); + + /** + * Gets all Git tracked files, including within directories, recursively + */ + bool ListFilesInDirectoryRecurse(const FString& InPathToGitBinary, const FString& InRepositoryRoot, const FString& InDirectory, TArray& 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& InParameters, const TArray& InFiles, TArray& OutResults, TArray& OutErrorMessages); + + /** + * 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& Files, + TArray& OutErrorMessages, TMap& 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& InFiles, + TArray& OutErrorMessages, TMap& OutStates); + + /** + * 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& 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 RelativeFilenames(const TArray& 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 AbsoluteFilenames(const TArray& 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& InParameters, const TArray& InFiles, TArray& OutResults, TArray& OutErrorMessages); + + /** + * Helper function for various commands to update cached states. + * @returns true if any states were updated + */ + bool UpdateCachedStates(const TMap& InResults); + + /** + * Helper function for various commands to collect new states. + * @returns true if any states were updated + */ + bool CollectNewStates(const TMap& InStates, TMap& OutResults); + + /** + * Helper function for various commands to collect new states. + * @returns true if any states were updated + */ + bool CollectNewStates(const TArray& InFiles, TMap& 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& OutErrorMessages, TMap& OutLocks, bool bInvalidateCache = false); + + /** + * Gets locks from state cache + */ + void GetLockedFiles(const TArray& InFiles, TArray& 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& InFiles, TArray& OutErrorMessages); + + bool FetchRemote(const FString& InPathToGitBinary, const FString& InPathToRepositoryRoot, bool InUsingGitLfsLocking, TArray& OutResults, TArray& OutErrorMessages); + + bool PullOrigin(const FString& InPathToGitBinary, const FString& InPathToRepositoryRoot, const TArray& InFiles, TArray& OutFiles, + TArray& OutResults, TArray& OutErrorMessages); }