mirror of
https://github.com/sourcegit-scm/sourcegit
synced 2025-05-21 12:15:00 +00:00
enhance: supports searching/filtering unstaged changes (#960)
Signed-off-by: leo <longshuang@msn.cn>
This commit is contained in:
parent
35abeae758
commit
8cc056d2af
2 changed files with 165 additions and 81 deletions
|
@ -99,12 +99,34 @@ namespace SourceGit.ViewModels
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public string UnstagedFilter
|
||||||
|
{
|
||||||
|
get => _unstagedFilter;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (SetProperty(ref _unstagedFilter, value))
|
||||||
|
{
|
||||||
|
if (_isLoadingData)
|
||||||
|
return;
|
||||||
|
|
||||||
|
VisibleUnstaged = GetVisibleUnstagedChanges();
|
||||||
|
SelectedUnstaged = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public List<Models.Change> Unstaged
|
public List<Models.Change> Unstaged
|
||||||
{
|
{
|
||||||
get => _unstaged;
|
get => _unstaged;
|
||||||
private set => SetProperty(ref _unstaged, value);
|
private set => SetProperty(ref _unstaged, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<Models.Change> VisibleUnstaged
|
||||||
|
{
|
||||||
|
get => _visibleUnstaged;
|
||||||
|
private set => SetProperty(ref _visibleUnstaged, value);
|
||||||
|
}
|
||||||
|
|
||||||
public List<Models.Change> Staged
|
public List<Models.Change> Staged
|
||||||
{
|
{
|
||||||
get => _staged;
|
get => _staged;
|
||||||
|
@ -191,8 +213,9 @@ namespace SourceGit.ViewModels
|
||||||
_selectedStaged.Clear();
|
_selectedStaged.Clear();
|
||||||
OnPropertyChanged(nameof(SelectedStaged));
|
OnPropertyChanged(nameof(SelectedStaged));
|
||||||
|
|
||||||
|
_visibleUnstaged.Clear();
|
||||||
_unstaged.Clear();
|
_unstaged.Clear();
|
||||||
OnPropertyChanged(nameof(Unstaged));
|
OnPropertyChanged(nameof(VisibleUnstaged));
|
||||||
|
|
||||||
_staged.Clear();
|
_staged.Clear();
|
||||||
OnPropertyChanged(nameof(Staged));
|
OnPropertyChanged(nameof(Staged));
|
||||||
|
@ -249,7 +272,6 @@ namespace SourceGit.ViewModels
|
||||||
}
|
}
|
||||||
|
|
||||||
var unstaged = new List<Models.Change>();
|
var unstaged = new List<Models.Change>();
|
||||||
var selectedUnstaged = new List<Models.Change>();
|
|
||||||
var hasConflict = false;
|
var hasConflict = false;
|
||||||
foreach (var c in changes)
|
foreach (var c in changes)
|
||||||
{
|
{
|
||||||
|
@ -257,12 +279,19 @@ namespace SourceGit.ViewModels
|
||||||
{
|
{
|
||||||
unstaged.Add(c);
|
unstaged.Add(c);
|
||||||
hasConflict |= c.IsConflit;
|
hasConflict |= c.IsConflit;
|
||||||
|
|
||||||
if (lastSelectedUnstaged.Contains(c.Path))
|
|
||||||
selectedUnstaged.Add(c);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_unstaged = unstaged;
|
||||||
|
|
||||||
|
var visibleUnstaged = GetVisibleUnstagedChanges();
|
||||||
|
var selectedUnstaged = new List<Models.Change>();
|
||||||
|
foreach (var c in visibleUnstaged)
|
||||||
|
{
|
||||||
|
if (lastSelectedUnstaged.Contains(c.Path))
|
||||||
|
selectedUnstaged.Add(c);
|
||||||
|
}
|
||||||
|
|
||||||
var staged = GetStagedChanges();
|
var staged = GetStagedChanges();
|
||||||
var selectedStaged = new List<Models.Change>();
|
var selectedStaged = new List<Models.Change>();
|
||||||
foreach (var c in staged)
|
foreach (var c in staged)
|
||||||
|
@ -275,7 +304,7 @@ namespace SourceGit.ViewModels
|
||||||
{
|
{
|
||||||
_isLoadingData = true;
|
_isLoadingData = true;
|
||||||
HasUnsolvedConflicts = hasConflict;
|
HasUnsolvedConflicts = hasConflict;
|
||||||
Unstaged = unstaged;
|
VisibleUnstaged = visibleUnstaged;
|
||||||
Staged = staged;
|
Staged = staged;
|
||||||
SelectedUnstaged = selectedUnstaged;
|
SelectedUnstaged = selectedUnstaged;
|
||||||
SelectedStaged = selectedStaged;
|
SelectedStaged = selectedStaged;
|
||||||
|
@ -336,46 +365,7 @@ namespace SourceGit.ViewModels
|
||||||
|
|
||||||
public void StageAll()
|
public void StageAll()
|
||||||
{
|
{
|
||||||
StageChanges(_unstaged, null);
|
StageChanges(_visibleUnstaged, null);
|
||||||
}
|
|
||||||
|
|
||||||
public async void StageChanges(List<Models.Change> changes, Models.Change next)
|
|
||||||
{
|
|
||||||
if (_unstaged.Count == 0 || changes.Count == 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// Use `_selectedUnstaged` instead of `SelectedUnstaged` to avoid UI refresh.
|
|
||||||
_selectedUnstaged = next != null ? [next] : [];
|
|
||||||
|
|
||||||
IsStaging = true;
|
|
||||||
_repo.SetWatcherEnabled(false);
|
|
||||||
if (changes.Count == _unstaged.Count)
|
|
||||||
{
|
|
||||||
await Task.Run(() => new Commands.Add(_repo.FullPath, _repo.IncludeUntracked).Exec());
|
|
||||||
}
|
|
||||||
else if (Native.OS.GitVersion >= Models.GitVersions.ADD_WITH_PATHSPECFILE)
|
|
||||||
{
|
|
||||||
var paths = new List<string>();
|
|
||||||
foreach (var c in changes)
|
|
||||||
paths.Add(c.Path);
|
|
||||||
|
|
||||||
var tmpFile = Path.GetTempFileName();
|
|
||||||
File.WriteAllLines(tmpFile, paths);
|
|
||||||
await Task.Run(() => new Commands.Add(_repo.FullPath, tmpFile).Exec());
|
|
||||||
File.Delete(tmpFile);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
for (int i = 0; i < changes.Count; i += 10)
|
|
||||||
{
|
|
||||||
var count = Math.Min(10, changes.Count - i);
|
|
||||||
var step = changes.GetRange(i, count);
|
|
||||||
await Task.Run(() => new Commands.Add(_repo.FullPath, step).Exec());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_repo.MarkWorkingCopyDirtyManually();
|
|
||||||
_repo.SetWatcherEnabled(true);
|
|
||||||
IsStaging = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UnstageSelected(Models.Change next)
|
public void UnstageSelected(Models.Change next)
|
||||||
|
@ -388,44 +378,17 @@ namespace SourceGit.ViewModels
|
||||||
UnstageChanges(_staged, null);
|
UnstageChanges(_staged, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async void UnstageChanges(List<Models.Change> changes, Models.Change next)
|
|
||||||
{
|
|
||||||
if (_staged.Count == 0 || changes.Count == 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// Use `_selectedStaged` instead of `SelectedStaged` to avoid UI refresh.
|
|
||||||
_selectedStaged = next != null ? [next] : [];
|
|
||||||
|
|
||||||
IsUnstaging = true;
|
|
||||||
_repo.SetWatcherEnabled(false);
|
|
||||||
if (_useAmend)
|
|
||||||
{
|
|
||||||
await Task.Run(() => new Commands.UnstageChangesForAmend(_repo.FullPath, changes).Exec());
|
|
||||||
}
|
|
||||||
else if (changes.Count == _staged.Count)
|
|
||||||
{
|
|
||||||
await Task.Run(() => new Commands.Reset(_repo.FullPath).Exec());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
for (int i = 0; i < changes.Count; i += 10)
|
|
||||||
{
|
|
||||||
var count = Math.Min(10, changes.Count - i);
|
|
||||||
var step = changes.GetRange(i, count);
|
|
||||||
await Task.Run(() => new Commands.Reset(_repo.FullPath, step).Exec());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_repo.MarkWorkingCopyDirtyManually();
|
|
||||||
_repo.SetWatcherEnabled(true);
|
|
||||||
IsUnstaging = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Discard(List<Models.Change> changes)
|
public void Discard(List<Models.Change> changes)
|
||||||
{
|
{
|
||||||
if (_repo.CanCreatePopup())
|
if (_repo.CanCreatePopup())
|
||||||
_repo.ShowPopup(new Discard(_repo, changes));
|
_repo.ShowPopup(new Discard(_repo, changes));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void ClearUnstagedFilter()
|
||||||
|
{
|
||||||
|
UnstagedFilter = string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
public async void UseTheirs(List<Models.Change> changes)
|
public async void UseTheirs(List<Models.Change> changes)
|
||||||
{
|
{
|
||||||
var files = new List<string>();
|
var files = new List<string>();
|
||||||
|
@ -1496,6 +1459,22 @@ namespace SourceGit.ViewModels
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private List<Models.Change> GetVisibleUnstagedChanges()
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(_unstagedFilter))
|
||||||
|
return _unstaged;
|
||||||
|
|
||||||
|
var visible = new List<Models.Change>();
|
||||||
|
|
||||||
|
foreach (var c in _unstaged)
|
||||||
|
{
|
||||||
|
if (c.Path.Contains(_unstagedFilter, StringComparison.OrdinalIgnoreCase))
|
||||||
|
visible.Add(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
return visible;
|
||||||
|
}
|
||||||
|
|
||||||
private List<Models.Change> GetStagedChanges()
|
private List<Models.Change> GetStagedChanges()
|
||||||
{
|
{
|
||||||
if (_useAmend)
|
if (_useAmend)
|
||||||
|
@ -1511,6 +1490,77 @@ namespace SourceGit.ViewModels
|
||||||
return rs;
|
return rs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async void StageChanges(List<Models.Change> changes, Models.Change next)
|
||||||
|
{
|
||||||
|
if (changes.Count == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Use `_selectedUnstaged` instead of `SelectedUnstaged` to avoid UI refresh.
|
||||||
|
_selectedUnstaged = next != null ? [next] : [];
|
||||||
|
|
||||||
|
IsStaging = true;
|
||||||
|
_repo.SetWatcherEnabled(false);
|
||||||
|
if (changes.Count == _unstaged.Count)
|
||||||
|
{
|
||||||
|
await Task.Run(() => new Commands.Add(_repo.FullPath, _repo.IncludeUntracked).Exec());
|
||||||
|
}
|
||||||
|
else if (Native.OS.GitVersion >= Models.GitVersions.ADD_WITH_PATHSPECFILE)
|
||||||
|
{
|
||||||
|
var paths = new List<string>();
|
||||||
|
foreach (var c in changes)
|
||||||
|
paths.Add(c.Path);
|
||||||
|
|
||||||
|
var tmpFile = Path.GetTempFileName();
|
||||||
|
File.WriteAllLines(tmpFile, paths);
|
||||||
|
await Task.Run(() => new Commands.Add(_repo.FullPath, tmpFile).Exec());
|
||||||
|
File.Delete(tmpFile);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for (int i = 0; i < changes.Count; i += 10)
|
||||||
|
{
|
||||||
|
var count = Math.Min(10, changes.Count - i);
|
||||||
|
var step = changes.GetRange(i, count);
|
||||||
|
await Task.Run(() => new Commands.Add(_repo.FullPath, step).Exec());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_repo.MarkWorkingCopyDirtyManually();
|
||||||
|
_repo.SetWatcherEnabled(true);
|
||||||
|
IsStaging = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void UnstageChanges(List<Models.Change> changes, Models.Change next)
|
||||||
|
{
|
||||||
|
if (changes.Count == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Use `_selectedStaged` instead of `SelectedStaged` to avoid UI refresh.
|
||||||
|
_selectedStaged = next != null ? [next] : [];
|
||||||
|
|
||||||
|
IsUnstaging = true;
|
||||||
|
_repo.SetWatcherEnabled(false);
|
||||||
|
if (_useAmend)
|
||||||
|
{
|
||||||
|
await Task.Run(() => new Commands.UnstageChangesForAmend(_repo.FullPath, changes).Exec());
|
||||||
|
}
|
||||||
|
else if (changes.Count == _staged.Count)
|
||||||
|
{
|
||||||
|
await Task.Run(() => new Commands.Reset(_repo.FullPath).Exec());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for (int i = 0; i < changes.Count; i += 10)
|
||||||
|
{
|
||||||
|
var count = Math.Min(10, changes.Count - i);
|
||||||
|
var step = changes.GetRange(i, count);
|
||||||
|
await Task.Run(() => new Commands.Reset(_repo.FullPath, step).Exec());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_repo.MarkWorkingCopyDirtyManually();
|
||||||
|
_repo.SetWatcherEnabled(true);
|
||||||
|
IsUnstaging = false;
|
||||||
|
}
|
||||||
|
|
||||||
private void SetDetail(Models.Change change, bool isUnstaged)
|
private void SetDetail(Models.Change change, bool isUnstaged)
|
||||||
{
|
{
|
||||||
if (_isLoadingData)
|
if (_isLoadingData)
|
||||||
|
@ -1609,11 +1659,13 @@ namespace SourceGit.ViewModels
|
||||||
private bool _hasRemotes = false;
|
private bool _hasRemotes = false;
|
||||||
private List<Models.Change> _cached = [];
|
private List<Models.Change> _cached = [];
|
||||||
private List<Models.Change> _unstaged = [];
|
private List<Models.Change> _unstaged = [];
|
||||||
|
private List<Models.Change> _visibleUnstaged = [];
|
||||||
private List<Models.Change> _staged = [];
|
private List<Models.Change> _staged = [];
|
||||||
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 _commitMessage = string.Empty;
|
private string _commitMessage = string.Empty;
|
||||||
|
|
||||||
private bool _hasUnsolvedConflicts = false;
|
private bool _hasUnsolvedConflicts = false;
|
||||||
|
|
|
@ -25,7 +25,7 @@
|
||||||
</Grid.RowDefinitions>
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
<!-- Unstaged -->
|
<!-- Unstaged -->
|
||||||
<Grid Grid.Row="0" RowDefinitions="28,*">
|
<Grid Grid.Row="0" RowDefinitions="28,36,*">
|
||||||
<!-- 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,15 +75,47 @@
|
||||||
</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="1"
|
<v:ChangeCollectionView Grid.Row="2"
|
||||||
x:Name="UnstagedChangesView"
|
x:Name="UnstagedChangesView"
|
||||||
Focusable="True"
|
Focusable="True"
|
||||||
IsUnstagedChange="True"
|
IsUnstagedChange="True"
|
||||||
SelectionMode="Multiple"
|
SelectionMode="Multiple"
|
||||||
Background="{DynamicResource Brush.Contents}"
|
Background="{DynamicResource Brush.Contents}"
|
||||||
ViewMode="{Binding Source={x:Static vm:Preferences.Instance}, Path=UnstagedChangeViewMode}"
|
ViewMode="{Binding Source={x:Static vm:Preferences.Instance}, Path=UnstagedChangeViewMode}"
|
||||||
Changes="{Binding Unstaged}"
|
Changes="{Binding VisibleUnstaged}"
|
||||||
SelectedChanges="{Binding SelectedUnstaged, Mode=TwoWay}"
|
SelectedChanges="{Binding SelectedUnstaged, Mode=TwoWay}"
|
||||||
ContextRequested="OnUnstagedContextRequested"
|
ContextRequested="OnUnstagedContextRequested"
|
||||||
ChangeDoubleTapped="OnUnstagedChangeDoubleTapped"
|
ChangeDoubleTapped="OnUnstagedChangeDoubleTapped"
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue