feature: add Reset File(s) to <revision> context menu entry to selected change(s) in revision compare view (#2079)

Co-authored-by: ybeapps <ybeapps@gmail.com>
Signed-off-by: leo <longshuang@msn.cn>
This commit is contained in:
leo
2026-01-29 12:03:47 +08:00
parent 769c22083d
commit 96a9e99621
6 changed files with 248 additions and 25 deletions

View File

@@ -95,6 +95,7 @@
<x:String x:Key="Text.ChangeCM.GenerateCommitMessage" xml:space="preserve">Generate commit message</x:String>
<x:String x:Key="Text.ChangeCM.Merge" xml:space="preserve">Merge (Built-in)</x:String>
<x:String x:Key="Text.ChangeCM.MergeExternal" xml:space="preserve">Merge (External)</x:String>
<x:String x:Key="Text.ChangeCM.ResetFileTo" xml:space="preserve">Reset File(s) to ${0}$</x:String>
<x:String x:Key="Text.ChangeDisplayMode" xml:space="preserve">CHANGE DISPLAY MODE</x:String>
<x:String x:Key="Text.ChangeDisplayMode.Grid" xml:space="preserve">Show as File and Dir List</x:String>
<x:String x:Key="Text.ChangeDisplayMode.List" xml:space="preserve">Show as Path List</x:String>

View File

@@ -99,6 +99,7 @@
<x:String x:Key="Text.ChangeCM.GenerateCommitMessage" xml:space="preserve">生成提交信息</x:String>
<x:String x:Key="Text.ChangeCM.Merge" xml:space="preserve">解决冲突(内部工具)</x:String>
<x:String x:Key="Text.ChangeCM.MergeExternal" xml:space="preserve">解决冲突(外部工具)</x:String>
<x:String x:Key="Text.ChangeCM.ResetFileTo" xml:space="preserve">重置文件到 ${0}$</x:String>
<x:String x:Key="Text.ChangeDisplayMode" xml:space="preserve">切换变更显示模式</x:String>
<x:String x:Key="Text.ChangeDisplayMode.Grid" xml:space="preserve">文件名+路径列表模式</x:String>
<x:String x:Key="Text.ChangeDisplayMode.List" xml:space="preserve">全路径列表模式</x:String>

View File

@@ -99,6 +99,7 @@
<x:String x:Key="Text.ChangeCM.GenerateCommitMessage" xml:space="preserve">產生提交訊息</x:String>
<x:String x:Key="Text.ChangeCM.Merge" xml:space="preserve">解決衝突 (內建工具)</x:String>
<x:String x:Key="Text.ChangeCM.MergeExternal" xml:space="preserve">解決衝突 (外部工具)</x:String>
<x:String x:Key="Text.ChangeCM.ResetFileTo" xml:space="preserve">重設檔案到 ${0}$</x:String>
<x:String x:Key="Text.ChangeDisplayMode" xml:space="preserve">切換變更顯示模式</x:String>
<x:String x:Key="Text.ChangeDisplayMode.Grid" xml:space="preserve">檔案名稱 + 路徑列表模式</x:String>
<x:String x:Key="Text.ChangeDisplayMode.List" xml:space="preserve">全路徑列表模式</x:String>

View File

@@ -206,7 +206,7 @@ namespace SourceGit.ViewModels
var end = commits[0] as Models.Commit;
var start = commits[1] as Models.Commit;
DetailContext = new RevisionCompare(_repo.FullPath, start, end);
DetailContext = new RevisionCompare(_repo, start, end);
}
else
{
@@ -403,7 +403,7 @@ namespace SourceGit.ViewModels
_repo.SearchCommitContext.Selected = null;
head = await new Commands.QuerySingleCommit(_repo.FullPath, "HEAD").GetResultAsync();
if (head != null)
DetailContext = new RevisionCompare(_repo.FullPath, commit, head);
DetailContext = new RevisionCompare(_repo, commit, head);
return null;
}
@@ -413,7 +413,7 @@ namespace SourceGit.ViewModels
public void CompareWithWorktree(Models.Commit commit)
{
DetailContext = new RevisionCompare(_repo.FullPath, commit, null);
DetailContext = new RevisionCompare(_repo, commit, null);
}
private Repository _repo = null;

