mirror of
https://github.com/sourcegit-scm/sourcegit
synced 2025-05-23 21:24:59 +00:00
feature: supports filter displayed branches
This commit is contained in:
parent
17e48d86fe
commit
02e71d4d75
6 changed files with 121 additions and 43 deletions
204
src/ViewModels/BranchTreeNode.cs
Normal file
204
src/ViewModels/BranchTreeNode.cs
Normal file
|
@ -0,0 +1,204 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using Avalonia.Collections;
|
||||
|
||||
namespace SourceGit.ViewModels
|
||||
{
|
||||
public enum BranchTreeNodeType
|
||||
{
|
||||
Remote,
|
||||
Folder,
|
||||
Branch,
|
||||
}
|
||||
|
||||
public class BranchTreeNode
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public BranchTreeNodeType Type { get; set; }
|
||||
public object Backend { get; set; }
|
||||
public bool IsExpanded { get; set; }
|
||||
public bool IsFiltered { get; set; }
|
||||
public List<BranchTreeNode> Children { get; set; } = new List<BranchTreeNode>();
|
||||
|
||||
public bool IsUpstreamTrackStatusVisible
|
||||
{
|
||||
get => IsBranch && !string.IsNullOrEmpty((Backend as Models.Branch).UpstreamTrackStatus);
|
||||
}
|
||||
|
||||
public string UpstreamTrackStatus
|
||||
{
|
||||
get => Type == BranchTreeNodeType.Branch ? (Backend as Models.Branch).UpstreamTrackStatus : "";
|
||||
}
|
||||
|
||||
public bool IsRemote
|
||||
{
|
||||
get => Type == BranchTreeNodeType.Remote;
|
||||
}
|
||||
|
||||
public bool IsFolder
|
||||
{
|
||||
get => Type == BranchTreeNodeType.Folder;
|
||||
}
|
||||
|
||||
public bool IsBranch
|
||||
{
|
||||
get => Type == BranchTreeNodeType.Branch;
|
||||
}
|
||||
|
||||
public bool IsCurrent
|
||||
{
|
||||
get => IsBranch && (Backend as Models.Branch).IsCurrent;
|
||||
}
|
||||
|
||||
public class Builder
|
||||
{
|
||||
public List<BranchTreeNode> Locals => _locals;
|
||||
public List<BranchTreeNode> Remotes => _remotes;
|
||||
|
||||
public void Run(List<Models.Branch> branches, List<Models.Remote> remotes, bool bForceExpanded)
|
||||
{
|
||||
foreach (var remote in remotes)
|
||||
{
|
||||
var path = $"remote/{remote.Name}";
|
||||
var node = new BranchTreeNode()
|
||||
{
|
||||
Name = remote.Name,
|
||||
Type = BranchTreeNodeType.Remote,
|
||||
Backend = remote,
|
||||
IsExpanded = bForceExpanded || _expanded.Contains(path),
|
||||
};
|
||||
|
||||
_maps.Add(path, node);
|
||||
_remotes.Add(node);
|
||||
}
|
||||
|
||||
foreach (var branch in branches)
|
||||
{
|
||||
var isFiltered = _filters.Contains(branch.FullName);
|
||||
if (branch.IsLocal)
|
||||
{
|
||||
MakeBranchNode(branch, _locals, "local", isFiltered, bForceExpanded);
|
||||
}
|
||||
else
|
||||
{
|
||||
var remote = _remotes.Find(x => x.Name == branch.Remote);
|
||||
if (remote != null)
|
||||
MakeBranchNode(branch, remote.Children, $"remote/{remote.Name}", isFiltered, bForceExpanded);
|
||||
}
|
||||
}
|
||||
|
||||
SortNodes(_locals);
|
||||
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)
|
||||
{
|
||||
foreach (var node in nodes)
|
||||
{
|
||||
var path = prefix + "/" + node.Name;
|
||||
if (node.Type != BranchTreeNodeType.Branch && node.IsExpanded)
|
||||
_expanded.Add(path);
|
||||
CollectExpandedNodes(node.Children, path);
|
||||
}
|
||||
}
|
||||
|
||||
private void MakeBranchNode(Models.Branch branch, List<BranchTreeNode> roots, string prefix, bool isFiltered, bool bForceExpanded)
|
||||
{
|
||||
var subs = branch.Name.Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
if (subs.Length == 1)
|
||||
{
|
||||
var node = new BranchTreeNode()
|
||||
{
|
||||
Name = subs[0],
|
||||
Type = BranchTreeNodeType.Branch,
|
||||
Backend = branch,
|
||||
IsExpanded = false,
|
||||
IsFiltered = isFiltered,
|
||||
};
|
||||
roots.Add(node);
|
||||
return;
|
||||
}
|
||||
|
||||
BranchTreeNode lastFolder = null;
|
||||
var path = prefix;
|
||||
for (var i = 0; i < subs.Length - 1; i++)
|
||||
{
|
||||
path = string.Concat(path, "/", subs[i]);
|
||||
if (_maps.TryGetValue(path, out var value))
|
||||
{
|
||||
lastFolder = value;
|
||||
}
|
||||
else if (lastFolder == null)
|
||||
{
|
||||
lastFolder = new BranchTreeNode()
|
||||
{
|
||||
Name = subs[i],
|
||||
Type = BranchTreeNodeType.Folder,
|
||||
IsExpanded = bForceExpanded || branch.IsCurrent || _expanded.Contains(path),
|
||||
};
|
||||
roots.Add(lastFolder);
|
||||
_maps.Add(path, lastFolder);
|
||||
}
|
||||
else
|
||||
{
|
||||
var folder = new BranchTreeNode()
|
||||
{
|
||||
Name = subs[i],
|
||||
Type = BranchTreeNodeType.Folder,
|
||||
IsExpanded = bForceExpanded || branch.IsCurrent || _expanded.Contains(path),
|
||||
};
|
||||
_maps.Add(path, folder);
|
||||
lastFolder.Children.Add(folder);
|
||||
lastFolder = folder;
|
||||
}
|
||||
}
|
||||
|
||||
var last = new BranchTreeNode()
|
||||
{
|
||||
Name = subs[subs.Length - 1],
|
||||
Type = BranchTreeNodeType.Branch,
|
||||
Backend = branch,
|
||||
IsExpanded = false,
|
||||
IsFiltered = isFiltered,
|
||||
};
|
||||
lastFolder.Children.Add(last);
|
||||
}
|
||||
|
||||
private void SortNodes(List<BranchTreeNode> nodes)
|
||||
{
|
||||
nodes.Sort((l, r) =>
|
||||
{
|
||||
if (l.Type == r.Type)
|
||||
{
|
||||
return l.Name.CompareTo(r.Name);
|
||||
}
|
||||
else
|
||||
{
|
||||
return (int)l.Type - (int)r.Type;
|
||||
}
|
||||
});
|
||||
|
||||
foreach (var node in nodes)
|
||||
SortNodes(node.Children);
|
||||
}
|
||||
|
||||
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>();
|
||||
private readonly Dictionary<string, BranchTreeNode> _maps = new Dictionary<string, BranchTreeNode>();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -89,6 +89,21 @@ namespace SourceGit.ViewModels
|
|||
set => SetProperty(ref _selectedView, value);
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public string SearchBranchFilter
|
||||
{
|
||||
get => _searchBranchFilter;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _searchBranchFilter, value))
|
||||
{
|
||||
var builder = BuildBranchTree(_branches, _remotes);
|
||||
LocalBranchTrees = builder.Locals;
|
||||
RemoteBranchTrees = builder.Remotes;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public List<Models.Remote> Remotes
|
||||
{
|
||||
|
@ -104,14 +119,14 @@ namespace SourceGit.ViewModels
|
|||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public List<Models.BranchTreeNode> LocalBranchTrees
|
||||
public List<BranchTreeNode> LocalBranchTrees
|
||||
{
|
||||
get => _localBranchTrees;
|
||||
private set => SetProperty(ref _localBranchTrees, value);
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public List<Models.BranchTreeNode> RemoteBranchTrees
|
||||
public List<BranchTreeNode> RemoteBranchTrees
|
||||
{
|
||||
get => _remoteBranchTrees;
|
||||
private set => SetProperty(ref _remoteBranchTrees, value);
|
||||
|
@ -422,6 +437,11 @@ namespace SourceGit.ViewModels
|
|||
SearchedCommits = visible;
|
||||
}
|
||||
|
||||
public void ClearSearchBranchFilter()
|
||||
{
|
||||
SearchBranchFilter = string.Empty;
|
||||
}
|
||||
|
||||
public void SetWatcherEnabled(bool enabled)
|
||||
{
|
||||
if (_watcher != null)
|
||||
|
@ -533,12 +553,7 @@ namespace SourceGit.ViewModels
|
|||
{
|
||||
var branches = new Commands.QueryBranches(FullPath).Result();
|
||||
var remotes = new Commands.QueryRemotes(FullPath).Result();
|
||||
|
||||
var builder = new Models.BranchTreeNode.Builder();
|
||||
builder.SetFilters(Filters);
|
||||
builder.CollectExpandedNodes(_localBranchTrees, true);
|
||||
builder.CollectExpandedNodes(_remoteBranchTrees, false);
|
||||
builder.Run(branches, remotes);
|
||||
var builder = BuildBranchTree(branches, remotes);
|
||||
|
||||
Dispatcher.UIThread.Invoke(() =>
|
||||
{
|
||||
|
@ -1354,6 +1369,32 @@ namespace SourceGit.ViewModels
|
|||
return menu;
|
||||
}
|
||||
|
||||
private BranchTreeNode.Builder BuildBranchTree(List<Models.Branch> branches, List<Models.Remote> remotes)
|
||||
{
|
||||
var builder = new BranchTreeNode.Builder();
|
||||
builder.SetFilters(Filters);
|
||||
|
||||
if (string.IsNullOrEmpty(_searchBranchFilter))
|
||||
{
|
||||
builder.CollectExpandedNodes(_localBranchTrees, true);
|
||||
builder.CollectExpandedNodes(_remoteBranchTrees, false);
|
||||
builder.Run(branches, remotes, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
var visibles = new List<Models.Branch>();
|
||||
foreach (var b in branches)
|
||||
{
|
||||
if (b.FullName.Contains(_searchBranchFilter, StringComparison.OrdinalIgnoreCase))
|
||||
visibles.Add(b);
|
||||
}
|
||||
|
||||
builder.Run(visibles, remotes, true);
|
||||
}
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
private string _fullpath = string.Empty;
|
||||
private string _gitDir = string.Empty;
|
||||
private Models.GitFlow _gitflow = new Models.GitFlow();
|
||||
|
@ -1372,10 +1413,12 @@ namespace SourceGit.ViewModels
|
|||
private bool _isTagGroupExpanded = false;
|
||||
private bool _isSubmoduleGroupExpanded = false;
|
||||
|
||||
private string _searchBranchFilter = string.Empty;
|
||||
|
||||
private List<Models.Remote> _remotes = new List<Models.Remote>();
|
||||
private List<Models.Branch> _branches = new List<Models.Branch>();
|
||||
private List<Models.BranchTreeNode> _localBranchTrees = new List<Models.BranchTreeNode>();
|
||||
private List<Models.BranchTreeNode> _remoteBranchTrees = new List<Models.BranchTreeNode>();
|
||||
private List<BranchTreeNode> _localBranchTrees = new List<BranchTreeNode>();
|
||||
private List<BranchTreeNode> _remoteBranchTrees = new List<BranchTreeNode>();
|
||||
private List<Models.Tag> _tags = new List<Models.Tag>();
|
||||
private List<string> _submodules = new List<string>();
|
||||
private bool _canCommitWithPush = false;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue