From 0c37957a217e194771813e84068e58ea2c1acf79 Mon Sep 17 00:00:00 2001 From: leo Date: Mon, 30 Mar 2026 14:48:11 +0800 Subject: [PATCH] feature: support `git stash branch ` command (#2227) Signed-off-by: leo --- src/Commands/Stash.cs | 6 ++ src/Resources/Locales/en_US.axaml | 4 ++ src/Resources/Locales/zh_CN.axaml | 4 ++ src/Resources/Locales/zh_TW.axaml | 4 ++ src/ViewModels/CheckoutBranchFromStash.cs | 70 ++++++++++++++++++++++ src/ViewModels/StashesPage.cs | 6 ++ src/Views/CheckoutBranchFromStash.axaml | 48 +++++++++++++++ src/Views/CheckoutBranchFromStash.axaml.cs | 12 ++++ src/Views/StashesPage.axaml.cs | 10 ++++ 9 files changed, 164 insertions(+) create mode 100644 src/ViewModels/CheckoutBranchFromStash.cs create mode 100644 src/Views/CheckoutBranchFromStash.axaml create mode 100644 src/Views/CheckoutBranchFromStash.axaml.cs diff --git a/src/Commands/Stash.cs b/src/Commands/Stash.cs index 3bf72308..31b5b71c 100644 --- a/src/Commands/Stash.cs +++ b/src/Commands/Stash.cs @@ -76,6 +76,12 @@ namespace SourceGit.Commands return await ExecAsync().ConfigureAwait(false); } + public async Task CheckoutBranchAsync(string name, string branch) + { + Args = $"stash branch {branch.Quoted()} {name.Quoted()}"; + return await ExecAsync().ConfigureAwait(false); + } + public async Task PopAsync(string name) { Args = $"stash pop -q --index {name.Quoted()}"; diff --git a/src/Resources/Locales/en_US.axaml b/src/Resources/Locales/en_US.axaml index ee46c20e..ed115881 100644 --- a/src/Resources/Locales/en_US.axaml +++ b/src/Resources/Locales/en_US.axaml @@ -115,6 +115,9 @@ The following submodules need to be updated:{0}Do you want to update them? Checkout & Fast-Forward Fast-Forward to: + Checkout Branch From Stash + New Branch: + Stash: Cherry Pick Append source to commit message Commit(s): @@ -858,6 +861,7 @@ Stash Local Changes Apply Apply Changes + Checkout New Branch Copy Message Drop Save as Patch... diff --git a/src/Resources/Locales/zh_CN.axaml b/src/Resources/Locales/zh_CN.axaml index e863edd9..d52f3b59 100644 --- a/src/Resources/Locales/zh_CN.axaml +++ b/src/Resources/Locales/zh_CN.axaml @@ -119,6 +119,9 @@ 以下子模块需要更新:{0}是否立即更新? 检出分支并快进 上游分支 : + 从所选贮藏检出分支 + 新分支 : + 所选贮藏 : 挑选提交 提交信息中追加来源信息 提交列表 : @@ -862,6 +865,7 @@ 贮藏本地变更 应用(apply) 应用(apply)选中变更 + 检出新分支 复制描述信息 删除(drop) 另存为补丁... diff --git a/src/Resources/Locales/zh_TW.axaml b/src/Resources/Locales/zh_TW.axaml index 8d1c0881..907dfeae 100644 --- a/src/Resources/Locales/zh_TW.axaml +++ b/src/Resources/Locales/zh_TW.axaml @@ -119,6 +119,9 @@ 以下子模組需要更新: {0},您要立即更新嗎? 簽出分支並快轉 上游分支: + 從所選擱置簽出分支 + 新分支: + 所選擱置: 揀選提交 提交資訊中追加來源資訊 提交列表: @@ -862,6 +865,7 @@ 擱置本機變更 套用 (apply) 套用 (apply) 所選變更 + 簽出分支 複製描述訊息 刪除 (drop) 另存為修補檔 (patch)... diff --git a/src/ViewModels/CheckoutBranchFromStash.cs b/src/ViewModels/CheckoutBranchFromStash.cs new file mode 100644 index 00000000..d62f4f2d --- /dev/null +++ b/src/ViewModels/CheckoutBranchFromStash.cs @@ -0,0 +1,70 @@ +using System; +using System.ComponentModel.DataAnnotations; +using System.Threading.Tasks; + +namespace SourceGit.ViewModels +{ + public class CheckoutBranchFromStash : Popup + { + public Models.Stash Target + { + get; + } + + [Required(ErrorMessage = "Branch name is required!")] + [RegularExpression(@"^[\w\-/\.#\+]+$", ErrorMessage = "Bad branch name format!")] + [CustomValidation(typeof(CheckoutBranchFromStash), nameof(ValidateBranchName))] + public string BranchName + { + get => _branchName; + set => SetProperty(ref _branchName, value, true); + } + + public CheckoutBranchFromStash(Repository repo, Models.Stash stash) + { + _repo = repo; + Target = stash; + } + + public static ValidationResult ValidateBranchName(string name, ValidationContext ctx) + { + if (ctx.ObjectInstance is CheckoutBranchFromStash caller) + { + foreach (var b in caller._repo.Branches) + { + if (b.FriendlyName.Equals(name, StringComparison.Ordinal)) + return new ValidationResult("A branch with same name already exists!"); + } + + return ValidationResult.Success; + } + + return new ValidationResult("Missing runtime context to create branch!"); + } + + public override async Task Sure() + { + using var lockWatcher = _repo.LockWatcher(); + ProgressDescription = "Checkout branch from stash..."; + + var log = _repo.CreateLog($"Checkout Branch '{_branchName}'"); + Use(log); + + var succ = await new Commands.Stash(_repo.FullPath) + .Use(log) + .CheckoutBranchAsync(Target.Name, _branchName); + + if (succ) + { + _repo.MarkWorkingCopyDirtyManually(); + _repo.MarkStashesDirtyManually(); + } + + log.Complete(); + return true; + } + + private readonly Repository _repo; + private string _branchName = string.Empty; + } +} diff --git a/src/ViewModels/StashesPage.cs b/src/ViewModels/StashesPage.cs index a484b022..d0ccf0fa 100644 --- a/src/ViewModels/StashesPage.cs +++ b/src/ViewModels/StashesPage.cs @@ -153,6 +153,12 @@ namespace SourceGit.ViewModels _repo.ShowPopup(new ApplyStash(_repo, stash)); } + public void CheckoutBranch(Models.Stash stash) + { + if (_repo.CanCreatePopup()) + _repo.ShowPopup(new CheckoutBranchFromStash(_repo, stash)); + } + public void Drop(Models.Stash stash) { if (_repo.CanCreatePopup()) diff --git a/src/Views/CheckoutBranchFromStash.axaml b/src/Views/CheckoutBranchFromStash.axaml new file mode 100644 index 00000000..0e030896 --- /dev/null +++ b/src/Views/CheckoutBranchFromStash.axaml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Views/CheckoutBranchFromStash.axaml.cs b/src/Views/CheckoutBranchFromStash.axaml.cs new file mode 100644 index 00000000..b05e4fce --- /dev/null +++ b/src/Views/CheckoutBranchFromStash.axaml.cs @@ -0,0 +1,12 @@ +using Avalonia.Controls; + +namespace SourceGit.Views +{ + public partial class CheckoutBranchFromStash : UserControl + { + public CheckoutBranchFromStash() + { + InitializeComponent(); + } + } +} diff --git a/src/Views/StashesPage.axaml.cs b/src/Views/StashesPage.axaml.cs index 8e8bb231..9e9b7987 100644 --- a/src/Views/StashesPage.axaml.cs +++ b/src/Views/StashesPage.axaml.cs @@ -59,6 +59,15 @@ namespace SourceGit.Views ev.Handled = true; }; + var branch = new MenuItem(); + branch.Header = App.Text("StashCM.Branch"); + branch.Icon = App.CreateMenuIcon("Icons.Branch.Add"); + branch.Click += (_, ev) => + { + vm.CheckoutBranch(stash); + ev.Handled = true; + }; + var drop = new MenuItem(); drop.Header = App.Text("StashCM.Drop"); drop.Icon = App.CreateMenuIcon("Icons.Clear"); @@ -109,6 +118,7 @@ namespace SourceGit.Views var menu = new ContextMenu(); menu.Items.Add(apply); + menu.Items.Add(branch); menu.Items.Add(drop); menu.Items.Add(new MenuItem { Header = "-" }); menu.Items.Add(patch);