summary refs log tree commit diff
path: root/LibGit/CommitObject.cs
blob: c2107df8451bc426115f4b6ae04514ab2820d5f6 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
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<string> ParentIds { get; set; } = new();

    public string Author { get; set; }

    //The Arcane Brony <myrainbowdash949@gmail.com> 1682901812 +0200
    public string Committer { get; set; }
    public string GpgSignature { get; set; }
    public string Message { get; set; }
    public string Description { get; set; }

    public List<CommitObject> 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<TreeObject> 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<byte>(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;
    }
}