Skip to main content

Understanding the VCS Plugin Interface in Unreal Engine

Unreal Engine comes with built-in integration for version control systems (VCS) like Git and Perforce. You can right-click assets in the Content Browser to perform operations like "Check Out" and "Submit", and the connection status is displayed in the status bar at the bottom-right of the editor.

So how is this functionality implemented? If you want to create a custom VCS plugin, how much does the engine support you?

This article explores the engine source code to provide a detailed explanation of the interfaces for VCS plugin development.

Target Audience

  • Those who want to develop custom VCS plugins
  • Those who want to understand the behavior of existing VCS plugins
  • Those interested in UE's modular design

This article assumes basic understanding of C++ and UE's module system.

Environment

  • Unreal Engine 5.7

This article explains the engine source code and is not intended as a best practices guide. When developing actual plugins, please also refer to Epic's official documentation.

Overall Architecture

Layer Structure

UE's VCS integration consists of four layers.

The key point of this design is loose coupling through abstraction.

The editor doesn't know which VCS is being used. It simply requests operations from the provider obtained via ISourceControlModule::Get().GetProvider(). Whether it's Git or Perforce depends on that provider's implementation.

Source Code Locations

PathContents
Engine/Source/Developer/SourceControl/Core module (interface definitions)
Engine/Plugins/Developer/GitSourceControl/Git plugin
Engine/Plugins/Developer/PerforceSourceControl/Perforce plugin
Engine/Plugins/Developer/SubversionSourceControl/SVN plugin
Engine/Plugins/Developer/PlasticSourceControl/Plastic SCM plugin

The core module consists of about 45 files, and the Git plugin has about 20 files. The Git plugin is the simplest implementation, so it's recommended as a reference for plugin development.

Core Interfaces

There are 6 interfaces that make up a VCS plugin. They work together to bridge the editor and the VCS.

Let's first get an overview.

InterfaceIn a NutshellImplement for Plugin Development?
ISourceControlModuleCommand centerNo (provided by engine)
ISourceControlProviderGateway to VCSRequired
ISourceControlStateFile stateRequired
ISourceControlOperationOperation definitionAs needed
ISourceControlChangelistGroup of changesIf VCS supports it
ISourceControlRevisionSingle history entryIf implementing history

[Command Center] ISourceControlModule

File: Public/ISourceControlModule.h

This interface is the "command center" of the entire source control system. Plugin developers don't implement this—it's provided by the engine. However, understanding its role helps you see how your plugin gets called.

What It Does

ISourceControlModule has three main responsibilities.

1. Provider Management

In the editor, you can select which VCS to use from Editor Preferences. By default, Git, Perforce, SVN, Plastic SCM, and None (disabled) are available, but this list isn't fixed.

Any provider registered with IModularFeatures as "SourceControl" appears in this selection list. This means if you create a custom plugin implementing the interfaces explained in this article, it will also appear as an option.

When a user selects a provider, SetProvider("provider name") is called internally, and all subsequent VCS operations are delegated to that provider.

2. Operation Relay

VCS operation requests come from various parts of the editor (Content Browser, Level Editor, Material Editor, etc.). All of these go through ISourceControlModule::Get().GetProvider() to reach the currently active provider.

This means editor-side code doesn't need to know which VCS is being used. It just requests operations from whatever GetProvider() returns.

3. Background Update Management

When you have the Content Browser open, VCS status (checked out, modified, etc.) is displayed on file icons. However, querying the server every time would impact performance.

ISourceControlModule has a mechanism to "update only stale states". When QueueStatusUpdate() is called, it checks if more than 5 minutes have passed since the file's state was last updated. If so, the file is added to an update queue and processed asynchronously in batches during Tick().

Rather than "polling periodically", the design is "efficiently updating only stale items among requested files".

Relevance to Plugin Developers

Plugin developers don't implement ISourceControlModule directly. However, you should remember the following:

  • Your provider is called via GetProvider()
  • Tick() is called every frame to check for async operation completion
  • Init() and Close() are called when switching providers

[Gateway to VCS] ISourceControlProvider

File: Public/ISourceControlProvider.h

