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); }
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 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); }
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)); }