diff --git a/src/Commands/SaveChangesAsPatch.cs b/src/Commands/SaveChangesAsPatch.cs index 409127ba..461bbfb5 100644 --- a/src/Commands/SaveChangesAsPatch.cs +++ b/src/Commands/SaveChangesAsPatch.cs @@ -9,7 +9,7 @@ namespace SourceGit.Commands { public static class SaveChangesAsPatch { - public static bool Exec(string repo, List changes, bool isUnstaged, string saveTo) + public static bool ProcessLocalChanges(string repo, List changes, bool isUnstaged, string saveTo) { using (var sw = File.Create(saveTo)) { @@ -23,6 +23,20 @@ namespace SourceGit.Commands return true; } + public static bool ProcessRevisionCompareChanges(string repo, List changes, string baseRevision, string targetRevision, string saveTo) + { + using (var sw = File.Create(saveTo)) + { + foreach (var change in changes) + { + if (!ProcessSingleChange(repo, new Models.DiffOption(baseRevision, targetRevision, change), sw)) + return false; + } + } + + return true; + } + private static bool ProcessSingleChange(string repo, Models.DiffOption opt, FileStream writer) { var starter = new ProcessStartInfo(); diff --git a/src/Resources/Locales/en_US.axaml b/src/Resources/Locales/en_US.axaml index ee44181b..7393fd19 100644 --- a/src/Resources/Locales/en_US.axaml +++ b/src/Resources/Locales/en_US.axaml @@ -238,6 +238,7 @@ Next Difference NO CHANGES OR ONLY EOL CHANGES Previous Difference + Save as Patch Show hidden symbols Side-By-Side Diff SUBMODULE diff --git a/src/Resources/Locales/zh_CN.axaml b/src/Resources/Locales/zh_CN.axaml index b97f9aab..dd457295 100644 --- a/src/Resources/Locales/zh_CN.axaml +++ b/src/Resources/Locales/zh_CN.axaml @@ -241,6 +241,7 @@ 下一个差异 没有变更或仅有换行符差异 上一个差异 + 保存为补丁文件 显示隐藏符号 分列对比 子模块 diff --git a/src/Resources/Locales/zh_TW.axaml b/src/Resources/Locales/zh_TW.axaml index 494616ab..c4963c39 100644 --- a/src/Resources/Locales/zh_TW.axaml +++ b/src/Resources/Locales/zh_TW.axaml @@ -241,6 +241,7 @@ 下一個差異 沒有變更或僅有換行字元差異 上一個差異 + 另存為修補檔 顯示隱藏符號 並排對比 子模組 diff --git a/src/ViewModels/Histories.cs b/src/ViewModels/Histories.cs index 713e1635..688dab87 100644 --- a/src/ViewModels/Histories.cs +++ b/src/ViewModels/Histories.cs @@ -2,6 +2,7 @@ using System.Collections; using System.Collections.Generic; using System.Text; +using System.Threading.Tasks; using Avalonia.Controls; using Avalonia.Platform.Storage; @@ -258,6 +259,44 @@ namespace SourceGit.ViewModels multipleMenu.Items.Add(new MenuItem() { Header = "-" }); } + var saveToPatchMultiple = new MenuItem(); + saveToPatchMultiple.Icon = App.CreateMenuIcon("Icons.Diff"); + saveToPatchMultiple.Header = App.Text("CommitCM.SaveAsPatch"); + saveToPatchMultiple.Click += async (_, e) => + { + var storageProvider = App.GetStorageProvider(); + if (storageProvider == null) + return; + + var options = new FolderPickerOpenOptions() { AllowMultiple = false }; + try + { + var picker = await storageProvider.OpenFolderPickerAsync(options); + if (picker.Count == 1) + { + var saveTo = $"{picker[0].Path.LocalPath}/patches"; + var succ = false; + foreach (var c in selected) + { + succ = await Task.Run(() => new Commands.FormatPatch(_repo.FullPath, c.SHA, saveTo).Exec()); + if (!succ) + break; + } + + if (succ) + App.SendNotification(_repo.FullPath, App.Text("SaveAsPatchSuccess")); + } + } + catch (Exception exception) + { + App.RaiseException(_repo.FullPath, $"Failed to save as patch: {exception.Message}"); + } + + e.Handled = true; + }; + multipleMenu.Items.Add(saveToPatchMultiple); + multipleMenu.Items.Add(new MenuItem() { Header = "-" }); + var copyMultipleSHAs = new MenuItem(); copyMultipleSHAs.Header = App.Text("CommitCM.CopySHA"); copyMultipleSHAs.Icon = App.CreateMenuIcon("Icons.Copy"); diff --git a/src/ViewModels/RevisionCompare.cs b/src/ViewModels/RevisionCompare.cs index 55c85129..ffc05643 100644 --- a/src/ViewModels/RevisionCompare.cs +++ b/src/ViewModels/RevisionCompare.cs @@ -24,6 +24,11 @@ namespace SourceGit.ViewModels private set => SetProperty(ref _endPoint, value); } + public bool CanSaveAsPatch + { + get => _canSaveAsPatch; + } + public List VisibleChanges { get => _visibleChanges; @@ -73,6 +78,7 @@ namespace SourceGit.ViewModels _repo = repo; _startPoint = (object)startPoint ?? new Models.Null(); _endPoint = (object)endPoint ?? new Models.Null(); + _canSaveAsPatch = startPoint != null && endPoint != null; Task.Run(Refresh); } @@ -105,6 +111,16 @@ namespace SourceGit.ViewModels Task.Run(Refresh); } + public void SaveAsPatch(string saveTo) + { + Task.Run(() => + { + var succ = Commands.SaveChangesAsPatch.ProcessRevisionCompareChanges(_repo, _changes, GetSHA(_startPoint), GetSHA(_endPoint), saveTo); + if (succ) + Dispatcher.UIThread.Invoke(() => App.SendNotification(_repo, App.Text("SaveAsPatchSuccess"))); + }); + } + public void ClearSearchFilter() { SearchFilter = string.Empty; @@ -218,6 +234,7 @@ namespace SourceGit.ViewModels private string _repo; private object _startPoint = null; private object _endPoint = null; + private bool _canSaveAsPatch = false; private List _changes = null; private List _visibleChanges = null; private List _selectedChanges = null; diff --git a/src/ViewModels/WorkingCopy.cs b/src/ViewModels/WorkingCopy.cs index 020053f9..4bac8e08 100644 --- a/src/ViewModels/WorkingCopy.cs +++ b/src/ViewModels/WorkingCopy.cs @@ -539,7 +539,7 @@ namespace SourceGit.ViewModels var storageFile = await storageProvider.SaveFilePickerAsync(options); if (storageFile != null) { - var succ = await Task.Run(() => Commands.SaveChangesAsPatch.Exec(_repo.FullPath, _selectedUnstaged, true, storageFile.Path.LocalPath)); + var succ = await Task.Run(() => Commands.SaveChangesAsPatch.ProcessLocalChanges(_repo.FullPath, _selectedUnstaged, true, storageFile.Path.LocalPath)); if (succ) App.SendNotification(_repo.FullPath, App.Text("SaveAsPatchSuccess")); } @@ -858,7 +858,7 @@ namespace SourceGit.ViewModels var storageFile = await storageProvider.SaveFilePickerAsync(options); if (storageFile != null) { - var succ = await Task.Run(() => Commands.SaveChangesAsPatch.Exec(_repo.FullPath, _selectedUnstaged, true, storageFile.Path.LocalPath)); + var succ = await Task.Run(() => Commands.SaveChangesAsPatch.ProcessLocalChanges(_repo.FullPath, _selectedUnstaged, true, storageFile.Path.LocalPath)); if (succ) App.SendNotification(_repo.FullPath, App.Text("SaveAsPatchSuccess")); } @@ -981,7 +981,7 @@ namespace SourceGit.ViewModels var storageFile = await storageProvider.SaveFilePickerAsync(options); if (storageFile != null) { - var succ = await Task.Run(() => Commands.SaveChangesAsPatch.Exec(_repo.FullPath, _selectedStaged, false, storageFile.Path.LocalPath)); + var succ = await Task.Run(() => Commands.SaveChangesAsPatch.ProcessLocalChanges(_repo.FullPath, _selectedStaged, false, storageFile.Path.LocalPath)); if (succ) App.SendNotification(_repo.FullPath, App.Text("SaveAsPatchSuccess")); } @@ -1156,7 +1156,7 @@ namespace SourceGit.ViewModels var storageFile = await storageProvider.SaveFilePickerAsync(options); if (storageFile != null) { - var succ = await Task.Run(() => Commands.SaveChangesAsPatch.Exec(_repo.FullPath, _selectedStaged, false, storageFile.Path.LocalPath)); + var succ = await Task.Run(() => Commands.SaveChangesAsPatch.ProcessLocalChanges(_repo.FullPath, _selectedStaged, false, storageFile.Path.LocalPath)); if (succ) App.SendNotification(_repo.FullPath, App.Text("SaveAsPatchSuccess")); } diff --git a/src/Views/RevisionCompare.axaml b/src/Views/RevisionCompare.axaml index f6303b45..912fde39 100644 --- a/src/Views/RevisionCompare.axaml +++ b/src/Views/RevisionCompare.axaml @@ -35,21 +35,26 @@ - + - + + + + diff --git a/src/Views/RevisionCompare.axaml.cs b/src/Views/RevisionCompare.axaml.cs index b484b78f..2c548240 100644 --- a/src/Views/RevisionCompare.axaml.cs +++ b/src/Views/RevisionCompare.axaml.cs @@ -1,5 +1,7 @@ using Avalonia.Controls; using Avalonia.Input; +using Avalonia.Interactivity; +using Avalonia.Platform.Storage; namespace SourceGit.Views { @@ -28,5 +30,27 @@ namespace SourceGit.Views e.Handled = true; } + + private async void OnSaveAsPatch(object sender, RoutedEventArgs e) + { + var topLevel = TopLevel.GetTopLevel(this); + if (topLevel == null) + return; + + var vm = DataContext as ViewModels.RevisionCompare; + if (vm == null) + return; + + var options = new FilePickerSaveOptions(); + options.Title = App.Text("FileCM.SaveAsPatch"); + options.DefaultExtension = ".patch"; + options.FileTypeChoices = [new FilePickerFileType("Patch File") { Patterns = ["*.patch"] }]; + + var storageFile = await topLevel.StorageProvider.SaveFilePickerAsync(options); + if (storageFile != null) + vm.SaveAsPatch(storageFile.Path.LocalPath); + + e.Handled = true; + } } }