This is the core of plugin development. By implementing this interface, you can integrate any VCS into the UE editor.

There are about 30 methods, but they can be classified into 5 major categories.

Lifecycle Management

Providers have a lifecycle "from birth to death".

Init() is called when the user selects this provider. Here you attempt to connect to the VCS server or verify the local repository exists. If the bForceConnection argument is true, connection is attempted immediately; if false, it waits until the user explicitly connects via the login dialog.

Close() is called when switching to another provider or when the editor exits. Close connections and release resources.

In the Git plugin, Init() checks for the git command's existence and whether the project directory is a Git repository.

State Queries

GetState() answers questions like "Is this file under VCS control?", "Has someone checked it out?", "Is it up to date?".

The important thing here is the EStateCacheUsage parameter.

ISourceControlProvider.h
namespace EStateCacheUsage
{
enum Type
{
ForceUpdate, // Always query the server
Use, // Use cache if available
};
}

VCS queries often go over the network and take time. If you had to fetch status for hundreds of files every time you open a folder in the Content Browser, it would be unusable.

Therefore, providers maintain an internal state cache. When EStateCacheUsage::Use is specified, the cache is returned; only when ForceUpdate is specified does it actually query the VCS.

Operation Execution

Execute() is the method that performs operations like check out, check in, revert, and sync.

A distinctive aspect of Execute()'s design is that operation types are represented as subclasses of ISourceControlOperation. There are classes like FCheckOut, FCheckIn, and FSync, each holding operation-specific parameters.

The provider identifies the operation type within Execute() and performs the appropriate processing. The Git plugin uses a worker pattern to delegate each operation to separate classes (explained later).

Workflow Characteristic Declaration

Different VCS have different "ways of working". Perforce requires checkout before editing, but Git allows editing anytime. Perforce has changelists, but Git doesn't.

"Workflow characteristic methods" absorb these differences.

MethodQuestion
UsesCheckout()Is checkout required before editing?
UsesChangelists()Does it have the concept of changelists?
UsesLocalReadOnlyState()Make unchecked-out files read-only?
UsesFileRevisions()Are there per-file revisions?
UsesSnapshots()Does it have the concept of snapshots (tags)?

The editor UI checks these return values to adjust what items to display. For example, with providers where UsesCheckout() returns false, the behavior of the "Check Out" menu item changes.

In the Git plugin, most of these methods return false. Git is a distributed VCS and doesn't have server-based locking mechanisms like Perforce.

FGitSourceControlProvider.cpp
// FGitSourceControlProvider
bool UsesCheckout() const override { return false; }
bool UsesChangelists() const override { return false; }
bool UsesLocalReadOnlyState() const override { return false; }

UI Provision

MakeSettingsWidget() returns the settings UI displayed in the login dialog. Here users enter server addresses, usernames, passwords, etc.

In the Git plugin, UI for setting the Git binary path and repository root directory is displayed. In the Perforce plugin, there are input fields for server address, username, workspace name, etc.

[Communicating File Status] ISourceControlState

File: Public/ISourceControlState.h

This interface represents the VCS state of a single file. It's the source of information for the small badges displayed in the bottom-right of file icons in the Content Browser.

State Representation

ISourceControlState answers three main types of questions.

"What's the current state of this file?"

  • IsCheckedOut(): I have it checked out
  • IsCheckedOutOther(): Someone else has it checked out
  • IsAdded(): Marked as newly added
  • IsDeleted(): Marked for deletion
  • IsModified(): Has changes
  • IsConflicted(): In conflict
  • IsCurrent(): Synced with latest version

"What can I do with this file?"

  • CanCheckout(): Can check out
  • CanCheckIn(): Can check in
  • CanRevert(): Can revert
  • CanAdd(): Can add to VCS
  • CanDelete(): Can delete
  • CanEdit(): Can edit

"What's the display information?"

  • GetDisplayName(): Display name of the state (e.g., "Checked Out")
  • GetDisplayTooltip(): Tooltip text
  • GetIcon(): Icon indicating the state

Design to Absorb VCS Differences

