Skip to content

Add changed lines counter #1176

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 12 commits into
base: develop
Choose a base branch
from
Open
44 changes: 44 additions & 0 deletions src/Commands/QueryCommitChangedLines.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
using System.Text.RegularExpressions;

namespace SourceGit.Commands
{
public class QueryCommitChangedLines : Command
{
public QueryCommitChangedLines(string repo, string sha)
{
WorkingDirectory = repo;
Context = repo;
Args = $"show --shortstat --oneline {sha}";
_pattern = new Regex(@"(\d+) files? changed(?:, (\d+) insertions?\(\+\))?(?:, (\d+) deletions?\(-\))?");
}

public (int, int) Result()
{
_addedLines = 0;
_removedLines = 0;
Exec();
return (_addedLines, _removedLines);
}

protected override void OnReadline(string line)
{
var match = _pattern.Match(line);
if (match.Success)
{
if (match.Groups[2].Success)
{
_addedLines = int.Parse(match.Groups[2].Value);
}

if (match.Groups[3].Success)
{
_removedLines = int.Parse(match.Groups[3].Value);
}
}
}

private readonly Regex _pattern;
private int _addedLines;
private int _removedLines;
}
}
124 changes: 124 additions & 0 deletions src/Commands/QueryFileChangedLines.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
using System.IO;

namespace SourceGit.Commands
{
public class QueryFileChangedLines : Command
{
public QueryFileChangedLines(string repo, string revision1, string revision2, string filePath)
{
WorkingDirectory = repo;
Context = repo;
_repo = repo;
_filePath = filePath;

// Handle various diff scenarios
if (string.IsNullOrEmpty(revision1) && string.IsNullOrEmpty(revision2))
{
// Working copy changes (unstaged)
Args = $"diff --numstat -- \"{filePath}\"";
_checkNewWorkingDirFile = true;
}
else if (string.IsNullOrEmpty(revision1) && revision2 == "--staged")
{
// Staged changes
Args = $"diff --cached --numstat -- \"{filePath}\"";
_checkNewStagedFile = true;
}
else if (string.IsNullOrEmpty(revision1) || revision1 == "/dev/null")
{
// New file case - we'll count lines manually
_isNewFile = true;
_newRevision = revision2;
}
else
{
// Comparing two revisions
Args = $"diff --numstat {revision1} {revision2} -- \"{filePath}\"";
}
}

public (int, int) Result()
{
_addedLines = 0;
_removedLines = 0;

// Check for new files first
if (_isNewFile || _checkNewWorkingDirFile || _checkNewStagedFile)
{
int lineCount = 0;

if (_isNewFile && !string.IsNullOrEmpty(_newRevision))
{
var stream = QueryFileContent.Run(_repo, _newRevision, _filePath);
using (var reader = new StreamReader(stream))
{
string line;
while ((line = reader.ReadLine()) != null)
{
lineCount++;
}
}
}
else
{
var fullPath = Path.Combine(_repo, _filePath);
if (File.Exists(fullPath))
{
if (_checkNewWorkingDirFile || _checkNewStagedFile)
{
Exec();
if (_addedLines == 0 && _removedLines == 0)
{
var lines = File.ReadAllLines(fullPath);
lineCount = lines.Length;
}
else
{
return (_addedLines, _removedLines);
}
}
else
{
var lines = File.ReadAllLines(fullPath);
lineCount = lines.Length;
}
}
}

if (lineCount > 0)
{
return (lineCount, 0);
}
}

Exec();
return (_addedLines, _removedLines);
}

protected override void OnReadline(string line)
{
var parts = line.Split('\t');
if (parts.Length >= 2)
{
if (int.TryParse(parts[0], out int added))
{
_addedLines = added;
}

if (int.TryParse(parts[1], out int removed))
{
_removedLines = removed;
}
}
}

private readonly string _repo;
private readonly string _filePath;
private readonly bool _isNewFile = false;
private readonly string _newRevision = null;
private readonly bool _checkNewWorkingDirFile = false;
private readonly bool _checkNewStagedFile = false;
private int _addedLines;
private int _removedLines;
}
}
40 changes: 39 additions & 1 deletion src/Models/Commit.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;

using Avalonia;
using Avalonia.Media;
Expand All @@ -15,8 +17,15 @@ public enum CommitSearchMethod
ByFile,
}

public class Commit
public class Commit : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;

protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}

