From 2df1830ffc4e6e8b6e274165c04a91520f74d493 Mon Sep 17 00:00:00 2001 From: leo Date: Tue, 26 Aug 2025 21:17:45 +0800 Subject: [PATCH] enhance: try load lfs image from local cache first before loading it by `git lfs smudge` command Signed-off-by: leo --- src/Commands/QueryGitCommonDir.cs | 27 ++++++++++++ src/ViewModels/FileHistories.cs | 57 +++++++++++++++---------- src/ViewModels/ImageSource.cs | 8 +++- src/ViewModels/RevisionLFSImage.cs | 2 +- src/Views/CommitDetail.axaml.cs | 2 +- src/Views/RevisionFileTreeView.axaml.cs | 2 +- src/Views/SubmodulesView.axaml.cs | 2 +- src/Views/WorkingCopy.axaml.cs | 4 +- 8 files changed, 74 insertions(+), 30 deletions(-) create mode 100644 src/Commands/QueryGitCommonDir.cs diff --git a/src/Commands/QueryGitCommonDir.cs b/src/Commands/QueryGitCommonDir.cs new file mode 100644 index 00000000..e71cf2b0 --- /dev/null +++ b/src/Commands/QueryGitCommonDir.cs @@ -0,0 +1,27 @@ +using System.IO; +using System.Threading.Tasks; + +namespace SourceGit.Commands +{ + public class QueryGitCommonDir : Command + { + public QueryGitCommonDir(string workDir) + { + WorkingDirectory = workDir; + Args = "rev-parse --git-common-dir"; + RaiseError = false; + } + + public async Task GetResultAsync() + { + var rs = await ReadToEndAsync().ConfigureAwait(false); + if (!rs.IsSuccess || string.IsNullOrEmpty(rs.StdOut)) + return string.Empty; + + var dir = rs.StdOut.Trim(); + if (Path.IsPathRooted(dir)) + return dir; + return Path.GetFullPath(Path.Combine(WorkingDirectory, dir)); + } + } +} diff --git a/src/ViewModels/FileHistories.cs b/src/ViewModels/FileHistories.cs index 011adda9..91cd5623 100644 --- a/src/ViewModels/FileHistories.cs +++ b/src/ViewModels/FileHistories.cs @@ -36,7 +36,7 @@ namespace SourceGit.ViewModels set => SetProperty(ref _viewContent, value); } - public FileHistoriesSingleRevision(Repository repo, string file, Models.Commit revision, bool prevIsDiffMode) + public FileHistoriesSingleRevision(string repo, string file, Models.Commit revision, bool prevIsDiffMode) { _repo = repo; _file = file; @@ -49,7 +49,7 @@ namespace SourceGit.ViewModels public async Task ResetToSelectedRevisionAsync() { - return await new Commands.Checkout(_repo.FullPath) + return await new Commands.Checkout(_repo) .FileWithRevisionAsync(_file, $"{_revision.SHA}") .ConfigureAwait(false); } @@ -59,13 +59,13 @@ namespace SourceGit.ViewModels if (_viewContent is not FileHistoriesRevisionFile { CanOpenWithDefaultEditor: true }) return; - var fullPath = Native.OS.GetAbsPath(_repo.FullPath, _file); + var fullPath = Native.OS.GetAbsPath(_repo, _file); var fileName = Path.GetFileNameWithoutExtension(fullPath) ?? ""; var fileExt = Path.GetExtension(fullPath) ?? ""; var tmpFile = Path.Combine(Path.GetTempPath(), $"{fileName}~{_revision.SHA.AsSpan(0, 10)}{fileExt}"); await Commands.SaveRevisionFile - .RunAsync(_repo.FullPath, _revision.SHA, _file, tmpFile) + .RunAsync(_repo, _revision.SHA, _file, tmpFile) .ConfigureAwait(false); Native.OS.OpenWithDefaultEditor(tmpFile); @@ -81,7 +81,7 @@ namespace SourceGit.ViewModels Task.Run(async () => { - var objs = await new Commands.QueryRevisionObjects(_repo.FullPath, _revision.SHA, _file) + var objs = await new Commands.QueryRevisionObjects(_repo, _revision.SHA, _file) .GetResultAsync() .ConfigureAwait(false); @@ -100,23 +100,23 @@ namespace SourceGit.ViewModels { if (obj.Type == Models.ObjectType.Blob) { - var isBinary = await new Commands.IsBinary(_repo.FullPath, _revision.SHA, _file).GetResultAsync().ConfigureAwait(false); + var isBinary = await new Commands.IsBinary(_repo, _revision.SHA, _file).GetResultAsync().ConfigureAwait(false); if (isBinary) { var imgDecoder = ImageSource.GetDecoder(_file); if (imgDecoder != Models.ImageDecoder.None) { - var source = await ImageSource.FromRevisionAsync(_repo.FullPath, _revision.SHA, _file, imgDecoder).ConfigureAwait(false); + var source = await ImageSource.FromRevisionAsync(_repo, _revision.SHA, _file, imgDecoder).ConfigureAwait(false); var image = new Models.RevisionImageFile(_file, source.Bitmap, source.Size); return new FileHistoriesRevisionFile(_file, image, true); } - var size = await new Commands.QueryFileSize(_repo.FullPath, _file, _revision.SHA).GetResultAsync().ConfigureAwait(false); + var size = await new Commands.QueryFileSize(_repo, _file, _revision.SHA).GetResultAsync().ConfigureAwait(false); var binaryFile = new Models.RevisionBinaryFile() { Size = size }; return new FileHistoriesRevisionFile(_file, binaryFile, true); } - var contentStream = await Commands.QueryFileContent.RunAsync(_repo.FullPath, _revision.SHA, _file).ConfigureAwait(false); + var contentStream = await Commands.QueryFileContent.RunAsync(_repo, _revision.SHA, _file).ConfigureAwait(false); var content = await new StreamReader(contentStream).ReadToEndAsync(); var lfs = Models.LFSObject.Parse(content); if (lfs != null) @@ -124,7 +124,7 @@ namespace SourceGit.ViewModels var imgDecoder = ImageSource.GetDecoder(_file); if (imgDecoder != Models.ImageDecoder.None) { - var combined = new RevisionLFSImage(_repo.FullPath, _file, lfs, imgDecoder); + var combined = new RevisionLFSImage(_repo, _file, lfs, imgDecoder); return new FileHistoriesRevisionFile(_file, combined, true); } @@ -138,7 +138,7 @@ namespace SourceGit.ViewModels if (obj.Type == Models.ObjectType.Commit) { - var submoduleRoot = Path.Combine(_repo.FullPath, _file); + var submoduleRoot = Path.Combine(_repo, _file); var commit = await new Commands.QuerySingleCommit(submoduleRoot, obj.SHA).GetResultAsync().ConfigureAwait(false); var message = commit != null ? await new Commands.QueryCommitFullMessage(submoduleRoot, obj.SHA).GetResultAsync().ConfigureAwait(false) : null; var module = new Models.RevisionSubmodule() @@ -156,10 +156,10 @@ namespace SourceGit.ViewModels private void SetViewContentAsDiff() { var option = new Models.DiffOption(_revision, _file); - ViewContent = new DiffContext(_repo.FullPath, option, _viewContent as DiffContext); + ViewContent = new DiffContext(_repo, option, _viewContent as DiffContext); } - private Repository _repo = null; + private string _repo = null; private string _file = null; private Models.Commit _revision = null; private bool _isDiffMode = false; @@ -186,7 +186,7 @@ namespace SourceGit.ViewModels set => SetProperty(ref _viewContent, value); } - public FileHistoriesCompareRevisions(Repository repo, string file, Models.Commit start, Models.Commit end) + public FileHistoriesCompareRevisions(string repo, string file, Models.Commit start, Models.Commit end) { _repo = repo; _file = file; @@ -204,7 +204,7 @@ namespace SourceGit.ViewModels public async Task SaveAsPatch(string saveTo) { return await Commands.SaveChangesAsPatch - .ProcessRevisionCompareChangesAsync(_repo.FullPath, _changes, _startPoint.SHA, _endPoint.SHA, saveTo) + .ProcessRevisionCompareChangesAsync(_repo, _changes, _startPoint.SHA, _endPoint.SHA, saveTo) .ConfigureAwait(false); } @@ -212,7 +212,7 @@ namespace SourceGit.ViewModels { Task.Run(async () => { - _changes = await new Commands.CompareRevisions(_repo.FullPath, _startPoint.SHA, _endPoint.SHA, _file).ReadAsync().ConfigureAwait(false); + _changes = await new Commands.CompareRevisions(_repo, _startPoint.SHA, _endPoint.SHA, _file).ReadAsync().ConfigureAwait(false); if (_changes.Count == 0) { Dispatcher.UIThread.Post(() => ViewContent = null); @@ -220,12 +220,12 @@ namespace SourceGit.ViewModels else { var option = new Models.DiffOption(_startPoint.SHA, _endPoint.SHA, _changes[0]); - Dispatcher.UIThread.Post(() => ViewContent = new DiffContext(_repo.FullPath, option, _viewContent)); + Dispatcher.UIThread.Post(() => ViewContent = new DiffContext(_repo, option, _viewContent)); } }); } - private Repository _repo = null; + private string _repo = null; private string _file = null; private Models.Commit _startPoint = null; private Models.Commit _endPoint = null; @@ -264,7 +264,7 @@ namespace SourceGit.ViewModels private set => SetProperty(ref _viewContent, value); } - public FileHistories(Repository repo, string file, string commit = null) + public FileHistories(string repo, string file, string commit = null) { if (!string.IsNullOrEmpty(commit)) Title = $"{file} @ {commit}"; @@ -282,7 +282,7 @@ namespace SourceGit.ViewModels .Append(" -- ") .Append(file.Quoted()); - var commits = await new Commands.QueryCommits(_repo.FullPath, argsBuilder.ToString(), false) + var commits = await new Commands.QueryCommits(_repo, argsBuilder.ToString(), false) .GetResultAsync() .ConfigureAwait(false); @@ -311,7 +311,18 @@ namespace SourceGit.ViewModels public void NavigateToCommit(Models.Commit commit) { - _repo.NavigateToCommit(commit.SHA); + var launcher = App.GetLauncher(); + if (launcher != null) + { + foreach (var page in launcher.Pages) + { + if (page.Data is Repository repo && repo.FullPath.Equals(_repo, StringComparison.Ordinal)) + { + repo.NavigateToCommit(commit.SHA); + break; + } + } + } } public string GetCommitFullMessage(Models.Commit commit) @@ -320,12 +331,12 @@ namespace SourceGit.ViewModels if (_fullCommitMessages.TryGetValue(sha, out var msg)) return msg; - msg = new Commands.QueryCommitFullMessage(_repo.FullPath, sha).GetResult(); + msg = new Commands.QueryCommitFullMessage(_repo, sha).GetResult(); _fullCommitMessages[sha] = msg; return msg; } - private readonly Repository _repo = null; + private readonly string _repo = null; private bool _isLoading = true; private bool _prevIsDiffMode = true; private List _commits = null; diff --git a/src/ViewModels/ImageSource.cs b/src/ViewModels/ImageSource.cs index 3095e4c6..84405df3 100644 --- a/src/ViewModels/ImageSource.cs +++ b/src/ViewModels/ImageSource.cs @@ -3,6 +3,7 @@ using System.Globalization; using System.IO; using System.Runtime.InteropServices; using System.Threading.Tasks; + using Avalonia; using Avalonia.Media.Imaging; using Avalonia.Platform; @@ -52,7 +53,12 @@ namespace SourceGit.ViewModels if (string.IsNullOrEmpty(lfs.Oid) || lfs.Size == 0) return new ImageSource(null, 0); - var stream = await Commands.QueryFileContent.FromLFSAsync(repo, lfs.Oid, lfs.Size).ConfigureAwait(false); + var commonDir = await new Commands.QueryGitCommonDir(repo).GetResultAsync().ConfigureAwait(false); + var localFile = Path.Combine(commonDir, "lfs", "objects", lfs.Oid.Substring(0, 2), lfs.Oid.Substring(2, 2), lfs.Oid); + if (File.Exists(localFile)) + return await FromFileAsync(localFile, decoder).ConfigureAwait(false); + + await using var stream = await Commands.QueryFileContent.FromLFSAsync(repo, lfs.Oid, lfs.Size).ConfigureAwait(false); return await Task.Run(() => LoadFromStream(stream, decoder)).ConfigureAwait(false); } diff --git a/src/ViewModels/RevisionLFSImage.cs b/src/ViewModels/RevisionLFSImage.cs index 2cdd8e66..2e51c69c 100644 --- a/src/ViewModels/RevisionLFSImage.cs +++ b/src/ViewModels/RevisionLFSImage.cs @@ -25,7 +25,7 @@ namespace SourceGit.ViewModels { var source = await ImageSource.FromLFSObjectAsync(repo, lfs, decoder).ConfigureAwait(false); var img = new Models.RevisionImageFile(file, source.Bitmap, source.Size); - Dispatcher.UIThread.Invoke(() => Image = img); + Dispatcher.UIThread.Post(() => Image = img); }); } diff --git a/src/Views/CommitDetail.axaml.cs b/src/Views/CommitDetail.axaml.cs index 3c800d9c..8f06cc4b 100644 --- a/src/Views/CommitDetail.axaml.cs +++ b/src/Views/CommitDetail.axaml.cs @@ -137,7 +137,7 @@ namespace SourceGit.Views history.Icon = App.CreateMenuIcon("Icons.Histories"); history.Click += (_, ev) => { - App.ShowWindow(new ViewModels.FileHistories(repo, change.Path, commit.SHA)); + App.ShowWindow(new ViewModels.FileHistories(repo.FullPath, change.Path, commit.SHA)); ev.Handled = true; }; diff --git a/src/Views/RevisionFileTreeView.axaml.cs b/src/Views/RevisionFileTreeView.axaml.cs index b1d345bb..be8b8f57 100644 --- a/src/Views/RevisionFileTreeView.axaml.cs +++ b/src/Views/RevisionFileTreeView.axaml.cs @@ -535,7 +535,7 @@ namespace SourceGit.Views history.Icon = App.CreateMenuIcon("Icons.Histories"); history.Click += (_, ev) => { - App.ShowWindow(new ViewModels.FileHistories(repo, file.Path, commit.SHA)); + App.ShowWindow(new ViewModels.FileHistories(repo.FullPath, file.Path, commit.SHA)); ev.Handled = true; }; diff --git a/src/Views/SubmodulesView.axaml.cs b/src/Views/SubmodulesView.axaml.cs index fdd88c08..6e4b7b7f 100644 --- a/src/Views/SubmodulesView.axaml.cs +++ b/src/Views/SubmodulesView.axaml.cs @@ -248,7 +248,7 @@ namespace SourceGit.Views histories.Icon = App.CreateMenuIcon("Icons.Histories"); histories.Click += (_, ev) => { - App.ShowWindow(new ViewModels.FileHistories(repo, submodule.Path)); + App.ShowWindow(new ViewModels.FileHistories(repo.FullPath, submodule.Path)); ev.Handled = true; }; diff --git a/src/Views/WorkingCopy.axaml.cs b/src/Views/WorkingCopy.axaml.cs index a69d86bf..1ab1dd8a 100644 --- a/src/Views/WorkingCopy.axaml.cs +++ b/src/Views/WorkingCopy.axaml.cs @@ -620,7 +620,7 @@ namespace SourceGit.Views history.Icon = App.CreateMenuIcon("Icons.Histories"); history.Click += (_, e) => { - App.ShowWindow(new ViewModels.FileHistories(repo, change.Path)); + App.ShowWindow(new ViewModels.FileHistories(repo.FullPath, change.Path)); e.Handled = true; }; @@ -1086,7 +1086,7 @@ namespace SourceGit.Views history.Icon = App.CreateMenuIcon("Icons.Histories"); history.Click += (_, e) => { - App.ShowWindow(new ViewModels.FileHistories(repo, change.Path)); + App.ShowWindow(new ViewModels.FileHistories(repo.FullPath, change.Path)); e.Handled = true; };