Show the window before slow location probes and wrap git.exe for commit, diff, and merge.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-24 16:31:56 +02:00
parent a3c54bbb03
commit 9efb306979
42 changed files with 3184 additions and 77 deletions

View File

@@ -35,11 +35,21 @@ public partial class App : System.Windows.Application
.Build();
var vm = _host.Services.GetRequiredService<MainViewModel>();
await vm.InitializeAsync().ConfigureAwait(true);
await _host.StartAsync().ConfigureAwait(true);
var window = _host.Services.GetRequiredService<MainWindow>();
vm.PrepareUi();
window.DataContext = vm;
window.Show();
try
{
await vm.InitializeAsync().ConfigureAwait(true);
}
catch (Exception ex)
{
Log.Error(ex, "Startup initialization failed");
vm.Footer = "Started with errors. See logs.";
}
await _host.StartAsync().ConfigureAwait(true);
}
protected override async void OnExit(ExitEventArgs e)

View File

@@ -42,7 +42,9 @@ public static class AppServices
services.AddSingleton<StorageProviderRegistry>();
services.AddSingleton<IHydrationGuard, HydrationGuard>();
services.AddSingleton<IArchiveExecutor, SevenZipArchiveExecutor>();
services.AddSingleton<IGitStatusProvider, WindowsGitStatusProvider>();
services.AddSingleton<WindowsGitStatusProvider>();
services.AddSingleton<IGitStatusProvider>(sp => sp.GetRequiredService<WindowsGitStatusProvider>());
services.AddSingleton<IGitCommandProvider>(sp => sp.GetRequiredService<WindowsGitStatusProvider>());
services.AddSingleton<IWorkspaceLauncher, WindowsWorkspaceLauncher>();
services.AddSingleton<IElevatedScanService, WindowsElevatedScanService>();
services.AddSingleton<IRecycleBinCatalog, WindowsRecycleBinCatalog>();
@@ -115,7 +117,7 @@ public sealed class WatcherHostedService : BackgroundService
await _hub.RefreshAsync(stoppingToken).ConfigureAwait(false);
while (await timer.WaitForNextTickAsync(stoppingToken).ConfigureAwait(false))
{
await _sources.RefreshOnlineStateAsync(stoppingToken).ConfigureAwait(false);
await _sources.RefreshOnlineStateAsync(forceRefresh: true, stoppingToken).ConfigureAwait(false);
_transfers.NotifyAvailability();
await _sync.TryAutoRunAsync(stoppingToken).ConfigureAwait(false);
await _profiles.TryAutoRunAsync(stoppingToken).ConfigureAwait(false);

View File

@@ -0,0 +1,80 @@
<Window x:Class="Explorer.App.GitChangesWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Git changes"
Icon="pack://application:,,,/Assets/explorer-workbench.ico"
Height="620" Width="920"
MinHeight="400" MinWidth="640"
WindowStartupLocation="CenterOwner"
Background="{DynamicResource Bg}" Foreground="{DynamicResource Fg}">
<DockPanel Margin="16">
<DockPanel DockPanel.Dock="Bottom" Margin="0,12,0,0">
<Button DockPanel.Dock="Right" Content="Close" MinWidth="88" Height="32" IsCancel="True" Margin="8,0,0,0"/>
<Button DockPanel.Dock="Right" Content="Commit…" MinWidth="88" Height="32"
Click="OnCommit" IsEnabled="{Binding CanCommit}" Margin="8,0,0,0"/>
<Button DockPanel.Dock="Right" Content="Push" MinWidth="72" Height="32"
Click="OnPush" IsEnabled="{Binding CanNetwork}" Margin="8,0,0,0"/>
<Button DockPanel.Dock="Right" Content="Pull" MinWidth="72" Height="32"
Click="OnPull" IsEnabled="{Binding CanNetwork}" Margin="8,0,0,0"/>
<Button DockPanel.Dock="Right" Content="Fetch" MinWidth="72" Height="32"
Click="OnFetch" IsEnabled="{Binding CanNetwork}" Margin="8,0,0,0"/>
<Button DockPanel.Dock="Right" Content="Refresh" MinWidth="88" Height="32"
Command="{Binding RefreshCommand}" Margin="8,0,0,0"/>
<TextBlock Text="{Binding Status}" VerticalAlignment="Center" Foreground="{DynamicResource FgMuted}"
TextWrapping="Wrap"/>
</DockPanel>
<StackPanel DockPanel.Dock="Top" Margin="0,0,0,8">
<TextBlock Text="{Binding Summary}" FontWeight="SemiBold" TextWrapping="Wrap"/>
<TextBlock Text="{Binding RepoRoot}" Margin="0,4,0,0" Foreground="{DynamicResource FgMuted}"
TextTrimming="CharacterEllipsis"/>
</StackPanel>
<WrapPanel DockPanel.Dock="Top" Margin="0,0,0,8">
<Button Content="View diff" MinWidth="88" Height="32" Click="OnDiff"
IsEnabled="{Binding CanDiff}" Margin="0,0,8,8"/>
<Button Content="Open in Cursor" MinWidth="120" Height="32"
Command="{Binding OpenSelectedInCursorCommand}"
IsEnabled="{Binding CanOpenInCursor}" Margin="0,0,8,8"/>
<Button Content="Stage" MinWidth="72" Height="32" Click="OnStage"
IsEnabled="{Binding CanStage}" Margin="0,0,8,8"/>
<Button Content="Unstage" MinWidth="72" Height="32" Click="OnUnstage"
IsEnabled="{Binding CanUnstage}" Margin="0,0,8,8"/>
<Button Content="Discard…" MinWidth="88" Height="32" Click="OnDiscard"
IsEnabled="{Binding CanDiscard}" Margin="0,0,16,8"/>
<Button Content="Use ours" MinWidth="88" Height="32" Click="OnUseOurs"
IsEnabled="{Binding CanResolve}" Margin="0,0,8,8"/>
<Button Content="Use theirs" MinWidth="88" Height="32" Click="OnUseTheirs"
IsEnabled="{Binding CanResolve}" Margin="0,0,8,8"/>
<Button Content="Mark resolved" MinWidth="110" Height="32" Click="OnMarkResolved"
IsEnabled="{Binding CanResolve}" Margin="0,0,16,8"/>
<Button Content="Abort" MinWidth="72" Height="32" Click="OnAbort"
IsEnabled="{Binding CanAbort}" Margin="0,0,8,8"/>
<Button Content="Continue" MinWidth="88" Height="32" Click="OnContinue"
IsEnabled="{Binding CanContinue}" Margin="0,0,8,8"/>
</WrapPanel>
<ListView ItemsSource="{Binding Changes}" SelectedItem="{Binding Selected}"
MouseDoubleClick="OnRowDoubleClick">
<ListView.ContextMenu>
<ContextMenu>
<MenuItem Header="View diff" Click="OnDiff" IsEnabled="{Binding CanDiff}"/>
<MenuItem Header="Open in Cursor" Command="{Binding OpenSelectedInCursorCommand}"
IsEnabled="{Binding CanOpenInCursor}"/>
<Separator/>
<MenuItem Header="Stage" Click="OnStage" IsEnabled="{Binding CanStage}"/>
<MenuItem Header="Unstage" Click="OnUnstage" IsEnabled="{Binding CanUnstage}"/>
<MenuItem Header="Discard…" Click="OnDiscard" IsEnabled="{Binding CanDiscard}"/>
<Separator/>
<MenuItem Header="Use ours" Click="OnUseOurs" IsEnabled="{Binding CanResolve}"/>
<MenuItem Header="Use theirs" Click="OnUseTheirs" IsEnabled="{Binding CanResolve}"/>
<MenuItem Header="Mark resolved" Click="OnMarkResolved" IsEnabled="{Binding CanResolve}"/>
</ContextMenu>
</ListView.ContextMenu>
<ListView.View>
<GridView>
<GridViewColumn Header="Kind" Width="100" DisplayMemberBinding="{Binding KindLabel}"/>
<GridViewColumn Header="Change" Width="110" DisplayMemberBinding="{Binding ChangeLabel}"/>
<GridViewColumn Header="Path" Width="620" DisplayMemberBinding="{Binding DisplayPath}"/>
</GridView>
</ListView.View>
</ListView>
</DockPanel>
</Window>

View File

@@ -0,0 +1,147 @@
using System.Windows;
using System.Windows.Input;
using Explorer.Domain;
using Explorer.Presentation.ViewModels;
namespace Explorer.App;
public partial class GitChangesWindow : Window
{
public GitChangesWindow(GitChangesViewModel vm)
{
InitializeComponent();
DataContext = vm;
ViewModel = vm;
}
public GitChangesViewModel ViewModel { get; }
private async void OnRowDoubleClick(object sender, MouseButtonEventArgs e)
=> await ShowDiffAsync().ConfigureAwait(true);
private async void OnDiff(object sender, RoutedEventArgs e)
=> await ShowDiffAsync().ConfigureAwait(true);
private async Task ShowDiffAsync()
{
var diff = await ViewModel.DiffSelectedAsync().ConfigureAwait(true);
if (diff is null)
{
return;
}
new GitDiffWindow(new GitDiffViewModel(diff)) { Owner = this }.Show();
}
private async void OnCommit(object sender, RoutedEventArgs e)
{
var vm = ViewModel.CreateCommitViewModel();
if (vm is null)
{
return;
}
var dlg = new GitCommitWindow(vm) { Owner = this };
if (dlg.ShowDialog() == true && Owner is MainWindow main)
{
await main.AfterGitMutationAsync().ConfigureAwait(true);
await ViewModel.RefreshAsync().ConfigureAwait(true);
}
}
private async void OnFetch(object sender, RoutedEventArgs e)
=> await ShowGitResultAsync(await ViewModel.FetchAsync().ConfigureAwait(true)).ConfigureAwait(true);
private async void OnPull(object sender, RoutedEventArgs e)
=> await ShowGitResultAsync(await ViewModel.PullAsync().ConfigureAwait(true)).ConfigureAwait(true);
private async void OnPush(object sender, RoutedEventArgs e)
=> await ShowGitResultAsync(await ViewModel.PushAsync().ConfigureAwait(true)).ConfigureAwait(true);
private async void OnStage(object sender, RoutedEventArgs e)
=> await ShowGitResultAsync(await ViewModel.StageSelectedAsync().ConfigureAwait(true)).ConfigureAwait(true);
private async void OnUnstage(object sender, RoutedEventArgs e)
=> await ShowGitResultAsync(await ViewModel.UnstageSelectedAsync().ConfigureAwait(true)).ConfigureAwait(true);
private async void OnDiscard(object sender, RoutedEventArgs e)
{
if (ViewModel.Selected is null)
{
return;
}
var path = ViewModel.Selected.DisplayPath;
var choice = MessageBox.Show(
this,
$"Discard local changes to {path}?",
"Git",
MessageBoxButton.YesNo,
MessageBoxImage.Warning);
if (choice != MessageBoxResult.Yes)
{
return;
}
await ShowGitResultAsync(await ViewModel.DiscardSelectedAsync().ConfigureAwait(true)).ConfigureAwait(true);
}
private async void OnUseOurs(object sender, RoutedEventArgs e)
=> await ShowGitResultAsync(await ViewModel.UseOursAsync().ConfigureAwait(true)).ConfigureAwait(true);
private async void OnUseTheirs(object sender, RoutedEventArgs e)
=> await ShowGitResultAsync(await ViewModel.UseTheirsAsync().ConfigureAwait(true)).ConfigureAwait(true);
private async void OnMarkResolved(object sender, RoutedEventArgs e)
=> await ShowGitResultAsync(await ViewModel.MarkResolvedAsync().ConfigureAwait(true)).ConfigureAwait(true);
private async void OnAbort(object sender, RoutedEventArgs e)
{
var choice = MessageBox.Show(
this,
$"Abort the {OperationName()} and restore the previous state?",
"Git",
MessageBoxButton.YesNo,
MessageBoxImage.Warning);
if (choice != MessageBoxResult.Yes)
{
return;
}
await ShowGitResultAsync(await ViewModel.AbortAsync().ConfigureAwait(true)).ConfigureAwait(true);
}
private async void OnContinue(object sender, RoutedEventArgs e)
=> await ShowGitResultAsync(await ViewModel.ContinueAsync().ConfigureAwait(true)).ConfigureAwait(true);
private async Task ShowGitResultAsync(GitCommandResult result)
{
if (result.Succeeded)
{
if (Owner is MainWindow main)
{
await main.AfterGitMutationAsync().ConfigureAwait(true);
}
return;
}
if (Owner is MainWindow owner)
{
await owner.ShowGitResultAsync(result).ConfigureAwait(true);
return;
}
MessageBox.Show(this, result.DisplayMessage, "Git", MessageBoxButton.OK, MessageBoxImage.Warning);
}
private string OperationName()
=> ViewModel.Operation switch
{
GitOperationKind.Merge => "merge",
GitOperationKind.Rebase => "rebase",
GitOperationKind.CherryPick => "cherry-pick",
GitOperationKind.Revert => "revert",
_ => "Git operation"
};
}

View File

@@ -0,0 +1,43 @@
<Window x:Class="Explorer.App.GitCommitWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Commit"
Icon="pack://application:,,,/Assets/explorer-workbench.ico"
Height="520" Width="720"
MinHeight="360" MinWidth="520"
WindowStartupLocation="CenterOwner"
Background="{DynamicResource Bg}" Foreground="{DynamicResource Fg}">
<DockPanel Margin="16">
<DockPanel DockPanel.Dock="Bottom" Margin="0,12,0,0">
<Button DockPanel.Dock="Right" Content="Cancel" MinWidth="88" Height="32" IsCancel="True" Margin="8,0,0,0"/>
<Button DockPanel.Dock="Right" Content="Commit" MinWidth="88" Height="32" IsDefault="True"
Command="{Binding CommitCommand}" IsEnabled="{Binding CanCommit}"/>
<TextBlock Text="{Binding Status}" VerticalAlignment="Center" Foreground="{DynamicResource FgMuted}"
TextWrapping="Wrap"/>
</DockPanel>
<TextBlock DockPanel.Dock="Top" Text="{Binding RepoRoot}" Margin="0,0,0,8"
Foreground="{DynamicResource FgMuted}" TextTrimming="CharacterEllipsis"/>
<TextBlock DockPanel.Dock="Top" Text="Message" Foreground="{DynamicResource FgMuted}" Margin="0,0,0,4"/>
<TextBox DockPanel.Dock="Top" Text="{Binding Message, UpdateSourceTrigger=PropertyChanged}"
AcceptsReturn="True" Height="88" TextWrapping="Wrap" VerticalScrollBarVisibility="Auto"
Margin="0,0,0,12"/>
<TextBlock DockPanel.Dock="Top" Text="Files" Foreground="{DynamicResource FgMuted}" Margin="0,0,0,4"/>
<ListView ItemsSource="{Binding Files}">
<ListView.View>
<GridView>
<GridViewColumn Width="36">
<GridViewColumn.CellTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding Include, UpdateSourceTrigger=PropertyChanged}"
IsEnabled="{Binding CanInclude}" VerticalAlignment="Center"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Kind" Width="90" DisplayMemberBinding="{Binding KindLabel}"/>
<GridViewColumn Header="Change" Width="100" DisplayMemberBinding="{Binding ChangeLabel}"/>
<GridViewColumn Header="Path" Width="420" DisplayMemberBinding="{Binding DisplayPath}"/>
</GridView>
</ListView.View>
</ListView>
</DockPanel>
</Window>

