From 4b0af79f7388702fd9fcaf426d6defbef1472feb Mon Sep 17 00:00:00 2001 From: leo Date: Tue, 4 Jun 2024 10:20:31 +0800 Subject: [PATCH 01/44] enhance: GPG signing settings. * remove gpg format settings from repository's local setting * add support for X.509 format * ux style --- src/Models/GPGFormat.cs | 22 ++++++++------------- src/Resources/Locales/en_US.axaml | 2 +- src/Resources/Locales/zh_CN.axaml | 6 +++--- src/ViewModels/RepositoryConfigure.cs | 15 ++------------ src/Views/Preference.axaml | 10 ++++++---- src/Views/Preference.axaml.cs | 26 ++++++++++++------------- src/Views/RepositoryConfigure.axaml | 28 ++++----------------------- 7 files changed, 37 insertions(+), 72 deletions(-) diff --git a/src/Models/GPGFormat.cs b/src/Models/GPGFormat.cs index bf3b3678..0ba4e9e2 100644 --- a/src/Models/GPGFormat.cs +++ b/src/Models/GPGFormat.cs @@ -2,24 +2,18 @@ namespace SourceGit.Models { - public class GPGFormat(string name, string value, string desc) + public class GPGFormat(string name, string value, string desc, string program, bool needFindProgram) { public string Name { get; set; } = name; public string Value { get; set; } = value; public string Desc { get; set; } = desc; + public string Program { get; set; } = program; + public bool NeedFindProgram { get; set; } = needFindProgram; - public static readonly GPGFormat OPENPGP = new GPGFormat("OPENPGP", "openpgp", "DEFAULT"); - - public static readonly GPGFormat SSH = new GPGFormat("SSH", "ssh", "Git >= 2.34.0"); - - public static readonly List Supported = new List() { - OPENPGP, - SSH, - }; - - public bool Equals(GPGFormat other) - { - return Value == other.Value; - } + public static readonly List Supported = [ + new GPGFormat("OPENPGP", "openpgp", "DEFAULT", "gpg", true), + new GPGFormat("X.509", "x509", "", "gpgsm", true), + new GPGFormat("SSH", "ssh", "Requires Git >= 2.34.0", "ssh-keygen", false), + ]; } } diff --git a/src/Resources/Locales/en_US.axaml b/src/Resources/Locales/en_US.axaml index edf3fe1a..b5a96ecd 100644 --- a/src/Resources/Locales/en_US.axaml +++ b/src/Resources/Locales/en_US.axaml @@ -314,7 +314,7 @@ Commit GPG signing Tag GPG signing GPG Format - Install Path + Program Install Path Input path for installed gpg program User Signing Key User's gpg signing key diff --git a/src/Resources/Locales/zh_CN.axaml b/src/Resources/Locales/zh_CN.axaml index 015cb6cc..f1c5a95f 100644 --- a/src/Resources/Locales/zh_CN.axaml +++ b/src/Resources/Locales/zh_CN.axaml @@ -316,9 +316,9 @@ GPG签名 启用提交签名 启用标签签名 - GPG签名格式 - 可执行文件位置 - gpg.exe所在路径 + 签名格式 + 签名程序位置 + 签名程序所在路径 用户签名KEY 输入签名提交所使用的KEY 外部合并工具 diff --git a/src/ViewModels/RepositoryConfigure.cs b/src/ViewModels/RepositoryConfigure.cs index c1bbdcf5..1409dfa2 100644 --- a/src/ViewModels/RepositoryConfigure.cs +++ b/src/ViewModels/RepositoryConfigure.cs @@ -17,12 +17,6 @@ namespace SourceGit.ViewModels set; } - public Models.GPGFormat GPGFormat - { - get; - set; - } - public bool GPGCommitSigningEnabled { get; @@ -60,10 +54,6 @@ namespace SourceGit.ViewModels GPGCommitSigningEnabled = gpgCommitSign == "true"; if (_cached.TryGetValue("tag.gpgSign", out var gpgTagSign)) GPGTagSigningEnabled = gpgTagSign == "true"; - if (_cached.TryGetValue("gpg.format", out var gpgFormat)) - GPGFormat = Models.GPGFormat.Supported.Find(x => x.Value == gpgFormat); - else - GPGFormat = Models.GPGFormat.OPENPGP; if (_cached.TryGetValue("user.signingkey", out var signingKey)) GPGUserSigningKey = signingKey; if (_cached.TryGetValue("http.proxy", out var proxy)) @@ -78,20 +68,19 @@ namespace SourceGit.ViewModels SetIfChanged("user.email", UserEmail); SetIfChanged("commit.gpgsign", GPGCommitSigningEnabled ? "true" : "false"); SetIfChanged("tag.gpgSign", GPGTagSigningEnabled ? "true" : "false"); - SetIfChanged("gpg.format", GPGFormat?.Value, Models.GPGFormat.OPENPGP.Value); SetIfChanged("user.signingkey", GPGUserSigningKey); SetIfChanged("http.proxy", HttpProxy); return null; } - private void SetIfChanged(string key, string value, string defaultValue = null) + private void SetIfChanged(string key, string value) { bool changed = false; if (_cached.TryGetValue(key, out var old)) { changed = old != value; } - else if (!string.IsNullOrEmpty(value) && value != defaultValue) + else if (!string.IsNullOrEmpty(value)) { changed = true; } diff --git a/src/Views/Preference.axaml b/src/Views/Preference.axaml index 46279470..ab11cca3 100644 --- a/src/Views/Preference.axaml +++ b/src/Views/Preference.axaml @@ -409,7 +409,7 @@ - + + SelectedItem="{Binding #me.GPGFormat, Mode=TwoWay}"> @@ -433,12 +433,14 @@ + Margin="0,0,16,0" + IsVisible="{Binding #me.GPGFormat.NeedFindProgram}"/> + Watermark="{DynamicResource Text.Preference.GPG.Path.Placeholder}" + IsVisible="{Binding #me.GPGFormat.NeedFindProgram}"> - diff --git a/src/Views/Repository.axaml.cs b/src/Views/Repository.axaml.cs index 1eadb7ac..2cca3ab3 100644 --- a/src/Views/Repository.axaml.cs +++ b/src/Views/Repository.axaml.cs @@ -1,6 +1,4 @@ -using System; using System.Collections.Generic; -using System.Threading.Tasks; using Avalonia; using Avalonia.Controls; @@ -305,14 +303,6 @@ namespace SourceGit.Views e.Handled = true; } - - private void UpdateSubmodules(object sender, RoutedEventArgs e) - { - if (DataContext is ViewModels.Repository repo) - repo.UpdateSubmodules(); - - e.Handled = true; - } private void CollectBranchesFromNode(List outs, ViewModels.BranchTreeNode node) { From 4be068eb213d7ac1057dbfb5548fc437d495e187 Mon Sep 17 00:00:00 2001 From: leo Date: Tue, 4 Jun 2024 14:52:05 +0800 Subject: [PATCH 06/44] update: remove unused resources --- src/Resources/Icons.axaml | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Resources/Icons.axaml b/src/Resources/Icons.axaml index b6369398..a3b59cbf 100644 --- a/src/Resources/Icons.axaml +++ b/src/Resources/Icons.axaml @@ -9,7 +9,6 @@ M30 0 30 30 0 15z M0 0 0 30 30 15z M192 192m-64 0a64 64 0 1 0 128 0 64 64 0 1 0-128 0ZM192 512m-64 0a64 64 0 1 0 128 0 64 64 0 1 0-128 0ZM192 832m-64 0a64 64 0 1 0 128 0 64 64 0 1 0-128 0ZM864 160H352c-17.7 0-32 14.3-32 32s14.3 32 32 32h512c17.7 0 32-14.3 32-32s-14.3-32-32-32zM864 480H352c-17.7 0-32 14.3-32 32s14.3 32 32 32h512c17.7 0 32-14.3 32-32s-14.3-32-32-32zM864 800H352c-17.7 0-32 14.3-32 32s14.3 32 32 32h512c17.7 0 32-14.3 32-32s-14.3-32-32-32z - M512 945c-238 0-433-195-433-433S274 79 512 79c238 0 433 195 433 433S750 945 512 945M512 0C228 0 0 228 0 512s228 512 512 512 512-228 512-512-228-512-512-512zM752 477H364l128-128a38 38 0 000-55 38 38 0 00-55 0l-185 183a55 55 0 00-16 39c0 16 6 30 16 39l185 185a39 39 0 0028 12 34 34 0 0028-14 38 38 0 000-55l-128-128h386c22 0 41-18 41-39a39 39 0 00-39-39 M576 832C576 867 547 896 512 896 477 896 448 867 448 832 448 797 477 768 512 768 547 768 576 797 576 832ZM512 256C477 256 448 285 448 320L448 640C448 675 477 704 512 704 547 704 576 675 576 640L576 320C576 285 547 256 512 256ZM1024 896C1024 967 967 1024 896 1024L128 1024C57 1024 0 967 0 896 0 875 5 855 14 837L14 837 398 69 398 69C420 28 462 0 512 0 562 0 604 28 626 69L1008 835C1018 853 1024 874 1024 896ZM960 896C960 885 957 875 952 865L952 864 951 863 569 98C557 77 536 64 512 64 488 64 466 77 455 99L452 105 92 825 93 825 71 867C66 876 64 886 64 896 64 931 93 960 128 960L896 960C931 960 960 931 960 896Z M608 0q48 0 88 23t63 63 23 87v70h55q35 0 67 14t57 38 38 57 14 67V831q0 34-14 66t-38 57-57 38-67 13H426q-34 0-66-13t-57-38-38-57-14-66v-70h-56q-34 0-66-14t-57-38-38-57-13-67V174q0-47 23-87T109 23 196 0h412m175 244H426q-46 0-86 22T278 328t-26 85v348H608q47 0 86-22t63-62 25-85l1-348m-269 318q18 0 31 13t13 31-13 31-31 13-31-13-13-31 13-31 31-13m0-212q13 0 22 9t11 22v125q0 14-9 23t-22 10-23-7-11-22l-1-126q0-13 10-23t23-10z m186 532 287 0 0 287c0 11 9 20 20 20s20-9 20-20l0-287 287 0c11 0 20-9 20-20s-9-20-20-20l-287 0 0-287c0-11-9-20-20-20s-20 9-20 20l0 287-287 0c-11 0-20 9-20 20s9 20 20 20z From d2ea90be232dfcb76f0e4e24bf026990e8ff0732 Mon Sep 17 00:00:00 2001 From: leo Date: Tue, 4 Jun 2024 17:29:08 +0800 Subject: [PATCH 07/44] ux: Opacity for selected items --- src/Resources/Styles.axaml | 58 ++++++++++++++++++++++-------------- src/Views/CommitDetail.axaml | 15 ++++++++-- src/Views/Repository.axaml | 43 +++++++++++++++++++++----- 3 files changed, 82 insertions(+), 34 deletions(-) diff --git a/src/Resources/Styles.axaml b/src/Resources/Styles.axaml index c373d891..114ac72d 100644 --- a/src/Resources/Styles.axaml +++ b/src/Resources/Styles.axaml @@ -1045,32 +1045,33 @@ - - - - - + + + + + - + HorizontalAlignment="Center" + IsChecked="{TemplateBinding IsExpanded, Mode=TwoWay}" /> + + + + + + + + + + - + - + diff --git a/src/Views/Repository.axaml b/src/Views/Repository.axaml index 9731da6f..d393f508 100644 --- a/src/Views/Repository.axaml +++ b/src/Views/Repository.axaml @@ -212,12 +212,21 @@ - - - + + @@ -276,12 +285,21 @@ - - - + + @@ -344,13 +362,22 @@ - + + - + From 802b429cc81c42899f7c3c03de539ed80b894044 Mon Sep 17 00:00:00 2001 From: leo Date: Tue, 4 Jun 2024 20:19:49 +0800 Subject: [PATCH 08/44] enhance: supports word wrap in text diff view --- src/Models/DiffResult.cs | 2 ++ src/Resources/Icons.axaml | 1 + src/Resources/Locales/en_US.axaml | 1 + src/Resources/Locales/zh_CN.axaml | 1 + src/ViewModels/DiffContext.cs | 10 +++------- src/ViewModels/Preference.cs | 7 +++++++ src/ViewModels/WorkingCopy.cs | 7 ------- src/Views/DiffView.axaml | 12 +++++++++++- src/Views/TextDiffView.axaml | 24 +++++++++++------------- src/Views/TextDiffView.axaml.cs | 2 -- 10 files changed, 37 insertions(+), 30 deletions(-) diff --git a/src/Models/DiffResult.cs b/src/Models/DiffResult.cs index d9d21031..eafe44ec 100644 --- a/src/Models/DiffResult.cs +++ b/src/Models/DiffResult.cs @@ -2,6 +2,7 @@ using System.Text; using System.Text.RegularExpressions; +using Avalonia; using Avalonia.Media.Imaging; namespace SourceGit.Models @@ -63,6 +64,7 @@ namespace SourceGit.Models public string File { get; set; } = string.Empty; public List Lines { get; set; } = new List(); public int MaxLineNumber = 0; + public Vector SyncScrollOffset { get; set; } = Vector.Zero; public void GenerateNewPatchFromSelection(Change change, string fileBlobGuid, TextDiffSelection selection, bool revert, string output) { diff --git a/src/Resources/Icons.axaml b/src/Resources/Icons.axaml index a3b59cbf..8f43742c 100644 --- a/src/Resources/Icons.axaml +++ b/src/Resources/Icons.axaml @@ -97,4 +97,5 @@ M408 232C408 210 426 192 448 192h416a40 40 0 110 80H448a40 40 0 01-40-40zM408 512c0-22 18-40 40-40h416a40 40 0 110 80H448A40 40 0 01408 512zM448 752A40 40 0 00448 832h416a40 40 0 100-80H448zM32 480l132 0 0-128 64 0 0 128 132 0 0 64-132 0 0 128-64 0 0-128-132 0Z M408 232C408 210 426 192 448 192h416a40 40 0 110 80H448a40 40 0 01-40-40zM408 512c0-22 18-40 40-40h416a40 40 0 110 80H448A40 40 0 01408 512zM448 752A40 40 0 00448 832h416a40 40 0 100-80H448zM32 480l328 0 0 64-328 0Z M645 448l64 64 220-221L704 64l-64 64 115 115H128v90h628zM375 576l-64-64-220 224L314 960l64-64-116-115H896v-90H262z + M248 221a77 77 0 00-30-21c-18-7-40-10-68-5a224 224 0 00-45 13c-5 2-10 5-15 8l-3 2v68l11-9c10-8 21-14 34-19 13-5 26-7 39-7 12 0 21 3 28 10 6 6 9 16 9 29l-62 9c-14 2-26 6-36 11a80 80 0 00-25 20c-7 8-12 17-15 27-6 21-6 44 1 65a70 70 0 0041 43c10 4 21 6 34 6a80 80 0 0063-28v22h64V298c0-16-2-31-6-44a91 91 0 00-18-33zm-41 121v15c0 8-1 15-4 22a48 48 0 01-24 29 44 44 0 01-33 2 29 29 0 01-10-6 25 25 0 01-6-9 30 30 0 01-2-12c0-5 1-9 2-14a21 21 0 015-9 28 28 0 0110-7 83 83 0 0120-5l42-6zm323-68a144 144 0 00-16-42 87 87 0 00-28-29 75 75 0 00-41-11 73 73 0 00-44 14c-6 5-12 11-17 17V64H326v398h59v-18c8 10 18 17 30 21 6 2 13 3 21 3 16 0 31-4 43-11 12-7 23-18 31-31a147 147 0 0019-46 248 248 0 006-57c0-17-2-33-5-49zm-55 49c0 15-1 28-4 39-2 11-6 20-10 27a41 41 0 01-15 15 37 37 0 01-36 1 44 44 0 01-13-12 59 59 0 01-9-18A76 76 0 01384 352v-33c0-10 1-20 4-29 2-8 6-15 10-22a43 43 0 0115-13 37 37 0 0119-5 35 35 0 0132 18c4 6 7 14 9 23 2 9 3 20 3 31zM154 634a58 58 0 0120-15c14-6 35-7 49-1 7 3 13 6 20 12l21 17V572l-6-4a124 124 0 00-58-14c-20 0-38 4-54 11-16 7-30 17-41 30-12 13-20 29-26 46-6 17-9 36-9 57 0 18 3 36 8 52 6 16 14 30 24 42 10 12 23 21 38 28 15 7 32 10 50 10 15 0 28-2 39-5 11-3 21-8 30-14l5-4v-57l-13 6a26 26 0 01-5 2c-3 1-6 2-8 3-2 1-15 6-15 6-4 2-9 3-14 4a63 63 0 01-38-4 53 53 0 01-20-14 70 70 0 01-13-24 111 111 0 01-5-34c0-13 2-26 5-36 3-10 8-19 14-26zM896 384h-256V320h288c21 1 32 12 32 32v384c0 18-12 32-32 32H504l132 133-45 45-185-185c-16-21-16-25 0-45l185-185L637 576l-128 128H896V384z diff --git a/src/Resources/Locales/en_US.axaml b/src/Resources/Locales/en_US.axaml index ad919346..a7f470da 100644 --- a/src/Resources/Locales/en_US.axaml +++ b/src/Resources/Locales/en_US.axaml @@ -166,6 +166,7 @@ SUBMODULE NEW Syntax Highlighting + Line Word Wrap Open In Merge Tool Decrease Number of Visible Lines Increase Number of Visible Lines diff --git a/src/Resources/Locales/zh_CN.axaml b/src/Resources/Locales/zh_CN.axaml index ba330800..6851458b 100644 --- a/src/Resources/Locales/zh_CN.axaml +++ b/src/Resources/Locales/zh_CN.axaml @@ -169,6 +169,7 @@ 子模块 新增 语法高亮 + 自动换行 使用外部合并工具查看 减少可见的行数 增加可见的行数 diff --git a/src/ViewModels/DiffContext.cs b/src/ViewModels/DiffContext.cs index a9bfb804..823f589b 100644 --- a/src/ViewModels/DiffContext.cs +++ b/src/ViewModels/DiffContext.cs @@ -58,12 +58,6 @@ namespace SourceGit.ViewModels private set => SetProperty(ref _content, value); } - public Vector SyncScrollOffset - { - get => _syncScrollOffset; - set => SetProperty(ref _syncScrollOffset, value); - } - public int Unified { get => _unified; @@ -203,6 +197,9 @@ namespace SourceGit.ViewModels Dispatcher.UIThread.Post(() => { + if (_content is Models.TextDiff old && rs is Models.TextDiff cur && old.File == cur.File) + cur.SyncScrollOffset = old.SyncScrollOffset; + FileModeChange = latest.FileModeChange; Content = rs; IsTextDiff = rs is Models.TextDiff; @@ -229,7 +226,6 @@ namespace SourceGit.ViewModels private bool _isLoading = true; private bool _isTextDiff = false; private object _content = null; - private Vector _syncScrollOffset = Vector.Zero; private int _unified = 4; } } diff --git a/src/ViewModels/Preference.cs b/src/ViewModels/Preference.cs index 68dd3532..7b07b4a7 100644 --- a/src/ViewModels/Preference.cs +++ b/src/ViewModels/Preference.cs @@ -164,6 +164,12 @@ namespace SourceGit.ViewModels set => SetProperty(ref _useSyntaxHighlighting, value); } + public bool EnableDiffViewWordWrap + { + get => _enableDiffViewWordWrap; + set => SetProperty(ref _enableDiffViewWordWrap, value); + } + public Models.ChangeViewMode UnstagedChangeViewMode { get => _unstagedChangeViewMode; @@ -526,6 +532,7 @@ namespace SourceGit.ViewModels private bool _useTwoColumnsLayoutInHistories = false; private bool _useSideBySideDiff = false; private bool _useSyntaxHighlighting = false; + private bool _enableDiffViewWordWrap = false; private Models.ChangeViewMode _unstagedChangeViewMode = Models.ChangeViewMode.List; private Models.ChangeViewMode _stagedChangeViewMode = Models.ChangeViewMode.List; diff --git a/src/ViewModels/WorkingCopy.cs b/src/ViewModels/WorkingCopy.cs index 4167c244..3d481153 100644 --- a/src/ViewModels/WorkingCopy.cs +++ b/src/ViewModels/WorkingCopy.cs @@ -273,10 +273,6 @@ namespace SourceGit.ViewModels Staged = staged; _isLoadingData = false; - var scrollOffset = Vector.Zero; - if (_detailContext is DiffContext old) - scrollOffset = old.SyncScrollOffset; - if (selectedUnstaged.Count > 0) SelectedUnstaged = selectedUnstaged; else if (selectedStaged.Count > 0) @@ -284,9 +280,6 @@ namespace SourceGit.ViewModels else SetDetail(null); - if (_detailContext is DiffContext cur) - cur.SyncScrollOffset = scrollOffset; - // Try to load merge message from MERGE_MSG if (string.IsNullOrEmpty(_commitMessage)) { diff --git a/src/Views/DiffView.axaml b/src/Views/DiffView.axaml index 9705715c..d8ed4356 100644 --- a/src/Views/DiffView.axaml +++ b/src/Views/DiffView.axaml @@ -41,7 +41,7 @@ - + + + + + - + SyncScrollOffset="{Binding #ThisControl.TextDiff.SyncScrollOffset, Mode=TwoWay}" + UseSyntaxHighlighting="{Binding Source={x:Static vm:Preference.Instance}, Path=UseSyntaxHighlighting}" + WordWrap="{Binding Source={x:Static vm:Preference.Instance}, Path=EnableDiffViewWordWrap}"/> Date: Tue, 4 Jun 2024 20:34:18 +0800 Subject: [PATCH 09/44] ux: use TextTrimming="CharacterEllipsis" for diff view title --- src/Views/DiffView.axaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Views/DiffView.axaml b/src/Views/DiffView.axaml index d8ed4356..adffabda 100644 --- a/src/Views/DiffView.axaml +++ b/src/Views/DiffView.axaml @@ -30,7 +30,7 @@ - + From 57540b960a0911a5cc92079c405699f163f77ed5 Mon Sep 17 00:00:00 2001 From: leo Date: Tue, 4 Jun 2024 21:05:23 +0800 Subject: [PATCH 10/44] code_style: remove unused using --- src/ViewModels/DiffContext.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/ViewModels/DiffContext.cs b/src/ViewModels/DiffContext.cs index 823f589b..f5aec074 100644 --- a/src/ViewModels/DiffContext.cs +++ b/src/ViewModels/DiffContext.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.IO; using System.Threading.Tasks; -using Avalonia; using Avalonia.Media.Imaging; using Avalonia.Threading; From ce9a3dad2faab6ccf4d3499a1a4e1f8daa8a98d9 Mon Sep 17 00:00:00 2001 From: leo Date: Wed, 5 Jun 2024 11:46:31 +0800 Subject: [PATCH 11/44] enhance: improve commit and stash parsing time --- src/Commands/QueryCommits.cs | 174 ++++++++++++------------------ src/Commands/QuerySingleCommit.cs | 98 +++++------------ src/Commands/QueryStashes.cs | 56 ++++------ src/Models/Commit.cs | 11 -- src/Models/Stash.cs | 1 - src/Models/User.cs | 7 +- 6 files changed, 117 insertions(+), 230 deletions(-) diff --git a/src/Commands/QueryCommits.cs b/src/Commands/QueryCommits.cs index 6788884c..0bfb132c 100644 --- a/src/Commands/QueryCommits.cs +++ b/src/Commands/QueryCommits.cs @@ -5,130 +5,82 @@ namespace SourceGit.Commands { public class QueryCommits : Command { - private const string GPGSIG_START = "gpgsig -----BEGIN "; - private const string GPGSIG_END = " -----END "; - - private readonly List commits = new List(); - private Models.Commit current = null; - private bool isSkipingGpgsig = false; - private bool isHeadFounded = false; - private readonly bool findFirstMerged = true; - public QueryCommits(string repo, string limits, bool needFindHead = true) { + _endOfBodyToken = $"----- END OF BODY {Guid.NewGuid()} -----"; + WorkingDirectory = repo; Context = repo; - Args = "log --date-order --decorate=full --pretty=raw " + limits; - findFirstMerged = needFindHead; + Args = $"log --date-order --no-show-signature --decorate=full --pretty=format:\"%H%n%P%n%D%n%aN±%aE%n%at%n%cN±%cE%n%ct%n%B%n{_endOfBodyToken}\" " + limits; + _findFirstMerged = needFindHead; } public List Result() { Exec(); - if (current != null) - { - current.Message = current.Message.Trim(); - commits.Add(current); - } - - if (findFirstMerged && !isHeadFounded && commits.Count > 0) - { + if (_findFirstMerged && !_isHeadFounded && _commits.Count > 0) MarkFirstMerged(); - } - return commits; + return _commits; } protected override void OnReadline(string line) { - if (isSkipingGpgsig) + switch (_nextPartIdx) { - if (line.StartsWith(GPGSIG_END, StringComparison.Ordinal)) - isSkipingGpgsig = false; - return; - } - else if (line.StartsWith(GPGSIG_START, StringComparison.Ordinal)) - { - isSkipingGpgsig = true; - return; + case 0: + _current = new Models.Commit() { SHA = line }; + _commits.Add(_current); + break; + case 1: + if (!string.IsNullOrEmpty(line)) + _current.Parents.AddRange(line.Split(' ', StringSplitOptions.RemoveEmptyEntries)); + break; + case 2: + if (!string.IsNullOrEmpty(line)) + ParseDecorators(line); + break; + case 3: + _current.Author = Models.User.FindOrAdd(line); + break; + case 4: + _current.AuthorTime = ulong.Parse(line); + break; + case 5: + _current.Committer = Models.User.FindOrAdd(line); + break; + case 6: + _current.CommitterTime = ulong.Parse(line); + break; + default: + if (line.Equals(_endOfBodyToken, StringComparison.Ordinal)) + { + _nextPartIdx = 0; + if (!string.IsNullOrEmpty(_current.Message)) _current.Message = _current.Message.Trim(); + } + else + { + if (string.IsNullOrEmpty(_current.Subject)) + _current.Subject = line; + else + _current.Message += (line + "\n"); + } + return; } - if (line.StartsWith("commit ", StringComparison.Ordinal)) - { - if (current != null) - { - current.Message = current.Message.Trim(); - commits.Add(current); - } - - current = new Models.Commit(); - line = line.Substring(7); - - var decoratorStart = line.IndexOf('(', StringComparison.Ordinal); - if (decoratorStart < 0) - { - current.SHA = line.Trim(); - } - else - { - current.SHA = line.Substring(0, decoratorStart).Trim(); - current.IsMerged = ParseDecorators(current.Decorators, line.Substring(decoratorStart + 1)); - if (!isHeadFounded) - isHeadFounded = current.IsMerged; - } - - return; - } - - if (current == null) - return; - - if (line.StartsWith("tree ", StringComparison.Ordinal)) - { - return; - } - else if (line.StartsWith("parent ", StringComparison.Ordinal)) - { - current.Parents.Add(line.Substring("parent ".Length)); - } - else if (line.StartsWith("author ", StringComparison.Ordinal)) - { - Models.User user = Models.User.Invalid; - ulong time = 0; - Models.Commit.ParseUserAndTime(line.Substring(7), ref user, ref time); - current.Author = user; - current.AuthorTime = time; - } - else if (line.StartsWith("committer ", StringComparison.Ordinal)) - { - Models.User user = Models.User.Invalid; - ulong time = 0; - Models.Commit.ParseUserAndTime(line.Substring(10), ref user, ref time); - current.Committer = user; - current.CommitterTime = time; - } - else if (string.IsNullOrEmpty(current.Subject)) - { - current.Subject = line.Trim(); - } - else - { - current.Message += (line.Trim() + "\n"); - } + _nextPartIdx++; } - private bool ParseDecorators(List decorators, string data) + private void ParseDecorators(string data) { - bool isHeadOfCurrent = false; - - var subs = data.Split(new char[] { ',', ')', '(' }, StringSplitOptions.RemoveEmptyEntries); + var subs = data.Split(',', StringSplitOptions.RemoveEmptyEntries); foreach (var sub in subs) { var d = sub.Trim(); if (d.StartsWith("tag: refs/tags/", StringComparison.Ordinal)) { - decorators.Add(new Models.Decorator() + _current.Decorators.Add(new Models.Decorator() { Type = Models.DecoratorType.Tag, Name = d.Substring(15).Trim(), @@ -140,8 +92,8 @@ namespace SourceGit.Commands } else if (d.StartsWith("HEAD -> refs/heads/", StringComparison.Ordinal)) { - isHeadOfCurrent = true; - decorators.Add(new Models.Decorator() + _current.IsMerged = true; + _current.Decorators.Add(new Models.Decorator() { Type = Models.DecoratorType.CurrentBranchHead, Name = d.Substring(19).Trim(), @@ -149,8 +101,8 @@ namespace SourceGit.Commands } else if (d.Equals("HEAD")) { - isHeadOfCurrent = true; - decorators.Add(new Models.Decorator() + _current.IsMerged = true; + _current.Decorators.Add(new Models.Decorator() { Type = Models.DecoratorType.CurrentCommitHead, Name = d.Trim(), @@ -158,7 +110,7 @@ namespace SourceGit.Commands } else if (d.StartsWith("refs/heads/", StringComparison.Ordinal)) { - decorators.Add(new Models.Decorator() + _current.Decorators.Add(new Models.Decorator() { Type = Models.DecoratorType.LocalBranchHead, Name = d.Substring(11).Trim(), @@ -166,7 +118,7 @@ namespace SourceGit.Commands } else if (d.StartsWith("refs/remotes/", StringComparison.Ordinal)) { - decorators.Add(new Models.Decorator() + _current.Decorators.Add(new Models.Decorator() { Type = Models.DecoratorType.RemoteBranchHead, Name = d.Substring(13).Trim(), @@ -174,7 +126,7 @@ namespace SourceGit.Commands } } - decorators.Sort((l, r) => + _current.Decorators.Sort((l, r) => { if (l.Type != r.Type) { @@ -186,12 +138,13 @@ namespace SourceGit.Commands } }); - return isHeadOfCurrent; + if (_current.IsMerged && !_isHeadFounded) + _isHeadFounded = true; } private void MarkFirstMerged() { - Args = $"log --since=\"{commits[commits.Count - 1].CommitterTimeStr}\" --format=\"%H\""; + Args = $"log --since=\"{_commits[_commits.Count - 1].CommitterTimeStr}\" --format=\"%H\""; var rs = ReadToEnd(); var shas = rs.StdOut.Split(new char[] { '\n' }, StringSplitOptions.RemoveEmptyEntries); @@ -202,7 +155,7 @@ namespace SourceGit.Commands foreach (var sha in shas) set.Add(sha); - foreach (var c in commits) + foreach (var c in _commits) { if (set.Contains(c.SHA)) { @@ -211,5 +164,12 @@ namespace SourceGit.Commands } } } + + private string _endOfBodyToken = string.Empty; + private List _commits = new List(); + private Models.Commit _current = null; + private bool _isHeadFounded = false; + private readonly bool _findFirstMerged = true; + private int _nextPartIdx = 0; } } diff --git a/src/Commands/QuerySingleCommit.cs b/src/Commands/QuerySingleCommit.cs index 0213061d..3ea7fd08 100644 --- a/src/Commands/QuerySingleCommit.cs +++ b/src/Commands/QuerySingleCommit.cs @@ -1,100 +1,57 @@ using System; using System.Collections.Generic; +using System.Text; namespace SourceGit.Commands { public class QuerySingleCommit : Command { - private const string GPGSIG_START = "gpgsig -----BEGIN "; - private const string GPGSIG_END = " -----END "; - public QuerySingleCommit(string repo, string sha) { WorkingDirectory = repo; Context = repo; - Args = $"show --pretty=raw --decorate=full -s {sha}"; + Args = $"show --no-show-signature --decorate=full --pretty=format:%H%n%P%n%D%n%aN±%aE%n%at%n%cN±%cE%n%ct%n%B -s {sha}"; } public Models.Commit Result() { - var succ = Exec(); - if (!succ) - return null; - - _commit.Message.Trim(); - return _commit; - } - - protected override void OnReadline(string line) - { - if (isSkipingGpgsig) + var rs = ReadToEnd(); + if (rs.IsSuccess && !string.IsNullOrEmpty(rs.StdOut)) { - if (line.StartsWith(GPGSIG_END, StringComparison.Ordinal)) - isSkipingGpgsig = false; - return; - } - else if (line.StartsWith(GPGSIG_START, StringComparison.Ordinal)) - { - isSkipingGpgsig = true; - return; - } + var commit = new Models.Commit(); + var lines = rs.StdOut.Split('\n'); + if (lines.Length < 8) + return null; - if (line.StartsWith("commit ", StringComparison.Ordinal)) - { - line = line.Substring(7); + commit.SHA = lines[0]; + if (!string.IsNullOrEmpty(lines[1])) + commit.Parents.AddRange(lines[1].Split(' ', StringSplitOptions.RemoveEmptyEntries)); + if (!string.IsNullOrEmpty(lines[2])) + commit.IsMerged = ParseDecorators(commit.Decorators, lines[2]); + commit.Author = Models.User.FindOrAdd(lines[3]); + commit.AuthorTime = ulong.Parse(lines[4]); + commit.Committer = Models.User.FindOrAdd(lines[5]); + commit.CommitterTime = ulong.Parse(lines[6]); + commit.Subject = lines[7]; - var decoratorStart = line.IndexOf('(', StringComparison.Ordinal); - if (decoratorStart < 0) + if (lines.Length > 8) { - _commit.SHA = line.Trim(); - } - else - { - _commit.SHA = line.Substring(0, decoratorStart).Trim(); - ParseDecorators(_commit.Decorators, line.Substring(decoratorStart + 1)); + StringBuilder builder = new StringBuilder(); + for (int i = 8; i < lines.Length; i++) + builder.Append(lines[i]); + commit.Message = builder.ToString(); } - return; + return commit; } - if (line.StartsWith("tree ", StringComparison.Ordinal)) - { - return; - } - else if (line.StartsWith("parent ", StringComparison.Ordinal)) - { - _commit.Parents.Add(line.Substring("parent ".Length)); - } - else if (line.StartsWith("author ", StringComparison.Ordinal)) - { - Models.User user = Models.User.Invalid; - ulong time = 0; - Models.Commit.ParseUserAndTime(line.Substring(7), ref user, ref time); - _commit.Author = user; - _commit.AuthorTime = time; - } - else if (line.StartsWith("committer ", StringComparison.Ordinal)) - { - Models.User user = Models.User.Invalid; - ulong time = 0; - Models.Commit.ParseUserAndTime(line.Substring(10), ref user, ref time); - _commit.Committer = user; - _commit.CommitterTime = time; - } - else if (string.IsNullOrEmpty(_commit.Subject)) - { - _commit.Subject = line.Trim(); - } - else - { - _commit.Message += (line.Trim() + "\n"); - } + return null; } private bool ParseDecorators(List decorators, string data) { bool isHeadOfCurrent = false; - var subs = data.Split(new char[] { ',', ')', '(' }, StringSplitOptions.RemoveEmptyEntries); + var subs = data.Split(',', StringSplitOptions.RemoveEmptyEntries); foreach (var sub in subs) { var d = sub.Trim(); @@ -160,8 +117,5 @@ namespace SourceGit.Commands return isHeadOfCurrent; } - - private Models.Commit _commit = new Models.Commit(); - private bool isSkipingGpgsig = false; } } diff --git a/src/Commands/QueryStashes.cs b/src/Commands/QueryStashes.cs index 5362f87b..6d089f8e 100644 --- a/src/Commands/QueryStashes.cs +++ b/src/Commands/QueryStashes.cs @@ -1,64 +1,48 @@ -using System; -using System.Collections.Generic; -using System.Text.RegularExpressions; +using System.Collections.Generic; namespace SourceGit.Commands { - public partial class QueryStashes : Command + public class QueryStashes : Command { - - [GeneratedRegex(@"^Reflog: refs/(stash@\{\d+\}).*$")] - private static partial Regex REG_STASH(); - public QueryStashes(string repo) { WorkingDirectory = repo; Context = repo; - Args = "stash list --pretty=raw"; + Args = "stash list --pretty=format:%H%n%ct%n%gd%n%s"; } public List Result() { Exec(); - if (_current != null) - _stashes.Add(_current); return _stashes; } protected override void OnReadline(string line) { - if (line.StartsWith("commit ", StringComparison.Ordinal)) + switch (_nextLineIdx) { - if (_current != null && !string.IsNullOrEmpty(_current.Name)) + case 0: + _current = new Models.Stash() { SHA = line }; _stashes.Add(_current); - _current = new Models.Stash() { SHA = line.Substring(7, 8) }; - return; + break; + case 1: + _current.Time = ulong.Parse(line); + break; + case 2: + _current.Name = line; + break; + case 3: + _current.Message = line; + break; } - if (_current == null) - return; - - if (line.StartsWith("Reflog: refs/stash@", StringComparison.Ordinal)) - { - var match = REG_STASH().Match(line); - if (match.Success) - _current.Name = match.Groups[1].Value; - } - else if (line.StartsWith("Reflog message: ", StringComparison.Ordinal)) - { - _current.Message = line.Substring(16); - } - else if (line.StartsWith("author ", StringComparison.Ordinal)) - { - Models.User user = Models.User.Invalid; - ulong time = 0; - Models.Commit.ParseUserAndTime(line.Substring(7), ref user, ref time); - _current.Author = user; - _current.Time = time; - } + _nextLineIdx++; + if (_nextLineIdx > 3) + _nextLineIdx = 0; } private readonly List _stashes = new List(); private Models.Stash _current = null; + private int _nextLineIdx = 0; } } diff --git a/src/Models/Commit.cs b/src/Models/Commit.cs index d8355e81..4a7313f0 100644 --- a/src/Models/Commit.cs +++ b/src/Models/Commit.cs @@ -40,17 +40,6 @@ namespace SourceGit.Models get => string.IsNullOrWhiteSpace(Message) ? Subject : $"{Subject}\n\n{Message}"; } - public static void ParseUserAndTime(string data, ref User user, ref ulong time) - { - var userEndIdx = data.IndexOf('>', StringComparison.Ordinal); - if (userEndIdx < 0) - return; - - var timeEndIdx = data.IndexOf(' ', userEndIdx + 2); - user = User.FindOrAdd(data.Substring(0, userEndIdx)); - time = timeEndIdx < 0 ? 0 : ulong.Parse(data.Substring(userEndIdx + 2, timeEndIdx - userEndIdx - 2)); - } - private static readonly DateTime _utcStart = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).ToLocalTime(); } } diff --git a/src/Models/Stash.cs b/src/Models/Stash.cs index 2376959a..2fab0f2f 100644 --- a/src/Models/Stash.cs +++ b/src/Models/Stash.cs @@ -8,7 +8,6 @@ namespace SourceGit.Models public string Name { get; set; } = ""; public string SHA { get; set; } = ""; - public User Author { get; set; } = User.Invalid; public ulong Time { get; set; } = 0; public string Message { get; set; } = ""; diff --git a/src/Models/User.cs b/src/Models/User.cs index 5a93e135..cb0d21cd 100644 --- a/src/Models/User.cs +++ b/src/Models/User.cs @@ -1,4 +1,5 @@ -using System.Collections.Concurrent; +using System; +using System.Collections.Concurrent; namespace SourceGit.Models { @@ -27,8 +28,8 @@ namespace SourceGit.Models { return _caches.GetOrAdd(data, key => { - var nameEndIdx = key.IndexOf('<', System.StringComparison.Ordinal); - var name = nameEndIdx >= 2 ? key.Substring(0, nameEndIdx - 1) : string.Empty; + var nameEndIdx = key.IndexOf('±', StringComparison.Ordinal); + var name = nameEndIdx > 0 ? key.Substring(0, nameEndIdx) : string.Empty; var email = key.Substring(nameEndIdx + 1); return new User() { Name = name, Email = email }; From 1870dcd46827db7fa1357684f5090a4eee94e92c Mon Sep 17 00:00:00 2001 From: leo Date: Wed, 5 Jun 2024 13:21:01 +0800 Subject: [PATCH 12/44] fix: select item in tree not work --- src/Models/TreeDataGridSelectionModel.cs | 38 +++++++++++++++++------- 1 file changed, 27 insertions(+), 11 deletions(-) diff --git a/src/Models/TreeDataGridSelectionModel.cs b/src/Models/TreeDataGridSelectionModel.cs index b016d739..e45393d0 100644 --- a/src/Models/TreeDataGridSelectionModel.cs +++ b/src/Models/TreeDataGridSelectionModel.cs @@ -42,23 +42,15 @@ namespace SourceGit.Models public void Select(IEnumerable items) { - var sets = new HashSet(); - foreach (var item in items) - sets.Add(item); - using (BatchUpdate()) { Clear(); - int num = _source.Rows.Count; - for (int i = 0; i < num; ++i) + foreach (var selected in items) { - var m = _source.Rows[i].Model as TModel; - if (m != null && sets.Contains(m)) - { - var idx = _source.Rows.RowIndexToModelIndex(i); + var idx = GetModelIndex(_source.Items, selected, IndexPath.Unselected); + if (!idx.Equals(IndexPath.Unselected)) Select(idx); - } } } } @@ -431,6 +423,30 @@ namespace SourceGit.Models return _childrenGetter?.Invoke(node); } + private IndexPath GetModelIndex(IEnumerable collection, TModel model, IndexPath parent) + { + int i = 0; + + foreach (var item in collection) + { + var index = parent.Append(i); + if (item != null && item == model) + return index; + + var children = GetChildren(item); + if (children != null) + { + var findInChildren = GetModelIndex(children, model, index); + if (!findInChildren.Equals(IndexPath.Unselected)) + return findInChildren; + } + + i++; + } + + return IndexPath.Unselected; + } + private bool HasChildren(IRow row) { var children = GetChildren(row.Model as TModel); From 78000b6d1a247283121fa27e802986a9ddeedf60 Mon Sep 17 00:00:00 2001 From: leo Date: Wed, 5 Jun 2024 13:54:52 +0800 Subject: [PATCH 13/44] fix: show a empty commit with sha in submodule diff view if commit has been dropped --- src/ViewModels/DiffContext.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ViewModels/DiffContext.cs b/src/ViewModels/DiffContext.cs index f5aec074..8a10feab 100644 --- a/src/ViewModels/DiffContext.cs +++ b/src/ViewModels/DiffContext.cs @@ -131,12 +131,12 @@ namespace SourceGit.ViewModels if (line.Type == Models.TextDiffLineType.Added) { var sha = line.Content.Substring("Subproject commit ".Length); - submoduleDiff.New = new Commands.QuerySingleCommit(submoduleRoot, sha).Result(); + submoduleDiff.New = new Commands.QuerySingleCommit(submoduleRoot, sha).Result() ?? new Models.Commit() { SHA = sha }; } else if (line.Type == Models.TextDiffLineType.Deleted) { var sha = line.Content.Substring("Subproject commit ".Length); - submoduleDiff.Old = new Commands.QuerySingleCommit(submoduleRoot, sha).Result(); + submoduleDiff.Old = new Commands.QuerySingleCommit(submoduleRoot, sha).Result() ?? new Models.Commit() { SHA = sha }; } } rs = submoduleDiff; From 9aca84533cb629954bec5694f72b2473c5314b1d Mon Sep 17 00:00:00 2001 From: leo Date: Wed, 5 Jun 2024 14:20:01 +0800 Subject: [PATCH 14/44] ux: color scheme for revision compare --- src/Views/RevisionCompare.axaml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Views/RevisionCompare.axaml b/src/Views/RevisionCompare.axaml index 82fd5265..27976d26 100644 --- a/src/Views/RevisionCompare.axaml +++ b/src/Views/RevisionCompare.axaml @@ -20,8 +20,8 @@ IsHitTestVisible="False" User="{Binding StartPoint.Author}"/> - - + + @@ -44,8 +44,8 @@ IsHitTestVisible="False" User="{Binding Author}"/> - - + + @@ -56,8 +56,8 @@ - - + + From 1adf18c724348e49d98d0c41cf180eaeaa7db3c5 Mon Sep 17 00:00:00 2001 From: Gadfly Date: Wed, 5 Jun 2024 17:32:19 +0800 Subject: [PATCH 15/44] fix: remove null-check of sshKey in AddRemote and change command execution order --- src/ViewModels/AddRemote.cs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/ViewModels/AddRemote.cs b/src/ViewModels/AddRemote.cs index 84ca2326..8386b755 100644 --- a/src/ViewModels/AddRemote.cs +++ b/src/ViewModels/AddRemote.cs @@ -79,11 +79,8 @@ namespace SourceGit.ViewModels public static ValidationResult ValidateSSHKey(string sshkey, ValidationContext ctx) { - if (ctx.ObjectInstance is AddRemote add && add._useSSH) + if (ctx.ObjectInstance is AddRemote { _useSSH: true } && !string.IsNullOrEmpty(sshkey)) { - if (string.IsNullOrEmpty(sshkey)) - return new ValidationResult("SSH private key is required"); - if (!File.Exists(sshkey)) return new ValidationResult("Given SSH private key can NOT be found!"); } @@ -102,10 +99,9 @@ namespace SourceGit.ViewModels if (succ) { SetProgressDescription("Fetching from added remote ..."); - new Commands.Fetch(_repo.FullPath, _name, true, SetProgressDescription).Exec(); - - SetProgressDescription("Post processing ..."); new Commands.Config(_repo.FullPath).Set($"remote.{_name}.sshkey", _useSSH ? SSHKey : null); + + new Commands.Fetch(_repo.FullPath, _name, true, SetProgressDescription).Exec(); } CallUIThread(() => { From 62e5ed8a42f871c02b07b23f24de5753fe09014a Mon Sep 17 00:00:00 2001 From: leo Date: Wed, 5 Jun 2024 18:23:28 +0800 Subject: [PATCH 16/44] enhance: supports user overrides the default color schema --- src/App.JsonCodeGen.cs | 4 +++- src/App.axaml.cs | 36 ++++++++++++++++++++++++++----- src/Resources/Locales/en_US.axaml | 1 + src/Resources/Locales/zh_CN.axaml | 1 + src/Resources/Themes.axaml | 2 +- src/ViewModels/Preference.cs | 17 ++++++++++----- src/Views/Preference.axaml | 29 +++++++++++++++++++------ src/Views/Preference.axaml.cs | 17 +++++++++++++++ src/Views/TextDiffView.axaml.cs | 6 +++--- 9 files changed, 91 insertions(+), 22 deletions(-) diff --git a/src/App.JsonCodeGen.cs b/src/App.JsonCodeGen.cs index 7269b252..af2e9913 100644 --- a/src/App.JsonCodeGen.cs +++ b/src/App.JsonCodeGen.cs @@ -1,10 +1,12 @@ -using System.Text.Json.Serialization; +using System.Collections.Generic; +using System.Text.Json.Serialization; namespace SourceGit { [JsonSourceGenerationOptions(WriteIndented = true, IgnoreReadOnlyFields = true, IgnoreReadOnlyProperties = true)] [JsonSerializable(typeof(Models.Version))] [JsonSerializable(typeof(Models.JetBrainsState))] + [JsonSerializable(typeof(Dictionary))] [JsonSerializable(typeof(ViewModels.Preference))] internal partial class JsonCodeGen : JsonSerializerContext { } } diff --git a/src/App.axaml.cs b/src/App.axaml.cs index df491dd0..3dee174f 100644 --- a/src/App.axaml.cs +++ b/src/App.axaml.cs @@ -145,19 +145,44 @@ namespace SourceGit app._activeLocale = targetLocale; } - public static void SetTheme(string theme) + public static void SetTheme(string theme, string colorsFile) { + var app = Current as App; + if (theme.Equals("Light", StringComparison.OrdinalIgnoreCase)) { - Current.RequestedThemeVariant = ThemeVariant.Light; + app.RequestedThemeVariant = ThemeVariant.Light; } else if (theme.Equals("Dark", StringComparison.OrdinalIgnoreCase)) { - Current.RequestedThemeVariant = ThemeVariant.Dark; + app.RequestedThemeVariant = ThemeVariant.Dark; } else { - Current.RequestedThemeVariant = ThemeVariant.Default; + app.RequestedThemeVariant = ThemeVariant.Default; + } + + if (app._colorOverrides != null) + { + app.Resources.MergedDictionaries.Remove(app._colorOverrides); + app._colorOverrides = null; + } + + if (!string.IsNullOrEmpty(colorsFile) && File.Exists(colorsFile)) + { + try + { + var resDic = new ResourceDictionary(); + var schema = JsonSerializer.Deserialize(File.ReadAllText(colorsFile), JsonCodeGen.Default.DictionaryStringString); + foreach (var kv in schema) + resDic[kv.Key] = Color.Parse(kv.Value); + + app.Resources.MergedDictionaries.Add(resDic); + app._colorOverrides = resDic; + } + catch + { + } } } @@ -256,7 +281,7 @@ namespace SourceGit var pref = ViewModels.Preference.Instance; SetLocale(pref.Locale); - SetTheme(pref.Theme); + SetTheme(pref.Theme, pref.ColorOverrides); } public override void OnFrameworkInitializationCompleted() @@ -300,6 +325,7 @@ namespace SourceGit } private ResourceDictionary _activeLocale = null; + private ResourceDictionary _colorOverrides = null; private Models.INotificationReceiver _notificationReceiver = null; } } diff --git a/src/Resources/Locales/en_US.axaml b/src/Resources/Locales/en_US.axaml index a7f470da..830f736a 100644 --- a/src/Resources/Locales/en_US.axaml +++ b/src/Resources/Locales/en_US.axaml @@ -286,6 +286,7 @@ Paste Preference APPEARANCE + Custom Color Schema Default Font Default Font Size Monospace Font diff --git a/src/Resources/Locales/zh_CN.axaml b/src/Resources/Locales/zh_CN.axaml index 6851458b..14f6085d 100644 --- a/src/Resources/Locales/zh_CN.axaml +++ b/src/Resources/Locales/zh_CN.axaml @@ -289,6 +289,7 @@ 粘贴 偏好设置 外观配置 + 自定义配色文件 缺省字体 默认字体大小 等宽字体 diff --git a/src/Resources/Themes.axaml b/src/Resources/Themes.axaml index f102b5f7..cf56f42a 100644 --- a/src/Resources/Themes.axaml +++ b/src/Resources/Themes.axaml @@ -35,7 +35,7 @@ #FF1F1F1F #FF2C2C2C #FF2B2B2B - #FF181818 + #FF1B1B1B #FF8F8F8F #FF505050 #FFF8F8F8 diff --git a/src/ViewModels/Preference.cs b/src/ViewModels/Preference.cs index 7b07b4a7..917349e0 100644 --- a/src/ViewModels/Preference.cs +++ b/src/ViewModels/Preference.cs @@ -65,9 +65,7 @@ namespace SourceGit.ViewModels set { if (SetProperty(ref _locale, value)) - { App.SetLocale(value); - } } } @@ -77,9 +75,17 @@ namespace SourceGit.ViewModels set { if (SetProperty(ref _theme, value)) - { - App.SetTheme(value); - } + App.SetTheme(_theme, _colorOverrides); + } + } + + public string ColorOverrides + { + get => _colorOverrides; + set + { + if (SetProperty(ref _colorOverrides, value)) + App.SetTheme(_theme, value); } } @@ -521,6 +527,7 @@ namespace SourceGit.ViewModels private string _locale = "en_US"; private string _theme = "Default"; + private string _colorOverrides = string.Empty; private FontFamily _defaultFont = null; private FontFamily _monospaceFont = null; private double _defaultFontSize = 13; diff --git a/src/Views/Preference.axaml b/src/Views/Preference.axaml index ab11cca3..ce3e809b 100644 --- a/src/Views/Preference.axaml +++ b/src/Views/Preference.axaml @@ -144,7 +144,7 @@ - + Light - + + + + + + + + - - - - - KnownLayer.Background; @@ -174,7 +174,7 @@ namespace SourceGit.Views public class LineStyleTransformer : DocumentColorizingTransformer { - private static readonly Brush HL_ADDED = new SolidColorBrush(Color.FromArgb(90, 0, 255, 0)); + private static readonly Brush HL_ADDED = new SolidColorBrush(Color.FromArgb(128, 0, 190, 0)); private static readonly Brush HL_DELETED = new SolidColorBrush(Color.FromArgb(80, 255, 0, 0)); public LineStyleTransformer(CombinedTextDiffPresenter editor) From 7b71f8512def9520a85b96da45b6023518a6cca5 Mon Sep 17 00:00:00 2001 From: leo Date: Wed, 5 Jun 2024 18:32:55 +0800 Subject: [PATCH 17/44] ux: re-arrange options in apperance page --- src/App.axaml.cs | 13 +----------- src/ViewModels/AddRemote.cs | 1 - src/Views/Preference.axaml | 42 ++++++++++++++++++------------------- 3 files changed, 22 insertions(+), 34 deletions(-) diff --git a/src/App.axaml.cs b/src/App.axaml.cs index 3dee174f..6d73bfab 100644 --- a/src/App.axaml.cs +++ b/src/App.axaml.cs @@ -132,14 +132,10 @@ namespace SourceGit var app = Current as App; var targetLocale = app.Resources[localeKey] as ResourceDictionary; if (targetLocale == null || targetLocale == app._activeLocale) - { return; - } if (app._activeLocale != null) - { app.Resources.MergedDictionaries.Remove(app._activeLocale); - } app.Resources.MergedDictionaries.Add(targetLocale); app._activeLocale = targetLocale; @@ -150,17 +146,11 @@ namespace SourceGit var app = Current as App; if (theme.Equals("Light", StringComparison.OrdinalIgnoreCase)) - { app.RequestedThemeVariant = ThemeVariant.Light; - } else if (theme.Equals("Dark", StringComparison.OrdinalIgnoreCase)) - { app.RequestedThemeVariant = ThemeVariant.Dark; - } else - { app.RequestedThemeVariant = ThemeVariant.Default; - } if (app._colorOverrides != null) { @@ -173,6 +163,7 @@ namespace SourceGit try { var resDic = new ResourceDictionary(); + var schema = JsonSerializer.Deserialize(File.ReadAllText(colorsFile), JsonCodeGen.Default.DictionaryStringString); foreach (var kv in schema) resDic[kv.Key] = Color.Parse(kv.Value); @@ -191,9 +182,7 @@ namespace SourceGit if (Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) { if (desktop.MainWindow.Clipboard is { } clipbord) - { await clipbord.SetTextAsync(data); - } } } diff --git a/src/ViewModels/AddRemote.cs b/src/ViewModels/AddRemote.cs index 8386b755..296ddfdb 100644 --- a/src/ViewModels/AddRemote.cs +++ b/src/ViewModels/AddRemote.cs @@ -100,7 +100,6 @@ namespace SourceGit.ViewModels { SetProgressDescription("Fetching from added remote ..."); new Commands.Config(_repo.FullPath).Set($"remote.{_name}.sshkey", _useSSH ? SSHKey : null); - new Commands.Fetch(_repo.FullPath, _name, true, SetProgressDescription).Exec(); } CallUIThread(() => diff --git a/src/Views/Preference.axaml b/src/Views/Preference.axaml index ce3e809b..d7bc08a6 100644 --- a/src/Views/Preference.axaml +++ b/src/Views/Preference.axaml @@ -161,27 +161,12 @@ Light - - - - - - - - - - - - - + + + + + + + From 1d7518a32764ec83f206c6b2f9dabbf699d4174f Mon Sep 17 00:00:00 2001 From: leo Date: Wed, 5 Jun 2024 19:18:47 +0800 Subject: [PATCH 18/44] fix: add homebrew to default PATH environment --- build/resources/app/App.plist | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/build/resources/app/App.plist b/build/resources/app/App.plist index 01d93ad7..3ccf7335 100644 --- a/build/resources/app/App.plist +++ b/build/resources/app/App.plist @@ -12,6 +12,11 @@ SOURCE_GIT_VERSION.0 LSMinimumSystemVersion 10.12 + LSEnvironment + + PATH + /opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin + CFBundleExecutable SourceGit CFBundleInfoDictionaryVersion From ce35a0365d5bdda56cfbf3f5b2a2fcceb40c8296 Mon Sep 17 00:00:00 2001 From: leo Date: Wed, 5 Jun 2024 19:56:45 +0800 Subject: [PATCH 19/44] fix: sync scroll not working --- src/Models/DiffResult.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/Models/DiffResult.cs b/src/Models/DiffResult.cs index eafe44ec..71099390 100644 --- a/src/Models/DiffResult.cs +++ b/src/Models/DiffResult.cs @@ -5,6 +5,8 @@ using System.Text.RegularExpressions; using Avalonia; using Avalonia.Media.Imaging; +using CommunityToolkit.Mvvm.ComponentModel; + namespace SourceGit.Models { public enum TextDiffLineType @@ -59,12 +61,17 @@ namespace SourceGit.Models } } - public partial class TextDiff + public partial class TextDiff : ObservableObject { public string File { get; set; } = string.Empty; public List Lines { get; set; } = new List(); public int MaxLineNumber = 0; - public Vector SyncScrollOffset { get; set; } = Vector.Zero; + + private Vector _syncScrollOffset = Vector.Zero; + public Vector SyncScrollOffset { + get => _syncScrollOffset; + set => SetProperty(ref _syncScrollOffset, value); + } public void GenerateNewPatchFromSelection(Change change, string fileBlobGuid, TextDiffSelection selection, bool revert, string output) { From 0c618998b210ca8b489fe463133f7d5a0ffc4180 Mon Sep 17 00:00:00 2001 From: leo Date: Wed, 5 Jun 2024 20:33:33 +0800 Subject: [PATCH 20/44] refactor: sync scroll implement --- src/Models/DiffResult.cs | 11 ++--------- src/Views/TextDiffView.axaml | 6 +++--- src/Views/TextDiffView.axaml.cs | 29 ++++++++++++++++++++++++++++- 3 files changed, 33 insertions(+), 13 deletions(-) diff --git a/src/Models/DiffResult.cs b/src/Models/DiffResult.cs index 71099390..d99c7a0e 100644 --- a/src/Models/DiffResult.cs +++ b/src/Models/DiffResult.cs @@ -5,8 +5,6 @@ using System.Text.RegularExpressions; using Avalonia; using Avalonia.Media.Imaging; -using CommunityToolkit.Mvvm.ComponentModel; - namespace SourceGit.Models { public enum TextDiffLineType @@ -61,17 +59,12 @@ namespace SourceGit.Models } } - public partial class TextDiff : ObservableObject + public partial class TextDiff { public string File { get; set; } = string.Empty; public List Lines { get; set; } = new List(); public int MaxLineNumber = 0; - - private Vector _syncScrollOffset = Vector.Zero; - public Vector SyncScrollOffset { - get => _syncScrollOffset; - set => SetProperty(ref _syncScrollOffset, value); - } + public Vector SyncScrollOffset = Vector.Zero; public void GenerateNewPatchFromSelection(Change change, string fileBlobGuid, TextDiffSelection selection, bool revert, string output) { diff --git a/src/Views/TextDiffView.axaml b/src/Views/TextDiffView.axaml index bef3c878..b4758f06 100644 --- a/src/Views/TextDiffView.axaml +++ b/src/Views/TextDiffView.axaml @@ -17,7 +17,7 @@ SecondaryFG="{DynamicResource Brush.FG2}" FontFamily="{Binding Source={x:Static vm:Preference.Instance}, Path=MonospaceFont}" DiffData="{Binding}" - SyncScrollOffset="{Binding #ThisControl.TextDiff.SyncScrollOffset, Mode=TwoWay}" + SyncScrollOffset="{Binding #ThisControl.SyncScrollOffset, Mode=TwoWay}" UseSyntaxHighlighting="{Binding Source={x:Static vm:Preference.Instance}, Path=UseSyntaxHighlighting}" WordWrap="{Binding Source={x:Static vm:Preference.Instance}, Path=EnableDiffViewWordWrap}"/> @@ -25,7 +25,7 @@ SetValue(UseSideBySideDiffProperty, value); } + public static readonly StyledProperty SyncScrollOffsetProperty = + AvaloniaProperty.Register(nameof(SyncScrollOffset)); + + public Vector SyncScrollOffset + { + get => GetValue(SyncScrollOffsetProperty); + set => SetValue(SyncScrollOffsetProperty, value); + } + public TextDiffView() { InitializeComponent(); @@ -1081,7 +1090,7 @@ namespace SourceGit.Views { base.OnPropertyChanged(change); - if (change.Property == TextDiffProperty || change.Property == UseSideBySideDiffProperty) + if (change.Property == TextDiffProperty) { if (TextDiff == null) { @@ -1090,12 +1099,30 @@ namespace SourceGit.Views else if (UseSideBySideDiff) { Content = new ViewModels.TwoSideTextDiff(TextDiff); + SyncScrollOffset = TextDiff.SyncScrollOffset; } else { Content = TextDiff; + SyncScrollOffset = TextDiff.SyncScrollOffset; } } + else if (change.Property == UseSideBySideDiffProperty) + { + SyncScrollOffset = Vector.Zero; + + if (TextDiff == null) + Content = null; + else if (UseSideBySideDiff) + Content = new ViewModels.TwoSideTextDiff(TextDiff); + else + Content = TextDiff; + } + else if (change.Property == SyncScrollOffsetProperty) + { + if (TextDiff != null) + TextDiff.SyncScrollOffset = SyncScrollOffset; + } } private Models.TextDiffSelection GetUnifiedSelection(int startLine, int endLine, bool isOldSide) From 7836f57904ff95fb530b698c77dffd6b13b5c7cc Mon Sep 17 00:00:00 2001 From: "Chuanyan.Chen" Date: Wed, 5 Jun 2024 17:24:03 +0800 Subject: [PATCH 21/44] ux: get brush color for LineBackgrounRenderer from themes --- src/Resources/Themes.axaml | 15 ++++ src/Views/TextDiffView.axaml | 15 ++++ src/Views/TextDiffView.axaml.cs | 126 +++++++++++++++++++++++++------- 3 files changed, 131 insertions(+), 25 deletions(-) diff --git a/src/Resources/Themes.axaml b/src/Resources/Themes.axaml index cf56f42a..f3c72a36 100644 --- a/src/Resources/Themes.axaml +++ b/src/Resources/Themes.axaml @@ -25,6 +25,11 @@ #FF1F1F1F #FF6F6F6F #FFFFFFFF + #3C000000 + #3C00FF00 + #3CFF0000 + #5A00FF00 + #50FF0000 @@ -51,6 +56,11 @@ #FFDDDDDD #40F1F1F1 #FF252525 + #FF424242 + #FF38573C + #FF633F3E + #FF388442 + #FF9F4247 @@ -79,4 +89,9 @@ + + + + + diff --git a/src/Views/TextDiffView.axaml b/src/Views/TextDiffView.axaml index bef3c878..902db491 100644 --- a/src/Views/TextDiffView.axaml +++ b/src/Views/TextDiffView.axaml @@ -13,6 +13,11 @@ KnownLayer.Background; public LineBackgroundRenderer(CombinedTextDiffPresenter editor) @@ -139,7 +135,7 @@ namespace SourceGit.Views { if (line.FirstDocumentLine == null) continue; - + var index = line.FirstDocumentLine.LineNumber; if (index > _editor.DiffData.Lines.Count) break; @@ -159,11 +155,11 @@ namespace SourceGit.Views switch (type) { case Models.TextDiffLineType.None: - return BG_EMPTY; + return _editor.LineBGEmpty; case Models.TextDiffLineType.Added: - return BG_ADDED; + return _editor.LineBGAdd; case Models.TextDiffLineType.Deleted: - return BG_DELETED; + return _editor.LineBGDeleted; default: return null; } @@ -174,9 +170,6 @@ namespace SourceGit.Views public class LineStyleTransformer : DocumentColorizingTransformer { - private static readonly Brush HL_ADDED = new SolidColorBrush(Color.FromArgb(128, 0, 190, 0)); - private static readonly Brush HL_DELETED = new SolidColorBrush(Color.FromArgb(80, 255, 0, 0)); - public LineStyleTransformer(CombinedTextDiffPresenter editor) { _editor = editor; @@ -202,7 +195,7 @@ namespace SourceGit.Views if (info.Highlights.Count > 0) { - var bg = info.Type == Models.TextDiffLineType.Added ? HL_ADDED : HL_DELETED; + var bg = info.Type == Models.TextDiffLineType.Added ? _editor.SecondaryLineBGAdd : _editor.SecondaryLineBGDeleted; foreach (var highlight in info.Highlights) { ChangeLinePart(line.Offset + highlight.Start, line.Offset + highlight.Start + highlight.Count, v => @@ -225,6 +218,51 @@ namespace SourceGit.Views set => SetValue(DiffDataProperty, value); } + public static readonly StyledProperty LineBGEmptyProperty = + AvaloniaProperty.Register(nameof(LineBGEmpty), new SolidColorBrush(Color.FromArgb(60, 0, 0, 0))); + + public IBrush LineBGEmpty + { + get => GetValue(LineBGEmptyProperty); + set => SetValue(LineBGEmptyProperty, value); + } + + public static readonly StyledProperty LineBGAddProperty = + AvaloniaProperty.Register(nameof(LineBGAdd), new SolidColorBrush(Color.FromArgb(60, 0, 255, 0))); + + public IBrush LineBGAdd + { + get => GetValue(LineBGAddProperty); + set => SetValue(LineBGAddProperty, value); + } + + public static readonly StyledProperty LineBGDeletedProperty = + AvaloniaProperty.Register(nameof(LineBGDeleted), new SolidColorBrush(Color.FromArgb(60, 255, 0, 0))); + + public IBrush LineBGDeleted + { + get => GetValue(LineBGDeletedProperty); + set => SetValue(LineBGDeletedProperty, value); + } + + public static readonly StyledProperty SecondaryLineBGAddProperty = + AvaloniaProperty.Register(nameof(SecondaryLineBGAdd), new SolidColorBrush(Color.FromArgb(90, 0, 255, 0))); + + public IBrush SecondaryLineBGAdd + { + get => GetValue(SecondaryLineBGAddProperty); + set => SetValue(SecondaryLineBGAddProperty, value); + } + + public static readonly StyledProperty SecondaryLineBGDeletedProperty = + AvaloniaProperty.Register(nameof(SecondaryLineBGDeleted), new SolidColorBrush(Color.FromArgb(80, 255, 0, 0))); + + public IBrush SecondaryLineBGDeleted + { + get => GetValue(SecondaryLineBGDeletedProperty); + set => SetValue(SecondaryLineBGDeletedProperty, value); + } + public static readonly StyledProperty SecondaryFGProperty = AvaloniaProperty.Register(nameof(SecondaryFG), Brushes.Gray); @@ -499,10 +537,6 @@ namespace SourceGit.Views public class LineBackgroundRenderer : IBackgroundRenderer { - private static readonly Brush BG_EMPTY = new SolidColorBrush(Color.FromArgb(60, 0, 0, 0)); - private static readonly Brush BG_ADDED = new SolidColorBrush(Color.FromArgb(60, 0, 255, 0)); - private static readonly Brush BG_DELETED = new SolidColorBrush(Color.FromArgb(60, 255, 0, 0)); - public KnownLayer Layer => KnownLayer.Background; public LineBackgroundRenderer(SingleSideTextDiffPresenter editor) @@ -521,7 +555,7 @@ namespace SourceGit.Views { if (line.FirstDocumentLine == null) continue; - + var index = line.FirstDocumentLine.LineNumber; if (index > infos.Count) break; @@ -541,11 +575,11 @@ namespace SourceGit.Views switch (type) { case Models.TextDiffLineType.None: - return BG_EMPTY; + return _editor.LineBGEmpty; case Models.TextDiffLineType.Added: - return BG_ADDED; + return _editor.LineBGAdd; case Models.TextDiffLineType.Deleted: - return BG_DELETED; + return _editor.LineBGDeleted; default: return null; } @@ -556,9 +590,6 @@ namespace SourceGit.Views public class LineStyleTransformer : DocumentColorizingTransformer { - private static readonly Brush HL_ADDED = new SolidColorBrush(Color.FromArgb(90, 0, 255, 0)); - private static readonly Brush HL_DELETED = new SolidColorBrush(Color.FromArgb(80, 255, 0, 0)); - public LineStyleTransformer(SingleSideTextDiffPresenter editor) { _editor = editor; @@ -585,7 +616,7 @@ namespace SourceGit.Views if (info.Highlights.Count > 0) { - var bg = info.Type == Models.TextDiffLineType.Added ? HL_ADDED : HL_DELETED; + var bg = info.Type == Models.TextDiffLineType.Added ? _editor.LineBGAdd : _editor.LineBGDeleted; foreach (var highlight in info.Highlights) { ChangeLinePart(line.Offset + highlight.Start, line.Offset + highlight.Start + highlight.Count, v => @@ -617,6 +648,51 @@ namespace SourceGit.Views set => SetValue(DiffDataProperty, value); } + public static readonly StyledProperty LineBGEmptyProperty = + AvaloniaProperty.Register(nameof(LineBGEmpty), new SolidColorBrush(Color.FromArgb(60, 0, 0, 0))); + + public IBrush LineBGEmpty + { + get => GetValue(LineBGEmptyProperty); + set => SetValue(LineBGEmptyProperty, value); + } + + public static readonly StyledProperty LineBGAddProperty = + AvaloniaProperty.Register(nameof(LineBGAdd), new SolidColorBrush(Color.FromArgb(60, 0, 255, 0))); + + public IBrush LineBGAdd + { + get => GetValue(LineBGAddProperty); + set => SetValue(LineBGAddProperty, value); + } + + public static readonly StyledProperty LineBGDeletedProperty = + AvaloniaProperty.Register(nameof(LineBGDeleted), new SolidColorBrush(Color.FromArgb(60, 255, 0, 0))); + + public IBrush LineBGDeleted + { + get => GetValue(LineBGDeletedProperty); + set => SetValue(LineBGDeletedProperty, value); + } + + public static readonly StyledProperty SecondaryLineBGAddProperty = + AvaloniaProperty.Register(nameof(SecondaryLineBGAdd), new SolidColorBrush(Color.FromArgb(90, 0, 255, 0))); + + public IBrush SecondaryLineBGAdd + { + get => GetValue(SecondaryLineBGAddProperty); + set => SetValue(SecondaryLineBGAddProperty, value); + } + + public static readonly StyledProperty SecondaryLineBGDeletedProperty = + AvaloniaProperty.Register(nameof(SecondaryLineBGDeleted), new SolidColorBrush(Color.FromArgb(80, 255, 0, 0))); + + public IBrush SecondaryLineBGDeleted + { + get => GetValue(SecondaryLineBGDeletedProperty); + set => SetValue(SecondaryLineBGDeletedProperty, value); + } + public static readonly StyledProperty SecondaryFGProperty = AvaloniaProperty.Register(nameof(SecondaryFG), Brushes.Gray); @@ -736,7 +812,7 @@ namespace SourceGit.Views }; menu.Items.Add(copy); - + TextArea.TextView.OpenContextMenu(menu); e.Handled = true; } From 87dccda8ff9ebda0d0ba9979c483f8d67b783121 Mon Sep 17 00:00:00 2001 From: leo Date: Wed, 5 Jun 2024 21:04:19 +0800 Subject: [PATCH 22/44] fix: solve warnings - Layout cycle detected --- src/Views/ChangeCollectionView.axaml.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Views/ChangeCollectionView.axaml.cs b/src/Views/ChangeCollectionView.axaml.cs index 05e418c1..07623696 100644 --- a/src/Views/ChangeCollectionView.axaml.cs +++ b/src/Views/ChangeCollectionView.axaml.cs @@ -85,7 +85,7 @@ namespace SourceGit.Views Content = null; var changes = Changes; - if (changes == null) + if (changes == null || changes.Count == 0) return; var viewMode = ViewMode; From 2c0a58a99be008cd2b43f3802a08c5609a146cf7 Mon Sep 17 00:00:00 2001 From: leo Date: Wed, 5 Jun 2024 21:11:44 +0800 Subject: [PATCH 23/44] ux: default color scheme with high contrast --- src/Resources/Themes.axaml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Resources/Themes.axaml b/src/Resources/Themes.axaml index f3c72a36..36889bca 100644 --- a/src/Resources/Themes.axaml +++ b/src/Resources/Themes.axaml @@ -56,11 +56,11 @@ #FFDDDDDD #40F1F1F1 #FF252525 - #FF424242 - #FF38573C - #FF633F3E - #FF388442 - #FF9F4247 + #3C000000 + #3C00FF00 + #3CFF0000 + #5A00FF00 + #50FF0000 From e432559a5e1c9157a838ce55ea16857257c070b9 Mon Sep 17 00:00:00 2001 From: leo Date: Thu, 6 Jun 2024 09:53:26 +0800 Subject: [PATCH 24/44] enhance: only change the selected changes if it is necessary --- src/Views/ChangeCollectionView.axaml.cs | 40 +++++++++++++++++++------ 1 file changed, 31 insertions(+), 9 deletions(-) diff --git a/src/Views/ChangeCollectionView.axaml.cs b/src/Views/ChangeCollectionView.axaml.cs index 07623696..5d0694c0 100644 --- a/src/Views/ChangeCollectionView.axaml.cs +++ b/src/Views/ChangeCollectionView.axaml.cs @@ -116,9 +116,7 @@ namespace SourceGit.Views foreach (var c in model.SelectedItems) CollectChangesInNode(selected, c); - _isSelecting = true; - SetCurrentValue(SelectedChangesProperty, selected); - _isSelecting = false; + TrySetSelected(selected); } }; @@ -144,9 +142,7 @@ namespace SourceGit.Views foreach (var c in model.SelectedItems) selected.Add(c); - _isSelecting = true; - SetCurrentValue(SelectedChangesProperty, selected); - _isSelecting = false; + TrySetSelected(selected); } }; @@ -172,9 +168,7 @@ namespace SourceGit.Views foreach (var c in model.SelectedItems) selected.Add(c); - _isSelecting = true; - SetCurrentValue(SelectedChangesProperty, selected); - _isSelecting = false; + TrySetSelected(selected); } }; @@ -269,6 +263,34 @@ namespace SourceGit.Views } } + private void TrySetSelected(List changes) + { + var old = SelectedChanges; + + if (old == null && changes.Count == 0) + return; + + if (old != null && old.Count == changes.Count) + { + bool allEquals = true; + foreach (var c in old) + { + if (!changes.Contains(c)) + { + allEquals = false; + break; + } + } + + if (allEquals) + return; + } + + _isSelecting = true; + SetCurrentValue(SelectedChangesProperty, changes); + _isSelecting = false; + } + private bool _isSelecting = false; } } From 54ef9c0bf7f1b6e9fb9260ff294aa18ba0edd156 Mon Sep 17 00:00:00 2001 From: leo Date: Thu, 6 Jun 2024 10:01:14 +0800 Subject: [PATCH 25/44] refactor: use bindings instead of sync manually --- src/Models/DiffResult.cs | 2 +- src/Views/DiffView.axaml | 1 + src/Views/TextDiffView.axaml.cs | 13 ------------- 3 files changed, 2 insertions(+), 14 deletions(-) diff --git a/src/Models/DiffResult.cs b/src/Models/DiffResult.cs index d99c7a0e..3cc33f59 100644 --- a/src/Models/DiffResult.cs +++ b/src/Models/DiffResult.cs @@ -63,8 +63,8 @@ namespace SourceGit.Models { public string File { get; set; } = string.Empty; public List Lines { get; set; } = new List(); + public Vector SyncScrollOffset { get; set; } = Vector.Zero; public int MaxLineNumber = 0; - public Vector SyncScrollOffset = Vector.Zero; public void GenerateNewPatchFromSelection(Change change, string fileBlobGuid, TextDiffSelection selection, bool revert, string output) { diff --git a/src/Views/DiffView.axaml b/src/Views/DiffView.axaml index adffabda..96af72cb 100644 --- a/src/Views/DiffView.axaml +++ b/src/Views/DiffView.axaml @@ -224,6 +224,7 @@ diff --git a/src/Views/TextDiffView.axaml.cs b/src/Views/TextDiffView.axaml.cs index fc1bc211..0edfd47f 100644 --- a/src/Views/TextDiffView.axaml.cs +++ b/src/Views/TextDiffView.axaml.cs @@ -1169,19 +1169,11 @@ namespace SourceGit.Views if (change.Property == TextDiffProperty) { if (TextDiff == null) - { Content = null; - } else if (UseSideBySideDiff) - { Content = new ViewModels.TwoSideTextDiff(TextDiff); - SyncScrollOffset = TextDiff.SyncScrollOffset; - } else - { Content = TextDiff; - SyncScrollOffset = TextDiff.SyncScrollOffset; - } } else if (change.Property == UseSideBySideDiffProperty) { @@ -1194,11 +1186,6 @@ namespace SourceGit.Views else Content = TextDiff; } - else if (change.Property == SyncScrollOffsetProperty) - { - if (TextDiff != null) - TextDiff.SyncScrollOffset = SyncScrollOffset; - } } private Models.TextDiffSelection GetUnifiedSelection(int startLine, int endLine, bool isOldSide) From 8b1f28ac950603aef5e33dc35ddc15b10c2e4177 Mon Sep 17 00:00:00 2001 From: leo Date: Thu, 6 Jun 2024 10:36:17 +0800 Subject: [PATCH 26/44] enhance: show file size change in image diff --- src/Models/DiffResult.cs | 7 +++++-- src/ViewModels/DiffContext.cs | 18 ++++++++++++------ src/Views/DiffView.axaml | 14 +++++++++----- 3 files changed, 26 insertions(+), 13 deletions(-) diff --git a/src/Models/DiffResult.cs b/src/Models/DiffResult.cs index 3cc33f59..64ec2393 100644 --- a/src/Models/DiffResult.cs +++ b/src/Models/DiffResult.cs @@ -570,8 +570,11 @@ namespace SourceGit.Models public Bitmap Old { get; set; } = null; public Bitmap New { get; set; } = null; - public string OldSize => Old != null ? $"{Old.PixelSize.Width} x {Old.PixelSize.Height}" : "0 x 0"; - public string NewSize => New != null ? $"{New.PixelSize.Width} x {New.PixelSize.Height}" : "0 x 0"; + public long OldFileSize { get; set; } = 0; + public long NewFileSize { get; set; } = 0; + + public string OldImageSize => Old != null ? $"{Old.PixelSize.Width} x {Old.PixelSize.Height}" : "0 x 0"; + public string NewImageSize => New != null ? $"{New.PixelSize.Width} x {New.PixelSize.Height}" : "0 x 0"; } public class NoOrEOLChange diff --git a/src/ViewModels/DiffContext.cs b/src/ViewModels/DiffContext.cs index 8a10feab..4ff80c23 100644 --- a/src/ViewModels/DiffContext.cs +++ b/src/ViewModels/DiffContext.cs @@ -157,14 +157,19 @@ namespace SourceGit.ViewModels var imgDiff = new Models.ImageDiff(); if (_option.Revisions.Count == 2) { - imgDiff.Old = BitmapFromRevisionFile(_repo, _option.Revisions[0], oldPath); - imgDiff.New = BitmapFromRevisionFile(_repo, _option.Revisions[1], oldPath); + (imgDiff.Old, imgDiff.OldFileSize) = BitmapFromRevisionFile(_repo, _option.Revisions[0], oldPath); + (imgDiff.New, imgDiff.NewFileSize) = BitmapFromRevisionFile(_repo, _option.Revisions[1], oldPath); } else { var fullPath = Path.Combine(_repo, _option.Path); - imgDiff.Old = BitmapFromRevisionFile(_repo, "HEAD", oldPath); - imgDiff.New = File.Exists(fullPath) ? new Bitmap(fullPath) : null; + (imgDiff.Old, imgDiff.OldFileSize) = BitmapFromRevisionFile(_repo, "HEAD", oldPath); + + if (File.Exists(fullPath)) + { + imgDiff.New = new Bitmap(fullPath); + imgDiff.NewFileSize = new FileInfo(fullPath).Length; + } } rs = imgDiff; } @@ -207,10 +212,11 @@ namespace SourceGit.ViewModels }); } - private Bitmap BitmapFromRevisionFile(string repo, string revision, string file) + private (Bitmap, long) BitmapFromRevisionFile(string repo, string revision, string file) { var stream = Commands.QueryFileContent.Run(repo, revision, file); - return stream.Length > 0 ? new Bitmap(stream) : null; + var size = stream.Length; + return size > 0 ? (new Bitmap(stream), size) : (null, size); } private static readonly HashSet IMG_EXTS = new HashSet() diff --git a/src/Views/DiffView.axaml b/src/Views/DiffView.axaml index 96af72cb..17bba0f3 100644 --- a/src/Views/DiffView.axaml +++ b/src/Views/DiffView.axaml @@ -177,18 +177,22 @@ - - + + - + + + - + - + + + From 2099f8e17e36859dcc64f6432a39a0bbfc947fb5 Mon Sep 17 00:00:00 2001 From: owen Date: Thu, 6 Jun 2024 10:41:22 +0800 Subject: [PATCH 27/44] feat: add support for Traditional Chinese (zh_TW) i18n --- README.md | 2 +- src/App.axaml | 1 + src/Resources/Locales/zh_TW.axaml | 497 ++++++++++++++++++++++++++++++ 3 files changed, 499 insertions(+), 1 deletion(-) create mode 100644 src/Resources/Locales/zh_TW.axaml diff --git a/README.md b/README.md index 63bbe45f..ace7901c 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ Opensource Git GUI client. * Supports Windows/macOS/Linux * Opensource/Free * Fast -* English/简体中文 +* English/简体中文/繁體中文 * Built-in light/dark themes * Visual commit graph * Supports SSH access with each remote diff --git a/src/App.axaml b/src/App.axaml index fff2ca0d..d73ea45d 100644 --- a/src/App.axaml +++ b/src/App.axaml @@ -13,6 +13,7 @@ + diff --git a/src/Resources/Locales/zh_TW.axaml b/src/Resources/Locales/zh_TW.axaml new file mode 100644 index 00000000..ba579a32 --- /dev/null +++ b/src/Resources/Locales/zh_TW.axaml @@ -0,0 +1,497 @@ + + + + + 關於軟體 + 關於本軟體 + • 專案依賴於 + © 2024 sourcegit-scm + • 文字編輯器使用 + • 等寬字型來自於 + • 專案原始碼地址 + 開源免費的Git客戶端 + 應用補丁(apply) + 錯誤 + 輸出錯誤,並終止應用補丁 + 更多錯誤 + 與【錯誤】級別相似,但輸出內容更多 + 補丁檔案 : + 選擇補丁檔案 + 忽略空白符號 + 忽略 + 關閉所有警告 + 應用補丁 + 警告 + 應用補丁,輸出關於空白符的警告 + 空白符號處理 : + 存檔(archive) ... + 存檔檔案路徑: + 選擇存檔檔案的存放路徑 + 指定的提交: + 存檔 + 不跟蹤更改的檔案 + 沒有不跟蹤更改的檔案 + 移除 + 二進位制檔案不支援該操作!!! + 逐行追溯(blame) + 選中檔案不支援該操作!!! + 檢出(checkout)${0}$ + 與當前HEAD比較 + 與本地工作樹比較 + 複製分支名 + 刪除${0}$ + 刪除選中的 {0} 個分支 + 放棄所有更改 + 快進(fast-forward)到${0}$ + GIT工作流 - 完成${0}$ + 合併${0}$到${1}$ + 拉回(pull)${0}$ + 拉回(pull)${0}$內容至${1}$ + 推送(push)${0}$ + 變基(rebase)${0}$分支至${1}$ + 重新命名${0}$ + 切換上游分支... + 取消追蹤 + 位元組 + 取 消 + 切換變更顯示模式 + 檔名+路徑列表模式 + 全路徑列表模式 + 檔案目錄樹形結構模式 + 檢出(checkout)分支 + 檢出(checkout)提交 + 注意:執行該操作後,當前HEAD會變為遊離(detached)狀態! + 提交 : + 目標分支 : + 未提交更改 : + 丟棄更改 + 不做處理 + 儲藏並自動恢復 + 挑選(cherry-pick)此提交 + 提交ID : + 提交變化 + 挑選提交 + 丟棄儲藏確認 + 您正在丟棄所有的儲藏,一經操作,無法回退,是否繼續? + 克隆遠端倉庫 + 額外引數 : + 其他克隆引數,選填。 + 本地倉庫名 : + 本地倉庫目錄的名字,選填。 + 父級目錄 : + 遠端倉庫 : + 關閉 + 挑選(cherry-pick)此提交 + 檢出此提交 + 與當前HEAD比較 + 與本地工作樹比較 + 複製提交指紋 + 變基(rebase)${0}$到此處 + 重置(reset)${0}$到此處 + 回滾此提交 + 編輯提交資訊 + 另存為補丁 ... + 合併此提交到上一個提交 + 變更對比 + 查詢變更... + 檔案列表 + LFS檔案 + 查詢檔案... + 子模組 + 標籤檔案 + 子樹 + 基本資訊 + 修改者 + 變更列表 + 提交者 + 提交資訊 + 父提交 + 相關引用 + 提交指紋 + 倉庫配置 + 電子郵箱 + 郵箱地址 + HTTP代理 + HTTP網路代理 + 使用者名稱 + 應用於本倉庫的使用者名稱 + 複製 + 複製路徑 + 複製檔名 + 新建分支 + 新分支基於 : + 完成後切換到新分支 + 未提交更改 : + 丟棄更改 + 不做處理 + 儲藏並自動恢復 + 新分支名 : + 填寫分支名稱。 + 建立本地分支 + 新建標籤 + 標籤位於 : + 使用GPG簽名 + 標籤描述 : + 選填。 + 標籤名 : + 推薦格式 :v1.0.0-alpha + 推送到所有遠端倉庫 + 型別 : + 附註標籤 + 輕量標籤 + 剪下 + 刪除分支確認 + 分支名 : + 您正在刪除遠端上的分支,請務必小心!!! + 同時刪除遠端分支${0}$ + 刪除多個分支 + 您正在嘗試一次性刪除多個分支,請務必仔細檢查後再執行操作! + 刪除遠端確認 + 遠端名 : + 目標 : + 刪除分組確認 + 刪除倉庫確認 + 刪除子模組確認 + 子模組路徑 : + 刪除標籤確認 + 標籤名 : + 同時刪除遠端倉庫中的此標籤 + 二進位制檔案 + 當前大小 + 原始大小 + 複製 + 檔案許可權已變化 + LFS物件變更 + 下一個差異 + 沒有變更或僅有換行符差異 + 上一個差異 + 分列對比 + 子模組 + 新增 + 語法高亮 + 使用外部合併工具檢視 + 減少可見的行數 + 增加可見的行數 + 請選擇需要對比的檔案 + 使用外部比對工具檢視 + 放棄更改確認 + 所有本地址未提交的修改。 + 需要放棄的變更 : + 總計{0}項選中更改 + 本操作不支援回退,請確認後繼續!!! + 書籤 : + 名稱 : + 目標 : + 編輯分組 + 編輯倉庫 + 快進(fast-forward,無需checkout) + 拉取(fetch) + 拉取所有的遠端倉庫 + 自動清理遠端已刪除分支 + 遠端倉庫 : + 拉取遠端倉庫內容 + 不跟蹤此檔案的更改 + 放棄更改... + 放棄 {0} 個檔案的更改... + 放棄選中的更改 + 使用外部合併工具開啟 + 另存為補丁... + 暫存(add)... + 暫存(add){0} 個檔案... + 暫存選中的更改 + 儲藏(stash)... + 儲藏(stash)選中的 {0} 個檔案... + 從暫存中移除 + 從暫存中移除 {0} 個檔案 + 從暫存中移除選中的更改 + 使用 THEIRS (checkout --theirs) + 使用 MINE (checkout --ours) + 檔案歷史 + 過濾 + GIT工作流 + 開發分支 : + 特性分支 : + 特性分支名字首 : + 結束特性分支 + 結束脩復分支 + 結束版本分支 + 目標分支 : + 修復分支 : + 修復分支名字首 : + 初始化GIT工作流 + 保留分支 + 釋出分支 : + 版本分支 : + 版本分支名字首 : + 開始特性分支... + 開始特性分支 + 開始修復分支... + 開始修復分支 + 輸入分支名 + 開始版本分支... + 開始版本分支 + 版本標籤字首 : + 歷史記錄 + 切換橫向/縱向顯示 + 切換曲線/折線顯示 + 查詢提交指紋、資訊、作者。回車鍵開始,ESC鍵取消 + 清空 + 已選中 {0} 項提交 + 快捷鍵參考 + 全域性快捷鍵 + 取消彈出面板 + 關閉當前頁面 + 切換到上一個頁面 + 切換到下一個頁面 + 新建頁面 + 開啟偏好設定面板 + 倉庫頁面快捷鍵 + 重新載入倉庫狀態 + 將選中的變更暫存或從暫存列表中移除 + 開啟歷史搜尋 + 顯示本地更改 + 顯示歷史記錄 + 顯示儲藏列表 + 文字編輯器 + 關閉搜尋 + 定位到下一個匹配搜尋的位置 + 定位到上一個匹配搜尋的位置 + 開啟搜尋 + 初始化新倉庫 + 路徑 : + 選擇目錄不是有效的Git倉庫。是否需要在此目錄執行`git init`操作? + 挑選(Cherry-Pick)操作進行中。點選【終止】回滾到操作前的狀態。 + 合併操作進行中。點選【終止】回滾到操作前的狀態。 + 變基(Rebase)操作進行中。點選【終止】回滾到操作前的狀態。 + 回滾提交操作進行中。點選【終止】回滾到操作前的狀態。 + Source Git + 出錯了 + 系統提示 + 主選單 + 合併分支 + 目標分支 : + 合併方式 : + 合併分支 : + 名稱 : + GIT尚未配置。請開啟【偏好設定】配置GIT路徑。 + 系統提示 + 選擇資料夾 + 開啟檔案... + 選填。 + 新建空白頁 + 設定書籤 + 關閉標籤頁 + 關閉其他標籤頁 + 關閉右側標籤頁 + 複製倉庫路徑 + 新標籤頁 + 貼上 + 偏好設定 + 外觀配置 + 預設字型 + 預設字型大小 + 等寬字型 + 主題 + 通用配置 + 頭像服務 + 啟動時檢測軟體更新 + 顯示語言 + 最大歷史提交數 + 啟動時恢復上次開啟的倉庫 + 使用固定寬度的標題欄標籤 + GIT配置 + 啟用定時自動拉取遠端更新 + 自動拉取間隔 + 分鐘 + 自動換行轉換 + 預設克隆路徑 + 郵箱 + 預設GIT使用者郵箱 + 安裝路徑 + 終端Shell + 使用者名稱 + 預設GIT使用者名稱 + Git 版本 + 本軟體要求GIT最低版本為2.23.0 + GPG簽名 + 啟用提交簽名 + 啟用標籤簽名 + GPG簽名格式 + 可執行檔案位置 + gpg.exe所在路徑 + 使用者簽名KEY + 輸入簽名提交所使用的KEY + 外部合併工具 + 對比模式啟動引數 + 合併模式啟動引數 + 安裝路徑 + 填寫工具可執行檔案所在位置 + 工具 + 拉回(pull) + 拉取分支 : + 本地分支 : + 未提交更改 : + 丟棄更改 + 不做處理 + 儲藏並自動恢復 + 遠端 : + 拉回(拉取併合並) + 使用變基方式合併分支 + 推送(push) + 啟用強制推送 + 本地分支 : + 遠端倉庫 : + 推送到遠端倉庫 + 遠端分支 : + 跟蹤遠端分支 + 同時推送標籤 + 推送標籤到遠端倉庫 + 推送到所有遠端倉庫 + 遠端倉庫 : + 標籤 : + 退出 + 變基(rebase)操作 + 自動儲藏並恢復本地變更 + 目標提交 : + 分支 : + 重新載入 + 新增遠端倉庫 + 編輯遠端倉庫 + 遠端名 : + 唯一遠端名 + 倉庫地址 : + 遠端倉庫的地址 + 複製遠端地址 + 刪除 ... + 編輯 ... + 拉取(fetch)更新 ... + 清理遠端已刪除分支 + 目標 : + 分支重新命名 + 新的名稱 : + 新的分支名不能與現有分支名相同 + 分支 : + 終止合併 + 清理本倉庫(GC) + 本操作將執行`gc`,對於啟用LFS的倉庫也會執行`lfs prune`。 + 配置本倉庫 + 下一步 + 在檔案瀏覽器中開啟 + 過濾顯示分支 + 本地分支 + 定位HEAD + 新建分支 + 在 {0} 中開啟 + 使用外部工具開啟 + 重新載入 + 遠端列表 + 新增遠端 + 解決衝突 + 查詢提交 + 支援搜尋作者/提交者/主題/指紋 + 提交統計 + 子模組列表 + 新增子模組 + 更新子模組 + 標籤列表 + 新建標籤 + 在終端中開啟 + 工作區 + 遠端倉庫地址 + 重置(reset)當前分支到指定版本 + 重置模式 : + 提交 : + 當前分支 : + 在檔案瀏覽器中檢視 + 回滾操作確認 + 目標提交 : + 回滾後提交更改 + 編輯提交資訊 + 提交資訊: + 提交: + 執行操作中,請耐心等待... + 保 存 + 另存為... + 補丁已成功儲存! + 檢測更新... + 檢測到軟體有版本更新: + 獲取最新版本資訊失敗! + 下 載 + 忽略此版本 + 軟體更新 + 當前已是最新版本。 + 合併HEAD到上一個提交 + 當前提交 : + 修改提交資訊: + 合併到 : + SSH金鑰 : + SSH金鑰檔案 + 開 始 + 儲藏(stash) + 包含未跟蹤的檔案 + 資訊 : + 選填,用於命名此儲藏 + 儲藏本地變更 + 應用(apply) + 刪除(drop) + 應用並刪除(pop) + 丟棄儲藏確認 + 丟棄儲藏 : + 儲藏列表 + 檢視變更 + 儲藏列表 + 提交統計 + 提交次數 + 提交者 + 本月 + 本週 + 本年 + 提交次數: + 提交者: + 子模組 + 新增子模組 + 複製路徑 + 拉取子孫模組 + 開啟倉庫 + 相對倉庫路徑 : + 本地存放的相對路徑。 + 刪除子模組 + 確 定 + 複製標籤名 + 刪除${0}$ + 推送${0}$ + 倉庫地址 : + 警告 + 新建分組 + 新建子分組 + 克隆遠端倉庫 + 刪除 + 支援拖放目錄新增。支援自定義分組。 + 編輯 + 開啟本地倉庫 + 開啟終端 + 快速查詢倉庫... + 排序 + 本地更改 + 修補(--amend) + 現在您已可將其加入暫存區中 + 提交 + 提交併推送 + 填寫提交資訊 + CTRL + Enter + 檢測到衝突 + 檔案衝突已解決 + 最近輸入的提交資訊 + 顯示未跟蹤檔案 + 歷史提交資訊 + 沒有提交資訊記錄 + 已暫存 + 從暫存區移除選中 + 從暫存區移除所有 + 未暫存 + 暫存選中 + 暫存所有 + 檢視忽略變更檔案 + 請選中衝突檔案,開啟右鍵選單,選擇合適的解決方式 + 本地工作樹 + From cf02c890bf8fa743a32b44b32edf3c7ec113532c Mon Sep 17 00:00:00 2001 From: leo Date: Thu, 6 Jun 2024 11:09:28 +0800 Subject: [PATCH 28/44] ux: re-design the image diff view --- src/Views/DiffView.axaml | 77 ++++++++++++++++++++++------------------ 1 file changed, 42 insertions(+), 35 deletions(-) diff --git a/src/Views/DiffView.axaml b/src/Views/DiffView.axaml index 17bba0f3..713e4338 100644 --- a/src/Views/DiffView.axaml +++ b/src/Views/DiffView.axaml @@ -176,52 +176,59 @@ - - - + + + - - - + + + - + - - - + + + - - - + + + + + - - - - 0,0,0,4 - 0 - 0 - 8 - 16 - 16 - - + + + + + + + + 0,0,0,4 + 0 + 0 + 8 + 16 + 16 + + + From 7201f71e4ba94ef035a18b4ae0cdaef745e45af7 Mon Sep 17 00:00:00 2001 From: leo Date: Thu, 6 Jun 2024 11:15:22 +0800 Subject: [PATCH 29/44] feature: add Traditional Chinese option (#172) --- src/Models/Locales.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Models/Locales.cs b/src/Models/Locales.cs index 022da0fe..cbb1e7f1 100644 --- a/src/Models/Locales.cs +++ b/src/Models/Locales.cs @@ -10,6 +10,7 @@ namespace SourceGit.Models public static readonly List Supported = new List() { new Locale("English", "en_US"), new Locale("简体中文", "zh_CN"), + new Locale("繁體中文", "zh_TW"), }; public Locale(string name, string key) From ed62174942a87b7e86575e97fb06cdb68623dc37 Mon Sep 17 00:00:00 2001 From: leo Date: Thu, 6 Jun 2024 11:26:43 +0800 Subject: [PATCH 30/44] localization: add missing translations for zh_TW --- src/Resources/Locales/zh_TW.axaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Resources/Locales/zh_TW.axaml b/src/Resources/Locales/zh_TW.axaml index ba579a32..3bb64e47 100644 --- a/src/Resources/Locales/zh_TW.axaml +++ b/src/Resources/Locales/zh_TW.axaml @@ -169,6 +169,7 @@ 子模組 新增 語法高亮 + 自動換行 使用外部合併工具檢視 減少可見的行數 增加可見的行數 @@ -288,6 +289,7 @@ 貼上 偏好設定 外觀配置 + 自訂配色檔 預設字型 預設字型大小 等寬字型 @@ -461,6 +463,8 @@ 刪除${0}$ 推送${0}$ 倉庫地址 : + 更新子模組 + 本操作將執行 `submodule update` 。 警告 新建分組 新建子分組 From 4610f702b3be1956e35634ecb63961d014732918 Mon Sep 17 00:00:00 2001 From: leo Date: Thu, 6 Jun 2024 15:31:02 +0800 Subject: [PATCH 31/44] code_style: run `dotnet format` --- src/App.axaml.cs | 4 ++-- src/Commands/QueryBranches.cs | 4 ++-- src/Commands/QueryCommits.cs | 3 ++- src/Commands/QuerySingleCommit.cs | 5 +++-- src/Converters/ChangeViewModeConverters.cs | 9 --------- src/Converters/DecoratorTypeConverters.cs | 4 ++-- src/Converters/LauncherPageConverters.cs | 9 +-------- src/Converters/StringConverters.cs | 8 +------- src/Converters/WindowStateConverters.cs | 10 ---------- src/Models/TreeDataGridSelectionModel.cs | 4 ++-- src/ViewModels/BranchTreeNode.cs | 6 +++--- src/ViewModels/CheckoutCommit.cs | 6 +++--- src/ViewModels/CommitDetail.cs | 2 +- src/ViewModels/CreateBranch.cs | 6 +++--- src/ViewModels/DeleteBranch.cs | 2 +- src/ViewModels/DeleteMultipleBranches.cs | 2 +- src/ViewModels/DiffContext.cs | 4 ++-- src/ViewModels/Pull.cs | 2 +- src/ViewModels/RevisionCompare.cs | 2 +- src/ViewModels/StashesPage.cs | 1 + src/ViewModels/WorkingCopy.cs | 3 +-- src/Views/AssumeUnchangedManager.axaml.cs | 3 ++- src/Views/ChangeCollectionView.axaml.cs | 2 +- src/Views/Clone.axaml.cs | 4 ++-- src/Views/CommitBaseInfo.axaml.cs | 2 +- src/Views/CommitDetail.axaml.cs | 2 +- src/Views/ContextMenuExtension.cs | 5 +++-- src/Views/LoadingIcon.axaml.cs | 4 ++-- src/Views/Repository.axaml.cs | 6 +++--- 29 files changed, 48 insertions(+), 76 deletions(-) diff --git a/src/App.axaml.cs b/src/App.axaml.cs index 6d73bfab..69917120 100644 --- a/src/App.axaml.cs +++ b/src/App.axaml.cs @@ -163,14 +163,14 @@ namespace SourceGit try { var resDic = new ResourceDictionary(); - + var schema = JsonSerializer.Deserialize(File.ReadAllText(colorsFile), JsonCodeGen.Default.DictionaryStringString); foreach (var kv in schema) resDic[kv.Key] = Color.Parse(kv.Value); app.Resources.MergedDictionaries.Add(resDic); app._colorOverrides = resDic; - } + } catch { } diff --git a/src/Commands/QueryBranches.cs b/src/Commands/QueryBranches.cs index ed5282f7..9200db4b 100644 --- a/src/Commands/QueryBranches.cs +++ b/src/Commands/QueryBranches.cs @@ -52,12 +52,12 @@ namespace SourceGit.Commands var refName = parts[0]; if (refName.EndsWith("/HEAD", StringComparison.Ordinal)) return; - + if (refName.StartsWith(PREFIX_DETACHED, StringComparison.Ordinal)) { branch.IsHead = true; } - + if (refName.StartsWith(PREFIX_LOCAL, StringComparison.Ordinal)) { branch.Name = refName.Substring(PREFIX_LOCAL.Length); diff --git a/src/Commands/QueryCommits.cs b/src/Commands/QueryCommits.cs index 0bfb132c..8308f577 100644 --- a/src/Commands/QueryCommits.cs +++ b/src/Commands/QueryCommits.cs @@ -57,7 +57,8 @@ namespace SourceGit.Commands if (line.Equals(_endOfBodyToken, StringComparison.Ordinal)) { _nextPartIdx = 0; - if (!string.IsNullOrEmpty(_current.Message)) _current.Message = _current.Message.Trim(); + if (!string.IsNullOrEmpty(_current.Message)) + _current.Message = _current.Message.Trim(); } else { diff --git a/src/Commands/QuerySingleCommit.cs b/src/Commands/QuerySingleCommit.cs index 3ea7fd08..7d443c4e 100644 --- a/src/Commands/QuerySingleCommit.cs +++ b/src/Commands/QuerySingleCommit.cs @@ -6,7 +6,8 @@ namespace SourceGit.Commands { public class QuerySingleCommit : Command { - public QuerySingleCommit(string repo, string sha) { + public QuerySingleCommit(string repo, string sha) + { WorkingDirectory = repo; Context = repo; Args = $"show --no-show-signature --decorate=full --pretty=format:%H%n%P%n%D%n%aN±%aE%n%at%n%cN±%cE%n%ct%n%B -s {sha}"; @@ -19,7 +20,7 @@ namespace SourceGit.Commands { var commit = new Models.Commit(); var lines = rs.StdOut.Split('\n'); - if (lines.Length < 8) + if (lines.Length < 8) return null; commit.SHA = lines[0]; diff --git a/src/Converters/ChangeViewModeConverters.cs b/src/Converters/ChangeViewModeConverters.cs index 01bc1774..a5b07bca 100644 --- a/src/Converters/ChangeViewModeConverters.cs +++ b/src/Converters/ChangeViewModeConverters.cs @@ -19,14 +19,5 @@ namespace SourceGit.Converters return App.Current?.FindResource("Icons.Tree") as StreamGeometry; } }); - - public static readonly FuncValueConverter IsList = - new FuncValueConverter(v => v == Models.ChangeViewMode.List); - - public static readonly FuncValueConverter IsGrid = - new FuncValueConverter(v => v == Models.ChangeViewMode.Grid); - - public static readonly FuncValueConverter IsTree = - new FuncValueConverter(v => v == Models.ChangeViewMode.Tree); } } diff --git a/src/Converters/DecoratorTypeConverters.cs b/src/Converters/DecoratorTypeConverters.cs index 9f3d9447..e19cb37c 100644 --- a/src/Converters/DecoratorTypeConverters.cs +++ b/src/Converters/DecoratorTypeConverters.cs @@ -38,8 +38,8 @@ namespace SourceGit.Converters }); public static readonly FuncValueConverter ToFontWeight = - new FuncValueConverter(v => - v is Models.DecoratorType.CurrentBranchHead or Models.DecoratorType.CurrentCommitHead + new FuncValueConverter(v => + v is Models.DecoratorType.CurrentBranchHead or Models.DecoratorType.CurrentCommitHead ? FontWeight.Bold : FontWeight.Regular ); } diff --git a/src/Converters/LauncherPageConverters.cs b/src/Converters/LauncherPageConverters.cs index 05eec2b1..cc040653 100644 --- a/src/Converters/LauncherPageConverters.cs +++ b/src/Converters/LauncherPageConverters.cs @@ -25,14 +25,7 @@ namespace SourceGit.Converters var selected = array[1] as ViewModels.LauncherPage; var collections = array[2] as AvaloniaList; - if (selected != null && collections != null && (self == selected || collections.IndexOf(self) + 1 == collections.IndexOf(selected))) - { - return false; - } - else - { - return true; - } + return selected != null && collections != null && (self == selected || collections.IndexOf(self) + 1 == collections.IndexOf(selected)); }); } } diff --git a/src/Converters/StringConverters.cs b/src/Converters/StringConverters.cs index aa687f23..ac1785c0 100644 --- a/src/Converters/StringConverters.cs +++ b/src/Converters/StringConverters.cs @@ -30,17 +30,11 @@ namespace SourceGit.Converters { var theme = (string)value; if (theme.Equals("Light", StringComparison.OrdinalIgnoreCase)) - { return ThemeVariant.Light; - } else if (theme.Equals("Dark", StringComparison.OrdinalIgnoreCase)) - { return ThemeVariant.Dark; - } else - { return ThemeVariant.Default; - } } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) @@ -70,7 +64,7 @@ namespace SourceGit.Converters public static readonly FuncValueConverter ToShortSHA = new FuncValueConverter(v => v == null ? string.Empty : (v.Length > 10 ? v.Substring(0, 10) : v)); - + public static readonly FuncValueConverter UnderRecommendGitVersion = new(v => { diff --git a/src/Converters/WindowStateConverters.cs b/src/Converters/WindowStateConverters.cs index 7122dc1f..05f0b1cd 100644 --- a/src/Converters/WindowStateConverters.cs +++ b/src/Converters/WindowStateConverters.cs @@ -12,30 +12,20 @@ namespace SourceGit.Converters new FuncValueConverter(state => { if (OperatingSystem.IsWindows() && state == WindowState.Maximized) - { return new Thickness(6); - } else if (OperatingSystem.IsLinux() && state != WindowState.Maximized) - { return new Thickness(6); - } else - { return new Thickness(0); - } }); public static readonly FuncValueConverter ToTitleBarHeight = new FuncValueConverter(state => { if (state == WindowState.Maximized) - { return new GridLength(OperatingSystem.IsMacOS() ? 34 : 30); - } else - { return new GridLength(38); - } }); public static readonly FuncValueConverter IsNormal = diff --git a/src/Models/TreeDataGridSelectionModel.cs b/src/Models/TreeDataGridSelectionModel.cs index e45393d0..be9d7d7c 100644 --- a/src/Models/TreeDataGridSelectionModel.cs +++ b/src/Models/TreeDataGridSelectionModel.cs @@ -182,7 +182,7 @@ namespace SourceGit.Models { var focus = _source.Rows[row.RowIndex]; if (focus is IExpander expander && HasChildren(focus)) - expander.IsExpanded = !expander.IsExpanded; + expander.IsExpanded = !expander.IsExpanded; else _rowDoubleTapped?.Invoke(this, e); @@ -455,7 +455,7 @@ namespace SourceGit.Models foreach (var c in children) return true; } - + return false; } } diff --git a/src/ViewModels/BranchTreeNode.cs b/src/ViewModels/BranchTreeNode.cs index d5690889..ef61579d 100644 --- a/src/ViewModels/BranchTreeNode.cs +++ b/src/ViewModels/BranchTreeNode.cs @@ -52,7 +52,7 @@ namespace SourceGit.ViewModels { get => Type == BranchTreeNodeType.Branch; } - + public bool IsDetachedHead { get => Type == BranchTreeNodeType.DetachedHead; @@ -219,7 +219,7 @@ namespace SourceGit.ViewModels start = sepIdx + 1; sepIdx = branch.Name.IndexOf('/', start); } - + lastFolder.Children.Add(new BranchTreeNode() { Name = Path.GetFileName(branch.Name), @@ -242,7 +242,7 @@ namespace SourceGit.ViewModels { return l.Name.CompareTo(r.Name); } - + return (int)l.Type - (int)r.Type; }); diff --git a/src/ViewModels/CheckoutCommit.cs b/src/ViewModels/CheckoutCommit.cs index da91f7fb..3f610e6f 100644 --- a/src/ViewModels/CheckoutCommit.cs +++ b/src/ViewModels/CheckoutCommit.cs @@ -2,14 +2,14 @@ namespace SourceGit.ViewModels { - public class CheckoutCommit: Popup + public class CheckoutCommit : Popup { public Models.Commit Commit { get; private set; } - + public bool HasLocalChanges { get => _repo.WorkingCopyChangesCount > 0; @@ -32,7 +32,7 @@ namespace SourceGit.ViewModels { _repo.SetWatcherEnabled(false); ProgressDescription = $"Checkout Commit '{Commit.SHA}' ..."; - + return Task.Run(() => { var needPopStash = false; diff --git a/src/ViewModels/CommitDetail.cs b/src/ViewModels/CommitDetail.cs index 9f79f81d..bcacd042 100644 --- a/src/ViewModels/CommitDetail.cs +++ b/src/ViewModels/CommitDetail.cs @@ -228,7 +228,7 @@ namespace SourceGit.ViewModels e.Handled = true; }; menu.Items.Add(copyFileName); - + return menu; } diff --git a/src/ViewModels/CreateBranch.cs b/src/ViewModels/CreateBranch.cs index ad270809..aa3dc165 100644 --- a/src/ViewModels/CreateBranch.cs +++ b/src/ViewModels/CreateBranch.cs @@ -2,7 +2,7 @@ using System.Threading.Tasks; namespace SourceGit.ViewModels -{ +{ public class CreateBranch : Popup { [Required(ErrorMessage = "Branch name is required!")] @@ -19,13 +19,13 @@ namespace SourceGit.ViewModels get; private set; } - + public Models.DealWithLocalChanges PreAction { get => _preAction; set => SetProperty(ref _preAction, value); } - + public bool CheckoutAfterCreated { get; diff --git a/src/ViewModels/DeleteBranch.cs b/src/ViewModels/DeleteBranch.cs index 52a04d93..787e00fc 100644 --- a/src/ViewModels/DeleteBranch.cs +++ b/src/ViewModels/DeleteBranch.cs @@ -58,7 +58,7 @@ namespace SourceGit.ViewModels { SetProgressDescription("Deleting tracking remote branch..."); Commands.Branch.DeleteRemote(_repo.FullPath, TrackingRemoteBranch.Remote, TrackingRemoteBranch.Name); - } + } } else { diff --git a/src/ViewModels/DeleteMultipleBranches.cs b/src/ViewModels/DeleteMultipleBranches.cs index a863db16..433d80c6 100644 --- a/src/ViewModels/DeleteMultipleBranches.cs +++ b/src/ViewModels/DeleteMultipleBranches.cs @@ -17,7 +17,7 @@ namespace SourceGit.ViewModels Targets = branches; View = new Views.DeleteMultipleBranches() { DataContext = this }; } - + public override Task Sure() { _repo.SetWatcherEnabled(false); diff --git a/src/ViewModels/DiffContext.cs b/src/ViewModels/DiffContext.cs index 4ff80c23..1e6e105e 100644 --- a/src/ViewModels/DiffContext.cs +++ b/src/ViewModels/DiffContext.cs @@ -122,7 +122,7 @@ namespace SourceGit.ViewModels if (latest.TextDiff != null) { var repo = Preference.FindRepository(_repo); - if (repo != null && repo.Submodules.Contains(_option.Path)) + if (repo != null && repo.Submodules.Contains(_option.Path)) { var submoduleDiff = new Models.SubmoduleDiff(); var submoduleRoot = $"{_repo}/{_option.Path}".Replace("\\", "/"); @@ -164,7 +164,7 @@ namespace SourceGit.ViewModels { var fullPath = Path.Combine(_repo, _option.Path); (imgDiff.Old, imgDiff.OldFileSize) = BitmapFromRevisionFile(_repo, "HEAD", oldPath); - + if (File.Exists(fullPath)) { imgDiff.New = new Bitmap(fullPath); diff --git a/src/ViewModels/Pull.cs b/src/ViewModels/Pull.cs index 86987895..b4f06a9d 100644 --- a/src/ViewModels/Pull.cs +++ b/src/ViewModels/Pull.cs @@ -136,7 +136,7 @@ namespace SourceGit.ViewModels } needPopStash = true; - } + } else if (_preAction == Models.DealWithLocalChanges.Discard) { SetProgressDescription("Discard local changes ..."); diff --git a/src/ViewModels/RevisionCompare.cs b/src/ViewModels/RevisionCompare.cs index 1fdec102..99fae39f 100644 --- a/src/ViewModels/RevisionCompare.cs +++ b/src/ViewModels/RevisionCompare.cs @@ -192,7 +192,7 @@ namespace SourceGit.ViewModels e.Handled = true; }; menu.Items.Add(copyFileName); - + return menu; } diff --git a/src/ViewModels/StashesPage.cs b/src/ViewModels/StashesPage.cs index 8276e654..487e08e1 100644 --- a/src/ViewModels/StashesPage.cs +++ b/src/ViewModels/StashesPage.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Threading.Tasks; + using Avalonia.Controls; using Avalonia.Threading; diff --git a/src/ViewModels/WorkingCopy.cs b/src/ViewModels/WorkingCopy.cs index 3d481153..91e07b7f 100644 --- a/src/ViewModels/WorkingCopy.cs +++ b/src/ViewModels/WorkingCopy.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.IO; using System.Threading.Tasks; -using Avalonia; using Avalonia.Controls; using Avalonia.Platform.Storage; using Avalonia.Threading; @@ -793,7 +792,7 @@ namespace SourceGit.ViewModels App.CopyText(change.Path); e.Handled = true; }; - + var copyFileName = new MenuItem(); copyFileName.Header = App.Text("CopyFileName"); copyFileName.Icon = App.CreateMenuIcon("Icons.Copy"); diff --git a/src/Views/AssumeUnchangedManager.axaml.cs b/src/Views/AssumeUnchangedManager.axaml.cs index 3035ab77..6db53f42 100644 --- a/src/Views/AssumeUnchangedManager.axaml.cs +++ b/src/Views/AssumeUnchangedManager.axaml.cs @@ -21,7 +21,8 @@ namespace SourceGit.Views Close(); } - private void OnRemoveButtonClicked(object sender, RoutedEventArgs e) { + private void OnRemoveButtonClicked(object sender, RoutedEventArgs e) + { if (DataContext is ViewModels.AssumeUnchangedManager vm && sender is Button button) vm.Remove(button.DataContext as string); diff --git a/src/Views/ChangeCollectionView.axaml.cs b/src/Views/ChangeCollectionView.axaml.cs index 5d0694c0..61f8de97 100644 --- a/src/Views/ChangeCollectionView.axaml.cs +++ b/src/Views/ChangeCollectionView.axaml.cs @@ -81,7 +81,7 @@ namespace SourceGit.Views { if (Content is TreeDataGrid tree && tree.Source is IDisposable disposable) disposable.Dispose(); - + Content = null; var changes = Changes; diff --git a/src/Views/Clone.axaml.cs b/src/Views/Clone.axaml.cs index a1e38050..9148daec 100644 --- a/src/Views/Clone.axaml.cs +++ b/src/Views/Clone.axaml.cs @@ -17,7 +17,7 @@ namespace SourceGit.Views var toplevel = TopLevel.GetTopLevel(this); if (toplevel == null) return; - + var selected = await toplevel.StorageProvider.OpenFolderPickerAsync(options); if (selected.Count == 1) TxtParentFolder.Text = selected[0].Path.LocalPath; @@ -31,7 +31,7 @@ namespace SourceGit.Views var toplevel = TopLevel.GetTopLevel(this); if (toplevel == null) return; - + var selected = await toplevel.StorageProvider.OpenFilePickerAsync(options); if (selected.Count == 1) TxtSshKey.Text = selected[0].Path.LocalPath; diff --git a/src/Views/CommitBaseInfo.axaml.cs b/src/Views/CommitBaseInfo.axaml.cs index a4775570..d835895b 100644 --- a/src/Views/CommitBaseInfo.axaml.cs +++ b/src/Views/CommitBaseInfo.axaml.cs @@ -24,7 +24,7 @@ namespace SourceGit.Views { if (DataContext is ViewModels.CommitDetail detail && CanNavigate) detail.NavigateTo((sender as Control).DataContext as string); - + e.Handled = true; } } diff --git a/src/Views/CommitDetail.axaml.cs b/src/Views/CommitDetail.axaml.cs index 75806e59..a7373acb 100644 --- a/src/Views/CommitDetail.axaml.cs +++ b/src/Views/CommitDetail.axaml.cs @@ -16,7 +16,7 @@ namespace SourceGit.Views { var datagrid = sender as DataGrid; detail.ActivePageIndex = 1; - detail.SelectedChanges = new () { datagrid.SelectedItem as Models.Change }; + detail.SelectedChanges = new() { datagrid.SelectedItem as Models.Change }; } e.Handled = true; diff --git a/src/Views/ContextMenuExtension.cs b/src/Views/ContextMenuExtension.cs index c03a20de..6e6dbf20 100644 --- a/src/Views/ContextMenuExtension.cs +++ b/src/Views/ContextMenuExtension.cs @@ -8,8 +8,9 @@ namespace SourceGit.Views { public static void OpenContextMenu(this Control control, ContextMenu menu) { - if (menu == null) return; - + if (menu == null) + return; + menu.PlacementTarget = control; menu.Closing += OnContextMenuClosing; // Clear context menu because it is dynamic. diff --git a/src/Views/LoadingIcon.axaml.cs b/src/Views/LoadingIcon.axaml.cs index 5a7e0014..62efed1d 100644 --- a/src/Views/LoadingIcon.axaml.cs +++ b/src/Views/LoadingIcon.axaml.cs @@ -18,7 +18,7 @@ namespace SourceGit.Views { base.OnLoaded(e); - if (IsVisible) + if (IsVisible) StartAnim(); } @@ -37,7 +37,7 @@ namespace SourceGit.Views if (IsVisible) StartAnim(); else - StopAnim(); + StopAnim(); } } diff --git a/src/Views/Repository.axaml.cs b/src/Views/Repository.axaml.cs index 2cca3ab3..bc155592 100644 --- a/src/Views/Repository.axaml.cs +++ b/src/Views/Repository.axaml.cs @@ -88,7 +88,7 @@ namespace SourceGit.Views ViewModels.BranchTreeNode prev = null; foreach (var node in repo.LocalBranchTrees) node.UpdateCornerRadius(ref prev); - + if (tree.SelectedItems.Count == 1) { var node = tree.SelectedItem as ViewModels.BranchTreeNode; @@ -303,12 +303,12 @@ namespace SourceGit.Views e.Handled = true; } - + private void CollectBranchesFromNode(List outs, ViewModels.BranchTreeNode node) { if (node == null || node.IsRemote) return; - + if (node.IsFolder) { foreach (var child in node.Children) From b0c14ab3e423bbedaf81ea69f70ed23e9a9ea76b Mon Sep 17 00:00:00 2001 From: leo Date: Thu, 6 Jun 2024 17:17:58 +0800 Subject: [PATCH 32/44] fix: tab splitter visible issue --- src/Converters/LauncherPageConverters.cs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/Converters/LauncherPageConverters.cs b/src/Converters/LauncherPageConverters.cs index cc040653..8a322ad6 100644 --- a/src/Converters/LauncherPageConverters.cs +++ b/src/Converters/LauncherPageConverters.cs @@ -23,9 +23,21 @@ namespace SourceGit.Converters return false; var selected = array[1] as ViewModels.LauncherPage; - var collections = array[2] as AvaloniaList; + if (selected == null) + return true; - return selected != null && collections != null && (self == selected || collections.IndexOf(self) + 1 == collections.IndexOf(selected)); + var collections = array[2] as AvaloniaList; + if (collections == null) + return true; + + if (self == selected) + return false; + + var selfIdx = collections.IndexOf(self); + if (selfIdx == collections.Count - 1) + return true; + + return collections[selfIdx + 1] != selected; }); } } From 5514c56a298fa7c572f50126a563e558ddf96bdf Mon Sep 17 00:00:00 2001 From: leo Date: Thu, 6 Jun 2024 18:09:35 +0800 Subject: [PATCH 33/44] refactor: more efficient way to update the visibility of tab splitters --- src/Converters/LauncherPageConverters.cs | 43 ------------------------ src/ViewModels/Launcher.cs | 40 +++++++++------------- src/ViewModels/LauncherPage.cs | 9 +++-- src/Views/Launcher.axaml | 20 ++++------- 4 files changed, 29 insertions(+), 83 deletions(-) delete mode 100644 src/Converters/LauncherPageConverters.cs diff --git a/src/Converters/LauncherPageConverters.cs b/src/Converters/LauncherPageConverters.cs deleted file mode 100644 index 8a322ad6..00000000 --- a/src/Converters/LauncherPageConverters.cs +++ /dev/null @@ -1,43 +0,0 @@ -using System.Collections.Generic; - -using Avalonia.Collections; -using Avalonia.Data.Converters; - -namespace SourceGit.Converters -{ - public static class LauncherPageConverters - { - public static readonly FuncMultiValueConverter ToTabSeperatorVisible = - new FuncMultiValueConverter(v => - { - if (v == null) - return false; - - var array = new List(); - array.AddRange(v); - if (array.Count != 3) - return false; - - var self = array[0] as ViewModels.LauncherPage; - if (self == null) - return false; - - var selected = array[1] as ViewModels.LauncherPage; - if (selected == null) - return true; - - var collections = array[2] as AvaloniaList; - if (collections == null) - return true; - - if (self == selected) - return false; - - var selfIdx = collections.IndexOf(self); - if (selfIdx == collections.Count - 1) - return true; - - return collections[selfIdx + 1] != selected; - }); - } -} diff --git a/src/ViewModels/Launcher.cs b/src/ViewModels/Launcher.cs index 5bdd3c18..3c08a8fa 100644 --- a/src/ViewModels/Launcher.cs +++ b/src/ViewModels/Launcher.cs @@ -23,6 +23,7 @@ namespace SourceGit.ViewModels if (SetProperty(ref _activePage, value)) { PopupHost.Active = value; + UpdateTabSplitterVisible(); } } } @@ -157,13 +158,12 @@ namespace SourceGit.ViewModels ActivePage = Pages[removeIdx == Pages.Count - 1 ? removeIdx - 1 : removeIdx + 1]; CloseRepositoryInTab(page); Pages.RemoveAt(removeIdx); - OnPropertyChanged(nameof(Pages)); } else if (removeIdx + 1 == activeIdx) { CloseRepositoryInTab(page); Pages.RemoveAt(removeIdx); - OnPropertyChanged(nameof(Pages)); + UpdateTabSplitterVisible(); } else { @@ -174,42 +174,25 @@ namespace SourceGit.ViewModels GC.Collect(); } - public void CloseOtherTabs(object param) + public void CloseOtherTabs() { if (Pages.Count == 1) return; - var page = param as LauncherPage; - if (page == null) - page = _activePage; - - ActivePage = page; - + var id = ActivePage.Node.Id; foreach (var one in Pages) { - if (one.Node.Id != page.Node.Id) + if (one.Node.Id != id) CloseRepositoryInTab(one); } - Pages = new AvaloniaList { page }; - OnPropertyChanged(nameof(Pages)); - + Pages = new AvaloniaList { ActivePage }; GC.Collect(); } - public void CloseRightTabs(object param) + public void CloseRightTabs() { - LauncherPage page = param as LauncherPage; - if (page == null) - page = _activePage; - - var endIdx = Pages.IndexOf(page); - var activeIdx = Pages.IndexOf(_activePage); - if (endIdx < activeIdx) - { - ActivePage = page; - } - + var endIdx = Pages.IndexOf(ActivePage); for (var i = Pages.Count - 1; i > endIdx; i--) { CloseRepositoryInTab(Pages[i]); @@ -275,6 +258,13 @@ namespace SourceGit.ViewModels page.Data = null; } + private void UpdateTabSplitterVisible() + { + var activePageIdx = ActivePage == null ? -1 : Pages.IndexOf(ActivePage); + for (int i = 0; i < Pages.Count; i++) + Pages[i].IsTabSplitterVisible = (activePageIdx != i && activePageIdx != i + 1); + } + private LauncherPage _activePage = null; } } diff --git a/src/ViewModels/LauncherPage.cs b/src/ViewModels/LauncherPage.cs index 5fce2bf3..703e5eca 100644 --- a/src/ViewModels/LauncherPage.cs +++ b/src/ViewModels/LauncherPage.cs @@ -18,6 +18,12 @@ namespace SourceGit.ViewModels set => SetProperty(ref _data, value); } + public bool IsTabSplitterVisible + { + get => _isTabSplitterVisible; + set => SetProperty(ref _isTabSplitterVisible, value); + } + public AvaloniaList Notifications { get; @@ -50,12 +56,11 @@ namespace SourceGit.ViewModels public void DismissNotification(object param) { if (param is Models.Notification notice) - { Notifications.Remove(notice); - } } private RepositoryNode _node = null; private object _data = null; + private bool _isTabSplitterVisible = true; } } diff --git a/src/Views/Launcher.axaml b/src/Views/Launcher.axaml index 4ba10c30..16e242bc 100644 --- a/src/Views/Launcher.axaml +++ b/src/Views/Launcher.axaml @@ -125,11 +125,9 @@ CommandParameter="{Binding}" InputGesture="{OnPlatform Ctrl+W, macOS=⌘+W}"/> + Command="{Binding #me.((vm:Launcher)DataContext).CloseOtherTabs}"/> + Command="{Binding #me.((vm:Launcher)DataContext).CloseRightTabs}"/> @@ -210,15 +208,11 @@ - - - - - - - - - + From 1a18235a7604752d82d827c52fd89a5317e65680 Mon Sep 17 00:00:00 2001 From: leo Date: Thu, 6 Jun 2024 18:25:28 +0800 Subject: [PATCH 34/44] fix: tab splitter visible issue --- src/ViewModels/Launcher.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ViewModels/Launcher.cs b/src/ViewModels/Launcher.cs index 3c08a8fa..c8453930 100644 --- a/src/ViewModels/Launcher.cs +++ b/src/ViewModels/Launcher.cs @@ -158,6 +158,7 @@ namespace SourceGit.ViewModels ActivePage = Pages[removeIdx == Pages.Count - 1 ? removeIdx - 1 : removeIdx + 1]; CloseRepositoryInTab(page); Pages.RemoveAt(removeIdx); + UpdateTabSplitterVisible(); } else if (removeIdx + 1 == activeIdx) { From 064d04fccc81b1a1ec6223b9f85f57f7755e4df0 Mon Sep 17 00:00:00 2001 From: leo Date: Thu, 6 Jun 2024 20:25:16 +0800 Subject: [PATCH 35/44] enhance: improve QueryCommits performance --- src/Commands/QueryCommits.cs | 43 +++++++++++++++++++++++++-------- src/Models/CommitGraph.cs | 18 +++++++++++--- src/ViewModels/Histories.cs | 31 +++--------------------- src/ViewModels/Repository.cs | 3 +++ src/Views/Histories.axaml | 2 +- src/Views/Histories.axaml.cs | 17 +++---------- src/Views/TextDiffView.axaml.cs | 22 +++++++++++------ 7 files changed, 72 insertions(+), 64 deletions(-) diff --git a/src/Commands/QueryCommits.cs b/src/Commands/QueryCommits.cs index 8308f577..295bb950 100644 --- a/src/Commands/QueryCommits.cs +++ b/src/Commands/QueryCommits.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Text; namespace SourceGit.Commands { @@ -31,15 +32,14 @@ namespace SourceGit.Commands { case 0: _current = new Models.Commit() { SHA = line }; + _isSubjectSet = false; _commits.Add(_current); break; case 1: - if (!string.IsNullOrEmpty(line)) - _current.Parents.AddRange(line.Split(' ', StringSplitOptions.RemoveEmptyEntries)); + ParseParent(line); break; case 2: - if (!string.IsNullOrEmpty(line)) - ParseDecorators(line); + ParseDecorators(line); break; case 3: _current.Author = Models.User.FindOrAdd(line); @@ -57,15 +57,17 @@ namespace SourceGit.Commands if (line.Equals(_endOfBodyToken, StringComparison.Ordinal)) { _nextPartIdx = 0; - if (!string.IsNullOrEmpty(_current.Message)) - _current.Message = _current.Message.Trim(); + _current.Message = _messageReader.ToString().Trim(); + _messageReader.Clear(); + } + else if (!_isSubjectSet) + { + _isSubjectSet = true; + _current.Subject = line; } else { - if (string.IsNullOrEmpty(_current.Subject)) - _current.Subject = line; - else - _current.Message += (line + "\n"); + _messageReader.AppendLine(line); } return; } @@ -73,8 +75,27 @@ namespace SourceGit.Commands _nextPartIdx++; } + private void ParseParent(string data) + { + if (data.Length < 8) + return; + + var idx = data.IndexOf(' ', StringComparison.Ordinal); + if (idx == -1) + { + _current.Parents.Add(data); + return; + } + + _current.Parents.Add(data.Substring(0, idx)); + _current.Parents.Add(data.Substring(idx + 1)); + } + private void ParseDecorators(string data) { + if (data.Length < 3) + return; + var subs = data.Split(',', StringSplitOptions.RemoveEmptyEntries); foreach (var sub in subs) { @@ -172,5 +193,7 @@ namespace SourceGit.Commands private bool _isHeadFounded = false; private readonly bool _findFirstMerged = true; private int _nextPartIdx = 0; + private bool _isSubjectSet = false; + private StringBuilder _messageReader = new StringBuilder(); } } diff --git a/src/Models/CommitGraph.cs b/src/Models/CommitGraph.cs index 201a1e69..6b26eba5 100644 --- a/src/Models/CommitGraph.cs +++ b/src/Models/CommitGraph.cs @@ -2,11 +2,23 @@ using System.Collections.Generic; using Avalonia; +using Avalonia.Media; namespace SourceGit.Models { public class CommitGraph { + public static readonly Pen[] Pens = [ + new Pen(Brushes.Orange, 2), + new Pen(Brushes.ForestGreen, 2), + new Pen(Brushes.Gold, 2), + new Pen(Brushes.Magenta, 2), + new Pen(Brushes.Red, 2), + new Pen(Brushes.Gray, 2), + new Pen(Brushes.Turquoise, 2), + new Pen(Brushes.Olive, 2), + ]; + public class Path { public List Points = new List(); @@ -101,12 +113,12 @@ namespace SourceGit.Models public List Links { get; set; } = new List(); public List Dots { get; set; } = new List(); - public static CommitGraph Parse(List commits, double rowHeight, int colorCount) + public static CommitGraph Parse(List commits, int colorCount) { double UNIT_WIDTH = 12; double HALF_WIDTH = 6; - double UNIT_HEIGHT = rowHeight; - double HALF_HEIGHT = rowHeight / 2; + double UNIT_HEIGHT = 28; + double HALF_HEIGHT = 14; var temp = new CommitGraph(); var unsolved = new List(); diff --git a/src/ViewModels/Histories.cs b/src/ViewModels/Histories.cs index bba8f901..73007e02 100644 --- a/src/ViewModels/Histories.cs +++ b/src/ViewModels/Histories.cs @@ -1,11 +1,9 @@ using System; using System.Collections; using System.Collections.Generic; -using System.Threading.Tasks; using Avalonia.Controls; using Avalonia.Platform.Storage; -using Avalonia.Threading; using CommunityToolkit.Mvvm.ComponentModel; @@ -24,38 +22,16 @@ namespace SourceGit.ViewModels set => SetProperty(ref _isLoading, value); } - public double DataGridRowHeight - { - get => _dataGridRowHeight; - } - public List Commits { get => _commits; set { - var oldAutoSelectedCommitSHA = AutoSelectedCommit?.SHA; + var lastSelected = AutoSelectedCommit; if (SetProperty(ref _commits, value)) { - Models.Commit newSelectedCommit = null; - if (value.Count > 0 && oldAutoSelectedCommitSHA != null) - { - newSelectedCommit = value.Find(x => x.SHA == oldAutoSelectedCommitSHA); - } - if (newSelectedCommit != AutoSelectedCommit) - { - AutoSelectedCommit = newSelectedCommit; - } - - Graph = null; - Task.Run(() => - { - var graph = Models.CommitGraph.Parse(value, DataGridRowHeight, 8); - Dispatcher.UIThread.Invoke(() => - { - Graph = graph; - }); - }); + if (value.Count > 0 && lastSelected != null) + AutoSelectedCommit = value.Find(x => x.SHA == lastSelected.SHA); } } } @@ -652,7 +628,6 @@ namespace SourceGit.ViewModels } private Repository _repo = null; - private readonly double _dataGridRowHeight = 28; private bool _isLoading = true; private List _commits = new List(); private Models.CommitGraph _graph = null; diff --git a/src/ViewModels/Repository.cs b/src/ViewModels/Repository.cs index 65ee45f2..898d40c5 100644 --- a/src/ViewModels/Repository.cs +++ b/src/ViewModels/Repository.cs @@ -606,12 +606,15 @@ namespace SourceGit.ViewModels } var commits = new Commands.QueryCommits(FullPath, limits).Result(); + var graph = Models.CommitGraph.Parse(commits, 8); + Dispatcher.UIThread.Invoke(() => { if (_histories != null) { _histories.IsLoading = false; _histories.Commits = commits; + _histories.Graph = graph; } }); } diff --git a/src/Views/Histories.axaml b/src/Views/Histories.axaml index 650d52e2..e5d064c2 100644 --- a/src/Views/Histories.axaml +++ b/src/Views/Histories.axaml @@ -23,7 +23,7 @@ IsReadOnly="True" HeadersVisibility="None" Focusable="False" - RowHeight="{Binding DataGridRowHeight}" + RowHeight="28" HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto" LayoutUpdated="OnCommitDataGridLayoutUpdated" diff --git a/src/Views/Histories.axaml.cs b/src/Views/Histories.axaml.cs index 508645d4..37ad441d 100644 --- a/src/Views/Histories.axaml.cs +++ b/src/Views/Histories.axaml.cs @@ -69,17 +69,6 @@ namespace SourceGit.Views public class CommitGraph : Control { - public static readonly Pen[] Pens = [ - new Pen(Brushes.Orange, 2), - new Pen(Brushes.ForestGreen, 2), - new Pen(Brushes.Gold, 2), - new Pen(Brushes.Magenta, 2), - new Pen(Brushes.Red, 2), - new Pen(Brushes.Gray, 2), - new Pen(Brushes.Turquoise, 2), - new Pen(Brushes.Olive, 2), - ]; - public static readonly StyledProperty GraphProperty = AvaloniaProperty.Register(nameof(Graph)); @@ -151,7 +140,7 @@ namespace SourceGit.Views if (dot.Center.Y > bottom) break; - context.DrawEllipse(dotFill, Pens[dot.Color], dot.Center, 3, 3); + context.DrawEllipse(dotFill, Models.CommitGraph.Pens[dot.Color], dot.Center, 3, 3); } } @@ -168,7 +157,7 @@ namespace SourceGit.Views continue; var geo = new StreamGeometry(); - var pen = Pens[line.Color]; + var pen = Models.CommitGraph.Pens[line.Color]; using (var ctx = geo.Open()) { var started = false; @@ -238,7 +227,7 @@ namespace SourceGit.Views ctx.QuadraticBezierTo(link.Control, link.End); } - context.DrawGeometry(null, Pens[link.Color], geo); + context.DrawGeometry(null, Models.CommitGraph.Pens[link.Color], geo); } } } diff --git a/src/Views/TextDiffView.axaml.cs b/src/Views/TextDiffView.axaml.cs index 0edfd47f..50576cc4 100644 --- a/src/Views/TextDiffView.axaml.cs +++ b/src/Views/TextDiffView.axaml.cs @@ -1166,25 +1166,31 @@ namespace SourceGit.Views { base.OnPropertyChanged(change); + var data = TextDiff; + if (data == null) + { + Content = null; + SyncScrollOffset = Vector.Zero; + return; + } + if (change.Property == TextDiffProperty) { - if (TextDiff == null) - Content = null; - else if (UseSideBySideDiff) + if (UseSideBySideDiff) Content = new ViewModels.TwoSideTextDiff(TextDiff); else Content = TextDiff; + + SetCurrentValue(SyncScrollOffsetProperty, TextDiff.SyncScrollOffset); } else if (change.Property == UseSideBySideDiffProperty) { - SyncScrollOffset = Vector.Zero; - - if (TextDiff == null) - Content = null; - else if (UseSideBySideDiff) + if (UseSideBySideDiff) Content = new ViewModels.TwoSideTextDiff(TextDiff); else Content = TextDiff; + + SetCurrentValue(SyncScrollOffsetProperty, Vector.Zero); } } From b4e01a8b93526b4baa2b1505b86774a3a821794e Mon Sep 17 00:00:00 2001 From: leo Date: Thu, 6 Jun 2024 20:59:09 +0800 Subject: [PATCH 36/44] refactor: commits only hold the end position of subject in body --- src/Commands/QueryCommits.cs | 21 +++++++++++---------- src/Commands/QuerySingleCommit.cs | 13 +++++-------- src/Models/Commit.cs | 21 +++++---------------- src/ViewModels/Repository.cs | 3 +-- src/ViewModels/Reword.cs | 4 ++-- src/ViewModels/Squash.cs | 2 +- src/ViewModels/WorkingCopy.cs | 2 +- src/Views/CommitBaseInfo.axaml | 2 +- 8 files changed, 27 insertions(+), 41 deletions(-) diff --git a/src/Commands/QueryCommits.cs b/src/Commands/QueryCommits.cs index 295bb950..e60331ee 100644 --- a/src/Commands/QueryCommits.cs +++ b/src/Commands/QueryCommits.cs @@ -57,17 +57,18 @@ namespace SourceGit.Commands if (line.Equals(_endOfBodyToken, StringComparison.Ordinal)) { _nextPartIdx = 0; - _current.Message = _messageReader.ToString().Trim(); - _messageReader.Clear(); - } - else if (!_isSubjectSet) - { - _isSubjectSet = true; - _current.Subject = line; + _current.Body = _bodyReader.ToString().TrimEnd(); + _bodyReader.Clear(); } else { - _messageReader.AppendLine(line); + if (!_isSubjectSet) + { + _isSubjectSet = true; + _current.SubjectLen = line.Length; + } + + _bodyReader.AppendLine(line); } return; } @@ -191,9 +192,9 @@ namespace SourceGit.Commands private List _commits = new List(); private Models.Commit _current = null; private bool _isHeadFounded = false; - private readonly bool _findFirstMerged = true; + private bool _findFirstMerged = true; private int _nextPartIdx = 0; private bool _isSubjectSet = false; - private StringBuilder _messageReader = new StringBuilder(); + private StringBuilder _bodyReader = new StringBuilder(); } } diff --git a/src/Commands/QuerySingleCommit.cs b/src/Commands/QuerySingleCommit.cs index 7d443c4e..a5209908 100644 --- a/src/Commands/QuerySingleCommit.cs +++ b/src/Commands/QuerySingleCommit.cs @@ -32,15 +32,12 @@ namespace SourceGit.Commands commit.AuthorTime = ulong.Parse(lines[4]); commit.Committer = Models.User.FindOrAdd(lines[5]); commit.CommitterTime = ulong.Parse(lines[6]); - commit.Subject = lines[7]; + commit.SubjectLen = lines[7].Length; - if (lines.Length > 8) - { - StringBuilder builder = new StringBuilder(); - for (int i = 8; i < lines.Length; i++) - builder.Append(lines[i]); - commit.Message = builder.ToString(); - } + StringBuilder builder = new StringBuilder(); + for (int i = 7; i < lines.Length; i++) + builder.AppendLine(lines[i]); + commit.Body = builder.ToString().TrimEnd(); return commit; } diff --git a/src/Models/Commit.cs b/src/Models/Commit.cs index 4a7313f0..81c48b79 100644 --- a/src/Models/Commit.cs +++ b/src/Models/Commit.cs @@ -12,33 +12,22 @@ namespace SourceGit.Models public ulong AuthorTime { get; set; } = 0; public User Committer { get; set; } = User.Invalid; public ulong CommitterTime { get; set; } = 0; - public string Subject { get; set; } = string.Empty; - public string Message { get; set; } = string.Empty; + public int SubjectLen { get; set; } = 0; + public string Body { get; set; } = string.Empty; public List Parents { get; set; } = new List(); public List Decorators { get; set; } = new List(); public bool HasDecorators => Decorators.Count > 0; public bool IsMerged { get; set; } = false; public Thickness Margin { get; set; } = new Thickness(0); + public string Subject => string.IsNullOrWhiteSpace(Body) ? string.Empty : Body.Substring(0, SubjectLen); public string AuthorTimeStr => _utcStart.AddSeconds(AuthorTime).ToString("yyyy/MM/dd HH:mm:ss"); public string CommitterTimeStr => _utcStart.AddSeconds(CommitterTime).ToString("yyyy/MM/dd HH:mm:ss"); public string AuthorTimeShortStr => _utcStart.AddSeconds(AuthorTime).ToString("yyyy/MM/dd"); public string CommitterTimeShortStr => _utcStart.AddSeconds(CommitterTime).ToString("yyyy/MM/dd"); - public bool IsCommitterVisible - { - get => Author != Committer || AuthorTime != CommitterTime; - } - - public bool IsCurrentHead - { - get => Decorators.Find(x => x.Type is DecoratorType.CurrentBranchHead or DecoratorType.CurrentCommitHead) != null; - } - - public string FullMessage - { - get => string.IsNullOrWhiteSpace(Message) ? Subject : $"{Subject}\n\n{Message}"; - } + public bool IsCommitterVisible => Author != Committer || AuthorTime != CommitterTime; + public bool IsCurrentHead => Decorators.Find(x => x.Type is DecoratorType.CurrentBranchHead or DecoratorType.CurrentCommitHead) != null; private static readonly DateTime _utcStart = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).ToLocalTime(); } diff --git a/src/ViewModels/Repository.cs b/src/ViewModels/Repository.cs index 898d40c5..af197759 100644 --- a/src/ViewModels/Repository.cs +++ b/src/ViewModels/Repository.cs @@ -419,8 +419,7 @@ namespace SourceGit.ViewModels foreach (var c in _histories.Commits) { if (c.SHA.Contains(_searchCommitFilter, StringComparison.OrdinalIgnoreCase) - || c.Subject.Contains(_searchCommitFilter, StringComparison.OrdinalIgnoreCase) - || c.Message.Contains(_searchCommitFilter, StringComparison.OrdinalIgnoreCase) + || c.Body.Contains(_searchCommitFilter, StringComparison.OrdinalIgnoreCase) || c.Author.Name.Contains(_searchCommitFilter, StringComparison.OrdinalIgnoreCase) || c.Committer.Name.Contains(_searchCommitFilter, StringComparison.OrdinalIgnoreCase) || c.Author.Email.Contains(_searchCommitFilter, StringComparison.OrdinalIgnoreCase) diff --git a/src/ViewModels/Reword.cs b/src/ViewModels/Reword.cs index dc67a095..012aab89 100644 --- a/src/ViewModels/Reword.cs +++ b/src/ViewModels/Reword.cs @@ -22,13 +22,13 @@ namespace SourceGit.ViewModels { _repo = repo; Head = head; - Message = head.FullMessage; + Message = head.Body; View = new Views.Reword() { DataContext = this }; } public override Task Sure() { - if (_message == Head.FullMessage) + if (_message == Head.Body) return null; _repo.SetWatcherEnabled(false); diff --git a/src/ViewModels/Squash.cs b/src/ViewModels/Squash.cs index bcf6e101..9b9ccd0c 100644 --- a/src/ViewModels/Squash.cs +++ b/src/ViewModels/Squash.cs @@ -27,7 +27,7 @@ namespace SourceGit.ViewModels public Squash(Repository repo, Models.Commit head, Models.Commit parent) { _repo = repo; - _message = parent.FullMessage; + _message = parent.Body; Head = head; Parent = parent; View = new Views.Squash() { DataContext = this }; diff --git a/src/ViewModels/WorkingCopy.cs b/src/ViewModels/WorkingCopy.cs index 91e07b7f..9decf79c 100644 --- a/src/ViewModels/WorkingCopy.cs +++ b/src/ViewModels/WorkingCopy.cs @@ -93,7 +93,7 @@ namespace SourceGit.ViewModels } else { - CommitMessage = commits[0].FullMessage; + CommitMessage = commits[0].Body; } } diff --git a/src/Views/CommitBaseInfo.axaml b/src/Views/CommitBaseInfo.axaml index f1a3a0b7..9a43d891 100644 --- a/src/Views/CommitBaseInfo.axaml +++ b/src/Views/CommitBaseInfo.axaml @@ -104,7 +104,7 @@ - + From 78c7168a46e110379988a958c773c7214754ba8e Mon Sep 17 00:00:00 2001 From: leo Date: Fri, 7 Jun 2024 12:31:10 +0800 Subject: [PATCH 37/44] enhance: make commit's subject the same with pretty print parameter `%s` in `git log` command --- src/Commands/QueryCommits.cs | 126 +++++++++++++++--------------- src/Commands/QuerySingleCommit.cs | 15 ++-- src/Models/Commit.cs | 20 ++++- src/ViewModels/FileHistories.cs | 2 +- src/ViewModels/WorkingCopy.cs | 16 +++- src/Views/Histories.axaml | 2 +- 6 files changed, 98 insertions(+), 83 deletions(-) diff --git a/src/Commands/QueryCommits.cs b/src/Commands/QueryCommits.cs index e60331ee..17e8b09b 100644 --- a/src/Commands/QueryCommits.cs +++ b/src/Commands/QueryCommits.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Text; namespace SourceGit.Commands { @@ -18,7 +17,61 @@ namespace SourceGit.Commands public List Result() { - Exec(); + var rs = ReadToEnd(); + if (!rs.IsSuccess) + return _commits; + + var nextPartIdx = 0; + var start = 0; + var end = rs.StdOut.IndexOf('\n', start); + var max = rs.StdOut.Length; + while (end > 0) + { + var line = rs.StdOut.Substring(start, end - start); + switch (nextPartIdx) + { + case 0: + _current = new Models.Commit() { SHA = line }; + _commits.Add(_current); + break; + case 1: + ParseParent(line); + break; + case 2: + ParseDecorators(line); + break; + case 3: + _current.Author = Models.User.FindOrAdd(line); + break; + case 4: + _current.AuthorTime = ulong.Parse(line); + break; + case 5: + _current.Committer = Models.User.FindOrAdd(line); + break; + case 6: + _current.CommitterTime = ulong.Parse(line); + start = end + 1; + end = rs.StdOut.IndexOf(_endOfBodyToken, start, StringComparison.Ordinal); + if (end > 0) + { + if (end > start) + _current.Body = rs.StdOut.Substring(start, end - start).TrimEnd(); + + start = end + _endOfBodyToken.Length + 1; + end = start >= max ? -1 : rs.StdOut.IndexOf('\n', start); + } + + nextPartIdx = 0; + continue; + default: + break; + } + + nextPartIdx++; + start = end + 1; + end = rs.StdOut.IndexOf('\n', start); + } if (_findFirstMerged && !_isHeadFounded && _commits.Count > 0) MarkFirstMerged(); @@ -26,56 +79,6 @@ namespace SourceGit.Commands return _commits; } - protected override void OnReadline(string line) - { - switch (_nextPartIdx) - { - case 0: - _current = new Models.Commit() { SHA = line }; - _isSubjectSet = false; - _commits.Add(_current); - break; - case 1: - ParseParent(line); - break; - case 2: - ParseDecorators(line); - break; - case 3: - _current.Author = Models.User.FindOrAdd(line); - break; - case 4: - _current.AuthorTime = ulong.Parse(line); - break; - case 5: - _current.Committer = Models.User.FindOrAdd(line); - break; - case 6: - _current.CommitterTime = ulong.Parse(line); - break; - default: - if (line.Equals(_endOfBodyToken, StringComparison.Ordinal)) - { - _nextPartIdx = 0; - _current.Body = _bodyReader.ToString().TrimEnd(); - _bodyReader.Clear(); - } - else - { - if (!_isSubjectSet) - { - _isSubjectSet = true; - _current.SubjectLen = line.Length; - } - - _bodyReader.AppendLine(line); - } - return; - } - - _nextPartIdx++; - } - private void ParseParent(string data) { if (data.Length < 8) @@ -106,7 +109,7 @@ namespace SourceGit.Commands _current.Decorators.Add(new Models.Decorator() { Type = Models.DecoratorType.Tag, - Name = d.Substring(15).Trim(), + Name = d.Substring(15), }); } else if (d.EndsWith("/HEAD", StringComparison.Ordinal)) @@ -119,7 +122,7 @@ namespace SourceGit.Commands _current.Decorators.Add(new Models.Decorator() { Type = Models.DecoratorType.CurrentBranchHead, - Name = d.Substring(19).Trim(), + Name = d.Substring(19), }); } else if (d.Equals("HEAD")) @@ -128,7 +131,7 @@ namespace SourceGit.Commands _current.Decorators.Add(new Models.Decorator() { Type = Models.DecoratorType.CurrentCommitHead, - Name = d.Trim(), + Name = d, }); } else if (d.StartsWith("refs/heads/", StringComparison.Ordinal)) @@ -136,7 +139,7 @@ namespace SourceGit.Commands _current.Decorators.Add(new Models.Decorator() { Type = Models.DecoratorType.LocalBranchHead, - Name = d.Substring(11).Trim(), + Name = d.Substring(11), }); } else if (d.StartsWith("refs/remotes/", StringComparison.Ordinal)) @@ -144,7 +147,7 @@ namespace SourceGit.Commands _current.Decorators.Add(new Models.Decorator() { Type = Models.DecoratorType.RemoteBranchHead, - Name = d.Substring(13).Trim(), + Name = d.Substring(13), }); } } @@ -152,13 +155,9 @@ namespace SourceGit.Commands _current.Decorators.Sort((l, r) => { if (l.Type != r.Type) - { return (int)l.Type - (int)r.Type; - } else - { return l.Name.CompareTo(r.Name); - } }); if (_current.IsMerged && !_isHeadFounded) @@ -191,10 +190,7 @@ namespace SourceGit.Commands private string _endOfBodyToken = string.Empty; private List _commits = new List(); private Models.Commit _current = null; + private bool _findFirstMerged = false; private bool _isHeadFounded = false; - private bool _findFirstMerged = true; - private int _nextPartIdx = 0; - private bool _isSubjectSet = false; - private StringBuilder _bodyReader = new StringBuilder(); } } diff --git a/src/Commands/QuerySingleCommit.cs b/src/Commands/QuerySingleCommit.cs index a5209908..8d2087ab 100644 --- a/src/Commands/QuerySingleCommit.cs +++ b/src/Commands/QuerySingleCommit.cs @@ -32,7 +32,6 @@ namespace SourceGit.Commands commit.AuthorTime = ulong.Parse(lines[4]); commit.Committer = Models.User.FindOrAdd(lines[5]); commit.CommitterTime = ulong.Parse(lines[6]); - commit.SubjectLen = lines[7].Length; StringBuilder builder = new StringBuilder(); for (int i = 7; i < lines.Length; i++) @@ -58,7 +57,7 @@ namespace SourceGit.Commands decorators.Add(new Models.Decorator() { Type = Models.DecoratorType.Tag, - Name = d.Substring(15).Trim(), + Name = d.Substring(15), }); } else if (d.EndsWith("/HEAD", StringComparison.Ordinal)) @@ -71,7 +70,7 @@ namespace SourceGit.Commands decorators.Add(new Models.Decorator() { Type = Models.DecoratorType.CurrentBranchHead, - Name = d.Substring(19).Trim(), + Name = d.Substring(19), }); } else if (d.Equals("HEAD")) @@ -80,7 +79,7 @@ namespace SourceGit.Commands decorators.Add(new Models.Decorator() { Type = Models.DecoratorType.CurrentCommitHead, - Name = d.Trim(), + Name = d, }); } else if (d.StartsWith("refs/heads/", StringComparison.Ordinal)) @@ -88,7 +87,7 @@ namespace SourceGit.Commands decorators.Add(new Models.Decorator() { Type = Models.DecoratorType.LocalBranchHead, - Name = d.Substring(11).Trim(), + Name = d.Substring(11), }); } else if (d.StartsWith("refs/remotes/", StringComparison.Ordinal)) @@ -96,7 +95,7 @@ namespace SourceGit.Commands decorators.Add(new Models.Decorator() { Type = Models.DecoratorType.RemoteBranchHead, - Name = d.Substring(13).Trim(), + Name = d.Substring(13), }); } } @@ -104,13 +103,9 @@ namespace SourceGit.Commands decorators.Sort((l, r) => { if (l.Type != r.Type) - { return (int)l.Type - (int)r.Type; - } else - { return l.Name.CompareTo(r.Name); - } }); return isHeadOfCurrent; diff --git a/src/Models/Commit.cs b/src/Models/Commit.cs index 81c48b79..6cf764a5 100644 --- a/src/Models/Commit.cs +++ b/src/Models/Commit.cs @@ -12,7 +12,6 @@ namespace SourceGit.Models public ulong AuthorTime { get; set; } = 0; public User Committer { get; set; } = User.Invalid; public ulong CommitterTime { get; set; } = 0; - public int SubjectLen { get; set; } = 0; public string Body { get; set; } = string.Empty; public List Parents { get; set; } = new List(); public List Decorators { get; set; } = new List(); @@ -20,7 +19,24 @@ namespace SourceGit.Models public bool IsMerged { get; set; } = false; public Thickness Margin { get; set; } = new Thickness(0); - public string Subject => string.IsNullOrWhiteSpace(Body) ? string.Empty : Body.Substring(0, SubjectLen); + public string Subject + { + get + { + var end = Body.IndexOf("\r\n\r\n", StringComparison.Ordinal); + if (end == -1) + { + end = Body.IndexOf("\n\n", StringComparison.Ordinal); + if (end > 0) + return Body.Substring(0, end).Replace("\n", "", StringComparison.Ordinal); + + return Body.Replace("\n", " ", StringComparison.Ordinal); + } + + return Body.Substring(0, end).Replace("\r\n", " ", StringComparison.Ordinal); + } + } + public string AuthorTimeStr => _utcStart.AddSeconds(AuthorTime).ToString("yyyy/MM/dd HH:mm:ss"); public string CommitterTimeStr => _utcStart.AddSeconds(CommitterTime).ToString("yyyy/MM/dd HH:mm:ss"); public string AuthorTimeShortStr => _utcStart.AddSeconds(AuthorTime).ToString("yyyy/MM/dd"); diff --git a/src/ViewModels/FileHistories.cs b/src/ViewModels/FileHistories.cs index 2a01f3c8..5256da49 100644 --- a/src/ViewModels/FileHistories.cs +++ b/src/ViewModels/FileHistories.cs @@ -62,7 +62,7 @@ namespace SourceGit.ViewModels Task.Run(() => { - var commits = new Commands.QueryCommits(_repo, $"-n 10000 -- \"{file}\"").Result(); + var commits = new Commands.QueryCommits(_repo, $"-n 10000 -- \"{file}\"", false).Result(); Dispatcher.UIThread.Invoke(() => { IsLoading = false; diff --git a/src/ViewModels/WorkingCopy.cs b/src/ViewModels/WorkingCopy.cs index 9decf79c..ffd82bb1 100644 --- a/src/ViewModels/WorkingCopy.cs +++ b/src/ViewModels/WorkingCopy.cs @@ -84,17 +84,25 @@ namespace SourceGit.ViewModels { if (SetProperty(ref _useAmend, value) && value) { - var commits = new Commands.QueryCommits(_repo.FullPath, "-n 1", false).Result(); - if (commits.Count == 0) + var currentBranch = _repo.Branches.Find(x => x.IsCurrent); + if (currentBranch == null) { App.RaiseException(_repo.FullPath, "No commits to amend!!!"); _useAmend = false; OnPropertyChanged(); + return; } - else + + var head = new Commands.QuerySingleCommit(_repo.FullPath, currentBranch.Head).Result(); + if (head == null) { - CommitMessage = commits[0].Body; + App.RaiseException(_repo.FullPath, "No commits to amend!!!"); + _useAmend = false; + OnPropertyChanged(); + return; } + + CommitMessage = head.Body; } OnPropertyChanged(nameof(IsCommitWithPushVisible)); diff --git a/src/Views/Histories.axaml b/src/Views/Histories.axaml index e5d064c2..a726f57c 100644 --- a/src/Views/Histories.axaml +++ b/src/Views/Histories.axaml @@ -68,7 +68,7 @@ From bacc1c85ad1a5c8687757271a4d34e960fcbe7aa Mon Sep 17 00:00:00 2001 From: leo Date: Fri, 7 Jun 2024 16:32:06 +0800 Subject: [PATCH 38/44] enhance: reduce memory usage by commit detail view --- src/Commands/QueryRevisionObjects.cs | 15 +- src/Models/FileTreeNode.cs | 185 --------------- src/ViewModels/CommitDetail.cs | 296 ++++++++---------------- src/Views/ChangeCollectionView.axaml | 4 +- src/Views/ChangeCollectionView.axaml.cs | 125 ++++++++-- src/Views/RevisionFiles.axaml | 56 ++--- src/Views/RevisionFiles.axaml.cs | 200 ++++++++++++---- 7 files changed, 385 insertions(+), 496 deletions(-) delete mode 100644 src/Models/FileTreeNode.cs diff --git a/src/Commands/QueryRevisionObjects.cs b/src/Commands/QueryRevisionObjects.cs index 7a3db057..bcad9129 100644 --- a/src/Commands/QueryRevisionObjects.cs +++ b/src/Commands/QueryRevisionObjects.cs @@ -5,22 +5,23 @@ namespace SourceGit.Commands { public partial class QueryRevisionObjects : Command { - [GeneratedRegex(@"^\d+\s+(\w+)\s+([0-9a-f]+)\s+(.*)$")] private static partial Regex REG_FORMAT(); - private readonly List objects = new List(); - public QueryRevisionObjects(string repo, string sha) + public QueryRevisionObjects(string repo, string sha, string parentFolder) { WorkingDirectory = repo; Context = repo; - Args = $"ls-tree -r {sha}"; + Args = $"ls-tree {sha}"; + + if (!string.IsNullOrEmpty(parentFolder)) + Args += $" -- \"{parentFolder}\""; } public List Result() { Exec(); - return objects; + return _objects; } protected override void OnReadline(string line) @@ -50,7 +51,9 @@ namespace SourceGit.Commands break; } - objects.Add(obj); + _objects.Add(obj); } + + private List _objects = new List(); } } diff --git a/src/Models/FileTreeNode.cs b/src/Models/FileTreeNode.cs deleted file mode 100644 index ad1298c9..00000000 --- a/src/Models/FileTreeNode.cs +++ /dev/null @@ -1,185 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace SourceGit.Models -{ - public class FileTreeNode - { - public string FullPath { get; set; } = string.Empty; - public bool IsFolder { get; set; } = false; - public bool IsExpanded { get; set; } = false; - public object Backend { get; set; } = null; - public List Children { get; set; } = new List(); - - public static List Build(List changes, bool expanded) - { - var nodes = new List(); - var folders = new Dictionary(); - - foreach (var c in changes) - { - var sepIdx = c.Path.IndexOf('/', StringComparison.Ordinal); - if (sepIdx == -1) - { - nodes.Add(new FileTreeNode() - { - FullPath = c.Path, - Backend = c, - IsFolder = false, - IsExpanded = false - }); - } - else - { - FileTreeNode lastFolder = null; - var start = 0; - - while (sepIdx != -1) - { - var folder = c.Path.Substring(0, sepIdx); - if (folders.TryGetValue(folder, out var value)) - { - lastFolder = value; - } - else if (lastFolder == null) - { - lastFolder = new FileTreeNode() - { - FullPath = folder, - Backend = null, - IsFolder = true, - IsExpanded = expanded - }; - nodes.Add(lastFolder); - folders.Add(folder, lastFolder); - } - else - { - var cur = new FileTreeNode() - { - FullPath = folder, - Backend = null, - IsFolder = true, - IsExpanded = expanded - }; - folders.Add(folder, cur); - lastFolder.Children.Add(cur); - lastFolder = cur; - } - - start = sepIdx + 1; - sepIdx = c.Path.IndexOf('/', start); - } - - lastFolder.Children.Add(new FileTreeNode() - { - FullPath = c.Path, - Backend = c, - IsFolder = false, - IsExpanded = false - }); - } - } - - folders.Clear(); - Sort(nodes); - return nodes; - } - - public static List Build(List files, bool expanded) - { - var nodes = new List(); - var folders = new Dictionary(); - - foreach (var f in files) - { - var sepIdx = f.Path.IndexOf('/', StringComparison.Ordinal); - if (sepIdx == -1) - { - nodes.Add(new FileTreeNode() - { - FullPath = f.Path, - Backend = f, - IsFolder = false, - IsExpanded = false - }); - } - else - { - FileTreeNode lastFolder = null; - var start = 0; - - while (sepIdx != -1) - { - var folder = f.Path.Substring(0, sepIdx); - if (folders.TryGetValue(folder, out var value)) - { - lastFolder = value; - } - else if (lastFolder == null) - { - lastFolder = new FileTreeNode() - { - FullPath = folder, - Backend = null, - IsFolder = true, - IsExpanded = expanded - }; - nodes.Add(lastFolder); - folders.Add(folder, lastFolder); - } - else - { - var cur = new FileTreeNode() - { - FullPath = folder, - Backend = null, - IsFolder = true, - IsExpanded = expanded - }; - folders.Add(folder, cur); - lastFolder.Children.Add(cur); - lastFolder = cur; - } - - start = sepIdx + 1; - sepIdx = f.Path.IndexOf('/', start); - } - - lastFolder.Children.Add(new FileTreeNode() - { - FullPath = f.Path, - Backend = f, - IsFolder = false, - IsExpanded = false - }); - } - } - - folders.Clear(); - Sort(nodes); - return nodes; - } - - private static void Sort(List nodes) - { - nodes.Sort((l, r) => - { - if (l.IsFolder == r.IsFolder) - { - return l.FullPath.CompareTo(r.FullPath); - } - else - { - return l.IsFolder ? -1 : 1; - } - }); - - foreach (var node in nodes) - { - if (node.Children.Count > 1) - Sort(node.Children); - } - } - } -} diff --git a/src/ViewModels/CommitDetail.cs b/src/ViewModels/CommitDetail.cs index bcacd042..c521ce5b 100644 --- a/src/ViewModels/CommitDetail.cs +++ b/src/ViewModels/CommitDetail.cs @@ -4,7 +4,6 @@ using System.IO; using System.Threading.Tasks; using Avalonia.Controls; -using Avalonia.Controls.Models.TreeDataGrid; using Avalonia.Media.Imaging; using Avalonia.Platform.Storage; using Avalonia.Threading; @@ -76,24 +75,6 @@ namespace SourceGit.ViewModels } } - public HierarchicalTreeDataGridSource RevisionFiles - { - get => _revisionFiles; - private set => SetProperty(ref _revisionFiles, value); - } - - public string SearchFileFilter - { - get => _searchFileFilter; - set - { - if (SetProperty(ref _searchFileFilter, value)) - { - RefreshVisibleFiles(); - } - } - } - public object ViewRevisionFileContent { get => _viewRevisionFileContent; @@ -117,11 +98,6 @@ namespace SourceGit.ViewModels _selectedChanges.Clear(); _searchChangeFilter = null; _diffContext = null; - if (_revisionFilesBackup != null) - _revisionFilesBackup.Clear(); - if (_revisionFiles != null) - _revisionFiles.Dispose(); - _searchFileFilter = null; _viewRevisionFileContent = null; _cancelToken = null; } @@ -138,9 +114,93 @@ namespace SourceGit.ViewModels SearchChangeFilter = string.Empty; } - public void ClearSearchFileFilter() + public List GetRevisionFilesUnderFolder(string parentFolder) { - SearchFileFilter = string.Empty; + return new Commands.QueryRevisionObjects(_repo, _commit.SHA, parentFolder).Result(); + } + + public void ViewRevisionFile(Models.Object file) + { + if (file == null) + { + ViewRevisionFileContent = null; + return; + } + + switch (file.Type) + { + case Models.ObjectType.Blob: + Task.Run(() => + { + var isBinary = new Commands.IsBinary(_repo, _commit.SHA, file.Path).Result(); + if (isBinary) + { + var ext = Path.GetExtension(file.Path); + if (IMG_EXTS.Contains(ext)) + { + var stream = Commands.QueryFileContent.Run(_repo, _commit.SHA, file.Path); + var bitmap = stream.Length > 0 ? new Bitmap(stream) : null; + Dispatcher.UIThread.Invoke(() => + { + ViewRevisionFileContent = new Models.RevisionImageFile() { Image = bitmap }; + }); + } + else + { + var size = new Commands.QueryFileSize(_repo, file.Path, _commit.SHA).Result(); + Dispatcher.UIThread.Invoke(() => + { + ViewRevisionFileContent = new Models.RevisionBinaryFile() { Size = size }; + }); + } + + return; + } + + var contentStream = Commands.QueryFileContent.Run(_repo, _commit.SHA, file.Path); + var content = new StreamReader(contentStream).ReadToEnd(); + if (content.StartsWith("version https://git-lfs.github.com/spec/", StringComparison.Ordinal)) + { + var obj = new Models.RevisionLFSObject() { Object = new Models.LFSObject() }; + var lines = content.Split('\n', StringSplitOptions.RemoveEmptyEntries); + if (lines.Length == 3) + { + foreach (var line in lines) + { + if (line.StartsWith("oid sha256:", StringComparison.Ordinal)) + { + obj.Object.Oid = line.Substring(11); + } + else if (line.StartsWith("size ", StringComparison.Ordinal)) + { + obj.Object.Size = long.Parse(line.Substring(5)); + } + } + Dispatcher.UIThread.Invoke(() => + { + ViewRevisionFileContent = obj; + }); + return; + } + } + + Dispatcher.UIThread.Invoke(() => + { + ViewRevisionFileContent = new Models.RevisionTextFile() + { + FileName = file.Path, + Content = content + }; + }); + }); + break; + case Models.ObjectType.Commit: + ViewRevisionFileContent = new Models.RevisionSubmodule() { SHA = file.SHA }; + break; + default: + ViewRevisionFileContent = null; + break; + } } public ContextMenu CreateChangeContextMenu(Models.Change change) @@ -319,29 +379,19 @@ namespace SourceGit.ViewModels VisibleChanges = null; SelectedChanges = null; - if (_revisionFiles != null) - { - _revisionFiles.Dispose(); - _revisionFiles = null; - } - if (_commit == null) return; + if (_cancelToken != null) _cancelToken.Requested = true; _cancelToken = new Commands.Command.CancelToken(); - var parent = _commit.Parents.Count == 0 ? "4b825dc642cb6eb9a060e54bf8d69288fbee4904" : _commit.Parents[0]; - var cmdChanges = new Commands.CompareRevisions(_repo, parent, _commit.SHA) { Cancel = _cancelToken }; - var cmdRevisionFiles = new Commands.QueryRevisionObjects(_repo, _commit.SHA) { Cancel = _cancelToken }; - Task.Run(() => { + var parent = _commit.Parents.Count == 0 ? "4b825dc642cb6eb9a060e54bf8d69288fbee4904" : _commit.Parents[0]; + var cmdChanges = new Commands.CompareRevisions(_repo, parent, _commit.SHA) { Cancel = _cancelToken }; var changes = cmdChanges.Result(); - if (cmdChanges.Cancel.Requested) - return; - var visible = changes; if (!string.IsNullOrWhiteSpace(_searchChangeFilter)) { @@ -349,39 +399,18 @@ namespace SourceGit.ViewModels foreach (var c in changes) { if (c.Path.Contains(_searchChangeFilter, StringComparison.OrdinalIgnoreCase)) - { visible.Add(c); - } } } - Dispatcher.UIThread.Invoke(() => + if (!cmdChanges.Cancel.Requested) { - Changes = changes; - VisibleChanges = visible; - }); - }); - - Task.Run(() => - { - _revisionFilesBackup = cmdRevisionFiles.Result(); - if (cmdRevisionFiles.Cancel.Requested) - return; - - var visible = _revisionFilesBackup; - var isSearching = !string.IsNullOrWhiteSpace(_searchFileFilter); - if (isSearching) - { - visible = new List(); - foreach (var f in _revisionFilesBackup) + Dispatcher.UIThread.Post(() => { - if (f.Path.Contains(_searchFileFilter, StringComparison.OrdinalIgnoreCase)) - visible.Add(f); - } + Changes = changes; + VisibleChanges = visible; + }); } - - var tree = Models.FileTreeNode.Build(visible, isSearching || visible.Count <= 100); - Dispatcher.UIThread.Invoke(() => BuildRevisionFilesSource(tree)); }); } @@ -407,140 +436,6 @@ namespace SourceGit.ViewModels } } - private void RefreshVisibleFiles() - { - if (_revisionFiles == null) - return; - - var visible = _revisionFilesBackup; - var isSearching = !string.IsNullOrWhiteSpace(_searchFileFilter); - if (isSearching) - { - visible = new List(); - foreach (var f in _revisionFilesBackup) - { - if (f.Path.Contains(_searchFileFilter, StringComparison.OrdinalIgnoreCase)) - visible.Add(f); - } - } - - BuildRevisionFilesSource(Models.FileTreeNode.Build(visible, isSearching || visible.Count < 100)); - } - - private void RefreshViewRevisionFile(Models.Object file) - { - if (file == null) - { - ViewRevisionFileContent = null; - return; - } - - switch (file.Type) - { - case Models.ObjectType.Blob: - Task.Run(() => - { - var isBinary = new Commands.IsBinary(_repo, _commit.SHA, file.Path).Result(); - if (isBinary) - { - var ext = Path.GetExtension(file.Path); - if (IMG_EXTS.Contains(ext)) - { - var stream = Commands.QueryFileContent.Run(_repo, _commit.SHA, file.Path); - var bitmap = stream.Length > 0 ? new Bitmap(stream) : null; - Dispatcher.UIThread.Invoke(() => - { - ViewRevisionFileContent = new Models.RevisionImageFile() { Image = bitmap }; - }); - } - else - { - var size = new Commands.QueryFileSize(_repo, file.Path, _commit.SHA).Result(); - Dispatcher.UIThread.Invoke(() => - { - ViewRevisionFileContent = new Models.RevisionBinaryFile() { Size = size }; - }); - } - - return; - } - - var contentStream = Commands.QueryFileContent.Run(_repo, _commit.SHA, file.Path); - var content = new StreamReader(contentStream).ReadToEnd(); - if (content.StartsWith("version https://git-lfs.github.com/spec/", StringComparison.Ordinal)) - { - var obj = new Models.RevisionLFSObject() { Object = new Models.LFSObject() }; - var lines = content.Split('\n', StringSplitOptions.RemoveEmptyEntries); - if (lines.Length == 3) - { - foreach (var line in lines) - { - if (line.StartsWith("oid sha256:", StringComparison.Ordinal)) - { - obj.Object.Oid = line.Substring(11); - } - else if (line.StartsWith("size ", StringComparison.Ordinal)) - { - obj.Object.Size = long.Parse(line.Substring(5)); - } - } - Dispatcher.UIThread.Invoke(() => - { - ViewRevisionFileContent = obj; - }); - return; - } - } - - Dispatcher.UIThread.Invoke(() => - { - ViewRevisionFileContent = new Models.RevisionTextFile() - { - FileName = file.Path, - Content = content - }; - }); - }); - break; - case Models.ObjectType.Commit: - ViewRevisionFileContent = new Models.RevisionSubmodule() { SHA = file.SHA }; - break; - default: - ViewRevisionFileContent = null; - break; - } - } - - private void BuildRevisionFilesSource(List tree) - { - var source = new HierarchicalTreeDataGridSource(tree) - { - Columns = - { - new HierarchicalExpanderColumn( - new TemplateColumn("Icon", "FileTreeNodeExpanderTemplate", null, GridLength.Auto), - x => x.Children, - x => x.Children.Count > 0, - x => x.IsExpanded), - new TextColumn( - null, - x => string.Empty, - GridLength.Star) - } - }; - - var selection = new Models.TreeDataGridSelectionModel(source, x => x.Children); - selection.SingleSelect = true; - selection.SelectionChanged += (s, _) => - { - if (s is Models.TreeDataGridSelectionModel selection) - RefreshViewRevisionFile(selection.SelectedItem?.Backend as Models.Object); - }; - - source.Selection = selection; - RevisionFiles = source; - } - private static readonly HashSet IMG_EXTS = new HashSet() { ".ico", ".bmp", ".jpg", ".png", ".jpeg" @@ -554,9 +449,6 @@ namespace SourceGit.ViewModels private List _selectedChanges = null; private string _searchChangeFilter = string.Empty; private DiffContext _diffContext = null; - private List _revisionFilesBackup = null; - private HierarchicalTreeDataGridSource _revisionFiles = null; - private string _searchFileFilter = string.Empty; private object _viewRevisionFileContent = null; private Commands.Command.CancelToken _cancelToken = null; } diff --git a/src/Views/ChangeCollectionView.axaml b/src/Views/ChangeCollectionView.axaml index c364080e..634ca205 100644 --- a/src/Views/ChangeCollectionView.axaml +++ b/src/Views/ChangeCollectionView.axaml @@ -9,10 +9,10 @@ x:Class="SourceGit.Views.ChangeCollectionView" x:Name="ThisControl"> - + - + diff --git a/src/Views/ChangeCollectionView.axaml.cs b/src/Views/ChangeCollectionView.axaml.cs index 61f8de97..c9ae72b2 100644 --- a/src/Views/ChangeCollectionView.axaml.cs +++ b/src/Views/ChangeCollectionView.axaml.cs @@ -9,6 +9,101 @@ using Avalonia.Interactivity; namespace SourceGit.Views { + public class ChangeTreeNode + { + public string FullPath { get; set; } = string.Empty; + public bool IsFolder { get; set; } = false; + public bool IsExpanded { get; set; } = false; + public Models.Change Change { get; set; } = null; + public List Children { get; set; } = new List(); + + public static List Build(IList changes, bool expanded) + { + var nodes = new List(); + var folders = new Dictionary(); + + foreach (var c in changes) + { + var sepIdx = c.Path.IndexOf('/', StringComparison.Ordinal); + if (sepIdx == -1) + { + nodes.Add(new ChangeTreeNode() + { + FullPath = c.Path, + Change = c, + IsFolder = false, + IsExpanded = false + }); + } + else + { + ChangeTreeNode lastFolder = null; + var start = 0; + + while (sepIdx != -1) + { + var folder = c.Path.Substring(0, sepIdx); + if (folders.TryGetValue(folder, out var value)) + { + lastFolder = value; + } + else if (lastFolder == null) + { + lastFolder = new ChangeTreeNode() + { + FullPath = folder, + IsFolder = true, + IsExpanded = expanded + }; + folders.Add(folder, lastFolder); + InsertFolder(nodes, lastFolder); + } + else + { + var cur = new ChangeTreeNode() + { + FullPath = folder, + IsFolder = true, + IsExpanded = expanded + }; + folders.Add(folder, cur); + InsertFolder(lastFolder.Children, cur); + lastFolder = cur; + } + + start = sepIdx + 1; + sepIdx = c.Path.IndexOf('/', start); + } + + lastFolder.Children.Add(new ChangeTreeNode() + { + FullPath = c.Path, + Change = c, + IsFolder = false, + IsExpanded = false + }); + } + } + + folders.Clear(); + return nodes; + } + + private static void InsertFolder(List collection, ChangeTreeNode subFolder) + { + for (int i = 0; i < collection.Count; i++) + { + if (!collection[i].IsFolder) + { + collection.Insert(i, subFolder); + return; + } + } + + collection.Add(subFolder); + } + } + public partial class ChangeCollectionView : UserControl { public static readonly StyledProperty IsWorkingCopyChangeProperty = @@ -91,26 +186,26 @@ namespace SourceGit.Views var viewMode = ViewMode; if (viewMode == Models.ChangeViewMode.Tree) { - var filetree = Models.FileTreeNode.Build(changes, true); + var filetree = ChangeTreeNode.Build(changes, true); var template = this.FindResource("TreeModeTemplate") as IDataTemplate; - var source = new HierarchicalTreeDataGridSource(filetree) + var source = new HierarchicalTreeDataGridSource(filetree) { Columns = { - new HierarchicalExpanderColumn( - new TemplateColumn(null, template, null, GridLength.Auto), + new HierarchicalExpanderColumn( + new TemplateColumn(null, template, null, GridLength.Auto), x => x.Children, x => x.Children.Count > 0, x => x.IsExpanded) } }; - var selection = new Models.TreeDataGridSelectionModel(source, x => x.Children); + var selection = new Models.TreeDataGridSelectionModel(source, x => x.Children); selection.SingleSelect = SingleSelect; selection.RowDoubleTapped += (_, e) => RaiseEvent(new RoutedEventArgs(ChangeDoubleTappedEvent)); selection.SelectionChanged += (s, _) => { - if (!_isSelecting && s is Models.TreeDataGridSelectionModel model) + if (!_isSelecting && s is Models.TreeDataGridSelectionModel model) { var selected = new List(); foreach (var c in model.SelectedItems) @@ -195,7 +290,7 @@ namespace SourceGit.Views else changeSelection.Select(selected); } - else if (tree.Source.Selection is Models.TreeDataGridSelectionModel treeSelection) + else if (tree.Source.Selection is Models.TreeDataGridSelectionModel treeSelection) { if (selected == null || selected.Count == 0) { @@ -208,9 +303,9 @@ namespace SourceGit.Views foreach (var c in selected) set.Add(c); - var nodes = new List(); + var nodes = new List(); foreach (var node in tree.Source.Items) - CollectSelectedNodeByChange(nodes, node as Models.FileTreeNode, set); + CollectSelectedNodeByChange(nodes, node as ChangeTreeNode, set); if (nodes.Count == 0) treeSelection.Clear(); @@ -232,22 +327,20 @@ namespace SourceGit.Views }; } - private void CollectChangesInNode(List outs, Models.FileTreeNode node) + private void CollectChangesInNode(List outs, ChangeTreeNode node) { if (node.IsFolder) { foreach (var child in node.Children) CollectChangesInNode(outs, child); } - else + else if (!outs.Contains(node.Change)) { - var change = node.Backend as Models.Change; - if (change != null && !outs.Contains(change)) - outs.Add(change); + outs.Add(node.Change); } } - private void CollectSelectedNodeByChange(List outs, Models.FileTreeNode node, HashSet selected) + private void CollectSelectedNodeByChange(List outs, ChangeTreeNode node, HashSet selected) { if (node == null) return; @@ -257,7 +350,7 @@ namespace SourceGit.Views foreach (var child in node.Children) CollectSelectedNodeByChange(outs, child, selected); } - else if (node.Backend != null && selected.Contains(node.Backend)) + else if (node.Change != null && selected.Contains(node.Change)) { outs.Add(node); } diff --git a/src/Views/RevisionFiles.axaml b/src/Views/RevisionFiles.axaml index 82d2651c..9c999061 100644 --- a/src/Views/RevisionFiles.axaml +++ b/src/Views/RevisionFiles.axaml @@ -17,48 +17,20 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + Children { get; set; } = new List(); + + public bool IsFolder => Backend != null && Backend.Type == Models.ObjectType.Tree; + public string Name => Backend != null ? Path.GetFileName(Backend.Path) : string.Empty; + } + + public class RevisionFileTreeView : UserControl + { + public static readonly StyledProperty RevisionProperty = + AvaloniaProperty.Register(nameof(Revision), null); + + public string Revision + { + get => GetValue(RevisionProperty); + set => SetValue(RevisionProperty, value); + } + + public Models.Object SelectedObject + { + get; + private set; + } = null; + + protected override Type StyleKeyOverride => typeof(UserControl); + + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) + { + base.OnPropertyChanged(change); + + if (change.Property == RevisionProperty) + { + SelectedObject = null; + + if (Content is TreeDataGrid tree && tree.Source is IDisposable disposable) + disposable.Dispose(); + + var vm = DataContext as ViewModels.CommitDetail; + if (vm == null) + { + Content = null; + GC.Collect(); + return; + } + + var objects = vm.GetRevisionFilesUnderFolder(null); + if (objects == null || objects.Count == 0) + { + Content = null; + GC.Collect(); + return; + } + + var toplevelObjects = new List(); + foreach (var obj in objects) + toplevelObjects.Add(new RevisionFileTreeNode() { Backend = obj }); + + toplevelObjects.Sort((l, r) => + { + if (l.IsFolder == r.IsFolder) + return l.Name.CompareTo(r.Name); + return l.IsFolder ? -1 : 1; + }); + + var template = this.FindResource("RevisionFileTreeNodeTemplate") as IDataTemplate; + var source = new HierarchicalTreeDataGridSource(toplevelObjects) + { + Columns = + { + new HierarchicalExpanderColumn( + new TemplateColumn(null, template, null, GridLength.Auto), + GetChildrenOfTreeNode, + x => x.IsFolder, + x => x.IsExpanded) + } + }; + + var selection = new Models.TreeDataGridSelectionModel(source, GetChildrenOfTreeNode); + selection.SingleSelect = true; + selection.SelectionChanged += (s, _) => + { + if (s is Models.TreeDataGridSelectionModel model) + { + var node = model.SelectedItem; + var detail = DataContext as ViewModels.CommitDetail; + + if (node != null && !node.IsFolder) + { + SelectedObject = node.Backend; + detail.ViewRevisionFile(node.Backend); + } + else + { + SelectedObject = null; + detail.ViewRevisionFile(null); + } + } + }; + + source.Selection = selection; + Content = new TreeDataGrid() + { + AutoDragDropRows = false, + ShowColumnHeaders = false, + CanUserResizeColumns = false, + CanUserSortColumns = false, + Source = source, + }; + + GC.Collect(); + } + } + + private List GetChildrenOfTreeNode(RevisionFileTreeNode node) + { + if (!node.IsFolder) + return null; + + if (node.Children.Count > 0) + return node.Children; + + var vm = DataContext as ViewModels.CommitDetail; + if (vm == null) + return null; + + var objects = vm.GetRevisionFilesUnderFolder(node.Backend.Path + "/"); + if (objects == null || objects.Count == 0) + return null; + + foreach (var obj in objects) + node.Children.Add(new RevisionFileTreeNode() { Backend = obj }); + + node.Children.Sort((l, r) => + { + if (l.IsFolder == r.IsFolder) + return l.Name.CompareTo(r.Name); + return l.IsFolder ? -1 : 1; + }); + + return node.Children; + } + } + public class RevisionImageFileView : Control { public static readonly StyledProperty SourceProperty = @@ -59,9 +208,7 @@ namespace SourceGit.Views var source = Source; if (source != null) - { context.DrawImage(source, new Rect(source.Size), new Rect(8, 8, Bounds.Width - 16, Bounds.Height - 16)); - } } protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) @@ -79,9 +226,7 @@ namespace SourceGit.Views { var source = Source; if (source == null) - { return availableSize; - } var w = availableSize.Width - 16; var h = availableSize.Height - 16; @@ -89,13 +234,9 @@ namespace SourceGit.Views if (size.Width <= w) { if (size.Height <= h) - { return new Size(size.Width + 16, size.Height + 16); - } else - { return new Size(h * size.Width / size.Height + 16, availableSize.Height); - } } else { @@ -130,12 +271,6 @@ namespace SourceGit.Views base.OnLoaded(e); TextArea.TextView.ContextRequested += OnTextViewContextRequested; - - _textMate = Models.TextMateHelper.CreateForEditor(this); - if (DataContext is Models.RevisionTextFile source) - { - Models.TextMateHelper.SetGrammarByFileName(_textMate, source.FileName); - } } protected override void OnUnloaded(RoutedEventArgs e) @@ -143,13 +278,6 @@ namespace SourceGit.Views base.OnUnloaded(e); TextArea.TextView.ContextRequested -= OnTextViewContextRequested; - - if (_textMate != null) - { - _textMate.Dispose(); - _textMate = null; - } - GC.Collect(); } @@ -159,20 +287,9 @@ namespace SourceGit.Views var source = DataContext as Models.RevisionTextFile; if (source != null) - { Text = source.Content; - Models.TextMateHelper.SetGrammarByFileName(_textMate, source.FileName); - } - } - - protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) - { - base.OnPropertyChanged(change); - - if (change.Property.Name == "ActualThemeVariant" && change.NewValue != null) - { - Models.TextMateHelper.SetThemeByApp(_textMate); - } + else + Text = string.Empty; } private void OnTextViewContextRequested(object sender, ContextRequestedEventArgs e) @@ -202,8 +319,6 @@ namespace SourceGit.Views TextArea.TextView.OpenContextMenu(menu); e.Handled = true; } - - private TextMate.Installation _textMate = null; } public partial class RevisionFiles : UserControl @@ -213,15 +328,14 @@ namespace SourceGit.Views InitializeComponent(); } - private void OnFileContextRequested(object sender, ContextRequestedEventArgs e) + private void OnRevisionFileTreeViewContextRequested(object sender, ContextRequestedEventArgs e) { - if (DataContext is ViewModels.CommitDetail vm && sender is TreeDataGrid tree) + if (DataContext is ViewModels.CommitDetail vm && sender is RevisionFileTreeView view) { - var selected = tree.RowSelection.SelectedItem as Models.FileTreeNode; - if (selected != null && !selected.IsFolder && selected.Backend is Models.Object obj) + if (view.SelectedObject != null && view.SelectedObject.Type != Models.ObjectType.Tree) { - var menu = vm.CreateRevisionFileContextMenu(obj); - tree.OpenContextMenu(menu); + var menu = vm.CreateRevisionFileContextMenu(view.SelectedObject); + view.OpenContextMenu(menu); } } From 89f2d3dd11af8fdc44f2b502ebdb8166fdeb37f2 Mon Sep 17 00:00:00 2001 From: leo Date: Fri, 7 Jun 2024 17:38:12 +0800 Subject: [PATCH 39/44] localization: remove unused keys --- src/Resources/Locales/en_US.axaml | 3 --- src/Resources/Locales/zh_CN.axaml | 3 --- src/Resources/Locales/zh_TW.axaml | 3 --- 3 files changed, 9 deletions(-) diff --git a/src/Resources/Locales/en_US.axaml b/src/Resources/Locales/en_US.axaml index 830f736a..4f3244c2 100644 --- a/src/Resources/Locales/en_US.axaml +++ b/src/Resources/Locales/en_US.axaml @@ -93,10 +93,7 @@ Search Changes ... FILES LFS File - Search Files ... Submodule - Tag - Tree INFORMATION AUTHOR CHANGED diff --git a/src/Resources/Locales/zh_CN.axaml b/src/Resources/Locales/zh_CN.axaml index 14f6085d..9677cf22 100644 --- a/src/Resources/Locales/zh_CN.axaml +++ b/src/Resources/Locales/zh_CN.axaml @@ -96,10 +96,7 @@ 查找变更... 文件列表 LFS文件 - 查找文件... 子模块 - 标签文件 - 子树 基本信息 修改者 变更列表 diff --git a/src/Resources/Locales/zh_TW.axaml b/src/Resources/Locales/zh_TW.axaml index 3bb64e47..a6cc8b21 100644 --- a/src/Resources/Locales/zh_TW.axaml +++ b/src/Resources/Locales/zh_TW.axaml @@ -96,10 +96,7 @@ 查詢變更... 檔案列表 LFS檔案 - 查詢檔案... 子模組 - 標籤檔案 - 子樹 基本資訊 修改者 變更列表 From 6426da32897336ed44b24dce3e0c5857b083cce6 Mon Sep 17 00:00:00 2001 From: leo Date: Fri, 7 Jun 2024 18:43:37 +0800 Subject: [PATCH 40/44] enhance: supports search commits by changed file path like `src/Commands/*` or `*/Add.cs`. --- src/Resources/Locales/en_US.axaml | 4 +++- src/Resources/Locales/zh_CN.axaml | 4 +++- src/Resources/Locales/zh_TW.axaml | 4 +++- src/ViewModels/Repository.cs | 32 +++++++++++++++++++++++-------- src/Views/Repository.axaml | 31 +++++++++++++++++++++++++----- 5 files changed, 59 insertions(+), 16 deletions(-) diff --git a/src/Resources/Locales/en_US.axaml b/src/Resources/Locales/en_US.axaml index 4f3244c2..856577d7 100644 --- a/src/Resources/Locales/en_US.axaml +++ b/src/Resources/Locales/en_US.axaml @@ -384,7 +384,9 @@ ADD REMOTE RESOLVE Search Commit - Search Author/Committer/Message/SHA + Search By + Information + File Statistics SUBMODULES ADD SUBMODULE diff --git a/src/Resources/Locales/zh_CN.axaml b/src/Resources/Locales/zh_CN.axaml index 9677cf22..e5b46759 100644 --- a/src/Resources/Locales/zh_CN.axaml +++ b/src/Resources/Locales/zh_CN.axaml @@ -387,7 +387,9 @@ 添加远程 解决冲突 查找提交 - 支持搜索作者/提交者/主题/指纹 + 搜索途径 + 摘要 + 文件 提交统计 子模块列表 添加子模块 diff --git a/src/Resources/Locales/zh_TW.axaml b/src/Resources/Locales/zh_TW.axaml index a6cc8b21..ae7a6d47 100644 --- a/src/Resources/Locales/zh_TW.axaml +++ b/src/Resources/Locales/zh_TW.axaml @@ -387,7 +387,9 @@ 新增遠端 解決衝突 查詢提交 - 支援搜尋作者/提交者/主題/指紋 + 查詢方式 + 摘要 + 檔案 提交統計 子模組列表 新增子模組 diff --git a/src/ViewModels/Repository.cs b/src/ViewModels/Repository.cs index af197759..24853daf 100644 --- a/src/ViewModels/Repository.cs +++ b/src/ViewModels/Repository.cs @@ -185,6 +185,13 @@ namespace SourceGit.ViewModels } } + [JsonIgnore] + public int SearchCommitFilterType + { + get => _searchCommitFilterType; + set => SetProperty(ref _searchCommitFilterType, value); + } + [JsonIgnore] public string SearchCommitFilter { @@ -416,18 +423,26 @@ namespace SourceGit.ViewModels return; var visible = new List(); - foreach (var c in _histories.Commits) + + if (_searchCommitFilterType == 0) { - if (c.SHA.Contains(_searchCommitFilter, StringComparison.OrdinalIgnoreCase) - || c.Body.Contains(_searchCommitFilter, StringComparison.OrdinalIgnoreCase) - || c.Author.Name.Contains(_searchCommitFilter, StringComparison.OrdinalIgnoreCase) - || c.Committer.Name.Contains(_searchCommitFilter, StringComparison.OrdinalIgnoreCase) - || c.Author.Email.Contains(_searchCommitFilter, StringComparison.OrdinalIgnoreCase) - || c.Committer.Email.Contains(_searchCommitFilter, StringComparison.OrdinalIgnoreCase)) + foreach (var c in _histories.Commits) { - visible.Add(c); + if (c.SHA.Contains(_searchCommitFilter, StringComparison.OrdinalIgnoreCase) + || c.Body.Contains(_searchCommitFilter, StringComparison.OrdinalIgnoreCase) + || c.Author.Name.Contains(_searchCommitFilter, StringComparison.OrdinalIgnoreCase) + || c.Committer.Name.Contains(_searchCommitFilter, StringComparison.OrdinalIgnoreCase) + || c.Author.Email.Contains(_searchCommitFilter, StringComparison.OrdinalIgnoreCase) + || c.Committer.Email.Contains(_searchCommitFilter, StringComparison.OrdinalIgnoreCase)) + { + visible.Add(c); + } } } + else + { + visible = new Commands.QueryCommits(FullPath, $"-1000 -- \"{_searchCommitFilter}\"", false).Result(); + } SearchedCommits = visible; } @@ -1508,6 +1523,7 @@ namespace SourceGit.ViewModels private object _selectedView = null; private bool _isSearching = false; + private int _searchCommitFilterType = 0; private string _searchCommitFilter = string.Empty; private List _searchedCommits = new List(); diff --git a/src/Views/Repository.axaml b/src/Views/Repository.axaml index d393f508..7a631e31 100644 --- a/src/Views/Repository.axaml +++ b/src/Views/Repository.axaml @@ -493,17 +493,17 @@ - + @@ -529,7 +529,27 @@ - + + + + + + + + + + + @@ -572,7 +593,7 @@ - Date: Sat, 8 Jun 2024 12:19:48 +0800 Subject: [PATCH 41/44] enhance: only store subject in commits. It has several advantages: * reduce the memory costed by histories * higher performance while parsing commits * no need to calculate subject every time, which is invoked most frequently to render histories --- src/Commands/QueryCommitFullMessage.cs | 19 ++++++++++++++++++ src/Commands/QueryCommits.cs | 27 ++++++++------------------ src/Commands/QuerySingleCommit.cs | 10 +++------- src/Models/Commit.cs | 20 +------------------ src/ViewModels/CommitDetail.cs | 10 ++++++++++ src/ViewModels/Repository.cs | 2 +- src/ViewModels/Reword.cs | 10 +++++++--- src/ViewModels/Squash.cs | 3 ++- src/ViewModels/WorkingCopy.cs | 11 +---------- src/Views/CommitBaseInfo.axaml | 9 +++++---- src/Views/CommitBaseInfo.axaml.cs | 9 +++++++++ src/Views/CommitDetail.axaml | 2 +- 12 files changed, 67 insertions(+), 65 deletions(-) create mode 100644 src/Commands/QueryCommitFullMessage.cs diff --git a/src/Commands/QueryCommitFullMessage.cs b/src/Commands/QueryCommitFullMessage.cs new file mode 100644 index 00000000..f4be9bc5 --- /dev/null +++ b/src/Commands/QueryCommitFullMessage.cs @@ -0,0 +1,19 @@ +namespace SourceGit.Commands +{ + public class QueryCommitFullMessage : Command + { + public QueryCommitFullMessage(string repo, string sha) + { + WorkingDirectory = repo; + Context = repo; + Args = $"show --no-show-signature --pretty=format:%B -s {sha}"; + } + + public string Result() + { + var rs = ReadToEnd(); + if (rs.IsSuccess) return rs.StdOut.TrimEnd(); + return string.Empty; + } + } +} diff --git a/src/Commands/QueryCommits.cs b/src/Commands/QueryCommits.cs index 17e8b09b..af7cb959 100644 --- a/src/Commands/QueryCommits.cs +++ b/src/Commands/QueryCommits.cs @@ -7,11 +7,9 @@ namespace SourceGit.Commands { public QueryCommits(string repo, string limits, bool needFindHead = true) { - _endOfBodyToken = $"----- END OF BODY {Guid.NewGuid()} -----"; - WorkingDirectory = repo; Context = repo; - Args = $"log --date-order --no-show-signature --decorate=full --pretty=format:\"%H%n%P%n%D%n%aN±%aE%n%at%n%cN±%cE%n%ct%n%B%n{_endOfBodyToken}\" " + limits; + Args = $"log --date-order --no-show-signature --decorate=full --pretty=format:%H%n%P%n%D%n%aN±%aE%n%at%n%cN±%cE%n%ct%n%s " + limits; _findFirstMerged = needFindHead; } @@ -24,7 +22,6 @@ namespace SourceGit.Commands var nextPartIdx = 0; var start = 0; var end = rs.StdOut.IndexOf('\n', start); - var max = rs.StdOut.Length; while (end > 0) { var line = rs.StdOut.Substring(start, end - start); @@ -51,24 +48,17 @@ namespace SourceGit.Commands break; case 6: _current.CommitterTime = ulong.Parse(line); - start = end + 1; - end = rs.StdOut.IndexOf(_endOfBodyToken, start, StringComparison.Ordinal); - if (end > 0) - { - if (end > start) - _current.Body = rs.StdOut.Substring(start, end - start).TrimEnd(); - - start = end + _endOfBodyToken.Length + 1; - end = start >= max ? -1 : rs.StdOut.IndexOf('\n', start); - } - - nextPartIdx = 0; - continue; + break; + case 7: + _current.Subject = line; + break; default: break; } nextPartIdx++; + if (nextPartIdx == 8) nextPartIdx = 0; + start = end + 1; end = rs.StdOut.IndexOf('\n', start); } @@ -157,7 +147,7 @@ namespace SourceGit.Commands if (l.Type != r.Type) return (int)l.Type - (int)r.Type; else - return l.Name.CompareTo(r.Name); + return string.Compare(l.Name, r.Name, StringComparison.Ordinal); }); if (_current.IsMerged && !_isHeadFounded) @@ -187,7 +177,6 @@ namespace SourceGit.Commands } } - private string _endOfBodyToken = string.Empty; private List _commits = new List(); private Models.Commit _current = null; private bool _findFirstMerged = false; diff --git a/src/Commands/QuerySingleCommit.cs b/src/Commands/QuerySingleCommit.cs index 8d2087ab..4a553817 100644 --- a/src/Commands/QuerySingleCommit.cs +++ b/src/Commands/QuerySingleCommit.cs @@ -10,7 +10,7 @@ namespace SourceGit.Commands { WorkingDirectory = repo; Context = repo; - Args = $"show --no-show-signature --decorate=full --pretty=format:%H%n%P%n%D%n%aN±%aE%n%at%n%cN±%cE%n%ct%n%B -s {sha}"; + Args = $"show --no-show-signature --decorate=full --pretty=format:%H%n%P%n%D%n%aN±%aE%n%at%n%cN±%cE%n%ct%n%s -s {sha}"; } public Models.Commit Result() @@ -32,11 +32,7 @@ namespace SourceGit.Commands commit.AuthorTime = ulong.Parse(lines[4]); commit.Committer = Models.User.FindOrAdd(lines[5]); commit.CommitterTime = ulong.Parse(lines[6]); - - StringBuilder builder = new StringBuilder(); - for (int i = 7; i < lines.Length; i++) - builder.AppendLine(lines[i]); - commit.Body = builder.ToString().TrimEnd(); + commit.Subject = lines[7]; return commit; } @@ -105,7 +101,7 @@ namespace SourceGit.Commands if (l.Type != r.Type) return (int)l.Type - (int)r.Type; else - return l.Name.CompareTo(r.Name); + return string.Compare(l.Name, r.Name, StringComparison.Ordinal); }); return isHeadOfCurrent; diff --git a/src/Models/Commit.cs b/src/Models/Commit.cs index 6cf764a5..363b4b08 100644 --- a/src/Models/Commit.cs +++ b/src/Models/Commit.cs @@ -12,31 +12,13 @@ namespace SourceGit.Models public ulong AuthorTime { get; set; } = 0; public User Committer { get; set; } = User.Invalid; public ulong CommitterTime { get; set; } = 0; - public string Body { get; set; } = string.Empty; + public string Subject { get; set; } = string.Empty; public List Parents { get; set; } = new List(); public List Decorators { get; set; } = new List(); public bool HasDecorators => Decorators.Count > 0; public bool IsMerged { get; set; } = false; public Thickness Margin { get; set; } = new Thickness(0); - public string Subject - { - get - { - var end = Body.IndexOf("\r\n\r\n", StringComparison.Ordinal); - if (end == -1) - { - end = Body.IndexOf("\n\n", StringComparison.Ordinal); - if (end > 0) - return Body.Substring(0, end).Replace("\n", "", StringComparison.Ordinal); - - return Body.Replace("\n", " ", StringComparison.Ordinal); - } - - return Body.Substring(0, end).Replace("\r\n", " ", StringComparison.Ordinal); - } - } - public string AuthorTimeStr => _utcStart.AddSeconds(AuthorTime).ToString("yyyy/MM/dd HH:mm:ss"); public string CommitterTimeStr => _utcStart.AddSeconds(CommitterTime).ToString("yyyy/MM/dd HH:mm:ss"); public string AuthorTimeShortStr => _utcStart.AddSeconds(AuthorTime).ToString("yyyy/MM/dd"); diff --git a/src/ViewModels/CommitDetail.cs b/src/ViewModels/CommitDetail.cs index c521ce5b..27a13836 100644 --- a/src/ViewModels/CommitDetail.cs +++ b/src/ViewModels/CommitDetail.cs @@ -36,6 +36,12 @@ namespace SourceGit.ViewModels } } + public string FullMessage + { + get => _fullMessage; + private set => SetProperty(ref _fullMessage, value); + } + public List Changes { get => _changes; @@ -376,6 +382,7 @@ namespace SourceGit.ViewModels private void Refresh() { _changes = null; + FullMessage = string.Empty; VisibleChanges = null; SelectedChanges = null; @@ -389,6 +396,7 @@ namespace SourceGit.ViewModels Task.Run(() => { + var fullMessage = new Commands.QueryCommitFullMessage(_repo, _commit.SHA).Result(); var parent = _commit.Parents.Count == 0 ? "4b825dc642cb6eb9a060e54bf8d69288fbee4904" : _commit.Parents[0]; var cmdChanges = new Commands.CompareRevisions(_repo, parent, _commit.SHA) { Cancel = _cancelToken }; var changes = cmdChanges.Result(); @@ -407,6 +415,7 @@ namespace SourceGit.ViewModels { Dispatcher.UIThread.Post(() => { + FullMessage = fullMessage; Changes = changes; VisibleChanges = visible; }); @@ -444,6 +453,7 @@ namespace SourceGit.ViewModels private string _repo = string.Empty; private int _activePageIndex = 0; private Models.Commit _commit = null; + private string _fullMessage = string.Empty; private List _changes = null; private List _visibleChanges = null; private List _selectedChanges = null; diff --git a/src/ViewModels/Repository.cs b/src/ViewModels/Repository.cs index 24853daf..bbb0d6be 100644 --- a/src/ViewModels/Repository.cs +++ b/src/ViewModels/Repository.cs @@ -429,7 +429,7 @@ namespace SourceGit.ViewModels foreach (var c in _histories.Commits) { if (c.SHA.Contains(_searchCommitFilter, StringComparison.OrdinalIgnoreCase) - || c.Body.Contains(_searchCommitFilter, StringComparison.OrdinalIgnoreCase) + || c.Subject.Contains(_searchCommitFilter, StringComparison.OrdinalIgnoreCase) || c.Author.Name.Contains(_searchCommitFilter, StringComparison.OrdinalIgnoreCase) || c.Committer.Name.Contains(_searchCommitFilter, StringComparison.OrdinalIgnoreCase) || c.Author.Email.Contains(_searchCommitFilter, StringComparison.OrdinalIgnoreCase) diff --git a/src/ViewModels/Reword.cs b/src/ViewModels/Reword.cs index 012aab89..7c5ad88a 100644 --- a/src/ViewModels/Reword.cs +++ b/src/ViewModels/Reword.cs @@ -1,4 +1,5 @@ -using System.ComponentModel.DataAnnotations; +using System; +using System.ComponentModel.DataAnnotations; using System.Threading.Tasks; namespace SourceGit.ViewModels @@ -21,14 +22,16 @@ namespace SourceGit.ViewModels public Reword(Repository repo, Models.Commit head) { _repo = repo; + _oldMessage = new Commands.QueryCommitFullMessage(_repo.FullPath, head.SHA).Result(); + _message = _oldMessage; + Head = head; - Message = head.Body; View = new Views.Reword() { DataContext = this }; } public override Task Sure() { - if (_message == Head.Body) + if (string.Compare(_message, _oldMessage, StringComparison.Ordinal) == 0) return null; _repo.SetWatcherEnabled(false); @@ -44,5 +47,6 @@ namespace SourceGit.ViewModels private readonly Repository _repo = null; private string _message = string.Empty; + private string _oldMessage = string.Empty; } } diff --git a/src/ViewModels/Squash.cs b/src/ViewModels/Squash.cs index 9b9ccd0c..6ada9318 100644 --- a/src/ViewModels/Squash.cs +++ b/src/ViewModels/Squash.cs @@ -27,7 +27,8 @@ namespace SourceGit.ViewModels public Squash(Repository repo, Models.Commit head, Models.Commit parent) { _repo = repo; - _message = parent.Body; + _message = new Commands.QueryCommitFullMessage(_repo.FullPath, parent.SHA).Result(); + Head = head; Parent = parent; View = new Views.Squash() { DataContext = this }; diff --git a/src/ViewModels/WorkingCopy.cs b/src/ViewModels/WorkingCopy.cs index ffd82bb1..16dab612 100644 --- a/src/ViewModels/WorkingCopy.cs +++ b/src/ViewModels/WorkingCopy.cs @@ -93,16 +93,7 @@ namespace SourceGit.ViewModels return; } - var head = new Commands.QuerySingleCommit(_repo.FullPath, currentBranch.Head).Result(); - if (head == null) - { - App.RaiseException(_repo.FullPath, "No commits to amend!!!"); - _useAmend = false; - OnPropertyChanged(); - return; - } - - CommitMessage = head.Body; + CommitMessage = new Commands.QueryCommitFullMessage(_repo.FullPath, currentBranch.Head).Result(); } OnPropertyChanged(nameof(IsCommitWithPushVisible)); diff --git a/src/Views/CommitBaseInfo.axaml b/src/Views/CommitBaseInfo.axaml index 9a43d891..9b683948 100644 --- a/src/Views/CommitBaseInfo.axaml +++ b/src/Views/CommitBaseInfo.axaml @@ -7,14 +7,15 @@ xmlns:v="using:SourceGit.Views" xmlns:c="using:SourceGit.Converters" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" - x:Class="SourceGit.Views.CommitBaseInfo"> + x:Class="SourceGit.Views.CommitBaseInfo" + x:Name="ThisControl"> - + @@ -30,7 +31,7 @@ - + @@ -104,7 +105,7 @@ - + diff --git a/src/Views/CommitBaseInfo.axaml.cs b/src/Views/CommitBaseInfo.axaml.cs index d835895b..eea7921c 100644 --- a/src/Views/CommitBaseInfo.axaml.cs +++ b/src/Views/CommitBaseInfo.axaml.cs @@ -14,6 +14,15 @@ namespace SourceGit.Views get => GetValue(CanNavigateProperty); set => SetValue(CanNavigateProperty, value); } + + public static readonly StyledProperty MessageProperty = + AvaloniaProperty.Register(nameof(Message), string.Empty); + + public string Message + { + get => GetValue(MessageProperty); + set => SetValue(MessageProperty, value); + } public CommitBaseInfo() { diff --git a/src/Views/CommitDetail.axaml b/src/Views/CommitDetail.axaml index 70618e8f..a46d5e54 100644 --- a/src/Views/CommitDetail.axaml +++ b/src/Views/CommitDetail.axaml @@ -19,7 +19,7 @@ - + From 4ec93b9d75e201830edab3054b97a7dea699373c Mon Sep 17 00:00:00 2001 From: leo Date: Sat, 8 Jun 2024 14:45:18 +0800 Subject: [PATCH 42/44] enhance: remove nextPartIdx bound check --- src/Commands/QueryCommits.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Commands/QueryCommits.cs b/src/Commands/QueryCommits.cs index af7cb959..075d6467 100644 --- a/src/Commands/QueryCommits.cs +++ b/src/Commands/QueryCommits.cs @@ -51,13 +51,13 @@ namespace SourceGit.Commands break; case 7: _current.Subject = line; + nextPartIdx = -1; break; default: break; } nextPartIdx++; - if (nextPartIdx == 8) nextPartIdx = 0; start = end + 1; end = rs.StdOut.IndexOf('\n', start); From c3cbb6d8956b12ce705dcd402d47f48bfe7e37d1 Mon Sep 17 00:00:00 2001 From: leo Date: Sat, 8 Jun 2024 21:13:59 +0800 Subject: [PATCH 43/44] fix: submodule diff missing commit message --- src/Models/DiffResult.cs | 10 ++++++++-- src/ViewModels/DiffContext.cs | 20 ++++++++++++++++++-- src/Views/DiffView.axaml | 8 ++++---- 3 files changed, 30 insertions(+), 8 deletions(-) diff --git a/src/Models/DiffResult.cs b/src/Models/DiffResult.cs index 64ec2393..a0a02c6f 100644 --- a/src/Models/DiffResult.cs +++ b/src/Models/DiffResult.cs @@ -587,10 +587,16 @@ namespace SourceGit.Models public string New { get; set; } = string.Empty; } + public class SubmoduleRevision + { + public Commit Commit { get; set; } = null; + public string FullMessage { get; set; } = string.Empty; + } + public class SubmoduleDiff { - public Commit Old { get; set; } = null; - public Commit New { get; set; } = null; + public SubmoduleRevision Old { get; set; } = null; + public SubmoduleRevision New { get; set; } = null; } public class DiffResult diff --git a/src/ViewModels/DiffContext.cs b/src/ViewModels/DiffContext.cs index 1e6e105e..53f6839c 100644 --- a/src/ViewModels/DiffContext.cs +++ b/src/ViewModels/DiffContext.cs @@ -131,12 +131,12 @@ namespace SourceGit.ViewModels if (line.Type == Models.TextDiffLineType.Added) { var sha = line.Content.Substring("Subproject commit ".Length); - submoduleDiff.New = new Commands.QuerySingleCommit(submoduleRoot, sha).Result() ?? new Models.Commit() { SHA = sha }; + submoduleDiff.New = QuerySubmoduleRevision(submoduleRoot, sha); } else if (line.Type == Models.TextDiffLineType.Deleted) { var sha = line.Content.Substring("Subproject commit ".Length); - submoduleDiff.Old = new Commands.QuerySingleCommit(submoduleRoot, sha).Result() ?? new Models.Commit() { SHA = sha }; + submoduleDiff.Old = QuerySubmoduleRevision(submoduleRoot, sha); } } rs = submoduleDiff; @@ -219,6 +219,22 @@ namespace SourceGit.ViewModels return size > 0 ? (new Bitmap(stream), size) : (null, size); } + private Models.SubmoduleRevision QuerySubmoduleRevision(string repo, string sha) + { + var commit = new Commands.QuerySingleCommit(repo, sha).Result(); + if (commit != null) + { + var body = new Commands.QueryCommitFullMessage(repo, sha).Result(); + return new Models.SubmoduleRevision() { Commit = commit, FullMessage = body }; + } + + return new Models.SubmoduleRevision() + { + Commit = new Models.Commit() { SHA = sha }, + FullMessage = string.Empty, + }; + } + private static readonly HashSet IMG_EXTS = new HashSet() { ".ico", ".bmp", ".jpg", ".png", ".jpeg" diff --git a/src/Views/DiffView.axaml b/src/Views/DiffView.axaml index 713e4338..081abdf0 100644 --- a/src/Views/DiffView.axaml +++ b/src/Views/DiffView.axaml @@ -151,9 +151,9 @@ - + - + @@ -167,7 +167,7 @@ - + @@ -239,7 +239,7 @@ UseSideBySideDiff="{Binding Source={x:Static vm:Preference.Instance}, Path=UseSideBySideDiff, Mode=OneWay}"/> - + From f4a70ba1be1e51af9690d5d60d9118fcdba2fed2 Mon Sep 17 00:00:00 2001 From: leo Date: Tue, 11 Jun 2024 09:15:36 +0800 Subject: [PATCH 44/44] version: Release 8.16 --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index d9316e8b..b14fccec 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -8.15 \ No newline at end of file +8.16 \ No newline at end of file