enhance: do not create temp string for large output of some git command

Signed-off-by: leo <longshuang@msn.cn>
This commit is contained in:
leo
2025-09-01 12:38:50 +08:00
parent 632e394bcd
commit 9be50eb892
8 changed files with 323 additions and 304 deletions

View File

@@ -1,5 +1,5 @@
using System;
using System.Collections.Generic;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
@@ -33,56 +33,62 @@ namespace SourceGit.Commands
public async Task<List<Models.Change>> ReadAsync()
{
var changes = new List<Models.Change>();
var rs = await ReadToEndAsync().ConfigureAwait(false);
if (!rs.IsSuccess)
return changes;
var lines = rs.StdOut.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries);
foreach (var line in lines)
ParseLine(changes, line);
changes.Sort((l, r) => Models.NumericSort.Compare(l.Path, r.Path));
return changes;
}
private void ParseLine(List<Models.Change> outs, string line)
{
var match = REG_FORMAT().Match(line);
if (!match.Success)
try
{
match = REG_RENAME_FORMAT().Match(line);
if (match.Success)
using var proc = new Process();
proc.StartInfo = CreateGitStartInfo(true);
proc.Start();
while (await proc.StandardOutput.ReadLineAsync() is { } line)
{
var renamed = new Models.Change() { Path = match.Groups[1].Value };
renamed.Set(Models.ChangeState.Renamed);
outs.Add(renamed);
var match = REG_FORMAT().Match(line);
if (!match.Success)
{
match = REG_RENAME_FORMAT().Match(line);
if (match.Success)
{
var renamed = new Models.Change() { Path = match.Groups[1].Value };
renamed.Set(Models.ChangeState.Renamed);
changes.Add(renamed);
}
continue;
}
var change = new Models.Change() { Path = match.Groups[2].Value };
var status = match.Groups[1].Value;
switch (status[0])
{
case 'M':
change.Set(Models.ChangeState.Modified);
changes.Add(change);
break;
case 'A':
change.Set(Models.ChangeState.Added);
changes.Add(change);
break;
case 'D':
change.Set(Models.ChangeState.Deleted);
changes.Add(change);
break;
case 'C':
change.Set(Models.ChangeState.Copied);
changes.Add(change);
break;
}
}
return;
await proc.WaitForExitAsync().ConfigureAwait(false);
changes.Sort((l, r) => Models.NumericSort.Compare(l.Path, r.Path));
}
var change = new Models.Change() { Path = match.Groups[2].Value };
var status = match.Groups[1].Value;
switch (status[0])
catch
{
case 'M':
change.Set(Models.ChangeState.Modified);
outs.Add(change);
break;
case 'A':
change.Set(Models.ChangeState.Added);
outs.Add(change);
break;
case 'D':
change.Set(Models.ChangeState.Deleted);
outs.Add(change);
break;
case 'C':
change.Set(Models.ChangeState.Copied);
outs.Add(change);
break;
//ignore changes;
}
return changes;
}
}
}

View File

