refactor: rewrite the histories filter function to supports both include and exclude modes (#690)

Signed-off-by: leo <longshuang@msn.cn>
This commit is contained in:
leo 2024-11-13 21:45:28 +08:00
parent e3ffe3ef6c
commit ca5bc4b4df
No known key found for this signature in database
27 changed files with 767 additions and 309 deletions

View file

@ -1,9 +1,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using Avalonia;
using Avalonia.Collections;
using Avalonia.Media;
using CommunityToolkit.Mvvm.ComponentModel;
@ -13,15 +11,16 @@ namespace SourceGit.ViewModels
public class BranchTreeNode : ObservableObject
{
public string Name { get; private set; } = string.Empty;
public string Path { get; private set; } = string.Empty;
public object Backend { get; private set; } = null;
public int Depth { get; set; } = 0;
public bool IsSelected { get; set; } = false;
public List<BranchTreeNode> Children { get; private set; } = new List<BranchTreeNode>();
public bool IsFiltered
public Models.FilterMode FilterMode
{
get => _isFiltered;
set => SetProperty(ref _isFiltered, value);
get => _filterMode;
set => SetProperty(ref _filterMode, value);
}
public bool IsExpanded
@ -51,7 +50,7 @@ namespace SourceGit.ViewModels
get => Backend is Models.Branch b ? b.FriendlyName : null;
}
private bool _isFiltered = false;
private Models.FilterMode _filterMode = Models.FilterMode.None;
private bool _isExpanded = false;
private CornerRadius _cornerRadius = new CornerRadius(4);
@ -60,18 +59,25 @@ namespace SourceGit.ViewModels
public List<BranchTreeNode> Locals => _locals;
public List<BranchTreeNode> Remotes => _remotes;
public Builder(Models.RepositorySettings settings)
{
_settings = settings;
}
public void Run(List<Models.Branch> branches, List<Models.Remote> remotes, bool bForceExpanded)
{
var folders = new Dictionary<string, BranchTreeNode>();
foreach (var remote in remotes)
{
var path = $"remote/{remote.Name}";
var path = $"refs/remotes/{remote.Name}";
var node = new BranchTreeNode()
{
Name = remote.Name,
Path = path,
Backend = remote,
IsExpanded = bForceExpanded || _expanded.Contains(path),
FilterMode = _settings.GetHistoriesFilterMode(path, Models.FilterType.RemoteBranchFolder)
};
folders.Add(path, node);
@ -80,16 +86,15 @@ namespace SourceGit.ViewModels
foreach (var branch in branches)
{
var isFiltered = _filters.Contains(branch.FullName);
if (branch.IsLocal)
{
MakeBranchNode(branch, _locals, folders, "local", isFiltered, bForceExpanded);
MakeBranchNode(branch, _locals, folders, "refs/heads", bForceExpanded);
}
else
{
var remote = _remotes.Find(x => x.Name == branch.Remote);
if (remote != null)
MakeBranchNode(branch, remote.Children, folders, $"remote/{remote.Name}", isFiltered, bForceExpanded);
MakeBranchNode(branch, remote.Children, folders, $"refs/remotes/{remote.Name}", bForceExpanded);
}
}
@ -98,42 +103,36 @@ namespace SourceGit.ViewModels
SortNodes(_remotes);
}
public void SetFilters(AvaloniaList<string> filters)
{
_filters.AddRange(filters);
}
public void CollectExpandedNodes(List<BranchTreeNode> nodes, bool isLocal)
{
CollectExpandedNodes(nodes, isLocal ? "local" : "remote");
}
private void CollectExpandedNodes(List<BranchTreeNode> nodes, string prefix)
public void CollectExpandedNodes(List<BranchTreeNode> nodes)
{
foreach (var node in nodes)
{
if (node.Backend is Models.Branch)
continue;
var path = prefix + "/" + node.Name;
if (node.IsExpanded)
_expanded.Add(path);
_expanded.Add(node.Path);
CollectExpandedNodes(node.Children, path);
CollectExpandedNodes(node.Children);
}
}
private void MakeBranchNode(Models.Branch branch, List<BranchTreeNode> roots, Dictionary<string, BranchTreeNode> folders, string prefix, bool isFiltered, bool bForceExpanded)
private void MakeBranchNode(Models.Branch branch, List<BranchTreeNode> roots, Dictionary<string, BranchTreeNode> folders, string prefix, bool bForceExpanded)
{
var fullpath = $"{prefix}/{branch.Name}";
var branchFilterType = branch.IsLocal ? Models.FilterType.LocalBranch : Models.FilterType.RemoteBranch;
var folderFilterType = branch.IsLocal ? Models.FilterType.LocalBranchFolder : Models.FilterType.RemoteBranchFolder;
var sepIdx = branch.Name.IndexOf('/', StringComparison.Ordinal);
if (sepIdx == -1 || branch.IsDetachedHead)
{
roots.Add(new BranchTreeNode()
{
Name = branch.Name,
Path = fullpath,
Backend = branch,
IsExpanded = false,
IsFiltered = isFiltered,
FilterMode = _settings.GetHistoriesFilterMode(fullpath, branchFilterType),
});
return;
}
@ -156,7 +155,9 @@ namespace SourceGit.ViewModels
lastFolder = new BranchTreeNode()
{
Name = name,
Path = folder,
IsExpanded = bForceExpanded || branch.IsCurrent || _expanded.Contains(folder),
FilterMode = _settings.GetHistoriesFilterMode(folder, folderFilterType),
};
roots.Add(lastFolder);
folders.Add(folder, lastFolder);
@ -166,7 +167,9 @@ namespace SourceGit.ViewModels
var cur = new BranchTreeNode()
{
Name = name,
Path = folder,
IsExpanded = bForceExpanded || branch.IsCurrent || _expanded.Contains(folder),
FilterMode = _settings.GetHistoriesFilterMode(folder, folderFilterType),
};
lastFolder.Children.Add(cur);
folders.Add(folder, cur);
@ -179,10 +182,11 @@ namespace SourceGit.ViewModels
lastFolder?.Children.Add(new BranchTreeNode()
{
Name = Path.GetFileName(branch.Name),
Name = System.IO.Path.GetFileName(branch.Name),
Path = fullpath,
Backend = branch,
IsExpanded = false,
IsFiltered = isFiltered,
FilterMode = _settings.GetHistoriesFilterMode(fullpath, branchFilterType),
});
}
@ -203,10 +207,10 @@ namespace SourceGit.ViewModels
SortNodes(node.Children);
}
private readonly Models.RepositorySettings _settings = null;
private readonly List<BranchTreeNode> _locals = new List<BranchTreeNode>();
private readonly List<BranchTreeNode> _remotes = new List<BranchTreeNode>();
private readonly HashSet<string> _expanded = new HashSet<string>();
private readonly List<string> _filters = new List<string>();
}
}
}