View File

@@ -0,0 +1,24 @@
using System.Windows;
using Explorer.Presentation.ViewModels;
namespace Explorer.App;
public partial class GitCommitWindow : Window
{
public GitCommitWindow(GitCommitViewModel vm)
{
InitializeComponent();
DataContext = vm;
vm.CloseRequested += (_, _) =>
{
try
{
DialogResult = true;
}
catch (InvalidOperationException)
{
Close();
}
};
}
}

View File

@@ -0,0 +1,60 @@
<Window x:Class="Explorer.App.GitDiffWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="{Binding Title}"
Icon="pack://application:,,,/Assets/explorer-workbench.ico"
Height="640" Width="860"
MinHeight="360" MinWidth="520"
WindowStartupLocation="CenterOwner"
Background="{DynamicResource Bg}" Foreground="{DynamicResource Fg}">
<DockPanel Margin="16">
<Button DockPanel.Dock="Bottom" Content="Close" MinWidth="88" Height="32" HorizontalAlignment="Right"
IsCancel="True" Margin="0,12,0,0"/>
<TextBlock DockPanel.Dock="Top" Text="{Binding EmptyText}" Margin="0,0,0,8"
Foreground="{DynamicResource FgMuted}" TextWrapping="Wrap"
Visibility="{Binding ShowEmpty, Converter={StaticResource BoolVis}}"/>
<ListBox ItemsSource="{Binding Lines}" FontFamily="Consolas" FontSize="13"
Background="{DynamicResource InputBg}" BorderBrush="{DynamicResource Stroke}"
ScrollViewer.HorizontalScrollBarVisibility="Auto"
ScrollViewer.VerticalScrollBarVisibility="Auto"
VirtualizingPanel.IsVirtualizing="True"
VirtualizingPanel.VirtualizationMode="Recycling">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="Padding" Value="0"/>
<Setter Property="Margin" Value="0"/>
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="Focusable" Value="False"/>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Text}" FontFamily="Consolas" Padding="8,1" TextWrapping="NoWrap">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Foreground" Value="{DynamicResource Fg}"/>
<Setter Property="Background" Value="Transparent"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Kind}" Value="Added">
<Setter Property="Foreground" Value="{DynamicResource GitDiffAdded}"/>
<Setter Property="Background" Value="{DynamicResource GitDiffAddedBg}"/>
</DataTrigger>
<DataTrigger Binding="{Binding Kind}" Value="Removed">
<Setter Property="Foreground" Value="{DynamicResource GitDiffRemoved}"/>
<Setter Property="Background" Value="{DynamicResource GitDiffRemovedBg}"/>
</DataTrigger>
<DataTrigger Binding="{Binding Kind}" Value="Hunk">
<Setter Property="Foreground" Value="{DynamicResource GitDiffHunk}"/>
</DataTrigger>
<DataTrigger Binding="{Binding Kind}" Value="Meta">
<Setter Property="Foreground" Value="{DynamicResource FgMuted}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</DockPanel>
</Window>

View File

@@ -0,0 +1,14 @@
using System.Windows;
using Explorer.Presentation.ViewModels;
namespace Explorer.App;
public partial class GitDiffWindow : Window
{
public GitDiffWindow(GitDiffViewModel vm)
{
InitializeComponent();
DataContext = vm;
Title = vm.Title;
}
}

View File

@@ -93,6 +93,20 @@
<MenuItem Header="_Operation profiles…" Click="OnOperationProfiles"/>
</MenuItem>
<MenuItem Header="_Development">
<MenuItem Header="View _changes…" Click="OnGitChanges"
IsEnabled="{Binding ShowGitActions}"/>
<MenuItem Header="_Commit…" Click="OnGitCommit"
IsEnabled="{Binding ShowGitActions}"/>
<Separator/>
<MenuItem Header="_Fetch" Click="OnGitFetch"
IsEnabled="{Binding ShowGitActions}"/>
<MenuItem Header="P_ull (fast-forward)" Click="OnGitPull"
IsEnabled="{Binding ShowGitActions}"/>
<MenuItem Header="Pull (_merge)" Click="OnGitPullMerge"
IsEnabled="{Binding ShowGitActions}"/>
<MenuItem Header="_Push" Click="OnGitPush"
IsEnabled="{Binding ShowGitActions}"/>
<Separator/>
<MenuItem Header="Open _terminal here" Command="{Binding OpenTerminalCommand}"
IsEnabled="{Binding ShowOpenTerminal}"/>
<MenuItem Header="Open in _Cursor" Command="{Binding OpenInCursorCommand}"
@@ -460,6 +474,18 @@
<Separator/>
<MenuItem Header="New folder" Click="OnCtxNewFolder"/>
<MenuItem Header="Copy path" Click="OnCtxCopyPath"/>
<MenuItem Header="View changes…" Click="OnGitChanges"
Visibility="{Binding ShowGitActions, Converter={StaticResource BoolVis}}"/>
<MenuItem Header="Commit…" Click="OnGitCommit"
Visibility="{Binding ShowGitActions, Converter={StaticResource BoolVis}}"/>
<MenuItem Header="Fetch" Click="OnGitFetch"
Visibility="{Binding ShowGitActions, Converter={StaticResource BoolVis}}"/>
<MenuItem Header="Pull (fast-forward)" Click="OnGitPull"
Visibility="{Binding ShowGitActions, Converter={StaticResource BoolVis}}"/>
<MenuItem Header="Pull (merge)" Click="OnGitPullMerge"
Visibility="{Binding ShowGitActions, Converter={StaticResource BoolVis}}"/>
<MenuItem Header="Push" Click="OnGitPush"
Visibility="{Binding ShowGitActions, Converter={StaticResource BoolVis}}"/>
<MenuItem Header="Open terminal here" Command="{Binding OpenTerminalCommand}"
Visibility="{Binding ShowOpenTerminal, Converter={StaticResource BoolVis}}"/>
<MenuItem Header="Open in Cursor" Command="{Binding OpenInCursorCommand}"

