From 7d0536d94b83c64a3d9ff16a5cbe689ee49e36dd Mon Sep 17 00:00:00 2001 From: leo Date: Tue, 10 Jun 2025 16:56:22 +0800 Subject: [PATCH] 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(); + } + } +}