View File

@@ -1,6 +1,8 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using Avalonia.Threading;
using CommunityToolkit.Mvvm.ComponentModel;
@@ -26,7 +28,30 @@ namespace SourceGit.ViewModels
private set => SetProperty(ref _endPoint, value);
}
public bool CanSaveAsPatch { get; }
public string LeftSideDesc
{
get => GetDesc(StartPoint);
}
public string RightSideDesc
{
get => GetDesc(EndPoint);
}
public bool CanResetToLeft
{
get => !_repo.IsBare && _startPoint != null;
}
public bool CanResetToRight
{
get => !_repo.IsBare && _endPoint != null;
}
public bool CanSaveAsPatch
{
get => _startPoint != null && _endPoint != null;
}
public int TotalChanges
{
@@ -50,7 +75,7 @@ namespace SourceGit.ViewModels
if (value is { Count: 1 })
{
var option = new Models.DiffOption(GetSHA(_startPoint), GetSHA(_endPoint), value[0]);
DiffContext = new DiffContext(_repo, option, _diffContext);
DiffContext = new DiffContext(_repo.FullPath, option, _diffContext);
}
else
{
@@ -76,12 +101,11 @@ namespace SourceGit.ViewModels
private set => SetProperty(ref _diffContext, value);
}
public RevisionCompare(string repo, Models.Commit startPoint, Models.Commit endPoint)
public RevisionCompare(Repository repo, Models.Commit startPoint, Models.Commit endPoint)
{
_repo = repo;
_startPoint = (object)startPoint ?? new Models.Null();
_endPoint = (object)endPoint ?? new Models.Null();
CanSaveAsPatch = startPoint != null && endPoint != null;
Refresh();
}
@@ -100,23 +124,12 @@ namespace SourceGit.ViewModels
public void OpenChangeWithExternalDiffTool(Models.Change change)
{
var opt = new Models.DiffOption(GetSHA(_startPoint), GetSHA(_endPoint), change);
new Commands.DiffTool(_repo, opt).Open();
new Commands.DiffTool(_repo.FullPath, opt).Open();
}
public void NavigateTo(string commitSHA)
{
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);
break;
}
}
_repo?.NavigateToCommit(commitSHA);
}
public void Swap()
@@ -130,14 +143,170 @@ namespace SourceGit.ViewModels
public string GetAbsPath(string path)
{
return Native.OS.GetAbsPath(_repo, path);
return Native.OS.GetAbsPath(_repo.FullPath, path);
}
public async Task ResetToLeftAsync(Models.Change change)
{
var sha = GetSHA(_startPoint);
var log = _repo.CreateLog($"Reset File to '{GetDesc(_startPoint)}'");
if (change.Index == Models.ChangeState.Added)
{
var fullpath = Native.OS.GetAbsPath(_repo.FullPath, change.Path);
if (File.Exists(fullpath))
await new Commands.Remove(_repo.FullPath, [change.Path])
.Use(log)
.ExecAsync();
}
else if (change.Index == Models.ChangeState.Renamed)
{
var renamed = Native.OS.GetAbsPath(_repo.FullPath, change.Path);
if (File.Exists(renamed))
await new Commands.Remove(_repo.FullPath, [change.Path])
.Use(log)
.ExecAsync();
await new Commands.Checkout(_repo.FullPath)
.Use(log)
.FileWithRevisionAsync(change.OriginalPath, sha);
}
else
{
await new Commands.Checkout(_repo.FullPath)
.Use(log)
.FileWithRevisionAsync(change.Path, sha);
}
log.Complete();
}
public async Task ResetToRightAsync(Models.Change change)
{
var sha = GetSHA(_endPoint);
var log = _repo.CreateLog($"Reset File to '{GetDesc(_endPoint)}'");
if (change.Index == Models.ChangeState.Deleted)
{
var fullpath = Native.OS.GetAbsPath(_repo.FullPath, change.Path);
if (File.Exists(fullpath))
await new Commands.Remove(_repo.FullPath, [change.Path])
.Use(log)
.ExecAsync();
}
else if (change.Index == Models.ChangeState.Renamed)
{
var old = Native.OS.GetAbsPath(_repo.FullPath, change.OriginalPath);
if (File.Exists(old))
await new Commands.Remove(_repo.FullPath, [change.OriginalPath])
.Use(log)
.ExecAsync();
await new Commands.Checkout(_repo.FullPath)
.Use(log)
.FileWithRevisionAsync(change.Path, sha);
}
else
{
await new Commands.Checkout(_repo.FullPath)
.Use(log)
.FileWithRevisionAsync(change.Path, sha);
}
log.Complete();
}
public async Task ResetMultipleToLeftAsync(List<Models.Change> changes)
{
var sha = GetSHA(_startPoint);
var checkouts = new List<string>();
var removes = new List<string>();
foreach (var c in changes)
{
if (c.Index == Models.ChangeState.Added)
{
var fullpath = Native.OS.GetAbsPath(_repo.FullPath, c.Path);
if (File.Exists(fullpath))
removes.Add(c.Path);
}
else if (c.Index == Models.ChangeState.Renamed)
{
var old = Native.OS.GetAbsPath(_repo.FullPath, c.Path);
if (File.Exists(old))
removes.Add(c.Path);
checkouts.Add(c.OriginalPath);
}
else
{
checkouts.Add(c.Path);
}
}
var log = _repo.CreateLog($"Reset Files to '{GetDesc(_startPoint)}'");
if (removes.Count > 0)
await new Commands.Remove(_repo.FullPath, removes)
.Use(log)
.ExecAsync();
if (checkouts.Count > 0)
await new Commands.Checkout(_repo.FullPath)
.Use(log)
.MultipleFilesWithRevisionAsync(checkouts, sha);
log.Complete();
}
public async Task ResetMultipleToRightAsync(List<Models.Change> changes)
{
var sha = GetSHA(_endPoint);
var checkouts = new List<string>();
var removes = new List<string>();
foreach (var c in changes)
{
if (c.Index == Models.ChangeState.Deleted)
{
var fullpath = Native.OS.GetAbsPath(_repo.FullPath, c.Path);
if (File.Exists(fullpath))
removes.Add(c.Path);
}
else if (c.Index == Models.ChangeState.Renamed)
{
var renamed = Native.OS.GetAbsPath(_repo.FullPath, c.OriginalPath);
if (File.Exists(renamed))
removes.Add(c.OriginalPath);
checkouts.Add(c.Path);
}
else
{
checkouts.Add(c.Path);
}
}
var log = _repo.CreateLog($"Reset Files to '{GetDesc(_endPoint)}'");
if (removes.Count > 0)
await new Commands.Remove(_repo.FullPath, removes)
.Use(log)
.ExecAsync();
if (checkouts.Count > 0)
await new Commands.Checkout(_repo.FullPath)
.Use(log)
.MultipleFilesWithRevisionAsync(checkouts, sha);
log.Complete();
}
public async Task SaveChangesAsPatchAsync(List<Models.Change> changes, string saveTo)
{
var succ = await Commands.SaveChangesAsPatch.ProcessRevisionCompareChangesAsync(_repo, changes ?? _changes, GetSHA(_startPoint), GetSHA(_endPoint), saveTo);
var succ = await Commands.SaveChangesAsPatch.ProcessRevisionCompareChangesAsync(_repo.FullPath, changes ?? _changes, GetSHA(_startPoint), GetSHA(_endPoint), saveTo);
if (succ)
App.SendNotification(_repo, App.Text("SaveAsPatchSuccess"));
App.SendNotification(_repo.FullPath, App.Text("SaveAsPatchSuccess"));
}
public void ClearSearchFilter()
@@ -171,7 +340,7 @@ namespace SourceGit.ViewModels
{
Task.Run(async () =>
{
_changes = await new Commands.CompareRevisions(_repo, GetSHA(_startPoint), GetSHA(_endPoint))
_changes = await new Commands.CompareRevisions(_repo.FullPath, GetSHA(_startPoint), GetSHA(_endPoint))
.ReadAsync()
.ConfigureAwait(false);
@@ -205,7 +374,12 @@ namespace SourceGit.ViewModels
return obj is Models.Commit commit ? commit.SHA : string.Empty;
}
private string _repo;
private string GetDesc(object obj)
{
return obj is Models.Commit commit ? commit.GetFriendlyName() : App.Text("Worktree");
}
private Repository _repo;
private bool _isLoading = true;
private object _startPoint = null;
private object _endPoint = null;

View File

@@ -83,6 +83,26 @@ namespace SourceGit.Views
menu.Items.Add(explore);
}
var resetToLeft = new MenuItem();
resetToLeft.Header = App.Text("ChangeCM.ResetFileTo", vm.LeftSideDesc);
resetToLeft.Icon = App.CreateMenuIcon("Icons.File.Checkout");
resetToLeft.IsEnabled = vm.CanResetToLeft;
resetToLeft.Click += async (_, ev) =>
{
await vm.ResetToLeftAsync(change);
ev.Handled = true;
};
var resetToRight = new MenuItem();
resetToRight.Header = App.Text("ChangeCM.ResetFileTo", vm.RightSideDesc);
resetToRight.Icon = App.CreateMenuIcon("Icons.File.Checkout");
resetToRight.IsEnabled = vm.CanResetToRight;
resetToRight.Click += async (_, ev) =>
{
await vm.ResetToRightAsync(change);
ev.Handled = true;
};
var copyPath = new MenuItem();
copyPath.Header = App.Text("CopyPath");
copyPath.Icon = App.CreateMenuIcon("Icons.Copy");
@@ -106,11 +126,34 @@ namespace SourceGit.Views
menu.Items.Add(new MenuItem() { Header = "-" });
menu.Items.Add(patch);
menu.Items.Add(new MenuItem() { Header = "-" });
menu.Items.Add(resetToLeft);
menu.Items.Add(resetToRight);
menu.Items.Add(new MenuItem() { Header = "-" });
menu.Items.Add(copyPath);
menu.Items.Add(copyFullPath);
}
else
{
var resetToLeft = new MenuItem();
resetToLeft.Header = App.Text("ChangeCM.ResetFileTo", vm.LeftSideDesc);
resetToLeft.Icon = App.CreateMenuIcon("Icons.File.Checkout");
resetToLeft.IsEnabled = vm.CanResetToLeft;
resetToLeft.Click += async (_, ev) =>
{
await vm.ResetMultipleToLeftAsync(selected);
ev.Handled = true;
};
var resetToRight = new MenuItem();
resetToRight.Header = App.Text("ChangeCM.ResetFileTo", vm.RightSideDesc);
resetToRight.Icon = App.CreateMenuIcon("Icons.File.Checkout");
resetToRight.IsEnabled = vm.CanResetToRight;
resetToRight.Click += async (_, ev) =>
{
await vm.ResetMultipleToRightAsync(selected);
ev.Handled = true;
};
var copyPath = new MenuItem();
copyPath.Header = App.Text("CopyPath");
copyPath.Icon = App.CreateMenuIcon("Icons.Copy");
@@ -141,6 +184,9 @@ namespace SourceGit.Views
menu.Items.Add(patch);
menu.Items.Add(new MenuItem() { Header = "-" });
menu.Items.Add(resetToLeft);
menu.Items.Add(resetToRight);
menu.Items.Add(new MenuItem() { Header = "-" });
menu.Items.Add(copyPath);
menu.Items.Add(copyFullPath);
}