code_review: PR #1153

- use a single filter for both unstage and staged files
- show confirm dialog if staged files are displayed partially

Signed-off-by: leo <longshuang@msn.cn>
This commit is contained in:
leo 2025-04-07 11:45:14 +08:00
parent a37c6b29ec
commit fa02c65da5
No known key found for this signature in database
9 changed files with 103 additions and 143 deletions

View file

@ -719,6 +719,7 @@
<x:String x:Key="Text.WorkingCopy.CommitTip" xml:space="preserve">Trigger click event</x:String> <x:String x:Key="Text.WorkingCopy.CommitTip" xml:space="preserve">Trigger click event</x:String>
<x:String x:Key="Text.WorkingCopy.CommitToEdit" xml:space="preserve">Commit (Edit)</x:String> <x:String x:Key="Text.WorkingCopy.CommitToEdit" xml:space="preserve">Commit (Edit)</x:String>
<x:String x:Key="Text.WorkingCopy.CommitWithAutoStage" xml:space="preserve">Stage all changes and commit</x:String> <x:String x:Key="Text.WorkingCopy.CommitWithAutoStage" xml:space="preserve">Stage all changes and commit</x:String>
<x:String x:Key="Text.WorkingCopy.ConfirmCommitWithFilter">You have staged {0} file(s) but only {1} file(s) displayed ({2} files are filtered out). Do you want to continue?</x:String>
<x:String x:Key="Text.WorkingCopy.ConfirmCommitWithoutFiles" xml:space="preserve">Empty commit detected! Do you want to continue (--allow-empty)?</x:String> <x:String x:Key="Text.WorkingCopy.ConfirmCommitWithoutFiles" xml:space="preserve">Empty commit detected! Do you want to continue (--allow-empty)?</x:String>
<x:String x:Key="Text.WorkingCopy.Conflicts" xml:space="preserve">CONFLICTS DETECTED</x:String> <x:String x:Key="Text.WorkingCopy.Conflicts" xml:space="preserve">CONFLICTS DETECTED</x:String>
<x:String x:Key="Text.WorkingCopy.Conflicts.Resolved" xml:space="preserve">FILE CONFLICTS ARE RESOLVED</x:String> <x:String x:Key="Text.WorkingCopy.Conflicts.Resolved" xml:space="preserve">FILE CONFLICTS ARE RESOLVED</x:String>

View file

@ -723,6 +723,7 @@
<x:String x:Key="Text.WorkingCopy.CommitTip" xml:space="preserve">触发点击事件</x:String> <x:String x:Key="Text.WorkingCopy.CommitTip" xml:space="preserve">触发点击事件</x:String>
<x:String x:Key="Text.WorkingCopy.CommitToEdit" xml:space="preserve">提交(修改原始提交)</x:String> <x:String x:Key="Text.WorkingCopy.CommitToEdit" xml:space="preserve">提交(修改原始提交)</x:String>
<x:String x:Key="Text.WorkingCopy.CommitWithAutoStage" xml:space="preserve">自动暂存所有变更并提交</x:String> <x:String x:Key="Text.WorkingCopy.CommitWithAutoStage" xml:space="preserve">自动暂存所有变更并提交</x:String>
<x:String x:Key="Text.WorkingCopy.ConfirmCommitWithFilter">当前有 {0} 个文件在暂存区中,但仅显示了 {1} 个文件({2} 个文件被过滤掉了),是否继续提交?</x:String>
<x:String x:Key="Text.WorkingCopy.ConfirmCommitWithoutFiles" xml:space="preserve">提交未包含变更文件!是否继续(--allow-empty)</x:String> <x:String x:Key="Text.WorkingCopy.ConfirmCommitWithoutFiles" xml:space="preserve">提交未包含变更文件!是否继续(--allow-empty)</x:String>
<x:String x:Key="Text.WorkingCopy.Conflicts" xml:space="preserve">检测到冲突</x:String> <x:String x:Key="Text.WorkingCopy.Conflicts" xml:space="preserve">检测到冲突</x:String>
<x:String x:Key="Text.WorkingCopy.Conflicts.Resolved" xml:space="preserve">文件冲突已解决</x:String> <x:String x:Key="Text.WorkingCopy.Conflicts.Resolved" xml:space="preserve">文件冲突已解决</x:String>

View file

@ -722,6 +722,7 @@
<x:String x:Key="Text.WorkingCopy.CommitTip" xml:space="preserve">觸發點擊事件</x:String> <x:String x:Key="Text.WorkingCopy.CommitTip" xml:space="preserve">觸發點擊事件</x:String>
<x:String x:Key="Text.WorkingCopy.CommitToEdit" xml:space="preserve">提交 (修改原始提交)</x:String> <x:String x:Key="Text.WorkingCopy.CommitToEdit" xml:space="preserve">提交 (修改原始提交)</x:String>
<x:String x:Key="Text.WorkingCopy.CommitWithAutoStage" xml:space="preserve">自動暫存全部變更並提交</x:String> <x:String x:Key="Text.WorkingCopy.CommitWithAutoStage" xml:space="preserve">自動暫存全部變更並提交</x:String>
<x:String x:Key="Text.WorkingCopy.ConfirmCommitWithFilter">您已暫存 {0} 檔案,但只顯示 {1} 檔案 ({2} 檔案被篩選器隱藏)。您要繼續嗎?</x:String>
<x:String x:Key="Text.WorkingCopy.ConfirmCommitWithoutFiles" xml:space="preserve">未包含任何檔案變更! 您是否仍要提交 (--allow-empty)?</x:String> <x:String x:Key="Text.WorkingCopy.ConfirmCommitWithoutFiles" xml:space="preserve">未包含任何檔案變更! 您是否仍要提交 (--allow-empty)?</x:String>
<x:String x:Key="Text.WorkingCopy.Conflicts" xml:space="preserve">檢測到衝突</x:String> <x:String x:Key="Text.WorkingCopy.Conflicts" xml:space="preserve">檢測到衝突</x:String>
<x:String x:Key="Text.WorkingCopy.Conflicts.Resolved" xml:space="preserve">檔案衝突已解決</x:String> <x:String x:Key="Text.WorkingCopy.Conflicts.Resolved" xml:space="preserve">檔案衝突已解決</x:String>

View file

@ -0,0 +1,26 @@
using System;
namespace SourceGit.ViewModels
{
public class ConfirmCommit
{
public string Message
{
get;
private set;
}
public ConfirmCommit(string message, Action onSure)
{
Message = message;
_onSure = onSure;
}
public void Continue()
{
_onSure?.Invoke();
}
private Action _onSure;
}
}

View file

@ -1,19 +0,0 @@
namespace SourceGit.ViewModels
{
public class ConfirmCommitWithoutFiles
{
public ConfirmCommitWithoutFiles(WorkingCopy wc, bool autoPush)
{
_wc = wc;
_autoPush = autoPush;
}
public void Continue()
{
_wc.CommitWithoutFiles(_autoPush);
}
private readonly WorkingCopy _wc;
private bool _autoPush;
}
}

View file

