diff --git a/src/Models/RepositorySettings.cs b/src/Models/RepositorySettings.cs index cb07c295..4027ea9f 100644 --- a/src/Models/RepositorySettings.cs +++ b/src/Models/RepositorySettings.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Text; using Avalonia.Collections; -using Avalonia.Threading; namespace SourceGit.Models { @@ -218,10 +217,26 @@ namespace SourceGit.Models return true; } + public void RemoveChildrenBranchFilters(string pattern) + { + var dirty = new List(); + var prefix = $"{pattern}/"; + + foreach (var filter in HistoriesFilters) + { + if (filter.Type == FilterType.Tag) + continue; + + if (filter.Pattern.StartsWith(prefix, StringComparison.Ordinal)) + dirty.Add(filter); + } + + foreach (var filter in dirty) + HistoriesFilters.Remove(filter); + } + public string BuildHistoriesFilter() { - var builder = new StringBuilder(); - var excludedBranches = new List(); var excludedRemotes = new List(); var excludedTags = new List(); @@ -276,14 +291,11 @@ namespace SourceGit.Models } } - foreach (var b in excludedBranches) - { - builder.Append("--exclude="); - builder.Append(b); - builder.Append(' '); - } + bool hasIncluded = includedBranches.Count > 0 || includedRemotes.Count > 0 || includedTags.Count > 0; + bool hasExcluded = excludedBranches.Count > 0 || excludedRemotes.Count > 0 || excludedTags.Count > 0; - if (includedBranches.Count > 0) + var builder = new StringBuilder(); + if (hasIncluded) { foreach (var b in includedBranches) { @@ -291,44 +303,14 @@ namespace SourceGit.Models 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="); @@ -336,8 +318,42 @@ namespace SourceGit.Models builder.Append(' '); } } - else if (excludedTags.Count > 0 || (includedBranches.Count == 0 && includedRemotes.Count == 0)) + else if (hasExcluded) { + if (excludedBranches.Count > 0) + { + foreach (var b in excludedBranches) + { + builder.Append("--exclude="); + builder.Append(b); + builder.Append(' '); + } + } + + builder.Append("--exclude=HEA[D] --branches "); + + if (excludedRemotes.Count > 0) + { + foreach (var r in excludedRemotes) + { + builder.Append("--exclude="); + builder.Append(r); + builder.Append(' '); + } + } + + builder.Append("--exclude=origin/HEA[D] --remotes "); + + if (excludedTags.Count > 0) + { + foreach (var t in excludedTags) + { + builder.Append("--exclude="); + builder.Append(t); + builder.Append(' '); + } + } + builder.Append("--tags "); } diff --git a/src/ViewModels/Checkout.cs b/src/ViewModels/Checkout.cs index 45276783..713c260f 100644 --- a/src/ViewModels/Checkout.cs +++ b/src/ViewModels/Checkout.cs @@ -64,8 +64,8 @@ namespace SourceGit.ViewModels CallUIThread(() => { var b = _repo.Branches.Find(x => x.IsLocal && x.Name == Branch); - if (b != null) - _repo.UpdateHistoriesFilterAfterCheckout(b); + if (b != null && _repo.HistoriesFilterMode == Models.FilterMode.Included) + _repo.Settings.UpdateHistoriesFilter(b.FullName, Models.FilterType.LocalBranch, Models.FilterMode.Included); _repo.MarkBranchesDirtyManually(); _repo.SetWatcherEnabled(true); diff --git a/src/ViewModels/CreateBranch.cs b/src/ViewModels/CreateBranch.cs index f711100f..653d2db8 100644 --- a/src/ViewModels/CreateBranch.cs +++ b/src/ViewModels/CreateBranch.cs @@ -125,14 +125,8 @@ namespace SourceGit.ViewModels CallUIThread(() => { - if (CheckoutAfterCreated) - { - _repo.UpdateHistoriesFilterAfterCheckout(new Models.Branch() - { - FullName = $"refs/heads/{_name}", - Upstream = BasedOn is Models.Branch { IsLocal: false } remoteBranch ? remoteBranch.FullName : string.Empty, - }); - } + if (CheckoutAfterCreated && _repo.HistoriesFilterMode == Models.FilterMode.Included) + _repo.Settings.UpdateHistoriesFilter($"refs/heads/{_name}", Models.FilterType.LocalBranch, Models.FilterMode.Included); _repo.MarkBranchesDirtyManually(); _repo.SetWatcherEnabled(true); diff --git a/src/ViewModels/Repository.cs b/src/ViewModels/Repository.cs index 4922f4cb..90847041 100644 --- a/src/ViewModels/Repository.cs +++ b/src/ViewModels/Repository.cs @@ -46,6 +46,12 @@ namespace SourceGit.ViewModels get => _settings; } + public Models.FilterMode HistoriesFilterMode + { + get => _historiesFilterMode; + private set => SetProperty(ref _historiesFilterMode, value); + } + public bool HasAllowedSignersFile { get => _hasAllowedSignersFile; @@ -383,6 +389,11 @@ namespace SourceGit.ViewModels App.RaiseException(string.Empty, $"Failed to start watcher for repository: '{_fullpath}'. You may need to press 'F5' to refresh repository manually!\n\nReason: {ex.Message}"); } + if (_settings.HistoriesFilters.Count > 0) + _historiesFilterMode = _settings.HistoriesFilters[0].Mode; + else + _historiesFilterMode = Models.FilterMode.None; + _histories = new Histories(this); _workingCopy = new WorkingCopy(this); _stashesPage = new StashesPage(this); @@ -407,6 +418,7 @@ namespace SourceGit.ViewModels // Ignore } _settings = null; + _historiesFilterMode = Models.FilterMode.None; _autoFetchTimer.Dispose(); _autoFetchTimer = null; @@ -670,49 +682,92 @@ namespace SourceGit.ViewModels public void ClearHistoriesFilter() { _settings.HistoriesFilters.Clear(); + HistoriesFilterMode = Models.FilterMode.None; + ResetBranchTreeFilterMode(LocalBranchTrees); ResetBranchTreeFilterMode(RemoteBranchTrees); ResetTagFilterMode(); Task.Run(RefreshCommits); } - public void MarkHistoriesFilterDirty() + public void SetTagFilterMode(Models.Tag tag, Models.FilterMode mode) { - UpdateBranchTreeFilterMode(LocalBranchTrees, true); - UpdateBranchTreeFilterMode(RemoteBranchTrees, false); - UpdateTagFilterMode(); - Task.Run(RefreshCommits); + var changed = _settings.UpdateHistoriesFilter(tag.Name, Models.FilterType.Tag, mode); + if (changed) + { + if (mode != Models.FilterMode.None || _settings.HistoriesFilters.Count == 0) + HistoriesFilterMode = mode; + + RefreshHistoriesFilters(); + } } - public void UpdateHistoriesFilterAfterCheckout(Models.Branch local) + public void SetBranchFilterMode(BranchTreeNode node, Models.FilterMode mode) { - var hasIncludedBranch = false; - foreach (var filter in _settings.HistoriesFilters) + var isLocal = node.Path.StartsWith("refs/heads/", StringComparison.Ordinal); + var tree = isLocal ? _localBranchTrees : _remoteBranchTrees; + + if (node.Backend is Models.Branch branch) { - if (filter.Type == Models.FilterType.LocalBranch) - { - if (filter.Pattern.Equals(local.FullName, StringComparison.Ordinal)) - return; + var type = isLocal ? Models.FilterType.LocalBranch : Models.FilterType.RemoteBranch; + var changed = _settings.UpdateHistoriesFilter(node.Path, type, mode); + if (!changed) + return; - hasIncludedBranch |= filter.Mode == Models.FilterMode.Included; - } - else if (filter.Type == Models.FilterType.LocalBranchFolder) + if (isLocal && !string.IsNullOrEmpty(branch.Upstream) && mode != Models.FilterMode.Excluded) { - if (local.FullName.StartsWith(filter.Pattern, StringComparison.Ordinal)) - return; + var upstream = branch.Upstream; + var canUpdateUpstream = true; + foreach (var filter in _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); - hasIncludedBranch |= filter.Mode == Models.FilterMode.Included; - } - else if (filter.Type == Models.FilterType.RemoteBranch || filter.Type == Models.FilterType.RemoteBranchFolder) - { - hasIncludedBranch |= filter.Mode == Models.FilterMode.Included; + if (matched && filter.Mode == Models.FilterMode.Excluded) + { + canUpdateUpstream = false; + break; + } + } + + if (canUpdateUpstream) + _settings.UpdateHistoriesFilter(upstream, Models.FilterType.RemoteBranch, mode); } } + else + { + var type = isLocal ? Models.FilterType.LocalBranchFolder : Models.FilterType.RemoteBranchFolder; + var changed = _settings.UpdateHistoriesFilter(node.Path, type, mode); + if (!changed) + return; - if (!hasIncludedBranch) - return; + _settings.RemoveChildrenBranchFilters(node.Path); + } - _settings.UpdateHistoriesFilter(local.FullName, Models.FilterType.LocalBranch, Models.FilterMode.Included); + 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; + + _settings.UpdateHistoriesFilter(parent.Path, parentType, Models.FilterMode.None); + cur = parent; + } while (true); + + if (mode != Models.FilterMode.None || _settings.HistoriesFilters.Count == 0) + HistoriesFilterMode = mode; + + RefreshHistoriesFilters(); } public void StashAll(bool autoStart) @@ -2023,6 +2078,14 @@ namespace SourceGit.ViewModels return visible; } + private void RefreshHistoriesFilters() + { + UpdateBranchTreeFilterMode(LocalBranchTrees, true); + UpdateBranchTreeFilterMode(RemoteBranchTrees, false); + UpdateTagFilterMode(); + Task.Run(RefreshCommits); + } + private void UpdateBranchTreeFilterMode(List nodes, bool isLocal) { foreach (var node in nodes) @@ -2061,6 +2124,24 @@ namespace SourceGit.ViewModels tag.FilterMode = Models.FilterMode.None; } + private 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; + } + private void UpdateCurrentRevisionFilesForSearchSuggestion() { _revisionFiles.Clear(); @@ -2124,6 +2205,7 @@ namespace SourceGit.ViewModels private string _fullpath = string.Empty; private string _gitDir = string.Empty; private Models.RepositorySettings _settings = null; + private Models.FilterMode _historiesFilterMode = Models.FilterMode.None; private bool _hasAllowedSignersFile = false; private Models.Watcher _watcher = null; diff --git a/src/Views/FilterModeSwitchButton.axaml.cs b/src/Views/FilterModeSwitchButton.axaml.cs index 448f455e..f68e9166 100644 --- a/src/Views/FilterModeSwitchButton.axaml.cs +++ b/src/Views/FilterModeSwitchButton.axaml.cs @@ -1,6 +1,3 @@ -using System; -using System.Collections.Generic; - using Avalonia; using Avalonia.Controls; using Avalonia.Interactivity; @@ -82,7 +79,7 @@ namespace SourceGit.Views unset.Header = App.Text("Repository.FilterCommits.Default"); unset.Click += (_, ev) => { - UpdateTagFilterMode(repo, tag, Models.FilterMode.None); + repo.SetTagFilterMode(tag, Models.FilterMode.None); ev.Handled = true; }; @@ -96,7 +93,7 @@ namespace SourceGit.Views include.IsEnabled = mode != Models.FilterMode.Included; include.Click += (_, ev) => { - UpdateTagFilterMode(repo, tag, Models.FilterMode.Included); + repo.SetTagFilterMode(tag, Models.FilterMode.Included); ev.Handled = true; }; @@ -106,7 +103,7 @@ namespace SourceGit.Views exclude.IsEnabled = mode != Models.FilterMode.Excluded; exclude.Click += (_, ev) => { - UpdateTagFilterMode(repo, tag, Models.FilterMode.Excluded); + repo.SetTagFilterMode(tag, Models.FilterMode.Excluded); ev.Handled = true; }; @@ -123,7 +120,7 @@ namespace SourceGit.Views unset.Header = App.Text("Repository.FilterCommits.Default"); unset.Click += (_, ev) => { - UpdateBranchFilterMode(repo, node, Models.FilterMode.None); + repo.SetBranchFilterMode(node, Models.FilterMode.None); ev.Handled = true; }; @@ -137,7 +134,7 @@ namespace SourceGit.Views include.IsEnabled = mode != Models.FilterMode.Included; include.Click += (_, ev) => { - UpdateBranchFilterMode(repo, node, Models.FilterMode.Included); + repo.SetBranchFilterMode(node, Models.FilterMode.Included); ev.Handled = true; }; @@ -147,7 +144,7 @@ namespace SourceGit.Views exclude.IsEnabled = mode != Models.FilterMode.Excluded; exclude.Click += (_, ev) => { - UpdateBranchFilterMode(repo, node, Models.FilterMode.Excluded); + repo.SetBranchFilterMode(node, Models.FilterMode.Excluded); ev.Handled = true; }; @@ -164,113 +161,6 @@ namespace SourceGit.Views 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); - repo.MarkHistoriesFilterDirty(); - } - - 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; - - // Try to update its upstream. - if (isLocal && !string.IsNullOrEmpty(branch.Upstream) && mode != Models.FilterMode.Excluded) - { - var upstream = branch.Upstream; - 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) - repo.Settings.UpdateHistoriesFilter(upstream, Models.FilterType.RemoteBranch, mode); - } - } - else - { - var changed = repo.Settings.UpdateHistoriesFilter(node.Path, isLocal ? Models.FilterType.LocalBranchFolder : Models.FilterType.RemoteBranchFolder, mode); - if (!changed) - return; - - 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); - cur = parent; - } while (true); - - repo.MarkHistoriesFilterDirty(); - } - - private void ResetChildrenBranchNodeFilterMode(ViewModels.Repository repo, ViewModels.BranchTreeNode node, bool isLocal) - { - foreach (var child in node.Children) - { - 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 d32bfa1f..e3da837b 100644 --- a/src/Views/Repository.axaml +++ b/src/Views/Repository.axaml @@ -575,12 +575,19 @@ + Width="12" Height="12" + Data="{StaticResource Icons.Filter}" + Fill="{DynamicResource Brush.FG2}" + IsVisible="{Binding HistoriesFilterMode, Converter={x:Static ObjectConverters.Equal}, ConverterParameter={x:Static m:FilterMode.Included}}"/> + - + diff --git a/src/Views/Repository.axaml.cs b/src/Views/Repository.axaml.cs index cbc654ae..dec3d447 100644 --- a/src/Views/Repository.axaml.cs +++ b/src/Views/Repository.axaml.cs @@ -395,40 +395,5 @@ namespace SourceGit.Views } e.Handled = true; } - - private void OnHistoriesFiltersLayoutUpdated(object sender, EventArgs e) - { - var repo = DataContext as ViewModels.Repository; - if (repo == null) - return; - - var filters = repo.Settings.HistoriesFilters; - if (filters.Count == 0) - return; - - var mode = filters[0].Mode; - if (mode == _lastFilterMode) - return; - - _lastFilterMode = mode; - - var icon = null as StreamGeometry; - switch (mode) - { - case Models.FilterMode.Included: - icon = this.FindResource("Icons.Filter") as StreamGeometry; - break; - case Models.FilterMode.Excluded: - icon = this.FindResource("Icons.EyeClose") as StreamGeometry; - break; - default: - break; - } - - if (icon != null) - HistoriesFilterModeIcon.Data = icon; - } - - private Models.FilterMode _lastFilterMode = Models.FilterMode.None; } }