Add full git plugin
This commit is contained in:
12
Plugins/GitSourceControl/.gitignore
vendored
Normal file
12
Plugins/GitSourceControl/.gitignore
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
# PBGet
|
||||
Binaries/
|
||||
/Intermediate
|
||||
.DS_Store
|
||||
|
||||
### whitelisted files
|
||||
!Resources/*
|
||||
!Screenshots/*
|
||||
!git-lfs.exe
|
||||
!git-lfs
|
||||
!git-lfs-mac-amd64
|
||||
!git-lfs-mac-arm64
|
||||
@@ -8,11 +8,11 @@
|
||||
"CreatedBy": "Project Borealis",
|
||||
"CreatedByURL": "https://projectborealis.com",
|
||||
"DocsURL": "https://github.com/ProjectBorealis/UEGitPlugin/blob/release/README.md",
|
||||
"MarketplaceURL": "",
|
||||
"SupportURL": "https://github.com/ProjectBorealis/UEGitPlugin/issues",
|
||||
"EngineVersion": "5.6.0",
|
||||
"EnabledByDefault": true,
|
||||
"CanContainContent": false,
|
||||
"Installed": true,
|
||||
"IsBetaVersion": false,
|
||||
"Installed": false,
|
||||
"Modules": [
|
||||
{
|
||||
"Name": "GitSourceControl",
|
||||
|
||||
21
Plugins/GitSourceControl/LICENSE.txt
Normal file
21
Plugins/GitSourceControl/LICENSE.txt
Normal file
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2021-2023 Project Borealis
|
||||
Copyright (c) 2014-2020 Sebastien Rombauts (sebastien.rombauts@gmail.com)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is furnished
|
||||
to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
||||
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
216
Plugins/GitSourceControl/README.md
Normal file
216
Plugins/GitSourceControl/README.md
Normal file
@@ -0,0 +1,216 @@
|
||||
# Unreal Engine Git Plugin
|
||||
|
||||
This is a refactor of the [Git LFS 2 plugin by SRombauts](https://github.com/SRombauts/UE4GitPlugin), with lessons learned from production
|
||||
that include performance optimizations, new features and workflow improvements.
|
||||
|
||||
## Features
|
||||
|
||||
* Multi-threaded locking/unlocking, greatly improving performance when locking/unlocking many files
|
||||
* Remote lock status only checked when needed
|
||||
* Added local lock cache for speeding up local operations
|
||||
* Improved performance of repository file traversal
|
||||
* Improved initialization logic
|
||||
* Generally improved wording and UX throughout
|
||||
* Greatly improved pull within editor
|
||||
* Only refreshes changed files, which prevents crashes in large projects
|
||||
* Uses rebase workflow to properly manage local work
|
||||
* Added support for Status Branches, which check outdated files vs. remote across multiple branches
|
||||
* Periodic background remote refresh to keep remote file statuses up to date
|
||||
* Automatic handling of pushing from an outdated local copy
|
||||
* Optimized status updates for successful operations
|
||||
* Manage both lockable (assets, maps) and non-lockable files (configs, project file) in editor
|
||||
* Improved status display in editor
|
||||
* Integration with [PBSync](https://github.com/ProjectBorealis/PBSync) binaries syncing
|
||||
* General improvements to performance and memory usage
|
||||
|
||||
## Installation
|
||||
|
||||
Either install this into your project's `Plugins/` folder, or if you would like to install to the engine,
|
||||
rename `Engine/Plugins/Developer/GitSourceControl.uplugin` to `Engine/Plugins/Developer/GitSourceControl.uplugin.disabled`
|
||||
and then install this plugin to the `Engine/Plugins` folder.
|
||||
|
||||
Please note that we do not provide precompiled binaries at this time, so you will need to use Visual Studio to compile the plugin.
|
||||
|
||||
### Note about .gitattributes and .gitignore
|
||||
|
||||
This plugin requires explicit file attributes for `*.umap` and `*.uasset`, rather than other approaches of using wildcards for the content folder (`Content/**`).
|
||||
|
||||
See [our own `.gitattributes`](https://github.com/ProjectBorealis/PBCore/blob/main/.gitattributes) for an example.
|
||||
|
||||
You may also want to check out [our robust `.gitignore`](https://github.com/ProjectBorealis/PBCore/blob/main/.gitignore) too.
|
||||
|
||||
### Note about authentication
|
||||
|
||||
We would highly recommend using HTTPS authentication for your Git repo.
|
||||
|
||||
This allows a single credential path to be used, with the robust and fast HTTPS support in LFS.
|
||||
|
||||
With [Git Credential Manager](https://github.com/GitCredentialManager/git-credential-manager), authenticating with HTTPS is also much easier, with a GUI available to authenticate with any Git provider.
|
||||
|
||||
### Note about Unreal configuration
|
||||
|
||||
#### Required
|
||||
|
||||
* The plugin makes the assumption that files are always explicitly added. We made this decision because it is beneficial for performance and our workflows. In `Config/DefaultEditorPerProjectUserSettings.ini`
|
||||
|
||||
```ini
|
||||
[/Script/UnrealEd.EditorLoadingSavingSettings]
|
||||
bSCCAutoAddNewFiles=False
|
||||
```
|
||||
|
||||
#### Recommended
|
||||
|
||||
* As a general revision control usability improvement, you can enable new checkout features in `Config/DefaultEditorPerProjectUserSettings.ini`. To enable auto-checkout on modification, which is great for OFPA and other workflows (but requires user attention to excessive locking of content):
|
||||
|
||||
```ini
|
||||
[/Script/UnrealEd.EditorLoadingSavingSettings]
|
||||
bAutomaticallyCheckoutOnAssetModification=True
|
||||
bPromptForCheckoutOnAssetModification=False
|
||||
```
|
||||
|
||||
* OR, to enable auto-prompt on modification, which is a bit more upfront/intrusive in user flows, but more conservative with locking, flip the settings:
|
||||
|
||||
```ini
|
||||
[/Script/UnrealEd.EditorLoadingSavingSettings]
|
||||
bAutomaticallyCheckoutOnAssetModification=False
|
||||
bPromptForCheckoutOnAssetModification=True
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
* As another general usability improvement, you can set the editor to load any checked out packages for faster loading. In `Config/DefaultEditorPerProjectUserSettings.ini`:
|
||||
|
||||
```ini
|
||||
[/Script/UnrealEd.EditorPerProjectUserSettings]
|
||||
bAutoloadCheckedOutPackages=True
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
* In `Config/DefaultEngine.ini` you can set this option to `1` to disable a feature that is unnecessary for Git (for performance):
|
||||
|
||||
```ini
|
||||
[SystemSettingsEditor]
|
||||
r.Editor.SkipSourceControlCheckForEditablePackages=1
|
||||
```
|
||||
|
||||
## Status Branches - Required Code Changes
|
||||
|
||||
Epic Games added Status Branches in 4.20, and this plugin has implemented support for them. See [Workflow on Fortnite](https://youtu.be/p4RcDpGQ_tI?t=1443) for more information. Here is an example of how you may apply it to your own game.
|
||||
|
||||
1. Make an `UUnrealEdEngine` subclass, preferrably in an editor only module, or guarded by `WITH_EDITOR`.
|
||||
2. Add the following:
|
||||
|
||||
```cpp
|
||||
#include "ISourceControlModule.h"
|
||||
#include "ISourceControlProvider.h"
|
||||
|
||||
void UMyEdEngine::Init(IEngineLoop* InEngineLoop)
|
||||
{
|
||||
Super::Init(InEngineLoop);
|
||||
|
||||
// Register state branches
|
||||
const ISourceControlModule& SourceControlModule = ISourceControlModule::Get();
|
||||
{
|
||||
ISourceControlProvider& SourceControlProvider = SourceControlModule.GetProvider();
|
||||
// Order matters. Lower values are lower in the hierarchy, i.e., changes from higher branches get automatically merged down.
|
||||
// (Automatic merging requires an appropriately configured CI pipeline)
|
||||
// With this paradigm, the higher the branch is, the stabler it is, and has changes manually promoted up.
|
||||
const TArray<FString> Branches {"origin/develop", "origin/promoted"};
|
||||
SourceControlProvider.RegisterStateBranches(Branches, TEXT("Content"));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
3. Set to use the editor engine in `Config/DefaultEngine.ini` (make sure the class name is `MyUnrealEdEngine` for a class called `UMyUnrealEdEngine`!):
|
||||
|
||||
```ini
|
||||
[/Script/Engine.Engine]
|
||||
UnrealEdEngine=/Script/MyModule.MyEdEngine
|
||||
```
|
||||
|
||||
5. In this example, `origin/promoted` is the highest tested branch. Any changes in this branch are asset changes that do not need testing, and get automatically merged down to `origin/develop`. This may be extended to involve multiple branches, like `origin/trunk`, `origin/main`, or whatever you may prefer, where changes may be cascaded from most-stable to least-stable automatically. With this paradigm, changes from less-stable branches are manually promoted to more-stable branches after a merge review.
|
||||
**NOTE**: The second argument in `RegisterStateBranches` is Perforce specific and is ignored, but is meant to point to the relative content path.
|
||||
|
||||
6. If you decide to implement the status branch code in a editor-only module, ensure the loading phase in the editor module is set to `Default` in your .uproject settings, like so: (Otherwise, the editor will likely have difficulty finding your subclass'd UUnrealEdEngine class.)
|
||||
```json
|
||||
{
|
||||
"Name": "MyTestProjectEditor",
|
||||
"Type": "Editor",
|
||||
"LoadingPhase": "Default"
|
||||
}
|
||||
```
|
||||
|
||||
## Status Branches - Conceptual Overview
|
||||
|
||||
This feature helps ensure you're not locking and modifying files that are out-of-date.
|
||||
|
||||
If a user is on **any** branch, regardless if it's tracking a branch included in the 'status branch' list, they will be **unable to checkout** files that have **more recent changes on the remote server** than they have on the local branch, **provided** those changes are in a branch in the **'status branch' list.**
|
||||
* **If** the **remote branch with the changes** is **not** in the status branch list, the user will **not be notified of remote changes.**
|
||||
* **If** the user makes changes to a **local branch** and **switches** to **another local branch**, the user will **not** be notified of their **own changes** to the other branch, **regardless** if it's in the 'status branch' list or not **(this feature only checks remote branches!)**
|
||||
* **If** the user is tracking a remote branch that is in the status branch list, they will be **unable to lock stale files** (files that are changed up-stream).
|
||||
|
||||

|
||||
|
||||
#### Note:
|
||||
|
||||
It's important to only release file locks after changes have been pushed to the server. The system has no way to determine that there are local changes to a file, so if you modify a locked file it's imperative that you push the changes to a remote branch included in the 'status branch' list so other users can see those changes and avoid modifying a stale file. Otherwise, you'll want to keep the file locked!
|
||||
|
||||
Additionally, if you're switching back and forth between two or more branches locally you'll need to keep track of what branch you've made changes to locked files, as the system will not prevent you from modifying the same locked file on multiple different branches!
|
||||
|
||||
#### Real-world example of the 'status branch' feature:
|
||||
|
||||
* The user has checked out the `develop` branch, but there is an up-stream change on `origin/develop` for `FirstPersonProjectileMaterial`, indicated with the **yellow** exclamation mark.
|
||||
* There are also newer upstream changes on the `promoted` branch, indicated with the **red** exclamation mark. (NOTE: The plugin does not currently report the branch name the changes are on.)
|
||||
|
||||

|
||||
|
||||
## General In-Editor Usage
|
||||
|
||||
### Connecting to revision control:
|
||||
|
||||
Generally speaking, the field next to `Uses Git LFS 2 File Locking workflow` should match your Git server's `User Name`, like so:
|
||||
(If you find that the checkmark turns blue shortly after checking out a file, then the LFS name is incorrect, update it to the name it says checked out the file)
|
||||
|
||||

|
||||
|
||||
### Checking out (locking) one or more assets:
|
||||
|
||||
You can lock individual files or you can hold `shift` to select and lock multiple at once, which can be quite a bit faster than locking them individually.
|
||||
|
||||

|
||||
|
||||
### Unlocking one or more un-changed assets:
|
||||
|
||||
You can unlock individual files or you can hold `shift` to select and unlock multiple at once, which can be quite a bit faster than unlocking them individually.
|
||||
|
||||

|
||||
|
||||
### Locking every asset within a folder:
|
||||
|
||||
You can lock every file in a folder by right clicking on the folder and clicking `Check Out`.
|
||||
|
||||

|
||||
|
||||
### Viewing locks:
|
||||
|
||||
View the owner of a file lock simply by hovering over the asset icon. Your locked files have a **red** check-mark, other user's locks will show up with a **blue** checkmark.
|
||||
|
||||

|
||||
|
||||
### Pulling latest from within the editor:
|
||||
|
||||
You can pull the latest changes from your currently checked-out branch within the editor. This doesn't always work smoothly, but effort has been made to improve this process. It is still recommended to always save changes before doing this, however.
|
||||
|
||||

|
||||
|
||||
### Submitting changes up-stream:
|
||||
|
||||
`Submit to revision control` will create a local commit, push it, and release your file lock.
|
||||
(While you cannot check out branches within the plugin, it is fully branch-aware! In this scenario, the user has checked out the `develop` branch, so their change is pushed to `origin/develop`.)
|
||||
|
||||

|
||||
|
||||
## Additional Resources
|
||||
|
||||
You can learn more about how we set up our Git repository at [the PBCore wiki](https://github.com/ProjectBorealis/PBCore/wiki).
|
||||
BIN
Plugins/GitSourceControl/Screenshots/FileHistory.png
(Stored with Git LFS)
Normal file
BIN
Plugins/GitSourceControl/Screenshots/FileHistory.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
Plugins/GitSourceControl/Screenshots/Icons/Added.png
(Stored with Git LFS)
Normal file
BIN
Plugins/GitSourceControl/Screenshots/Icons/Added.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
Plugins/GitSourceControl/Screenshots/Icons/Modified.png
(Stored with Git LFS)
Normal file
BIN
Plugins/GitSourceControl/Screenshots/Icons/Modified.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
Plugins/GitSourceControl/Screenshots/Icons/New.png
(Stored with Git LFS)
Normal file
BIN
Plugins/GitSourceControl/Screenshots/Icons/New.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
Plugins/GitSourceControl/Screenshots/Icons/Renamed.png
(Stored with Git LFS)
Normal file
BIN
Plugins/GitSourceControl/Screenshots/Icons/Renamed.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
Plugins/GitSourceControl/Screenshots/Icons/Unchanged.png
(Stored with Git LFS)
Normal file
BIN
Plugins/GitSourceControl/Screenshots/Icons/Unchanged.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
Plugins/GitSourceControl/Screenshots/SourceControlLogin_Init.png
(Stored with Git LFS)
Normal file
BIN
Plugins/GitSourceControl/Screenshots/SourceControlLogin_Init.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
Plugins/GitSourceControl/Screenshots/SourceControlMenu.png
(Stored with Git LFS)
Normal file
BIN
Plugins/GitSourceControl/Screenshots/SourceControlMenu.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
Plugins/GitSourceControl/Screenshots/SourceControlStatusTooltip.png
(Stored with Git LFS)
Normal file
BIN
Plugins/GitSourceControl/Screenshots/SourceControlStatusTooltip.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
Plugins/GitSourceControl/Screenshots/SubmitFiles.png
(Stored with Git LFS)
Normal file
BIN
Plugins/GitSourceControl/Screenshots/SubmitFiles.png
(Stored with Git LFS)
Normal file
Binary file not shown.
@@ -29,5 +29,19 @@ public class GitSourceControl : ModuleRules
|
||||
{
|
||||
PrivateDependencyModuleNames.Add("ToolMenus");
|
||||
}
|
||||
|
||||
if (Target.Platform == UnrealTargetPlatform.Win64)
|
||||
{
|
||||
RuntimeDependencies.Add("$(PluginDir)/git-lfs.exe");
|
||||
}
|
||||
else if (Target.Platform == UnrealTargetPlatform.Mac)
|
||||
{
|
||||
RuntimeDependencies.Add("$(PluginDir)/git-lfs-mac-amd64");
|
||||
RuntimeDependencies.Add("$(PluginDir)/git-lfs-mac-arm64");
|
||||
}
|
||||
else if (Target.Platform == UnrealTargetPlatform.Linux)
|
||||
{
|
||||
RuntimeDependencies.Add("$(PluginDir)/git-lfs");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
#include "GitSourceControlChangelist.h"
|
||||
#include "GitSourceControlChangelist.h"
|
||||
|
||||
#if ENGINE_MAJOR_VERSION == 5
|
||||
FGitSourceControlChangelist FGitSourceControlChangelist::WorkingChangelist(TEXT("Working"), true);
|
||||
FGitSourceControlChangelist FGitSourceControlChangelist::StagedChangelist(TEXT("Staged"), true);
|
||||
#endif
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "GitSourceControlChangelistState.h"
|
||||
#include "GitSourceControlChangelistState.h"
|
||||
|
||||
#if ENGINE_MAJOR_VERSION == 5
|
||||
#define LOCTEXT_NAMESPACE "GitSourceControl.ChangelistState"
|
||||
|
||||
FName FGitSourceControlChangelistState::GetIconName() const
|
||||
@@ -72,3 +73,4 @@ FSourceControlChangelistRef FGitSourceControlChangelistState::GetChangelist() co
|
||||
}
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
#endif
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
#pragma once
|
||||
#pragma once
|
||||
#include "Runtime/Launch/Resources/Version.h"
|
||||
#if ENGINE_MAJOR_VERSION == 5
|
||||
#include "GitSourceControlChangelist.h"
|
||||
#include "ISourceControlChangelistState.h"
|
||||
#include "ISourceControlState.h"
|
||||
@@ -82,3 +84,4 @@ public:
|
||||
/** The timestamp of the last update */
|
||||
FDateTime TimeStamp;
|
||||
};
|
||||
#endif
|
||||
|
||||
@@ -28,7 +28,7 @@ FGitSourceControlCommand::FGitSourceControlCommand(const TSharedRef<class ISourc
|
||||
PathToGitRoot = Provider.GetPathToGitRoot();
|
||||
}
|
||||
|
||||
void FGitSourceControlCommand::UpdateRepositoryRootIfSubmodule(const TArray<FString>& AbsoluteFilePaths)
|
||||
void FGitSourceControlCommand::UpdateRepositoryRootIfSubmodule(TArray<FString>& AbsoluteFilePaths)
|
||||
{
|
||||
PathToRepositoryRoot = GitSourceControlUtils::ChangeRepositoryRootIfSubmodule(AbsoluteFilePaths, PathToRepositoryRoot);
|
||||
}
|
||||
|
||||
@@ -38,9 +38,13 @@
|
||||
#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"
|
||||
|
||||
@@ -49,7 +53,7 @@ TWeakPtr<SNotificationItem> FGitSourceControlMenu::OperationInProgressNotificati
|
||||
void FGitSourceControlMenu::Register()
|
||||
{
|
||||
#if ENGINE_MAJOR_VERSION >= 5
|
||||
FToolMenuOwnerScoped SourceControlMenuOwner("GitSourceControlMenu");
|
||||
FToolMenuOwnerScoped SourceControlMenuOwner( GitSourceControlMenuTabName );
|
||||
if (UToolMenus* ToolMenus = UToolMenus::Get())
|
||||
{
|
||||
UToolMenu* SourceControlMenu = ToolMenus->ExtendMenu("StatusBar.ToolBar.SourceControl");
|
||||
@@ -59,7 +63,7 @@ void FGitSourceControlMenu::Register()
|
||||
}
|
||||
#else
|
||||
// Register the extension with the level editor
|
||||
FLevelEditorModule* LevelEditorModule = FModuleManager::GetModulePtr<FLevelEditorModule>(TEXT("LevelEditor"));
|
||||
FLevelEditorModule * LevelEditorModule = FModuleManager::GetModulePtr< FLevelEditorModule >( LevelEditorName );
|
||||
if (LevelEditorModule)
|
||||
{
|
||||
FLevelEditorModule::FLevelEditorMenuExtender ViewMenuExtender = FLevelEditorModule::FLevelEditorMenuExtender::CreateRaw(this, &FGitSourceControlMenu::OnExtendLevelEditorViewMenu);
|
||||
@@ -79,7 +83,7 @@ void FGitSourceControlMenu::Unregister()
|
||||
}
|
||||
#else
|
||||
// Unregister the level editor extensions
|
||||
FLevelEditorModule* LevelEditorModule = FModuleManager::GetModulePtr<FLevelEditorModule>("LevelEditor");
|
||||
FLevelEditorModule * LevelEditorModule = FModuleManager::GetModulePtr< FLevelEditorModule >( LevelEditorName );
|
||||
if (LevelEditorModule)
|
||||
{
|
||||
LevelEditorModule->GetAllLevelEditorToolbarSourceControlMenuExtenders().RemoveAll([=](const FLevelEditorModule::FLevelEditorMenuExtender& Extender) { return Extender.GetHandle() == ViewMenuExtenderHandle; });
|
||||
@@ -93,6 +97,17 @@ bool FGitSourceControlMenu::HaveRemoteUrl() const
|
||||
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()
|
||||
{
|
||||
@@ -527,6 +542,20 @@ void FGitSourceControlMenu::AddMenuExtension(FToolMenuSection& Builder)
|
||||
void FGitSourceControlMenu::AddMenuExtension(FMenuBuilder& Builder)
|
||||
#endif
|
||||
{
|
||||
#if ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION >= 6
|
||||
// UE 5.6 removed the submit content button, so re-create one here
|
||||
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",
|
||||
|
||||
@@ -25,11 +25,19 @@
|
||||
#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()
|
||||
{
|
||||
@@ -51,16 +59,51 @@ void FGitSourceControlModule::StartupModule()
|
||||
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();
|
||||
|
||||
// Bind our revision control provider to the editor
|
||||
IModularFeatures::Get().RegisterModularFeature( "SourceControl", &GitSourceControlProvider );
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
FContentBrowserModule& ContentBrowserModule = FModuleManager::Get().LoadModuleChecked<FContentBrowserModule>("ContentBrowser");
|
||||
// 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
|
||||
@@ -86,11 +129,11 @@ void FGitSourceControlModule::ShutdownModule()
|
||||
GitSourceControlProvider.Close();
|
||||
|
||||
// unbind provider from editor
|
||||
IModularFeatures::Get().UnregisterModularFeature("SourceControl", &GitSourceControlProvider);
|
||||
IModularFeatures::Get().UnregisterModularFeature( NAME_SourceControl, &GitSourceControlProvider );
|
||||
|
||||
|
||||
// Unregister ContentBrowserDelegate Handles
|
||||
FContentBrowserModule & ContentBrowserModule = FModuleManager::Get().LoadModuleChecked< FContentBrowserModule >( "ContentBrowser" );
|
||||
FContentBrowserModule & ContentBrowserModule = FModuleManager::Get().GetModuleChecked< FContentBrowserModule >( NAME_ContentBrowser );
|
||||
#if ENGINE_MAJOR_VERSION >= 5
|
||||
ContentBrowserModule.GetOnFilterChanged().Remove( CbdHandle_OnFilterChanged );
|
||||
ContentBrowserModule.GetOnSearchBoxChanged().Remove( CbdHandle_OnSearchBoxChanged );
|
||||
|
||||
@@ -856,6 +856,7 @@ bool FGitResolveWorker::UpdateStates() const
|
||||
return GitSourceControlUtils::UpdateCachedStates(States);
|
||||
}
|
||||
|
||||
#if ENGINE_MAJOR_VERSION == 5
|
||||
FName FGitMoveToChangelistWorker::GetName() const
|
||||
{
|
||||
return "MoveToChangelist";
|
||||
@@ -905,5 +906,6 @@ bool FGitUpdateStagingWorker::UpdateStates() const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
|
||||
@@ -183,6 +183,7 @@ public:
|
||||
TMap<const FString, FGitState> States;
|
||||
};
|
||||
|
||||
#if ENGINE_MAJOR_VERSION == 5
|
||||
class FGitMoveToChangelistWorker : public IGitSourceControlWorker
|
||||
{
|
||||
public:
|
||||
@@ -208,3 +209,4 @@ public:
|
||||
/** Temporary states for results */
|
||||
TMap<const FString, FGitState> States;
|
||||
};
|
||||
#endif
|
||||
|
||||
@@ -28,7 +28,13 @@
|
||||
#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"
|
||||
|
||||
@@ -48,7 +54,9 @@ void FGitSourceControlProvider::Init(bool bForceConnection)
|
||||
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);
|
||||
@@ -119,12 +127,14 @@ void FGitSourceControlProvider::CheckRepositoryStatus()
|
||||
if (!IsInGameThread())
|
||||
{
|
||||
// Wait until the module interface is valid
|
||||
IModuleInterface* GitModule;
|
||||
do
|
||||
{
|
||||
GitModule = FModuleManager::Get().GetModule("GitSourceControl");
|
||||
FPlatformProcess::Sleep(0.0f);
|
||||
} while (!GitModule);
|
||||
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)
|
||||
@@ -148,6 +158,19 @@ void FGitSourceControlProvider::CheckRepositoryStatus()
|
||||
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{FPaths::ConvertRelativePathToFull(FPaths::ProjectContentDir()),
|
||||
FPaths::ConvertRelativePathToFull(FPaths::ProjectConfigDir()),
|
||||
FPaths::ConvertRelativePathToFull(FPaths::GetProjectFilePath())};
|
||||
@@ -262,6 +285,7 @@ TSharedRef<FGitSourceControlState, ESPMode::ThreadSafe> FGitSourceControlProvide
|
||||
}
|
||||
}
|
||||
|
||||
#if ENGINE_MAJOR_VERSION == 5
|
||||
TSharedRef<FGitSourceControlChangelistState, ESPMode::ThreadSafe> FGitSourceControlProvider::GetStateInternal(const FGitSourceControlChangelist& InChangelist)
|
||||
{
|
||||
TSharedRef<FGitSourceControlChangelistState, ESPMode::ThreadSafe>* State = ChangelistsStateCache.Find(InChangelist);
|
||||
@@ -278,6 +302,7 @@ TSharedRef<FGitSourceControlChangelistState, ESPMode::ThreadSafe> FGitSourceCont
|
||||
return NewState;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
FText FGitSourceControlProvider::GetStatusText() const
|
||||
{
|
||||
@@ -437,7 +462,7 @@ ECommandResult::Type FGitSourceControlProvider::Execute( const FSourceControlOpe
|
||||
return ECommandResult::Failed;
|
||||
}
|
||||
|
||||
const TArray<FString>& AbsoluteFiles = SourceControlHelpers::AbsoluteFilenames(InFiles);
|
||||
TArray<FString> AbsoluteFiles = SourceControlHelpers::AbsoluteFilenames(InFiles);
|
||||
|
||||
// Query to see if we allow this operation
|
||||
TSharedPtr<IGitSourceControlWorker, ESPMode::ThreadSafe> Worker = CreateWorker(InOperation->GetName());
|
||||
@@ -457,12 +482,14 @@ ECommandResult::Type FGitSourceControlProvider::Execute( const FSourceControlOpe
|
||||
}
|
||||
|
||||
FGitSourceControlCommand* Command = new FGitSourceControlCommand(InOperation, Worker.ToSharedRef());
|
||||
Command->Files = AbsoluteFiles;
|
||||
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)
|
||||
|
||||
@@ -16,7 +16,7 @@ static const FString SettingsSection = TEXT("GitSourceControl.GitSourceControlSe
|
||||
|
||||
}
|
||||
|
||||
const FString FGitSourceControlSettings::GetBinaryPath() const
|
||||
const FString & FGitSourceControlSettings::GetBinaryPath() const
|
||||
{
|
||||
FScopeLock ScopeLock(&CriticalSection);
|
||||
return BinaryPath; // Return a copy to be thread-safe
|
||||
@@ -83,4 +83,5 @@ void FGitSourceControlSettings::SaveSettings() const
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -34,7 +34,11 @@
|
||||
#include "PackageTools.h"
|
||||
#include "FileHelpers.h"
|
||||
#include "Misc/MessageDialog.h"
|
||||
|
||||
#include "Runtime/Launch/Resources/Version.h"
|
||||
#if ENGINE_MAJOR_VERSION == 5
|
||||
#include "UObject/ObjectSaveContext.h"
|
||||
#endif
|
||||
|
||||
#include "Async/Async.h"
|
||||
#include "UObject/Linker.h"
|
||||
@@ -124,11 +128,14 @@ void FGitLockedFilesCache::OnFileLockChanged(const FString& filePath, const FStr
|
||||
|
||||
namespace GitSourceControlUtils
|
||||
{
|
||||
FString ChangeRepositoryRootIfSubmodule(const TArray<FString>& AbsoluteFilePaths, const FString& PathToRepositoryRoot)
|
||||
FString ChangeRepositoryRootIfSubmodule(TArray<FString>& AbsoluteFilePaths, const FString& PathToRepositoryRoot)
|
||||
{
|
||||
FString Ret = PathToRepositoryRoot;
|
||||
// note this is not going to support operations where selected files are in different repositories
|
||||
|
||||
TArray<FString> PackageNotIncludedInGit;
|
||||
PackageNotIncludedInGit.Reserve(AbsoluteFilePaths.Num());
|
||||
|
||||
for (auto& FilePath : AbsoluteFilePaths)
|
||||
{
|
||||
FString TestPath = FilePath;
|
||||
@@ -139,8 +146,10 @@ namespace GitSourceControlUtils
|
||||
|
||||
if (TestPath.IsEmpty())
|
||||
{
|
||||
// early out if empty directory string to prevent infinite loop
|
||||
UE_LOG(LogSourceControl, Error, TEXT("Can't find directory path for file :%s"), *FilePath);
|
||||
// TestPath.IsEmpty() meaning is that FilePath is not git file. So it need to removed to git command file list.
|
||||
PackageNotIncludedInGit.Add(FilePath);
|
||||
UE_LOG(LogSourceControl, Warning, TEXT("Package file to update has included dependent file is not git or Can't find directory path for file : %s"), *FilePath);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -151,7 +160,7 @@ namespace GitSourceControlUtils
|
||||
FPaths::NormalizeDirectoryName(RetNormalized);
|
||||
FString PathToRepositoryRootNormalized = PathToRepositoryRoot;
|
||||
FPaths::NormalizeDirectoryName(PathToRepositoryRootNormalized);
|
||||
if (!FPaths::IsSamePath(RetNormalized, PathToRepositoryRootNormalized) && Ret != GitTestPath)
|
||||
if (!FPaths::IsSamePath(RetNormalized, PathToRepositoryRootNormalized) && Ret != FPaths::GetPath(GitTestPath))
|
||||
{
|
||||
UE_LOG(LogSourceControl, Error, TEXT("Selected files belong to different submodules"));
|
||||
return PathToRepositoryRoot;
|
||||
@@ -161,10 +170,22 @@ namespace GitSourceControlUtils
|
||||
}
|
||||
}
|
||||
}
|
||||
#if ENGINE_MAJOR_VERSION >= 5
|
||||
if (!PackageNotIncludedInGit.IsEmpty())
|
||||
#else
|
||||
if (PackageNotIncludedInGit.Num() > 0)
|
||||
#endif
|
||||
{
|
||||
for (const FString& ToRemoveFile : PackageNotIncludedInGit)
|
||||
{
|
||||
AbsoluteFilePaths.Remove(ToRemoveFile);
|
||||
}
|
||||
}
|
||||
|
||||
return Ret;
|
||||
}
|
||||
|
||||
FString ChangeRepositoryRootIfSubmodule(const FString& AbsoluteFilePath, const FString& PathToRepositoryRoot)
|
||||
FString ChangeRepositoryRootIfSubmodule(FString & AbsoluteFilePath, const FString& PathToRepositoryRoot)
|
||||
{
|
||||
TArray<FString> AbsoluteFilePaths = { AbsoluteFilePath };
|
||||
return ChangeRepositoryRootIfSubmodule(AbsoluteFilePaths, PathToRepositoryRoot);
|
||||
@@ -952,16 +973,18 @@ R Content/Textures/T_Perlin_Noise_M.uasset -> Content/Textures/T_Perlin_Noise_M
|
||||
static FString FilenameFromGitStatus(const FString& InResult)
|
||||
{
|
||||
int32 RenameIndex;
|
||||
FString Result;
|
||||
if (InResult.FindLastChar('>', RenameIndex))
|
||||
{
|
||||
// Extract only the second part of a rename "from -> to"
|
||||
return InResult.RightChop(RenameIndex + 2);
|
||||
Result = InResult.RightChop(RenameIndex + 2);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Extract the relative filename from the Git status result (after the 2 letters status and 1 space)
|
||||
return InResult.RightChop(3);
|
||||
Result = InResult.RightChop(3);
|
||||
}
|
||||
return Result.TrimQuotes();
|
||||
}
|
||||
|
||||
/** Match the relative filename of a Git status result with a provided absolute filename */
|
||||
@@ -1370,20 +1393,7 @@ static void ParseFileStatusResult(const FString& InPathToGitBinary, const FStrin
|
||||
ParseDirectoryStatusResult(InUsingLfsLocking, Results, OutStates);
|
||||
}
|
||||
|
||||
/**
|
||||
* @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")
|
||||
*/
|
||||
static void ParseStatusResults(const FString& InPathToGitBinary, const FString& InRepositoryRoot, const bool InUsingLfsLocking, const TArray<FString>& InFiles,
|
||||
void ParseStatusResults(const FString& InPathToGitBinary, const FString& InRepositoryRoot, const bool InUsingLfsLocking, const TArray<FString>& InFiles,
|
||||
const TMap<FString, FString>& InResults, TMap<FString, FGitSourceControlState>& OutStates)
|
||||
{
|
||||
TSet<FString> Files;
|
||||
@@ -1444,9 +1454,13 @@ void CheckRemote(const FString& InPathToGitBinary, const FString& InRepositoryRo
|
||||
|
||||
TArray<FString> ErrorMessages;
|
||||
|
||||
TArray<FString> Results;
|
||||
TArray<FString> LogResults;
|
||||
TArray<FString> DiffResults;
|
||||
TArray<FString> Intersection;
|
||||
|
||||
TMap<FString, FString> NewerFiles;
|
||||
|
||||
|
||||
//const TArray<FString>& RelativeFiles = RelativeFilenames(Files, InRepositoryRoot);
|
||||
// Get the full remote status of the Content folder, since it's the only lockable folder we track in editor.
|
||||
// This shows any new files as well.
|
||||
@@ -1468,10 +1482,27 @@ void CheckRemote(const FString& InPathToGitBinary, const FString& InRepositoryRo
|
||||
// .. means commits in the right that are not in the left
|
||||
ParametersLog[2] = FString::Printf(TEXT("..%s"), *Branch);
|
||||
|
||||
const bool bResultDiff = RunCommand(TEXT("log"), InPathToGitBinary, InRepositoryRoot, ParametersLog, FilesToDiff, Results, ErrorMessages);
|
||||
if (bResultDiff)
|
||||
const bool bResultLog = RunCommand(TEXT("log"), InPathToGitBinary, InRepositoryRoot, ParametersLog, FilesToDiff, LogResults, ErrorMessages);
|
||||
if (bResultLog)
|
||||
{
|
||||
for (const FString& NewerFileName : Results)
|
||||
// Status Branches may not be initialized because they're not in use by the project. They can also be not initilaized in some other quirky circumstances
|
||||
// eg. When running multi client / dedicated server in editor without running them under the same process, those game instances will run as an editor instance
|
||||
// which means editor plugins are enabled and running, but they don't run UnrealEdEngine, so the status branches are not initialized.
|
||||
if (StatusBranches.Num() > 0)
|
||||
{
|
||||
// Check if the files state in the branch in which is changed is actually different from compared branch
|
||||
// This opens files for edit if they were modified in another branch but have since been reverted back to state in status.
|
||||
TArray<FString> DiffParametersLog{ TEXT("--pretty="), TEXT("--name-only"), FString::Printf(TEXT("...%s"), *Branch), TEXT(""), TEXT("--") };
|
||||
const bool bResultDiff = RunCommand(TEXT("diff"), InPathToGitBinary, InRepositoryRoot, DiffParametersLog, FilesToDiff, DiffResults, ErrorMessages);
|
||||
// Get the intersection of the 2 containers
|
||||
Intersection = DiffResults.FilterByPredicate([&LogResults](const FString& ChangedFile) { return LogResults.Contains(ChangedFile); });
|
||||
}
|
||||
else
|
||||
{
|
||||
Intersection = LogResults;
|
||||
}
|
||||
|
||||
for (const FString& NewerFileName : Intersection)
|
||||
{
|
||||
// Don't care about mergeable files (.collection, .ini, .uproject, etc)
|
||||
if (!IsFileLFSLockable(NewerFileName))
|
||||
@@ -1491,7 +1522,9 @@ void CheckRemote(const FString& InPathToGitBinary, const FString& InRepositoryRo
|
||||
}
|
||||
}
|
||||
}
|
||||
Results.Reset();
|
||||
LogResults.Reset();
|
||||
DiffResults.Reset();
|
||||
Intersection.Reset();
|
||||
}
|
||||
|
||||
for (const auto& NewFile : NewerFiles)
|
||||
@@ -1629,8 +1662,19 @@ FString GetFullPathFromGitStatus(const FString& Result, const FString& InReposit
|
||||
return File;
|
||||
}
|
||||
|
||||
#if ENGINE_MAJOR_VERSION == 5
|
||||
bool UpdateChangelistStateByCommand()
|
||||
{
|
||||
// TODO: This is a temporary solution.
|
||||
FModuleManager &ModuleManager = FModuleManager::Get();
|
||||
FName GitModuleName = "GitSourceControl";
|
||||
|
||||
if (!ModuleManager.IsModuleLoaded(GitModuleName))
|
||||
{
|
||||
UE_LOG(LogSourceControl, Warning, TEXT("GitSourceControl module is not loaded."));
|
||||
return false;
|
||||
}
|
||||
|
||||
FGitSourceControlModule& GitSourceControl = FModuleManager::GetModuleChecked<FGitSourceControlModule>("GitSourceControl");
|
||||
FGitSourceControlProvider& Provider = GitSourceControl.GetProvider();
|
||||
if (!Provider.IsGitAvailable())
|
||||
@@ -1672,6 +1716,7 @@ bool UpdateChangelistStateByCommand()
|
||||
}
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Run a batch of Git "status" command to update status of given files and/or directories.
|
||||
bool RunUpdateStatus(const FString& InPathToGitBinary, const FString& InRepositoryRoot, const bool InUsingLfsLocking, const TArray<FString>& InFiles,
|
||||
@@ -1704,13 +1749,16 @@ bool RunUpdateStatus(const FString& InPathToGitBinary, const FString& InReposito
|
||||
ParseStatusResults(InPathToGitBinary, InRepositoryRoot, InUsingLfsLocking, RepoFiles, ResultsMap, OutStates);
|
||||
}
|
||||
|
||||
#if ENGINE_MAJOR_VERSION == 5
|
||||
UpdateChangelistStateByCommand();
|
||||
#endif
|
||||
|
||||
CheckRemote(InPathToGitBinary, InRepositoryRoot, RepoFiles, OutErrorMessages, OutStates);
|
||||
|
||||
return bResult;
|
||||
}
|
||||
|
||||
#if ENGINE_MAJOR_VERSION == 5
|
||||
void UpdateFileStagingOnSaved(const FString& Filename, UPackage* Pkg, FObjectPostSaveContext ObjectSaveContext)
|
||||
{
|
||||
UpdateFileStagingOnSavedInternal(Filename);
|
||||
@@ -1738,6 +1786,7 @@ bool UpdateFileStagingOnSavedInternal(const FString& Filename)
|
||||
|
||||
return bResult;
|
||||
}
|
||||
#endif
|
||||
|
||||
void UpdateStateOnAssetRename(const FAssetData& InAssetData, const FString& InOldName)
|
||||
{
|
||||
@@ -1749,7 +1798,11 @@ void UpdateStateOnAssetRename(const FAssetData& InAssetData, const FString& InOl
|
||||
}
|
||||
TSharedRef<FGitSourceControlState, ESPMode::ThreadSafe> State = Provider.GetStateInternal(InOldName);
|
||||
|
||||
#if ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION >= 1
|
||||
State->LocalFilename = InAssetData.GetObjectPathString();
|
||||
#else
|
||||
State->LocalFilename = InAssetData.ObjectPath.ToString();
|
||||
#endif
|
||||
}
|
||||
|
||||
// Run a Git `cat-file --filters` command to dump the binary content of a revision into a file.
|
||||
@@ -1821,51 +1874,20 @@ bool RunDumpToFile(const FString& InPathToGitBinary, const FString& InRepository
|
||||
FPlatformProcess::Sleep(0.01f);
|
||||
|
||||
TArray<uint8> BinaryFileContent;
|
||||
bool bRemovedLFSMessage = false;
|
||||
while (FPlatformProcess::IsProcRunning(ProcessHandle))
|
||||
bool bShouldContinue = true;
|
||||
while (FPlatformProcess::IsProcRunning(ProcessHandle) || bShouldContinue)
|
||||
{
|
||||
TArray<uint8> BinaryData;
|
||||
FPlatformProcess::ReadPipeToArray(PipeRead, BinaryData);
|
||||
bShouldContinue = FPlatformProcess::ReadPipeToArray(PipeRead, BinaryData);
|
||||
if (BinaryData.Num() > 0)
|
||||
{
|
||||
// @todo: this is hacky!
|
||||
if (BinaryData[0] == 68) // Check for D in "Downloading"
|
||||
bool bIsLFSMessage = BinaryData[0] == 68 // Check for D in "Downloading"
|
||||
&& BinaryData.Last() == 10; // Check for new line
|
||||
if (GitSourceControl.AccessSettings().IsUsingGitLfsLocking() && bIsLFSMessage)
|
||||
{
|
||||
if (BinaryData[BinaryData.Num() - 1] == 10) // Check for newline
|
||||
{
|
||||
BinaryData.Reset();
|
||||
bRemovedLFSMessage = true;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
BinaryFileContent.Append(MoveTemp(BinaryData));
|
||||
}
|
||||
}
|
||||
}
|
||||
TArray<uint8> BinaryData;
|
||||
FPlatformProcess::ReadPipeToArray(PipeRead, BinaryData);
|
||||
if (BinaryData.Num() > 0)
|
||||
{
|
||||
// @todo: this is hacky!
|
||||
if (!bRemovedLFSMessage && BinaryData[0] == 68) // Check for D in "Downloading"
|
||||
{
|
||||
int32 NewLineIndex = 0;
|
||||
for (int32 Index = 0; Index < BinaryData.Num(); Index++)
|
||||
{
|
||||
if (BinaryData[Index] == 10) // Check for newline
|
||||
{
|
||||
NewLineIndex = Index;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (NewLineIndex > 0)
|
||||
{
|
||||
BinaryData.RemoveAt(0, NewLineIndex + 1);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
BinaryFileContent.Append(MoveTemp(BinaryData));
|
||||
}
|
||||
}
|
||||
@@ -2334,6 +2356,7 @@ bool CheckLFSLockable(const FString& InPathToGitBinary, const FString& InReposit
|
||||
{
|
||||
TArray<FString> Results;
|
||||
TArray<FString> Parameters;
|
||||
LockableTypes.Empty(); // clear previous results
|
||||
Parameters.Add(TEXT("lockable")); // follow file renames
|
||||
|
||||
const bool bResults = RunCommand(TEXT("check-attr"), InPathToGitBinary, InRepositoryRoot, Parameters, InFiles, Results, OutErrorMessages);
|
||||
@@ -2345,7 +2368,7 @@ bool CheckLFSLockable(const FString& InPathToGitBinary, const FString& InReposit
|
||||
for (int i = 0; i < InFiles.Num(); i++)
|
||||
{
|
||||
const FString& Result = Results[i];
|
||||
if (Result.EndsWith("set"))
|
||||
if (Result.EndsWith("set") && !Result.EndsWith("unset"))
|
||||
{
|
||||
const FString FileExt = InFiles[i].RightChop(1); // Remove wildcard (*)
|
||||
LockableTypes.Add(FileExt);
|
||||
|
||||
@@ -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,312 @@
|
||||
// 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;
|
||||
virtual TOptional<bool> IsAtLatestRevision() const override;
|
||||
virtual TOptional<int> GetNumLocalChanges() const override;
|
||||
#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 >= 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
|
||||
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,390 @@
|
||||
// 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);
|
||||
|
||||
/**
|
||||
* 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;
|
||||
2
Plugins/GitSourceControl/_config.yml
Normal file
2
Plugins/GitSourceControl/_config.yml
Normal file
@@ -0,0 +1,2 @@
|
||||
show_downloads: true
|
||||
theme: jekyll-theme-slate
|
||||
BIN
Plugins/GitSourceControl/git-lfs
Normal file
BIN
Plugins/GitSourceControl/git-lfs
Normal file
Binary file not shown.
BIN
Plugins/GitSourceControl/git-lfs-mac-amd64
Normal file
BIN
Plugins/GitSourceControl/git-lfs-mac-amd64
Normal file
Binary file not shown.
BIN
Plugins/GitSourceControl/git-lfs-mac-arm64
Normal file
BIN
Plugins/GitSourceControl/git-lfs-mac-arm64
Normal file
Binary file not shown.
BIN
Plugins/GitSourceControl/git-lfs.exe
(Stored with Git LFS)
Normal file
BIN
Plugins/GitSourceControl/git-lfs.exe
(Stored with Git LFS)
Normal file
Binary file not shown.
Reference in New Issue
Block a user