What's interesting here is that "checkout" means completely different things in Git and Perforce.

In the Perforce world, checkout means "acquiring editing rights". You declare to the server before editing a file and lock it so others can't edit it simultaneously.

In the Git world, this concept doesn't exist. Files can be edited anytime, and conflicts are resolved at merge time.

So what should the Git plugin's IsCheckedOut() return?

The Git plugin treats "having changes in the working copy" as the checked-out state. Files that show as modified in git status have IsCheckedOut() == true.

FGitSourceControlState.cpp
bool FGitSourceControlState::IsCheckedOut() const
{
return WorkingCopyState == EWorkingCopyState::Added
|| WorkingCopyState == EWorkingCopyState::Deleted
|| WorkingCopyState == EWorkingCopyState::Modified
|| WorkingCopyState == EWorkingCopyState::Renamed
|| WorkingCopyState == EWorkingCopyState::Copied;
}

This is "semantic translation". Perforce's "checked out" and Git's "modified" are technically different, but their meaning to the user ("this file is being worked on") is the same.

Thread Safety

ISourceControlState inherits from TSharedFromThis<ISourceControlState, ESPMode::ThreadSafe>. This is because state objects may be referenced from multiple threads.

VCS queries may be performed on background threads, and state objects are updated as a result. Meanwhile, the UI thread reads that state to draw icons. Thread-safe reference counting is used so both can operate safely.

[Abstracting Operations] ISourceControlOperation

File: Public/ISourceControlOperation.h

"I want to check out", "I want to check in", "I want to sync"—ISourceControlOperation abstracts these operations.

Why Make Operations Classes?

Simply thinking, you might expect methods like Provider->CheckOut(Files). However, this has the following problems:

  1. Different parameters for each operation: Check in needs a description, sync needs revision specification, etc.
  2. Result retrieval can't be unified: Handling of error messages, warnings, and success messages would vary by operation
  3. Hard to add new operations: Adding methods to the provider interface requires modifying all implementations

Representing operations as classes solves these problems.

Operation Execution Pattern

Typical code to execute an operation looks like this:

Operation execution example
// Create check in operation
TSharedRef<FCheckIn> CheckInOp = ISourceControlOperation::Create<FCheckIn>();
CheckInOp->SetDescription(FText::FromString(TEXT("Bug fix")));

// Execute
ECommandResult::Type Result = Provider.Execute(
CheckInOp,
nullptr, // Changelist
FilesToCheckIn,
EConcurrency::Synchronous
);

// Check result
if (Result == ECommandResult::Succeeded)
{
FText SuccessMsg = CheckInOp->GetSuccessMessage();
}
else
{
const FSourceControlResultInfo& ResultInfo = CheckInOp->GetResultInfo();
for (const FText& Error : ResultInfo.ErrorMessages)
{
// Error handling
}
}

The operation object functions as a container carrying both input parameters (SetDescription) and output results (GetSuccessMessage, GetResultInfo).

[Grouping Changes] ISourceControlChangelist

File: Public/ISourceControlChangelist.h

A changelist is a concept for grouping multiple file changes together. It's a standard feature in Perforce but doesn't exist in Git or SVN.

This interface is very simple, with only methods for getting identifiers and checking if deletion is allowed.

ISourceControlChangelist.h
class ISourceControlChangelist
{
public:
virtual bool CanDelete() const { return true; }
virtual FString GetIdentifier() const { return TEXT(""); }
virtual bool IsDefault() const { return false; }
};

In the Git plugin, this interface is barely used. Since UsesChangelists() returns false, the editor UI doesn't display changelist-related features.

[A Page of History] ISourceControlRevision

File: Public/ISourceControlRevision.h

This represents a single entry in a file's change history. It holds information about "who", "when", and "what" was changed.

When implementing history features, this interface is returned from ISourceControlState::GetHistoryItem().

The main methods are:

  • GetRevision(): Revision identifier (commit hash or CL number)
  • GetUserName(): Who made the change
  • GetDate(): When it was changed
  • GetDescription(): Commit message
  • Get(): Get the file contents at this revision

