mirror of
https://github.com/sourcegit-scm/sourcegit.git
synced 2024-12-25 21:07:20 -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 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))
|
using (var sw = File.Create(saveTo))
|
||||||
{
|
{
|
||||||
|
@ -23,6 +23,20 @@ namespace SourceGit.Commands
|
||||||
return true;
|
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)
|
private static bool ProcessSingleChange(string repo, Models.DiffOption opt, FileStream writer)
|
||||||
{
|
{
|
||||||
var starter = new ProcessStartInfo();
|
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.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.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.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.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.SideBySide" xml:space="preserve">Side-By-Side Diff</x:String>
|
||||||
<x:String x:Key="Text.Diff.Submodule" xml:space="preserve">SUBMODULE</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.Next" xml:space="preserve">下一个差异</x:String>
|
||||||
<x:String x:Key="Text.Diff.NoChange" 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.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.ShowHiddenSymbols" xml:space="preserve">显示隐藏符号</x:String>
|
||||||
<x:String x:Key="Text.Diff.SideBySide" 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>
|
<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.Next" xml:space="preserve">下一個差異</x:String>
|
||||||
<x:String x:Key="Text.Diff.NoChange" 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.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.ShowHiddenSymbols" xml:space="preserve">顯示隱藏符號</x:String>
|
||||||
<x:String x:Key="Text.Diff.SideBySide" 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>
|
<x:String x:Key="Text.Diff.Submodule" xml:space="preserve">子模組</x:String>
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Platform.Storage;
|
using Avalonia.Platform.Storage;
|
||||||
|
@ -258,6 +259,44 @@ namespace SourceGit.ViewModels
|
||||||
multipleMenu.Items.Add(new MenuItem() { Header = "-" });
|
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();
|
var copyMultipleSHAs = new MenuItem();
|
||||||
copyMultipleSHAs.Header = App.Text("CommitCM.CopySHA");
|
copyMultipleSHAs.Header = App.Text("CommitCM.CopySHA");
|
||||||
copyMultipleSHAs.Icon = App.CreateMenuIcon("Icons.Copy");
|
copyMultipleSHAs.Icon = App.CreateMenuIcon("Icons.Copy");
|
||||||
|
|
|
@ -24,6 +24,11 @@ namespace SourceGit.ViewModels
|
||||||
private set => SetProperty(ref _endPoint, value);
|
private set => SetProperty(ref _endPoint, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool CanSaveAsPatch
|
||||||
|
{
|
||||||
|
get => _canSaveAsPatch;
|
||||||
|
}
|
||||||
|
|
||||||
public List<Models.Change> VisibleChanges
|
public List<Models.Change> VisibleChanges
|
||||||
{
|
{
|
||||||
get => _visibleChanges;
|
get => _visibleChanges;
|
||||||
|
@ -73,6 +78,7 @@ namespace SourceGit.ViewModels
|
||||||
_repo = repo;
|
_repo = repo;
|
||||||
_startPoint = (object)startPoint ?? new Models.Null();
|
_startPoint = (object)startPoint ?? new Models.Null();
|
||||||
_endPoint = (object)endPoint ?? new Models.Null();
|
_endPoint = (object)endPoint ?? new Models.Null();
|
||||||
|
_canSaveAsPatch = startPoint != null && endPoint != null;
|
||||||
|
|
||||||
Task.Run(Refresh);
|
Task.Run(Refresh);
|
||||||
}
|
}
|
||||||
|
@ -105,6 +111,16 @@ namespace SourceGit.ViewModels
|
||||||
Task.Run(Refresh);
|
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()
|
public void ClearSearchFilter()
|
||||||
{
|
{
|
||||||
SearchFilter = string.Empty;
|
SearchFilter = string.Empty;
|
||||||
|
@ -218,6 +234,7 @@ namespace SourceGit.ViewModels
|
||||||
private string _repo;
|
private string _repo;
|
||||||
private object _startPoint = null;
|
private object _startPoint = null;
|
||||||
private object _endPoint = null;
|
private object _endPoint = null;
|
||||||
|
private bool _canSaveAsPatch = false;
|
||||||
private List<Models.Change> _changes = null;
|
private List<Models.Change> _changes = null;
|
||||||
private List<Models.Change> _visibleChanges = null;
|
private List<Models.Change> _visibleChanges = null;
|
||||||
private List<Models.Change> _selectedChanges = null;
|
private List<Models.Change> _selectedChanges = null;
|
||||||
|
|
|
@ -539,7 +539,7 @@ namespace SourceGit.ViewModels
|
||||||
var storageFile = await storageProvider.SaveFilePickerAsync(options);
|
var storageFile = await storageProvider.SaveFilePickerAsync(options);
|
||||||
if (storageFile != null)
|
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)
|
if (succ)
|
||||||
App.SendNotification(_repo.FullPath, App.Text("SaveAsPatchSuccess"));
|
App.SendNotification(_repo.FullPath, App.Text("SaveAsPatchSuccess"));
|
||||||
}
|
}
|
||||||
|
@ -858,7 +858,7 @@ namespace SourceGit.ViewModels
|
||||||
var storageFile = await storageProvider.SaveFilePickerAsync(options);
|
var storageFile = await storageProvider.SaveFilePickerAsync(options);
|
||||||
if (storageFile != null)
|
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)
|
if (succ)
|
||||||
App.SendNotification(_repo.FullPath, App.Text("SaveAsPatchSuccess"));
|
App.SendNotification(_repo.FullPath, App.Text("SaveAsPatchSuccess"));
|
||||||
}
|
}
|
||||||
|
@ -981,7 +981,7 @@ namespace SourceGit.ViewModels
|
||||||
var storageFile = await storageProvider.SaveFilePickerAsync(options);
|
var storageFile = await storageProvider.SaveFilePickerAsync(options);
|
||||||
if (storageFile != null)
|
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)
|
if (succ)
|
||||||
App.SendNotification(_repo.FullPath, App.Text("SaveAsPatchSuccess"));
|
App.SendNotification(_repo.FullPath, App.Text("SaveAsPatchSuccess"));
|
||||||
}
|
}
|
||||||
|
@ -1156,7 +1156,7 @@ namespace SourceGit.ViewModels
|
||||||
var storageFile = await storageProvider.SaveFilePickerAsync(options);
|
var storageFile = await storageProvider.SaveFilePickerAsync(options);
|
||||||
if (storageFile != null)
|
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)
|
if (succ)
|
||||||
App.SendNotification(_repo.FullPath, App.Text("SaveAsPatchSuccess"));
|
App.SendNotification(_repo.FullPath, App.Text("SaveAsPatchSuccess"));
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,21 +35,26 @@
|
||||||
|
|
||||||
<Grid RowDefinitions="50,*" Margin="4">
|
<Grid RowDefinitions="50,*" Margin="4">
|
||||||
<!-- Compare Revision Info -->
|
<!-- 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 -->
|
<!-- Base Revision -->
|
||||||
<Border Grid.Column="0" BorderBrush="{DynamicResource Brush.Border2}" BorderThickness="1" Background="{DynamicResource Brush.Contents}" CornerRadius="4" Padding="4">
|
<Border Grid.Column="0" BorderBrush="{DynamicResource Brush.Border2}" BorderThickness="1" Background="{DynamicResource Brush.Contents}" CornerRadius="4" Padding="4">
|
||||||
<ContentControl Content="{Binding StartPoint}"/>
|
<ContentControl Content="{Binding StartPoint}"/>
|
||||||
</Border>
|
</Border>
|
||||||
|
|
||||||
<!-- Swap Button -->
|
<!-- Swap Buttons -->
|
||||||
<Button Grid.Column="1" Classes="icon_button" Command="{Binding Swap}" HorizontalAlignment="Center" ToolTip.Tip="{DynamicResource Text.Diff.SwapCommits}">
|
<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>
|
</Button>
|
||||||
|
|
||||||
<!-- Right Revision -->
|
<!-- Right Revision -->
|
||||||
<Border Grid.Column="2" BorderBrush="{DynamicResource Brush.Border2}" BorderThickness="1" Background="{DynamicResource Brush.Contents}" CornerRadius="4" Padding="4">
|
<Border Grid.Column="2" BorderBrush="{DynamicResource Brush.Border2}" BorderThickness="1" Background="{DynamicResource Brush.Contents}" CornerRadius="4" Padding="4">
|
||||||
<ContentControl Content="{Binding EndPoint}"/>
|
<ContentControl Content="{Binding EndPoint}"/>
|
||||||
</Border>
|
</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>
|
</Grid>
|
||||||
|
|
||||||
<!-- Changes View -->
|
<!-- Changes View -->
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Input;
|
using Avalonia.Input;
|
||||||
|
using Avalonia.Interactivity;
|
||||||
|
using Avalonia.Platform.Storage;
|
||||||
|
|
||||||
namespace SourceGit.Views
|
namespace SourceGit.Views
|
||||||
{
|
{
|
||||||
|
@ -28,5 +30,27 @@ namespace SourceGit.Views
|
||||||
|
|
||||||
e.Handled = true;
|
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