From 7f8372f6b57a15eda2e7ac1f8925e080891ff632 Mon Sep 17 00:00:00 2001 From: GadflyFang Date: Mon, 3 Mar 2025 10:38:58 +0800 Subject: [PATCH 01/37] enhance: Handle file mode changes for new/deleted file (#1040) --- src/Commands/Diff.cs | 12 ++++++++++++ src/Models/DiffResult.cs | 14 +++++++++++++- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/src/Commands/Diff.cs b/src/Commands/Diff.cs index da971e58..8ae6350f 100644 --- a/src/Commands/Diff.cs +++ b/src/Commands/Diff.cs @@ -68,6 +68,18 @@ namespace SourceGit.Commands return; } + if (line.StartsWith("deleted file mode ", StringComparison.Ordinal)) + { + _result.OldMode = line.Substring(18); + return; + } + + if (line.StartsWith("new file mode ", StringComparison.Ordinal)) + { + _result.NewMode = line.Substring(14); + return; + } + if (_result.IsBinary) return; diff --git a/src/Models/DiffResult.cs b/src/Models/DiffResult.cs index e0ae82e0..88992e10 100644 --- a/src/Models/DiffResult.cs +++ b/src/Models/DiffResult.cs @@ -681,6 +681,18 @@ namespace SourceGit.Models public TextDiff TextDiff { get; set; } = null; public LFSDiff LFSDiff { get; set; } = null; - public string FileModeChange => string.IsNullOrEmpty(OldMode) ? string.Empty : $"{OldMode} → {NewMode}"; + public string FileModeChange + { + get + { + if (string.IsNullOrEmpty(OldMode) && string.IsNullOrEmpty(NewMode)) + return string.Empty; + + var oldDisplay = string.IsNullOrEmpty(OldMode) ? "0" : OldMode; + var newDisplay = string.IsNullOrEmpty(NewMode) ? "0" : NewMode; + + return $"{oldDisplay} → {newDisplay}"; + } + } } } From 9dffd55c2b720bb1ee71a0bd9bfddfb20f1fbd7c Mon Sep 17 00:00:00 2001 From: GadflyFang Date: Mon, 3 Mar 2025 10:39:57 +0800 Subject: [PATCH 02/37] build: Add pre-install and pre-removal scripts for DEBIAN package (#1041) kill sourcegit before install --- SourceGit.sln | 2 ++ build/resources/deb/DEBIAN/preinst | 32 ++++++++++++++++++++++++++ build/resources/deb/DEBIAN/prerm | 36 ++++++++++++++++++++++++++++++ 3 files changed, 70 insertions(+) create mode 100755 build/resources/deb/DEBIAN/preinst create mode 100755 build/resources/deb/DEBIAN/prerm diff --git a/SourceGit.sln b/SourceGit.sln index cf761abd..624322f8 100644 --- a/SourceGit.sln +++ b/SourceGit.sln @@ -60,6 +60,8 @@ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "DEBIAN", "DEBIAN", "{F101849D-BDB7-40D4-A516-751150C3CCFC}" ProjectSection(SolutionItems) = preProject build\resources\deb\DEBIAN\control = build\resources\deb\DEBIAN\control + build\resources\deb\DEBIAN\preinst = build\resources\deb\DEBIAN\preinst + build\resources\deb\DEBIAN\prerm = build\resources\deb\DEBIAN\prerm EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "rpm", "rpm", "{9BA0B044-0CC9-46F8-B551-204F149BF45D}" diff --git a/build/resources/deb/DEBIAN/preinst b/build/resources/deb/DEBIAN/preinst new file mode 100755 index 00000000..edcf9088 --- /dev/null +++ b/build/resources/deb/DEBIAN/preinst @@ -0,0 +1,32 @@ +#!/bin/sh + +set -e + +# summary of how this script can be called: +# * `install' +# * `install' +# * `upgrade' +# * `abort-upgrade' +# for details, see http://www.debian.org/doc/debian-policy/ + +case "$1" in + install|upgrade) + # Check if SourceGit is running and stop it + if pidof -q sourcegit || pgrep -f sourcegit > /dev/null; then + echo "SourceGit is running, stopping it..." + killall sourcegit 2>/dev/null || pkill -f sourcegit 2>/dev/null || true + # Wait for SourceGit to exit + sleep 1 + fi + ;; + + abort-upgrade) + ;; + + *) + echo "preinst called with unknown argument \`$1'" >&2 + exit 1 + ;; +esac + +exit 0 diff --git a/build/resources/deb/DEBIAN/prerm b/build/resources/deb/DEBIAN/prerm new file mode 100755 index 00000000..8ecd4b8d --- /dev/null +++ b/build/resources/deb/DEBIAN/prerm @@ -0,0 +1,36 @@ +#!/bin/sh + +set -e + +# summary of how this script can be called: +# * `remove' +# * `upgrade' +# * `failed-upgrade' +# * `remove' `in-favour' +# * `deconfigure' `in-favour' +# `removing' +# +# for details, see http://www.debian.org/doc/debian-policy/ or +# the debian-policy package + +case "$1" in + remove|upgrade|deconfigure) + # Check if SourceGit is running and stop it + if pidof -q sourcegit || pgrep -f sourcegit > /dev/null; then + echo "SourceGit is running, stopping it before removal..." + killall sourcegit 2>/dev/null || pkill -f sourcegit 2>/dev/null || true + # Wait for SourceGit to exit + sleep 1 + fi + ;; + + failed-upgrade) + ;; + + *) + echo "prerm called with unknown argument \`$1'" >&2 + exit 1 + ;; +esac + +exit 0 From 72c7f24e2f31e20f4150e805934b503a8493ea66 Mon Sep 17 00:00:00 2001 From: GadflyFang Date: Mon, 3 Mar 2025 14:17:03 +0800 Subject: [PATCH 03/37] fix: set FileModeChange after RefreshAll (#1043) --- src/ViewModels/DiffContext.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/ViewModels/DiffContext.cs b/src/ViewModels/DiffContext.cs index 05bedca4..8a5c3e80 100644 --- a/src/ViewModels/DiffContext.cs +++ b/src/ViewModels/DiffContext.cs @@ -116,7 +116,10 @@ namespace SourceGit.ViewModels var latest = new Commands.Diff(_repo, _option, numLines, _ignoreWhitespace).Result(); var info = new Info(_option, numLines, _ignoreWhitespace, latest); if (_info != null && info.IsSame(_info)) + { + FileModeChange = latest.FileModeChange; return; + } _info = info; From 598bba521028f6e2562500e41ee8f850ea76e0e3 Mon Sep 17 00:00:00 2001 From: leo Date: Mon, 3 Mar 2025 14:25:49 +0800 Subject: [PATCH 04/37] code_review: PR #1043 `FileModeChange` should not be changed out of UIThread Signed-off-by: leo --- src/ViewModels/DiffContext.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/ViewModels/DiffContext.cs b/src/ViewModels/DiffContext.cs index 8a5c3e80..b10f4b62 100644 --- a/src/ViewModels/DiffContext.cs +++ b/src/ViewModels/DiffContext.cs @@ -60,6 +60,7 @@ namespace SourceGit.ViewModels { _isTextDiff = previous._isTextDiff; _content = previous._content; + _fileModeChange = previous._fileModeChange; _unifiedLines = previous._unifiedLines; _ignoreWhitespace = previous._ignoreWhitespace; _info = previous._info; @@ -116,10 +117,7 @@ namespace SourceGit.ViewModels var latest = new Commands.Diff(_repo, _option, numLines, _ignoreWhitespace).Result(); var info = new Info(_option, numLines, _ignoreWhitespace, latest); if (_info != null && info.IsSame(_info)) - { - FileModeChange = latest.FileModeChange; return; - } _info = info; From 35ee4a47db9b2124699fbcfac9148c68bd238a23 Mon Sep 17 00:00:00 2001 From: leo Date: Mon, 3 Mar 2025 15:39:59 +0800 Subject: [PATCH 05/37] fix: the last selected line is missing while trying to copy multiple lines in text diff view (#1044) Signed-off-by: leo --- src/Views/TextDiffView.axaml.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Views/TextDiffView.axaml.cs b/src/Views/TextDiffView.axaml.cs index dccbb9b2..958e803e 100644 --- a/src/Views/TextDiffView.axaml.cs +++ b/src/Views/TextDiffView.axaml.cs @@ -1007,8 +1007,8 @@ namespace SourceGit.Views if (startPosition.Location > endPosition.Location) (startPosition, endPosition) = (endPosition, startPosition); - var startIdx = Math.Min(startPosition.Line - 1, lines.Count - 1); - var endIdx = Math.Min(endPosition.Line - 1, lines.Count - 1); + var startIdx = startPosition.Line - 1; + var endIdx = endPosition.Line - 1; if (startIdx == endIdx) { @@ -1025,7 +1025,7 @@ namespace SourceGit.Views } var builder = new StringBuilder(); - for (var i = startIdx; i <= endIdx; i++) + for (var i = startIdx; i <= endIdx && i <= lines.Count - 1; i++) { var line = lines[i]; if (line.Type == Models.TextDiffLineType.Indicator || @@ -1040,7 +1040,7 @@ namespace SourceGit.Views if (i == endIdx && endPosition.Column < line.Content.Length) { - builder.AppendLine(line.Content.Substring(0, endPosition.Column)); + builder.AppendLine(line.Content.Substring(0, endPosition.Column - 1)); continue; } From d4bcc60113f11dfa7768bb2b24d6ae265a87177f Mon Sep 17 00:00:00 2001 From: leo Date: Tue, 4 Mar 2025 09:29:07 +0800 Subject: [PATCH 06/37] enhance: disable `CONTINUE` button while it is running (#1046) Signed-off-by: leo --- src/ViewModels/WorkingCopy.cs | 12 ++++++++++++ src/Views/WorkingCopy.axaml | 3 ++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/ViewModels/WorkingCopy.cs b/src/ViewModels/WorkingCopy.cs index 97684093..35db11b9 100644 --- a/src/ViewModels/WorkingCopy.cs +++ b/src/ViewModels/WorkingCopy.cs @@ -440,6 +440,8 @@ namespace SourceGit.ViewModels public void ContinueMerge() { + IsCommitting = true; + if (_inProgressContext != null) { _repo.SetWatcherEnabled(false); @@ -456,17 +458,21 @@ namespace SourceGit.ViewModels CommitMessage = string.Empty; _repo.SetWatcherEnabled(true); + IsCommitting = false; }); }); } else { _repo.MarkWorkingCopyDirtyManually(); + IsCommitting = false; } } public void SkipMerge() { + IsCommitting = true; + if (_inProgressContext != null) { _repo.SetWatcherEnabled(false); @@ -479,17 +485,21 @@ namespace SourceGit.ViewModels CommitMessage = string.Empty; _repo.SetWatcherEnabled(true); + IsCommitting = false; }); }); } else { _repo.MarkWorkingCopyDirtyManually(); + IsCommitting = false; } } public void AbortMerge() { + IsCommitting = true; + if (_inProgressContext != null) { _repo.SetWatcherEnabled(false); @@ -502,12 +512,14 @@ namespace SourceGit.ViewModels CommitMessage = string.Empty; _repo.SetWatcherEnabled(true); + IsCommitting = false; }); }); } else { _repo.MarkWorkingCopyDirtyManually(); + IsCommitting = false; } } diff --git a/src/Views/WorkingCopy.axaml b/src/Views/WorkingCopy.axaml index ebf26a96..0120eda5 100644 --- a/src/Views/WorkingCopy.axaml +++ b/src/Views/WorkingCopy.axaml @@ -397,7 +397,8 @@ Width="0" Height="0" Background="Transparent" Command="{Binding CommitWithAutoStage}" - HotKey="{OnPlatform Ctrl+Shift+Enter, macOS=⌘+Shift+Enter}"/> + HotKey="{OnPlatform Ctrl+Shift+Enter, macOS=⌘+Shift+Enter}" + IsEnabled="{Binding !IsCommitting}"/> + + - - - + + From b75676a7f8e350e27c9faf34b85e61e20707a589 Mon Sep 17 00:00:00 2001 From: leo Date: Tue, 4 Mar 2025 16:04:19 +0800 Subject: [PATCH 08/37] refactor: commit message - move issue tracker and commit hash links parsing to view models - parsing links async - make sure matched hash is a valid commit oid - disable `CHILDREN` row in submodule info panel Signed-off-by: leo --- src/Commands/IsCommitSHA.cs | 17 +++ ...cs => QueryCommitsForInteractiveRebase.cs} | 16 +- src/Models/Commit.cs | 6 +- src/Models/InteractiveRebase.cs | 6 + src/Models/RevisionFile.cs | 2 +- src/ViewModels/CommitDetail.cs | 142 +++++++++++------- src/ViewModels/DiffContext.cs | 8 +- src/ViewModels/FileHistories.cs | 12 +- src/ViewModels/InteractiveRebase.cs | 2 +- src/Views/CommitBaseInfo.axaml | 11 +- src/Views/CommitBaseInfo.axaml.cs | 33 ++-- src/Views/CommitDetail.axaml | 5 +- src/Views/CommitMessagePresenter.cs | 98 +++--------- src/Views/DiffView.axaml | 4 +- src/Views/RevisionFileContentViewer.axaml | 2 +- src/Views/RevisionFiles.axaml | 2 +- src/Views/RevisionFiles.axaml.cs | 11 +- 17 files changed, 191 insertions(+), 186 deletions(-) create mode 100644 src/Commands/IsCommitSHA.cs rename src/Commands/{QueryCommitsWithFullMessage.cs => QueryCommitsForInteractiveRebase.cs} (86%) diff --git a/src/Commands/IsCommitSHA.cs b/src/Commands/IsCommitSHA.cs new file mode 100644 index 00000000..1b0c50e3 --- /dev/null +++ b/src/Commands/IsCommitSHA.cs @@ -0,0 +1,17 @@ +namespace SourceGit.Commands +{ + public class IsCommitSHA : Command + { + public IsCommitSHA(string repo, string hash) + { + WorkingDirectory = repo; + Args = $"cat-file -t {hash}"; + } + + public bool Result() + { + var rs = ReadToEnd(); + return rs.IsSuccess && rs.StdOut.Trim().Equals("commit"); + } + } +} diff --git a/src/Commands/QueryCommitsWithFullMessage.cs b/src/Commands/QueryCommitsForInteractiveRebase.cs similarity index 86% rename from src/Commands/QueryCommitsWithFullMessage.cs rename to src/Commands/QueryCommitsForInteractiveRebase.cs index c15cdbe1..232d86e5 100644 --- a/src/Commands/QueryCommitsWithFullMessage.cs +++ b/src/Commands/QueryCommitsForInteractiveRebase.cs @@ -3,18 +3,18 @@ using System.Collections.Generic; namespace SourceGit.Commands { - public class QueryCommitsWithFullMessage : Command + public class QueryCommitsForInteractiveRebase : Command { - public QueryCommitsWithFullMessage(string repo, string args) + public QueryCommitsForInteractiveRebase(string repo, string on) { _boundary = $"----- BOUNDARY OF COMMIT {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{_boundary}\" {args}"; + 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{_boundary}\" {on}..HEAD"; } - public List Result() + public List Result() { var rs = ReadToEnd(); if (!rs.IsSuccess) @@ -29,7 +29,7 @@ namespace SourceGit.Commands switch (nextPartIdx) { case 0: - _current = new Models.CommitWithMessage(); + _current = new Models.InteractiveCommit(); _current.Commit.SHA = line; _commits.Add(_current); break; @@ -52,7 +52,7 @@ namespace SourceGit.Commands _current.Commit.CommitterTime = ulong.Parse(line); break; default: - var boundary = rs.StdOut.IndexOf(_boundary, end + 1); + var boundary = rs.StdOut.IndexOf(_boundary, end + 1, StringComparison.Ordinal); if (boundary > end) { _current.Message = rs.StdOut.Substring(start, boundary - start - 1); @@ -88,8 +88,8 @@ namespace SourceGit.Commands _current.Commit.Parents.AddRange(data.Split(separator: ' ', options: StringSplitOptions.RemoveEmptyEntries)); } - private List _commits = new List(); - private Models.CommitWithMessage _current = null; + private List _commits = []; + private Models.InteractiveCommit _current = null; private string _boundary = ""; } } diff --git a/src/Models/Commit.cs b/src/Models/Commit.cs index f015130a..5c48b0c0 100644 --- a/src/Models/Commit.cs +++ b/src/Models/Commit.cs @@ -112,9 +112,9 @@ namespace SourceGit.Models } } - public class CommitWithMessage + public class CommitFullMessage { - public Commit Commit { get; set; } = new Commit(); - public string Message { get; set; } = ""; + public string Message { get; set; } = string.Empty; + public List Links { get; set; } = []; } } diff --git a/src/Models/InteractiveRebase.cs b/src/Models/InteractiveRebase.cs index 0980587a..691aadeb 100644 --- a/src/Models/InteractiveRebase.cs +++ b/src/Models/InteractiveRebase.cs @@ -12,6 +12,12 @@ namespace SourceGit.Models Drop, } + public class InteractiveCommit + { + public Commit Commit { get; set; } = new Commit(); + public string Message { get; set; } = string.Empty; + } + public class InteractiveRebaseJob { public string SHA { get; set; } = string.Empty; diff --git a/src/Models/RevisionFile.cs b/src/Models/RevisionFile.cs index f1f5265f..8cc1be2a 100644 --- a/src/Models/RevisionFile.cs +++ b/src/Models/RevisionFile.cs @@ -29,6 +29,6 @@ namespace SourceGit.Models public class RevisionSubmodule { public Commit Commit { get; set; } = null; - public string FullMessage { get; set; } = string.Empty; + public CommitFullMessage FullMessage { get; set; } = null; } } diff --git a/src/ViewModels/CommitDetail.cs b/src/ViewModels/CommitDetail.cs index 7c5aa28f..26fa37c1 100644 --- a/src/ViewModels/CommitDetail.cs +++ b/src/ViewModels/CommitDetail.cs @@ -5,7 +5,6 @@ using System.IO; using System.Text.RegularExpressions; using System.Threading.Tasks; -using Avalonia.Collections; using Avalonia.Controls; using Avalonia.Media.Imaging; using Avalonia.Platform.Storage; @@ -46,7 +45,7 @@ namespace SourceGit.ViewModels } } - public string FullMessage + public Models.CommitFullMessage FullMessage { get => _fullMessage; private set => SetProperty(ref _fullMessage, value); @@ -85,11 +84,11 @@ namespace SourceGit.ViewModels } } - public AvaloniaList Children + public List Children { - get; - private set; - } = []; + get => _children; + private set => SetProperty(ref _children, value); + } public string SearchChangeFilter { @@ -109,17 +108,12 @@ namespace SourceGit.ViewModels set => SetProperty(ref _viewRevisionFileContent, value); } - public AvaloniaList WebLinks + public List WebLinks { get; private set; } = []; - public AvaloniaList IssueTrackerRules - { - get => _repo.Settings?.IssueTrackerRules; - } - public string RevisionFileSearchFilter { get => _revisionFileSearchFilter; @@ -127,25 +121,23 @@ namespace SourceGit.ViewModels { if (SetProperty(ref _revisionFileSearchFilter, value)) { - RevisionFileSearchSuggestion.Clear(); - if (!string.IsNullOrEmpty(value)) { - if (_revisionFiles.Count == 0) + if (_revisionFiles == null) { var sha = Commit.SHA; Task.Run(() => { var files = new Commands.QueryRevisionFileNames(_repo.FullPath, sha).Result(); + var filesList = new List(); + filesList.AddRange(files); Dispatcher.UIThread.Invoke(() => { if (sha == Commit.SHA) { - _revisionFiles.Clear(); - _revisionFiles.AddRange(files); - + _revisionFiles = filesList; if (!string.IsNullOrEmpty(_revisionFileSearchFilter)) UpdateRevisionFileSearchSuggestion(); } @@ -159,23 +151,17 @@ namespace SourceGit.ViewModels } else { - IsRevisionFileSearchSuggestionOpen = false; + RevisionFileSearchSuggestion = null; GC.Collect(); } } } } - public AvaloniaList RevisionFileSearchSuggestion + public List RevisionFileSearchSuggestion { - get; - private set; - } = []; - - public bool IsRevisionFileSearchSuggestionOpen - { - get => _isRevisionFileSearchSuggestionOpen; - set => SetProperty(ref _isRevisionFileSearchSuggestionOpen, value); + get => _revisionFileSearchSuggestion; + private set => SetProperty(ref _revisionFileSearchSuggestion, value); } public CommitDetail(Repository repo) @@ -212,23 +198,17 @@ namespace SourceGit.ViewModels { _repo = null; _commit = null; - - if (_changes != null) - _changes.Clear(); - if (_visibleChanges != null) - _visibleChanges.Clear(); - if (_selectedChanges != null) - _selectedChanges.Clear(); - + _changes = null; + _visibleChanges = null; + _selectedChanges = null; _signInfo = null; _searchChangeFilter = null; _diffContext = null; _viewRevisionFileContent = null; _cancelToken = null; - WebLinks.Clear(); - _revisionFiles.Clear(); - RevisionFileSearchSuggestion.Clear(); + _revisionFiles = null; + _revisionFileSearchSuggestion = null; } public void NavigateTo(string commitSHA) @@ -251,6 +231,11 @@ namespace SourceGit.ViewModels RevisionFileSearchFilter = string.Empty; } + public void CancelRevisionFileSuggestions() + { + RevisionFileSearchSuggestion = null; + } + public Models.Commit GetParent(string sha) { return new Commands.QuerySingleCommit(_repo.FullPath, sha).Result(); @@ -322,7 +307,12 @@ namespace SourceGit.ViewModels if (commit != null) { var body = new Commands.QueryCommitFullMessage(submoduleRoot, file.SHA).Result(); - var submodule = new Models.RevisionSubmodule() { Commit = commit, FullMessage = body }; + var submodule = new Models.RevisionSubmodule() + { + Commit = commit, + FullMessage = new Models.CommitFullMessage { Message = body } + }; + Dispatcher.UIThread.Invoke(() => ViewRevisionFileContent = submodule); } else @@ -332,7 +322,7 @@ namespace SourceGit.ViewModels ViewRevisionFileContent = new Models.RevisionSubmodule() { Commit = new Models.Commit() { SHA = file.SHA }, - FullMessage = string.Empty, + FullMessage = null, }; }); } @@ -622,23 +612,22 @@ namespace SourceGit.ViewModels private void Refresh() { _changes = null; - _revisionFiles.Clear(); + _revisionFiles = null; SignInfo = null; ViewRevisionFileContent = null; - Children.Clear(); + Children = null; RevisionFileSearchFilter = string.Empty; - IsRevisionFileSearchSuggestionOpen = false; - - GC.Collect(); + RevisionFileSearchSuggestion = null; if (_commit == null) return; Task.Run(() => { - var fullMessage = new Commands.QueryCommitFullMessage(_repo.FullPath, _commit.SHA).Result(); - Dispatcher.UIThread.Invoke(() => FullMessage = fullMessage); + var message = new Commands.QueryCommitFullMessage(_repo.FullPath, _commit.SHA).Result(); + var links = ParseLinksInMessage(message); + Dispatcher.UIThread.Invoke(() => FullMessage = new Models.CommitFullMessage { Message = message, Links = links }); }); Task.Run(() => @@ -694,6 +683,49 @@ namespace SourceGit.ViewModels }); } + private List ParseLinksInMessage(string message) + { + var links = new List(); + if (_repo.Settings.IssueTrackerRules is { Count: > 0 } rules) + { + foreach (var rule in rules) + rule.Matches(links, message); + } + + var shas = REG_SHA_FORMAT().Matches(message); + for (int i = 0; i < shas.Count; i++) + { + var sha = shas[i]; + if (!sha.Success) + continue; + + var hash = sha.Groups[1].Value; + var test = new Commands.IsCommitSHA(_repo.FullPath, hash).Result(); + if (!test) + continue; + + var start = sha.Index; + var len = sha.Length; + var intersect = false; + foreach (var link in links) + { + if (link.Intersect(start, len)) + { + intersect = true; + break; + } + } + + if (!intersect) + links.Add(new Models.Hyperlink(start, len, hash, true)); + } + + if (links.Count > 0) + links.Sort((l, r) => l.Start - r.Start); + + return links; + } + private void RefreshVisibleChanges() { if (_changes == null) @@ -813,11 +845,12 @@ namespace SourceGit.ViewModels break; } - RevisionFileSearchSuggestion.Clear(); - RevisionFileSearchSuggestion.AddRange(suggestion); - IsRevisionFileSearchSuggestionOpen = suggestion.Count > 0; + RevisionFileSearchSuggestion = suggestion; } + [GeneratedRegex(@"\b([0-9a-fA-F]{6,40})\b")] + private static partial Regex REG_SHA_FORMAT(); + [GeneratedRegex(@"^version https://git-lfs.github.com/spec/v\d+\r?\noid sha256:([0-9a-f]+)\r?\nsize (\d+)[\r\n]*$")] private static partial Regex REG_LFS_FORMAT(); @@ -828,8 +861,9 @@ namespace SourceGit.ViewModels private Repository _repo = null; private Models.Commit _commit = null; - private string _fullMessage = string.Empty; + private Models.CommitFullMessage _fullMessage = null; private Models.CommitSignInfo _signInfo = null; + private List _children = null; private List _changes = null; private List _visibleChanges = null; private List _selectedChanges = null; @@ -837,8 +871,8 @@ namespace SourceGit.ViewModels private DiffContext _diffContext = null; private object _viewRevisionFileContent = null; private Commands.Command.CancelToken _cancelToken = null; - private List _revisionFiles = []; + private List _revisionFiles = null; private string _revisionFileSearchFilter = string.Empty; - private bool _isRevisionFileSearchSuggestionOpen = false; + private List _revisionFileSearchSuggestion = null; } } diff --git a/src/ViewModels/DiffContext.cs b/src/ViewModels/DiffContext.cs index b10f4b62..6dd836bf 100644 --- a/src/ViewModels/DiffContext.cs +++ b/src/ViewModels/DiffContext.cs @@ -235,13 +235,17 @@ namespace SourceGit.ViewModels if (commit != null) { var body = new Commands.QueryCommitFullMessage(repo, sha).Result(); - return new Models.RevisionSubmodule() { Commit = commit, FullMessage = body }; + return new Models.RevisionSubmodule() + { + Commit = commit, + FullMessage = new Models.CommitFullMessage { Message = body } + }; } return new Models.RevisionSubmodule() { Commit = new Models.Commit() { SHA = sha }, - FullMessage = string.Empty, + FullMessage = null, }; } diff --git a/src/ViewModels/FileHistories.cs b/src/ViewModels/FileHistories.cs index 7e248274..417b816d 100644 --- a/src/ViewModels/FileHistories.cs +++ b/src/ViewModels/FileHistories.cs @@ -123,12 +123,20 @@ namespace SourceGit.ViewModels if (commit != null) { var message = new Commands.QueryCommitFullMessage(submoduleRoot, obj.SHA).Result(); - var module = new Models.RevisionSubmodule() { Commit = commit, FullMessage = message }; + var module = new Models.RevisionSubmodule() + { + Commit = commit, + FullMessage = new Models.CommitFullMessage { Message = message } + }; Dispatcher.UIThread.Invoke(() => ViewContent = new FileHistoriesRevisionFile(_file, module)); } else { - var module = new Models.RevisionSubmodule() { Commit = new Models.Commit() { SHA = obj.SHA }, FullMessage = "" }; + var module = new Models.RevisionSubmodule() + { + Commit = new Models.Commit() { SHA = obj.SHA }, + FullMessage = null + }; Dispatcher.UIThread.Invoke(() => ViewContent = new FileHistoriesRevisionFile(_file, module)); } }); diff --git a/src/ViewModels/InteractiveRebase.cs b/src/ViewModels/InteractiveRebase.cs index 9f28fbc7..2fd2d67c 100644 --- a/src/ViewModels/InteractiveRebase.cs +++ b/src/ViewModels/InteractiveRebase.cs @@ -118,7 +118,7 @@ namespace SourceGit.ViewModels Task.Run(() => { - var commits = new Commands.QueryCommitsWithFullMessage(repoPath, $"{on.SHA}..HEAD").Result(); + var commits = new Commands.QueryCommitsForInteractiveRebase(repoPath, on.SHA).Result(); var list = new List(); foreach (var c in commits) diff --git a/src/Views/CommitBaseInfo.axaml b/src/Views/CommitBaseInfo.axaml index c92a3612..4ff8f20f 100644 --- a/src/Views/CommitBaseInfo.axaml +++ b/src/Views/CommitBaseInfo.axaml @@ -95,8 +95,8 @@ - - + + @@ -133,8 +133,8 @@ - - + + @@ -187,8 +187,7 @@ diff --git a/src/Views/CommitBaseInfo.axaml.cs b/src/Views/CommitBaseInfo.axaml.cs index ce1a7cfd..8767db9f 100644 --- a/src/Views/CommitBaseInfo.axaml.cs +++ b/src/Views/CommitBaseInfo.axaml.cs @@ -1,7 +1,7 @@ +using System.Collections.Generic; using System.Threading.Tasks; using Avalonia; -using Avalonia.Collections; using Avalonia.Controls; using Avalonia.Input; using Avalonia.Interactivity; @@ -11,13 +11,13 @@ namespace SourceGit.Views { public partial class CommitBaseInfo : UserControl { - public static readonly StyledProperty MessageProperty = - AvaloniaProperty.Register(nameof(Message), string.Empty); + public static readonly StyledProperty FullMessageProperty = + AvaloniaProperty.Register(nameof(FullMessage)); - public string Message + public Models.CommitFullMessage FullMessage { - get => GetValue(MessageProperty); - set => SetValue(MessageProperty, value); + get => GetValue(FullMessageProperty); + set => SetValue(FullMessageProperty, value); } public static readonly StyledProperty SignInfoProperty = @@ -38,28 +38,19 @@ namespace SourceGit.Views set => SetValue(SupportsContainsInProperty, value); } - public static readonly StyledProperty> WebLinksProperty = - AvaloniaProperty.Register>(nameof(WebLinks)); + public static readonly StyledProperty> WebLinksProperty = + AvaloniaProperty.Register>(nameof(WebLinks)); - public AvaloniaList WebLinks + public List WebLinks { get => GetValue(WebLinksProperty); set => SetValue(WebLinksProperty, value); } - public static readonly StyledProperty> IssueTrackerRulesProperty = - AvaloniaProperty.Register>(nameof(IssueTrackerRules)); + public static readonly StyledProperty> ChildrenProperty = + AvaloniaProperty.Register>(nameof(Children)); - public AvaloniaList IssueTrackerRules - { - get => GetValue(IssueTrackerRulesProperty); - set => SetValue(IssueTrackerRulesProperty, value); - } - - public static readonly StyledProperty> ChildrenProperty = - AvaloniaProperty.Register>(nameof(Children)); - - public AvaloniaList Children + public List Children { get => GetValue(ChildrenProperty); set => SetValue(ChildrenProperty, value); diff --git a/src/Views/CommitDetail.axaml b/src/Views/CommitDetail.axaml index d0d52473..28b21eb7 100644 --- a/src/Views/CommitDetail.axaml +++ b/src/Views/CommitDetail.axaml @@ -20,12 +20,11 @@ + Children="{Binding Children}"/> diff --git a/src/Views/CommitMessagePresenter.cs b/src/Views/CommitMessagePresenter.cs index dc8a3bb7..002beda1 100644 --- a/src/Views/CommitMessagePresenter.cs +++ b/src/Views/CommitMessagePresenter.cs @@ -1,10 +1,8 @@ using System; using System.Collections.Generic; -using System.Text.RegularExpressions; using System.Threading.Tasks; using Avalonia; -using Avalonia.Collections; using Avalonia.Controls; using Avalonia.Controls.Documents; using Avalonia.Input; @@ -13,27 +11,15 @@ using Avalonia.VisualTree; namespace SourceGit.Views { - public partial class CommitMessagePresenter : SelectableTextBlock + public class CommitMessagePresenter : SelectableTextBlock { - [GeneratedRegex(@"\b([0-9a-fA-F]{6,40})\b")] - private static partial Regex REG_SHA_FORMAT(); + public static readonly StyledProperty FullMessageProperty = + AvaloniaProperty.Register(nameof(FullMessage)); - public static readonly StyledProperty MessageProperty = - AvaloniaProperty.Register(nameof(Message)); - - public string Message + public Models.CommitFullMessage FullMessage { - get => GetValue(MessageProperty); - set => SetValue(MessageProperty, value); - } - - public static readonly StyledProperty> IssueTrackerRulesProperty = - AvaloniaProperty.Register>(nameof(IssueTrackerRules)); - - public AvaloniaList IssueTrackerRules - { - get => GetValue(IssueTrackerRulesProperty); - set => SetValue(IssueTrackerRulesProperty, value); + get => GetValue(FullMessageProperty); + set => SetValue(FullMessageProperty, value); } protected override Type StyleKeyOverride => typeof(SelectableTextBlock); @@ -42,69 +28,36 @@ namespace SourceGit.Views { base.OnPropertyChanged(change); - if (change.Property == MessageProperty || change.Property == IssueTrackerRulesProperty) + if (change.Property == FullMessageProperty) { Inlines!.Clear(); _inlineCommits.Clear(); - _matches = null; _lastHover = null; ClearHoveredIssueLink(); - var message = Message; + var message = FullMessage?.Message; if (string.IsNullOrEmpty(message)) return; - var matches = new List(); - if (IssueTrackerRules is { Count: > 0 } rules) - { - foreach (var rule in rules) - rule.Matches(matches, message); - } - - var shas = REG_SHA_FORMAT().Matches(message); - for (int i = 0; i < shas.Count; i++) - { - var sha = shas[i]; - if (!sha.Success) - continue; - - var start = sha.Index; - var len = sha.Length; - var intersect = false; - foreach (var match in matches) - { - if (match.Intersect(start, len)) - { - intersect = true; - break; - } - } - - if (!intersect) - matches.Add(new Models.Hyperlink(start, len, sha.Groups[1].Value, true)); - } - - if (matches.Count == 0) + var links = FullMessage?.Links; + if (links == null || links.Count == 0) { Inlines.Add(new Run(message)); return; } - matches.Sort((l, r) => l.Start - r.Start); - _matches = matches; - var inlines = new List(); var pos = 0; - foreach (var match in matches) + foreach (var link in links) { - if (match.Start > pos) - inlines.Add(new Run(message.Substring(pos, match.Start - pos))); + if (link.Start > pos) + inlines.Add(new Run(message.Substring(pos, link.Start - pos))); - var link = new Run(message.Substring(match.Start, match.Length)); - link.Classes.Add(match.IsCommitSHA ? "commit_link" : "issue_link"); - inlines.Add(link); + var run = new Run(message.Substring(link.Start, link.Length)); + run.Classes.Add(link.IsCommitSHA ? "commit_link" : "issue_link"); + inlines.Add(run); - pos = match.Start + match.Length; + pos = link.Start + link.Length; } if (pos < message.Length) @@ -134,7 +87,7 @@ namespace SourceGit.Views scrollViewer.LineDown(); } } - else if (_matches != null) + else if (FullMessage is { Links: { Count: > 0 } links }) { var point = e.GetPosition(this) - new Point(Padding.Left, Padding.Top); var x = Math.Min(Math.Max(point.X, 0), Math.Max(TextLayout.WidthIncludingTrailingWhitespace, 0)); @@ -142,25 +95,25 @@ namespace SourceGit.Views point = new Point(x, y); var pos = TextLayout.HitTestPoint(point).TextPosition; - foreach (var match in _matches) + foreach (var link in links) { - if (!match.Intersect(pos, 1)) + if (!link.Intersect(pos, 1)) continue; - if (match == _lastHover) + if (link == _lastHover) return; SetCurrentValue(CursorProperty, Cursor.Parse("Hand")); - _lastHover = match; - if (!match.IsCommitSHA) + _lastHover = link; + if (!link.IsCommitSHA) { - ToolTip.SetTip(this, match.Link); + ToolTip.SetTip(this, link.Link); ToolTip.SetIsOpen(this, true); } else { - ProcessHoverCommitLink(match); + ProcessHoverCommitLink(link); } return; @@ -361,7 +314,6 @@ namespace SourceGit.Views } } - private List _matches = null; private Models.Hyperlink _lastHover = null; private Dictionary _inlineCommits = new(); } diff --git a/src/Views/DiffView.axaml b/src/Views/DiffView.axaml index 3644f8fc..2842715c 100644 --- a/src/Views/DiffView.axaml +++ b/src/Views/DiffView.axaml @@ -257,7 +257,7 @@ - + @@ -271,7 +271,7 @@ - + diff --git a/src/Views/RevisionFileContentViewer.axaml b/src/Views/RevisionFileContentViewer.axaml index de8b3b75..f7599ba0 100644 --- a/src/Views/RevisionFileContentViewer.axaml +++ b/src/Views/RevisionFileContentViewer.axaml @@ -63,7 +63,7 @@ - + diff --git a/src/Views/RevisionFiles.axaml b/src/Views/RevisionFiles.axaml index 3d5cff38..6847b14b 100644 --- a/src/Views/RevisionFiles.axaml +++ b/src/Views/RevisionFiles.axaml @@ -44,7 +44,7 @@ + IsOpen="{Binding RevisionFileSearchSuggestion, Converter={x:Static c:ListConverters.IsNotNullOrEmpty}}"> 0) { SearchSuggestionBox.Focus(NavigationMethod.Tab); SearchSuggestionBox.SelectedIndex = 0; @@ -33,12 +33,7 @@ namespace SourceGit.Views } else if (e.Key == Key.Escape) { - if (vm.IsRevisionFileSearchSuggestionOpen) - { - vm.RevisionFileSearchSuggestion.Clear(); - vm.IsRevisionFileSearchSuggestionOpen = false; - } - + vm.CancelRevisionFileSuggestions(); e.Handled = true; } } @@ -57,7 +52,7 @@ namespace SourceGit.Views if (e.Key == Key.Escape) { - vm.RevisionFileSearchSuggestion.Clear(); + vm.CancelRevisionFileSuggestions(); e.Handled = true; } else if (e.Key == Key.Enter && SearchSuggestionBox.SelectedItem is string content) From 96538b9a6246ceebbb259cbdfd7abc3dc8239132 Mon Sep 17 00:00:00 2001 From: leo Date: Tue, 4 Mar 2025 16:26:14 +0800 Subject: [PATCH 09/37] fix: there's an extra line-ending while copy multiple lines from text diff view (#1049) Signed-off-by: leo --- src/Views/TextDiffView.axaml.cs | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/src/Views/TextDiffView.axaml.cs b/src/Views/TextDiffView.axaml.cs index 958e803e..30034bc6 100644 --- a/src/Views/TextDiffView.axaml.cs +++ b/src/Views/TextDiffView.axaml.cs @@ -1032,18 +1032,32 @@ namespace SourceGit.Views line.Type == Models.TextDiffLineType.None) continue; + // The first selected line (partial selection) if (i == startIdx && startPosition.Column > 1) { builder.AppendLine(line.Content.Substring(startPosition.Column - 1)); continue; } - if (i == endIdx && endPosition.Column < line.Content.Length) + // The selection range is larger than original source. + if (i == lines.Count - 1 && i < endIdx) { - builder.AppendLine(line.Content.Substring(0, endPosition.Column - 1)); - continue; + builder.Append(line.Content); + break; } + // For the last line (selection range is within original source) + if (i == endIdx) + { + if (endPosition.Column < line.Content.Length) + builder.Append(line.Content.Substring(0, endPosition.Column - 1)); + else + builder.Append(line.Content); + + break; + } + + // Other lines. builder.AppendLine(line.Content); } From 25e6e261a626898942eeb981f75981caab307ff0 Mon Sep 17 00:00:00 2001 From: GadflyFang Date: Tue, 4 Mar 2025 16:33:43 +0800 Subject: [PATCH 10/37] refactor: Improve key modifier checks and AltGr detection (#1051) --- src/Views/Launcher.axaml.cs | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/src/Views/Launcher.axaml.cs b/src/Views/Launcher.axaml.cs index e3140191..b8c09b35 100644 --- a/src/Views/Launcher.axaml.cs +++ b/src/Views/Launcher.axaml.cs @@ -112,8 +112,19 @@ namespace SourceGit.Views // We should clear all unhandled key modifiers. _unhandledModifiers = KeyModifiers.None; + // Check for AltGr (which is detected as Ctrl+Alt) + bool isAltGr = e.KeyModifiers.HasFlag(KeyModifiers.Control) && + e.KeyModifiers.HasFlag(KeyModifiers.Alt); + + // Skip hotkey processing if AltGr is pressed + if (isAltGr) + { + base.OnKeyDown(e); + return; + } + // Ctrl+Shift+P opens preference dialog (macOS use hotkeys in system menu bar) - if (!OperatingSystem.IsMacOS() && e.KeyModifiers == (KeyModifiers.Control | KeyModifiers.Shift) && e.Key == Key.P) + if (!OperatingSystem.IsMacOS() && e is { KeyModifiers: (KeyModifiers.Control | KeyModifiers.Shift), Key: Key.P }) { App.OpenDialog(new Preferences()); e.Handled = true; @@ -243,13 +254,13 @@ namespace SourceGit.Views { _unhandledModifiers = e.KeyModifiers; - if (!_unhandledModifiers.HasFlag(KeyModifiers.Alt) && (e.Key == Key.LeftAlt || e.Key == Key.RightAlt)) + if (!_unhandledModifiers.HasFlag(KeyModifiers.Alt) && e.Key is Key.LeftAlt or Key.RightAlt) _unhandledModifiers |= KeyModifiers.Alt; - if (!_unhandledModifiers.HasFlag(KeyModifiers.Control) && (e.Key == Key.LeftCtrl || e.Key == Key.RightCtrl)) + if (!_unhandledModifiers.HasFlag(KeyModifiers.Control) && e.Key is Key.LeftCtrl or Key.RightCtrl) _unhandledModifiers |= KeyModifiers.Control; - if (!_unhandledModifiers.HasFlag(KeyModifiers.Shift) && (e.Key == Key.LeftShift || e.Key == Key.RightShift)) + if (!_unhandledModifiers.HasFlag(KeyModifiers.Shift) && e.Key is Key.LeftShift or Key.RightShift) _unhandledModifiers |= KeyModifiers.Shift; } } From 68e96f428e650cf29bde1b27276cf63456fd5530 Mon Sep 17 00:00:00 2001 From: GadflyFang Date: Tue, 4 Mar 2025 16:34:51 +0800 Subject: [PATCH 11/37] fix: validate result not update #1052 (#1053) --- src/ViewModels/Pull.cs | 2 +- src/ViewModels/Push.cs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ViewModels/Pull.cs b/src/ViewModels/Pull.cs index 755b58f4..ff557792 100644 --- a/src/ViewModels/Pull.cs +++ b/src/ViewModels/Pull.cs @@ -35,7 +35,7 @@ namespace SourceGit.ViewModels public Models.Branch SelectedBranch { get => _selectedBranch; - set => SetProperty(ref _selectedBranch, value); + set => SetProperty(ref _selectedBranch, value, true); } public Models.DealWithLocalChanges PreAction diff --git a/src/ViewModels/Push.cs b/src/ViewModels/Push.cs index 1f18b38e..fb06c76e 100644 --- a/src/ViewModels/Push.cs +++ b/src/ViewModels/Push.cs @@ -18,7 +18,7 @@ namespace SourceGit.ViewModels get => _selectedLocalBranch; set { - if (SetProperty(ref _selectedLocalBranch, value)) + if (SetProperty(ref _selectedLocalBranch, value, true)) AutoSelectBranchByRemote(); } } @@ -39,7 +39,7 @@ namespace SourceGit.ViewModels get => _selectedRemote; set { - if (SetProperty(ref _selectedRemote, value)) + if (SetProperty(ref _selectedRemote, value, true)) AutoSelectBranchByRemote(); } } @@ -56,7 +56,7 @@ namespace SourceGit.ViewModels get => _selectedRemoteBranch; set { - if (SetProperty(ref _selectedRemoteBranch, value)) + if (SetProperty(ref _selectedRemoteBranch, value, true)) IsSetTrackOptionVisible = value != null && _selectedLocalBranch.Upstream != value.FullName; } } From 11af5d9b29ba0f994892e336f13e119fa9c9730b Mon Sep 17 00:00:00 2001 From: leo Date: Tue, 4 Mar 2025 16:42:32 +0800 Subject: [PATCH 12/37] code_style: use `?:` operator instead of `if ... else` statement Signed-off-by: leo --- src/Views/TextDiffView.axaml.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/Views/TextDiffView.axaml.cs b/src/Views/TextDiffView.axaml.cs index 30034bc6..93d31668 100644 --- a/src/Views/TextDiffView.axaml.cs +++ b/src/Views/TextDiffView.axaml.cs @@ -1049,11 +1049,7 @@ namespace SourceGit.Views // For the last line (selection range is within original source) if (i == endIdx) { - if (endPosition.Column < line.Content.Length) - builder.Append(line.Content.Substring(0, endPosition.Column - 1)); - else - builder.Append(line.Content); - + builder.Append(endPosition.Column - 1 < line.Content.Length ? line.Content.Substring(0, endPosition.Column - 1) : line.Content); break; } From 2137ad9ec97c2c8aacb78773af2999a5d7f9d57b Mon Sep 17 00:00:00 2001 From: leo Date: Tue, 4 Mar 2025 17:26:27 +0800 Subject: [PATCH 13/37] enhance: allow to configure editor tab width in preferences window (#1048) Signed-off-by: leo --- src/Resources/Locales/en_US.axaml | 1 + src/Resources/Locales/zh_CN.axaml | 1 + src/Resources/Locales/zh_TW.axaml | 1 + src/ViewModels/Preferences.cs | 7 ++++ src/Views/Blame.axaml | 1 + src/Views/Blame.axaml.cs | 19 +++++++++-- src/Views/Preferences.axaml | 35 ++++++++++++++------ src/Views/RevisionFileContentViewer.axaml | 1 + src/Views/RevisionFileContentViewer.axaml.cs | 26 +++++++++++++-- src/Views/TextDiffView.axaml | 3 ++ src/Views/TextDiffView.axaml.cs | 22 +++++++++--- 11 files changed, 98 insertions(+), 19 deletions(-) diff --git a/src/Resources/Locales/en_US.axaml b/src/Resources/Locales/en_US.axaml index 5208a269..5df3ca71 100644 --- a/src/Resources/Locales/en_US.axaml +++ b/src/Resources/Locales/en_US.axaml @@ -464,6 +464,7 @@ Enable Streaming APPEARANCE Default Font + Editor Tab Width Font Size Default Editor diff --git a/src/Resources/Locales/zh_CN.axaml b/src/Resources/Locales/zh_CN.axaml index 0e8b9f7d..72b434f9 100644 --- a/src/Resources/Locales/zh_CN.axaml +++ b/src/Resources/Locales/zh_CN.axaml @@ -467,6 +467,7 @@ 启用流式输出 外观配置 缺省字体 + 编辑器制表符宽度 字体大小 默认 代码编辑器 diff --git a/src/Resources/Locales/zh_TW.axaml b/src/Resources/Locales/zh_TW.axaml index f4cfad08..bc9991f6 100644 --- a/src/Resources/Locales/zh_TW.axaml +++ b/src/Resources/Locales/zh_TW.axaml @@ -467,6 +467,7 @@ 啟用串流輸出 外觀設定 預設字型 + 編輯器制表符寬度 字型大小 預設 程式碼 diff --git a/src/ViewModels/Preferences.cs b/src/ViewModels/Preferences.cs index 5cd7c8a4..016fd4c4 100644 --- a/src/ViewModels/Preferences.cs +++ b/src/ViewModels/Preferences.cs @@ -111,6 +111,12 @@ namespace SourceGit.ViewModels set => SetProperty(ref _editorFontSize, value); } + public int EditorTabWidth + { + get => _editorTabWidth; + set => SetProperty(ref _editorTabWidth, value); + } + public LayoutInfo Layout { get => _layout; @@ -649,6 +655,7 @@ namespace SourceGit.ViewModels private bool _useSystemWindowFrame = false; private double _defaultFontSize = 13; private double _editorFontSize = 13; + private int _editorTabWidth = 4; private LayoutInfo _layout = new LayoutInfo(); private int _maxHistoryCommits = 20000; diff --git a/src/Views/Blame.axaml b/src/Views/Blame.axaml index 1ddb18fb..b700b580 100644 --- a/src/Views/Blame.axaml +++ b/src/Views/Blame.axaml @@ -58,6 +58,7 @@ Foreground="{DynamicResource Brush.FG1}" FontFamily="{DynamicResource Fonts.Monospace}" FontSize="{Binding Source={x:Static vm:Preferences.Instance}, Path=EditorFontSize}" + TabWidth="{Binding Source={x:Static vm:Preferences.Instance}, Path=EditorTabWidth}" BlameData="{Binding Data}"/> diff --git a/src/Views/Blame.axaml.cs b/src/Views/Blame.axaml.cs index 1784efc1..7e273998 100644 --- a/src/Views/Blame.axaml.cs +++ b/src/Views/Blame.axaml.cs @@ -260,6 +260,15 @@ namespace SourceGit.Views set => SetValue(BlameDataProperty, value); } + public static readonly StyledProperty TabWidthProperty = + AvaloniaProperty.Register(nameof(TabWidth), 4); + + public int TabWidth + { + get => GetValue(TabWidthProperty); + set => SetValue(TabWidthProperty, value); + } + protected override Type StyleKeyOverride => typeof(TextEditor); public BlameTextEditor() : base(new TextArea(), new TextDocument()) @@ -268,6 +277,10 @@ namespace SourceGit.Views ShowLineNumbers = false; WordWrap = false; + Options.IndentationSize = TabWidth; + Options.EnableHyperlinks = false; + Options.EnableEmailHyperlinks = false; + _textMate = Models.TextMateHelper.CreateForEditor(this); TextArea.LeftMargins.Add(new LineNumberMargin() { Margin = new Thickness(8, 0) }); @@ -280,8 +293,6 @@ namespace SourceGit.Views TextArea.TextView.ContextRequested += OnTextViewContextRequested; TextArea.TextView.VisualLinesChanged += OnTextViewVisualLinesChanged; TextArea.TextView.Margin = new Thickness(4, 0); - TextArea.TextView.Options.EnableHyperlinks = false; - TextArea.TextView.Options.EnableEmailHyperlinks = false; } public override void Render(DrawingContext context) @@ -350,6 +361,10 @@ namespace SourceGit.Views Text = string.Empty; } } + else if (change.Property == TabWidthProperty) + { + Options.IndentationSize = TabWidth; + } else if (change.Property.Name == "ActualThemeVariant" && change.NewValue != null) { Models.TextMateHelper.SetThemeByApp(_textMate); diff --git a/src/Views/Preferences.axaml b/src/Views/Preferences.axaml index 3bdd150a..12679e50 100644 --- a/src/Views/Preferences.axaml +++ b/src/Views/Preferences.axaml @@ -148,7 +148,7 @@ - + + Minimum="10" Maximum="18" Increment="0.5" + Height="28" + Padding="4" + BorderThickness="1" BorderBrush="{DynamicResource Brush.Border1}" + CornerRadius="3" + Value="{Binding DefaultFontSize, Mode=TwoWay}"> @@ -218,10 +218,23 @@ + + + + + - @@ -232,16 +245,16 @@ - - - diff --git a/src/Views/RevisionFileContentViewer.axaml.cs b/src/Views/RevisionFileContentViewer.axaml.cs index c74f2db2..16f4fc83 100644 --- a/src/Views/RevisionFileContentViewer.axaml.cs +++ b/src/Views/RevisionFileContentViewer.axaml.cs @@ -15,6 +15,15 @@ namespace SourceGit.Views { public class RevisionTextFileView : TextEditor { + public static readonly StyledProperty TabWidthProperty = + AvaloniaProperty.Register(nameof(TabWidth), 4); + + public int TabWidth + { + get => GetValue(TabWidthProperty); + set => SetValue(TabWidthProperty, value); + } + protected override Type StyleKeyOverride => typeof(TextEditor); public RevisionTextFileView() : base(new TextArea(), new TextDocument()) @@ -22,13 +31,16 @@ namespace SourceGit.Views IsReadOnly = true; ShowLineNumbers = true; WordWrap = false; + + Options.IndentationSize = TabWidth; + Options.EnableHyperlinks = false; + Options.EnableEmailHyperlinks = false; + HorizontalScrollBarVisibility = ScrollBarVisibility.Auto; VerticalScrollBarVisibility = ScrollBarVisibility.Auto; TextArea.LeftMargins[0].Margin = new Thickness(8, 0); TextArea.TextView.Margin = new Thickness(4, 0); - TextArea.TextView.Options.EnableHyperlinks = false; - TextArea.TextView.Options.EnableEmailHyperlinks = false; } protected override void OnLoaded(RoutedEventArgs e) @@ -69,6 +81,16 @@ namespace SourceGit.Views } } + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) + { + base.OnPropertyChanged(change); + + if (change.Property == TabWidthProperty) + { + Options.IndentationSize = TabWidth; + } + } + private void OnTextViewContextRequested(object sender, ContextRequestedEventArgs e) { var selected = SelectedText; diff --git a/src/Views/TextDiffView.axaml b/src/Views/TextDiffView.axaml index 3ea7af14..7ea6b44b 100644 --- a/src/Views/TextDiffView.axaml +++ b/src/Views/TextDiffView.axaml @@ -27,6 +27,7 @@ IndicatorForeground="{DynamicResource Brush.FG2}" FontFamily="{DynamicResource Fonts.Monospace}" FontSize="{Binding Source={x:Static vm:Preferences.Instance}, Path=EditorFontSize}" + TabWidth="{Binding Source={x:Static vm:Preferences.Instance}, Path=EditorTabWidth}" UseSyntaxHighlighting="{Binding Source={x:Static vm:Preferences.Instance}, Path=UseSyntaxHighlighting}" WordWrap="{Binding Source={x:Static vm:Preferences.Instance}, Path=EnableDiffViewWordWrap}" ShowHiddenSymbols="{Binding Source={x:Static vm:Preferences.Instance}, Path=ShowHiddenSymbolsInDiffView}" @@ -59,6 +60,7 @@ IndicatorForeground="{DynamicResource Brush.FG2}" FontFamily="{DynamicResource Fonts.Monospace}" FontSize="{Binding Source={x:Static vm:Preferences.Instance}, Path=EditorFontSize}" + TabWidth="{Binding Source={x:Static vm:Preferences.Instance}, Path=EditorTabWidth}" UseSyntaxHighlighting="{Binding Source={x:Static vm:Preferences.Instance}, Path=UseSyntaxHighlighting}" WordWrap="False" ShowHiddenSymbols="{Binding Source={x:Static vm:Preferences.Instance}, Path=ShowHiddenSymbolsInDiffView}" @@ -81,6 +83,7 @@ IndicatorForeground="{DynamicResource Brush.FG2}" FontFamily="{DynamicResource Fonts.Monospace}" FontSize="{Binding Source={x:Static vm:Preferences.Instance}, Path=EditorFontSize}" + TabWidth="{Binding Source={x:Static vm:Preferences.Instance}, Path=EditorTabWidth}" UseSyntaxHighlighting="{Binding Source={x:Static vm:Preferences.Instance}, Path=UseSyntaxHighlighting}" WordWrap="False" ShowHiddenSymbols="{Binding Source={x:Static vm:Preferences.Instance}, Path=ShowHiddenSymbolsInDiffView}" diff --git a/src/Views/TextDiffView.axaml.cs b/src/Views/TextDiffView.axaml.cs index 93d31668..630be1b2 100644 --- a/src/Views/TextDiffView.axaml.cs +++ b/src/Views/TextDiffView.axaml.cs @@ -475,6 +475,15 @@ namespace SourceGit.Views set => SetValue(ShowHiddenSymbolsProperty, value); } + public static readonly StyledProperty TabWidthProperty = + AvaloniaProperty.Register(nameof(TabWidth), 4); + + public int TabWidth + { + get => GetValue(TabWidthProperty); + set => SetValue(TabWidthProperty, value); + } + public static readonly StyledProperty EnableChunkSelectionProperty = AvaloniaProperty.Register(nameof(EnableChunkSelection)); @@ -519,12 +528,13 @@ namespace SourceGit.Views ShowLineNumbers = false; BorderThickness = new Thickness(0); + Options.IndentationSize = TabWidth; + Options.EnableHyperlinks = false; + Options.EnableEmailHyperlinks = false; + _lineStyleTransformer = new LineStyleTransformer(this); TextArea.TextView.Margin = new Thickness(2, 0); - TextArea.TextView.Options.EnableHyperlinks = false; - TextArea.TextView.Options.EnableEmailHyperlinks = false; - TextArea.TextView.BackgroundRenderers.Add(new LineBackgroundRenderer(this)); TextArea.TextView.LineTransformers.Add(_lineStyleTransformer); } @@ -734,10 +744,14 @@ namespace SourceGit.Views } else if (change.Property == ShowHiddenSymbolsProperty) { - var val = change.NewValue is true; + var val = ShowHiddenSymbols; Options.ShowTabs = val; Options.ShowSpaces = val; } + else if (change.Property == TabWidthProperty) + { + Options.IndentationSize = TabWidth; + } else if (change.Property == FileNameProperty) { Models.TextMateHelper.SetGrammarByFileName(_textMate, FileName); From 5af856b9da238d552e8be47fbc002af7efae0742 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 4 Mar 2025 09:26:48 +0000 Subject: [PATCH 14/37] doc: Update translation status and missing keys --- README.md | 2 +- TRANSLATION.md | 19 ++++++++++++------- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index dae76783..849407b3 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ ## Translation Status -[![en_US](https://img.shields.io/badge/en__US-%E2%88%9A-brightgreen)](TRANSLATION.md) [![de__DE](https://img.shields.io/badge/de__DE-99.21%25-yellow)](TRANSLATION.md) [![es__ES](https://img.shields.io/badge/es__ES-99.87%25-yellow)](TRANSLATION.md) [![fr__FR](https://img.shields.io/badge/fr__FR-91.80%25-yellow)](TRANSLATION.md) [![it__IT](https://img.shields.io/badge/it__IT-%E2%88%9A-brightgreen)](TRANSLATION.md) [![pt__BR](https://img.shields.io/badge/pt__BR-91.53%25-yellow)](TRANSLATION.md) [![ru__RU](https://img.shields.io/badge/ru__RU-99.07%25-yellow)](TRANSLATION.md) [![zh__CN](https://img.shields.io/badge/zh__CN-%E2%88%9A-brightgreen)](TRANSLATION.md) [![zh__TW](https://img.shields.io/badge/zh__TW-%E2%88%9A-brightgreen)](TRANSLATION.md) +[![en_US](https://img.shields.io/badge/en__US-%E2%88%9A-brightgreen)](TRANSLATION.md) [![de__DE](https://img.shields.io/badge/de__DE-99.08%25-yellow)](TRANSLATION.md) [![es__ES](https://img.shields.io/badge/es__ES-99.74%25-yellow)](TRANSLATION.md) [![fr__FR](https://img.shields.io/badge/fr__FR-91.68%25-yellow)](TRANSLATION.md) [![it__IT](https://img.shields.io/badge/it__IT-99.87%25-yellow)](TRANSLATION.md) [![pt__BR](https://img.shields.io/badge/pt__BR-91.41%25-yellow)](TRANSLATION.md) [![ru__RU](https://img.shields.io/badge/ru__RU-98.94%25-yellow)](TRANSLATION.md) [![zh__CN](https://img.shields.io/badge/zh__CN-%E2%88%9A-brightgreen)](TRANSLATION.md) [![zh__TW](https://img.shields.io/badge/zh__TW-%E2%88%9A-brightgreen)](TRANSLATION.md) > [!NOTE] > You can find the missing keys in [TRANSLATION.md](TRANSLATION.md) diff --git a/TRANSLATION.md b/TRANSLATION.md index 716ddd3a..8a7bd378 100644 --- a/TRANSLATION.md +++ b/TRANSLATION.md @@ -1,4 +1,4 @@ -### de_DE.axaml: 99.21% +### de_DE.axaml: 99.08%
@@ -9,21 +9,23 @@ - Text.Diff.First - Text.Diff.Last - Text.Preferences.AI.Streaming +- Text.Preferences.Appearance.EditorTabWidth - Text.StashCM.SaveAsPatch
-### es_ES.axaml: 99.87% +### es_ES.axaml: 99.74%
Missing Keys +- Text.Preferences.Appearance.EditorTabWidth - Text.StashCM.SaveAsPatch
-### fr_FR.axaml: 91.80% +### fr_FR.axaml: 91.68%
@@ -56,6 +58,7 @@ - Text.MergeMultiple.Strategy - Text.MergeMultiple.Targets - Text.Preferences.AI.Streaming +- Text.Preferences.Appearance.EditorTabWidth - Text.Preferences.Appearance.FontSize - Text.Preferences.Appearance.FontSize.Default - Text.Preferences.Appearance.FontSize.Editor @@ -94,17 +97,17 @@
-### it_IT.axaml: 100.00% +### it_IT.axaml: 99.87%
Missing Keys - +- Text.Preferences.Appearance.EditorTabWidth
-### pt_BR.axaml: 91.53% +### pt_BR.axaml: 91.41%
@@ -148,6 +151,7 @@ - Text.MergeMultiple.Strategy - Text.MergeMultiple.Targets - Text.Preferences.AI.Streaming +- Text.Preferences.Appearance.EditorTabWidth - Text.Preferences.General.DateFormat - Text.Preferences.General.ShowChildren - Text.Preferences.Git.SSLVerify @@ -177,7 +181,7 @@
-### ru_RU.axaml: 99.07% +### ru_RU.axaml: 98.94%
@@ -189,6 +193,7 @@ - Text.Diff.First - Text.Diff.Last - Text.Preferences.AI.Streaming +- Text.Preferences.Appearance.EditorTabWidth - Text.StashCM.SaveAsPatch
From e28f8611ef74bf259096b322d6c91531bc4ceeac Mon Sep 17 00:00:00 2001 From: leo Date: Tue, 4 Mar 2025 17:39:07 +0800 Subject: [PATCH 15/37] ux: re-design the layout for `Interactive Rebase` window that tries to fix issue #1037 Signed-off-by: leo --- src/Views/InteractiveRebase.axaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Views/InteractiveRebase.axaml b/src/Views/InteractiveRebase.axaml index f031479b..a4c4dc8e 100644 --- a/src/Views/InteractiveRebase.axaml +++ b/src/Views/InteractiveRebase.axaml @@ -65,6 +65,7 @@ + @@ -77,7 +78,7 @@ - + Date: Tue, 4 Mar 2025 18:02:29 +0800 Subject: [PATCH 16/37] code_style: re-order `CommitDetail` properties Signed-off-by: leo --- src/ViewModels/CommitDetail.cs | 105 +++++++++++++++++---------------- 1 file changed, 54 insertions(+), 51 deletions(-) diff --git a/src/ViewModels/CommitDetail.cs b/src/ViewModels/CommitDetail.cs index 26fa37c1..21931181 100644 --- a/src/ViewModels/CommitDetail.cs +++ b/src/ViewModels/CommitDetail.cs @@ -16,12 +16,6 @@ namespace SourceGit.ViewModels { public partial class CommitDetail : ObservableObject { - public DiffContext DiffContext - { - get => _diffContext; - private set => SetProperty(ref _diffContext, value); - } - public int ActivePageIndex { get => _repo.CommitDetailActivePageIndex; @@ -57,6 +51,18 @@ namespace SourceGit.ViewModels private set => SetProperty(ref _signInfo, value); } + public List WebLinks + { + get; + private set; + } = []; + + public List Children + { + get => _children; + private set => SetProperty(ref _children, value); + } + public List Changes { get => _changes; @@ -84,10 +90,10 @@ namespace SourceGit.ViewModels } } - public List Children + public DiffContext DiffContext { - get => _children; - private set => SetProperty(ref _children, value); + get => _diffContext; + private set => SetProperty(ref _diffContext, value); } public string SearchChangeFilter @@ -108,53 +114,13 @@ namespace SourceGit.ViewModels set => SetProperty(ref _viewRevisionFileContent, value); } - public List WebLinks - { - get; - private set; - } = []; - public string RevisionFileSearchFilter { get => _revisionFileSearchFilter; set { if (SetProperty(ref _revisionFileSearchFilter, value)) - { - if (!string.IsNullOrEmpty(value)) - { - if (_revisionFiles == null) - { - var sha = Commit.SHA; - - Task.Run(() => - { - var files = new Commands.QueryRevisionFileNames(_repo.FullPath, sha).Result(); - var filesList = new List(); - filesList.AddRange(files); - - Dispatcher.UIThread.Invoke(() => - { - if (sha == Commit.SHA) - { - _revisionFiles = filesList; - if (!string.IsNullOrEmpty(_revisionFileSearchFilter)) - UpdateRevisionFileSearchSuggestion(); - } - }); - }); - } - else - { - UpdateRevisionFileSearchSuggestion(); - } - } - else - { - RevisionFileSearchSuggestion = null; - GC.Collect(); - } - } + RefreshRevisionSearchSuggestion(); } } @@ -832,7 +798,44 @@ namespace SourceGit.ViewModels menu.Items.Add(new MenuItem() { Header = "-" }); } - private void UpdateRevisionFileSearchSuggestion() + private void RefreshRevisionSearchSuggestion() + { + if (!string.IsNullOrEmpty(_revisionFileSearchFilter)) + { + if (_revisionFiles == null) + { + var sha = Commit.SHA; + + Task.Run(() => + { + var files = new Commands.QueryRevisionFileNames(_repo.FullPath, sha).Result(); + var filesList = new List(); + filesList.AddRange(files); + + Dispatcher.UIThread.Invoke(() => + { + if (sha == Commit.SHA) + { + _revisionFiles = filesList; + if (!string.IsNullOrEmpty(_revisionFileSearchFilter)) + CalcRevisionFileSearchSuggestion(); + } + }); + }); + } + else + { + CalcRevisionFileSearchSuggestion(); + } + } + else + { + RevisionFileSearchSuggestion = null; + GC.Collect(); + } + } + + private void CalcRevisionFileSearchSuggestion() { var suggestion = new List(); foreach (var file in _revisionFiles) From 792e61b24f8ed6b18dc6d77d7125312baca99a77 Mon Sep 17 00:00:00 2001 From: leo Date: Tue, 4 Mar 2025 18:23:13 +0800 Subject: [PATCH 17/37] ux: re-design the layout for `Interactive Rebase` window that tries to fix issue #1037 Signed-off-by: leo --- src/Views/InteractiveRebase.axaml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Views/InteractiveRebase.axaml b/src/Views/InteractiveRebase.axaml index a4c4dc8e..b4499ddc 100644 --- a/src/Views/InteractiveRebase.axaml +++ b/src/Views/InteractiveRebase.axaml @@ -60,7 +60,7 @@ SelectedItem="{Binding SelectedItem, Mode=OneWayToSource}" ScrollViewer.HorizontalScrollBarVisibility="Disabled" ScrollViewer.VerticalScrollBarVisibility="Auto"> - + - + - + - + - + - + - - + + - + From 71d0b69eee5d48f0118d0c8bd8029a7bf73497bc Mon Sep 17 00:00:00 2001 From: GadflyFang Date: Tue, 4 Mar 2025 19:51:36 +0800 Subject: [PATCH 18/37] fix: prevent kill apt process by accident (#1054) --- build/resources/deb/DEBIAN/preinst | 8 ++++---- build/resources/deb/DEBIAN/prerm | 9 ++++----- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/build/resources/deb/DEBIAN/preinst b/build/resources/deb/DEBIAN/preinst index edcf9088..a93f8090 100755 --- a/build/resources/deb/DEBIAN/preinst +++ b/build/resources/deb/DEBIAN/preinst @@ -12,10 +12,10 @@ set -e case "$1" in install|upgrade) # Check if SourceGit is running and stop it - if pidof -q sourcegit || pgrep -f sourcegit > /dev/null; then - echo "SourceGit is running, stopping it..." - killall sourcegit 2>/dev/null || pkill -f sourcegit 2>/dev/null || true - # Wait for SourceGit to exit + if pgrep -f '/opt/sourcegit/sourcegit' > /dev/null; then + echo "Stopping running SourceGit instance..." + pkill -f '/opt/sourcegit/sourcegit' || true + # Give the process a moment to terminate sleep 1 fi ;; diff --git a/build/resources/deb/DEBIAN/prerm b/build/resources/deb/DEBIAN/prerm index 8ecd4b8d..c2c9e4f0 100755 --- a/build/resources/deb/DEBIAN/prerm +++ b/build/resources/deb/DEBIAN/prerm @@ -15,11 +15,10 @@ set -e case "$1" in remove|upgrade|deconfigure) - # Check if SourceGit is running and stop it - if pidof -q sourcegit || pgrep -f sourcegit > /dev/null; then - echo "SourceGit is running, stopping it before removal..." - killall sourcegit 2>/dev/null || pkill -f sourcegit 2>/dev/null || true - # Wait for SourceGit to exit + if pgrep -f '/opt/sourcegit/sourcegit' > /dev/null; then + echo "Stopping running SourceGit instance..." + pkill -f '/opt/sourcegit/sourcegit' || true + # Give the process a moment to terminate sleep 1 fi ;; From 5e898a809e0b8171a4349371690f5ce491a486ba Mon Sep 17 00:00:00 2001 From: leo Date: Wed, 5 Mar 2025 09:30:38 +0800 Subject: [PATCH 19/37] enhance: check commit hash after intersect testing Signed-off-by: leo --- src/ViewModels/CommitDetail.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/ViewModels/CommitDetail.cs b/src/ViewModels/CommitDetail.cs index 21931181..32827db3 100644 --- a/src/ViewModels/CommitDetail.cs +++ b/src/ViewModels/CommitDetail.cs @@ -665,11 +665,6 @@ namespace SourceGit.ViewModels if (!sha.Success) continue; - var hash = sha.Groups[1].Value; - var test = new Commands.IsCommitSHA(_repo.FullPath, hash).Result(); - if (!test) - continue; - var start = sha.Index; var len = sha.Length; var intersect = false; @@ -682,7 +677,12 @@ namespace SourceGit.ViewModels } } - if (!intersect) + if (intersect) + continue; + + var hash = sha.Groups[1].Value; + var isCommitSHA = new Commands.IsCommitSHA(_repo.FullPath, hash).Result(); + if (isCommitSHA) links.Add(new Models.Hyperlink(start, len, hash, true)); } From 269903503fe77029ec8db1e16121a3b6e830027d Mon Sep 17 00:00:00 2001 From: GadflyFang Date: Wed, 5 Mar 2025 09:36:32 +0800 Subject: [PATCH 20/37] feat: Add commit Tooltip in Blame (#1055) Fixes #993 --- src/ViewModels/Blame.cs | 5 ++++ src/Views/Blame.axaml | 19 ++++++++++++-- src/Views/Blame.axaml.cs | 45 ++++++++++++++++++++++++++------- src/Views/TextDiffView.axaml.cs | 8 +++--- 4 files changed, 62 insertions(+), 15 deletions(-) diff --git a/src/ViewModels/Blame.cs b/src/ViewModels/Blame.cs index 253626bc..7ee39550 100644 --- a/src/ViewModels/Blame.cs +++ b/src/ViewModels/Blame.cs @@ -57,6 +57,11 @@ namespace SourceGit.ViewModels } } + public Models.Commit GetCommitInfo(string commitSHA) + { + return new Commands.QuerySingleCommit(_repo, commitSHA).Result(); + } + private readonly string _repo; private Models.BlameData _data = null; } diff --git a/src/Views/Blame.axaml b/src/Views/Blame.axaml index b700b580..4d050bb5 100644 --- a/src/Views/Blame.axaml +++ b/src/Views/Blame.axaml @@ -2,6 +2,7 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:m="using:SourceGit.Models" xmlns:vm="using:SourceGit.ViewModels" xmlns:v="using:SourceGit.Views" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" @@ -59,8 +60,22 @@ FontFamily="{DynamicResource Fonts.Monospace}" FontSize="{Binding Source={x:Static vm:Preferences.Instance}, Path=EditorFontSize}" TabWidth="{Binding Source={x:Static vm:Preferences.Instance}, Path=EditorTabWidth}" - BlameData="{Binding Data}"/> - + BlameData="{Binding Data}"> + + + + + + + + + + + + + + + + { + var commit = blame.GetCommitInfo(info.CommitSHA); + if (commit == null) + return; + + Dispatcher.UIThread.Invoke(() => + { + if (IsEffectivelyVisible && IsPointerOver) + { + ToolTip.SetTip(this, commit); + ToolTip.SetIsOpen(this, true); + } + }); + }); + } + return; } } - } - Cursor = Cursor.Default; + Cursor = Cursor.Default; + ToolTip.SetIsOpen(this, false); + } } protected override void OnPointerPressed(PointerPressedEventArgs e) @@ -230,9 +257,9 @@ namespace SourceGit.Views private readonly BlameTextEditor _editor = null; } - public class VerticalSeperatorMargin : AbstractMargin + public class VerticalSeparatorMargin : AbstractMargin { - public VerticalSeperatorMargin(BlameTextEditor editor) + public VerticalSeparatorMargin(BlameTextEditor editor) { _editor = editor; } @@ -284,9 +311,9 @@ namespace SourceGit.Views _textMate = Models.TextMateHelper.CreateForEditor(this); TextArea.LeftMargins.Add(new LineNumberMargin() { Margin = new Thickness(8, 0) }); - TextArea.LeftMargins.Add(new VerticalSeperatorMargin(this)); + TextArea.LeftMargins.Add(new VerticalSeparatorMargin(this)); TextArea.LeftMargins.Add(new CommitInfoMargin(this) { Margin = new Thickness(8, 0) }); - TextArea.LeftMargins.Add(new VerticalSeperatorMargin(this)); + TextArea.LeftMargins.Add(new VerticalSeparatorMargin(this)); TextArea.Caret.PositionChanged += OnTextAreaCaretPositionChanged; TextArea.LayoutUpdated += OnTextAreaLayoutUpdated; TextArea.PointerWheelChanged += OnTextAreaPointerWheelChanged; diff --git a/src/Views/TextDiffView.axaml.cs b/src/Views/TextDiffView.axaml.cs index 630be1b2..925c7622 100644 --- a/src/Views/TextDiffView.axaml.cs +++ b/src/Views/TextDiffView.axaml.cs @@ -60,7 +60,7 @@ namespace SourceGit.Views public class ThemedTextDiffPresenter : TextEditor { - public class VerticalSeperatorMargin : AbstractMargin + public class VerticalSeparatorMargin : AbstractMargin { public override void Render(DrawingContext context) { @@ -1085,9 +1085,9 @@ namespace SourceGit.Views public CombinedTextDiffPresenter() : base(new TextArea(), new TextDocument()) { TextArea.LeftMargins.Add(new LineNumberMargin(false, true)); - TextArea.LeftMargins.Add(new VerticalSeperatorMargin()); + TextArea.LeftMargins.Add(new VerticalSeparatorMargin()); TextArea.LeftMargins.Add(new LineNumberMargin(false, false)); - TextArea.LeftMargins.Add(new VerticalSeperatorMargin()); + TextArea.LeftMargins.Add(new VerticalSeparatorMargin()); TextArea.LeftMargins.Add(new LineModifyTypeMargin()); } @@ -1286,7 +1286,7 @@ namespace SourceGit.Views public SingleSideTextDiffPresenter() : base(new TextArea(), new TextDocument()) { TextArea.LeftMargins.Add(new LineNumberMargin(true, false)); - TextArea.LeftMargins.Add(new VerticalSeperatorMargin()); + TextArea.LeftMargins.Add(new VerticalSeparatorMargin()); TextArea.LeftMargins.Add(new LineModifyTypeMargin()); } From fb8d4a2542063a76cff6d41b8fc2ad02de3afa0c Mon Sep 17 00:00:00 2001 From: leo Date: Wed, 5 Mar 2025 09:54:23 +0800 Subject: [PATCH 21/37] code_review: PR #1055 - since the author and time are already shown in the blame view, use the full message as tooltip is better. - cache the commit message in `ViewModels.Blame` since the `Tooltip` of control may change. - querying commit message synchronously (it's very fast) to avoid similar issues in commit details panel. Signed-off-by: leo --- src/ViewModels/Blame.cs | 13 ++++++++++--- src/Views/Blame.axaml | 17 +---------------- src/Views/Blame.axaml.cs | 30 ++++++------------------------ 3 files changed, 17 insertions(+), 43 deletions(-) diff --git a/src/ViewModels/Blame.cs b/src/ViewModels/Blame.cs index 7ee39550..7cfa8eac 100644 --- a/src/ViewModels/Blame.cs +++ b/src/ViewModels/Blame.cs @@ -1,4 +1,5 @@ -using System.Threading.Tasks; +using System.Collections.Generic; +using System.Threading.Tasks; using Avalonia.Threading; @@ -57,12 +58,18 @@ namespace SourceGit.ViewModels } } - public Models.Commit GetCommitInfo(string commitSHA) + public string GetCommitMessage(string sha) { - return new Commands.QuerySingleCommit(_repo, commitSHA).Result(); + if (_commitMessages.TryGetValue(sha, out var msg)) + return msg; + + msg = new Commands.QueryCommitFullMessage(_repo, sha).Result(); + _commitMessages[sha] = msg; + return msg; } private readonly string _repo; private Models.BlameData _data = null; + private Dictionary _commitMessages = new Dictionary(); } } diff --git a/src/Views/Blame.axaml b/src/Views/Blame.axaml index 4d050bb5..d5c3fd22 100644 --- a/src/Views/Blame.axaml +++ b/src/Views/Blame.axaml @@ -2,7 +2,6 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - xmlns:m="using:SourceGit.Models" xmlns:vm="using:SourceGit.ViewModels" xmlns:v="using:SourceGit.Views" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" @@ -60,21 +59,7 @@ FontFamily="{DynamicResource Fonts.Monospace}" FontSize="{Binding Source={x:Static vm:Preferences.Instance}, Path=EditorFontSize}" TabWidth="{Binding Source={x:Static vm:Preferences.Instance}, Path=EditorTabWidth}" - BlameData="{Binding Data}"> - - - - - - - - - - - - - - + BlameData="{Binding Data}"/> - { - var commit = blame.GetCommitInfo(info.CommitSHA); - if (commit == null) - return; - - Dispatcher.UIThread.Invoke(() => - { - if (IsEffectivelyVisible && IsPointerOver) - { - ToolTip.SetTip(this, commit); - ToolTip.SetIsOpen(this, true); - } - }); - }); + var msg = blame.GetCommitMessage(info.CommitSHA); + ToolTip.SetTip(this, msg); + ToolTip.SetIsOpen(this, true); } return; From 991ebe40824f9d42b4fabff69c2a30b1e00e98e9 Mon Sep 17 00:00:00 2001 From: Gadfly Date: Wed, 5 Mar 2025 09:55:55 +0800 Subject: [PATCH 22/37] ci: set ubuntu:20.04 (#1056) --- .github/workflows/build.yml | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d4117364..bcb32580 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -19,14 +19,25 @@ jobs: os: macos-latest runtime: osx-arm64 - name : Linux - os: ubuntu-20.04 + os: ubuntu-latest runtime: linux-x64 + container: ubuntu:20.04 - name : Linux (arm64) - os: ubuntu-20.04 + os: ubuntu-latest runtime: linux-arm64 + container: ubuntu:20.04 name: Build ${{ matrix.name }} runs-on: ${{ matrix.os }} + container: ${{ matrix.container || '' }} steps: + - name: Install common CLI tools + if: ${{ startsWith(matrix.runtime, 'linux-') }} + run: | + export DEBIAN_FRONTEND=noninteractive + ln -fs /usr/share/zoneinfo/Etc/UTC /etc/localtime + apt-get update + apt-get install -y sudo + sudo apt-get install -y curl wget git unzip zip libicu66 tzdata clang - name: Checkout sources uses: actions/checkout@v4 - name: Setup .NET @@ -47,7 +58,7 @@ jobs: if: ${{ matrix.runtime == 'linux-arm64' }} run: | sudo apt-get update - sudo apt-get install clang llvm gcc-aarch64-linux-gnu zlib1g-dev:arm64 + sudo apt-get install -y llvm gcc-aarch64-linux-gnu zlib1g-dev:arm64 - name: Build run: dotnet build -c Release - name: Publish From 81bbe11345522e6ace161747e294c77c3a6f1a81 Mon Sep 17 00:00:00 2001 From: leo Date: Wed, 5 Mar 2025 17:06:45 +0800 Subject: [PATCH 23/37] code_style: rename some variables in `ParseLinksInMessage` Signed-off-by: leo --- src/ViewModels/CommitDetail.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/ViewModels/CommitDetail.cs b/src/ViewModels/CommitDetail.cs index 32827db3..a4f3f595 100644 --- a/src/ViewModels/CommitDetail.cs +++ b/src/ViewModels/CommitDetail.cs @@ -658,15 +658,15 @@ namespace SourceGit.ViewModels rule.Matches(links, message); } - var shas = REG_SHA_FORMAT().Matches(message); - for (int i = 0; i < shas.Count; i++) + var matches = REG_SHA_FORMAT().Matches(message); + for (int i = 0; i < matches.Count; i++) { - var sha = shas[i]; - if (!sha.Success) + var match = matches[i]; + if (!match.Success) continue; - var start = sha.Index; - var len = sha.Length; + var start = match.Index; + var len = match.Length; var intersect = false; foreach (var link in links) { @@ -680,10 +680,10 @@ namespace SourceGit.ViewModels if (intersect) continue; - var hash = sha.Groups[1].Value; - var isCommitSHA = new Commands.IsCommitSHA(_repo.FullPath, hash).Result(); + var sha = match.Groups[1].Value; + var isCommitSHA = new Commands.IsCommitSHA(_repo.FullPath, sha).Result(); if (isCommitSHA) - links.Add(new Models.Hyperlink(start, len, hash, true)); + links.Add(new Models.Hyperlink(start, len, sha, true)); } if (links.Count > 0) From 78c0d8d3344c3315a5a8b022fb893124c703d71a Mon Sep 17 00:00:00 2001 From: leo Date: Thu, 6 Mar 2025 10:37:50 +0800 Subject: [PATCH 24/37] fix: both `--tags` and `--no-tags` are used in `git pull` command Signed-off-by: leo --- src/Commands/Pull.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Commands/Pull.cs b/src/Commands/Pull.cs index a4efa4b6..35a6289a 100644 --- a/src/Commands/Pull.cs +++ b/src/Commands/Pull.cs @@ -11,10 +11,11 @@ namespace SourceGit.Commands Context = repo; TraitErrorAsOutput = true; SSHKey = new Config(repo).Get($"remote.{remote}.sshkey"); - Args = "pull --verbose --progress --tags "; + Args = "pull --verbose --progress "; if (useRebase) - Args += "--rebase "; + Args += "--rebase=true "; + if (noTags) Args += "--no-tags "; From aa0d4b42969271dbc9f42edaadac4d521e09f634 Mon Sep 17 00:00:00 2001 From: leo Date: Thu, 6 Mar 2025 11:50:22 +0800 Subject: [PATCH 25/37] ux: adjust column width of commit hash and time after font size changed (#994) Signed-off-by: leo --- src/Views/Histories.axaml | 10 +++++----- src/Views/InteractiveRebase.axaml | 22 +++++++++++++++++++--- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/src/Views/Histories.axaml b/src/Views/Histories.axaml index 5cde532f..40b2636f 100644 --- a/src/Views/Histories.axaml +++ b/src/Views/Histories.axaml @@ -24,7 +24,7 @@ - + - - + + @@ -121,8 +121,8 @@ - - + + diff --git a/src/Views/InteractiveRebase.axaml b/src/Views/InteractiveRebase.axaml index b4499ddc..1ef2b6cd 100644 --- a/src/Views/InteractiveRebase.axaml +++ b/src/Views/InteractiveRebase.axaml @@ -59,7 +59,8 @@ SelectionMode="Single" SelectedItem="{Binding SelectedItem, Mode=OneWayToSource}" ScrollViewer.HorizontalScrollBarVisibility="Disabled" - ScrollViewer.VerticalScrollBarVisibility="Auto"> + ScrollViewer.VerticalScrollBarVisibility="Auto" + Grid.IsSharedSizeScope="True">