The Get() method retrieves the file at the specified revision as a temporary file. It's used for diff display and merge tools.

Operation Classes

File: Public/SourceControlOperations.h

Why Represent Operations as Classes

In the VCS plugin system, individual operations like "check out" and "check in" are defined as independent classes. At first glance, it might seem like preparing methods like Provider->CheckOut(Files) would suffice, but that's not actually the case.

Parameter Diversity

VCS operations require different information for each.

Check in needs a "commit message". Sync needs specification of "which revision to sync to". Revert has an option for "soft revert (keep changes) or hard revert (completely restore)".

If you tried to pass all of these as method arguments, the number of arguments would become enormous. Also, since different VCS have different options, the code would be full of NULL checks.

By making operations classes, each operation can have only the parameters it needs as member variables. The FCheckIn class has SetDescription(), and the FSync class has SetRevision(). The caller only needs to set the necessary parameters.

Unifying Result Retrieval

VCS operation results aren't just "success/failure". If check in succeeds, you want to know "what revision was created", and if it fails, you need error messages about "why it failed".

Operation classes function as containers carrying both input parameters and output results. The FCheckIn class's GetSuccessMessage() returns a message like "Changelist 12345 submitted" after successful check in. Error messages and warnings can be obtained from GetResultInfo().

Ensuring Extensibility

When new operations are needed in the future, adding methods to the provider interface requires modifying all existing implementations. However, with the approach of adding operation classes, existing providers can treat "unknown operations as unsupported" without affecting existing code.

Operation Class Categories

The approximately 30 operation classes can be divided into 4 major categories.

Basic File Operations

Operations used in daily version control work.

Operation ClassPurpose
FConnectEstablish connection to VCS server
FUpdateStatusGet/update file VCS status
FCheckOutMake file editable (Perforce-style lock acquisition)
FCheckInCommit changes to repository
FMarkForAddAdd new file to VCS control
FDeleteMark file for deletion
FRevertDiscard local changes and restore to repository state
FSyncGet latest version (or specified revision) from repository
FCopyCopy file (choose whether to maintain history)
FResolveMark conflict state as resolved

These are basic operations expected to be implemented by any VCS plugin. The Git plugin has workers registered for 9 operations (Connect, UpdateStatus, MarkForAdd, Delete, Revert, Sync, CheckIn, Copy, Resolve).

Changelist Operations

Operations for VCS with "changelist" concepts like Perforce.

Operation ClassPurpose
FGetPendingChangelistsGet list of unsubmitted changelists
FGetSubmittedChangelistsGet list of submitted changelists
FNewChangelistCreate new changelist
FDeleteChangelistDelete empty changelist
FEditChangelistEdit changelist description
FMoveToChangelistMove file to different changelist
FUpdatePendingChangelistsStatusUpdate changelist status

The Git plugin doesn't implement these. Since UsesChangelists() returns false, the editor UI doesn't call these operations.

Shelve Operations

Operations for the feature of "temporarily saving work-in-progress changes to the server and retrieving them later".

Operation ClassPurpose
FShelveShelve changes
FUnshelveRetrieve shelved changes
FDeleteShelvedDelete shelved changes

This feature is particularly important in Perforce. It's similar to Git's stash but differs in that it's saved server-side.

Workspace & Utility Operations

Operations for environment configuration and information retrieval.

Operation ClassPurpose
FCreateWorkspaceCreate workspace (client)
FDeleteWorkspaceDelete workspace
FGetWorkspacesGet list of available workspaces
FDownloadFileDownload file from server (without syncing)
FGetFileGet file contents at specific revision
FWhereGet local/remote path mapping for file
FGetFileListGet list of files matching specified pattern

Operation-Specific Parameters

Some operation classes have specific parameters. Plugin developers need to handle these parameters appropriately.

FCheckIn Parameters

The check in operation always receives a commit message. There's also an option to "keep file checked out after check in". This is convenient when you want to continue working.

  • SetDescription() / GetDescription(): Commit message
  • SetKeepCheckedOut() / GetKeepCheckedOut(): Keep checked out state after check in

FSync Parameters

