summary refs log tree commit diff
diff options
context:
space:
mode:
authorRory& <root@rory.gay>2025-12-12 20:28:15 +0100
committerRory& <root@rory.gay>2025-12-12 20:28:15 +0100
commitd65325f1298bc456824dc68e5d34a4ac0d686e42 (patch)
tree6d7445236bf34c1c2b380bae511e227a6fefb2bc
parentread indexes and packs correctly (diff)
downloadGitTools-d65325f1298bc456824dc68e5d34a4ac0d686e42.tar.xz
Use bulk reads and more intrinsics HEAD master
-rw-r--r--GitStaticPageBuilder.sln.DotSettings.user8
-rw-r--r--LibGit/Extensions/StreamExtensions.cs101
-rw-r--r--LibGit/GitPack.cs229
-rw-r--r--LibGit/LibGit.csproj1
-rw-r--r--LibGitTest/LibGitTest.csproj1
-rw-r--r--LibGitTest/Program.cs7
-rw-r--r--LibGitTest/Test1.cs2
-rw-r--r--LibGitTest/Test3.cs2
-rw-r--r--LibGitTest/Test4.cs29
9 files changed, 237 insertions, 143 deletions
diff --git a/GitStaticPageBuilder.sln.DotSettings.user b/GitStaticPageBuilder.sln.DotSettings.user

