diff --git a/src/Commands/CompareRevisions.cs b/src/Commands/CompareRevisions.cs index 7951e6ad..853db182 100644 --- a/src/Commands/CompareRevisions.cs +++ b/src/Commands/CompareRevisions.cs @@ -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> ReadAsync() { var changes = new List(); - 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 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; } } } diff --git a/src/Commands/Diff.cs b/src/Commands/Diff.cs index ee5433ac..ffd60d4e 100644 --- a/src/Commands/Diff.cs +++ b/src/Commands/Diff.cs @@ -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 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) { diff --git a/src/Commands/QueryCommits.cs b/src/Commands/QueryCommits.cs index 311406bd..329bd4da 100644 --- a/src/Commands/QueryCommits.cs +++ b/src/Commands/QueryCommits.cs @@ -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> 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(); + 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 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(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 _commits = new List(); - private Models.Commit _current = null; private bool _findFirstMerged = false; private bool _isHeadFound = false; } diff --git a/src/Commands/QueryCommitsForInteractiveRebase.cs b/src/Commands/QueryCommitsForInteractiveRebase.cs index fc9dda04..62f84c0b 100644 --- a/src/Commands/QueryCommitsForInteractiveRebase.cs +++ b/src/Commands/QueryCommitsForInteractiveRebase.cs @@ -17,9 +17,15 @@ namespace SourceGit.Commands public async Task> GetResultAsync() { + var commits = new List(); 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 _commits = []; - private Models.InteractiveCommit _current = null; private readonly string _boundary; } } diff --git a/src/Commands/QueryLocalChanges.cs b/src/Commands/QueryLocalChanges.cs index 9605014d..c83a3824 100644 --- a/src/Commands/QueryLocalChanges.cs +++ b/src/Commands/QueryLocalChanges.cs @@ -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> GetResultAsync() { var outs = new List(); - 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; diff --git a/src/Commands/QueryRevisionFileNames.cs b/src/Commands/QueryRevisionFileNames.cs index 74753412..72d61ed9 100644 --- a/src/Commands/QueryRevisionFileNames.cs +++ b/src/Commands/QueryRevisionFileNames.cs @@ -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> GetResultAsync() { - var rs = await ReadToEndAsync().ConfigureAwait(false); - if (!rs.IsSuccess) - return []; + var outs = new List(); - 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; } } } diff --git a/src/Commands/QueryRevisionObjects.cs b/src/Commands/QueryRevisionObjects.cs index 9657c7c7..2206b4ac 100644 --- a/src/Commands/QueryRevisionObjects.cs +++ b/src/Commands/QueryRevisionObjects.cs @@ -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> GetResultAsync() { var outs = new List(); - 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 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); - } } } diff --git a/src/Models/Commit.cs b/src/Models/Commit.cs index 61438424..584694de 100644 --- a/src/Models/Commit.cs +++ b/src/Models/Commit.cs @@ -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)