The sync operation can specify which revision to sync to. If not specified, it syncs to the latest version (HEAD).

  • SetRevision() / GetRevision(): Target revision for sync
  • SetHeadRevisionFlag(): Latest version flag
  • SetForce(): Force sync (re-fetch even if already at same revision)

FRevert Parameters

The revert operation has a concept of "soft revert". This mode only clears VCS marks (like checked-out state) while keeping file content changes.

  • SetSoftRevert() / IsSoftRevert(): Is it a soft revert
  • SetRevertAll() / IsRevertAll(): Target all files

FUpdateStatus Parameters

The status update operation can specify exactly what to update. Since updating everything takes time, it's common to get only needed information.

  • SetUpdateHistory(): Also get history information
  • SetGetOpenedOnly(): Target only files being edited
  • SetUpdateModifiedState(): Perform detailed modification state check (expensive)
  • SetCheckingAllFiles(): Hint for full file check (providers can use for optimization)

Synchronous and Asynchronous Execution

VCS operations can be executed in two modes: synchronous (blocking) and asynchronous.

Why Asynchronous Execution is Needed

VCS operations often involve network communication and can take seconds to tens of seconds to complete. You want to avoid the UI freezing during this time.

When saving a file in the editor, check out may happen automatically. With synchronous execution, the entire save operation would be blocked. With asynchronous execution, check out happens in the background, and you're notified when it completes.

Choosing Execution Mode

Specify EConcurrency::Synchronous or EConcurrency::Asynchronous in the Execute() arguments.

Synchronous execution is suitable when you want to use the result immediately. For example, when the user explicitly clicks a "Sync" button and waits for completion.

Asynchronous execution is suitable when you want to process in the background and receive a callback on completion. The callback receives the operation object and result code, allowing handling based on success/failure.

Provider Implementer's Responsibility

To support asynchronous execution, providers need to manage threads. Many plugins adopt the "command queue" pattern.

  1. When an async request is received, create a command object and add it to the queue
  2. A worker thread takes commands from the queue and executes them
  3. After execution completes, call the callback during main thread's Tick()

This mechanism is explained in detail in the "Worker Thread Architecture" section below.

Plugin Registration Mechanism

There are several steps before a VCS plugin "appears in the editor's selection list". This section explains how plugins are registered and recognized by the engine.

What is IModularFeatures

UE has a mechanism called "Modular Features". This is a registry for one module to declare "I have this functionality" and for another module to discover and use it.

VCS plugins use this mechanism to announce their existence to the engine. Specifically, they register their provider object as "SourceControl".

Why Use IModularFeatures

VCS plugins and the engine core don't know about each other directly. The engine's source control module doesn't know the Git plugin exists, and the Git plugin doesn't know the engine's internal implementation.

To coordinate in this "neither knows about the other" state, an intermediary is needed. IModularFeatures plays that role.

  • The Git plugin registers: "I can provide SourceControl functionality"
  • The engine queries: "Give me plugins that provide SourceControl functionality"
  • IModularFeatures bridges the two

With this design, no engine-side code needs to be changed when adding a new VCS plugin.

Plugin Module Responsibilities

VCS plugins are implemented as standard UE modules. Create a module class that implements IModuleInterface, and handle initialization/shutdown in StartupModule() and ShutdownModule().

Processing in StartupModule

When the plugin is loaded, StartupModule() is called. There are two things to do here.

1. Worker Registration

"Workers" (explained later) are classes that handle the actual processing for each operation. The provider registers the mapping between operation names ("Connect", "CheckIn", etc.) and worker factory functions.

For example, the Git plugin registers FGitConnectWorker for the "Connect" operation and FGitUpdateStatusWorker for the "UpdateStatus" operation. This registration ensures that when the engine requests "execute Connect operation", the appropriate worker is called.

2. Modular Feature Registration

Register the provider object with IModularFeatures. The registration name is "SourceControl". This registration allows the engine's source control module to discover this provider.

Processing in ShutdownModule

When the module is unloaded, ShutdownModule() is called. Here you do the reverse.

First, call the provider's Close() to release open connections and resources. Then, unregister from IModularFeatures.

