Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions src/Commands/CompareRevisions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,15 @@ public CompareRevisions(string repo, string start, string end)
Args = $"diff --name-status {based} {end}";
}

public CompareRevisions(string repo, string start, string end, string path)
{
WorkingDirectory = repo;
Context = repo;

var based = string.IsNullOrEmpty(start) ? "-R" : start;
Args = $"diff --name-status {based} {end} -- {path}";
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I apologize, I just noticed that I forgot to add quotes around the {path}. Could you please fix this during your review?

}

public List<Models.Change> Result()
{
Exec();
Expand Down
3 changes: 3 additions & 0 deletions src/Converters/IntConverters.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ public static class IntConverters
public static readonly FuncValueConverter<int, bool> IsNotOne =
new FuncValueConverter<int, bool>(v => v != 1);

public static readonly FuncValueConverter<int, bool> IsTwo =
new FuncValueConverter<int, bool>(v => v == 2);

public static readonly FuncValueConverter<int, bool> IsSubjectLengthBad =
new FuncValueConverter<int, bool>(v => v > ViewModels.Preferences.Instance.SubjectGuideLength);

Expand Down
99 changes: 83 additions & 16 deletions src/ViewModels/FileHistories.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,12 @@ public List<Models.Commit> Commits
set => SetProperty(ref _commits, value);
}

public Models.Commit SelectedCommit
public List<Models.Commit> SelectedCommits
{
get => _selectedCommit;
get => _selectedCommits;
set
{
if (SetProperty(ref _selectedCommit, value))
if (SetProperty(ref _selectedCommits, value))
RefreshViewContent();
}
}
Expand All @@ -51,6 +51,18 @@ public bool IsViewContent
}
}

public Models.Commit StartPoint
{
get => _startPoint;
set => SetProperty(ref _startPoint, value);
}

public Models.Commit EndPoint
{
get => _endPoint;
set => SetProperty(ref _endPoint, value);
}

public object ViewContent
{
get => _viewContent;
Expand All @@ -71,7 +83,7 @@ public FileHistories(Repository repo, string file, string commit = null)
IsLoading = false;
Commits = commits;
if (commits.Count > 0)
SelectedCommit = commits[0];
SelectedCommits = [commits[0]];
});
});
}
Expand All @@ -83,26 +95,51 @@ public void NavigateToCommit(Models.Commit commit)

public void ResetToSelectedRevision()
{
new Commands.Checkout(_repo.FullPath).FileWithRevision(_file, $"{_selectedCommit.SHA}");
if (_selectedCommits is not { Count: 1 })
return;
new Commands.Checkout(_repo.FullPath).FileWithRevision(_file, $"{_selectedCommits[0].SHA}");
}

public void Swap()
{
if (_selectedCommits is not { Count: 2 })
return;

(_selectedCommits[0], _selectedCommits[1]) = (_selectedCommits[1], _selectedCommits[0]);
RefreshViewContent();
}

public Task<bool> SaveAsPatch(string saveTo)
{
return Task.Run(() =>
{
Commands.SaveChangesAsPatch.ProcessRevisionCompareChanges(_repo.FullPath, _changes, GetSHA(_startPoint), GetSHA(_endPoint), saveTo);
return true;
});
}

private void RefreshViewContent()
{
if (_selectedCommit == null)
if (_selectedCommits == null || _selectedCommits.Count == 0)
{
ViewContent = null;
StartPoint = null;
EndPoint = null;
ViewContent = 0;
return;
}

if (_isViewContent)
if (_isViewContent && _selectedCommits.Count == 1)
SetViewContentAsRevisionFile();
else
SetViewContentAsDiff();
}

