diff --git a/Directory.Packages.props b/Directory.Packages.props index 4f0b23586d00..495d82dff260 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -21,8 +21,7 @@ - - + diff --git a/src/Files.App/Actions/Git/GitFetchAction.cs b/src/Files.App/Actions/Git/GitFetchAction.cs index 26e84936f9d8..bd5260948c94 100644 --- a/src/Files.App/Actions/Git/GitFetchAction.cs +++ b/src/Files.App/Actions/Git/GitFetchAction.cs @@ -24,11 +24,9 @@ public GitFetchAction() _context.PropertyChanged += Context_PropertyChanged; } - public Task ExecuteAsync(object? parameter = null) + public async Task ExecuteAsync(object? parameter = null) { - GitHelpers.FetchOrigin(_context.ShellPage!.InstanceViewModel.GitRepositoryPath); - - return Task.CompletedTask; + await GitHelpers.FetchOrigin(_context.ShellPage!.InstanceViewModel.GitRepositoryPath); } private void Context_PropertyChanged(object? sender, PropertyChangedEventArgs e) diff --git a/src/Files.App/Data/Enums/FileOperationType.cs b/src/Files.App/Data/Enums/FileOperationType.cs index dfa51b51c514..21d632f383ed 100644 --- a/src/Files.App/Data/Enums/FileOperationType.cs +++ b/src/Files.App/Data/Enums/FileOperationType.cs @@ -72,5 +72,20 @@ public enum FileOperationType : byte /// A font has been installed /// InstallFont = 13, + + /// + /// A git repo has been pushed + /// + GitPush = 14, + + /// + /// A git repo has been fetched + /// + GitFetch = 15, + + /// + /// A git repo has been pulled + /// + GitPull = 16, } } diff --git a/src/Files.App/Strings/en-US/Resources.resw b/src/Files.App/Strings/en-US/Resources.resw index 1de736ba80bb..c27ca00e9012 100644 --- a/src/Files.App/Strings/en-US/Resources.resw +++ b/src/Files.App/Strings/en-US/Resources.resw @@ -3738,6 +3738,105 @@ Failed to empty Recycle Bin Shown in a StatusCenter card. + + Canceled pushing to "{0}" + Shown in a StatusCenter card. + + + Canceled pushing branch "{0}" to "{1}" + Shown in a StatusCenter card. + + + Pushed to "{0}" + Shown in a StatusCenter card. + + + Pushed branch "{0}" to "{1}" + Shown in a StatusCenter card. + + + Error pushing to "{0}" + Shown in a StatusCenter card. + + + Failed to push branch "{0}" to "{1}" + Shown in a StatusCenter card. + + + Pushing to "{0}" + Shown in a StatusCenter card. + + + Pushing branch "{0}" to "{1}" + Shown in a StatusCenter card. + + + Canceled fetching from "{0}" + Shown in a StatusCenter card. + + + Canceled fetching from "{0}" + Shown in a StatusCenter card. + + + Fetched from "{0}" + Shown in a StatusCenter card. + + + Fetched from "{0}" + Shown in a StatusCenter card. + + + Error fetching from "{0}" + Shown in a StatusCenter card. + + + Failed to fetch from "{0}" + Shown in a StatusCenter card. + + + Fetching from "{0}" + Shown in a StatusCenter card. + + + Fetching from "{0}" + Shown in a StatusCenter card. + + + Canceled pulling from "{0}" + Shown in a StatusCenter card. + + + Canceled pulling branch "{0}" from "{1}" + Shown in a StatusCenter card. + + + Pulled from "{0}" + Shown in a StatusCenter card. + + + Pulled branch "{0}" from "{1}" + Shown in a StatusCenter card. + + + Error pulling from "{0}" + Shown in a StatusCenter card. + + + We couldn't pull the latest changes from the remote right now. + + + Please commit or stash your changes before pulling. + Shown in a StatusCenter card. + + + Pulling from "{0}" + Shown in a StatusCenter card. + + + Pulling branch "{0}" from "{1}" + Shown in a StatusCenter card. + Preparing the operation... Shown in a StatusCenter card. diff --git a/src/Files.App/Utils/Git/GitHelpers.cs b/src/Files.App/Utils/Git/GitHelpers.cs index e49032fa77fc..1ab18f33ed1d 100644 --- a/src/Files.App/Utils/Git/GitHelpers.cs +++ b/src/Files.App/Utils/Git/GitHelpers.cs @@ -29,11 +29,6 @@ internal static partial class GitHelpers private static readonly IDialogService _dialogService = Ioc.Default.GetRequiredService(); - private static readonly FetchOptions _fetchOptions = new() - { - Prune = true - }; - private static readonly PullOptions _pullOptions = new(); private static readonly string _clientId = AppLifecycleHelper.AppEnvironment is AppEnvironment.Dev @@ -355,7 +350,7 @@ public static bool ValidateBranchNameForRepository(string branchName, string rep branch.FriendlyName.Equals(branchName, StringComparison.OrdinalIgnoreCase)); } - public static async void FetchOrigin(string? repositoryPath, CancellationToken cancellationToken = default) + public static async Task FetchOrigin(string? repositoryPath, CancellationToken cancellationToken = default) { if (string.IsNullOrWhiteSpace(repositoryPath)) return; @@ -363,57 +358,101 @@ public static async void FetchOrigin(string? repositoryPath, CancellationToken c using var repository = new Repository(repositoryPath); var signature = repository.Config.BuildSignature(DateTimeOffset.Now); - var token = CredentialsHelpers.GetPassword(GIT_RESOURCE_NAME, GIT_RESOURCE_USERNAME); - if (signature is not null && !string.IsNullOrWhiteSpace(token)) + var remoteName = repository.Network.Remotes.FirstOrDefault()?.Name ?? "origin"; + + // Add Status Center Card + var banner = StatusCenterHelper.AddCard_GitFetch(remoteName, ReturnResult.InProgress); + var fsProgress = new StatusCenterItemProgressModel(banner.ProgressEventSource, enumerationCompleted: true, FileSystemStatusCode.InProgress); + + // Link CancellationTokens + using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, banner.CancellationToken); + var token = linkedCts.Token; + + var fetchOptions = new FetchOptions { - _fetchOptions.CredentialsProvider = (url, user, cred) - => new UsernamePasswordCredentials + CredentialsProvider = CredentialsHandler, + OnTransferProgress = (progress) => + { + if (token.IsCancellationRequested) + return false; + + // Ensure StatusCenter updates are dispatched to the UI thread + MainWindow.Instance.DispatcherQueue.TryEnqueue(() => { - Username = signature.Name, - Password = token - }; - } + if (progress.TotalObjects > 0) + { + fsProgress.ItemsCount = progress.TotalObjects; + fsProgress.SetProcessedSize(progress.ReceivedBytes); + fsProgress.AddProcessedItemsCount(progress.ReceivedObjects); + fsProgress.Report((int)((progress.ReceivedObjects / (double)progress.TotalObjects) * 100)); + } + }); + return true; + }, + Prune = true // Restore Prune behavior as requested in PR review + }; MainWindow.Instance.DispatcherQueue.TryEnqueue(() => { IsExecutingGitAction = true; }); - await DoGitOperationAsync(() => + var result = await DoGitOperationAsync(() => { - cancellationToken.ThrowIfCancellationRequested(); - - var result = GitOperationResult.Success; try { + token.ThrowIfCancellationRequested(); + + // Iterate remotes (though usually only one matters for progress, we'll fetch all) foreach (var remote in repository.Network.Remotes) { - cancellationToken.ThrowIfCancellationRequested(); + token.ThrowIfCancellationRequested(); LibGit2Sharp.Commands.Fetch( repository, remote.Name, remote.FetchRefSpecs.Select(rs => rs.Specification), - _fetchOptions, + fetchOptions, "git fetch updated a ref"); } - cancellationToken.ThrowIfCancellationRequested(); + token.ThrowIfCancellationRequested(); + return GitOperationResult.Success; + } + catch (LibGit2SharpException e) + { + MainWindow.Instance.DispatcherQueue.TryEnqueue(async () => + { + var dialog = new DynamicDialog(new DynamicDialogViewModel + { + TitleText = Strings.GitError.GetLocalizedResource(), + SubtitleText = e.Message, + CloseButtonText = Strings.Close.GetLocalizedResource(), + DynamicButtons = DynamicDialogButtons.Cancel + }); + await dialog.TryShowAsync(); + }); + return GitOperationResult.GenericError; } catch (Exception ex) { - result = IsAuthorizationException(ex) + return IsAuthorizationException(ex) ? GitOperationResult.AuthorizationError : GitOperationResult.GenericError; } + }); - return result; + MainWindow.Instance.DispatcherQueue.TryEnqueue(() => + { + StatusCenterViewModel.RemoveItem(banner); + StatusCenterHelper.AddCard_GitFetch( + remoteName, + result == GitOperationResult.Success ? ReturnResult.Success : ReturnResult.Failed); }); MainWindow.Instance.DispatcherQueue.TryEnqueue(() => { - if (cancellationToken.IsCancellationRequested) - // Do nothing because the operation was cancelled and another fetch may be in progress + if (token.IsCancellationRequested) return; IsExecutingGitAction = false; @@ -431,17 +470,40 @@ public static async Task PullOriginAsync(string? repositoryPath) if (signature is null) return; - var token = CredentialsHelpers.GetPassword(GIT_RESOURCE_NAME, GIT_RESOURCE_USERNAME); - if (!string.IsNullOrWhiteSpace(token)) + var branchName = repository.Head.FriendlyName; + // We use 'origin' as generic remote name for display, though pull might use upstream + var remoteName = "origin"; + + // Add Status Center Card + var banner = StatusCenterHelper.AddCard_GitPull(remoteName, branchName, ReturnResult.InProgress); + var fsProgress = new StatusCenterItemProgressModel(banner.ProgressEventSource, enumerationCompleted: true, FileSystemStatusCode.InProgress); + var token = banner.CancellationToken; + + var pullOptions = new PullOptions { - _pullOptions.FetchOptions ??= _fetchOptions; - _pullOptions.FetchOptions.CredentialsProvider = (url, user, cred) - => new UsernamePasswordCredentials + FetchOptions = new FetchOptions + { + CredentialsProvider = CredentialsHandler, + OnTransferProgress = (progress) => { - Username = signature.Name, - Password = token - }; - } + if (token.IsCancellationRequested) + return false; + + // Ensure StatusCenter updates are dispatched to the UI thread + MainWindow.Instance.DispatcherQueue.TryEnqueue(() => + { + if (progress.TotalObjects > 0) + { + fsProgress.ItemsCount = progress.TotalObjects; + fsProgress.SetProcessedSize(progress.ReceivedBytes); + fsProgress.AddProcessedItemsCount(progress.ReceivedObjects); + fsProgress.Report((int)((progress.ReceivedObjects / (double)progress.TotalObjects) * 100)); + } + }); + return true; + } + } + }; MainWindow.Instance.DispatcherQueue.TryEnqueue(() => { @@ -452,10 +514,41 @@ public static async Task PullOriginAsync(string? repositoryPath) { try { + // LibGit2Sharp Pull doesn't take CancellationToken explicitly in all overloads, rely on callbacks LibGit2Sharp.Commands.Pull( repository, signature, - _pullOptions); + pullOptions); + } + catch (CheckoutConflictException) + { + MainWindow.Instance.DispatcherQueue.TryEnqueue(async () => + { + var dialog = new DynamicDialog(new DynamicDialogViewModel + { + TitleText = Strings.GitError.GetLocalizedResource(), + SubtitleText = Strings.GitError_UncommittedChanges.GetLocalizedResource(), + CloseButtonText = Strings.Close.GetLocalizedResource(), + DynamicButtons = DynamicDialogButtons.Cancel + }); + await dialog.TryShowAsync(); + }); + return GitOperationResult.GenericError; + } + catch (LibGit2SharpException e) + { + MainWindow.Instance.DispatcherQueue.TryEnqueue(async () => + { + var dialog = new DynamicDialog(new DynamicDialogViewModel + { + TitleText = Strings.GitError.GetLocalizedResource(), + SubtitleText = e.Message, + CloseButtonText = Strings.Close.GetLocalizedResource(), + DynamicButtons = DynamicDialogButtons.Cancel + }); + await dialog.TryShowAsync(); + }); + return GitOperationResult.GenericError; } catch (Exception ex) { @@ -467,6 +560,12 @@ public static async Task PullOriginAsync(string? repositoryPath) return GitOperationResult.Success; }); + MainWindow.Instance.DispatcherQueue.TryEnqueue(() => + { + StatusCenterViewModel.RemoveItem(banner); + StatusCenterHelper.AddCard_GitPull(remoteName, branchName, result == GitOperationResult.Success ? ReturnResult.Success : ReturnResult.Failed); + }); + if (result is GitOperationResult.AuthorizationError) { await RequireGitAuthenticationAsync(); @@ -487,6 +586,10 @@ public static async Task PullOriginAsync(string? repositoryPath) MainWindow.Instance.DispatcherQueue.TryEnqueue(() => { IsExecutingGitAction = false; + if (result == GitOperationResult.Success) + { + GitFetchCompleted?.Invoke(null, EventArgs.Empty); + } }); } @@ -500,21 +603,31 @@ public static async Task PushToOriginAsync(string? repositoryPath, string? branc if (signature is null) return; - var token = CredentialsHelpers.GetPassword(GIT_RESOURCE_NAME, GIT_RESOURCE_USERNAME); - if (string.IsNullOrWhiteSpace(token)) + // Add Status Center Card + var banner = StatusCenterHelper.AddCard_GitPush("origin", branchName, ReturnResult.InProgress); + var fsProgress = new StatusCenterItemProgressModel(banner.ProgressEventSource, enumerationCompleted: true, FileSystemStatusCode.InProgress); + banner.CancellationToken.Register(() => { - await RequireGitAuthenticationAsync(); - token = CredentialsHelpers.GetPassword(GIT_RESOURCE_NAME, GIT_RESOURCE_USERNAME); - } + // Handle cancellation if needed, though LibGit2Sharp might not support seamless cancellation mid-push easily without callback + }); var options = new PushOptions() { - CredentialsProvider = (url, user, cred) - => new UsernamePasswordCredentials + CredentialsProvider = CredentialsHandler, + OnPushTransferProgress = (current, total, bytes) => + { + if (banner.CancellationToken.IsCancellationRequested) + return false; // Cancel + + if (total > 0) { - Username = signature.Name, - Password = token + fsProgress.ItemsCount = total; + fsProgress.SetProcessedSize(bytes); + fsProgress.AddProcessedItemsCount(1); // Not accurate but shows activity + fsProgress.Report((int)((current / (double)total) * 100)); } + return true; + } }; MainWindow.Instance.DispatcherQueue.TryEnqueue(() => @@ -522,6 +635,7 @@ public static async Task PushToOriginAsync(string? repositoryPath, string? branc IsExecutingGitAction = true; }); + GitOperationResult result = GitOperationResult.GenericError; try { var branch = repository.Branches[branchName]; @@ -534,10 +648,13 @@ public static async Task PushToOriginAsync(string? repositoryPath, string? branc b => b.UpstreamBranch = branch.CanonicalName); } - var result = await DoGitOperationAsync(() => + result = await DoGitOperationAsync(() => { try { + if (banner.CancellationToken.IsCancellationRequested) + return GitOperationResult.GenericError; // Or Cancelled + repository.Network.Push(branch, options); } catch (Exception ex) @@ -558,9 +675,22 @@ public static async Task PushToOriginAsync(string? repositoryPath, string? branc _logger.LogWarning(ex.Message); } + // Remove InProgress Card + StatusCenterViewModel.RemoveItem(banner); + + // Add Result Card + StatusCenterHelper.AddCard_GitPush( + "origin", + branchName, + result == GitOperationResult.Success ? ReturnResult.Success : ReturnResult.Failed); + MainWindow.Instance.DispatcherQueue.TryEnqueue(() => { IsExecutingGitAction = false; + if (result == GitOperationResult.Success) + { + GitFetchCompleted?.Invoke(null, EventArgs.Empty); + } }); } @@ -858,25 +988,29 @@ private static bool IsAuthorizationException(Exception ex) { return ex.Message.Contains("status code: 401", StringComparison.OrdinalIgnoreCase) || - ex.Message.Contains("authentication replays", StringComparison.OrdinalIgnoreCase); + ex.Message.Contains("authentication replays", StringComparison.OrdinalIgnoreCase) || + ex.Message.Contains("authentication failed", StringComparison.OrdinalIgnoreCase) || + ex.Message.Contains("user cancelled", StringComparison.OrdinalIgnoreCase); } private static async Task DoGitOperationAsync(Func payload, bool useSemaphore = false) { - if (useSemaphore) - await GitOperationSemaphore.WaitAsync(); - else - await Task.Yield(); - - try - { - return (T)payload(); - } - finally + // Offload to background thread to prevent UI freeze + return await Task.Run(async () => { if (useSemaphore) - GitOperationSemaphore.Release(); - } + await GitOperationSemaphore.WaitAsync(); + + try + { + return (T)payload(); + } + finally + { + if (useSemaphore) + GitOperationSemaphore.Release(); + } + }); } /// @@ -891,11 +1025,21 @@ public static (string RepoUrl, string RepoName) GetRepoInfo(string url) if (!match.Success) return (string.Empty, string.Empty); - string platform = match.Groups["domain"].Value; + string domain = match.Groups["domain"].Value; string userOrOrg = match.Groups["user"].Value; string repoName = match.Groups["repo"].Value; + string protocol = match.Groups["protocol"].Value; + + string repoUrl; + if (protocol.Contains("@")) + { + repoUrl = $"git@{domain}.com:{userOrOrg}/{repoName}.git"; + } + else + { + repoUrl = $"https://{domain}.com/{userOrOrg}/{repoName}"; + } - string repoUrl = $"https://{platform}.com/{userOrOrg}/{repoName}"; return (repoUrl, repoName); } @@ -923,6 +1067,7 @@ public static async Task CloneRepoAsync(string repoUrl, string repoName, string { FetchOptions = { + CredentialsProvider = CredentialsHandler, OnTransferProgress = progress => { banner.CancellationToken.ThrowIfCancellationRequested(); @@ -965,7 +1110,28 @@ public static async Task CloneRepoAsync(string repoUrl, string repoName, string ReturnResult.Failed); } - [GeneratedRegex(@"^(?:https?:\/\/)?(?:www\.)?(?github|gitlab)\.com\/(?[^\/]+)\/(?[^\/]+?)(?=\.git|\/|$)(?:\.git)?(?:\/)?", RegexOptions.IgnoreCase)] + [GeneratedRegex(@"^(?:(?https?:\/\/)?(?:www\.)?|(?git@))(?github|gitlab)\.com(?:\/|:)(?[^\/]+)\/(?[^\/]+?)(?=\.git|\/|$)(?:\.git)?(?:\/)?", RegexOptions.IgnoreCase)] private static partial Regex GitHubRepositoryRegex(); + + private static Credentials? CredentialsHandler(string url, string usernameFromUrl, SupportedCredentialTypes types) + { + if (types.HasFlag(SupportedCredentialTypes.UsernamePassword)) + { + var token = CredentialsHelpers.GetPassword(GIT_RESOURCE_NAME, GIT_RESOURCE_USERNAME); + if (!string.IsNullOrWhiteSpace(token)) + { + return new UsernamePasswordCredentials + { + Username = "Personal Access Token", + Password = token + }; + } + } + + // For SSH or other types, return null to let LibGit2/OpenSSH handle it natively (e.g. via ssh-agent) + return null; + } + + } } diff --git a/src/Files.App/Utils/StatusCenter/StatusCenterHelper.cs b/src/Files.App/Utils/StatusCenter/StatusCenterHelper.cs index 8605bdd12415..c6f0005a0363 100644 --- a/src/Files.App/Utils/StatusCenter/StatusCenterHelper.cs +++ b/src/Files.App/Utils/StatusCenter/StatusCenterHelper.cs @@ -538,6 +538,191 @@ public static StatusCenterItem AddCard_GitClone( } } + public static StatusCenterItem AddCard_GitPush( + string destination, + string branchName, + ReturnResult returnStatus, + long itemsCount = 0, + long totalSize = 0) + { + if (returnStatus == ReturnResult.Cancelled) + { + return _statusCenterViewModel.AddItem( + "StatusCenter_GitPushCanceled_Header", + "StatusCenter_GitPushCanceled_SubHeader", + ReturnResult.Cancelled, + FileOperationType.GitPush, + branchName.CreateEnumerable(), + destination.CreateEnumerable(), + false, + itemsCount, + totalSize); + } + else if (returnStatus == ReturnResult.InProgress) + { + return _statusCenterViewModel.AddItem( + "StatusCenter_GitPushInProgress_Header", + "StatusCenter_GitPushInProgress_SubHeader", + ReturnResult.InProgress, + FileOperationType.GitPush, + branchName.CreateEnumerable(), + destination.CreateEnumerable(), + true, + itemsCount, + totalSize, + new CancellationTokenSource()); + } + else if (returnStatus == ReturnResult.Success) + { + return _statusCenterViewModel.AddItem( + "StatusCenter_GitPushComplete_Header", + "StatusCenter_GitPushComplete_SubHeader", + ReturnResult.Success, + FileOperationType.GitPush, + branchName.CreateEnumerable(), + destination.CreateEnumerable(), + false, + itemsCount, + totalSize); + } + else + { + return _statusCenterViewModel.AddItem( + "StatusCenter_GitPushFailed_Header", + "StatusCenter_GitPushFailed_SubHeader", + ReturnResult.Failed, + FileOperationType.GitPush, + branchName.CreateEnumerable(), + destination.CreateEnumerable(), + false, + itemsCount, + totalSize); + } + } + + public static StatusCenterItem AddCard_GitFetch( + string remoteName, + ReturnResult returnStatus, + long itemsCount = 0, + long totalSize = 0) + { + if (returnStatus == ReturnResult.Cancelled) + { + return _statusCenterViewModel.AddItem( + "StatusCenter_GitFetchCanceled_Header", + "StatusCenter_GitFetchCanceled_SubHeader", + ReturnResult.Cancelled, + FileOperationType.GitFetch, + remoteName.CreateEnumerable(), + null, + false, + itemsCount, + totalSize); + } + else if (returnStatus == ReturnResult.InProgress) + { + return _statusCenterViewModel.AddItem( + "StatusCenter_GitFetchInProgress_Header", + "StatusCenter_GitFetchInProgress_SubHeader", + ReturnResult.InProgress, + FileOperationType.GitFetch, + remoteName.CreateEnumerable(), + null, + true, + itemsCount, + totalSize, + new CancellationTokenSource()); + } + else if (returnStatus == ReturnResult.Success) + { + return _statusCenterViewModel.AddItem( + "StatusCenter_GitFetchComplete_Header", + "StatusCenter_GitFetchComplete_SubHeader", + ReturnResult.Success, + FileOperationType.GitFetch, + remoteName.CreateEnumerable(), + null, + false, + itemsCount, + totalSize); + } + else + { + return _statusCenterViewModel.AddItem( + "StatusCenter_GitFetchFailed_Header", + "StatusCenter_GitFetchFailed_SubHeader", + ReturnResult.Failed, + FileOperationType.GitFetch, + remoteName.CreateEnumerable(), + null, + false, + itemsCount, + totalSize); + } + } + + public static StatusCenterItem AddCard_GitPull( + string destination, + string branchName, + ReturnResult returnStatus, + long itemsCount = 0, + long totalSize = 0) + { + if (returnStatus == ReturnResult.Cancelled) + { + return _statusCenterViewModel.AddItem( + "StatusCenter_GitPullCanceled_Header", + "StatusCenter_GitPullCanceled_SubHeader", + ReturnResult.Cancelled, + FileOperationType.GitPull, + branchName.CreateEnumerable(), + destination.CreateEnumerable(), + false, + itemsCount, + totalSize); + } + else if (returnStatus == ReturnResult.InProgress) + { + return _statusCenterViewModel.AddItem( + "StatusCenter_GitPullInProgress_Header", + "StatusCenter_GitPullInProgress_SubHeader", + ReturnResult.InProgress, + FileOperationType.GitPull, + branchName.CreateEnumerable(), + destination.CreateEnumerable(), + true, + itemsCount, + totalSize, + new CancellationTokenSource()); + } + else if (returnStatus == ReturnResult.Success) + { + return _statusCenterViewModel.AddItem( + "StatusCenter_GitPullComplete_Header", + "StatusCenter_GitPullComplete_SubHeader", + ReturnResult.Success, + FileOperationType.GitPull, + branchName.CreateEnumerable(), + destination.CreateEnumerable(), + false, + itemsCount, + totalSize); + } + else + { + return _statusCenterViewModel.AddItem( + "StatusCenter_GitPullFailed_Header", + "StatusCenter_GitPullFailed_SubHeader", + ReturnResult.Failed, + FileOperationType.GitPull, + branchName.CreateEnumerable(), + destination.CreateEnumerable(), + false, + itemsCount, + totalSize); + } + } + public static StatusCenterItem AddCard_InstallFont( IEnumerable source, ReturnResult returnStatus, @@ -755,6 +940,40 @@ public static void UpdateCardStrings(StatusCenterItem card, StatusCenterItemProg card.SubHeader = subHeaderString; break; } + + case FileOperationType.GitPush: + { + string headerString = string.IsNullOrWhiteSpace(card.HeaderStringResource) ? string.Empty + : card.HeaderStringResource.GetLocalizedFormatResource(destinationDirName); + card.Header = headerString; + + string subHeaderString = string.IsNullOrWhiteSpace(card.SubHeaderStringResource) ? string.Empty + : card.SubHeaderStringResource.GetLocalizedFormatResource(sourceFileName, destinationDirName); + card.SubHeader = subHeaderString; + break; + } + case FileOperationType.GitFetch: + { + string headerString = string.IsNullOrWhiteSpace(card.HeaderStringResource) ? string.Empty + : card.HeaderStringResource.GetLocalizedFormatResource(sourceFileName); + card.Header = headerString; + + string subHeaderString = string.IsNullOrWhiteSpace(card.SubHeaderStringResource) ? string.Empty + : card.SubHeaderStringResource.GetLocalizedFormatResource(sourceFileName); + card.SubHeader = subHeaderString; + break; + } + case FileOperationType.GitPull: + { + string headerString = string.IsNullOrWhiteSpace(card.HeaderStringResource) ? string.Empty + : card.HeaderStringResource.GetLocalizedFormatResource(destinationDirName); + card.Header = headerString; + + string subHeaderString = string.IsNullOrWhiteSpace(card.SubHeaderStringResource) ? string.Empty + : card.SubHeaderStringResource.GetLocalizedFormatResource(sourceFileName, destinationDirName); + card.SubHeader = subHeaderString; + break; + } } } }