mirror of
https://github.com/sourcegit-scm/sourcegit.git
synced 2024-12-24 20:57:19 -08:00
feature: saving as patch supports multiple commits (#658)
Signed-off-by: leo <longshuang@msn.cn>
This commit is contained in:
parent
c72506d939
commit
9cb85081ab
9 changed files with 110 additions and 8 deletions
|
@ -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();
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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"));
|
||||
}
|
||||
|
|
|
@ -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 -->
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue