Beispiel #1
0
        public static GitTree Read(Stream stream, GitObjectId objectId)
        {
            byte[] buffer = ArrayPool <byte> .Shared.Rent((int)stream.Length);

#if DEBUG
            Array.Clear(buffer, 0, buffer.Length);
#endif

            GitTree value = new GitTree()
            {
                Sha = objectId,
            };

            try
            {
                Span <byte> contents = buffer.AsSpan(0, (int)stream.Length);
                stream.ReadAll(contents);

                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);
                    var currentObjectId = GitObjectId.Parse(contents.Slice(fileNameEnds + 1, 20));

                    var name = GitRepository.GetString(currentName);

                    value.Children.Add(
                        name,
                        new GitTreeEntry(name, isFile, currentObjectId));

                    contents = contents.Slice(fileNameEnds + 1 + 20);
                }
            }
            finally
            {
                ArrayPool <byte> .Shared.Return(buffer);
            }

            return(value);
        }
Beispiel #2
0
        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);
        }
Beispiel #3
0
        public GitObjectId?Lookup(string objectish)
        {
            bool skipObjectIdLookup = false;

            if (objectish == "HEAD")
            {
                var reference = this.GetHeadAsReferenceOrSha();
                if (reference is GitObjectId headObjectId)
                {
                    return(headObjectId);
                }

                objectish = (string)reference;
            }

            var possibleLooseFileMatches = new List <string>();

            if (objectish.StartsWith("refs/", StringComparison.Ordinal))
            {
                // Match on loose ref files by their canonical name.
                possibleLooseFileMatches.Add(Path.Combine(this.CommonDirectory, objectish));
                skipObjectIdLookup = true;
            }
            else
            {
                // Look for simple names for branch or tag.
                possibleLooseFileMatches.Add(Path.Combine(this.CommonDirectory, "refs", "heads", objectish));
                possibleLooseFileMatches.Add(Path.Combine(this.CommonDirectory, "refs", "tags", objectish));
                possibleLooseFileMatches.Add(Path.Combine(this.CommonDirectory, "refs", "remotes", objectish));
            }

            if (possibleLooseFileMatches.FirstOrDefault(File.Exists) is string existingPath)
            {
                return(GitObjectId.Parse(File.ReadAllText(existingPath).TrimEnd()));
            }

            // Match in packed-refs file.
            string packedRefPath = Path.Combine(this.CommonDirectory, "packed-refs");

            if (File.Exists(packedRefPath))
            {
                using var refReader = File.OpenText(packedRefPath);
                string?line;
                while ((line = refReader.ReadLine()) is object)
                {
                    if (line.StartsWith("#", StringComparison.Ordinal))
                    {
                        continue;
                    }

                    string refName = line.Substring(41);
                    if (string.Equals(refName, objectish, StringComparison.Ordinal))
                    {
                        return(GitObjectId.Parse(line.Substring(0, 40)));
                    }
                    else if (!objectish.StartsWith("refs/", StringComparison.Ordinal))
                    {
                        // Not a canonical ref, so try heads and tags
                        if (string.Equals(refName, "refs/heads/" + objectish, StringComparison.Ordinal))
                        {
                            return(GitObjectId.Parse(line.Substring(0, 40)));
                        }
                        else if (string.Equals(refName, "refs/tags/" + objectish, StringComparison.Ordinal))
                        {
                            return(GitObjectId.Parse(line.Substring(0, 40)));
                        }
                        else if (string.Equals(refName, "refs/remotes/" + objectish, StringComparison.Ordinal))
                        {
                            return(GitObjectId.Parse(line.Substring(0, 40)));
                        }
                    }
                }
            }

            if (skipObjectIdLookup)
            {
                return(null);
            }

            if (objectish.Length == 40)
            {
                return(GitObjectId.Parse(objectish));
            }

            var possibleObjectIds = new List <GitObjectId>();

            if (objectish.Length > 2 && objectish.Length < 40)
            {
                // Search for _any_ object whose id starts with objectish in the object database
                var directory = Path.Combine(this.ObjectDirectory, objectish.Substring(0, 2));

                if (Directory.Exists(directory))
                {
                    var files = Directory.GetFiles(directory, $"{objectish.Substring(2)}*");

                    foreach (var file in files)
                    {
                        var objectId = $"{objectish.Substring(0, 2)}{Path.GetFileName(file)}";
                        possibleObjectIds.Add(GitObjectId.Parse(objectId));
                    }
                }

                // Search for _any_ object whose id starts with objectish in the packfile
                bool endsWithHalfByte = objectish.Length % 2 == 1;
                if (endsWithHalfByte)
                {
                    // Add one more character so hex can be converted to bytes.
                    // The bit length to be compared will not consider the last four bits.
                    objectish += "0";
                }

                if (objectish.Length <= 40 && objectish.Length % 2 == 0)
                {
                    Span <byte> decodedHex = stackalloc byte[objectish.Length / 2];
                    if (TryConvertHexStringToByteArray(objectish, decodedHex))
                    {
                        foreach (var pack in this.packs.Value.Span)
                        {
                            var objectId = pack.Lookup(decodedHex, endsWithHalfByte);

                            // It's possible for the same object to be present in both the object database and the pack files,
                            // or in multiple pack files.
                            if (objectId is not null && !possibleObjectIds.Contains(objectId.Value))
                            {
                                if (possibleObjectIds.Count > 0)
                                {
                                    // If objectish already resolved to at least one object which is different from the current
                                    // object id, objectish is not well-defined; so stop resolving and return null instead.
                                    return(null);
                                }
                                else
                                {
                                    possibleObjectIds.Add(objectId.Value);
                                }
                            }
                        }
                    }
                }
            }

            if (possibleObjectIds.Count == 1)
            {
                return(possibleObjectIds[0]);
            }

            return(null);
        }
Beispiel #4
0
        public static Stream GetObject(GitPack pack, Stream stream, long offset, string objectType, GitPackObjectType packObjectType)
        {
            if (pack is null)
            {
                throw new ArgumentNullException(nameof(pack));
            }

            if (stream is 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);
                long baseObjectOffset         = offset - baseObjectRelativeOffset;

                var deltaStream      = new ZLibStream(stream, decompressedSize);
                var baseObjectStream = pack.GetObject(baseObjectOffset, objectType);

                return(new GitPackDeltafiedStream(baseObjectStream, deltaStream));
            }
            else if (type == GitPackObjectType.OBJ_REF_DELTA)
            {
                Span <byte> baseObjectId = stackalloc byte[20];
                stream.ReadAll(baseObjectId);

                Stream baseObject         = pack.GetObjectFromRepository(GitObjectId.Parse(baseObjectId), objectType) !;
                var    seekableBaseObject = new GitPackMemoryCacheStream(baseObject);

                var deltaStream = new ZLibStream(stream, decompressedSize);

                return(new GitPackDeltafiedStream(seekableBaseObject, deltaStream));
            }

            // Tips for handling deltas: https://github.com/choffmeister/gitnet/blob/4d907623d5ce2d79a8875aee82e718c12a8aad0b/src/GitNet/GitPack.cs
            if (type != packObjectType)
            {
                throw new GitException($"An object of type {objectType} could not be located at offset {offset}.")
                      {
                          ErrorCode = GitException.ErrorCodes.ObjectNotFound
                      };
            }

            return(new ZLibStream(stream, decompressedSize));
        }