feature: support git stash branch <branch_name> <stash> command (#2227)

Signed-off-by: leo <longshuang@msn.cn>
This commit is contained in:
leo
2026-03-30 14:48:11 +08:00
parent ca9f5e179f
commit 0c37957a21
9 changed files with 164 additions and 0 deletions

View File

@@ -76,6 +76,12 @@ namespace SourceGit.Commands
return await ExecAsync().ConfigureAwait(false);
}
public async Task<bool> CheckoutBranchAsync(string name, string branch)
{
Args = $"stash branch {branch.Quoted()} {name.Quoted()}";
return await ExecAsync().ConfigureAwait(false);
}
public async Task<bool> PopAsync(string name)
{
Args = $"stash pop -q --index {name.Quoted()}";

View File

@@ -115,6 +115,9 @@
<x:String x:Key="Text.Checkout.WarnUpdatingSubmodules" xml:space="preserve">The following submodules need to be updated:{0}Do you want to update them?</x:String>
<x:String x:Key="Text.Checkout.WithFastForward" xml:space="preserve">Checkout &amp; Fast-Forward</x:String>
<x:String x:Key="Text.Checkout.WithFastForward.Upstream" xml:space="preserve">Fast-Forward to:</x:String>
<x:String x:Key="Text.CheckoutBranchFromStash" xml:space="preserve">Checkout Branch From Stash</x:String>
<x:String x:Key="Text.CheckoutBranchFromStash.Branch" xml:space="preserve">New Branch:</x:String>
<x:String x:Key="Text.CheckoutBranchFromStash.Stash" xml:space="preserve">Stash:</x:String>
<x:String x:Key="Text.CherryPick" xml:space="preserve">Cherry Pick</x:String>
<x:String x:Key="Text.CherryPick.AppendSourceToMessage" xml:space="preserve">Append source to commit message</x:String>
<x:String x:Key="Text.CherryPick.Commit" xml:space="preserve">Commit(s):</x:String>
@@ -858,6 +861,7 @@
<x:String x:Key="Text.Stash.Title" xml:space="preserve">Stash Local Changes</x:String>
<x:String x:Key="Text.StashCM.Apply" xml:space="preserve">Apply</x:String>
<x:String x:Key="Text.StashCM.ApplyFileChanges" xml:space="preserve">Apply Changes</x:String>
<x:String x:Key="Text.StashCM.Branch" xml:space="preserve">Checkout New Branch</x:String>
<x:String x:Key="Text.StashCM.CopyMessage" xml:space="preserve">Copy Message</x:String>
<x:String x:Key="Text.StashCM.Drop" xml:space="preserve">Drop</x:String>
<x:String x:Key="Text.StashCM.SaveAsPatch" xml:space="preserve">Save as Patch...</x:String>

View File

@@ -119,6 +119,9 @@
<x:String x:Key="Text.Checkout.WarnUpdatingSubmodules" xml:space="preserve">以下子模块需要更新:{0}是否立即更新?</x:String>
<x:String x:Key="Text.Checkout.WithFastForward" xml:space="preserve">检出分支并快进</x:String>
<x:String x:Key="Text.Checkout.WithFastForward.Upstream" xml:space="preserve">上游分支 </x:String>
<x:String x:Key="Text.CheckoutBranchFromStash" xml:space="preserve">从所选贮藏检出分支</x:String>
<x:String x:Key="Text.CheckoutBranchFromStash.Branch" xml:space="preserve">新分支 </x:String>
<x:String x:Key="Text.CheckoutBranchFromStash.Stash" xml:space="preserve">所选贮藏 </x:String>
<x:String x:Key="Text.CherryPick" xml:space="preserve">挑选提交</x:String>
<x:String x:Key="Text.CherryPick.AppendSourceToMessage" xml:space="preserve">提交信息中追加来源信息</x:String>
<x:String x:Key="Text.CherryPick.Commit" xml:space="preserve">提交列表 </x:String>
@@ -862,6 +865,7 @@
<x:String x:Key="Text.Stash.Title" xml:space="preserve">贮藏本地变更</x:String>
<x:String x:Key="Text.StashCM.Apply" xml:space="preserve">应用(apply)</x:String>
<x:String x:Key="Text.StashCM.ApplyFileChanges" xml:space="preserve">应用(apply)选中变更</x:String>
<x:String x:Key="Text.StashCM.Branch" xml:space="preserve">检出新分支</x:String>
<x:String x:Key="Text.StashCM.CopyMessage" xml:space="preserve">复制描述信息</x:String>
<x:String x:Key="Text.StashCM.Drop" xml:space="preserve">删除(drop)</x:String>
<x:String x:Key="Text.StashCM.SaveAsPatch" xml:space="preserve">另存为补丁...</x:String>

View File

@@ -119,6 +119,9 @@
<x:String x:Key="Text.Checkout.WarnUpdatingSubmodules" xml:space="preserve">以下子模組需要更新: {0},您要立即更新嗎?</x:String>
<x:String x:Key="Text.Checkout.WithFastForward" xml:space="preserve">簽出分支並快轉</x:String>
<x:String x:Key="Text.Checkout.WithFastForward.Upstream" xml:space="preserve">上游分支: </x:String>
<x:String x:Key="Text.CheckoutBranchFromStash" xml:space="preserve">從所選擱置簽出分支</x:String>
<x:String x:Key="Text.CheckoutBranchFromStash.Branch" xml:space="preserve">新分支:</x:String>
<x:String x:Key="Text.CheckoutBranchFromStash.Stash" xml:space="preserve">所選擱置:</x:String>
<x:String x:Key="Text.CherryPick" xml:space="preserve">揀選提交</x:String>
<x:String x:Key="Text.CherryPick.AppendSourceToMessage" xml:space="preserve">提交資訊中追加來源資訊</x:String>
<x:String x:Key="Text.CherryPick.Commit" xml:space="preserve">提交列表:</x:String>
@@ -862,6 +865,7 @@
<x:String x:Key="Text.Stash.Title" xml:space="preserve">擱置本機變更</x:String>
<x:String x:Key="Text.StashCM.Apply" xml:space="preserve">套用 (apply)</x:String>
<x:String x:Key="Text.StashCM.ApplyFileChanges" xml:space="preserve">套用 (apply) 所選變更</x:String>
<x:String x:Key="Text.StashCM.Branch" xml:space="preserve">簽出分支</x:String>
<x:String x:Key="Text.StashCM.CopyMessage" xml:space="preserve">複製描述訊息</x:String>
<x:String x:Key="Text.StashCM.Drop" xml:space="preserve">刪除 (drop)</x:String>
<x:String x:Key="Text.StashCM.SaveAsPatch" xml:space="preserve">另存為修補檔 (patch)...</x:String>

View File

@@ -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<bool> 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;
}
}