View file

@ -65,7 +65,7 @@ namespace SourceGit.ViewModels
{
var b = _repo.Branches.Find(x => x.IsLocal && x.Name == Branch);
if (b != null)
_repo.AutoAddBranchFilterPostCheckout(b);
_repo.UpdateHistoriesFilterAfterCheckout(b);
_repo.MarkBranchesDirtyManually();
_repo.SetWatcherEnabled(true);

View file

@ -127,7 +127,7 @@ namespace SourceGit.ViewModels
{
if (CheckoutAfterCreated)
{
_repo.AutoAddBranchFilterPostCheckout(new Models.Branch()
_repo.UpdateHistoriesFilterAfterCheckout(new Models.Branch()
{
FullName = $"refs/heads/{_name}",
Upstream = BasedOn is Models.Branch { IsLocal: false } remoteBranch ? remoteBranch.FullName : string.Empty,

View file

@ -1,4 +1,5 @@
using System.ComponentModel.DataAnnotations;
using System;
using System.ComponentModel.DataAnnotations;
using System.Threading.Tasks;
namespace SourceGit.ViewModels
@ -57,10 +58,17 @@ namespace SourceGit.ViewModels
var succ = Commands.Branch.Rename(_repo.FullPath, Target.Name, _name);
CallUIThread(() =>
{
if (succ && _repo.Settings.Filters.Contains(oldName))
if (succ)
{
_repo.Settings.Filters.Remove(oldName);
_repo.Settings.Filters.Add($"refs/heads/{_name}");
foreach (var filter in _repo.Settings.HistoriesFilters)
{
if (filter.Type == Models.FilterType.LocalBranch &&
filter.Pattern.Equals(oldName, StringComparison.Ordinal))
{
filter.Pattern = $"refs/heads/{_name}";
break;
}
}
}
_repo.MarkBranchesDirtyManually();

View file

@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
@ -455,13 +456,9 @@ namespace SourceGit.ViewModels
_hasAllowedSignersFile = !string.IsNullOrEmpty(allowedSignersFile);
});
Task.Run(() =>
{
RefreshBranches();
RefreshTags();
RefreshCommits();
});
Task.Run(RefreshBranches);
Task.Run(RefreshTags);
Task.Run(RefreshCommits);
Task.Run(RefreshSubmodules);
Task.Run(RefreshWorktrees);
Task.Run(RefreshWorkingCopyChanges);
@ -587,18 +584,6 @@ namespace SourceGit.ViewModels
Filter = string.Empty;
}
public void ClearHistoriesFilter()
{
_settings.Filters.Clear();
Task.Run(() =>
{
RefreshBranches();
RefreshTags();
RefreshCommits();
});
}
public void ClearSearchCommitFilter()
{
SearchCommitFilter = string.Empty;
@ -653,12 +638,8 @@ namespace SourceGit.ViewModels
{
if (_watcher == null)
{
Task.Run(() =>
{
RefreshBranches();
RefreshCommits();
});
Task.Run(RefreshBranches);
Task.Run(RefreshCommits);
Task.Run(RefreshWorkingCopyChanges);
Task.Run(RefreshWorktrees);
}
@ -696,53 +677,48 @@ namespace SourceGit.ViewModels
NavigateToCommit(_currentBranch.Head);
}
public void AutoAddBranchFilterPostCheckout(Models.Branch local)
public void ClearHistoriesFilter()
{
if (_settings.Filters.Count == 0 || _settings.Filters.Contains(local.FullName))
return;
_settings.HistoriesFilters.Clear();
var hasLeft = false;
foreach (var b in _branches)
{
if (!b.FullName.Equals(local.FullName, StringComparison.Ordinal) &&
!b.FullName.Equals(local.Upstream, StringComparison.Ordinal) &&
!_settings.Filters.Contains(b.FullName))
{
hasLeft = true;
break;
}
}
var builder = BuildBranchTree(_branches, _remotes);
LocalBranchTrees = builder.Locals;
RemoteBranchTrees = builder.Remotes;
foreach (var tag in VisibleTags)
tag.FilterMode = Models.FilterMode.None;
if (!hasLeft)
_settings.Filters.Clear();
else if (string.IsNullOrEmpty(local.Upstream) || _settings.Filters.Contains(local.Upstream))
_settings.Filters.Add(local.FullName);
else
_settings.Filters.AddRange([local.FullName, local.Upstream]);
Task.Run(RefreshCommits);
}
public void UpdateFilters(List<string> filters, bool toggle)
public void UpdateHistoriesFilterAfterCheckout(Models.Branch local)
{
var changed = false;
if (toggle)
var hasIncludedBranch = false;
foreach (var filter in _settings.HistoriesFilters)
{
foreach (var filter in filters)
if (filter.Type == Models.FilterType.LocalBranch)
{
if (!_settings.Filters.Contains(filter))
{
_settings.Filters.Add(filter);
changed = true;
}
if (filter.Pattern.Equals(local.FullName, StringComparison.Ordinal))
return;
hasIncludedBranch |= filter.Mode == Models.FilterMode.Included;
}
else if (filter.Type == Models.FilterType.LocalBranchFolder)
{
if (local.FullName.StartsWith(filter.Pattern, StringComparison.Ordinal))
return;
hasIncludedBranch |= filter.Mode == Models.FilterMode.Included;
}
else if (filter.Type == Models.FilterType.RemoteBranch || filter.Type == Models.FilterType.RemoteBranchFolder)
{
hasIncludedBranch |= filter.Mode == Models.FilterMode.Included;
}
}
else
{
foreach (var filter in filters)
changed |= _settings.Filters.Remove(filter);
}
if (changed)
Task.Run(RefreshCommits);
if (!hasIncludedBranch)
return;
_settings.UpdateHistoriesFilter(local.FullName, Models.FilterType.LocalBranch, Models.FilterMode.Included);
}
public void StashAll(bool autoStart)
@ -834,7 +810,7 @@ namespace SourceGit.ViewModels
{
var tags = new Commands.QueryTags(_fullpath).Result();
foreach (var tag in tags)
tag.IsFiltered = _settings.Filters.Contains(tag.Name);
tag.FilterMode = _settings.GetHistoriesFilterMode(tag.Name, Models.FilterType.Tag);
Dispatcher.UIThread.Invoke(() =>
{
@ -847,49 +823,21 @@ namespace SourceGit.ViewModels
{
Dispatcher.UIThread.Invoke(() => _histories.IsLoading = true);
var limits = $"-{Preference.Instance.MaxHistoryCommits} ";
var builder = new StringBuilder();
builder.Append($"-{Preference.Instance.MaxHistoryCommits} ");
if (_enableReflog)
limits += "--reflog ";
builder.Append("--reflog ");
if (_enableFirstParentInHistories)
limits += "--first-parent ";
builder.Append("--first-parent ");
var validFilters = new List<string>();
foreach (var filter in _settings.Filters)
{
if (filter.StartsWith("refs/", StringComparison.Ordinal))
{
if (_branches.FindIndex(x => x.FullName == filter) >= 0)
validFilters.Add(filter);
}
else
{
if (_tags.FindIndex(t => t.Name == filter) >= 0)
validFilters.Add(filter);
}
}
if (validFilters.Count > 0)
{
limits += string.Join(" ", validFilters);
if (_settings.Filters.Count != validFilters.Count)
{
Dispatcher.UIThread.Invoke(() =>
{
_settings.Filters.Clear();
_settings.Filters.AddRange(validFilters);
});
}
}
var invalidFilters = new List<Models.Filter>();
var filters = _settings.BuildHistoriesFilter();
if (string.IsNullOrEmpty(filters))
builder.Append("--branches --remotes --tags");
else
{
if (_settings.Filters.Count != 0)
Dispatcher.UIThread.Invoke(() => _settings.Filters.Clear());
builder.Append(filters);
limits += "--exclude=refs/stash --all";
}
var commits = new Commands.QueryCommits(_fullpath, limits).Result();
var commits = new Commands.QueryCommits(_fullpath, builder.ToString()).Result();
var graph = Models.CommitGraph.Parse(commits, _enableFirstParentInHistories);
Dispatcher.UIThread.Invoke(() =>
@ -2061,13 +2009,11 @@ namespace SourceGit.ViewModels
private BranchTreeNode.Builder BuildBranchTree(List<Models.Branch> branches, List<Models.Remote> remotes)
{
var builder = new BranchTreeNode.Builder();
builder.SetFilters(_settings.Filters);
var builder = new BranchTreeNode.Builder(_settings);
if (string.IsNullOrEmpty(_filter))
{
builder.CollectExpandedNodes(_localBranchTrees, true);
builder.CollectExpandedNodes(_remoteBranchTrees, false);
builder.CollectExpandedNodes(_localBranchTrees);
builder.CollectExpandedNodes(_remoteBranchTrees);
builder.Run(branches, remotes, false);
}
else

View file

@ -22,16 +22,6 @@ namespace SourceGit.ViewModels
get => Tag == null;
}
public bool IsFiltered
{
get => Tag?.IsFiltered ?? false;
set
{
if (Tag != null)
Tag.IsFiltered = value;
}
}
public bool IsExpanded
{
get => _isExpanded;