@ -99,38 +99,23 @@ namespace SourceGit.ViewModels
} }
} }
public string UnstagedFilter public string Filter
{ {
get => _unstagedFilter; get => _filter;
set set
{ {
if (SetProperty(ref _unstagedFilter, value)) if (SetProperty(ref _filter, value))
{ {
if (_isLoadingData) if (_isLoadingData)
return; return;
VisibleUnstaged = GetVisibleChanges(_unstaged, _unstagedFilter); VisibleUnstaged = GetVisibleChanges(_unstaged);
VisibleStaged = GetVisibleChanges(_staged);
SelectedUnstaged = []; SelectedUnstaged = [];
} }
} }
} }
public string StagedFilter
{
get => _stagedFilter;
set
{
if (SetProperty(ref _stagedFilter, value))
{
if (_isLoadingData)
return;
VisibleStaged = GetVisibleChanges(_staged, _stagedFilter);
SelectedStaged = [];
}
}
}
public List<Models.Change> Unstaged public List<Models.Change> Unstaged
{ {
get => _unstaged; get => _unstaged;
@ -294,7 +279,7 @@ namespace SourceGit.ViewModels
} }
} }
var visibleUnstaged = GetVisibleChanges(unstaged, _unstagedFilter); var visibleUnstaged = GetVisibleChanges(unstaged);
var selectedUnstaged = new List<Models.Change>(); var selectedUnstaged = new List<Models.Change>();
foreach (var c in visibleUnstaged) foreach (var c in visibleUnstaged)
{ {
@ -304,7 +289,7 @@ namespace SourceGit.ViewModels
var staged = GetStagedChanges(); var staged = GetStagedChanges();
var visibleStaged = GetVisibleChanges(staged, _stagedFilter); var visibleStaged = GetVisibleChanges(staged);
var selectedStaged = new List<Models.Change>(); var selectedStaged = new List<Models.Change>();
foreach (var c in visibleStaged) foreach (var c in visibleStaged)
{ {
@ -374,14 +359,9 @@ namespace SourceGit.ViewModels
_repo.ShowPopup(new Discard(_repo, changes)); _repo.ShowPopup(new Discard(_repo, changes));
} }
public void ClearUnstagedFilter() public void ClearFilter()
{ {
UnstagedFilter = string.Empty; Filter = string.Empty;
}
public void ClearStagedFilter()
{
StagedFilter = string.Empty;
} }
public async void UseTheirs(List<Models.Change> changes) public async void UseTheirs(List<Models.Change> changes)
@ -571,11 +551,6 @@ namespace SourceGit.ViewModels
DoCommit(false, true, false); DoCommit(false, true, false);
} }
public void CommitWithoutFiles(bool autoPush)
{
DoCommit(false, autoPush, true);
}
public ContextMenu CreateContextMenuForUnstagedChanges() public ContextMenu CreateContextMenuForUnstagedChanges()
{ {
if (_selectedUnstaged == null || _selectedUnstaged.Count == 0) if (_selectedUnstaged == null || _selectedUnstaged.Count == 0)
@ -1505,16 +1480,16 @@ namespace SourceGit.ViewModels
return menu; return menu;
} }
private List<Models.Change> GetVisibleChanges(List<Models.Change> changes, string filter) private List<Models.Change> GetVisibleChanges(List<Models.Change> changes)
{ {
if (string.IsNullOrEmpty(filter)) if (string.IsNullOrEmpty(_filter))
return changes; return changes;
var visible = new List<Models.Change>(); var visible = new List<Models.Change>();
foreach (var c in changes) foreach (var c in changes)
{ {
if (c.Path.Contains(filter, StringComparison.OrdinalIgnoreCase)) if (c.Path.Contains(_filter, StringComparison.OrdinalIgnoreCase))
visible.Add(c); visible.Add(c);
} }
@ -1675,7 +1650,7 @@ namespace SourceGit.ViewModels
DetailContext = new DiffContext(_repo.FullPath, new Models.DiffOption(change, isUnstaged), _detailContext as DiffContext); DetailContext = new DiffContext(_repo.FullPath, new Models.DiffOption(change, isUnstaged), _detailContext as DiffContext);
} }
private void DoCommit(bool autoStage, bool autoPush, bool allowEmpty) private void DoCommit(bool autoStage, bool autoPush, bool allowEmpty = false, bool confirmWithFilter = false)
{ {
if (!_repo.CanCreatePopup()) if (!_repo.CanCreatePopup())
{ {
@ -1683,10 +1658,17 @@ namespace SourceGit.ViewModels
return; return;
} }
if (!string.IsNullOrEmpty(_stagedFilter)) if (!string.IsNullOrEmpty(_filter) && _staged.Count > _visibleStaged.Count && !confirmWithFilter)
{ {
// FIXME - make this a proper warning message-box "Staged-area filter will not be applied to commit. Continue?" Yes/No var confirmMessage = App.Text("WorkingCopy.ConfirmCommitWithFilter", _staged.Count, _visibleStaged.Count, _staged.Count - _visibleStaged.Count);
App.RaiseException(_repo.FullPath, "Committing with staged-area filter applied is NOT allowed!"); App.OpenDialog(new Views.ConfirmCommit()
{
DataContext = new ConfirmCommit(confirmMessage, () =>
{
DoCommit(autoStage, autoPush, allowEmpty, true);
})
});
return; return;
} }
@ -1700,9 +1682,13 @@ namespace SourceGit.ViewModels
{ {
if ((autoStage && _count == 0) || (!autoStage && _staged.Count == 0)) if ((autoStage && _count == 0) || (!autoStage && _staged.Count == 0))
{ {
App.OpenDialog(new Views.ConfirmCommitWithoutFiles() var confirmMessage = App.Text("WorkingCopy.ConfirmCommitWithoutFiles");
App.OpenDialog(new Views.ConfirmCommit()
{ {
DataContext = new ConfirmCommitWithoutFiles(this, autoPush) DataContext = new ConfirmCommit(confirmMessage, () =>
{
DoCommit(autoStage, autoPush, true, confirmWithFilter);
})
}); });
return; return;
@ -1774,8 +1760,7 @@ namespace SourceGit.ViewModels
private List<Models.Change> _selectedStaged = []; private List<Models.Change> _selectedStaged = [];
private int _count = 0; private int _count = 0;
private object _detailContext = null; private object _detailContext = null;
private string _unstagedFilter = string.Empty; private string _filter = string.Empty;
private string _stagedFilter = string.Empty;
private string _commitMessage = string.Empty; private string _commitMessage = string.Empty;
private bool _hasUnsolvedConflicts = false; private bool _hasUnsolvedConflicts = false;

View file

@ -5,8 +5,8 @@
xmlns:v="using:SourceGit.Views" xmlns:v="using:SourceGit.Views"
xmlns:vm="using:SourceGit.ViewModels" xmlns:vm="using:SourceGit.ViewModels"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="SourceGit.Views.ConfirmCommitWithoutFiles" x:Class="SourceGit.Views.ConfirmCommit"
x:DataType="vm:ConfirmCommitWithoutFiles" x:DataType="vm:ConfirmCommit"
x:Name="ThisControl" x:Name="ThisControl"
Icon="/App.ico" Icon="/App.ico"
Title="{DynamicResource Text.Warn}" Title="{DynamicResource Text.Warn}"
@ -38,7 +38,7 @@
<!-- Body --> <!-- Body -->
<Border Grid.Row="1" Margin="16"> <Border Grid.Row="1" Margin="16">
<TextBlock Text="{DynamicResource Text.WorkingCopy.ConfirmCommitWithoutFiles}"/> <TextBlock Text="{Binding Message}" MaxWidth="400" TextWrapping="Wrap"/>
</Border> </Border>
<!-- Buttons --> <!-- Buttons -->

View file

@ -2,20 +2,16 @@ using Avalonia.Interactivity;
namespace SourceGit.Views namespace SourceGit.Views
{ {
public partial class ConfirmCommitWithoutFiles : ChromelessWindow public partial class ConfirmCommit : ChromelessWindow
{ {
public ConfirmCommitWithoutFiles() public ConfirmCommit()
{ {
InitializeComponent(); InitializeComponent();
} }
private void Sure(object _1, RoutedEventArgs _2) private void Sure(object _1, RoutedEventArgs _2)
{ {
if (DataContext is ViewModels.ConfirmCommitWithoutFiles vm) (DataContext as ViewModels.ConfirmCommit)?.Continue();
{
vm.Continue();
}
Close(); Close();
} }

View file

@ -19,13 +19,46 @@
<!-- Left --> <!-- Left -->
<Grid Grid.Column="0"> <Grid Grid.Column="0">
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="36"/>
<RowDefinition Height="*" MinHeight="100"/> <RowDefinition Height="*" MinHeight="100"/>
<RowDefinition Height="1"/> <RowDefinition Height="1"/>
<RowDefinition Height="*" MinHeight="100"/> <RowDefinition Height="*" MinHeight="100"/>
</Grid.RowDefinitions> </Grid.RowDefinitions>
<!-- Search Filter -->
<Border Grid.Row="0" BorderThickness="0,0,0,1" BorderBrush="{DynamicResource Brush.Border0}">
<TextBox Height="24"
Margin="4,0"
BorderThickness="1"
CornerRadius="12"
Text="{Binding Filter, Mode=TwoWay}"
BorderBrush="{DynamicResource Brush.Border2}"
VerticalContentAlignment="Center">
<TextBox.InnerLeftContent>
<Path Width="14" Height="14"
Margin="6,0,0,0"
Fill="{DynamicResource Brush.FG2}"
Data="{StaticResource Icons.Search}"/>
</TextBox.InnerLeftContent>
<TextBox.InnerRightContent>
<Button Classes="icon_button"
Width="16"
Margin="0,0,6,0"
Command="{Binding ClearFilter}"
IsVisible="{Binding Filter, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"
HorizontalAlignment="Right">
<Path Width="14" Height="14"
Margin="0,1,0,0"
Fill="{DynamicResource Brush.FG1}"
Data="{StaticResource Icons.Clear}"/>
</Button>
</TextBox.InnerRightContent>
</TextBox>
</Border>
<!-- Unstaged --> <!-- Unstaged -->
<Grid Grid.Row="0" RowDefinitions="28,36,*"> <Grid Grid.Row="1" RowDefinitions="28,*">
<!-- Unstaged Toolbar --> <!-- Unstaged Toolbar -->
<Border Grid.Row="0" BorderThickness="0,0,0,1" BorderBrush="{DynamicResource Brush.Border0}"> <Border Grid.Row="0" BorderThickness="0,0,0,1" BorderBrush="{DynamicResource Brush.Border0}">
<Grid ColumnDefinitions="Auto,Auto,Auto,Auto,*,Auto,Auto,Auto,Auto,Auto"> <Grid ColumnDefinitions="Auto,Auto,Auto,Auto,*,Auto,Auto,Auto,Auto,Auto">
@ -75,40 +108,8 @@
</Grid> </Grid>
</Border> </Border>
<!-- Unstaged Filter -->
<Border Grid.Row="1" BorderThickness="0,0,0,1" BorderBrush="{DynamicResource Brush.Border0}">
<TextBox Height="24"
Margin="4,0"
BorderThickness="1"
CornerRadius="12"
Text="{Binding UnstagedFilter, Mode=TwoWay}"
BorderBrush="{DynamicResource Brush.Border2}"
VerticalContentAlignment="Center">
<TextBox.InnerLeftContent>
<Path Width="14" Height="14"
Margin="6,0,0,0"
Fill="{DynamicResource Brush.FG2}"
Data="{StaticResource Icons.Search}"/>
</TextBox.InnerLeftContent>
<TextBox.InnerRightContent>
<Button Classes="icon_button"
Width="16"
Margin="0,0,6,0"
Command="{Binding ClearUnstagedFilter}"
IsVisible="{Binding UnstagedFilter, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"
HorizontalAlignment="Right">
<Path Width="14" Height="14"
Margin="0,1,0,0"
Fill="{DynamicResource Brush.FG1}"
Data="{StaticResource Icons.Clear}"/>
</Button>
</TextBox.InnerRightContent>
</TextBox>
</Border>
<!-- Unstaged Changes --> <!-- Unstaged Changes -->
<v:ChangeCollectionView Grid.Row="2" <v:ChangeCollectionView Grid.Row="1"
x:Name="UnstagedChangesView" x:Name="UnstagedChangesView"
Focusable="True" Focusable="True"
IsUnstagedChange="True" IsUnstagedChange="True"
@ -123,13 +124,13 @@
</Grid> </Grid>
<!-- Splitter --> <!-- Splitter -->
<GridSplitter Grid.Row="1" <GridSplitter Grid.Row="2"
MinHeight="1" MinHeight="1"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
Background="{DynamicResource Brush.Border0}"/> Background="{DynamicResource Brush.Border0}"/>
<!-- Staged --> <!-- Staged -->
<Grid Grid.Row="2" RowDefinitions="28,36,*"> <Grid Grid.Row="3" RowDefinitions="28,*">
<!-- Staged Toolbar --> <!-- Staged Toolbar -->
<Border Grid.Row="0" BorderThickness="0,0,0,1" BorderBrush="{DynamicResource Brush.Border0}"> <Border Grid.Row="0" BorderThickness="0,0,0,1" BorderBrush="{DynamicResource Brush.Border0}">
<Grid ColumnDefinitions="Auto,Auto,Auto,Auto,*,Auto,Auto,Auto"> <Grid ColumnDefinitions="Auto,Auto,Auto,Auto,*,Auto,Auto,Auto">
@ -156,40 +157,8 @@
</Grid> </Grid>
</Border> </Border>
<!-- Staged Filter -->
<Border Grid.Row="1" BorderThickness="0,0,0,1" BorderBrush="{DynamicResource Brush.Border0}">
<TextBox Height="24"
Margin="4,0"
BorderThickness="1"
CornerRadius="12"
Text="{Binding StagedFilter, Mode=TwoWay}"
BorderBrush="{DynamicResource Brush.Border2}"
VerticalContentAlignment="Center">
<TextBox.InnerLeftContent>
<Path Width="14" Height="14"
Margin="6,0,0,0"
Fill="{DynamicResource Brush.FG2}"
Data="{StaticResource Icons.Search}"/>
</TextBox.InnerLeftContent>
<TextBox.InnerRightContent>
<Button Classes="icon_button"
Width="16"
Margin="0,0,6,0"
Command="{Binding ClearStagedFilter}"
IsVisible="{Binding StagedFilter, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"
HorizontalAlignment="Right">
<Path Width="14" Height="14"
Margin="0,1,0,0"
Fill="{DynamicResource Brush.FG1}"
Data="{StaticResource Icons.Clear}"/>
</Button>
</TextBox.InnerRightContent>
</TextBox>
</Border>
<!-- Staged Changes --> <!-- Staged Changes -->
<v:ChangeCollectionView Grid.Row="2" <v:ChangeCollectionView Grid.Row="1"
x:Name="StagedChangesView" x:Name="StagedChangesView"
Focusable="True" Focusable="True"
IsUnstagedChange="False" IsUnstagedChange="False"