mirror of
https://fastgit.cc/github.com/sourcegit-scm/sourcegit
synced 2026-04-21 21:30:37 +08:00
fix: support to switch blaming revision with renamed files (#2040)
Signed-off-by: leo <longshuang@msn.cn>
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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; } = [];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user