diff --git a/README.md b/README.md index e2cf9da2..1f69cccf 100644 --- a/README.md +++ b/README.md @@ -39,8 +39,8 @@ This software creates a folder `$"{System.Environment.SpecialFolder.ApplicationD | OS | PATH | | --- | --- | | Windows | `C:\Users\USER_NAME\AppData\Roaming\SourceGit` | -| Linux | `/home/USER_NAME/.config/SourceGit` | -| macOS | `/Users/USER_NAME/.config/SourceGit` | +| Linux | `${HOME}/.config/SourceGit` | +| macOS | `${HOME}/Library/Application Support/SourceGit` | For **Windows** users: diff --git a/VERSION b/VERSION index 9c57ca32..90c5b336 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -8.12 \ No newline at end of file +8.13 \ No newline at end of file diff --git a/build/build.osx.command b/build/build.osx.command old mode 100644 new mode 100755 diff --git a/build/resources/rpm/SPECS/build.spec b/build/resources/rpm/SPECS/build.spec index 2e6875a6..6025acac 100644 --- a/build/resources/rpm/SPECS/build.spec +++ b/build/resources/rpm/SPECS/build.spec @@ -24,7 +24,7 @@ chmod 755 -R $RPM_BUILD_ROOT %files /opt -/usr/bin +%attr(555,root,root)/usr/bin /usr/share %changelog diff --git a/screenshots/theme_dark.png b/screenshots/theme_dark.png index 4a9646dc..cfd94d8f 100644 Binary files a/screenshots/theme_dark.png and b/screenshots/theme_dark.png differ diff --git a/screenshots/theme_light.png b/screenshots/theme_light.png index 669b4d18..c4044d49 100644 Binary files a/screenshots/theme_light.png and b/screenshots/theme_light.png differ diff --git a/src/App.axaml b/src/App.axaml index ffb634c7..7129fd7e 100644 --- a/src/App.axaml +++ b/src/App.axaml @@ -26,12 +26,12 @@ - + - + diff --git a/src/App.axaml.cs b/src/App.axaml.cs index ed92b621..151c6e92 100644 --- a/src/App.axaml.cs +++ b/src/App.axaml.cs @@ -266,6 +266,7 @@ namespace SourceGit if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) { BindingPlugins.DataValidators.RemoveAt(0); + Native.OS.SetupEnternalTools(); var launcher = new Views.Launcher(); _notificationReceiver = launcher; diff --git a/src/Models/ExternalTool.cs b/src/Models/ExternalTool.cs index 1489e1cf..98351cd6 100644 --- a/src/Models/ExternalTool.cs +++ b/src/Models/ExternalTool.cs @@ -12,34 +12,23 @@ namespace SourceGit.Models { public class ExternalTool { - public string Name { get; set; } = string.Empty; - public string Icon { get; set; } = string.Empty; - public string Executable { get; set; } = string.Empty; - public string OpenCmdArgs { get; set; } = string.Empty; + public string Name { get; private set; } = string.Empty; + public string Executable { get; private set; } = string.Empty; + public string OpenCmdArgs { get; private set; } = string.Empty; + public Bitmap IconImage { get; private set; } = null; - public Bitmap IconImage + public ExternalTool(string name, string icon, string executable, string openCmdArgs) { - get + Name = name; + Executable = executable; + OpenCmdArgs = openCmdArgs; + + try { - if (_isFirstTimeGetIcon) - { - _isFirstTimeGetIcon = false; - - if (!string.IsNullOrWhiteSpace(Icon)) - { - try - { - var icon = AssetLoader.Open(new Uri($"avares://SourceGit/Resources/ExternalToolIcons/{Icon}.png", UriKind.RelativeOrAbsolute)); - _iconImage = new Bitmap(icon); - } - catch - { - } - } - } - - return _iconImage; + var asset = AssetLoader.Open(new Uri($"avares://SourceGit/Resources/ExternalToolIcons/{icon}.png", UriKind.RelativeOrAbsolute)); + IconImage = new Bitmap(asset); } + catch { } } public void Open(string repo) @@ -52,9 +41,6 @@ namespace SourceGit.Models UseShellExecute = false, }); } - - private bool _isFirstTimeGetIcon = true; - private Bitmap _iconImage = null; } public class JetBrainsState @@ -107,13 +93,7 @@ namespace SourceGit.Models return; } - Founded.Add(new ExternalTool - { - Name = name, - Icon = icon, - OpenCmdArgs = args, - Executable = path - }); + Founded.Add(new ExternalTool(name, icon, path, args)); } public void VSCode(Func platformFinder) @@ -154,13 +134,11 @@ namespace SourceGit.Models if (exclude.Contains(tool.ToolId.ToLowerInvariant())) continue; - Founded.Add(new ExternalTool - { - Name = $"{tool.DisplayName} {tool.DisplayVersion}", - Icon = supported_icons.Contains(tool.ProductCode) ? $"JetBrains/{tool.ProductCode}" : $"JetBrains/JB", - OpenCmdArgs = "\"{0}\"", - Executable = Path.Combine(tool.InstallLocation, tool.LaunchCommand), - }); + Founded.Add(new ExternalTool( + $"{tool.DisplayName} {tool.DisplayVersion}", + supported_icons.Contains(tool.ProductCode) ? $"JetBrains/{tool.ProductCode}" : "JetBrains/JB", + Path.Combine(tool.InstallLocation, tool.LaunchCommand), + "\"{0}\"")); } } } diff --git a/src/Models/User.cs b/src/Models/User.cs index ba1bebd6..5a93e135 100644 --- a/src/Models/User.cs +++ b/src/Models/User.cs @@ -25,20 +25,14 @@ namespace SourceGit.Models public static User FindOrAdd(string data) { - if (_caches.TryGetValue(data, out var value)) + return _caches.GetOrAdd(data, key => { - return value; - } - else - { - var nameEndIdx = data.IndexOf('<', System.StringComparison.Ordinal); - var name = nameEndIdx >= 2 ? data.Substring(0, nameEndIdx - 1) : string.Empty; - var email = data.Substring(nameEndIdx + 1); + var nameEndIdx = key.IndexOf('<', System.StringComparison.Ordinal); + var name = nameEndIdx >= 2 ? key.Substring(0, nameEndIdx - 1) : string.Empty; + var email = key.Substring(nameEndIdx + 1); - User user = new User() { Name = name, Email = email }; - _caches.TryAdd(data, user); - return user; - } + return new User() { Name = name, Email = email }; + }); } private static ConcurrentDictionary _caches = new ConcurrentDictionary(); diff --git a/src/Native/Linux.cs b/src/Native/Linux.cs index b34943a4..5474c5c5 100644 --- a/src/Native/Linux.cs +++ b/src/Native/Linux.cs @@ -43,6 +43,11 @@ namespace SourceGit.Native DefaultFamilyName = "fonts:SourceGit#JetBrains Mono", }); + builder.With(new X11PlatformOptions() + { + EnableIme = true, + }); + // Free-desktop file picker has an extra black background panel. builder.UseManagedSystemDialogs(); } diff --git a/src/Native/OS.cs b/src/Native/OS.cs index c3a94037..46a49e6a 100644 --- a/src/Native/OS.cs +++ b/src/Native/OS.cs @@ -41,8 +41,6 @@ namespace SourceGit.Native { throw new Exception("Platform unsupported!!!"); } - - ExternalTools = _backend.FindExternalTools(); } public static Models.Shell GetShell() @@ -77,6 +75,11 @@ namespace SourceGit.Native _backend.SetupApp(builder); } + public static void SetupEnternalTools() + { + ExternalTools = _backend.FindExternalTools(); + } + public static string FindGitExecutable() { return _backend.FindGitExecutable(); diff --git a/src/Resources/Locales/en_US.axaml b/src/Resources/Locales/en_US.axaml index b095a6a9..bf7d660e 100644 --- a/src/Resources/Locales/en_US.axaml +++ b/src/Resources/Locales/en_US.axaml @@ -210,8 +210,7 @@ SEARCH SHA/SUBJECT/AUTHOR. PRESS ENTER TO SEARCH, ESC TO QUIT CLEAR SELECTED {0} COMMITS - HotKeys - About HotKeys + Keyboard Shortcuts Reference GLOBAL Cancel current popup Close current page @@ -343,6 +342,7 @@ Configure this repository CONTINUE Open In File Browser + Filter Branches LOCAL BRANCHES Navigate To HEAD Create Branch diff --git a/src/Resources/Locales/zh_CN.axaml b/src/Resources/Locales/zh_CN.axaml index 1c972de5..9f464b79 100644 --- a/src/Resources/Locales/zh_CN.axaml +++ b/src/Resources/Locales/zh_CN.axaml @@ -210,8 +210,7 @@ 查询提交指纹、信息、作者。回车键开始,ESC键取消 清空 已选中 {0} 项提交 - 快捷键 - 显示快捷键 + 快捷键参考 全局快捷键 取消弹出面板 关闭当前页面 @@ -343,6 +342,7 @@ 配置本仓库 下一步 在文件浏览器中打开 + 过滤显示分支 本地分支 定位HEAD 新建分支 diff --git a/src/Models/BranchTreeNode.cs b/src/ViewModels/BranchTreeNode.cs similarity index 67% rename from src/Models/BranchTreeNode.cs rename to src/ViewModels/BranchTreeNode.cs index 66296519..b6b88b5d 100644 --- a/src/Models/BranchTreeNode.cs +++ b/src/ViewModels/BranchTreeNode.cs @@ -1,9 +1,9 @@ using System; using System.Collections.Generic; - +using System.IO; using Avalonia.Collections; -namespace SourceGit.Models +namespace SourceGit.ViewModels { public enum BranchTreeNodeType { @@ -23,12 +23,12 @@ namespace SourceGit.Models public bool IsUpstreamTrackStatusVisible { - get => IsBranch && !string.IsNullOrEmpty((Backend as Branch).UpstreamTrackStatus); + get => IsBranch && !string.IsNullOrEmpty((Backend as Models.Branch).UpstreamTrackStatus); } public string UpstreamTrackStatus { - get => Type == BranchTreeNodeType.Branch ? (Backend as Branch).UpstreamTrackStatus : ""; + get => Type == BranchTreeNodeType.Branch ? (Backend as Models.Branch).UpstreamTrackStatus : ""; } public bool IsRemote @@ -48,7 +48,7 @@ namespace SourceGit.Models public bool IsCurrent { - get => IsBranch && (Backend as Branch).IsCurrent; + get => IsBranch && (Backend as Models.Branch).IsCurrent; } public class Builder @@ -56,8 +56,10 @@ namespace SourceGit.Models public List Locals => _locals; public List Remotes => _remotes; - public void Run(List branches, List remotes) + public void Run(List branches, List remotes, bool bForceExpanded) { + var folders = new Dictionary(); + foreach (var remote in remotes) { var path = $"remote/{remote.Name}"; @@ -66,10 +68,10 @@ namespace SourceGit.Models Name = remote.Name, Type = BranchTreeNodeType.Remote, Backend = remote, - IsExpanded = _expanded.Contains(path), + IsExpanded = bForceExpanded || _expanded.Contains(path), }; - _maps.Add(path, node); + folders.Add(path, node); _remotes.Add(node); } @@ -78,16 +80,17 @@ namespace SourceGit.Models var isFiltered = _filters.Contains(branch.FullName); if (branch.IsLocal) { - MakeBranchNode(branch, _locals, "local", isFiltered); + MakeBranchNode(branch, _locals, folders, "local", isFiltered, bForceExpanded); } else { var remote = _remotes.Find(x => x.Name == branch.Remote); if (remote != null) - MakeBranchNode(branch, remote.Children, $"remote/{remote.Name}", isFiltered); + MakeBranchNode(branch, remote.Children, folders, $"remote/{remote.Name}", isFiltered, bForceExpanded); } } + folders.Clear(); SortNodes(_locals); SortNodes(_remotes); } @@ -113,67 +116,69 @@ namespace SourceGit.Models } } - private void MakeBranchNode(Branch branch, List roots, string prefix, bool isFiltered) + private void MakeBranchNode(Models.Branch branch, List roots, Dictionary folders, string prefix, bool isFiltered, bool bForceExpanded) { - var subs = branch.Name.Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries); - - if (subs.Length == 1) + var sepIdx = branch.Name.IndexOf('/', StringComparison.Ordinal); + if (sepIdx == -1) { - var node = new BranchTreeNode() + roots.Add(new BranchTreeNode() { - Name = subs[0], + Name = branch.Name, Type = BranchTreeNodeType.Branch, Backend = branch, IsExpanded = false, IsFiltered = isFiltered, - }; - roots.Add(node); + }); return; } - BranchTreeNode lastFolder = null; - string path = prefix; - for (int i = 0; i < subs.Length - 1; i++) + var lastFolder = null as BranchTreeNode; + var start = 0; + + while (sepIdx != -1) { - path = string.Concat(path, "/", subs[i]); - if (_maps.TryGetValue(path, out var value)) + var folder = string.Concat(prefix, "/", branch.Name.Substring(0, sepIdx)); + var name = branch.Name.Substring(start, sepIdx - start); + if (folders.TryGetValue(folder, out var val)) { - lastFolder = value; + lastFolder = val; } else if (lastFolder == null) { lastFolder = new BranchTreeNode() { - Name = subs[i], + Name = name, Type = BranchTreeNodeType.Folder, - IsExpanded = branch.IsCurrent || _expanded.Contains(path), + IsExpanded = bForceExpanded || branch.IsCurrent || _expanded.Contains(folder), }; roots.Add(lastFolder); - _maps.Add(path, lastFolder); + folders.Add(folder, lastFolder); } else { - var folder = new BranchTreeNode() + var cur = new BranchTreeNode() { - Name = subs[i], + Name = name, Type = BranchTreeNodeType.Folder, - IsExpanded = branch.IsCurrent || _expanded.Contains(path), + IsExpanded = bForceExpanded || branch.IsCurrent || _expanded.Contains(folder), }; - _maps.Add(path, folder); - lastFolder.Children.Add(folder); - lastFolder = folder; + lastFolder.Children.Add(cur); + folders.Add(folder, cur); + lastFolder = cur; } + + start = sepIdx + 1; + sepIdx = branch.Name.IndexOf('/', start); } - var last = new BranchTreeNode() + lastFolder.Children.Add(new BranchTreeNode() { - Name = subs[subs.Length - 1], + Name = Path.GetFileName(branch.Name), Type = BranchTreeNodeType.Branch, Backend = branch, IsExpanded = false, IsFiltered = isFiltered, - }; - lastFolder.Children.Add(last); + }); } private void SortNodes(List nodes) @@ -186,7 +191,7 @@ namespace SourceGit.Models } else { - return (int)(l.Type) - (int)(r.Type); + return (int)l.Type - (int)r.Type; } }); @@ -198,7 +203,6 @@ namespace SourceGit.Models private readonly List _remotes = new List(); private readonly HashSet _expanded = new HashSet(); private readonly List _filters = new List(); - private readonly Dictionary _maps = new Dictionary(); } } } diff --git a/src/ViewModels/Repository.cs b/src/ViewModels/Repository.cs index 0e67f11b..a0f84a15 100644 --- a/src/ViewModels/Repository.cs +++ b/src/ViewModels/Repository.cs @@ -89,6 +89,21 @@ namespace SourceGit.ViewModels set => SetProperty(ref _selectedView, value); } + [JsonIgnore] + public string SearchBranchFilter + { + get => _searchBranchFilter; + set + { + if (SetProperty(ref _searchBranchFilter, value)) + { + var builder = BuildBranchTree(_branches, _remotes); + LocalBranchTrees = builder.Locals; + RemoteBranchTrees = builder.Remotes; + } + } + } + [JsonIgnore] public List Remotes { @@ -104,14 +119,14 @@ namespace SourceGit.ViewModels } [JsonIgnore] - public List LocalBranchTrees + public List LocalBranchTrees { get => _localBranchTrees; private set => SetProperty(ref _localBranchTrees, value); } [JsonIgnore] - public List RemoteBranchTrees + public List RemoteBranchTrees { get => _remoteBranchTrees; private set => SetProperty(ref _remoteBranchTrees, value); @@ -221,6 +236,7 @@ namespace SourceGit.ViewModels private set => SetProperty(ref _hasUnsolvedConflicts, value); } + [JsonIgnore] public Models.Commit SearchResultSelectedCommit { get => _searchResultSelectedCommit; @@ -422,6 +438,11 @@ namespace SourceGit.ViewModels SearchedCommits = visible; } + public void ClearSearchBranchFilter() + { + SearchBranchFilter = string.Empty; + } + public void SetWatcherEnabled(bool enabled) { if (_watcher != null) @@ -533,12 +554,7 @@ namespace SourceGit.ViewModels { var branches = new Commands.QueryBranches(FullPath).Result(); var remotes = new Commands.QueryRemotes(FullPath).Result(); - - var builder = new Models.BranchTreeNode.Builder(); - builder.SetFilters(Filters); - builder.CollectExpandedNodes(_localBranchTrees, true); - builder.CollectExpandedNodes(_remoteBranchTrees, false); - builder.Run(branches, remotes); + var builder = BuildBranchTree(branches, remotes); Dispatcher.UIThread.Invoke(() => { @@ -1354,6 +1370,32 @@ namespace SourceGit.ViewModels return menu; } + private BranchTreeNode.Builder BuildBranchTree(List branches, List remotes) + { + var builder = new BranchTreeNode.Builder(); + builder.SetFilters(Filters); + + if (string.IsNullOrEmpty(_searchBranchFilter)) + { + builder.CollectExpandedNodes(_localBranchTrees, true); + builder.CollectExpandedNodes(_remoteBranchTrees, false); + builder.Run(branches, remotes, false); + } + else + { + var visibles = new List(); + foreach (var b in branches) + { + if (b.FullName.Contains(_searchBranchFilter, StringComparison.OrdinalIgnoreCase)) + visibles.Add(b); + } + + builder.Run(visibles, remotes, visibles.Count <= 20); + } + + return builder; + } + private string _fullpath = string.Empty; private string _gitDir = string.Empty; private Models.GitFlow _gitflow = new Models.GitFlow(); @@ -1372,10 +1414,12 @@ namespace SourceGit.ViewModels private bool _isTagGroupExpanded = false; private bool _isSubmoduleGroupExpanded = false; + private string _searchBranchFilter = string.Empty; + private List _remotes = new List(); private List _branches = new List(); - private List _localBranchTrees = new List(); - private List _remoteBranchTrees = new List(); + private List _localBranchTrees = new List(); + private List _remoteBranchTrees = new List(); private List _tags = new List(); private List _submodules = new List(); private bool _canCommitWithPush = false; diff --git a/src/Views/Hotkeys.axaml b/src/Views/Hotkeys.axaml index 79a06cb7..fb067d15 100644 --- a/src/Views/Hotkeys.axaml +++ b/src/Views/Hotkeys.axaml @@ -68,7 +68,7 @@ Margin="0,0,0,8"/> - + diff --git a/src/Views/Launcher.axaml.cs b/src/Views/Launcher.axaml.cs index 17a5e78a..fd6fd690 100644 --- a/src/Views/Launcher.axaml.cs +++ b/src/Views/Launcher.axaml.cs @@ -223,7 +223,10 @@ namespace SourceGit.Views private void BeginMoveWindow(object sender, PointerPressedEventArgs e) { - BeginMoveDrag(e); + if (e.ClickCount != 2) + { + BeginMoveDrag(e); + } } private void ScrollTabs(object sender, PointerWheelEventArgs e) diff --git a/src/Views/Repository.axaml b/src/Views/Repository.axaml index c854faa2..6b62a0b1 100644 --- a/src/Views/Repository.axaml +++ b/src/Views/Repository.axaml @@ -109,7 +109,7 @@ - + @@ -159,9 +159,43 @@ + + + + + + + + + + + + - - + - - + @@ -208,13 +242,13 @@ - + - - - + @@ -250,7 +284,7 @@ - + @@ -259,7 +293,7 @@ - - + @@ -328,7 +362,7 @@ -