View File

@@ -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())

View File

@@ -0,0 +1,48 @@
<UserControl xmlns="https://github.com/avaloniaui"
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:vm="using:SourceGit.ViewModels"
xmlns:v="using:SourceGit.Views"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="SourceGit.Views.CheckoutBranchFromStash"
x:DataType="vm:CheckoutBranchFromStash">
<StackPanel Orientation="Vertical" Margin="8,0,0,0">
<StackPanel Orientation="Horizontal">
<Path Width="16" Height="16"
Data="{StaticResource Icons.Branch.Add}"/>
<TextBlock FontSize="18"
Margin="8,0,0,0"
Classes="bold"
Text="{DynamicResource Text.CheckoutBranchFromStash}"/>
</StackPanel>
<Grid Margin="0,16,8,0" RowDefinitions="32,32" ColumnDefinitions="120,*">
<TextBlock Grid.Row="0" Grid.Column="0"
HorizontalAlignment="Right" VerticalAlignment="Center"
Margin="0,0,8,0"
Text="{DynamicResource Text.CheckoutBranchFromStash.Stash}"/>
<Grid Grid.Row="0" Grid.Column="1" ColumnDefinitions="Auto,Auto,*">
<Path Grid.Column="0"
Width="12" Height="12"
Margin="2,0,8,0"
HorizontalAlignment="Left" VerticalAlignment="Center"
Data="{StaticResource Icons.Stashes}"/>
<TextBlock Grid.Column="1" VerticalAlignment="Center" Text="{Binding Target.Name}" Foreground="DarkOrange"/>
<TextBlock Grid.Column="2" VerticalAlignment="Center" Text="{Binding Target.Subject}" TextTrimming="CharacterEllipsis" Margin="4,0,0,0"/>
</Grid>
<TextBlock Grid.Row="1" Grid.Column="0"
HorizontalAlignment="Right" VerticalAlignment="Center"
Margin="0,0,8,0"
Text="{DynamicResource Text.CheckoutBranchFromStash.Branch}"/>
<v:BranchOrTagNameTextBox Grid.Row="1" Grid.Column="1"
Height="26"
VerticalAlignment="Center"
CornerRadius="3"
Text="{Binding BranchName, Mode=TwoWay}"/>
</Grid>
</StackPanel>
</UserControl>

View File

@@ -0,0 +1,12 @@
using Avalonia.Controls;
namespace SourceGit.Views
{
public partial class CheckoutBranchFromStash : UserControl
{
public CheckoutBranchFromStash()
{
InitializeComponent();
}
}
}

View File

@@ -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);