@@ -1,6 +1,6 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Diagnostics;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
@@ -35,10 +35,21 @@ namespace SourceGit.Commands
public async Task<Models.DiffResult> ReadAsync()
{
var rs = await ReadToEndAsync().ConfigureAwait(false);
var sr = new StringReader(rs.StdOut);
while (sr.ReadLine() is { } line)
ParseLine(line);
try
{
using var proc = new Process();
proc.StartInfo = CreateGitStartInfo(true);
proc.Start();
while (await proc.StandardOutput.ReadLineAsync() is { } line)
ParseLine(line);
await proc.WaitForExitAsync().ConfigureAwait(false);
}
catch
{
// Ignore exceptions.
}
if (_result.IsBinary || _result.IsLFS || _result.TextDiff.Lines.Count == 0)
{

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
using System.Threading.Tasks;
@@ -11,7 +12,7 @@ namespace SourceGit.Commands
{
WorkingDirectory = repo;
Context = repo;
Args = $"log --no-show-signature --decorate=full --format=%H%n%P%n%D%n%aN±%aE%n%at%n%cN±%cE%n%ct%n%s {limits}";
Args = $"log --no-show-signature --decorate=full --format=%H%x00%P%x00%D%x00%aN±%aE%x00%at%x00%cN±%cE%x00%ct%x00%s {limits}";
_findFirstMerged = needFindHead;
}
@@ -50,80 +51,55 @@ namespace SourceGit.Commands
WorkingDirectory = repo;
Context = repo;
Args = $"log -1000 --date-order --no-show-signature --decorate=full --format=%H%n%P%n%D%n%aN±%aE%n%at%n%cN±%cE%n%ct%n%s {search}";
Args = $"log -1000 --date-order --no-show-signature --decorate=full --format=%H%x00%P%x00%D%x00%aN±%aE%x00%at%x00%cN±%cE%x00%ct%x00%s {search}";
_findFirstMerged = false;
}
public async Task<List<Models.Commit>> GetResultAsync()
{
var rs = await ReadToEndAsync().ConfigureAwait(false);
if (!rs.IsSuccess)
return _commits;
var nextPartIdx = 0;
var start = 0;
var end = rs.StdOut.IndexOf('\n', start);
while (end > 0)
var commits = new List<Models.Commit>();
try
{
var line = rs.StdOut.Substring(start, end - start);
switch (nextPartIdx)
using var proc = new Process();
proc.StartInfo = CreateGitStartInfo(true);
proc.Start();
while (await proc.StandardOutput.ReadLineAsync() is { } line)
{
case 0:
_current = new Models.Commit() { SHA = line };
_commits.Add(_current);
break;
case 1:
ParseParent(line);
break;
case 2:
_current.ParseDecorators(line);
if (_current.IsMerged && !_isHeadFound)
_isHeadFound = true;
break;
case 3:
_current.Author = Models.User.FindOrAdd(line);
break;
case 4:
_current.AuthorTime = ulong.Parse(line);
break;
case 5:
_current.Committer = Models.User.FindOrAdd(line);
break;
case 6:
_current.CommitterTime = ulong.Parse(line);
break;
case 7:
_current.Subject = line;
nextPartIdx = -1;
break;
var parts = line.Split('\0');
if (parts.Length != 8)
continue;
var commit = new Models.Commit() { SHA = parts[0] };
commit.ParseParents(parts[1]);
commit.ParseDecorators(parts[2]);
commit.Author = Models.User.FindOrAdd(parts[3]);
commit.AuthorTime = ulong.Parse(parts[4]);
commit.Committer = Models.User.FindOrAdd(parts[5]);
commit.CommitterTime = ulong.Parse(parts[6]);
commit.Subject = parts[7];
commits.Add(commit);
if (commit.IsMerged && !_isHeadFound)
_isHeadFound = true;
}
nextPartIdx++;
await proc.WaitForExitAsync().ConfigureAwait(false);
start = end + 1;
end = rs.StdOut.IndexOf('\n', start);
if (_findFirstMerged && !_isHeadFound && commits.Count > 0)
await MarkFirstMergedAsync(commits).ConfigureAwait(false);
}
catch (Exception e)
{
App.RaiseException(Context, $"Failed to query commits. Reason: {e.Message}");
}
if (start < rs.StdOut.Length)
_current.Subject = rs.StdOut.Substring(start);
if (_findFirstMerged && !_isHeadFound && _commits.Count > 0)
await MarkFirstMergedAsync().ConfigureAwait(false);
return _commits;
return commits;
}
private void ParseParent(string data)
private async Task MarkFirstMergedAsync(List<Models.Commit> commits)
{
if (data.Length < 8)
return;
_current.Parents.AddRange(data.Split(' ', StringSplitOptions.RemoveEmptyEntries));
}
private async Task MarkFirstMergedAsync()
{
Args = $"log --since={_commits[^1].CommitterTimeStr.Quoted()} --format=\"%H\"";
Args = $"log --since={commits[^1].CommitterTimeStr.Quoted()} --format=\"%H\"";
var rs = await ReadToEndAsync().ConfigureAwait(false);
var shas = rs.StdOut.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries);
@@ -132,7 +108,7 @@ namespace SourceGit.Commands
var set = new HashSet<string>(shas);
foreach (var c in _commits)
foreach (var c in commits)
{
if (set.Contains(c.SHA))
{
@@ -142,8 +118,6 @@ namespace SourceGit.Commands
}
}
private List<Models.Commit> _commits = new List<Models.Commit>();
private Models.Commit _current = null;
private bool _findFirstMerged = false;
private bool _isHeadFound = false;
}

