using System.IO.Compression; using System.Text.Json.Serialization; using LibGit.Extensions; using LibGit.Interfaces; namespace LibGit; public class CommitObject { [JsonIgnore] public IRepoSource RepoSource { get; } public string CommitId { get; } public CommitObject(IRepoSource repoSource, string commitId) { RepoSource = repoSource; CommitId = commitId; } private const bool _debug = false; public string Length { get; set; } public string TreeId { get; set; } public List ParentIds { get; set; } = new(); public string Author { get; set; } //The Arcane Brony 1682901812 +0200 public string Committer { get; set; } public string GpgSignature { get; set; } public string Message { get; set; } public string Description { get; set; } public List Children { get; set; } = new(); [JsonIgnore] public DateTime CommitDate => new DateTime(long.Parse(Committer.Split(" ").TakeLast(2).First())*TimeSpan.TicksPerSecond) .AddHours(long.Parse(Committer.Split(" ").TakeLast(1).First()[1..3])) .AddMinutes(long.Parse(Committer.Split(" ").TakeLast(1).First()[3..])) .AddYears(1970); [JsonIgnore] public DateTime AuthorDate => new DateTime(long.Parse(Author.Split(" ").TakeLast(2).First())*TimeSpan.TicksPerSecond) .AddHours(long.Parse(Committer.Split(" ").TakeLast(1).First()[1..3])) .AddMinutes(long.Parse(Committer.Split(" ").TakeLast(1).First()[3..])) .AddYears(1970); [JsonIgnore] public string AuthorEmail => Author.Split(" <").Last().Split("> ").First(); [JsonIgnore] public string AuthorName => Author.Split(" <").First(); [JsonIgnore] public string CommitterEmail => Committer.Split(" <").Last().Split("> ").First(); [JsonIgnore] public string CommitterName => Committer.Split(" <").First(); public async Task GetTreeAsync() => new TreeObject(RepoSource, TreeId).ReadFromZlibCompressedObjFile(await RepoSource.GetObjectStreamById(TreeId)); public CommitObject ReadFromZlibCompressedObjFile(Stream bytes) { if (_debug) { Console.WriteLine($"Decompressing {this.GetType().Name}"); Console.WriteLine($"File header:"); // bytes.Peek(64).HexDump(16); } using ZLibStream stream = new ZLibStream(bytes, CompressionMode.Decompress); using var result = new MemoryStream(); stream.CopyTo(result); stream.Flush(); stream.Close(); return ReadFromDecompressedObjFile(result); } public CommitObject ReadFromDecompressedObjFile(Stream data) { if(_debug) Console.WriteLine("Parsing commit object"); // var data = new Queue(bytes.ToArray()); int iters = 0; data.Seek(0, SeekOrigin.Begin); if(_debug)Console.WriteLine($"Iteration {iters}: starting pos: {data.Position}/+{data.Remaining()}/{data.Length}"); Length = data.ReadNullTerminatedField(asciiPrefix: "commit ").AsString(); while (data.Position < data.Length) { if(_debug) Console.WriteLine($"Iteration {iters} ({data.Position}/+{data.Remaining()}/{data.Length})"); if (data.StartsWith("tree ")) TreeId = data.ReadTerminatedField((byte)'\n', asciiPrefix: "tree ").AsString(); while (data.StartsWith("parent ")) ParentIds.Add(data.ReadTerminatedField((byte)'\n', asciiPrefix: "parent ").AsString()); if (data.StartsWith("author ")) Author = data.ReadTerminatedField((byte)'\n', asciiPrefix: "author ").AsString(); if (data.StartsWith("committer ")) Committer = data.ReadTerminatedField((byte)'\n', asciiPrefix: "committer ").AsString();; if (data.StartsWith("gpgsig ")) GpgSignature = GetGpgSignature(data); if(data.Remaining() >= 1 && (data.Peek() == (byte)'\n' || data.Peek() == (byte)'\r')) data.Skip(1); if(data.Peek() != (byte)'\n') Message = GetMessage(data); while(data.Remaining() >= 1 && (data.Peek() == (byte)'\n' || data.Peek() == (byte)'\r')) data.Skip(1); if(data.Remaining() >= 1 && data.Peek() != (byte)'\n') Description = GetDescription(data); if (data.Remaining() > 0) { Console.WriteLine($"--Unparsed data after {++iters} iteration(s) of parsing CommitObject--"); Console.WriteLine(this.ToJson()); Console.WriteLine("--HexDump of remaining data--"); data.Peek(data.Remaining()).HexDump(); //Console.WriteLine($"Unparsed data: {Encoding.UTF8.GetString(data.ToArray())}"); } if(iters > 100) throw new Exception("Too many iterations"); } data.Close(); return this; } //parsing private string GetMessage(Stream data) { if(_debug) Console.WriteLine($"--commit.GetMessage--"); var message = ""; while (data.Remaining() > 0 && data.Peek() != (byte)'\n') { if(_debug) Console.WriteLine($"Commit.GetMessage -- pos: {data.Position}/+{data.Remaining()}/{data.Length} | next: {(char)data.Peek()} | Message: {message}"); message += (char)data.ReadByte(); } // if(data.Count > 0 && data.Peek() == (byte)'\n') // data.Dequeue(); return message.Trim(); } private string GetDescription(Stream data) { if(_debug) Console.WriteLine($"--commit.GetDescription--"); var message = ""; while (data.Remaining() > 0) { message += (char)data.ReadByte(); } // if(data.Count > 0 && data.Peek() == (byte)'\n') // data.Dequeue(); return message.Trim(); } private string GetGpgSignature(Stream data) { if(_debug) Console.WriteLine($"--commit.GetGpgSignature--"); data.Seek(7, SeekOrigin.Current); var signature = ""; while ( !signature.EndsWith("-----END PGP SIGNATURE-----\n\n") && !signature.EndsWith("-----END PGP SIGNATURE-----\n \n") && !signature.EndsWith("-----END SSH SIGNATURE-----\n\n") ) { // Console.Write((char)data.Peek()); signature += (char)data.ReadByte(); } return signature; } }