diff --git a/src/Commands/QueryDiffLineStats.cs b/src/Commands/QueryDiffLineStats.cs new file mode 100644 index 000000000..0767bc801 --- /dev/null +++ b/src/Commands/QueryDiffLineStats.cs @@ -0,0 +1,64 @@ +using System; +using System.Text; +using System.Threading.Tasks; + +namespace SourceGit.Commands +{ + public class QueryDiffLineStats : Command + { + public QueryDiffLineStats(string repo, string start, string end, bool ignoreWhitespace, bool ignoreCRAtEOL) + : this(repo, ignoreWhitespace, ignoreCRAtEOL, $"{(string.IsNullOrEmpty(start) ? "-R" : start)} {end}") + { + } + + public QueryDiffLineStats(string repo, bool staged, bool ignoreWhitespace, bool ignoreCRAtEOL) + : this(repo, ignoreWhitespace, ignoreCRAtEOL, staged ? "--cached" : string.Empty) + { + } + + private QueryDiffLineStats(string repo, bool ignoreWhitespace, bool ignoreCRAtEOL, string suffix) + { + WorkingDirectory = repo; + Context = repo; + RaiseError = false; + + var builder = new StringBuilder(); + builder.Append("diff --no-color --no-ext-diff --numstat -z "); + if (ignoreCRAtEOL) + builder.Append("--ignore-cr-at-eol "); + if (ignoreWhitespace) + builder.Append("--ignore-space-change --ignore-blank-lines "); + builder.Append(suffix); + Args = builder.ToString().TrimEnd(); + } + + public async Task<(int added, int deleted)> GetResultAsync() + { + try + { + var rs = await ReadToEndAsync().ConfigureAwait(false); + if (string.IsNullOrWhiteSpace(rs.StdOut)) + return (0, 0); + + int added = 0, deleted = 0; + foreach (var token in rs.StdOut.Split('\0', StringSplitOptions.RemoveEmptyEntries)) + { + var parts = token.Split('\t', 3); + if (parts.Length < 2) + continue; + + added += ParseCount(parts[0]); + deleted += ParseCount(parts[1]); + } + + return (added, deleted); + } + catch + { + return (0, 0); + } + } + + private static int ParseCount(string value) => int.TryParse(value, out var parsed) ? parsed : 0; + } +} diff --git a/src/ViewModels/CommitDetail.cs b/src/ViewModels/CommitDetail.cs index 636aad665..698e58858 100644 --- a/src/ViewModels/CommitDetail.cs +++ b/src/ViewModels/CommitDetail.cs @@ -91,6 +91,18 @@ public List VisibleChanges set => SetProperty(ref _visibleChanges, value); } + public int TotalAddedLines + { + get => _totalAddedLines; + private set => SetProperty(ref _totalAddedLines, value); + } + + public int TotalDeletedLines + { + get => _totalDeletedLines; + private set => SetProperty(ref _totalDeletedLines, value); + } + public ChangeSelection ChangeSelection { get => _changeSelection; @@ -512,6 +524,16 @@ private void Refresh() .ReadAsync() .ConfigureAwait(false); + var pref = Preferences.Instance; + var stats = await new Commands.QueryDiffLineStats( + _repo.FullPath, + _commit.FirstParentToCompare, + _commit.SHA, + pref.IgnoreWhitespaceChangesInDiff, + pref.IgnoreCRAtEOLInDiff) + .GetResultAsync() + .ConfigureAwait(false); + var visible = changes; if (!string.IsNullOrWhiteSpace(_searchChangeFilter)) { @@ -529,6 +551,8 @@ private void Refresh() { Changes = changes; VisibleChanges = visible; + TotalAddedLines = stats.added; + TotalDeletedLines = stats.deleted; if (visible.Count == 0) ChangeSelection = new(null); @@ -736,6 +760,8 @@ private async Task SetViewingCommitAsync(Models.Object file) private List _changes = []; private List _visibleChanges = []; private ChangeSelection _changeSelection = new(null); + private int _totalAddedLines = 0; + private int _totalDeletedLines = 0; private string _searchChangeFilter = string.Empty; private DiffContext _diffContext = null; private string _viewRevisionFilePath = string.Empty; diff --git a/src/ViewModels/Compare.cs b/src/ViewModels/Compare.cs index e30a53445..b0fa5f6d6 100644 --- a/src/ViewModels/Compare.cs +++ b/src/ViewModels/Compare.cs @@ -62,6 +62,18 @@ public int TotalChanges private set => SetProperty(ref _totalChanges, value); } + public int TotalAddedLines + { + get => _totalAddedLines; + private set => SetProperty(ref _totalAddedLines, value); + } + + public int TotalDeletedLines + { + get => _totalDeletedLines; + private set => SetProperty(ref _totalDeletedLines, value); + } + public List VisibleChanges { get => _visibleChanges; @@ -317,6 +329,15 @@ private void UpdateChanges() .ReadAsync() .ConfigureAwait(false); + var pref = Preferences.Instance; + var stats = await new Commands.QueryDiffLineStats( + _repo.FullPath, + _based, + _to, + pref.IgnoreWhitespaceChangesInDiff, + pref.IgnoreCRAtEOLInDiff) + .GetResultAsync().ConfigureAwait(false); + var visible = _changes; if (!string.IsNullOrWhiteSpace(_searchFilter)) { @@ -331,6 +352,8 @@ private void UpdateChanges() Dispatcher.UIThread.Post(() => { TotalChanges = _changes.Count; + TotalAddedLines = stats.added; + TotalDeletedLines = stats.deleted; VisibleChanges = visible; IsLoadingChanges = false; @@ -396,6 +419,8 @@ private string GetSHA(object obj) private Models.Commit _baseHead = null; private Models.Commit _toHead = null; private int _totalChanges = 0; + private int _totalAddedLines = 0; + private int _totalDeletedLines = 0; private List _changes = null; private List _visibleChanges = null; private ChangeSelection _changeSelection = new(null); diff --git a/src/ViewModels/RevisionCompare.cs b/src/ViewModels/RevisionCompare.cs index 9f5e77dc5..1e3f982ca 100644 --- a/src/ViewModels/RevisionCompare.cs +++ b/src/ViewModels/RevisionCompare.cs @@ -59,6 +59,18 @@ public int TotalChanges private set => SetProperty(ref _totalChanges, value); } + public int TotalAddedLines + { + get => _totalAddedLines; + private set => SetProperty(ref _totalAddedLines, value); + } + + public int TotalDeletedLines + { + get => _totalDeletedLines; + private set => SetProperty(ref _totalDeletedLines, value); + } + public List VisibleChanges { get => _visibleChanges; @@ -345,10 +357,21 @@ private void Refresh() { Task.Run(async () => { - _changes = await new Commands.CompareRevisions(_repo.FullPath, GetSHA(_startPoint), GetSHA(_endPoint)) + var startSHA = GetSHA(_startPoint); + var endSHA = GetSHA(_endPoint); + _changes = await new Commands.CompareRevisions(_repo.FullPath, startSHA, endSHA) .ReadAsync() .ConfigureAwait(false); + var pref = Preferences.Instance; + var stats = await new Commands.QueryDiffLineStats( + _repo.FullPath, + startSHA, + endSHA, + pref.IgnoreWhitespaceChangesInDiff, + pref.IgnoreCRAtEOLInDiff) + .GetResultAsync().ConfigureAwait(false); + var visible = _changes; if (!string.IsNullOrWhiteSpace(_searchFilter)) { @@ -363,6 +386,8 @@ private void Refresh() Dispatcher.UIThread.Post(() => { TotalChanges = _changes.Count; + TotalAddedLines = stats.added; + TotalDeletedLines = stats.deleted; VisibleChanges = visible; IsLoading = false; @@ -389,6 +414,8 @@ private string GetDesc(object obj) private object _startPoint = null; private object _endPoint = null; private int _totalChanges = 0; + private int _totalAddedLines = 0; + private int _totalDeletedLines = 0; private List _changes = null; private List _visibleChanges = null; private ChangeSelection _changeSelection = new(null); diff --git a/src/ViewModels/WorkingCopy.cs b/src/ViewModels/WorkingCopy.cs index 9a33cd18c..6d3bdef23 100644 --- a/src/ViewModels/WorkingCopy.cs +++ b/src/ViewModels/WorkingCopy.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.IO; using System.Threading.Tasks; +using Avalonia.Threading; using CommunityToolkit.Mvvm.ComponentModel; namespace SourceGit.ViewModels @@ -187,6 +188,30 @@ public string StagedCountInfo get => string.IsNullOrEmpty(_filter) ? $"({_staged.Count})" : $"({_visibleStaged.Count}/{_staged.Count})"; } + public int UnstagedAddedLines + { + get => _unstagedAddedLines; + private set => SetProperty(ref _unstagedAddedLines, value); + } + + public int UnstagedDeletedLines + { + get => _unstagedDeletedLines; + private set => SetProperty(ref _unstagedDeletedLines, value); + } + + public int StagedAddedLines + { + get => _stagedAddedLines; + private set => SetProperty(ref _stagedAddedLines, value); + } + + public int StagedDeletedLines + { + get => _stagedDeletedLines; + private set => SetProperty(ref _stagedDeletedLines, value); + } + public ChangeSelection SelectedUnstaged { get => _selectedUnstaged; @@ -358,6 +383,29 @@ public void SetData(List changes) UpdateInProgressState(); UpdateDetail(); + + _ = Task.Run(async () => + { + var pref = Preferences.Instance; + var unstagedStats = await new Commands.QueryDiffLineStats( + _repo.FullPath, false, pref.IgnoreWhitespaceChangesInDiff, pref.IgnoreCRAtEOLInDiff) + .GetResultAsync().ConfigureAwait(false); + var stagedStats = await new Commands.QueryDiffLineStats( + _repo.FullPath, true, pref.IgnoreWhitespaceChangesInDiff, pref.IgnoreCRAtEOLInDiff) + .GetResultAsync().ConfigureAwait(false); + + Dispatcher.UIThread.Post(() => + { + // A newer SetData may have run while fetching stats; drop this stale update. + if (!ReferenceEquals(_cached, changes)) + return; + + UnstagedAddedLines = unstagedStats.added; + UnstagedDeletedLines = unstagedStats.deleted; + StagedAddedLines = stagedStats.added; + StagedDeletedLines = stagedStats.deleted; + }); + }); } public async Task StageChangesAsync(List changes, Models.Change next) @@ -874,6 +922,10 @@ private bool IsChanged(List old, List cur) private List _visibleStaged = []; private ChangeSelection _selectedUnstaged = new(null); private ChangeSelection _selectedStaged = new(null); + private int _unstagedAddedLines = 0; + private int _unstagedDeletedLines = 0; + private int _stagedAddedLines = 0; + private int _stagedDeletedLines = 0; private object _detailContext = null; private string _filter = string.Empty; private string _commitMessage = string.Empty; diff --git a/src/Views/CommitChanges.axaml b/src/Views/CommitChanges.axaml index 7c8bab40f..2097f450b 100644 --- a/src/Views/CommitChanges.axaml +++ b/src/Views/CommitChanges.axaml @@ -4,6 +4,7 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:vm="using:SourceGit.ViewModels" xmlns:v="using:SourceGit.Views" + xmlns:c="using:SourceGit.Converters" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="SourceGit.Views.CommitChanges" x:DataType="vm:CommitDetail"> @@ -65,12 +66,24 @@ - - - - + + + + + + + + diff --git a/src/Views/Compare.axaml b/src/Views/Compare.axaml index 6591a574d..10dcf1318 100644 --- a/src/Views/Compare.axaml +++ b/src/Views/Compare.axaml @@ -195,12 +195,24 @@ - - - - + + + + + + + + diff --git a/src/Views/RevisionCompare.axaml b/src/Views/RevisionCompare.axaml index e5e93710f..d57e12dae 100644 --- a/src/Views/RevisionCompare.axaml +++ b/src/Views/RevisionCompare.axaml @@ -125,12 +125,24 @@ - - - - + + + + + + + + diff --git a/src/Views/WorkingCopy.axaml b/src/Views/WorkingCopy.axaml index 94df3cd5a..80c4548ea 100644 --- a/src/Views/WorkingCopy.axaml +++ b/src/Views/WorkingCopy.axaml @@ -69,7 +69,21 @@ - + + + + +