From ca5bc4b4dfec6a6e210bb28d02422f3b354abc01 Mon Sep 17 00:00:00 2001 From: leo Date: Wed, 13 Nov 2024 21:45:28 +0800 Subject: [PATCH] refactor: rewrite the histories filter function to supports both `include` and `exclude` modes (#690) Signed-off-by: leo --- src/Converters/FilterModeConverters.cs | 22 ++ src/Models/Filter.cs | 60 +++++ src/Models/RepositorySettings.cs | 184 ++++++++++++- src/Models/Tag.cs | 21 +- src/Models/Watcher.cs | 17 +- src/Resources/Locales/de_DE.axaml | 3 +- src/Resources/Locales/en_US.axaml | 6 +- src/Resources/Locales/es_ES.axaml | 3 +- src/Resources/Locales/fr_FR.axaml | 3 +- src/Resources/Locales/pt_BR.axaml | 3 +- src/Resources/Locales/ru_RU.axaml | 3 +- src/Resources/Locales/zh_CN.axaml | 6 +- src/Resources/Locales/zh_TW.axaml | 6 +- src/Resources/Styles.axaml | 29 --- src/ViewModels/BranchTreeNode.cs | 62 ++--- src/ViewModels/Checkout.cs | 2 +- src/ViewModels/CreateBranch.cs | 2 +- src/ViewModels/RenameBranch.cs | 16 +- src/ViewModels/Repository.cs | 156 ++++------- src/ViewModels/TagCollection.cs | 10 - src/Views/BranchTree.axaml | 17 +- src/Views/BranchTree.axaml.cs | 39 --- src/Views/FilterModeSwitchButton.axaml | 32 +++ src/Views/FilterModeSwitchButton.axaml.cs | 299 ++++++++++++++++++++++ src/Views/Repository.axaml | 20 +- src/Views/TagsView.axaml | 38 ++- src/Views/TagsView.axaml.cs | 17 -- 27 files changed, 767 insertions(+), 309 deletions(-) create mode 100644 src/Converters/FilterModeConverters.cs create mode 100644 src/Models/Filter.cs create mode 100644 src/Views/FilterModeSwitchButton.axaml create mode 100644 src/Views/FilterModeSwitchButton.axaml.cs diff --git a/src/Converters/FilterModeConverters.cs b/src/Converters/FilterModeConverters.cs new file mode 100644 index 00000000..402a287d --- /dev/null +++ b/src/Converters/FilterModeConverters.cs @@ -0,0 +1,22 @@ +using Avalonia.Data.Converters; +using Avalonia.Media; + +namespace SourceGit.Converters +{ + public static class FilterModeConverters + { + public static readonly FuncValueConverter ToBorderBrush = + new FuncValueConverter(v => + { + switch (v) + { + case Models.FilterMode.Included: + return Brushes.Green; + case Models.FilterMode.Excluded: + return Brushes.Red; + default: + return Brushes.Transparent; + } + }); + } +} diff --git a/src/Models/Filter.cs b/src/Models/Filter.cs new file mode 100644 index 00000000..8ffd27c7 --- /dev/null +++ b/src/Models/Filter.cs @@ -0,0 +1,60 @@ +using CommunityToolkit.Mvvm.ComponentModel; + +namespace SourceGit.Models +{ + public enum FilterType + { + LocalBranch = 0, + LocalBranchFolder, + RemoteBranch, + RemoteBranchFolder, + Tag, + } + + public enum FilterMode + { + None = 0, + Included, + Excluded, + } + + public class Filter : ObservableObject + { + public string Pattern + { + get => _pattern; + set => SetProperty(ref _pattern, value); + } + + public FilterType Type + { + get; + set; + } = FilterType.LocalBranch; + + public FilterMode Mode + { + get => _mode; + set => SetProperty(ref _mode, value); + } + + public bool IsBranch + { + get => Type != FilterType.Tag; + } + + public Filter() + { + } + + public Filter(string pattern, FilterType type, FilterMode mode) + { + _pattern = pattern; + _mode = mode; + Type = type; + } + + private string _pattern = string.Empty; + private FilterMode _mode = FilterMode.None; + } +} diff --git a/src/Models/RepositorySettings.cs b/src/Models/RepositorySettings.cs index 77c58ee7..f5f02e46 100644 --- a/src/Models/RepositorySettings.cs +++ b/src/Models/RepositorySettings.cs @@ -1,4 +1,9 @@ -using Avalonia.Collections; +using System; +using System.Collections.Generic; +using System.Text; + +using Avalonia.Collections; +using Avalonia.Threading; namespace SourceGit.Models { @@ -76,11 +81,11 @@ namespace SourceGit.Models set; } = true; - public AvaloniaList Filters + public AvaloniaList HistoriesFilters { get; set; - } = new AvaloniaList(); + } = new AvaloniaList(); public AvaloniaList CommitTemplates { @@ -148,6 +153,179 @@ namespace SourceGit.Models set; } = "---"; + public FilterMode GetHistoriesFilterMode(string pattern, FilterType type) + { + foreach (var filter in HistoriesFilters) + { + if (filter.Type != type) + continue; + + if (filter.Pattern.Equals(pattern, StringComparison.Ordinal)) + return filter.Mode; + } + + return FilterMode.None; + } + + public bool UpdateHistoriesFilter(string pattern, FilterType type, FilterMode mode) + { + for (int i = 0; i < HistoriesFilters.Count; i++) + { + var filter = HistoriesFilters[i]; + if (filter.Type != type) + continue; + + if (filter.Pattern.Equals(pattern, StringComparison.Ordinal)) + { + if (mode == FilterMode.None) + { + HistoriesFilters.RemoveAt(i); + return true; + } + + if (mode != filter.Mode) + { + filter.Mode = mode; + return true; + } + } + } + + if (mode != FilterMode.None) + { + HistoriesFilters.Add(new Filter(pattern, type, mode)); + return true; + } + + return false; + } + + public string BuildHistoriesFilter() + { + var builder = new StringBuilder(); + + var excludedBranches = new List(); + var excludedRemotes = new List(); + var excludedTags = new List(); + var includedBranches = new List(); + var includedRemotes = new List(); + var includedTags = new List(); + foreach (var filter in HistoriesFilters) + { + if (filter.Type == FilterType.LocalBranch) + { + var name = filter.Pattern.Substring(11); + var b = $"{name.Substring(0, name.Length - 1)}[{name[^1]}]"; + + if (filter.Mode == FilterMode.Included) + includedBranches.Add(b); + else if (filter.Mode == FilterMode.Excluded) + excludedBranches.Add(b); + } + else if (filter.Type == FilterType.LocalBranchFolder) + { + if (filter.Mode == FilterMode.Included) + includedBranches.Add($"{filter.Pattern.Substring(11)}/*"); + else if (filter.Mode == FilterMode.Excluded) + excludedBranches.Add($"{filter.Pattern.Substring(11)}/*"); + } + else if (filter.Type == FilterType.RemoteBranch) + { + var name = filter.Pattern.Substring(13); + var r = $"{name.Substring(0, name.Length - 1)}[{name[^1]}]"; + + if (filter.Mode == FilterMode.Included) + includedRemotes.Add(r); + else if (filter.Mode == FilterMode.Excluded) + excludedRemotes.Add(r); + } + else if (filter.Type == FilterType.RemoteBranchFolder) + { + if (filter.Mode == FilterMode.Included) + includedRemotes.Add($"{filter.Pattern.Substring(13)}/*"); + else if (filter.Mode == FilterMode.Excluded) + excludedRemotes.Add($"{filter.Pattern.Substring(13)}/*"); + } + else if (filter.Type == FilterType.Tag) + { + var name = filter.Pattern; + var t = $"{name.Substring(0, name.Length - 1)}[{name[^1]}]"; + + if (filter.Mode == FilterMode.Included) + includedTags.Add(t); + else if (filter.Mode == FilterMode.Excluded) + excludedTags.Add(t); + } + } + + foreach (var b in excludedBranches) + { + builder.Append("--exclude="); + builder.Append(b); + builder.Append(' '); + } + + if (includedBranches.Count > 0) + { + foreach (var b in includedBranches) + { + builder.Append("--branches="); + builder.Append(b); + builder.Append(' '); + } + } + else if (excludedBranches.Count > 0 || (includedRemotes.Count == 0 && includedTags.Count == 0)) + { + builder.Append("--exclude=HEA[D] "); + builder.Append("--branches "); + } + + foreach (var r in excludedRemotes) + { + builder.Append("--exclude="); + builder.Append(r); + builder.Append(' '); + } + + if (includedRemotes.Count > 0) + { + foreach (var r in includedRemotes) + { + builder.Append("--remotes="); + builder.Append(r); + builder.Append(' '); + } + } + else if (excludedRemotes.Count > 0 || (includedBranches.Count == 0 && includedTags.Count == 0)) + { + builder.Append("--exclude=origin/HEA[D] "); + builder.Append("--remotes "); + } + + foreach (var t in excludedTags) + { + builder.Append("--exclude="); + builder.Append(t); + builder.Append(' '); + } + + if (includedTags.Count > 0) + { + foreach (var t in includedTags) + { + builder.Append("--tags="); + builder.Append(t); + builder.Append(' '); + } + } + else if (excludedTags.Count > 0 || (includedBranches.Count == 0 && includedRemotes.Count == 0)) + { + builder.Append("--tags "); + } + + return builder.ToString(); + } + public void PushCommitMessage(string message) { var existIdx = CommitMessages.IndexOf(message); diff --git a/src/Models/Tag.cs b/src/Models/Tag.cs index 2ec9e093..2e8f2c8e 100644 --- a/src/Models/Tag.cs +++ b/src/Models/Tag.cs @@ -1,10 +1,19 @@ -namespace SourceGit.Models +using CommunityToolkit.Mvvm.ComponentModel; + +namespace SourceGit.Models { - public class Tag + public class Tag : ObservableObject { - public string Name { get; set; } - public string SHA { get; set; } - public string Message { get; set; } - public bool IsFiltered { get; set; } + public string Name { get; set; } = string.Empty; + public string SHA { get; set; } = string.Empty; + public string Message { get; set; } = string.Empty; + + public FilterMode FilterMode + { + get => _filterMode; + set => SetProperty(ref _filterMode, value); + } + + private FilterMode _filterMode = FilterMode.None; } } diff --git a/src/Models/Watcher.cs b/src/Models/Watcher.cs index 6665250c..710b307d 100644 --- a/src/Models/Watcher.cs +++ b/src/Models/Watcher.cs @@ -113,22 +113,11 @@ namespace SourceGit.Models if (_updateTags > 0) { _updateTags = 0; - Task.Run(() => - { - _repo.RefreshTags(); - _repo.RefreshBranches(); - _repo.RefreshCommits(); - }); - } - else - { - Task.Run(() => - { - _repo.RefreshBranches(); - _repo.RefreshCommits(); - }); + Task.Run(_repo.RefreshTags); } + Task.Run(_repo.RefreshBranches); + Task.Run(_repo.RefreshCommits); Task.Run(_repo.RefreshWorkingCopyChanges); Task.Run(_repo.RefreshWorktrees); } diff --git a/src/Resources/Locales/de_DE.axaml b/src/Resources/Locales/de_DE.axaml index ae2e89d7..4e94bb67 100644 --- a/src/Resources/Locales/de_DE.axaml +++ b/src/Resources/Locales/de_DE.axaml @@ -292,7 +292,6 @@ Datei Historie INHALT ÄNDERUNGEN - FILTER Git-Flow Development-Branch: Feature: @@ -544,7 +543,7 @@ Aktiviere '--reflog' Option Öffne im Datei-Browser Suche Branches/Tags/Submodule - GEFILTERT: + GEFILTERT: LOKALE BRANCHES Zum HEAD wechseln Aktiviere '--first-parent' Option diff --git a/src/Resources/Locales/en_US.axaml b/src/Resources/Locales/en_US.axaml index 281f13a3..da860d05 100644 --- a/src/Resources/Locales/en_US.axaml +++ b/src/Resources/Locales/en_US.axaml @@ -289,7 +289,6 @@ File History CONTENT CHANGE - FILTER Git-Flow Development Branch: Feature: @@ -541,7 +540,10 @@ Enable '--reflog' Option Open in File Browser Search Branches/Tags/Submodules - FILTERED BY: + Unset (Default) + Hide in commit graph + Filter in commit graph + FILTERED BY: LOCAL BRANCHES Navigate to HEAD Enable '--first-parent' Option diff --git a/src/Resources/Locales/es_ES.axaml b/src/Resources/Locales/es_ES.axaml index 365cebfb..5e3ed9a9 100644 --- a/src/Resources/Locales/es_ES.axaml +++ b/src/Resources/Locales/es_ES.axaml @@ -294,7 +294,6 @@ Historial de Archivos CONTENIDO CAMBIO - FILTRO Git-Flow Rama de Desarrollo: Feature: @@ -542,7 +541,7 @@ Habilitar Opción '--reflog' Abrir en el Explorador Buscar Ramas/Etiquetas/Submódulos - FILTRAR POR: + FILTRAR POR: RAMAS LOCALES Navegar a HEAD Habilitar Opción '--first-parent' diff --git a/src/Resources/Locales/fr_FR.axaml b/src/Resources/Locales/fr_FR.axaml index e1562523..c07a6ee5 100644 --- a/src/Resources/Locales/fr_FR.axaml +++ b/src/Resources/Locales/fr_FR.axaml @@ -292,7 +292,6 @@ Historique du fichier CONTENU MODIFICATION - FILTRER Git-Flow Branche de développement : Feature: @@ -543,7 +542,7 @@ Activer l'option '--reflog' Ouvrir dans l'explorateur de fichiers Rechercher Branches/Tags/Submodules - FILTRE PAR : + FILTRE PAR : BRANCHES LOCALES Naviguer vers le HEAD Activer l'option '--first-parent' diff --git a/src/Resources/Locales/pt_BR.axaml b/src/Resources/Locales/pt_BR.axaml index ecd1654c..987b3398 100644 --- a/src/Resources/Locales/pt_BR.axaml +++ b/src/Resources/Locales/pt_BR.axaml @@ -317,7 +317,6 @@ Histórico de Arquivos CONTEUDO MUDANÇA - FILTRO Branch de Desenvolvimento: Feature: Prefixo da Feature: @@ -566,7 +565,7 @@ Habilitar opção '--reflog' Abrir no Navegador de Arquivos Pesquisar Branches/Tags/Submódulos - FILTRADO POR: + FILTRADO POR: Habilitar opção '--first-parent' BRANCHES LOCAIS Navegar para HEAD diff --git a/src/Resources/Locales/ru_RU.axaml b/src/Resources/Locales/ru_RU.axaml index ac94ee77..e86dbbe7 100644 --- a/src/Resources/Locales/ru_RU.axaml +++ b/src/Resources/Locales/ru_RU.axaml @@ -293,7 +293,6 @@ История файлов СОДЕРЖИМОЕ ИЗМЕНИТЬ - ФИЛЬТР Git-поток Ветка разработчика: Свойство: @@ -545,7 +544,7 @@ Разрешить опцию --reflog Открыть в файловом менеджере Поиск веток, меток и подмодулей - ОТФИЛЬТРОВАНО: + ОТФИЛЬТРОВАНО: ЛОКАЛЬНЫЕ ВЕТКИ Навигация по заголовку Включить опцию --first-parent diff --git a/src/Resources/Locales/zh_CN.axaml b/src/Resources/Locales/zh_CN.axaml index 5b1f1403..4fd32a37 100644 --- a/src/Resources/Locales/zh_CN.axaml +++ b/src/Resources/Locales/zh_CN.axaml @@ -292,7 +292,6 @@ 文件历史 文件内容 文件变更 - 过滤 GIT工作流 开发分支 : 特性分支 : @@ -545,7 +544,10 @@ 启用 --reflog 选项 在文件浏览器中打开 快速查找分支/标签/子模块 - 过滤规则 : + 未指定(默认行为) + 在提交列表中隐藏 + 使用其对提交列表过滤 + 过滤规则 : 本地分支 定位HEAD 启用 --first-parent 过滤选项 diff --git a/src/Resources/Locales/zh_TW.axaml b/src/Resources/Locales/zh_TW.axaml index 0587f089..b5d9aea8 100644 --- a/src/Resources/Locales/zh_TW.axaml +++ b/src/Resources/Locales/zh_TW.axaml @@ -292,7 +292,6 @@ 檔案歷史 檔案内容 檔案變更 - 篩選 Git 工作流 開發分支: 功能分支: @@ -544,7 +543,10 @@ 啟用 [--reflog] 選項 在檔案瀏覽器中開啟 快速搜尋分支/標籤/子模組 - 篩選規則: + 未指定 (預設) + 在提交清單中隱藏 + 使用其來篩選提交清單 + 篩選規則: 本機分支 回到 HEAD 啟用 [--first-parent] 選項 diff --git a/src/Resources/Styles.axaml b/src/Resources/Styles.axaml index 88aca6b0..8fdfaa3c 100644 --- a/src/Resources/Styles.axaml +++ b/src/Resources/Styles.axaml @@ -1038,35 +1038,6 @@ - - - - + + @@ -67,15 +71,10 @@ Foreground="{DynamicResource Brush.BadgeFG}" Background="{DynamicResource Brush.Badge}"/> - - + + diff --git a/src/Views/BranchTree.axaml.cs b/src/Views/BranchTree.axaml.cs index e96b2594..92c2b043 100644 --- a/src/Views/BranchTree.axaml.cs +++ b/src/Views/BranchTree.axaml.cs @@ -428,28 +428,6 @@ namespace SourceGit.Views } } - private void OnToggleFilterClicked(object sender, RoutedEventArgs e) - { - if (DataContext is ViewModels.Repository repo && - sender is ToggleButton toggle && - toggle.DataContext is ViewModels.BranchTreeNode { Backend: Models.Branch branch } node) - { - bool filtered = toggle.IsChecked == true; - List filters = [branch.FullName]; - if (branch.IsLocal && !string.IsNullOrEmpty(branch.Upstream)) - { - filters.Add(branch.Upstream); - - node.IsFiltered = filtered; - UpdateUpstreamFilterState(repo.RemoteBranchTrees, branch.Upstream, filtered); - } - - repo.UpdateFilters(filters, filtered); - } - - e.Handled = true; - } - private void MakeRows(List rows, List nodes, int depth) { foreach (var node in nodes) @@ -477,23 +455,6 @@ namespace SourceGit.Views CollectBranchesInNode(outs, sub); } - private bool UpdateUpstreamFilterState(List collection, string upstream, bool isFiltered) - { - foreach (var node in collection) - { - if (node.Backend is Models.Branch b && b.FullName == upstream) - { - node.IsFiltered = isFiltered; - return true; - } - - if (node.Backend is Models.Remote r && upstream.StartsWith($"refs/remotes/{r.Name}/", StringComparison.Ordinal)) - return UpdateUpstreamFilterState(node.Children, upstream, isFiltered); - } - - return false; - } - private bool _disableSelectionChangingEvent = false; } } diff --git a/src/Views/FilterModeSwitchButton.axaml b/src/Views/FilterModeSwitchButton.axaml new file mode 100644 index 00000000..0fbbbf8e --- /dev/null +++ b/src/Views/FilterModeSwitchButton.axaml @@ -0,0 +1,32 @@ + + + diff --git a/src/Views/FilterModeSwitchButton.axaml.cs b/src/Views/FilterModeSwitchButton.axaml.cs new file mode 100644 index 00000000..e8e4ad6a --- /dev/null +++ b/src/Views/FilterModeSwitchButton.axaml.cs @@ -0,0 +1,299 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +using Avalonia; +using Avalonia.Controls; +using Avalonia.Interactivity; +using Avalonia.VisualTree; + +namespace SourceGit.Views +{ + public partial class FilterModeSwitchButton : UserControl + { + public static readonly StyledProperty ModeProperty = + AvaloniaProperty.Register(nameof(Mode)); + + public Models.FilterMode Mode + { + get => GetValue(ModeProperty); + set => SetValue(ModeProperty, value); + } + + public static readonly StyledProperty IsNoneVisibleProperty = + AvaloniaProperty.Register(nameof(IsNoneVisible)); + + public bool IsNoneVisible + { + get => GetValue(IsNoneVisibleProperty); + set => SetValue(IsNoneVisibleProperty, value); + } + + public static readonly StyledProperty IsContextMenuOpeningProperty = + AvaloniaProperty.Register(nameof(IsContextMenuOpening)); + + public bool IsContextMenuOpening + { + get => GetValue(IsContextMenuOpeningProperty); + set => SetValue(IsContextMenuOpeningProperty, value); + } + + public FilterModeSwitchButton() + { + IsVisible = false; + InitializeComponent(); + } + + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) + { + base.OnPropertyChanged(change); + + if (change.Property == ModeProperty || + change.Property == IsNoneVisibleProperty || + change.Property == IsContextMenuOpeningProperty) + { + var visible = (Mode != Models.FilterMode.None || IsNoneVisible || IsContextMenuOpening); + SetCurrentValue(IsVisibleProperty, visible); + } + } + + private void OnChangeFilterModeButtonClicked(object sender, RoutedEventArgs e) + { + var repoView = this.FindAncestorOfType(); + if (repoView == null) + return; + + var repo = repoView.DataContext as ViewModels.Repository; + if (repo == null) + return; + + var button = sender as Button; + if (button == null) + return; + + if (DataContext is Models.Tag tag) + { + var mode = tag.FilterMode; + + var none = new MenuItem(); + none.Icon = App.CreateMenuIcon("Icons.Eye"); + none.Header = App.Text("Repository.FilterCommits.Default"); + none.IsEnabled = mode != Models.FilterMode.None; + none.Click += (_, ev) => + { + UpdateTagFilterMode(repo, tag, Models.FilterMode.None); + ev.Handled = true; + }; + + var include = new MenuItem(); + include.Icon = App.CreateMenuIcon("Icons.Filter"); + include.Header = App.Text("Repository.FilterCommits.Include"); + include.IsEnabled = mode != Models.FilterMode.Included; + include.Click += (_, ev) => + { + UpdateTagFilterMode(repo, tag, Models.FilterMode.Included); + ev.Handled = true; + }; + + var exclude = new MenuItem(); + exclude.Icon = App.CreateMenuIcon("Icons.EyeClose"); + exclude.Header = App.Text("Repository.FilterCommits.Exclude"); + exclude.IsEnabled = mode != Models.FilterMode.Excluded; + exclude.Click += (_, ev) => + { + UpdateTagFilterMode(repo, tag, Models.FilterMode.Excluded); + ev.Handled = true; + }; + + var menu = new ContextMenu(); + menu.Items.Add(none); + menu.Items.Add(include); + menu.Items.Add(exclude); + + if (mode == Models.FilterMode.None) + { + IsContextMenuOpening = true; + menu.Closed += (_, _) => IsContextMenuOpening = false; + } + + menu.Open(button); + } + else if (DataContext is ViewModels.BranchTreeNode node) + { + var mode = node.FilterMode; + + var none = new MenuItem(); + none.Icon = App.CreateMenuIcon("Icons.Eye"); + none.Header = App.Text("Repository.FilterCommits.Default"); + none.IsEnabled = mode != Models.FilterMode.None; + none.Click += (_, ev) => + { + UpdateBranchFilterMode(repo, node, Models.FilterMode.None); + ev.Handled = true; + }; + + var include = new MenuItem(); + include.Icon = App.CreateMenuIcon("Icons.Filter"); + include.Header = App.Text("Repository.FilterCommits.Include"); + include.IsEnabled = mode != Models.FilterMode.Included; + include.Click += (_, ev) => + { + UpdateBranchFilterMode(repo, node, Models.FilterMode.Included); + ev.Handled = true; + }; + + var exclude = new MenuItem(); + exclude.Icon = App.CreateMenuIcon("Icons.EyeClose"); + exclude.Header = App.Text("Repository.FilterCommits.Exclude"); + exclude.IsEnabled = mode != Models.FilterMode.Excluded; + exclude.Click += (_, ev) => + { + UpdateBranchFilterMode(repo, node, Models.FilterMode.Excluded); + ev.Handled = true; + }; + + var menu = new ContextMenu(); + menu.Items.Add(none); + menu.Items.Add(include); + menu.Items.Add(exclude); + + if (mode == Models.FilterMode.None) + { + IsContextMenuOpening = true; + menu.Closed += (_, _) => IsContextMenuOpening = false; + } + + menu.Open(button); + } + + e.Handled = true; + } + + private void UpdateTagFilterMode(ViewModels.Repository repo, Models.Tag tag, Models.FilterMode mode) + { + var changed = repo.Settings.UpdateHistoriesFilter(tag.Name, Models.FilterType.Tag, mode); + if (changed) + { + tag.FilterMode = mode; + Task.Run(repo.RefreshCommits); + } + } + + private void UpdateBranchFilterMode(ViewModels.Repository repo, ViewModels.BranchTreeNode node, Models.FilterMode mode) + { + var isLocal = node.Path.StartsWith("refs/heads/", StringComparison.Ordinal); + var type = isLocal ? Models.FilterType.LocalBranch : Models.FilterType.RemoteBranch; + var tree = isLocal ? repo.LocalBranchTrees : repo.RemoteBranchTrees; + + if (node.Backend is Models.Branch branch) + { + var changed = repo.Settings.UpdateHistoriesFilter(node.Path, type, mode); + if (!changed) + return; + + node.FilterMode = mode; + + // Try to update its upstream. + if (isLocal && !string.IsNullOrEmpty(branch.Upstream) && mode != Models.FilterMode.Excluded) + { + var upstream = branch.Upstream; + var upstreamNode = FindBranchNode(repo.RemoteBranchTrees, upstream); + if (upstreamNode != null) + { + var canUpdateUpstream = true; + foreach (var filter in repo.Settings.HistoriesFilters) + { + bool matched = false; + if (filter.Type == Models.FilterType.RemoteBranch) + matched = filter.Pattern.Equals(upstream, StringComparison.Ordinal); + else if (filter.Type == Models.FilterType.RemoteBranchFolder) + matched = upstream.StartsWith(filter.Pattern, StringComparison.Ordinal); + + if (matched && filter.Mode == Models.FilterMode.Excluded) + { + canUpdateUpstream = false; + break; + } + } + + if (canUpdateUpstream) + { + changed = repo.Settings.UpdateHistoriesFilter(upstream, Models.FilterType.RemoteBranch, mode); + if (changed) + upstreamNode.FilterMode = mode; + } + } + } + } + else + { + var changed = repo.Settings.UpdateHistoriesFilter(node.Path, isLocal ? Models.FilterType.LocalBranchFolder : Models.FilterType.RemoteBranchFolder, mode); + if (!changed) + return; + + node.FilterMode = mode; + ResetChildrenBranchNodeFilterMode(repo, node, isLocal); + } + + var parentType = isLocal ? Models.FilterType.LocalBranchFolder : Models.FilterType.RemoteBranchFolder; + var cur = node; + do + { + var lastSepIdx = cur.Path.LastIndexOf('/'); + if (lastSepIdx <= 0) + break; + + var parentPath = cur.Path.Substring(0, lastSepIdx); + var parent = FindBranchNode(tree, parentPath); + if (parent == null) + break; + + repo.Settings.UpdateHistoriesFilter(parent.Path, parentType, Models.FilterMode.None); + parent.FilterMode = Models.FilterMode.None; + cur = parent; + } while (true); + + Task.Run(repo.RefreshCommits); + } + + private void ResetChildrenBranchNodeFilterMode(ViewModels.Repository repo, ViewModels.BranchTreeNode node, bool isLocal) + { + foreach (var child in node.Children) + { + child.FilterMode = Models.FilterMode.None; + + if (child.IsBranch) + { + var type = isLocal ? Models.FilterType.LocalBranch : Models.FilterType.RemoteBranch; + repo.Settings.UpdateHistoriesFilter(child.Path, type, Models.FilterMode.None); + } + else + { + var type = isLocal ? Models.FilterType.LocalBranchFolder : Models.FilterType.RemoteBranchFolder; + repo.Settings.UpdateHistoriesFilter(child.Path, type, Models.FilterMode.None); + ResetChildrenBranchNodeFilterMode(repo, child, isLocal); + } + } + } + + private ViewModels.BranchTreeNode FindBranchNode(List nodes, string path) + { + foreach (var node in nodes) + { + if (node.Path.Equals(path, StringComparison.Ordinal)) + return node; + + if (path.StartsWith(node.Path, StringComparison.Ordinal)) + { + var founded = FindBranchNode(node.Children, path); + if (founded != null) + return founded; + } + } + + return null; + } + } +} + + diff --git a/src/Views/Repository.axaml b/src/Views/Repository.axaml index eab0d28a..4ec328f5 100644 --- a/src/Views/Repository.axaml +++ b/src/Views/Repository.axaml @@ -577,14 +577,14 @@ - + - + - + @@ -592,9 +592,17 @@ - - - + + + + + + + diff --git a/src/Views/TagsView.axaml b/src/Views/TagsView.axaml index b50ef481..5dedb661 100644 --- a/src/Views/TagsView.axaml +++ b/src/Views/TagsView.axaml @@ -12,6 +12,10 @@ + + @@ -43,15 +47,14 @@ Classes="primary" Text="{Binding FullPath, Converter={x:Static c:PathConverters.PureFileName}}" Margin="8,0,0,0"/> - - + + + + + + + + @@ -60,33 +63,28 @@ - + Margin="8,0,0,0" + TextTrimming="CharacterEllipsis"/> - + diff --git a/src/Views/TagsView.axaml.cs b/src/Views/TagsView.axaml.cs index bde5ae32..c83cfd28 100644 --- a/src/Views/TagsView.axaml.cs +++ b/src/Views/TagsView.axaml.cs @@ -247,23 +247,6 @@ namespace SourceGit.Views } } - private void OnToggleFilterClicked(object sender, RoutedEventArgs e) - { - if (sender is ToggleButton toggle && DataContext is ViewModels.Repository repo) - { - var target = null as Models.Tag; - if (toggle.DataContext is ViewModels.TagTreeNode node) - target = node.Tag; - else if (toggle.DataContext is Models.Tag tag) - target = tag; - - if (target != null) - repo.UpdateFilters([target.Name], toggle.IsChecked == true); - } - - e.Handled = true; - } - private void MakeTreeRows(List rows, List nodes) { foreach (var node in nodes)