private void SetViewContentAsRevisionFile()
{
var objs = new Commands.QueryRevisionObjects(_repo.FullPath, _selectedCommit.SHA, _file).Result();
StartPoint = null;
EndPoint = null;
var selectedCommit = _selectedCommits[0];
var objs = new Commands.QueryRevisionObjects(_repo.FullPath, selectedCommit.SHA, _file).Result();
if (objs.Count == 0)
{
ViewContent = new FileHistoriesRevisionFile(_file, null);
Expand All @@ -115,13 +152,13 @@ private void SetViewContentAsRevisionFile()
case Models.ObjectType.Blob:
Task.Run(() =>
{
var isBinary = new Commands.IsBinary(_repo.FullPath, _selectedCommit.SHA, _file).Result();
var isBinary = new Commands.IsBinary(_repo.FullPath, selectedCommit.SHA, _file).Result();
if (isBinary)
{
var ext = Path.GetExtension(_file);
if (IMG_EXTS.Contains(ext))
{
var stream = Commands.QueryFileContent.Run(_repo.FullPath, _selectedCommit.SHA, _file);
var stream = Commands.QueryFileContent.Run(_repo.FullPath, selectedCommit.SHA, _file);
var fileSize = stream.Length;
var bitmap = fileSize > 0 ? new Bitmap(stream) : null;
var imageType = Path.GetExtension(_file).TrimStart('.').ToUpper(CultureInfo.CurrentCulture);
Expand All @@ -130,15 +167,15 @@ private void SetViewContentAsRevisionFile()
}
else
{
var size = new Commands.QueryFileSize(_repo.FullPath, _file, _selectedCommit.SHA).Result();
var size = new Commands.QueryFileSize(_repo.FullPath, _file, selectedCommit.SHA).Result();
var binaryFile = new Models.RevisionBinaryFile() { Size = size };
Dispatcher.UIThread.Invoke(() => ViewContent = new FileHistoriesRevisionFile(_file, binaryFile));
}

return;
}

var contentStream = Commands.QueryFileContent.Run(_repo.FullPath, _selectedCommit.SHA, _file);
var contentStream = Commands.QueryFileContent.Run(_repo.FullPath, selectedCommit.SHA, _file);
var content = new StreamReader(contentStream).ReadToEnd();
var matchLFS = REG_LFS_FORMAT().Match(content);
if (matchLFS.Success)
Expand Down Expand Up @@ -181,8 +218,35 @@ private void SetViewContentAsRevisionFile()

private void SetViewContentAsDiff()
{
var option = new Models.DiffOption(_selectedCommit, _file);
ViewContent = new DiffContext(_repo.FullPath, option, _viewContent as DiffContext);
if (_selectedCommits is { Count: 1 })
{
StartPoint = null;
EndPoint = null;
var option = new Models.DiffOption(_selectedCommits[0], _file);
ViewContent = new DiffContext(_repo.FullPath, option, _viewContent as DiffContext);
}
else if (_selectedCommits is { Count: 2 })
{
StartPoint = _selectedCommits[0];
EndPoint = _selectedCommits[1];
_changes = new Commands.CompareRevisions(_repo.FullPath, GetSHA(_selectedCommits[0]), GetSHA(_selectedCommits[1]), _file).Result();
if (_changes.Count == 0)
{
ViewContent = null;
return;
}
var option = new Models.DiffOption(GetSHA(_selectedCommits[0]), GetSHA(_selectedCommits[1]), _changes[0]);
ViewContent = new DiffContext(_repo.FullPath, option, _viewContent as DiffContext);
}
else
{
ViewContent = _selectedCommits.Count;
}
}

private string GetSHA(object obj)
{
return obj is Models.Commit commit ? commit.SHA : string.Empty;
}

[GeneratedRegex(@"^version https://git-lfs.github.com/spec/v\d+\r?\noid sha256:([0-9a-f]+)\r?\nsize (\d+)[\r\n]*$")]
Expand All @@ -197,8 +261,11 @@ private void SetViewContentAsDiff()
private readonly string _file = null;
private bool _isLoading = true;
private List<Models.Commit> _commits = null;
private Models.Commit _selectedCommit = null;
private List<Models.Commit> _selectedCommits = [];
private bool _isViewContent = false;
private object _viewContent = null;
private Models.Commit _startPoint = null;
private Models.Commit _endPoint = null;
private List<Models.Change> _changes = null;
}
}
71 changes: 67 additions & 4 deletions src/Views/FileHistories.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,30 @@
Icon="/App.ico"
Title="{DynamicResource Text.FileHistory}"
MinWidth="1280" MinHeight="720">
<v:ChromelessWindow.DataTemplates>
<DataTemplate DataType="m:Null">
<Border HorizontalAlignment="Center" VerticalAlignment="Center" Background="{DynamicResource Brush.Accent}" CornerRadius="4">
<TextBlock Text="{DynamicResource Text.Worktree}" Classes="primary" Margin="4,2" Foreground="#FFDDDDDD"/>
</Border>
</DataTemplate>

<DataTemplate DataType="m:Commit">
<Grid RowDefinitions="Auto,*">
<Grid Grid.Row="0" ColumnDefinitions="Auto,*,Auto,Auto,Auto">
<v:Avatar Width="16" Height="16" VerticalAlignment="Center" IsHitTestVisible="False" User="{Binding Author}"/>
<TextBlock Grid.Column="1" Classes="primary" Text="{Binding Author.Name}" Margin="8,0,0,0"/>
<Border Grid.Column="2" Background="{DynamicResource Brush.Accent}" CornerRadius="4" IsVisible="{Binding IsCurrentHead}">
<TextBlock Text="HEAD" Classes="primary" Margin="4,0" Foreground="#FFDDDDDD"/>
</Border>
<TextBlock Grid.Column="3" Classes="primary" Text="{Binding SHA, Converter={x:Static c:StringConverters.ToShortSHA}}" Foreground="DarkOrange" Margin="8,0,0,0" TextDecorations="Underline" Cursor="Hand" PointerPressed="OnPressCommitSHA" />
<TextBlock Grid.Column="4" Classes="primary" Text="{Binding CommitterTimeStr}" Foreground="{DynamicResource Brush.FG2}" Margin="8,0,0,0"/>
</Grid>

<TextBlock Grid.Row="1" Classes="primary" Text="{Binding Subject}" VerticalAlignment="Bottom"/>
</Grid>
</DataTemplate>
</v:ChromelessWindow.DataTemplates>

<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
Expand Down Expand Up @@ -56,8 +80,8 @@
Margin="8,4,4,8"
BorderBrush="{DynamicResource Brush.Border2}"
ItemsSource="{Binding Commits}"
SelectedItem="{Binding SelectedCommit, Mode=TwoWay}"
SelectionMode="Single"
SelectionMode="Multiple"
SelectionChanged="OnRowSelectionChanged"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
ScrollViewer.VerticalScrollBarVisibility="Auto">
<ListBox.Styles>
Expand Down Expand Up @@ -113,7 +137,7 @@
IsVisible="{Binding IsLoading}"/>

<Grid Grid.Column="2" RowDefinitions="Auto,*,Auto" IsVisible="{Binding !IsLoading}">
<StackPanel Grid.Row="0" Margin="0,8" Height="28" HorizontalAlignment="Center" Orientation="Horizontal">
<StackPanel Grid.Row="0" Margin="0,8" Height="28" HorizontalAlignment="Center" Orientation="Horizontal" IsVisible="{Binding SelectedCommits.Count, Converter={x:Static c:IntConverters.IsOne}}">
<RadioButton Classes="switch_button"
GroupName="SearchGroup"
IsChecked="{Binding !IsViewContent, Mode=OneWay}">
Expand All @@ -126,6 +150,29 @@
<TextBlock Margin="16,0" Text="{DynamicResource Text.FileHistory.FileContent}" FontWeight="Bold"/>
</RadioButton>
</StackPanel>

<!-- Compare Revision Info -->
<Grid Grid.Row="0" Margin="0,0,0,6" ColumnDefinitions="*,32,*,Auto" IsVisible="{Binding SelectedCommits.Count, Converter={x:Static c:IntConverters.IsTwo}}">
<!-- 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 Buttons -->
<Button Grid.Column="1" Classes="icon_button" Command="{Binding Swap}" HorizontalAlignment="Center" ToolTip.Tip="{DynamicResource Text.Diff.SwapCommits}">
<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" ToolTip.Tip="{DynamicResource Text.Diff.SaveAsPatch}">
<Path Width="16" Height="16" Data="{DynamicResource Icons.Diff}"/>
</Button>
</Grid>

<ContentControl Grid.Row="1" Margin="4,4,8,8" Content="{Binding ViewContent}">
<ContentControl.DataTemplates>
Expand Down Expand Up @@ -154,6 +201,21 @@
</Grid>
</Border>
</DataTemplate>

<DataTemplate DataType="x:Int32">
<StackPanel Orientation="Vertical" HorizontalAlignment="Center" VerticalAlignment="Center">
<Path Width="128" Height="128"
Data="{StaticResource Icons.Detail}"
HorizontalAlignment="Center"
Fill="{DynamicResource Brush.FG2}"/>

<TextBlock HorizontalAlignment="Center"
Margin="0,16"
FontSize="24" FontWeight="Bold"
Foreground="{DynamicResource Brush.FG2}"
Text="{Binding Converter={x:Static c:StringConverters.FormatByResourceKey}, ConverterParameter='Histories.Selected'}"/>
</StackPanel>
</DataTemplate>
</ContentControl.DataTemplates>
</ContentControl>

Expand All @@ -162,7 +224,8 @@
Margin="0,0,0,8"
HorizontalAlignment="Center"
Content="{DynamicResource Text.ChangeCM.CheckoutThisRevision}"
Click="OnResetToSelectedRevision"/>
Click="OnResetToSelectedRevision"
IsVisible="{Binding SelectedCommits.Count, Converter={x:Static c:IntConverters.IsOne}}"/>
</Grid>
</Grid>

Expand Down
44 changes: 44 additions & 0 deletions src/Views/FileHistories.axaml.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
using System.Collections.Generic;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Platform.Storage;
using SourceGit.Models;

namespace SourceGit.Views
{
Expand Down Expand Up @@ -38,5 +41,46 @@ private void OnCloseNotifyPanel(object _, PointerPressedEventArgs e)
NotifyDonePanel.IsVisible = false;
e.Handled = true;
}

private void OnRowSelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (DataContext is ViewModels.FileHistories vm && sender is ListBox { SelectedItems: IList<object> commits })
{
var selectedCommits = new List<Models.Commit>();
foreach (var commit in commits)
{
if (commit is Models.Commit modelCommit)
{
selectedCommits.Add(modelCommit);
}
}
vm.SelectedCommits = selectedCommits;
}

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.FileHistories;
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)
await vm.SaveAsPatch(storageFile.Path.LocalPath);
NotifyDonePanel.IsVisible = true;

e.Handled = true;
}
}
}