View File

@@ -17,9 +17,15 @@ namespace SourceGit.Commands
public async Task<List<Models.InteractiveCommit>> GetResultAsync()
{
var commits = new List<Models.InteractiveCommit>();
var rs = await ReadToEndAsync().ConfigureAwait(false);
if (!rs.IsSuccess)
return _commits;
{
App.RaiseException(Context, $"Failed to query commits for interactive-rebase. Reason: {rs.StdErr}");
return commits;
}
Models.InteractiveCommit current = null;
var nextPartIdx = 0;
var start = 0;
@@ -30,38 +36,38 @@ namespace SourceGit.Commands
switch (nextPartIdx)
{
case 0:
_current = new Models.InteractiveCommit();
_current.Commit.SHA = line;
_commits.Add(_current);
current = new Models.InteractiveCommit();
current.Commit.SHA = line;
commits.Add(current);
break;
case 1:
ParseParent(line);
current.Commit.ParseParents(line);
break;
case 2:
_current.Commit.ParseDecorators(line);
current.Commit.ParseDecorators(line);
break;
case 3:
_current.Commit.Author = Models.User.FindOrAdd(line);
current.Commit.Author = Models.User.FindOrAdd(line);
break;
case 4:
_current.Commit.AuthorTime = ulong.Parse(line);
current.Commit.AuthorTime = ulong.Parse(line);
break;
case 5:
_current.Commit.Committer = Models.User.FindOrAdd(line);
current.Commit.Committer = Models.User.FindOrAdd(line);
break;
case 6:
_current.Commit.CommitterTime = ulong.Parse(line);
current.Commit.CommitterTime = ulong.Parse(line);
break;
default:
var boundary = rs.StdOut.IndexOf(_boundary, end + 1, StringComparison.Ordinal);
if (boundary > end)
{
_current.Message = rs.StdOut.Substring(start, boundary - start - 1);
current.Message = rs.StdOut.Substring(start, boundary - start - 1);
end = boundary + _boundary.Length;
}
else
{
_current.Message = rs.StdOut.Substring(start);
current.Message = rs.StdOut.Substring(start);
end = rs.StdOut.Length - 2;
}
@@ -78,19 +84,9 @@ namespace SourceGit.Commands
end = rs.StdOut.IndexOf('\n', start);
}
return _commits;
return commits;
}
private void ParseParent(string data)
{
if (data.Length < 8)
return;
_current.Commit.Parents.AddRange(data.Split(' ', StringSplitOptions.RemoveEmptyEntries));
}
private List<Models.InteractiveCommit> _commits = [];
private Models.InteractiveCommit _current = null;
private readonly string _boundary;
}
}

View File

