diff --git a/src/Commands/Diff.cs b/src/Commands/Diff.cs index fb008aeee..728ee13ff 100644 --- a/src/Commands/Diff.cs +++ b/src/Commands/Diff.cs @@ -38,6 +38,10 @@ public partial class Diff : Command private const int MAX_INLINE_CONTENT_LENGTH = 1024; private const int MAX_INLINE_CHUNKS_PER_LINE = 16; + public const long MAX_TEXT_DIFF_SIZE = 5 * 1024 * 1024; + + public bool IgnoreSizeLimit { get; set; } = false; + public Diff(string repo, Models.DiffOption opt, int numContextLines, bool ignoreWhitespace, bool ignoreCRAtEOL) { _result.TextDiff = new Models.TextDiff(); @@ -66,7 +70,22 @@ public Diff(string repo, Models.DiffOption opt, int numContextLines, bool ignore proc.Start(); using var ms = new MemoryStream(); - await proc.StandardOutput.BaseStream.CopyToAsync(ms, CancellationToken).ConfigureAwait(false); + var stdout = proc.StandardOutput.BaseStream; + var chunk = new byte[81920]; + int read; + while ((read = await stdout.ReadAsync(chunk, CancellationToken).ConfigureAwait(false)) > 0) + { + ms.Write(chunk, 0, read); + + // Stop reading huge diffs (generated files, minified bundles, data dumps, etc.). Only + // the diff headers will be parsed. + if (!IgnoreSizeLimit && ms.Length > MAX_TEXT_DIFF_SIZE) + { + _result.IsTooLarge = true; + proc.Kill(true); + break; + } + } if (ms.TryGetBuffer(out var buffer)) { @@ -82,7 +101,7 @@ public Diff(string repo, Models.DiffOption opt, int numContextLines, bool ignore } ParseLine(buffer[start..lineEnd]); - if (_result.IsBinary) + if (_result.IsBinary || (_result.IsTooLarge && _isInChunk)) break; start = lineEnd + 1; @@ -96,7 +115,7 @@ public Diff(string repo, Models.DiffOption opt, int numContextLines, bool ignore // Ignore exceptions. } - if (_isLFS || _result.IsBinary || _result.TextDiff.Lines.Count == 0) + if (_isLFS || _result.IsBinary || _result.IsTooLarge || _result.TextDiff.Lines.Count == 0) { _result.TextDiff = null; } diff --git a/src/Models/DiffResult.cs b/src/Models/DiffResult.cs index 1eba6a02d..33a0b4c79 100644 --- a/src/Models/DiffResult.cs +++ b/src/Models/DiffResult.cs @@ -92,6 +92,11 @@ public class EmptyFile public class NoOrEOLChange; + public class TooLargeDiff + { + public long MaxSizeInMB { get; set; } = 0; + } + public class SubmoduleDiff { public string FullPath { get; set; } = string.Empty; @@ -107,6 +112,7 @@ public class DiffResult { public bool IsBinary { get; set; } = false; public bool IsSubmoduleChange { get; set; } = false; + public bool IsTooLarge { get; set; } = false; public string OldHash { get; set; } = string.Empty; public string NewHash { get; set; } = string.Empty; public int OldMode { get; set; } = 0; diff --git a/src/Resources/Locales/en_US.axaml b/src/Resources/Locales/en_US.axaml index 603d223a9..d97d8551e 100644 --- a/src/Resources/Locales/en_US.axaml +++ b/src/Resources/Locales/en_US.axaml @@ -398,6 +398,9 @@ Swap Syntax Highlighting Line Word Wrap + DIFF TOO LARGE TO DISPLAY + SHOW ANYWAY + The diff output exceeds {0} MB. Loading it may make SourceGit unresponsive for a while. Open in Merge Tool Show All Lines Decrease Number of Visible Lines diff --git a/src/ViewModels/DiffContext.cs b/src/ViewModels/DiffContext.cs index f268e5ceb..d10b4cf9a 100644 --- a/src/ViewModels/DiffContext.cs +++ b/src/ViewModels/DiffContext.cs @@ -63,6 +63,7 @@ public DiffContext(string repo, Models.DiffOption option, DiffContext previous = _newMode = previous._newMode; _unifiedLines = previous._unifiedLines; _info = previous._info; + _ignoreSizeLimit = previous._ignoreSizeLimit && previous._option.ToString().Equals(_option.ToString(), StringComparison.Ordinal); } if (string.IsNullOrEmpty(_option.OrgPath) || _option.OrgPath == "/dev/null") @@ -85,6 +86,13 @@ public void DecrUnified() LoadContent(); } + public void ShowTooLargeDiff() + { + _ignoreSizeLimit = true; + _info = null; + LoadContent(); + } + public void OpenExternalMergeTool() { new Commands.DiffTool(_repo, _option).Open(); @@ -134,7 +142,7 @@ private void LoadContent() var ignoreWhitespace = pref.IgnoreWhitespaceChangesInDiff; var ignoreCRAtEOL = pref.IgnoreCRAtEOLInDiff; - var latest = await new Commands.Diff(_repo, _option, numLines, ignoreWhitespace, ignoreCRAtEOL) + var latest = await new Commands.Diff(_repo, _option, numLines, ignoreWhitespace, ignoreCRAtEOL) { IgnoreSizeLimit = _ignoreSizeLimit } .ReadAsync() .ConfigureAwait(false); @@ -149,6 +157,10 @@ private void LoadContent() { rs = textDiff; } + else if (latest.IsTooLarge) + { + rs = new Models.TooLargeDiff() { MaxSizeInMB = Commands.Diff.MAX_TEXT_DIFF_SIZE / 1024 / 1024 }; + } else if (latest.LFSDiff is { } lfs) { var imgDecoder = ImageSource.GetDecoder(_option.Path); @@ -388,6 +400,7 @@ public bool IsSame(Info other) private int _unifiedLines = 4; private bool _isTextDiff = false; private bool _isIgnoreWhitespaceVisible = true; + private bool _ignoreSizeLimit = false; private object _content = null; private Info _info = null; } diff --git a/src/Views/DiffView.axaml b/src/Views/DiffView.axaml index ba889d09b..9fdab41fe 100644 --- a/src/Views/DiffView.axaml +++ b/src/Views/DiffView.axaml @@ -411,6 +411,27 @@ + + + + + + +