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));
        }
Exemple #3
0
        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));
        }