fix: support to switch blaming revision with renamed files (#2040)

Signed-off-by: leo <longshuang@msn.cn>
This commit is contained in:
leo
2026-01-12 21:00:07 +08:00
parent 46c1e4b324
commit 96e2754bac
5 changed files with 92 additions and 58 deletions

View File

@@ -7,17 +7,15 @@ namespace SourceGit.Commands
{
public partial class Blame : Command
{
[GeneratedRegex(@"^\^?([0-9a-f]+)\s+.*\((.*)\s+(\d+)\s+[\-\+]?\d+\s+\d+\) (.*)")]
[GeneratedRegex(@"^\^?([0-9a-f]+)\s+(.*)\s+\((.*)\s+(\d+)\s+[\-\+]?\d+\s+\d+\) (.*)")]
private static partial Regex REG_FORMAT();
public Blame(string repo, string file, string revision)
{
WorkingDirectory = repo;
Context = repo;
Args = $"blame -t {revision} -- {file.Quoted()}";
Args = $"blame -f -t {revision} -- {file.Quoted()}";
RaiseError = false;
_result.File = file;
}
public async Task<Models.BlameData> ReadAsync()
@@ -61,19 +59,20 @@ namespace SourceGit.Commands
if (!match.Success)
return;
_content.AppendLine(match.Groups[4].Value);
_content.AppendLine(match.Groups[5].Value);
var commit = match.Groups[1].Value;
var author = match.Groups[2].Value;
var timestamp = int.Parse(match.Groups[3].Value);
var when = DateTime.UnixEpoch.AddSeconds(timestamp).ToLocalTime().ToString(_dateFormat);
var file = match.Groups[2].Value.Trim();
var author = match.Groups[3].Value;
var timestamp = ulong.Parse(match.Groups[4].Value);
var info = new Models.BlameLineInfo()
{
IsFirstInGroup = commit != _lastSHA,
CommitSHA = commit,
File = file,
Author = author,
Time = when,
Timestamp = timestamp,
};
_result.LineInfos.Add(info);
@@ -88,7 +87,6 @@ namespace SourceGit.Commands
private readonly Models.BlameData _result = new Models.BlameData();
private readonly StringBuilder _content = new StringBuilder();
private readonly string _dateFormat = Models.DateTimeFormat.Active.DateOnly;
private string _lastSHA = string.Empty;
private bool _needUnifyCommitSHA = false;
private int _minSHALen = 64;

View File

@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
namespace SourceGit.Models
{
@@ -6,15 +7,16 @@ namespace SourceGit.Models
{
public bool IsFirstInGroup { get; set; } = false;
public string CommitSHA { get; set; } = string.Empty;
public string File { get; set; } = string.Empty;
public string Author { get; set; } = string.Empty;
public string Time { get; set; } = string.Empty;
public ulong Timestamp { get; set; } = 0;
public string Time => DateTime.UnixEpoch.AddSeconds(Timestamp).ToLocalTime().ToString(DateTimeFormat.Active.DateOnly);
}
public class BlameData
{
public string File { get; set; } = string.Empty;
public List<BlameLineInfo> LineInfos { get; set; } = new List<BlameLineInfo>();
public string Content { get; set; } = string.Empty;
public bool IsBinary { get; set; } = false;
public string Content { get; set; } = string.Empty;
public List<BlameLineInfo> LineInfos { get; set; } = [];
}
}

View File

@@ -10,9 +10,10 @@ namespace SourceGit.ViewModels
{
public class Blame : ObservableObject
{
public string FilePath
public string File
{
get;
get => _file;
private set => SetProperty(ref _file, value);
}
public Models.Commit Revision
@@ -55,14 +56,9 @@ namespace SourceGit.ViewModels
public Blame(string repo, string file, Models.Commit commit)
{
var sha = commit.SHA.Substring(0, 10);
FilePath = file;
Revision = commit;
PrevRevision = null;
_repo = repo;
_navigationHistory.Add(sha);
SetBlameData(sha);
_navigationHistory.Add(new RevisionInfo(file, sha));
SetBlameData(_navigationHistory[0]);
}
public string GetCommitMessage(string sha)
@@ -83,7 +79,7 @@ namespace SourceGit.ViewModels
_navigationActiveIndex--;
OnPropertyChanged(nameof(CanBack));
OnPropertyChanged(nameof(CanForward));
NavigateToCommit(_navigationHistory[_navigationActiveIndex], true);
NavigateToCommit(_navigationHistory[_navigationActiveIndex]);
}
public void Forward()
@@ -94,18 +90,16 @@ namespace SourceGit.ViewModels
_navigationActiveIndex++;
OnPropertyChanged(nameof(CanBack));
OnPropertyChanged(nameof(CanForward));
NavigateToCommit(_navigationHistory[_navigationActiveIndex], true);
NavigateToCommit(_navigationHistory[_navigationActiveIndex]);
}
public void GotoPrevRevision()
{
if (_prevRevision == null)
return;
NavigateToCommit(_prevRevision.SHA, false);
if (_prevRevision != null)
NavigateToCommit(_file, _prevRevision.SHA.Substring(0, 10));
}
public void NavigateToCommit(string commitSHA, bool isBackOrForward)
public void NavigateToCommit(string file, string sha)
{
if (App.GetLauncher() is { Pages: { } pages })
{
@@ -113,31 +107,46 @@ namespace SourceGit.ViewModels
{
if (page.Data is Repository repo && repo.FullPath.Equals(_repo))
{
repo.NavigateToCommit(commitSHA);
repo.NavigateToCommit(sha);
break;
}
}
}
if (Revision.SHA.StartsWith(commitSHA, StringComparison.Ordinal))
if (Revision.SHA.StartsWith(sha, StringComparison.Ordinal))
return;
if (!isBackOrForward)
{
var count = _navigationHistory.Count;
if (_navigationActiveIndex < count - 1)
_navigationHistory.RemoveRange(_navigationActiveIndex + 1, count - _navigationActiveIndex - 1);
var count = _navigationHistory.Count;
if (_navigationActiveIndex < count - 1)
_navigationHistory.RemoveRange(_navigationActiveIndex + 1, count - _navigationActiveIndex - 1);
_navigationHistory.Add(commitSHA);
_navigationActiveIndex++;
OnPropertyChanged(nameof(CanBack));
OnPropertyChanged(nameof(CanForward));
}
SetBlameData(commitSHA);
var rev = new RevisionInfo(file, sha);
_navigationHistory.Add(rev);
_navigationActiveIndex++;
OnPropertyChanged(nameof(CanBack));
OnPropertyChanged(nameof(CanForward));
SetBlameData(rev);
}
private void SetBlameData(string commitSHA)
private void NavigateToCommit(RevisionInfo rev)
{
if (App.GetLauncher() is { Pages: { } pages })
{
foreach (var page in pages)
{
if (page.Data is Repository repo && repo.FullPath.Equals(_repo))
{
repo.NavigateToCommit(rev.SHA);
break;
}
}
}
if (!Revision.SHA.StartsWith(rev.SHA, StringComparison.Ordinal))
SetBlameData(rev);
}
private void SetBlameData(RevisionInfo rev)
{
if (_cancellationSource is { IsCancellationRequested: false })
_cancellationSource.Cancel();
@@ -145,14 +154,16 @@ namespace SourceGit.ViewModels
_cancellationSource = new CancellationTokenSource();
var token = _cancellationSource.Token;
File = rev.File;
Task.Run(async () =>
{
var argsBuilder = new StringBuilder();
argsBuilder
.Append("--date-order -n 2 ")
.Append(commitSHA ?? string.Empty)
.Append(rev.SHA)
.Append(" -- ")
.Append(FilePath.Quoted());
.Append(rev.File.Quoted());
var commits = await new Commands.QueryCommits(_repo, argsBuilder.ToString(), false)
.GetResultAsync()
@@ -163,14 +174,14 @@ namespace SourceGit.ViewModels
if (!token.IsCancellationRequested)
{
Revision = commits.Count > 0 ? commits[0] : null;
PrevRevision = commits.Count == 2 ? commits[1] : null;
PrevRevision = commits.Count > 1 ? commits[1] : null;
}
});
});
Task.Run(async () =>
{
var result = await new Commands.Blame(_repo, FilePath, commitSHA)
var result = await new Commands.Blame(_repo, rev.File, rev.SHA)
.ReadAsync()
.ConfigureAwait(false);
@@ -182,12 +193,25 @@ namespace SourceGit.ViewModels
}, token);
}
private class RevisionInfo
{
public string File { get; set; } = string.Empty;
public string SHA { get; set; } = string.Empty;
public RevisionInfo(string file, string sha)
{
File = file;
SHA = sha;
}
}
private string _repo;
private string _file;
private Models.Commit _revision;
private Models.Commit _prevRevision;
private CancellationTokenSource _cancellationSource = null;
private int _navigationActiveIndex = 0;
private List<string> _navigationHistory = [];
private List<RevisionInfo> _navigationHistory = [];
private Models.BlameData _data = null;
private Dictionary<string, string> _commitMessages = new();
}

