├── .gitignore ├── Art ├── AddedStatusIcon.png ├── AddedStatusIcon.svg ├── CleanStatusIcon.png ├── CleanStatusIcon.svg ├── Icon128.svg ├── MissingStatusIcon.png ├── MissingStatusIcon.svg ├── ModifiedStatusIcon.png ├── ModifiedStatusIcon.svg ├── NotTrackedStatusIcon.png ├── NotTrackedStatusIcon.svg ├── RemovedStatusIcon.png └── RemovedStatusIcon.svg ├── Content └── SlateBrushes │ ├── AddedStatusIcon.uasset │ ├── CleanStatusIcon.uasset │ ├── MissingStatusIcon.uasset │ ├── ModifiedStatusIcon.uasset │ ├── NotTrackedStatusIcon.uasset │ └── RemovedStatusIcon.uasset ├── LICENSE ├── MercurialSourceControl.uplugin ├── README.md ├── Resources └── Icon128.png └── Source └── MercurialSourceControl ├── MercurialSourceControl.Build.cs └── Private ├── IMercurialSourceControlWorker.h ├── MercurialSourceControlClient.cpp ├── MercurialSourceControlClient.h ├── MercurialSourceControlCommand.cpp ├── MercurialSourceControlCommand.h ├── MercurialSourceControlFileRevision.cpp ├── MercurialSourceControlFileRevision.h ├── MercurialSourceControlFileState.cpp ├── MercurialSourceControlFileState.h ├── MercurialSourceControlModule.cpp ├── MercurialSourceControlModule.h ├── MercurialSourceControlOperationNames.cpp ├── MercurialSourceControlOperationNames.h ├── MercurialSourceControlPrivatePCH.h ├── MercurialSourceControlProvider.cpp ├── MercurialSourceControlProvider.h ├── MercurialSourceControlProviderSettings.cpp ├── MercurialSourceControlProviderSettings.h ├── MercurialSourceControlStyle.cpp ├── MercurialSourceControlStyle.h ├── MercurialSourceControlWorkers.cpp ├── MercurialSourceControlWorkers.h ├── SLargeAssetTypeTreeWidget.cpp ├── SLargeAssetTypeTreeWidget.h ├── SMercurialSourceControlSettingsWidget.cpp └── SMercurialSourceControlSettingsWidget.h /.gitignore: -------------------------------------------------------------------------------- 1 | Intermediate/ 2 | Binaries/ -------------------------------------------------------------------------------- /Art/AddedStatusIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enlight/ue4-hg-plugin/272554b7b6a67c815274486b50a4a800bfa09abb/Art/AddedStatusIcon.png -------------------------------------------------------------------------------- /Art/AddedStatusIcon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 20 | 30 | 35 | 43 | 50 | 51 | 52 | 71 | 73 | 74 | 76 | image/svg+xml 77 | 79 | 80 | 81 | 82 | Vadim Macagon 83 | 84 | 85 | 2014-09-04 86 | 88 | 89 | 91 | 93 | 95 | 97 | 99 | 101 | 102 | 103 | 104 | 108 | + 134 | 135 | -------------------------------------------------------------------------------- /Art/CleanStatusIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enlight/ue4-hg-plugin/272554b7b6a67c815274486b50a4a800bfa09abb/Art/CleanStatusIcon.png -------------------------------------------------------------------------------- /Art/CleanStatusIcon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 20 | 26 | 31 | 39 | 46 | 47 | 48 | 67 | 69 | 70 | 72 | image/svg+xml 73 | 75 | 76 | 2014-09-04 77 | 78 | 79 | Vadim Macagon 80 | 81 | 82 | 84 | 85 | 87 | 89 | 91 | 93 | 95 | 97 | 98 | 99 | 100 | 104 | 110 | 111 | 112 | -------------------------------------------------------------------------------- /Art/Icon128.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 22 | 24 | 27 | 31 | 35 | 36 | 45 | 46 | 65 | 67 | 68 | 70 | image/svg+xml 71 | 73 | 74 | 75 | 76 | 77 | 82 | Hg 97 | 98 | -------------------------------------------------------------------------------- /Art/MissingStatusIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enlight/ue4-hg-plugin/272554b7b6a67c815274486b50a4a800bfa09abb/Art/MissingStatusIcon.png -------------------------------------------------------------------------------- /Art/MissingStatusIcon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 21 | 23 | 32 | 37 | 45 | 52 | 53 | 54 | 73 | 75 | 76 | 78 | image/svg+xml 79 | 81 | 82 | 2014-09-04 83 | 84 | 85 | Vadim Macagon 86 | 87 | 88 | 90 | 91 | 93 | 95 | 97 | 99 | 101 | 103 | 104 | 105 | 106 | 110 | ! 136 | 137 | -------------------------------------------------------------------------------- /Art/ModifiedStatusIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enlight/ue4-hg-plugin/272554b7b6a67c815274486b50a4a800bfa09abb/Art/ModifiedStatusIcon.png -------------------------------------------------------------------------------- /Art/ModifiedStatusIcon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 20 | 26 | 31 | 39 | 46 | 47 | 48 | 67 | 69 | 70 | 72 | image/svg+xml 73 | 75 | 76 | 2014-09-04 77 | 78 | 79 | Vadim Macagon 80 | 81 | 82 | 84 | 85 | 87 | 89 | 91 | 93 | 95 | 97 | 98 | 99 | 100 | 104 | 110 | 111 | 112 | -------------------------------------------------------------------------------- /Art/NotTrackedStatusIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enlight/ue4-hg-plugin/272554b7b6a67c815274486b50a4a800bfa09abb/Art/NotTrackedStatusIcon.png -------------------------------------------------------------------------------- /Art/NotTrackedStatusIcon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 20 | 28 | 33 | 41 | 48 | 49 | 50 | 69 | 71 | 72 | 74 | image/svg+xml 75 | 77 | 78 | 79 | 80 | Vadim Macagon 81 | 82 | 83 | 85 | 2014-09-04 86 | 87 | 89 | 91 | 93 | 95 | 97 | 99 | 100 | 101 | 102 | 106 | ? 132 | 133 | -------------------------------------------------------------------------------- /Art/RemovedStatusIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enlight/ue4-hg-plugin/272554b7b6a67c815274486b50a4a800bfa09abb/Art/RemovedStatusIcon.png -------------------------------------------------------------------------------- /Art/RemovedStatusIcon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 20 | 26 | 31 | 39 | 46 | 47 | 48 | 67 | 69 | 70 | 72 | image/svg+xml 73 | 75 | 76 | 77 | 78 | Vadim Macagon 79 | 80 | 81 | 2014-09-04 82 | 84 | 85 | 87 | 89 | 91 | 93 | 95 | 97 | 98 | 99 | 100 | 104 | X 130 | 131 | -------------------------------------------------------------------------------- /Content/SlateBrushes/AddedStatusIcon.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enlight/ue4-hg-plugin/272554b7b6a67c815274486b50a4a800bfa09abb/Content/SlateBrushes/AddedStatusIcon.uasset -------------------------------------------------------------------------------- /Content/SlateBrushes/CleanStatusIcon.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enlight/ue4-hg-plugin/272554b7b6a67c815274486b50a4a800bfa09abb/Content/SlateBrushes/CleanStatusIcon.uasset -------------------------------------------------------------------------------- /Content/SlateBrushes/MissingStatusIcon.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enlight/ue4-hg-plugin/272554b7b6a67c815274486b50a4a800bfa09abb/Content/SlateBrushes/MissingStatusIcon.uasset -------------------------------------------------------------------------------- /Content/SlateBrushes/ModifiedStatusIcon.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enlight/ue4-hg-plugin/272554b7b6a67c815274486b50a4a800bfa09abb/Content/SlateBrushes/ModifiedStatusIcon.uasset -------------------------------------------------------------------------------- /Content/SlateBrushes/NotTrackedStatusIcon.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enlight/ue4-hg-plugin/272554b7b6a67c815274486b50a4a800bfa09abb/Content/SlateBrushes/NotTrackedStatusIcon.uasset -------------------------------------------------------------------------------- /Content/SlateBrushes/RemovedStatusIcon.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enlight/ue4-hg-plugin/272554b7b6a67c815274486b50a4a800bfa09abb/Content/SlateBrushes/RemovedStatusIcon.uasset -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Vadim Macagon 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /MercurialSourceControl.uplugin: -------------------------------------------------------------------------------- 1 | { 2 | "FileVersion" : 3, 3 | 4 | "Version" : 3, 5 | "VersionName" : "0.3", 6 | "FriendlyName" : "Mercurial", 7 | "CreatedBy" : "Vadim Macagon", 8 | "CreatedByURL" : "https://github.com/enlight/ue4-hg-plugin", 9 | "Description" : "Allows the Editor to interact with the Mercurial distributed version control system. Supports basic add, remove, revert, commit operations, provides access to asset history, and enables built-in visual diffing of Blueprints.", 10 | "Category" : "Editor.Source Control", 11 | "EngineVersion" : "4.21.2", 12 | 13 | "Modules" : 14 | [ 15 | { 16 | "Name" : "MercurialSourceControl", 17 | "Type" : "Editor" 18 | } 19 | ], 20 | 21 | "CanContainContent" : true, 22 | "IsBetaVersion" : true 23 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ue4-hg-plugin (alpha) 2 | 3 | A basic Mercurial source control plugin for Unreal Engine 4, available under the MIT license. 4 | The master branch can be built against UE 4.7. 5 | 6 | ## Overview 7 | 8 | The Unreal Editor has built-in visual diffing for Blueprint assets, this feature relies on a **Source Control Provider** plugin to interface with the repository your assets are stored in. Currently UE4 ships with built-in source control provider plugins for SVN and Perforce. This source control provider plugin allows the Unreal Editor to interact with a Mercurial repository, thus unlocking all the built-in asset diffing goodness for those of us who prefer to use Mercurial. 9 | 10 | ## Supported Operating Systems 11 | 12 | The plugin should work on any OS the Unreal Editor can run on, however, I do all my development on Windows so if something isn't working right on another OS please let me know how to fix it :). 13 | 14 | ## Installation 15 | 16 | ### Prerequisites 17 | You need Mercurial installed on your system, preferably a standalone version that doesn't rely on Python (though that may work too, I just haven't tried). On Windows I'd recommend installing [TortoiseHg](http://tortoisehg.bitbucket.org/), the plugin will then auto-detect the location of the relevant Mercurial executable. 18 | 19 | ### Using the Binary UE4 Release 20 | When using a binary release of UE4 you can extract a binary release of the plugin (if available) to either of the following locations: 21 | >```/4.7/Engine/Plugins/Editor/MercurialSourceControl/``` 22 | > 23 | >```/Plugins/Editor/MercurialSourceControl/``` 24 | 25 | If you extract the plugin binaries into your project's plugins directory it will only be available for that project. 26 | 27 | Alternatively, you can either clone or extract the plugin source to your project's plugins directory, which is covered next. Note that placing the plugin source into the engine plugins directory probably won't work because I don't think the binary UE4 release is configured to build engine plugins from source (but I haven't tried yet). 28 | 29 | ### Using the GitHub UE4 Release 30 | If you'd like the plugin to be available for all your UE4 projects you need to clone or extract the plugin source to: 31 | >```/Engine/Plugins/Editor/MercurialSourceControl/``` 32 | 33 | Then follow these steps on Windows (adjust as needed on other OSes): 34 | 35 | 1. Run **GenerateProjectFiles.bat** in the UE4 source directory. 36 | 2. Open the generated **UE4.sln** Visual Studio solution file and build it. 37 | 3. Launch the Unreal Editor, open any project, and follow the instructions in the next section. 38 | 39 | Alternatively, if you only want to make the plugin available for a single project clone or extract the plugin source to: 40 | 41 | >```/Plugins/Editor/MercurialSourceControl/``` 42 | 43 | Then follow these steps on Windows (adjust as needed on other OSes): 44 | 45 | 1. Right-click on the **.uproject** file in Windows Explorer (e.g. **MyProject.uproject**) in your root project directory and select **Generate Visual Studio Files**. 46 | 2. Open the generated Visual Studio solution file (e.g. **MyProject.sln**) and build it. 47 | 3. Launch the Unreal Editor, open the project you've just built, and follow the instructions in the next section. 48 | 49 | Note that your existing project must have a **Source** subdirectory with a couple of **.Target.cs** files in it, if it doesn't you may need to follow the steps in the **Building from Scratch** section below and then copy the built plugin into your project(s). 50 | 51 | ### Editor Configuration 52 | Once you've got a binary version of the plugin (either by building or downloading) follow these steps: 53 | 54 | 1. Open **Window->Plugins** from the main menu of the Unreal Editor. 55 | 2. Navigate to the **Built-in/Editor/Source Control** or the **Installed/Editor/Source Control** sub-category, you should see the **Mercurial** plugin in the list. 56 | 3. Enable the plugin and restart the editor if requested to do so. 57 | 4. Click on the **circular red icon** in the top right corner of the Unreal Editor. 58 | 5. Select **Mercurial** from the drop-down. 59 | 6. If you installed [TortoiseHg](http://tortoisehg.bitbucket.org/) the **Mercurial Executable** should've been auto-detected, otherwise you need to specify the location of the Mercurial executable (hg.exe on Windows, may be just hg elsewhere). 60 | 7. Press the **Accept Settings** button to enable the Mercurial source control provider. 61 | 62 | ## Building from Scratch 63 | 64 | The following steps explain how to build the plugin as part of a new (mostly) empty project, in case you hit any issues while attempting to build it as part of an existing project, or as an engine plugin. 65 | 66 | 1. Create a new **Basic Code (C++)** project from the UE4 editor, e.g. **MyProject**, close the editor. 67 | 2. Create a new subdirectory called **Plugins** in your root project directory, e.g. **MyProject/Plugins**. 68 | 3. Clone or extract the code for this plugin into a subdirectory within the **Plugins** directory, e.g. **MyProject/Plugins/MercurialSourceControl**. 69 | 4. On Windows right-click on the **.uproject** file (e.g. **MyProject.uproject**) in your root project directory and select **Generate Visual Studio Files**. 70 | 5. On Windows open the generated Visual Studio solution file (e.g. **MyProject.sln**) and build it. 71 | 6. Launch the UE4 editor and open the project you created in step 1. 72 | 7. Open **Window->Plugins** from the main menu, navigate to the **Installed/Editor** category and you should see the **Mercurial** plugin in the list. 73 | 74 | If you'd like to build this plugin within an existing project just skip step 1, note that your existing project must have a **Source** subdirectory with a couple of **.Target.cs** files in it. 75 | -------------------------------------------------------------------------------- /Resources/Icon128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enlight/ue4-hg-plugin/272554b7b6a67c815274486b50a4a800bfa09abb/Resources/Icon128.png -------------------------------------------------------------------------------- /Source/MercurialSourceControl/MercurialSourceControl.Build.cs: -------------------------------------------------------------------------------- 1 | namespace UnrealBuildTool.Rules 2 | { 3 | public class MercurialSourceControl : ModuleRules 4 | { 5 | public MercurialSourceControl(ReadOnlyTargetRules Target) : base(Target) 6 | { 7 | PrivatePCHHeaderFile = "Private/MercurialSourceControlPrivatePCH.h"; 8 | PublicIncludePaths.AddRange( 9 | new string[] { 10 | // ... add public include paths required here ... 11 | } 12 | ); 13 | 14 | PrivateIncludePaths.AddRange( 15 | new string[] { 16 | // ... add other private include paths required here ... 17 | } 18 | ); 19 | 20 | PublicDependencyModuleNames.AddRange( 21 | new string[] 22 | { 23 | // ... add other public dependencies that you statically link with here ... 24 | } 25 | ); 26 | 27 | PrivateDependencyModuleNames.AddRange( 28 | new string[] 29 | { 30 | "Core", 31 | "Slate", 32 | "SlateCore", 33 | "EditorStyle", 34 | "SourceControl", 35 | "XmlParser", 36 | "InputCore", 37 | "DesktopPlatform", 38 | "AssetTools", 39 | "CoreUObject", 40 | "AssetRegistry", 41 | "UnrealEd" 42 | // ... add private dependencies that you statically link with here ... 43 | } 44 | ); 45 | 46 | DynamicallyLoadedModuleNames.AddRange( 47 | new string[] 48 | { 49 | // ... add any modules that your module loads dynamically here ... 50 | } 51 | ); 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /Source/MercurialSourceControl/Private/IMercurialSourceControlWorker.h: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------- 2 | // The MIT License (MIT) 3 | // 4 | // Copyright (c) 2014 Vadim Macagon 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in all 14 | // copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | // SOFTWARE. 23 | //------------------------------------------------------------------------------- 24 | #pragma once 25 | 26 | namespace MercurialSourceControl { 27 | 28 | /** 29 | * Interface for objects that do all the actual work of interfacing with Mercurial 30 | * to get things done. 31 | */ 32 | class IWorker 33 | { 34 | public: 35 | /** 36 | * Get a distinct name for the operation performed by the worker. 37 | * @note Must be unique for each class that implements this interface. 38 | */ 39 | virtual FName GetName() const = 0; 40 | 41 | /** 42 | * Perform the source control operation. 43 | * @note May be called on another thread. 44 | */ 45 | virtual bool Execute(class FCommand& InCommand) = 0; 46 | 47 | /** 48 | * Update the state of any affected items after completion of the operation. 49 | * @note Always called on the main thread. 50 | */ 51 | virtual bool UpdateStates() const = 0; 52 | 53 | virtual ~IWorker() = 0 {}; 54 | }; 55 | 56 | typedef TSharedRef FWorkerRef; 57 | typedef TSharedPtr FWorkerPtr; 58 | 59 | } // namespace MercurialSourceControl 60 | -------------------------------------------------------------------------------- /Source/MercurialSourceControl/Private/MercurialSourceControlClient.cpp: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------- 2 | // The MIT License (MIT) 3 | // 4 | // Copyright (c) 2014 Vadim Macagon 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in all 14 | // copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | // SOFTWARE. 23 | //------------------------------------------------------------------------------- 24 | 25 | #include "MercurialSourceControlPrivatePCH.h" 26 | #include "MercurialSourceControlClient.h" 27 | #include "ISourceControlModule.h" 28 | #include "XmlParser.h" 29 | #include "PlatformFilemanager.h" 30 | #include "WindowsHWrapper.h" 31 | 32 | // WinBase.h defines GetUserName conflicting with ISourceControlRevision::GetUserName and leads to obscure errors. 33 | // The line bellow prevents this error. 34 | #undef GetUserName 35 | 36 | namespace MercurialSourceControl { 37 | 38 | #define LOCTEXT_NAMESPACE "MercurialSourceControl.Client" 39 | 40 | FClientSharedPtr FClient::Singleton; 41 | 42 | /** 43 | * Creates a temp file on disk that is bound to the lifetime of an FScopedTempFile instance. 44 | * When an FScopedTempFile instance is destroyed the temp file it created is deleted from disk. 45 | */ 46 | class FScopedTempFile 47 | { 48 | public: 49 | FScopedTempFile(const FString& InExtension) 50 | : Extension(InExtension) 51 | { 52 | } 53 | 54 | ~FScopedTempFile() 55 | { 56 | if (!Filename.IsEmpty()) 57 | { 58 | IFileManager::Get().Delete(*Filename); 59 | } 60 | } 61 | 62 | const FString& GetFilename() 63 | { 64 | if (Filename.IsEmpty()) 65 | { 66 | FString OutputDir = FPaths::ProjectLogDir(); 67 | FPaths::NormalizeDirectoryName(OutputDir); 68 | Filename = FPaths::CreateTempFilename(*OutputDir, TEXT("hg-"), *Extension); 69 | Filename = FPaths::ConvertRelativePathToFull(Filename); 70 | } 71 | return Filename; 72 | } 73 | 74 | private: 75 | FString Filename; 76 | FString Extension; 77 | }; 78 | 79 | bool FClient::IsValidExecutable(const FString& InFilename) 80 | { 81 | if (FPaths::FileExists(InFilename)) 82 | { 83 | int32 ReturnCode = 0; 84 | FString Output; 85 | FString Error; 86 | 87 | FPlatformProcess::ExecProcess(*InFilename, TEXT("version"), &ReturnCode, &Output, &Error); 88 | return (ReturnCode == 0) && Output.Contains(TEXT("Mercurial")); 89 | } 90 | return false; 91 | } 92 | 93 | bool FClient::FindExecutable(FString& OutFilename) 94 | { 95 | OutFilename.Empty(); 96 | 97 | #if PLATFORM_WINDOWS 98 | // look for the hg.exe that's shipped with TortoiseHg 99 | const TCHAR* SubKey = TEXT("Software\\TortoiseHg"); 100 | const TCHAR* ValueName = TEXT(""); 101 | FString HgPath; 102 | 103 | if (FPlatformMisc::QueryRegKey(HKEY_CURRENT_USER, SubKey, ValueName, HgPath) || 104 | FPlatformMisc::QueryRegKey(HKEY_LOCAL_MACHINE, SubKey, ValueName, HgPath)) 105 | { 106 | HgPath /= TEXT("hg.exe"); 107 | if (IsValidExecutable(HgPath)) 108 | { 109 | OutFilename = HgPath; 110 | } 111 | } 112 | #endif // PLATFORM_WINDOWS 113 | 114 | return !OutFilename.IsEmpty(); 115 | } 116 | 117 | bool FClient::Create(const FString& InMercurialPath, FText& OutError) 118 | { 119 | if (ensure(!Singleton.IsValid())) 120 | { 121 | FString ExePath = InMercurialPath; 122 | 123 | bool bExeFound = ExePath.IsEmpty() ? 124 | FClient::FindExecutable(ExePath) : FClient::IsValidExecutable(ExePath); 125 | 126 | if (bExeFound) 127 | { 128 | Singleton = MakeShareable(new FClient(ExePath)); 129 | } 130 | else 131 | { 132 | OutError = LOCTEXT("ExeNotFound", "Failed to locate a valid Mercurial executable."); 133 | } 134 | } 135 | return Singleton.IsValid(); 136 | } 137 | 138 | const FClientSharedPtr& FClient::Get() 139 | { 140 | return Singleton; 141 | } 142 | 143 | void FClient::Destroy() 144 | { 145 | Singleton.Reset(); 146 | } 147 | 148 | bool FClient::GetRepositoryRoot(const FString& InWorkingDirectory, FString& OutRepositoryRoot) const 149 | { 150 | FString Output; 151 | TArray None; 152 | TArray Errors; 153 | if (RunCommand(TEXT("root"), None, InWorkingDirectory, None, false, Output, Errors)) 154 | { 155 | Output.RemoveFromEnd(TEXT("\n")); 156 | OutRepositoryRoot = Output; 157 | FPaths::NormalizeDirectoryName(OutRepositoryRoot); 158 | OutRepositoryRoot += TEXT("/"); 159 | return true; 160 | } 161 | return false; 162 | } 163 | 164 | bool FClient::GetFileStates( 165 | const FString& InWorkingDirectory, const TArray& InAbsoluteFiles, 166 | TArray& OutFileStates, TArray& OutErrors 167 | ) const 168 | { 169 | TArray RelativeFiles; 170 | // convert absolute paths to be relative to the working directory 171 | for (const auto& AbsoluteFilename : InAbsoluteFiles) 172 | { 173 | // TODO: Consider logging a warning if the relative path can't be deduced. 174 | // Unfortunately UnrealEd has a tendency to pass in paths to built-in engine content, 175 | // and if the end user creates their project on a different drive to the one the 176 | // engine is installed on those paths can't be converted to be relative to the 177 | // project's repository working directory. 178 | FString Filename = AbsoluteFilename; 179 | if (FPaths::MakePathRelativeTo(Filename, *InWorkingDirectory)) 180 | { 181 | RelativeFiles.Add(Filename); 182 | } 183 | } 184 | 185 | if (RelativeFiles.Num() == 0) 186 | { 187 | return true; 188 | } 189 | 190 | TArray Options; 191 | // show all modified, added, removed, deleted, unknown, clean, and ignored files 192 | Options.Add(TEXT("-marduci")); 193 | FString Output; 194 | 195 | if (RunCommand(TEXT("status"), Options, InWorkingDirectory, RelativeFiles, false, Output, OutErrors)) 196 | { 197 | TArray Lines; 198 | Output.ParseIntoArray(Lines, TEXT("\n"), true); 199 | for (const auto& Line : Lines) 200 | { 201 | // each line consists of a one character status code followed by a filename, 202 | // a single space separates the status code from the filename 203 | FString Filename = Line.RightChop(2); 204 | FPaths::NormalizeFilename(Filename); 205 | FFileState FileState(InWorkingDirectory / Filename); 206 | FileState.SetFileStatus(StatusCodeToFileStatus(Line[0])); 207 | OutFileStates.Add(FileState); 208 | } 209 | return true; 210 | } 211 | return false; 212 | } 213 | 214 | bool FClient::GetFileHistory( 215 | const FString& InWorkingDirectory, const TArray& InAbsoluteFiles, 216 | TMap >& OutFileRevisionsMap, TArray& OutErrors 217 | ) const 218 | { 219 | TArray RelativeFiles; 220 | if (!ConvertFilesToRelative(InWorkingDirectory, InAbsoluteFiles, RelativeFiles)) 221 | { 222 | // FIXME: Instead of quiting as soon as we get an invalid filename keep going! 223 | return false; 224 | } 225 | 226 | bool bResult = true; 227 | TArray Options; 228 | Options.Add(TEXT("--encoding utf-8")); 229 | Options.Add(TEXT("--style xml")); 230 | // verbose: all changes and full commit messages 231 | Options.Add(TEXT("-v")); 232 | FXmlFile XmlFile; 233 | 234 | for (const auto& RelativeFile : RelativeFiles) 235 | { 236 | FString Output; 237 | if (RunCommand(TEXT("log"), Options, InWorkingDirectory, RelativeFile, Output, OutErrors)) 238 | { 239 | if (XmlFile.LoadFile(Output, EConstructMethod::ConstructFromBuffer)) 240 | { 241 | TArray FileRevisions; 242 | GetFileRevisionsFromXml(RelativeFile, XmlFile, FileRevisions); 243 | if (FileRevisions.Num() > 0) 244 | { 245 | FString AbsoluteFile = InWorkingDirectory / RelativeFile; 246 | for (const auto& Revision : FileRevisions) 247 | { 248 | Revision->SetFilename(AbsoluteFile); 249 | } 250 | OutFileRevisionsMap.Add(AbsoluteFile, FileRevisions); 251 | } 252 | } 253 | } 254 | else 255 | { 256 | bResult = false; 257 | } 258 | } 259 | return bResult; 260 | } 261 | 262 | bool FClient::ExtractFileFromRevision( 263 | const FString& InWorkingDirectory, int32 RevisionNumber, const FString& InFileToExtract, 264 | const FString& InDestinationFile, TArray& OutErrors 265 | ) const 266 | { 267 | FString Filename = InFileToExtract; 268 | if (!FPaths::MakePathRelativeTo(Filename, *InWorkingDirectory)) 269 | { 270 | return false; 271 | } 272 | 273 | TArray Options; 274 | Options.Add(FString::Printf(TEXT("--rev %d"), RevisionNumber)); 275 | Options.Add(FString(TEXT("--output ")) + QuoteFilename(InDestinationFile)); 276 | FString Output; 277 | 278 | return RunCommand( 279 | TEXT("cat"), Options, InWorkingDirectory, Filename, Output, OutErrors 280 | ); 281 | } 282 | 283 | bool FClient::AddFiles( 284 | const FString& InWorkingDirectory, const TArray& InAbsoluteFiles, bool bInAddAsLarge, 285 | TArray& OutErrors 286 | ) const 287 | { 288 | TArray RelativeFiles; 289 | if (!ConvertFilesToRelative(InWorkingDirectory, InAbsoluteFiles, RelativeFiles)) 290 | { 291 | return false; 292 | } 293 | 294 | TArray Options; 295 | if (bInAddAsLarge) 296 | { 297 | Options.Add("--large"); 298 | } 299 | FString Output; 300 | 301 | return RunCommand(TEXT("add"), Options, InWorkingDirectory, RelativeFiles, false, Output, OutErrors); 302 | } 303 | 304 | bool FClient::RevertFiles( 305 | const FString& InWorkingDirectory, const TArray& InAbsoluteFiles, 306 | TArray& OutErrors 307 | ) const 308 | { 309 | TArray RelativeFiles; 310 | if (!ConvertFilesToRelative(InWorkingDirectory, InAbsoluteFiles, RelativeFiles)) 311 | { 312 | return false; 313 | } 314 | 315 | TArray Options; 316 | // TODO: It would be a good idea to allow users to toggle this option via the source control 317 | // provider settings panel/dialog. 318 | Options.Add(TEXT("--no-backup")); 319 | FString Output; 320 | 321 | return RunCommand( 322 | TEXT("revert"), Options, InWorkingDirectory, RelativeFiles, false, Output, OutErrors 323 | ); 324 | } 325 | 326 | bool FClient::RemoveFiles( 327 | const FString& InWorkingDirectory, const TArray& InAbsoluteFiles, 328 | TArray& OutErrors 329 | ) const 330 | { 331 | TArray RelativeFiles; 332 | if (!ConvertFilesToRelative(InWorkingDirectory, InAbsoluteFiles, RelativeFiles)) 333 | { 334 | return false; 335 | } 336 | 337 | TArray Options; 338 | FString Output; 339 | 340 | return RunCommand( 341 | TEXT("remove"), Options, InWorkingDirectory, RelativeFiles, false, Output, OutErrors 342 | ); 343 | } 344 | 345 | bool FClient::RemoveAllFiles( 346 | const FString& InWorkingDirectory, const TArray& InAbsoluteFiles, 347 | TArray& OutErrors 348 | ) const 349 | { 350 | // The idea here is to emulate the functionality of "svn delete", which works slightly 351 | // differently to "hg remove". The difference being SVN will delete files with a status of 352 | // "added" from the disk, but HG will not (it expects you to use "hg forget" first and then 353 | // delete the file from disk manually). 354 | 355 | // first we need to figure out what the status of each file we need to remove is 356 | TArray FileStates; 357 | if (!GetFileStates(InWorkingDirectory, InAbsoluteFiles, FileStates, OutErrors)) 358 | { 359 | return false; 360 | } 361 | 362 | // now we can split out the "added" files that need special handling from the rest 363 | TArray AddedFiles; 364 | TArray RemovableFiles; 365 | for (const auto& FileState : FileStates) 366 | { 367 | switch (FileState.GetFileStatus()) 368 | { 369 | case EFileStatus::Added: 370 | AddedFiles.Add(FileState.GetFilename()); 371 | break; 372 | 373 | case EFileStatus::Clean: 374 | case EFileStatus::Missing: 375 | RemovableFiles.Add(FileState.GetFilename()); 376 | break; 377 | } 378 | } 379 | 380 | bool bResult = true; 381 | 382 | // forget and delete added files 383 | if (AddedFiles.Num() > 0) 384 | { 385 | TArray RelativeFiles; 386 | if (!ConvertFilesToRelative(InWorkingDirectory, AddedFiles, RelativeFiles)) 387 | { 388 | return false; 389 | } 390 | 391 | TArray Options; 392 | FString Output; 393 | 394 | bResult &= RunCommand( 395 | TEXT("forget"), Options, InWorkingDirectory, RelativeFiles, false, Output, OutErrors 396 | ); 397 | 398 | for (const auto& Filename : AddedFiles) 399 | { 400 | bResult &= IFileManager::Get().Delete(*Filename); 401 | } 402 | } 403 | 404 | // remove any other removable files 405 | if (RemovableFiles.Num() > 0) 406 | { 407 | bResult &= RemoveFiles(InWorkingDirectory, RemovableFiles, OutErrors); 408 | } 409 | 410 | return bResult; 411 | } 412 | 413 | bool FClient::CommitFiles( 414 | const FString& InWorkingDirectory, const TArray& InAbsoluteFiles, 415 | const FString& InCommitMessage, TArray& OutErrors 416 | ) const 417 | { 418 | TArray RelativeFiles; 419 | if (!ConvertFilesToRelative(InWorkingDirectory, InAbsoluteFiles, RelativeFiles)) 420 | { 421 | return false; 422 | } 423 | 424 | auto Encoding = FCString::IsPureAnsi(*InCommitMessage) ? 425 | FFileHelper::EEncodingOptions::ForceAnsi : FFileHelper::EEncodingOptions::ForceUTF8; 426 | 427 | // write the commit message to a temp file 428 | FScopedTempFile CommitMessageFile(TEXT(".txt")); 429 | bool bResult = FFileHelper::SaveStringToFile( 430 | InCommitMessage, *CommitMessageFile.GetFilename(), Encoding 431 | ); 432 | 433 | if (!bResult) 434 | { 435 | OutErrors.Add(FString::Printf( 436 | TEXT("Failed to write to temp file: %s"), *CommitMessageFile.GetFilename() 437 | )); 438 | return false; 439 | } 440 | 441 | TArray Options; 442 | if (Encoding == FFileHelper::EEncodingOptions::ForceUTF8) 443 | { 444 | Options.Add(FString(TEXT("--encoding utf-8"))); 445 | } 446 | Options.Add(FString(TEXT("--logfile ")) + QuoteFilename(CommitMessageFile.GetFilename())); 447 | FString Output; 448 | 449 | return RunCommand( 450 | TEXT("commit"), Options, InWorkingDirectory, RelativeFiles, true, Output, OutErrors 451 | ); 452 | } 453 | 454 | bool FClient::GetWorkingDirectoryParentRevisionID( 455 | const FString& InWorkingDirectory, FString& OutRevisionID, TArray& OutErrors 456 | ) const 457 | { 458 | TArray Options; 459 | // just grab the local revision number 460 | Options.Add(FString(TEXT("--template \"{rev}\""))); 461 | FString Command(TEXT("parents")); 462 | AppendCommandOptions(Command, Options, InWorkingDirectory); 463 | return RunCommand(Command, OutRevisionID, OutErrors); 464 | } 465 | 466 | void FClient::AppendCommandOptions( 467 | FString& InOutCommand, const TArray& InOptions, const FString& InWorkingDirectory 468 | ) 469 | { 470 | for (int32 i = 0; i < InOptions.Num(); ++i) 471 | { 472 | InOutCommand += TEXT(" "); 473 | InOutCommand += InOptions[i]; 474 | } 475 | 476 | // run in non-interactive mode 477 | // (not strictly necessary as hg should detect the lack of a terminal, but just in case) 478 | InOutCommand += TEXT(" -y"); 479 | 480 | // set the current working directory to the current game's content root 481 | InOutCommand += TEXT(" --cwd "); 482 | InOutCommand += QuoteFilename(InWorkingDirectory); 483 | } 484 | 485 | void FClient::AppendCommandFile(FString& InOutCommand, const FString& InFilename) 486 | { 487 | InOutCommand += TEXT(" "); 488 | InOutCommand += QuoteFilename(InFilename); 489 | } 490 | 491 | void FClient::AppendCommandFiles(FString& InOutCommand, const TArray& InFiles) 492 | { 493 | for (int32 i = 0; i < InFiles.Num(); ++i) 494 | { 495 | InOutCommand += TEXT(" "); 496 | InOutCommand += QuoteFilename(InFiles[i]); 497 | } 498 | } 499 | 500 | int32 FClient::GetFullCommandLength(const FString& InCommand, const TArray& InFiles) 501 | { 502 | int32 Length = InCommand.Len(); 503 | for (const auto& Filename : InFiles) 504 | { 505 | Length += Filename.Len(); 506 | Length += 3; // 1 space + 2 double-quotes 507 | } 508 | return Length; 509 | } 510 | 511 | bool FClient::RunCommand( 512 | const FString& InCommand, FString& OutResults, TArray& OutErrorMessages 513 | ) const 514 | { 515 | UE_LOG(LogSourceControl, Log, TEXT("Executing hg %s"), *InCommand); 516 | 517 | int32 ReturnCode = 0; 518 | FString StdError; 519 | 520 | FPlatformProcess::ExecProcess( 521 | *MercurialExecutablePath, *InCommand, &ReturnCode, &OutResults, &StdError 522 | ); 523 | 524 | TArray ErrorMessages; 525 | if (StdError.ParseIntoArray(ErrorMessages, TEXT("\n"), true) > 0) 526 | { 527 | OutErrorMessages.Append(ErrorMessages); 528 | } 529 | 530 | return ReturnCode == 0; 531 | } 532 | 533 | bool FClient::RunCommand( 534 | const FString& InCommand, const TArray& InOptions, 535 | const FString& InWorkingDirectory, const TArray& InFiles, bool bForceFileList, 536 | FString& OutResults, TArray& OutErrorMessages 537 | ) const 538 | { 539 | FString Command(InCommand); 540 | AppendCommandOptions(Command, InOptions, InWorkingDirectory); 541 | 542 | // on Windows 7+ this number is actually around 32,000, but we'll pick something lower in case 543 | // other platforms are less generous 544 | const int32 MaxCommandLineLength = 16000; 545 | 546 | if (bForceFileList 547 | || ((InFiles.Num() > 0) && (GetFullCommandLength(Command, InFiles) > MaxCommandLineLength))) 548 | { 549 | // Write all the filenames to be committed to a temp file that will be passed in to hg, 550 | // this gets around command-line argument length limitations. 551 | FString FileList; 552 | for (const auto& RelativeFilename : InFiles) 553 | { 554 | FileList += TEXT("path:"); 555 | FileList += RelativeFilename + TEXT("\n"); 556 | } 557 | 558 | // The file list must be saved using the system's default encoding, because that's the 559 | // encoding hg will always use when reading in the file list. For future reference: 560 | // http://mercurial.selenic.com/wiki/EncodingStrategy 561 | // http://en.it-usenet.org/thread/16853/40385/ 562 | FScopedTempFile ListFile(TEXT(".lst")); 563 | bool bResult = FFileHelper::SaveStringToFile( 564 | FileList, *ListFile.GetFilename(), FFileHelper::EEncodingOptions::ForceAnsi 565 | ); 566 | 567 | if (!bResult) 568 | { 569 | OutErrorMessages.Add( 570 | FString::Printf(TEXT("Failed to write to temp file: '%s'"), *ListFile.GetFilename()) 571 | ); 572 | return false; 573 | } 574 | 575 | AppendCommandFile(Command, FString::Printf(TEXT("listfile:%s"), *ListFile.GetFilename())); 576 | // ListFile must be in-scope when this call is made 577 | return RunCommand(Command, OutResults, OutErrorMessages); 578 | } 579 | else 580 | { 581 | AppendCommandFiles(Command, InFiles); 582 | return RunCommand(Command, OutResults, OutErrorMessages); 583 | } 584 | } 585 | 586 | bool FClient::RunCommand( 587 | const FString& InCommand, const TArray& InOptions, 588 | const FString& InWorkingDirectory, const FString& InFilename, 589 | FString& OutResults, TArray& OutErrorMessages 590 | ) const 591 | { 592 | FString Command(InCommand); 593 | AppendCommandOptions(Command, InOptions, InWorkingDirectory); 594 | AppendCommandFile(Command, InFilename); 595 | return RunCommand(Command, OutResults, OutErrorMessages); 596 | } 597 | 598 | FString FClient::QuoteFilename(const FString& InFilename) 599 | { 600 | const FString Quote(TEXT("\"")); 601 | return Quote + InFilename + Quote; 602 | } 603 | 604 | EFileStatus FClient::StatusCodeToFileStatus(TCHAR StatusCode) 605 | { 606 | switch (StatusCode) 607 | { 608 | case TEXT('M'): 609 | return EFileStatus::Modified; 610 | 611 | case TEXT('A'): 612 | return EFileStatus::Added; 613 | 614 | case TEXT('R'): 615 | return EFileStatus::Removed; 616 | 617 | case TEXT('C'): 618 | return EFileStatus::Clean; 619 | 620 | case TEXT('!'): 621 | return EFileStatus::Missing; 622 | 623 | case TEXT('?'): 624 | return EFileStatus::NotTracked; 625 | 626 | case TEXT('I'): 627 | return EFileStatus::Ignored; 628 | 629 | default: 630 | return EFileStatus::Unknown; 631 | } 632 | } 633 | 634 | FString FClient::ActionCodeToString(TCHAR ActionCode) 635 | { 636 | switch (ActionCode) 637 | { 638 | case TEXT('M'): 639 | return TEXT("edit"); 640 | 641 | case TEXT('A'): 642 | return TEXT("add"); 643 | 644 | case TEXT('R'): 645 | return TEXT("remove"); 646 | 647 | default: 648 | return TEXT("unknown"); 649 | } 650 | } 651 | 652 | FDateTime FClient::Rfc3339DateToDateTime(const FString& InDateString) 653 | { 654 | // There are some slight variations but the variant Mercurial seems to use by default is: 655 | // YYYY-MM-DDTHH:MM:SS[+,-]HH:MM 656 | const TCHAR* Space = TEXT(" "); 657 | FString Buffer = InDateString.Replace(TEXT("T"), Space); 658 | Buffer.ReplaceInline(TEXT("Z"), Space); 659 | Buffer.ReplaceInline(TEXT("-"), Space); 660 | Buffer.ReplaceInline(TEXT(":"), Space); 661 | 662 | TArray Segments; 663 | Buffer.ParseIntoArray(Segments, Space, true); 664 | 665 | int32 Year = FMath::Clamp((Segments.Num() > 0) ? FCString::Atoi(*Segments[0]) : 0, 0, 9999); 666 | int32 Month = FMath::Clamp((Segments.Num() > 1) ? FCString::Atoi(*Segments[1]) : 0, 1, 12); 667 | int32 Day = FMath::Clamp( 668 | (Segments.Num() > 2) ? FCString::Atoi(*Segments[2]) : 0, 669 | 1, FDateTime::DaysInMonth(Year, Month) 670 | ); 671 | int32 Hour = FMath::Clamp((Segments.Num() > 3) ? FCString::Atoi(*Segments[3]) : 0, 0, 23); 672 | int32 Minute = FMath::Clamp((Segments.Num() > 4) ? FCString::Atoi(*Segments[4]) : 0, 0, 59); 673 | int32 Second = FMath::Clamp((Segments.Num() > 5) ? FCString::Atoi(*Segments[5]) : 0, 0, 59); 674 | 675 | return FDateTime(Year, Month, Day, Hour, Minute, Second); 676 | } 677 | 678 | void FClient::GetFileRevisionsFromXml( 679 | const FString& InFilename, const FXmlFile& InXmlFile, TArray& OutFileRevisions 680 | ) 681 | { 682 | static const FString LogTag(TEXT("log")); 683 | static const FString LogEntryTag(TEXT("logentry")); 684 | static const FString RevisionTag(TEXT("revision")); 685 | static const FString CommitIdTag(TEXT("node")); 686 | static const FString AuthorTag(TEXT("author")); 687 | static const FString DateTag(TEXT("date")); 688 | static const FString MsgTag(TEXT("msg")); 689 | static const FString PathsTag(TEXT("paths")); 690 | static const FString PathTag(TEXT("path")); 691 | static const FString ActionTag(TEXT("action")); 692 | 693 | const FXmlNode* LogNode = InXmlFile.GetRootNode(); 694 | check(LogNode && (LogNode->GetTag() == LogTag)); 695 | if (!LogNode || (LogNode->GetTag() != LogTag)) 696 | return; 697 | 698 | const TArray LogEntries = LogNode->GetChildrenNodes(); 699 | for (auto LogEntryIt(LogEntries.CreateConstIterator()); LogEntryIt; LogEntryIt++) 700 | { 701 | const FXmlNode* LogEntryNode = *LogEntryIt; 702 | check(LogEntryNode && (LogEntryNode->GetTag() == LogEntryTag)); 703 | if (!LogEntryNode || (LogEntryNode->GetTag() != LogEntryTag)) 704 | { 705 | continue; 706 | } 707 | 708 | // note: we don't set the filename for the created revision, this is because the filename 709 | // must be absolute and we only have the relative filename at this point 710 | FFileRevisionRef FileRevision = MakeShareable(new FFileRevision()); 711 | FileRevision->SetRevisionNumber(FCString::Atoi(*LogEntryNode->GetAttribute(RevisionTag))); 712 | FileRevision->SetCommitId(*LogEntryNode->GetAttribute(CommitIdTag)); 713 | 714 | const FXmlNode* AuthorNode = LogEntryNode->FindChildNode(AuthorTag); 715 | if (AuthorNode) 716 | { 717 | FileRevision->SetUserName(AuthorNode->GetContent()); 718 | } 719 | 720 | const FXmlNode* DateNode = LogEntryNode->FindChildNode(DateTag); 721 | if (DateNode) 722 | { 723 | FileRevision->SetDate(Rfc3339DateToDateTime(DateNode->GetContent())); 724 | } 725 | 726 | const FXmlNode* MsgNode = LogEntryNode->FindChildNode(MsgTag); 727 | if (MsgNode) 728 | { 729 | FileRevision->SetDescription(UnescapeXMLEntities(MsgNode->GetContent())); 730 | } 731 | 732 | // the paths node contains path nodes indicating the operations that were performed, 733 | // e.g. 734 | // 735 | // foo/bar/Test.txt 736 | // foo/Test.txt 737 | // 738 | // In the example above Test.txt was moved from directory foo to foo/bar. 739 | const FXmlNode* PathsNode = LogEntryNode->FindChildNode(PathsTag); 740 | if (PathsNode) 741 | { 742 | const TArray Paths = PathsNode->GetChildrenNodes(); 743 | for (auto PathIt(Paths.CreateConstIterator()); PathIt; PathIt++) 744 | { 745 | const FXmlNode* PathNode = *PathIt; 746 | if (PathNode && (PathNode->GetTag() == PathTag)) 747 | { 748 | if (PathNode->GetContent() == InFilename) 749 | { 750 | FString ActionCode = PathNode->GetAttribute(ActionTag); 751 | if (ActionCode.Len() > 0) 752 | { 753 | FileRevision->SetAction(ActionCodeToString(ActionCode[0])); 754 | } 755 | else 756 | { 757 | FileRevision->SetAction(TEXT("unknown")); 758 | } 759 | } 760 | } 761 | } 762 | } 763 | 764 | OutFileRevisions.Add(FileRevision); 765 | } 766 | } 767 | 768 | FString FClient::UnescapeXMLEntities(const FString& InEscapedText) 769 | { 770 | FString Text(InEscapedText); 771 | 772 | Text.ReplaceInline(TEXT("<"), TEXT("<")); 773 | Text.ReplaceInline(TEXT("<"), TEXT("<")); 774 | 775 | Text.ReplaceInline(TEXT(">"), TEXT(">")); 776 | Text.ReplaceInline(TEXT(">"), TEXT(">")); 777 | 778 | Text.ReplaceInline(TEXT("""), TEXT("\"")); 779 | Text.ReplaceInline(TEXT("""), TEXT("\"")); 780 | 781 | Text.ReplaceInline(TEXT("'"), TEXT("'")); 782 | Text.ReplaceInline(TEXT("'"), TEXT("'")); 783 | 784 | Text.ReplaceInline(TEXT("&"), TEXT("&")); 785 | Text.ReplaceInline(TEXT("&"), TEXT("&")); 786 | 787 | return Text; 788 | } 789 | 790 | bool FClient::ConvertFilesToRelative( 791 | const FString& InRelativeTo, const TArray& InFiles, TArray& OutFiles 792 | ) 793 | { 794 | for (const auto& AbsoluteFilename : InFiles) 795 | { 796 | FString Filename = AbsoluteFilename; 797 | if (FPaths::MakePathRelativeTo(Filename, *InRelativeTo)) 798 | { 799 | OutFiles.Add(Filename); 800 | } 801 | else 802 | { 803 | return false; 804 | } 805 | } 806 | return true; 807 | } 808 | 809 | #undef LOCTEXT_NAMESPACE 810 | 811 | } // namespace MercurialSourceControl 812 | -------------------------------------------------------------------------------- /Source/MercurialSourceControl/Private/MercurialSourceControlClient.h: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------- 2 | // The MIT License (MIT) 3 | // 4 | // Copyright (c) 2014 Vadim Macagon 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in all 14 | // copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | // SOFTWARE. 23 | //------------------------------------------------------------------------------- 24 | #pragma once 25 | 26 | #include "MercurialSourceControlFileState.h" 27 | #include "MercurialSourceControlFileRevision.h" 28 | 29 | class FXmlFile; 30 | 31 | namespace MercurialSourceControl { 32 | 33 | typedef TSharedPtr FClientSharedPtr; 34 | 35 | class FFileState; 36 | 37 | /** Executes source control commands in a Mercurial repository by invoking hg.exe. */ 38 | class FClient : public TSharedFromThis 39 | { 40 | public: 41 | /** 42 | * Check if the given filename corresponds to a valid Mercurial executable file. 43 | * @note It's safe to call this method at any time, even before Initialize(). 44 | */ 45 | static bool IsValidExecutable(const FString& InFilename); 46 | 47 | static bool FindExecutable(FString& OutFilename); 48 | 49 | /** 50 | * Create and initialize the FClient singleton instance. 51 | * @param InMercurialPath Absolute path to the Mercurial executable that should be invoked to 52 | * manipulate a Mercurial repository. 53 | * @param OutError Will contain an error message if this method returns false. 54 | * @return true if the singleton instance was created and initialized successfully, 55 | * false otherwise. 56 | */ 57 | static bool Create(const FString& InMercurialPath, FText& OutError); 58 | static const FClientSharedPtr& Get(); 59 | static void Destroy(); 60 | 61 | public: 62 | /** Get the root directory of the repository in which the given working directory resides. */ 63 | bool GetRepositoryRoot(const FString& InWorkingDirectory, FString& OutRepositoryRoot) const; 64 | 65 | bool GetFileStates( 66 | const FString& InWorkingDirectory, const TArray& InAbsoluteFiles, 67 | TArray& OutFileStates, TArray& OutErrors 68 | ) const; 69 | 70 | bool GetFileHistory( 71 | const FString& InWorkingDirectory, const TArray& InAbsoluteFiles, 72 | TMap >& OutFileRevisionsMap, TArray& OutErrors 73 | ) const; 74 | 75 | /** 76 | * Recreate a file as it was at the given revision. 77 | * @param InWorkingDirectory The working directory to set for hg.exe. 78 | * @param RevisionNumber The local revision to recreate the file from. 79 | * @param InFileToExtract The original absolute filename of the file to be recreated. 80 | * @param InDestinationFile The absolute path at which the file should be recreated. 81 | * @param OutErrors Output from stderr of hg.exe. 82 | * @return true if the operation was successful, false otherwise. 83 | */ 84 | bool ExtractFileFromRevision( 85 | const FString& InWorkingDirectory, int32 RevisionNumber, const FString& InFileToExtract, 86 | const FString& InDestinationFile, TArray& OutErrors 87 | ) const; 88 | 89 | /** 90 | * Add files to the repository. 91 | * @param InWorkingDirectory The working directory to set for hg.exe. 92 | * @param InAbsoluteFiles Full filenames of files to add to the repository. 93 | * @param bInAddAsLarge If true the files will be flagged as large when they're added to the 94 | * repository, otherwise they'll be added with no special flags. 95 | * @param OutErrors Output from stderr of hg.exe. 96 | * @return true if the operation was successful, false otherwise. 97 | */ 98 | bool AddFiles( 99 | const FString& InWorkingDirectory, const TArray& InAbsoluteFiles, 100 | bool bInAddAsLarge, TArray& OutErrors 101 | ) const; 102 | 103 | /** 104 | * Revert the given files to the contents they had in the parent of the working directory. 105 | * The files will be restored to an unmodified state and any pending adds, removes, copies, 106 | * and renames will be undone. 107 | * @param InWorkingDirectory The working directory to set for hg.exe. 108 | * @param InAbsoluteFiles The absolute filenames of the files to revert. 109 | * @param OutErrors Output from stderr of hg.exe. 110 | * @return true if the operation was successful, false otherwise. 111 | */ 112 | bool RevertFiles( 113 | const FString& InWorkingDirectory, const TArray& InAbsoluteFiles, 114 | TArray& OutErrors 115 | ) const; 116 | 117 | /** Remove clean and missing files from the repository. */ 118 | bool RemoveFiles( 119 | const FString& InWorkingDirectory, const TArray& InAbsoluteFiles, 120 | TArray& OutErrors 121 | ) const; 122 | 123 | /** Remove added, clean, and missing files from the repository. */ 124 | bool RemoveAllFiles( 125 | const FString& InWorkingDirectory, const TArray& InAbsoluteFiles, 126 | TArray& OutErrors 127 | ) const; 128 | 129 | bool CommitFiles( 130 | const FString& InWorkingDirectory, const TArray& InAbsoluteFiles, 131 | const FString& InCommitMessage, TArray& OutErrors 132 | ) const; 133 | 134 | /** Get the local ID of the working directory's parent revision. */ 135 | bool GetWorkingDirectoryParentRevisionID( 136 | const FString& InWorkingDirectory, FString& OutRevisionID, TArray& OutErrors 137 | ) const; 138 | 139 | private: 140 | static void AppendCommandOptions( 141 | FString& InOutCommand, const TArray& InOptions, 142 | const FString& InWorkingDirectory 143 | ); 144 | static void AppendCommandFile(FString& InOutCommand, const FString& InFilename); 145 | static void AppendCommandFiles(FString& InOutCommand, const TArray& InFiles); 146 | static int32 GetFullCommandLength(const FString& InCommand, const TArray& InFiles); 147 | 148 | /** Enclose the given filename in double-quotes. */ 149 | static FString QuoteFilename(const FString& InFilename); 150 | 151 | /** Convert a standard Mercurial status code character to the corresponding EFileStatus. */ 152 | static EFileStatus StatusCodeToFileStatus(TCHAR StatusCode); 153 | 154 | static FString ActionCodeToString(TCHAR ActionCode); 155 | static FDateTime Rfc3339DateToDateTime(const FString& InDateString); 156 | 157 | /** 158 | * Extract file revisions from an XML log. 159 | * @param InFilename The filename for which revisions should be extracted. 160 | * @note The extracted revisions don't have a filename set! 161 | */ 162 | static void GetFileRevisionsFromXml( 163 | const FString& InFilename, const FXmlFile& InXmlFile, 164 | TArray& OutFileRevisions 165 | ); 166 | 167 | static FString UnescapeXMLEntities(const FString& InEscapedText); 168 | 169 | /** Convert all the given filenames to be relative to the specified path. */ 170 | static bool ConvertFilesToRelative( 171 | const FString& InRelativeTo, const TArray& InFiles, TArray& OutFiles 172 | ); 173 | 174 | private: 175 | /** 176 | * Constructor. 177 | * @param InMercurialPath Absolute valid path to hg.exe. 178 | */ 179 | FClient(const FString& InMercurialPath) : MercurialExecutablePath(InMercurialPath) {} 180 | 181 | /** 182 | * Invoke hg.exe with the given command and return the output. 183 | * @param InCommand A fully formed hg command, e.g. status --verbose Content/SomeFile.txt 184 | * @param OutResults Output from stdout of hg.exe. 185 | * @param OutErrorMessages Output from stderr of hg.exe. 186 | * @return true if hg indicated the command was successful, false otherwise. 187 | */ 188 | bool RunCommand( 189 | const FString& InCommand, FString& OutResults, TArray& OutErrorMessages 190 | ) const; 191 | 192 | /** 193 | * Invoke hg.exe with the given arguments and return the output. 194 | * @param InCommand An hg command, e.g. add 195 | * @param InOptions Zero or more options for the hg command. 196 | * @param InWorkingDirectory The working directory to set for hg.exe. 197 | * @param InFiles Zero or more filenames the hg command should operate on, all filenames should 198 | * be relative to InWorkingDirectory. 199 | * @param bForceFileList If true force all filenames in InFiles to be written to a temporary 200 | * file which is then passed in as a command argument instead of the 201 | * individual filenames in InFiles. 202 | * If false a temporary file will only be used when command line length 203 | * limits are exceeded. 204 | * @param OutResults Output from stdout of hg.exe. 205 | * @param OutErrorMessages Output from stderr of hg.exe. 206 | * @return true if hg indicated the command was successful, false otherwise. 207 | */ 208 | bool RunCommand( 209 | const FString& InCommand, const TArray& InOptions, 210 | const FString& InWorkingDirectory, const TArray& InFiles, bool bForceFileList, 211 | FString& OutResults, TArray& OutErrorMessages 212 | ) const; 213 | 214 | /** 215 | * Invoke hg.exe with the given arguments and return the output. 216 | * @param InCommand An hg command, e.g. add 217 | * @param InOptions Zero or more options for the hg command. 218 | * @param InWorkingDirectory The working directory to set for hg.exe. 219 | * @param InFilename The filename the hg command should operate on, the filename should 220 | * be relative to InWorkingDirectory. 221 | * @param OutResults Output from stdout of hg.exe. 222 | * @param OutErrorMessages Output from stderr of hg.exe. 223 | * @return true if hg indicated the command was successful, false otherwise. 224 | */ 225 | bool RunCommand( 226 | const FString& InCommand, const TArray& InOptions, 227 | const FString& InWorkingDirectory, const FString& InFilename, 228 | FString& OutResults, TArray& OutErrorMessages 229 | ) const; 230 | 231 | private: 232 | FString MercurialExecutablePath; 233 | 234 | private: 235 | static FClientSharedPtr Singleton; 236 | }; 237 | 238 | } // namespace MercurialSourceControl 239 | -------------------------------------------------------------------------------- /Source/MercurialSourceControl/Private/MercurialSourceControlCommand.cpp: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------- 2 | // The MIT License (MIT) 3 | // 4 | // Copyright (c) 2014 Vadim Macagon 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in all 14 | // copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | // SOFTWARE. 23 | //------------------------------------------------------------------------------- 24 | 25 | #include "MercurialSourceControlPrivatePCH.h" 26 | #include "MercurialSourceControlCommand.h" 27 | 28 | namespace MercurialSourceControl { 29 | 30 | FCommand::FCommand( 31 | const FString& InWorkingDirectory, 32 | const FString& InContentDirectory, 33 | const FSourceControlOperationRef& InOperation, 34 | const FWorkerRef& InWorker, 35 | const FSourceControlOperationComplete& InCompleteDelegate 36 | ) : Operation(InOperation) 37 | , Worker(InWorker) 38 | , WorkingDirectory(InWorkingDirectory) 39 | , ContentDirectory(InContentDirectory) 40 | , OperationCompleteDelegate(InCompleteDelegate) 41 | , bExecuteProcessed(0) 42 | , bCommandSuccessful(false) 43 | , Concurrency(EConcurrency::Synchronous) 44 | { 45 | check(IsInGameThread()); 46 | } 47 | 48 | bool FCommand::DoWork() 49 | { 50 | bCommandSuccessful = Worker->Execute(*this); 51 | FPlatformAtomics::InterlockedExchange(&bExecuteProcessed, 1); 52 | return bCommandSuccessful; 53 | } 54 | 55 | void FCommand::DoThreadedWork() 56 | { 57 | Concurrency = EConcurrency::Asynchronous; 58 | DoWork(); 59 | } 60 | 61 | void FCommand::Abandon() 62 | { 63 | FPlatformAtomics::InterlockedExchange(&bExecuteProcessed, 1); 64 | } 65 | 66 | } // namespace MercurialSourceControl 67 | -------------------------------------------------------------------------------- /Source/MercurialSourceControl/Private/MercurialSourceControlCommand.h: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------- 2 | // The MIT License (MIT) 3 | // 4 | // Copyright (c) 2014 Vadim Macagon 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in all 14 | // copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | // SOFTWARE. 23 | //------------------------------------------------------------------------------- 24 | #pragma once 25 | 26 | #include "IMercurialSourceControlWorker.h" 27 | #include "ISourceControlProvider.h" 28 | 29 | namespace MercurialSourceControl { 30 | 31 | typedef TSharedRef FSourceControlOperationRef; 32 | 33 | /** 34 | * Executes a Mercurial command, the execution may be done on a worker thread. 35 | * The hard work is delegated to an IMercurialSourceControlWorker object. 36 | */ 37 | class FCommand : public IQueuedWork 38 | { 39 | public: 40 | FCommand( 41 | const FString& InWorkingDirectory, 42 | const FString& InContentDirectory, 43 | const FSourceControlOperationRef& InOperation, 44 | const FWorkerRef& InWorker, 45 | const FSourceControlOperationComplete& InCompleteDelegate = FSourceControlOperationComplete() 46 | ); 47 | 48 | /** Execute the command. */ 49 | bool DoWork(); 50 | 51 | /** Return true iff the command has finished executing. */ 52 | bool HasExecuted() const 53 | { 54 | return bExecuteProcessed != 0; 55 | } 56 | 57 | /** Update the state of any affected items after the command has executed. */ 58 | bool UpdateStates() 59 | { 60 | check(bExecuteProcessed); 61 | 62 | return Worker->UpdateStates(); 63 | } 64 | 65 | /** Get the result (succeeded/failed) of the command execution. */ 66 | ECommandResult::Type GetResult() const 67 | { 68 | check(bExecuteProcessed); 69 | 70 | return bCommandSuccessful ? ECommandResult::Succeeded : ECommandResult::Failed; 71 | } 72 | 73 | /** Notify that the command has finished executing. */ 74 | void NotifyOperationComplete() 75 | { 76 | OperationCompleteDelegate.ExecuteIfBound(Operation, GetResult()); 77 | } 78 | 79 | /** Get the absolute path to the working directory of the command. */ 80 | const FString& GetWorkingDirectory() const 81 | { 82 | return WorkingDirectory; 83 | } 84 | 85 | /** Get the absolute path to the current content directory. */ 86 | const FString& GetContentDirectory() const 87 | { 88 | return ContentDirectory; 89 | } 90 | 91 | FSourceControlOperationRef GetOperation() const 92 | { 93 | return Operation; 94 | } 95 | 96 | void SetAbsoluteFiles(const TArray& InAbsoluteFiles) 97 | { 98 | Files = InAbsoluteFiles; 99 | } 100 | 101 | /** Get the absolute paths to the files the source control operation should be performed on. */ 102 | const TArray& GetAbsoluteFiles() const 103 | { 104 | return Files; 105 | } 106 | 107 | void SetAbsoluteLargeFiles(const TArray& InAbsoluteLargeFiles) 108 | { 109 | LargeFiles = InAbsoluteLargeFiles; 110 | } 111 | 112 | const TArray& GetAbsoluteLargeFiles() const 113 | { 114 | return LargeFiles; 115 | } 116 | 117 | public: 118 | // FQueuedWork methods 119 | virtual void DoThreadedWork() override; 120 | virtual void Abandon() override; 121 | 122 | public: 123 | /** Descriptions of errors (if any) encountered while executing the command. */ 124 | TArray ErrorMessages; 125 | 126 | private: 127 | /** The source control operation to perform when the command is executed. */ 128 | FSourceControlOperationRef Operation; 129 | 130 | /** The absolute paths to the files (if any) to perform the operation on. */ 131 | TArray Files; 132 | 133 | /** The absolute paths to the large files (if any) to perform an 'add' operation on. */ 134 | TArray LargeFiles; 135 | 136 | /** The worker that will actually perform the operation. */ 137 | FWorkerRef Worker; 138 | 139 | /** Absolute path to the working directory for the command. */ 140 | FString WorkingDirectory; 141 | 142 | /** Absolute path to the current content directory. */ 143 | FString ContentDirectory; 144 | 145 | /** Will be set to true if the operation is performed successfully. */ 146 | bool bCommandSuccessful; 147 | 148 | /** Executed after the operation completes. */ 149 | FSourceControlOperationComplete OperationCompleteDelegate; 150 | 151 | /** Has the operation been completed? */ 152 | volatile int32 bExecuteProcessed; 153 | 154 | /** Is this operation being performed synchronously or asynchronously? */ 155 | EConcurrency::Type Concurrency; 156 | }; 157 | 158 | } // namespace MercurialSourceControl 159 | -------------------------------------------------------------------------------- /Source/MercurialSourceControl/Private/MercurialSourceControlFileRevision.cpp: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------- 2 | // The MIT License (MIT) 3 | // 4 | // Copyright (c) 2014 Vadim Macagon 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in all 14 | // copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | // SOFTWARE. 23 | //------------------------------------------------------------------------------- 24 | 25 | #include "MercurialSourceControlPrivatePCH.h" 26 | #include "MercurialSourceControlModule.h" 27 | #include "MercurialSourceControlFileRevision.h" 28 | #include "MercurialSourceControlProvider.h" 29 | #include "MercurialSourceControlClient.h" 30 | 31 | namespace MercurialSourceControl { 32 | 33 | bool FFileRevision::Get(FString& InOutFilename) const 34 | { 35 | const FClientSharedPtr Client = FClient::Get(); 36 | if (!Client.IsValid()) 37 | { 38 | return false; 39 | } 40 | 41 | // if a filename for the temp file wasn't supplied generate a unique-ish one 42 | if (InOutFilename.Len() == 0) 43 | { 44 | InOutFilename = FString::Printf( 45 | TEXT("Temp-Rev-%d-%d-%s"), 46 | RevisionNumber, 47 | FDateTime::UtcNow().ToUnixTimestamp(), 48 | *FPaths::GetCleanFilename(AbsoluteFilename) 49 | ); 50 | // the extracted file should go into the designated diffing directory 51 | IFileManager::Get().MakeDirectory(*FPaths::DiffDir(), true); 52 | InOutFilename = FPaths::ConvertRelativePathToFull(FPaths::DiffDir() / InOutFilename); 53 | } 54 | 55 | FProvider& Provider = FModule::GetProvider(); 56 | TArray Errors; 57 | bool bSucceeded = Client->ExtractFileFromRevision( 58 | Provider.GetWorkingDirectory(), RevisionNumber, AbsoluteFilename, InOutFilename, Errors 59 | ); 60 | Provider.LogErrors(Errors); 61 | return bSucceeded; 62 | } 63 | 64 | bool FFileRevision::GetAnnotated(TArray& OutLines) const 65 | { 66 | // TODO 67 | return false; 68 | } 69 | 70 | bool FFileRevision::GetAnnotated(FString& InOutFilename) const 71 | { 72 | // TODO 73 | return false; 74 | } 75 | 76 | const FString& FFileRevision::GetFilename() const 77 | { 78 | return AbsoluteFilename; 79 | } 80 | 81 | int32 FFileRevision::GetRevisionNumber() const 82 | { 83 | return RevisionNumber; 84 | } 85 | 86 | const FString& FFileRevision::GetRevision() const 87 | { 88 | return CommitId; 89 | } 90 | 91 | const FString& FFileRevision::GetDescription() const 92 | { 93 | return Description; 94 | } 95 | 96 | const FString& FFileRevision::GetUserName() const 97 | { 98 | return UserName; 99 | } 100 | 101 | const FString& FFileRevision::GetClientSpec() const 102 | { 103 | static const FString EmptyString(TEXT("")); 104 | return EmptyString; 105 | } 106 | 107 | const FString& FFileRevision::GetAction() const 108 | { 109 | return Action; 110 | } 111 | 112 | FSourceControlRevisionPtr FFileRevision::GetBranchSource() const 113 | { 114 | // TODO: if this revision was copied from some other revision, then that source revision should 115 | // be returned here (this should be determined when history is being fetched) 116 | return nullptr; 117 | } 118 | 119 | const FDateTime& FFileRevision::GetDate() const 120 | { 121 | return Date; 122 | } 123 | 124 | int32 FFileRevision::GetCheckInIdentifier() const 125 | { 126 | return RevisionNumber; 127 | } 128 | 129 | int32 FFileRevision::GetFileSize() const 130 | { 131 | // Mercurial doesn't appear to provide easy access to file sizes. 132 | return 0; 133 | } 134 | 135 | } // namespace MercurialSourceControl 136 | -------------------------------------------------------------------------------- /Source/MercurialSourceControl/Private/MercurialSourceControlFileRevision.h: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------- 2 | // The MIT License (MIT) 3 | // 4 | // Copyright (c) 2014 Vadim Macagon 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in all 14 | // copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | // SOFTWARE. 23 | //------------------------------------------------------------------------------- 24 | #pragma once 25 | 26 | #include "ISourceControlRevision.h" 27 | 28 | namespace MercurialSourceControl { 29 | 30 | typedef TSharedPtr FSourceControlRevisionPtr; 31 | 32 | /** 33 | * Provides information relating to a revision of a file in a Mercurial repository. 34 | */ 35 | class FFileRevision 36 | : public ISourceControlRevision 37 | , public TSharedFromThis 38 | { 39 | public: 40 | FFileRevision() : RevisionNumber(0) {} 41 | 42 | void SetFilename(const FString& InFilename) 43 | { 44 | AbsoluteFilename = InFilename; 45 | } 46 | 47 | void SetRevisionNumber(int32 InRevisionNumber) 48 | { 49 | RevisionNumber = InRevisionNumber; 50 | } 51 | 52 | void SetCommitId(const FString &commitId) 53 | { 54 | CommitId = commitId; 55 | } 56 | 57 | void SetUserName(const FString& InUserName) 58 | { 59 | UserName = InUserName; 60 | } 61 | 62 | void SetDate(const FDateTime& InDate) 63 | { 64 | Date = InDate; 65 | } 66 | 67 | void SetDescription(const FString& InDescription) 68 | { 69 | Description = InDescription; 70 | } 71 | 72 | void SetAction(const FString& InAction) 73 | { 74 | Action = InAction; 75 | } 76 | 77 | public: 78 | // ISourceControlRevision methods 79 | 80 | /** 81 | * Copy this file revision into a temporary file. 82 | * @param InOutFilename The filename that this revision should be written to. If this is empty 83 | * a temporary filename will be generated and returned in this string. 84 | * @return true on success, false otherwise. 85 | */ 86 | virtual bool Get(FString& InOutFilename) const override; 87 | virtual bool GetAnnotated(TArray& OutLines) const override; 88 | virtual bool GetAnnotated(FString& InOutFilename) const override; 89 | virtual const FString& GetFilename() const override; 90 | virtual int32 GetRevisionNumber() const override; 91 | virtual const FString& GetRevision() const override; 92 | virtual const FString& GetDescription() const override; 93 | virtual const FString& GetUserName() const override; 94 | virtual const FString& GetClientSpec() const override; 95 | virtual const FString& GetAction() const override; 96 | virtual FSourceControlRevisionPtr GetBranchSource() const override; 97 | virtual const FDateTime& GetDate() const override; 98 | virtual int32 GetCheckInIdentifier() const override; 99 | virtual int32 GetFileSize() const override; 100 | 101 | private: 102 | FString AbsoluteFilename; 103 | int32 RevisionNumber; 104 | FString CommitId; 105 | FString Description; 106 | FString UserName; 107 | FString Action; 108 | FDateTime Date; 109 | }; 110 | 111 | typedef TSharedRef FFileRevisionRef; 112 | 113 | } // namespace MercurialSourceControl 114 | -------------------------------------------------------------------------------- /Source/MercurialSourceControl/Private/MercurialSourceControlFileState.cpp: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------- 2 | // The MIT License (MIT) 3 | // 4 | // Copyright (c) 2014 Vadim Macagon 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in all 14 | // copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | // SOFTWARE. 23 | //------------------------------------------------------------------------------- 24 | 25 | #include "MercurialSourceControlPrivatePCH.h" 26 | #include "MercurialSourceControlFileState.h" 27 | #include "MercurialSourceControlStyle.h" 28 | 29 | namespace MercurialSourceControl { 30 | 31 | #define LOCTEXT_NAMESPACE "MercurialSourceControl.State" 32 | 33 | int32 FFileState::GetHistorySize() const 34 | { 35 | return History.Num(); 36 | } 37 | 38 | FSourceControlRevisionPtr FFileState::GetHistoryItem(int32 HistoryIndex) const 39 | { 40 | check(History.IsValidIndex(HistoryIndex)); 41 | return History[HistoryIndex]; 42 | } 43 | 44 | FSourceControlRevisionPtr FFileState::FindHistoryRevision(int32 RevisionNumber) const 45 | { 46 | for (int32 i = 0; i < History.Num(); ++i) 47 | { 48 | if (History[i]->GetRevisionNumber() == RevisionNumber) 49 | { 50 | return History[i]; 51 | } 52 | } 53 | return NULL; 54 | } 55 | 56 | FSourceControlRevisionPtr FFileState::FindHistoryRevision(const FString& InRevision) const 57 | { 58 | for (int32 i = 0; i < History.Num(); ++i) 59 | { 60 | if (History[i]->GetRevision() == InRevision) 61 | { 62 | return History[i]; 63 | } 64 | } 65 | return NULL; 66 | } 67 | 68 | FSourceControlRevisionPtr FFileState::GetBaseRevForMerge() const 69 | { 70 | // TODO: return the revision of the common ancestor when there is a conflict 71 | return nullptr; 72 | } 73 | 74 | FName FFileState::GetIconName() const 75 | { 76 | if (!IsCurrent()) 77 | { 78 | return FName("Subversion.NotAtHeadRevision"); 79 | } 80 | 81 | // TODO: Moar icons?! 82 | switch (FileStatus) 83 | { 84 | case EFileStatus::Clean: 85 | return FMercurialStyle::CleanStatusIcon32; 86 | case EFileStatus::Added: 87 | return FMercurialStyle::AddedStatusIcon32; 88 | case EFileStatus::NotTracked: 89 | return FMercurialStyle::NotTrackedStatusIcon32; 90 | case EFileStatus::Modified: 91 | return FMercurialStyle::ModifiedStatusIcon32; 92 | case EFileStatus::Removed: 93 | return FMercurialStyle::RemovedStatusIcon32; 94 | case EFileStatus::Missing: 95 | return FMercurialStyle::MissingStatusIcon32; 96 | default: 97 | return NAME_None; 98 | } 99 | } 100 | 101 | FName FFileState::GetSmallIconName() const 102 | { 103 | if (!IsCurrent()) 104 | { 105 | return FName("Subversion.NotAtHeadRevision_Small"); 106 | } 107 | 108 | // TODO: Moar icons?! 109 | switch (FileStatus) 110 | { 111 | case EFileStatus::Clean: 112 | return FMercurialStyle::CleanStatusIcon16; 113 | case EFileStatus::Added: 114 | return FMercurialStyle::AddedStatusIcon16; 115 | case EFileStatus::NotTracked: 116 | return FMercurialStyle::NotTrackedStatusIcon16; 117 | case EFileStatus::Modified: 118 | return FMercurialStyle::ModifiedStatusIcon16; 119 | case EFileStatus::Removed: 120 | return FMercurialStyle::RemovedStatusIcon16; 121 | case EFileStatus::Missing: 122 | return FMercurialStyle::MissingStatusIcon16; 123 | default: 124 | return NAME_None; 125 | } 126 | } 127 | 128 | FText FFileState::GetDisplayName() const 129 | { 130 | switch (FileStatus) 131 | { 132 | case EFileStatus::Unknown: 133 | return LOCTEXT("Unknown", "Uknown"); 134 | 135 | case EFileStatus::Clean: 136 | return LOCTEXT("Clean", "Clean"); 137 | 138 | case EFileStatus::Added: 139 | return LOCTEXT("Added", "Added"); 140 | 141 | case EFileStatus::Removed: 142 | return LOCTEXT("Removed", "Removed"); 143 | 144 | case EFileStatus::Modified: 145 | return LOCTEXT("Modified", "Modified"); 146 | 147 | case EFileStatus::NotTracked: 148 | return LOCTEXT("NotTracked", "Not Tracked"); 149 | 150 | case EFileStatus::Ignored: 151 | return LOCTEXT("Ignored", "Ignored"); 152 | 153 | case EFileStatus::Missing: 154 | return LOCTEXT("Missing", "Missing"); 155 | } 156 | return FText(); 157 | } 158 | 159 | FText FFileState::GetDisplayTooltip() const 160 | { 161 | switch (FileStatus) 162 | { 163 | case EFileStatus::Unknown: 164 | return LOCTEXT("Unknown_Tooltip", "Item status is unknown, or maybe hell froze over."); 165 | 166 | case EFileStatus::Clean: 167 | return LOCTEXT("Clean_Tooltip", "Item hasn't been modified."); 168 | 169 | case EFileStatus::Added: 170 | return LOCTEXT("Added_Tooltip", "Item has been added."); 171 | 172 | case EFileStatus::Removed: 173 | return LOCTEXT("Removed_Tooltip", "Item has been removed."); 174 | 175 | case EFileStatus::Modified: 176 | return LOCTEXT("Modified_Tooltip", "Item has been modified."); 177 | 178 | case EFileStatus::NotTracked: 179 | return LOCTEXT("NotTracked_Tooltip", "Item is not under source control."); 180 | 181 | case EFileStatus::Ignored: 182 | return LOCTEXT("Ignored_Tooltip", "Item is being ignored."); 183 | 184 | case EFileStatus::Missing: 185 | return LOCTEXT( 186 | "Missing_Tooltip", 187 | "Mercurial is unable to locate the item on disk, this may occur when an item is deleted or moved by a non-Mercurial command." 188 | ); 189 | } 190 | return FText(); 191 | } 192 | 193 | const FString& FFileState::GetFilename() const 194 | { 195 | return AbsoluteFilename; 196 | } 197 | 198 | const FDateTime& FFileState::GetTimeStamp() const 199 | { 200 | return TimeStamp; 201 | } 202 | 203 | bool FFileState::CanCheckIn() const 204 | { 205 | return !IsConflicted() 206 | && ((FileStatus == EFileStatus::Added) 207 | || (FileStatus == EFileStatus::Modified) 208 | || (FileStatus == EFileStatus::Removed)); 209 | } 210 | 211 | bool FFileState::CanCheckout() const 212 | { 213 | // the check-out operation is not supported by the Mercurial provider 214 | return false; 215 | } 216 | 217 | bool FFileState::IsCheckedOut() const 218 | { 219 | // since Mercurial has no concept of exclusive checkouts (unlike Perforce & SVN) 220 | // any file being tracked by Mercurial is always considered checked out so that the end user 221 | // doesn't have to perform a pointless check-out operation before they can edit a file 222 | return IsSourceControlled(); 223 | } 224 | 225 | bool FFileState::IsCheckedOutOther(FString* Who) const 226 | { 227 | // Mercurial doesn't keep track of who checked what out 228 | return false; 229 | } 230 | 231 | bool FFileState::IsCurrent() const 232 | { 233 | // TODO 234 | return true; 235 | } 236 | 237 | bool FFileState::IsSourceControlled() const 238 | { 239 | return (FileStatus != EFileStatus::NotTracked) && (FileStatus != EFileStatus::Unknown); 240 | } 241 | 242 | bool FFileState::IsAdded() const 243 | { 244 | return FileStatus == EFileStatus::Added; 245 | } 246 | 247 | bool FFileState::IsDeleted() const 248 | { 249 | return FileStatus == EFileStatus::Removed; 250 | } 251 | 252 | bool FFileState::IsIgnored() const 253 | { 254 | return FileStatus == EFileStatus::Ignored; 255 | } 256 | 257 | bool FFileState::CanEdit() const 258 | { 259 | return true; 260 | } 261 | 262 | bool FFileState::IsUnknown() const 263 | { 264 | return FileStatus == EFileStatus::Unknown; 265 | } 266 | 267 | bool FFileState::IsModified() const 268 | { 269 | // In case you're wondering why we check for EFileStatus::Added in here, it's because 270 | // UnrealEd makes certain assumptions about source control providers, and those assumptions are 271 | // based on Perfoce. In this particular case we're working around the assumption that it's 272 | // a good idea to revert unchanged files before a commit (see 273 | // FSourceControlWindows::PromptForCheckin() for details), with that in mind... 274 | // 275 | // What is an unchanged file? Anything that IsCheckedOut() && !IsModified(). In the Mercurial 276 | // provider all the files are checked out all the time, so it all comes down to !IsModified(). 277 | // Here's what would happen if we didn't account for EFileStatus::Added in IsModified(): 278 | // 1. User creates a new file and marks it for add, its status is now EFileStatus::Added. 279 | // 2. User tries to commit the added file (Check In in UnrealEd). 280 | // 3. UnrealEd reverts the file because IsModified() == false, so its status is now 281 | // EFileStatus::NotTracked. 282 | // 4. UnrealEd tries to commit the file it just reverted, but that fails since Mercurial is 283 | // no longer tracking it. 284 | 285 | return (FileStatus == EFileStatus::Modified) || (FileStatus == EFileStatus::Added); 286 | } 287 | 288 | bool FFileState::CanAdd() const 289 | { 290 | return FileStatus == EFileStatus::NotTracked; 291 | } 292 | 293 | bool FFileState::IsConflicted() const 294 | { 295 | // TODO: Figure out if the file is actually in conflict or not when retrieving the file status 296 | return false; 297 | } 298 | 299 | bool FFileState::CanDelete() const 300 | { 301 | // TODO: Stub for 4.14 build 302 | return (FileStatus == EFileStatus::Clean) || (FileStatus == EFileStatus::Added) || (FileStatus == EFileStatus::Removed); 303 | } 304 | 305 | bool FFileState::CanRevert() const 306 | { 307 | // TODO: This should work fine, but needs to be checked. Fix for UE 4.19 308 | return (FileStatus == EFileStatus::Modified) || (FileStatus == EFileStatus::Missing); 309 | } 310 | 311 | bool FFileState::IsCheckedOutInOtherBranch(const FString& CurrentBranch) const 312 | { 313 | // TODO: Stub for 4.20 build 314 | return false; 315 | } 316 | 317 | bool FFileState::IsModifiedInOtherBranch(const FString& CurrentBranch) const 318 | { 319 | // TODO: Stub for 4.20 build 320 | return false; 321 | } 322 | 323 | bool FFileState::IsCheckedOutOrModifiedInOtherBranch(const FString& CurrentBranch) const 324 | { 325 | // TODO: Stub for 4.20 build 326 | return false; 327 | } 328 | 329 | TArray FFileState::GetCheckedOutBranches() const 330 | { 331 | // TODO: Stub for 4.20 build 332 | return TArray(); 333 | } 334 | 335 | FString FFileState::GetOtherUserBranchCheckedOuts() const 336 | { 337 | // TODO: Stub for 4.20 build 338 | return FString(); 339 | } 340 | 341 | bool FFileState::GetOtherBranchHeadModification(FString& HeadBranchOut, FString& ActionOut, int32& HeadChangeListOut) const 342 | { 343 | // TODO: Stub for 4.20 build 344 | return false; 345 | } 346 | 347 | #undef LOCTEXT_NAMESPACE 348 | 349 | } // namespace MercurialSourceControl 350 | -------------------------------------------------------------------------------- /Source/MercurialSourceControl/Private/MercurialSourceControlFileState.h: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------- 2 | // The MIT License (MIT) 3 | // 4 | // Copyright (c) 2014 Vadim Macagon 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in all 14 | // copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | // SOFTWARE. 23 | //------------------------------------------------------------------------------- 24 | #pragma once 25 | 26 | #include "ISourceControlState.h" 27 | #include "MercurialSourceControlFileRevision.h" 28 | 29 | namespace MercurialSourceControl { 30 | 31 | enum class EFileStatus 32 | { 33 | Unknown, 34 | Clean, 35 | Added, 36 | Removed, 37 | Modified, 38 | NotTracked, 39 | Ignored, 40 | Missing, 41 | }; 42 | 43 | /** 44 | * Provides information relating to the current status of a file in a Mercurial repository, 45 | * and the revision history of that file. 46 | */ 47 | class FFileState 48 | : public ISourceControlState 49 | , public TSharedFromThis 50 | { 51 | public: 52 | FFileState(const FString& InFilename) 53 | : AbsoluteFilename(InFilename) 54 | , FileStatus(EFileStatus::Unknown) 55 | , TimeStamp(0) 56 | { 57 | } 58 | 59 | void SetFileStatus(EFileStatus InFileStatus) 60 | { 61 | FileStatus = InFileStatus; 62 | } 63 | 64 | EFileStatus GetFileStatus() const 65 | { 66 | return FileStatus; 67 | } 68 | 69 | void SetTimeStamp(const FDateTime& InTimeStamp) 70 | { 71 | TimeStamp = InTimeStamp; 72 | } 73 | 74 | void SetHistory(const TArray& InFileRevisions) 75 | { 76 | History = InFileRevisions; 77 | } 78 | 79 | public: 80 | // ISourceControlState methods 81 | 82 | virtual int32 GetHistorySize() const; 83 | virtual FSourceControlRevisionPtr GetHistoryItem(int32 HistoryIndex) const override; 84 | virtual FSourceControlRevisionPtr FindHistoryRevision(int32 RevisionNumber) const override; 85 | virtual FSourceControlRevisionPtr FindHistoryRevision(const FString& InRevision) const override; 86 | virtual FSourceControlRevisionPtr GetBaseRevForMerge() const override; 87 | virtual FName GetIconName() const override; 88 | virtual FName GetSmallIconName() const override; 89 | virtual FText GetDisplayName() const override; 90 | virtual FText GetDisplayTooltip() const override; 91 | virtual const FString& GetFilename() const override; 92 | virtual const FDateTime& GetTimeStamp() const override; 93 | virtual bool CanCheckIn() const override; 94 | virtual bool CanCheckout() const override; 95 | virtual bool IsCheckedOut() const override; 96 | virtual bool IsCheckedOutOther(FString* Who = nullptr) const override; 97 | virtual bool IsCurrent() const override; 98 | virtual bool IsSourceControlled() const override; 99 | virtual bool IsAdded() const override; 100 | virtual bool IsDeleted() const override; 101 | virtual bool IsIgnored() const override; 102 | virtual bool CanEdit() const override; 103 | virtual bool IsUnknown() const override; 104 | virtual bool IsModified() const override; 105 | virtual bool CanAdd() const override; 106 | virtual bool IsConflicted() const override; 107 | virtual bool CanDelete() const override; 108 | virtual bool CanRevert() const override; 109 | virtual bool IsCheckedOutInOtherBranch(const FString& CurrentBranch = FString()) const override; 110 | virtual bool IsModifiedInOtherBranch(const FString& CurrentBranch = FString()) const override; 111 | virtual bool IsCheckedOutOrModifiedInOtherBranch(const FString& CurrentBranch = FString()) const override; 112 | virtual TArray GetCheckedOutBranches() const override; 113 | virtual FString GetOtherUserBranchCheckedOuts() const override; 114 | virtual bool GetOtherBranchHeadModification(FString& HeadBranchOut, FString& ActionOut, int32& HeadChangeListOut) const override; 115 | 116 | private: 117 | /** All the revisions of the file */ 118 | TArray History; 119 | 120 | FString AbsoluteFilename; 121 | EFileStatus FileStatus; 122 | 123 | /** 124 | * Last time the state was updated. 125 | * @note This is not the last modified time of the file, just the last time 126 | * the FileStatus etc. member fields were updated. 127 | */ 128 | FDateTime TimeStamp; 129 | }; 130 | 131 | typedef TSharedRef FFileStateRef; 132 | 133 | } // MercurialSourceControl 134 | -------------------------------------------------------------------------------- /Source/MercurialSourceControl/Private/MercurialSourceControlModule.cpp: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------- 2 | // The MIT License (MIT) 3 | // 4 | // Copyright (c) 2014 Vadim Macagon 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in all 14 | // copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | // SOFTWARE. 23 | //------------------------------------------------------------------------------- 24 | 25 | #include "MercurialSourceControlPrivatePCH.h" 26 | #include "MercurialSourceControlModule.h" 27 | #include "Features/IModularFeatures.h" 28 | #include "MercurialSourceControlOperationNames.h" 29 | #include "MercurialSourceControlWorkers.h" 30 | #include "MercurialSourceControlStyle.h" 31 | 32 | namespace MercurialSourceControl { 33 | 34 | namespace 35 | { 36 | template 37 | FWorkerRef CreateWorker() 38 | { 39 | return MakeShareable(new T()); 40 | } 41 | } // unnamed namespace 42 | 43 | void FModule::StartupModule() 44 | { 45 | FMercurialStyle::Initialize(); 46 | 47 | Provider.RegisterWorkerCreator( 48 | OperationNames::Connect, 49 | FCreateWorkerDelegate::CreateStatic(&CreateWorker) 50 | ); 51 | Provider.RegisterWorkerCreator( 52 | OperationNames::UpdateStatus, 53 | FCreateWorkerDelegate::CreateStatic(&CreateWorker) 54 | ); 55 | Provider.RegisterWorkerCreator( 56 | OperationNames::Revert, 57 | FCreateWorkerDelegate::CreateStatic(&CreateWorker) 58 | ); 59 | Provider.RegisterWorkerCreator( 60 | OperationNames::Delete, 61 | FCreateWorkerDelegate::CreateStatic(&CreateWorker) 62 | ); 63 | Provider.RegisterWorkerCreator( 64 | OperationNames::MarkForAdd, 65 | FCreateWorkerDelegate::CreateStatic(&CreateWorker) 66 | ); 67 | Provider.RegisterWorkerCreator( 68 | OperationNames::CheckIn, 69 | FCreateWorkerDelegate::CreateStatic(&CreateWorker) 70 | ); 71 | 72 | IModularFeatures::Get().RegisterModularFeature(FeatureName, &Provider); 73 | } 74 | 75 | void FModule::ShutdownModule() 76 | { 77 | Provider.Close(); 78 | IModularFeatures::Get().UnregisterModularFeature(FeatureName, &Provider); 79 | FMercurialStyle::Shutdown(); 80 | } 81 | 82 | bool FModule::IsGameModule() const 83 | { 84 | // no gameplay code in this module 85 | return false; 86 | } 87 | 88 | FProvider& FModule::GetProvider() 89 | { 90 | FModule& MercurialModule = FModuleManager::LoadModuleChecked("MercurialSourceControl"); 91 | return MercurialModule.Provider; 92 | } 93 | 94 | } // namespace MercurialSourceControl 95 | 96 | IMPLEMENT_MODULE(MercurialSourceControl::FModule, MercurialSourceControl); -------------------------------------------------------------------------------- /Source/MercurialSourceControl/Private/MercurialSourceControlModule.h: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------- 2 | // The MIT License (MIT) 3 | // 4 | // Copyright (c) 2014 Vadim Macagon 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in all 14 | // copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | // SOFTWARE. 23 | //------------------------------------------------------------------------------- 24 | #pragma once 25 | 26 | #include "MercurialSourceControlProvider.h" 27 | 28 | namespace MercurialSourceControl { 29 | 30 | class FModule : public IModuleInterface 31 | { 32 | public: 33 | // IModuleInterface methods 34 | virtual void StartupModule() override; 35 | virtual void ShutdownModule() override; 36 | virtual bool IsGameModule() const; 37 | 38 | public: 39 | FModule() : FeatureName("SourceControl") {} 40 | 41 | static FProvider& GetProvider(); 42 | 43 | private: 44 | FProvider Provider; 45 | FName FeatureName; 46 | }; 47 | 48 | } // namespace MercurialSourceControl 49 | -------------------------------------------------------------------------------- /Source/MercurialSourceControl/Private/MercurialSourceControlOperationNames.cpp: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------- 2 | // The MIT License (MIT) 3 | // 4 | // Copyright (c) 2014 Vadim Macagon 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in all 14 | // copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | // SOFTWARE. 23 | //------------------------------------------------------------------------------- 24 | 25 | #include "MercurialSourceControlPrivatePCH.h" 26 | #include "MercurialSourceControlOperationNames.h" 27 | 28 | namespace MercurialSourceControl { 29 | 30 | const FName OperationNames::Connect("Connect"); 31 | const FName OperationNames::UpdateStatus("UpdateStatus"); 32 | const FName OperationNames::Revert("Revert"); 33 | const FName OperationNames::Delete("Delete"); 34 | const FName OperationNames::MarkForAdd("MarkForAdd"); 35 | const FName OperationNames::CheckIn("CheckIn"); 36 | 37 | } // namespace MercurialSourceControl -------------------------------------------------------------------------------- /Source/MercurialSourceControl/Private/MercurialSourceControlOperationNames.h: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------- 2 | // The MIT License (MIT) 3 | // 4 | // Copyright (c) 2014 Vadim Macagon 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in all 14 | // copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | // SOFTWARE. 23 | //------------------------------------------------------------------------------- 24 | #pragma once 25 | 26 | namespace MercurialSourceControl { 27 | 28 | /** Names of all the operations supported by the Mercurial source control provider. */ 29 | struct OperationNames 30 | { 31 | static const FName Connect; 32 | static const FName UpdateStatus; 33 | static const FName Revert; 34 | static const FName Delete; 35 | static const FName MarkForAdd; 36 | static const FName CheckIn; 37 | }; 38 | 39 | } // namespace MercurialSourceControl -------------------------------------------------------------------------------- /Source/MercurialSourceControl/Private/MercurialSourceControlPrivatePCH.h: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------- 2 | // The MIT License (MIT) 3 | // 4 | // Copyright (c) 2014 Vadim Macagon 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in all 14 | // copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | // SOFTWARE. 23 | //------------------------------------------------------------------------------- 24 | #pragma once 25 | 26 | #include "Core.h" 27 | #include "SlateBasics.h" 28 | #include "EditorStyle.h" 29 | #include "AssetToolsModule.h" 30 | #include "AssetRegistryModule.h" 31 | -------------------------------------------------------------------------------- /Source/MercurialSourceControl/Private/MercurialSourceControlProvider.cpp: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------- 2 | // The MIT License (MIT) 3 | // 4 | // Copyright (c) 2014 Vadim Macagon 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in all 14 | // copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | // SOFTWARE. 23 | //------------------------------------------------------------------------------- 24 | 25 | #include "MercurialSourceControlPrivatePCH.h" 26 | #include "MercurialSourceControlProvider.h" 27 | #include "SMercurialSourceControlSettingsWidget.h" 28 | #include "MercurialSourceControlCommand.h" 29 | #include "MercurialSourceControlFileState.h" 30 | #include "MercurialSourceControlClient.h" 31 | #include "MessageLog.h" 32 | #include "ScopedSourceControlProgress.h" 33 | #include "MercurialSourceControlOperationNames.h" 34 | #include "ISourceControlModule.h" 35 | #include "ARFilter.h" 36 | #include "SourceControlOperations.h" 37 | 38 | namespace MercurialSourceControl { 39 | 40 | FName FProvider::SourceControlLogName("SourceControl"); 41 | 42 | // for LOCTEXT() 43 | #define LOCTEXT_NAMESPACE "MercurialSourceControl" 44 | 45 | void FProvider::Init(bool bForceConnection) 46 | { 47 | Settings.Load(); 48 | AbsoluteContentDirectory = FPaths::ConvertRelativePathToFull(FPaths::ProjectContentDir()); 49 | } 50 | 51 | void FProvider::Close() 52 | { 53 | // clear out the file state cache 54 | FileStateMap.Empty(); 55 | // destroy the FClient singleton 56 | FClient::Destroy(); 57 | } 58 | 59 | const FName& FProvider::GetName() const 60 | { 61 | return ProviderName; 62 | } 63 | 64 | FText FProvider::GetStatusText() const 65 | { 66 | FFormatNamedArguments Args; 67 | Args.Add(TEXT("ProviderName"), LOCTEXT("Mercurial", "Mercurial")); 68 | Args.Add(TEXT("YesOrNo"), IsEnabled() ? LOCTEXT("Yes", "Yes") : LOCTEXT("No", "No")); 69 | Args.Add(TEXT("LocalPath"), FText::FromString(GetWorkingDirectory())); 70 | return FText::Format( 71 | LOCTEXT("Status", "Provider: {ProviderName}\nEnabled: {YesOrNo}\nRepository: {LocalPath}"), 72 | Args 73 | ); 74 | } 75 | 76 | bool FProvider::IsEnabled() const 77 | { 78 | return FClient::Get().IsValid(); 79 | } 80 | 81 | bool FProvider::IsAvailable() const 82 | { 83 | return IsEnabled(); 84 | } 85 | 86 | ECommandResult::Type FProvider::Login( 87 | const FString& InPassword, EConcurrency::Type InConcurrency, 88 | const FSourceControlOperationComplete& InOperationCompleteDelegate 89 | ) 90 | { 91 | // UnrealEd occasionally likes to "login" even though the "connection" has already been 92 | // established, just to be sure. There isn't much point in doing so with Mercurial so ignore 93 | // any pointless login requests. 94 | if (IsAvailable()) 95 | { 96 | TSharedRef ConnectOperation = 97 | ISourceControlOperation::Create(); 98 | ConnectOperation->SetPassword(InPassword); 99 | InOperationCompleteDelegate.ExecuteIfBound(ConnectOperation, ECommandResult::Succeeded); 100 | } 101 | else 102 | { 103 | // call the base class method 104 | return ISourceControlProvider::Login(InPassword, InConcurrency, InOperationCompleteDelegate); 105 | } 106 | return ECommandResult::Succeeded; 107 | } 108 | 109 | ECommandResult::Type FProvider::GetState( 110 | const TArray& InFiles, 111 | TArray< TSharedRef >& OutState, 112 | EStateCacheUsage::Type InStateCacheUsage 113 | ) 114 | { 115 | if (!IsEnabled()) 116 | { 117 | return ECommandResult::Failed; 118 | } 119 | 120 | TArray AbsoluteFiles; 121 | for (const auto& Filename : InFiles) 122 | { 123 | AbsoluteFiles.Add(FPaths::ConvertRelativePathToFull(Filename)); 124 | } 125 | 126 | // update the cache if requested to do so 127 | if (InStateCacheUsage == EStateCacheUsage::ForceUpdate) 128 | { 129 | // TODO: Should really check the return value, but the SVN/Perforce providers don't 130 | // this call will block until the operation is complete 131 | Execute(ISourceControlOperation::Create(), AbsoluteFiles); 132 | } 133 | 134 | // retrieve the states for the given files from the cache 135 | for (const auto& Filename : AbsoluteFiles) 136 | { 137 | OutState.Add(GetFileStateFromCache(Filename)); 138 | } 139 | 140 | return ECommandResult::Succeeded; 141 | } 142 | 143 | FDelegateHandle FProvider::RegisterSourceControlStateChanged_Handle( 144 | const FSourceControlStateChanged::FDelegate& SourceControlStateChanged 145 | ) 146 | { 147 | return OnSourceControlStateChanged.Add(SourceControlStateChanged); 148 | } 149 | 150 | void FProvider::UnregisterSourceControlStateChanged_Handle( 151 | FDelegateHandle Handle 152 | ) 153 | { 154 | OnSourceControlStateChanged.Remove(Handle); 155 | } 156 | 157 | ECommandResult::Type FProvider::Execute( 158 | const TSharedRef& InOperation, 159 | const TArray& InFiles, 160 | EConcurrency::Type InConcurrency, 161 | const FSourceControlOperationComplete& InOperationCompleteDelegate 162 | ) 163 | { 164 | // the "Connect" operation is the only operation that can be performed while the 165 | // provider is disabled, if the operation is successful the provider will be enabled 166 | if (!IsEnabled() && (InOperation->GetName() != OperationNames::Connect)) 167 | { 168 | return ECommandResult::Failed; 169 | } 170 | 171 | // attempt to create a worker to perform the requested operation 172 | FWorkerPtr WorkerPtr = CreateWorker(InOperation->GetName()); 173 | if (!WorkerPtr.IsValid()) 174 | { 175 | // apparently we don't support this particular operation 176 | FFormatNamedArguments Arguments; 177 | Arguments.Add(TEXT("OperationName"), FText::FromName(InOperation->GetName())); 178 | Arguments.Add(TEXT("ProviderName"), FText::FromName(GetName())); 179 | LogError( 180 | FText::Format( 181 | LOCTEXT( 182 | "UnsupportedOperation", 183 | "Operation '{OperationName}' not supported by source control provider '{ProviderName}'" 184 | ), 185 | Arguments 186 | ) 187 | ); 188 | return ECommandResult::Failed; 189 | } 190 | 191 | auto* Command = new FCommand( 192 | GetWorkingDirectory(), AbsoluteContentDirectory, InOperation, 193 | WorkerPtr.ToSharedRef(), InOperationCompleteDelegate 194 | ); 195 | 196 | TArray AbsoluteFiles; 197 | if (InOperation->GetName() == OperationNames::Connect) 198 | { 199 | AbsoluteFiles.Add(Settings.GetMercurialPath()); 200 | } 201 | else if (InOperation->GetName() == OperationNames::MarkForAdd) 202 | { 203 | TArray AbsoluteLargeFiles; 204 | PrepareFilenamesForAddCommand(InFiles, AbsoluteFiles, AbsoluteLargeFiles); 205 | 206 | if (AbsoluteLargeFiles.Num() > 0) 207 | { 208 | Command->SetAbsoluteLargeFiles(AbsoluteLargeFiles); 209 | } 210 | } 211 | else 212 | { 213 | for (const auto& Filename : InFiles) 214 | { 215 | AbsoluteFiles.Add(FPaths::ConvertRelativePathToFull(Filename)); 216 | } 217 | } 218 | 219 | if (AbsoluteFiles.Num() > 0) 220 | { 221 | Command->SetAbsoluteFiles(AbsoluteFiles); 222 | } 223 | 224 | if (InConcurrency == EConcurrency::Synchronous) 225 | { 226 | auto Result = ExecuteSynchronousCommand(Command, InOperation->GetInProgressString()); 227 | delete Command; 228 | return Result; 229 | } 230 | else 231 | { 232 | return ExecuteCommand(Command, true); 233 | } 234 | } 235 | 236 | bool FProvider::CanCancelOperation( 237 | const TSharedRef& InOperation 238 | ) const 239 | { 240 | // don't support cancellation 241 | return false; 242 | } 243 | 244 | void FProvider::CancelOperation( 245 | const TSharedRef& InOperation 246 | ) 247 | { 248 | // nothing to do here 249 | } 250 | 251 | TArray< TSharedRef > FProvider::GetLabels( 252 | const FString& InMatchingSpec 253 | ) const 254 | { 255 | TArray< TSharedRef > Labels; 256 | // TODO: figure out if this needs to be implemented 257 | return Labels; 258 | } 259 | 260 | bool FProvider::UsesLocalReadOnlyState() const 261 | { 262 | return false; 263 | } 264 | 265 | bool FProvider::UsesChangelists() const 266 | { 267 | return false; 268 | } 269 | 270 | void FProvider::Tick() 271 | { 272 | bool bNotifyStateChanged = false; 273 | 274 | // remove commands that have finished executing from the queue 275 | for (int32 i = 0; i < CommandQueue.Num(); ++i) 276 | { 277 | FCommandQueueEntry CommandQueueEntry = CommandQueue[i]; 278 | auto* Command = CommandQueueEntry.Command; 279 | if (Command->HasExecuted()) 280 | { 281 | CommandQueue.RemoveAt(i); 282 | bNotifyStateChanged = Command->UpdateStates(); 283 | LogErrors(Command->ErrorMessages); 284 | Command->NotifyOperationComplete(); 285 | 286 | if (CommandQueueEntry.bAutoDelete) 287 | { 288 | delete Command; 289 | } 290 | 291 | // NotifyOperationComplete() may indirectly alter the command queue 292 | // so the loop must stop here, the queue cleanup will resume on the 293 | // next tick. 294 | break; 295 | } 296 | } 297 | 298 | if (bNotifyStateChanged) 299 | { 300 | OnSourceControlStateChanged.Broadcast(); 301 | } 302 | } 303 | 304 | bool FProvider::UsesCheckout() const 305 | { 306 | // TODO 307 | return false; 308 | } 309 | 310 | bool FProvider::QueryStateBranchConfig(const FString& ConfigSrc, const FString& ConfigDest) 311 | { 312 | // TODO: Stub for 4.20 build 313 | return false; 314 | } 315 | 316 | void FProvider::RegisterStateBranches(const TArray& BranchNames, const FString& ContentRoot) 317 | { 318 | // TODO: Stub for 4.20 build 319 | } 320 | 321 | int32 FProvider::GetStateBranchIndex(const FString& BranchName) const 322 | { 323 | // TODO: Stub for 4.20 build 324 | return 0; 325 | } 326 | 327 | #if SOURCE_CONTROL_WITH_SLATE 328 | TSharedRef FProvider::MakeSettingsWidget() const 329 | { 330 | return SNew(SProviderSettingsWidget); 331 | } 332 | #endif // SOURCE_CONTROL_WITH_SLATE 333 | 334 | void FProvider::RegisterWorkerCreator( 335 | const FName& InOperationName, const FCreateWorkerDelegate& InDelegate 336 | ) 337 | { 338 | WorkerCreatorsMap.Add(InOperationName, InDelegate); 339 | } 340 | 341 | bool FProvider::UpdateFileStateCache(const TArray& InStates) 342 | { 343 | for (auto StateIt(InStates.CreateConstIterator()); StateIt; StateIt++) 344 | { 345 | FFileStateRef FileState = GetFileStateFromCache(StateIt->GetFilename()); 346 | FileState->SetFileStatus(StateIt->GetFileStatus()); 347 | FileState->SetTimeStamp(StateIt->GetTimeStamp()); 348 | } 349 | return InStates.Num() > 0; 350 | } 351 | 352 | bool FProvider::UpdateFileStateCache( 353 | const TMap >& InFileRevisionsMap 354 | ) 355 | { 356 | for (auto It(InFileRevisionsMap.CreateConstIterator()); It; ++It) 357 | { 358 | FFileStateRef FileState = GetFileStateFromCache(It.Key()); 359 | FileState->SetHistory(It.Value()); 360 | FileState->SetTimeStamp(FDateTime::Now()); 361 | } 362 | return InFileRevisionsMap.Num() > 0; 363 | } 364 | 365 | void FProvider::LogError(const FText& InErrorMessage) 366 | { 367 | FMessageLog(SourceControlLogName).Error(InErrorMessage); 368 | } 369 | 370 | void FProvider::LogErrors(const TArray& ErrorMessages) 371 | { 372 | FMessageLog SourceControlLog(SourceControlLogName); 373 | for (auto It(ErrorMessages.CreateConstIterator()); It; ++It) 374 | { 375 | SourceControlLog.Error(FText::FromString(*It)); 376 | } 377 | } 378 | 379 | FFileStateRef FProvider::GetFileStateFromCache(const FString& Filename) 380 | { 381 | FFileStateRef* StatePtr = FileStateMap.Find(Filename); 382 | if (StatePtr) 383 | { 384 | return *StatePtr; 385 | } 386 | else 387 | { 388 | FFileStateRef DefaultFileState = MakeShareable(new FFileState(Filename)); 389 | FileStateMap.Add(Filename, DefaultFileState); 390 | return DefaultFileState; 391 | } 392 | } 393 | 394 | ECommandResult::Type FProvider::ExecuteSynchronousCommand( 395 | FCommand* Command, const FText& ProgressText 396 | ) 397 | { 398 | // display a progress dialog if progress text was provided 399 | FScopedSourceControlProgress Progress(ProgressText); 400 | 401 | // attempt to execute the command asynchronously 402 | ExecuteCommand(Command, false); 403 | 404 | // wait for the command to finish executing 405 | while (!Command->HasExecuted()) 406 | { 407 | Tick(); 408 | Progress.Tick(); 409 | FPlatformProcess::Sleep(0.01f); 410 | } 411 | 412 | // make sure the command queue is cleaned up 413 | Tick(); 414 | 415 | return Command->GetResult(); 416 | } 417 | 418 | ECommandResult::Type FProvider::ExecuteCommand(FCommand* Command, bool bAutoDelete) 419 | { 420 | if (GThreadPool) 421 | { 422 | GThreadPool->AddQueuedWork(Command); 423 | CommandQueue.Add({Command, bAutoDelete}); 424 | return ECommandResult::Succeeded; 425 | } 426 | else // fall back to synchronous execution 427 | { 428 | Command->DoWork(); 429 | Command->UpdateStates(); 430 | LogErrors(Command->ErrorMessages); 431 | Command->NotifyOperationComplete(); 432 | 433 | auto Result = Command->GetResult(); 434 | if (bAutoDelete) 435 | { 436 | delete Command; 437 | } 438 | return Result; 439 | } 440 | } 441 | 442 | FWorkerPtr FProvider::CreateWorker(const FName& InOperationName) const 443 | { 444 | const auto* CreateWorkerPtr = WorkerCreatorsMap.Find(InOperationName); 445 | if (CreateWorkerPtr) 446 | { 447 | return CreateWorkerPtr->Execute(); 448 | } 449 | return nullptr; 450 | } 451 | 452 | void FProvider::PrepareFilenamesForAddCommand( 453 | const TArray& InFiles, 454 | TArray& OutAbsoluteFiles, TArray& OutAbsoluteLargeFiles 455 | ) 456 | { 457 | if (Settings.IsLargefilesIntegrationEnabled()) 458 | { 459 | FARFilter LargeAssetFilter; 460 | LargeAssetFilter.bRecursiveClasses = true; 461 | 462 | // convert filenames to long package names that can be used in the asset filter 463 | for (const auto& Filename : InFiles) 464 | { 465 | // currently only .uasset files can be auto-flagged as large 466 | if (!Filename.EndsWith(FPackageName::GetAssetPackageExtension())) 467 | { 468 | continue; 469 | } 470 | 471 | FString PackageName; 472 | if (!FPackageName::TryConvertFilenameToLongPackageName(Filename, PackageName)) 473 | { 474 | UE_LOG( 475 | LogSourceControl, Error, 476 | TEXT("Failed to convert filename '%s' to package name"), *Filename 477 | ); 478 | continue; 479 | } 480 | 481 | LargeAssetFilter.PackageNames.Add(*PackageName); 482 | } 483 | 484 | // add the asset types that the user has designated as "large" to the asset filter 485 | TArray LargeAssetTypes; 486 | Settings.GetLargeAssetTypes(LargeAssetTypes); 487 | for (const auto& ClassName : LargeAssetTypes) 488 | { 489 | LargeAssetFilter.ClassNames.Add(*ClassName); 490 | } 491 | 492 | FAssetRegistryModule& AssetRegistryModule = 493 | FModuleManager::LoadModuleChecked(TEXT("AssetRegistry")); 494 | 495 | // find all the assets matching the asset filter 496 | TArray LargeAssets; 497 | AssetRegistryModule.Get().GetAssets(LargeAssetFilter, LargeAssets); 498 | 499 | // convert the long package names of all matching assets back to filenames 500 | for (const auto& Asset : LargeAssets) 501 | { 502 | FString RelativePath = FPackageName::LongPackageNameToFilename( 503 | Asset.PackageName.ToString(), FPackageName::GetAssetPackageExtension() 504 | ); 505 | OutAbsoluteLargeFiles.Add( 506 | FPaths::ConvertRelativePathToFull(RelativePath) 507 | ); 508 | } 509 | 510 | // any input file that didn't match the asset filter will be added with no special flags 511 | for (const auto& Filename : InFiles) 512 | { 513 | FString FullPath(FPaths::ConvertRelativePathToFull(Filename)); 514 | if (!OutAbsoluteLargeFiles.Contains(FullPath)) 515 | { 516 | OutAbsoluteFiles.Add(FullPath); 517 | } 518 | } 519 | } 520 | else 521 | { 522 | for (const auto& Filename : InFiles) 523 | { 524 | OutAbsoluteFiles.Add(FPaths::ConvertRelativePathToFull(Filename)); 525 | } 526 | } 527 | } 528 | 529 | TArray FProvider::GetCachedStateByPredicate( 530 | TFunctionRef Predicate 531 | ) const 532 | { 533 | TArray MatchingFileStates; 534 | for (const auto& FileStateMapEntry : FileStateMap) 535 | { 536 | auto FileState = FileStateMapEntry.Value; 537 | if (Predicate(FileState)) 538 | { 539 | MatchingFileStates.Add(FileState); 540 | } 541 | } 542 | return MatchingFileStates; 543 | } 544 | 545 | #undef LOCTEXT_NAMESPACE 546 | 547 | } // namespace namespace MercurialSourceControl 548 | -------------------------------------------------------------------------------- /Source/MercurialSourceControl/Private/MercurialSourceControlProvider.h: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------- 2 | // The MIT License (MIT) 3 | // 4 | // Copyright (c) 2014 Vadim Macagon 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in all 14 | // copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | // SOFTWARE. 23 | //------------------------------------------------------------------------------- 24 | #pragma once 25 | 26 | #include "ISourceControlProvider.h" 27 | #include "IMercurialSourceControlWorker.h" 28 | #include "MercurialSourceControlFileState.h" 29 | #include "MercurialSourceControlProviderSettings.h" 30 | 31 | namespace MercurialSourceControl { 32 | 33 | DECLARE_DELEGATE_RetVal(FWorkerRef, FCreateWorkerDelegate) 34 | 35 | class FCommand; 36 | 37 | /** 38 | * Provides access to the file revision history stored in a Mercurial repository. 39 | * 40 | * This source control provider works with files that are stored in the project Content directory. 41 | * The project Content directory must be located within a Mercurial repository, 42 | * or be the root of the repository itself. 43 | */ 44 | class FProvider : public ISourceControlProvider 45 | { 46 | public: 47 | // ISourceControlProvider methods 48 | 49 | virtual void Init(bool bForceConnection = true) override; 50 | virtual void Close() override; 51 | virtual const FName& GetName() const override; 52 | virtual FText GetStatusText() const override; 53 | virtual bool IsEnabled() const override; 54 | virtual bool IsAvailable() const override; 55 | 56 | virtual ECommandResult::Type Login( 57 | const FString& InPassword, EConcurrency::Type InConcurrency, 58 | const FSourceControlOperationComplete& InOperationCompleteDelegate 59 | ) override; 60 | 61 | virtual ECommandResult::Type GetState( 62 | const TArray& InFiles, 63 | TArray< TSharedRef >& OutState, 64 | EStateCacheUsage::Type InStateCacheUsage 65 | ) override; 66 | 67 | virtual TArray GetCachedStateByPredicate( 68 | TFunctionRef Predicate 69 | ) const override; 70 | 71 | 72 | virtual FDelegateHandle RegisterSourceControlStateChanged_Handle( 73 | const FSourceControlStateChanged::FDelegate& SourceControlStateChanged 74 | ) override; 75 | 76 | virtual void UnregisterSourceControlStateChanged_Handle( 77 | FDelegateHandle Handle 78 | ) override; 79 | 80 | virtual ECommandResult::Type Execute( 81 | const TSharedRef& InOperation, 82 | const TArray& InFiles, 83 | EConcurrency::Type InConcurrency = EConcurrency::Synchronous, 84 | const FSourceControlOperationComplete& InOperationCompleteDelegate = FSourceControlOperationComplete() 85 | ) override; 86 | 87 | virtual bool CanCancelOperation( 88 | const TSharedRef& InOperation 89 | ) const override; 90 | 91 | virtual void CancelOperation( 92 | const TSharedRef& InOperation 93 | ) override; 94 | 95 | virtual TArray< TSharedRef > GetLabels( 96 | const FString& InMatchingSpec 97 | ) const override; 98 | 99 | virtual bool UsesLocalReadOnlyState() const override; 100 | virtual bool UsesChangelists() const override; 101 | virtual void Tick() override; 102 | virtual bool UsesCheckout() const override; 103 | virtual bool QueryStateBranchConfig(const FString& ConfigSrc, const FString& ConfigDest) override; 104 | virtual void RegisterStateBranches(const TArray& BranchNames, const FString& ContentRoot) override; 105 | virtual int32 GetStateBranchIndex(const FString& BranchName) const override; 106 | 107 | #if SOURCE_CONTROL_WITH_SLATE 108 | virtual TSharedRef MakeSettingsWidget() const override; 109 | #endif // SOURCE_CONTROL_WITH_SLATE 110 | 111 | public: 112 | FProvider() : ProviderName("Mercurial") {} 113 | 114 | /** 115 | * Register a delegate that creates a worker. 116 | * Each worker performs a specific source control operation. 117 | * @param InOperationName The name of the operation the worker will perform. 118 | * @param InDelegate The delegate that will be called to create a worker. 119 | */ 120 | void RegisterWorkerCreator(const FName& InOperationName, const FCreateWorkerDelegate& InDelegate); 121 | 122 | /** Update the file status cache with the content of the given file states. */ 123 | bool UpdateFileStateCache(const TArray& InStates); 124 | 125 | /** Update the file status cache with the content of the given file revisions. */ 126 | bool UpdateFileStateCache(const TMap >& InFileRevisionsMap); 127 | 128 | static void LogError(const FText& InErrorMessage); 129 | static void LogErrors(const TArray& ErrorMessages); 130 | 131 | /** 132 | * Set the absolute path to the repository root. 133 | * @note The path must end in a '/'. 134 | */ 135 | void SetRepositoryRoot(const FString& InRepositoryRoot) 136 | { 137 | check(!FPaths::IsRelative(InRepositoryRoot)); 138 | 139 | RepositoryRoot = InRepositoryRoot; 140 | } 141 | 142 | /** Get the working directory that will be used when hg.exe is invoked. */ 143 | const FString& GetWorkingDirectory() const 144 | { 145 | // repository root will only be set after a successful "Connect" command 146 | return (RepositoryRoot.Len() > 0) ? RepositoryRoot : AbsoluteContentDirectory; 147 | } 148 | 149 | FProviderSettings& GetSettings() 150 | { 151 | return Settings; 152 | } 153 | 154 | private: 155 | /** 156 | * Attempt to retrieve the state of the given file from the cache, if that fails create a 157 | * default state for the file. 158 | */ 159 | FFileStateRef GetFileStateFromCache(const FString& Filename); 160 | 161 | /** 162 | * Execute a command synchronously. 163 | * @param ProgressText Text to be displayed on the progress dialog while the command is 164 | * executing. 165 | */ 166 | ECommandResult::Type ExecuteSynchronousCommand(FCommand* Command, const FText& ProgressText); 167 | 168 | /** 169 | * Execute a command asynchronously if possible, 170 | * fall back to synchronous execution if necessary. 171 | * @param bAutoDelete If true the command will be deleted after it finishes executing, 172 | * assuming it's executed asynchronously this will happen in Tick(). 173 | */ 174 | ECommandResult::Type ExecuteCommand(FCommand* Command, bool bAutoDelete); 175 | 176 | /** 177 | * Attempt to create a worker to perform the named operation, 178 | * if that fails return an invalid pointer. 179 | */ 180 | FWorkerPtr CreateWorker(const FName& InOperationName) const; 181 | 182 | /** 183 | * Split out the given files into two sets, regular, and large. 184 | * @param InFiles Either relative or absolute filenames that should be tracked by Mercurial. 185 | * @param OutAbsoluteFiles Will be filled in with the absolute filenames of any files in 186 | * InFiles that are not in OutAbsoluteFiles. 187 | * @param OutAbsoluteLargeFiles Will be filled in with the absolute filenames of any files in 188 | * InFiles that match one of the asset types specified by the user. 189 | */ 190 | void PrepareFilenamesForAddCommand( 191 | const TArray& InFiles, 192 | TArray& OutAbsoluteFiles, TArray& OutAbsoluteLargeFiles 193 | ); 194 | 195 | private: 196 | /** All the registered worker creation delegates. */ 197 | TMap WorkerCreatorsMap; 198 | 199 | struct FCommandQueueEntry 200 | { 201 | FCommand* Command; 202 | bool bAutoDelete; 203 | }; 204 | 205 | /** Queue of commands given by the main thread. */ 206 | TArray CommandQueue; 207 | 208 | /** Cache of file states. */ 209 | TMap FileStateMap; 210 | 211 | /** Used to notify when the state of an item (or group of items) has changed. */ 212 | FSourceControlStateChanged OnSourceControlStateChanged; 213 | 214 | /** Absolute path to the current project's content directory. */ 215 | FString AbsoluteContentDirectory; 216 | 217 | /** Absolute path to the repository root directory. */ 218 | FString RepositoryRoot; 219 | 220 | /** User accessible settings. */ 221 | FProviderSettings Settings; 222 | 223 | FName ProviderName; 224 | 225 | private: 226 | static FName SourceControlLogName; 227 | }; 228 | 229 | } // namespace MercurialSourceControl 230 | -------------------------------------------------------------------------------- /Source/MercurialSourceControl/Private/MercurialSourceControlProviderSettings.cpp: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------- 2 | // The MIT License (MIT) 3 | // 4 | // Copyright (c) 2014 Vadim Macagon 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in all 14 | // copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | // SOFTWARE. 23 | //------------------------------------------------------------------------------- 24 | 25 | #include "MercurialSourceControlPrivatePCH.h" 26 | #include "MercurialSourceControlProviderSettings.h" 27 | #include "SourceControlHelpers.h" 28 | 29 | namespace MercurialSourceControl { 30 | 31 | namespace Settings { 32 | const TCHAR* Section = TEXT("MercurialSourceControl.ProviderSettings"); 33 | const TCHAR* MercurialPath = TEXT("MercurialPath"); 34 | const TCHAR* LargefilesIntegration = TEXT("LargefilesIntegration"); 35 | const TCHAR* LargeAssetTypes = TEXT("LargeAssetTypes"); 36 | } // namespace Settings 37 | 38 | 39 | const FString& FProviderSettings::GetMercurialPath() const 40 | { 41 | FScopeLock ScopeLock(&CriticalSection); 42 | return MercurialPath; 43 | } 44 | 45 | void FProviderSettings::SetMercurialPath(const FString& InMercurialPath) 46 | { 47 | FScopeLock ScopeLock(&CriticalSection); 48 | MercurialPath = InMercurialPath; 49 | } 50 | 51 | bool FProviderSettings::IsLargefilesIntegrationEnabled() const 52 | { 53 | FScopeLock ScopeLock(&CriticalSection); 54 | return bEnableLargefilesIntegration; 55 | } 56 | 57 | void FProviderSettings::EnableLargefilesIntegration(bool bEnable) 58 | { 59 | FScopeLock ScopeLock(&CriticalSection); 60 | bEnableLargefilesIntegration = bEnable; 61 | } 62 | 63 | void FProviderSettings::GetLargeAssetTypes(TArray& OutLargeAssetTypes) const 64 | { 65 | FScopeLock ScopeLock(&CriticalSection); 66 | OutLargeAssetTypes = LargeAssetTypes; 67 | } 68 | 69 | void FProviderSettings::SetLargeAssetTypes(const TArray& InLargeAssetTypes) 70 | { 71 | FScopeLock ScopeLock(&CriticalSection); 72 | LargeAssetTypes = InLargeAssetTypes; 73 | } 74 | 75 | void FProviderSettings::Save() 76 | { 77 | FScopeLock ScopeLock(&CriticalSection); 78 | const FString& SettingsFile = SourceControlHelpers::GetSettingsIni(); 79 | if (GConfig) 80 | { 81 | GConfig->SetString(Settings::Section, Settings::MercurialPath, *MercurialPath, SettingsFile); 82 | GConfig->SetBool(Settings::Section, Settings::LargefilesIntegration, bEnableLargefilesIntegration, SettingsFile); 83 | GConfig->SetArray(Settings::Section, Settings::LargeAssetTypes, LargeAssetTypes, SettingsFile); 84 | } 85 | } 86 | 87 | void FProviderSettings::Load() 88 | { 89 | FScopeLock ScopeLock(&CriticalSection); 90 | const FString& SettingsFile = SourceControlHelpers::GetSettingsIni(); 91 | if (GConfig) 92 | { 93 | GConfig->GetString(Settings::Section, Settings::MercurialPath, MercurialPath, SettingsFile); 94 | GConfig->GetBool(Settings::Section, Settings::LargefilesIntegration, bEnableLargefilesIntegration, SettingsFile); 95 | GConfig->GetArray(Settings::Section, Settings::LargeAssetTypes, LargeAssetTypes, SettingsFile); 96 | } 97 | } 98 | 99 | } // namespace MercurialSourceControl 100 | -------------------------------------------------------------------------------- /Source/MercurialSourceControl/Private/MercurialSourceControlProviderSettings.h: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------- 2 | // The MIT License (MIT) 3 | // 4 | // Copyright (c) 2014 Vadim Macagon 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in all 14 | // copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | // SOFTWARE. 23 | //------------------------------------------------------------------------------- 24 | 25 | #pragma once 26 | 27 | namespace MercurialSourceControl { 28 | 29 | /** Provides access to settings stored in SourceControlSettings.ini. */ 30 | class FProviderSettings 31 | { 32 | public: 33 | const FString& GetMercurialPath() const; 34 | void SetMercurialPath(const FString& InMercurialPath); 35 | bool IsLargefilesIntegrationEnabled() const; 36 | void EnableLargefilesIntegration(bool bEnable); 37 | void GetLargeAssetTypes(TArray& OutLargeAssetTypes) const; 38 | void SetLargeAssetTypes(const TArray& InLargeAssetTypes); 39 | 40 | void Save(); 41 | void Load(); 42 | 43 | private: 44 | mutable FCriticalSection CriticalSection; 45 | 46 | /** Full path to Mercurial executable. */ 47 | FString MercurialPath; 48 | 49 | bool bEnableLargefilesIntegration; 50 | 51 | /** 52 | Class names of all the asset types that should be flagged as "large" by the Largefiles 53 | extension. 54 | */ 55 | TArray LargeAssetTypes; 56 | }; 57 | 58 | } // namespace MercurialSourceControl 59 | -------------------------------------------------------------------------------- /Source/MercurialSourceControl/Private/MercurialSourceControlStyle.cpp: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------- 2 | // The MIT License (MIT) 3 | // 4 | // Copyright (c) 2014 Vadim Macagon 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in all 14 | // copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | // SOFTWARE. 23 | //------------------------------------------------------------------------------- 24 | 25 | #include "MercurialSourceControlPrivatePCH.h" 26 | #include "MercurialSourceControlStyle.h" 27 | #include "SlateStyle.h" 28 | #include "EditorStyle.h" 29 | 30 | namespace MercurialSourceControl { 31 | 32 | const FName FMercurialStyle::CleanStatusIcon32("Mercurial.CleanStatusIcon"); 33 | const FName FMercurialStyle::CleanStatusIcon16("Mercurial.CleanStatusIcon.Small"); 34 | const FName FMercurialStyle::AddedStatusIcon32("Mercurial.AddedStatusIcon"); 35 | const FName FMercurialStyle::AddedStatusIcon16("Mercurial.AddedStatusIcon.Small"); 36 | const FName FMercurialStyle::ModifiedStatusIcon32("Mercurial.ModifiedStatusIcon"); 37 | const FName FMercurialStyle::ModifiedStatusIcon16("Mercurial.ModifiedStatusIcon.Small"); 38 | const FName FMercurialStyle::RemovedStatusIcon32("Mercurial.RemovedStatusIcon"); 39 | const FName FMercurialStyle::RemovedStatusIcon16("Mercurial.RemovedStatusIcon.Small"); 40 | const FName FMercurialStyle::NotTrackedStatusIcon32("Mercurial.NotTrackedStatusIcon"); 41 | const FName FMercurialStyle::NotTrackedStatusIcon16("Mercurial.NotTrackedStatusIcon.Small"); 42 | const FName FMercurialStyle::MissingStatusIcon32("Mercurial.MissingStatusIcon"); 43 | const FName FMercurialStyle::MissingStatusIcon16("Mercurial.MissingStatusIcon.Small"); 44 | 45 | TSharedPtr MercurialSourceControl::FMercurialStyle::Instance = nullptr; 46 | 47 | void FMercurialStyle::Initialize() 48 | { 49 | if (!Instance.IsValid()) 50 | { 51 | Instance = Create(); 52 | } 53 | SetStyle(Instance.ToSharedRef()); 54 | } 55 | 56 | void FMercurialStyle::Shutdown() 57 | { 58 | ResetToDefault(); // switch back to the previous style 59 | ensure(Instance.IsUnique()); 60 | Instance.Reset(); 61 | } 62 | 63 | TSharedRef FMercurialStyle::Create() 64 | { 65 | auto& EditorStyleModule = FModuleManager::LoadModuleChecked(TEXT("EditorStyle")); 66 | TSharedRef EditorStyleSetRef = EditorStyleModule.CreateEditorStyleInstance(); 67 | FSlateStyleSet& EditorStyle = EditorStyleSetRef.Get(); 68 | 69 | const FVector2D Icon16x16(16.0f, 16.0f); 70 | const FVector2D Icon32x32(32.0f, 32.0f); 71 | 72 | const FString SlateBrushesPath = 73 | FSlateBrush::UTextureIdentifier() 74 | // content root (i.e. parent directory of this plugin's Content folder), 75 | // will be resolved to the location of the MercurialSourceControl.uplugin file, 76 | // but this only works if "CanContainContent" is set to true in the .uplugin file 77 | + "/MercurialSourceControl" 78 | // asset path that's relative to /Content 79 | + "/SlateBrushes"; 80 | 81 | EditorStyle.Set( 82 | CleanStatusIcon32, 83 | new FSlateImageBrush( 84 | SlateBrushesPath / TEXT("CleanStatusIcon.CleanStatusIcon"), Icon32x32 85 | ) 86 | ); 87 | EditorStyle.Set( 88 | CleanStatusIcon16, 89 | new FSlateImageBrush( 90 | SlateBrushesPath / TEXT("CleanStatusIcon.CleanStatusIcon"), Icon16x16 91 | ) 92 | ); 93 | EditorStyle.Set( 94 | AddedStatusIcon32, 95 | new FSlateImageBrush( 96 | SlateBrushesPath / TEXT("AddedStatusIcon.AddedStatusIcon"), Icon32x32 97 | ) 98 | ); 99 | EditorStyle.Set( 100 | AddedStatusIcon16, 101 | new FSlateImageBrush( 102 | SlateBrushesPath / TEXT("AddedStatusIcon.AddedStatusIcon"), Icon16x16 103 | ) 104 | ); 105 | EditorStyle.Set( 106 | ModifiedStatusIcon32, 107 | new FSlateImageBrush( 108 | SlateBrushesPath / TEXT("ModifiedStatusIcon.ModifiedStatusIcon"), Icon32x32 109 | ) 110 | ); 111 | EditorStyle.Set( 112 | ModifiedStatusIcon16, 113 | new FSlateImageBrush( 114 | SlateBrushesPath / TEXT("ModifiedStatusIcon.ModifiedStatusIcon"), Icon16x16 115 | ) 116 | ); 117 | EditorStyle.Set( 118 | RemovedStatusIcon32, 119 | new FSlateImageBrush( 120 | SlateBrushesPath / TEXT("RemovedStatusIcon.RemovedStatusIcon"), Icon32x32 121 | ) 122 | ); 123 | EditorStyle.Set( 124 | RemovedStatusIcon16, 125 | new FSlateImageBrush( 126 | SlateBrushesPath / TEXT("RemovedStatusIcon.RemovedStatusIcon"), Icon16x16 127 | ) 128 | ); 129 | EditorStyle.Set( 130 | NotTrackedStatusIcon32, 131 | new FSlateImageBrush( 132 | SlateBrushesPath / TEXT("NotTrackedStatusIcon.NotTrackedStatusIcon"), Icon32x32 133 | ) 134 | ); 135 | EditorStyle.Set( 136 | NotTrackedStatusIcon16, 137 | new FSlateImageBrush( 138 | SlateBrushesPath / TEXT("NotTrackedStatusIcon.NotTrackedStatusIcon"), Icon16x16 139 | ) 140 | ); 141 | EditorStyle.Set( 142 | MissingStatusIcon32, 143 | new FSlateImageBrush( 144 | SlateBrushesPath / TEXT("MissingStatusIcon.MissingStatusIcon"), Icon32x32 145 | ) 146 | ); 147 | EditorStyle.Set( 148 | MissingStatusIcon16, 149 | new FSlateImageBrush( 150 | SlateBrushesPath / TEXT("MissingStatusIcon.MissingStatusIcon"), Icon16x16 151 | ) 152 | ); 153 | 154 | return EditorStyleSetRef; 155 | } 156 | 157 | } // namespace MercurialSourceControl 158 | -------------------------------------------------------------------------------- /Source/MercurialSourceControl/Private/MercurialSourceControlStyle.h: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------- 2 | // The MIT License (MIT) 3 | // 4 | // Copyright (c) 2014 Vadim Macagon 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in all 14 | // copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | // SOFTWARE. 23 | //------------------------------------------------------------------------------- 24 | #pragma once 25 | 26 | class FSlateStyleSet; 27 | 28 | /** 29 | * Extends EditorStyle with Mercurial source control icon overlays. 30 | */ 31 | namespace MercurialSourceControl { 32 | 33 | class FMercurialStyle : public FEditorStyle 34 | { 35 | public: 36 | /** Replace the current EditorStyle with this one. */ 37 | static void Initialize(); 38 | /** Restore the previous EditorStyle. */ 39 | static void Shutdown(); 40 | 41 | public: 42 | static const FName CleanStatusIcon32; 43 | static const FName CleanStatusIcon16; 44 | static const FName AddedStatusIcon32; 45 | static const FName AddedStatusIcon16; 46 | static const FName ModifiedStatusIcon32; 47 | static const FName ModifiedStatusIcon16; 48 | static const FName RemovedStatusIcon32; 49 | static const FName RemovedStatusIcon16; 50 | static const FName NotTrackedStatusIcon32; 51 | static const FName NotTrackedStatusIcon16; 52 | static const FName MissingStatusIcon32; 53 | static const FName MissingStatusIcon16; 54 | 55 | private: 56 | FMercurialStyle() {} 57 | 58 | static TSharedRef Create(); 59 | 60 | private: 61 | static TSharedPtr Instance; 62 | }; 63 | 64 | } // namespace MercurialSourceControl 65 | -------------------------------------------------------------------------------- /Source/MercurialSourceControl/Private/MercurialSourceControlWorkers.cpp: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------- 2 | // The MIT License (MIT) 3 | // 4 | // Copyright (c) 2014 Vadim Macagon 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in all 14 | // copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | // SOFTWARE. 23 | //------------------------------------------------------------------------------- 24 | 25 | #include "MercurialSourceControlPrivatePCH.h" 26 | #include "MercurialSourceControlWorkers.h" 27 | #include "MercurialSourceControlOperationNames.h" 28 | #include "MercurialSourceControlClient.h" 29 | #include "MercurialSourceControlModule.h" 30 | #include "MercurialSourceControlCommand.h" 31 | 32 | namespace MercurialSourceControl { 33 | 34 | #define LOCTEXT_NAMESPACE "MercurialSourceControl.Workers" 35 | 36 | FName FConnectWorker::GetName() const 37 | { 38 | return OperationNames::Connect; 39 | } 40 | 41 | bool FConnectWorker::Execute(FCommand& InCommand) 42 | { 43 | check(InCommand.GetOperation()->GetName() == OperationNames::Connect); 44 | check(InCommand.GetAbsoluteFiles().Num() == 1); 45 | 46 | FText ErrorMessage; 47 | TSharedRef Operation = 48 | StaticCastSharedRef(InCommand.GetOperation()); 49 | 50 | if (!FClient::Create(InCommand.GetAbsoluteFiles()[0], ErrorMessage)) 51 | { 52 | Operation->SetErrorText(ErrorMessage); 53 | return false; 54 | } 55 | 56 | if (!FClient::Get()->GetRepositoryRoot(InCommand.GetWorkingDirectory(), RepositoryRoot)) 57 | { 58 | Operation->SetErrorText( 59 | FText::Format( 60 | LOCTEXT("DirNotInRepo", "Directory '{0}' is not in a Mercurial repository."), 61 | FText::FromString(InCommand.GetWorkingDirectory()) 62 | ) 63 | ); 64 | return false; 65 | } 66 | 67 | return true; 68 | } 69 | 70 | bool FConnectWorker::UpdateStates() const 71 | { 72 | FProvider& Provider = FModule::GetProvider(); 73 | if (!RepositoryRoot.IsEmpty()) 74 | { 75 | Provider.SetRepositoryRoot(RepositoryRoot); 76 | } 77 | return false; 78 | } 79 | 80 | FName FUpdateStatusWorker::GetName() const 81 | { 82 | return OperationNames::UpdateStatus; 83 | } 84 | 85 | bool FUpdateStatusWorker::Execute(FCommand& InCommand) 86 | { 87 | check(InCommand.GetOperation()->GetName() == OperationNames::UpdateStatus); 88 | 89 | const FClientSharedPtr Client = FClient::Get(); 90 | if (!Client.IsValid()) 91 | { 92 | return false; 93 | } 94 | 95 | TSharedRef Operation = 96 | StaticCastSharedRef(InCommand.GetOperation()); 97 | 98 | bool bResult = false; 99 | 100 | if (Operation->ShouldGetOpenedOnly()) 101 | { 102 | // What Perforce calls "opened" files roughly corresponds to files with an 103 | // added/modified/removed status in Mercurial. To keep things simple we'll just update 104 | // the status of all the files in the current content directory. 105 | TArray Files; 106 | if (InCommand.GetWorkingDirectory() != InCommand.GetContentDirectory()) 107 | { 108 | FString Directory = InCommand.GetContentDirectory(); 109 | if (FPaths::MakePathRelativeTo(Directory, *InCommand.GetWorkingDirectory())) 110 | { 111 | Files.Add(Directory); 112 | } 113 | else 114 | { 115 | // In this particular case the working directory should be the repository root, 116 | // if the content directory can't be made relative to the repository root then 117 | // it's not in the repository! 118 | // TODO: localize the error message 119 | InCommand.ErrorMessages.Add(TEXT("Content directory is not in a repository.")); 120 | return false; 121 | } 122 | } 123 | bResult = Client->GetFileStates( 124 | InCommand.GetWorkingDirectory(), Files, FileStates, InCommand.ErrorMessages 125 | ); 126 | } 127 | else if (InCommand.GetAbsoluteFiles().Num() > 0) 128 | { 129 | bResult = Client->GetFileStates( 130 | InCommand.GetWorkingDirectory(), InCommand.GetAbsoluteFiles(), FileStates, 131 | InCommand.ErrorMessages 132 | ); 133 | } 134 | else // no filenames were provided, so there's nothing to do 135 | { 136 | return true; 137 | } 138 | 139 | if (Operation->ShouldUpdateHistory() && (InCommand.GetAbsoluteFiles().Num() > 0)) 140 | { 141 | bResult = Client->GetFileHistory( 142 | InCommand.GetWorkingDirectory(), InCommand.GetAbsoluteFiles(), FileRevisionsMap, 143 | InCommand.ErrorMessages 144 | ); 145 | } 146 | 147 | return bResult; 148 | } 149 | 150 | bool FUpdateStatusWorker::UpdateStates() const 151 | { 152 | FProvider& Provider = FModule::GetProvider(); 153 | bool bStatesUpdated = false; 154 | if (FileStates.Num() > 0) 155 | { 156 | bStatesUpdated |= Provider.UpdateFileStateCache(FileStates); 157 | } 158 | if (FileRevisionsMap.Num() > 0) 159 | { 160 | bStatesUpdated |= Provider.UpdateFileStateCache(FileRevisionsMap); 161 | } 162 | return bStatesUpdated; 163 | } 164 | 165 | FName FRevertWorker::GetName() const 166 | { 167 | return OperationNames::Revert; 168 | } 169 | 170 | bool FRevertWorker::Execute(FCommand& InCommand) 171 | { 172 | check(InCommand.GetOperation()->GetName() == OperationNames::Revert); 173 | 174 | const FClientSharedPtr Client = FClient::Get(); 175 | if (!Client.IsValid()) 176 | { 177 | return false; 178 | } 179 | 180 | bool bResult = Client->RevertFiles( 181 | InCommand.GetWorkingDirectory(), InCommand.GetAbsoluteFiles(), InCommand.ErrorMessages 182 | ); 183 | 184 | bResult &= Client->GetFileStates( 185 | InCommand.GetWorkingDirectory(), InCommand.GetAbsoluteFiles(), FileStates, 186 | InCommand.ErrorMessages 187 | ); 188 | 189 | return bResult; 190 | } 191 | 192 | bool FRevertWorker::UpdateStates() const 193 | { 194 | return FModule::GetProvider().UpdateFileStateCache(FileStates); 195 | } 196 | 197 | FName FDeleteWorker::GetName() const 198 | { 199 | return OperationNames::Delete; 200 | } 201 | 202 | bool FDeleteWorker::Execute(FCommand& InCommand) 203 | { 204 | check(InCommand.GetOperation()->GetName() == OperationNames::Delete); 205 | 206 | const FClientSharedPtr Client = FClient::Get(); 207 | if (!Client.IsValid()) 208 | { 209 | return false; 210 | } 211 | 212 | // NOTE: This will not remove files with an "added" status, but the Editor seems to revert 213 | // files before deleting them, so we shouldn't need to handle "added" files here. 214 | bool bResult = Client->RemoveFiles( 215 | InCommand.GetWorkingDirectory(), InCommand.GetAbsoluteFiles(), InCommand.ErrorMessages 216 | ); 217 | 218 | bResult &= Client->GetFileStates( 219 | InCommand.GetWorkingDirectory(), InCommand.GetAbsoluteFiles(), FileStates, 220 | InCommand.ErrorMessages 221 | ); 222 | 223 | return bResult; 224 | } 225 | 226 | bool FDeleteWorker::UpdateStates() const 227 | { 228 | return FModule::GetProvider().UpdateFileStateCache(FileStates); 229 | } 230 | 231 | FName FMarkForAddWorker::GetName() const 232 | { 233 | return OperationNames::MarkForAdd; 234 | } 235 | 236 | bool FMarkForAddWorker::Execute(FCommand& InCommand) 237 | { 238 | check(InCommand.GetOperation()->GetName() == OperationNames::MarkForAdd); 239 | check((InCommand.GetAbsoluteFiles().Num() > 0) || (InCommand.GetAbsoluteLargeFiles().Num() > 0)); 240 | 241 | const FClientSharedPtr Client = FClient::Get(); 242 | if (!Client.IsValid()) 243 | { 244 | return false; 245 | } 246 | 247 | bool bResult = true; 248 | 249 | if (InCommand.GetAbsoluteFiles().Num() > 0) 250 | { 251 | bResult &= Client->AddFiles( 252 | InCommand.GetWorkingDirectory(), InCommand.GetAbsoluteFiles(), false, 253 | InCommand.ErrorMessages 254 | ); 255 | } 256 | 257 | if (InCommand.GetAbsoluteLargeFiles().Num() > 0) 258 | { 259 | bResult &= Client->AddFiles( 260 | InCommand.GetWorkingDirectory(), InCommand.GetAbsoluteLargeFiles(), true, 261 | InCommand.ErrorMessages 262 | ); 263 | } 264 | 265 | bResult &= Client->GetFileStates( 266 | InCommand.GetWorkingDirectory(), InCommand.GetAbsoluteFiles(), FileStates, 267 | InCommand.ErrorMessages 268 | ); 269 | 270 | return bResult; 271 | } 272 | 273 | bool FMarkForAddWorker::UpdateStates() const 274 | { 275 | return FModule::GetProvider().UpdateFileStateCache(FileStates); 276 | } 277 | 278 | FName FCheckInWorker::GetName() const 279 | { 280 | return OperationNames::CheckIn; 281 | } 282 | 283 | bool FCheckInWorker::Execute(FCommand& InCommand) 284 | { 285 | check(InCommand.GetOperation()->GetName() == OperationNames::CheckIn); 286 | 287 | const FClientSharedPtr Client = FClient::Get(); 288 | if (!Client.IsValid()) 289 | { 290 | return false; 291 | } 292 | 293 | TSharedRef Operation = 294 | StaticCastSharedRef(InCommand.GetOperation()); 295 | 296 | bool bResult = Client->CommitFiles( 297 | InCommand.GetWorkingDirectory(), InCommand.GetAbsoluteFiles(), 298 | Operation->GetDescription().ToString(), InCommand.ErrorMessages 299 | ); 300 | 301 | if (bResult) 302 | { 303 | FString RevisionID; 304 | bool bRetrievedID = Client->GetWorkingDirectoryParentRevisionID( 305 | InCommand.GetWorkingDirectory(), RevisionID, InCommand.ErrorMessages 306 | ); 307 | 308 | if (!bRetrievedID) 309 | { 310 | RevisionID = "???"; 311 | } 312 | 313 | Operation->SetSuccessMessage( 314 | FText::Format( 315 | LOCTEXT("CommitSuccessful", "Committed revision {0}."), 316 | FText::FromString(RevisionID) 317 | ) 318 | ); 319 | } 320 | 321 | bResult &= Client->GetFileStates( 322 | InCommand.GetWorkingDirectory(), InCommand.GetAbsoluteFiles(), FileStates, 323 | InCommand.ErrorMessages 324 | ); 325 | 326 | return bResult; 327 | } 328 | 329 | bool FCheckInWorker::UpdateStates() const 330 | { 331 | return FModule::GetProvider().UpdateFileStateCache(FileStates); 332 | } 333 | 334 | #undef LOCTEXT_NAMESPACE 335 | 336 | } // namespace MercurialSourceControl 337 | -------------------------------------------------------------------------------- /Source/MercurialSourceControl/Private/MercurialSourceControlWorkers.h: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------- 2 | // The MIT License (MIT) 3 | // 4 | // Copyright (c) 2014 Vadim Macagon 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in all 14 | // copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | // SOFTWARE. 23 | //------------------------------------------------------------------------------- 24 | #pragma once 25 | 26 | #include "IMercurialSourceControlWorker.h" 27 | #include "MercurialSourceControlFileRevision.h" 28 | 29 | namespace MercurialSourceControl { 30 | 31 | class FCommand; 32 | class FFileState; 33 | 34 | /** 35 | * Determines the location of the Mercurial repository root. 36 | * If the repository root is not found the Mercurial source control provider will not be enabled. 37 | */ 38 | class FConnectWorker : public IWorker 39 | { 40 | public: 41 | virtual FName GetName() const override; 42 | virtual bool Execute(FCommand& InCommand) override; 43 | virtual bool UpdateStates() const override; 44 | 45 | private: 46 | FString RepositoryRoot; 47 | }; 48 | 49 | /** 50 | * Updates the file status and file revision history caches in the Mercurial 51 | * source control provider. 52 | */ 53 | class FUpdateStatusWorker : public IWorker 54 | { 55 | public: 56 | virtual FName GetName() const override; 57 | virtual bool Execute(FCommand& InCommand) override; 58 | virtual bool UpdateStates() const override; 59 | 60 | private: 61 | TArray FileStates; 62 | TMap > FileRevisionsMap; 63 | }; 64 | 65 | /** Reverts files back to the most recent revision in the repository. */ 66 | class FRevertWorker : public IWorker 67 | { 68 | public: 69 | virtual FName GetName() const; 70 | virtual bool Execute(FCommand& InCommand); 71 | virtual bool UpdateStates() const; 72 | 73 | private: 74 | TArray FileStates; 75 | }; 76 | 77 | /** Removes files from the repository. */ 78 | class FDeleteWorker : public IWorker 79 | { 80 | public: 81 | virtual FName GetName() const; 82 | virtual bool Execute(FCommand& InCommand); 83 | virtual bool UpdateStates() const; 84 | 85 | private: 86 | TArray FileStates; 87 | }; 88 | 89 | /** Marks files to be added to the repository. */ 90 | class FMarkForAddWorker : public IWorker 91 | { 92 | public: 93 | virtual FName GetName() const; 94 | virtual bool Execute(FCommand& InCommand); 95 | virtual bool UpdateStates() const; 96 | 97 | private: 98 | TArray FileStates; 99 | }; 100 | 101 | /** Commits files to the repository. */ 102 | class FCheckInWorker : public IWorker 103 | { 104 | public: 105 | virtual FName GetName() const; 106 | virtual bool Execute(FCommand& InCommand); 107 | virtual bool UpdateStates() const; 108 | 109 | private: 110 | TArray FileStates; 111 | }; 112 | 113 | } // namespace MercurialSourceControl 114 | -------------------------------------------------------------------------------- /Source/MercurialSourceControl/Private/SLargeAssetTypeTreeWidget.cpp: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------- 2 | // The MIT License (MIT) 3 | // 4 | // Copyright (c) 2014 Vadim Macagon 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in all 14 | // copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | // SOFTWARE. 23 | //------------------------------------------------------------------------------- 24 | 25 | #include "MercurialSourceControlPrivatePCH.h" 26 | #include "SLargeAssetTypeTreeWidget.h" 27 | 28 | #include "IAssetTypeActions.h" 29 | #include "IAssetTools.h" 30 | 31 | namespace MercurialSourceControl { 32 | 33 | typedef IAssetTypeActions IAssetType; // sorry, but the original name was horrid 34 | typedef TWeakPtr FAssetTypeWeakPtr; 35 | 36 | /** 37 | * A tree item in an SLargeAssetTypeTreeWidget. 38 | * 39 | * Tree items of this type can represent either an asset category or an asset type. 40 | * Asset categories are the top level tree items and can contain asset type items. 41 | */ 42 | class FLargeAssetTypeTreeItem : public TSharedFromThis 43 | { 44 | public: 45 | // text that will be displayed for this item in the tree view 46 | FText Title; 47 | // parents store shared pointers to children, 48 | // so children must only store weak pointers to their parents to ensure proper cleanup 49 | FLargeAssetTypeTreeItemWeakPtr Parent; 50 | // only asset category items will actually have any children 51 | TArray Children; 52 | // the class name that corresponds to this asset type 53 | FString AssetTypeClassName; 54 | 55 | public: 56 | FLargeAssetTypeTreeItem(const FText& InTitle) 57 | : Title(InTitle) 58 | , bIsSelected(false) 59 | { 60 | } 61 | 62 | FLargeAssetTypeTreeItem( 63 | const IAssetType& InAssetType, const TWeakPtr& InParent 64 | ) 65 | : Parent(InParent) 66 | , bIsSelected(false) 67 | { 68 | Title = InAssetType.GetName(); 69 | AssetTypeClassName = InAssetType.GetSupportedClass()->GetName(); 70 | } 71 | 72 | bool IsSelected() const 73 | { 74 | if (SharedState.IsValid()) 75 | { 76 | return SharedState->bIsSelected; 77 | } 78 | return bIsSelected; 79 | } 80 | 81 | void SetIsSelected(bool bInIsSelected) 82 | { 83 | if (SharedState.IsValid()) 84 | { 85 | SharedState->bIsSelected = bInIsSelected; 86 | } 87 | else 88 | { 89 | bIsSelected = bInIsSelected; 90 | } 91 | } 92 | 93 | static void CreateSharedState( 94 | const TArray& InAssetTypeItems, bool bIsSelected 95 | ) 96 | { 97 | // An asset type may belong to multiple asset categories, however, 98 | // all tree items are distinct, so multiple tree items may correspond to the same 99 | // asset type. The checked state is shared between such duplicate items, 100 | // so that checking/unchecking an asset type in one category results in that 101 | // asset type being checked/unchecked in any other categories it may appear in. 102 | TSharedPtr SharedState = MakeShareable(new FSharedState(bIsSelected)); 103 | 104 | for (auto AssetTypeItem : InAssetTypeItems) 105 | { 106 | AssetTypeItem.Pin()->SharedState = SharedState; 107 | } 108 | } 109 | 110 | private: 111 | // state shared by items that represent asset types belonging to multiple categories 112 | struct FSharedState : public TSharedFromThis 113 | { 114 | bool bIsSelected; 115 | 116 | FSharedState(bool bInIsSelected) : bIsSelected(bInIsSelected) {} 117 | }; 118 | 119 | private: 120 | // only relevant for asset type items, and furthermore only when SharedState.IsValid() == false 121 | bool bIsSelected; 122 | // will only be valid for asset type items that correspond to asset types that belong to 123 | // multiple categories 124 | TSharedPtr SharedState; 125 | }; 126 | 127 | #define LOCTEXT_NAMESPACE "MercurialSourceControl.SLargeAssetTypeTreeWidget" 128 | 129 | void SLargeAssetTypeTreeWidget::Construct(const FArguments& InArgs) 130 | { 131 | OnItemCheckStateChanged = InArgs._OnItemCheckStateChanged; 132 | 133 | ChildSlot 134 | [ 135 | SAssignNew(TreeView, SLargeAssetTypeTreeView) 136 | .SelectionMode(ESelectionMode::None) 137 | .TreeItemsSource(&AssetCategories) 138 | // get child items for any given parent item 139 | .OnGetChildren(this, &SLargeAssetTypeTreeWidget::TreeView_OnGetChildren) 140 | // generate a widget for each item 141 | .OnGenerateRow(this, &SLargeAssetTypeTreeWidget::TreeView_OnGenerateRow) 142 | ]; 143 | 144 | Populate(InArgs._SelectedAssetTypeNames); 145 | } 146 | 147 | void SLargeAssetTypeTreeWidget::AddCategoriesToCategoryMap( 148 | TMap& OutCategoryMap 149 | ) 150 | { 151 | // reuse localized strings from the content browser 152 | #define NS_CONTENT_BROWSER "ContentBrowser" 153 | 154 | OutCategoryMap.Add( 155 | EAssetTypeCategories::Basic, 156 | MakeShareable( 157 | new FLargeAssetTypeTreeItem( 158 | NSLOCTEXT(NS_CONTENT_BROWSER, "BasicFilter", "Basic") 159 | ) 160 | ) 161 | ); 162 | OutCategoryMap.Add( 163 | EAssetTypeCategories::Animation, 164 | MakeShareable( 165 | new FLargeAssetTypeTreeItem( 166 | NSLOCTEXT(NS_CONTENT_BROWSER, "AnimationFilter", "Animation") 167 | ) 168 | ) 169 | ); 170 | OutCategoryMap.Add( 171 | EAssetTypeCategories::MaterialsAndTextures, 172 | MakeShareable( 173 | new FLargeAssetTypeTreeItem( 174 | NSLOCTEXT(NS_CONTENT_BROWSER, "MaterialFilter", "Materials & Textures") 175 | ) 176 | ) 177 | ); 178 | OutCategoryMap.Add( 179 | EAssetTypeCategories::Sounds, 180 | MakeShareable( 181 | new FLargeAssetTypeTreeItem( 182 | NSLOCTEXT(NS_CONTENT_BROWSER, "SoundFilter", "Sounds") 183 | ) 184 | ) 185 | ); 186 | OutCategoryMap.Add( 187 | EAssetTypeCategories::Physics, 188 | MakeShareable( 189 | new FLargeAssetTypeTreeItem( 190 | NSLOCTEXT(NS_CONTENT_BROWSER, "PhysicsFilter", "Physics") 191 | ) 192 | ) 193 | ); 194 | OutCategoryMap.Add( 195 | EAssetTypeCategories::Misc, 196 | MakeShareable( 197 | new FLargeAssetTypeTreeItem( 198 | NSLOCTEXT(NS_CONTENT_BROWSER, "MiscFilter", "Miscellaneous") 199 | ) 200 | ) 201 | ); 202 | 203 | #undef NS_CONTENT_BROWSER 204 | } 205 | 206 | void SLargeAssetTypeTreeWidget::Populate(const TArray& InSelectedAssetTypeClassNames) 207 | { 208 | AssetCategories.Empty(); 209 | 210 | TMap CategoryMap; 211 | AddCategoriesToCategoryMap(CategoryMap); 212 | 213 | FAssetToolsModule& AssetToolsModule = 214 | FModuleManager::LoadModuleChecked(TEXT("AssetTools")); 215 | TArray AssetTypes; 216 | AssetToolsModule.Get().GetAssetTypeActionsList(AssetTypes); 217 | 218 | struct FOrderAssetTypesByNameAsc 219 | { 220 | // return true if A should appear before B, false otherwise 221 | bool operator()(const FAssetTypeWeakPtr& A, const FAssetTypeWeakPtr& B) const 222 | { 223 | return A.Pin()->GetName().CompareTo(B.Pin()->GetName()) < 0; 224 | } 225 | }; 226 | 227 | AssetTypes.Sort(FOrderAssetTypesByNameAsc()); 228 | 229 | // assign all the asset types to the corresponding category tree items 230 | for (const auto AssetTypeWeakPtr : AssetTypes) 231 | { 232 | TSharedPtr AssetType = AssetTypeWeakPtr.Pin(); 233 | // for consistency ignore asset types that can't be filtered by in the content browser, 234 | // usually this is because the asset type is not fully supported 235 | if (AssetType.IsValid() && AssetType->CanFilter()) 236 | { 237 | TArray AssetTypeItems; 238 | for (auto CategoryMapEntry : CategoryMap) 239 | { 240 | if (AssetType->GetCategories() & CategoryMapEntry.Key) 241 | { 242 | FLargeAssetTypeTreeItemPtr AssetTypeItem = 243 | MakeShareable( 244 | new FLargeAssetTypeTreeItem(*AssetType.Get(), CategoryMapEntry.Value) 245 | ); 246 | 247 | AssetTypeItems.Add(AssetTypeItem); 248 | CategoryMapEntry.Value->Children.Add(AssetTypeItem); 249 | } 250 | } 251 | 252 | if (AssetTypeItems.Num() == 1) 253 | { 254 | auto AssetTypeItem = AssetTypeItems[0].Pin(); 255 | AssetTypeItem->SetIsSelected( 256 | InSelectedAssetTypeClassNames.Contains(AssetTypeItem->AssetTypeClassName) 257 | ); 258 | } 259 | else if (AssetTypeItems.Num() > 1) 260 | { 261 | auto AssetTypeItem = AssetTypeItems[0].Pin(); 262 | FLargeAssetTypeTreeItem::CreateSharedState( 263 | AssetTypeItems, 264 | InSelectedAssetTypeClassNames.Contains(AssetTypeItem->AssetTypeClassName) 265 | ); 266 | } 267 | } 268 | } 269 | 270 | for (const auto CategoryMapEntry : CategoryMap) 271 | { 272 | AssetCategories.Add(CategoryMapEntry.Value); 273 | } 274 | 275 | TreeView->RequestTreeRefresh(); 276 | } 277 | 278 | void SLargeAssetTypeTreeWidget::SelectAssetTypesByClassName( 279 | const TArray& InAssetTypeClassNames 280 | ) 281 | { 282 | for (const auto AssetCategory : AssetCategories) 283 | { 284 | for (const auto AssetType : AssetCategory->Children) 285 | { 286 | AssetType->SetIsSelected(InAssetTypeClassNames.Contains(AssetType->AssetTypeClassName)); 287 | } 288 | } 289 | } 290 | 291 | void SLargeAssetTypeTreeWidget::GetSelectedAssetTypeClassNames( 292 | TArray& OutAssetTypeClassNames 293 | ) const 294 | { 295 | for (const auto AssetCategory : AssetCategories) 296 | { 297 | for (const auto AssetType : AssetCategory->Children) 298 | { 299 | if (AssetType->IsSelected()) 300 | { 301 | OutAssetTypeClassNames.AddUnique(AssetType->AssetTypeClassName); 302 | } 303 | } 304 | } 305 | } 306 | 307 | TSharedRef SLargeAssetTypeTreeWidget::TreeView_OnGenerateRow( 308 | FLargeAssetTypeTreeItemPtr Item, const TSharedRef& OwnerTable 309 | ) 310 | { 311 | return 312 | SNew(STableRow, OwnerTable) 313 | [ 314 | SNew(SHorizontalBox) 315 | + SHorizontalBox::Slot() 316 | .AutoWidth() 317 | [ 318 | SNew(SCheckBox) 319 | .IsChecked( 320 | this, &SLargeAssetTypeTreeWidget::TreeView_IsChecked, 321 | FLargeAssetTypeTreeItemWeakPtr(Item) 322 | ) 323 | .OnCheckStateChanged( 324 | this, &SLargeAssetTypeTreeWidget::TreeView_OnCheckStateChanged, 325 | FLargeAssetTypeTreeItemWeakPtr(Item) 326 | ) 327 | ] 328 | + SHorizontalBox::Slot() 329 | [ 330 | SNew(STextBlock) 331 | .Text(Item->Title) 332 | .ToolTipText( 333 | !Item->AssetTypeClassName.IsEmpty() ? 334 | FText::FromString(FString(TEXT("Class: ")) + Item->AssetTypeClassName) : FText() 335 | ) 336 | ] 337 | ]; 338 | } 339 | 340 | void SLargeAssetTypeTreeWidget::TreeView_OnGetChildren( 341 | FLargeAssetTypeTreeItemPtr Parent, TArray& OutChildren 342 | ) 343 | { 344 | if (Parent.IsValid()) 345 | { 346 | OutChildren = Parent->Children; 347 | } 348 | } 349 | 350 | ECheckBoxState SLargeAssetTypeTreeWidget::TreeView_IsChecked( 351 | FLargeAssetTypeTreeItemWeakPtr ItemWeakPtr 352 | ) const 353 | { 354 | FLargeAssetTypeTreeItemPtr Item = ItemWeakPtr.Pin(); 355 | if (Item.IsValid()) 356 | { 357 | if (Item->Children.Num() > 0) // asset category item 358 | { 359 | // the checked state of an asset category item is determined by the checked states of 360 | // the contained asset type items 361 | int32 NumberOfSelectedChildren = 0; 362 | for (const auto ChildItem : Item->Children) 363 | { 364 | if (ChildItem->IsSelected()) 365 | { 366 | ++NumberOfSelectedChildren; 367 | } 368 | } 369 | 370 | if (NumberOfSelectedChildren == Item->Children.Num()) 371 | { 372 | return ECheckBoxState::Checked; 373 | } 374 | else if (NumberOfSelectedChildren == 0) 375 | { 376 | return ECheckBoxState::Unchecked; 377 | } 378 | else // some but not all asset type items in this asset category are checked 379 | { 380 | return ECheckBoxState::Undetermined; 381 | } 382 | } 383 | else // asset type item 384 | { 385 | return Item->IsSelected() ? 386 | ECheckBoxState::Checked : ECheckBoxState::Unchecked; 387 | } 388 | } 389 | return ECheckBoxState::Undetermined; 390 | } 391 | 392 | void SLargeAssetTypeTreeWidget::TreeView_OnCheckStateChanged( 393 | ECheckBoxState NewState, FLargeAssetTypeTreeItemWeakPtr ItemWeakPtr) 394 | { 395 | FLargeAssetTypeTreeItemPtr Item = ItemWeakPtr.Pin(); 396 | if (Item.IsValid()) 397 | { 398 | bool bIsItemChecked = (NewState == ECheckBoxState::Checked); 399 | // propagate the checked state of the asset category to the asset types within it 400 | for (const auto ItemChild : Item->Children) 401 | { 402 | ItemChild->SetIsSelected(bIsItemChecked); 403 | } 404 | Item->SetIsSelected(bIsItemChecked); 405 | // the delegate will only be executed once, even if the user checks/unchecks an asset 406 | // category and the checked state of multiple asset type items in that category changes, 407 | // this is by design 408 | OnItemCheckStateChanged.ExecuteIfBound(); 409 | } 410 | } 411 | 412 | #undef LOCTEXT_NAMESPACE 413 | 414 | } // namespace MercurialSourceControl 415 | -------------------------------------------------------------------------------- /Source/MercurialSourceControl/Private/SLargeAssetTypeTreeWidget.h: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------- 2 | // The MIT License (MIT) 3 | // 4 | // Copyright (c) 2014 Vadim Macagon 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in all 14 | // copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | // SOFTWARE. 23 | //------------------------------------------------------------------------------- 24 | #pragma once 25 | 26 | #include "AssetTypeCategories.h" 27 | 28 | namespace MercurialSourceControl { 29 | 30 | typedef TSharedPtr FLargeAssetTypeTreeItemPtr; 31 | typedef TWeakPtr FLargeAssetTypeTreeItemWeakPtr; 32 | 33 | /** A tree view that displays all available asset types grouped by asset categories. */ 34 | class SLargeAssetTypeTreeWidget : public SCompoundWidget 35 | { 36 | public: 37 | /** Invoked when the user checks/unchecks an item in the tree. */ 38 | DECLARE_DELEGATE(FOnItemCheckStateChanged); 39 | 40 | SLATE_BEGIN_ARGS(SLargeAssetTypeTreeWidget) {} 41 | 42 | SLATE_ARGUMENT(TArray, SelectedAssetTypeNames) 43 | SLATE_EVENT(FOnItemCheckStateChanged, OnItemCheckStateChanged) 44 | 45 | SLATE_END_ARGS() 46 | 47 | public: 48 | void Construct(const FArguments& InArgs); 49 | 50 | void GetSelectedAssetTypeClassNames(TArray& OutAssetTypeClassNames) const; 51 | 52 | private: 53 | static void AddCategoriesToCategoryMap( 54 | TMap& OutCategoryMap 55 | ); 56 | 57 | private: 58 | /** Load all the asset categories and get TreeView to redraw itself. */ 59 | void Populate(const TArray& InSelectedAssetTypeClassNames); 60 | 61 | /** Check the items in TreeView that match the given asset type class names. */ 62 | void SelectAssetTypesByClassName(const TArray& InAssetTypeClassNames); 63 | 64 | /** Called by TreeView to generate a table row for the given item. */ 65 | TSharedRef TreeView_OnGenerateRow( 66 | FLargeAssetTypeTreeItemPtr Item, const TSharedRef& OwnerTable 67 | ); 68 | 69 | /** Called by TreeView to obtain child items for the given parent item. */ 70 | void TreeView_OnGetChildren( 71 | FLargeAssetTypeTreeItemPtr Parent, TArray& OutChildren 72 | ); 73 | 74 | /** Called by TreeView to obtain the checked state of the given item. */ 75 | ECheckBoxState TreeView_IsChecked(FLargeAssetTypeTreeItemWeakPtr ItemWeakPtr) const; 76 | 77 | /** Called by TreeView when an item is checked or unchecked. */ 78 | void TreeView_OnCheckStateChanged( 79 | ECheckBoxState NewState, FLargeAssetTypeTreeItemWeakPtr ItemWeakPtr 80 | ); 81 | 82 | private: 83 | typedef STreeView SLargeAssetTypeTreeView; 84 | TSharedPtr TreeView; 85 | 86 | /** Asset categories are the top-level items in the tree. */ 87 | TArray AssetCategories; 88 | 89 | /** Delegate to execute when the user checks/unchecks an item in the tree. */ 90 | FOnItemCheckStateChanged OnItemCheckStateChanged; 91 | }; 92 | 93 | } // namespace MercurialSourceControl 94 | -------------------------------------------------------------------------------- /Source/MercurialSourceControl/Private/SMercurialSourceControlSettingsWidget.cpp: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------- 2 | // The MIT License (MIT) 3 | // 4 | // Copyright (c) 2014 Vadim Macagon 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in all 14 | // copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | // SOFTWARE. 23 | //------------------------------------------------------------------------------- 24 | 25 | #include "MercurialSourceControlPrivatePCH.h" 26 | #include "SMercurialSourceControlSettingsWidget.h" 27 | #include "DesktopPlatformModule.h" 28 | #include "MercurialSourceControlModule.h" 29 | #include "MercurialSourceControlClient.h" 30 | #include "SLargeAssetTypeTreeWidget.h" 31 | 32 | namespace MercurialSourceControl { 33 | 34 | #define LOCTEXT_NAMESPACE "MercurialSourceControl.SSettingsWidget" 35 | 36 | void SProviderSettingsWidget::Construct(const FArguments& InArgs) 37 | { 38 | const FProviderSettings& ProviderSettings = FModule::GetProvider().GetSettings(); 39 | FString MercurialPath = ProviderSettings.GetMercurialPath(); 40 | if (MercurialPath.IsEmpty()) 41 | { 42 | FClient::FindExecutable(MercurialPath); 43 | } 44 | MercurialPathText = FText::FromString(MercurialPath); 45 | bEnableLargefilesIntegration = ProviderSettings.IsLargefilesIntegrationEnabled(); 46 | TArray LargeAssetTypes; 47 | ProviderSettings.GetLargeAssetTypes(LargeAssetTypes); 48 | 49 | FSlateFontInfo TextFont = FEditorStyle::GetFontStyle(TEXT("SourceControl.LoginWindow.Font")); 50 | 51 | ChildSlot 52 | [ 53 | SNew(SVerticalBox) 54 | // Mercurial Executable 55 | +SVerticalBox::Slot() 56 | .AutoHeight() 57 | [ 58 | SNew(SHorizontalBox) 59 | +SHorizontalBox::Slot() 60 | .FillWidth(1.0f) 61 | [ 62 | SNew(SVerticalBox) 63 | +SVerticalBox::Slot() 64 | .FillHeight(1.0f) 65 | .Padding(2.0f) 66 | .VAlign(VAlign_Center) 67 | [ 68 | SNew(STextBlock) 69 | .Text(LOCTEXT("MercurialExecutableLabel", "Mercurial Executable")) 70 | .ToolTipText( 71 | LOCTEXT( 72 | "MercurialExecutableLabel_ToolTip", "Path to Mercurial executable (hg.exe)" 73 | ) 74 | ) 75 | .Font(TextFont) 76 | ] 77 | ] 78 | +SHorizontalBox::Slot() 79 | .FillWidth(2.0f) 80 | [ 81 | SNew(SVerticalBox) 82 | +SVerticalBox::Slot() 83 | .FillHeight(1.0f) 84 | .Padding(2.0f) 85 | [ 86 | SNew(SHorizontalBox) 87 | +SHorizontalBox::Slot() 88 | .FillWidth(3.0f) 89 | [ 90 | SNew(SEditableTextBox) 91 | .Text(this, &SProviderSettingsWidget::GetMercurialPathText) 92 | .OnTextCommitted(this, &SProviderSettingsWidget::MercurialPath_OnTextCommitted) 93 | .OnTextChanged( 94 | this, &SProviderSettingsWidget::MercurialPath_OnTextCommitted, 95 | ETextCommit::Default 96 | ) 97 | .Font(TextFont) 98 | ] 99 | +SHorizontalBox::Slot() 100 | .FillWidth(1.0f) 101 | .Padding(5.0f, 0.0f, 0.0f, 0.0f) 102 | [ 103 | SNew(SButton) 104 | .HAlign(HAlign_Center) 105 | .VAlign(VAlign_Center) 106 | .Text(LOCTEXT("MercurialExecutableBrowseButtonLabel", "Browse")) 107 | .OnClicked(this, &SProviderSettingsWidget::MercurialPathBrowse_OnClicked) 108 | ] 109 | ] 110 | ] 111 | ] 112 | // Enable Largefiles Integration Checkbox 113 | +SVerticalBox::Slot() 114 | .AutoHeight() 115 | [ 116 | SNew(SCheckBox) 117 | .IsChecked(EnableLargefilesIntegration_IsChecked()) 118 | .OnCheckStateChanged(this, &SProviderSettingsWidget::EnableLargefilesIntegration_OnCheckStateChanged) 119 | .ToolTipText( 120 | LOCTEXT( 121 | "EnableLargefiles_Tooltip", 122 | "When enabled the editor will always flag certain asset types as \"large\" when adding them to a repository." 123 | ) 124 | ) 125 | [ 126 | SNew(STextBlock) 127 | .Text(LOCTEXT("EnableLargefilesLabel", "Enable Largefiles Integration")) 128 | .Font(TextFont) 129 | ] 130 | ] 131 | // Largefiles Integration Settings 132 | +SVerticalBox::Slot() 133 | .AutoHeight() 134 | [ 135 | // wrap the asset type tree in a fixed height box to prevent excessive 136 | // flickering when toggling the visibility of this section 137 | SAssignNew(LargefilesSettingsBox, SBox) 138 | .Padding(FMargin(0.0f, 10.0f)) 139 | .HeightOverride(400.0f) 140 | .Visibility(GetLargeAssetTypeTreeVisibility()) 141 | [ 142 | SNew(SHorizontalBox) 143 | + SHorizontalBox::Slot() 144 | .FillWidth(1.0f) 145 | [ 146 | SNew(STextBlock) 147 | .Text( 148 | LOCTEXT( 149 | "LargeAssetTypeTreeLabel", 150 | "Select the asset types the editor should always mark as \"large\" when adding them to a repository." 151 | ) 152 | ) 153 | .Font(TextFont) 154 | .AutoWrapText(true) 155 | ] 156 | + SHorizontalBox::Slot() 157 | .FillWidth(2.0f) 158 | [ 159 | SNew(SBorder) 160 | .BorderImage(FEditorStyle::GetBrush("DetailsView.CategoryMiddle")) 161 | .Padding(FMargin(5.0f)) 162 | [ 163 | SAssignNew(LargeAssetTypeTreeWidget, SLargeAssetTypeTreeWidget) 164 | .SelectedAssetTypeNames(LargeAssetTypes) 165 | .OnItemCheckStateChanged( 166 | this, 167 | &SProviderSettingsWidget::LargeAssetTypeTree_OnItemCheckStateChanged 168 | ) 169 | ] 170 | ] 171 | ] 172 | ] 173 | ]; 174 | } 175 | 176 | FText SProviderSettingsWidget::GetMercurialPathText() const 177 | { 178 | return MercurialPathText; 179 | } 180 | 181 | void SProviderSettingsWidget::MercurialPath_OnTextCommitted( 182 | const FText& InText, ETextCommit::Type InCommitType 183 | ) 184 | { 185 | FProviderSettings& Settings = FModule::GetProvider().GetSettings(); 186 | Settings.SetMercurialPath(InText.ToString()); 187 | Settings.Save(); 188 | // update the backing field for the SEditableTextBox 189 | MercurialPathText = InText; 190 | } 191 | 192 | FReply SProviderSettingsWidget::MercurialPathBrowse_OnClicked() 193 | { 194 | IDesktopPlatform* DesktopPlatform = FDesktopPlatformModule::Get(); 195 | if (DesktopPlatform) 196 | { 197 | FString MercurialPath = MercurialPathText.ToString(); 198 | 199 | #ifdef PLATFORM_WINDOWS 200 | const FString Filter(TEXT("Executable files (*.exe;*.bat;*.cmd)|*.exe;*.bat;*.cmd")); 201 | #else 202 | const FString Filter(TEXT("All files (*.*)|*.*")); 203 | #endif 204 | 205 | TArray SelectedFiles; 206 | 207 | bool bFileSelected = DesktopPlatform->OpenFileDialog( 208 | nullptr, 209 | LOCTEXT("ChooseExecutableDialogTitle", "Choose a Mercurial executable").ToString(), 210 | *FPaths::GetPath(MercurialPath), TEXT(""), Filter, EFileDialogFlags::None, SelectedFiles 211 | ); 212 | 213 | if (bFileSelected) 214 | { 215 | check(SelectedFiles.Num() == 1); 216 | 217 | MercurialPath = FPaths::ConvertRelativePathToFull(SelectedFiles[0]); 218 | if (FClient::IsValidExecutable(MercurialPath)) 219 | { 220 | FProviderSettings& Settings = FModule::GetProvider().GetSettings(); 221 | Settings.SetMercurialPath(MercurialPath); 222 | Settings.Save(); 223 | // update the backing field for the SEditableTextBox 224 | MercurialPathText = FText::FromString(MercurialPath); 225 | } 226 | else 227 | { 228 | FMessageDialog::Open( 229 | EAppMsgType::Ok, 230 | LOCTEXT( 231 | "WrongExecutablePath", 232 | "The file you selected is not a Mercurial executable." 233 | ) 234 | ); 235 | } 236 | } 237 | } 238 | return FReply::Handled(); 239 | } 240 | 241 | ECheckBoxState SProviderSettingsWidget::EnableLargefilesIntegration_IsChecked() const 242 | { 243 | return bEnableLargefilesIntegration ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; 244 | } 245 | 246 | EVisibility SProviderSettingsWidget::GetLargeAssetTypeTreeVisibility() const 247 | { 248 | return bEnableLargefilesIntegration ? EVisibility::Visible : EVisibility::Collapsed; 249 | } 250 | 251 | void SProviderSettingsWidget::EnableLargefilesIntegration_OnCheckStateChanged( 252 | ECheckBoxState NewState 253 | ) 254 | { 255 | bEnableLargefilesIntegration = (NewState == ECheckBoxState::Checked); 256 | 257 | if (LargefilesSettingsBox.IsValid()) 258 | { 259 | LargefilesSettingsBox->SetVisibility(GetLargeAssetTypeTreeVisibility()); 260 | } 261 | 262 | FProviderSettings& Settings = FModule::GetProvider().GetSettings(); 263 | Settings.EnableLargefilesIntegration(bEnableLargefilesIntegration); 264 | Settings.Save(); 265 | } 266 | 267 | void SProviderSettingsWidget::LargeAssetTypeTree_OnItemCheckStateChanged() 268 | { 269 | TArray LargeAssetTypes; 270 | LargeAssetTypeTreeWidget->GetSelectedAssetTypeClassNames(LargeAssetTypes); 271 | 272 | FProviderSettings& Settings = FModule::GetProvider().GetSettings(); 273 | Settings.SetLargeAssetTypes(LargeAssetTypes); 274 | Settings.Save(); 275 | } 276 | 277 | #undef LOCTEXT_NAMESPACE 278 | 279 | } // namespace MercurialSourceControl 280 | -------------------------------------------------------------------------------- /Source/MercurialSourceControl/Private/SMercurialSourceControlSettingsWidget.h: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------- 2 | // The MIT License (MIT) 3 | // 4 | // Copyright (c) 2014 Vadim Macagon 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in all 14 | // copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | // SOFTWARE. 23 | //------------------------------------------------------------------------------- 24 | #pragma once 25 | 26 | namespace MercurialSourceControl { 27 | 28 | /** Slate widget that displays settings for the Mercurial source control provider. */ 29 | class SProviderSettingsWidget : public SCompoundWidget 30 | { 31 | public: 32 | SLATE_BEGIN_ARGS(SProviderSettingsWidget) {} 33 | 34 | SLATE_END_ARGS() 35 | 36 | public: 37 | void Construct(const FArguments& InArgs); 38 | 39 | private: 40 | FText GetMercurialPathText() const; 41 | void MercurialPath_OnTextCommitted(const FText& InText, ETextCommit::Type InCommitType); 42 | FReply MercurialPathBrowse_OnClicked(); 43 | EVisibility GetLargeAssetTypeTreeVisibility() const; 44 | ECheckBoxState EnableLargefilesIntegration_IsChecked() const; 45 | void EnableLargefilesIntegration_OnCheckStateChanged(ECheckBoxState NewState); 46 | void LargeAssetTypeTree_OnItemCheckStateChanged(); 47 | 48 | private: 49 | FText MercurialPathText; 50 | bool bEnableLargefilesIntegration; 51 | TSharedPtr LargefilesSettingsBox; 52 | TSharedPtr LargeAssetTypeTreeWidget; 53 | }; 54 | 55 | } // namespace MercurialSourceControl 56 | --------------------------------------------------------------------------------