View File

@@ -16,6 +16,7 @@ namespace Explorer.App;
public partial class MainWindow : Window
{
private DocumentationWindow? _docs;
private GitChangesWindow? _gitChanges;
private Point _dragStart;
private bool _dragPending;
private MouseButton _dragButton;
@@ -39,6 +40,7 @@ public partial class MainWindow : Window
{
_wiredVm.InlineRenameRequested -= OnInlineRenameRequested;
_wiredVm.PropertyChanged -= OnViewModelPropertyChanged;
_wiredVm.WorkspaceChanged -= OnWorkspaceChanged;
}
_wiredVm = DataContext as MainViewModel;
@@ -46,6 +48,7 @@ public partial class MainWindow : Window
{
_wiredVm.InlineRenameRequested += OnInlineRenameRequested;
_wiredVm.PropertyChanged += OnViewModelPropertyChanged;
_wiredVm.WorkspaceChanged += OnWorkspaceChanged;
RestoreLayout(_wiredVm);
_ = _wiredVm.RefreshUndoRenameAsync();
}
@@ -63,6 +66,9 @@ public partial class MainWindow : Window
}
}
private void OnWorkspaceChanged(object? sender, EventArgs e)
=> _ = ReloadGitChangesIfOpenAsync();
private void OnInlineRenameRequested(object? sender, string path)
=> Dispatcher.BeginInvoke(() => BeginInlineRenameForPath(path), DispatcherPriority.Loaded);
@@ -1480,6 +1486,126 @@ public partial class MainWindow : Window
private void OnAbout(object sender, RoutedEventArgs e)
=> new AboutWindow { Owner = this }.ShowDialog();
private async void OnGitChanges(object sender, RoutedEventArgs e)
{
if (_gitChanges is { IsVisible: true })
{
_gitChanges.Activate();
await ReloadGitChangesIfOpenAsync().ConfigureAwait(true);
return;
}
var vm = Vm.CreateGitChangesViewModel();
_gitChanges = new GitChangesWindow(vm) { Owner = this };
_gitChanges.Closed += (_, _) => _gitChanges = null;
_gitChanges.Show();
await vm.LoadAsync(Vm.GitWorkspacePath()).ConfigureAwait(true);
}
private async void OnGitCommit(object sender, RoutedEventArgs e)
{
var vm = await Vm.CreateGitCommitViewModelAsync().ConfigureAwait(true);
if (vm is null)
{
return;
}
var dlg = new GitCommitWindow(vm) { Owner = this };
if (dlg.ShowDialog() == true)
{
await AfterGitMutationAsync().ConfigureAwait(true);
}
}
private async void OnGitFetch(object sender, RoutedEventArgs e)
=> await ShowGitResultAsync(await Vm.GitFetchAsync().ConfigureAwait(true), reloadChanges: true).ConfigureAwait(true);
private async void OnGitPull(object sender, RoutedEventArgs e)
=> await ShowGitResultAsync(await Vm.GitPullAsync().ConfigureAwait(true), reloadChanges: true).ConfigureAwait(true);
private async void OnGitPullMerge(object sender, RoutedEventArgs e)
=> await ShowGitResultAsync(await Vm.GitPullMergeAsync().ConfigureAwait(true), reloadChanges: true).ConfigureAwait(true);
private async void OnGitPush(object sender, RoutedEventArgs e)
=> await ShowGitResultAsync(await Vm.GitPushAsync().ConfigureAwait(true), reloadChanges: true).ConfigureAwait(true);
public async Task AfterGitMutationAsync()
{
await Vm.RefreshGitOverlaysAsync().ConfigureAwait(true);
await ReloadGitChangesIfOpenAsync().ConfigureAwait(true);
}
public Task ShowGitResultAsync(GitCommandResult result, bool reloadChanges = false)
=> ShowGitResultCoreAsync(result, reloadChanges);
public void ShowGitResult(GitCommandResult result, bool reloadChanges = false)
=> _ = ShowGitResultCoreAsync(result, reloadChanges);
private async Task ShowGitResultCoreAsync(GitCommandResult result, bool reloadChanges)
{
if (reloadChanges)
{
await ReloadGitChangesIfOpenAsync().ConfigureAwait(true);
}
if (result.Succeeded)
{
return;
}
if (result.SuggestMergePull)
{
var choice = MessageBox.Show(
this,
result.DisplayMessage + Environment.NewLine + Environment.NewLine
+ "Yes = pull with a merge commit" + Environment.NewLine
+ "No = open a terminal" + Environment.NewLine
+ "Cancel = dismiss",
"Git",
MessageBoxButton.YesNoCancel,
MessageBoxImage.Warning);
if (choice == MessageBoxResult.Yes)
{
await ShowGitResultCoreAsync(await Vm.GitPullMergeAsync().ConfigureAwait(true), reloadChanges: true)
.ConfigureAwait(true);
}
else if (choice == MessageBoxResult.No)
{
Vm.OpenTerminal();
}
return;
}
if (result.SuggestTerminal)
{
var choice = MessageBox.Show(
this,
result.DisplayMessage + Environment.NewLine + Environment.NewLine + "Open a terminal in this repository?",
"Git",
MessageBoxButton.YesNo,
MessageBoxImage.Warning);
if (choice == MessageBoxResult.Yes)
{
Vm.OpenTerminal();
}
return;
}
MessageBox.Show(this, result.DisplayMessage, "Git", MessageBoxButton.OK, MessageBoxImage.Warning);
}
private Task ReloadGitChangesIfOpenAsync()
{
if (_gitChanges is not { IsVisible: true })
{
return Task.CompletedTask;
}
return _gitChanges.ViewModel.LoadAsync(Vm.GitWorkspacePath());
}
private void ShowDocumentation()
{
if (_docs is { IsVisible: true })

View File

@@ -72,7 +72,7 @@
<TextBlock Text="Git" FontSize="16" FontWeight="SemiBold" Margin="0,8,0,10"/>
<TextBlock TextWrapping="Wrap" Foreground="{DynamicResource FgMuted}" Margin="0,0,0,8" FontSize="12"
Text="Repository badges use git.exe when it is installed. Leave the path empty to look in Program Files and PATH. Git is not bundled. Explorer Workbench does not commit, push, or pull."/>
Text="Repository badges and Git actions use git.exe when it is installed. Leave the path empty to look in Program Files and PATH. Git is not bundled. There is no branch UI or credential dialog."/>
<DockPanel Margin="0,0,0,6">
<Button DockPanel.Dock="Right" Content="Browse…" MinWidth="88" Height="28" Click="OnBrowseGit" Margin="8,0,0,0"/>
<TextBox x:Name="GitPath"/>

View File

@@ -19,4 +19,9 @@
<SolidColorBrush x:Key="ScrollThumb" Color="#5A5A5E"/>
<SolidColorBrush x:Key="ScrollThumbHover" Color="#7A7A7E"/>
<SolidColorBrush x:Key="ScrollThumbPressed" Color="#9A9A9E"/>
<SolidColorBrush x:Key="GitDiffAdded" Color="#81C784"/>
<SolidColorBrush x:Key="GitDiffRemoved" Color="#E57373"/>
<SolidColorBrush x:Key="GitDiffHunk" Color="#64B5F6"/>
<SolidColorBrush x:Key="GitDiffAddedBg" Color="#1B3A24"/>
<SolidColorBrush x:Key="GitDiffRemovedBg" Color="#3A1B1B"/>
</ResourceDictionary>

View File

@@ -19,4 +19,9 @@
<SolidColorBrush x:Key="ScrollThumb" Color="#B0B0B0"/>
<SolidColorBrush x:Key="ScrollThumbHover" Color="#8A8A8A"/>
<SolidColorBrush x:Key="ScrollThumbPressed" Color="#6A6A6A"/>
<SolidColorBrush x:Key="GitDiffAdded" Color="#1B7F3A"/>
<SolidColorBrush x:Key="GitDiffRemoved" Color="#C62828"/>
<SolidColorBrush x:Key="GitDiffHunk" Color="#1565C0"/>
<SolidColorBrush x:Key="GitDiffAddedBg" Color="#E8F5E9"/>
<SolidColorBrush x:Key="GitDiffRemovedBg" Color="#FFEBEE"/>
</ResourceDictionary>