feature: saving as patch supports multiple commits (#658)

Signed-off-by: leo <longshuang@msn.cn>
This commit is contained in:
leo 2024-11-06 12:35:55 +08:00
parent c72506d939
commit 9cb85081ab
No known key found for this signature in database
9 changed files with 110 additions and 8 deletions

View file

@ -9,7 +9,7 @@ namespace SourceGit.Commands
{
public static class SaveChangesAsPatch
{
public static bool Exec(string repo, List<Models.Change> changes, bool isUnstaged, string saveTo)
public static bool ProcessLocalChanges(string repo, List<Models.Change> 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<Models.Change> 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();

View file

@ -238,6 +238,7 @@
<x:String x:Key="Text.Diff.Next" xml:space="preserve">Next Difference</x:String>
<x:String x:Key="Text.Diff.NoChange" xml:space="preserve">NO CHANGES OR ONLY EOL CHANGES</x:String>
<x:String x:Key="Text.Diff.Prev" xml:space="preserve">Previous Difference</x:String>
<x:String x:Key="Text.Diff.SaveAsPatch" xml:space="preserve">Save as Patch</x:String>
<x:String x:Key="Text.Diff.ShowHiddenSymbols" xml:space="preserve">Show hidden symbols</x:String>
<x:String x:Key="Text.Diff.SideBySide" xml:space="preserve">Side-By-Side Diff</x:String>
<x:String x:Key="Text.Diff.Submodule" xml:space="preserve">SUBMODULE</x:String>

View file

@ -241,6 +241,7 @@
<x:String x:Key="Text.Diff.Next" xml:space="preserve">下一个差异</x:String>
<x:String x:Key="Text.Diff.NoChange" xml:space="preserve">没有变更或仅有换行符差异</x:String>
<x:String x:Key="Text.Diff.Prev" xml:space="preserve">上一个差异</x:String>
<x:String x:Key="Text.Diff.SaveAsPatch" xml:space="preserve">保存为补丁文件</x:String>
<x:String x:Key="Text.Diff.ShowHiddenSymbols" xml:space="preserve">显示隐藏符号</x:String>
<x:String x:Key="Text.Diff.SideBySide" xml:space="preserve">分列对比</x:String>
<x:String x:Key="Text.Diff.Submodule" xml:space="preserve">子模块</x:String>

View file

@ -241,6 +241,7 @@
<x:String x:Key="Text.Diff.Next" xml:space="preserve">下一個差異</x:String>
<x:String x:Key="Text.Diff.NoChange" xml:space="preserve">沒有變更或僅有換行字元差異</x:String>
<x:String x:Key="Text.Diff.Prev" xml:space="preserve">上一個差異</x:String>
<x:String x:Key="Text.Diff.SaveAsPatch" xml:space="preserve">另存為修補檔</x:String>
<x:String x:Key="Text.Diff.ShowHiddenSymbols" xml:space="preserve">顯示隱藏符號</x:String>
<x:String x:Key="Text.Diff.SideBySide" xml:space="preserve">並排對比</x:String>
<x:String x:Key="Text.Diff.Submodule" xml:space="preserve">子模組</x:String>

View file

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

View file

@ -24,6 +24,11 @@ namespace SourceGit.ViewModels
private set => SetProperty(ref _endPoint, value);
}
public bool CanSaveAsPatch
{
get => _canSaveAsPatch;
}
public List<Models.Change> 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<Models.Change> _changes = null;
private List<Models.Change> _visibleChanges = null;
private List<Models.Change> _selectedChanges = null;

View file

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

View file

@ -35,21 +35,26 @@
<Grid RowDefinitions="50,*" Margin="4">
<!-- Compare Revision Info -->
<Grid Grid.Row="0" Margin="48,0,48,4" ColumnDefinitions="*,48,*">
<Grid Grid.Row="0" Margin="0,0,0,6" ColumnDefinitions="*,32,*,Auto">
<!-- Base Revision -->
<Border Grid.Column="0" BorderBrush="{DynamicResource Brush.Border2}" BorderThickness="1" Background="{DynamicResource Brush.Contents}" CornerRadius="4" Padding="4">
<ContentControl Content="{Binding StartPoint}"/>
</Border>
<!-- Swap Button -->
<!-- Swap Buttons -->
<Button Grid.Column="1" Classes="icon_button" Command="{Binding Swap}" HorizontalAlignment="Center" ToolTip.Tip="{DynamicResource Text.Diff.SwapCommits}">
<Path Width="16" Height="16" Fill="{DynamicResource Brush.FG2}" Data="{DynamicResource Icons.Compare}"/>
<Path Width="16" Height="16" Data="{DynamicResource Icons.Compare}"/>
</Button>
<!-- Right Revision -->
<Border Grid.Column="2" BorderBrush="{DynamicResource Brush.Border2}" BorderThickness="1" Background="{DynamicResource Brush.Contents}" CornerRadius="4" Padding="4">
<ContentControl Content="{Binding EndPoint}"/>
</Border>
<!-- Save As Patch Button -->
<Button Grid.Column="3" Classes="icon_button" Width="32" Click="OnSaveAsPatch" IsVisible="{Binding CanSaveAsPatch}" ToolTip.Tip="{DynamicResource Text.Diff.SaveAsPatch}">
<Path Width="16" Height="16" Data="{DynamicResource Icons.Diff}"/>
</Button>
</Grid>
<!-- Changes View -->

View file

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