View File

@@ -46,7 +46,7 @@
<TextBlock Grid.Column="1"
Margin="4,0,0,0"
VerticalAlignment="Center"
Text="{Binding FilePath, Mode=OneWay}"/>
Text="{Binding File, Mode=OneWay}"/>
<Button Grid.Column="2"
Classes="icon_button"
@@ -101,6 +101,7 @@
FontFamily="{DynamicResource Fonts.Monospace}"
FontSize="{Binding Source={x:Static vm:Preferences.Instance}, Path=EditorFontSize}"
TabWidth="{Binding Source={x:Static vm:Preferences.Instance}, Path=EditorTabWidth}"
File="{Binding File, Mode=OneWay}"
BlameData="{Binding Data}"
IsVisible="{Binding IsBinary, Converter={x:Static BoolConverters.Not}}">
<ToolTip.IsOpen>

View File

@@ -223,7 +223,7 @@ namespace SourceGit.Views
if (rect.Contains(pos))
{
if (DataContext is ViewModels.Blame blame)
blame.NavigateToCommit(info.CommitSHA, false);
blame.NavigateToCommit(info.File, info.CommitSHA);
e.Handled = true;
break;
@@ -256,6 +256,15 @@ namespace SourceGit.Views
private readonly BlameTextEditor _editor = null;
}
public static readonly StyledProperty<string> FileProperty =
AvaloniaProperty.Register<BlameTextEditor, string>(nameof(File));
public string File
{
get => GetValue(FileProperty);
set => SetValue(FileProperty, value);
}
public static readonly StyledProperty<Models.BlameData> BlameDataProperty =
AvaloniaProperty.Register<BlameTextEditor, Models.BlameData>(nameof(BlameData));
@@ -350,17 +359,17 @@ namespace SourceGit.Views
{
base.OnPropertyChanged(change);
if (change.Property == FileProperty)
{
if (File is { Length: > 0 })
Models.TextMateHelper.SetGrammarByFileName(_textMate, File);
}
if (change.Property == BlameDataProperty)
{
if (BlameData is { IsBinary: false } blame)
{
Models.TextMateHelper.SetGrammarByFileName(_textMate, blame.File);
Text = blame.Content;
}
else
{
Text = string.Empty;
}
}
else if (change.Property == TabWidthProperty)
{