Skip to content

Commit 9cb8508

Browse files
committed
feature: saving as patch supports multiple commits (#658)
Signed-off-by: leo <[email protected]>
1 parent c72506d commit 9cb8508

File tree

9 files changed

+110
-8
lines changed

9 files changed

+110
-8
lines changed

src/Commands/SaveChangesAsPatch.cs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ namespace SourceGit.Commands
99
{
1010
public static class SaveChangesAsPatch
1111
{
12-
public static bool Exec(string repo, List<Models.Change> changes, bool isUnstaged, string saveTo)
12+
public static bool ProcessLocalChanges(string repo, List<Models.Change> changes, bool isUnstaged, string saveTo)
1313
{
1414
using (var sw = File.Create(saveTo))
1515
{
@@ -23,6 +23,20 @@ public static bool Exec(string repo, List<Models.Change> changes, bool isUnstage
2323
return true;
2424
}
2525

26+
public static bool ProcessRevisionCompareChanges(string repo, List<Models.Change> changes, string baseRevision, string targetRevision, string saveTo)
27+
{
28+
using (var sw = File.Create(saveTo))
29+
{
30+
foreach (var change in changes)
31+
{
32+
if (!ProcessSingleChange(repo, new Models.DiffOption(baseRevision, targetRevision, change), sw))
33+
return false;
34+
}
35+
}
36+
37+
return true;
38+
}
39+
2640
private static bool ProcessSingleChange(string repo, Models.DiffOption opt, FileStream writer)
2741
{
2842
var starter = new ProcessStartInfo();

src/Resources/Locales/en_US.axaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,7 @@
238238
<x:String x:Key="Text.Diff.Next" xml:space="preserve">Next Difference</x:String>
239239
<x:String x:Key="Text.Diff.NoChange" xml:space="preserve">NO CHANGES OR ONLY EOL CHANGES</x:String>
240240
<x:String x:Key="Text.Diff.Prev" xml:space="preserve">Previous Difference</x:String>
241+
<x:String x:Key="Text.Diff.SaveAsPatch" xml:space="preserve">Save as Patch</x:String>
241242
<x:String x:Key="Text.Diff.ShowHiddenSymbols" xml:space="preserve">Show hidden symbols</x:String>
242243
<x:String x:Key="Text.Diff.SideBySide" xml:space="preserve">Side-By-Side Diff</x:String>
243244
<x:String x:Key="Text.Diff.Submodule" xml:space="preserve">SUBMODULE</x:String>

src/Resources/Locales/zh_CN.axaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,7 @@
241241
<x:String x:Key="Text.Diff.Next" xml:space="preserve">下一个差异</x:String>
242242
<x:String x:Key="Text.Diff.NoChange" xml:space="preserve">没有变更或仅有换行符差异</x:String>
243243
<x:String x:Key="Text.Diff.Prev" xml:space="preserve">上一个差异</x:String>
244+
<x:String x:Key="Text.Diff.SaveAsPatch" xml:space="preserve">保存为补丁文件</x:String>
244245
<x:String x:Key="Text.Diff.ShowHiddenSymbols" xml:space="preserve">显示隐藏符号</x:String>
245246
<x:String x:Key="Text.Diff.SideBySide" xml:space="preserve">分列对比</x:String>
246247
<x:String x:Key="Text.Diff.Submodule" xml:space="preserve">子模块</x:String>

src/Resources/Locales/zh_TW.axaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,7 @@
241241
<x:String x:Key="Text.Diff.Next" xml:space="preserve">下一個差異</x:String>
242242
<x:String x:Key="Text.Diff.NoChange" xml:space="preserve">沒有變更或僅有換行字元差異</x:String>
243243
<x:String x:Key="Text.Diff.Prev" xml:space="preserve">上一個差異</x:String>
244+
<x:String x:Key="Text.Diff.SaveAsPatch" xml:space="preserve">另存為修補檔</x:String>
244245
<x:String x:Key="Text.Diff.ShowHiddenSymbols" xml:space="preserve">顯示隱藏符號</x:String>
245246
<x:String x:Key="Text.Diff.SideBySide" xml:space="preserve">並排對比</x:String>
246247
<x:String x:Key="Text.Diff.Submodule" xml:space="preserve">子模組</x:String>

src/ViewModels/Histories.cs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.Collections;
33
using System.Collections.Generic;
44
using System.Text;
5+
using System.Threading.Tasks;
56

67
using Avalonia.Controls;
78
using Avalonia.Platform.Storage;
@@ -258,6 +259,44 @@ public ContextMenu MakeContextMenu(ListBox list)
258259
multipleMenu.Items.Add(new MenuItem() { Header = "-" });
259260
}
260261

262+
var saveToPatchMultiple = new MenuItem();
263+
saveToPatchMultiple.Icon = App.CreateMenuIcon("Icons.Diff");
264+
saveToPatchMultiple.Header = App.Text("CommitCM.SaveAsPatch");
265+
saveToPatchMultiple.Click += async (_, e) =>
266+
{
267+
var storageProvider = App.GetStorageProvider();
268+
if (storageProvider == null)
269+
return;
270+
271+
var options = new FolderPickerOpenOptions() { AllowMultiple = false };
272+
try
273+
{
274+
var picker = await storageProvider.OpenFolderPickerAsync(options);
275+
if (picker.Count == 1)
276+
{
277+
var saveTo = $"{picker[0].Path.LocalPath}/patches";
278+
var succ = false;
279+
foreach (var c in selected)
280+
{
281+
succ = await Task.Run(() => new Commands.FormatPatch(_repo.FullPath, c.SHA, saveTo).Exec());
282+
if (!succ)
283+
break;
284+
}
285+
286+
if (succ)
287+
App.SendNotification(_repo.FullPath, App.Text("SaveAsPatchSuccess"));
288+
}
289+
}
290+
catch (Exception exception)
291+
{
292+
App.RaiseException(_repo.FullPath, $"Failed to save as patch: {exception.Message}");
293+
}
294+
295+
e.Handled = true;
296+
};
297+
multipleMenu.Items.Add(saveToPatchMultiple);
298+
multipleMenu.Items.Add(new MenuItem() { Header = "-" });
299+
261300
var copyMultipleSHAs = new MenuItem();
262301
copyMultipleSHAs.Header = App.Text("CommitCM.CopySHA");
263302
copyMultipleSHAs.Icon = App.CreateMenuIcon("Icons.Copy");

src/ViewModels/RevisionCompare.cs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@ public object EndPoint
2424
private set => SetProperty(ref _endPoint, value);
2525
}
2626

27+
public bool CanSaveAsPatch
28+
{
29+
get => _canSaveAsPatch;
30+
}
31+
2732
public List<Models.Change> VisibleChanges
2833
{
2934
get => _visibleChanges;
@@ -73,6 +78,7 @@ public RevisionCompare(string repo, Models.Commit startPoint, Models.Commit endP
7378
_repo = repo;
7479
_startPoint = (object)startPoint ?? new Models.Null();
7580
_endPoint = (object)endPoint ?? new Models.Null();
81+
_canSaveAsPatch = startPoint != null && endPoint != null;
7682

7783
Task.Run(Refresh);
7884
}
@@ -105,6 +111,16 @@ public void Swap()
105111
Task.Run(Refresh);
106112
}
107113

114+
public void SaveAsPatch(string saveTo)
115+
{
116+
Task.Run(() =>
117+
{
118+
var succ = Commands.SaveChangesAsPatch.ProcessRevisionCompareChanges(_repo, _changes, GetSHA(_startPoint), GetSHA(_endPoint), saveTo);
119+
if (succ)
120+
Dispatcher.UIThread.Invoke(() => App.SendNotification(_repo, App.Text("SaveAsPatchSuccess")));
121+
});
122+
}
123+
108124
public void ClearSearchFilter()
109125
{
110126
SearchFilter = string.Empty;
@@ -218,6 +234,7 @@ private string GetSHA(object obj)
218234
private string _repo;
219235
private object _startPoint = null;
220236
private object _endPoint = null;
237+
private bool _canSaveAsPatch = false;
221238
private List<Models.Change> _changes = null;
222239
private List<Models.Change> _visibleChanges = null;
223240
private List<Models.Change> _selectedChanges = null;

src/ViewModels/WorkingCopy.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -539,7 +539,7 @@ public ContextMenu CreateContextMenuForUnstagedChanges()
539539
var storageFile = await storageProvider.SaveFilePickerAsync(options);
540540
if (storageFile != null)
541541
{
542-
var succ = await Task.Run(() => Commands.SaveChangesAsPatch.Exec(_repo.FullPath, _selectedUnstaged, true, storageFile.Path.LocalPath));
542+
var succ = await Task.Run(() => Commands.SaveChangesAsPatch.ProcessLocalChanges(_repo.FullPath, _selectedUnstaged, true, storageFile.Path.LocalPath));
543543
if (succ)
544544
App.SendNotification(_repo.FullPath, App.Text("SaveAsPatchSuccess"));
545545
}
@@ -858,7 +858,7 @@ public ContextMenu CreateContextMenuForUnstagedChanges()
858858
var storageFile = await storageProvider.SaveFilePickerAsync(options);
859859
if (storageFile != null)
860860
{
861-
var succ = await Task.Run(() => Commands.SaveChangesAsPatch.Exec(_repo.FullPath, _selectedUnstaged, true, storageFile.Path.LocalPath));
861+
var succ = await Task.Run(() => Commands.SaveChangesAsPatch.ProcessLocalChanges(_repo.FullPath, _selectedUnstaged, true, storageFile.Path.LocalPath));
862862
if (succ)
863863
App.SendNotification(_repo.FullPath, App.Text("SaveAsPatchSuccess"));
864864
}
@@ -981,7 +981,7 @@ public ContextMenu CreateContextMenuForStagedChanges()
981981
var storageFile = await storageProvider.SaveFilePickerAsync(options);
982982
if (storageFile != null)
983983
{
984-
var succ = await Task.Run(() => Commands.SaveChangesAsPatch.Exec(_repo.FullPath, _selectedStaged, false, storageFile.Path.LocalPath));
984+
var succ = await Task.Run(() => Commands.SaveChangesAsPatch.ProcessLocalChanges(_repo.FullPath, _selectedStaged, false, storageFile.Path.LocalPath));
985985
if (succ)
986986
App.SendNotification(_repo.FullPath, App.Text("SaveAsPatchSuccess"));
987987
}
@@ -1156,7 +1156,7 @@ public ContextMenu CreateContextMenuForStagedChanges()
11561156
var storageFile = await storageProvider.SaveFilePickerAsync(options);
11571157
if (storageFile != null)
11581158
{
1159-
var succ = await Task.Run(() => Commands.SaveChangesAsPatch.Exec(_repo.FullPath, _selectedStaged, false, storageFile.Path.LocalPath));
1159+
var succ = await Task.Run(() => Commands.SaveChangesAsPatch.ProcessLocalChanges(_repo.FullPath, _selectedStaged, false, storageFile.Path.LocalPath));
11601160
if (succ)
11611161
App.SendNotification(_repo.FullPath, App.Text("SaveAsPatchSuccess"));
11621162
}

src/Views/RevisionCompare.axaml

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,21 +35,26 @@
3535

3636
<Grid RowDefinitions="50,*" Margin="4">
3737
<!-- Compare Revision Info -->
38-
<Grid Grid.Row="0" Margin="48,0,48,4" ColumnDefinitions="*,48,*">
38+
<Grid Grid.Row="0" Margin="0,0,0,6" ColumnDefinitions="*,32,*,Auto">
3939
<!-- Base Revision -->
4040
<Border Grid.Column="0" BorderBrush="{DynamicResource Brush.Border2}" BorderThickness="1" Background="{DynamicResource Brush.Contents}" CornerRadius="4" Padding="4">
4141
<ContentControl Content="{Binding StartPoint}"/>
4242
</Border>
4343

44-
<!-- Swap Button -->
44+
<!-- Swap Buttons -->
4545
<Button Grid.Column="1" Classes="icon_button" Command="{Binding Swap}" HorizontalAlignment="Center" ToolTip.Tip="{DynamicResource Text.Diff.SwapCommits}">
46-
<Path Width="16" Height="16" Fill="{DynamicResource Brush.FG2}" Data="{DynamicResource Icons.Compare}"/>
46+
<Path Width="16" Height="16" Data="{DynamicResource Icons.Compare}"/>
4747
</Button>
4848

4949
<!-- Right Revision -->
5050
<Border Grid.Column="2" BorderBrush="{DynamicResource Brush.Border2}" BorderThickness="1" Background="{DynamicResource Brush.Contents}" CornerRadius="4" Padding="4">
5151
<ContentControl Content="{Binding EndPoint}"/>
5252
</Border>
53+
54+
<!-- Save As Patch Button -->
55+
<Button Grid.Column="3" Classes="icon_button" Width="32" Click="OnSaveAsPatch" IsVisible="{Binding CanSaveAsPatch}" ToolTip.Tip="{DynamicResource Text.Diff.SaveAsPatch}">
56+
<Path Width="16" Height="16" Data="{DynamicResource Icons.Diff}"/>
57+
</Button>
5358
</Grid>
5459

5560
<!-- Changes View -->

src/Views/RevisionCompare.axaml.cs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
using Avalonia.Controls;
22
using Avalonia.Input;
3+
using Avalonia.Interactivity;
4+
using Avalonia.Platform.Storage;
35

46
namespace SourceGit.Views
57
{
@@ -28,5 +30,27 @@ private void OnPressedSHA(object sender, PointerPressedEventArgs e)
2830

2931
e.Handled = true;
3032
}
33+
34+
private async void OnSaveAsPatch(object sender, RoutedEventArgs e)
35+
{
36+
var topLevel = TopLevel.GetTopLevel(this);
37+
if (topLevel == null)
38+
return;
39+
40+
var vm = DataContext as ViewModels.RevisionCompare;
41+
if (vm == null)
42+
return;
43+
44+
var options = new FilePickerSaveOptions();
45+
options.Title = App.Text("FileCM.SaveAsPatch");
46+
options.DefaultExtension = ".patch";
47+
options.FileTypeChoices = [new FilePickerFileType("Patch File") { Patterns = ["*.patch"] }];
48+
49+
var storageFile = await topLevel.StorageProvider.SaveFilePickerAsync(options);
50+
if (storageFile != null)
51+
vm.SaveAsPatch(storageFile.Path.LocalPath);
52+
53+
e.Handled = true;
54+
}
3155
}
3256
}

0 commit comments

Comments
 (0)