In Local Changes, added filter-box in Staged area, to match Unstaged area (#1153)

Also added minimal handling (RaiseException) if trying to commit with active filter (might commit more changes than visible, so disallow).
Minor unification in unstageChanges() to make it more similar to StageChanges().
This commit is contained in:
Göran W 2025-04-07 04:37:58 +02:00 committed by GitHub
parent ac7b02590b
commit a37c6b29ec
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 92 additions and 17 deletions

View file

@ -109,12 +109,28 @@ namespace SourceGit.ViewModels
if (_isLoadingData)
return;
VisibleUnstaged = GetVisibleUnstagedChanges(_unstaged);
VisibleUnstaged = GetVisibleChanges(_unstaged, _unstagedFilter);
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
{
get => _unstaged;
@ -133,6 +149,12 @@ namespace SourceGit.ViewModels
private set => SetProperty(ref _staged, value);
}
public List<Models.Change> VisibleStaged
{
get => _visibleStaged;
private set => SetProperty(ref _visibleStaged, value);
}
public List<Models.Change> SelectedUnstaged
{
get => _selectedUnstaged;
@ -216,6 +238,9 @@ namespace SourceGit.ViewModels
_visibleUnstaged.Clear();
OnPropertyChanged(nameof(VisibleUnstaged));
_visibleStaged.Clear();
OnPropertyChanged(nameof(VisibleStaged));
_unstaged.Clear();
OnPropertyChanged(nameof(Unstaged));
@ -269,7 +294,7 @@ namespace SourceGit.ViewModels
}
}
var visibleUnstaged = GetVisibleUnstagedChanges(unstaged);
var visibleUnstaged = GetVisibleChanges(unstaged, _unstagedFilter);
var selectedUnstaged = new List<Models.Change>();
foreach (var c in visibleUnstaged)
{
@ -278,8 +303,10 @@ namespace SourceGit.ViewModels
}
var staged = GetStagedChanges();
var visibleStaged = GetVisibleChanges(staged, _stagedFilter);
var selectedStaged = new List<Models.Change>();
foreach (var c in staged)
foreach (var c in visibleStaged)
{
if (lastSelectedStaged.Contains(c.Path))
selectedStaged.Add(c);
@ -290,6 +317,7 @@ namespace SourceGit.ViewModels
_isLoadingData = true;
HasUnsolvedConflicts = hasConflict;
VisibleUnstaged = visibleUnstaged;
VisibleStaged = visibleStaged;
Unstaged = unstaged;
Staged = staged;
SelectedUnstaged = selectedUnstaged;
@ -337,7 +365,7 @@ namespace SourceGit.ViewModels
public void UnstageAll()
{
UnstageChanges(_staged, null);
UnstageChanges(_visibleStaged, null);
}
public void Discard(List<Models.Change> changes)
@ -350,6 +378,11 @@ namespace SourceGit.ViewModels
{
UnstagedFilter = string.Empty;
}
public void ClearStagedFilter()
{
StagedFilter = string.Empty;
}
public async void UseTheirs(List<Models.Change> changes)
{
@ -1472,16 +1505,16 @@ namespace SourceGit.ViewModels
return menu;
}
private List<Models.Change> GetVisibleUnstagedChanges(List<Models.Change> unstaged)
private List<Models.Change> GetVisibleChanges(List<Models.Change> changes, string filter)
{
if (string.IsNullOrEmpty(_unstagedFilter))
return unstaged;
if (string.IsNullOrEmpty(filter))
return changes;
var visible = new List<Models.Change>();
foreach (var c in unstaged)
foreach (var c in changes)
{
if (c.Path.Contains(_unstagedFilter, StringComparison.OrdinalIgnoreCase))
if (c.Path.Contains(filter, StringComparison.OrdinalIgnoreCase))
visible.Add(c);
}
@ -1599,7 +1632,8 @@ namespace SourceGit.ViewModels
private async void UnstageChanges(List<Models.Change> changes, Models.Change next)
{
if (changes.Count == 0)
var count = changes.Count;
if (count == 0)
return;
// Use `_selectedStaged` instead of `SelectedStaged` to avoid UI refresh.
@ -1611,16 +1645,15 @@ namespace SourceGit.ViewModels
{
await Task.Run(() => new Commands.UnstageChangesForAmend(_repo.FullPath, changes).Exec());
}
else if (changes.Count == _staged.Count)
else if (count == _staged.Count)
{
await Task.Run(() => new Commands.Reset(_repo.FullPath).Exec());
}
else
{
for (int i = 0; i < changes.Count; i += 10)
for (int i = 0; i < count; i += 10)
{
var count = Math.Min(10, changes.Count - i);
var step = changes.GetRange(i, count);
var step = changes.GetRange(i, Math.Min(10, count - i));
await Task.Run(() => new Commands.Reset(_repo.FullPath, step).Exec());
}
}
@ -1650,6 +1683,13 @@ namespace SourceGit.ViewModels
return;
}
if (!string.IsNullOrEmpty(_stagedFilter))
{
// FIXME - make this a proper warning message-box "Staged-area filter will not be applied to commit. Continue?" Yes/No
App.RaiseException(_repo.FullPath, "Committing with staged-area filter applied is NOT allowed!");
return;
}
if (string.IsNullOrWhiteSpace(_commitMessage))
{
App.RaiseException(_repo.FullPath, "Commit without message is NOT allowed!");
@ -1729,11 +1769,13 @@ namespace SourceGit.ViewModels
private List<Models.Change> _unstaged = [];
private List<Models.Change> _visibleUnstaged = [];
private List<Models.Change> _staged = [];
private List<Models.Change> _visibleStaged = [];
private List<Models.Change> _selectedUnstaged = [];
private List<Models.Change> _selectedStaged = [];
private int _count = 0;
private object _detailContext = null;
private string _unstagedFilter = string.Empty;
private string _stagedFilter = string.Empty;
private string _commitMessage = string.Empty;
private bool _hasUnsolvedConflicts = false;

View file

@ -129,7 +129,7 @@
Background="{DynamicResource Brush.Border0}"/>
<!-- Staged -->
<Grid Grid.Row="2" RowDefinitions="28,*">
<Grid Grid.Row="2" RowDefinitions="28,36,*">
<!-- Staged Toolbar -->
<Border Grid.Row="0" BorderThickness="0,0,0,1" BorderBrush="{DynamicResource Brush.Border0}">
<Grid ColumnDefinitions="Auto,Auto,Auto,Auto,*,Auto,Auto,Auto">
@ -156,14 +156,47 @@
</Grid>
</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 -->
<v:ChangeCollectionView Grid.Row="1"
<v:ChangeCollectionView Grid.Row="2"
x:Name="StagedChangesView"
Focusable="True"
IsUnstagedChange="False"
SelectionMode="Multiple"
Background="{DynamicResource Brush.Contents}"
ViewMode="{Binding Source={x:Static vm:Preferences.Instance}, Path=StagedChangeViewMode}"
Changes="{Binding Staged}"
Changes="{Binding VisibleStaged}"
SelectedChanges="{Binding SelectedStaged, Mode=TwoWay}"
ContextRequested="OnStagedContextRequested"
ChangeDoubleTapped="OnStagedChangeDoubleTapped"