feature: add context menu entry Apply Changes to only apply changes of selected file from stash (not reset mode) (#2122)

Signed-off-by: leo <longshuang@msn.cn>
This commit is contained in:
leo
2026-02-25 16:38:59 +08:00
parent 18819724c8
commit cf3a1c2c53
5 changed files with 89 additions and 82 deletions

View File

@@ -851,6 +851,7 @@
<x:String x:Key="Text.Stash.TipForSelectedFiles" xml:space="preserve">Both staged and unstaged changes of selected file(s) will be stashed!!!</x:String>
<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.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

@@ -855,6 +855,7 @@
<x:String x:Key="Text.Stash.TipForSelectedFiles" xml:space="preserve">选中文件的所有变更均会被贮藏!</x:String>
<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.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

@@ -855,6 +855,7 @@
<x:String x:Key="Text.Stash.TipForSelectedFiles" xml:space="preserve">已選取的檔案中的變更均會被擱置!</x:String>
<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.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

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using Avalonia.Threading;
@@ -196,22 +197,7 @@ namespace SourceGit.ViewModels
new Commands.DiffTool(_repo.FullPath, opt).Open();
}
public async Task CheckoutSingleFileAsync(Models.Change change)
{
var revision = _selectedStash.SHA;
if (_untracked.Contains(change) && _selectedStash.Parents.Count == 3)
revision = _selectedStash.Parents[2];
else if (change.Index == Models.ChangeState.Added && _selectedStash.Parents.Count > 1)
revision = _selectedStash.Parents[1];
var log = _repo.CreateLog($"Reset File to '{_selectedStash.Name}'");
await new Commands.Checkout(_repo.FullPath)
.Use(log)
.FileWithRevisionAsync(change.Path, revision);
log.Complete();
}
public async Task CheckoutMultipleFileAsync(List<Models.Change> changes)
public async Task CheckoutFilesAsync(List<Models.Change> changes)
{
var untracked = new List<string>();
var added = new List<string>();
@@ -247,6 +233,34 @@ namespace SourceGit.ViewModels
log.Complete();
}
public async Task ApplySelectedChanges(List<Models.Change> changes)
{
if (_selectedStash == null)
return;
var opts = new List<Models.DiffOption>();
foreach (var c in changes)
{
if (_untracked.Contains(c) && _selectedStash.Parents.Count == 3)
opts.Add(new Models.DiffOption(Models.Commit.EmptyTreeSHA1, _selectedStash.Parents[2], c));
else
opts.Add(new Models.DiffOption(_selectedStash.Parents[0], _selectedStash.SHA, c));
}
var saveTo = Path.GetTempFileName();
var succ = await Commands.SaveChangesAsPatch.ProcessStashChangesAsync(_repo.FullPath, opts, saveTo);
if (!succ)
return;
var log = _repo.CreateLog($"Apply changes from '{_selectedStash.Name}'");
await new Commands.Apply(_repo.FullPath, saveTo, true, string.Empty, string.Empty)
.Use(log)
.ExecAsync();
log.Complete();
File.Delete(saveTo);
}
private void RefreshVisible()
{
if (string.IsNullOrEmpty(_searchFilter))

View File

@@ -134,6 +134,8 @@ namespace SourceGit.Views
if (DataContext is ViewModels.StashesPage { SelectedChanges: { Count: > 0 } selected } vm &&
sender is ChangeCollectionView view)
{
var menu = new ContextMenu();
if (selected.Count == 1)
{
var change = selected[0];
@@ -159,91 +161,79 @@ namespace SourceGit.Views
ev.Handled = true;
};
var resetToThisRevision = new MenuItem();
resetToThisRevision.Header = App.Text("ChangeCM.CheckoutThisRevision");
resetToThisRevision.Icon = App.CreateMenuIcon("Icons.File.Checkout");
resetToThisRevision.Click += async (_, ev) =>
{
await vm.CheckoutSingleFileAsync(change);
ev.Handled = true;
};
var copyPath = new MenuItem();
copyPath.Header = App.Text("CopyPath");
copyPath.Icon = App.CreateMenuIcon("Icons.Copy");
copyPath.Tag = OperatingSystem.IsMacOS() ? "⌘+C" : "Ctrl+C";
copyPath.Click += async (_, ev) =>
{
await App.CopyTextAsync(change.Path);
ev.Handled = true;
};
var copyFullPath = new MenuItem();
copyFullPath.Header = App.Text("CopyFullPath");
copyFullPath.Icon = App.CreateMenuIcon("Icons.Copy");
copyFullPath.Tag = OperatingSystem.IsMacOS() ? "⌘+⇧+C" : "Ctrl+Shift+C";
copyFullPath.Click += async (_, ev) =>
{
await App.CopyTextAsync(fullPath);
ev.Handled = true;
};
var menu = new ContextMenu();
menu.Items.Add(openWithMerger);
menu.Items.Add(explore);
menu.Items.Add(new MenuItem { Header = "-" });
menu.Items.Add(resetToThisRevision);
menu.Items.Add(new MenuItem { Header = "-" });
menu.Items.Add(copyPath);
menu.Items.Add(copyFullPath);
menu.Open(view);
}
else
{
var resetToThisRevision = new MenuItem();
resetToThisRevision.Header = App.Text("ChangeCM.CheckoutThisRevision");
resetToThisRevision.Icon = App.CreateMenuIcon("Icons.File.Checkout");
resetToThisRevision.Click += async (_, ev) =>
{
await vm.CheckoutMultipleFileAsync(selected);
ev.Handled = true;
};
var copyPath = new MenuItem();
copyPath.Header = App.Text("CopyPath");
copyPath.Icon = App.CreateMenuIcon("Icons.Copy");
copyPath.Tag = OperatingSystem.IsMacOS() ? "⌘+C" : "Ctrl+C";
copyPath.Click += async (_, ev) =>
var applyChanges = new MenuItem();
applyChanges.Header = App.Text("StashCM.ApplyFileChanges");
applyChanges.Icon = App.CreateMenuIcon("Icons.Diff");
applyChanges.Click += async (_, ev) =>
{
await vm.ApplySelectedChanges(selected);
ev.Handled = true;
};
var checkoutFiles = new MenuItem();
checkoutFiles.Header = App.Text("ChangeCM.CheckoutThisRevision");
checkoutFiles.Icon = App.CreateMenuIcon("Icons.File.Checkout");
checkoutFiles.Click += async (_, ev) =>
{
await vm.CheckoutFilesAsync(selected);
ev.Handled = true;
};
var copyPath = new MenuItem();
copyPath.Header = App.Text("CopyPath");
copyPath.Icon = App.CreateMenuIcon("Icons.Copy");
copyPath.Tag = OperatingSystem.IsMacOS() ? "⌘+C" : "Ctrl+C";
copyPath.Click += async (_, ev) =>
{
if (selected.Count == 1)
{
await App.CopyTextAsync(selected[0].Path);
}
else
{
var builder = new StringBuilder();
foreach (var c in selected)
builder.AppendLine(c.Path);
await App.CopyTextAsync(builder.ToString());
ev.Handled = true;
};
}
var copyFullPath = new MenuItem();
copyFullPath.Header = App.Text("CopyFullPath");
copyFullPath.Icon = App.CreateMenuIcon("Icons.Copy");
copyFullPath.Tag = OperatingSystem.IsMacOS() ? "⌘+⇧+C" : "Ctrl+Shift+C";
copyFullPath.Click += async (_, ev) =>
ev.Handled = true;
};
var copyFullPath = new MenuItem();
copyFullPath.Header = App.Text("CopyFullPath");
copyFullPath.Icon = App.CreateMenuIcon("Icons.Copy");
copyFullPath.Tag = OperatingSystem.IsMacOS() ? "⌘+⇧+C" : "Ctrl+Shift+C";
copyFullPath.Click += async (_, ev) =>
{
if (selected.Count == 1)
{
await App.CopyTextAsync(vm.GetAbsPath(selected[0].Path));
}
else
{
var builder = new StringBuilder();
foreach (var c in selected)
builder.AppendLine(vm.GetAbsPath(c.Path));
await App.CopyTextAsync(builder.ToString());
ev.Handled = true;
};
}
var menu = new ContextMenu();
menu.Items.Add(resetToThisRevision);
menu.Items.Add(new MenuItem { Header = "-" });
menu.Items.Add(copyPath);
menu.Items.Add(copyFullPath);
menu.Open(view);
}
ev.Handled = true;
};
menu.Items.Add(applyChanges);
menu.Items.Add(checkoutFiles);
menu.Items.Add(new MenuItem { Header = "-" });
menu.Items.Add(copyPath);
menu.Items.Add(copyFullPath);
menu.Open(view);
}
e.Handled = true;