From ecfe9145a3ed659f9088b088e7bfdaec91a2bf69 Mon Sep 17 00:00:00 2001 From: leo Date: Wed, 27 Aug 2025 15:33:03 +0800 Subject: [PATCH] fix: modified/renamed issue tracker rule can not be saved (#1768) This commit also change the way to save modified issue tracker - It will be saved when closing the `Repository Configuration` dialog. Signed-off-by: leo --- src/Commands/IssueTracker.cs | 29 +++++--- src/Models/IssueTracker.cs | 3 +- src/ViewModels/Repository.cs | 42 +---------- src/ViewModels/RepositoryConfigure.cs | 99 ++++++++++++++++++++++---- src/Views/RepositoryConfigure.axaml | 3 +- src/Views/RepositoryConfigure.axaml.cs | 48 ++++++------- 6 files changed, 131 insertions(+), 93 deletions(-) diff --git a/src/Commands/IssueTracker.cs b/src/Commands/IssueTracker.cs index 6f714da6..e4c15641 100644 --- a/src/Commands/IssueTracker.cs +++ b/src/Commands/IssueTracker.cs @@ -7,20 +7,21 @@ namespace SourceGit.Commands { public class IssueTracker : Command { - public IssueTracker(string repo, string storage) + public IssueTracker(string repo, bool isShared) { WorkingDirectory = repo; Context = repo; - if (string.IsNullOrEmpty(storage)) + if (isShared) { - _isStorageFileExists = true; - _baseArg = "config --local"; + var storage = $"{repo}/.issuetracker"; + _isStorageFileExists = File.Exists(storage); + _baseArg = $"config -f {storage.Quoted()}"; } else { - _isStorageFileExists = File.Exists(storage); - _baseArg = $"config -f {storage.Quoted()}"; + _isStorageFileExists = true; + _baseArg = "config --local"; } } @@ -79,12 +80,24 @@ namespace SourceGit.Commands return false; } - public async Task RemoveAsync(Models.IssueTracker rule) + public async Task UpdateRegexAsync(Models.IssueTracker rule) + { + Args = $"{_baseArg} issuetracker.{rule.Name.Quoted()}.regex {rule.RegexString.Quoted()}"; + return await ExecAsync().ConfigureAwait(false); + } + + public async Task UpdateURLTemplateAsync(Models.IssueTracker rule) + { + Args = $"{_baseArg} issuetracker.{rule.Name.Quoted()}.url {rule.URLTemplate.Quoted()}"; + return await ExecAsync().ConfigureAwait(false); + } + + public async Task RemoveAsync(string name) { if (!_isStorageFileExists) return true; - Args = $"{_baseArg} --remove-section issuetracker.{rule.Name.Quoted()}"; + Args = $"{_baseArg} --remove-section issuetracker.{name.Quoted()}"; return await ExecAsync().ConfigureAwait(false); } diff --git a/src/Models/IssueTracker.cs b/src/Models/IssueTracker.cs index 19aeb3a1..b424707f 100644 --- a/src/Models/IssueTracker.cs +++ b/src/Models/IssueTracker.cs @@ -26,12 +26,11 @@ namespace SourceGit.Models { try { - _regex = null; _regex = new Regex(_regexString, RegexOptions.Multiline); } catch { - // Ignore errors. + _regex = null; } } diff --git a/src/ViewModels/Repository.cs b/src/ViewModels/Repository.cs index f651e8f5..bd258b9d 100644 --- a/src/ViewModels/Repository.cs +++ b/src/ViewModels/Repository.cs @@ -740,39 +740,6 @@ namespace SourceGit.ViewModels return log; } - public async Task AddIssueTrackerAsync(string name, string regex, string url) - { - var rule = new Models.IssueTracker() - { - IsShared = false, - Name = name, - RegexString = regex, - URLTemplate = url, - }; - - var succ = await CreateIssueTrackerCommand(false).AddAsync(rule); - if (succ) - { - IssueTrackers.Add(rule); - return rule; - } - - return null; - } - - public async Task RemoveIssueTrackerAsync(Models.IssueTracker rule) - { - var succ = await CreateIssueTrackerCommand(rule.IsShared).RemoveAsync(rule); - if (succ) - IssueTrackers.Remove(rule); - } - - public async Task ChangeIssueTrackerShareModeAsync(Models.IssueTracker rule) - { - await CreateIssueTrackerCommand(!rule.IsShared).RemoveAsync(rule); - await CreateIssueTrackerCommand(rule.IsShared).AddAsync(rule); - } - public void RefreshAll() { RefreshCommits(); @@ -786,8 +753,8 @@ namespace SourceGit.ViewModels Task.Run(async () => { var issuetrackers = new List(); - await CreateIssueTrackerCommand(true).ReadAllAsync(issuetrackers, true).ConfigureAwait(false); - await CreateIssueTrackerCommand(false).ReadAllAsync(issuetrackers, false).ConfigureAwait(false); + await new Commands.IssueTracker(FullPath, true).ReadAllAsync(issuetrackers, true).ConfigureAwait(false); + await new Commands.IssueTracker(FullPath, false).ReadAllAsync(issuetrackers, false).ConfigureAwait(false); Dispatcher.UIThread.Post(() => { IssueTrackers.Clear(); @@ -1743,11 +1710,6 @@ namespace SourceGit.ViewModels return null; } - private Commands.IssueTracker CreateIssueTrackerCommand(bool shared) - { - return new Commands.IssueTracker(FullPath, shared ? $"{FullPath}/.issuetracker" : null); - } - private BranchTreeNode.Builder BuildBranchTree(List branches, List remotes) { var builder = new BranchTreeNode.Builder(_settings.LocalBranchSortMode, _settings.RemoteBranchSortMode); diff --git a/src/ViewModels/RepositoryConfigure.cs b/src/ViewModels/RepositoryConfigure.cs index 83f22255..0074a19f 100644 --- a/src/ViewModels/RepositoryConfigure.cs +++ b/src/ViewModels/RepositoryConfigure.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Threading.Tasks; using Avalonia.Collections; @@ -114,8 +115,8 @@ namespace SourceGit.ViewModels public AvaloniaList IssueTrackers { - get => _repo.IssueTrackers; - } + get; + } = []; public Models.IssueTracker SelectedIssueTracker { @@ -176,6 +177,17 @@ namespace SourceGit.ViewModels HttpProxy = proxy; if (_cached.TryGetValue("fetch.prune", out var prune)) EnablePruneOnFetch = (prune == "true"); + + foreach (var rule in _repo.IssueTrackers) + { + IssueTrackers.Add(new() + { + IsShared = rule.IsShared, + Name = rule.Name, + RegexString = rule.RegexString, + URLTemplate = rule.URLTemplate, + }); + } } public void ClearHttpProxy() @@ -208,25 +220,28 @@ namespace SourceGit.ViewModels return outs; } - public async Task AddIssueTrackerAsync(string name, string regex, string url) + public void AddIssueTracker(string name, string regex, string url) { - SelectedIssueTracker = await _repo.AddIssueTrackerAsync(name, regex, url); + var rule = new Models.IssueTracker() + { + IsShared = false, + Name = name, + RegexString = regex, + URLTemplate = url, + }; + + IssueTrackers.Add(rule); + SelectedIssueTracker = rule; } - public async Task RemoveIssueTrackerAsync() + public void RemoveIssueTracker() { if (_selectedIssueTracker is { } rule) - await _repo.RemoveIssueTrackerAsync(rule); + IssueTrackers.Remove(rule); SelectedIssueTracker = null; } - public async Task ChangeIssueTrackerShareModeAsync() - { - if (_selectedIssueTracker is { } rule) - await _repo.ChangeIssueTrackerShareModeAsync(rule); - } - public void AddNewCustomAction() { SelectedCustomAction = _repo.Settings.AddNewCustomAction(); @@ -259,6 +274,8 @@ namespace SourceGit.ViewModels await SetIfChangedAsync("user.signingkey", GPGUserSigningKey, ""); await SetIfChangedAsync("http.proxy", HttpProxy, ""); await SetIfChangedAsync("fetch.prune", EnablePruneOnFetch ? "true" : "false", "false"); + + await ApplyIssueTrackerChangesAsync(); } private async Task SetIfChangedAsync(string key, string value, string defValue) @@ -267,6 +284,62 @@ namespace SourceGit.ViewModels await new Commands.Config(_repo.FullPath).SetAsync(key, value); } + private async Task ApplyIssueTrackerChangesAsync() + { + var changed = false; + var oldRules = new Dictionary(); + foreach (var rule in _repo.IssueTrackers) + oldRules.Add(rule.Name, rule); + + foreach (var rule in IssueTrackers) + { + if (oldRules.TryGetValue(rule.Name, out var old)) + { + if (old.IsShared != rule.IsShared) + { + changed = true; + await new Commands.IssueTracker(_repo.FullPath, old.IsShared).RemoveAsync(old.Name); + await new Commands.IssueTracker(_repo.FullPath, rule.IsShared).AddAsync(rule); + } + else + { + if (!old.RegexString.Equals(rule.RegexString, StringComparison.Ordinal)) + { + changed = true; + await new Commands.IssueTracker(_repo.FullPath, old.IsShared).UpdateRegexAsync(rule); + } + + if (!old.URLTemplate.Equals(rule.URLTemplate, StringComparison.Ordinal)) + { + changed = true; + await new Commands.IssueTracker(_repo.FullPath, old.IsShared).UpdateURLTemplateAsync(rule); + } + } + + oldRules.Remove(rule.Name); + } + else + { + changed = true; + await new Commands.IssueTracker(_repo.FullPath, rule.IsShared).AddAsync(rule); + } + } + + if (oldRules.Count > 0) + { + changed = true; + + foreach (var kv in oldRules) + await new Commands.IssueTracker(_repo.FullPath, kv.Value.IsShared).RemoveAsync(kv.Key); + } + + if (changed) + { + _repo.IssueTrackers.Clear(); + _repo.IssueTrackers.AddRange(IssueTrackers); + } + } + private readonly Repository _repo = null; private readonly Dictionary _cached = null; private string _httpProxy; diff --git a/src/Views/RepositoryConfigure.axaml b/src/Views/RepositoryConfigure.axaml index 167d6815..ef682ec5 100644 --- a/src/Views/RepositoryConfigure.axaml +++ b/src/Views/RepositoryConfigure.axaml @@ -374,8 +374,7 @@ + IsChecked="{Binding IsShared, Mode=TwoWay}"/> diff --git a/src/Views/RepositoryConfigure.axaml.cs b/src/Views/RepositoryConfigure.axaml.cs index 4b786f1d..3ce581d7 100644 --- a/src/Views/RepositoryConfigure.axaml.cs +++ b/src/Views/RepositoryConfigure.axaml.cs @@ -50,15 +50,15 @@ namespace SourceGit.Views e.Handled = true; } - private async void OnNewCustomIssueTracker(object sender, RoutedEventArgs e) + private void OnNewCustomIssueTracker(object sender, RoutedEventArgs e) { if (DataContext is ViewModels.RepositoryConfigure vm) - await vm.AddIssueTrackerAsync("New Issue Tracker", @"#(\d+)", "https://xxx/$1"); + vm.AddIssueTracker("New Issue Tracker", @"#(\d+)", "https://xxx/$1"); e.Handled = true; } - private async void OnAddGitHubIssueTracker(object sender, RoutedEventArgs e) + private void OnAddGitHubIssueTracker(object sender, RoutedEventArgs e) { if (DataContext is ViewModels.RepositoryConfigure vm) { @@ -73,17 +73,17 @@ namespace SourceGit.Views } } - await vm.AddIssueTrackerAsync("GitHub Issue", @"#(\d+)", link); + vm.AddIssueTracker("GitHub Issue", @"#(\d+)", link); } e.Handled = true; } - private async void OnAddJiraIssueTracker(object sender, RoutedEventArgs e) + private void OnAddJiraIssueTracker(object sender, RoutedEventArgs e) { if (DataContext is ViewModels.RepositoryConfigure vm) { - await vm.AddIssueTrackerAsync( + vm.AddIssueTracker( "Jira Tracker", @"PROJ-(\d+)", "https://jira.yourcompany.com/browse/PROJ-$1"); @@ -92,11 +92,11 @@ namespace SourceGit.Views e.Handled = true; } - private async void OnAddAzureWorkItemTracker(object sender, RoutedEventArgs e) + private void OnAddAzureWorkItemTracker(object sender, RoutedEventArgs e) { if (DataContext is ViewModels.RepositoryConfigure vm) { - await vm.AddIssueTrackerAsync( + vm.AddIssueTracker( "Azure DevOps Tracker", @"#(\d+)", "https://dev.azure.com/yourcompany/workspace/_workitems/edit/$1"); @@ -105,7 +105,7 @@ namespace SourceGit.Views e.Handled = true; } - private async void OnAddGitLabIssueTracker(object sender, RoutedEventArgs e) + private void OnAddGitLabIssueTracker(object sender, RoutedEventArgs e) { if (DataContext is ViewModels.RepositoryConfigure vm) { @@ -117,13 +117,13 @@ namespace SourceGit.Views break; } - await vm.AddIssueTrackerAsync("GitLab Issue", @"#(\d+)", link); + vm.AddIssueTracker("GitLab Issue", @"#(\d+)", link); } e.Handled = true; } - private async void OnAddGitLabMergeRequestTracker(object sender, RoutedEventArgs e) + private void OnAddGitLabMergeRequestTracker(object sender, RoutedEventArgs e) { if (DataContext is ViewModels.RepositoryConfigure vm) { @@ -135,13 +135,13 @@ namespace SourceGit.Views break; } - await vm.AddIssueTrackerAsync("GitLab MR", @"!(\d+)", link); + vm.AddIssueTracker("GitLab MR", @"!(\d+)", link); } e.Handled = true; } - private async void OnAddGiteeIssueTracker(object sender, RoutedEventArgs e) + private void OnAddGiteeIssueTracker(object sender, RoutedEventArgs e) { if (DataContext is ViewModels.RepositoryConfigure vm) { @@ -156,13 +156,13 @@ namespace SourceGit.Views } } - await vm.AddIssueTrackerAsync("Gitee Issue", @"#([0-9A-Z]{6,10})", link); + vm.AddIssueTracker("Gitee Issue", @"#([0-9A-Z]{6,10})", link); } e.Handled = true; } - private async void OnAddGiteePullRequestTracker(object sender, RoutedEventArgs e) + private void OnAddGiteePullRequestTracker(object sender, RoutedEventArgs e) { if (DataContext is ViewModels.RepositoryConfigure vm) { @@ -177,17 +177,17 @@ namespace SourceGit.Views } } - await vm.AddIssueTrackerAsync("Gitee Pull Request", @"!(\d+)", link); + vm.AddIssueTracker("Gitee Pull Request", @"!(\d+)", link); } e.Handled = true; } - private async void OnAddGerritChangeIdTracker(object sender, RoutedEventArgs e) + private void OnAddGerritChangeIdTracker(object sender, RoutedEventArgs e) { if (DataContext is ViewModels.RepositoryConfigure vm) { - await vm.AddIssueTrackerAsync( + vm.AddIssueTracker( "Gerrit Change-Id", @"(I[A-Za-z0-9]{40})", "https://gerrit.yourcompany.com/q/$1"); @@ -196,18 +196,10 @@ namespace SourceGit.Views e.Handled = true; } - private async void OnRemoveIssueTracker(object sender, RoutedEventArgs e) + private void OnRemoveIssueTracker(object sender, RoutedEventArgs e) { if (DataContext is ViewModels.RepositoryConfigure vm) - await vm.RemoveIssueTrackerAsync(); - - e.Handled = true; - } - - private async void OnIssueTrackerIsSharedChanged(object sender, RoutedEventArgs e) - { - if (DataContext is ViewModels.RepositoryConfigure vm) - await vm.ChangeIssueTrackerShareModeAsync(); + vm.RemoveIssueTracker(); e.Handled = true; }