Forgetting to unregister will leave references to non-existent providers, causing crashes.

Engine-Side Discovery Mechanism

The engine's source control module (FSourceControlModule) automatically discovers VCS plugins.

Processing at Startup

In FSourceControlModule::StartupModule(), it sets up monitoring for IModularFeatures registration events. This allows detection of plugins loaded later.

Also, the default provider ("None", i.e., the option to not use VCS) is registered first. Without this, no providers would be available at editor startup, causing an error.

Processing on Provider Registration

When a VCS plugin registers with IModularFeatures, FSourceControlModule receives a notification. Upon notification, it updates the list of registered providers and performs initialization processing as needed.

This mechanism allows the engine to dynamically respond to plugin additions and removals.

Mapping Operations to Workers

"Workers" are an important concept in plugin registration.

What is a Worker

Operation classes (FCheckIn, FSync, etc.) express "what you want to do". Workers implement "how to actually execute that operation".

For example, the FSync operation class represents the intent to "sync files". The Git plugin's FGitSyncWorker performs the specific processing of "executing git pull to update files".

Why Separate Them

Operation classes are common interfaces defined by the engine. The same FSync class is used regardless of VCS type.

On the other hand, workers are implemented independently by each VCS plugin. Git uses git pull, Perforce uses p4 sync, SVN uses svn update—completely different commands need to be executed.

This separation means editor-side code only needs to know about operation classes, and VCS-specific implementation differences are hidden in workers.

Registration Mechanism

Providers have a mapping table of operation names to worker factories. Using the RegisterWorker() method, you register the correspondence between operation names (FName) and delegates that generate workers.

When the provider's Execute() method is called, it first gets the operation name using GetName() on the operation object. Then it looks up the worker factory corresponding to that name in the mapping table and generates a worker instance. Finally, it delegates the actual processing to that worker.

If the operation name isn't found in the mapping table, that operation is treated as "unsupported". This allows plugins to function without implementing all operations.

Flow Until Plugin Recognition

Summarizing the overall flow:

  1. Editor startup: FSourceControlModule is loaded and starts monitoring Modular Features
  2. Default registration: "None" provider is registered
  3. Plugin load: Git plugin etc. are loaded and StartupModule() is called
  4. Worker registration: Workers for each operation are registered with the provider
  5. Modular Feature registration: Provider is registered as "SourceControl"
  6. Engine notification: FSourceControlModule detects the registration and updates the provider list
  7. UI reflection: Appears in provider selection in editor settings

When the user selects a provider, FSourceControlModule::SetProvider() is called and the selected provider's Init() is executed. From then on, all VCS operations are delegated to that provider.

Worker Interface

The actual processing of operations is delegated to "workers". Workers are implementation classes corresponding to each operation (Connect, CheckIn, Sync, etc.) and are called from the provider's Execute().

IGitSourceControlWorker

The Git plugin's worker interface is defined as follows:

IGitSourceControlWorker.h
class IGitSourceControlWorker
{
public:
// Worker identification name ("Connect", "CheckIn", etc.)
virtual FName GetName() const = 0;

// Execute VCS command. May be called from another thread
virtual bool Execute( class FGitSourceControlCommand& InCommand ) = 0;

// Update state cache. Always called from main thread
virtual bool UpdateStates() const = 0;
};

The important thing is the separation of Execute() and UpdateStates(). Since Execute() may be executed on a background thread, it must not directly access the provider's state cache. Instead, results are temporarily stored in the worker's own member variables.

UpdateStates() is always called from the main thread, so this is when writes to the cache are actually performed.

Worker Implementation Example

Let's look at the Git plugin's FGitResolveWorker as an example.

GitSourceControlOperations.cpp
class FGitResolveWorker : public IGitSourceControlWorker
{
public:
virtual FName GetName() const override { return "Resolve"; }
virtual bool Execute(FGitSourceControlCommand& InCommand) override;
virtual bool UpdateStates() const override;

// Temporary storage for results
TArray<FGitSourceControlState> States;
};

