public static GitObjectId FindNode(Stream stream, ReadOnlySpan <byte> name) { byte[] buffer = ArrayPool <byte> .Shared.Rent((int)stream.Length); Span <byte> contents = new Span <byte>(buffer, 0, (int)stream.Length); stream.ReadAll(contents); GitObjectId value = GitObjectId.Empty; while (contents.Length > 0) { // Format: [mode] [file/ folder name]\0[SHA - 1 of referencing blob or tree] // Mode is either 6-bytes long (directory) or 7-bytes long (file). // If the entry is a file, the first byte is '1' var fileNameEnds = contents.IndexOf((byte)0); bool isFile = contents[0] == (byte)'1'; var modeLength = isFile ? 7 : 6; var currentName = contents.Slice(modeLength, fileNameEnds - modeLength); if (currentName.SequenceEqual(name)) { value = GitObjectId.Parse(contents.Slice(fileNameEnds + 1, 20)); break; } else { contents = contents.Slice(fileNameEnds + 1 + 20); } } ArrayPool <byte> .Shared.Return(buffer); return(value); }
public long?GetOffset(GitObjectId objectId) { this.Initialize(); Span <byte> buffer = stackalloc byte[4]; Span <byte> objectName = stackalloc byte[20]; objectId.CopyTo(objectName); var packStart = this.fanoutTable[objectName[0]]; var packEnd = this.fanoutTable[objectName[0] + 1]; var objectCount = this.fanoutTable[256]; // The fanout table is followed by a table of sorted 20-byte SHA-1 object names. // These are packed together without offset values to reduce the cache footprint of the binary search for a specific object name. // The object names start at: 4 (header) + 4 (version) + 256 * 4 (fanout table) + 20 * (packStart) // and end at 4 (header) + 4 (version) + 256 * 4 (fanout table) + 20 * (packEnd) this.stream.Seek(4 + 4 + 256 * 4 + 20 * packStart, SeekOrigin.Begin); var i = 0; var order = 0; var tableSize = 20 * (packEnd - packStart + 1); byte[] table = ArrayPool <byte> .Shared.Rent(tableSize); this.stream.Seek(4 + 4 + 256 * 4 + 20 * packStart, SeekOrigin.Begin); this.stream.Read(table.AsSpan(0, tableSize)); Span <byte> current = stackalloc byte[20]; int originalPackStart = packStart; packEnd -= originalPackStart; packStart = 0; while (packStart <= packEnd) { i = (packStart + packEnd) / 2; order = table.AsSpan(20 * i, 20).SequenceCompareTo(objectName); if (order < 0) { packStart = i + 1; } else if (order > 0) { packEnd = i - 1; } else { break; } } ArrayPool <byte> .Shared.Return(table); if (order != 0) { return(null); } // Get the offset value. It's located at: // 4 (header) + 4 (version) + 256 * 4 (fanout table) + 20 * objectCount (SHA1 object name table) + 4 * objectCount (CRC32) + 4 * i (offset values) this.stream.Seek(4 + 4 + 256 * 4 + 20 * objectCount + 4 * objectCount + 4 * (i + originalPackStart), SeekOrigin.Begin); this.stream.ReadAll(buffer); // If the most significant bit is not set we have a 4-byte offset if (buffer[0] < 128) { var offset = BinaryPrimitives.ReadInt32BigEndian(buffer); return(offset); } // 8-byte offset buffer[0] &= 0x7f; var offset64 = BinaryPrimitives.ReadInt32BigEndian(buffer); Span <byte> buffer64 = stackalloc byte[8]; // 4 (header) + 4 (version) + 256 * 4 (fanout table) + 20 * objectCount (SHA1 object name table) + 4 * objectCount (CRC32) + 4 * objectCount (offset values) + 8 * offset64 this.stream.Seek(4 + 4 + 256 * 4 + 20 * objectCount + 4 * objectCount + 4 * objectCount + 8 * offset64, SeekOrigin.Begin); this.stream.ReadAll(buffer64); return(BinaryPrimitives.ReadInt64BigEndian(buffer64)); }
public static Stream GetObject(GitPack pack, Stream stream, long offset, string objectType, GitPackObjectType packObjectType) { if (pack == null) { throw new ArgumentNullException(nameof(pack)); } if (stream == null) { throw new ArgumentNullException(nameof(stream)); } // Read the signature #if DEBUG stream.Seek(0, SeekOrigin.Begin); Span <byte> buffer = stackalloc byte[12]; stream.ReadAll(buffer); Debug.Assert(buffer.Slice(0, 4).SequenceEqual(Signature)); var versionNumber = BinaryPrimitives.ReadInt32BigEndian(buffer.Slice(4, 4)); Debug.Assert(versionNumber == 2); var numberOfObjects = BinaryPrimitives.ReadInt32BigEndian(buffer.Slice(8, 4)); #endif stream.Seek(offset, SeekOrigin.Begin); var(type, decompressedSize) = ReadObjectHeader(stream); if (type == GitPackObjectType.OBJ_OFS_DELTA) { var baseObjectRelativeOffset = ReadVariableLengthInteger(stream); var baseObjectOffset = (long)(offset - baseObjectRelativeOffset); var deltaStream = GitObjectStream.Create(stream, decompressedSize); int baseObjectlength = ReadMbsInt(deltaStream); int targetLength = ReadMbsInt(deltaStream); var baseObjectStream = pack.GetObject(baseObjectOffset, objectType); return(new GitPackDeltafiedStream(baseObjectStream, deltaStream, targetLength)); } else if (type == GitPackObjectType.OBJ_REF_DELTA) { Span <byte> baseObjectId = stackalloc byte[20]; stream.ReadAll(baseObjectId); Stream baseObject = pack.Repository.GetObjectBySha(GitObjectId.Parse(baseObjectId), objectType, seekable: true); var deltaStream = GitObjectStream.Create(stream, decompressedSize); int baseObjectlength = ReadMbsInt(deltaStream); int targetLength = ReadMbsInt(deltaStream); return(new GitPackDeltafiedStream(baseObject, deltaStream, targetLength)); } // Tips for handling deltas: https://github.com/choffmeister/gitnet/blob/4d907623d5ce2d79a8875aee82e718c12a8aad0b/src/GitNet/GitPack.cs if (type != packObjectType) { throw new GitException(); } return(GitObjectStream.Create(stream, decompressedSize)); }