From b467eddead24a76849f395e817a9a48750e1e4e1 Mon Sep 17 00:00:00 2001 From: leo Date: Fri, 18 Jul 2025 13:09:34 +0800 Subject: [PATCH] feature: supports to share issuetracker rules in .issuetracker file (#1567) Signed-off-by: leo --- src/Commands/SharedIssueTracker.cs | 94 ++++++++++++++++++++++++++ src/Models/IssueTrackerRule.cs | 7 ++ src/Resources/Locales/en_US.axaml | 1 + src/Resources/Locales/zh_CN.axaml | 1 + src/Resources/Locales/zh_TW.axaml | 1 + src/ViewModels/Repository.cs | 11 +++ src/ViewModels/RepositoryConfigure.cs | 11 +++ src/Views/RepositoryConfigure.axaml | 8 ++- src/Views/RepositoryConfigure.axaml.cs | 9 +++ 9 files changed, 142 insertions(+), 1 deletion(-) create mode 100644 src/Commands/SharedIssueTracker.cs diff --git a/src/Commands/SharedIssueTracker.cs b/src/Commands/SharedIssueTracker.cs new file mode 100644 index 00000000..3e96fbeb --- /dev/null +++ b/src/Commands/SharedIssueTracker.cs @@ -0,0 +1,94 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; + +namespace SourceGit.Commands +{ + public class SharedIssueTracker : Command + { + public SharedIssueTracker(string repo) + { + WorkingDirectory = repo; + Context = repo; + _file = $"{repo}/.issuetracker"; + } + + public async Task> ReadAllAsync() + { + if (!File.Exists(_file)) + return []; + + Args = $"config -f {_file.Quoted()} -l"; + + var output = await ReadToEndAsync().ConfigureAwait(false); + var rs = new List(); + if (output.IsSuccess) + { + var lines = output.StdOut.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries); + foreach (var line in lines) + { + var parts = line.Split('=', 2); + if (parts.Length < 2) + continue; + + var key = parts[0]; + var value = parts[1]; + + if (!key.StartsWith("issuetracker.", StringComparison.Ordinal)) + continue; + + if (key.EndsWith(".regex", StringComparison.Ordinal)) + { + var prefixLen = "issuetracker.".Length; + var suffixLen = ".regex".Length; + var ruleName = key.Substring(prefixLen, key.Length - prefixLen - suffixLen); + FindOrAdd(rs, ruleName).RegexString = value; + } + else if (key.EndsWith(".url", StringComparison.Ordinal)) + { + var prefixLen = "issuetracker.".Length; + var suffixLen = ".url".Length; + var ruleName = key.Substring(prefixLen, key.Length - prefixLen - suffixLen); + FindOrAdd(rs, ruleName).URLTemplate = value; + } + } + } + + return rs; + } + + public async Task AddAsync(Models.IssueTrackerRule rule) + { + Args = $"config -f {_file.Quoted()} issuetracker.{rule.Name.Quoted()}.regex {rule.RegexString.Quoted()}"; + + var succ = await ExecAsync().ConfigureAwait(false); + if (succ) + { + Args = $"config -f {_file.Quoted()} issuetracker.{rule.Name.Quoted()}.url {rule.URLTemplate.Quoted()}"; + return await ExecAsync().ConfigureAwait(false); + } + + return false; + } + + public async Task RemoveAsync(Models.IssueTrackerRule rule) + { + Args = $"config -f {_file.Quoted()} --remove-section issuetracker.{rule.Name.Quoted()}"; + return await ExecAsync().ConfigureAwait(false); + } + + private Models.IssueTrackerRule FindOrAdd(List rules, string ruleName) + { + var rule = rules.Find(x => x.Name.Equals(ruleName, StringComparison.Ordinal)); + if (rule != null) + return rule; + + rule = new Models.IssueTrackerRule() { IsShared = true, Name = ruleName }; + rules.Add(rule); + return rule; + } + + private readonly string _file; + } +} diff --git a/src/Models/IssueTrackerRule.cs b/src/Models/IssueTrackerRule.cs index 40bb295a..7adaa176 100644 --- a/src/Models/IssueTrackerRule.cs +++ b/src/Models/IssueTrackerRule.cs @@ -6,6 +6,12 @@ namespace SourceGit.Models { public class IssueTrackerRule : ObservableObject { + public bool IsShared + { + get => _isShared; + set => SetProperty(ref _isShared, value); + } + public string Name { get => _name; @@ -70,6 +76,7 @@ namespace SourceGit.Models } } + private bool _isShared; private string _name; private string _regexString; private string _urlTemplate; diff --git a/src/Resources/Locales/en_US.axaml b/src/Resources/Locales/en_US.axaml index 6c57b7c6..03282867 100644 --- a/src/Resources/Locales/en_US.axaml +++ b/src/Resources/Locales/en_US.axaml @@ -204,6 +204,7 @@ New Rule Issue Regex Expression: Rule Name: + Share this rule in .issuetracker file Result URL: Please use $1, $2 to access regex groups values. AI diff --git a/src/Resources/Locales/zh_CN.axaml b/src/Resources/Locales/zh_CN.axaml index 465a432d..df9bcea7 100644 --- a/src/Resources/Locales/zh_CN.axaml +++ b/src/Resources/Locales/zh_CN.axaml @@ -208,6 +208,7 @@ 新增自定义规则 匹配ISSUE的正则表达式 : 规则名 : + 写入 .issuetracker 文件共享此规则 为ISSUE生成的URL链接 : 可在URL中使用$1,$2等变量填入正则表达式匹配的内容 AI diff --git a/src/Resources/Locales/zh_TW.axaml b/src/Resources/Locales/zh_TW.axaml index fcf3b399..f2a54cd0 100644 --- a/src/Resources/Locales/zh_TW.axaml +++ b/src/Resources/Locales/zh_TW.axaml @@ -208,6 +208,7 @@ 新增自訂規則 符合 Issue 的正規表達式: 規則名稱: + 寫入 .issuetracker 檔案以分享此規則 為 Issue 產生的網址連結: 可在網址中使用 $1、$2 等變數填入正規表達式相符的內容 AI diff --git a/src/ViewModels/Repository.cs b/src/ViewModels/Repository.cs index 7dfd60bc..931a1087 100644 --- a/src/ViewModels/Repository.cs +++ b/src/ViewModels/Repository.cs @@ -546,6 +546,13 @@ namespace SourceGit.ViewModels _settings.LastCommitMessage = _workingCopy.CommitMessage; + var sharedIssueTrackers = new List(); + foreach (var rule in _settings.IssueTrackerRules) + if (rule.IsShared) + sharedIssueTrackers.Add(rule); + + _settings.IssueTrackerRules.RemoveAll(sharedIssueTrackers); + try { using var stream = File.Create(Path.Combine(_gitDir, "sourcegit.settings")); @@ -704,6 +711,10 @@ namespace SourceGit.ViewModels Task.Run(async () => { + var sharedIssueTrackers = await new Commands.SharedIssueTracker(_fullpath).ReadAllAsync().ConfigureAwait(false); + if (sharedIssueTrackers.Count > 0) + Dispatcher.UIThread.Post(() => _settings.IssueTrackerRules.InsertRange(0, sharedIssueTrackers)); + var config = await new Commands.Config(_fullpath).ReadAllAsync().ConfigureAwait(false); _hasAllowedSignersFile = config.TryGetValue("gpg.ssh.allowedSignersFile", out var allowedSignersFile) && !string.IsNullOrEmpty(allowedSignersFile); diff --git a/src/ViewModels/RepositoryConfigure.cs b/src/ViewModels/RepositoryConfigure.cs index 4bf5731f..f118a66d 100644 --- a/src/ViewModels/RepositoryConfigure.cs +++ b/src/ViewModels/RepositoryConfigure.cs @@ -295,6 +295,17 @@ namespace SourceGit.ViewModels SelectedIssueTrackerRule = null; } + public async Task ChangeIssueTrackerShareModeAsync() + { + if (_selectedIssueTrackerRule is not { } rule) + return; + + if (rule.IsShared) + await new Commands.SharedIssueTracker(_repo.FullPath).AddAsync(rule); + else + await new Commands.SharedIssueTracker(_repo.FullPath).RemoveAsync(rule); + } + public void AddNewCustomAction() { SelectedCustomAction = _repo.Settings.AddNewCustomAction(); diff --git a/src/Views/RepositoryConfigure.axaml b/src/Views/RepositoryConfigure.axaml index 526ccfed..f243540c 100644 --- a/src/Views/RepositoryConfigure.axaml +++ b/src/Views/RepositoryConfigure.axaml @@ -355,7 +355,7 @@ - + @@ -369,6 +369,12 @@ + + diff --git a/src/Views/RepositoryConfigure.axaml.cs b/src/Views/RepositoryConfigure.axaml.cs index 4f897038..ce6538e4 100644 --- a/src/Views/RepositoryConfigure.axaml.cs +++ b/src/Views/RepositoryConfigure.axaml.cs @@ -48,5 +48,14 @@ namespace SourceGit.Views await dialog.ShowDialog(this); e.Handled = true; } + + private async void OnIssueTrackerIsSharedChanged(object sender, RoutedEventArgs e) + { + if (DataContext is ViewModels.RepositoryConfigure configure) + { + await configure.ChangeIssueTrackerShareModeAsync(); + e.Handled = true; + } + } } }