bool FGitResolveWorker::Execute(FGitSourceControlCommand& InCommand)
{
// Mark conflict as resolved with git add
TArray<FString> Results;
InCommand.bCommandSuccessful = GitSourceControlUtils::RunCommand(
TEXT("add"), InCommand.PathToGitBinary,
InCommand.PathToRepositoryRoot, TArray<FString>(),
InCommand.Files, Results, InCommand.ErrorMessages);

// Get status and save to States (don't touch cache)
GitSourceControlUtils::RunUpdateStatus(..., States);
return InCommand.bCommandSuccessful;
}

bool FGitResolveWorker::UpdateStates() const
{
// Reflect to cache on main thread
return GitSourceControlUtils::UpdateCachedStates(States);
}

This design achieves thread-safe asynchronous processing without complex locking mechanisms.

UI Elements Controllable from Plugins

How much can VCS plugins influence the editor's UI? This section organizes UI elements controllable from plugins and UI elements commonly provided by the engine.

Plugin-Controlled Areas

There are mainly 3 UI elements that plugin developers can freely customize.

File State Display

With ISourceControlState's GetDisplayName() and GetDisplayTooltip(), you can define the state name and tooltip displayed when hovering over a file in the Content Browser.

The Git plugin can display Git-like terms such as "Modified", "Added", "Deleted", while the Perforce plugin can display terms like "Checked Out", "Marked for Add". Since it's file-level information, expressions matching the VCS characteristics are possible.

Settings Screen

ISourceControlProvider::MakeSettingsWidget() returns provider-specific settings UI. This UI is completely under plugin control, and you can freely build Slate widgets.

The Git plugin can place "Git binary path" and "repository root", while the Perforce plugin can place "server address", "username", "workspace" and other VCS-specific settings. It's also possible to include operation explanations and help information here.

State Icons

With ISourceControlState::GetIcon(), you can specify icons indicating file state. You can choose from UE's prepared icon set or add custom icon resources on the plugin side.

UI Commonly Provided by Engine

Menus, dialogs, progress displays, etc. are provided by the engine as common UI.

Right-click menu items in the Content Browser ("Check Out", "Check In", "Sync", etc.) and toolbar buttons are standardized by the engine to provide a consistent operation experience. Regardless of which VCS plugin is being used, the same menus appear in the same places, so users don't need to relearn operations when switching VCS.

Dialogs

Check in dialogs, sync confirmation dialogs, revert confirmation dialogs, etc. are provided by the engine as common workflows. Plugin developers can focus on operation logic without worrying about dialog implementation.

Progress Messages

Messages like "Checking file(s) into Revision Control..." displayed during operation execution are defined in operation classes (FCheckIn, etc.). This is also commonly managed by the engine, so there's no need to implement it individually on the plugin side.

About Terminology

The engine's UI adopts a common terminology system across VCS.

UE DisplayGit MeaningPerforce Meaning
Submitcommit + pushsubmit
Check Out(automatic)edit / lock
Syncpullsync
Revertcheckout / resetrevert

The terminology is based on the Perforce system, which originates from Perforce being the standard VCS within Epic Games.

For Git users, terms like "Submit" and "Check Out" may be unfamiliar. However, as a plugin developer, you just need to translate these operation requests into VCS-specific commands (git commit, git push, etc.) when received. The correspondence between UI terminology and internal implementation is clearly separated as the plugin's responsibility.

Providing User-Facing Information

If you want to convey VCS-specific information to users, the following methods are effective.

Utilizing Tooltips

With ISourceControlState::GetDisplayTooltip(), you can include VCS-specific terms and states in the detailed description of file status. You can provide more detailed information when users hover their cursor.

Explanations in Settings Screen

In the settings UI created by MakeSettingsWidget(), you can display operation correspondence tables ("Submit = git commit + git push", etc.) and VCS-specific notes.

Documentation

Explaining the correspondence between UE operations and VCS commands in the plugin's documentation or README helps user understanding.

Conclusion

This article explained UE's VCS plugin interface.

Most people probably won't create VCS plugins, but I hope this has helped you learn a little about the integration between the VCS you use daily and UE.

... Want to try making a VCS plugin too? 😄👌