From 52b51ffb160fb44531ac4f049daa01ae993efacf Mon Sep 17 00:00:00 2001 From: leo Date: Mon, 13 Apr 2026 11:20:42 +0800 Subject: [PATCH 01/45] enhance: AI-based commit message generator (#2255) - Make `AI Assistant` window resizable - Make `AI Assistant` window non-modal dialog - Clean up code Signed-off-by: leo --- src/Views/AIAssistant.axaml | 9 ++++----- src/Views/CommitMessageToolBox.axaml.cs | 19 +++++++++++++++---- src/Views/WorkingCopy.axaml.cs | 20 ++++++++++++++++---- 3 files changed, 35 insertions(+), 13 deletions(-) diff --git a/src/Views/AIAssistant.axaml b/src/Views/AIAssistant.axaml index d7111439..b4c496ea 100644 --- a/src/Views/AIAssistant.axaml +++ b/src/Views/AIAssistant.axaml @@ -4,16 +4,16 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:v="using:SourceGit.Views" xmlns:vm="using:SourceGit.ViewModels" - mc:Ignorable="d" d:DesignWidth="400" d:DesignHeight="120" + mc:Ignorable="d" d:DesignWidth="520" d:DesignHeight="400" x:Class="SourceGit.Views.AIAssistant" x:DataType="vm:AIAssistant" x:Name="ThisControl" Icon="/App.ico" Title="{DynamicResource Text.AIAssistant}" - Width="520" SizeToContent="Height" - CanResize="False" + Width="520" Height="400" + CanResize="True" WindowStartupLocation="CenterOwner"> - + + item.Click += (_, ev) => { - await App.ShowDialog(new ViewModels.AIAssistant(repo, dup, vm.Staged)); + DoOpenAIAssistant(repo, dup, vm.Staged); ev.Handled = true; }; @@ -637,5 +637,16 @@ namespace SourceGit.Views e.Handled = true; } + + private void DoOpenAIAssistant(ViewModels.Repository repo, AI.Service service, List changes) + { + var owner = TopLevel.GetTopLevel(this) as Window; + if (owner == null) + return; + + var assistant = new ViewModels.AIAssistant(repo, service, changes); + var view = new AIAssistant() { DataContext = assistant }; + view.Show(owner); + } } } diff --git a/src/Views/WorkingCopy.axaml.cs b/src/Views/WorkingCopy.axaml.cs index febc0853..ea7a215d 100644 --- a/src/Views/WorkingCopy.axaml.cs +++ b/src/Views/WorkingCopy.axaml.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.IO; using Avalonia.Controls; @@ -927,9 +928,9 @@ namespace SourceGit.Views if (services.Count == 1) { - ai.Click += async (_, e) => + ai.Click += (_, e) => { - await App.ShowDialog(new ViewModels.AIAssistant(repo, services[0], selectedStaged)); + DoOpenAIAssistant(repo, services[0], selectedStaged); e.Handled = true; }; } @@ -941,9 +942,9 @@ namespace SourceGit.Views var item = new MenuItem(); item.Header = service.Name; - item.Click += async (_, e) => + item.Click += (_, e) => { - await App.ShowDialog(new ViewModels.AIAssistant(repo, dup, selectedStaged)); + DoOpenAIAssistant(repo, dup, selectedStaged); e.Handled = true; }; @@ -1369,5 +1370,16 @@ namespace SourceGit.Views menu.Items.Add(custom); menu.Items.Add(new MenuItem() { Header = "-" }); } + + private void DoOpenAIAssistant(ViewModels.Repository repo, AI.Service serivce, List changes) + { + var owner = TopLevel.GetTopLevel(this) as Window; + if (owner == null) + return; + + var assistant = new ViewModels.AIAssistant(repo, serivce, changes); + var view = new AIAssistant() { DataContext = assistant }; + view.Show(owner); + } } } From 3db91ed5aee2c0ee53c5de90658d3dd5f51eddcf Mon Sep 17 00:00:00 2001 From: leo Date: Mon, 13 Apr 2026 14:05:19 +0800 Subject: [PATCH 02/45] feature: supports to choose group and bookmark when cloning remote repository Signed-off-by: leo --- src/Resources/Locales/en_US.axaml | 2 ++ src/Resources/Locales/zh_CN.axaml | 2 ++ src/Resources/Locales/zh_TW.axaml | 2 ++ src/ViewModels/Clone.cs | 49 ++++++++++++++++++++++++++++++- src/Views/Clone.axaml | 42 ++++++++++++++++++++++++-- 5 files changed, 94 insertions(+), 3 deletions(-) diff --git a/src/Resources/Locales/en_US.axaml b/src/Resources/Locales/en_US.axaml index a14c66a1..469ac96b 100644 --- a/src/Resources/Locales/en_US.axaml +++ b/src/Resources/Locales/en_US.axaml @@ -130,6 +130,8 @@ Clone Remote Repository Extra Parameters: Additional arguments to clone repository. Optional. + Bookmark: + Group: Local Name: Repository name. Optional. Parent Folder: diff --git a/src/Resources/Locales/zh_CN.axaml b/src/Resources/Locales/zh_CN.axaml index b8b926bf..9ab93f57 100644 --- a/src/Resources/Locales/zh_CN.axaml +++ b/src/Resources/Locales/zh_CN.axaml @@ -134,6 +134,8 @@ 克隆远程仓库 额外参数 : 其他克隆参数,选填。 + 书签 : + 自定义分组 : 本地仓库名 : 本地仓库目录的名字,选填。 父级目录 : diff --git a/src/Resources/Locales/zh_TW.axaml b/src/Resources/Locales/zh_TW.axaml index 4751416e..dd35a996 100644 --- a/src/Resources/Locales/zh_TW.axaml +++ b/src/Resources/Locales/zh_TW.axaml @@ -134,6 +134,8 @@ 複製 (clone) 遠端存放庫 額外參數: 其他複製參數,選填。 + 書籤: + 群組: 本機存放庫名稱: 本機存放庫目錄的名稱,選填。 上層目錄: diff --git a/src/ViewModels/Clone.cs b/src/ViewModels/Clone.cs index 84e712fd..d6ec8109 100644 --- a/src/ViewModels/Clone.cs +++ b/src/ViewModels/Clone.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.IO; using System.Threading.Tasks; @@ -45,6 +46,28 @@ namespace SourceGit.ViewModels set => SetProperty(ref _local, value); } + public List Groups + { + get; + } + + public RepositoryNode SelectedGroup + { + get => _selectedGroup; + set => SetProperty(ref _selectedGroup, value); + } + + public List Bookmarks + { + get; + } + + public int Bookmark + { + get => _bookmark; + set => SetProperty(ref _bookmark, value); + } + public string ExtraArgs { get => _extraArgs; @@ -61,6 +84,15 @@ namespace SourceGit.ViewModels { _pageId = pageId; + Groups = new List(); + CollectGroups(Groups, Preferences.Instance.RepositoryNodes); + if (Groups.Count > 0) + SelectedGroup = Groups[0]; + + Bookmarks = new List(); + for (var i = 0; i < Models.Bookmarks.Brushes.Length; i++) + Bookmarks.Add(i); + var activeWorkspace = Preferences.Instance.GetActiveWorkspace(); _parentFolder = activeWorkspace?.DefaultCloneDir; if (string.IsNullOrEmpty(ParentFolder)) @@ -134,7 +166,8 @@ namespace SourceGit.ViewModels log.Complete(); - var node = Preferences.Instance.FindOrAddNodeByRepositoryPath(path, null, true); + var node = Preferences.Instance.FindOrAddNodeByRepositoryPath(path, _selectedGroup, true); + node.Bookmark = _bookmark; await node.UpdateStatusAsync(false, null); var launcher = App.GetLauncher(); @@ -153,6 +186,18 @@ namespace SourceGit.ViewModels return true; } + private void CollectGroups(List outs, List collections) + { + foreach (var node in collections) + { + if (!node.IsRepository) + { + outs.Add(node); + CollectGroups(outs, node.SubNodes); + } + } + } + private string _pageId = string.Empty; private string _remote = string.Empty; private bool _useSSH = false; @@ -160,5 +205,7 @@ namespace SourceGit.ViewModels private string _parentFolder = string.Empty; private string _local = string.Empty; private string _extraArgs = string.Empty; + private RepositoryNode _selectedGroup = null; + private int _bookmark = 0; } } diff --git a/src/Views/Clone.axaml b/src/Views/Clone.axaml index 79289ed2..a501474b 100644 --- a/src/Views/Clone.axaml +++ b/src/Views/Clone.axaml @@ -4,6 +4,7 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:vm="using:SourceGit.ViewModels" xmlns:v="using:SourceGit.Views" + xmlns:c="using:SourceGit.Converters" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="SourceGit.Views.Clone" x:DataType="vm:Clone"> @@ -19,7 +20,7 @@ Text="{DynamicResource Text.Clone}"/> - + - + + + + + + + + + + + + + + + + + + + + From c1a5e986bfa26f33c3804530bde1b0923ec70259 Mon Sep 17 00:00:00 2001 From: leo Date: Mon, 13 Apr 2026 15:16:22 +0800 Subject: [PATCH 03/45] refactor: rewrite `Open Local Repository` feature Signed-off-by: leo --- src/Resources/Locales/en_US.axaml | 4 + src/Resources/Locales/zh_CN.axaml | 4 + src/Resources/Locales/zh_TW.axaml | 4 + src/ViewModels/OpenLocalRepository.cs | 115 +++++++++++++++++++++++++ src/ViewModels/Welcome.cs | 26 +++--- src/Views/OpenLocalRepository.axaml | 77 +++++++++++++++++ src/Views/OpenLocalRepository.axaml.cs | 56 ++++++++++++ src/Views/WelcomeToolbar.axaml | 2 +- src/Views/WelcomeToolbar.axaml.cs | 56 ------------ 9 files changed, 274 insertions(+), 70 deletions(-) create mode 100644 src/ViewModels/OpenLocalRepository.cs create mode 100644 src/Views/OpenLocalRepository.axaml create mode 100644 src/Views/OpenLocalRepository.axaml.cs diff --git a/src/Resources/Locales/en_US.axaml b/src/Resources/Locales/en_US.axaml index 469ac96b..cea5479a 100644 --- a/src/Resources/Locales/en_US.axaml +++ b/src/Resources/Locales/en_US.axaml @@ -594,6 +594,10 @@ Open Data Storage Directory Open File Open in External Merge Tool + Open Local Repository + Bookmark: + Group: + Folder: Optional. Create New Tab Close Tab diff --git a/src/Resources/Locales/zh_CN.axaml b/src/Resources/Locales/zh_CN.axaml index 9ab93f57..05ac46fb 100644 --- a/src/Resources/Locales/zh_CN.axaml +++ b/src/Resources/Locales/zh_CN.axaml @@ -598,6 +598,10 @@ 浏览应用数据目录 打开文件 使用外部对比工具查看 + 打开本地仓库 + 书签 : + 分组 : + 仓库位置 : 选填。 新建空白页 关闭标签页 diff --git a/src/Resources/Locales/zh_TW.axaml b/src/Resources/Locales/zh_TW.axaml index dd35a996..890a7560 100644 --- a/src/Resources/Locales/zh_TW.axaml +++ b/src/Resources/Locales/zh_TW.axaml @@ -598,6 +598,10 @@ 瀏覽程式資料目錄 開啟檔案 使用外部比對工具檢視 + 開啟本機存放庫 + 書籤: + 群組: + 存放庫位置: 選填。 新增分頁 關閉分頁 diff --git a/src/ViewModels/OpenLocalRepository.cs b/src/ViewModels/OpenLocalRepository.cs new file mode 100644 index 00000000..18fd9d6a --- /dev/null +++ b/src/ViewModels/OpenLocalRepository.cs @@ -0,0 +1,115 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.IO; +using System.Threading.Tasks; + +namespace SourceGit.ViewModels +{ + public class OpenLocalRepository : Popup + { + [Required(ErrorMessage = "Repository folder is required")] + [CustomValidation(typeof(OpenLocalRepository), nameof(ValidateRepoPath))] + public string RepoPath + { + get => _repoPath; + set => SetProperty(ref _repoPath, value, true); + } + + public List Groups + { + get; + } + + public RepositoryNode Group + { + get => _group; + set => SetProperty(ref _group, value); + } + + public List Bookmarks + { + get; + } + + public int Bookmark + { + get => _bookmark; + set => SetProperty(ref _bookmark, value); + } + + public OpenLocalRepository(string pageId, RepositoryNode group) + { + _pageId = pageId; + _group = group; + + Groups = new List(); + CollectGroups(Groups, Preferences.Instance.RepositoryNodes); + if (Groups.Count > 0 && _group == null) + Group = Groups[0]; + + Bookmarks = new List(); + for (var i = 0; i < Models.Bookmarks.Brushes.Length; i++) + Bookmarks.Add(i); + } + + public static ValidationResult ValidateRepoPath(string folder, ValidationContext _) + { + if (!Directory.Exists(folder)) + return new ValidationResult("Given path can NOT be found"); + return ValidationResult.Success; + } + + public override async Task Sure() + { + var isBare = await new Commands.IsBareRepository(_repoPath).GetResultAsync(); + var repoRoot = _repoPath; + if (!isBare) + { + var test = await new Commands.QueryRepositoryRootPath(_repoPath).GetResultAsync(); + if (test.IsSuccess && !string.IsNullOrWhiteSpace(test.StdOut)) + { + repoRoot = test.StdOut.Trim(); + } + else + { + var launcher = App.GetLauncher(); + foreach (var page in launcher.Pages) + { + if (page.Node.Id.Equals(_pageId, StringComparison.Ordinal)) + { + page.Popup = new Init(page.Node.Id, _repoPath, _group, test.StdErr); + break; + } + } + + return false; + } + } + + var node = Preferences.Instance.FindOrAddNodeByRepositoryPath(repoRoot, _group, true); + node.Bookmark = _bookmark; + await node.UpdateStatusAsync(false, null); + Welcome.Instance.Refresh(); + node.Open(); + return true; + } + + private void CollectGroups(List outs, List collections) + { + foreach (var node in collections) + { + if (!node.IsRepository) + { + outs.Add(node); + CollectGroups(outs, node.SubNodes); + } + } + } + + private string _pageId = string.Empty; + private string _repoPath = string.Empty; + private RepositoryNode _group = null; + private int _bookmark = 0; + } +} diff --git a/src/ViewModels/Welcome.cs b/src/ViewModels/Welcome.cs index 66bde74b..49539292 100644 --- a/src/ViewModels/Welcome.cs +++ b/src/ViewModels/Welcome.cs @@ -128,19 +128,6 @@ namespace SourceGit.ViewModels return rs.StdOut.Trim(); } - public void InitRepository(string path, RepositoryNode parent, string reason) - { - if (!Preferences.Instance.IsGitConfigured()) - { - Models.Notification.Send(null, App.Text("NotConfigured"), true); - return; - } - - var activePage = App.GetLauncher().ActivePage; - if (activePage != null && activePage.CanCreatePopup()) - activePage.Popup = new Init(activePage.Node.Id, path, parent, reason); - } - public async Task AddRepositoryAsync(string path, RepositoryNode parent, bool moveNode, bool open) { var node = Preferences.Instance.FindOrAddNodeByRepositoryPath(path, parent, moveNode); @@ -163,6 +150,19 @@ namespace SourceGit.ViewModels activePage.Popup = new Clone(activePage.Node.Id); } + public void OpenLocalRepository() + { + if (!Preferences.Instance.IsGitConfigured()) + { + Models.Notification.Send(null, App.Text("NotConfigured"), true); + return; + } + + var activePage = App.GetLauncher().ActivePage; + if (activePage != null && activePage.CanCreatePopup()) + activePage.Popup = new OpenLocalRepository(activePage.Node.Id, null); + } + public void OpenTerminal() { if (!Preferences.Instance.IsGitConfigured()) diff --git a/src/Views/OpenLocalRepository.axaml b/src/Views/OpenLocalRepository.axaml new file mode 100644 index 00000000..45dbfdf4 --- /dev/null +++ b/src/Views/OpenLocalRepository.axaml @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Views/OpenLocalRepository.axaml.cs b/src/Views/OpenLocalRepository.axaml.cs new file mode 100644 index 00000000..151662c2 --- /dev/null +++ b/src/Views/OpenLocalRepository.axaml.cs @@ -0,0 +1,56 @@ +using System; +using System.IO; + +using Avalonia.Controls; +using Avalonia.Interactivity; +using Avalonia.Platform.Storage; + +namespace SourceGit.Views +{ + public partial class OpenLocalRepository : UserControl + { + public OpenLocalRepository() + { + InitializeComponent(); + } + + private async void OnSelectRepositoryFolder(object _1, RoutedEventArgs e) + { + if (DataContext is not ViewModels.OpenLocalRepository vm) + return; + + var topLevel = TopLevel.GetTopLevel(this); + if (topLevel == null) + return; + + var preference = ViewModels.Preferences.Instance; + var workspace = preference.GetActiveWorkspace(); + var initDir = workspace.DefaultCloneDir; + if (string.IsNullOrEmpty(initDir) || !Directory.Exists(initDir)) + initDir = preference.GitDefaultCloneDir; + + var options = new FolderPickerOpenOptions() { AllowMultiple = false }; + if (Directory.Exists(initDir)) + { + var folder = await topLevel.StorageProvider.TryGetFolderFromPathAsync(initDir); + options.SuggestedStartLocation = folder; + } + + try + { + var selected = await topLevel.StorageProvider.OpenFolderPickerAsync(options); + if (selected.Count == 1) + { + var folder = selected[0]; + vm.RepoPath = folder is { Path: { IsAbsoluteUri: true } path } ? path.LocalPath : folder?.Path.ToString(); + } + } + catch (Exception exception) + { + Models.Notification.Send(null, $"Failed to open repository: {exception.Message}", true); + } + + e.Handled = true; + } + } +} diff --git a/src/Views/WelcomeToolbar.axaml b/src/Views/WelcomeToolbar.axaml index c218058c..0114c3d3 100644 --- a/src/Views/WelcomeToolbar.axaml +++ b/src/Views/WelcomeToolbar.axaml @@ -19,7 +19,7 @@ - diff --git a/src/Views/WelcomeToolbar.axaml.cs b/src/Views/WelcomeToolbar.axaml.cs index d949c095..19894c4a 100644 --- a/src/Views/WelcomeToolbar.axaml.cs +++ b/src/Views/WelcomeToolbar.axaml.cs @@ -1,9 +1,4 @@ -using System; -using System.IO; - using Avalonia.Controls; -using Avalonia.Interactivity; -using Avalonia.Platform.Storage; namespace SourceGit.Views { @@ -13,56 +8,5 @@ namespace SourceGit.Views { InitializeComponent(); } - - private async void OpenLocalRepository(object _1, RoutedEventArgs e) - { - var activePage = App.GetLauncher().ActivePage; - if (activePage == null || !activePage.CanCreatePopup()) - return; - - var topLevel = TopLevel.GetTopLevel(this); - if (topLevel == null) - return; - - var preference = ViewModels.Preferences.Instance; - var workspace = preference.GetActiveWorkspace(); - var initDir = workspace.DefaultCloneDir; - if (string.IsNullOrEmpty(initDir) || !Directory.Exists(initDir)) - initDir = preference.GitDefaultCloneDir; - - var options = new FolderPickerOpenOptions() { AllowMultiple = false }; - if (Directory.Exists(initDir)) - { - var folder = await topLevel.StorageProvider.TryGetFolderFromPathAsync(initDir); - options.SuggestedStartLocation = folder; - } - - try - { - var selected = await topLevel.StorageProvider.OpenFolderPickerAsync(options); - if (selected.Count == 1) - { - var folder = selected[0]; - var folderPath = folder is { Path: { IsAbsoluteUri: true } path } ? path.LocalPath : folder?.Path.ToString(); - var repoPath = await ViewModels.Welcome.Instance.GetRepositoryRootAsync(folderPath); - if (!string.IsNullOrEmpty(repoPath)) - { - await ViewModels.Welcome.Instance.AddRepositoryAsync(repoPath, null, false, true); - ViewModels.Welcome.Instance.Refresh(); - } - else if (Directory.Exists(folderPath)) - { - var test = await new Commands.QueryRepositoryRootPath(folderPath).GetResultAsync(); - ViewModels.Welcome.Instance.InitRepository(folderPath, null, test.StdErr); - } - } - } - catch (Exception exception) - { - Models.Notification.Send(null, $"Failed to open repository: {exception.Message}", true); - } - - e.Handled = true; - } } } From d6f45cca7d4d0177bd9a91a0f9d1ac139b9c9491 Mon Sep 17 00:00:00 2001 From: leo Date: Mon, 13 Apr 2026 15:17:43 +0800 Subject: [PATCH 04/45] fix: bad condition to check if auto-fetch is enabled (#2257) Signed-off-by: leo --- src/ViewModels/Repository.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ViewModels/Repository.cs b/src/ViewModels/Repository.cs index 7f91241f..6b748f35 100644 --- a/src/ViewModels/Repository.cs +++ b/src/ViewModels/Repository.cs @@ -1887,7 +1887,7 @@ namespace SourceGit.ViewModels try { - if (Preferences.Instance.EnableAutoFetch || !CanCreatePopup()) + if (!Preferences.Instance.EnableAutoFetch || !CanCreatePopup()) { _lastFetchTime = DateTime.Now; return; From 6eaf636b2335f1083e7fbfbe5141700472fe426c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 13 Apr 2026 07:20:39 +0000 Subject: [PATCH 05/45] doc: Update translation status and sort locale files --- TRANSLATION.md | 100 +++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 89 insertions(+), 11 deletions(-) diff --git a/TRANSLATION.md b/TRANSLATION.md index 813b763c..6ed0b42b 100644 --- a/TRANSLATION.md +++ b/TRANSLATION.md @@ -6,7 +6,7 @@ This document shows the translation status of each locale file in the repository ### ![en_US](https://img.shields.io/badge/en__US-%E2%88%9A-brightgreen) -### ![de__DE](https://img.shields.io/badge/de__DE-97.53%25-yellow) +### ![de__DE](https://img.shields.io/badge/de__DE-96.94%25-yellow)
Missing keys in de_DE.axaml @@ -16,6 +16,8 @@ This document shows the translation status of each locale file in the repository - Text.CheckoutBranchFromStash - Text.CheckoutBranchFromStash.Branch - Text.CheckoutBranchFromStash.Stash +- Text.Clone.Bookmark +- Text.Clone.Group - Text.CommandPalette.Branches - Text.CommandPalette.BranchesAndTags - Text.CommandPalette.RepositoryActions @@ -28,6 +30,10 @@ This document shows the translation status of each locale file in the repository - Text.Hotkeys.Repo.GoToChild - Text.Init.CommandTip - Text.Init.ErrorMessageTip +- Text.OpenLocalRepository +- Text.OpenLocalRepository.Bookmark +- Text.OpenLocalRepository.Group +- Text.OpenLocalRepository.Path - Text.Preferences.AI.AdditionalPrompt - Text.Preferences.General.Use24Hours - Text.StashCM.ApplyFileChanges @@ -38,9 +44,21 @@ This document shows the translation status of each locale file in the repository
-### ![es__ES](https://img.shields.io/badge/es__ES-%E2%88%9A-brightgreen) +### ![es__ES](https://img.shields.io/badge/es__ES-99.39%25-yellow) -### ![fr__FR](https://img.shields.io/badge/fr__FR-91.37%25-yellow) +
+Missing keys in es_ES.axaml + +- Text.Clone.Bookmark +- Text.Clone.Group +- Text.OpenLocalRepository +- Text.OpenLocalRepository.Bookmark +- Text.OpenLocalRepository.Group +- Text.OpenLocalRepository.Path + +
+ +### ![fr__FR](https://img.shields.io/badge/fr__FR-90.81%25-yellow)
Missing keys in fr_FR.axaml @@ -60,6 +78,8 @@ This document shows the translation status of each locale file in the repository - Text.CheckoutBranchFromStash - Text.CheckoutBranchFromStash.Branch - Text.CheckoutBranchFromStash.Stash +- Text.Clone.Bookmark +- Text.Clone.Group - Text.CommandPalette.Branches - Text.CommandPalette.BranchesAndTags - Text.CommandPalette.RepositoryActions @@ -101,6 +121,10 @@ This document shows the translation status of each locale file in the repository - Text.MergeConflictEditor.Undo - Text.No - Text.OpenFile +- Text.OpenLocalRepository +- Text.OpenLocalRepository.Bookmark +- Text.OpenLocalRepository.Group +- Text.OpenLocalRepository.Path - Text.PageTabBar.Tab.MoveToWorkspace - Text.PageTabBar.Tab.Refresh - Text.Preferences.AI.AdditionalPrompt @@ -132,7 +156,7 @@ This document shows the translation status of each locale file in the repository
-### ![id__ID](https://img.shields.io/badge/id__ID-89.21%25-yellow) +### ![id__ID](https://img.shields.io/badge/id__ID-88.66%25-yellow)
Missing keys in id_ID.axaml @@ -156,6 +180,8 @@ This document shows the translation status of each locale file in the repository - Text.CheckoutBranchFromStash - Text.CheckoutBranchFromStash.Branch - Text.CheckoutBranchFromStash.Stash +- Text.Clone.Bookmark +- Text.Clone.Group - Text.CommandPalette.Branches - Text.CommandPalette.BranchesAndTags - Text.CommandPalette.RepositoryActions @@ -212,6 +238,10 @@ This document shows the translation status of each locale file in the repository - Text.Open - Text.Open.SystemDefaultEditor - Text.OpenFile +- Text.OpenLocalRepository +- Text.OpenLocalRepository.Bookmark +- Text.OpenLocalRepository.Group +- Text.OpenLocalRepository.Path - Text.PageTabBar.Tab.MoveToWorkspace - Text.PageTabBar.Tab.Refresh - Text.Preferences.AI.AdditionalPrompt @@ -245,7 +275,7 @@ This document shows the translation status of each locale file in the repository
-### ![it__IT](https://img.shields.io/badge/it__IT-96.92%25-yellow) +### ![it__IT](https://img.shields.io/badge/it__IT-96.32%25-yellow)
Missing keys in it_IT.axaml @@ -256,6 +286,8 @@ This document shows the translation status of each locale file in the repository - Text.CheckoutBranchFromStash - Text.CheckoutBranchFromStash.Branch - Text.CheckoutBranchFromStash.Stash +- Text.Clone.Bookmark +- Text.Clone.Group - Text.CommandPalette.Branches - Text.CommandPalette.BranchesAndTags - Text.CommandPalette.RepositoryActions @@ -271,6 +303,10 @@ This document shows the translation status of each locale file in the repository - Text.Hotkeys.Repo.GoToParent - Text.Init.CommandTip - Text.Init.ErrorMessageTip +- Text.OpenLocalRepository +- Text.OpenLocalRepository.Bookmark +- Text.OpenLocalRepository.Group +- Text.OpenLocalRepository.Path - Text.Preferences.AI.AdditionalPrompt - Text.Preferences.General.Use24Hours - Text.SelfUpdate.CurrentVersion @@ -283,7 +319,7 @@ This document shows the translation status of each locale file in the repository
-### ![ja__JP](https://img.shields.io/badge/ja__JP-97.84%25-yellow) +### ![ja__JP](https://img.shields.io/badge/ja__JP-97.24%25-yellow)
Missing keys in ja_JP.axaml @@ -293,6 +329,8 @@ This document shows the translation status of each locale file in the repository - Text.CheckoutBranchFromStash - Text.CheckoutBranchFromStash.Branch - Text.CheckoutBranchFromStash.Stash +- Text.Clone.Bookmark +- Text.Clone.Group - Text.CommandPalette.Branches - Text.CommandPalette.BranchesAndTags - Text.CommandPalette.RepositoryActions @@ -303,6 +341,10 @@ This document shows the translation status of each locale file in the repository - Text.Hotkeys.Repo.CreateBranch - Text.Init.CommandTip - Text.Init.ErrorMessageTip +- Text.OpenLocalRepository +- Text.OpenLocalRepository.Bookmark +- Text.OpenLocalRepository.Group +- Text.OpenLocalRepository.Path - Text.Preferences.AI.AdditionalPrompt - Text.Preferences.General.Use24Hours - Text.StashCM.Branch @@ -312,7 +354,7 @@ This document shows the translation status of each locale file in the repository
-### ![ko__KR](https://img.shields.io/badge/ko__KR-89.52%25-yellow) +### ![ko__KR](https://img.shields.io/badge/ko__KR-88.97%25-yellow)
Missing keys in ko_KR.axaml @@ -336,6 +378,8 @@ This document shows the translation status of each locale file in the repository - Text.CheckoutBranchFromStash - Text.CheckoutBranchFromStash.Branch - Text.CheckoutBranchFromStash.Stash +- Text.Clone.Bookmark +- Text.Clone.Group - Text.CommandPalette.Branches - Text.CommandPalette.BranchesAndTags - Text.CommandPalette.RepositoryActions @@ -387,6 +431,10 @@ This document shows the translation status of each locale file in the repository - Text.Open - Text.Open.SystemDefaultEditor - Text.OpenFile +- Text.OpenLocalRepository +- Text.OpenLocalRepository.Bookmark +- Text.OpenLocalRepository.Group +- Text.OpenLocalRepository.Path - Text.PageTabBar.Tab.MoveToWorkspace - Text.PageTabBar.Tab.Refresh - Text.Preferences.AI.AdditionalPrompt @@ -422,7 +470,7 @@ This document shows the translation status of each locale file in the repository
-### ![pt__BR](https://img.shields.io/badge/pt__BR-67.73%25-red) +### ![pt__BR](https://img.shields.io/badge/pt__BR-67.31%25-red)
Missing keys in pt_BR.axaml @@ -449,6 +497,8 @@ This document shows the translation status of each locale file in the repository - Text.CheckoutBranchFromStash - Text.CheckoutBranchFromStash.Branch - Text.CheckoutBranchFromStash.Stash +- Text.Clone.Bookmark +- Text.Clone.Group - Text.Clone.RecurseSubmodules - Text.CommandPalette.Branches - Text.CommandPalette.BranchesAndTags @@ -610,6 +660,10 @@ This document shows the translation status of each locale file in the repository - Text.Open - Text.Open.SystemDefaultEditor - Text.OpenFile +- Text.OpenLocalRepository +- Text.OpenLocalRepository.Bookmark +- Text.OpenLocalRepository.Group +- Text.OpenLocalRepository.Path - Text.PageTabBar.Tab.MoveToWorkspace - Text.PageTabBar.Tab.Refresh - Text.Preferences.AI.AdditionalPrompt @@ -744,9 +798,21 @@ This document shows the translation status of each locale file in the repository
-### ![ru__RU](https://img.shields.io/badge/ru__RU-%E2%88%9A-brightgreen) +### ![ru__RU](https://img.shields.io/badge/ru__RU-99.39%25-yellow) -### ![ta__IN](https://img.shields.io/badge/ta__IN-69.78%25-red) +
+Missing keys in ru_RU.axaml + +- Text.Clone.Bookmark +- Text.Clone.Group +- Text.OpenLocalRepository +- Text.OpenLocalRepository.Bookmark +- Text.OpenLocalRepository.Group +- Text.OpenLocalRepository.Path + +
+ +### ![ta__IN](https://img.shields.io/badge/ta__IN-69.36%25-red)
Missing keys in ta_IN.axaml @@ -802,6 +868,8 @@ This document shows the translation status of each locale file in the repository - Text.CheckoutBranchFromStash - Text.CheckoutBranchFromStash.Branch - Text.CheckoutBranchFromStash.Stash +- Text.Clone.Bookmark +- Text.Clone.Group - Text.CommandPalette.Branches - Text.CommandPalette.BranchesAndTags - Text.CommandPalette.RepositoryActions @@ -937,6 +1005,10 @@ This document shows the translation status of each locale file in the repository - Text.Open - Text.Open.SystemDefaultEditor - Text.OpenFile +- Text.OpenLocalRepository +- Text.OpenLocalRepository.Bookmark +- Text.OpenLocalRepository.Group +- Text.OpenLocalRepository.Path - Text.PageTabBar.Tab.MoveToWorkspace - Text.PageTabBar.Tab.Refresh - Text.Preferences.AI.AdditionalPrompt @@ -1048,7 +1120,7 @@ This document shows the translation status of each locale file in the repository
-### ![uk__UA](https://img.shields.io/badge/uk__UA-70.61%25-red) +### ![uk__UA](https://img.shields.io/badge/uk__UA-70.17%25-red)
Missing keys in uk_UA.axaml @@ -1104,6 +1176,8 @@ This document shows the translation status of each locale file in the repository - Text.CheckoutBranchFromStash - Text.CheckoutBranchFromStash.Branch - Text.CheckoutBranchFromStash.Stash +- Text.Clone.Bookmark +- Text.Clone.Group - Text.CommandPalette.Branches - Text.CommandPalette.BranchesAndTags - Text.CommandPalette.RepositoryActions @@ -1235,6 +1309,10 @@ This document shows the translation status of each locale file in the repository - Text.Open - Text.Open.SystemDefaultEditor - Text.OpenFile +- Text.OpenLocalRepository +- Text.OpenLocalRepository.Bookmark +- Text.OpenLocalRepository.Group +- Text.OpenLocalRepository.Path - Text.PageTabBar.Tab.MoveToWorkspace - Text.PageTabBar.Tab.Refresh - Text.Preferences.AI.AdditionalPrompt From 6f58f479c2a3c499b54e373c864a6917eb39344e Mon Sep 17 00:00:00 2001 From: leo Date: Mon, 13 Apr 2026 15:42:49 +0800 Subject: [PATCH 06/45] =?UTF-8?q?feature:=20add=20hotkey=20`Ctrl+Shift+O/?= =?UTF-8?q?=E2=8C=98+=E2=87=A7+O`=20to=20open=20local=20repository=20(#225?= =?UTF-8?q?6)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: leo --- src/Resources/Locales/en_US.axaml | 1 + src/Resources/Locales/zh_CN.axaml | 1 + src/Resources/Locales/zh_TW.axaml | 1 + src/Views/Hotkeys.axaml | 21 ++++++++++++--------- src/Views/Launcher.axaml.cs | 10 ++++++++++ src/Views/WelcomeToolbar.axaml | 9 ++++++++- 6 files changed, 33 insertions(+), 10 deletions(-) diff --git a/src/Resources/Locales/en_US.axaml b/src/Resources/Locales/en_US.axaml index cea5479a..b009e0e6 100644 --- a/src/Resources/Locales/en_US.axaml +++ b/src/Resources/Locales/en_US.axaml @@ -501,6 +501,7 @@ Go to next tab Go to previous tab Create new tab + Open local repository Open Preferences dialog Show workspace dropdown menu Switch active tab diff --git a/src/Resources/Locales/zh_CN.axaml b/src/Resources/Locales/zh_CN.axaml index 05ac46fb..59ba7047 100644 --- a/src/Resources/Locales/zh_CN.axaml +++ b/src/Resources/Locales/zh_CN.axaml @@ -505,6 +505,7 @@ 切换到下一个页面 切换到上一个页面 新建页面 + 打开本地仓库 打开偏好设置面板 显示工作区下拉菜单 切换显示页面 diff --git a/src/Resources/Locales/zh_TW.axaml b/src/Resources/Locales/zh_TW.axaml index 890a7560..ea1146d3 100644 --- a/src/Resources/Locales/zh_TW.axaml +++ b/src/Resources/Locales/zh_TW.axaml @@ -505,6 +505,7 @@ 切換到下一個頁面 切換到上一個頁面 新增頁面 + 開啟本機存放庫 開啟偏好設定面板 顯示工作區的下拉式選單 切換目前頁面 diff --git a/src/Views/Hotkeys.axaml b/src/Views/Hotkeys.axaml index 855e205b..c2f0561e 100644 --- a/src/Views/Hotkeys.axaml +++ b/src/Views/Hotkeys.axaml @@ -45,7 +45,7 @@ FontSize="{Binding Source={x:Static vm:Preferences.Instance}, Path=DefaultFontSize, Converter={x:Static c:DoubleConverters.Increase}}" Margin="0,0,0,8"/> - + @@ -64,17 +64,20 @@ - - + + - - + + - - + + - - + + + + + - From f8e8dcab56d2fda14e18ab59236179e49eb10533 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 13 Apr 2026 07:43:11 +0000 Subject: [PATCH 07/45] doc: Update translation status and sort locale files --- TRANSLATION.md | 33 ++++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/TRANSLATION.md b/TRANSLATION.md index 6ed0b42b..81c5b158 100644 --- a/TRANSLATION.md +++ b/TRANSLATION.md @@ -6,7 +6,7 @@ This document shows the translation status of each locale file in the repository ### ![en_US](https://img.shields.io/badge/en__US-%E2%88%9A-brightgreen) -### ![de__DE](https://img.shields.io/badge/de__DE-96.94%25-yellow) +### ![de__DE](https://img.shields.io/badge/de__DE-96.84%25-yellow)
Missing keys in de_DE.axaml @@ -26,6 +26,7 @@ This document shows the translation status of each locale file in the repository - Text.ConfirmEmptyCommit.StageSelectedThenCommit - Text.Discard.IncludeModified - Text.GotoRevisionSelector +- Text.Hotkeys.Global.OpenLocalRepository - Text.Hotkeys.Repo.CreateBranch - Text.Hotkeys.Repo.GoToChild - Text.Init.CommandTip @@ -44,13 +45,14 @@ This document shows the translation status of each locale file in the repository
-### ![es__ES](https://img.shields.io/badge/es__ES-99.39%25-yellow) +### ![es__ES](https://img.shields.io/badge/es__ES-99.29%25-yellow)
Missing keys in es_ES.axaml - Text.Clone.Bookmark - Text.Clone.Group +- Text.Hotkeys.Global.OpenLocalRepository - Text.OpenLocalRepository - Text.OpenLocalRepository.Bookmark - Text.OpenLocalRepository.Group @@ -58,7 +60,7 @@ This document shows the translation status of each locale file in the repository
-### ![fr__FR](https://img.shields.io/badge/fr__FR-90.81%25-yellow) +### ![fr__FR](https://img.shields.io/badge/fr__FR-90.71%25-yellow)
Missing keys in fr_FR.axaml @@ -96,6 +98,7 @@ This document shows the translation status of each locale file in the repository - Text.GotoRevisionSelector - Text.Histories.Header.DateTime - Text.Histories.ShowColumns +- Text.Hotkeys.Global.OpenLocalRepository - Text.Hotkeys.Global.ShowWorkspaceDropdownMenu - Text.Hotkeys.Global.Zoom - Text.Hotkeys.Repo.CreateBranch @@ -156,7 +159,7 @@ This document shows the translation status of each locale file in the repository
-### ![id__ID](https://img.shields.io/badge/id__ID-88.66%25-yellow) +### ![id__ID](https://img.shields.io/badge/id__ID-88.57%25-yellow)
Missing keys in id_ID.axaml @@ -208,6 +211,7 @@ This document shows the translation status of each locale file in the repository - Text.GotoRevisionSelector - Text.Histories.Header.DateTime - Text.Histories.ShowColumns +- Text.Hotkeys.Global.OpenLocalRepository - Text.Hotkeys.Global.ShowWorkspaceDropdownMenu - Text.Hotkeys.Global.Zoom - Text.Hotkeys.Repo.CreateBranch @@ -275,7 +279,7 @@ This document shows the translation status of each locale file in the repository
-### ![it__IT](https://img.shields.io/badge/it__IT-96.32%25-yellow) +### ![it__IT](https://img.shields.io/badge/it__IT-96.22%25-yellow)
Missing keys in it_IT.axaml @@ -298,6 +302,7 @@ This document shows the translation status of each locale file in the repository - Text.GotoRevisionSelector - Text.Histories.Header.DateTime - Text.Histories.ShowColumns +- Text.Hotkeys.Global.OpenLocalRepository - Text.Hotkeys.Repo.CreateBranch - Text.Hotkeys.Repo.GoToChild - Text.Hotkeys.Repo.GoToParent @@ -319,7 +324,7 @@ This document shows the translation status of each locale file in the repository
-### ![ja__JP](https://img.shields.io/badge/ja__JP-97.24%25-yellow) +### ![ja__JP](https://img.shields.io/badge/ja__JP-97.14%25-yellow)
Missing keys in ja_JP.axaml @@ -338,6 +343,7 @@ This document shows the translation status of each locale file in the repository - Text.ConfirmEmptyCommit.StageSelectedThenCommit - Text.DealWithLocalChanges.DoNothing - Text.Discard.IncludeModified +- Text.Hotkeys.Global.OpenLocalRepository - Text.Hotkeys.Repo.CreateBranch - Text.Init.CommandTip - Text.Init.ErrorMessageTip @@ -354,7 +360,7 @@ This document shows the translation status of each locale file in the repository
-### ![ko__KR](https://img.shields.io/badge/ko__KR-88.97%25-yellow) +### ![ko__KR](https://img.shields.io/badge/ko__KR-88.88%25-yellow)
Missing keys in ko_KR.axaml @@ -401,6 +407,7 @@ This document shows the translation status of each locale file in the repository - Text.GotoRevisionSelector - Text.Histories.Header.DateTime - Text.Histories.ShowColumns +- Text.Hotkeys.Global.OpenLocalRepository - Text.Hotkeys.Global.ShowWorkspaceDropdownMenu - Text.Hotkeys.Global.Zoom - Text.Hotkeys.Repo.CreateBranch @@ -470,7 +477,7 @@ This document shows the translation status of each locale file in the repository
-### ![pt__BR](https://img.shields.io/badge/pt__BR-67.31%25-red) +### ![pt__BR](https://img.shields.io/badge/pt__BR-67.24%25-red)
Missing keys in pt_BR.axaml @@ -612,6 +619,7 @@ This document shows the translation status of each locale file in the repository - Text.Histories.Header.DateTime - Text.Histories.ShowColumns - Text.Hotkeys.Global.Clone +- Text.Hotkeys.Global.OpenLocalRepository - Text.Hotkeys.Global.ShowWorkspaceDropdownMenu - Text.Hotkeys.Global.SwitchTab - Text.Hotkeys.Global.Zoom @@ -798,13 +806,14 @@ This document shows the translation status of each locale file in the repository
-### ![ru__RU](https://img.shields.io/badge/ru__RU-99.39%25-yellow) +### ![ru__RU](https://img.shields.io/badge/ru__RU-99.29%25-yellow)
Missing keys in ru_RU.axaml - Text.Clone.Bookmark - Text.Clone.Group +- Text.Hotkeys.Global.OpenLocalRepository - Text.OpenLocalRepository - Text.OpenLocalRepository.Bookmark - Text.OpenLocalRepository.Group @@ -812,7 +821,7 @@ This document shows the translation status of each locale file in the repository
-### ![ta__IN](https://img.shields.io/badge/ta__IN-69.36%25-red) +### ![ta__IN](https://img.shields.io/badge/ta__IN-69.29%25-red)
Missing keys in ta_IN.axaml @@ -966,6 +975,7 @@ This document shows the translation status of each locale file in the repository - Text.GotoRevisionSelector - Text.Histories.Header.DateTime - Text.Histories.ShowColumns +- Text.Hotkeys.Global.OpenLocalRepository - Text.Hotkeys.Global.ShowWorkspaceDropdownMenu - Text.Hotkeys.Global.SwitchTab - Text.Hotkeys.Global.Zoom @@ -1120,7 +1130,7 @@ This document shows the translation status of each locale file in the repository
-### ![uk__UA](https://img.shields.io/badge/uk__UA-70.17%25-red) +### ![uk__UA](https://img.shields.io/badge/uk__UA-70.10%25-red)
Missing keys in uk_UA.axaml @@ -1270,6 +1280,7 @@ This document shows the translation status of each locale file in the repository - Text.GotoRevisionSelector - Text.Histories.Header.DateTime - Text.Histories.ShowColumns +- Text.Hotkeys.Global.OpenLocalRepository - Text.Hotkeys.Global.ShowWorkspaceDropdownMenu - Text.Hotkeys.Global.SwitchTab - Text.Hotkeys.Global.Zoom From c4c75c32c7f019e823300462f7fdb10cbc2ddd01 Mon Sep 17 00:00:00 2001 From: leo Date: Mon, 13 Apr 2026 17:28:55 +0800 Subject: [PATCH 08/45] revert: use blank `User-Agent` header when communicating with open-ai compatible service This reverts commit 26ab0a7529ff502e375baa42a6c7322c22d8b428. Signed-off-by: leo --- src/AI/Service.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/AI/Service.cs b/src/AI/Service.cs index f7eb25e6..e36bc15b 100644 --- a/src/AI/Service.cs +++ b/src/AI/Service.cs @@ -84,8 +84,8 @@ namespace SourceGit.AI { var credential = new ApiKeyCredential(ReadApiKeyFromEnv ? Environment.GetEnvironmentVariable(ApiKey) : ApiKey); return Server.Contains("openai.azure.com/", StringComparison.Ordinal) - ? new AzureOpenAIClient(new Uri(Server), credential, new AzureOpenAIClientOptions() { UserAgentApplicationId = string.Empty }) - : new OpenAIClient(credential, new() { Endpoint = new Uri(Server), UserAgentApplicationId = string.Empty }); + ? new AzureOpenAIClient(new Uri(Server), credential) + : new OpenAIClient(credential, new() { Endpoint = new Uri(Server) }); } private string _name = string.Empty; From b7796f19efcdef7d0d9e8cde3950bc101c194425 Mon Sep 17 00:00:00 2001 From: leo Date: Mon, 13 Apr 2026 18:04:08 +0800 Subject: [PATCH 09/45] feature: show a checked icon when sha was copied Signed-off-by: leo --- src/Views/CommitBaseInfo.axaml | 5 +++- src/Views/CommitBaseInfo.axaml.cs | 38 +++++++++++++++++++++++++++++++ src/Views/CommitTimeTextBlock.cs | 10 +++----- 3 files changed, 45 insertions(+), 8 deletions(-) diff --git a/src/Views/CommitBaseInfo.axaml b/src/Views/CommitBaseInfo.axaml index 7af9d59b..67c1c7b6 100644 --- a/src/Views/CommitBaseInfo.axaml +++ b/src/Views/CommitBaseInfo.axaml @@ -62,7 +62,10 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Views/SubmoduleRevisionCompare.axaml.cs b/src/Views/SubmoduleRevisionCompare.axaml.cs new file mode 100644 index 00000000..3ee7cee6 --- /dev/null +++ b/src/Views/SubmoduleRevisionCompare.axaml.cs @@ -0,0 +1,190 @@ +using System; +using System.IO; +using System.Text; + +using Avalonia.Controls; +using Avalonia.Input; +using Avalonia.Platform.Storage; + +namespace SourceGit.Views +{ + public partial class SubmoduleRevisionCompare : ChromelessWindow + { + public SubmoduleRevisionCompare() + { + InitializeComponent(); + } + + private void OnChangeContextRequested(object sender, ContextRequestedEventArgs e) + { + if (DataContext is ViewModels.SubmoduleRevisionCompare { SelectedChanges: { Count: > 0 } selected } vm && + sender is ChangeCollectionView view) + { + var menu = new ContextMenu(); + + var patch = new MenuItem(); + patch.Header = App.Text("FileCM.SaveAsPatch"); + patch.Icon = this.CreateMenuIcon("Icons.Save"); + patch.Click += async (_, e) => + { + var storageProvider = this.StorageProvider; + if (storageProvider == null) + return; + + var options = new FilePickerSaveOptions(); + options.Title = App.Text("FileCM.SaveAsPatch"); + options.DefaultExtension = ".patch"; + options.FileTypeChoices = [new FilePickerFileType("Patch File") { Patterns = ["*.patch"] }]; + + try + { + var storageFile = await storageProvider.SaveFilePickerAsync(options); + if (storageFile != null) + { + var saveTo = storageFile.Path.LocalPath; + var succ = await vm.SaveChangesAsPatchAsync(selected, saveTo); + if (succ) + await new Alert().ShowAsync(this, "Save patch successfully.", false); + } + } + catch (Exception exception) + { + await new Alert().ShowAsync(this, $"Failed to save as patch: {exception.Message}", true); + } + + e.Handled = true; + }; + + if (selected.Count == 1) + { + var change = selected[0]; + var openWithMerger = new MenuItem(); + openWithMerger.Header = App.Text("OpenInExternalMergeTool"); + openWithMerger.Icon = this.CreateMenuIcon("Icons.OpenWith"); + openWithMerger.Tag = OperatingSystem.IsMacOS() ? "⌘+⇧+D" : "Ctrl+Shift+D"; + openWithMerger.Click += (_, ev) => + { + vm.OpenInExternalDiffTool(change); + ev.Handled = true; + }; + menu.Items.Add(openWithMerger); + + if (change.Index != Models.ChangeState.Deleted) + { + var full = vm.GetAbsPath(change.Path); + var explore = new MenuItem(); + explore.Header = App.Text("RevealFile"); + explore.Icon = this.CreateMenuIcon("Icons.Explore"); + explore.IsEnabled = File.Exists(full); + explore.Click += (_, ev) => + { + Native.OS.OpenInFileManager(full); + ev.Handled = true; + }; + menu.Items.Add(explore); + } + + menu.Items.Add(new MenuItem() { Header = "-" }); + menu.Items.Add(patch); + + var copyPath = new MenuItem(); + copyPath.Header = App.Text("CopyPath"); + copyPath.Icon = this.CreateMenuIcon("Icons.Copy"); + copyPath.Tag = OperatingSystem.IsMacOS() ? "⌘+C" : "Ctrl+C"; + copyPath.Click += async (_, ev) => + { + await this.CopyTextAsync(change.Path); + ev.Handled = true; + }; + + var copyFullPath = new MenuItem(); + copyFullPath.Header = App.Text("CopyFullPath"); + copyFullPath.Icon = this.CreateMenuIcon("Icons.Copy"); + copyFullPath.Tag = OperatingSystem.IsMacOS() ? "⌘+⇧+C" : "Ctrl+Shift+C"; + copyFullPath.Click += async (_, ev) => + { + await this.CopyTextAsync(vm.GetAbsPath(change.Path)); + ev.Handled = true; + }; + + menu.Items.Add(new MenuItem() { Header = "-" }); + menu.Items.Add(copyPath); + menu.Items.Add(copyFullPath); + } + else + { + menu.Items.Add(patch); + + var copyPath = new MenuItem(); + copyPath.Header = App.Text("CopyPath"); + copyPath.Icon = this.CreateMenuIcon("Icons.Copy"); + copyPath.Tag = OperatingSystem.IsMacOS() ? "⌘+C" : "Ctrl+C"; + copyPath.Click += async (_, ev) => + { + var builder = new StringBuilder(); + foreach (var c in selected) + builder.AppendLine(c.Path); + + await this.CopyTextAsync(builder.ToString()); + ev.Handled = true; + }; + + var copyFullPath = new MenuItem(); + copyFullPath.Header = App.Text("CopyFullPath"); + copyFullPath.Icon = this.CreateMenuIcon("Icons.Copy"); + copyFullPath.Tag = OperatingSystem.IsMacOS() ? "⌘+⇧+C" : "Ctrl+Shift+C"; + copyFullPath.Click += async (_, ev) => + { + var builder = new StringBuilder(); + foreach (var c in selected) + builder.AppendLine(vm.GetAbsPath(c.Path)); + + await this.CopyTextAsync(builder.ToString()); + ev.Handled = true; + }; + + menu.Items.Add(new MenuItem() { Header = "-" }); + menu.Items.Add(copyPath); + menu.Items.Add(copyFullPath); + } + + menu.Open(view); + } + + e.Handled = true; + } + + private async void OnChangeCollectionViewKeyDown(object sender, KeyEventArgs e) + { + if (DataContext is not ViewModels.SubmoduleRevisionCompare vm) + return; + + if (sender is not ChangeCollectionView { SelectedChanges: { Count: > 0 } selectedChanges }) + return; + + var cmdKey = OperatingSystem.IsMacOS() ? KeyModifiers.Meta : KeyModifiers.Control; + if (e.Key == Key.C && e.KeyModifiers.HasFlag(cmdKey)) + { + var builder = new StringBuilder(); + var copyAbsPath = e.KeyModifiers.HasFlag(KeyModifiers.Shift); + if (selectedChanges.Count == 1) + { + builder.Append(copyAbsPath ? vm.GetAbsPath(selectedChanges[0].Path) : selectedChanges[0].Path); + } + else + { + foreach (var c in selectedChanges) + builder.AppendLine(copyAbsPath ? vm.GetAbsPath(c.Path) : c.Path); + } + + await this.CopyTextAsync(builder.ToString()); + e.Handled = true; + } + else if (e.Key == Key.F && e.KeyModifiers == cmdKey) + { + ChangeSearchBox.Focus(); + e.Handled = true; + } + } + } +} From 87768e9d98acc97e43c565858eaed9da5b202fa6 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 14 Apr 2026 12:15:24 +0000 Subject: [PATCH 18/45] doc: Update translation status and sort locale files --- TRANSLATION.md | 44 +++++++++++++++++++++++++++++++++----------- 1 file changed, 33 insertions(+), 11 deletions(-) diff --git a/TRANSLATION.md b/TRANSLATION.md index 81c5b158..75d83075 100644 --- a/TRANSLATION.md +++ b/TRANSLATION.md @@ -6,7 +6,7 @@ This document shows the translation status of each locale file in the repository ### ![en_US](https://img.shields.io/badge/en__US-%E2%88%9A-brightgreen) -### ![de__DE](https://img.shields.io/badge/de__DE-96.84%25-yellow) +### ![de__DE](https://img.shields.io/badge/de__DE-96.64%25-yellow)
Missing keys in de_DE.axaml @@ -39,13 +39,15 @@ This document shows the translation status of each locale file in the repository - Text.Preferences.General.Use24Hours - Text.StashCM.ApplyFileChanges - Text.StashCM.Branch +- Text.SubmoduleRevisionCompare +- Text.SubmoduleRevisionCompare.OpenDetails - Text.Worktree.Branch - Text.Worktree.Head - Text.Worktree.Path
-### ![es__ES](https://img.shields.io/badge/es__ES-99.29%25-yellow) +### ![es__ES](https://img.shields.io/badge/es__ES-99.08%25-yellow)
Missing keys in es_ES.axaml @@ -57,10 +59,12 @@ This document shows the translation status of each locale file in the repository - Text.OpenLocalRepository.Bookmark - Text.OpenLocalRepository.Group - Text.OpenLocalRepository.Path +- Text.SubmoduleRevisionCompare +- Text.SubmoduleRevisionCompare.OpenDetails
-### ![fr__FR](https://img.shields.io/badge/fr__FR-90.71%25-yellow) +### ![fr__FR](https://img.shields.io/badge/fr__FR-90.53%25-yellow)
Missing keys in fr_FR.axaml @@ -147,6 +151,8 @@ This document shows the translation status of each locale file in the repository - Text.SquashOrFixup.Into - Text.StashCM.ApplyFileChanges - Text.StashCM.Branch +- Text.SubmoduleRevisionCompare +- Text.SubmoduleRevisionCompare.OpenDetails - Text.TagCM.CompareTwo - Text.TagCM.CompareWith - Text.TagCM.CompareWithHead @@ -159,7 +165,7 @@ This document shows the translation status of each locale file in the repository
-### ![id__ID](https://img.shields.io/badge/id__ID-88.57%25-yellow) +### ![id__ID](https://img.shields.io/badge/id__ID-88.39%25-yellow)
Missing keys in id_ID.axaml @@ -267,6 +273,8 @@ This document shows the translation status of each locale file in the repository - Text.SquashOrFixup.Into - Text.StashCM.ApplyFileChanges - Text.StashCM.Branch +- Text.SubmoduleRevisionCompare +- Text.SubmoduleRevisionCompare.OpenDetails - Text.TagCM.CompareTwo - Text.TagCM.CompareWith - Text.TagCM.CompareWithHead @@ -279,7 +287,7 @@ This document shows the translation status of each locale file in the repository
-### ![it__IT](https://img.shields.io/badge/it__IT-96.22%25-yellow) +### ![it__IT](https://img.shields.io/badge/it__IT-96.03%25-yellow)
Missing keys in it_IT.axaml @@ -318,13 +326,15 @@ This document shows the translation status of each locale file in the repository - Text.SelfUpdate.ReleaseDate - Text.StashCM.ApplyFileChanges - Text.StashCM.Branch +- Text.SubmoduleRevisionCompare +- Text.SubmoduleRevisionCompare.OpenDetails - Text.Worktree.Branch - Text.Worktree.Head - Text.Worktree.Path
-### ![ja__JP](https://img.shields.io/badge/ja__JP-97.14%25-yellow) +### ![ja__JP](https://img.shields.io/badge/ja__JP-96.95%25-yellow)
Missing keys in ja_JP.axaml @@ -354,13 +364,15 @@ This document shows the translation status of each locale file in the repository - Text.Preferences.AI.AdditionalPrompt - Text.Preferences.General.Use24Hours - Text.StashCM.Branch +- Text.SubmoduleRevisionCompare +- Text.SubmoduleRevisionCompare.OpenDetails - Text.Worktree.Branch - Text.Worktree.Head - Text.Worktree.Path
-### ![ko__KR](https://img.shields.io/badge/ko__KR-88.88%25-yellow) +### ![ko__KR](https://img.shields.io/badge/ko__KR-88.70%25-yellow)
Missing keys in ko_KR.axaml @@ -465,6 +477,8 @@ This document shows the translation status of each locale file in the repository - Text.StashCM.ApplyFileChanges - Text.StashCM.Branch - Text.Submodule.Status.Unmerged +- Text.SubmoduleRevisionCompare +- Text.SubmoduleRevisionCompare.OpenDetails - Text.TagCM.CompareTwo - Text.TagCM.CompareWith - Text.TagCM.CompareWithHead @@ -477,7 +491,7 @@ This document shows the translation status of each locale file in the repository
-### ![pt__BR](https://img.shields.io/badge/pt__BR-67.24%25-red) +### ![pt__BR](https://img.shields.io/badge/pt__BR-67.11%25-red)
Missing keys in pt_BR.axaml @@ -768,6 +782,8 @@ This document shows the translation status of each locale file in the repository - Text.Submodule.Status.Unmerged - Text.Submodule.Update - Text.Submodule.URL +- Text.SubmoduleRevisionCompare +- Text.SubmoduleRevisionCompare.OpenDetails - Text.Tag.Tagger - Text.Tag.Time - Text.TagCM.CompareTwo @@ -806,7 +822,7 @@ This document shows the translation status of each locale file in the repository
-### ![ru__RU](https://img.shields.io/badge/ru__RU-99.29%25-yellow) +### ![ru__RU](https://img.shields.io/badge/ru__RU-99.08%25-yellow)
Missing keys in ru_RU.axaml @@ -818,10 +834,12 @@ This document shows the translation status of each locale file in the repository - Text.OpenLocalRepository.Bookmark - Text.OpenLocalRepository.Group - Text.OpenLocalRepository.Path +- Text.SubmoduleRevisionCompare +- Text.SubmoduleRevisionCompare.OpenDetails
-### ![ta__IN](https://img.shields.io/badge/ta__IN-69.29%25-red) +### ![ta__IN](https://img.shields.io/badge/ta__IN-69.14%25-red)
Missing keys in ta_IN.axaml @@ -1094,6 +1112,8 @@ This document shows the translation status of each locale file in the repository - Text.Submodule.Status.Unmerged - Text.Submodule.Update - Text.Submodule.URL +- Text.SubmoduleRevisionCompare +- Text.SubmoduleRevisionCompare.OpenDetails - Text.Tag.Tagger - Text.Tag.Time - Text.TagCM.CompareTwo @@ -1130,7 +1150,7 @@ This document shows the translation status of each locale file in the repository
-### ![uk__UA](https://img.shields.io/badge/uk__UA-70.10%25-red) +### ![uk__UA](https://img.shields.io/badge/uk__UA-69.96%25-red)
Missing keys in uk_UA.axaml @@ -1399,6 +1419,8 @@ This document shows the translation status of each locale file in the repository - Text.Submodule.Status.Unmerged - Text.Submodule.Update - Text.Submodule.URL +- Text.SubmoduleRevisionCompare +- Text.SubmoduleRevisionCompare.OpenDetails - Text.Tag.Tagger - Text.Tag.Time - Text.TagCM.CompareTwo From cff6db8dc7cd15aba68bb47b38ca2e4dbbb7feb5 Mon Sep 17 00:00:00 2001 From: leo Date: Tue, 14 Apr 2026 20:30:19 +0800 Subject: [PATCH 19/45] enhance: disable `OPEN DETAILS` button when one of submodule revision is lost Signed-off-by: leo --- src/Models/DiffResult.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Models/DiffResult.cs b/src/Models/DiffResult.cs index 80f0678d..c85090d9 100644 --- a/src/Models/DiffResult.cs +++ b/src/Models/DiffResult.cs @@ -612,7 +612,9 @@ namespace SourceGit.Models public RevisionSubmodule Old { get; set; } = null; public RevisionSubmodule New { get; set; } = null; - public bool CanOpenDetails => File.Exists(Path.Combine(FullPath, ".git")) && Old != null && New != null; + public bool CanOpenDetails => File.Exists(Path.Combine(FullPath, ".git")) && + Old != null && Old.Commit.Author != User.Invalid && + New != null && New.Commit.Author != User.Invalid; } public class DiffResult From 5fd7551869b76656e4f425a7e6880b0256d8ab90 Mon Sep 17 00:00:00 2001 From: leo Date: Wed, 15 Apr 2026 10:36:54 +0800 Subject: [PATCH 20/45] enhance: show the `Submodule Change Details` window on the same screen of it's parent (#2264) Signed-off-by: leo --- src/Views/DiffView.axaml.cs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/Views/DiffView.axaml.cs b/src/Views/DiffView.axaml.cs index d0198125..43e6e8b4 100644 --- a/src/Views/DiffView.axaml.cs +++ b/src/Views/DiffView.axaml.cs @@ -45,15 +45,10 @@ namespace SourceGit.Views private void OnOpenSubmoduleRevisionCompare(object sender, RoutedEventArgs e) { - var owner = TopLevel.GetTopLevel(this) as Window; - if (owner == null) - return; - if (sender is Button { DataContext: Models.SubmoduleDiff diff } && diff.CanOpenDetails) { var vm = new ViewModels.SubmoduleRevisionCompare(diff); - var dailog = new SubmoduleRevisionCompare() { DataContext = vm }; - dailog.Show(owner); + App.ShowWindow(vm); } } } From 0bf4f922477f2617905537ce207c80c136f94af4 Mon Sep 17 00:00:00 2001 From: leo Date: Wed, 15 Apr 2026 11:00:06 +0800 Subject: [PATCH 21/45] fix: fetch AI models in background to avoid main window waiting to show (#2267) Signed-off-by: leo --- src/AI/Service.cs | 5 +---- src/App.axaml.cs | 1 + src/ViewModels/Preferences.cs | 21 ++++++++++++--------- src/Views/Launcher.axaml.cs | 4 +--- src/Views/Preferences.axaml.cs | 2 +- 5 files changed, 16 insertions(+), 17 deletions(-) diff --git a/src/AI/Service.cs b/src/AI/Service.cs index e36bc15b..ebf9170e 100644 --- a/src/AI/Service.cs +++ b/src/AI/Service.cs @@ -2,7 +2,6 @@ using System.ClientModel; using System.Collections.Generic; using System.Text.Json.Serialization; -using System.Threading.Tasks; using Azure.AI.OpenAI; using CommunityToolkit.Mvvm.ComponentModel; using OpenAI; @@ -55,7 +54,7 @@ namespace SourceGit.AI set; } = string.Empty; - public async Task> FetchAvailableModelsAsync() + public void FetchAvailableModels() { var allModels = GetOpenAIClient().GetOpenAIModelClient().GetModels(); AvailableModels = new List(); @@ -71,8 +70,6 @@ namespace SourceGit.AI { Model = null; } - - return AvailableModels; } public ChatClient GetChatClient() diff --git a/src/App.axaml.cs b/src/App.axaml.cs index 44dbc977..c2f03f7f 100644 --- a/src/App.axaml.cs +++ b/src/App.axaml.cs @@ -547,6 +547,7 @@ namespace SourceGit var pref = ViewModels.Preferences.Instance; pref.SetCanModify(); + pref.UpdateAvailableAIModels(); _launcher = new ViewModels.Launcher(startupRepo); desktop.MainWindow = new Views.Launcher() { DataContext = _launcher }; diff --git a/src/ViewModels/Preferences.cs b/src/ViewModels/Preferences.cs index 04ffbeb9..c68f5559 100644 --- a/src/ViewModels/Preferences.cs +++ b/src/ViewModels/Preferences.cs @@ -629,19 +629,22 @@ namespace SourceGit.ViewModels RemoveInvalidRepositoriesRecursive(RepositoryNodes); } - public async Task UpdateAvailableAIModelsAsync() + public void UpdateAvailableAIModels() { - foreach (var service in OpenAIServices) + Task.Run(() => { - try + foreach (var service in OpenAIServices) { - await service.FetchAvailableModelsAsync(); + try + { + service.FetchAvailableModels(); + } + catch + { + // Ignore errors. + } } - catch - { - // Ignore errors. - } - } + }); } public void Save() diff --git a/src/Views/Launcher.axaml.cs b/src/Views/Launcher.axaml.cs index fb295c55..0c8b32c5 100644 --- a/src/Views/Launcher.axaml.cs +++ b/src/Views/Launcher.axaml.cs @@ -106,7 +106,7 @@ namespace SourceGit.Views Activate(); } - protected override async void OnOpened(EventArgs e) + protected override void OnOpened(EventArgs e) { base.OnOpened(e); @@ -114,8 +114,6 @@ namespace SourceGit.Views var state = preferences.Layout.LauncherWindowState; if (state == WindowState.Maximized || state == WindowState.FullScreen) WindowState = WindowState.Maximized; - - await preferences.UpdateAvailableAIModelsAsync(); } protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) diff --git a/src/Views/Preferences.axaml.cs b/src/Views/Preferences.axaml.cs index 28cdf901..3c9c2e3f 100644 --- a/src/Views/Preferences.axaml.cs +++ b/src/Views/Preferences.axaml.cs @@ -206,7 +206,7 @@ namespace SourceGit.Views } var preferences = ViewModels.Preferences.Instance; - await preferences.UpdateAvailableAIModelsAsync(); + preferences.UpdateAvailableAIModels(); preferences.Save(); } From 2ed3a79e7f75640ee0b2dd074829f57b6161cb02 Mon Sep 17 00:00:00 2001 From: leo Date: Wed, 15 Apr 2026 12:14:55 +0800 Subject: [PATCH 22/45] refactor: move some code from `App` to `Views.ControlExtensions` Signed-off-by: leo --- src/App.Commands.cs | 47 ++++++++++-- src/App.axaml.cs | 80 ++------------------ src/ViewModels/BlameCommandPalette.cs | 6 +- src/ViewModels/CompareCommandPalette.cs | 6 +- src/ViewModels/Conflict.cs | 5 +- src/ViewModels/FileHistoryCommandPalette.cs | 6 +- src/ViewModels/Histories.cs | 16 ---- src/ViewModels/RepositoryCommandPalette.cs | 1 - src/Views/BlameCommandPalette.axaml.cs | 4 +- src/Views/BranchTree.axaml.cs | 10 +-- src/Views/CommitDetail.axaml.cs | 6 +- src/Views/CompareCommandPalette.axaml.cs | 4 +- src/Views/Conflict.axaml.cs | 5 +- src/Views/ControlExtensions.cs | 60 +++++++++++++++ src/Views/DiffView.axaml.cs | 2 +- src/Views/FileHistoryCommandPalette.axaml.cs | 4 +- src/Views/Histories.axaml.cs | 31 ++++++-- src/Views/Launcher.axaml.cs | 6 +- src/Views/Preferences.axaml.cs | 9 +-- src/Views/RepositoryConfigure.axaml.cs | 7 +- src/Views/RepositoryToolbar.axaml.cs | 10 +-- src/Views/RevisionFileTreeView.axaml.cs | 6 +- src/Views/SubmodulesView.axaml.cs | 2 +- src/Views/TagsView.axaml.cs | 4 +- src/Views/WorkingCopy.axaml.cs | 20 ++--- 25 files changed, 185 insertions(+), 172 deletions(-) diff --git a/src/App.Commands.cs b/src/App.Commands.cs index 54175070..56c15009 100644 --- a/src/App.Commands.cs +++ b/src/App.Commands.cs @@ -37,12 +37,47 @@ namespace SourceGit } } - public static readonly Command OpenPreferencesCommand = new Command(async _ => await ShowDialog(new Views.Preferences())); - public static readonly Command OpenHotkeysCommand = new Command(async _ => await ShowDialog(new Views.Hotkeys())); - public static readonly Command OpenAppDataDirCommand = new Command(_ => Native.OS.OpenInFileManager(Native.OS.DataDir)); - public static readonly Command OpenAboutCommand = new Command(async _ => await ShowDialog(new Views.About())); - public static readonly Command CheckForUpdateCommand = new Command(_ => (Current as App)?.Check4Update(true)); - public static readonly Command QuitCommand = new Command(_ => Quit(0)); + public static readonly Command OpenPreferencesCommand = new Command(async _ => + { + if (Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime { MainWindow: { } owner }) + { + var dialog = new Views.Preferences(); + await dialog.ShowDialog(owner); + } + }); + + public static readonly Command OpenHotkeysCommand = new Command(async _ => + { + if (Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime { MainWindow: { } owner }) + { + var dialog = new Views.Hotkeys(); + await dialog.ShowDialog(owner); + } + }); + + public static readonly Command OpenAppDataDirCommand = new Command(_ => + { + Native.OS.OpenInFileManager(Native.OS.DataDir); + }); + + public static readonly Command OpenAboutCommand = new Command(async _ => + { + if (Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime { MainWindow: { } owner }) + { + var dialog = new Views.About(); + await dialog.ShowDialog(owner); + } + }); + + public static readonly Command CheckForUpdateCommand = new Command(_ => + { + (Current as App)?.Check4Update(true); + }); + + public static readonly Command QuitCommand = new Command(_ => + { + Quit(0); + }); public static readonly Command HideAppCommand = new Command(_ => { diff --git a/src/App.axaml.cs b/src/App.axaml.cs index c2f03f7f..8d6bafb7 100644 --- a/src/App.axaml.cs +++ b/src/App.axaml.cs @@ -75,79 +75,6 @@ namespace SourceGit #endregion #region Utility Functions - public static Task ShowDialog(object data, Window owner = null) - { - if (owner == null) - { - if (Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime { MainWindow: { } mainWindow }) - owner = mainWindow; - else - return null; - } - - if (data is Views.ChromelessWindow window) - return window.ShowDialog(owner); - - window = Views.ControlExtensions.CreateFromViewModels(data) as Views.ChromelessWindow; - if (window != null) - { - window.DataContext = data; - return window.ShowDialog(owner); - } - - return null; - } - - public static void ShowWindow(object data) - { - if (data is not Views.ChromelessWindow window) - { - window = Views.ControlExtensions.CreateFromViewModels(data) as Views.ChromelessWindow; - if (window == null) - return; - - window.DataContext = data; - } - - do - { - if (Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime { Windows: { Count: > 0 } windows }) - { - // Try to find the actived window (fall back to `MainWindow`) - Window actived = windows[0]; - if (!actived.IsActive) - { - for (var i = 1; i < windows.Count; i++) - { - var test = windows[i]; - if (test.IsActive) - { - actived = test; - break; - } - } - } - - // Get the screen where current window locates. - var screen = actived.Screens.ScreenFromWindow(actived) ?? actived.Screens.Primary; - if (screen == null) - break; - - // Calculate the startup position (Center Screen Mode) of target window - var rect = new PixelRect(PixelSize.FromSize(window.ClientSize, actived.DesktopScaling)); - var centeredRect = screen.WorkingArea.CenterRect(rect); - if (actived.Screens.ScreenFromPoint(centeredRect.Position) == null) - break; - - // Use the startup position - window.WindowStartupLocation = WindowStartupLocation.Manual; - window.Position = centeredRect.Position; - } - } while (false); - - window.Show(); - } - public static async Task AskConfirmAsync(string message, Models.ConfirmButtonType buttonType = Models.ConfirmButtonType.OkCancel) { if (Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime { MainWindow: { } owner }) @@ -620,7 +547,12 @@ namespace SourceGit { Dispatcher.UIThread.Invoke(async () => { - await ShowDialog(new ViewModels.SelfUpdate { Data = data }); + if (Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime { MainWindow: { } owner }) + { + var ctx = new ViewModels.SelfUpdate { Data = data }; + var dialog = new Views.SelfUpdate() { DataContext = ctx }; + await dialog.ShowDialog(owner); + } }); } catch diff --git a/src/ViewModels/BlameCommandPalette.cs b/src/ViewModels/BlameCommandPalette.cs index c277a47c..e7343087 100644 --- a/src/ViewModels/BlameCommandPalette.cs +++ b/src/ViewModels/BlameCommandPalette.cs @@ -65,14 +65,12 @@ namespace SourceGit.ViewModels Filter = string.Empty; } - public void Launch() + public Blame Launch() { _repoFiles.Clear(); _visibleFiles.Clear(); Close(); - - if (!string.IsNullOrEmpty(_selectedFile)) - App.ShowWindow(new Blame(_repo, _selectedFile, _head)); + return !string.IsNullOrEmpty(_selectedFile) ? new Blame(_repo, _selectedFile, _head) : null; } private void UpdateVisible() diff --git a/src/ViewModels/CompareCommandPalette.cs b/src/ViewModels/CompareCommandPalette.cs index bebffe37..7eb49efd 100644 --- a/src/ViewModels/CompareCommandPalette.cs +++ b/src/ViewModels/CompareCommandPalette.cs @@ -44,13 +44,11 @@ namespace SourceGit.ViewModels Filter = string.Empty; } - public void Launch() + public Compare Launch() { _refs.Clear(); Close(); - - if (_compareTo != null) - App.ShowWindow(new Compare(_repo, _basedOn, _compareTo)); + return _compareTo != null ? new Compare(_repo, _basedOn, _compareTo) : null; } private void UpdateRefs() diff --git a/src/ViewModels/Conflict.cs b/src/ViewModels/Conflict.cs index 06ac6077..380ad68d 100644 --- a/src/ViewModels/Conflict.cs +++ b/src/ViewModels/Conflict.cs @@ -73,10 +73,9 @@ namespace SourceGit.ViewModels await _wc.UseMineAsync([_change]); } - public async Task MergeAsync() + public MergeConflictEditor CreateOpenMergeEditorRequest() { - if (CanMerge) - await App.ShowDialog(new MergeConflictEditor(_repo, _head, _change.Path)); + return CanMerge ? new MergeConflictEditor(_repo, _head, _change.Path) : null; } public async Task MergeExternalAsync() diff --git a/src/ViewModels/FileHistoryCommandPalette.cs b/src/ViewModels/FileHistoryCommandPalette.cs index 8a371feb..097e8566 100644 --- a/src/ViewModels/FileHistoryCommandPalette.cs +++ b/src/ViewModels/FileHistoryCommandPalette.cs @@ -60,14 +60,12 @@ namespace SourceGit.ViewModels Filter = string.Empty; } - public void Launch() + public FileHistories Launch() { _repoFiles.Clear(); _visibleFiles.Clear(); Close(); - - if (!string.IsNullOrEmpty(_selectedFile)) - App.ShowWindow(new FileHistories(_repo, _selectedFile)); + return !string.IsNullOrEmpty(_selectedFile) ? new FileHistories(_repo, _selectedFile) : null; } private void UpdateVisible() diff --git a/src/ViewModels/Histories.cs b/src/ViewModels/Histories.cs index d7a81b0f..4d0fd02f 100644 --- a/src/ViewModels/Histories.cs +++ b/src/ViewModels/Histories.cs @@ -418,22 +418,6 @@ namespace SourceGit.ViewModels _repo.ShowPopup(new DropHead(_repo, head, parent)); } - public async Task InteractiveRebaseAsync(Models.Commit commit, Models.InteractiveRebaseAction act) - { - var prefill = new InteractiveRebasePrefill(commit.SHA, act); - var start = act switch - { - Models.InteractiveRebaseAction.Squash or Models.InteractiveRebaseAction.Fixup => $"{commit.SHA}~~", - _ => $"{commit.SHA}~", - }; - - var on = await new Commands.QuerySingleCommit(_repo.FullPath, start).GetResultAsync(); - if (on == null) - _repo.SendNotification($"Can not squash current commit into parent!", true); - else - await App.ShowDialog(new InteractiveRebase(_repo, on, prefill)); - } - public async Task GetCommitFullMessageAsync(Models.Commit commit) { return await new Commands.QueryCommitFullMessage(_repo.FullPath, commit.SHA) diff --git a/src/ViewModels/RepositoryCommandPalette.cs b/src/ViewModels/RepositoryCommandPalette.cs index 6d6c77c8..2b57414a 100644 --- a/src/ViewModels/RepositoryCommandPalette.cs +++ b/src/ViewModels/RepositoryCommandPalette.cs @@ -73,7 +73,6 @@ namespace SourceGit.ViewModels _cmds.Add(new("Push", "push", "Push", async () => await repo.PushAsync(false))); _cmds.Add(new("Stash.Title", "stash", "Stashes.Add", async () => await repo.StashAllAsync(false))); _cmds.Add(new("Apply.Title", "apply", "Diff", () => repo.ApplyPatch())); - _cmds.Add(new("Configure", "configure", "Settings", async () => await App.ShowDialog(new RepositoryConfigure(repo)))); _cmds.Sort((l, r) => l.Label.CompareTo(r.Label)); _visibleCmds = _cmds; diff --git a/src/Views/BlameCommandPalette.axaml.cs b/src/Views/BlameCommandPalette.axaml.cs index 32c5381f..56de2d6d 100644 --- a/src/Views/BlameCommandPalette.axaml.cs +++ b/src/Views/BlameCommandPalette.axaml.cs @@ -19,7 +19,7 @@ namespace SourceGit.Views if (e.Key == Key.Enter) { - vm.Launch(); + this.ShowWindow(vm.Launch()); e.Handled = true; } else if (e.Key == Key.Up) @@ -55,7 +55,7 @@ namespace SourceGit.Views { if (DataContext is ViewModels.BlameCommandPalette vm) { - vm.Launch(); + this.ShowWindow(vm.Launch()); e.Handled = true; } } diff --git a/src/Views/BranchTree.axaml.cs b/src/Views/BranchTree.axaml.cs index c7ca5276..603666b3 100644 --- a/src/Views/BranchTree.axaml.cs +++ b/src/Views/BranchTree.axaml.cs @@ -537,7 +537,7 @@ namespace SourceGit.Views compare.Icon = this.CreateMenuIcon("Icons.Compare"); compare.Click += (_, ev) => { - App.ShowWindow(new ViewModels.Compare(repo, branches[0], branches[1])); + this.ShowWindow(new ViewModels.Compare(repo, branches[0], branches[1])); ev.Handled = true; }; menu.Items.Add(compare); @@ -819,7 +819,7 @@ namespace SourceGit.Views interactiveRebase.Click += async (_, e) => { var commit = await new Commands.QuerySingleCommit(repo.FullPath, branch.Head).GetResultAsync(); - await App.ShowDialog(new ViewModels.InteractiveRebase(repo, commit)); + await this.ShowDialogAsync(new ViewModels.InteractiveRebase(repo, commit)); e.Handled = true; }; @@ -853,7 +853,7 @@ namespace SourceGit.Views compareWithCurrent.Icon = this.CreateMenuIcon("Icons.Compare"); compareWithCurrent.Click += (_, _) => { - App.ShowWindow(new ViewModels.Compare(repo, branch, current)); + this.ShowWindow(new ViewModels.Compare(repo, branch, current)); }; var compareWith = new MenuItem(); @@ -1154,7 +1154,7 @@ namespace SourceGit.Views interactiveRebase.Click += async (_, e) => { var commit = await new Commands.QuerySingleCommit(repo.FullPath, branch.Head).GetResultAsync(); - await App.ShowDialog(new ViewModels.InteractiveRebase(repo, commit)); + await this.ShowDialogAsync(new ViewModels.InteractiveRebase(repo, commit)); e.Handled = true; }; @@ -1163,7 +1163,7 @@ namespace SourceGit.Views compareWithHead.Icon = this.CreateMenuIcon("Icons.Compare"); compareWithHead.Click += (_, _) => { - App.ShowWindow(new ViewModels.Compare(repo, branch, current)); + this.ShowWindow(new ViewModels.Compare(repo, branch, current)); }; var compareWith = new MenuItem(); diff --git a/src/Views/CommitDetail.axaml.cs b/src/Views/CommitDetail.axaml.cs index f0e55665..99969853 100644 --- a/src/Views/CommitDetail.axaml.cs +++ b/src/Views/CommitDetail.axaml.cs @@ -38,7 +38,7 @@ namespace SourceGit.Views history.Icon = this.CreateMenuIcon("Icons.Histories"); history.Click += (_, ev) => { - App.ShowWindow(new ViewModels.DirHistories(repo, node.FullPath, commit.SHA)); + this.ShowWindow(new ViewModels.DirHistories(repo, node.FullPath, commit.SHA)); ev.Handled = true; }; @@ -273,7 +273,7 @@ namespace SourceGit.Views history.Icon = this.CreateMenuIcon("Icons.Histories"); history.Click += (_, ev) => { - App.ShowWindow(new ViewModels.FileHistories(repo.FullPath, change.Path, commit.SHA)); + this.ShowWindow(new ViewModels.FileHistories(repo.FullPath, change.Path, commit.SHA)); ev.Handled = true; }; @@ -283,7 +283,7 @@ namespace SourceGit.Views blame.IsEnabled = change.Index != Models.ChangeState.Deleted; blame.Click += (_, ev) => { - App.ShowWindow(new ViewModels.Blame(repo.FullPath, change.Path, commit)); + this.ShowWindow(new ViewModels.Blame(repo.FullPath, change.Path, commit)); ev.Handled = true; }; diff --git a/src/Views/CompareCommandPalette.axaml.cs b/src/Views/CompareCommandPalette.axaml.cs index e0c0c825..44809f27 100644 --- a/src/Views/CompareCommandPalette.axaml.cs +++ b/src/Views/CompareCommandPalette.axaml.cs @@ -19,7 +19,7 @@ namespace SourceGit.Views if (e.Key == Key.Enter) { - vm.Launch(); + this.ShowWindow(vm.Launch()); e.Handled = true; } else if (e.Key == Key.Up) @@ -55,7 +55,7 @@ namespace SourceGit.Views { if (DataContext is ViewModels.CompareCommandPalette vm) { - vm.Launch(); + this.ShowWindow(vm.Launch()); e.Handled = true; } } diff --git a/src/Views/Conflict.axaml.cs b/src/Views/Conflict.axaml.cs index cdd616f9..0de67ccc 100644 --- a/src/Views/Conflict.axaml.cs +++ b/src/Views/Conflict.axaml.cs @@ -40,7 +40,10 @@ namespace SourceGit.Views private async void OnMerge(object _, RoutedEventArgs e) { if (DataContext is ViewModels.Conflict vm) - await vm.MergeAsync(); + { + var request = vm.CreateOpenMergeEditorRequest(); + await this.ShowDialogAsync(request); + } e.Handled = true; } diff --git a/src/Views/ControlExtensions.cs b/src/Views/ControlExtensions.cs index 5f043d8a..33c20b4f 100644 --- a/src/Views/ControlExtensions.cs +++ b/src/Views/ControlExtensions.cs @@ -1,5 +1,7 @@ using System; using System.Threading.Tasks; + +using Avalonia; using Avalonia.Controls; using Avalonia.Controls.Shapes; using Avalonia.Media; @@ -44,5 +46,63 @@ namespace SourceGit.Views return null; } + + public static void ShowWindow(this Control control, object data) + { + if (data == null) + return; + + if (data is not ChromelessWindow window) + { + window = CreateFromViewModels(data) as ChromelessWindow; + if (window == null) + return; + + window.DataContext = data; + } + + do + { + var owner = TopLevel.GetTopLevel(control) as Window; + if (owner != null) + { + // Get the screen where current window locates. + var screen = owner.Screens.ScreenFromWindow(owner) ?? owner.Screens.Primary; + if (screen == null) + break; + + // Calculate the startup position (Center Screen Mode) of target window + var rect = new PixelRect(PixelSize.FromSize(window.ClientSize, owner.DesktopScaling)); + var centeredRect = screen.WorkingArea.CenterRect(rect); + if (owner.Screens.ScreenFromPoint(centeredRect.Position) == null) + break; + + // Use the startup position + window.WindowStartupLocation = WindowStartupLocation.Manual; + window.Position = centeredRect.Position; + } + } while (false); + + window.Show(); + } + + public static Task ShowDialogAsync(this Control control, object data) + { + var owner = TopLevel.GetTopLevel(control) as Window; + if (owner == null) + return null; + + if (data is ChromelessWindow window) + return window.ShowDialog(owner); + + window = CreateFromViewModels(data) as ChromelessWindow; + if (window != null) + { + window.DataContext = data; + return window.ShowDialog(owner); + } + + return null; + } } } diff --git a/src/Views/DiffView.axaml.cs b/src/Views/DiffView.axaml.cs index 43e6e8b4..504df14d 100644 --- a/src/Views/DiffView.axaml.cs +++ b/src/Views/DiffView.axaml.cs @@ -48,7 +48,7 @@ namespace SourceGit.Views if (sender is Button { DataContext: Models.SubmoduleDiff diff } && diff.CanOpenDetails) { var vm = new ViewModels.SubmoduleRevisionCompare(diff); - App.ShowWindow(vm); + this.ShowWindow(vm); } } } diff --git a/src/Views/FileHistoryCommandPalette.axaml.cs b/src/Views/FileHistoryCommandPalette.axaml.cs index 2361e971..4038c44b 100644 --- a/src/Views/FileHistoryCommandPalette.axaml.cs +++ b/src/Views/FileHistoryCommandPalette.axaml.cs @@ -19,7 +19,7 @@ namespace SourceGit.Views if (e.Key == Key.Enter) { - vm.Launch(); + this.ShowWindow(vm.Launch()); e.Handled = true; } else if (e.Key == Key.Up) @@ -55,7 +55,7 @@ namespace SourceGit.Views { if (DataContext is ViewModels.FileHistoryCommandPalette vm) { - vm.Launch(); + this.ShowWindow(vm.Launch()); e.Handled = true; } } diff --git a/src/Views/Histories.axaml.cs b/src/Views/Histories.axaml.cs index 39312ec6..f042a78b 100644 --- a/src/Views/Histories.axaml.cs +++ b/src/Views/Histories.axaml.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Text; +using System.Threading.Tasks; using Avalonia; using Avalonia.Collections; @@ -832,7 +833,7 @@ namespace SourceGit.Views manually.Icon = this.CreateMenuIcon("Icons.InteractiveRebase"); manually.Click += async (_, e) => { - await App.ShowDialog(new ViewModels.InteractiveRebase(repo, commit)); + await this.ShowDialogAsync(new ViewModels.InteractiveRebase(repo, commit)); e.Handled = true; }; @@ -841,7 +842,7 @@ namespace SourceGit.Views reword.Icon = this.CreateMenuIcon("Icons.Rename"); reword.Click += async (_, e) => { - await vm.InteractiveRebaseAsync(commit, Models.InteractiveRebaseAction.Reword); + await InteractiveRebaseWithPrefillActionAsync(repo, commit, Models.InteractiveRebaseAction.Reword); e.Handled = true; }; @@ -850,7 +851,7 @@ namespace SourceGit.Views edit.Icon = this.CreateMenuIcon("Icons.Edit"); edit.Click += async (_, e) => { - await vm.InteractiveRebaseAsync(commit, Models.InteractiveRebaseAction.Edit); + await InteractiveRebaseWithPrefillActionAsync(repo, commit, Models.InteractiveRebaseAction.Edit); e.Handled = true; }; @@ -859,7 +860,7 @@ namespace SourceGit.Views squash.Icon = this.CreateMenuIcon("Icons.SquashIntoParent"); squash.Click += async (_, e) => { - await vm.InteractiveRebaseAsync(commit, Models.InteractiveRebaseAction.Squash); + await InteractiveRebaseWithPrefillActionAsync(repo, commit, Models.InteractiveRebaseAction.Squash); e.Handled = true; }; @@ -868,7 +869,7 @@ namespace SourceGit.Views fixup.Icon = this.CreateMenuIcon("Icons.Fix"); fixup.Click += async (_, e) => { - await vm.InteractiveRebaseAsync(commit, Models.InteractiveRebaseAction.Fixup); + await InteractiveRebaseWithPrefillActionAsync(repo, commit, Models.InteractiveRebaseAction.Fixup); e.Handled = true; }; @@ -877,7 +878,7 @@ namespace SourceGit.Views drop.Icon = this.CreateMenuIcon("Icons.Clear"); drop.Click += async (_, e) => { - await vm.InteractiveRebaseAsync(commit, Models.InteractiveRebaseAction.Drop); + await InteractiveRebaseWithPrefillActionAsync(repo, commit, Models.InteractiveRebaseAction.Drop); e.Handled = true; }; @@ -902,7 +903,7 @@ namespace SourceGit.Views interactiveRebase.Icon = this.CreateMenuIcon("Icons.InteractiveRebase"); interactiveRebase.Click += async (_, e) => { - await App.ShowDialog(new ViewModels.InteractiveRebase(repo, commit)); + await this.ShowDialogAsync(new ViewModels.InteractiveRebase(repo, commit)); e.Handled = true; }; @@ -1417,6 +1418,22 @@ namespace SourceGit.Views menu.Items.Add(submenu); } + private async Task InteractiveRebaseWithPrefillActionAsync(ViewModels.Repository repo, Models.Commit target, Models.InteractiveRebaseAction action) + { + var prefill = new ViewModels.InteractiveRebasePrefill(target.SHA, action); + var start = action switch + { + Models.InteractiveRebaseAction.Squash or Models.InteractiveRebaseAction.Fixup => $"{target.SHA}~~", + _ => $"{target.SHA}~", + }; + + var on = await new Commands.QuerySingleCommit(repo.FullPath, start).GetResultAsync(); + if (on == null) + repo.SendNotification($"Commit '{start}' is not a valid revision for `git rebase -i`!", true); + else + await this.ShowDialogAsync(new ViewModels.InteractiveRebase(repo, on, prefill)); + } + private double _lastGraphStartY = 0; private double _lastGraphClipWidth = 0; private double _lastGraphRowHeight = 0; diff --git a/src/Views/Launcher.axaml.cs b/src/Views/Launcher.axaml.cs index 0c8b32c5..20d5617a 100644 --- a/src/Views/Launcher.axaml.cs +++ b/src/Views/Launcher.axaml.cs @@ -172,14 +172,14 @@ namespace SourceGit.Views { if (e is { KeyModifiers: KeyModifiers.Control, Key: Key.OemComma }) { - await App.ShowDialog(new Preferences()); + await this.ShowDialogAsync(new Preferences()); e.Handled = true; return; } if (e is { KeyModifiers: KeyModifiers.None, Key: Key.F1 }) { - await App.ShowDialog(new Hotkeys()); + await this.ShowDialogAsync(new Hotkeys()); e.Handled = true; return; } @@ -404,7 +404,7 @@ namespace SourceGit.Views configure.Header = App.Text("Workspace.Configure"); configure.Click += async (_, ev) => { - await App.ShowDialog(new ViewModels.ConfigureWorkspace()); + await this.ShowDialogAsync(new ViewModels.ConfigureWorkspace()); ev.Handled = true; }; menu.Items.Add(configure); diff --git a/src/Views/Preferences.axaml.cs b/src/Views/Preferences.axaml.cs index 3c9c2e3f..6ab603f0 100644 --- a/src/Views/Preferences.axaml.cs +++ b/src/Views/Preferences.axaml.cs @@ -386,7 +386,7 @@ namespace SourceGit.Views if (sender is CheckBox box) { ViewModels.Preferences.Instance.UseSystemWindowFrame = box.IsChecked == true; - await App.ShowDialog(new ConfirmRestart()); + await this.ShowDialogAsync(new ConfirmRestart()); } e.Handled = true; @@ -486,12 +486,7 @@ namespace SourceGit.Views if (sender is not Button { DataContext: Models.CustomAction act }) return; - var dialog = new ConfigureCustomActionControls() - { - DataContext = new ViewModels.ConfigureCustomActionControls(act.Controls) - }; - - await dialog.ShowDialog(this); + await this.ShowDialogAsync(new ViewModels.ConfigureCustomActionControls(act.Controls)); e.Handled = true; } diff --git a/src/Views/RepositoryConfigure.axaml.cs b/src/Views/RepositoryConfigure.axaml.cs index 2c5622df..bc445d2a 100644 --- a/src/Views/RepositoryConfigure.axaml.cs +++ b/src/Views/RepositoryConfigure.axaml.cs @@ -56,12 +56,7 @@ namespace SourceGit.Views if (sender is not Button { DataContext: Models.CustomAction act }) return; - var dialog = new ConfigureCustomActionControls() - { - DataContext = new ViewModels.ConfigureCustomActionControls(act.Controls) - }; - - await dialog.ShowDialog(this); + await this.ShowDialogAsync(new ViewModels.ConfigureCustomActionControls(act.Controls)); e.Handled = true; } diff --git a/src/Views/RepositoryToolbar.axaml.cs b/src/Views/RepositoryToolbar.axaml.cs index e224b1ba..72bb0462 100644 --- a/src/Views/RepositoryToolbar.axaml.cs +++ b/src/Views/RepositoryToolbar.axaml.cs @@ -138,7 +138,7 @@ namespace SourceGit.Views { if (DataContext is ViewModels.Repository repo) { - await App.ShowDialog(new ViewModels.Statistics(repo.FullPath)); + await this.ShowDialogAsync(new ViewModels.Statistics(repo.FullPath)); e.Handled = true; } } @@ -147,7 +147,7 @@ namespace SourceGit.Views { if (DataContext is ViewModels.Repository repo) { - await App.ShowDialog(new ViewModels.RepositoryConfigure(repo)); + await this.ShowDialogAsync(new ViewModels.RepositoryConfigure(repo)); e.Handled = true; } } @@ -387,7 +387,7 @@ namespace SourceGit.Views { locks.Click += async (_, e) => { - await App.ShowDialog(new ViewModels.LFSLocks(repo, repo.Remotes[0].Name)); + await this.ShowDialogAsync(new ViewModels.LFSLocks(repo, repo.Remotes[0].Name)); e.Handled = true; }; } @@ -400,7 +400,7 @@ namespace SourceGit.Views lockRemote.Header = remoteName; lockRemote.Click += async (_, e) => { - await App.ShowDialog(new ViewModels.LFSLocks(repo, remoteName)); + await this.ShowDialogAsync(new ViewModels.LFSLocks(repo, remoteName)); e.Handled = true; }; locks.Items.Add(lockRemote); @@ -494,7 +494,7 @@ namespace SourceGit.Views { if (DataContext is ViewModels.Repository repo) { - await App.ShowDialog(new ViewModels.ViewLogs(repo)); + await this.ShowDialogAsync(new ViewModels.ViewLogs(repo)); e.Handled = true; } } diff --git a/src/Views/RevisionFileTreeView.axaml.cs b/src/Views/RevisionFileTreeView.axaml.cs index 4ac1901c..e9a0e22f 100644 --- a/src/Views/RevisionFileTreeView.axaml.cs +++ b/src/Views/RevisionFileTreeView.axaml.cs @@ -472,7 +472,7 @@ namespace SourceGit.Views history.Icon = this.CreateMenuIcon("Icons.Histories"); history.Click += (_, ev) => { - App.ShowWindow(new ViewModels.DirHistories(repo, path, commit.SHA)); + this.ShowWindow(new ViewModels.DirHistories(repo, path, commit.SHA)); ev.Handled = true; }; @@ -601,7 +601,7 @@ namespace SourceGit.Views history.Icon = this.CreateMenuIcon("Icons.Histories"); history.Click += (_, ev) => { - App.ShowWindow(new ViewModels.FileHistories(repo.FullPath, file.Path, commit.SHA)); + this.ShowWindow(new ViewModels.FileHistories(repo.FullPath, file.Path, commit.SHA)); ev.Handled = true; }; @@ -611,7 +611,7 @@ namespace SourceGit.Views blame.IsEnabled = file.Type == Models.ObjectType.Blob; blame.Click += (_, ev) => { - App.ShowWindow(new ViewModels.Blame(repo.FullPath, file.Path, commit)); + this.ShowWindow(new ViewModels.Blame(repo.FullPath, file.Path, commit)); ev.Handled = true; }; diff --git a/src/Views/SubmodulesView.axaml.cs b/src/Views/SubmodulesView.axaml.cs index e45be0ca..c8205049 100644 --- a/src/Views/SubmodulesView.axaml.cs +++ b/src/Views/SubmodulesView.axaml.cs @@ -257,7 +257,7 @@ namespace SourceGit.Views histories.Icon = this.CreateMenuIcon("Icons.Histories"); histories.Click += (_, ev) => { - App.ShowWindow(new ViewModels.FileHistories(repo.FullPath, submodule.Path)); + this.ShowWindow(new ViewModels.FileHistories(repo.FullPath, submodule.Path)); ev.Handled = true; }; diff --git a/src/Views/TagsView.axaml.cs b/src/Views/TagsView.axaml.cs index 0e66a5cc..3878fcab 100644 --- a/src/Views/TagsView.axaml.cs +++ b/src/Views/TagsView.axaml.cs @@ -271,7 +271,7 @@ namespace SourceGit.Views compareWithHead.Icon = this.CreateMenuIcon("Icons.Compare"); compareWithHead.Click += (_, _) => { - App.ShowWindow(new ViewModels.Compare(repo, tag, repo.CurrentBranch)); + this.ShowWindow(new ViewModels.Compare(repo, tag, repo.CurrentBranch)); }; var compareWith = new MenuItem(); @@ -387,7 +387,7 @@ namespace SourceGit.Views if (based.CreatorDate > to.CreatorDate) (based, to) = (to, based); - App.ShowWindow(new ViewModels.Compare(repo, based, to)); + this.ShowWindow(new ViewModels.Compare(repo, based, to)); ev.Handled = true; }; menu.Items.Add(compare); diff --git a/src/Views/WorkingCopy.axaml.cs b/src/Views/WorkingCopy.axaml.cs index ea7a215d..9a5969de 100644 --- a/src/Views/WorkingCopy.axaml.cs +++ b/src/Views/WorkingCopy.axaml.cs @@ -34,7 +34,7 @@ namespace SourceGit.Views { var repoView = this.FindAncestorOfType(); if (repoView is { DataContext: ViewModels.Repository repo }) - await App.ShowDialog(new ViewModels.AssumeUnchangedManager(repo)); + await this.ShowDialogAsync(new ViewModels.AssumeUnchangedManager(repo)); e.Handled = true; } @@ -360,7 +360,7 @@ namespace SourceGit.Views mergeBuiltin.Click += async (_, e) => { var head = await new Commands.QuerySingleCommit(repo.FullPath, "HEAD").GetResultAsync(); - await App.ShowDialog(new ViewModels.MergeConflictEditor(repo, head, change.Path)); + await this.ShowDialogAsync(new ViewModels.MergeConflictEditor(repo, head, change.Path)); e.Handled = true; }; @@ -648,7 +648,7 @@ namespace SourceGit.Views history.Icon = this.CreateMenuIcon("Icons.Histories"); history.Click += (_, e) => { - App.ShowWindow(new ViewModels.DirHistories(repo, selectedSingleFolder)); + this.ShowWindow(new ViewModels.DirHistories(repo, selectedSingleFolder)); e.Handled = true; }; @@ -662,7 +662,7 @@ namespace SourceGit.Views history.Icon = this.CreateMenuIcon("Icons.Histories"); history.Click += (_, e) => { - App.ShowWindow(new ViewModels.FileHistories(repo.FullPath, change.Path)); + this.ShowWindow(new ViewModels.FileHistories(repo.FullPath, change.Path)); e.Handled = true; }; @@ -672,7 +672,7 @@ namespace SourceGit.Views blame.Click += async (_, ev) => { var commit = await new Commands.QuerySingleCommit(repo.FullPath, "HEAD").GetResultAsync(); - App.ShowWindow(new ViewModels.Blame(repo.FullPath, change.Path, commit)); + this.ShowWindow(new ViewModels.Blame(repo.FullPath, change.Path, commit)); ev.Handled = true; }; @@ -872,7 +872,7 @@ namespace SourceGit.Views history.Icon = this.CreateMenuIcon("Icons.Histories"); history.Click += (_, e) => { - App.ShowWindow(new ViewModels.DirHistories(repo, selectedSingleFolder)); + this.ShowWindow(new ViewModels.DirHistories(repo, selectedSingleFolder)); e.Handled = true; }; @@ -1119,7 +1119,7 @@ namespace SourceGit.Views history.Icon = this.CreateMenuIcon("Icons.Histories"); history.Click += (_, e) => { - App.ShowWindow(new ViewModels.DirHistories(repo, selectedSingleFolder)); + this.ShowWindow(new ViewModels.DirHistories(repo, selectedSingleFolder)); e.Handled = true; }; @@ -1133,7 +1133,7 @@ namespace SourceGit.Views history.Icon = this.CreateMenuIcon("Icons.Histories"); history.Click += (_, e) => { - App.ShowWindow(new ViewModels.FileHistories(repo.FullPath, change.Path)); + this.ShowWindow(new ViewModels.FileHistories(repo.FullPath, change.Path)); e.Handled = true; }; @@ -1143,7 +1143,7 @@ namespace SourceGit.Views blame.Click += async (_, e) => { var commit = await new Commands.QuerySingleCommit(repo.FullPath, "HEAD").GetResultAsync(); - App.ShowWindow(new ViewModels.Blame(repo.FullPath, change.Path, commit)); + this.ShowWindow(new ViewModels.Blame(repo.FullPath, change.Path, commit)); e.Handled = true; }; @@ -1263,7 +1263,7 @@ namespace SourceGit.Views history.Icon = this.CreateMenuIcon("Icons.Histories"); history.Click += (_, e) => { - App.ShowWindow(new ViewModels.DirHistories(repo, selectedSingleFolder)); + this.ShowWindow(new ViewModels.DirHistories(repo, selectedSingleFolder)); e.Handled = true; }; From 07b9c7a1c244e7738cc39cc6f2bd9eb29b08ec91 Mon Sep 17 00:00:00 2001 From: leo Date: Wed, 15 Apr 2026 17:47:34 +0800 Subject: [PATCH 23/45] feature: show uncommitted changes count for submodule (#2264) Signed-off-by: leo --- src/Models/RevisionFile.cs | 1 + src/Resources/Locales/en_US.axaml | 1 + src/Resources/Locales/zh_CN.axaml | 1 + src/Resources/Locales/zh_TW.axaml | 1 + src/ViewModels/DiffContext.cs | 13 ++++++++++++- src/Views/DiffView.axaml | 10 +++++++++- 6 files changed, 25 insertions(+), 2 deletions(-) diff --git a/src/Models/RevisionFile.cs b/src/Models/RevisionFile.cs index 29a23efa..9a9fe7d2 100644 --- a/src/Models/RevisionFile.cs +++ b/src/Models/RevisionFile.cs @@ -39,5 +39,6 @@ namespace SourceGit.Models { public Commit Commit { get; set; } = null; public CommitFullMessage FullMessage { get; set; } = null; + public int UncommittedChanges { get; set; } = 0; } } diff --git a/src/Resources/Locales/en_US.axaml b/src/Resources/Locales/en_US.axaml index aa1c8197..e9569bcd 100644 --- a/src/Resources/Locales/en_US.axaml +++ b/src/Resources/Locales/en_US.axaml @@ -372,6 +372,7 @@ SUBMODULE DELETED NEW + + {0} Uncommitted Changes Swap Syntax Highlighting Line Word Wrap diff --git a/src/Resources/Locales/zh_CN.axaml b/src/Resources/Locales/zh_CN.axaml index 3df2b8f2..299df234 100644 --- a/src/Resources/Locales/zh_CN.axaml +++ b/src/Resources/Locales/zh_CN.axaml @@ -376,6 +376,7 @@ 子模块 删除 新增 + + {0} 项未提交变更 交换比对双方 语法高亮 自动换行 diff --git a/src/Resources/Locales/zh_TW.axaml b/src/Resources/Locales/zh_TW.axaml index e1a79d49..98433ce9 100644 --- a/src/Resources/Locales/zh_TW.axaml +++ b/src/Resources/Locales/zh_TW.axaml @@ -376,6 +376,7 @@ 子模組 已刪除 新增 + + {0} 項未提交變更 交換比對雙方 語法上色 自動換行 diff --git a/src/ViewModels/DiffContext.cs b/src/ViewModels/DiffContext.cs index 16639427..25155771 100644 --- a/src/ViewModels/DiffContext.cs +++ b/src/ViewModels/DiffContext.cs @@ -291,6 +291,16 @@ namespace SourceGit.ViewModels private async Task QuerySubmoduleRevisionAsync(string repo, string sha) { + if (!File.Exists(Path.Combine(repo, ".git"))) + return new Models.RevisionSubmodule() { Commit = new Models.Commit() { SHA = sha } }; + + var uncommittedChangesCount = 0; + if (sha.EndsWith("-dirty", StringComparison.Ordinal)) + { + sha = sha.Substring(0, sha.Length - 6); + uncommittedChangesCount = await new Commands.CountLocalChanges(repo, true).GetResultAsync().ConfigureAwait(false); + } + var commit = await new Commands.QuerySingleCommit(repo, sha).GetResultAsync().ConfigureAwait(false); if (commit == null) return new Models.RevisionSubmodule() { Commit = new Models.Commit() { SHA = sha } }; @@ -299,7 +309,8 @@ namespace SourceGit.ViewModels return new Models.RevisionSubmodule() { Commit = commit, - FullMessage = new Models.CommitFullMessage { Message = body } + FullMessage = new Models.CommitFullMessage { Message = body }, + UncommittedChanges = uncommittedChangesCount }; } diff --git a/src/Views/DiffView.axaml b/src/Views/DiffView.axaml index fdac0fb8..b8c7b8f3 100644 --- a/src/Views/DiffView.axaml +++ b/src/Views/DiffView.axaml @@ -321,7 +321,15 @@ - + + + + From 27f5f5e27d49fa401df55462b1fe74bba263cbda Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 15 Apr 2026 09:47:56 +0000 Subject: [PATCH 24/45] doc: Update translation status and sort locale files --- TRANSLATION.md | 33 ++++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/TRANSLATION.md b/TRANSLATION.md index 75d83075..537e1919 100644 --- a/TRANSLATION.md +++ b/TRANSLATION.md @@ -6,7 +6,7 @@ This document shows the translation status of each locale file in the repository ### ![en_US](https://img.shields.io/badge/en__US-%E2%88%9A-brightgreen) -### ![de__DE](https://img.shields.io/badge/de__DE-96.64%25-yellow) +### ![de__DE](https://img.shields.io/badge/de__DE-96.54%25-yellow)
Missing keys in de_DE.axaml @@ -24,6 +24,7 @@ This document shows the translation status of each locale file in the repository - Text.CommandPalette.RevisionFiles - Text.CommitMessageTextBox.Column - Text.ConfirmEmptyCommit.StageSelectedThenCommit +- Text.Diff.Submodule.UncommittedChanges - Text.Discard.IncludeModified - Text.GotoRevisionSelector - Text.Hotkeys.Global.OpenLocalRepository @@ -47,13 +48,14 @@ This document shows the translation status of each locale file in the repository
-### ![es__ES](https://img.shields.io/badge/es__ES-99.08%25-yellow) +### ![es__ES](https://img.shields.io/badge/es__ES-98.98%25-yellow)
Missing keys in es_ES.axaml - Text.Clone.Bookmark - Text.Clone.Group +- Text.Diff.Submodule.UncommittedChanges - Text.Hotkeys.Global.OpenLocalRepository - Text.OpenLocalRepository - Text.OpenLocalRepository.Bookmark @@ -64,7 +66,7 @@ This document shows the translation status of each locale file in the repository
-### ![fr__FR](https://img.shields.io/badge/fr__FR-90.53%25-yellow) +### ![fr__FR](https://img.shields.io/badge/fr__FR-90.44%25-yellow)
Missing keys in fr_FR.axaml @@ -95,6 +97,7 @@ This document shows the translation status of each locale file in the repository - Text.Compare.WithHead - Text.Configure.Git.AskBeforeAutoUpdatingSubmodules - Text.ConfirmEmptyCommit.StageSelectedThenCommit +- Text.Diff.Submodule.UncommittedChanges - Text.Discard.IncludeModified - Text.EditBranchDescription - Text.EditBranchDescription.Target @@ -165,7 +168,7 @@ This document shows the translation status of each locale file in the repository
-### ![id__ID](https://img.shields.io/badge/id__ID-88.39%25-yellow) +### ![id__ID](https://img.shields.io/badge/id__ID-88.30%25-yellow)
Missing keys in id_ID.axaml @@ -205,6 +208,7 @@ This document shows the translation status of each locale file in the repository - Text.ConfigureCustomActionControls.StringValue.Tip - Text.ConfirmEmptyCommit.StageSelectedThenCommit - Text.DealWithLocalChanges.DoNothing +- Text.Diff.Submodule.UncommittedChanges - Text.Discard.IncludeModified - Text.DropHead - Text.DropHead.Commit @@ -287,7 +291,7 @@ This document shows the translation status of each locale file in the repository
-### ![it__IT](https://img.shields.io/badge/it__IT-96.03%25-yellow) +### ![it__IT](https://img.shields.io/badge/it__IT-95.93%25-yellow)
Missing keys in it_IT.axaml @@ -306,6 +310,7 @@ This document shows the translation status of each locale file in the repository - Text.CommandPalette.RevisionFiles - Text.CommitMessageTextBox.Column - Text.ConfirmEmptyCommit.StageSelectedThenCommit +- Text.Diff.Submodule.UncommittedChanges - Text.Discard.IncludeModified - Text.GotoRevisionSelector - Text.Histories.Header.DateTime @@ -334,7 +339,7 @@ This document shows the translation status of each locale file in the repository
-### ![ja__JP](https://img.shields.io/badge/ja__JP-96.95%25-yellow) +### ![ja__JP](https://img.shields.io/badge/ja__JP-96.85%25-yellow)
Missing keys in ja_JP.axaml @@ -352,6 +357,7 @@ This document shows the translation status of each locale file in the repository - Text.CommandPalette.RevisionFiles - Text.ConfirmEmptyCommit.StageSelectedThenCommit - Text.DealWithLocalChanges.DoNothing +- Text.Diff.Submodule.UncommittedChanges - Text.Discard.IncludeModified - Text.Hotkeys.Global.OpenLocalRepository - Text.Hotkeys.Repo.CreateBranch @@ -372,7 +378,7 @@ This document shows the translation status of each locale file in the repository
-### ![ko__KR](https://img.shields.io/badge/ko__KR-88.70%25-yellow) +### ![ko__KR](https://img.shields.io/badge/ko__KR-88.61%25-yellow)
Missing keys in ko_KR.axaml @@ -410,6 +416,7 @@ This document shows the translation status of each locale file in the repository - Text.ConfigureCustomActionControls.StringValue.Tip - Text.ConfirmEmptyCommit.StageSelectedThenCommit - Text.DealWithLocalChanges.DoNothing +- Text.Diff.Submodule.UncommittedChanges - Text.Discard.IncludeModified - Text.EditBranchDescription - Text.EditBranchDescription.Target @@ -491,7 +498,7 @@ This document shows the translation status of each locale file in the repository
-### ![pt__BR](https://img.shields.io/badge/pt__BR-67.11%25-red) +### ![pt__BR](https://img.shields.io/badge/pt__BR-67.04%25-red)
Missing keys in pt_BR.axaml @@ -609,6 +616,7 @@ This document shows the translation status of each locale file in the repository - Text.Diff.New - Text.Diff.Old - Text.Diff.Submodule.Deleted +- Text.Diff.Submodule.UncommittedChanges - Text.DirHistories - Text.DirtyState.HasLocalChanges - Text.DirtyState.HasPendingPullOrPush @@ -822,13 +830,14 @@ This document shows the translation status of each locale file in the repository
-### ![ru__RU](https://img.shields.io/badge/ru__RU-99.08%25-yellow) +### ![ru__RU](https://img.shields.io/badge/ru__RU-98.98%25-yellow)
Missing keys in ru_RU.axaml - Text.Clone.Bookmark - Text.Clone.Group +- Text.Diff.Submodule.UncommittedChanges - Text.Hotkeys.Global.OpenLocalRepository - Text.OpenLocalRepository - Text.OpenLocalRepository.Bookmark @@ -839,7 +848,7 @@ This document shows the translation status of each locale file in the repository
-### ![ta__IN](https://img.shields.io/badge/ta__IN-69.14%25-red) +### ![ta__IN](https://img.shields.io/badge/ta__IN-69.07%25-red)
Missing keys in ta_IN.axaml @@ -972,6 +981,7 @@ This document shows the translation status of each locale file in the repository - Text.Diff.New - Text.Diff.Old - Text.Diff.Submodule.Deleted +- Text.Diff.Submodule.UncommittedChanges - Text.DirHistories - Text.DirtyState.HasLocalChanges - Text.DirtyState.HasPendingPullOrPush @@ -1150,7 +1160,7 @@ This document shows the translation status of each locale file in the repository
-### ![uk__UA](https://img.shields.io/badge/uk__UA-69.96%25-red) +### ![uk__UA](https://img.shields.io/badge/uk__UA-69.89%25-red)
Missing keys in uk_UA.axaml @@ -1279,6 +1289,7 @@ This document shows the translation status of each locale file in the repository - Text.Diff.New - Text.Diff.Old - Text.Diff.Submodule.Deleted +- Text.Diff.Submodule.UncommittedChanges - Text.DirHistories - Text.DirtyState.HasLocalChanges - Text.DirtyState.HasPendingPullOrPush From 460cc689958ecdd1ad32ba8730a3777e52da392e Mon Sep 17 00:00:00 2001 From: leo Date: Wed, 15 Apr 2026 19:10:03 +0800 Subject: [PATCH 25/45] enhance: do not show `Git LFS` submenu for submodules (#2264) Signed-off-by: leo --- src/Views/WorkingCopy.axaml.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Views/WorkingCopy.axaml.cs b/src/Views/WorkingCopy.axaml.cs index 9a5969de..10a0d263 100644 --- a/src/Views/WorkingCopy.axaml.cs +++ b/src/Views/WorkingCopy.axaml.cs @@ -541,7 +541,7 @@ namespace SourceGit.Views hasExtra = true; } - if (repo.IsLFSEnabled()) + if (File.Exists(path) && repo.IsLFSEnabled()) { var lfs = new MenuItem(); lfs.Header = App.Text("GitLFS"); @@ -1038,7 +1038,7 @@ namespace SourceGit.Views menu.Items.Add(patch); menu.Items.Add(new MenuItem() { Header = "-" }); - if (repo.IsLFSEnabled()) + if (File.Exists(path) && repo.IsLFSEnabled()) { var lfs = new MenuItem(); lfs.Header = App.Text("GitLFS"); From 6abdf32cf4b476116c80a35f1c58b77f19f3ab9e Mon Sep 17 00:00:00 2001 From: leo Date: Wed, 15 Apr 2026 19:23:14 +0800 Subject: [PATCH 26/45] fix: remove `--push` option because it is not valid parameter for `git-flow-next` (#2269) Signed-off-by: leo --- src/Commands/GitFlow.cs | 4 +--- src/Resources/Locales/en_US.axaml | 1 - src/ViewModels/GitFlowFinish.cs | 8 +------- src/Views/GitFlowFinish.axaml | 7 +------ 4 files changed, 3 insertions(+), 17 deletions(-) diff --git a/src/Commands/GitFlow.cs b/src/Commands/GitFlow.cs index c2052f0e..8e29e6b5 100644 --- a/src/Commands/GitFlow.cs +++ b/src/Commands/GitFlow.cs @@ -49,7 +49,7 @@ namespace SourceGit.Commands return await start.Use(log).ExecAsync().ConfigureAwait(false); } - public static async Task FinishAsync(string repo, Models.GitFlowBranchType type, string name, bool squash, bool push, bool keepBranch, Models.ICommandLog log) + public static async Task FinishAsync(string repo, Models.GitFlowBranchType type, string name, bool squash, bool keepBranch, Models.ICommandLog log) { var builder = new StringBuilder(); builder.Append("flow "); @@ -73,8 +73,6 @@ namespace SourceGit.Commands builder.Append(" finish "); if (squash) builder.Append("--squash "); - if (push) - builder.Append("--push "); if (keepBranch) builder.Append("-k "); builder.Append(name); diff --git a/src/Resources/Locales/en_US.axaml b/src/Resources/Locales/en_US.axaml index e9569bcd..07e2fcec 100644 --- a/src/Resources/Locales/en_US.axaml +++ b/src/Resources/Locales/en_US.axaml @@ -436,7 +436,6 @@ FLOW - Finish Hotfix FLOW - Finish Release Target: - Push to remote(s) after performing finish Squash during merge Hotfix: Hotfix Prefix: diff --git a/src/ViewModels/GitFlowFinish.cs b/src/ViewModels/GitFlowFinish.cs index 7ed48cbf..42cf0544 100644 --- a/src/ViewModels/GitFlowFinish.cs +++ b/src/ViewModels/GitFlowFinish.cs @@ -21,12 +21,6 @@ namespace SourceGit.ViewModels set; } = false; - public bool AutoPush - { - get; - set; - } = false; - public bool KeepBranch { get; @@ -50,7 +44,7 @@ namespace SourceGit.ViewModels var prefix = _repo.GitFlow.GetPrefix(Type); var name = Branch.Name.StartsWith(prefix) ? Branch.Name.Substring(prefix.Length) : Branch.Name; - var succ = await Commands.GitFlow.FinishAsync(_repo.FullPath, Type, name, Squash, AutoPush, KeepBranch, log); + var succ = await Commands.GitFlow.FinishAsync(_repo.FullPath, Type, name, Squash, KeepBranch, log); log.Complete(); return succ; diff --git a/src/Views/GitFlowFinish.axaml b/src/Views/GitFlowFinish.axaml index 66097c0f..d0d8d212 100644 --- a/src/Views/GitFlowFinish.axaml +++ b/src/Views/GitFlowFinish.axaml @@ -27,7 +27,7 @@ IsVisible="{Binding Type, Converter={x:Static ObjectConverters.Equal}, ConverterParameter={x:Static m:GitFlowBranchType.Hotfix}}"/> - + - - From f5d5f63bddbc978338286df71849ad195cd37333 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 15 Apr 2026 11:23:35 +0000 Subject: [PATCH 27/45] doc: Update translation status and sort locale files --- TRANSLATION.md | 17 +++++++---------- src/Resources/Locales/de_DE.axaml | 1 - src/Resources/Locales/es_ES.axaml | 1 - src/Resources/Locales/fr_FR.axaml | 1 - src/Resources/Locales/id_ID.axaml | 1 - src/Resources/Locales/it_IT.axaml | 1 - src/Resources/Locales/ja_JP.axaml | 1 - src/Resources/Locales/ko_KR.axaml | 1 - src/Resources/Locales/ru_RU.axaml | 1 - src/Resources/Locales/zh_CN.axaml | 1 - src/Resources/Locales/zh_TW.axaml | 1 - 11 files changed, 7 insertions(+), 20 deletions(-) diff --git a/TRANSLATION.md b/TRANSLATION.md index 537e1919..e93c0afe 100644 --- a/TRANSLATION.md +++ b/TRANSLATION.md @@ -66,7 +66,7 @@ This document shows the translation status of each locale file in the repository
-### ![fr__FR](https://img.shields.io/badge/fr__FR-90.44%25-yellow) +### ![fr__FR](https://img.shields.io/badge/fr__FR-90.43%25-yellow)
Missing keys in fr_FR.axaml @@ -168,7 +168,7 @@ This document shows the translation status of each locale file in the repository
-### ![id__ID](https://img.shields.io/badge/id__ID-88.30%25-yellow) +### ![id__ID](https://img.shields.io/badge/id__ID-88.29%25-yellow)
Missing keys in id_ID.axaml @@ -339,7 +339,7 @@ This document shows the translation status of each locale file in the repository
-### ![ja__JP](https://img.shields.io/badge/ja__JP-96.85%25-yellow) +### ![ja__JP](https://img.shields.io/badge/ja__JP-96.84%25-yellow)
Missing keys in ja_JP.axaml @@ -378,7 +378,7 @@ This document shows the translation status of each locale file in the repository
-### ![ko__KR](https://img.shields.io/badge/ko__KR-88.61%25-yellow) +### ![ko__KR](https://img.shields.io/badge/ko__KR-88.59%25-yellow)
Missing keys in ko_KR.axaml @@ -498,7 +498,7 @@ This document shows the translation status of each locale file in the repository
-### ![pt__BR](https://img.shields.io/badge/pt__BR-67.04%25-red) +### ![pt__BR](https://img.shields.io/badge/pt__BR-67.11%25-red)
Missing keys in pt_BR.axaml @@ -633,7 +633,6 @@ This document shows the translation status of each locale file in the repository - Text.Fetch.Force - Text.FileCM.CustomAction - Text.FileCM.ResolveUsing -- Text.GitFlow.FinishWithPush - Text.GitFlow.FinishWithSquash - Text.GitLFS.Locks.UnlockAllMyLocks - Text.GitLFS.Locks.UnlockAllMyLocks.Confirm @@ -848,7 +847,7 @@ This document shows the translation status of each locale file in the repository
-### ![ta__IN](https://img.shields.io/badge/ta__IN-69.07%25-red) +### ![ta__IN](https://img.shields.io/badge/ta__IN-69.14%25-red)
Missing keys in ta_IN.axaml @@ -996,7 +995,6 @@ This document shows the translation status of each locale file in the repository - Text.ExecuteCustomAction.Target - Text.ExecuteCustomAction.Repository - Text.FileCM.CustomAction -- Text.GitFlow.FinishWithPush - Text.GitFlow.FinishWithSquash - Text.GitLFS.Locks.UnlockAllMyLocks - Text.GitLFS.Locks.UnlockAllMyLocks.Confirm @@ -1160,7 +1158,7 @@ This document shows the translation status of each locale file in the repository
-### ![uk__UA](https://img.shields.io/badge/uk__UA-69.89%25-red) +### ![uk__UA](https://img.shields.io/badge/uk__UA-69.96%25-red)
Missing keys in uk_UA.axaml @@ -1304,7 +1302,6 @@ This document shows the translation status of each locale file in the repository - Text.ExecuteCustomAction.Target - Text.ExecuteCustomAction.Repository - Text.FileCM.CustomAction -- Text.GitFlow.FinishWithPush - Text.GitFlow.FinishWithSquash - Text.GitLFS.Locks.UnlockAllMyLocks - Text.GitLFS.Locks.UnlockAllMyLocks.Confirm diff --git a/src/Resources/Locales/de_DE.axaml b/src/Resources/Locales/de_DE.axaml index 7db1f934..d4ecd50b 100644 --- a/src/Resources/Locales/de_DE.axaml +++ b/src/Resources/Locales/de_DE.axaml @@ -425,7 +425,6 @@ $1, $2, … Werte der Eingabe-Steuerelemente FLOW – Hotfix fertigstellen FLOW – Release fertigstellen Ziel: - Push zu Remote(s) nach Abschluss Squash beim Merge Hotfix: Hotfix-Präfix: diff --git a/src/Resources/Locales/es_ES.axaml b/src/Resources/Locales/es_ES.axaml index be11ae58..428f6628 100644 --- a/src/Resources/Locales/es_ES.axaml +++ b/src/Resources/Locales/es_ES.axaml @@ -437,7 +437,6 @@ FLOW - Finalizar Hotfix FLOW - Finalizar Release Destino: - Push al/los remoto(s) después de Finalizar Squash durante el merge Hotfix: Prefijo de Hotfix: diff --git a/src/Resources/Locales/fr_FR.axaml b/src/Resources/Locales/fr_FR.axaml index 7a62a8ac..f2d00296 100644 --- a/src/Resources/Locales/fr_FR.axaml +++ b/src/Resources/Locales/fr_FR.axaml @@ -409,7 +409,6 @@ FLOW - Terminer Hotfix FLOW - Terminer Release Cible: - Pousser vers le(s) dépôt(s) distant(s) après avoir terminé Squash lors de la fusion Hotfix: Hotfix Prefix: diff --git a/src/Resources/Locales/id_ID.axaml b/src/Resources/Locales/id_ID.axaml index f9e1b6b8..621abaa9 100644 --- a/src/Resources/Locales/id_ID.axaml +++ b/src/Resources/Locales/id_ID.axaml @@ -390,7 +390,6 @@ FLOW - Selesaikan Hotfix FLOW - Selesaikan Release Target: - Push ke remote setelah selesai Squash saat merge Hotfix: Prefix Hotfix: diff --git a/src/Resources/Locales/it_IT.axaml b/src/Resources/Locales/it_IT.axaml index df7ab6f1..629f6da4 100644 --- a/src/Resources/Locales/it_IT.axaml +++ b/src/Resources/Locales/it_IT.axaml @@ -424,7 +424,6 @@ ${pure_files:N} Come ${files:N}, ma senza cartelle FLOW - Completa Hotfix FLOW - Completa Rilascio Target: - Invia al remote dopo aver finito Esegui squash durante il merge Hotfix: Prefisso Hotfix: diff --git a/src/Resources/Locales/ja_JP.axaml b/src/Resources/Locales/ja_JP.axaml index 5d4969ab..26c1701b 100644 --- a/src/Resources/Locales/ja_JP.axaml +++ b/src/Resources/Locales/ja_JP.axaml @@ -425,7 +425,6 @@ フロー - 緊急のバグ修正を完了 フロー - リリース作業を完了 対象: - コミットの完了後、リモートにプッシュ スカッシュしてマージ 緊急のバグ修正: 緊急のバグ修正用のプレフィックス: diff --git a/src/Resources/Locales/ko_KR.axaml b/src/Resources/Locales/ko_KR.axaml index 7a278e27..fa71db59 100644 --- a/src/Resources/Locales/ko_KR.axaml +++ b/src/Resources/Locales/ko_KR.axaml @@ -392,7 +392,6 @@ FLOW - Hotfix 완료 FLOW - Release 완료 대상: - 완료 후 원격(들)에 푸시 병합 시 스쿼시 핫픽스: Hotfix 접두사: diff --git a/src/Resources/Locales/ru_RU.axaml b/src/Resources/Locales/ru_RU.axaml index 5b59a22f..9084e4cd 100644 --- a/src/Resources/Locales/ru_RU.axaml +++ b/src/Resources/Locales/ru_RU.axaml @@ -437,7 +437,6 @@ ПРОЦЕСС - Закончить исправление ПРОЦЕСС - Завершить выпуск Цель: - Выложить на удалённый(ые) после завершения Втиснуть при слиянии Исправление: Префикс исправлений: diff --git a/src/Resources/Locales/zh_CN.axaml b/src/Resources/Locales/zh_CN.axaml index 299df234..2f977a51 100644 --- a/src/Resources/Locales/zh_CN.axaml +++ b/src/Resources/Locales/zh_CN.axaml @@ -440,7 +440,6 @@ 结束修复分支 结束版本分支 目标分支 : - 完成后自动推送 压缩变更为单一提交后合并分支 修复分支 : 修复分支名前缀 : diff --git a/src/Resources/Locales/zh_TW.axaml b/src/Resources/Locales/zh_TW.axaml index 98433ce9..bbd0d0eb 100644 --- a/src/Resources/Locales/zh_TW.axaml +++ b/src/Resources/Locales/zh_TW.axaml @@ -440,7 +440,6 @@ 完成修復分支 完成發行分支 目標分支: - 完成後自動推送 壓縮為單一提交後合併 修復分支: 修復分支前置詞: From 538001a72fb26773f95175b5c6cfb1724b33019e Mon Sep 17 00:00:00 2001 From: leo Date: Thu, 16 Apr 2026 10:51:20 +0800 Subject: [PATCH 28/45] fix: enable `Set as tracking branch` should be visible when push to a new branch (#2273) Signed-off-by: leo --- src/ViewModels/Push.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ViewModels/Push.cs b/src/ViewModels/Push.cs index ad794fbd..2da3895b 100644 --- a/src/ViewModels/Push.cs +++ b/src/ViewModels/Push.cs @@ -58,7 +58,7 @@ namespace SourceGit.ViewModels set { if (SetProperty(ref _selectedRemoteBranch, value, true)) - IsSetTrackOptionVisible = value != null && _selectedLocalBranch.Upstream != value.FullName; + IsSetTrackOptionVisible = value != null && (value.Head == null || _selectedLocalBranch.Upstream != value.FullName); } } From 8bdba69cb4876c2a8413b0a1e2ac991f3fc6332a Mon Sep 17 00:00:00 2001 From: leo Date: Thu, 16 Apr 2026 11:47:16 +0800 Subject: [PATCH 29/45] refactor: rewrite the way to quit app Signed-off-by: leo --- src/App.axaml.cs | 15 ++++++--------- src/ViewModels/Launcher.cs | 2 +- src/Views/Launcher.axaml.cs | 13 ++++++++++--- 3 files changed, 17 insertions(+), 13 deletions(-) diff --git a/src/App.axaml.cs b/src/App.axaml.cs index 8d6bafb7..234b7d97 100644 --- a/src/App.axaml.cs +++ b/src/App.axaml.cs @@ -230,15 +230,9 @@ namespace SourceGit public static void Quit(int exitCode) { if (Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) - { - desktop.ShutdownMode = ShutdownMode.OnExplicitShutdown; - desktop.MainWindow?.Close(); desktop.Shutdown(exitCode); - } else - { Environment.Exit(exitCode); - } } #endregion @@ -478,7 +472,12 @@ namespace SourceGit _launcher = new ViewModels.Launcher(startupRepo); desktop.MainWindow = new Views.Launcher() { DataContext = _launcher }; - desktop.ShutdownMode = ShutdownMode.OnMainWindowClose; + desktop.ShutdownMode = ShutdownMode.OnExplicitShutdown; + desktop.Exit += (_, _) => + { + _ipcChannel?.Dispose(); + _ipcChannel = null; + }; _ipcChannel.MessageReceived += repo => { @@ -490,8 +489,6 @@ namespace SourceGit }); }; - desktop.Exit += (_, _) => _ipcChannel.Dispose(); - #if !DISABLE_UPDATE_DETECTION if (pref.ShouldCheck4UpdateOnStartup()) Check4Update(); diff --git a/src/ViewModels/Launcher.cs b/src/ViewModels/Launcher.cs index 4287cb9b..5beb3f13 100644 --- a/src/ViewModels/Launcher.cs +++ b/src/ViewModels/Launcher.cs @@ -117,7 +117,7 @@ namespace SourceGit.ViewModels return false; } - public void Quit() + public void CloseAll() { _ignoreIndexChange = true; diff --git a/src/Views/Launcher.axaml.cs b/src/Views/Launcher.axaml.cs index 20d5617a..e19b9ec2 100644 --- a/src/Views/Launcher.axaml.cs +++ b/src/Views/Launcher.axaml.cs @@ -338,10 +338,17 @@ namespace SourceGit.Views base.OnClosing(e); if (!Design.IsDesignMode && DataContext is ViewModels.Launcher launcher) - { + launcher.CloseAll(); + } + + protected override void OnClosed(EventArgs e) + { + base.OnClosed(e); + + if (!Design.IsDesignMode) ViewModels.Preferences.Instance.Save(); - launcher.Quit(); - } + + App.Quit(0); } private void OnPositionChanged(object sender, PixelPointEventArgs e) From 5a35c41517193d7c139570f54a917c3ff0df6bbc Mon Sep 17 00:00:00 2001 From: leo Date: Thu, 16 Apr 2026 12:23:15 +0800 Subject: [PATCH 30/45] fix: directly patch the final file when trying to stage/unstage/discard selected hunk with renamed/copied file (#2272) Signed-off-by: leo --- src/Models/DiffResult.cs | 28 ++++++++++++---------------- src/Views/TextDiffView.axaml.cs | 18 +++++++++--------- 2 files changed, 21 insertions(+), 25 deletions(-) diff --git a/src/Models/DiffResult.cs b/src/Models/DiffResult.cs index c85090d9..27a8ff09 100644 --- a/src/Models/DiffResult.cs +++ b/src/Models/DiffResult.cs @@ -99,19 +99,19 @@ namespace SourceGit.Models return rs; } - public void GenerateNewPatchFromSelection(Change change, string fileBlobGuid, TextDiffSelection selection, bool revert, string output) + public void GenerateNewPatchFromSelection(string file, string fileBlobGuid, TextDiffSelection selection, bool revert, string output) { var isTracked = !string.IsNullOrEmpty(fileBlobGuid); var fileGuid = isTracked ? fileBlobGuid : "00000000"; using var writer = new StreamWriter(output); writer.NewLine = "\n"; - writer.WriteLine($"diff --git a/{change.Path} b/{change.Path}"); + writer.WriteLine($"diff --git a/{file} b/{file}"); if (!revert && !isTracked) writer.WriteLine("new file mode 100644"); writer.WriteLine($"index 00000000...{fileGuid}"); - writer.WriteLine($"--- {(revert || isTracked ? $"a/{change.Path}" : "/dev/null")}"); - writer.WriteLine($"+++ b/{change.Path}"); + writer.WriteLine($"--- {(revert || isTracked ? $"a/{file}" : "/dev/null")}"); + writer.WriteLine($"+++ b/{file}"); var additions = selection.EndLine - selection.StartLine; if (selection.StartLine != 1) @@ -148,16 +148,14 @@ namespace SourceGit.Models writer.Flush(); } - public void GeneratePatchFromSelection(Change change, string fileTreeGuid, TextDiffSelection selection, bool revert, string output) + public void GeneratePatchFromSelection(string file, string fileTreeGuid, TextDiffSelection selection, bool revert, string output) { - var orgFile = !string.IsNullOrEmpty(change.OriginalPath) ? change.OriginalPath : change.Path; - using var writer = new StreamWriter(output); writer.NewLine = "\n"; - writer.WriteLine($"diff --git a/{change.Path} b/{change.Path}"); + writer.WriteLine($"diff --git a/{file} b/{file}"); writer.WriteLine($"index 00000000...{fileTreeGuid} 100644"); - writer.WriteLine($"--- a/{orgFile}"); - writer.WriteLine($"+++ b/{change.Path}"); + writer.WriteLine($"--- a/{file}"); + writer.WriteLine($"+++ b/{file}"); // If last line of selection is a change. Find one more line. TextDiffLine tail = null; @@ -263,16 +261,14 @@ namespace SourceGit.Models writer.Flush(); } - public void GeneratePatchFromSelectionSingleSide(Change change, string fileTreeGuid, TextDiffSelection selection, bool revert, bool isOldSide, string output) + public void GeneratePatchFromSelectionSingleSide(string file, string fileTreeGuid, TextDiffSelection selection, bool revert, bool isOldSide, string output) { - var orgFile = !string.IsNullOrEmpty(change.OriginalPath) ? change.OriginalPath : change.Path; - using var writer = new StreamWriter(output); writer.NewLine = "\n"; - writer.WriteLine($"diff --git a/{change.Path} b/{change.Path}"); + writer.WriteLine($"diff --git a/{file} b/{file}"); writer.WriteLine($"index 00000000...{fileTreeGuid} 100644"); - writer.WriteLine($"--- a/{orgFile}"); - writer.WriteLine($"+++ b/{change.Path}"); + writer.WriteLine($"--- a/{file}"); + writer.WriteLine($"+++ b/{file}"); // If last line of selection is a change. Find one more line. TextDiffLine tail = null; diff --git a/src/Views/TextDiffView.axaml.cs b/src/Views/TextDiffView.axaml.cs index b7be2ef3..d3b602c3 100644 --- a/src/Views/TextDiffView.axaml.cs +++ b/src/Views/TextDiffView.axaml.cs @@ -1495,17 +1495,17 @@ namespace SourceGit.Views var tmpFile = Path.GetTempFileName(); if (change.WorkTree == Models.ChangeState.Untracked) { - diff.GenerateNewPatchFromSelection(change, null, selection, false, tmpFile); + diff.GenerateNewPatchFromSelection(change.Path, null, selection, false, tmpFile); } else if (chunk.Combined) { var treeGuid = await new Commands.QueryStagedFileBlobGuid(repo.FullPath, change.Path).GetResultAsync(); - diff.GeneratePatchFromSelection(change, treeGuid, selection, false, tmpFile); + diff.GeneratePatchFromSelection(change.Path, treeGuid, selection, false, tmpFile); } else { var treeGuid = await new Commands.QueryStagedFileBlobGuid(repo.FullPath, change.Path).GetResultAsync(); - diff.GeneratePatchFromSelectionSingleSide(change, treeGuid, selection, false, chunk.IsOldSide, tmpFile); + diff.GeneratePatchFromSelectionSingleSide(change.Path, treeGuid, selection, false, chunk.IsOldSide, tmpFile); } await new Commands.Apply(repo.FullPath, tmpFile, true, "nowarn", "--cache --index").ExecAsync(); @@ -1533,11 +1533,11 @@ namespace SourceGit.Views var treeGuid = await new Commands.QueryStagedFileBlobGuid(repo.FullPath, change.Path).GetResultAsync(); var tmpFile = Path.GetTempFileName(); if (change.Index == Models.ChangeState.Added) - diff.GenerateNewPatchFromSelection(change, treeGuid, selection, true, tmpFile); + diff.GenerateNewPatchFromSelection(change.Path, treeGuid, selection, true, tmpFile); else if (chunk.Combined) - diff.GeneratePatchFromSelection(change, treeGuid, selection, true, tmpFile); + diff.GeneratePatchFromSelection(change.Path, treeGuid, selection, true, tmpFile); else - diff.GeneratePatchFromSelectionSingleSide(change, treeGuid, selection, true, chunk.IsOldSide, tmpFile); + diff.GeneratePatchFromSelectionSingleSide(change.Path, treeGuid, selection, true, chunk.IsOldSide, tmpFile); await new Commands.Apply(repo.FullPath, tmpFile, true, "nowarn", "--cache --index --reverse").ExecAsync(); File.Delete(tmpFile); @@ -1564,17 +1564,17 @@ namespace SourceGit.Views var tmpFile = Path.GetTempFileName(); if (change.WorkTree == Models.ChangeState.Untracked) { - diff.GenerateNewPatchFromSelection(change, null, selection, true, tmpFile); + diff.GenerateNewPatchFromSelection(change.Path, null, selection, true, tmpFile); } else if (chunk.Combined) { var treeGuid = await new Commands.QueryStagedFileBlobGuid(repo.FullPath, change.Path).GetResultAsync(); - diff.GeneratePatchFromSelection(change, treeGuid, selection, true, tmpFile); + diff.GeneratePatchFromSelection(change.Path, treeGuid, selection, true, tmpFile); } else { var treeGuid = await new Commands.QueryStagedFileBlobGuid(repo.FullPath, change.Path).GetResultAsync(); - diff.GeneratePatchFromSelectionSingleSide(change, treeGuid, selection, true, chunk.IsOldSide, tmpFile); + diff.GeneratePatchFromSelectionSingleSide(change.Path, treeGuid, selection, true, chunk.IsOldSide, tmpFile); } await new Commands.Apply(repo.FullPath, tmpFile, true, "nowarn", "--reverse").ExecAsync(); From 3c747494a8bd06a31f4cec801e15e3426a31062e Mon Sep 17 00:00:00 2001 From: leo Date: Thu, 16 Apr 2026 14:18:13 +0800 Subject: [PATCH 31/45] fix: app will crash when it is quiting from Dock (#2271) Signed-off-by: leo --- src/App.axaml.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/App.axaml.cs b/src/App.axaml.cs index 234b7d97..31e31912 100644 --- a/src/App.axaml.cs +++ b/src/App.axaml.cs @@ -473,6 +473,17 @@ namespace SourceGit _launcher = new ViewModels.Launcher(startupRepo); desktop.MainWindow = new Views.Launcher() { DataContext = _launcher }; desktop.ShutdownMode = ShutdownMode.OnExplicitShutdown; + + // Fix macOS crash when quiting from Dock + if (OperatingSystem.IsMacOS()) + { + desktop.ShutdownRequested += (_, e) => + { + e.Cancel = true; + Dispatcher.UIThread.Post(() => Quit(0)); + }; + } + desktop.Exit += (_, _) => { _ipcChannel?.Dispose(); From 5114c43dcd17795b326528a06439a99210d258ad Mon Sep 17 00:00:00 2001 From: leo Date: Thu, 16 Apr 2026 14:37:43 +0800 Subject: [PATCH 32/45] code_style: move `App.FixFontFamilyNames` to `StringExtensions.FormatFontNames` Signed-off-by: leo --- src/App.Extensions.cs | 44 ++++++++++++++++++++++++++++++++++++++++ src/App.axaml.cs | 47 ++----------------------------------------- 2 files changed, 46 insertions(+), 45 deletions(-) diff --git a/src/App.Extensions.cs b/src/App.Extensions.cs index 1819d3c8..7c2d0195 100644 --- a/src/App.Extensions.cs +++ b/src/App.Extensions.cs @@ -1,5 +1,8 @@ using System; +using System.Collections.Generic; using System.IO; +using System.Text; +using Avalonia.Media; namespace SourceGit { @@ -14,6 +17,47 @@ namespace SourceGit { return value.Replace("\"", "\\\"", StringComparison.Ordinal); } + + public static string FormatFontNames(string input) + { + if (string.IsNullOrEmpty(input)) + return string.Empty; + + var parts = input.Split(','); + var trimmed = new List(); + + foreach (var part in parts) + { + var t = part.Trim(); + if (string.IsNullOrEmpty(t)) + continue; + + var sb = new StringBuilder(); + var prevChar = '\0'; + + foreach (var c in t) + { + if (c == ' ' && prevChar == ' ') + continue; + sb.Append(c); + prevChar = c; + } + + var name = sb.ToString(); + try + { + var fontFamily = FontFamily.Parse(name); + if (fontFamily.FamilyTypefaces.Count > 0) + trimmed.Add(name); + } + catch + { + // Ignore exceptions. + } + } + + return trimmed.Count > 0 ? string.Join(',', trimmed) : string.Empty; + } } public static class CommandExtensions diff --git a/src/App.axaml.cs b/src/App.axaml.cs index 31e31912..7a230dc1 100644 --- a/src/App.axaml.cs +++ b/src/App.axaml.cs @@ -1,8 +1,6 @@ using System; -using System.Collections.Generic; using System.IO; using System.Net.Http; -using System.Text; using System.Text.Json; using System.Threading.Tasks; @@ -180,8 +178,8 @@ namespace SourceGit app._fontsOverrides = null; } - defaultFont = app.FixFontFamilyName(defaultFont); - monospaceFont = app.FixFontFamilyName(monospaceFont); + defaultFont = StringExtensions.FormatFontNames(defaultFont); + monospaceFont = StringExtensions.FormatFontNames(monospaceFont); var resDic = new ResourceDictionary(); if (!string.IsNullOrEmpty(defaultFont)) @@ -570,47 +568,6 @@ namespace SourceGit } #endregion - private string FixFontFamilyName(string input) - { - if (string.IsNullOrEmpty(input)) - return string.Empty; - - var parts = input.Split(','); - var trimmed = new List(); - - foreach (var part in parts) - { - var t = part.Trim(); - if (string.IsNullOrEmpty(t)) - continue; - - var sb = new StringBuilder(); - var prevChar = '\0'; - - foreach (var c in t) - { - if (c == ' ' && prevChar == ' ') - continue; - sb.Append(c); - prevChar = c; - } - - var name = sb.ToString(); - try - { - var fontFamily = FontFamily.Parse(name); - if (fontFamily.FamilyTypefaces.Count > 0) - trimmed.Add(name); - } - catch - { - // Ignore exceptions. - } - } - - return trimmed.Count > 0 ? string.Join(',', trimmed) : string.Empty; - } - private Models.IpcChannel _ipcChannel = null; private ViewModels.Launcher _launcher = null; private ResourceDictionary _activeLocale = null; From 29cf5fc5386704850de4aa1ec85ea9ecf4976903 Mon Sep 17 00:00:00 2001 From: leo Date: Fri, 17 Apr 2026 12:35:45 +0800 Subject: [PATCH 33/45] feature: add `Hide Others` app menu on macOS and correct the behaviour of `Show All` Signed-off-by: leo --- src/App.Commands.cs | 13 +++++---- src/App.axaml | 3 +- src/Native/Linux.cs | 15 ++++++++++ src/Native/MacOS.cs | 46 +++++++++++++++++++++++++++++++ src/Native/OS.cs | 19 +++++++++++++ src/Native/Windows.cs | 15 ++++++++++ src/Resources/Locales/en_US.axaml | 1 + src/Resources/Locales/zh_CN.axaml | 3 +- src/Resources/Locales/zh_TW.axaml | 3 +- 9 files changed, 110 insertions(+), 8 deletions(-) diff --git a/src/App.Commands.cs b/src/App.Commands.cs index 56c15009..daa930b7 100644 --- a/src/App.Commands.cs +++ b/src/App.Commands.cs @@ -81,14 +81,17 @@ namespace SourceGit public static readonly Command HideAppCommand = new Command(_ => { - if (Current is App app && app.TryGetFeature(typeof(IActivatableLifetime)) is IActivatableLifetime lifetime) - lifetime.TryEnterBackground(); + Native.OS.HideSelf(); }); - public static readonly Command ShowAppCommand = new Command(_ => + public static readonly Command HideOtherApplicationsCommand = new Command(_ => { - if (Current is App app && app.TryGetFeature(typeof(IActivatableLifetime)) is IActivatableLifetime lifetime) - lifetime.TryLeaveBackground(); + Native.OS.HideOtherApplications(); + }); + + public static readonly Command ShowAllApplicationsCommand = new Command(_ => + { + Native.OS.ShowAllApplications(); }); } } diff --git a/src/App.axaml b/src/App.axaml index a7a0c17f..2fa98263 100644 --- a/src/App.axaml +++ b/src/App.axaml @@ -45,7 +45,8 @@ - + + diff --git a/src/Native/Linux.cs b/src/Native/Linux.cs index 881c9bcf..e411cbce 100644 --- a/src/Native/Linux.cs +++ b/src/Native/Linux.cs @@ -33,6 +33,21 @@ namespace SourceGit.Native } } + public void HideSelf() + { + // Do Nothing. Never used. + } + + public void HideOtherApplications() + { + // Do Nothing. Never used. + } + + public void ShowAllApplications() + { + // Do Nothing. Never used. + } + public string GetDataDir() { // AppImage supports portable mode diff --git a/src/Native/MacOS.cs b/src/Native/MacOS.cs index fe0c0475..6431d771 100644 --- a/src/Native/MacOS.cs +++ b/src/Native/MacOS.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; +using System.Runtime.InteropServices; using System.Runtime.Versioning; using Avalonia; @@ -13,6 +14,18 @@ namespace SourceGit.Native [SupportedOSPlatform("macOS")] internal class MacOS : OS.IBackend { + [DllImport("/usr/lib/libobjc.dylib", EntryPoint = "objc_getClass")] + public static extern IntPtr objc_getClass(string name); + + [DllImport("/usr/lib/libobjc.dylib", EntryPoint = "sel_registerName")] + public static extern IntPtr sel_registerName(string name); + + [DllImport("/usr/lib/libobjc.dylib", EntryPoint = "objc_msgSend")] + public static extern IntPtr objc_msgSend(IntPtr receiver, IntPtr selector); + + [DllImport("/usr/lib/libobjc.dylib", EntryPoint = "objc_msgSend")] + public static extern IntPtr objc_msgSendWithArg(IntPtr receiver, IntPtr selector, IntPtr arg); + public void SetupApp(AppBuilder builder) { builder.With(new MacOSPlatformOptions() @@ -44,6 +57,39 @@ namespace SourceGit.Native window.ExtendClientAreaToDecorationsHint = true; } + public void HideSelf() + { + IntPtr nsApplicationClass = objc_getClass("NSApplication"); + IntPtr nsSharedApplicationSelector = sel_registerName("sharedApplication"); + IntPtr nsApp = objc_msgSend(nsApplicationClass, nsSharedApplicationSelector); + IntPtr nsMethodSelector = sel_registerName("hide:"); + IntPtr nsDelegateSelector = sel_registerName("delegate"); + IntPtr nsDelegate = objc_msgSend(nsApp, nsDelegateSelector); + objc_msgSendWithArg(nsApp, nsMethodSelector, nsDelegate); + } + + public void HideOtherApplications() + { + IntPtr nsApplicationClass = objc_getClass("NSApplication"); + IntPtr nsSharedApplicationSelector = sel_registerName("sharedApplication"); + IntPtr nsApp = objc_msgSend(nsApplicationClass, nsSharedApplicationSelector); + IntPtr nsMethodSelector = sel_registerName("hideOtherApplications:"); + IntPtr nsDelegateSelector = sel_registerName("delegate"); + IntPtr nsDelegate = objc_msgSend(nsApp, nsDelegateSelector); + objc_msgSendWithArg(nsApp, nsMethodSelector, nsDelegate); + } + + public void ShowAllApplications() + { + IntPtr nsApplicationClass = objc_getClass("NSApplication"); + IntPtr nsSharedApplicationSelector = sel_registerName("sharedApplication"); + IntPtr nsApp = objc_msgSend(nsApplicationClass, nsSharedApplicationSelector); + IntPtr nsMethodSelector = sel_registerName("unhideAllApplications:"); + IntPtr nsDelegateSelector = sel_registerName("delegate"); + IntPtr nsDelegate = objc_msgSend(nsApp, nsDelegateSelector); + objc_msgSendWithArg(nsApp, nsMethodSelector, nsDelegate); + } + public string GetDataDir() { return Path.Combine( diff --git a/src/Native/OS.cs b/src/Native/OS.cs index 7c12287a..6f899f23 100644 --- a/src/Native/OS.cs +++ b/src/Native/OS.cs @@ -18,6 +18,10 @@ namespace SourceGit.Native void SetupApp(AppBuilder builder); void SetupWindow(Window window); + void HideSelf(); + void HideOtherApplications(); + void ShowAllApplications(); + string GetDataDir(); string FindGitExecutable(); string FindTerminal(Models.ShellOrTerminal shell); @@ -154,6 +158,21 @@ namespace SourceGit.Native _backend.SetupWindow(window); } + public static void HideSelf() + { + _backend.HideSelf(); + } + + public static void HideOtherApplications() + { + _backend.HideOtherApplications(); + } + + public static void ShowAllApplications() + { + _backend.ShowAllApplications(); + } + public static void LogException(Exception ex) { if (ex == null) diff --git a/src/Native/Windows.cs b/src/Native/Windows.cs index e71c0de7..35b8aba6 100644 --- a/src/Native/Windows.cs +++ b/src/Native/Windows.cs @@ -58,6 +58,21 @@ namespace SourceGit.Native window.BorderThickness = new Thickness(1); } + public void HideSelf() + { + // Do Nothing. Never used. + } + + public void HideOtherApplications() + { + // Do Nothing. Never used. + } + + public void ShowAllApplications() + { + // Do Nothing. Never used. + } + public string GetDataDir() { var execFile = Process.GetCurrentProcess().MainModule!.FileName; diff --git a/src/Resources/Locales/en_US.axaml b/src/Resources/Locales/en_US.axaml index 07e2fcec..556f2259 100644 --- a/src/Resources/Locales/en_US.axaml +++ b/src/Resources/Locales/en_US.axaml @@ -23,6 +23,7 @@ Use AI to generate commit message Use Hide SourceGit + Hide Others Show All Patch 3-Way Merge diff --git a/src/Resources/Locales/zh_CN.axaml b/src/Resources/Locales/zh_CN.axaml index 2f977a51..0d874891 100644 --- a/src/Resources/Locales/zh_CN.axaml +++ b/src/Resources/Locales/zh_CN.axaml @@ -27,7 +27,8 @@ 使用AI助手生成提交信息 应用所选 隐藏 SourceGit - 显示所有窗口 + 隐藏其他 + 显示全部 应用补丁(apply) 尝试三路合并 补丁文件 : diff --git a/src/Resources/Locales/zh_TW.axaml b/src/Resources/Locales/zh_TW.axaml index bbd0d0eb..e9db5ca0 100644 --- a/src/Resources/Locales/zh_TW.axaml +++ b/src/Resources/Locales/zh_TW.axaml @@ -27,7 +27,8 @@ 使用 AI 產生提交訊息 套用選取 隱藏 SourceGit - 顯示所有 + 隱藏其他 + 顯示全部 套用修補檔 (apply patch) 嘗試三向合併 修補檔: From 53cd847bb0f05ffa62a0d780e548a43d6d0f3416 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 17 Apr 2026 04:36:10 +0000 Subject: [PATCH 34/45] doc: Update translation status and sort locale files --- TRANSLATION.md | 33 ++++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/TRANSLATION.md b/TRANSLATION.md index e93c0afe..adf3cf06 100644 --- a/TRANSLATION.md +++ b/TRANSLATION.md @@ -6,12 +6,13 @@ This document shows the translation status of each locale file in the repository ### ![en_US](https://img.shields.io/badge/en__US-%E2%88%9A-brightgreen) -### ![de__DE](https://img.shields.io/badge/de__DE-96.54%25-yellow) +### ![de__DE](https://img.shields.io/badge/de__DE-96.44%25-yellow)
Missing keys in de_DE.axaml - Text.AIAssistant.Use +- Text.App.HideOthers - Text.Apply.3Way - Text.CheckoutBranchFromStash - Text.CheckoutBranchFromStash.Branch @@ -48,11 +49,12 @@ This document shows the translation status of each locale file in the repository
-### ![es__ES](https://img.shields.io/badge/es__ES-98.98%25-yellow) +### ![es__ES](https://img.shields.io/badge/es__ES-98.88%25-yellow)
Missing keys in es_ES.axaml +- Text.App.HideOthers - Text.Clone.Bookmark - Text.Clone.Group - Text.Diff.Submodule.UncommittedChanges @@ -66,13 +68,14 @@ This document shows the translation status of each locale file in the repository
-### ![fr__FR](https://img.shields.io/badge/fr__FR-90.43%25-yellow) +### ![fr__FR](https://img.shields.io/badge/fr__FR-90.34%25-yellow)
Missing keys in fr_FR.axaml - Text.About.ReleaseDate - Text.AIAssistant.Use +- Text.App.HideOthers - Text.Apply.3Way - Text.Blame.IgnoreWhitespace - Text.BranchCM.CompareTwo @@ -168,7 +171,7 @@ This document shows the translation status of each locale file in the repository
-### ![id__ID](https://img.shields.io/badge/id__ID-88.29%25-yellow) +### ![id__ID](https://img.shields.io/badge/id__ID-88.20%25-yellow)
Missing keys in id_ID.axaml @@ -176,6 +179,7 @@ This document shows the translation status of each locale file in the repository - Text.About.ReleaseDate - Text.About.ReleaseNotes - Text.AIAssistant.Use +- Text.App.HideOthers - Text.Apply.3Way - Text.Blame.BlameOnPreviousRevision - Text.Blame.IgnoreWhitespace @@ -291,12 +295,13 @@ This document shows the translation status of each locale file in the repository
-### ![it__IT](https://img.shields.io/badge/it__IT-95.93%25-yellow) +### ![it__IT](https://img.shields.io/badge/it__IT-95.83%25-yellow)
Missing keys in it_IT.axaml - Text.AIAssistant.Use +- Text.App.HideOthers - Text.Apply.3Way - Text.ChangeCM.ResetFileTo - Text.CheckoutBranchFromStash @@ -339,12 +344,13 @@ This document shows the translation status of each locale file in the repository
-### ![ja__JP](https://img.shields.io/badge/ja__JP-96.84%25-yellow) +### ![ja__JP](https://img.shields.io/badge/ja__JP-96.74%25-yellow)
Missing keys in ja_JP.axaml - Text.AIAssistant.Use +- Text.App.HideOthers - Text.Apply.3Way - Text.CheckoutBranchFromStash - Text.CheckoutBranchFromStash.Branch @@ -378,13 +384,14 @@ This document shows the translation status of each locale file in the repository
-### ![ko__KR](https://img.shields.io/badge/ko__KR-88.59%25-yellow) +### ![ko__KR](https://img.shields.io/badge/ko__KR-88.50%25-yellow)
Missing keys in ko_KR.axaml - Text.About.ReleaseDate - Text.AIAssistant.Use +- Text.App.HideOthers - Text.Apply.3Way - Text.Blame.BlameOnPreviousRevision - Text.Blame.IgnoreWhitespace @@ -498,12 +505,13 @@ This document shows the translation status of each locale file in the repository
-### ![pt__BR](https://img.shields.io/badge/pt__BR-67.11%25-red) +### ![pt__BR](https://img.shields.io/badge/pt__BR-67.04%25-red)
Missing keys in pt_BR.axaml - Text.AIAssistant.Use +- Text.App.HideOthers - Text.Apply.3Way - Text.Blame.BlameOnPreviousRevision - Text.BranchCM.InteractiveRebase.Manually @@ -829,11 +837,12 @@ This document shows the translation status of each locale file in the repository
-### ![ru__RU](https://img.shields.io/badge/ru__RU-98.98%25-yellow) +### ![ru__RU](https://img.shields.io/badge/ru__RU-98.88%25-yellow)
Missing keys in ru_RU.axaml +- Text.App.HideOthers - Text.Clone.Bookmark - Text.Clone.Group - Text.Diff.Submodule.UncommittedChanges @@ -847,7 +856,7 @@ This document shows the translation status of each locale file in the repository
-### ![ta__IN](https://img.shields.io/badge/ta__IN-69.14%25-red) +### ![ta__IN](https://img.shields.io/badge/ta__IN-69.07%25-red)
Missing keys in ta_IN.axaml @@ -859,6 +868,7 @@ This document shows the translation status of each locale file in the repository - Text.AddToIgnore.Storage - Text.AIAssistant.Use - Text.App.Hide +- Text.App.HideOthers - Text.App.ShowAll - Text.Apply.3Way - Text.Askpass.Passphrase @@ -1158,7 +1168,7 @@ This document shows the translation status of each locale file in the repository
-### ![uk__UA](https://img.shields.io/badge/uk__UA-69.96%25-red) +### ![uk__UA](https://img.shields.io/badge/uk__UA-69.89%25-red)
Missing keys in uk_UA.axaml @@ -1170,6 +1180,7 @@ This document shows the translation status of each locale file in the repository - Text.AddToIgnore.Storage - Text.AIAssistant.Use - Text.App.Hide +- Text.App.HideOthers - Text.App.ShowAll - Text.Apply.3Way - Text.Askpass.Passphrase From dfe362f237da75c8c6c28fab8eb0db30ade50548 Mon Sep 17 00:00:00 2001 From: leo Date: Fri, 17 Apr 2026 16:32:41 +0800 Subject: [PATCH 35/45] feature: add `Local Branch Selector` and `Remote Branch Selector` control type for custom actions (#2274) Signed-off-by: leo --- src/Models/CustomAction.cs | 2 + src/Resources/Locales/en_US.axaml | 1 + src/Resources/Locales/zh_CN.axaml | 1 + src/Resources/Locales/zh_TW.axaml | 1 + src/ViewModels/ExecuteCustomAction.cs | 51 +++++++++++++++---- src/Views/ConfigureCustomActionControls.axaml | 17 ++++++- src/Views/ExecuteCustomAction.axaml | 23 +++++++++ 7 files changed, 85 insertions(+), 11 deletions(-) diff --git a/src/Models/CustomAction.cs b/src/Models/CustomAction.cs index ec500d3c..8c12bc33 100644 --- a/src/Models/CustomAction.cs +++ b/src/Models/CustomAction.cs @@ -19,6 +19,8 @@ namespace SourceGit.Models PathSelector, CheckBox, ComboBox, + LocalBranchSelector, + RemoteBranchSelector, } public record CustomActionTargetFile(string File, Commit Revision); diff --git a/src/Resources/Locales/en_US.axaml b/src/Resources/Locales/en_US.axaml index 556f2259..f5b84018 100644 --- a/src/Resources/Locales/en_US.axaml +++ b/src/Resources/Locales/en_US.axaml @@ -280,6 +280,7 @@ Use '|' as delimiter for options The built-in variables ${REPO}, ${REMOTE}, ${BRANCH}, ${BRANCH_FRIENDLY_NAME}, ${SHA}, ${FILE}, and ${TAG} remain available here Type: + Use Friendly Name: Workspaces Color Name diff --git a/src/Resources/Locales/zh_CN.axaml b/src/Resources/Locales/zh_CN.axaml index 0d874891..c3c40041 100644 --- a/src/Resources/Locales/zh_CN.axaml +++ b/src/Resources/Locales/zh_CN.axaml @@ -284,6 +284,7 @@ 选项之间请使用英文 '|' 作为分隔符 内置变量 ${REPO}, ${REMOTE}, ${BRANCH}, ${BRANCH_FRIENDLY_NAME}, ${SHA}, ${FILE} 与 ${TAG} 在这里仍然可用 类型 : + 输出结果带有远程名: 工作区 颜色 名称 diff --git a/src/Resources/Locales/zh_TW.axaml b/src/Resources/Locales/zh_TW.axaml index e9db5ca0..9c8d33cd 100644 --- a/src/Resources/Locales/zh_TW.axaml +++ b/src/Resources/Locales/zh_TW.axaml @@ -284,6 +284,7 @@ 請使用英文「|」符號分隔選項 內建變數 ${REPO}、${REMOTE}、${BRANCH}、${BRANCH_FRIENDLY_NAME}、${SHA}、${FILE} 及 ${TAG} 在此處仍可使用 類型: + 輸出包含遠端的名稱: 工作區 顏色 名稱 diff --git a/src/ViewModels/ExecuteCustomAction.cs b/src/ViewModels/ExecuteCustomAction.cs index d12971d0..536f7202 100644 --- a/src/ViewModels/ExecuteCustomAction.cs +++ b/src/ViewModels/ExecuteCustomAction.cs @@ -77,12 +77,7 @@ namespace SourceGit.ViewModels public string Label { get; set; } public string Description { get; set; } public List Options { get; set; } = []; - - public string Value - { - get => _value; - set => SetProperty(ref _value, value); - } + public string Value { get; set; } public CustomActionControlComboBox(string label, string description, string options) { @@ -93,13 +88,45 @@ namespace SourceGit.ViewModels if (parts.Length > 0) { Options.AddRange(parts); - _value = parts[0]; + Value = parts[0]; } } - public string GetValue() => _value; + public string GetValue() => Value; + } - private string _value = string.Empty; + public class CustomActionControlBranchSelector : ObservableObject, ICustomActionControlParameter + { + public string Label { get; set; } + public string Description { get; set; } + public List Branches { get; set; } = []; + public Models.Branch SelectedBranch { get; set; } + + public CustomActionControlBranchSelector(string label, string description, Repository repo, bool isLocal, bool useFriendlyName) + { + Label = label; + Description = description; + _useFriendlyName = useFriendlyName; + + foreach (var b in repo.Branches) + { + if (b.IsLocal == isLocal && !b.IsDetachedHead) + Branches.Add(b); + } + + if (Branches.Count > 0) + SelectedBranch = Branches[0]; + } + + public string GetValue() + { + if (SelectedBranch == null) + return string.Empty; + + return _useFriendlyName ? SelectedBranch.FriendlyName : SelectedBranch.Name; + } + + private bool _useFriendlyName = false; } public class ExecuteCustomAction : Popup @@ -171,6 +198,12 @@ namespace SourceGit.ViewModels case Models.CustomActionControlType.ComboBox: ControlParameters.Add(new CustomActionControlComboBox(ctl.Label, ctl.Description, PrepareStringByTarget(ctl.StringValue))); break; + case Models.CustomActionControlType.LocalBranchSelector: + ControlParameters.Add(new CustomActionControlBranchSelector(ctl.Label, ctl.Description, _repo, true, false)); + break; + case Models.CustomActionControlType.RemoteBranchSelector: + ControlParameters.Add(new CustomActionControlBranchSelector(ctl.Label, ctl.Description, _repo, false, ctl.BoolValue)); + break; } } } diff --git a/src/Views/ConfigureCustomActionControls.axaml b/src/Views/ConfigureCustomActionControls.axaml index e24bc010..72368060 100644 --- a/src/Views/ConfigureCustomActionControls.axaml +++ b/src/Views/ConfigureCustomActionControls.axaml @@ -129,9 +129,11 @@ - + + + @@ -160,7 +162,14 @@ + Text="{Binding StringValue, Mode=TwoWay}"> + + + + + + + + + + + + + + + + + + From 7b875d60055383144360bef60a7494a47bca1ed1 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 17 Apr 2026 08:33:06 +0000 Subject: [PATCH 36/45] doc: Update translation status and sort locale files --- TRANSLATION.md | 33 ++++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/TRANSLATION.md b/TRANSLATION.md index adf3cf06..00daf327 100644 --- a/TRANSLATION.md +++ b/TRANSLATION.md @@ -6,7 +6,7 @@ This document shows the translation status of each locale file in the repository ### ![en_US](https://img.shields.io/badge/en__US-%E2%88%9A-brightgreen) -### ![de__DE](https://img.shields.io/badge/de__DE-96.44%25-yellow) +### ![de__DE](https://img.shields.io/badge/de__DE-96.34%25-yellow)
Missing keys in de_DE.axaml @@ -24,6 +24,7 @@ This document shows the translation status of each locale file in the repository - Text.CommandPalette.RepositoryActions - Text.CommandPalette.RevisionFiles - Text.CommitMessageTextBox.Column +- Text.ConfigureCustomActionControls.UseFriendlyName - Text.ConfirmEmptyCommit.StageSelectedThenCommit - Text.Diff.Submodule.UncommittedChanges - Text.Discard.IncludeModified @@ -49,7 +50,7 @@ This document shows the translation status of each locale file in the repository
-### ![es__ES](https://img.shields.io/badge/es__ES-98.88%25-yellow) +### ![es__ES](https://img.shields.io/badge/es__ES-98.78%25-yellow)
Missing keys in es_ES.axaml @@ -57,6 +58,7 @@ This document shows the translation status of each locale file in the repository - Text.App.HideOthers - Text.Clone.Bookmark - Text.Clone.Group +- Text.ConfigureCustomActionControls.UseFriendlyName - Text.Diff.Submodule.UncommittedChanges - Text.Hotkeys.Global.OpenLocalRepository - Text.OpenLocalRepository @@ -68,7 +70,7 @@ This document shows the translation status of each locale file in the repository
-### ![fr__FR](https://img.shields.io/badge/fr__FR-90.34%25-yellow) +### ![fr__FR](https://img.shields.io/badge/fr__FR-90.24%25-yellow)
Missing keys in fr_FR.axaml @@ -99,6 +101,7 @@ This document shows the translation status of each locale file in the repository - Text.CommitMessageTextBox.Placeholder - Text.Compare.WithHead - Text.Configure.Git.AskBeforeAutoUpdatingSubmodules +- Text.ConfigureCustomActionControls.UseFriendlyName - Text.ConfirmEmptyCommit.StageSelectedThenCommit - Text.Diff.Submodule.UncommittedChanges - Text.Discard.IncludeModified @@ -171,7 +174,7 @@ This document shows the translation status of each locale file in the repository
-### ![id__ID](https://img.shields.io/badge/id__ID-88.20%25-yellow) +### ![id__ID](https://img.shields.io/badge/id__ID-88.11%25-yellow)
Missing keys in id_ID.axaml @@ -210,6 +213,7 @@ This document shows the translation status of each locale file in the repository - Text.Configure.Git.AskBeforeAutoUpdatingSubmodules - Text.Configure.Git.ConventionalTypesOverride - Text.ConfigureCustomActionControls.StringValue.Tip +- Text.ConfigureCustomActionControls.UseFriendlyName - Text.ConfirmEmptyCommit.StageSelectedThenCommit - Text.DealWithLocalChanges.DoNothing - Text.Diff.Submodule.UncommittedChanges @@ -295,7 +299,7 @@ This document shows the translation status of each locale file in the repository
-### ![it__IT](https://img.shields.io/badge/it__IT-95.83%25-yellow) +### ![it__IT](https://img.shields.io/badge/it__IT-95.73%25-yellow)
Missing keys in it_IT.axaml @@ -314,6 +318,7 @@ This document shows the translation status of each locale file in the repository - Text.CommandPalette.RepositoryActions - Text.CommandPalette.RevisionFiles - Text.CommitMessageTextBox.Column +- Text.ConfigureCustomActionControls.UseFriendlyName - Text.ConfirmEmptyCommit.StageSelectedThenCommit - Text.Diff.Submodule.UncommittedChanges - Text.Discard.IncludeModified @@ -344,7 +349,7 @@ This document shows the translation status of each locale file in the repository
-### ![ja__JP](https://img.shields.io/badge/ja__JP-96.74%25-yellow) +### ![ja__JP](https://img.shields.io/badge/ja__JP-96.65%25-yellow)
Missing keys in ja_JP.axaml @@ -361,6 +366,7 @@ This document shows the translation status of each locale file in the repository - Text.CommandPalette.BranchesAndTags - Text.CommandPalette.RepositoryActions - Text.CommandPalette.RevisionFiles +- Text.ConfigureCustomActionControls.UseFriendlyName - Text.ConfirmEmptyCommit.StageSelectedThenCommit - Text.DealWithLocalChanges.DoNothing - Text.Diff.Submodule.UncommittedChanges @@ -384,7 +390,7 @@ This document shows the translation status of each locale file in the repository
-### ![ko__KR](https://img.shields.io/badge/ko__KR-88.50%25-yellow) +### ![ko__KR](https://img.shields.io/badge/ko__KR-88.41%25-yellow)
Missing keys in ko_KR.axaml @@ -421,6 +427,7 @@ This document shows the translation status of each locale file in the repository - Text.Configure.Git.AskBeforeAutoUpdatingSubmodules - Text.Configure.Git.ConventionalTypesOverride - Text.ConfigureCustomActionControls.StringValue.Tip +- Text.ConfigureCustomActionControls.UseFriendlyName - Text.ConfirmEmptyCommit.StageSelectedThenCommit - Text.DealWithLocalChanges.DoNothing - Text.Diff.Submodule.UncommittedChanges @@ -505,7 +512,7 @@ This document shows the translation status of each locale file in the repository
-### ![pt__BR](https://img.shields.io/badge/pt__BR-67.04%25-red) +### ![pt__BR](https://img.shields.io/badge/pt__BR-66.97%25-red)
Missing keys in pt_BR.axaml @@ -597,6 +604,7 @@ This document shows the translation status of each locale file in the repository - Text.ConfigureCustomActionControls.Options.Tip - Text.ConfigureCustomActionControls.StringValue.Tip - Text.ConfigureCustomActionControls.Type +- Text.ConfigureCustomActionControls.UseFriendlyName - Text.ConfirmEmptyCommit.Continue - Text.ConfirmEmptyCommit.NoLocalChanges - Text.ConfirmEmptyCommit.StageAllThenCommit @@ -837,7 +845,7 @@ This document shows the translation status of each locale file in the repository
-### ![ru__RU](https://img.shields.io/badge/ru__RU-98.88%25-yellow) +### ![ru__RU](https://img.shields.io/badge/ru__RU-98.78%25-yellow)
Missing keys in ru_RU.axaml @@ -845,6 +853,7 @@ This document shows the translation status of each locale file in the repository - Text.App.HideOthers - Text.Clone.Bookmark - Text.Clone.Group +- Text.ConfigureCustomActionControls.UseFriendlyName - Text.Diff.Submodule.UncommittedChanges - Text.Hotkeys.Global.OpenLocalRepository - Text.OpenLocalRepository @@ -856,7 +865,7 @@ This document shows the translation status of each locale file in the repository
-### ![ta__IN](https://img.shields.io/badge/ta__IN-69.07%25-red) +### ![ta__IN](https://img.shields.io/badge/ta__IN-69.00%25-red)
Missing keys in ta_IN.axaml @@ -968,6 +977,7 @@ This document shows the translation status of each locale file in the repository - Text.ConfigureCustomActionControls.Options.Tip - Text.ConfigureCustomActionControls.StringValue.Tip - Text.ConfigureCustomActionControls.Type +- Text.ConfigureCustomActionControls.UseFriendlyName - Text.ConfirmEmptyCommit.Continue - Text.ConfirmEmptyCommit.NoLocalChanges - Text.ConfirmEmptyCommit.StageAllThenCommit @@ -1168,7 +1178,7 @@ This document shows the translation status of each locale file in the repository
-### ![uk__UA](https://img.shields.io/badge/uk__UA-69.89%25-red) +### ![uk__UA](https://img.shields.io/badge/uk__UA-69.82%25-red)
Missing keys in uk_UA.axaml @@ -1279,6 +1289,7 @@ This document shows the translation status of each locale file in the repository - Text.ConfigureCustomActionControls.Options.Tip - Text.ConfigureCustomActionControls.StringValue.Tip - Text.ConfigureCustomActionControls.Type +- Text.ConfigureCustomActionControls.UseFriendlyName - Text.ConfigureWorkspace.Name - Text.ConfirmEmptyCommit.StageSelectedThenCommit - Text.ConfirmRestart.Title From 8395efdd55c722a9b4d78fc62d0bb848545a52e8 Mon Sep 17 00:00:00 2001 From: leo Date: Fri, 17 Apr 2026 17:23:04 +0800 Subject: [PATCH 37/45] enhance: replace `${BRANCH}` with current branch name if the custom action's scope is `Repository` (#2274) Signed-off-by: leo --- src/ViewModels/ExecuteCustomAction.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ViewModels/ExecuteCustomAction.cs b/src/ViewModels/ExecuteCustomAction.cs index 536f7202..98cf10ef 100644 --- a/src/ViewModels/ExecuteCustomAction.cs +++ b/src/ViewModels/ExecuteCustomAction.cs @@ -219,7 +219,7 @@ namespace SourceGit.ViewModels Models.Tag t => org.Replace("${TAG}", t.Name), Models.Remote r => org.Replace("${REMOTE}", r.Name), Models.CustomActionTargetFile f => org.Replace("${FILE}", f.File).Replace("${SHA}", f.Revision?.SHA ?? string.Empty), - _ => org + _ => org.Replace("${BRANCH}", _repo.CurrentBranch?.Name ?? "HEAD") }; } From 9144daeb6f42345a0313ebdad00fd94b6de861ca Mon Sep 17 00:00:00 2001 From: leo Date: Fri, 17 Apr 2026 17:27:37 +0800 Subject: [PATCH 38/45] code_style: remove unnecessary function call Signed-off-by: leo --- src/ViewModels/ExecuteCustomAction.cs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/ViewModels/ExecuteCustomAction.cs b/src/ViewModels/ExecuteCustomAction.cs index 98cf10ef..0fd0fcfa 100644 --- a/src/ViewModels/ExecuteCustomAction.cs +++ b/src/ViewModels/ExecuteCustomAction.cs @@ -210,7 +210,8 @@ namespace SourceGit.ViewModels private string PrepareStringByTarget(string org) { - org = org.Replace("${REPO}", GetWorkdir()); + var repoPath = OperatingSystem.IsWindows() ? _repo.FullPath.Replace("/", "\\") : _repo.FullPath; + org = org.Replace("${REPO}", repoPath); return Target switch { @@ -223,11 +224,6 @@ namespace SourceGit.ViewModels }; } - private string GetWorkdir() - { - return OperatingSystem.IsWindows() ? _repo.FullPath.Replace("/", "\\") : _repo.FullPath; - } - private void Run(string args) { var start = new ProcessStartInfo(); From fb708065ff496c0d10de28c72df4c12cdea7c979 Mon Sep 17 00:00:00 2001 From: leo Date: Fri, 17 Apr 2026 18:22:16 +0800 Subject: [PATCH 39/45] feature: add `String Formatter` for `TextBox` in custom action (#2274) Signed-off-by: leo --- src/Models/CustomAction.cs | 7 +++++++ src/Resources/Locales/en_US.axaml | 2 ++ src/Resources/Locales/zh_CN.axaml | 2 ++ src/Resources/Locales/zh_TW.axaml | 2 ++ src/ViewModels/ExecuteCustomAction.cs | 13 ++++++++++--- src/Views/ConfigureCustomActionControls.axaml | 16 ++++++++++++++++ 6 files changed, 39 insertions(+), 3 deletions(-) diff --git a/src/Models/CustomAction.cs b/src/Models/CustomAction.cs index 8c12bc33..59652d32 100644 --- a/src/Models/CustomAction.cs +++ b/src/Models/CustomAction.cs @@ -51,6 +51,12 @@ namespace SourceGit.Models set => SetProperty(ref _stringValue, value); } + public string StringFormatter + { + get => _stringFormatter; + set => SetProperty(ref _stringFormatter, value); + } + public bool BoolValue { get => _boolValue; @@ -61,6 +67,7 @@ namespace SourceGit.Models private string _label = string.Empty; private string _description = string.Empty; private string _stringValue = string.Empty; + private string _stringFormatter = string.Empty; private bool _boolValue = false; } diff --git a/src/Resources/Locales/en_US.axaml b/src/Resources/Locales/en_US.axaml index f5b84018..efd003af 100644 --- a/src/Resources/Locales/en_US.axaml +++ b/src/Resources/Locales/en_US.axaml @@ -278,6 +278,8 @@ Label: Options: Use '|' as delimiter for options + String Formatter: + Optional. Used to format output string. Ignored when input is empty. Please use `${VALUE}` to represent the input string. The built-in variables ${REPO}, ${REMOTE}, ${BRANCH}, ${BRANCH_FRIENDLY_NAME}, ${SHA}, ${FILE}, and ${TAG} remain available here Type: Use Friendly Name: diff --git a/src/Resources/Locales/zh_CN.axaml b/src/Resources/Locales/zh_CN.axaml index c3c40041..f5d1a64e 100644 --- a/src/Resources/Locales/zh_CN.axaml +++ b/src/Resources/Locales/zh_CN.axaml @@ -282,6 +282,8 @@ 名称 : 选项列表 : 选项之间请使用英文 '|' 作为分隔符 + 输出内容格式化字串: + 可选。用于格式化输出结果。当用户输入为空时忽略该操作。请使用`${VALUE}`代替用户输入。 内置变量 ${REPO}, ${REMOTE}, ${BRANCH}, ${BRANCH_FRIENDLY_NAME}, ${SHA}, ${FILE} 与 ${TAG} 在这里仍然可用 类型 : 输出结果带有远程名: diff --git a/src/Resources/Locales/zh_TW.axaml b/src/Resources/Locales/zh_TW.axaml index 9c8d33cd..f457cf03 100644 --- a/src/Resources/Locales/zh_TW.axaml +++ b/src/Resources/Locales/zh_TW.axaml @@ -282,6 +282,8 @@ 名稱: 選項列表: 請使用英文「|」符號分隔選項 + 字串格式化器: + 可選。用於格式化輸出字串。當輸入為空時將被忽略。請使用 `${VALUE}` 來表示輸入字串。 內建變數 ${REPO}、${REMOTE}、${BRANCH}、${BRANCH_FRIENDLY_NAME}、${SHA}、${FILE} 及 ${TAG} 在此處仍可使用 類型: 輸出包含遠端的名稱: diff --git a/src/ViewModels/ExecuteCustomAction.cs b/src/ViewModels/ExecuteCustomAction.cs index 0fd0fcfa..376386e5 100644 --- a/src/ViewModels/ExecuteCustomAction.cs +++ b/src/ViewModels/ExecuteCustomAction.cs @@ -18,15 +18,22 @@ namespace SourceGit.ViewModels public string Label { get; set; } public string Placeholder { get; set; } public string Text { get; set; } + public string Formatter { get; set; } - public CustomActionControlTextBox(string label, string placeholder, string defaultValue) + public CustomActionControlTextBox(string label, string placeholder, string defaultValue, string formatter) { Label = label + ":"; Placeholder = placeholder; Text = defaultValue; + Formatter = formatter; } - public string GetValue() => Text; + public string GetValue() + { + if (string.IsNullOrEmpty(Text)) + return string.Empty; + return string.IsNullOrEmpty(Formatter) ? Text : Formatter.Replace("${VALUE}", Text); + } } public class CustomActionControlPathSelector : ObservableObject, ICustomActionControlParameter @@ -187,7 +194,7 @@ namespace SourceGit.ViewModels switch (ctl.Type) { case Models.CustomActionControlType.TextBox: - ControlParameters.Add(new CustomActionControlTextBox(ctl.Label, ctl.Description, PrepareStringByTarget(ctl.StringValue))); + ControlParameters.Add(new CustomActionControlTextBox(ctl.Label, ctl.Description, PrepareStringByTarget(ctl.StringValue), ctl.StringFormatter)); break; case Models.CustomActionControlType.PathSelector: ControlParameters.Add(new CustomActionControlPathSelector(ctl.Label, ctl.Description, ctl.BoolValue, PrepareStringByTarget(ctl.StringValue))); diff --git a/src/Views/ConfigureCustomActionControls.axaml b/src/Views/ConfigureCustomActionControls.axaml index 72368060..520afeff 100644 --- a/src/Views/ConfigureCustomActionControls.axaml +++ b/src/Views/ConfigureCustomActionControls.axaml @@ -196,6 +196,22 @@ + + + + + From 92b010a6e127e4200f886640103288fac0194eb9 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 17 Apr 2026 10:22:34 +0000 Subject: [PATCH 40/45] doc: Update translation status and sort locale files --- TRANSLATION.md | 44 +++++++++++++++++++++++++++++++++----------- 1 file changed, 33 insertions(+), 11 deletions(-) diff --git a/TRANSLATION.md b/TRANSLATION.md index 00daf327..39f095cc 100644 --- a/TRANSLATION.md +++ b/TRANSLATION.md @@ -6,7 +6,7 @@ This document shows the translation status of each locale file in the repository ### ![en_US](https://img.shields.io/badge/en__US-%E2%88%9A-brightgreen) -### ![de__DE](https://img.shields.io/badge/de__DE-96.34%25-yellow) +### ![de__DE](https://img.shields.io/badge/de__DE-96.15%25-yellow)
Missing keys in de_DE.axaml @@ -24,6 +24,8 @@ This document shows the translation status of each locale file in the repository - Text.CommandPalette.RepositoryActions - Text.CommandPalette.RevisionFiles - Text.CommitMessageTextBox.Column +- Text.ConfigureCustomActionControls.StringFormatter +- Text.ConfigureCustomActionControls.StringFormatter.Tip - Text.ConfigureCustomActionControls.UseFriendlyName - Text.ConfirmEmptyCommit.StageSelectedThenCommit - Text.Diff.Submodule.UncommittedChanges @@ -50,7 +52,7 @@ This document shows the translation status of each locale file in the repository
-### ![es__ES](https://img.shields.io/badge/es__ES-98.78%25-yellow) +### ![es__ES](https://img.shields.io/badge/es__ES-98.58%25-yellow)
Missing keys in es_ES.axaml @@ -58,6 +60,8 @@ This document shows the translation status of each locale file in the repository - Text.App.HideOthers - Text.Clone.Bookmark - Text.Clone.Group +- Text.ConfigureCustomActionControls.StringFormatter +- Text.ConfigureCustomActionControls.StringFormatter.Tip - Text.ConfigureCustomActionControls.UseFriendlyName - Text.Diff.Submodule.UncommittedChanges - Text.Hotkeys.Global.OpenLocalRepository @@ -70,7 +74,7 @@ This document shows the translation status of each locale file in the repository
-### ![fr__FR](https://img.shields.io/badge/fr__FR-90.24%25-yellow) +### ![fr__FR](https://img.shields.io/badge/fr__FR-90.06%25-yellow)
Missing keys in fr_FR.axaml @@ -101,6 +105,8 @@ This document shows the translation status of each locale file in the repository - Text.CommitMessageTextBox.Placeholder - Text.Compare.WithHead - Text.Configure.Git.AskBeforeAutoUpdatingSubmodules +- Text.ConfigureCustomActionControls.StringFormatter +- Text.ConfigureCustomActionControls.StringFormatter.Tip - Text.ConfigureCustomActionControls.UseFriendlyName - Text.ConfirmEmptyCommit.StageSelectedThenCommit - Text.Diff.Submodule.UncommittedChanges @@ -174,7 +180,7 @@ This document shows the translation status of each locale file in the repository
-### ![id__ID](https://img.shields.io/badge/id__ID-88.11%25-yellow) +### ![id__ID](https://img.shields.io/badge/id__ID-87.93%25-yellow)
Missing keys in id_ID.axaml @@ -212,6 +218,8 @@ This document shows the translation status of each locale file in the repository - Text.Configure.CommitMessageTemplate.BuiltinVars - Text.Configure.Git.AskBeforeAutoUpdatingSubmodules - Text.Configure.Git.ConventionalTypesOverride +- Text.ConfigureCustomActionControls.StringFormatter +- Text.ConfigureCustomActionControls.StringFormatter.Tip - Text.ConfigureCustomActionControls.StringValue.Tip - Text.ConfigureCustomActionControls.UseFriendlyName - Text.ConfirmEmptyCommit.StageSelectedThenCommit @@ -299,7 +307,7 @@ This document shows the translation status of each locale file in the repository
-### ![it__IT](https://img.shields.io/badge/it__IT-95.73%25-yellow) +### ![it__IT](https://img.shields.io/badge/it__IT-95.54%25-yellow)
Missing keys in it_IT.axaml @@ -318,6 +326,8 @@ This document shows the translation status of each locale file in the repository - Text.CommandPalette.RepositoryActions - Text.CommandPalette.RevisionFiles - Text.CommitMessageTextBox.Column +- Text.ConfigureCustomActionControls.StringFormatter +- Text.ConfigureCustomActionControls.StringFormatter.Tip - Text.ConfigureCustomActionControls.UseFriendlyName - Text.ConfirmEmptyCommit.StageSelectedThenCommit - Text.Diff.Submodule.UncommittedChanges @@ -349,7 +359,7 @@ This document shows the translation status of each locale file in the repository
-### ![ja__JP](https://img.shields.io/badge/ja__JP-96.65%25-yellow) +### ![ja__JP](https://img.shields.io/badge/ja__JP-96.45%25-yellow)
Missing keys in ja_JP.axaml @@ -366,6 +376,8 @@ This document shows the translation status of each locale file in the repository - Text.CommandPalette.BranchesAndTags - Text.CommandPalette.RepositoryActions - Text.CommandPalette.RevisionFiles +- Text.ConfigureCustomActionControls.StringFormatter +- Text.ConfigureCustomActionControls.StringFormatter.Tip - Text.ConfigureCustomActionControls.UseFriendlyName - Text.ConfirmEmptyCommit.StageSelectedThenCommit - Text.DealWithLocalChanges.DoNothing @@ -390,7 +402,7 @@ This document shows the translation status of each locale file in the repository
-### ![ko__KR](https://img.shields.io/badge/ko__KR-88.41%25-yellow) +### ![ko__KR](https://img.shields.io/badge/ko__KR-88.24%25-yellow)
Missing keys in ko_KR.axaml @@ -426,6 +438,8 @@ This document shows the translation status of each locale file in the repository - Text.Compare.WithHead - Text.Configure.Git.AskBeforeAutoUpdatingSubmodules - Text.Configure.Git.ConventionalTypesOverride +- Text.ConfigureCustomActionControls.StringFormatter +- Text.ConfigureCustomActionControls.StringFormatter.Tip - Text.ConfigureCustomActionControls.StringValue.Tip - Text.ConfigureCustomActionControls.UseFriendlyName - Text.ConfirmEmptyCommit.StageSelectedThenCommit @@ -512,7 +526,7 @@ This document shows the translation status of each locale file in the repository
-### ![pt__BR](https://img.shields.io/badge/pt__BR-66.97%25-red) +### ![pt__BR](https://img.shields.io/badge/pt__BR-66.84%25-red)
Missing keys in pt_BR.axaml @@ -602,6 +616,8 @@ This document shows the translation status of each locale file in the repository - Text.ConfigureCustomActionControls.Label - Text.ConfigureCustomActionControls.Options - Text.ConfigureCustomActionControls.Options.Tip +- Text.ConfigureCustomActionControls.StringFormatter +- Text.ConfigureCustomActionControls.StringFormatter.Tip - Text.ConfigureCustomActionControls.StringValue.Tip - Text.ConfigureCustomActionControls.Type - Text.ConfigureCustomActionControls.UseFriendlyName @@ -845,7 +861,7 @@ This document shows the translation status of each locale file in the repository
-### ![ru__RU](https://img.shields.io/badge/ru__RU-98.78%25-yellow) +### ![ru__RU](https://img.shields.io/badge/ru__RU-98.58%25-yellow)
Missing keys in ru_RU.axaml @@ -853,6 +869,8 @@ This document shows the translation status of each locale file in the repository - Text.App.HideOthers - Text.Clone.Bookmark - Text.Clone.Group +- Text.ConfigureCustomActionControls.StringFormatter +- Text.ConfigureCustomActionControls.StringFormatter.Tip - Text.ConfigureCustomActionControls.UseFriendlyName - Text.Diff.Submodule.UncommittedChanges - Text.Hotkeys.Global.OpenLocalRepository @@ -865,7 +883,7 @@ This document shows the translation status of each locale file in the repository
-### ![ta__IN](https://img.shields.io/badge/ta__IN-69.00%25-red) +### ![ta__IN](https://img.shields.io/badge/ta__IN-68.86%25-red)
Missing keys in ta_IN.axaml @@ -975,6 +993,8 @@ This document shows the translation status of each locale file in the repository - Text.ConfigureCustomActionControls.Label - Text.ConfigureCustomActionControls.Options - Text.ConfigureCustomActionControls.Options.Tip +- Text.ConfigureCustomActionControls.StringFormatter +- Text.ConfigureCustomActionControls.StringFormatter.Tip - Text.ConfigureCustomActionControls.StringValue.Tip - Text.ConfigureCustomActionControls.Type - Text.ConfigureCustomActionControls.UseFriendlyName @@ -1178,7 +1198,7 @@ This document shows the translation status of each locale file in the repository
-### ![uk__UA](https://img.shields.io/badge/uk__UA-69.82%25-red) +### ![uk__UA](https://img.shields.io/badge/uk__UA-69.68%25-red)
Missing keys in uk_UA.axaml @@ -1287,6 +1307,8 @@ This document shows the translation status of each locale file in the repository - Text.ConfigureCustomActionControls.Label - Text.ConfigureCustomActionControls.Options - Text.ConfigureCustomActionControls.Options.Tip +- Text.ConfigureCustomActionControls.StringFormatter +- Text.ConfigureCustomActionControls.StringFormatter.Tip - Text.ConfigureCustomActionControls.StringValue.Tip - Text.ConfigureCustomActionControls.Type - Text.ConfigureCustomActionControls.UseFriendlyName From 770a9184ba68cdcb565c4f74495b1345fdcc5d89 Mon Sep 17 00:00:00 2001 From: leo Date: Fri, 17 Apr 2026 18:32:23 +0800 Subject: [PATCH 41/45] code_style: remove unnecessary function call Signed-off-by: leo --- src/ViewModels/ExecuteCustomAction.cs | 54 +++++++++++++-------------- 1 file changed, 25 insertions(+), 29 deletions(-) diff --git a/src/ViewModels/ExecuteCustomAction.cs b/src/ViewModels/ExecuteCustomAction.cs index 376386e5..cf652371 100644 --- a/src/ViewModels/ExecuteCustomAction.cs +++ b/src/ViewModels/ExecuteCustomAction.cs @@ -158,7 +158,31 @@ namespace SourceGit.ViewModels _repo = repo; CustomAction = action; Target = scopeTarget ?? new Models.Null(); - PrepareControlParameters(); + + foreach (var ctl in CustomAction.Controls) + { + switch (ctl.Type) + { + case Models.CustomActionControlType.TextBox: + ControlParameters.Add(new CustomActionControlTextBox(ctl.Label, ctl.Description, PrepareStringByTarget(ctl.StringValue), ctl.StringFormatter)); + break; + case Models.CustomActionControlType.PathSelector: + ControlParameters.Add(new CustomActionControlPathSelector(ctl.Label, ctl.Description, ctl.BoolValue, PrepareStringByTarget(ctl.StringValue))); + break; + case Models.CustomActionControlType.CheckBox: + ControlParameters.Add(new CustomActionControlCheckBox(ctl.Label, ctl.Description, ctl.StringValue, ctl.BoolValue)); + break; + case Models.CustomActionControlType.ComboBox: + ControlParameters.Add(new CustomActionControlComboBox(ctl.Label, ctl.Description, PrepareStringByTarget(ctl.StringValue))); + break; + case Models.CustomActionControlType.LocalBranchSelector: + ControlParameters.Add(new CustomActionControlBranchSelector(ctl.Label, ctl.Description, _repo, true, false)); + break; + case Models.CustomActionControlType.RemoteBranchSelector: + ControlParameters.Add(new CustomActionControlBranchSelector(ctl.Label, ctl.Description, _repo, false, ctl.BoolValue)); + break; + } + } } public override async Task Sure() @@ -187,34 +211,6 @@ namespace SourceGit.ViewModels return true; } - private void PrepareControlParameters() - { - foreach (var ctl in CustomAction.Controls) - { - switch (ctl.Type) - { - case Models.CustomActionControlType.TextBox: - ControlParameters.Add(new CustomActionControlTextBox(ctl.Label, ctl.Description, PrepareStringByTarget(ctl.StringValue), ctl.StringFormatter)); - break; - case Models.CustomActionControlType.PathSelector: - ControlParameters.Add(new CustomActionControlPathSelector(ctl.Label, ctl.Description, ctl.BoolValue, PrepareStringByTarget(ctl.StringValue))); - break; - case Models.CustomActionControlType.CheckBox: - ControlParameters.Add(new CustomActionControlCheckBox(ctl.Label, ctl.Description, ctl.StringValue, ctl.BoolValue)); - break; - case Models.CustomActionControlType.ComboBox: - ControlParameters.Add(new CustomActionControlComboBox(ctl.Label, ctl.Description, PrepareStringByTarget(ctl.StringValue))); - break; - case Models.CustomActionControlType.LocalBranchSelector: - ControlParameters.Add(new CustomActionControlBranchSelector(ctl.Label, ctl.Description, _repo, true, false)); - break; - case Models.CustomActionControlType.RemoteBranchSelector: - ControlParameters.Add(new CustomActionControlBranchSelector(ctl.Label, ctl.Description, _repo, false, ctl.BoolValue)); - break; - } - } - } - private string PrepareStringByTarget(string org) { var repoPath = OperatingSystem.IsWindows() ? _repo.FullPath.Replace("/", "\\") : _repo.FullPath; From 8713e5866de062660d3b38a4fc20b2271d125029 Mon Sep 17 00:00:00 2001 From: leo Date: Fri, 17 Apr 2026 20:02:07 +0800 Subject: [PATCH 42/45] fix: left margins of text editor do not update after `FontSize` changed (#2276) Signed-off-by: leo --- src/Views/MergeConflictEditor.axaml.cs | 5 +-- src/Views/TextDiffView.axaml.cs | 43 ++++++++++++-------------- 2 files changed, 23 insertions(+), 25 deletions(-) diff --git a/src/Views/MergeConflictEditor.axaml.cs b/src/Views/MergeConflictEditor.axaml.cs index 1d2352dd..8c7b2621 100644 --- a/src/Views/MergeConflictEditor.axaml.cs +++ b/src/Views/MergeConflictEditor.axaml.cs @@ -351,8 +351,6 @@ namespace SourceGit.Views Models.TextMateHelper.SetThemeByApp(_textMate); else if (change.Property == SelectedChunkProperty) TextArea.TextView.InvalidateVisual(); - else if (change.Property == MaxLineNumberProperty) - TextArea.LeftMargins[0].InvalidateMeasure(); } private void UpdateContent() @@ -439,6 +437,9 @@ namespace SourceGit.Views return; } + foreach (var margin in TextArea.LeftMargins) + margin.InvalidateMeasure(); + var lines = Lines; var start = int.MaxValue; var count = 0; diff --git a/src/Views/TextDiffView.axaml.cs b/src/Views/TextDiffView.axaml.cs index d3b602c3..94940e74 100644 --- a/src/Views/TextDiffView.axaml.cs +++ b/src/Views/TextDiffView.axaml.cs @@ -98,6 +98,23 @@ namespace SourceGit.Views } } + protected override Size MeasureOverride(Size availableSize) + { + if (DataContext is not ViewModels.TextDiffContext ctx) + return new Size(0, 0); + + var typeface = new Typeface(TextArea.FontFamily); + var test = new FormattedText( + $"{ctx.Data.MaxLineNumber}", + CultureInfo.CurrentCulture, + FlowDirection.LeftToRight, + typeface, + TextArea.FontSize, + Brushes.White); + + return new Size(test.Width, 0); + } + private readonly bool _usePresenter; private readonly bool _isOld; } @@ -578,29 +595,6 @@ namespace SourceGit.Views protected override void OnDataContextChanged(EventArgs e) { base.OnDataContextChanged(e); - - if (DataContext is ViewModels.TextDiffContext ctx) - { - var typeface = new Typeface(FontFamily); - var test = new FormattedText( - $"{ctx.Data.MaxLineNumber}", - CultureInfo.CurrentCulture, - FlowDirection.LeftToRight, - typeface, - FontSize, - Brushes.White); - - var width = test.WidthIncludingTrailingWhitespace; - foreach (var margin in TextArea.LeftMargins) - { - if (margin is LineNumberMargin lineNumberMargin) - margin.Width = width; - } - - var dock = TextArea.FindDescendantOfType(); - dock?.InvalidateArrange(); - } - AutoScrollToFirstChange(); } @@ -721,6 +715,9 @@ namespace SourceGit.Views if (DataContext is not ViewModels.TextDiffContext ctx) return; + foreach (var margin in TextArea.LeftMargins) + margin.InvalidateMeasure(); + if (ctx.IsSideBySide() && !IsOld) return; From 6a118e26dee4f693719ddab07423e540b849eb63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20J=2E=20Mart=C3=ADnez=20M=2E?= <56406225+jjesus-dev@users.noreply.github.com> Date: Sun, 19 Apr 2026 20:23:29 -0600 Subject: [PATCH 43/45] localization: update Spanish translation (#2278) + Add missing strings. --- src/Resources/Locales/es_ES.axaml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/Resources/Locales/es_ES.axaml b/src/Resources/Locales/es_ES.axaml index 428f6628..6bcce512 100644 --- a/src/Resources/Locales/es_ES.axaml +++ b/src/Resources/Locales/es_ES.axaml @@ -27,6 +27,7 @@ Usar OpenAI para generar mensaje de commit Usar Ocultar SourceGit + Ocultar Otros Mostrar Todo Aplicar Parche Merge a 3 vías (3-Way) @@ -134,6 +135,8 @@ Clonar Repositorio Remoto Parámetros Adicionales: Argumentos adicionales para clonar el repositorio. Opcional. + Marcador: + Grupo: Nombre Local: Nombre del repositorio. Opcional. Carpeta Padre: @@ -279,8 +282,11 @@ Etiqueta: Opciones: Usar '|' como delimitador para las opciones + Formateador de Cadenas de texto: + Opcional. Utilizado para formatear la cadena de salida. Ignorado cuando la entrada está vacía. Por favor utiliza `${VALUE}` para representar la cadena de entrada. La variables incorporadas ${REPO}, ${REMOTE}, ${BRANCH}, ${BRANCH_FRIENDLY_NAME}, ${SHA}, ${FILE}, y ${TAG} permanecen disponibles aquí Tipo: + Utilizar Nombre Amigable: Espacios de Trabajo Color Nombre @@ -374,6 +380,7 @@ SUBMÓDULO BORRADO NUEVO + + {0} Cambios Sin confirmar Intercambiar Resaltado de Sintaxis Ajuste de Línea @@ -502,6 +509,7 @@ Ir a la siguiente página Ir a la página anterior Crear nueva página + Abrir repositorio local Abrir diálogo de preferencias Mostrar menú desplegable del espacio de trabajo Cambiar página activa @@ -595,6 +603,10 @@ Abrir Directorio de Datos de la App Abrir Archivo Abrir en Herramienta de Merge + Abrir Repositorio Local + Marcador: + Grupo: + Carpeta: Opcional. Crear Nueva Página Cerrar Pestaña @@ -901,6 +913,8 @@ unmerged Actualizar URL + Cambiar Detalles del Submódulo + ABRIR DETALLES OK ETIQUETADOR HORA From d4be556a196b80c1d9e1e80d9249ac198f28ed28 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 20 Apr 2026 02:23:45 +0000 Subject: [PATCH 44/45] doc: Update translation status and sort locale files --- TRANSLATION.md | 22 +--------------------- 1 file changed, 1 insertion(+), 21 deletions(-) diff --git a/TRANSLATION.md b/TRANSLATION.md index 39f095cc..13ac441a 100644 --- a/TRANSLATION.md +++ b/TRANSLATION.md @@ -52,27 +52,7 @@ This document shows the translation status of each locale file in the repository
-### ![es__ES](https://img.shields.io/badge/es__ES-98.58%25-yellow) - -
-Missing keys in es_ES.axaml - -- Text.App.HideOthers -- Text.Clone.Bookmark -- Text.Clone.Group -- Text.ConfigureCustomActionControls.StringFormatter -- Text.ConfigureCustomActionControls.StringFormatter.Tip -- Text.ConfigureCustomActionControls.UseFriendlyName -- Text.Diff.Submodule.UncommittedChanges -- Text.Hotkeys.Global.OpenLocalRepository -- Text.OpenLocalRepository -- Text.OpenLocalRepository.Bookmark -- Text.OpenLocalRepository.Group -- Text.OpenLocalRepository.Path -- Text.SubmoduleRevisionCompare -- Text.SubmoduleRevisionCompare.OpenDetails - -
+### ![es__ES](https://img.shields.io/badge/es__ES-%E2%88%9A-brightgreen) ### ![fr__FR](https://img.shields.io/badge/fr__FR-90.06%25-yellow) From 83edc515e2d05b7f878bad6733a916be93686bc0 Mon Sep 17 00:00:00 2001 From: leo Date: Mon, 20 Apr 2026 10:25:12 +0800 Subject: [PATCH 45/45] version: Release 2026.09 Signed-off-by: leo --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 1628770a..0180a8fc 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2026.08 \ No newline at end of file +2026.09 \ No newline at end of file