Editor · guide
The source-control service
What a plugin can ask the project's version control by asking for ISourceControl — the status of a path, a file's history and diffs, and Revert and Restore — and when its answers arrive.
Edit this page on GitHubDocuments
What it is
ISourceControl is the editor's source-control context as one service. The editor publishes it
through PluginServices, and the content browser's status column, Assets ▸ Revert to Source Control,
Assets ▸ Show History and the Revisions panel all use the same object:
| Member | What it does |
|---|---|
Name / IsSought | The provider's name ("Git"), or null. Once IsSought is true, a null name means the project is not in a working tree. |
IsKnown / Status(path) | Whether a status sweep has landed, and what it said about a project-relative path. A folder takes the strongest status under it. |
Sweep() / Swept | Asks for a fresh sweep of the whole working tree, and the event raised when one lands. |
HistoryAsync(path, limit) / DiffAsync(path, revision) | A file's commits, newest first, and what changed at one of them. An empty revision means the working tree's own changes. |
Revert(paths) / Restore(path, revision) / Reverted | Throw a file's changes away, or write an old version over it. Reverted is raised when the project has been rescanned afterwards. |
SourceControlStatus has six answers plus Unknown. Unknown draws nothing, and it is what every
path says before the first sweep and in a project with no provider. ⚠ It is never rounded down to
"unmodified": a status column that is sometimes right is worse than none.
SourceControlRevision carries both the full id, which is what DiffAsync and Restore take, and
the short one a person reads. SourceControlDiff.IsText says whether Text is a patch, or a
sentence about a binary file and how its size changed.
What it is for
Doc 50's rule is that a use case is a method on a service, and a menu line only names one. Before
this service existed, the provider, the sweep and the revisions panel were private to the editor
application. The sweep also ran only once the content browser had been opened, so Revert and Show
History stayed greyed in a git project until somebody opened the Project tab. Those verbs now belong
to the editor's SourceControlModule, which is a module over this service.
The provider is found lazily, on the thread pool, by the first sweep. The editor takes that sweep
when it starts, and another on every IProjectSession.Rescanned: when the file
watcher drains, on Refresh, and after a revert. So IsKnown does not depend on any panel being open.
⚠ Every provider call runs on the thread pool. Git is a process, and it can take seconds on a
cold cache or a locked index. Swept and Reverted are raised on the frame thread.
HistoryAsync and DiffAsync complete wherever the provider finishes, so a module that touches its
UI with the answer has to get back onto the frame thread first.
⚠ Revert and Restore do not ask, and neither can be undone. Both write files on disk, and the
undo stack holds documents. The editor's own commands ask first and name the files in the question.
A script putting back files it generated has nobody to ask. Restore leaves an ordinary uncommitted
change behind, and Revert undoes it.
What it deliberately does not do:
- Commit, branch or pull. Those are a git client's job. The editor reports the working tree and puts single files back.
- Watch or poll. If a cache refreshed on a timer, an external checkout would leave it wrong. The sweep follows the rescans instead, since those are already the moments the project changed.
- Take a provider from a plugin. Git, through the command-line client, is the only provider. The provider seam stays internal until a second one is wanted.
Using it
Ask for it in Activate, the way any module asks for a service:
public void Activate(PluginContext context) { var sourceControl = context.Services.Require<ISourceControl>(); var project = context.Services.Require<EditorProject>(); context.AddCommand( new EditorCommand("mine.show-modified", new StringId("mine.show-modified", "Select Modified"), () => project.Selection.Set(project.Assets.Entries .Where(entry => sourceControl.Status(entry.Path) == SourceControlStatus.Modified) .Select(entry => entry.Guid))) { Enablement = () => sourceControl.IsKnown });}Examples
Following the sweeps, for a panel that draws its own marks:
sourceControl.Swept += () => marks.Refresh();See also
- The content library — the same shape for the project's assets.
- The project session — the rescans a sweep follows.
- Writing a plugin —
Require,Ownsand the registration scope.