@@ -1,5 +1,5 @@
using System;
using System.Collections.Generic;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
@@ -21,141 +21,145 @@ namespace SourceGit.Commands
public async Task<List<Models.Change>> GetResultAsync()
{
var outs = new List<Models.Change>();
var rs = await ReadToEndAsync().ConfigureAwait(false);
if (!rs.IsSuccess)
try
{
App.RaiseException(Context, rs.StdErr);
return outs;
}
using var proc = new Process();
proc.StartInfo = CreateGitStartInfo(true);
proc.Start();
var lines = rs.StdOut.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries);
foreach (var line in lines)
{
var match = REG_FORMAT().Match(line);
if (!match.Success)
continue;
var change = new Models.Change() { Path = match.Groups[2].Value };
var status = match.Groups[1].Value;
switch (status)
while (await proc.StandardOutput.ReadLineAsync() is { } line)
{
case " M":
change.Set(Models.ChangeState.None, Models.ChangeState.Modified);
break;
case " T":
change.Set(Models.ChangeState.None, Models.ChangeState.TypeChanged);
break;
case " A":
change.Set(Models.ChangeState.None, Models.ChangeState.Added);
break;
case " D":
change.Set(Models.ChangeState.None, Models.ChangeState.Deleted);
break;
case " R":
change.Set(Models.ChangeState.None, Models.ChangeState.Renamed);
break;
case " C":
change.Set(Models.ChangeState.None, Models.ChangeState.Copied);
break;
case "M":
change.Set(Models.ChangeState.Modified);
break;
case "MM":
change.Set(Models.ChangeState.Modified, Models.ChangeState.Modified);
break;
case "MT":
change.Set(Models.ChangeState.Modified, Models.ChangeState.TypeChanged);
break;
case "MD":
change.Set(Models.ChangeState.Modified, Models.ChangeState.Deleted);
break;
case "T":
change.Set(Models.ChangeState.TypeChanged);
break;
case "TM":
change.Set(Models.ChangeState.TypeChanged, Models.ChangeState.Modified);
break;
case "TT":
change.Set(Models.ChangeState.TypeChanged, Models.ChangeState.TypeChanged);
break;
case "TD":
change.Set(Models.ChangeState.TypeChanged, Models.ChangeState.Deleted);
break;
case "A":
change.Set(Models.ChangeState.Added);
break;
case "AM":
change.Set(Models.ChangeState.Added, Models.ChangeState.Modified);
break;
case "AT":
change.Set(Models.ChangeState.Added, Models.ChangeState.TypeChanged);
break;
case "AD":
change.Set(Models.ChangeState.Added, Models.ChangeState.Deleted);
break;
case "D":
change.Set(Models.ChangeState.Deleted);
break;
case "R":
change.Set(Models.ChangeState.Renamed);
break;
case "RM":
change.Set(Models.ChangeState.Renamed, Models.ChangeState.Modified);
break;
case "RT":
change.Set(Models.ChangeState.Renamed, Models.ChangeState.TypeChanged);
break;
case "RD":
change.Set(Models.ChangeState.Renamed, Models.ChangeState.Deleted);
break;
case "C":
change.Set(Models.ChangeState.Copied);
break;
case "CM":
change.Set(Models.ChangeState.Copied, Models.ChangeState.Modified);
break;
case "CT":
change.Set(Models.ChangeState.Copied, Models.ChangeState.TypeChanged);
break;
case "CD":
change.Set(Models.ChangeState.Copied, Models.ChangeState.Deleted);
break;
case "DD":
change.ConflictReason = Models.ConflictReason.BothDeleted;
change.Set(Models.ChangeState.None, Models.ChangeState.Conflicted);
break;
case "AU":
change.ConflictReason = Models.ConflictReason.AddedByUs;
change.Set(Models.ChangeState.None, Models.ChangeState.Conflicted);
break;
case "UD":
change.ConflictReason = Models.ConflictReason.DeletedByThem;
change.Set(Models.ChangeState.None, Models.ChangeState.Conflicted);
break;
case "UA":
change.ConflictReason = Models.ConflictReason.AddedByThem;
change.Set(Models.ChangeState.None, Models.ChangeState.Conflicted);
break;
case "DU":
change.ConflictReason = Models.ConflictReason.DeletedByUs;
change.Set(Models.ChangeState.None, Models.ChangeState.Conflicted);
break;
case "AA":
change.ConflictReason = Models.ConflictReason.BothAdded;
change.Set(Models.ChangeState.None, Models.ChangeState.Conflicted);
break;
case "UU":
change.ConflictReason = Models.ConflictReason.BothModified;
change.Set(Models.ChangeState.None, Models.ChangeState.Conflicted);
break;
case "??":
change.Set(Models.ChangeState.None, Models.ChangeState.Untracked);
break;
}
var match = REG_FORMAT().Match(line);
if (!match.Success)
continue;
if (change.Index != Models.ChangeState.None || change.WorkTree != Models.ChangeState.None)
outs.Add(change);
var change = new Models.Change() { Path = match.Groups[2].Value };
var status = match.Groups[1].Value;
switch (status)
{
case " M":
change.Set(Models.ChangeState.None, Models.ChangeState.Modified);
break;
case " T":
change.Set(Models.ChangeState.None, Models.ChangeState.TypeChanged);
break;
case " A":
change.Set(Models.ChangeState.None, Models.ChangeState.Added);
break;
case " D":
change.Set(Models.ChangeState.None, Models.ChangeState.Deleted);
break;
case " R":
change.Set(Models.ChangeState.None, Models.ChangeState.Renamed);
break;
case " C":
change.Set(Models.ChangeState.None, Models.ChangeState.Copied);
break;
case "M":
change.Set(Models.ChangeState.Modified);
break;
case "MM":
change.Set(Models.ChangeState.Modified, Models.ChangeState.Modified);
break;
case "MT":
change.Set(Models.ChangeState.Modified, Models.ChangeState.TypeChanged);
break;
case "MD":
change.Set(Models.ChangeState.Modified, Models.ChangeState.Deleted);
break;
case "T":
change.Set(Models.ChangeState.TypeChanged);
break;
case "TM":
change.Set(Models.ChangeState.TypeChanged, Models.ChangeState.Modified);
break;
case "TT":
change.Set(Models.ChangeState.TypeChanged, Models.ChangeState.TypeChanged);
break;
case "TD":
change.Set(Models.ChangeState.TypeChanged, Models.ChangeState.Deleted);
break;
case "A":
change.Set(Models.ChangeState.Added);
break;
case "AM":
change.Set(Models.ChangeState.Added, Models.ChangeState.Modified);
break;
case "AT":
change.Set(Models.ChangeState.Added, Models.ChangeState.TypeChanged);
break;
case "AD":
change.Set(Models.ChangeState.Added, Models.ChangeState.Deleted);
break;
case "D":
change.Set(Models.ChangeState.Deleted);
break;
case "R":
change.Set(Models.ChangeState.Renamed);
break;
case "RM":
change.Set(Models.ChangeState.Renamed, Models.ChangeState.Modified);
break;
case "RT":
change.Set(Models.ChangeState.Renamed, Models.ChangeState.TypeChanged);
break;
case "RD":
change.Set(Models.ChangeState.Renamed, Models.ChangeState.Deleted);
break;
case "C":
change.Set(Models.ChangeState.Copied);
break;
case "CM":
change.Set(Models.ChangeState.Copied, Models.ChangeState.Modified);
break;
case "CT":
change.Set(Models.ChangeState.Copied, Models.ChangeState.TypeChanged);
break;
case "CD":
change.Set(Models.ChangeState.Copied, Models.ChangeState.Deleted);
break;
case "DD":
change.ConflictReason = Models.ConflictReason.BothDeleted;
change.Set(Models.ChangeState.None, Models.ChangeState.Conflicted);
break;
case "AU":
change.ConflictReason = Models.ConflictReason.AddedByUs;
change.Set(Models.ChangeState.None, Models.ChangeState.Conflicted);
break;
case "UD":
change.ConflictReason = Models.ConflictReason.DeletedByThem;
change.Set(Models.ChangeState.None, Models.ChangeState.Conflicted);
break;
case "UA":
change.ConflictReason = Models.ConflictReason.AddedByThem;
change.Set(Models.ChangeState.None, Models.ChangeState.Conflicted);
break;
case "DU":
change.ConflictReason = Models.ConflictReason.DeletedByUs;
change.Set(Models.ChangeState.None, Models.ChangeState.Conflicted);
break;
case "AA":
change.ConflictReason = Models.ConflictReason.BothAdded;
change.Set(Models.ChangeState.None, Models.ChangeState.Conflicted);
break;
case "UU":
change.ConflictReason = Models.ConflictReason.BothModified;
change.Set(Models.ChangeState.None, Models.ChangeState.Conflicted);
break;
case "??":
change.Set(Models.ChangeState.None, Models.ChangeState.Untracked);
break;
}
if (change.Index != Models.ChangeState.None || change.WorkTree != Models.ChangeState.None)
outs.Add(change);
}
}
catch
{
// Ignore exceptions.
}
return outs;

