From 1b1dc2f6661cd890df4041463b60a2a9ba8727b4 Mon Sep 17 00:00:00 2001 From: leo Date: Mon, 9 Jun 2025 15:42:02 +0800 Subject: [PATCH 01/31] code_style: remove unnecessary code Signed-off-by: leo --- src/Views/LauncherTabBar.axaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Views/LauncherTabBar.axaml b/src/Views/LauncherTabBar.axaml index f078a598..ef7cbb93 100644 --- a/src/Views/LauncherTabBar.axaml +++ b/src/Views/LauncherTabBar.axaml @@ -87,7 +87,6 @@ FontSize="{Binding Source={x:Static vm:Preferences.Instance}, Path=DefaultFontSize, Converter={x:Static c:DoubleConverters.Decrease}}" TextAlignment="Center" Text="{Binding Node.Name}" - IsVisible="{Binding Node.IsRepository}" IsHitTestVisible="False"/> @@ -99,6 +98,7 @@ Text="{DynamicResource Text.PageTabBar.Welcome.Title}" IsVisible="{Binding !Node.IsRepository}" IsHitTestVisible="False"/> + + + + diff --git a/src/Views/Blame.axaml.cs b/src/Views/Blame.axaml.cs index 00342b0b..47f3809d 100644 --- a/src/Views/Blame.axaml.cs +++ b/src/Views/Blame.axaml.cs @@ -225,7 +225,7 @@ namespace SourceGit.Views { if (DataContext is ViewModels.Blame blame) { - blame.NavigateToCommit(info.CommitSHA); + blame.NavigateToCommit(info.CommitSHA, true); } e.Handled = true; @@ -433,6 +433,8 @@ namespace SourceGit.Views public Blame() { InitializeComponent(); + + AddHandler(PointerReleasedEvent, MouseUpHandler, handledEventsToo: true); } protected override void OnClosed(EventArgs e) @@ -440,5 +442,32 @@ namespace SourceGit.Views base.OnClosed(e); GC.Collect(); } + + private void HistoryBack(object _, RoutedEventArgs e) + { + if (DataContext is ViewModels.Blame blame) + { + blame.Back(); + } + } + private void HistoryForward(object _, RoutedEventArgs e) + { + if (DataContext is ViewModels.Blame blame) + { + blame.Forward(); + } + } + + private void MouseUpHandler(object sender, PointerReleasedEventArgs e) + { + if (e.InitialPressMouseButton == MouseButton.XButton1) + { + HistoryBack(null, null); + } + else if (e.InitialPressMouseButton == MouseButton.XButton2) + { + HistoryForward(null, null); + } + } } } From ee4d8a6a0e50a63bc3700541023db442830c0b89 Mon Sep 17 00:00:00 2001 From: leo Date: Tue, 10 Jun 2025 11:18:20 +0800 Subject: [PATCH 05/31] code_review: PR #1408 Signed-off-by: leo --- src/ViewModels/Blame.cs | 213 ++++++++++++++++++++------------- src/ViewModels/CommitDetail.cs | 4 +- src/Views/Blame.axaml | 55 ++++++--- src/Views/Blame.axaml.cs | 40 +++---- 4 files changed, 182 insertions(+), 130 deletions(-) diff --git a/src/ViewModels/Blame.cs b/src/ViewModels/Blame.cs index 4b57575b..e9917aaf 100644 --- a/src/ViewModels/Blame.cs +++ b/src/ViewModels/Blame.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; using Avalonia.Threading; @@ -10,10 +11,25 @@ namespace SourceGit.ViewModels { public class Blame : ObservableObject { - public string Title + public string FilePath { - get => _title; - private set => SetProperty(ref _title, value); + get; + } + + public Models.Commit Revision + { + get => _revision; + private set => SetProperty(ref _revision, value); + } + + public Models.BlameData Data + { + get => _data; + private set + { + if (SetProperty(ref _data, value)) + OnPropertyChanged(nameof(IsBinary)); + } } public bool IsBinary @@ -21,92 +37,27 @@ namespace SourceGit.ViewModels get => _data != null && _data.IsBinary; } - public bool CanMoveBack + public bool CanBack { - get => _shaHistoryIndex > 0 && _shaHistory.Count > 1; - } - public bool CanMoveForward - { - get => _shaHistoryIndex < _shaHistory.Count - 1; + get => _navigationActiveIndex > 0; } - public Models.BlameData Data + public bool CanForward { - get => _data; - private set => SetProperty(ref _data, value); + get => _navigationActiveIndex < _navigationHistory.Count - 1; } - public Blame(string repo, string file, string revision) + public Blame(string repo, string file, Models.Commit commit) { + var sha = commit.SHA.Substring(0, 10); + + FilePath = file; + Revision = commit; + _repo = repo; - _file = file; - - SetBlameData($"{revision.AsSpan(0, 10)}", true); - } - - private void SetBlameData(string commitSHA, bool resetHistoryForward) - { - Title = $"{_file} @ {commitSHA}"; - - Task.Run(() => - { - var result = new Commands.Blame(_repo, _file, commitSHA).Result(); - Dispatcher.UIThread.Invoke(() => - { - Data = result; - OnPropertyChanged(nameof(IsBinary)); - }); - }); - - if (resetHistoryForward) - { - if (_shaHistoryIndex < _shaHistory.Count - 1) - _shaHistory.RemoveRange(_shaHistoryIndex + 1, _shaHistory.Count - _shaHistoryIndex - 1); - - if (_shaHistory.Count == 0 || _shaHistory[_shaHistoryIndex] != commitSHA) - { - _shaHistory.Add(commitSHA); - _shaHistoryIndex = _shaHistory.Count - 1; - } - } - - OnPropertyChanged(nameof(CanMoveBack)); - OnPropertyChanged(nameof(CanMoveForward)); - } - - public void Back() - { - --_shaHistoryIndex; - if (_shaHistoryIndex < 0) - _shaHistoryIndex = 0; - - NavigateToCommit(_shaHistory[_shaHistoryIndex], false); - } - - public void Forward() - { - ++_shaHistoryIndex; - if (_shaHistoryIndex >= _shaHistory.Count) - _shaHistoryIndex = _shaHistory.Count - 1; - - NavigateToCommit(_shaHistory[_shaHistoryIndex], false); - } - - public void NavigateToCommit(string commitSHA, bool resetHistoryForward) - { - var launcher = App.GetLauncher(); - if (launcher == null) - return; - - foreach (var page in launcher.Pages) - { - if (page.Data is Repository repo && repo.FullPath.Equals(_repo)) - { - repo.NavigateToCommit(commitSHA); - SetBlameData(commitSHA, resetHistoryForward); - break; - } - } + _navigationHistory.Add(sha); + _commits.Add(sha, commit); + SetBlameData(sha); } public string GetCommitMessage(string sha) @@ -119,12 +70,102 @@ namespace SourceGit.ViewModels return msg; } + public void Back() + { + if (_navigationActiveIndex <= 0) + return; + + _navigationActiveIndex--; + OnPropertyChanged(nameof(CanBack)); + OnPropertyChanged(nameof(CanForward)); + NavigateToCommit(_navigationHistory[_navigationActiveIndex]); + } + + public void Forward() + { + if (_navigationActiveIndex >= _navigationHistory.Count - 1) + return; + + _navigationActiveIndex++; + OnPropertyChanged(nameof(CanBack)); + OnPropertyChanged(nameof(CanForward)); + NavigateToCommit(_navigationHistory[_navigationActiveIndex]); + } + + public void NavigateToCommit(string commitSHA) + { + if (!_navigationHistory[_navigationActiveIndex].Equals(commitSHA, StringComparison.Ordinal)) + { + _navigationHistory.Add(commitSHA); + _navigationActiveIndex = _navigationHistory.Count - 1; + OnPropertyChanged(nameof(CanBack)); + OnPropertyChanged(nameof(CanForward)); + } + + if (!Revision.SHA.StartsWith(commitSHA, StringComparison.Ordinal)) + SetBlameData(commitSHA); + + if (App.GetLauncher() is { Pages: { } pages }) + { + foreach (var page in pages) + { + if (page.Data is Repository repo && repo.FullPath.Equals(_repo)) + { + repo.NavigateToCommit(commitSHA); + break; + } + } + } + } + + private void SetBlameData(string commitSHA) + { + if (_cancellationSource is { IsCancellationRequested: false }) + _cancellationSource.Cancel(); + + _cancellationSource = new CancellationTokenSource(); + var token = _cancellationSource.Token; + + if (_commits.TryGetValue(commitSHA, out var c)) + { + Revision = c; + } + else + { + Task.Run(() => + { + var result = new Commands.QuerySingleCommit(_repo, commitSHA).Result(); + + Dispatcher.UIThread.Invoke(() => + { + if (!token.IsCancellationRequested) + { + _commits.Add(commitSHA, result); + Revision = result ?? new Models.Commit() { SHA = commitSHA }; + } + }); + }, token); + } + + Task.Run(() => + { + var result = new Commands.Blame(_repo, FilePath, commitSHA).Result(); + + Dispatcher.UIThread.Invoke(() => + { + if (!token.IsCancellationRequested) + Data = result; + }); + }, token); + } + private string _repo; - private string _file; - private string _title; - private int _shaHistoryIndex = 0; - private List _shaHistory = []; + private Models.Commit _revision; + private CancellationTokenSource _cancellationSource = null; + private int _navigationActiveIndex = 0; + private List _navigationHistory = []; private Models.BlameData _data = null; - private Dictionary _commitMessages = new Dictionary(); + private Dictionary _commits = new(); + private Dictionary _commitMessages = new(); } } diff --git a/src/ViewModels/CommitDetail.cs b/src/ViewModels/CommitDetail.cs index 8d2ade09..3ec168db 100644 --- a/src/ViewModels/CommitDetail.cs +++ b/src/ViewModels/CommitDetail.cs @@ -313,7 +313,7 @@ namespace SourceGit.ViewModels blame.IsEnabled = change.Index != Models.ChangeState.Deleted; blame.Click += (_, ev) => { - App.ShowWindow(new Blame(_repo.FullPath, change.Path, _commit.SHA), false); + App.ShowWindow(new Blame(_repo.FullPath, change.Path, _commit), false); ev.Handled = true; }; @@ -481,7 +481,7 @@ namespace SourceGit.ViewModels blame.IsEnabled = file.Type == Models.ObjectType.Blob; blame.Click += (_, ev) => { - App.ShowWindow(new Blame(_repo.FullPath, file.Path, _commit.SHA), false); + App.ShowWindow(new Blame(_repo.FullPath, file.Path, _commit), false); ev.Handled = true; }; diff --git a/src/Views/Blame.axaml b/src/Views/Blame.axaml index e89bb2ec..6f299f8e 100644 --- a/src/Views/Blame.axaml +++ b/src/Views/Blame.axaml @@ -4,6 +4,7 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:vm="using:SourceGit.ViewModels" xmlns:v="using:SourceGit.Views" + xmlns:c="using:SourceGit.Converters" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="SourceGit.Views.Blame" x:DataType="vm:Blame" @@ -43,22 +44,44 @@ - - - - - - + + + + + + + + + + + + + + + + diff --git a/src/Views/Blame.axaml.cs b/src/Views/Blame.axaml.cs index 47f3809d..72dd605d 100644 --- a/src/Views/Blame.axaml.cs +++ b/src/Views/Blame.axaml.cs @@ -224,9 +224,7 @@ namespace SourceGit.Views if (rect.Contains(pos)) { if (DataContext is ViewModels.Blame blame) - { - blame.NavigateToCommit(info.CommitSHA, true); - } + blame.NavigateToCommit(info.CommitSHA); e.Handled = true; break; @@ -433,8 +431,6 @@ namespace SourceGit.Views public Blame() { InitializeComponent(); - - AddHandler(PointerReleasedEvent, MouseUpHandler, handledEventsToo: true); } protected override void OnClosed(EventArgs e) @@ -443,30 +439,22 @@ namespace SourceGit.Views GC.Collect(); } - private void HistoryBack(object _, RoutedEventArgs e) + protected override void OnPointerReleased(PointerReleasedEventArgs e) { - if (DataContext is ViewModels.Blame blame) - { - blame.Back(); - } - } - private void HistoryForward(object _, RoutedEventArgs e) - { - if (DataContext is ViewModels.Blame blame) - { - blame.Forward(); - } - } + base.OnPointerReleased(e); - private void MouseUpHandler(object sender, PointerReleasedEventArgs e) - { - if (e.InitialPressMouseButton == MouseButton.XButton1) + if (!e.Handled && DataContext is ViewModels.Blame blame) { - HistoryBack(null, null); - } - else if (e.InitialPressMouseButton == MouseButton.XButton2) - { - HistoryForward(null, null); + if (e.InitialPressMouseButton == MouseButton.XButton1) + { + blame.Back(); + e.Handled = true; + } + else if (e.InitialPressMouseButton == MouseButton.XButton2) + { + blame.Forward(); + e.Handled = true; + } } } } From 6c04f5390a28fab8f2327f13ba24d6edd4d78ab0 Mon Sep 17 00:00:00 2001 From: Nathan Baulch Date: Tue, 10 Jun 2025 17:58:57 +1000 Subject: [PATCH 06/31] feature: double tap commit with tracked remote branch checks out local tracking branch (#1409) --- src/ViewModels/Histories.cs | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/ViewModels/Histories.cs b/src/ViewModels/Histories.cs index 6b2208f9..044d436e 100644 --- a/src/ViewModels/Histories.cs +++ b/src/ViewModels/Histories.cs @@ -231,9 +231,24 @@ namespace SourceGit.ViewModels return; } } - else if (d.Type == Models.DecoratorType.RemoteBranchHead && firstRemoteBranch == null) + else if (d.Type == Models.DecoratorType.RemoteBranchHead) { - firstRemoteBranch = _repo.Branches.Find(x => x.FriendlyName == d.Name); + var remoteBranch = _repo.Branches.Find(x => x.FriendlyName == d.Name); + if (remoteBranch != null) + { + var localBranch = _repo.Branches.Find(x => x.IsLocal && x.Upstream == remoteBranch.FullName); + if (localBranch != null) + { + if (!localBranch.IsCurrent) + _repo.CheckoutBranch(localBranch); + return; + } + } + + if (firstRemoteBranch == null) + { + firstRemoteBranch = remoteBranch; + } } } From 7d0536d94b83c64a3d9ff16a5cbe689ee49e36dd Mon Sep 17 00:00:00 2001 From: leo Date: Tue, 10 Jun 2025 16:56:22 +0800 Subject: [PATCH 07/31] feature: when trying to checkout a local branch from its tracking upstream and it is behind the upstream, show `Checkout & Fast-Forward` popup Signed-off-by: leo --- src/Resources/Locales/en_US.axaml | 2 + src/Resources/Locales/zh_CN.axaml | 2 + src/Resources/Locales/zh_TW.axaml | 2 + src/ViewModels/CheckoutAndFastForward.cs | 111 ++++++++++++++++++++++ src/ViewModels/Histories.cs | 8 +- src/ViewModels/Repository.cs | 10 +- src/Views/CheckoutAndFastForward.axaml | 62 ++++++++++++ src/Views/CheckoutAndFastForward.axaml.cs | 12 +++ 8 files changed, 203 insertions(+), 6 deletions(-) create mode 100644 src/ViewModels/CheckoutAndFastForward.cs create mode 100644 src/Views/CheckoutAndFastForward.axaml create mode 100644 src/Views/CheckoutAndFastForward.axaml.cs diff --git a/src/Resources/Locales/en_US.axaml b/src/Resources/Locales/en_US.axaml index afdff5da..ee035551 100644 --- a/src/Resources/Locales/en_US.axaml +++ b/src/Resources/Locales/en_US.axaml @@ -86,6 +86,8 @@ Stash & Reapply Update all submodules Branch: + Checkout & Fast-Forward + Fast-Forward to: Cherry Pick Append source to commit message Commit(s): diff --git a/src/Resources/Locales/zh_CN.axaml b/src/Resources/Locales/zh_CN.axaml index c7e57c97..2a117fb8 100644 --- a/src/Resources/Locales/zh_CN.axaml +++ b/src/Resources/Locales/zh_CN.axaml @@ -90,6 +90,8 @@ 贮藏并自动恢复 同时更新所有子模块 目标分支 : + 检出分支并快进 + 上游分支 : 挑选提交 提交信息中追加来源信息 提交列表 : diff --git a/src/Resources/Locales/zh_TW.axaml b/src/Resources/Locales/zh_TW.axaml index 18a3b04e..29a5346d 100644 --- a/src/Resources/Locales/zh_TW.axaml +++ b/src/Resources/Locales/zh_TW.axaml @@ -90,6 +90,8 @@ 擱置變更並自動復原 同時更新所有子模組 目標分支: + 簽出分支並快轉 + 上游分支 : 揀選提交 提交資訊中追加來源資訊 提交列表: diff --git a/src/ViewModels/CheckoutAndFastForward.cs b/src/ViewModels/CheckoutAndFastForward.cs new file mode 100644 index 00000000..8e62a452 --- /dev/null +++ b/src/ViewModels/CheckoutAndFastForward.cs @@ -0,0 +1,111 @@ +using System.Threading.Tasks; + +namespace SourceGit.ViewModels +{ + public class CheckoutAndFastForward : Popup + { + public Models.Branch LocalBranch + { + get; + } + + public Models.Branch RemoteBrach + { + get; + } + + public bool DiscardLocalChanges + { + get; + set; + } + + public bool IsRecurseSubmoduleVisible + { + get => _repo.Submodules.Count > 0; + } + + public bool RecurseSubmodules + { + get => _repo.Settings.UpdateSubmodulesOnCheckoutBranch; + set => _repo.Settings.UpdateSubmodulesOnCheckoutBranch = value; + } + + public CheckoutAndFastForward(Repository repo, Models.Branch localBranch, Models.Branch remoteBranch) + { + _repo = repo; + LocalBranch = localBranch; + RemoteBrach = remoteBranch; + } + + public override Task Sure() + { + _repo.SetWatcherEnabled(false); + ProgressDescription = $"Checkout and Fast-Forward '{LocalBranch.Name}' ..."; + + var log = _repo.CreateLog($"Checkout and Fast-Forward '{LocalBranch.Name}' ..."); + Use(log); + + var updateSubmodules = IsRecurseSubmoduleVisible && RecurseSubmodules; + return Task.Run(() => + { + var succ = false; + var needPopStash = false; + + if (DiscardLocalChanges) + { + succ = new Commands.Checkout(_repo.FullPath).Use(log).Branch(LocalBranch.Name, RemoteBrach.Head, true, true); + } + else + { + var changes = new Commands.CountLocalChangesWithoutUntracked(_repo.FullPath).Result(); + if (changes > 0) + { + succ = new Commands.Stash(_repo.FullPath).Use(log).Push("CHECKOUT_AND_FASTFORWARD_AUTO_STASH"); + if (!succ) + { + log.Complete(); + CallUIThread(() => _repo.SetWatcherEnabled(true)); + return false; + } + + needPopStash = true; + } + + succ = new Commands.Checkout(_repo.FullPath).Use(log).Branch(LocalBranch.Name, RemoteBrach.Head, false, true); + } + + if (succ) + { + if (updateSubmodules) + { + var submodules = new Commands.QueryUpdatableSubmodules(_repo.FullPath).Result(); + if (submodules.Count > 0) + new Commands.Submodule(_repo.FullPath).Use(log).Update(submodules, true, true); + } + + if (needPopStash) + new Commands.Stash(_repo.FullPath).Use(log).Pop("stash@{0}"); + } + + log.Complete(); + + CallUIThread(() => + { + ProgressDescription = "Waiting for branch updated..."; + + if (_repo.HistoriesFilterMode == Models.FilterMode.Included) + _repo.SetBranchFilterMode(LocalBranch, Models.FilterMode.Included, true, false); + + _repo.MarkBranchesDirtyManually(); + _repo.SetWatcherEnabled(true); + }); + + Task.Delay(400).Wait(); + return succ; + }); + } + + private Repository _repo; + } +} diff --git a/src/ViewModels/Histories.cs b/src/ViewModels/Histories.cs index 044d436e..43f060d7 100644 --- a/src/ViewModels/Histories.cs +++ b/src/ViewModels/Histories.cs @@ -236,11 +236,13 @@ namespace SourceGit.ViewModels var remoteBranch = _repo.Branches.Find(x => x.FriendlyName == d.Name); if (remoteBranch != null) { + // If there's a local branch that is tracking on this remote branch and it does not ahead of + // its upstream, show `Create and Fast-Forward` popup. var localBranch = _repo.Branches.Find(x => x.IsLocal && x.Upstream == remoteBranch.FullName); - if (localBranch != null) + if (localBranch is { TrackStatus: { Ahead: { Count: 0 } } }) { - if (!localBranch.IsCurrent) - _repo.CheckoutBranch(localBranch); + if (_repo.CanCreatePopup()) + _repo.ShowPopup(new CheckoutAndFastForward(_repo, localBranch, remoteBranch)); return; } } diff --git a/src/ViewModels/Repository.cs b/src/ViewModels/Repository.cs index 43a67ff4..af72e72a 100644 --- a/src/ViewModels/Repository.cs +++ b/src/ViewModels/Repository.cs @@ -1316,7 +1316,7 @@ namespace SourceGit.ViewModels { if (branch.IsLocal) { - var worktree = _worktrees.Find(x => x.Branch == branch.FullName); + var worktree = _worktrees.Find(x => x.Branch.Equals(branch.FullName, StringComparison.Ordinal)); if (worktree != null) { OpenWorktree(worktree); @@ -1341,9 +1341,13 @@ namespace SourceGit.ViewModels { foreach (var b in _branches) { - if (b.IsLocal && b.Upstream == branch.FullName) + if (b.IsLocal && + b.Upstream.Equals(branch.FullName, StringComparison.Ordinal) && + b.TrackStatus.Ahead.Count == 0) { - if (!b.IsCurrent) + if (b.TrackStatus.Behind.Count > 0) + ShowPopup(new CheckoutAndFastForward(this, b, branch)); + else if (!b.IsCurrent) CheckoutBranch(b); return; diff --git a/src/Views/CheckoutAndFastForward.axaml b/src/Views/CheckoutAndFastForward.axaml new file mode 100644 index 00000000..725c081e --- /dev/null +++ b/src/Views/CheckoutAndFastForward.axaml @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Views/CheckoutAndFastForward.axaml.cs b/src/Views/CheckoutAndFastForward.axaml.cs new file mode 100644 index 00000000..c54f5a1f --- /dev/null +++ b/src/Views/CheckoutAndFastForward.axaml.cs @@ -0,0 +1,12 @@ +using Avalonia.Controls; + +namespace SourceGit.Views +{ + public partial class CheckoutAndFastForward : UserControl + { + public CheckoutAndFastForward() + { + InitializeComponent(); + } + } +} From 5e303d43d43a2b5cfcf680bb7b72387e5e105ab4 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 10 Jun 2025 09:04:35 +0000 Subject: [PATCH 08/31] doc: Update translation status and sort locale files --- TRANSLATION.md | 42 +++++++++++++++++++++++++++++++++--------- 1 file changed, 33 insertions(+), 9 deletions(-) diff --git a/TRANSLATION.md b/TRANSLATION.md index ba51b82c..40f81e80 100644 --- a/TRANSLATION.md +++ b/TRANSLATION.md @@ -6,13 +6,15 @@ This document shows the translation status of each locale file in the repository ### ![en_US](https://img.shields.io/badge/en__US-%E2%88%9A-brightgreen) -### ![de__DE](https://img.shields.io/badge/de__DE-96.38%25-yellow) +### ![de__DE](https://img.shields.io/badge/de__DE-96.14%25-yellow)
Missing keys in de_DE.axaml - Text.Avatar.Load - Text.BranchCM.ResetToSelectedCommit +- Text.Checkout.WithFastForward +- Text.Checkout.WithFastForward.Upstream - Text.CommitDetail.Changes.Count - Text.CreateBranch.OverwriteExisting - Text.DeinitSubmodule @@ -43,16 +45,18 @@ This document shows the translation status of each locale file in the repository
-### ![es__ES](https://img.shields.io/badge/es__ES-99.88%25-yellow) +### ![es__ES](https://img.shields.io/badge/es__ES-99.63%25-yellow)
Missing keys in es_ES.axaml - Text.Avatar.Load +- Text.Checkout.WithFastForward +- Text.Checkout.WithFastForward.Upstream
-### ![fr__FR](https://img.shields.io/badge/fr__FR-92.26%25-yellow) +### ![fr__FR](https://img.shields.io/badge/fr__FR-92.03%25-yellow)
Missing keys in fr_FR.axaml @@ -67,6 +71,8 @@ This document shows the translation status of each locale file in the repository - Text.Bisect.WaitingForRange - Text.BranchCM.ResetToSelectedCommit - Text.Checkout.RecurseSubmodules +- Text.Checkout.WithFastForward +- Text.Checkout.WithFastForward.Upstream - Text.CommitCM.CopyAuthor - Text.CommitCM.CopyCommitter - Text.CommitCM.CopySubject @@ -122,13 +128,15 @@ This document shows the translation status of each locale file in the repository
-### ![it__IT](https://img.shields.io/badge/it__IT-97.63%25-yellow) +### ![it__IT](https://img.shields.io/badge/it__IT-97.38%25-yellow)
Missing keys in it_IT.axaml - Text.Avatar.Load - Text.BranchCM.ResetToSelectedCommit +- Text.Checkout.WithFastForward +- Text.Checkout.WithFastForward.Upstream - Text.CommitDetail.Changes.Count - Text.CreateBranch.OverwriteExisting - Text.DeinitSubmodule @@ -149,7 +157,7 @@ This document shows the translation status of each locale file in the repository
-### ![ja__JP](https://img.shields.io/badge/ja__JP-92.01%25-yellow) +### ![ja__JP](https://img.shields.io/badge/ja__JP-91.78%25-yellow)
Missing keys in ja_JP.axaml @@ -165,6 +173,8 @@ This document shows the translation status of each locale file in the repository - Text.BranchCM.CompareWithCurrent - Text.BranchCM.ResetToSelectedCommit - Text.Checkout.RecurseSubmodules +- Text.Checkout.WithFastForward +- Text.Checkout.WithFastForward.Upstream - Text.CommitCM.CopyAuthor - Text.CommitCM.CopyCommitter - Text.CommitCM.CopySubject @@ -221,7 +231,7 @@ This document shows the translation status of each locale file in the repository
-### ![pt__BR](https://img.shields.io/badge/pt__BR-84.02%25-yellow) +### ![pt__BR](https://img.shields.io/badge/pt__BR-83.81%25-yellow)
Missing keys in pt_BR.axaml @@ -245,6 +255,8 @@ This document shows the translation status of each locale file in the repository - Text.BranchCM.ResetToSelectedCommit - Text.BranchUpstreamInvalid - Text.Checkout.RecurseSubmodules +- Text.Checkout.WithFastForward +- Text.Checkout.WithFastForward.Upstream - Text.Clone.RecurseSubmodules - Text.CommitCM.CopyAuthor - Text.CommitCM.CopyCommitter @@ -357,9 +369,17 @@ This document shows the translation status of each locale file in the repository
-### ![ru__RU](https://img.shields.io/badge/ru__RU-%E2%88%9A-brightgreen) +### ![ru__RU](https://img.shields.io/badge/ru__RU-99.75%25-yellow) -### ![ta__IN](https://img.shields.io/badge/ta__IN-92.13%25-yellow) +
+Missing keys in ru_RU.axaml + +- Text.Checkout.WithFastForward +- Text.Checkout.WithFastForward.Upstream + +
+ +### ![ta__IN](https://img.shields.io/badge/ta__IN-91.91%25-yellow)
Missing keys in ta_IN.axaml @@ -375,6 +395,8 @@ This document shows the translation status of each locale file in the repository - Text.BranchCM.CompareWithCurrent - Text.BranchCM.ResetToSelectedCommit - Text.Checkout.RecurseSubmodules +- Text.Checkout.WithFastForward +- Text.Checkout.WithFastForward.Upstream - Text.CommitCM.CopyAuthor - Text.CommitCM.CopyCommitter - Text.CommitCM.CopySubject @@ -430,7 +452,7 @@ This document shows the translation status of each locale file in the repository
-### ![uk__UA](https://img.shields.io/badge/uk__UA-93.38%25-yellow) +### ![uk__UA](https://img.shields.io/badge/uk__UA-93.15%25-yellow)
Missing keys in uk_UA.axaml @@ -445,6 +467,8 @@ This document shows the translation status of each locale file in the repository - Text.Bisect.WaitingForRange - Text.BranchCM.ResetToSelectedCommit - Text.Checkout.RecurseSubmodules +- Text.Checkout.WithFastForward +- Text.Checkout.WithFastForward.Upstream - Text.CommitCM.CopyAuthor - Text.CommitCM.CopyCommitter - Text.CommitCM.CopySubject From aa1c8b1cc1fe5a5ed7d4db9925e39406de51630b Mon Sep 17 00:00:00 2001 From: leo Date: Tue, 10 Jun 2025 17:09:57 +0800 Subject: [PATCH 09/31] code_style: use combined expr Signed-off-by: leo --- src/ViewModels/Histories.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ViewModels/Histories.cs b/src/ViewModels/Histories.cs index 43f060d7..448e3f10 100644 --- a/src/ViewModels/Histories.cs +++ b/src/ViewModels/Histories.cs @@ -237,9 +237,9 @@ namespace SourceGit.ViewModels if (remoteBranch != null) { // If there's a local branch that is tracking on this remote branch and it does not ahead of - // its upstream, show `Create and Fast-Forward` popup. + // its upstream, show `Checkout and Fast-Forward` popup. var localBranch = _repo.Branches.Find(x => x.IsLocal && x.Upstream == remoteBranch.FullName); - if (localBranch is { TrackStatus: { Ahead: { Count: 0 } } }) + if (localBranch is { TrackStatus.Ahead.Count: 0 }) { if (_repo.CanCreatePopup()) _repo.ShowPopup(new CheckoutAndFastForward(_repo, localBranch, remoteBranch)); From bcefb773c1d53e981bb934e63c6e2ab71b6c2d6c Mon Sep 17 00:00:00 2001 From: leo Date: Tue, 10 Jun 2025 17:19:37 +0800 Subject: [PATCH 10/31] code_style: remove comment Signed-off-by: leo --- src/ViewModels/Histories.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/ViewModels/Histories.cs b/src/ViewModels/Histories.cs index 448e3f10..a1f71409 100644 --- a/src/ViewModels/Histories.cs +++ b/src/ViewModels/Histories.cs @@ -236,8 +236,6 @@ namespace SourceGit.ViewModels var remoteBranch = _repo.Branches.Find(x => x.FriendlyName == d.Name); if (remoteBranch != null) { - // If there's a local branch that is tracking on this remote branch and it does not ahead of - // its upstream, show `Checkout and Fast-Forward` popup. var localBranch = _repo.Branches.Find(x => x.IsLocal && x.Upstream == remoteBranch.FullName); if (localBranch is { TrackStatus.Ahead.Count: 0 }) { @@ -248,9 +246,7 @@ namespace SourceGit.ViewModels } if (firstRemoteBranch == null) - { firstRemoteBranch = remoteBranch; - } } } From 7c1a8945254cffe6f6b29a95a4cf3a0a95c993ce Mon Sep 17 00:00:00 2001 From: leo Date: Wed, 11 Jun 2025 10:12:03 +0800 Subject: [PATCH 11/31] refactor: do not change original image aspect ratio and do not upscale image in `SWIPE` mode Signed-off-by: leo --- src/Views/ImageContainer.cs | 38 +++++++++++++++++++++++-------------- 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/src/Views/ImageContainer.cs b/src/Views/ImageContainer.cs index 2c37d12a..45acfcd0 100644 --- a/src/Views/ImageContainer.cs +++ b/src/Views/ImageContainer.cs @@ -137,21 +137,12 @@ namespace SourceGit.Views var w = Bounds.Width; var h = Bounds.Height; var x = w * alpha; - var left = OldImage; - if (left != null && alpha > 0) - { - var src = new Rect(0, 0, left.Size.Width * alpha, left.Size.Height); - var dst = new Rect(0, 0, x, h); - context.DrawImage(left, src, dst); - } - var right = NewImage; - if (right != null && alpha < 1) - { - var src = new Rect(right.Size.Width * alpha, 0, right.Size.Width * (1 - alpha), right.Size.Height); - var dst = new Rect(x, 0, w - x, h); - context.DrawImage(right, src, dst); - } + if (OldImage is { } left && alpha > 0) + RenderSingleSide(context, left, new Rect(0, 0, x, h)); + + if (NewImage is { } right && alpha < 1) + RenderSingleSide(context, right, new Rect(x, 0, w - x, h)); context.DrawLine(new Pen(Brushes.DarkGreen, 2), new Point(x, 0), new Point(x, Bounds.Height)); } @@ -233,6 +224,25 @@ namespace SourceGit.Views return new Size(scale * img.Width, scale * img.Height); } + private void RenderSingleSide(DrawingContext context, Bitmap img, Rect clip) + { + var w = Bounds.Width; + var h = Bounds.Height; + + var imgW = img.Size.Width; + var imgH = img.Size.Height; + var scale = Math.Min(1, Math.Min(w / imgW, h / imgH)); + + var scaledW = img.Size.Width * scale; + var scaledH = img.Size.Height * scale; + + var src = new Rect(0, 0, imgW, imgH); + var dst = new Rect((w - scaledW) * 0.5, (h - scaledH) * 0.5, scaledW, scaledH); + + using (context.PushClip(clip)) + context.DrawImage(img, src, dst); + } + private bool _pressedOnSlider = false; private bool _lastInSlider = false; } From c3c7d321671d251df39b5556cbd6dbe0c71229fa Mon Sep 17 00:00:00 2001 From: leo Date: Wed, 11 Jun 2025 10:25:24 +0800 Subject: [PATCH 12/31] refactor: do not change original image aspect ratio and do not up-scale image in `BLEND` image-diff mode Signed-off-by: leo --- src/Views/ImageContainer.cs | 34 ++++++++++++++++++++++------------ 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/src/Views/ImageContainer.cs b/src/Views/ImageContainer.cs index 45acfcd0..995f269b 100644 --- a/src/Views/ImageContainer.cs +++ b/src/Views/ImageContainer.cs @@ -286,7 +286,6 @@ namespace SourceGit.Views { base.Render(context); - var rect = new Rect(0, 0, Bounds.Width, Bounds.Height); var alpha = Alpha; var left = OldImage; var right = NewImage; @@ -295,32 +294,27 @@ namespace SourceGit.Views if (drawLeft && drawRight) { - using (var rt = new RenderTargetBitmap(right.PixelSize, right.Dpi)) + using (var rt = new RenderTargetBitmap(new PixelSize((int)Bounds.Width, (int)Bounds.Height), right.Dpi)) { - var rtRect = new Rect(rt.Size); using (var dc = rt.CreateDrawingContext()) { using (dc.PushRenderOptions(RO_SRC)) - using (dc.PushOpacity(1 - alpha)) - dc.DrawImage(left, rtRect); + RenderSingleSide(dc, left, rt.Size.Width, rt.Size.Height, 1 - alpha); using (dc.PushRenderOptions(RO_DST)) - using (dc.PushOpacity(alpha)) - dc.DrawImage(right, rtRect); + RenderSingleSide(dc, right, rt.Size.Width, rt.Size.Height, alpha); } - context.DrawImage(rt, rtRect, rect); + context.DrawImage(rt, new Rect(0, 0, Bounds.Width, Bounds.Height)); } } else if (drawLeft) { - using (context.PushOpacity(1 - alpha)) - context.DrawImage(left, rect); + RenderSingleSide(context, left, Bounds.Width, Bounds.Height, 1 - alpha); } else if (drawRight) { - using (context.PushOpacity(alpha)) - context.DrawImage(right, rect); + RenderSingleSide(context, right, Bounds.Width, Bounds.Height, alpha); } } @@ -348,6 +342,22 @@ namespace SourceGit.Views return new Size(scale * img.Width, scale * img.Height); } + private void RenderSingleSide(DrawingContext context, Bitmap img, double w, double h, double alpha) + { + var imgW = img.Size.Width; + var imgH = img.Size.Height; + var scale = Math.Min(1, Math.Min(w / imgW, h / imgH)); + + var scaledW = img.Size.Width * scale; + var scaledH = img.Size.Height * scale; + + var src = new Rect(0, 0, imgW, imgH); + var dst = new Rect((w - scaledW) * 0.5, (h - scaledH) * 0.5, scaledW, scaledH); + + using (context.PushOpacity(alpha)) + context.DrawImage(img, src, dst); + } + private static readonly RenderOptions RO_SRC = new RenderOptions() { BitmapBlendingMode = BitmapBlendingMode.Source, BitmapInterpolationMode = BitmapInterpolationMode.HighQuality }; private static readonly RenderOptions RO_DST = new RenderOptions() { BitmapBlendingMode = BitmapBlendingMode.Plus, BitmapInterpolationMode = BitmapInterpolationMode.HighQuality }; } From 549409326175952369cd356736fa75c7b8f8ab71 Mon Sep 17 00:00:00 2001 From: leo Date: Wed, 11 Jun 2025 15:20:50 +0800 Subject: [PATCH 13/31] refactor: now all filesystem related trees/lists are sorted in case-insensitive mode Signed-off-by: leo --- src/Commands/CompareRevisions.cs | 2 +- src/Models/NumericSort.cs | 9 ++++++--- src/ViewModels/Preferences.cs | 2 +- src/ViewModels/Repository.cs | 1 + src/ViewModels/StashesPage.cs | 2 +- src/Views/RevisionFileTreeView.axaml.cs | 25 ++++++++++++------------- 6 files changed, 22 insertions(+), 19 deletions(-) diff --git a/src/Commands/CompareRevisions.cs b/src/Commands/CompareRevisions.cs index 7b4a496d..c88e087a 100644 --- a/src/Commands/CompareRevisions.cs +++ b/src/Commands/CompareRevisions.cs @@ -39,7 +39,7 @@ namespace SourceGit.Commands foreach (var line in lines) ParseLine(line); - _changes.Sort((l, r) => string.Compare(l.Path, r.Path, StringComparison.Ordinal)); + _changes.Sort((l, r) => Models.NumericSort.Compare(l.Path, r.Path)); return _changes; } diff --git a/src/Models/NumericSort.cs b/src/Models/NumericSort.cs index ed5002e6..3bb69fe2 100644 --- a/src/Models/NumericSort.cs +++ b/src/Models/NumericSort.cs @@ -1,4 +1,7 @@ -namespace SourceGit.Models +using System; +using System.Globalization; + +namespace SourceGit.Models { public static class NumericSort { @@ -23,7 +26,7 @@ bool isDigit1 = char.IsDigit(c1); bool isDigit2 = char.IsDigit(c2); if (isDigit1 != isDigit2) - return c1.CompareTo(c2); + return char.ToUpper(c1, CultureInfo.CurrentCulture).CompareTo(char.ToUpper(c2, CultureInfo.CurrentCulture)); do { @@ -55,7 +58,7 @@ if (isDigit1) result = loc1 == loc2 ? string.CompareOrdinal(sub1, sub2) : loc1 - loc2; else - result = string.CompareOrdinal(sub1, sub2); + result = string.Compare(sub1, sub2, StringComparison.OrdinalIgnoreCase); if (result != 0) return result; diff --git a/src/ViewModels/Preferences.cs b/src/ViewModels/Preferences.cs index e41e046e..665b7604 100644 --- a/src/ViewModels/Preferences.cs +++ b/src/ViewModels/Preferences.cs @@ -449,7 +449,7 @@ namespace SourceGit.ViewModels if (l.IsRepository != r.IsRepository) return l.IsRepository ? 1 : -1; - return string.Compare(l.Name, r.Name, StringComparison.Ordinal); + return Models.NumericSort.Compare(l.Name, r.Name); }); } diff --git a/src/ViewModels/Repository.cs b/src/ViewModels/Repository.cs index af72e72a..44fbffc5 100644 --- a/src/ViewModels/Repository.cs +++ b/src/ViewModels/Repository.cs @@ -1275,6 +1275,7 @@ namespace SourceGit.ViewModels if (_workingCopy == null) return; + changes.Sort((l, r) => Models.NumericSort.Compare(l.Path, r.Path)); _workingCopy.SetData(changes); Dispatcher.UIThread.Invoke(() => diff --git a/src/ViewModels/StashesPage.cs b/src/ViewModels/StashesPage.cs index dec8ea6b..eba8ac28 100644 --- a/src/ViewModels/StashesPage.cs +++ b/src/ViewModels/StashesPage.cs @@ -71,7 +71,7 @@ namespace SourceGit.ViewModels changes.Add(c); if (needSort) - changes.Sort((l, r) => string.Compare(l.Path, r.Path, StringComparison.Ordinal)); + changes.Sort((l, r) => Models.NumericSort.Compare(l.Path, r.Path)); } Dispatcher.UIThread.Invoke(() => diff --git a/src/Views/RevisionFileTreeView.axaml.cs b/src/Views/RevisionFileTreeView.axaml.cs index 410f747e..98f32c25 100644 --- a/src/Views/RevisionFileTreeView.axaml.cs +++ b/src/Views/RevisionFileTreeView.axaml.cs @@ -270,12 +270,7 @@ namespace SourceGit.Views foreach (var obj in objects) _tree.Add(new ViewModels.RevisionFileTreeNode { Backend = obj }); - _tree.Sort((l, r) => - { - if (l.IsFolder == r.IsFolder) - return string.Compare(l.Name, r.Name, StringComparison.Ordinal); - return l.IsFolder ? -1 : 1; - }); + SortNodes(_tree); var topTree = new List(); MakeRows(topTree, _tree, 0); @@ -341,13 +336,7 @@ namespace SourceGit.Views foreach (var obj in objects) node.Children.Add(new ViewModels.RevisionFileTreeNode() { Backend = obj }); - node.Children.Sort((l, r) => - { - if (l.IsFolder == r.IsFolder) - return Models.NumericSort.Compare(l.Name, r.Name); - return l.IsFolder ? -1 : 1; - }); - + SortNodes(node.Children); return node.Children; } @@ -365,6 +354,16 @@ namespace SourceGit.Views } } + private void SortNodes(List nodes) + { + nodes.Sort((l, r) => + { + if (l.IsFolder == r.IsFolder) + return Models.NumericSort.Compare(l.Name, r.Name); + return l.IsFolder ? -1 : 1; + }); + } + private List _tree = []; private AvaloniaList _rows = []; private bool _disableSelectionChangingEvent = false; From 196b454ae8751f5b158c4aa5e96d2836eb012c0f Mon Sep 17 00:00:00 2001 From: Nathan Baulch Date: Wed, 11 Jun 2025 17:35:43 +1000 Subject: [PATCH 14/31] feature: support delete key everywhere (#1412) --- src/ViewModels/Repository.cs | 18 ++++++++++++++++ src/ViewModels/StashesPage.cs | 6 ++++++ src/Views/BranchTree.axaml | 1 + src/Views/BranchTree.axaml.cs | 38 ++++++++++++++++++++++++++++++++++ src/Views/StashesPage.axaml | 1 + src/Views/StashesPage.axaml.cs | 16 ++++++++++++++ src/Views/TagsView.axaml | 2 ++ src/Views/TagsView.axaml.cs | 12 +++++++++++ src/Views/ViewLogs.axaml | 1 + src/Views/ViewLogs.axaml.cs | 13 ++++++++++++ src/Views/Welcome.axaml.cs | 30 +++++++++++++++++---------- 11 files changed, 127 insertions(+), 11 deletions(-) diff --git a/src/ViewModels/Repository.cs b/src/ViewModels/Repository.cs index 44fbffc5..7f76dc2a 100644 --- a/src/ViewModels/Repository.cs +++ b/src/ViewModels/Repository.cs @@ -1359,6 +1359,18 @@ namespace SourceGit.ViewModels } } + public void DeleteBranch(Models.Branch branch) + { + if (CanCreatePopup()) + ShowPopup(new DeleteBranch(this, branch)); + } + + public void DeleteRemote(Models.Remote remote) + { + if (CanCreatePopup()) + ShowPopup(new DeleteRemote(this, remote)); + } + public void DeleteMultipleBranches(List branches, bool isLocal) { if (CanCreatePopup()) @@ -1383,6 +1395,12 @@ namespace SourceGit.ViewModels ShowPopup(new CreateTag(this, _currentBranch)); } + public void DeleteTag(Models.Tag tag) + { + if (CanCreatePopup()) + ShowPopup(new DeleteTag(this, tag)); + } + public void AddRemote() { if (CanCreatePopup()) diff --git a/src/ViewModels/StashesPage.cs b/src/ViewModels/StashesPage.cs index eba8ac28..6cdf9f45 100644 --- a/src/ViewModels/StashesPage.cs +++ b/src/ViewModels/StashesPage.cs @@ -314,6 +314,12 @@ namespace SourceGit.ViewModels } } + public void Drop(Models.Stash stash) + { + if (_repo.CanCreatePopup()) + _repo.ShowPopup(new DropStash(_repo, stash)); + } + private Repository _repo = null; private List _stashes = []; private List _visibleStashes = []; diff --git a/src/Views/BranchTree.axaml b/src/Views/BranchTree.axaml index dad4f820..151b41bf 100644 --- a/src/Views/BranchTree.axaml +++ b/src/Views/BranchTree.axaml @@ -13,6 +13,7 @@ ItemsSource="{Binding #ThisControl.Rows}" SelectionMode="Multiple" SelectionChanged="OnNodesSelectionChanged" + KeyDown="OnListKeyDown" ContextRequested="OnTreeContextRequested"> diff --git a/src/Views/BranchTree.axaml.cs b/src/Views/BranchTree.axaml.cs index d8fe7903..45cc8608 100644 --- a/src/Views/BranchTree.axaml.cs +++ b/src/Views/BranchTree.axaml.cs @@ -450,6 +450,44 @@ namespace SourceGit.Views } } + private void OnListKeyDown(object _, KeyEventArgs e) + { + if (e.Key is not (Key.Delete or Key.Back)) + return; + + var repo = DataContext as ViewModels.Repository; + if (repo?.Settings == null) + return; + + var selected = BranchesPresenter.SelectedItems; + if (selected == null || selected.Count == 0) + return; + + if (selected is [ViewModels.BranchTreeNode { Backend: Models.Remote remote }]) + { + repo.DeleteRemote(remote); + e.Handled = true; + return; + } + + var branches = new List(); + foreach (var item in selected) + { + if (item is ViewModels.BranchTreeNode node) + CollectBranchesInNode(branches, node); + } + + if (branches.Find(x => x.IsCurrent) != null) + return; + + if (branches.Count == 1) + repo.DeleteBranch(branches[0]); + else + repo.DeleteMultipleBranches(branches, branches[0].IsLocal); + + e.Handled = true; + } + private void OnDoubleTappedBranchNode(object sender, TappedEventArgs _) { if (sender is Grid { DataContext: ViewModels.BranchTreeNode node }) diff --git a/src/Views/StashesPage.axaml b/src/Views/StashesPage.axaml index 15427b93..34913973 100644 --- a/src/Views/StashesPage.axaml +++ b/src/Views/StashesPage.axaml @@ -65,6 +65,7 @@ ItemsSource="{Binding VisibleStashes}" SelectedItem="{Binding SelectedStash, Mode=TwoWay}" SelectionMode="Single" + KeyDown="OnStashKeyDown" ScrollViewer.HorizontalScrollBarVisibility="Disabled" ScrollViewer.VerticalScrollBarVisibility="Auto"> diff --git a/src/Views/StashesPage.axaml.cs b/src/Views/StashesPage.axaml.cs index 461f9d5d..f95d0b98 100644 --- a/src/Views/StashesPage.axaml.cs +++ b/src/Views/StashesPage.axaml.cs @@ -1,4 +1,5 @@ using Avalonia.Controls; +using Avalonia.Input; namespace SourceGit.Views { @@ -33,6 +34,21 @@ namespace SourceGit.Views e.Handled = true; } + private void OnStashKeyDown(object sender, KeyEventArgs e) + { + if (e.Key is not (Key.Delete or Key.Back)) + return; + + if (DataContext is not ViewModels.StashesPage vm) + return; + + if (sender is not ListBox { SelectedValue: Models.Stash stash }) + return; + + vm.Drop(stash); + e.Handled = true; + } + private void OnChangeContextRequested(object sender, ContextRequestedEventArgs e) { if (DataContext is ViewModels.StashesPage vm && sender is Grid grid) diff --git a/src/Views/TagsView.axaml b/src/Views/TagsView.axaml index 0dbe6dae..7dadbaad 100644 --- a/src/Views/TagsView.axaml +++ b/src/Views/TagsView.axaml @@ -23,6 +23,7 @@ @@ -88,6 +89,7 @@ Margin="8,0,0,0" ItemsSource="{Binding Tags}" SelectionMode="Single" + KeyDown="OnKeyDown" SelectionChanged="OnSelectionChanged"> diff --git a/src/Views/TagsView.axaml.cs b/src/Views/TagsView.axaml.cs index 6764c7ea..6bd312f3 100644 --- a/src/Views/TagsView.axaml.cs +++ b/src/Views/TagsView.axaml.cs @@ -214,6 +214,18 @@ namespace SourceGit.Views if (selectedTag != null) RaiseEvent(new RoutedEventArgs(SelectionChangedEvent)); } + + private void OnKeyDown(object sender, KeyEventArgs e) + { + if (sender is not ListBox { SelectedValue: Models.Tag tag }) + return; + + if (DataContext is not ViewModels.Repository repo) + return; + + repo.DeleteTag(tag); + e.Handled = true; + } } } diff --git a/src/Views/ViewLogs.axaml b/src/Views/ViewLogs.axaml index 1fb44d66..07d0048e 100644 --- a/src/Views/ViewLogs.axaml +++ b/src/Views/ViewLogs.axaml @@ -53,6 +53,7 @@ ItemsSource="{Binding Logs}" SelectedItem="{Binding SelectedLog, Mode=TwoWay}" SelectionMode="Single" + KeyDown="OnLogKeyDown" Grid.IsSharedSizeScope="True" ScrollViewer.HorizontalScrollBarVisibility="Disabled" ScrollViewer.VerticalScrollBarVisibility="Auto"> diff --git a/src/Views/ViewLogs.axaml.cs b/src/Views/ViewLogs.axaml.cs index f0a27884..0ed24449 100644 --- a/src/Views/ViewLogs.axaml.cs +++ b/src/Views/ViewLogs.axaml.cs @@ -1,4 +1,5 @@ using Avalonia.Controls; +using Avalonia.Input; namespace SourceGit.Views { @@ -39,5 +40,17 @@ namespace SourceGit.Views e.Handled = true; } + + private void OnLogKeyDown(object _, KeyEventArgs e) + { + if (e.Key is not (Key.Delete or Key.Back)) + return; + + if (DataContext is not ViewModels.ViewLogs vm) + return; + + vm.Logs.Remove(vm.SelectedLog); + e.Handled = true; + } } } diff --git a/src/Views/Welcome.axaml.cs b/src/Views/Welcome.axaml.cs index 3ab11782..3a69247a 100644 --- a/src/Views/Welcome.axaml.cs +++ b/src/Views/Welcome.axaml.cs @@ -94,20 +94,28 @@ namespace SourceGit.Views private void OnTreeViewKeyDown(object _, KeyEventArgs e) { - if (TreeContainer.SelectedItem is ViewModels.RepositoryNode node && e.Key == Key.Enter) + if (TreeContainer.SelectedItem is ViewModels.RepositoryNode node) { - if (node.IsRepository) + if (e.Key == Key.Enter) { - var parent = this.FindAncestorOfType(); - if (parent is { DataContext: ViewModels.Launcher launcher }) - launcher.OpenRepositoryInTab(node, null); - } - else - { - ViewModels.Welcome.Instance.ToggleNodeIsExpanded(node); - } + if (node.IsRepository) + { + var parent = this.FindAncestorOfType(); + if (parent is { DataContext: ViewModels.Launcher launcher }) + launcher.OpenRepositoryInTab(node, null); + } + else + { + ViewModels.Welcome.Instance.ToggleNodeIsExpanded(node); + } - e.Handled = true; + e.Handled = true; + } + else if (e.Key is Key.Delete or Key.Back) + { + node.Delete(); + e.Handled = true; + } } } From a128b67bd4e8dfbdc679c4fa32617acdb478a952 Mon Sep 17 00:00:00 2001 From: leo Date: Wed, 11 Jun 2025 16:13:47 +0800 Subject: [PATCH 15/31] code_review: PR #1412 - Use `ViewModels.StashesPage.SelectedStash` instead of `sender is not ListBox { SelectedValue: Models.Stash stash }` - In tags view, `SelectedItem` can be `Models.Tag` or `ViewModels.TagTreeNode` - In logs window, `vm.SelectedLog` may be null Signed-off-by: leo --- src/ViewModels/Repository.cs | 12 ++++----- src/ViewModels/StashesPage.cs | 12 ++++----- src/Views/BranchTree.axaml | 2 +- src/Views/BranchTree.axaml.cs | 4 +-- src/Views/StashesPage.axaml | 2 +- src/Views/StashesPage.axaml.cs | 25 ++++++++---------- src/Views/TagsView.axaml.cs | 12 +++++---- src/Views/ViewLogs.axaml.cs | 7 +++-- src/Views/Welcome.axaml | 3 +-- src/Views/Welcome.axaml.cs | 47 +++++++++++++--------------------- 10 files changed, 56 insertions(+), 70 deletions(-) diff --git a/src/ViewModels/Repository.cs b/src/ViewModels/Repository.cs index 7f76dc2a..b97bbf5d 100644 --- a/src/ViewModels/Repository.cs +++ b/src/ViewModels/Repository.cs @@ -1365,12 +1365,6 @@ namespace SourceGit.ViewModels ShowPopup(new DeleteBranch(this, branch)); } - public void DeleteRemote(Models.Remote remote) - { - if (CanCreatePopup()) - ShowPopup(new DeleteRemote(this, remote)); - } - public void DeleteMultipleBranches(List branches, bool isLocal) { if (CanCreatePopup()) @@ -1407,6 +1401,12 @@ namespace SourceGit.ViewModels ShowPopup(new AddRemote(this)); } + public void DeleteRemote(Models.Remote remote) + { + if (CanCreatePopup()) + ShowPopup(new DeleteRemote(this, remote)); + } + public void AddSubmodule() { if (CanCreatePopup()) diff --git a/src/ViewModels/StashesPage.cs b/src/ViewModels/StashesPage.cs index 6cdf9f45..f039d54e 100644 --- a/src/ViewModels/StashesPage.cs +++ b/src/ViewModels/StashesPage.cs @@ -295,6 +295,12 @@ namespace SourceGit.ViewModels SearchFilter = string.Empty; } + public void Drop(Models.Stash stash) + { + if (stash != null && _repo.CanCreatePopup()) + _repo.ShowPopup(new DropStash(_repo, stash)); + } + private void RefreshVisible() { if (string.IsNullOrEmpty(_searchFilter)) @@ -314,12 +320,6 @@ namespace SourceGit.ViewModels } } - public void Drop(Models.Stash stash) - { - if (_repo.CanCreatePopup()) - _repo.ShowPopup(new DropStash(_repo, stash)); - } - private Repository _repo = null; private List _stashes = []; private List _visibleStashes = []; diff --git a/src/Views/BranchTree.axaml b/src/Views/BranchTree.axaml index 151b41bf..1783ffe0 100644 --- a/src/Views/BranchTree.axaml +++ b/src/Views/BranchTree.axaml @@ -13,7 +13,7 @@ ItemsSource="{Binding #ThisControl.Rows}" SelectionMode="Multiple" SelectionChanged="OnNodesSelectionChanged" - KeyDown="OnListKeyDown" + KeyDown="OnTreeKeyDown" ContextRequested="OnTreeContextRequested"> diff --git a/src/Views/BranchTree.axaml.cs b/src/Views/BranchTree.axaml.cs index 45cc8608..0579c0da 100644 --- a/src/Views/BranchTree.axaml.cs +++ b/src/Views/BranchTree.axaml.cs @@ -450,7 +450,7 @@ namespace SourceGit.Views } } - private void OnListKeyDown(object _, KeyEventArgs e) + private void OnTreeKeyDown(object _, KeyEventArgs e) { if (e.Key is not (Key.Delete or Key.Back)) return; @@ -463,7 +463,7 @@ namespace SourceGit.Views if (selected == null || selected.Count == 0) return; - if (selected is [ViewModels.BranchTreeNode { Backend: Models.Remote remote }]) + if (selected.Count == 1 && selected[0] is ViewModels.BranchTreeNode { Backend: Models.Remote remote }) { repo.DeleteRemote(remote); e.Handled = true; diff --git a/src/Views/StashesPage.axaml b/src/Views/StashesPage.axaml index 34913973..30c6e183 100644 --- a/src/Views/StashesPage.axaml +++ b/src/Views/StashesPage.axaml @@ -65,7 +65,7 @@ ItemsSource="{Binding VisibleStashes}" SelectedItem="{Binding SelectedStash, Mode=TwoWay}" SelectionMode="Single" - KeyDown="OnStashKeyDown" + KeyDown="OnStashListKeyDown" ScrollViewer.HorizontalScrollBarVisibility="Disabled" ScrollViewer.VerticalScrollBarVisibility="Auto"> diff --git a/src/Views/StashesPage.axaml.cs b/src/Views/StashesPage.axaml.cs index f95d0b98..d152a12f 100644 --- a/src/Views/StashesPage.axaml.cs +++ b/src/Views/StashesPage.axaml.cs @@ -24,17 +24,7 @@ namespace SourceGit.Views layout.StashesLeftWidth = new GridLength(maxLeft, GridUnitType.Pixel); } - private void OnStashContextRequested(object sender, ContextRequestedEventArgs e) - { - if (DataContext is ViewModels.StashesPage vm && sender is Border border) - { - var menu = vm.MakeContextMenu(border.DataContext as Models.Stash); - menu?.Open(border); - } - e.Handled = true; - } - - private void OnStashKeyDown(object sender, KeyEventArgs e) + private void OnStashListKeyDown(object sender, KeyEventArgs e) { if (e.Key is not (Key.Delete or Key.Back)) return; @@ -42,10 +32,17 @@ namespace SourceGit.Views if (DataContext is not ViewModels.StashesPage vm) return; - if (sender is not ListBox { SelectedValue: Models.Stash stash }) - return; + vm.Drop(vm.SelectedStash); + e.Handled = true; + } - vm.Drop(stash); + private void OnStashContextRequested(object sender, ContextRequestedEventArgs e) + { + if (DataContext is ViewModels.StashesPage vm && sender is Border border) + { + var menu = vm.MakeContextMenu(border.DataContext as Models.Stash); + menu?.Open(border); + } e.Handled = true; } diff --git a/src/Views/TagsView.axaml.cs b/src/Views/TagsView.axaml.cs index 6bd312f3..390063ee 100644 --- a/src/Views/TagsView.axaml.cs +++ b/src/Views/TagsView.axaml.cs @@ -217,13 +217,15 @@ namespace SourceGit.Views private void OnKeyDown(object sender, KeyEventArgs e) { - if (sender is not ListBox { SelectedValue: Models.Tag tag }) - return; - if (DataContext is not ViewModels.Repository repo) return; - - repo.DeleteTag(tag); + + var selected = (sender as ListBox)?.SelectedItem; + if (selected is ViewModels.TagTreeNode { Tag: { } tagInNode }) + repo.DeleteTag(tagInNode); + else if (selected is Models.Tag tag) + repo.DeleteTag(tag); + e.Handled = true; } } diff --git a/src/Views/ViewLogs.axaml.cs b/src/Views/ViewLogs.axaml.cs index 0ed24449..b9c71882 100644 --- a/src/Views/ViewLogs.axaml.cs +++ b/src/Views/ViewLogs.axaml.cs @@ -45,11 +45,10 @@ namespace SourceGit.Views { if (e.Key is not (Key.Delete or Key.Back)) return; - - if (DataContext is not ViewModels.ViewLogs vm) - return; - vm.Logs.Remove(vm.SelectedLog); + if (DataContext is ViewModels.ViewLogs { SelectedLog: { } log } vm) + vm.Logs.Remove(log); + e.Handled = true; } } diff --git a/src/Views/Welcome.axaml b/src/Views/Welcome.axaml index 1e300653..d249e5ef 100644 --- a/src/Views/Welcome.axaml +++ b/src/Views/Welcome.axaml @@ -61,8 +61,7 @@ ItemsSource="{Binding Rows}" SelectionMode="Single" Loaded="SetupTreeViewDragAndDrop" - LostFocus="OnTreeViewLostFocus" - KeyDown="OnTreeViewKeyDown"> + LostFocus="OnTreeViewLostFocus"> @@ -38,7 +38,7 @@ - + @@ -206,7 +206,7 @@ - + - + - + - + - + @@ -737,7 +737,7 @@ - + @@ -956,7 +956,7 @@ - + - + - +