From 8cc056d2af52cda0c56776955d759d10a5b6ac14 Mon Sep 17 00:00:00 2001 From: leo Date: Sat, 8 Feb 2025 17:16:56 +0800 Subject: [PATCH] enhance: supports searching/filtering unstaged changes (#960) Signed-off-by: leo --- src/ViewModels/WorkingCopy.cs | 208 +++++++++++++++++++++------------- src/Views/WorkingCopy.axaml | 38 ++++++- 2 files changed, 165 insertions(+), 81 deletions(-) diff --git a/src/ViewModels/WorkingCopy.cs b/src/ViewModels/WorkingCopy.cs index 12bf1068..ecb3c0d6 100644 --- a/src/ViewModels/WorkingCopy.cs +++ b/src/ViewModels/WorkingCopy.cs @@ -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 Unstaged { get => _unstaged; private set => SetProperty(ref _unstaged, value); } + public List VisibleUnstaged + { + get => _visibleUnstaged; + private set => SetProperty(ref _visibleUnstaged, value); + } + public List Staged { get => _staged; @@ -191,8 +213,9 @@ namespace SourceGit.ViewModels _selectedStaged.Clear(); OnPropertyChanged(nameof(SelectedStaged)); + _visibleUnstaged.Clear(); _unstaged.Clear(); - OnPropertyChanged(nameof(Unstaged)); + OnPropertyChanged(nameof(VisibleUnstaged)); _staged.Clear(); OnPropertyChanged(nameof(Staged)); @@ -249,7 +272,6 @@ namespace SourceGit.ViewModels } var unstaged = new List(); - var selectedUnstaged = new List(); var hasConflict = false; foreach (var c in changes) { @@ -257,12 +279,19 @@ namespace SourceGit.ViewModels { unstaged.Add(c); hasConflict |= c.IsConflit; - - if (lastSelectedUnstaged.Contains(c.Path)) - selectedUnstaged.Add(c); } } + _unstaged = unstaged; + + var visibleUnstaged = GetVisibleUnstagedChanges(); + var selectedUnstaged = new List(); + foreach (var c in visibleUnstaged) + { + if (lastSelectedUnstaged.Contains(c.Path)) + selectedUnstaged.Add(c); + } + var staged = GetStagedChanges(); var selectedStaged = new List(); foreach (var c in staged) @@ -275,7 +304,7 @@ namespace SourceGit.ViewModels { _isLoadingData = true; HasUnsolvedConflicts = hasConflict; - Unstaged = unstaged; + VisibleUnstaged = visibleUnstaged; Staged = staged; SelectedUnstaged = selectedUnstaged; SelectedStaged = selectedStaged; @@ -336,46 +365,7 @@ namespace SourceGit.ViewModels public void StageAll() { - StageChanges(_unstaged, null); - } - - public async void StageChanges(List 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(); - 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; + StageChanges(_visibleUnstaged, null); } public void UnstageSelected(Models.Change next) @@ -388,44 +378,17 @@ namespace SourceGit.ViewModels UnstageChanges(_staged, null); } - public async void UnstageChanges(List 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 changes) { if (_repo.CanCreatePopup()) _repo.ShowPopup(new Discard(_repo, changes)); } + public void ClearUnstagedFilter() + { + UnstagedFilter = string.Empty; + } + public async void UseTheirs(List changes) { var files = new List(); @@ -1496,6 +1459,22 @@ namespace SourceGit.ViewModels } } + private List GetVisibleUnstagedChanges() + { + if (string.IsNullOrEmpty(_unstagedFilter)) + return _unstaged; + + var visible = new List(); + + foreach (var c in _unstaged) + { + if (c.Path.Contains(_unstagedFilter, StringComparison.OrdinalIgnoreCase)) + visible.Add(c); + } + + return visible; + } + private List GetStagedChanges() { if (_useAmend) @@ -1511,6 +1490,77 @@ namespace SourceGit.ViewModels return rs; } + private async void StageChanges(List 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(); + 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 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) { if (_isLoadingData) @@ -1609,11 +1659,13 @@ namespace SourceGit.ViewModels private bool _hasRemotes = false; private List _cached = []; private List _unstaged = []; + private List _visibleUnstaged = []; private List _staged = []; private List _selectedUnstaged = []; private List _selectedStaged = []; private int _count = 0; private object _detailContext = null; + private string _unstagedFilter = string.Empty; private string _commitMessage = string.Empty; private bool _hasUnsolvedConflicts = false; diff --git a/src/Views/WorkingCopy.axaml b/src/Views/WorkingCopy.axaml index c22fb32d..84b60c14 100644 --- a/src/Views/WorkingCopy.axaml +++ b/src/Views/WorkingCopy.axaml @@ -25,7 +25,7 @@ - + @@ -75,15 +75,47 @@ + + + + + + + + + + + + + -