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

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