View File

@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading.Tasks;
namespace SourceGit.Commands
@@ -9,17 +10,30 @@ namespace SourceGit.Commands
{
WorkingDirectory = repo;
Context = repo;
Args = $"ls-tree -r -z --name-only {revision}";
Args = $"ls-tree -r --name-only {revision}";
}
public async Task<List<string>> GetResultAsync()
{
var rs = await ReadToEndAsync().ConfigureAwait(false);
if (!rs.IsSuccess)
return [];
var outs = new List<string>();
var lines = rs.StdOut.Split('\0', System.StringSplitOptions.RemoveEmptyEntries);
return [.. lines];
try
{
using var proc = new Process();
proc.StartInfo = CreateGitStartInfo(true);
proc.Start();
while (await proc.StandardOutput.ReadLineAsync() is { Length: > 0 } line)
outs.Add(line);
await proc.WaitForExitAsync().ConfigureAwait(false);
}
catch
{
// Ignore exceptions.
}
return outs;
}
}
}

View File

@@ -1,5 +1,5 @@
using System.Collections.Generic;
using System.IO;
using System.Diagnostics;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
@@ -23,38 +23,44 @@ namespace SourceGit.Commands
public async Task<List<Models.Object>> GetResultAsync()
{
var outs = new List<Models.Object>();
var rs = await ReadToEndAsync().ConfigureAwait(false);
if (rs.IsSuccess)
try
{
var sr = new StringReader(rs.StdOut);
while (sr.ReadLine() is { } line)
Parse(outs, line);
using var proc = new Process();
proc.StartInfo = CreateGitStartInfo(true);
proc.Start();
while (await proc.StandardOutput.ReadLineAsync() is { } line)
{
var match = REG_FORMAT().Match(line);
if (!match.Success)
continue;
var obj = new Models.Object();
obj.SHA = match.Groups[2].Value;
obj.Type = Models.ObjectType.Blob;
obj.Path = match.Groups[3].Value;
obj.Type = match.Groups[1].Value switch
{
"blob" => Models.ObjectType.Blob,
"tree" => Models.ObjectType.Tree,
"tag" => Models.ObjectType.Tag,
"commit" => Models.ObjectType.Commit,
_ => obj.Type,
};
outs.Add(obj);
}
await proc.WaitForExitAsync().ConfigureAwait(false);
}
catch
{
// Ignore exceptions.
}
return outs;
}
private void Parse(List<Models.Object> outs, string line)
{
var match = REG_FORMAT().Match(line);
if (!match.Success)
return;
var obj = new Models.Object();
obj.SHA = match.Groups[2].Value;
obj.Type = Models.ObjectType.Blob;
obj.Path = match.Groups[3].Value;
obj.Type = match.Groups[1].Value switch
{
"blob" => Models.ObjectType.Blob,
"tree" => Models.ObjectType.Tree,
"tag" => Models.ObjectType.Tag,
"commit" => Models.ObjectType.Commit,
_ => obj.Type,
};
outs.Add(obj);
}
}
}

View File

@@ -65,6 +65,14 @@ namespace SourceGit.Models
return SHA[..10];
}
public void ParseParents(string data)
{
if (data.Length < 8)
return;
Parents.AddRange(data.Split(' ', StringSplitOptions.RemoveEmptyEntries));
}
public void ParseDecorators(string data)
{
if (data.Length < 3)