-
-
Notifications
You must be signed in to change notification settings - Fork 167
/
GitPackReader.cs
117 lines (90 loc) · 3.79 KB
/
GitPackReader.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
#nullable enable
using System;
using System.Buffers.Binary;
using System.Diagnostics;
using System.IO;
namespace Nerdbank.GitVersioning.ManagedGit
{
internal static class GitPackReader
{
private static readonly byte[] Signature = GitRepository.Encoding.GetBytes("PACK");
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);
}
private static (GitPackObjectType, long) ReadObjectHeader(Stream stream)
{
Span<byte> value = stackalloc byte[1];
stream.Read(value);
var type = (GitPackObjectType)((value[0] & 0b0111_0000) >> 4);
long length = value[0] & 0b_1111;
if ((value[0] & 0b1000_0000) == 0)
{
return (type, length);
}
int shift = 4;
do
{
stream.Read(value);
length = length | ((value[0] & (long)0b0111_1111) << shift);
shift += 7;
} while ((value[0] & 0b1000_0000) != 0);
return (type, length);
}
private static long ReadVariableLengthInteger(Stream stream)
{
long offset = -1;
int b;
do
{
offset++;
b = stream.ReadByte();
offset = (offset << 7) + (b & 127);
}
while ((b & (byte)128) != 0);
return offset;
}
}
}