public static double OpacityForNotMerged
{
get;
Expand Down Expand Up @@ -48,6 +57,35 @@ public static double OpacityForNotMerged
public Thickness Margin { get; set; } = new Thickness(0);
public IBrush Brush => CommitGraph.Pens[Color].Brush;

private int _addedLines = 0;
private int _removedLines = 0;

public int AddedLines
{
get => _addedLines;
set
{
if (_addedLines != value)
{
_addedLines = value;
OnPropertyChanged();
}
}
}

public int RemovedLines
{
get => _removedLines;
set
{
if (_removedLines != value)
{
_removedLines = value;
OnPropertyChanged();
}
}
}

public void ParseDecorators(string data)
{
if (data.Length < 3)
Expand Down
1 change: 1 addition & 0 deletions src/Resources/Locales/en_US.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@
<x:String x:Key="Text.CommitDetail.Info.Parents" xml:space="preserve">PARENTS</x:String>
<x:String x:Key="Text.CommitDetail.Info.Refs" xml:space="preserve">REFS</x:String>
<x:String x:Key="Text.CommitDetail.Info.SHA" xml:space="preserve">SHA</x:String>
<x:String x:Key="Text.CommitDetail.Info.Stats" xml:space="preserve">STATS</x:String>
<x:String x:Key="Text.CommitDetail.Info.WebLinks" xml:space="preserve">Open in Browser</x:String>
<x:String x:Key="Text.CommitMessageTextBox.SubjectPlaceholder" xml:space="preserve">Enter commit subject</x:String>
<x:String x:Key="Text.CommitMessageTextBox.MessagePlaceholder" xml:space="preserve">Description</x:String>
Expand Down
45 changes: 44 additions & 1 deletion src/ViewModels/CommitDetail.cs
Original file line number Diff line number Diff line change
Expand Up @@ -548,7 +548,7 @@ public ContextMenu CreateRevisionFileContextMenu(Models.Object file)

menu.Items.Add(resetToThisRevision);
menu.Items.Add(resetToFirstParent);
menu.Items.Add(new MenuItem() { Header = "-" });
menu.Items.Add(new MenuItem { Header = "-" });

if (File.Exists(Path.Combine(fullPath)))
TryToAddContextMenuItemsForGitLFS(menu, file.Path);
Expand Down Expand Up @@ -590,6 +590,30 @@ private void Refresh()
if (_commit == null)
return;

var sha = _commit.SHA;

if (_lineCountCache.TryGetValue(sha, out var lineCount))
{
_commit.AddedLines = lineCount.added;
_commit.RemovedLines = lineCount.removed;
}
else
{
Task.Run(() =>
{
(var addedLines, var removedLines) = new Commands.QueryCommitChangedLines(_repo.FullPath, sha).Result();
_lineCountCache[sha] = (addedLines, removedLines);

Dispatcher.UIThread.Invoke(() => {
if (_commit != null && _commit.SHA == sha)
{
_commit.AddedLines = addedLines;
_commit.RemovedLines = removedLines;
}
});
});
}

if (_cancellationSource is { IsCancellationRequested: false })
_cancellationSource.Cancel();

Expand Down Expand Up @@ -654,6 +678,24 @@ private void Refresh()
});
}

public void PreloadLineCountData(List<string> commitSHAs)
{
if (commitSHAs == null || commitSHAs.Count == 0)
return;

Task.Run(() =>
{
foreach (var sha in commitSHAs)
{
if (!_lineCountCache.ContainsKey(sha))
{
var (addedLines, removedLines) = new Commands.QueryCommitChangedLines(_repo.FullPath, sha).Result();
_lineCountCache[sha] = (addedLines, removedLines);
}
}
});
}

private List<Models.Hyperlink> ParseLinksInMessage(string message)
{
var links = new List<Models.Hyperlink>();
Expand Down Expand Up @@ -878,6 +920,7 @@ private void CalcRevisionFileSearchSuggestion()
private CancellationTokenSource _cancellationSource = null;
private List<string> _revisionFiles = null;
private string _revisionFileSearchFilter = string.Empty;
private Dictionary<string, (int added, int removed)> _lineCountCache = new Dictionary<string, (int added, int removed)>();
private List<string> _revisionFileSearchSuggestion = null;
}
}
50 changes: 49 additions & 1 deletion src/ViewModels/DiffContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

namespace SourceGit.ViewModels
{
public class DiffContext : ObservableObject
public partial class DiffContext : ObservableObject
{
public string Title
{
Expand Down Expand Up @@ -51,6 +51,12 @@ public int UnifiedLines
private set => SetProperty(ref _unifiedLines, value);
}

[ObservableProperty]
private int _addedLines;

[ObservableProperty]
private int _removedLines;

public DiffContext(string repo, Models.DiffOption option, DiffContext previous = null)
{
_repo = repo;
Expand All @@ -71,6 +77,48 @@ public DiffContext(string repo, Models.DiffOption option, DiffContext previous =
else
_title = $"{_option.OrgPath} → {_option.Path}";

AddedLines = 0;
RemovedLines = 0;

Task.Run(() =>
{
string oldRevision = "";
string newRevision = "";
string filePath = option.Path;

if (option.Revisions.Count == 2)
{
oldRevision = option.Revisions[0];
newRevision = option.Revisions[1];

if (string.IsNullOrEmpty(oldRevision) || string.IsNullOrEmpty(newRevision))
{
var result = new Commands.QueryFileChangedLines(repo, oldRevision, newRevision, filePath).Result();

Dispatcher.UIThread.Invoke(() =>
{
AddedLines = result.Item1;
RemovedLines = result.Item2;
});
return;
}
}

if (option.Revisions.Count == 1 && option.Revisions[0] == "STAGE")
{
oldRevision = "HEAD";
newRevision = "--staged";
}

var lineChanges = new Commands.QueryFileChangedLines(repo, oldRevision, newRevision, filePath).Result();

Dispatcher.UIThread.Invoke(() =>
{
AddedLines = lineChanges.Item1;
RemovedLines = lineChanges.Item2;
});
});

LoadDiffContent();
}

Expand Down
Loading