Skip to content

Commit c5381ee

Browse files
committed
Refactor as a service and migrate FetchOrigin
1 parent 2b885bd commit c5381ee

5 files changed

Lines changed: 96 additions & 84 deletions

File tree

src/Files.App/Utils/Git/IVersionControl.cs renamed to src/Files.App/Data/Contracts/IVersionControlService.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,15 @@
44
using LibGit2Sharp;
55
using System.ComponentModel;
66

7-
namespace Files.App.Utils.Git
7+
namespace Files.App.Data.Contracts
88
{
99
/// <summary>
1010
/// Defines a version control abstraction.
1111
/// </summary>
1212
/// <remarks>
1313
/// This interface is intended to decouple the app from a specific backend implementation (e.g. a library such as LibGit2Sharp, or a command-line implementation backed by the <c>git.exe</c> executable).
1414
/// </remarks>
15-
internal interface IVersionControl
15+
public interface IVersionControlService
1616
{
1717
/// <summary>
1818
/// Attempts to locate the root of a version control repository (by walking up the directory hierarchy).

src/Files.App/Data/Models/GitItemModel.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ namespace Files.App.Data.Models
66
/// <summary>
77
/// Represents a model for Git items
88
/// </summary>
9-
internal sealed class GitItemModel
9+
public sealed class GitItemModel
1010
{
1111
/// <summary>
1212
/// Gets or initializes file change kind

src/Files.App/Helpers/Application/AppLifecycleHelper.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Licensed under the MIT License.
33

44
using Files.App.Helpers.Application;
5+
using Files.App.Services.Git;
56
using Files.App.Services.SizeProvider;
67
using Files.App.Utils.Logger;
78
using Files.App.ViewModels.Settings;
@@ -265,6 +266,7 @@ public static IHost ConfigureHost()
265266
.AddSingleton<IStorageArchiveService, StorageArchiveService>()
266267
.AddSingleton<IStorageSecurityService, StorageSecurityService>()
267268
.AddSingleton<IWindowsCompatibilityService, WindowsCompatibilityService>()
269+
.AddSingleton</*IVersionControlService,*/ LibGit2Service>()
268270
// ViewModels
269271
.AddSingleton<MainPageViewModel>()
270272
.AddSingleton<InfoPaneViewModel>()

src/Files.App/Utils/Git/LibGit2.cs renamed to src/Files.App/Services/Git/LibGit2Service.cs

Lines changed: 68 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@
33
using System.Text;
44
using System.Text.RegularExpressions;
55

6-
namespace Files.App.Utils.Git;
6+
namespace Files.App.Services.Git;
77

8-
internal sealed partial class LibGit2 // : IVersionControl
8+
internal sealed partial class LibGit2Service // : IVersionControl
99
{
1010
private const string GIT_RESOURCE_NAME = "Files:https://github.com";
1111
private const string GIT_RESOURCE_USERNAME = "Personal Access Token";
@@ -159,6 +159,72 @@ public async Task<BranchItem[]> GetBranchNames(string? path)
159159
return returnValue;
160160
}
161161

162+
public async void FetchOrigin(string? repositoryPath, CancellationToken cancellationToken = default)
163+
{
164+
if (string.IsNullOrWhiteSpace(repositoryPath))
165+
return;
166+
167+
using var repository = new Repository(repositoryPath);
168+
var signature = repository.Config.BuildSignature(DateTimeOffset.Now);
169+
170+
var token = CredentialsHelpers.GetPassword(GIT_RESOURCE_NAME, GIT_RESOURCE_USERNAME);
171+
if (signature is not null && !string.IsNullOrWhiteSpace(token))
172+
{
173+
_fetchOptions.CredentialsProvider = (url, user, cred)
174+
=> new UsernamePasswordCredentials
175+
{
176+
Username = signature.Name,
177+
Password = token
178+
};
179+
}
180+
181+
MainWindow.Instance.DispatcherQueue.TryEnqueue(() =>
182+
{
183+
IsExecutingGitAction = true;
184+
});
185+
186+
await DoGitOperationAsync<GitOperationResult>(() =>
187+
{
188+
cancellationToken.ThrowIfCancellationRequested();
189+
190+
var result = GitOperationResult.Success;
191+
try
192+
{
193+
foreach (var remote in repository.Network.Remotes)
194+
{
195+
cancellationToken.ThrowIfCancellationRequested();
196+
197+
LibGit2Sharp.Commands.Fetch(
198+
repository,
199+
remote.Name,
200+
remote.FetchRefSpecs.Select(rs => rs.Specification),
201+
_fetchOptions,
202+
"git fetch updated a ref");
203+
}
204+
205+
cancellationToken.ThrowIfCancellationRequested();
206+
}
207+
catch (Exception ex)
208+
{
209+
result = IsAuthorizationException(ex)
210+
? GitOperationResult.AuthorizationError
211+
: GitOperationResult.GenericError;
212+
}
213+
214+
return result;
215+
});
216+
217+
MainWindow.Instance.DispatcherQueue.TryEnqueue(() =>
218+
{
219+
if (cancellationToken.IsCancellationRequested)
220+
// Do nothing because the operation was cancelled and another fetch may be in progress
221+
return;
222+
223+
IsExecutingGitAction = false;
224+
GitFetchCompleted?.Invoke(null, EventArgs.Empty);
225+
});
226+
}
227+
162228
private static bool IsRepoValid(string path)
163229
{
164230
return SafetyExtensions.IgnoreExceptions(() => Repository.IsValid(path));

src/Files.App/Utils/Git/GitHelpers.cs

Lines changed: 23 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Licensed under the MIT License.
33

44
using Files.App.Dialogs;
5+
using Files.App.Services.Git;
56
using LibGit2Sharp;
67
using Microsoft.Extensions.Logging;
78
using Sentry;
@@ -15,25 +16,38 @@ namespace Files.App.Utils.Git
1516
internal static partial class GitHelpers
1617
{
1718
// The implementation of the version control interface; it's hardcoded right now but will be made configurable in the future (#16738)
18-
private static readonly LibGit2 _implementation = new LibGit2(); // TODO: Replace with IVersionControl abstraction when it is complete
19+
private static LibGit2Service _implementation = Ioc.Default.GetRequiredService<LibGit2Service>(); // TODO: Replace with IVersionControl abstraction when it is complete
1920

20-
/// <inheritdoc cref="IVersionControl.GetGitRepositoryPath(string?,string)"/>
21+
/// <inheritdoc cref="IVersionControlService.GetGitRepositoryPath(string?,string)"/>
2122
public static string? GetGitRepositoryPath(string? path, string root) => _implementation.GetGitRepositoryPath(path, root);
2223

23-
/// <inheritdoc cref="IVersionControl.GetOriginRepositoryName(string?)"/>
24+
/// <inheritdoc cref="IVersionControlService.GetOriginRepositoryName(string?)"/>
2425
public static string GetOriginRepositoryName(string? path) => _implementation.GetOriginRepositoryName(path);
2526

26-
/// <inheritdoc cref="IVersionControl.GetBranchNames(string?)"/>
27+
/// <inheritdoc cref="IVersionControlService.GetBranchNames(string?)"/>
2728
public static Task<BranchItem[]> GetBranchNames(string? path) => _implementation.GetBranchNames(path);
2829

29-
/// <inheritdoc cref="IVersionControl.GetRepositoryHead(string?)"/>
30+
/// <inheritdoc cref="IVersionControlService.GetRepositoryHead(string?)"/>
3031
public static Task<BranchItem?> GetRepositoryHead(string? path) => _implementation.GetRepositoryHead(path);
3132

32-
/// <inheritdoc cref="IVersionControl.IsExecutingGitAction"/>
33-
public static bool IsExecutingGitAction
33+
/// <inheritdoc cref="IVersionControlService.FetchOrigin(string?, CancellationToken)"/>
34+
public static async void FetchOrigin(string? repositoryPath, CancellationToken cancellationToken = default) => _implementation.FetchOrigin(repositoryPath, cancellationToken);
35+
36+
/// <inheritdoc cref="IVersionControlService.IsExecutingGitAction"/>
37+
public static bool IsExecutingGitAction => _implementation.IsExecutingGitAction;
38+
39+
/// <inheritdoc cref="IVersionControlService.IsExecutingGitActionChanged"/>
40+
public static event PropertyChangedEventHandler? IsExecutingGitActionChanged
3441
{
35-
get => _implementation.IsExecutingGitAction;
36-
set => _implementation.IsExecutingGitAction = value;
42+
add => _implementation.IsExecutingGitActionChanged += value;
43+
remove => _implementation.IsExecutingGitActionChanged -= value;
44+
}
45+
46+
/// <inheritdoc cref="IVersionControlService.GitFetchCompleted"/>
47+
public static event EventHandler? GitFetchCompleted
48+
{
49+
add => _implementation.GitFetchCompleted += value;
50+
remove => _implementation.GitFetchCompleted -= value;
3751
}
3852

3953
#region Legacy implementation
@@ -79,9 +93,6 @@ public static bool IsExecutingGitAction
7993
// Property already moved into abstraction
8094
private static readonly SemaphoreSlim GitOperationSemaphore = new SemaphoreSlim(1, 1);
8195

82-
// Event handler already moved into abstraction
83-
public static event EventHandler? GitFetchCompleted;
84-
8596
public static async Task<bool> Checkout(string? repositoryPath, string? branch)
8697
{
8798
SentrySdk.Experimental.Metrics.EmitCounter("Triggered git checkout", 1);
@@ -243,7 +254,6 @@ await DoGitOperationAsync<GitOperationResult>(() =>
243254
_implementation.IsExecutingGitAction = false;
244255
}
245256

246-
247257
public static bool ValidateBranchNameForRepository(string branchName, string repositoryPath)
248258
{
249259
if (string.IsNullOrEmpty(branchName) || !IsRepoValid(repositoryPath))
@@ -258,72 +268,6 @@ public static bool ValidateBranchNameForRepository(string branchName, string rep
258268
branch.FriendlyName.Equals(branchName, StringComparison.OrdinalIgnoreCase));
259269
}
260270

261-
public static async void FetchOrigin(string? repositoryPath, CancellationToken cancellationToken = default)
262-
{
263-
if (string.IsNullOrWhiteSpace(repositoryPath))
264-
return;
265-
266-
using var repository = new Repository(repositoryPath);
267-
var signature = repository.Config.BuildSignature(DateTimeOffset.Now);
268-
269-
var token = CredentialsHelpers.GetPassword(GIT_RESOURCE_NAME, GIT_RESOURCE_USERNAME);
270-
if (signature is not null && !string.IsNullOrWhiteSpace(token))
271-
{
272-
_fetchOptions.CredentialsProvider = (url, user, cred)
273-
=> new UsernamePasswordCredentials
274-
{
275-
Username = signature.Name,
276-
Password = token
277-
};
278-
}
279-
280-
MainWindow.Instance.DispatcherQueue.TryEnqueue(() =>
281-
{
282-
_implementation.IsExecutingGitAction = true;
283-
});
284-
285-
await DoGitOperationAsync<GitOperationResult>(() =>
286-
{
287-
cancellationToken.ThrowIfCancellationRequested();
288-
289-
var result = GitOperationResult.Success;
290-
try
291-
{
292-
foreach (var remote in repository.Network.Remotes)
293-
{
294-
cancellationToken.ThrowIfCancellationRequested();
295-
296-
LibGit2Sharp.Commands.Fetch(
297-
repository,
298-
remote.Name,
299-
remote.FetchRefSpecs.Select(rs => rs.Specification),
300-
_fetchOptions,
301-
"git fetch updated a ref");
302-
}
303-
304-
cancellationToken.ThrowIfCancellationRequested();
305-
}
306-
catch (Exception ex)
307-
{
308-
result = IsAuthorizationException(ex)
309-
? GitOperationResult.AuthorizationError
310-
: GitOperationResult.GenericError;
311-
}
312-
313-
return result;
314-
});
315-
316-
MainWindow.Instance.DispatcherQueue.TryEnqueue(() =>
317-
{
318-
if (cancellationToken.IsCancellationRequested)
319-
// Do nothing because the operation was cancelled and another fetch may be in progress
320-
return;
321-
322-
_implementation.IsExecutingGitAction = false;
323-
GitFetchCompleted?.Invoke(null, EventArgs.Empty);
324-
});
325-
}
326-
327271
public static async Task PullOriginAsync(string? repositoryPath)
328272
{
329273
if (string.IsNullOrWhiteSpace(repositoryPath))

0 commit comments

Comments
 (0)