index d2fd913..90e964e 100644 --- a/GitStaticPageBuilder.sln.DotSettings.user +++ b/GitStaticPageBuilder.sln.DotSettings.user
@@ -1,8 +1,16 @@ <wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation"> + <s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AArray_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F171934cfea6d47e7b448809a75c8111ddba800_003Fdc_003Fec70b72b_003FArray_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> + <s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AAvx2_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F171934cfea6d47e7b448809a75c8111ddba800_003F10_003Ff575a23a_003FAvx2_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> + <s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ABitConverter_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F171934cfea6d47e7b448809a75c8111ddba800_003F2c_003F3d0cc1cc_003FBitConverter_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> <s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ADeflateStream_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F3684b2f6cbc5487aaabe08de60bdfbc861000_003F1e_003Fbd0c2641_003FDeflateStream_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> <s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AExceptionDispatchInfo_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F171934cfea6d47e7b448809a75c8111ddba800_003F07_003F685e6f25_003FExceptionDispatchInfo_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> <s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AInflater_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F3684b2f6cbc5487aaabe08de60bdfbc861000_003F4c_003F22dee77c_003FInflater_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> + <s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AMemoryExtensions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F171934cfea6d47e7b448809a75c8111ddba800_003F38_003F09c92a09_003FMemoryExtensions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> + <s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ASpanHelpers_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F171934cfea6d47e7b448809a75c8111ddba800_003Fea_003F5fb3adee_003FSpanHelpers_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> <s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AStream_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F171934cfea6d47e7b448809a75c8111ddba800_003Fea_003F084b71d4_003FStream_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> + <s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AThrowHelper_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F171934cfea6d47e7b448809a75c8111ddba800_003F08_003F3251f443_003FThrowHelper_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> + <s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AThrowHelper_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F264933f5493344a9bd939d6f1fac156a83800_003F2c_003F32748566_003FThrowHelper_002Ecs_002Fz_003A2_002D1/@EntryIndexedValue">ForceIncluded</s:String> <s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AZLibStream_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F3684b2f6cbc5487aaabe08de60bdfbc861000_003F01_003F92d6c825_003FZLibStream_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> + <s:Boolean x:Key="/Default/Monitoring/Counters/=System_002ERuntime/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/UnloadedProject/UnloadedProjects/=7c42b766_002Dc2c9_002D44cf_002D997d_002Dc52e614ce906_0023GitStaticPageBuilder/@EntryIndexedValue">True</s:Boolean> </wpf:ResourceDictionary> \ No newline at end of file diff --git a/LibGit/Extensions/StreamExtensions.cs b/LibGit/Extensions/StreamExtensions.cs
index 41e4b4f..90bd35a 100644 --- a/LibGit/Extensions/StreamExtensions.cs +++ b/LibGit/Extensions/StreamExtensions.cs
@@ -1,4 +1,5 @@ using System.IO.Compression; +using System.Runtime.InteropServices; namespace LibGit.Extensions; @@ -53,18 +54,25 @@ public static class StreamExtensions { if (!stream.CanRead) throw new InvalidOperationException("Can't read a non-readable stream"); - if (!stream.CanSeek) - throw new InvalidOperationException("Can't read a non-seekable stream"); - for (long i = 0; i < count; i++) + var buffer = new byte[count]; + var readCount = stream.Read(buffer, 0, (int)count); + for (int i = 0; i < readCount; i++) { - int read = stream.ReadByte(); - if (read == -1) - yield break; - yield return (byte)read; + yield return buffer[i]; } } + public static byte[] ReadBlock(this Stream stream, int count) + { + if (!stream.CanRead) + throw new InvalidOperationException("Can't read a non-readable stream"); + + Span<byte> buffer = stackalloc byte[count]; + stream.ReadExactly(buffer); + return buffer.ToArray(); + } + public static bool StartsWith(this Stream stream, IEnumerable<byte> sequence) { if (!stream.CanRead) @@ -172,13 +180,25 @@ public static class StreamExtensions if (!stream.CanRead) throw new InvalidOperationException("Can't read a non-readable stream"); - var bytes = stream.ReadBytes(4).ToArray(); + Span<byte> b = stackalloc byte[4]; + stream.ReadExactly(b); + return ((b[0] << 24) | (b[1] << 16) | (b[2] << 8) | b[3]); + } - if (BitConverter.IsLittleEndian) - Array.Reverse(bytes); + public static int[] MultiReadInt32BE(this Stream stream, int count) + { + if (!stream.CanRead) + throw new InvalidOperationException("Can't read a non-readable stream"); - // Console.WriteLine("ReadInt32BE: " + bytes.AsHexString() + " => " + BitConverter.ToInt32(bytes)); - return BitConverter.ToInt32(bytes); + Span<byte> b = stackalloc byte[4 * count]; + stream.ReadExactly(b); + int[] results = new int[count]; + for (int i = 0; i < count; i++) + { + results[i] = (b[i * 4 + 0] << 24) | (b[i * 4 + 1] << 16) | (b[i * 4 + 2] << 8) | b[i * 4 + 3]; + } + + return results; } public static uint ReadUInt32BE(this Stream stream) @@ -186,13 +206,25 @@ public static class StreamExtensions if (!stream.CanRead) throw new InvalidOperationException("Can't read a non-readable stream"); - var bytes = stream.ReadBytes(4).ToArray(); + Span<byte> b = stackalloc byte[4]; + stream.ReadExactly(b); + return (uint)((b[0] << 24) | (b[1] << 16) | (b[2] << 8) | b[3]); + } + + public static uint[] MultiReadUInt32BE(this Stream stream, int count) + { + if (!stream.CanRead) + throw new InvalidOperationException("Can't read a non-readable stream"); - if (BitConverter.IsLittleEndian) - Array.Reverse(bytes); + Span<byte> b = count >= 32767 ? new byte[4 * count] : stackalloc byte[4 * count]; + stream.ReadExactly(b); + uint[] results = new uint[count]; + for (int i = 0; i < count; i++) + { + results[i] = (uint)((b[i * 4 + 0] << 24) | (b[i * 4 + 1] << 16) | (b[i * 4 + 2] << 8) | b[i * 4 + 3]); + } - // Console.WriteLine("ReadUInt32BE: " + bytes.AsHexString() + " => " + BitConverter.ToUInt32(bytes)); - return BitConverter.ToUInt32(bytes); + return results; } public static ulong ReadUInt64BE(this Stream stream) @@ -200,13 +232,38 @@ public static class StreamExtensions if (!stream.CanRead) throw new InvalidOperationException("Can't read a non-readable stream"); - var bytes = stream.ReadBytes(8).ToArray(); + Span<byte> b = stackalloc byte[8]; + stream.ReadExactly(b); + return ((ulong)b[0] << 56) | ((ulong)b[1] << 48) | ((ulong)b[2] << 40) | ((ulong)b[3] << 32) | + ((ulong)b[4] << 24) | ((ulong)b[5] << 16) | ((ulong)b[6] << 8) | b[7]; + } - if (BitConverter.IsLittleEndian) - Array.Reverse(bytes); + public static ulong[] MultiReadUInt64BE(this Stream stream, int count) + { + if (!stream.CanRead) + throw new InvalidOperationException("Can't read a non-readable stream"); + + Span<byte> b = count >= 16384 ? new byte[8 * count] : stackalloc byte[8 * count]; + stream.ReadExactly(b); + ulong[] results = new ulong[count]; + for (int i = 0; i < count; i++) + { + results[i] = ((ulong)b[i * 8 + 0] << 56) | ((ulong)b[i * 8 + 1] << 48) | ((ulong)b[i * 8 + 2] << 40) | ((ulong)b[i * 8 + 3] << 32) | + ((ulong)b[i * 8 + 4] << 24) | ((ulong)b[i * 8 + 5] << 16) | ((ulong)b[i * 8 + 6] << 8) | b[i * 8 + 7]; + } + + return results; + } + + public static T[] MultiReadInlineArray<T>(this Stream stream, int count) where T : unmanaged + { + if (!stream.CanRead) + throw new InvalidOperationException("Can't read a non-readable stream"); - // Console.WriteLine("ReadUInt64BE: " + bytes.AsHexString() + " => " + BitConverter.ToUInt64(bytes)); - return BitConverter.ToUInt64(bytes); + Span<T> buffer = count >= 8192 ? new T[count] : stackalloc T[count]; + Span<byte> byteBuffer = MemoryMarshal.AsBytes(buffer); + stream.ReadExactly(byteBuffer); + return buffer.ToArray(); } //read variable length number diff --git a/LibGit/GitPack.cs b/LibGit/GitPack.cs
index bbb53d1..9e574ee 100644 --- a/LibGit/GitPack.cs +++ b/LibGit/GitPack.cs
@@ -1,5 +1,7 @@ +using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.IO.Compression; +using System.Runtime.CompilerServices; using LibGit.Extensions; namespace LibGit; @@ -7,6 +9,7 @@ namespace LibGit; // https://shafiul.github.io//gitbook/7_the_packfile.html - easier to digest than the git documentation public class GitPack { + private const bool _log = false; public string PackId { get; set; } public GitRepo Repo { get; set; } @@ -15,14 +18,17 @@ public class GitPack public GitPackIndex Index { get; set; } public List<GitPackObject> Objects { get; set; } = new(); - public GitPack Read(Stream packStream, Stream idxStream) + public GitPack Read(Stream packStream, Stream? idxStream = null, GitPackIndex? index = null) { - Console.Write(" Header: "); - packStream.Peek(04).ToArray()[0..].HexDump(4); - Console.Write("Version: "); - packStream.Peek(08).ToArray()[4..].HexDump(4); - Console.Write(" ObjCnt: "); - packStream.Peek(12).ToArray()[8..].HexDump(4); + if (_log) + { + Console.Write(" Header: "); + packStream.Peek(04).ToArray()[0..].HexDump(4); + Console.Write("Version: "); + packStream.Peek(08).ToArray()[4..].HexDump(4); + Console.Write(" ObjCnt: "); + packStream.Peek(12).ToArray()[8..].HexDump(4); + } if (!packStream.StartsWith("PACK")) throw new Exception("Invalid pack file header"); @@ -31,25 +37,37 @@ public class GitPack Version = packStream.ReadInt32BE(); ObjectCount = packStream.ReadInt32BE(); - Console.WriteLine($"Got git v{Version} pack with {ObjectCount} objects"); - Console.WriteLine("Reading index..."); + if (_log) + { + Console.WriteLine($"Got git v{Version} pack with {ObjectCount} objects"); + Console.WriteLine("Reading index..."); + } + Index = new GitPackIndex().Read(idxStream); - // Console.WriteLine("Pack index entries:"); - // foreach (var entry in Index.Entries.OrderByDescending(x => x.Offset)) - // { - // Console.WriteLine($" - {entry.Sha.AsHexString().Replace(" ", "").ToLower()} @ {entry.Offset}"); - // } - - // Console.WriteLine(string.Join("\n - ", Index.Entries.OrderByDescending(x => x.Offset).Select(x => $"{x.Sha.AsHexString().Replace(" ", "").ToLower()} @ {x.Offset}"))); - Console.WriteLine("Reading pack objects..."); + if (_log) Console.WriteLine("Reading pack objects..."); var ordered = Index.Entries.OrderBy(x => x.Offset).ToArray(); + // prevent spamming the console + var sw = Stopwatch.StartNew(); + var tsw = Stopwatch.StartNew(); for (int i = 0; i < ObjectCount; i++) { - // Console.WriteLine("Reading object " + (i + 1) + " of " + ObjectCount); - Objects.Add(new GitPackObject().Read(packStream, ordered[i].Offset)); + var obj = ordered[i]; + if (sw.ElapsedMilliseconds >= 50 || i == ObjectCount - 1) + { + Console.Write($"\r[{tsw.Elapsed}] Reading object {i + 1}/{ObjectCount} ({ordered[i].Sha}) @ {obj.Offset}"); + sw.Restart(); + } + + Objects.Add(new GitPackObject().ReadHeader(packStream, obj.Offset)); } + Console.WriteLine(); + + foreach (var group in Objects.GroupBy(x => x.ObjType)) + { + Console.WriteLine($" - {group.Key}: {group.Count()} objects"); + } return this; } @@ -63,17 +81,30 @@ public class GitPack public class GitPackIndex { + private const bool _log = false; public int Version { get; set; } public int[] fanOutTable = new int[256]; - public List<IndexEntry> Entries { get; set; } = new List<IndexEntry>(); - public Byte[] PackSHA { get; set; } = null!; - public Byte[] IndexSHA { get; set; } = null!; + public List<IndexEntry> Entries { get; set; } = []; + public Sha1Value PackSHA { get; set; } + public Sha1Value IndexSHA { get; set; } + + [InlineArray(20)] + public struct Sha1Value + { + private byte _e0; + + public override string ToString() + { + ReadOnlySpan<byte> bytes = this; + return Convert.ToHexStringLower(bytes); + } + } public struct IndexEntry { - public byte[] Sha { get; set; } - public uint Crc32 { get; set; } - public ulong Offset { get; set; } + public Sha1Value Sha; + public uint Crc32; + public ulong Offset; } public GitPackIndex Read(Stream stream) @@ -83,91 +114,48 @@ public class GitPackIndex stream.Skip(4); Version = stream.ReadInt32BE(); - Console.WriteLine($"Got git v{Version} pack index"); + if (_log) Console.WriteLine($"Got git v{Version} pack index"); - //fan-out table - for (int i = 0; i < 255; i++) - { - fanOutTable[i] = stream.ReadInt32BE(); - } + fanOutTable = stream.MultiReadInt32BE(255); var size = stream.ReadInt32BE(); // aka "fanout[255]" - Console.WriteLine($"Index contains {size} objects"); - - // Console.WriteLine("Fan-out table:"); - // var tableWidth = 8; - // if (Console.WindowWidth >= 320) tableWidth = 12; - // else if (Console.WindowWidth >= 240) tableWidth = 10; - // else if (Console.WindowWidth >= 160) tableWidth = 8; - // else if (Console.WindowWidth >= 80) tableWidth = 4; - // Console.WriteLine($"TW: {tableWidth}, CW: {Console.WindowWidth}"); - // for (int i = 0; i < 256; i++) - // { - // Console.Write($"[{i:X2}] {fanOutTable[i]:X8} ({fanOutTable[i].ToString(),8}) "); - // if ((i + 1) % tableWidth == 0) - // Console.WriteLine(); - // } - - // Console.WriteLine($"\t\t END OF TABLE @ {stream.Position}"); - - for (int i = 0; i < size; i++) - { - // sha list - var sha = stream.ReadBytes(20).ToArray(); - // Console.WriteLine($"OBJ {i:X4}: {sha.AsHexString()}"); - Entries.Add(new IndexEntry - { - Sha = sha - }); - } + if (_log) Console.WriteLine($"Index contains {size} objects"); + Entries = new List<IndexEntry>(new IndexEntry[size]); - for (int i = 0; i < size; i++) - { - // crc32 list - var crc = stream.ReadUInt32BE(); - // Console.WriteLine($"CRC {i:X4}: {crc:X8}"); - Entries[i] = new IndexEntry - { - Sha = Entries[i].Sha, - Crc32 = crc - }; - } + if (_log) Console.Write("Reading SHA1..."); + var sha1Values = stream.MultiReadInlineArray<Sha1Value>(size); + + if (_log) Console.Write(" CRC..."); + var crcValues = stream.MultiReadUInt32BE(size); + + if (_log) Console.Write(" OFS..."); + var raw32 = stream.MultiReadUInt32BE(size); // uint[] + var raw64 = stream.MultiReadUInt64BE((int)((stream.Remaining() - 40) / sizeof(ulong))); - for (int i = 0; i < size; i++) + if (_log) Console.WriteLine(" Hashes..."); + var h = new Sha1Value(); + stream.ReadBlock(20).CopyTo(h); + PackSHA = h; + if (_log) Console.WriteLine($"Pack SHA: {PackSHA}"); + + h = new Sha1Value(); + stream.ReadBlock(20).CopyTo(h); + IndexSHA = h; + if (_log) Console.WriteLine($"Index SHA: {IndexSHA}"); + + if (_log) Console.WriteLine("Constructing entries..."); + for (var i = 0; i < size; i++) { - // offset list - var offset = stream.ReadInt32BE(); - // Console.WriteLine($"OFF {i:X4}: {offset}"); Entries[i] = new IndexEntry { - Sha = Entries[i].Sha, - Crc32 = Entries[i].Crc32, - Offset = (uint)offset + Sha = sha1Values[i], + Crc32 = crcValues[i], + Offset = (raw32[i] & 0x80000000) == 0 + ? raw32[i] + : raw64[raw32[i] & 0x7FFFFFFF] }; } - // for (int i = 0; i < size; i++) - // { - // Console.WriteLine($"ENTRY {i:X4}: {Entries[i].Sha.AsHexString()} | CRC32: {Entries[i].Crc32:X8} | OFF: {Entries[i].Offset}"); - // } - - if (stream.Remaining() > 20) - for (int i = 0; i < size; i++) - { - var entry = Entries[i]; - if ((entry.Offset & 0x80000000) == 0) continue; - - var largeOffset = stream.ReadUInt64BE(); - Console.WriteLine($"LARGE OFF {i:X4}: {largeOffset} (idx: {i})"); - Entries[i] = entry with { Offset = largeOffset }; - // Thread.Sleep(10); - } - - PackSHA = stream.ReadBytes(20).ToArray(); - Console.WriteLine($"Pack SHA: {PackSHA.AsHexString()}"); - IndexSHA = stream.ReadBytes(20).ToArray(); - Console.WriteLine($"Index SHA: {IndexSHA.AsHexString()}"); - return this; } } @@ -176,24 +164,25 @@ public class GitPackObject { private const bool _debug = false; - public GitPackObject Read(Stream stream, ulong offset) + public GitPackObject ReadHeader(Stream stream, ulong offset) { + Offset = offset; stream.Seek((long)offset, SeekOrigin.Begin); if (_debug) Console.WriteLine($"Reading pack object at offset {offset}, stream position {stream.Position}"); var headerPos = stream.Position; - var data = stream.ReadBytes(1).First(); + var data = stream.ReadByte(); if (_debug) Console.WriteLine($"data: {data:X8} ({data}/{data:b8})"); - + //format: 1 bit continue, 3 bits type, 4 bits size (A), continued by up to 3 more bytes of size (B, C and D), A is least significant ObjType = (GitObjectType)((data >> 4) & 0b0000_0111); var sizeBits = data & 0b0000_1111; // Lower 4 bits are the initial size var restOfSize = (data & 0b1000_0000) != 0 ? stream.ReadVLQ() : 0; UncompressedSize = (restOfSize << 4) | sizeBits; - + // handle delta objects if (ObjType == GitObjectType.RefDelta) { - RefDeltaBaseObjectId = stream.ReadBytes(20).ToArray(); + RefDeltaBaseObjectId = stream.ReadBlock(20); if (_debug) Console.WriteLine($"Ref delta base object id: {RefDeltaBaseObjectId.AsHexString()}"); } else if (ObjType == GitObjectType.OffsDelta) @@ -201,20 +190,27 @@ public class GitPackObject OffsDeltaBaseOffset = stream.ReadGitPackOffsetModifiedVLQ(); if (_debug) Console.WriteLine($"Offset delta base offset: {OffsDeltaBaseOffset}"); } - - var dataPos = stream.Position; - if (_debug) Console.WriteLine($"pack objType: {ObjType} ({(int)ObjType}), uncompressed size: {UncompressedSize}, position: HDR={headerPos}, DATA={dataPos}"); - // stream.Peek(Size).Take(16).ToArray().HexDump(16); - // stream.Skip(Size); + + // var dataPos = stream.Position; + // if (_debug) Console.WriteLine($"pack objType: {ObjType} ({(int)ObjType}), uncompressed size: {UncompressedSize}, position: HDR={headerPos}, DATA={dataPos}"); + + DataOffset = (ulong) stream.Position; + + return this; + } + + public GitPackObject Read(Stream stream, ulong offset) + { + ReadHeader(stream, offset); try { using var zlibStream = new ZLibStream(stream, CompressionMode.Decompress, true); - var decompressedData = new byte[UncompressedSize]; + var decompressedData = new Span<byte>(new byte[UncompressedSize]); int totalRead = 0; while (totalRead < UncompressedSize) { - int bytesRead = zlibStream.Read(decompressedData, totalRead, UncompressedSize - totalRead); + int bytesRead = zlibStream.Read(decompressedData); //, totalRead, UncompressedSize - totalRead); if (bytesRead == 0) throw new Exception("Unexpected end of zlib stream"); totalRead += bytesRead; @@ -223,25 +219,28 @@ public class GitPackObject catch (Exception ex) { Console.WriteLine($"Error during zlib decompression: {ex.Message}"); - stream.Seek(headerPos - 5, SeekOrigin.Begin); + stream.Seek((long)Offset, SeekOrigin.Begin); stream.Peek(32).HexDump(); throw; } - var endPos = stream.Position; - if (_debug) Console.WriteLine($"Decompressed data ({UncompressedSize} bytes/{endPos - dataPos} compressed, stream @ {endPos}):"); + // var endPos = stream.Position; + // if (_debug) Console.WriteLine($"Decompressed data ({UncompressedSize} bytes/{endPos - dataPos} compressed, stream @ {endPos}):"); // if (_debug) decompressedData.ToArray().HexDump(32); // Environment.Exit(1); return this; } + public ulong Offset { get; set; } + public ulong DataOffset { get; set; } + public GitObjectType ObjType { get; set; } public int UncompressedSize { get; set; } public byte[]? RefDeltaBaseObjectId { get; set; } - + public int? OffsDeltaBaseOffset { get; set; } } diff --git a/LibGit/LibGit.csproj b/LibGit/LibGit.csproj
index a55e5be..c87aeab 100644 --- a/LibGit/LibGit.csproj +++ b/LibGit/LibGit.csproj
@@ -4,6 +4,7 @@ <TargetFramework>net10.0</TargetFramework> <ImplicitUsings>enable</ImplicitUsings> <Nullable>enable</Nullable> + <AllowUnsafeBlocks>true</AllowUnsafeBlocks> </PropertyGroup> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'"> diff --git a/LibGitTest/LibGitTest.csproj b/LibGitTest/LibGitTest.csproj
index fddc46c..5f1d18e 100644 --- a/LibGitTest/LibGitTest.csproj +++ b/LibGitTest/LibGitTest.csproj
@@ -5,6 +5,7 @@ <TargetFramework>net10.0</TargetFramework> <ImplicitUsings>enable</ImplicitUsings> <Nullable>enable</Nullable> + <PublishAOT>true</PublishAOT> </PropertyGroup> <ItemGroup> diff --git a/LibGitTest/Program.cs b/LibGitTest/Program.cs
index 3d87631..18459c7 100644 --- a/LibGitTest/Program.cs +++ b/LibGitTest/Program.cs
@@ -1,11 +1,10 @@ // See https://aka.ms/new-console-template for more information -using LibGit; -using LibGit.Extensions; using LibGitTest; Console.WriteLine("Hello, World!"); -//await Test1.Run(); +// await Test1.Run(); //await Test2.Run(); -await Test3.Run(); \ No newline at end of file +// await Test3.Run(); +await Test4.Run(); \ No newline at end of file diff --git a/LibGitTest/Test1.cs b/LibGitTest/Test1.cs
index ebd6f7e..71de9df 100644 --- a/LibGitTest/Test1.cs +++ b/LibGitTest/Test1.cs
@@ -8,7 +8,7 @@ public class Test1 public static async Task Run() { Console.WriteLine("Test1 running"); - var repo = new GitRepo(new FileRepoSource(@"/home/Rory/git/matrix/MatrixRoomUtils.git")); + var repo = new GitRepo(new FileRepoSource(@"/home/Rory/git/spacebar/server-master/.git")); // var repo = new GitRepo(new WebRepoSource("https://git.rory.gay/MatrixRoomUtils.git/")); var commit = await repo.GetCommit("HEAD"); diff --git a/LibGitTest/Test3.cs b/LibGitTest/Test3.cs
index 24d6986..fee7230 100644 --- a/LibGitTest/Test3.cs +++ b/LibGitTest/Test3.cs
@@ -7,7 +7,7 @@ public class Test3 public static async Task Run() { Console.WriteLine("Test3 running"); - var repo = new GitRepo(new FileRepoSource(@"/home/Rory/git/spacebar/server-master/.git")); + var repo = new GitRepo(new FileRepoSource(@"/home/Rory/git/nixpkgs-Draupnir/.git")); var packs = repo.GetPacks().GetAsyncEnumerator(); int count = 0; while(await packs.MoveNextAsync()) diff --git a/LibGitTest/Test4.cs b/LibGitTest/Test4.cs new file mode 100644
index 0000000..5a9e47f --- /dev/null +++ b/LibGitTest/Test4.cs
@@ -0,0 +1,29 @@ +using LibGit; + +namespace LibGitTest; + +public class Test4 +{ + public static async Task Run() + { + Console.WriteLine("Test4 running"); + var repo = new GitRepo(new FileRepoSource(@"/home/Rory/git/spacebar/server-master/.git")); + var packs = repo.GetPacks().GetAsyncEnumerator(); + int count = 0; + if (Directory.Exists("out-git")) + Directory.Delete("out-git", true); + Directory.CreateDirectory("out-git"); + Directory.CreateDirectory("out-git/objects"); + while (await packs.MoveNextAsync()) + { + count += packs.Current.ObjectCount; + foreach (var gitPackObject in packs.Current.Index.Entries) + { + var item = packs.Current.Objects.First(x => x.Offset == gitPackObject.Offset); + Console.WriteLine($"{item.ObjType}"); + } + } + + Console.WriteLine($"Read {count} objects from pack files."); + } +} \ No newline at end of file